diff --git a/rpcs3/Emu/Cell/Modules/cellPad.h b/rpcs3/Emu/Cell/Modules/cellPad.h index 7dd0828217..6de18866dc 100644 --- a/rpcs3/Emu/Cell/Modules/cellPad.h +++ b/rpcs3/Emu/Cell/Modules/cellPad.h @@ -121,5 +121,5 @@ struct CellPadFilterIIRSos struct pad_info { atomic_t max_connect = 0; - std::array port_setting; + std::array port_setting{ 0 }; }; diff --git a/rpcs3/Emu/Cell/Modules/cellScreenshot.cpp b/rpcs3/Emu/Cell/Modules/cellScreenshot.cpp index 17a01edb86..50cfbadf5c 100644 --- a/rpcs3/Emu/Cell/Modules/cellScreenshot.cpp +++ b/rpcs3/Emu/Cell/Modules/cellScreenshot.cpp @@ -1,5 +1,6 @@ -#include "stdafx.h" +#include "stdafx.h" #include "Emu/System.h" +#include "Emu/IdManager.h" #include "Emu/Cell/PPUModule.h" #include "cellScreenshot.h" @@ -8,27 +9,104 @@ LOG_CHANNEL(cellScreenshot); -s32 cellScreenShotSetParameter(vm::cptr param) +template<> +void fmt_class_string::format(std::string& out, u64 arg) { - cellScreenshot.todo("cellScreenShotSetParameter(param=*0x%x)", param); + format_enum(out, arg, [](auto error) + { + switch (error) + { + STR_CASE(CELL_SCREENSHOT_ERROR_INTERNAL); + STR_CASE(CELL_SCREENSHOT_ERROR_PARAM); + STR_CASE(CELL_SCREENSHOT_ERROR_DECODE); + STR_CASE(CELL_SCREENSHOT_ERROR_NOSPACE); + STR_CASE(CELL_SCREENSHOT_ERROR_UNSUPPORTED_COLOR_FORMAT); + } + + return unknown; + }); +} + +error_code cellScreenShotSetParameter(vm::cptr param) +{ + cellScreenshot.warning("cellScreenShotSetParameter(param=*0x%x)", param); + + if (!param) // TODO: check if param->reserved must be null + return CELL_SCREENSHOT_ERROR_PARAM; + + if (param->photo_title && !memchr(param->photo_title.get_ptr(), '\0', CELL_SCREENSHOT_PHOTO_TITLE_MAX_LENGTH)) + return CELL_SCREENSHOT_ERROR_PARAM; + + if (param->game_title && !memchr(param->game_title.get_ptr(), '\0', CELL_SCREENSHOT_GAME_TITLE_MAX_LENGTH)) + return CELL_SCREENSHOT_ERROR_PARAM; + + if (param->game_comment && !memchr(param->game_comment.get_ptr(), '\0', CELL_SCREENSHOT_GAME_COMMENT_MAX_SIZE)) + return CELL_SCREENSHOT_ERROR_PARAM; + + const auto manager = g_fxo->get(); + + if (param->photo_title && param->photo_title[0] != '\0') + manager->photo_title = std::string(param->photo_title.get_ptr()); + else + manager->photo_title = ""; + + if (param->game_title && param->game_title[0] != '\0') + manager->game_title = std::string(param->game_title.get_ptr()); + else + manager->game_title = ""; + + if (param->game_comment && param->game_comment[0] != '\0') + manager->game_comment = std::string(param->game_comment.get_ptr()); + else + manager->game_comment = ""; + + cellScreenshot.notice("cellScreenShotSetParameter(photo_title=%s, game_title=%s, game_comment=%s)", manager->photo_title, manager->game_title, manager->game_comment); + return CELL_OK; } -s32 cellScreenShotSetOverlayImage(vm::cptr srcDir, vm::cptr srcFile, s32 offset_x, s32 offset_y) +error_code cellScreenShotSetOverlayImage(vm::cptr srcDir, vm::cptr srcFile, s32 offset_x, s32 offset_y) { - cellScreenshot.todo("cellScreenShotSetOverlayImage(srcDir=%s, srcFile=%s, offset_x=%d, offset_y=%d)", srcDir, srcFile, offset_x, offset_y); + cellScreenshot.warning("cellScreenShotSetOverlayImage(srcDir=%s, srcFile=%s, offset_x=%d, offset_y=%d)", srcDir, srcFile, offset_x, offset_y); + + if (!srcDir || !srcFile) + return CELL_SCREENSHOT_ERROR_PARAM; + + // TODO: check srcDir (size 1024) and srcFile (size 64) for '-' or '_' or '.' or '/' in some manner (range checks?) + + // Make sure that srcDir starts with /dev_hdd0, /dev_bdvd, /app_home or /host_root + if (strncmp(srcDir.get_ptr(), "/dev_hdd0", 9) && strncmp(srcDir.get_ptr(), "/dev_bdvd", 9) && strncmp(srcDir.get_ptr(), "/app_home", 9) && strncmp(srcDir.get_ptr(), "/host_root", 10)) + { + return CELL_SCREENSHOT_ERROR_PARAM; + } + + const auto manager = g_fxo->get(); + + manager->overlay_dir_name = std::string(srcDir.get_ptr()); + manager->overlay_file_name = std::string(srcFile.get_ptr()); + manager->overlay_offset_x = offset_x; + manager->overlay_offset_y = offset_y; + return CELL_OK; } -s32 cellScreenShotEnable() +error_code cellScreenShotEnable() { - cellScreenshot.todo("cellScreenShotEnable()"); + cellScreenshot.warning("cellScreenShotEnable()"); + + const auto manager = g_fxo->get(); + manager->is_enabled = true; + return CELL_OK; } -s32 cellScreenShotDisable() +error_code cellScreenShotDisable() { - cellScreenshot.todo("cellScreenShotDisable()"); + cellScreenshot.warning("cellScreenShotDisable()"); + + const auto manager = g_fxo->get(); + manager->is_enabled = false; + return CELL_OK; } diff --git a/rpcs3/Emu/Cell/Modules/cellScreenshot.h b/rpcs3/Emu/Cell/Modules/cellScreenshot.h index 8025081f5d..29bb693099 100644 --- a/rpcs3/Emu/Cell/Modules/cellScreenshot.h +++ b/rpcs3/Emu/Cell/Modules/cellScreenshot.h @@ -1,20 +1,70 @@ -#pragma once +#pragma once #include "Emu/Memory/vm_ptr.h" // Return Codes -enum +enum CellScreenShotError : u32 { - CELL_SCREENSHOT_ERROR_INTERNAL = 0x8002d101, - CELL_SCREENSHOT_ERROR_PARAM = 0x8002d102, - CELL_SCREENSHOT_ERROR_DECODE = 0x8002d103, - CELL_SCREENSHOT_ERROR_NOSPACE = 0x8002d104, + CELL_SCREENSHOT_ERROR_INTERNAL = 0x8002d101, + CELL_SCREENSHOT_ERROR_PARAM = 0x8002d102, + CELL_SCREENSHOT_ERROR_DECODE = 0x8002d103, + CELL_SCREENSHOT_ERROR_NOSPACE = 0x8002d104, CELL_SCREENSHOT_ERROR_UNSUPPORTED_COLOR_FORMAT = 0x8002d105, }; +enum CellScreenShotParamSize +{ + CELL_SCREENSHOT_PHOTO_TITLE_MAX_LENGTH = 64, + CELL_SCREENSHOT_GAME_TITLE_MAX_LENGTH = 64, + CELL_SCREENSHOT_GAME_COMMENT_MAX_SIZE = 1024, +}; + struct CellScreenShotSetParam { vm::bcptr photo_title; vm::bcptr game_title; vm::bcptr game_comment; + vm::bptr reserved; +}; + +struct screenshot_manager +{ + atomic_t is_enabled{ false }; + + std::string photo_title; + std::string game_title; + std::string game_comment; + + atomic_t overlay_offset_x{ 0 }; + atomic_t overlay_offset_y{ 0 }; + std::string overlay_dir_name; + std::string overlay_file_name; + + std::string get_overlay_path() const + { + return vfs::get(overlay_dir_name + overlay_file_name); + } + + std::string get_photo_title() const + { + std::string photo = photo_title; + if (photo.empty()) + photo = Emu.GetTitle(); + return photo; + } + + std::string get_game_title() const + { + std::string game = game_title; + if (game.empty()) + game = Emu.GetTitle(); + return game; + } + + std::string get_screenshot_path() const + { + // TODO: make sure the file can be saved, add suffix and increase counter if file exists + // TODO: maybe find a proper home for these + return fs::get_config_dir() + "/screenshots/cell/" + get_photo_title() + ".png"; + } }; diff --git a/rpcs3/rpcs3qt/gs_frame.cpp b/rpcs3/rpcs3qt/gs_frame.cpp index 56c6ecbe6f..9ed8cfc33e 100644 --- a/rpcs3/rpcs3qt/gs_frame.cpp +++ b/rpcs3/rpcs3qt/gs_frame.cpp @@ -4,6 +4,7 @@ #include "Utilities/Timer.h" #include "Utilities/date_time.h" #include "Emu/System.h" +#include "Emu/Cell/Modules/cellScreenshot.h" #include #include @@ -152,7 +153,11 @@ void gs_frame::keyPressEvent(QKeyEvent *keyEvent) else if (Emu.IsPaused()) { Emu.Resume(); return; } } break; - case Qt::Key_F12: screenshot_toggle = true; break; + case Qt::Key_F12: + screenshot_toggle = true; + break; + default: + break; } } @@ -313,63 +318,103 @@ void gs_frame::flip(draw_context_t, bool /*skip_frame*/) void gs_frame::take_screenshot(const std::vector sshot_data, const u32 sshot_width, const u32 sshot_height) { std::thread( - [sshot_width, sshot_height](const std::vector sshot_data) { - std::string screen_path = fs::get_config_dir() + "/screenshots/"; + [sshot_width, sshot_height](const std::vector sshot_data) + { + std::string screen_path = fs::get_config_dir() + "/screenshots/"; - if (!fs::create_dir(screen_path) && fs::g_tls_error != fs::error::exist) - { - LOG_ERROR(GENERAL, "Failed to create screenshot path \"%s\" : %s", screen_path, fs::g_tls_error); - return; - } + if (!fs::create_dir(screen_path) && fs::g_tls_error != fs::error::exist) + { + LOG_ERROR(GENERAL, "Failed to create screenshot path \"%s\" : %s", screen_path, fs::g_tls_error); + return; + } - std::string filename = screen_path + "screenshot-" + date_time::current_time_narrow<'_'>() + ".png"; + std::string filename = screen_path + "screenshot-" + date_time::current_time_narrow<'_'>() + ".png"; - fs::file sshot_file(filename, fs::open_mode::create + fs::open_mode::write + fs::open_mode::excl); - if (!sshot_file) - { - LOG_ERROR(GENERAL, "[Screenshot] Failed to save screenshot \"%s\" : %s", filename, fs::g_tls_error); - return; - } + fs::file sshot_file(filename, fs::open_mode::create + fs::open_mode::write + fs::open_mode::excl); + if (!sshot_file) + { + LOG_ERROR(GENERAL, "[Screenshot] Failed to save screenshot \"%s\" : %s", filename, fs::g_tls_error); + return; + } - std::vector sshot_data_alpha(sshot_data.size()); - const u32* sshot_ptr = (const u32*)sshot_data.data(); - u32* alpha_ptr = (u32*)sshot_data_alpha.data(); + std::vector sshot_data_alpha(sshot_data.size()); + const u32* sshot_ptr = (const u32*)sshot_data.data(); + u32* alpha_ptr = (u32*)sshot_data_alpha.data(); - for (size_t index = 0; index < sshot_data.size() / sizeof(u32); index++) - { - alpha_ptr[index] = ((sshot_ptr[index] & 0xFF) << 16) | (sshot_ptr[index] & 0xFF00) | ((sshot_ptr[index] & 0xFF0000) >> 16) | 0xFF000000; - } + for (size_t index = 0; index < sshot_data.size() / sizeof(u32); index++) + { + alpha_ptr[index] = ((sshot_ptr[index] & 0xFF) << 16) | (sshot_ptr[index] & 0xFF00) | ((sshot_ptr[index] & 0xFF0000) >> 16) | 0xFF000000; + } - std::vector encoded_png; + std::vector encoded_png; - png_structp write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - png_infop info_ptr = png_create_info_struct(write_ptr); - png_set_IHDR(write_ptr, info_ptr, sshot_width, sshot_height, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_structp write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + png_infop info_ptr = png_create_info_struct(write_ptr); + png_set_IHDR(write_ptr, info_ptr, sshot_width, sshot_height, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); - std::vector rows(sshot_height); - for (size_t y = 0; y < sshot_height; y++) - rows[y] = (u8*)sshot_data_alpha.data() + y * sshot_width * 4; + std::vector rows(sshot_height); + for (size_t y = 0; y < sshot_height; y++) + rows[y] = (u8*)sshot_data_alpha.data() + y * sshot_width * 4; - png_set_rows(write_ptr, info_ptr, &rows[0]); - png_set_write_fn(write_ptr, &encoded_png, - [](png_structp png_ptr, png_bytep data, png_size_t length) { - std::vector* p = (std::vector*)png_get_io_ptr(png_ptr); - p->insert(p->end(), data, data + length); - }, - nullptr); + png_set_rows(write_ptr, info_ptr, &rows[0]); + png_set_write_fn(write_ptr, &encoded_png, + [](png_structp png_ptr, png_bytep data, png_size_t length) + { + std::vector* p = (std::vector*)png_get_io_ptr(png_ptr); + p->insert(p->end(), data, data + length); + }, + nullptr); - png_write_png(write_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nullptr); + png_write_png(write_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nullptr); - png_free_data(write_ptr, info_ptr, PNG_FREE_ALL, -1); - png_destroy_write_struct(&write_ptr, nullptr); + png_free_data(write_ptr, info_ptr, PNG_FREE_ALL, -1); + png_destroy_write_struct(&write_ptr, nullptr); - sshot_file.write(encoded_png.data(), encoded_png.size()); + sshot_file.write(encoded_png.data(), encoded_png.size()); - LOG_SUCCESS(GENERAL, "[Screenshot] Successfully saved screenshot to %s", filename); - return; - }, - std::move(sshot_data)) - .detach(); + LOG_SUCCESS(GENERAL, "[Screenshot] Successfully saved screenshot to %s", filename); + + const auto fxo = g_fxo->get(); + + if (fxo->is_enabled) + { + const std::string cell_sshot_filename = fxo->get_screenshot_path(); + const std::string cell_sshot_dir = fs::get_parent_dir(cell_sshot_filename); + + LOG_NOTICE(GENERAL, "[Screenshot] Saving cell screenshot to %s", cell_sshot_filename); + + if (!fs::create_path(cell_sshot_dir) && fs::g_tls_error != fs::error::exist) + { + LOG_ERROR(GENERAL, "Failed to create cell screenshot dir \"%s\" : %s", cell_sshot_dir, fs::g_tls_error); + return; + } + + fs::file cell_sshot_file(cell_sshot_filename, fs::open_mode::create + fs::open_mode::write + fs::open_mode::excl); + if (!cell_sshot_file) + { + LOG_ERROR(GENERAL, "[Screenshot] Failed to save cell screenshot \"%s\" : %s", cell_sshot_filename, fs::g_tls_error); + return; + } + + const std::string cell_sshot_overlay_path = fxo->get_overlay_path(); + if (fs::is_file(cell_sshot_overlay_path)) + { + LOG_NOTICE(GENERAL, "[Screenshot] Adding overlay to cell screenshot from %s", cell_sshot_overlay_path); + // TODO: add overlay to screenshot + } + + // TODO: add tEXt chunk with creation time, source, title id + // TODO: add tTXt chunk with data procured from cellScreenShotSetParameter (get_photo_title, get_game_title, game_comment) + + cell_sshot_file.write(encoded_png.data(), encoded_png.size()); + + LOG_SUCCESS(GENERAL, "[Screenshot] Successfully saved cell screenshot to %s", cell_sshot_filename); + } + + return; + }, + std::move(sshot_data)) + .detach(); } void gs_frame::mouseDoubleClickEvent(QMouseEvent* ev)