Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
DaZombieKiller committed Sep 8, 2023
1 parent ea255dc commit 59a9538
Show file tree
Hide file tree
Showing 24 changed files with 367 additions and 0 deletions.
42 changes: 42 additions & 0 deletions CSharpHelper/CSharpHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;

/// <summary>Provides helpers for parsing C# source code.</summary>
public static unsafe class CSharpHelper
{
/// <summary>Given some C# source text and a type name, determines the namespace of the type.</summary>
[UnmanagedCallersOnly(EntryPoint = nameof(GetClassNamespace))]
public static int GetClassNamespace(byte* pText, int textLength, byte* pClassName, void* state, delegate* unmanaged<byte*, void*, void> callback)
{
using var text = new UnmanagedMemoryStream(pText, textLength);
var className = Marshal.PtrToStringUTF8((nint)pClassName)!;

try
{
// Parse the source into a SyntaxTree and use NamespaceSyntaxWalker
// to determine which namespace the requested class is located in.
var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(text));
var walker = new NamespaceSyntaxWalker(className);
walker.Visit(tree.GetRoot());

// Now we either have a namespace or an empty string.
var namespaceName = Marshal.StringToCoTaskMemUTF8(walker.Namespace);

try
{
callback((byte*)namespaceName, state);
}
finally
{
Marshal.FreeCoTaskMem(namespaceName);
}
}
catch (Exception ex)
{
return Marshal.GetHRForException(ex);
}

return 0;
}
}
12 changes: 12 additions & 0 deletions CSharpHelper/CSharpHelper.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
</ItemGroup>
</Project>
5 changes: 5 additions & 0 deletions CSharpHelper/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<UseArtifactsOutput>true</UseArtifactsOutput>
</PropertyGroup>
</Project>
44 changes: 44 additions & 0 deletions CSharpHelper/NamespaceSyntaxWalker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

/// <summary>A <see cref="CSharpSyntaxWalker"/> implementation for locating the namespace of a class.</summary>
public sealed class NamespaceSyntaxWalker : CSharpSyntaxWalker
{
/// <summary>The name of the class to locate the namespace of.</summary>
public string ClassName { get; }

/// <summary>The namespace of the requested class.</summary>
public string Namespace { get; private set; }

/// <summary>Initializes a new <see cref="NamespaceSyntaxWalker"/> instance.</summary>
/// <param name="className"></param>
public NamespaceSyntaxWalker(string className)
{
Namespace = string.Empty;
ClassName = className;
}

/// <inheritdoc/>
public override void Visit(SyntaxNode? node)
{
if (node is TypeDeclarationSyntax type && type.Identifier.ValueText == ClassName)
{
for (var parent = node.Parent; parent != null; parent = parent.Parent)
{
// We don't want a nested type that happens to share its name
// with the source file to result in a false-positive here.
if (parent is TypeDeclarationSyntax)
break;

if (parent is BaseNamespaceDeclarationSyntax ns)
{
Namespace = ns.Name.ToString();
return;
}
}
}

base.Visit(node);
}
}
2 changes: 2 additions & 0 deletions CSharpHelper/build-windows.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@echo off
dotnet publish -c Release -r win-x64 -p:PublishAot=true -p:DebugType=none
20 changes: 20 additions & 0 deletions NamespacePatch/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.20)
project(UnityNamespacePatch)

# Find Microsoft Detours
find_path(DETOURS_INCLUDE_DIRS detours.h PATH_SUFFIXES detours REQUIRED)
find_library(DETOURS_LIBRARIES detours REQUIRED)

# Find source files
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "source/*.cpp")
file(GLOB_RECURSE INCLUDES CONFIGURE_DEPENDS "include/*.h")

# Initialize target
set(TARGET_NAME "version")
add_library(${TARGET_NAME} SHARED ${SOURCES} ${INCLUDES})
target_include_directories(${TARGET_NAME} PRIVATE include ${DETOURS_INCLUDE_DIRS})
target_link_directories(${TARGET_NAME} PRIVATE libraries)
target_link_libraries(${TARGET_NAME} PRIVATE delayimp dbghelp pathcch Unity CSharpHelper ${DETOURS_LIBRARIES})

# Enable delay loading for Unity
target_link_options(${TARGET_NAME} PRIVATE "/DELAYLOAD:Unity.exe")
2 changes: 2 additions & 0 deletions NamespacePatch/generate_cmake.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@echo off
cmake -G "Visual Studio 17 2022" -A x64 -B build -DCMAKE_TOOLCHAIN_FILE="%VCPKG_ROOT%\scripts\buildsystems\vcpkg.cmake"
6 changes: 6 additions & 0 deletions NamespacePatch/generate_lib.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@echo off
pushd libraries
for %%f in (*.def) do (
lib /MACHINE:X64 /DEF:%%~nxf /OUT:%%~nf.lib
)
popd
4 changes: 4 additions & 0 deletions NamespacePatch/include/CSharpHelper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#pragma once
#include <Windows.h>

extern "C" HRESULT GetClassNamespace(const char* text, int textLen, const char* className, void* state, void(*callback)(const char*, void*));
8 changes: 8 additions & 0 deletions NamespacePatch/include/CSharpParser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#pragma once
#include "basic_string.h"
#include "dynamic_array.h"

