1+ using System ;
2+ using System . Collections ;
3+ using System . Diagnostics ;
4+ using System . IO ;
5+ using System . IO . Compression ;
6+ using System . Linq ;
7+ using System . Reflection ;
8+ using System . Security . Cryptography ;
9+ using AquaMai . Config . Attributes ;
10+ using AquaMai . Core . Helpers ;
11+ using AquaMai . Mods . Types ;
12+ using HarmonyLib ;
13+ using MelonLoader ;
14+ using MelonLoader . TinyJSON ;
15+ using Monitor ;
16+ using Process ;
17+ using UnityEngine ;
18+ using UnityEngine . Networking ;
19+
20+ namespace AquaMai . Mods . Enhancement ;
21+
22+ [ ConfigSection (
23+ defaultOn : true ,
24+ exampleHidden : true ,
25+ zh : "加载服务器下发的资源(如果支持)" ) ]
26+ public class ServerResources
27+ {
28+ [ ConfigEntry ]
29+ private static readonly string publicKey =
30+ "zJo+1YAJXHorMNSs0abC/qQchDX9J7um07mUp+jkYUVh9Y74IUKeSLlSoMrhardJJolQMcy+m7qtdx/xyTmp8pyANBi7xxgUB752SRhHBnK4XQhALd0WmTo4hE6deRHl/SlDxbfZM+c0fW4FMrHUFpHCy+JQoTvJPXkLQRfOCik=" ;
31+
32+ [ ConfigEntry ]
33+ private static readonly string cacheFile = "LocalAssets/ServerResources" ;
34+
35+ private const string FieldName = "_aquaMaiResources" ;
36+
37+ private class ServerResourcesEntry : ConditionalMessage
38+ {
39+ public string url ;
40+ public string sign ;
41+ }
42+
43+ private class ServerResourcesData
44+ {
45+ public ServerResourcesEntry [ ] entries = [ ] ;
46+ }
47+
48+ private static ServerResourcesEntry _entry ;
49+ public static AssetBundle bundle ;
50+ private static Download0rder _downloader ;
51+
52+ public static void OnBeforePatch ( )
53+ {
54+ NetPacketHook . OnNetPacketComplete += OnNetPacketComplete ;
55+ }
56+
57+ private static Variant OnNetPacketComplete ( string api , Variant _ , Variant response )
58+ {
59+ if ( bundle != null ) return null ;
60+ if ( _downloader != null ) return null ;
61+ if ( _entry != null ) return null ;
62+ if ( api != "GetGameSettingApi" || response is not ProxyObject obj ) return null ;
63+ var serverResourcesJson = obj . Keys . Contains ( FieldName ) ? obj [ FieldName ] : null ;
64+ if ( serverResourcesJson == null ) return null ;
65+
66+ var data = serverResourcesJson . Make < ServerResourcesData > ( ) ;
67+ _entry = data . entries . FirstOrDefault ( it => it . ShouldShow ( ) ) ;
68+ if ( _entry == null ) return null ;
69+ if ( File . Exists ( cacheFile ) )
70+ {
71+ var cached = File . ReadAllBytes ( cacheFile ) ;
72+ if ( VerifySignature ( cached , _entry . sign )
73+ #if DEBUG
74+ || File . Exists ( "LocalAssets/ServerResourcesDebug" )
75+ #endif
76+ )
77+ {
78+ LoadFile ( ) ;
79+ return null ;
80+ }
81+ #if DEBUG
82+ MelonLogger . Msg ( "[ServerResources] Invalid signature for existed file" ) ;
83+ #endif
84+ File . Delete ( cacheFile ) ;
85+ }
86+
87+ blinktimer . Start ( ) ;
88+ var go = new GameObject ( "[AquaMai] ServerResourcesDownloader" ) ;
89+ _downloader = go . AddComponent < Download0rder > ( ) ;
90+
91+ return null ;
92+ }
93+
94+ private class Download0rder : MonoBehaviour
95+ {
96+ private void Start ( )
97+ {
98+ StartCoroutine ( GetFile ( ) ) ;
99+ }
100+
101+ private IEnumerator GetFile ( )
102+ {
103+ yield return GetFileImpl ( ) ;
104+ Destroy ( this ) ;
105+ }
106+ }
107+
108+ private static byte _poweronState = 0 ;
109+ private static Stopwatch blinktimer = new Stopwatch ( ) ;
110+ [ HarmonyPrefix ]
111+ [ HarmonyPatch ( typeof ( PowerOnMonitor ) , nameof ( PowerOnMonitor . SetMainMessage ) ) ]
112+ public static void PreSetMainMessage ( ref string mmessage , ref string smessage )
113+ {
114+ if ( _poweronState < 4 ) return ;
115+ if ( _downloader == null ) return ;
116+ mmessage += "\n \n SERVER RESOURCES" ;
117+ if ( _poweronState == 255 )
118+ {
119+ smessage += Util . Utility . isBlinkDisp ( blinktimer ) ? "\n \n LOADING" : string . Empty ;
120+ }
121+ }
122+
123+ [ HarmonyPrefix ]
124+ [ HarmonyPatch ( typeof ( PowerOnProcess ) , "OnUpdate" ) ]
125+ public static void PrePowerOnStart ( ref byte ____state )
126+ {
127+ if ( ____state == 9 && _downloader != null )
128+ {
129+ ____state = 255 ;
130+ }
131+ if ( ____state == 255 && _downloader == null )
132+ {
133+ ____state = 9 ;
134+ }
135+ _poweronState = ____state ;
136+ }
137+
138+ private static IEnumerator GetFileImpl ( )
139+ {
140+ #if DEBUG
141+ yield return new WaitForSeconds ( 2 ) ; // DEBUG
142+ #endif
143+ using var webRequest = UnityWebRequest . Get ( _entry . url ) ;
144+ yield return webRequest . SendWebRequest ( ) ;
145+
146+ if ( webRequest . isNetworkError )
147+ {
148+ MelonLogger . Warning ( "[ServerResources] Unable to download file: " + webRequest . error ) ;
149+ yield break ;
150+ }
151+
152+ var bytes = webRequest . downloadHandler . data ;
153+ if ( ! VerifySignature ( bytes , _entry . sign ) )
154+ {
155+ MelonLogger . Warning ( "[ServerResources] Invalid signature for file" ) ;
156+ // yield break;
157+ }
158+
159+ File . WriteAllBytes ( cacheFile , bytes ) ;
160+ LoadFile ( ) ;
161+ }
162+
163+ private static void LoadFile ( )
164+ {
165+ bundle = AssetBundle . LoadFromFile ( Path . Combine ( Environment . CurrentDirectory , cacheFile ) ) ;
166+ if ( bundle == null )
167+ {
168+ MelonLogger . Error ( $ "[ServerResources] Failed to load asset bundle from { Path . Combine ( Environment . CurrentDirectory , cacheFile ) } ") ;
169+ return ;
170+ }
171+ var compressed = bundle . LoadAsset < TextAsset > ( "AquaMaiExtension" ) ;
172+ if ( compressed == null ) return ;
173+ using var ms = new MemoryStream ( compressed . bytes ) ;
174+ using var ds = new DeflateStream ( ms , CompressionMode . Decompress ) ;
175+ using var os = new MemoryStream ( ) ;
176+ ds . CopyTo ( os ) ;
177+ var bytes = os . ToArray ( ) ;
178+ var asm = AppDomain . CurrentDomain . Load ( bytes ) ;
179+ var ext = asm . GetType ( "AquaMai.Extension.Entrypoint" ) ;
180+ var main = ext . GetMethod ( "Main" , BindingFlags . Static | BindingFlags . Public ) ;
181+ main . Invoke ( null , [ ] ) ;
182+ }
183+
184+ private static bool VerifySignature ( byte [ ] data , string signatureBase64 )
185+ {
186+ var signature = Convert . FromBase64String ( signatureBase64 ) ;
187+ var pubKey = Convert . FromBase64String ( publicKey ) ;
188+
189+ var param = new RSAParameters
190+ {
191+ Modulus = pubKey ,
192+ Exponent = [ 1 , 0 , 1 ] ,
193+ } ;
194+ using var rsa = RSA . Create ( ) ;
195+ rsa . ImportParameters ( param ) ;
196+ return rsa . VerifyData ( data , signature , HashAlgorithmName . SHA256 , RSASignaturePadding . Pkcs1 ) ;
197+ }
198+ }
0 commit comments