hle: Improve safety (#2778)

* timezone: Make timezone implementation safe

* hle: Do not use TrimEnd to parse ASCII strings

This adds an util that handle reading an ASCII string in a safe way.
Previously it was possible to read malformed data that could cause
various undefined behaviours in multiple services.

* hid: Remove an useless unsafe modifier on keyboard update

* Address gdkchan's comment

* Address gdkchan's comment
This commit is contained in:
Mary 2021-10-25 00:13:20 +02:00 committed by GitHub
parent b4dc33efc2
commit 51fa1b2cb0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 141 additions and 172 deletions

View file

@ -1,3 +1,4 @@
using Ryujinx.HLE.Utilities;
using System.IO; using System.IO;
using System.Text; using System.Text;
@ -30,10 +31,10 @@ namespace Ryujinx.HLE.FileSystem.Content
reader.ReadBytes(2); // Padding reader.ReadBytes(2); // Padding
PlatformString = Encoding.ASCII.GetString(reader.ReadBytes(0x20)).TrimEnd('\0'); PlatformString = StringUtils.ReadInlinedAsciiString(reader, 0x20);
Hex = Encoding.ASCII.GetString(reader.ReadBytes(0x40)).TrimEnd('\0'); Hex = StringUtils.ReadInlinedAsciiString(reader, 0x40);
VersionString = Encoding.ASCII.GetString(reader.ReadBytes(0x18)).TrimEnd('\0'); VersionString = StringUtils.ReadInlinedAsciiString(reader, 0x18);
VersionTitle = Encoding.ASCII.GetString(reader.ReadBytes(0x80)).TrimEnd('\0'); VersionTitle = StringUtils.ReadInlinedAsciiString(reader, 0x80);
} }
} }
} }

View file

