Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/build-be.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ jobs:
New-Item -ItemType Directory -Force -Path ./artifacts/release/x86
New-Item -ItemType Directory -Force -Path ./artifacts/verbose/x64
New-Item -ItemType Directory -Force -Path ./artifacts/verbose/x86
- name: Run Windows regression tests
run: ./build.ps1 -Arch x64 test
- name: Build Release
Comment on lines +18 to 20
run: |
./build.ps1
Expand Down Expand Up @@ -73,6 +75,8 @@ jobs:
cp assets/nix/run.sh artifacts/release/x86/run.sh
cp assets/nix/run.sh artifacts/release/x64/run.sh
cp LICENSE artifacts/release/LICENSE
- name: Run nix regression tests
run: ./build.sh -arch=x64 test
- name: Build Verbose
run: |
./build.sh -with_logging
Expand Down Expand Up @@ -128,6 +132,8 @@ jobs:
cp build/macosx/universal/release/.doorstop_version artifacts/release/universal/.doorstop_version
cp assets/nix/run.sh artifacts/release/universal/run.sh
cp LICENSE artifacts/release/LICENSE
- name: Run macOS interposition smoke test
run: ./build.sh test
- name: Build Verbose
run: |
./build.sh -with_logging
Expand Down
96 changes: 93 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,11 @@ Doorstop sets some environment variables useful for code execution:

| Environment variable | Description |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `DOORSTOP_INITIALIZED` | Always set to `TRUE`. Use to determine if your code is run via Doorstop. |
| `DOORSTOP_INVOKE_DLL_PATH` | Path to the assembly executed by Doorstop relative to the current working directory. |
| `DOORSTOP_INITIALIZED` | Set to `TRUE` when Doorstop bootstraps a managed target. Use to determine if your code is run via Doorstop. |
| `DOORSTOP_INVOKE_DLL_PATH` | Full path to the assembly executed by Doorstop. |
| `DOORSTOP_PROCESS_PATH` | Path to the application executable where the injected assembly is run. |
| `DOORSTOP_MANAGED_FOLDER_DIR` | *UnityMono*: Path to the game's `Managed` folder. *Il2Cpp*: Path to CoreCLR's base class library folder. |
| `DOORSTOP_DLL_SEARCH_DIRS` | Paths where the runtime searchs assemblies from by default, separated by OS-specific separator (`;` on windows and `:` on *nix). |
| `DOORSTOP_DLL_SEARCH_DIRS` | Paths where the runtime searches for assemblies by default, separated by the OS-specific separator (`;` on Windows and `:` on Unix). |
| `DOORSTOP_MONO_LIB_PATH` | *Only on UnityMono*: Full path to the mono runtime library. |

### Debugging
Expand Down Expand Up @@ -141,6 +141,96 @@ All Doorstop arguments start with `--doorstop-` and always contain an argument.
| `--doorstop-mono-debug-address string` | The address to use for the Mono debugger server. |
| `--doorstop-clr-corlib-dir string` | Path to coreclr library that contains the CoreCLR runtime |
| `--doorstop-clr-runtime-coreclr-path string` | Path to the directory containing the managed core libraries for CoreCLR (`mscorlib`, `System`, etc.) |
| `--doorstop-macos-architectures string` | *Only in macOS `run.sh`*: Comma-separated architectures supported by the game and modding framework |

## Troubleshooting and compatibility

### Choosing a Windows proxy DLL

Windows builds can be installed as `winhttp.dll`, `version.dll`, or
`dxgi.dll`. If a game or launcher uses WinHTTP during very early startup,
proxying `winhttp.dll` can conflict with that initialization. Rename the
Doorstop proxy to `dxgi.dll` (or `version.dll`) and keep
`doorstop_config.ini` beside it. This is particularly useful for games that
silently exit before Doorstop reaches the managed bootstrap.
Install only one of these proxy DLL names at a time.

### Native Unix games, Proton, and relative paths

`run.sh` is for native Linux and macOS executables. A Windows PE executable
running through Wine or Proton must use the Windows Doorstop build and a
Windows proxy DLL instead.

Relative `target_assembly` paths and each relative entry in
`dll_search_path_override` are resolved from the `run.sh` directory. The target
assembly's parent directory is not added to Mono's search path automatically.
Separate multiple paths with `;` on Windows and `:` on Unix.

On Apple Silicon, `macos_architectures` in `run.sh` must list the architectures
supported by both the game and the modding framework. The default
`arm64,x86_64` prefers native execution and falls back to Rosetta; set it to
`x86_64` when any framework component lacks arm64 support. The equivalent CLI
option is `--doorstop-macos-architectures`. The launcher keeps both
`DYLD_LIBRARY_PATH` and `DYLD_INSERT_LIBRARIES` out of the `/usr/bin/arch`
helper, then restores them only for the game process.

