private void ApplyCode(IEnumerable <DataPatcherUnitTask> instructionTasks) { using (var executableConnection = new ExecutableConnection(_executableFilePath)) { foreach (var instructionTask in instructionTasks) { var realPosition = InstructionHelper.CalculateRealPositionFromVirtualPosition(instructionTask.VirtualPosition); Debug.WriteLine($"Executing task {instructionTask.TaskId:D2} at location {instructionTask.VirtualPosition:X8} ({realPosition:X8})"); executableConnection.WriteByteArray(realPosition, instructionTask.Instructions); } } }
/// <summary> /// Applies the changes to enter the track editor in the race screen. /// This method is only intended for use with gpw.exe v1.01b. /// This method requires the Jump Bypass Patcher to be applied beforehand. /// Do not invoke this method more than once on the same file. /// </summary> public void Apply() { const int replacementWndProcLocation = 0x44F2B0; var replacementWndProcInstructions = new byte[] { 0xE8, 0x94, 0x59, 0x02, 0x00 }; const int newTrackEditorToggleLocation = 0x00474C49; var newTrackEditorToggleInstructions = new byte[] { 0x55, 0x8B, 0xEC, 0x53, 0x56, 0x57, 0x83, 0x3D, 0xDC, 0x31, 0x5F, 0x01, 0x00, 0x0F, 0x85, 0x0F, 0x00, 0x00, 0x00, 0xC7, 0x05, 0xDC, 0x31, 0x5F, 0x01, 0x01, 0x00, 0x00, 0x00, 0xE9, 0x0A, 0x00, 0x00, 0x00, 0xC7, 0x05, 0xDC, 0x31, 0x5F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x5F, 0x5E, 0x5B, 0xC9, 0xC3 }; // Open file and write using (var executableConnection = new ExecutableConnection(_executableFilePath)) { // Replace code in WndProc to respond to WM_KEYDOWN for VK_F8 (0x77) // Overwrites existing functionality to reduce race speed with the F8 key executableConnection.WriteByteArray(InstructionHelper.CalculateRealPositionFromVirtualPosition(replacementWndProcLocation), replacementWndProcInstructions); // Apply new function to toggle track editor flag value executableConnection.WriteByteArray(InstructionHelper.CalculateRealPositionFromVirtualPosition(newTrackEditorToggleLocation), newTrackEditorToggleInstructions); } }
private static void ClearExistingBytes(ExecutableConnection executableConnection, int address, int length) { // Create byte array var clearByteArray = new byte[length]; for (var i = 0; i < clearByteArray.Length; i++) { clearByteArray[i] = 0xCC; // Fill array with int3 opcode } // Write byte array executableConnection.WriteByteArray(InstructionHelper.CalculateRealPositionFromVirtualPosition(address), clearByteArray); }
public void Apply() { var firstByte = 0x0047141F; var lastByte = 0x00478F18; // Open file and write using (var executableConnection = new ExecutableConnection(_executableFilePath)) { // Create byte array of NOPs var nopInstructions = new byte[lastByte + 1 - firstByte]; for (var i = 0; i < nopInstructions.Length; i++) { nopInstructions[i] = 0x90; } // Write byte array of NOPs executableConnection.WriteByteArray(InstructionHelper.CalculateRealPositionFromVirtualPosition(firstByte), nopInstructions); // Get the data-laden instructions var teamInstructions = TeamData.GetInstructions(); var personnelInstructions = PersonnelData.GetInstructions(); var driverInstructions = DriverData.GetInstructions(); // Write instructions var writePosition = InstructionHelper.CalculateRealPositionFromVirtualPosition(firstByte); executableConnection.WriteByteArray(writePosition, teamInstructions); writePosition += teamInstructions.Length; executableConnection.WriteByteArray(writePosition, personnelInstructions); writePosition += personnelInstructions.Length; executableConnection.WriteByteArray(writePosition, driverInstructions); // Apply track changes firstByte = 0x005031C6; lastByte = 0x00503EE5; // Create byte array of NOPs var nopTrackInstructions = new byte[lastByte + 1 - firstByte]; for (var i = 0; i < nopTrackInstructions.Length; i++) { nopTrackInstructions[i] = 0x90; } // Write byte array of NOPs executableConnection.WriteByteArray(InstructionHelper.CalculateRealPositionFromVirtualPosition(firstByte), nopTrackInstructions); // Get the data-laden instructions var trackInstructions = TrackData.GetInstructions(); writePosition = InstructionHelper.CalculateRealPositionFromVirtualPosition(firstByte); executableConnection.WriteByteArray(writePosition, trackInstructions); } }
/// <summary> /// Applys the bypass on all detected references to jump functions. /// This method is only intended for use with gpw.exe v1.01b. /// Do not invoke this method more than once on the same file. /// </summary> public void Apply() { // Stats counters var textBypassStatsCounter = 0; var text68BypassStatsCounter = 0; var textE8BypassStatsCounter = 0; var textC704BypassStatsCounter = 0; var textC705BypassStatsCounter = 0; var textC745BypassStatsCounter = 0; var textC784BypassStatsCounter = 0; var rdataBypassStatsCounter = 0; var dataBypassStatsCounter = 0; var totalBypassesStatsCounter = 0; // Define range where jump functions are located const int jumpFunctionFirstReference = 0x00401000; // 0x00000400 const int jumpFunctionLastReference = 0x0040452A; // 0x0000392A const int jumpFunctionReferenceStep = 0x00000005; // Bytes // Define range of .text section const int textSectionFirstByte = 0x00407A60; const int textSectionLastByte = 0x00689FFF; // Define range of .rdata section const int rdataSectionFirstByte = 0x015ED000; const int rdataSectionLastByte = 0x015EF7CF; // Define range of .data section const int dataSectionFirstByte = 0x015F0000; const int dataSectionLastByte = 0x0160CBFF; // Create collection to hold details of each jump function var jumpFunctions = new Collection <JumpFunction>(); // Populate collection with the positions of the jump functions for (var i = jumpFunctionFirstReference; i <= jumpFunctionLastReference; i += jumpFunctionReferenceStep) { jumpFunctions.Add(new JumpFunction() { Position = i }); } // Collection of changes to make after read var jumpBypasses = new Collection <JumpFunction>(); // Open file and read using (var executableConnection = new ExecutableConnection(_executableFilePath)) { // Populate collection with the referenced function inside each jump function foreach (var item in jumpFunctions) { var instructions = new byte[4]; instructions = executableConnection.ReadByteArray(InstructionHelper.CalculateRealPositionFromVirtualPosition(item.Position) + 1, instructions.Length); item.Function = item.Position + 5 + BitConverter.ToInt32(instructions, 0); } // Analyse .text section for opcodes with references to jump functions for (var i = textSectionFirstByte; i <= textSectionLastByte - 4; i++) { // Read two bytes var opcodeCheck = executableConnection.ReadByteArray(InstructionHelper.CalculateRealPositionFromVirtualPosition(i), 2); // Check for matching opcodes if (opcodeCheck[0] == 0x68) { // Read last four bytes of five byte signature var referenceCheckBytes = executableConnection.ReadByteArray(InstructionHelper.CalculateRealPositionFromVirtualPosition(i + 1), 4); var referenceCheckValue = BitConverter.ToInt32(referenceCheckBytes, 0); // Do bytes match a reference to a jump function in the collection foreach (var item in jumpFunctions) { if (referenceCheckValue == item.Position) { // Add bypass to collection jumpBypasses.Add(new JumpFunction() { Position = i + 1, Function = item.Function }); textBypassStatsCounter++; text68BypassStatsCounter++; } } } else if (opcodeCheck[0] == 0xE8) { // Read last four bytes of five byte signature var referenceCheckBytes = executableConnection.ReadByteArray(InstructionHelper.CalculateRealPositionFromVirtualPosition(i + 1), 4); var referenceCheckValue = i + 5 + BitConverter.ToInt32(referenceCheckBytes, 0); // Do bytes match a reference to a jump function in the collection foreach (var item in jumpFunctions) { if (referenceCheckValue == item.Position) { // Calc relative function position for opcode xE8 var functionOffset = BitConverter.GetBytes(item.Function - (i + 5)); // Add bypass to collection jumpBypasses.Add(new JumpFunction() { Position = i + 1, Function = BitConverter.ToInt32(functionOffset, 0) }); textBypassStatsCounter++; textE8BypassStatsCounter++; } } } else if ((opcodeCheck[0] == 0xC7) && (opcodeCheck[1] == 0x04)) { // Read last four bytes of eleven byte signature var referenceCheckBytes = executableConnection.ReadByteArray(InstructionHelper.CalculateRealPositionFromVirtualPosition(i + 7), 4); var referenceCheckValue = BitConverter.ToInt32(referenceCheckBytes, 0); // Do bytes match a reference to a jump function in the collection foreach (var item in jumpFunctions) { if (referenceCheckValue == item.Position) { // Add bypass to collection jumpBypasses.Add(new JumpFunction() { Position = i + 7, Function = item.Function }); textBypassStatsCounter++; textC704BypassStatsCounter++; } } } else if ((opcodeCheck[0] == 0xC7) && (opcodeCheck[1] == 0x05)) { // Read last four bytes of ten byte signature var referenceCheckBytes = executableConnection.ReadByteArray(InstructionHelper.CalculateRealPositionFromVirtualPosition(i + 6), 4); var referenceCheckValue = BitConverter.ToInt32(referenceCheckBytes, 0); // Do bytes match a reference to a jump function in the collection foreach (var item in jumpFunctions) { if (referenceCheckValue == item.Position) { // Add bypass to collection jumpBypasses.Add(new JumpFunction() { Position = i + 6, Function = item.Function }); textBypassStatsCounter++; textC705BypassStatsCounter++; } } } else if ((opcodeCheck[0] == 0xC7) && (opcodeCheck[1] == 0x45)) { // Read last four bytes of seven byte signature var referenceCheckBytes = executableConnection.ReadByteArray(InstructionHelper.CalculateRealPositionFromVirtualPosition(i + 3), 4); var referenceCheckValue = BitConverter.ToInt32(referenceCheckBytes, 0); // Do bytes match a reference to a jump function in the collection foreach (var item in jumpFunctions) { if (referenceCheckValue == item.Position) { // Add bypass to collection jumpBypasses.Add(new JumpFunction() { Position = i + 3, Function = item.Function }); textBypassStatsCounter++; textC745BypassStatsCounter++; } } } else if ((opcodeCheck[0] == 0xC7) && (opcodeCheck[1] == 0x84)) { // Read last four bytes of eleven byte signature var referenceCheckBytes = executableConnection.ReadByteArray(InstructionHelper.CalculateRealPositionFromVirtualPosition(i + 7), 4); var referenceCheckValue = BitConverter.ToInt32(referenceCheckBytes, 0); // Do bytes match a reference to a jump function in the collection foreach (var item in jumpFunctions) { if (referenceCheckValue == item.Position) { // Add bypass to collection jumpBypasses.Add(new JumpFunction() { Position = i + 7, Function = item.Function }); textBypassStatsCounter++; textC784BypassStatsCounter++; } } } } // Analyse .rdata section for references to jump functions for (var i = rdataSectionFirstByte; i <= rdataSectionLastByte - 3; i++) { // Read four bytes var referenceCheckBytes = executableConnection.ReadByteArray(InstructionHelper.CalculateRealPositionFromVirtualPosition(i), 4); var referenceCheckValue = BitConverter.ToInt32(referenceCheckBytes, 0); // Do bytes match a reference to a jump function in the collection foreach (var item in jumpFunctions) { if (referenceCheckValue == item.Position) { // Add bypass to collection jumpBypasses.Add(new JumpFunction() { Position = i, Function = item.Function }); rdataBypassStatsCounter++; } } } // Analyse .data section for references to jump functions for (var i = dataSectionFirstByte; i <= dataSectionLastByte - 3; i++) { // Read four bytes var referenceCheckBytes = executableConnection.ReadByteArray(InstructionHelper.CalculateRealPositionFromVirtualPosition(i), 4); var referenceCheckValue = BitConverter.ToInt32(referenceCheckBytes, 0); // Do bytes match a reference to a jump function in the collection foreach (var item in jumpFunctions) { if (referenceCheckValue == item.Position) { // Add bypass to collection jumpBypasses.Add(new JumpFunction() { Position = i, Function = item.Function }); dataBypassStatsCounter++; } } } } // Open file and write using (var executableConnection = new ExecutableConnection(_executableFilePath)) { // Apply bypasses foreach (var item in jumpBypasses) { // Write four bytes executableConnection.WriteByteArray(InstructionHelper.CalculateRealPositionFromVirtualPosition(item.Position), BitConverter.GetBytes(item.Function)); totalBypassesStatsCounter++; } // Remove redundant jump functions var clearByteArray = new byte[0x352F]; for (var i = 0; i < clearByteArray.Length; i++) { clearByteArray[i] = 0xCC; // Fill array with int3 opcode } executableConnection.WriteByteArray(InstructionHelper.CalculateRealPositionFromVirtualPosition(jumpFunctionFirstReference), clearByteArray); } Debug.WriteLine(" .text section bypass count: " + textBypassStatsCounter); Debug.WriteLine(" .text opCode 68 count: " + text68BypassStatsCounter); Debug.WriteLine(" .text opCode E8 count: " + textE8BypassStatsCounter); Debug.WriteLine(" .text opCode C7 04 count: " + textC704BypassStatsCounter); Debug.WriteLine(" .text opCode C7 05 count: " + textC705BypassStatsCounter); Debug.WriteLine(" .text opCode C7 45 count: " + textC745BypassStatsCounter); Debug.WriteLine(" .text opCode C7 84 count: " + textC784BypassStatsCounter); Debug.WriteLine(".rdata section bypass count: " + rdataBypassStatsCounter); Debug.WriteLine(" .data section bypass count: " + dataBypassStatsCounter); Debug.WriteLine("----------------------------"); Debug.WriteLine(" Total bypasses: " + totalBypassesStatsCounter); }
/// <summary> /// Applys the new switch idiom to all predefined target addresses within. /// This method is only intended for use with gpw.exe v1.01b. /// Do not invoke this method more than once on the same file. /// </summary> public void Apply() { // New instruction template, 0x00 will be overwritten by jump/indirect table locations var newInstructions = new byte[] { 0x33, 0xD2, 0x8A, 0x90, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x24, 0x95, 0x00, 0x00, 0x00, 0x00, 0x90, 0x90, 0x90 }; // File location of each switch idiom to modify var switchIdiomLocation = new long[] { 0x00021A9C, 0x0002EFE2, 0x00034FC5, 0x000387AE, 0x0003AD71, 0x00044C99, 0x0004EFD4, 0x0004FEDF, 0x0005F0EB, 0x00078996, 0x0008E2F7, 0x0008E850, 0x0008EE9E, 0x00093B02, 0x0009507E, 0x0009ED62, 0x000A5F9E, 0x001130A4, 0x001131B2, 0x00116871, 0x001170FA, 0x00117198, 0x001180EF, 0x0011C88A, 0x00120DB0, 0x00120E1E, 0x0012DE99, 0x00130D87, 0x00138A8E, 0x00139B64, 0x0013F6D1, 0x0013FE4B, 0x0013FF79, 0x001499BF, 0x00149A59, 0x0014A727, 0x0014A7F6, 0x0014B09F, 0x0014B5EF, 0x0014BC5D, 0x0014BCEE, 0x00154C42, 0x00154F47, 0x00155176, 0x0015533B, 0x001556C7, 0x0015BC0B, 0x0015DC59, 0x0015DE57, 0x0015DF24, 0x0015DFA2, 0x0015E1C0, 0x001675AA, 0x0017344E, 0x0018BF05, 0x0019012B, 0x001A1AEE, 0x001A1D23, 0x001A1DCC, 0x001A2F62, 0x001AE631, 0x001B4CAE, 0x001B50EC, 0x001D4F47, 0x001DCC1B, 0x001DE2A5, 0x001E30E6, 0x001F4FE5, 0x001F51EF, 0x001F74D8, 0x00203BFE, 0x0021862C, 0x00218788, 0x00218977, 0x0021BFB7, 0x0021C26A, 0x0025FF17, 0x0026028F, 0x0026082B, 0x002622D7, 0x002638F9, 0x002739E7, 0x00276F01, 0x00276FE8, 0x00278CB2, 0x00278D53, 0x00278FDD, 0x002790F0 }; // For every switch idiom (represented by the target address) for (var i = 0; i < switchIdiomLocation.Length; i++) { var instructions = new byte[18]; // Get current instructions at target address using (var executableConnection = new ExecutableConnection(_executableFilePath)) { instructions = executableConnection.ReadByteArray(switchIdiomLocation[i], instructions.Length); } // Extract table location values var indirectTableLocation = new byte[4]; var jumpTableLocation = new byte[4]; // Indirect table location is represented in little endian byte order for (var j = 0; j < indirectTableLocation.Length; j++) { // Read four bytes indirectTableLocation[j] = instructions[j + 2]; } // Jump table location is represented in little endian byte order for (var j = 0; j < jumpTableLocation.Length; j++) { // Read four bytes jumpTableLocation[j] = instructions[j + 14]; } // Create copy of new instructions by overwriting current instructions Array.Copy(newInstructions, instructions, instructions.Length); // Modify with new instructions for (var j = 4; j < 8; j++) { // Write four bytes instructions[j] = indirectTableLocation[j - 4]; } for (var j = 11; j < 15; j++) { // Write four bytes instructions[j] = jumpTableLocation[j - 11]; } using (var executableConnection = new ExecutableConnection(_executableFilePath)) { // Write new instructions to target address executableConnection.WriteByteArray(switchIdiomLocation[i], instructions); } } }
/// <summary> /// Applys the redirect on all calls to GlobalUnlock where the return value is tested. /// This method is only intended for use with gpw.exe v1.01b. /// This method requires the Jump Bypass Patcher to be applied beforehand. /// Do not invoke this method more than once on the same file. /// </summary> public void Apply() { // New function to call GlobalUnlock on behalf of original calls var newGlobalUnlockInstructions = new byte[] { 0x55, // push ebp 0x8B, 0xEC, // mov ebp, esp 0x53, // push ebx 0x56, // push esi 0x57, // push edi // loc_call 0x8B, 0x45, 0x08, // mov eax, [ebp + arg_0] 0x50, // push eax 0xFF, 0x15, 0xAC, 0xD5, 0x60, 0x01, // call GlobalUnlock // if result greater than 1, go to loc_call 0x83, 0xF8, 0x01, // cmp eax, 1 0x0F, 0x8F, 0xED, 0xFF, 0xFF, 0xFF, // jg loc_call // if result is 0, jump to loc_continue 0x85, 0xC0, // test eax, eax 0x0F, 0x84, 0x0C, 0x00, 0x00, 0x00, // jz loc_continue // therefore result must be 1 // call once more for safety (and ignore if still returns 1) 0x8B, 0x45, 0x08, // mov eax, [ebp + arg_0] 0x50, // push eax 0xFF, 0x15, 0xAC, 0xD5, 0x60, 0x01, // call GlobalUnlock // ensure eax is set to 0 0x33, 0xC0, // xor eax, eax // loc_contine 0x5F, // pop edi 0x5E, // pop esi 0x5B, // pop ebx 0xC9, // leave 0xC3 // retn }; // File location to insert new function var newGlobalUnlockLocation = 0x00474C17; // New redirect instruction template, 0x00 will be overwritten by relative function location var newRedirectInstructions = new byte[] { 0x90, 0xE8, 0x00, 0x00, 0x00, 0x00 }; // File location of each global unlock call to modify var globalUnlockLocation = new long[] { 0x00413322, 0x0041362A, 0x004138A1, 0x00413AB3, 0x00413FB3, 0x00414031, 0x00414219, 0x0041424B, 0x004142CC, 0x004144B0, 0x0041459F, 0x0041462E, 0x004146A0, 0x0041487B, 0x00414A5E, 0x00414A9A, 0x00414AE2, 0x0043B9A5, 0x005C5525 }; // Open file and write using (var executableConnection = new ExecutableConnection(_executableFilePath)) { // Apply new function to call GlobalUnlock on behalf of original calls executableConnection.WriteByteArray(InstructionHelper.CalculateRealPositionFromVirtualPosition(newGlobalUnlockLocation), newGlobalUnlockInstructions); // Apply redirects foreach (var item in globalUnlockLocation) { var redirectInstructions = new byte[6]; var relativeFunctionLocation = BitConverter.GetBytes(newGlobalUnlockLocation - (item + 6)); // Create copy of new instructions by overwriting current instructions Array.Copy(newRedirectInstructions, redirectInstructions, redirectInstructions.Length); // Modify with new instructions for (var i = 2; i < 6; i++) { // Write first four bytes of eight into the last four bytes of six redirectInstructions[i] = relativeFunctionLocation[i - 2]; } // Write six bytes executableConnection.WriteByteArray(InstructionHelper.CalculateRealPositionFromVirtualPosition(item), redirectInstructions); } } }
private static void WriteNewBytes(ExecutableConnection executableConnection, int address, byte[] instructions) { var realAddress = InstructionHelper.CalculateRealPositionFromVirtualPosition(address); executableConnection.WriteByteArray(realAddress, instructions); }