From 4e753ba602bfc5ad8e4123768caefe9a172e8ec7 Mon Sep 17 00:00:00 2001 From: Menci Date: Sun, 26 Jan 2025 09:20:15 +0800 Subject: [PATCH] [+] SanitizeUserData (#13) * save * commit * it works * correct namespace * JSON compare * fix * fix * fix * Prioritize LogNetworkRequests * format --- AquaMai.Core/Helpers/JsonHelper.cs | 69 ++++ AquaMai.Core/Helpers/NetPacketExtension.cs | 53 --- AquaMai.Core/Helpers/NetPacketHook.cs | 71 ++++ AquaMai.Core/Startup.cs | 3 +- .../Fix/Stability/SanitizeUserData.cs | 350 ++++++++++++++++++ AquaMai.Mods/UX/ServerAnnouncement.cs | 11 +- AquaMai.Mods/Utils/LogNetworkRequests.cs | 3 + 7 files changed, 500 insertions(+), 60 deletions(-) create mode 100644 AquaMai.Core/Helpers/JsonHelper.cs delete mode 100644 AquaMai.Core/Helpers/NetPacketExtension.cs create mode 100644 AquaMai.Core/Helpers/NetPacketHook.cs create mode 100644 AquaMai.Mods/Fix/Stability/SanitizeUserData.cs diff --git a/AquaMai.Core/Helpers/JsonHelper.cs b/AquaMai.Core/Helpers/JsonHelper.cs new file mode 100644 index 00000000..ed43038c --- /dev/null +++ b/AquaMai.Core/Helpers/JsonHelper.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using MelonLoader.TinyJSON; + +namespace AquaMai.Core.Helpers; + +public static class JsonHelper +{ + public static bool TryToInt32(Variant variant, out int result) + { + if (variant is ProxyNumber proxyNumber) + { + try + { + result = proxyNumber.ToInt32(CultureInfo.InvariantCulture); + return true; + } + catch + {} + } + else if (variant is ProxyString proxyString) + { + return int.TryParse(proxyString.ToString(), out result); + } + result = 0; + return false; + } + + public static bool TryToInt64(Variant variant, out long result) + { + if (variant is ProxyNumber proxyNumber) + { + try + { + result = proxyNumber.ToInt64(CultureInfo.InvariantCulture); + return true; + } + catch + {} + } + else if (variant is ProxyString proxyString) + { + return long.TryParse(proxyString.ToString(), out result); + } + result = 0; + return false; + } + + public class DeepEqualityComparer : IEqualityComparer + { + public bool Equals(Variant a, Variant b) => DeepEqual(a, b); + public int GetHashCode(Variant a) => a.ToJSON().GetHashCode(); + } + + + public static bool DeepEqual(Variant a, Variant b) => + (a, b) switch { + (ProxyArray arrayA, ProxyArray arrayB) => Enumerable.SequenceEqual(arrayA, arrayB, new DeepEqualityComparer()), + (ProxyObject objectA, ProxyObject objectB) => + objectA.Keys.Count == objectB.Keys.Count && + objectA.All(pair => objectB.TryGetValue(pair.Key, out var valueB) && DeepEqual(pair.Value, valueB)), + (ProxyBoolean booleanA, ProxyBoolean booleanB) => booleanA.ToBoolean(null) == booleanB.ToBoolean(null), + (ProxyNumber numberA, ProxyNumber numberB) => numberA.ToString() == numberB.ToString(), + (ProxyString stringA, ProxyString stringB) => stringA.ToString() == stringB.ToString(), + (null, null) => true, + _ => false + }; +} diff --git a/AquaMai.Core/Helpers/NetPacketExtension.cs b/AquaMai.Core/Helpers/NetPacketExtension.cs deleted file mode 100644 index 40f5662a..00000000 --- a/AquaMai.Core/Helpers/NetPacketExtension.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Text; -using Net; -using Net.Packet; -using MelonLoader; -using MelonLoader.TinyJSON; -using HarmonyLib; -using AquaMai.Core.Helpers; -using System.Linq; - -namespace AquaMai.Mods.Utils; - -public class NetPacketExtension -{ - public delegate void NetPacketResponseHandler(string api, Variant json); - - public static event NetPacketResponseHandler OnNetPacketResponse; - - [HarmonyPrefix] - [HarmonyPatch(typeof(Packet), "ProcImpl")] - public static void PreProcImpl(Packet __instance) - { - try - { - if ( - __instance.State == PacketState.Process && - Traverse.Create(__instance).Field("Client").GetValue() is NetHttpClient client && - client.State == NetHttpClient.StateDone) - { - var netQuery = __instance.Query; - var api = Shim.RemoveApiSuffix(netQuery.Api); - var response = client.GetResponse().ToArray(); - var decryptedResponse = Shim.NetHttpClientDecryptsResponse ? response : Shim.DecryptNetPacketBody(response); - var json = JSON.Load(Encoding.UTF8.GetString(decryptedResponse)); - foreach (var handler in OnNetPacketResponse?.GetInvocationList()) - { - try - { - handler.DynamicInvoke(api, json); - } - catch (Exception e) - { - MelonLogger.Error($"[NetPacketExtension] Error in handler: {e}"); - } - } - } - } - catch (Exception e) - { - MelonLogger.Error($"[NetPacketExtension] Failed to process NetPacket: {e}"); - } - } -} diff --git a/AquaMai.Core/Helpers/NetPacketHook.cs b/AquaMai.Core/Helpers/NetPacketHook.cs new file mode 100644 index 00000000..44b236d5 --- /dev/null +++ b/AquaMai.Core/Helpers/NetPacketHook.cs @@ -0,0 +1,71 @@ +using System; +using System.Text; +using Net; +using Net.Packet; +using MelonLoader; +using MelonLoader.TinyJSON; +using HarmonyLib; +using System.IO; + +namespace AquaMai.Core.Helpers; + +public class NetPacketHook +{ + // Returns true if the packet was modified + public delegate Variant NetPacketCompleteHook(string api, Variant request, Variant response); + + public static event NetPacketCompleteHook OnNetPacketComplete; + + [HarmonyPrefix] + [HarmonyPatch(typeof(Packet), "ProcImpl")] + public static void PreProcImpl(Packet __instance) + { + try + { + if ( + __instance.State == PacketState.Process && + Traverse.Create(__instance).Field("Client").GetValue() is NetHttpClient client && + client.State == NetHttpClient.StateDone) + { + var netQuery = __instance.Query; + var api = Shim.RemoveApiSuffix(netQuery.Api); + var responseBytes = client.GetResponse().ToArray(); + var decryptedResponse = Shim.NetHttpClientDecryptsResponse ? responseBytes : Shim.DecryptNetPacketBody(responseBytes); + var decodedResponse = Encoding.UTF8.GetString(decryptedResponse); + var responseJson = JSON.Load(decodedResponse); + var requestJson = JSON.Load(netQuery.GetRequest()); + var modified = false; + foreach (var handler in OnNetPacketComplete?.GetInvocationList()) + { + try + { + if (handler.DynamicInvoke(api, requestJson, responseJson) is Variant result) + { + responseJson = result; + modified = true; + } + } + catch (Exception e) + { + MelonLogger.Error($"[NetPacketExtension] Error in handler: {e}"); + } + } + if ( + modified && + Traverse.Create(client).Field("_memoryStream").GetValue() is MemoryStream memoryStream && + !JsonHelper.DeepEqual(responseJson, JSON.Load(decodedResponse))) + { + var modifiedResponse = Encoding.UTF8.GetBytes(responseJson.ToJSON()); + memoryStream.SetLength(0); + memoryStream.Write(modifiedResponse, 0, modifiedResponse.Length); + memoryStream.Seek(0, SeekOrigin.Begin); + MelonLogger.Msg($"[NetPacketExtension] Modified response for {api} ({decodedResponse.Length} bytes -> {modifiedResponse.Length} bytes)"); + } + } + } + catch (Exception e) + { + MelonLogger.Error($"[NetPacketExtension] Failed to process NetPacket: {e}"); + } + } +} diff --git a/AquaMai.Core/Startup.cs b/AquaMai.Core/Startup.cs index 8df3dd96..f0f09de5 100644 --- a/AquaMai.Core/Startup.cs +++ b/AquaMai.Core/Startup.cs @@ -6,7 +6,6 @@ using System.Reflection; using AquaMai.Core.Attributes; using AquaMai.Core.Helpers; using AquaMai.Core.Resources; -using AquaMai.Mods.Utils; using MelonLoader; using UnityEngine; @@ -156,7 +155,7 @@ public class Startup CollectWantedPatches(wantedPatches, typeof(GuiSizes)); CollectWantedPatches(wantedPatches, typeof(KeyListener)); CollectWantedPatches(wantedPatches, typeof(Shim)); - CollectWantedPatches(wantedPatches, typeof(NetPacketExtension)); + CollectWantedPatches(wantedPatches, typeof(NetPacketHook)); // Collect patches based on the config var config = ConfigLoader.Config; diff --git a/AquaMai.Mods/Fix/Stability/SanitizeUserData.cs b/AquaMai.Mods/Fix/Stability/SanitizeUserData.cs new file mode 100644 index 00000000..ee3ad65b --- /dev/null +++ b/AquaMai.Mods/Fix/Stability/SanitizeUserData.cs @@ -0,0 +1,350 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using AquaMai.Config.Attributes; +using AquaMai.Core.Helpers; +using Manager; +using MelonLoader; +using MelonLoader.TinyJSON; +using Net.VO.Mai2; + +namespace AquaMai.Mods.Fix.Stability; + +[ConfigSection(exampleHidden: true, defaultOn: true)] +public class SanitizeUserData +{ + public static void OnBeforePatch() + { + NetPacketHook.OnNetPacketComplete += OnNetPacketComplete; + } + + private static Variant OnNetPacketComplete(string api, Variant request, Variant response) + { + var handlerMap = new Dictionary> + { + // ["GetUserPreviewApi"] = /* no need */, + // ["GetUserFriendCheckApi"] = /* no need */, + ["GetUserDataApi"] = OnUserDataResponse, + // ["GetUserCardApi"] = = /* no need */, + ["GetUserCharacterApi"] = (_, response) => FilterListResponseById(api, response, "userCharacterList", "characterId", "GetCharas"), + ["GetUserItemApi"] = OnUserItemResponse, + ["GetUserCourseApi"] = (_, response) => FilterListResponseById(api, response, "userCourseList", "courseId", "GetCourses"), + ["GetUserChargeApi"] = (_, response) => FilterListResponseById(api, response, "userChargeList", "chargeId", "GetTickets"), + ["GetUserFavoriteApi"] = OnUserFavoriteResponse, + // ["GetUserGhostApi"] = /* no need */, + ["GetUserMapApi"] = (_, response) => FilterListResponseById(api, response, "userMapList", "mapId", "GetMapDatas"), + // ["GetUserLoginBonusApi"] = /* no need */, + // ["GetUserRegionApi"] = /* no need */, + // ["GetUserRecommendRateMusicApi"] = /* no need */, + // ["GetUserRecommendSelectionMusicApi"] = /* no need */, + // ["GetUserOptionApi"] = /* no need */, + ["GetUserExtendApi"] = OnUserExtendResponse, + // ["GetUserRatingApi"] = /* no need */, + // ["GetUserMusicApi"] = /* no need */, + // ["GetUserPortraitApi"] = /* no need */, + // ["GetUserActivityApi"] = /* no need */, + // ["GetUserFriendSeasonRankingApi"] = /* no need */, + ["GetUserFavoriteItemApi"] = OnUserFavoriteItemResponse, + // ["GetUserRivalDataApi"] = /* no need */, + // ["GetUserRivalMusicApi"] = /* no need */, + // ["GetUserMissionDataApi"] = /* no need */, + // ["GetUserFriendBonusApi"] = /* no need */, + // ["GetUserIntimateApi"] = /* no need */, + // ["GetUserShopStockApi"] = /* no need */, + ["GetUserKaleidxScopeApi"] = (_, response) => FilterListResponseById(api, response, "userKaleidxScopeList", "gateId", "GetKaleidxScopeKeys"), + // ["GetUserScoreRankingApi"] = /* no need */, + // ["GetUserNewItemApi"] = /* no need */, + // ["GetUserNewItemListApi"] = /* no need */, + }; + if (handlerMap.TryGetValue(api, out var handler)) + { + var requestObject = request is ProxyObject reqObj ? reqObj : []; + var responseObject = response is ProxyObject resObj ? resObj : []; + handler(requestObject, responseObject); + return responseObject; + } + return null; + } + + private static void OnUserDataResponse(ProxyObject _, ProxyObject response) + { + var userData = GetObjectOrSetDefault(response, "userData"); + SanitizeItemIdField(userData, "iconId", "GetIcons"); + SanitizeItemIdField(userData, "plateId", "GetPlates"); + SanitizeItemIdField(userData, "titleId", "GetTitles"); + SanitizeItemIdField(userData, "partnerId", "GetPartners"); + SanitizeItemIdField(userData, "frameId", "GetFrames"); + SanitizeItemIdField(userData, "selectMapId", "GetMapDatas"); + var charaSlot = GetArrayOrSetDefault(userData, "charaSlot"); + for (var i = 0; i < 5; i++) + { + if (charaSlot.Count <= i) + { + charaSlot.Add(new ProxyNumber(0)); + } + else if ( + !JsonHelper.TryToInt32(charaSlot[i], out var charaSlotEntryInt) || + !SafelyCheckItemId("GetCharas", charaSlotEntryInt)) + { + charaSlot.Add(new ProxyNumber(0)); + MelonLogger.Warning($"[SanitizeUserData] Filtered out invalid chara {charaSlot[i].ToJSON()} at index {i}"); + } + } + userData["charaSlot"] = ToProxyArray(charaSlot.Take(5)); + } + + private static void OnUserExtendResponse(ProxyObject _, ProxyObject response) + { + var userExtend = GetObjectOrSetDefault(response, "userExtend"); + SanitizeItemIdField(userExtend, "selectMusicId", "GetMusics"); + SanitizeItemIdField(userExtend, "selectDifficultyId", "GetMusicDifficultys"); + // categoryIndex? + // musicIndex? + SanitizeEnumFieldIfDefined(userExtend, "selectScoreType", ResolveEnumType("ConstParameter.ScoreKind")); + SanitizeEnumFieldIfDefined(userExtend, "selectResultScoreViewType", ResolveEnumType("Process.ResultProcess.ResultScoreViewType")); + SanitizeEnumFieldIfDefined(userExtend, "sortCategorySetting", ResolveEnumType("DB.SortTabID")); + SanitizeEnumFieldIfDefined(userExtend, "sortMusicSetting", ResolveEnumType("DB.SortMusicID")); + SanitizeEnumFieldIfDefined(userExtend, "playStatusSetting", ResolveEnumType("DB.PlaystatusTabID")); + } + + private static void OnUserItemResponse(ProxyObject request, ProxyObject response) + { + var requestKind = (ItemKind)( + request.TryGetValue("nextIndex", out var nextIndexVariant) && + JsonHelper.TryToInt64(nextIndexVariant, out var nextIndex) + ? nextIndex / 10000000000L + : 0); + + var filteredOutCount = FilterListResponse( + null, + response, + "userItemList", + userItem => + JsonHelper.TryToInt32(userItem["itemId"], out var itemId) && + SafelyCheckItemIdByKind(requestKind, itemId)); + + if (filteredOutCount > 0) + { + MelonLogger.Warning($"[SanitizeUserData] Filtered out {filteredOutCount} invalid entries of kind {requestKind} in GetUserItemApi"); + } + } + + private static void OnUserFavoriteResponse(ProxyObject request, ProxyObject response) + { + var requestKind = (ItemKind)( + request.TryGetValue("itemKind", out var itemKindVariant) && + JsonHelper.TryToInt64(itemKindVariant, out var itemKind) + ? itemKind + : 0); + + var userFavorite = GetObjectOrSetDefault(response, "userFavorite"); + var itemIdList = GetArrayOrSetDefault(userFavorite, "itemIdList"); + var validItemIdList = itemIdList + .Select(itemIdVariant => + JsonHelper.TryToInt32(itemIdVariant, out var itemId) && + SafelyCheckItemIdByKind(requestKind, itemId) + ? itemIdVariant + : null) + .Where(itemIdVariant => itemIdVariant != null) + .ToList(); + userFavorite["itemIdList"] = ToProxyArray(validItemIdList); + + var filteredOutCount = itemIdList.Count - validItemIdList.Count; + if (filteredOutCount > 0) + { + MelonLogger.Warning($"[SanitizeUserData] Filtered out {filteredOutCount} invalid entries of kind {requestKind} in GetUserFavoriteApi"); + } + } + + private static void OnUserFavoriteItemResponse(ProxyObject request, ProxyObject response) + { + // Older versions of the game don't have the FavoriteItemKind enum + var enumType = AppDomain.CurrentDomain + .GetAssemblies() + .SelectMany(assembly => assembly.GetTypes()) + .FirstOrDefault(type => type.FullName == "Net.VO.Mai2.FavoriteItemKind" && type.IsEnum); + + var requestKind = Enum.ToObject( + enumType, + request.TryGetValue("kind", out var kindVariant) && + JsonHelper.TryToInt64(kindVariant, out var kind) + ? kind + : 0); + + var userFavoriteItemList = GetArrayOrSetDefault(response, "userFavoriteItemList"); + var validItemList = userFavoriteItemList + .Select(itemariant => + itemariant is ProxyObject itemObject && + itemObject.TryGetValue("id", out var idVariant) && + JsonHelper.TryToInt32(idVariant, out var id) + ? (id, itemObject) + : (0, null)) + .Where(tuple => tuple.itemObject != null) + .Where(requestKind.ToString() switch + { + "FavoriteMusic" => tuple => SafelyCheckItemId("GetMusics", tuple.id), + "RivalScore" => _ => true, + _ => _ => true // Fail-safe for newly introduced favorite item kinds after mod release + }) + .Select(tuple => tuple.itemObject) + .ToList(); + response["userFavoriteItemList"] = ToProxyArray(validItemList); + + var filteredOutCount = userFavoriteItemList.Count - validItemList.Count; + if (filteredOutCount > 0) + { + MelonLogger.Warning($"[SanitizeUserData] Filtered out {filteredOutCount} invalid entries of kind {requestKind} in GetUserFavoriteItemApi"); + } + } + + private static ProxyObject GetObjectOrSetDefault(ProxyObject response, string fieldName) + { + if ( + !response.TryGetValue(fieldName, out var fieldVariant) || + fieldVariant is not ProxyObject field) + { + field = []; + response[fieldName] = field; + } + return field; + } + + private static ProxyArray GetArrayOrSetDefault(ProxyObject response, string fieldName) + { + if ( + !response.TryGetValue(fieldName, out var fieldVariant) || + fieldVariant is not ProxyArray field) + { + field = []; + response[fieldName] = field; + } + return field; + } + + private static int FilterListResponse(string logApiName, ProxyObject response, string listFieldName, Func isValidEntry) + { + var before = GetArrayOrSetDefault(response, listFieldName); + var after = GetArrayOrSetDefault(response, listFieldName) + .Select(entry => entry is ProxyObject entryObject ? entryObject : null) + .Select(entry => isValidEntry(entry) ? entry : null) + .Where(entry => entry != null) + .ToList(); + response[listFieldName] = ToProxyArray(after); + var filteredOutCount = before.Count - after.Count; + if (logApiName != null && filteredOutCount > 0) + { + MelonLogger.Warning($"[SanitizeUserData] Filtered out {filteredOutCount} invalid entries in {logApiName}"); + } + return filteredOutCount; + } + + private static int FilterListResponseById(string logApiName, ProxyObject response, string listFieldName, string idFieldName, string dataManagerGetDictionaryMethod) => + FilterListResponse( + logApiName, + response, + listFieldName, + listEntry => + listEntry.TryGetValue(idFieldName, out var idVariant) && + JsonHelper.TryToInt32(idVariant, out var idInt) && + SafelyCheckItemId(dataManagerGetDictionaryMethod, idInt)); + + private static void SanitizeInt32Field(ProxyObject obj, string fieldName, Func isValid, int defaultValue) + { + if ( + !obj.TryGetValue(fieldName, out var fieldVariant) || + !JsonHelper.TryToInt32(fieldVariant, out var fieldValue) || + !isValid(fieldValue)) + { + MelonLogger.Warning($"[SanitizeUserData] Set value of invalid int32 field {fieldName} from {fieldVariant?.ToJSON() ?? "null"} to {defaultValue}"); + obj[fieldName] = new ProxyNumber(defaultValue); + } + } + + private static void SanitizeEnumFieldIfDefined(ProxyObject obj, string fieldName, System.Type enumType) => + SanitizeInt32Field( + obj, + fieldName, + value => enumType == null || Enum.IsDefined(enumType, value), + enumType == null ? 0 : (int)enumType.GetEnumValues().GetValue(0)); + + private static void SanitizeItemIdField(ProxyObject obj, string fieldName, string dataManagerGetDictionaryMethod) => + SanitizeInt32Field( + obj, + fieldName, + itemId => SafelyCheckItemId(dataManagerGetDictionaryMethod, itemId), + SafelyGetDefaultItemId(dataManagerGetDictionaryMethod)); + + // The corresponding DataManager methods may not exist in all game versions + private static object SafelyGetDataMangerDictionary(string dataManagerGetDictionaryMethod) + { + return typeof(DataManager) + .GetMethod(dataManagerGetDictionaryMethod, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Invoke(DataManager.Instance, []); + } + + private static int SafelyGetDefaultItemId(string dataManagerGetDictionaryMethod) + { + var dictionary = SafelyGetDataMangerDictionary(dataManagerGetDictionaryMethod); + var enumerator = dictionary + .GetType() + .GetMethod("GetEnumerator", BindingFlags.Instance | BindingFlags.Public) + .Invoke(dictionary, []) as IEnumerator; + return !enumerator.MoveNext() + ? 0 + : enumerator.Current + .GetType() + .GetProperty("Key", BindingFlags.Instance | BindingFlags.Public) + .GetValue(enumerator.Current) as int? ?? 0; + } + + private static bool SafelyCheckItemId(string dataManagerGetDictionaryMethod, int itemId) + { + var dictionary = SafelyGetDataMangerDictionary(dataManagerGetDictionaryMethod); + return dictionary + .GetType() + .GetMethod("ContainsKey", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Invoke(dictionary, [itemId]) as bool? ?? false; + } + + private static bool SafelyCheckItemIdByKind(ItemKind itemKind, int itemId) => + itemKind.ToString() switch + { + "Plate" => SafelyCheckItemId("GetPlates", itemId), + "Title" => SafelyCheckItemId("GetTitles", itemId), + "Icon" => SafelyCheckItemId("GetIcons", itemId), + "Present" => DataManager.Instance.ConvertPresentID2Item(itemId, out _, out _), + // It's safe to have invalid music IDs in the user item list + "Music" => true, + "MusicMas" => true, + "MusicRem" => true, + "MusicSrg" => true, + "Character" => SafelyCheckItemId("GetCharas", itemId), + "Partner" => SafelyCheckItemId("GetPartners", itemId), + "Frame" => SafelyCheckItemId("GetFrames", itemId), + "Ticket" => SafelyCheckItemId("GetTickets", itemId), + "Mile" => true, + "IntimateItem" => true, + "KaleidxScopeKey" => SafelyCheckItemId("GetKaleidxScopeKeys", itemId), + _ => true, // Fail-safe for newly introduced item kinds after mod release + }; + + private static ProxyArray ToProxyArray(IEnumerable values) + { + var array = new ProxyArray(); + foreach (var value in values) + { + array.Add(value); + } + return array; + } + + private static System.Type ResolveEnumType(string enumName) + { + return AppDomain.CurrentDomain + .GetAssemblies() + .SelectMany(assembly => assembly.GetTypes()) + .FirstOrDefault(type => type.FullName == enumName && type.IsEnum); + } +} diff --git a/AquaMai.Mods/UX/ServerAnnouncement.cs b/AquaMai.Mods/UX/ServerAnnouncement.cs index 3fcc5197..f8080e92 100644 --- a/AquaMai.Mods/UX/ServerAnnouncement.cs +++ b/AquaMai.Mods/UX/ServerAnnouncement.cs @@ -2,7 +2,6 @@ using System.Linq; using AquaMai.Config.Attributes; using AquaMai.Core.Helpers; -using AquaMai.Mods.Utils; using DB; using HarmonyLib; using JetBrains.Annotations; @@ -51,14 +50,14 @@ public static class ServerAnnouncement public static void OnBeforePatch() { - NetPacketExtension.OnNetPacketResponse += OnNetPacketResponse; + NetPacketHook.OnNetPacketComplete += OnNetPacketComplete; } - private static void OnNetPacketResponse(string api, Variant json) + private static Variant OnNetPacketComplete(string api, Variant _, Variant response) { - if (api != "GetGameSettingApi" || json is not ProxyObject obj) return; + if (api != "GetGameSettingApi" || response is not ProxyObject obj) return null; var serverAnnouncementJson = obj.Keys.Contains(FieldName) ? obj[FieldName] : null; - if (serverAnnouncementJson == null) return; + if (serverAnnouncementJson == null) return null; var serverAnnouncementData = serverAnnouncementJson.Make(); ServerAnnouncementEntry chosenAnnouncement = null; @@ -88,6 +87,8 @@ public static class ServerAnnouncement } _announcement = chosenAnnouncement; + + return null; } private static bool ShouldShowAnnouncement(ServerAnnouncementEntry announcement) diff --git a/AquaMai.Mods/Utils/LogNetworkRequests.cs b/AquaMai.Mods/Utils/LogNetworkRequests.cs index 9e2cf743..dfee58c3 100644 --- a/AquaMai.Mods/Utils/LogNetworkRequests.cs +++ b/AquaMai.Mods/Utils/LogNetworkRequests.cs @@ -59,6 +59,7 @@ public class LogNetworkRequests } [EnableIf(nameof(url))] + [HarmonyPriority(Priority.First)] [HarmonyPostfix] [HarmonyPatch(typeof(Packet), "Create")] public static void PostCreate(Packet __instance) @@ -80,6 +81,7 @@ public class LogNetworkRequests } // Record the error responses of NetHttpClient to display. These responses could not be acquired in other ways. + [HarmonyPriority(Priority.First)] [HarmonyPrefix] [HarmonyPatch(typeof(NetHttpClient), "SetError")] public static void PreSetError(NetHttpClient __instance, HttpWebResponse response) @@ -90,6 +92,7 @@ public class LogNetworkRequests } } + [HarmonyPriority(Priority.First)] [HarmonyPrefix] [HarmonyPatch(typeof(Packet), "ProcImpl")] public static void PreProcImpl(Packet __instance)