This is a mod loader for the 2017 Windows port of the 2004 Xbox game Phantom Dust. It heavily relies on DLL injection to load arbitrary mod code in the game process. Function hooking is also used to intercept/redirect file I/O from the game and force it to open modded files (so that modders don't need to replace the original files).
DLL injection and the mod loader's virtual filesystem also provides some extra options for modders:
- Add extra functionality that runs before or after any function in the game
- Completely replace any function in the game with their own code
- Read and write directly to the game's memory without the overhead and occasional permission problems of WinAPI
- Effortlessly read/write files from the original game, the mods folder, or .zip / .7z archives in the mods folder. The virtual filesystem handles file I/O from archives and figures out which of the 3 locations to use automatically.
For those interested, more technical info can be found in the "Plugin Development" section. If you have any problems or questions, check the FAQ section. There (might) already be an answer there.
The mods
folder used by the virtual filesystem is at the following location:
%LOCALAPPDATA%\Packages\Microsoft.MSEsper_8wekyb3d8bbwe\RoamingState\mods
This folder will be created when you run the plugin manager for the first time, or when you run the included
setup_junction.bat
script. This script sets up a hard link (similar to a shortcut) that lets you easily access the
mods folder without needing to go to this long folder path every time.
To start the game with mods, run pdpl.exe
(found on the Releases page).
This will close Phantom Dust if it's already open.
You'll know mods are enabled if the version number on the title screen reads 0.00
. Mods are only enabled when
you run the game using pdpl.exe
.
If you want to see console output from mods, you'll have to sideload the game by following this guide.
Whenever you put anything into the mods
folder, make sure to copy the file(s), not move them. If you move the
file, it won't be encrypted correctly by Windows and neither the game nor the mods will be able to read it.
To replace a file in the game, place it in the mods folder with the same path it would have in the original game.
For example: to replace Edgar's third outfit (located at Assets/Data/Player/pc01a/pc01a2.alr
), you would copy your
edited pc01a2.alr
to mods/Assets/Data/player/pc01a/pc01a2.alr
. This only applies to files that you can see being
opened in the console (prefixed with CreateFile2():
).
Files like .mp4
cutscenes and .cso
DirectX shaders can't be replaced using this program (yet), but the code which
loads mod files is in this repo at pd_loader_core/src/hooks.c
.
To install a plugin, just copy the DLL file into the mods/plugins
folder and run pdpl.exe
. If the plugin came as a
.zip
or .7z
file, then copy it to the mods
folder (not mods/plugins
).
The release includes a plugin you can test with, inside single_skills.7z
. This mod lets you load custom skill files
and text from the mods/skills
and mods/skills/text
folders, without needing to edit a single large file for all of
them. Source code for the mod is available here. To install the mod, copy
the single_skills.7z
file into the mods
folder.
The release also includes a simple test skill for you to test that mod. It makes Rapid Cannon
bouncy and renames it
to Bullet Hell
. To use it, copy the bullet_hell.7z
file into the mods
folder.
Original game files can also be replaced by ones in .zip
or .7z
archives. This makes it easy to install mods without
needing to install 7zip or extract anything.
Q: It's not allowing me to put files in the mods folder. What's wrong?
A: The mods folder gets encrypted by Windows and has some restrictions on it, try copying the file instead of moving it
(hold Ctrl while dragging or use Ctrl+C / Ctrl+V).
Q: The game is failing to load the files I put in the mods folder, even though I can see them in File Explorer. What's
going on?
A: You probably accidentally moved the file instead of copying it.
Q: Why don't you provide / publish the source code for pdpl.exe?
A: I want to make it as difficult as possible to cheat in normal multiplayer lobbies using my tool(s). With source code
access, anyone could easily remove all of my anti-cheating measures and recompile it. This is also why I chose to use C
instead of a higher-level language like C#. I understand that being closed-source comes off a bit sketchy to some people
(especially because the main executable does DLL injection), but I don't feel that I can safely open-source it without
causing damage to the unusually healthy multiplayer scene of this game.
The single_skills.dll
mod essentially serves as an example / proof of concept mod for this mod loader. The source
code is available here.
Plugins can be written in standard C or C++. Standard output is sent to the plugin console automatically, but I'm not
sure about C++ std::cout
. If it doesn't work, try running std::ios_base::sync_with_stdio(true);
first. My program
and test plugins are written in C99, so I haven't tested it.
The entry point for your plugin is a normal DllMain()
. Read the single_skills source code
or Microsoft's documentation for more details.
Note: pd_loader_core
is normally compiled with MinGW. It compiles on MSVC, but will crash a few seconds after starting.
Plugins are manually mapped into the game by MemoryModule, so they're not
visible in any module list like normal DLLs are. However, you can still import functions from other plugin DLLs or from
pd_loader_core.dll
by linking against them. The plugin_dev.zip
file has a copy of the .a
import library you'll need
to link with the core loader, and headers to access the filesystem API. The single_skills
project has an example of
this linking.
One major benefit DLL injection provides is that your code can directly read/write game memory. Game functions and
variables are stored at a static offset from the main module PDUWP.exe
. You can use this to hardcode locations into
your code without needing to search for some data structure or piece of code in memory. This is used in single_skills
to hook the function that loads skill data/text, and read/write the modded data to a pre-determined location the game
reads it from. (Direct link to relevant code)
To change the behaviour of a function, use the MinHook library (https://github.com/TsudaKageyu/minhook).
This function in the
single_skills
mod shows how to set up a hook on a game function. The mod hooks a function responsible for loading
skill data/text, then runs its own code afterwards to load in the modded files.
Hooking isn't useful for everything, but when you need it, it's amazing. Here are some ideas for using hooks in a mod:
- Make a freecam by hooking the camera controller
- Intercept some part of the game's Direct3D rendering and render your own extra graphics on top
- Rewrite a function that loads 3D models to use a common format like
.gltf
or.obj
- Intercept a function processing input data to make your own keybinds or completely disable certain buttons
- Intercept calls from DirectX to make the game load shaders from source code (instead of
.cso
bytecode) and compile them at runtime
The PHYSFS_*
functions give you access to the PhysicsFS virtual filesystem,
which lets you read data from the original game, the mods folder, or zip files with no extra effort. PhysicsFS will
automatically figure out which location to use in the following order of priority:
1. Files inside .zip or .7z archives, in alphabetical order by archive name
2. Loose files in the mods folder
3. Original game folder
Keep in mind that .
and ..
directories are not allowed in PhysicsFS calls and will fail. For documentation on
exactly what each PhysicsFS function does, search for a function in the header file, or their
documentation.
By default, the write directory for PhysicsFS is the RoamingState
folder which contains the mods
folder.
Other archive formats supported by the library are:
- .iso images
- .vdf (Gothic/Gothic II)
- .slb (Independence War)
- .grp (Build Engine)
- .pak (Quake / Quake 2)
- .hog (Descent / Descent 2)
- .mvl (Descent / Descent 2)
- .wad (DOOM)
To save space, I disabled support for all archives except .zip
and .7z
. If you want me to enable one of the
available formats (or you have some custom archiver), let me know and I can add it (or you can enable it here
and rebuild pd_loader_core
yourself).