namespace CSharpParser
{
core::string GetClassAndNamespace(const core::string& text, core::string& className, core::string& namespaceName, int& errorLine, const dynamic_array<core::string>* defines);
};
17 changes: 17 additions & 0 deletions NamespacePatch/include/MemLabelId.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once

enum MemLabelIdentifier
{
};

struct AllocationRootWithSalt
{
unsigned m_Salt;
unsigned m_RootReferenceIndex;
};

struct MemLabelId
{
AllocationRootWithSalt m_RootReferenceWithSalt;
MemLabelIdentifier identifier;
};
25 changes: 25 additions & 0 deletions NamespacePatch/include/StringStorageDefault.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once
#include "MemLabelId.h"

namespace core
{
template<class T>
class StringStorageDefault
{
enum
{
kInternalBufferCapacity = 0xF,
};
protected:
T* m_data;

union
{
size_t m_capacity;
T m_internal[(kInternalBufferCapacity + 1) / sizeof(T)];
};

size_t m_size;
MemLabelId m_label;
};
};
4 changes: 4 additions & 0 deletions NamespacePatch/include/SymbolHandler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#pragma once

void InitSymbolHandler();
void ReleaseSymbolHandler();
20 changes: 20 additions & 0 deletions NamespacePatch/include/basic_string.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once
#include "MemLabelId.h"
#include "StringStorageDefault.h"

namespace core
{
template<class T, class TStorage = StringStorageDefault<T>>
class basic_string : TStorage
{
public:
basic_string() = default;
const T* c_str() const;
size_t length() const;
basic_string& operator=(const T* copyStr);
static basic_string create_from_external(const T* referenceStr, const MemLabelId& label);
static basic_string create_from_external(const T* referenceStr, size_t referenceStrLen, const MemLabelId& label);
};

using string = basic_string<char>;
};
15 changes: 15 additions & 0 deletions NamespacePatch/include/dynamic_array.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once
#include "MemLabelId.h"

struct dynamic_array_detail
{
void* m_ptr;
MemLabelId m_label;
size_t m_size;
size_t m_capacity;
};

template<typename T, int = 0>
struct dynamic_array : dynamic_array_detail
{
};
3 changes: 3 additions & 0 deletions NamespacePatch/libraries/CSharpHelper.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
NAME "CSharpHelper.dll"
EXPORTS
GetClassNamespace
Binary file added NamespacePatch/libraries/CSharpHelper.lib
Binary file not shown.
10 changes: 10 additions & 0 deletions NamespacePatch/libraries/Unity.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
NAME "Unity.exe"
EXPORTS
?GetClassAndNamespace@CSharpParser@@YA?AV?$basic_string@DV?$StringStorageDefault@D@core@@@core@@AEBV23@AEAV23@1AEAHPEBU?$dynamic_array@V?$basic_string@DV?$StringStorageDefault@D@core@@@core@@$0A@@@@Z
??$?0PEBD@?$basic_string@DV?$StringStorageDefault@D@core@@@core@@QEAA@PEBD0AEBUMemLabelId@@@Z
??4?$basic_string@DV?$StringStorageDefault@D@core@@@core@@QEAAAEAV01@PEBD@Z
?c_str@?$basic_string@DV?$StringStorageDefault@D@core@@@core@@QEBAPEBDXZ
?create_from_external@?$basic_string@DV?$StringStorageDefault@D@core@@@core@@SA?AV12@PEBD_KAEBUMemLabelId@@@Z
?create_from_external@?$basic_string@DV?$StringStorageDefault@D@core@@@core@@SA?AV12@PEBDAEBUMemLabelId@@@Z
?length@?$basic_string@DV?$StringStorageDefault@D@core@@@core@@QEBA_KXZ
?kMemString@@3UMemLabelId@@A
Binary file added NamespacePatch/libraries/Unity.lib
Binary file not shown.
33 changes: 33 additions & 0 deletions NamespacePatch/source/DliNotifyHook.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include <Windows.h>
#include <DbgHelp.h>
#include <delayimp.h>
#include "SymbolHandler.h"

FARPROC WINAPI DliNotifyHook(unsigned dliNotify, PDelayLoadInfo pdli)
{
switch (dliNotify)
{
case dliNoteStartProcessing:
InitSymbolHandler();
break;
case dliNotePreGetProcAddress:
if (pdli->dlp.fImportByName)
{
SYMBOL_INFO Symbol{};
Symbol.SizeOfStruct = sizeof(Symbol);

if (SymFromName(GetCurrentProcess(), pdli->dlp.szProcName, &Symbol))
{
return (FARPROC)Symbol.Address;
}
}
break;
case dliNoteEndProcessing:
ReleaseSymbolHandler();
break;
}

return nullptr;
}

ExternC const PfnDliHook __pfnDliNotifyHook2 = DliNotifyHook;
26 changes: 26 additions & 0 deletions NamespacePatch/source/SymbolHandler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#include <Windows.h>
#include <DbgHelp.h>
#include <PathCch.h>
#include "SymbolHandler.h"

