//ALTF2 Autosplitter V1.0 - 5th June 2024
//Supports Load Remover & Autosplits
//By TheDementedSalad & Rumii
//Special thanks to Rumii for doing the code injection to get the loading progress bar

state("ALTF42-Win64-Shipping"){ }

startup
{
	vars.ItemSettingFormat = "[{0}] {1} ({2})";

	Assembly.Load(File.ReadAllBytes("Components/asl-help")).CreateInstance("Basic");
	vars.Helper.Settings.CreateFromXml("Components/ALTF42.Settings.xml");
	vars.Helper.GameName = "ALTF4 2 (2024)";
	//vars.Helper.StartFileLogger("AitD_Log.txt");

	vars.completedSplits = new HashSet<string>();
}

onStart
{
	vars.completedSplits.Clear();

	// This makes sure the timer always starts at 0.00
	timer.IsGameTimePaused = true;
}

init
{
	IntPtr gWorld = vars.Helper.ScanRel(3, "48 8B 05 ???????? 48 3B C? 48 0F 44 C? 48 89 05 ???????? E8");
	IntPtr gEngine = vars.Helper.ScanRel(3, "48 89 05 ???????? 48 85 c9 74 ?? e8 ???????? 48 8d 4d");
	vars.FNames = vars.Helper.ScanRel(25, "66 0F 7F 44 24 20 E8 ???????? EB ?? 80 3D ???????? 00");

	vars.Helper["cantMove"] = vars.Helper.Make<bool>(gEngine, 0x1080, 0x38, 0x0, 0x30, 0x2E8, 0xB1F);

	vars.Helper["Level"] = vars.Helper.MakeString(gEngine, 0xB98, 0x20);
	vars.Helper["Level"].FailAction = MemoryWatcher.ReadFailAction.SetZeroOrNull;

	vars.Helper["localPlayer"] = vars.Helper.Make<long>(gWorld, 0x1B8, 0x38, 0x0, 0x30);
	vars.Helper["localPlayer"].FailAction = MemoryWatcher.ReadFailAction.SetZeroOrNull;

	vars.GetObjectName = (Func<IntPtr, string>)((uObject) =>
	{
		ulong fName = memory.ReadValue<ulong>(uObject + 0x18);
		if (fName != 0)
		{
			var nameIdx = (fName & 0x000000000000FFFF) >> 0x00;
			var chunkIdx = (fName & 0x00000000FFFF0000) >> 0x10;
			var number = (fName & 0xFFFFFFFF00000000) >> 0x20;

			IntPtr chunk = memory.ReadValue<IntPtr>((IntPtr)vars.FNames + 0x10 + (int)chunkIdx * 0x8);
			IntPtr entry = chunk + (int)nameIdx * sizeof(short);

			int length = memory.ReadValue<short>(entry) >> 6;
			string name = memory.ReadString(entry + sizeof(short), length);

			return number == 0 ? name : name + "_" + number;
		}
		else return "";
	});

	vars.ProgressBarPtr = 0; ulong allocated = 0; byte[] longJump = { 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 };
	ulong updateProgressBar = (ulong)vars.Helper.Scan(-0x1F, "C6 44 24 24 01 0F 11 44 24 30 48 8D 54 24 20 48 89 44 24");
	if (updateProgressBar != 0)
	{
		byte[] goodBytes = { 0xF3, 0x0F, 0x11, 0x81, 0x10, 0x04, 0x00, 0x00, 0x48, 0x8B, 0x89, 0x58, 0x04, 0x00, 0x00 };
		byte[] foundBytes = memory.ReadBytes((IntPtr)updateProgressBar, goodBytes.Length);

		// numero uno
		if (foundBytes.Length == goodBytes.Length)
		{
			if (goodBytes.SequenceEqual(foundBytes))
			{
				allocated = (ulong)memory.AllocateMemory(0x1000);

				if (allocated != 0)
				{
					vars.ProgressBarPtr = allocated + 0x100;
					byte[] s1 = { 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 };
					byte[] s2 = BitConverter.GetBytes((ulong)allocated);
					byte[] s3 = { 0x90 };
					byte[] start = s1.Concat(s2).Concat(s3).ToArray();

					byte[] e1 = { 0x48, 0x89, 0x0D, 0xF9, 0x00, 0x00, 0x00, 0x90 };
					byte[] e2 = goodBytes;
					byte[] e3 = { 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 };
					byte[] e4 = BitConverter.GetBytes((ulong)updateProgressBar + (ulong)start.Length);
					byte[] end = e1.Concat(e2).Concat(e3).Concat(e4).ToArray();

					memory.WriteBytes((IntPtr)allocated, end);
					memory.WriteBytes((IntPtr)updateProgressBar, start);
				}
			}
			else
			{
				byte[] couldBeJump = new byte[longJump.Length];
				Array.Copy(foundBytes, couldBeJump, longJump.Length);

				if (couldBeJump.SequenceEqual(longJump))
				{
					vars.ProgressBarPtr = memory.ReadValue<ulong>((IntPtr)updateProgressBar + longJump.Length, 8) + 0x100;
					allocated = vars.ProgressBarPtr - 0x100;
				}
			}

			// numero dos
			if (allocated != 0)
			{
				allocated += 0x200;
				ulong updateData = (ulong)vars.Helper.Scan(23, "48 8D 54 24 30 48 8B CE E8 ?? ?? ?? ?? B2 01 49 8B CE E8");
				if (updateData != 0)
				{
					byte[] goodBytes2 = { 0x41, 0xC6, 0x86, 0xA9, 0x03, 0x00, 0x00, 0x00, 0x49, 0x8B, 0xCE, 0x41, 0x80, 0x7F, 0x08, 0x00, 0x0F, 0x94, 0xC2, 0xFE, 0xC2 };
					byte[] foundBytes2 = memory.ReadBytes((IntPtr)updateData, goodBytes2.Length);

					if (goodBytes2.SequenceEqual(foundBytes2))
					{
						byte[] s1 = { 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 };
						byte[] s2 = BitConverter.GetBytes((ulong)allocated);
						byte[] s3 = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 };
						byte[] start = s1.Concat(s2).Concat(s3).ToArray();

						byte[] e1 = { 0x90, 0x90, 0x51, 0x90, 0x48, 0x31, 0xC9, 0x90, 0x48, 0x89, 0x0D, 0xF1, 0xFE, 0xFF, 0xFF, 0x59, 0x90 };
						byte[] e2 = goodBytes2;
						byte[] e3 = { 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 };
						byte[] e4 = BitConverter.GetBytes((ulong)updateData + (ulong)start.Length);
						byte[] end = e1.Concat(e2).Concat(e3).Concat(e4).ToArray();

						memory.WriteBytes((IntPtr)allocated, end);
						memory.WriteBytes((IntPtr)updateData, start);
					}
				}
			}
		}
	}
}

