mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2024-08-19 23:28:42 +02:00
* Avoid allocations in .Parse methods Use the Span overloads of the Parse methods when possible to avoid string allocations and remove one unnecessarry array allocation * Avoid another string allocation
275 lines
7.9 KiB
C#
275 lines
7.9 KiB
C#
using Ryujinx.Common.Logging;
|
|
using System;
|
|
using System.IO;
|
|
using System.Text;
|
|
|
|
namespace Ryujinx.HLE.Loaders.Mods
|
|
{
|
|
class IPSwitchPatcher
|
|
{
|
|
const string BidHeader = "@nsobid-";
|
|
|
|
private enum Token
|
|
{
|
|
Normal,
|
|
String,
|
|
EscapeChar,
|
|
Comment
|
|
}
|
|
|
|
private readonly StreamReader _reader;
|
|
public string BuildId { get; }
|
|
|
|
public IPSwitchPatcher(StreamReader reader)
|
|
{
|
|
string header = reader.ReadLine();
|
|
if (header == null || !header.StartsWith(BidHeader))
|
|
{
|
|
Logger.Error?.Print(LogClass.ModLoader, "IPSwitch: Malformed PCHTXT file. Skipping...");
|
|
|
|
return;
|
|
}
|
|
|
|
_reader = reader;
|
|
BuildId = header.Substring(BidHeader.Length).TrimEnd().TrimEnd('0');
|
|
}
|
|
|
|
// Uncomments line and unescapes C style strings within
|
|
private static string PreprocessLine(string line)
|
|
{
|
|
StringBuilder str = new StringBuilder();
|
|
Token state = Token.Normal;
|
|
|
|
for (int i = 0; i < line.Length; ++i)
|
|
{
|
|
char c = line[i];
|
|
char la = i + 1 != line.Length ? line[i + 1] : '\0';
|
|
|
|
switch (state)
|
|
{
|
|
case Token.Normal:
|
|
state = c == '"' ? Token.String :
|
|
c == '/' && la == '/' ? Token.Comment :
|
|
c == '/' && la != '/' ? Token.Comment : // Ignore error and stop parsing
|
|
Token.Normal;
|
|
break;
|
|
case Token.String:
|
|
state = c switch
|
|
{
|
|
'"' => Token.Normal,
|
|
'\\' => Token.EscapeChar,
|
|
_ => Token.String
|
|
};
|
|
break;
|
|
case Token.EscapeChar:
|
|
state = Token.String;
|
|
c = c switch
|
|
{
|
|
'a' => '\a',
|
|
'b' => '\b',
|
|
'f' => '\f',
|
|
'n' => '\n',
|
|
'r' => '\r',
|
|
't' => '\t',
|
|
'v' => '\v',
|
|
'\\' => '\\',
|
|
_ => '?'
|
|
};
|
|
break;
|
|
}
|
|
|
|
if (state == Token.Comment) break;
|
|
|
|
if (state < Token.EscapeChar)
|
|
{
|
|
str.Append(c);
|
|
}
|
|
}
|
|
|
|
return str.ToString().Trim();
|
|
}
|
|
|
|
static int ParseHexByte(byte c)
|
|
{
|
|
if (c >= '0' && c <= '9')
|
|
{
|
|
return c - '0';
|
|
}
|
|
else if (c >= 'A' && c <= 'F')
|
|
{
|
|
return c - 'A' + 10;
|
|
}
|
|
else if (c >= 'a' && c <= 'f')
|
|
{
|
|
return c - 'a' + 10;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Big Endian
|
|
static byte[] Hex2ByteArrayBE(string hexstr)
|
|
{
|
|
if ((hexstr.Length & 1) == 1) return null;
|
|
|
|
byte[] bytes = new byte[hexstr.Length >> 1];
|
|
|
|
for (int i = 0; i < hexstr.Length; i += 2)
|
|
{
|
|
int high = ParseHexByte((byte)hexstr[i]);
|
|
int low = ParseHexByte((byte)hexstr[i + 1]);
|
|
|
|
bytes[i >> 1] = (byte)((high << 4) | low);
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
// Auto base discovery
|
|
private static bool ParseInt(string str, out int value)
|
|
{
|
|
if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X'))
|
|
{
|
|
return int.TryParse(str.AsSpan(2), System.Globalization.NumberStyles.HexNumber, null, out value);
|
|
}
|
|
else
|
|
{
|
|
return int.TryParse(str, System.Globalization.NumberStyles.Integer, null, out value);
|
|
}
|
|
}
|
|
|
|
private MemPatch Parse()
|
|
{
|
|
if (_reader == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
MemPatch patches = new MemPatch();
|
|
|
|
bool enabled = false;
|
|
bool printValues = false;
|
|
int offset_shift = 0;
|
|
|
|
string line;
|
|
int lineNum = 0;
|
|
|
|
static void Print(string s) => Logger.Info?.Print(LogClass.ModLoader, $"IPSwitch: {s}");
|
|
|
|
void ParseWarn() => Logger.Warning?.Print(LogClass.ModLoader, $"IPSwitch: Parse error at line {lineNum} for bid={BuildId}");
|
|
|
|
while ((line = _reader.ReadLine()) != null)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(line))
|
|
{
|
|
enabled = false;
|
|
|
|
continue;
|
|
}
|
|
|
|
line = PreprocessLine(line);
|
|
lineNum += 1;
|
|
|
|
if (line.Length == 0)
|
|
{
|
|
continue;
|
|
}
|
|
else if (line.StartsWith('#'))
|
|
{
|
|
Print(line);
|
|
}
|
|
else if (line.StartsWith("@stop"))
|
|
{
|
|
break;
|
|
}
|
|
else if (line.StartsWith("@enabled"))
|
|
{
|
|
enabled = true;
|
|
}
|
|
else if (line.StartsWith("@disabled"))
|
|
{
|
|
enabled = false;
|
|
}
|
|
else if (line.StartsWith("@flag"))
|
|
{
|
|
var tokens = line.Split(' ', 3, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
if (tokens.Length < 2)
|
|
{
|
|
ParseWarn();
|
|
|
|
continue;
|
|
}
|
|
|
|
if (tokens[1] == "offset_shift")
|
|
{
|
|
if (tokens.Length != 3 || !ParseInt(tokens[2], out offset_shift))
|
|
{
|
|
ParseWarn();
|
|
|
|
continue;
|
|
}
|
|
}
|
|
else if (tokens[1] == "print_values")
|
|
{
|
|
printValues = true;
|
|
}
|
|
}
|
|
else if (line.StartsWith('@'))
|
|
{
|
|
// Ignore
|
|
}
|
|
else
|
|
{
|
|
if (!enabled)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var tokens = line.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
if (tokens.Length < 2)
|
|
{
|
|
ParseWarn();
|
|
|
|
continue;
|
|
}
|
|
|
|
if (!int.TryParse(tokens[0], System.Globalization.NumberStyles.HexNumber, null, out int offset))
|
|
{
|
|
ParseWarn();
|
|
|
|
continue;
|
|
}
|
|
|
|
offset += offset_shift;
|
|
|
|
if (printValues)
|
|
{
|
|
Print($"print_values 0x{offset:x} <= {tokens[1]}");
|
|
}
|
|
|
|
if (tokens[1][0] == '"')
|
|
{
|
|
var patch = Encoding.ASCII.GetBytes(tokens[1].Trim('"') + "\0");
|
|
patches.Add((uint)offset, patch);
|
|
}
|
|
else
|
|
{
|
|
var patch = Hex2ByteArrayBE(tokens[1]);
|
|
patches.Add((uint)offset, patch);
|
|
}
|
|
}
|
|
}
|
|
|
|
return patches;
|
|
}
|
|
|
|
public void AddPatches(MemPatch patches)
|
|
{
|
|
patches.AddFrom(Parse());
|
|
}
|
|
}
|
|
} |