From a6fb80f8c46fffcb2adcce5732af3be7e90be9bc Mon Sep 17 00:00:00 2001 From: Philippe Symons Date: Tue, 4 Mar 2025 13:12:58 +0100 Subject: [PATCH] Implement TransferPakRTCReader and add a "Print Game Clock" menu option temporarily --- Makefile | 3 ++ include/menu/MenuFunctions.h | 2 + include/scenes/AboutScene.h | 1 + include/scenes/InitTransferPakScene.h | 1 + include/transferpak/TransferPakManager.h | 6 +++ include/transferpak/TransferPakRTCReader.h | 28 +++++++++++ include/version.h | 8 +++ libpokemegb | 2 +- src/menu/MenuEntries.cpp | 8 +++ src/menu/MenuFunctions.cpp | 37 +++++++++++++- src/scenes/AboutScene.cpp | 7 ++- src/scenes/InitTransferPakScene.cpp | 6 ++- src/transferpak/TransferPakManager.cpp | 7 +++ src/transferpak/TransferPakRTCReader.cpp | 57 ++++++++++++++++++++++ src/version.cpp | 52 ++++++++++++++++++++ 15 files changed, 220 insertions(+), 5 deletions(-) create mode 100644 include/transferpak/TransferPakRTCReader.h create mode 100644 include/version.h create mode 100644 src/transferpak/TransferPakRTCReader.cpp create mode 100644 src/version.cpp diff --git a/Makefile b/Makefile index ef9b212..7f9eb9a 100755 --- a/Makefile +++ b/Makefile @@ -11,6 +11,9 @@ N64_ROM_REGIONFREE=1 N64_ROM_CONTROLLER_TYPE1=n64,pak=transfer N64_ROM_TITLE="PokeMe64" +# Uncomment this when generating a dev build +N64_C_AND_CXX_FLAGS += -DDEV_BUILD + SRCS := $(shell find $(SOURCE_DIR) -type f -name '*.cpp') OBJS := $(patsubst $(SOURCE_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(SRCS)) diff --git a/include/menu/MenuFunctions.h b/include/menu/MenuFunctions.h index d68db3d..f46f841 100755 --- a/include/menu/MenuFunctions.h +++ b/include/menu/MenuFunctions.h @@ -116,4 +116,6 @@ void askConfirmationWipeSave(void* context, const void* param); */ void resetRTC(void* context, const void* param); +void printClock(void* context, const void* param); + #endif \ No newline at end of file diff --git a/include/scenes/AboutScene.h b/include/scenes/AboutScene.h index 5cf655f..eb5d318 100644 --- a/include/scenes/AboutScene.h +++ b/include/scenes/AboutScene.h @@ -22,6 +22,7 @@ public: void render(RDPQGraphics& gfx, const Rectangle& sceneBounds) override; protected: private: + char headerTextString_[128]; uint8_t fontIdMainFont_; uint8_t fontMainFontStyleWhiteId_; sprite_t* logoLibDragon_; diff --git a/include/scenes/InitTransferPakScene.h b/include/scenes/InitTransferPakScene.h index 37118e8..b6d96c0 100755 --- a/include/scenes/InitTransferPakScene.h +++ b/include/scenes/InitTransferPakScene.h @@ -32,6 +32,7 @@ private: void loadSaveMetadata(); const char* getGameTypeString(); + char pokeMe64String_[48]; sprite_t* menu9SliceSprite_; TransferPakDetectionWidget tpakDetectWidget_; WidgetFocusChainSegment tpakDetectWidgetSegment_; diff --git a/include/transferpak/TransferPakManager.h b/include/transferpak/TransferPakManager.h index cc2a706..01f2d7c 100755 --- a/include/transferpak/TransferPakManager.h +++ b/include/transferpak/TransferPakManager.h @@ -54,6 +54,11 @@ public: */ void switchGBROMBank(uint8_t bankIndex); + /** + * @brief Returns whether we called setRAMEnabled(true) on this TransferPakManager instance before + */ + bool isRAMEnabled() const; + /** * @brief This function enables/disables gameboy RAM/RTC access. * WARNING: it switches to transfer pak bank 0 @@ -110,6 +115,7 @@ protected: private: joypad_port_t port_; bool isPoweredOn_; + bool ramEnabled_; uint8_t currentSRAMBank_; uint16_t readBufferBankOffset_; uint16_t writeBufferSRAMBankOffset_; diff --git a/include/transferpak/TransferPakRTCReader.h b/include/transferpak/TransferPakRTCReader.h new file mode 100644 index 0000000..c194f5a --- /dev/null +++ b/include/transferpak/TransferPakRTCReader.h @@ -0,0 +1,28 @@ +#ifndef _TRANSFERPAKRTCREADER_H +#define _TRANSFERPAKRTCREADER_H + +#include "RTCReader.h" + +class TransferPakManager; + +/** + * @brief This class implements the IRTCReader interface specifically for the N64 Transfer Pak with libdragon + */ +class TransferPakRTCReader : public BaseRTCReader +{ +public: + TransferPakRTCReader(TransferPakManager& tpakManager); + virtual ~TransferPakRTCReader(); + + /** + * This function will grab the current values of the RTC chip and store them internally + * + * Use the getXYZ() functions to actually retrieve this data + */ + void latch() override; +protected: +private: + TransferPakManager& tpakManager_; +}; + +#endif \ No newline at end of file diff --git a/include/version.h b/include/version.h new file mode 100644 index 0000000..fa16c8b --- /dev/null +++ b/include/version.h @@ -0,0 +1,8 @@ +#ifndef _VERSION_H +#define _VERSION_H + +#define POKEME64_VERSION_NUMBER "0.4" + +const char* getPokeMe64VersionString(); + +#endif \ No newline at end of file diff --git a/libpokemegb b/libpokemegb index aedb588..58fa35f 160000 --- a/libpokemegb +++ b/libpokemegb @@ -1 +1 @@ -Subproject commit aedb58832c188d3c7ce28da6bc1c453c128ec1be +Subproject commit 58fa35f882a83f2baaa0c2aded494189f7b22372 diff --git a/src/menu/MenuEntries.cpp b/src/menu/MenuEntries.cpp index 6e79f43..abbbeb1 100755 --- a/src/menu/MenuEntries.cpp +++ b/src/menu/MenuEntries.cpp @@ -70,6 +70,10 @@ MenuItemData gen2MenuEntries[] = { .title = "Reset Game Clock", .onConfirmAction = resetRTC }, + { + .title = "Print Game Clock", + .onConfirmAction = printClock + }, { .title = "About", .onConfirmAction = goToAboutScene @@ -107,6 +111,10 @@ MenuItemData gen2CrystalMenuEntries[] = { .title = "Reset Game Clock", .onConfirmAction = resetRTC }, + { + .title = "Print Game Clock", + .onConfirmAction = printClock + }, { .title = "About", .onConfirmAction = goToAboutScene diff --git a/src/menu/MenuFunctions.cpp b/src/menu/MenuFunctions.cpp index 0b23125..0a13305 100755 --- a/src/menu/MenuFunctions.cpp +++ b/src/menu/MenuFunctions.cpp @@ -10,6 +10,7 @@ #include "transferpak/TransferPakManager.h" #include "transferpak/TransferPakRomReader.h" #include "transferpak/TransferPakSaveManager.h" +#include "transferpak/TransferPakRTCReader.h" #define POKEMON_CRYSTAL_ITEM_ID_GS_BALL 0x73 @@ -795,13 +796,47 @@ void resetRTC(void* context, const void* param) TransferPakManager& tpakManager = scene->getDependencies().tpakManager; TransferPakRomReader romReader(tpakManager); TransferPakSaveManager saveManager(tpakManager); + TransferPakRTCReader rtcReader(tpakManager); Gen2GameReader gameReader(romReader, saveManager, gameType, language); tpakManager.setRAMEnabled(true); - gameReader.resetRTC(); + gameReader.getClockManager(rtcReader).resetRTC(); tpakManager.finishWrites(); tpakManager.setRAMEnabled(false); setDialogDataText(*diag, "The games' clock was reset! Start the game to reconfigure it! Don't forget to save!"); + scene->showDialog(diag); +} + +void printClock(void* context, const void* param) +{ + MenuScene* scene = static_cast(context); + + auto diag = new DialogData{ + .shouldDeleteWhenDone = true + }; + + if(scene->getDependencies().generation != 2) + { + setDialogDataText(*diag, "Sorry! This is only supported for Gen 2 Pokémon games!"); + scene->showDialog(diag); + return; + } + + const Gen2GameType gameType = static_cast(scene->getDependencies().specificGenVersion); + const Gen2LocalizationLanguage language = static_cast(scene->getDependencies().localization); + TransferPakManager& tpakManager = scene->getDependencies().tpakManager; + TransferPakRomReader romReader(tpakManager); + TransferPakSaveManager saveManager(tpakManager); + TransferPakRTCReader rtcReader(tpakManager); + Gen2GameReader gameReader(romReader, saveManager, gameType, language); + Gen2ClockManager clockManager = gameReader.getClockManager(rtcReader); + + tpakManager.setRAMEnabled(true); + clockManager.latchRTC(); + tpakManager.setRAMEnabled(false); + + setDialogDataText(*diag, "The current time is set to %s, %02hhu:%02hhu:%02hhu!\nRTC: %04hx %02hhx %02hhx %02hhx, off: %02hhx %02hhx %02hhx %02hhx", toString(clockManager.getDay()), clockManager.getHours(), clockManager.getMinutes(), clockManager.getSeconds(), rtcReader.getDaysCounter(), rtcReader.getHours(), rtcReader.getMinutes(), rtcReader.getSeconds(), clockManager.getRTCDayOffset(), clockManager.getRTCHourOffset(), clockManager.getRTCMinuteOffset(), clockManager.getRTCSecondOffset()); + scene->showDialog(diag); } \ No newline at end of file diff --git a/src/scenes/AboutScene.cpp b/src/scenes/AboutScene.cpp index 3110ea4..9d3ae05 100644 --- a/src/scenes/AboutScene.cpp +++ b/src/scenes/AboutScene.cpp @@ -1,6 +1,7 @@ #include "scenes/AboutScene.h" #include "core/FontManager.h" #include "scenes/SceneManager.h" +#include "version.h" static const ScrollWidgetStyle scrollWidgetStyle = { .scrollStep = 15, @@ -37,7 +38,7 @@ TESTING/VALIDATION MajorUpgrade )delim"; -static const char* headerTextString = R"delim(PokeMe64 Version 0.3 +static const char* headerTextStringFormat = R"delim(PokeMe64 Version %s by risingPhil SPECIAL THANKS TO: @@ -46,6 +47,7 @@ SPECIAL THANKS TO: AboutScene::AboutScene(SceneDependencies& deps, void*) : AbstractUIScene(deps) + , headerTextString_() , fontIdMainFont_(1) , fontMainFontStyleWhiteId_(0) , logoLibDragon_(nullptr) @@ -76,6 +78,7 @@ AboutScene::AboutScene(SceneDependencies& deps, void*) }) , bButtonPressed_(false) { + snprintf(headerTextString_, sizeof(headerTextString_), headerTextStringFormat, getPokeMe64VersionString()); fontIdMainFont_ = deps.fontManager.getFont("rom://Arial.font64"); const rdpq_fontstyle_t mainFontWhite = { @@ -129,7 +132,7 @@ void AboutScene::init() headerText_.setBounds(headerTextBounds); headerText_.setStyle(headerTextStyle); - headerText_.setData(headerTextString); + headerText_.setData(headerTextString_); scrollWidget_.addWidget(&headerText_); const ImageWidgetStyle imgDragonStyle = { diff --git a/src/scenes/InitTransferPakScene.cpp b/src/scenes/InitTransferPakScene.cpp index 872ca8a..2b03888 100755 --- a/src/scenes/InitTransferPakScene.cpp +++ b/src/scenes/InitTransferPakScene.cpp @@ -7,6 +7,7 @@ #include "gen1/Gen1GameReader.h" #include "gen2/Gen2GameReader.h" #include "menu/MenuEntries.h" +#include "version.h" #include @@ -27,6 +28,7 @@ static void tpakWidgetStateChangedCallback(void* context, TransferPakWidgetState InitTransferPakScene::InitTransferPakScene(SceneDependencies& deps, void*) : SceneWithDialogWidget(deps) + , pokeMe64String_() , menu9SliceSprite_(nullptr) , tpakDetectWidget_(deps.animationManager, deps.tpakManager) , tpakDetectWidgetSegment_(WidgetFocusChainSegment{ @@ -45,6 +47,8 @@ InitTransferPakScene::~InitTransferPakScene() void InitTransferPakScene::init() { uint8_t systemEntropy[4]; + + snprintf(pokeMe64String_, sizeof(pokeMe64String_), "PokeMe64 by risingPhil. Version %s", getPokeMe64VersionString()); menu9SliceSprite_ = sprite_load("rom://menu-bg-9slice.sprite"); SceneWithDialogWidget::init(); @@ -77,7 +81,7 @@ void InitTransferPakScene::destroy() void InitTransferPakScene::render(RDPQGraphics& gfx, const Rectangle& sceneBounds) { - gfx.drawText(Rectangle{0, 10, 320, 16}, "PokeMe64 by risingPhil. Version 0.3", pokeMe64TextSettings_); + gfx.drawText(Rectangle{0, 10, 320, 16}, pokeMe64String_, pokeMe64TextSettings_); tpakDetectWidget_.render(gfx, sceneBounds); SceneWithDialogWidget::render(gfx, sceneBounds); diff --git a/src/transferpak/TransferPakManager.cpp b/src/transferpak/TransferPakManager.cpp index 23fd11c..71ebe2e 100755 --- a/src/transferpak/TransferPakManager.cpp +++ b/src/transferpak/TransferPakManager.cpp @@ -11,6 +11,7 @@ static const uint16_t sramBankStartGBAddress = 0xA000; TransferPakManager::TransferPakManager() : port_(JOYPAD_PORT_1) , isPoweredOn_(false) + , ramEnabled_(false) , currentSRAMBank_(0) , readBufferBankOffset_(0xFFFF) , writeBufferSRAMBankOffset_(0xFFFF) @@ -139,6 +140,11 @@ void TransferPakManager::switchGBROMBank(uint8_t bankIndex) readBufferBankOffset_ = 0xFFFF; } +bool TransferPakManager::isRAMEnabled() const +{ + return ramEnabled_; +} + void TransferPakManager::setRAMEnabled(bool enabled) { uint8_t data[TPAK_BLOCK_SIZE]; @@ -157,6 +163,7 @@ void TransferPakManager::setRAMEnabled(bool enabled) debugf("[TransferPakManager]: %s: ERROR: transfer pak not ready yet. Current status is %hu\r\n", __FUNCTION__, status); status = getStatus(); } + ramEnabled_ = enabled; } void TransferPakManager::switchGBSRAMBank(uint8_t bankIndex) diff --git a/src/transferpak/TransferPakRTCReader.cpp b/src/transferpak/TransferPakRTCReader.cpp new file mode 100644 index 0000000..58e472d --- /dev/null +++ b/src/transferpak/TransferPakRTCReader.cpp @@ -0,0 +1,57 @@ +#include "transferpak/TransferPakRTCReader.h" +#include "transferpak/TransferPakManager.h" + +TransferPakRTCReader::TransferPakRTCReader(TransferPakManager& tpakManager) + : tpakManager_(tpakManager) +{ +} + +TransferPakRTCReader::~TransferPakRTCReader() +{ +} + +void TransferPakRTCReader::latch() +{ + uint8_t data[TPAK_BLOCK_SIZE]; + const int port = tpakManager_.getPort(); + + // enable SRAM and RTC first + const bool previousRAMState = tpakManager_.isRAMEnabled(); + tpakManager_.setRAMEnabled(true); + tpakManager_.finishWrites(); + + // now latch the RTC data into the RTC registers + // https://gbdev.io/pandocs/MBC3.html + memset(data, 0, sizeof(data)); + tpak_write(port, 0x6000, data, TPAK_BLOCK_SIZE); + memset(data, 1, sizeof(data)); + tpak_write(port, 0x6000, data, TPAK_BLOCK_SIZE); + + // RTC S (seconds) + tpakManager_.switchGBSRAMBank(0x08); + tpak_read(port, 0xA000, data, sizeof(data)); + seconds_ = data[0]; + + // RTC M (minutes) + tpakManager_.switchGBSRAMBank(0x09); + tpak_read(port, 0xA000, data, sizeof(data)); + minutes_ = data[0]; + + // RTC H (hours) + tpakManager_.switchGBSRAMBank(0x0A); + tpak_read(port, 0xA000, data, sizeof(data)); + hours_ = data[0]; + + // RTC DH (days counter upper 8 bits) + tpakManager_.switchGBSRAMBank(0x0C); + tpak_read(port, 0xA000, data, sizeof(data)); + daysRaw_ = data[0]; + daysRaw_ <<= 8; + + // RTC DL (days counter lower 8 bits) + tpakManager_.switchGBSRAMBank(0x0B); + tpak_read(port, 0xA000, data, sizeof(data)); + daysRaw_ |= data[0]; + + tpakManager_.setRAMEnabled(previousRAMState); +} diff --git a/src/version.cpp b/src/version.cpp new file mode 100644 index 0000000..11ba97c --- /dev/null +++ b/src/version.cpp @@ -0,0 +1,52 @@ +#include "version.h" + +#include +#include +#include + +#ifdef DEV_BUILD +static const char DEV_SUFFIX_STRING[] = "-dev"; + +static const char *months[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +static uint8_t get_month_number(const char *month) +{ + for (uint8_t i = 0; i < 12; ++i) + { + if (strcmp(month, months[i]) == 0) + { + return i + 1; + } + } + return 0; // Should never happen +} +#endif + +const char* getPokeMe64VersionString() +{ + static char versionString[20]; + + memcpy(versionString, POKEME64_VERSION_NUMBER, sizeof(POKEME64_VERSION_NUMBER)); + +#ifdef DEV_BUILD + size_t currentStringLength; + char month[4]; + uint16_t year; + int day; + uint8_t monthNumber; + + strcat(versionString, DEV_SUFFIX_STRING); + // -2 to not count the null terminators + currentStringLength = sizeof(POKEME64_VERSION_NUMBER) + sizeof(DEV_SUFFIX_STRING) - 2; + + // Parse __DATE__ ("MMM DD YYYY") + // We can't use strptime() because libdragon doesn't appear to have it. + sscanf(__DATE__, "%3s %d %hu", month, &day, &year); + monthNumber = get_month_number(month); + snprintf(versionString + currentStringLength, sizeof(versionString) - currentStringLength, "%04hu%02hhu%02d", year, monthNumber, day); +#endif + return versionString; +} \ No newline at end of file