-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWinchCompatLayer.cs
More file actions
164 lines (140 loc) · 6.16 KB
/
WinchCompatLayer.cs
File metadata and controls
164 lines (140 loc) · 6.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Logging;
using HarmonyLib;
using Mono.Cecil;
using Logger = BepInEx.Logging.Logger;
namespace WinchCompatLayer;
// ReSharper disable once UnusedType.Global
internal static class WinchCompatLayer
{
private const string Id = "com.grahamkracker.abyss.winchcompatlayer";
private const string Name = "WinchCompatLayer";
// ReSharper disable once UnusedMember.Global
public static IEnumerable<string> TargetDLLs { get; } =
Array.Empty<string>(); // Needed in order to get recognized as a patcher
private static ManualLogSource _logger = null!;
private static ManualLogSource _winchLogger = null!;
private static Harmony _harmonyInstance = null!;
public static void Patch(AssemblyDefinition assemblyDefinition)
{
// Needed in order to get recognized as a patcher
}
// ReSharper disable once UnusedMember.Global
public static void Finish() //called by bie
{
AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
_logger = Logger.CreateLogSource(Name);
_winchLogger = Logger.CreateLogSource("Winch");
_harmonyInstance = new Harmony(Id);
_harmonyInstance.Patch(typeof(Chainloader).GetMethod(nameof(Chainloader.Initialize)),
postfix: new HarmonyMethod(AccessTools.Method(typeof(WinchCompatLayer), nameof(InitWinch))));
}
private static string WinchPath => Path.Combine(Paths.GameRootPath, "Winch");
private static void DownloadWinch()
{
var winchUrl = "https://github.com/DREDGE-Mods/Winch/releases/latest/download/Winch.zip";
if (File.Exists(Path.Combine(WinchPath, "Winch.dll")))
return;
try
{
using var webClient = new WebClient();
if (Directory.Exists(WinchPath))
Directory.Delete(WinchPath, true);
Directory.CreateDirectory(WinchPath);
webClient.DownloadFile(winchUrl, Path.Combine(WinchPath, "Winch.zip"));
System.IO.Compression.ZipFile.ExtractToDirectory(Path.Combine(WinchPath, "Winch.zip"), WinchPath);
File.Delete(Path.Combine(WinchPath, "Winch.zip"));
var releaseDir = Path.Combine(WinchPath, "Release");
//move all files and folders from Release to Winch
foreach (var file in Directory.GetFiles(releaseDir))
{
File.Move(file, Path.Combine(WinchPath, Path.GetFileName(file)));
}
foreach (var dir in Directory.GetDirectories(releaseDir))
{
Directory.Move(dir, Path.Combine(WinchPath, Path.GetFileName(dir)));
}
Directory.Delete(releaseDir);
}
catch (Exception e)
{
_logger.LogWarning($"Failed to download winch.zip from {winchUrl}.");
if (e is WebException webException)
{
_logger.LogError(webException.Message);
_logger.LogError(webException.StackTrace);
}
else
{
_logger.LogError(e);
}
}
}
private static void InitWinch()
{
DownloadWinch();
var winchAsm = Assembly.LoadFile(Path.Combine(Paths.GameRootPath, "Winch", "Winch.dll"));
var logLevelType = winchAsm.GetType("Winch.Logging.Logger").GetField("_minLogLevel", BindingFlags.NonPublic | BindingFlags.Instance).FieldType.GenericTypeArguments[0];
_harmonyInstance.Patch(AccessTools.Method(winchAsm.GetType("Winch.Logging.Logger"), "Log", [
logLevelType, typeof(string), typeof(string)
]), postfix: new HarmonyMethod(typeof(WinchCompatLayer).GetMethod(nameof(WinchMessageLogged))));
winchAsm.GetType("Winch.Core.WinchCore").GetMethod("Main")!.Invoke(null, null);
}
public static void WinchMessageLogged(dynamic level, string message, string source)
{
var logMessage = $"[{level}] : {message}";
switch ((int) level)
{
case 2 /*Winch.LogLevel.INFO*/:
_winchLogger.LogInfo(logMessage);
break;
case 1 /*Winch.LogLevel.DEBUG*/:
_winchLogger.LogInfo(logMessage);
break;
case 3 /*Winch.LogLevel.WARN*/:
_winchLogger.LogWarning(logMessage);
break;
case 4 /*Winch.LogLevel.ERROR*/:
_winchLogger.LogError(logMessage);
break;
case 0 /*Winch.LogLevel.UNITY*/:
_winchLogger.LogInfo(logMessage);
break;
default:
_winchLogger.LogMessage(logMessage);
break;
}
}
private static Assembly LocalResolve(object sender, ResolveEventArgs args)
{
if (!Utility.TryParseAssemblyName(args.Name, out var assemblyName))
return null;
// Use parse assembly name on managed side because native GetName() can fail on some locales
// if the game path has "exotic" characters
var validAssemblies = AppDomain.CurrentDomain
.GetAssemblies()
.Select(a => new
{ assembly = a, name = Utility.TryParseAssemblyName(a.FullName, out var name) ? name : null })
.Where(a => a.name != null && a.name.Name == assemblyName.Name)
.OrderByDescending(a => a.name.Version)
.ToList();
// First try to match by version, then just pick the best match (generally highest)
// This should mainly affect cases where the game itself loads some assembly (like Mono.Cecil)
var foundMatch = validAssemblies.Find(a => a.name.Version == assemblyName.Version) ??
validAssemblies.FirstOrDefault();
var foundAssembly = foundMatch?.assembly;
if (foundAssembly != null)
return foundAssembly;
if (Utility.TryResolveDllAssembly(assemblyName, WinchPath, out foundAssembly)
|| Utility.TryResolveDllAssembly(assemblyName, Path.Combine(Paths.GameRootPath, "Mods"), out foundAssembly))
return foundAssembly;
return null;
}
}