From b03f87ed3b8e2c777370d0c58704b70704556563 Mon Sep 17 00:00:00 2001 From: Fluto Date: Sat, 13 Aug 2022 21:00:45 +1000 Subject: [PATCH] Added support for il2cpp --- .gitignore | 4 +- TJAConvert/TJAConvert.csproj | 25 + TakoTako.Common/TakoTako.Common.csproj | 42 +- TakoTako.sln | 42 +- TakoTako.sln.DotSettings | 1 + TakoTako/IL2CPPHelper.cs | 20 + TakoTako/Il2cppRedirection.cs | 98 +++ TakoTako/MusicPatch.cs | 907 +++++++++++++++++------- TakoTako/Plugin.cs | 48 +- TakoTako/SignInPatch.cs | 24 +- TakoTako/SkipDLCCheck.cs | 28 + TakoTako/TaikoDrumSupport.cs | 44 +- TakoTako/TakoTako.csproj | 148 ++-- test/MyFirstPlugin/MyFirstPlugin.csproj | 17 + test/MyFirstPlugin/NuGet.Config | 6 + test/MyFirstPlugin/Plugin.cs | 15 + 16 files changed, 1110 insertions(+), 359 deletions(-) create mode 100644 TakoTako/IL2CPPHelper.cs create mode 100644 TakoTako/Il2cppRedirection.cs create mode 100644 TakoTako/SkipDLCCheck.cs create mode 100644 test/MyFirstPlugin/MyFirstPlugin.csproj create mode 100644 test/MyFirstPlugin/NuGet.Config create mode 100644 test/MyFirstPlugin/Plugin.cs diff --git a/.gitignore b/.gitignore index bd476dd..863b617 100644 --- a/.gitignore +++ b/.gitignore @@ -398,4 +398,6 @@ FodyWeavers.xsd *.sln.iml .idea -TakoTako/Executables/TJAConvert.exe \ No newline at end of file +TakoTako/Executables/TJAConvert.exe +TakoTako/lib +TaikoUnityAssets \ No newline at end of file diff --git a/TJAConvert/TJAConvert.csproj b/TJAConvert/TJAConvert.csproj index 4b2fc3c..5761d61 100644 --- a/TJAConvert/TJAConvert.csproj +++ b/TJAConvert/TJAConvert.csproj @@ -1,5 +1,28 @@ + + true + TRACE;TAIKO_IL2CPP + true + + + + true + TRACE;TAIKO_MONO; + + + + true + false + TRACE;TAIKO_MONO; + + + + true + false + TRACE;TAIKO_IL2CPP; + + Exe net48 @@ -7,6 +30,8 @@ true win10-x64 true + Release-BepInEx6;Release-BepInEx5;Debug-BepInEx6;Debug-BepInEx5 + AnyCPU diff --git a/TakoTako.Common/TakoTako.Common.csproj b/TakoTako.Common/TakoTako.Common.csproj index 86b9c71..cdaab51 100644 --- a/TakoTako.Common/TakoTako.Common.csproj +++ b/TakoTako.Common/TakoTako.Common.csproj @@ -1,16 +1,50 @@ + + true + TRACE;TAIKO_IL2CPP + true + + + + true + TRACE;TAIKO_MONO; + + + + true + false + TRACE;TAIKO_MONO; + + + + true + false + TRACE;TAIKO_IL2CPP; + + - net48 TakoTako.Common TakoTako.Common latest + Release-BepInEx6;Release-BepInEx5;Debug-BepInEx6;Debug-BepInEx5 + AnyCPU + net48;netstandard2.1 + + + + true + + + + true + false - - ..\TakoTako\References\Newtonsoft.Json.dll - + + ..\TakoTako\References\Newtonsoft.Json.dll + diff --git a/TakoTako.sln b/TakoTako.sln index 96f2f2d..6991037 100644 --- a/TakoTako.sln +++ b/TakoTako.sln @@ -11,21 +11,35 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakoTako", "TakoTako\TakoTa EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU + Release-BepInEx6|Any CPU = Release-BepInEx6|Any CPU + Release-BepInEx5|Any CPU = Release-BepInEx5|Any CPU + Debug-BepInEx6|Any CPU = Debug-BepInEx6|Any CPU + Debug-BepInEx5|Any CPU = Debug-BepInEx5|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {DDD2BC9B-15CB-47AE-A104-F45A3C65C7B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DDD2BC9B-15CB-47AE-A104-F45A3C65C7B1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DDD2BC9B-15CB-47AE-A104-F45A3C65C7B1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DDD2BC9B-15CB-47AE-A104-F45A3C65C7B1}.Release|Any CPU.Build.0 = Release|Any CPU - {9ED2476B-FB39-4BE9-8661-21311AD9A3E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9ED2476B-FB39-4BE9-8661-21311AD9A3E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9ED2476B-FB39-4BE9-8661-21311AD9A3E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9ED2476B-FB39-4BE9-8661-21311AD9A3E8}.Release|Any CPU.Build.0 = Release|Any CPU - {3B286FDB-8AB8-49B0-852E-5180AFBC20D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3B286FDB-8AB8-49B0-852E-5180AFBC20D5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3B286FDB-8AB8-49B0-852E-5180AFBC20D5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3B286FDB-8AB8-49B0-852E-5180AFBC20D5}.Release|Any CPU.Build.0 = Release|Any CPU + {DDD2BC9B-15CB-47AE-A104-F45A3C65C7B1}.Release-BepInEx6|Any CPU.ActiveCfg = Release-BepInEx6|Any CPU + {DDD2BC9B-15CB-47AE-A104-F45A3C65C7B1}.Release-BepInEx6|Any CPU.Build.0 = Release-BepInEx6|Any CPU + {DDD2BC9B-15CB-47AE-A104-F45A3C65C7B1}.Release-BepInEx5|Any CPU.ActiveCfg = Release-BepInEx5|Any CPU + {DDD2BC9B-15CB-47AE-A104-F45A3C65C7B1}.Release-BepInEx5|Any CPU.Build.0 = Release-BepInEx5|Any CPU + {DDD2BC9B-15CB-47AE-A104-F45A3C65C7B1}.Debug-BepInEx6|Any CPU.ActiveCfg = Debug-BepInEx6|Any CPU + {DDD2BC9B-15CB-47AE-A104-F45A3C65C7B1}.Debug-BepInEx6|Any CPU.Build.0 = Debug-BepInEx6|Any CPU + {DDD2BC9B-15CB-47AE-A104-F45A3C65C7B1}.Debug-BepInEx5|Any CPU.ActiveCfg = Debug-BepInEx5|Any CPU + {DDD2BC9B-15CB-47AE-A104-F45A3C65C7B1}.Debug-BepInEx5|Any CPU.Build.0 = Debug-BepInEx5|Any CPU + {9ED2476B-FB39-4BE9-8661-21311AD9A3E8}.Release-BepInEx6|Any CPU.ActiveCfg = Release-BepInEx6|Any CPU + {9ED2476B-FB39-4BE9-8661-21311AD9A3E8}.Release-BepInEx6|Any CPU.Build.0 = Release-BepInEx6|Any CPU + {9ED2476B-FB39-4BE9-8661-21311AD9A3E8}.Release-BepInEx5|Any CPU.ActiveCfg = Release-BepInEx5|Any CPU + {9ED2476B-FB39-4BE9-8661-21311AD9A3E8}.Release-BepInEx5|Any CPU.Build.0 = Release-BepInEx5|Any CPU + {9ED2476B-FB39-4BE9-8661-21311AD9A3E8}.Debug-BepInEx6|Any CPU.ActiveCfg = Debug-BepInEx6|Any CPU + {9ED2476B-FB39-4BE9-8661-21311AD9A3E8}.Debug-BepInEx6|Any CPU.Build.0 = Debug-BepInEx6|Any CPU + {9ED2476B-FB39-4BE9-8661-21311AD9A3E8}.Debug-BepInEx5|Any CPU.ActiveCfg = Debug-BepInEx5|Any CPU + {9ED2476B-FB39-4BE9-8661-21311AD9A3E8}.Debug-BepInEx5|Any CPU.Build.0 = Debug-BepInEx5|Any CPU + {3B286FDB-8AB8-49B0-852E-5180AFBC20D5}.Release-BepInEx6|Any CPU.ActiveCfg = Release-BepInEx6|Any CPU + {3B286FDB-8AB8-49B0-852E-5180AFBC20D5}.Release-BepInEx6|Any CPU.Build.0 = Release-BepInEx6|Any CPU + {3B286FDB-8AB8-49B0-852E-5180AFBC20D5}.Release-BepInEx5|Any CPU.ActiveCfg = Release-BepInEx5|Any CPU + {3B286FDB-8AB8-49B0-852E-5180AFBC20D5}.Release-BepInEx5|Any CPU.Build.0 = Release-BepInEx5|Any CPU + {3B286FDB-8AB8-49B0-852E-5180AFBC20D5}.Debug-BepInEx6|Any CPU.ActiveCfg = Debug-BepInEx6|Any CPU + {3B286FDB-8AB8-49B0-852E-5180AFBC20D5}.Debug-BepInEx6|Any CPU.Build.0 = Debug-BepInEx6|Any CPU + {3B286FDB-8AB8-49B0-852E-5180AFBC20D5}.Debug-BepInEx5|Any CPU.ActiveCfg = Debug-BepInEx5|Any CPU + {3B286FDB-8AB8-49B0-852E-5180AFBC20D5}.Debug-BepInEx5|Any CPU.Build.0 = Debug-BepInEx5|Any CPU EndGlobalSection EndGlobal diff --git a/TakoTako.sln.DotSettings b/TakoTako.sln.DotSettings index fd2fec2..5dc15b2 100644 --- a/TakoTako.sln.DotSettings +++ b/TakoTako.sln.DotSettings @@ -1,4 +1,5 @@  + DLC True True True diff --git a/TakoTako/IL2CPPHelper.cs b/TakoTako/IL2CPPHelper.cs new file mode 100644 index 0000000..e79766b --- /dev/null +++ b/TakoTako/IL2CPPHelper.cs @@ -0,0 +1,20 @@ + +#if TAIKO_IL2CPP +using System; +using System.Runtime.InteropServices; +// using Il2CppSystem.Runtime.InteropServices; +using UnhollowerBaseLib; + +namespace TakoTako; + +public class ReferenceObject : Il2CppObjectBase +{ + public T Value { get; private set; } + public ReferenceObject(IntPtr pointer) : base(pointer) + { + Value = Marshal.PtrToStructure(pointer); + } + + public static implicit operator T (ReferenceObject value) => value.Value; +} +#endif diff --git a/TakoTako/Il2cppRedirection.cs b/TakoTako/Il2cppRedirection.cs new file mode 100644 index 0000000..7243c3e --- /dev/null +++ b/TakoTako/Il2cppRedirection.cs @@ -0,0 +1,98 @@ +#if TAIKO_IL2CPP +using HarmonyLib; +using Il2CppSystem; +using UnhollowerBaseLib; +using UnhollowerBaseLib.Attributes; +using UnhollowerBaseLib.Runtime; +using Il2CppException = UnhollowerBaseLib.Il2CppException; + +namespace TakoTako; + +public static class Il2cppRedirection +{ + [CallerCount(4)] + [CachedScanResults(MetadataInitFlagRva = 0, MetadataInitTokenRva = 0, RefRangeEnd = 504456, RefRangeStart = 504452, XrefRangeEnd = 504452, XrefRangeStart = 504452)] + public static unsafe void GetMusicInfoExAllIl2cpp(this PlayDataManager instance, int playerId, out Il2CppStructArray dst) + { + IL2CPP.Il2CppObjectBaseToPtrNotNull(instance); + System.IntPtr* numPtr = stackalloc System.IntPtr[2]; + numPtr[0] = (System.IntPtr) (&playerId); + var outPtr = IntPtr.Zero; + numPtr[1] = (System.IntPtr) (&outPtr); + System.IntPtr exc = System.IntPtr.Zero; + var field = (System.IntPtr) AccessTools.Field(typeof(PlayDataManager), "NativeMethodInfoPtr_GetMusicInfoExAll_Public_Void_Int32_byref_ArrayOf_MusicInfoEx_0").GetValue(null); + IL2CPP.il2cpp_runtime_invoke(field, IL2CPP.Il2CppObjectBaseToPtrNotNull(instance), (void**) numPtr, ref exc); + dst = new Il2CppStructArray(outPtr); + Il2CppException.RaiseExceptionIfNecessary(exc); + } + + public static void GetPlayerInfoRemake(this PlayDataManager instance, int playerId, out PlayerInfo dst) => dst = instance.saveData[playerId].dataBody.playerInfo; + + public static bool SetPlayerInfoRemake(this PlayDataManager instance, int playerId, ref PlayerInfo src, bool immediate = false) + { + return SetPlayerInfoRemake(instance, playerId, ref src, true, !immediate); + } + + public static bool SetPlayerInfoRemake(this PlayDataManager instance, int playerId, ref PlayerInfo src, bool savemode, bool async = true) + { + src.checkNull(); + if (!src.IsValid()) + return false; + instance.saveData[playerId].dataBody.playerInfo = src; + if (savemode && playerId == 0) + { + if (async) + instance.SaveObjectAsync(); + else + instance.SaveObject(); + } + instance.isSaveDataChanged = true; + return true; + } + + public static void GetRankMatchSeasonRecordInfoRemake( + this PlayDataManager instance, + int playerId, + int seasonId, + out RankMatchSeasonRecordInfo dst) + { + if (instance.saveData[playerId].dataBody.rankMatchSeasonRecordInfo == null) + instance.saveData[playerId].ResetRankMatchSeasonRecordInfo(); + dst = instance.saveData[playerId].dataBody.rankMatchSeasonRecordInfo[seasonId]; + } + + public static void CopySettingsRemake(this EnsoDataManager ensoDataManager, out EnsoData.Settings dst) => ensoDataManager.ensoSettings.CopyRemake(out dst); + + public static void GetSystemOptionRemake(this PlayDataManager instance, out SystemOption dst, bool forceReload = false) + { + dst = instance.systemOption; + } + + public static void SetSettingsRemake(this EnsoDataManager instance, ref EnsoData.Settings src) + { + src.CopyRemake(out var value); + instance.ensoSettings = value; + } + + public static void CopyRemake(this EnsoData.Settings settings, out EnsoData.Settings dst) + { + dst = settings; + for (int i = 0; i < settings.ensoPlayerSettings.Length; i++) + dst.ensoPlayerSettings = settings.ensoPlayerSettings; + + for (int i = 0; i < settings.partsSettings.Length; i++) + dst.partsSettings = settings.partsSettings; + + + DebugCopyRemake(settings.debugSettings, out var value); + settings.debugSettings = value; + } + public static void DebugCopyRemake(this EnsoData.DebugSettings settings, out EnsoData.DebugSettings dst) + { + dst = settings; + + for (int i = 0; i < settings.debugPlayerSettings.Length; i++) + dst.debugPlayerSettings = settings.debugPlayerSettings; + } +} +#endif diff --git a/TakoTako/MusicPatch.cs b/TakoTako/MusicPatch.cs index 26c9850..dcc3518 100644 --- a/TakoTako/MusicPatch.cs +++ b/TakoTako/MusicPatch.cs @@ -1,23 +1,26 @@ using System; using System.Collections; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Compression; using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using BepInEx.Logging; +using Blittables; using HarmonyLib; +#if TAIKO_IL2CPP +using UnhollowerBaseLib; +using BepInEx.IL2CPP.Utils.Collections; +using Object = Il2CppSystem.Object; +#endif using Newtonsoft.Json; +using SongSelect; +using SongSelectRanking; using TakoTako.Common; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; using UnityEngine; namespace TakoTako; @@ -42,8 +45,6 @@ public class MusicPatch CreateDirectoryIfNotExist(Path.GetDirectoryName(SaveFilePath)); CreateDirectoryIfNotExist(MusicTrackDirectory); - PatchManual(harmony); - void CreateDirectoryIfNotExist(string path) { path = Path.GetFullPath(path); @@ -55,14 +56,6 @@ public class MusicPatch } } - private static void PatchManual(Harmony harmony) - { - var original = typeof(FumenLoader).GetNestedType("PlayerData", BindingFlags.NonPublic).GetMethod("Read"); - var prefix = typeof(MusicPatch).GetMethod(nameof(Read_Prefix), BindingFlags.Static | BindingFlags.NonPublic); - - harmony.Patch(original, new HarmonyMethod(prefix)); - } - #region Custom Save Data private static CustomMusicSaveDataBody _customSaveData; @@ -85,8 +78,8 @@ public class MusicPatch { using var fileStream = File.OpenRead(savePath); data = (CustomMusicSaveDataBody) JsonConvert.DeserializeObject(File.ReadAllText(savePath)); - data.CustomTrackToEnsoRecordInfo ??= new Dictionary(); - data.CustomTrackToMusicInfoEx ??= new Dictionary(); + data.CustomTrackToEnsoRecordInfo ??= new System.Collections.Generic.Dictionary(); + data.CustomTrackToMusicInfoEx ??= new System.Collections.Generic.Dictionary(); } _customSaveData = data; @@ -131,7 +124,7 @@ public class MusicPatch var json = JsonConvert.SerializeObject((CustomMusicSaveDataBodySerializable) data); using Stream fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, FileOptions.WriteThrough); - using StreamWriter streamWriter = new StreamWriter(fs); + using var streamWriter = new StreamWriter(fs); await streamWriter.WriteAsync(json); } catch (Exception e) @@ -391,7 +384,7 @@ public class MusicPatch else { // For official songs, we can just use the hash of the song internal name. - song.UniqueId = (int)(MurmurHash2.Hash(song.id) & 0xFFFF_FFF); + song.UniqueId = (int) (MurmurHash2.Hash(song.id) & 0xFFFF_FFF); } if (song.UniqueId <= SaveDataMax) @@ -402,6 +395,7 @@ public class MusicPatch throw new Exception($"Song \"{song.id}\" has collision with \"{uniqueIdToSong[song.UniqueId].id}\", bailing out..."); } + song.id += $"_custom_{song.UniqueId}"; customSongsList.Add(song); idToSong[song.id] = song; uniqueIdToSong[song.UniqueId] = song; @@ -436,16 +430,35 @@ public class MusicPatch #endregion - #region Loading and Initializing Data + #region Read in custom tracks + + [HarmonyPatch(typeof(DataManager), nameof(DataManager.Awake))] + [HarmonyPostfix] + [HarmonyWrapSafe] + private static void DataManager_PostFix(DataManager __instance) + { + if (__instance.MusicData != null) + { + MusicDataInterface_Postfix(__instance.MusicData); + SongDataInterface_Postfix(__instance.SongData); + } + } + + [HarmonyPatch(typeof(DataManager), nameof(DataManager.ExchangeWordData))] + [HarmonyPostfix] + [HarmonyWrapSafe] + private static void ExchangeWordData_PostFix(DataManager __instance, string language) + { + if (__instance.MusicData != null) + { + WordDataInterface_Postfix(__instance.WordData, language); + } + } /// /// This will handle loading the meta data of tracks /// - [HarmonyPatch(typeof(MusicDataInterface))] - [HarmonyPatch(MethodType.Constructor)] - [HarmonyPatch(new[] {typeof(string)})] - [HarmonyPostfix] - private static void MusicDataInterface_Postfix(MusicDataInterface __instance, string path) + private static void MusicDataInterface_Postfix(MusicDataInterface __instance) { try { @@ -466,7 +479,7 @@ public class MusicPatch if (song == null) continue; - musicInfoAccessors.Add(new MusicDataInterface.MusicInfoAccesser( + var musicInfo = new MusicDataInterface.MusicInfoAccesser( song.UniqueId, // From SongInstance, as we always recalculate it now song.id, $"song_{song.id}", @@ -474,7 +487,8 @@ public class MusicPatch song.genreNo, true, // We always want to mark songs as DLC, otherwise ranked games will be broken as you are gonna match songs that other people don't have false, - 0, false, + 0, + true, // can we capture footage 2, // Always mark custom songs as "both players need to have this song to play it" new[] { @@ -511,13 +525,36 @@ public class MusicPatch song.scoreHard, song.scoreMania, song.scoreUra - })); + } +#if TAIKO_IL2CPP + , 0, // no idea what this is, going to mark them as default for now :) + string.Empty, // no idea what this is, going to mark them as default for now :) + string.Empty, // no idea what this is, going to mark them as default for now :) + false, // no idea what this is, going to mark them as default for now :), + new[] // no idea what this is, setting it to shinuti score + { + song.shinutiEasy, + song.shinutiNormal, + song.shinutiHard, + song.shinutiMania, + song.shinutiUra + }, new[] // no idea what this is, setting it to shinuti duet score + { + song.shinutiEasyDuet, + song.shinutiNormalDuet, + song.shinutiHardDuet, + song.shinutiManiaDuet, + song.shinutiUraDuet + } +#endif + ); + musicInfoAccessors.Add(musicInfo); } #endregion - // sort this - musicInfoAccessors.Sort((a, b) => a.Order - b.Order); + BubbleSort(musicInfoAccessors, (a, b) => a.Order - b.Order); + __instance.musicInfoAccessers = musicInfoAccessors; } catch (Exception e) { @@ -525,15 +562,38 @@ public class MusicPatch } } +#if TAIKO_IL2CPP + // this is to work around sorting unmanaged lists + public static void BubbleSort(Il2CppSystem.Collections.Generic.List data, Func compare) where T : Object + { + var tempList = new System.Collections.Generic.List(data.ToArray()); + + BubbleSort(tempList, compare); + data.Clear(); + foreach (var temp in tempList) + data.Add(temp); + } +#endif + // this is to work around sorting unmanaged lists + public static void BubbleSort(System.Collections.Generic.List data, Func compare) + { + int i, j; + int N = data.Count; + + for (j = N - 1; j > 0; j--) + { + for (i = 0; i < j; i++) + { + if (compare(data[i], data[i + 1]) > 0) + (data[i + 1], data[i]) = (data[i], data[i + 1]); + } + } + } /// /// This will handle loading the preview data of tracks /// - [HarmonyPatch(typeof(SongDataInterface))] - [HarmonyPatch(MethodType.Constructor)] - [HarmonyPatch(new[] {typeof(string)})] - [HarmonyPostfix] - private static void SongDataInterface_Postfix(SongDataInterface __instance, string path) + private static void SongDataInterface_Postfix(SongDataInterface __instance) { // This is where the metadata for tracks are read in our attempt to allow custom tracks will be to add additional metadata to the list that is created Log.LogInfo("Injecting custom song preview data"); @@ -554,17 +614,14 @@ public class MusicPatch musicInfoAccessors.Add(new SongDataInterface.SongInfoAccesser(customTrack.id, customTrack.previewPos, customTrack.fumenOffsetPos)); } - } + __instance.songInfoAccessers = musicInfoAccessors; + } /// /// This will handle loading the localisation of tracks /// - [HarmonyPatch(typeof(WordDataInterface))] - [HarmonyPatch(MethodType.Constructor)] - [HarmonyPatch(new[] {typeof(string), typeof(string)})] - [HarmonyPostfix] - private static void WordDataInterface_Postfix(WordDataInterface __instance, string path, string language) + private static void WordDataInterface_Postfix(WordDataInterface __instance, string language) { // This is where the metadata for tracks are read in our attempt to allow custom tracks will be to add additional metadata to the list that is created var customSongs = GetCustomSongs(); @@ -583,13 +640,17 @@ public class MusicPatch // override the existing songs if we're using a custom language if (languageValue != language) { - var wordListInfoRead = (ReadData) typeof(WordDataInterface).GetField("wordListInfoRead", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance); + var wordListInfoRead = (ReadData) AccessTools.Property(typeof(WordDataInterface), nameof(WordDataInterface.wordListInfoRead)).GetValue(__instance); var dictionary = wordListInfoRead.InfomationDatas.ToList(); for (int i = 0; i < musicInfoAccessors.Count; i++) { const string songDetailPrefix = "song_detail_"; +#if TAIKO_IL2CPP + var entry = musicInfoAccessors._items[i]; +#elif TAIKO_MONO var entry = musicInfoAccessors[i]; +#endif var index = entry.Key.IndexOf(songDetailPrefix, StringComparison.Ordinal); if (index < 0) continue; @@ -609,7 +670,16 @@ public class MusicPatch if (songEntry == null || subtitleEntry == null || detailEntry == null) continue; - musicInfoAccessors.RemoveAll(x => x.Key == songKey || x.Key == subtitleKey || x.Key == detailKey); + for (int j = musicInfoAccessors.Count - 1; j >= 0; j--) + { +#if TAIKO_IL2CPP + var info = musicInfoAccessors._items[i]; +#elif TAIKO_MONO + var info = musicInfoAccessors[i]; +#endif + if (info.Key == songKey || info.Key == subtitleKey || info.Key == detailKey) + musicInfoAccessors.RemoveAt(i); + } var songValues = GetValuesWordList(songEntry); var subtitleValues = GetValuesWordList(songEntry); @@ -634,6 +704,8 @@ public class MusicPatch } } + __instance.wordListInfoAccessers = musicInfoAccessors; + (string text, int font) GetValuesWordList(WordListInfo wordListInfo) { string text; @@ -778,7 +850,7 @@ public class MusicPatch /// /// When loading, make sure to ignore custom tracks, as their IDs will be different /// - [HarmonyPatch(typeof(SongSelectManager), "LoadSongList")] + [HarmonyPatch(typeof(SongSelectManager), nameof(SongSelectManager.LoadSongList))] [HarmonyPrefix] private static bool LoadSongList_Prefix(SongSelectManager __instance) { @@ -791,15 +863,11 @@ public class MusicPatch #region Setup instanced variables / methods - var playDataMgr = (PlayDataManager) typeof(SongSelectManager).GetField("playDataMgr", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(__instance); - var musicInfoAccess = (MusicDataInterface.MusicInfoAccesser[]) typeof(SongSelectManager).GetField("musicInfoAccess", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(__instance); - var enableKakuninSong = (bool) (typeof(SongSelectManager).GetField("enableKakuninSong", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(__instance) ?? false); - - var getLocalizedTextMethodInfo = typeof(SongSelectManager).GetMethod("GetLocalizedText", BindingFlags.NonPublic | BindingFlags.Instance); - var getLocalizedText = (string x) => (string) getLocalizedTextMethodInfo?.Invoke(__instance, new object[] {x, string.Empty}); - - var updateSortCategoryInfoMethodInfo = typeof(SongSelectManager).GetMethod("UpdateSortCategoryInfo", BindingFlags.NonPublic | BindingFlags.Instance); - var updateSortCategoryInfo = (DataConst.SongSortType x) => updateSortCategoryInfoMethodInfo?.Invoke(__instance, new object[] {x}); + var playDataMgr = __instance.playDataMgr; + var musicInfoAccess = __instance.musicInfoAccess; + var enableKakuninSong = __instance.enableKakuninSong; + var getLocalizedText = (string x) => __instance.GetLocalizedText(x); + var updateSortCategoryInfo = __instance.UpdateSortCategoryInfo; #endregion @@ -809,10 +877,13 @@ public class MusicPatch return true; } - __instance.UnsortedSongList.Clear(); + var unsortedSongList = __instance.UnsortedSongList; + unsortedSongList.Clear(); +#if TAIKO_IL2CPP + playDataMgr.GetMusicInfoExAllIl2cpp(0, out var dst); +#elif TAIKO_MONO playDataMgr.GetMusicInfoExAll(0, out var dst); - playDataMgr.GetPlayerInfo(0, out var _); - _ = TaikoSingletonMonoBehaviour.Instance.newFriends.Count; +#endif for (int i = 0; i < 8; i++) { for (int j = 0; j < musicInfoAccess.Length; j++) @@ -853,7 +924,7 @@ public class MusicPatch song2.HighScores2P = new SongSelectManager.Score[5]; song2.DLC = musicInfoAccess[j].IsDLC; song2.Price = musicInfoAccess[j].Price; - song2.IsCap = musicInfoAccess[j].IsCap; + song2.IsCap = true; // should DVR Capture be enabled? if (TaikoSingletonMonoBehaviour.Instance.MyDataManager.SongData.GetInfo(song2.Id) != null) { song2.AudioStartMS = TaikoSingletonMonoBehaviour.Instance.MyDataManager.SongData.GetInfo(song2.Id).PreviewPos; @@ -891,36 +962,59 @@ public class MusicPatch for (int k = 0; k < 5; k++) { - playDataMgr.GetPlayerRecordInfo(0, musicInfoAccess[j].UniqueId, (EnsoData.EnsoLevelType) k, out var dst4); + GetPlayerRecordInfo(playDataMgr, 0, musicInfoAccess[j].UniqueId, (EnsoData.EnsoLevelType) k, out var dst4); song2.NotPlayed[k] = dst4.playCount <= 0; song2.NotCleared[k] = dst4.crown < DataConst.CrownType.Silver; song2.NotFullCombo[k] = dst4.crown < DataConst.CrownType.Gold; song2.NotDondaFullCombo[k] = dst4.crown < DataConst.CrownType.Rainbow; +#if TAIKO_IL2CPP + var highScore1 = song2.HighScores[k]; + highScore1.hiScoreRecordInfos = dst4.normalHiScore; + highScore1.crown = dst4.crown; + song2.HighScores[k] = highScore1; +#elif TAIKO_MONO song2.HighScores[k].hiScoreRecordInfos = dst4.normalHiScore; song2.HighScores[k].crown = dst4.crown; - playDataMgr.GetPlayerRecordInfo(1, musicInfoAccess[j].UniqueId, (EnsoData.EnsoLevelType) k, out var dst5); +#endif + + GetPlayerRecordInfo(playDataMgr, 1, musicInfoAccess[j].UniqueId, (EnsoData.EnsoLevelType) k, out var dst5); song2.NotPlayed2P[k] = dst5.playCount <= 0; song2.NotCleared2P[k] = dst4.crown < DataConst.CrownType.Silver; song2.NotFullCombo2P[k] = dst5.crown < DataConst.CrownType.Gold; song2.NotDondaFullCombo2P[k] = dst5.crown < DataConst.CrownType.Rainbow; + +#if TAIKO_IL2CPP + var highScore2 = song2.HighScores2P[k]; + highScore2.hiScoreRecordInfos = dst5.normalHiScore; + highScore2.crown = dst5.crown; + song2.HighScores2P[k] = highScore2; +#elif TAIKO_MONO song2.HighScores2P[k].hiScoreRecordInfos = dst5.normalHiScore; song2.HighScores2P[k].crown = dst5.crown; +#endif } song2.NewSong = isNew && (song2.DLC || song2.Price > 0); } - __instance.UnsortedSongList.Add(song2); + unsortedSongList.Add(song2); } } - var unsortedSongList = (from song in __instance.UnsortedSongList - orderby song.SongGenre, song.Order - select song).ToList(); - typeof(SongSelectManager).GetProperty(nameof(SongSelectManager.UnsortedSongList), BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance)?.SetValue(__instance, unsortedSongList); + BubbleSort(unsortedSongList, (a, b) => + { + var value = a.SongGenre.CompareTo(b.SongGenre); + if (value != 0) + return value; - var songList = new List(__instance.UnsortedSongList); - typeof(SongSelectManager).GetProperty(nameof(SongSelectManager.SongList), BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance)?.SetValue(__instance, songList); + return a.Order - b.Order; + }); + + __instance.SongList.Clear(); + foreach (var song in unsortedSongList) + __instance.SongList.Add(song); + + __instance.UnsortedSongList = unsortedSongList; updateSortCategoryInfo(DataConst.SongSortType.Genre); return false; @@ -933,14 +1027,16 @@ public class MusicPatch [HarmonyPrefix] private static bool SaveFavotiteSongs_Prefix(SongSelectManager __instance) { - var playDataMgr = (PlayDataManager) typeof(SongSelectManager).GetField("playDataMgr", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(__instance); - - playDataMgr.GetMusicInfoExAll(0, out var dst); +#if TAIKO_IL2CPP + __instance.playDataMgr.GetMusicInfoExAllIl2cpp(0, out var dst); +#elif TAIKO_MONO + __instance.playDataMgr.GetMusicInfoExAll(0, out var dst); +#endif var customSaveData = GetCustomSaveData(); bool saveCustomData = false; int num = 0; - foreach (SongSelectManager.Song unsortedSong in __instance.UnsortedSongList) + foreach (var unsortedSong in __instance.UnsortedSongList) { num++; if (uniqueIdToSong.ContainsKey(unsortedSong.UniqueId)) @@ -952,8 +1048,11 @@ public class MusicPatch } else { - dst[unsortedSong.UniqueId].favorite = unsortedSong.Favorite; - playDataMgr.SetMusicInfoEx(0, unsortedSong.UniqueId, ref dst[unsortedSong.UniqueId], num >= __instance.UnsortedSongList.Count); + var entry = dst[unsortedSong.UniqueId]; + entry.favorite = unsortedSong.Favorite; + dst[unsortedSong.UniqueId] = entry; + + __instance.playDataMgr.SetMusicInfoEx(0, unsortedSong.UniqueId, ref entry, num >= __instance.UnsortedSongList.Count); } } @@ -970,75 +1069,76 @@ public class MusicPatch [HarmonyPrefix] private static bool EnsoConfigSubmit_Prefix(CourseSelect __instance) { - var songInfoType = typeof(CourseSelect).GetNestedType("SongInfo", BindingFlags.NonPublic); - var scoreType = typeof(CourseSelect).GetNestedType("Score", BindingFlags.NonPublic); - var playerTypeEnumType = typeof(CourseSelect).GetNestedType("PlayerType", BindingFlags.NonPublic); + var settings = __instance.settings; + var playDataManager = __instance.playDataManager; + var ensoDataManager = __instance.ensoDataManager; - var settings = (EnsoData.Settings) typeof(CourseSelect).GetField("settings", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance); - var playDataManager = (PlayDataManager) typeof(CourseSelect).GetField("playDataManager", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance); - var ensoDataManager = (EnsoDataManager) typeof(CourseSelect).GetField("ensoDataManager", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance); + var selectedSongInfo = __instance.selectedSongInfo; + var ensoMode = __instance.ensoMode; + var ensoMode2P = __instance.ensoMode2P; + var selectedCourse = __instance.selectedCourse; + var selectedCourse2P = __instance.selectedCourse2P; + var status = __instance.status; - var selectedSongInfo = typeof(CourseSelect).GetField("selectedSongInfo", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance); - var ensoMode = (EnsoMode) typeof(CourseSelect).GetField("ensoMode", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance); - var ensoMode2P = (EnsoMode) typeof(CourseSelect).GetField("ensoMode2P", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance); - var selectedCourse = (int) typeof(CourseSelect).GetField("selectedCourse", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance); - var selectedCourse2P = (int) typeof(CourseSelect).GetField("selectedCourse2P", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance); - var status = (SongSelectStatus) typeof(CourseSelect).GetField("status", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance); - - var SetSaveDataEnsoModeMethodInfo = typeof(CourseSelect).GetMethod("SetSaveDataEnsoMode", BindingFlags.NonPublic | BindingFlags.Instance); - var SetSaveDataEnsoMode = (object x) => (string) SetSaveDataEnsoModeMethodInfo.Invoke(__instance, new object[] {x}); - - var songUniqueId = (int) songInfoType.GetField("UniqueId").GetValue(selectedSongInfo); - - void SetSettings() => typeof(CourseSelect).GetField("settings", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(__instance, settings); + var songUniqueId = selectedSongInfo.UniqueId; settings.ensoType = EnsoData.EnsoType.Normal; settings.rankMatchType = EnsoData.RankMatchType.None; - settings.musicuid = (string) songInfoType.GetField("Id").GetValue(selectedSongInfo); + settings.musicuid = selectedSongInfo.Id; settings.musicUniqueId = songUniqueId; - settings.genre = (EnsoData.SongGenre) songInfoType.GetField("SongGenre").GetValue(selectedSongInfo); + settings.genre = (EnsoData.SongGenre) selectedSongInfo.SongGenre; settings.playerNum = 1; - settings.ensoPlayerSettings[0].neiroId = ensoMode.neiro; - settings.ensoPlayerSettings[0].courseType = (EnsoData.EnsoLevelType) selectedCourse; - settings.ensoPlayerSettings[0].speed = ensoMode.speed; - settings.ensoPlayerSettings[0].dron = ensoMode.dron; - settings.ensoPlayerSettings[0].reverse = ensoMode.reverse; - settings.ensoPlayerSettings[0].randomlv = ensoMode.randomlv; - settings.ensoPlayerSettings[0].special = ensoMode.special; + var player1Entry = settings.ensoPlayerSettings[0]; + player1Entry.neiroId = ensoMode.neiro; + player1Entry.courseType = (EnsoData.EnsoLevelType) selectedCourse; + player1Entry.speed = ensoMode.speed; + player1Entry.dron = ensoMode.dron; + player1Entry.reverse = ensoMode.reverse; + player1Entry.randomlv = ensoMode.randomlv; + player1Entry.special = ensoMode.special; - var array = (Array) songInfoType.GetField("HighScores").GetValue(selectedSongInfo); - settings.ensoPlayerSettings[0].hiScore = ((HiScoreRecordInfo) scoreType.GetField("hiScoreRecordInfos").GetValue(array.GetValue(selectedCourse))).score; + var array = selectedSongInfo.HighScores; + player1Entry.hiScore = array[selectedCourse].hiScoreRecordInfos.score; + settings.ensoPlayerSettings[0] = player1Entry; - SetSettings(); + __instance.settings = settings; if (status.Is2PActive) { - settings.ensoPlayerSettings[1].neiroId = ensoMode2P.neiro; - settings.ensoPlayerSettings[1].courseType = (EnsoData.EnsoLevelType) selectedCourse2P; - settings.ensoPlayerSettings[1].speed = ensoMode2P.speed; - settings.ensoPlayerSettings[1].dron = ensoMode2P.dron; - settings.ensoPlayerSettings[1].reverse = ensoMode2P.reverse; - settings.ensoPlayerSettings[1].randomlv = ensoMode2P.randomlv; - settings.ensoPlayerSettings[1].special = ensoMode2P.special; - TaikoSingletonMonoBehaviour.Instance.MyDataManager.PlayData.GetPlayerRecordInfo(1, songUniqueId, (EnsoData.EnsoLevelType) selectedCourse2P, out var dst); - settings.ensoPlayerSettings[1].hiScore = dst.normalHiScore.score; + var player2Entry = settings.ensoPlayerSettings[1]; + player2Entry.neiroId = ensoMode2P.neiro; + player2Entry.courseType = (EnsoData.EnsoLevelType) selectedCourse2P; + player2Entry.speed = ensoMode2P.speed; + player2Entry.dron = ensoMode2P.dron; + player2Entry.reverse = ensoMode2P.reverse; + player2Entry.randomlv = ensoMode2P.randomlv; + player2Entry.special = ensoMode2P.special; + GetPlayerRecordInfo(TaikoSingletonMonoBehaviour.Instance.MyDataManager.PlayData, 1, songUniqueId, (EnsoData.EnsoLevelType) selectedCourse2P, out var dst); + player2Entry.hiScore = dst.normalHiScore.score; settings.playerNum = 2; + settings.ensoPlayerSettings[1] = player2Entry; } settings.debugSettings.isTestMenu = false; settings.rankMatchType = EnsoData.RankMatchType.None; - settings.isRandomSelect = (bool) songInfoType.GetField("IsRandomSelect").GetValue(selectedSongInfo); - settings.isDailyBonus = (bool) songInfoType.GetField("IsDailyBonus").GetValue(selectedSongInfo); + settings.isRandomSelect = selectedSongInfo.IsRandomSelect; + settings.isDailyBonus = selectedSongInfo.IsDailyBonus; ensoMode.songUniqueId = settings.musicUniqueId; ensoMode.level = (EnsoData.EnsoLevelType) selectedCourse; - SetSettings(); - typeof(CourseSelect).GetField("ensoMode", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(__instance, ensoMode); - SetSaveDataEnsoMode(Enum.Parse(playerTypeEnumType, "Player1")); + __instance.settings = settings; + __instance.ensoMode = ensoMode; + __instance.SetSaveDataEnsoMode(CourseSelect.PlayerType.Player1); ensoMode2P.songUniqueId = settings.musicUniqueId; ensoMode2P.level = (EnsoData.EnsoLevelType) selectedCourse2P; - typeof(CourseSelect).GetField("ensoMode2P", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(__instance, ensoMode2P); - SetSaveDataEnsoMode(Enum.Parse(playerTypeEnumType, "Player2")); + __instance.ensoMode2P = ensoMode2P; + __instance.SetSaveDataEnsoMode(CourseSelect.PlayerType.Player2); + +#if TAIKO_IL2CPP + playDataManager.GetSystemOptionRemake(out var dst2); +#elif TAIKO_MONO playDataManager.GetSystemOption(out var dst2); +#endif + int deviceTypeIndex = EnsoDataManager.GetDeviceTypeIndex(settings.ensoPlayerSettings[0].inputDevice); settings.noteDispOffset = dst2.onpuDispLevels[deviceTypeIndex]; settings.noteDelay = dst2.onpuHitLevels[deviceTypeIndex]; @@ -1048,8 +1148,12 @@ public class MusicPatch settings.bgmVolume = TaikoSingletonMonoBehaviour.Instance.MySoundManager.GetVolume(SoundManager.SoundType.Bgm); settings.neiroVolume = TaikoSingletonMonoBehaviour.Instance.MySoundManager.GetVolume(SoundManager.SoundType.InGameNeiro); settings.effectLevel = (EnsoData.EffectLevel) dst2.qualityLevel; - SetSettings(); + __instance.settings = settings; +#if TAIKO_IL2CPP + ensoDataManager.SetSettingsRemake(ref settings); +#elif TAIKO_MONO ensoDataManager.SetSettings(ref settings); +#endif ensoDataManager.DecideSetting(); if (status.Is2PActive) { @@ -1068,9 +1172,16 @@ public class MusicPatch } else { +#if TAIKO_IL2CPP + playDataManager.GetMusicInfoExAllIl2cpp(0, out var dst3); +#elif TAIKO_MONO playDataManager.GetMusicInfoExAll(0, out var dst3); - dst3[songUniqueId].isNew = false; - playDataManager.SetMusicInfoEx(0, songUniqueId, ref dst3[songUniqueId]); +#endif + var entry = dst3[songUniqueId]; + entry.isNew = false; + dst3[songUniqueId] = entry; + + playDataManager.SetMusicInfoEx(0, songUniqueId, ref entry); } return false; @@ -1083,7 +1194,11 @@ public class MusicPatch [HarmonyPrefix] private static bool GetEnsoSettings_Prefix(KpiListCommon.MusicKpiInfo __instance) { +#if TAIKO_IL2CPP + TaikoSingletonMonoBehaviour.Instance.MyDataManager.EnsoData.CopySettingsRemake(out var dst); +#elif TAIKO_MONO TaikoSingletonMonoBehaviour.Instance.MyDataManager.EnsoData.CopySettings(out var dst); +#endif __instance.music_id = dst.musicuid; __instance.genre = (int) dst.genre; __instance.course_type = (int) dst.ensoPlayerSettings[0].courseType; @@ -1100,7 +1215,11 @@ public class MusicPatch __instance.sort_filter = (int) dst2.songFilterType; __instance.sort_favorite = (int) dst2.songFilterTypeFavorite; MusicDataInterface.MusicInfoAccesser[] array = TaikoSingletonMonoBehaviour.Instance.MyDataManager.MusicData.musicInfoAccessers.ToArray(); +#if TAIKO_IL2CPP + playData.GetMusicInfoExAllIl2cpp(0, out var dst3); +#elif TAIKO_MONO playData.GetMusicInfoExAll(0, out var dst3); +#endif #region edited code @@ -1123,26 +1242,54 @@ public class MusicPatch #endregion +#if TAIKO_IL2CPP + playData.GetPlayerInfoRemake(0, out var dst4); +#elif TAIKO_MONO playData.GetPlayerInfo(0, out var dst4); +#endif __instance.current_coins_num = dst4.donCoin; __instance.total_coins_num = dst4.getCoinsInTotal; +#if TAIKO_IL2CPP + playData.GetRankMatchSeasonRecordInfoRemake(0, 0, out var dst5); +#elif TAIKO_MONO playData.GetRankMatchSeasonRecordInfo(0, 0, out var dst5); +#endif __instance.rank_point = dst5.rankPointMax; return false; } - /// - /// Load scores from custom save data - /// - [HarmonyPatch(typeof(PlayDataManager), "GetPlayerRecordInfo")] - [HarmonyPrefix] - public static bool GetPlayerRecordInfo_Prefix(int playerId, int uniqueId, EnsoData.EnsoLevelType levelType, out EnsoRecordInfo dst, PlayDataManager __instance) + [HarmonyPatch(typeof(PlayDataManager), nameof(PlayDataManager.IsValueInRange))] + [HarmonyPostfix] + [HarmonyWrapSafe] + private static void IsValueInRange(int myValue, int minValue, int maxValue, ref bool __result) + { + // if the max value is the same as music max, hopefully we're validating song ids + // in which case return true if this is one of our songs + if (maxValue != DataConst.MusicMax) return; + + if (uniqueIdToSong.ContainsKey(myValue)) + __result = true; + } + + #region Methods with GetPlayerRecordInfo + + // this doesn't patch well, so I have to redo each method that uses it + // /// + // /// Load scores from custom save data + // /// + // [HarmonyPatch(typeof(PlayDataManager), nameof(PlayDataManager.GetPlayerRecordInfo))] + // [HarmonyPrefix] + public static void GetPlayerRecordInfo(PlayDataManager __instance, + int playerId, + int uniqueId, + EnsoData.EnsoLevelType levelType, + out EnsoRecordInfo dst) { if (!uniqueIdToSong.ContainsKey(uniqueId)) { - dst = new EnsoRecordInfo(); - return true; + __instance.GetPlayerRecordInfo(playerId, uniqueId, levelType, out dst); + return; } int num = (int) levelType; @@ -1158,9 +1305,194 @@ public class MusicPatch } dst = ensoData[num]; - return false; } + [HarmonyPatch(typeof(CourseSelect), nameof(CourseSelect.SetInfo))] + [HarmonyPostfix] + [HarmonyWrapSafe] + public static void SetInfo_Postfix( + CourseSelect __instance, + MusicDataInterface.MusicInfoAccesser song, + bool isRandomSelect, + bool isDailyBonus) + { + for (int levelType = 0; levelType < __instance.selectedSongInfo.HighScores.Length; ++levelType) + { + EnsoRecordInfo dst; + GetPlayerRecordInfo(__instance.playDataManager, 0, song.UniqueId, (EnsoData.EnsoLevelType) levelType, out dst); + var highScore = __instance.selectedSongInfo.HighScores[levelType]; + highScore.hiScoreRecordInfos = dst.normalHiScore; + highScore.crown = dst.crown; + __instance.selectedSongInfo.HighScores[levelType] = highScore; + } + } + + [HarmonyPatch(typeof(CourseSelect), nameof(CourseSelect.UpdateDiffCourseAnim))] + [HarmonyPostfix] + [HarmonyWrapSafe] + public static void UpdateDiffCourseAnim_Postfix(CourseSelect __instance) + { + int num = __instance.selectedSongInfo.Stars[4] == 0 ? 4 : 5; + for (int levelType = 0; levelType < num; ++levelType) + { + Animator iconCrown2 = __instance.diffCourseAnims[levelType].IconCrowns[1]; + if (__instance.status.Is2PActive) + { + GetPlayerRecordInfo(TaikoSingletonMonoBehaviour.Instance.MyDataManager.PlayData, 1, __instance.selectedSongInfo.UniqueId, (EnsoData.EnsoLevelType) levelType, out var dst); + switch (dst.crown) + { + case DataConst.CrownType.Silver: + iconCrown2.Play("Silver"); + break; + case DataConst.CrownType.Gold: + iconCrown2.Play("Gold"); + break; + case DataConst.CrownType.Rainbow: + iconCrown2.Play("Rainbow"); + break; + default: + iconCrown2.Play("None"); + break; + } + } + } + } + + [HarmonyPatch(typeof(EnsoGameManager), nameof(EnsoGameManager.SetResults))] + [HarmonyPostfix] + [HarmonyWrapSafe] + public static void SetResults_Postfix(EnsoGameManager __instance) + { + TaikoCoreFrameResults frameResults = __instance.ensoParam.GetFrameResults(); + for (int index = 0; index < __instance.settings.playerNum; ++index) + { + EnsoData.PlayerResult playerResult = __instance.ensoParam.GetPlayerResult(index); + var eachPlayer = frameResults.GetEachPlayer(index); + ref EachPlayer local = ref eachPlayer; + if (__instance.ensoParam.IsOnlineRankedMatch && index == 1) + __instance.SetEnsoInfoOnlineRecieve(ref local); + playerResult.level = __instance.settings.ensoPlayerSettings[index].courseType; + bool flag = frameResults.isAllOnpuEndPlayer[index]; + playerResult.resultType = (double) local.tamashii >= (double) local.constTamashiiNorm ? DataConst.ResultType.NormaClear : DataConst.ResultType.NormaFailer; + if (((playerResult.resultType != DataConst.ResultType.NormaClear ? 0 : (local.countFuka == 0U ? 1 : 0)) & (flag ? 1 : 0)) != 0) + playerResult.resultType = DataConst.ResultType.Fullcombo; + playerResult.combomax = (int) local.maxCombo; + playerResult.rendatotal = (int) local.countRenda; + playerResult.hits = (int) local.countRyo + (int) local.countKa; + playerResult.score = (int) local.score; + playerResult.tamashii = local.tamashii; + playerResult.isAllOnpuEnd = flag; + GetPlayerRecordInfo(__instance.playDataMgr, index, __instance.settings.musicUniqueId, __instance.settings.ensoPlayerSettings[index].courseType, out var dst1); + playerResult.isHiScore = (int) local.score > dst1.normalHiScore.score; + playerResult.crown = DataConst.CrownType.None; + for (int crown = (int) playerResult.crown; (DataConst.CrownType) crown > dst1.crown; --crown) + playerResult.isNewCrown[crown] = true; + + if (index == 0) + TaikoSingletonMonoBehaviour.Instance.CosmosLib._kpiListCommon._musicKpiInfo.SetEnsoResult1p(playerResult); + else + TaikoSingletonMonoBehaviour.Instance.CosmosLib._kpiListCommon._musicKpiInfo.SetEnsoResult2p(playerResult); + } + } + + [HarmonyPatch(typeof(SongSelectRankingBestScoreDisplay), nameof(SongSelectRankingBestScoreDisplay.SetMyInfo))] + [HarmonyPostfix] + [HarmonyWrapSafe] + public static void SetMyInfo_Postfix(SongSelectRankingBestScoreDisplay __instance, int musicUniqueId, EnsoData.EnsoLevelType ensoLevel) + { + GetPlayerRecordInfo(TaikoSingletonMonoBehaviour.Instance.MyDataManager.PlayData, 0, musicUniqueId, ensoLevel, out var dst); + __instance.UpdateScoreDisplay(dst.normalHiScore); + } + + [HarmonyPatch(typeof(CourseSelectScoreDisplay), nameof(CourseSelectScoreDisplay.UpdateDisplay))] + [HarmonyPostfix] + [HarmonyWrapSafe] + public static void UpdateDisplay_Postfix(CourseSelectScoreDisplay __instance, int musicUniqueId, EnsoData.EnsoLevelType levelType) + { + GetPlayerRecordInfo(TaikoSingletonMonoBehaviour.Instance.MyDataManager.PlayData, __instance.playerType == DataConst.PlayerType.Player_1 ? 0 : 1, musicUniqueId, levelType, out var dst); + var normalHiScore = dst.normalHiScore; + for (int index = 0; index < 6; ++index) + { + int num = 0; + switch (index) + { + case 0: + num = normalHiScore.score; + break; + case 1: + num = (int) normalHiScore.excellent; + break; + case 2: + num = (int) normalHiScore.good; + break; + case 3: + num = (int) normalHiScore.bad; + break; + case 4: + num = (int) normalHiScore.combo; + break; + case 5: + num = (int) normalHiScore.renda; + break; + } + + __instance.numDisplays[index].NumberPlayer.SetValue(num); + } + } + + [HarmonyPatch(typeof(SongSelectScoreDisplay), nameof(SongSelectScoreDisplay.UpdateCrownNumDisplay))] + [HarmonyPostfix] + [HarmonyWrapSafe] + public static void UpdateCrownNumDisplay_Postfix(SongSelectScoreDisplay __instance, int playerId) + { + PlayDataManager playData = TaikoSingletonMonoBehaviour.Instance.MyDataManager.PlayData; + var musicInfoAccessers = TaikoSingletonMonoBehaviour.Instance.MyDataManager.MusicData.musicInfoAccessers; + int[,] numArray = new int[3, 5]; + foreach (MusicDataInterface.MusicInfoAccesser musicInfoAccesser in musicInfoAccessers) + { + int num = musicInfoAccesser.Stars[4] > 0 ? 5 : 4; + for (int levelType = 0; levelType < num; ++levelType) + { + GetPlayerRecordInfo(playData, playerId, musicInfoAccesser.UniqueId, (EnsoData.EnsoLevelType) levelType, out var dst); + switch (dst.crown) + { + case DataConst.CrownType.Silver: + ++numArray[0, levelType]; + break; + case DataConst.CrownType.Gold: + ++numArray[1, levelType]; + break; + case DataConst.CrownType.Rainbow: + ++numArray[2, levelType]; + break; + } + } + } + + for (int index = 0; index < 5; ++index) + { + __instance.crownNums[index].CrownNumbers[0].SetNum(numArray[0, index]); + __instance.crownNums[index].CrownNumbers[1].SetNum(numArray[1, index]); + __instance.crownNums[index].CrownNumbers[2].SetNum(numArray[2, index]); + } + } + + [HarmonyPatch(typeof(SongSelectScoreDisplay), nameof(SongSelectScoreDisplay.UpdateScoreDisplay))] + [HarmonyPostfix] + [HarmonyWrapSafe] + public static void UpdateScoreDisplay_Postfix(SongSelectScoreDisplay __instance, int playerId, int musicUniqueId, bool enableUra) + { + var num = enableUra ? 5 : 4; + + for (int levelType = 0; levelType < num; ++levelType) + { + GetPlayerRecordInfo(TaikoSingletonMonoBehaviour.Instance.MyDataManager.PlayData, playerId, musicUniqueId, (EnsoData.EnsoLevelType) levelType, out var dst); + __instance.bestScores[levelType].RootObject.SetValue(dst.normalHiScore.score); + } + } + + #endregion + /// /// Save scores to custom save data /// @@ -1221,27 +1553,53 @@ public class MusicPatch } } - [HarmonyPatch(typeof(SongSelectManager), "Start")] + [HarmonyPatch(typeof(SongSelectManager), nameof(SongSelectManager.Start))] [HarmonyPostfix] + [HarmonyWrapSafe] public static void Start_Postfix(SongSelectManager __instance) { + if (__instance.SongList == null) + return; + Plugin.Instance.StartCustomCoroutine(SetSelectedSongAsync()); IEnumerator SetSelectedSongAsync() { - while (__instance.SongList.Count == 0 || (bool) typeof(SongSelectManager).GetField("isAsyncLoading", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance)) + yield return null; + while (__instance.SongList.Count == 0 || __instance.isAsyncLoading) yield return null; // if the song id is < 0 then fix the selected song index - var ensoMode = (EnsoMode) typeof(SongSelectManager).GetField("ensoMode", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance); - var lastPlaySongId = ensoMode.songUniqueId; + var lastPlaySongId = GetCustomSaveData().LastSongID; + if (lastPlaySongId == 0) + yield break; + + var songIndex = -1; + + for (int i = 0; i < __instance.SongList.Count; i++) + { +#if TAIKO_IL2CPP + var song = (SongSelectManager.Song) __instance.SongList[(Index) i]; +#elif TAIKO_MONO + var song = __instance.SongList[i]; +#endif + if (song.UniqueId != lastPlaySongId) + continue; + + songIndex = i; + } - var songIndex = __instance.SongList.IndexOf(__instance.SongList.FirstOrDefault(song => song.UniqueId == lastPlaySongId)); if (songIndex < 0) yield break; - typeof(SongSelectManager).GetProperty("SelectedSongIndex").SetValue(__instance, songIndex); - __instance.SortSongList(ensoMode.songSortCourse, ensoMode.songSortType, ensoMode.songFilterType, ensoMode.songFilterTypeFavorite); + __instance.SelectedSongIndex = songIndex; + __instance.songPlayer.Stop(true); + __instance.songPlayer.Dispose(); + __instance.isSongLoadRequested = true; + __instance.UpdateScoreDisplay(); + __instance.UpdateKanbanSurface(); + __instance.UpdateSortBarSurface(); + __instance.UpdateScoreDisplay(); } } @@ -1331,20 +1689,35 @@ public class MusicPatch #region Read Fumen - private static readonly Regex fumenFilePathRegex = new Regex("(?.*?)_(?[ehmnx])(_(?[12]))?.bin"); + private static readonly Regex fumenFilePathRegex = new Regex("(?.*?_custom_\\d*?)_(?[ehmnx])(_(?[12]))?.bin"); + private static readonly System.Collections.Generic.Dictionary pathToData = new System.Collections.Generic.Dictionary(); - private static readonly Dictionary playerToFumenData = new Dictionary(); - - /// - /// Read unencrypted Fumen files, save them to - /// - /// - private static unsafe bool Read_Prefix(string filePath, ref bool __result, object __instance) + [HarmonyPatch(typeof(Cryptgraphy), nameof(Cryptgraphy.ReadAllAesAndGZipBytes))] + [HarmonyPrefix] + private static bool ReadAllAesAndGZipBytes_Prefix(string path, Cryptgraphy.AesKeyType type, + #if TAIKO_IL2CPP + ref Il2CppStructArray __result + #elif TAIKO_MONO + ref byte[] __result + #endif + ) { - var type = typeof(FumenLoader).GetNestedType("PlayerData", BindingFlags.NonPublic); + if (pathToData.TryGetValue(path, out var data)) + { + __result = data; + return false; + } + return true; + } + + [HarmonyPatch(typeof(FumenLoader.PlayerData), nameof(FumenLoader.PlayerData.Read))] + [HarmonyPrefix] + private static void Read_Prefix(FumenLoader.PlayerData __instance, ref string filePath) + { + GetCustomSaveData().LastSongID = 0; if (File.Exists(filePath)) - return true; + return; // if the file doesn't exist, perhaps it's a custom song? var fileName = Path.GetFileName(filePath); @@ -1352,7 +1725,7 @@ public class MusicPatch if (!match.Success) { Log.LogError($"Cannot interpret {fileName}"); - return true; + return; } // get song id @@ -1363,9 +1736,11 @@ public class MusicPatch if (!idToSong.TryGetValue(songId, out var songInstance)) { Log.LogError($"Cannot find song with id: {songId}"); - return true; + return; } + GetCustomSaveData().LastSongID = songInstance.UniqueId; + SaveCustomData(); var path = songInstance.FolderPath; var songName = songInstance.SongName; @@ -1373,20 +1748,17 @@ public class MusicPatch if (files.Length == 0) { Log.LogError($"Cannot find fumen at {path}"); - return true; + return; } - var newPath = GetPathOfBestFumen(); - if (!File.Exists(newPath)) + var customPath = GetPathOfBestFumen(); + if (!File.Exists(customPath)) { - Log.LogError($"Cannot find fumen for {newPath}"); - return true; + Log.LogError($"Cannot find fumen for {customPath}"); + return; } - type.GetMethod("Dispose").Invoke(__instance, new object[] { }); - type.GetField("fumenPath").SetValue(__instance, newPath); - - byte[] array = File.ReadAllBytes(newPath); + byte[] array = File.ReadAllBytes(customPath); if (songInstance.areFilesGZipped) { using var memoryStream = new MemoryStream(array); @@ -1394,22 +1766,15 @@ public class MusicPatch using var gzipStream = new GZipStream(memoryStream, CompressionMode.Decompress); gzipStream.CopyTo(destination); array = destination.ToArray(); + + pathToData[customPath] = array; + } + else + { + pathToData[customPath] = array; } - var fumenSize = array.Length; - type.GetField("fumenSize").SetValue(__instance, fumenSize); - - var fumenData = UnsafeUtility.Malloc(fumenSize, 16, Allocator.Persistent); - type.GetField("fumenData").SetValue(__instance, (IntPtr) fumenData); - - Marshal.Copy(array, 0, (IntPtr) fumenData, fumenSize); - - type.GetField("isReadEnd").SetValue(__instance, true); - type.GetField("isReadSucceed").SetValue(__instance, true); - __result = true; - - playerToFumenData[__instance] = (IntPtr) fumenData; - return false; + filePath = customPath; string GetPathOfBestFumen() { @@ -1429,7 +1794,7 @@ public class MusicPatch Difficulty difficultyEnum = (Difficulty) Enum.Parse(typeof(Difficulty), difficulty); int difficultyInt = (int) difficultyEnum; - var checkDifficulties = new List(); + var checkDifficulties = new System.Collections.Generic.List(); for (int i = 1; i < (int) Difficulty.Count; i++) { @@ -1474,105 +1839,117 @@ public class MusicPatch private static Difficulty[] AllDifficulties = (Difficulty[]) Enum.GetValues(typeof(Difficulty)); - /// - /// When asking to get a Fumen, used the ones we stored above - /// - [HarmonyPatch(typeof(FumenLoader), "GetFumenData")] - [HarmonyPrefix] - public static unsafe bool GetFumenData_Prefix(int player, ref void* __result, FumenLoader __instance) - { - var settings = (EnsoData.Settings) typeof(FumenLoader).GetField("settings", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance); - var playerData = (Array) typeof(FumenLoader).GetField("playerData", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance); - - if (player >= 0 && player < settings.playerNum) - { - if (playerToFumenData.TryGetValue(playerData.GetValue(player), out var data)) - { - __result = (void*) data; - return false; - } - } - - // try loading the actual data - return true; - } - #endregion #region Read Song - private static readonly Regex musicFilePathRegex = new Regex("^song_(?.*?)$"); + private static readonly Regex musicFilePathRegex = new Regex("^song_(?.*?_custom_\\d*?)$"); /// /// Read an unencrypted song "asynchronously" (it does it instantly, we should have fast enough PCs right?) /// /// - [HarmonyPatch(typeof(CriPlayer), "LoadAsync")] - [HarmonyPostfix] - public static void LoadAsync_Postfix(CriPlayer __instance) + [HarmonyPatch(typeof(CriPlayer), nameof(CriPlayer.LoadAsync))] + [HarmonyPrefix] + [HarmonyWrapSafe] + public static bool LoadAsync_Postfix(CriPlayer __instance, +#if TAIKO_IL2CPP + ref Il2CppSystem.Collections.IEnumerator __result +#elif TAIKO_MONO + ref IEnumerator __result +#endif + ) { + var sheetName = __instance.CueSheetName; + var path = UnityEngine.Application.streamingAssetsPath + "/sound/" + sheetName + ".bin"; + + if (File.Exists(path)) + return true; + + var match = musicFilePathRegex.Match(sheetName); + if (!match.Success) + { + Log.LogError($"Cannot interpret {sheetName}"); + return true; + } + + var songName = match.Groups["songName"].Value; + if (!idToSong.TryGetValue(songName, out var songInstance)) + { + Log.LogError($"Cannot find song : {songName}"); + return true; + } + + __instance.isLoadingAsync = true; + __instance.isCancelLoadingAsync = false; + __instance.IsPrepared = false; + __instance.IsLoadSucceed = false; + __instance.LoadingState = CriPlayer.LoadingStates.Loading; + __instance.LoadTime = -1f; + __instance.loadStartTime = UnityEngine.Time.time; + // Run this on the next frame - Plugin.Instance.StartCustomCoroutine(LoadAsync()); +#if TAIKO_IL2CPP + __result = LoadAsync().WrapToIl2Cpp(); +#elif TAIKO_MONO + __result = LoadAsync(); +#endif + return false; IEnumerator LoadAsync() { yield return null; - var sheetName = __instance.CueSheetName; - var path = Application.streamingAssetsPath + "/sound/" + sheetName + ".bin"; - - if (File.Exists(path)) - yield break; - - typeof(CriPlayer).GetField("isLoadingAsync", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(__instance, true); - typeof(CriPlayer).GetField("isCancelLoadingAsync", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(__instance, false); - typeof(CriPlayer).GetProperty("IsPrepared").SetValue(__instance, false); - typeof(CriPlayer).GetProperty("IsLoadSucceed").SetValue(__instance, false); - typeof(CriPlayer).GetProperty("LoadingState").SetValue(__instance, CriPlayer.LoadingStates.Loading); - typeof(CriPlayer).GetProperty("LoadTime").SetValue(__instance, -1f); - typeof(CriPlayer).GetField("loadStartTime", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(__instance, Time.time); - - var match = musicFilePathRegex.Match(sheetName); - if (!match.Success) - { - Log.LogError($"Cannot interpret {sheetName}"); - yield break; - } - - var songName = match.Groups["songName"].Value; - if (!idToSong.TryGetValue(songName, out var songInstance)) - { - Log.LogError($"Cannot find song : {songName}"); - yield break; - } - + var newPath = Path.Combine(songInstance.FolderPath, $"{sheetName.Replace(songName, songInstance.SongName)}.bin"); - - var bytes = File.ReadAllBytes(newPath); - if (songInstance.areFilesGZipped) + var task = Task.Run(async () => { - using var memoryStream = new MemoryStream(bytes); - using var destination = new MemoryStream(); - using var gzipStream = new GZipStream(memoryStream, CompressionMode.Decompress); - gzipStream.CopyTo(destination); - bytes = destination.ToArray(); - } + try + { + var bytes = File.ReadAllBytes(newPath); + if (songInstance.areFilesGZipped) + { + using var memoryStream = new MemoryStream(bytes); + using var destination = new MemoryStream(); + using var gzipStream = new GZipStream(memoryStream, CompressionMode.Decompress); + await gzipStream.CopyToAsync(destination); + bytes = destination.ToArray(); + } + return bytes; + } + catch (Exception e) + { + Log.LogError(e); + return null; + } + }); + + do + { + yield return null; + } while (!task.IsCompleted); + + var bytes = task.Result; var cueSheet = CriAtom.AddCueSheetAsync(sheetName, bytes, null); - typeof(CriPlayer).GetProperty("CueSheet").SetValue(__instance, cueSheet); + + __instance.CueSheet = cueSheet; if (cueSheet != null) { - typeof(CriPlayer).GetField("isLoadingAsync", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(__instance, false); - typeof(CriPlayer).GetProperty("IsLoadSucceed").SetValue(__instance, true); + while (cueSheet.IsLoading) + yield return null; - typeof(CriPlayer).GetProperty("LoadingState").SetValue(__instance, CriPlayer.LoadingStates.Finished); - typeof(CriPlayer).GetProperty("LoadTime").SetValue(__instance, 0); + __instance.isLoadingAsync = false; + __instance.IsLoadSucceed = true; + __instance.LoadingState = CriPlayer.LoadingStates.Finished; + __instance.LoadTime = 0; + yield break; } Log.LogError($"Could not load music"); - typeof(CriPlayer).GetProperty("LoadingState").SetValue(__instance, CriPlayer.LoadingStates.Finished); - typeof(CriPlayer).GetField("isLoadingAsync", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(__instance, false); + __instance.LoadingState = CriPlayer.LoadingStates.Finished; + __instance.isLoadingAsync = false; } } @@ -1607,15 +1984,15 @@ public class MusicPatch var newPath = Path.Combine(songInstance.FolderPath, $"{sheetName.Replace(songName, songInstance.SongName)}.bin"); // load custom song - typeof(CriPlayer).GetProperty("IsPrepared").SetValue(__instance, false); - typeof(CriPlayer).GetProperty("LoadingState").SetValue(__instance, CriPlayer.LoadingStates.Loading); - typeof(CriPlayer).GetProperty("IsLoadSucceed").SetValue(__instance, false); - typeof(CriPlayer).GetProperty("LoadTime").SetValue(__instance, -1f); - typeof(CriPlayer).GetField("loadStartTime", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(__instance, Time.time); + __instance.IsPrepared = false; + __instance.LoadingState = CriPlayer.LoadingStates.Loading; + __instance.IsLoadSucceed = false; + __instance.LoadTime = -1f; + __instance.loadStartTime = Time.time; if (sheetName == "") { - typeof(CriPlayer).GetProperty("LoadingState").SetValue(__instance, CriPlayer.LoadingStates.Finished); + __instance.LoadingState = CriPlayer.LoadingStates.Finished; __result = false; return false; } @@ -1631,7 +2008,7 @@ public class MusicPatch } var cueSheet = CriAtom.AddCueSheetAsync(sheetName, bytes, null); - typeof(CriPlayer).GetProperty("CueSheet").SetValue(__instance, cueSheet); + __instance.CueSheet = cueSheet; if (cueSheet != null) { @@ -1639,7 +2016,7 @@ public class MusicPatch return false; } - typeof(CriPlayer).GetProperty("LoadingState").SetValue(__instance, CriPlayer.LoadingStates.Finished); + __instance.LoadingState = CriPlayer.LoadingStates.Finished; __result = false; return false; } @@ -1651,8 +2028,9 @@ public class MusicPatch [Serializable] public class CustomMusicSaveDataBody { - public Dictionary CustomTrackToMusicInfoEx = new(); - public Dictionary CustomTrackToEnsoRecordInfo = new(); + public int LastSongID; + public System.Collections.Generic.Dictionary CustomTrackToMusicInfoEx = new(); + public System.Collections.Generic.Dictionary CustomTrackToEnsoRecordInfo = new(); } /// @@ -1661,18 +2039,20 @@ public class MusicPatch [Serializable] public class CustomMusicSaveDataBodySerializable { - [JsonProperty("m")] public Dictionary CustomTrackToMusicInfoEx = new(); + [JsonProperty("l")] public int LastSongID; + + [JsonProperty("m")] public System.Collections.Generic.Dictionary CustomTrackToMusicInfoEx = new(); [JsonProperty("CustomTrackToMusicInfoEx")] - public Dictionary CustomTrackToMusicInfoEx_v0 + public System.Collections.Generic.Dictionary CustomTrackToMusicInfoEx_v0 { set => CustomTrackToMusicInfoEx = value; } - [JsonProperty("r")] public Dictionary CustomTrackToEnsoRecordInfo = new(); + [JsonProperty("r")] public System.Collections.Generic.Dictionary CustomTrackToEnsoRecordInfo = new(); [JsonProperty("CustomTrackToEnsoRecordInfo")] - public Dictionary CustomTrackToEnsoRecordInfo_v0 + public System.Collections.Generic.Dictionary CustomTrackToEnsoRecordInfo_v0 { set => CustomTrackToEnsoRecordInfo = value; } @@ -1680,6 +2060,8 @@ public class MusicPatch public static explicit operator CustomMusicSaveDataBodySerializable(CustomMusicSaveDataBody m) { var result = new CustomMusicSaveDataBodySerializable(); + result.LastSongID = m.LastSongID; + foreach (var musicInfoEx in m.CustomTrackToMusicInfoEx) result.CustomTrackToMusicInfoEx[musicInfoEx.Key] = musicInfoEx.Value; @@ -1698,6 +2080,8 @@ public class MusicPatch public static explicit operator CustomMusicSaveDataBody(CustomMusicSaveDataBodySerializable m) { var result = new CustomMusicSaveDataBody(); + result.LastSongID = m.LastSongID; + foreach (var musicInfoEx in m.CustomTrackToMusicInfoEx) result.CustomTrackToMusicInfoEx[musicInfoEx.Key] = musicInfoEx.Value; @@ -1713,7 +2097,6 @@ public class MusicPatch return result; } - [Serializable] public class MusicInfoExSerializable { @@ -1900,7 +2283,7 @@ public class MusicPatch { public static Regex ConversionResultRegex = new("(?-?\\d*)\\:(?.*?)$"); - [JsonProperty("i")] public List Items = new(); + [JsonProperty("i")] public System.Collections.Generic.List Items = new(); public override string ToString() { diff --git a/TakoTako/Plugin.cs b/TakoTako/Plugin.cs index 894bcda..2e3386f 100644 --- a/TakoTako/Plugin.cs +++ b/TakoTako/Plugin.cs @@ -4,13 +4,20 @@ using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; -using HarmonyLib.Tools; using UnityEngine; +#if TAIKO_IL2CPP +using BepInEx.IL2CPP.Utils; +using BepInEx.IL2CPP; +#endif namespace TakoTako { [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)] +#if TAIKO_MONO public class Plugin : BaseUnityPlugin +#elif TAIKO_IL2CPP + public class Plugin : BasePlugin +#endif { public ConfigEntry ConfigSkipSplashScreen; public ConfigEntry ConfigDisableScreenChangeOnFocus; @@ -18,6 +25,7 @@ namespace TakoTako public ConfigEntry ConfigEnableCustomSongs; public ConfigEntry ConfigEnableTaikoDrumSupport; public ConfigEntry ConfigTaikoDrumUseNintendoLayout; + public ConfigEntry ConfigSkipDLCCheck; public ConfigEntry ConfigSongDirectory; public ConfigEntry ConfigSaveEnabled; @@ -27,17 +35,27 @@ namespace TakoTako public static Plugin Instance; private Harmony _harmony; - public static ManualLogSource Log; + public new static ManualLogSource Log; + // private ModMonoBehaviourHelper _modMonoBehaviourHelper; + +#if TAIKO_MONO private void Awake() +#elif TAIKO_IL2CPP + public override void Load() +#endif { Instance = this; - Log = Logger; - Logger.LogInfo($"Plugin {PluginInfo.PLUGIN_GUID} is loaded!"); +#if TAIKO_MONO + Log = Logger; +#elif TAIKO_IL2CPP + Log = base.Log; +#endif + + Log.LogInfo($"Plugin {PluginInfo.PLUGIN_GUID} is loaded!"); SetupConfig(); - SetupHarmony(); } @@ -79,13 +97,18 @@ namespace TakoTako ConfigFixSignInScreen = Config.Bind("General", "FixSignInScreen", - true, + false, "When true this will apply the patch to fix signing into Xbox Live"); ConfigSkipSplashScreen = Config.Bind("General", "SkipSplashScreen", true, "When true this will skip the intro"); + + ConfigSkipDLCCheck = Config.Bind("General", + "SkipDLCCheck", + true, + "When true this will skip slow DLC checks"); ConfigDisableScreenChangeOnFocus = Config.Bind("General", "DisableScreenChangeOnFocus", @@ -120,6 +143,11 @@ namespace TakoTako if (ConfigEnableTaikoDrumSupport.Value) _harmony.PatchAll(typeof(TaikoDrumSupport)); + #if TAIKO_IL2CPP + if (ConfigSkipDLCCheck.Value) + _harmony.PatchAll(typeof(SkipDLCCheck)); + #endif + if (ConfigEnableCustomSongs.Value) { _harmony.PatchAll(typeof(MusicPatch)); @@ -127,9 +155,15 @@ namespace TakoTako } } + public static MonoBehaviour GetMonoBehaviour() => TaikoSingletonMonoBehaviour.Instance; + public void StartCustomCoroutine(IEnumerator enumerator) { - StartCoroutine(enumerator); + #if TAIKO_MONO + GetMonoBehaviour().StartCoroutine(enumerator); + #elif TAIKO_IL2CPP + GetMonoBehaviour().StartCoroutine(enumerator); + #endif } } } diff --git a/TakoTako/SignInPatch.cs b/TakoTako/SignInPatch.cs index c6f3439..69e9176 100644 --- a/TakoTako/SignInPatch.cs +++ b/TakoTako/SignInPatch.cs @@ -1,29 +1,25 @@ -using System.Reflection; using HarmonyLib; +#if TAIKO_MONO using Microsoft.Xbox; - +#endif +#if TAIKO_IL2CPP +using Il2CppMicrosoft.Xbox; +#endif namespace TakoTako; /// /// This patch will address the issue where signing with GDK is done correctly /// -[HarmonyPatch(typeof(GdkHelpers))] -[HarmonyPatch("SignIn")] public static class SignInPatch { - + [HarmonyPatch(typeof(GdkHelpers))] + [HarmonyPatch("SignIn")] + [HarmonyPrefix] // ReSharper disable once InconsistentNaming - private static bool Prefix(GdkHelpers __instance) + private static bool SignIn_Prefix(GdkHelpers __instance) { Plugin.Log.LogInfo("Patching sign in to force the user to be prompted to sign in"); - var methodInfo = typeof(GdkHelpers).GetMethod("SignInImpl", BindingFlags.NonPublic | BindingFlags.Instance); - if (methodInfo == null) - { - Plugin.Log.LogError("Failed to patch"); - return true; - } - - methodInfo.Invoke(__instance, new object[] {true}); + __instance.SignInImpl(true); return false; } } diff --git a/TakoTako/SkipDLCCheck.cs b/TakoTako/SkipDLCCheck.cs new file mode 100644 index 0000000..f332379 --- /dev/null +++ b/TakoTako/SkipDLCCheck.cs @@ -0,0 +1,28 @@ +using System.Diagnostics.CodeAnalysis; +using HarmonyLib; + +namespace TakoTako; + +#if TAIKO_IL2CPP +[SuppressMessage("ReSharper", "InconsistentNaming")] +public class SkipDLCCheck +{ + private static SongSelectManager songSelectManager; + + [HarmonyPatch(typeof(SongSelectManager), "Start")] + [HarmonyPostfix] + public static void Start_Postfix(SongSelectManager __instance) + { + songSelectManager = __instance; + } + + [HarmonyPatch(typeof(DLCLicenseCheckWindow), nameof(DLCLicenseCheckWindow.OpenWindow))] + [HarmonyPostfix] + private static void OpenWindow_Postfix(DLCLicenseCheckWindow __instance) + { + songSelectManager.isDLCLicenseChecking = false; + songSelectManager.isDLCPackInfoUpdateChecked = true; + __instance.CloseWindowImmediately(); + } +} +#endif diff --git a/TakoTako/TaikoDrumSupport.cs b/TakoTako/TaikoDrumSupport.cs index 5b844e2..c93be16 100644 --- a/TakoTako/TaikoDrumSupport.cs +++ b/TakoTako/TaikoDrumSupport.cs @@ -1,7 +1,12 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; using HarmonyLib; using UnityEngine; +#if TAIKO_IL2CPP +using Array = Il2CppSystem.Array; +using UnhollowerBaseLib; +#endif namespace TakoTako; @@ -64,6 +69,26 @@ public class TaikoDrumSupport } } + private static unsafe +#if TAIKO_IL2CPP + bool[] +#elif TAIKO_MONO + bool[,] +#endif + GetPreviousButtons(ControllerManager instance) + { +#if TAIKO_IL2CPP + var fieldPtr = (IntPtr)AccessTools.Field(typeof(ControllerManager), "NativeFieldInfoPtr_prevButtons").GetValue(null); + System.IntPtr nativeObject = *(System.IntPtr*) (IL2CPP.Il2CppObjectBaseToPtrNotNull((Il2CppObjectBase) instance) + (int) IL2CPP.il2cpp_field_get_offset(fieldPtr)); + var array = nativeObject == System.IntPtr.Zero ? (Il2CppStructArray) null : new Il2CppStructArray(nativeObject); + return array; + // return instance.prevButtons.Cast>(); +#elif TAIKO_MONO + return instance.prevButtons; +#endif + } + + [HarmonyPatch(typeof(ControllerManager), "GetButtonDown")] [HarmonyPostfix] private static void GetButtonDown_Postfix(ControllerManager __instance, ref bool __result, ControllerManager.ControllerPlayerNo controllerPlayerNo, ControllerManager.Buttons btn) @@ -73,7 +98,14 @@ public class TaikoDrumSupport return; var prefix = $"J{controllerIndex}"; - var previous = __instance.prevButtons[(int) (controllerPlayerNo - 1), (int) btn]; + #if TAIKO_IL2CPP + var previousButtons = GetPreviousButtons(__instance); + int playerIndex = (int) (controllerPlayerNo - 1); + int btnIndex = (int) btn; + var previous = previousButtons[playerIndex * 31 + btnIndex]; + #elif TAIKO_MONO + var previous = GetPreviousButtons(__instance)[(int) (controllerPlayerNo - 1), (int) btn]; + #endif __result = btn switch { @@ -114,7 +146,15 @@ public class TaikoDrumSupport return; var prefix = $"J{controllerIndex}"; - var previous = __instance.prevButtons[(int) (controllerPlayerNo - 1), (int) btn]; + +#if TAIKO_IL2CPP + var previousButtons = GetPreviousButtons(__instance); + int playerIndex = (int) (controllerPlayerNo - 1); + int btnIndex = (int) btn; + var previous = previousButtons[playerIndex * 31 + btnIndex]; +#elif TAIKO_MONO + var previous = GetPreviousButtons(__instance)[(int) (controllerPlayerNo - 1), (int) btn]; +#endif __result = btn switch { diff --git a/TakoTako/TakoTako.csproj b/TakoTako/TakoTako.csproj index 58dd84a..535af04 100644 --- a/TakoTako/TakoTako.csproj +++ b/TakoTako/TakoTako.csproj @@ -1,66 +1,104 @@ + + true + TRACE;TAIKO_IL2CPP + true + C:\XboxGames\T Tablet\Content + - - net48 - com.fluto.takotako - Fixes Taiko issues and allows custom songs - 2.2.0 - true - latest - TakoTako - com.fluto.takotako - 2.2.0 - + + true + false + TRACE;TAIKO_IL2CPP; + C:\XboxGames\T Tablet\Content + - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive; compile - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive; compile - - - - + + true + TRACE;TAIKO_MONO; + D:\XboxGames\T Tablet\Content + - - - References\Newtonsoft.Json.dll - - + + true + false + TRACE;TAIKO_MONO; + D:\XboxGames\T Tablet\Content + + + + netstandard2.1 + net48 + com.fluto.takotako + A suite of mods that allow for Custom Songs and Taiko Drum support + 3.0.0 + true + latest + TakoTako + com.fluto.takotako + 3.0.0 + Release-BepInEx6;Release-BepInEx5;Debug-BepInEx6;Debug-BepInEx5 + AnyCPU + - - - + + + + - - - - - + + + + + + - - - - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive; compile + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive; compile + + - - - - + + + References\Newtonsoft.Json.dll + + - - - + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/MyFirstPlugin/MyFirstPlugin.csproj b/test/MyFirstPlugin/MyFirstPlugin.csproj new file mode 100644 index 0000000..6d8150e --- /dev/null +++ b/test/MyFirstPlugin/MyFirstPlugin.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.1 + MyFirstPlugin + My first plugin + 1.0.0 + true + latest + + + + + + + + diff --git a/test/MyFirstPlugin/NuGet.Config b/test/MyFirstPlugin/NuGet.Config new file mode 100644 index 0000000..1864ded --- /dev/null +++ b/test/MyFirstPlugin/NuGet.Config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/test/MyFirstPlugin/Plugin.cs b/test/MyFirstPlugin/Plugin.cs new file mode 100644 index 0000000..2fc9f6b --- /dev/null +++ b/test/MyFirstPlugin/Plugin.cs @@ -0,0 +1,15 @@ +using BepInEx; +using BepInEx.IL2CPP; + +namespace MyFirstPlugin +{ + [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)] + public class Plugin : BasePlugin + { + public override void Load() + { + // Plugin startup logic + Log.LogInfo($"Plugin {PluginInfo.PLUGIN_GUID} is loaded!"); + } + } +}