diff --git a/core/libs/prx/libkernel/Apr/src/Apr.cpp b/core/libs/prx/libkernel/Apr/src/Apr.cpp index 8e307d014..6910a43eb 100644 --- a/core/libs/prx/libkernel/Apr/src/Apr.cpp +++ b/core/libs/prx/libkernel/Apr/src/Apr.cpp @@ -882,4 +882,29 @@ int APS5_VABI sceKernelAprResolveFilepathsWithPrefixToIdsAndFileSizesForEach(con return _resolveForEach(prefix, paths, count, ids, sizes, results); } +bool _validWriteRange(std::uint64_t address, std::uint64_t size) { + constexpr std::uint64_t pageMask = 0x3FFFu; + return address != 0u && size != 0u && ((address | size) & pageMask) == 0u && address + size > address; +} + +bool _validWriteProtection(std::uint64_t protection) { + return (protection & ~0x3F3ULL) == 0u; +} + +int APS5_VABI sceKernelWriteModifyMtypeProtectCommand(void* dst, std::uint64_t address, std::uint64_t size, std::uint64_t type, std::uint64_t protection, std::uint64_t mask, std::uint64_t* outSize) { + if (!_validWriteRange(address, size) || !_validWriteProtection(protection) || !_validWriteProtection(mask)) return SCE_KERNEL_ERROR_EINVAL; + const Apr::AmmProtectCommand command{{Apr::Opcode::AmmModifyMtypeProtect, sizeof(command)}, address, size, static_cast(type), static_cast(protection), static_cast(mask), 0}; + if (dst) std::memcpy(dst, &command, sizeof(command)); + if (outSize) *outSize = sizeof(command); + return 0; +} + +int APS5_VABI sceKernelWriteRemapIntoPrtCommand(std::uint32_t* dst, std::uint64_t address, std::uint64_t source, std::uint64_t size, int protection, int opcode, std::uint64_t* outSize) { + if (!_validWriteRange(address, size) || !_validWriteRange(source, size) || !_validWriteProtection(static_cast(protection))) return SCE_KERNEL_ERROR_EINVAL; + const Apr::AmmRemapCommand command{{Apr::Opcode::AmmRemapIntoPrt, sizeof(command)}, address, source, size, protection, opcode ? opcode : 1011}; + if (dst) std::memcpy(dst, &command, sizeof(command)); + if (outSize) *outSize = sizeof(command); + return 0; +} + } diff --git a/core/libs/tests/GuestAmpr.cpp b/core/libs/tests/GuestAmpr.cpp index d2a646ac1..3b366edd4 100644 --- a/core/libs/tests/GuestAmpr.cpp +++ b/core/libs/tests/GuestAmpr.cpp @@ -108,6 +108,8 @@ int APS5_VABI sceKernelMunmap(void*, std::size_t); int APS5_VABI sceAmprAmmCommandBufferMapAsPrt(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t); int APS5_VABI sceAmprAmmCommandBufferAllocatePaForPrt(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t, std::int32_t, std::int32_t); int APS5_VABI sceAmprAmmCommandBufferRemapIntoPrt(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t, std::uint64_t, std::int32_t, std::uint32_t); +int APS5_VABI sceKernelWriteModifyMtypeProtectCommand(void*, std::uint64_t, std::uint64_t, std::uint64_t, std::uint64_t, std::uint64_t, std::uint64_t*); +int APS5_VABI sceKernelWriteRemapIntoPrtCommand(std::uint32_t*, std::uint64_t, std::uint64_t, std::uint64_t, int, int, std::uint64_t*); int APS5_VABI sceAmprAmmCommandBufferUnmapToPrt(Apr::CommandBufferObject*, std::uint64_t, std::uint64_t); std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeMapAsPrt(std::uint64_t, std::uint64_t); std::int64_t APS5_VABI sceAmprAmmMeasureAmmCommandSizeAllocatePaForPrt(std::uint64_t, std::uint64_t, std::int32_t, std::int32_t); @@ -1122,6 +1124,87 @@ void TestAmmPrt() { Require(sceAmprAmmMeasureAmmCommandSizeAllocatePaForPrt(prt + 8, page, 0, cpuReadWrite) == ammRejected); } +void TestKernelWriteCommands() { + std::uint64_t start = 0; + std::uint64_t end = 0; + std::uint64_t multimapStart = 0; + std::uint64_t multimapEnd = 0; + Require(sceAmprAmmGetVirtualAddressRanges(&start, &end, &multimapStart, &multimapEnd) == 0); + std::int64_t pool = -1; + Require(sceAmprAmmGiveDirectMemory(0, static_cast(sceKernelGetDirectMemorySize()), 4 * page, page, 1, &pool) == 0); + + const std::uint64_t target = start + 0x400000; + const std::uint64_t source = start + 0x440000; + const std::uint64_t prt = start + 0x480000; + + Recorder setup; + Require(sceAmprAmmCommandBufferMap(&setup.buffer, target, 2 * page, 0, cpuReadWrite) == 0); + Require(sceAmprAmmCommandBufferMap(&setup.buffer, source, page, 0, cpuReadWrite) == 0); + Require(sceAmprAmmCommandBufferMapAsPrt(&setup.buffer, prt, 2 * page) == 0); + SubmitAmm(setup); + At(source) = 0x9C; + + std::uint64_t measured = 0; + Require(sceKernelWriteModifyMtypeProtectCommand(nullptr, target, 2 * page, 5, 0x13, 0x10, &measured) == 0); + Require(measured == sizeof(Apr::AmmProtectCommand)); + std::array written{}; + std::uint64_t reported = 0; + Require(sceKernelWriteModifyMtypeProtectCommand(written.data(), target, 2 * page, 5, 0x13, 0x10, &reported) == 0); + Require(reported == sizeof(Apr::AmmProtectCommand)); + Apr::CommandHeader header; + std::memcpy(&header, written.data(), sizeof(header)); + Require(header.opcode == Apr::Opcode::AmmModifyMtypeProtect && header.bytes == sizeof(Apr::AmmProtectCommand)); + Recorder sibling; + Require(sceAmprAmmCommandBufferModifyMtypeProtect(&sibling.buffer, target, 2 * page, 5, 0x13, 0x10) == 0); + Require(std::memcmp(written.data(), sibling.memory.data(), sizeof(Apr::AmmProtectCommand)) == 0); + Require(sceKernelWriteModifyMtypeProtectCommand(written.data(), target, 2 * page, 5, 0x13, 0x10, nullptr) == 0); + std::uint32_t id = 0; + Require(sceAmprAmmSubmitCommandBuffer3(written.data(), static_cast(reported), 0, &id) == 0); + Require(sceAmprAmmWaitCommandBufferCompletion(id) == 0); + Require(Protection(target) == 0x13 && Protection(target + page) == 0x13); + At(target) = 0x9D; + Require(At(target) == 0x9D); + + std::uint64_t prtMeasured = 0; + Require(sceKernelWriteRemapIntoPrtCommand(nullptr, prt + page, source, page, cpuReadWrite, 0, &prtMeasured) == 0); + Require(prtMeasured == sizeof(Apr::AmmRemapCommand)); + std::array prtWritten{}; + Require(sceKernelWriteRemapIntoPrtCommand(reinterpret_cast(prtWritten.data()), prt + page, source, page, cpuReadWrite, 0, &prtMeasured) == 0); + Apr::AmmRemapCommand remap; + std::memcpy(&remap, prtWritten.data(), sizeof(remap)); + Require(remap.header.opcode == Apr::Opcode::AmmRemapIntoPrt && remap.header.bytes == sizeof(remap)); + Require(remap.address == prt + page && remap.source == source && remap.size == page && remap.protection == cpuReadWrite && remap.reserved == 1011); + Require(sceKernelWriteRemapIntoPrtCommand(reinterpret_cast(prtWritten.data()), prt + page, source, page, cpuReadWrite, 42, &prtMeasured) == 0); + std::memcpy(&remap, prtWritten.data(), sizeof(remap)); + Require(remap.reserved == 42); + Recorder prtSibling; + Require(sceAmprAmmCommandBufferRemapIntoPrt(&prtSibling.buffer, prt + page, source, page, cpuReadWrite, 0) == 0); + std::array prtSubmit{}; + Require(sceKernelWriteRemapIntoPrtCommand(reinterpret_cast(prtSubmit.data()), prt + page, source, page, cpuReadWrite, 0, nullptr) == 0); + Require(std::memcmp(prtSubmit.data(), prtSibling.memory.data(), sizeof(Apr::AmmRemapCommand)) == 0); + Require(sceAmprAmmSubmitCommandBuffer3(prtSubmit.data(), static_cast(sizeof(Apr::AmmRemapCommand)), 0, &id) == 0); + Require(sceAmprAmmWaitCommandBufferCompletion(id) == 0); + Require(At(prt + page) == 0x9C); + + std::array untouched{}; + untouched.fill(0xCC); + std::uint64_t sentinel = 0xDDDDDDDDDDDDDDDDULL; + Require(sceKernelWriteModifyMtypeProtectCommand(untouched.data(), target + 8, page, 0, 0x03, 0x03, &sentinel) == invalidArgument); + Require(sceKernelWriteModifyMtypeProtectCommand(untouched.data(), target, 0, 0, 0x03, 0x03, &sentinel) == invalidArgument); + Require(sceKernelWriteModifyMtypeProtectCommand(untouched.data(), 0, page, 0, 0x03, 0x03, &sentinel) == invalidArgument); + Require(sceKernelWriteModifyMtypeProtectCommand(untouched.data(), target, page, 0, 0x04, 0x03, &sentinel) == invalidArgument); + Require(sceKernelWriteModifyMtypeProtectCommand(untouched.data(), target, page, 0, 0x03, 0x04, &sentinel) == invalidArgument); + Require(sceKernelWriteModifyMtypeProtectCommand(untouched.data(), ~std::uint64_t(0), page, 0, 0x03, 0x03, &sentinel) == invalidArgument); + Require(sceKernelWriteModifyMtypeProtectCommand(untouched.data(), target, page, 0, 0x100000000ULL, 0x03, &sentinel) == invalidArgument); + Require(sceKernelWriteRemapIntoPrtCommand(reinterpret_cast(untouched.data()), prt, source + 8, page, cpuReadWrite, 0, &sentinel) == invalidArgument); + Require(sceKernelWriteRemapIntoPrtCommand(reinterpret_cast(untouched.data()), prt, source, 0, cpuReadWrite, 0, &sentinel) == invalidArgument); + Require(sceKernelWriteRemapIntoPrtCommand(reinterpret_cast(untouched.data()), prt, source, page, 0x04, 0, &sentinel) == invalidArgument); + Require(sentinel == 0xDDDDDDDDDDDDDDDDULL); + for (const auto byte : untouched) Require(byte == 0xCC); + Require(sceKernelWriteModifyMtypeProtectCommand(nullptr, target, page, 0, 0x03, 0x03, nullptr) == 0); + Require(sceKernelWriteRemapIntoPrtCommand(nullptr, prt, source, page, cpuReadWrite, 0, nullptr) == 0); +} + } int main() { @@ -1152,5 +1235,6 @@ int main() { TestAmm(); TestAmmRemapAndProtect(); TestAmmPrt(); + TestKernelWriteCommands(); return 0; } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 51f08cc9e..07b98364e 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -126,6 +126,7 @@ Throughout the project, every function at every stage either **does exactly what - [AMM remap, multimap and protection changes](../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libSceAmpr) - signatures and the protection and mask checks taken from ampr_emu; the effects are inferred from the names, not confirmed on a title. A remap moves every page of a fully mapped range to the new address with the given protection and leaves the old range reserved; a multimap maps the same pages at a second address, anywhere in the AMM ranges, and a pool page goes back to the pool once its last mapping is unmapped; a multimap onto its own source range throws. A protection change sets the masked bits on every page of a fully mapped range; the memory type is recorded and ignored. A command on a range that is not fully mapped throws - [sceVideodec2GetAvcPictureInfo](../../core/libs/prx/libSceVideodec2/Export.cpp) (libSceVideodec2) - runs the `sceVideodec2GetPictureInfo` body (KytyPS5 binds both NIDs to one function; SharpProspero gives both the same `outputInfo, first, second` signature); the second (bottom field) picture info is never written, since only progressive H.264 output is modelled. prosper calls the AVC layout unestablished (no title observed calling it) - [AMM PRT ranges](../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libSceAmpr) - signatures, checks and their order taken from ampr_emu, where `MapAsPrt` is a map with the PRT flag and protection 0 and `AllocatePaForPrt` a memory type and protection change with mask 1019, both checking the buffer memory after its size; the effects are inferred from the names, not confirmed on a title. Unbacked PRT pages are zero-filled pages with GPU read access, so CPU reads see zeros instead of faulting and GPU writes fault instead of being dropped. `AllocatePaForPrt` backs each unbacked page with a pool page and sets the type and protection of backed ones; `RemapIntoPrt` moves a fully mapped range into a PRT range, its opcode argument (1011 when 0, as in ampr_emu) is recorded and ignored; `UnmapToPrt` returns the pages to the pool and zero-fills them again. A plain map, unmap or remap over a PRT range ends it; `AllocatePaForPrt`, `RemapIntoPrt` or `UnmapToPrt` outside a PRT range throws +- [AMM kernel write commands](../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libkernel) - signatures of `sceKernelWriteModifyMtypeProtectCommand` and `sceKernelWriteRemapIntoPrtCommand` taken from [ampr_emu's libkernel hook targets](https://github.com/drakmor/ampr_emu/blob/cfa85df379f6eeeb165d7badf9b648e266fe77b7/include/ampr_libkernel_hook_targets.h), which forward to the real kernel; the measure mode (`dst` null reports the size, `outSize` null skips the report) follows ampr_emu's measure-then-record calls, not confirmed on a title. The range, protection and mask checks mirror the `libSceAmpr` siblings, and the recorded bytes match them exactly so the submitted buffer executes on the existing paths - [APR map begin and end](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - as in ampr_emu, `MapBegin`/`MapDirectBegin` map when they run and `MapEnd` only closes the region; while a region is open a second begin and every completion write return `EPERM`, the at-start `_04_00` writes are accepted - [AMPR markers](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - signatures taken from the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation, not confirmed on a title: `sceAmprCommandBufferSetMarkerWithColor` takes the color by pointer, the push and measure variants by value. Colors are not recorded - [sceFontGetRenderScaledKerning](../../core/libs/prx/libSceFont/src/Render.cpp) (libSceFont) - arguments, the cleared output and the bound renderer check taken from `cellFontGetRenderScaledKerning` in [RPCS3](https://github.com/RPCS3/rpcs3/blob/master/rpcs3/Emu/Cell/Modules/cellFont.cpp) (PS3 predecessor of the library), not confirmed on a title. The kerning is the `sceFontGetKerning` one at the render scale