### Debug-only mode

Mono debugging can be enabled without loading a target assembly. Set
`debug_enabled=true` (or `debug_enable=1` in `run.sh`) and leave
`target_assembly` empty. Doorstop installs the Mono initialization hook and
configures the debugger, then skips the managed entrypoint.

Doorstop accepts `localhost` for the Mono debugger and canonicalizes it to
`127.0.0.1`; using `127.0.0.1` in the IDE avoids an IPv6 (`::1`) DNS choice on
clients that do not retry IPv4. An explicitly configured IPv6 listener remains
distinct. Doorstop also honors dnSpy's `DNSPY_UNITY_DBG2` environment variable,
so clear a stale value when debugging is unexpectedly enabled.

On modern macOS Mach-O images that use chained fixups, Doorstop uses dyld
interposition for runtime symbol lookup, boot.config access, and UnityPlayer's
stdout protection. Those stdio hooks verify that their caller is UnityPlayer,
so an inherited `DYLD_INSERT_LIBRARIES` value does not change shell or launcher
redirection. Legacy Mono players that bind initialization directly continue to
use the traditional Mach-O lazy-bind path.

Reconnect behavior after an IDE disconnect is implemented by the Mono runtime
bundled with the game. Some older Unity Mono versions do not reliably reopen
their listener; Doorstop cannot replace that runtime-side connection loop.

### Games that restart themselves

Steam and self-restarting games can copy `DOORSTOP_DISABLE` and
`DOORSTOP_INITIALIZED` into the replacement process. Set
`ignore_disable_switch=true` on Windows, or `ignore_disable_switch=1` in
`run.sh`, when the launcher is known to do this. Doorstop then clears both
inherited markers; the Mono bootstrap also uses a process-local guard to
prevent genuine duplicate initialization.

### Waiting until a game assembly is loaded

Doorstop deliberately invokes `Doorstop.Entrypoint.Start()` before game
assemblies. A target that needs types from `Assembly-CSharp` can use the
managed assembly-load event instead of a version-specific native hook:

```cs
AppDomain.CurrentDomain.AssemblyLoad += (_, eventArgs) =>
{
if (eventArgs.LoadedAssembly.GetName().Name == "Assembly-CSharp")
OnGameAssemblyLoaded();
};
```

Register the handler in `Start()` and make the callback one-shot if the loader
must run only once.

### RenderDoc and other native hook tools

Doorstop preserves and calls through a pre-existing `GetProcAddress` IAT hook,
which allows common RenderDoc/apitrace injection orders to coexist. Tools that
replace other required import-table entries can still conflict; in that case,
start through Doorstop first, load or attach the graphics tool from the managed
entrypoint, and then capture the running process.


## License
Expand Down
96 changes: 64 additions & 32 deletions assets/nix/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
# MACOS: name of the .app directory
executable_name=""

# MACOS: comma-separated architectures supported by both the game and the
# modding framework. Use "x86_64" when any framework component lacks arm64
# support; keep the default to prefer native arm64 with an x86_64 fallback.
macos_architectures="arm64,x86_64"

# All of the below can be overriden with command line args

# General Config Options
Expand All @@ -27,7 +32,8 @@ target_assembly="Doorstop.dll"
# Overrides the default boot.config file path
boot_config_override=

# If enabled, DOORSTOP_DISABLE env var value is ignored
# If enabled, inherited DOORSTOP_DISABLE and DOORSTOP_INITIALIZED markers are
# ignored so launchers can safely start a replacement game process
# USE THIS ONLY WHEN ASKED TO OR YOU KNOW WHAT THIS MEANS
ignore_disable_switch="0"

Expand Down Expand Up @@ -62,33 +68,6 @@ corlib_dir=""
# Everything past this point is the actual script
set -e

