-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLuaStringDecoder.cs
More file actions
185 lines (161 loc) · 5.25 KB
/
Copy pathLuaStringDecoder.cs
File metadata and controls
185 lines (161 loc) · 5.25 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
using System.Buffers;
using System.Text;
namespace LuaDecompilerDesktop;
internal readonly record struct LuaDecodeResult(string Text, int ConvertedSegments);
internal static class LuaStringDecoder
{
private static readonly Encoding StrictUtf8 = new UTF8Encoding(false, true);
private static readonly Encoding StrictChineseEncoding = CreateChineseEncoding();
public static LuaDecodeResult DecodeSuspectedChinese(
string source,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(source)) return new(source, 0);
var converted = 0;
StringBuilder? builder = null;
var copyFrom = 0;
for (var index = 0; index < source.Length;)
{
if ((index & 0xFFFF) == 0) cancellationToken.ThrowIfCancellationRequested();
if (!TryReadDecimalByteRun(source, index, out var runEnd, out var bytes))
{
index++;
continue;
}
var decoded = IsValidUtf8(bytes) ? StrictUtf8.GetString(bytes) : null;
decoded ??= TryDecode(StrictChineseEncoding, bytes);
if (decoded is null || !LooksLikeReadableEastAsianText(decoded))
{
index = runEnd;
continue;
}
builder ??= new StringBuilder(source.Length);
builder.Append(source, copyFrom, index - copyFrom);
builder.Append(EscapeForLuaString(decoded));
copyFrom = runEnd;
index = runEnd;
converted++;
}
if (builder is null) return new(source, 0);
builder.Append(source, copyFrom, source.Length - copyFrom);
return new(builder.ToString(), converted);
}
private static Encoding CreateChineseEncoding()
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
return Encoding.GetEncoding(
54936,
EncoderFallback.ExceptionFallback,
DecoderFallback.ExceptionFallback);
}
private static bool TryReadDecimalByteRun(
string source,
int start,
out int end,
out byte[] bytes)
{
end = start;
bytes = Array.Empty<byte>();
var cursor = start;
var count = 0;
while (TryReadDecimalByte(source, cursor, out _, out var next))
{
count++;
cursor = next;
}
if (count < 2) return false;
bytes = new byte[count];
cursor = start;
for (var index = 0; index < count; index++)
{
TryReadDecimalByte(source, cursor, out bytes[index], out cursor);
}
end = cursor;
return true;
}
private static bool TryReadDecimalByte(
string source,
int start,
out byte value,
out int next)
{
value = 0;
next = start;
if (start + 1 >= source.Length || source[start] != '\\' || !IsAsciiDigit(source[start + 1]))
return false;
var number = 0;
var cursor = start + 1;
var digits = 0;
while (cursor < source.Length && digits < 3 && IsAsciiDigit(source[cursor]))
{
number = number * 10 + source[cursor] - '0';
cursor++;
digits++;
}
if (number > byte.MaxValue) return false;
value = (byte)number;
next = cursor;
return true;
}
private static bool IsAsciiDigit(char character) => character is >= '0' and <= '9';
private static string? TryDecode(Encoding encoding, byte[] bytes)
{
try
{
return encoding.GetString(bytes);
}
catch (DecoderFallbackException)
{
return null;
}
}
private static bool IsValidUtf8(ReadOnlySpan<byte> bytes)
{
while (!bytes.IsEmpty)
{
var status = Rune.DecodeFromUtf8(bytes, out _, out var consumed);
if (status != OperationStatus.Done) return false;
bytes = bytes[consumed..];
}
return true;
}
private static bool LooksLikeReadableEastAsianText(string value)
{
var hasEastAsianCharacter = false;
foreach (var rune in value.EnumerateRunes())
{
var codePoint = rune.Value;
if (codePoint is >= 0x4E00 and <= 0x9FFF
or >= 0x3400 and <= 0x4DBF
or >= 0x3040 and <= 0x30FF
or >= 0xAC00 and <= 0xD7AF)
{
hasEastAsianCharacter = true;
}
if (Rune.IsControl(rune) && codePoint is not 9 and not 10 and not 13)
return false;
}
return hasEastAsianCharacter;
}
private static string EscapeForLuaString(string value)
{
var builder = new StringBuilder(value.Length);
foreach (var character in value)
{
builder.Append(character switch
{
'\\' => "\\\\",
'"' => "\\\"",
'\a' => "\\a",
'\b' => "\\b",
'\f' => "\\f",
'\n' => "\\n",
'\r' => "\\r",
'\t' => "\\t",
'\v' => "\\v",
_ => character.ToString()
});
}
return builder.ToString();
}
}