I pushed a lot of common behavior up to the Mapper base class,
the most substantial of which is the bank-chunking logic.
Doing so did lead me to yak-shave a bit, since I needed to add
proper inheritence support to the Serialization library. It's not
_completely_ transparent, but it's pretty good. Essentially, since
C++ doesn't have a built-in "super" keyword to access the parent
class, you have to manually specify the parent class.
I didn't implement multiple-inheritence support though, since that
isn't relevant to me right now.
That said, the serialization library is really nifty, so (like I
mentioned earlier) i'd like to spin it off into it's own repo.
I also took this chance to add power_cycle() and reset() methods to
the mapper interface, since I realized that some games would die
on reset due to registers not being cleared properly.
So yeah, feels gud man.
it's not working though, and i'm not entirely sure why...
I doubt i'll spend more time looking into this right now though,
since it's a relatively minor thing. I was hoping it would
Just Work (TM), but alas, it looks to be a bit more involved...
Welp, it was worth a shot.
(oh, and if you can't tell, i'm just procrastinating starting to
reimplement the APU)
tinyfiledialogs was a good band-aid, but it was always kind-of a
janky solution. Mind-you, this file-browser I threw together ain't
the nicest either, but at least it's within the main SDL window.
Now that the menu is in place, it's possible to switch between
games without restarting! Also, opening the file-picker pauses
ANESE, so that's good to finally have.
Thanks a bunch to cute_headers for a neat little cross-platform
directory traversal library, and also SDL_inprint for a quick-and-
dirty embedded font for use in the menu. Not having to bring-in
SDL_ttf is a huge load off my shoulders.
Oh, and i've noticed that `gui.cc` has started to get bloated, so
now I need to get around to cleaning it up weeeeee.
It wasn't that hard to do per-se, it's more about me finally
getting around to doing it. main.cc is hella clean, just making a
new SDL_GUI instance, and letting it RAII all of the SDL stuff.
Also, now that the new project directory structure is in place, it
shouldn't be that hard to add a LibRetro core! Fun!
It's not the prettiest code, and it sure as hell isn't how the
hardware does it, but by lord it does what I need it to do!
Yep, I pretty much copied the existing sprite fetching code from
one part of my PPU, and slapped it in spr_fetch(), with
cycle-accurate timings.
This is awesome, since I can remove the ugly "passing the PPU to
the mapper" shenanigans I wrote to get it working. I can actaully
use the correct MMC3 timing behavior, i.e: watching PPU A12 for
a rising edge.
Plus, if I switch to a subcycle-accurate CPU emulator, it _should_
automagically fix flickering in Kirby's Dreamland!
Oh, i'm almost forgetting the most important bit: Punch Out!!
It works now!!
That was the last game I really cared about getting up and running,
which means that I'm not going to be focussing on mappers for the
next little while, shifting gears instead towards accuracy stuff.
Basically, there are 2 things that I really want to get around to:
1) subinstruction cycle-accuracy - i.e: rewrite the CPU
2) write my own APU implementation
Of these two, I think i'll tackle 2) first, because if I don't do
it, i'll never be fully satisfied with myself. How can I say I
wrote a NES emulator if I haven't even written a major chunk of it!
So yeah, good stuff!
It's kinda jank, but hey, it works?
The serious issue is that it relies on the SDL2 dylib installed
from homebrew, and if that verion ever gets bumped, RIP.
hey, look at that. I have CI now :D
One of these days i'll look into how to package up macOS builds,
and update my cmake scripts to prevent a console from popping up
on windows.
One major thing I changed is that `nes.cc` no longer calls `new` to
instantiate components, and instead simply contains them. This is
nice for 2 reasons: 1) it is more faithful the the hardware
metaphor i'm going for, and 2) it lets me get a good idea of ANESE
Core's memory footprint, which is neat.
I also finally cleaned up the PPU's nametable mirroring, and while
it looks a bit uglier, at actual operations should be more readable
for those who know what to look for.
fucking windows REEEEEEEEEEEEEEEEEEEEEEEE
fopen() silently converts line-endings on windows, and since I did
not explicitly say I was wiring binary data, it solently bloated my
sav size.
Literally fixed by adding a single char: "w" -> "wb"
Also cleaned up some things that were causing compiler warnings.
Nothing mission-critical, but its nice to keep things clean.
MMC2 is fancy since it snoops reads from the PPU to do bank-
switching on-the-fly, with 0 CPU intervention.
Unfortuantely, this means that for it to work properly, I need to
make my PPU fetching code accurate.
So yeah, if I ever fix the PPU, it should make Punch Out!! work :)
Save-states now also save the APU (which wasn't actually that hard
to do lol), and a better overview of Mapper state. I pushed the
`update_banks()` function up to the base mapper implementation,
and made it overridable, as by doing so I can override the
deserialize function on the mapper-level to call `update_banks`
after deserialization.
After hammering out the initial implementation, I took a look-back
and realized there was a lot that I could have done better. So,
with a fresh-mind, I cleaned up how serialization works under the
hood a bunch. It's a lot cleaner now, and AFAIK, it doesn't rely
on [that much] undefined behavior.
There are a few more things to iron out with it before i'm totally
happy with how it works, but i'm pretty proud of it right now :)
Guess what? There is a known issue with MSVC regarding aggregate
initialization! Yep, that's right, my standards-compliant code
didn't bloody work on MSVC because of a COMPILER ERROR.
God damn.
Well, I fixed that, and now save-states work.
Unfortuantely, it seems to save/load battery-backed RAM improperly,
which is something i'll need to dig into.
Fuck windows.
This serialization framework sure was something!
It is by far the jankiest, ugliest, nastiest C++ i've written,
using a bunch of dirty pointer-hackery and casting to make it work
as "seamlessly" as it does, but god-damn if the end result isn't
marvelous.
Serializing arbitary C++ classes is hella simple now! Just extend
it as "Serializable" and slap-on the appropriate field definitions.
Now, there are a couple rough edges...
First of all, I don't handle Serialization inheritence that well.
Well... at all.
Only data serialized by the last-class in an inheritence chain gets
serialized, which works well-enough for Mapper extending
Serializable (since it doesn't actually have any data to save at
the base level), but things might break down the line.
Since this serialization library is pretty neat on its own, I
think I might spin-it-off into it's own project :)
Try and run Zelda, it should generate a .sav once ANESE closes, and
it should automatically read it once ANESE is reloaded with the same
file :)
So yeah, serialization.
It's an important part of any emulator.
It makes saving unsavable games possible.
It opens the door to a whole mess of neat stuff (rewind OwO)
The question becomes, what's a good way of doing it?
While there are probably much better solutions out there, I want my
solution to be fairly simple and lightweight.
The gist of the system is that anything that can be serialized will
extend the Serializable class. Though some clever use of
inheritance and macros, serializing the state of an object will be
as straigtforward as defining which fields should be serialized
(in the constructor, using some macros), and then calling
serialize() and deserialize(data) appropriately :D
I'm not planning on doing anything too fancy, basically just some
structured memcpy's, but it aughta work okay. Hell, i'm not even
labelling chunks, so if I ever change the order in which something
serializes it's members, old saves won't work anymore haha.
At least it's portable... ish? I've been using sizeof with fixed-
width types, so saves _should_ transfer across machines.
(assuming they have the same endianess)
Oh, and I still haven't figured out how i'm going to handle
serializing data-structures that have serializable members, (which
is kind-of important when serializing the NES / some of the fancier
mappers)
Off the top of my head, it might make sense to adopt some sort of
tree-like approach... but idk yet.
I'm still not happy about having to pass a reference of the PPU to
mappers, since i'd much rather be faithful to the actual HW.
Alas, until I rewrite the PPU's sprite handling routines to be
cycle-accurate, I can't snoop PPU A12 in the mapper for timing.
Damn you MMC3!
Unrelated to that, I've gone ahead and implemented a (togglable!)
NMI timing hack. Some games were failing to boot at all, and it
seems to be due to my CPU not being subinstruction-cycle accurate.
I checked, and fogleman/nes had the same issue, so I wholesale
copied the hack from him. Seems to get Bad Dudes and Solomon's Key
up and running :)
As usual, the best way to solve a problem is to not think about it
for a few days, and glance at the code again later.
Just like MMC1, the root of the issue was mishandling of RAM R/W
bits. I'm a real dingus.
1) Movie Cleanup
- Movie recording and playback are now well-defined classes.
- Infrastructure is in place to enable lazy-loading from a fm2 file
(if need be).
- File I/O is not handled in main anymore AT ALL
2) Reorg Memory Management
I changed the ROM class to be a zero-allocation wrapper around
existing memory, essentially just there to bound check raw ROM, and
to wrap it in a Memory interface.
I also reintroduced the Cartridge class, since in hindsight,
deleting it completely was overkill, and that it's actually a
useful memory-management construct. It owns both the ROM_File and
the Mapper, and frees them automatically.
The only real difference between the new Cartridge class and the
old one is that now, instead of being the entire class being
passed to the nes subsystem, one simply uses a getter to retrieve
the Mapper, (the important bit) to pass that to the NES.
I shuffled A LOT of files and functions around.
Two big things in this change:
1) There is no more Cartridge class.
I wrote that class when I was just starting out with ANESE, and
before I had a good idea of how NES hardware operates. As it turned
out, the Cartridge class essentially just mirrored the Mapepr
interface! The only real functionality it brought to the table was
routing the raw ROM data through into thte ROM_File class, which
would parse the ROM data.
I restructured all that.
File parsing shouldn't be under the /nes directory, since the nes
couldn't care less if the ROM file is iNES, NES2.0, or whatever
other formats there are. All the NES cares bout is that there is a
ROM_File struct with good data in it.
So, I moved the specifics of file-format parsing out of /nes, and
put them under /ui/fs/parse_rom.h.
I've changed all the interfaces that used Cartridge to just use
Mapper, and pushed the construction and ownership of the ROM_File
struct to main() for now. Which leads to number 2...
2) Main doesn't have any filesystem code anymore.
Yep, i've moved all the code required to read roms (and general
files) into /ui/fs/load_rom.h, so all main() has to do is pass the
path to a ROM / file, and it gets back a fully validated ROM_File
struct, or filled data pointers!
The code could still use some work, reorganizing the freestanding
functions into classes / namespaces, but this is a big step-up
from just having it all cluttering up main()
----
So yeah. Not much on the emulation front today, mostly just
improving code-quality.
(i'm also just procrastinating having to implement MMC3 lol)
i've also had a chance to experiment a bit with ANESE, and I've
realized that I probably won't get supreme cycle-accuracy working.
My CPU emulator is instruction-level cycle accurate, but there are
some tests, games, and demos out there that rely on sub-instruction
level timings.
fixing these issues would involve a substantial rewrite of the CPU
core, which is something that I doubt i'll get around to. I'd
rather just push forward with implementing some more mappers,
cleaning up the code and UI, and taking a stab at a proper
home-grown APU implementation.
It's one of those little featues that is nice to have. I'll record
myself playing some games, and i'll be able to see how various
changes I make alter the timings.
also, maybe i'll get to the point where I can actually run propper
TAS movies on ANESE. that'd be pretty rad.
----
I also moved speedup out from the nes core, since movies would need
a lot more fancy logic if I had kept the functionality contained in
nes.cc. The only reason the nes is still notified of the speed
change is so Blaarg's APU can adjust itself.
attention to detail strikes again! in blindly copying and pasting
the read function from another mapper, I totally didn't change the
fact that mapper 7 is one-big bank vs. 2 smaller ones.
yep. i'm a dingus.
ah well, c'est la vie. Battletoads works now!
you know, I just realized that MMC1 and Mapper 7 don't seem to be
passing the mapper tests...
MMC1 is incorrectly attributed to Mapper 2, and Mapper 7 doesn't
even work!
I've gotta investigate...
HAHA SUCK IT, I DID IT! THE PPU WORKS (well enough)
You know what it was? I completely overlooked handling 8x16 sized
sprites when fetching data! After I put that in, BAM, things began
working!
I'm sure the PPU isn't perfect (by any means), but games look good
now! HOORAY!
I can move onto implementing more mappers!
WOOOO! YES! WHAT A DAY!!!
well, I don't think the issue is with DMA anymore. I've greatly
simplified the DMA class, and the implementation is so basic that I
doubt there is a issue with it (or at least one that I can see)
I also ran through a bunch of test roms, even implementing mapper 7
to get one working (oamtest3, which sucks by the way). Although I
could not fix the gfx issues, I did stumble across some minor bugs
that I fixed up:
- I realized that I was failing the CPU dummy_reads/dummy_write
tests, and while I did go ahead and fix dummy_writes, I was too
lazy to look into fixing dummy_reads.
- I noticed that adresses 0x4018 - 0x401F in my CPU_MMU were being
handled wrong, being assigned to the Cartridge, instead of being
their own special thing. I've rectified that issue by simply having
them return 0x00 for now, at least until / if I implement the wierd
CPU / APU test functionality they are responsible for.
My methodology for narrowing down issues has been to run the test
roms in various emulators, and seeing if they pass / fail it. If
they fail the test, and so do I, that means that the innacuracy
that test is looking for shouldn't be affecting the garbled-gfx
issues i've been having. On the other hand, if they pass tests and
I don't, i'd try to fix the issue and see if that got my problems
to go away.
No luck yet with the gfx thing though...
That said, I think i've narrowed down the gfx issue to the Sprite
implementation. It never was written that well, and although it
works for some games, clearly it's breaking for many others.
Instead of debugging that mess, I'll probably just completely
reimplement the Sprite code in the PPU. Fun!
so, MMC3 == Mapper 4. that kind of tripped me up a bit, but that's
a minor thing
So yeah, MMC3 is a tricky boi, and it's probably going to require
some restructuring / new wiring. I'll tackle that later.
In the meantime, I got mappers 2 and 3 working! They are really
simple mappers: 2 has a single swappable PRG ROM, and 3 has a
single swappable CHR ROM. ezpz implementations.
Mappers 2 and 3 got some quality games "working," such as Mega
Man 1, Contra, and Arkanoid! "working" is in quotes because i'm
still seeing glitchy graphics, namely in Contra.
By the looks of it, the glitchy graphics are similar to those in
some MMC1 games i've messed abou with, which indicates to me that
odds are the root of the problem lies somewhere with my PPU
implementation.
My gut is telling me it might be something to do with DMA timings,
but that may very well be completely wrong...
All in all, i'm pretty happy with this commit. I can stop stressing
over my MMC1 implementation, and instead get _really_ stressed over
my PPU implementaion!
weeeeeeeeee
You know what the issue was?
The ram enable flag in the MMC1 control register enables access to
SRAM when it is set to 0.
Let me repeat that: 0 == enabled. 1 == disabled.
WHY WOULD ANY SANE PERSON DO THAT GOD DAMMIT WHY AAAAAGGHHHHH!
...
Sorry about that, it's just shit like that which really pisses me
off.
Well, regarless, now that i've put that fix in, Zelda actually
boots. Albeit with garbled graphics.
And other MMC1 games also still have the garbled-graphics issue,
which I still can't seem to fix...
As usual, i'm scared to say it but the issues probably lie within
the PPU. That said, I think i'm done with hammering on MMC1 for now
and instead I think i'll shift gears and see if I can get MMC3 up
and running. If graphical issue still remain with MMC3, then odds
are the issues are with the PPU, and not with MMC1.
weeeeeeeeeee I looooove emulator developmenttttt
It's been a while...
Once the semester started, I stopped working on my sideprojects,
as I had built a brand spanking new Gaming PC, and PUBG entered my
life.
Well, it's coop now, and I don't have a fancy gaming PC.
Might as well finish what I started here.
I always felt bad that ANESE was broken, and couldn't play all the
major titles. Yeah, SMB1 and Donkey Kong are great, but what about
Zelda? What about SMB2 and SMB3? Kirby?
So yeah. Hopefully I can get those working, and go on to bigger and
better things!
-----
Now, I don't know for sure, but I _think_ i've mostly fixed my MMC1
implementation. It passes the MMC1 mapper tests.
That means that things are broken for other reasons...
...
...
the PPU probably.
Ugh, this is going to suck.
So, lots of early assumptions proved to be wrong.
This meant that there was a bunch of code that had to be rewritten.
1) iNES format stores ROMs in 16k / 8k chunks, but that does _not_
mean that all mappers bank along those lines!
I rewrote the entire ROM File parsing logic, and instead of chunking
the raw ROM into banks at the iNES parsing stage, it is done on a
mapper-per-mapper basis
2) Cartridges are _not_ just dumb data storage devices, and can have
cycle-dependant logic!
To be fair, I knew I would have to cross this bridge someday. I had
to expand the Cartridge / Mapper interfaces to expose more of the
NES's. This is in both directions: Giving the Cartridge acess to the
NES's cycling logic, and also giving the NES a direct way to get
mirror modes from the cartridge (dynamically)
I was hoping that getting Mapper 1 working would also get Zelda
working, but alas, it's crasahing for some reason! As usual, I
suspect it's some shitty timings, and I can confirm, I am failing
a bunch of the NMI timing tests...
I _really_ don't want to go back into the hellhole that is the PPU,
but gosh darnit, I wanna play Zelda!
But hey, at least Megaman II works now :D
PS: I've been working on this commit for probably, oh, 8h total?
Not all at once mind you, it's been over the span of several days.
Now that i'm back in Toronto, i've been a bit busy, what with the
holidays, and old friends, and Wolfenstein II / DOOM taking up my
time.