# Special case: program is launched via Steam on Linux
# In that case rerun the script via their bootstrapper to delay adding Doorstop to LD_PRELOAD
# This is required until https://github.com/NeighTools/UnityDoorstop/issues/88 is resolved
for a in "$@"; do
if [ "$a" = "SteamLaunch" ]; then
rotated=0; max=$#
while [ $rotated -lt $max ]; do
# Test if argument is prefixed with the value of $PWD
if [ "$1" != "${1#"${PWD%/}/"}" ]; then
to_rotate=$(($# - rotated))
set -- "$@" "$0"
while [ $((to_rotate-=1)) -ge 0 ]; do
set -- "$@" "$1"
shift
done
exec "$@"
else
set -- "$@" "$1"
shift
rotated=$((rotated+1))
fi
done
echo "Could not determine game executable launched by Steam" 1>&2
exit 1
fi
done

# Handle first param being executable name
if [ -x "$1" ] ; then
executable_name="$1"
Expand All @@ -115,6 +94,38 @@ abs_path() {
echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
}

normalize_search_paths() {
remaining_paths="$1"
normalized_paths=""

while :; do
case "$remaining_paths" in
*:*)
search_path=${remaining_paths%%:*}
remaining_paths=${remaining_paths#*:}
has_more_paths=1
;;
*)
search_path=$remaining_paths
has_more_paths=0
;;
esac

if [ -n "$search_path" ]; then
search_path="$(abs_path "$search_path")"
if [ -n "$normalized_paths" ]; then
normalized_paths="${normalized_paths}:${search_path}"
else
normalized_paths=$search_path
fi
fi

[ "$has_more_paths" -eq 1 ] || break
done

printf '%s\n' "$normalized_paths"
}

# Set executable path and the extension to use for the libdoorstop shared object as well as check whether we're running on Apple Silicon
os_type="$(uname -s)"
case ${os_type} in
Expand Down Expand Up @@ -280,6 +291,11 @@ while [ $i -lt $max ]; do
shift
i=$((i+1))
;;
--doorstop-macos-architectures)
macos_architectures="$2"
shift
i=$((i+1))
;;
*)
set -- "$@" "$1"
;;
Expand All @@ -288,7 +304,10 @@ while [ $i -lt $max ]; do
i=$((i+1))
done

target_assembly="$(abs_path "$target_assembly")"
if [ -n "$target_assembly" ]; then
target_assembly="$(abs_path "$target_assembly")"
fi
dll_search_path_override="$(normalize_search_paths "$dll_search_path_override")"

# Move variables to environment
export DOORSTOP_ENABLED="$enabled"
Expand Down Expand Up @@ -321,13 +340,26 @@ else
fi

if [ -n "${is_apple_silicon}" ]; then
export ARCHPREFERENCE="arm64,x86_64"
if [ -z "${macos_architectures}" ]; then
echo "macos_architectures must list at least one supported architecture" 1>&2
exit 1
fi
export ARCHPREFERENCE="${macos_architectures}"

# We need to use arch for Apple Silicon to allow the executable to be run natively as otherwise if
# the executable is universal, supporting both x86_64 and arm64, MacOs will still run it as x86_64
# if the parent process is running as x86.
# arch also strips the DYLD_INSERT_LIBRARIES env var so we have to pass that in manually
exec arch -e DYLD_INSERT_LIBRARIES="${DYLD_INSERT_LIBRARIES}" "$executable_path" "$@"
# Keep injected libraries and their search paths out of the arm64e arch
# helper itself, then add them back only for the game process. This must use
# the shell builtin unset; an external env helper would encounter the same
# architecture mismatch.
doorstop_dyld_library_path="${DYLD_LIBRARY_PATH}"
doorstop_dyld_insert_libraries="${DYLD_INSERT_LIBRARIES}"
unset DYLD_LIBRARY_PATH DYLD_INSERT_LIBRARIES
exec arch \
-e DYLD_LIBRARY_PATH="${doorstop_dyld_library_path}" \
-e DYLD_INSERT_LIBRARIES="${doorstop_dyld_insert_libraries}" \
"$executable_path" "$@"
else
exec "$executable_path" "$@"
fi
2 changes: 1 addition & 1 deletion assets/windows/doorstop_config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ redirect_output_log=false
# Overrides the default boot.config file path
boot_config_override=

# If enabled, DOORSTOP_DISABLE env var value is ignored
# If enabled, inherited DOORSTOP_DISABLE and DOORSTOP_INITIALIZED values are ignored
# USE THIS ONLY WHEN ASKED TO OR YOU KNOW WHAT THIS MEANS
ignore_disable_switch=false

Expand Down
4 changes: 2 additions & 2 deletions build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ param (
$ScriptArgs
)

$VERSION = "2.6.1"
$VERSION = "2.8.9"

function writeErrorTip($msg) {
Write-Host $msg -BackgroundColor Red -ForegroundColor White
Expand Down Expand Up @@ -57,4 +57,4 @@ foreach ($a in $Arch) {
$verbose_opt = if ($with_logging) { "--include_logging=y" } else { "--include_logging=n" }
Invoke-Expression "& $XMAKE_EXE f -a $a $verbose_opt"
Invoke-Expression "& $XMAKE_EXE $($ScriptArgs -join " ")"
}
}
Loading