Improving Neon64 - infos needed by Eisi at 5:58 AM EDT on August 30, 2013
Hello everyone,
I've been using Neon64 on my Everdrive 64 very much lately. My roommate and I play Contra and Battletoads all day. We really love the emulator and we thought it would be nice to play Famicom Wars as well but mapper 10 is not implemented. According to iNES Mapper Grid Mapper 10 is nearly identical to Mapper 9 (which is already implemented). So I thought it would be only a few lines of code to get mapper 10 working. However I have only basic knowledge of C and Assembly and nearly no knowledge of emulation so I have a few questions: - I thought of copying the mapper 9 code, change a few lines so it matches mapper 10 and make Neon64 know that it now supports mapper 10. Is it really that "simple"? - Are there any newer version of the source code than version 1.2a? SVN repository is empty. - How to compile this? hcs' U64ASM doesn't work on Win8 so do I have to install an older OS (probably Win 98) or are there other possibilities? - How does the emulator determine which mapper it should use?
I'm sure that more questions will pop up but that's it for now.
- Yes, it should be that "simple". It seems like the difference between MMC2 and MMC4 is just the PRG-ROM banking. Note though that the MMC2 code is largely hacked for Punch-Out, so it may possibly not do everything it needs to do for MMC4.
Also a problem is that MMC2 has code in a few odd places because it needs to deal with PPU reads, see below.
- The easiest way to run U64ASM would be to build in DOSBox. Make sure you set it to run as fast as possible in the config file:
[cpu] core=dynamic cputype=auto cycles=max
The DOSBox shell doesn't support the
copy /b ..\header + neon64.bin neon64bu.rom
command in mbu.bat for concatenating the header and .bin into the final .rom, so you will have to do this some other way.
- Mappers are initially set up in rom.inc, there are a series of tests comparing the mapper number to various constants, mapper 9 is set up on line 185 after the "; Mapper 9 (MMC2)" comment, following the test at 182-183.
Mapper writes are handled in write.inc, the jump table mappertable near the end of the file jumps to the handler for the particular mapper, in the case of MMC2 this is the label map9, which is just before an #include mmc2.inc, which is where the real remapping logic takes place.
MMC2 also changes state on PPU reads, so there are a few chunks in the main neon64.asm loop that handle it.
First is a section labelled "; Da Cheat!" which resets the latch at the start of each frame, whatever read was supposed to trigger this I wasn't getting so this is a hack. This jumps to the mmc2latchgfx label in mmc2.inc.
Just after the "outer_loop" label, which is run once for each scanline, is the main handler to determine if the trigger tile is on the current scanline. This jumps to the mmc2 label in mmc2.inc.
Thank you for your very detailed answer. I already looked into it (working with the snapshot) and I think I set up the most things correctly. Although I'm pretty sure that there are errors. Anyway, I wanted to try it out so I used DOSBOX to compile and Win8 cmd.exe for the copy /b command. Everything went good so far but it only shows a blank screen on my N64 (for every nes rom). Because I didn't touch anything but the code you told me I guess that at least ROMs not using Mapper 9 or 10 should work. Now I have a feeling that it might be a wrong checksum for the neon64.rom. I have never been able to fix a checksum for any N64 rom. Either I am doing something completely wrong or I use the wrong tools. uCon64 for example always calculates a 0x00000000 checksum for any rom which is obviously wrong. I tried to use LemAsm and it calculates a CRC that seems valid but it still doesn't work. Now I once wanted to use the game gear emulator for the N64, inserted a rom but never got the checksum to work, regardless which program I used. Do you possibly know what I'm doing wrong?
Also I've got a question regarding the code in mmc2.inc line 19-20
li t4,0x20-1 la t5,nespagetable+(0x80*4)
They obviously correspond to map9setPRGloop and set up for the loop to go through the PGRROM adresses but since Mapper 10 uses 2 blocks of 16K instead of 4 blocks of 8K I wonder if I need to change something.
Nevermind the Checksum problem, I got it solved. I tried my compiled version on my N64 and while non-mapper 10 games still work (Punch-Out!! also works, yay) mapper 10 either shows a black screen after loading (Famicom Wars (J)) or hangs on the loading... screen forever (Famicom Wars (T eng)). I will look into this tomorrow. I probably mixed up the adresses.
EDIT: I fixed the hang on the loading... screen for the translated rom. Still shows a black screen after loading though. No sound, too. However it does not crash as I can open the Neon64 menu.
Sorry for triple posting. I further looked into it but after many tries I didn't get it to run. It always shows a black screen after loading. Debug info either shows an infinite loop or a current PC of 0x0000. Depending on what numbers I chose for the mapping part (either same, x2 or x1/2). The problem is that this mapping stuff is far above my knowledge. I did some research and understand some parts in theory but didn't get what map10setPRGloop really does (I guess copy the PRG from ROM to EMU ROM) and how it works. Same counts for map10resetPRGloop. I guess the only problem that I don't get the numbers right. So I uploaded my lines of code to pastebin and thought you could take a look at it. I would really appreciate it.
No worries about triple posting, this is your thread.
Forgive me if this explanation is too wordy, I figure it's better to err on that side than to say too little. You seem to be picking things up pretty well, so it's probably more explanation than you need.
Neon64's memory map (nespagetable) has an entry for every 0x100-byte (1/4 KB) "page" in the NES's CPU address space. This is initially set in NESPAGES.INC. The dcw mnemonic generates repeated data, so
dcw 0x08,mem
works like
dw mem,mem,mem,mem,mem,mem,mem,mem
These values map the first 8 pages (0x0000-0x07FF). The actual address of memory is computed as nespagetable[address>>8] + address (if this was C).
For the PRG-ROM region (0x8000-0xFFFF, 0x80 pages total) we initially set it up in two banks, 0x8000-0xBFFF and 0xC000-0xFFFF (0x40 pages each), both of which should point to the same thing: the first 16KB bank of the PRG-ROM. So when, for example, 0x8402 or 0xC402 is read by the NES CPU, we want the read to actually go to prgrom+0x402. You can see how the prgrom-0x8000 and prgrom-0xC000 make this happen:
The general rule is that the address in nespagetable should be the (real N64) address we want that page to point at minus the (emulated NES) address of the start of that page.
For MMC2 we consider 4 banks, 8KB (0x20 pages) each:
the last three should map to the last three 8KB banks in PRG-ROM. The last three banks start at prgrom + (rom size in 8K banks - 3)*8K, So, we put prgrom + (rom size in 8K banks - 3)*8K - 0xA000 into all the pages from 0xA0 to 0xFF (0x60 pages).
Then we have the adjustible first bank. Initially this is set as prgrom-0x8000 in nespagetable so we don't have to do anything in the initialization (in reality usually these mappers start up in an undefined state, so the initial value is overwritten before the game uses it anyway, the 6502 first reads from 0xFFFC, right near the end of memory so the state of the bank 0x8000-0x9FFF may not be relevant).
When we get a write to 0xA000 we wind up in the handler in mmc2.inc, here we have to determine from the written value what bank of ROM is intended. I use what is probably the wrong method:
written value % (prgromsize in 8K banks)
(% (modulo) is done via the div and mfhi stuff there). I think all I really needed to do was
written value & 0x0F
Either way we get an 8K bank number, we convert this to a byte address with sll 10+3, and do the normal calculation prgrom + (bank # << 10+3) - 0x8000 and store that into the nespagetable at 0x80 through 0x3F (0x80+0x20) to map it into 0x8000-0x9FFF.
---
Now, about your MMC4 changes.
in ROM.INC: - You don't need to do anything to convert to 16k page/bank count, the value in prgromsize is already in terms of 16k banks. For MMC2 we double this count (sll 1) because we want 8k banks, and there are twice as many 8k banks as there are 16k banks.
- As I mentioned above you probably don't even want to bother with my ugly divide here, it might break things. Just do
andi A6502_regval, 0x0F
or similar.
- 0xC000 should be subtracted instead of 0xA000 (this bank is being mapped to 0xC000 instead of the three at 0xA000, 0xC000, and 0xE000 for MMC2)
- We are mapping 0xC000-0xFFFF, which is 0x40 pages (0xFFFF+1 - 0xC000 = 0x4000), so map10resetPRGloop should init t1 to 0x40-1.
in MMC4.INC:
- Yes, you need to keep the check for 0xa000, otherwise you will be getting the writes that are setting up CHRROM mapping. Only writes into 0xa000-0xafff (addr&0xf000 == 0xa000) set the PRGROM mapping.
- You want to map the whole region from 0x8000 to 0xBFFF, this is 0x40 pages so the map10setPRGloop should init t4 to 0x40-1.
That may be all you need, you may have worked it out already in the time it took me to write this up.
Wow, that is definitly the biggest answer I ever got on any forum. Thanks for taking so much time, seeing that you already knew how to handle this you probably could have done this yourself in 15 minutes. Your explanation was still needed because it enabled me to put all these puzzle pieces together. Thanks to your efforts it finally works! Well that counts for Fire Emblem I+II. I tried the original japanese Versions and .ips translated roms and all of them work. However there are horrible graphic glitches as soon as a textbox pops up (which happens all the time when selecting a unit etc.) See Picture1 and Picture2. Bad quality incoming, only had my Laptop's webcam at hand. Now I guess fixing these would be a much bigger challenge since you needed to cheat for Punch Out!!. Anyway Famicom Wars only shows a gray screen on startup. I think that this is not the emulator's fault but the rom's since I was unable to obtain a Famicom Wars that is not a bad dump. I probably have to dig deeper...
I edited my pastebin to the working code, feel free to commit these changes to your Repo!
Now although the graphical glitches will probably also affect Famicom Wars and I would love to see them fixed (which is unlikely to happen) there is another feature I would love to have. Saving to SD Card on a modern Backup Device (mainly for Pirates!). I think that there are two ways to do this: 1. Make the Backup Device OS think that your N64 rom saves to gamepak sram, save game file to gamepak sram and let the OS do the rest. 2. Directly access the SD card, this would be specific to the Backup Device (which sucks).
Now my question again: Is it that simple? Do you think I would be able to pull this off as I'm still pretty unexperienced. It would probably end in you guiding me trough the code again so if this would be too time consuming I'm perfectly fine with that.
I'm now remembering what the major issue was with MMC2 graphics: there is no way in the current architecture of Neon64 to remap CHRROM banks mid-scanline. You can see this in Punch Out in a few places. I can see how Fire Emblem relies on this, it wants to start the line using the world map patterns, then switch to the text patterns in the text box, then switch back to the world map.
1. Is probably the best way to approach saving, but I don't know much about how writing to EEPROM/SRAM works. Neon64's save code isn't terribly complicated but you'd need to replace a lot of it.
Someone was working on saving to SRAM back in 2011, but I don't know that he ever got it working. [edit] Nope, ran into some unspecified problems. I see no reason why it fundamentally can't work, though. I asked him to try and dig up his old code to maybe give you something to start from.