22using System . Collections . Generic ;
33using System . Linq ;
44using System . Threading . Tasks ;
5+ using BepInEx . Logging ;
56using HarmonyLib ;
7+ using Polytopia . Data ;
68
7- namespace PolyMod . Managers
9+ namespace PolyMod . Managers ;
10+
11+ public static class Multiplayer
812{
9- public static class Multiplayer
13+ internal const string DEFAULT_SERVER_URL = "https://dev.polydystopia.xyz" ;
14+ private const string GldMarker = "##GLD:" ;
15+
16+ // Cache parsed GLD by game Seed to handle rewinds/reloads
17+ private static readonly Dictionary < int , GameLogicData > _gldCache = new ( ) ;
18+ private static readonly Dictionary < int , int > _versionCache = new ( ) ; // Seed -> modGldVersion
19+
20+ internal static void Init ( )
1021 {
11- internal static void Init ( )
12- {
13- Harmony . CreateAndPatchAll ( typeof ( Multiplayer ) ) ;
14- BuildConfigHelper . GetSelectedBuildConfig ( ) . buildServerURL = BuildServerURL . Custom ;
15- BuildConfigHelper . GetSelectedBuildConfig ( ) . customServerURL = Plugin . config . backendUrl ;
16- }
22+ Harmony . CreateAndPatchAll ( typeof ( Multiplayer ) ) ;
23+ BuildConfig buildConfig = BuildConfigHelper . GetSelectedBuildConfig ( ) ;
24+ buildConfig . buildServerURL = BuildServerURL . Custom ;
25+ buildConfig . customServerURL = Plugin . config . backendUrl ;
26+
27+ Plugin . logger . LogInfo ( $ "Polydystopia> Server URL set to: { Plugin . config . backendUrl } ") ;
28+ Plugin . logger . LogInfo ( "Polydystopia> GLD patches applied" ) ;
29+ }
30+
31+ [ HarmonyPostfix ]
32+ [ HarmonyPatch ( typeof ( MultiplayerScreen ) , nameof ( MultiplayerScreen . Show ) ) ]
33+ public static void MultiplayerScreen_Show ( MultiplayerScreen __instance )
34+ {
35+ __instance . multiplayerSelectionScreen . TournamentsButton . gameObject . SetActive ( false ) ;
36+ }
37+
1738
18- [ HarmonyPostfix ]
19- [ HarmonyPatch ( typeof ( MultiplayerScreen ) , nameof ( MultiplayerScreen . Show ) ) ]
20- public static void MultiplayerScreen_Show ( MultiplayerScreen __instance )
39+ [ HarmonyPostfix ]
40+ [ HarmonyPatch ( typeof ( ProfileScreen ) , nameof ( ProfileScreen . Start ) ) ]
41+ public static void ProfileScreen_Start ( ProfileScreen __instance )
42+ {
43+ }
44+
45+ [ HarmonyPostfix ]
46+ [ HarmonyPatch ( typeof ( StartScreen ) , nameof ( StartScreen . Start ) ) ]
47+ private static void StartScreen_Start ( StartScreen __instance )
48+ {
49+ __instance . highscoreButton . gameObject . SetActive ( false ) ;
50+ __instance . weeklyChallengesButton . gameObject . SetActive ( false ) ;
51+ }
52+
53+ /// <summary>
54+ /// After GameState deserialization, check for trailing GLD version ID and set mockedGameLogicData.
55+ /// The server appends "##GLD:" + modGldVersion (int) after the normal serialized data.
56+ /// </summary>
57+ [ HarmonyPostfix ]
58+ [ HarmonyPatch ( typeof ( GameState ) , nameof ( GameState . Deserialize ) ) ]
59+ private static void Deserialize_Postfix ( GameState __instance , BinaryReader __0 )
60+ {
61+ Plugin . logger ? . LogDebug ( "Deserialize_Postfix: Entered" ) ;
62+
63+ try
2164 {
22- __instance . multiplayerSelectionScreen . TournamentsButton . gameObject . SetActive ( false ) ;
23- }
65+ var reader = __0 ;
66+ if ( reader == null )
67+ {
68+ Plugin . logger ? . LogWarning ( "Deserialize_Postfix: reader is null" ) ;
69+ return ;
70+ }
71+
72+ var position = reader . BaseStream . Position ;
73+ var length = reader . BaseStream . Length ;
74+ var remaining = length - position ;
2475
76+ Plugin . logger ? . LogDebug ( $ "Deserialize_Postfix: Stream position={ position } , length={ length } , remaining={ remaining } ") ;
2577
26- [ HarmonyPostfix ]
27- [ HarmonyPatch ( typeof ( ProfileScreen ) , nameof ( ProfileScreen . Start ) ) ]
28- public static void ProfileScreen_Start ( ProfileScreen __instance )
78+ // Check if there's more data after normal deserialization
79+ if ( position >= length )
80+ {
81+ Plugin . logger ? . LogDebug ( "Deserialize_Postfix: No trailing data (position >= length)" ) ;
82+
83+ var sd = __instance . Seed ;
84+ if ( _gldCache . TryGetValue ( sd , out var cachedGld ) )
85+ {
86+ __instance . mockedGameLogicData = cachedGld ;
87+ var cachedVersion = _versionCache . GetValueOrDefault ( sd , - 1 ) ;
88+ Plugin . logger ? . LogInfo ( $ "Deserialize_Postfix: Applied cached GLD for Seed={ sd } , ModGldVersion={ cachedVersion } ") ;
89+ }
90+ return ;
91+ }
92+
93+ Plugin . logger ? . LogDebug ( $ "Deserialize_Postfix: Found { remaining } bytes of trailing data, attempting to read marker") ;
94+
95+ var marker = reader . ReadString ( ) ;
96+ Plugin . logger ? . LogDebug ( $ "Deserialize_Postfix: Read marker string: '{ marker } '") ;
97+
98+ if ( marker != GldMarker )
99+ {
100+ Plugin . logger ? . LogDebug ( $ "Deserialize_Postfix: Marker mismatch - expected '{ GldMarker } ', got '{ marker } '") ;
101+ return ;
102+ }
103+
104+ Plugin . logger ? . LogInfo ( $ "Deserialize_Postfix: Found GLD marker '{ GldMarker } '") ;
105+
106+ var modGldVersion = reader . ReadInt32 ( ) ;
107+ Plugin . logger ? . LogInfo ( $ "Deserialize_Postfix: Found embedded ModGldVersion: { modGldVersion } ") ;
108+
109+ Plugin . logger ? . LogDebug ( $ "Deserialize_Postfix: Fetching GLD from server for version { modGldVersion } ") ;
110+ var gldJson = FetchGldById ( modGldVersion ) ;
111+ if ( string . IsNullOrEmpty ( gldJson ) )
112+ {
113+ Plugin . logger ? . LogError ( $ "Deserialize_Postfix: Failed to fetch GLD for ModGldVersion: { modGldVersion } ") ;
114+ return ;
115+ }
116+
117+ Plugin . logger ? . LogDebug ( $ "Deserialize_Postfix: Parsing GLD JSON ({ gldJson . Length } chars)") ;
118+
119+ var customGld = new GameLogicData ( ) ;
120+ customGld . Parse ( gldJson ) ;
121+ __instance . mockedGameLogicData = customGld ;
122+
123+ // Cache for subsequent deserializations (rewinds, reloads)
124+ var seed = __instance . Seed ;
125+ _gldCache [ seed ] = customGld ;
126+ _versionCache [ seed ] = modGldVersion ;
127+
128+ Plugin . logger ? . LogInfo ( $ "Deserialize_Postfix: Successfully set mockedGameLogicData from ModGldVersion: { modGldVersion } , cached for Seed={ seed } ") ;
129+ }
130+ catch ( EndOfStreamException )
131+ {
132+ Plugin . logger ? . LogDebug ( "Deserialize_Postfix: EndOfStreamException - no trailing data" ) ;
133+ }
134+ catch ( Exception ex )
29135 {
136+ Plugin . logger ? . LogError ( $ "Deserialize_Postfix: Exception: { ex . GetType ( ) . Name } : { ex . Message } ") ;
137+ Plugin . logger ? . LogDebug ( $ "Deserialize_Postfix: Stack trace: { ex . StackTrace } ") ;
30138 }
139+ }
140+
141+ /// <summary>
142+ /// Fetch GLD from server using ModGldVersion ID
143+ /// </summary>
144+ private static string ? FetchGldById ( int modGldVersion )
145+ {
146+ try
147+ {
148+ using var client = new HttpClient ( ) ;
149+ var url = $ "{ Plugin . config . backendUrl . TrimEnd ( '/' ) } /api/mods/gld/{ modGldVersion } ";
150+ Plugin . logger ? . LogDebug ( $ "FetchGldById: Requesting URL: { url } ") ;
151+
152+ var response = client . GetAsync ( url ) . Result ;
153+ Plugin . logger ? . LogDebug ( $ "FetchGldById: Response status: { response . StatusCode } ") ;
31154
32- [ HarmonyPostfix ]
33- [ HarmonyPatch ( typeof ( StartScreen ) , nameof ( StartScreen . Start ) ) ]
34- private static void StartScreen_Start ( StartScreen __instance )
155+ if ( response . IsSuccessStatusCode )
156+ {
157+ var gld = response . Content . ReadAsStringAsync ( ) . Result ;
158+ Plugin . logger ? . LogInfo ( $ "FetchGldById: Successfully fetched mod GLD ({ gld . Length } chars)") ;
159+ return gld ;
160+ }
161+
162+ var errorContent = response . Content . ReadAsStringAsync ( ) . Result ;
163+ Plugin . logger ? . LogError ( $ "FetchGldById: Failed with status { response . StatusCode } : { errorContent } ") ;
164+ }
165+ catch ( Exception ex )
35166 {
36- __instance . highscoreButton . gameObject . SetActive ( false ) ;
37- __instance . weeklyChallengesButton . gameObject . SetActive ( false ) ;
167+ Plugin . logger ? . LogError ( $ "FetchGldById: Exception: { ex . GetType ( ) . Name } : { ex . Message } ") ;
168+ if ( ex . InnerException != null )
169+ {
170+ Plugin . logger ? . LogError ( $ "FetchGldById: Inner exception: { ex . InnerException . Message } ") ;
171+ }
38172 }
173+ return null ;
39174 }
40- }
175+ }
0 commit comments