diff --git a/Doorstop b/Doorstop
deleted file mode 160000
index f9a0d26b..00000000
--- a/Doorstop
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit f9a0d26bfb2a6f24abd3b438eb6b813a54e3ed4e
diff --git a/Doorstop/.gitignore b/Doorstop/.gitignore
new file mode 100644
index 00000000..f35b4165
--- /dev/null
+++ b/Doorstop/.gitignore
@@ -0,0 +1,325 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# Prerequisites
+*.d
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build folder
+Build/
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+.vscode/
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+**/Properties/launchSettings.json
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Typescript v1 declaration files
+typings/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
diff --git a/Doorstop/DoorstopTest/DoorstopTest.csproj b/Doorstop/DoorstopTest/DoorstopTest.csproj
new file mode 100644
index 00000000..77751093
--- /dev/null
+++ b/Doorstop/DoorstopTest/DoorstopTest.csproj
@@ -0,0 +1,58 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}
+ Library
+ Properties
+ Doorstop
+ Doorstop
+ v3.5
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+ true
+ bin\Verbose\
+ DEBUG;TRACE
+ full
+ AnyCPU
+ prompt
+ MinimumRecommendedRules.ruleset
+
+
+ bin\Verbose_Release\
+ TRACE
+ true
+ pdbonly
+ AnyCPU
+ prompt
+ MinimumRecommendedRules.ruleset
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Doorstop/DoorstopTest/Loader.cs b/Doorstop/DoorstopTest/Loader.cs
new file mode 100644
index 00000000..23780df0
--- /dev/null
+++ b/Doorstop/DoorstopTest/Loader.cs
@@ -0,0 +1,24 @@
+using System;
+using System.IO;
+
+namespace Doorstop
+{
+ public static class Loader
+ {
+ public static void Main(string[] args)
+ {
+ using (TextWriter tw = File.CreateText("doorstop_is_alive.txt"))
+ {
+ tw.WriteLine($"Hello! This text file was generated by Doorstop on {DateTime.Now:R}!");
+ tw.WriteLine($"I was called with {args.Length} params!");
+
+ for (int i = 0; i < args.Length; i++)
+ tw.WriteLine($"{i} => {args[i]}");
+
+ tw.WriteLine("Replace this DLL with a custom-made one to do whatever you want!");
+
+ tw.Flush();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Doorstop/DoorstopTest/Properties/AssemblyInfo.cs b/Doorstop/DoorstopTest/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..c19ccf3e
--- /dev/null
+++ b/Doorstop/DoorstopTest/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("PatchLoader")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("PatchLoader")]
+[assembly: AssemblyCopyright("Copyright © 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("62b61ccc-0775-4cf4-b37a-eb5e33cbd08c")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Doorstop/LICENSE b/Doorstop/LICENSE
new file mode 100644
index 00000000..3bbbc1ee
--- /dev/null
+++ b/Doorstop/LICENSE
@@ -0,0 +1,116 @@
+CC0 1.0 Universal
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator and
+subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for the
+purpose of contributing to a commons of creative, cultural and scientific
+works ("Commons") that the public can reliably and without fear of later
+claims of infringement build upon, modify, incorporate in other works, reuse
+and redistribute as freely as possible in any form whatsoever and for any
+purposes, including without limitation commercial purposes. These owners may
+contribute to the Commons to promote the ideal of a free culture and the
+further production of creative, cultural and scientific works, or to gain
+reputation or greater distribution for their Work in part through the use and
+efforts of others.
+
+For these and/or other purposes and motivations, and without any expectation
+of additional consideration or compensation, the person associating CC0 with a
+Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
+and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
+and publicly distribute the Work under its terms, with knowledge of his or her
+Copyright and Related Rights in the Work and the meaning and intended legal
+effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not limited
+to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display, communicate,
+ and translate a Work;
+
+ ii. moral rights retained by the original author(s) and/or performer(s);
+
+ iii. publicity and privacy rights pertaining to a person's image or likeness
+ depicted in a Work;
+
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+
+ v. rights protecting the extraction, dissemination, use and reuse of data in
+ a Work;
+
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation thereof,
+ including any amended or successor version of such directive); and
+
+ vii. other similar, equivalent or corresponding rights throughout the world
+ based on applicable law or treaty, and any national implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention of,
+applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
+unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
+and Related Rights and associated claims and causes of action, whether now
+known or unknown (including existing as well as future claims and causes of
+action), in the Work (i) in all territories worldwide, (ii) for the maximum
+duration provided by applicable law or treaty (including future time
+extensions), (iii) in any current or future medium and for any number of
+copies, and (iv) for any purpose whatsoever, including without limitation
+commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
+the Waiver for the benefit of each member of the public at large and to the
+detriment of Affirmer's heirs and successors, fully intending that such Waiver
+shall not be subject to revocation, rescission, cancellation, termination, or
+any other legal or equitable action to disrupt the quiet enjoyment of the Work
+by the public as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason be
+judged legally invalid or ineffective under applicable law, then the Waiver
+shall be preserved to the maximum extent permitted taking into account
+Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
+is so judged Affirmer hereby grants to each affected person a royalty-free,
+non transferable, non sublicensable, non exclusive, irrevocable and
+unconditional license to exercise Affirmer's Copyright and Related Rights in
+the Work (i) in all territories worldwide, (ii) for the maximum duration
+provided by applicable law or treaty (including future time extensions), (iii)
+in any current or future medium and for any number of copies, and (iv) for any
+purpose whatsoever, including without limitation commercial, advertising or
+promotional purposes (the "License"). The License shall be deemed effective as
+of the date CC0 was applied by Affirmer to the Work. Should any part of the
+License for any reason be judged legally invalid or ineffective under
+applicable law, such partial invalidity or ineffectiveness shall not
+invalidate the remainder of the License, and in such case Affirmer hereby
+affirms that he or she will not (i) exercise any of his or her remaining
+Copyright and Related Rights in the Work or (ii) assert any associated claims
+and causes of action with respect to the Work, in either case contrary to
+Affirmer's express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+
+ b. Affirmer offers the Work as-is and makes no representations or warranties
+ of any kind concerning the Work, express, implied, statutory or otherwise,
+ including without limitation warranties of title, merchantability, fitness
+ for a particular purpose, non infringement, or the absence of latent or
+ other defects, accuracy, or the present or absence of errors, whether or not
+ discoverable, all to the greatest extent permissible under applicable law.
+
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without limitation
+ any person's Copyright and Related Rights in the Work. Further, Affirmer
+ disclaims responsibility for obtaining any necessary consents, permissions
+ or other rights required for any use of the Work.
+
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to this
+ CC0 or use of the Work.
+
+For more information, please see
+
\ No newline at end of file
diff --git a/Doorstop/Proxy/Proxy.vcxproj b/Doorstop/Proxy/Proxy.vcxproj
new file mode 100644
index 00000000..a8c6a1f9
--- /dev/null
+++ b/Doorstop/Proxy/Proxy.vcxproj
@@ -0,0 +1,190 @@
+
+
+
+
+ Release
+ Win32
+
+
+ Release
+ x64
+
+
+ Verbose_Release
+ Win32
+
+
+ Verbose_Release
+ x64
+
+
+
+ {88609E16-731F-46C9-8139-6B1A7A83240D}
+ Win32Proj
+ proxy
+ 10.0
+ proxy
+
+
+
+ DynamicLibrary
+ false
+ v142
+ true
+ Unicode
+
+
+ DynamicLibrary
+ false
+ v142
+ true
+ Unicode
+
+
+ DynamicLibrary
+ false
+ v142
+ true
+ Unicode
+
+
+ DynamicLibrary
+ false
+ v142
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+ $(ProjectDir)bin\$(Platform)\$(Configuration)\
+ obj\$(Platform)\$(Configuration)\
+
+
+ false
+ $(ProjectDir)bin\$(Platform)\$(Configuration)\
+ obj\$(Platform)\$(Configuration)\
+
+
+ false
+ $(ProjectDir)bin\$(Platform)\$(Configuration)\
+ obj\$(Platform)\$(Configuration)\
+
+
+ false
+ $(ProjectDir)bin\$(Platform)\$(Configuration)\
+ obj\$(Platform)\$(Configuration)\
+
+
+
+ Level4
+
+
+ MinSpace
+ true
+ false
+ NDEBUG;_WINDOWS;_USRDLL;NOGDI;%(PreprocessorDefinitions)
+ c11
+ CompileAsC
+ CompileAsC
+ AnySuitable
+ false
+ false
+ false
+ false
+ MultiThreadedDLL
+ MultiThreadedDLL
+
+
+ Windows
+ true
+ true
+ true
+ $(MSBuildProjectDirectory)\proxy.def
+ %(AdditionalDependencies)
+
+
+ DllMain
+ false
+ false
+ false
+ %(AdditionalOptions)
+ %(AdditionalOptions)
+
+
+
+
+ Level4
+
+
+ MinSpace
+ true
+ false
+ _VERBOSE;NDEBUG;_WINDOWS;_USRDLL;NOGDI;%(PreprocessorDefinitions)
+ c11
+ CompileAsC
+ CompileAsC
+ AnySuitable
+ false
+ false
+ false
+ false
+ MultiThreadedDLL
+ MultiThreadedDLL
+
+
+ Windows
+ true
+ true
+ true
+ $(MSBuildProjectDirectory)\proxy.def
+ %(AdditionalDependencies)
+
+
+ DllMain
+ false
+ false
+ false
+ %(AdditionalOptions)
+ %(AdditionalOptions)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Doorstop/Proxy/Proxy.vcxproj.filters b/Doorstop/Proxy/Proxy.vcxproj.filters
new file mode 100644
index 00000000..162f1bac
--- /dev/null
+++ b/Doorstop/Proxy/Proxy.vcxproj.filters
@@ -0,0 +1,61 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;hm;inl;inc;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+ {384fe73e-c79a-42c8-8d61-73dc2f7e7432}
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+ Resource Files
+
+
+
+
+ Other
+
+
+
\ No newline at end of file
diff --git a/Doorstop/Proxy/assert_util.h b/Doorstop/Proxy/assert_util.h
new file mode 100644
index 00000000..ec026404
--- /dev/null
+++ b/Doorstop/Proxy/assert_util.h
@@ -0,0 +1,63 @@
+#pragma once
+
+
+#ifdef _VERBOSE
+
+#pragma comment(lib, "ucrt.lib")
+
+#include
+#include
+
+static HANDLE log_handle;
+char buffer[8192];
+wchar_t bufferW[8192];
+
+inline void init_logger()
+{
+ log_handle = CreateFileA("doorstop.log", GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
+ NULL);
+}
+
+inline void free_logger()
+{
+ CloseHandle(log_handle);
+}
+
+#define LOG(message, ...) \
+ { \
+ int len = _snprintf_s(buffer, sizeof(buffer)/sizeof(char), _TRUNCATE, message, __VA_ARGS__); \
+ WriteFile(log_handle, buffer, len, NULL, NULL); \
+ }
+#else
+inline void init_logger()
+{
+}
+
+inline void free_logger()
+{
+}
+
+#define LOG(message, ...)
+#endif
+
+#define ASSERT_F(test, message, ...) \
+ if(!(test)) \
+ { \
+ _snwprintf_s(bufferW, sizeof(bufferW)/sizeof(wchar_t), _TRUNCATE, message, __VA_ARGS__); \
+ MessageBoxW(NULL, bufferW, L"Doorstop: Fatal", MB_OK | MB_ICONERROR); \
+ ExitProcess(EXIT_FAILURE); \
+ }
+
+// A helper for cleaner error logging
+#define ASSERT(test, message) \
+ if(!(test)) \
+ { \
+ MessageBoxW(NULL, message, L"Doorstop: Fatal", MB_OK | MB_ICONERROR); \
+ ExitProcess(EXIT_FAILURE); \
+ }
+
+#define ASSERT_SOFT(test, ...) \
+ if(!(test)) \
+ { \
+ return __VA_ARGS__; \
+ }
diff --git a/Doorstop/Proxy/config.h b/Doorstop/Proxy/config.h
new file mode 100644
index 00000000..ca568c38
--- /dev/null
+++ b/Doorstop/Proxy/config.h
@@ -0,0 +1,137 @@
+#pragma once
+
+#pragma warning( disable : 4267 )
+
+#include
+#include
+#include "winapi_util.h"
+#include "assert_util.h"
+#include "crt.h"
+
+#define CONFIG_NAME L"doorstop_config"
+#define DEFAULT_TARGET_ASSEMBLY L"Doorstop.dll"
+#define EXE_EXTENSION_LENGTH 4
+
+BOOL enabled = FALSE;
+BOOL debug = FALSE;
+BOOL debug_server = FALSE;
+BOOL debug_info = FALSE;
+wchar_t *targetAssembly = NULL;
+
+#define STR_EQUAL(str1, str2) (lstrcmpiW(str1, str2) == 0)
+
+inline void initConfigFile()
+{
+ enabled = TRUE;
+
+ WIN32_FIND_DATAW findData;
+ HANDLE findHandle = FindFirstFileW(L"*_Data", &findData);
+ if (findHandle == INVALID_HANDLE_VALUE)
+ {
+ MessageBoxW(NULL, L"Could not locate game being injected!", L"No files found in current directory matching '*_Data'",
+ MB_OK | MB_ICONERROR | MB_SYSTEMMODAL | MB_TOPMOST | MB_SETFOREGROUND);
+
+ ExitProcess(GetLastError());
+ }
+
+ do
+ {
+ if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ { // must be a directory
+ wchar_t* target = memalloc(MAX_PATH * sizeof(wchar_t));
+
+ const int EXIT_FAILURE = 1;
+ ASSERT(target != NULL, L"Address returned by memalloc was NULL!");
+
+ wmemset(target, 0, MAX_PATH);
+
+ wmemcpy(target, findData.cFileName, wcslen(findData.cFileName));
+ wmemcpy(target + wcslen(target), L"/Managed/IPA.Injector.dll", 26);
+
+ targetAssembly = target;
+ FindClose(findHandle);
+ break;
+ }
+ }
+ while (FindNextFileW(findHandle, &findData) != 0);
+
+ if (targetAssembly == NULL)
+ {
+ MessageBoxW(NULL, L"Could not locate game being injected!", L"No valid directories matching '*_Data'",
+ MB_OK | MB_ICONERROR | MB_SYSTEMMODAL | MB_TOPMOST | MB_SETFOREGROUND);
+
+ ExitProcess(GetLastError());
+ }
+}
+
+inline void initCmdArgs()
+{
+ wchar_t *args = GetCommandLineW();
+ int argc = 0;
+ wchar_t **argv = CommandLineToArgvW(args, &argc);
+
+#define IS_ARGUMENT(arg_name) STR_EQUAL(arg, arg_name) && i < argc
+
+ for (int i = 0; i < argc; i++)
+ {
+ wchar_t *arg = argv[i];
+ /*if (IS_ARGUMENT(L"--doorstop-enable"))
+ {
+ wchar_t *par = argv[++i];
+
+ if (STR_EQUAL(par, L"true"))
+ enabled = TRUE;
+ else if (STR_EQUAL(par, L"false"))
+ enabled = FALSE;
+ }
+ else if (IS_ARGUMENT(L"--doorstop-target"))
+ {
+ if (targetAssembly != NULL)
+ memfree(targetAssembly);
+ const size_t len = wcslen(argv[i + 1]) + 1;
+ targetAssembly = memalloc(sizeof(wchar_t) * len);
+ lstrcpynW(targetAssembly, argv[++i], len);
+ LOG("Args; Target assembly: %S\n", targetAssembly);
+ }
+ else */if (IS_ARGUMENT(L"--mono-debug"))
+ {
+ debug = TRUE;
+ debug_info = TRUE;
+ LOG("Enabled debugging\n");
+ }
+ else if (IS_ARGUMENT(L"--debug"))
+ {
+ debug_info = TRUE;
+ LOG("Enabled loading of debug info\n");
+ }
+ else if (IS_ARGUMENT(L"--server"))
+ {
+ debug_server = TRUE;
+ LOG("Server-mode debugging enabled\n");
+ }
+ }
+
+ LocalFree(argv);
+}
+
+inline void initEnvVars()
+{
+ if (GetEnvironmentVariableW(L"DOORSTOP_DISABLE", NULL, 0) != 0)
+ {
+ LOG("DOORSTOP_DISABLE is set! Disabling Doorstop!\n");
+ enabled = FALSE;
+ }
+}
+
+inline void loadConfig()
+{
+ initConfigFile();
+ initCmdArgs();
+ initEnvVars();
+}
+
+inline void cleanupConfig()
+{
+ if (targetAssembly != NULL)
+ memfree(targetAssembly);
+}
diff --git a/Doorstop/Proxy/crt.h b/Doorstop/Proxy/crt.h
new file mode 100644
index 00000000..432f7cda
--- /dev/null
+++ b/Doorstop/Proxy/crt.h
@@ -0,0 +1,65 @@
+#pragma once
+
+#pragma warning( disable : 4028 28251 6001 )
+
+#include
+
+HANDLE hHeap;
+
+#define memalloc(size) HeapAlloc(hHeap, HEAP_GENERATE_EXCEPTIONS, size)
+#define memcalloc(size) HeapAlloc(hHeap, HEAP_ZERO_MEMORY, size)
+#define memfree(mem) HeapFree(hHeap, 0, mem)
+
+#define STR_LEN(str) (sizeof(str) / sizeof(str[0]))
+
+#define DEBUG_BREAK { if (IsDebuggerPresent()) { __debugbreak(); } }
+
+inline void *wmemcpy(wchar_t *dst, const wchar_t *src, size_t n)
+{
+ wchar_t *d = dst;
+ const wchar_t *s = src;
+ while (n--)
+ *d++ = *s++;
+ return dst;
+}
+
+inline void *wmemset(wchar_t *dst, wchar_t c, size_t n)
+{
+ wchar_t *d = dst;
+ while (n--)
+ *(d++) = c;
+ return dst;
+}
+
+void *memset(void *dst, char c, int n)
+{
+ char *d = dst;
+ while (n--)
+ *d = c;
+ return dst;
+}
+
+inline void *memcpy(void *dst, const void *src, int n)
+{
+ char *d = dst;
+ const char *s = src;
+ while (n--)
+ *d++ = *s++;
+ return dst;
+}
+
+inline size_t wcslen(wchar_t const *str)
+{
+ size_t result = 0;
+ while (*str++)
+ result++;
+ return result;
+}
+
+inline size_t strlen(char const *str)
+{
+ size_t result = 0;
+ while (*str++)
+ result++;
+ return result;
+}
diff --git a/Doorstop/Proxy/dll.def b/Doorstop/Proxy/dll.def
new file mode 100644
index 00000000..f85893cb
--- /dev/null
+++ b/Doorstop/Proxy/dll.def
@@ -0,0 +1,64 @@
+EXPORTS
+SvchostPushServiceGlobals
+WinHttpAddRequestHeaders
+WinHttpAutoProxySvcMain
+WinHttpCheckPlatform
+WinHttpCloseHandle
+WinHttpConnect
+WinHttpConnectionDeletePolicyEntries
+WinHttpConnectionDeleteProxyInfo
+WinHttpConnectionFreeNameList
+WinHttpConnectionFreeProxyInfo
+WinHttpConnectionFreeProxyList
+WinHttpConnectionGetNameList
+WinHttpConnectionGetProxyInfo
+WinHttpConnectionGetProxyList
+WinHttpConnectionSetPolicyEntries
+WinHttpConnectionSetProxyInfo
+WinHttpConnectionUpdateIfIndexTable
+WinHttpCrackUrl
+WinHttpCreateProxyResolver
+WinHttpCreateUrl
+WinHttpDetectAutoProxyConfigUrl
+WinHttpFreeProxyResult
+WinHttpFreeProxyResultEx
+WinHttpFreeProxySettings
+WinHttpGetDefaultProxyConfiguration
+WinHttpGetIEProxyConfigForCurrentUser
+WinHttpGetProxyForUrl
+WinHttpGetProxyForUrlEx
+WinHttpGetProxyForUrlEx2
+WinHttpGetProxyForUrlHvsi
+WinHttpGetProxyResult
+WinHttpGetProxyResultEx
+WinHttpGetProxySettingsVersion
+WinHttpGetTunnelSocket
+WinHttpOpen
+WinHttpOpenRequest
+WinHttpProbeConnectivity
+WinHttpQueryAuthSchemes
+WinHttpQueryDataAvailable
+WinHttpQueryHeaders
+WinHttpQueryOption
+WinHttpReadData
+WinHttpReadProxySettings
+WinHttpReadProxySettingsHvsi
+WinHttpReceiveResponse
+WinHttpResetAutoProxy
+WinHttpSaveProxyCredentials
+WinHttpSendRequest
+WinHttpSetCredentials
+WinHttpSetDefaultProxyConfiguration
+WinHttpSetOption
+WinHttpSetStatusCallback
+WinHttpSetTimeouts
+WinHttpTimeFromSystemTime
+WinHttpTimeToSystemTime
+WinHttpWebSocketClose
+WinHttpWebSocketCompleteUpgrade
+WinHttpWebSocketQueryCloseStatus
+WinHttpWebSocketReceive
+WinHttpWebSocketSend
+WinHttpWebSocketShutdown
+WinHttpWriteData
+WinHttpWriteProxySettings
diff --git a/Doorstop/Proxy/hook.h b/Doorstop/Proxy/hook.h
new file mode 100644
index 00000000..85e5e872
--- /dev/null
+++ b/Doorstop/Proxy/hook.h
@@ -0,0 +1,162 @@
+/*
+ * EAT-based hooking for x86/x64.
+ *
+ * Big thanks to ez (https://github.com/ezdiy/) for making this!
+ *
+ * Creates "hooks" by modifying the module's export address table.
+ * The procedure works in three main parts:
+ *
+ * 1. Reading the module's PE file and getting all exported functions.
+ * 2. Finding the right function to "hook" by simple address lookup
+ * 3. Modify the entry to point to the hook.
+ *
+ * The idea is based on the fact that the export table allows forwarding imports:
+ *
+ * https://en.wikibooks.org/wiki/X86_Disassembly/Windows_Executable_Files#Forwarding
+ *
+ * You can also find some reference material on the same page to understand this code better.
+ *
+ */
+
+#pragma once
+
+#pragma warning( disable : 4267 90 )
+
+#include
+
+// PE format uses RVAs (Relative Virtual Addresses) to save addresses relative to the base of the module
+// More info: https://en.wikibooks.org/wiki/X86_Disassembly/Windows_Executable_Files#Relative_Virtual_Addressing_(RVA)
+//
+// This helper macro converts the saved RVA to a fully valid pointer to the data in the PE file
+#define RVA2PTR(t,base,rva) ((t)(((PCHAR) base) + rva))
+
+// A helper function to write into protected memory
+inline int vpmemcpy(void *dst, void *src, size_t sz)
+{
+ DWORD oldp;
+ // Make the memory page writeable
+ if (!VirtualProtect(dst, sz, PAGE_READWRITE, &oldp))
+ return 1;
+ memcpy(dst, src, sz);
+ // Restore the protection level
+ VirtualProtect(dst, sz, oldp, &oldp);
+ return 0;
+}
+
+
+/**
+ * \brief Replaces the specified function entry in the EAT with a forward to a custom one, thus creating the "hook" effect.
+ * \param hostDll The address of the module to hook.
+ * \param originalFunction Address of the original function.
+ * \param forwardFunctionEntry Name of the function to add a forward to. Must be of form `dll.API`.
+ * \return TRUE, if hooking succeeded, otherwise, FALSE.
+ */
+inline BOOL ezHook(HMODULE hostDll, void *originalFunction, char *forwardFunctionEntry)
+{
+ /*
+ * Note that we are not doing any trampoline magic or editing the assembly!
+ *
+ * Instead, we are reading the module's PE file, find the original function's entry in the export address table (EAT),
+ * and replace it with a forward import.
+ *
+ * This ultimately will fool the game/executable to call our hook, while keeping original function intact.
+ *
+ * Thus, in order to work, the proxy DLL has to export the hook, because we are essentially
+ * asking the game to call our hook without ever going to the original function (unlike with trampolines).
+ */
+
+ size_t fwdlen = strlen(forwardFunctionEntry);
+
+ // The module always starts with a DOS (or "MZ") header
+ IMAGE_DOS_HEADER *mz = (PIMAGE_DOS_HEADER)hostDll;
+
+ // Next, get the NT headers. The offset to them is saved in e_lfanew
+ IMAGE_NT_HEADERS *nt = RVA2PTR(PIMAGE_NT_HEADERS, mz, mz->e_lfanew);
+
+ // Get the pointer to the data directory of the exports
+ IMAGE_DATA_DIRECTORY *edirp = &nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
+ IMAGE_DATA_DIRECTORY edir = *edirp;
+
+ // Finally, the virtual address in the data direcotry tells the location of the exports table
+ IMAGE_EXPORT_DIRECTORY *exports = RVA2PTR(PIMAGE_EXPORT_DIRECTORY, mz, edir.VirtualAddress);
+
+ // Read the addrress of the function list and the address of function names
+ DWORD *addrs = RVA2PTR(DWORD*, mz, exports->AddressOfFunctions);
+ // DWORD* names = RVA2PTR(DWORD*, mz, exports->AddressOfNames);
+
+ // Iterate through all functions
+ for (unsigned i = 0; i < exports->NumberOfFunctions; i++)
+ {
+ //char* name = RVA2PTR(char*, mz, names[i]); // Name of the exported function
+ void *addr = RVA2PTR(void*, mz, addrs[i]); // Address of the exported function
+
+ // Check if we have the function we need to modify
+ if (addr == originalFunction)
+ {
+ DWORD fptr = edir.VirtualAddress + edir.Size;
+ int err = 0;
+
+ // Update the entry to go the the last entry (which we will populate in the next memcpy)
+ err |= vpmemcpy(&addrs[i], &fptr, sizeof(fptr));
+
+ // Add the forwarding import to our function at the end of the EAT
+ err |= vpmemcpy(((char*)exports + edir.Size), forwardFunctionEntry, fwdlen);
+
+ // Increment the size of the export data directory
+ // and write the new export data directory
+ edir.Size += fwdlen + 1;
+ err |= vpmemcpy(edirp, &edir, sizeof(edir));
+ return err == 0;
+ }
+ }
+ return FALSE;
+}
+
+
+/**
+ * \brief Hooks the given function through the Import Address Table
+ * \param dll Module to hook
+ * \param targetFunction Address of the target function to hook
+ * \param detourFunction Address of the detour function
+ * \return TRUE if successful, otherwise FALSE
+ */
+inline BOOL iat_hook(HMODULE dll, char const* targetDLL, void *targetFunction, void *detourFunction)
+{
+ IMAGE_DOS_HEADER *mz = (PIMAGE_DOS_HEADER)dll;
+
+ IMAGE_NT_HEADERS *nt = RVA2PTR(PIMAGE_NT_HEADERS, mz, mz->e_lfanew);
+
+ IMAGE_IMPORT_DESCRIPTOR *imports = RVA2PTR(PIMAGE_IMPORT_DESCRIPTOR, mz, nt->OptionalHeader.DataDirectory[
+ IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
+
+ for (int i = 0; imports[i].Characteristics; i++)
+ {
+ char *name = RVA2PTR(char*, mz, imports[i].Name);
+
+ if(lstrcmpiA(name, targetDLL) != 0)
+ continue;
+
+ void **thunk = RVA2PTR(void**, mz, imports[i].FirstThunk);
+ void **nextThunk = RVA2PTR(void**, mz, imports[i+1].FirstThunk);
+
+ for (; thunk && thunk < nextThunk; thunk++)
+ {
+ void *import = *thunk;
+
+ if (import != targetFunction)
+ continue;
+
+ DWORD oldState;
+ if (!VirtualProtect(thunk, sizeof(void*), PAGE_READWRITE, &oldState))
+ return FALSE;
+
+ *thunk = (void*)detourFunction;
+
+ VirtualProtect(thunk, sizeof(void*), oldState, &oldState);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
diff --git a/Doorstop/Proxy/main.c b/Doorstop/Proxy/main.c
new file mode 100644
index 00000000..1184fc2d
--- /dev/null
+++ b/Doorstop/Proxy/main.c
@@ -0,0 +1,462 @@
+/*
+ * main.cpp -- The main "entry point" and the main logic of the DLL.
+ *
+ * Here, we define and initialize struct Main that contains the main code of this DLL.
+ *
+ * The main procedure goes as follows:
+ * 1. The loader checks that PatchLoader.dll and mono.dll exist
+ * 2. mono.dll is loaded into memory and some of its functions are looked up
+ * 3. mono_jit_init_version is hooked with the help of MinHook
+ *
+ * Then, the loader waits until Unity creates its root domain for mono (which is done with mono_jit_init_version).
+ *
+ * Inside mono_jit_init_version hook:
+ * 1. Call the original mono_jit_init_version to get the Unity root domain
+ * 2. Load PatchLoader.dll into the root domain
+ * 3. Find and invoke PatchLoader.Loader.Run()
+ *
+ * Rest of the work is done on the managed side.
+ *
+ */
+
+#pragma warning( disable : 4267 100 152 6387 4456 6011 )
+
+#include "winapi_util.h"
+#include
+
+#include "config.h"
+#include "mono.h"
+#include "hook.h"
+#include "assert_util.h"
+#include "proxy.h"
+#include
+
+#include
+
+EXTERN_C IMAGE_DOS_HEADER __ImageBase; // This is provided by MSVC with the infomration about this DLL
+
+HANDLE unhandledMutex;
+void ownMonoJitParseOptions(int argc, char * argv[]);
+BOOL setOptions = FALSE;
+
+void unhandledException(void* exc, void* data)
+{
+ WaitForSingleObject(unhandledMutex, INFINITE);
+
+ void* exception = NULL;
+ void* mstr = mono_object_to_string(exc, &exception);
+
+ if (exception != NULL)
+ {
+#ifdef _VERBOSE
+ void* monostr = mono_object_to_string(exception, &exception);
+ if (exception != NULL)
+ {
+ DEBUG_BREAK;
+ LOG("An error occurred while stringifying uncaught error, but the error could not be stringified.\n");
+ ASSERT(FALSE, L"Uncaught exception; could not stringify");
+ }
+ else
+ {
+ char* str = mono_string_to_utf8(monostr);
+ DEBUG_BREAK;
+ LOG("An error occurred stringifying uncaught error: %s\n", str);
+
+ size_t len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
+ wchar_t* wstr = memalloc(sizeof(wchar_t) * len);
+ MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, len);
+
+ ASSERT_F(FALSE, L"Uncaught exception; stringify failed: %s", wstr);
+
+ memfree(wstr);
+ mono_free(str);
+ }
+#else
+ ASSERT(FALSE, L"Could not stringify uncaught exception");
+#endif
+ }
+
+ char* str = mono_string_to_utf8(mstr);
+ DEBUG_BREAK;
+ LOG("Uncaught exception: %s\n", str);
+
+ size_t len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
+ wchar_t* wstr = memalloc(sizeof(wchar_t) * len);
+ MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, len);
+
+#ifdef _VERBOSE
+ ASSERT(FALSE, L"Uncaught exception; see doorstop.log for details");
+#else
+ ASSERT_F(FALSE, L"Uncaught exception: %s", wstr);
+#endif
+
+ memfree(wstr);
+ mono_free(str);
+
+ ReleaseMutex(unhandledMutex);
+}
+
+// The hook for mono_jit_init_version
+// We use this since it will always be called once to initialize Mono's JIT
+void *ownMonoJitInitVersion(const char *root_domain_name, const char *runtime_version)
+{
+ // Call the original mono_jit_init_version to initialize the Unity Root Domain
+ if (debug) {
+ char* opts[1];
+ opts[0] = "";
+ ownMonoJitParseOptions(0, opts);
+ }
+#ifdef WIN32
+ if (debug_info) {
+ mono_debug_init(MONO_DEBUG_FORMAT_MONO);
+ }
+#endif
+
+ void *domain = mono_jit_init_version(root_domain_name, runtime_version);
+
+ if (debug_info) {
+#ifdef WIN64
+ mono_debug_init(MONO_DEBUG_FORMAT_MONO);
+#endif
+ mono_debug_domain_create(domain);
+ }
+
+ size_t len = WideCharToMultiByte(CP_UTF8, 0, targetAssembly, -1, NULL, 0, NULL, NULL);
+ char *dll_path = memalloc(sizeof(char) * len);
+ WideCharToMultiByte(CP_UTF8, 0, targetAssembly, -1, dll_path, len, NULL, NULL);
+
+ LOG("Loading assembly: %s\n", dll_path);
+ // Load our custom assembly into the domain
+ void *assembly = mono_domain_assembly_open(domain, dll_path);
+
+ if (assembly == NULL)
+ LOG("Failed to load assembly\n");
+
+ memfree(dll_path);
+ ASSERT_SOFT(assembly != NULL, domain);
+
+ // Get assembly's image that contains CIL code
+ void *image = mono_assembly_get_image(assembly);
+ ASSERT_SOFT(image != NULL, domain);
+
+ // Note: we use the runtime_invoke route since jit_exec will not work on DLLs
+
+ // Create a descriptor for a random Main method
+ void *desc = mono_method_desc_new("*:Main", FALSE);
+
+ // Find the first possible Main method in the assembly
+ void *method = mono_method_desc_search_in_image(desc, image);
+ ASSERT_SOFT(method != NULL, domain);
+
+ void *signature = mono_method_signature(method);
+
+ // Get the number of parameters in the signature
+ UINT32 params = mono_signature_get_param_count(signature);
+
+ void **args = NULL;
+ wchar_t *app_path = NULL;
+ if (params == 1)
+ {
+ // If there is a parameter, it's most likely a string[].
+ // Populate it as follows
+ // 0 => path to the game's executable
+ // 1 => --doorstop-invoke
+
+ get_module_path(NULL, &app_path, NULL, 0);
+
+ void *exe_path = MONO_STRING(app_path);
+ void *doorstop_handle = MONO_STRING(L"--doorstop-invoke");
+
+ void *args_array = mono_array_new(domain, mono_get_string_class(), 2);
+
+ SET_ARRAY_REF(args_array, 0, exe_path);
+ SET_ARRAY_REF(args_array, 1, doorstop_handle);
+
+ args = memalloc(sizeof(void*) * 1);
+ _ASSERTE(args != nullptr);
+ args[0] = args_array;
+ }
+
+ LOG("Installing uncaught exception handler\n");
+
+ mono_install_unhandled_exception_hook(unhandledException, NULL);
+
+ unhandledMutex = CreateMutexW(NULL, FALSE, NULL);
+
+ LOG("Invoking method!\n");
+
+ void* exception = NULL;
+ mono_runtime_invoke(method, NULL, args, &exception);
+
+ WaitForSingleObject(unhandledMutex, INFINITE); // if the EH is triggered, wait for it
+
+ if (args != NULL)
+ {
+ memfree(app_path);
+ memfree(args);
+ NULL;
+ }
+
+#ifdef _VERBOSE
+ if (exception != NULL)
+ {
+ void* monostr = mono_object_to_string(exception, &exception);
+ if (exception != NULL)
+ LOG("An error occurred while invoking the injector, but the error could not be stringified.\n")
+ else
+ {
+ char* str = mono_string_to_utf8(monostr);
+ LOG("An error occurred invoking the injector: %s\n", str);
+ mono_free(str);
+ }
+ }
+#endif
+
+ cleanupConfig();
+
+ free_logger();
+
+ ReleaseMutex(unhandledMutex);
+
+ return domain;
+}
+
+void ownMonoJitParseOptions(int argc, char * argv[])
+{
+ setOptions = TRUE;
+
+ int size = argc;
+#ifdef WIN64
+ if (debug) size += 2;
+#elif defined(WIN32)
+ if (debug) size += 1;
+#endif
+
+ char** arguments = memalloc(sizeof(char*) * size);
+ _ASSERTE(arguments != nullptr);
+ memcpy(arguments, argv, sizeof(char*) * argc);
+ if (debug) {
+ //arguments[argc++] = "--debug";
+#ifdef WIN64
+ arguments[argc++] = "--soft-breakpoints";
+#endif
+ if (debug_server)
+ arguments[argc] = "--debugger-agent=transport=dt_socket,address=0.0.0.0:10000,server=y";
+ else
+ arguments[argc] = "--debugger-agent=transport=dt_socket,address=127.0.0.1:10000,server=n";
+ }
+
+ mono_jit_parse_options(size, arguments);
+
+ memfree(arguments);
+}
+
+BOOL initialized = FALSE;
+
+void init(HMODULE module)
+{
+ if (!initialized)
+ {
+ initialized = TRUE;
+ LOG("Got mono.dll at %p\n", module);
+ loadMonoFunctions(module);
+ }
+}
+
+void * WINAPI hookGetProcAddress(HMODULE module, char const *name)
+{
+ if (lstrcmpA(name, "mono_jit_init_version") == 0)
+ {
+ init(module);
+ return (void*)&ownMonoJitInitVersion;
+ }
+ if (lstrcmpA(name, "mono_jit_parse_options") == 0 && debug)
+ {
+ init(module);
+ return (void*)&ownMonoJitParseOptions;
+ }
+ return (void*)GetProcAddress(module, name);
+}
+
+BOOL hookGetMessage(
+ BOOL isW,
+ LPMSG msg,
+ HWND hwnd,
+ UINT wMsgFilterMin,
+ UINT wMsgFilterMax
+);
+
+BOOL WINAPI hookGetMessageA(LPMSG msg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax)
+{
+ return hookGetMessage(FALSE, msg, hwnd, wMsgFilterMin, wMsgFilterMax);
+}
+BOOL WINAPI hookGetMessageW(LPMSG msg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax)
+{
+ return hookGetMessage(TRUE, msg, hwnd, wMsgFilterMin, wMsgFilterMax);
+}
+
+typedef BOOL(*GetMessageHook)(BOOL isW, BOOL result, LPMSG msg, HWND hwnd, UINT filterMin, UINT filterMax);
+
+GetMessageHook getMessageHook = NULL;
+
+__declspec(dllexport) void __stdcall SetGetMessageHook(GetMessageHook hook) {
+ getMessageHook = hook;
+}
+
+BOOL hookGetMessage(
+ BOOL isW,
+ LPMSG msg,
+ HWND hwnd,
+ UINT wMsgFilterMin,
+ UINT wMsgFilterMax
+)
+{
+ BOOL loop = FALSE;
+
+ BOOL result;
+
+ do {
+ if (isW) {
+ result = GetMessageW(msg, hwnd, wMsgFilterMin, wMsgFilterMax);
+ } else {
+ result = GetMessageA(msg, hwnd, wMsgFilterMin, wMsgFilterMax);
+ }
+
+ if (getMessageHook) {
+ loop = getMessageHook(isW, result, msg, hwnd, wMsgFilterMin, wMsgFilterMax);
+ }
+ } while (loop);
+
+ return result;
+}
+
+BOOL hookPeekMessage(
+ BOOL isW,
+ LPMSG msg,
+ HWND hwnd,
+ UINT wMsgFilterMin,
+ UINT wMsgFilterMax,
+ UINT wRemoveMsg
+);
+
+BOOL WINAPI hookPeekMessageA(LPMSG msg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg)
+{
+ return hookPeekMessage(FALSE, msg, hwnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg);
+}
+BOOL WINAPI hookPeekMessageW(LPMSG msg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg)
+{
+ return hookPeekMessage(TRUE, msg, hwnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg);
+}
+
+typedef BOOL(*PeekMessageHook)(BOOL isW, BOOL result, LPMSG msg, HWND hwnd, UINT filterMin, UINT filterMax, UINT* wRemoveMsg);
+
+PeekMessageHook peekMessageHook = NULL;
+
+__declspec(dllexport) void __stdcall SetPeekMessageHook(PeekMessageHook hook) {
+ peekMessageHook = hook;
+}
+
+BOOL hookPeekMessage(
+ BOOL isW,
+ LPMSG msg,
+ HWND hwnd,
+ UINT wMsgFilterMin,
+ UINT wMsgFilterMax,
+ UINT wRemoveMsg
+)
+{
+ BOOL loop = FALSE;
+
+ BOOL result;
+
+ do {
+ if (isW) {
+ result = PeekMessageW(msg, hwnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg);
+ }
+ else {
+ result = PeekMessageA(msg, hwnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg);
+ }
+
+ if (peekMessageHook) {
+ loop = peekMessageHook(isW, result, msg, hwnd, wMsgFilterMin, wMsgFilterMax, &wRemoveMsg);
+ }
+ } while (loop);
+
+ return result;
+}
+
+BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reasonForDllLoad, LPVOID reserved)
+{
+ if (reasonForDllLoad != DLL_PROCESS_ATTACH)
+ return TRUE;
+
+ hHeap = GetProcessHeap();
+
+ init_logger();
+
+ LOG("Doorstop started!\n");
+
+ wchar_t *dll_path = NULL;
+ size_t dll_path_len = get_module_path((HINSTANCE)&__ImageBase, &dll_path, NULL, 0);
+
+ LOG("DLL Path: %S\n", dll_path);
+
+ wchar_t *dll_name = get_file_name_no_ext(dll_path, dll_path_len);
+
+ LOG("Doorstop DLL Name: %S\n", dll_name);
+
+ loadProxy(dll_name);
+ loadConfig();
+
+ // If the loader is disabled, don't inject anything.
+ if (enabled)
+ {
+ LOG("Doorstop enabled!\n");
+ ASSERT_SOFT(GetFileAttributesW(targetAssembly) != INVALID_FILE_ATTRIBUTES, TRUE);
+
+ HMODULE targetModule = GetModuleHandleA("UnityPlayer");
+
+ if(targetModule == NULL)
+ {
+ LOG("No UnityPlayer.dll; using EXE as the hook target.");
+ targetModule = GetModuleHandleA(NULL);
+ }
+
+ LOG("Installing IAT hook\n");
+ if (!iat_hook(targetModule, "kernel32.dll", &GetProcAddress, &hookGetProcAddress))
+ {
+ LOG("Failed to install IAT hook!\n");
+ free_logger();
+ }
+
+ LOG("Hook installed!\n");
+
+ LOG("Attempting to install GetMessageA and GetMessageW hooks\n");
+
+ if (!iat_hook(targetModule, "user32.dll", &GetMessageA, &hookGetMessageA)) {
+ LOG("Could not hook GetMessageA! (not an error)\n");
+ }
+ if (!iat_hook(targetModule, "user32.dll", &GetMessageW, &hookGetMessageW)) {
+ LOG("Could not hook GetMessageW! (not an error)\n");
+ }
+
+ LOG("Attempting to install PeekMessageA and PeekMessageW hooks\n");
+
+ if (!iat_hook(targetModule, "user32.dll", &PeekMessageA, &hookPeekMessageA)) {
+ LOG("Could not hook PeekMessageA! (not an error)\n");
+ }
+ if (!iat_hook(targetModule, "user32.dll", &PeekMessageW, &hookPeekMessageW)) {
+ LOG("Could not hook PeekMessageW! (not an error)\n");
+ }
+ }
+ else
+ {
+ LOG("Doorstop disabled! memfreeing resources\n");
+ free_logger();
+ }
+
+ memfree(dll_name);
+ memfree(dll_path);
+
+ return TRUE;
+}
diff --git a/Doorstop/Proxy/mono.h b/Doorstop/Proxy/mono.h
new file mode 100644
index 00000000..a49357f8
--- /dev/null
+++ b/Doorstop/Proxy/mono.h
@@ -0,0 +1,105 @@
+/*
+ * mono.h -- Definitions for Mono C API functions.
+ *
+ * The file contains definitions for some of functions provided by Mono C API.
+ *
+ * Note: Since we don't use any mono headers, all pointers to mono-related structs are
+ * replaced with void *.
+ */
+
+#pragma once
+
+#pragma warning( disable : 4152 )
+
+#include
+
+// Creates a MonoString based from a C wide string
+#define MONO_STRING(str) mono_string_new_utf16(domain, str, wcslen(str))
+
+// Set MonoArray's index to a reference type value (i.e. string)
+#define SET_ARRAY_REF(arr, index, refVal) \
+ { \
+ void **p = (void**) mono_array_addr_with_size(arr, sizeof(void*), index); \
+ mono_gc_wbarrier_set_arrayref(arr, p, refVal); \
+ }
+
+// Here we define the pointers to some functions within mono.dll
+// Note to C learners: these are not signature definitions, but rather "variable"
+// definitions with the function pointer type.
+
+// Note: we use void* instead of the real intented structs defined in mono API
+// This way we don't need to include or define any of Mono's structs, which saves space
+// This, obviously, comes with a drawback of not being able to easily access the contents of the structs
+
+typedef enum {
+ MONO_DEBUG_FORMAT_NONE,
+ MONO_DEBUG_FORMAT_MONO,
+ /* Deprecated, the mdb debugger is not longer supported. */
+ MONO_DEBUG_FORMAT_DEBUGGER
+} MonoDebugFormat;
+
+void (*mono_jit_parse_options)(int argc, char * argv[]);
+void (*mono_debug_init)(MonoDebugFormat format);
+void(*mono_debug_domain_create)(void*);
+
+void *(*mono_jit_init_version)(const char *root_domain_name, const char *runtime_version);
+void *(*mono_domain_assembly_open)(void *domain, const char *name);
+void *(*mono_assembly_get_image)(void *assembly);
+void *(*mono_runtime_invoke)(void *method, void *obj, void **params, void **exc);
+
+void *(*mono_method_desc_new)(const char *name, int include_namespace);
+void *(*mono_method_desc_search_in_image)(void *desc, void *image);
+void *(*mono_method_signature)(void *method);
+UINT32 (*mono_signature_get_param_count)(void *sig);
+
+void *(*mono_array_new)(void *domain, void *eclass, uintptr_t n);
+void (*mono_gc_wbarrier_set_arrayref)(void *arr, void *slot_ptr, void *value);
+char *(*mono_array_addr_with_size)(void *arr, int size, uintptr_t idx);
+
+void *(*mono_get_string_class)();
+void *(*mono_string_new_utf16)(void *domain, const wchar_t *text, INT32 len);
+
+void* (*mono_object_to_string)(void* obj, void** exc);
+
+char* (*mono_string_to_utf8)(void* str);
+void (*mono_free)(void*);
+
+/* Installs a function which is called when the runtime encounters an unhandled exception.
+ * This hook isn't expected to return.
+ * If no hook has been installed, the runtime will print a message before aborting.
+ */
+typedef void (*MonoUnhandledExceptionFunc)(void* exc, void* user_data);
+void (*mono_install_unhandled_exception_hook)(MonoUnhandledExceptionFunc func, void* user_data);
+
+/**
+* \brief Loads Mono C API function pointers so that the above definitions can be called.
+* \param monoLib Mono.dll module.
+*/
+inline void loadMonoFunctions(HMODULE monoLib)
+{
+ // Enjoy the fact that C allows such sloppy casting
+ // In C++ you would have to cast to the precise function pointer type
+#define GET_MONO_PROC(name) name = (void*)GetProcAddress(monoLib, #name)
+
+ // Find and assign all our functions that we are going to use
+ GET_MONO_PROC(mono_debug_domain_create);
+ GET_MONO_PROC(mono_domain_assembly_open);
+ GET_MONO_PROC(mono_assembly_get_image);
+ GET_MONO_PROC(mono_runtime_invoke);
+ GET_MONO_PROC(mono_debug_init);
+ GET_MONO_PROC(mono_jit_init_version);
+ GET_MONO_PROC(mono_jit_parse_options);
+ GET_MONO_PROC(mono_method_desc_new);
+ GET_MONO_PROC(mono_method_desc_search_in_image);
+ GET_MONO_PROC(mono_method_signature);
+ GET_MONO_PROC(mono_signature_get_param_count);
+ GET_MONO_PROC(mono_array_new);
+ GET_MONO_PROC(mono_get_string_class);
+ GET_MONO_PROC(mono_string_new_utf16);
+ GET_MONO_PROC(mono_gc_wbarrier_set_arrayref);
+ GET_MONO_PROC(mono_array_addr_with_size);
+ GET_MONO_PROC(mono_object_to_string);
+ GET_MONO_PROC(mono_string_to_utf8);
+ GET_MONO_PROC(mono_free);
+ GET_MONO_PROC(mono_install_unhandled_exception_hook);
+}
diff --git a/Doorstop/Proxy/proxy.c b/Doorstop/Proxy/proxy.c
new file mode 100644
index 00000000..0e3bc619
--- /dev/null
+++ b/Doorstop/Proxy/proxy.c
@@ -0,0 +1,133 @@
+/* ==================================
+ * COMPUTER GENERATED -- DO NOT EDIT
+ * ==================================
+ *
+ * This file contains the definitions for all proxy functions this DLL supports.
+ *
+ * The proxies are very simple functions that should be optimizied into a
+ * single JMP instruction without editing the stack at all.
+ *
+ * NOTE: While this works, this is a somewhat hackish approach that is based on how
+ * the compiler optimizes the code. That said, the proxy will not work on Debug build currently
+ * (that can be fixed by changing the appropriate compile flag that I am yet to locate).
+ */
+
+#pragma warning( disable : 4244 )
+
+#include
+
+#define ADD_ORIGINAL(i, name) originalFunctions[i] = GetProcAddress(dll, #name)
+
+#define PROXY(i, name) \
+ __declspec(dllexport) ULONG __stdcall name() \
+ { \
+ return originalFunctions[i](); \
+ }
+
+FARPROC originalFunctions[50] = {0};
+
+void loadFunctions(HMODULE dll)
+{
+ADD_ORIGINAL(0, WinHttpAddRequestHeaders);
+ADD_ORIGINAL(1, WinHttpAutoProxySvcMain);
+ADD_ORIGINAL(2, WinHttpCheckPlatform);
+ADD_ORIGINAL(3, WinHttpCloseHandle);
+ADD_ORIGINAL(4, WinHttpConnect);
+ADD_ORIGINAL(5, WinHttpConnectionDeleteProxyInfo);
+ADD_ORIGINAL(6, WinHttpConnectionFreeNameList);
+ADD_ORIGINAL(7, WinHttpConnectionFreeProxyInfo);
+ADD_ORIGINAL(8, WinHttpConnectionFreeProxyList);
+ADD_ORIGINAL(9, WinHttpConnectionGetNameList);
+ADD_ORIGINAL(10, WinHttpConnectionGetProxyInfo);
+ADD_ORIGINAL(11, WinHttpConnectionGetProxyList);
+ADD_ORIGINAL(12, WinHttpConnectionSetProxyInfo);
+ADD_ORIGINAL(13, WinHttpCrackUrl);
+ADD_ORIGINAL(14, WinHttpCreateProxyResolver);
+ADD_ORIGINAL(15, WinHttpCreateUrl);
+ADD_ORIGINAL(16, WinHttpDetectAutoProxyConfigUrl);
+ADD_ORIGINAL(17, WinHttpFreeProxyResult);
+ADD_ORIGINAL(18, WinHttpGetDefaultProxyConfiguration);
+ADD_ORIGINAL(19, WinHttpGetIEProxyConfigForCurrentUser);
+ADD_ORIGINAL(20, WinHttpGetProxyForUrl);
+ADD_ORIGINAL(21, WinHttpGetProxyForUrlEx);
+ADD_ORIGINAL(22, WinHttpGetProxyResult);
+ADD_ORIGINAL(23, WinHttpGetTunnelSocket);
+ADD_ORIGINAL(24, WinHttpOpen);
+ADD_ORIGINAL(25, WinHttpOpenRequest);
+ADD_ORIGINAL(26, WinHttpProbeConnectivity);
+ADD_ORIGINAL(27, WinHttpQueryAuthSchemes);
+ADD_ORIGINAL(28, WinHttpQueryDataAvailable);
+ADD_ORIGINAL(29, WinHttpQueryHeaders);
+ADD_ORIGINAL(30, WinHttpQueryOption);
+ADD_ORIGINAL(31, WinHttpReadData);
+ADD_ORIGINAL(32, WinHttpReceiveResponse);
+ADD_ORIGINAL(33, WinHttpResetAutoProxy);
+ADD_ORIGINAL(34, WinHttpSaveProxyCredentials);
+ADD_ORIGINAL(35, WinHttpSendRequest);
+ADD_ORIGINAL(36, WinHttpSetCredentials);
+ADD_ORIGINAL(37, WinHttpSetDefaultProxyConfiguration);
+ADD_ORIGINAL(38, WinHttpSetOption);
+ADD_ORIGINAL(39, WinHttpSetStatusCallback);
+ADD_ORIGINAL(40, WinHttpSetTimeouts);
+ADD_ORIGINAL(41, WinHttpTimeFromSystemTime);
+ADD_ORIGINAL(42, WinHttpTimeToSystemTime);
+ADD_ORIGINAL(43, WinHttpWebSocketClose);
+ADD_ORIGINAL(44, WinHttpWebSocketCompleteUpgrade);
+ADD_ORIGINAL(45, WinHttpWebSocketQueryCloseStatus);
+ADD_ORIGINAL(46, WinHttpWebSocketReceive);
+ADD_ORIGINAL(47, WinHttpWebSocketSend);
+ADD_ORIGINAL(48, WinHttpWebSocketShutdown);
+ADD_ORIGINAL(49, WinHttpWriteData);
+
+}
+
+PROXY(0, WinHttpAddRequestHeaders);
+PROXY(1, WinHttpAutoProxySvcMain);
+PROXY(2, WinHttpCheckPlatform);
+PROXY(3, WinHttpCloseHandle);
+PROXY(4, WinHttpConnect);
+PROXY(5, WinHttpConnectionDeleteProxyInfo);
+PROXY(6, WinHttpConnectionFreeNameList);
+PROXY(7, WinHttpConnectionFreeProxyInfo);
+PROXY(8, WinHttpConnectionFreeProxyList);
+PROXY(9, WinHttpConnectionGetNameList);
+PROXY(10, WinHttpConnectionGetProxyInfo);
+PROXY(11, WinHttpConnectionGetProxyList);
+PROXY(12, WinHttpConnectionSetProxyInfo);
+PROXY(13, WinHttpCrackUrl);
+PROXY(14, WinHttpCreateProxyResolver);
+PROXY(15, WinHttpCreateUrl);
+PROXY(16, WinHttpDetectAutoProxyConfigUrl);
+PROXY(17, WinHttpFreeProxyResult);
+PROXY(18, WinHttpGetDefaultProxyConfiguration);
+PROXY(19, WinHttpGetIEProxyConfigForCurrentUser);
+PROXY(20, WinHttpGetProxyForUrl);
+PROXY(21, WinHttpGetProxyForUrlEx);
+PROXY(22, WinHttpGetProxyResult);
+PROXY(23, WinHttpGetTunnelSocket);
+PROXY(24, WinHttpOpen);
+PROXY(25, WinHttpOpenRequest);
+PROXY(26, WinHttpProbeConnectivity);
+PROXY(27, WinHttpQueryAuthSchemes);
+PROXY(28, WinHttpQueryDataAvailable);
+PROXY(29, WinHttpQueryHeaders);
+PROXY(30, WinHttpQueryOption);
+PROXY(31, WinHttpReadData);
+PROXY(32, WinHttpReceiveResponse);
+PROXY(33, WinHttpResetAutoProxy);
+PROXY(34, WinHttpSaveProxyCredentials);
+PROXY(35, WinHttpSendRequest);
+PROXY(36, WinHttpSetCredentials);
+PROXY(37, WinHttpSetDefaultProxyConfiguration);
+PROXY(38, WinHttpSetOption);
+PROXY(39, WinHttpSetStatusCallback);
+PROXY(40, WinHttpSetTimeouts);
+PROXY(41, WinHttpTimeFromSystemTime);
+PROXY(42, WinHttpTimeToSystemTime);
+PROXY(43, WinHttpWebSocketClose);
+PROXY(44, WinHttpWebSocketCompleteUpgrade);
+PROXY(45, WinHttpWebSocketQueryCloseStatus);
+PROXY(46, WinHttpWebSocketReceive);
+PROXY(47, WinHttpWebSocketSend);
+PROXY(48, WinHttpWebSocketShutdown);
+PROXY(49, WinHttpWriteData);
diff --git a/Doorstop/Proxy/proxy.def b/Doorstop/Proxy/proxy.def
new file mode 100644
index 00000000..2b5dcf3a
--- /dev/null
+++ b/Doorstop/Proxy/proxy.def
@@ -0,0 +1,54 @@
+LIBRARY proxy.dll
+EXPORTS
+ WinHttpAddRequestHeaders @1
+ WinHttpAutoProxySvcMain @2
+ WinHttpCheckPlatform @3
+ WinHttpCloseHandle @4
+ WinHttpConnect @5
+ WinHttpConnectionDeleteProxyInfo @6
+ WinHttpConnectionFreeNameList @7
+ WinHttpConnectionFreeProxyInfo @8
+ WinHttpConnectionFreeProxyList @9
+ WinHttpConnectionGetNameList @10
+ WinHttpConnectionGetProxyInfo @11
+ WinHttpConnectionGetProxyList @12
+ WinHttpConnectionSetProxyInfo @13
+ WinHttpCrackUrl @14
+ WinHttpCreateProxyResolver @15
+ WinHttpCreateUrl @16
+ WinHttpDetectAutoProxyConfigUrl @17
+ WinHttpFreeProxyResult @18
+ WinHttpGetDefaultProxyConfiguration @19
+ WinHttpGetIEProxyConfigForCurrentUser @20
+ WinHttpGetProxyForUrl @21
+ WinHttpGetProxyForUrlEx @22
+ WinHttpGetProxyResult @23
+ WinHttpGetTunnelSocket @24
+ WinHttpOpen @25
+ WinHttpOpenRequest @26
+ WinHttpProbeConnectivity @27
+ WinHttpQueryAuthSchemes @28
+ WinHttpQueryDataAvailable @29
+ WinHttpQueryHeaders @30
+ WinHttpQueryOption @31
+ WinHttpReadData @32
+ WinHttpReceiveResponse @33
+ WinHttpResetAutoProxy @34
+ WinHttpSaveProxyCredentials @35
+ WinHttpSendRequest @36
+ WinHttpSetCredentials @37
+ WinHttpSetDefaultProxyConfiguration @38
+ WinHttpSetOption @39
+ WinHttpSetStatusCallback @40
+ WinHttpSetTimeouts @41
+ WinHttpTimeFromSystemTime @42
+ WinHttpTimeToSystemTime @43
+ WinHttpWebSocketClose @44
+ WinHttpWebSocketCompleteUpgrade @45
+ WinHttpWebSocketQueryCloseStatus @46
+ WinHttpWebSocketReceive @47
+ WinHttpWebSocketSend @48
+ WinHttpWebSocketShutdown @49
+ WinHttpWriteData @50
+ SetGetMessageHook @51
+ SetPeekMessageHook @52
diff --git a/Doorstop/Proxy/proxy.h b/Doorstop/Proxy/proxy.h
new file mode 100644
index 00000000..06542e7a
--- /dev/null
+++ b/Doorstop/Proxy/proxy.h
@@ -0,0 +1,74 @@
+/*
+ * Proxy.h -- Definitions for proxy-related functionality
+ *
+ * The proxy works roughly as follows:
+ * - We define our exports in proxy.c (computer generated)
+ * - loadProxy initializes the proxy:
+ * 1. Look up the name of this DLL
+ * 2. Find the original DLL with the same name
+ * 3. Load the original DLL
+ * 4. Load all functions into originalFunctions array
+ *
+ * For more information, refer to proxy.c
+ */
+
+#pragma once
+
+#pragma warning( disable : 4267 6387 6386 )
+
+#include
+#include
+#include "assert_util.h"
+#include
+
+#define ALT_POSTFIX L"_alt.dll"
+#define DLL_POSTFIX L".dll"
+
+extern FARPROC originalFunctions[];
+extern void loadFunctions(HMODULE dll);
+
+// Load the proxy functions into memory
+inline void loadProxy(wchar_t *moduleName)
+{
+ size_t module_name_len = wcslen(moduleName);
+
+ size_t alt_name_len = module_name_len + STR_LEN(ALT_POSTFIX);
+ wchar_t *alt_name = memalloc(sizeof(wchar_t) * alt_name_len);
+ wmemcpy(alt_name, moduleName, module_name_len);
+ wmemcpy(alt_name + module_name_len, ALT_POSTFIX, STR_LEN(ALT_POSTFIX));
+
+ wchar_t *dll_path = NULL; // The final DLL path
+
+ const int alt_full_path_len = GetFullPathNameW(alt_name, 0, NULL, NULL);
+ wchar_t *alt_full_path = memalloc(sizeof(wchar_t) * alt_full_path_len);
+ GetFullPathNameW(alt_name, alt_full_path_len, alt_full_path, NULL);
+ memfree(alt_name);
+
+ LOG("Looking for original DLL from %S\n", alt_full_path);
+
+ // Try to look for the alternative first in the same directory.
+ HMODULE handle = LoadLibrary(alt_full_path);
+
+ if (handle == NULL)
+ {
+ size_t system_dir_len = GetSystemDirectoryW(NULL, 0);
+ dll_path = memalloc(sizeof(wchar_t) * (system_dir_len + module_name_len + STR_LEN(DLL_POSTFIX)));
+ _ASSERTE(dll_path != nullptr);
+ GetSystemDirectoryW(dll_path, system_dir_len);
+ dll_path[system_dir_len - 1] = L'\\';
+ wmemcpy(dll_path + system_dir_len, moduleName, module_name_len);
+ wmemcpy(dll_path + system_dir_len + module_name_len, DLL_POSTFIX, STR_LEN(DLL_POSTFIX));
+
+ LOG("Looking for original DLL from %S\n", dll_path);
+
+ handle = LoadLibraryW(dll_path);
+ }
+
+ ASSERT_F(handle != NULL, L"Unable to load the original %s.dll (looked from system directory and from %s_alt.dll)!",
+ moduleName, moduleName);
+
+ memfree(alt_full_path);
+ memfree(dll_path);
+
+ loadFunctions(handle);
+}
diff --git a/Doorstop/Proxy/proxy.rc b/Doorstop/Proxy/proxy.rc
new file mode 100644
index 00000000..a6245589
Binary files /dev/null and b/Doorstop/Proxy/proxy.rc differ
diff --git a/Doorstop/Proxy/proxydefs.txt b/Doorstop/Proxy/proxydefs.txt
new file mode 100644
index 00000000..c8423005
--- /dev/null
+++ b/Doorstop/Proxy/proxydefs.txt
@@ -0,0 +1,63 @@
+SvchostPushServiceGlobals
+WinHttpAddRequestHeaders
+WinHttpAutoProxySvcMain
+WinHttpCheckPlatform
+WinHttpCloseHandle
+WinHttpConnect
+WinHttpConnectionDeletePolicyEntries
+WinHttpConnectionDeleteProxyInfo
+WinHttpConnectionFreeNameList
+WinHttpConnectionFreeProxyInfo
+WinHttpConnectionFreeProxyList
+WinHttpConnectionGetNameList
+WinHttpConnectionGetProxyInfo
+WinHttpConnectionGetProxyList
+WinHttpConnectionSetPolicyEntries
+WinHttpConnectionSetProxyInfo
+WinHttpConnectionUpdateIfIndexTable
+WinHttpCrackUrl
+WinHttpCreateProxyResolver
+WinHttpCreateUrl
+WinHttpDetectAutoProxyConfigUrl
+WinHttpFreeProxyResult
+WinHttpFreeProxyResultEx
+WinHttpFreeProxySettings
+WinHttpGetDefaultProxyConfiguration
+WinHttpGetIEProxyConfigForCurrentUser
+WinHttpGetProxyForUrl
+WinHttpGetProxyForUrlEx
+WinHttpGetProxyForUrlEx2
+WinHttpGetProxyForUrlHvsi
+WinHttpGetProxyResult
+WinHttpGetProxyResultEx
+WinHttpGetProxySettingsVersion
+WinHttpGetTunnelSocket
+WinHttpOpen
+WinHttpOpenRequest
+WinHttpProbeConnectivity
+WinHttpQueryAuthSchemes
+WinHttpQueryDataAvailable
+WinHttpQueryHeaders
+WinHttpQueryOption
+WinHttpReadData
+WinHttpReadProxySettings
+WinHttpReadProxySettingsHvsi
+WinHttpReceiveResponse
+WinHttpResetAutoProxy
+WinHttpSaveProxyCredentials
+WinHttpSendRequest
+WinHttpSetCredentials
+WinHttpSetDefaultProxyConfiguration
+WinHttpSetOption
+WinHttpSetStatusCallback
+WinHttpSetTimeouts
+WinHttpTimeFromSystemTime
+WinHttpTimeToSystemTime
+WinHttpWebSocketClose
+WinHttpWebSocketCompleteUpgrade
+WinHttpWebSocketQueryCloseStatus
+WinHttpWebSocketReceive
+WinHttpWebSocketSend
+WinHttpWebSocketShutdown
+WinHttpWriteData
+WinHttpWriteProxySettings
\ No newline at end of file
diff --git a/Doorstop/Proxy/winapi_util.h b/Doorstop/Proxy/winapi_util.h
new file mode 100644
index 00000000..599342d5
--- /dev/null
+++ b/Doorstop/Proxy/winapi_util.h
@@ -0,0 +1,65 @@
+#pragma once
+
+#pragma warning( disable : 4267 6387 )
+
+#include
+#include "crt.h"
+
+inline size_t get_module_path(HMODULE module, wchar_t **result, size_t *size, size_t free_space)
+{
+ size_t i = 0;
+ size_t len, s;
+ *result = NULL;
+ do
+ {
+ if (*result != NULL)
+ memfree(*result);
+ i++;
+ s = i * MAX_PATH + 1;
+ *result = memalloc(sizeof(wchar_t) * s);
+ len = GetModuleFileNameW(module, *result, s);
+ }
+ while (GetLastError() == ERROR_INSUFFICIENT_BUFFER && s - len >= free_space);
+
+ if (size != NULL)
+ *size = s;
+ return len;
+}
+
+inline wchar_t *get_ini_entry(const wchar_t *config_file, const wchar_t *section, const wchar_t *key,
+ const wchar_t *default_val)
+{
+ size_t i = 0;
+ size_t size, read;
+ wchar_t *result = NULL;
+ do
+ {
+ if (result != NULL)
+ memfree(result);
+ i++;
+ size = i * MAX_PATH + 1;
+ result = memalloc(sizeof(wchar_t) * size);
+ read = GetPrivateProfileStringW(section, key, default_val, result, size, config_file);
+ }
+ while (read == size - 1);
+ return result;
+}
+
+inline wchar_t *get_file_name_no_ext(wchar_t *str, size_t len)
+{
+ size_t ext_index = len;
+ size_t i;
+ for (i = len; i > 0; i--)
+ {
+ wchar_t c = *(str + i);
+ if (c == L'.' && ext_index == len)
+ ext_index = i;
+ else if (c == L'\\')
+ break;
+ }
+
+ size_t result_len = ext_index - i;
+ wchar_t *result = memcalloc(sizeof(wchar_t) * result_len);
+ wmemcpy(result, str + i + 1, result_len - 1);
+ return result;
+}
diff --git a/Doorstop/README.md b/Doorstop/README.md
new file mode 100644
index 00000000..f741936c
--- /dev/null
+++ b/Doorstop/README.md
@@ -0,0 +1,47 @@
+
+
+
+
+![Github All Releases](https://img.shields.io/github/downloads/NeighTools/UnityDoorstop/total.svg)
+![GitHub release](https://img.shields.io/github/release/NeighTools/UnityDoorstop.svg)
+![license](https://img.shields.io/github/license/NeighTools/UnityDoorstop.svg)
+
+*Run managed code before Unity does!*
+
+# Unity Doorstop
+
+Doorstop is a tool to execute managed assemblies inside Unity as early as possible!
+
+This repository is indented mainly for developers!
+Developers should package Doorstop into their applications for the end-users.
+
+## Features
+
+* **Runs first**: Doorstop runs its own code before Unity has an ability to do so.
+* **Configurable**: A very basic configuration file allows to specify your own assembly to execute!
+* **Public domain!** You are fully free to embed Doorstop into your application!
+
+## Guides for users and developers on [the wiki](https://github.com/NeighTools/UnityDoorstop/wiki)
+
+## Building
+
+To build, you will need:
+
+* Visual Studio 2017 (2015 might work as well with some modifications)
+* Visual C++ Toolset v140
+* .NET Framework 3.5 or newer (only for the example, not used by the proxy)
+* Python (only to generate proxy functions; not needed to build)
+
+Clone, open in Visual Studio, select the platform (x86/x64) and build.
+
+#### Custom proxy functions
+
+Doorstop's proxy is flexible and allows to be load as different DLLs.
+You can modify which functions you want to proxy by adding/removing function names in `Proxy/proxydefs.txt` and running `proxygen/proxy_gen.py ../Proxy/proxydefs.txt` to generate an appropriate proxy functions.
+
+The current set up allows to use the proxy for the following DLLs:
+
+* `winhttp.dll` (All exports)
+* `iphlpapi.dll` (Only `GetIpAddrTable`)
+
+(WIP: Currently all build results are placed in separate folders; will be changed later)
diff --git a/Doorstop/UnityDoorstop.sln b/Doorstop/UnityDoorstop.sln
new file mode 100644
index 00000000..0ba1ff64
--- /dev/null
+++ b/Doorstop/UnityDoorstop.sln
@@ -0,0 +1,77 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27428.2043
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "proxy", "Proxy\Proxy.vcxproj", "{88609E16-731F-46C9-8139-6B1A7A83240D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DoorstopTest", "DoorstopTest\DoorstopTest.csproj", "{62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ Verbose_Release|Any CPU = Verbose_Release|Any CPU
+ Verbose_Release|x64 = Verbose_Release|x64
+ Verbose_Release|x86 = Verbose_Release|x86
+ Verbose|Any CPU = Verbose|Any CPU
+ Verbose|x64 = Verbose|x64
+ Verbose|x86 = Verbose|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {88609E16-731F-46C9-8139-6B1A7A83240D}.Debug|Any CPU.ActiveCfg = Debug|Win32
+ {88609E16-731F-46C9-8139-6B1A7A83240D}.Debug|x64.ActiveCfg = Debug|x64
+ {88609E16-731F-46C9-8139-6B1A7A83240D}.Debug|x64.Build.0 = Debug|x64
+ {88609E16-731F-46C9-8139-6B1A7A83240D}.Debug|x86.ActiveCfg = Debug|Win32
+ {88609E16-731F-46C9-8139-6B1A7A83240D}.Debug|x86.Build.0 = Debug|Win32
+ {88609E16-731F-46C9-8139-6B1A7A83240D}.Release|Any CPU.ActiveCfg = Release|Win32
+ {88609E16-731F-46C9-8139-6B1A7A83240D}.Release|x64.ActiveCfg = Release|x64
+ {88609E16-731F-46C9-8139-6B1A7A83240D}.Release|x64.Build.0 = Release|x64
+ {88609E16-731F-46C9-8139-6B1A7A83240D}.Release|x86.ActiveCfg = Release|Win32
+ {88609E16-731F-46C9-8139-6B1A7A83240D}.Release|x86.Build.0 = Release|Win32
+ {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose_Release|Any CPU.ActiveCfg = Verbose_Release|Win32
+ {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose_Release|x64.ActiveCfg = Verbose_Release|x64
+ {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose_Release|x64.Build.0 = Verbose_Release|x64
+ {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose_Release|x86.ActiveCfg = Verbose_Release|Win32
+ {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose_Release|x86.Build.0 = Verbose_Release|Win32
+ {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose|Any CPU.ActiveCfg = Verbose|Win32
+ {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose|x64.ActiveCfg = Verbose|x64
+ {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose|x64.Build.0 = Verbose|x64
+ {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose|x86.ActiveCfg = Verbose|Win32
+ {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose|x86.Build.0 = Verbose|Win32
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Debug|x64.Build.0 = Debug|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Debug|x86.Build.0 = Debug|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Release|x64.ActiveCfg = Release|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Release|x64.Build.0 = Release|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Release|x86.ActiveCfg = Release|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Release|x86.Build.0 = Release|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose_Release|Any CPU.ActiveCfg = Verbose_Release|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose_Release|Any CPU.Build.0 = Verbose_Release|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose_Release|x64.ActiveCfg = Verbose_Release|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose_Release|x64.Build.0 = Verbose_Release|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose_Release|x86.ActiveCfg = Verbose_Release|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose_Release|x86.Build.0 = Verbose_Release|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose|Any CPU.ActiveCfg = Verbose|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose|Any CPU.Build.0 = Verbose|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose|x64.ActiveCfg = Verbose|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose|x64.Build.0 = Verbose|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose|x86.ActiveCfg = Verbose|Any CPU
+ {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose|x86.Build.0 = Verbose|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {4EF47C82-18DC-4E9E-A878-8C003FFB303D}
+ EndGlobalSection
+EndGlobal
diff --git a/Doorstop/docs/logo.png b/Doorstop/docs/logo.png
new file mode 100644
index 00000000..00b5565f
Binary files /dev/null and b/Doorstop/docs/logo.png differ
diff --git a/Doorstop/docs/logo_sm.png b/Doorstop/docs/logo_sm.png
new file mode 100644
index 00000000..7774e6c1
Binary files /dev/null and b/Doorstop/docs/logo_sm.png differ
diff --git a/Doorstop/proxygen/proxy_gen.py b/Doorstop/proxygen/proxy_gen.py
new file mode 100644
index 00000000..92e4f218
--- /dev/null
+++ b/Doorstop/proxygen/proxy_gen.py
@@ -0,0 +1,52 @@
+import sys
+import os
+#import pefile
+import io
+import string
+
+def main():
+ path = os.path.dirname(os.path.realpath(sys.argv[0]))
+ file = sys.argv[1]
+
+ proxies = io.StringIO()
+ proxy_add = io.StringIO()
+ proxy_def = io.StringIO()
+ proxy_def_file = io.StringIO()
+
+ count = 0
+
+ """ for dll in dlls:
+ pe = pefile.PE(dll, fast_load=True)
+ pe.parse_data_directories()
+ for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:
+ name = exp.name.decode()
+
+ proxies.write("%s\n" % name)
+ proxy_add.write("ADD_ORIGINAL(%d, %s);\n" % (count, name))
+ proxy_def.write("PROXY(%d, %s);\n" % (count, name))
+ count = count + 1 """
+
+ with open(file, "r") as includes:
+ names = includes.readlines()
+ for name in names:
+ name = name.strip()
+ proxies.write(f"{name}\n")
+ proxy_add.write(f"ADD_ORIGINAL({count}, {name});\n")
+ proxy_def.write(f"PROXY({count}, {name});\n")
+ proxy_def_file.write(f" {name} @{count+1}\n")
+ count = count + 1
+
+ with open(f"{path}\\templates\\proxy_template.c", "r") as proxy_file:
+ new_proxy = string.Template(proxy_file.read()).safe_substitute(proxy_count=count, proxy_add=proxy_add.getvalue(), proxy_def=proxy_def.getvalue())
+
+ with open(f"{path}\\..\\Proxy\\proxy.c", "w") as proxy_c_file:
+ proxy_c_file.write(new_proxy)
+
+ with open(f"{path}\\templates\\proxy_template.def", "r") as proxy_file:
+ new_proxy_def = string.Template(proxy_file.read()).safe_substitute(proxy_exports=proxy_def_file.getvalue())
+
+ with open(f"{path}\\..\\Proxy\\proxy.def", "w") as proxy_def_file:
+ proxy_def_file.write(new_proxy_def)
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/Doorstop/proxygen/templates/proxy_template.c b/Doorstop/proxygen/templates/proxy_template.c
new file mode 100644
index 00000000..84788fed
--- /dev/null
+++ b/Doorstop/proxygen/templates/proxy_template.c
@@ -0,0 +1,34 @@
+/* ==================================
+ * COMPUTER GENERATED -- DO NOT EDIT
+ * ==================================
+ *
+ * This file contains the definitions for all proxy functions this DLL supports.
+ *
+ * The proxies are very simple functions that should be optimizied into a
+ * single JMP instruction without editing the stack at all.
+ *
+ * NOTE: While this works, this is a somewhat hackish approach that is based on how
+ * the compiler optimizes the code. That said, the proxy will not work on Debug build currently
+ * (that can be fixed by changing the appropriate compile flag that I am yet to locate).
+ */
+
+#pragma warning( disable : 4244 )
+
+#include
+
+#define ADD_ORIGINAL(i, name) originalFunctions[i] = GetProcAddress(dll, #name)
+
+#define PROXY(i, name) \
+ __declspec(dllexport) ULONG __stdcall name() \
+ { \
+ return originalFunctions[i](); \
+ }
+
+FARPROC originalFunctions[${proxy_count}] = {0};
+
+void loadFunctions(HMODULE dll)
+{
+${proxy_add}
+}
+
+${proxy_def}
\ No newline at end of file
diff --git a/Doorstop/proxygen/templates/proxy_template.def b/Doorstop/proxygen/templates/proxy_template.def
new file mode 100644
index 00000000..564c35d5
--- /dev/null
+++ b/Doorstop/proxygen/templates/proxy_template.def
@@ -0,0 +1,3 @@
+LIBRARY proxy.dll
+EXPORTS
+${proxy_exports}
\ No newline at end of file
diff --git a/Doorstop/proxygen/winhttp.txt b/Doorstop/proxygen/winhttp.txt
new file mode 100644
index 00000000..7ac96698
--- /dev/null
+++ b/Doorstop/proxygen/winhttp.txt
@@ -0,0 +1,50 @@
+WinHttpAddRequestHeaders
+WinHttpAutoProxySvcMain
+WinHttpCheckPlatform
+WinHttpCloseHandle
+WinHttpConnect
+WinHttpConnectionDeleteProxyInfo
+WinHttpConnectionFreeNameList
+WinHttpConnectionFreeProxyInfo
+WinHttpConnectionFreeProxyList
+WinHttpConnectionGetNameList
+WinHttpConnectionGetProxyInfo
+WinHttpConnectionGetProxyList
+WinHttpConnectionSetProxyInfo
+WinHttpCrackUrl
+WinHttpCreateProxyResolver
+WinHttpCreateUrl
+WinHttpDetectAutoProxyConfigUrl
+WinHttpFreeProxyResult
+WinHttpGetDefaultProxyConfiguration
+WinHttpGetIEProxyConfigForCurrentUser
+WinHttpGetProxyForUrl
+WinHttpGetProxyForUrlEx
+WinHttpGetProxyResult
+WinHttpGetTunnelSocket
+WinHttpOpen
+WinHttpOpenRequest
+WinHttpProbeConnectivity
+WinHttpQueryAuthSchemes
+WinHttpQueryDataAvailable
+WinHttpQueryHeaders
+WinHttpQueryOption
+WinHttpReadData
+WinHttpReceiveResponse
+WinHttpResetAutoProxy
+WinHttpSaveProxyCredentials
+WinHttpSendRequest
+WinHttpSetCredentials
+WinHttpSetDefaultProxyConfiguration
+WinHttpSetOption
+WinHttpSetStatusCallback
+WinHttpSetTimeouts
+WinHttpTimeFromSystemTime
+WinHttpTimeToSystemTime
+WinHttpWebSocketClose
+WinHttpWebSocketCompleteUpgrade
+WinHttpWebSocketQueryCloseStatus
+WinHttpWebSocketReceive
+WinHttpWebSocketSend
+WinHttpWebSocketShutdown
+WinHttpWriteData
\ No newline at end of file