-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDecompilerService.cs
More file actions
215 lines (196 loc) · 7.55 KB
/
Copy pathDecompilerService.cs
File metadata and controls
215 lines (196 loc) · 7.55 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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
using System.Diagnostics;
using System.Text;
namespace LuaDecompilerDesktop;
internal readonly record struct FileDecompileResult(
bool Success,
string Error,
int CharacterCount,
int ConvertedSegments);
internal sealed class DecompilerService
{
public string JavaPath { get; set; } = "java";
public string JarPath { get; set; }
public string OpcodeMapPath { get; set; }
public DecompilerService()
{
JarPath = Path.Combine(AppContext.BaseDirectory, "third_party", "unluac.jar");
OpcodeMapPath = Path.Combine(AppContext.BaseDirectory, "third_party", "lua53.opmap");
}
public async Task<FileDecompileResult> DecompileToFileAsync(
string inputPath,
string outputPath,
CancellationToken cancellationToken)
{
if (!File.Exists(JarPath))
return new(false, $"找不到反编译引擎:{JarPath}", 0, 0);
string? normalizedPath = null;
Process? process = null;
try
{
normalizedPath = NormalizeChunkHeader(inputPath);
var engineInput = normalizedPath ?? inputPath;
var startInfo = CreateStartInfo(engineInput, normalizedPath is not null);
process = new Process { StartInfo = startInfo };
process.Start();
var stderrTask = process.StandardError.ReadToEndAsync();
long characterCount = 0;
var convertedSegments = 0;
await using (var writer = new StreamWriter(outputPath, false, new UTF8Encoding(false), 64 * 1024))
{
writer.NewLine = "\r\n";
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
var line = await process.StandardOutput.ReadLineAsync()
.WaitAsync(cancellationToken)
.ConfigureAwait(false);
if (line is null) break;
var decoded = LuaStringDecoder.DecodeSuspectedChinese(line, cancellationToken);
await writer.WriteLineAsync(decoded.Text)
.WaitAsync(cancellationToken)
.ConfigureAwait(false);
characterCount += decoded.Text.Length + writer.NewLine.Length;
convertedSegments += decoded.ConvertedSegments;
}
}
await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
var stderr = await stderrTask.ConfigureAwait(false);
if (process.ExitCode != 0 || characterCount == 0)
{
TryDeleteFile(outputPath);
var error = string.IsNullOrWhiteSpace(stderr)
? $"引擎退出码:{process.ExitCode}"
: stderr.Trim();
return new(false, error, 0, 0);
}
return new(true, stderr.Trim(), (int)Math.Min(int.MaxValue, characterCount), convertedSegments);
}
catch (OperationCanceledException)
{
TryKill(process);
TryDeleteFile(outputPath);
throw;
}
catch (Exception ex)
{
TryKill(process);
TryDeleteFile(outputPath);
return new(false, $"无法启动或读取反编译引擎:{ex.Message}", 0, 0);
}
finally
{
process?.Dispose();
if (normalizedPath is not null) TryDeleteFile(normalizedPath);
}
}
public async Task<(bool Success, string Output, string Error)> DecompileAsync(
string inputPath, CancellationToken cancellationToken)
{
if (!File.Exists(JarPath))
return (false, "", $"找不到反编译引擎:{JarPath}");
string? normalizedPath = null;
var engineInput = inputPath;
try
{
normalizedPath = NormalizeChunkHeader(inputPath);
if (normalizedPath is not null) engineInput = normalizedPath;
}
catch (Exception ex)
{
return (false, "", $"无法创建兼容副本:{ex.Message}");
}
var startInfo = CreateStartInfo(engineInput, normalizedPath is not null);
try
{
using var process = new Process { StartInfo = startInfo };
process.Start();
var stdoutTask = process.StandardOutput.ReadToEndAsync();
var stderrTask = process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync(cancellationToken);
var stdout = await stdoutTask;
var stderr = await stderrTask;
return process.ExitCode == 0 && !string.IsNullOrWhiteSpace(stdout)
? (true, stdout, stderr)
: (false, stdout, string.IsNullOrWhiteSpace(stderr) ? $"引擎退出码:{process.ExitCode}" : stderr.Trim());
}
catch (OperationCanceledException)
{
return (false, "", "用户已取消");
}
catch (Exception ex)
{
return (false, "", $"无法启动反编译引擎:{ex.Message}");
}
finally
{
if (normalizedPath is not null)
{
try { File.Delete(normalizedPath); }
catch { /* 临时文件会由系统清理。 */ }
}
}
}
private static string? NormalizeChunkHeader(string inputPath)
{
var source = File.ReadAllBytes(inputPath);
if (source.Length < 17 || source[0] != 0x1B || source[1] != (byte)'L' ||
source[2] != (byte)'u' || source[3] != (byte)'a' || source[4] != 0x53 ||
source[5] != 1 || source[12] != 4 || source[13] != 4 ||
source[14] != 8 || source[15] != 8 || source[16] != 0x78)
return null;
// Normalize a known non-standard Lua 5.3 header layout for unluac.
// The input file is never modified; conversion uses a temporary copy.
var normalized = new byte[source.Length + 1];
Buffer.BlockCopy(source, 0, normalized, 0, 14);
normalized[5] = 0;
normalized[14] = 4;
Buffer.BlockCopy(source, 14, normalized, 15, source.Length - 14);
var tempPath = Path.Combine(Path.GetTempPath(), $"lua-decompiler-{Guid.NewGuid():N}.luac");
File.WriteAllBytes(tempPath, normalized);
return tempPath;
}
private ProcessStartInfo CreateStartInfo(string engineInput, bool useOpcodeMap)
{
var startInfo = new ProcessStartInfo
{
FileName = JavaPath,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
StandardOutputEncoding = new UTF8Encoding(false),
StandardErrorEncoding = new UTF8Encoding(false)
};
startInfo.ArgumentList.Add("-jar");
startInfo.ArgumentList.Add(JarPath);
if (useOpcodeMap && File.Exists(OpcodeMapPath))
{
startInfo.ArgumentList.Add("--opmap");
startInfo.ArgumentList.Add(OpcodeMapPath);
}
startInfo.ArgumentList.Add(engineInput);
return startInfo;
}
private static void TryKill(Process? process)
{
try
{
if (process is not null && !process.HasExited) process.Kill(true);
}
catch
{
// 进程可能已自然退出。
}
}
private static void TryDeleteFile(string path)
{
try
{
if (File.Exists(path)) File.Delete(path);
}
catch
{
// 临时文件稍后由会话目录统一清理。
}
}
}