diff --git a/CHANGES b/CHANGES index da98501e3..7745f02da 100644 --- a/CHANGES +++ b/CHANGES @@ -27,33 +27,40 @@ Bugfixes: - GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208) - GBA: Reset now reloads multiboot ROMs - GBA BIOS: Fix multiboot entry point (fixes Magic Floor) - - Switch: Fix final cleanup (fixes mgba.io/i/1283) - - 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) + - Qt: More app metadata fixes Misc: - GBA Savedata: EEPROM performance fixes - GBA Savedata: Automatically map 1Mbit Flash files as 1Mbit Flash - GB Memory: Support running from blocked memory - 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 + +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) 0.7.0: (2019-01-26) diff --git a/include/mgba/internal/gba/gba.h b/include/mgba/internal/gba/gba.h index 4bd899fe6..f1b3fa2f5 100644 --- a/include/mgba/internal/gba/gba.h +++ b/include/mgba/internal/gba/gba.h @@ -143,8 +143,8 @@ void GBADestroy(struct GBA* gba); void GBAReset(struct ARMCore* cpu); void GBASkipBIOS(struct GBA* gba); -void GBARaiseIRQ(struct GBA* gba, enum GBAIRQ irq); -void GBATestIRQ(struct ARMCore* cpu); +void GBARaiseIRQ(struct GBA* gba, enum GBAIRQ irq, uint32_t cyclesLate); +void GBATestIRQ(struct GBA* gba, uint32_t cyclesLate); void GBAHalt(struct GBA* gba); void GBAStop(struct GBA* gba); void GBADebug(struct GBA* gba, uint16_t value); diff --git a/res/patrons.txt b/res/patrons.txt index deaca59b2..306534b40 100644 --- a/res/patrons.txt +++ b/res/patrons.txt @@ -1,8 +1,8 @@ -Elijah Chondropoulos Jaime J. Denizard Fog Philip Horton Oskenso Kashi +Mored1984 Rohit Nirmal Rhys Powell Yuri Kunde Schlesner diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index 0afbd355e..50c9dea42 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -271,7 +271,17 @@ static void _log(struct mLogger* logger, int category, enum mLogLevel level, con if (len >= sizeof(log2)) { 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) { diff --git a/src/gba/dma.c b/src/gba/dma.c index eb66635c4..bb13350a9 100644 --- a/src/gba/dma.c +++ b/src/gba/dma.c @@ -193,7 +193,7 @@ void _dmaEvent(struct mTiming* timing, void* context, uint32_t cyclesLate) { dma->nextDest = dma->dest; } if (GBADMARegisterIsDoIRQ(dma->reg)) { - GBARaiseIRQ(gba, IRQ_DMA0 + memory->activeDMA); + GBARaiseIRQ(gba, IRQ_DMA0 + memory->activeDMA, cyclesLate); } GBADMAUpdate(gba); } @@ -260,15 +260,13 @@ void GBADMAService(struct GBA* gba, int number, struct GBADMA* info) { } gba->bus = memory->dmaTransferRegister; cpu->memory.store32(cpu, dest, memory->dmaTransferRegister, 0); - memory->dmaTransferRegister &= 0xFFFF0000; - memory->dmaTransferRegister |= memory->dmaTransferRegister >> 16; } else { if (sourceRegion == REGION_CART2_EX && (memory->savedata.type == SAVEDATA_EEPROM || memory->savedata.type == SAVEDATA_EEPROM512)) { memory->dmaTransferRegister = GBASavedataReadEEPROM(&memory->savedata); - } else { - if (source) { - memory->dmaTransferRegister = cpu->memory.load16(cpu, source, 0); - } + memory->dmaTransferRegister |= memory->dmaTransferRegister << 16; + } else if (source) { + memory->dmaTransferRegister = cpu->memory.load16(cpu, source, 0); + memory->dmaTransferRegister |= memory->dmaTransferRegister << 16; } if (destRegion == REGION_CART2_EX) { 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); } - memory->dmaTransferRegister |= memory->dmaTransferRegister << 16; gba->bus = memory->dmaTransferRegister; } int sourceOffset = DMA_OFFSET[GBADMARegisterGetSrcControl(info->reg)] * width; diff --git a/src/gba/extra/battlechip.c b/src/gba/extra/battlechip.c index 94d591efa..b76247cd0 100644 --- a/src/gba/extra/battlechip.c +++ b/src/gba/extra/battlechip.c @@ -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->normalControl.start = 0; if (gate->d.p->normalControl.irq) { - GBARaiseIRQ(gate->d.p->p, IRQ_SIO); + GBARaiseIRQ(gate->d.p->p, IRQ_SIO, cyclesLate); } 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; if (gate->d.p->multiplayerControl.irq) { - GBARaiseIRQ(gate->d.p->p, IRQ_SIO); + GBARaiseIRQ(gate->d.p->p, IRQ_SIO, cyclesLate); } } diff --git a/src/gba/gba.c b/src/gba/gba.c index 1ee7718ca..b7a6d9917 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -46,6 +46,7 @@ static void GBAProcessEvents(struct ARMCore* cpu); static void GBAHitStub(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 GBATestIRQNoDelay(struct ARMCore* cpu); static void _triggerIRQ(struct mTiming*, void* user, uint32_t cyclesLate); @@ -180,7 +181,7 @@ void GBAInterruptHandlerInit(struct ARMInterruptHandler* irqh) { irqh->swi16 = GBASwi16; irqh->swi32 = GBASwi32; irqh->hitIllegal = GBAIllegal; - irqh->readCPSR = GBATestIRQ; + irqh->readCPSR = GBATestIRQNoDelay; irqh->hitStub = GBAHitStub; irqh->bkpt16 = GBABreakpoint; irqh->bkpt32 = GBABreakpoint; @@ -433,7 +434,7 @@ void GBAYankROM(struct GBA* gba) { gba->yankedRomSize = gba->memory.romSize; gba->memory.romSize = 0; gba->memory.romMask = 0; - GBARaiseIRQ(gba, IRQ_GAMEPAK); + GBARaiseIRQ(gba, IRQ_GAMEPAK, 0); } 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); } -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; - GBATestIRQ(gba->cpu); + GBATestIRQ(gba, cyclesLate); } -void GBATestIRQ(struct ARMCore* cpu) { +void GBATestIRQNoDelay(struct ARMCore* cpu) { 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 (!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; if (isAnd && keycnt == keyInput) { - GBARaiseIRQ(gba, IRQ_KEYPAD); + GBARaiseIRQ(gba, IRQ_KEYPAD, 0); } else if (!isAnd && keyInput) { - GBARaiseIRQ(gba, IRQ_KEYPAD); + GBARaiseIRQ(gba, IRQ_KEYPAD, 0); } } diff --git a/src/gba/hardware.c b/src/gba/hardware.c index bdda9cdf2..42a0fd950 100644 --- a/src/gba/hardware.c +++ b/src/gba/hardware.c @@ -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_HI >> 1] = tx >> 16; 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->p->p->memory.io[REG_SIOCNT >> 1] = gbp->d.p->siocnt & ~0x0080; diff --git a/src/gba/io.c b/src/gba/io.c index 734a80844..897b6ce5e 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -548,16 +548,16 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) { break; case REG_IE: gba->memory.io[REG_IE >> 1] = value; - GBATestIRQ(gba->cpu); + GBATestIRQ(gba->cpu, 1); return; case REG_IF: value = gba->memory.io[REG_IF >> 1] & ~value; gba->memory.io[REG_IF >> 1] = value; - GBATestIRQ(gba->cpu); + GBATestIRQ(gba->cpu, 1); return; case REG_IME: gba->memory.io[REG_IME >> 1] = value; - GBATestIRQ(gba->cpu); + GBATestIRQ(gba->cpu, 1); return; case REG_MAX: // Some bad interrupt libraries will write to this diff --git a/src/gba/sio.c b/src/gba/sio.c index 800af8aea..95e860363 100644 --- a/src/gba/sio.c +++ b/src/gba/sio.c @@ -158,7 +158,7 @@ void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value) { if ((value & 0x0081) == 0x0081) { if (value & 0x4000) { // TODO: Test this on hardware to see if this is correct - GBARaiseIRQ(sio->p, IRQ_SIO); + GBARaiseIRQ(sio->p, IRQ_SIO, 0); } value &= ~0x0080; } diff --git a/src/gba/sio/joybus.c b/src/gba/sio/joybus.c index e7aa934e6..8d6fc2d77 100644 --- a/src/gba/sio/joybus.c +++ b/src/gba/sio/joybus.c @@ -37,7 +37,7 @@ int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command case JOY_RESET: sio->p->p->memory.io[REG_JOYCNT >> 1] |= 1; 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 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]; 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; 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]; 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; } diff --git a/src/gba/sio/lockstep.c b/src/gba/sio/lockstep.c index c196fc3fa..848dc9b4d 100644 --- a/src/gba/sio/lockstep.c +++ b/src/gba/sio/lockstep.c @@ -166,7 +166,7 @@ static void _finishTransfer(struct GBASIOLockstepNode* node) { sio->multiplayerControl.busy = 0; sio->multiplayerControl.id = node->id; if (sio->multiplayerControl.irq) { - GBARaiseIRQ(sio->p, IRQ_SIO); + GBARaiseIRQ(sio->p, IRQ_SIO, 0); } break; case SIO_NORMAL_8: @@ -179,7 +179,7 @@ static void _finishTransfer(struct GBASIOLockstepNode* node) { node->d.p->p->memory.io[REG_SIODATA8 >> 1] = 0xFFFF; } if (sio->multiplayerControl.irq) { - GBARaiseIRQ(sio->p, IRQ_SIO); + GBARaiseIRQ(sio->p, IRQ_SIO, 0); } break; 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; } if (sio->multiplayerControl.irq) { - GBARaiseIRQ(sio->p, IRQ_SIO); + GBARaiseIRQ(sio->p, IRQ_SIO, 0); } break; default: diff --git a/src/gba/timer.c b/src/gba/timer.c index b7fe943d9..2c84a7052 100644 --- a/src/gba/timer.c +++ b/src/gba/timer.c @@ -13,10 +13,10 @@ #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]; 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; GBATimerUpdateAudio(gba, 0, 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)) { - 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; GBATimerUpdateAudio(gba, 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)) { - GBATimerIrq(gba, 2); + GBATimerIrq(gba, 2, cyclesLate); } } static void GBATimerUpdate2(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBA* gba = context; 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)) { - GBATimerIrq(gba, 3); + GBATimerIrq(gba, 3, cyclesLate); } } static void GBATimerUpdate3(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBA* gba = context; 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) { diff --git a/src/gba/video.c b/src/gba/video.c index 8a8b8f830..a1dae1dbf 100644 --- a/src/gba/video.c +++ b/src/gba/video.c @@ -154,7 +154,7 @@ void _startHdraw(struct mTiming* timing, void* context, uint32_t cyclesLate) { if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) { dispstat = GBARegisterDISPSTATFillVcounter(dispstat); if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) { - GBARaiseIRQ(video->p, IRQ_VCOUNTER); + GBARaiseIRQ(video->p, IRQ_VCOUNTER, cyclesLate); } } else { dispstat = GBARegisterDISPSTATClearVcounter(dispstat); @@ -173,7 +173,7 @@ void _startHdraw(struct mTiming* timing, void* context, uint32_t cyclesLate) { } GBADMARunVblank(video->p, -cyclesLate); if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) { - GBARaiseIRQ(video->p, IRQ_VBLANK); + GBARaiseIRQ(video->p, IRQ_VBLANK, cyclesLate); } GBAFrameEnded(video->p); mCoreSyncPostFrame(video->p->sync); @@ -209,7 +209,7 @@ void _startHblank(struct mTiming* timing, void* context, uint32_t cyclesLate) { GBADMARunDisplayStart(video->p, -cyclesLate); } if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) { - GBARaiseIRQ(video->p, IRQ_HBLANK); + GBARaiseIRQ(video->p, IRQ_HBLANK, cyclesLate); } video->p->memory.io[REG_DISPSTAT >> 1] = dispstat; } diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index 8060d26ae..f5843a911 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -160,7 +160,8 @@ int main() { .unpaused = mPSP2Unpaused, .incrementScreenMode = mPSP2IncrementScreenMode, .setFrameLimiter = mPSP2SetFrameLimiter, - .pollGameInput = mPSP2PollInput + .pollGameInput = mPSP2PollInput, + .running = mPSP2SystemPoll }; sceTouchSetSamplingState(SCE_TOUCH_PORT_FRONT, SCE_TOUCH_SAMPLING_STATE_START); diff --git a/src/platform/psp2/psp2-context.c b/src/platform/psp2/psp2-context.c index 41e3a4a11..98f44934c 100644 --- a/src/platform/psp2/psp2-context.c +++ b/src/platform/psp2/psp2-context.c @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -38,6 +39,9 @@ #define RUMBLE_PWM 8 #define CDRAM_ALIGN 0x40000 +mLOG_DECLARE_CATEGORY(GUI_PSP2); +mLOG_DEFINE_CATEGORY(GUI_PSP2, "Vita", "gui.psp2"); + static enum ScreenMode { SM_BACKDROP, SM_PLAIN, @@ -539,6 +543,18 @@ void mPSP2IncrementScreenMode(struct mGUIRunner* runner) { 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) { printf("ASSERT FAILED: %s in %s at %s:%i\n", expr, func, file, line); exit(1); diff --git a/src/platform/psp2/psp2-context.h b/src/platform/psp2/psp2-context.h index 28a403ba3..389f9f534 100644 --- a/src/platform/psp2/psp2-context.h +++ b/src/platform/psp2/psp2-context.h @@ -25,5 +25,6 @@ void mPSP2DrawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsig void mPSP2IncrementScreenMode(struct mGUIRunner* runner); void mPSP2SetFrameLimiter(struct mGUIRunner* runner, bool limit); uint16_t mPSP2PollInput(struct mGUIRunner* runner); +bool mPSP2SystemPoll(struct mGUIRunner* runner); #endif diff --git a/src/platform/qt/AbstractUpdater.cpp b/src/platform/qt/AbstractUpdater.cpp new file mode 100644 index 000000000..c165ae1a0 --- /dev/null +++ b/src/platform/qt/AbstractUpdater.cpp @@ -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 +#include + +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); +} diff --git a/src/platform/qt/AbstractUpdater.h b/src/platform/qt/AbstractUpdater.h new file mode 100644 index 000000000..298fe7a82 --- /dev/null +++ b/src/platform/qt/AbstractUpdater.h @@ -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 +#include +#include + +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; +}; + +} \ No newline at end of file diff --git a/src/platform/qt/BattleChipModel.cpp b/src/platform/qt/BattleChipModel.cpp new file mode 100644 index 000000000..bd65dfebe --- /dev/null +++ b/src/platform/qt/BattleChipModel.cpp @@ -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 +#include +#include + +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 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; +} \ No newline at end of file diff --git a/src/platform/qt/BattleChipModel.h b/src/platform/qt/BattleChipModel.h new file mode 100644 index 000000000..c0f115896 --- /dev/null +++ b/src/platform/qt/BattleChipModel.h @@ -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 +#include + +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 chipNames() const { return m_chipIdToName; } + +public slots: + void setFlavor(int); + void addChip(int id); + void removeChip(const QModelIndex&); + void setChips(QList ids); + void clear(); + void setScale(int); + void reloadAssets(); + +private: + struct BattleChip { + int id; + QString name; + QPixmap icon; + }; + + BattleChip createChip(int id) const; + + QMap m_chipIdToName; + int m_flavor; + int m_scale = 1; + + QList m_deck; +}; + +} \ No newline at end of file diff --git a/src/platform/qt/BattleChipUpdater.cpp b/src/platform/qt/BattleChipUpdater.cpp new file mode 100644 index 000000000..d2d919a99 --- /dev/null +++ b/src/platform/qt/BattleChipUpdater.cpp @@ -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 +#include +#include +#include + +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"; +} \ No newline at end of file diff --git a/src/platform/qt/BattleChipUpdater.h b/src/platform/qt/BattleChipUpdater.h new file mode 100644 index 000000000..4ec06e127 --- /dev/null +++ b/src/platform/qt/BattleChipUpdater.h @@ -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; +}; + +} \ No newline at end of file diff --git a/src/platform/qt/BattleChipView.cpp b/src/platform/qt/BattleChipView.cpp index 42cca1fc4..7baa7deb5 100644 --- a/src/platform/qt/BattleChipView.cpp +++ b/src/platform/qt/BattleChipView.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "BattleChipView.h" +#include "BattleChipUpdater.h" #include "ConfigController.h" #include "CoreController.h" #include "GBAApp.h" @@ -12,11 +13,10 @@ #include "Window.h" #include -#include +#include #include #include #include -#include #include #include @@ -27,10 +27,8 @@ BattleChipView::BattleChipView(std::shared_ptr controller, Windo , m_controller(controller) , m_window(window) { - QResource::registerResource(GBAApp::dataDir() + "/chips.rcc", "/exe"); - QResource::registerResource(ConfigController::configDir() + "/chips.rcc", "/exe"); - m_ui.setupUi(this); + m_ui.chipList->setModel(&m_model); char title[9]; CoreController::Interrupter interrupter(m_controller); @@ -46,12 +44,16 @@ BattleChipView::BattleChipView(std::shared_ptr controller, Windo #endif m_ui.chipList->setIconSize(m_ui.chipList->iconSize() * size); m_ui.chipList->setGridSize(m_ui.chipList->gridSize() * size); + m_model.setScale(size); connect(m_ui.chipId, static_cast(&QSpinBox::valueChanged), m_ui.inserted, [this]() { m_ui.inserted->setChecked(Qt::Unchecked); }); connect(m_ui.chipName, static_cast(&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); @@ -61,7 +63,8 @@ BattleChipView::BattleChipView(std::shared_ptr controller, Windo connect(controller.get(), &CoreController::stopping, this, &QWidget::close); connect(m_ui.save, &QAbstractButton::clicked, this, &BattleChipView::saveDeck); 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) { if (on) { @@ -85,13 +88,14 @@ BattleChipView::BattleChipView(std::shared_ptr controller, Windo connect(m_controller.get(), &CoreController::frameAvailable, this, &BattleChipView::advanceFrameCounter); - connect(m_ui.chipList, &QListWidget::itemClicked, this, [this](QListWidgetItem* item) { - QVariant chip = item->data(Qt::UserRole); + connect(m_ui.chipList, &QAbstractItemView::clicked, this, [this](const QModelIndex& index) { + QVariant chip = m_model.data(index, Qt::UserRole); bool blocked = m_ui.chipId->blockSignals(true); m_ui.chipId->setValue(chip.toInt()); m_ui.chipId->blockSignals(blocked); reinsert(); }); + connect(m_ui.chipList, &QListView::indexesMoved, this, &BattleChipView::resort); m_controller->attachBattleChipGate(); setFlavor(4); @@ -102,6 +106,18 @@ BattleChipView::BattleChipView(std::shared_ptr controller, Windo } else if (qtitle.startsWith("AGB-BR5") || qtitle.startsWith("AGB-BR6")) { 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() { @@ -110,7 +126,9 @@ BattleChipView::~BattleChipView() { void BattleChipView::setFlavor(int 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) { @@ -141,55 +159,13 @@ void BattleChipView::addChip() { if (insertedChip < 1) { return; } - addChipId(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); + m_model.addChip(insertedChip); } void BattleChipView::removeChip() { - qDeleteAll(m_ui.chipList->selectedItems()); -} - -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; + for (const auto& index : m_ui.chipList->selectionModel()->selectedIndexes()) { + m_model.removeChip(index); } - 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() { @@ -208,14 +184,14 @@ void BattleChipView::saveDeck() { } QStringList deck; - for (int i = 0; i < m_ui.chipList->count(); ++i) { - deck.append(m_ui.chipList->item(i)->data(Qt::UserRole).toString()); + for (int i = 0; i < m_model.rowCount(); ++i) { + deck.append(m_model.data(m_model.index(i, 0), Qt::UserRole).toString()); } QSettings ini(filename, QSettings::IniFormat); ini.clear(); ini.beginGroup("BattleChipDeck"); - ini.setValue("version", m_flavor); + ini.setValue("version", m_model.flavor()); ini.setValue("deck", deck.join(',')); ini.sync(); } @@ -229,7 +205,7 @@ void BattleChipView::loadDeck() { QSettings ini(filename, QSettings::IniFormat); ini.beginGroup("BattleChipDeck"); int flavor = ini.value("version").toInt(); - if (flavor != m_flavor) { + if (flavor != m_model.flavor()) { QMessageBox* error = new QMessageBox(this); error->setIcon(QMessageBox::Warning); error->setStandardButtons(QMessageBox::Ok); @@ -240,13 +216,47 @@ void BattleChipView::loadDeck() { return; } - m_ui.chipList->clear(); + QList newDeck; QStringList deck = ini.value("deck").toString().split(','); for (const auto& item : deck) { bool ok; int id = item.toInt(&ok); if (ok) { - addChipId(id); + newDeck.append(id); } } + m_model.setChips(newDeck); } + +void BattleChipView::resort() { + QMap 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(); +} \ No newline at end of file diff --git a/src/platform/qt/BattleChipView.h b/src/platform/qt/BattleChipView.h index 88a24d8a3..0617c6f98 100644 --- a/src/platform/qt/BattleChipView.h +++ b/src/platform/qt/BattleChipView.h @@ -5,6 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #pragma once +#include "BattleChipModel.h" + #include #include @@ -29,16 +31,18 @@ public slots: void setFlavor(int); void insertChip(bool); void reinsert(); + void resort(); private slots: void advanceFrameCounter(); void addChip(); - void addChipId(int); void removeChip(); void saveDeck(); void loadDeck(); + void updateData(); + private: static const int UNINSERTED_TIME = 10; @@ -46,15 +50,15 @@ private: Ui::BattleChipView m_ui; - QMap m_chipIndexToId; - QMap m_chipIdToName; + BattleChipModel m_model; std::shared_ptr m_controller; - int m_flavor; int m_frameCounter = -1; bool m_next = false; Window* m_window; + + BattleChipUpdater* m_updater = nullptr; }; } diff --git a/src/platform/qt/BattleChipView.ui b/src/platform/qt/BattleChipView.ui index f379a3bdc..0b50fe0be 100644 --- a/src/platform/qt/BattleChipView.ui +++ b/src/platform/qt/BattleChipView.ui @@ -6,7 +6,7 @@ 0 0 - 630 + 658 722 @@ -15,7 +15,7 @@ - + true @@ -35,7 +35,7 @@ - QListView::Static + QListView::Free true @@ -52,12 +52,6 @@ QListView::IconMode - - false - - - true - @@ -132,9 +126,9 @@ - - false - + + false + @@ -201,6 +195,19 @@ + + + + + 0 + 0 + + + + Update Chip data + + + @@ -260,21 +267,5 @@ - - chipList - indexesMoved(QModelIndexList) - chipList - doItemsLayout() - - - 314 - 203 - - - 314 - 203 - - - diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index af84ebbbf..eebcf3d35 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -67,6 +67,7 @@ endif() set(SOURCE_FILES AboutScreen.cpp + AbstractUpdater.cpp AssetTile.cpp AssetView.cpp AudioProcessor.cpp @@ -149,6 +150,8 @@ set(UI_FILES VideoView.ui) set(GBA_SRC + BattleChipModel.cpp + BattleChipUpdater.cpp BattleChipView.cpp GBAOverride.cpp) diff --git a/src/platform/qt/GBAApp.cpp b/src/platform/qt/GBAApp.cpp index b88328778..6a25d71c1 100644 --- a/src/platform/qt/GBAApp.cpp +++ b/src/platform/qt/GBAApp.cpp @@ -18,7 +18,6 @@ #include #include -#include #include #include @@ -42,17 +41,10 @@ GBAApp::GBAApp(int& argc, char* argv[], ConfigController* config) SDL_Init(SDL_INIT_NOPARACHUTE); #endif -#ifndef Q_OS_MAC - setWindowIcon(QIcon(":/res/mgba-512.png")); -#endif - SocketSubsystemInit(); qRegisterMetaType("const uint32_t*"); qRegisterMetaType("mCoreThread*"); - QApplication::setApplicationName(projectName); - QApplication::setApplicationVersion(projectVersion); - if (!m_configController->getQtOption("displayDriver").isNull()) { Display::setDriver(static_cast(m_configController->getQtOption("displayDriver").toInt())); } diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 8cdae1e11..b4ef8c0cd 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -107,7 +107,6 @@ Window::Window(CoreManager* manager, ConfigController* config, int playerId, QWi m_logo.setDevicePixelRatio(m_screenWidget->devicePixelRatio()); m_logo = m_logo; // Free memory left over in old pixmap - setWindowIcon(m_logo); #if defined(M_CORE_GBA) float i = 2; diff --git a/src/platform/qt/main.cpp b/src/platform/qt/main.cpp index ae7992157..8edc96e79 100644 --- a/src/platform/qt/main.cpp +++ b/src/platform/qt/main.cpp @@ -61,8 +61,15 @@ int main(int argc, char* argv[]) { return 0; } + QApplication::setApplicationName(projectName); + QApplication::setApplicationVersion(projectVersion); + GBAApp application(argc, argv, &configController); +#ifndef Q_OS_MAC + QApplication::setWindowIcon(QIcon(":/res/mgba-1024.png")); +#endif + QTranslator qtTranslator; qtTranslator.load(locale, "qt", "_", QLibraryInfo::location(QLibraryInfo::TranslationsPath)); application.installTranslator(&qtTranslator);