Skip to content

Commit 8718189

Browse files
Add mission skip cheat code (#8)
* Added a new cheat code 'orbiton' to skip missions * Improved readability of the class MovieHeapCorruptionPatch
1 parent 2f33531 commit 8718189

11 files changed

Lines changed: 361 additions & 97 deletions

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ Unofficial Submarine Titans patch
77

88
**Features**
99
* Support for native resolution ingame (tested up to 3840x2160)
10+
* Improved scrolling (Requires v1.1)
11+
* Added mission skip cheat (Requires v1.1; workaround for progression bugs)
12+
* Performance boost (Steam; not required for GOG)
13+
* Fixes various internal errors (Steam; not required for GOG)
1014
* Support for high DPI display resolutions (Steam; not required for GOG)
1115
* Fixes alt-tab crashes (Steam; not required for GOG)
12-
* Fixes various internal errors (Steam; not required for GOG)
13-
* Performance boost (Steam; not required for GOG)
14-
* Improved scrolling (Requires v1.1)
1516
* Windows 7 color fix (Steam; not required for GOG) - deprecated
1617

1718
**Instructions**
@@ -20,14 +21,19 @@ Unofficial Submarine Titans patch
2021
3. Run the game (through ST.exe or Steam).
2122

2223
**Q&A** \
24+
Q: How do I use the mission skip cheat? \
25+
A: Load the mission you want to skip and write 'orbiton' without quotes in the chatbox. \
26+
Exit to the menu and select a random campaign to start the next mission. \
27+
\
2328
Q: Help, I'm getting MSVCP140.dll errors! \
2429
A: Install vc_redist.x86.exe ( https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads ). \
2530
\
2631
Q: How to update Submarine Titans to version 1.1? \
2732
A: https://steamcommunity.com/sharedfiles/filedetails/?id=2129291420 \
2833
\
2934
Q: Why is the game running so slow? (Steam/Retail only) \
30-
A: Submarine Titan uses DirectDraw, a deprecated piece of technology from a bygone era. A patch to circumvent DirectDraw is in development.
35+
A: Submarine Titan uses DirectDraw, a deprecated piece of technology from a bygone era. \
36+
There are DirectDraw replacements/wrappers available that might improve performance (like DXWrapper, DDrawCompat, DXGL, dgVoodoo 2 and others). \
3137

3238
**Known bugs**
3339
* 1366*768 does not function correctly, reverts to 1280x768 ingame.

subtitans/gogpatcher.cpp

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "movieheapcorruptionpatch.h"
55
#include "scrollpatch.h"
66
#include "gogpatcher.h"
7+
#include "missionskippatch.h"
78

89
GOGPatcher::GOGPatcher()
910
{
@@ -48,38 +49,47 @@ void GOGPatcher::Configure()
4849

4950
auto movieHeapCorruptionPatch = new MovieHeapCorruptionPatch();
5051
movieHeapCorruptionPatch->AllocatedMemoryOffset = 0x00856900;
51-
movieHeapCorruptionPatch->StructurePatches[0] = 0x006D5923 + 3;
52-
movieHeapCorruptionPatch->StructurePatches[1] = 0x006D59B0 + 1;
53-
movieHeapCorruptionPatch->StructurePatches[2] = 0x006D5A0C + 1;
54-
movieHeapCorruptionPatch->StructurePatches[3] = 0x006D5A11 + 1;
55-
movieHeapCorruptionPatch->StructurePatches[4] = 0x006D5A21 + 2;
56-
movieHeapCorruptionPatch->StructurePatches[5] = 0x006D5A2D + 2;
57-
movieHeapCorruptionPatch->StructurePatches[6] = 0x006D5A33 + 1;
58-
movieHeapCorruptionPatch->StructurePatches[7] = 0x006D5A38 + 1;
59-
movieHeapCorruptionPatch->StructurePatches[8] = 0x006D5A43 + 2;
60-
movieHeapCorruptionPatch->StructurePatches[9] = 0x006D5A4F + 2;
61-
movieHeapCorruptionPatch->StructurePatches[10] = 0x006D5A55 + 2;
62-
movieHeapCorruptionPatch->StructurePatches[11] = 0x006D5A65 + 2;
63-
movieHeapCorruptionPatch->StructurePatches[12] = 0x006D5A73 + 2;
64-
movieHeapCorruptionPatch->StructurePatches[13] = 0x006D5A79 + 3;
65-
movieHeapCorruptionPatch->StructurePatches[14] = 0x006D5A84 + 1;
66-
movieHeapCorruptionPatch->StructurePatches[15] = 0x006D5A89 + 3;
67-
movieHeapCorruptionPatch->StructurePatches[16] = 0x006D5A90 + 1;
68-
movieHeapCorruptionPatch->StructurePatches[17] = 0x006D5AB4 + 1;
69-
movieHeapCorruptionPatch->StructurePatches[18] = 0x006D5ABE + 2;
70-
movieHeapCorruptionPatch->StructurePatches[19] = 0x006D5AD8 + 1;
71-
movieHeapCorruptionPatch->StructurePatches[20] = 0x006D5AE2 + 1;
72-
movieHeapCorruptionPatch->StructurePatches[21] = 0x006D5AE7 + 2;
73-
movieHeapCorruptionPatch->StructurePatches[22] = 0x006D5B1D + 1;
74-
movieHeapCorruptionPatch->StructurePatches[23] = 0x006D5B2B + 2;
75-
movieHeapCorruptionPatch->StructurePatches[24] = 0x006D5B37 + 2;
76-
movieHeapCorruptionPatch->StructurePatches[25] = 0x006D5B41 + 2;
77-
movieHeapCorruptionPatch->StructurePatches[26] = 0x006D5BDE + 1;
52+
movieHeapCorruptionPatch->StructurePointer = 0x006D5923 + 3;
53+
movieHeapCorruptionPatch->StructureOffsets[0] = 0x006D59B0 + 1;
54+
movieHeapCorruptionPatch->StructureOffsets[1] = 0x006D5A0C + 1;
55+
movieHeapCorruptionPatch->StructureOffsets[2] = 0x006D5A11 + 1;
56+
movieHeapCorruptionPatch->StructureOffsets[3] = 0x006D5A21 + 2;
57+
movieHeapCorruptionPatch->StructureOffsets[4] = 0x006D5A2D + 2;
58+
movieHeapCorruptionPatch->StructureOffsets[5] = 0x006D5A33 + 1;
59+
movieHeapCorruptionPatch->StructureOffsets[6] = 0x006D5A38 + 1;
60+
movieHeapCorruptionPatch->StructureOffsets[7] = 0x006D5A43 + 2;
61+
movieHeapCorruptionPatch->StructureOffsets[8] = 0x006D5A4F + 2;
62+
movieHeapCorruptionPatch->StructureOffsets[9] = 0x006D5A55 + 2;
63+
movieHeapCorruptionPatch->StructureOffsets[10] = 0x006D5A65 + 2;
64+
movieHeapCorruptionPatch->StructureOffsets[11] = 0x006D5A73 + 2;
65+
movieHeapCorruptionPatch->StructureOffsets[12] = 0x006D5A79 + 3;
66+
movieHeapCorruptionPatch->StructureOffsets[13] = 0x006D5A84 + 1;
67+
movieHeapCorruptionPatch->StructureOffsets[14] = 0x006D5A89 + 3;
68+
movieHeapCorruptionPatch->StructureOffsets[15] = 0x006D5A90 + 1;
69+
movieHeapCorruptionPatch->StructureOffsets[16] = 0x006D5AB4 + 1;
70+
movieHeapCorruptionPatch->StructureOffsets[17] = 0x006D5ABE + 2;
71+
movieHeapCorruptionPatch->StructureOffsets[18] = 0x006D5AD8 + 1;
72+
movieHeapCorruptionPatch->StructureOffsets[19] = 0x006D5AE2 + 1;
73+
movieHeapCorruptionPatch->StructureOffsets[20] = 0x006D5AE7 + 2;
74+
movieHeapCorruptionPatch->StructureOffsets[21] = 0x006D5B1D + 1;
75+
movieHeapCorruptionPatch->StructureOffsets[22] = 0x006D5B2B + 2;
76+
movieHeapCorruptionPatch->StructureOffsets[23] = 0x006D5B37 + 2;
77+
movieHeapCorruptionPatch->StructureOffsets[24] = 0x006D5B41 + 2;
78+
movieHeapCorruptionPatch->StructureOffsets[25] = 0x006D5BDE + 1;
7879
movieHeapCorruptionPatch->DetourAddress = 0x006D599C;
7980
_patches.push_back(movieHeapCorruptionPatch);
8081

8182
auto scrollPatch = new ScrollPatch();
8283
scrollPatch->UpdateRateAddress = 0x004AB083 + 2;
8384
scrollPatch->DetourAddress = 0x004AB0EF;
8485
_patches.push_back(scrollPatch);
86+
87+
auto missionSkipPatch = new MissionSkipPatch();
88+
missionSkipPatch->AddCheatCodeDetourAddress = 0x00522B66;
89+
missionSkipPatch->AddCheatCodeAlternativeReturnAddress = 0x00522B85;
90+
missionSkipPatch->FullMapPathDetourAddress = 0x00593442;
91+
missionSkipPatch->RelativeMapPathDetourAddress = 0x00593476;
92+
missionSkipPatch->CurrentMapNameVariable = 0x0080EF1E;
93+
missionSkipPatch->CheatValidationFunctionAddress = 0x0072E6F0;
94+
_patches.push_back(missionSkipPatch);
8595
}

subtitans/missionskippatch.cpp

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
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+
}

subtitans/missionskippatch.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#pragma once
2+
#include "patch.h"
3+
4+
class MissionSkipPatch : public Patch {
5+
public:
6+
MissionSkipPatch();
7+
virtual ~MissionSkipPatch();
8+
9+
bool Validate() override;
10+
bool Apply() override;
11+
const wchar_t* ErrorMessage() override;
12+
13+
unsigned long AddCheatCodeDetourAddress;
14+
unsigned long AddCheatCodeAlternativeReturnAddress;
15+
unsigned long FullMapPathDetourAddress;
16+
unsigned long RelativeMapPathDetourAddress;
17+
18+
unsigned long CurrentMapNameVariable;
19+
unsigned long CheatValidationFunctionAddress;
20+
};

0 commit comments

Comments
 (0)