games/gb: various HDMA fixes

H-blank DMA should only transfer 16 bytes per h-blank, rather than
waiting for the first h-blank and then transferring the whole size.

HDMAC should read 0xff when the transfer is finished, and 0 in the
high bit when the transfer is ongoing. Also, if 0 is written in the
high bit, the current transfer should be aborted.

Introduce two flags, DMAREADY and DMAHBLANK rather than special
constants 1 and -1. If dma is non-zero, there is an ongoing DMA. If
DMAREADY is set, the next chunk is ready to transfer.

Reference: https://gbdev.io/pandocs/#ff55-hdma5-cgb-mode-only-new-dma-length-mode-start

Tested with pokemon crystal.

What was happening is that when the game was loading N background tiles
into vram (each 16 bytes, so one per h-blank), it did something like
this:
- start an hdma transfer for N+1 tiles
- after the Nth tile is transferred, it would read HDMA5, clear the
high bit, then write it back to abort the transfer.

games/gb would instead transfer all N+1 tiles at once, overwriting one
extra tile with whatever was 1 past the end of the source array, and
then would interpret the cancel request as the start of a new transfer
of 16 bytes, which would copy an additional tile past the end. The end
result is that every transfer would end up copying N+2 tiles instead
of just N, overwriting certain tiles with whatever was after the end
of the source data.
This commit is contained in:
Michael Forney 2021-02-08 04:58:41 +01:00
parent 655170c873
commit e502abe001
4 changed files with 21 additions and 13 deletions

View file

@ -8,7 +8,7 @@ extern MBC3Timer timer;
extern uchar vram[16384];
extern int nrom, nback, nbackbank;
extern u32int pal[64];
extern s8int dma;
extern u8int dma;
extern u32int divclock;
extern Event *elist;
@ -114,6 +114,9 @@ enum {
FEATBAT = 2,
FEATTIM = 4,
DMAREADY = 1,
DMAHBLANK = 2,
INIT = -1,
SAVE = -2,
RSTR = -3,

View file

@ -308,7 +308,7 @@ threadmain(int argc, char **argv)
qlock(&pauselock);
qunlock(&pauselock);
}
if(dma > 0)
if(dma & DMAREADY)
t = dmastep();
else
t = step();

View file

@ -15,7 +15,7 @@ int nrom, nback, nbackbank;
u32int divclock;
int prish;
MBC3Timer timer, timerl;
s8int dma;
u8int dma;
u32int white;
u32int moncols[4];
@ -175,7 +175,14 @@ regwrite(u8int a, u8int v)
case HDMAC:
if((mode & COL) == 0)
goto ff;
dma = (v & 0x80) != 0 ? -1 : 1;
if(v & 0x80){
v &= 0x7f;
dma = DMAHBLANK;
}else if(dma){
v |= 0x80;
dma = 0;
}else
dma = DMAREADY;
break;
case NR10: v |= 0x80; goto snd;
case NR14: case NR24: v |= 0x38; goto snd;
@ -534,6 +541,7 @@ meminit(void)
reg[VBK] = 0xfe;
reg[SVBK] = 0xf8;
reg[IF] = 0xe0;
reg[HDMAC] = 0xff;
}
void
@ -583,12 +591,9 @@ dmastep(void)
reg[HDMADL] += 16;
if((reg[HDMADL] & 0xf0) == 0)
reg[HDMADH]++;
if((reg[HDMAC] & 0x7f) == 0)
if(--reg[HDMAC] == 0xff)
dma = 0;
else{
reg[HDMAC]--;
if((reg[HDMAC] & 0x80) != 0)
dma = 1;
}
else if(dma & DMAHBLANK)
dma &= ~DMAREADY;
return 64;
}

View file

@ -338,8 +338,8 @@ hblanktick(void *)
reg[IF] |= IRQLCDS;
t = hblclock + 456 * 2 - clock;
addevent(&evhblank, t < 0 ? 456 * 2 : t);
if(dma < 0)
dma = 1;
if(dma & DMAHBLANK)
dma |= DMAREADY;
break;
}
}