From 26a570633c3e37164ff8e0ad6ae138a83d21e902 Mon Sep 17 00:00:00 2001 From: j92580498-max Date: Tue, 14 Jul 2026 17:10:00 +0300 Subject: [PATCH] [HLE] Add strchr/strrchr/memchr/strcat/strncat/strstr libc exports (#132) Implement six missing libc string/memory search and concatenation routines in the kernel compat layer. Titles frequently call these during startup string handling (path parsing, config lookups, format string assembly), and without them the loader currently falls through to unresolved-import handling. The implementations follow the existing byte-at-a-time compat helpers (TryReadCompat/TryWriteCompat) already used by strcpy/strncpy/memcmp, matching native semantics: strchr/strrchr scan through and including the terminator, memchr is bounded strictly by count, strcat/strncat overwrite the destination terminator and re-terminate, and strstr returns the haystack pointer for an empty needle. NIDs are the libSceLibcInternal/libc symbol hashes for each name. --- .../Kernel/KernelMemoryCompatExports.cs | 208 ++++++++++++++++++ 1 file changed, 208 insertions(+) diff --git a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs index fd64d6df..432488da 100644 --- a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs @@ -1488,6 +1488,214 @@ public static class KernelMemoryCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + [SysAbiExport( + Nid = "ob5xAW4ln-0", + ExportName = "strchr", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libc")] + public static int Strchr(CpuContext ctx) + { + var address = ctx[CpuRegister.Rdi]; + var needle = unchecked((byte)ctx[CpuRegister.Rsi]); + if (address == 0) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + // The terminator counts as part of the scanned range, so strchr(s, '\0') + // returns a pointer to the string's null byte just like a native libc. + Span current = stackalloc byte[1]; + for (ulong index = 0; index < 1_048_576; index++) + { + if (!TryReadCompat(ctx, address + index, current)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + if (current[0] == needle) + { + ctx[CpuRegister.Rax] = address + index; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + if (current[0] == 0) + { + break; + } + } + + ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "9yDWMxEFdJU", + ExportName = "strrchr", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libc")] + public static int Strrchr(CpuContext ctx) + { + var address = ctx[CpuRegister.Rdi]; + var needle = unchecked((byte)ctx[CpuRegister.Rsi]); + if (address == 0) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + ulong match = 0; + var found = false; + Span current = stackalloc byte[1]; + for (ulong index = 0; index < 1_048_576; index++) + { + if (!TryReadCompat(ctx, address + index, current)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + if (current[0] == needle) + { + match = address + index; + found = true; + } + + if (current[0] == 0) + { + break; + } + } + + ctx[CpuRegister.Rax] = found ? match : 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "8u8lPzUEq+U", + ExportName = "memchr", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libc")] + public static int Memchr(CpuContext ctx) + { + var address = ctx[CpuRegister.Rdi]; + var needle = unchecked((byte)ctx[CpuRegister.Rsi]); + var count = ctx[CpuRegister.Rdx]; + + Span current = stackalloc byte[1]; + for (ulong index = 0; index < count; index++) + { + if (!TryReadCompat(ctx, address + index, current)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + if (current[0] == needle) + { + ctx[CpuRegister.Rax] = address + index; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + } + + ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "Ls4tzzhimqQ", + ExportName = "strcat", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libc")] + public static int Strcat(CpuContext ctx) + { + var destination = ctx[CpuRegister.Rdi]; + var source = ctx[CpuRegister.Rsi]; + if (!TryReadCString(ctx, source, 1_048_576, out var sourceBytes)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + if (!TryReadCString(ctx, destination, 1_048_576, out var destinationBytes)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + // Overwrite the destination terminator and re-terminate after the copied bytes. + var appendAddress = destination + (ulong)destinationBytes.Length; + var payload = new byte[sourceBytes.Length + 1]; + sourceBytes.CopyTo(payload.AsSpan()); + if (!TryWriteCompat(ctx, appendAddress, payload)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + ctx[CpuRegister.Rax] = destination; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "kHg45qPC6f0", + ExportName = "strncat", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libc")] + public static int Strncat(CpuContext ctx) + { + var destination = ctx[CpuRegister.Rdi]; + var source = ctx[CpuRegister.Rsi]; + var limit = ctx[CpuRegister.Rdx]; + + // Bounding the source read by the count yields strncat's "at most n bytes" + // semantics while still stopping early at the source terminator. + if (!TryReadCString(ctx, source, limit, out var sourceBytes)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + if (!TryReadCString(ctx, destination, 1_048_576, out var destinationBytes)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + var appendAddress = destination + (ulong)destinationBytes.Length; + var payload = new byte[sourceBytes.Length + 1]; + sourceBytes.CopyTo(payload.AsSpan()); + if (!TryWriteCompat(ctx, appendAddress, payload)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + ctx[CpuRegister.Rax] = destination; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "viiwFMaNamA", + ExportName = "strstr", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libc")] + public static int Strstr(CpuContext ctx) + { + var haystack = ctx[CpuRegister.Rdi]; + var needle = ctx[CpuRegister.Rsi]; + if (!TryReadCString(ctx, haystack, 1_048_576, out var haystackBytes)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + if (!TryReadCString(ctx, needle, 1_048_576, out var needleBytes)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + // An empty needle matches at the start of the haystack. + if (needleBytes.Length == 0) + { + ctx[CpuRegister.Rax] = haystack; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + var matchIndex = haystackBytes.AsSpan().IndexOf(needleBytes.AsSpan()); + ctx[CpuRegister.Rax] = matchIndex >= 0 ? haystack + (ulong)matchIndex : 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + [SysAbiExport( Nid = "QrZZdJ8XsX0", ExportName = "fputs",