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