diff --git a/.gitignore b/.gitignore index c331f9e70..683f6f0a6 100644 --- a/.gitignore +++ b/.gitignore @@ -417,3 +417,4 @@ FodyWeavers.xsd # JetBrains .idea +cmake-build-* diff --git a/.gitmodules b/.gitmodules index 07d1d4ef7..fb859c87d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -102,3 +102,6 @@ [submodule "externals/LibAtrac9"] path = externals/LibAtrac9 url = https://github.com/shadps4-emu/ext-LibAtrac9.git +[submodule "externals/libpng"] + path = externals/libpng + url = https://github.com/pnggroup/libpng diff --git a/CMakeLists.txt b/CMakeLists.txt index e135794ec..a967c540c 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -830,7 +830,7 @@ endif() create_target_directory_groups(shadps4) -target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half) +target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half zlib-ng::zlib png_static) target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3 pugixml::pugixml) target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h") @@ -860,9 +860,9 @@ if (NOT ENABLE_QT_GUI) endif() if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND MSVC) - target_link_libraries(shadps4 PRIVATE cryptoppwin zlib-ng::zlib) + target_link_libraries(shadps4 PRIVATE cryptoppwin) else() - target_link_libraries(shadps4 PRIVATE cryptopp::cryptopp zlib-ng::zlib) + target_link_libraries(shadps4 PRIVATE cryptopp::cryptopp) endif() if (ENABLE_QT_GUI) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 17d710878..8d080acc3 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -58,8 +58,10 @@ if (NOT TARGET zlib-ng::zlib) set(WITH_GTEST OFF) set(WITH_NEW_STRATEGIES ON) set(WITH_NATIVE_INSTRUCTIONS ON) + set(ZLIB_COMPAT ON CACHE BOOL "" FORCE) add_subdirectory(zlib-ng) add_library(zlib-ng::zlib ALIAS zlib) + add_library(ZLIB::ZLIB ALIAS zlib) endif() # SDL3 @@ -153,6 +155,17 @@ if (NOT TARGET half::half) add_library(half::half ALIAS half) endif() +# libpng +set(PNG_SHARED OFF CACHE BOOL "" FORCE) +set(PNG_STATIC ON CACHE BOOL "" FORCE) +set(PNG_TESTS OFF CACHE BOOL "" FORCE) +set(PNG_TOOLS OFF CACHE BOOL "" FORCE) +set(SKIP_INSTALL_ALL OFF CACHE BOOL "" FORCE) +set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/zlib-ng" CACHE STRING "" FORCE) +set(ZLIB_LIBRARY "${CMAKE_CURRENT_BINARY_DIR}/zlib-ng/zlibstatic-ngd" CACHE STRING "" FORCE) +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/libpng/zlib.h" "#include \"../zlib-ng/zlib.h\"") +add_subdirectory(libpng) + if (APPLE) # date if (NOT TARGET date::date-tz) diff --git a/externals/libpng b/externals/libpng new file mode 160000 index 000000000..c1cc0f3f4 --- /dev/null +++ b/externals/libpng @@ -0,0 +1 @@ +Subproject commit c1cc0f3f4c3d4abd11ca68c59446a29ff6f95003 diff --git a/src/core/file_format/pkg.cpp b/src/core/file_format/pkg.cpp index 0ae9f57eb..a6b5eb9a8 100644 --- a/src/core/file_format/pkg.cpp +++ b/src/core/file_format/pkg.cpp @@ -1,31 +1,30 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include +#include #include "common/io_file.h" #include "common/logging/formatter.h" #include "core/file_format/pkg.h" #include "core/file_format/pkg_type.h" -static void DecompressPFSC(std::span compressed_data, - std::span decompressed_data) { - zng_stream decompressStream; +static void DecompressPFSC(std::span compressed_data, std::span decompressed_data) { + z_stream decompressStream; decompressStream.zalloc = Z_NULL; decompressStream.zfree = Z_NULL; decompressStream.opaque = Z_NULL; - if (zng_inflateInit(&decompressStream) != Z_OK) { + if (inflateInit(&decompressStream) != Z_OK) { // std::cerr << "Error initializing zlib for deflation." << std::endl; } decompressStream.avail_in = compressed_data.size(); - decompressStream.next_in = reinterpret_cast(compressed_data.data()); + decompressStream.next_in = reinterpret_cast(compressed_data.data()); decompressStream.avail_out = decompressed_data.size(); - decompressStream.next_out = reinterpret_cast(decompressed_data.data()); + decompressStream.next_out = reinterpret_cast(decompressed_data.data()); - if (zng_inflate(&decompressStream, Z_FINISH)) { + if (inflate(&decompressStream, Z_FINISH)) { } - if (zng_inflateEnd(&decompressStream) != Z_OK) { + if (inflateEnd(&decompressStream) != Z_OK) { // std::cerr << "Error ending zlib inflate" << std::endl; } } diff --git a/src/core/libraries/error_codes.h b/src/core/libraries/error_codes.h index 6bbdc3080..280ddf1cb 100644 --- a/src/core/libraries/error_codes.h +++ b/src/core/libraries/error_codes.h @@ -593,7 +593,6 @@ constexpr int ORBIS_VIDEODEC2_ERROR_INVALID_SEQUENCE = 0x811D0303; constexpr int ORBIS_VIDEODEC2_ERROR_FATAL_STREAM = 0x811D0304; // Videodec library - constexpr int ORBIS_VIDEODEC_ERROR_API_FAIL = 0x80C10000; constexpr int ORBIS_VIDEODEC_ERROR_CODEC_TYPE = 0x80C10001; constexpr int ORBIS_VIDEODEC_ERROR_STRUCT_SIZE = 0x80C10002; @@ -616,3 +615,14 @@ constexpr int ORBIS_VIDEODEC_ERROR_MISMATCH_SPEC = 0x80C10012; constexpr int ORBIS_VIDEODEC_ERROR_INVALID_SEQUENCE = 0x80C10013; constexpr int ORBIS_VIDEODEC_ERROR_FATAL_STREAM = 0x80C10014; constexpr int ORBIS_VIDEODEC_ERROR_FATAL_STATE = 0x80C10015; + +// PngDec library +constexpr int ORBIS_PNG_DEC_ERROR_INVALID_ADDR = 0x80690001; +constexpr int ORBIS_PNG_DEC_ERROR_INVALID_SIZE = 0x80690002; +constexpr int ORBIS_PNG_DEC_ERROR_INVALID_PARAM = 0x80690003; +constexpr int ORBIS_PNG_DEC_ERROR_INVALID_HANDLE = 0x80690004; +constexpr int ORBIS_PNG_DEC_ERROR_INVALID_WORK_MEMORY = 0x80690005; +constexpr int ORBIS_PNG_DEC_ERROR_INVALID_DATA = 0x80690010; +constexpr int ORBIS_PNG_DEC_ERROR_UNSUPPORT_DATA = 0x80690011; +constexpr int ORBIS_PNG_DEC_ERROR_DECODE_ERROR = 0x80690012; +constexpr int ORBIS_PNG_DEC_ERROR_FATAL = 0x80690020; \ No newline at end of file diff --git a/src/core/libraries/libpng/pngdec.cpp b/src/core/libraries/libpng/pngdec.cpp index 3a5d1ba71..fb9fae4d6 100644 --- a/src/core/libraries/libpng/pngdec.cpp +++ b/src/core/libraries/libpng/pngdec.cpp @@ -1,55 +1,47 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include "common/assert.h" #include "common/logging/log.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" -#include "externals/stb_image.h" #include "pngdec.h" namespace Libraries::PngDec { -void setImageInfoParams(OrbisPngDecImageInfo* imageInfo, int width, int height, int channels, - bool isInterlaced, bool isTransparent) { - if (imageInfo != nullptr) { - imageInfo->imageWidth = width; - imageInfo->imageHeight = height; - imageInfo->bitDepth = 8; // always 8? - switch (channels) { // clut missing - case 1: - imageInfo->colorSpace = OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_GRAYSCALE; - break; - case 2: - imageInfo->colorSpace = - OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_GRAYSCALE_ALPHA; - break; - case 3: - imageInfo->colorSpace = OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_RGB; - break; - case 4: - imageInfo->colorSpace = OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_RGBA; - break; - default: - imageInfo->colorSpace = OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_RGB; - break; - } - imageInfo->imageFlag = 0; - if (isInterlaced) { - imageInfo->imageFlag |= ORBIS_PNG_DEC_IMAGE_FLAG_ADAM7_INTERLACE; - } - if (isTransparent) { - imageInfo->imageFlag |= ORBIS_PNG_DEC_IMAGE_FLAG_TRNS_CHUNK_EXIST; - } +struct PngHandler { + png_structp png_ptr; + png_infop info_ptr; +}; + +static inline OrbisPngDecColorSpace MapPngColor(int color) { + switch (color) { + case PNG_COLOR_TYPE_GRAY: + return OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_GRAYSCALE; + + case PNG_COLOR_TYPE_GRAY_ALPHA: + return OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_GRAYSCALE_ALPHA; + + case PNG_COLOR_TYPE_PALETTE: + return OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_CLUT; + + case PNG_COLOR_TYPE_RGB: + return OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_RGB; + + case PNG_COLOR_TYPE_RGB_ALPHA: + return OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_RGBA; } + + UNREACHABLE_MSG("unknown png color type"); } -bool checktRNS(const u8* png_raw, int size) { - for (int i = 30; i < size - 4; i += 4) { - if (std::memcmp(png_raw + i, "tRNS", 4) == 0) { - return true; - } - } - return false; +void PngDecError(png_structp png_ptr, png_const_charp error_message) { + LOG_ERROR(Lib_Png, "PNG error {}", error_message); +} + +void PngDecWarning(png_structp png_ptr, png_const_charp error_message) { + LOG_ERROR(Lib_Png, "PNG warning {}", error_message); } s32 PS4_SYSV_ABI scePngDecCreate(const OrbisPngDecCreateParam* param, void* memoryAddress, @@ -66,11 +58,21 @@ s32 PS4_SYSV_ABI scePngDecCreate(const OrbisPngDecCreateParam* param, void* memo LOG_ERROR(Lib_Png, "Invalid size! width = {}", param->maxImageWidth); return ORBIS_PNG_DEC_ERROR_INVALID_SIZE; } - const OrbisPngDecCreateParam* nextParam = param + 1; - int ret = (8 << (reinterpret_cast(nextParam) & 0x1f)) * - (param->maxImageWidth + 0x47U & 0xfffffff8) + - 0xd000; - *handle = reinterpret_cast(ret); + auto pngh = (PngHandler*)memoryAddress; + + pngh->png_ptr = + png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, PngDecError, PngDecWarning); + + if (pngh->png_ptr == nullptr) + return ORBIS_PNG_DEC_ERROR_FATAL; + + pngh->info_ptr = png_create_info_struct(pngh->png_ptr); + if (pngh->info_ptr == nullptr) { + png_destroy_read_struct(&pngh->png_ptr, nullptr, nullptr); + return false; + } + + *handle = pngh; return ORBIS_OK; } @@ -88,22 +90,91 @@ s32 PS4_SYSV_ABI scePngDecDecode(OrbisPngDecHandle handle, const OrbisPngDecDeco LOG_ERROR(Lib_Png, "invalid image address!"); return ORBIS_PNG_DEC_ERROR_INVALID_ADDR; } + LOG_TRACE(Lib_Png, + "pngMemSize = {} , imageMemSize = {} , pixelFormat = {} , alphaValue = {} , " + "imagePitch = {}", + param->pngMemSize, param->imageMemSize, param->pixelFormat, param->alphaValue, + param->imagePitch); - int width, height, channels; - const u8* png_raw = (const u8*)param->pngMemAddr; - u8* img = stbi_load_from_memory(png_raw, param->pngMemSize, &width, &height, &channels, - STBI_rgb_alpha); // STBI_rgb_alpha? - if (!img) { - LOG_ERROR(Lib_Png, "Decoding failed!"); - return ORBIS_PNG_DEC_ERROR_DECODE_ERROR; + auto pngh = (PngHandler*)handle; + + struct pngstruct { + const u8* data; + size_t size; + u64 offset; + } pngdata = { + .data = (const u8*)param->pngMemAddr, + .size = param->pngMemSize, + .offset = 0, + }; + + // Read png from memory + png_set_read_fn(pngh->png_ptr, (void*)&pngdata, + [](png_structp ps, png_bytep data, png_size_t len) { + if (len == 0) + return; + auto pngdata = (pngstruct*)png_get_io_ptr(ps); + ::memcpy(data, pngdata->data + pngdata->offset, len); + pngdata->offset += len; + }); + + u32 width, height; + int color_type, bit_depth; + png_read_info(pngh->png_ptr, pngh->info_ptr); + + width = png_get_image_width(pngh->png_ptr, pngh->info_ptr); + height = png_get_image_height(pngh->png_ptr, pngh->info_ptr); + color_type = MapPngColor(png_get_color_type(pngh->png_ptr, pngh->info_ptr)); + bit_depth = png_get_bit_depth(pngh->png_ptr, pngh->info_ptr); + + if (imageInfo != nullptr) { + imageInfo->bitDepth = bit_depth; + imageInfo->imageWidth = width; + imageInfo->imageHeight = height; + imageInfo->colorSpace = color_type; + imageInfo->imageFlag = 0; + if (png_get_interlace_type(pngh->png_ptr, pngh->info_ptr) == 1) { + imageInfo->imageFlag |= OrbisPngDecImageFlag::ORBIS_PNG_DEC_IMAGE_FLAG_ADAM7_INTERLACE; + } + if (png_get_valid(pngh->png_ptr, pngh->info_ptr, PNG_INFO_tRNS)) { + + imageInfo->imageFlag |= ORBIS_PNG_DEC_IMAGE_FLAG_TRNS_CHUNK_EXIST; + } } - bool isInterlaced = (png_raw[28] == 1); - bool isTransparent = checktRNS(png_raw, param->pngMemSize); - setImageInfoParams(imageInfo, width, height, channels, isInterlaced, isTransparent); - u8* imageBuffer = (u8*)(param->imageMemAddr); - memcpy(imageBuffer, img, width * height * 4); // copy/pass decoded data - stbi_image_free(img); - return 0; + + if (bit_depth == 16) + png_set_strip_16(pngh->png_ptr); + if (color_type == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb(pngh->png_ptr); + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) + png_set_expand_gray_1_2_4_to_8(pngh->png_ptr); + if (png_get_valid(pngh->png_ptr, pngh->info_ptr, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha(pngh->png_ptr); + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(pngh->png_ptr); + if (param->pixelFormat == OrbisPngDecPixelFormat::ORBIS_PNG_DEC_PIXEL_FORMAT_B8G8R8A8) + png_set_bgr(pngh->png_ptr); + if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_PALETTE) + png_set_add_alpha(pngh->png_ptr, param->alphaValue, PNG_FILLER_AFTER); + + int pass = png_set_interlace_handling(pngh->png_ptr); + png_read_update_info(pngh->png_ptr, pngh->info_ptr); + + auto const numChannels = png_get_channels(pngh->png_ptr, pngh->info_ptr); + auto horizontal_bytes = numChannels * width; + + int stride = param->imagePitch > 0 ? param->imagePitch : horizontal_bytes; + + for (int j = 0; j < pass; j++) { // interlaced + auto ptr = (png_bytep)param->imageMemAddr; + for (int y = 0; y < height; y++) { + png_read_row(pngh->png_ptr, ptr, nullptr); + ptr += stride; + } + } + + return (width > 32767 || height > 32767) ? 0 : (width << 16) | height; } s32 PS4_SYSV_ABI scePngDecDecodeWithInputControl() { @@ -112,8 +183,8 @@ s32 PS4_SYSV_ABI scePngDecDecodeWithInputControl() { } s32 PS4_SYSV_ABI scePngDecDelete(OrbisPngDecHandle handle) { - handle = nullptr; // ? - LOG_ERROR(Lib_Png, "(STUBBED)called"); + auto pngh = *(PngHandler**)handle; + png_destroy_read_struct(&pngh->png_ptr, &pngh->info_ptr, nullptr); return ORBIS_OK; } @@ -123,16 +194,60 @@ s32 PS4_SYSV_ABI scePngDecParseHeader(const OrbisPngDecParseParam* param, LOG_ERROR(Lib_Png, "Invalid param!"); return ORBIS_PNG_DEC_ERROR_INVALID_PARAM; } - int width, height, channels; - const u8* png_raw = (const u8*)(param->pngMemAddr); - int img = stbi_info_from_memory(png_raw, param->pngMemSize, &width, &height, &channels); - if (img == 0) { - LOG_ERROR(Lib_Png, "Decoding failed!"); - return ORBIS_PNG_DEC_ERROR_DECODE_ERROR; + + u8 header[8]; + memcpy(header, param->pngMemAddr, 8); + // Check if the header indicates a valid PNG file + if (png_sig_cmp(header, 0, 8)) { + LOG_ERROR(Lib_Png, "Memory doesn't contain a valid png file"); + return ORBIS_PNG_DEC_ERROR_INVALID_DATA; } - bool isInterlaced = (png_raw[28] == 1); - bool isTransparent = checktRNS(png_raw, param->pngMemSize); - setImageInfoParams(imageInfo, width, height, channels, isInterlaced, isTransparent); + // Create a libpng structure, also pass our custom error/warning functions + auto png_ptr = + png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, PngDecError, PngDecWarning); + + // Create a libpng info structure + auto info_ptr = png_create_info_struct(png_ptr); + + struct pngstruct { + const u8* data; + size_t size; + u64 offset; + } pngdata = { + .data = (const u8*)param->pngMemAddr, + .size = param->pngMemSize, + .offset = 0, + }; + + png_set_read_fn(png_ptr, (void*)&pngdata, [](png_structp ps, png_bytep data, png_size_t len) { + auto pngdata = (pngstruct*)png_get_io_ptr(ps); + ::memcpy(data, pngdata->data + pngdata->offset, len); + pngdata->offset += len; + }); + + // Now call png_read_info with our pngPtr as image handle, and infoPtr to receive the file + // info. + png_read_info(png_ptr, info_ptr); + + imageInfo->imageWidth = png_get_image_width(png_ptr, info_ptr); + imageInfo->imageHeight = png_get_image_height(png_ptr, info_ptr); + imageInfo->colorSpace = MapPngColor(png_get_color_type(png_ptr, info_ptr)); + imageInfo->bitDepth = png_get_bit_depth(png_ptr, info_ptr); + imageInfo->imageFlag = 0; + if (png_get_interlace_type(png_ptr, info_ptr) == 1) { + imageInfo->imageFlag |= OrbisPngDecImageFlag::ORBIS_PNG_DEC_IMAGE_FLAG_ADAM7_INTERLACE; + } + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + + imageInfo->imageFlag |= ORBIS_PNG_DEC_IMAGE_FLAG_TRNS_CHUNK_EXIST; + } + + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + LOG_TRACE( + Lib_Png, + "imageWidth = {} , imageHeight = {} , colorSpace = {} , bitDepth = {} , imageFlag = {}", + imageInfo->imageWidth, imageInfo->imageHeight, imageInfo->colorSpace, imageInfo->bitDepth, + imageInfo->imageFlag); return ORBIS_OK; } @@ -149,9 +264,7 @@ s32 PS4_SYSV_ABI scePngDecQueryMemorySize(const OrbisPngDecCreateParam* param) { LOG_ERROR(Lib_Png, "Invalid size! width = {}", param->maxImageWidth); return ORBIS_PNG_DEC_ERROR_INVALID_SIZE; } - int ret = - (8 << ((u8)param->attribute & 0x1f)) * (param->maxImageWidth + 0x47U & 0xfffffff8) + 0xd090; - return ret; + return sizeof(PngHandler); } void RegisterlibScePngDec(Core::Loader::SymbolsResolver* sym) { diff --git a/src/core/libraries/libpng/pngdec.h b/src/core/libraries/libpng/pngdec.h index 35034a196..9d807166c 100644 --- a/src/core/libraries/libpng/pngdec.h +++ b/src/core/libraries/libpng/pngdec.h @@ -8,54 +8,55 @@ namespace Core::Loader { class SymbolsResolver; } + namespace Libraries::PngDec { -constexpr int ORBIS_PNG_DEC_ERROR_INVALID_ADDR = 0x80690001; -constexpr int ORBIS_PNG_DEC_ERROR_INVALID_SIZE = 0x80690002; -constexpr int ORBIS_PNG_DEC_ERROR_INVALID_PARAM = 0x80690003; -constexpr int ORBIS_PNG_DEC_ERROR_INVALID_HANDLE = 0x80690004; -constexpr int ORBIS_PNG_DEC_ERROR_INVALID_WORK_MEMORY = 0x80690005; -constexpr int ORBIS_PNG_DEC_ERROR_INVALID_DATA = 0x80690010; -constexpr int ORBIS_PNG_DEC_ERROR_UNSUPPORT_DATA = 0x80690011; -constexpr int ORBIS_PNG_DEC_ERROR_DECODE_ERROR = 0x80690012; -constexpr int ORBIS_PNG_DEC_ERROR_FATAL = 0x80690020; - -typedef struct OrbisPngDecParseParam { - const void* pngMemAddr; - u32 pngMemSize; - u32 reserved; -} OrbisPngDecParseParam; - -typedef struct OrbisPngDecImageInfo { - u32 imageWidth; - u32 imageHeight; - u16 colorSpace; - u16 bitDepth; - u32 imageFlag; -} OrbisPngDecImageInfo; - -typedef enum OrbisPngDecColorSpace { +enum OrbisPngDecColorSpace { ORBIS_PNG_DEC_COLOR_SPACE_GRAYSCALE = 2, ORBIS_PNG_DEC_COLOR_SPACE_RGB, ORBIS_PNG_DEC_COLOR_SPACE_CLUT, ORBIS_PNG_DEC_COLOR_SPACE_GRAYSCALE_ALPHA = 18, ORBIS_PNG_DEC_COLOR_SPACE_RGBA -} ScePngDecColorSpace; +}; -typedef enum OrbisPngDecImageFlag { +enum OrbisPngDecImageFlag { ORBIS_PNG_DEC_IMAGE_FLAG_ADAM7_INTERLACE = 1, ORBIS_PNG_DEC_IMAGE_FLAG_TRNS_CHUNK_EXIST = 2 -} OrbisPngDecImageFlag; +}; -typedef struct OrbisPngDecCreateParam { +enum OrbisPngDecPixelFormat { + ORBIS_PNG_DEC_PIXEL_FORMAT_R8G8B8A8 = 0, + ORBIS_PNG_DEC_PIXEL_FORMAT_B8G8R8A8 +}; + +enum OrbisPngDecAttribute { + ORBIS_PNG_DEC_ATTRIBUTE_NONE = 0, + ORBIS_PNG_DEC_ATTRIBUTE_BIT_DEPTH_16 +}; + +struct OrbisPngDecParseParam { + const void* pngMemAddr; + u32 pngMemSize; + u32 reserved; +}; + +struct OrbisPngDecImageInfo { + u32 imageWidth; + u32 imageHeight; + u16 colorSpace; + u16 bitDepth; + u32 imageFlag; +}; + +struct OrbisPngDecCreateParam { u32 thisSize; u32 attribute; u32 maxImageWidth; -} OrbisPngDecCreateParam; +}; typedef void* OrbisPngDecHandle; -typedef struct OrbisPngDecDecodeParam { +struct OrbisPngDecDecodeParam { const void* pngMemAddr; void* imageMemAddr; u32 pngMemSize; @@ -63,7 +64,7 @@ typedef struct OrbisPngDecDecodeParam { u16 pixelFormat; u16 alphaValue; u32 imagePitch; -} OrbisPngDecDecodeParam; +}; s32 PS4_SYSV_ABI scePngDecCreate(const OrbisPngDecCreateParam* param, void* memoryAddress, u32 memorySize, OrbisPngDecHandle* handle); diff --git a/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp b/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp index 52abe9101..4e0d801a6 100644 --- a/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp +++ b/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp @@ -13,33 +13,6 @@ #include "imgui/imgui_std.h" #include "savedatadialog_ui.h" -#ifdef __APPLE__ -#include - -// Need to make a copy of the formatter for std::chrono::local_time for use with date::local_time -template -struct fmt::formatter, Char> : formatter { - FMT_CONSTEXPR formatter() { - this->format_str_ = fmt::detail::string_literal(); - } - - template - auto format(date::local_time val, FormatContext& ctx) const -> decltype(ctx.out()) { - using period = typename Duration::period; - if (period::num == 1 && period::den == 1 && - !std::is_floating_point::value) { - return formatter::format( - localtime(fmt::detail::to_time_t(date::current_zone()->to_sys(val))), ctx); - } - auto epoch = val.time_since_epoch(); - auto subsecs = fmt::detail::duration_cast( - epoch - fmt::detail::duration_cast(epoch)); - return formatter::do_format( - localtime(fmt::detail::to_time_t(date::current_zone()->to_sys(val))), ctx, &subsecs); - } -}; -#endif - using namespace ImGui; using namespace Libraries::CommonDialog; using Common::ElfInfo; @@ -125,12 +98,9 @@ SaveDialogState::SaveDialogState(const OrbisSaveDataDialogParam& param) { param_sfo.Open(param_sfo_path); auto last_write = param_sfo.GetLastWrite(); -#ifdef __APPLE__ - auto t = date::zoned_time{date::current_zone(), last_write}; -#else - auto t = std::chrono::zoned_time{std::chrono::current_zone(), last_write}; -#endif - std::string date_str = fmt::format("{:%d %b, %Y %R}", t.get_local_time()); + std::string date_str = + fmt::format("{:%d %b, %Y %R}", + fmt::localtime(std::chrono::system_clock::to_time_t(last_write))); size_t size = Common::FS::GetDirectorySize(dir_path); std::string size_str = SpaceSizeToString(size); diff --git a/src/emulator.cpp b/src/emulator.cpp index 24f5907a1..c0b01229a 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -162,8 +162,7 @@ void Emulator::Run(const std::filesystem::path& file) { if (!playgo->Open(filepath)) { LOG_ERROR(Loader, "PlayGo: unable to open file"); } - } else if (entry.path().filename() == "pic0.png" || - entry.path().filename() == "pic1.png") { + } else if (entry.path().filename() == "pic1.png") { auto* splash = Common::Singleton::Instance(); if (splash->IsLoaded()) { continue; diff --git a/src/main.cpp b/src/main.cpp index de1d92326..b12965677 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,13 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "functional" +#include "iostream" +#include "string" +#include "unordered_map" + #include +#include "common/config.h" #include "common/memory_patcher.h" #include "emulator.h" @@ -14,26 +20,105 @@ int main(int argc, char* argv[]) { SetConsoleOutputCP(CP_UTF8); #endif + bool has_game_argument = false; + std::string game_path; + + // Map of argument strings to lambda functions + std::unordered_map> arg_map = { + {"-h", + [&](int&) { + std::cout << "Usage: shadps4 [options] \n" + "Options:\n" + " -g, --game Specify game path to launch\n" + " -p, --patch Apply specified patch file\n" + " -f, --fullscreen Specify window initial fullscreen " + "state. Does not overwrite the config file." + " -h, --help Display this help message\n"; + exit(0); + }}, + {"--help", [&](int& i) { arg_map["-h"](i); }}, + + {"-g", + [&](int& i) { + if (i + 1 < argc) { + game_path = argv[++i]; + has_game_argument = true; + } else { + std::cerr << "Error: Missing argument for -g/--game\n"; + exit(1); + } + }}, + {"--game", [&](int& i) { arg_map["-g"](i); }}, + + {"-p", + [&](int& i) { + if (i + 1 < argc) { + MemoryPatcher::patchFile = argv[++i]; + } else { + std::cerr << "Error: Missing argument for -p/--patch\n"; + exit(1); + } + }}, + {"--patch", [&](int& i) { arg_map["-p"](i); }}, + {"-f", + [&](int& i) { + if (++i >= argc) { + std::cerr << "Error: Missing argument for -f/--fullscreen\n"; + exit(1); + } + std::string f_param(argv[i]); + bool is_fullscreen; + if (f_param == "true") { + is_fullscreen = true; + } else if (f_param == "false") { + is_fullscreen = false; + } else { + std::cerr + << "Error: Invalid argument for -f/--fullscreen. Use 'true' or 'false'.\n"; + exit(1); + } + // Set fullscreen mode without saving it to config file + Config::setFullscreenMode(is_fullscreen); + }}, + {"--fullscreen", [&](int& i) { arg_map["-f"](i); }}, + }; + if (argc == 1) { - fmt::print("Usage: {} \n", argv[0]); - return -1; - } - // check if eboot file exists - if (!std::filesystem::exists(argv[1])) { - fmt::print("Eboot.bin file not found\n"); + int dummy = 0; // one does not simply pass 0 directly + arg_map.at("-h")(dummy); return -1; } - for (int i = 0; i < argc; i++) { - std::string curArg = argv[i]; - if (curArg == "-p") { - std::string patchFile = argv[i + 1]; - MemoryPatcher::patchFile = patchFile; + // Parse command-line arguments using the map + for (int i = 1; i < argc; ++i) { + std::string cur_arg = argv[i]; + auto it = arg_map.find(cur_arg); + if (it != arg_map.end()) { + it->second(i); // Call the associated lambda function + } else if (i == argc - 1 && !has_game_argument) { + // Assume the last argument is the game file if not specified via -g/--game + game_path = argv[i]; + has_game_argument = true; + } else { + std::cerr << "Unknown argument: " << cur_arg << ", see --help for info.\n"; + return 1; } } + if (!has_game_argument) { + std::cerr << "Error: Please provide a game path or ID.\n"; + exit(1); + } + + // Check if the game path or ID exists + if (!std::filesystem::exists(game_path)) { + std::cerr << "Error: Game file not found\n"; + return -1; + } + + // Run the emulator with the specified game Core::Emulator emulator; - emulator.Run(argv[1]); + emulator.Run(game_path); return 0; } diff --git a/src/qt_gui/main.cpp b/src/qt_gui/main.cpp index da8804f69..7f9b29200 100644 --- a/src/qt_gui/main.cpp +++ b/src/qt_gui/main.cpp @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "iostream" +#include "unordered_map" + #include "common/config.h" #include "common/memory_patcher.h" #include "core/file_sys/fs.h" @@ -26,10 +29,90 @@ int main(int argc, char* argv[]) { const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); Config::load(user_dir / "config.toml"); - // Check if elf or eboot.bin path was passed as a command line argument bool has_command_line_argument = argc > 1; + bool show_gui = false, has_game_argument = false; + std::string gamePath; - // Check if the game install directory is set + // Map of argument strings to lambda functions + std::unordered_map> arg_map = { + {"-h", + [&](int&) { + std::cout << "Usage: shadps4 [options]\n" + "Options:\n" + " No arguments: Opens the GUI.\n" + " -g, --game Specify or " + " to launch\n" + " -p, --patch Apply specified patch file\n" + " -s, --show-gui Show the GUI\n" + " -f, --fullscreen Specify window initial fullscreen " + "state. Does not overwrite the config file." + " -h, --help Display this help message\n"; + exit(0); + }}, + {"--help", [&](int& i) { arg_map["-h"](i); }}, // Redirect --help to -h + + {"-s", [&](int&) { show_gui = true; }}, + {"--show-gui", [&](int& i) { arg_map["-s"](i); }}, + + {"-g", + [&](int& i) { + if (i + 1 < argc) { + gamePath = argv[++i]; + has_game_argument = true; + } else { + std::cerr << "Error: Missing argument for -g/--game\n"; + exit(1); + } + }}, + {"--game", [&](int& i) { arg_map["-g"](i); }}, + + {"-p", + [&](int& i) { + if (i + 1 < argc) { + MemoryPatcher::patchFile = argv[++i]; + } else { + std::cerr << "Error: Missing argument for -p\n"; + exit(1); + } + }}, + {"--patch", [&](int& i) { arg_map["-p"](i); }}, + {"-f", + [&](int& i) { + if (++i >= argc) { + std::cerr + << "Error: Invalid argument for -f/--fullscreen. Use 'true' or 'false'.\n"; + exit(1); + } + std::string f_param(argv[i]); + bool is_fullscreen; + if (f_param == "true") { + is_fullscreen = true; + } else if (f_param == "false") { + is_fullscreen = false; + } else { + std::cerr + << "Error: Invalid argument for -f/--fullscreen. Use 'true' or 'false'.\n"; + exit(1); + } + // Set fullscreen mode without saving it to config file + Config::setFullscreenMode(is_fullscreen); + }}, + {"--fullscreen", [&](int& i) { arg_map["-f"](i); }}, + }; + + // Parse command-line arguments using the map + for (int i = 1; i < argc; ++i) { + std::string cur_arg = argv[i]; + auto it = arg_map.find(cur_arg); + if (it != arg_map.end()) { + it->second(i); // Call the associated lambda function + } else { + std::cerr << "Unknown argument: " << cur_arg << ", see --help for info.\n"; + return 1; + } + } + + // If no game directory is set and no command line argument, prompt for it if (Config::getGameInstallDirs().empty() && !has_command_line_argument) { GameInstallDialog dlg; dlg.exec(); @@ -40,21 +123,46 @@ int main(int argc, char* argv[]) { // Initialize the main window MainWindow* m_main_window = new MainWindow(nullptr); - m_main_window->Init(); - - // Check for command line arguments - if (has_command_line_argument) { - Core::Emulator emulator; - for (int i = 0; i < argc; i++) { - std::string curArg = argv[i]; - if (curArg == "-p") { - std::string patchFile = argv[i + 1]; - MemoryPatcher::patchFile = patchFile; - } - } - emulator.Run(argv[1]); + if ((has_command_line_argument && show_gui) || !has_command_line_argument) { + m_main_window->Init(); } - // Run the Qt application + if (has_command_line_argument && !has_game_argument) { + std::cerr << "Error: Please provide a game path or ID.\n"; + exit(1); + } + + // Process game path or ID if provided + if (has_game_argument) { + std::filesystem::path game_file_path(gamePath); + + // Check if the provided path is a valid file + if (!std::filesystem::exists(game_file_path)) { + // If not a file, treat it as a game ID and search in install directories + bool game_found = false; + for (const auto& install_dir : Config::getGameInstallDirs()) { + auto potential_game_path = install_dir / gamePath / "eboot.bin"; + if (std::filesystem::exists(potential_game_path)) { + game_file_path = potential_game_path; + game_found = true; + break; + } + } + if (!game_found) { + std::cerr << "Error: Game ID or file path not found: " << gamePath << std::endl; + return 1; + } + } + + // Run the emulator with the resolved game path + Core::Emulator emulator; + emulator.Run(game_file_path.string()); + if (!show_gui) { + return 0; // Exit after running the emulator without showing the GUI + } + } + + // Show the main window and run the Qt application + m_main_window->show(); return a.exec(); -} +} \ No newline at end of file diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index b15eace12..42d3deba7 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -362,7 +362,7 @@ bool BufferCache::IsRegionRegistered(VAddr addr, size_t size) { if (buf_start_addr < end_addr && addr < buf_end_addr) { return true; } - page = Common::DivCeil(end_addr, CACHING_PAGESIZE); + page = Common::DivCeil(buf_end_addr, CACHING_PAGESIZE); } return false; }