Skip to content

Commit

Permalink
ProtectLoginData: moves login data outside savegame files, and encryp…
Browse files Browse the repository at this point in the history
…ts with CryptProtectData

older non-protected data can be protected by changing one of the settings in-game, an OnlineLoginData.bin should then be created
  • Loading branch information
emoose committed Jan 16, 2025
1 parent de5c6a9 commit 57034d1
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 5 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ target_link_libraries(outrun2006tweaks PUBLIC
SDL3-static
Winmm.lib
Setupapi.lib
Crypt32.lib
)

target_link_options(outrun2006tweaks PUBLIC
Expand Down
11 changes: 10 additions & 1 deletion OutRun2006Tweaks.ini
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,17 @@ RandomHighwayAnimSets = false
# (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)
DemonwareServerOverride = clarissa.port0.org

# Protects online login data by removing it from savegame files & encrypting against your Windows user account
# Making sure that your login details won't be exposed if you share savegame files
# (older non-protected data can be protected by changing one of the settings in-game, an OnlineLoginData.bin should then be created)
#
# NOTE: if you move your OR2006 install between different machines, the login data will likely fail to decrypt
# in that case the tweak can be disabled here
ProtectLoginData = true

[Overlay]
# Enables OR2006Tweaks overlay
# Enables the OutRun2006Tweaks overlay, accessible via F11 key
# (more settings for Overlay are available in the overlay itself)
Enabled = true

[Bugfixes]
Expand Down
9 changes: 5 additions & 4 deletions cmake.toml
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,11 @@ link-libraries = [
"version.lib",
"xinput9_1_0.lib",
"Hid.lib",
"libminiupnpc-static",
"SDL3-static",
"Winmm.lib",
"Setupapi.lib"
"libminiupnpc-static",
"SDL3-static",
"Winmm.lib",
"Setupapi.lib",
"Crypt32.lib"
]

[target.outrun2006tweaks.properties]
Expand Down
2 changes: 2 additions & 0 deletions src/dllmain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ namespace Settings
spdlog::info(" - AllowCharacterSelection: {}", AllowCharacterSelection);
spdlog::info(" - RandomHighwayAnimSets: {}", RandomHighwayAnimSets);
spdlog::info(" - DemonwareServerOverride: {}", DemonwareServerOverride);
spdlog::info(" - ProtectLoginData: {}", ProtectLoginData);

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

Expand Down Expand Up @@ -246,6 +247,7 @@ namespace Settings
AllowCharacterSelection = ini.Get("Misc", "AllowCharacterSelection", AllowCharacterSelection);
RandomHighwayAnimSets = ini.Get("Misc", "RandomHighwayAnimSets", RandomHighwayAnimSets);
DemonwareServerOverride = ini.Get("Misc", "DemonwareServerOverride", DemonwareServerOverride);
ProtectLoginData = ini.Get("Misc", "ProtectLoginData", ProtectLoginData);

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

Expand Down
166 changes: 166 additions & 0 deletions src/hooks_misc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,172 @@
#include <miniupnpc.h>
#include <upnpcommands.h>
#include <WinSock2.h>
#include <fstream>
#include <wincrypt.h>

class ProtectLoginData : public Hook
{
// C2C saves plaintext online login details into SaveGame/Common.dat by default
// It's not obvious this file contains login info though, so some might share it out freely
// Now that online is restored those details could be extracted and reused
//
// Instead of keeping it inside SaveGame/Common.dat, we'll store it in a seperate file next to game EXE
// and zero-out the data inside Common.dat before it gets saved to disk
//
// We'll also encrypt the data using CryptProtectData, which encrypts it against the users Windows account
// So even if the file does get shared, it'd be difficult for others to be able to decrypt it
const static inline std::string LoginDataFilename = "OnlineLoginData.dat";

static void EncryptDataToFile(const uint8_t* inputData, int dataLength, const std::filesystem::path& outputFilePath)
{
DATA_BLOB inputBlob = { static_cast<DWORD>(dataLength), const_cast<BYTE*>(inputData) };
DATA_BLOB outputBlob;

if (!CryptProtectData(&inputBlob, L"OnlineLoginData", nullptr, nullptr, nullptr, 0, &outputBlob))
throw std::runtime_error("CryptProtectData = false");
else
{
std::ofstream outputFile(outputFilePath, std::ios::binary);
if (outputFile.is_open())
{
outputFile.write((char*)outputBlob.pbData, outputBlob.cbData);
outputFile.close();
}
LocalFree(outputBlob.pbData);
}
}

static bool DecryptDataFromFile(uint8_t* outputData, int dataLength, const std::filesystem::path& inputFilePath)
{
if (!std::filesystem::exists(inputFilePath))
return false;

std::ifstream inputFile(inputFilePath, std::ios::binary | std::ios::ate);
if (inputFile.is_open())
{
std::streamsize size = inputFile.tellg();
inputFile.seekg(0, std::ios::beg);
std::vector<char> encryptedData(size);
if (inputFile.read(encryptedData.data(), size))
{
DATA_BLOB inputBlob = { static_cast<DWORD>(size), reinterpret_cast<BYTE*>(encryptedData.data()) };
DATA_BLOB outputBlob;

if (!CryptUnprotectData(&inputBlob, nullptr, nullptr, nullptr, nullptr, 0, &outputBlob))
throw std::runtime_error("CryptUnprotectData = false");
else
{
std::memcpy(outputData, outputBlob.pbData, min(int(outputBlob.cbData), dataLength));
LocalFree(outputBlob.pbData);
return true;
}
}
inputFile.close();
}

return false;
}

inline static SafetyHookInline Sumo_CommonDat_Read_hook = {};
static int Sumo_CommonDat_Read_dest()
{
auto ret = Sumo_CommonDat_Read_hook.call<int>();

uint8_t* loginData = Module::exe_ptr<uint8_t>(0x3C205C);
int loginDataLength = (0x10 * 8) + (8 * 8);

try
{
DecryptDataFromFile(loginData, loginDataLength, Module::ExePath.parent_path() / LoginDataFilename);
}
catch (const std::exception& e)
{
memset(loginData, 0, loginDataLength);
spdlog::error("ProtectLoginData: Failed to decrypt login data from {}: {}", LoginDataFilename, e.what());
}

return ret;
}
inline static SafetyHookInline Sumo_CommonDat_Write_hook = {};
static int Sumo_CommonDat_Write_dest()
{
constexpr int loginDataLength = (0x10 * 8) + (8 * 8);
uint8_t* loginData = Module::exe_ptr<uint8_t>(0x3C205C);

uint8_t tempLoginData[loginDataLength];
memcpy(tempLoginData, loginData, loginDataLength);

try
{
EncryptDataToFile(loginData, loginDataLength, Module::ExePath.parent_path() / LoginDataFilename);
}
catch (const std::exception& e)
{
spdlog::error("ProtectLoginData: failed to encrypt login data to {} ({}), login details will be scrubbed from common.dat", e.what(), LoginDataFilename);
}

SecureZeroMemory(loginData, loginDataLength);

auto ret = Sumo_CommonDat_Write_hook.call<int>();

memcpy(loginData, tempLoginData, loginDataLength);

return ret;
}
inline static SafetyHookInline Sumo_CommonDat_WriteRaw_hook = {};
static int Sumo_CommonDat_WriteRaw_dest()
{
constexpr int loginDataLength = (0x10 * 8) + (8 * 8);
uint8_t* loginData = Module::exe_ptr<uint8_t>(0x3C205C);

uint8_t tempLoginData[loginDataLength];
memcpy(tempLoginData, loginData, loginDataLength);

try
{
EncryptDataToFile(loginData, loginDataLength, Module::ExePath.parent_path() / LoginDataFilename);
}
catch (const std::exception& e)
{
spdlog::error("ProtectLoginData: failed to encrypt login data to {} ({}), login details will be scrubbed from common.dat", e.what(), LoginDataFilename);
}

SecureZeroMemory(loginData, loginDataLength);

auto ret = Sumo_CommonDat_WriteRaw_hook.call<int>();

memcpy(loginData, tempLoginData, loginDataLength);

return ret;
}

public:
std::string_view description() override
{
return "ProtectLoginData";
}

bool validate() override
{
return Settings::ProtectLoginData;
}

bool apply() override
{
constexpr int Sumo_CommonDat_Read_Addr = 0x16380;
constexpr int Sumo_CommonDat_Write_Addr = 0x16420;
constexpr int Sumo_CommonDat_WriteRaw_Addr = 0x164D0;

Sumo_CommonDat_Read_hook = safetyhook::create_inline(Module::exe_ptr(Sumo_CommonDat_Read_Addr), Sumo_CommonDat_Read_dest);
Sumo_CommonDat_Write_hook = safetyhook::create_inline(Module::exe_ptr(Sumo_CommonDat_Write_Addr), Sumo_CommonDat_Write_dest);
Sumo_CommonDat_WriteRaw_hook = safetyhook::create_inline(Module::exe_ptr(Sumo_CommonDat_WriteRaw_Addr), Sumo_CommonDat_WriteRaw_dest);

return true;
}

static ProtectLoginData instance;
};
ProtectLoginData ProtectLoginData::instance;

class PlaySegaJingle : public Hook
{
Expand Down
1 change: 1 addition & 0 deletions src/plugin.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ namespace Settings
inline bool AllowCharacterSelection = false;
inline bool RandomHighwayAnimSets = false;
inline std::string DemonwareServerOverride = "clarissa.port0.org";
inline bool ProtectLoginData = true;

inline bool OverlayEnabled = true;

Expand Down

0 comments on commit 57034d1

Please sign in to comment.