It's difficult to debug other's software, and you picked one of the most difficult things to attempt on the SNES, so instead I'll explain how mid-frame HDMA works, and hope that will help point you in the right direction.
First, at the start of every frame, the following is called no matter what:
void sCPU::hdma_init_reset() {
for(int i = 0; i < 8; i++) {
channel[ i ].hdma_completed = false;
channel[ i ].hdma_do_transfer = false;
}
}
Next, if and only if $420c is not clear, the below is called:
void sCPU::hdma_init() {
for(int i = 0; i < 8; i++) {
if(!channel[ i ].hdma_enabled)continue;
channel[ i ].dma_enabled = false; //HDMA init during DMA will stop DMA mid-transfer
channel[ i ].hdma_addr = channel[ i ].srcaddr;
hdma_update(i);
}
}
void sCPU::hdma_update(uint8 i) {
channel[ i ].hdma_line_counter = r_mem->read(hdma_addr(i));
dma_add_clocks( 8 );
channel[ i ].hdma_completed = (channel[ i ].hdma_line_counter == 0);
channel[ i ].hdma_do_transfer = !channel[ i ].hdma_completed;
if(channel[ i ].hdma_indirect) {
channel[ i ].hdma_iaddr = r_mem->read(hdma_addr(i)) << 8;
dma_add_clocks( 8 );
if(!channel[ i ].hdma_completed || hdma_active_after(i)) {
channel[ i ].hdma_iaddr >>= 8;
channel[ i ].hdma_iaddr |= r_mem->read(hdma_addr(i)) << 8;
dma_add_clocks( 8 );
}
}
}
Next, on every scanline, the below is called, if any [ i ]active[/i], non-terminated channels remain.
void sCPU::hdma_run() {
static uint8 hdma_xferlen[8] = { 1, 2, 2, 4, 4, 4, 2, 4 };
for(int i = 0; i < 8; i++) {
if(hdma_active(i) == false)continue;
channel[ i ].dma_enabled = false; //HDMA run during DMA will stop DMA mid-transfer
dma_add_clocks( 8 );
if(channel[ i ].hdma_do_transfer) {
int xferlen = hdma_xferlen[channel[ i ].xfermode];
for(int index = 0; index < xferlen; index++) {
if(bool(config::cpu.hdma_enable) == true) {
dma_transfer(channel[ i ].direction, dma_bbus(i, index),
!channel[ i ].hdma_indirect ? hdma_addr(i) : hdma_iaddr(i));
} else {
dma_add_clocks( 8 );
cycle_edge();
}
}
}
channel[ i ].hdma_line_counter--;
channel[ i ].hdma_do_transfer = bool(channel[ i ].hdma_line_counter & 0x80);
if((channel[ i ].hdma_line_counter & 0x7f) == 0) {
hdma_update(i);
}
}
}
Notice hdma_do_transfer? This is likely where your problem lies. If $420c == 0 at the start of the frame, do_transfer will be set to false. But fortunately (for you at least), hdma_completed is also set to false, so you can still enable HDMA with trickery. Notice in hdma_run(), which runs every line, that it decrements hdma_line_counter. Then, it sets the important flag, do_transfer.
You need this to get set, the test is do_transfer = bool(line_counter & 0x80), so you'll have to set your starting position to $81 in $43xa, [ i ]one line in advance of where you want mid-frame HDMA to start[/i]. This will turn on your transfer (anything < $80 will not), as well as fulfill the next condition, (line_counter & 0x7f) == 0. Remember, the decrement happens first, before those two tests, hence $81 and not $80. We use $81 to minimize the number of lines you have to setup HDMA in advance to just one. The hdma_update() that is called will load a new line counter value (directly or indirectly, depending on how you setup HDMA), so obviously your transfer won't be stuck in continuous mode or anything. It's just a one line thing to trick HDMA to start.
Lastly, you basically have to simulate hdma_init above, since that won't be called when $420c.dN is 0 at the start of the frame. But you probably already know that part.