Skip to content

Commit 57034d1

Browse files
committed
ProtectLoginData: moves login data outside savegame files, and encrypts with CryptProtectData
older non-protected data can be protected by changing one of the settings in-game, an OnlineLoginData.bin should then be created
1 parent de5c6a9 commit 57034d1

File tree

6 files changed

+185
-5
lines changed

6 files changed

+185
-5
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ target_link_libraries(outrun2006tweaks PUBLIC
368368
SDL3-static
369369
Winmm.lib
370370
Setupapi.lib
371+
Crypt32.lib
371372
)
372373

373374
target_link_options(outrun2006tweaks PUBLIC

OutRun2006Tweaks.ini

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,17 @@ RandomHighwayAnimSets = false
268268
# (Tweaks will try to port forward for you using UPnP, but you may need to forward ports 41455 / 41456 / 41457 in order to host games)
269269
DemonwareServerOverride = clarissa.port0.org
270270

271+
# Protects online login data by removing it from savegame files & encrypting against your Windows user account
272+
# Making sure that your login details won't be exposed if you share savegame files
273+
# (older non-protected data can be protected by changing one of the settings in-game, an OnlineLoginData.bin should then be created)
274+
#
275+
# NOTE: if you move your OR2006 install between different machines, the login data will likely fail to decrypt
276+
# in that case the tweak can be disabled here
277+
ProtectLoginData = true
278+
271279
[Overlay]
272-
# Enables OR2006Tweaks overlay
280+
# Enables the OutRun2006Tweaks overlay, accessible via F11 key
281+
# (more settings for Overlay are available in the overlay itself)
273282
Enabled = true
274283

275284
[Bugfixes]

cmake.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,11 @@ link-libraries = [
101101
"version.lib",
102102
"xinput9_1_0.lib",
103103
"Hid.lib",
104-
"libminiupnpc-static",
105-
"SDL3-static",
106-
"Winmm.lib",
107-
"Setupapi.lib"
104+
"libminiupnpc-static",
105+
"SDL3-static",
106+
"Winmm.lib",
107+
"Setupapi.lib",
108+
"Crypt32.lib"
108109
]
109110

110111
[target.outrun2006tweaks.properties]

src/dllmain.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ namespace Settings
131131
spdlog::info(" - AllowCharacterSelection: {}", AllowCharacterSelection);
132132
spdlog::info(" - RandomHighwayAnimSets: {}", RandomHighwayAnimSets);
133133
spdlog::info(" - DemonwareServerOverride: {}", DemonwareServerOverride);
134+
spdlog::info(" - ProtectLoginData: {}", ProtectLoginData);
134135

135136
spdlog::info(" - OverlayEnabled: {}", OverlayEnabled);
136137

@@ -246,6 +247,7 @@ namespace Settings
246247
AllowCharacterSelection = ini.Get("Misc", "AllowCharacterSelection", AllowCharacterSelection);
247248
RandomHighwayAnimSets = ini.Get("Misc", "RandomHighwayAnimSets", RandomHighwayAnimSets);
248249
DemonwareServerOverride = ini.Get("Misc", "DemonwareServerOverride", DemonwareServerOverride);
250+
ProtectLoginData = ini.Get("Misc", "ProtectLoginData", ProtectLoginData);
249251

250252
OverlayEnabled = ini.Get("Overlay", "Enabled", OverlayEnabled);
251253

src/hooks_misc.cpp

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,172 @@
88
#include <miniupnpc.h>
99
#include <upnpcommands.h>
1010
#include <WinSock2.h>
11+
#include <fstream>
12+
#include <wincrypt.h>
13+
14+
class ProtectLoginData : public Hook
15+
{
16+
// C2C saves plaintext online login details into SaveGame/Common.dat by default
17+
// It's not obvious this file contains login info though, so some might share it out freely
18+
// Now that online is restored those details could be extracted and reused
19+
//
20+
// Instead of keeping it inside SaveGame/Common.dat, we'll store it in a seperate file next to game EXE
21+
// and zero-out the data inside Common.dat before it gets saved to disk
22+
//
23+
// We'll also encrypt the data using CryptProtectData, which encrypts it against the users Windows account
24+
// So even if the file does get shared, it'd be difficult for others to be able to decrypt it
25+
const static inline std::string LoginDataFilename = "OnlineLoginData.dat";
26+
27+
static void EncryptDataToFile(const uint8_t* inputData, int dataLength, const std::filesystem::path& outputFilePath)
28+
{
29+
DATA_BLOB inputBlob = { static_cast<DWORD>(dataLength), const_cast<BYTE*>(inputData) };
30+
DATA_BLOB outputBlob;
31+
32+
if (!CryptProtectData(&inputBlob, L"OnlineLoginData", nullptr, nullptr, nullptr, 0, &outputBlob))
33+
throw std::runtime_error("CryptProtectData = false");
34+
else
35+
{
36+
std::ofstream outputFile(outputFilePath, std::ios::binary);
37+
if (outputFile.is_open())
38+
{
39+
outputFile.write((char*)outputBlob.pbData, outputBlob.cbData);
40+
outputFile.close();
41+
}
42+
LocalFree(outputBlob.pbData);
43+
}
44+
}
45+
46+
static bool DecryptDataFromFile(uint8_t* outputData, int dataLength, const std::filesystem::path& inputFilePath)
47+
{
48+
if (!std::filesystem::exists(inputFilePath))
49+
return false;
50+
51+
std::ifstream inputFile(inputFilePath, std::ios::binary | std::ios::ate);
52+
if (inputFile.is_open())
53+
{
54+
std::streamsize size = inputFile.tellg();
55+
inputFile.seekg(0, std::ios::beg);
56+
std::vector<char> encryptedData(size);
57+
if (inputFile.read(encryptedData.data(), size))
58+
{
59+
DATA_BLOB inputBlob = { static_cast<DWORD>(size), reinterpret_cast<BYTE*>(encryptedData.data()) };
60+
DATA_BLOB outputBlob;
61+
62+
if (!CryptUnprotectData(&inputBlob, nullptr, nullptr, nullptr, nullptr, 0, &outputBlob))
63+
throw std::runtime_error("CryptUnprotectData = false");
64+
else
65+
{
66+
std::memcpy(outputData, outputBlob.pbData, min(int(outputBlob.cbData), dataLength));
67+
LocalFree(outputBlob.pbData);
68+
return true;
69+
}
70+
}
71+
inputFile.close();
72+
}
73+
74+
return false;
75+
}
76+
77+
inline static SafetyHookInline Sumo_CommonDat_Read_hook = {};
78+
static int Sumo_CommonDat_Read_dest()
79+
{
80+
auto ret = Sumo_CommonDat_Read_hook.call<int>();
81+
82+
uint8_t* loginData = Module::exe_ptr<uint8_t>(0x3C205C);
83+
int loginDataLength = (0x10 * 8) + (8 * 8);
84+
85+
try
86+
{
87+
DecryptDataFromFile(loginData, loginDataLength, Module::ExePath.parent_path() / LoginDataFilename);
88+
}
89+
catch (const std::exception& e)
90+
{
91+
memset(loginData, 0, loginDataLength);
92+
spdlog::error("ProtectLoginData: Failed to decrypt login data from {}: {}", LoginDataFilename, e.what());
93+
}
94+
95+
return ret;
96+
}
97+
inline static SafetyHookInline Sumo_CommonDat_Write_hook = {};
98+
static int Sumo_CommonDat_Write_dest()
99+
{
100+
constexpr int loginDataLength = (0x10 * 8) + (8 * 8);
101+
uint8_t* loginData = Module::exe_ptr<uint8_t>(0x3C205C);
102+
103+
uint8_t tempLoginData[loginDataLength];
104+
memcpy(tempLoginData, loginData, loginDataLength);
105+
106+
try
107+
{
108+
EncryptDataToFile(loginData, loginDataLength, Module::ExePath.parent_path() / LoginDataFilename);
109+
}
110+
catch (const std::exception& e)
111+
{
112+
spdlog::error("ProtectLoginData: failed to encrypt login data to {} ({}), login details will be scrubbed from common.dat", e.what(), LoginDataFilename);
113+
}
114+
115+
SecureZeroMemory(loginData, loginDataLength);
116+
117+
auto ret = Sumo_CommonDat_Write_hook.call<int>();
118+
119+
memcpy(loginData, tempLoginData, loginDataLength);
120+
121+
return ret;
122+
}
123+
inline static SafetyHookInline Sumo_CommonDat_WriteRaw_hook = {};
124+
static int Sumo_CommonDat_WriteRaw_dest()
125+
{
126+
constexpr int loginDataLength = (0x10 * 8) + (8 * 8);
127+
uint8_t* loginData = Module::exe_ptr<uint8_t>(0x3C205C);
128+
129+
uint8_t tempLoginData[loginDataLength];
130+
memcpy(tempLoginData, loginData, loginDataLength);
131+
132+
try
133+
{
134+
EncryptDataToFile(loginData, loginDataLength, Module::ExePath.parent_path() / LoginDataFilename);
135+
}
136+
catch (const std::exception& e)
137+
{
138+
spdlog::error("ProtectLoginData: failed to encrypt login data to {} ({}), login details will be scrubbed from common.dat", e.what(), LoginDataFilename);
139+
}
140+
141+
SecureZeroMemory(loginData, loginDataLength);
142+
143+
auto ret = Sumo_CommonDat_WriteRaw_hook.call<int>();
144+
145+
memcpy(loginData, tempLoginData, loginDataLength);
146+
147+
return ret;
148+
}
149+
150+
public:
151+
std::string_view description() override
152+
{
153+
return "ProtectLoginData";
154+
}
155+
156+
bool validate() override
157+
{
158+
return Settings::ProtectLoginData;
159+
}
160+
161+
bool apply() override
162+
{
163+
constexpr int Sumo_CommonDat_Read_Addr = 0x16380;
164+
constexpr int Sumo_CommonDat_Write_Addr = 0x16420;
165+
constexpr int Sumo_CommonDat_WriteRaw_Addr = 0x164D0;
166+
167+
Sumo_CommonDat_Read_hook = safetyhook::create_inline(Module::exe_ptr(Sumo_CommonDat_Read_Addr), Sumo_CommonDat_Read_dest);
168+
Sumo_CommonDat_Write_hook = safetyhook::create_inline(Module::exe_ptr(Sumo_CommonDat_Write_Addr), Sumo_CommonDat_Write_dest);
169+
Sumo_CommonDat_WriteRaw_hook = safetyhook::create_inline(Module::exe_ptr(Sumo_CommonDat_WriteRaw_Addr), Sumo_CommonDat_WriteRaw_dest);
170+
171+
return true;
172+
}
173+
174+
static ProtectLoginData instance;
175+
};
176+
ProtectLoginData ProtectLoginData::instance;
11177

12178
class PlaySegaJingle : public Hook
13179
{

src/plugin.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ namespace Settings
151151
inline bool AllowCharacterSelection = false;
152152
inline bool RandomHighwayAnimSets = false;
153153
inline std::string DemonwareServerOverride = "clarissa.port0.org";
154+
inline bool ProtectLoginData = true;
154155

155156
inline bool OverlayEnabled = true;
156157

0 commit comments

Comments
 (0)