Merge branch 'master' (early part) into medusa

This commit is contained in:
Vicki Pfau 2019-06-28 17:16:56 -07:00
commit 21e7d76320
30 changed files with 661 additions and 169 deletions

49
CHANGES
View File

@ -27,33 +27,40 @@ Bugfixes:
- GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208) - GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208)
- GBA: Reset now reloads multiboot ROMs - GBA: Reset now reloads multiboot ROMs
- GBA BIOS: Fix multiboot entry point (fixes Magic Floor) - GBA BIOS: Fix multiboot entry point (fixes Magic Floor)
- Switch: Fix final cleanup (fixes mgba.io/i/1283) - Qt: More app metadata fixes
- Qt: Fix tile and sprite views not always displaying at first
- GBA Memory: Fix a few AGBPrint crashes
- GBA Memory: Fix OOB ROM reads showing up as AGBPrint memory
- GB Serialize: Fix loading states with negative pixel x (fixes mgba.io/i/1293)
- Qt: Fix audio context holding onto closed game controller
- Switch: Fix gyroscope orientation (fixes mgba.io/i/1300)
- GBA SIO: Prevent writing read-only multiplayer bits
- Qt: Fix color picking in sprite view (fixes mgba.io/i/1307)
- GB: Fix crash when accessing SRAM if no save loaded and cartridge has no SRAM
- Python: Fix crash when deleting files owned by library
- Python: Make sure GB link object isn't GC'd before GB object
- GBA DMA: Fix Display Start DMAs
- GBA DMA: Fix DMA start/end timing
- Qt: Fix window icon on X11
- GB, GBA Serialize: Fix loading two states in a row
- GBA Video: Fix enabling layers in non-tile modes (fixes mgba.io/i/1317)
- Qt: Fix quick load recent accidentally saving (fixes mgba.io/i/1309)
- GBA: Fix video timing when skipping BIOS (fixes mgba.io/i/1318)
- 3DS: Work around menu freezing (fixes mgba.io/i/1294)
Misc: Misc:
- GBA Savedata: EEPROM performance fixes - GBA Savedata: EEPROM performance fixes
- GBA Savedata: Automatically map 1Mbit Flash files as 1Mbit Flash - GBA Savedata: Automatically map 1Mbit Flash files as 1Mbit Flash
- GB Memory: Support running from blocked memory - GB Memory: Support running from blocked memory
- Qt: Don't unload ROM immediately if it crashes - Qt: Don't unload ROM immediately if it crashes
- GBA Video: Improve sprite cycle counting (fixes mgba.io/i/1274)
- Debugger: Add breakpoint and watchpoint listing - Debugger: Add breakpoint and watchpoint listing
0.7.1: (2019-02-24)
Bugfixes:
- 3DS: Work around menu freezing (fixes mgba.io/i/1294)
- GB: Fix crash when accessing SRAM if no save loaded and cartridge has no SRAM
- GB Serialize: Fix loading states with negative pixel x (fixes mgba.io/i/1293)
- GB, GBA Serialize: Fix loading two states in a row
- GBA: Fix video timing when skipping BIOS (fixes mgba.io/i/1318)
- GBA DMA: Fix Display Start DMAs
- GBA DMA: Fix DMA start/end timing
- GBA DMA: Fix invalid DMA handling (fixes mgba.io/i/1301)
- GBA Memory: Fix a few AGBPrint crashes
- GBA Memory: Fix OOB ROM reads showing up as AGBPrint memory
- GBA SIO: Prevent writing read-only multiplayer bits
- GBA Video: Fix enabling layers in non-tile modes (fixes mgba.io/i/1317)
- Python: Fix crash when deleting files owned by library
- Python: Make sure GB link object isn't GC'd before GB object
- PSP2: Fix file descriptors dying on suspend (fixes mgba.io/i/1123)
- Qt: Fix tile and sprite views not always displaying at first
- Qt: Fix audio context holding onto closed game controller
- Qt: Fix color picking in sprite view (fixes mgba.io/i/1307)
- Qt: Fix window icon on X11
- Qt: Fix quick load recent accidentally saving (fixes mgba.io/i/1309)
- Switch: Fix final cleanup (fixes mgba.io/i/1283)
- Switch: Fix gyroscope orientation (fixes mgba.io/i/1300)
Misc:
- GBA Video: Improve sprite cycle counting (fixes mgba.io/i/1274)
- Qt: Updated Italian translation (by Vecna) - Qt: Updated Italian translation (by Vecna)
0.7.0: (2019-01-26) 0.7.0: (2019-01-26)

View File

@ -143,8 +143,8 @@ void GBADestroy(struct GBA* gba);
void GBAReset(struct ARMCore* cpu); void GBAReset(struct ARMCore* cpu);
void GBASkipBIOS(struct GBA* gba); void GBASkipBIOS(struct GBA* gba);
void GBARaiseIRQ(struct GBA* gba, enum GBAIRQ irq); void GBARaiseIRQ(struct GBA* gba, enum GBAIRQ irq, uint32_t cyclesLate);
void GBATestIRQ(struct ARMCore* cpu); void GBATestIRQ(struct GBA* gba, uint32_t cyclesLate);
void GBAHalt(struct GBA* gba); void GBAHalt(struct GBA* gba);
void GBAStop(struct GBA* gba); void GBAStop(struct GBA* gba);
void GBADebug(struct GBA* gba, uint16_t value); void GBADebug(struct GBA* gba, uint16_t value);

View File

@ -1,8 +1,8 @@
Elijah Chondropoulos
Jaime J. Denizard Jaime J. Denizard
Fog Fog
Philip Horton Philip Horton
Oskenso Kashi Oskenso Kashi
Mored1984
Rohit Nirmal Rohit Nirmal
Rhys Powell Rhys Powell
Yuri Kunde Schlesner Yuri Kunde Schlesner

View File

