diff --git a/assets/sounds/screenshot.vag b/assets/sounds/screenshot.vag new file mode 100644 index 0000000..9d6cf3f Binary files /dev/null and b/assets/sounds/screenshot.vag differ diff --git a/resources.json b/resources.json index e75b4c6..37ce16a 100644 --- a/resources.json +++ b/resources.json @@ -73,6 +73,12 @@ "source": "${PROJECT_SOURCE_DIR}/assets/sounds/click.vag", "compression": "none" }, + { + "type": "binary", + "name": "assets/sounds/screenshot.vag", + "source": "${PROJECT_SOURCE_DIR}/assets/sounds/screenshot.vag", + "compression": "none" + }, { "type": "palette", "name": "assets/app.palette", diff --git a/src/common/file.cpp b/src/common/file.cpp index 14e8f60..c976938 100644 --- a/src/common/file.cpp +++ b/src/common/file.cpp @@ -4,6 +4,8 @@ #include #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;