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
+18
View File
@@ -0,0 +1,18 @@
build/
build-*/
bin/
obj/
release/
*.dll
*.exe
*.ilk
*.iobj
*.ipdb
*.lib
*.obj
*.pdb
*.res
*.suo
*.user
*.vcxproj
*.vcxproj.filters
+150
View File
@@ -0,0 +1,150 @@
cmake_minimum_required(VERSION 3.20)
cmake_policy(SET CMP0091 NEW)
project(rem-essentials LANGUAGES CXX)
set(FLSHARP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/flsharp")
set(FLSHARP_IMPORT_DIR "${CMAKE_CURRENT_BINARY_DIR}/third_party/flsharp")
set(FLSHARP_COMMON_LIB "${FLSHARP_IMPORT_DIR}/Common.lib")
set(FLSHARP_DALIB_LIB "${FLSHARP_IMPORT_DIR}/DALib.lib")
set(FLSHARP_DACOM_LIB "${FLSHARP_IMPORT_DIR}/Dacom.lib")
file(MAKE_DIRECTORY "${FLSHARP_IMPORT_DIR}")
add_custom_command(
OUTPUT "${FLSHARP_COMMON_LIB}"
COMMAND lib /NOLOGO /MACHINE:X86 /DEF:"${FLSHARP_DIR}/def/Common.def" /NAME:COMMON /OUT:"${FLSHARP_COMMON_LIB}"
DEPENDS "${FLSHARP_DIR}/def/Common.def"
)
add_custom_command(
OUTPUT "${FLSHARP_DALIB_LIB}"
COMMAND lib /NOLOGO /MACHINE:X86 /DEF:"${FLSHARP_DIR}/def/DALib.def" /NAME:DALIB /OUT:"${FLSHARP_DALIB_LIB}"
DEPENDS "${FLSHARP_DIR}/def/DALib.def"
)
add_custom_command(
OUTPUT "${FLSHARP_DACOM_LIB}"
COMMAND lib /NOLOGO /MACHINE:X86 /DEF:"${FLSHARP_DIR}/def/Dacom.def" /NAME:DACOM /OUT:"${FLSHARP_DACOM_LIB}"
DEPENDS "${FLSHARP_DIR}/def/Dacom.def"
)
add_custom_target(flsharp_imports DEPENDS
"${FLSHARP_COMMON_LIB}"
"${FLSHARP_DALIB_LIB}"
"${FLSHARP_DACOM_LIB}"
)
set(FLSHARP_SOURCES
third_party/flsharp/src/Freelancer.cpp
third_party/flsharp/src/alchemy_crash.cpp
third_party/flsharp/src/base_info.cpp
third_party/flsharp/src/blank_faction.cpp
third_party/flsharp/src/cheat_detection.cpp
third_party/flsharp/src/copy_paste.cpp
third_party/flsharp/src/cursor_colors.cpp
third_party/flsharp/src/dealer_fixes.cpp
third_party/flsharp/src/dll_crash.cpp
third_party/flsharp/src/exit.cpp
third_party/flsharp/src/flash_particles.cpp
third_party/flsharp/src/fl_math.cpp
third_party/flsharp/src/group_members.cpp
third_party/flsharp/src/infocards.cpp
third_party/flsharp/src/logger.cpp
third_party/flsharp/src/pilot_names.cpp
third_party/flsharp/src/projectiles.cpp
third_party/flsharp/src/rep_requirements.cpp
third_party/flsharp/src/resolutions.cpp
third_party/flsharp/src/resolutions_asm.cpp
third_party/flsharp/src/save_crash.cpp
third_party/flsharp/src/server_filter.cpp
third_party/flsharp/src/shield_capacity.cpp
third_party/flsharp/src/temp_fixes.cpp
third_party/flsharp/src/test_sounds.cpp
third_party/flsharp/src/trade_lane_lights.cpp
third_party/flsharp/src/ui_anim.cpp
third_party/flsharp/src/update.cpp
third_party/flsharp/src/utils.cpp
third_party/flsharp/src/waypoint.cpp
third_party/flsharp/src/waypoint_names.cpp
third_party/flsharp/src/weapon_anim.cpp
)
set(FLPLUSPLUS_SOURCES
src/features/flplusplus.cpp
src/features/flplusplus_common.cpp
src/features/flplusplus_config.cpp
third_party/flplusplus/src/codec.cpp
third_party/flplusplus/src/consolewindow.cpp
third_party/flplusplus/src/cursor.cpp
third_party/flplusplus/src/fontresource.cpp
third_party/flplusplus/src/Freelancer.cpp
third_party/flplusplus/src/graphics.cpp
third_party/flplusplus/src/log.cpp
third_party/flplusplus/src/patch.cpp
third_party/flplusplus/src/restart.cpp
third_party/flplusplus/src/savegame.cpp
third_party/flplusplus/src/screenshot.cpp
third_party/flplusplus/src/shippreviewscroll.cpp
third_party/flplusplus/src/startlocation.cpp
third_party/flplusplus/src/startup.cpp
third_party/flplusplus/src/thnplayer.cpp
third_party/flplusplus/src/touchpad.cpp
)
add_library(rem-essentials SHARED
src/main.cpp
src/rem/config.cpp
src/rem/feature_manager.cpp
src/rem/log.cpp
src/rem/patch.cpp
src/rem/runtime.cpp
src/rem/version_guard.cpp
src/features/alchemy_crash.cpp
src/features/jump_waypoint.cpp
src/features/mouse.cpp
src/features/quit_message.cpp
src/features/ship_buy_kick.cpp
${FLSHARP_SOURCES}
${FLPLUSPLUS_SOURCES}
)
add_dependencies(rem-essentials flsharp_imports)
target_include_directories(rem-essentials PRIVATE "${FLSHARP_DIR}/include" include third_party/flplusplus/src third_party/flplusplus/include)
target_compile_features(rem-essentials PRIVATE cxx_std_17)
target_link_libraries(rem-essentials PRIVATE
user32
gdi32
gdiplus
shell32
shlwapi
version
"${FLSHARP_COMMON_LIB}"
"${FLSHARP_DALIB_LIB}"
"${FLSHARP_DACOM_LIB}"
)
if(NOT WIN32)
message(FATAL_ERROR "rem-essentials builds a Windows Freelancer DLL.")
endif()
if(NOT MSVC)
message(FATAL_ERROR "The migrated hooks currently require 32-bit MSVC inline assembly.")
endif()
if(NOT CMAKE_SIZEOF_VOID_P EQUAL 4)
message(FATAL_ERROR "Freelancer is 32-bit; configure this project with a Win32/x86 generator.")
endif()
target_compile_options(rem-essentials PRIVATE /W4 /WX /EHsc /O2 /Zc:threadSafeInit-)
set_source_files_properties(${FLSHARP_SOURCES} PROPERTIES COMPILE_OPTIONS "/wd4005;/wd4100;/wd4200;/wd4701;/wd4703")
set_source_files_properties(${FLPLUSPLUS_SOURCES} PROPERTIES COMPILE_OPTIONS "/wd4005;/wd4068;/wd4100;/wd4189;/wd4200;/wd4244;/wd4459;/wd4996")
target_compile_definitions(rem-essentials PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX USE_ST6)
set_property(TARGET rem-essentials PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
set_target_properties(rem-essentials PROPERTIES
OUTPUT_NAME "rem-essentials"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
+37
View File
@@ -0,0 +1,37 @@
# rem-essentials
`rem-essentials` is a Freelancer plugin for the REM mod that collects the
client/server fixes REM actually needs. The goal is to keep a single, small,
configurable feature surface that works on native Windows and under
Wine/Proton.
## Feature surface
Every feature is individually configurable through `rem-essentials.ini`.
Features cover the mouse/cursor stack, server-side fixes, client stability
patches, navigation, graphics, saves, screenshots, logging, and more.
The Proton-sensitive mouse stack is split into separate switches:
- hide the in-game cursor when the real cursor is outside the game window
- prevent Windows cursor flicker at screen borders in borderless windowed mode
- prevent mouse warps to the center of the game window in windowed modes
- no server kick when buying the same ship again
- avoid the quit-message loop hang after the game receives `Quit`
- patch the rare `alchemy.dll` crash around file offset `0x77cb`
## Documentation
See [docs/architecture.md](docs/architecture.md) for feature registration rules
and [docs/build.md](docs/build.md) for the current MSVC/x86 build path.
See [docs/deployment.md](docs/deployment.md) for release validation and packaging.
See [docs/configuration.md](docs/configuration.md) for INI precedence, Launcher
overrides, manual installation, and save-directory behavior under Proton.
## Design rules
- Keep every fix independently configurable.
- Keep client-only and server-only fixes separate.
- Treat mouse/cursor fixes as Proton-sensitive and test them under Wine/Proton
before enabling them by default.
- Prefer small, named fixes over broad plugin behavior.
+43
View File
@@ -0,0 +1,43 @@
# Architecture
`rem-essentials` is intentionally feature-first. Every migrated behavior should
live in its own module and be registered as a named feature in `src/main.cpp`.
## Feature registration
Each feature has:
- a stable config key
- an init function
- an optional cleanup function
- a runtime side: `Client`, `Server`, or `Always`
- a code default
The INI file can override each default. Missing INI entries keep the compiled
default.
## Current feature keys
The full key list lives in `rem-essentials.ini`. Feature switches live in
`[rem-essentials]`; typed parameters used by the graphics, saves, and
screenshots behavior live in the `[flplusplus]` section.
## Design rules
- Prefer one feature flag per behavior, even when multiple behaviors share one
option.
- Keep client and server hooks in separate files.
- Resolve optional Freelancer imports at runtime when practical.
- Keep adapted third-party code isolated under `third_party/` and wrap it with
thin REM adapters instead of adding a second plugin entry point.
## Proton-sensitive fixes
Mouse features stay split because Wine/Proton can handle cursor APIs
differently from native Windows. The current conservative defaults are:
- `cursor_visibility_fix = true`
- `cursor_border_flicker_fix = false`
- `mouse_warp_fix = false`
These can be enabled per player in `rem-essentials.ini` after testing.
+37
View File
@@ -0,0 +1,37 @@
# Build
The migrated hooks currently require 32-bit MSVC because some patches use
`__declspec(naked)` and MSVC inline assembly.
## Requirements
- Visual Studio with C++ tools
- CMake 3.20 or newer; the Visual Studio bundled CMake works
- 32-bit/x86 compiler environment
## Configure and build
From this workspace, the verified command is:
```powershell
cmd /s /c '"C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars32.bat" && "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" -S . -B build-nmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release && "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" --build build-nmake'
```
The DLL is written to:
```text
build-nmake/bin/rem-essentials.dll
```
Copy `rem-essentials.dll` and `rem-essentials.ini` to Freelancer's `EXE`
directory and add the DLL to the `[Libraries]` section of `dacom.ini` and, for
server-side features, `dacomsrv.ini`.
## Current local verification status
Verified on this machine with Visual Studio 2022 Community's x86 developer
environment. The produced DLL was:
```text
build-nmake/bin/rem-essentials.dll
```
+138
View File
@@ -0,0 +1,138 @@
# Configuration
`rem-essentials` uses one active runtime configuration and, when the REM
Launcher is used, one persistent user override file.
## Configuration files
### Active game configuration
The DLL reads only this file at game startup:
```text
Freelancer/EXE/rem-essentials.ini
```
The file is shipped with the mod and contains the complete setting schema,
descriptions, metadata, and release defaults. A manual installation only needs
this INI and `rem-essentials.dll` in the `EXE` directory.
If the active INI is missing, the DLL uses its compiled defaults. The compiled
defaults intentionally match the shipped INI, but distributing the INI is
recommended because it exposes every feature and typed parameter.
### Launcher user overrides
The REM Launcher stores player choices separately:
```text
My Games/REM/rem-essentials.user.ini
```
On Wine or Proton, `My Games` is inside the Windows user profile of the active
prefix. Its exact Linux path depends on how the Launcher or game prefix was
created.
The DLL does not read this user file. The Launcher merges matching values from
the user file into `Freelancer/EXE/rem-essentials.ini`:
- after a game/mod update
- after saving settings in the Launcher
- immediately before starting Freelancer
This allows an update to replace the shipped schema while retaining player
choices. New settings from an updated default INI appear automatically. Values
from the user override win when the same section and key exist in both files.
## Value precedence
At runtime, the effective value is determined in this order:
1. The compiled DLL fallback is used when no setting can be read.
2. `Freelancer/EXE/rem-essentials.ini` overrides the compiled fallback.
3. Before launch, the Launcher materializes values from
`rem-essentials.user.ini` into the active INI.
For a manual installation without the Launcher, only the first two levels
apply. Editing the active INI is therefore sufficient.
## Sections and value types
Feature switches live in `[rem-essentials]`. Parameters used by the graphics,
saves, and screenshots behavior live in `[flplusplus]`.
Boolean values accept:
```text
true / false
1 / 0
yes / no
on / off
```
Integer, float, and string settings are described by the comments immediately
above each key. Metadata such as `type`, `min`, `max`, `default`, `group`, and
`risk` is also used by the Launcher to build the settings UI.
## Save directory behavior
REM keeps the historical installation-local save directory as its default:
```ini
[rem-essentials]
save_path_feature = true
[flplusplus]
save_in_directory = true
save_folder_name = Freelancer
```
With `save_in_directory = true`, the patched path is calculated relative to
`Freelancer.exe`:
```text
Freelancer/EXE/../SAVE
```
For an installation such as:
```text
/home/<user>/RemLauncher_AppData/current/Freelancer/EXE/Freelancer.exe
```
the effective save directory is:
```text
/home/<user>/RemLauncher_AppData/current/Freelancer/SAVE
```
With `save_in_directory = false`, the plugin instead uses the Windows
Documents folder exposed by Windows, Wine, or Proton and appends:
```text
My Games/<save_folder_name>
```
`save_folder_name` is therefore only relevant when
`save_in_directory = false`. Setting `save_path_feature = false` disables this
hook completely and restores Freelancer's unpatched path behavior.
Changing the setting does not move existing saves. Move existing files to the
selected directory before launching when switching between layouts.
## Manual installation
1. Copy `rem-essentials.dll` and `rem-essentials.ini` into `Freelancer/EXE`.
2. Add `rem-essentials.dll` to the appropriate `[Libraries]` section in
`dacom.ini` and, for FLServer features, `dacomsrv.ini`.
3. Ensure `save_path_feature = true` and `save_in_directory = true` for the REM
installation-local `SAVE` layout.
4. Fully restart Freelancer after changing the INI. Settings are read when the
DLL is loaded.
## Launcher-managed installation
Use the Game Settings page to change supported values. Saving creates or
updates `My Games/REM/rem-essentials.user.ini`. Avoid manually changing the
active INI while a conflicting user override exists, because the Launcher will
materialize the user value again before game startup.
+40
View File
@@ -0,0 +1,40 @@
# Deployment
## Build and package
Run the x86 build from the Visual Studio developer environment described in
`docs/build.md`, then package the verified output:
```powershell
.\tools\package-release.ps1
```
The command verifies that:
- every registered feature has exactly one INI toggle
- the Launcher and DLL ship the same default INI
- `direct_ips` is not compiled
- the release DLL is x86 and matches the current build
- the Launcher bridge and post-update merge are present
The generated artifacts are:
```text
release/rem-essentials.dll
release/rem-essentials-release.zip
```
The ZIP can be overlaid on the Freelancer installation. It contains the DLL and
default INI under `EXE/`, plus configuration documentation.
## Loader configuration
Add `rem-essentials.dll` to the `[Libraries]` section in client `dacom.ini` and
server `dacomsrv.ini`.
The Launcher keeps player overrides in
`My Games/REM/rem-essentials.user.ini`. It reapplies them after patching and
immediately before starting Freelancer.
The complete INI lifecycle, precedence rules, and save-directory mapping are
documented in [configuration.md](configuration.md).
+7
View File
@@ -0,0 +1,7 @@
#pragma once
namespace rem::features
{
void InitAlchemyCrashFix();
}
+21
View File
@@ -0,0 +1,21 @@
#pragma once
namespace rem::features
{
void InitFlplusplusTimestampedLogs();
void InitFlplusplusConsoleLog();
void InitFlplusplusSavePath();
void InitFlplusplusRestartRegeneration();
void InitFlplusplusGraphicsBaseFixes();
void InitFlplusplusDetailScaling();
void InitFlplusplusPngScreenshots();
void InitFlplusplusScreenshotPath();
void InitFlplusplusWineMp3CodecWarningFix();
void InitFlplusplusStartLocationWarningFix();
void InitFlplusplusFontFileLoading();
void InitFlplusplusThnPlayer();
void InitFlplusplusShipPreviewScroll();
void InitFlplusplusFailedSaveDirIds();
void InitFlplusplusTouchpadSupport();
void InitFlplusplusCursorConfinement();
}
+37
View File
@@ -0,0 +1,37 @@
#pragma once
void InitBetterUpdates();
void InitWaypointFixes();
void InitWaypointNameFixes();
void InitProjectilesSoundFix();
void InitProjectilesServerFix();
void InitBetterResolutions();
void CleanupBetterResolutions();
void InitTestSounds();
void InitTradeLaneLightsFix();
void InitCopyPasteFeature();
void InitSlideUiAnimFix();
void InitWeaponAnimFix();
void InitFlashParticlesFix();
void InitPrintRepRequirements();
void InitPostGameDeadlockFix();
void InitQuitMessageFix();
void CleanupQuitMessageFix();
void InitFlightControlsFix();
void InitDynamicSolarInfocards();
void InitSaveCrashFix();
void InitAlchemyCrashFix();
void InitBlankFactionNameFix();
void InitServerFilterCrashFix();
void InitServerFilterSpeedFix();
void InitMissingDllCrashFix();
void InitShieldCapacityFix();
void InitDealerOpenFix();
void InitDealerCrashFix();
void InitShipBuyKickFix();
void InitHostileGroupFormation();
void InitHostileGroupMembersFix();
void InitGroupMemberAttitudeFix();
void InitMoreCursorColors();
void InitBaseInfoSpacingFix();
void InitPilotNamesFix();
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <windows.h>
namespace rem::features
{
struct Waypoint
{
float pos[3];
UINT system;
UINT target;
int waypointNumber;
};
struct JumpDestinationSolar
{
BYTE x000[0x1B4];
UINT destinationSystem;
UINT destinationGate;
};
void InitJumpDestinationWaypointFix();
}
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include <windows.h>
#include "rem/vftable.h"
#define REM_STDCALL __stdcall
namespace rem::features
{
void InitCursorVisibilityFix();
void InitCursorBorderFlickerFix();
void InitMouseWarpFix();
}
struct IDirectInputDevice8_Rem
{
REM_FILL_VFTABLE(0)
virtual void Vftable_10();
virtual void Vftable_14();
virtual void Vftable_18();
virtual long REM_STDCALL Acquire();
virtual long REM_STDCALL Unacquire();
virtual void Vftable_24();
virtual void Vftable_28();
virtual void Vftable_2C();
virtual void Vftable_30();
virtual long REM_STDCALL SetCooperativeLevel(HWND hwnd, DWORD flags);
};
+8
View File
@@ -0,0 +1,8 @@
#pragma once
namespace rem::features
{
void InitQuitMessageFix();
void CleanupQuitMessageFix();
}
+58
View File
@@ -0,0 +1,58 @@
#pragma once
#include <windows.h>
#include "rem/fl_types.h"
#include "rem/st6.h"
namespace rem::features
{
struct PlayerData
{
BYTE x00[0x264];
UINT currentShipId;
BYTE x268[0xBC];
UINT shipIdOnLand;
};
struct BaseGood
{
BYTE x00[0x8];
UINT goodId;
float price;
int minQuantity;
int maxQuantity;
DWORD unknown18;
bool IsShipCandidate() const
{
return unknown18 == 0 || unknown18 == 2;
}
};
struct BaseGoodCollection
{
UINT baseName;
UINT launchpadName;
DWORD unknown08;
float unknown0C;
st6::list<BaseGood> goods;
bool HasShipPackageWithGood(UINT goodId);
};
struct MarketGood
{
BYTE x00[0x10];
DWORD type;
};
struct BaseMarket
{
UINT baseName;
BaseGoodCollection* baseGoods;
};
void InitShipBuyKickFix();
}
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include <windows.h>
#include <string>
#include <unordered_map>
#include "rem/feature_manager.h"
namespace rem
{
class Config
{
public:
void Load(HINSTANCE module);
bool GetBool(const char* section, const char* key, bool fallback) const;
int GetInt(const char* section, const char* key, int fallback) const;
float GetFloat(const char* section, const char* key, float fallback) const;
std::string GetString(const char* section, const char* key, const char* fallback = "") const;
private:
std::string GetValue(const char* section, const char* key) const;
std::unordered_map<std::string, std::unordered_map<std::string, std::string>> sections_;
};
Config& GetConfig();
void ReadConfig(HINSTANCE module, FeatureManager& manager);
}
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <windows.h>
#include <string>
#include <vector>
namespace rem
{
class Config;
enum class RuntimeSide
{
Always,
Client,
Server,
};
struct Feature
{
std::string name;
void (*init)();
void (*cleanup)();
RuntimeSide side;
bool enabled;
bool initialized;
};
class FeatureManager
{
public:
void Register(const char* name, void (*init)(), void (*cleanup)(), RuntimeSide side, bool defaultEnabled);
bool SetEnabled(const char* name, bool enabled);
void ApplyConfig(const Config& config);
void InitFeatures();
void CleanupFeatures();
private:
bool Applies(const Feature& feature) const;
std::vector<Feature> features_;
};
}
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include <windows.h>
#include "rem/st6.h"
namespace rem::fl
{
struct EquipDesc
{
DWORD unknown;
UINT archId;
};
struct EquipDescList
{
st6::list<EquipDesc> list;
};
enum class GoodType : DWORD
{
Commodity = 0,
Hull = 2,
Ship = 3,
};
struct GoodInfo
{
BYTE x00[0x4C];
GoodType type;
BYTE x50[0x4];
UINT shipId;
BYTE x58[0x38];
UINT shipHullId;
EquipDescList equipDescLists[3];
};
}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include <windows.h>
namespace rem::log
{
void Info(const char* message);
void Warn(const char* message);
void Error(const char* message);
void ModuleMissing(const char* feature, const char* moduleName);
void ProcMissing(const char* feature, const char* procName);
void UnknownFeature(const char* featureName);
void FeatureInitFailed(const char* featureName, DWORD exceptionCode);
}
+76
View File
@@ -0,0 +1,76 @@
#pragma once
#include <windows.h>
#include <cassert>
#include <cstring>
#include <initializer_list>
namespace rem
{
void Patch(DWORD address, const void* data, UINT length);
void PatchBytes(DWORD address, std::initializer_list<BYTE> bytes);
void Nop(DWORD address, UINT length);
void ReadWriteProtect(DWORD address, DWORD size);
template <typename Type>
inline void Patch(DWORD address, Type value)
{
Patch(address, &value, sizeof(Type));
}
template <typename Type>
inline Type& ValueAt(DWORD address)
{
ReadWriteProtect(address, sizeof(Type));
return *reinterpret_cast<Type*>(address);
}
template <typename Func>
inline Func FunctionAt(DWORD address)
{
return reinterpret_cast<Func>(address);
}
template <typename Func>
Func SetRelPointer(DWORD location, Func hook)
{
DWORD& relative = ValueAt<DWORD>(location);
DWORD original = location + relative + 4;
DWORD hookAddress = *reinterpret_cast<DWORD*>(&hook);
relative = hookAddress - (location + 4);
return FunctionAt<Func>(original);
}
template <typename Func>
void Hook(DWORD address, Func hook, UINT instructionLength, bool jump = false)
{
assert(instructionLength >= 5);
Patch<BYTE>(address, jump ? 0xE9 : 0xE8);
SetRelPointer(address + 1, hook);
if (instructionLength > 5)
Nop(address + 5, instructionLength - 5);
}
template <typename Func>
Func Trampoline(DWORD address, Func hook, UINT instructionLength)
{
BYTE* gateway = static_cast<BYTE*>(
VirtualAlloc(nullptr, instructionLength + 5, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE));
ReadWriteProtect(address, instructionLength);
memcpy(gateway, reinterpret_cast<void*>(address), instructionLength);
Hook(address, hook, instructionLength, true);
Hook(reinterpret_cast<DWORD>(gateway + instructionLength), FunctionAt<Func>(address + instructionLength), 5, true);
return FunctionAt<Func>(reinterpret_cast<DWORD>(gateway));
}
template <typename Func>
void CleanupTrampoline(Func trampoline)
{
VirtualFree(reinterpret_cast<void*>(*reinterpret_cast<DWORD*>(&trampoline)), 0, MEM_RELEASE);
}
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <windows.h>
namespace rem
{
bool IsServer();
bool IsClient();
bool IsWine();
HMODULE GetModule(const char* moduleName);
}
+64
View File
@@ -0,0 +1,64 @@
#pragma once
#include <cstddef>
namespace st6
{
template<class T>
class allocator
{
public:
using size_type = size_t;
using difference_type = ptrdiff_t;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using value_type = T;
};
template <class T, class A = allocator<T>>
class list
{
struct Node;
using NodePtr = Node*;
struct Node
{
NodePtr next;
NodePtr prev;
T value;
};
public:
class iterator
{
public:
iterator() : ptr_(nullptr) {}
explicit iterator(NodePtr ptr) : ptr_(ptr) {}
T& operator*() const { return ptr_->value; }
T* operator->() const { return &ptr_->value; }
iterator& operator++()
{
ptr_ = ptr_->next;
return *this;
}
bool operator==(const iterator& other) const { return ptr_ == other.ptr_; }
bool operator!=(const iterator& other) const { return ptr_ != other.ptr_; }
private:
NodePtr ptr_;
};
iterator begin() { return iterator(head_->next); }
iterator end() { return iterator(head_); }
A allocator_;
NodePtr head_;
size_t size_;
};
}
+6
View File
@@ -0,0 +1,6 @@
#pragma once
namespace rem
{
bool IsSupportedGameVersion();
}
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#define REM_FILL_VFTABLE(prefix) \
virtual void Vftable_##prefix##0(); \
virtual void Vftable_##prefix##4(); \
virtual void Vftable_##prefix##8(); \
virtual void Vftable_##prefix##C();
+258
View File
@@ -0,0 +1,258 @@
[rem-essentials]
; type=bool group=Server default=true
; Do not kick players from the server for re-purchasing the same ship.
ship_buy_kick_fix = true
; type=bool group=Client Stability default=true
; Fix Freelancer sometimes getting stuck in the message loop after the Quit message is retrieved.
quit_message_fix = true
; type=bool group=Client Stability default=true
; Fix rare alchemy.dll crashes around offsets 0x701b and 0x77cb.
alchemy_crash_fix = true
; type=bool group=Navigation default=true
; Clear the jump destination waypoint after a successful system jump.
jump_destination_waypoint_fix = true
; type=bool group=Mouse default=true
; Hide the in-game cursor when the real cursor is outside the game window.
cursor_visibility_fix = true
; type=bool group=Mouse default=false risk=proton
; Fix Windows cursor flicker at screen borders in borderless windowed mode.
cursor_border_flicker_fix = false
; type=bool group=Mouse default=false risk=proton
; Prevent mouse warps to the center of the game window in windowed modes.
mouse_warp_fix = false
; type=bool group=Client Updates default=true
; Smooth client-server updates and fix incorrect engine-state updates.
better_updates = true
; type=bool group=Navigation default=true
; Keep cross-system waypoints and prevent setting waypoints on the player ship.
waypoint_fixes = true
; type=bool group=Navigation default=true
; Fix waypoint names and waypoint information in target/current-info views.
waypoint_name_fixes = true
; type=bool group=Combat default=true
; Play one-shot sounds for multi-barrel launchers.
projectiles_sound_fix = true
; type=bool group=Combat default=true
; Fix ammo decrementing for multi-barrel launchers on client/server.
projectiles_server_fix = true
; type=bool group=Display default=false
; Expand selectable resolutions and use the main monitor as default.
better_resolutions = false
; type=bool group=Audio default=true
; Add interface/ambience test sounds and safer volume slider behavior.
more_test_sounds = true
; type=bool group=World default=true
; Fix trade-lane lights not turning back on after disruptions.
trade_lane_lights_fix = true
; type=bool group=UI default=false
; Enable Ctrl+C/Ctrl+V in Freelancer input boxes.
copy_paste_feature = false
; type=bool group=UI default=true
; Improve slide-out animation for multiplayer menu buttons.
slide_ui_anim_fix = true
; type=bool group=Equipment default=true
; Enable weapon use_animation scripts and parent animations with leading underscore.
weapon_anim_fix = true
; type=bool group=Equipment default=true
; Play weapon flash particles on all barrels instead of only the first.
flash_particle_fix = true
; type=bool group=Dealer default=false
; Print exact reputation requirements in dealer rejection text when resources support it.
print_rep_requirements = false
; type=bool group=Client Stability default=true
; Move DirectPlay shutdown before process exit to avoid post-game deadlocks.
post_game_deadlock_fix = true
; type=bool group=Controls default=true
; Reset rotation lock and auto-level defaults when launching to space.
flight_controls_fix = true
; type=bool group=Infocards default=false
; Show infocards for dynamic solars and archetype fallback infocards.
dynamic_solar_infocards = false
; type=bool group=Client Stability default=true
; Prevent crashes from malformed save files.
save_crash_fix = true
; type=bool group=Infocards default=true
; Fix blank faction text for fc_uk_grp ships.
blank_faction_fix = true
; type=bool group=Client Stability default=true
; Fix crash when closing the Server Filter dialog.
server_filter_crash_fix = true
; type=bool group=Client Stability default=true
; Fix sudden game-speed increases after opening the Server Filter dialog.
server_filter_speed_fix = true
; type=bool group=Client Stability default=true
; Prevent Freelancer and FLServer crashing on missing DLL entries in freelancer.ini.
freelancer_dll_crash_fix = true
; type=bool group=Infocards default=true
; Show shield capacity reduced by offline_threshold.
shield_capacity_fix = true
; type=bool group=Dealer default=true
; Fix dealer menus not opening when clicking the dealer twice.
dealer_menu_open_fix = true
; type=bool group=Dealer default=true
; Fix crashes that can occur when opening dealer menus.
dealer_crash_fix = true
; type=bool group=Groups default=false
; Allow entering formation with hostile group members.
hostile_group_formation = false
; type=bool group=Groups default=true
; Prevent hostile group members from being shown as hostile.
unhostile_group_members = true
; type=bool group=Groups default=true
; Show GROUP MEMBER attitude for group members.
group_member_attitude = true
; type=bool group=Targeting default=false
; Add custom targeting cursor colors for group members and trade request senders.
more_cursor_colors = false
; type=bool group=Infocards default=true
; Fix first sold-item category entry alignment in Current Information on some resolutions.
base_info_spacing_fix = true
; type=bool group=Infocards default=true
; Fix long pilot names being truncated in Current Information, comms, and cargo dialogs.
pilot_names_fix = true
; type=bool group=Logging default=true
; Add timestamps to FLSpew.txt and server log output.
timestamped_logs = true
; type=bool group=Logging default=false
; Redirect FLSpew output to a console window.
log_to_console = false
; type=bool group=Saves default=true
; Use the configured save folder path instead of Freelancer's hard-coded default.
save_path_feature = true
; type=bool group=Wine default=true
; Suppress MP3 codec warning spam on Wine/Proton.
wine_mp3_codec_warning_fix = true
; type=bool group=Logging default=true
; Remove the Failed to get start location warning.
remove_start_location_warning = true
; type=bool group=Saves default=true
; Regenerate Restart.fl on startup to avoid malformed restart-file crashes.
always_regenerate_restart_file = true
; type=bool group=Screenshots default=false
; Replace default screenshots with PNG output.
png_screenshots = false
; type=bool group=Screenshots default=true
; Use the configured screenshot folder instead of Freelancer's default path.
screenshot_path_feature = true
; type=bool group=Fonts default=false
; Load private TTF font resources from DATA/FONTS/fonts.ini [FontFiles].
font_file_loading = false
; type=bool group=Developer default=false
; Enable THN player behavior when Freelancer is launched with -thn.
thn_player = false
; type=bool group=Input default=false risk=mouse
; Fix touchpad scrolling but may break normal mouse wheel scrolling.
touchpad_support = false
; type=bool group=Mouse default=false risk=mouse
; Confine the in-game cursor in windowed mode.
confine_cursor = false
; type=bool group=Dealer default=false
; Enable mouse-wheel zoom in the ship dealer preview.
ship_preview_scroll = false
; type=bool group=Graphics default=true
; Disable unsupported video dialog, fix max texture size, and suppress Vibrocentric font override.
graphics_base_fixes = true
; type=bool group=Graphics default=true
; Apply configurable LOD, pBubble, character-detail, and asteroid-distance scaling.
graphics_detail_scaling = true
; type=bool group=Saves default=true
; Override the IDS shown when Freelancer cannot initialize the saves directory.
failed_save_dir_ids_override = true
[flplusplus]
; type=float min=1 max=1000000 group=Graphics default=1.0
lod_scale = 1.0
; type=float min=1 max=1000000 group=Graphics default=1.0
pbubble_scale = 1.0
; type=float min=1 max=1000000 group=Graphics default=1.0
character_detail_scale = 1.0
; type=float min=1 max=10 group=Graphics default=1.0
asteroid_dist_scale = 1.0
; type=string group=Saves default=Freelancer
save_folder_name = Freelancer
; type=bool group=Saves default=true
save_in_directory = true
; type=string group=Screenshots default=FreelancerShots
screenshots_folder_name = FreelancerShots
; type=bool group=Screenshots default=false
screenshots_in_directory = false
; type=bool group=Screenshots risk=proton default=true
alternative_fullscreen_screenshots_code = true
; type=bool group=Screenshots risk=proton default=false
alternative_windowed_screenshots_code = false
; type=int min=0 group=Saves default=1849
failed_to_init_saves_dir_ids = 1849
; type=float min=0 max=50 group=Dealer default=3.0
ship_preview_scrolling_speed = 3.0
; type=bool group=Dealer default=false
ship_preview_scrolling_inverse = false
; type=float min=0 max=100 group=Dealer default=0.0
ship_preview_scrolling_min_distance = 0.0
; type=float min=0 max=100 group=Dealer default=100.0
ship_preview_scrolling_max_distance = 100.0
+67
View File
@@ -0,0 +1,67 @@
#include "features/alchemy_crash.h"
#include <windows.h>
#include "rem/log.h"
#include "rem/patch.h"
#define REM_FASTCALL __fastcall
namespace
{
struct Alchemy
{
float progress;
void* effect;
};
struct AleLoop
{
int startOffset;
BYTE maxProgressOffset;
};
const Alchemy* REM_FASTCALL GetFinishedAle(int maxIndex, const Alchemy* aleArray, float maxProgress)
{
int index = 0;
for (; index < maxIndex - 1; ++index)
{
if (maxProgress < aleArray[index + 1].progress)
break;
}
return &aleArray[index];
}
}
namespace rem::features
{
void InitAlchemyCrashFix()
{
constexpr int getFinishedAleStartToEnd = 0x1A;
HMODULE alchemy = GetModuleHandleA("alchemy.dll");
if (!alchemy)
{
log::ModuleMissing("alchemy_crash_fix", "alchemy.dll");
return;
}
const DWORD base = reinterpret_cast<DWORD>(alchemy);
const AleLoop aleLoops[] = {
{ 0x6FDD, 0x10 },
{ 0x778D, 0x10 },
};
for (const AleLoop& loop : aleLoops)
{
const DWORD start = base + loop.startOffset;
PatchBytes(start, { 0x89, 0xF2, 0xFF, 0x74, 0x24 });
Patch<BYTE>(start + 5, loop.maxProgressOffset);
Hook(start + 6, GetFinishedAle, 20);
Patch<WORD>(start + getFinishedAleStartToEnd, 0xC689);
}
}
}
+175
View File
@@ -0,0 +1,175 @@
#include "features/flplusplus.h"
#include "codec.h"
#include "config.h"
#include "consolewindow.h"
#include "cursor.h"
#include "fontresource.h"
#include "graphics.h"
#include "log.h"
#include "rem/runtime.h"
#include "rem/log.h"
#include "restart.h"
#include "savegame.h"
#include "screenshot.h"
#include "shippreviewscroll.h"
#include "startlocation.h"
#include "startup.h"
#include "thnplayer.h"
#include "touchpad.h"
#include <shlwapi.h>
namespace
{
char g_dataPath[MAX_PATH]{};
bool g_pathsInitialized = false;
void EnsureConfig()
{
config::EnsureInitialized();
}
const char* GetDataPath()
{
if (g_pathsInitialized)
return g_dataPath;
char exePath[MAX_PATH]{};
GetModuleFileNameA(nullptr, exePath, MAX_PATH);
PathRemoveFileSpecA(exePath);
strcpy_s(g_dataPath, exePath);
PathRemoveFileSpecA(g_dataPath);
PathAppendA(g_dataPath, "DATA\\");
g_pathsInitialized = true;
return g_dataPath;
}
void ReadFontFiles()
{
char fontsIniPath[MAX_PATH]{};
strcpy_s(fontsIniPath, GetDataPath());
PathAppendA(fontsIniPath, "FONTS\\fonts.ini");
if (PathFileExistsA(fontsIniPath))
config::read_font_files(fontsIniPath);
}
}
namespace rem::features
{
void InitFlplusplusTimestampedLogs()
{
EnsureConfig();
if (!logger::enable_fdump(true, false))
rem::log::ProcMissing("timestamped_logs", "FDUMP");
if (rem::IsServer())
if (!logger::patch_serverlogf())
rem::log::ProcMissing("timestamped_logs", "ServerLogF");
}
void InitFlplusplusConsoleLog()
{
EnsureConfig();
if (config::get_config().logtoconsole)
{
RedirectIOToConsole();
if (!logger::enable_fdump(false, true))
rem::log::ProcMissing("log_to_console", "FDUMP");
}
}
void InitFlplusplusSavePath()
{
EnsureConfig();
if (!savegame::init())
rem::log::ProcMissing("save_path_feature", "?GetUserDataPath@@YA_NQAD@Z");
}
void InitFlplusplusRestartRegeneration()
{
EnsureConfig();
if (!restart::init())
rem::log::ModuleMissing("always_regenerate_restart_file", "server.dll");
}
void InitFlplusplusGraphicsBaseFixes()
{
EnsureConfig();
if (!graphics::init_base_fixes())
rem::log::ModuleMissing("graphics_base_fixes", "common.dll");
}
void InitFlplusplusDetailScaling()
{
EnsureConfig();
if (!graphics::init_detail_scaling())
rem::log::ModuleMissing("graphics_detail_scaling", "common.dll");
}
void InitFlplusplusPngScreenshots()
{
EnsureConfig();
if (!screenshot::init_png())
rem::log::Error("png_screenshots failed to initialize");
}
void InitFlplusplusScreenshotPath()
{
EnsureConfig();
if (!screenshot::init_path())
rem::log::ProcMissing("screenshot_path_feature", "?GetScreenShotPath@@YA_NQAD@Z");
}
void InitFlplusplusWineMp3CodecWarningFix()
{
EnsureConfig();
codec::init();
}
void InitFlplusplusStartLocationWarningFix()
{
EnsureConfig();
startlocation::init();
}
void InitFlplusplusFontFileLoading()
{
EnsureConfig();
ReadFontFiles();
fontresource::init(GetDataPath());
}
void InitFlplusplusThnPlayer()
{
EnsureConfig();
if (!thnplayer::init())
rem::log::ProcMissing("thn_player", "THN runtime exports");
}
void InitFlplusplusShipPreviewScroll()
{
EnsureConfig();
shippreviewscroll::init();
}
void InitFlplusplusFailedSaveDirIds()
{
EnsureConfig();
startup::init();
}
void InitFlplusplusTouchpadSupport()
{
EnsureConfig();
touchpad::init();
}
void InitFlplusplusCursorConfinement()
{
EnsureConfig();
cursor::init();
}
}
+31
View File
@@ -0,0 +1,31 @@
#include "../../third_party/flplusplus/src/Common.h"
namespace
{
template <typename Func>
Func ResolveCommon(const char* name)
{
HMODULE common = GetModuleHandleA("common.dll");
if (!common)
common = LoadLibraryA("common.dll");
return common ? reinterpret_cast<Func>(GetProcAddress(common, name)) : nullptr;
}
}
namespace Universe
{
IBase* get_base(UINT id)
{
using GetBase = IBase* (__cdecl*)(UINT);
static GetBase function = ResolveCommon<GetBase>("?get_base@Universe@@YAPAUIBase@1@I@Z");
return function ? function(id) : nullptr;
}
ISystem* get_system(UINT id)
{
using GetSystem = ISystem* (__cdecl*)(UINT);
static GetSystem function = ResolveCommon<GetSystem>("?get_system@Universe@@YAPBUISystem@1@I@Z");
return function ? function(id) : nullptr;
}
}
+117
View File
@@ -0,0 +1,117 @@
#include "config.h"
#include "Common.h"
#include "rem/config.h"
#include "rem/runtime.h"
#include <shlwapi.h>
namespace
{
config::ConfigData g_config;
bool g_initialized = false;
bool ReadBool(const char* key, bool fallback)
{
const rem::Config& config = rem::GetConfig();
return config.GetBool("rem-essentials", key, config.GetBool("flplusplus", key, fallback));
}
}
namespace config
{
ConfigData& get_config()
{
return g_config;
}
bool is_wine()
{
return rem::IsWine();
}
void init_defaults()
{
g_config.lodscale = 1.0f;
g_config.pbubblescale = 1.0f;
g_config.characterdetailscale = 1.0f;
g_config.asteroiddistscale = 1.0f;
g_config.savefoldername = "Freelancer";
g_config.saveindirectory = true;
g_config.screenshotsfoldername = "REM Screenshots";
g_config.screenshotsindirectory = false;
g_config.altfullscreenscreenshots = rem::IsWine();
g_config.altwindowedscreenshots = false;
g_config.removestartlocationwarning = true;
g_config.logtoconsole = false;
g_config.shippreviewscrollingspeed = 3.0f;
g_config.shippreviewscrollinginverse = false;
g_config.shippreviewscrollingmindistance = 0.0f;
g_config.shippreviewscrollingmaxdistance = 100.0f;
g_config.alwaysregeneraterestartfile = true;
g_config.failedtoinitsavesdirids = 1849;
g_config.touchpadsupport = false;
g_config.confinecursor = false;
g_config.fontfiles.clear();
}
void init_from_file(const char*)
{
init_defaults();
const rem::Config& config = rem::GetConfig();
g_config.lodscale = config.GetFloat("flplusplus", "lod_scale", g_config.lodscale);
g_config.pbubblescale = config.GetFloat("flplusplus", "pbubble_scale", g_config.pbubblescale);
g_config.characterdetailscale = config.GetFloat("flplusplus", "character_detail_scale", g_config.characterdetailscale);
g_config.asteroiddistscale = config.GetFloat("flplusplus", "asteroid_dist_scale", g_config.asteroiddistscale);
g_config.savefoldername = config.GetString("flplusplus", "save_folder_name", g_config.savefoldername.c_str());
g_config.saveindirectory = config.GetBool("flplusplus", "save_in_directory", g_config.saveindirectory);
g_config.screenshotsfoldername = config.GetString("flplusplus", "screenshots_folder_name", g_config.screenshotsfoldername.c_str());
g_config.screenshotsindirectory = config.GetBool("flplusplus", "screenshots_in_directory", g_config.screenshotsindirectory);
g_config.altfullscreenscreenshots = config.GetBool("flplusplus", "alternative_fullscreen_screenshots_code", g_config.altfullscreenscreenshots);
g_config.altwindowedscreenshots = config.GetBool("flplusplus", "alternative_windowed_screenshots_code", g_config.altwindowedscreenshots);
g_config.removestartlocationwarning = ReadBool("remove_start_location_warning", g_config.removestartlocationwarning);
g_config.logtoconsole = ReadBool("log_to_console", g_config.logtoconsole);
g_config.shippreviewscrollingspeed = config.GetFloat("flplusplus", "ship_preview_scrolling_speed", g_config.shippreviewscrollingspeed);
g_config.shippreviewscrollinginverse = config.GetBool("flplusplus", "ship_preview_scrolling_inverse", g_config.shippreviewscrollinginverse);
g_config.shippreviewscrollingmindistance = config.GetFloat("flplusplus", "ship_preview_scrolling_min_distance", g_config.shippreviewscrollingmindistance);
g_config.shippreviewscrollingmaxdistance = config.GetFloat("flplusplus", "ship_preview_scrolling_max_distance", g_config.shippreviewscrollingmaxdistance);
g_config.alwaysregeneraterestartfile = ReadBool("always_regenerate_restart_file", g_config.alwaysregeneraterestartfile);
g_config.failedtoinitsavesdirids = config.GetInt("flplusplus", "failed_to_init_saves_dir_ids", g_config.failedtoinitsavesdirids);
g_config.touchpadsupport = ReadBool("touchpad_support", g_config.touchpadsupport);
g_config.confinecursor = ReadBool("confine_cursor", g_config.confinecursor);
}
void read_font_files(const char* filename)
{
if (!ReadBool("font_file_loading", false))
return;
INI_Reader reader;
if (!reader.open(filename, false))
return;
while (reader.read_header())
{
if (reader.is_header("FontFiles"))
{
while (reader.read_value())
{
if (reader.is_value("path"))
g_config.fontfiles.emplace_back(reader.get_value_string(0));
}
}
}
reader.close();
}
void EnsureInitialized()
{
if (g_initialized)
return;
init_from_file(nullptr);
g_initialized = true;
}
}
+66
View File
@@ -0,0 +1,66 @@
#include "features/jump_waypoint.h"
#include "rem/patch.h"
#define REM_FASTCALL __fastcall
#define REM_NAKED __declspec(naked)
namespace
{
using rem::features::JumpDestinationSolar;
using rem::features::Waypoint;
using GetWaypointFn = Waypoint* (__cdecl*)(int);
using DeleteWaypointFn = void (__cdecl*)(int);
constexpr DWORD getWaypointAddress = 0x4C46A0;
constexpr DWORD deleteWaypointAddress = 0x4C46E0;
constexpr DWORD jumpTunnelEntrypointAddress = 0x5037E1;
Waypoint* GetWaypoint(int index)
{
return rem::FunctionAt<GetWaypointFn>(getWaypointAddress)(index);
}
void DeleteWaypoint(int index)
{
rem::FunctionAt<DeleteWaypointFn>(deleteWaypointAddress)(index);
}
void REM_FASTCALL ClearWaypointsToJumpDestination(const JumpDestinationSolar& solar)
{
if (!solar.destinationGate)
return;
for (int index = 0; Waypoint* waypoint = GetWaypoint(index); ++index)
{
if (waypoint->system != solar.destinationSystem || waypoint->target != solar.destinationGate)
continue;
for (; index >= 0; --index)
DeleteWaypoint(index);
break;
}
}
REM_NAKED void ClearWaypointsToJumpDestinationHook()
{
__asm {
pushad
mov ecx, edx
call ClearWaypointsToJumpDestination
popad
ret
}
}
}
namespace rem::features
{
void InitJumpDestinationWaypointFix()
{
rem::Hook(jumpTunnelEntrypointAddress, ClearWaypointsToJumpDestinationHook, 5);
}
}
+119
View File
@@ -0,0 +1,119 @@
#include "features/mouse.h"
#include "rem/patch.h"
#define REM_FASTCALL __fastcall
namespace
{
int& MouseX()
{
return *reinterpret_cast<int*>(0x616840);
}
int& MouseY()
{
return *reinterpret_cast<int*>(0x616844);
}
int WindowWidth()
{
return *reinterpret_cast<int*>(0x679BC8);
}
int WindowHeight()
{
return *reinterpret_cast<int*>(0x679BCC);
}
bool ShowMouseCursor()
{
return *reinterpret_cast<bool*>(0x6107DC);
}
HWND FreelancerWindow()
{
return *reinterpret_cast<HWND*>(0x67ECA0);
}
bool IsGameFullscreen()
{
constexpr DWORD fullscreenFlag = 1;
return (*reinterpret_cast<DWORD*>(0x679BE5) & fullscreenFlag) != 0;
}
bool ShowMouseCursorHook()
{
if (MouseX() < 0 || MouseY() < 0)
return false;
if (MouseX() >= WindowWidth() || MouseY() >= WindowHeight())
return false;
return ShowMouseCursor();
}
void ForceShowWindowsCursor()
{
while (ShowCursor(TRUE) < 1)
{
}
}
void REM_STDCALL ShowCursorHook()
{
POINT point{};
if (GetCursorPos(&point) && ScreenToClient(FreelancerWindow(), &point))
{
if (point.x < 0 || point.y < 0 || point.x >= WindowWidth() || point.y > WindowHeight())
ForceShowWindowsCursor();
MouseX() = point.x;
MouseY() = point.y;
}
}
long REM_FASTCALL AcquireHook(IDirectInputDevice8_Rem& mouseDevice)
{
const long result = mouseDevice.Acquire();
if (result == S_OK && !IsGameFullscreen())
{
POINT point{ MouseX(), MouseY() };
if (ClientToScreen(FreelancerWindow(), &point))
SetCursorPos(point.x, point.y);
}
return result;
}
}
namespace rem::features
{
void InitCursorVisibilityFix()
{
constexpr DWORD showMouseCursorCheckAddress = 0x41F30A;
Hook(showMouseCursorCheckAddress, ShowMouseCursorHook, 5);
}
void InitCursorBorderFlickerFix()
{
constexpr DWORD showWinCursorAddress = 0x420335;
constexpr DWORD cursorBottomBorderCheckAddress = 0x41EA9B;
Hook(showWinCursorAddress, ShowCursorHook, 19);
Patch<BYTE>(cursorBottomBorderCheckAddress, 0xEB);
}
void InitMouseWarpFix()
{
constexpr DWORD thisPointerAcquireMouseAddress = 0x41F7D1;
constexpr DWORD acquireMouseAddress = 0x41F7D3;
Patch<BYTE>(thisPointerAcquireMouseAddress, 0x4E);
Hook(acquireMouseAddress, AcquireHook, 6);
}
}
+34
View File
@@ -0,0 +1,34 @@
#include "features/quit_message.h"
#include <windows.h>
#include "rem/patch.h"
namespace
{
bool noQuitMessageRetrieved = true;
bool (*HandleMessagesOriginal)(WPARAM* msgWParam) = nullptr;
bool HandleMessagesHook(WPARAM* msgWParam)
{
const bool result = HandleMessagesOriginal(msgWParam);
noQuitMessageRetrieved = noQuitMessageRetrieved && result;
return noQuitMessageRetrieved;
}
}
namespace rem::features
{
void InitQuitMessageFix()
{
constexpr DWORD handleMessagesAddress = 0x5B0B60;
HandleMessagesOriginal = Trampoline(handleMessagesAddress, HandleMessagesHook, 5);
}
void CleanupQuitMessageFix()
{
if (HandleMessagesOriginal)
CleanupTrampoline(HandleMessagesOriginal);
}
}
+128
View File
@@ -0,0 +1,128 @@
#include "features/ship_buy_kick.h"
#include <algorithm>
#include "rem/log.h"
#include "rem/patch.h"
#include "rem/runtime.h"
#define REM_FASTCALL __fastcall
#define REM_NAKED __declspec(naked)
namespace
{
using rem::features::BaseGoodCollection;
using rem::features::BaseMarket;
using rem::features::MarketGood;
using rem::features::PlayerData;
using GetSoldGoodFn = const MarketGood* (__thiscall*)(const BaseMarket*, UINT);
using FindGoodByIdFn = const rem::fl::GoodInfo* (__cdecl*)(UINT);
GetSoldGoodFn getSoldGood = nullptr;
FindGoodByIdFn findGoodById = nullptr;
bool ShipPackageContainsGood(const rem::fl::GoodInfo& shipPackage, UINT goodId)
{
for (const rem::fl::EquipDescList& equipDescList : shipPackage.equipDescLists)
{
for (auto equip = const_cast<rem::fl::EquipDescList&>(equipDescList).list.begin();
equip != const_cast<rem::fl::EquipDescList&>(equipDescList).list.end();
++equip)
{
if (equip->archId == goodId)
return true;
}
}
return false;
}
const MarketGood* REM_FASTCALL GetGoodSoldByBaseOrPartOfShip(const BaseMarket& baseMarket, const PlayerData& playerData, UINT goodId)
{
const MarketGood* result = getSoldGood(&baseMarket, goodId);
if (result)
return result;
if (playerData.currentShipId
&& playerData.currentShipId == playerData.shipIdOnLand
&& baseMarket.baseGoods
&& baseMarket.baseGoods->HasShipPackageWithGood(goodId))
{
static const MarketGood validMarketGood = {};
return &validMarketGood;
}
return nullptr;
}
REM_NAKED void GetGoodSoldByBaseHook()
{
__asm {
mov edx, esi
jmp GetGoodSoldByBaseOrPartOfShip
}
}
}
namespace rem::features
{
bool BaseGoodCollection::HasShipPackageWithGood(UINT goodId)
{
if (!findGoodById)
return false;
for (auto good = goods.begin(); good != goods.end(); ++good)
{
if (!good->IsShipCandidate())
continue;
const rem::fl::GoodInfo* goodInfo = findGoodById(good->goodId);
if (goodInfo && goodInfo->type == rem::fl::GoodType::Ship && ShipPackageContainsGood(*goodInfo, goodId))
return true;
}
return false;
}
void InitShipBuyKickFix()
{
constexpr DWORD getGoodSoldByBaseCallOffsetServer = 0x6FEEB;
constexpr DWORD getGoodSoldByBaseOffsetServer = 0x33000;
constexpr const char* findByIdSymbol = "?find_by_id@GoodList@@YAPBUGoodInfo@@I@Z";
HMODULE server = GetModuleHandleA("server.dll");
if (!server)
{
log::ModuleMissing("ship_buy_kick_fix", "server.dll");
return;
}
HMODULE common = rem::GetModule("common.dll");
if (!common)
{
log::ModuleMissing("ship_buy_kick_fix", "common.dll");
return;
}
FARPROC findByIdProc = GetProcAddress(common, findByIdSymbol);
#pragma warning(suppress: 4191)
findGoodById = reinterpret_cast<FindGoodByIdFn>(findByIdProc);
if (!findGoodById)
{
log::ProcMissing("ship_buy_kick_fix", findByIdSymbol);
return;
}
const DWORD serverBase = reinterpret_cast<DWORD>(server);
getSoldGood = reinterpret_cast<GetSoldGoodFn>(serverBase + getGoodSoldByBaseOffsetServer);
Hook(serverBase + getGoodSoldByBaseCallOffsetServer, GetGoodSoldByBaseHook, 5);
}
}
+95
View File
@@ -0,0 +1,95 @@
#include <windows.h>
#include "features/flsharp.h"
#include "features/flplusplus.h"
#include "features/jump_waypoint.h"
#include "features/mouse.h"
#include "rem/config.h"
#include "rem/feature_manager.h"
#include "rem/version_guard.h"
namespace
{
rem::FeatureManager manager;
void Init(HINSTANCE module)
{
if (!rem::IsSupportedGameVersion())
return;
manager.Register("better_updates", InitBetterUpdates, nullptr, rem::RuntimeSide::Client, false);
manager.Register("waypoint_fixes", InitWaypointFixes, nullptr, rem::RuntimeSide::Client, true);
manager.Register("waypoint_name_fixes", InitWaypointNameFixes, nullptr, rem::RuntimeSide::Client, true);
manager.Register("projectiles_sound_fix", InitProjectilesSoundFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("projectiles_server_fix", InitProjectilesServerFix, nullptr, rem::RuntimeSide::Always, true);
manager.Register("better_resolutions", InitBetterResolutions, CleanupBetterResolutions, rem::RuntimeSide::Client, false);
manager.Register("more_test_sounds", InitTestSounds, nullptr, rem::RuntimeSide::Client, true);
manager.Register("trade_lane_lights_fix", InitTradeLaneLightsFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("copy_paste_feature", InitCopyPasteFeature, nullptr, rem::RuntimeSide::Client, false);
manager.Register("slide_ui_anim_fix", InitSlideUiAnimFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("weapon_anim_fix", InitWeaponAnimFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("flash_particle_fix", InitFlashParticlesFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("print_rep_requirements", InitPrintRepRequirements, nullptr, rem::RuntimeSide::Client, false);
manager.Register("post_game_deadlock_fix", InitPostGameDeadlockFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("quit_message_fix", InitQuitMessageFix, CleanupQuitMessageFix, rem::RuntimeSide::Client, true);
manager.Register("flight_controls_fix", InitFlightControlsFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("dynamic_solar_infocards", InitDynamicSolarInfocards, nullptr, rem::RuntimeSide::Client, false);
manager.Register("save_crash_fix", InitSaveCrashFix, nullptr, rem::RuntimeSide::Always, true);
manager.Register("alchemy_crash_fix", InitAlchemyCrashFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("blank_faction_fix", InitBlankFactionNameFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("server_filter_crash_fix", InitServerFilterCrashFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("server_filter_speed_fix", InitServerFilterSpeedFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("freelancer_dll_crash_fix", InitMissingDllCrashFix, nullptr, rem::RuntimeSide::Always, true);
manager.Register("shield_capacity_fix", InitShieldCapacityFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("dealer_menu_open_fix", InitDealerOpenFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("dealer_crash_fix", InitDealerCrashFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("ship_buy_kick_fix", InitShipBuyKickFix, nullptr, rem::RuntimeSide::Server, true);
manager.Register("hostile_group_formation", InitHostileGroupFormation, nullptr, rem::RuntimeSide::Client, false);
manager.Register("unhostile_group_members", InitHostileGroupMembersFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("group_member_attitude", InitGroupMemberAttitudeFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("more_cursor_colors", InitMoreCursorColors, nullptr, rem::RuntimeSide::Client, false);
manager.Register("base_info_spacing_fix", InitBaseInfoSpacingFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("pilot_names_fix", InitPilotNamesFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("jump_destination_waypoint_fix", rem::features::InitJumpDestinationWaypointFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("cursor_visibility_fix", rem::features::InitCursorVisibilityFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("cursor_border_flicker_fix", rem::features::InitCursorBorderFlickerFix, nullptr, rem::RuntimeSide::Client, false);
manager.Register("mouse_warp_fix", rem::features::InitMouseWarpFix, nullptr, rem::RuntimeSide::Client, false);
manager.Register("timestamped_logs", rem::features::InitFlplusplusTimestampedLogs, nullptr, rem::RuntimeSide::Always, true);
manager.Register("log_to_console", rem::features::InitFlplusplusConsoleLog, nullptr, rem::RuntimeSide::Always, false);
manager.Register("save_path_feature", rem::features::InitFlplusplusSavePath, nullptr, rem::RuntimeSide::Always, true);
manager.Register("always_regenerate_restart_file", rem::features::InitFlplusplusRestartRegeneration, nullptr, rem::RuntimeSide::Client, true);
manager.Register("graphics_base_fixes", rem::features::InitFlplusplusGraphicsBaseFixes, nullptr, rem::RuntimeSide::Client, true);
manager.Register("graphics_detail_scaling", rem::features::InitFlplusplusDetailScaling, nullptr, rem::RuntimeSide::Client, true);
manager.Register("png_screenshots", rem::features::InitFlplusplusPngScreenshots, nullptr, rem::RuntimeSide::Client, false);
manager.Register("screenshot_path_feature", rem::features::InitFlplusplusScreenshotPath, nullptr, rem::RuntimeSide::Client, true);
manager.Register("wine_mp3_codec_warning_fix", rem::features::InitFlplusplusWineMp3CodecWarningFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("remove_start_location_warning", rem::features::InitFlplusplusStartLocationWarningFix, nullptr, rem::RuntimeSide::Client, true);
manager.Register("font_file_loading", rem::features::InitFlplusplusFontFileLoading, nullptr, rem::RuntimeSide::Client, false);
manager.Register("thn_player", rem::features::InitFlplusplusThnPlayer, nullptr, rem::RuntimeSide::Client, false);
manager.Register("ship_preview_scroll", rem::features::InitFlplusplusShipPreviewScroll, nullptr, rem::RuntimeSide::Client, false);
manager.Register("failed_save_dir_ids_override", rem::features::InitFlplusplusFailedSaveDirIds, nullptr, rem::RuntimeSide::Client, true);
manager.Register("touchpad_support", rem::features::InitFlplusplusTouchpadSupport, nullptr, rem::RuntimeSide::Client, false);
manager.Register("confine_cursor", rem::features::InitFlplusplusCursorConfinement, nullptr, rem::RuntimeSide::Client, false);
rem::ReadConfig(module, manager);
manager.InitFeatures();
}
}
BOOL WINAPI DllMain(HINSTANCE module, DWORD reason, LPVOID)
{
if (reason == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(module);
Init(module);
}
else if (reason == DLL_PROCESS_DETACH)
{
manager.CleanupFeatures();
}
return TRUE;
}
+172
View File
@@ -0,0 +1,172 @@
#include "rem/config.h"
#include <array>
#include <cctype>
#include <cstring>
#include <fstream>
#include <sstream>
#include <string>
#include "rem/log.h"
namespace
{
std::string ConfigPath(HINSTANCE module)
{
std::array<char, MAX_PATH> path{};
DWORD length = GetModuleFileNameA(module, path.data(), static_cast<DWORD>(path.size()));
if (length == 0 || length >= path.size())
return "rem-essentials.ini";
std::string result(path.data(), length);
const size_t slash = result.find_last_of("\\/");
if (slash == std::string::npos)
return "rem-essentials.ini";
return result.substr(0, slash + 1) + "rem-essentials.ini";
}
bool ParseBool(const char* value, bool fallback)
{
if (_stricmp(value, "true") == 0 || _stricmp(value, "1") == 0 || _stricmp(value, "yes") == 0 || _stricmp(value, "on") == 0)
return true;
if (_stricmp(value, "false") == 0 || _stricmp(value, "0") == 0 || _stricmp(value, "no") == 0 || _stricmp(value, "off") == 0)
return false;
return fallback;
}
std::string Trim(const std::string& value)
{
size_t start = 0;
while (start < value.size() && std::isspace(static_cast<unsigned char>(value[start])))
++start;
size_t end = value.size();
while (end > start && std::isspace(static_cast<unsigned char>(value[end - 1])))
--end;
return value.substr(start, end - start);
}
std::string ToLower(std::string value)
{
for (char& ch : value)
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
return value;
}
}
namespace rem
{
Config& GetConfig()
{
static Config config;
return config;
}
void Config::Load(HINSTANCE module)
{
sections_.clear();
std::ifstream file(ConfigPath(module));
if (!file)
return;
std::string currentSection;
std::string line;
while (std::getline(file, line))
{
const size_t comment = line.find(';');
if (comment != std::string::npos)
line = line.substr(0, comment);
line = Trim(line);
if (line.empty())
continue;
if (line.front() == '[' && line.back() == ']')
{
currentSection = ToLower(Trim(line.substr(1, line.size() - 2)));
continue;
}
const size_t equals = line.find('=');
if (equals == std::string::npos || currentSection.empty())
continue;
std::string key = ToLower(Trim(line.substr(0, equals)));
std::string value = Trim(line.substr(equals + 1));
sections_[currentSection][key] = value;
}
}
bool Config::GetBool(const char* section, const char* key, bool fallback) const
{
const std::string value = GetValue(section, key);
return value.empty() ? fallback : ParseBool(value.c_str(), fallback);
}
int Config::GetInt(const char* section, const char* key, int fallback) const
{
const std::string value = GetValue(section, key);
if (value.empty())
return fallback;
try
{
return std::stoi(value);
}
catch (...)
{
return fallback;
}
}
float Config::GetFloat(const char* section, const char* key, float fallback) const
{
const std::string value = GetValue(section, key);
if (value.empty())
return fallback;
try
{
return std::stof(value);
}
catch (...)
{
return fallback;
}
}
std::string Config::GetString(const char* section, const char* key, const char* fallback) const
{
const std::string value = GetValue(section, key);
return value.empty() ? fallback : value;
}
std::string Config::GetValue(const char* section, const char* key) const
{
const auto sectionIt = sections_.find(ToLower(section));
if (sectionIt == sections_.end())
return {};
const auto keyIt = sectionIt->second.find(ToLower(key));
if (keyIt == sectionIt->second.end())
return {};
return keyIt->second;
}
void ReadConfig(HINSTANCE module, FeatureManager& manager)
{
Config& config = GetConfig();
config.Load(module);
manager.ApplyConfig(config);
}
}
+92
View File
@@ -0,0 +1,92 @@
#include "rem/feature_manager.h"
#include <algorithm>
#include <cstring>
#include "rem/config.h"
#include "rem/log.h"
#include "rem/runtime.h"
namespace
{
bool TryInitializeFeature(void (*init)(), DWORD& exceptionCode)
{
__try
{
init();
return true;
}
__except (exceptionCode = GetExceptionCode(), EXCEPTION_EXECUTE_HANDLER)
{
return false;
}
}
}
namespace rem
{
void FeatureManager::Register(const char* name, void (*init)(), void (*cleanup)(), RuntimeSide side, bool defaultEnabled)
{
features_.push_back(Feature{ name, init, cleanup, side, defaultEnabled, false });
}
bool FeatureManager::SetEnabled(const char* name, bool enabled)
{
auto feature = std::find_if(features_.begin(), features_.end(), [name](const Feature& candidate) {
return _stricmp(candidate.name.c_str(), name) == 0;
});
if (feature == features_.end())
{
log::UnknownFeature(name);
return false;
}
feature->enabled = enabled;
return true;
}
void FeatureManager::ApplyConfig(const Config& config)
{
for (Feature& feature : features_)
feature.enabled = config.GetBool("rem-essentials", feature.name.c_str(), feature.enabled);
}
void FeatureManager::InitFeatures()
{
for (Feature& feature : features_)
{
if (!feature.enabled || !feature.init || !Applies(feature))
continue;
DWORD exceptionCode = 0;
feature.initialized = TryInitializeFeature(feature.init, exceptionCode);
if (!feature.initialized)
log::FeatureInitFailed(feature.name.c_str(), exceptionCode);
}
}
void FeatureManager::CleanupFeatures()
{
for (auto feature = features_.rbegin(); feature != features_.rend(); ++feature)
{
if (feature->initialized && feature->cleanup)
feature->cleanup();
}
}
bool FeatureManager::Applies(const Feature& feature) const
{
switch (feature.side)
{
case RuntimeSide::Always:
return true;
case RuntimeSide::Client:
return IsClient();
case RuntimeSide::Server:
return IsServer();
default:
return false;
}
}
}
+59
View File
@@ -0,0 +1,59 @@
#include "rem/log.h"
#include <cstdio>
namespace
{
void Write(const char* level, const char* message)
{
char buffer[512]{};
sprintf_s(buffer, "rem-essentials [%s]: %s\n", level, message);
OutputDebugStringA(buffer);
}
}
namespace rem::log
{
void Info(const char* message)
{
Write("info", message);
}
void Warn(const char* message)
{
Write("warn", message);
}
void Error(const char* message)
{
Write("error", message);
}
void ModuleMissing(const char* feature, const char* moduleName)
{
char buffer[256]{};
sprintf_s(buffer, "%s could not find module %s", feature, moduleName);
Error(buffer);
}
void ProcMissing(const char* feature, const char* procName)
{
char buffer[256]{};
sprintf_s(buffer, "%s could not find proc %s", feature, procName);
Error(buffer);
}
void UnknownFeature(const char* featureName)
{
char buffer[256]{};
sprintf_s(buffer, "unknown config feature %s", featureName);
Warn(buffer);
}
void FeatureInitFailed(const char* featureName, DWORD exceptionCode)
{
char buffer[256]{};
sprintf_s(buffer, "feature %s failed to initialize (exception 0x%08lX)", featureName, exceptionCode);
Error(buffer);
}
}
+53
View File
@@ -0,0 +1,53 @@
#include "rem/patch.h"
#include <cstring>
namespace rem
{
void ReadWriteProtect(DWORD address, DWORD size)
{
DWORD oldProtect = 0;
VirtualProtect(reinterpret_cast<void*>(address), size, PAGE_EXECUTE_READWRITE, &oldProtect);
}
void Patch(DWORD address, const void* data, UINT length)
{
ReadWriteProtect(address, length);
memcpy(reinterpret_cast<void*>(address), data, length);
}
void PatchBytes(DWORD address, std::initializer_list<BYTE> bytes)
{
Patch(address, bytes.begin(), static_cast<UINT>(bytes.size()));
}
void Nop(DWORD address, UINT length)
{
static const struct
{
UINT length;
const char* bytes;
} nopTable[] = {
{ 9, "\x66\x0F\x1F\x84\x00\x00\x00\x00\x00" },
{ 8, "\x0F\x1F\x84\x00\x00\x00\x00\x00" },
{ 7, "\x0F\x1F\x80\x00\x00\x00\x00" },
{ 6, "\x66\x0F\x1F\x44\x00\x00" },
{ 5, "\x0F\x1F\x44\x00\x00" },
{ 4, "\x0F\x1F\x40\x00" },
{ 3, "\x0F\x1F\x00" },
{ 2, "\x66\x90" },
{ 1, "\x90" },
};
for (const auto& nop : nopTable)
{
while (length >= nop.length)
{
Patch(address, nop.bytes, nop.length);
address += nop.length;
length -= nop.length;
}
}
}
}
+41
View File
@@ -0,0 +1,41 @@
#include "rem/runtime.h"
#include <array>
#include <cstring>
#include <string>
namespace rem
{
bool IsServer()
{
std::array<char, MAX_PATH> path{};
DWORD length = GetModuleFileNameA(nullptr, path.data(), static_cast<DWORD>(path.size()));
if (length == 0 || length >= path.size())
return false;
std::string process(path.data(), length);
const size_t slash = process.find_last_of("\\/");
if (slash != std::string::npos)
process = process.substr(slash + 1);
return _stricmp(process.c_str(), "flserver.exe") == 0;
}
bool IsClient()
{
return !IsServer();
}
bool IsWine()
{
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
return ntdll && GetProcAddress(ntdll, "wine_get_version") != nullptr;
}
HMODULE GetModule(const char* moduleName)
{
return GetModuleHandleA(moduleName);
}
}
+99
View File
@@ -0,0 +1,99 @@
#include "rem/version_guard.h"
#include <windows.h>
#include <cstdint>
#include <vector>
#include "rem/log.h"
#include "rem/runtime.h"
namespace
{
constexpr std::uint16_t FreelancerV10Build = 1223;
bool ReadProductBuild(const char* moduleName, std::uint16_t& build)
{
HMODULE module = GetModuleHandleA(moduleName);
if (!module)
module = LoadLibraryA(moduleName);
if (!module)
{
rem::log::ModuleMissing("version_guard", moduleName);
return false;
}
char path[MAX_PATH]{};
if (GetModuleFileNameA(module, path, MAX_PATH) == 0)
return false;
DWORD ignored = 0;
const DWORD infoSize = GetFileVersionInfoSizeA(path, &ignored);
if (infoSize == 0)
return false;
std::vector<BYTE> info(infoSize);
if (!GetFileVersionInfoA(path, 0, infoSize, info.data()))
return false;
VS_FIXEDFILEINFO* fixedInfo = nullptr;
UINT fixedInfoSize = 0;
if (!VerQueryValueA(info.data(), "\\", reinterpret_cast<void**>(&fixedInfo), &fixedInfoSize)
|| !fixedInfo
|| fixedInfoSize < sizeof(VS_FIXEDFILEINFO)
|| fixedInfo->dwSignature != 0xFEEF04BD)
{
return false;
}
build = HIWORD(fixedInfo->dwProductVersionLS);
return true;
}
bool CheckModule(const char* moduleName)
{
std::uint16_t build = 0;
if (!ReadProductBuild(moduleName, build))
{
char message[256]{};
sprintf_s(message, "could not verify the product version of %s", moduleName);
rem::log::Error(message);
return false;
}
if (build <= FreelancerV10Build)
{
char message[256]{};
sprintf_s(message, "%s build %u is unsupported; Freelancer 1.1 is required", moduleName, build);
rem::log::Error(message);
return false;
}
return true;
}
}
namespace rem
{
bool IsSupportedGameVersion()
{
const bool commonSupported = CheckModule("common.dll");
const bool serverSupported = CheckModule("server.dll");
if (commonSupported && serverSupported)
return true;
log::Error("initialization stopped because the Freelancer 1.1 module check failed");
if (IsClient())
{
MessageBoxA(
nullptr,
"rem-essentials requires the official Freelancer 1.1 modules.\n"
"No memory patches were applied.",
"REM Essentials",
MB_OK | MB_ICONERROR);
}
return false;
}
}
+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();

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