update
{
	vars.Helper.Update();
	vars.Helper.MapPointers();

	//print("");
	//print("----------------------------------------------------------------------------------------------------------------");
	//print("Base: " + vars.GetObjectName((IntPtr)memory.ReadValue<ulong>((IntPtr)vars.ProgressBarPtr)));
	//print("Progress: " + memory.ReadValue<float>((IntPtr)memory.ReadValue<ulong>((IntPtr)(vars.ProgressBarPtr)) + 0x410).ToString());
}

start
{
	return (current.Level == "Map_A_01_Persistent" || current.Level == "Map_A_03_Persistent") && !current.cantMove && old.cantMove;
}

split
{
	string setting = "";

	if (current.Level != old.Level)
	{
		setting = current.Level;
	}

	if (settings.ContainsKey(setting) && settings[setting] && vars.completedSplits.Add(setting))
	{
		return true;
	}
}

isLoading
{
	return memory.ReadValue<ulong>((IntPtr)vars.ProgressBarPtr) != 0 || current.Level == "MainMenu";
	//return vars.GetObjectName((IntPtr)memory.ReadValue<ulong>((IntPtr)vars.ProgressBarPtr)) == "LoadingProgressBar" &&
	//memory.ReadValue<float>((IntPtr)memory.ReadValue<ulong>((IntPtr)(vars.ProgressBarPtr)) + 0x410) != 1;
}

reset
{
}

exit
{
	//pauses timer if the game crashes
	timer.IsGameTimePaused = true;
}