Core: Expose more ROM information from the API

This commit is contained in:
Vicki Pfau 2024-08-10 21:23:00 -07:00
parent ff216ad83b
commit 8ab2681bca
18 changed files with 129 additions and 109 deletions

View File

@ -49,6 +49,7 @@ Misc:
- Qt: Handle multiple save game files for disparate games separately (fixes mgba.io/i/2887) - Qt: Handle multiple save game files for disparate games separately (fixes mgba.io/i/2887)
- Qt: Remove maligned double-click-to-fullscreen shortcut (closes mgba.io/i/2632) - Qt: Remove maligned double-click-to-fullscreen shortcut (closes mgba.io/i/2632)
- Qt: Pass logging context through to video proxy thread (fixes mgba.io/i/3095) - Qt: Pass logging context through to video proxy thread (fixes mgba.io/i/3095)
- Qt: Show maker code and game version in ROM info
- Scripting: Add `callbacks:oneshot` for single-call callbacks - Scripting: Add `callbacks:oneshot` for single-call callbacks
- Switch: Add bilinear filtering option (closes mgba.io/i/3111) - Switch: Add bilinear filtering option (closes mgba.io/i/3111)
- Vita: Add imc0 and xmc0 mount point support - Vita: Add imc0 and xmc0 mount point support

View File

