diff --git a/rpcs3/Emu/Cell/Modules/cellPhotoDecode.cpp b/rpcs3/Emu/Cell/Modules/cellPhotoDecode.cpp index dc2cef51f8..7cd6599fb3 100644 --- a/rpcs3/Emu/Cell/Modules/cellPhotoDecode.cpp +++ b/rpcs3/Emu/Cell/Modules/cellPhotoDecode.cpp @@ -1,9 +1,10 @@ #include "stdafx.h" #include "Emu/Cell/PPUModule.h" +#include "Emu/IdManager.h" +#include "Emu/VFS.h" +#include "Emu/System.h" #include "cellSysutil.h" - - LOG_CHANNEL(cellPhotoDecode); // Return Codes @@ -36,6 +37,11 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } +enum +{ + CELL_PHOTO_DECODE_VERSION_CURRENT = 0 +}; + struct CellPhotoDecodeSetParam { vm::bptr dstBuffer; @@ -57,7 +63,22 @@ using CellPhotoDecodeFinishCallback = void(s32 result, vm::ptr userdata); error_code cellPhotoDecodeInitialize(u32 version, u32 container1, u32 container2, vm::ptr funcFinish, vm::ptr userdata) { - cellPhotoDecode.todo("cellPhotoDecodeInitialize(version=0x%x, container1=0x%x, container2=0x%x, funcFinish=*0x%x, userdata=*0x%x)", version, container1, container2, funcFinish, userdata); + cellPhotoDecode.warning("cellPhotoDecodeInitialize(version=0x%x, container1=0x%x, container2=0x%x, funcFinish=*0x%x, userdata=*0x%x)", version, container1, container2, funcFinish, userdata); + + if (version != CELL_PHOTO_DECODE_VERSION_CURRENT || !funcFinish) + { + return CELL_PHOTO_DECODE_ERROR_PARAM; + } + + if (container1 != 0xffffffff && false) // TODO: size < 0x300000 + { + return CELL_PHOTO_DECODE_ERROR_PARAM; + } + + if (container2 != 0xffffffff && false) // TODO: size depends on image type, width and height + { + return CELL_PHOTO_DECODE_ERROR_PARAM; + } sysutil_register_cb([=](ppu_thread& ppu) -> s32 { @@ -70,7 +91,17 @@ error_code cellPhotoDecodeInitialize(u32 version, u32 container1, u32 container2 error_code cellPhotoDecodeInitialize2(u32 version, u32 container2, vm::ptr funcFinish, vm::ptr userdata) { - cellPhotoDecode.todo("cellPhotoDecodeInitialize2(version=0x%x, container2=0x%x, funcFinish=*0x%x, userdata=*0x%x)", version, container2, funcFinish, userdata); + cellPhotoDecode.warning("cellPhotoDecodeInitialize2(version=0x%x, container2=0x%x, funcFinish=*0x%x, userdata=*0x%x)", version, container2, funcFinish, userdata); + + if (version != CELL_PHOTO_DECODE_VERSION_CURRENT || !funcFinish) + { + return CELL_PHOTO_DECODE_ERROR_PARAM; + } + + if (container2 != 0xffffffff && false) // TODO: size depends on image type, width and height + { + return CELL_PHOTO_DECODE_ERROR_PARAM; + } sysutil_register_cb([=](ppu_thread& ppu) -> s32 { @@ -83,7 +114,12 @@ error_code cellPhotoDecodeInitialize2(u32 version, u32 container2, vm::ptr funcFinish, vm::ptr userdata) { - cellPhotoDecode.todo("cellPhotoDecodeFinalize(funcFinish=*0x%x, userdata=*0x%x)", funcFinish, userdata); + cellPhotoDecode.warning("cellPhotoDecodeFinalize(funcFinish=*0x%x, userdata=*0x%x)", funcFinish, userdata); + + if (!funcFinish) + { + return CELL_PHOTO_DECODE_ERROR_PARAM; + } sysutil_register_cb([=](ppu_thread& ppu) -> s32 { @@ -96,7 +132,45 @@ error_code cellPhotoDecodeFinalize(vm::ptr funcFi error_code cellPhotoDecodeFromFile(vm::cptr srcHddDir, vm::cptr srcHddFile, vm::ptr set_param, vm::ptr return_param) { - cellPhotoDecode.todo("cellPhotoDecodeFromFile(srcHddDir=%s, srcHddFile=%s, set_param=*0x%x, return_param=*0x%x)", srcHddDir, srcHddFile, set_param, return_param); + cellPhotoDecode.warning("cellPhotoDecodeFromFile(srcHddDir=%s, srcHddFile=%s, set_param=*0x%x, return_param=*0x%x)", srcHddDir, srcHddFile, set_param, return_param); + + if (!srcHddDir || !srcHddFile || !set_param || !return_param) + { + return CELL_PHOTO_DECODE_ERROR_PARAM; + } + + *return_param = {}; + + const std::string vpath = fmt::format("%s/%s", srcHddDir.get_ptr(), srcHddFile.get_ptr()); + const std::string path = vfs::get(vpath); + + if (!vpath.starts_with("/dev_hdd0")) + { + cellPhotoDecode.error("Destination '%s' is not inside dev_hdd0", srcHddDir); + return CELL_PHOTO_DECODE_ERROR_ACCESS_ERROR; // TODO: is this correct? + } + + if (!fs::is_file(path)) + { + // TODO: check if the dir is user accessible and can be written to + cellPhotoDecode.error("Source '%s' is not a file (vfs='%s')", path, vpath); + return CELL_PHOTO_DECODE_ERROR_ACCESS_ERROR; // TODO: is this correct? + } + + cellPhotoDecode.notice("About to decode '%s' (set_param: width=%d, height=%d, dstBuffer=*0x%x)", path, set_param->width, set_param->height, set_param->dstBuffer); + + s32 width{}; + s32 height{}; + + if (!Emu.GetCallbacks().get_scaled_image(path, set_param->width, set_param->height, width, height, static_cast(set_param->dstBuffer.get_ptr()))) + { + cellPhotoDecode.error("Failed to decode '%s'", path); + return CELL_PHOTO_DECODE_ERROR_DECODE; + } + + return_param->width = width; + return_param->height = height; + return CELL_OK; } diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index dddd25c6e5..6fa1a2d948 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -97,6 +97,7 @@ struct EmuCallbacks std::function get_localized_u32string; std::function play_sound; std::function get_image_info; // (filename, sub_type, width, height, CellSearchOrientation) + std::function get_scaled_image; // (filename, target_width, target_height, width, height, dst) std::string(*resolve_path)(std::string_view) = [](std::string_view arg){ return std::string{arg}; }; // Resolve path using Qt }; diff --git a/rpcs3/main_application.cpp b/rpcs3/main_application.cpp index 39d26e3f44..a132ada532 100644 --- a/rpcs3/main_application.cpp +++ b/rpcs3/main_application.cpp @@ -5,6 +5,7 @@ #include "util/sysinfo.hpp" #include "Utilities/Thread.h" +#include "Utilities/File.h" #include "Input/pad_thread.h" #include "Emu/System.h" #include "Emu/system_config.h" @@ -192,6 +193,52 @@ EmuCallbacks main_application::CreateCallbacks() return success; }; + callbacks.get_scaled_image = [](const std::string& path, s32 target_width, s32 target_height, s32& width, s32& height, u8* dst) -> bool + { + width = 0; + height = 0; + + if (target_width <= 0 || target_height <= 0 || !dst || !fs::is_file(path)) + { + return false; + } + + bool success = false; + Emu.BlockingCallFromMainThread([&]() + { + QImage image{}; + success = image.load(QString::fromStdString(path)) && !image.isNull(); + + if (success) + { + width = image.width(); + height = image.height(); + + if (width <= 0 || height <= 0) + { + success = false; + return; + } + + if (width > target_width || height > target_height) + { + const QSize size(target_width, target_height); + image = image.scaled(QSize(target_width, target_height), Qt::AspectRatioMode::KeepAspectRatio, Qt::TransformationMode::SmoothTransformation); + width = image.width(); + height = image.height(); + } + + if (image.format() != QImage::Format::Format_RGBA8888) + { + image = image.convertToFormat(QImage::Format::Format_RGBA8888); + } + + std::memcpy(dst, image.constBits(), std::min(4 * target_width * target_height, image.height() * image.bytesPerLine())); + } + }); + return success; + }; + callbacks.resolve_path = [](std::string_view sv) { return QFileInfo(QString::fromUtf8(sv.data(), static_cast(sv.size()))).canonicalFilePath().toStdString();