From 5c6f9bb964d1438e6163a567a49ee21df6272951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Boh=C3=A1=C4=8Dek?= Date: Fri, 14 Jan 2022 20:08:18 +0100 Subject: [PATCH] feat: add .net 5+ injector --- .../NosSmooth.Inject/NosSmooth.Inject.vcxproj | 174 ++++++++++ .../NosSmooth.Inject.vcxproj.filters | 39 +++ Local/NosSmooth.Inject/coreclr_delegates.h | 47 +++ Local/NosSmooth.Inject/dllmain.cpp | 19 ++ Local/NosSmooth.Inject/framework.h | 5 + Local/NosSmooth.Inject/hostfxr.h | 323 ++++++++++++++++++ Local/NosSmooth.Inject/nethost.h | 94 +++++ Local/NosSmooth.Inject/nossmooth.cpp | 125 +++++++ Local/NosSmooth.Inject/nossmooth.h | 15 + Local/NosSmooth.Inject/pch.cpp | 5 + Local/NosSmooth.Inject/pch.h | 13 + .../Commands/InjectCommand.cs | 56 +++ .../Commands/ListProcessesCommand.cs | 41 +++ .../NosSmooth.Injector.CLI.csproj | 28 ++ Local/NosSmooth.Injector.CLI/Program.cs | 75 ++++ Local/NosSmooth.Injector.CLI/app.manifest | 11 + .../Errors/InjectionFailedError.cs | 15 + .../Errors/InsufficientPermissionsError.cs | 20 ++ .../Errors/ProcessNotFoundError.cs | 16 + Local/NosSmooth.Injector/LoadParams.cs | 44 +++ .../ManagedMemoryAllocation.cs | 47 +++ Local/NosSmooth.Injector/NosInjector.cs | 155 +++++++++ .../NosSmooth.Injector/NosInjectorOptions.cs | 32 ++ .../NosSmooth.Injector.csproj | 15 + .../NosSmooth.LocalCore.vcxproj | 0 NosSmooth.sln | 87 ++--- 26 files changed, 1458 insertions(+), 43 deletions(-) create mode 100644 Local/NosSmooth.Inject/NosSmooth.Inject.vcxproj create mode 100644 Local/NosSmooth.Inject/NosSmooth.Inject.vcxproj.filters create mode 100644 Local/NosSmooth.Inject/coreclr_delegates.h create mode 100644 Local/NosSmooth.Inject/dllmain.cpp create mode 100644 Local/NosSmooth.Inject/framework.h create mode 100644 Local/NosSmooth.Inject/hostfxr.h create mode 100644 Local/NosSmooth.Inject/nethost.h create mode 100644 Local/NosSmooth.Inject/nossmooth.cpp create mode 100644 Local/NosSmooth.Inject/nossmooth.h create mode 100644 Local/NosSmooth.Inject/pch.cpp create mode 100644 Local/NosSmooth.Inject/pch.h create mode 100644 Local/NosSmooth.Injector.CLI/Commands/InjectCommand.cs create mode 100644 Local/NosSmooth.Injector.CLI/Commands/ListProcessesCommand.cs create mode 100644 Local/NosSmooth.Injector.CLI/NosSmooth.Injector.CLI.csproj create mode 100644 Local/NosSmooth.Injector.CLI/Program.cs create mode 100644 Local/NosSmooth.Injector.CLI/app.manifest create mode 100644 Local/NosSmooth.Injector/Errors/InjectionFailedError.cs create mode 100644 Local/NosSmooth.Injector/Errors/InsufficientPermissionsError.cs create mode 100644 Local/NosSmooth.Injector/Errors/ProcessNotFoundError.cs create mode 100644 Local/NosSmooth.Injector/LoadParams.cs create mode 100644 Local/NosSmooth.Injector/ManagedMemoryAllocation.cs create mode 100644 Local/NosSmooth.Injector/NosInjector.cs create mode 100644 Local/NosSmooth.Injector/NosInjectorOptions.cs create mode 100644 Local/NosSmooth.Injector/NosSmooth.Injector.csproj create mode 100644 Local/NosSmooth.LocalCore/NosSmooth.LocalCore.vcxproj diff --git a/Local/NosSmooth.Inject/NosSmooth.Inject.vcxproj b/Local/NosSmooth.Inject/NosSmooth.Inject.vcxproj new file mode 100644 index 0000000..0093b71 --- /dev/null +++ b/Local/NosSmooth.Inject/NosSmooth.Inject.vcxproj @@ -0,0 +1,174 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {ca2873d8-bd0b-4583-818d-b94a3c2abba3} + NosSmoothInject + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;NOSSMOOTHINJECT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + + + Windows + true + false + %(AdditionalLibraryDirectories) + nethost.lib;%(AdditionalDependencies) + + + + + Level3 + true + true + true + WIN32;NDEBUG;NOSSMOOTHINJECT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + + + Windows + true + true + true + false + %(AdditionalLibraryDirectories) + nethost.lib;%(AdditionalDependencies) + + + + + Level3 + true + _DEBUG;NOSSMOOTHINJECT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + + + Windows + true + false + %(AdditionalLibraryDirectories) + nethost.lib;%(AdditionalDependencies) + + + + + Level3 + true + true + true + NDEBUG;NOSSMOOTHINJECT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + + + Windows + true + true + true + false + %(AdditionalLibraryDirectories) + nethost.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Local/NosSmooth.Inject/NosSmooth.Inject.vcxproj.filters b/Local/NosSmooth.Inject/NosSmooth.Inject.vcxproj.filters new file mode 100644 index 0000000..809672e --- /dev/null +++ b/Local/NosSmooth.Inject/NosSmooth.Inject.vcxproj.filters @@ -0,0 +1,39 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;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 + + + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/Local/NosSmooth.Inject/coreclr_delegates.h b/Local/NosSmooth.Inject/coreclr_delegates.h new file mode 100644 index 0000000..dc14638 --- /dev/null +++ b/Local/NosSmooth.Inject/coreclr_delegates.h @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef __CORECLR_DELEGATES_H__ +#define __CORECLR_DELEGATES_H__ + +#include + +#if defined(_WIN32) +#define CORECLR_DELEGATE_CALLTYPE __stdcall +#ifdef _WCHAR_T_DEFINED +typedef wchar_t char_t; +#else +typedef unsigned short char_t; +#endif +#else +#define CORECLR_DELEGATE_CALLTYPE +typedef char char_t; +#endif + +#define UNMANAGEDCALLERSONLY_METHOD ((const char_t*)-1) + +// Signature of delegate returned by coreclr_delegate_type::load_assembly_and_get_function_pointer +typedef int (CORECLR_DELEGATE_CALLTYPE* load_assembly_and_get_function_pointer_fn)( + const char_t* assembly_path /* Fully qualified path to assembly */, + const char_t* type_name /* Assembly qualified type name */, + const char_t* method_name /* Public static method name compatible with delegateType */, + const char_t* delegate_type_name /* Assembly qualified delegate type name or null + or UNMANAGEDCALLERSONLY_METHOD if the method is marked with + the UnmanagedCallersOnlyAttribute. */, + void* reserved /* Extensibility parameter (currently unused and must be 0) */, + /*out*/ void** delegate /* Pointer where to store the function pointer result */); + +// Signature of delegate returned by load_assembly_and_get_function_pointer_fn when delegate_type_name == null (default) +typedef int (CORECLR_DELEGATE_CALLTYPE* component_entry_point_fn)(void* arg, int32_t arg_size_in_bytes); + +typedef int (CORECLR_DELEGATE_CALLTYPE* get_function_pointer_fn)( + const char_t* type_name /* Assembly qualified type name */, + const char_t* method_name /* Public static method name compatible with delegateType */, + const char_t* delegate_type_name /* Assembly qualified delegate type name or null, + or UNMANAGEDCALLERSONLY_METHOD if the method is marked with + the UnmanagedCallersOnlyAttribute. */, + void* load_context /* Extensibility parameter (currently unused and must be 0) */, + void* reserved /* Extensibility parameter (currently unused and must be 0) */, + /*out*/ void** delegate /* Pointer where to store the function pointer result */); + +#endif // __CORECLR_DELEGATES_H__ \ No newline at end of file diff --git a/Local/NosSmooth.Inject/dllmain.cpp b/Local/NosSmooth.Inject/dllmain.cpp new file mode 100644 index 0000000..52d4ad2 --- /dev/null +++ b/Local/NosSmooth.Inject/dllmain.cpp @@ -0,0 +1,19 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/Local/NosSmooth.Inject/framework.h b/Local/NosSmooth.Inject/framework.h new file mode 100644 index 0000000..54b83e9 --- /dev/null +++ b/Local/NosSmooth.Inject/framework.h @@ -0,0 +1,5 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files +#include diff --git a/Local/NosSmooth.Inject/hostfxr.h b/Local/NosSmooth.Inject/hostfxr.h new file mode 100644 index 0000000..232da03 --- /dev/null +++ b/Local/NosSmooth.Inject/hostfxr.h @@ -0,0 +1,323 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef __HOSTFXR_H__ +#define __HOSTFXR_H__ + +#include +#include + +#if defined(_WIN32) +#define HOSTFXR_CALLTYPE __cdecl +#ifdef _WCHAR_T_DEFINED +typedef wchar_t char_t; +#else +typedef unsigned short char_t; +#endif +#else +#define HOSTFXR_CALLTYPE +typedef char char_t; +#endif + +enum hostfxr_delegate_type +{ + hdt_com_activation, + hdt_load_in_memory_assembly, + hdt_winrt_activation, + hdt_com_register, + hdt_com_unregister, + hdt_load_assembly_and_get_function_pointer, + hdt_get_function_pointer, +}; + +typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_main_fn)(const int argc, const char_t** argv); +typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_main_startupinfo_fn)( + const int argc, + const char_t** argv, + const char_t* host_path, + const char_t* dotnet_root, + const char_t* app_path); +typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_main_bundle_startupinfo_fn)( + const int argc, + const char_t** argv, + const char_t* host_path, + const char_t* dotnet_root, + const char_t* app_path, + int64_t bundle_header_offset); + +typedef void(HOSTFXR_CALLTYPE* hostfxr_error_writer_fn)(const char_t* message); + +// +// Sets a callback which is to be used to write errors to. +// +// Parameters: +// error_writer +// A callback function which will be invoked every time an error is to be reported. +// Or nullptr to unregister previously registered callback and return to the default behavior. +// Return value: +// The previously registered callback (which is now unregistered), or nullptr if no previous callback +// was registered +// +// The error writer is registered per-thread, so the registration is thread-local. On each thread +// only one callback can be registered. Subsequent registrations overwrite the previous ones. +// +// By default no callback is registered in which case the errors are written to stderr. +// +// Each call to the error writer is sort of like writing a single line (the EOL character is omitted). +// Multiple calls to the error writer may occure for one failure. +// +// If the hostfxr invokes functions in hostpolicy as part of its operation, the error writer +// will be propagated to hostpolicy for the duration of the call. This means that errors from +// both hostfxr and hostpolicy will be reporter through the same error writer. +// +typedef hostfxr_error_writer_fn(HOSTFXR_CALLTYPE* hostfxr_set_error_writer_fn)(hostfxr_error_writer_fn error_writer); + +typedef void* hostfxr_handle; +struct hostfxr_initialize_parameters +{ + size_t size; + const char_t* host_path; + const char_t* dotnet_root; +}; + +// +// Initializes the hosting components for a dotnet command line running an application +// +// Parameters: +// argc +// Number of argv arguments +// argv +// Command-line arguments for running an application (as if through the dotnet executable). +// Only command-line arguments which are accepted by runtime installation are supported, SDK/CLI commands are not supported. +// For example 'app.dll app_argument_1 app_argument_2`. +// parameters +// Optional. Additional parameters for initialization +// host_context_handle +// On success, this will be populated with an opaque value representing the initialized host context +// +// Return value: +// Success - Hosting components were successfully initialized +// HostInvalidState - Hosting components are already initialized +// +// This function parses the specified command-line arguments to determine the application to run. It will +// then find the corresponding .runtimeconfig.json and .deps.json with which to resolve frameworks and +// dependencies and prepare everything needed to load the runtime. +// +// This function only supports arguments for running an application. It does not support SDK commands. +// +// This function does not load the runtime. +// +typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_initialize_for_dotnet_command_line_fn)( + int argc, + const char_t** argv, + const struct hostfxr_initialize_parameters* parameters, + /*out*/ hostfxr_handle* host_context_handle); + +// +// Initializes the hosting components using a .runtimeconfig.json file +// +// Parameters: +// runtime_config_path +// Path to the .runtimeconfig.json file +// parameters +// Optional. Additional parameters for initialization +// host_context_handle +// On success, this will be populated with an opaque value representing the initialized host context +// +// Return value: +// Success - Hosting components were successfully initialized +// Success_HostAlreadyInitialized - Config is compatible with already initialized hosting components +// Success_DifferentRuntimeProperties - Config has runtime properties that differ from already initialized hosting components +// CoreHostIncompatibleConfig - Config is incompatible with already initialized hosting components +// +// This function will process the .runtimeconfig.json to resolve frameworks and prepare everything needed +// to load the runtime. It will only process the .deps.json from frameworks (not any app/component that +// may be next to the .runtimeconfig.json). +// +// This function does not load the runtime. +// +// If called when the runtime has already been loaded, this function will check if the specified runtime +// config is compatible with the existing runtime. +// +// Both Success_HostAlreadyInitialized and Success_DifferentRuntimeProperties codes are considered successful +// initializations. In the case of Success_DifferentRuntimeProperties, it is left to the consumer to verify that +// the difference in properties is acceptable. +// +typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_initialize_for_runtime_config_fn)( + const char_t* runtime_config_path, + const struct hostfxr_initialize_parameters* parameters, + /*out*/ hostfxr_handle* host_context_handle); + +// +// Gets the runtime property value for an initialized host context +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// name +// Runtime property name +// value +// Out parameter. Pointer to a buffer with the property value. +// +// Return value: +// The error code result. +// +// The buffer pointed to by value is owned by the host context. The lifetime of the buffer is only +// guaranteed until any of the below occur: +// - a 'run' method is called for the host context +// - properties are changed via hostfxr_set_runtime_property_value +// - the host context is closed via 'hostfxr_close' +// +// If host_context_handle is nullptr and an active host context exists, this function will get the +// property value for the active host context. +// +typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_get_runtime_property_value_fn)( + const hostfxr_handle host_context_handle, + const char_t* name, + /*out*/ const char_t** value); + +// +// Sets the value of a runtime property for an initialized host context +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// name +// Runtime property name +// value +// Value to set +// +// Return value: +// The error code result. +// +// Setting properties is only supported for the first host context, before the runtime has been loaded. +// +// If the property already exists in the host context, it will be overwritten. If value is nullptr, the +// property will be removed. +// +typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_set_runtime_property_value_fn)( + const hostfxr_handle host_context_handle, + const char_t* name, + const char_t* value); + +// +// Gets all the runtime properties for an initialized host context +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// count +// [in] Size of the keys and values buffers +// [out] Number of properties returned (size of keys/values buffers used). If the input value is too +// small or keys/values is nullptr, this is populated with the number of available properties +// keys +// Array of pointers to buffers with runtime property keys +// values +// Array of pointers to buffers with runtime property values +// +// Return value: +// The error code result. +// +// The buffers pointed to by keys and values are owned by the host context. The lifetime of the buffers is only +// guaranteed until any of the below occur: +// - a 'run' method is called for the host context +// - properties are changed via hostfxr_set_runtime_property_value +// - the host context is closed via 'hostfxr_close' +// +// If host_context_handle is nullptr and an active host context exists, this function will get the +// properties for the active host context. +// +typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_get_runtime_properties_fn)( + const hostfxr_handle host_context_handle, + /*inout*/ size_t* count, + /*out*/ const char_t** keys, + /*out*/ const char_t** values); + +// +// Load CoreCLR and run the application for an initialized host context +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// +// Return value: +// If the app was successfully run, the exit code of the application. Otherwise, the error code result. +// +// The host_context_handle must have been initialized using hostfxr_initialize_for_dotnet_command_line. +// +// This function will not return until the managed application exits. +// +typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_run_app_fn)(const hostfxr_handle host_context_handle); + +// +// Gets a typed delegate from the currently loaded CoreCLR or from a newly created one. +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// type +// Type of runtime delegate requested +// delegate +// An out parameter that will be assigned the delegate. +// +// Return value: +// The error code result. +// +// If the host_context_handle was initialized using hostfxr_initialize_for_runtime_config, +// then all delegate types are supported. +// If the host_context_handle was initialized using hostfxr_initialize_for_dotnet_command_line, +// then only the following delegate types are currently supported: +// hdt_load_assembly_and_get_function_pointer +// hdt_get_function_pointer +// +typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_get_runtime_delegate_fn)( + const hostfxr_handle host_context_handle, + enum hostfxr_delegate_type type, + /*out*/ void** delegate); + +// +// Closes an initialized host context +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// +// Return value: +// The error code result. +// +typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_close_fn)(const hostfxr_handle host_context_handle); + +struct hostfxr_dotnet_environment_sdk_info +{ + size_t size; + const char_t* version; + const char_t* path; +}; + +typedef void(HOSTFXR_CALLTYPE* hostfxr_get_dotnet_environment_info_result_fn)( + const struct hostfxr_dotnet_environment_info* info, + void* result_context); + +struct hostfxr_dotnet_environment_framework_info +{ + size_t size; + const char_t* name; + const char_t* version; + const char_t* path; +}; + +struct hostfxr_dotnet_environment_info +{ + size_t size; + + const char_t* hostfxr_version; + const char_t* hostfxr_commit_hash; + + size_t sdk_count; + const hostfxr_dotnet_environment_sdk_info* sdks; + + size_t framework_count; + const hostfxr_dotnet_environment_framework_info* frameworks; +}; + +#endif //__HOSTFXR_H__ \ No newline at end of file diff --git a/Local/NosSmooth.Inject/nethost.h b/Local/NosSmooth.Inject/nethost.h new file mode 100644 index 0000000..52c5635 --- /dev/null +++ b/Local/NosSmooth.Inject/nethost.h @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#ifndef __NETHOST_H__ +#define __NETHOST_H__ + +#include + +#if defined(_WIN32) +#ifdef NETHOST_EXPORT +#define NETHOST_API __declspec(dllexport) +#else +#define NETHOST_API __declspec(dllimport) +#endif + +#define NETHOST_CALLTYPE __stdcall +#ifdef _WCHAR_T_DEFINED +typedef wchar_t char_t; +#else +typedef unsigned short char_t; +#endif +#else +#ifdef NETHOST_EXPORT +#define NETHOST_API __attribute__((__visibility__("default"))) +#else +#define NETHOST_API +#endif + +#define NETHOST_CALLTYPE +typedef char char_t; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + // Parameters for get_hostfxr_path + // + // Fields: + // size + // Size of the struct. This is used for versioning. + // + // assembly_path + // Path to the compenent's assembly. + // If specified, hostfxr is located as if the assembly_path is the apphost + // + // dotnet_root + // Path to directory containing the dotnet executable. + // If specified, hostfxr is located as if an application is started using + // 'dotnet app.dll', which means it will be searched for under the dotnet_root + // path and the assembly_path is ignored. + // + struct get_hostfxr_parameters { + size_t size; + const char_t* assembly_path; + const char_t* dotnet_root; + }; + + // + // Get the path to the hostfxr library + // + // Parameters: + // buffer + // Buffer that will be populated with the hostfxr path, including a null terminator. + // + // buffer_size + // [in] Size of buffer in char_t units. + // [out] Size of buffer used in char_t units. If the input value is too small + // or buffer is nullptr, this is populated with the minimum required size + // in char_t units for a buffer to hold the hostfxr path + // + // get_hostfxr_parameters + // Optional. Parameters that modify the behaviour for locating the hostfxr library. + // If nullptr, hostfxr is located using the enviroment variable or global registration + // + // Return value: + // 0 on success, otherwise failure + // 0x80008098 - buffer is too small (HostApiBufferTooSmall) + // + // Remarks: + // The full search for the hostfxr library is done on every call. To minimize the need + // to call this function multiple times, pass a large buffer (e.g. PATH_MAX). + // + NETHOST_API int NETHOST_CALLTYPE get_hostfxr_path( + char_t* buffer, + size_t* buffer_size, + const struct get_hostfxr_parameters* parameters); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __NETHOST_H__ diff --git a/Local/NosSmooth.Inject/nossmooth.cpp b/Local/NosSmooth.Inject/nossmooth.cpp new file mode 100644 index 0000000..b5254ba --- /dev/null +++ b/Local/NosSmooth.Inject/nossmooth.cpp @@ -0,0 +1,125 @@ +#include "nossmooth.h" + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// Standard headers +#include +#include +#include +#include +#include +#include +#include "nethost.h" +#include "coreclr_delegates.h" +#include "hostfxr.h" + +#include + +#define STR(s) L ## s +#define CH(c) L ## c +#define DIR_SEPARATOR L'\\' + +using string_t = std::basic_string; + +// Globals to hold hostfxr exports +hostfxr_initialize_for_runtime_config_fn init_fptr; +hostfxr_get_runtime_delegate_fn get_delegate_fptr; +hostfxr_close_fn close_fptr; + +// Forward declarations +bool load_hostfxr(); +load_assembly_and_get_function_pointer_fn get_dotnet_load_assembly(const char_t* assembly); + +/******************************************************************************************** + * Function used to load and activate .NET Core + ********************************************************************************************/ + + // Forward declarations +void* load_library(const char_t*); +void* get_export(void*, const char*); + +void* load_library(const char_t* path) +{ + HMODULE h = ::LoadLibraryW(path); + assert(h != nullptr); + return (void*)h; +} +void* get_export(void* h, const char* name) +{ + void* f = ::GetProcAddress((HMODULE)h, name); + assert(f != nullptr); + return f; +} + +// Using the nethost library, discover the location of hostfxr and get exports +bool load_hostfxr() +{ + // Pre-allocate a large buffer for the path to hostfxr + char_t buffer[MAX_PATH]; + size_t buffer_size = sizeof(buffer) / sizeof(char_t); + int rc = get_hostfxr_path(buffer, &buffer_size, nullptr); + if (rc != 0) + return false; + + // Load hostfxr and get desired exports + void* lib = load_library(buffer); + init_fptr = (hostfxr_initialize_for_runtime_config_fn)get_export(lib, "hostfxr_initialize_for_runtime_config"); + get_delegate_fptr = (hostfxr_get_runtime_delegate_fn)get_export(lib, "hostfxr_get_runtime_delegate"); + close_fptr = (hostfxr_close_fn)get_export(lib, "hostfxr_close"); + + return (init_fptr && get_delegate_fptr && close_fptr); +} + +// Load and initialize .NET Core and get desired function pointer for scenario +load_assembly_and_get_function_pointer_fn get_dotnet_load_assembly(const char_t* config_path) +{ + // Load .NET Core + void* load_assembly_and_get_function_pointer = nullptr; + hostfxr_handle cxt = nullptr; + int rc = init_fptr(config_path, nullptr, &cxt); + if (rc != 0 || cxt == nullptr) + { + std::cerr << "Init failed: " << std::hex << std::showbase << rc << std::endl; + close_fptr(cxt); + return nullptr; + } + + // Get the load assembly function pointer + rc = get_delegate_fptr( + cxt, + hdt_load_assembly_and_get_function_pointer, + &load_assembly_and_get_function_pointer); + if (rc != 0 || load_assembly_and_get_function_pointer == nullptr) + std::cerr << "Get delegate failed: " << std::hex << std::showbase << rc << std::endl; + + close_fptr(cxt); + return (load_assembly_and_get_function_pointer_fn)load_assembly_and_get_function_pointer; +} + +bool LoadAndCallMethod(LoadParams* params) +{ + if (!load_hostfxr()) + { + assert(false && "Failure: load_hostfxr()"); + return false; + } + + load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer = nullptr; + load_assembly_and_get_function_pointer = get_dotnet_load_assembly(params->runtimeConfigPath); + assert(load_assembly_and_get_function_pointer != nullptr && "Failure: get_dotnet_load_assembly()"); + + typedef void (CORECLR_DELEGATE_CALLTYPE* main_entry_point_fn)(); + main_entry_point_fn main = nullptr; + int rc = load_assembly_and_get_function_pointer( + params->libraryPath, + params->typePath, + params->methodName, + UNMANAGEDCALLERSONLY_METHOD, + nullptr, + (void**)&main); + assert(rc == 0 && main != nullptr && "Failure: load_assembly_and_get_function_pointer()"); + main(); + return true; +} \ No newline at end of file diff --git a/Local/NosSmooth.Inject/nossmooth.h b/Local/NosSmooth.Inject/nossmooth.h new file mode 100644 index 0000000..55f8461 --- /dev/null +++ b/Local/NosSmooth.Inject/nossmooth.h @@ -0,0 +1,15 @@ +#pragma once +#include + +#pragma pack(push, 1) +struct LoadParams +{ + wchar_t *libraryPath; + wchar_t *runtimeConfigPath; + wchar_t *typePath; + wchar_t *methodName; +}; +#pragma pack(pop) +#define DllExport extern "C" __declspec( dllexport ) + +DllExport bool LoadAndCallMethod(LoadParams* params); \ No newline at end of file diff --git a/Local/NosSmooth.Inject/pch.cpp b/Local/NosSmooth.Inject/pch.cpp new file mode 100644 index 0000000..64b7eef --- /dev/null +++ b/Local/NosSmooth.Inject/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/Local/NosSmooth.Inject/pch.h b/Local/NosSmooth.Inject/pch.h new file mode 100644 index 0000000..885d5d6 --- /dev/null +++ b/Local/NosSmooth.Inject/pch.h @@ -0,0 +1,13 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef PCH_H +#define PCH_H + +// add headers that you want to pre-compile here +#include "framework.h" + +#endif //PCH_H diff --git a/Local/NosSmooth.Injector.CLI/Commands/InjectCommand.cs b/Local/NosSmooth.Injector.CLI/Commands/InjectCommand.cs new file mode 100644 index 0000000..ff7e035 --- /dev/null +++ b/Local/NosSmooth.Injector.CLI/Commands/InjectCommand.cs @@ -0,0 +1,56 @@ +// +// InjectCommand.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.ComponentModel; +using Remora.Commands.Attributes; +using Remora.Commands.Groups; +using Remora.Results; + +namespace NosSmooth.Injector.CLI.Commands +{ + /// + /// Injection command for injecting .NET 5+ libraries with UnmanagedCallersOnly method. + /// + internal class InjectCommand : CommandGroup + { + private readonly NosInjector _injector; + + /// + /// Initializes a new instance of the class. + /// + /// The nos smooth injector. + public InjectCommand(NosInjector injector) + { + _injector = injector; + } + + /// + /// The command to inject. + /// + /// The id of the process. + /// The path to the dll to inject. + /// The full type specifier. Default is LibraryName.DllMain, LibraryName. + /// The name of the UnmanagedCallersOnly method. Default is Main. + /// A result that may or may not have succeeded. + [Command("inject")] + public Task Inject + ( + [Description("The id of the process to inject into.")] + int processId, + [Description("The path to the dll to inject.")] + string dllPath, + [Option('t', "type"), Description("The full type specifier. Default is LibraryName.DllMain, LibraryName")] + string? typeName = null, + [Option('m', "method"), Description("The name of the UnmanagedCallersOnly method. Default is Main")] + string? methodName = null + ) + { + var dllName = Path.GetFileNameWithoutExtension(dllPath); + return Task.FromResult + (_injector.Inject(processId, dllPath, $"{dllName}.DllMain, {dllName}", methodName ?? "Main")); + } + } +} \ No newline at end of file diff --git a/Local/NosSmooth.Injector.CLI/Commands/ListProcessesCommand.cs b/Local/NosSmooth.Injector.CLI/Commands/ListProcessesCommand.cs new file mode 100644 index 0000000..9f73157 --- /dev/null +++ b/Local/NosSmooth.Injector.CLI/Commands/ListProcessesCommand.cs @@ -0,0 +1,41 @@ +// +// ListProcessesCommand.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; +using Remora.Commands.Attributes; +using Remora.Commands.Groups; +using Remora.Results; + +namespace NosSmooth.Injector.CLI.Commands +{ + /// + /// Command for listing processes to find id of NosTale process. + /// + internal class ListProcessesCommand : CommandGroup + { + /// + /// Lists processes by the given criteria. + /// + /// What should the name of the process contain. + /// A result that may or may not have succeeded. + [Command("list")] + public Task List(string nameContains = "Nostale") + { + var processes = Process.GetProcesses(); + foreach (var process in processes.Where(x => x.ProcessName.Contains(nameContains, StringComparison.OrdinalIgnoreCase))) + { + Console.WriteLine(ProcessToString(process)); + } + + return Task.FromResult(Result.FromSuccess()); + } + + private string ProcessToString(Process process) + { + return $"{process.ProcessName} - {process.Id}"; + } + } +} diff --git a/Local/NosSmooth.Injector.CLI/NosSmooth.Injector.CLI.csproj b/Local/NosSmooth.Injector.CLI/NosSmooth.Injector.CLI.csproj new file mode 100644 index 0000000..7ce67a9 --- /dev/null +++ b/Local/NosSmooth.Injector.CLI/NosSmooth.Injector.CLI.csproj @@ -0,0 +1,28 @@ + + + + Exe + net6.0 + enable + enable + app.manifest + + + + + + + + + + + + + + + + + + + + diff --git a/Local/NosSmooth.Injector.CLI/Program.cs b/Local/NosSmooth.Injector.CLI/Program.cs new file mode 100644 index 0000000..62c7d80 --- /dev/null +++ b/Local/NosSmooth.Injector.CLI/Program.cs @@ -0,0 +1,75 @@ +// +// Program.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Extensions.DependencyInjection; +using NosSmooth.Injector.CLI.Commands; +using Remora.Commands.Extensions; +using Remora.Commands.Services; +using Remora.Results; + +namespace NosSmooth.Injector.CLI +{ + /// + /// The entrypoint class. + /// + public static class Program + { + /// + /// The entrypoint method. + /// + /// The command line arguments. + /// A task that may or may not have succeeded. + public static async Task Main(string[] argv) + { + var services = CreateServices(); + var commandService = services.GetRequiredService(); + var preparedCommandResult = await commandService.TryPrepareCommandAsync(string.Join(' ', argv), services); + if (!preparedCommandResult.IsSuccess) + { + Console.Error.WriteLine($"There was an error, {preparedCommandResult.Error.Message}"); + return; + } + + if (preparedCommandResult.Entity is null) + { + Console.Error.WriteLine("You must enter a command such ast list or inject."); + return; + } + + var executionResult = await commandService.TryExecuteAsync(preparedCommandResult.Entity, services); + if (!executionResult.Entity.IsSuccess) + { + switch (executionResult.Entity.Error) + { + case ExceptionError exc: + Console.Error.WriteLine($"There was an exception, {exc.Exception.Message}"); + break; + default: + Console.Error.WriteLine($"There was an error, {executionResult.Entity.Error!.Message}"); + break; + } + + return; + } + } + + private static IServiceProvider CreateServices() + { + var collection = new ServiceCollection(); + collection + .AddSingleton() + .AddOptions(); + + collection + .AddCommands() + .AddCommandTree() + .WithCommandGroup() + .WithCommandGroup(); + + return collection.BuildServiceProvider(); + } + } +} diff --git a/Local/NosSmooth.Injector.CLI/app.manifest b/Local/NosSmooth.Injector.CLI/app.manifest new file mode 100644 index 0000000..26cb1ef --- /dev/null +++ b/Local/NosSmooth.Injector.CLI/app.manifest @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Local/NosSmooth.Injector/Errors/InjectionFailedError.cs b/Local/NosSmooth.Injector/Errors/InjectionFailedError.cs new file mode 100644 index 0000000..fed27cd --- /dev/null +++ b/Local/NosSmooth.Injector/Errors/InjectionFailedError.cs @@ -0,0 +1,15 @@ +// +// InjectionFailedError.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Remora.Results; + +namespace NosSmooth.Injector.Errors; + +/// +/// The injection could not be finished successfully. +/// +/// The path to the dll. +public record InjectionFailedError(string DllPath) : ResultError($"Could not inject {DllPath} dll into the process."); \ No newline at end of file diff --git a/Local/NosSmooth.Injector/Errors/InsufficientPermissionsError.cs b/Local/NosSmooth.Injector/Errors/InsufficientPermissionsError.cs new file mode 100644 index 0000000..92d6ee9 --- /dev/null +++ b/Local/NosSmooth.Injector/Errors/InsufficientPermissionsError.cs @@ -0,0 +1,20 @@ +// +// InsufficientPermissionsError.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Remora.Results; + +namespace NosSmooth.Injector.Errors; + +/// +/// The current user has insufficient permissions to inject into the given process. +/// +/// The id of the process. +/// The name of the process. +public record InsufficientPermissionsError(long ProcessId, string ProcessName) + : ResultError + ( + $"Insufficient permissions to open process {ProcessId} ({ProcessName}). Try running the injector as administrator." + ); \ No newline at end of file diff --git a/Local/NosSmooth.Injector/Errors/ProcessNotFoundError.cs b/Local/NosSmooth.Injector/Errors/ProcessNotFoundError.cs new file mode 100644 index 0000000..b1c04b7 --- /dev/null +++ b/Local/NosSmooth.Injector/Errors/ProcessNotFoundError.cs @@ -0,0 +1,16 @@ +// +// ProcessNotFoundError.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Remora.Results; + +namespace NosSmooth.Injector.Errors; + +/// +/// The given process was not found. +/// +/// The id of the process. +public record ProcessNotFoundError(string ProcessId) + : NotFoundError($"Could not find process with the given id {ProcessId}."); \ No newline at end of file diff --git a/Local/NosSmooth.Injector/LoadParams.cs b/Local/NosSmooth.Injector/LoadParams.cs new file mode 100644 index 0000000..e249612 --- /dev/null +++ b/Local/NosSmooth.Injector/LoadParams.cs @@ -0,0 +1,44 @@ +// +// LoadParams.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace NosSmooth.Injector +{ + /// + /// The parameters passed to the inject module. + /// + internal struct LoadParams + { + /// + /// The full path of the library. + /// + public int LibraryPath; + + /// + /// The full path of the library. + /// + public int RuntimeConfigPath; + + /// + /// The full path to the type with the method marked as UnsafeCallersOnly. + /// + /// + /// Can be for example "LibraryNamespace.Type, LibraryNamespace". + /// + public int TypePath; + + /// + /// The name of the method to execute. + /// + public int MethodName; + } +} diff --git a/Local/NosSmooth.Injector/ManagedMemoryAllocation.cs b/Local/NosSmooth.Injector/ManagedMemoryAllocation.cs new file mode 100644 index 0000000..8d7308e --- /dev/null +++ b/Local/NosSmooth.Injector/ManagedMemoryAllocation.cs @@ -0,0 +1,47 @@ +// +// ManagedMemoryAllocation.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Net.NetworkInformation; +using Reloaded.Memory.Sources; + +namespace NosSmooth.Injector; + +/// +/// Represents freeable memory allocation. +/// +internal class ManagedMemoryAllocation : IDisposable +{ + private readonly IMemory _memory; + + /// + /// Initializes a new instance of the class. + /// + /// The memory with allocation. + /// The pointer to allocated memory. + public ManagedMemoryAllocation(IMemory memory, IntPtr pointer) + { + Pointer = pointer; + _memory = memory; + + } + + /// + /// The allocated pointer number. + /// + public IntPtr Pointer { get; private set; } + + /// + /// Whether the memory is currently allocated. + /// + public bool Allocated => Pointer != IntPtr.Zero; + + /// + public void Dispose() + { + _memory.Free(Pointer); + Pointer = IntPtr.Zero; + } +} \ No newline at end of file diff --git a/Local/NosSmooth.Injector/NosInjector.cs b/Local/NosSmooth.Injector/NosInjector.cs new file mode 100644 index 0000000..2ee64eb --- /dev/null +++ b/Local/NosSmooth.Injector/NosInjector.cs @@ -0,0 +1,155 @@ +// +// NosInjector.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; +using System.Security; +using System.Text; +using Microsoft.Extensions.Options; +using NosSmooth.Injector.Errors; +using Reloaded.Memory.Sources; +using Remora.Results; + +namespace NosSmooth.Injector; + +/// +/// Nos smooth injector for .NET 5+ projects. +/// +/// +/// If you want to inject your project into NosTale that +/// uses NosSmooth.LocalClient, use this injector. +/// You must expose static UnmanagedCallersOnly method. +/// +public class NosInjector +{ + private readonly NosInjectorOptions _options; + + /// + /// Initializes a new instance of the class. + /// + /// The injector options. + public NosInjector(IOptions options) + { + _options = options.Value; + } + + /// + /// Inject the given .NET 5+ dll into NosTale process. + /// + /// + /// The dll must also have .runtimeconfig.json present next to the dll. + /// + /// The id of the process to inject to. + /// The absolute path to the dll to inject. + /// The full path to the class. Such as "MyLibrary.DllMain, MyLibrary". + /// The name of the method to execute. The method should return void and have no parameters. + /// A result that may or may not have succeeded. + public Result Inject + ( + int processId, + string dllPath, + string classPath, + string methodName = "Main" + ) + { + using var process = Process.GetProcessById(processId); + return Inject(process, dllPath, classPath, methodName); + } + + /// + /// Inject the given .NET 5+ dll into NosTale process. + /// + /// + /// The dll must also have .runtimeconfig.json present next to the dll. + /// + /// The process to inject to. + /// The absolute path to the dll to inject. + /// The full path to the class. Such as "MyLibrary.DllMain, MyLibrary". + /// The name of the method to execute. The method should return void and have no parameters. + /// A result that may or may not have succeeded. + public Result Inject + ( + Process process, + string dllPath, + string classPath, + string methodName = "Main" + ) + { + try + { + using var injector = new Reloaded.Injector.Injector(process); + var memory = new ExternalMemory(process); + + var directoryName = Path.GetDirectoryName(dllPath); + if (directoryName is null) + { + return new GenericError("There was an error obtaining directory name of the dll path."); + } + + var runtimePath = Path.Combine + (directoryName, Path.GetFileNameWithoutExtension(dllPath)) + ".runtimeconfig.json"; + + using var dllPathMemory = AllocateString(memory, dllPath); + using var classPathMemory = AllocateString(memory, classPath); + using var methodNameMemory = AllocateString(memory, methodName); + using var runtimePathMemory = AllocateString(memory, runtimePath); + + if (!dllPathMemory.Allocated || !classPathMemory.Allocated || !methodNameMemory.Allocated + || !runtimePathMemory.Allocated) + { + return new GenericError("Could not allocate memory in the external process."); + } + + var loadParams = new LoadParams() + { + LibraryPath = (int)dllPathMemory.Pointer, + MethodName = (int)methodNameMemory.Pointer, + RuntimeConfigPath = (int)runtimePathMemory.Pointer, + TypePath = (int)classPathMemory.Pointer + }; + + var nosSmoothInjectPath = Path.GetFullPath(_options.NosSmoothInjectPath); + var injected = injector.Inject(nosSmoothInjectPath); + if (injected == 0) + { + return new InjectionFailedError(nosSmoothInjectPath); + } + + var functionResult = injector.CallFunction(nosSmoothInjectPath, "LoadAndCallMethod", loadParams); + if (functionResult != 0) + { + return new InjectionFailedError(dllPath); + } + + return Result.FromSuccess(); + } + catch (UnauthorizedAccessException) + { + return new InsufficientPermissionsError(process.Id, process.ProcessName); + } + catch (SecurityException) + { + return new InsufficientPermissionsError(process.Id, process.ProcessName); + } + catch (Exception e) + { + return e; + } + } + + private ManagedMemoryAllocation AllocateString(IMemory memory, string str) + { + var bytes = Encoding.Unicode.GetBytes(str); + var allocated = memory.Allocate(bytes.Length + 1); + if (allocated == IntPtr.Zero) + { + return new ManagedMemoryAllocation(memory, allocated); + } + + memory.SafeWriteRaw(allocated + bytes.Length, new byte[] { 0 }); + memory.SafeWriteRaw(allocated, bytes); + return new ManagedMemoryAllocation(memory, allocated); + } +} \ No newline at end of file diff --git a/Local/NosSmooth.Injector/NosInjectorOptions.cs b/Local/NosSmooth.Injector/NosInjectorOptions.cs new file mode 100644 index 0000000..234f645 --- /dev/null +++ b/Local/NosSmooth.Injector/NosInjectorOptions.cs @@ -0,0 +1,32 @@ +// +// NosInjectorOptions.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; + +namespace NosSmooth.Injector +{ + /// + /// Options for NosInjector. + /// + public class NosInjectorOptions : IOptions + { + /// + /// Gets or sets the path to the nos smooth inject dll. + /// + /// + /// If not absolute path, then relative path from the current executing process is assumed. + /// + public string NosSmoothInjectPath { get; set; } = "NosSmooth.Inject.dll"; + + /// + public NosInjectorOptions Value => this; + } +} diff --git a/Local/NosSmooth.Injector/NosSmooth.Injector.csproj b/Local/NosSmooth.Injector/NosSmooth.Injector.csproj new file mode 100644 index 0000000..e222699 --- /dev/null +++ b/Local/NosSmooth.Injector/NosSmooth.Injector.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + enable + enable + + + + + + + + + diff --git a/Local/NosSmooth.LocalCore/NosSmooth.LocalCore.vcxproj b/Local/NosSmooth.LocalCore/NosSmooth.LocalCore.vcxproj new file mode 100644 index 0000000..e69de29 diff --git a/NosSmooth.sln b/NosSmooth.sln index b3b4c4f..ba38e08 100644 --- a/NosSmooth.sln +++ b/NosSmooth.sln @@ -48,11 +48,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NosSmooth.PacketSerializers EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NosSmooth.Packets.Tests", "Tests\NosSmooth.Packets.Tests\NosSmooth.Packets.Tests.csproj", "{9BC56B40-64E3-4A8F-AD49-C52857A35026}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NativeExecutor", "NativeExecutor\NativeExecutor.vcxproj", "{1BA17328-38FE-4830-9F26-F24585D4CAAB}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NosSmooth.Inject", "Local\NosSmooth.Inject\NosSmooth.Inject.vcxproj", "{CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassLibrary1", "ClassLibrary1\ClassLibrary1.csproj", "{4F7B9EBB-B2F4-4170-ABDA-504D19D807AA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NosSmooth.Injector.CLI", "Local\NosSmooth.Injector.CLI\NosSmooth.Injector.CLI.csproj", "{5D351C91-C631-40F6-82D2-F8D68468B076}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NosSmooth.LocalBinding", "Local\NosSmooth.LocalBinding\NosSmooth.LocalBinding.csproj", "{AA21666C-C2EF-4899-B047-889657CEB2D9}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NosSmooth.Injector", "Local\NosSmooth.Injector\NosSmooth.Injector.csproj", "{7B68DE7D-159B-42A7-8CF8-69F156CD04DE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -232,42 +232,42 @@ Global {9BC56B40-64E3-4A8F-AD49-C52857A35026}.Release|x64.Build.0 = Release|Any CPU {9BC56B40-64E3-4A8F-AD49-C52857A35026}.Release|x86.ActiveCfg = Release|Any CPU {9BC56B40-64E3-4A8F-AD49-C52857A35026}.Release|x86.Build.0 = Release|Any CPU - {1BA17328-38FE-4830-9F26-F24585D4CAAB}.Debug|Any CPU.ActiveCfg = Debug|x64 - {1BA17328-38FE-4830-9F26-F24585D4CAAB}.Debug|Any CPU.Build.0 = Debug|x64 - {1BA17328-38FE-4830-9F26-F24585D4CAAB}.Debug|x64.ActiveCfg = Debug|x64 - {1BA17328-38FE-4830-9F26-F24585D4CAAB}.Debug|x64.Build.0 = Debug|x64 - {1BA17328-38FE-4830-9F26-F24585D4CAAB}.Debug|x86.ActiveCfg = Debug|Win32 - {1BA17328-38FE-4830-9F26-F24585D4CAAB}.Debug|x86.Build.0 = Debug|Win32 - {1BA17328-38FE-4830-9F26-F24585D4CAAB}.Release|Any CPU.ActiveCfg = Release|x64 - {1BA17328-38FE-4830-9F26-F24585D4CAAB}.Release|Any CPU.Build.0 = Release|x64 - {1BA17328-38FE-4830-9F26-F24585D4CAAB}.Release|x64.ActiveCfg = Release|x64 - {1BA17328-38FE-4830-9F26-F24585D4CAAB}.Release|x64.Build.0 = Release|x64 - {1BA17328-38FE-4830-9F26-F24585D4CAAB}.Release|x86.ActiveCfg = Release|Win32 - {1BA17328-38FE-4830-9F26-F24585D4CAAB}.Release|x86.Build.0 = Release|Win32 - {4F7B9EBB-B2F4-4170-ABDA-504D19D807AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4F7B9EBB-B2F4-4170-ABDA-504D19D807AA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4F7B9EBB-B2F4-4170-ABDA-504D19D807AA}.Debug|x64.ActiveCfg = Debug|Any CPU - {4F7B9EBB-B2F4-4170-ABDA-504D19D807AA}.Debug|x64.Build.0 = Debug|Any CPU - {4F7B9EBB-B2F4-4170-ABDA-504D19D807AA}.Debug|x86.ActiveCfg = Debug|Any CPU - {4F7B9EBB-B2F4-4170-ABDA-504D19D807AA}.Debug|x86.Build.0 = Debug|Any CPU - {4F7B9EBB-B2F4-4170-ABDA-504D19D807AA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4F7B9EBB-B2F4-4170-ABDA-504D19D807AA}.Release|Any CPU.Build.0 = Release|Any CPU - {4F7B9EBB-B2F4-4170-ABDA-504D19D807AA}.Release|x64.ActiveCfg = Release|Any CPU - {4F7B9EBB-B2F4-4170-ABDA-504D19D807AA}.Release|x64.Build.0 = Release|Any CPU - {4F7B9EBB-B2F4-4170-ABDA-504D19D807AA}.Release|x86.ActiveCfg = Release|Any CPU - {4F7B9EBB-B2F4-4170-ABDA-504D19D807AA}.Release|x86.Build.0 = Release|Any CPU - {AA21666C-C2EF-4899-B047-889657CEB2D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AA21666C-C2EF-4899-B047-889657CEB2D9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AA21666C-C2EF-4899-B047-889657CEB2D9}.Debug|x64.ActiveCfg = Debug|Any CPU - {AA21666C-C2EF-4899-B047-889657CEB2D9}.Debug|x64.Build.0 = Debug|Any CPU - {AA21666C-C2EF-4899-B047-889657CEB2D9}.Debug|x86.ActiveCfg = Debug|Any CPU - {AA21666C-C2EF-4899-B047-889657CEB2D9}.Debug|x86.Build.0 = Debug|Any CPU - {AA21666C-C2EF-4899-B047-889657CEB2D9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AA21666C-C2EF-4899-B047-889657CEB2D9}.Release|Any CPU.Build.0 = Release|Any CPU - {AA21666C-C2EF-4899-B047-889657CEB2D9}.Release|x64.ActiveCfg = Release|Any CPU - {AA21666C-C2EF-4899-B047-889657CEB2D9}.Release|x64.Build.0 = Release|Any CPU - {AA21666C-C2EF-4899-B047-889657CEB2D9}.Release|x86.ActiveCfg = Release|Any CPU - {AA21666C-C2EF-4899-B047-889657CEB2D9}.Release|x86.Build.0 = Release|Any CPU + {CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Debug|Any CPU.ActiveCfg = Debug|x64 + {CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Debug|Any CPU.Build.0 = Debug|x64 + {CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Debug|x64.ActiveCfg = Debug|x64 + {CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Debug|x64.Build.0 = Debug|x64 + {CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Debug|x86.ActiveCfg = Debug|Win32 + {CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Debug|x86.Build.0 = Debug|Win32 + {CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Release|Any CPU.ActiveCfg = Release|x64 + {CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Release|Any CPU.Build.0 = Release|x64 + {CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Release|x64.ActiveCfg = Release|x64 + {CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Release|x64.Build.0 = Release|x64 + {CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Release|x86.ActiveCfg = Release|Win32 + {CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Release|x86.Build.0 = Release|Win32 + {5D351C91-C631-40F6-82D2-F8D68468B076}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D351C91-C631-40F6-82D2-F8D68468B076}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D351C91-C631-40F6-82D2-F8D68468B076}.Debug|x64.ActiveCfg = Debug|Any CPU + {5D351C91-C631-40F6-82D2-F8D68468B076}.Debug|x64.Build.0 = Debug|Any CPU + {5D351C91-C631-40F6-82D2-F8D68468B076}.Debug|x86.ActiveCfg = Debug|Any CPU + {5D351C91-C631-40F6-82D2-F8D68468B076}.Debug|x86.Build.0 = Debug|Any CPU + {5D351C91-C631-40F6-82D2-F8D68468B076}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D351C91-C631-40F6-82D2-F8D68468B076}.Release|Any CPU.Build.0 = Release|Any CPU + {5D351C91-C631-40F6-82D2-F8D68468B076}.Release|x64.ActiveCfg = Release|Any CPU + {5D351C91-C631-40F6-82D2-F8D68468B076}.Release|x64.Build.0 = Release|Any CPU + {5D351C91-C631-40F6-82D2-F8D68468B076}.Release|x86.ActiveCfg = Release|Any CPU + {5D351C91-C631-40F6-82D2-F8D68468B076}.Release|x86.Build.0 = Release|Any CPU + {7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Debug|x64.ActiveCfg = Debug|Any CPU + {7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Debug|x64.Build.0 = Debug|Any CPU + {7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Debug|x86.ActiveCfg = Debug|Any CPU + {7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Debug|x86.Build.0 = Debug|Any CPU + {7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Release|Any CPU.Build.0 = Release|Any CPU + {7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Release|x64.ActiveCfg = Release|Any CPU + {7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Release|x64.Build.0 = Release|Any CPU + {7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Release|x86.ActiveCfg = Release|Any CPU + {7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -278,18 +278,19 @@ Global {46D0A205-CA30-41CC-9A80-10B543FBD344} = {6078AE6E-7CD0-48E4-84E0-EB164D8881DA} {05E3039D-EDF6-4CDC-B062-CB67760ACB5F} = {F9EFA63C-0A88-45EB-B36F-C4A9D63BDFD1} {06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7} = {01B5E872-271F-4D30-A1AA-AD48D81840C5} + {63E97FF3-7E40-44DE-9E91-F5DEE79AF95F} = {6078AE6E-7CD0-48E4-84E0-EB164D8881DA} {4017A4F4-5E59-48AA-A7D0-A8518148933A} = {9025731C-084E-4E82-8CD4-0F52D3AA1F54} {726188BA-F0EA-4ECA-ACF4-CCC066464FF0} = {C6A8760D-92CB-4307-88A7-36CCAEBA4AD1} {F96F3AA0-131E-4B6B-AB21-BBE2DEBCEF3A} = {9025731C-084E-4E82-8CD4-0F52D3AA1F54} {9025731C-084E-4E82-8CD4-0F52D3AA1F54} = {F20FE754-FDEA-4F3A-93D4-0750CB9EBB33} - {18A62EF6-ADDA-4224-90AB-2D5DCFC95D3E} = {F20FE754-FDEA-4F3A-93D4-0750CB9EBB33} {1A10C624-48E5-425D-938E-31A4CC7AC687} = {C6A8760D-92CB-4307-88A7-36CCAEBA4AD1} {C4DAFD83-C6DC-4597-AA1F-BA2F3ABB612C} = {01B5E872-271F-4D30-A1AA-AD48D81840C5} {C61EBDB6-053C-48C3-B896-58642639C93F} = {01B5E872-271F-4D30-A1AA-AD48D81840C5} {9BC56B40-64E3-4A8F-AD49-C52857A35026} = {C6A8760D-92CB-4307-88A7-36CCAEBA4AD1} - {1BA17328-38FE-4830-9F26-F24585D4CAAB} = {F20FE754-FDEA-4F3A-93D4-0750CB9EBB33} - {4F7B9EBB-B2F4-4170-ABDA-504D19D807AA} = {F20FE754-FDEA-4F3A-93D4-0750CB9EBB33} - {AA21666C-C2EF-4899-B047-889657CEB2D9} = {6078AE6E-7CD0-48E4-84E0-EB164D8881DA} + {CA2873D8-BD0B-4583-818D-B94A3C2ABBA3} = {6078AE6E-7CD0-48E4-84E0-EB164D8881DA} + {5D351C91-C631-40F6-82D2-F8D68468B076} = {6078AE6E-7CD0-48E4-84E0-EB164D8881DA} + {7B68DE7D-159B-42A7-8CF8-69F156CD04DE} = {6078AE6E-7CD0-48E4-84E0-EB164D8881DA} + {18A62EF6-ADDA-4224-90AB-2D5DCFC95D3E} = {F20FE754-FDEA-4F3A-93D4-0750CB9EBB33} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C5F46653-4DEC-429B-8580-4ED18ED9B4CA} -- 2.48.1