From 2cea9e6d70fecaba150b29d0bea72df5c731e20a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 16 Oct 2022 03:29:36 -0700 Subject: [PATCH 01/20] README: Add MBC30 to the supported mappers list (closes #2686) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ce1fa0bb1..6cf621925 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ The following mappers are fully supported: - MBC2 - MBC3 - MBC3+RTC +- MBC30 - MBC5 - MBC5+Rumble - MBC7 From 153efa253c4f3913ba182e73aeb433145e3de350 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 16 Oct 2022 22:14:46 -0700 Subject: [PATCH 02/20] Qt: Fix e-Reader scanning function reentry (fixes #2693) --- CHANGES | 1 + src/platform/qt/CoreController.cpp | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 13d50691a..3a69d0996 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,7 @@ Features: Other fixes: - Qt: Manually split filename to avoid overzealous splitting (fixes mgba.io/i/2681) - Qt: Expand criteria for tag branch names (fixes mgba.io/i/2679) + - Qt: Fix scanning specific e-Reader dotcodes (fixes mgba.io/i/2693) - Res: Fix species name location in Ruby/Sapphire revs 1/2 (fixes mgba.io/i/2685) Misc: - GB Serialize: Add missing savestate support for MBC6 and NT (newer) diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 61774de71..37fc1b01f 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -914,7 +914,10 @@ void CoreController::scanCard(const QString& path) { if (!file.open(QIODevice::ReadOnly)) { return; } - m_eReaderData = file.read(2912); + QByteArray eReaderData = file.read(2912); + if (eReaderData.isEmpty()) { + return; + } file.seek(0); QStringList lines; @@ -936,6 +939,7 @@ void CoreController::scanCard(const QString& path) { } } scanCards(lines); + m_eReaderData = eReaderData; } else if (image.size() == QSize(989, 44) || image.size() == QSize(639, 44)) { const uchar* bits = image.constBits(); size_t size; From a305882dba28804ba755b634a8f943c26f2d0106 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 17 Oct 2022 00:59:59 -0700 Subject: [PATCH 03/20] GB MBC: Improve Li Cheng heuristic slightly --- src/gb/mbc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gb/mbc.c b/src/gb/mbc.c index f15cba519..441baaa53 100644 --- a/src/gb/mbc.c +++ b/src/gb/mbc.c @@ -216,6 +216,9 @@ static enum GBMemoryBankControllerType _detectUnlMBC(const uint8_t* mem, size_t if (cart->type == 0x01) { // Make sure we're not using a "fixed" version return GB_UNL_LI_CHENG; } + if ((0x8000 << cart->romSize) != size) { + return GB_UNL_LI_CHENG; + } break; } From ad2a7a748a6b10331b7d95b293ff56eed26c4254 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 17 Oct 2022 01:31:04 -0700 Subject: [PATCH 04/20] GB MBC: Add GGB-81 support --- CHANGES | 2 +- README.md | 1 + include/mgba/gb/interface.h | 1 + src/gb/mbc.c | 10 +++++++++- src/gb/mbc/mbc-private.h | 2 ++ src/gb/mbc/unlicensed.c | 31 +++++++++++++++++++++++++++++++ src/gb/memory.c | 2 ++ src/gb/overrides.c | 1 + src/platform/qt/GameBoy.cpp | 2 ++ 9 files changed, 50 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 3a69d0996..1cca99040 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,6 @@ 0.11.0: (Future) Features: - - New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng + - New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81 - Debugger: Add range watchpoints Other fixes: - Qt: Manually split filename to avoid overzealous splitting (fixes mgba.io/i/2681) diff --git a/README.md b/README.md index 6cf621925..20507604e 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ The following mappers are partially supported: - Sachen MMC2 (missing alternate wiring support) - BBD (missing logo switching) - Hitek (missing logo switching) +- GGB-81 (missing logo switching) - Li Cheng (missing logo switching) ### Planned features diff --git a/include/mgba/gb/interface.h b/include/mgba/gb/interface.h index 7c23bb8dd..0ec54559f 100644 --- a/include/mgba/gb/interface.h +++ b/include/mgba/gb/interface.h @@ -47,6 +47,7 @@ enum GBMemoryBankControllerType { GB_UNL_BBD = 0x220, GB_UNL_HITEK = 0x221, GB_UNL_LI_CHENG = 0x222, + GB_UNL_GGB81 = 0x223, GB_UNL_SACHEN_MMC1 = 0x230, GB_UNL_SACHEN_MMC2 = 0x231, }; diff --git a/src/gb/mbc.c b/src/gb/mbc.c index 441baaa53..27ccfe784 100644 --- a/src/gb/mbc.c +++ b/src/gb/mbc.c @@ -126,7 +126,7 @@ static struct { {"SAM2", GB_UNL_SACHEN_MMC2}, {"ROCK", GB_MBC_AUTODETECT}, // TODO {"NGHK", GB_MBC_AUTODETECT}, // TODO - {"GB81", GB_MBC_AUTODETECT}, // TODO + {"GB81", GB_UNL_GGB81}, {"TPP1", GB_MBC_AUTODETECT}, // TODO {NULL, GB_MBC_AUTODETECT}, @@ -211,6 +211,9 @@ static enum GBMemoryBankControllerType _detectUnlMBC(const uint8_t* mem, size_t return GB_UNL_BBD; } break; + case 0x79f34594: // DATA. + case 0x7e8c539b: // TD-SOFT + return GB_UNL_GGB81; case 0x20d092e2: case 0xd2b57657: if (cart->type == 0x01) { // Make sure we're not using a "fixed" version @@ -479,6 +482,11 @@ void GBMBCInit(struct GB* gb) { case GB_UNL_LI_CHENG: gb->memory.mbcWrite = _GBLiCheng; break; + case GB_UNL_GGB81: + gb->memory.mbcWrite = _GBGGB81; + gb->memory.mbcRead = _GBGGB81Read; + gb->memory.mbcReadBank1 = true; + break; case GB_UNL_SACHEN_MMC1: gb->memory.mbcWrite = _GBSachen; gb->memory.mbcRead = _GBSachenMMC1Read; diff --git a/src/gb/mbc/mbc-private.h b/src/gb/mbc/mbc-private.h index b841839f8..4a07ab716 100644 --- a/src/gb/mbc/mbc-private.h +++ b/src/gb/mbc/mbc-private.h @@ -36,6 +36,7 @@ void _GBNTNew(struct GB* gb, uint16_t address, uint8_t value); void _GBBBD(struct GB* gb, uint16_t address, uint8_t value); void _GBHitek(struct GB* gb, uint16_t address, uint8_t value); void _GBLiCheng(struct GB* gb, uint16_t address, uint8_t value); +void _GBGGB81(struct GB* gb, uint16_t address, uint8_t value); void _GBSachen(struct GB* gb, uint16_t address, uint8_t value); uint8_t _GBMBC2Read(struct GBMemory*, uint16_t address); @@ -50,6 +51,7 @@ uint8_t _GBHuC3Read(struct GBMemory*, uint16_t address); uint8_t _GBPKJDRead(struct GBMemory*, uint16_t address); uint8_t _GBBBDRead(struct GBMemory*, uint16_t address); uint8_t _GBHitekRead(struct GBMemory*, uint16_t address); +uint8_t _GBGGB81Read(struct GBMemory*, uint16_t address); uint8_t _GBSachenMMC1Read(struct GBMemory*, uint16_t address); uint8_t _GBSachenMMC2Read(struct GBMemory*, uint16_t address); diff --git a/src/gb/mbc/unlicensed.c b/src/gb/mbc/unlicensed.c index 157dd34e6..58725fa98 100644 --- a/src/gb/mbc/unlicensed.c +++ b/src/gb/mbc/unlicensed.c @@ -362,6 +362,37 @@ uint8_t _GBHitekRead(struct GBMemory* memory, uint16_t address) { } } +static const uint8_t _ggb81DataReordering[8][8] = { + { 0, 1, 2, 3, 4, 5, 6, 7 }, + { 0, 2, 1, 3, 4, 6, 5, 7 }, + { 0, 6, 5, 3, 4, 2, 1, 7 }, + { 0, 5, 1, 3, 4, 2, 6, 7 }, + { 0, 5, 2, 3, 4, 1, 6, 7 }, + { 0, 2, 6, 3, 4, 5, 1, 7 }, + { 0, 1, 6, 3, 4, 2, 5, 7 }, + { 0, 2, 5, 3, 4, 6, 1, 7 }, +}; + +void _GBGGB81(struct GB* gb, uint16_t address, uint8_t value) { + struct GBMemory* memory = &gb->memory; + switch (address & 0xF0FF) { + case 0x2001: + memory->mbcState.bbd.dataSwapMode = value & 0x07; + break; + } + _GBMBC5(gb, address, value); +} + +uint8_t _GBGGB81Read(struct GBMemory* memory, uint16_t address) { + switch (address >> 14) { + case 0: + default: + return memory->romBank[address & (GB_SIZE_CART_BANK0 - 1)]; + case 1: + return _reorderBits(memory->romBank[address & (GB_SIZE_CART_BANK0 - 1)], _ggb81DataReordering[memory->mbcState.bbd.dataSwapMode]); + } +} + void _GBLiCheng(struct GB* gb, uint16_t address, uint8_t value) { if (address > 0x2100 && address < 0x3000) { return; diff --git a/src/gb/memory.c b/src/gb/memory.c index 8ac15dbdc..50b9b7d6f 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -816,6 +816,7 @@ void GBMemorySerialize(const struct GB* gb, struct GBSerializedState* state) { break; case GB_UNL_BBD: case GB_UNL_HITEK: + case GB_UNL_GGB81: state->memory.bbd.dataSwapMode = memory->mbcState.bbd.dataSwapMode; state->memory.bbd.bankSwapMode = memory->mbcState.bbd.bankSwapMode; break; @@ -978,6 +979,7 @@ void GBMemoryDeserialize(struct GB* gb, const struct GBSerializedState* state) { break; case GB_UNL_BBD: case GB_UNL_HITEK: + case GB_UNL_GGB81: memory->mbcState.bbd.dataSwapMode = state->memory.bbd.dataSwapMode & 0x7; memory->mbcState.bbd.bankSwapMode = state->memory.bbd.bankSwapMode & 0x7; break; diff --git a/src/gb/overrides.c b/src/gb/overrides.c index ca1a8e9bf..20e489b9a 100644 --- a/src/gb/overrides.c +++ b/src/gb/overrides.c @@ -695,6 +695,7 @@ static const struct GBCartridgeOverride _overrides[] = { { 0xBC75D7B8, GB_MODEL_AUTODETECT, GB_UNL_NT_NEW, { 0 } }, // Pokemon - Mewtwo Strikes Back { 0xFF0B60CC, GB_MODEL_AUTODETECT, GB_UNL_NT_NEW, { 0 } }, // Shuma Baolong 02 4 { 0x14A992A6, GB_MODEL_AUTODETECT, GB_UNL_NT_NEW, { 0 } }, // /Street Fighter Zero 4 + { 0x3EF5AFB2, GB_MODEL_AUTODETECT, GB_UNL_LI_CHENG, { 0 } }, // Pokemon Jade Version (Telefang Speed bootleg) { 0, 0, 0, { 0 } } }; diff --git a/src/platform/qt/GameBoy.cpp b/src/platform/qt/GameBoy.cpp index 0c1ec8a9b..b07982721 100644 --- a/src/platform/qt/GameBoy.cpp +++ b/src/platform/qt/GameBoy.cpp @@ -40,6 +40,7 @@ static const QList s_mbcList{ GB_UNL_NT_NEW, GB_UNL_BBD, GB_UNL_HITEK, + GB_UNL_GGB81, GB_UNL_LI_CHENG, GB_UNL_SACHEN_MMC1, GB_UNL_SACHEN_MMC2, @@ -97,6 +98,7 @@ QString GameBoy::mbcName(GBMemoryBankControllerType mbc) { s_mbcNames[GB_UNL_PKJD] = tr("Pokémon Jade/Diamond"); s_mbcNames[GB_UNL_BBD] = tr("BBD"); s_mbcNames[GB_UNL_HITEK] = tr("Hitek"); + s_mbcNames[GB_UNL_GGB81] = tr("GGB-81"); s_mbcNames[GB_UNL_LI_CHENG] = tr("Li Cheng"); s_mbcNames[GB_UNL_SACHEN_MMC1] = tr("Sachen (MMC1)"); s_mbcNames[GB_UNL_SACHEN_MMC2] = tr("Sachen (MMC2)"); From 75155738a510c2c4f1a12a74ba6ac777b0862e04 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 18 Oct 2022 01:39:15 -0700 Subject: [PATCH 05/20] GB Serialize: Don't write BGP/OBP when loading SCGB state (fixes #2694) --- CHANGES | 2 ++ src/gb/io.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 1cca99040..1545fbd30 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,8 @@ Features: - New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81 - Debugger: Add range watchpoints +Emulation fixes: + - GB Serialize: Don't write BGP/OBP when loading SCGB state (fixes mgba.io/i/2694) Other fixes: - Qt: Manually split filename to avoid overzealous splitting (fixes mgba.io/i/2681) - Qt: Expand criteria for tag branch names (fixes mgba.io/i/2679) diff --git a/src/gb/io.c b/src/gb/io.c index c6ad66c36..06a7927cd 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -754,7 +754,7 @@ void GBIODeserialize(struct GB* gb, const struct GBSerializedState* state) { gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_SCX, state->io[GB_REG_SCX]); gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_WY, state->io[GB_REG_WY]); gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_WX, state->io[GB_REG_WX]); - if (gb->model & GB_MODEL_SGB) { + if (gb->model == GB_MODEL_SGB) { gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_BGP, state->io[GB_REG_BGP]); gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_OBP0, state->io[GB_REG_OBP0]); gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_OBP1, state->io[GB_REG_OBP1]); From 879e7561cc42f561623f0116606859a7a6d96c52 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 19 Oct 2022 04:15:41 -0700 Subject: [PATCH 06/20] Qt: Keep track of current pslette preset name (fixes #2680) --- CHANGES | 1 + src/platform/qt/SettingsView.cpp | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 1545fbd30..98658856c 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,7 @@ Other fixes: Misc: - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - macOS: Add category to plist (closes mgba.io/i/2691) + - Qt: Keep track of current pslette preset name (fixes mgba.io/i/2680) 0.10.0: (2022-10-11) Features: diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 570b15d6c..cba481dc8 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -296,9 +296,14 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC } const GBColorPreset* colorPresets; + QString usedPreset = m_controller->getQtOption("gb.pal").toString(); size_t nPresets = GBColorPresetList(&colorPresets); for (size_t i = 0; i < nPresets; ++i) { - m_ui.colorPreset->addItem(QString(colorPresets[i].name)); + QString presetName(colorPresets[i].name); + m_ui.colorPreset->addItem(presetName); + if (usedPreset == presetName) { + m_ui.colorPreset->setCurrentIndex(i); + } } connect(m_ui.colorPreset, static_cast(&QComboBox::currentIndexChanged), this, [this, colorPresets](int n) { const GBColorPreset* preset = &colorPresets[n]; @@ -640,6 +645,7 @@ void SettingsView::updateConfig() { m_controller->setOption(color.toUtf8().constData(), m_gbColors[colorId] & ~0xFF000000); } + m_controller->setQtOption("gb.pal", m_ui.colorPreset->currentText()); int gbColors = GB_COLORS_CGB; if (m_ui.gbColor->isChecked()) { From 75da9f0a9444761c891c2c4291587619ccfb3fcd Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 19 Oct 2022 04:33:03 -0700 Subject: [PATCH 07/20] Qt: Fix Discord Rich Presence if the game title is excessively, extremely, overly, ridiculously, very very long (fixes #2697) --- src/platform/qt/DiscordCoordinator.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/platform/qt/DiscordCoordinator.cpp b/src/platform/qt/DiscordCoordinator.cpp index b090863cd..72ca4cc1b 100644 --- a/src/platform/qt/DiscordCoordinator.cpp +++ b/src/platform/qt/DiscordCoordinator.cpp @@ -22,7 +22,7 @@ namespace DiscordCoordinator { static bool s_gameRunning = false; static bool s_inited = false; -static QString s_title; +static QByteArray s_title; static void updatePresence() { if (!s_inited) { @@ -30,7 +30,7 @@ static void updatePresence() { } if (s_gameRunning) { DiscordRichPresence discordPresence{}; - discordPresence.details = s_title.toUtf8().constData(); + discordPresence.details = s_title.constData(); discordPresence.instance = 1; discordPresence.largeImageKey = "mgba"; #if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) @@ -74,8 +74,10 @@ void gameStarted(std::shared_ptr controller) { s_title = controller->thread()->core->dirs.baseName; QString dbTitle = controller->dbTitle(); if (!dbTitle.isNull()) { - s_title = dbTitle; + s_title = dbTitle.toUtf8(); } + // Non-const QByteArrays are null-terminated so we don't need to append null even after truncation + s_title.truncate(128); updatePresence(); } From 981d01134b15c1d8214d9a7e5944879852588063 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 20 Oct 2022 20:11:19 -0700 Subject: [PATCH 08/20] macOS: Fix modern build with libepoxy (fixes #2700) --- CHANGES | 1 + CMakeLists.txt | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 98658856c..25e4f5efe 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,7 @@ Other fixes: Misc: - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - macOS: Add category to plist (closes mgba.io/i/2691) + - macOS: Fix modern build with libepoxy (fixes mgba.io/i/2700) - Qt: Keep track of current pslette preset name (fixes mgba.io/i/2680) 0.10.0: (2022-10-11) diff --git a/CMakeLists.txt b/CMakeLists.txt index ce8e4d687..a00e93371 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -718,8 +718,12 @@ if (USE_LZMA) endif() if(USE_EPOXY) - list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gl.c ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gles2.c) - list(APPEND FEATURE_DEFINES BUILD_GL BUILD_GLES2 BUILD_GLES3) + if(NOT APPLE OR NOT MACOSX_SDK VERSION_GREATER 10.14) + list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gl.c) + list(APPEND FEATURE_DEFINES BUILD_GL) + endif() + list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gles2.c) + list(APPEND FEATURE_DEFINES BUILD_GLES2 BUILD_GLES3) list(APPEND FEATURES EPOXY) include_directories(AFTER ${EPOXY_INCLUDE_DIRS}) link_directories(${EPOXY_LIBRARY_DIRS}) From 73afc7d7f703bb6a4d001969627fb212d88f7d99 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 22 Oct 2022 19:33:34 -0700 Subject: [PATCH 09/20] 3DS: Allow loading ROM out of romfs if included --- src/platform/3ds/CMakeLists.txt | 2 +- src/platform/3ds/main.c | 63 +++++++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/platform/3ds/CMakeLists.txt b/src/platform/3ds/CMakeLists.txt index 7fd3e270f..893af0317 100644 --- a/src/platform/3ds/CMakeLists.txt +++ b/src/platform/3ds/CMakeLists.txt @@ -1,4 +1,4 @@ -set(USE_VFS_3DS ON CACHE BOOL "Use 3DS-specific file support") +set(USE_VFS_3DS OFF CACHE BOOL "Use 3DS-specific file support") mark_as_advanced(USE_VFS_3DS) find_program(3DSLINK 3dslink) diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 71fcc1aa2..fad2fd96b 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -817,7 +817,43 @@ THREAD_ENTRY _core2Test(void* context) { UNUSED(context); } -int main() { +bool setupRomfs(char* initialPath, size_t outLength, struct mGUIRunner* runner) { + int fd = open("romfs:/filename", O_RDONLY); + strcpy(initialPath, "romfs:/"); + if (fd < 0) { + return false; + } + size_t len = strlen(initialPath); + ssize_t size = read(fd, initialPath + len, outLength - len); + if (size > 0 && initialPath[len + size - 1] == '\n') { + initialPath[len + size - 1] = '\0'; + } + close(fd); + if (size <= 0) { + return false; + } + char basedir[64]; + mCoreConfigDirectory(basedir, sizeof(basedir)); + strlcat(basedir, "/forwarders", sizeof(basedir)); + FSUSER_CreateDirectory(sdmcArchive, fsMakePath(PATH_ASCII, basedir), 0); + + mCoreConfigSetValue(&runner->config, "savegamePath", basedir); + mCoreConfigSetValue(&runner->config, "savestatePath", basedir); + mCoreConfigSetValue(&runner->config, "screenshotPath", basedir); + mCoreConfigSetValue(&runner->config, "cheatsPath", basedir); + return true; +} + +int main(int argc, char* argv[]) { + char initialPath[PATH_MAX] = { 0 }; + if (argc > 1) { + strncpy(initialPath, argv[1], sizeof(PATH_MAX)); + } else { + u8 hmac[0x20]; + memset(hmac, 0, sizeof(hmac)); + APT_ReceiveDeliverArg(initialPath, sizeof(initialPath), hmac, NULL, NULL); + } + rotation.d.sample = _sampleRotation; rotation.d.readTiltX = _readTiltX; rotation.d.readTiltY = _readTiltY; @@ -1046,9 +1082,32 @@ int main() { _map3DSKey(&runner.params.keyMap, KEY_CSTICK_UP, mGUI_INPUT_INCREASE_BRIGHTNESS); _map3DSKey(&runner.params.keyMap, KEY_CSTICK_DOWN, mGUI_INPUT_DECREASE_BRIGHTNESS); - mGUIRunloop(&runner); + Result res = romfsInit(); + bool useRomfs = false; + if (R_SUCCEEDED(res)) { + useRomfs = setupRomfs(initialPath, sizeof(initialPath), &runner); + if (!useRomfs) { + romfsExit(); + _cleanup(); + return 1; + } + } + + if (initialPath[0] == '/' || useRomfs) { + size_t i; + for (i = 0; runner.keySources[i].id; ++i) { + mInputMapLoad(&runner.params.keyMap, runner.keySources[i].id, mCoreConfigGetInput(&runner.config)); + } + mGUIRun(&runner, initialPath); + } else { + mGUIRunloop(&runner); + } + mGUIDeinit(&runner); + if (useRomfs) { + romfsExit(); + } _cleanup(); return 0; } From 85e66155f2845fa2a0e720b3ddb6c0415d3bf95a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 22 Oct 2022 23:34:28 -0700 Subject: [PATCH 10/20] Vita: Allow loading ROM out of app0 if included --- src/feature/gui/gui-runner.c | 44 ++++++++++++++++++++++++++++++++++++ src/feature/gui/gui-runner.h | 4 ++++ src/platform/3ds/main.c | 29 +----------------------- src/platform/psp2/main.c | 13 ++++++++++- 4 files changed, 61 insertions(+), 29 deletions(-) diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index 64488dcb8..820f1cb07 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -19,6 +19,12 @@ #include #include +#ifdef PSP2 +#include +#elif defined(__3DS__) +#include +#endif + #include mLOG_DECLARE_CATEGORY(GUI_RUNNER); @@ -770,6 +776,44 @@ void mGUIRunloop(struct mGUIRunner* runner) { } } +#if defined(__3DS__) || defined(PSP2) +bool mGUIGetRom(struct mGUIRunner* runner, char* out, size_t outLength) { +#ifdef PSP2 + int fd = open("app0:/filename", O_RDONLY); + strcpy(out, "app0:/"); +#elif defined(__3DS__) + int fd = open("romfs:/filename", O_RDONLY); + strcpy(out, "romfs:/"); +#endif + if (fd < 0) { + return false; + } + size_t len = strlen(out); + ssize_t size = read(fd, out + len, outLength - len); + if (size > 0 && out[len + size - 1] == '\n') { + out[len + size - 1] = '\0'; + } + close(fd); + if (size <= 0) { + return false; + } + char basedir[64]; + mCoreConfigDirectory(basedir, sizeof(basedir)); + strlcat(basedir, "/forwarders", sizeof(basedir)); +#ifdef PSP2 + sceIoMkdir(basedir, 0777); +#elif defined(__3DS__) + FSUSER_CreateDirectory(sdmcArchive, fsMakePath(PATH_ASCII, basedir), 0); +#endif + + mCoreConfigSetValue(&runner->config, "savegamePath", basedir); + mCoreConfigSetValue(&runner->config, "savestatePath", basedir); + mCoreConfigSetValue(&runner->config, "screenshotPath", basedir); + mCoreConfigSetValue(&runner->config, "cheatsPath", basedir); + return true; +} +#endif + #ifndef DISABLE_THREADING THREAD_ENTRY mGUIAutosaveThread(void* context) { struct mGUIAutosaveContext* autosave = context; diff --git a/src/feature/gui/gui-runner.h b/src/feature/gui/gui-runner.h index ea18cc5b8..860b96b99 100644 --- a/src/feature/gui/gui-runner.h +++ b/src/feature/gui/gui-runner.h @@ -98,6 +98,10 @@ void mGUIDeinit(struct mGUIRunner*); void mGUIRun(struct mGUIRunner*, const char* path); void mGUIRunloop(struct mGUIRunner*); +#if defined(__3DS__) || defined(PSP2) +bool mGUIGetRom(struct mGUIRunner* runner, char* out, size_t outLength); +#endif + #ifndef DISABLE_THREADING THREAD_ENTRY mGUIAutosaveThread(void* context); #endif diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index fad2fd96b..b19a0ca2f 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -817,33 +817,6 @@ THREAD_ENTRY _core2Test(void* context) { UNUSED(context); } -bool setupRomfs(char* initialPath, size_t outLength, struct mGUIRunner* runner) { - int fd = open("romfs:/filename", O_RDONLY); - strcpy(initialPath, "romfs:/"); - if (fd < 0) { - return false; - } - size_t len = strlen(initialPath); - ssize_t size = read(fd, initialPath + len, outLength - len); - if (size > 0 && initialPath[len + size - 1] == '\n') { - initialPath[len + size - 1] = '\0'; - } - close(fd); - if (size <= 0) { - return false; - } - char basedir[64]; - mCoreConfigDirectory(basedir, sizeof(basedir)); - strlcat(basedir, "/forwarders", sizeof(basedir)); - FSUSER_CreateDirectory(sdmcArchive, fsMakePath(PATH_ASCII, basedir), 0); - - mCoreConfigSetValue(&runner->config, "savegamePath", basedir); - mCoreConfigSetValue(&runner->config, "savestatePath", basedir); - mCoreConfigSetValue(&runner->config, "screenshotPath", basedir); - mCoreConfigSetValue(&runner->config, "cheatsPath", basedir); - return true; -} - int main(int argc, char* argv[]) { char initialPath[PATH_MAX] = { 0 }; if (argc > 1) { @@ -1085,7 +1058,7 @@ int main(int argc, char* argv[]) { Result res = romfsInit(); bool useRomfs = false; if (R_SUCCEEDED(res)) { - useRomfs = setupRomfs(initialPath, sizeof(initialPath), &runner); + useRomfs = mGUIGetRom(&runner, initialPath, sizeof(initialPath)); if (!useRomfs) { romfsExit(); _cleanup(); diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index c474d9d85..d66ba8494 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -153,6 +153,8 @@ static enum GUIKeyboardStatus _keyboardRun(struct GUIKeyboardParams* keyboard) { } int main() { + char initialPath[PATH_MAX] = { 0 }; + vita2d_init(); struct GUIFont* font = GUIFontCreate(); struct mGUIRunner runner = { @@ -278,7 +280,16 @@ int main() { mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_SQUARE, mGUI_INPUT_SCREEN_MODE); scePowerSetArmClockFrequency(444); - mGUIRunloop(&runner); + + if (mGUIGetRom(&runner, initialPath, sizeof(initialPath))) { + size_t i; + for (i = 0; runner.keySources[i].id; ++i) { + mInputMapLoad(&runner.params.keyMap, runner.keySources[i].id, mCoreConfigGetInput(&runner.config)); + } + mGUIRun(&runner, initialPath); + } else { + mGUIRunloop(&runner); + } vita2d_fini(); mGUIDeinit(&runner); From fcf764e3c6f62c2c3372da6854bab2493ed817c0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 22 Oct 2022 23:41:24 -0700 Subject: [PATCH 11/20] mGUI: Refactoring out common code --- src/feature/gui/gui-runner.c | 20 ++++++++++---------- src/feature/gui/gui-runner.h | 1 + src/platform/3ds/main.c | 5 +---- src/platform/psp2/main.c | 5 +---- src/platform/switch/main.c | 5 +---- src/platform/wii/main.c | 5 +---- 6 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index 820f1cb07..2e837891f 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -456,11 +456,7 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { runner->setup(runner); } if (runner->config.port && runner->keySources) { - mLOG(GUI_RUNNER, DEBUG, "Loading key sources for %s...", runner->config.port); - size_t i; - for (i = 0; runner->keySources[i].id; ++i) { - mInputMapLoad(&runner->core->inputMap, runner->keySources[i].id, mCoreConfigGetInput(&runner->config)); - } + mGUILoadInputMaps(runner); } mLOG(GUI_RUNNER, DEBUG, "Reseting..."); runner->core->reset(runner->core); @@ -751,11 +747,7 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { void mGUIRunloop(struct mGUIRunner* runner) { if (runner->keySources) { - mLOG(GUI_RUNNER, DEBUG, "Loading key sources for %s...", runner->config.port); - size_t i; - for (i = 0; runner->keySources[i].id; ++i) { - mInputMapLoad(&runner->params.keyMap, runner->keySources[i].id, mCoreConfigGetInput(&runner->config)); - } + mGUILoadInputMaps(runner); } while (!runner->running || runner->running(runner)) { char path[PATH_MAX]; @@ -776,6 +768,14 @@ void mGUIRunloop(struct mGUIRunner* runner) { } } +void mGUILoadInputMaps(struct mGUIRunner* runner) { + mLOG(GUI_RUNNER, DEBUG, "Loading key sources for %s...", runner->config.port); + size_t i; + for (i = 0; runner->keySources[i].id; ++i) { + mInputMapLoad(&runner->params.keyMap, runner->keySources[i].id, mCoreConfigGetInput(&runner->config)); + } +} + #if defined(__3DS__) || defined(PSP2) bool mGUIGetRom(struct mGUIRunner* runner, char* out, size_t outLength) { #ifdef PSP2 diff --git a/src/feature/gui/gui-runner.h b/src/feature/gui/gui-runner.h index 860b96b99..fc8e85227 100644 --- a/src/feature/gui/gui-runner.h +++ b/src/feature/gui/gui-runner.h @@ -95,6 +95,7 @@ struct mGUIRunner { void mGUIInit(struct mGUIRunner*, const char* port); void mGUIDeinit(struct mGUIRunner*); +void mGUILoadInputMaps(struct mGUIRunner* runner); void mGUIRun(struct mGUIRunner*, const char* path); void mGUIRunloop(struct mGUIRunner*); diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index b19a0ca2f..5356130ce 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -1067,10 +1067,7 @@ int main(int argc, char* argv[]) { } if (initialPath[0] == '/' || useRomfs) { - size_t i; - for (i = 0; runner.keySources[i].id; ++i) { - mInputMapLoad(&runner.params.keyMap, runner.keySources[i].id, mCoreConfigGetInput(&runner.config)); - } + mGUILoadInputMaps(&runner); mGUIRun(&runner, initialPath); } else { mGUIRunloop(&runner); diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index d66ba8494..747535e25 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -282,10 +282,7 @@ int main() { scePowerSetArmClockFrequency(444); if (mGUIGetRom(&runner, initialPath, sizeof(initialPath))) { - size_t i; - for (i = 0; runner.keySources[i].id; ++i) { - mInputMapLoad(&runner.params.keyMap, runner.keySources[i].id, mCoreConfigGetInput(&runner.config)); - } + mGUILoadInputMaps(&runner); mGUIRun(&runner, initialPath); } else { mGUIRunloop(&runner); diff --git a/src/platform/switch/main.c b/src/platform/switch/main.c index 25b6f6eca..9f1e84563 100644 --- a/src/platform/switch/main.c +++ b/src/platform/switch/main.c @@ -1061,10 +1061,7 @@ int main(int argc, char* argv[]) { } if (argc > 1) { - size_t i; - for (i = 0; runner.keySources[i].id; ++i) { - mInputMapLoad(&runner.params.keyMap, runner.keySources[i].id, mCoreConfigGetInput(&runner.config)); - } + mGUILoadInputMaps(&runner); mGUIRun(&runner, argv[1]); } else { mGUIRunloop(&runner); diff --git a/src/platform/wii/main.c b/src/platform/wii/main.c index 36bb9abfc..312388347 100644 --- a/src/platform/wii/main.c +++ b/src/platform/wii/main.c @@ -653,10 +653,7 @@ int main(int argc, char* argv[]) { } if (argc > 1) { - size_t i; - for (i = 0; runner.keySources[i].id; ++i) { - mInputMapLoad(&runner.params.keyMap, runner.keySources[i].id, mCoreConfigGetInput(&runner.config)); - } + mGUILoadInputMaps(&runner); mGUIRun(&runner, argv[1]); } else { mGUIRunloop(&runner); From 15e8b20537e754a61282a28350b5837375fb4a17 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 27 Oct 2022 02:24:39 -0700 Subject: [PATCH 12/20] Updater: Fix mUpdaterGetUpdateForChannel --- src/feature/updater.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/feature/updater.c b/src/feature/updater.c index 442a71648..e9f47f440 100644 --- a/src/feature/updater.c +++ b/src/feature/updater.c @@ -78,14 +78,7 @@ static void _updateMatch(const char* key, const char* value, void* user) { return; } const char* item = &key[dotLoc + 1]; - - struct Table* out = user; - struct mUpdate* update = HashTableLookup(out, match->channel); - if (!update) { - update = calloc(1, sizeof(*update)); - HashTableInsert(out, match->channel, update); - } - _updateUpdate(update, item, value); + _updateUpdate(match->out, item, value); } bool mUpdaterInit(struct mUpdaterContext* context, const char* manifest) { From c49f09dabc99e18a801fe454bf57d846ffc56bb1 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 28 Oct 2022 00:01:21 -0700 Subject: [PATCH 13/20] Util: Add PS Vita SFO generator --- include/mgba-util/sfo.h | 30 ++++++ src/util/CMakeLists.txt | 2 + src/util/sfo.c | 228 ++++++++++++++++++++++++++++++++++++++++ src/util/test/sfo.c | 107 +++++++++++++++++++ 4 files changed, 367 insertions(+) create mode 100644 include/mgba-util/sfo.h create mode 100644 src/util/sfo.c create mode 100644 src/util/test/sfo.c diff --git a/include/mgba-util/sfo.h b/include/mgba-util/sfo.h new file mode 100644 index 000000000..89404ceca --- /dev/null +++ b/include/mgba-util/sfo.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2013-2022 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef SFO_H +#define SFO_H + +#include + +CXX_GUARD_START + +#include + +void SfoInit(struct Table* sfo); + +static inline void SfoDeinit(struct Table* sfo) { + HashTableDeinit(sfo); +} + +struct VFile; +bool SfoWrite(struct Table* sfo, struct VFile* vf); + +bool SfoAddU32Value(struct Table* sfo, const char* name, uint32_t value); +bool SfoAddStrValue(struct Table* sfo, const char* name, const char* value); +bool SfoSetTitle(struct Table* sfo, const char* title); + +CXX_GUARD_END + +#endif diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index e4d03d343..df0d84c5c 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -21,6 +21,7 @@ set(SOURCE_FILES patch-ups.c png-io.c ring-fifo.c + sfo.c text-codec.c) set(GUI_FILES @@ -31,6 +32,7 @@ set(GUI_FILES gui/menu.c) set(TEST_FILES + test/sfo.c test/string-parser.c test/string-utf8.c test/table.c diff --git a/src/util/sfo.c b/src/util/sfo.c new file mode 100644 index 000000000..90142897b --- /dev/null +++ b/src/util/sfo.c @@ -0,0 +1,228 @@ +/* Copyright (c) 2013-2022 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* This code is loosely based on vita-mksfoex.c from the vitasdk + * Copyright (c) 2015 Sergi Granell + * Copyright (c) 2015 Danielle Church + * Used under the MIT license + * + * Which itself is based on mksfoex.c from the pspsdk + * Copyright (c) 2005 adresd + * Copyright (c) 2005 Marcus R. Brown + * Copyright (c) 2005 James Forshaw + * Copyright (c) 2005 John Kelley + * Copyright (c) 2005 Jesper Svennevid + * Used under the BSD 3-clause license +*/ + +#include +#include +#include + +#define PSF_MAGIC 0x46535000 +#define PSF_VERSION 0x00000101 + +struct SfoHeader { + uint32_t magic; + uint32_t version; + uint32_t keyofs; + uint32_t valofs; + uint32_t count; +}; + +struct SfoEntry { + uint16_t nameofs; + uint8_t alignment; + uint8_t type; + uint32_t valsize; + uint32_t totalsize; + uint32_t dataofs; +}; + +enum PSFType { + PSF_TYPE_BIN = 0, + PSF_TYPE_STR = 2, + PSF_TYPE_U32 = 4, +}; + +struct SfoEntryContainer { + const char* name; + enum PSFType type; + union { + const char* str; + uint32_t u32; + } data; + uint32_t size; +}; + +static struct SfoEntryContainer sfoDefaults[] = { + { "APP_VER", PSF_TYPE_STR, { .str = "00.00" } }, + { "ATTRIBUTE", PSF_TYPE_U32, { .u32 = 0x8000 } }, + { "ATTRIBUTE2", PSF_TYPE_U32, { .u32 = 0 } }, + { "ATTRIBUTE_MINOR", PSF_TYPE_U32, { .u32 = 0x10 } }, + { "BOOT_FILE", PSF_TYPE_STR, { .str = ""}, 0x20 }, + { "CATEGORY", PSF_TYPE_STR, { .str = "gd" } }, + { "CONTENT_ID", PSF_TYPE_STR, { .str = "" }, 0x30 }, + { "EBOOT_APP_MEMSIZE", PSF_TYPE_U32, { .u32 = 0 } }, + { "EBOOT_ATTRIBUTE", PSF_TYPE_U32, { .u32 = 0 } }, + { "EBOOT_PHY_MEMSIZE", PSF_TYPE_U32, { .u32 = 0 } }, + { "LAREA_TYPE", PSF_TYPE_U32, { .u32 = 0 } }, + { "NP_COMMUNICATION_ID", PSF_TYPE_STR, { .str = "" }, 0x10 }, + { "PARENTAL_LEVEL", PSF_TYPE_U32, { .u32 = 0 } }, + { "PSP2_DISP_VER", PSF_TYPE_STR, { .str = "00.000" } }, + { "PSP2_SYSTEM_VER", PSF_TYPE_U32, { .u32 = 0 } }, + { "STITLE", PSF_TYPE_STR, { .str = "Homebrew" }, 52 }, + { "TITLE", PSF_TYPE_STR, { .str = "Homebrew" }, 0x80 }, + { "TITLE_ID", PSF_TYPE_STR, { .str = "ABCD99999" } }, + { "VERSION", PSF_TYPE_STR, { .str = "00.00" } }, +}; + +bool SfoAddStrValue(struct Table* sfo, const char* name, const char* value) { + struct SfoEntryContainer* entry = HashTableLookup(sfo, name); + if (!entry) { + entry = calloc(1, sizeof(*entry)); + if (!entry) { + return false; + } + entry->name = name; + HashTableInsert(sfo, name, entry); + } + entry->type = PSF_TYPE_STR; + entry->data.str = value; + return true; +} + +bool SfoAddU32Value(struct Table* sfo, const char* name, uint32_t value) { + struct SfoEntryContainer* entry = HashTableLookup(sfo, name); + if (!entry) { + entry = calloc(1, sizeof(*entry)); + if (!entry) { + return false; + } + entry->name = name; + HashTableInsert(sfo, name, entry); + } + entry->type = PSF_TYPE_U32; + entry->data.u32 = value; + return true; +} + +bool SfoSetTitle(struct Table* sfo, const char* title) { + return SfoAddStrValue(sfo, "TITLE", title) && SfoAddStrValue(sfo, "STITLE", title); +} + +void SfoInit(struct Table* sfo) { + HashTableInit(sfo, 32, free); + + size_t i; + for (i = 0; i < sizeof(sfoDefaults) / sizeof(sfoDefaults[0]); ++i) { + struct SfoEntryContainer* entry = calloc(1, sizeof(*entry)); + memcpy(entry, &sfoDefaults[i], sizeof(*entry)); + HashTableInsert(sfo, entry->name, entry); + } +} + +#define ALIGN4(X) (((X) + 3) & ~3) + +static int _sfoSort(const void* a, const void* b) { + const struct SfoEntryContainer* ea = a; + const struct SfoEntryContainer* eb = b; + return strcmp(ea->name, eb->name); +} + +bool SfoWrite(struct Table* sfo, struct VFile* vf) { + struct SfoHeader header; + size_t count = HashTableSize(sfo); + STORE_32LE(PSF_MAGIC, 0, &header.magic); + STORE_32LE(PSF_VERSION, 0, &header.version); + STORE_32LE(count, 0, &header.count); + + struct TableIterator iter; + if (!TableIteratorStart(sfo, &iter)) { + return false; + } + + struct SfoEntryContainer* sortedEntries = calloc(count, sizeof(struct SfoEntryContainer)); + + uint32_t keysSize = 0; + uint32_t dataSize = 0; + size_t i = 0; + do { + memcpy(&sortedEntries[i], TableIteratorGetValue(sfo, &iter), sizeof(struct SfoEntryContainer)); + keysSize += strlen(sortedEntries[i].name) + 1; + if (!sortedEntries[i].size) { + switch (sortedEntries[i].type) { + case PSF_TYPE_STR: + sortedEntries[i].size = strlen(sortedEntries[i].data.str) + 1; + break; + case PSF_TYPE_U32: + sortedEntries[i].size = 4; + break; + } + } + dataSize += ALIGN4(sortedEntries[i].size); + ++i; + } while (TableIteratorNext(sfo, &iter)); + + keysSize = ALIGN4(keysSize); + + qsort(sortedEntries, count, sizeof(struct SfoEntryContainer), _sfoSort); + + uint32_t keysOffset = 0; + uint32_t dataOffset = 0; + + char* keys = calloc(1, keysSize); + char* data = calloc(1, dataSize); + + struct SfoEntry* entries = calloc(count, sizeof(struct SfoEntry)); + for (i = 0; i < count; ++i) { + STORE_16LE(keysOffset, 0, &entries[i].nameofs); + STORE_32LE(dataOffset, 0, &entries[i].dataofs); + entries[i].alignment = 4; + entries[i].type = sortedEntries[i].type; + + strcpy(&keys[keysOffset], sortedEntries[i].name); + keysOffset += strlen(sortedEntries[i].name) + 1; + + if (sortedEntries[i].type == PSF_TYPE_U32) { + STORE_32LE(4, 0, &entries[i].valsize); + STORE_32LE(4, 0, &entries[i].totalsize); + STORE_32LE(sortedEntries[i].data.u32, dataOffset, data); + dataOffset += 4; + } else { + STORE_32LE(ALIGN4(sortedEntries[i].size), 0, &entries[i].totalsize); + + memset(&data[dataOffset], 0, ALIGN4(sortedEntries[i].size)); + if (sortedEntries[i].data.str) { + STORE_32LE(strlen(sortedEntries[i].data.str) + 1, 0, &entries[i].valsize); + strncpy(&data[dataOffset], sortedEntries[i].data.str, sortedEntries[i].size); + } else { + STORE_32LE(sortedEntries[i].size, 0, &entries[i].valsize); + } + dataOffset += ALIGN4(sortedEntries[i].size); + } + } + + if (keysSize != ALIGN4(keysOffset) || dataSize != dataOffset) { + abort(); + } + + free(sortedEntries); + + STORE_32LE(count * sizeof(struct SfoEntry) + sizeof(header), 0, &header.keyofs); + STORE_32LE(count * sizeof(struct SfoEntry) + sizeof(header) + keysSize, 0, &header.valofs); + + vf->write(vf, &header, sizeof(header)); + vf->write(vf, entries, sizeof(entries[0]) * count); + vf->write(vf, keys, keysSize); + vf->write(vf, data, dataSize); + + free(entries); + free(keys); + free(data); + + return true; +} diff --git a/src/util/test/sfo.c b/src/util/test/sfo.c new file mode 100644 index 000000000..54f923ae9 --- /dev/null +++ b/src/util/test/sfo.c @@ -0,0 +1,107 @@ +/* Copyright (c) 2013-2022 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/test/suite.h" + +#include +#include + +const char defaultMksfoex[] = { + 0x00, 0x50, 0x53, 0x46, 0x01, 0x01, 0x00, 0x00, 0x44, 0x01, 0x00, 0x00, + 0x30, 0x02, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x02, + 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x12, 0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x04, 0x04, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x2d, 0x00, 0x04, 0x02, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x37, 0x00, 0x04, 0x02, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x40, 0x00, 0x04, 0x02, + 0x01, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, + 0x4b, 0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x68, 0x00, 0x00, 0x00, 0x5d, 0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x6d, 0x00, 0x04, 0x04, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, + 0x7f, 0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x74, 0x00, 0x00, 0x00, 0x8a, 0x00, 0x04, 0x02, 0x01, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x04, 0x04, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, + 0xad, 0x00, 0x04, 0x02, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x8c, 0x00, 0x00, 0x00, 0xbb, 0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00, 0xcb, 0x00, 0x04, 0x02, + 0x09, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00, + 0xd2, 0x00, 0x04, 0x02, 0x09, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0xcc, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x04, 0x02, 0x0a, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x4c, 0x01, 0x00, 0x00, 0xe1, 0x00, 0x04, 0x02, + 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x58, 0x01, 0x00, 0x00, + 0x41, 0x50, 0x50, 0x5f, 0x56, 0x45, 0x52, 0x00, 0x41, 0x54, 0x54, 0x52, + 0x49, 0x42, 0x55, 0x54, 0x45, 0x00, 0x41, 0x54, 0x54, 0x52, 0x49, 0x42, + 0x55, 0x54, 0x45, 0x32, 0x00, 0x41, 0x54, 0x54, 0x52, 0x49, 0x42, 0x55, + 0x54, 0x45, 0x5f, 0x4d, 0x49, 0x4e, 0x4f, 0x52, 0x00, 0x42, 0x4f, 0x4f, + 0x54, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x00, 0x43, 0x41, 0x54, 0x45, 0x47, + 0x4f, 0x52, 0x59, 0x00, 0x43, 0x4f, 0x4e, 0x54, 0x45, 0x4e, 0x54, 0x5f, + 0x49, 0x44, 0x00, 0x45, 0x42, 0x4f, 0x4f, 0x54, 0x5f, 0x41, 0x50, 0x50, + 0x5f, 0x4d, 0x45, 0x4d, 0x53, 0x49, 0x5a, 0x45, 0x00, 0x45, 0x42, 0x4f, + 0x4f, 0x54, 0x5f, 0x41, 0x54, 0x54, 0x52, 0x49, 0x42, 0x55, 0x54, 0x45, + 0x00, 0x45, 0x42, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x48, 0x59, 0x5f, 0x4d, + 0x45, 0x4d, 0x53, 0x49, 0x5a, 0x45, 0x00, 0x4c, 0x41, 0x52, 0x45, 0x41, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x00, 0x4e, 0x50, 0x5f, 0x43, 0x4f, 0x4d, + 0x4d, 0x55, 0x4e, 0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x49, + 0x44, 0x00, 0x50, 0x41, 0x52, 0x45, 0x4e, 0x54, 0x41, 0x4c, 0x5f, 0x4c, + 0x45, 0x56, 0x45, 0x4c, 0x00, 0x50, 0x53, 0x50, 0x32, 0x5f, 0x44, 0x49, + 0x53, 0x50, 0x5f, 0x56, 0x45, 0x52, 0x00, 0x50, 0x53, 0x50, 0x32, 0x5f, + 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x5f, 0x56, 0x45, 0x52, 0x00, 0x53, + 0x54, 0x49, 0x54, 0x4c, 0x45, 0x00, 0x54, 0x49, 0x54, 0x4c, 0x45, 0x00, + 0x54, 0x49, 0x54, 0x4c, 0x45, 0x5f, 0x49, 0x44, 0x00, 0x56, 0x45, 0x52, + 0x53, 0x49, 0x4f, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x2e, 0x30, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x67, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x2e, 0x30, 0x30, 0x30, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x48, 0x6f, 0x6d, 0x65, 0x62, 0x72, 0x65, 0x77, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x6f, 0x6d, 0x65, + 0x62, 0x72, 0x65, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x41, 0x42, 0x43, 0x44, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x00, 0x00, 0x00, 0x30, 0x30, 0x2e, 0x30, 0x30, 0x00, 0x00, 0x00 +}; + +M_TEST_DEFINE(defaultSfo) { + struct VFile* vf = VFileMemChunk(NULL, 0); + struct Table sfo; + SfoInit(&sfo); + SfoWrite(&sfo, vf); + SfoDeinit(&sfo); + + assert_int_equal(vf->size(vf), sizeof(defaultMksfoex)); + void* buffer = vf->map(vf, sizeof(defaultMksfoex), MAP_READ); + assert_memory_equal(defaultMksfoex, buffer, sizeof(defaultMksfoex)); + + vf->unmap(vf, buffer, sizeof(defaultMksfoex)); + vf->close(vf); +} + +M_TEST_SUITE_DEFINE(Sfo, + cmocka_unit_test(defaultSfo), +) From 3b558a950961094bdb6f24aa137dbcbed786ea38 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 29 Oct 2022 01:37:52 -0700 Subject: [PATCH 14/20] Qt: Move ROM filter function to utils --- src/platform/qt/Window.cpp | 51 ++----------------------------------- src/platform/qt/Window.h | 1 - src/platform/qt/utils.cpp | 52 ++++++++++++++++++++++++++++++++++++++ src/platform/qt/utils.h | 2 ++ 4 files changed, 56 insertions(+), 50 deletions(-) diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 20249f4a0..41f29dbe5 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -304,53 +304,6 @@ void Window::saveConfig() { m_config->write(); } -QString Window::getFilters() const { - QStringList filters; - QStringList formats; - -#ifdef M_CORE_GBA - QStringList gbaFormats{ - "*.gba", -#if defined(USE_LIBZIP) || defined(USE_MINIZIP) - "*.zip", -#endif -#ifdef USE_LZMA - "*.7z", -#endif -#ifdef USE_ELF - "*.elf", -#endif - "*.agb", - "*.mb", - "*.rom", - "*.bin"}; - formats.append(gbaFormats); - filters.append(tr("Game Boy Advance ROMs (%1)").arg(gbaFormats.join(QChar(' ')))); -#endif - -#ifdef M_CORE_GB - QStringList gbFormats{ - "*.gb", - "*.gbc", - "*.sgb", -#if defined(USE_LIBZIP) || defined(USE_MINIZIP) - "*.zip", -#endif -#ifdef USE_LZMA - "*.7z", -#endif - "*.rom", - "*.bin"}; - formats.append(gbFormats); - filters.append(tr("Game Boy ROMs (%1)").arg(gbFormats.join(QChar(' ')))); -#endif - - formats.removeDuplicates(); - filters.prepend(tr("All ROMs (%1)").arg(formats.join(QChar(' ')))); - filters.append(tr("%1 Video Logs (*.mvl)").arg(projectName)); - return filters.join(";;"); -} - QString Window::getFiltersArchive() const { QStringList filters; @@ -367,7 +320,7 @@ QString Window::getFiltersArchive() const { } void Window::selectROM() { - QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), getFilters()); + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), romFilters(true)); if (!filename.isEmpty()) { setController(m_manager->loadGame(filename), filename); } @@ -410,7 +363,7 @@ void Window::addDirToLibrary() { #endif void Window::replaceROM() { - QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), getFilters()); + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), romFilters()); if (!filename.isEmpty()) { m_controller->replaceGame(filename); } diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 52e0da965..9495cb3ee 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -187,7 +187,6 @@ private: void updateTitle(float fps = -1); - QString getFilters() const; QString getFiltersArchive() const; CoreManager* m_manager; diff --git a/src/platform/qt/utils.cpp b/src/platform/qt/utils.cpp index 8b273cb6d..b77652c6c 100644 --- a/src/platform/qt/utils.cpp +++ b/src/platform/qt/utils.cpp @@ -5,6 +5,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "utils.h" +#include + +#include #include namespace QGBA { @@ -59,4 +62,53 @@ bool convertAddress(const QHostAddress* input, Address* output) { return true; } +QString romFilters(bool includeMvl) { + QStringList filters; + QStringList formats; + +#ifdef M_CORE_GBA + QStringList gbaFormats{ + "*.gba", +#if defined(USE_LIBZIP) || defined(USE_MINIZIP) + "*.zip", +#endif +#ifdef USE_LZMA + "*.7z", +#endif +#ifdef USE_ELF + "*.elf", +#endif + "*.agb", + "*.mb", + "*.rom", + "*.bin"}; + formats.append(gbaFormats); + filters.append(QCoreApplication::translate("QGBA", "Game Boy Advance ROMs (%1)", nullptr).arg(gbaFormats.join(QChar(' ')))); +#endif + +#ifdef M_CORE_GB + QStringList gbFormats{ + "*.gb", + "*.gbc", + "*.sgb", +#if defined(USE_LIBZIP) || defined(USE_MINIZIP) + "*.zip", +#endif +#ifdef USE_LZMA + "*.7z", +#endif + "*.rom", + "*.bin"}; + formats.append(gbFormats); + filters.append(QCoreApplication::translate("QGBA", "Game Boy ROMs (%1)", nullptr).arg(gbFormats.join(QChar(' ')))); +#endif + + formats.removeDuplicates(); + filters.prepend(QCoreApplication::translate("QGBA", "All ROMs (%1)", nullptr).arg(formats.join(QChar(' ')))); + if (includeMvl) { + filters.append(QCoreApplication::translate("QGBA", "%1 Video Logs (*.mvl)", nullptr).arg(projectName)); + } + return filters.join(";;"); +} + } diff --git a/src/platform/qt/utils.h b/src/platform/qt/utils.h index ca9ef027f..9ccc8803f 100644 --- a/src/platform/qt/utils.h +++ b/src/platform/qt/utils.h @@ -67,4 +67,6 @@ constexpr const T& clamp(const T& v, const T& lo, const T& hi) { } #endif +QString romFilters(bool includeMvl = false); + } From dd13ceb42d50c4799ca3968ef22017a586d32fa6 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 29 Oct 2022 01:38:34 -0700 Subject: [PATCH 15/20] VFS: Fix minizip write returning 0 on success instead of size --- CHANGES | 1 + src/util/vfs/vfs-zip.c | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 25e4f5efe..581ec1405 100644 --- a/CHANGES +++ b/CHANGES @@ -9,6 +9,7 @@ Other fixes: - Qt: Expand criteria for tag branch names (fixes mgba.io/i/2679) - Qt: Fix scanning specific e-Reader dotcodes (fixes mgba.io/i/2693) - Res: Fix species name location in Ruby/Sapphire revs 1/2 (fixes mgba.io/i/2685) + - VFS: Fix minizip write returning 0 on success instead of size Misc: - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - macOS: Add category to plist (closes mgba.io/i/2691) diff --git a/src/util/vfs/vfs-zip.c b/src/util/vfs/vfs-zip.c index 2d353c566..2f8234b94 100644 --- a/src/util/vfs/vfs-zip.c +++ b/src/util/vfs/vfs-zip.c @@ -593,7 +593,11 @@ ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t size) { ssize_t _vfzWrite(struct VFile* vf, const void* buffer, size_t size) { struct VFileZip* vfz = (struct VFileZip*) vf; - return zipWriteInFileInZip(vfz->z, buffer, size); + int res = zipWriteInFileInZip(vfz->z, buffer, size); + if (res != ZIP_OK) { + return res; + } + return size; } void* _vfzMap(struct VFile* vf, size_t size, int flags) { From fec87062ca6a4125d449d145f38a1c6689ba94d6 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 29 Oct 2022 01:39:13 -0700 Subject: [PATCH 16/20] CMake: Add another K&R warning to the -Werror list --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a00e93371..42cff1ebc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,7 @@ if(NOT MSVC) # mingw32 likes to complain about using the "wrong" format strings despite them actually working set(WARNING_FLAGS "${WARNING_FLAGS} -Wno-format") endif() - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=implicit-function-declaration") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=implicit-function-declaration -Werror=implicit-int") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}") else() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_CRT_SECURE_NO_WARNINGS /wd4003 /wd4244 /wd4146 /wd4267 /Zc:preprocessor-") From 56c9065f70d8037a1fefbe3cf67c26a1dd231796 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 29 Oct 2022 01:40:52 -0700 Subject: [PATCH 17/20] Qt: Add forwarder UI and Vita backend (closes #2267) --- src/platform/qt/CMakeLists.txt | 6 + src/platform/qt/ForwarderController.cpp | 114 ++++++ src/platform/qt/ForwarderController.h | 54 +++ src/platform/qt/ForwarderGenerator.cpp | 74 ++++ src/platform/qt/ForwarderGenerator.h | 57 +++ src/platform/qt/ForwarderGenerator3DS.cpp | 24 ++ src/platform/qt/ForwarderGenerator3DS.h | 25 ++ src/platform/qt/ForwarderGeneratorVita.cpp | 187 +++++++++ src/platform/qt/ForwarderGeneratorVita.h | 35 ++ src/platform/qt/ForwarderView.cpp | 159 ++++++++ src/platform/qt/ForwarderView.h | 42 ++ src/platform/qt/ForwarderView.ui | 426 +++++++++++++++++++++ src/platform/qt/VFileDevice.cpp | 9 + src/platform/qt/VFileDevice.h | 2 + src/platform/qt/Window.cpp | 3 + 15 files changed, 1217 insertions(+) create mode 100644 src/platform/qt/ForwarderController.cpp create mode 100644 src/platform/qt/ForwarderController.h create mode 100644 src/platform/qt/ForwarderGenerator.cpp create mode 100644 src/platform/qt/ForwarderGenerator.h create mode 100644 src/platform/qt/ForwarderGenerator3DS.cpp create mode 100644 src/platform/qt/ForwarderGenerator3DS.h create mode 100644 src/platform/qt/ForwarderGeneratorVita.cpp create mode 100644 src/platform/qt/ForwarderGeneratorVita.h create mode 100644 src/platform/qt/ForwarderView.cpp create mode 100644 src/platform/qt/ForwarderView.h create mode 100644 src/platform/qt/ForwarderView.ui diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 9b1f60288..099bb9d8e 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -94,6 +94,11 @@ set(SOURCE_FILES Display.cpp DisplayGL.cpp DisplayQt.cpp + ForwarderController.cpp + ForwarderGenerator.cpp + ForwarderGenerator3DS.cpp + ForwarderGeneratorVita.cpp + ForwarderView.cpp FrameView.cpp GBAApp.cpp GBAKeyEditor.cpp @@ -151,6 +156,7 @@ set(UI_FILES CheatsView.ui DebuggerConsole.ui DolphinConnector.ui + ForwarderView.ui FrameView.ui GIFView.ui IOViewer.ui diff --git a/src/platform/qt/ForwarderController.cpp b/src/platform/qt/ForwarderController.cpp new file mode 100644 index 000000000..7130327c2 --- /dev/null +++ b/src/platform/qt/ForwarderController.cpp @@ -0,0 +1,114 @@ +/* Copyright (c) 2013-2022 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "ForwarderController.h" + +#include +#include +#include + +#include "ConfigController.h" + +#include +#include + +using namespace QGBA; + +ForwarderController::ForwarderController(QObject* parent) + : QObject(parent) + , m_netman(new QNetworkAccessManager(this)) +{ + m_netman->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); + connect(this, &ForwarderController::buildFailed, this, [this]() { + m_inProgress = false; + }); + connect(this, &ForwarderController::buildComplete, this, [this]() { + m_inProgress = false; + }); +} + +void ForwarderController::startBuild(const QString& outFilename) { + if (m_inProgress) { + return; + } + m_inProgress = true; + m_outFilename = outFilename; + downloadManifest(); +} + +void ForwarderController::downloadManifest() { + QNetworkReply* reply = m_netman->get(QNetworkRequest(QUrl("https://mgba.io/latest.ini"))); + connect(reply, &QNetworkReply::finished, this, [this, reply]() { + gotManifest(reply); + }); + connect(reply, &QNetworkReply::errorOccurred, this, [this, reply]() { + emit buildFailed(); + }); +} + +void ForwarderController::gotManifest(QNetworkReply* reply) { + if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) { + emit buildFailed(); + return; + } + + QByteArray manifest = reply->readAll(); + QString platform = m_generator->systemName(); + + mUpdaterContext context; + if (!mUpdaterInit(&context, manifest.constData())) { + emit buildFailed(); + return; + } + QString bucket = QLatin1String(mUpdaterGetBucket(&context)); + + mUpdate update; + mUpdaterGetUpdateForChannel(&context, platform.toUtf8().constData(), m_channel.toUtf8().constData(), &update); + + downloadBuild({bucket + update.path}); + mUpdaterDeinit(&context); +} + +void ForwarderController::downloadBuild(const QUrl& url) { + QString extension(QFileInfo(url.path()).suffix()); + // TODO: cache this + QString configDir(ConfigController::configDir()); + m_sourceFile.setFileName(QString("%1/%2-%3-%4.%5").arg(configDir) + .arg(projectName) + .arg(m_generator->systemName()) + .arg(channel()) + .arg(extension)); + if (!m_sourceFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + emit buildFailed(); + return; + } + QNetworkReply* reply = m_netman->get(QNetworkRequest(url)); + + connect(reply, &QNetworkReply::finished, this, [this, reply]() { + gotBuild(reply); + }); + + connect(reply, &QNetworkReply::readyRead, this, [this, reply]() { + QByteArray data = reply->readAll(); + m_sourceFile.write(data); + }); +} + +void ForwarderController::gotBuild(QNetworkReply* reply) { + if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) { + emit buildFailed(); + return; + } + + QByteArray data = reply->readAll(); + m_sourceFile.write(data); + m_sourceFile.close(); + if (!m_generator->rebuild(m_sourceFile.fileName(), m_outFilename)) { + emit buildFailed(); + } else { + emit buildComplete(); + } + m_sourceFile.remove(); +} diff --git a/src/platform/qt/ForwarderController.h b/src/platform/qt/ForwarderController.h new file mode 100644 index 000000000..47baa3949 --- /dev/null +++ b/src/platform/qt/ForwarderController.h @@ -0,0 +1,54 @@ +/* Copyright (c) 2013-2022 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#pragma once + +#include +#include + +#include "ForwarderGenerator.h" + +#include + +class QNetworkAccessManager; +class QNetworkReply; + +namespace QGBA { + +class ForwarderController : public QObject { +Q_OBJECT + +public: + ForwarderController(QObject* parent = nullptr); + + void setGenerator(std::unique_ptr&& generator) { m_generator = std::move(generator); } + ForwarderGenerator* generator() { return m_generator.get(); } + + QString channel() const { return m_channel; } + +public slots: + void startBuild(const QString& outFilename); + +signals: + void buildComplete(); + void buildFailed(); + +private slots: + void gotManifest(QNetworkReply*); + void gotBuild(QNetworkReply*); + +private: + void downloadManifest(); + void downloadBuild(const QUrl&); + + QString m_channel{"dev"}; + QString m_outFilename; + QNetworkAccessManager* m_netman; + std::unique_ptr m_generator; + QFile m_sourceFile; + bool m_inProgress = false; +}; + +} diff --git a/src/platform/qt/ForwarderGenerator.cpp b/src/platform/qt/ForwarderGenerator.cpp new file mode 100644 index 000000000..1c1c2ef94 --- /dev/null +++ b/src/platform/qt/ForwarderGenerator.cpp @@ -0,0 +1,74 @@ +/* Copyright (c) 2013-2022 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "ForwarderGenerator.h" + +#include +#include + +#include "ForwarderGenerator3DS.h" +#include "ForwarderGeneratorVita.h" + +using namespace QGBA; + +std::unique_ptr ForwarderGenerator::createForSystem(System system) { + switch (system) { + case System::N3DS: + return std::make_unique(); + case System::VITA: + return std::make_unique(); + } + return nullptr; +} + +ForwarderGenerator::ForwarderGenerator(int imageTypes, QObject* parent) + : QObject(parent) +{ + m_images.resize(imageTypes); +} + +void ForwarderGenerator::setImage(int index, const QImage& image) { + if (index < 0 || index >= m_images.count()) { + return; + } + + m_images[index] = image; +} + +QImage ForwarderGenerator::image(int index) const { + if (index >= m_images.size()) { + return {}; + } + return m_images[index]; +} + +QByteArray ForwarderGenerator::hashRom() const { + if (m_romPath.isEmpty()) { + return {}; + } + + QFile romFile(m_romPath); + if (!romFile.open(QIODevice::ReadOnly)) { + return {}; + } + + QCryptographicHash hash(QCryptographicHash::Sha256); + if (!hash.addData(&romFile)) { + return {}; + } + + return hash.result(); +} + +QString ForwarderGenerator::systemName(ForwarderGenerator::System system) { + switch (system) { + case ForwarderGenerator::System::N3DS: + return QLatin1String("3ds"); + case ForwarderGenerator::System::VITA: + return QLatin1String("vita"); + } + + return {}; +} diff --git a/src/platform/qt/ForwarderGenerator.h b/src/platform/qt/ForwarderGenerator.h new file mode 100644 index 000000000..1e8eedce4 --- /dev/null +++ b/src/platform/qt/ForwarderGenerator.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2013-2022 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace QGBA { + +class ForwarderGenerator : public QObject { +Q_OBJECT + +public: + enum class System { + N3DS, + VITA, + }; + + static std::unique_ptr createForSystem(System); + + void setTitle(const QString& title) { m_title = title; } + void setRom(const QString& path) { m_romPath = path; } + void setImage(int index, const QImage&); + + QString title() const { return m_title; } + QString rom() const { return m_romPath; } + QImage image(int index) const; + + QByteArray hashRom() const; + + virtual QList> imageTypes() const = 0; + virtual System system() const = 0; + QString systemName() const { return systemName(system()); } + virtual QString extension() const = 0; + + static QString systemName(System); + + virtual bool rebuild(const QString& source, const QString& target) = 0; + +protected: + ForwarderGenerator(int imageTypes, QObject* parent = nullptr); + +private: + QString m_title; + QString m_romPath; + QVector m_images; +}; + +} diff --git a/src/platform/qt/ForwarderGenerator3DS.cpp b/src/platform/qt/ForwarderGenerator3DS.cpp new file mode 100644 index 000000000..123406a3d --- /dev/null +++ b/src/platform/qt/ForwarderGenerator3DS.cpp @@ -0,0 +1,24 @@ +/* Copyright (c) 2013-2022 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "ForwarderGenerator3DS.h" + +using namespace QGBA; + +ForwarderGenerator3DS::ForwarderGenerator3DS() + : ForwarderGenerator(2) +{ +} + +QList> ForwarderGenerator3DS::imageTypes() const { + return { + { tr("Icon"), QSize(48, 48) }, + { tr("Banner"), QSize(256, 128) } + }; +} + +bool ForwarderGenerator3DS::rebuild(const QString& source, const QString& target) { + return false; +} diff --git a/src/platform/qt/ForwarderGenerator3DS.h b/src/platform/qt/ForwarderGenerator3DS.h new file mode 100644 index 000000000..ba6a6131a --- /dev/null +++ b/src/platform/qt/ForwarderGenerator3DS.h @@ -0,0 +1,25 @@ +/* Copyright (c) 2013-2022 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#pragma once + +#include "ForwarderGenerator.h" + +namespace QGBA { + +class ForwarderGenerator3DS final : public ForwarderGenerator { +Q_OBJECT + +public: + ForwarderGenerator3DS(); + + QList> imageTypes() const override; + System system() const override { return System::N3DS; } + QString extension() const override { return QLatin1String("cia"); } + + bool rebuild(const QString& source, const QString& target) override; +}; + +} diff --git a/src/platform/qt/ForwarderGeneratorVita.cpp b/src/platform/qt/ForwarderGeneratorVita.cpp new file mode 100644 index 000000000..1a52817c6 --- /dev/null +++ b/src/platform/qt/ForwarderGeneratorVita.cpp @@ -0,0 +1,187 @@ +/* Copyright (c) 2013-2022 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "ForwarderGeneratorVita.h" + +#include +#include + +#include "VFileDevice.h" + +#include +#include + +using namespace QGBA; + +ForwarderGeneratorVita::ForwarderGeneratorVita() + : ForwarderGenerator(3) +{ +} + +QList> ForwarderGeneratorVita::imageTypes() const { + return { + { tr("Bubble"), QSize(128, 128) }, + { tr("Background"), QSize(840, 500) }, + { tr("Startup"), QSize(280, 158) } + }; +} + +bool ForwarderGeneratorVita::rebuild(const QString& source, const QString& target) { + QString vpk = dumpVpk(source); + if (vpk.isNull()) { + return false; + } + + QFile vpkFile(vpk); + VDir* outdir = VDirOpenZip(target.toLocal8Bit().constData(), O_WRONLY | O_CREAT | O_TRUNC); + if (outdir && !copyAssets(vpk, outdir)) { + outdir->close(outdir); + outdir = nullptr; + } + vpkFile.remove(); + if (!outdir) { + return false; + } + + VFile* sfo = outdir->openFile(outdir, "sce_sys/param.sfo", O_WRONLY); + writeSfo(sfo); + sfo->close(sfo); + + QFileInfo info(rom()); + QByteArray buffer(info.fileName().toUtf8()); + VFile* filename = outdir->openFile(outdir, "filename", O_WRONLY); + filename->write(filename, buffer.constData(), buffer.size()); + filename->close(filename); + + VFile* romfileOut = outdir->openFile(outdir, buffer.constData(), O_WRONLY); + VFileDevice romfileIn(rom(), QIODevice::ReadOnly); + VFileDevice::copyFile(romfileIn, romfileOut); + romfileIn.close(); + romfileOut->close(romfileOut); + + if (!image(0).isNull()) { + injectImage(outdir, "sce_sys/icon0.png", 0); + } + if (!image(1).isNull()) { + injectImage(outdir, "sce_sys/livearea/contents/bg.png", 1); + } + if (!image(2).isNull()) { + injectImage(outdir, "sce_sys/livearea/contents/startup.png", 2); + } + + outdir->close(outdir); + + return true; +} + +QString ForwarderGeneratorVita::dumpVpk(const QString& archive) { + bool gotFile = false; + + VDir* inArchive = VFileDevice::openArchive(archive); + if (!inArchive) { + return {}; + } + for (VDirEntry* dirent = inArchive->listNext(inArchive); dirent; dirent = inArchive->listNext(inArchive)) { + if (dirent->type(dirent) != VFS_FILE) { + continue; + } + QString filename(dirent->name(dirent)); + if (!filename.endsWith(".vpk")) { + continue; + } + + VFile* outfile = VFileOpen("tmp.vpk", O_WRONLY | O_TRUNC | O_CREAT); + VFile* vpk = inArchive->openFile(inArchive, dirent->name(dirent), O_RDONLY); + VFileDevice::copyFile(vpk, outfile); + vpk->close(vpk); + outfile->close(outfile); + gotFile = true; + break; + } + inArchive->close(inArchive); + + if (gotFile) { + return QLatin1String("tmp.vpk"); + } + return {}; +} + +bool ForwarderGeneratorVita::copyAssets(const QString& vpk, VDir* outdir) { + VDir* indir = VDirOpenZip(vpk.toLocal8Bit().constData(), O_RDONLY); + if (!indir) { + return false; + } + + bool ok = true; + for (VDirEntry* dirent = indir->listNext(indir); dirent; dirent = indir->listNext(indir)) { + if (dirent->name(dirent) == QLatin1String("sce_sys/param.sfo")) { + continue; + } + if (dirent->name(dirent) == QLatin1String("sce_sys/icon0.png") && !image(0).isNull()) { + continue; + } + if (dirent->name(dirent) == QLatin1String("sce_sys/livearea/contents/bg.png") && !image(1).isNull()) { + continue; + } + if (dirent->name(dirent) == QLatin1String("sce_sys/livearea/contents/startup.png") && !image(2).isNull()) { + continue; + } + if (dirent->type(dirent) != VFS_FILE) { + continue; + } + + VFile* infile = indir->openFile(indir, dirent->name(dirent), O_RDONLY); + if (!infile) { + ok = false; + break; + } + + VFile* outfile = outdir->openFile(outdir, dirent->name(dirent), O_WRONLY); + if (!outfile) { + infile->close(infile); + ok = false; + break; + } + + VFileDevice::copyFile(infile, outfile); + + infile->close(infile); + outfile->close(outfile); + } + + indir->close(indir); + return ok; +} + +QString ForwarderGeneratorVita::makeSerial() const { + QByteArray hash = hashRom(); + quint32 hashBits = (hash[0] << 24) | (hash[1] << 16) | (hash[2] << 8) | hash[3]; + + QString serial("MFXXXXXXX"); + for (int i = 0; i < 7; ++i) { + static const char alphabet[37] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + serial[i + 2] = alphabet[hashBits % 36]; + hashBits /= 36; + } + + return serial; +} + +void ForwarderGeneratorVita::writeSfo(VFile* out) { + Table sfo; + QByteArray serial(makeSerial().toLocal8Bit()); + QByteArray titleBytes(title().toUtf8()); + SfoInit(&sfo); + SfoSetTitle(&sfo, titleBytes.constData()); + SfoAddStrValue(&sfo, "TITLE_ID", serial.constData()); + SfoWrite(&sfo, out); + SfoDeinit(&sfo); +} + +void ForwarderGeneratorVita::injectImage(VDir* out, const char* name, int index) { + VFile* outfile = out->openFile(out, name, O_WRONLY); + VFileDevice outdev(outfile); + image(index).convertToFormat(QImage::Format_Indexed8).save(&outdev, "PNG"); +} diff --git a/src/platform/qt/ForwarderGeneratorVita.h b/src/platform/qt/ForwarderGeneratorVita.h new file mode 100644 index 000000000..abb85fea1 --- /dev/null +++ b/src/platform/qt/ForwarderGeneratorVita.h @@ -0,0 +1,35 @@ +/* Copyright (c) 2013-2022 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#pragma once + +#include "ForwarderGenerator.h" + +struct VDir; +struct VFile; + +namespace QGBA { + +class ForwarderGeneratorVita final : public ForwarderGenerator { +Q_OBJECT + +public: + ForwarderGeneratorVita(); + + QList> imageTypes() const override; + System system() const override { return System::VITA; } + QString extension() const override { return QLatin1String("vpk"); } + + bool rebuild(const QString& source, const QString& target) override; + +private: + QString dumpVpk(const QString& archive); + bool copyAssets(const QString& vpk, VDir* out); + QString makeSerial() const; + void writeSfo(VFile* out); + void injectImage(VDir* out, const char* name, int index); +}; + +} diff --git a/src/platform/qt/ForwarderView.cpp b/src/platform/qt/ForwarderView.cpp new file mode 100644 index 000000000..bb88c9bc3 --- /dev/null +++ b/src/platform/qt/ForwarderView.cpp @@ -0,0 +1,159 @@ +/* Copyright (c) 2013-2022 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/ */ +#include "ForwarderView.h" + +#include +#include + +#include "ForwarderGenerator.h" +#include "GBAApp.h" +#include "utils.h" + +using namespace QGBA; + +ForwarderView::ForwarderView(QWidget* parent) + : QDialog(parent) +{ + m_ui.setupUi(this); + + connectBrowseButton(m_ui.romBrowse, m_ui.romFilename, tr("Select ROM file"), false, romFilters()); + connectBrowseButton(m_ui.outputBrowse, m_ui.outputFilename, tr("Select output filename"), true); + connectBrowseButton(m_ui.baseBrowse, m_ui.baseFilename, tr("Select base file")); + + connect(m_ui.romFilename, &QLineEdit::textChanged, this, &ForwarderView::validate); + connect(m_ui.outputFilename, &QLineEdit::textChanged, this, &ForwarderView::validate); + connect(m_ui.baseFilename, &QLineEdit::textChanged, this, &ForwarderView::validate); + connect(m_ui.title, &QLineEdit::textChanged, this, &ForwarderView::validate); + connect(m_ui.baseType, qOverload(&QComboBox::currentIndexChanged), this, &ForwarderView::validate); + + connect(m_ui.imageSelect, qOverload(&QComboBox::currentIndexChanged), this, &ForwarderView::setActiveImage); + connect(m_ui.imageBrowse, &QAbstractButton::clicked, this, &ForwarderView::selectImage); + + connect(&m_controller, &ForwarderController::buildComplete, this, &QDialog::accept); + connect(&m_controller, &ForwarderController::buildFailed, this, [this]() { + QMessageBox* error = new QMessageBox(QMessageBox::Critical, tr("Build failed"), + tr("Failed to build forwarder"), + QMessageBox::Ok, this, Qt::Sheet); + error->setAttribute(Qt::WA_DeleteOnClose); + error->show(); + }); + + connect(m_ui.system3DS, &QAbstractButton::clicked, this, [this]() { + setSystem(ForwarderGenerator::System::N3DS); + }); + connect(m_ui.systemVita, &QAbstractButton::clicked, this, [this]() { + setSystem(ForwarderGenerator::System::VITA); + }); + + m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + connect(m_ui.buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, [this]() { + m_controller.generator()->setRom(m_ui.romFilename->text()); + m_controller.startBuild(m_ui.outputFilename->text()); + }); +} + +void ForwarderView::build() { + if (!m_controller.generator()) { + return; + } + m_controller.generator()->setTitle(m_ui.title->text()); + m_controller.generator()->setRom(m_ui.romFilename->text()); + m_controller.startBuild(m_ui.outputFilename->text()); +} + +void ForwarderView::validate() { + bool valid = true; + if (m_ui.romFilename->text().isEmpty()) { + valid = false; + } else if (!QFileInfo(m_ui.romFilename->text()).exists()) { + valid = false; + } + if (m_ui.outputFilename->text().isEmpty()) { + valid = false; + } + if (m_ui.title->text().isEmpty()) { + valid = false; + } + if (!m_ui.system->checkedButton()) { + valid = false; + } + if (m_ui.baseType->currentIndex() != 1) { + valid = false; + } + m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid); +} + +void ForwarderView::setSystem(ForwarderGenerator::System system) { + m_controller.setGenerator(ForwarderGenerator::createForSystem(system)); + auto types = m_controller.generator()->imageTypes(); + m_images.clear(); + m_images.resize(types.count()); + m_ui.imageSelect->clear(); + for (const auto& pair : types) { + m_ui.imageSelect->addItem(pair.first); + } + m_ui.imageSelect->setEnabled(true); + m_ui.imagePreview->setEnabled(true); + m_ui.imageBrowse->setEnabled(true); + m_ui.imagesLabel->setEnabled(true); + m_ui.preferredLabel->setEnabled(true); + m_ui.preferredWidth->setEnabled(true); + m_ui.preferredX->setEnabled(true); + m_ui.preferredHeight->setEnabled(true); +} + +void ForwarderView::connectBrowseButton(QAbstractButton* button, QLineEdit* lineEdit, const QString& title, bool save, const QString& filter) { + connect(button, &QAbstractButton::clicked, lineEdit, [this, lineEdit, save, title, filter]() { + QString filename; + if (save) { + filename = GBAApp::app()->getSaveFileName(this, title, filter); + } else { + filename = GBAApp::app()->getOpenFileName(this, title, filter); + } + if (filename.isEmpty()) { + return; + } + lineEdit->setText(filename); + }); +} + +void ForwarderView::selectImage() { + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select an image"), {}); + if (filename.isEmpty()) { + return; + } + + QImage image(filename); + if (image.isNull()) { + return; + } + image = image.scaled(m_activeSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + + m_ui.imagePreview->setPixmap(QPixmap::fromImage(image)); + m_ui.useDefaultImage->setChecked(false); + m_controller.generator()->setImage(m_currentImage, image); +} + +void ForwarderView::setActiveImage(int index) { + if (index < 0) { + m_currentImage = -1; + m_activeSize = QSize(); + return; + } + if (!m_controller.generator()) { + return; + } + auto types = m_controller.generator()->imageTypes(); + if (index >= types.count()) { + return; + } + m_currentImage = index; + m_activeSize = types[index].second; + m_ui.preferredWidth->setText(QString::number(m_activeSize.width())); + m_ui.preferredHeight->setText(QString::number(m_activeSize.height())); + m_ui.imagePreview->setMaximumSize(m_activeSize); + m_ui.imagePreview->setPixmap(QPixmap::fromImage(m_controller.generator()->image(index))); +} diff --git a/src/platform/qt/ForwarderView.h b/src/platform/qt/ForwarderView.h new file mode 100644 index 000000000..cd80d65b5 --- /dev/null +++ b/src/platform/qt/ForwarderView.h @@ -0,0 +1,42 @@ +/* Copyright (c) 2013-2022 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#pragma once + +#include "ForwarderController.h" +#include "ForwarderGenerator.h" + +#include +#include + +#include "ui_ForwarderView.h" + +namespace QGBA { + +class ForwarderView : public QDialog { +Q_OBJECT + +public: + ForwarderView(QWidget* parent = nullptr); + +private slots: + void build(); + void validate(); + +private: + void setSystem(ForwarderGenerator::System); + void connectBrowseButton(QAbstractButton* button, QLineEdit* lineEdit, const QString& title, bool save = false, const QString& filter = {}); + void selectImage(); + void setActiveImage(int); + + ForwarderController m_controller; + QVector m_images; + int m_currentImage; + QSize m_activeSize; + + Ui::ForwarderView m_ui; +}; + +} diff --git a/src/platform/qt/ForwarderView.ui b/src/platform/qt/ForwarderView.ui new file mode 100644 index 000000000..cac27af70 --- /dev/null +++ b/src/platform/qt/ForwarderView.ui @@ -0,0 +1,426 @@ + + + QGBA::ForwarderView + + + + 0 + 0 + 710 + 465 + + + + Create forwarder + + + + + + + 0 + 0 + + + + System + + + + + + 3DS + + + system + + + + + + + Vita + + + system + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Files + + + + + + ROM file: + + + + + + + + + + Browse + + + + + + + Output filename: + + + + + + + + + + Browse + + + + + + + Forwarder base: + + + + + + + + 0 + 0 + + + + + Latest stable verison + + + + + Latest development build + + + + + Specific file + + + + + + + + false + + + Base file: + + + + + + + false + + + + + + + false + + + Browse + + + + + + + Qt::Horizontal + + + + + + + + + + Presentation + + + + + + + + Title: + + + + + + + + + + false + + + Images: + + + + + + + false + + + + 0 + 0 + + + + + + + + Use default image + + + true + + + + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + false + + + + 0 + 0 + + + + + 32 + 32 + + + + + 128 + 128 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + true + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + false + + + Preferred size: + + + + + + + false + + + 0 + + + + + + + false + + + + 0 + 0 + + + + × + + + + + + + false + + + 0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + false + + + + 0 + 0 + + + + Select image file + + + + + + + + + + + + buttonBox + rejected() + QGBA::ForwarderView + deleteLater() + + + 354 + 439 + + + 354 + 232 + + + + + + + + diff --git a/src/platform/qt/VFileDevice.cpp b/src/platform/qt/VFileDevice.cpp index bce0ed830..9ef822cde 100644 --- a/src/platform/qt/VFileDevice.cpp +++ b/src/platform/qt/VFileDevice.cpp @@ -184,6 +184,15 @@ VDir* VFileDevice::openArchive(const QString& path) { return VDirOpenArchive(path.toUtf8().constData()); } +bool VFileDevice::copyFile(VFile* input, VFile* output) { + uint8_t buffer[0x800]; + ssize_t size; + while ((size = input->read(input, buffer, sizeof(buffer))) > 0) { + output->write(output, buffer, size); + } + return size >= 0; +} + VFileAbstractWrapper::VFileAbstractWrapper(QIODevice* iodev) : m_iodev(iodev) { diff --git a/src/platform/qt/VFileDevice.h b/src/platform/qt/VFileDevice.h index 9df1aff93..2f0a94662 100644 --- a/src/platform/qt/VFileDevice.h +++ b/src/platform/qt/VFileDevice.h @@ -42,6 +42,8 @@ public: static VDir* openDir(const QString& path); static VDir* openArchive(const QString& path); + static bool copyFile(VFile* input, VFile* output); + protected: virtual qint64 readData(char* data, qint64 maxSize) override; virtual qint64 writeData(const char* data, qint64 maxSize) override; diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 41f29dbe5..02460aeb8 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -30,6 +30,7 @@ #include "Display.h" #include "DolphinConnector.h" #include "CoreController.h" +#include "ForwarderView.h" #include "FrameView.h" #include "GBAApp.h" #include "GDBController.h" @@ -1615,6 +1616,8 @@ void Window::setupMenu(QMenuBar* menubar) { m_actions.addAction(tr("Scripting..."), "scripting", this, &Window::scriptingOpen, "tools"); #endif + m_actions.addAction(tr("Create forwarder..."), "createForwarder", openTView(), "tools"); + m_actions.addSeparator("tools"); m_actions.addAction(tr("Settings..."), "settings", this, &Window::openSettingsWindow, "tools")->setRole(Action::Role::SETTINGS); m_actions.addAction(tr("Make portable"), "makePortable", this, &Window::tryMakePortable, "tools"); From 2a5417e3ce15f8ebf85c59f10c0a6e3c6ffd0c46 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 29 Oct 2022 02:02:30 -0700 Subject: [PATCH 18/20] Qt: Fix build on Qt 5.9 - 5.14 --- src/platform/qt/ForwarderController.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/platform/qt/ForwarderController.cpp b/src/platform/qt/ForwarderController.cpp index 7130327c2..cfad550e8 100644 --- a/src/platform/qt/ForwarderController.cpp +++ b/src/platform/qt/ForwarderController.cpp @@ -43,7 +43,11 @@ void ForwarderController::downloadManifest() { connect(reply, &QNetworkReply::finished, this, [this, reply]() { gotManifest(reply); }); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) connect(reply, &QNetworkReply::errorOccurred, this, [this, reply]() { +#else + connect(reply, qOverload<>(&QNetworkReply::error), this, [this, reply]() { +#endif emit buildFailed(); }); } From 472660a5d3429b1979a10fe45922a4173e08a910 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 30 Oct 2022 02:48:39 -0700 Subject: [PATCH 19/20] Qt: Pay down some VFile technical debt --- src/platform/qt/CoreManager.cpp | 18 ++++++++---------- src/platform/qt/VFileDevice.cpp | 8 ++++++-- src/platform/qt/VFileDevice.h | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/platform/qt/CoreManager.cpp b/src/platform/qt/CoreManager.cpp index b5fc3e413..cac1c2560 100644 --- a/src/platform/qt/CoreManager.cpp +++ b/src/platform/qt/CoreManager.cpp @@ -31,6 +31,7 @@ void CoreManager::setMultiplayerController(MultiplayerController* multiplayer) { CoreController* CoreManager::loadGame(const QString& path) { QFileInfo info(path); if (!info.isReadable()) { + // Open specific file in archive QString fname = info.fileName(); QString base = info.path(); if (base.endsWith("/") || base.endsWith(QDir::separator())) { @@ -40,13 +41,8 @@ CoreController* CoreManager::loadGame(const QString& path) { if (dir) { VFile* vf = dir->openFile(dir, fname.toUtf8().constData(), O_RDONLY); if (vf) { - struct VFile* vfclone = VFileMemChunk(NULL, vf->size(vf)); - uint8_t buffer[2048]; - ssize_t read; - while ((read = vf->read(vf, buffer, sizeof(buffer))) > 0) { - vfclone->write(vfclone, buffer, read); - } - vf->close(vf); + struct VFile* vfclone = VFileDevice::openMemory(vf->size(vf)); + VFileDevice::copyFile(vf, vfclone); vf = vfclone; } dir->close(dir); @@ -59,15 +55,16 @@ CoreController* CoreManager::loadGame(const QString& path) { VFile* vf = nullptr; VDir* archive = VDirOpenArchive(path.toUtf8().constData()); if (archive) { + // Open first file in archive VFile* vfOriginal = VDirFindFirst(archive, [](VFile* vf) { return mCoreIsCompatible(vf) != mPLATFORM_NONE; }); if (vfOriginal) { ssize_t size = vfOriginal->size(vfOriginal); if (size > 0) { - void* mem = vfOriginal->map(vfOriginal, size, MAP_READ); - vf = VFileMemChunk(mem, size); - vfOriginal->unmap(vfOriginal, mem, size); + struct VFile* vfclone = VFileDevice::openMemory(vfOriginal->size(vfOriginal)); + VFileDevice::copyFile(vfOriginal, vfclone); + vf = vfclone; } vfOriginal->close(vfOriginal); } @@ -75,6 +72,7 @@ CoreController* CoreManager::loadGame(const QString& path) { } QDir dir(info.dir()); if (!vf) { + // Open bare file vf = VFileOpen(info.canonicalFilePath().toUtf8().constData(), O_RDONLY); } return loadGame(vf, info.fileName(), dir.canonicalPath()); diff --git a/src/platform/qt/VFileDevice.cpp b/src/platform/qt/VFileDevice.cpp index 9ef822cde..18231a263 100644 --- a/src/platform/qt/VFileDevice.cpp +++ b/src/platform/qt/VFileDevice.cpp @@ -172,8 +172,8 @@ VFile* VFileDevice::open(const QString& path, int mode) { return VFileOpen(path.toUtf8().constData(), mode); } -VFile* VFileDevice::openMemory() { - return VFileMemChunk(nullptr, 0); +VFile* VFileDevice::openMemory(quint64 size) { + return VFileMemChunk(nullptr, size); } VDir* VFileDevice::openDir(const QString& path) { @@ -186,6 +186,10 @@ VDir* VFileDevice::openArchive(const QString& path) { bool VFileDevice::copyFile(VFile* input, VFile* output) { uint8_t buffer[0x800]; + + input->seek(input, 0, SEEK_SET); + output->seek(output, 0, SEEK_SET); + ssize_t size; while ((size = input->read(input, buffer, sizeof(buffer))) > 0) { output->write(output, buffer, size); diff --git a/src/platform/qt/VFileDevice.h b/src/platform/qt/VFileDevice.h index 2f0a94662..15f79bbcb 100644 --- a/src/platform/qt/VFileDevice.h +++ b/src/platform/qt/VFileDevice.h @@ -38,7 +38,7 @@ public: static VFile* wrap(QBuffer*, QIODevice::OpenMode); static VFile* open(const QString& path, int mode); - static VFile* openMemory(); + static VFile* openMemory(quint64 size = 0); static VDir* openDir(const QString& path); static VDir* openArchive(const QString& path); From 9ed00c95b6373aa2a860a15eeb1260fbc698589b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 31 Oct 2022 19:45:38 -0700 Subject: [PATCH 20/20] Qt: Add cache dir call, for later expansion --- src/platform/qt/ApplicationUpdater.cpp | 2 +- src/platform/qt/ConfigController.cpp | 4 ++++ src/platform/qt/ConfigController.h | 1 + src/platform/qt/ForwarderController.cpp | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/platform/qt/ApplicationUpdater.cpp b/src/platform/qt/ApplicationUpdater.cpp index 4225bd7ee..a0efca23d 100644 --- a/src/platform/qt/ApplicationUpdater.cpp +++ b/src/platform/qt/ApplicationUpdater.cpp @@ -136,7 +136,7 @@ QUrl ApplicationUpdater::parseManifest(const QByteArray& manifest) { QString ApplicationUpdater::destination() const { QFileInfo path(updateInfo().url.path()); - QDir dir(ConfigController::configDir()); + QDir dir(ConfigController::cacheDir()); // QFileInfo::completeSuffix will eat all .'s in the filename...including // ones in the version string, turning mGBA-1.0.0-win32.7z into // 0.0-win32.7z instead of the intended .7z diff --git a/src/platform/qt/ConfigController.cpp b/src/platform/qt/ConfigController.cpp index f940e4c78..1116dba79 100644 --- a/src/platform/qt/ConfigController.cpp +++ b/src/platform/qt/ConfigController.cpp @@ -382,3 +382,7 @@ const QString& ConfigController::configDir() { } return s_configDir; } + +const QString& ConfigController::cacheDir() { + return configDir(); +} diff --git a/src/platform/qt/ConfigController.h b/src/platform/qt/ConfigController.h index 4b7f859fb..ecd126867 100644 --- a/src/platform/qt/ConfigController.h +++ b/src/platform/qt/ConfigController.h @@ -105,6 +105,7 @@ public: void usage(const char* arg0) const; static const QString& configDir(); + static const QString& cacheDir(); static bool isPortable(); public slots: diff --git a/src/platform/qt/ForwarderController.cpp b/src/platform/qt/ForwarderController.cpp index cfad550e8..9edbe50b9 100644 --- a/src/platform/qt/ForwarderController.cpp +++ b/src/platform/qt/ForwarderController.cpp @@ -78,7 +78,7 @@ void ForwarderController::gotManifest(QNetworkReply* reply) { void ForwarderController::downloadBuild(const QUrl& url) { QString extension(QFileInfo(url.path()).suffix()); // TODO: cache this - QString configDir(ConfigController::configDir()); + QString configDir(ConfigController::cacheDir()); m_sourceFile.setFileName(QString("%1/%2-%3-%4.%5").arg(configDir) .arg(projectName) .arg(m_generator->systemName())