From d85582d9b543a4dc52af17d1fd3a9ddf7c43ac22 Mon Sep 17 00:00:00 2001 From: spicyjpeg Date: Sat, 20 Apr 2024 16:50:23 +0200 Subject: [PATCH] Add screenshot functionality --- assets/sounds/screenshot.vag | Bin 0 -> 5424 bytes resources.json | 6 +++ src/common/file.cpp | 87 ++++++++++++++++++++++++++++++ src/common/file.hpp | 2 + src/common/gpu.cpp | 38 +++++++++++++ src/common/gpu.hpp | 10 ++++ src/common/ide.hpp | 10 ++-- src/common/util.hpp | 4 ++ src/main/app/app.cpp | 54 ++++++++++++++----- src/main/app/app.hpp | 9 ++-- src/main/app/cartactions.cpp | 2 +- src/main/app/cartunlock.cpp | 8 +-- src/main/app/cartworkers.cpp | 4 +- src/main/app/romactions.cpp | 3 +- src/main/app/romworkers.cpp | 2 +- src/main/cart.cpp | 6 +-- src/main/cart.hpp | 21 ++------ src/main/cartdata.cpp | 6 +-- src/main/cartdata.hpp | 8 --- src/main/cartio.cpp | 15 +++--- src/main/uibase.cpp | 100 +++++++++++++++++++++++++---------- src/main/uibase.hpp | 56 +++++++++++++++----- src/main/uicommon.cpp | 8 +-- src/main/uimodals.cpp | 17 +++--- src/main/zs01.hpp | 3 -- 25 files changed, 351 insertions(+), 128 deletions(-) create mode 100644 assets/sounds/screenshot.vag diff --git a/assets/sounds/screenshot.vag b/assets/sounds/screenshot.vag new file mode 100644 index 0000000000000000000000000000000000000000..9d6cf3f3dbe8b019a4c1146d31237823bdc48338 GIT binary patch literal 5424 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 #include #include "common/file.hpp" +#include "common/gpu.hpp" +#include "common/util.hpp" #include "ps1/pcdrv.h" #include "vendor/ff.h" #include "vendor/miniz.h" @@ -256,6 +258,8 @@ size_t Provider::saveData(const void *input, size_t length, const char *path) { #endif } +// TODO: these *really* belong somewhere else + size_t Provider::loadTIM(gpu::Image &output, const char *path) { util::Data data; @@ -308,6 +312,89 @@ size_t Provider::loadVAG(spu::Sound &output, const char *path) { return data.length; } +struct [[gnu::packed]] BMPHeader { +public: + uint16_t magic; + uint32_t fileLength; + uint8_t _reserved[4]; + uint32_t dataOffset; + + uint32_t headerLength, width, height; + uint16_t numPlanes, bpp; + uint32_t compType, dataLength, ppmX, ppmY, numColors, numColors2; + + inline void init(int _width, int _height, int _bpp) { + util::clear(*this); + + size_t length = _width * _height * _bpp / 8; + + magic = 0x4d42; + fileLength = sizeof(BMPHeader) + length; + dataOffset = sizeof(BMPHeader); + headerLength = sizeof(BMPHeader) - offsetof(BMPHeader, headerLength); + width = _width; + height = _height; + numPlanes = 1; + bpp = _bpp; + dataLength = length; + } +}; + +size_t Provider::saveVRAMBMP(gpu::RectWH &rect, const char *path) { +#ifdef ENABLE_FILE_WRITING + auto _file = openFile(path, WRITE | ALLOW_CREATE); + + if (!_file) + return 0; + + BMPHeader header; + + header.init(rect.w, rect.h, 16); + + size_t length = _file->write(&header, sizeof(header)); + util::Data buffer; + + if (buffer.allocate(rect.w * 2 + 32)) { + // Read the image from VRAM one line at a time from the bottom up, as + // the BMP format stores lines in reversed order. + gpu::RectWH slice; + + slice.x = rect.x; + slice.w = rect.w; + slice.h = 1; + + for (int y = rect.y + rect.h - 1; y >= rect.y; y--) { + slice.y = y; + auto lineLength = gpu::download(slice, buffer.ptr, true); + + // BMP stores channels in BGR order as opposed to RGB, so the red + // and blue channels must be swapped. + auto ptr = buffer.as(); + + for (int i = lineLength; i > 0; i -= 2) { + uint16_t value = *ptr, newValue; + + newValue = (value & (31 << 5)); + newValue |= (value & (31 << 10)) >> 10; + newValue |= (value & (31 << 0)) << 10; + *(ptr++) = newValue; + } + + length += _file->write(buffer.ptr, lineLength); + } + + buffer.destroy(); + } + + _file->close(); + delete _file; + + return length; +#else + return 0; +#endif +} + bool HostProvider::init(void) { int error = pcdrvInit(); diff --git a/src/common/file.hpp b/src/common/file.hpp index d2afe97..28b7b22 100644 --- a/src/common/file.hpp +++ b/src/common/file.hpp @@ -148,8 +148,10 @@ public: virtual size_t loadData(util::Data &output, const char *path); virtual size_t loadData(void *output, size_t length, const char *path); virtual size_t saveData(const void *input, size_t length, const char *path); + size_t loadTIM(gpu::Image &output, const char *path); size_t loadVAG(spu::Sound &output, const char *path); + size_t saveVRAMBMP(gpu::RectWH &rect, const char *path); }; class HostProvider : public Provider { diff --git a/src/common/gpu.cpp b/src/common/gpu.cpp index bfeba89..56edbc8 100644 --- a/src/common/gpu.cpp +++ b/src/common/gpu.cpp @@ -54,6 +54,44 @@ size_t upload(const RectWH &rect, const void *data, bool wait) { return length * _DMA_CHUNK_SIZE * 4; } +size_t download(const RectWH &rect, void *data, bool wait) { + size_t length = (rect.w * rect.h) / 2; + + util::assertAligned(data); + //assert(!(length % _DMA_CHUNK_SIZE)); + length = (length + _DMA_CHUNK_SIZE - 1) / _DMA_CHUNK_SIZE; + + if (!waitForDMATransfer(DMA_GPU, _DMA_TIMEOUT)) + return 0; + + auto enable = disableInterrupts(); + GPU_GP1 = gp1_dmaRequestMode(GP1_DREQ_NONE); + + while (!(GPU_GP1 & GP1_STAT_CMD_READY)) + __asm__ volatile(""); + + GPU_GP0 = gp0_flushCache(); + GPU_GP0 = gp0_vramRead(); + GPU_GP0 = gp0_xy(rect.x, rect.y); + GPU_GP0 = gp0_xy(rect.w, rect.h); + + GPU_GP1 = gp1_dmaRequestMode(GP1_DREQ_GP0_READ); + + while (!(GPU_GP1 & GP1_STAT_READ_READY)) + __asm__ volatile(""); + + DMA_MADR(DMA_GPU) = reinterpret_cast(data); + DMA_BCR (DMA_GPU) = _DMA_CHUNK_SIZE | (length << 16); + DMA_CHCR(DMA_GPU) = DMA_CHCR_READ | DMA_CHCR_MODE_SLICE | DMA_CHCR_ENABLE; + + if (enable) + enableInterrupts(); + if (wait) + waitForDMATransfer(DMA_GPU, _DMA_TIMEOUT); + + return length * _DMA_CHUNK_SIZE * 4; +} + /* Rendering context */ void Context::_applyResolution( diff --git a/src/common/gpu.hpp b/src/common/gpu.hpp index 89a66cf..4fb7ac0 100644 --- a/src/common/gpu.hpp +++ b/src/common/gpu.hpp @@ -52,6 +52,7 @@ static inline void enableDisplay(bool enable) { } size_t upload(const RectWH &rect, const void *data, bool wait); +size_t download(const RectWH &rect, void *data, bool wait); /* Rendering context */ @@ -93,6 +94,15 @@ public: ) : _lastTexpage(0) { setResolution(mode, width, height, forceInterlace, sideBySide); } + inline void getVRAMClipRect(RectWH &output) const { + auto &clip = _buffers[_currentBuffer ^ 1].clip; + + output.x = clip.x1; + output.y = clip.y1; + output.w = width; + output.h = height; + } + inline void newLayer(int x, int y) { newLayer(x, y, width, height); } diff --git a/src/common/ide.hpp b/src/common/ide.hpp index 6465ea2..647e06a 100644 --- a/src/common/ide.hpp +++ b/src/common/ide.hpp @@ -3,6 +3,7 @@ #include #include +#include "common/util.hpp" #include "ps1/registers573.h" namespace ide { @@ -241,17 +242,14 @@ public: uint8_t param[11]; uint8_t _reserved[4]; - inline void clear(void) { - __builtin_memset(this, 0, sizeof(Packet)); - } inline void setStartStopUnit(ATAPIStartStopMode mode) { - clear(); + util::clear(*this); command = ATAPI_START_STOP_UNIT; param[3] = mode & 3; } inline void setRead(uint32_t lba, size_t count) { - clear(); + util::clear(*this); command = ATAPI_READ12; param[1] = (lba >> 24) & 0xff; @@ -264,7 +262,7 @@ public: param[8] = (count >> 0) & 0xff; } inline void setSetCDSpeed(uint16_t value) { - clear(); + util::clear(*this); command = ATAPI_SET_CD_SPEED; param[1] = (value >> 8) & 0xff; diff --git a/src/common/util.hpp b/src/common/util.hpp index 28f1a0d..d0f4744 100644 --- a/src/common/util.hpp +++ b/src/common/util.hpp @@ -53,6 +53,10 @@ template static inline void assertAligned(X *ptr) { //assert(!(reinterpret_cast(ptr) % alignof(T))); } +template static inline void clear(T &obj, uint8_t value = 0) { + __builtin_memset(&obj, value, sizeof(obj)); +} + template static constexpr inline size_t countOf(T &array) { return sizeof(array) / sizeof(array[0]); } diff --git a/src/main/app/app.cpp b/src/main/app/app.cpp index 0d87c27..302b65d 100644 --- a/src/main/app/app.cpp +++ b/src/main/app/app.cpp @@ -1,6 +1,7 @@ #include "common/defs.hpp" #include "common/file.hpp" +#include "common/gpu.hpp" #include "common/io.hpp" #include "common/util.hpp" #include "main/app/app.hpp" @@ -62,7 +63,7 @@ static constexpr size_t _WORKER_STACK_SIZE = 0x20000; App::App(ui::Context &ctx, file::ZIPProvider &resourceProvider) #ifdef ENABLE_LOG_BUFFER -: _overlayLayer(_logBuffer), +: _logOverlay(_logBuffer), #else : #endif @@ -94,7 +95,7 @@ void App::_unloadCartData(void) { _cartDump.chipType = cart::NONE; _cartDump.flags = 0; _cartDump.clearIdentifiers(); - _cartDump.clearData(); + util::clear(_cartDump.data); _identified = nullptr; //_selectedEntry = nullptr; @@ -138,20 +139,44 @@ static const char *const _UI_SOUND_PATHS[ui::NUM_UI_SOUNDS]{ "assets/sounds/moveright.vag", // ui::SOUND_MOVE_RIGHT "assets/sounds/enter.vag", // ui::SOUND_ENTER "assets/sounds/exit.vag", // ui::SOUND_EXIT - "assets/sounds/click.vag" // ui::SOUND_CLICK + "assets/sounds/click.vag", // ui::SOUND_CLICK + "assets/sounds/screenshot.vag" // ui::SOUND_SCREENSHOT }; void App::_loadResources(void) { - _resourceProvider.loadTIM(_backgroundLayer.tile, "assets/textures/background.tim"); - _resourceProvider.loadTIM(_ctx.font.image, "assets/textures/font.tim"); - _resourceProvider.loadStruct(_ctx.font.metrics, "assets/textures/font.metrics"); - _resourceProvider.loadStruct(_ctx.colors, "assets/app.palette"); - _resourceProvider.loadData(_stringTable, "assets/app.strings"); + _resourceProvider.loadTIM(_background.tile, "assets/textures/background.tim"); + _resourceProvider.loadTIM(_ctx.font.image, "assets/textures/font.tim"); + _resourceProvider.loadStruct(_ctx.font.metrics, "assets/textures/font.metrics"); + _resourceProvider.loadStruct(_ctx.colors, "assets/app.palette"); + _resourceProvider.loadData(_stringTable, "assets/app.strings"); for (int i = 0; i < ui::NUM_UI_SOUNDS; i++) _resourceProvider.loadVAG(_ctx.sounds[i], _UI_SOUND_PATHS[i]); } +bool App::_takeScreenshot(void) { + file::FileInfo info; + char path[32]; + int index = 0; + + do { + index++; + snprintf(path, sizeof(path), EXTERNAL_DATA_DIR "/shot%04d.bmp", index); + } while (_fileProvider.getFileInfo(info, path)); + + gpu::RectWH clip; + + _ctx.gpuCtx.getVRAMClipRect(clip); + + if (_fileProvider.saveVRAMBMP(clip, path)) { + LOG("%s saved", path); + return true; + } else { + LOG("%s saving failed", path); + return false; + } +} + void App::_worker(void) { if (_workerFunction) { (this->*_workerFunction)(); @@ -189,13 +214,18 @@ void App::_interruptHandler(void) { char dateString[24]; - _backgroundLayer.leftText = dateString; - _backgroundLayer.rightText = "v" VERSION_STRING; + _textOverlay.leftText = dateString; + _textOverlay.rightText = "v" VERSION_STRING; + _screenshotOverlay.callback = [](ui::Context &ctx) -> bool { + return APP->_takeScreenshot(); + }; - _ctx.background = &_backgroundLayer; + _ctx.backgrounds[0] = &_background; + _ctx.backgrounds[1] = &_textOverlay; #ifdef ENABLE_LOG_BUFFER - _ctx.overlay = &_overlayLayer; + _ctx.overlays[0] = &_logOverlay; #endif + _ctx.overlays[1] = &_screenshotOverlay; _ctx.show(_workerStatusScreen); for (;;) { diff --git a/src/main/app/app.hpp b/src/main/app/app.hpp index 54c09f2..600edd7 100644 --- a/src/main/app/app.hpp +++ b/src/main/app/app.hpp @@ -100,10 +100,12 @@ private: ChecksumScreen _checksumScreen; #ifdef ENABLE_LOG_BUFFER - util::LogBuffer _logBuffer; - ui::LogOverlay _overlayLayer; + util::LogBuffer _logBuffer; + ui::LogOverlay _logOverlay; #endif - ui::TiledBackground _backgroundLayer; + ui::TiledBackground _background; + ui::TextOverlay _textOverlay; + ui::ScreenshotOverlay _screenshotOverlay; ui::Context &_ctx; file::ZIPProvider &_resourceProvider; @@ -129,6 +131,7 @@ private: void _setupWorker(bool (App::*func)(void)); void _setupInterrupts(void); void _loadResources(void); + bool _takeScreenshot(void); // cartworkers.cpp bool _cartDetectWorker(void); diff --git a/src/main/app/cartactions.cpp b/src/main/app/cartactions.cpp index fbe1f21..57a8e9a 100644 --- a/src/main/app/cartactions.cpp +++ b/src/main/app/cartactions.cpp @@ -118,7 +118,7 @@ void CartActionsScreen::resetSystemID(ui::Context &ctx) { APP->_confirmScreen.setMessage( *this, [](ui::Context &ctx) { - APP->_cartParser->getIdentifiers()->systemID.clear(); + util::clear(APP->_cartParser->getIdentifiers()->systemID); APP->_cartParser->flush(); APP->_setupWorker(&App::_cartWriteWorker); diff --git a/src/main/app/cartunlock.cpp b/src/main/app/cartunlock.cpp index 4e3cd11..c30ba40 100644 --- a/src/main/app/cartunlock.cpp +++ b/src/main/app/cartunlock.cpp @@ -278,18 +278,14 @@ void UnlockKeyScreen::useCustomKey(ui::Context &ctx) { } void UnlockKeyScreen::use00Key(ui::Context &ctx) { - __builtin_memset( - APP->_cartDump.dataKey, 0x00, sizeof(APP->_cartDump.dataKey) - ); + util::clear(APP->_cartDump.dataKey, 0x00); APP->_selectedEntry = nullptr; ctx.show(APP->_confirmScreen, false, true); } void UnlockKeyScreen::useFFKey(ui::Context &ctx) { - __builtin_memset( - APP->_cartDump.dataKey, 0xff, sizeof(APP->_cartDump.dataKey) - ); + util::clear(APP->_cartDump.dataKey, 0xff); APP->_selectedEntry = nullptr; ctx.show(APP->_confirmScreen, false, true); diff --git a/src/main/app/cartworkers.cpp b/src/main/app/cartworkers.cpp index 5a1c37e..55aa8c9 100644 --- a/src/main/app/cartworkers.cpp +++ b/src/main/app/cartworkers.cpp @@ -230,7 +230,7 @@ bool App::_cartDumpWorker(void) { do { index++; snprintf( - path, sizeof(path), EXTERNAL_DATA_DIR "/cart%d.573", index + path, sizeof(path), EXTERNAL_DATA_DIR "/cart%04d.573", index ); } while (_fileProvider.getFileInfo(info, path)); } @@ -389,7 +389,7 @@ bool App::_cartReflashWorker(void) { auto pri = _cartParser->getIdentifiers(); auto pub = _cartParser->getPublicIdentifiers(); - _cartDump.clearData(); + util::clear(_cartDump.data); _cartDump.initConfig( 9, _selectedEntry->flags & cart::DATA_HAS_PUBLIC_SECTION ); diff --git a/src/main/app/romactions.cpp b/src/main/app/romactions.cpp index 69b0707..1bc2795 100644 --- a/src/main/app/romactions.cpp +++ b/src/main/app/romactions.cpp @@ -5,7 +5,6 @@ #include "common/util.hpp" #include "main/app/app.hpp" #include "main/app/romactions.hpp" -#include "main/cartdata.hpp" #include "main/uibase.hpp" #include "main/uicommon.hpp" @@ -264,7 +263,7 @@ void StorageActionsScreen::resetFlashHeader(ui::Context &ctx) { APP->_confirmScreen.setMessage( *this, [](ui::Context &ctx) { - APP->_romHeaderDump.clearData(); + util::clear(APP->_romHeaderDump.data); APP->_setupWorker(&App::_flashHeaderWriteWorker); ctx.show(APP->_workerStatusScreen, false, true); }, diff --git a/src/main/app/romworkers.cpp b/src/main/app/romworkers.cpp index 607054f..a983d4a 100644 --- a/src/main/app/romworkers.cpp +++ b/src/main/app/romworkers.cpp @@ -117,7 +117,7 @@ bool App::_romDumpWorker(void) { do { index++; - snprintf(dirPath, sizeof(dirPath), EXTERNAL_DATA_DIR "/dump%d", index); + snprintf(dirPath, sizeof(dirPath), EXTERNAL_DATA_DIR "/dump%04d", index); } while (_fileProvider.getFileInfo(info, dirPath)); LOG("saving dumps to %s", dirPath); diff --git a/src/main/cart.cpp b/src/main/cart.cpp index 40f58cf..ce4a58b 100644 --- a/src/main/cart.cpp +++ b/src/main/cart.cpp @@ -54,7 +54,7 @@ const ChipSize CHIP_SIZES[NUM_CHIP_TYPES]{ }; void CartDump::initConfig(uint8_t maxAttempts, bool hasPublicSection) { - clearConfig(); + util::clear(config); switch (chipType) { case X76F041: @@ -121,8 +121,8 @@ size_t CartDump::toQRString(char *output) const { size_t uncompLength = getDumpLength(); size_t compLength = MAX_QR_STRING_LENGTH; - __builtin_memset(compressed, 0, MAX_QR_STRING_LENGTH); - int error = mz_compress2( + util::clear(compressed); + auto error = mz_compress2( compressed, reinterpret_cast(&compLength), reinterpret_cast(this), uncompLength, MZ_BEST_COMPRESSION diff --git a/src/main/cart.hpp b/src/main/cart.hpp index cf48769..65dac12 100644 --- a/src/main/cart.hpp +++ b/src/main/cart.hpp @@ -43,9 +43,6 @@ public: inline void copyTo(uint8_t *dest) const { __builtin_memcpy(dest, data, sizeof(data)); } - inline void clear(void) { - __builtin_memset(data, 0, sizeof(data)); - } inline bool isEmpty(void) const { return (util::sum(data, sizeof(data)) == 0); } @@ -102,9 +99,9 @@ public: return (sizeof(CartDump) - sizeof(data)) + getChipSize().dataLength; } inline void clearIdentifiers(void) { - systemID.clear(); - cartID.clear(); - zsID.clear(); + util::clear(systemID); + util::clear(cartID); + util::clear(zsID); } inline void copyDataFrom(const uint8_t *source) { __builtin_memcpy(data, source, getChipSize().dataLength); @@ -112,27 +109,18 @@ public: inline void copyDataTo(uint8_t *dest) const { __builtin_memcpy(dest, data, getChipSize().dataLength); } - inline void clearData(void) { - __builtin_memset(data, 0, sizeof(data)); - } inline void copyKeyFrom(const uint8_t *source) { __builtin_memcpy(dataKey, source, sizeof(dataKey)); } inline void copyKeyTo(uint8_t *dest) const { __builtin_memcpy(dest, dataKey, sizeof(dataKey)); } - inline void clearKey(void) { - __builtin_memset(dataKey, 0, sizeof(dataKey)); - } inline void copyConfigFrom(const uint8_t *source) { __builtin_memcpy(config, source, sizeof(config)); } inline void copyConfigTo(uint8_t *dest) const { __builtin_memcpy(dest, config, sizeof(config)); } - inline void clearConfig(void) { - __builtin_memset(config, 0, sizeof(config)); - } void initConfig(uint8_t maxAttempts, bool hasPublicSection = false); bool isPublicDataEmpty(void) const; @@ -158,9 +146,6 @@ public: inline bool validateMagic(void) const { return (magic == ROM_HEADER_DUMP_HEADER_MAGIC); } - inline void clearData(void) { - __builtin_memset(data, 0xff, sizeof(data)); - } bool isDataEmpty(void) const; }; diff --git a/src/main/cartdata.cpp b/src/main/cartdata.cpp index fbce72c..e6ced83 100644 --- a/src/main/cartdata.cpp +++ b/src/main/cartdata.cpp @@ -25,7 +25,7 @@ uint8_t IdentifierSet::getFlags(void) const { } void IdentifierSet::setInstallID(uint8_t prefix) { - installID.clear(); + util::clear(installID); installID.data[0] = prefix; installID.updateChecksum(); @@ -34,7 +34,7 @@ void IdentifierSet::setInstallID(uint8_t prefix) { void IdentifierSet::updateTraceID( TraceIDType type, int param, const Identifier *_cartID ) { - traceID.clear(); + util::clear(traceID); const uint8_t *input = _cartID ? &_cartID->data[1] : &cartID.data[1]; uint16_t checksum = 0; @@ -93,7 +93,7 @@ uint8_t PublicIdentifierSet::getFlags(void) const { } void PublicIdentifierSet::setInstallID(uint8_t prefix) { - installID.clear(); + util::clear(installID); installID.data[0] = prefix; installID.updateChecksum(); diff --git a/src/main/cartdata.hpp b/src/main/cartdata.hpp index f796eda..48bce14 100644 --- a/src/main/cartdata.hpp +++ b/src/main/cartdata.hpp @@ -57,10 +57,6 @@ class [[gnu::packed]] IdentifierSet { public: Identifier traceID, cartID, installID, systemID; // aka TID, SID, MID, XID - inline void clear(void) { - __builtin_memset(this, 0, sizeof(IdentifierSet)); - } - uint8_t getFlags(void) const; void setInstallID(uint8_t prefix); void updateTraceID( @@ -72,10 +68,6 @@ class [[gnu::packed]] PublicIdentifierSet { public: Identifier installID, systemID; // aka MID, XID - inline void clear(void) { - __builtin_memset(this, 0, sizeof(PublicIdentifierSet)); - } - uint8_t getFlags(void) const; void setInstallID(uint8_t prefix); }; diff --git a/src/main/cartio.cpp b/src/main/cartio.cpp index a9e1b56..967f62e 100644 --- a/src/main/cartio.cpp +++ b/src/main/cartio.cpp @@ -1,6 +1,7 @@ #include #include "common/io.hpp" +#include "common/util.hpp" #include "main/cart.hpp" #include "main/cartio.hpp" #include "main/zs01.hpp" @@ -90,11 +91,11 @@ DriverError DummyDriver::erase(void) { if (!__builtin_memcmp( _dump.dataKey, dummyDriverDump.dataKey, sizeof(_dump.dataKey) )) { - dummyDriverDump.clearData(); - dummyDriverDump.clearKey(); + util::clear(dummyDriverDump.data); + util::clear(dummyDriverDump.dataKey); // TODO: clear config registers as well - _dump.clearKey(); + util::clear(_dump.dataKey); return NO_ERROR; } @@ -276,7 +277,7 @@ DriverError X76F041Driver::readPrivateData(void) { if (error) return error; - _dump.clearConfig(); + util::clear(_dump.config); io::i2cReadBytes(_dump.config, 5); io::i2cStopWithCS(); @@ -328,7 +329,7 @@ DriverError X76F041Driver::erase(void) { io::i2cStopWithCS(_X76_WRITE_DELAY); - _dump.clearKey(); + util::clear(_dump.dataKey); return NO_ERROR; } @@ -607,7 +608,7 @@ DriverError ZS01Driver::erase(void) { key.unpackFrom(_dump.dataKey); - __builtin_memset(request.data, 0, sizeof(request.data)); + util::clear(request.data); request.address = zs01::ADDR_ERASE; request.encodeWriteRequest(key, _encoderState); @@ -615,7 +616,7 @@ DriverError ZS01Driver::erase(void) { if (error) return error; - _dump.clearKey(); + util::clear(_dump.dataKey); return NO_ERROR; } diff --git a/src/main/uibase.cpp b/src/main/uibase.cpp index 9b7d82b..8b540b2 100644 --- a/src/main/uibase.cpp +++ b/src/main/uibase.cpp @@ -4,6 +4,7 @@ #include "common/gpufont.hpp" #include "common/io.hpp" #include "common/pad.hpp" +#include "common/util.hpp" #include "main/uibase.hpp" #include "ps1/gpucmd.h" @@ -51,8 +52,9 @@ static const uint32_t _BUTTON_MAPPINGS[NUM_BUTTON_MAPS][NUM_BUTTONS]{ }; ButtonState::ButtonState(void) -: _held(0), _prevHeld(0), _pressed(0), _released(0), _repeating(0), -_repeatTimer(0), buttonMap(MAP_JOYSTICK) {} +: _held(0), _prevHeld(0), _longHeld(0), _prevLongHeld(0), _pressed(0), +_released(0), _longPressed(0), _longReleased(0), _repeatTimer(0), +buttonMap(MAP_JOYSTICK) {} uint8_t ButtonState::_getHeld(void) const { uint32_t inputs = io::getJAMMAInputs(); @@ -94,25 +96,29 @@ uint8_t ButtonState::_getHeld(void) const { } void ButtonState::reset(void) { - _held = _getHeld(); - _prevHeld = _held; + _held = _getHeld(); + _prevHeld = _held; + _longHeld = 0; + _prevLongHeld = 0; - _pressed = 0; - _released = 0; - _repeating = 0; - _repeatTimer = 0; + _pressed = 0; + _released = 0; + _longPressed = 0; + _longReleased = 0; + _repeatTimer = 0; } void ButtonState::update(void) { - _prevHeld = _held; - _held = _getHeld(); + _prevHeld = _held; + _prevLongHeld = _longHeld; + _held = _getHeld(); uint32_t changed = _prevHeld ^ _held; if (buttonMap == MAP_SINGLE_BUTTON) { - _pressed = 0; - _released = 0; - _repeating = 0; + _pressed = 0; + _released = 0; + _longHeld = 0; // In single-button mode, interpret a short button press as the right // button and a long press as start. @@ -135,19 +141,24 @@ void ButtonState::update(void) { else if (_held) _repeatTimer++; - _pressed = (changed & _held) & ~_pressed; - _released = (changed & _prevHeld) & ~_released; - _repeating = (_repeatTimer >= REPEAT_DELAY) ? _held : 0; + _pressed = (changed & _held) & ~_pressed; + _released = (changed & _prevHeld) & ~_released; + _longHeld = (_repeatTimer >= REPEAT_DELAY) ? _held : 0; } + + changed = _prevLongHeld ^ _longHeld; + + _longPressed = (changed & _longHeld) & ~_longPressed; + _longReleased = (changed & _prevLongHeld) & ~_longReleased; } /* UI context */ Context::Context(gpu::Context &gpuCtx, void *screenData) -: _currentScreen(0), gpuCtx(gpuCtx), background(nullptr), overlay(nullptr), -time(0), screenData(screenData) { - _screens[0] = nullptr; - _screens[1] = nullptr; +: _currentScreen(0), gpuCtx(gpuCtx), time(0), screenData(screenData) { + util::clear(_screens); + util::clear(backgrounds); + util::clear(overlays); } void Context::show(Screen &screen, bool goBack, bool playSound) { @@ -169,21 +180,30 @@ void Context::draw(void) { auto oldScreen = _screens[_currentScreen ^ 1]; auto newScreen = _screens[_currentScreen]; - if (background) - background->draw(*this); + for (auto layer : backgrounds) { + if (layer) + layer->draw(*this); + } + if (oldScreen) oldScreen->draw(*this, false); if (newScreen) newScreen->draw(*this, true); - if (overlay) - overlay->draw(*this); + + for (auto layer : overlays) { + if (layer) + layer->draw(*this); + } } void Context::update(void) { buttons.update(); - if (overlay) - overlay->update(*this); + for (auto layer : overlays) { + if (layer) + layer->update(*this); + } + if (_screens[_currentScreen]) _screens[_currentScreen]->update(*this); } @@ -215,7 +235,9 @@ void TiledBackground::draw(Context &ctx, bool active) const { for (int y = -offsetY; y < ctx.gpuCtx.height; y += tile.height) tile.draw(ctx.gpuCtx, x, y); } +} +void TextOverlay::draw(Context &ctx, bool active) const { gpu::RectWH rect; rect.y = ctx.gpuCtx.height - (8 + ctx.font.metrics.lineHeight); @@ -272,7 +294,9 @@ void LogOverlay::draw(Context &ctx, bool active) const { } void LogOverlay::update(Context &ctx) { - if (ctx.buttons.pressed(BTN_DEBUG)) { + if ( + ctx.buttons.released(BTN_DEBUG) && !ctx.buttons.longReleased(BTN_DEBUG) + ) { bool shown = !_slideAnim.getTargetValue(); _slideAnim.setValue(ctx.time, shown ? ctx.gpuCtx.height : 0, SPEED_SLOW); @@ -280,6 +304,27 @@ void LogOverlay::update(Context &ctx) { } } +void ScreenshotOverlay::draw(Context &ctx, bool active) const { + int brightness = _flashAnim.getValue(ctx.time); + + if (!brightness) + return; + + _newLayer(ctx, 0, 0, ctx.gpuCtx.width, ctx.gpuCtx.height); + ctx.gpuCtx.drawBackdrop( + gp0_rgb(brightness, brightness, brightness), GP0_BLEND_ADD + ); +} + +void ScreenshotOverlay::update(Context &ctx) { + if (ctx.buttons.longPressed(BTN_DEBUG)) { + if (callback(ctx)) { + _flashAnim.setValue(ctx.time, 0xff, 0, SPEED_SLOW); + ctx.sounds[ui::SOUND_SCREENSHOT].play(); + } + } +} + /* Base screen classes */ void AnimatedScreen::_newLayer( @@ -308,6 +353,7 @@ void BackdropScreen::hide(Context &ctx, bool goBack) { void BackdropScreen::draw(Context &ctx, bool active) const { int brightness = _backdropAnim.getValue(ctx.time); + if (!brightness) return; diff --git a/src/main/uibase.hpp b/src/main/uibase.hpp index 54aa4a7..6474dff 100644 --- a/src/main/uibase.hpp +++ b/src/main/uibase.hpp @@ -12,7 +12,7 @@ namespace ui { /* Public constants */ static constexpr int NUM_UI_COLORS = 18; -static constexpr int NUM_UI_SOUNDS = 9; +static constexpr int NUM_UI_SOUNDS = 10; enum Color { COLOR_DEFAULT = 0, @@ -44,7 +44,8 @@ enum Sound { SOUND_MOVE_RIGHT = 5, SOUND_ENTER = 6, SOUND_EXIT = 7, - SOUND_CLICK = 8 + SOUND_CLICK = 8, + SOUND_SCREENSHOT = 9 }; enum AnimationSpeed { @@ -106,7 +107,9 @@ class ButtonState { private: uint32_t _mappings[NUM_BUTTONS]; uint8_t _held, _prevHeld; - uint8_t _pressed, _released, _repeating; + uint8_t _longHeld, _prevLongHeld; + uint8_t _pressed, _released; + uint8_t _longPressed, _longReleased; int _repeatTimer; @@ -115,20 +118,24 @@ private: public: ButtonMap buttonMap; - inline bool pressed(Button button) const { - return _pressed & (1 << button); + inline bool held(Button button) const { + return (_held >> button) & 1; } - inline bool pressedRepeating(Button button) const { - return (_pressed | _repeating) & (1 << button); + inline bool pressed(Button button) const { + return (_pressed >> button) & 1; } inline bool released(Button button) const { - return _released & (1 << button); + return (_released >> button) & 1; } - inline bool repeating(Button button) const { - return _repeating & (1 << button); + + inline bool longHeld(Button button) const { + return (_longHeld >> button) & 1; } - inline bool held(Button button) const { - return _held & (1 << button); + inline bool longPressed(Button button) const { + return (_longPressed >> button) & 1; + } + inline bool longReleased(Button button) const { + return (_longReleased >> button) & 1; } ButtonState(void); @@ -148,7 +155,8 @@ private: public: gpu::Context &gpuCtx; - Layer *background, *overlay; + + Layer *backgrounds[4], *overlays[4]; gpu::Font font; gpu::Color colors[NUM_UI_COLORS]; @@ -186,9 +194,15 @@ public: class TiledBackground : public Layer { public: gpu::Image tile; + + void draw(Context &ctx, bool active = true) const; +}; + +class TextOverlay : public Layer { +public: const char *leftText, *rightText; - inline TiledBackground(void) + inline TextOverlay(void) : leftText(nullptr), rightText(nullptr) {} void draw(Context &ctx, bool active = true) const; @@ -205,6 +219,20 @@ public: void update(Context &ctx); }; +class ScreenshotOverlay : public Layer { +private: + util::Tween _flashAnim; + +public: + bool (*callback)(ui::Context &ctx); + + inline ScreenshotOverlay(void) + : callback(nullptr) {} + + void draw(Context &ctx, bool active = true) const; + void update(Context &ctx); +}; + /* Base screen classes */ // This is probably the most stripped-down way to implement something that diff --git a/src/main/uicommon.cpp b/src/main/uicommon.cpp index 44a4e6e..5717ce6 100644 --- a/src/main/uicommon.cpp +++ b/src/main/uicommon.cpp @@ -84,7 +84,7 @@ void TextScreen::update(Context &ctx) { if ( ctx.buttons.pressed(ui::BTN_LEFT) || - (ctx.buttons.repeating(ui::BTN_LEFT) && (value > 0)) + (ctx.buttons.longHeld(ui::BTN_LEFT) && (value > 0)) ) { if (value <= 0) { value = scrollHeight; @@ -98,7 +98,7 @@ void TextScreen::update(Context &ctx) { } if ( ctx.buttons.pressed(ui::BTN_RIGHT) || - (ctx.buttons.repeating(ui::BTN_RIGHT) && (value < scrollHeight)) + (ctx.buttons.longHeld(ui::BTN_RIGHT) && (value < scrollHeight)) ) { if (value >= scrollHeight) { value = 0; @@ -281,7 +281,7 @@ void ListScreen::draw(Context &ctx, bool active) const { void ListScreen::update(Context &ctx) { if ( ctx.buttons.pressed(ui::BTN_LEFT) || - (ctx.buttons.repeating(ui::BTN_LEFT) && (_activeItem > 0)) + (ctx.buttons.longHeld(ui::BTN_LEFT) && (_activeItem > 0)) ) { _activeItem--; if (_activeItem < 0) { @@ -296,7 +296,7 @@ void ListScreen::update(Context &ctx) { } if ( ctx.buttons.pressed(ui::BTN_RIGHT) || - (ctx.buttons.repeating(ui::BTN_RIGHT) && (_activeItem < (_listLength - 1))) + (ctx.buttons.longHeld(ui::BTN_RIGHT) && (_activeItem < (_listLength - 1))) ) { _activeItem++; if (_activeItem >= _listLength) { diff --git a/src/main/uimodals.cpp b/src/main/uimodals.cpp index b69dc5b..f7ad023 100644 --- a/src/main/uimodals.cpp +++ b/src/main/uimodals.cpp @@ -2,6 +2,7 @@ #include #include #include "common/gpu.hpp" +#include "common/util.hpp" #include "main/uibase.hpp" #include "main/uimodals.hpp" @@ -82,7 +83,7 @@ void MessageBoxScreen::update(Context &ctx) { if ( ctx.buttons.pressed(ui::BTN_LEFT) || - (ctx.buttons.repeating(ui::BTN_LEFT) && (_activeButton > 0)) + (ctx.buttons.longHeld(ui::BTN_LEFT) && (_activeButton > 0)) ) { _activeButton--; if (_activeButton < 0) { @@ -96,7 +97,7 @@ void MessageBoxScreen::update(Context &ctx) { } if ( ctx.buttons.pressed(ui::BTN_RIGHT) || - (ctx.buttons.repeating(ui::BTN_RIGHT) && (_activeButton < (numButtons - 1))) + (ctx.buttons.longHeld(ui::BTN_RIGHT) && (_activeButton < (numButtons - 1))) ) { _activeButton++; if (_activeButton >= numButtons) { @@ -112,13 +113,13 @@ void MessageBoxScreen::update(Context &ctx) { HexEntryScreen::HexEntryScreen(void) : _bufferLength(0) { - __builtin_memset(_buffer, 0, sizeof(_buffer)); + util::clear(_buffer); } void HexEntryScreen::show(Context &ctx, bool goBack) { MessageBoxScreen::show(ctx, goBack); - //__builtin_memset(_buffer, 0, _bufferLength); + //util::clear(_buffer); _buttonIndexOffset = _bufferLength * 2; _charWidth = ctx.font.getCharacterWidth('0'); @@ -187,7 +188,7 @@ void HexEntryScreen::update(Context &ctx) { if ( ctx.buttons.pressed(ui::BTN_LEFT) || - (ctx.buttons.repeating(ui::BTN_LEFT) && (value > 0)) + (ctx.buttons.longHeld(ui::BTN_LEFT) && (value > 0)) ) { if (--value < 0) { value = 0xf; @@ -198,7 +199,7 @@ void HexEntryScreen::update(Context &ctx) { } if ( ctx.buttons.pressed(ui::BTN_RIGHT) || - (ctx.buttons.repeating(ui::BTN_RIGHT) && (value < 0xf)) + (ctx.buttons.longHeld(ui::BTN_RIGHT) && (value < 0xf)) ) { if (++value > 0xf) { value = 0; @@ -365,7 +366,7 @@ void DateEntryScreen::update(Context &ctx) { if ( ctx.buttons.pressed(ui::BTN_LEFT) || - (ctx.buttons.repeating(ui::BTN_LEFT) && (value > field.minValue)) + (ctx.buttons.longHeld(ui::BTN_LEFT) && (value > field.minValue)) ) { if (--value < field.minValue) { value = field.maxValue; @@ -376,7 +377,7 @@ void DateEntryScreen::update(Context &ctx) { } if ( ctx.buttons.pressed(ui::BTN_RIGHT) || - (ctx.buttons.repeating(ui::BTN_RIGHT) && (value < field.maxValue)) + (ctx.buttons.longHeld(ui::BTN_RIGHT) && (value < field.maxValue)) ) { if (++value > field.maxValue) { value = field.minValue; diff --git a/src/main/zs01.hpp b/src/main/zs01.hpp index 6f3e165..758b220 100644 --- a/src/main/zs01.hpp +++ b/src/main/zs01.hpp @@ -64,9 +64,6 @@ public: inline void copyTo(uint8_t *dest) const { __builtin_memcpy(dest, data, sizeof(data)); } - inline void clear(void) { - __builtin_memset(data, 0, sizeof(data)); - } void updateCRC(void); bool validateCRC(void) const;