static ULONG g_SymbolHandlerRef = 0;

void InitSymbolHandler()
{
if (InterlockedIncrement(&g_SymbolHandlerRef) == 1)
{
WCHAR UserSearchPath[MAX_PATH];
GetModuleFileNameW(GetModuleHandle(NULL), UserSearchPath, MAX_PATH);
PathCchRemoveFileSpec(UserSearchPath, MAX_PATH);
SymSetOptions(SYMOPT_IGNORE_CVREC);
SymInitializeW(GetCurrentProcess(), UserSearchPath, TRUE);
}
}

void ReleaseSymbolHandler()
{
if (InterlockedDecrement(&g_SymbolHandlerRef) == 0)
{
SymCleanup(GetCurrentProcess());
}
}
47 changes: 47 additions & 0 deletions NamespacePatch/source/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include <Windows.h>
#include <DbgHelp.h>
#include <detours.h>
#include "CSharpHelper.h"
#include "CSharpParser.h"
#include "SymbolHandler.h"
#include <string>

BOOL AttachGetClassAndNamespaceDetour()
{
static decltype(&CSharpParser::GetClassAndNamespace) Pointer;
static decltype(Pointer) Detour = [](const core::string& text, core::string& className, core::string& namespaceName, int& errorLine, const dynamic_array<core::string> *defines)
{
errorLine = -1;

GetClassNamespace(text.c_str(), text.length(), className.c_str(), &namespaceName, [](auto namespaceName, auto state)
{
*(core::string*)state = namespaceName;
});

// Return value is an error message string.
return core::string{};
};

SYMBOL_INFO Symbol{};
Symbol.SizeOfStruct = sizeof(Symbol);

if (!SymFromName(GetCurrentProcess(), "?GetClassAndNamespace@CSharpParser@@YA?AV?$basic_string@DV?$StringStorageDefault@D@core@@@core@@AEBV23@AEAV23@1AEAHPEBU?$dynamic_array@V?$basic_string@DV?$StringStorageDefault@D@core@@@core@@$0A@@@@Z", &Symbol))
return FALSE;

*(ULONG64*)&Pointer = Symbol.Address;
DetourAttach((void**)&Pointer, Detour);
return TRUE;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
if (fdwReason != DLL_PROCESS_ATTACH)
return TRUE;

InitSymbolHandler();
DetourTransactionBegin();
AttachGetClassAndNamespaceDetour();
DetourTransactionCommit();
ReleaseSymbolHandler();
return TRUE;
}
17 changes: 17 additions & 0 deletions NamespacePatch/source/version.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma comment(linker, "/export:GetFileVersionInfoA=C:\\Windows\\System32\\version.GetFileVersionInfoA")
#pragma comment(linker, "/export:GetFileVersionInfoByHandle=C:\\Windows\\System32\\version.GetFileVersionInfoByHandle")
#pragma comment(linker, "/export:GetFileVersionInfoExA=C:\\Windows\\System32\\version.GetFileVersionInfoExA")
#pragma comment(linker, "/export:GetFileVersionInfoExW=C:\\Windows\\System32\\version.GetFileVersionInfoExW")
#pragma comment(linker, "/export:GetFileVersionInfoSizeA=C:\\Windows\\System32\\version.GetFileVersionInfoSizeA")
#pragma comment(linker, "/export:GetFileVersionInfoSizeExA=C:\\Windows\\System32\\version.GetFileVersionInfoSizeExA")
#pragma comment(linker, "/export:GetFileVersionInfoSizeExW=C:\\Windows\\System32\\version.GetFileVersionInfoSizeExW")
#pragma comment(linker, "/export:GetFileVersionInfoSizeW=C:\\Windows\\System32\\version.GetFileVersionInfoSizeW")
#pragma comment(linker, "/export:GetFileVersionInfoW=C:\\Windows\\System32\\version.GetFileVersionInfoW")
#pragma comment(linker, "/export:VerFindFileA=C:\\Windows\\System32\\version.VerFindFileA")
#pragma comment(linker, "/export:VerFindFileW=C:\\Windows\\System32\\version.VerFindFileW")
#pragma comment(linker, "/export:VerInstallFileA=C:\\Windows\\System32\\version.VerInstallFileA")
#pragma comment(linker, "/export:VerInstallFileW=C:\\Windows\\System32\\version.VerInstallFileW")
#pragma comment(linker, "/export:VerLanguageNameA=C:\\Windows\\System32\\version.VerLanguageNameA")
#pragma comment(linker, "/export:VerLanguageNameW=C:\\Windows\\System32\\version.VerLanguageNameW")
#pragma comment(linker, "/export:VerQueryValueA=C:\\Windows\\System32\\version.VerQueryValueA")
#pragma comment(linker, "/export:VerQueryValueW=C:\\Windows\\System32\\version.VerQueryValueW")
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Unity Namespace Patch
An experimental patch for Unity 2021.x that adds support for file-scoped namespaces.

# Installation
Copy `version.dll` and `CSharpHelper.dll` into the Unity editor folder (next to `Unity.exe`).

0 comments on commit 59a9538

Please sign in to comment.