diff --git a/src/core/cpu_patches.cpp b/src/core/cpu_patches.cpp index 8c0897a48..2788cfe58 100644 --- a/src/core/cpu_patches.cpp +++ b/src/core/cpu_patches.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -122,6 +123,30 @@ static void GenerateTcbAccess(void* /* address */, const ZydisDecodedOperand* op #endif } +static bool FilterStackCheck(const ZydisDecodedOperand* operands) { + const auto& dst_op = operands[0]; + const auto& src_op = operands[1]; + + // Some compilers emit stack checks by starting a function with + // 'mov (64-bit register), fs:[0x28]', then checking with `xor (64-bit register), fs:[0x28]` + return src_op.type == ZYDIS_OPERAND_TYPE_MEMORY && src_op.mem.segment == ZYDIS_REGISTER_FS && + src_op.mem.base == ZYDIS_REGISTER_NONE && src_op.mem.index == ZYDIS_REGISTER_NONE && + src_op.mem.disp.value == 0x28 && dst_op.reg.value >= ZYDIS_REGISTER_RAX && + dst_op.reg.value <= ZYDIS_REGISTER_R15; +} + +static void GenerateStackCheck(void* /* address */, const ZydisDecodedOperand* operands, + Xbyak::CodeGenerator& c) { + const auto dst = ZydisToXbyakRegisterOperand(operands[0]); + c.xor_(dst, 0); +} + +static void GenerateStackCanary(void* /* address */, const ZydisDecodedOperand* operands, + Xbyak::CodeGenerator& c) { + const auto dst = ZydisToXbyakRegisterOperand(operands[0]); + c.mov(dst, 0); +} + static bool FilterNoSSE4a(const ZydisDecodedOperand*) { Cpu cpu; return !cpu.has(Cpu::tSSE4a); @@ -440,18 +465,26 @@ struct PatchInfo { bool trampoline; }; -static const std::unordered_map Patches = { +static const std::unordered_map> Patches = { // SSE4a - {ZYDIS_MNEMONIC_EXTRQ, {FilterNoSSE4a, GenerateEXTRQ, true}}, - {ZYDIS_MNEMONIC_INSERTQ, {FilterNoSSE4a, GenerateINSERTQ, true}}, - {ZYDIS_MNEMONIC_MOVNTSS, {FilterNoSSE4a, ReplaceMOVNTSS, false}}, - {ZYDIS_MNEMONIC_MOVNTSD, {FilterNoSSE4a, ReplaceMOVNTSD, false}}, + {ZYDIS_MNEMONIC_EXTRQ, {{FilterNoSSE4a, GenerateEXTRQ, true}}}, + {ZYDIS_MNEMONIC_INSERTQ, {{FilterNoSSE4a, GenerateINSERTQ, true}}}, + {ZYDIS_MNEMONIC_MOVNTSS, {{FilterNoSSE4a, ReplaceMOVNTSS, false}}}, + {ZYDIS_MNEMONIC_MOVNTSD, {{FilterNoSSE4a, ReplaceMOVNTSD, false}}}, +#if !defined(__APPLE__) + // FS segment patches + // These first two patches are for accesses to the stack canary, fs:[0x28] + {ZYDIS_MNEMONIC_XOR, {{FilterStackCheck, GenerateStackCheck, false}}}, + {ZYDIS_MNEMONIC_MOV, + {{FilterStackCheck, GenerateStackCanary, false}, #if defined(_WIN32) - // Windows needs a trampoline. - {ZYDIS_MNEMONIC_MOV, {FilterTcbAccess, GenerateTcbAccess, true}}, -#elif !defined(__APPLE__) - {ZYDIS_MNEMONIC_MOV, {FilterTcbAccess, GenerateTcbAccess, false}}, + // Windows needs a trampoline for Tcb accesses. + {FilterTcbAccess, GenerateTcbAccess, true} +#else + {FilterTcbAccess, GenerateTcbAccess, false} +#endif + }}, #endif }; @@ -503,51 +536,53 @@ static std::pair TryPatch(u8* code, PatchModule* module) { } if (Patches.contains(instruction.mnemonic)) { - const auto& patch_info = Patches.at(instruction.mnemonic); - bool needs_trampoline = patch_info.trampoline; - if (patch_info.filter(operands)) { - auto& patch_gen = module->patch_gen; + const auto& patches = Patches.at(instruction.mnemonic); + for (const auto& patch_info : patches) { + bool needs_trampoline = patch_info.trampoline; + if (patch_info.filter(operands)) { + auto& patch_gen = module->patch_gen; - if (needs_trampoline && instruction.length < 5) { - // Trampoline is needed but instruction is too short to patch. - // Return false and length to signal to AOT compilation that this instruction - // should be skipped and handled at runtime. - return std::make_pair(false, instruction.length); - } + if (needs_trampoline && instruction.length < 5) { + // Trampoline is needed but instruction is too short to patch. + // Return false and length to signal to AOT compilation that this instruction + // should be skipped and handled at runtime. + return std::make_pair(false, instruction.length); + } - // Reset state and move to current code position. - patch_gen.reset(); - patch_gen.setSize(code - patch_gen.getCode()); + // Reset state and move to current code position. + patch_gen.reset(); + patch_gen.setSize(code - patch_gen.getCode()); - if (needs_trampoline) { - auto& trampoline_gen = module->trampoline_gen; - const auto trampoline_ptr = trampoline_gen.getCurr(); + if (needs_trampoline) { + auto& trampoline_gen = module->trampoline_gen; + const auto trampoline_ptr = trampoline_gen.getCurr(); - patch_info.generator(code, operands, trampoline_gen); + patch_info.generator(code, operands, trampoline_gen); - // Return to the following instruction at the end of the trampoline. - trampoline_gen.jmp(code + instruction.length); + // Return to the following instruction at the end of the trampoline. + trampoline_gen.jmp(code + instruction.length); - // Replace instruction with near jump to the trampoline. - patch_gen.jmp(trampoline_ptr, Xbyak::CodeGenerator::LabelType::T_NEAR); - } else { - patch_info.generator(code, operands, patch_gen); - } + // Replace instruction with near jump to the trampoline. + patch_gen.jmp(trampoline_ptr, Xbyak::CodeGenerator::LabelType::T_NEAR); + } else { + patch_info.generator(code, operands, patch_gen); + } - const auto patch_size = patch_gen.getCurr() - code; - if (patch_size > 0) { - ASSERT_MSG(instruction.length >= patch_size, - "Instruction {} with length {} is too short to replace at: {}", - ZydisMnemonicGetString(instruction.mnemonic), instruction.length, - fmt::ptr(code)); + const auto patch_size = patch_gen.getCurr() - code; + if (patch_size > 0) { + ASSERT_MSG(instruction.length >= patch_size, + "Instruction {} with length {} is too short to replace at: {}", + ZydisMnemonicGetString(instruction.mnemonic), instruction.length, + fmt::ptr(code)); - // Fill remaining space with nops. - patch_gen.nop(instruction.length - patch_size); + // Fill remaining space with nops. + patch_gen.nop(instruction.length - patch_size); - module->patched.insert(code); - LOG_DEBUG(Core, "Patched instruction '{}' at: {}", - ZydisMnemonicGetString(instruction.mnemonic), fmt::ptr(code)); - return std::make_pair(true, instruction.length); + module->patched.insert(code); + LOG_DEBUG(Core, "Patched instruction '{}' at: {}", + ZydisMnemonicGetString(instruction.mnemonic), fmt::ptr(code)); + return std::make_pair(true, instruction.length); + } } } } diff --git a/src/core/libraries/kernel/threads/exception.cpp b/src/core/libraries/kernel/threads/exception.cpp index 5455d425e..95ced79c0 100644 --- a/src/core/libraries/kernel/threads/exception.cpp +++ b/src/core/libraries/kernel/threads/exception.cpp @@ -173,6 +173,8 @@ void RegisterException(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("OMDRKKAZ8I4", "libkernel", 1, "libkernel", sceKernelDebugRaiseException); LIB_FUNCTION("zE-wXIZjLoM", "libkernel", 1, "libkernel", sceKernelDebugRaiseExceptionOnReleaseMode); + LIB_FUNCTION("WkwEd3N7w0Y", "libkernel", 1, "libkernel", sceKernelInstallExceptionHandler); + LIB_FUNCTION("Qhv5ARAoOEc", "libkernel", 1, "libkernel", sceKernelRemoveExceptionHandler); } } // namespace Libraries::Kernel diff --git a/src/core/libraries/network/http.cpp b/src/core/libraries/network/http.cpp index 1ae48dfed..ebb10db68 100644 --- a/src/core/libraries/network/http.cpp +++ b/src/core/libraries/network/http.cpp @@ -712,8 +712,61 @@ int PS4_SYSV_ABI sceHttpUriCopy() { return ORBIS_OK; } -int PS4_SYSV_ABI sceHttpUriEscape() { - LOG_ERROR(Lib_Http, "(STUBBED) called"); +int PS4_SYSV_ABI sceHttpUriEscape(char* out, u64* require, u64 prepare, const char* in) { + LOG_TRACE(Lib_Http, "called"); + + if (!in) { + LOG_ERROR(Lib_Http, "Invalid input string"); + return ORBIS_HTTP_ERROR_INVALID_VALUE; + } + + auto IsUnreserved = [](unsigned char c) -> bool { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || + c == '-' || c == '_' || c == '.' || c == '~'; + }; + + u64 needed = 0; + const char* src = in; + while (*src) { + unsigned char c = static_cast(*src); + if (IsUnreserved(c)) { + needed++; + } else { + needed += 3; // %XX format + } + src++; + } + needed++; // null terminator + + if (require) { + *require = needed; + } + + if (!out) { + return ORBIS_OK; + } + + if (prepare < needed) { + LOG_ERROR(Lib_Http, "Buffer too small: need {} but only {} available", needed, prepare); + return ORBIS_HTTP_ERROR_OUT_OF_MEMORY; + } + + static const char hex_chars[] = "0123456789ABCDEF"; + src = in; + char* dst = out; + while (*src) { + unsigned char c = static_cast(*src); + if (IsUnreserved(c)) { + *dst++ = *src; + } else { + *dst++ = '%'; + *dst++ = hex_chars[(c >> 4) & 0x0F]; + *dst++ = hex_chars[c & 0x0F]; + } + src++; + } + *dst = '\0'; + return ORBIS_OK; } @@ -1072,12 +1125,163 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v } int PS4_SYSV_ABI sceHttpUriSweepPath(char* dst, const char* src, u64 srcSize) { - LOG_ERROR(Lib_Http, "(STUBBED) called"); + LOG_TRACE(Lib_Http, "called"); + + if (!dst || !src) { + LOG_ERROR(Lib_Http, "Invalid parameters"); + return ORBIS_HTTP_ERROR_INVALID_VALUE; + } + + if (srcSize == 0) { + dst[0] = '\0'; + return ORBIS_OK; + } + + u64 len = 0; + while (len < srcSize && src[len] != '\0') { + len++; + } + + for (u64 i = 0; i < len; i++) { + dst[i] = src[i]; + } + dst[len] = '\0'; + + char* read = dst; + char* write = dst; + + while (*read) { + if (read[0] == '.' && read[1] == '.' && read[2] == '/') { + read += 3; + continue; + } + + if (read[0] == '.' && read[1] == '/') { + read += 2; + continue; + } + + if (read[0] == '/' && read[1] == '.' && read[2] == '/') { + read += 2; + continue; + } + + if (read[0] == '/' && read[1] == '.' && read[2] == '\0') { + if (write == dst) { + *write++ = '/'; + } + break; + } + + bool is_dotdot_mid = (read[0] == '/' && read[1] == '.' && read[2] == '.' && read[3] == '/'); + bool is_dotdot_end = + (read[0] == '/' && read[1] == '.' && read[2] == '.' && read[3] == '\0'); + + if (is_dotdot_mid || is_dotdot_end) { + if (write > dst) { + if (*(write - 1) == '/') { + write--; + } + while (write > dst && *(write - 1) != '/') { + write--; + } + + if (is_dotdot_mid && write > dst) { + write--; + } + } + + if (is_dotdot_mid) { + read += 3; + } else { + break; + } + continue; + } + + if ((read[0] == '.' && read[1] == '\0') || + (read[0] == '.' && read[1] == '.' && read[2] == '\0')) { + break; + } + + if (read[0] == '/') { + *write++ = *read++; + } + while (*read && *read != '/') { + *write++ = *read++; + } + } + + *write = '\0'; return ORBIS_OK; } int PS4_SYSV_ABI sceHttpUriUnescape(char* out, u64* require, u64 prepare, const char* in) { - LOG_ERROR(Lib_Http, "(STUBBED) called"); + LOG_TRACE(Lib_Http, "called"); + + if (!in) { + LOG_ERROR(Lib_Http, "Invalid input string"); + return ORBIS_HTTP_ERROR_INVALID_VALUE; + } + + // Locale-independent hex digit check + auto IsHex = [](char c) -> bool { + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); + }; + + // Convert hex char to int value + auto HexToInt = [](char c) -> int { + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return 0; + }; + + // Check for valid percent-encoded sequence (%XX) + auto IsValidPercentSequence = [&](const char* s) -> bool { + return s[0] == '%' && s[1] != '\0' && s[2] != '\0' && IsHex(s[1]) && IsHex(s[2]); + }; + + u64 needed = 0; + const char* src = in; + while (*src) { + if (IsValidPercentSequence(src)) { + src += 3; + } else { + src++; + } + needed++; + } + needed++; // null terminator + + if (require) { + *require = needed; + } + + if (!out) { + return ORBIS_OK; + } + + if (prepare < needed) { + LOG_ERROR(Lib_Http, "Buffer too small: need {} but only {} available", needed, prepare); + return ORBIS_HTTP_ERROR_OUT_OF_MEMORY; + } + + src = in; + char* dst = out; + while (*src) { + if (IsValidPercentSequence(src)) { + *dst++ = static_cast((HexToInt(src[1]) << 4) | HexToInt(src[2])); + src += 3; + } else { + *dst++ = *src++; + } + } + *dst = '\0'; + return ORBIS_OK; } diff --git a/src/core/libraries/network/http.h b/src/core/libraries/network/http.h index 701bb0e05..2ad5e171f 100644 --- a/src/core/libraries/network/http.h +++ b/src/core/libraries/network/http.h @@ -148,7 +148,7 @@ int PS4_SYSV_ABI sceHttpUnsetEpoll(); int PS4_SYSV_ABI sceHttpUriBuild(char* out, u64* require, u64 prepare, const OrbisHttpUriElement* srcElement, u32 option); int PS4_SYSV_ABI sceHttpUriCopy(); -int PS4_SYSV_ABI sceHttpUriEscape(); +int PS4_SYSV_ABI sceHttpUriEscape(char* out, u64* require, u64 prepare, const char* in); int PS4_SYSV_ABI sceHttpUriMerge(char* mergedUrl, char* url, char* relativeUri, u64* require, u64 prepare, u32 option); int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, void* pool, diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 94cefb958..0803647a2 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -1050,7 +1050,14 @@ void Translator::V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const } void Translator::V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst) { - ASSERT(inst.src[1].field == OperandField::ConstZero); + const bool is_zero = inst.src[1].field == OperandField::ConstZero; + const bool is_neg_one = inst.src[1].field == OperandField::SignedConstIntNeg; + ASSERT(is_zero || is_neg_one); + if (is_neg_one) { + ASSERT_MSG(-s32(inst.src[1].code) + SignedConstIntNegMin - 1 == -1, + "SignedConstIntNeg must be -1"); + } + const IR::U1 src0 = [&] { switch (inst.src[0].field) { case OperandField::ScalarGPR: @@ -1064,10 +1071,11 @@ void Translator::V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const const IR::U1 result = [&] { switch (op) { case ConditionOp::EQ: - return ir.LogicalNot(src0); + return is_zero ? ir.LogicalNot(src0) : src0; case ConditionOp::LG: // NE - return src0; + return is_zero ? src0 : ir.LogicalNot(src0); case ConditionOp::GT: + ASSERT(is_zero); return ir.GroupAny(ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code))); default: UNREACHABLE_MSG("Unsupported V_CMP_U64 condition operation: {}", u32(op));