Initial public release of rem-essentials

This commit is contained in:
2026-08-11 16:48:50 +02:00
commit d611924a5e
161 changed files with 9792 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
#ifndef _FLPLUSPLUS_H_
#define _FLPLUSPLUS_H_
#define FLPEXPORT __declspec(dllexport)
#ifdef __cplusplus
extern "C" {
#endif
//typedef void (*flplusplus_cblatehook)(void*);
//FLPEXPORT void flplusplus_add_latehook(flplusplus_cblatehook hkfunc, void *userData);
#ifdef __cplusplus
}
#endif
#endif
+71
View File
@@ -0,0 +1,71 @@
#pragma once
#pragma ms_struct on
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "offsets.h"
class INI_Reader
{
public:
INI_Reader();
~INI_Reader();
bool open(LPCSTR path, bool throwExceptionOnFail);
bool read_header();
bool is_header(LPCSTR header);
bool read_value();
bool is_value(LPCSTR value);
bool get_value_bool(UINT index);
int get_value_int(UINT index);
float get_value_float(UINT index);
LPCSTR get_value_string(UINT index);
void close();
LPCSTR get_name_ptr();
private:
BYTE data[0x1568];
};
namespace Archetype
{
struct Ship
{
BYTE x00[0x14];
UINT idsName;
};
Ship* GetShip(UINT id);
}
struct CShip
{
BYTE x00[0x88];
Archetype::Ship* shiparch;
};
struct IObjInspectImpl
{
BYTE data[0x10];
CShip* ship;
};
namespace Universe
{
struct IBase
{
BYTE data[0xC];
UINT idsName;
};
struct ISystem
{
BYTE data[0x68];
UINT idsName;
};
IBase* get_base(UINT id);
ISystem* get_system(UINT id);
}
bool IsMPServer();
+65
View File
@@ -0,0 +1,65 @@
#include "Freelancer.h"
#include "Common.h"
#include <cstdlib>
CShip* GetShip()
{
typedef IObjInspectImpl* GetPlayerIObjInspectImpl();
IObjInspectImpl* playerIObjInspect = ((GetPlayerIObjInspectImpl*) OF_GET_PLAYER_INSPECT_IMPL)();
return !playerIObjInspect ? nullptr : playerIObjInspect->ship;
}
std::wstring GetSystemName()
{
UINT currentSystemId = *((PUINT) OF_CURRENT_SYSTEM_ID);
if (!currentSystemId)
return {};
UINT systemIds = Universe::get_system(currentSystemId)->idsName;
if (!systemIds)
return {};
WCHAR buffer[64] = { 0 };
GetFlString(systemIds, buffer, _countof(buffer));
return std::wstring(buffer);
}
std::wstring GetBaseName()
{
UINT currentBaseId = *((PUINT) OF_CURRENT_BASE_ID);
if (!currentBaseId)
return {};
UINT baseIds = Universe::get_base(currentBaseId)->idsName;
if (!baseIds)
return {};
WCHAR buffer[64] = { 0 };
GetFlString(baseIds, buffer, _countof(buffer));
return std::wstring(buffer);
}
std::wstring GetShipName()
{
UINT currentShipId = GetShipId();
if (!currentShipId)
return {};
Archetype::Ship* shiparch = Archetype::GetShip(currentShipId);
if (!shiparch)
return {};
WCHAR buffer[64] = { 0 };
GetFlString(shiparch->idsName, buffer, _countof(buffer));
return std::wstring(buffer);
}
+24
View File
@@ -0,0 +1,24 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <string>
#include "offsets.h"
std::wstring GetSystemName();
std::wstring GetBaseName();
std::wstring GetShipName();
inline UINT GetFlString(UINT ids, PWCHAR buffer, UINT bufferSize)
{
PDWORD resourceHandle = *((PDWORD*) OF_RESOURCES_HANDLE);
typedef UINT GetFlStringFunc(PDWORD, UINT, PWCHAR, UINT);
return ((GetFlStringFunc*) OF_GET_FL_STRING)(resourceHandle, ids, buffer, bufferSize);
}
// It is recommended to call this function rather than getting the CURRENT_SHIP_ID directly.
// This is because Console hooks this function to make it so that the player has no ship sometimes.
inline UINT GetShipId()
{
typedef UINT GetShipIdFunc();
return ((GetShipIdFunc*) OF_GET_SHIP_ID)();
}
+25
View File
@@ -0,0 +1,25 @@
#include "codec.h"
#include "offsets.h"
#include "patch.h"
#include "config.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
void codec::init()
{
// adoxa created a plugin called MP3 Codec Fix which fixes the "missing MP3 codec" spew warning the right way.
// However, supposedly this fix doesn't work on Wine (probably due to the missing Fraunhofer codec), despite the audio working just fine.
// Hence we apply patches to wipe out the MP3 codec spew warnings on Wine only.
if (!config::is_wine())
return;
//Patch out MP3 warnings
auto soundManager = (DWORD) GetModuleHandleA("soundmanager.dll");
if (soundManager)
patch::patch_uint8(soundManager + F_OF_SOUNDMAN_MP3, 0xC3);
auto soundStreamer = (DWORD) GetModuleHandleA("soundstreamer.dll");
if (soundStreamer)
patch::patch_uint8(soundStreamer + F_OF_SOUNDSTR_MP3, 0xC3);
}
+4
View File
@@ -0,0 +1,4 @@
#pragma once
namespace codec {
void init();
}
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include <iostream>
#include <vector>
namespace config {
void init_defaults();
void init_from_file(const char *filename);
void read_font_files(const char *filename);
void EnsureInitialized();
class ConfigData
{
public:
float lodscale;
float pbubblescale;
float characterdetailscale;
float asteroiddistscale;
std::string savefoldername;
bool saveindirectory;
std::string screenshotsfoldername;
bool screenshotsindirectory;
bool altfullscreenscreenshots;
bool altwindowedscreenshots;
bool removestartlocationwarning;
bool logtoconsole;
float shippreviewscrollingspeed;
bool shippreviewscrollinginverse;
float shippreviewscrollingmindistance;
float shippreviewscrollingmaxdistance;
bool alwaysregeneraterestartfile;
int failedtoinitsavesdirids;
bool touchpadsupport;
bool confinecursor;
std::vector<std::string> fontfiles{};
};
ConfigData& get_config();
bool is_wine();
}
+61
View File
@@ -0,0 +1,61 @@
#include <windows.h>
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <iostream>
#include <fstream>
using namespace std;
// maximum mumber of lines the output console should have
static const WORD MAX_CONSOLE_LINES = 500;
void RedirectIOToConsole()
{
int hConHandle;
long lStdHandle;
CONSOLE_SCREEN_BUFFER_INFO coninfo;
FILE *fp;
// allocate a console for this app
AllocConsole();
// set the screen buffer to be big enough to let us scroll text
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE),&coninfo);
coninfo.dwSize.Y = MAX_CONSOLE_LINES;
SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE),coninfo.dwSize);
// redirect unbuffered STDOUT to the console
lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE);
hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
fp = _fdopen( hConHandle, "w" );
freopen_s(&fp, "CONOUT$", "w", stdout);
setvbuf( stdout, NULL, _IONBF, 0 );
// redirect unbuffered STDIN to the console
lStdHandle = (long)GetStdHandle(STD_INPUT_HANDLE);
hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
fp = _fdopen( hConHandle, "r" );
freopen_s(&fp, "CONIN$", "w", stdin);
setvbuf( stdin, NULL, _IONBF, 0 );
// redirect unbuffered STDERR to the console
lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE);
hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
fp = _fdopen( hConHandle, "w" );
freopen_s(&fp, "CONOUT$", "w", stderr);
setvbuf( stderr, NULL, _IONBF, 0 );
// make cout, wcout, cin, wcin, wcerr, cerr, wclog and clog
// point to console as well
ios::sync_with_stdio();
}
+2
View File
@@ -0,0 +1,2 @@
#pragma once
void RedirectIOToConsole();
+43
View File
@@ -0,0 +1,43 @@
#include "cursor.h"
#include "config.h"
#include "offsets.h"
#include "patch.h"
int __fastcall UpdateMouseX(int mouseX, int increase)
{
MOUSE_X = mouseX + increase;
if (MOUSE_X < 0)
MOUSE_X = 0;
else if (MOUSE_X > WINDOW_WIDTH - 1)
MOUSE_X = WINDOW_WIDTH - 1;
return increase;
}
int __fastcall UpdateMouseY(int mouseY, int increase)
{
MOUSE_Y = mouseY + increase;
if (MOUSE_Y < 0)
MOUSE_Y = 0;
else if (MOUSE_Y > WINDOW_HEIGHT - 1)
MOUSE_Y = WINDOW_HEIGHT - 1;
return increase;
}
void cursor::hook_mouse_func(unsigned int address, void* func)
{
unsigned char dummy[5];
patch::patch_uint16(address, 0xC289); // mov edx, eax
patch::detour((unsigned char*) (address + 2), func, dummy, false);
patch::patch_uint8(address + 7, 0x90);
}
void cursor::init()
{
if (!config::get_config().confinecursor)
return;
hook_mouse_func(OF_MOUSE_X_UPDATE, (void*) UpdateMouseX);
hook_mouse_func(OF_MOUSE_Y_UPDATE, (void*) UpdateMouseY);
}
+6
View File
@@ -0,0 +1,6 @@
#pragma once
namespace cursor {
void hook_mouse_func(unsigned int address, void* func);
void init();
}
+27
View File
@@ -0,0 +1,27 @@
#include "fontresource.h"
#include "config.h"
#include "log.h"
#include <shlwapi.h>
void fontresource::init(LPCSTR fontDirectory)
{
// Add font resources
for (const auto &fontFile : config::get_config().fontfiles) {
char path[MAX_PATH];
// Create full path to font file
strcpy_s(path, sizeof(path), fontDirectory);
PathAppendA(path, fontFile.c_str());
if (!PathFileExists(path)) {
logger::writeformat("path to font %s does not exist (%s)", fontFile.c_str(), path);
continue;
}
if (AddFontResourceEx(path, FR_PRIVATE, nullptr))
logger::writeformat("successfully added font %s", fontFile.c_str());
else
logger::writeformat("error adding font %s", fontFile.c_str());
}
}
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
namespace fontresource {
void init(LPCSTR fontDirectory);
}
+148
View File
@@ -0,0 +1,148 @@
#include "graphics.h"
#include "patch.h"
#include "offsets.h"
#include "config.h"
#include "Common.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
config::ConfigData& cfg = config::get_config();
#define MIN_DETAIL_SCALE 1.0f
#define MAX_DETAIL_SCALE 1000000.0f
#define MAX_ASTEROID_DIST_SCALE 10.0f
float __fastcall multiply_lodranges_float(INI_Reader* reader, PVOID _edx, UINT index)
{
return reader->get_value_float(index) * cfg.lodscale;
}
float __fastcall multiply_pbubble_float(INI_Reader* reader, PVOID _edx, UINT index)
{
return reader->get_value_float(index) * cfg.pbubblescale;
}
float __fastcall multiply_characterdetail_float(INI_Reader* reader, PVOID _edx, UINT index)
{
return reader->get_value_float(index) * cfg.characterdetailscale;
}
float __fastcall multiply_asteroiddist_float(INI_Reader* reader, PVOID _edx, UINT index)
{
return reader->get_value_float(index) * cfg.asteroiddistscale;
}
bool patch_lodranges()
{
if(cfg.lodscale <= MIN_DETAIL_SCALE)
return false;
else if (cfg.lodscale > MAX_DETAIL_SCALE)
cfg.lodscale = MAX_DETAIL_SCALE;
//distances
patch::set_execute_read_write(OF_REN_DIST0, sizeof(float));
*((float*)OF_REN_DIST0) *= cfg.lodscale;
static UINT multiplyLodsPtr = (UINT) &multiply_lodranges_float;
patch::patch_uint32(OF_LODS_GET_VALUE, (UINT) &multiplyLodsPtr);
return true;
}
bool patch_pbubble()
{
if (cfg.pbubblescale <= MIN_DETAIL_SCALE)
return false;
else if (cfg.pbubblescale > MAX_DETAIL_SCALE)
cfg.pbubblescale = MAX_DETAIL_SCALE;
static UINT multiplyPbubblePtr = (UINT) &multiply_pbubble_float;
patch::patch_uint32(OF_PBUBBLE_GET_VALUE0, (UINT) &multiplyPbubblePtr);
patch::patch_uint32(OF_PBUBBLE_GET_VALUE1, (UINT) &multiplyPbubblePtr);
patch::set_execute_read_write(OF_REN_DIST1, sizeof(float));
float ren_dist1 = *((float*)OF_REN_DIST1);
if (cfg.pbubblescale > 2.0f)
ren_dist1 += (ren_dist1 / 10.0f) * (cfg.pbubblescale - 2.0f);
// 40000.0f is considered to be the maximum "safe" value
if (ren_dist1 > 40000.0f)
ren_dist1 = 40000.0f;
*((float*)OF_REN_DIST1) = ren_dist1;
return true;
}
bool patch_characterdetail()
{
if (cfg.characterdetailscale <= MIN_DETAIL_SCALE)
return false;
else if (cfg.characterdetailscale > MAX_DETAIL_SCALE)
cfg.characterdetailscale = MAX_DETAIL_SCALE;
auto common = (DWORD) GetModuleHandleA("common.dll");
if (!common)
common = (DWORD) LoadLibraryA("common.dll");
if (!common)
return false;
UINT multiplyCharacterDetailPtr = (UINT) &multiply_characterdetail_float;
UINT detailSwitchAddr = common + F_OF_BODYPART_DETAILSWITCH_GET_VALUE;
patch::patch_uint32(detailSwitchAddr, multiplyCharacterDetailPtr - detailSwitchAddr - 4);
return true;
}
bool patch_asteroiddist()
{
if (cfg.asteroiddistscale <= MIN_DETAIL_SCALE)
return false;
else if (cfg.asteroiddistscale > MAX_ASTEROID_DIST_SCALE)
cfg.asteroiddistscale = MAX_ASTEROID_DIST_SCALE;
static UINT multiplyAsteroidDistPtr = (UINT) &multiply_asteroiddist_float;
patch::patch_uint32(OF_ASTEROID_DIST_GET_VALUE, (UINT) &multiplyAsteroidDistPtr);
patch::patch_uint32(OF_AST_BILLBOARD_DIST_GET_VALUE, (UINT)&multiplyAsteroidDistPtr);
return true;
}
bool graphics::init_base_fixes()
{
auto common = (DWORD) GetModuleHandleA("common.dll");
if (!common)
common = (DWORD) LoadLibraryA("common.dll");
if (!common)
return false;
patch::patch_uint8(OF_VIDEODIALOG, 0x33); //disable unsupported video dialog
patch::patch_uint16(OF_MAXTEXSIZE, 0x2000); //texture size bug fix
//replace "Vibrocentric" string
//FL tries to load this font over Agency FB, screws up UI if it finds it
//if you have a font named '\b' you have big problems
const char *garbageFont = "\b\0";
unsigned int address = common + F_OF_VIBROCENTRICFONT_V11;
patch::patch_bytes(address, (void*)garbageFont, 2);
return true;
}
bool graphics::init_detail_scaling()
{
patch_lodranges();
patch_pbubble();
if (!patch_characterdetail())
return false;
patch_asteroiddist();
return true;
}
void graphics::init()
{
init_base_fixes();
init_detail_scaling();
}
+6
View File
@@ -0,0 +1,6 @@
#pragma once
namespace graphics {
void init();
bool init_base_fixes();
bool init_detail_scaling();
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef _JUMPTABLE_H_
#define _JUMPTABLE_H_
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
// Macro magic to produce a compact set of functions to
// manually import functions
#ifdef _MSC_VER
#define JUMPTABLE( def, table, index ) __declspec(naked) def { __asm jmp table[index*8+4] }
#else
#define JUMPTABLE( def, table, index ) __attribute__((naked)) def { \
__asm(".intel_syntax noprefix\n"); \
__asm__("jmp _" #table "[" #index "*8+4]\n"); \
__asm__(".att_syntax"); \
}
#endif
#define FUNC_INITIALIZER(f) \
static void f(void); \
struct f##_t_ { f##_t_(void) { f(); } }; static f##_t_ f##_; \
static void f(void)
#define JUMPTABLE_INIT( dll, table ) \
FUNC_INITIALIZER( table ## _Load) \
{ \
HMODULE library = LoadLibraryA(dll); \
for(int i = 0; i < (sizeof(table) / sizeof(const char*)); i+=2) \
{ \
table[i + 1] = (const char*)GetProcAddress(library, table[i]); \
} \
}
#endif
+137
View File
@@ -0,0 +1,137 @@
#include "log.h"
#include "config.h"
#include "patch.h"
#include "offsets.h"
#define WIN32_LEAN_AND_MEAN
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <stdio.h>
#include <ctime>
#include <stdarg.h>
static bool linked = false;
static bool fdump_patched = false;
static bool timestamps_enabled = false;
static bool console_enabled = false;
typedef int (*pFDUMP)(DWORD, const char *, ...);
static pFDUMP *FDUMP = nullptr;
static pFDUMP fdump_original = nullptr;
static bool do_linking()
{
if (linked)
return FDUMP != nullptr;
linked = true;
HMODULE dacom = GetModuleHandleA("dacom.dll");
if (!dacom)
dacom = LoadLibraryA("dacom.dll");
if (!dacom)
return false;
FDUMP = (pFDUMP*)GetProcAddress(dacom, "FDUMP");
return FDUMP != nullptr;
}
void logger::writeline(const char *line)
{
#define ERRORCODE_NOTICE 0x100003
if (do_linking() && *FDUMP)
(*FDUMP)(ERRORCODE_NOTICE, "%s", line);
}
static DWORD fdump_rem(DWORD errorCode, const char *fmt, ...)
{
char buffer[4096];
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
std::time_t rawtime;
std::tm* timeinfo;
char timestamp[100];
std::time(&rawtime);
timeinfo = std::localtime(&rawtime);
std::strftime(timestamp, 80, "%Y-%m-%d %H:%M:%S", timeinfo);
if(console_enabled) {
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
auto severity = (BYTE) (errorCode);
if (severity <= 1)
SetConsoleTextAttribute(hConsole, FOREGROUND_RED);
else if (severity <= 2)
SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN);
else
SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
if (timestamps_enabled)
printf("[%s] %s\n", timestamp, buffer);
else
printf("%s\n", buffer);
}
if (timestamps_enabled)
return fdump_original(errorCode, "[%s] %s", timestamp, buffer);
return fdump_original(errorCode, "%s", buffer);
}
bool logger::enable_fdump(bool timestamps, bool console)
{
timestamps_enabled = timestamps_enabled || timestamps;
console_enabled = console_enabled || console;
if (fdump_patched)
return true;
if (!do_linking() || !FDUMP || !*FDUMP)
return false;
fdump_original = *FDUMP;
*FDUMP = (pFDUMP)fdump_rem;
fdump_patched = true;
return true;
}
// FLServer has hard coded calls to a function called ServerLogF,
// which prints non-timestamped messages,
// so patch every call to show timestamped messages instead.
bool logger::patch_serverlogf()
{
if (!enable_fdump(true, false))
return false;
#define FLSERVER_BASE (0x400000)
// File offsets of ServerLogF calls
DWORD serverLogCalls[] = {
0xB152, 0xB18F, 0xB1CC, 0xB235, 0xB26D, 0xBCE4,
0xBFD6, 0xCD03, 0x1398D, 0x13B1F, 0x13BA0
};
// Hook all instances where ServerLogF is called
unsigned char originalData[5];
for (const DWORD serverLogCall : serverLogCalls) {
auto *originalFunc = (unsigned char *)(serverLogCall + FLSERVER_BASE);
patch::detour(originalFunc, (void*) fdump_rem, originalData, false);
}
// Sets the server log function in remoteclient.dll
// Never seen it being used but overwrite the function just in case
patch::patch_uint32(OF_SERVER_LOG_FUNCTION_REF, (UINT) &fdump_rem);
return true;
}
void logger::writeformat(const char *fmt, ...)
{
char buffer[4096];
va_list args;
va_start (args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end (args);
logger::writeline(buffer);
}
+8
View File
@@ -0,0 +1,8 @@
#pragma once
namespace logger {
void writeline(const char *line);
void writeformat(const char *fmt, ...);
bool enable_fdump(bool timestamps, bool console);
bool patch_serverlogf();
}
+73
View File
@@ -0,0 +1,73 @@
#pragma once
#define OF_REN_DIST0 (0x613EC8)
#define OF_REN_DIST1 (0x5C8910)
#define OF_MAXTEXSIZE (0x41AD6F)
#define OF_VIDEODIALOG (0x5B16FC)
#define OF_PRINTSCREEN (0x425170)
#define F_OF_SOUNDMAN_MP3 (0x8660)
#define F_OF_SOUNDSTR_MP3 (0x1000)
#define OF_STARTLOCATION (0x43B348)
#define F_OF_VIBROCENTRICFONT_V11 (0x143DC0)
#define OF_LODS_GET_VALUE (0x402385)
#define OF_PBUBBLE_GET_VALUE0 (0x4FD82C)
#define OF_PBUBBLE_GET_VALUE1 (0x4FD839)
#define OF_ASTEROID_DIST_GET_VALUE (0x520B26)
#define OF_AST_BILLBOARD_DIST_GET_VALUE (0x54DD32)
#define F_OF_BODYPART_DETAILSWITCH_GET_VALUE (0x9EFB2)
#define OF_SHIP_PREVIEW_WINDOW_SCROLL (0x5E4C14)
#define OF_FREELANCER_HWND (0x67ECA0)
#define OF_FREELANCER_FULLSCREEN_FLAG (0x679BE5)
#define F_OF_LOAD_SAVE_GAME_CALL (0x69012)
#define F_OF_LOAD_SAVE_GAME (0x68D50)
#define F_OF_RESTART_NAME_PTR (0x68FDC)
#define F_OF_SAVE_FILE_FMT_PTR (0x68FE8)
#define OF_CHECK_SAVE_GAMES_CALL (0x573BA7)
#define OF_CHECK_SAVE_GAMES (0x5A8840)
#define OF_SAVE_GAME_FAILED_ERROR_IDS (0x5A88C2)
#define OF_GET_PLAYER_INSPECT_IMPL (0x54BAF0)
#define OF_GET_SHIP_ID (0x4C3E10)
#define OF_GET_FL_STRING (0x4347E0)
#define OF_RESOURCES_HANDLE (0x4347E0)
#define OF_CURRENT_SYSTEM_ID (0x673354)
#define OF_CURRENT_BASE_ID (0x673358)
#define OF_CURRENT_SHIP_ID (0x67337C)
#define OF_GAME_STARTED (0x67A7A4)
#define OF_INIT_MAIN_MENU (0x5B2BD2)
#define F_OF_DACOM_VERSION_MS_OFFSET (0x281D + 0xC00)
#define OF_TOUCHPAD_FIX (0x41FE8F)
#define CONNECT_TO_SERVER_FUNC (0x5AC710)
#define CONNECT_TO_SERVER_THIS (0x67E7B8)
#define OF_SERVER_LOG_FUNCTION_REF (0x40BDA2 + 1)
#define OF_MOUSE_Y_UPDATE (0x41FE78)
#define OF_MOUSE_X_UPDATE (0x41FE14)
#define MOUSE_X (*(int*) 0x616840)
#define MOUSE_Y (*(int*) 0x616844)
#define WINDOW_WIDTH (*(int*) 0x679BC8)
#define WINDOW_HEIGHT (*(int*) 0x679BCC)
+42
View File
@@ -0,0 +1,42 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
namespace patch {
void detour(unsigned char* pOFunc, void* pHkFunc, unsigned char* originalData, bool jmp)
{
DWORD dwOldProtection = 0; // Create a DWORD for VirtualProtect calls to allow us to write.
BYTE bPatch[5]; // We need to change 5 bytes and I'm going to use memcpy so this is the simplest way.
bPatch[0] = jmp ? 0xE9 : 0xE8; // Set the first byte of the byte array to the op code for the JMP or CALL instruction.
VirtualProtect((void*)pOFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtection); // Allow us to write to the memory we need to change
DWORD dwRelativeAddress = (DWORD)pHkFunc - (DWORD)pOFunc - 5; // Calculate the relative JMP address.
memcpy(&bPatch[1], &dwRelativeAddress, 4); // Copy the relative address to the byte array.
memcpy(originalData, pOFunc, 5);
memcpy(pOFunc, bPatch, 5); // Change the first 5 bytes to the JMP instruction.
VirtualProtect((void*)pOFunc, 5, dwOldProtection, &dwOldProtection); // Set the protection back to what it was.
}
void undetour(unsigned char* pOFunc, unsigned char* originalData)
{
DWORD dwOldProtection = 0; // Create a DWORD for VirtualProtect calls to allow us to write.
VirtualProtect((void*)pOFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtection); // Allow us to write to the memory we need to change
memcpy(pOFunc, originalData, 5);
VirtualProtect((void*)pOFunc, 5, dwOldProtection, &dwOldProtection); // Set the protection back to what it was.
}
void patch_bytes(unsigned int address, void* pData, unsigned int pSize)
{
DWORD dwOldProtection = 0;
VirtualProtect((void*)address, pSize, PAGE_READWRITE, &dwOldProtection);
memcpy((void*)address, pData, pSize);
VirtualProtect((void*)address, pSize, dwOldProtection, &dwOldProtection);
}
void set_execute_read_write(unsigned int address, unsigned int size)
{
DWORD dummy;
VirtualProtect((void*)address, size, PAGE_EXECUTE_READWRITE, &dummy);
}
}
+32
View File
@@ -0,0 +1,32 @@
#pragma once
namespace patch {
void detour(unsigned char* pOFunc, void* pHkFunc, unsigned char* originalData, bool jmp = true);
void undetour(unsigned char* pOFunc, unsigned char* originalData);
void patch_bytes(unsigned int address, void* pData, unsigned int pSize);
void set_execute_read_write(unsigned int address, unsigned int size);
inline void patch_uint32(unsigned int address, unsigned int data)
{
patch_bytes(address, (void*)&data, 4);
}
inline void patch_uint16(unsigned int address, unsigned short data)
{
patch_bytes(address, (void*)&data, 2);
}
inline void patch_uint8(unsigned int address, unsigned char data)
{
patch_bytes(address, (void*)&data, 1);
}
inline void patch_float(unsigned int address, float data)
{
patch_bytes(address, (void*)&data, 4);
}
inline void patch_x3(unsigned int address, unsigned char a, unsigned char b, unsigned char c)
{
unsigned char bytes[] = { a, b, c };
patch_bytes(address, (void*)bytes, 3);
}
}
+48
View File
@@ -0,0 +1,48 @@
#include "restart.h"
#include "offsets.h"
#include "config.h"
#include "patch.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <cstring>
DWORD loadSaveGameFuncAddr = 0;
char restartFileName[64];
bool __fastcall LoadSaveGame_Hook(PVOID thisptr, PVOID _edx, LPCSTR path, LPCSTR fileName)
{
if (_stricmp(fileName, restartFileName) == 0)
return false;
typedef bool __fastcall LoadSaveGame(PVOID, PVOID, LPCSTR, LPCSTR);
return ((LoadSaveGame*) loadSaveGameFuncAddr)(thisptr, _edx, path, fileName);
}
// Prevents crashes when FL loads a malformed (e.g. from another mod) Restart.fl file.
// This code makes it so that Restart.fl is recreated on every restart.
bool restart::init()
{
if (!config::get_config().alwaysregeneraterestartfile)
return true;
auto server = (DWORD) GetModuleHandleA("Server.dll");
if (!server)
server = (DWORD) LoadLibraryA("Server.dll");
if (!server)
return false;
UINT loadSaveGameHookPtr = (UINT) &LoadSaveGame_Hook;
UINT loadSaveGameCallAddr = server + F_OF_LOAD_SAVE_GAME_CALL;
loadSaveGameFuncAddr = server + F_OF_LOAD_SAVE_GAME;
patch::set_execute_read_write(server + F_OF_RESTART_NAME_PTR, 4);
patch::set_execute_read_write(server + F_OF_SAVE_FILE_FMT_PTR, 4);
// Dynamically obtain the name of the restart file
LPCSTR restartName = *((LPCSTR*) (server + F_OF_RESTART_NAME_PTR));
LPCSTR saveFileFmt = *((LPCSTR*) (server + F_OF_SAVE_FILE_FMT_PTR));
sprintf_s(restartFileName, sizeof(restartFileName), saveFileFmt, restartName);
patch::patch_uint32(loadSaveGameCallAddr, loadSaveGameHookPtr - loadSaveGameCallAddr - 4);
return true;
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
namespace restart {
bool init();
}
+127
View File
@@ -0,0 +1,127 @@
#include "savegame.h"
#include "config.h"
#include "patch.h"
#include "log.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <cstring>
#include <shlwapi.h>
#include <shlobj.h>
#include <io.h>
#include <direct.h>
void HandleUserDataPathFail(char * const outputBuffer, char * failedSavesDirectory)
{
static bool alreadyPrinted = false;
if (!alreadyPrinted) {
logger::writeformat("flplusplus: failed to access the saves directory for reading and writing (%s). Freelancer may not be able to properly load and store save files.", failedSavesDirectory);
alreadyPrinted = true;
}
*outputBuffer = '\0';
}
void WriteSaveDirSuccessMessage(const char* dir)
{
static bool alreadyPrinted = false;
if (!alreadyPrinted) {
logger::writeformat("flplusplus: using the following saves directory: \"%s\"", dir);
alreadyPrinted = true;
}
}
void WriteFallbackMessage()
{
static bool alreadyPrinted = false;
if (!alreadyPrinted) {
logger::writeline("flplusplus: saveindirectory option not set but trying to access the root SAVE directory regardless (fallback).");
alreadyPrinted = true;
}
}
void GetSavesInDirectoryPath(char * path)
{
GetModuleFileNameA(NULL, path, MAX_PATH);
PathRemoveFileSpecA(path);
PathAppendA(path, "..\\SAVE");
}
bool TryGetMyGamesPath(char * path)
{
if (SHGetFolderPathA(NULL, CSIDL_PERSONAL | CSIDL_FLAG_CREATE, NULL, 0, path) != S_OK) {
return false;
}
PathAppendA(path, "My Games");
if (_access(path, 6) != 0) {
if (_mkdir(path) != 0) {
return false;
}
}
PathAppendA(path, config::get_config().savefoldername.c_str());
if (_access(path, 6) != 0) {
if (_mkdir(path) != 0) {
return false;
}
}
return true;
}
bool UserDataPath(char * const outputBuffer)
{
char path[MAX_PATH];
if(config::get_config().saveindirectory) {
GetSavesInDirectoryPath(path);
} else {
if (!TryGetMyGamesPath(path)) {
HandleUserDataPathFail(outputBuffer, path);
// Fallback
WriteFallbackMessage();
GetSavesInDirectoryPath(path);
} else {
WriteSaveDirSuccessMessage(path);
strcpy_s(outputBuffer, MAX_PATH, path);
return true;
}
}
if (_access(path, 6) != 0) {
if (_mkdir(path) != 0) {
HandleUserDataPathFail(outputBuffer, path);
return false;
}
}
WriteSaveDirSuccessMessage(path);
strcpy_s(outputBuffer, MAX_PATH, path);
return true;
}
bool savegame::init()
{
HMODULE common = GetModuleHandleA("common.dll");
if (!common)
common = LoadLibraryA("common.dll");
if (!common)
return false;
auto *origFunc = (unsigned char*)GetProcAddress(common, "?GetUserDataPath@@YA_NQAD@Z");
if (!origFunc)
return false;
unsigned char buffer[5];
patch::detour(origFunc, (void*)UserDataPath, buffer);
return true;
}
void savegame::get_save_folder(char *buffer)
{
UserDataPath(buffer);
}
+6
View File
@@ -0,0 +1,6 @@
#pragma once
namespace savegame {
bool init();
void get_save_folder(char *buffer);
}
+290
View File
@@ -0,0 +1,290 @@
//Based off code by Laz
#include "screenshot.h"
#include "patch.h"
#include "offsets.h"
#include "config.h"
#include "log.h"
#include "Freelancer.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <ctime>
#include <string>
#include <shlwapi.h>
#include <shlobj.h>
#include <gdiplus.h>
#include <wchar.h>
#include <io.h>
#include <direct.h>
#include <algorithm>
#include <cstdlib>
using namespace Gdiplus;
#define MAX_SCREENSHOT_PATH_CHECK_ATTEMPTS 20
bool altFullscreenScreenshots = false;
bool altWindowedScreenshots = false;
void HandleScreenShotPathFail(char * const outputBuffer, char * failedScreenshotsDirectory)
{
logger::writeformat(
"flplusplus: failed to access the screenshots directory for reading and writing (%s). Freelancer may not be able to properly store screenshots.",
failedScreenshotsDirectory);
*outputBuffer = '\0';
}
void GetScInDirectoryPath(char * path)
{
GetModuleFileNameA(NULL, path, MAX_PATH);
PathRemoveFileSpecA(path);
PathAppendA(path, "..\\SCREENSHOTS");
}
bool TryGetScreenshotsPath(char * path)
{
if (SHGetFolderPathA(NULL, CSIDL_MYPICTURES | CSIDL_FLAG_CREATE, NULL, 0, path) != S_OK) {
return false;
}
PathAppendA(path, config::get_config().screenshotsfoldername.c_str());
if (_access(path, 6) != 0) {
if (_mkdir(path) != 0) {
return false;
}
}
return true;
}
bool ScreenShotPath(char * const outputBuffer)
{
char path[MAX_PATH];
if (config::get_config().screenshotsindirectory) {
GetScInDirectoryPath(path);
} else {
if (!TryGetScreenshotsPath(path)) {
HandleScreenShotPathFail(outputBuffer, path);
logger::writeline("flplusplus: screenshotsindirectory option not set but trying to access the root SCREENSHOTS directory regardless (fallback).");
// Fallback
GetScInDirectoryPath(path);
} else {
strcpy_s(outputBuffer, MAX_PATH, path);
return true;
}
}
if (_access(path, 6) != 0) {
if (_mkdir(path) != 0) {
HandleScreenShotPathFail(outputBuffer, path);
return false;
}
}
strcpy_s(outputBuffer, MAX_PATH, path);
return true;
}
std::wstring stows(const std::string& str)
{
return std::wstring(str.begin(), str.end());
}
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0; // number of image encoders
UINT size = 0; // size of the image encoder array in bytes
ImageCodecInfo* pImageCodecInfo = nullptr;
GetImageEncodersSize(&num, &size);
if (size == 0)
return -1; // Failure
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
if (pImageCodecInfo == nullptr)
return -1; // Failure
GetImageEncoders(num, size, pImageCodecInfo);
for (UINT j = 0; j < num; ++j)
{
if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
{
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j; // Success
}
}
free(pImageCodecInfo);
return -1; // Failure
}
void GetWindowSize(HWND flHWND, int& width, int& height)
{
RECT gameWindow;
GetClientRect(flHWND, &gameWindow);
ClientToScreen(flHWND, (LPPOINT) &gameWindow.left);
ClientToScreen(flHWND, (LPPOINT) &gameWindow.right);
width = gameWindow.right - gameWindow.left;
height = gameWindow.bottom - gameWindow.top;
}
std::wstring GetScreenshotOutPath(LPCWSTR directory, const std::wstring &baseFileName, int suffixIndex)
{
std::wstring fileName = baseFileName;
if (suffixIndex > 0) {
fileName += std::wstring(L"_") + std::to_wstring(suffixIndex);
}
fileName += std::wstring(L".png");
WCHAR cleanedFileName[MAX_PATH];
wcscpy_s(cleanedFileName, _countof(cleanedFileName), fileName.c_str());
PathCleanupSpec(directory, cleanedFileName);
return std::wstring(directory) + L'\\' + std::wstring(cleanedFileName);
}
static DWORD OnScreenshot()
{
char directoryA[MAX_PATH];
if(!ScreenShotPath(directoryA))
{
return DWORD(-1);
}
WCHAR directory[MAX_PATH];
size_t charsConverted;
mbstowcs_s(&charsConverted, directory, _countof(directory), directoryA, _countof(directory) - 1);
// TODO: Is this check volatile?
// What happens if the user turned fullscreen off via a third party app like Borderless Gaming.
// Will this be reflected in Freelancer's fullscreen flag?
// TODO: Also if fullscreen is true, it's possible that the user moved their window to a secondary monitor and then enabled fullscreen.
// Now the FL window is in fullscreen mode on the secondary monitor.
// When this happens, the code below will take a screenshot of the main monitor's display which won't show anything from the FL window.
// It should actually capture the fullscreen FL window on the secondary monitor.
// Test if you can move fullscreen window to other monitor and still take a screenshot (windows shift left arrow).
// Replace nullptr with GetActiveWindow, or GetForegroundWindow, or GetDesktopWindow, GetWindowDC. Test with broken DxWrapper version from the old FLSR release.
bool isFullscreen = (*((PBYTE) OF_FREELANCER_FULLSCREEN_FLAG) & 1) == 1;
bool useFullscreenScreenshotCode = isFullscreen;
if ((isFullscreen && altFullscreenScreenshots) || (!isFullscreen && altWindowedScreenshots))
useFullscreenScreenshotCode = !useFullscreenScreenshotCode;
HWND flHWND = useFullscreenScreenshotCode ? nullptr : *(HWND*) OF_FREELANCER_HWND;
// get the device context of FL's window
HDC hScreenDC = GetDC(flHWND);
// and a device context to put it in
HDC hMemoryDC = CreateCompatibleDC(hScreenDC);
int width, height;
if (useFullscreenScreenshotCode)
{
width = GetDeviceCaps(hScreenDC, HORZRES);
height = GetDeviceCaps(hScreenDC, VERTRES);
}
else
{
GetWindowSize(flHWND, width, height);
}
// maybe worth checking these are positive values
HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, width, height);
// get a new bitmap
HBITMAP hOldBitmap = HBITMAP(SelectObject(hMemoryDC, hBitmap));
BitBlt(hMemoryDC, 0, 0, width, height, hScreenDC, 0, 0, SRCCOPY);
hBitmap = (HBITMAP)SelectObject(hMemoryDC, hOldBitmap);
std::time_t rawtime;
std::tm* timeinfo;
WCHAR buffer[100];
std::time(&rawtime);
timeinfo = std::localtime(&rawtime);
std::wcsftime(buffer, 80, L"%Y-%m-%d_%H-%M-%S", timeinfo);
std::wstring fileName = std::wstring(buffer);
// If the player starts a game and then goes back to the main menu,
// the current system, base, and ship are not always reset.
// Hence, this code may obtain the incorrect names if the player is in the main menu.
// Therefore, only append the names if a game has started.
if (*(bool*) OF_GAME_STARTED)
{
std::wstring names[] = { GetSystemName(), GetBaseName(), GetShipName() };
for (const auto& name : names)
{
if (!name.empty())
fileName += L'_' + name;
}
}
int i = 0;
std::wstring outfile = GetScreenshotOutPath(directory, fileName, i);
while (PathFileExistsW(outfile.c_str()) && i < MAX_SCREENSHOT_PATH_CHECK_ATTEMPTS) {
i++;
outfile = GetScreenshotOutPath(directory, fileName, i);
}
if (i < MAX_SCREENSHOT_PATH_CHECK_ATTEMPTS)
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);
Bitmap* image = new Bitmap(hBitmap, nullptr);
CLSID myClsId;
GetEncoderClsid(L"image/png", &myClsId);
Status status = image->Save(outfile.c_str(), &myClsId, nullptr);
delete image;
GdiplusShutdown(gdiplusToken);
}
// clean up
DeleteDC(hMemoryDC);
ReleaseDC(flHWND, hScreenDC);
return DWORD(-1);
}
bool screenshot::init_png()
{
altFullscreenScreenshots = config::get_config().altfullscreenscreenshots;
altWindowedScreenshots = config::get_config().altwindowedscreenshots;
unsigned char buffer[5];
patch::detour((unsigned char*)OF_PRINTSCREEN, (void*)OnScreenshot, buffer);
return true;
}
bool screenshot::init_path()
{
HMODULE common = GetModuleHandleA("common.dll");
if (!common)
common = LoadLibraryA("common.dll");
if (!common)
return false;
auto* getScreenShotPath = (unsigned char*)GetProcAddress(common, "?GetScreenShotPath@@YA_NQAD@Z");
if (!getScreenShotPath)
return false;
unsigned char buffer[5];
patch::detour(getScreenShotPath, (void*)ScreenShotPath, buffer);
return true;
}
+6
View File
@@ -0,0 +1,6 @@
#pragma once
namespace screenshot {
bool init_png();
bool init_path();
}
+66
View File
@@ -0,0 +1,66 @@
#include "shippreviewscroll.h"
#include "offsets.h"
#include "patch.h"
#include "config.h"
#include <algorithm>
using namespace shippreviewscroll;
#define MIN_SCROLLING_SPEED 0.0f
#define MAX_SCROLLING_SPEED 50.0f
#define NN_SHIPTRADER_VFTABLE_ADDR (0x5D593C)
float scrollingSpeed;
float scrollMinDistance;
float scrollMaxDistance;
bool __fastcall ShipPreviewWindowScroll(ShipPreviewWindow* window, PVOID _edx, int scrollValue)
{
// The exact same ship preview element is used in other places as well,
// e.g. the FL beta inventory showed a top-down model of the player ship, but plugins can re-enable it.
// We don't want the zooming to work anywhere else other than in the ship dealer.
if (window->parent->vftable != NN_SHIPTRADER_VFTABLE_ADDR)
return false;
window->zoomLevel += scrollingSpeed * static_cast<float>(scrollValue);
// Zoom levels are always negative if you "zoom away" from the ship.
// If you want to zoom "through" the ship, it becomes positive.
window->zoomLevel = std::max<float>(-scrollMaxDistance, std::min<float>(-scrollMinDistance, window->zoomLevel));
// The scroll function should just return false
return false;
}
// TODO: Hook the "on-frame" update function and implement smooth scrolling (see Turret Zoom plugin)
// TODO: Allow the scrolling speed to be scaled based on the ship archetype (ini configurable)
// [ShipPreviewWindow* +0x32C] contains the ship archetype ID
// Alternatively, scale the speed based on the ship class. Array for 3 ships should be in [NN_ShipTrader* + 0x3EC]
// Player ship class: [NN_ShipTrader* + 0x3E8]
// Alternatively, scale by the ship's radius.
// [dalib engine + 0x88] = DALib::Engine::GetRadius(long engineIndex, BYTE unk, float* radius, Vector* vec)
// Engine index: [ShipPreviewWindow* +0x4B8]
void shippreviewscroll::init()
{
if (config::get_config().shippreviewscrollingspeed < MIN_SCROLLING_SPEED)
scrollingSpeed = MIN_SCROLLING_SPEED;
else if (config::get_config().shippreviewscrollingspeed > MAX_SCROLLING_SPEED)
scrollingSpeed = MAX_SCROLLING_SPEED;
else
scrollingSpeed = config::get_config().shippreviewscrollingspeed;
if (config::get_config().shippreviewscrollinginverse)
scrollingSpeed = -scrollingSpeed;
scrollMinDistance = config::get_config().shippreviewscrollingmindistance;
scrollMaxDistance = config::get_config().shippreviewscrollingmaxdistance;
if (scrollMaxDistance < scrollMinDistance)
scrollMaxDistance = scrollMinDistance;
// Every window in Freelancer has a virtual "scroll" function
// In the case of the ship preview window, this function does basically nothing
// We replace the pointer to this dummy function in the ship preview window's vftable with our own
patch::patch_uint32(OF_SHIP_PREVIEW_WINDOW_SCROLL, (UINT) ShipPreviewWindowScroll);
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
namespace shippreviewscroll
{
struct ShipPreviewParent
{
DWORD vftable;
};
struct ShipPreviewWindow
{
DWORD vftable;
ShipPreviewParent* parent;
BYTE x08[0x3E4];
float zoomLevel; // 0x3EC
};
void init();
}
+12
View File
@@ -0,0 +1,12 @@
#include "startlocation.h"
#include "offsets.h"
#include "patch.h"
#include "config.h"
void startlocation::init()
{
if (config::get_config().removestartlocationwarning) {
//Patch out "Failed to get start location" warning
patch::patch_uint8(OF_STARTLOCATION, 0xEB);
}
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
namespace startlocation {
void init();
}
+28
View File
@@ -0,0 +1,28 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "startup.h"
#include "patch.h"
#include "offsets.h"
#include "config.h"
bool startup::check_save_games_hook()
{
int originalIds = *((int*) OF_SAVE_GAME_FAILED_ERROR_IDS); // Save the original value
*((int*) OF_SAVE_GAME_FAILED_ERROR_IDS) = config::get_config().failedtoinitsavesdirids; // Overwrite the original value
// Call the original function
typedef bool (check_save_games)();
bool result = ((check_save_games*) OF_CHECK_SAVE_GAMES)();
*((int*) OF_SAVE_GAME_FAILED_ERROR_IDS) = originalIds; // Restore the original value
return result;
}
void startup::init()
{
BYTE originalBytes[5];
patch::set_execute_read_write(OF_SAVE_GAME_FAILED_ERROR_IDS, sizeof(UINT));
patch::detour((unsigned char*) OF_CHECK_SAVE_GAMES_CALL, (void*) startup::check_save_games_hook, originalBytes, false);
}
+4
View File
@@ -0,0 +1,4 @@
namespace startup {
void init();
bool check_save_games_hook();
}
+210
View File
@@ -0,0 +1,210 @@
#include "thnplayer.h"
#include "savegame.h"
#include "log.h"
#include "patch.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <shlwapi.h>
#include <shellapi.h>
#include <shlobj.h>
#include <flplusplus.h>
#include <stdlib.h>
#include <commdlg.h>
char *ScriptOverride = NULL;
static bool ParseArguments(LPWSTR* args, int numArgs, LPWSTR* ret)
{
for(int i = 0; i < numArgs; i++) {
if(wcscmp(args[i], L"-thn") == 0) {
if(i + 1 < numArgs && PathFileExistsW(args[i + 1])) {
*ret = _wcsdup(args[i + 1]);
}
return true;
}
}
return false;
}
const unsigned char MenuButtonsRemove[] = {
0xE8, 0x47, 0xE5, 0xEA, 0xFF, 0x83, 0x7C, 0x24, 0x50,
0x04, 0x77, 0x11, 0xDB, 0x05, 0x4C, 0x46, 0x57, 0x00,
0xEB, 0x0D
};
typedef bool (__fastcall *pBExit)(void*);
typedef bool (__fastcall *pBEnter)(void*,int,UINT);
typedef void (__cdecl *pUpdateTime)(double delta);
typedef void(__cdecl *pFlushConsole)(int ident, char* data, DWORD len);
void* FL = (void*)0x668708;
static pBEnter FL_BaseEnter = (pBEnter)0x43b290;
static pBExit FL_BaseExit = (pBExit)0x43b3e0;
static pUpdateTime UpdateTime;
static pFlushConsole FlushConsole = (pFlushConsole)0x46A150;
static unsigned char thornLoadData[5];
typedef void *(__cdecl *ScriptLoadPtr)(const char*);
static ScriptLoadPtr _ThornScriptLoad;
static bool firstLoad = true;
struct Chat {
UINT Type1;
UINT Len1;
UINT Mask;
UINT Data;
UINT Type2;
UINT Len2;
wchar_t Message[1024];
};
static void PrintText(const wchar_t *text)
{
Chat chat;
memset(&chat, 0, sizeof(Chat));
chat.Type1 = 0x1;
chat.Len1 = 0x8;
chat.Mask = 0xFFFFFF00;
chat.Data = 0xFFFFFF00;
chat.Type2 = 0x2;
chat.Len2 = wcslen(text) * 2 + 2;
wcscpy(chat.Message, text);
FlushConsole(0, (char*)&chat, chat.Len2 + 24);
}
void * __cdecl OnThornLoad(const char *script)
{
patch::undetour((unsigned char*)_ThornScriptLoad, thornLoadData);
logger::writeline(ScriptOverride ? (const char*)ScriptOverride : script);
void* retval = _ThornScriptLoad(ScriptOverride ? (const char*)ScriptOverride : script);
patch::detour((unsigned char*)_ThornScriptLoad, (void*)OnThornLoad, thornLoadData);
if(firstLoad) {
PrintText(L"THN Player");
PrintText(L"F5 - Refresh");
PrintText(L"F9 - Open");
PrintText(L"Esc - Exit");
firstLoad = false;
}
return retval;
}
static void SceneReload()
{
FL_BaseExit(FL);
FL_BaseEnter(FL, 0, 0xA3BC3888); //intro1_base
}
static int reloadFrames = -1;
static void OpenFile()
{
OPENFILENAME ofn; // common dialog box structure
char szFile[260]; // buffer for file name
// Initialize OPENFILENAME
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = NULL;
ofn.lpstrFile = szFile;
// Set lpstrFile[0] to '\0' so that GetOpenFileName does not
// use the contents of szFile to initialize itself.
ofn.lpstrFile[0] = '\0';
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFilter = "THN\0*.THN;*.LUA\0All\0*.*\0";
ofn.nFilterIndex = 0;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;
// Display the Open dialog box.
if (GetOpenFileName(&ofn)==TRUE) {
logger::writeline("Opening");
logger::writeline(szFile);
ScriptOverride = _strdup(szFile);
reloadFrames = 4;
}
}
unsigned char updateData[5];
static void Update(const double delta)
{
if((GetAsyncKeyState(VK_F5) & 1)) {
SceneReload();
}
if((GetAsyncKeyState(VK_F9) & 1)) {
OpenFile();
}
// Skip hitching
if(reloadFrames > 0) {
reloadFrames--;
} else if(reloadFrames == 0) {
reloadFrames = -1;
SceneReload();
}
patch::undetour((unsigned char*)UpdateTime, updateData);
UpdateTime(delta);
patch::detour((unsigned char*)UpdateTime, (void*)Update, updateData);
}
bool thnplayer::init()
{
int numArgs;
LPWSTR *args = CommandLineToArgvW(GetCommandLineW(), &numArgs);
if (!args)
return false;
LPWSTR thnscript = NULL;
if(ParseArguments(args, numArgs, &thnscript)) {
HMODULE freelancer = GetModuleHandleA("freelancer.exe");
HMODULE common = GetModuleHandleA("common.dll");
if (!common)
common = LoadLibraryA("common.dll");
pUpdateTime updateTime = common
? (pUpdateTime)GetProcAddress(common, "?UpdateGlobalTime@Timing@@YAXN@Z")
: nullptr;
ScriptLoadPtr thornScriptLoad = common
? (ScriptLoadPtr)GetProcAddress(common, "?ThornScriptLoad@@YAPAUIScriptEngine@@PBD@Z")
: nullptr;
if (!freelancer || !updateTime || !thornScriptLoad) {
free((void*)thnscript);
LocalFree(args);
return false;
}
if(thnscript) {
char buffer[500];
wcstombs(buffer, thnscript, 500);
free((void*)thnscript);
ScriptOverride = _strdup(buffer);
}
DWORD FL = (DWORD)freelancer;
//Remove all menu buttons
patch::patch_bytes(FL + 0x174634, (void*)MenuButtonsRemove, 20);
patch::patch_uint16(FL + 0x1746CA, 0x0);
patch::patch_uint16(FL + 0x174707, 0x0);
patch::patch_uint16(FL + 0x174744, 0x0);
patch::patch_uint16(FL + 0x174781, 0x0);
patch::patch_uint16(FL + 0x1747BE, 0x0);
patch::patch_bytes(FL + 0x1E23DC, (void*)"null", 5); //Disable ui_motion_swish
//Remove version text
patch::patch_uint32(FL + 0x16DDEC, 0x1);
patch::patch_uint32(FL + 0x174890, 0x1);
//Remove logo
patch::patch_uint32(FL + 0x1E266C, 0x0);
//Persistent text
patch::patch_uint32(FL + 0x0691CA, 0x7FFFFFFE);
//Hook update
UpdateTime = updateTime;
patch::detour((unsigned char*)UpdateTime, (void*)Update, updateData);
//Permanent hook load function
_ThornScriptLoad = thornScriptLoad;
patch::detour((unsigned char*)_ThornScriptLoad, (void*)OnThornLoad, thornLoadData);
}
LocalFree(args);
return true;
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
namespace thnplayer {
bool init();
}
+18
View File
@@ -0,0 +1,18 @@
#include "touchpad.h"
#include "offsets.h"
#include "patch.h"
#include "config.h"
// TODO: There's a bug in FL where if you play the game on a system that has a touchpad (laptop for instance),
// then scrolling will not work at all. If you use that touchpad to scroll, then it appears that you scroll infinitely.
// When this happens, toggling engine kill doesn't seem to work anymore either.
// This function applies a patch that makes scrolling with a touchpad behave as you'd expect.
// However, it completely breaks normal mouse wheel scrolling.
// It'd be nice if a solution could be implemented that fixes touchpad scrolling without breaking mouse wheel scrolling.
void touchpad::init()
{
if (config::get_config().touchpadsupport) {
// Fix touchpad scrolling but break normal mouse wheel scrolling
patch::patch_uint8(OF_TOUCHPAD_FIX, 0x00);
}
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
namespace touchpad {
void init();
}