@ -271,7 +271,17 @@ static void _log(struct mLogger* logger, int category, enum mLogLevel level, con
if (len >= sizeof(log2)) { if (len >= sizeof(log2)) {
len = sizeof(log2) - 1; len = sizeof(log2) - 1;
} }
guiLogger->vf->write(guiLogger->vf, log2, len); if (guiLogger->vf->write(guiLogger->vf, log2, len) < 0) {
char path[PATH_MAX];
mCoreConfigDirectory(path, PATH_MAX);
strncat(path, PATH_SEP "log", PATH_MAX - strlen(path));
guiLogger->vf->close(guiLogger->vf);
guiLogger->vf = VFileOpen(path, O_CREAT | O_WRONLY | O_APPEND);
if (guiLogger->vf->write(guiLogger->vf, log2, len) < 0) {
guiLogger->vf->close(guiLogger->vf);
guiLogger->vf = NULL;
}
}
} }
void mGUIRun(struct mGUIRunner* runner, const char* path) { void mGUIRun(struct mGUIRunner* runner, const char* path) {

View File

@ -193,7 +193,7 @@ void _dmaEvent(struct mTiming* timing, void* context, uint32_t cyclesLate) {
dma->nextDest = dma->dest; dma->nextDest = dma->dest;
} }
if (GBADMARegisterIsDoIRQ(dma->reg)) { if (GBADMARegisterIsDoIRQ(dma->reg)) {
GBARaiseIRQ(gba, IRQ_DMA0 + memory->activeDMA); GBARaiseIRQ(gba, IRQ_DMA0 + memory->activeDMA, cyclesLate);
} }
GBADMAUpdate(gba); GBADMAUpdate(gba);
} }
@ -260,15 +260,13 @@ void GBADMAService(struct GBA* gba, int number, struct GBADMA* info) {
} }
gba->bus = memory->dmaTransferRegister; gba->bus = memory->dmaTransferRegister;
cpu->memory.store32(cpu, dest, memory->dmaTransferRegister, 0); cpu->memory.store32(cpu, dest, memory->dmaTransferRegister, 0);
memory->dmaTransferRegister &= 0xFFFF0000;
memory->dmaTransferRegister |= memory->dmaTransferRegister >> 16;
} else { } else {
if (sourceRegion == REGION_CART2_EX && (memory->savedata.type == SAVEDATA_EEPROM || memory->savedata.type == SAVEDATA_EEPROM512)) { if (sourceRegion == REGION_CART2_EX && (memory->savedata.type == SAVEDATA_EEPROM || memory->savedata.type == SAVEDATA_EEPROM512)) {
memory->dmaTransferRegister = GBASavedataReadEEPROM(&memory->savedata); memory->dmaTransferRegister = GBASavedataReadEEPROM(&memory->savedata);
} else { memory->dmaTransferRegister |= memory->dmaTransferRegister << 16;
if (source) { } else if (source) {
memory->dmaTransferRegister = cpu->memory.load16(cpu, source, 0); memory->dmaTransferRegister = cpu->memory.load16(cpu, source, 0);
} memory->dmaTransferRegister |= memory->dmaTransferRegister << 16;
} }
if (destRegion == REGION_CART2_EX) { if (destRegion == REGION_CART2_EX) {
if (memory->savedata.type == SAVEDATA_AUTODETECT) { if (memory->savedata.type == SAVEDATA_AUTODETECT) {
@ -282,7 +280,6 @@ void GBADMAService(struct GBA* gba, int number, struct GBADMA* info) {
cpu->memory.store16(cpu, dest, memory->dmaTransferRegister, 0); cpu->memory.store16(cpu, dest, memory->dmaTransferRegister, 0);
} }
memory->dmaTransferRegister |= memory->dmaTransferRegister << 16;
gba->bus = memory->dmaTransferRegister; gba->bus = memory->dmaTransferRegister;
} }
int sourceOffset = DMA_OFFSET[GBADMARegisterGetSrcControl(info->reg)] * width; int sourceOffset = DMA_OFFSET[GBADMARegisterGetSrcControl(info->reg)] * width;

View File

@ -101,7 +101,7 @@ void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cycle
gate->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0; gate->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0;
gate->d.p->normalControl.start = 0; gate->d.p->normalControl.start = 0;
if (gate->d.p->normalControl.irq) { if (gate->d.p->normalControl.irq) {
GBARaiseIRQ(gate->d.p->p, IRQ_SIO); GBARaiseIRQ(gate->d.p->p, IRQ_SIO, cyclesLate);
} }
return; return;
} }
@ -193,6 +193,6 @@ void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cycle
gate->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = reply; gate->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = reply;
if (gate->d.p->multiplayerControl.irq) { if (gate->d.p->multiplayerControl.irq) {
GBARaiseIRQ(gate->d.p->p, IRQ_SIO); GBARaiseIRQ(gate->d.p->p, IRQ_SIO, cyclesLate);
} }
} }

View File

@ -46,6 +46,7 @@ static void GBAProcessEvents(struct ARMCore* cpu);
static void GBAHitStub(struct ARMCore* cpu, uint32_t opcode); static void GBAHitStub(struct ARMCore* cpu, uint32_t opcode);
static void GBAIllegal(struct ARMCore* cpu, uint32_t opcode); static void GBAIllegal(struct ARMCore* cpu, uint32_t opcode);
static void GBABreakpoint(struct ARMCore* cpu, int immediate); static void GBABreakpoint(struct ARMCore* cpu, int immediate);
static void GBATestIRQNoDelay(struct ARMCore* cpu);
static void _triggerIRQ(struct mTiming*, void* user, uint32_t cyclesLate); static void _triggerIRQ(struct mTiming*, void* user, uint32_t cyclesLate);
@ -180,7 +181,7 @@ void GBAInterruptHandlerInit(struct ARMInterruptHandler* irqh) {
irqh->swi16 = GBASwi16; irqh->swi16 = GBASwi16;
irqh->swi32 = GBASwi32; irqh->swi32 = GBASwi32;
irqh->hitIllegal = GBAIllegal; irqh->hitIllegal = GBAIllegal;
irqh->readCPSR = GBATestIRQ; irqh->readCPSR = GBATestIRQNoDelay;
irqh->hitStub = GBAHitStub; irqh->hitStub = GBAHitStub;
irqh->bkpt16 = GBABreakpoint; irqh->bkpt16 = GBABreakpoint;
irqh->bkpt32 = GBABreakpoint; irqh->bkpt32 = GBABreakpoint;
@ -433,7 +434,7 @@ void GBAYankROM(struct GBA* gba) {
gba->yankedRomSize = gba->memory.romSize; gba->yankedRomSize = gba->memory.romSize;
gba->memory.romSize = 0; gba->memory.romSize = 0;
gba->memory.romMask = 0; gba->memory.romMask = 0;
GBARaiseIRQ(gba, IRQ_GAMEPAK); GBARaiseIRQ(gba, IRQ_GAMEPAK, 0);
} }
void GBALoadBIOS(struct GBA* gba, struct VFile* vf) { void GBALoadBIOS(struct GBA* gba, struct VFile* vf) {
@ -486,16 +487,20 @@ void GBAApplyPatch(struct GBA* gba, struct Patch* patch) {
gba->romCrc32 = doCrc32(gba->memory.rom, gba->memory.romSize); gba->romCrc32 = doCrc32(gba->memory.rom, gba->memory.romSize);
} }
void GBARaiseIRQ(struct GBA* gba, enum GBAIRQ irq) { void GBARaiseIRQ(struct GBA* gba, enum GBAIRQ irq, uint32_t cyclesLate) {
gba->memory.io[REG_IF >> 1] |= 1 << irq; gba->memory.io[REG_IF >> 1] |= 1 << irq;
GBATestIRQ(gba->cpu); GBATestIRQ(gba, cyclesLate);
} }
void GBATestIRQ(struct ARMCore* cpu) { void GBATestIRQNoDelay(struct ARMCore* cpu) {
struct GBA* gba = (struct GBA*) cpu->master; struct GBA* gba = (struct GBA*) cpu->master;
GBATestIRQ(gba, 0);
}
void GBATestIRQ(struct GBA* gba, uint32_t cyclesLate) {
if (gba->memory.io[REG_IE >> 1] & gba->memory.io[REG_IF >> 1]) { if (gba->memory.io[REG_IE >> 1] & gba->memory.io[REG_IF >> 1]) {
if (!mTimingIsScheduled(&gba->timing, &gba->irqEvent)) { if (!mTimingIsScheduled(&gba->timing, &gba->irqEvent)) {
mTimingSchedule(&gba->timing, &gba->irqEvent, GBA_IRQ_DELAY); mTimingSchedule(&gba->timing, &gba->irqEvent, GBA_IRQ_DELAY - cyclesLate);
} }
} }
} }
@ -845,9 +850,9 @@ void GBATestKeypadIRQ(struct GBA* gba) {
uint16_t keyInput = *gba->keySource & keycnt; uint16_t keyInput = *gba->keySource & keycnt;
if (isAnd && keycnt == keyInput) { if (isAnd && keycnt == keyInput) {
GBARaiseIRQ(gba, IRQ_KEYPAD); GBARaiseIRQ(gba, IRQ_KEYPAD, 0);
} else if (!isAnd && keyInput) { } else if (!isAnd && keyInput) {
GBARaiseIRQ(gba, IRQ_KEYPAD); GBARaiseIRQ(gba, IRQ_KEYPAD, 0);
} }
} }

View File

@ -578,7 +578,7 @@ void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLat
gbp->p->p->memory.io[REG_SIODATA32_LO >> 1] = tx; gbp->p->p->memory.io[REG_SIODATA32_LO >> 1] = tx;
gbp->p->p->memory.io[REG_SIODATA32_HI >> 1] = tx >> 16; gbp->p->p->memory.io[REG_SIODATA32_HI >> 1] = tx >> 16;
if (gbp->d.p->normalControl.irq) { if (gbp->d.p->normalControl.irq) {
GBARaiseIRQ(gbp->p->p, IRQ_SIO); GBARaiseIRQ(gbp->p->p, IRQ_SIO, cyclesLate);
} }
gbp->d.p->normalControl.start = 0; gbp->d.p->normalControl.start = 0;
gbp->p->p->memory.io[REG_SIOCNT >> 1] = gbp->d.p->siocnt & ~0x0080; gbp->p->p->memory.io[REG_SIOCNT >> 1] = gbp->d.p->siocnt & ~0x0080;

View File

@ -548,16 +548,16 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
break; break;
case REG_IE: case REG_IE:
gba->memory.io[REG_IE >> 1] = value; gba->memory.io[REG_IE >> 1] = value;
GBATestIRQ(gba->cpu); GBATestIRQ(gba->cpu, 1);
return; return;
case REG_IF: case REG_IF:
value = gba->memory.io[REG_IF >> 1] & ~value; value = gba->memory.io[REG_IF >> 1] & ~value;
gba->memory.io[REG_IF >> 1] = value; gba->memory.io[REG_IF >> 1] = value;
GBATestIRQ(gba->cpu); GBATestIRQ(gba->cpu, 1);
return; return;
case REG_IME: case REG_IME:
gba->memory.io[REG_IME >> 1] = value; gba->memory.io[REG_IME >> 1] = value;
GBATestIRQ(gba->cpu); GBATestIRQ(gba->cpu, 1);
return; return;
case REG_MAX: case REG_MAX:
// Some bad interrupt libraries will write to this // Some bad interrupt libraries will write to this

View File

@ -158,7 +158,7 @@ void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value) {
if ((value & 0x0081) == 0x0081) { if ((value & 0x0081) == 0x0081) {
if (value & 0x4000) { if (value & 0x4000) {
// TODO: Test this on hardware to see if this is correct // TODO: Test this on hardware to see if this is correct
GBARaiseIRQ(sio->p, IRQ_SIO); GBARaiseIRQ(sio->p, IRQ_SIO, 0);
} }
value &= ~0x0080; value &= ~0x0080;
} }

View File

@ -37,7 +37,7 @@ int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command
case JOY_RESET: case JOY_RESET:
sio->p->p->memory.io[REG_JOYCNT >> 1] |= 1; sio->p->p->memory.io[REG_JOYCNT >> 1] |= 1;
if (sio->p->p->memory.io[REG_JOYCNT >> 1] & 0x40) { if (sio->p->p->memory.io[REG_JOYCNT >> 1] & 0x40) {
GBARaiseIRQ(sio->p->p, IRQ_SIO); GBARaiseIRQ(sio->p->p, IRQ_SIO, 0);
} }
// Fall through // Fall through
case JOY_POLL: case JOY_POLL:
@ -55,7 +55,7 @@ int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command
data[0] = sio->p->p->memory.io[REG_JOYSTAT >> 1]; data[0] = sio->p->p->memory.io[REG_JOYSTAT >> 1];
if (sio->p->p->memory.io[REG_JOYCNT >> 1] & 0x40) { if (sio->p->p->memory.io[REG_JOYCNT >> 1] & 0x40) {
GBARaiseIRQ(sio->p->p, IRQ_SIO); GBARaiseIRQ(sio->p->p, IRQ_SIO, 0);
} }
return 1; return 1;
case JOY_TRANS: case JOY_TRANS:
@ -68,7 +68,7 @@ int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command
data[4] = sio->p->p->memory.io[REG_JOYSTAT >> 1]; data[4] = sio->p->p->memory.io[REG_JOYSTAT >> 1];
if (sio->p->p->memory.io[REG_JOYCNT >> 1] & 0x40) { if (sio->p->p->memory.io[REG_JOYCNT >> 1] & 0x40) {
GBARaiseIRQ(sio->p->p, IRQ_SIO); GBARaiseIRQ(sio->p->p, IRQ_SIO, 0);
} }
return 5; return 5;
} }

View File

@ -166,7 +166,7 @@ static void _finishTransfer(struct GBASIOLockstepNode* node) {
sio->multiplayerControl.busy = 0; sio->multiplayerControl.busy = 0;
sio->multiplayerControl.id = node->id; sio->multiplayerControl.id = node->id;
if (sio->multiplayerControl.irq) { if (sio->multiplayerControl.irq) {
GBARaiseIRQ(sio->p, IRQ_SIO); GBARaiseIRQ(sio->p, IRQ_SIO, 0);
} }
break; break;
case SIO_NORMAL_8: case SIO_NORMAL_8:
@ -179,7 +179,7 @@ static void _finishTransfer(struct GBASIOLockstepNode* node) {
node->d.p->p->memory.io[REG_SIODATA8 >> 1] = 0xFFFF; node->d.p->p->memory.io[REG_SIODATA8 >> 1] = 0xFFFF;
} }
if (sio->multiplayerControl.irq) { if (sio->multiplayerControl.irq) {
GBARaiseIRQ(sio->p, IRQ_SIO); GBARaiseIRQ(sio->p, IRQ_SIO, 0);
} }
break; break;
case SIO_NORMAL_32: case SIO_NORMAL_32:
@ -194,7 +194,7 @@ static void _finishTransfer(struct GBASIOLockstepNode* node) {
node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0xFFFF; node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0xFFFF;
} }
if (sio->multiplayerControl.irq) { if (sio->multiplayerControl.irq) {
GBARaiseIRQ(sio->p, IRQ_SIO); GBARaiseIRQ(sio->p, IRQ_SIO, 0);
} }
break; break;
default: default:

View File

@ -13,10 +13,10 @@
#define REG_TMCNT_LO(X) (REG_TM0CNT_LO + ((X) << 2)) #define REG_TMCNT_LO(X) (REG_TM0CNT_LO + ((X) << 2))
static void GBATimerIrq(struct GBA* gba, int timerId) { static void GBATimerIrq(struct GBA* gba, int timerId, uint32_t cyclesLate) {
struct GBATimer* timer = &gba->timers[timerId]; struct GBATimer* timer = &gba->timers[timerId];
if (GBATimerFlagsIsDoIrq(timer->flags)) { if (GBATimerFlagsIsDoIrq(timer->flags)) {
GBARaiseIRQ(gba, IRQ_TIMER0 + timerId); GBARaiseIRQ(gba, IRQ_TIMER0 + timerId, cyclesLate);
} }
} }
@ -56,9 +56,9 @@ static void GBATimerUpdate0(struct mTiming* timing, void* context, uint32_t cycl
struct GBA* gba = context; struct GBA* gba = context;
GBATimerUpdateAudio(gba, 0, cyclesLate); GBATimerUpdateAudio(gba, 0, cyclesLate);
GBATimerUpdate(timing, &gba->timers[0], &gba->memory.io[REG_TM0CNT_LO >> 1], cyclesLate); GBATimerUpdate(timing, &gba->timers[0], &gba->memory.io[REG_TM0CNT_LO >> 1], cyclesLate);
GBATimerIrq(gba, 0); GBATimerIrq(gba, 0, cyclesLate);
if (GBATimerUpdateCountUp(timing, &gba->timers[1], &gba->memory.io[REG_TM1CNT_LO >> 1], cyclesLate)) { if (GBATimerUpdateCountUp(timing, &gba->timers[1], &gba->memory.io[REG_TM1CNT_LO >> 1], cyclesLate)) {
GBATimerIrq(gba, 1); GBATimerIrq(gba, 1, cyclesLate);
} }
} }
@ -66,25 +66,25 @@ static void GBATimerUpdate1(struct mTiming* timing, void* context, uint32_t cycl
struct GBA* gba = context; struct GBA* gba = context;
GBATimerUpdateAudio(gba, 1, cyclesLate); GBATimerUpdateAudio(gba, 1, cyclesLate);
GBATimerUpdate(timing, &gba->timers[1], &gba->memory.io[REG_TM1CNT_LO >> 1], cyclesLate); GBATimerUpdate(timing, &gba->timers[1], &gba->memory.io[REG_TM1CNT_LO >> 1], cyclesLate);
GBATimerIrq(gba, 1); GBATimerIrq(gba, 1, cyclesLate);
if (GBATimerUpdateCountUp(timing, &gba->timers[2], &gba->memory.io[REG_TM2CNT_LO >> 1], cyclesLate)) { if (GBATimerUpdateCountUp(timing, &gba->timers[2], &gba->memory.io[REG_TM2CNT_LO >> 1], cyclesLate)) {
GBATimerIrq(gba, 2); GBATimerIrq(gba, 2, cyclesLate);
} }
} }
static void GBATimerUpdate2(struct mTiming* timing, void* context, uint32_t cyclesLate) { static void GBATimerUpdate2(struct mTiming* timing, void* context, uint32_t cyclesLate) {
struct GBA* gba = context; struct GBA* gba = context;
GBATimerUpdate(timing, &gba->timers[2], &gba->memory.io[REG_TM2CNT_LO >> 1], cyclesLate); GBATimerUpdate(timing, &gba->timers[2], &gba->memory.io[REG_TM2CNT_LO >> 1], cyclesLate);
GBATimerIrq(gba, 2); GBATimerIrq(gba, 2, cyclesLate);
if (GBATimerUpdateCountUp(timing, &gba->timers[3], &gba->memory.io[REG_TM3CNT_LO >> 1], cyclesLate)) { if (GBATimerUpdateCountUp(timing, &gba->timers[3], &gba->memory.io[REG_TM3CNT_LO >> 1], cyclesLate)) {
GBATimerIrq(gba, 3); GBATimerIrq(gba, 3, cyclesLate);
} }
} }
static void GBATimerUpdate3(struct mTiming* timing, void* context, uint32_t cyclesLate) { static void GBATimerUpdate3(struct mTiming* timing, void* context, uint32_t cyclesLate) {
struct GBA* gba = context; struct GBA* gba = context;
GBATimerUpdate(timing, &gba->timers[3], &gba->memory.io[REG_TM3CNT_LO >> 1], cyclesLate); GBATimerUpdate(timing, &gba->timers[3], &gba->memory.io[REG_TM3CNT_LO >> 1], cyclesLate);
GBATimerIrq(gba, 3); GBATimerIrq(gba, 3, cyclesLate);
} }
void GBATimerInit(struct GBA* gba) { void GBATimerInit(struct GBA* gba) {

View File

@ -154,7 +154,7 @@ void _startHdraw(struct mTiming* timing, void* context, uint32_t cyclesLate) {
if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) { if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) {
dispstat = GBARegisterDISPSTATFillVcounter(dispstat); dispstat = GBARegisterDISPSTATFillVcounter(dispstat);
if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) { if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) {
GBARaiseIRQ(video->p, IRQ_VCOUNTER); GBARaiseIRQ(video->p, IRQ_VCOUNTER, cyclesLate);
} }
} else { } else {
dispstat = GBARegisterDISPSTATClearVcounter(dispstat); dispstat = GBARegisterDISPSTATClearVcounter(dispstat);
@ -173,7 +173,7 @@ void _startHdraw(struct mTiming* timing, void* context, uint32_t cyclesLate) {
} }
GBADMARunVblank(video->p, -cyclesLate); GBADMARunVblank(video->p, -cyclesLate);
if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) { if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) {
GBARaiseIRQ(video->p, IRQ_VBLANK); GBARaiseIRQ(video->p, IRQ_VBLANK, cyclesLate);
} }
GBAFrameEnded(video->p); GBAFrameEnded(video->p);
mCoreSyncPostFrame(video->p->sync); mCoreSyncPostFrame(video->p->sync);
@ -209,7 +209,7 @@ void _startHblank(struct mTiming* timing, void* context, uint32_t cyclesLate) {
GBADMARunDisplayStart(video->p, -cyclesLate); GBADMARunDisplayStart(video->p, -cyclesLate);
} }
if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) { if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) {
GBARaiseIRQ(video->p, IRQ_HBLANK); GBARaiseIRQ(video->p, IRQ_HBLANK, cyclesLate);
} }
video->p->memory.io[REG_DISPSTAT >> 1] = dispstat; video->p->memory.io[REG_DISPSTAT >> 1] = dispstat;
} }

View File

@ -160,7 +160,8 @@ int main() {
.unpaused = mPSP2Unpaused, .unpaused = mPSP2Unpaused,
.incrementScreenMode = mPSP2IncrementScreenMode, .incrementScreenMode = mPSP2IncrementScreenMode,
.setFrameLimiter = mPSP2SetFrameLimiter, .setFrameLimiter = mPSP2SetFrameLimiter,
.pollGameInput = mPSP2PollInput .pollGameInput = mPSP2PollInput,
.running = mPSP2SystemPoll
}; };
sceTouchSetSamplingState(SCE_TOUCH_PORT_FRONT, SCE_TOUCH_SAMPLING_STATE_START); sceTouchSetSamplingState(SCE_TOUCH_PORT_FRONT, SCE_TOUCH_SAMPLING_STATE_START);

View File

@ -25,6 +25,7 @@
#include <mgba-util/vfs.h> #include <mgba-util/vfs.h>
#include <mgba-util/platform/psp2/sce-vfs.h> #include <mgba-util/platform/psp2/sce-vfs.h>
#include <psp2/appmgr.h>
#include <psp2/audioout.h> #include <psp2/audioout.h>
#include <psp2/camera.h> #include <psp2/camera.h>
#include <psp2/ctrl.h> #include <psp2/ctrl.h>
@ -38,6 +39,9 @@
#define RUMBLE_PWM 8 #define RUMBLE_PWM 8
#define CDRAM_ALIGN 0x40000 #define CDRAM_ALIGN 0x40000
mLOG_DECLARE_CATEGORY(GUI_PSP2);
mLOG_DEFINE_CATEGORY(GUI_PSP2, "Vita", "gui.psp2");
static enum ScreenMode { static enum ScreenMode {
SM_BACKDROP, SM_BACKDROP,
SM_PLAIN, SM_PLAIN,
@ -539,6 +543,18 @@ void mPSP2IncrementScreenMode(struct mGUIRunner* runner) {
mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode); mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
} }
bool mPSP2SystemPoll(struct mGUIRunner* runner) {
SceAppMgrSystemEvent event;
if (sceAppMgrReceiveSystemEvent(&event) < 0) {
return true;
}
if (event.systemEvent == SCE_APPMGR_SYSTEMEVENT_ON_RESUME) {
mLOG(GUI_PSP2, INFO, "Suspend detected, reloading save");
mCoreAutoloadSave(runner->core);
}
return true;
}
__attribute__((noreturn, weak)) void __assert_func(const char* file, int line, const char* func, const char* expr) { __attribute__((noreturn, weak)) void __assert_func(const char* file, int line, const char* func, const char* expr) {
printf("ASSERT FAILED: %s in %s at %s:%i\n", expr, func, file, line); printf("ASSERT FAILED: %s in %s at %s:%i\n", expr, func, file, line);
exit(1); exit(1);

View File

@ -25,5 +25,6 @@ void mPSP2DrawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsig
void mPSP2IncrementScreenMode(struct mGUIRunner* runner); void mPSP2IncrementScreenMode(struct mGUIRunner* runner);
void mPSP2SetFrameLimiter(struct mGUIRunner* runner, bool limit); void mPSP2SetFrameLimiter(struct mGUIRunner* runner, bool limit);
uint16_t mPSP2PollInput(struct mGUIRunner* runner); uint16_t mPSP2PollInput(struct mGUIRunner* runner);
bool mPSP2SystemPoll(struct mGUIRunner* runner);
#endif #endif

View File

@ -0,0 +1,89 @@
/* Copyright (c) 2013-2019 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 "AbstractUpdater.h"
#include <QNetworkAccessManager>
#include <QNetworkReply>
using namespace QGBA;
AbstractUpdater::AbstractUpdater(QObject* parent)
: QObject(parent)
, m_netman(new QNetworkAccessManager(this))
{
}
void AbstractUpdater::checkUpdate() {
QNetworkReply* reply = m_netman->get(QNetworkRequest(manifestLocation()));
chaseRedirects(reply, &AbstractUpdater::manifestDownloaded);
}
void AbstractUpdater::downloadUpdate() {
if (m_isUpdating) {
return;
}
if (m_manifest.isEmpty()) {
m_isUpdating = true;
checkUpdate();
return;
}
QUrl url = parseManifest(m_manifest);
if (!url.isValid()) {
emit updateDone(false);
return;
}
m_isUpdating = true;
QNetworkReply* reply = m_netman->get(QNetworkRequest(url));
chaseRedirects(reply, &AbstractUpdater::updateDownloaded);
}
void AbstractUpdater::chaseRedirects(QNetworkReply* reply, void (AbstractUpdater::*cb)(QNetworkReply*)) {
connect(reply, &QNetworkReply::finished, this, [this, reply, cb]() {
// TODO: check domains, etc
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() / 100 == 3) {
QNetworkReply* newReply = m_netman->get(QNetworkRequest(reply->header(QNetworkRequest::LocationHeader).toString()));
chaseRedirects(newReply, cb);
} else {
(this->*cb)(reply);
}
});
}
void AbstractUpdater::manifestDownloaded(QNetworkReply* reply) {
m_manifest = reply->readAll();
QUrl url = parseManifest(m_manifest);
if (m_isUpdating) {
if (!url.isValid()) {
emit updateDone(false);
} else {
QNetworkReply* reply = m_netman->get(QNetworkRequest(url));
chaseRedirects(reply, &AbstractUpdater::updateDownloaded);
}
} else {
emit updateAvailable(url.isValid());
}
}
void AbstractUpdater::updateDownloaded(QNetworkReply* reply) {
m_isUpdating = false;
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() / 100 != 2) {
emit updateDone(false);
return;
}
QFile f(destination());
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
emit updateDone(false);
return;
}
while (true) {
QByteArray bytes = reply->read(4096);
if (!bytes.size()) {
break;
}
f.write(bytes);
}
emit updateDone(true);
}

View File

@ -0,0 +1,47 @@
/* Copyright (c) 2013-2019 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#pragma once
#include <QByteArray>
#include <QFile>
#include <QObject>
class QNetworkAccessManager;
class QNetworkReply;
namespace QGBA {
class AbstractUpdater : public QObject {
Q_OBJECT
public:
AbstractUpdater(QObject* parent = nullptr);
virtual ~AbstractUpdater() {}
public slots:
void checkUpdate();
void downloadUpdate();
signals:
void updateAvailable(bool);
void updateDone(bool);
protected:
virtual QUrl manifestLocation() const = 0;
virtual QUrl parseManifest(const QByteArray&) const = 0;
virtual QString destination() const = 0;
private:
void chaseRedirects(QNetworkReply*, void (AbstractUpdater::*cb)(QNetworkReply*));
void manifestDownloaded(QNetworkReply*);
void updateDownloaded(QNetworkReply*);
bool m_isUpdating = false;
QNetworkAccessManager* m_netman;
QByteArray m_manifest;
};
}

View File

@ -0,0 +1,186 @@
/* Copyright (c) 2013-2019 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 "BattleChipModel.h"
#include "ConfigController.h"
#include "GBAApp.h"
#include <QFile>
#include <QMimeData>
#include <QResource>
using namespace QGBA;
BattleChipModel::BattleChipModel(QObject* parent)
: QAbstractListModel(parent)
{
QResource::registerResource(GBAApp::dataDir() + "/chips.rcc", "/exe");
QResource::registerResource(ConfigController::configDir() + "/chips.rcc", "/exe");
}
int BattleChipModel::rowCount(const QModelIndex& parent) const {
if (parent.isValid()) {
return 0;
}
return m_deck.count();
}
QVariant BattleChipModel::data(const QModelIndex& index, int role) const {
const BattleChip& item = m_deck[index.row()];
switch (role) {
case Qt::DisplayRole:
return item.name;
case Qt::DecorationRole:
return item.icon.scaled(item.icon.size() * m_scale);
case Qt::UserRole:
return item.id;
}
return QVariant();
}
Qt::ItemFlags BattleChipModel::flags(const QModelIndex& index) const {
return Qt::ItemIsSelectable | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren;
}
bool BattleChipModel::removeRows(int row, int count, const QModelIndex& parent) {
if (parent.isValid()) {
return false;
}
beginRemoveRows(QModelIndex(), row, row + count - 1);
for (size_t i = 0; i < count; ++i) {
m_deck.removeAt(row);
}
endRemoveRows();
return true;
}
QStringList BattleChipModel::mimeTypes() const {
return {"text/plain"};
}
Qt::DropActions BattleChipModel::supportedDropActions() const {
return Qt::MoveAction;
}
QMimeData* BattleChipModel::mimeData(const QModelIndexList& indices) const {
QStringList deck;
for (const QModelIndex& index : indices) {
if (index.parent().isValid()) {
continue;
}
deck.append(QString::number(m_deck[index.row()].id));
}
QMimeData* mimeData = new QMimeData();
mimeData->setData("text/plain", deck.join(',').toLocal8Bit());
return mimeData;
}
bool BattleChipModel::dropMimeData(const QMimeData* data, Qt::DropAction, int row, int, const QModelIndex& parent) {
if (parent.parent().isValid()) {
return false;
}
QStringList deck = QString::fromLocal8Bit(data->data("text/plain")).split(',');
if (deck.isEmpty()) {
return true;
}
row = parent.row();
beginInsertRows(QModelIndex(), row, row + deck.count() - 1);
for (int i = 0; i < deck.count(); ++i) {
int id = deck[i].toInt();
m_deck.insert(row + i, createChip(id));
}
endInsertRows();
return true;
}
void BattleChipModel::setFlavor(int flavor) {
m_chipIdToName.clear();
if (flavor == GBA_FLAVOR_BEAST_LINK_GATE_US) {
flavor = GBA_FLAVOR_BEAST_LINK_GATE;
}
m_flavor = flavor;
QFile file(QString(":/exe/exe%1/chip-names.txt").arg(flavor));
file.open(QIODevice::ReadOnly | QIODevice::Text);
int id = 0;
while (true) {
QByteArray line = file.readLine();
if (line.isEmpty()) {
break;
}
++id;
if (line.trimmed().isEmpty()) {
continue;
}
QString name = QString::fromUtf8(line).trimmed();
m_chipIdToName[id] = name;
}
}
void BattleChipModel::addChip(int id) {
beginInsertRows(QModelIndex(), m_deck.count(), m_deck.count());
m_deck.append(createChip(id));
endInsertRows();
}
void BattleChipModel::removeChip(const QModelIndex& index) {
beginRemoveRows(QModelIndex(), index.row(), index.row());
m_deck.removeAt(index.row());
endRemoveRows();
}
void BattleChipModel::setChips(QList<int> ids) {
beginResetModel();
m_deck.clear();
for (int id : ids) {
m_deck.append(createChip(id));
}
endResetModel();
}
void BattleChipModel::clear() {
beginResetModel();
m_deck.clear();
endResetModel();
}
void BattleChipModel::setScale(int scale) {
m_scale = scale;
}
void BattleChipModel::reloadAssets() {
QResource::unregisterResource(ConfigController::configDir() + "/chips.rcc", "/exe");
QResource::unregisterResource(GBAApp::dataDir() + "/chips.rcc", "/exe");
QResource::registerResource(GBAApp::dataDir() + "/chips.rcc", "/exe");
QResource::registerResource(ConfigController::configDir() + "/chips.rcc", "/exe");
emit layoutAboutToBeChanged();
setFlavor(m_flavor);
for (int i = 0; i < m_deck.count(); ++i) {
m_deck[i] = createChip(m_deck[i].id);
}
emit layoutChanged();
}
BattleChipModel::BattleChip BattleChipModel::createChip(int id) const {
QString path = QString(":/exe/exe%1/%2.png").arg(m_flavor).arg(id, 3, 10, QLatin1Char('0'));
if (!QFile(path).exists()) {
path = QString(":/exe/exe%1/placeholder.png").arg(m_flavor);
}
QPixmap icon(path);
BattleChip chip = {
id,
m_chipIdToName[id],
icon
};
return chip;
}

View File

@ -0,0 +1,58 @@
/* Copyright (c) 2013-2019 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 <QAbstractListModel>
#include <QPixmap>
namespace QGBA {
class BattleChipUpdater;
class BattleChipModel : public QAbstractListModel {
Q_OBJECT
public:
BattleChipModel(QObject* parent = nullptr);
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
virtual bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override;
virtual Qt::DropActions supportedDropActions() const override;
virtual QStringList mimeTypes() const override;
virtual QMimeData* mimeData(const QModelIndexList& indices) const override;
virtual bool dropMimeData(const QMimeData* data, Qt::DropAction, int row, int column, const QModelIndex& parent) override;
int flavor() const { return m_flavor; }
QMap<int, QString> chipNames() const { return m_chipIdToName; }
public slots:
void setFlavor(int);
void addChip(int id);
void removeChip(const QModelIndex&);
void setChips(QList<int> ids);
void clear();
void setScale(int);
void reloadAssets();
private:
struct BattleChip {
int id;
QString name;
QPixmap icon;
};
BattleChip createChip(int id) const;
QMap<int, QString> m_chipIdToName;
int m_flavor;
int m_scale = 1;
QList<BattleChip> m_deck;
};
}

View File

@ -0,0 +1,47 @@
/* Copyright (c) 2013-2019 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 "BattleChipUpdater.h"
#include "ConfigController.h"
#include "GBAApp.h"
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
using namespace QGBA;
BattleChipUpdater::BattleChipUpdater(QObject* parent)
: AbstractUpdater(parent)
{
}
QUrl BattleChipUpdater::manifestLocation() const {
return {"https://api.github.com/repos/mgba-emu/chip-assets/releases/latest"};
}
QUrl BattleChipUpdater::parseManifest(const QByteArray& manifest) const {
QJsonDocument manifestDoc(QJsonDocument::fromJson(manifest));
if (manifestDoc.isNull()) {
return QUrl();
}
for (const auto& assetv : manifestDoc.object()["assets"].toArray()) {
QJsonObject asset = assetv.toObject();
if (asset["name"].toString() == "chips.rcc") {
return asset["browser_download_url"].toString();
}
}
return QUrl();
}
QString BattleChipUpdater::destination() const {
QFileInfo info(GBAApp::dataDir() + "/chips.rcc");
if (info.isWritable()) {
return info.filePath();
}
return ConfigController::configDir() + "/chips.rcc";
}

View File

@ -0,0 +1,22 @@
/* Copyright (c) 2013-2019 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 "AbstractUpdater.h"
namespace QGBA {
class BattleChipUpdater : public AbstractUpdater {
public:
BattleChipUpdater(QObject* parent = nullptr);
protected:
virtual QUrl manifestLocation() const override;
virtual QUrl parseManifest(const QByteArray&) const override;
virtual QString destination() const override;
};
}

View File

@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "BattleChipView.h" #include "BattleChipView.h"
#include "BattleChipUpdater.h"
#include "ConfigController.h" #include "ConfigController.h"
#include "CoreController.h" #include "CoreController.h"
#include "GBAApp.h" #include "GBAApp.h"
@ -12,11 +13,10 @@
#include "Window.h" #include "Window.h"
#include <QtAlgorithms> #include <QtAlgorithms>
#include <QFile> #include <QFileInfo>
#include <QFontMetrics> #include <QFontMetrics>
#include <QMessageBox> #include <QMessageBox>
#include <QMultiMap> #include <QMultiMap>
#include <QResource>
#include <QSettings> #include <QSettings>
#include <QStringList> #include <QStringList>
@ -27,10 +27,8 @@ BattleChipView::BattleChipView(std::shared_ptr<CoreController> controller, Windo
, m_controller(controller) , m_controller(controller)
, m_window(window) , m_window(window)
{ {
QResource::registerResource(GBAApp::dataDir() + "/chips.rcc", "/exe");
QResource::registerResource(ConfigController::configDir() + "/chips.rcc", "/exe");
m_ui.setupUi(this); m_ui.setupUi(this);
m_ui.chipList->setModel(&m_model);
char title[9]; char title[9];
CoreController::Interrupter interrupter(m_controller); CoreController::Interrupter interrupter(m_controller);
@ -46,12 +44,16 @@ BattleChipView::BattleChipView(std::shared_ptr<CoreController> controller, Windo
#endif #endif
m_ui.chipList->setIconSize(m_ui.chipList->iconSize() * size); m_ui.chipList->setIconSize(m_ui.chipList->iconSize() * size);
m_ui.chipList->setGridSize(m_ui.chipList->gridSize() * size); m_ui.chipList->setGridSize(m_ui.chipList->gridSize() * size);
m_model.setScale(size);
connect(m_ui.chipId, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), m_ui.inserted, [this]() { connect(m_ui.chipId, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), m_ui.inserted, [this]() {
m_ui.inserted->setChecked(Qt::Unchecked); m_ui.inserted->setChecked(Qt::Unchecked);
}); });
connect(m_ui.chipName, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), m_ui.chipId, [this](int id) { connect(m_ui.chipName, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), m_ui.chipId, [this](int id) {
m_ui.chipId->setValue(m_chipIndexToId[id]); if (id < 0) {
return;
}
m_ui.chipId->setValue(m_model.chipNames().keys()[id]);
}); });
connect(m_ui.inserted, &QAbstractButton::toggled, this, &BattleChipView::insertChip); connect(m_ui.inserted, &QAbstractButton::toggled, this, &BattleChipView::insertChip);
@ -61,7 +63,8 @@ BattleChipView::BattleChipView(std::shared_ptr<CoreController> controller, Windo
connect(controller.get(), &CoreController::stopping, this, &QWidget::close); connect(controller.get(), &CoreController::stopping, this, &QWidget::close);
connect(m_ui.save, &QAbstractButton::clicked, this, &BattleChipView::saveDeck); connect(m_ui.save, &QAbstractButton::clicked, this, &BattleChipView::saveDeck);
connect(m_ui.load, &QAbstractButton::clicked, this, &BattleChipView::loadDeck); connect(m_ui.load, &QAbstractButton::clicked, this, &BattleChipView::loadDeck);
connect(m_ui.buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, m_ui.chipList, &QListWidget::clear); connect(m_ui.updateData, &QAbstractButton::clicked, this, &BattleChipView::updateData);
connect(m_ui.buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, &m_model, &BattleChipModel::clear);
connect(m_ui.gateBattleChip, &QAbstractButton::toggled, this, [this](bool on) { connect(m_ui.gateBattleChip, &QAbstractButton::toggled, this, [this](bool on) {
if (on) { if (on) {
@ -85,13 +88,14 @@ BattleChipView::BattleChipView(std::shared_ptr<CoreController> controller, Windo
connect(m_controller.get(), &CoreController::frameAvailable, this, &BattleChipView::advanceFrameCounter); connect(m_controller.get(), &CoreController::frameAvailable, this, &BattleChipView::advanceFrameCounter);
connect(m_ui.chipList, &QListWidget::itemClicked, this, [this](QListWidgetItem* item) { connect(m_ui.chipList, &QAbstractItemView::clicked, this, [this](const QModelIndex& index) {
QVariant chip = item->data(Qt::UserRole); QVariant chip = m_model.data(index, Qt::UserRole);
bool blocked = m_ui.chipId->blockSignals(true); bool blocked = m_ui.chipId->blockSignals(true);
m_ui.chipId->setValue(chip.toInt()); m_ui.chipId->setValue(chip.toInt());
m_ui.chipId->blockSignals(blocked); m_ui.chipId->blockSignals(blocked);
reinsert(); reinsert();
}); });
connect(m_ui.chipList, &QListView::indexesMoved, this, &BattleChipView::resort);
m_controller->attachBattleChipGate(); m_controller->attachBattleChipGate();
setFlavor(4); setFlavor(4);
@ -102,6 +106,18 @@ BattleChipView::BattleChipView(std::shared_ptr<CoreController> controller, Windo
} else if (qtitle.startsWith("AGB-BR5") || qtitle.startsWith("AGB-BR6")) { } else if (qtitle.startsWith("AGB-BR5") || qtitle.startsWith("AGB-BR6")) {
m_ui.gateBeastLink->setChecked(Qt::Checked); m_ui.gateBeastLink->setChecked(Qt::Checked);
} }
if (!QFileInfo(GBAApp::dataDir() + "/chips.rcc").exists() && !QFileInfo(ConfigController::configDir() + "/chips.rcc").exists()) {
QMessageBox* download = new QMessageBox(this);
download->setIcon(QMessageBox::Information);
download->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
download->setWindowTitle(tr("BattleChip data missing"));
download->setText(tr("BattleChip data is missing. BattleChip Gates will still work, but some graphics will be missing. Would you like to download the data now?"));
download->setAttribute(Qt::WA_DeleteOnClose);
download->setWindowModality(Qt::NonModal);
connect(download, &QDialog::accepted, this, &BattleChipView::updateData);
download->show();
}
} }
BattleChipView::~BattleChipView() { BattleChipView::~BattleChipView() {
@ -110,7 +126,9 @@ BattleChipView::~BattleChipView() {
void BattleChipView::setFlavor(int flavor) { void BattleChipView::setFlavor(int flavor) {
m_controller->setBattleChipFlavor(flavor); m_controller->setBattleChipFlavor(flavor);
loadChipNames(flavor); m_model.setFlavor(flavor);
m_ui.chipName->clear();
m_ui.chipName->addItems(m_model.chipNames().values());
} }
void BattleChipView::insertChip(bool inserted) { void BattleChipView::insertChip(bool inserted) {
@ -141,55 +159,13 @@ void BattleChipView::addChip() {
if (insertedChip < 1) { if (insertedChip < 1) {
return; return;
} }
addChipId(insertedChip); m_model.addChip(insertedChip);
}
void BattleChipView::addChipId(int id) {
QListWidgetItem* add = new QListWidgetItem(m_chipIdToName[id]);
add->setData(Qt::UserRole, id);
QString path = QString(":/exe/exe%1/%2.png").arg(m_flavor).arg(id, 3, 10, QLatin1Char('0'));
if (!QFile(path).exists()) {
path = QString(":/exe/exe%1/placeholder.png").arg(m_flavor);
}
add->setIcon(QPixmap(path).scaled(m_ui.chipList->iconSize()));
m_ui.chipList->addItem(add);
} }
void BattleChipView::removeChip() { void BattleChipView::removeChip() {
qDeleteAll(m_ui.chipList->selectedItems()); for (const auto& index : m_ui.chipList->selectionModel()->selectedIndexes()) {
} m_model.removeChip(index);
void BattleChipView::loadChipNames(int flavor) {
QStringList chipNames;
chipNames.append(tr("(None)"));
m_chipIndexToId.clear();
m_chipIdToName.clear();
if (flavor == GBA_FLAVOR_BEAST_LINK_GATE_US) {
flavor = GBA_FLAVOR_BEAST_LINK_GATE;
} }
m_flavor = flavor;
QFile file(QString(":/exe/exe%1/chip-names.txt").arg(flavor));
file.open(QIODevice::ReadOnly | QIODevice::Text);
int id = 0;
while (true) {
QByteArray line = file.readLine();
if (line.isEmpty()) {
break;
}
++id;
if (line.trimmed().isEmpty()) {
continue;
}
QString name = QString::fromUtf8(line).trimmed();
m_chipIndexToId[chipNames.length()] = id;
m_chipIdToName[id] = name;
chipNames.append(name);
}
m_ui.chipName->clear();
m_ui.chipName->addItems(chipNames);
} }
void BattleChipView::advanceFrameCounter() { void BattleChipView::advanceFrameCounter() {
@ -208,14 +184,14 @@ void BattleChipView::saveDeck() {
} }
QStringList deck; QStringList deck;
for (int i = 0; i < m_ui.chipList->count(); ++i) { for (int i = 0; i < m_model.rowCount(); ++i) {
deck.append(m_ui.chipList->item(i)->data(Qt::UserRole).toString()); deck.append(m_model.data(m_model.index(i, 0), Qt::UserRole).toString());
} }
QSettings ini(filename, QSettings::IniFormat); QSettings ini(filename, QSettings::IniFormat);
ini.clear(); ini.clear();
ini.beginGroup("BattleChipDeck"); ini.beginGroup("BattleChipDeck");
ini.setValue("version", m_flavor); ini.setValue("version", m_model.flavor());
ini.setValue("deck", deck.join(',')); ini.setValue("deck", deck.join(','));
ini.sync(); ini.sync();
} }
@ -229,7 +205,7 @@ void BattleChipView::loadDeck() {
QSettings ini(filename, QSettings::IniFormat); QSettings ini(filename, QSettings::IniFormat);
ini.beginGroup("BattleChipDeck"); ini.beginGroup("BattleChipDeck");
int flavor = ini.value("version").toInt(); int flavor = ini.value("version").toInt();
if (flavor != m_flavor) { if (flavor != m_model.flavor()) {
QMessageBox* error = new QMessageBox(this); QMessageBox* error = new QMessageBox(this);
error->setIcon(QMessageBox::Warning); error->setIcon(QMessageBox::Warning);
error->setStandardButtons(QMessageBox::Ok); error->setStandardButtons(QMessageBox::Ok);
@ -240,13 +216,47 @@ void BattleChipView::loadDeck() {
return; return;
} }
m_ui.chipList->clear(); QList<int> newDeck;
QStringList deck = ini.value("deck").toString().split(','); QStringList deck = ini.value("deck").toString().split(',');
for (const auto& item : deck) { for (const auto& item : deck) {
bool ok; bool ok;
int id = item.toInt(&ok); int id = item.toInt(&ok);
if (ok) { if (ok) {
addChipId(id); newDeck.append(id);
} }
} }
m_model.setChips(newDeck);
}
void BattleChipView::resort() {
QMap<int, int> chips;
for (int i = 0; i < m_model.rowCount(); ++i) {
QModelIndex index = m_model.index(i, 0);
QRect visualRect = m_ui.chipList->visualRect(index);
QSize gridSize = m_ui.chipList->gridSize();
int x = visualRect.y() / gridSize.height();
x *= m_ui.chipList->viewport()->width();
x += visualRect.x();
x *= m_model.rowCount();
x += index.row();
chips[x] = m_model.data(index, Qt::UserRole).toInt();
}
m_model.setChips(chips.values());
}
void BattleChipView::updateData() {
if (m_updater) {
return;
}
m_updater = new BattleChipUpdater(this);
connect(m_updater, &BattleChipUpdater::updateDone, this, [this](bool success) {
if (success) {
m_model.reloadAssets();
m_ui.chipName->clear();
m_ui.chipName->addItems(m_model.chipNames().values());
}
delete m_updater;
m_updater = nullptr;
});
m_updater->downloadUpdate();
} }

View File

@ -5,6 +5,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#pragma once #pragma once
#include "BattleChipModel.h"
#include <QDialog> #include <QDialog>
#include <memory> #include <memory>
@ -29,16 +31,18 @@ public slots:
void setFlavor(int); void setFlavor(int);
void insertChip(bool); void insertChip(bool);
void reinsert(); void reinsert();
void resort();
private slots: private slots:
void advanceFrameCounter(); void advanceFrameCounter();
void addChip(); void addChip();
void addChipId(int);
void removeChip(); void removeChip();
void saveDeck(); void saveDeck();
void loadDeck(); void loadDeck();
void updateData();
private: private:
static const int UNINSERTED_TIME = 10; static const int UNINSERTED_TIME = 10;
@ -46,15 +50,15 @@ private:
Ui::BattleChipView m_ui; Ui::BattleChipView m_ui;
QMap<int, int> m_chipIndexToId; BattleChipModel m_model;
QMap<int, QString> m_chipIdToName;
std::shared_ptr<CoreController> m_controller; std::shared_ptr<CoreController> m_controller;
int m_flavor;
int m_frameCounter = -1; int m_frameCounter = -1;
bool m_next = false; bool m_next = false;
Window* m_window; Window* m_window;
BattleChipUpdater* m_updater = nullptr;
}; };
} }

View File

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>630</width> <width>658</width>
<height>722</height> <height>722</height>
</rect> </rect>
</property> </property>
@ -15,7 +15,7 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,0,0,0,0,0"> <layout class="QVBoxLayout" name="verticalLayout" stretch="1,0,0,0,0,0">
<item> <item>
<widget class="QListWidget" name="chipList"> <widget class="QListView" name="chipList">
<property name="acceptDrops"> <property name="acceptDrops">
<bool>true</bool> <bool>true</bool>
</property> </property>
@ -35,7 +35,7 @@
</size> </size>
</property> </property>
<property name="movement"> <property name="movement">
<enum>QListView::Static</enum> <enum>QListView::Free</enum>
</property> </property>
<property name="isWrapping" stdset="0"> <property name="isWrapping" stdset="0">
<bool>true</bool> <bool>true</bool>
@ -52,12 +52,6 @@
<property name="viewMode"> <property name="viewMode">
<enum>QListView::IconMode</enum> <enum>QListView::IconMode</enum>
</property> </property>
<property name="uniformItemSizes">
<bool>false</bool>
</property>
<property name="selectionRectVisible">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -201,6 +195,19 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0" colspan="2">
<widget class="QPushButton" name="updateData">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Update Chip data</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
</layout> </layout>
@ -260,21 +267,5 @@
</hint> </hint>
</hints> </hints>
</connection> </connection>
<connection>
<sender>chipList</sender>
<signal>indexesMoved(QModelIndexList)</signal>
<receiver>chipList</receiver>
<slot>doItemsLayout()</slot>
<hints>
<hint type="sourcelabel">
<x>314</x>
<y>203</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>203</y>
</hint>
</hints>
</connection>
</connections> </connections>
</ui> </ui>

View File

@ -67,6 +67,7 @@ endif()
set(SOURCE_FILES set(SOURCE_FILES
AboutScreen.cpp AboutScreen.cpp
AbstractUpdater.cpp
AssetTile.cpp AssetTile.cpp
AssetView.cpp AssetView.cpp
AudioProcessor.cpp AudioProcessor.cpp
@ -149,6 +150,8 @@ set(UI_FILES
VideoView.ui) VideoView.ui)
set(GBA_SRC set(GBA_SRC
BattleChipModel.cpp
BattleChipUpdater.cpp
BattleChipView.cpp BattleChipView.cpp
GBAOverride.cpp) GBAOverride.cpp)

View File

@ -18,7 +18,6 @@
#include <QFileOpenEvent> #include <QFileOpenEvent>
#include <QIcon> #include <QIcon>
#include <mgba/core/version.h>
#include <mgba-util/socket.h> #include <mgba-util/socket.h>
#include <mgba-util/vfs.h> #include <mgba-util/vfs.h>
@ -42,17 +41,10 @@ GBAApp::GBAApp(int& argc, char* argv[], ConfigController* config)
SDL_Init(SDL_INIT_NOPARACHUTE); SDL_Init(SDL_INIT_NOPARACHUTE);
#endif #endif
#ifndef Q_OS_MAC
setWindowIcon(QIcon(":/res/mgba-512.png"));
#endif
SocketSubsystemInit(); SocketSubsystemInit();
qRegisterMetaType<const uint32_t*>("const uint32_t*"); qRegisterMetaType<const uint32_t*>("const uint32_t*");
qRegisterMetaType<mCoreThread*>("mCoreThread*"); qRegisterMetaType<mCoreThread*>("mCoreThread*");
QApplication::setApplicationName(projectName);
QApplication::setApplicationVersion(projectVersion);
if (!m_configController->getQtOption("displayDriver").isNull()) { if (!m_configController->getQtOption("displayDriver").isNull()) {
Display::setDriver(static_cast<Display::Driver>(m_configController->getQtOption("displayDriver").toInt())); Display::setDriver(static_cast<Display::Driver>(m_configController->getQtOption("displayDriver").toInt()));
} }

View File

@ -107,7 +107,6 @@ Window::Window(CoreManager* manager, ConfigController* config, int playerId, QWi
m_logo.setDevicePixelRatio(m_screenWidget->devicePixelRatio()); m_logo.setDevicePixelRatio(m_screenWidget->devicePixelRatio());
m_logo = m_logo; // Free memory left over in old pixmap m_logo = m_logo; // Free memory left over in old pixmap
setWindowIcon(m_logo);
#if defined(M_CORE_GBA) #if defined(M_CORE_GBA)
float i = 2; float i = 2;

View File

@ -61,8 +61,15 @@ int main(int argc, char* argv[]) {
return 0; return 0;
} }
QApplication::setApplicationName(projectName);
QApplication::setApplicationVersion(projectVersion);
GBAApp application(argc, argv, &configController); GBAApp application(argc, argv, &configController);
#ifndef Q_OS_MAC
QApplication::setWindowIcon(QIcon(":/res/mgba-1024.png"));
#endif
QTranslator qtTranslator; QTranslator qtTranslator;
qtTranslator.load(locale, "qt", "_", QLibraryInfo::location(QLibraryInfo::TranslationsPath)); qtTranslator.load(locale, "qt", "_", QLibraryInfo::location(QLibraryInfo::TranslationsPath));
application.installTranslator(&qtTranslator); application.installTranslator(&qtTranslator);