state("SplitFiction"){} state("SplitFiction_Trial"){} startup { if (timer.CurrentTimingMethod == TimingMethod.RealTime) { var timingMessage = MessageBox.Show( "This game uses IGT as the main timing method.\n" + "LiveSplit is currently set to show Real Time (RTA).\n" + "Would you like to set the timing method to IGT?", "Split Fiction | LiveSplit", MessageBoxButtons.YesNo, MessageBoxIcon.Question ); if (timingMessage == DialogResult.Yes) { timer.CurrentTimingMethod = TimingMethod.GameTime; } } vars.AccumulatedSessionTime = 0d; string LOGFILE = "Logs/SplitFiction.log"; if (!File.Exists(LOGFILE)) { Directory.CreateDirectory("Logs"); File.Create(LOGFILE); } Func<object, bool> WriteLog = (data) => { using (StreamWriter wr = new StreamWriter(LOGFILE, true)) { wr.WriteLine( DateTime.Now.ToString(@"HH\:mm\:ss.fff") + (timer != null && timer.CurrentTime.GameTime.HasValue ? " | " + timer.CurrentTime.GameTime.Value.ToString("G").Substring(3, 11) : "") + ": " + data); } print("[SplitFiction] " + data); return true; }; vars.Log = WriteLog; Action<string, string> AddSetting = (name, id) => { settings.Add(id, false, name); }; AddSetting("Enable the in-game timer", "enableInGameTimer"); AddSetting("Chapter Splits", "chapterSplits"); AddSetting("Subchapter Splits", "subchapterSplits"); AddSetting("Side Stories", "sideStories"); settings.CurrentDefaultParent = "chapterSplits"; AddSetting("Neon Revenge", "Skyline_Highway_Tutorial_BP##Tutorial"); AddSetting("Hopes of Spring", "Tundra_Crack_Swamp_BP##Swamp_Caves"); AddSetting("Final Dawn", "Island_Entrance_BP##Skydive"); AddSetting("Rise of the Dragon Realm", "Summit_EggPath_BP##Entrance"); AddSetting("Isolation", "Prison_Intro_BP##Outside_LevelStart"); AddSetting("The Hollow", "Sanctuary_Upper_Tutorial_BP##Sanctuary Intro"); AddSetting("Split", "Meltdown_SplitTraversal_BP##Split Traversal Intro"); AddSetting("Defeating Rader", "SEQ_Meltdown_Battle_Phase3_ScreenPush -> None"); settings.CurrentDefaultParent = "subchapterSplits"; AddSetting("Rader Publishing", "raderPublishing"); AddSetting("Neon Revenge", "neonRevenge"); AddSetting("Hopes of Spring", "hopesOfSpring"); AddSetting("Final Dawn", "finalDawn"); AddSetting("Rise of the Dragon Realm", "dragonRealm"); AddSetting("Isolation", "isolation"); AddSetting("The Hollow", "hollow"); AddSetting("Split", "split"); settings.CurrentDefaultParent = "sideStories"; AddSetting("Neon Revenge", "neonRevengeSS"); AddSetting("Hopes of Spring", "hopesOfSpringSS"); AddSetting("Final Dawn", "finalDawnSS"); AddSetting("Rise of the Dragon Realm", "dragonRealmSS"); settings.CurrentDefaultParent = "raderPublishing"; AddSetting("Brave Knights", "Village_BP##Intro"); settings.CurrentDefaultParent = "neonRevenge"; AddSetting("Play Me Techno", "Skyline_DaClub_BP##DaClub Finding Sandfish"); AddSetting("Hello, Mr. Hammer", "Skyline_Nightclub_Club_BP##Club Combat Entry"); AddSetting("Streets of Neon", "Skyline_Nightclub_Alley_BP##Alley 0 - Start"); AddSetting("Parking Garage", "Skyline_CarTower_BP##BallBoss_Chase_Intro"); AddSetting("The Getaway Car", "Skyline_Chase_Tutorial_BP##Chase_Alley_Start"); AddSetting("Big City Life", "Skyline_InnerCity_CarCrash_BP##CarCrash Site"); AddSetting("Flipped Cityscapes", "Skyline_InnerCity_Limbo_BP##Limbo Intro"); AddSetting("Gravity Bike", "Skyline_GravityBike_Tutorial_BP##Intro"); AddSetting("Skyscraper Climb", "Skyline_Boss_Tutorial_BP##BikeTutorial 1"); AddSetting("Head of the Crime Syndicate", "Skyline_Boss_V2_BP##Tank Phase 1 (No intro)"); settings.CurrentDefaultParent = "neonRevengeSS"; AddSetting("The Legend of the Sandfish", "Desert_SandFish_BP##Intro"); AddSetting("The Legend of the Sandfish Ending", "Skyline_DaClub_BP##DaClub Exiting Sandfish"); AddSetting("Farmlife", "PigWorld_BP##Pigsty_Intro"); AddSetting("Farmlife Ending", "Skyline_Nightclub_PigWorld_BP##PigWorld Exit Cutscene"); AddSetting("Mountain Hike", "Summit_Giants_BP##IntroTraversalArea"); AddSetting("Mountain Hike Ending", "Skyline_InnerCity_Drones_BP##Giants Exit Cutscene"); settings.CurrentDefaultParent = "hopesOfSpring"; AddSetting("Lord Evergreen", "Tundra_Crack_Swamp_BP##Swamp_Wetlands"); AddSetting("Heart of the Forest", "Tundra_Crack_Evergreen_BP##Evergreen_Inside"); AddSetting("Mother Earth", "Tundra_Crack_EvergreenSide_BP##SideSectionStart"); AddSetting("Walking Stick of Doom", "Tundra_Crack_Forest_BP##CreepyForest"); AddSetting("Silly Monkeys", "Tundra_River_MonkeyRealm_BP##MountainPath"); AddSetting("It Takes Three to Tango", "Tundra_River_MonkeyRealm_BP##MonkeyConga"); AddSetting("Halls of Ice", "None -> SEQ_Tundra_IcePalace_Outergate_Entering"); AddSetting("The Ice King", "Tundra_River_IcePalace_BP##IceKing - Phase01"); settings.CurrentDefaultParent = "hopesOfSpringSS"; AddSetting("Train Heist", "Coast_TwistyTrain_BP##WingsuitIntroNoSEQ"); AddSetting("Train Heist Ending", "None -> SEQ_Tundra_SideGlitch_Coast_Outro"); AddSetting("Gameshow", "GameShowArena_BP##GameShowArena - Start"); AddSetting("Gameshow Ending", "None -> SEQ_Tundra_SideGlitch_Gameshow_Outro"); AddSetting("Collapsing Star", "SolarFlare_BP##SolarFlare_Intro"); AddSetting("Collapsing Start Ending", "Tundra_River_IcePalace_BP##IcePalace - Completed Solarflare"); settings.CurrentDefaultParent = "finalDawn"; AddSetting("Infiltration", "Island_Stormdrain_BP##Start"); AddSetting("Gun Upgrade", "Island_Stormdrain_BP##WeaponUpgradeStation"); AddSetting("Toxic Tumblers", "Island_Stormdrain_BP##SpinningHallway"); AddSetting("Factory Entrance", "Island_Rift_BP##Hallway"); AddSetting("Factory Exterior", "Island_Rift_BP##Cable House"); AddSetting("Test Chamber", "Island_Rift_BP##Walker Arena"); AddSetting("Run and Gun", "Island_Tower_Sidescroller_BP##Sidescroller_Intro"); AddSetting("The Overseer", "Island_Tower_Sidescroller_BossFight_BP##Overseer_Entry"); AddSetting("Soaring Desperados", "Island_Tower_Sidescroller_Jetpack_BP##Jetpack_Tutorial"); AddSetting("The Escape", "Island_Escape_BP##Inner Tower"); AddSetting("System Fail Safe Mode", "RedSpace_BP##BeforeRedspace"); settings.CurrentDefaultParent = "finalDawnSS"; AddSetting("Kites", "KiteTown_BP##Intro"); AddSetting("Kites Ending", "Island_Stormdrain_BP##SpinningHallway_AfterKitesFinished"); AddSetting("Moon Market", "MoonMarket_BP##Intro"); AddSetting("Moon Market Ending", "Island_Rift_BP##Robot Arms After Moon Market Finished"); AddSetting("Notebook", "Sketchbook_BP##Intro"); AddSetting("Notebook Ending", "Island_Tower_OuterRift_BP##After SketchBook Finished"); settings.CurrentDefaultParent = "dragonRealm"; AddSetting("Water Temple", "Summit_WaterTempleInner_Raft_BP##SlowRaftStart"); AddSetting("Dragon Riders Unite", "Summit_CraftApproach_BP##Water Volcano"); AddSetting("The Dragon Slayer", "None -> SEQ_Summit_CraftTemple_KnightArena_Intro"); AddSetting("Craft Temple", "Summit_CraftTemple_BP##CraftTemple_Start"); AddSetting("Dragon Souls", "Summit_CraftTemple_BP##CraftTemple_DarkCaveEntrance"); AddSetting("Treasure Temple", "Summit_TreasureTemple_BP##Gauntlet"); AddSetting("Royal Palace", "Summit_TreasureTemple_BP##TopDown"); AddSetting("Treasure Traitor", "Summit_TreasureTemple_BP##Decimator"); AddSetting("Might of Dragons", "Summit_StormSiegeIntro_BP##Intro_Tutorial"); AddSetting("Into the Storm", "Summit_StormSiegeChase_BP##Intro"); AddSetting("Megalith's Wrath", "Summit_StormSiegeFinale_BP##Summit_StormSiegeFinale_FallingDebris"); settings.CurrentDefaultParent = "dragonRealmSS"; AddSetting("Slopes of War", "Battlefield_BP##Battlefield_Intro"); AddSetting("Slopes of War Ending", "Summit_WaterTempleInner_BP##Summit_WaterTempleInner_BattlefieldCompleted"); AddSetting("Space Escape", "SpaceWalk_BP##SpaceWalk_Indoor"); AddSetting("Space Escape Ending", "Summit_CraftTemple_BP##CraftTemple_SpaceWalkCompleted"); AddSetting("Birthday Cake", "DentistNightmare_BP##Intro"); AddSetting("Birthday Cake Ending", "Summit_TreasureTemple_BP##DentistCompleted"); settings.CurrentDefaultParent = "isolation"; AddSetting("Handy Drones", "Prison_Drones_Maintenance_BP##Drones_LevelStart"); AddSetting("Down the Rabbit Hole", "Prison_Drones_InBetween_01_BP##InBetween_Slide"); AddSetting("Hydration Facility", "Prison_Drones_Cooling_BP##Cooling_Start_NoIntro"); AddSetting("Prison Courtyard", "Prison_Drones_Stealth_Outdoor_BP##Stealth_Intro"); AddSetting("Pinball Lock", "Prison_Drones_Pinball_BP##Pinball_Start"); AddSetting("Execution Arena", "Prison_Arena_BP##Intro"); AddSetting("Waste Depot", "Prison_GarbageRoom_BP##GarbageRoom_Slide"); AddSetting("Cell Blocks", "Prison_TrashCompactor_BP##TrashCompactor_Top"); AddSetting("Maximum Security", "Prison_MaxSecurity_BP##MaxSecurity_Intro"); AddSetting("The Prisoner", "Prison_Boss_BP##Intro"); settings.CurrentDefaultParent = "hollow"; AddSetting("Mosaic of Memories", "Sanctuary_Upper_BP##Upper WatchTower"); AddSetting("Ghost Town", "Sanctuary_DiscSlide_BP##DiscSlide_Start"); AddSetting("Light in the Dark", "Sanctuary_Below_CrackApproach_BP##CrackApproach_DrawBridge"); AddSetting("Spiritual Guides", "Sanctuary_Centipede_Tutorial_BP##Centipede_StartRoom"); AddSetting("The Hydra", "Sanctuary_Boss_Medallion_BP##Hydra Reveal Intro"); settings.CurrentDefaultParent = "split"; AddSetting("A Warm Greeting", "Meltdown_SplitTraversal_BP##Split Traversal Bridge Entrance"); AddSetting("Face-to-Face", "Meltdown_BossBattleFirstPhase_BP##Boss phase Start"); AddSetting("Worlds Apart", "Meltdown_SoftSplit_BP##Soft Split Start"); AddSetting("Cross Section", "Meltdown_SplitBonanza_BP##Split Bonanza Start"); AddSetting("Fight a God", "Meltdown_BossBattleSecondPhase_BP##BossBattlePhase Lava"); AddSetting("A New Perspective", "Meltdown_ScreenWalk_BP##Meltdown ScreenWalk Intro"); AddSetting("Outside the Box", "Meltdown_WorldSpin_Fullscreen_BP##WorldSpin Cutscene Intro"); AddSetting("Final Showdown", "Meltdown_BossBattleThirdPhase_BP##BossBattlePhaseThree First Phase "); } init { vars.CancelSource = new CancellationTokenSource(); vars.ScanThread = new Thread(() => { vars.Log("Starting scan thread."); var scanner = new SignatureScanner(game, modules.First().BaseAddress, modules.First().ModuleMemorySize); var token = vars.CancelSource.Token; var GWorld = IntPtr.Zero; var GWorldTrg = new SigScanTarget(3, "48 8B 15 ???????? 49 8D 4F ?? 4C 8B C8") { OnFound = (p, s, ptr) => ptr + 0x4 + p.ReadValue<int>(ptr)}; var FNamePool = IntPtr.Zero; var FNamePoolTrg = new SigScanTarget(7, "8B D9 74 ?? 48 8D 15 ???????? EB") { OnFound = (p, s, ptr) => ptr + 0x4 + p.ReadValue<int>(ptr)}; while (!token.IsCancellationRequested) { if (GWorld == IntPtr.Zero && (GWorld = scanner.Scan(GWorldTrg)) != IntPtr.Zero) { vars.Log("Found GWorld at 0x" + GWorld.ToString("X") + "."); vars.bDisplayTimerDP = new DeepPointer(GWorld, 0x1D8, 0x3E0, 0x78, 0x38); vars.Data = new MemoryWatcherList { new StringWatcher(new DeepPointer(GWorld, 0x5C8, 0x0), 255) { Name = "Level"}, // GWorld.GameInstance.PlayerCharacters[0(Mio)].ActiveLevelSequenceActor.SequencePlayer.Sequence.Name new MemoryWatcher<int>(new DeepPointer(GWorld, 0x1D8, 0x300, 0x0, 0x430, 0x368, 0x2b0, 0x18)) { Name = "SequenceName", FailAction = MemoryWatcher.ReadFailAction.SetZeroOrNull, Current = 0}, // GWorld.GameInstance.SingletonObjects[78(UGlobalMenuSingleton)].Padding // Padding is always FF899701107998FF // new MemoryWatcher<IntPtr>(new DeepPointer(GWorld, 0x1D8, 0x3E0, 0x78, 0x30)) { Name = "Padding"}, new MemoryWatcher<bool>(vars.bDisplayTimerDP) { Name = "bDisplayTimer"}, new MemoryWatcher<double>(new DeepPointer(GWorld, 0x1D8, 0x3E0, 0x78, 0x40)) { Name = "GameSessionTimer", Current = 0}, new MemoryWatcher<bool>(new DeepPointer(GWorld, 0x1D8, 0x3E0, 0x78, 0x58)) { Name = "bGameIsLoading", Current = true}, new StringWatcher(new DeepPointer(GWorld, 0x1D8, 0x3E0, 0x78, 0x60, 0x0), 255) { Name = "CurrentChapter"}, // This string is localized //new StringWatcher(new DeepPointer(GWorld, 0x1D8, 0x3E0, 0x78, 0x80 + 0x0, 0x0), 255) { Name = "CurrentChapterRef.InLevel"}, // Going to use this one instead //new StringWatcher(new DeepPointer(GWorld, 0x1D8, 0x3E0, 0x78, 0x80 + 0x10, 0x0), 255) { Name = "CurrentChapterRef.Name"}, new StringWatcher(new DeepPointer(GWorld, 0x1D8, 0x298, 0xF8, 0x0), 255) { Name = "ProgressPoint.InLevel"}, new StringWatcher(new DeepPointer(GWorld, 0x1D8, 0x298, 0x108, 0x0), 255) { Name = "ProgressPoint.Name"}, }; } if (FNamePool == IntPtr.Zero && (FNamePool = scanner.Scan(FNamePoolTrg)) != IntPtr.Zero) { vars.FNamePool = FNamePool; vars.Log("Found FNamePool at 0x" + FNamePool.ToString("X") + "."); } if (GWorld != IntPtr.Zero && FNamePool != IntPtr.Zero) { break; } vars.Log("Sleeping..."); Thread.Sleep(2000); } vars.Log("Exiting scan thread."); }); vars.ScanThread.Start(); vars.FNamePool = IntPtr.Zero; vars.FNameToString = (Func<int, string>)((comparisonIndex) => { if (vars.FNamePool == IntPtr.Zero) { return null; } var blockIndex = comparisonIndex >> 16; var blockOffset = 2 * (comparisonIndex & 0xFFFF); var headerPtr = new DeepPointer(vars.FNamePool + blockIndex * 8 + 0x10, blockOffset); byte[] headerBytes = null; if (headerPtr.DerefBytes(game, 2, out headerBytes)) { bool isWide = (headerBytes[0] & 0x01) != 0; int length = (headerBytes[1] << 2) | ((headerBytes[0] & 0xC0) >> 6); IntPtr headerRawPtr; if (headerPtr.DerefOffsets(game, out headerRawPtr)) { var stringPtr = new DeepPointer(headerRawPtr + 2); ReadStringType stringType = isWide ? ReadStringType.UTF16 : ReadStringType.ASCII; int numBytes = length * (isWide ? 2 : 1); string str; if (stringPtr.DerefString(game, stringType, numBytes, out str)) { return str; } } } return null; }); Func<string, string> GetObjectNameFromObjectPath = (objectPath) => { if (objectPath == null) { return null; } int lastDotIndex = objectPath.LastIndexOf('.'); if (lastDotIndex == -1) { return objectPath; } return objectPath.Substring(lastDotIndex + 1); }; vars.GetObjectNameFromFName = (Func<int, string>) ((comparisonIndex) => { return GetObjectNameFromObjectPath(vars.FNameToString(comparisonIndex)); }); current.ProgressPoint = ""; current.InLevelShort = ""; } update { if (vars.ScanThread.IsAlive) return false; vars.Data.UpdateAll(game); if (vars.Data["SequenceName"].Old != vars.Data["SequenceName"].Current) { vars.Log(String.Format( "SequenceName changed: Old: '{0}' -> New: '{1}'", vars.FNameToString(vars.Data["SequenceName"].Old), vars.FNameToString(vars.Data["SequenceName"].Current) )); } if (vars.Data["CurrentChapter"].Old != vars.Data["CurrentChapter"].Current) { vars.Log(String.Format( "CurrentChapter changed: Old: '{0}' -> New: '{1}'", vars.Data["CurrentChapter"].Old, vars.Data["CurrentChapter"].Current )); } if (vars.Data["ProgressPoint.InLevel"].Old != vars.Data["ProgressPoint.InLevel"].Current || vars.Data["ProgressPoint.Name"].Old != vars.Data["ProgressPoint.Name"].Current) { vars.Log(String.Format( "ProgressPoint.InLevel changed: Old: '{0}' -> New: '{1}'", vars.Data["ProgressPoint.InLevel"].Old, vars.Data["ProgressPoint.InLevel"].Current )); current.InLevelShort = vars.Data["ProgressPoint.InLevel"].Current; int lastIndex = current.InLevelShort.LastIndexOf('/'); if (lastIndex != -1) { current.InLevelShort = current.InLevelShort.Substring(lastIndex + 1); } old.ProgressPoint = current.ProgressPoint; current.ProgressPoint = String.Format("{0}##{1}", current.InLevelShort, vars.Data["ProgressPoint.Name"].Current); vars.Log(String.Format( "ProgressPoint changed: Old: '{0}' -> New: '{1}'", old.ProgressPoint, current.ProgressPoint )); } if (settings["enableInGameTimer"] && !vars.Data["bDisplayTimer"].Current) { // vars.Log("Enabling in-game timer."); IntPtr bDisplayTimerPtr; vars.bDisplayTimerDP.DerefOffsets(game, out bDisplayTimerPtr); game.WriteValue<bool>(bDisplayTimerPtr, true); } } isLoading { return true; // return vars.Data["bGameIsLoading"].Current; } start { return vars.Data["GameSessionTimer"].Current > 0.000d && vars.Data["GameSessionTimer"].Old <= 0.000d; } exit { timer.IsGameTimePaused = true; } onReset { vars.AccumulatedSessionTime = 0; } onStart { vars.AccumulatedSessionTime = 0; } gameTime { var GameSessionTimer = vars.Data["GameSessionTimer"]; if (GameSessionTimer.Current > GameSessionTimer.Old) { vars.AccumulatedSessionTime += (GameSessionTimer.Current - GameSessionTimer.Old); } return TimeSpan.FromSeconds(vars.AccumulatedSessionTime); } split { var ids = new List<string>(); if (vars.Data["SequenceName"].Changed) { ids.Add(vars.FNameToString(vars.Data["SequenceName"].Old) + " -> " + vars.FNameToString(vars.Data["SequenceName"].Current)); } if (current.InLevelShort != old.InLevelShort) { ids.Add(current.InLevelShort); } if (current.ProgressPoint != old.ProgressPoint) { ids.Add(current.ProgressPoint); } foreach (var id in ids) { if (settings.ContainsKey(id) && settings[id]) { vars.Log("Split! " + id); return true; } } }