using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text.RegularExpressions;
using BepInEx.Logging;
using HarmonyLib;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
namespace TaikoMods;
///
/// This will allow custom songs to be read in
///
[HarmonyPatch]
[SuppressMessage("ReSharper", "InconsistentNaming")]
public class MusicPatch
{
public static int SaveDataMax => DataConst.MusicMax;
public static string MusicTrackDirectory => Plugin.Instance.ConfigSongDirectory.Value;
public static string SaveFilePath => $"{Plugin.Instance.ConfigSaveDirectory.Value}/save.json";
private const string SongDataFileName = "data.json";
public static ManualLogSource Log => Plugin.Log;
public static void Setup(Harmony harmony)
{
CreateDirectoryIfNotExist(Path.GetDirectoryName(SaveFilePath));
CreateDirectoryIfNotExist(MusicTrackDirectory);
PatchManual(harmony);
void CreateDirectoryIfNotExist(string path)
{
path = Path.GetFullPath(path);
if (!Directory.Exists(path))
{
Log.LogInfo($"Creating path at {path}");
Directory.CreateDirectory(path);
}
}
}
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;
private static readonly DataContractJsonSerializer saveDataSerializer = new(typeof(CustomMusicSaveDataBody));
private static CustomMusicSaveDataBody GetCustomSaveData()
{
if (_customSaveData != null)
return _customSaveData;
Log.LogInfo("Loading custom save data");
var savePath = SaveFilePath;
try
{
CustomMusicSaveDataBody data;
if (!File.Exists(savePath))
{
data = new CustomMusicSaveDataBody();
using var fileStream = File.OpenWrite(savePath);
saveDataSerializer.WriteObject(fileStream, data);
}
else
{
using var fileStream = File.OpenRead(savePath);
data = (CustomMusicSaveDataBody) saveDataSerializer.ReadObject(fileStream);
data.CustomTrackToEnsoRecordInfo ??= new Dictionary();
data.CustomTrackToMusicInfoEx ??= new Dictionary();
}
_customSaveData = data;
return data;
}
catch (Exception e)
{
Log.LogError($"Could not load custom data, creating a fresh one\n {e}");
}
try
{
var data = new CustomMusicSaveDataBody();
using var fileStream = File.OpenWrite(savePath);
saveDataSerializer.WriteObject(fileStream, data);
}
catch (Exception e)
{
Log.LogError($"Cannot save data at path {savePath}\n {e}");
}
return new CustomMusicSaveDataBody();
}
private static void SaveCustomData()
{
if (_customSaveData == null)
return;
Log.LogInfo("Saving custom save data");
try
{
var data = GetCustomSaveData();
var savePath = SaveFilePath;
using var fileStream = File.OpenWrite(savePath);
saveDataSerializer.WriteObject(fileStream, data);
}
catch (Exception e)
{
Log.LogError($"Could not save custom data \n {e}");
}
}
#endregion
#region Load Custom Songs
private static readonly DataContractJsonSerializer customSongSerializer = new(typeof(CustomSong));
private static List customSongsList;
private static readonly Dictionary idToSong = new Dictionary();
private static readonly Dictionary uniqueIdToSong = new Dictionary();
public static List GetCustomSongs()
{
if (customSongsList != null)
return customSongsList;
if (!Directory.Exists(MusicTrackDirectory))
{
Log.LogError($"Cannot find {MusicTrackDirectory}");
customSongsList = new List();
return customSongsList;
}
customSongsList = new List();
foreach (var musicDirectory in Directory.GetDirectories(MusicTrackDirectory))
{
var files = Directory.GetFiles(musicDirectory);
var customSongPath = files.FirstOrDefault(x => Path.GetFileName(x) == SongDataFileName);
if (string.IsNullOrWhiteSpace(customSongPath))
continue;
using var fileStream = File.OpenRead(customSongPath);
var song = (CustomSong) customSongSerializer.ReadObject(fileStream);
if (song == null)
{
Log.LogError($"Cannot read {customSongPath}");
continue;
}
if (idToSong.ContainsKey(song.id))
{
Log.LogError($"Cannot load song {song.songName.text} with ID {song.uniqueId} as it clashes with another, skipping it...");
continue;
}
if (uniqueIdToSong.ContainsKey(song.uniqueId))
{
var uniqueIdTest = song.id.GetHashCode();
while (uniqueIdToSong.ContainsKey(uniqueIdTest))
uniqueIdTest++;
Log.LogWarning($"Found song {song.songName.text} with an existing ID {song.uniqueId}, changing it to {uniqueIdTest}");
song.uniqueId = uniqueIdTest;
}
customSongsList.Add(song);
idToSong[song.id] = song;
uniqueIdToSong[song.uniqueId] = song;
Log.LogInfo($"Added Song {song.songName.text}:{song.id}:{song.uniqueId}");
}
if (customSongsList.Count == 0)
Log.LogInfo($"No tracks found");
return customSongsList;
}
#endregion
#region Loading and Initializing Data
///
/// 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)
{
// 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 songs");
var customSongs = GetCustomSongs();
if (customSongs.Count == 0)
return;
// now that we have loaded this json, inject it into the existing `musicInfoAccessers`
var musicInfoAccessors = __instance.musicInfoAccessers;
#region Logic from the original constructor
for (int i = 0; i < customSongs.Count; i++)
{
if (!customSongs[i].debug || !(customSongs[i].id != "kakunin"))
{
musicInfoAccessors.Add(new MusicDataInterface.MusicInfoAccesser(customSongs[i].uniqueId, customSongs[i].id, customSongs[i].songFileName, customSongs[i].order, customSongs[i].genreNo,
customSongs[i].isDLC, customSongs[i].debug, customSongs[i].price, customSongs[i].isCap, customSongs[i].rankmatchNeedHasPlayer, new bool[5]
{
customSongs[i].branchEasy,
customSongs[i].branchNormal,
customSongs[i].branchHard,
customSongs[i].branchMania,
customSongs[i].branchUra
}, new int[5]
{
customSongs[i].starEasy,
customSongs[i].starNormal,
customSongs[i].starHard,
customSongs[i].starMania,
customSongs[i].starUra
}, new int[5]
{
customSongs[i].shinutiEasy,
customSongs[i].shinutiNormal,
customSongs[i].shinutiHard,
customSongs[i].shinutiMania,
customSongs[i].shinutiUra
}, new int[5]
{
customSongs[i].shinutiEasyDuet,
customSongs[i].shinutiNormalDuet,
customSongs[i].shinutiHardDuet,
customSongs[i].shinutiManiaDuet,
customSongs[i].shinutiUraDuet
}, new int[5]
{
customSongs[i].scoreEasy,
customSongs[i].scoreNormal,
customSongs[i].scoreHard,
customSongs[i].scoreMania,
customSongs[i].scoreUra
}));
}
}
#endregion
// sort this
musicInfoAccessors.Sort((MusicDataInterface.MusicInfoAccesser a, MusicDataInterface.MusicInfoAccesser b) => a.Order - b.Order);
}
///
/// 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)
{
// 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");
var customSongs = GetCustomSongs();
if (customSongs.Count == 0)
return;
// now that we have loaded this json, inject it into the existing `songInfoAccessers`
var musicInfoAccessors = __instance.songInfoAccessers;
foreach (var customTrack in customSongs)
{
musicInfoAccessors.Add(new SongDataInterface.SongInfoAccesser(customTrack.id, customTrack.previewPos, customTrack.fumenOffsetPos));
}
}
///
/// 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)
{
// 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();
if (customSongs.Count == 0)
return;
// now that we have loaded this json, inject it into the existing `songInfoAccessers`
var musicInfoAccessors = __instance.wordListInfoAccessers;
foreach (var customTrack in customSongs)
{
musicInfoAccessors.Add(new WordDataInterface.WordListInfoAccesser($"song_{customTrack.id}", customTrack.songName.text, customTrack.songName.font));
musicInfoAccessors.Add(new WordDataInterface.WordListInfoAccesser($"song_sub_{customTrack.id}", customTrack.songSubtitle.text, customTrack.songSubtitle.font));
musicInfoAccessors.Add(new WordDataInterface.WordListInfoAccesser($"song_detail_{customTrack.id}", customTrack.songDetail.text, customTrack.songDetail.font));
}
}
#endregion
#region Loading / Save Custom Save Data
///
/// When loading, make sure to ignore custom tracks, as their IDs will be different
///
[HarmonyPatch(typeof(SongSelectManager), "LoadSongList")]
[HarmonyPrefix]
private static bool LoadSongList_Prefix(SongSelectManager __instance)
{
#region Edited Code
Log.LogInfo("Loading custom save");
var customData = GetCustomSaveData();
#endregion
#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});
#endregion
if (playDataMgr == null)
{
Log.LogError("Could not find playDataMgr");
return true;
}
__instance.UnsortedSongList.Clear();
playDataMgr.GetMusicInfoExAll(0, out var dst);
playDataMgr.GetPlayerInfo(0, out var _);
_ = TaikoSingletonMonoBehaviour.Instance.newFriends.Count;
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < musicInfoAccess.Length; j++)
{
bool flag = false;
playDataMgr.GetUnlockInfo(0, DataConst.ItemType.Music, musicInfoAccess[j].UniqueId, out var dst3);
if (!dst3.isUnlock && musicInfoAccess[j].Price != 0)
{
flag = true;
}
if (!enableKakuninSong && musicInfoAccess[j].IsKakuninSong())
{
flag = true;
}
if (flag || musicInfoAccess[j].GenreNo != i)
{
continue;
}
SongSelectManager.Song song2 = new SongSelectManager.Song();
song2.PreviewIndex = j;
song2.Id = musicInfoAccess[j].Id;
song2.TitleKey = "song_" + musicInfoAccess[j].Id;
song2.SubKey = "song_sub_" + musicInfoAccess[j].Id;
song2.RubyKey = "song_detail_" + musicInfoAccess[j].Id;
song2.UniqueId = musicInfoAccess[j].UniqueId;
song2.SongGenre = musicInfoAccess[j].GenreNo;
song2.ListGenre = i;
song2.Order = musicInfoAccess[j].Order;
song2.TitleText = getLocalizedText("song_" + song2.Id);
song2.SubText = getLocalizedText("song_sub_" + song2.Id);
song2.DetailText = getLocalizedText("song_detail_" + song2.Id);
song2.Stars = musicInfoAccess[j].Stars;
song2.Branches = musicInfoAccess[j].Branches;
song2.HighScores = new SongSelectManager.Score[5];
song2.HighScores2P = new SongSelectManager.Score[5];
song2.DLC = musicInfoAccess[j].IsDLC;
song2.Price = musicInfoAccess[j].Price;
song2.IsCap = musicInfoAccess[j].IsCap;
if (TaikoSingletonMonoBehaviour.Instance.MyDataManager.SongData.GetInfo(song2.Id) != null)
{
song2.AudioStartMS = TaikoSingletonMonoBehaviour.Instance.MyDataManager.SongData.GetInfo(song2.Id).PreviewPos;
}
else
{
song2.AudioStartMS = 0;
}
if (dst != null)
{
#region Edited Code
MusicInfoEx data;
if (musicInfoAccess[j].UniqueId >= SaveDataMax)
customData.CustomTrackToMusicInfoEx.TryGetValue(musicInfoAccess[j].UniqueId, out data);
else
data = dst[musicInfoAccess[j].UniqueId];
song2.Favorite = data.favorite;
song2.NotPlayed = new bool[5];
song2.NotCleared = new bool[5];
song2.NotFullCombo = new bool[5];
song2.NotDondaFullCombo = new bool[5];
song2.NotPlayed2P = new bool[5];
song2.NotCleared2P = new bool[5];
song2.NotFullCombo2P = new bool[5];
song2.NotDondaFullCombo2P = new bool[5];
bool isNew = data.isNew;
#endregion
for (int k = 0; k < 5; k++)
{
playDataMgr.GetPlayerRecordInfo(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;
song2.HighScores[k].hiScoreRecordInfos = dst4.normalHiScore;
song2.HighScores[k].crown = dst4.crown;
playDataMgr.GetPlayerRecordInfo(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;
song2.HighScores2P[k].hiScoreRecordInfos = dst5.normalHiScore;
song2.HighScores2P[k].crown = dst5.crown;
}
song2.NewSong = isNew && (song2.DLC || song2.Price > 0);
}
__instance.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);
var songList = new List(__instance.UnsortedSongList);
typeof(SongSelectManager).GetProperty(nameof(SongSelectManager.SongList), BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance)?.SetValue(__instance, songList);
updateSortCategoryInfo(DataConst.SongSortType.Genre);
return false;
}
///
/// When saving favourite tracks, save the custom ones too
///
[HarmonyPatch(typeof(SongSelectManager), "SaveFavotiteSongs")]
[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);
var customSaveData = GetCustomSaveData();
bool saveCustomData = false;
int num = 0;
foreach (SongSelectManager.Song unsortedSong in __instance.UnsortedSongList)
{
num++;
if (unsortedSong.UniqueId < SaveDataMax)
{
dst[unsortedSong.UniqueId].favorite = unsortedSong.Favorite;
playDataMgr.SetMusicInfoEx(0, unsortedSong.UniqueId, ref dst[unsortedSong.UniqueId], num >= __instance.UnsortedSongList.Count);
}
else
{
customSaveData.CustomTrackToMusicInfoEx.TryGetValue(unsortedSong.UniqueId, out var data);
saveCustomData |= data.favorite != unsortedSong.Favorite;
data.favorite = unsortedSong.Favorite;
customSaveData.CustomTrackToMusicInfoEx[unsortedSong.UniqueId] = data;
}
}
if (saveCustomData)
SaveCustomData();
return false;
}
///
/// When loading the song, mark the custom song as not new
///
[HarmonyPatch(typeof(CourseSelect), "EnsoConfigSubmit")]
[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 = (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 = 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);
settings.ensoType = EnsoData.EnsoType.Normal;
settings.rankMatchType = EnsoData.RankMatchType.None;
settings.musicuid = (string) songInfoType.GetField("Id").GetValue(selectedSongInfo);
settings.musicUniqueId = songUniqueId;
settings.genre = (EnsoData.SongGenre) songInfoType.GetField("SongGenre").GetValue(selectedSongInfo);
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 array = (Array) songInfoType.GetField("HighScores").GetValue(selectedSongInfo);
settings.ensoPlayerSettings[0].hiScore = ((HiScoreRecordInfo) scoreType.GetField("hiScoreRecordInfos").GetValue(array.GetValue(selectedCourse))).score;
SetSettings();
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;
settings.playerNum = 2;
}
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);
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"));
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"));
playDataManager.GetSystemOption(out var dst2);
int deviceTypeIndex = EnsoDataManager.GetDeviceTypeIndex(settings.ensoPlayerSettings[0].inputDevice);
settings.noteDispOffset = dst2.onpuDispLevels[deviceTypeIndex];
settings.noteDelay = dst2.onpuHitLevels[deviceTypeIndex];
settings.songVolume = TaikoSingletonMonoBehaviour.Instance.MySoundManager.GetVolume(SoundManager.SoundType.InGameSong);
settings.seVolume = TaikoSingletonMonoBehaviour.Instance.MySoundManager.GetVolume(SoundManager.SoundType.Se);
settings.voiceVolume = TaikoSingletonMonoBehaviour.Instance.MySoundManager.GetVolume(SoundManager.SoundType.Voice);
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();
ensoDataManager.SetSettings(ref settings);
ensoDataManager.DecideSetting();
if (status.Is2PActive)
{
dst2.controlType[1] = dst2.controlType[0];
playDataManager.SetSystemOption(ref dst2);
}
var customSaveData = GetCustomSaveData();
if (songUniqueId < SaveDataMax)
{
playDataManager.GetMusicInfoExAll(0, out var dst3);
dst3[songUniqueId].isNew = false;
playDataManager.SetMusicInfoEx(0, songUniqueId, ref dst3[songUniqueId]);
}
else
{
customSaveData.CustomTrackToMusicInfoEx.TryGetValue(songUniqueId, out var data);
data.isNew = false;
customSaveData.CustomTrackToMusicInfoEx[songUniqueId] = data;
SaveCustomData();
}
UnityEngine.Debug.Log($"p1 is {ensoMode}");
return false;
}
///
/// When loading the song obtain isfavourite correctly
///
[HarmonyPatch(typeof(KpiListCommon.MusicKpiInfo), "GetEnsoSettings")]
[HarmonyPrefix]
private static bool GetEnsoSettings_Prefix(KpiListCommon.MusicKpiInfo __instance)
{
TaikoSingletonMonoBehaviour.Instance.MyDataManager.EnsoData.CopySettings(out var dst);
__instance.music_id = dst.musicuid;
__instance.genre = (int) dst.genre;
__instance.course_type = (int) dst.ensoPlayerSettings[0].courseType;
__instance.neiro_id = dst.ensoPlayerSettings[0].neiroId;
__instance.speed = (int) dst.ensoPlayerSettings[0].speed;
__instance.dron = (int) dst.ensoPlayerSettings[0].dron;
__instance.reverse = (int) dst.ensoPlayerSettings[0].reverse;
__instance.randomlv = (int) dst.ensoPlayerSettings[0].randomlv;
__instance.special = (int) dst.ensoPlayerSettings[0].special;
PlayDataManager playData = TaikoSingletonMonoBehaviour.Instance.MyDataManager.PlayData;
playData.GetEnsoMode(out var dst2);
__instance.sort_course = (int) dst2.songSortCourse;
__instance.sort_type = (int) dst2.songSortType;
__instance.sort_filter = (int) dst2.songFilterType;
__instance.sort_favorite = (int) dst2.songFilterTypeFavorite;
MusicDataInterface.MusicInfoAccesser[] array = TaikoSingletonMonoBehaviour.Instance.MyDataManager.MusicData.musicInfoAccessers.ToArray();
playData.GetMusicInfoExAll(0, out var dst3);
#region edited code
for (int i = 0; i < array.Length; i++)
{
var id = array[i].UniqueId;
if (id == dst.musicUniqueId && dst3 != null)
{
if (id < SaveDataMax)
{
__instance.is_favorite = dst3[id].favorite;
}
else
{
GetCustomSaveData().CustomTrackToMusicInfoEx.TryGetValue(id, out var data);
__instance.is_favorite = data.favorite;
}
}
}
#endregion
playData.GetPlayerInfo(0, out var dst4);
__instance.current_coins_num = dst4.donCoin;
__instance.total_coins_num = dst4.getCoinsInTotal;
playData.GetRankMatchSeasonRecordInfo(0, 0, out var dst5);
__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)
{
if (uniqueId < SaveDataMax)
{
dst = new EnsoRecordInfo();
return true;
}
int num = (int) levelType;
if (num is < 0 or >= 5)
num = 0;
// load our custom save, this will combine the scores of player1 and player2
var saveData = GetCustomSaveData().CustomTrackToEnsoRecordInfo;
if (!saveData.TryGetValue(uniqueId, out var ensoData))
{
ensoData = new EnsoRecordInfo[(int) EnsoData.EnsoLevelType.Num];
saveData[uniqueId] = ensoData;
}
dst = ensoData[num];
return false;
}
///
/// Save scores to custom save data
///
[HarmonyPatch(typeof(PlayDataManager), "UpdatePlayerScoreRecordInfo",
new Type[] {typeof(int), typeof(int), typeof(int), typeof(EnsoData.EnsoLevelType), typeof(bool), typeof(DataConst.SpecialTypes), typeof(HiScoreRecordInfo), typeof(DataConst.ResultType), typeof(bool), typeof(DataConst.CrownType)})]
[HarmonyPrefix]
public static bool UpdatePlayerScoreRecordInfo(PlayDataManager __instance, int playerId, int charaIndex, int uniqueId, EnsoData.EnsoLevelType levelType, bool isSinuchi, DataConst.SpecialTypes spTypes, HiScoreRecordInfo record,
DataConst.ResultType resultType, bool savemode, DataConst.CrownType crownType)
{
if (uniqueId < SaveDataMax)
return true;
int num = (int) levelType;
if (num is < 0 or >= 5)
num = 0;
var saveData = GetCustomSaveData().CustomTrackToEnsoRecordInfo;
if (!saveData.TryGetValue(uniqueId, out var ensoData))
{
ensoData = new EnsoRecordInfo[(int) EnsoData.EnsoLevelType.Num];
saveData[uniqueId] = ensoData;
}
EnsoRecordInfo ensoRecordInfo = ensoData[(int) levelType];
#pragma warning disable Harmony003
if (ensoRecordInfo.normalHiScore.score <= record.score)
{
ensoRecordInfo.normalHiScore.score = record.score;
ensoRecordInfo.normalHiScore.combo = record.combo;
ensoRecordInfo.normalHiScore.excellent = record.excellent;
ensoRecordInfo.normalHiScore.good = record.good;
ensoRecordInfo.normalHiScore.bad = record.bad;
ensoRecordInfo.normalHiScore.renda = record.renda;
}
#pragma warning restore Harmony003
if (crownType != DataConst.CrownType.Off)
{
if (IsValueInRange((int) crownType, 0, 5) && ensoRecordInfo.crown <= crownType)
{
ensoRecordInfo.crown = crownType;
ensoRecordInfo.cleared = crownType >= DataConst.CrownType.Silver;
}
}
ensoData[(int) levelType] = ensoRecordInfo;
if (savemode && playerId == 0)
SaveCustomData();
return false;
bool IsValueInRange(int myValue, int minValue, int maxValue)
{
if (myValue >= minValue && myValue < maxValue)
return true;
return false;
}
}
///
/// Allow for a song id > 400
///
[HarmonyPatch(typeof(EnsoMode), "IsValid")]
[HarmonyPrefix]
public static bool IsValid_Prefix(ref bool __result, EnsoMode __instance)
{
#pragma warning disable Harmony003
__result = Validate();
return false;
bool Validate()
{
// commented out this code
// if (songUniqueId < DataConst.InvalidId || songUniqueId > DataConst.MusicMax)
// {
// return false;
// }
if (!Enum.IsDefined(typeof(EnsoData.SongGenre), __instance.listGenre))
{
return false;
}
if (__instance.neiro < 0 || __instance.neiro > DataConst.NeiroMax)
{
return false;
}
if (!Enum.IsDefined(typeof(EnsoData.EnsoLevelType), __instance.level))
{
return false;
}
if (!Enum.IsDefined(typeof(DataConst.SpeedTypes), __instance.speed))
{
return false;
}
if (!Enum.IsDefined(typeof(DataConst.OptionOnOff), __instance.dron))
{
return false;
}
if (!Enum.IsDefined(typeof(DataConst.OptionOnOff), __instance.reverse))
{
return false;
}
if (!Enum.IsDefined(typeof(DataConst.RandomLevel), __instance.randomlv))
{
return false;
}
if (!Enum.IsDefined(typeof(DataConst.SpecialTypes), __instance.special))
{
return false;
}
if (!Enum.IsDefined(typeof(DataConst.SongSortType), __instance.songSortType))
{
return false;
}
if (!Enum.IsDefined(typeof(DataConst.SongSortCourse), __instance.songSortCourse))
{
return false;
}
if (!Enum.IsDefined(typeof(DataConst.SongFilterType), __instance.songFilterType))
{
return false;
}
if (!Enum.IsDefined(typeof(DataConst.SongFilterTypeFavorite), __instance.songFilterTypeFavorite))
{
return false;
}
return true;
}
#pragma warning restore Harmony003
}
#endregion
#region Read Fumen
private static readonly Regex fumenFilePathRegex = new Regex("(?.*?)_(?[ehmn])(?_[12])?.bin");
private static readonly Dictionary