From 25b558cdb963b12c5bdd2535d2b7d8dcef898a1d Mon Sep 17 00:00:00 2001 From: goaaats Date: Mon, 7 Feb 2022 13:11:27 +0100 Subject: [PATCH] Generate deterministic UIDs for official songs + TJA, DLC compatibility --- TakoTako/MurmurHash2.cs | 85 +++++++++++++++++++++++++++++++ TakoTako/MusicPatch.cs | 110 ++++++++-------------------------------- TakoTako/Plugin.cs | 9 +--- 3 files changed, 107 insertions(+), 97 deletions(-) create mode 100644 TakoTako/MurmurHash2.cs diff --git a/TakoTako/MurmurHash2.cs b/TakoTako/MurmurHash2.cs new file mode 100644 index 0000000..b11e7e3 --- /dev/null +++ b/TakoTako/MurmurHash2.cs @@ -0,0 +1,85 @@ +using System.Text; + +// MIT License +// +// Copyright (c) 2017 Jitbit, 2022 TaikoMods contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace TaikoMods; + +public class MurmurHash2 +{ + public static uint Hash(string data) + { + return Hash(Encoding.UTF8.GetBytes(data)); + } + + public static uint Hash(byte[] data) + { + return Hash(data, 0xc58f1a7a); + } + + private const uint m = 0x5bd1e995; + private const int r = 24; + + public static uint Hash(byte[] data, uint seed) + { + var length = data.Length; + if (length == 0) + return 0; + var h = seed ^ (uint)length; + var currentIndex = 0; + while (length >= 4) + { + var k = (uint)(data[currentIndex++] | (data[currentIndex++] << 8) | (data[currentIndex++] << 16) | + (data[currentIndex++] << 24)); + k *= m; + k ^= k >> r; + k *= m; + + h *= m; + h ^= k; + length -= 4; + } + + switch (length) + { + case 3: + h ^= (ushort)(data[currentIndex++] | (data[currentIndex++] << 8)); + h ^= (uint)(data[currentIndex] << 16); + h *= m; + break; + case 2: + h ^= (ushort)(data[currentIndex++] | (data[currentIndex] << 8)); + h *= m; + break; + case 1: + h ^= data[currentIndex]; + h *= m; + break; + } + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; + } +} \ No newline at end of file diff --git a/TakoTako/MusicPatch.cs b/TakoTako/MusicPatch.cs index def04d6..9061b97 100644 --- a/TakoTako/MusicPatch.cs +++ b/TakoTako/MusicPatch.cs @@ -15,6 +15,7 @@ using System.Threading.Tasks; using BepInEx.Logging; using HarmonyLib; using Newtonsoft.Json; +using TaikoMods; using TakoTako.Common; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; @@ -372,24 +373,32 @@ public class MusicPatch song.genreNo = 7; } - var instanceId = Guid.NewGuid().ToString(); + if (isTjaSong) + { + song.id = song.songName?.text + song.songSubtitle?.text + song.songDetail?.text; + if (string.IsNullOrEmpty(song.id)) + throw new Exception($"Song at {directory} does not have name, subtitle or detail text"); + + song.id += song.fumenOffsetPos + song.previewPos; + } + song.SongName = song.id; song.FolderPath = directory; - song.id = instanceId; - if (uniqueIdToSong.ContainsKey(song.uniqueId) || (song.uniqueId >= 0 && song.uniqueId <= SaveDataMax)) + // Clip off the last bit of the hash to make sure that the number is positive. This will lead to more collisions, but we should be fine. + song.uniqueId = (int)(MurmurHash2.Hash(song.id) & 0xFFFF_FFF); + if (song.uniqueId <= SaveDataMax) + song.uniqueId += SaveDataMax; + + if (uniqueIdToSong.ContainsKey(song.uniqueId)) { - var uniqueIdTest = unchecked(song.id.GetHashCode() + song.previewPos + song.fumenOffsetPos); - while (uniqueIdToSong.ContainsKey(uniqueIdTest) || (uniqueIdTest >= 0 && uniqueIdTest <= SaveDataMax)) - uniqueIdTest = unchecked((uniqueIdTest + 1) * (uniqueIdTest + 1)); - - song.uniqueId = uniqueIdTest; + throw new Exception($"Song \"{song.id}\" has collision with \"{uniqueIdToSong[song.uniqueId].id}\", bailing out..."); } customSongsList.Add(song); idToSong[song.id] = song; uniqueIdToSong[song.uniqueId] = song; - Log.LogInfo($"Added {(isTjaSong ? "TJA" : "")} Song {song.songName.text}"); + Log.LogInfo($"Added{(isTjaSong ? " TJA" : "")} Song {song.songName.text}({song.uniqueId})"); } } @@ -456,10 +465,10 @@ public class MusicPatch $"song_{song.id}", song.order, song.genreNo, - !Plugin.Instance.ConfigDisableCustomDLCSongs.Value, + 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, + 2, // Always mark custom songs as "both players need to have this song to play it" new[] { song.branchEasy, @@ -1287,83 +1296,6 @@ public class MusicPatch #pragma warning restore Harmony003 } - /// - /// Allow for a song id less than 0 - /// - [HarmonyPatch(typeof(EnsoDataManager), "DecideSetting")] - [HarmonyPrefix] - public static bool DecideSetting_Prefix(EnsoDataManager __instance) - { - var ensoSettings = (EnsoData.Settings) typeof(EnsoDataManager).GetField("ensoSettings", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance); - // if (ensoSettings.musicuid.Length <= 0) - // { - // MusicDataInterface.MusicInfoAccesser infoByUniqueId = TaikoSingletonMonoBehaviour.Instance.MyDataManager.MusicData.GetInfoByUniqueId(ensoSettings.musicUniqueId); - // if (infoByUniqueId != null) - // { - // ensoSettings.musicuid = infoByUniqueId.Id; - // } - // } - // else if (ensoSettings.musicUniqueId <= DataConst.InvalidId) - // { - // MusicDataInterface.MusicInfoAccesser infoById = TaikoSingletonMonoBehaviour.Instance.MyDataManager.MusicData.GetInfoById(ensoSettings.musicuid); - // if (infoById != null) - // { - // ensoSettings.musicUniqueId = infoById.UniqueId; - // } - // } - if (ensoSettings.musicuid.Length <= 0 /* || ensoSettings.musicUniqueId <= DataConst.InvalidId*/) - { - List musicInfoAccessers = TaikoSingletonMonoBehaviour.Instance.MyDataManager.MusicData.musicInfoAccessers; - for (int i = 0; i < musicInfoAccessers.Count; i++) - { - if (!musicInfoAccessers[i].Debug) - { - ensoSettings.musicuid = musicInfoAccessers[i].Id; - ensoSettings.musicUniqueId = musicInfoAccessers[i].UniqueId; - } - } - } - - MusicDataInterface.MusicInfoAccesser infoByUniqueId2 = TaikoSingletonMonoBehaviour.Instance.MyDataManager.MusicData.GetInfoByUniqueId(ensoSettings.musicUniqueId); - if (infoByUniqueId2 != null) - { - ensoSettings.songFilePath = infoByUniqueId2.SongFileName; - } - - __instance.DecidePartsSetting(); - if (ensoSettings.ensoType == EnsoData.EnsoType.Normal) - { - int num = 0; - int dlcType = 2; - if (ensoSettings.rankMatchType == EnsoData.RankMatchType.None) - { - num = ((ensoSettings.playerNum != 1) ? 1 : 0); - } - else if (ensoSettings.rankMatchType == EnsoData.RankMatchType.RankMatch) - { - num = 2; - ensoSettings.isRandomSelect = false; - ensoSettings.isDailyBonus = false; - } - else - { - num = 3; - ensoSettings.isRandomSelect = false; - ensoSettings.isDailyBonus = false; - } - - TaikoSingletonMonoBehaviour.Instance.CosmosLib._kpiListCommon._musicKpiInfo.SetMusicSortSettings(num, dlcType, ensoSettings.isRandomSelect, ensoSettings.isDailyBonus); - } - else - { - ensoSettings.isRandomSelect = false; - ensoSettings.isDailyBonus = false; - } - - typeof(EnsoDataManager).GetField("ensoSettings", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(__instance, ensoSettings); - return false; - } - #endregion #region Read Fumen @@ -1950,4 +1882,4 @@ public class MusicPatch public string FolderPath; public string SongName; } -} +} \ No newline at end of file diff --git a/TakoTako/Plugin.cs b/TakoTako/Plugin.cs index 703bde2..0a099b8 100644 --- a/TakoTako/Plugin.cs +++ b/TakoTako/Plugin.cs @@ -20,7 +20,6 @@ namespace TakoTako public ConfigEntry ConfigSongDirectory; public ConfigEntry ConfigSaveEnabled; public ConfigEntry ConfigSaveDirectory; - public ConfigEntry ConfigDisableCustomDLCSongs; public ConfigEntry ConfigOverrideDefaultSongLanguage; public ConfigEntry ConfigApplyGenreOverride; @@ -64,12 +63,6 @@ namespace TakoTako $"{userFolder}/Documents/{typeof(Plugin).Namespace}/saves", "The directory where saves are stored"); - ConfigDisableCustomDLCSongs = Config.Bind("CustomSongs", - "DisableCustomDLCSongs", - false, - "By default, DLC is enabled for custom songs, this is to reduce any hiccups when playing online with other people. " + - "Set this to true if you want DLC to be marked as false, be aware that the fact you're playing a custom song will be sent over the internet"); - ConfigOverrideDefaultSongLanguage = Config.Bind("CustomSongs", "ConfigOverrideDefaultSongLanguage", string.Empty, @@ -124,4 +117,4 @@ namespace TakoTako StartCoroutine(enumerator); } } -} +} \ No newline at end of file