diff --git a/.gitmodules b/.gitmodules index dcde427ae7..181cb3dfb9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -61,3 +61,6 @@ [submodule "3rdparty/vorbis"] path = 3rdparty/vorbis url = https://github.com/xiph/vorbis.git +[submodule "3rdparty/jxrlib"] + path = 3rdparty/jxrlib + url = https://github.com/4creators/jxrlib.git diff --git a/3rdparty/jxrlib b/3rdparty/jxrlib new file mode 160000 index 0000000000..f752187986 --- /dev/null +++ b/3rdparty/jxrlib @@ -0,0 +1 @@ +Subproject commit f7521879862b9085318e814c6157490dd9dbbdb4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 155214af16..5580e5a860 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -385,6 +385,7 @@ set_target_properties(usb-1.0 PROPERTIES POSITION_INDEPENDENT_CODE ON) include(cmake/ZArchive.cmake) include(cmake/Opus.cmake) +include(cmake/Jxr.cmake) if(WIN32) set(MI_BUILD_SHARED ON CACHE BOOL "" FORCE) set(MI_BUILD_STATIC OFF CACHE BOOL "" FORCE) diff --git a/cmake/Jxr.cmake b/cmake/Jxr.cmake new file mode 100644 index 0000000000..2935946f23 --- /dev/null +++ b/cmake/Jxr.cmake @@ -0,0 +1,43 @@ +enable_language(C) +set(JXR_SOURCE_DIR ${CMAKE_SOURCE_DIR}/3rdparty/jxrlib) + +function(configure_jxr_native target) + target_include_directories(${target} SYSTEM PRIVATE + ${JXR_SOURCE_DIR}/common/include ${JXR_SOURCE_DIR}/image/sys ${JXR_SOURCE_DIR}/jxrgluelib) + target_compile_definitions(${target} PRIVATE DISABLE_PERF_MEASUREMENT) + if(WIN32) + target_compile_definitions(${target} PRIVATE WIN32 __in=) + target_compile_options(${target} PRIVATE -include ${JXR_SOURCE_DIR}/image/sys/strcodec.h) + else() + target_compile_definitions(${target} PRIVATE __ANSI__) + endif() + set_target_properties(${target} PROPERTIES C_STANDARD 99 C_STANDARD_REQUIRED ON + POSITION_INDEPENDENT_CODE ON C_VISIBILITY_PRESET hidden) +endfunction() + +add_library(jxr STATIC EXCLUDE_FROM_ALL + ${JXR_SOURCE_DIR}/image/sys/adapthuff.c + ${JXR_SOURCE_DIR}/image/sys/image.c + ${JXR_SOURCE_DIR}/image/sys/strcodec.c + ${JXR_SOURCE_DIR}/image/sys/strPredQuant.c + ${JXR_SOURCE_DIR}/image/sys/strTransform.c + ${JXR_SOURCE_DIR}/image/decode/decode.c + ${JXR_SOURCE_DIR}/image/decode/JXRTranscode.c + ${JXR_SOURCE_DIR}/image/decode/postprocess.c + ${JXR_SOURCE_DIR}/image/decode/segdec.c + ${JXR_SOURCE_DIR}/image/decode/strdec.c + ${JXR_SOURCE_DIR}/image/decode/strInvTransform.c + ${JXR_SOURCE_DIR}/image/decode/strPredQuantDec.c + ${JXR_SOURCE_DIR}/image/encode/encode.c + ${JXR_SOURCE_DIR}/image/encode/segenc.c + ${JXR_SOURCE_DIR}/image/encode/strenc.c + ${JXR_SOURCE_DIR}/image/encode/strFwdTransform.c + ${JXR_SOURCE_DIR}/image/encode/strPredQuantEnc.c + ${JXR_SOURCE_DIR}/jxrgluelib/JXRGlue.c + ${JXR_SOURCE_DIR}/jxrgluelib/JXRGlueJxr.c + ${JXR_SOURCE_DIR}/jxrgluelib/JXRGluePFC.c + ${JXR_SOURCE_DIR}/jxrgluelib/JXRMeta.c) +configure_jxr_native(jxr) +if(UNIX) + target_link_libraries(jxr PRIVATE m) +endif() diff --git a/core/libs/prx/libSceJxr/CMakeLists.txt b/core/libs/prx/libSceJxr/CMakeLists.txt index 02202c7522..261516c533 100644 --- a/core/libs/prx/libSceJxr/CMakeLists.txt +++ b/core/libs/prx/libSceJxr/CMakeLists.txt @@ -1,8 +1,38 @@ -add_library(libSceJxr SHARED EXCLUDE_FROM_ALL Export.cpp) +add_library(jxr_lookup_native STATIC src/NativeLookup.c) +configure_jxr_native(jxr_lookup_native) +target_include_directories(jxr_lookup_native PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(jxr_lookup_native PRIVATE jxr) +add_library(libSceJxr SHARED EXCLUDE_FROM_ALL Export.cpp src/PixelFormatLookup.cpp) +target_link_libraries(libSceJxr PRIVATE jxr_lookup_native) +if(NOT APPLE) + target_link_options(libSceJxr PRIVATE LINKER:--exclude-libs,ALL) +endif() target_include_directories(libSceJxr PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libSceJxr) if(BUILD_TESTING) + add_library(jxr_lookup_reference STATIC ${LIBS_INCLUDE_DIR}/tests/GuestJxrLookupReference.c) + configure_jxr_native(jxr_lookup_reference) + target_link_libraries(jxr_lookup_reference PRIVATE jxr) + add_test_executable(guest_jxr_lookup_tests ${LIBS_INCLUDE_DIR}/tests/GuestJxrLookup.cpp) + target_include_directories(guest_jxr_lookup_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_jxr_lookup_tests PRIVATE jxr_lookup_reference ${CMAKE_DL_LIBS}) + if(WIN32) + target_link_libraries(guest_jxr_lookup_tests PRIVATE libc) + endif() + configure_windows_unwind(guest_jxr_lookup_tests) + add_custom_target(jxr_lookup_patched DEPENDS ${PATCHED_DIR}/libSceJxr.prx ${LIBC_PATCHED}) + add_dependencies(guest_jxr_lookup_tests jxr_lookup_patched) + add_test(NAME guest_jxr_lookup_reference COMMAND guest_jxr_lookup_tests reference) + add_test(NAME guest_jxr_lookup COMMAND guest_jxr_lookup_tests $ raw) + add_test(NAME guest_jxr_lookup_patched COMMAND guest_jxr_lookup_tests ${PATCHED_DIR}/libSceJxr.prx patched) + set_tests_properties(guest_jxr_lookup_reference guest_jxr_lookup guest_jxr_lookup_patched PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_jxr_lookup_reference guest_jxr_lookup PROPERTIES + ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + set_tests_properties(guest_jxr_lookup_patched PROPERTIES + ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:${PATCHED_DIR}") + endif() add_test_executable(guest_jxr_tests ${LIBS_INCLUDE_DIR}/tests/GuestJxr.cpp) target_include_directories(guest_jxr_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_jxr_tests PRIVATE libSceJxr libc) diff --git a/core/libs/prx/libSceJxr/Export.cpp b/core/libs/prx/libSceJxr/Export.cpp index 52c894fef6..df9d322a78 100644 --- a/core/libs/prx/libSceJxr/Export.cpp +++ b/core/libs/prx/libSceJxr/Export.cpp @@ -24,11 +24,6 @@ std::int64_t APS5_VABI PKFree_nid_postfix(void** memory) { return WmpErrSuccess; } -int APS5_VABI PixelFormatLookup_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - std::int64_t APS5_VABI readIS_nid_postfix(void*, Jxr::BitIo* io) { if (!io) NotImplemented_nid_no_patch(__func__); constexpr std::uintptr_t packetSize = 4096; diff --git a/core/libs/prx/libSceJxr/GuestPixelInfo.h b/core/libs/prx/libSceJxr/GuestPixelInfo.h new file mode 100644 index 0000000000..95a9893d0a --- /dev/null +++ b/core/libs/prx/libSceJxr/GuestPixelInfo.h @@ -0,0 +1,15 @@ +#pragma once +#include + +typedef struct JxrPixelInfo { + const void* guid; + uint64_t channels; + int32_t colorFormat; + int32_t bitDepth; + uint32_t bitsPerUnit; + uint64_t flags; + uint32_t interpretation; + uint32_t samplesPerPixel; + uint32_t bitsPerSample; + uint32_t sampleFormat; +} JxrPixelInfo; diff --git a/core/libs/prx/libSceJxr/src/NativeLookup.c b/core/libs/prx/libSceJxr/src/NativeLookup.c new file mode 100644 index 0000000000..086cc753cf --- /dev/null +++ b/core/libs/prx/libSceJxr/src/NativeLookup.c @@ -0,0 +1,40 @@ +#include "strcodec.h" +#include "JXRGlue.h" +#include "prx/libSceJxr/GuestPixelInfo.h" +#include +#include + +_Static_assert(sizeof(GUID) == 16, "GUID size"); +_Static_assert(sizeof(COLORFORMAT) == 4 && sizeof(BITDEPTH_BITS) == 4, "enum size"); +_Static_assert(sizeof(U32) == 4 && sizeof(JxrPixelInfo) == 56, "pixel info layout"); +_Static_assert(offsetof(JxrPixelInfo, flags) == 32 && offsetof(JxrPixelInfo, interpretation) == 40, "LP64 offsets"); + +int64_t JxrLookup_nid_no_patch(JxrPixelInfo* value, uint8_t mode) { + PKPixelInfo native = {0}; + GUID guid; + ERR result; + if (mode > LOOKUP_BACKWARD_TIF) return PixelFormatLookup(NULL, mode); + if (mode == LOOKUP_FORWARD) { + memcpy(&guid, value->guid, sizeof(guid)); + native.pGUIDPixFmt = &guid; + } else { + native.grBit = (WMP_GRBIT)value->flags; + native.uInterpretation = value->interpretation; + native.uSamplePerPixel = value->samplesPerPixel; + native.uBitsPerSample = value->bitsPerSample; + native.uSampleFormat = value->sampleFormat; + } + result = PixelFormatLookup(&native, mode); + if (result != WMP_errSuccess) return result; + value->guid = native.pGUIDPixFmt; + value->channels = native.cChannel; + value->colorFormat = native.cfColorFormat; + value->bitDepth = native.bdBitDepth; + value->bitsPerUnit = native.cbitUnit; + value->flags = native.grBit; + value->interpretation = native.uInterpretation; + value->samplesPerPixel = native.uSamplePerPixel; + value->bitsPerSample = native.uBitsPerSample; + value->sampleFormat = native.uSampleFormat; + return result; +} diff --git a/core/libs/prx/libSceJxr/src/PixelFormatLookup.cpp b/core/libs/prx/libSceJxr/src/PixelFormatLookup.cpp new file mode 100644 index 0000000000..d296e04483 --- /dev/null +++ b/core/libs/prx/libSceJxr/src/PixelFormatLookup.cpp @@ -0,0 +1,31 @@ +#include "prx/libSceJxr/GuestPixelInfo.h" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include + +extern "C" std::int64_t JxrLookup_nid_no_patch(JxrPixelInfo* value, std::uint8_t mode); + +static_assert(sizeof(JxrPixelInfo) == 56 && alignof(JxrPixelInfo) == 8); +static_assert(offsetof(JxrPixelInfo, flags) == 32 && offsetof(JxrPixelInfo, interpretation) == 40); + +extern "C" std::int64_t APS5_VABI PixelFormatLookup_nid_postfix(JxrPixelInfo* value, std::uint8_t mode) { + if (mode > 1) return JxrLookup_nid_no_patch(nullptr, mode); + if (value == nullptr) throw std::invalid_argument("PixelFormatLookup: null record behavior is unknown"); + JxrPixelInfo local{}; + auto* bytes = reinterpret_cast(value); + if (mode == 0) { + std::memcpy(&local.guid, bytes, sizeof(local.guid)); + if (local.guid == nullptr) throw std::invalid_argument("PixelFormatLookup: null GUID behavior is unknown"); + } else { + std::memcpy(&local.flags, bytes + offsetof(JxrPixelInfo, flags), sizeof(local.flags)); + std::memcpy(&local.interpretation, bytes + offsetof(JxrPixelInfo, interpretation), 4 * sizeof(std::uint32_t)); + } + const auto result = JxrLookup_nid_no_patch(&local, mode); + if (result == 0) { + std::memcpy(bytes, &local, offsetof(JxrPixelInfo, bitsPerUnit) + sizeof(local.bitsPerUnit)); + std::memcpy(bytes + offsetof(JxrPixelInfo, flags), &local.flags, sizeof(local) - offsetof(JxrPixelInfo, flags)); + } + return result; +} diff --git a/core/libs/tests/GuestJxrLookup.cpp b/core/libs/tests/GuestJxrLookup.cpp new file mode 100644 index 0000000000..13a721d7ee --- /dev/null +++ b/core/libs/tests/GuestJxrLookup.cpp @@ -0,0 +1,249 @@ +#include "prx/libSceJxr/GuestPixelInfo.h" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +extern "C" int JxrReferenceCase_nid_no_patch(std::uint8_t, int, int, std::uint8_t*, std::uint64_t*); +using Query = std::int64_t (APS5_VABI*)(JxrPixelInfo*, std::uint8_t); +static_assert(sizeof(JxrPixelInfo) == 56 && alignof(JxrPixelInfo) == 8); +static_assert(offsetof(JxrPixelInfo, guid) == 0 && offsetof(JxrPixelInfo, channels) == 8); +static_assert(offsetof(JxrPixelInfo, colorFormat) == 16 && offsetof(JxrPixelInfo, bitDepth) == 20); +static_assert(offsetof(JxrPixelInfo, bitsPerUnit) == 24 && offsetof(JxrPixelInfo, flags) == 32); +static_assert(offsetof(JxrPixelInfo, interpretation) == 40 && offsetof(JxrPixelInfo, sampleFormat) == 52); + +namespace { + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "JXR lookup: %s\n", message); + std::exit(1); +} + +struct Reference { + std::array guid{}; + std::array fields{}; +}; + +bool ReadReference(unsigned hash, bool reverse, bool white, Reference& expected) { + const auto result = JxrReferenceCase_nid_no_patch(static_cast(hash), reverse, white, + expected.guid.data(), expected.fields.data()); + Require(result >= 0, "native lookup reference failed"); + return result != 0; +} + +JxrPixelInfo BackwardInput(const Reference& source) { + JxrPixelInfo input{}; + input.guid = reinterpret_cast(std::uintptr_t{1}); + input.channels = UINT64_MAX; + input.colorFormat = -1; + input.bitDepth = -1; + input.bitsPerUnit = UINT32_MAX; + input.flags = source.fields[4] | UINT64_C(0xfedcba9880000040); + input.interpretation = static_cast(source.fields[5]); + input.samplesPerPixel = static_cast(source.fields[6]); + input.bitsPerSample = static_cast(source.fields[7]); + input.sampleFormat = static_cast(source.fields[8]); + return input; +} + +void CheckValue(const JxrPixelInfo& actual, const Reference& expected) { + const std::array fields{{actual.channels, static_cast(actual.colorFormat), + static_cast(actual.bitDepth), actual.bitsPerUnit, actual.flags, actual.interpretation, + actual.samplesPerPixel, actual.bitsPerSample, actual.sampleFormat}}; + Require(fields == expected.fields, "metadata differs from pinned native reference"); + Require(actual.guid != nullptr, "successful lookup returned null GUID"); + Require(std::memcmp(actual.guid, expected.guid.data(), 16) == 0, "GUID differs from pinned native reference"); +} + +class GuardedTail { + void* memory; + std::size_t page; + void* data; +public: + GuardedTail(const void* source, std::size_t bytes) { +#ifdef _WIN32 + SYSTEM_INFO info; + GetSystemInfo(&info); + page = info.dwPageSize; + memory = VirtualAlloc(nullptr, page * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + Require(memory != nullptr, "guard allocation"); + DWORD previous; + Require(VirtualProtect(static_cast(memory) + page, page, PAGE_NOACCESS, &previous) != 0, "guard protection"); +#else + page = static_cast(sysconf(_SC_PAGESIZE)); + memory = mmap(nullptr, page * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(memory != MAP_FAILED, "guard allocation"); + Require(mprotect(static_cast(memory) + page, page, PROT_NONE) == 0, "guard protection"); +#endif + Require(bytes <= page, "guard fixture size"); + data = static_cast(memory) + page - bytes; + std::memcpy(data, source, bytes); + } + void ReadOnly() { +#ifdef _WIN32 + DWORD previous; + Require(VirtualProtect(memory, page, PAGE_READONLY, &previous) != 0, "read-only protection"); +#else + Require(mprotect(memory, page, PROT_READ) == 0, "read-only protection"); +#endif + } + ~GuardedTail() { +#ifdef _WIN32 + VirtualFree(memory, 0, MEM_RELEASE); +#else + munmap(memory, page * 2); +#endif + } + template T* As() const { return static_cast(data); } +}; + +void Cases(Query query) { + Require(query(nullptr, 2) == -106, "invalid mode has signed 64-bit unsupported-format result"); + Reference forward; + unsigned formats = 0; + for (unsigned hash = 0; hash < 256; ++hash) { + if (!ReadReference(hash, false, false, forward)) continue; + ++formats; + JxrPixelInfo input; + std::memset(&input, 0xa5, sizeof(input)); + input.guid = forward.guid.data(); + const auto originalGuid = forward.guid; + Require(query(&input, 0) == 0, "forward lookup"); + CheckValue(input, forward); + Require(forward.guid == originalGuid, "lookup modified input GUID"); + Reference backward; + Require(ReadReference(hash, true, false, backward), "backward native case missing"); + input = BackwardInput(forward); + Require(query(&input, 1) == 0, "backward lookup ignores GUID and high flag bits"); + CheckValue(input, backward); + } + Require(formats != 0, "reference format inventory empty"); + Reference white; + Require(ReadReference(5, false, false, forward), "black-white reference"); + Require(ReadReference(5, true, true, white), "white-is-zero reference"); + Require(forward.fields[5] == 1 && white.fields[5] == 0, "duplicate GUID reference order"); + auto input = BackwardInput(forward); + input.interpretation = 0; + Require(query(&input, 1) == 0, "backward white-is-zero lookup"); + CheckValue(input, white); + Require(query(&input, 0) == 0, "forward lookup selects first duplicate GUID"); + CheckValue(input, forward); + for (unsigned mode = 2; mode < 256; ++mode) { + std::memset(&input, 0xa5, sizeof(input)); + std::array before; + std::memcpy(before.data(), &input, sizeof(input)); + Require(query(&input, static_cast(mode)) == -106, "invalid mode error"); + Require(std::memcmp(&input, before.data(), sizeof(input)) == 0, "invalid mode changed record"); + Require(query(reinterpret_cast(std::uintptr_t{1}), static_cast(mode)) == -106, + "invalid mode must not read inaccessible record"); + } + std::array unknown{}; + std::memset(&input, 0xff, sizeof(input)); + input.guid = unknown.data(); + std::array before; + std::memcpy(before.data(), &input, sizeof(input)); + Require(query(&input, 0) == -106 && std::memcmp(&input, before.data(), sizeof(input)) == 0, "unknown GUID unchanged"); + Require(query(&input, 1) == -106 && std::memcmp(&input, before.data(), sizeof(input)) == 0, "unknown TIFF fields unchanged"); + GuardedTail failedRecord(&input, sizeof(input)); + failedRecord.ReadOnly(); + Require(query(failedRecord.As(), 0) == -106, "failed lookup does not write read-only record"); + const void* unknownPointer = unknown.data(); + GuardedTail failedPrefix(&unknownPointer, sizeof(unknownPointer)); + failedPrefix.ReadOnly(); + Require(query(failedPrefix.As(), 0) == -106, "failed forward lookup only reads GUID pointer"); + Require(ReadReference(8, false, false, forward), "Gray8 reference"); + Require(forward.fields[0] == 1 && forward.fields[7] == 8, "Gray8 reference values"); + GuardedTail guid(forward.guid.data(), 16); + guid.ReadOnly(); + input = {}; + input.guid = guid.As(); + GuardedTail record(&input, sizeof(input)); + Require(query(record.As(), 0) == 0, "bounded record and read-only GUID"); + CheckValue(*record.As(), forward); + input = BackwardInput(forward); + input.guid = nullptr; + Require(query(&input, 1) == 0, "backward lookup ignores null GUID"); + CheckValue(input, forward); + alignas(16) std::array unalignedGuid{}; + std::memcpy(unalignedGuid.data() + 1, forward.guid.data(), 16); + input = {}; + input.guid = unalignedGuid.data() + 1; + Require(query(&input, 0) == 0, "unaligned GUID lookup"); + CheckValue(input, forward); + const auto borrowedGuid = record.As()->guid; + std::array threads; + for (auto& thread : threads) thread = std::thread([&] { + for (unsigned i = 0; i < 32; ++i) { + JxrPixelInfo value{}; + value.guid = forward.guid.data(); + Require(query(&value, 0) == 0, "concurrent independent lookup"); + CheckValue(value, forward); + Require(value.guid == borrowedGuid, "borrowed GUID pointer changed across calls"); + } + }); + for (auto& thread : threads) thread.join(); + for (const bool nullGuid : {false, true}) { + bool threw = false; + input = {}; + try { query(nullGuid ? &input : nullptr, 0); } catch (const std::invalid_argument&) { threw = true; } + Require(threw, "valid-mode null record/GUID is explicitly unsupported"); + } + bool threw = false; + try { query(nullptr, 1); } catch (const std::invalid_argument&) { threw = true; } + Require(threw, "backward null record is explicitly unsupported"); + std::printf("JXR guest lookup cases passed across %u reference hashes\n", formats); +} + +} + +int main(int argc, char** argv) { +#ifdef _WIN32 + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); +#endif + try { + if (argc == 2 && std::strcmp(argv[1], "reference") == 0) { + Reference gray; + Require(ReadReference(8, false, false, gray) && gray.fields[0] == 1 && gray.fields[7] == 8, "native Gray8 reference"); + unsigned formats = 0; + for (unsigned hash = 0; hash < 256; ++hash) { Reference item; if (ReadReference(hash, false, false, item)) ++formats; } + std::printf("Pinned native JXR reference passed with %u hashes\n", formats); + return 0; + } + Require(argc == 3, "expected library and export mode"); + const char* name = std::strcmp(argv[2], "patched") == 0 ? "5Gjz1nLOKvo" : "PixelFormatLookup_nid_postfix"; +#ifdef _WIN32 + const auto library = LoadLibraryA(argv[1]); + Require(library != nullptr, "load requested library"); + const auto query = reinterpret_cast(GetProcAddress(library, name)); +#else + const auto library = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL); + Require(library != nullptr, "load requested library"); + const auto query = reinterpret_cast(dlsym(library, name)); +#endif + Require(query != nullptr, "missing lookup export"); + Cases(query); +#ifdef _WIN32 + FreeLibrary(library); +#else + dlclose(library); +#endif + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "JXR lookup exception: %s\n", error.what()); + return 1; + } +} diff --git a/core/libs/tests/GuestJxrLookupReference.c b/core/libs/tests/GuestJxrLookupReference.c new file mode 100644 index 0000000000..46d61a89e1 --- /dev/null +++ b/core/libs/tests/GuestJxrLookupReference.c @@ -0,0 +1,31 @@ +#include "strcodec.h" +#include "JXRGlue.h" +#include +#include + +_Static_assert(sizeof(GUID) == 16, "GUID size"); +_Static_assert(sizeof(U32) == 4, "U32 size"); + +int JxrReferenceCase_nid_no_patch(uint8_t hash, int reverse, int white, uint8_t* guid, uint64_t* fields) { + PKPixelInfo native = {0}; + native.pGUIDPixFmt = GetPixelFormatFromHash(hash); + if (!native.pGUIDPixFmt) return 0; + if (PixelFormatLookup(&native, LOOKUP_FORWARD) != 0) return -1; + if (reverse) { + native.pGUIDPixFmt = NULL; + native.grBit |= PK_pixfmtBGR | PK_pixfmtNeedConvert; + if (white) native.uInterpretation = 0; + if (PixelFormatLookup(&native, LOOKUP_BACKWARD_TIF) != 0) return -1; + } + memcpy(guid, native.pGUIDPixFmt, 16); + fields[0] = native.cChannel; + fields[1] = native.cfColorFormat; + fields[2] = native.bdBitDepth; + fields[3] = native.cbitUnit; + fields[4] = native.grBit; + fields[5] = native.uInterpretation; + fields[6] = native.uSamplePerPixel; + fields[7] = native.uBitsPerSample; + fields[8] = native.uSampleFormat; + return 1; +} diff --git a/docs/dev/debt/libSceJxr.md b/docs/dev/debt/libSceJxr.md index 2d93664275..2d5247906d 100644 --- a/docs/dev/debt/libSceJxr.md +++ b/docs/dev/debt/libSceJxr.md @@ -1,11 +1,9 @@ # libSceJxr technical debt -### Unknown function info - -- [PixelFormatLookup](../../../core/libs/prx/libSceJxr/Export.cpp) is listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceJxr.c) and was cross-checked against firmware 13.60. Its signature and behaviour remain unverified; the declaration uses the no-parameter unknown-signature convention and throws through `NotImplemented_nid_no_patch`. - ### Functional +- [PixelFormatLookup](../../../core/libs/prx/libSceJxr/src/PixelFormatLookup.cpp) uses the pinned public jxrlib table and lookup algorithm through a C bridge. The signed 64-bit result and 56-byte LP64 pixel-info model, 16-byte GUID, 32-bit enums and TIFF fields, module/library versions and Sony table/configuration equivalence are inferred or unknown, not console-measured. Modes 0/1 follow forward GUID/reverse TIFF lookup; other modes and misses return the public unsupported-format error without modifying the record. Valid-mode null records and forward null GUIDs throw because console behavior is unknown. Success returns a borrowed read-only static GUID valid while the module remains loaded; the caller must not free it. Closes when: Sony prototypes, layouts, configuration, table and invalid-argument behavior are verified by attributable disassembly or console measurements + - [PKFree](../../../core/libs/prx/libSceJxr/Export.cpp) (libSceJxr) follows Microsoft's jxrlib [JXRGlue.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/jxrgluelib/JXRGlue.c#L53-L62), whose four exported names the module shares: a non-null `ppv` has `*ppv` released with the guest `free` and set to null, and the result is always `WMP_errSuccess` (0) as a 64-bit `ERR` (`long`). That the console's module is built from jxrlib, and that its `PKFree` releases through libc's `free`, is inferred from the export names and not checked against the console's code. Closes when: the console's `PKFree` is disassembled or measured on a PS5 - [readIS and writeIS](../../../core/libs/prx/libSceJxr/Export.cpp) use the non-ARM, LP64 `BitIOInfo` and `WMPStream` ABI and packet algorithm from [Microsoft's JPEG XR reference implementation](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/image/sys/strcodec.c#L1090-L1177). Console structure layouts and execution remain unverified. Profiling callbacks are omitted as in the reference Unix build with `DISABLE_PERF_MEASUREMENT`; the codec argument is unused. Invalid null state or required callbacks throw instead of dereferencing them. No codec is instantiated by these helpers; callers must supply the reference ring buffer and guest-ABI stream callbacks.