// ASL by Chimpaneez, Skajdrovski, & Meta state("bf3") { byte loading : 0x1EE37C0; // this old pointer seems prone to timebleed, but leaving for now - may update in future with permission of moderators float FOV : 0x1F8B4A0, 0x0; string300 levelName : 0x1EF2650, 0x18, 0x7C, 0x0; string300 activeMovie : 0x1FB06F4, 0xD8, 0x3C8; } startup { #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 3"; 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"}, {"Debug" , true, "Print various debug info to LiveSplit layout" , null}, {"Autostart" , false, "Autostart" ,"Debug"}, {"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_New_York/SP_New_York", "Semper Fidelis"}, {"Levels/SP_Earthquake/SP_Earthquake", "Operation Swordbreaker"}, {"Levels/SP_Earthquake2/SP_Earthquake2","Uprising"}, {"Levels/SP_Jet/SP_Jet", "Going Hunting"}, {"Levels/SP_Bank/SP_Bank", "Operation Guillotinne"}, {"Levels/SP_Paris/SP_Paris", "Comrades"}, {"Levels/SP_Tank/SP_Tank", "Thunder Run"}, {"Levels/SP_Tank_b/SP_Tank_b", "Fear No Evil"}, {"Levels/SP_Sniper/SP_Sniper", "Night Shift"}, {"Levels/SP_Valley/SP_Valley", "Rock and a Hard Place"}, {"Levels/SP_Villa/SP_Villa", "Kaffarov"}, {"Levels/SP_Finale/SP_Finale", "The Great Destroyer"}, }; //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.completedSplits = new List(); current.autostartMovieSubstring = ""; 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"; current.autostartMovieSubstring = "No Movie Currently Playing";} if(current.levelName == null) {current.levelName = "Waiting For Mission...";} //Setting up a substring for autostart and prettyfying current.activeMovie and current.levelName while we're at it 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" current.autostartMovieSubstring = movieFullString.Substring(movieSecondIndex + 3); } else { current.activeMoviePretty = "No Movie Currently Playing"; //If "SP" wasn't found twice, theres probably no movie playing so we say so current.autostartMovieSubstring = "No Movie Currently Playing"; } } } 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 + 3); } 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("Autostart", current.autostartMovieSubstring); 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 // BF3 FOV Pointer Chain - bf3.exe + 0x1F8B4A0 → 0 // 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 - bf3.exe + the offset where the pointer is located and store it in the variable base1 IntPtr base1 = IntPtr.Add(modules.First().BaseAddress, 0x1F8B4A0); // 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 fovAddr = (IntPtr)BitConverter.ToInt64(buf, 0); // Default FOV value to write is 55 byte[] FOVBytes = BitConverter.GetBytes((float)55); // 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 != 0; } start { return old.autostartMovieSubstring != current.autostartMovieSubstring && old.autostartMovieSubstring == "Intro"; } 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; } } /* split { return current.level != old.level && current.level != "" && current.level != "Levels/Web_Loading/Web_Loading" && current.level != "Levels/FrontEnd/FrontEnd"; } */ onReset { vars.completedSplits.Clear(); } exit { timer.IsGameTimePaused = true; }