mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' (early part) into medusa
This commit is contained in:
commit
21e7d76320
49
CHANGES
49
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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
Elijah Chondropoulos
|
||||
Jaime J. Denizard
|
||||
Fog
|
||||
Philip Horton
|
||||
Oskenso Kashi
|
||||
Mored1984
|
||||
Rohit Nirmal
|
||||
Rhys Powell
|
||||
Yuri Kunde Schlesner
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include <mgba-util/vfs.h>
|
||||
#include <mgba-util/platform/psp2/sce-vfs.h>
|
||||
|
||||
#include <psp2/appmgr.h>
|
||||
#include <psp2/audioout.h>
|
||||
#include <psp2/camera.h>
|
||||
#include <psp2/ctrl.h>
|
||||
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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 <QtAlgorithms>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QFontMetrics>
|
||||
#include <QMessageBox>
|
||||
#include <QMultiMap>
|
||||
#include <QResource>
|
||||
#include <QSettings>
|
||||
#include <QStringList>
|
||||
|
||||
|
@ -27,10 +27,8 @@ BattleChipView::BattleChipView(std::shared_ptr<CoreController> 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<CoreController> 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<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), m_ui.inserted, [this]() {
|
||||
m_ui.inserted->setChecked(Qt::Unchecked);
|
||||
});
|
||||
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);
|
||||
|
@ -61,7 +63,8 @@ BattleChipView::BattleChipView(std::shared_ptr<CoreController> 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<CoreController> 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<CoreController> 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<int> 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<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();
|
||||
}
|
|
@ -5,6 +5,8 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#pragma once
|
||||
|
||||
#include "BattleChipModel.h"
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#include <memory>
|
||||
|
@ -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<int, int> m_chipIndexToId;
|
||||
QMap<int, QString> m_chipIdToName;
|
||||
BattleChipModel m_model;
|
||||
std::shared_ptr<CoreController> m_controller;
|
||||
int m_flavor;
|
||||
|
||||
int m_frameCounter = -1;
|
||||
bool m_next = false;
|
||||
|
||||
Window* m_window;
|
||||
|
||||
BattleChipUpdater* m_updater = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>630</width>
|
||||
<width>658</width>
|
||||
<height>722</height>
|
||||
</rect>
|
||||
</property>
|
||||
|
@ -15,7 +15,7 @@
|
|||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,0,0,0,0,0">
|
||||
<item>
|
||||
<widget class="QListWidget" name="chipList">
|
||||
<widget class="QListView" name="chipList">
|
||||
<property name="acceptDrops">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
|
@ -35,7 +35,7 @@
|
|||
</size>
|
||||
</property>
|
||||
<property name="movement">
|
||||
<enum>QListView::Static</enum>
|
||||
<enum>QListView::Free</enum>
|
||||
</property>
|
||||
<property name="isWrapping" stdset="0">
|
||||
<bool>true</bool>
|
||||
|
@ -52,12 +52,6 @@
|
|||
<property name="viewMode">
|
||||
<enum>QListView::IconMode</enum>
|
||||
</property>
|
||||
<property name="uniformItemSizes">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="selectionRectVisible">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -132,9 +126,9 @@
|
|||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="advanced" native="true">
|
||||
<property name="visible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="visible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
|
@ -201,6 +195,19 @@
|
|||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -260,21 +267,5 @@
|
|||
</hint>
|
||||
</hints>
|
||||
</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>
|
||||
</ui>
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
#include <QFileOpenEvent>
|
||||
#include <QIcon>
|
||||
|
||||
#include <mgba/core/version.h>
|
||||
#include <mgba-util/socket.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
|
@ -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*>("const uint32_t*");
|
||||
qRegisterMetaType<mCoreThread*>("mCoreThread*");
|
||||
|
||||
QApplication::setApplicationName(projectName);
|
||||
QApplication::setApplicationVersion(projectVersion);
|
||||
|
||||
if (!m_configController->getQtOption("displayDriver").isNull()) {
|
||||
Display::setDriver(static_cast<Display::Driver>(m_configController->getQtOption("displayDriver").toInt()));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue