3D Block (Hwang Shinwei)

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

NewRisingSun
Posts: 1143
Joined: Thu May 19, 2005 11:30 am

Re: 3D Block (Hwang Shinwei)

Post by NewRisingSun » Fri Sep 28, 2018 4:22 pm

I have been able to deduce for 3D Block how the main game code communicates with the PIC, and what data is being sent and returned.

As we know, the game can only communicate with the PIC through the upper twelve bits of the CPU address bus. The game does this by calling wait loop procedures that are spaced 16 bytes apart. In the case of 3D Block, there is one wait loop procedure each at E180, E190... E1F0. The PIC sees address E18x for several cycles and concludes that its function 0 is to be executed; it sees address E1Fx and concludes that its function 7 is to be executed --- you get the idea. Both the functions and the addresses that trigger them are game-specific; 3D Block's are incompatible to Block Force's. 3D Block's PIC functions are:
  • Function 0: Main game screen split --- raise one IRQ about 12,400 M2 cycles later.
  • Function 1: Clear internal 32-bit register and prepare for further data --- raise one IRQ immediately to acknowledge.
  • Function 2: Start raising IRQs timed such that a counter in the main game, increased on every IRQ being generated, reaches 247, no lower, no higher. (I don't know the exact timing values, only the result that must be reached in the main game.)
  • Function 3: Send a 1 bit to the PIC. Raise one IRQ immediately to acknowledge.
  • Function 4: Level introduction screen split --- raise several IRQs about 2,175-or-so cycles apart. I don't know the exact duration or how many; I'm basing this on the fact that the pattern table is switched after the IRQ handler has been called six times, and the pattern switch still needs to be about mid-screen for the playfield to not look wong.
  • Function 5: Send a 0 bit to the PIC. Raise one IRQ immediately to acknowledge.
  • Function 6: Receive a result byte from the PIC. The number of IRQs generated resembles the result byte value and can be 0 if no IRQ is generated.
  • Function 7: Reset the IRQ generation counter for subfunction 2.
Functions 7 and 2 are used on the title screen to set the byte at $02D1 to its proper value of $F7. Wrong value, and the blocks that have fallen to the ground already will be displayed incorrectly.
Functions 1, 3 and 5 are used in-game whenever the wire-frame pieces are being redrawn. The main game packs most of its game state into four bytes and sends them to the PIC. After receiving 32 bits, the PIC apparently starts processing them.
Function 6 is called four times to receive four result bytes. Basically, based on the current game state that was sent before, the PIC spits out the next position of the current piece. That is why not emulating the PIC causes the pieces to never move. Mercifully, the proper game logic can be deduced from the later 1990 release of the game.
Functions 0 and 4 are only cosmetic and cause pattern table switches and X position updates in the case of 4.

Block Force has sixteen functions from FE00 to FEF0, again spaced in 16-byte distances:
  • Function 12: status bar during game play. Raise one IRQ after about 25,000 M2 cycles.
  • Function 15: something at bootup. I just raise one IRQ after 6,000 M2 cycles.
  • Functions 9 and 11: unknown, but raising one IRQ immediately minimizes the pauses.
Wait procedure 0 immediately jumps to wait procedure 8, 1 to 9, and so on. Apparently, this version of the PIC program will not execute the same function twice, so functions 0-7 seem to be dummy functions to enable executing functions 8-15 multiple times in a row.

This is all preliminary, because I have not tested the games at length. Block Force is supposed to display an initial high score of 006502, which it does not with the above description. But it's playable with the status bar.

Try a current build of NintendulatorNRS if you want, and be sure to set your unmodified ROM images to NES 2.0 Mapper 355. Credit to krzysiobal for pointing out the 16-bytes-spaced wait loops that signal a function request to the PIC, and credit to me for determining what the subfunctions that have been identified do.

This does not remove the need to have both games' PIC prgoram to be dumped.

lidnariq
Posts: 9131
Joined: Sun Apr 13, 2008 11:12 am
Location: Seattle

Re: 3D Block (Hwang Shinwei)

Post by lidnariq » Fri Sep 28, 2018 8:41 pm

NewRisingSun wrote:Wait procedure 0 immediately jumps to wait procedure 8, 1 to 9, and so on. Apparently, this version of the PIC program will not execute the same function twice, so functions 0-7 seem to be dummy functions to enable executing functions 8-15 multiple times in a row.
I bet that's for better synchronization. An iteration through the tightest possible poll loop takes 3 PIC instruction cycles, or 12 M2. To check for a specific value requires more instructions—loading the value, XORing it, check the flag, repeat—but just polling for a single pin's rising edge is tighter:

Somewhere in Block Force's PIC there's assuredly going to be a pair of instructions:

Code: Select all

btfss PORTB, 1 ;  0x726 → when enciphered should read back as 3
goto $-2 ; encoding unknown; no relative address instructions in 12-bit PIC
CPU A14 through CPU A7 are available on the PIC's 8-bit wide "port B", while CPU A6 through A4 are are the 4-bit wide "port A". This means that the PIC can literally just use PORTA to select the requested function.

Since the poll loop in 3D Block will involve looking for the address $E180, that will be the byte 0xC3 inside the PIC. ((0xE180 >> 7) & 0xFF). It'll most likely show up as either movlw 0xC3 / xorwf PORTB, w (0xCC3 / 0x186 → 3 / F) or movf PORTB, w / xorlw 0xC3 (0x206 / 0xFC3 → 4 / 0) although they could have written the code in various other obfuscatory ways. not C3. E2. Bits are shuffled.
Last edited by lidnariq on Sun Sep 30, 2018 7:03 pm, edited 1 time in total.

NewRisingSun
Posts: 1143
Joined: Thu May 19, 2005 11:30 am

Re: 3D Block (Hwang Shinwei)

Post by NewRisingSun » Sat Sep 29, 2018 7:01 am

I have been able to gain a better understanding of Block Force's PIC functions.

As mentioned before (and as originally found out by Krzysiobal), function 0 is invoked by calling a wait loop at $FE00, function 15 at $FEF0, and so on. The wait loops at functions 0-7 directly jump to the wait loop of the respective function +8. The function is therefore invoked by a rising edge of CPU A7. This was not the case with 3D Block.
  • Function 0: Set PIC RAM address to 0.
  • Function 1: Increase PIC RAM address by 1.
  • Function 2: Set the byte at the current PIC RAM address to $00.
  • Function 3: Increase the byte at the current PIC RAM address.
  • Function 4: Generate an IRQ after about 25,000 M2 cycles. This function is used to time the status bar during gameplay.
  • Function 5: Replace the high score at RAM $1A-$1F with the current score at RAM $14-$19 if the current score is higher.
  • Function 6: Load the PIC latch with the byte at the current PIC RAM address.
  • Function 7: Decrease the PIC latch, and generate an IRQ if it has become zero after decreasing.
The PIC RAM has 32 bytes, and is initialized with zeros except for addresses $1A-$1F, which hold the high score, and which are initialized to 006502 ("02 00 05 06 00 00"). The game also writes the current game level to RAM $08, and $09 in two-player mode, but I have no idea why. I don't know whether the functions other than 4 and 7 generate an IRQ at all, to acknowledge the request; the game would ignore the IRQ if they do.

I have updated the NintendulatorNRS build linked to in my previous post.
Last edited by NewRisingSun on Sun Sep 30, 2018 8:58 am, edited 1 time in total.

lidnariq
Posts: 9131
Joined: Sun Apr 13, 2008 11:12 am
Location: Seattle

Re: 3D Block (Hwang Shinwei)

Post by lidnariq » Sat Sep 29, 2018 11:17 am

NewRisingSun wrote:The PIC RAM has 32 bytes, and is initialized with zeros except for addresses $1A-$1F, which hold the high score, and which are initialized to 006502 ("02 00 05 06 00 00"). The game also writes the current game level to RAM $08, and $09 in two-player mode, but I have no idea why. I don't know whether the functions other than 4 and 7 generate an IRQ at all, to acknowledge the request; the game would ignore the IRQ if they do.
Huh. That's probably directly manipulating the PIC's pointer register ("File Selection Register"). The PIC16C54 only has 25 bytes of RAM (addresses 7-0x1F), in 32 addressable locations. The other 7 addresses are so-called "Special Function Registers" and control things like indirect branching, the current pin i/o values, and a few other things.

Because of this, we should be able to read PCL ("Program Counter Low byte", address 2) to figure out where in the PIC's memory space function 6 is.

lidnariq
Posts: 9131
Joined: Sun Apr 13, 2008 11:12 am
Location: Seattle

Re: 3D Block (Hwang Shinwei)

Post by lidnariq » Sun Sep 30, 2018 10:47 am

Do we have the enciphered dump of Block Force's PIC anywhere?

NewRisingSun
Posts: 1143
Joined: Thu May 19, 2005 11:30 am

Re: 3D Block (Hwang Shinwei)

Post by NewRisingSun » Sun Sep 30, 2018 11:00 am

Right here as an attachment. Or maybe I am misunderstanding your request.

lidnariq
Posts: 9131
Joined: Sun Apr 13, 2008 11:12 am
Location: Seattle

Re: 3D Block (Hwang Shinwei)

Post by lidnariq » Sun Sep 30, 2018 11:07 am

I thought that one was 3D block?

NewRisingSun
Posts: 1143
Joined: Thu May 19, 2005 11:30 am

Re: 3D Block (Hwang Shinwei)

Post by NewRisingSun » Sun Sep 30, 2018 11:14 am

No, the PCB is from a Block Force cartridge. The PCB has just 3-D Block written on it, apparently because Block Force reuses that game's PCB, with different EPROM and PIC ROM content.

lidnariq
Posts: 9131
Joined: Sun Apr 13, 2008 11:12 am
Location: Seattle

Re: 3D Block (Hwang Shinwei)

Post by lidnariq » Sun Sep 30, 2018 3:32 pm

NewRisingSun wrote:The PIC RAM has 32 bytes, and is initialized with zeros except for addresses $1A-$1F, which hold the high score, and which are initialized to 006502 ("02 00 05 06 00 00").
Does the 6502 tell the PIC to do this? Or does the PIC need to have done this on its own?Oh, it's the PIC.

I've been playing "jigsaw puzzle" with the dump, and am pretty certain of this small handful of instructions starting at word 3:

Code: Select all

03 c movlw 0xFF
04 6 tris PORTB
05 e movlw 2
06 9 movwf 0x1A
07 c movlw 0
08 8 movwf 0x1B
09 9 movlw 5
0a f movwf 0x1C
0b a movlw 6
0c e movwf 0x1D
0d c movlw 0
0e d movwf 0x1E
0f c movlw 0
10 c movwf 0x1F
slightly lower confidence for the immediate next eight:

Code: Select all

11 c  movlw 0xdd ; 0xDD corresponds to address 0xFE00
12 f  xorwf PORTB, w
13 0  btfss STATUS,Z ; wait until address bus matches
14 a  goto 0x11
15 c  movlw 0xdd
16 f  xorwf PORTB, w
17 1  btfsc STATUS,Z ; wait until address bus mismatch
18 e  goto 0x15
a hunch:

Code: Select all

30 2 clrf FSR ; f'n 0
31 a incfsz 7,f
32 c incf FSR,f ; f'n 1
33 a incfsz 7,f
34 6 clrf INDF ; f'n 2
35 a incfsz 7,f
36 8 incf INDF,f ; f'n 3
37 a incfsz 7,f
There's also an interesting set of almost-repeats starting at word 4F:
AA61846
AB71146
A441946
A551046
A621E46
A713746
.... I wonder if that's the routine that copies over the score? I can't figure out why it takes 7 instructions per byte instead of 2, though.

lidnariq
Posts: 9131
Joined: Sun Apr 13, 2008 11:12 am
Location: Seattle

Re: 3D Block (Hwang Shinwei)

Post by lidnariq » Mon Oct 01, 2018 5:58 pm

NewRisingSun wrote:Function 5: Replace the high score at RAM $1A-$1F with the current score at RAM $14-$19 if the current score is higher.
PIC RAM has 32 bytes, and is initialized with zeros except for addresses $1A-$1F, which hold the high score, and which are initialized to 006502 ("02 00 05 06 00 00"). The game also writes the current game level to RAM $08, and $09 in two-player mode
Have you seen it use memory addresses other than 8, 9, and 0x14-0x1F ?

Does krzysiobal have the ability to test the PIC's reactions to certain queries? I'm curious what the result is if one tries to read from PIC RAM addresses 0-7.

The repetitions starting at word 4F are probably the bit that compare the score at $1A-$1F with the score at $14-$19, something like this:

Code: Select all

4f a
50 a  movf 0x19,w ; {a,b,4,5,6,7} ^ 3 = {9,8,7,6,5,4} 
51 6  subwf 0x1f,w ; {6,7,4,5,2,3} ^ 9 = {f,e,d,c,b,a}
52 1  btfsc STATUS,Z
53 8  goto 0x57 (8,1,9,0,e) is +7 before enciphering
54 4  btfss STATUS,C
55 6  goto ?

NewRisingSun
Posts: 1143
Joined: Thu May 19, 2005 11:30 am

Re: 3D Block (Hwang Shinwei)

Post by NewRisingSun » Mon Oct 01, 2018 7:08 pm

lidnariq wrote:Have you seen it use memory addresses other than 8, 9, and 0x14-0x1F ?
It uses addresses 0x10 and 0x11 to store values that are immediately returned, basically to test the PIC's function, because it deliberately goes on a code path to nowhere if the just-written value is not properly returned.

lidnariq
Posts: 9131
Joined: Sun Apr 13, 2008 11:12 am
Location: Seattle

Re: 3D Block (Hwang Shinwei)

Post by lidnariq » Tue Oct 02, 2018 12:07 pm

If the PIC only asserted /IRQ for one Tcy (four M2) would that pose a problem for the existing 2A03 code?

Hunch:

Code: Select all

48 2   movf INDF, w ??? ; f'n 6????
49 5   movwf 7
4a a   goto 0x11 ???
4b b   decfsz 7,f ; f'n 7???
4c a   goto 0x11 ???
4d 1   bcf PORTA,0 ???
4e 0   bsf PORTA,0 ???
4f a   goto 0x11 ???

NewRisingSun
Posts: 1143
Joined: Thu May 19, 2005 11:30 am

Re: 3D Block (Hwang Shinwei)

Post by NewRisingSun » Tue Oct 02, 2018 12:47 pm

That would depend on what the 6502 CPU does when an IRQ is raised while an instruction is executing but lowered before that same instruction is finished --- is the IRQ lost? If so, then yes, it would be a problem, otherwise no.

lidnariq
Posts: 9131
Joined: Sun Apr 13, 2008 11:12 am
Location: Seattle

Re: 3D Block (Hwang Shinwei)

Post by lidnariq » Tue Oct 02, 2018 1:08 pm

Yeah, my understanding is that the 6502 will only dispatch an IRQ if /IRQ is asserted during the φ1 of the penultimate cycle of an instruction. (Right?)

The reason I ask is I see that "1 / 0" pattern in two places, which could mean "drive /IRQ low then drive /IRQ high" (bcf PORTA,0 / bsf PORTA,0) and no obvious other places where 1 and 0 are separated by other instructions. Which in turn means it would only be asserted for 4 M2, which makes it highly dependent on the exact code that's interrupted as to whether the 6502 would receive them correctly. (Hence asking)

The other possibility is that it's actually using the tris PORTA instruction to toggle the line between driven low and hiZ; tris PORTA will encipher to 5. There's only one place that could be, but it's not really all that plausible:

unlikely:

Code: Select all

3c f movlw 0xde
3d 5 tris PORTA
3e e movlw 0xdf
3f 5 tris PORTA
Generally one doesn't put a random-feeling number like "0xd" in the don't care bits.

NewRisingSun
Posts: 1143
Joined: Thu May 19, 2005 11:30 am

Re: 3D Block (Hwang Shinwei)

Post by NewRisingSun » Tue Oct 02, 2018 1:27 pm

The 6502 code is extremely obfuscated. We're talking replacing a single STA by a mountain of indirect-indirect-indirectly-addressed transfers, which take 5 or 6 cycles each.

And when reading PIC RAM, the 6502 code cannot afford to miss the IRQ, because it works like this (greatly simplified):

Code: Select all

	JSR	ReadPICValue
	; (...)
	
	; subroutine
ReadPICValue:
	JSR	PIC_LoadLatch	; fn#6
	LDA	#0
	STA	value+0
	STA	value+1
loopy:
	JSR	PIC_DecLatch	; fn#7
	CLC
	LDA	value+0
	ADC	#1
	STA	value+0
	LDA	value+1
	ADC	#0
	STA	value+1
	JMP	loopy
	; no RTS!
	
IRQhandler:
	PLA
	PLA
	PLA
	RTS
	

Post Reply