Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Xplorer64 is similar to the GameShark/Action Replay line. By all appearances the firmware was written by 'sceners and, frankly, is a bit buggy. In other respects it is less invasive than the GS/AR (it undoes many of the changes it makes to hook into the exception handler) and somewhat more convenient for end-users (simpler method of booting non-6101/7102 CICs).
- For lack of any real documentation, here's some info gleaned from a partial disassembly of the more interesting parts of the device.
- .: Codelists :.
- There are three modes of operation, each with its own code limit. This isn't a total count of codes but instead allocated space for the ASM they generate. If 8MB of memory are present the default is to always run from the upper 4MB with a maximum* of 170 codes.
- How many opcodes each code generates is listed in the "Normal Xplorer Code Types" section below.
- Mode 0) opcode limit: 78 - 2 opcodes, so 25 normal codes
- Mode 1) opcode limit: 38 - 2 opcodes, so 12 normal codes
- Mode 2) opcode limit: 514 - 2 opcodes, so 170 normal codes.
- This leads to the first bug. Two opcodes are needed for the return from the codelist but these aren't reserved ahead of time. When generating ASM, the list automatically cuts off if the end of the allocation is reached or exceeded. That means it's both possible to overflow the normal end and to overflow when the terminating jump is added.
- Despite these limits it will parse up to 512 codes. That's more than the device holds, but whatever.
- .: Special Code Types :.
- These codetypes are applied before the game is executed, used to circumvent protections or manipulate how the hook is generated.
- Only a single 2* code type may be used. This is applied once, before the game is booted. Its most common use is to eliminate an opcode during initialization that removes the Watch.
- 21.aaaaaa 00dd write byte [d]ata
- 22.aaaaaa dddd write halfword [d]ata
- 24.aaaaaa dddd write unsigned HW [d]ata as a word; upper halfword is always zero.
- 29.aaaaaa 00dd write byte [d]ata to an uncached address (A0000000 instead of 80000000)
- 2A.aaaaaa dddd write halfword [d]ata to an uncached address
- 2C.aaaaaa dddd write unsigned HW [d]ata as a word to an uncached address
- These two special codes affect how the hook is set.
- 3FFFFF.FE dddd
- Shifts the codelist return jump. The value [d]ata is ORed to the base jump instruction to create a new pointer. Depending on mode, the base jump is either 80000000 (08000000) or 80750000 (081D4000). To set [d]ata, take your target pointer, subtract the base, then shift right 2.
- Note that because the value is ORed it isn't possible to set pointers in the range 80760000 to 80770000.
- 3FFFFF.FF cccc
- Allow Watch to trigger [c]ount times before removing the Watch. If set to zero, defaults back to 1.
- This is useful for 64DD expansion games where the disk will "false reset", causing the exception handler to be written again.
- .: Normal Xplorer Code Types :.
- Any GS/AR style 8* or D* types are converted into Xplorer format before generating ASM for them.
- It should be noted that at some point the format changed from the bitflag method used in 2* codes to what appears below. Masks for the relevent flags are still present but values ignored.
- It doesn't fix unaligned addresses like a GS/AR will, and a HW write to an odd address (as in not even) may crash the device. Likewise, word write addresses should be multiples of 4.
- Unlike the GS/AR loop types are actual loops, so as long as you write at least four codes with them you'll show some kind of gain (11 opcodes per loop vs 3 for a normal code). Any less and you might as well expand the codes by hand.
- 00.aaaaaa dddd store unsigned HW as word
- 3 opcodes
- Writes unsigned HW [d]ata to the word at [a]ddress. The upper two bytes will always be zero as a result.
- 3C1A**** LUI K0,{a}
- 341B00** ORI K1,R0,{d}
- AF5B**** SW K1,{a} (K0)
- 30.aaaaaa 00dd store byte
- 3 opcodes
- Writes byte [d]ata to [a]ddress.
- 3C1A**** LUI K0,{a}
- 341B00** ORI K1,R0,{d}
- A35B**** SB K1,{a} (K0)
- 50.cccc.AA DDDD 0f.aaaaaa dddd loop type 1
- 11 opcodes
- Writes [c]ount codes, incrementing the [a]ddress by [A] and [d]ata by [D]. This is an actual loop, only 11 opcodes long.
- If [f] is 0 writes bytes, if [f] is 1 writes HWs.
- 4081F000 MTC0 AT,ErrorPC (just storing the register safely)
- 3C1A**** LUI K0,{a}
- 275A**** ADDIU K0,K0,{a}
- 341B**** ORI K1,R0,{d}
- 3401**** ORI AT,R0,{c}
- A35B0000 / A75B0000 SB/SH K1,0000 (K0) (depending on [f])
- 277B**** ADDIU K1,K1,{D}
- 275A00** ADDIU K0,K0,{A}
- 1420FFFC BNE AT,R0,-4
- 2421FFFF ADDIU AT,AT,FFFF
- 4001F000 MFC0 AT,ErrorPC (restoring the register)
- 70.aaaaaa dddd unsigned HW equal
- 5 opcodes
- Next code is run if HW at [a]ddress matches unsigned HW [d]ata.
- * Bugged
- The value at the address is loaded as a signed value but tested against an unsigned value. A test for a negative value will always be False!
- 3C1A**** LUI K0,{a}
- 875A**** LH K0,{a} (K0)
- 341B**** ORI K1,R0,{d}
- 175B**** BNE K0,K1,{len next code}
- 00000000 NOP
- 80.aaaaaa dddd store HW
- 3 opcodes
- Writes HW [d]ata to [a]ddress.
- 3C1A**** LUI K0,{a}
- 341B**** ORI K1,R0,{d}
- A75B**** SH K1,{a} (K0)
- 90.aaaaaa dddd unsigned HW not equal
- 5 opcodes
- Next code is run if HW at [a]ddress does not match unsigned HW [d]ata.
- * Bugged
- The value at the address is loaded as a signed value but tested against an unsigned value. A test for a negative value will always be False!
- 3C1A**** LUI K0,{a}
- 875A**** LH K0,{a} (K0)
- 341B**** ORI K1,R0,{d}
- 135B**** BEQ K0,K1,{len next code}
- 00000000 NOP
- B.f.cc.AAAA DDDD 00.aaaaaa dddd loop type 2
- 11 opcodes
- Writes [c]ount codes, incrementing the [a]ddress by [A] and [d]ata by [D].
- If [f] is 0 writes bytes, if [f] is 1 writes HWs.
- 4081F000 MTC0 AT,ErrorPC (just storing the register safely)
- 3C1A**** LUI K0,{a}
- 275A**** ADDIU K0,K0,{a}
- 341B**** ORI K1,R0,{d}
- 340100** ORI AT,R0,{c}
- A35B0000 / A75B0000 SB/SH K1,0000 (K0) (depending on [f])
- 277B**** ADDIU K1,K1,{D}
- 275A**** ADDIU K0,K0,{A}
- 1420FFFC BNE AT,R0,-4
- 2421FFFF ADDIU AT,AT,FFFF
- 4001F000 MFC0 AT,ErrorPC (restoring the register)
- D0.aaaaaa 00dd unsigned byte equal
- 5 opcodes
- Next code is run if byte at [a]ddress matches [d]ata.
- 3C1A**** LUI K0,{a}
- 935A**** LBU K0,{a} (K0)
- 341B00** ORI K1,R0,{d}
- 175B**** BNE K0,K1,{len next code}
- 00000000 NOP
- F0.aaaaaa dddd terminate codelist if HW not equal
- 5 opcodes
- Ignore all codes following this one if the HW value at [a]ddress does not match [d]ata.
- * Potentially bugged. There's two problems here.
- First, if 80014878 is set it will be a jump that ends the codelist. They stick that jump in the delay slot of a branch with no regard for how the jump's delay slot will be handled. Shouldn't be a critical problem though.
- Second, if 80014878 is not set this code does nothing at all. The handler for setting it when NULL is right after the code parser, just before it's written to the end of the list. It appears there is an edge case in mode 1 where it won't be set automatically at an earlier point. To force the matter, include a 3FFFFFFE code before writing this code.
- 3C1A**** LUI K0,{a}
- 875A**** LH K0,{a} (K0)
- 341B**** ORI K1,R0,{d}
- 575B0001 BNEL K0,K1,+1
- ******** Jump to end of codelist or, theoretically, NOP
- You can use 3FFFFFFE codes to set individual targets for the jump so long as 1) you accurately predict where in the list you want to jump to and 2) after all of the 3F/F0 pairs you add a final 3F that sets the correct return target for the whole list. This may be handy to add complex switches for parts of a code, like different #players conditionals, etc. where you don't want to exit the whole list or to simply turn a single code off in a larger set of them.
- .: Comms Port Commands :.
- The PC communication port is only used in the shell, not in the trainer. Only three commands are tested for, with an impossible condition set for a fourth.
- Interrupts are disabled during communication.
- To read a byte from comms:
- 1) Read the port until & 0x400 is set.
- 2) Read the port; data is & 0xFF.
- 3) Write 0x800 to port. This acknowledges the read.
- 4) Read the port until & 0x400 is NOT set.
- 5) Write 0 to port.
- 6) Return the data read at step 2.
- Writing a byte to comms is more complex. The value is split across multiple writes and written in both the upper and lower halfword.
- 1) Read port until & 0x400 is NOT set.
- 2) Write value to port: v = byte >> 6; v, 00, (v | 0xC), 0
- 3) Read port until & 0x400 is set.
- 4) Write value to port: v = (byte >> 3) & 7; v, 00, (v | 0xC), 0
- 5) Read port until & 0x400 is NOT set.
- 6) Write value to port: v = byte & 7; v, 00, (v | 8), 0
- 7) Read port until & 0x400 is set.
- 8) Write 0x100 to port.
- 9) Read port until & 0x400 is NOT set.
- Before any command the register is cleared by writing 0 to the port, then reading (and ignoring) the next byte posted by PC. The following byte will be interpreted as a command.
- 'W' command (wait)
- PC is waiting. If N64 responds '6' communication is in a ready state.
- 'X' command (execute)
- 4 bytes (little endian) p->rdram target
- 4 bytes (little endian) length
- (send "length" bytes. keep a 16bit sum of these values)
- 2 bytes (little endian) 16bit sum
- N64 will respond with 'OK', then execute your code!
- If it responds 'CF' the checksum on the data was invalid and nothing happens.
- 'U' command (update)
- 4 bytes (little endian) p->rdram target
- 4 bytes (little endian) length
- (send "length" bytes. keep a 16bit sum of these values)
- 2 bytes (little endian) 16bit sum
- If it responds 'CF' the checksum on the data was invalid and nothing happens.
- The Xplorer's eeprom will be overwritten with data starting at address 0. Maximum is not tested, but should cap at 0x40000. After flashing it will check if what's on the eeprom matches the data you sent. If all is well it responds 'OK', otherwise 'FF' is sent (flash failure). It is *highly* suggested you continue trying to reflash the device until successful, otherwise it's fairly likely not to boot again.
- .: Special Registers :.
- The device has multiple possible mappings that can be enabled and disabled via registers.
- At boot, the device overloads the cart domain mapping (10400000). When applying the hook & trainer the mapping is switched to BF400000.
- B02F0000 Reading this register enables and disables Xplorer mapping. This is required before and after DMA from an attached cart.
- BF200000 Same as B02F0000 when BF- mapping applied?
- B0300000 Read/Write: comms port. 0x400 & 0x800 flags are used as semaphores.
- B0400400 Write 001E001E here to switch between the B0- mapping and BF- mapping. It is unknown if the value itself is used to create the new mapping (via << 3).
- B0710000 Read: button index; increases when the XP button pressed (to change CIC selection).
- B0740000 Read after an eeprom read/write operation to place back in normal address mode.
- B0760000 Read before reading or writing the low (0) halfword of an eeprom address.
- B0770000 Read before reading or writing the high (2) halfword of an eeprom address.
- .: Compatible IC Types :.
- (Please note the author does not have this device nor has seen board scans. Below is based on disassembly of the reflash code.)
- Devices are alternately called EEPROM & FLASH in their datasheets. They act like EEPROM though, being programmable without prior erase.
- They're sorted by device ID first, then if an ID isn't found by manufacturer. Because of that lazy sorting false positives are possible.
- Compatible devices (well, probably):
- **DA 512Kx16 MX29LV800* (or compatible; i.e.: S29AL008J)
- **A4 512Kb SST27SF512 (or compatible; likely includes AM29F040)
- **D5 1Mbit SST39LF/VF010 (or compatible)
- BF** Literally any other SST device that conforms to the interface.
- DA** An unknown Winbond IC (manufacturer ID 0xDA) that uses 0x90, not 0xA0, to program.
- .: Plugin Support :.
- Plugins are executables read from Controller Paks. As an important note, multibank Controller Paks are accepted but only so long as they have no more than 16 banks! That's the largest one Datel ever made but nowhere near the max for the specification. (Blaze's own 1MB paks all appear to use switches instead of being actual high-capacity.)
- Only the first plugin found will be used! Any others will be ignored. A message will be displayed if a plugin is found.
- The GUID must be set to "PLGX" and company ID to "XP". Filenames/extensions are ignored.
- Until an example appears, this is a best-guess as to the format:
- 0x0 4 total size
- 0x4 4 offset in data to entrypoint; must be at least 8 bytes before the end of the file (this is an actual test)
- 0x8 ???
- 0x10 4 checksum for data below; each uses a different algo
- 0x14 4 hash for data below
- 0x18 4 inverse checksum for data below
- 0x1C 4 inverse hash for data below
- 0x20 start of data
- The funny thing about the checksum algos is they appear to throw away the more intricate of the two registers (the actually hashed ones). The first is a simple sum of every byte, the third an inverse sum. The other two take each byte and XOR it in the lowest two bytes of the hash.
- Entrypoint function accepts two arguments:
- 1) p->start of data above
- 2) p->an array of 16 function imports.
- They are not expected to return. If they do you'd be greeted with a confusing "Loading Plugin failed" message.
- Specifics aren't known for all 16 of the import functions, but here's a list of the function called in 1.067E:
- 80011550 # V0 = interrupts enabled flag; disable interrupts
- 80011568 # Status |= A0; enable interrupts
- 80012CAC # V0 = p->A0 allocated bytes
- 80012E50 # free A0
- 800135D8 ^ 0x87654321 # reflash A2 bytes at addr A1 to A0; XOR with 0x87654321 before calling with a JALR
- 80012398 # V0 = p->data starting at controller pak page A0 else None
- 800122B8 # V0 = p->Note matching GUID A0, company A1 else None
- 80012180 # V0 = p->A2 bytes of data at controller pak address A1 read to A0
- 800107E0 # set display mode A0
- 80010BA4 # ???; fill screen with color A0? Called before printing a string.
- 800043CC # print string A2 at (A0, A1)
- None
- 80010920 # ???; video buffer swap over A0 frames? Called before printing a string.
- 80010E94 # set pixel at (A0, A1) to c16 color A2
- 80011248 # read PIFram to 8001F1B0
- 80011344 # V0 = controller A0 button state
- Considering this list of functions, updates to the BIOS (0x40000) or cheat files (0xE000 bytes) would be the most likely use. If replaced in their entirety either compression or higher-capacity Controller Paks would be required. That said, any executable code could be used.
- -Zoinkity
Advertisement
Add Comment
Please, Sign In to add comment