@ -119,8 +119,7 @@ struct mCore {
int32_t (*frameCycles)(const struct mCore*); int32_t (*frameCycles)(const struct mCore*);
int32_t (*frequency)(const struct mCore*); int32_t (*frequency)(const struct mCore*);
void (*getGameTitle)(const struct mCore*, char* title); void (*getGameInfo)(const struct mCore*, struct mGameInfo* info);
void (*getGameCode)(const struct mCore*, char* title);
void (*setPeripheral)(struct mCore*, int type, void*); void (*setPeripheral)(struct mCore*, int type, void*);
void* (*getPeripheral)(struct mCore*, int type); void* (*getPeripheral)(struct mCore*, int type);

View File

@ -22,6 +22,14 @@ enum mCoreFeature {
mCORE_FEATURE_OPENGL = 1, mCORE_FEATURE_OPENGL = 1,
}; };
struct mGameInfo {
char title[17];
char system[4];
char code[5];
char maker[3];
uint8_t version;
};
struct mCoreCallbacks { struct mCoreCallbacks {
void* context; void* context;
void (*videoFrameStarted)(void* context); void (*videoFrameStarted)(void* context);

View File

@ -190,8 +190,7 @@ void GBSavedataUnmask(struct GB* gb);
struct Patch; struct Patch;
void GBApplyPatch(struct GB* gb, struct Patch* patch); void GBApplyPatch(struct GB* gb, struct Patch* patch);
void GBGetGameTitle(const struct GB* gba, char* out); void GBGetGameInfo(const struct GB* gba, struct mGameInfo* info);
void GBGetGameCode(const struct GB* gba, char* out);
void GBTestKeypadIRQ(struct GB* gb); void GBTestKeypadIRQ(struct GB* gb);

View File

@ -182,8 +182,7 @@ void GBAUnloadMB(struct GBA* gba);
bool GBALoadNull(struct GBA* gba); bool GBALoadNull(struct GBA* gba);
void GBAGetGameCode(const struct GBA* gba, char* out); void GBAGetGameInfo(const struct GBA* gba, struct mGameInfo* info);
void GBAGetGameTitle(const struct GBA* gba, char* out);
void GBATestKeypadIRQ(struct GBA* gba); void GBATestKeypadIRQ(struct GBA* gba);

View File

@ -6,6 +6,7 @@
#include <mgba/core/library.h> #include <mgba/core/library.h>
#include <mgba/core/core.h> #include <mgba/core/core.h>
#include <mgba-util/string.h>
#include <mgba-util/vfs.h> #include <mgba-util/vfs.h>
#ifdef USE_SQLITE3 #ifdef USE_SQLITE3
@ -291,8 +292,10 @@ bool _mLibraryAddEntry(struct mLibrary* library, const char* filename, const cha
core->init(core); core->init(core);
core->loadROM(core, vf); core->loadROM(core, vf);
core->getGameTitle(core, entry.internalTitle); struct mGameInfo info;
core->getGameCode(core, entry.internalCode); core->getGameInfo(core, &info);
snprintf(entry.internalCode, sizeof(entry.internalCode), "%s-%s", info.system, info.code);
strlcpy(entry.internalTitle, info.title, sizeof(entry.internalTitle));
core->checksum(core, &entry.crc32, mCHECKSUM_CRC32); core->checksum(core, &entry.crc32, mCHECKSUM_CRC32);
entry.platform = core->platform(core); entry.platform = core->platform(core);
entry.title = NULL; entry.title = NULL;

View File

@ -332,15 +332,15 @@ mSCRIPT_DEFINE_STRUCT(mScriptMemoryDomain)
mSCRIPT_DEFINE_END; mSCRIPT_DEFINE_END;
static struct mScriptValue* _mScriptCoreGetGameTitle(const struct mCore* core) { static struct mScriptValue* _mScriptCoreGetGameTitle(const struct mCore* core) {
char title[32] = {0}; struct mGameInfo info;
core->getGameTitle(core, title); core->getGameInfo(core, &info);
return mScriptStringCreateFromASCII(title); return mScriptStringCreateFromASCII(info.title);
} }
static struct mScriptValue* _mScriptCoreGetGameCode(const struct mCore* core) { static struct mScriptValue* _mScriptCoreGetGameCode(const struct mCore* core) {
char code[16] = {0}; struct mGameInfo info;
core->getGameCode(core, code); core->getGameInfo(core, &info);
return mScriptStringCreateFromASCII(code); return mScriptStringCreateFromASCII(info.code);
} }
static struct mScriptValue* _mScriptCoreChecksum(const struct mCore* core, int t) { static struct mScriptValue* _mScriptCoreChecksum(const struct mCore* core, int t) {

View File

@ -781,12 +781,8 @@ static int32_t _GBCoreFrequency(const struct mCore* core) {
return DMG_SM83_FREQUENCY; return DMG_SM83_FREQUENCY;
} }
static void _GBCoreGetGameTitle(const struct mCore* core, char* title) { static void _GBCoreGetGameInfo(const struct mCore* core, struct mGameInfo* info) {
GBGetGameTitle(core->board, title); GBGetGameInfo(core->board, info);
}
static void _GBCoreGetGameCode(const struct mCore* core, char* title) {
GBGetGameCode(core->board, title);
} }
static void _GBCoreSetPeripheral(struct mCore* core, int type, void* periph) { static void _GBCoreSetPeripheral(struct mCore* core, int type, void* periph) {
@ -1332,8 +1328,7 @@ struct mCore* GBCoreCreate(void) {
core->frameCounter = _GBCoreFrameCounter; core->frameCounter = _GBCoreFrameCounter;
core->frameCycles = _GBCoreFrameCycles; core->frameCycles = _GBCoreFrameCycles;
core->frequency = _GBCoreFrequency; core->frequency = _GBCoreFrequency;
core->getGameTitle = _GBCoreGetGameTitle; core->getGameInfo = _GBCoreGetGameInfo;
core->getGameCode = _GBCoreGetGameCode;
core->setPeripheral = _GBCoreSetPeripheral; core->setPeripheral = _GBCoreSetPeripheral;
core->getPeripheral = _GBCoreGetPeripheral; core->getPeripheral = _GBCoreGetPeripheral;
core->busRead8 = _GBCoreBusRead8; core->busRead8 = _GBCoreBusRead8;

View File

@ -16,6 +16,7 @@
#include <mgba-util/memory.h> #include <mgba-util/memory.h>
#include <mgba-util/math.h> #include <mgba-util/math.h>
#include <mgba-util/patch.h> #include <mgba-util/patch.h>
#include <mgba-util/string.h>
#include <mgba-util/vfs.h> #include <mgba-util/vfs.h>
const uint32_t CGB_SM83_FREQUENCY = 0x800000; const uint32_t CGB_SM83_FREQUENCY = 0x800000;
@ -1116,38 +1117,28 @@ bool GBIsROM(struct VFile* vf) {
return false; return false;
} }
void GBGetGameTitle(const struct GB* gb, char* out) { void GBGetGameInfo(const struct GB* gb, struct mGameInfo* info) {
memset(info, 0, sizeof(*info));
const struct GBCartridge* cart = NULL; const struct GBCartridge* cart = NULL;
if (gb->memory.rom) { if (gb->memory.rom) {
cart = (const struct GBCartridge*) &gb->memory.rom[0x100]; cart = (const struct GBCartridge*) &gb->memory.rom[0x100];
} }
if (!cart) {
return;
}
if (cart->oldLicensee != 0x33) {
memcpy(out, cart->titleLong, 16);
} else {
memcpy(out, cart->titleShort, 11);
}
}
void GBGetGameCode(const struct GB* gb, char* out) {
memset(out, 0, 8);
const struct GBCartridge* cart = NULL;
if (gb->memory.rom) {
cart = (const struct GBCartridge*) &gb->memory.rom[0x100];
}
if (!cart) {
return;
}
if (cart->cgb == 0xC0) { if (cart->cgb == 0xC0) {
memcpy(out, "CGB-????", 8); strlcpy(info->system, "CGB", sizeof(info->system));
} else { } else {
memcpy(out, "DMG-????", 8); strlcpy(info->system, "DMG", sizeof(info->system));
} }
if (cart->oldLicensee == 0x33) {
memcpy(&out[4], cart->maker, 4); if (cart->oldLicensee != 0x33) {
memcpy(info->title, cart->titleLong, 16);
snprintf(info->maker, sizeof(info->maker), "%02X", cart->oldLicensee);
} else {
memcpy(info->title, cart->titleShort, 11);
memcpy(info->code, cart->maker, 4);
memcpy(info->maker, &cart->licensee, 2);
} }
info->version = cart->version;
} }
void GBFrameStarted(struct GB* gb) { void GBFrameStarted(struct GB* gb) {

View File

@ -870,12 +870,8 @@ static int32_t _GBACoreFrequency(const struct mCore* core) {
return GBA_ARM7TDMI_FREQUENCY; return GBA_ARM7TDMI_FREQUENCY;
} }
static void _GBACoreGetGameTitle(const struct mCore* core, char* title) { static void _GBACoreGetGameInfo(const struct mCore* core, struct mGameInfo* info) {
GBAGetGameTitle(core->board, title); GBAGetGameInfo(core->board, info);
}
static void _GBACoreGetGameCode(const struct mCore* core, char* title) {
GBAGetGameCode(core->board, title);
} }
static void _GBACoreSetPeripheral(struct mCore* core, int type, void* periph) { static void _GBACoreSetPeripheral(struct mCore* core, int type, void* periph) {
@ -1550,8 +1546,7 @@ struct mCore* GBACoreCreate(void) {
core->frameCounter = _GBACoreFrameCounter; core->frameCounter = _GBACoreFrameCounter;
core->frameCycles = _GBACoreFrameCycles; core->frameCycles = _GBACoreFrameCycles;
core->frequency = _GBACoreFrequency; core->frequency = _GBACoreFrequency;
core->getGameTitle = _GBACoreGetGameTitle; core->getGameInfo = _GBACoreGetGameInfo;
core->getGameCode = _GBACoreGetGameCode;
core->setPeripheral = _GBACoreSetPeripheral; core->setPeripheral = _GBACoreSetPeripheral;
core->getPeripheral = _GBACoreGetPeripheral; core->getPeripheral = _GBACoreGetPeripheral;
core->busRead8 = _GBACoreBusRead8; core->busRead8 = _GBACoreBusRead8;

View File

@ -18,6 +18,7 @@
#include <mgba-util/crc32.h> #include <mgba-util/crc32.h>
#include <mgba-util/math.h> #include <mgba-util/math.h>
#include <mgba-util/memory.h> #include <mgba-util/memory.h>
#include <mgba-util/string.h>
#include <mgba-util/vfs.h> #include <mgba-util/vfs.h>
#ifdef USE_ELF #ifdef USE_ELF
@ -849,26 +850,24 @@ bool GBAIsBIOS(struct VFile* vf) {
return true; return true;
} }
void GBAGetGameCode(const struct GBA* gba, char* out) { void GBAGetGameInfo(const struct GBA* gba, struct mGameInfo* info) {
memset(out, 0, 8); memset(info, 0, sizeof(*info));
if (!gba->memory.rom) { strlcpy(info->system, "AGB", sizeof(info->system));
return; struct GBACartridge* cart = NULL;
}
memcpy(out, "AGB-", 4);
memcpy(&out[4], &((struct GBACartridge*) gba->memory.rom)->id, 4);
}
void GBAGetGameTitle(const struct GBA* gba, char* out) {
if (gba->memory.rom) { if (gba->memory.rom) {
memcpy(out, &((struct GBACartridge*) gba->memory.rom)->title, 12); cart = (struct GBACartridge*) gba->memory.rom;
return; } else if (gba->isPristine && gba->memory.wram) {
cart = (struct GBACartridge*) gba->memory.wram;
} }
if (gba->isPristine && gba->memory.wram) {
memcpy(out, &((struct GBACartridge*) gba->memory.wram)->title, 12); if (cart) {
return; memcpy(info->title, &cart->title, 12);
memcpy(info->code, &cart->id, 4);
memcpy(info->maker, &cart->maker, 2);
info->version = cart->version;
} else {
strlcpy(info->title, "(BIOS)", 12);
} }
strncpy(out, "(BIOS)", 12);
} }
void GBAHitStub(struct ARMCore* cpu, uint32_t opcode) { void GBAHitStub(struct ARMCore* cpu, uint32_t opcode) {

View File

@ -30,12 +30,11 @@ BattleChipView::BattleChipView(std::shared_ptr<CoreController> controller, Windo
m_ui.setupUi(this); m_ui.setupUi(this);
m_ui.chipList->setModel(&m_model); m_ui.chipList->setModel(&m_model);
char title[9];
CoreController::Interrupter interrupter(m_controller); CoreController::Interrupter interrupter(m_controller);
mCore* core = m_controller->thread()->core; mCore* core = m_controller->thread()->core;
title[8] = '\0'; mGameInfo info;
core->getGameCode(core, title); core->getGameInfo(core, &info);
QString qtitle(title); QString qtitle(info.title);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
int size = QFontMetrics(QFont()).height() / ((int) ceil(devicePixelRatioF()) * 12); int size = QFontMetrics(QFont()).height() / ((int) ceil(devicePixelRatioF()) * 12);
@ -101,11 +100,11 @@ BattleChipView::BattleChipView(std::shared_ptr<CoreController> controller, Windo
m_controller->attachBattleChipGate(); m_controller->attachBattleChipGate();
setFlavor(4); setFlavor(4);
if (qtitle.startsWith("AGB-B4B") || qtitle.startsWith("AGB-B4W") || qtitle.startsWith("AGB-BR4") || qtitle.startsWith("AGB-BZ3")) { if (qtitle.startsWith("B4B") || qtitle.startsWith("B4W") || qtitle.startsWith("BR4") || qtitle.startsWith("BZ3")) {
m_ui.gateBattleChip->setChecked(true); m_ui.gateBattleChip->setChecked(true);
} else if (qtitle.startsWith("AGB-BRB") || qtitle.startsWith("AGB-BRK")) { } else if (qtitle.startsWith("BRB") || qtitle.startsWith("BRK")) {
m_ui.gateProgress->setChecked(true); m_ui.gateProgress->setChecked(true);
} else if (qtitle.startsWith("AGB-BR5") || qtitle.startsWith("AGB-BR6")) { } else if (qtitle.startsWith("BR5") || qtitle.startsWith("BR6")) {
m_ui.gateBeastLink->setChecked(true); m_ui.gateBeastLink->setChecked(true);
} }

View File

@ -1318,9 +1318,9 @@ void CoreController::updateROMInfo() {
mCore* core = m_threadContext.core; mCore* core = m_threadContext.core;
core->checksum(core, &m_crc32, mCHECKSUM_CRC32); core->checksum(core, &m_crc32, mCHECKSUM_CRC32);
char gameTitle[17] = { '\0' }; mGameInfo info;
core->getGameTitle(core, gameTitle); core->getGameInfo(core, &info);
m_internalTitle = QLatin1String(gameTitle); m_internalTitle = QLatin1String(info.title);
#ifdef USE_SQLITE3 #ifdef USE_SQLITE3
if (db && m_crc32 && NoIntroDBLookupGameByCRC(db, m_crc32, &game)) { if (db && m_crc32 && NoIntroDBLookupGameByCRC(db, m_crc32, &game)) {

View File

@ -14,9 +14,9 @@ void GBAOverride::identify(const struct mCore* core) {
if (core->platform(core) != mPLATFORM_GBA) { if (core->platform(core) != mPLATFORM_GBA) {
return; return;
} }
char gameId[8]; mGameInfo info;
core->getGameCode(core, gameId); core->getGameInfo(core, &info);
memcpy(override.id, &gameId[4], 4); memcpy(override.id, info.code, 4);
} }
void GBAOverride::save(struct Configuration* config) const { void GBAOverride::save(struct Configuration* config) const {

View File

@ -27,16 +27,16 @@ ROMInfo::ROMInfo(std::shared_ptr<CoreController> controller, QWidget* parent)
CoreController::Interrupter interrupter(controller); CoreController::Interrupter interrupter(controller);
mCore* core = controller->thread()->core; mCore* core = controller->thread()->core;
char title[17] = {}; mGameInfo info;
core->getGameTitle(core, title); core->getGameInfo(core, &info);
m_ui.title->setText(QLatin1String(title)); m_ui.title->setText(QLatin1String(info.title));
title[8] = '\0'; if (info.code[0]) {
core->getGameCode(core, title); m_ui.id->setText(QLatin1String(info.code));
if (title[0]) {
m_ui.id->setText(QLatin1String(title));
} else { } else {
m_ui.id->setText(tr("(unknown)")); m_ui.id->setText(tr("(unknown)"));
} }
m_ui.maker->setText(QLatin1String(info.maker));
m_ui.version->setText(QString::number(info.version));
core->checksum(core, &crc32, mCHECKSUM_CRC32); core->checksum(core, &crc32, mCHECKSUM_CRC32);

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>236</width> <width>178</width>
<height>146</height> <height>198</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -75,13 +75,47 @@
</widget> </widget>
</item> </item>
<item row="3" column="0"> <item row="3" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Maker Code:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="maker">
<property name="text">
<string notr="true">{MAKER}</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Revision:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="version">
<property name="text">
<string notr="true">{VERSION}</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label_3">
<property name="text"> <property name="text">
<string>File size:</string> <string>File size:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="5" column="1">
<widget class="QLabel" name="size"> <widget class="QLabel" name="size">
<property name="text"> <property name="text">
<string notr="true">{SIZE}</string> <string notr="true">{SIZE}</string>
@ -91,14 +125,14 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0"> <item row="6" column="0">
<widget class="QLabel" name="label_4"> <widget class="QLabel" name="label_4">
<property name="text"> <property name="text">
<string>CRC32:</string> <string>CRC32:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1"> <item row="6" column="1">
<widget class="QLabel" name="crc"> <widget class="QLabel" name="crc">
<property name="text"> <property name="text">
<string notr="true">{CRC}</string> <string notr="true">{CRC}</string>
@ -108,14 +142,14 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0"> <item row="7" column="0">
<widget class="QLabel" name="label_6"> <widget class="QLabel" name="label_6">
<property name="text"> <property name="text">
<string>Save file:</string> <string>Save file:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="1"> <item row="7" column="1">
<widget class="QLabel" name="savefile"> <widget class="QLabel" name="savefile">
<property name="text"> <property name="text">
<string notr="true">{SAVEFILE}</string> <string notr="true">{SAVEFILE}</string>

View File

@ -497,17 +497,16 @@ void ReportView::addROMInfo(QStringList& report, CoreController* controller) {
report << QString("Currently paused: %1").arg(yesNo[controller->isPaused()]); report << QString("Currently paused: %1").arg(yesNo[controller->isPaused()]);
mCore* core = controller->thread()->core; mCore* core = controller->thread()->core;
char title[17] = {}; struct mGameInfo info;
core->getGameTitle(core, title); core->getGameInfo(core, &info);
report << QString("Internal title: %1").arg(QLatin1String(title)); report << QString("Internal title: %1").arg(QLatin1String(info.title));
if (info.code[0]) {
title[8] = '\0'; report << QString("Game code: %1").arg(QLatin1String(info.code));
core->getGameCode(core, title);
if (title[0]) {
report << QString("Game code: %1").arg(QLatin1String(title));
} else { } else {
report << QString("Invalid game code"); report << QString("Invalid game code");
} }
report << QString("Game maker: %1").arg(QLatin1String(info.maker));
report << QString("Game version: %1").arg(info.version);
uint32_t crc32 = 0; uint32_t crc32 = 0;
core->checksum(core, &crc32, mCHECKSUM_CRC32); core->checksum(core, &crc32, mCHECKSUM_CRC32);

View File

@ -196,8 +196,6 @@ bool _mPerfRunCore(const char* fname, const struct mArguments* args, const struc
} }
// TODO: Put back debugger // TODO: Put back debugger
char gameCode[9] = { 0 };
core->init(core); core->init(core);
if (!perfOpts->noVideo) { if (!perfOpts->noVideo) {
core->setVideoBuffer(core, _outputBuffer, 256); core->setVideoBuffer(core, _outputBuffer, 256);
@ -226,7 +224,8 @@ bool _mPerfRunCore(const char* fname, const struct mArguments* args, const struc
mCoreLoadStateNamed(core, _savestate, 0); mCoreLoadStateNamed(core, _savestate, 0);
} }
core->getGameCode(core, gameCode); struct mGameInfo info;
core->getGameInfo(core, &info);
int frames = perfOpts->frames; int frames = perfOpts->frames;
if (!frames) { if (!frames) {
@ -255,7 +254,7 @@ bool _mPerfRunCore(const char* fname, const struct mArguments* args, const struc
} else { } else {
rendererName = "software"; rendererName = "software";
} }
snprintf(buffer, sizeof(buffer), "%s,%i,%" PRIu64 ",%s\n", gameCode, frames, duration, rendererName); snprintf(buffer, sizeof(buffer), "%s-%s,%i,%" PRIu64 ",%s\n", info.system, info.code, frames, duration, rendererName);
printf("%s", buffer); printf("%s", buffer);
if (_socket != INVALID_SOCKET) { if (_socket != INVALID_SOCKET) {
SocketSend(_socket, buffer, strlen(buffer)); SocketSend(_socket, buffer, strlen(buffer));