diff --git a/core/hw/naomi/printer.cpp b/core/hw/naomi/printer.cpp index 3c5edc0e1..737a3e622 100644 --- a/core/hw/naomi/printer.cpp +++ b/core/hw/naomi/printer.cpp @@ -341,6 +341,11 @@ public: { if (page.empty()) return false; + const auto& appendData = [](void *context, void *data, int size) { + std::vector& v = *(std::vector *)context; + v.insert(v.end(), (u8 *)data, (u8 *)data + size); + }; + stbi_flip_vertically_on_write(0); if (settings.content.gameId.substr(0, 4) == "F355") { u8 *data = nullptr; @@ -383,15 +388,29 @@ public: *p = 0xff000000; p++; } - stbi_write_png(filename.c_str(), printerWidth, lines, 4, data, printerWidth * 4); + std::vector pngData; + stbi_write_png_to_func(appendData, &pngData, printerWidth, lines, 4, data, printerWidth * 4); stbi_image_free(data); + try { + hostfs::saveScreenshot(filename, pngData); + } catch (const FlycastException& e) { + ERROR_LOG(NAOMI, "Error saving print out: %s", e.what()); + return false; + } return true; } } for (u8& b : page) b = 0xff - b; - stbi_write_png(filename.c_str(), printerWidth, lines, 1, &page[0], printerWidth); + std::vector pngData; + stbi_write_png_to_func(appendData, &pngData, printerWidth, lines, 1, &page[0], printerWidth); + try { + hostfs::saveScreenshot(filename, pngData); + } catch (const FlycastException& e) { + ERROR_LOG(NAOMI, "Error saving print out: %s", e.what()); + return false; + } return true; } @@ -827,12 +846,16 @@ private: state = Default; if (bitmapWriter && bitmapWriter->isDirty()) { - // TODO save to ~/Pictures instead - std::string s = get_writable_data_path(settings.content.gameId + "-results.png"); - bitmapWriter->save(s); + std::string date = timeToISO8601(time(nullptr)); + std::replace(date.begin(), date.end(), '/', '-'); + std::replace(date.begin(), date.end(), ':', '-'); + std::string s = settings.content.gameId + " - " + date + ".png"; + const bool success = bitmapWriter->save(s); bitmapWriter.reset(); - os_notify("Print out saved", 5000, s.c_str()); - NOTICE_LOG(NAOMI, "%s", s.c_str()); + if (success) { + os_notify("Print out saved", 5000, s.c_str()); + NOTICE_LOG(NAOMI, "%s", s.c_str()); + } } break; case 'K': // Set Kanji mode diff --git a/core/stdclass.cpp b/core/stdclass.cpp index 32b26bd3d..f8c97ab5e 100644 --- a/core/stdclass.cpp +++ b/core/stdclass.cpp @@ -221,3 +221,20 @@ u64 getTimeMs() return std::chrono::duration_cast(now - start).count(); } + +#ifdef _WIN32 +static struct tm *localtime_r(const time_t *_clock, struct tm *_result) +{ + return localtime_s(_result, _clock) ? nullptr : _result; +} +#endif + +std::string timeToISO8601(time_t time) +{ + tm t; + if (localtime_r(&time, &t) == nullptr) + return {}; + std::string s(32, '\0'); + s.resize(snprintf(s.data(), 32, "%04d/%02d/%02d %02d:%02d:%02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec)); + return s; +} diff --git a/core/stdclass.h b/core/stdclass.h index 6c7d209fd..5a3cad09e 100644 --- a/core/stdclass.h +++ b/core/stdclass.h @@ -11,6 +11,7 @@ #include #include #include +#include #ifdef __ANDROID__ #include @@ -201,6 +202,7 @@ public: }; u64 getTimeMs(); +std::string timeToISO8601(time_t time); class ThreadRunner { diff --git a/core/ui/gui.cpp b/core/ui/gui.cpp index 3cad3a112..390484d48 100644 --- a/core/ui/gui.cpp +++ b/core/ui/gui.cpp @@ -604,23 +604,6 @@ static void getScreenshot(std::vector& data, int width = 0) stbi_write_png_to_func(appendVectorData, &data, width, height, 3, &rawData[0], 0); } -#ifdef _WIN32 -static struct tm *localtime_r(const time_t *_clock, struct tm *_result) -{ - return localtime_s(_result, _clock) ? nullptr : _result; -} -#endif - -static std::string timeToString(time_t time) -{ - tm t; - if (localtime_r(&time, &t) == nullptr) - return {}; - std::string s(32, '\0'); - s.resize(snprintf(s.data(), 32, "%04d/%02d/%02d %02d:%02d:%02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec)); - return s; -} - static void savestate() { // TODO save state async: png compression, savestate file compression/write @@ -795,7 +778,7 @@ static void gui_display_commands() if (savestateDate == 0) ImGui::TextColored(gray, "Empty"); else - ImGui::TextColored(gray, "%s", timeToString(savestateDate).c_str()); + ImGui::TextColored(gray, "%s", timeToISO8601(savestateDate).c_str()); } savestatePic.draw(ScaledVec2(buttonWidth, 0.f)); } @@ -3764,7 +3747,7 @@ void gui_takeScreenshot() if (!game_started) return; uiThreadRunner.runOnThread([]() { - std::string date = timeToString(time(nullptr)); + std::string date = timeToISO8601(time(nullptr)); std::replace(date.begin(), date.end(), '/', '-'); std::replace(date.begin(), date.end(), ':', '-'); std::string name = "Flycast-" + date + ".png"; diff --git a/shell/libretro/oslib.cpp b/shell/libretro/oslib.cpp index c577ff0d9..17c3957c9 100644 --- a/shell/libretro/oslib.cpp +++ b/shell/libretro/oslib.cpp @@ -19,6 +19,9 @@ #include "oslib/oslib.h" #include "stdclass.h" #include "file/file_path.h" +#ifndef _WIN32 +#include +#endif const char *retro_get_system_directory(); @@ -128,6 +131,27 @@ std::string getTextureDumpPath() + "texdump" + std::string(path_default_slash()); } +std::string getScreenshotsPath() +{ + // Unfortunately retroarch doesn't expose its "screenshots" path + return std::string(retro_get_system_directory()) + "/dc"; +} + +void saveScreenshot(const std::string& name, const std::vector& data) +{ + std::string path = getScreenshotsPath(); + path += "/" + name; + FILE *f = nowide::fopen(path.c_str(), "wb"); + if (f == nullptr) + throw FlycastException(path); + if (std::fwrite(&data[0], data.size(), 1, f) != 1) { + std::fclose(f); + unlink(path.c_str()); + throw FlycastException(path); + } + std::fclose(f); +} + } #if defined(_WIN32) || defined(__APPLE__)