Initial public release of rem-essentials
This commit is contained in:
+18
@@ -0,0 +1,18 @@
|
||||
build/
|
||||
build-*/
|
||||
bin/
|
||||
obj/
|
||||
release/
|
||||
*.dll
|
||||
*.exe
|
||||
*.ilk
|
||||
*.iobj
|
||||
*.ipdb
|
||||
*.lib
|
||||
*.obj
|
||||
*.pdb
|
||||
*.res
|
||||
*.suo
|
||||
*.user
|
||||
*.vcxproj
|
||||
*.vcxproj.filters
|
||||
+150
@@ -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"
|
||||
)
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace rem::features
|
||||
{
|
||||
void InitAlchemyCrashFix();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
namespace rem::features
|
||||
{
|
||||
void InitQuitMessageFix();
|
||||
void CleanupQuitMessageFix();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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_;
|
||||
};
|
||||
}
|
||||
@@ -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];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace rem
|
||||
{
|
||||
bool IsServer();
|
||||
bool IsClient();
|
||||
bool IsWine();
|
||||
HMODULE GetModule(const char* moduleName);
|
||||
}
|
||||
@@ -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_;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace rem
|
||||
{
|
||||
bool IsSupportedGameVersion();
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
Vendored
+71
@@ -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
@@ -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
@@ -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)();
|
||||
}
|
||||
Vendored
+25
@@ -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);
|
||||
}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
namespace codec {
|
||||
void init();
|
||||
}
|
||||
Vendored
+38
@@ -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
@@ -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
@@ -0,0 +1,2 @@
|
||||
#pragma once
|
||||
void RedirectIOToConsole();
|
||||
+43
@@ -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);
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace cursor {
|
||||
void hook_mouse_func(unsigned int address, void* func);
|
||||
void init();
|
||||
}
|
||||
+27
@@ -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
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
namespace fontresource {
|
||||
void init(LPCSTR fontDirectory);
|
||||
}
|
||||
+148
@@ -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
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
namespace graphics {
|
||||
void init();
|
||||
bool init_base_fixes();
|
||||
bool init_detail_scaling();
|
||||
}
|
||||
+29
@@ -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
|
||||
Vendored
+137
@@ -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);
|
||||
}
|
||||
Vendored
+8
@@ -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();
|
||||
}
|
||||
Vendored
+73
@@ -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)
|
||||
Vendored
+42
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
+32
@@ -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
@@ -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;
|
||||
}
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace restart {
|
||||
bool init();
|
||||
}
|
||||
+127
@@ -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
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace savegame {
|
||||
bool init();
|
||||
void get_save_folder(char *buffer);
|
||||
}
|
||||
+290
@@ -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
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace screenshot {
|
||||
bool init_png();
|
||||
bool init_path();
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace startlocation {
|
||||
void init();
|
||||
}
|
||||
+28
@@ -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);
|
||||
}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
namespace startup {
|
||||
void init();
|
||||
bool check_save_games_hook();
|
||||
}
|
||||
+210
@@ -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
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
namespace thnplayer {
|
||||
bool init();
|
||||
}
|
||||
|
||||
+18
@@ -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
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace touchpad {
|
||||
void init();
|
||||
}
|
||||
Vendored
+52
@@ -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
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
EXPORTS
|
||||
?Shutdown@CGunWrapper@@SAXXZ
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
EXPORTS
|
||||
DACOM_GetDllVersion
|
||||
FDUMP
|
||||
Vendored
+386
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
Vendored
+15
@@ -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)
|
||||
Vendored
+23
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
struct Alchemy
|
||||
{
|
||||
float progress;
|
||||
void* effect;
|
||||
};
|
||||
|
||||
struct AleLoop
|
||||
{
|
||||
int startOffset;
|
||||
unsigned char maxProgressOffset;
|
||||
};
|
||||
|
||||
void InitAlchemyCrashFix();
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
struct BaseInfoCat
|
||||
{
|
||||
DWORD headerStyleAddr;
|
||||
DWORD headerNamePrintAddr;
|
||||
};
|
||||
|
||||
void InitBaseInfoSpacingFix();
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
void InitBlankFactionNameFix();
|
||||
+72
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
void InitMissingDllCrashFix();
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
void InitPostGameDeadlockFix();
|
||||
|
||||
void InitQuitMessageFix();
|
||||
void CleanupQuitMessageFix();
|
||||
+30
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user