Skip to content

Commit b34599b

Browse files
committed
[+] ServerResources
1 parent 699fdcf commit b34599b

File tree

1 file changed

+198
-0
lines changed

1 file changed

+198
-0
lines changed
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
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\nSERVER RESOURCES";
117+
if (_poweronState == 255)
118+
{
119+
smessage += Util.Utility.isBlinkDisp(blinktimer) ? "\n\nLOADING" : 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

Comments
 (0)