n64dev / cen64

Cycle-Accurate Nintendo 64 Emulator

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Perfect Dark does not detect expansion pak

iamgreaser opened this issue · comments

Using NTSC USA 8.7 final ROM. Perfect Dark does not detect the expansion pak, and thus the single-player campaign is missing.

This is a known bug.

I started digging into what different games use for expansion pak detection. This problem seems unique to PD. It might be a difference of reading global osMemSize vs. using osGetMemSize() which allegedly performs additional checks by writing to the extended memory. I'll keep digging through PD code to see which method it uses.
perfect dark small

At least a dozen other titles successfully detect the presence of an expansion pak under cen64 (build 30ce1c9)
expansion_pak

I have identified the root cause of PD thinking the expansions pak isn't present, but am currently unsure how to fix it. The issue seems to be that IPL3 thinks the cold reset is an NMI and the game's code takes a different logic path based on this.

There is some code early on in the game around 0x700016C8 (TLB mapped, unsure where this is in physical RAM or ROM) that looks like:

if (reset_type == NMI) { // *0x80000308 == 1
  mem_size = saved_mem_size; // *0x8008dcb4 = *0x803f50b8;
} else { // cold reset
  mem_size = ipl3_mem_size; // *0x8008dcb4 = *0x80000318;
}

Unfortunately, under cen64, reset_type is identified by IPL3 as NMI during a cold reset, so the game tries to use its saved value of mem_size from 803f50b8, which is 0. It compares this mem_size 0x400001: if less than sets a "is_4mb" byte flag at 0x80090af0 = 1, else sets 0.

I've checked other games under cen64 and the word at 0x80000308 always appears to be 1 (reset_type = NMI) at the game entry point.

Interesting. I am not sure why this would be. I verified that Status.SR = 0 at CPU boot.

As you indicated, I see Status.SR (bit 20) = 0 at boot, but when the reset type is handed off to IPL3 through S5, it is 1 (reset_type = NMI). To verify this, I added the following code to vr4300_ex_stage() in vr4300/pipeline.c:

  if (rfex_latch->common.pc == 0xffffffffa4000040) { // IPL3
    debug("S3 = %08X\nS4 = %08X\nS5 = %08X\n",
      vr4300->regs[VR4300_REGISTER_S3],
      vr4300->regs[VR4300_REGISTER_S4],
      vr4300->regs[VR4300_REGISTER_S5]);
  }

Output:

S3 = 00000000
S4 = 00000001
S5 = 00000001

I don't yet know enough to understand how the data is propagated from the CP0 Status register into S5, but it looks like IPL2 is expecting the PIF to set magic bits set in PIF RAM offset 0x24 (bfc007e4/1fc007e4): S5 = ((*0xbfc007e4) >> 17) & 1. For Perfect Dark, the word loaded from 0xbfc007e4 is 0x2913F which is the CIC_SEED_NUS_6105. Is this seed value supposed to be stored here? Is the PIF supposed to overwrite it with something else at some point?

To increase my confidence that this is the issue with PD, I set a breakpoint on the debug() line above and merely change vr4300->regs[VR4300_REGISTER_S5] = 0 and the expansion pak detection in PD worked as expected:
image

I asked for clarification over on #n64dev and got a lot of good information. Long description below, but the problem is that the word in PIF RAM offset 0x24 contains status bits in addition to the CIC seed values. Specifically, bit 17 is the reset type (1 = NMI, 0 = cold reset).

bits     | reg | description
00080000 | S3  | osRomType (0=GamePack, 1=DD)
00040000 | S7  | osVersion
00020000 | S5  | osResetType (1 = NMI, 0 = cold reset)
0000FF00 | S6  | CIC IPL3 seed value
000000FF | --  | CIC IPL2 seed value
-------- | S4  | TV Type (0=PAL, 1=NTSC, 2=MPAL)

Therefore, I think the CIC seeds in si/cic.c should be updated as below and if NMI needs to be generated, 0x00020000 should be set. I've only tested this with Perfect Dark so far.

#define CIC_SEED_NUS_5101 0x0000AC00U
#define CIC_SEED_NUS_6101 0x00043F3FU
#define CIC_SEED_NUS_6102 0x00003F3FU
#define CIC_SEED_NUS_6103 0x0000783FU
#define CIC_SEED_NUS_6105 0x0000913FU
#define CIC_SEED_NUS_6106 0x0000853FU
#define CIC_SEED_NUS_8303 0x0000DD00U

For completeness, here is a summary of the N64 boot.

  • PIF talks with CIC and receives CIC seed value during PIF reset
  • PIF writes CIC seed value and other bits to word in PIF RAM at offset 0x24
  • IPL1 boots from PIF ROM
    • IPL1 sets VR4300 config and status registers, waits for SP to halt and SP DMA to finish
    • IPL1 resets PI, VI, AI registers
    • IPL1 copies IPL2 from PIF ROM to SP IMEM
  • IPL2 is jumped to from IPL1
    • IPL2 waits for PIF to signal PIF<-> CIC communications are finished
    • IPL2 reads CIC seed value and status bits from 0x24 and stores values in S3, S4, S5, S6, S7
    • IPL2 copies IPL3 from cart ROM to DMEM
  • IPL3 is jumped to from IPL2
    • IPL3 initializes RDRAM, clears ICache, DCache, traces of IPL1/IPL2
    • IPL3 copies cart code to RDRAM and verifies checksum

Thanks to _Happy_, marshallh, Zoinkity for explaining this info.

Looks good, and thanks for the info. Free free to make a PR... you've done all the hard work!