mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' (early part) into medusa
This commit is contained in:
commit
cc06ae4690
6
CHANGES
6
CHANGES
|
@ -36,6 +36,7 @@ Misc:
|
|||
0.9.0: (Future)
|
||||
Features:
|
||||
- e-Reader card scanning
|
||||
- New tool for converting between different save game formats
|
||||
- WebP and APNG recording
|
||||
- Separate overrides for GBC games that can also run on SGB or regular GB
|
||||
- Game Boy Player features can be enabled by default for all compatible games
|
||||
|
@ -84,6 +85,7 @@ Emulation fixes:
|
|||
- GBA SIO: Fix copying Normal mode transfer values
|
||||
- GBA SIO: Fix Normal mode being totally broken (fixes mgba.io/i/1800)
|
||||
- GBA SIO: Fix deseralizing SIO registers
|
||||
- GBA SIO: Fix hanging on starting a second multiplayer window (fixes mgba.io/i/854)
|
||||
- GBA Video: Latch scanline at end of Hblank (fixes mgba.io/i/1319)
|
||||
- GBA Video: Fix Hblank timing
|
||||
- GBA Video: Implement green swap (fixes mgba.io/i/1609)
|
||||
|
@ -93,6 +95,7 @@ Emulation fixes:
|
|||
Other fixes:
|
||||
- 3DS: Fix thread cleanup
|
||||
- All: Improve export headers (fixes mgba.io/i/1738)
|
||||
- Cheats: Fix indirect write cheats (fixes mgba.io/i/2026)
|
||||
- CMake: Fix build with downstream minizip that exports incompatible symbols
|
||||
- CMake: Link with correct OpenGL library (fixes mgba.io/i/1872)
|
||||
- Core: Ensure ELF regions can be written before trying
|
||||
|
@ -129,8 +132,10 @@ Misc:
|
|||
- Core: Add shutdown callback
|
||||
- Core: Rework thread state synchronization
|
||||
- Core: Improve support for ROM patch cheats, supporting disabling overlapping patches
|
||||
- Core: Adding to library is now recursive
|
||||
- GB: Allow pausing event loop while CPU is blocked
|
||||
- GB: Add support for sleep and shutdown callbacks
|
||||
- GB: Redo double speed emulation (closes mgba.io/i/1515)
|
||||
- GB Core: Return the current number of banks for ROM/SRAM, not theoretical max
|
||||
- GB I/O: Implement preliminary support for PCM12/PCM34 (closes mgba.io/i/1468)
|
||||
- GBA: Allow pausing event loop while CPU is blocked
|
||||
|
@ -156,6 +161,7 @@ Misc:
|
|||
- Qt: Unify monospace font usage
|
||||
- Qt: Add button to jump to log settings
|
||||
- Qt: Use relative paths in portable mode when applicable (fixes mgba.io/i/838)
|
||||
- Qt: Better initial shortcut editor column sizes
|
||||
- SDL: Fall back to sw blit if OpenGL init fails
|
||||
- Util: Reset vector size on deinit
|
||||
- VFS: Change semantics of VFile.sync on mapped files (fixes mgba.io/i/1730)
|
||||
|
|
|
@ -5,7 +5,8 @@ medusa is an emulator for running Nintendo DS, Game Boy Advance and Game Boy gam
|
|||
|
||||
Up-to-date news and downloads can be found at [mgba.io](https://mgba.io/).
|
||||
|
||||
[](https://travis-ci.org/mgba-emu/mgba)
|
||||
[](https://buildbot.mgba.io)
|
||||
[](https://hosted.weblate.org/engage/mgba)
|
||||
|
||||
Features
|
||||
--------
|
||||
|
@ -37,6 +38,7 @@ Features
|
|||
- Configurable emulation rewinding.
|
||||
- Support for loading and exporting GameShark and Action Replay snapshots.
|
||||
- Cores available for RetroArch/Libretro and OpenEmu.
|
||||
- Community-provided translations for several languages via [Weblate](https://hosted.weblate.org/engage/mgba).
|
||||
- Many, many smaller things.
|
||||
|
||||
#### Game Boy mappers
|
||||
|
@ -297,7 +299,7 @@ Missing features on DS are
|
|||
Copyright
|
||||
---------
|
||||
|
||||
medusa is Copyright © 2013 – 2020 Jeffrey Pfau. It is distributed under the [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/). A copy of the license is available in the distributed LICENSE file.
|
||||
medusa is Copyright © 2013 – 2021 Jeffrey Pfau. It is distributed under the [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/). A copy of the license is available in the distributed LICENSE file.
|
||||
|
||||
medusa contains the following third-party libraries:
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ mGBA ist ein Emulator für Game Boy Advance-Spiele. Das Ziel von mGBA ist, schne
|
|||
Aktuelle Neuigkeiten und Downloads findest Du auf [mgba.io](https://mgba.io).
|
||||
|
||||
[](https://travis-ci.org/mgba-emu/mgba)
|
||||
[](https://hosted.weblate.org/engage/mgba)
|
||||
|
||||
Features
|
||||
--------
|
||||
|
@ -36,6 +37,7 @@ Features
|
|||
- Einstellbare Rücklauf-Funktion.
|
||||
- Unterstützung für das Laden und Exportieren von GameShark- und Action Replay-Abbildern.
|
||||
- Verfügbare Cores für RetroArch/Libretro und OpenEmu.
|
||||
- Übersetzungen für mehrere Sprachen über [Weblate](https://hosted.weblate.org/engage/mgba).
|
||||
- Viele, viele kleinere Dinge.
|
||||
|
||||
### Game Boy-Mapper
|
||||
|
@ -240,7 +242,7 @@ Fußnoten
|
|||
Copyright
|
||||
---------
|
||||
|
||||
Copyright für mGBA © 2013 – 2020 Jeffrey Pfau. mGBA wird unter der [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/) veröffentlicht. Eine Kopie der Lizenz ist in der mitgelieferten Datei LICENSE verfügbar.
|
||||
Copyright für mGBA © 2013 – 2021 Jeffrey Pfau. mGBA wird unter der [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/) veröffentlicht. Eine Kopie der Lizenz ist in der mitgelieferten Datei LICENSE verfügbar.
|
||||
|
||||
mGBA beinhaltet die folgenden Bibliotheken von Drittanbietern:
|
||||
|
||||
|
|
|
@ -5,7 +5,8 @@ mGBA es un emulador para juegos de Game Boy Advance. Su objetivo es ser más rá
|
|||
|
||||
Las noticias actualizadas y las descargas se encuentran en [mgba.io](https://mgba.io/).
|
||||
|
||||
[](https://travis-ci.org/mgba-emu/mgba)
|
||||
[](https://travis-ci.org/mgba-emu/mgba)
|
||||
[](https://hosted.weblate.org/engage/mgba)
|
||||
|
||||
Características
|
||||
--------
|
||||
|
@ -36,6 +37,7 @@ Características
|
|||
- Retroceso configurable.
|
||||
- Soporte para cargar y exportar instantáneas de GameShark y Action Replay.
|
||||
- Núcleos disponibles para RetroArch/Libretro y OpenEmu.
|
||||
- Traducciones de la comunidad a través de [Weblate](https://hosted.weblate.org/engage/mgba).
|
||||
- Otras cosas más pequeñas.
|
||||
|
||||
#### Mappers (controladores de memoria) soportados
|
||||
|
@ -240,7 +242,7 @@ Notas a pie
|
|||
Copyright
|
||||
---------
|
||||
|
||||
mGBA es Copyright © 2013 – 2020 Jeffrey Pfau. Es distribuído bajo la [licencia pública de Mozilla (Mozilla Public License) version 2.0](https://www.mozilla.org/MPL/2.0/). Una copia de la licencia está disponible en el archivo LICENSE.
|
||||
mGBA es Copyright © 2013 – 2021 Jeffrey Pfau. Es distribuído bajo la [licencia pública de Mozilla (Mozilla Public License) version 2.0](https://www.mozilla.org/MPL/2.0/). Una copia de la licencia está disponible en el archivo LICENSE.
|
||||
|
||||
mGBA contiene las siguientes bibliotecas de terceros:
|
||||
|
||||
|
|
|
@ -119,6 +119,33 @@ static inline int reduceFraction(int* num, int* den) {
|
|||
return n;
|
||||
}
|
||||
|
||||
#define TYPE_GENERICIZE(MACRO) \
|
||||
MACRO(int, Int) \
|
||||
MACRO(unsigned, UInt)
|
||||
|
||||
#define LOCK_ASPECT_RATIO(T, t) \
|
||||
static inline void lockAspectRatio ## t(T refW, T refH, T* w, T* h) { \
|
||||
if (*w * refH > *h * refW) { \
|
||||
*w = *h * refW / refH; \
|
||||
} else if (*w * refH < *h * refW) { \
|
||||
*h = *w * refH / refW; \
|
||||
} \
|
||||
}
|
||||
|
||||
TYPE_GENERICIZE(LOCK_ASPECT_RATIO)
|
||||
#undef LOCK_ASPECT_RATIO
|
||||
|
||||
#define LOCK_INTEGER_RATIO(T, t) \
|
||||
static inline void lockIntegerRatio ## t(T ref, T* val) { \
|
||||
if (*val >= ref) { \
|
||||
*val -= *val % ref; \
|
||||
} \
|
||||
}
|
||||
|
||||
TYPE_GENERICIZE(LOCK_INTEGER_RATIO)
|
||||
#undef LOCK_INTEGER_RATIO
|
||||
|
||||
#undef TYPE_GENERICIZE
|
||||
CXX_GUARD_END
|
||||
|
||||
#endif
|
||||
|
|
|
@ -79,7 +79,7 @@ struct VFile* VFileFIFO(struct CircleBuffer* backing);
|
|||
struct VDir* VDirOpen(const char* path);
|
||||
struct VDir* VDirOpenArchive(const char* path);
|
||||
|
||||
#if defined(USE_LIBZIP) || defined(USE_ZLIB)
|
||||
#if defined(USE_LIBZIP) || defined(USE_MINIZIP)
|
||||
struct VDir* VDirOpenZip(const char* path, int flags);
|
||||
#endif
|
||||
|
||||
|
|
|
@ -191,6 +191,7 @@ void mCoreTakeScreenshot(struct mCore* core);
|
|||
|
||||
struct mCore* mCoreFindVF(struct VFile* vf);
|
||||
enum mPlatform mCoreIsCompatible(struct VFile* vf);
|
||||
struct mCore* mCoreCreate(enum mPlatform);
|
||||
|
||||
bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags);
|
||||
bool mCoreLoadStateNamed(struct mCore* core, struct VFile* vf, int flags);
|
||||
|
|
|
@ -35,7 +35,7 @@ void mLibraryDestroy(struct mLibrary*);
|
|||
|
||||
struct VDir;
|
||||
struct VFile;
|
||||
void mLibraryLoadDirectory(struct mLibrary* library, const char* base);
|
||||
void mLibraryLoadDirectory(struct mLibrary* library, const char* base, bool recursive);
|
||||
void mLibraryClear(struct mLibrary* library);
|
||||
|
||||
size_t mLibraryCount(struct mLibrary* library, const struct mLibraryEntry* constraints);
|
||||
|
|
|
@ -13,8 +13,8 @@ CXX_GUARD_START
|
|||
#include <mgba/core/core.h>
|
||||
#include <mgba/internal/gb/gb.h>
|
||||
|
||||
extern const uint32_t GB_SAVESTATE_MAGIC;
|
||||
extern const uint32_t GB_SAVESTATE_VERSION;
|
||||
extern MGBA_EXPORT const uint32_t GBSavestateMagic;
|
||||
extern MGBA_EXPORT const uint32_t GBSavestateVersion;
|
||||
|
||||
mLOG_DECLARE_CATEGORY(GB_STATE);
|
||||
|
||||
|
|
|
@ -14,8 +14,8 @@ CXX_GUARD_START
|
|||
#include <mgba/internal/gba/gba.h>
|
||||
#include <mgba/internal/gb/serialize.h>
|
||||
|
||||
extern const uint32_t GBA_SAVESTATE_MAGIC;
|
||||
extern const uint32_t GBA_SAVESTATE_VERSION;
|
||||
extern MGBA_EXPORT const uint32_t GBASavestateMagic;
|
||||
extern MGBA_EXPORT const uint32_t GBASavestateVersion;
|
||||
|
||||
mLOG_DECLARE_CATEGORY(GBA_STATE);
|
||||
|
||||
|
|
|
@ -133,6 +133,7 @@ struct SM83Core {
|
|||
|
||||
uint16_t index;
|
||||
|
||||
int tMultiplier;
|
||||
int32_t cycles;
|
||||
int32_t nextEvent;
|
||||
enum SM83ExecutionState executionState;
|
||||
|
|
|
@ -666,7 +666,7 @@ void mCheatRefresh(struct mCheatDevice* device, struct mCheatSet* cheats) {
|
|||
break;
|
||||
case CHEAT_ASSIGN_INDIRECT:
|
||||
value = operand;
|
||||
address = _readMem(device->p, address + cheat->addressOffset, 4);
|
||||
address = _readMem(device->p, address, 4) + cheat->addressOffset;
|
||||
performAssignment = true;
|
||||
break;
|
||||
case CHEAT_AND:
|
||||
|
|
|
@ -212,14 +212,20 @@ void mCoreConfigDirectory(char* out, size_t outLength) {
|
|||
}
|
||||
}
|
||||
#ifdef _WIN32
|
||||
wchar_t wpath[MAX_PATH];
|
||||
wchar_t wprojectName[MAX_PATH];
|
||||
wchar_t* home;
|
||||
WCHAR wpath[MAX_PATH];
|
||||
WCHAR wprojectName[MAX_PATH];
|
||||
WCHAR* home;
|
||||
MultiByteToWideChar(CP_UTF8, 0, projectName, -1, wprojectName, MAX_PATH);
|
||||
SHGetKnownFolderPath(&FOLDERID_RoamingAppData, 0, NULL, &home);
|
||||
StringCchPrintfW(wpath, MAX_PATH, L"%ws\\%ws", home, wprojectName);
|
||||
CoTaskMemFree(home);
|
||||
CreateDirectoryW(wpath, NULL);
|
||||
if (PATH_SEP[0] != '\\') {
|
||||
WCHAR* pathSep;
|
||||
for (pathSep = wpath; pathSep = wcschr(pathSep, L'\\');) {
|
||||
pathSep[0] = PATH_SEP[0];
|
||||
}
|
||||
}
|
||||
WideCharToMultiByte(CP_UTF8, 0, wpath, -1, out, outLength, 0, 0);
|
||||
#elif defined(PSP2)
|
||||
snprintf(out, outLength, "ux0:data/%s", projectName);
|
||||
|
@ -256,8 +262,14 @@ void mCoreConfigPortablePath(char* out, size_t outLength) {
|
|||
HMODULE hModule = GetModuleHandleW(NULL);
|
||||
GetModuleFileNameW(hModule, wpath, MAX_PATH);
|
||||
PathRemoveFileSpecW(wpath);
|
||||
if (PATH_SEP[0] != '\\') {
|
||||
WCHAR* pathSep;
|
||||
for (pathSep = wpath; pathSep = wcschr(pathSep, L'\\');) {
|
||||
pathSep[0] = PATH_SEP[0];
|
||||
}
|
||||
}
|
||||
WideCharToMultiByte(CP_UTF8, 0, wpath, -1, out, outLength, 0, 0);
|
||||
StringCchCatA(out, outLength, "\\portable.ini");
|
||||
StringCchCatA(out, outLength, PATH_SEP "portable.ini");
|
||||
#elif defined(PSP2) || defined(GEKKO) || defined(__SWITCH__) || defined(_3DS)
|
||||
out[0] = '\0';
|
||||
#else
|
||||
|
|
|
@ -80,6 +80,19 @@ enum mPlatform mCoreIsCompatible(struct VFile* vf) {
|
|||
return mPLATFORM_NONE;
|
||||
}
|
||||
|
||||
struct mCore* mCoreCreate(enum mPlatform platform) {
|
||||
const struct mCoreFilter* filter;
|
||||
for (filter = &_filters[0]; filter->filter; ++filter) {
|
||||
if (filter->platform == platform) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (filter->open) {
|
||||
return filter->open();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||
#include <mgba-util/png-io.h>
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ struct mLibrary {
|
|||
|
||||
static void _mLibraryDeleteEntry(struct mLibrary* library, struct mLibraryEntry* entry);
|
||||
static void _mLibraryInsertEntry(struct mLibrary* library, struct mLibraryEntry* entry);
|
||||
static void _mLibraryAddEntry(struct mLibrary* library, const char* filename, const char* base, struct VFile* vf);
|
||||
static bool _mLibraryAddEntry(struct mLibrary* library, const char* filename, const char* base, struct VFile* vf);
|
||||
|
||||
static void _bindConstraints(sqlite3_stmt* statement, const struct mLibraryEntry* constraints) {
|
||||
if (!constraints) {
|
||||
|
@ -212,7 +212,7 @@ void mLibraryDestroy(struct mLibrary* library) {
|
|||
free(library);
|
||||
}
|
||||
|
||||
void mLibraryLoadDirectory(struct mLibrary* library, const char* base) {
|
||||
void mLibraryLoadDirectory(struct mLibrary* library, const char* base, bool recursive) {
|
||||
struct VDir* dir = VDirOpenArchive(base);
|
||||
if (!dir) {
|
||||
dir = VDirOpen(base);
|
||||
|
@ -248,44 +248,55 @@ void mLibraryLoadDirectory(struct mLibrary* library, const char* base) {
|
|||
dir->rewind(dir);
|
||||
struct VDirEntry* dirent = dir->listNext(dir);
|
||||
while (dirent) {
|
||||
struct VFile* vf = dir->openFile(dir, dirent->name(dirent), O_RDONLY);
|
||||
if (!vf) {
|
||||
dirent = dir->listNext(dir);
|
||||
continue;
|
||||
const char* name = dirent->name(dirent);
|
||||
struct VFile* vf = dir->openFile(dir, name, O_RDONLY);
|
||||
bool wasAdded = false;
|
||||
|
||||
if (vf) {
|
||||
wasAdded = _mLibraryAddEntry(library, name, base, vf);
|
||||
}
|
||||
if (!wasAdded && name[0] != '.') {
|
||||
char newBase[PATH_MAX];
|
||||
snprintf(newBase, sizeof(newBase), "%s" PATH_SEP "%s", base, name);
|
||||
|
||||
if (recursive) {
|
||||
mLibraryLoadDirectory(library, newBase, recursive);
|
||||
} else if (dirent->type(dirent) == VFS_FILE) {
|
||||
mLibraryLoadDirectory(library, newBase, true); // This will add as an archive
|
||||
}
|
||||
}
|
||||
_mLibraryAddEntry(library, dirent->name(dirent), base, vf);
|
||||
dirent = dir->listNext(dir);
|
||||
}
|
||||
dir->close(dir);
|
||||
sqlite3_exec(library->db, "COMMIT;", NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
void _mLibraryAddEntry(struct mLibrary* library, const char* filename, const char* base, struct VFile* vf) {
|
||||
struct mCore* core;
|
||||
bool _mLibraryAddEntry(struct mLibrary* library, const char* filename, const char* base, struct VFile* vf) {
|
||||
if (!vf) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
core = mCoreFindVF(vf);
|
||||
if (core) {
|
||||
struct mLibraryEntry entry;
|
||||
memset(&entry, 0, sizeof(entry));
|
||||
core->init(core);
|
||||
core->loadROM(core, vf);
|
||||
|
||||
core->getGameTitle(core, entry.internalTitle);
|
||||
core->getGameCode(core, entry.internalCode);
|
||||
core->checksum(core, &entry.crc32, mCHECKSUM_CRC32);
|
||||
entry.platform = core->platform(core);
|
||||
entry.title = NULL;
|
||||
entry.base = base;
|
||||
entry.filename = filename;
|
||||
entry.filesize = vf->size(vf);
|
||||
_mLibraryInsertEntry(library, &entry);
|
||||
// Note: this destroys the VFile
|
||||
core->deinit(core);
|
||||
} else {
|
||||
struct mCore* core = mCoreFindVF(vf);
|
||||
if (!core) {
|
||||
vf->close(vf);
|
||||
return false;
|
||||
}
|
||||
struct mLibraryEntry entry;
|
||||
memset(&entry, 0, sizeof(entry));
|
||||
core->init(core);
|
||||
core->loadROM(core, vf);
|
||||
|
||||
core->getGameTitle(core, entry.internalTitle);
|
||||
core->getGameCode(core, entry.internalCode);
|
||||
core->checksum(core, &entry.crc32, mCHECKSUM_CRC32);
|
||||
entry.platform = core->platform(core);
|
||||
entry.title = NULL;
|
||||
entry.base = base;
|
||||
entry.filename = filename;
|
||||
entry.filesize = vf->size(vf);
|
||||
_mLibraryInsertEntry(library, &entry);
|
||||
// Note: this destroys the VFile
|
||||
core->deinit(core);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void _mLibraryInsertEntry(struct mLibrary* library, struct mLibraryEntry* entry) {
|
||||
|
|
|
@ -282,7 +282,11 @@ static void* _loadPNGState(struct mCore* core, struct VFile* vf, struct mStateEx
|
|||
success = success && PNGReadFooter(png, end);
|
||||
PNGReadClose(png, info, end);
|
||||
|
||||
if (success) {
|
||||
if (!success) {
|
||||
free(pixels);
|
||||
mappedMemoryFree(state, stateSize);
|
||||
return NULL;
|
||||
} else if (extdata) {
|
||||
struct mStateExtdataItem item = {
|
||||
.size = width * height * 4,
|
||||
.data = pixels,
|
||||
|
@ -291,8 +295,6 @@ static void* _loadPNGState(struct mCore* core, struct VFile* vf, struct mStateEx
|
|||
mStateExtdataPut(extdata, EXTDATA_SCREENSHOT, &item);
|
||||
} else {
|
||||
free(pixels);
|
||||
mappedMemoryFree(state, stateSize);
|
||||
return 0;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ void GBAudioInit(struct GBAudio* audio, size_t samples, uint8_t* nr52, enum GBAu
|
|||
if (style == GB_AUDIO_GBA) {
|
||||
audio->timingFactor = 4;
|
||||
} else {
|
||||
audio->timingFactor = 1;
|
||||
audio->timingFactor = 2;
|
||||
}
|
||||
|
||||
audio->frameEvent.context = audio;
|
||||
|
@ -339,7 +339,7 @@ void GBAudioWriteNR34(struct GBAudio* audio, uint8_t value) {
|
|||
if (audio->playingCh3) {
|
||||
audio->ch3.readable = audio->style != GB_AUDIO_DMG;
|
||||
// TODO: Where does this cycle delay come from?
|
||||
mTimingSchedule(audio->timing, &audio->ch3Event, audio->timingFactor * 4 + 2 * (2048 - audio->ch3.rate));
|
||||
mTimingSchedule(audio->timing, &audio->ch3Event, audio->timingFactor * (4 + 2 * (2048 - audio->ch3.rate)));
|
||||
}
|
||||
*audio->nr52 &= ~0x0004;
|
||||
*audio->nr52 |= audio->playingCh3 << 2;
|
||||
|
@ -477,11 +477,8 @@ void GBAudioWriteNR52(struct GBAudio* audio, uint8_t value) {
|
|||
audio->skipFrame = false;
|
||||
audio->frame = 7;
|
||||
|
||||
if (audio->p) {
|
||||
unsigned timingFactor = 0x400 >> !audio->p->doubleSpeed;
|
||||
if (audio->p->timer.internalDiv & timingFactor) {
|
||||
audio->skipFrame = true;
|
||||
}
|
||||
if (audio->p && audio->p->timer.internalDiv & 0x400) {
|
||||
audio->skipFrame = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -914,7 +911,7 @@ static void _updateChannel3(struct mTiming* timing, void* user, uint32_t cyclesL
|
|||
audio->ch3.readable = true;
|
||||
if (audio->style == GB_AUDIO_DMG) {
|
||||
mTimingDeschedule(audio->timing, &audio->ch3Fade);
|
||||
mTimingSchedule(timing, &audio->ch3Fade, 2 - cyclesLate);
|
||||
mTimingSchedule(timing, &audio->ch3Fade, 4 - cyclesLate);
|
||||
}
|
||||
int cycles = 2 * (2048 - ch->rate);
|
||||
mTimingSchedule(timing, &audio->ch3Event, audio->timingFactor * cycles - cyclesLate);
|
||||
|
|
|
@ -742,7 +742,7 @@ void GBSetInterrupts(struct SM83Core* cpu, bool enable) {
|
|||
gb->memory.ime = false;
|
||||
GBUpdateIRQs(gb);
|
||||
} else {
|
||||
mTimingSchedule(&gb->timing, &gb->eiPending, 4);
|
||||
mTimingSchedule(&gb->timing, &gb->eiPending, 4 * cpu->tMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -796,7 +796,7 @@ void GBStop(struct SM83Core* cpu) {
|
|||
struct GB* gb = (struct GB*) cpu->master;
|
||||
if (gb->model >= GB_MODEL_CGB && gb->memory.io[GB_REG_KEY1] & 1) {
|
||||
gb->doubleSpeed ^= 1;
|
||||
gb->audio.timingFactor = gb->doubleSpeed + 1;
|
||||
gb->cpu->tMultiplier = 2 - gb->doubleSpeed;
|
||||
gb->memory.io[GB_REG_KEY1] = 0;
|
||||
gb->memory.io[GB_REG_KEY1] |= gb->doubleSpeed << 7;
|
||||
} else {
|
||||
|
|
|
@ -417,15 +417,15 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) {
|
|||
}
|
||||
return;
|
||||
case GB_REG_TIMA:
|
||||
if (value && mTimingUntil(&gb->timing, &gb->timer.irq) > 1) {
|
||||
if (value && mTimingUntil(&gb->timing, &gb->timer.irq) > 2 - (int) gb->doubleSpeed) {
|
||||
mTimingDeschedule(&gb->timing, &gb->timer.irq);
|
||||
}
|
||||
if (mTimingUntil(&gb->timing, &gb->timer.irq) == -1) {
|
||||
if (mTimingUntil(&gb->timing, &gb->timer.irq) == (int) gb->doubleSpeed - 2) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case GB_REG_TMA:
|
||||
if (mTimingUntil(&gb->timing, &gb->timer.irq) == -1) {
|
||||
if (mTimingUntil(&gb->timing, &gb->timer.irq) == (int) gb->doubleSpeed - 2) {
|
||||
gb->memory.io[GB_REG_TIMA] = value;
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -531,10 +531,7 @@ void GBMemoryDMA(struct GB* gb, uint16_t base) {
|
|||
base &= 0xDFFF;
|
||||
}
|
||||
mTimingDeschedule(&gb->timing, &gb->memory.dmaEvent);
|
||||
mTimingSchedule(&gb->timing, &gb->memory.dmaEvent, 8);
|
||||
if (gb->cpu->cycles + 8 < gb->cpu->nextEvent) {
|
||||
gb->cpu->nextEvent = gb->cpu->cycles + 8;
|
||||
}
|
||||
mTimingSchedule(&gb->timing, &gb->memory.dmaEvent, 8 * (2 - gb->doubleSpeed));
|
||||
gb->memory.dmaSource = base;
|
||||
gb->memory.dmaDest = 0;
|
||||
gb->memory.dmaRemaining = 0xA0;
|
||||
|
@ -580,7 +577,7 @@ void _GBMemoryDMAService(struct mTiming* timing, void* context, uint32_t cyclesL
|
|||
++gb->memory.dmaDest;
|
||||
gb->memory.dmaRemaining = dmaRemaining - 1;
|
||||
if (gb->memory.dmaRemaining) {
|
||||
mTimingSchedule(timing, &gb->memory.dmaEvent, 4 - cyclesLate);
|
||||
mTimingSchedule(timing, &gb->memory.dmaEvent, 4 * (2 - gb->doubleSpeed) - cyclesLate);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -594,7 +591,7 @@ void _GBMemoryHDMAService(struct mTiming* timing, void* context, uint32_t cycles
|
|||
--gb->memory.hdmaRemaining;
|
||||
if (gb->memory.hdmaRemaining) {
|
||||
mTimingDeschedule(timing, &gb->memory.hdmaEvent);
|
||||
mTimingSchedule(timing, &gb->memory.hdmaEvent, 2 - cyclesLate);
|
||||
mTimingSchedule(timing, &gb->memory.hdmaEvent, 4 - cyclesLate);
|
||||
} else {
|
||||
gb->cpuBlocked = false;
|
||||
gb->memory.io[GB_REG_HDMA1] = gb->memory.hdmaSource >> 8;
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
|
||||
mLOG_DEFINE_CATEGORY(GB_STATE, "GB Savestate", "gb.serialize");
|
||||
|
||||
const uint32_t GB_SAVESTATE_MAGIC = 0x00400000;
|
||||
const uint32_t GB_SAVESTATE_VERSION = 0x00000002;
|
||||
MGBA_EXPORT const uint32_t GBSavestateMagic = 0x00400000;
|
||||
MGBA_EXPORT const uint32_t GBSavestateVersion = 0x00000002;
|
||||
|
||||
void GBSerialize(struct GB* gb, struct GBSerializedState* state) {
|
||||
STORE_32LE(GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION, 0, &state->versionMagic);
|
||||
STORE_32LE(GBSavestateMagic + GBSavestateVersion, 0, &state->versionMagic);
|
||||
STORE_32LE(gb->romCrc32, 0, &state->romCrc32);
|
||||
STORE_32LE(gb->timing.masterCycles, 0, &state->masterCycles);
|
||||
STORE_64LE(gb->timing.globalCycles, 0, &state->globalCycles);
|
||||
|
@ -76,20 +76,20 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) {
|
|||
int16_t check16;
|
||||
uint16_t ucheck16;
|
||||
LOAD_32LE(ucheck, 0, &state->versionMagic);
|
||||
if (ucheck > GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION) {
|
||||
mLOG(GB_STATE, WARN, "Invalid or too new savestate: expected %08X, got %08X", GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION, ucheck);
|
||||
if (ucheck > GBSavestateMagic + GBSavestateVersion) {
|
||||
mLOG(GB_STATE, WARN, "Invalid or too new savestate: expected %08X, got %08X", GBSavestateMagic + GBSavestateVersion, ucheck);
|
||||
error = true;
|
||||
} else if (ucheck < GB_SAVESTATE_MAGIC) {
|
||||
mLOG(GB_STATE, WARN, "Invalid savestate: expected %08X, got %08X", GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION, ucheck);
|
||||
} else if (ucheck < GBSavestateMagic) {
|
||||
mLOG(GB_STATE, WARN, "Invalid savestate: expected %08X, got %08X", GBSavestateMagic + GBSavestateVersion, ucheck);
|
||||
error = true;
|
||||
} else if (ucheck < GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION) {
|
||||
mLOG(GB_STATE, WARN, "Old savestate: expected %08X, got %08X, continuing anyway", GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION, ucheck);
|
||||
} else if (ucheck < GBSavestateMagic + GBSavestateVersion) {
|
||||
mLOG(GB_STATE, WARN, "Old savestate: expected %08X, got %08X, continuing anyway", GBSavestateMagic + GBSavestateVersion, ucheck);
|
||||
}
|
||||
bool canSgb = ucheck >= GB_SAVESTATE_MAGIC + 2;
|
||||
bool canSgb = ucheck >= GBSavestateMagic + 2;
|
||||
|
||||
if (gb->memory.rom && memcmp(state->title, ((struct GBCartridge*) &gb->memory.rom[0x100])->titleLong, sizeof(state->title))) {
|
||||
LOAD_32LE(ucheck, 0, &state->versionMagic);
|
||||
if (ucheck > GB_SAVESTATE_MAGIC + 2 || memcmp(state->title, ((struct GBCartridge*) gb->memory.rom)->titleLong, sizeof(state->title))) {
|
||||
if (ucheck > GBSavestateMagic + 2 || memcmp(state->title, ((struct GBCartridge*) gb->memory.rom)->titleLong, sizeof(state->title))) {
|
||||
// There was a bug in previous versions where the memory address being compared was wrong
|
||||
mLOG(GB_STATE, WARN, "Savestate is for a different game");
|
||||
error = true;
|
||||
|
@ -175,8 +175,6 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) {
|
|||
gb->cpu->halted = GBSerializedCpuFlagsGetHalted(flags);
|
||||
gb->cpuBlocked = GBSerializedCpuFlagsGetBlocked(flags);
|
||||
|
||||
gb->audio.timingFactor = gb->doubleSpeed + 1;
|
||||
|
||||
LOAD_32LE(gb->cpu->cycles, 0, &state->cpu.cycles);
|
||||
LOAD_32LE(gb->cpu->nextEvent, 0, &state->cpu.nextEvent);
|
||||
gb->timing.root = NULL;
|
||||
|
|
|
@ -77,7 +77,7 @@ void _GBSIOProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesL
|
|||
sio->pendingSB = 0xFF;
|
||||
}
|
||||
} else {
|
||||
mTimingSchedule(timing, &sio->event, sio->period);
|
||||
mTimingSchedule(timing, &sio->event, sio->period * (2 - sio->p->doubleSpeed));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,7 @@ void GBSIOWriteSC(struct GBSIO* sio, uint8_t sc) {
|
|||
if (GBRegisterSCIsEnable(sc)) {
|
||||
mTimingDeschedule(&sio->p->timing, &sio->event);
|
||||
if (GBRegisterSCIsShiftClock(sc)) {
|
||||
mTimingSchedule(&sio->p->timing, &sio->event, sio->period);
|
||||
mTimingSchedule(&sio->p->timing, &sio->event, sio->period * (2 - sio->p->doubleSpeed));
|
||||
sio->remainingBits = 8;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -128,7 +128,7 @@ static int32_t _masterUpdate(struct GBSIOLockstepNode* node) {
|
|||
case TRANSFER_FINISHING:
|
||||
// Finish the transfer
|
||||
// We need to make sure the other GBs catch up so they don't get behind
|
||||
node->nextEvent += node->d.p->period - 8; // Split the cycles to avoid waiting too long
|
||||
node->nextEvent += node->d.p->period * (2 - node->d.p->p->doubleSpeed) - 8; // Split the cycles to avoid waiting too long
|
||||
#ifndef NDEBUG
|
||||
ATOMIC_ADD(node->p->d.transferId, 1);
|
||||
#endif
|
||||
|
@ -208,7 +208,7 @@ static void _GBSIOLockstepNodeProcessEvents(struct mTiming* timing, void* user,
|
|||
struct GBSIOLockstepNode* node = user;
|
||||
mLockstepLock(&node->p->d);
|
||||
if (node->p->d.attached < 2) {
|
||||
mTimingSchedule(timing, &node->event, (GBSIOCyclesPerTransfer[0] >> 1) - cyclesLate);
|
||||
mTimingSchedule(timing, &node->event, (GBSIOCyclesPerTransfer[0] >> 1) * (2 - node->d.p->p->doubleSpeed) - cyclesLate);
|
||||
mLockstepUnlock(&node->p->d);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -20,17 +20,18 @@ void _GBTimerIRQ(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
|||
}
|
||||
|
||||
static void _GBTimerDivIncrement(struct GBTimer* timer, uint32_t cyclesLate) {
|
||||
while (timer->nextDiv >= GB_DMG_DIV_PERIOD) {
|
||||
timer->nextDiv -= GB_DMG_DIV_PERIOD;
|
||||
int tMultiplier = 2 - timer->p->doubleSpeed;
|
||||
while (timer->nextDiv >= GB_DMG_DIV_PERIOD * tMultiplier) {
|
||||
timer->nextDiv -= GB_DMG_DIV_PERIOD * tMultiplier;
|
||||
|
||||
// Make sure to trigger when the correct bit is a falling edge
|
||||
if (timer->timaPeriod > 0 && (timer->internalDiv & (timer->timaPeriod - 1)) == timer->timaPeriod - 1) {
|
||||
++timer->p->memory.io[GB_REG_TIMA];
|
||||
if (!timer->p->memory.io[GB_REG_TIMA]) {
|
||||
mTimingSchedule(&timer->p->timing, &timer->irq, 7 - ((timer->p->cpu->executionState - cyclesLate) & 3));
|
||||
mTimingSchedule(&timer->p->timing, &timer->irq, 7 * tMultiplier - ((timer->p->cpu->executionState * tMultiplier - cyclesLate) & (3 * tMultiplier)));
|
||||
}
|
||||
}
|
||||
unsigned timingFactor = 0x3FF >> !timer->p->doubleSpeed;
|
||||
unsigned timingFactor = 0x1FF;
|
||||
if ((timer->internalDiv & timingFactor) == timingFactor) {
|
||||
GBAudioUpdateFrame(&timer->p->audio, &timer->p->timing);
|
||||
}
|
||||
|
@ -52,7 +53,7 @@ void _GBTimerUpdate(struct mTiming* timing, void* context, uint32_t cyclesLate)
|
|||
if (timaToGo < divsToGo) {
|
||||
divsToGo = timaToGo;
|
||||
}
|
||||
timer->nextDiv = GB_DMG_DIV_PERIOD * divsToGo;
|
||||
timer->nextDiv = GB_DMG_DIV_PERIOD * divsToGo * (2 - timer->p->doubleSpeed);
|
||||
mTimingSchedule(timing, &timer->event, timer->nextDiv - cyclesLate);
|
||||
}
|
||||
|
||||
|
@ -66,7 +67,7 @@ void GBTimerReset(struct GBTimer* timer) {
|
|||
timer->irq.callback = _GBTimerIRQ;
|
||||
timer->event.priority = 0x21;
|
||||
|
||||
timer->nextDiv = GB_DMG_DIV_PERIOD; // TODO: GBC differences
|
||||
timer->nextDiv = GB_DMG_DIV_PERIOD * 2;
|
||||
timer->timaPeriod = 1024 >> 4;
|
||||
}
|
||||
|
||||
|
@ -74,27 +75,27 @@ void GBTimerDivReset(struct GBTimer* timer) {
|
|||
timer->nextDiv -= mTimingUntil(&timer->p->timing, &timer->event);
|
||||
mTimingDeschedule(&timer->p->timing, &timer->event);
|
||||
_GBTimerDivIncrement(timer, 0);
|
||||
if (((timer->internalDiv << 1) | ((timer->nextDiv >> 3) & 1)) & timer->timaPeriod) {
|
||||
int tMultiplier = 2 - timer->p->doubleSpeed;
|
||||
if (((timer->internalDiv << 1) | ((timer->nextDiv >> (4 - timer->p->doubleSpeed)) & 1)) & timer->timaPeriod) {
|
||||
++timer->p->memory.io[GB_REG_TIMA];
|
||||
if (!timer->p->memory.io[GB_REG_TIMA]) {
|
||||
mTimingSchedule(&timer->p->timing, &timer->irq, 7 - (timer->p->cpu->executionState & 3));
|
||||
mTimingSchedule(&timer->p->timing, &timer->irq, (7 - (timer->p->cpu->executionState & 3)) * tMultiplier);
|
||||
}
|
||||
}
|
||||
unsigned timingFactor = 0x400 >> !timer->p->doubleSpeed;
|
||||
if (timer->internalDiv & timingFactor) {
|
||||
if (timer->internalDiv & 0x200) {
|
||||
GBAudioUpdateFrame(&timer->p->audio, &timer->p->timing);
|
||||
}
|
||||
timer->p->memory.io[GB_REG_DIV] = 0;
|
||||
timer->internalDiv = 0;
|
||||
timer->nextDiv = GB_DMG_DIV_PERIOD;
|
||||
mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv - ((timer->p->cpu->executionState + 1) & 3));
|
||||
timer->nextDiv = GB_DMG_DIV_PERIOD * (2 - timer->p->doubleSpeed);
|
||||
mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv - ((timer->p->cpu->executionState + 1) & 3) * tMultiplier);
|
||||
}
|
||||
|
||||
uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) {
|
||||
if (GBRegisterTACIsRun(tac)) {
|
||||
timer->nextDiv -= mTimingUntil(&timer->p->timing, &timer->event);
|
||||
mTimingDeschedule(&timer->p->timing, &timer->event);
|
||||
_GBTimerDivIncrement(timer, (timer->p->cpu->executionState + 2) & 3);
|
||||
_GBTimerDivIncrement(timer, ((timer->p->cpu->executionState + 2) & 3) * (2 - timer->p->doubleSpeed));
|
||||
|
||||
switch (GBRegisterTACGetClock(tac)) {
|
||||
case 0:
|
||||
|
@ -111,7 +112,7 @@ uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) {
|
|||
break;
|
||||
}
|
||||
|
||||
timer->nextDiv += GB_DMG_DIV_PERIOD;
|
||||
timer->nextDiv += GB_DMG_DIV_PERIOD * (2 - timer->p->doubleSpeed);
|
||||
mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv);
|
||||
} else {
|
||||
timer->timaPeriod = 0;
|
||||
|
|
|
@ -253,7 +253,7 @@ void GBVideoSkipBIOS(struct GBVideo* video) {
|
|||
GBUpdateIRQs(video->p);
|
||||
video->p->memory.io[GB_REG_STAT] = video->stat;
|
||||
mTimingDeschedule(&video->p->timing, &video->modeEvent);
|
||||
mTimingSchedule(&video->p->timing, &video->modeEvent, next);
|
||||
mTimingSchedule(&video->p->timing, &video->modeEvent, next << 1);
|
||||
}
|
||||
|
||||
void _endMode0(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
|
@ -297,7 +297,7 @@ void _endMode0(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
|||
|
||||
GBUpdateIRQs(video->p);
|
||||
video->p->memory.io[GB_REG_STAT] = video->stat;
|
||||
mTimingSchedule(timing, &video->modeEvent, (next << video->p->doubleSpeed) - cyclesLate);
|
||||
mTimingSchedule(timing, &video->modeEvent, (next << 1) - cyclesLate);
|
||||
}
|
||||
|
||||
void _endMode1(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
|
@ -334,14 +334,14 @@ void _endMode1(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
|||
GBUpdateIRQs(video->p);
|
||||
}
|
||||
video->p->memory.io[GB_REG_STAT] = video->stat;
|
||||
mTimingSchedule(timing, &video->modeEvent, (next << video->p->doubleSpeed) - cyclesLate);
|
||||
mTimingSchedule(timing, &video->modeEvent, (next << 1) - cyclesLate);
|
||||
}
|
||||
|
||||
void _endMode2(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct GBVideo* video = context;
|
||||
_cleanOAM(video, video->ly);
|
||||
video->x = -(video->p->memory.io[GB_REG_SCX] & 7);
|
||||
video->dotClock = mTimingCurrentTime(timing) - cyclesLate + 5 - (video->x << video->p->doubleSpeed);
|
||||
video->dotClock = mTimingCurrentTime(timing) - cyclesLate + 10 - (video->x << 1);
|
||||
int32_t next = GB_VIDEO_MODE_3_LENGTH_BASE + video->objMax * 6 - video->x;
|
||||
video->mode = 3;
|
||||
video->modeEvent.callback = _endMode3;
|
||||
|
@ -352,7 +352,7 @@ void _endMode2(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
|||
GBUpdateIRQs(video->p);
|
||||
}
|
||||
video->p->memory.io[GB_REG_STAT] = video->stat;
|
||||
mTimingSchedule(timing, &video->modeEvent, (next << video->p->doubleSpeed) - cyclesLate);
|
||||
mTimingSchedule(timing, &video->modeEvent, (next << 1) - cyclesLate);
|
||||
}
|
||||
|
||||
void _endMode3(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
|
@ -375,18 +375,18 @@ void _endMode3(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
|||
video->p->memory.io[GB_REG_STAT] = video->stat;
|
||||
// TODO: Cache SCX & 7 in case it changes
|
||||
int32_t next = GB_VIDEO_MODE_0_LENGTH_BASE - video->objMax * 6 - (video->p->memory.io[GB_REG_SCX] & 7);
|
||||
mTimingSchedule(timing, &video->modeEvent, (next << video->p->doubleSpeed) - cyclesLate);
|
||||
mTimingSchedule(timing, &video->modeEvent, (next << 1) - cyclesLate);
|
||||
}
|
||||
|
||||
void _updateFrameCount(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
UNUSED(cyclesLate);
|
||||
struct GBVideo* video = context;
|
||||
if (video->p->cpu->executionState != SM83_CORE_FETCH) {
|
||||
mTimingSchedule(timing, &video->frameEvent, 4 - ((video->p->cpu->executionState + 1) & 3));
|
||||
mTimingSchedule(timing, &video->frameEvent, (4 - ((video->p->cpu->executionState + 1) & 3)) * (2 - video->p->doubleSpeed));
|
||||
return;
|
||||
}
|
||||
if (!GBRegisterLCDCIsEnable(video->p->memory.io[GB_REG_LCDC])) {
|
||||
mTimingSchedule(timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH);
|
||||
mTimingSchedule(timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH << 1);
|
||||
}
|
||||
|
||||
--video->frameskipCounter;
|
||||
|
@ -424,7 +424,7 @@ void GBVideoProcessDots(struct GBVideo* video, uint32_t cyclesLate) {
|
|||
return;
|
||||
}
|
||||
int oldX = video->x;
|
||||
video->x = (int32_t) (mTimingCurrentTime(&video->p->timing) - cyclesLate - video->dotClock) >> video->p->doubleSpeed;
|
||||
video->x = ((int32_t) (mTimingCurrentTime(&video->p->timing) - cyclesLate - video->dotClock)) >> 1;
|
||||
if (video->x > GB_VIDEO_HORIZONTAL_PIXELS) {
|
||||
video->x = GB_VIDEO_HORIZONTAL_PIXELS;
|
||||
} else if (video->x < 0) {
|
||||
|
@ -444,7 +444,7 @@ void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) {
|
|||
video->modeEvent.callback = _endMode2;
|
||||
int32_t next = GB_VIDEO_MODE_2_LENGTH - 5; // TODO: Why is this fudge factor needed? Might be related to T-cycles for load/store differing
|
||||
mTimingDeschedule(&video->p->timing, &video->modeEvent);
|
||||
mTimingSchedule(&video->p->timing, &video->modeEvent, next << video->p->doubleSpeed);
|
||||
mTimingSchedule(&video->p->timing, &video->modeEvent, next << 1);
|
||||
|
||||
video->ly = 0;
|
||||
video->p->memory.io[GB_REG_LY] = 0;
|
||||
|
@ -471,7 +471,7 @@ void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) {
|
|||
|
||||
mTimingDeschedule(&video->p->timing, &video->modeEvent);
|
||||
mTimingDeschedule(&video->p->timing, &video->frameEvent);
|
||||
mTimingSchedule(&video->p->timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH);
|
||||
mTimingSchedule(&video->p->timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH << 1);
|
||||
}
|
||||
video->p->memory.io[GB_REG_STAT] = video->stat;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
#include <mgba/internal/gba/serialize.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
#define MAPPING_MASK (GBA_MATRIX_MAPPINGS_MAX - 1)
|
||||
|
||||
static void _remapMatrix(struct GBA* gba) {
|
||||
if (gba->memory.matrix.vaddr & 0xFFFFE1FF) {
|
||||
mLOG(GBA_MEM, ERROR, "Invalid Matrix mapping: %08X", gba->memory.matrix.vaddr);
|
||||
|
@ -24,11 +26,11 @@ static void _remapMatrix(struct GBA* gba) {
|
|||
mLOG(GBA_MEM, ERROR, "Invalid Matrix mapping end: %08X", gba->memory.matrix.vaddr + gba->memory.matrix.size);
|
||||
return;
|
||||
}
|
||||
int start = (gba->memory.matrix.vaddr >> 9) & 0x1F;
|
||||
int size = (gba->memory.matrix.size >> 9) & 0x1F;
|
||||
int start = gba->memory.matrix.vaddr >> 9;
|
||||
int size = (gba->memory.matrix.size >> 9) & MAPPING_MASK;
|
||||
int i;
|
||||
for (i = 0; i < size; ++i) {
|
||||
gba->memory.matrix.mappings[start + i] = gba->memory.matrix.paddr + (i << 9);
|
||||
gba->memory.matrix.mappings[(start + i) & MAPPING_MASK] = gba->memory.matrix.paddr + (i << 9);
|
||||
}
|
||||
|
||||
gba->romVf->seek(gba->romVf, gba->memory.matrix.paddr, SEEK_SET);
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
|
||||
#include <fcntl.h>
|
||||
|
||||
const uint32_t GBA_SAVESTATE_MAGIC = 0x01000000;
|
||||
const uint32_t GBA_SAVESTATE_VERSION = 0x00000004;
|
||||
MGBA_EXPORT const uint32_t GBASavestateMagic = 0x01000000;
|
||||
MGBA_EXPORT const uint32_t GBASavestateVersion = 0x00000004;
|
||||
|
||||
mLOG_DEFINE_CATEGORY(GBA_STATE, "GBA Savestate", "gba.serialize");
|
||||
|
||||
|
@ -25,7 +25,7 @@ struct GBABundledState {
|
|||
};
|
||||
|
||||
void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
|
||||
STORE_32(GBA_SAVESTATE_MAGIC + GBA_SAVESTATE_VERSION, 0, &state->versionMagic);
|
||||
STORE_32(GBASavestateMagic + GBASavestateVersion, 0, &state->versionMagic);
|
||||
STORE_32(gba->biosChecksum, 0, &state->biosChecksum);
|
||||
STORE_32(gba->romCrc32, 0, &state->romCrc32);
|
||||
STORE_32(gba->timing.masterCycles, 0, &state->masterCycles);
|
||||
|
@ -87,14 +87,14 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
|||
int32_t check;
|
||||
uint32_t ucheck;
|
||||
LOAD_32(ucheck, 0, &state->versionMagic);
|
||||
if (ucheck > GBA_SAVESTATE_MAGIC + GBA_SAVESTATE_VERSION) {
|
||||
mLOG(GBA_STATE, WARN, "Invalid or too new savestate: expected %08X, got %08X", GBA_SAVESTATE_MAGIC + GBA_SAVESTATE_VERSION, ucheck);
|
||||
if (ucheck > GBASavestateMagic + GBASavestateVersion) {
|
||||
mLOG(GBA_STATE, WARN, "Invalid or too new savestate: expected %08X, got %08X", GBASavestateMagic + GBASavestateVersion, ucheck);
|
||||
error = true;
|
||||
} else if (ucheck < GBA_SAVESTATE_MAGIC) {
|
||||
mLOG(GBA_STATE, WARN, "Invalid savestate: expected %08X, got %08X", GBA_SAVESTATE_MAGIC + GBA_SAVESTATE_VERSION, ucheck);
|
||||
} else if (ucheck < GBASavestateMagic) {
|
||||
mLOG(GBA_STATE, WARN, "Invalid savestate: expected %08X, got %08X", GBASavestateMagic + GBASavestateVersion, ucheck);
|
||||
error = true;
|
||||
} else if (ucheck < GBA_SAVESTATE_MAGIC + GBA_SAVESTATE_VERSION) {
|
||||
mLOG(GBA_STATE, WARN, "Old savestate: expected %08X, got %08X, continuing anyway", GBA_SAVESTATE_MAGIC + GBA_SAVESTATE_VERSION, ucheck);
|
||||
} else if (ucheck < GBASavestateMagic + GBASavestateVersion) {
|
||||
mLOG(GBA_STATE, WARN, "Old savestate: expected %08X, got %08X, continuing anyway", GBASavestateMagic + GBASavestateVersion, ucheck);
|
||||
}
|
||||
LOAD_32(ucheck, 0, &state->biosChecksum);
|
||||
if (ucheck != gba->biosChecksum) {
|
||||
|
|
|
@ -149,11 +149,7 @@ bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) {
|
|||
|
||||
// Flush ongoing transfer
|
||||
if (mTimingIsScheduled(&driver->p->p->timing, &node->event)) {
|
||||
int oldWhen = node->event.when;
|
||||
|
||||
mTimingDeschedule(&driver->p->p->timing, &node->event);
|
||||
mTimingSchedule(&driver->p->p->timing, &node->event, 0);
|
||||
node->eventDiff -= oldWhen - node->event.when;
|
||||
node->eventDiff -= node->event.when - mTimingCurrentTime(&driver->p->p->timing);
|
||||
mTimingDeschedule(&driver->p->p->timing, &node->event);
|
||||
}
|
||||
|
||||
|
@ -190,15 +186,11 @@ static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver
|
|||
ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
|
||||
ATOMIC_STORE(node->p->d.transferCycles, GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(node->d.p->siocnt)][node->p->d.attached - 1]);
|
||||
|
||||
bool scheduled = mTimingIsScheduled(&driver->p->p->timing, &node->event);
|
||||
int oldWhen = node->event.when;
|
||||
|
||||
mTimingDeschedule(&driver->p->p->timing, &node->event);
|
||||
mTimingSchedule(&driver->p->p->timing, &node->event, 0);
|
||||
|
||||
if (scheduled) {
|
||||
node->eventDiff -= oldWhen - node->event.when;
|
||||
if (mTimingIsScheduled(&driver->p->p->timing, &node->event)) {
|
||||
node->eventDiff -= node->event.when - mTimingCurrentTime(&driver->p->p->timing);
|
||||
mTimingDeschedule(&driver->p->p->timing, &node->event);
|
||||
}
|
||||
mTimingSchedule(&driver->p->p->timing, &node->event, 0);
|
||||
} else {
|
||||
value &= ~0x0080;
|
||||
}
|
||||
|
@ -443,14 +435,13 @@ static uint32_t _slaveUpdate(struct GBASIOLockstepNode* node) {
|
|||
static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) {
|
||||
struct GBASIOLockstepNode* node = user;
|
||||
mLockstepLock(&node->p->d);
|
||||
if (node->p->d.attached < 2) {
|
||||
mLockstepUnlock(&node->p->d);
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t cycles = 0;
|
||||
node->nextEvent -= cyclesLate;
|
||||
node->eventDiff += cyclesLate;
|
||||
if (node->nextEvent <= 0) {
|
||||
if (node->p->d.attached < 2) {
|
||||
cycles = GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(node->d.p->siocnt)][0];
|
||||
} else if (node->nextEvent <= 0) {
|
||||
if (!node->id) {
|
||||
cycles = _masterUpdate(node);
|
||||
} else {
|
||||
|
|
|
@ -88,19 +88,11 @@ static void mGLContextResized(struct VideoBackend* v, unsigned w, unsigned h) {
|
|||
unsigned drawW = w;
|
||||
unsigned drawH = h;
|
||||
if (v->lockAspectRatio) {
|
||||
if (w * v->height > h * v->width) {
|
||||
drawW = h * v->width / v->height;
|
||||
} else if (w * v->height < h * v->width) {
|
||||
drawH = w * v->height / v->width;
|
||||
}
|
||||
lockAspectRatioUInt(v->width, v->height, &drawW, &drawH);
|
||||
}
|
||||
if (v->lockIntegerScaling) {
|
||||
if (drawW >= v->width) {
|
||||
drawW -= drawW % v->width;
|
||||
}
|
||||
if (drawH >= v->height) {
|
||||
drawH -= drawH % v->height;
|
||||
}
|
||||
lockIntegerRatioUInt(v->width, &drawW);
|
||||
lockIntegerRatioUInt(v->height, &drawH);
|
||||
}
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
|
|
|
@ -221,19 +221,11 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h)
|
|||
unsigned drawW = w;
|
||||
unsigned drawH = h;
|
||||
if (v->lockAspectRatio) {
|
||||
if (w * v->height > h * v->width) {
|
||||
drawW = h * v->width / v->height;
|
||||
} else if (w * v->height < h * v->width) {
|
||||
drawH = w * v->height / v->width;
|
||||
}
|
||||
lockAspectRatioUInt(v->width, v->height, &drawW, &drawH);
|
||||
}
|
||||
if (v->lockIntegerScaling) {
|
||||
if (drawW >= v->width) {
|
||||
drawW -= drawW % v->width;
|
||||
}
|
||||
if (drawH >= v->height) {
|
||||
drawH -= drawH % v->height;
|
||||
}
|
||||
lockIntegerRatioUInt(v->width, &drawW);
|
||||
lockIntegerRatioUInt(v->height, &drawH);
|
||||
}
|
||||
size_t n;
|
||||
for (n = 0; n < context->nShaders; ++n) {
|
||||
|
|
|
@ -71,4 +71,10 @@ AboutScreen::AboutScreen(QWidget* parent)
|
|||
patrons.replace("{patrons}", patronList.join(" • "));
|
||||
m_ui.patrons->setText(patrons);
|
||||
}
|
||||
|
||||
{
|
||||
QString copyright = m_ui.copyright->text();
|
||||
copyright.replace("{year}", tr("2021"));
|
||||
m_ui.copyright->setText(copyright);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>© 2013 – 2020 Jeffrey Pfau, licensed under the Mozilla Public License, version 2.0
|
||||
<string>© 2013 – {year} Jeffrey Pfau, licensed under the Mozilla Public License, version 2.0
|
||||
Game Boy Advance is a registered trademark of Nintendo Co., Ltd.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
|
|
|
@ -20,7 +20,7 @@ void AssetInfo::addCustomProperty(const QString& id, const QString& visibleName)
|
|||
QHBoxLayout* newLayout = new QHBoxLayout;
|
||||
newLayout->addWidget(new QLabel(visibleName));
|
||||
QLabel* value = new QLabel;
|
||||
value->setFont(GBAApp::monospaceFont());
|
||||
value->setFont(GBAApp::app()->monospaceFont());
|
||||
value->setAlignment(Qt::AlignRight);
|
||||
newLayout->addWidget(value);
|
||||
m_customProperties[id] = value;
|
||||
|
|
|
@ -31,7 +31,7 @@ AssetTile::AssetTile(QWidget* parent)
|
|||
|
||||
connect(m_ui.preview, &Swatch::indexPressed, this, &AssetTile::selectColor);
|
||||
|
||||
const QFont font = GBAApp::monospaceFont();
|
||||
const QFont font = GBAApp::app()->monospaceFont();
|
||||
|
||||
m_ui.tileId->setFont(font);
|
||||
m_ui.paletteId->setFont(font);
|
||||
|
|
|
@ -40,20 +40,18 @@ void AudioDevice::setInput(mCoreThread* input) {
|
|||
}
|
||||
|
||||
qint64 AudioDevice::readData(char* data, qint64 maxSize) {
|
||||
if (maxSize > 0xFFFFFFFFLL) {
|
||||
maxSize = 0xFFFFFFFFLL;
|
||||
}
|
||||
|
||||
if (!m_context->core) {
|
||||
LOG(QT, WARN) << tr("Audio device is missing its core");
|
||||
return 0;
|
||||
}
|
||||
|
||||
maxSize /= sizeof(GBAStereoSample);
|
||||
mCoreSyncLockAudio(&m_context->impl->sync);
|
||||
int available = blip_samples_avail(m_context->core->getAudioChannel(m_context->core, 0));
|
||||
if (available > maxSize / sizeof(GBAStereoSample)) {
|
||||
available = maxSize / sizeof(GBAStereoSample);
|
||||
}
|
||||
int available = std::min<qint64>({
|
||||
blip_samples_avail(m_context->core->getAudioChannel(m_context->core, 0)),
|
||||
maxSize,
|
||||
std::numeric_limits<int>::max()
|
||||
});
|
||||
blip_read_samples(m_context->core->getAudioChannel(m_context->core, 0), &reinterpret_cast<GBAStereoSample*>(data)->left, available, true);
|
||||
blip_read_samples(m_context->core->getAudioChannel(m_context->core, 1), &reinterpret_cast<GBAStereoSample*>(data)->right, available, true);
|
||||
mCoreSyncConsumeAudio(&m_context->impl->sync);
|
||||
|
|
|
@ -52,7 +52,7 @@ void AudioProcessorSDL::pause() {
|
|||
|
||||
void AudioProcessorSDL::setBufferSamples(int samples) {
|
||||
AudioProcessor::setBufferSamples(samples);
|
||||
if (m_audio.samples != samples) {
|
||||
if (m_audio.samples != static_cast<size_t>(samples)) {
|
||||
m_audio.samples = samples;
|
||||
if (m_audio.core) {
|
||||
mSDLDeinitAudio(&m_audio);
|
||||
|
|
|
@ -101,6 +101,7 @@ set(SOURCE_FILES
|
|||
ReportView.cpp
|
||||
ROMInfo.cpp
|
||||
RotatedHeaderView.cpp
|
||||
SaveConverter.cpp
|
||||
SavestateButton.cpp
|
||||
SensorView.cpp
|
||||
SettingsView.cpp
|
||||
|
@ -145,6 +146,7 @@ set(UI_FILES
|
|||
PrinterView.ui
|
||||
ReportView.ui
|
||||
ROMInfo.ui
|
||||
SaveConverter.ui
|
||||
SensorView.ui
|
||||
SettingsView.ui
|
||||
ShaderSelector.ui
|
||||
|
|
|
@ -20,7 +20,7 @@ CheatsModel::CheatsModel(mCheatDevice* device, QObject* parent)
|
|||
: QAbstractItemModel(parent)
|
||||
, m_device(device)
|
||||
{
|
||||
m_font = GBAApp::monospaceFont();
|
||||
m_font = GBAApp::app()->monospaceFont();
|
||||
}
|
||||
|
||||
QVariant CheatsModel::data(const QModelIndex& index, int role) const {
|
||||
|
|
|
@ -23,7 +23,7 @@ ConfigOption::ConfigOption(const QString& name, QObject* parent)
|
|||
|
||||
void ConfigOption::connect(std::function<void(const QVariant&)> slot, QObject* parent) {
|
||||
m_slots[parent] = slot;
|
||||
QObject::connect(parent, &QObject::destroyed, [this, slot, parent]() {
|
||||
QObject::connect(parent, &QObject::destroyed, this, [this, parent]() {
|
||||
m_slots.remove(parent);
|
||||
});
|
||||
}
|
||||
|
@ -37,10 +37,10 @@ Action* ConfigOption::addValue(const QString& text, const QVariant& value, Actio
|
|||
if (actions) {
|
||||
action = actions->addAction(text, name, function, menu);
|
||||
} else {
|
||||
action = new Action(function, name, text);
|
||||
action = new Action(function, name, text, this);
|
||||
}
|
||||
action->setExclusive();
|
||||
QObject::connect(action, &QObject::destroyed, [this, action, value]() {
|
||||
QObject::connect(action, &QObject::destroyed, this, [this, action, value]() {
|
||||
m_actions.removeAll(std::make_pair(action, value));
|
||||
});
|
||||
m_actions.append(std::make_pair(action, value));
|
||||
|
@ -59,10 +59,10 @@ Action* ConfigOption::addBoolean(const QString& text, ActionMapper* actions, con
|
|||
if (actions) {
|
||||
action = actions->addBooleanAction(text, m_name, function, menu);
|
||||
} else {
|
||||
action = new Action(function, m_name, text);
|
||||
action = new Action(function, m_name, text, this);
|
||||
}
|
||||
|
||||
QObject::connect(action, &QObject::destroyed, [this, action]() {
|
||||
QObject::connect(action, &QObject::destroyed, this, [this, action]() {
|
||||
m_actions.removeAll(std::make_pair(action, 1));
|
||||
});
|
||||
m_actions.append(std::make_pair(action, 1));
|
||||
|
@ -103,7 +103,7 @@ ConfigController::ConfigController(QObject* parent)
|
|||
QString fileName = configDir();
|
||||
fileName.append(QDir::separator());
|
||||
fileName.append("qt.ini");
|
||||
m_settings = new QSettings(fileName, QSettings::IniFormat, this);
|
||||
m_settings = std::make_unique<QSettings>(fileName, QSettings::IniFormat);
|
||||
|
||||
mCoreConfigInit(&m_config, PORT);
|
||||
|
||||
|
@ -150,7 +150,7 @@ ConfigOption* ConfigController::addOption(const char* key) {
|
|||
}
|
||||
ConfigOption* newOption = new ConfigOption(optionName, this);
|
||||
m_optionSet[optionName] = newOption;
|
||||
connect(newOption, &ConfigOption::valueChanged, [this, key](const QVariant& value) {
|
||||
connect(newOption, &ConfigOption::valueChanged, this, [this, key](const QVariant& value) {
|
||||
setOption(key, value);
|
||||
});
|
||||
return newOption;
|
||||
|
@ -272,6 +272,9 @@ void ConfigController::setMRU(const QList<QString>& mru) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
for (; i < MRU_LIST_SIZE; ++i) {
|
||||
m_settings->remove(QString::number(i));
|
||||
}
|
||||
m_settings->endGroup();
|
||||
}
|
||||
|
||||
|
@ -289,12 +292,11 @@ void ConfigController::makePortable() {
|
|||
QString fileName(configDir());
|
||||
fileName.append(QDir::separator());
|
||||
fileName.append("qt.ini");
|
||||
QSettings* settings2 = new QSettings(fileName, QSettings::IniFormat, this);
|
||||
auto settings2 = std::make_unique<QSettings>(fileName, QSettings::IniFormat);
|
||||
for (const auto& key : m_settings->allKeys()) {
|
||||
settings2->setValue(key, m_settings->value(key));
|
||||
}
|
||||
delete m_settings;
|
||||
m_settings = settings2;
|
||||
m_settings = std::move(settings2);
|
||||
}
|
||||
|
||||
bool ConfigController::isPortable() {
|
||||
|
|
|
@ -7,12 +7,13 @@
|
|||
|
||||
#include "Override.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QSettings>
|
||||
#include <QVariant>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <mgba/core/config.h>
|
||||
#include <mgba-util/configuration.h>
|
||||
|
@ -53,7 +54,7 @@ signals:
|
|||
void valueChanged(const QVariant& value);
|
||||
|
||||
private:
|
||||
QMap<QObject*, std::function<void(const QVariant&)>> m_slots;
|
||||
QHash<QObject*, std::function<void(const QVariant&)>> m_slots;
|
||||
QList<std::pair<Action*, QVariant>> m_actions;
|
||||
QString m_name;
|
||||
};
|
||||
|
@ -110,8 +111,8 @@ private:
|
|||
mCoreConfig m_config;
|
||||
mCoreOptions m_opts{};
|
||||
|
||||
QMap<QString, ConfigOption*> m_optionSet;
|
||||
QSettings* m_settings;
|
||||
QHash<QString, ConfigOption*> m_optionSet;
|
||||
std::unique_ptr<QSettings> m_settings;
|
||||
static QString s_configDir;
|
||||
};
|
||||
|
||||
|
|
|
@ -14,9 +14,6 @@
|
|||
#ifdef M_CORE_GBA
|
||||
#include <mgba/gba/core.h>
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
#include <mgba/gb/core.h>
|
||||
#endif
|
||||
|
||||
#include <mgba/core/core.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
@ -31,57 +28,6 @@ void CoreManager::setMultiplayerController(MultiplayerController* multiplayer) {
|
|||
m_multiplayer = multiplayer;
|
||||
}
|
||||
|
||||
QByteArray CoreManager::getExtdata(const QString& filename, mStateExtdataTag extdataType) {
|
||||
VFileDevice vf(filename, QIODevice::ReadOnly);
|
||||
|
||||
if (!vf.isOpen()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
mStateExtdata extdata;
|
||||
mStateExtdataInit(&extdata);
|
||||
|
||||
QByteArray bytes;
|
||||
auto extract = [&bytes, &extdata, &vf, extdataType](mCore* core) -> bool {
|
||||
if (mCoreExtractExtdata(core, vf, &extdata)) {
|
||||
mStateExtdataItem extitem;
|
||||
if (!mStateExtdataGet(&extdata, extdataType, &extitem)) {
|
||||
return false;
|
||||
}
|
||||
if (extitem.size) {
|
||||
bytes = QByteArray::fromRawData(static_cast<const char*>(extitem.data), extitem.size);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
bool done = false;
|
||||
struct mCore* core = nullptr;
|
||||
#ifdef USE_PNG
|
||||
done = extract(nullptr);
|
||||
#endif
|
||||
#ifdef M_CORE_GBA
|
||||
if (!done) {
|
||||
core = GBACoreCreate();
|
||||
core->init(core);
|
||||
done = extract(core);
|
||||
core->deinit(core);
|
||||
}
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
if (!done) {
|
||||
core = GBCoreCreate();
|
||||
core->init(core);
|
||||
done = extract(core);
|
||||
core->deinit(core);
|
||||
}
|
||||
#endif
|
||||
|
||||
mStateExtdataDeinit(&extdata);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
CoreController* CoreManager::loadGame(const QString& path) {
|
||||
QFileInfo info(path);
|
||||
if (!info.isReadable()) {
|
||||
|
|
|
@ -9,8 +9,6 @@
|
|||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include <mgba/core/serialize.h>
|
||||
|
||||
struct mCoreConfig;
|
||||
struct VFile;
|
||||
|
||||
|
@ -27,8 +25,6 @@ public:
|
|||
void setMultiplayerController(MultiplayerController*);
|
||||
void setPreload(bool preload) { m_preload = preload; }
|
||||
|
||||
static QByteArray getExtdata(const QString& filename, mStateExtdataTag extdataType);
|
||||
|
||||
public slots:
|
||||
CoreController* loadGame(const QString& path);
|
||||
CoreController* loadGame(VFile* vf, const QString& path, const QString& base);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "DisplayQt.h"
|
||||
|
||||
#include "CoreController.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
|
@ -98,26 +99,7 @@ void DisplayQt::paintEvent(QPaintEvent*) {
|
|||
if (isFiltered()) {
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
}
|
||||
// TODO: Refactor this code out (since it's copied in like 3 places)
|
||||
QSize s = size();
|
||||
QSize ds = viewportSize();
|
||||
if (isAspectRatioLocked()) {
|
||||
if (s.width() * m_height > s.height() * m_width) {
|
||||
ds.setWidth(s.height() * m_width / m_height);
|
||||
} else if (s.width() * m_height < s.height() * m_width) {
|
||||
ds.setHeight(s.width() * m_height / m_width);
|
||||
}
|
||||
}
|
||||
if (isIntegerScalingLocked()) {
|
||||
if (ds.width() >= m_width) {
|
||||
ds.setWidth(ds.width() - ds.width() % m_width);
|
||||
}
|
||||
if (ds.height() >= m_height) {
|
||||
ds.setHeight(ds.height() - ds.height() % m_height);
|
||||
}
|
||||
}
|
||||
QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2);
|
||||
QRect full(origin, ds);
|
||||
QRect full(clampSize(QSize(m_width, m_height), size(), isAspectRatioLocked(), isIntegerScalingLocked()));
|
||||
|
||||
if (hasInterframeBlending()) {
|
||||
painter.drawImage(full, m_oldBacking, QRect(0, 0, m_width, m_height));
|
||||
|
|
|
@ -83,7 +83,12 @@ FrameView::FrameView(std::shared_ptr<CoreController> controller, QWidget* parent
|
|||
FrameView::~FrameView() {
|
||||
QMutexLocker locker(&m_mutex);
|
||||
*m_callbackLocker = false;
|
||||
|
||||
if (m_nextFrame) {
|
||||
m_controller->endVideoLog(true);
|
||||
}
|
||||
if (m_vl) {
|
||||
mCoreConfigDeinit(&m_vl->config);
|
||||
m_vl->deinit(m_vl);
|
||||
}
|
||||
}
|
||||
|
@ -517,6 +522,9 @@ bool FrameView::eventFilter(QObject*, QEvent* event) {
|
|||
|
||||
void FrameView::refreshVl() {
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (m_currentFrame) {
|
||||
m_currentFrame->close(m_currentFrame);
|
||||
}
|
||||
m_currentFrame = m_nextFrame;
|
||||
m_nextFrame = VFileDevice::openMemory();
|
||||
if (m_currentFrame) {
|
||||
|
@ -536,6 +544,7 @@ void FrameView::newVl() {
|
|||
return;
|
||||
}
|
||||
if (m_vl) {
|
||||
mCoreConfigDeinit(&m_vl->config);
|
||||
m_vl->deinit(m_vl);
|
||||
}
|
||||
m_vl = mCoreFindVF(m_currentFrame);
|
||||
|
|
|
@ -36,14 +36,12 @@ static GBAApp* g_app = nullptr;
|
|||
|
||||
mLOG_DEFINE_CATEGORY(QT, "Qt", "platform.qt");
|
||||
|
||||
QFont GBAApp::s_monospace;
|
||||
|
||||
GBAApp::GBAApp(int& argc, char* argv[], ConfigController* config)
|
||||
: QApplication(argc, argv)
|
||||
, m_configController(config)
|
||||
, m_monospace(QFontDatabase::systemFont(QFontDatabase::FixedFont))
|
||||
{
|
||||
g_app = this;
|
||||
s_monospace = QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
SDL_Init(SDL_INIT_NOPARACHUTE);
|
||||
|
|
|
@ -57,7 +57,7 @@ public:
|
|||
|
||||
static QString dataDir();
|
||||
|
||||
static QFont monospaceFont() { return s_monospace; }
|
||||
QFont monospaceFont() { return m_monospace; }
|
||||
|
||||
QList<Window*> windows() { return m_windows; }
|
||||
Window* newWindow();
|
||||
|
@ -114,7 +114,7 @@ private:
|
|||
QThreadPool m_workerThreads;
|
||||
qint64 m_nextJob = 1;
|
||||
|
||||
static QFont s_monospace;
|
||||
QFont m_monospace;
|
||||
|
||||
NoIntroDB* m_db = nullptr;
|
||||
};
|
||||
|
|
|
@ -1594,7 +1594,7 @@ IOViewer::IOViewer(std::shared_ptr<CoreController> controller, QWidget* parent)
|
|||
m_ui.regSelect->addItem("0x" + QString("%1: %2").arg((i << m_width) + m_base, 4, 16, QChar('0')).toUpper().arg(reg), i << m_width);
|
||||
}
|
||||
|
||||
const QFont font = GBAApp::monospaceFont();
|
||||
const QFont font = GBAApp::app()->monospaceFont();
|
||||
m_ui.regValue->setFont(font);
|
||||
|
||||
connect(m_ui.buttonBox, &QDialogButtonBox::clicked, this, &IOViewer::buttonPressed);
|
||||
|
|
|
@ -200,7 +200,7 @@ void LoadSaveState::loadState(int slot) {
|
|||
unsigned width, height;
|
||||
thread->core->desiredVideoDimensions(thread->core, &width, &height);
|
||||
mStateExtdataItem item;
|
||||
if (mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item) && item.size >= width * height * 4) {
|
||||
if (mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item) && item.size >= static_cast<int32_t>(width * height * 4)) {
|
||||
stateImage = QImage((uchar*) item.data, width, height, QImage::Format_ARGB32).rgbSwapped();
|
||||
}
|
||||
|
||||
|
|
|
@ -109,10 +109,10 @@ MapView::MapView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
|||
}
|
||||
|
||||
void MapView::selectMap(int map) {
|
||||
if (map >= mMapCacheSetSize(&m_cacheSet->maps)) {
|
||||
if (map == m_map || map < 0) {
|
||||
return;
|
||||
}
|
||||
if (map == m_map) {
|
||||
if (static_cast<unsigned>(map) >= mMapCacheSetSize(&m_cacheSet->maps)) {
|
||||
return;
|
||||
}
|
||||
m_map = map;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "CoreController.h"
|
||||
#include "LogController.h"
|
||||
#include "VFileDevice.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
|
@ -27,7 +28,7 @@ using namespace QGBA;
|
|||
MemoryModel::MemoryModel(QWidget* parent)
|
||||
: QAbstractScrollArea(parent)
|
||||
{
|
||||
m_font = GBAApp::monospaceFont();
|
||||
m_font = GBAApp::app()->monospaceFont();
|
||||
#ifdef Q_OS_MAC
|
||||
m_font.setPointSize(12);
|
||||
#else
|
||||
|
@ -346,7 +347,7 @@ void MemoryModel::paintEvent(QPaintEvent*) {
|
|||
int height = (viewport()->size().height() - m_cellHeight) / m_cellHeight;
|
||||
for (int y = 0; y < height; ++y) {
|
||||
int yp = m_cellHeight * y + m_margins.top();
|
||||
if ((y + m_top) * 16 >= m_size) {
|
||||
if ((y + m_top) * 16U >= m_size) {
|
||||
break;
|
||||
}
|
||||
QString data;
|
||||
|
@ -628,11 +629,7 @@ void MemoryModel::keyPressEvent(QKeyEvent* event) {
|
|||
}
|
||||
|
||||
void MemoryModel::boundsCheck() {
|
||||
if (m_top < 0) {
|
||||
m_top = 0;
|
||||
} else if (m_top > (m_size >> 4) + 1 - viewport()->size().height() / m_cellHeight) {
|
||||
m_top = (m_size >> 4) + 1 - viewport()->size().height() / m_cellHeight;
|
||||
}
|
||||
m_top = clamp(m_top, 0, static_cast<int32_t>(m_size >> 4) + 1 - viewport()->size().height() / m_cellHeight);
|
||||
}
|
||||
|
||||
bool MemoryModel::isInSelection(uint32_t address) {
|
||||
|
@ -676,34 +673,35 @@ void MemoryModel::adjustCursor(int adjust, bool shift) {
|
|||
}
|
||||
int cursorPosition = m_top;
|
||||
if (shift) {
|
||||
uint32_t absolute;
|
||||
if (m_selectionAnchor == m_selection.first) {
|
||||
if (adjust < 0 && m_base - adjust > m_selection.second) {
|
||||
adjust = m_base - m_selection.second + m_align;
|
||||
absolute = m_base - m_selection.second + m_align;
|
||||
} else if (adjust > 0 && m_selection.second + adjust >= m_base + m_size) {
|
||||
adjust = m_base + m_size - m_selection.second;
|
||||
absolute = m_base + m_size - m_selection.second;
|
||||
}
|
||||
adjust += m_selection.second;
|
||||
if (adjust <= m_selection.first) {
|
||||
absolute += m_selection.second;
|
||||
if (absolute <= m_selection.first) {
|
||||
m_selection.second = m_selection.first + m_align;
|
||||
m_selection.first = adjust - m_align;
|
||||
m_selection.first = absolute - m_align;
|
||||
cursorPosition = m_selection.first;
|
||||
} else {
|
||||
m_selection.second = adjust;
|
||||
m_selection.second = absolute;
|
||||
cursorPosition = m_selection.second - m_align;
|
||||
}
|
||||
} else {
|
||||
if (adjust < 0 && m_base - adjust > m_selection.first) {
|
||||
adjust = m_base - m_selection.first;
|
||||
absolute = m_base - m_selection.first;
|
||||
} else if (adjust > 0 && m_selection.first + adjust >= m_base + m_size) {
|
||||
adjust = m_base + m_size - m_selection.first - m_align;
|
||||
absolute = m_base + m_size - m_selection.first - m_align;
|
||||
}
|
||||
adjust += m_selection.first;
|
||||
if (adjust >= m_selection.second) {
|
||||
absolute += m_selection.first;
|
||||
if (absolute >= m_selection.second) {
|
||||
m_selection.first = m_selection.second - m_align;
|
||||
m_selection.second = adjust + m_align;
|
||||
cursorPosition = adjust;
|
||||
m_selection.second = absolute + m_align;
|
||||
cursorPosition = absolute;
|
||||
} else {
|
||||
m_selection.first = adjust;
|
||||
m_selection.first = absolute;
|
||||
cursorPosition = m_selection.first;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -256,7 +256,7 @@ void MemoryView::updateSelection(uint32_t start, uint32_t end) {
|
|||
}
|
||||
|
||||
void MemoryView::updateStatus() {
|
||||
int align = m_ui.hexfield->alignment();
|
||||
unsigned align = m_ui.hexfield->alignment();
|
||||
mCore* core = m_controller->thread()->core;
|
||||
QByteArray selection(m_ui.hexfield->serialize());
|
||||
QString text(m_ui.hexfield->decodeText(selection));
|
||||
|
|
|
@ -16,7 +16,7 @@ using namespace QGBA;
|
|||
MessagePainter::MessagePainter(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
m_messageFont = GBAApp::monospaceFont();
|
||||
m_messageFont = GBAApp::app()->monospaceFont();
|
||||
m_messageFont.setPixelSize(13);
|
||||
connect(&m_messageTimer, &QTimer::timeout, this, &MessagePainter::clearMessage);
|
||||
m_messageTimer.setSingleShot(true);
|
||||
|
|
|
@ -33,7 +33,7 @@ ObjView::ObjView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
|||
m_ui.setupUi(this);
|
||||
m_ui.tile->setController(controller);
|
||||
|
||||
const QFont font = GBAApp::monospaceFont();
|
||||
const QFont font = GBAApp::app()->monospaceFont();
|
||||
|
||||
m_ui.x->setFont(font);
|
||||
m_ui.y->setFont(font);
|
||||
|
@ -133,8 +133,8 @@ void ObjView::updateTilesGBA(bool force) {
|
|||
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, newInfo.paletteSet);
|
||||
|
||||
int i = 0;
|
||||
for (int y = 0; y < newInfo.height; ++y) {
|
||||
for (int x = 0; x < newInfo.width; ++x, ++i, ++tile, ++tileBase) {
|
||||
for (unsigned y = 0; y < newInfo.height; ++y) {
|
||||
for (unsigned x = 0; x < newInfo.width; ++x, ++i, ++tile, ++tileBase) {
|
||||
const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[16 * tileBase], tile, newInfo.paletteId);
|
||||
if (data) {
|
||||
m_ui.tiles->setTile(i, data);
|
||||
|
@ -224,7 +224,7 @@ void ObjView::updateTilesGB(bool force) {
|
|||
|
||||
int i = 0;
|
||||
m_ui.tile->setPalette(newInfo.paletteId);
|
||||
for (int y = 0; y < newInfo.height; ++y, ++i) {
|
||||
for (unsigned y = 0; y < newInfo.height; ++y, ++i) {
|
||||
unsigned t = tile + i;
|
||||
const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[8 * t], t, newInfo.paletteId);
|
||||
if (data) {
|
||||
|
|
|
@ -47,7 +47,7 @@ PaletteView::PaletteView(std::shared_ptr<CoreController> controller, QWidget* pa
|
|||
m_ui.selected->setDimensions(QSize(1, 1));
|
||||
updatePalette();
|
||||
|
||||
const QFont font = GBAApp::monospaceFont();
|
||||
const QFont font = GBAApp::app()->monospaceFont();
|
||||
|
||||
m_ui.hexcode->setFont(font);
|
||||
m_ui.value->setFont(font);
|
||||
|
@ -69,7 +69,7 @@ void PaletteView::updatePalette() {
|
|||
return;
|
||||
}
|
||||
const uint16_t* palette;
|
||||
size_t count;
|
||||
int count;
|
||||
switch (m_controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA:
|
||||
|
|
|
@ -74,7 +74,7 @@ RegisterView::RegisterView(std::shared_ptr<CoreController> controller, QWidget*
|
|||
|
||||
void RegisterView::addRegisters(const QStringList& names) {
|
||||
QFormLayout* form = static_cast<QFormLayout*>(layout());
|
||||
const QFont font = GBAApp::monospaceFont();
|
||||
const QFont font = GBAApp::app()->monospaceFont();
|
||||
for (const auto& reg : names) {
|
||||
QLabel* value = new QLabel;
|
||||
value->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
|
|
|
@ -12,7 +12,9 @@
|
|||
#include <QSysInfo>
|
||||
#include <QWindow>
|
||||
|
||||
#include <mgba/core/serialize.h>
|
||||
#include <mgba/core/version.h>
|
||||
#include <mgba-util/png-io.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
#include "CoreController.h"
|
||||
|
@ -37,8 +39,43 @@
|
|||
#include <QOpenGLFunctions>
|
||||
#endif
|
||||
|
||||
#ifdef USE_EDITLINE
|
||||
#include <histedit.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_FFMPEG
|
||||
#include <libavcodec/version.h>
|
||||
#include <libavfilter/version.h>
|
||||
#include <libavformat/version.h>
|
||||
#include <libavutil/version.h>
|
||||
#include <libswscale/version.h>
|
||||
#ifdef USE_LIBAVRESAMPLE
|
||||
#include <libavresample/version.h>
|
||||
#endif
|
||||
#ifdef USE_LIBSWRESAMPLE
|
||||
#include <libswresample/version.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIBZIP
|
||||
#include <zip.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_LZMA
|
||||
#include <7zVersion.h>
|
||||
#endif
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
#include <SDL_version.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_SQLITE3
|
||||
#include "feature/sqlite3/no-intro.h"
|
||||
#include <sqlite3.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_ZLIB
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
using namespace QGBA;
|
||||
|
@ -84,6 +121,72 @@ void ReportView::generateReport() {
|
|||
swReport << QString("Build architecture: %1").arg(QSysInfo::buildCpuArchitecture());
|
||||
swReport << QString("Run architecture: %1").arg(QSysInfo::currentCpuArchitecture());
|
||||
swReport << QString("Qt version: %1").arg(QLatin1String(qVersion()));
|
||||
#ifdef USE_FFMPEG
|
||||
QStringList libavVers;
|
||||
libavVers << QLatin1String(LIBAVCODEC_IDENT);
|
||||
libavVers << QLatin1String(LIBAVFILTER_IDENT);
|
||||
libavVers << QLatin1String(LIBAVFORMAT_IDENT);
|
||||
#ifdef USE_LIBAVRESAMPLE
|
||||
libavVers << QLatin1String(LIBAVRESAMPLE_IDENT);
|
||||
#endif
|
||||
libavVers << QLatin1String(LIBAVUTIL_IDENT);
|
||||
#ifdef USE_LIBSWRESAMPLE
|
||||
libavVers << QLatin1String(LIBSWRESAMPLE_IDENT);
|
||||
#endif
|
||||
libavVers << QLatin1String(LIBSWSCALE_IDENT);
|
||||
#ifdef USE_LIBAV
|
||||
swReport << QString("Libav versions: %1.%2").arg(libavVers.join(", "));
|
||||
#else
|
||||
swReport << QString("FFmpeg versions: %1.%2").arg(libavVers.join(", "));
|
||||
#endif
|
||||
#else
|
||||
swReport << QString("FFmpeg not linked");
|
||||
#endif
|
||||
#ifdef USE_EDITLINE
|
||||
swReport << QString("libedit version: %1.%2").arg(LIBEDIT_MAJOR).arg(LIBEDIT_MINOR);
|
||||
#else
|
||||
swReport << QString("libedit not linked");
|
||||
#endif
|
||||
#ifdef USE_ELF
|
||||
swReport << QString("libelf linked");
|
||||
#else
|
||||
swReport << QString("libelf not linked");
|
||||
#endif
|
||||
#ifdef USE_PNG
|
||||
swReport << QString("libpng version: %1").arg(QLatin1String(PNG_LIBPNG_VER_STRING));
|
||||
#else
|
||||
swReport << QString("libpng not linked");
|
||||
#endif
|
||||
#ifdef USE_LIBZIP
|
||||
swReport << QString("libzip version: %1").arg(QLatin1String(LIBZIP_VERSION));
|
||||
#else
|
||||
swReport << QString("libzip not linked");
|
||||
#endif
|
||||
#ifdef USE_LZMA
|
||||
swReport << QString("libLZMA version: %1").arg(QLatin1String(MY_VERSION_NUMBERS));
|
||||
#else
|
||||
swReport << QString("libLZMA not linked");
|
||||
#endif
|
||||
#ifdef USE_MINIZIP
|
||||
swReport << QString("minizip linked");
|
||||
#else
|
||||
swReport << QString("minizip not linked");
|
||||
#endif
|
||||
#ifdef BUILD_SDL
|
||||
swReport << QString("SDL version: %1.%2.%3").arg(SDL_MAJOR_VERSION).arg(SDL_MINOR_VERSION).arg(SDL_PATCHLEVEL);
|
||||
#else
|
||||
swReport << QString("SDL not linked");
|
||||
#endif
|
||||
#ifdef USE_SQLITE3
|
||||
swReport << QString("SQLite3 version: %1").arg(QLatin1String(SQLITE_VERSION));
|
||||
#else
|
||||
swReport << QString("SQLite3 not linked");
|
||||
#endif
|
||||
#ifdef USE_ZLIB
|
||||
swReport << QString("zlib version: %1").arg(QLatin1String(ZLIB_VERSION));
|
||||
#else
|
||||
swReport << QString("zlib not linked");
|
||||
#endif
|
||||
addReport(QString("System info"), swReport.join('\n'));
|
||||
|
||||
QStringList hwReport;
|
||||
|
@ -209,6 +312,7 @@ void ReportView::generateReport() {
|
|||
}
|
||||
|
||||
void ReportView::save() {
|
||||
#if defined(USE_LIBZIP) || defined(USE_MINIZIP)
|
||||
QString filename = GBAApp::app()->getSaveFileName(this, tr("Bug report archive"), tr("ZIP archive (*.zip)"));
|
||||
if (filename.isNull()) {
|
||||
return;
|
||||
|
@ -228,6 +332,7 @@ void ReportView::save() {
|
|||
vf.close();
|
||||
}
|
||||
zip->close(zip);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ReportView::setShownReport(const QString& filename) {
|
||||
|
@ -243,7 +348,9 @@ void ReportView::rebuildModel() {
|
|||
}
|
||||
m_ui.fileList->addItem(item);
|
||||
}
|
||||
#if defined(USE_LIBZIP) || defined(USE_MINIZIP)
|
||||
m_ui.save->setEnabled(true);
|
||||
#endif
|
||||
m_ui.fileList->setEnabled(true);
|
||||
m_ui.fileView->setEnabled(true);
|
||||
m_ui.openList->setEnabled(true);
|
||||
|
|
|
@ -0,0 +1,658 @@
|
|||
/* Copyright (c) 2013-2021 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 "SaveConverter.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "GBAApp.h"
|
||||
#include "LogController.h"
|
||||
#include "VFileDevice.h"
|
||||
#include "utils.h"
|
||||
|
||||
#ifdef M_CORE_GBA
|
||||
#include <mgba/gba/core.h>
|
||||
#include <mgba/internal/gba/serialize.h>
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
#include <mgba/gb/core.h>
|
||||
#include <mgba/internal/gb/serialize.h>
|
||||
#endif
|
||||
|
||||
#include <mgba-util/memory.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
SaveConverter::SaveConverter(std::shared_ptr<CoreController> controller, QWidget* parent)
|
||||
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint)
|
||||
, m_controller(controller)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
connect(m_ui.inputFile, &QLineEdit::textEdited, this, &SaveConverter::refreshInputTypes);
|
||||
connect(m_ui.inputBrowse, &QAbstractButton::clicked, this, [this]() {
|
||||
// TODO: Add gameshark saves here too
|
||||
QStringList formats{"*.sav", "*.sgm", "*.ss0", "*.ss1", "*.ss2", "*.ss3", "*.ss4", "*.ss5", "*.ss6", "*.ss7", "*.ss8", "*.ss9"};
|
||||
QString filter = tr("Save games and save states (%1)").arg(formats.join(QChar(' ')));
|
||||
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select save game or save state"), filter);
|
||||
if (!filename.isEmpty()) {
|
||||
m_ui.inputFile->setText(filename);
|
||||
refreshInputTypes();
|
||||
}
|
||||
});
|
||||
connect(m_ui.inputType, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &SaveConverter::refreshOutputTypes);
|
||||
|
||||
connect(m_ui.outputFile, &QLineEdit::textEdited, this, &SaveConverter::checkCanConvert);
|
||||
connect(m_ui.outputBrowse, &QAbstractButton::clicked, this, [this]() {
|
||||
// TODO: Add gameshark saves here too
|
||||
QStringList formats{"*.sav", "*.sgm"};
|
||||
QString filter = tr("Save games (%1)").arg(formats.join(QChar(' ')));
|
||||
QString filename = GBAApp::app()->getSaveFileName(this, tr("Select save game"), filter);
|
||||
if (!filename.isEmpty()) {
|
||||
m_ui.outputFile->setText(filename);
|
||||
checkCanConvert();
|
||||
}
|
||||
});
|
||||
connect(m_ui.outputType, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &SaveConverter::checkCanConvert);
|
||||
connect(this, &QDialog::accepted, this, &SaveConverter::convert);
|
||||
|
||||
refreshInputTypes();
|
||||
m_ui.buttonBox->button(QDialogButtonBox::Save)->setDisabled(true);
|
||||
}
|
||||
|
||||
void SaveConverter::convert() {
|
||||
if (m_validSaves.isEmpty() || m_validOutputs.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
const AnnotatedSave& input = m_validSaves[m_ui.inputType->currentIndex()];
|
||||
const AnnotatedSave& output = m_validOutputs[m_ui.outputType->currentIndex()];
|
||||
QByteArray converted = input.convertTo(output);
|
||||
if (converted.isEmpty()) {
|
||||
QMessageBox* failure = new QMessageBox(QMessageBox::Warning, tr("Conversion failed"), tr("Failed to convert the save game. This is probably a bug."),
|
||||
QMessageBox::Ok, this, Qt::Sheet);
|
||||
failure->setAttribute(Qt::WA_DeleteOnClose);
|
||||
failure->show();
|
||||
return;
|
||||
}
|
||||
QFile out(m_ui.outputFile->text());
|
||||
out.open(QIODevice::WriteOnly | QIODevice::Truncate);
|
||||
out.write(converted);
|
||||
out.close();
|
||||
}
|
||||
|
||||
void SaveConverter::refreshInputTypes() {
|
||||
m_validSaves.clear();
|
||||
m_ui.inputType->clear();
|
||||
if (m_ui.inputFile->text().isEmpty()) {
|
||||
m_ui.inputType->addItem(tr("No file selected"));
|
||||
m_ui.inputType->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<VFileDevice> vf = std::make_shared<VFileDevice>(m_ui.inputFile->text(), QIODevice::ReadOnly);
|
||||
if (!vf->isOpen()) {
|
||||
m_ui.inputType->addItem(tr("Could not open file"));
|
||||
m_ui.inputType->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
detectFromSavestate(*vf);
|
||||
detectFromSize(vf);
|
||||
|
||||
for (const auto& save : m_validSaves) {
|
||||
m_ui.inputType->addItem(save);
|
||||
}
|
||||
if (m_validSaves.count()) {
|
||||
m_ui.inputType->setEnabled(true);
|
||||
} else {
|
||||
m_ui.inputType->addItem(tr("No valid formats found"));
|
||||
m_ui.inputType->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void SaveConverter::refreshOutputTypes() {
|
||||
m_ui.outputType->clear();
|
||||
if (m_validSaves.isEmpty()) {
|
||||
m_ui.outputType->addItem(tr("Please select a valid input file"));
|
||||
m_ui.outputType->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
m_validOutputs = m_validSaves[m_ui.inputType->currentIndex()].possibleConversions();
|
||||
for (const auto& save : m_validOutputs) {
|
||||
m_ui.outputType->addItem(save);
|
||||
}
|
||||
if (m_validOutputs.count()) {
|
||||
m_ui.outputType->setEnabled(true);
|
||||
} else {
|
||||
m_ui.outputType->addItem(tr("No valid conversions found"));
|
||||
m_ui.outputType->setEnabled(false);
|
||||
}
|
||||
checkCanConvert();
|
||||
}
|
||||
|
||||
void SaveConverter::checkCanConvert() {
|
||||
QAbstractButton* button = m_ui.buttonBox->button(QDialogButtonBox::Save);
|
||||
if (m_ui.inputFile->text().isEmpty()) {
|
||||
button->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
if (m_ui.outputFile->text().isEmpty()) {
|
||||
button->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
if (!m_ui.inputType->isEnabled()) {
|
||||
button->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
if (!m_ui.outputType->isEnabled()) {
|
||||
button->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
button->setEnabled(true);
|
||||
}
|
||||
|
||||
void SaveConverter::detectFromSavestate(VFile* vf) {
|
||||
mPlatform platform = getStatePlatform(vf);
|
||||
if (platform == mPLATFORM_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray extSavedata = getExtdata(vf, platform, EXTDATA_SAVEDATA);
|
||||
if (!extSavedata.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray state = getState(vf, platform);
|
||||
AnnotatedSave save{platform, std::make_shared<VFileDevice>(extSavedata)};
|
||||
switch (platform) {
|
||||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA:
|
||||
save.gba.type = static_cast<SavedataType>(state.at(offsetof(GBASerializedState, savedata.type)));
|
||||
if (save.gba.type == SAVEDATA_EEPROM || save.gba.type == SAVEDATA_EEPROM512) {
|
||||
save.endianness = Endian::LITTLE;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case mPLATFORM_GB:
|
||||
// GB savestates don't store the MBC type...should probably fix that
|
||||
save.gb.type = GB_MBC_AUTODETECT;
|
||||
if (state.size() == 0x100) {
|
||||
// MBC2 packed save
|
||||
save.endianness = Endian::LITTLE;
|
||||
save.gb.type = GB_MBC2;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
m_validSaves.append(save);
|
||||
}
|
||||
|
||||
void SaveConverter::detectFromSize(std::shared_ptr<VFileDevice> vf) {
|
||||
#ifdef M_CORE_GBA
|
||||
switch (vf->size()) {
|
||||
case SIZE_CART_SRAM:
|
||||
m_validSaves.append(AnnotatedSave{SAVEDATA_SRAM, vf});
|
||||
break;
|
||||
case SIZE_CART_FLASH512:
|
||||
m_validSaves.append(AnnotatedSave{SAVEDATA_FLASH512, vf});
|
||||
break;
|
||||
case SIZE_CART_FLASH1M:
|
||||
m_validSaves.append(AnnotatedSave{SAVEDATA_FLASH1M, vf});
|
||||
break;
|
||||
case SIZE_CART_EEPROM:
|
||||
m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM, vf, Endian::LITTLE});
|
||||
m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM, vf, Endian::BIG});
|
||||
break;
|
||||
case SIZE_CART_EEPROM512:
|
||||
m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM512, vf, Endian::LITTLE});
|
||||
m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM512, vf, Endian::BIG});
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef M_CORE_GB
|
||||
switch (vf->size()) {
|
||||
case 0x800:
|
||||
case 0x82C:
|
||||
case 0x830:
|
||||
case 0x2000:
|
||||
case 0x202C:
|
||||
case 0x2030:
|
||||
case 0x8000:
|
||||
case 0x802C:
|
||||
case 0x8030:
|
||||
case 0x10000:
|
||||
case 0x1002C:
|
||||
case 0x10030:
|
||||
case 0x20000:
|
||||
case 0x2002C:
|
||||
case 0x20030:
|
||||
m_validSaves.append(AnnotatedSave{GB_MBC_AUTODETECT, vf});
|
||||
break;
|
||||
case 0x100:
|
||||
m_validSaves.append(AnnotatedSave{GB_MBC2, vf, Endian::LITTLE});
|
||||
m_validSaves.append(AnnotatedSave{GB_MBC2, vf, Endian::BIG});
|
||||
break;
|
||||
case 0x200:
|
||||
m_validSaves.append(AnnotatedSave{GB_MBC2, vf});
|
||||
break;
|
||||
case GB_SIZE_MBC6_FLASH: // Flash only
|
||||
case GB_SIZE_MBC6_FLASH + 0x8000: // Concatenated SRAM and flash
|
||||
m_validSaves.append(AnnotatedSave{GB_MBC6, vf});
|
||||
break;
|
||||
case 0x20:
|
||||
m_validSaves.append(AnnotatedSave{GB_TAMA5, vf});
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
mPlatform SaveConverter::getStatePlatform(VFile* vf) {
|
||||
uint32_t magic;
|
||||
void* state = nullptr;
|
||||
struct mCore* core = nullptr;
|
||||
mPlatform platform = mPLATFORM_NONE;
|
||||
#ifdef M_CORE_GBA
|
||||
if (platform == mPLATFORM_NONE) {
|
||||
core = GBACoreCreate();
|
||||
core->init(core);
|
||||
state = mCoreExtractState(core, vf, nullptr);
|
||||
core->deinit(core);
|
||||
if (state) {
|
||||
LOAD_32LE(magic, 0, state);
|
||||
if (magic - GBASavestateMagic <= GBASavestateVersion) {
|
||||
platform = mPLATFORM_GBA;
|
||||
}
|
||||
mappedMemoryFree(state, core->stateSize(core));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
if (platform == mPLATFORM_NONE) {
|
||||
core = GBCoreCreate();
|
||||
core->init(core);
|
||||
state = mCoreExtractState(core, vf, nullptr);
|
||||
core->deinit(core);
|
||||
if (state) {
|
||||
LOAD_32LE(magic, 0, state);
|
||||
if (magic - GBSavestateMagic <= GBSavestateVersion) {
|
||||
platform = mPLATFORM_GB;
|
||||
}
|
||||
mappedMemoryFree(state, core->stateSize(core));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return platform;
|
||||
}
|
||||
|
||||
QByteArray SaveConverter::getState(VFile* vf, mPlatform platform) {
|
||||
QByteArray bytes;
|
||||
struct mCore* core = mCoreCreate(platform);
|
||||
core->init(core);
|
||||
void* state = mCoreExtractState(core, vf, nullptr);
|
||||
if (state) {
|
||||
size_t size = core->stateSize(core);
|
||||
bytes = QByteArray::fromRawData(static_cast<const char*>(state), size);
|
||||
bytes.data(); // Trigger a deep copy before we delete the backing
|
||||
mappedMemoryFree(state, size);
|
||||
}
|
||||
core->deinit(core);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
QByteArray SaveConverter::getExtdata(VFile* vf, mPlatform platform, mStateExtdataTag extdataType) {
|
||||
mStateExtdata extdata;
|
||||
mStateExtdataInit(&extdata);
|
||||
QByteArray bytes;
|
||||
struct mCore* core = mCoreCreate(platform);
|
||||
core->init(core);
|
||||
if (mCoreExtractExtdata(core, vf, &extdata)) {
|
||||
mStateExtdataItem extitem;
|
||||
if (mStateExtdataGet(&extdata, extdataType, &extitem) && extitem.size) {
|
||||
bytes = QByteArray::fromRawData(static_cast<const char*>(extitem.data), extitem.size);
|
||||
bytes.data(); // Trigger a deep copy before we delete the backing
|
||||
}
|
||||
}
|
||||
core->deinit(core);
|
||||
mStateExtdataDeinit(&extdata);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
SaveConverter::AnnotatedSave::AnnotatedSave()
|
||||
: savestate(false)
|
||||
, platform(mPLATFORM_NONE)
|
||||
, size(0)
|
||||
, backing()
|
||||
, endianness(Endian::NONE)
|
||||
{
|
||||
}
|
||||
|
||||
SaveConverter::AnnotatedSave::AnnotatedSave(mPlatform platform, std::shared_ptr<VFileDevice> vf, Endian endianness)
|
||||
: savestate(true)
|
||||
, platform(platform)
|
||||
, size(vf->size())
|
||||
, backing(vf)
|
||||
, endianness(endianness)
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef M_CORE_GBA
|
||||
SaveConverter::AnnotatedSave::AnnotatedSave(SavedataType type, std::shared_ptr<VFileDevice> vf, Endian endianness)
|
||||
: savestate(false)
|
||||
, platform(mPLATFORM_GBA)
|
||||
, size(vf->size())
|
||||
, backing(vf)
|
||||
, endianness(endianness)
|
||||
, gba({type})
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef M_CORE_GB
|
||||
SaveConverter::AnnotatedSave::AnnotatedSave(GBMemoryBankControllerType type, std::shared_ptr<VFileDevice> vf, Endian endianness)
|
||||
: savestate(false)
|
||||
, platform(mPLATFORM_GB)
|
||||
, size(vf->size())
|
||||
, backing(vf)
|
||||
, endianness(endianness)
|
||||
, gb({type})
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
SaveConverter::AnnotatedSave SaveConverter::AnnotatedSave::asRaw() const {
|
||||
AnnotatedSave raw;
|
||||
raw.platform = platform;
|
||||
raw.size = size;
|
||||
raw.endianness = endianness;
|
||||
switch (platform) {
|
||||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA:
|
||||
raw.gba = gba;
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case mPLATFORM_GB:
|
||||
raw.gb = gb;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
SaveConverter::AnnotatedSave::operator QString() const {
|
||||
QString sizeStr(niceSizeFormat(size));
|
||||
QString typeFormat("%1");
|
||||
QString endianStr;
|
||||
QString saveType;
|
||||
QString format = QCoreApplication::translate("SaveConverter", "%1 %2 save game");
|
||||
|
||||
switch (endianness) {
|
||||
case Endian::LITTLE:
|
||||
endianStr = QCoreApplication::translate("SaveConverter", "little endian");
|
||||
break;
|
||||
case Endian::BIG:
|
||||
endianStr = QCoreApplication::translate("SaveConverter", "big endian");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (platform) {
|
||||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA:
|
||||
switch (gba.type) {
|
||||
case SAVEDATA_SRAM:
|
||||
typeFormat = QCoreApplication::translate("SaveConverter", "SRAM");
|
||||
break;
|
||||
case SAVEDATA_FLASH512:
|
||||
case SAVEDATA_FLASH1M:
|
||||
typeFormat = QCoreApplication::translate("SaveConverter", "%1 flash");
|
||||
break;
|
||||
case SAVEDATA_EEPROM:
|
||||
case SAVEDATA_EEPROM512:
|
||||
typeFormat = QCoreApplication::translate("SaveConverter", "%1 EEPROM");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case mPLATFORM_GB:
|
||||
switch (gb.type) {
|
||||
case GB_MBC_AUTODETECT:
|
||||
if (size & 0xFF) {
|
||||
typeFormat = QCoreApplication::translate("SaveConverter", "%1 SRAM + RTC");
|
||||
} else {
|
||||
typeFormat = QCoreApplication::translate("SaveConverter", "%1 SRAM");
|
||||
}
|
||||
break;
|
||||
case GB_MBC2:
|
||||
if (size == 0x100) {
|
||||
typeFormat = QCoreApplication::translate("SaveConverter", "packed MBC2");
|
||||
} else {
|
||||
typeFormat = QCoreApplication::translate("SaveConverter", "unpacked MBC2");
|
||||
}
|
||||
break;
|
||||
case GB_MBC6:
|
||||
if (size == GB_SIZE_MBC6_FLASH) {
|
||||
typeFormat = QCoreApplication::translate("SaveConverter", "MBC6 flash");
|
||||
} else if (size > GB_SIZE_MBC6_FLASH) {
|
||||
typeFormat = QCoreApplication::translate("SaveConverter", "MBC6 combined SRAM + flash");
|
||||
} else {
|
||||
typeFormat = QCoreApplication::translate("SaveConverter", "MBC6 SRAM");
|
||||
}
|
||||
break;
|
||||
case GB_TAMA5:
|
||||
typeFormat = QCoreApplication::translate("SaveConverter", "TAMA5");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
saveType = typeFormat.arg(sizeStr);
|
||||
if (!endianStr.isEmpty()) {
|
||||
saveType = QCoreApplication::translate("SaveConverter", "%1 (%2)").arg(saveType).arg(endianStr);
|
||||
}
|
||||
if (savestate) {
|
||||
format = QCoreApplication::translate("SaveConverter", "%1 save state with embedded %2 save game");
|
||||
}
|
||||
return format.arg(nicePlatformFormat(platform)).arg(saveType);
|
||||
}
|
||||
|
||||
bool SaveConverter::AnnotatedSave::operator==(const AnnotatedSave& other) const {
|
||||
if (other.savestate != savestate || other.platform != platform || other.size != size || other.endianness != endianness) {
|
||||
return false;
|
||||
}
|
||||
switch (platform) {
|
||||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA:
|
||||
if (other.gba.type != gba.type) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case mPLATFORM_GB:
|
||||
if (other.gb.type != gb.type) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QList<SaveConverter::AnnotatedSave> SaveConverter::AnnotatedSave::possibleConversions() const {
|
||||
QList<AnnotatedSave> possible;
|
||||
AnnotatedSave same = asRaw();
|
||||
same.backing.reset();
|
||||
same.savestate = false;
|
||||
|
||||
if (savestate) {
|
||||
possible.append(same);
|
||||
}
|
||||
|
||||
|
||||
AnnotatedSave endianSwapped = same;
|
||||
switch (endianness) {
|
||||
case Endian::LITTLE:
|
||||
endianSwapped.endianness = Endian::BIG;
|
||||
possible.append(endianSwapped);
|
||||
break;
|
||||
case Endian::BIG:
|
||||
endianSwapped.endianness = Endian::LITTLE;
|
||||
possible.append(endianSwapped);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (platform) {
|
||||
#ifdef M_CORE_GB
|
||||
case mPLATFORM_GB:
|
||||
switch (gb.type) {
|
||||
case GB_MBC2:
|
||||
if (size == 0x100) {
|
||||
AnnotatedSave unpacked = same;
|
||||
unpacked.size = 0x200;
|
||||
unpacked.endianness = Endian::NONE;
|
||||
possible.append(unpacked);
|
||||
} else {
|
||||
AnnotatedSave packed = same;
|
||||
packed.size = 0x100;
|
||||
packed.endianness = Endian::LITTLE;
|
||||
possible.append(packed);
|
||||
packed.endianness = Endian::BIG;
|
||||
possible.append(packed);
|
||||
}
|
||||
break;
|
||||
case GB_MBC6:
|
||||
if (size > GB_SIZE_MBC6_FLASH) {
|
||||
AnnotatedSave separated = same;
|
||||
separated.size = size - GB_SIZE_MBC6_FLASH;
|
||||
possible.append(separated);
|
||||
separated.size = GB_SIZE_MBC6_FLASH;
|
||||
possible.append(separated);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return possible;
|
||||
}
|
||||
|
||||
QByteArray SaveConverter::AnnotatedSave::convertTo(const SaveConverter::AnnotatedSave& target) const {
|
||||
QByteArray converted;
|
||||
QByteArray buffer;
|
||||
backing->seek(0);
|
||||
if (target == asRaw()) {
|
||||
return backing->readAll();
|
||||
}
|
||||
|
||||
if (platform != target.platform) {
|
||||
LOG(QT, ERROR) << tr("Cannot convert save games between platforms");
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (platform) {
|
||||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA:
|
||||
switch (gba.type) {
|
||||
case SAVEDATA_EEPROM:
|
||||
case SAVEDATA_EEPROM512:
|
||||
converted.resize(target.size);
|
||||
buffer = backing->readAll();
|
||||
for (int i = 0; i < size; i += 8) {
|
||||
uint64_t word;
|
||||
const uint64_t* in = reinterpret_cast<const uint64_t*>(buffer.constData());
|
||||
uint64_t* out = reinterpret_cast<uint64_t*>(converted.data());
|
||||
LOAD_64LE(word, i, in);
|
||||
STORE_64BE(word, i, out);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case mPLATFORM_GB:
|
||||
switch (gb.type) {
|
||||
case GB_MBC2:
|
||||
converted.reserve(target.size);
|
||||
buffer = backing->readAll();
|
||||
if (size == 0x100 && target.size == 0x200) {
|
||||
if (endianness == Endian::LITTLE) {
|
||||
for (uint8_t byte : buffer) {
|
||||
converted.append(0xF0 | (byte & 0xF));
|
||||
converted.append(0xF0 | (byte >> 4));
|
||||
}
|
||||
} else if (endianness == Endian::BIG) {
|
||||
for (uint8_t byte : buffer) {
|
||||
converted.append(0xF0 | (byte >> 4));
|
||||
converted.append(0xF0 | (byte & 0xF));
|
||||
}
|
||||
}
|
||||
} else if (size == 0x200 && target.size == 0x100) {
|
||||
uint8_t byte;
|
||||
if (target.endianness == Endian::LITTLE) {
|
||||
for (int i = 0; i < target.size; ++i) {
|
||||
byte = buffer[i * 2] & 0xF;
|
||||
byte |= (buffer[i * 2 + 1] & 0xF) << 4;
|
||||
converted.append(byte);
|
||||
}
|
||||
} else if (target.endianness == Endian::BIG) {
|
||||
for (int i = 0; i < target.size; ++i) {
|
||||
byte = (buffer[i * 2] & 0xF) << 4;
|
||||
byte |= buffer[i * 2 + 1] & 0xF;
|
||||
converted.append(byte);
|
||||
}
|
||||
}
|
||||
} else if (size == 0x100 && target.size == 0x100) {
|
||||
for (uint8_t byte : buffer) {
|
||||
converted.append((byte >> 4) | (byte << 4));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GB_MBC6:
|
||||
if (size == target.size + GB_SIZE_MBC6_FLASH) {
|
||||
converted = backing->read(target.size);
|
||||
} else if (target.size == GB_SIZE_MBC6_FLASH) {
|
||||
backing->seek(size - GB_SIZE_MBC6_FLASH);
|
||||
converted = backing->read(GB_SIZE_MBC6_FLASH);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return converted;
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/* Copyright (c) 2013-2021 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 <QDialog>
|
||||
|
||||
#include "CoreController.h"
|
||||
#include "utils.h"
|
||||
|
||||
#ifdef M_CORE_GBA
|
||||
#include <mgba/gba/core.h>
|
||||
#include <mgba/internal/gba/savedata.h>
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
#include <mgba/gb/core.h>
|
||||
#include <mgba/gb/interface.h>
|
||||
#endif
|
||||
|
||||
#include <mgba/core/serialize.h>
|
||||
|
||||
#include "ui_SaveConverter.h"
|
||||
|
||||
struct VFile;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class SaveConverter : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SaveConverter(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
|
||||
|
||||
static mPlatform getStatePlatform(VFile*);
|
||||
static QByteArray getState(VFile*, mPlatform);
|
||||
static QByteArray getExtdata(VFile*, mPlatform, mStateExtdataTag);
|
||||
|
||||
public slots:
|
||||
void convert();
|
||||
|
||||
private slots:
|
||||
void refreshInputTypes();
|
||||
void refreshOutputTypes();
|
||||
void checkCanConvert();
|
||||
|
||||
private:
|
||||
#ifdef M_CORE_GBA
|
||||
struct GBASave {
|
||||
SavedataType type;
|
||||
};
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
struct GBSave {
|
||||
GBMemoryBankControllerType type;
|
||||
};
|
||||
#endif
|
||||
struct AnnotatedSave {
|
||||
AnnotatedSave();
|
||||
AnnotatedSave(mPlatform, std::shared_ptr<VFileDevice>, Endian = Endian::NONE);
|
||||
#ifdef M_CORE_GBA
|
||||
AnnotatedSave(SavedataType, std::shared_ptr<VFileDevice>, Endian = Endian::NONE);
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
AnnotatedSave(GBMemoryBankControllerType, std::shared_ptr<VFileDevice>, Endian = Endian::NONE);
|
||||
#endif
|
||||
|
||||
AnnotatedSave asRaw() const;
|
||||
operator QString() const;
|
||||
bool operator==(const AnnotatedSave&) const;
|
||||
|
||||
QList<AnnotatedSave> possibleConversions() const;
|
||||
QByteArray convertTo(const AnnotatedSave&) const;
|
||||
|
||||
bool savestate;
|
||||
mPlatform platform;
|
||||
ssize_t size;
|
||||
std::shared_ptr<VFileDevice> backing;
|
||||
Endian endianness;
|
||||
union {
|
||||
#ifdef M_CORE_GBA
|
||||
GBASave gba;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
GBSave gb;
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
||||
void detectFromSavestate(VFile*);
|
||||
void detectFromSize(std::shared_ptr<VFileDevice>);
|
||||
void detectFromHeaders(std::shared_ptr<VFileDevice>);
|
||||
|
||||
Ui::SaveConverter m_ui;
|
||||
|
||||
std::shared_ptr<CoreController> m_controller;
|
||||
QList<AnnotatedSave> m_validSaves;
|
||||
QList<AnnotatedSave> m_validOutputs;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SaveConverter</class>
|
||||
<widget class="QDialog" name="SaveConverter">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>546</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Convert/Extract Save Game</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Input file</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLineEdit" name="inputFile"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="inputBrowse">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QComboBox" name="inputType">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Output file</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLineEdit" name="outputFile"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="outputBrowse">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QComboBox" name="outputType">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>inputFile</tabstop>
|
||||
<tabstop>inputBrowse</tabstop>
|
||||
<tabstop>inputType</tabstop>
|
||||
<tabstop>outputFile</tabstop>
|
||||
<tabstop>outputBrowse</tabstop>
|
||||
<tabstop>outputType</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>SaveConverter</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>SaveConverter</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
|
@ -8,6 +8,7 @@
|
|||
#include "CoreController.h"
|
||||
#include "GamepadAxisEvent.h"
|
||||
#include "InputController.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <mgba/core/core.h>
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
|
@ -129,7 +130,7 @@ void SensorView::updateSensors() {
|
|||
}
|
||||
|
||||
void SensorView::setLuminanceValue(int value) {
|
||||
value = std::max(0, std::min(value, 255));
|
||||
value = clamp(value, 0, 255);
|
||||
if (m_input) {
|
||||
m_input->setLuminanceValue(value);
|
||||
}
|
||||
|
|
|
@ -883,7 +883,7 @@
|
|||
<item row="11" column="0">
|
||||
<widget class="QLabel" name="label_24">
|
||||
<property name="text">
|
||||
<string>Savestate extra data:</string>
|
||||
<string>Save state extra data:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -900,7 +900,7 @@
|
|||
<item row="12" column="1">
|
||||
<widget class="QCheckBox" name="saveStateSave">
|
||||
<property name="text">
|
||||
<string>Save data</string>
|
||||
<string>Save game</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
|
@ -927,7 +927,7 @@
|
|||
<item row="15" column="0">
|
||||
<widget class="QLabel" name="label_25">
|
||||
<property name="text">
|
||||
<string>Load extra data:</string>
|
||||
<string>Load state extra data:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -944,7 +944,7 @@
|
|||
<item row="16" column="1">
|
||||
<widget class="QCheckBox" name="loadStateSave">
|
||||
<property name="text">
|
||||
<string>Save data</string>
|
||||
<string>Save game</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "ShortcutController.h"
|
||||
#include "ShortcutModel.h"
|
||||
|
||||
#include <QFontMetrics>
|
||||
#include <QKeyEvent>
|
||||
|
||||
using namespace QGBA;
|
||||
|
@ -134,6 +135,15 @@ void ShortcutView::closeEvent(QCloseEvent*) {
|
|||
}
|
||||
}
|
||||
|
||||
void ShortcutView::showEvent(QShowEvent*) {
|
||||
QString longString("Ctrl+Alt+Shift+Tab");
|
||||
int width = QFontMetrics(QFont()).width(longString);
|
||||
QHeaderView* header = m_ui.shortcutTable->header();
|
||||
header->resizeSection(0, header->length() - width * 2);
|
||||
header->resizeSection(1, width);
|
||||
header->resizeSection(2, width);
|
||||
}
|
||||
|
||||
bool ShortcutView::event(QEvent* event) {
|
||||
if (m_input) {
|
||||
QEvent::Type type = event->type();
|
||||
|
|
|
@ -32,6 +32,7 @@ public:
|
|||
protected:
|
||||
virtual bool event(QEvent*) override;
|
||||
virtual void closeEvent(QCloseEvent*) override;
|
||||
virtual void showEvent(QShowEvent*) override;
|
||||
|
||||
private slots:
|
||||
void load(const QModelIndex&);
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTreeView" name="shortcutTable">
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOn</enum>
|
||||
</property>
|
||||
<attribute name="headerDefaultSectionSize">
|
||||
<number>120</number>
|
||||
</attribute>
|
||||
|
|
|
@ -91,6 +91,17 @@ VFileDevice::VFileDevice(const QString& filename, QIODevice::OpenMode mode, QObj
|
|||
}
|
||||
}
|
||||
|
||||
VFileDevice::VFileDevice(const QByteArray& mem, QObject* parent)
|
||||
: QIODevice(parent)
|
||||
, m_vf(VFileMemChunk(mem.constData(), mem.size()))
|
||||
{
|
||||
setOpenMode(QIODevice::ReadWrite);
|
||||
}
|
||||
|
||||
VFileDevice::~VFileDevice() {
|
||||
close();
|
||||
}
|
||||
|
||||
void VFileDevice::close() {
|
||||
if (!m_vf) {
|
||||
return;
|
||||
|
@ -117,6 +128,13 @@ VFileDevice& VFileDevice::operator=(VFile* vf) {
|
|||
return *this;
|
||||
}
|
||||
|
||||
VFile* VFileDevice::take() {
|
||||
VFile* vf = m_vf;
|
||||
m_vf = nullptr;
|
||||
QIODevice::close();
|
||||
return vf;
|
||||
}
|
||||
|
||||
qint64 VFileDevice::readData(char* data, qint64 maxSize) {
|
||||
return m_vf->read(m_vf, data, maxSize);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,9 @@ Q_OBJECT
|
|||
|
||||
public:
|
||||
VFileDevice(VFile* vf = nullptr, QObject* parent = nullptr);
|
||||
VFileDevice(const QByteArray& mem, QObject* parent = nullptr);
|
||||
VFileDevice(const QString&, QIODevice::OpenMode, QObject* parent = nullptr);
|
||||
virtual ~VFileDevice();
|
||||
|
||||
virtual void close() override;
|
||||
virtual bool seek(qint64 pos) override;
|
||||
|
@ -29,6 +31,7 @@ public:
|
|||
|
||||
VFileDevice& operator=(VFile*);
|
||||
operator VFile*() { return m_vf; }
|
||||
VFile* take();
|
||||
|
||||
static VFile* wrap(QIODevice*, QIODevice::OpenMode);
|
||||
static VFile* wrap(QFileDevice*, QIODevice::OpenMode);
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "GBAApp.h"
|
||||
#include "LogController.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <mgba-util/math.h>
|
||||
|
||||
|
@ -522,11 +523,7 @@ void VideoView::setPreset(const Preset& preset) {
|
|||
|
||||
QSize VideoView::maintainAspect(const QSize& size) {
|
||||
QSize ds = size;
|
||||
if (ds.width() * m_nativeHeight > ds.height() * m_nativeWidth) {
|
||||
ds.setWidth(ds.height() * m_nativeWidth / m_nativeHeight);
|
||||
} else if (ds.width() * m_nativeHeight < ds.height() * m_nativeWidth) {
|
||||
ds.setHeight(ds.width() * m_nativeHeight / m_nativeWidth);
|
||||
}
|
||||
lockAspectRatio(QSize(m_nativeWidth, m_nativeHeight), ds);
|
||||
return ds;
|
||||
}
|
||||
|
||||
|
|
|
@ -49,12 +49,14 @@
|
|||
#include "PrinterView.h"
|
||||
#include "ReportView.h"
|
||||
#include "ROMInfo.h"
|
||||
#include "SaveConverter.h"
|
||||
#include "SensorView.h"
|
||||
#include "ShaderSelector.h"
|
||||
#include "ShortcutController.h"
|
||||
#include "TileView.h"
|
||||
#include "VideoProxy.h"
|
||||
#include "VideoView.h"
|
||||
#include "utils.h"
|
||||
|
||||
#ifdef USE_DISCORD_RPC
|
||||
#include "DiscordCoordinator.h"
|
||||
|
@ -417,8 +419,8 @@ void Window::replaceROM() {
|
|||
|
||||
void Window::selectSave(bool temporary) {
|
||||
QStringList formats{"*.sav"};
|
||||
QString filter = tr("Game Boy Advance save files (%1)").arg(formats.join(QChar(' ')));
|
||||
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select save"), filter);
|
||||
QString filter = tr("Save games (%1)").arg(formats.join(QChar(' ')));
|
||||
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select save game"), filter);
|
||||
if (!filename.isEmpty()) {
|
||||
m_controller->loadSave(filename, temporary);
|
||||
}
|
||||
|
@ -426,14 +428,14 @@ void Window::selectSave(bool temporary) {
|
|||
|
||||
void Window::selectState(bool load) {
|
||||
QStringList formats{"*.ss0", "*.ss1", "*.ss2", "*.ss3", "*.ss4", "*.ss5", "*.ss6", "*.ss7", "*.ss8", "*.ss9"};
|
||||
QString filter = tr("mGBA savestate files (%1)").arg(formats.join(QChar(' ')));
|
||||
QString filter = tr("mGBA save state files (%1)").arg(formats.join(QChar(' ')));
|
||||
if (load) {
|
||||
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select savestate"), filter);
|
||||
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select save state"), filter);
|
||||
if (!filename.isEmpty()) {
|
||||
m_controller->loadState(filename);
|
||||
}
|
||||
} else {
|
||||
QString filename = GBAApp::app()->getSaveFileName(this, tr("Select savestate"), filter);
|
||||
QString filename = GBAApp::app()->getSaveFileName(this, tr("Select save state"), filter);
|
||||
if (!filename.isEmpty()) {
|
||||
m_controller->saveState(filename);
|
||||
}
|
||||
|
@ -1183,10 +1185,10 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
m_actions.addAction(tr("Add folder to library..."), "addDirToLibrary", this, &Window::addDirToLibrary, "file");
|
||||
#endif
|
||||
|
||||
addGameAction(tr("Load alternate save..."), "loadAlternateSave", [this]() {
|
||||
addGameAction(tr("Load alternate save game..."), "loadAlternateSave", [this]() {
|
||||
this->selectSave(false);
|
||||
}, "file");
|
||||
addGameAction(tr("Load temporary save..."), "loadTemporarySave", [this]() {
|
||||
addGameAction(tr("Load temporary save game..."), "loadTemporarySave", [this]() {
|
||||
this->selectSave(true);
|
||||
}, "file");
|
||||
|
||||
|
@ -1294,6 +1296,8 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
|
||||
#ifdef M_CORE_GBA
|
||||
m_actions.addSeparator("file");
|
||||
m_actions.addAction(tr("Convert save game..."), "convertSave", openControllerTView<SaveConverter>(), "file");
|
||||
|
||||
Action* importShark = addGameAction(tr("Import GameShark Save..."), "importShark", this, &Window::importSharkport, "file");
|
||||
m_platformActions.insert(mPLATFORM_GBA, importShark);
|
||||
|
||||
|
@ -1980,9 +1984,7 @@ void Window::attachDisplay() {
|
|||
void Window::setLogo() {
|
||||
m_screenWidget->setPixmap(m_logo);
|
||||
m_screenWidget->setCenteredAspectRatio(m_logo.width(), m_logo.height());
|
||||
m_screenWidget->setDimensions(m_logo.width(), m_logo.height());
|
||||
m_screenWidget->setLockIntegerScaling(false);
|
||||
m_screenWidget->setLockAspectRatio(true);
|
||||
m_screenWidget->filter(true);
|
||||
m_screenWidget->unsetCursor();
|
||||
}
|
||||
|
@ -2037,30 +2039,6 @@ void WindowBackground::paintEvent(QPaintEvent* event) {
|
|||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform, m_filter);
|
||||
painter.fillRect(QRect(QPoint(), size()), Qt::black);
|
||||
QSize s = size();
|
||||
QSize ds = s;
|
||||
if (m_centered) {
|
||||
if (ds.width() * m_aspectHeight < ds.height() * m_aspectWidth) {
|
||||
ds.setWidth(ds.height() * m_aspectWidth / m_aspectHeight);
|
||||
} else if (ds.width() * m_aspectHeight > ds.height() * m_aspectWidth) {
|
||||
ds.setHeight(ds.width() * m_aspectHeight / m_aspectWidth);
|
||||
}
|
||||
} else if (m_lockAspectRatio) {
|
||||
if (ds.width() * m_aspectHeight > ds.height() * m_aspectWidth) {
|
||||
ds.setWidth(ds.height() * m_aspectWidth / m_aspectHeight);
|
||||
} else if (ds.width() * m_aspectHeight < ds.height() * m_aspectWidth) {
|
||||
ds.setHeight(ds.width() * m_aspectHeight / m_aspectWidth);
|
||||
}
|
||||
}
|
||||
if (m_lockIntegerScaling) {
|
||||
if (ds.width() >= m_aspectWidth) {
|
||||
ds.setWidth(ds.width() - ds.width() % m_aspectWidth);
|
||||
}
|
||||
if (ds.height() >= m_aspectHeight) {
|
||||
ds.setHeight(ds.height() - ds.height() % m_aspectHeight);
|
||||
}
|
||||
}
|
||||
QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2);
|
||||
QRect full(origin, ds);
|
||||
QRect full(clampSize(QSize(m_aspectWidth, m_aspectHeight), size(), m_lockAspectRatio, m_lockIntegerScaling, m_centered));
|
||||
painter.drawPixmap(full, logo);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "InputModel.h"
|
||||
#include "InputProfile.h"
|
||||
#include "LogController.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QKeyEvent>
|
||||
|
@ -1002,7 +1003,7 @@ void InputController::decreaseLuminanceLevel() {
|
|||
|
||||
void InputController::setLuminanceLevel(int level) {
|
||||
int value = 0x16;
|
||||
level = std::max(0, std::min(10, level));
|
||||
level = clamp(level, 0, 10);
|
||||
if (level > 0) {
|
||||
value += GBA_LUX_LEVELS[level - 1];
|
||||
}
|
||||
|
|
|
@ -108,10 +108,10 @@ QPair<QString, QString> LibraryController::selectedPath() {
|
|||
return e ? qMakePair(e->base(), e->filename()) : qMakePair<QString, QString>("", "");
|
||||
}
|
||||
|
||||
void LibraryController::addDirectory(const QString& dir) {
|
||||
void LibraryController::addDirectory(const QString& dir, bool recursive) {
|
||||
// The worker thread temporarily owns the library
|
||||
std::shared_ptr<mLibrary> library = m_library;
|
||||
m_libraryJob = GBAApp::app()->submitWorkerJob(std::bind(&LibraryController::loadDirectory, this, dir), this, [this, library]() {
|
||||
m_libraryJob = GBAApp::app()->submitWorkerJob(std::bind(&LibraryController::loadDirectory, this, dir, recursive), this, [this, library]() {
|
||||
m_libraryJob = -1;
|
||||
refresh();
|
||||
});
|
||||
|
@ -181,10 +181,10 @@ void LibraryController::selectLastBootedGame() {
|
|||
}
|
||||
}
|
||||
|
||||
void LibraryController::loadDirectory(const QString& dir) {
|
||||
// This class can get delted during this function (sigh) so we need to hold onto this
|
||||
void LibraryController::loadDirectory(const QString& dir, bool recursive) {
|
||||
// This class can get deleted during this function (sigh) so we need to hold onto this
|
||||
std::shared_ptr<mLibrary> library = m_library;
|
||||
mLibraryLoadDirectory(library.get(), dir.toUtf8().constData());
|
||||
mLibraryLoadDirectory(library.get(), dir.toUtf8().constData(), recursive);
|
||||
}
|
||||
|
||||
void LibraryController::freeLibrary() {
|
||||
|
@ -192,4 +192,4 @@ void LibraryController::freeLibrary() {
|
|||
mLibraryEntryFree(mLibraryListingGetPointer(&m_listing, i));
|
||||
}
|
||||
mLibraryListingClear(&m_listing);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,7 +84,7 @@ public:
|
|||
|
||||
void selectLastBootedGame();
|
||||
|
||||
void addDirectory(const QString& dir);
|
||||
void addDirectory(const QString& dir, bool recursive = true);
|
||||
|
||||
public slots:
|
||||
void clear();
|
||||
|
@ -97,7 +97,7 @@ private slots:
|
|||
void refresh();
|
||||
|
||||
private:
|
||||
void loadDirectory(const QString&); // Called on separate thread
|
||||
void loadDirectory(const QString&, bool recursive = true); // Called on separate thread
|
||||
void freeLibrary();
|
||||
|
||||
ConfigController* m_config = nullptr;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -11,16 +11,16 @@ namespace QGBA {
|
|||
|
||||
QString niceSizeFormat(size_t filesize) {
|
||||
double size = filesize;
|
||||
QString unit = "B";
|
||||
QString unit = QObject::tr("%1 byte");
|
||||
if (size >= 1024.0) {
|
||||
size /= 1024.0;
|
||||
unit = "kiB";
|
||||
unit = QObject::tr("%1 kiB");
|
||||
}
|
||||
if (size >= 1024.0) {
|
||||
size /= 1024.0;
|
||||
unit = "MiB";
|
||||
unit = QObject::tr("%1 MiB");
|
||||
}
|
||||
return QString("%0 %1").arg(size, 0, 'f', 1).arg(unit);
|
||||
return unit.arg(size, 0, 'f', int(size * 10) % 10 ? 1 : 0);
|
||||
}
|
||||
QString nicePlatformFormat(mPlatform platform) {
|
||||
switch (platform) {
|
||||
|
|
|
@ -7,11 +7,70 @@
|
|||
|
||||
#include <mgba/core/core.h>
|
||||
|
||||
#include <QRect>
|
||||
#include <QSize>
|
||||
#include <QString>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
enum class Endian {
|
||||
NONE = 0b00,
|
||||
BIG = 0b01,
|
||||
LITTLE = 0b10,
|
||||
UNKNOWN = 0b11
|
||||
};
|
||||
|
||||
QString niceSizeFormat(size_t filesize);
|
||||
QString nicePlatformFormat(mPlatform platform);
|
||||
|
||||
inline void lockAspectRatio(const QSize& ref, QSize& size) {
|
||||
if (size.width() * ref.height() > size.height() * ref.width()) {
|
||||
size.setWidth(size.height() * ref.width() / ref.height());
|
||||
} else if (size.width() * ref.height() < size.height() * ref.width()) {
|
||||
size.setHeight(size.width() * ref.height() / ref.width());
|
||||
}
|
||||
}
|
||||
|
||||
inline void zoomAspectRatio(const QSize& ref, QSize& size) {
|
||||
if (size.width() * ref.height() < size.height() * ref.width()) {
|
||||
size.setWidth(size.height() * ref.width() / ref.height());
|
||||
} else if (size.width() * ref.height() > size.height() * ref.width()) {
|
||||
size.setHeight(size.width() * ref.height() / ref.width());
|
||||
}
|
||||
}
|
||||
|
||||
inline void lockIntegerScaling(const QSize& ref, QSize& size) {
|
||||
if (size.width() >= ref.width()) {
|
||||
size.setWidth(size.width() - size.width() % ref.width());
|
||||
}
|
||||
if (size.height() >= ref.height()) {
|
||||
size.setHeight(size.height() - size.height() % ref.height());
|
||||
}
|
||||
}
|
||||
|
||||
inline QRect clampSize(const QSize& ref, const QSize& size, bool aspectRatio, bool integerScaling, bool zoom = false) {
|
||||
QSize ds = size;
|
||||
if (zoom) {
|
||||
zoomAspectRatio(ref, ds);
|
||||
} else if (aspectRatio) {
|
||||
lockAspectRatio(ref, ds);
|
||||
}
|
||||
if (integerScaling) {
|
||||
QGBA::lockIntegerScaling(ref, ds);
|
||||
}
|
||||
QPoint origin = QPoint((size.width() - ds.width()) / 2, (size.height() - ds.height()) / 2);
|
||||
return QRect(origin, ds);
|
||||
}
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
using std::clamp;
|
||||
#else
|
||||
template<class T>
|
||||
constexpr const T& clamp(const T& v, const T& lo, const T& hi) {
|
||||
return std::max(lo, std::min(hi, v));
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ void SM83Reset(struct SM83Core* cpu) {
|
|||
|
||||
cpu->instruction = 0;
|
||||
|
||||
cpu->tMultiplier = 2;
|
||||
cpu->cycles = 0;
|
||||
cpu->nextEvent = 0;
|
||||
cpu->executionState = SM83_CORE_FETCH;
|
||||
|
@ -102,7 +103,7 @@ static void _SM83InstructionIRQ(struct SM83Core* cpu) {
|
|||
}
|
||||
|
||||
static void _SM83Step(struct SM83Core* cpu) {
|
||||
++cpu->cycles;
|
||||
cpu->cycles += cpu->tMultiplier;
|
||||
enum SM83ExecutionState state = cpu->executionState;
|
||||
cpu->executionState = SM83_CORE_IDLE_0;
|
||||
switch (state) {
|
||||
|
@ -147,23 +148,31 @@ static void _SM83Step(struct SM83Core* cpu) {
|
|||
}
|
||||
}
|
||||
|
||||
static inline bool _SM83TickInternal(struct SM83Core* cpu) {
|
||||
bool running = true;
|
||||
_SM83Step(cpu);
|
||||
int t = cpu->tMultiplier;
|
||||
if (cpu->cycles + t * 2 >= cpu->nextEvent) {
|
||||
int32_t diff = cpu->nextEvent - cpu->cycles;
|
||||
cpu->cycles = cpu->nextEvent;
|
||||
cpu->executionState += diff >> (t - 1); // NB: This assumes tMultiplier is either 1 or 2
|
||||
cpu->irqh.processEvents(cpu);
|
||||
cpu->cycles += (SM83_CORE_EXECUTE - cpu->executionState) * t;
|
||||
running = false;
|
||||
} else {
|
||||
cpu->cycles += t * 2;
|
||||
}
|
||||
cpu->executionState = SM83_CORE_FETCH;
|
||||
cpu->instruction(cpu);
|
||||
cpu->cycles += t;
|
||||
return running;
|
||||
}
|
||||
|
||||
void SM83Tick(struct SM83Core* cpu) {
|
||||
while (cpu->cycles >= cpu->nextEvent) {
|
||||
cpu->irqh.processEvents(cpu);
|
||||
}
|
||||
_SM83Step(cpu);
|
||||
if (cpu->cycles + 2 >= cpu->nextEvent) {
|
||||
int32_t diff = cpu->nextEvent - cpu->cycles;
|
||||
cpu->cycles = cpu->nextEvent;
|
||||
cpu->executionState += diff;
|
||||
cpu->irqh.processEvents(cpu);
|
||||
cpu->cycles += SM83_CORE_EXECUTE - cpu->executionState;
|
||||
} else {
|
||||
cpu->cycles += 2;
|
||||
}
|
||||
cpu->executionState = SM83_CORE_FETCH;
|
||||
cpu->instruction(cpu);
|
||||
++cpu->cycles;
|
||||
_SM83TickInternal(cpu);
|
||||
}
|
||||
|
||||
void SM83Run(struct SM83Core* cpu) {
|
||||
|
@ -173,19 +182,6 @@ void SM83Run(struct SM83Core* cpu) {
|
|||
cpu->irqh.processEvents(cpu);
|
||||
break;
|
||||
}
|
||||
_SM83Step(cpu);
|
||||
if (cpu->cycles + 2 >= cpu->nextEvent) {
|
||||
int32_t diff = cpu->nextEvent - cpu->cycles;
|
||||
cpu->cycles = cpu->nextEvent;
|
||||
cpu->executionState += diff;
|
||||
cpu->irqh.processEvents(cpu);
|
||||
cpu->cycles += SM83_CORE_EXECUTE - cpu->executionState;
|
||||
running = false;
|
||||
} else {
|
||||
cpu->cycles += 2;
|
||||
}
|
||||
cpu->executionState = SM83_CORE_FETCH;
|
||||
cpu->instruction(cpu);
|
||||
++cpu->cycles;
|
||||
running = _SM83TickInternal(cpu) && running;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -210,6 +210,7 @@ void PNGWriteClose(png_structp png, png_infop info) {
|
|||
|
||||
bool isPNG(struct VFile* source) {
|
||||
png_byte header[PNG_HEADER_BYTES];
|
||||
source->seek(source, 0, SEEK_SET);
|
||||
if (source->read(source, header, PNG_HEADER_BYTES) < PNG_HEADER_BYTES) {
|
||||
return false;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue