mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' (early part) into medusa
This commit is contained in:
commit
47de096841
8
CHANGES
8
CHANGES
|
@ -41,15 +41,21 @@ Misc:
|
|||
|
||||
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
|
||||
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)
|
||||
- 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)
|
||||
- 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)
|
||||
Features:
|
||||
|
|
|
@ -33,7 +33,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-")
|
||||
|
@ -735,8 +735,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})
|
||||
|
|
|
@ -51,6 +51,7 @@ The following mappers are fully supported:
|
|||
- MBC2
|
||||
- MBC3
|
||||
- MBC3+RTC
|
||||
- MBC30
|
||||
- MBC5
|
||||
- MBC5+Rumble
|
||||
- MBC7
|
||||
|
@ -71,6 +72,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
|
||||
|
|
|
@ -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 <mgba-util/common.h>
|
||||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba-util/table.h>
|
||||
|
||||
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
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -19,6 +19,12 @@
|
|||
#include <mgba-util/png-io.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
#ifdef PSP2
|
||||
#include <psp2/io/stat.h>
|
||||
#elif defined(__3DS__)
|
||||
#include <mgba-util/platform/3ds/3ds-vfs.h>
|
||||
#endif
|
||||
|
||||
#include <sys/time.h>
|
||||
|
||||
mLOG_DECLARE_CATEGORY(GUI_RUNNER);
|
||||
|
@ -450,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);
|
||||
|
@ -745,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];
|
||||
|
@ -770,6 +768,52 @@ 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
|
||||
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;
|
||||
|
|
|
@ -95,9 +95,14 @@ 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*);
|
||||
|
||||
#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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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]);
|
||||
|
|
13
src/gb/mbc.c
13
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,11 +211,17 @@ 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
|
||||
return GB_UNL_LI_CHENG;
|
||||
}
|
||||
if ((0x8000 << cart->romSize) != size) {
|
||||
return GB_UNL_LI_CHENG;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -476,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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 } }
|
||||
};
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -817,7 +817,16 @@ THREAD_ENTRY _core2Test(void* context) {
|
|||
UNUSED(context);
|
||||
}
|
||||
|
||||
int main() {
|
||||
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;
|
||||
|
@ -1047,9 +1056,29 @@ 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 = mGUIGetRom(&runner, initialPath, sizeof(initialPath));
|
||||
if (!useRomfs) {
|
||||
romfsExit();
|
||||
_cleanup();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (initialPath[0] == '/' || useRomfs) {
|
||||
mGUILoadInputMaps(&runner);
|
||||
mGUIRun(&runner, initialPath);
|
||||
} else {
|
||||
mGUIRunloop(&runner);
|
||||
}
|
||||
|
||||
mGUIDeinit(&runner);
|
||||
|
||||
if (useRomfs) {
|
||||
romfsExit();
|
||||
}
|
||||
_cleanup();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -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,13 @@ int main() {
|
|||
mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_SQUARE, mGUI_INPUT_SCREEN_MODE);
|
||||
|
||||
scePowerSetArmClockFrequency(444);
|
||||
mGUIRunloop(&runner);
|
||||
|
||||
if (mGUIGetRom(&runner, initialPath, sizeof(initialPath))) {
|
||||
mGUILoadInputMaps(&runner);
|
||||
mGUIRun(&runner, initialPath);
|
||||
} else {
|
||||
mGUIRunloop(&runner);
|
||||
}
|
||||
|
||||
vita2d_fini();
|
||||
mGUIDeinit(&runner);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -95,6 +95,11 @@ set(SOURCE_FILES
|
|||
Display.cpp
|
||||
DisplayGL.cpp
|
||||
DisplayQt.cpp
|
||||
ForwarderController.cpp
|
||||
ForwarderGenerator.cpp
|
||||
ForwarderGenerator3DS.cpp
|
||||
ForwarderGeneratorVita.cpp
|
||||
ForwarderView.cpp
|
||||
FrameView.cpp
|
||||
GBAApp.cpp
|
||||
GIFView.cpp
|
||||
|
@ -154,6 +159,7 @@ set(UI_FILES
|
|||
CheatsView.ui
|
||||
DebuggerConsole.ui
|
||||
DolphinConnector.ui
|
||||
ForwarderView.ui
|
||||
FrameView.ui
|
||||
GIFView.ui
|
||||
IOViewer.ui
|
||||
|
|
|
@ -382,3 +382,7 @@ const QString& ConfigController::configDir() {
|
|||
}
|
||||
return s_configDir;
|
||||
}
|
||||
|
||||
const QString& ConfigController::cacheDir() {
|
||||
return configDir();
|
||||
}
|
||||
|
|
|
@ -105,6 +105,7 @@ public:
|
|||
void usage(const char* arg0) const;
|
||||
|
||||
static const QString& configDir();
|
||||
static const QString& cacheDir();
|
||||
static bool isPortable();
|
||||
|
||||
public slots:
|
||||
|
|
|
@ -910,7 +910,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;
|
||||
|
@ -932,6 +935,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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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<CoreController> 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();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
/* 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 <QFileInfo>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "ConfigController.h"
|
||||
|
||||
#include <mgba/core/version.h>
|
||||
#include <mgba/feature/updater.h>
|
||||
|
||||
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);
|
||||
});
|
||||
#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();
|
||||
});
|
||||
}
|
||||
|
||||
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::cacheDir());
|
||||
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();
|
||||
}
|
|
@ -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 <QFile>
|
||||
#include <QObject>
|
||||
|
||||
#include "ForwarderGenerator.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class ForwarderController : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ForwarderController(QObject* parent = nullptr);
|
||||
|
||||
void setGenerator(std::unique_ptr<ForwarderGenerator>&& 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<ForwarderGenerator> m_generator;
|
||||
QFile m_sourceFile;
|
||||
bool m_inProgress = false;
|
||||
};
|
||||
|
||||
}
|
|
@ -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 <QCryptographicHash>
|
||||
#include <QFile>
|
||||
|
||||
#include "ForwarderGenerator3DS.h"
|
||||
#include "ForwarderGeneratorVita.h"
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
std::unique_ptr<ForwarderGenerator> ForwarderGenerator::createForSystem(System system) {
|
||||
switch (system) {
|
||||
case System::N3DS:
|
||||
return std::make_unique<ForwarderGenerator3DS>();
|
||||
case System::VITA:
|
||||
return std::make_unique<ForwarderGeneratorVita>();
|
||||
}
|
||||
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 {};
|
||||
}
|
|
@ -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 <QByteArray>
|
||||
#include <QImage>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class ForwarderGenerator : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum class System {
|
||||
N3DS,
|
||||
VITA,
|
||||
};
|
||||
|
||||
static std::unique_ptr<ForwarderGenerator> 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<QPair<QString, QSize>> 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<QImage> m_images;
|
||||
};
|
||||
|
||||
}
|
|
@ -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<QPair<QString, QSize>> 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;
|
||||
}
|
|
@ -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<QPair<QString, QSize>> 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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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 <QFileInfo>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
#include "VFileDevice.h"
|
||||
|
||||
#include <mgba-util/sfo.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
ForwarderGeneratorVita::ForwarderGeneratorVita()
|
||||
: ForwarderGenerator(3)
|
||||
{
|
||||
}
|
||||
|
||||
QList<QPair<QString, QSize>> 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");
|
||||
}
|
|
@ -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<QPair<QString, QSize>> 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);
|
||||
};
|
||||
|
||||
}
|
|
@ -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 <QMessageBox>
|
||||
#include <QResizeEvent>
|
||||
|
||||
#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<int>(&QComboBox::currentIndexChanged), this, &ForwarderView::validate);
|
||||
|
||||
connect(m_ui.imageSelect, qOverload<int>(&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)));
|
||||
}
|
|
@ -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 <QImage>
|
||||
#include <QVector>
|
||||
|
||||
#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<QImage> m_images;
|
||||
int m_currentImage;
|
||||
QSize m_activeSize;
|
||||
|
||||
Ui::ForwarderView m_ui;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,426 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>QGBA::ForwarderView</class>
|
||||
<widget class="QDialog" name="QGBA::ForwarderView">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>710</width>
|
||||
<height>465</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Create forwarder</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>System</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item alignment="Qt::AlignHCenter">
|
||||
<widget class="QRadioButton" name="system3DS">
|
||||
<property name="text">
|
||||
<string>3DS</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">system</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item alignment="Qt::AlignHCenter">
|
||||
<widget class="QRadioButton" name="systemVita">
|
||||
<property name="text">
|
||||
<string>Vita</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">system</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Files</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>ROM file:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="romFilename"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="romBrowse">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Output filename:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="outputFilename"/>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QPushButton" name="outputBrowse">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Forwarder base:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QComboBox" name="baseType">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Latest stable verison</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Latest development build</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Specific file</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Base file:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="baseFilename">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QPushButton" name="baseBrowse">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" rowspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Presentation</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Title:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="title"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="imagesLabel">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Images:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="imageSelect">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="useDefaultImage">
|
||||
<property name="text">
|
||||
<string>Use default image</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_2" rowstretch="0,1,0" columnstretch="0,1,0">
|
||||
<item row="0" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="imagePreview">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>128</width>
|
||||
<height>128</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<spacer name="horizontalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="2,1,0,1,0">
|
||||
<item alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="preferredLabel">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Preferred size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="preferredWidth">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item alignment="Qt::AlignHCenter">
|
||||
<widget class="QLabel" name="preferredX">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">×</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="preferredHeight">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="imageBrowse">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Select image file</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>QGBA::ForwarderView</receiver>
|
||||
<slot>deleteLater()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>354</x>
|
||||
<y>439</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>354</x>
|
||||
<y>232</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<buttongroups>
|
||||
<buttongroup name="system"/>
|
||||
</buttongroups>
|
||||
</ui>
|
|
@ -40,6 +40,7 @@ static const QList<GBMemoryBankControllerType> 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)");
|
||||
|
|
|
@ -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<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [this, colorPresets](int n) {
|
||||
const GBColorPreset* preset = &colorPresets[n];
|
||||
|
@ -642,6 +647,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()) {
|
||||
|
|
|
@ -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) {
|
||||
|
@ -184,6 +184,19 @@ VDir* VFileDevice::openArchive(const QString& path) {
|
|||
return VDirOpenArchive(path.toUtf8().constData());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return size >= 0;
|
||||
}
|
||||
|
||||
VFileAbstractWrapper::VFileAbstractWrapper(QIODevice* iodev)
|
||||
: m_iodev(iodev)
|
||||
{
|
||||
|
|
|
@ -38,10 +38,12 @@ 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);
|
||||
|
||||
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;
|
||||
|
|
|
@ -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"
|
||||
|
@ -337,69 +338,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_DS
|
||||
QStringList dsFormats{
|
||||
"*.nds",
|
||||
"*.srl",
|
||||
#if defined(USE_LIBZIP) || defined(USE_ZLIB)
|
||||
"*.zip",
|
||||
#endif
|
||||
#ifdef USE_LZMA
|
||||
"*.7z",
|
||||
#endif
|
||||
"*.rom",
|
||||
"*.bin"};
|
||||
formats.append(dsFormats);
|
||||
filters.append(tr("DS ROMs (%1)").arg(dsFormats.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;
|
||||
|
||||
|
@ -416,7 +354,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);
|
||||
}
|
||||
|
@ -459,7 +397,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);
|
||||
}
|
||||
|
@ -1755,6 +1693,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<ForwarderView>(), "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");
|
||||
|
|
|
@ -187,7 +187,6 @@ private:
|
|||
|
||||
void updateTitle(float fps = -1);
|
||||
|
||||
QString getFilters() const;
|
||||
QString getFiltersArchive() const;
|
||||
|
||||
CoreManager* m_manager;
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "utils.h"
|
||||
|
||||
#include <mgba/core/version.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QObject>
|
||||
|
||||
namespace QGBA {
|
||||
|
@ -59,4 +62,69 @@ 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_DS
|
||||
QStringList dsFormats{
|
||||
"*.nds",
|
||||
"*.srl",
|
||||
#if defined(USE_LIBZIP) || defined(USE_ZLIB)
|
||||
"*.zip",
|
||||
#endif
|
||||
#ifdef USE_LZMA
|
||||
"*.7z",
|
||||
#endif
|
||||
"*.rom",
|
||||
"*.bin"};
|
||||
formats.append(dsFormats);
|
||||
filters.append(QCoreApplication::translate("QGBA", "DS ROMs (%1)").arg(dsFormats.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(";;");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -77,4 +77,6 @@ constexpr const T& clamp(const T& v, const T& lo, const T& hi) {
|
|||
}
|
||||
#endif
|
||||
|
||||
QString romFilters(bool includeMvl = false);
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <mgba-util/common.h>
|
||||
#include <mgba-util/table.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
#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;
|
||||
}
|
|
@ -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 <mgba-util/sfo.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
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),
|
||||
)
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue