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();
}
+52
View File
@@ -0,0 +1,52 @@
EXPORTS
??0INI_Reader@@QAE@XZ
??1INI_Reader@@QAE@XZ
?open@INI_Reader@@QAE_NPBD_N@Z
?read_header@INI_Reader@@QAE_NXZ
?is_header@INI_Reader@@QAE_NPBD@Z
?get_header_ptr@INI_Reader@@QAEPBDXZ
?read_value@INI_Reader@@QAE_NXZ
?is_value@INI_Reader@@QAE_NPBD@Z
?get_value_string@INI_Reader@@QAEPBDI@Z
?get_value_bool@INI_Reader@@QAE_NI@Z
?get_value_float@INI_Reader@@QAEMI@Z
?get_value_int@INI_Reader@@QAEHI@Z
?get_name_ptr@INI_Reader@@QAEPBDXZ
?get_file_name@INI_Reader@@QBEPBDXZ
?close@INI_Reader@@QAEXXZ
?FindFirst@CEquipManager@@QBEPBVCEquip@@I@Z
?get_throttle@CShip@@QBEMXZ
?is_using_tradelane@CShip@@QBE_NXZ
?shiparch@CShip@@QBEPBUShip@Archetype@@XZ
?get_group_name@CShip@@QBEIXZ
?is_enemy@CShip@@QAE_NPAUIObjInspect@@@Z
?get_radius@EngineObject@@QBE?BMXZ
?get_orientation@EngineObject@@QBEABVMatrix@@XZ
?IsTriggered@FuseAction@@UBE_NXZ
?cast@CEEngine@@SAPBV1@PBVCEquip@@@Z
?is_base@CEqObj@@QBE_NXZ
?is_dynamic@CSolar@@QBE_NXZ
?is_waypoint@CSolar@@QBE_NXZ
?solararch@CSolar@@QBEPBUSolar@Archetype@@XZ
?CheckForSync@CRemotePhysicsSimulation@@QAE_NABVVector@@0ABVQuaternion@@@Z
?SinglePlayer@@YA_NXZ
?GetProjectilesPerFire@CELauncher@@QBEIXZ
?get_script_index@Root@Archetype@@QBEHXZ
?get_archetype@CObject@@QBEPAURoot@Archetype@@XZ
?IsMPServer@@YA_NXZ
??0CEquipTraverser@@QAE@H@Z
?Traverse@CEquipManager@@QAEPAVCEquip@@AAVCEquipTraverser@@@Z
?cast@CELightEquip@@SAPAV1@PAVCEquip@@@Z
?GetBehaviorManager@@YAPAVIBehaviorManager@@PAUIObjRW@@@Z
?CreateID@@YAIPBD@Z
?GetInfocard@Vibe@Reputation@@YAHABHAAI@Z
?get_undamaged_collision_group_list@EqObj@Archetype@@QBE_NAAV?$list@UCollisionGroupDesc@@V?$allocator@UCollisionGroupDesc@@@std@@@std@@@Z
?GetShip@Archetype@@YAPAUShip@1@I@Z
?find_by_id@GoodList@@YAPBUGoodInfo@@I@Z
+2
View File
@@ -0,0 +1,2 @@
EXPORTS
?Shutdown@CGunWrapper@@SAXXZ
+3
View File
@@ -0,0 +1,3 @@
EXPORTS
DACOM_GetDllVersion
FDUMP
+386
View File
@@ -0,0 +1,386 @@
#pragma once
#include "fl_math.h"
#include "vftable.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <list>
#include "st6.h"
#define IMPORT __declspec(dllimport)
#define ENGINE_TYPE 0x20000
IMPORT UINT CreateID(LPCSTR str);
class INI_Reader
{
public:
IMPORT INI_Reader();
IMPORT ~INI_Reader();
IMPORT bool open(LPCSTR path, bool throwExceptionOnFail = false);
IMPORT bool read_header();
IMPORT bool is_header(LPCSTR name);
IMPORT LPCSTR get_header_ptr();
IMPORT bool read_value();
IMPORT bool is_value(LPCSTR name);
IMPORT LPCSTR get_value_string(UINT index = 0);
IMPORT bool get_value_bool(UINT index = 0);
IMPORT float get_value_float(UINT index = 0);
IMPORT int get_value_int(UINT index = 0);
IMPORT LPCSTR get_name_ptr();
IMPORT LPCSTR get_file_name() const;
IMPORT void close();
// inline UINT get_value_uint(UINT index = 0)
// {
// return static_cast<UINT>(get_value_float(index));
// }
inline UINT get_value_id(UINT index = 0)
{
return CreateID(get_value_string(index));
}
private:
BYTE data[0x1565];
};
class IMPORT CEquip
{
public:
FILL_VFTABLE(0);
FILL_VFTABLE(1);
FILL_VFTABLE(2);
virtual bool Activate(bool value);
};
class IMPORT CELightEquip : public CEquip
{
public:
static CELightEquip * cast(CEquip * equip);
};
class CELauncher
{
public:
IMPORT UINT GetProjectilesPerFire() const;
UINT GetProjectilesPerFire_Hook() const;
};
class IMPORT CEquipTraverser
{
public:
CEquipTraverser(int equipClass);
private:
BYTE data[0x10];
};
class IMPORT CEquipManager
{
public:
CEquip const * FindFirst(UINT type) const;
CEquip * Traverse(CEquipTraverser& equipTraverser);
private:
BYTE x00[0x20];
};
struct CollisionGroupDesc
{};
namespace Archetype
{
struct Root
{
BYTE data[0x44];
int scriptIndex; // 0x44
IMPORT int get_script_index() const;
};
struct EqObj
{
BYTE x00[0x14];
UINT idsName; // 0x14
UINT idsInfo; // 0x18
// st6::list
IMPORT bool get_undamaged_collision_group_list(std::list<CollisionGroupDesc>& colGroupList) const;
bool get_undamaged_collision_group_list_Hook(std::list<CollisionGroupDesc>& colGroupList) const;
};
struct Ship : public EqObj
{
BYTE x1C[0xEC];
Vector angularDrag; // 0x108
Vector steeringTorque; // 0x114
};
IMPORT Ship* GetShip(UINT shipId);
struct Solar : public EqObj
{
};
struct ShieldGenerator
{
BYTE x00[0x94];
float maxCapacity; // 0x94
BYTE x98[0x8];
float offlineThreshold; // 0xA0
};
}
class IMPORT EngineObject
{
public:
float const get_radius() const;
Matrix const & get_orientation() const;
long engineInstance; // 0x04
BYTE x08[0x44];
private:
FILL_VFTABLE(0);
FILL_VFTABLE(1);
FILL_VFTABLE(2);
FILL_VFTABLE(3);
};
struct IMPORT CObject : public EngineObject
{
Archetype::Root* get_archetype() const;
DWORD classType; // 0x4C
};
struct IMPORT CSimple : CObject
{
BYTE x50[0x60];
UINT nickname; // or simpleId, 0xB0
};
class CAttachedEquip
{
FILL_VFTABLE(0);
FILL_VFTABLE(1);
FILL_VFTABLE(2);
FILL_VFTABLE(3);
FILL_VFTABLE(4);
FILL_VFTABLE(5);
FILL_VFTABLE(6);
FILL_VFTABLE(7);
virtual void Vftable_x80();
public:
virtual long GetRootIndex() const;
CObject* parent; // x04
};
struct IMPORT CEqObj : public CSimple
{
private:
FILL_VFTABLE(4);
FILL_VFTABLE(5);
FILL_VFTABLE(6);
FILL_VFTABLE(7);
virtual void Vftable_x80();
virtual void Vftable_x84();
BYTE xB4[0x30];
public:
CEquipManager equipManager; // 0xE4
BYTE x104[0x5C];
UINT baseId; // 0x160
virtual UINT get_name() const; // 0x88
bool is_base() const;
};
struct IObjInspect;
#define CSHIP_CLASS_TYPE 0x503
struct CShip : public CEqObj
{
BYTE x164[0x50];
DWORD groupId; // 0x1B4
IMPORT float get_throttle() const;
IMPORT Archetype::Ship const * shiparch() const;
IMPORT bool is_using_tradelane() const;
IMPORT UINT get_group_name() const;
IMPORT bool is_enemy(IObjInspect *obj);
UINT get_group_name_Hook() const;
bool is_enemy_Hook(IObjInspect *obj);
};
#define CSOLAR_CLASS_TYPE 0x303
struct CSolar : public CEqObj
{
IMPORT bool is_dynamic() const;
IMPORT bool is_waypoint() const;
IMPORT Archetype::Solar const * solararch() const;
static inline const CSolar* cast(const CObject& obj)
{
if ((obj.classType & CSOLAR_CLASS_TYPE) == CSOLAR_CLASS_TYPE)
return (const CSolar*) &obj;
return nullptr;
}
};
inline bool IsObjectAWaypoint(const CObject& cobject)
{
const CSolar* solar = CSolar::cast(cobject);
if (!solar)
return false;
return solar->is_waypoint();
}
class IMPORT FuseAction
{
public:
virtual void Dealloc(bool unk);
virtual bool IsTriggered() const;
};
class IMPORT CEEngine : public FuseAction
{
public:
static CEEngine const * cast(CEquip const * equip);
};
class CRemotePhysicsSimulation
{
public:
IMPORT bool CheckForSync(Vector const &shipPos, Vector const &shipPos2, Quaternion const &unk);
bool CheckForSync_Hook(const CShip& ship, Vector const &shipPos, Quaternion const &unk);
};
struct IObjRW // : public IObjInspectImpl
{
BYTE x04[0xC];
CObject* cobject; // 0x10
BYTE x14[0x8];
int unk_x1C; // 0x1C
BYTE x20[0x16C];
DWORD flags; // 0x18C
FILL_VFTABLE(0)
FILL_VFTABLE(1)
virtual UINT get_simple_id() const; // 0x20
virtual void Vftable_x24();
virtual void Vftable_x28();
virtual void Vftable_x2C();
FILL_VFTABLE(3)
FILL_VFTABLE(4)
FILL_VFTABLE(5)
virtual void Vftable_x60();
virtual void Vftable_x64();
virtual int get_attitude_towards(float &attitude, IObjRW const *other) const; // 0x68
virtual void Vftable_x6C();
virtual void Vftable_x70();
virtual int get_target(const IObjRW *&target) const; // 0x74
virtual void Vftable_x78();
virtual void Vftable_x7C();
FILL_VFTABLE(8)
FILL_VFTABLE(9)
FILL_VFTABLE(A)
virtual void Vftable_xB0();
virtual void Vftable_xB4();
virtual void Vftable_xB8();
virtual bool is_player() const; // 0xBC
inline bool SentTradeRequest() const
{
#define TRADE_REQUEST_FLAGS 0x4
return (flags & TRADE_REQUEST_FLAGS) != 0;
}
};
struct IObjInspect : public IObjRW
{
};
struct PhysicsInfo
{
BYTE x00[0x2F];
bool autoLevel; // 0x2F
};
class IBehaviorManager
{
public:
BYTE x00[0x08];
PhysicsInfo* physicsInfo; // 0x08
BYTE x0C[0xED];
bool rotationLock; // 0xF9
};
IMPORT IBehaviorManager* GetBehaviorManager(IObjRW *iObjRw);
struct ID_String
{
UINT ids;
};
IMPORT bool SinglePlayer();
IMPORT bool IsMPServer();
namespace Reputation
{
namespace Vibe
{
IMPORT int GetInfocard(int const& id, unsigned int& idsInfo);
}
}
struct EquipDesc
{
DWORD x00;
UINT archId; // 0x4
};
class EquipDescList
{
public:
#ifdef USE_ST6
st6
#else
std
#endif
::list<EquipDesc> list; // 0x0
};
enum GoodType : DWORD
{
Commodity = 0,
Hull = 2,
Ship = 3
};
struct GoodInfo
{
BYTE x00[0x4C];
GoodType type; // 0x4C
BYTE x50[0x4];
UINT shipId; // only if type = Hull, 0x54
BYTE x58[0x38];
UINT shipHullId; // only if type = Ship, 0x90
EquipDescList equipDescLists[3]; // 0x94
};
namespace GoodList
{
IMPORT GoodInfo const * find_by_id(UINT id);
};
+15
View File
@@ -0,0 +1,15 @@
#pragma once
#define IMPORT __declspec(dllimport)
class CGunWrapper
{
public:
IMPORT static void __cdecl Shutdown();
};
class CDPClient
{
};
#define FL_CDP_CLIENT ((CDPClient*) 0x67E7BC)
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define IMPORT __declspec(dllimport)
enum DumpSeverity : DWORD
{
SEV_ERROR = 0x100001,
SEV_WARNING = 0x100002,
SEV_NOTICE = 0x100003
};
typedef int (*FDUMP_HANDLER)(DumpSeverity severity, LPCSTR fmt, ...);
extern "C"
{
#ifndef ASM_FDUMP
IMPORT FDUMP_HANDLER FDUMP;
#endif
IMPORT int DACOM_GetDllVersion(LPCSTR dllPath, UINT32& major, UINT32& minor, UINT32& build);
}
+178
View File
@@ -0,0 +1,178 @@
#pragma once
#include "Common.h"
#define PLAYER_SYSTEM *((PUINT) 0x673354)
#define CHECK_FOR_SYNC_CALL_ADDR 0x541602
#define PUSH_SHIP_POS_SYNC_CHECK_ADDR 0x5415FF
#define POST_INIT_DEALLOC_CALL_ADDR 0x54B8B9
#define OBJ_UPDATE_CALL_ADDR 0x54167C
#define WAYPOINT_CHECK_CALL_ADDR 0x4F4141
#define INIT_NN_ELEMENTS_CALL_ADDR 0x5D4A80
#define TEST_RESOLUTIONS_ADDR 0x4B2440
// The buffer length is denoted in WORDs.
#define FL_BUFFER_1 ((LPWSTR) 0x66DC60)
#define FL_BUFFER_2 ((LPWSTR) 0x66FC60)
#define FL_BUFFER_LEN *((PUINT) 0x6119F8)
#define FL_RESOURCES_HANDLE *((PDWORD) 0x67ECA8)
#define UNKNOWN_OBJECT_IDS 1191
#define WAYPOINT_IDS 1090
#define MISSION_WAYPOINT_IDS 1091
#define KNOW_VISIT_FLAG (1)
#define LAND_VISIT_FLAG (1 << 1)
#define COMMODITY_DEALER_VISIT_FLAG (1 << 2)
#define EQUIPMENT_DEALER_VISIT_FLAG (1 << 3)
#define SHIP_DEALER_VISIT_FLAG (1 << 4)
// Time elapsed since startup in miliseconds
#define FL_TIME_ELAPSED_MS (*(double*) 0x667D38)
// System time in miliseconds
#define TIMING_DELTA_TICK_COUNT (*(PDWORD) 0x667D14)
#define TIME_GET_TIME_VAL (*(PDWORD) 0x667D20)
struct Waypoint
{
Vector pos;
UINT system;
UINT target;
int waypointNumber;
};
struct NavMapObj
{
UINT type;
};
struct NeuroNetNavMap
{
NavMapObj* GetHighlightedObject_Hook(DWORD unk1, DWORD unk2);
NavMapObj* GetHighlightedObject(DWORD unk1, DWORD unk2);
};
struct AudioOption
{
UINT idsName;
UINT idsTooltip;
UINT defaultVolume;
DWORD x0C, x10, x14;
};
#define NN_PREFERENCES_NEW_DATA 0x98C
// 0x330 = current selected width
// 0x8b8 = current active width (int)
// 0x8cc = start of resolution array (10 * 4 * 3 bytes)
// 0x8d4 = start of resolution array + 0x8 (points to the bpp of the first element)
// 0x944 = array of 10 bytes that contains flags of whether the resolution index is supported (1 = supported, 0 = unsupported)
// 0x94e = unallocated word (2 bytes)
// 0x950 = amount of supported resolutions (integer)
// 0x954 = array of 4 * 10 bytes that contains the indices of the resolutions in the selection menu (-1 is unsupported resolution)
struct NN_Preferences
{
BYTE x00[0x528];
AudioOption* audioOptions; // pointer to array of audio info from up to 14 UI scroll elements
BYTE x52C[0x128];
PVOID scrollElements[14]; // 0x654, array of pointers to 14 volume scroll elements (there's more but we only need up to 14)
BYTE x68C[0x2C4];
UINT supportedResAmount;
BYTE x954[0x28];
bool unk_x97C;
BYTE x97D[0x3];
UINT selectedHeight;
UINT activeHeight;
bool* resSupportedArr; // Points to new version of 0x944
BYTE newData;
bool InitElements_Hook(DWORD unk1, DWORD unk2);
bool SetResolution_Active_Hook(UINT width, DWORD unk);
bool SetResolution_Selected_Hook(UINT width, DWORD unk);
void TestResolutions_Hook(DWORD unk);
void VolumeSliderAdjustEnd_Hook(PVOID scrollElement);
bool SetResolution(UINT width, DWORD unk, UINT height);
};
void StopSound(BYTE soundId);
void StartSound(BYTE soundId);
Waypoint* GetWaypoint(int index);
struct WaypointWatcher
{
bool GetCurrentWaypointInfo(bool& isPlayerWaypoint, int& waypointIndex);
};
#define WAYPOINT_WATCHER (*((WaypointWatcher**) 0x674BC8))
IObjRW* GetPlayerIObjRW();
CShip* GetPlayerShip();
CShip* GetPlayerShipSafe();
bool AreIObjRWsInSameGroup(const IObjRW& o1, const IObjRW& o2);
bool AreShipsInSameGroup(const CShip* ship1, const CShip* ship2);
bool IsSimpleUnvisited(const CSimple& simple);
BYTE GetSimpleVisitedValue(const CSimple& simple);
UINT GetIdsForUnvisitedSimple(const CSimple& simple);
UINT GetCShipOrCEqObjName(const CEqObj &eqObj);
UINT GetFlStringFromResources(DWORD resourcesHandle, UINT ids, LPWSTR buffer, UINT bufferLen);
inline UINT GetFlString(UINT ids, LPWSTR buffer, UINT bufferLen)
{
return GetFlStringFromResources(FL_RESOURCES_HANDLE, ids, buffer, bufferLen);
}
class RenderDisplayList
{};
void AppendXmlWsToRdlEx(LPCWSTR ws, UINT wsLen, RenderDisplayList& rdl, DWORD flags);
inline void AppendXmlWsToRdl(LPCWSTR ws, RenderDisplayList& rdl)
{
AppendXmlWsToRdlEx(ws, wcslen(ws), rdl, 0);
}
#define SHIP_TRADER_SHIP_AMOUNT 3
#define SHIP_TRADER_PLAYER_SHIP_INDEX -2
#define SHIP_TRADER_NONE_SELECTED_INDEX -1
// 0x370 = ShipTrader3DShip*
struct NN_ShipTrader
{
BYTE x00[0x3CC];
int shipCount; // 0x3CC
int selectedShipIndex; // 0x3D0
BYTE x3D4[0x24];
float playerReputationWithBaseOwners; // 0x3F8
int shipStatuses[SHIP_TRADER_SHIP_AMOUNT]; // 0x3FC, basically enums for available, rep too low, or level too low
BYTE x408[0x40];
int shipRepPercentages[SHIP_TRADER_SHIP_AMOUNT]; // 0x448
void StoreShipRepRequirement(int shipIndex, float repRequirement);
LPWSTR PrintFmtShipRepRequirement();
PBYTE SwapShipRepPercentages(PBYTE rhsShipStatusAddr);
};
void ExpandNNShipTraderObjMemory();
struct FLCursor
{
float xPos, yPos, distFromZero;
};
struct ServerFilterDialog
{
bool OnFrameUpdate_Hook();
};
double GetDeltaTime();
void UpdateDeltaTime();
void UpdateDeltaTimeAndUpTime();
UINT GetNumOfActiveMissionObjectives();
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "vftable.h"
struct SSPObjUpdateInfo
{
BYTE x00[40];
float throttle; // 0x28
};
class IServerImpl {
public:
FILL_VFTABLE(0)
FILL_VFTABLE(1)
FILL_VFTABLE(2)
FILL_VFTABLE(3)
FILL_VFTABLE(4)
FILL_VFTABLE(5)
FILL_VFTABLE(6)
FILL_VFTABLE(7)
FILL_VFTABLE(8)
FILL_VFTABLE(9)
FILL_VFTABLE(A)
FILL_VFTABLE(B)
FILL_VFTABLE(C)
virtual void SPObjUpdate(SSPObjUpdateInfo &updateInfo, UINT client);
void SPObjUpdate_Hook(const CShip& ship, SSPObjUpdateInfo &updateInfo, UINT client);
};
+15
View File
@@ -0,0 +1,15 @@
#pragma once
struct Alchemy
{
float progress;
void* effect;
};
struct AleLoop
{
int startOffset;
unsigned char maxProgressOffset;
};
void InitAlchemyCrashFix();
+10
View File
@@ -0,0 +1,10 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
struct BaseInfoCat
{
DWORD headerStyleAddr;
DWORD headerNamePrintAddr;
};
void InitBaseInfoSpacingFix();
+3
View File
@@ -0,0 +1,3 @@
#pragma once
void InitBlankFactionNameFix();
+72
View File
@@ -0,0 +1,72 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#ifdef USE_ST6
#include "st6.h"
#else
#include <list>
namespace st6 = std;
#endif
#define FASTCALL __fastcall
struct PlayerData
{
BYTE x00[0x264];
UINT currentShipId; // 0x264
BYTE x268[0xBC];
UINT shipIdOnLand; // 0x324
};
struct BaseGood
{
BYTE x00[0x8];
UINT goodId; // 0x8
float price; // 0xC
int minQuantity; // 0x10
int maxQuantity; // 0x14
DWORD unk_x18; // 0x18
inline bool IsShipCandidate() const
{
return unk_x18 == 0 || unk_x18 == 2;
}
};
struct BaseGoodIt
{
BaseGood* good; // 0x0
void Advance();
};
struct BaseGoodCollection
{
UINT baseName; // 0x0
UINT launchpadName; // 0x4
DWORD unk_x08; // 0x8
float unk_x0C; // 0xC
st6::list<BaseGood> goods; // 0x10
bool HasShipPackageWithGood(UINT goodId);
};
struct MarketGood
{
BYTE x00[0x10];
DWORD type; // 0x10
};
struct BaseMarket
{
UINT baseName; // 0x0
BaseGoodCollection* baseGoods; // 0x4
const MarketGood* GetSoldGood(UINT goodId) const;
};
const MarketGood* FASTCALL GetGoodSoldByBaseOrPartOfShip(const BaseMarket &baseMarket, const PlayerData &playerData, UINT goodId);
void InitShipBuyKickFix();
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "feature_config.h"
void ReadConfig(LPCSTR path, FeatureManager &manager);
+72
View File
@@ -0,0 +1,72 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "vftable.h"
#include "Common.h"
#ifdef USE_ST6
#include "st6.h"
#else
#include <vector>
namespace st6 = std;
#endif
struct InputChar
{
WCHAR c;
DWORD flags; // I don't know whether this field actually represents flags; it's just an educated guess.
DWORD unk; // Allocated but never assigned.
};
struct KeyMapInfo
{
BYTE x00[0x8];
DWORD controlCharacterFlags; // 0x8
DWORD x0C;
WCHAR enteredKey; // 0x10
inline bool IsCtrlPressed() const
{
return (controlCharacterFlags & 4) == 4;
}
};
struct InputBoxWindow
{
BYTE x04[0x498];
int pos; // 0x49C
BYTE x4A0[0x24];
st6::vector<InputChar> chars; // 0x4C4
BYTE x4D4[0x3C];
int maxCharsLength; // 0x510
BYTE x514[0x14];
WCHAR forbiddenChar; // 0x528
bool noForbiddenChar; // 0x52A
PDWORD ime; // 0x52C
FILL_VFTABLE(0)
FILL_VFTABLE(1)
FILL_VFTABLE(2)
FILL_VFTABLE(3)
FILL_VFTABLE(4)
FILL_VFTABLE(5)
FILL_VFTABLE(6)
FILL_VFTABLE(7)
FILL_VFTABLE(8)
FILL_VFTABLE(9)
FILL_VFTABLE(A)
FILL_VFTABLE(B)
virtual void Vftable_xC0();
virtual void Vftable_xC4();
virtual bool WriteTypedKey(const KeyMapInfo& kmi);
void HandleCopyPaste(const KeyMapInfo& kmi);
void CopyToClipboard();
void CopyFromClipboard();
void WriteString(LPCWSTR str);
};
void HandleDefaultInputKey_Hook();
void InitCopyPasteFeature();
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "Common.h"
// Read from the [Cursor] values in DATA\mouse.ini.
// Constructor: 0x41E550
struct MouseCursor
{
UINT32 nicknameLen; // 0x0
char nickname[32]; // 0x4
PDWORD unk_0x24; // 0x24
UINT32 animNameLen; // 0x28
char animName[24]; // 0x2C
float hotspotX; // 0x44
float hotspotY; // 0x48
DWORD color; // rgba, 0x4C
BYTE x50[0x10];
int animValue1; // 0x60
int animState; // should be preserved when copying, 0x64
BYTE x68[0x28];
};
struct Targetable_Objects
{
BYTE x00[0x3F0];
const CSimple* selectedSimple; // 0x3F0
BYTE x3F4[0x534];
bool isAimLocking; // 0x928
void UpdateTargeting_Hook();
void UpdateTargeting();
};
void InitMoreCursorColors();
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define UI_ELEMENT_VISIBLE 0x3
struct ManeuverFrame
{
BYTE x00[0x6C];
BYTE flags; // 0x6C
};
struct NavBar
{
BYTE x00[0x3D8];
ManeuverFrame* maneuverFrame; // 0x3D8
BYTE x3DC[0x4];
PVOID unkUiElement; // 0x3E0
bool shipDealerMenuOpened; // 0x3E4
void SetHotspot_Hook(PVOID hotspot);
};
struct DealerOpenCamera
{
BYTE x00[0x1338];
bool animationInProgress; // 0x1338
bool StartAnimation(LPCSTR name, PVOID unk, NavBar* navBar, DWORD unk2);
bool StartAnimation_Hook(LPCSTR name, PVOID unk, NavBar* navBar, DWORD unk2);
};
void InitDealerOpenFix();
void InitDealerCrashFix();
+3
View File
@@ -0,0 +1,3 @@
#pragma once
void InitMissingDllCrashFix();
+6
View File
@@ -0,0 +1,6 @@
#pragma once
void InitPostGameDeadlockFix();
void InitQuitMessageFix();
void CleanupQuitMessageFix();
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <string.h>
#include <map>
struct FlSharpFeature
{
void (*initFunc)(); // feature's init function
void (*cleanupFunc)(); // feature's cleanup function
bool (*applyPredicate)(); // function that determines whether the feature must be applied from a technical perspective
bool enabled; // value determined by the user so they can choose whether they want it to be applied
};
class FeatureManager
{
public:
void RegisterFeature(LPCSTR name, void (*initFunc)(), void (*cleanupFunc)(), bool (*applyPredicate)());
bool SetFeatureEnabled(LPCSTR name, bool enabled);
void InitFeatures();
void CleanupFeatures();
private:
std::map<UINT, FlSharpFeature> features;
};
bool ApplyAlways();
bool ApplyOnlyOnClient();
bool ApplyOnlyOnServer();
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#define NAKED __declspec(naked)
#define NOINLINE __declspec(noinline)
#define FL_FUNC(func, addr) \
NAKED NOINLINE func \
{ \
__asm mov eax, addr \
__asm jmp eax \
}
+35
View File
@@ -0,0 +1,35 @@
#pragma once
// NOTE: This only works when loaded via Freelancer.exe, not FLServer.exe.
#define FL_180_OVER_PI (*(float*) 0x5D3D38)
class Vector
{
public:
float x, y, z;
};
class Quaternion
{
public:
float w, x, y, z;
};
class Matrix
{
public:
float data[3][3];
};
float GetRotationDelta(const Quaternion& quat, const Matrix& rot);
Quaternion MatrixToQuaternion(const Matrix& m);
#ifdef _MSC_VER
#if _MSC_VER < 1700
inline float copysign(float x, float y)
{
return (x < 0 && y > 0) || (x > 0 && y < 0) ? -x : x;
}
#endif
#endif
+85
View File
@@ -0,0 +1,85 @@
#pragma once
#include "Common.h"
#include "utils.h"
void InitFlashParticlesFix();
struct EffectInstance
{
virtual void Vftable_x00();
virtual void FreeEngineEffect();
// Dealloc function which the game calls to clean up the effects when e.g. a ship or solar gets destroyed.
inline void EngineDealloc()
{
FreeEngineEffect();
FreeHeapMemory();
}
// Dealloc function which the game calls before creating a new flash effect instance for the same barrel/launcher.
inline void GeneralDealloc()
{
FreeAleEffect();
EngineDealloc();
}
// Dealloc function which the game calls right after quitting the play session.
inline void PostGameDealloc()
{
ResetBaseWatcher();
EngineDealloc();
}
inline void DoFreeHeapMemory()
{
FreeHeapMemory();
}
private:
struct WatcherInfo
{
float data[12];
};
void FreeAleEffect();
int FreeHeapMemory();
void SetBaseWatcher(int unk1, int unk2, const WatcherInfo& watcherInfo);
inline void ResetBaseWatcher()
{
WatcherInfo watcherInfo = { 0 };
watcherInfo.data[0] = watcherInfo.data[4] = watcherInfo.data[8] = 1.0f;
SetBaseWatcher(0, -1, watcherInfo);
}
};
EffectInstance** CreateFlashParticlesArray(UINT barrelAmount);
struct CliLauncher
{
DWORD vftable;
CELauncher* launcher; // 0x04
IObjRW* parent; // 0x08
BYTE x0C[0x1C];
// After playing the flash particle on a launcher, the effect instance is stored in 0x28.
// We need to keep track of more than one effect instance if the launcher has multiple barrels.
// Expanding the struct's memory is not feasible due to there existing many variations of this struct,
// which each have their own unique constructor and object size. Hence we dynamically manage this array at the same offset as currentFlashParticle.
union { // 0x28
EffectInstance* currentFlashParticle;
EffectInstance** flashParticlesArr;
};
void PlayAllFlashParticles(const ID_String& effectName);
// PlayFlashParticleForBarrel must be __cdecl because this code jumps to a vanilla FL function which does ret instead of ret n at the end.
// Therefore, the caller must clean the stack.
void __cdecl PlayFlashParticleForBarrel(const ID_String& effectName, UINT barrelIndex);
void CleanFlashParticlesPostGame_Hook();
void CleanFlashParticlesEngine_Hook();
void CleanFlashParticlesMemory_Hook();
void CleanFlashParticlesArr(void (EffectInstance::*deallocFunc)());
};
+15
View File
@@ -0,0 +1,15 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
enum AttitudeType : int
{
Hostile = -1,
Neutral = 0,
Friendly = 1
};
void InitHostileGroupFormation();
void InitHostileGroupMembersFix();
void InitGroupMemberAttitudeFix();
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <map>
struct InfocardEntry
{
std::map<UINT, UINT>& map;
LPCSTR key;
LPCSTR value;
};
void InitDynamicSolarInfocards();
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
namespace Logger
{
void PrintModuleError(LPCSTR functionName, LPCSTR moduleName);
void PrintFileOpenError(LPCSTR functionName, LPCSTR filePath);
void PrintV10Warning(LPCSTR moduleName);
void PrintInvalidFeatureWarning(LPCSTR functionName, LPCSTR featureName, LPCSTR iniPath);
void PrintInvalidHeaderWarning(LPCSTR functionName, LPCSTR headerName, LPCSTR iniPath);
}
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include "vftable.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define STDCALL __stdcall
void InitCursorFix();
void InitMouseWarpFix();
// Redefining this because I don't want the project to depend on the DirectX SDK...
struct IDirectInputDevice8
{
FILL_VFTABLE(0)
virtual void Vftable_x10();
virtual void Vftable_x14();
virtual void Vftable_x18();
virtual long STDCALL Acquire(); // 0x1C
virtual long STDCALL Unacquire(); // 0x20
virtual void Vftable_x24();
virtual void Vftable_x28();
virtual void Vftable_x2C();
virtual void Vftable_x30();
virtual long STDCALL SetCooperativeLevel(HWND hwnd, DWORD flags); // 0x34
};
+17
View File
@@ -0,0 +1,17 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
struct StrBuffer
{
LPWSTR str;
size_t capacity;
};
enum NameType : DWORD
{
FactionAndDesignation = 0,
PilotName = 1,
Unk = 2
};
void InitPilotNamesFix();
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "Common.h"
void InitProjectilesSoundFix();
void InitProjectilesServerFix();
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
void InitPrintRepRequirements();
struct DealerStack
{
BYTE x00[0x24];
float repRequired;
};
struct NN_Dealer
{
void PrintFmtStrPurchaseInfo_Hook(UINT idsPurchaseInfo, const DealerStack& stack);
void PrintFmtStrPurchaseInfo(UINT idsPurchaseInfo, int fmtValue);
};
+70
View File
@@ -0,0 +1,70 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "Freelancer.h"
#define MIN_RES_WIDTH 800
#define MIN_RES_HEIGHT 600
#define NN_PREFERENCES_ALLOC_SIZE_PTR 0x4B296A
#define NN_PREFERENCES_ALLOC_SIZE 0x980
struct WidthHeight
{
UINT width, height;
bool Equals(const WidthHeight &other)
{
return memcmp(this, &other, sizeof(other)) == 0;
}
};
struct ResolutionInfo
{
ResolutionInfo(UINT width, UINT height, UINT bpp)
: width(width), height(height), bpp(bpp)
{}
// bpp = bits per pixel. FL appears to only support 16 and 32
UINT width, height, bpp;
// First sort by bpp, then width, then height, all in ascending order
bool operator < (const ResolutionInfo& other) const
{
if (bpp != other.bpp)
return bpp < other.bpp;
else if (width != other.width)
return width < other.width;
else
return height < other.height;
}
};
struct ResolutionInitInfo
{
BYTE x00[0x8];
ResolutionInfo resolutionInfo;
};
inline bool IsResolutionAllowed(const DEVMODE &dm)
{
return dm.dmPelsWidth >= MIN_RES_WIDTH && dm.dmPelsHeight >= MIN_RES_HEIGHT && (dm.dmBitsPerPel == 16 || dm.dmBitsPerPel == 32);
}
// Returns true if the given resolution is narrower than 4:3.
inline bool IsResolutionNarrow(UINT width, UINT height)
{
#define MIN_4_BY_3_FACTOR (4.0f / 3.0f) - 0.02f
if (height == 0)
return true;
return ((float) width / (float) height) < MIN_4_BY_3_FACTOR;
}
bool ResolutionInit(HWND windowHandle, ResolutionInitInfo& info, DWORD windowFlags);
void InitBetterResolutions();
void CleanupBetterResolutions();
+23
View File
@@ -0,0 +1,23 @@
#pragma once
void CurrentResInfoWrite1();
void CurrentResInfoWrite2();
void CurrentResInfoWrite3();
void CurrentResInfoWrite4();
void CurrentResInfoWrite5();
void CurrentResInfoWrite6();
void CurrentResInfoWrite7();
void CurrentResInfoCheck1();
void CurrentResInfoCheck2();
void CurrentResInfoCheck3();
void CurrentResInfoCheck4();
void CurrentResInfoCheck5();
void CurrentResInfoCheck6();
void CurrentResInfoCheck7();
void DefaultResSet1();
void DefaultResSet2();
void SetMainResWidth(int value);
void SetMainResHeight(int value);
+3
View File
@@ -0,0 +1,3 @@
#pragma once
void InitSaveCrashFix();
+4
View File
@@ -0,0 +1,4 @@
#pragma once
void InitServerFilterCrashFix();
void InitServerFilterSpeedFix();
+3
View File
@@ -0,0 +1,3 @@
#pragma once
void InitShieldCapacityFix();
+190
View File
@@ -0,0 +1,190 @@
#pragma once
#include <cstddef>
#include <stdexcept>
#include <iterator>
#ifndef _POINTER_X
#define _POINTER_X(T, A) T*
#endif
#ifndef _REFERENCE_X
#define _REFERENCE_X(T, A) T&
#endif
namespace st6
{
template<class _Ty>
class allocator
{
public:
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef _Ty* pointer;
typedef const _Ty* const_pointer;
typedef _Ty& reference;
typedef const _Ty& const_reference;
typedef _Ty value_type;
pointer address(reference _X) const { return (&_X); }
const_pointer address(const_reference _X) const { return (&_X); }
void construct(pointer _P, const _Ty& _V) { _Construct(_P, _V); }
void destroy(pointer _P) { _Destroy(_P); }
size_t max_size() const
{
size_t _N = (size_t)(-1) / sizeof(_Ty);
return (0 < _N ? _N : 1);
}
};
template<class _Ty, class _A = allocator<_Ty>>
class vector
{
public:
typedef vector<_Ty, _A> _Myt;
typedef _A allocator_type;
typedef typename _A::size_type size_type;
typedef typename _A::difference_type difference_type;
typedef typename _A::pointer _Tptr;
typedef typename _A::const_pointer _Ctptr;
typedef typename _A::reference reference;
typedef typename _A::const_reference const_reference;
typedef typename _A::value_type value_type;
typedef _Tptr iterator;
typedef _Ctptr const_iterator;
iterator begin() { return (_First); }
const_iterator begin() const { return ((const_iterator)_First); }
iterator end() { return (_Last); }
const_iterator end() const { return ((const_iterator)_Last); }
size_type size() const { return (_First == 0 ? 0 : _Last - _First); }
bool empty() const { return (size() == 0); }
const_reference operator[](size_type _P) const { return (*(begin() + _P)); }
reference operator[](size_type _P) { return (*(begin() + _P)); }
protected:
_A allocator;
iterator _First, _Last, _End;
};
template <class _Ty, class _A = allocator<_Ty>>
class list
{
protected:
struct _Node;
friend struct _Node;
typedef _POINTER_X(_Node, _A) _Nodeptr;
struct _Node
{
_Nodeptr _Next, _Prev;
_Ty _Value;
};
struct _Acc;
friend struct _Acc;
struct _Acc
{
typedef _REFERENCE_X(_Nodeptr, _A) _Nodepref;
typedef typename _A::reference _Vref;
static _Nodepref _Next(_Nodeptr _P) { return ((_Nodepref)(*_P)._Next); }
static _Nodepref _Prev(_Nodeptr _P) { return ((_Nodepref)(*_P)._Prev); }
static _Vref _Value(_Nodeptr _P) { return ((_Vref)(*_P)._Value); }
};
public:
typedef list<_Ty, _A> _Myt;
typedef _A allocator_type;
typedef typename _A::size_type size_type;
typedef typename _A::difference_type difference_type;
typedef typename _A::pointer _Tptr;
typedef typename _A::const_pointer _Ctptr;
typedef typename _A::reference reference;
typedef typename _A::const_reference const_reference;
typedef typename _A::value_type value_type;
// CLASS const_iterator
class iterator;
class const_iterator;
friend class const_iterator;
class const_iterator
{
public:
const_iterator() {}
const_iterator(_Nodeptr _P) : _Ptr(_P) {}
const_iterator(const iterator& _X) : _Ptr(_X._Ptr) {}
const_reference operator*() const { return (_Acc::_Value(_Ptr)); }
_Ctptr operator->() const { return (&**this); }
const_iterator& operator++()
{
_Ptr = _Acc::_Next(_Ptr);
return (*this);
}
const_iterator operator++(int)
{
const_iterator _Tmp = *this;
++*this;
return (_Tmp);
}
const_iterator& operator--()
{
_Ptr = _Acc::_Prev(_Ptr);
return (*this);
}
const_iterator operator--(int)
{
const_iterator _Tmp = *this;
--*this;
return (_Tmp);
}
bool operator==(const const_iterator& _X) const { return (_Ptr == _X._Ptr); }
bool operator!=(const const_iterator& _X) const { return (!(*this == _X)); }
_Nodeptr _Mynode() const { return (_Ptr); }
protected:
_Nodeptr _Ptr;
};
// CLASS iterator
friend class iterator;
class iterator : public const_iterator
{
public:
iterator() {}
iterator(_Nodeptr _P) : const_iterator(_P) {}
reference operator*() const { return (_Acc::_Value(this->_Ptr)); }
_Tptr operator->() const { return (&**this); }
iterator& operator++()
{
this->_Ptr = _Acc::_Next(this->_Ptr);
return (*this);
}
iterator operator++(int)
{
iterator _Tmp = *this;
++*this;
return (_Tmp);
}
iterator& operator--()
{
this->_Ptr = _Acc::_Prev(this->_Ptr);
return (*this);
}
iterator operator--(int)
{
iterator _Tmp = *this;
--*this;
return (_Tmp);
}
bool operator==(const iterator& _X) const { return (this->_Ptr == _X._Ptr); }
bool operator!=(const iterator& _X) const { return (!(*this == _X)); }
};
iterator begin() { return (iterator(_Acc::_Next(_Head))); }
const_iterator begin() const { return (const_iterator(_Acc::_Next(_Head))); }
iterator end() { return (iterator(_Head)); }
const_iterator end() const { return (const_iterator(_Head)); }
_A allocator;
_Nodeptr _Head;
size_type _Size;
};
}
+3
View File
@@ -0,0 +1,3 @@
#pragma once
void InitFlightControlsFix();
+67
View File
@@ -0,0 +1,67 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "vftable.h"
#include "Common.h"
struct SoundHandle
{
BYTE data_x04[0x2C];
int unkBytePtr; // I don't know anything about this value (besides it being a pointer to some byte), but it gets nulled when the music stops playing.
inline bool FinishedPlaying()
{
return unkBytePtr == NULL || unkBytePtr == -1;
}
void ForcePause();
void ForceResume();
virtual void Vftable_x00();
virtual void Vftable_x04();
virtual DWORD __stdcall FreeReference();
virtual void Vftable_x0C();
FILL_VFTABLE(1)
FILL_VFTABLE(2)
FILL_VFTABLE(3)
FILL_VFTABLE(4)
FILL_VFTABLE(5)
virtual void Vftable_x60();
virtual void Vftable_x64();
virtual void Pause();
virtual void Resume();
virtual bool IsPaused();
};
struct TestSound
{
UINT idsName;
BYTE soundId;
};
struct FlSound
{
DWORD vftable;
UINT id;
LPCSTR filePath;
int unk_x0C;
float unk_x10;
float unk_x14;
};
FlSound* GetSound(const ID_String& ids);
bool GetBackgroundMusicHandle(SoundHandle **pHandle);
bool GetBackgroundAmbienceHandle(SoundHandle **pHandle);
bool GetBackgroundMusicHandle_Hook(SoundHandle **handle);
void StopMusicTestSound_Hook(BYTE soundId);
void InitTestSounds();
typedef bool (*GetSoundHandleFunc)(SoundHandle **pHandle);
void PauseSound(bool &shouldResume, GetSoundHandleFunc getHandle, bool force = false);
void ResumeSound(bool &shouldResume, GetSoundHandleFunc getHandle, bool force = false);
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include "Common.h"
struct CETradeLaneEquip
{
DWORD vftable;
CSolar* solar;
};
struct TradeLaneEquipObj
{
DWORD vftable;
CETradeLaneEquip* tradeLaneEquip;
BYTE x08[0x28];
BOOL isDisrupted;
void SetLightsState_Hook();
};
void InitTradeLaneLightsFix();
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "vftable.h"
#include "Common.h"
struct BigImage
{
virtual void Vftable_x00();
virtual void Vftable_x04();
virtual DWORD __stdcall Destroy();
};
struct UITextMsgButton
{
public:
int UpdatePosition_Hook(BYTE unk1, const Vector* newPosOffset, BYTE unk2);
FILL_VFTABLE(0)
FILL_VFTABLE(1)
FILL_VFTABLE(2)
FILL_VFTABLE(3)
FILL_VFTABLE(4)
FILL_VFTABLE(5)
FILL_VFTABLE(6)
FILL_VFTABLE(7)
FILL_VFTABLE(8)
FILL_VFTABLE(9)
virtual void Vftable_xA0();
virtual void Vftable_xA4();
// UpdatePosition is actually Transform and unk1 is the transform type, with 6 = UPDATE_POS.
// Thus the function actually has more purposes than just updating the position.
virtual int UpdatePosition(BYTE unk1, const Vector* newPosOffset, BYTE unk2);
BYTE x04[0x3E8];
BigImage* textImage; // 0x3EC. textImage = nullptr will prevent the text from rendering
BYTE x3F0[0x81];
bool disableHovering; // 0x471
};
void InitSlideUiAnimFix();
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include "Common.h"
#include "RemoteServer.h"
void ResetTimeSinceLastUpdate();
void InitBetterUpdates();
+108
View File
@@ -0,0 +1,108 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <cassert>
#include <initializer_list>
void Patch(DWORD vOffset, const LPVOID mem, UINT len);
template <typename Type>
inline void Patch(DWORD vOffset, Type value)
{
Patch(vOffset, &value, sizeof(Type));
}
void PatchBytes(DWORD vOffset, std::initializer_list<BYTE> bytes);
void Nop(DWORD vOffset, UINT len);
inline void ReadWriteProtect(DWORD location, DWORD size)
{
DWORD _;
VirtualProtect((PVOID) location, size, PAGE_EXECUTE_READWRITE, &_);
}
template <typename Func>
Func SetRelPointer(DWORD location, Func hookFunc)
{
// Set and calculate the relative offset for the hook function
DWORD& relOriginalLocation = GetValue<DWORD>(location);
DWORD originalPointer = location + relOriginalLocation + 4;
DWORD hookFuncLocation = *((PDWORD) &hookFunc);
relOriginalLocation = hookFuncLocation - (location + 4);
return GetFuncDef<Func>(originalPointer);
}
template <typename Func>
void Hook(DWORD location, Func hookFunc, UINT instrLen, bool jmp = false)
{
assert(instrLen >= 5);
// Set the opcode for the call or jmp instruction
Patch<BYTE>(location, jmp ? 0xE9 : 0xE8); // 0xE9 = jmp, 0xE8 = call
// Set the relative address
SetRelPointer(location + 1, hookFunc);
// Nop out excess bytes
if (instrLen > 5)
Nop(location + 5, instrLen - 5);
}
template <typename Func>
Func Trampoline(DWORD location, Func hookFunc, UINT instrLen)
{
// Allocate memory for gateway function.
PBYTE gatewayFunc = (PBYTE) VirtualAlloc(nullptr, instrLen + 5, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
// Copy the instruction(s) that will be overwritten by setting the hooks to the gateway code.
ReadWriteProtect(location, instrLen);
memcpy(gatewayFunc, (PVOID) location, instrLen);
// Jmp from location to hook function.
Hook(location, hookFunc, instrLen, true);
// Jmp from gateway to original function.
Hook((DWORD) (gatewayFunc + instrLen), GetFuncDef<Func>(location + instrLen), 5, true);
// Return handle for calling the gateway function which in turn calls the original function.
return GetFuncDef<Func>((DWORD) gatewayFunc);
}
template <typename Func>
void CleanupTrampoline(Func trampolineFunc)
{
VirtualFree((LPVOID) *((PDWORD) &trampolineFunc), 0, MEM_RELEASE);
}
template <typename Func>
Func SetPointer(DWORD location, Func hookFunc)
{
DWORD originalPointer = GetValue<DWORD>(location);
*(Func*) location = hookFunc;
return GetFuncDef<Func>(originalPointer);
}
template <typename Type>
inline Type& GetValue(DWORD location)
{
ReadWriteProtect(location, sizeof(Type));
return *(Type*) location;
}
template <class Func>
inline Func GetFuncDef(DWORD funcAddr)
{
return *(Func*) &funcAddr;
}
DWORD GetUnloadedModuleHandle(LPCTSTR moduleName);
struct NopStr
{
UINT len;
LPCSTR nopSequence;
};
+6
View File
@@ -0,0 +1,6 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
UINT32 GetDllProductBuildVersion(LPCSTR dllName);
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#define FILL_VFTABLE(tensPlace) \
virtual void Vftable_x ##tensPlace## 0(); \
virtual void Vftable_x ##tensPlace## 4(); \
virtual void Vftable_x ##tensPlace## 8(); \
virtual void Vftable_x ##tensPlace## C();
+3
View File
@@ -0,0 +1,3 @@
#pragma once
void InitWaypointFixes();
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
struct MissionObjective
{
BYTE fmtStr[0x16]; // 0x0
DWORD flags; // 0x18
};
void InitWaypointNameFixes();
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "Common.h"
#include "vftable.h"
enum EngModelType : DWORD
{
Object = 0,
Virtual = 2,
};
struct EngModel
{
EngModelType type; // 0x00
BYTE x04[0xC];
EngModel* parent; // 0x10
};
struct EngAnimation
{
// The first parameter seems to be a pointer to a stack-struct with the first three DWORDS set to 0 and then a ModelBinary*.
bool SetModel_Hook(PDWORD unk, const EngModel* model);
bool SetModel(PDWORD unk, const EngModel* model);
};
struct IAnimation2
{
FILL_VFTABLE(0);
FILL_VFTABLE(1);
virtual void Vftable_x20();
virtual int __stdcall Open(int scriptIndex, long engineInstance, LPCSTR animationScript, int unk1 = 0, int unk2 = 0);
int Open_Hook(LPCSTR animationScript, int scriptIndex, const CAttachedEquip& equip);
};
void InitWeaponAnimFix();
+77
View File
@@ -0,0 +1,77 @@
#include "Freelancer.h"
#include "fl_func.h"
#include "utils.h"
FL_FUNC(void StopSound(BYTE soundId), 0x5646E0)
FL_FUNC(void StartSound(BYTE soundId), 0x564650)
FL_FUNC(UINT GetFlStringFromResources(DWORD resourcesHandle, UINT ids, LPWSTR buffer, UINT bufferLen), 0x4347E0)
FL_FUNC(void AppendXmlWsToRdlEx(LPCWSTR ws, UINT wsLen, RenderDisplayList& rdl, DWORD flags), 0x57E2C0)
FL_FUNC(NavMapObj* NeuroNetNavMap::GetHighlightedObject(DWORD unk1, DWORD unk2), 0x496D40)
FL_FUNC(Waypoint* GetWaypoint(int index), 0x4C46A0)
FL_FUNC(bool WaypointWatcher::GetCurrentWaypointInfo(bool& isPlayerWaypoint, int& waypointIndex), 0x4F42A0);
FL_FUNC(IObjRW* GetPlayerIObjRW(), 0x54BAF0);
CShip* GetPlayerShip()
{
IObjRW* playerIObjRW = GetPlayerIObjRW();
return !playerIObjRW ? nullptr : (CShip*) playerIObjRW->cobject;
}
CShip* GetPlayerShipSafe()
{
IObjRW* playerIObjRW = GetPlayerIObjRW();
if (playerIObjRW && playerIObjRW->cobject)
{
if ((playerIObjRW->cobject->classType & CSHIP_CLASS_TYPE) == CSHIP_CLASS_TYPE)
return (CShip*) playerIObjRW->cobject;
}
return nullptr;
}
// Assumes both the CObjects of IObjRWs are CShips.
bool AreIObjRWsInSameGroup(const IObjRW& o1, const IObjRW& o2)
{
auto* ship1 = (const CShip*) o1.cobject;
auto* ship2 = (const CShip*) o2.cobject;
return AreShipsInSameGroup(ship1, ship2);
}
bool AreShipsInSameGroup(const CShip* ship1, const CShip* ship2)
{
return ship1->groupId && ship1->groupId == ship2->groupId;
}
FL_FUNC(bool IsSimpleUnvisited(const CSimple& simple), 0x4D4C70);
FL_FUNC(BYTE GetSimpleVisitedValue(const CSimple& simple), 0x4D4D00);
FL_FUNC(UINT GetIdsForUnvisitedSimple(const CSimple& simple), 0x4D4D50);
FL_FUNC(UINT GetCShipOrCEqObjName(const CEqObj &eqObj), 0x5472A0);
FL_FUNC(bool NN_Preferences::SetResolution(UINT width, DWORD unk, UINT height), 0x4B1C00)
void ExpandNNShipTraderObjMemory()
{
#define NN_SHIPTRADER_OBJ_SIZE_ADDR 0x4B9739
static bool memoryExpanded = false;
if (!memoryExpanded)
{
// Expand the size of the NN_ShipTrader object if it hasn't been done yet.
GetValue<UINT>(NN_SHIPTRADER_OBJ_SIZE_ADDR) += sizeof(NN_ShipTrader::shipRepPercentages);
memoryExpanded = true;
}
}
FL_FUNC(double GetDeltaTime(), 0x42D680)
FL_FUNC(void UpdateDeltaTime(), 0x42D770)
FL_FUNC(void UpdateDeltaTimeAndUpTime(), 0x5B2360)
FL_FUNC(UINT GetNumOfActiveMissionObjectives(), 0x4C4FB0)
+67
View File
@@ -0,0 +1,67 @@
#include "alchemy_crash.h"
#include "utils.h"
#include "logger.h"
#define FASTCALL __fastcall
// This rewrites the original loop present in alchemy.dll.
// In principle it would have been possible to just patch one asm instruction to fix the bug,
// but rewriting the loop is cooler.
const Alchemy* FASTCALL GetFinishedAle(int maxIndex, const Alchemy* aleArr, float maxProgress)
{
int i = 0;
for (; i < maxIndex - 1; ++i) // original loop condition: "i < maxIndex"
{
if (maxProgress < aleArr[i + 1].progress)
break;
}
return &aleArr[i];
}
// There is code in alchemy.dll that determines how ALE effects should transition to a different effect.
// Many times per frame it loops over a set of ALEs and finds which element meets the condition.
// However, it assumes that there is at least one element for which this condition holds.
// If not, we get that at the end of the loop, i == maxIndex, causing later code to access an out-of-bounds array element and thus crash (offset 0x701b).
// This occurs under extremely rare circumstances; you can play the game for 1,000 hours straight and not notice anything,
// but one day you start the game and it crashes within 15 minutes. The reason why suddenly no ALE meets this condition is unclear;
// the fact that it's so inconsistent and rare makes it impossible to bisect.
// This hook code rewrites the loop such that it never loops beyond maxIndex - 1.
// If the original problem were to occur, then one or more ALE effects may become invisible, though at least it certainly fixes the crash.
// Edit 18/04/26: It seems that even if the crash is fixed, there are other occurrences where it can happen.
// For instance, 0x778D has a loop which looks like it was directly copy pasted from 0x6FDD.
// Hence, I've looked carefully at the assembly for more similar loops and found two more (but I don't know if they ever get called).
// All instances now have the same fix applied. Hopefully, this fixes all variations of this particular crash.
void InitAlchemyCrashFix()
{
#define GET_FINISHED_ALE_START_TO_END 0x1A
DWORD alchemyHandle = (DWORD) GetModuleHandle("alchemy.dll");
if (!alchemyHandle)
{
Logger::PrintModuleError("InitAlchemyCrashFix", "alchemy.dll");
return;
}
static const AleLoop aleLoops[] = {
{ 0x6FDD, 0x10 },
{ 0x778D, 0x10 },
// { 0x7F4C, 0x3C },
// { 0x4136D, 0x10 }
// I noticed these have the exact same kind of loop as the above two.
// AFAICT however, these are never actually called, unlike the above two which are called every frame.
// Hence I can't properly test if this hook even works for the latter two instances.
};
for (const auto& aleLoop : aleLoops)
{
// mov edx, esi followed by push [esp+maxProgressOffset] (passes the needed parameters to our hook)
PatchBytes(alchemyHandle + aleLoop.startOffset, { 0x89, 0xF2, 0xFF, 0x74, 0x24 });
Patch<BYTE>(alchemyHandle + aleLoop.startOffset + 5, aleLoop.maxProgressOffset);
Hook(alchemyHandle + aleLoop.startOffset + 6, GetFinishedAle, 20);
// mov esi, eax (set the return value so that the rest of the alchemy code can use it)
Patch<WORD>(alchemyHandle + aleLoop.startOffset + GET_FINISHED_ALE_START_TO_END, 0xC689);
}
}
+42
View File
@@ -0,0 +1,42 @@
#include "base_info.h"
#include "Freelancer.h"
#include "utils.h"
#include <cstdio>
// Prints the header name in bold.
void PrintInfoCategoryHeader_Hook(UINT headerIds, RenderDisplayList &rdl)
{
WCHAR headerName[128];
GetFlString(headerIds, headerName, _countof(headerName));
LPCWSTR rdlBoldTextFmt = L"<RDL><PUSH/><TRA bold=\"true\"/><TEXT>%s</TEXT><TRA bold=\"false\"/><POP/></RDL>";
swprintf_s(FL_BUFFER_2, FL_BUFFER_LEN, rdlBoldTextFmt, headerName);
AppendXmlWsToRdl(FL_BUFFER_2, rdl);
}
// If you open the "Current Information" window of a base, it shows which ships,
// equipment, and commodities it is selling/buying. Every item displayed under each category
// is preceded by a number of spaces. The first entry has 5 spaces and all the others 4.
// The different number of spaces was done to fix a misalignment visible on lower 4:3 resolutions.
// However, the misalignment still happens on higher resolutions; it is caused by the bold header category text.
// This is because the bold closing tag is not added in the correct place (at least I think).
// Hence the spaces which are added for the first entry are bold and are thus wider than normal on some resolutions.
// This hook fixes it by making sure all entries use 5 spaces and printing the bold header text correctly.
void InitBaseInfoSpacingFix()
{
// Stores for each category, the headerStyleAddr and headerNamePrintAddr, respectively.
// The address of the first spacing string is always 6 bytes in front of headerNamePrintAddr.
const BaseInfoCat baseInfoCategories[] = {
{ 0x476177, 0x476203 }, // Ships For Sale (ids 0x669/1641)
{ 0x476388, 0x476414 }, // Commodities Selling (ids 0x668/1640)
{ 0x4765F4, 0x476684 }, // Commodities Buying (ids 0x667/1639)
{ 0x476939, 0x4769E7 } // Equipment For Sale (ids 0x66A/1642)
};
for (const auto &baseInfoCat : baseInfoCategories)
{
Patch<WORD>(baseInfoCat.headerStyleAddr + 1, 0x9CA4); // remove the bold style for the category header
Hook(baseInfoCat.headerNamePrintAddr, PrintInfoCategoryHeader_Hook, 5); // ensure the header is printed manually, in bold
Patch<BYTE>(baseInfoCat.headerNamePrintAddr + 6, 0x54); // use 5 spaces for the first line
}
}
+27
View File
@@ -0,0 +1,27 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "Common.h"
#include "utils.h"
#define FC_UK_GRP_IDS_NAME 197510
#define NONE_IDS 3022
UINT CShip::get_group_name_Hook() const
{
UINT result = this->get_group_name();
if (result == FC_UK_GRP_IDS_NAME)
return NONE_IDS;
return result;
}
// When you open the Current Information window on a factionless ship (fc_uk_grp),
// one of the lines will say "Faction:".
// This is because the fc_uk_grp faction has no name.
// This code replaces the ids_name of fc_uk_grp only in this particular instance with "None" to make it look nicer.
void InitBlankFactionNameFix()
{
#define CURRENT_INFO_GET_GROUP_NAME_INFOCARD_CALL_ADDR 0x475950
Hook(CURRENT_INFO_GET_GROUP_NAME_INFOCARD_CALL_ADDR, &CShip::get_group_name_Hook, 6);
}
+103
View File
@@ -0,0 +1,103 @@
#include "cheat_detection.h"
#include "logger.h"
#include "fl_func.h"
#include "utils.h"
#include "Common.h"
#include <algorithm>
#define NAKED __declspec(naked)
DWORD getGoodSoldByBaseCallAddr = 0;
DWORD baseGoodItAdvanceAddr = 0;
FL_FUNC(const MarketGood* BaseMarket::GetSoldGood(UINT goodId) const, getGoodSoldByBaseCallAddr)
FL_FUNC(void BaseGoodIt::Advance(), baseGoodItAdvanceAddr)
NAKED void GetGoodSoldByBase_Hook()
{
__asm {
mov edx, esi // PlayerData&
jmp GetGoodSoldByBaseOrPartOfShip
}
}
bool ShipPackageContainsGood(GoodInfo const &shipPackage, UINT goodId)
{
for (const auto& equipDescList : shipPackage.equipDescLists) {
bool containsGoodId = std::any_of(equipDescList.list.begin(), equipDescList.list.end(),
[goodId](const EquipDesc &equipDesc) { return equipDesc.archId == goodId; });
if (containsGoodId)
return true;
}
return false;
}
bool BaseGoodCollection::HasShipPackageWithGood(UINT goodId)
{
// Iterate over all the base's sold goods and try to find the ship packages.
for (auto goodIt = goods.begin(); goodIt != goods.end(); ((BaseGoodIt*) &goodIt)->Advance())
{
if (!goodIt->IsShipCandidate())
continue;
GoodInfo const *goodInfo = GoodList::find_by_id(goodIt->goodId);
// Is it a ship package?
if (goodInfo && goodInfo->type == GoodType::Ship)
{
if (ShipPackageContainsGood(*goodInfo, goodId))
return true;
}
}
return false;
}
const MarketGood* FASTCALL GetGoodSoldByBaseOrPartOfShip(const BaseMarket &baseMarket, const PlayerData &playerData, UINT goodId)
{
const MarketGood* result = baseMarket.GetSoldGood(goodId);
if (result)
return result;
// If the good is not sold by the base directly, maybe it's part of the purchased ship package.
// This should only be checked if the player's ship has remained the same while staying on the base.
if (playerData.currentShipId
&& playerData.currentShipId == playerData.shipIdOnLand
&& baseMarket.baseGoods->HasShipPackageWithGood(goodId))
{
// Return a MarketGood such that FL's return value check passes.
static const MarketGood validMarketGood = { 0 };
return &validMarketGood;
}
return nullptr;
}
// In Freelancer there is a bug where if you have a server with players on it
// and a player purchases a ship which they already have and then undock, they get kicked from the server.
// This is because on undock, FL's anticheat does a check to see if you obtained any equipment which is not sold by the base.
// This check only proceeds if your ship hasn't changed since you landed on the base.
// If you buy a ship, you usually get some additional equipment as part of the package (e.g. shield).
// However, after re-buying the same ship and undocking, you still have the same ship as far as the game is concerned,
// and you have a shield which is not sold by the base, and thus you get kicked.
// This code fixes it by checking if the "cheated" equipment is part of any of the base's offered ship packages.
void InitShipBuyKickFix()
{
#define GET_GOOD_SOLD_BY_BASE_CALL_OFFSET_SERVER 0x6FEEB
DWORD serverHandle = (DWORD) GetModuleHandle("server.dll");
if (!serverHandle)
{
Logger::PrintModuleError("InitShipBuyKickFix", "server.dll");
return;
}
getGoodSoldByBaseCallAddr = serverHandle + 0x33000;
baseGoodItAdvanceAddr = serverHandle + 0x35DE0;
Hook(serverHandle + GET_GOOD_SOLD_BY_BASE_CALL_OFFSET_SERVER, GetGoodSoldByBase_Hook, 5);
}
+25
View File
@@ -0,0 +1,25 @@
#include "config_reader.h"
#include "Common.h"
#include "feature_config.h"
#include "logger.h"
void ReadConfig(LPCSTR path, FeatureManager &manager)
{
INI_Reader reader;
if (!reader.open(path))
return;
while (reader.read_header())
{
while (reader.read_value())
{
if (!manager.SetFeatureEnabled(reader.get_name_ptr(), reader.get_value_bool()))
{
Logger::PrintInvalidFeatureWarning("ReadConfig", reader.get_name_ptr(), reader.get_file_name());
}
}
}
reader.close();
}
+123
View File
@@ -0,0 +1,123 @@
#include "copy_paste.h"
#include "utils.h"
#define NAKED __declspec(naked)
NAKED void HandleDefaultInputKey_Hook()
{
#define HANDLE_DEFAULT_INPUT_KEY_OG 0x57CDDA
__asm {
mov ecx, esi
push edi
call InputBoxWindow::HandleCopyPaste
mov byte ptr [esp+0x13], 0
mov eax, HANDLE_DEFAULT_INPUT_KEY_OG
jmp eax
}
}
void InputBoxWindow::CopyFromClipboard()
{
if (!OpenClipboard(nullptr))
return;
HANDLE clipboard = GetClipboardData(CF_UNICODETEXT);
if (!clipboard)
goto _closeClipboard;
LPCWSTR clipboardStr = static_cast<LPCWSTR>(GlobalLock(clipboard));
if (!clipboardStr)
goto _closeClipboard;
WriteString(clipboardStr);
GlobalUnlock(clipboard);
_closeClipboard:
CloseClipboard();
}
// There exists a WriteTypedKey function which takes the typedKey variable from a KeyMapInfo object and writes it to the input box.
// However, this typedKey variable is the only thing that the function needs from this entire object.
// So we just define a dummy KeyMapInfo object where we fill the character we want to enter in every loop iteration.
void InputBoxWindow::WriteString(LPCWSTR str)
{
KeyMapInfo kmi;
// Stop when the end of the string has been reached, or if the buffer is full.
for (size_t i = 0; str[i] != L'\0' && chars.size() < (size_t) maxCharsLength; ++i)
{
kmi.enteredKey = str[i];
this->WriteTypedKey(kmi);
}
}
void InputBoxWindow::CopyToClipboard()
{
size_t inputLength = this->chars.size();
// If the chars vector is empty, there isn't anything to copy to the clipboard.
// If the clipboard won't even open, there's no point in trying either.
if (inputLength == 0 || !OpenClipboard(nullptr))
return;
if (!EmptyClipboard())
goto _closeClipboard;
HGLOBAL clipboardData = GlobalAlloc(GMEM_MOVEABLE, sizeof(WCHAR) * (inputLength + 1));
if (!clipboardData)
goto _closeClipboard;
LPWSTR clipboardStr = static_cast<LPWSTR>(GlobalLock(clipboardData));
if (!clipboardStr)
{
GlobalFree(clipboardData);
goto _closeClipboard;
}
// Copy every char from the input box buffer to clipboardStr.
for (size_t i = 0; i < inputLength; ++i)
clipboardStr[i] = this->chars[i].c;
clipboardStr[inputLength] = L'\0'; // Set the null character at the end.
GlobalUnlock(clipboardData);
if (!SetClipboardData(CF_UNICODETEXT, clipboardData))
GlobalFree(clipboardData);
_closeClipboard:
CloseClipboard();
}
void InputBoxWindow::HandleCopyPaste(const KeyMapInfo& kmi)
{
// I saw this check being made in many key handling function, but for this one I don't think it's necessary.
// if (this->ime == nullptr)
// return;
if (kmi.IsCtrlPressed())
{
// Ctrl + V pressed?
if (toupper(kmi.enteredKey) == L'V')
{
CopyFromClipboard();
}
// Ctrl + C pressed?
else if (toupper(kmi.enteredKey) == L'C')
{
CopyToClipboard();
}
}
}
// Allows for the Ctrl + C and Ctrl + V key combinations to copy and paste the current clipboard from/to the input box.
void InitCopyPasteFeature()
{
#define HANDLE_DEFAULT_INPUT_KEY_ADDR 0x57CE3C
SetPointer(HANDLE_DEFAULT_INPUT_KEY_ADDR, HandleDefaultInputKey_Hook);
}
+223
View File
@@ -0,0 +1,223 @@
#include "cursor_colors.h"
#include "utils.h"
#include "Freelancer.h"
#include "fl_func.h"
#include <map>
#include <vector>
#include <memory>
#include <algorithm>
#define FASTCALL __fastcall
#define NAKED __declspec(naked)
#define CURSOR_LIST ((MouseCursor**) 0x616744)
#define CURSOR_LIST_SIZE (*(PUINT) 0x616740)
#define CURRENT_CURSOR (*(MouseCursor**) 0x616858)
#define GROUP_MEMBER_COLOR (*(PDWORD) 0x679B88)
#define TRADE_REQUEST_COLOR (*(PDWORD) 0x679B9C)
// The yellow color of objects using radio, but it is not used in the contact list.
// Presumably because this color is already reserved for the selected target.
#define HIGHLIGHT_COLOR (*(PDWORD) 0x679BA4)
const IObjRW *lastSelectedObj = nullptr;
FL_FUNC(void Targetable_Objects::UpdateTargeting(), 0x4F2220)
FL_FUNC(bool IsSimpleUsingRadio(UINT simpleId), 0x4CC880)
// We want to reset the lastSelectedObj before the targeting is updated
// to ensure lastSelectedObj never points to invalid memory.
void Targetable_Objects::UpdateTargeting_Hook()
{
lastSelectedObj = nullptr;
UpdateTargeting();
}
FL_FUNC(const IObjRW* FindIObjRW(UINT nickname, DWORD unk), 0x05416C0)
// Calling FindIObjRW manually every time we want to check the highlighted object is inefficient,
// so we intercept the call that FL makes every frame and save the last selected object.
const IObjRW* FindCurrentSelectedIObjRW_Hook(UINT nickname, DWORD unk)
{
const IObjRW* result = FindIObjRW(nickname, unk);
if (result)
lastSelectedObj = result;
return result;
}
// Gets called when FL checks the attitude of the targeted (aim locked) object
NAKED void GetAttitudeOfTarget_Hook()
{
#define GET_ATTITUDE_OF_TARGET_RET_ADDR 0x4F2465
__asm {
test ebx, ebx
je skip
mov lastSelectedObj, eax // save the targeted (aim locked) object
skip:
mov edx, [esp+0x30] // overwritten instructions
push eax
push edx
mov ecx, GET_ATTITUDE_OF_TARGET_RET_ADDR
jmp ecx
}
}
std::map<MouseCursor*, std::shared_ptr<MouseCursor>> groupCursors, tradeRequestCursors;
//std::map<MouseCursor*, std::shared_ptr<MouseCursor>> radioCursors;
std::shared_ptr<MouseCursor> CreateCustomCursor(const MouseCursor* originalCursor, DWORD color, LPCSTR nicknameSuffix)
{
auto result = std::make_shared<MouseCursor>(*originalCursor);
strcat_s(result->nickname, sizeof(result->nickname), nicknameSuffix);
result->nicknameLen = strlen(result->nickname);
result->color = color;
return result;
}
void FillCustomCursorMap(const std::vector<LPCSTR> &cursorNames, LPCSTR neutralCursorName)
{
std::vector<MouseCursor*> cursors;
// Find the relevant cursors.
for (UINT i = 0; i < CURSOR_LIST_SIZE; ++i)
{
for (const auto cursorName : cursorNames)
{
if (strcmp(CURSOR_LIST[i]->nickname, cursorName) == 0)
cursors.push_back(CURSOR_LIST[i]);
}
}
// Get the neutral cursor which we want to copy.
auto neutralCursorIt = std::find_if(cursors.begin(), cursors.end(),
[neutralCursorName](const MouseCursor* cursor) {
return strcmp(cursor->nickname, neutralCursorName) == 0;
}
);
// Create new cursors based on the copied neutral cursor
// and store them by the original friendly, neutral, and hostile version for easy access.
if (neutralCursorIt != cursors.end())
{
auto groupCursor = CreateCustomCursor(*neutralCursorIt, GROUP_MEMBER_COLOR, "_group");
auto tradeRequestCursor = CreateCustomCursor(*neutralCursorIt, TRADE_REQUEST_COLOR, "_trade");
//auto radioCursor = CreateCustomCursor(*neutralCursorIt, HIGHLIGHT_COLOR, "_radio");
for (const auto cursor : cursors)
{
groupCursors.emplace(cursor, groupCursor);
tradeRequestCursors.emplace(cursor, tradeRequestCursor);
//radioCursors.emplace(cursor, radioCursor);
}
}
}
void (*InitCursors_Original)();
void InitCursors_Hook()
{
// This function initializes all the standard cursors.
// After it has finished, we want to create our custom-colored cursors by copying the existing neutral cursors.
InitCursors_Original();
std::vector<LPCSTR> normalCursorNames = { "friendly", "neutral", "hostile" };
std::vector<LPCSTR> fireCursorNames = { "fire_friendly", "fire_neutral", "fire" };
FillCustomCursorMap(normalCursorNames, "neutral");
FillCustomCursorMap(fireCursorNames, "fire_neutral");
}
FL_FUNC(void SetCurrentCursor(LPCSTR cursorName, bool unk), 0x41DDE0)
void FASTCALL SetCurrentCustomAimCursor(const Targetable_Objects& to, const IObjRW *highlightedObj, LPCSTR cursorName, bool unk)
{
// This function updates the CURRENT_CURSOR for targeting (aiming).
SetCurrentCursor(cursorName, unk);
// Check if the player can be obtained.
const IObjRW* player = GetPlayerIObjRW();
if (!player || player->unk_x1C != 1)
return;
// Try to get the target.
const IObjRW *target = nullptr;
if (highlightedObj != player && !to.isAimLocking)
{
target = highlightedObj;
}
else if (lastSelectedObj)
{
target = lastSelectedObj;
}
if (!target)
return;
// If the target has been found, check if it is a player who sent a trade request or is a group member.
decltype(groupCursors)* customCursorMap = nullptr;
// if (IsSimpleUsingRadio(target->get_simple_id()))
// {
// customCursorMap = &radioCursors;
// }
// else
if (target->is_player())
{
if (target->SentTradeRequest())
customCursorMap = &tradeRequestCursors;
else if (AreIObjRWsInSameGroup(*target, *player))
customCursorMap = &groupCursors;
}
// If we found a better suitable custom cursor, set it as the current cursor.
if (customCursorMap)
{
auto it = customCursorMap->find(CURRENT_CURSOR);
if (it != customCursorMap->end())
{
it->second->animState = CURRENT_CURSOR->animState;
CURRENT_CURSOR = it->second.get();
}
}
}
// Gets called when FL changes the current aim cursor.
NAKED void SetCurrentAimCursor_Hook()
{
__asm {
mov ecx, ebp // Targetable_Objects&
mov edx, esi // IObjRW *highlightedObj
jmp SetCurrentCustomAimCursor
}
}
// In Multiplayer, if you hover over a group member with the mouse, the cursor does not honor the pink group color.
// Similarly, if you hover over someone who sent you a trade request, the cursor is not dark purple, either.
// This code fixes this by creating custom cursors based on the existing neutral cursors
// and showing them if it has been detected that the target is a group member or someone who sent a trade request.
void InitMoreCursorColors()
{
#define INIT_CURSORS_CALL_ADDR 0x59D60B
InitCursors_Original = SetRelPointer(INIT_CURSORS_CALL_ADDR + 1, InitCursors_Hook);
#define UPDATE_TARGETING_CALL_ADDR 0x4EC5EE
Hook(UPDATE_TARGETING_CALL_ADDR, &Targetable_Objects::UpdateTargeting_Hook, 5);
#define FIND_SELECTED_IOBJRW_CALL_ADDR 0x4F22D6
Hook(FIND_SELECTED_IOBJRW_CALL_ADDR, FindCurrentSelectedIObjRW_Hook, 5);
#define GET_ATTITUDE_OF_TARGET_ADDR 0x4F245F
Hook(GET_ATTITUDE_OF_TARGET_ADDR, GetAttitudeOfTarget_Hook, 6, true);
DWORD setCurrentAimCursorCalls[] = { 0x4EC914, 0x4EC953 };
for (auto aimCursorCall : setCurrentAimCursorCalls)
Hook(aimCursorCall, SetCurrentAimCursor_Hook, 8);
}
+81
View File
@@ -0,0 +1,81 @@
#include "dealer_fixes.h"
#include "utils.h"
#include "fl_func.h"
#define FASTCALL __fastcall
FL_FUNC(bool DealerOpenCamera::StartAnimation(LPCSTR name, PVOID unk, NavBar* navBar, DWORD unk2), 0x44BA60)
bool DealerOpenCamera::StartAnimation_Hook(LPCSTR name, PVOID unk, NavBar* navBar, DWORD unk2)
{
// Return true instead of false if the animation is already in progress. This fixes the bug.
if (animationInProgress)
return true;
return StartAnimation(name, unk, navBar, unk2);
}
void FASTCALL SetShipDealerMenuOpened_Hook(PVOID unkUiElement, NavBar& navBar)
{
navBar.unkUiElement = unkUiElement; // overwritten instruction
// Don't allow the ship dealer menu to be opened if the room transition hasn't finished yet.
// Otherwise it'll crash the game.
bool roomTransitionFinished = (navBar.maneuverFrame->flags & UI_ELEMENT_VISIBLE) == UI_ELEMENT_VISIBLE;
navBar.shipDealerMenuOpened = roomTransitionFinished;
}
NAKED void GetRoomHotspot_Hook()
{
__asm {
mov ebp, eax // overwritten instruction #1
test esi, esi
je null
mov eax, [esi + 0x1C] // overwritten instruction #2
ret
null:
xor eax, eax
ret
}
}
void (NavBar::*SetHotspot_Original)(PVOID hotspot);
void NavBar::SetHotspot_Hook(PVOID hotspot)
{
// Yeah, let's not call the function without a valid hotspot.
if (hotspot)
{
(this->*SetHotspot_Original)(hotspot);
}
}
// In Freelancer there is an infamous bug where if you click the equipment or commodity dealer twice very quickly, the camera goes up but the dealer menu never appears.
// Once the bug has been triggered the dealer menus will continue to not show up until you undock and redock, or reload your save file.
void InitDealerOpenFix()
{
#define INIT_CAMERA_TRANSITION_EQUIPMENT_DEALER_ADDR 0x4417E7
#define INIT_CAMERA_TRANSITION_COMMODITY_DEALER_ADDR 0x441862
DWORD initCameraCalls[] = { INIT_CAMERA_TRANSITION_EQUIPMENT_DEALER_ADDR, INIT_CAMERA_TRANSITION_COMMODITY_DEALER_ADDR };
for (const auto &call : initCameraCalls)
Hook(call, &DealerOpenCamera::StartAnimation_Hook, 5);
};
// There are some rare crashes that can occur when opening the dealer menus.
void InitDealerCrashFix()
{
#define SET_SHIP_DEALER_MENU_OPENED_ADDR 0x441D28
#define GET_ROOM_HOTSPOT_ADDR 0x43FFB6
#define SET_HOTSPOT_CALL_ADDR 0x43E9CA
// Fixes a crash when clicking on the ship dealer before the room transition has finished.
PatchBytes(SET_SHIP_DEALER_MENU_OPENED_ADDR, { 0x89, 0xDA, 0x89, 0xC5 }); // mov edx, ebx + mov ebp, eax
Hook(SET_SHIP_DEALER_MENU_OPENED_ADDR + sizeof(DWORD), SetShipDealerMenuOpened_Hook, 5);
PatchBytes(SET_SHIP_DEALER_MENU_OPENED_ADDR + sizeof(DWORD) + 5, { 0x89, 0xE8, 0x66, 0x90 }); // mov eax, ebp + nop
// Fixes a very rare crash that occurs when randomly clicking on various dealers at a base.
Hook(GET_ROOM_HOTSPOT_ADDR, GetRoomHotspot_Hook, 5);
SetHotspot_Original = SetRelPointer(SET_HOTSPOT_CALL_ADDR + 1, &NavBar::SetHotspot_Hook);
}
+46
View File
@@ -0,0 +1,46 @@
#include "dll_crash.h"
#include "utils.h"
#include "logger.h"
#define SKIP_DLL_LOAD_FILE_OFFSET_SERVER 0x63F54
#define FDUMP_DLL_INSTANCE_FAILED_1_FILE_OFFSET_SERVER 0x63D50
#define CREATE_DLL_INSTANCE_FAILED_1_FILE_OFFSET_SERVER 0x63D6B
#define CREATE_DLL_INSTANCE_FAILED_2_FILE_OFFSET_SERVER 0x63E6D
#define NAKED __declspec(naked)
DWORD skipDllLoadAddr;
NAKED void CreateDllInstanceFail_Hook()
{
__asm {
call [edx] // overwritten instruction #1
add esp, 0x14 // overwritten instruction #2
jmp [skipDllLoadAddr] // skip the DLL loading code
}
}
// If you try to load a DLL which doesn't exist via Freelancer.ini (Initial MP DLL or Initial SP DLL),
// the game logs an error to the Spew and then crashes. This code fixes the crash to ensure that the game at least still runs.
void InitMissingDllCrashFix()
{
// E.g. console.dll enforces the server library to load without causing any issues, so should be fine
DWORD serverHandle = GetUnloadedModuleHandle("server.dll");
if (!serverHandle)
{
Logger::PrintModuleError("InitMissingDllCrashFix", "server.dll");
return;
}
skipDllLoadAddr = serverHandle + SKIP_DLL_LOAD_FILE_OFFSET_SERVER;
// mov edx, [FDUMP] <- mov ecx, [FDUMP] to ensure that we can use the same hook for instance 1.
Patch<BYTE>(serverHandle + FDUMP_DLL_INSTANCE_FAILED_1_FILE_OFFSET_SERVER, 0x15);
const DWORD dllInstanceFailedOffsets[] = {
CREATE_DLL_INSTANCE_FAILED_1_FILE_OFFSET_SERVER, CREATE_DLL_INSTANCE_FAILED_2_FILE_OFFSET_SERVER, };
for (const auto& offset : dllInstanceFailedOffsets)
Hook(serverHandle + offset, CreateDllInstanceFail_Hook, 5, true);
}
+90
View File
@@ -0,0 +1,90 @@
#include "exit.h"
#include "DALib.h"
#include "utils.h"
#include "fl_func.h"
FL_FUNC(void exit_Original(int const status), dword ptr ds:[0x5C713C])
void exit_Hook(int const status)
{
// Call the original function with WaitForSingleObject.
CGunWrapper::Shutdown();
// Call the original exit function.
exit_Original(status);
}
bool noQuitMsgRetrieved = true;
bool (*HandleMessages_Original)(WPARAM *msgWParam);
bool HandleMessages_Hook(WPARAM *msgWParam)
{
bool result = HandleMessages_Original(msgWParam);
return noQuitMsgRetrieved &= result;
}
// In Freelancer, when you close the server list menu (provided that there were servers listed),
// a thread would be created that closes the DirectPlay connection (takes 15-30 seconds to execute).
// If you quit the game before the DirectPlay was connection closed, a WaitForSingleObject call would be made
// which actually waited indefinitely for the thread to finish.
// Consequently, the Freelancer process would remain open until forcefully closed (via Task Manager for instance).
// Turns out that whenever the thread calls FreeLibrary after FL's main exit function had already been called,
// that FreeLibrary call would never return, and thus the thread would never finish its task.
// I believe this was caused by a deadlock. Yet, I could not explain why this deadlock would occur under these circumstances,
// nor was I able to come up with a "clean fix" for it. So now instead of calling the function with WaitForSingleObject after the exit,
// I call it before the exit. The thread still takes a very long time to close the DirectPlay connection (which I think is a bug too),
// but at least there is no more deadlock and the Freelancer process will eventually close, as it should.
void InitPostGameDeadlockFix()
{
#define CGUNWRAPPER_SHUTDOWN_CALL_ADDR 0x5B2190
#define FL_EXE_EXIT_CALL_ADDR 0x5B81C6
Nop(CGUNWRAPPER_SHUTDOWN_CALL_ADDR, 5); // nop out the post-game CGunWrapper::Shutdown() call; call it in the exit hook instead
Hook(FL_EXE_EXIT_CALL_ADDR, exit_Hook, 6);
}
// Freelancer has a message handler function which can be called from multiple places.
// Normally it is called by the "main" function. However, if for example you see the "disconnected"
// dialog, the message handler is actually called from somewhere else.
// This is normally not a problem, unless you exit the game while that dialog is showing.
// The message handler function returns false if the "Quit" message was retrieved.
// When the main caller sees that false was returned, it exits from the loop and shuts down the game.
// This does not happen when the disconnected dialog is showing; it will continue handling messages as normal.
// The "Quit" message is only retrieved once, so when the main handler takes over, it will continue handling messages forever,
// despite the the window being closed by the user.
// This hook fixes the problem by always returning false after the message handler returned false at some point.
void InitQuitMessageFix()
{
#define HANDLE_MESSAGES_ADDR 0x5B0B60
HandleMessages_Original = Trampoline(HANDLE_MESSAGES_ADDR, HandleMessages_Hook, 5);
}
void CleanupQuitMessageFix()
{
CleanupTrampoline(HandleMessages_Original);
}
// TODO: If anyone would like to look into this further: in dpnet.dll there's a function called "DN_Close" (locate it by downloading the debug symbols from Microsoft).
// I believe this function is supposed to represent IDirectPlay8Client::Close. It is this exact function that takes ~40 seconds to return on my end.
// This seems strange since in all online examples I could find that closed some DirectPlay connection, it is always done on the main thread.
// Surely, something must have been done incorrectly in one of the DirectPlay calls. Since DA couldn't figure out what,
// they took the band-aid approach and closed the connection on a separate thread.
// Otherwise the screen freezes for 40 seconds every time the server list menu is closed.
// TODO Idea: File offset 0x30896 in gundll.dll. This is a call to IDirectPlay8Client::Connect.
// phAsyncHandle
// A DPNHANDLE. When the method returns, phAsyncHandle will point to a handle that you can pass to IDirectPlay8Client::CancelAsyncOperation to cancel the operation.
// This parameter must be set to NULL if you set the DPNCONNECT_SYNC flag in dwFlags.
// What happens if you call IDirectPlay8Client::CancelAsyncOperation before? See dplay.doc in Downloads folder.
// IDirectPlay8Client::CancelAsyncOperation
// Cancels asynchronous requests. Many methods of the IDirectPlay8Client interface run asynchronously by default. Depending on the situation, you might want to cancel requests before they are processed. All the methods of this interface that can be run asynchronously return a hAsyncHandle parameter.
// Specific requests are canceled by passing the hAsyncHandle of the request in this methods hAsyncHandle parameter. You can cancel all pending asynchronous operations by calling this method, specifying NULL in the hAsyncHandle parameter, and specifying DPNCANCEL_ALL_OPERATIONS in the dwFlags parameter. If a specific handle is provided to this method, no flags should be set.
// DirectPlayClient->CancelAsyncOperation( NULL, DPNCANCEL_ALL_OPERATIONS ); Find where DirectPlayClient is
// gundll.dll: file offset 0x8376. change xor esi, esi in the function call to mov esi, 1. This fixes the 30 second timer
// Test if this works in Win XP too
// dalib.dll: file offset 0x4C82 = load library call of gundll.dll. Use this to set hooks
// dxcheckOK( DirectPlayClient->Close(DPNCLOSE_IMMEDIATE) ); // WARNING DPNCLOSE_IMMEDIATE is a DP feature from DirectX 9 (released shortly after FL came out)
// SafeRelease( DirectPlayClient );
+57
View File
@@ -0,0 +1,57 @@
#include "feature_config.h"
#include "Common.h"
void FeatureManager::RegisterFeature(LPCSTR name, void (*initFunc)(), void (*cleanupFunc)(), bool (*applyPredicate)())
{
// Enable the feature by default.
FlSharpFeature feature { initFunc, cleanupFunc, applyPredicate, true };
features.emplace(CreateID(name), feature);
}
bool FeatureManager::SetFeatureEnabled(LPCSTR name, bool enabled)
{
const auto it = features.find(CreateID(name));
if (it == features.end())
return false;
it->second.enabled = enabled;
return true;
}
void FeatureManager::InitFeatures()
{
for (const auto& it : features)
{
const FlSharpFeature& feature = it.second;
if (feature.enabled && feature.initFunc && feature.applyPredicate())
feature.initFunc();
}
}
void FeatureManager::CleanupFeatures()
{
for (const auto& it : features)
{
const FlSharpFeature& feature = it.second;
if (feature.enabled && feature.cleanupFunc && feature.applyPredicate())
feature.cleanupFunc();
}
}
bool ApplyAlways()
{
return true;
}
bool ApplyOnlyOnClient()
{
return !IsMPServer();
}
bool ApplyOnlyOnServer()
{
return IsMPServer();
}
+38
View File
@@ -0,0 +1,38 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <algorithm>
#include <cmath>
#include "fl_math.h"
#define M_PI 3.14159265358979323846f
Quaternion MatrixToQuaternion(const Matrix& m)
{
Quaternion result;
result.w = sqrtf(std::max(0.0f, 1 + m.data[0][0] + m.data[1][1] + m.data[2][2])) / 2;
result.x = sqrtf(std::max(0.0f, 1 + m.data[0][0] - m.data[1][1] - m.data[2][2])) / 2;
result.y = sqrtf(std::max(0.0f, 1 - m.data[0][0] + m.data[1][1] - m.data[2][2])) / 2;
result.z = sqrtf(std::max(0.0f, 1 - m.data[0][0] - m.data[1][1] + m.data[2][2])) / 2;
result.x = copysign(result.x, m.data[2][1] - m.data[1][2]);
result.y = copysign(result.y, m.data[0][2] - m.data[2][0]);
result.z = copysign(result.z, m.data[1][0] - m.data[0][1]);
return result;
}
float QuaternionDotProduct(const Quaternion &left, const Quaternion &right)
{
return left.x * right.x + left.y * right.y + left.z * right.z + left.w * right.w;
}
float QuaternionAngleDifference(const Quaternion &left, const Quaternion &right)
{
float dot = QuaternionDotProduct(left, right);
return acosf(fabsf(dot)) * 2 * (180.0f / M_PI);
}
float GetRotationDelta(const Quaternion& quat, const Matrix& rot)
{
return QuaternionAngleDifference(quat, MatrixToQuaternion(rot));
}
+142
View File
@@ -0,0 +1,142 @@
#include "flash_particles.h"
#include "Common.h"
#include "utils.h"
#include "fl_func.h"
#define NAKED __declspec(naked)
// This hook gets called when Freelancer wants to play a flash effect animation.
// We intercept this call to play the flash effect for every barrel.
NAKED void PlayFlashEffect_Hook()
{
#define PLAY_FLASH_EFFECT_RET_ADDR 0x52D271
__asm {
mov ecx, ebx // CliLauncher*
push esi // ID_String&
call CliLauncher::PlayAllFlashParticles
mov eax, PLAY_FLASH_EFFECT_RET_ADDR
jmp eax
}
}
// This function has some asm setup code which redirects us to FLs original code
// to allow the flash particle to play on a given barrel index.
// This is convenient because this way we are reusing FL's own code.
NAKED void CliLauncher::PlayFlashParticleForBarrel(const ID_String& effectName, UINT barrelIndex)
{
#define GET_BARREL_INFO_FOR_FLASH_PROJ_CALL_ADDR 0x52D1DC
__asm {
sub esp, 0x58
push ebx
push ebp
push esi
push edi
mov ebx, [esp+0x6C] // CliLauncher*
mov ecx, [ebx+0x4] // CELauncher*
mov esi, [esp+0x70] // ID_String&
push [esp+0x74] // barrel index
mov eax, GET_BARREL_INFO_FOR_FLASH_PROJ_CALL_ADDR
jmp eax
}
}
// In this function we play the flash particle effect for every barrel, instead of only the first barrel.
// We do this by keeping track of a custom heap-allocated array of size n (n = amount of barrels of the launcher).
void CliLauncher::PlayAllFlashParticles(const ID_String& effectName)
{
UINT barrelAmount = this->launcher->GetProjectilesPerFire();
// Create the flash particles array if it doesn't exist yet.
// TODO: Check for potential memory leaks due to copy constructors, etc.
// Can be checked by keeping track of amount of "new" and "delete" calls and verifying whether they are the same.
if (!this->flashParticlesArr)
this->flashParticlesArr = new EffectInstance*[barrelAmount]();
for (UINT i = 0; i < barrelAmount; ++i)
{
// Clean up the previous instance.
if (this->flashParticlesArr[i])
{
this->flashParticlesArr[i]->GeneralDealloc();
this->flashParticlesArr[i] = nullptr;
}
// The PlayFlashParticleForBarrel function stores the effect instance in the currentFlashParticle variable (provided creation was successful).
// However, this offset also stores our custom array.
// So temporarily keep a copy of the original array pointer, and after calling the function,
// save the instance in the original array, and then restore the array at the original offset.
EffectInstance** ogFlashParticlesArr = this->flashParticlesArr;
PlayFlashParticleForBarrel(effectName, i);
ogFlashParticlesArr[i] = this->currentFlashParticle;
this->flashParticlesArr = ogFlashParticlesArr;
}
}
void CliLauncher::CleanFlashParticlesArr(void (EffectInstance::*deallocFunc)())
{
UINT barrelAmount = this->launcher->GetProjectilesPerFire();
// Deallocate all active flash particle instances.
for (UINT i = 0; i < barrelAmount; ++i)
{
if (flashParticlesArr[i])
(flashParticlesArr[i]->*deallocFunc)();
}
// Destruct the array.
delete[] this->flashParticlesArr;
}
// The three hooks below are there to ensure that all flash particles stored in the new array are cleaned.
// Hence we hook the instances where FL tries to clean up the individual object, and clean up the whole array instead.
// There are three different versions of this hook because for each instance the game calls a different sequence of functions for the cleaning.
void CliLauncher::CleanFlashParticlesPostGame_Hook()
{
CleanFlashParticlesArr(&EffectInstance::PostGameDealloc);
}
void CliLauncher::CleanFlashParticlesEngine_Hook()
{
CleanFlashParticlesArr(&EffectInstance::EngineDealloc);
}
void CliLauncher::CleanFlashParticlesMemory_Hook()
{
CleanFlashParticlesArr(&EffectInstance::DoFreeHeapMemory);
this->flashParticlesArr = NULL;
}
FL_FUNC(void EffectInstance::FreeAleEffect(), 0x4F8110)
FL_FUNC(int EffectInstance::FreeHeapMemory(), 0x4F7A90)
FL_FUNC(void EffectInstance::SetBaseWatcher(int unk1, int unk2, const WatcherInfo& watcherInfo), 0x4F7D20)
// In vanilla Freelancer, if you fire any launcher with a flash particle, the game explicitly plays the particle on barrel index 0 only.
// For most launchers this isn't an issue, but if you have a multi-barrel launcher, the flash effect will only play on the first barrel.
void InitFlashParticlesFix()
{
#define PLAY_FLASH_EFFECT_ADDR 0x52D1B4
#define CLI_LAUNCHER_POST_GAME_CLEANUP_ADDR 0x52CAF3
#define CLI_LAUNCHER_POST_GAME_FREE_HEAP_CALL_ADDR 0x52CB6B
#define CLI_LAUNCHER_RELEASE_MEMORY_ADDR 0x52F6B2
Hook(PLAY_FLASH_EFFECT_ADDR, PlayFlashEffect_Hook, 5, true);
BYTE ecxPatch[] = { 0x89, 0xF1, 0x90 }; // mov ecx, esi followed by nop
Patch(CLI_LAUNCHER_POST_GAME_CLEANUP_ADDR, ecxPatch, sizeof(ecxPatch) - 1); // mov ecx, esi
Patch<WORD>(CLI_LAUNCHER_POST_GAME_CLEANUP_ADDR + 0x2, 0x74EB); // jmp
Hook(CLI_LAUNCHER_POST_GAME_FREE_HEAP_CALL_ADDR, &CliLauncher::CleanFlashParticlesPostGame_Hook, 5);
Patch(CLI_LAUNCHER_RELEASE_MEMORY_ADDR, ecxPatch, sizeof(ecxPatch));
Hook(CLI_LAUNCHER_RELEASE_MEMORY_ADDR + 0x3, &CliLauncher::CleanFlashParticlesMemory_Hook, 5);
const DWORD engineDeallocCalls[] = { 0x52CD0F, 0x52D68D, 0x52D836, 0x52DBC7 };
for (const auto& call : engineDeallocCalls)
{
Nop(call, 6);
Patch(call + 0x6, ecxPatch, sizeof(ecxPatch) - 1); // mov ecx, esi
Hook(call + 0x8, &CliLauncher::CleanFlashParticlesEngine_Hook, 5);
}
}
+150
View File
@@ -0,0 +1,150 @@
#include "group_members.h"
#include "utils.h"
#include "Freelancer.h"
#include "logger.h"
#include "fl_func.h"
#define FASTCALL __fastcall
#define NEUTRAL_REP (0.0f)
FL_FUNC(AttitudeType GetAttitudeType(const IObjRW* towards, const IObjRW* from), 0x45A490)
float hostileRepThreshold = -0.6f;
int FASTCALL get_attitude_towards_Hook(const IObjRW& target, float& attitude, const IObjRW* player)
{
int result = target.get_attitude_towards(attitude, player);
// FL doesn't test the return value so why should I?
// Check if the reported attitude is hostile and if the target is a player.
// As a sanity check I'm also checking if the "player" actually is the player.
if (attitude <= hostileRepThreshold && target.is_player()
&& player && player == GetPlayerIObjRW() && player->cobject)
{
if (AreIObjRWsInSameGroup(*player, target))
{
// Set the attitude to a value such that FL's return value check thinks the ship is non-hostile.
attitude = NEUTRAL_REP;
return S_OK;
}
}
return result;
}
AttitudeType GetAttitudeType_Hook(const IObjRW* towards, const IObjRW* from)
{
// Call the original function.
AttitudeType result = GetAttitudeType(towards, from);
if (result != AttitudeType::Hostile)
return result;
// If GetAttitudeType returned Attitude::Hostile, that implies towards and from are both non-zero.
// Check if the towards object is the player and if "from" is another player.
if (from->is_player() && towards == GetPlayerIObjRW() && towards != from && towards->cobject)
{
// If they're in the same group, treat them as neutral rather than hostile.
if (AreIObjRWsInSameGroup(*towards, *from))
{
return AttitudeType::Neutral;
}
}
return result;
}
#define NEUTRAL_ATTITUDE_IDS 1589
#define GROUP_MEMBER_IDS 1551
// Prints "GROUP MEMBER" as the "ATTITUDE" in the current information window if the ship is a group member.
void GetAttitudeString_Hook(const IObjRW& towards, const IObjRW* from)
{
UINT ids;
// Is the target a group member?
if (from && from->is_player() && AreIObjRWsInSameGroup(towards, *from))
{
// TODO: GROUP_MEMBER_IDS is capitalized, as opposed to the attitude IDS'.
// It could be converted to lowercase using towlower but this may not work on localizations that use non-Latin characters.
// For now I changed the other attitude IDS' to capitalized versions as well, since the "ATTITUDE: " prefix is spelled in all caps too.
ids = GROUP_MEMBER_IDS;
}
else
{
AttitudeType attitude = GetAttitudeType(&towards, from);
ids = (UINT) ((int) NEUTRAL_ATTITUDE_IDS - attitude);
}
GetFlString(ids, FL_BUFFER_1, FL_BUFFER_LEN);
}
// Called as part of the "Closest Enemy" function.
// CShip is the player and obj is the target candidate.
// We want to ensure group members cannot be chosen as nearest enemies.
bool CShip::is_enemy_Hook(IObjInspect *obj)
{
bool enemy = is_enemy(obj);
if (enemy && obj->is_player())
{
return !AreShipsInSameGroup(this, (CShip*) obj->cobject);
}
return enemy;
}
// In Freelancer, it's not possible to enter formation with group members that are hostile to you.
// This code fixes that by checking if the player's selected target is a group member.
void InitHostileGroupFormation()
{
#define GROUP_FORMATION_REP_CHECK_COMMON_OFFSET 0x6C37C
#define HOSTILE_REP_THRESHOLD_COMMON_OFFSET 0x13F540
DWORD commonHandle = (DWORD) GetModuleHandle("common.dll");
if (commonHandle)
{
Hook(commonHandle + GROUP_FORMATION_REP_CHECK_COMMON_OFFSET, get_attitude_towards_Hook, 6);
hostileRepThreshold = *(float*) (commonHandle + HOSTILE_REP_THRESHOLD_COMMON_OFFSET);
}
else
{
Logger::PrintModuleError("InitHostileGroupFormation", "common.dll");
}
}
// Ensures hostile group members are no longer treated as hostile.
// For example if you are near a hostile group member, then you will hear the danger/battle music.
// For such group members there is also an attack marker displayed.
// These things can be quite distracting. The code below ensures they are treated as neutral instead.
void InitHostileGroupMembersFix()
{
// Doing a trampoline hook was inconvenient here, so just manually hook all the call locations,
// except for 0x475770 which should be handled by the TODO below.
const DWORD getAttitudeTypeCalls[] = {
0x48AEAB, 0x4E4950, 0x4EC10E, 0x4EC71A, 0x4EC891, 0x4F1CFF, 0x4F22E4,
0x4F2465, 0x53A98C, 0x553290, 0x5532AD, 0x553325, 0x5552A8 };
for (const auto &call : getAttitudeTypeCalls)
SetRelPointer(call + 1, GetAttitudeType_Hook);
// Fixes enemy group members being selected as "nearest enemies".
#define NEAREST_ENEMY_CHECK_ADDR (0x544A8E)
Hook(NEAREST_ENEMY_CHECK_ADDR, &CShip::is_enemy_Hook, 6);
}
// If the Current Information window is opened on a group member, this code will make it show "GROUP MEMBER" as the attitude.
void InitGroupMemberAttitudeFix()
{
#define GET_ATTITUDE_TYPE_CURRENT_INFO_ADDR 0x475770
#define CLEAN_STACK_GET_ATTITUDE_STRING_ADDR 0x47579F
#define ATTITUDE_CHECK_CURRENT_INFO_ADDR 0x4757A2
#define CLEAN_WCSCAT_STACK_ADDR 0x47580B
Nop(GET_ATTITUDE_TYPE_CURRENT_INFO_ADDR, 5); // wipe out GetAttitudeType call
GetValue<BYTE>(CLEAN_STACK_GET_ATTITUDE_STRING_ADDR + 2) -= sizeof(DWORD) * 2; // ensure "towards" and "from" remain on the stack
Hook(ATTITUDE_CHECK_CURRENT_INFO_ADDR, GetAttitudeString_Hook, 5);
Patch<WORD>(ATTITUDE_CHECK_CURRENT_INFO_ADDR + 5, 0x56EB); // Jump directly to wcscat after our hook executed
GetValue<BYTE>(CLEAN_WCSCAT_STACK_ADDR + 2) -= sizeof(DWORD) * 2; // two params were removed so do not clean them up
}
+148
View File
@@ -0,0 +1,148 @@
#include "infocards.h"
#include "Common.h"
#include "utils.h"
#include "logger.h"
std::map<UINT, UINT> msnBaseIdsInfoMap;
std::map<UINT, UINT> msnNicknameIdsInfoMap;
void ParseEntries(INI_Reader& reader, const std::map<UINT, InfocardEntry>& entries)
{
while (reader.read_header())
{
const auto it = entries.find(CreateID(reader.get_header_ptr()));
if (it == entries.end())
{
Logger::PrintInvalidHeaderWarning("ParseEntries", reader.get_header_ptr(), reader.get_file_name());
continue;
}
UINT key = 0, value = 0;
while (reader.read_value())
{
if (reader.is_value(it->second.key))
{
key = reader.get_value_id();
}
else if (reader.is_value(it->second.value))
{
value = reader.get_value_int();
}
}
it->second.map.emplace(key, value);
}
}
// Parses the MissionCreatedSolars.ini file and for every solar stores its ids_info in a map.
void ParseMsnCreatedSolars(LPCSTR iniPath)
{
INI_Reader reader;
if (!reader.open(iniPath))
{
Logger::PrintFileOpenError("ParseMsnCreatedSolars", iniPath);
return;
}
std::map<UINT, InfocardEntry> entries = {
{ CreateID("MissionCreatedSolar"), { msnBaseIdsInfoMap, "base", "ids_info" } },
{ CreateID("MissionCreatedNonDockableSolar"), { msnNicknameIdsInfoMap, "nickname", "ids_info" } }
};
ParseEntries(reader, entries);
reader.close();
}
bool FindValueInMap(std::map<UINT, UINT>& map, UINT key, UINT& foundValue)
{
auto it = map.find(key);
if (it != map.end())
{
foundValue = it->second;
return true;
}
return false;
}
void GetAltSolarIdsInfo(const CSolar* solar, UINT &idsInfo)
{
const Archetype::Solar* solarArch = solar->solararch();
if (solarArch->idsInfo)
idsInfo = solarArch->idsInfo;
// Showing the solar's own name as the infocard doesn't really add any value.
// else if (UINT solarIdsName = solar->get_name())
// idsInfo = solarIdsName;
else
idsInfo = solarArch->idsName;
}
// Function which Freelancer calls to obtain the ids infocard of the selected object in the Current Info window.
int GetInfocard_Hook(const CObject& selectedObj, const int &id, UINT &idsInfo)
{
// Is the selected object a solar?
if (const CSolar* solar = CSolar::cast(selectedObj))
{
if (solar->is_dynamic())
{
// Try to find the idsInfo in the base map.
if (solar->is_base() && FindValueInMap(msnBaseIdsInfoMap, solar->baseId, idsInfo))
return S_OK;
// Otherwise try the nickname map.
if (FindValueInMap(msnNicknameIdsInfoMap, solar->nickname, idsInfo))
return S_OK;
// GetInfocard will never return a correct infocard for dynamic solars, so don't bother calling it.
// Try the alternatives as a last resort.
GetAltSolarIdsInfo(solar, idsInfo);
return S_OK;
}
int result = Reputation::Vibe::GetInfocard(id, idsInfo);
if (!idsInfo || result != S_OK)
{
// If a non-dynamic solar doesn't have an infocard, use one of the alternatives.
GetAltSolarIdsInfo(solar, idsInfo);
return S_OK;
}
return result;
}
// If the selected object isn't a solar, get the infocard by calling the original function.
return Reputation::Vibe::GetInfocard(id, idsInfo);
}
// In Freelancer, when opening the Current Info window on a dynamic solar, it won't display its infocard.
// Presumably this happens because they are not stored by the server.
// A workaround is to first parse MissionCreatedSolars.ini and store the values.
// Then hook the get infocard function for the Current Info window, check if the selected object is a dynamic solar,
// if so, return the stored ids_info.
void InitDynamicSolarInfocards()
{
// Get the full path to MissionCreatedSolars.ini dynamically.
char fullIniPath[MAX_PATH];
strcpy_s(fullIniPath, sizeof(fullIniPath), "..\\DATA\\");
LPCSTR relIniPath = GetValue<LPCSTR>(0x476C7A); // Universe\\MissionCreatedSolars.ini
strcat_s(fullIniPath, sizeof(fullIniPath), relIniPath);
ParseMsnCreatedSolars(fullIniPath);
// Add a "push esi" instruction so we can check out the selected CObject in our hook.
#define GET_INFOCARD_CURRENT_INFO_CALL_ADDR 0x475BD8
Patch<BYTE>(GET_INFOCARD_CURRENT_INFO_CALL_ADDR, 0x56); // push esi (CObject&)
Hook(GET_INFOCARD_CURRENT_INFO_CALL_ADDR + 1, GetInfocard_Hook, 5);
// Fix the stack offset of the return value (shifted by 4 bytes due to the added parameter).
#define GET_INFOCARD_IDS_STACK_OFFSET 0x475BE1
GetValue<BYTE>(GET_INFOCARD_IDS_STACK_OFFSET) += sizeof(DWORD);
// Increase the amount of stack bytes cleaned because the GetInfocard hook takes an additional parameter.
#define GET_INFOCARD_RET_STACK_SIZE 0x475BE4
GetValue<BYTE>(GET_INFOCARD_RET_STACK_SIZE) += sizeof(DWORD);
}
+49
View File
@@ -0,0 +1,49 @@
#include "logger.h"
#include "Dacom.h"
#define NAKED __declspec(naked)
#ifdef ASM_FDUMP
NAKED void FDUMP_Asm(DumpSeverity severity, LPCSTR fmt, ...)
{
#define FL_FDUMP_IMPORT_ADDR 0x5C6D18
__asm {
mov eax, dword ptr ds:[FL_FDUMP_IMPORT_ADDR]
jmp dword ptr ds:[eax]
}
}
#define FDUMP_FUNC FDUMP_Asm
#else
#define FDUMP_FUNC FDUMP
#endif
void Logger::PrintModuleError(LPCSTR functionName, LPCSTR moduleName)
{
FDUMP_FUNC(DumpSeverity::SEV_ERROR, "FLSharp (%s): Could not get module handle \"%s\".", functionName, moduleName);
}
void Logger::PrintFileOpenError(LPCSTR functionName, LPCSTR filePath)
{
FDUMP_FUNC(DumpSeverity::SEV_ERROR, "FLSharp (%s): Could not open file \"%s\".", functionName, filePath);
}
void Logger::PrintV10Warning(LPCSTR moduleName)
{
FDUMP_FUNC(DumpSeverity::SEV_WARNING, "FLSharp: %s may be v1.0 while v1.1 is assumed. "
"Please install the official 1.1 patch, or proceed at your own risk.", moduleName);
}
void Logger::PrintInvalidFeatureWarning(LPCSTR functionName, LPCSTR featureName, LPCSTR iniPath)
{
FDUMP_FUNC(DumpSeverity::SEV_WARNING,
"FLSharp (%s): invalid feature name \"%s\" found in file \"%s\". See \"src/main.cpp\" for a full list of supported features.",
functionName, featureName, iniPath);
}
void Logger::PrintInvalidHeaderWarning(LPCSTR functionName, LPCSTR headerName, LPCSTR iniPath)
{
FDUMP_FUNC(DumpSeverity::SEV_WARNING, "FLSharp (%s): invalid header \"%s\" found in file \"%s\".",
functionName, headerName, iniPath);
}

Some files were not shown because too many files have changed in this diff Show More