324 lines
8.5 KiB
C++
324 lines
8.5 KiB
C++
#include "windows.h"
|
|
#include "vector"
|
|
#include <tchar.h>
|
|
#include <GL/freeglut.h>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <filesystem>
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
#include <regex>
|
|
#include <detours.h>
|
|
|
|
#include "PluginConfigApi.h"
|
|
#include "PatchApplier.h"
|
|
#include "PatchApplier600.h"
|
|
#include "PatchApplier710.h"
|
|
#include "framework.h"
|
|
#include "storage.h"
|
|
#include "databank.h"
|
|
#pragma comment(lib, "detours.lib")
|
|
|
|
unsigned short game_version = 710;
|
|
|
|
void InjectCode(void* address, const std::vector<uint8_t> data);
|
|
void ApplyPatches();
|
|
void ApplyCustomPatches(std::wstring CPATCH_FILE_STRING);
|
|
|
|
constexpr uint64_t PVLIST_INIT_ADDRESS = 0x000000014001EC80;
|
|
void(__fastcall* divaPvListInitializer)(void* pvListContainer) = (void(__fastcall*)(void* pvListContainer))PVLIST_INIT_ADDRESS;
|
|
void initPvListContainerHook();
|
|
|
|
HMODULE *hModulePtr;
|
|
|
|
BOOL APIENTRY DllMain(HMODULE hModule,
|
|
DWORD ul_reason_for_call,
|
|
LPVOID lpReserved
|
|
)
|
|
{
|
|
switch (ul_reason_for_call)
|
|
{
|
|
case DLL_PROCESS_ATTACH:
|
|
hModulePtr = &hModule;
|
|
if (*(char*)0x140A570F0 == '6') game_version = 600;
|
|
ApplyPatches();
|
|
case DLL_THREAD_ATTACH:
|
|
case DLL_THREAD_DETACH:
|
|
case DLL_PROCESS_DETACH:
|
|
break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
void ApplyPatches() {
|
|
if (game_version == 710)
|
|
{
|
|
const char* exe_ver_string = (const char*)0x140A92CB8;
|
|
if (strcmp(exe_ver_string, "7.10.00") != 0)
|
|
{
|
|
MessageBoxW(nullptr, L"Game version not 7.10. Please verify your files.", L"Patches", MB_ICONERROR);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
std::string version_string = std::to_string(game_version);
|
|
version_string.insert(version_string.begin()+1, '.');
|
|
std::cout << "[Patches] Game version " + version_string << std::endl;
|
|
|
|
PatchApplier* pa;
|
|
|
|
switch (game_version)
|
|
{
|
|
case 600:
|
|
pa = new PatchApplier600;
|
|
break;
|
|
default:
|
|
pa = new PatchApplier710;
|
|
}
|
|
|
|
pa->ApplyPatches();
|
|
printf("[Patches] Patches applied\n");
|
|
|
|
if (nCustomPatches)
|
|
{
|
|
std::cout << "[Patches] Reading custom patches...\n";
|
|
try {
|
|
for (std::filesystem::path p : std::filesystem::directory_iterator("../patches"))
|
|
{
|
|
if (std::filesystem::path(p).filename().string()._Starts_with("._")) continue; // exclude macOS metadata
|
|
|
|
std::string extension = std::filesystem::path(p).extension().string();
|
|
if ((extension == ".p" || extension == ".P" || extension == ".p2" || extension == ".P2") &&
|
|
GetPrivateProfileIntW(L"plugins", std::filesystem::path(p).filename().c_str(), TRUE, CONFIG_FILE))
|
|
{
|
|
std::cout << "[Patches] Reading custom patch file: " << std::filesystem::path(p).filename().string() << std::endl;
|
|
ApplyCustomPatches(std::filesystem::path(p).wstring());
|
|
}
|
|
else std::cout << "[Patches] Skipping disabled patch file: " << std::filesystem::path(p).filename().string() << std::endl;
|
|
}
|
|
}
|
|
catch (const std::filesystem::filesystem_error &e) {
|
|
std::cout << "[Patches] File system error " << e.what() << " " << e.path1() << " " << e.path2() << " " << e.code() << std::endl;
|
|
}
|
|
|
|
std::cout << "[Patches] All custom patches applied\n";
|
|
}
|
|
|
|
if (nAutoDatabank)
|
|
{
|
|
initPvListContainerHook();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void ApplyCustomPatches(std::wstring CPATCH_FILE_STRING)
|
|
{
|
|
LPCWSTR CPATCH_FILE = CPATCH_FILE_STRING.c_str();
|
|
std::ifstream fileStream(CPATCH_FILE_STRING);
|
|
|
|
if (!fileStream.is_open())
|
|
return;
|
|
|
|
std::string line;
|
|
|
|
// check for BOM
|
|
std::getline(fileStream, line);
|
|
if (line.size() >= 3 && line.rfind("\xEF\xBB\xBF", 0) == 0)
|
|
fileStream.seekg(3);
|
|
else
|
|
fileStream.seekg(0);
|
|
|
|
while (std::getline(fileStream, line))
|
|
{
|
|
if (line[0] == '#')
|
|
{
|
|
std::cout << "[Patches]" << line.substr(1, line.size()-1) << std::endl;
|
|
continue;
|
|
}
|
|
if (line == "IGNORE") break;
|
|
if (line.find(':') == std::string::npos || (line[0] == '/' && line[1] == '/')) continue;
|
|
|
|
std::vector<std::string> commentHSplit = SplitString(line, "#");
|
|
std::vector<std::string> commentDSSplit = SplitString(commentHSplit[0], "//");
|
|
std::vector<std::string> colonSplit = SplitString(commentDSSplit[0], ":");
|
|
if (colonSplit.size() != 2) continue;
|
|
bool echo = true;
|
|
if (colonSplit[0].at(0) == '@')
|
|
{
|
|
echo = false;
|
|
colonSplit[0].at(0) = ' ';
|
|
}
|
|
long long int address;
|
|
std::istringstream iss(colonSplit[0]);
|
|
iss >> std::setbase(16) >> address;
|
|
if (address == 0) std::cout << "[Patches] Custom patch address wrong: " << std::hex << address << std::endl;
|
|
|
|
if (colonSplit[1].at(0) == '!')
|
|
{
|
|
if (echo) std::cout << "[Patches] Patching: " << std::hex << address << ":!";
|
|
std::vector<std::string> fullColonSplit = SplitString(line, ":");
|
|
for (int i = 1; i < fullColonSplit[1].size(); i++)
|
|
{
|
|
unsigned char byte_u = fullColonSplit[1].at(i);
|
|
if(byte_u=='\\' && i<fullColonSplit[1].size())
|
|
{
|
|
switch (fullColonSplit[1].at(i + 1))
|
|
{
|
|
case '0':
|
|
byte_u = '\0';
|
|
i++;
|
|
break;
|
|
case 'n':
|
|
byte_u = '\n';
|
|
i++;
|
|
break;
|
|
case 't':
|
|
byte_u = '\t';
|
|
i++;
|
|
break;
|
|
case 'r':
|
|
byte_u = '\r';
|
|
i++;
|
|
break;
|
|
case 'b':
|
|
byte_u = '\b';
|
|
i++;
|
|
break;
|
|
case 'a':
|
|
byte_u = '\a';
|
|
i++;
|
|
break;
|
|
case 'f':
|
|
byte_u = '\f';
|
|
i++;
|
|
break;
|
|
case 'v':
|
|
byte_u = '\v';
|
|
i++;
|
|
break;
|
|
case '\\':
|
|
byte_u = '\\';
|
|
i++;
|
|
break;
|
|
}
|
|
}
|
|
if (echo) std::cout << byte_u;
|
|
std::vector<uint8_t> patch = { byte_u };
|
|
InjectCode((void*)address, patch);
|
|
address++;
|
|
}
|
|
if (echo) std::cout << std::endl;
|
|
}
|
|
else
|
|
{
|
|
std::vector<std::string> bytes = SplitString(colonSplit[1], " ");
|
|
if (bytes.size() < 1) continue;
|
|
|
|
std::string comment_string = "";
|
|
int comment_counter = 0;
|
|
if (commentHSplit.size() > 1)
|
|
{
|
|
bool ignore = 1;
|
|
for (std::string comment : commentHSplit)
|
|
{
|
|
if (ignore)
|
|
{
|
|
ignore = 0;
|
|
continue;
|
|
}
|
|
comment_string = comment_string + comment;
|
|
}
|
|
}
|
|
|
|
if (echo) std::cout << "[Patches] Patching: " << std::hex << address << ":";
|
|
for (std::string bytestring : bytes)
|
|
{
|
|
int byte;
|
|
std::istringstream issb(bytestring);
|
|
issb >> std::setbase(16) >> byte;
|
|
unsigned char byte_u = byte;
|
|
if (echo)
|
|
{
|
|
std::cout << std::hex << byte << " ";
|
|
if (comment_counter < comment_string.length())
|
|
{
|
|
std::cout << "(" << comment_string.at(comment_counter) << ") ";
|
|
comment_counter++;
|
|
}
|
|
}
|
|
std::vector<uint8_t> patch = { byte_u };
|
|
InjectCode((void*)address, patch);
|
|
address++;
|
|
}
|
|
if (echo) std::cout << std::endl;
|
|
else if (comment_string.length() > 0)
|
|
{
|
|
std::cout << "[Patches]";
|
|
if (comment_string.at(0) != ' ') std::cout << ' ';
|
|
std::cout << comment_string << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
fileStream.close();
|
|
}
|
|
|
|
Databank::PvList lists[5] = {};
|
|
void hookedPvListInitializer(void* pvListContainer)
|
|
{
|
|
Storage::FileRecord* records = (Storage::FileRecord*)((uint64_t)pvListContainer + 0xA0);
|
|
|
|
for (int i = 0; i <= 4; i++)
|
|
{
|
|
std::regex pattern("(PvList" + std::to_string(i) +"_SBZV_7.10_.*_.*\\.dat)");
|
|
try
|
|
{
|
|
for (const auto& entry : std::filesystem::directory_iterator("ram/databank/"))
|
|
{
|
|
if (entry.is_regular_file() && std::regex_match(entry.path().filename().string(), pattern))
|
|
{
|
|
std::string path = entry.path().string();
|
|
std::cout << "[Patches] Reading databank: " << path << std::endl;
|
|
lists[i].readFromFile(path);
|
|
}
|
|
}
|
|
}
|
|
catch (const std::filesystem::filesystem_error& e)
|
|
{
|
|
std::cerr << "[Patches] Databank filesystem error: " << e.what() << std::endl;
|
|
}
|
|
|
|
if (i != 4) lists[i].generateMissingEntries();
|
|
std::string finalString = lists[i].finalize();
|
|
|
|
Storage::customAddStringContainer(&records[i], finalString.c_str());
|
|
}
|
|
|
|
*(uint8_t*)(0x140CDB1D9) = 0x01; // enables databank
|
|
}
|
|
|
|
void initPvListContainerHook()
|
|
{
|
|
if (game_version != 710) return;
|
|
|
|
DetourTransactionBegin();
|
|
DetourUpdateThread(GetCurrentThread());
|
|
DetourAttach(&(PVOID&)divaPvListInitializer, (PVOID)(hookedPvListInitializer));
|
|
DetourTransactionCommit();
|
|
std::cout << "[Patch] Auto Databank enabled" << std::endl;
|
|
}
|
|
|
|
|
|
|
|
using namespace PluginConfig;
|
|
|
|
extern "C" __declspec(dllexport) LPCWSTR GetPluginName(void)
|
|
{
|
|
return L"Patches";
|
|
}
|
|
|
|
extern "C" __declspec(dllexport) LPCWSTR GetPluginDescription(void)
|
|
{
|
|
return L"Applies patches/tweaks to the game before it starts.\nThis plugin is required.";
|
|
} |