|
| 1 | +#include "subtitans.h" |
| 2 | +#include "missionskippatch.h" |
| 3 | + |
| 4 | +/* |
| 5 | + Issue: Some missions don't always trigger the mission end event |
| 6 | + Solution: Add a cheat for mission skipping |
| 7 | +*/ |
| 8 | + |
| 9 | +namespace MissionSkip { |
| 10 | + // Runtime variables |
| 11 | + static char* CurrentMapNamePointer = 0; |
| 12 | + |
| 13 | + // Function specific variables |
| 14 | + const char MissionSkipCheatString[] = { 'O', 'R', 'B', 'I', 'T', 'O', 'N', 0x00 }; |
| 15 | + const char MissionValidationString[] = { 'M', 'I', 'S', 'S', 'I', 'O', 'N', 'S', '\\', 'M', 'I', 'S', 'S', 0x00 }; |
| 16 | + |
| 17 | + // For Detours |
| 18 | + static char OrbitonMissionPathPattern[] = { '%', 's', '%', 's', '%', 's', '0', '0', '0', 0x00 }; |
| 19 | + static char DefaultMissionPathPattern[] = { '%', 's', '%', 's', '%', 's', '%', 'd', '0', '1', 0x00 }; |
| 20 | + static bool OrbitonActivated = false; |
| 21 | + |
| 22 | + void ActivateOrbiton() |
| 23 | + { |
| 24 | + GetLogger()->Informational("Orbiton is activated for %s\n", CurrentMapNamePointer); |
| 25 | + |
| 26 | + // Copy map name and convert to uppercase |
| 27 | + char cheatActivatedMapName[32] = { 0x00, }; |
| 28 | + strncpy_s(cheatActivatedMapName, sizeof(cheatActivatedMapName), CurrentMapNamePointer, strlen(CurrentMapNamePointer)); |
| 29 | + if (_strupr_s(cheatActivatedMapName, sizeof(cheatActivatedMapName)) != 0) |
| 30 | + { |
| 31 | + GetLogger()->Error("Failed to convert map name to uppercase\n"); |
| 32 | + return; |
| 33 | + } |
| 34 | + |
| 35 | + // Check if the current map is a mission |
| 36 | + for (unsigned int i = 0; i < sizeof(MissionValidationString) - 1; ++i) |
| 37 | + { |
| 38 | + if (MissionValidationString[i] != cheatActivatedMapName[i]) |
| 39 | + { |
| 40 | + GetLogger()->Warning("You're only allowed to skip mission maps!\n"); |
| 41 | + GetLogger()->Informational("Orbiton has been disabled\n"); |
| 42 | + return; |
| 43 | + } |
| 44 | + } |
| 45 | + |
| 46 | + char missionNumberAsString[4] = { 0x00, }; |
| 47 | + memcpy_s(missionNumberAsString, sizeof(missionNumberAsString), cheatActivatedMapName + strlen(MissionValidationString), 3); |
| 48 | + int missionNumber = atoi(missionNumberAsString + 1); // Skip team |
| 49 | + if (missionNumber < 10) |
| 50 | + missionNumber++; |
| 51 | + |
| 52 | + sprintf_s(missionNumberAsString + 1, sizeof(missionNumberAsString), "%02d", missionNumber); |
| 53 | + memcpy_s(cheatActivatedMapName + strlen(MissionValidationString), 3, missionNumberAsString, 3); |
| 54 | + |
| 55 | + memcpy_s(OrbitonMissionPathPattern + sizeof(OrbitonMissionPathPattern) - 4, 3, missionNumberAsString, 3); |
| 56 | + |
| 57 | + GetLogger()->Informational("Next mission will be forced to: %s\nExit to main menu and start a new campaign.\n", cheatActivatedMapName); |
| 58 | + |
| 59 | + OrbitonActivated = true; |
| 60 | + } |
| 61 | + |
| 62 | + namespace AddCheatCode { |
| 63 | + // Detour variables |
| 64 | + constexpr unsigned long DetourSize = 7; |
| 65 | + static unsigned long JmpFromAddress = 0; // Inside of TECH cheat |
| 66 | + static unsigned long JmpBackAddress = 0; // Execute TECH cheat |
| 67 | + static unsigned long JmpBackAlternativeAddress = 0; // Jump to begin of FOW cheat |
| 68 | + static unsigned long CheatValidationFunctionAddress = 0; |
| 69 | + |
| 70 | + __declspec(naked) void Implementation() |
| 71 | + { |
| 72 | + // Restore code |
| 73 | + __asm add esp, 0x0C; |
| 74 | + __asm test eax, eax; |
| 75 | + __asm jnz missionSkipCheat; |
| 76 | + __asm jmp [JmpBackAddress]; |
| 77 | + |
| 78 | + missionSkipCheat: |
| 79 | + // New cheat |
| 80 | + __asm push 0x07; |
| 81 | + __asm push offset [MissionSkipCheatString]; |
| 82 | + __asm push ebx; |
| 83 | + __asm call [CheatValidationFunctionAddress]; |
| 84 | + __asm test eax, eax; |
| 85 | + __asm jnz missionSkipCheatEpilogue; |
| 86 | + |
| 87 | + __asm pushfd; |
| 88 | + __asm pushad; |
| 89 | + |
| 90 | + ActivateOrbiton(); |
| 91 | + |
| 92 | + __asm popad; |
| 93 | + __asm popfd; |
| 94 | + |
| 95 | + missionSkipCheatEpilogue: |
| 96 | + __asm jmp [JmpBackAlternativeAddress]; |
| 97 | + } |
| 98 | + } |
| 99 | + |
| 100 | + namespace FullPathOverride |
| 101 | + { |
| 102 | + // Detour variables |
| 103 | + constexpr unsigned long DetourSize = 5; |
| 104 | + static unsigned long JmpFromAddress = 0; |
| 105 | + static unsigned long JmpBackAddress = 0; |
| 106 | + |
| 107 | + __declspec(naked) void Implementation() |
| 108 | + { |
| 109 | + __asm cmp [OrbitonActivated], 0; |
| 110 | + __asm je fullPathOrbitonDeactivated; |
| 111 | + |
| 112 | + __asm push offset [OrbitonMissionPathPattern]; |
| 113 | + __asm jmp [JmpBackAddress]; |
| 114 | + |
| 115 | + fullPathOrbitonDeactivated: |
| 116 | + __asm push offset [DefaultMissionPathPattern]; |
| 117 | + __asm jmp [JmpBackAddress]; |
| 118 | + } |
| 119 | + } |
| 120 | + |
| 121 | + namespace RelativePathOverride |
| 122 | + { |
| 123 | + // Detour variables |
| 124 | + constexpr unsigned long DetourSize = 5; |
| 125 | + static unsigned long JmpFromAddress = 0; |
| 126 | + static unsigned long JmpBackAddress = 0; |
| 127 | + |
| 128 | + __declspec(naked) void Implementation() |
| 129 | + { |
| 130 | + __asm cmp [OrbitonActivated], 0; |
| 131 | + __asm je fullPathOrbitonDeactivated; |
| 132 | + |
| 133 | + __asm push offset [OrbitonMissionPathPattern + 0x02]; |
| 134 | + __asm mov [OrbitonActivated], 0; |
| 135 | + __asm jmp [JmpBackAddress]; |
| 136 | + |
| 137 | + fullPathOrbitonDeactivated: |
| 138 | + __asm push offset [DefaultMissionPathPattern + 0x02]; |
| 139 | + __asm jmp [JmpBackAddress]; |
| 140 | + } |
| 141 | + } |
| 142 | +} |
| 143 | + |
| 144 | +MissionSkipPatch::MissionSkipPatch() |
| 145 | +{ |
| 146 | + GetLogger()->Informational("Constructing %s\n", __func__); |
| 147 | + |
| 148 | + AddCheatCodeDetourAddress = 0; |
| 149 | + AddCheatCodeAlternativeReturnAddress = 0; |
| 150 | + FullMapPathDetourAddress = 0; |
| 151 | + RelativeMapPathDetourAddress = 0; |
| 152 | + |
| 153 | + CurrentMapNameVariable = 0; |
| 154 | + CheatValidationFunctionAddress = 0; |
| 155 | +} |
| 156 | + |
| 157 | +MissionSkipPatch::~MissionSkipPatch() |
| 158 | +{ |
| 159 | + GetLogger()->Informational("Destructing %s\n", __func__); |
| 160 | +} |
| 161 | + |
| 162 | +bool MissionSkipPatch::Validate() |
| 163 | +{ |
| 164 | + return AddCheatCodeDetourAddress && |
| 165 | + AddCheatCodeAlternativeReturnAddress && |
| 166 | + FullMapPathDetourAddress && |
| 167 | + RelativeMapPathDetourAddress && |
| 168 | + CurrentMapNameVariable && |
| 169 | + CheatValidationFunctionAddress; |
| 170 | +} |
| 171 | + |
| 172 | +bool MissionSkipPatch::Apply() |
| 173 | +{ |
| 174 | + GetLogger()->Informational("%s\n", __FUNCTION__); |
| 175 | + |
| 176 | + MissionSkip::CurrentMapNamePointer = (char*)CurrentMapNameVariable; |
| 177 | + |
| 178 | + MissionSkip::AddCheatCode::JmpFromAddress = AddCheatCodeDetourAddress; |
| 179 | + MissionSkip::AddCheatCode::JmpBackAddress = MissionSkip::AddCheatCode::JmpFromAddress + MissionSkip::AddCheatCode::DetourSize; |
| 180 | + MissionSkip::AddCheatCode::JmpBackAlternativeAddress = AddCheatCodeAlternativeReturnAddress; |
| 181 | + MissionSkip::AddCheatCode::CheatValidationFunctionAddress = CheatValidationFunctionAddress; |
| 182 | + if (!Detour::Create(MissionSkip::AddCheatCode::JmpFromAddress, MissionSkip::AddCheatCode::DetourSize, (unsigned long)MissionSkip::AddCheatCode::Implementation)) |
| 183 | + return false; |
| 184 | + |
| 185 | + MissionSkip::FullPathOverride::JmpFromAddress = FullMapPathDetourAddress; |
| 186 | + MissionSkip::FullPathOverride::JmpBackAddress = MissionSkip::FullPathOverride::JmpFromAddress + MissionSkip::FullPathOverride::DetourSize; |
| 187 | + if (!Detour::Create(MissionSkip::FullPathOverride::JmpFromAddress, MissionSkip::FullPathOverride::DetourSize, (unsigned long)MissionSkip::FullPathOverride::Implementation)) |
| 188 | + return false; |
| 189 | + |
| 190 | + MissionSkip::RelativePathOverride::JmpFromAddress = RelativeMapPathDetourAddress; |
| 191 | + MissionSkip::RelativePathOverride::JmpBackAddress = MissionSkip::RelativePathOverride::JmpFromAddress + MissionSkip::RelativePathOverride::DetourSize; |
| 192 | + if (!Detour::Create(MissionSkip::RelativePathOverride::JmpFromAddress, MissionSkip::RelativePathOverride::DetourSize, (unsigned long)MissionSkip::RelativePathOverride::Implementation)) |
| 193 | + return false; |
| 194 | + |
| 195 | + return true; |
| 196 | +} |
| 197 | + |
| 198 | +const wchar_t* MissionSkipPatch::ErrorMessage() |
| 199 | +{ |
| 200 | + return L"Failed to apply the mission skip patch"; |
| 201 | +} |
0 commit comments