From 5ad7e46eae41cda1cee97b1ddbc87e22264778be Mon Sep 17 00:00:00 2001 From: Anairkoen Schno Date: Sun, 1 Dec 2019 00:22:45 -0600 Subject: [PATCH] Hoisted Doorstop files the rest of the way out of the submodule This also adds some error tracking stuff to Doorstop --- Doorstop | 1 - Doorstop/.gitignore | 325 ++++++++++++ Doorstop/DoorstopTest/DoorstopTest.csproj | 58 +++ Doorstop/DoorstopTest/Loader.cs | 24 + .../DoorstopTest/Properties/AssemblyInfo.cs | 36 ++ Doorstop/LICENSE | 116 +++++ Doorstop/Proxy/Proxy.vcxproj | 190 +++++++ Doorstop/Proxy/Proxy.vcxproj.filters | 61 +++ Doorstop/Proxy/assert_util.h | 63 +++ Doorstop/Proxy/config.h | 137 ++++++ Doorstop/Proxy/crt.h | 65 +++ Doorstop/Proxy/dll.def | 64 +++ Doorstop/Proxy/hook.h | 162 ++++++ Doorstop/Proxy/main.c | 462 ++++++++++++++++++ Doorstop/Proxy/mono.h | 105 ++++ Doorstop/Proxy/proxy.c | 133 +++++ Doorstop/Proxy/proxy.def | 54 ++ Doorstop/Proxy/proxy.h | 74 +++ Doorstop/Proxy/proxy.rc | Bin 0 -> 2014 bytes Doorstop/Proxy/proxydefs.txt | 63 +++ Doorstop/Proxy/winapi_util.h | 65 +++ Doorstop/README.md | 47 ++ Doorstop/UnityDoorstop.sln | 77 +++ Doorstop/docs/logo.png | Bin 0 -> 86433 bytes Doorstop/docs/logo_sm.png | Bin 0 -> 14575 bytes Doorstop/proxygen/proxy_gen.py | 52 ++ Doorstop/proxygen/templates/proxy_template.c | 34 ++ .../proxygen/templates/proxy_template.def | 3 + Doorstop/proxygen/winhttp.txt | 50 ++ 29 files changed, 2520 insertions(+), 1 deletion(-) delete mode 160000 Doorstop create mode 100644 Doorstop/.gitignore create mode 100644 Doorstop/DoorstopTest/DoorstopTest.csproj create mode 100644 Doorstop/DoorstopTest/Loader.cs create mode 100644 Doorstop/DoorstopTest/Properties/AssemblyInfo.cs create mode 100644 Doorstop/LICENSE create mode 100644 Doorstop/Proxy/Proxy.vcxproj create mode 100644 Doorstop/Proxy/Proxy.vcxproj.filters create mode 100644 Doorstop/Proxy/assert_util.h create mode 100644 Doorstop/Proxy/config.h create mode 100644 Doorstop/Proxy/crt.h create mode 100644 Doorstop/Proxy/dll.def create mode 100644 Doorstop/Proxy/hook.h create mode 100644 Doorstop/Proxy/main.c create mode 100644 Doorstop/Proxy/mono.h create mode 100644 Doorstop/Proxy/proxy.c create mode 100644 Doorstop/Proxy/proxy.def create mode 100644 Doorstop/Proxy/proxy.h create mode 100644 Doorstop/Proxy/proxy.rc create mode 100644 Doorstop/Proxy/proxydefs.txt create mode 100644 Doorstop/Proxy/winapi_util.h create mode 100644 Doorstop/README.md create mode 100644 Doorstop/UnityDoorstop.sln create mode 100644 Doorstop/docs/logo.png create mode 100644 Doorstop/docs/logo_sm.png create mode 100644 Doorstop/proxygen/proxy_gen.py create mode 100644 Doorstop/proxygen/templates/proxy_template.c create mode 100644 Doorstop/proxygen/templates/proxy_template.def create mode 100644 Doorstop/proxygen/winhttp.txt 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 0000000000000000000000000000000000000000..a6245589bf905ee8767610f10f6691ab4143c3b7 GIT binary patch literal 2014 zcmd6oTW{Jx5QWckrTzztJ~j%MM#^)*5TP1^VnFH>B7`KC(txpR6~svtE($bbgeep7`d-R-_WG| z_S8g^AbS+^pTTbH@FG?8!(nh!jK ztzd6cWKqi=SMc-LXK=LVZ10m-YsmZ#VCd}{FTSTRZ&IV8Rfycg@6vV0$BjUVuuiCL z%T=ZVgN-|JPn^&W93RQ6OXu(Czdr@u%lr)Axo=4 z1>OsN=YE*NamU2U1B=%E5JIK{ijpN0@+MC^>Z?U)^l zV&`SUzJx;z*MxnEcEQ~3j*4GCg&}T(JMo-VXT4U_!+8xHn_F(Y8BCv7?e%>ArFP{Q z$7j%nU%WplKAV@C@(hoeg4KUbZ+XUspxJz_nIHSNDt6!Q5 +#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 0000000000000000000000000000000000000000..00b5565f405202dbe91f7f13b62787bd5fac2fdd GIT binary patch literal 86433 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 ZTTI@Oti9rZ$$Q~N4^Z}(eC_$o{{tqyMZ^FA literal 0 HcmV?d00001 diff --git a/Doorstop/docs/logo_sm.png b/Doorstop/docs/logo_sm.png new file mode 100644 index 0000000000000000000000000000000000000000..7774e6c1057ce168d1db9528599c31e46a563364 GIT binary patch literal 14575 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 beraQh%3mTg&gBElBi@q-D@zwiLVf-p$WouR literal 0 HcmV?d00001 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