state("bf4") { int loading : 0x23819B0, 0x328, 0x48; float FOV : 0x26611E0, 0x90; string300 levelName : 0x25EEA30, 0x20, 0x118, 0x0; string300 activeMovie : 0x23D4FC8, 0x1D8, 0x2C; } //1:36.589 startup { vars.startTimeOffset = -96.589f; #region ASL Helper Setup //Load asl-help binary and instantiate it - will inject code into the asl in the background //This isnt actually a Unity game but shhhhhhh Assembly.Load(File.ReadAllBytes("Components/asl-help")).CreateInstance("Basic"); //Setting Game Name and toggling alert to ensure runner is comparing against Game TIme vars.Helper.GameName = "Battlefield 4"; vars.Helper.AlertLoadless(); #endregion #region setting creation //Autosplitter Settings Creation dynamic[,] _settings = { {"Split Options" , true, "Split Options" , null}, {"LevelSplit" , true, "Split on Level Change" , "Split Options"}, {"SetFOV" , true, "Set Custom FOV values - Select One" , null}, {"SetFOV70" , true, "Set FOV to 70" , "SetFOV"}, {"SetFOV80" , false, "Set FOV to 80" , "SetFOV"}, {"SetFOV90" , false, "Set FOV to 90" , "SetFOV"}, {"SetFOV100" , false, "Set FOV to 100" , "SetFOV"}, {"GameInfo" , true, "Print various Game Info to LiveSplit layout" , null}, {"Mission" , true, "Mission" ,"GameInfo"}, {"Movie File" , true, "Movie" ,"GameInfo"}, {"Loading" , false, "Loading" ,"GameInfo"}, {"FOV" , true, "FOV" ,"GameInfo"}, {"textDisplay" , true, "Text Options" , null}, {"removeTexts" , true, "Remove all texts on exit" , "textDisplay"}, }; vars.Helper.Settings.Create(_settings); #endregion #region TextComponent vars.lcCache = new Dictionary(); vars.SetText = (Action)((text1, text2) => { const string FileName = "LiveSplit.Text.dll"; LiveSplit.UI.Components.ILayoutComponent lc; if (!vars.lcCache.TryGetValue(text1, out lc)) { lc = timer.Layout.LayoutComponents.Reverse().Cast() .FirstOrDefault(llc => llc.Path.EndsWith(FileName) && llc.Component.Settings.Text1 == text1) ?? LiveSplit.UI.Components.ComponentManager.LoadLayoutComponent(FileName, timer); vars.lcCache.Add(text1, lc); } if (!timer.Layout.LayoutComponents.Contains(lc)) timer.Layout.LayoutComponents.Add(lc); dynamic tc = lc.Component; tc.Settings.Text1 = text1; tc.Settings.Text2 = text2.ToString(); }); vars.RemoveText = (Action)(text1 => { LiveSplit.UI.Components.ILayoutComponent lc; if (vars.lcCache.TryGetValue(text1, out lc)) { timer.Layout.LayoutComponents.Remove(lc); vars.lcCache.Remove(text1); } }); vars.RemoveAllTexts = (Action)(() => { foreach (var lc in vars.lcCache.Values) timer.Layout.LayoutComponents.Remove(lc); vars.lcCache.Clear(); }); #endregion //Creating the setting for the episode names to sit under settings.Add("Levels", true, "All Levels"); //Dictionary containing all of the episodes that can be split on vars.Levels = new Dictionary { {"Levels/FrontEnd/FrontEnd", "Main Menu"}, {"Levels/SP/SP_Prologue/SP_Prologue", "Baku"}, {"Levels/SP/SP_Shanghai/SP_Shanghai", "Shanghai"}, {"Levels/SP/SP_Naval/SP_Naval", "South China Sea"}, {"Levels/SP/SP_Airfield/SP_Airfield", "Singapore"}, {"Levels/SP/SP_Prison/SP_Prison", "Kunlun Mountains"}, {"Levels/SP/SP_Dam/SP_Dam", "Tashgar"}, {"Levels/SP/SP_Suez/SP_Suez", "Suez"}, }; //When a new level is detected and is in the dictionary, add it as a setting value which we will use to split later on foreach (var script in vars.Levels) { settings.Add(script.Key, true, script.Value, "Levels"); } } init { vars.setStartTime = false; vars.completedSplits = new List(); current.activeMoviePretty = ""; current.levelNamePretty = ""; vars.SetTextIfEnabled = (Action)((text1, text2) => { if (settings[text1]) vars.SetText(text1, text2); else vars.RemoveText(text1); }); } onStart { vars.setStartTime = true; vars.completedSplits.Add(current.levelName); timer.IsGameTimePaused = true; } update { #region Prettifying Strings //null checks if(current.activeMovie == null) {current.activeMoviePretty = "No Movie Currently Playing";} if(current.levelName == null) {current.levelName = "Waiting For Mission...";} //prettyfying current.activeMovie and current.levelName if (current.activeMovie != null) { var movieFullString = current.activeMovie.ToString(); // Convert the activeMovie value to a string so we can search inside it - in this case its already a string, but this is good for template use :) var movieFirstIndex = movieFullString.IndexOf("SP"); // Find the index (position) of the first occurrence of "SP" var movieSecondIndex = movieFullString.IndexOf("SP", movieFirstIndex + 1); // Find the index of the second occurrence of "SP" then we start searching just after the first "SP" to find the next one if(current.activeMovie != null) { //If we successfully found the second "SP"... if (movieSecondIndex != -1) { current.activeMoviePretty = movieFullString.Substring(movieSecondIndex + 3); //...cut off everything before the second "SP" ie "0,Levels/SP_Tank_b/SP_Tank_b" → "SP_Tank_b" } else { current.activeMoviePretty = "No Movie Currently Playing"; //If "SP" wasn't found twice, theres probably no movie playing so we say so } } } var levelFullString = current.levelName.ToString(); var levelFirstIndex = levelFullString.IndexOf("SP_"); var levelSecondIndex = levelFullString.IndexOf("SP_", levelFirstIndex + 1); //If we successfully found the second "SP"... if (levelSecondIndex != -1) { current.levelNamePretty = levelFullString.Substring(levelSecondIndex); } else { if(current.levelName != "Levels/FrontEnd/FrontEnd") { current.levelNamePretty = "Waiting For Mission..."; } if(current.levelName == "Levels/FrontEnd/FrontEnd") { current.levelNamePretty = "Main Menu"; } } #endregion //Generating the text for the text components vars.SetTextIfEnabled("Mission",current.levelNamePretty); vars.SetTextIfEnabled("Movie File",current.activeMoviePretty); vars.SetTextIfEnabled("Loading", current.loading); vars.SetTextIfEnabled("FOV",current.FOV); //Debug //print("Loading: " + current.loading); //print("secondaryFOV: " + current.secondaryFOV); //print("levelName: " + current.levelName); //print("Loading: " + current.loading + " (type: " + current.loading.GetType().ToString() + ")"); #region FOV Injection // BF4 FOV Pointer Chain - bf4.exe + 0x26611E0 → 0x90 // Get a handle to the game process so we can read/write its memory IntPtr handle = game.Handle; // Set up a buffer to hold data read from memory - we use 8 bytes because we're working with a 64-bit game byte[] buf = new byte[8]; UIntPtr size = (UIntPtr)8; // Size of what we're reading or writing UIntPtr bytesRead; // Stores how many bytes we successfully read UIntPtr bytesWritten; // Stores how many bytes we successfully wrote // Get the address of the pointer that leads us to the FOV value - bf4.exe + the offset where the pointer is located and store it in the variable base1 IntPtr base1 = IntPtr.Add(modules.First().BaseAddress, 0x26611E0); // If the user enabled FOV injection in the settings, continue if (settings["SetFOV"] && WinAPI.ReadProcessMemory(handle, base1, buf, size, out bytesRead)) { // Read the pointer we stored in the variable base1 // This gives us the memory address that points to the actual FOV value. IntPtr p1 = (IntPtr)BitConverter.ToInt64(buf, 0); // dereference pointer IntPtr fovAddr = IntPtr.Add(p1, 0x90); // now add offset 0x90 here // Default FOV value to write is 55 byte[] FOVBytes = BitConverter.GetBytes((float)45); // Override FOV value if the user picked a specific setting if (settings["SetFOV70"]) { FOVBytes = BitConverter.GetBytes((float)70); } if (settings["SetFOV80"]) { FOVBytes = BitConverter.GetBytes((float)80); } if (settings["SetFOV90"]) { FOVBytes = BitConverter.GetBytes((float)90); } if (settings["SetFOV100"]) { FOVBytes = BitConverter.GetBytes((float)100); } // Write the new FOV value to the memory address we found earlier WinAPI.WriteProcessMemory(handle, fovAddr, FOVBytes, (UIntPtr)4, out bytesWritten); // Optional: Print out the memory address we wrote to (for debugging) // print("Wrote FOV to " + fovAddr.ToString("X")); } #endregion } isLoading { return current.loading == 1; } start { return current.levelName == "Levels/SP/SP_Prologue/SP_Prologue" && old.loading == 1 && current.loading == 0; } split { //if the level is in the settings, has not been entered into the dictionary yet, and is not Null or White Space if(settings[current.levelName] && !vars.completedSplits.Contains(current.levelName) && !String.IsNullOrWhiteSpace(current.levelName) && current.levelNamePretty != "Main Menu") { vars.completedSplits.Add(current.levelName); return true; } } onReset { vars.completedSplits.Clear(); } gameTime { if(vars.setStartTime) { vars.setStartTime = false; return TimeSpan.FromSeconds(vars.startTimeOffset); } } exit { timer.IsGameTimePaused = true; }