// See https://aka.ms/new-console-template for more information using CHUNITHM_Patch_Finder.Converters; using CHUNITHM_Patch_Finder.Models; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Reloaded.Memory.Sigscan; using Reloaded.Memory.Sigscan.Definitions.Structs; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; if (args.Length < 1) { Console.WriteLine($"Usage: {Environment.ProcessPath} [PATH TO PATTERNS]"); Environment.Exit(22); // EINVAL } var deserializer = new DeserializerBuilder() .WithNamingConvention(LowerCaseNamingConvention.Instance) .Build(); var exeFile = args[0]; var exeFileName = Path.GetFileName(exeFile); var patternsFile = args.Length >= 2 ? args[1] : Path.Join("Patterns", Path.GetFileNameWithoutExtension(exeFile) + ".yaml"); if (!Path.Exists(patternsFile)) { Console.WriteLine($"[ERROR] Could not find patterns file {patternsFile}. Nothing to search for."); Environment.Exit(1); } var patches = deserializer.Deserialize( File.ReadAllText(patternsFile)); var binary = File.ReadAllBytes(exeFile); var scanner = new Scanner(binary); var exportedPatches = new List(); // Other patches foreach (var patch in patches) { var signatures = patch.Patches.Select(p => p.Signature ?? patch.Signature).ToList(); if (signatures.Any(s => s == null)) { Console.WriteLine($"[ERROR] No signature provided for patch {patch.Name}"); continue; } var matches = signatures .Zip(scanner.FindPatternsCached(signatures)!) .GroupBy(p => p.First) .ToDictionary(g => g.Key, g => g.First().Second); var nonMatches = matches.Where(p => !p.Value.Found).ToList(); if (nonMatches.Count > 0) { Console.WriteLine($"No offset found for patch {patch.Name}, signatures {string.Join(",", nonMatches.Select(p => p.Key))}"); continue; } var patchObject = new BemaniPatcherStandardPatch { Name = patch.Name, Tooltip = patch.Tooltip, Danger = patch.Danger, Patches = patch.Patches.Select(p => { var signature = (p.Signature ?? patch.Signature)!; return new BemaniPatcherStandardPatchEntry { Offset = matches[signature].Offset + p.Offset, Off = p.Off, On = p.On, }; }).ToList() }; exportedPatches.Add(patchObject); } // max track count patch if (exeFileName == "chusanApp.exe") { var offset = scanner .FindPatterns([ "E8 ?? ?? ?? ?? 8D ?? 78 8B F0", // SDHD 2.00-2.20 "E8 ?? ?? ?? ?? 8B 4C 24 24 8B F0", // SDGS 1.30 ]) .FirstOrDefault(o => o.Found, new PatternScanResult(-1)); if (offset.Found) { var trackCountRelativeAddress = BitConverter.ToInt32(binary, offset.Offset + 1); var trackCountAddress = offset.Offset + 5 + trackCountRelativeAddress; Console.WriteLine($"Found track count function at {trackCountAddress:X}"); var patch = new BemaniPatcherNumberPatch { Name = "Maximum tracks", Offset = trackCountAddress + 1, Size = 4, Min = 3, Max = 12, }; exportedPatches.Add(patch); } else { Console.WriteLine("Track count function not found"); } } else if (exeFileName == "amdaemon.exe") { var results = scanner.FindPatterns([ "E8 ?? ?? ?? ?? 3B C5 7C 32", "E8 ?? ?? ?? ?? A8 10 74 C1", ]); if (results.All(r => r.Found)) { var logLevel1RelativeAddress = BitConverter.ToInt32(binary, results[0].Offset + 1); var logLevel1Address = results[0].Offset + 5 + logLevel1RelativeAddress; var logLevel2RelativeAddress = BitConverter.ToInt32(binary, results[1].Offset + 1); var logLevel2Address = results[1].Offset + 5 + logLevel2RelativeAddress; Console.WriteLine($"Found log level functions at {logLevel1Address:X} and {logLevel2Address:X}"); exportedPatches.Add(new BemaniPatcherStandardPatch { Name = "Verbose logging", Tooltip = "Force sets 2 flags that enables verbose logging, may be useful for developers", Patches = [ new BemaniPatcherStandardPatchEntry { Offset = logLevel1Address + 6, Off = [0xC3, 0xCC, 0xCC, 0xCC], On = [0x83, 0xC0, 0x3F, 0xC3], }, new BemaniPatcherStandardPatchEntry { Offset = logLevel2Address + 6, Off = [0xC3, 0xCC, 0xCC, 0xCC], On = [0x83, 0xC8, 0xFF, 0xC3], } ] }); } results = scanner.FindPatterns([ "E8 ?? ?? ?? ?? 83 F8 01 0F 84 ?? ?? ?? ?? 85 C0 79 2E", // http3ConnectServerSocketNb "E8 ?? ?? ?? ?? 83 F8 01 74 29 85 C0", // http3ReadResponseSocketNb "E8 ?? ?? ?? ?? 83 F8 01 74 4F", // http3SendRequestSocketNb ]); if (results.All(r => r.Found)) { Console.WriteLine($"Found Auth2.0 HTTP routines at {string.Join(", ", results.Select(r => r.Offset.ToString("X")))}"); var patchEntries = ( from result in results let relativeAddress = BitConverter.ToInt32(binary, result.Offset + 1) select result.Offset + 5 + relativeAddress into address select new BemaniPatcherStandardPatchEntry { Offset = address + 7, Off = [0x0F, 0x84], On = [0x90, 0xE9] } ).ToList(); exportedPatches.Add(new BemaniPatcherStandardPatch { Name = "Disable SSL for Auth2.0", Tooltip = "Force using non-SSL routines for Auth2.0 online certification, may be useful for developers", Patches = patchEntries, }); } } var settings = new JsonSerializerSettings { ContractResolver = new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy(), }, Converters = [new HexNumberJsonConverter()], Formatting = Formatting.Indented, }; File.WriteAllText( "patches.json", JsonConvert.SerializeObject(exportedPatches, settings)); Console.WriteLine("Wrote patches to patches.json");