// Undertale Wildfire Autosplitter by NERS state("Undertale Wildfire", "Combat Demo v1.11") { double menuSelection : 0xE28C90, 0x98, 0x48, 0x10, 0x30, 0x0; // obj_menu.selection string16 menuState : 0xE383A0, 0x18, 0x88, 0x8, 0x0, 0x0, 0x0; // obj_menu.fsm state (SnowState) float playerX : 0xC18F60, 0x0, 0x4A0, 0x18, 0x58, 0x10, 0xF4; // obj_player.x } startup { refreshRate = 30; settings.Add("Combat_Demo", true, "Combat Demo"); settings.CurrentDefaultParent = "Combat_Demo"; settings.Add("C_Nochallenge", false, "No challenge"); settings.Add("C_Trinketless", false, "Trinketless"); settings.Add("C_StressHurts", false, "Stress Hurts"); settings.Add("C_Patience", false, "Patience"); settings.Add("C_OneHitWonder", true, "One Hit Wonder"); settings.CurrentDefaultParent = null; } init { var module = modules.First(); // Thanks to Jujstme and Ero for this (finding room names) var scanner = new SignatureScanner(game, module.BaseAddress, module.ModuleMemorySize); Func<int, string, IntPtr> scan = (o, sig) => { IntPtr ptr = scanner.Scan(new SigScanTarget(o, sig) { OnFound = (p, s, addr) => addr + p.ReadValue<int>(addr) + 0x4 }); if(ptr == IntPtr.Zero) throw new NullReferenceException("[Undertale Wildfire] Signature scanning failed"); print("[Undertale Wildfire] Signature found at " + ptr.ToString("X")); return ptr; }; IntPtr ptrRoomArray = scan(5, "74 0C 48 8B 05 ?? ?? ?? ?? 48 8B 04 D0"); vars.ptrRoomID = scan(6, "48 ?? ?? ?? 3B 35 ?? ?? ?? ?? 41 ?? ?? ?? 49 ?? ?? E8 ?? ?? ?? ?? FF"); vars.getRoomName = (Func<string>)(() => { IntPtr arrayMain = game.ReadPointer(ptrRoomArray); if(arrayMain == IntPtr.Zero) return string.Empty; IntPtr arrayItem = game.ReadPointer(arrayMain + game.ReadValue<int>((IntPtr)vars.ptrRoomID) * 8); if(arrayItem == IntPtr.Zero) return string.Empty; return game.ReadString(arrayItem, 64); }); string hash; using(var md5 = System.Security.Cryptography.MD5.Create()) using(var fs = File.OpenRead(new FileInfo(module.FileName).DirectoryName + @"\data.win")) hash = string.Concat(md5.ComputeHash(fs).Select(b => b.ToString("X2"))); switch(hash) { case "395BF917B794A55BB792B4E6B2E2D10F": version = "Combat Demo v1.11"; break; default: version = "Unknown"; MessageBox.Show ( "This version of Undertale Wildfire is not supported by the autosplitter.\nIf you are playing an older version, update your game.\nIf not, please wait until the autosplitter receives an update.\n\n" + "Supported version: Combat Demo v1.11.", "LiveSplit | Undertale Wildfire", MessageBoxButtons.OK, MessageBoxIcon.Warning ); break; } print("[Undertale Wildfire] Version: " + version + " (" + hash + ")"); vars.currentChallenge = 0; vars.splits = new Dictionary<string, object[]>() { // Object variables in order: done, old room, new room, special condition {"C_Nochallenge", new object[] {false, null, "rm_trail_anser_quigley", 1}}, {"C_Trinketless", new object[] {false, null, "rm_trail_anser_quigley", 2}}, {"C_StressHurts", new object[] {false, null, "rm_trail_anser_quigley", 3}}, {"C_Patience", new object[] {false, null, "rm_trail_anser_quigley", 4}}, {"C_OneHitWonder", new object[] {false, null, "rm_trail_anser_quigley", 5}} }; } start { if(version.StartsWith("Combat Demo")) return (current.roomName == "rm_menu" && old.menuState == "challenges" && current.menuState == "fade_challenges" && old.menuSelection <= 1); // Checking for selections 0 and 1 because you can do Down+Z on the same frame while on Back and it counts as starting No challenge while selection is still 0 } reset { if(version.StartsWith("Combat Demo")) return (current.roomName == "rm_menu" && old.menuState == "challenges" && current.menuState == "fade_challenges" && old.menuSelection <= 1); } onReset { if(game != null) { foreach(string split in vars.splits.Keys) vars.splits[split][0] = false; print("[Undertale Wildfire] All splits have been reset to initial state"); } } update { if(version == "Unknown") return false; current.room = game.ReadValue<int>((IntPtr)vars.ptrRoomID); current.roomName = vars.getRoomName(); if(version.StartsWith("Combat Demo")) { if(current.roomName == "rm_menu" && old.menuState == "challenges" && current.menuState == "fade_challenges") vars.currentChallenge = (old.menuSelection == 0 ? 1 : old.menuSelection); // There is a global.challenge variable but it's 0 for both No challenge and Trinketless so it's not really reliable // Checking for selection 0 for the same reason mentioned in start{} } if(old.room != current.room) print("[Undertale Wildfire] Room: " + old.room + " (" + old.roomName + ")" + " -> " + current.room + " (" + current.roomName + ")"); } split { int done = 0, oldRoom = 1, newRoom = 2, condition = 3; foreach(string splitKey in vars.splits.Keys) { if((!settings[splitKey] || vars.splits[splitKey][done]) || (vars.splits[splitKey][oldRoom] != null && old.roomName != vars.splits[splitKey][oldRoom]) || (vars.splits[splitKey][newRoom] != null && current.roomName != vars.splits[splitKey][newRoom])) continue; bool pass = false; int specialCondition = vars.splits[splitKey][condition]; switch(specialCondition) { case 0: pass = true; break; case 1: case 2: case 3: case 4: case 5: pass = (current.playerX >= 969 && vars.currentChallenge == specialCondition); break; } if(pass) { vars.splits[splitKey][done] = true; print("[Undertale Wildfire] Split triggered (" + splitKey + ")"); return true; } } }