Skip to content

Commit 22c5cc4

Browse files
committed
第一个稳定的V2实现
1 parent 63cb8f4 commit 22c5cc4

File tree

149 files changed

+9334
-189
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

149 files changed

+9334
-189
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*.exe
2+
*.exe.meta
3+
.vs
4+
obj
5+
Tools/net6.0/ProxyLinker.meta
6+
Tools/net6.0/ProxyLinker

.npmignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
VSProj~
2+
.github
3+
.exe
4+
README.md
5+
Documentation

CliTool~/CliTool.sln

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.6.33829.357
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProxyLinker", "ProxyLinker.csproj", "{3A40235B-D23C-4A82-86C6-C276031777E2}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{3A40235B-D23C-4A82-86C6-C276031777E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{3A40235B-D23C-4A82-86C6-C276031777E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{3A40235B-D23C-4A82-86C6-C276031777E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{3A40235B-D23C-4A82-86C6-C276031777E2}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {963788C5-6AE0-4620-89E1-B9EEF87C64AC}
24+
EndGlobalSection
25+
EndGlobal

CliTool~/Common.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System.Reflection;
2+
3+
namespace BBBirder.UnityInjection.Tools;
4+
5+
using static ToolsConstants;
6+
public static class Common
7+
{
8+
static string[]? s_allowedAssemblies;
9+
10+
public static void Init(string[] allowedAssemblies)
11+
{
12+
s_allowedAssemblies = allowedAssemblies;
13+
AppDomain.CurrentDomain.AssemblyResolve += (s, args) =>
14+
{
15+
var assemblyName = new AssemblyName(args.Name)?.Name;
16+
if (assemblyName == null)
17+
{
18+
throw new ArgumentException($"cannot resolve assembly {assemblyName}");
19+
}
20+
return LoadAssembly(assemblyName);
21+
};
22+
}
23+
24+
public static void Assert(bool condition, string message)
25+
{
26+
if (!condition) throw new Exception(message);
27+
}
28+
29+
static Assembly LoadAssembly(string name)
30+
{
31+
var assembly = AppDomain.CurrentDomain.GetAssemblies()
32+
.FirstOrDefault(a => a.GetName().Name == name);
33+
if (assembly != null) return assembly;
34+
var fileName = name + ".dll";
35+
36+
// directly reside in allowed assemblies
37+
var fullPath = s_allowedAssemblies!.FirstOrDefault(a => Path.GetFileNameWithoutExtension(a) == name);
38+
39+
// reside in save path of an allowed assembly
40+
if (fullPath == null)
41+
{
42+
43+
foreach (var p in s_allowedAssemblies!.Select(Path.GetDirectoryName).Distinct())
44+
{
45+
var filePath = Path.Combine(p, fileName);
46+
if (File.Exists(filePath))
47+
{
48+
fullPath = filePath;
49+
break;
50+
}
51+
}
52+
}
53+
54+
// reside in ScriptAssemblies
55+
if (fullPath == null)
56+
{
57+
fullPath = Path.Combine("Library\\ScriptAssemblies", fileName);
58+
if (!File.Exists(fullPath)) fullPath = null;
59+
}
60+
61+
if (fullPath != null) return Assembly.Load(File.ReadAllBytes(fullPath));
62+
63+
throw new ArgumentException($"cannot resolve assembly {name}");
64+
}
65+
}

CliTool~/ProxyLinker.csproj

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net6.0</TargetFramework>
4+
<ImplicitUsings>enable</ImplicitUsings>
5+
<Nullable>enable</Nullable>
6+
<StartupObject>BBBirder.UnityInjection.Tools.ProxyLinkerProgram</StartupObject>
7+
<OutputType>Exe</OutputType>
8+
<OutputPath>../Tools</OutputPath>
9+
<EnableDefaultItems>false</EnableDefaultItems>
10+
</PropertyGroup>
11+
<ItemGroup>
12+
<Compile Include="Common.cs" />
13+
<Compile Include="ProxyLinker*.cs" />
14+
<Compile Include="../Editor/ToolsConstants.cs" />
15+
<Compile Include="../Runtime/Logger.cs" />
16+
</ItemGroup>
17+
</Project>

CliTool~/ProxyLinkerProgram.cs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System.IO.Pipes;
2+
using System.Text;
3+
using System.Text.RegularExpressions;
4+
5+
namespace BBBirder.UnityInjection.Tools;
6+
using static ToolsConstants;
7+
8+
public class ProxyLinkerProgram
9+
{
10+
record ArgumentData
11+
{
12+
public string outdir;
13+
public string[] allowedAssemblies;
14+
}
15+
16+
static void ParseArguments(string[] args, out ArgumentData argumentData)
17+
{
18+
Common.Assert(args.Length >= 1, "no argument provided");
19+
string outdir = null;
20+
var allowedAssemblies = new List<string>();
21+
if (args[0].StartsWith('@'))
22+
{
23+
var path = args[0].Trim('@');
24+
var content = File.ReadAllText(path);
25+
var matches = Regex.Matches(content, @"([^ =]+)=?(""[^""]*""|[^ ""]*)");
26+
Common.Assert(matches != null, "not a valid arguments file");
27+
28+
for (int i = 0; i < matches!.Count; i++)
29+
{
30+
var match = matches[i]!;
31+
OnProcessArgument(match);
32+
}
33+
}
34+
else
35+
{
36+
foreach (var arg in args)
37+
{
38+
var match = Regex.Match(arg, @"([^ =]+)=?(""[^""]*""|[^ ""]*)");
39+
if (!match.Success)
40+
{
41+
continue;
42+
}
43+
OnProcessArgument(match);
44+
}
45+
}
46+
argumentData = new()
47+
{
48+
outdir = outdir,
49+
allowedAssemblies = allowedAssemblies.ToArray()
50+
};
51+
void OnProcessArgument(Match match)
52+
{
53+
var key = match.Groups[1].Value;
54+
var value = match.Groups[2].Value.Trim('\'', '\"');
55+
switch (key)
56+
{
57+
case "--out":
58+
outdir = value;
59+
break;
60+
case "--allowed-assembly":
61+
allowedAssemblies.Add(value);
62+
break;
63+
default:
64+
break;
65+
}
66+
}
67+
}
68+
public static int Main(string[] args)
69+
{
70+
ParseArguments(args, out var argumentData);
71+
var outDir = argumentData.outdir;
72+
Common.Assert(outDir != null, "no out argument");
73+
74+
using var client = new NamedPipeClientStream(LINKER_PIPE_NAME);
75+
client.Connect(8_000);
76+
if (!client.IsConnected)
77+
{
78+
return (int)ProxyLinkerResultCode.ConnectionTimeout;
79+
}
80+
var bytes = Encoding.UTF8.GetBytes(outDir!);
81+
foreach (var b in bytes)
82+
{
83+
client.WriteByte(b);
84+
}
85+
client.WriteByte(0);
86+
client.Flush();
87+
88+
return client.ReadByte();
89+
}
90+
}

Documentation.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Documentation/unity-dlls.png

-90.9 KB
Binary file not shown.

Documentation/usage-decorator.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# 装饰器
2+
3+
值得一提的是,装饰器的实现有以下优势:
4+
5+
* 调用是0GC,低开销的
6+
* 实现只需要指定一个`DecoratorAttribute`
7+
* 支持装饰异步函数
8+
9+
## 例子
10+
11+
下面的例子实现目标方法执行前和后打印信息
12+
13+
定义一个装饰器,需要继承自`DecoratorAttribute`,并实现`Decorate`方法
14+
15+
```csharp
16+
public class DebugInvocationAttribute:DecoratorAttribute
17+
{
18+
string info;
19+
20+
public DebugInvocationAttribute(string _info)
21+
{
22+
info = _info;
23+
}
24+
25+
void OnCompleted()
26+
{
27+
Debug.Log("end "+info);
28+
}
29+
30+
protected override R Decorate<R>(InvocationInfo<R> invocation)
31+
{
32+
Debug.Log("begin "+info+string.Join(",",invocation.Arguments));
33+
// invoke original method
34+
var r = invocation.FastInvoke();
35+
if(IsAsyncMethod)
36+
{
37+
// delay on async method
38+
invocation.GetAwaiter(r).OnCompleted(OnCompleted);
39+
}
40+
else
41+
{
42+
OnCompleted();
43+
}
44+
return r;
45+
}
46+
}
47+
48+
```
49+
50+
使用
51+
52+
```csharp
53+
54+
public class Demo:MonoBehaviour{
55+
56+
void Start(){
57+
FixHelper.InstallAll();
58+
}
59+
60+
async void Update(){
61+
if(Input.GetKeyDown(KeyCode.A)){
62+
Work(1,"foo");
63+
}
64+
if(Input.GetKeyDown(KeyCode.S)){
65+
await AsyncWork(2,"bar");
66+
print("return");
67+
}
68+
}
69+
70+
//decorate a standard method
71+
[DebugInvocation("w1")]
72+
int Work(int i, string s){
73+
Debug.Log("do work");
74+
return 123+i;
75+
}
76+
77+
//decorate an async method
78+
[DebugInvocation("aw2")]
79+
async Task<int> AsyncWork(int i, string s){
80+
Debug.Log("do a lot work");
81+
await Task.Delay(1000);
82+
return 123+i;
83+
}
84+
}
85+
```
86+
87+
## 异步方法补充说明
88+
89+
async方法有一个值得斟酌的问题。如果上例的Task换成UniTask。则不会打印return,这是因为Decrote方法覆盖了原本的continuationAction(这与UniTask的实现有关)。使用如下Decorate方法可以解决所有此类问题:
90+
91+
```csharp
92+
protected override R Decorate<R>(InvocationInfo<R> invocation)
93+
{
94+
Debug.Log("begin "+info+string.Join(",",invocation.Arguments));
95+
var r = invocation.FastInvoke();
96+
if(IsAsyncMethod)
97+
{
98+
// delay when its an async method
99+
var awaiter = invocation.GetAwaiter(r);
100+
UniTask.Create(async()=>
101+
{
102+
try
103+
{
104+
while(!invocation.IsAwaiterCompleted(awaiter))
105+
await UniTask.Yield();
106+
}
107+
catch {}
108+
finally
109+
{
110+
OnCompleted();
111+
}
112+
});
113+
// invocation.GetAwaiter(r).OnCompleted(OnCompleted);
114+
}
115+
else
116+
{
117+
OnCompleted();
118+
}
119+
return r;
120+
}
121+
```
122+
123+
上例使用`UniTask.Create`创建了一个Timer,可以使用其他类似的方法,如自定义MonoBehaviour等。一旦`IsAwaiterCompleted`检查结束,立即执行自定义的`OnCompleted`方法。
124+
125+
因为Unity没有官方支持的Timer功能,基于“此库只做自己该做的”原则,这里只是给出提示。

Documentation/usage-decorator.md.meta

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)