diff --git a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs index 3696910..f829eb3 100644 --- a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs @@ -225,6 +225,22 @@ public static class KernelMemoryCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + [SysAbiExport( + Nid = "WkkeywLJcgU", + ExportName = "wcslen", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libc")] + public static int Wcslen(CpuContext ctx) + { + if (!TryReadWideCString(ctx, ctx[CpuRegister.Rdi], 1_048_576, out var units)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + ctx[CpuRegister.Rax] = unchecked((ulong)units.Length); + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + [SysAbiExport( Nid = "Ovb2dSJOAuE", ExportName = "strcmp", @@ -243,6 +259,24 @@ public static class KernelMemoryCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + [SysAbiExport( + Nid = "pNtJdE3x49E", + ExportName = "wcscmp", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libc")] + public static int Wcscmp(CpuContext ctx) + { + var left = ctx[CpuRegister.Rdi]; + var right = ctx[CpuRegister.Rsi]; + if (!TryCompareWideStrings(ctx, left, right, limit: ulong.MaxValue, out var compare)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + ctx[CpuRegister.Rax] = unchecked((ulong)compare); + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + [SysAbiExport( Nid = "aesyjrHVWy4", ExportName = "strncmp", @@ -262,6 +296,25 @@ public static class KernelMemoryCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + [SysAbiExport( + Nid = "E8wCoUEbfzk", + ExportName = "wcsncmp", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libc")] + public static int Wcsncmp(CpuContext ctx) + { + var left = ctx[CpuRegister.Rdi]; + var right = ctx[CpuRegister.Rsi]; + var limit = ctx[CpuRegister.Rdx]; + if (!TryCompareWideStrings(ctx, left, right, limit, out var compare)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + ctx[CpuRegister.Rax] = unchecked((ulong)compare); + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + [SysAbiExport( Nid = "eLdDw6l0-bU", ExportName = "snprintf", @@ -340,6 +393,29 @@ public static class KernelMemoryCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + [SysAbiExport( + Nid = "FM5NPnLqBc8", + ExportName = "wcscpy", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libc")] + public static int Wcscpy(CpuContext ctx) + { + var destination = ctx[CpuRegister.Rdi]; + var source = ctx[CpuRegister.Rsi]; + if (!TryReadWideCString(ctx, source, 1_048_576, out var units)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + if (!TryWriteCompat(ctx, destination, EncodeWideUnitsWithTerminator(units))) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + ctx[CpuRegister.Rax] = destination; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + [SysAbiExport( Nid = "6sJWiWSRuqk", ExportName = "strncpy", @@ -382,6 +458,80 @@ public static class KernelMemoryCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + [SysAbiExport( + Nid = "0nV21JjYCH8", + ExportName = "wcsncpy", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libc")] + public static int Wcsncpy(CpuContext ctx) + { + var destination = ctx[CpuRegister.Rdi]; + var source = ctx[CpuRegister.Rsi]; + var count = (int)Math.Min(ctx[CpuRegister.Rdx], int.MaxValue); + if (count < 0 || count > (int.MaxValue / sizeof(ushort))) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + } + + var payload = new byte[count * sizeof(ushort)]; + for (var copied = 0; copied < count; copied++) + { + if (!TryReadUInt16Compat(ctx, source + ((ulong)copied * sizeof(ushort)), out var unit)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + BinaryPrimitives.WriteUInt16LittleEndian( + payload.AsSpan(copied * sizeof(ushort), sizeof(ushort)), + unit); + + if (unit == 0) + { + break; + } + } + + if (!TryWriteCompat(ctx, destination, payload)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + ctx[CpuRegister.Rax] = destination; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "Ezzq78ZgHPs", + ExportName = "wcschr", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libc")] + public static int Wcschr(CpuContext ctx) + { + var address = ctx[CpuRegister.Rdi]; + var needle = unchecked((ushort)ctx[CpuRegister.Rsi]); + for (ulong index = 0; index < 1_048_576; index++) + { + if (!TryReadUInt16Compat(ctx, address + (index * sizeof(ushort)), out var unit)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + if (unit == needle) + { + ctx[CpuRegister.Rax] = address + (index * sizeof(ushort)); + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + if (unit == 0) + { + break; + } + } + + ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + [SysAbiExport( Nid = "Q3VBxCXhUHs", ExportName = "memcpy", @@ -2440,6 +2590,84 @@ public static class KernelMemoryCompatExports return true; } + private static bool TryReadWideCString(CpuContext ctx, ulong address, ulong maxLength, out ushort[] units) + { + units = Array.Empty(); + if (address == 0) + { + return false; + } + + var limit = (int)Math.Min(maxLength, 1_048_576UL); + var buffer = new List(Math.Min(limit, 256)); + for (var i = 0; i < limit; i++) + { + if (!TryReadUInt16Compat(ctx, address + ((ulong)i * sizeof(ushort)), out var unit)) + { + return false; + } + + if (unit == 0) + { + units = buffer.ToArray(); + return true; + } + + buffer.Add(unit); + } + + units = buffer.ToArray(); + return true; + } + + private static bool TryCompareWideStrings(CpuContext ctx, ulong left, ulong right, ulong limit, out int compare) + { + compare = 0; + if (left == 0 || right == 0) + { + return false; + } + + var max = limit == ulong.MaxValue ? 1_048_576UL : Math.Min(limit, 1_048_576UL); + for (ulong i = 0; i < max; i++) + { + if (!TryReadUInt16Compat(ctx, left + (i * sizeof(ushort)), out var leftUnit) || + !TryReadUInt16Compat(ctx, right + (i * sizeof(ushort)), out var rightUnit)) + { + return false; + } + + compare = leftUnit - rightUnit; + if (compare != 0 || leftUnit == 0 || rightUnit == 0) + { + return true; + } + } + + compare = 0; + return true; + } + + private static byte[] EncodeWideUnits(ReadOnlySpan units) + { + var bytes = new byte[units.Length * sizeof(ushort)]; + for (var i = 0; i < units.Length; i++) + { + BinaryPrimitives.WriteUInt16LittleEndian( + bytes.AsSpan(i * sizeof(ushort), sizeof(ushort)), + units[i]); + } + + return bytes; + } + + private static byte[] EncodeWideUnitsWithTerminator(ReadOnlySpan units) + { + var bytes = new byte[(units.Length + 1) * sizeof(ushort)]; + EncodeWideUnits(units).CopyTo(bytes, 0); + return bytes; + } + private static bool TryReadNullTerminatedUtf8(CpuContext ctx, ulong address, int maxLength, out string value) { value = string.Empty; @@ -2510,6 +2738,19 @@ public static class KernelMemoryCompatExports return true; } + private static bool TryReadUInt16Compat(CpuContext ctx, ulong address, out ushort value) + { + Span bytes = stackalloc byte[sizeof(ushort)]; + if (!TryReadCompat(ctx, address, bytes)) + { + value = 0; + return false; + } + + value = BinaryPrimitives.ReadUInt16LittleEndian(bytes); + return true; + } + private static bool TryReadUInt64Compat(CpuContext ctx, ulong address, out ulong value) { Span bytes = stackalloc byte[sizeof(ulong)];