@ -8,7 +8,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
{ {
public KeyboardDevice(Switch device, bool active) : base(device, active) { } public KeyboardDevice(Switch device, bool active) : base(device, active) { }
public unsafe void Update(KeyboardInput keyState) public void Update(KeyboardInput keyState)
{ {
ref RingLifo<KeyboardState> lifo = ref _device.Hid.SharedMemory.Keyboard; ref RingLifo<KeyboardState> lifo = ref _device.Hid.SharedMemory.Keyboard;

View file

@ -218,11 +218,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres
private ResultCode GetHostByNameRequestImpl(ServiceCtx context, ulong inputBufferPosition, ulong inputBufferSize, ulong outputBufferPosition, ulong outputBufferSize, ulong optionsBufferPosition, ulong optionsBufferSize) private ResultCode GetHostByNameRequestImpl(ServiceCtx context, ulong inputBufferPosition, ulong inputBufferSize, ulong outputBufferPosition, ulong outputBufferSize, ulong optionsBufferPosition, ulong optionsBufferSize)
{ {
byte[] rawName = new byte[inputBufferSize]; string name = MemoryHelper.ReadAsciiString(context.Memory, inputBufferPosition, (int)inputBufferSize);
context.Memory.Read(inputBufferPosition, rawName);
string name = Encoding.ASCII.GetString(rawName).TrimEnd('\0');
// TODO: Use params. // TODO: Use params.
bool enableNsdResolve = (context.RequestData.ReadInt32() & 1) != 0; bool enableNsdResolve = (context.RequestData.ReadInt32() & 1) != 0;

View file

@ -116,7 +116,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
// SetupTimeZoneManager(nn::time::LocationName location_name, nn::time::SteadyClockTimePoint timezone_update_timepoint, u32 total_location_name_count, nn::time::TimeZoneRuleVersion timezone_rule_version, buffer<nn::time::TimeZoneBinary, 0x21> timezone_binary) // SetupTimeZoneManager(nn::time::LocationName location_name, nn::time::SteadyClockTimePoint timezone_update_timepoint, u32 total_location_name_count, nn::time::TimeZoneRuleVersion timezone_rule_version, buffer<nn::time::TimeZoneBinary, 0x21> timezone_binary)
public ResultCode SetupTimeZoneManager(ServiceCtx context) public ResultCode SetupTimeZoneManager(ServiceCtx context)
{ {
string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0'); string locationName = StringUtils.ReadInlinedAsciiString(context.RequestData, 0x24);
SteadyClockTimePoint timeZoneUpdateTimePoint = context.RequestData.ReadStruct<SteadyClockTimePoint>(); SteadyClockTimePoint timeZoneUpdateTimePoint = context.RequestData.ReadStruct<SteadyClockTimePoint>();
uint totalLocationNameCount = context.RequestData.ReadUInt32(); uint totalLocationNameCount = context.RequestData.ReadUInt32();
UInt128 timeZoneRuleVersion = context.RequestData.ReadStruct<UInt128>(); UInt128 timeZoneRuleVersion = context.RequestData.ReadStruct<UInt128>();

View file

@ -1,6 +1,7 @@
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Cpu; using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Services.Time.TimeZone; using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using Ryujinx.HLE.Utilities;
using System; using System;
using System.Text; using System.Text;
@ -35,7 +36,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
return ResultCode.PermissionDenied; return ResultCode.PermissionDenied;
} }
string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0'); string locationName = StringUtils.ReadInlinedAsciiString(context.RequestData, 0x24);
return _timeZoneContentManager.SetDeviceLocationName(locationName); return _timeZoneContentManager.SetDeviceLocationName(locationName);
} }
@ -97,7 +98,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0'); string locationName = StringUtils.ReadInlinedAsciiString(context.RequestData, 0x24);
ResultCode resultCode = _timeZoneContentManager.LoadTimeZoneRule(out TimeZoneRule rules, locationName); ResultCode resultCode = _timeZoneContentManager.LoadTimeZoneRule(out TimeZoneRule rules, locationName);

View file

@ -125,7 +125,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
(ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(); (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21();
string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0'); string locationName = StringUtils.ReadInlinedAsciiString(context.RequestData, 0x24);
ResultCode result; ResultCode result;

View file

@ -2,6 +2,7 @@
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.HLE.Utilities; using Ryujinx.HLE.Utilities;
using System; using System;
using System.Buffers.Binary;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
@ -107,40 +108,24 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
public int TransitionTime; public int TransitionTime;
} }
private static int Detzcode32(byte[] bytes) private static int Detzcode32(ReadOnlySpan<byte> bytes)
{ {
if (BitConverter.IsLittleEndian) return BinaryPrimitives.ReadInt32BigEndian(bytes);
{
Array.Reverse(bytes, 0, bytes.Length);
}
return BitConverter.ToInt32(bytes, 0);
} }
private static unsafe int Detzcode32(int* data) private static int Detzcode32(int value)
{ {
int result = *data;
if (BitConverter.IsLittleEndian) if (BitConverter.IsLittleEndian)
{ {
byte[] bytes = BitConverter.GetBytes(result); return BinaryPrimitives.ReverseEndianness(value);
Array.Reverse(bytes, 0, bytes.Length);
result = BitConverter.ToInt32(bytes, 0);
} }
return result; return value;
} }
private static unsafe long Detzcode64(long* data) private static long Detzcode64(ReadOnlySpan<byte> bytes)
{ {
long result = *data; return BinaryPrimitives.ReadInt64BigEndian(bytes);
if (BitConverter.IsLittleEndian)
{
byte[] bytes = BitConverter.GetBytes(result);
Array.Reverse(bytes, 0, bytes.Length);
result = BitConverter.ToInt64(bytes, 0);
}
return result;
} }
private static bool DifferByRepeat(long t1, long t0) private static bool DifferByRepeat(long t1, long t0)
@ -148,7 +133,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return (t1 - t0) == SecondsPerRepeat; return (t1 - t0) == SecondsPerRepeat;
} }
private static unsafe bool TimeTypeEquals(TimeZoneRule outRules, byte aIndex, byte bIndex) private static bool TimeTypeEquals(TimeZoneRule outRules, byte aIndex, byte bIndex)
{ {
if (aIndex < 0 || aIndex >= outRules.TypeCount || bIndex < 0 || bIndex >= outRules.TypeCount) if (aIndex < 0 || aIndex >= outRules.TypeCount || bIndex < 0 || bIndex >= outRules.TypeCount)
{ {
@ -158,17 +143,14 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
TimeTypeInfo a = outRules.Ttis[aIndex]; TimeTypeInfo a = outRules.Ttis[aIndex];
TimeTypeInfo b = outRules.Ttis[bIndex]; TimeTypeInfo b = outRules.Ttis[bIndex];
fixed (char* chars = outRules.Chars) return a.GmtOffset == b.GmtOffset &&
{ a.IsDaySavingTime == b.IsDaySavingTime &&
return a.GmtOffset == b.GmtOffset && a.IsStandardTimeDaylight == b.IsStandardTimeDaylight &&
a.IsDaySavingTime == b.IsDaySavingTime && a.IsGMT == b.IsGMT &&
a.IsStandardTimeDaylight == b.IsStandardTimeDaylight && StringUtils.CompareCStr(outRules.Chars[a.AbbreviationListIndex..], outRules.Chars[b.AbbreviationListIndex..]) == 0;
a.IsGMT == b.IsGMT &&
StringUtils.CompareCStr(chars + a.AbbreviationListIndex, chars + b.AbbreviationListIndex) == 0;
}
} }
private static int GetQZName(char[] name, int namePosition, char delimiter) private static int GetQZName(ReadOnlySpan<char> name, int namePosition, char delimiter)
{ {
int i = namePosition; int i = namePosition;
@ -403,7 +385,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return 0; return 0;
} }
private static bool ParsePosixName(Span<char> name, out TimeZoneRule outRules, bool lastDitch) private static bool ParsePosixName(ReadOnlySpan<char> name, out TimeZoneRule outRules, bool lastDitch)
{ {
outRules = new TimeZoneRule outRules = new TimeZoneRule
{ {
@ -414,9 +396,10 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
}; };
int stdLen; int stdLen;
Span<char> stdName = name;
int namePosition = 0; ReadOnlySpan<char> stdName = name;
int stdOffset = 0; int namePosition = 0;
int stdOffset = 0;
if (lastDitch) if (lastDitch)
{ {
@ -433,7 +416,8 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
int stdNamePosition = namePosition; int stdNamePosition = namePosition;
namePosition = GetQZName(name.ToArray(), namePosition, '>'); namePosition = GetQZName(name, namePosition, '>');
if (name[namePosition] != '>') if (name[namePosition] != '>')
{ {
return false; return false;
@ -465,7 +449,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
int destLen = 0; int destLen = 0;
int dstOffset = 0; int dstOffset = 0;
Span<char> destName = name.Slice(namePosition); ReadOnlySpan<char> destName = name.Slice(namePosition);
if (TzCharsArraySize < charCount) if (TzCharsArraySize < charCount)
{ {
@ -903,7 +887,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return ParsePosixName(name.ToCharArray(), out outRules, false); return ParsePosixName(name.ToCharArray(), out outRules, false);
} }
internal static unsafe bool ParseTimeZoneBinary(out TimeZoneRule outRules, Stream inputData) internal static bool ParseTimeZoneBinary(out TimeZoneRule outRules, Stream inputData)
{ {
outRules = new TimeZoneRule outRules = new TimeZoneRule
{ {
@ -967,12 +951,11 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
timeCount = 0; timeCount = 0;
fixed (byte* workBufferPtrStart = workBuffer)
{ {
byte* p = workBufferPtrStart; Span<byte> p = workBuffer;
for (int i = 0; i < outRules.TimeCount; i++) for (int i = 0; i < outRules.TimeCount; i++)
{ {
long at = Detzcode64((long*)p); long at = Detzcode64(p);
outRules.Types[i] = 1; outRules.Types[i] = 1;
if (timeCount != 0 && at <= outRules.Ats[timeCount - 1]) if (timeCount != 0 && at <= outRules.Ats[timeCount - 1])
@ -988,13 +971,15 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
outRules.Ats[timeCount++] = at; outRules.Ats[timeCount++] = at;
p += TimeTypeSize; p = p[TimeTypeSize..];
} }
timeCount = 0; timeCount = 0;
for (int i = 0; i < outRules.TimeCount; i++) for (int i = 0; i < outRules.TimeCount; i++)
{ {
byte type = *p++; byte type = p[0];
p = p[1..];
if (outRules.TypeCount <= type) if (outRules.TypeCount <= type)
{ {
return false; return false;
@ -1011,18 +996,20 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
for (int i = 0; i < outRules.TypeCount; i++) for (int i = 0; i < outRules.TypeCount; i++)
{ {
TimeTypeInfo ttis = outRules.Ttis[i]; TimeTypeInfo ttis = outRules.Ttis[i];
ttis.GmtOffset = Detzcode32((int*)p); ttis.GmtOffset = Detzcode32(p);
p += 4; p = p[sizeof(int)..];
if (*p >= 2) if (p[0] >= 2)
{ {
return false; return false;
} }
ttis.IsDaySavingTime = *p != 0; ttis.IsDaySavingTime = p[0] != 0;
p++; p = p[1..];
int abbreviationListIndex = p[0];
p = p[1..];
int abbreviationListIndex = *p++;
if (abbreviationListIndex >= outRules.CharCount) if (abbreviationListIndex >= outRules.CharCount)
{ {
return false; return false;
@ -1033,12 +1020,9 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
outRules.Ttis[i] = ttis; outRules.Ttis[i] = ttis;
} }
fixed (char* chars = outRules.Chars) Encoding.ASCII.GetChars(p[..outRules.CharCount].ToArray()).CopyTo(outRules.Chars.AsSpan());
{
Encoding.ASCII.GetChars(p, outRules.CharCount, chars, outRules.CharCount);
}
p += outRules.CharCount; p = p[outRules.CharCount..];
outRules.Chars[outRules.CharCount] = '\0'; outRules.Chars[outRules.CharCount] = '\0';
for (int i = 0; i < outRules.TypeCount; i++) for (int i = 0; i < outRules.TypeCount; i++)
@ -1049,14 +1033,14 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
} }
else else
{ {
if (*p >= 2) if (p[0] >= 2)
{ {
return false; return false;
} }
outRules.Ttis[i].IsStandardTimeDaylight = *p++ != 0; outRules.Ttis[i].IsStandardTimeDaylight = p[0] != 0;
p = p[1..];
} }
} }
for (int i = 0; i < outRules.TypeCount; i++) for (int i = 0; i < outRules.TypeCount; i++)
@ -1067,17 +1051,18 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
} }
else else
{ {
if (*p >= 2) if (p[0] >= 2)
{ {
return false; return false;
} }
outRules.Ttis[i].IsGMT = *p++ != 0; outRules.Ttis[i].IsGMT = p[0] != 0;
p = p[1..];
} }
} }
long position = (p - workBufferPtrStart); long position = (workBuffer.Length - p.Length);
long nRead = streamLength - position; long nRead = streamLength - position;
if (nRead < 0) if (nRead < 0)
@ -1107,77 +1092,75 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
int abbreviationCount = 0; int abbreviationCount = 0;
charCount = outRules.CharCount; charCount = outRules.CharCount;
fixed (char* chars = outRules.Chars) Span<char> chars = outRules.Chars;
for (int i = 0; i < tempRules.TypeCount; i++)
{ {
for (int i = 0; i < tempRules.TypeCount; i++) ReadOnlySpan<char> tempChars = tempRules.Chars;
ReadOnlySpan<char> tempAbbreviation = tempChars[tempRules.Ttis[i].AbbreviationListIndex..];
int j;
for (j = 0; j < charCount; j++)
{ {
fixed (char* tempChars = tempRules.Chars) if (StringUtils.CompareCStr(chars[j..], tempAbbreviation) == 0)
{ {
char* tempAbbreviation = tempChars + tempRules.Ttis[i].AbbreviationListIndex; tempRules.Ttis[i].AbbreviationListIndex = j;
int j; abbreviationCount++;
break;
for (j = 0; j < charCount; j++)
{
if (StringUtils.CompareCStr(chars + j, tempAbbreviation) == 0)
{
tempRules.Ttis[i].AbbreviationListIndex = j;
abbreviationCount++;
break;
}
}
if (j >= charCount)
{
int abbreviationLength = StringUtils.LengthCstr(tempAbbreviation);
if (j + abbreviationLength < TzMaxChars)
{
for (int x = 0; x < abbreviationLength; x++)
{
chars[j + x] = tempAbbreviation[x];
}
charCount = j + abbreviationLength + 1;
tempRules.Ttis[i].AbbreviationListIndex = j;
abbreviationCount++;
}
}
} }
} }
if (abbreviationCount == tempRules.TypeCount) if (j >= charCount)
{ {
outRules.CharCount = charCount; int abbreviationLength = StringUtils.LengthCstr(tempAbbreviation);
if (j + abbreviationLength < TzMaxChars)
// Remove trailing
while (1 < outRules.TimeCount && (outRules.Types[outRules.TimeCount - 1] == outRules.Types[outRules.TimeCount - 2]))
{ {
outRules.TimeCount--; for (int x = 0; x < abbreviationLength; x++)
}
int i;
for (i = 0; i < tempRules.TimeCount; i++)
{
if (outRules.TimeCount == 0 || outRules.Ats[outRules.TimeCount - 1] < tempRules.Ats[i])
{ {
break; chars[j + x] = tempAbbreviation[x];
} }
}
while (i < tempRules.TimeCount && outRules.TimeCount < TzMaxTimes) charCount = j + abbreviationLength + 1;
tempRules.Ttis[i].AbbreviationListIndex = j;
abbreviationCount++;
}
}
}
if (abbreviationCount == tempRules.TypeCount)
{
outRules.CharCount = charCount;
// Remove trailing
while (1 < outRules.TimeCount && (outRules.Types[outRules.TimeCount - 1] == outRules.Types[outRules.TimeCount - 2]))
{
outRules.TimeCount--;
}
int i;
for (i = 0; i < tempRules.TimeCount; i++)
{
if (outRules.TimeCount == 0 || outRules.Ats[outRules.TimeCount - 1] < tempRules.Ats[i])
{ {
outRules.Ats[outRules.TimeCount] = tempRules.Ats[i]; break;
outRules.Types[outRules.TimeCount] = (byte)(outRules.TypeCount + (byte)tempRules.Types[i]);
outRules.TimeCount++;
i++;
} }
}
for (i = 0; i < tempRules.TypeCount; i++) while (i < tempRules.TimeCount && outRules.TimeCount < TzMaxTimes)
{ {
outRules.Ttis[outRules.TypeCount++] = tempRules.Ttis[i]; outRules.Ats[outRules.TimeCount] = tempRules.Ats[i];
} outRules.Types[outRules.TimeCount] = (byte)(outRules.TypeCount + (byte)tempRules.Types[i]);
outRules.TimeCount++;
i++;
}
for (i = 0; i < tempRules.TypeCount; i++)
{
outRules.Ttis[outRules.TypeCount++] = tempRules.Ttis[i];
} }
} }
} }
@ -1467,17 +1450,11 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{ {
calendarAdditionalInfo.IsDaySavingTime = rules.Ttis[ttiIndex].IsDaySavingTime; calendarAdditionalInfo.IsDaySavingTime = rules.Ttis[ttiIndex].IsDaySavingTime;
unsafe ReadOnlySpan<char> timeZoneAbbreviation = rules.Chars.AsSpan()[rules.Ttis[ttiIndex].AbbreviationListIndex..];
{
fixed (char* timeZoneAbbreviation = &rules.Chars[rules.Ttis[ttiIndex].AbbreviationListIndex]) int timeZoneSize = Math.Min(StringUtils.LengthCstr(timeZoneAbbreviation), 8);
{
int timeZoneSize = Math.Min(StringUtils.LengthCstr(timeZoneAbbreviation), 8); timeZoneAbbreviation[..timeZoneSize].CopyTo(calendarAdditionalInfo.TimezoneName.AsSpan());
for (int i = 0; i < timeZoneSize; i++)
{
calendarAdditionalInfo.TimezoneName[i] = timeZoneAbbreviation[i];
}
}
}
} }
return result; return result;

View file

@ -1,34 +1,19 @@
using System.Runtime.InteropServices; using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Time.TimeZone namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{ {
[StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x2C)] [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x2C)]
struct TzifHeader struct TzifHeader
{ {
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public Array4<byte> Magic;
public char[] Magic; public byte Version;
private Array15<byte> _reserved;
public char Version; public int TtisGMTCount;
public int TtisSTDCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)] public int LeapCount;
public byte[] Reserved; public int TimeCount;
public int TypeCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public int CharCount;
public byte[] TtisGMTCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] TtisSTDCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] LeapCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] TimeCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] TypeCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] CharCount;
} }
} }

View file

@ -36,6 +36,15 @@ namespace Ryujinx.HLE.Utilities
return output; return output;
} }
public static string ReadInlinedAsciiString(BinaryReader reader, int maxSize)
{
byte[] data = reader.ReadBytes(maxSize);
int stringSize = Array.IndexOf<byte>(data, 0);
return Encoding.ASCII.GetString(data, 0, stringSize < 0 ? maxSize : stringSize);
}
public static byte[] HexToBytes(string hexString) public static byte[] HexToBytes(string hexString)
{ {
// Ignore last character if HexLength % 2 != 0. // Ignore last character if HexLength % 2 != 0.
@ -107,7 +116,7 @@ namespace Ryujinx.HLE.Utilities
} }
} }
public static unsafe int CompareCStr(char* s1, char* s2) public static int CompareCStr(ReadOnlySpan<char> s1, ReadOnlySpan<char> s2)
{ {
int s1Index = 0; int s1Index = 0;
int s2Index = 0; int s2Index = 0;
@ -121,7 +130,7 @@ namespace Ryujinx.HLE.Utilities
return s2[s2Index] - s1[s1Index]; return s2[s2Index] - s1[s1Index];
} }
public static unsafe int LengthCstr(char* s) public static int LengthCstr(ReadOnlySpan<char> s)
{ {
int i = 0; int i = 0;