Compare commits

..
44 Commits
Author SHA1 Message Date
byuu 1ebdb69516 Update to bsnes v019 release.
I´m releasing bsnes v0.019 today. This version contains Bandai Sufami Turbo support, new IRQ emulation code, and some various bugfixes.
Unfortunately, this release is not entirely cause for celebration. Due to fatal errors in Microsoft´s "enterprise class" c++ compiler package, I am no longer able to compile bsnes with profile guided optimizations. I have tested v0.018 with and without these optimizations, and the difference is a 40% speedup when PGO is used, even more significant than I had previously believed. However, bsnes has now become too complex for Visual C++ to handle. Unfortunately, there is nothing I can do about this, except wait for Microsoft to fix their compiler.
(Warning: this paragraph contains personal opinions, skip it if you can´t handle that) As if this wasn´t enough, I´m now doing my best to wean my dependence from Microsoft´s line of operating systems, as I´m particularly concerned about the black box nature of Vista and its´ DRM control mechanisms. This isn´t a road I wish to begin traveling down, and thusly have no interest in upgrading to future versions of Windows. Therefore, as of late, I´ve been writing a UI wrapper that will allow me to code applications that are truly platform independent. The biggest goal for this library is to design a GUI for bsnes that runs virtually identically on both Windows and Linux/BSD. This is mostly complete, however there were many tricks I used in bsnes using the win32 API that I simply cannot do with GTK+ on Linux/BSD, such as the memory editor window subclassing. I will be porting bsnes to use this new UI wrapper, and in turn this will lessen the attractiveness / functionality of the bsnes UI to a certain degree.
Perhaps the most devastating news is that I am still contemplating the idea of designing a dot-based PPU renderer for bsnes. As if the loss of PGO wasn´t bad enough, this will likely eat away an unimaginable level of performance as well. I can only estimate the speed loss being between 100-500%. Yes, it will be that bad. And despite weeks of planning, I cannot think of a way to allow a scanline-based and dot-based renderer to coexist as selectable options, given their massive differences in implementation.
And let´s not even joke about SA-1 or SuperFX support ... those processors are each four to eight times more powerful than the SNES´ main CPU.
All of these speed losses will basically make bsnes mostly irrelevant as an alternative to ZSNES, SNES9x et al. Although I believe I really came close to a viable alternative with v0.018, I know that I cannot both create a mainstream emulator, as well as keep with my original goal to emulate the SNES as accurately as possible.
The past few months have been very tough for me; trying to decide which of the above two goals to pursue. I´ve still not absolutely made up my mind. But for now, I´ve been sitting on a mostly untouched version of bsnes for the last few months, and have decided to release it to the public, profile guided optimizations be damned.
I´m once again asking for help, if anyone can figure out why bsnes won´t compile with PGO support, please let me know. I´d very much like to get one last PGO build of bsnes released before starting on a dot-based PPU renderer. But given the usual response I get from these requests for help, I´d suggest no one getting their hopes up that bsnes will ever be as fast as it once was again.
The new version can be downloaded at the usual place. I´m leaving v0.018 up, as it may very well be the last stable, fast version of bsnes ever released.
2007-01-01 21:04:34 +00:00
byuu add0f74387 Update to bsnes v018r10? release.
Ok, Sufami Turbo is finished. Now I just need to add back in
cheat/patch loading, and that should do for src/cart modifications for
a while.
             Maybe I'll add split ROM support while I'm at it just for
fun.

             There's now _some_ safety code regarding ST loading, but
it's not all there. Specifically, if a file fails to load, then you
won't get any errors, the game will just not work, obviously. I now
protect against loading oversized ROMs and SRAM files.

The database now lists each Sufami Turbo cart only once, and the cart
loading code handles matching up two ROMs from the database. It is
also now possible to play ST games with invalid checksums, so eg
translations/hacks of these games should now be possible, however
unlikely.

             FF:MQ (E) is fixed now too, so we're back to
FAVOR_ACCURACY in WIP builds again.

 A little more src/cart polishing and I'm going to start documenting
all that's known about IRQs, and try and figure out how they work once
and for all (hahah, yeah right -- I give it 2-3 weeks after fixing it
again before more problems are found).

[No archive available]
2006-11-07 08:55:00 +00:00
byuu b20f70f333 Update to bsnes v018r09? release.
Ok, the new WIP is extremely fragile with ST stuff, but it should work
if you're careful.

             It took a _lot_ of rewriting to get those damn dual carts
booting. Right now, everything but the SD Gundam games are in the
database. I need to think of a way of combining those. So, the only
other dualable game is SD Ultra Battle - Ultraman Densetsu + Seven
Densetsu. Otherwise, test with just one ST cartridge at a time.

Anyway, it's definitely a work in progress, so be gentle with it. You
need "stbios.bin" in bsnes.cfg::path.bios for it to work. It probably
won't even give you an error if it isn't there.

             Suggestions for how to layout the file menu are welcome.

[No archive available]
2006-11-06 07:51:00 +00:00
byuu 9aebf7bc6b Update to bsnes v018r08? release.
Ok, the new WIP adds ppu.hack.obj_cache = [true/false], and renames
the scanline render pos to ppu.hack.scanline_render_position = [dec].

             OBJ cache defaults to off now, as two bugs are better
than four.

 Made SDP a bit more friendly to view now. I may port that style over
to my main website, too. Specifically the non fixed width part.

[No archive available]
2006-11-03 07:58:00 +00:00
byuu a7bf219d5d Update to bsnes v018r07? release.
> Overall, I have a small list of possibles. Will wait until after
> R-Type to explore.




               Damn :(
 I don't think the R-Type III fix will correct anything else. But,
cross your fingers I guess. The new WIP fixes the aforementioned game.

 My SNES tests seem to indicate that writing #$20 to $4200 when
VCOUNTER==VIRQ will trigger an IRQ, even after an IRQ has already
fired on said line. My tests today indicate that it will not trigger
an IRQ under the above circumstances when the I flag is set. I don't
know why, it's the only thing other than the final IRQ trigger test
that cares what the I flag is set to. I'm not happy with the fix, but
it's the only explanation I can come up with, and all IRQ sensitive
games are running, as well all IRQ tests are still passing. So for
now, it'll have to do.

 I'm going to attempt to document all of SNES IRQs and see if I can
figure out a more simple method of emulating them, but I'm not
hopeful.

 I also removed the "guessed" entries from my database. I've decided
not to add anything unless we definitively know its' PCB ID, or in the
case of ST games, if it doesn't have one.

 Lastly, rewrote my SDP page on my site. It now uses XHTML 1.0 + pure
CSS2, so it should be a little easier on the eyes and a lot easier to
write documentation pages for.

[No archive available]
2006-11-01 09:05:00 +00:00
byuu 04118be59a Update to bsnes v018r04 release.
Ok, _please_ be courteous to my webhost and only download this WIP if
you're going to test it on a processor that hasn't been tested thus
far.

             byuu.org/files/bsnes_v018_wip4.zip
             byuu.org/files/bsnes_tests.zip

 This has two separate builds. Neither have PGO, SSE, SSE2, ZIP or JMA
support. They are identical except for the FAVOR_ flag define and
title of the program.

             FAVOR_ACCURACY [bsnes_accurate.exe]:
             - Always tests OAM RTO flags even on skipped frames
             - Tests NMI/IRQ trigger every clock cycle

             FAVOR_SPEED [bsnes_fast.exe]:
             - Only tests OAM RTO flags on rendered frames (always
with no frameskipping)
             - Tests NMI/IRQ trigger using ranges

 If you'd like to test, please run demo_mode3.smc on both versions of
bsnes, turn off speed regulation, and report the framerate both with a
frameskip of zero and a frameskip of nine (max), along with your
processor speed.

 The other test ROMs are just to verify that IRQ behavior is still
reliable in both versions. A blue screen indicates passing, they all
pass on both versions. Don't expect test_* ROMs to pass on other
emulators, but demo_* ones should.

             Example (my main PC):
             AMD Athlon 3500+

             Accurate:
             - 121.5 fps w/o frameskipping
             - 171 fps w/max frameskipping

             Fast:
             - 146.5 fps w/o frameskipping
             - 271.5 fps w/max frameskipping

             -----

             As you can see, there are _major_ speed differences on my
A64. Personally, I'm all for accuracy, but I also want people to
actually be able to use this program in the interim. Perhaps in the
future when a low end computer is a current low-end Core 2 Duo, we can
remove all of the "speedhack" code. And in the meantime, the full 100%
precision is there for people who have the CPU power to afford it.

             -----

             If anyone wants to try and help, heh.
 src/cpu/scpu/timing/irqtiming_accurate.cpp and
src/cpu/scpu/timing/irqtiming_fast.cpp are the two versions of the IRQ
testing code. If you see any ways to optimize either (preferrably the
former, obviously), I'd greatly appreciate it. Understand that both
the CPU counters (VCOUNTER, HCLOCK) and the IRQ timing positions
(VIRQPOS, HIRQPOS) can wrap not only the horizontal clock position
(1362->0), but the vertical position as well (261->0). And also that
they are "misaligned" by 10 clocks (which is really more of an
internal CPU IC delay thing, we aren't entirely sure why the
difference is there). You probably shouldn't mess with the code if you
don't understand the implications of this on eg range testing :/
2006-10-20 03:53:34 +00:00
byuu f24d17859f Update to bsnes v018r01? release.
I've written a new scheduler for bsnes to take 100% full advantage of
cooperative multithreading. Now, bsnes only performs jumps directly
from one thread to another (CPU->SMP instead of CPU->main->SMP), and
even then only when absolutely needed (eg CPU is accessing SMP when
CPU is currently ahead of SMP).
This unfortunately makes bCPU and bSMP no longer compile. However, it
does yield some impressive speed gains. From 109fps to 125fps.
             By comparison, bsnes v0.017 yielded 128fps with my test
ROM.
 The speed gain though is dependant upon how utilized the CPU<>SMP
communication is, the difference in speed between v0.017 and my WIP
can be anywhere between 1% and 10%, with the WIP always being slower.
 The better news is that this is still without IRQs fully optimized. I
don't know how easy it will be to optimize these, if it's even doable
at all... but if I can, that would yield another very important speed
increase, making the next release the fastest ever. Here's to hoping.
 The bad news though is that cothreading's advantages are pretty much
maxed out completely now. Don't expect any future leaps in performance
from this. Still, overall... a 40% total speed increase and double the
processor synchronization precision was definitely worth the effort,
even for the potential loss of savestates.

 The scheduler should also make sPPU much faster when and if that's
ever started upon, but that's still going to take a very significant
speed hit over bPPU.

 One last benefit of the scheduler is that the new synchronization
method isn't limited to only two clocks. I can now easily add another
clock, eg for SFX/SA-1. Not that I'll be emulating either of those
within the next year or two, though. Just saying...

 I might also make two schedulers, one for cothreaded cores and one
for non-cothreaded cores. One thing is for certain though, I won't be
writing schedulers for every combination of cothreaded<>non-cothreaded
cores (there's 4 of them, CPU, SMP, PPU and DSP). And this will also
rule out run-time polymorphism's compile-time option, so expect that
to change to a compile-time only setting, meaning possibly two
versions of bsnes in the future.

 Now then, I also fixed up S-CPU emulation mode opcodes. Direct page
wrapping, stack wrapping with native mode opcodes and processor status
flag fixes. No games use emulation mode, but accuracy is always nice.

[No archive available]
2006-10-18 05:33:00 +00:00
byuu 35fd80bde7 Update to bsnes v018 release.
I began working on bsnes on October 14th, 2004. I am releasing bsnes v0.018 today to celebrate bsnes' two year anniversary. Please note that this release incurs a ~15% speed reduction since v0.017, due to IRQ and S-SMP timing improvements.
Changelog:
    - Fixed many critical errors in IRQ timing, should be *very* close to real hardware now
    - Corrected major CPU timing bug involving CPU I/O condition 4
    - Corrected bug with generic HiROM / LoROM memory maps
    - Corrected bug involving HDMA indirect channel termination [anomie]
    - OAM address reset now occurs when screen display is enabled, per recent research
    - Readded full DMA, HDMA and HDMA init bus sync timing
    - Added preliminary emulation of S-SMP $00f0 TEST register (6 of 8 bits are supported)
    - Readded emulation of known timing differences between CPU revisions 1 and 2
    - Config file can now control scanline-based PPU render position. This will only be needed until a proper dot-based PPU renderer is added
    - Removed core debugging hooks so that debugging console can remain in public releases, it now functions as a tracer and memory editor
    - Config file paths once again work correctly even if missing trailing backslash
    - Video configuration simplified, sorry in advance to those who enjoyed the profile mode used before
    - Added new configuration screen to control some emulation settings
    - Replaced bsnes program icon with a much nicer one [FitzRoy]
    - Optimized memory speed detection algorithm
    - Preliminary UPS soft-patching support (do not use this yet!)
    - Decreased memory usage and optimized generic libraries used by bsnes (/src/lib)
    - Now caching OAM by one line, somewhat similar to a real SNES. Fixes Winter Gold, but causes line rendering error in Mega lo Mania
    - Lots more, as usual
The following games have been fixed since v0.017 by the above bugfixes:
    - Battle Blaze (J, U)
    - Circuit USA (J)
    - F1 Grand Prix (J)
    - Funaki Masakatsu no Hybrid Wrestler - Tougi Denshou (J)
    - Jumbo Ozaki no Hole in One (J)
    - Mahjongg Taikai II (J)
    - RPG Tsukuru - Super Dante (J)
    - Robocop Versus The Terminator (U, E)
    - Sink or Swim (U, E)
    - Street Racer (J)
    - Touge Densetsu Saisoku Battle (J)
    - Winter Olympics (U, E)
2006-10-14 05:34:24 +00:00
byuu ccf1c00b58 Update to bsnes v017r16? release.
Ok, reverted the SPCRAM initialization pattern, which
should fix Kamen Rider SD.
 Verified DMA timing steps, I had them right. Still need to verify
HDMA/HDMA init, but they're almost definitely the same anyway.
 Also, I noticed the spc700.txt doc by anomie on romhacking.net was
more recent than mine, and had info on $00f0 - TEST o_O
 So, went ahead and added emulation for 5 out of 8 of these bits.
Notably, the CPU speed control bits and the RAM write enable bit. The
other three aren't well understood enough to add support for them just
yet.
 Now, the CPU speed control in the S-SMP means the SMP core is taking
a significant speed hit to support this register. ~5% total speed hit,
though I can probably get that number down a little with some more
optimizations. I know the register is never used by any games, but you
know how I am. I added support for it anyway.
 Note that the WIP doesn't like my inlining combination and is taking
a much more significant speed hit with global optimizations turned on,
so the WIP is ~13% slower than the last one.







> On a side note, kernel streaming method works with event
> notification per audio packet you feed into it, and that
> notification receives full precision time slices even without
> setting the timer resolution manually. At least, when I was using
> kernel streaming in my NES emulator, it didn't need vsync to output
> almost a smooth 60fps, while WaveOut mode outputs in bursts and
> requires vsync to smooth out the frames.




 If you wouldn't mind turning that into a compatible derived Audio
class, I'd love to add this as an option into bsnes :)
 It'll be drop-in and compile, so you don't have to worry about me not
adding the code this time. No problem if you don't have the time /
desire / patience to do this.
 Although, I wouldn't want to do this if it requires 3rd-party
libraries / loading a special .sys driver into the kernel space /
Windows DDK to compile / something else crazy like that.

[No archive available]
2006-10-04 06:27:00 +00:00
byuu f4520d41ec Update to bsnes v017r06? release.
New WIP should fix: RPG Tsukuru, Circuit USA, Jumbo Ozaki no Hole in
One (not a permanent fix, I'm not entirely happy with the HDMA timing,
but at least the name entry screen works again for now), and Taz-
Mania.

 The two games you said started flickering since v0.017.07 might be
fixed now, but I'm not worried about these horizontal-line issues
regardless of when they started occurring at the moment. The other
ones you said would be fixed by setting HCLOCK=256 should be fixed as
well, as this is the new default value.

 Super Mario Kart's line doesn't appear to flicker now, but I think
it's because I'm technically running the emulation a little too fast
again, due to the Ozaki fix. Another game you shouldn't expect to stay
fixed, and again another game I'm not worried about remaining fixed.

 Koushien 2 and Mahjongg Taikai 2 are very likely still broken.
Uniracers definitely is. These appear to be the only three serious
known bugs remaining.

[No archive available]
2006-09-20 04:06:00 +00:00
byuu e308cf4275 Update to bsnes v017 release.
- This version adds major accuracy improvements, countless bugfixes and DSP-1 support. At the time of this release, the only remaining known bug in bsnes is with Uniracers 2-player mode, with well over 300+ games tested.
Changelog:
    - DSP-1 support added [Andreas Naive, byuu]
    - Added cooperative multithreading library, written by myself
    - Rewritten CPU core, now bus accurate
    - Rewritten APU core, now bus accurate
    - Added cartridge database
    - Added several PCB mappers, thanks to research from Overload
    - Added several games to database, fixing several mapping-related bugs
    - Improved mirroring [Nach, grinvader, byuu]
    - vscroll bug in hires, interlaced mode fixed. Fixes RPM racing
    - RTO X=256 bug corrected. Fixes Super Conflict title screen [anomie]
    - Fixed bug in NTSC filter with hires games
    - Updated snes_ntsc to version 2.0.1 [blargg]
    - Fixed bugs in HiROM / LoROM memory mapping. Fixes countless games
    - Fixed major bugs in HDMA routine. Fixes ToP, Mortal Kombat and Genjuu Ryodan
    - Added out-of-order execution to CPU, APU synchronization for major speedup with no accuracy loss
    - IRQs are now delayed after H/DMA transfers. Fixes Wild Guns
    - HDMA transfers now kill active DMA channels that are on the same channel. Fixes Bugs Bunny and World Class Rugby. Special thanks to zones for researching this
    - CPU emulation mode accuracy was improved
    - Cleaned up port-specific code to ease porting
    - Created unified Makefile, used by all ports [Nach]
    - Created GTK+ port of bsnes (although input is currently broken)
    - WRAM is now initialized to 0x55, SRAM to 0xff. Fixes Power Drive, Death Brade and RPM Racing
    - Fixed extreme NMI / IRQ edge case. Fixes Chou Aniki
    - Adjusted PAL execution speed. Fixes Earthworm Jim 2 (E) sound effects
    - Fixed auto joypad polling bug. Fixes La Wares
    - Fixed H/DMA bug that was preventing saves from working in Secret of Evermore
    - bsnes low loads d3dx9_*.dll dynamically at runtime, it is no longer required
    - Added support for 239-line PAL mode rendering
    - As usual, there have been much more changes I've forgotten about since the last release
    - Two C4 bugs fixed. Mega Man X2 / X3 have no remaining known bugs [anomie, byuu]
2006-08-27 03:01:06 +00:00
byuu 192e53bb87 Update to bsnes v016r52 release.
bsnes now builds with no warnings on Linux:
               http://byuu.cinnamonpirate.com/images/desktop082106.png
               However, input is not working unless you build the non-
GTK+ port (see below for more info).

 I'm planning on releasing next weekend. This will likely be the last
public WIP, unless something major is found before the weekend:
               byuu.cinnamonpirate.com/files/bsnes_v016_wip52.zip <-
copy/paste link







> If you can actually get it going fast in an all-in-one window like
> that it'd be cool. I normally just punt and have the GUI separate
> from the emulator output (GTK or Qt for the UI, SDL for the output)
> but it'd be nice for my NEStopia port if I could make it "one piece"
> like the Win32 original




 I can. Please take a look at my above sourcecode, and check your
private messages for another note. Specifically, src/ui/video/sdl.cpp
and src/ui/gtk/gtk_mainwindow.cpp. I am able to merge the SDL output
into the GTK+ window by setting the environment variable
"SDL_WINDOWID=%ld", GDK_WINDOW_XWINDOW(mydrawingbox->window).
 One important thing to note is that you must not initialize SDL video
until the render window has been realized. Simply showing the window
is not enough. You need to also clear all pending events in GTK+ after
showing the window before calling SDL video init, or it will die.
               You can do that with this code:






    gtk_widget_show(mainwindow);
                       while(gtk_events_pending() == true) {
                       gtk_main_iteration_do(false);
                       }




 However, one problem I am having is that by calling
gtk_main_iteration_do(), it steals all SDL input, and I'm not able to
poll any keypresses. This happens whether I embed the SDL video output
into the GTK+ window or not. The only way to get SDL input is to
ignore all GTK+ events, effectively freezing the window completely.

               I don't suppose you'd mind sharing how you got SDL
input working with GTK+ with me?
2006-08-21 00:43:46 +00:00
byuu 0ed9edfcdb Update to bsnes v016r46 release.
wip46 up. Adds all kinds of things, please test.

 First, no more d3dx9_27.dll requirement to run the application, but
screenshots still work if you have any d3dx9_nn.dll files.
 I specifically want to know if any of the other versions (24, 30,
etc) cause the emulator to crash when use. I'm pretty sure the
function is backwards-compatible, but we should probably make sure
before I make the next release and start getting bugreports about
screenshots crashing the program.
             Note: there is no error message for failed screen
captures, I'll add that in eventually.

 Next, the video options finally enable/disable controls depending on
certain settings. Should make using the video options a little easier.

 Next, to enable SDL audio on Windows and remove the win32 port's
wMain.hwnd reference, I now pass GetDesktopWindow() to DirectSound's
SetCooperativeLevel function, since no sound comes out if you pass a
null handle. This is because I don't know how to get the window handle
from SDL, and I prefer to keep port-specific code out of there if
possible.
             Note: SDL is not a windows port, but it builds on
windows, and thus needs DirectSound to output audio on windows.
 I'm hoping this doesn't cause audio problems for anyone else, but
honestly I have no idea what DSound uses the window handle with
DSSCL_PRIORITY for anyway.

 The $2100 luminance stuff was improved by adding rounding support to
the double-to-int casts, so fades should appear a little smoother now
in games.

 Possibly fixed a bug where RTO wasn't being calculated when
brightness=0 and the screen is enabled. Didn't see any improvements in
the three known bugged games.

[No archive available]
2006-08-11 06:59:00 +00:00
byuu 764fe1974a Update to bsnes v016r44 release.
[No changelog available]
2006-08-08 02:02:38 +00:00
byuu a55d640459 Update to bsnes v016r42 release.
Ok, one semi-large change if anyone wants to test.

             byuu.cinnamonpirate.com/files/bsnes_v016_wip42.zip

 This is built for maximum speed. No debugger, PGO enabled, favor
speed, no c++ EH (so no ZIP/JMA), and a new addition: links against
msvcrt instead of libcmt.

 By using msvcrt and some evil linker hacks I was finally able to
build the SDL port again on Windows. So now I just need to focus on
cleaning that up so the next release will build on Linux out of the
box. Anyway, I tried it on the non-SDL port for the hell of it, and
noticed not only a 20% drop in EXE size, but a ~10-11% speedup as
well. Only problem is it requires msvcr80.dll, and I have no idea how
common that file is. So, that's what this wip is for. Does this
version work for you, and if it does, does it run faster? A direct FPS
comparison between v0.016 and v0.016.42 would be helpful if you're not
sure.
2006-08-04 01:27:04 +00:00
byuu 6010bffe5d Update to bsnes v016r38 release.
Ok, this WIP rewrites the input code and modifies the PAL clock speed.
Fairly major changes. Ideally, this will wipe out four bugs without
causing any new ones since wip37.

             Bug fixes :
             Earthworm Jim 2 (E) - adjusted PAL CPU clock speed.
Please test for *new* sound problems in PAL games
             La Wares (J) + Galivan 2 (J) - no longer return 0 when
auto joypad is off for polling $4218-$421f
             Super Conflict (J) - added anomie's new OAM RTO findings
to fix title screen

 The input code was almost completely rewritten to simulate real
hardware more. As such, it's very possible there are new input bugs.

             Ok, so then
byuu.cinnamonpirate.com/files/bsnes_v016_wip38.zip
 Please only download if you intend to test games and report feedback.
This version is slower than normal, lacks ZIP+JMA loading, and has the
debugger enabled (that is only useful to me, it lacks a functional
user interface) which slows down emulation even more. eg you're better
off with v0.016 official if you just want to run games.
             As always, please don't post this link anywhere else, or
I will be forced to remove the file to conserve bandwidth.

 If anyone posts bugs that hasn't tested against wip37, can I please
have someone with wip37 verify/deny the bug presence in wip37 as well
as in 016 official? wip37 isn't on my website because I don't have a
lot of web space to spare.

             Thank you to everyone in advance for helping.
2006-07-27 23:56:42 +00:00
byuu e492268025 Update to bsnes v016r27a release.
Ok, I tried converting the switch/case table to a jump table for both
CPU+APU cores. Results? EXE is 70kb larger, compile time is 5-10%
slower, and speed is identical. Needless to say I reverted that change
back. I then tried narrowing down the cause of the PGO error. Found
out it was Dai Kaijuu Monogatari. If I don't run that, I can build
with PGO. Unfortunately, this is the ROM I use to stress optimize
color add/sub. So as a result, this game will run a little slowly now
(sort of like how Chrono Trigger's OPT title screen effects were
before). But, better one game than all, right?

             byuu.org/files/bsnes_v016_wip27a.zip

 Once again, please do not submit news about this to an emulation
site. The file will be removed if I notice anyone mentioning it
anywhere.

             That will be 20-25% faster than wip27, but otherwise
everything is identical.

 DSP1: there's either a bug in op02, op06, or in the getSr/getDr/setDr
functions. We have so far been unable to spot the error and correct
it. Help is always welcome, as always. Please consider DSP-1 support
as not being there at all. I doubt any games will work right with it
right now :(

             This is how interlace works :
             I call each frame a "field", meaning even or odd fields
on your television / monitor.
             When interlace is off, I draw to the even fields every
time, so you don't notice anything.
 However, when interlace is on, I alternate between which one I draw
to each field. So depending on your frameskip, this can cause serious
problems for interlace mode. I also only physically draw to "half" the
resolution each field, much like a real TV would. This makes 512x448
mode just as fast as 512x224 mode.
 I can't think of an easy way to cheat the system with frameskipping.
Luckily, very very few games use interlace at all. Most use hires
512x224 and that's it.
2006-07-09 05:32:10 +00:00
byuu a36c26c997 Update to bsnes v016r27 release.
Here's a WIP to try out, it's 20-40% slower than it
should be, due to PGO crashing the compiler*.

               Please copy and paste link, and _do not_ post this on
emulation news sites or I will remove the file.

               byuu.org/files/bsnes_v016_wip27.zip

 Even though it's slower, could I get some people to try running
through a bunch of games and look for new bugs? Given I rewrote the
entire CPU+APU, it's possible some new bugs crept in.

               * No release this weekend. Please be sure to thank
Microsoft personally for the delay.







    rc /r /fobsnes.res bsnes.rc
                       cl /Febsnes.exe /nologo /O2 /GL /EHsc main.obj
    libco.obj libstring.obj
                       libconfig.obj libbpf.obj reader.obj cart.obj
    cheat.obj memory.obj bmemory.obj
                       cpu.obj scpu.obj bcpu.obj apu.obj sapu.obj
    bapu.obj bdsp.obj ppu.obj bppu.ob
                       j snes.obj srtc.obj sdd1.obj c4.obj dsp1.obj
    dsp2.obj obc1.obj adler32.obj co
                       mpress.obj crc32.obj deflate.obj gzio.obj
    inffast.obj inflate.obj inftrees.obj
                       ioapi.obj trees.obj unzip.obj zip.obj zutil.obj
    jma.obj jcrc32.obj lzmadec.obj
                       7zlzma.obj iiostrm.obj inbyte.obj lzma.obj
    winout.obj bsnes.res kernel32.lib use
                       r32.lib gdi32.lib comdlg32.lib comctl32.lib
    d3d9.lib d3dx9.lib ddraw.lib dsound
                       .lib dinput8.lib dxguid.lib /link
    /PGD:bsnes.pgd /LTCG:PGOPTIMIZE
                       Merging bsnes!1.pgc
                       Generating code
                       \bsnes\src\apu\sapu\core\core.cpp(16) : fatal
    error C1001: An internal er
                       ror has occurred in the compiler.
                       (compiler file
    'f:\rtm\vctools\compiler\utc\src\P2\main.c[0x10CB9ABB:0x00000025]
                       ', line 182)
                       To work around this problem, try simplifying or
    changing the program near the l
                       ocations listed above.
                       Please choose the Technical Support command on
    the Visual C++
                       Help menu, or open the Technical Support help
    file for more information

                       LINK : fatal error LNK1000: Internal error
    during IMAGE::BuildImage




               What is on sapu\core\core.cpp(16) that's too complex
for Visual c++ to handle?







    status.in_opcode = false;




               Please, if anyone can simplify that for me, let me
know.

 Seriously, though, if anyone can take a look at the source and fix
this compiler error I'd really appreciate it, and I'll get a release
out this weekend. I'm using Visual C++ 2005 Professional. Otherwise
I'll have to set it aside because I don't have time.

[No archive available]
2006-07-09 01:38:00 +00:00
byuu a3945e5772 Update to bsnes v016 release.
- Added Direct3D renderer with options for disabling hardware filtering and scanlines
    - Screenshots can now be captured in BMP, JPEG, or PNG format
    - Added config file option to specify default ROM and SRAM paths
    - Config file is always loaded from path to bsnes executable
    - Added support for analog mode joypad input
    - Up to 32 joypads can be used at once now
    - Fixed bug regarding enabling interlace mid-frame
    - Moved PPU rendering to V=240, from V=0
    - Started on new debugger. So far only debug messages and memory editor added
    - Added joypad axis resistance option for analog input mode
    - Added config file option to set window style attributes
    - Added color adjustment settings for brightness, contrast, gamma, and scanline intensity
    - Added grayscale, sepia, and invert color settings
    - Added NTSC filter by blargg, HQ2x filter by MaxSt, and Scale2x filter
    - PPU now renders scanline 224
    - Revampled about box
    - Added Game Genie / PAR cheat code support + editor, saves codes to .cht files
    - HDMA channels are no longer disabled when starting DMA, fixes Dracula X [DMV27]
    - Fixes to OAM priority mode (not perfect), fixes Final Fantasy: Mystic Quest [DMV27]
    - Fixed ENDX sound bug, fixes voices in Earthworm Jim 2 [DMV27]
    - bsnes should now compile with MinGW [DMV27]
    - Added DSP-2 support
    - Added OBC-1 support
    - Major rewrite of SNES address bus mirroring and MMIO handlers
    - Many address mirroring corrections, fixes Dezaemon, etc
    - Blocked invalid (H)DMA transfers, fixes Kirby's Super Funhouse
    - Wrote Win32 API wrapper and ported all GUI code to use it, should help to create Linux GUI later on
    - Revampled input system, should lead to customizable GUI shortcut keys later on
    - Fixed numerous bugs with input registers. Fixes many games that previous had their intro cut off (Super Conflict, etc), and many that never accepted input (Super Double Dragon, etc)
    - Moved auto joypad strobing from V=225 to V=227
    - Killed OAM table caching and window range caching, as they were actually hindering speed
    - Rewrote input configuration screen to show currently mapped keys
    - Greatly enhanced configuration options for each video profile
    - Modified fullscreen mode to exit to windowed mode when menu is activated, use F11 to toggle fullscreen mode
    - Fixed bugs in txs, wai, brk, cop, and rti opcodes [DMV27]
    - Fixed bug with emulation-mode IRQs [DMV27]
    - Initializing DMA registers to $ff [DMV27]
    - Memory writes now update CPU MDR register (open bus) [DMV27]
    - Improved ROM header detection, fixes Chou Jikuu Yousai Macross [DMV27]
    - Reading OAM no longer updates OAM latch
    - Writing to OAM high table no longer updates OAM latch
    - Writing CGRAM now updates CGRAM latch
    - Improved pseudo-hires rendering [blargg]
    - Much, much more
2006-04-25 15:51:10 +00:00
byuu 6b6233b3af Update to bsnes v015 rc3 release.
[No changelog available]
2006-04-22 01:02:32 +00:00
byuu 9f63cb1b99 Update to bsnes v015 rc2 release.
[No changelog available]
2006-04-20 00:26:54 +00:00
byuu 49c39e0e4d Update to bsnes v015 release.
- Added GZ / ZIP / JMA archive support [Nach, NSRT team]
    - Fixed bug in APU ADDW/SUBW opcode flags, thanks to DMV27, anomymous for info
    - Mosaic support is now (mostly) hardware accurate, thanks to TRAC for info
    - Fixed a bug in SC tilemap clipping, fixes Seiken Densetsu 3
    - Emulated pseudo-hires mode, uses a fairly poor color filter to simulate TV effect, the same one that SNES9x and Super Sleuth use
    - Rewrote the ROM loading code to be more port-friendly, and improved header detection
    - Added C4 emulation -- mostly correct. Only minor bugs remain, possibly not C4 related [Nach, byuu], also uses code from zsKnight, Overload, and anomie
    - Fixed noise channel generation for DSP, fixes Dual Orb 2 opening. Thanks to DMV27 for info
    - Fixed bug with DSP VxSRCN registers, fixes horrible sound corruption in Mortal Kombat 2/3
    - Modified DSP KON register reading to act according to anomie's research, while still allowing Der Langrisser, etc. to play sounds correctly
    - Fixed a bug in CPU BCD math, fixes numbers in SimEarth, thanks to DMV27 for info
    - Rewrote the windows port from scratch
    - -- Added triple buffering support (buggy)
    - -- Added DirectInput (joypad) support, allows both keyboard and joypad to be mapped to the same SNES controller button. Only one controller supported for this release, will be improved shortly
    - -- Added pause key (mapped to Pause/Break)
    - -- bsnes no longer consumes CPU time when paused or when no ROM is loaded
    - -- Updated DirectDraw to 7, and added video mode configuration options to configuration file
    - -- Video modes can specify screen width+height, refresh rate, and render width+height
    - -- Added CTRL+[1-0] hotkeys for swapping video modes
    - -- Added +/- hotkeys for adjusting frameskipping rate
    - -- Added adjustable speed regulation. There are five modes, all can be adjusted inside the configuration file. CTRL+[+/-] will adjust the speed mode.
    - -- Added PPU options to toggle any BG / OAM layers with any priority, HDMA effects, and offset per tile effects
    - -- Added option to accept invalid button combinations (up+down, left+right) to joypad config menu
    - -- bsnes now properly clears the main window when unloading games
    - [code] Made destructors for base classes virtual, so the correct destructors will be called now
2005-12-03 21:05:52 +00:00
byuu 7dec0b2a3c Update to bsnes v014 release.
This version adds speed regulation, greatly improves PPU rendering, and increases speed by ~30% over the previous version.
Changelog:
    - Rewrote offset-per-tile mode emulation, should be correct now. Fixes Chrono Trigger, Contra III, Tetris Attack, etc.
    - Fixed a bug with HDMA occuring during interrupts. Fixes Tales of Phantasia souond test screen
    - Updated compiler to Visual Studio 2005, and enabled profile guided optimizations
    - Added conditional compilation of debugging functions (faster without them)
    - Added conditional compilation of core classes as pointers (allowing polymorphism) or objects (allowing inlining). The latter results in a speed increase
    - Small fixes to BG and OAM rendering routines
    - Corrected sprite tile bounds wrapping
    - Corrected sprite rendering in hires video modes
    - Rewrote color add/sub routines, should be correct now. Fixes Illusion of Gaia menu, etc.
    - Optimized video blitting routines, will temporarilly break mixed video mode screenshots
    - Prevented selecting menu options via return key from being recognized as keypresses by the emulator
    - Added system speed regulation (60hz/NTSC or 50hz/PAL)! Many thanks to kode54, GIGO, and Richard Bannister for their assistance
I disabled the debugger and polymorphism, and enabled profile guided optimizations for this build, to maximize speed. The debugger and polymorphism can be re-enabled via uncommenting the respective #defines in src/base.h and recompiling, or bsnes v0.013 can be used. I may start releasing two separate builds in the future... not sure yet.
2005-11-12 16:49:26 +00:00
byuu f288280ceb Update to bsnes v013r02 release.
[No changelog available]
2005-10-25 23:25:28 +00:00
byuu c6c5f4669c Update to bsnes v013 release.
- Greatly improved HDMA timing and accuracy with help from anomie and DMV27 -- fixes bugs in Energy Breaker and Street Fighter Alpha 2
    - Fixed a problem with color add/sub code -- fixes opening battle in Tales of Phantasia and clouds in Energy Breaker
    - Temporarily added DMV27's bugfix for the DSP KON register -- fixes sound in Der Langrisser, but this is not a hardware-accurate fix
    - Disabled VRAM writes outside of vblank -- fixes Hook, but breaks many PD ROMs and fan translations (Roto's BS Zelda hack, Gideon Zhi's Ys 4 translation, etc). I might add an option in the future to toggle this behavior, but for now these games will no longer work. Please keep in mind these games will not run properly on real SNES hardware, either.
    - Improved frameskipping code thanks to a suggestion from Richard Bannister
    - Misc. other code cleanups and improvements (notably in the color table generation code)
    - bsnes is now endian-safe and runs on Mac OS X
    - Added caching support for window clipping tables resulting in a slight speedup. Please let me know if you spot any errors as a result of this change.
2005-10-23 23:32:30 +00:00
byuu 397b9c4505 Update to bsnes v012 release.
Changelog:
    - Added S-DSP emulation
    - Added sound output support via DirectSound -- no sound buffering though, so sound is muted by default
    - Added option to record raw sound output to WAV files
    - Added multiple color adjustment filters to the video output
    - Added mode3/4 direct color support
    - Added mode7 direct color and mosaic support
    - Greatly improved mode7 rendering algorithm thanks to anomie
    - Fixed mode7 screen repitition and EXTBG effects
    - Greatly increased accuracy of NMI and IRQ timing, and emulated many newly discovered hardware quirks involving the two
    - A few speed improvements courtesy of Nach for profiling the code for me
I'm now looking for assistance with sound buffering. Specifically, I need help modifying the DirectSound code to allow the emulator to be ran between 50%-400% normal speed, while keeping the sound output relatively good. If you have experience with this and can help, please get in touch with me (setsunakun0 at hotmail dot com).
2005-10-02 00:38:34 +00:00
byuu 7e2cfb6d40 Update to bsnes v011 release.
- Fixed Mode 0 color palette index problem. Fixes ToP, DQ5, etc.
    - Improved LoROM memory mapper to support 32mbit images. Fixes Tokimeki Memorial, etc.
    - Added full S-DD1 support, SFA2 and Star Ocean are now playable. Special thanks to Andreas Naive
    - Updated BGnxOFS / Mode7 registers with anomie's latest findings
    - Added basic ROM mirroring support. Fixes copy protection issues in MMX, etc.
    - Rewrote string library to work better on gcc/linux
    - Cleaned up S-RTC/S-DD1 emulation to make way for future add-on chip emulation
    - Rewrote DMA code, now runs cycle-by-cycle
    - Rewrote HDMA code, now allows HDMA to be enabled mid-frame, fixes many games
    - Fixed a bug in Mode7 vertical screen flip mode. Fixes FF5 title screen, etc.
    - Greatly improved IRQ triggering. Fixes Der Langrisser, etc.
    - Added full support for open bus. This includes PPU1 and PPU2 open bus support
    - Modified CPU core back to cycle-based system. Slower, but improves debugger
    - Implemented temporary fix for debugger to handle new cycle-based cores
    - Modified CGRAM to ignore highest bit, since it is not used at all by the SNES, and is impossible to read on real hardware. Lowers memory usage by ~1.2mb
    - Added mostly accurate PAL timing support. This should increase compatibility by ~30% or so
    - More stuff I'm forgetting at the moment...
2005-08-26 20:38:00 +00:00
byuu 970dcea0ac Update to bsnes v010 release.
bsnes now supports SPC700 emulation (no DSP or sound support, however), and has greatly improved compatibility. It also now contains a keyboard-only joypad configuration tool.
2005-08-03 21:22:42 +00:00
byuu 402c146a53 Update to bsnes v009 release.
- Fixed non-interlaced display modes from not drawing every other frame
    - Changed OAM halve to skip every other scanline in 224-height modes
    - Updated renderer to properly support games that switch resolutions mid-frame
    - Fixed VRAM address remapping modes, fixes DQ3R, FF: MQ
    - Fixed a bug in main color window clipping affecting BGs
    - Added video color curve option, thanks to Overload for the idea + color table
    - Added vblank, FPS counter, and DDraw surface memory options to settings menu
    - Added fullscreen modes 640x480 and 1024x768 to video modes
    - Added option to toggle the menubar on and off by pressing the escape key
    - Mode3 was not rendering sprites
    - Priorities were wrong for modes 2-4, thanks to anomie for info
    - Fixed a serious bug in IRQ interrupts. May not be perfect, but helps many games
2005-06-26 11:07:38 +00:00
byuu a471c150c9 Update to bsnes v008 release.
moving the window + main color window clipping into the bg/oam/mode7 rendering routines themselves, I was able to greatly simplify the most complicated part of rendering: the final pass where color add/sub effects are applied. As a result, the new PPU core is not only ~35% faster (on graphics intensive screens, even faster on simpler screens), but more accurate as well. Awesome.
In celebration, I´m releasing bsnes v0.008. I can actually run all games I have at >60fps on my Athlon 1.67ghz PC. Probably not something to brag about, though ...
Oh, and I also updated the keyboard polling code to only capture keypresses if the main window has focus. I´ve been meaning to do this for the better part of a year now, but never got around to it.
If, for some reason, you still want to use the old renderer, you can uncomment the first line in src/ppu/bppu/bppu.h and recompile the emulator yourself. Or you can use v0.007a, I´ll leave it up for a bit.
2005-06-21 09:13:40 +00:00
byuu ea38ea2537 Update to bsnes v007a release.
[No changelog available]
2005-06-13 04:34:10 +00:00
byuu 09b326ae86 Update to bsnes v007 release.
I have done quite a bit, so I´ll try my best to recap most of the fixes since the last release...
    - HDMA was not running during DMA transfers
    - Emulator did not recognize any filetype other than .smc
    - Added configuration file support and imported my vector/string/config libraries into bsnes
    - Added option to use system RAM instead of video RAM for display, this can greatly increase speed on certain video cards
    - Increased speed by ~15% by adding 256x224 renderer (still very buggy when the SNES mixes video modes mid-frame)
    - mvn/mvp opcodes were not setting the DB register
    - Fixed joypad input in many games (Super Mario: All Stars, Dragon Quest III, etc.)
    - Major speedup with frakeskip option
    - Fixed default aspect ratio when emulator is first started
There´s probably a lot more, but that´s all I remember offhand... this release should be a lot closer to the quality of v0.005a, but still needs a bit more polishing.
2005-06-12 08:15:22 +00:00
byuu a60f667b25 Update to bsnes v006 release.
The rewrite is now complete. I finished adding frameskip, and fixed some crashing issues with loading multiple ROMs. I wrapped all malloc/free calls with memalloc/memfree, which are custom functions that log each call, and let you pass sprintf-style arguments to them to debug memory leaks. Don´t see any, nor have I noticed any in bsnes; but it makes a nice test tool anyway.
I found out today that the mvn/mvp instructions actually set the DB register to the destination bank. Weird. This fixed Final Fantasy V, Chrono Trigger, and Dragon Quest III (which now runs as a result) graphics. Not all of them, but quite a few. I had also previously been setting the M/X flags of the P register when xce was executed. I never anticipated a game using xce while in native mode to switch to... native mode... but apparently, Chrono Trigger does. And my code was incorrect. So now Chrono Trigger gets past the name select screen, but dies a few screens after still.
The only really major flaw I am aware of that bsnes v0.005 did not have is with the battle screens in Squaresoft games. I get horrible flickering and "crushed scanlines" every other frame on them, for some reason. I´ll try and track that down for the next release, but I didn´t want to hold up a new release any longer. As a result, I´ll leave up v0.005a for the time being until I get this problem fixed.
2005-05-13 12:03:02 +00:00
byuu 6f7cf1bd31 Update to bsnes v005a release.
[No changelog available]
2005-02-06 04:04:32 +00:00
byuu 8751bc433f Update to bsnes v005 release.
Released a new version of bsnes today. Version 0.0.005 is up on the main page now.
The changes since .004 wip10 include: DRAM timing fix (now occurs mid-opcode), memory address speed fix, added CPU cycle condition 4 to all opcodes that use it, and fixed a bug where DRAM refresh was not changing start positions every other scanline.
With the new version, Chrono Trigger should get a little further, and Final Fantasy 5 should get in-game now. The only problem FF5 seems to have is sprite corrution in the menus. I´m too busy with the CPU timing to mess around with the PPU right now, though. Also, the APU is not bridged to the CPU in this release, so compatibility is (still) really low.
After I get the APU working fairly well, I´ll (try and) start keeping a what´s new file so that people can know what all changed between public releases of the emulator without having to follow this page. Sorry for the inconvenience in the mean time.

[No archive available]
2005-02-05 00:00:00 +00:00
byuu afbee40bc0 Update to bsnes v004 release.
[No changelog available]

[No archive available]
2004-12-24 00:00:00 +00:00
byuu 81e9be64d2 Update to bsnes v003r01 release.
[No changelog available]

[No archive available]
2004-12-13 00:00:00 +00:00
byuu 0cd4972d11 Update to bsnes v002 ir11 release.
Doubled the width of sprites when the width of the screen is 512. The SNES Test Program now runs through all of the character test correctly. The windows are off-by-one though.
Added the very bizarre 00:4300-00:437f memory region that is unique from 7e:4300-7e:437f. Then I added read support from $2180. This allowed the SNES Test Program to not crash after failing the electronics test, and it also fixed the horrible graphics corruption in Der Langrisser.
The fonts were still failing, so I went in with my tracer and found out what was causing it. The following code:
	gx816->regs.pc += (signed char)(arg + 2);
This is the code used to increment the pc counter after relative branches. The problem is that I had parinthesis around the + 2 as well as arg. Therefore, if arg was 0x7e, 0x7f, 0xfe, or 0xff, it would change the sign. This would in turn make the program counter off by 256 bytes. Very bad. Fixing this fixed the font in Der Langrisser, and also fixed Super Mario World, when Yoshi spits a turtle out, the game will no longer crash.
Next, via the Der Langrisser world map, I realized that anomie´s description of the BG/mode7 register latches was reversed: the BG registers (may) share a common latch between all of them, but the mode7 registers do not. I removed the common latches from the mode7 regs, but I haven´t added the latch to the BG registers yet.
I also realized that clearing the entire screen on a BG mode change would probably not be a good thing for games that change video modes mid-frame. So I tried working on getting the weird single-frame garble that pops up every minute or so to go away, but to no avail. I´m having a difficult time tracking down the exact cause of the error, but I believe it to be a buffer overflow, or something along those lines.

[No archive available]
2004-12-11 00:00:00 +00:00
byuu 938609818f Update to bsnes v002 ir9 release.
[No changelog available]
2004-12-07 01:12:32 +00:00
byuu 487491e6f2 Update to bsnes v002 ir7 release.
The bug in the Bahamut Lagoon mode7 screens turned out to be that I never added the dividend result to $4216/$4217. It always returned the multiplication of $4202/$4203. This also fixed the sprites in battles for Dai Kaijuu Monogatari. I added a bunch of window stuff, including a double size windowed mode, and a fullscreen mode. Lots of fixes to the interface, as well as code cleanup. It's still pretty buggy, there's a lot of variables to keep up with in the GUI at present...
I also added better tile caching, I just cache all tiles when they are rendered, for all 3 color depths (4, 16, 256). Seems to help a little. A portion of the speed gain was lost by fixing the dividend result, though. Very strange, since I wouldn't think much work would go into a simple line of code that's called 20 times a frame probably at best... oh well.

[No archive available]
2004-12-05 00:00:00 +00:00
byuu a6ccc7e5d2 Update to unreleased version.
First, I fixed some issues with the mode7 / scroll regs thanks to info from anomie. Qwertie's mode7 demo runs without modification now. Next, I fixed a small glitch with tiles getting cut off on the right edge of the screen, fixes some flickering on the edges in Zelda 3. Finally, I added tile caching to all 4 BG layers. It didn't really increase speed by much (~10%), and upped the RAM requirements by 1.2mb... I left the old code in case I need to fall back on it. My only guess (hope) is that the bottleneck lies somewhere else...

[No archive available]
2004-12-04 00:00:00 +00:00
byuu 39dd39cc09 Update to unreleased version.
Had a small glitch in the OAM $2102/$2103 register settings, fixed that and now both Zelda 3 and Super Mario World always show sprites perfectly. The only error to my knowledge in the below screenshots is that the large version of the mario sprite in the third pic is not shown at the top left of the screen. Which could be because I have no mosaic support. But at any rate, both games should be completely playable now, albeit very slowly. I tried to speed up things by caching some stuff in the PPU, but I couldn't find a nice way to implement it that would result in a significant speed increase. Oh yeah, Zelda won't follow you in Zelda 3, but you can just go to the throne room and push the picture to the right yourself, and she'll appear when you reach the sanctuary.
I also updated the debugger, it uses a smaller font, which is a bit harder to read, but saves a lot of screen space. This will definately be needed when I add in more of the debugging windows in the future, so it's better to get used to it now than later.

[No archive available]
2004-11-27 00:00:00 +00:00
byuu 1a017ff697 Update to unreleased version.
Added SRAM support. It's compatible with other emulators. It maps both the LoROM SRAM between $700000-$7dffff, and HiROM SRAM between $3n6000-$3n7fff (where n = 0 - f).
Modified the screen to redraw at scanline 224, instead of 0. Fixes some flickering between screens in some games.
Moved the lighting adjustment code ($2100 bits 0-3) directly into the PPU, rather than outside in the platform-specific code. This is because games have the ability to adjust this value per scanline. Also allowed the games to control the background enable ($2100 bit 7) flag per scanline now. This allowed Bahamut Lagoon's dragon intro to work.
I then added mode2 support, so now all that's missing is modes 5 and 6 (interlaced).
I also created an easy #define to select between the public domain mode7 code I wrote, and the snes9x mode7 code. I'll go through one of these days and rewrite the snes9x code in a clean environment (e.g. without using the current mode7 file as a reference) to avoid the issue altogether, but for now... the snes9x code is twice as fast, and that's good enough for me.
Lastly, I also added in a trace checkbox to enable tracing. As such, the zip file of my work up now has debug mode enabled by default. I really need an overhaul of the gui implementation, and a configuration file. I have a very nice string library system (that I wrote myself) that I would love to take advantage of in this project.
Here's some screenshots of the dragon intro. Lots of HDMA here, showing that I pretty much have HDMA down, now. I remember zsnes used to have a problem with these screens, and had to do something with an HDMA hack to get around it. They figured out what the problem was, eventually. I was actually quite surprised my emulator does not suffer from this same problem. Lucky for me, as they never did mention what the problem was to begin with. The HDMA is technically drawing too soon (at the start of the scanline instead of the end) which probably messes things up a bit, and I'm guessing the scroll registers are screwing up in one or two of the below pics. No mosaic support either.

[No archive available]
2004-11-21 00:00:00 +00:00
byuu 591adfcb25 Update to unreleased version.
Initial release

[No archive available]
2004-10-14 00:00:00 +00:00
1843 changed files with 56411 additions and 217061 deletions

No files matched your search

-2
View File
@@ -1,2 +0,0 @@
higan/profile/WonderSwan.sys/internal.ram
higan/profile/WonderSwan Color.sys/internal.ram
-65
View File
@@ -1,65 +0,0 @@
include ../nall/GNUmakefile
target := tomoko
# console := true
flags += -I. -I.. -O3
objects := libco audio video resource
# profile-guided optimization mode
# pgo := instrument
# pgo := optimize
ifeq ($(pgo),instrument)
flags += -fprofile-generate
link += -lgcov
else ifeq ($(pgo),optimize)
flags += -fprofile-use
endif
# platform
ifeq ($(platform),windows)
ifeq ($(console),true)
link += -mconsole
else
link += -mwindows
endif
link += -mthreads -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32
link += -Wl,-enable-auto-import
link += -Wl,-enable-runtime-pseudo-reloc
else ifeq ($(platform),macosx)
flags += -march=native
else ifneq ($(filter $(platform),linux bsd),)
flags += -march=native -fopenmp
link += -fopenmp
link += -Wl,-export-dynamic
link += -lX11 -lXext
else
$(error "unsupported platform")
endif
compile = \
$(strip \
$(if $(filter %.c,$<), \
$(compiler) $(cflags) $(flags) $1 -c $< -o $@, \
$(if $(filter %.cpp,$<), \
$(compiler) $(cppflags) $(flags) $1 -c $< -o $@ \
) \
) \
)
%.o: $<; $(call compile)
all: build;
obj/libco.o: ../libco/libco.c $(call rwildcard,../libco/)
obj/audio.o: audio/audio.cpp $(call rwildcard,audio/)
obj/video.o: video/video.cpp $(call rwildcard,video/)
obj/resource.o: resource/resource.cpp $(call rwildcard,resource/)
ui := target-$(target)
include $(ui)/GNUmakefile
clean:
-@$(call delete,out/*)
-@$(call delete,obj/*)
-88
View File
@@ -1,88 +0,0 @@
#include <emulator/emulator.hpp>
namespace Emulator {
#include "stream.cpp"
Audio audio;
auto Audio::reset(maybe<uint> channels_, maybe<double> frequency_) -> void {
if(channels_) channels = channels_();
if(frequency_) frequency = frequency_();
streams.reset();
reverb.reset();
reverb.resize(channels);
for(auto c : range(channels)) {
reverb[c].resize(7);
reverb[c][0].resize(1229);
reverb[c][1].resize(1559);
reverb[c][2].resize(1907);
reverb[c][3].resize(4057);
reverb[c][4].resize(8117);
reverb[c][5].resize(8311);
reverb[c][6].resize(9931);
}
}
auto Audio::setInterface(Interface* interface) -> void {
this->interface = interface;
}
auto Audio::setVolume(double volume) -> void {
this->volume = volume;
}
auto Audio::setBalance(double balance) -> void {
this->balance = balance;
}
auto Audio::setReverb(bool enabled) -> void {
this->reverbEnable = enabled;
}
auto Audio::createStream(uint channels, double frequency) -> shared_pointer<Stream> {
shared_pointer<Stream> stream = new Stream;
stream->reset(channels, frequency, this->frequency);
streams.append(stream);
return stream;
}
auto Audio::process() -> void {
while(true) {
for(auto& stream : streams) {
if(!stream->pending()) return;
}
double samples[channels] = {0.0};
for(auto& stream : streams) {
double buffer[16];
uint length = stream->read(buffer), offset = 0;
for(auto c : range(channels)) {
samples[c] += buffer[offset];
if(++offset >= length) offset = 0;
}
}
for(auto c : range(channels)) {
samples[c] = max(-1.0, min(+1.0, samples[c] * volume));
if(reverbEnable) {
samples[c] *= 0.125;
for(auto n : range(7)) samples[c] += 0.125 * reverb[c][n].last();
for(auto n : range(7)) reverb[c][n].write(samples[c]);
samples[c] *= 8.000;
}
}
if(channels == 2) {
if(balance < 0.0) samples[1] *= 1.0 + balance;
if(balance > 0.0) samples[0] *= 1.0 - balance;
}
interface->audioSample(samples, channels);
}
}
}
-65
View File
@@ -1,65 +0,0 @@
#pragma once
#include <nall/dsp/iir/biquad.hpp>
#include <nall/dsp/resampler/cubic.hpp>
namespace Emulator {
struct Interface;
struct Audio;
struct Stream;
struct Audio {
auto reset(maybe<uint> channels = nothing, maybe<double> frequency = nothing) -> void;
auto setInterface(Interface*) -> void;
auto setVolume(double volume) -> void;
auto setBalance(double balance) -> void;
auto setReverb(bool enabled) -> void;
auto createStream(uint channels, double frequency) -> shared_pointer<Stream>;
private:
auto process() -> void;
Interface* interface = nullptr;
vector<shared_pointer<Stream>> streams;
uint channels = 0;
double frequency = 0.0;
double volume = 1.0;
double balance = 0.0;
bool reverbEnable = false;
vector<vector<queue<double>>> reverb;
friend class Stream;
};
struct Stream {
auto reset(uint channels, double inputFrequency, double outputFrequency) -> void;
auto pending() const -> bool;
auto read(double* samples) -> uint;
auto write(const double* samples) -> void;
template<typename... P> auto sample(P&&... p) -> void {
double samples[sizeof...(P)] = {forward<P>(p)...};
write(samples);
}
private:
const uint order = 6; //Nth-order filter (must be an even number)
struct Channel {
vector<DSP::IIR::Biquad> iir;
DSP::Resampler::Cubic resampler;
};
vector<Channel> channels;
friend class Audio;
};
extern Audio audio;
}
-35
View File
@@ -1,35 +0,0 @@
auto Stream::reset(uint channels_, double inputFrequency, double outputFrequency) -> void {
channels.reset();
channels.resize(channels_);
for(auto& channel : channels) {
if(outputFrequency / inputFrequency <= 0.5) {
channel.iir.resize(order / 2);
for(auto phase : range(order / 2)) {
double q = DSP::IIR::Biquad::butterworth(order, phase);
channel.iir[phase].reset(DSP::IIR::Biquad::Type::LowPass, 20000.0 / inputFrequency, q);
}
}
channel.resampler.reset(inputFrequency, outputFrequency);
}
}
auto Stream::pending() const -> bool {
return channels && channels[0].resampler.pending();
}
auto Stream::read(double* samples) -> uint {
for(auto c : range(channels)) samples[c] = channels[c].resampler.read();
return channels.size();
}
auto Stream::write(const double* samples) -> void {
for(auto c : range(channels)) {
double sample = samples[c] + 1e-25; //constant offset used to suppress denormals
for(auto& iir : channels[c].iir) sample = iir.process(sample);
channels[c].resampler.write(sample);
}
audio.process();
}
-87497
View File
File diff suppressed because it is too large. Load diff
-9
View File
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="higan" version="1.0.0.0" processorArchitecture="*"/>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
</assembly>
-8
View File
@@ -1,8 +0,0 @@
[Desktop Entry]
Name=higan
Comment=Emulator
Exec=higan
Icon=higan
Terminal=false
Type=Application
Categories=Game;Emulator;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

-18
View File
@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>org.byuu.higan</string>
<key>CFBundleDisplayName</key>
<string>higan</string>
<key>CFBundleExecutable</key>
<string>higan</string>
<key>CFBundleIconFile</key>
<string>higan.icns</string>
<key>NSHighResolutionCapable</key>
<false/>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
</dict>
</plist>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

-2
View File
@@ -1,2 +0,0 @@
1 24 "higan.Manifest"
2 ICON DISCARDABLE "higan.ico"
-13
View File
@@ -1,13 +0,0 @@
#pragma once
namespace Emulator {
#if defined(DEBUGGER)
#define debug(id, ...) if(debugger.id) debugger.id(__VA_ARGS__)
#define privileged public
#else
#define debug(id, ...)
#define privileged private
#endif
}
-24
View File
@@ -1,24 +0,0 @@
#pragma once
#include <nall/nall.hpp>
#include <nall/vfs.hpp>
using namespace nall;
#include <libco/libco.h>
#include <audio/audio.hpp>
#include <video/video.hpp>
#include <resource/resource.hpp>
namespace Emulator {
static const string Name = "higan";
static const string Version = "100.01";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "http://byuu.org/";
//incremented only when serialization format changes
static const string SerializerVersion = "100";
}
#include "interface.hpp"
#include "debugger.hpp"
-121
View File
@@ -1,121 +0,0 @@
#pragma once
namespace Emulator {
struct Interface {
struct Information {
string manufacturer;
string name;
uint width;
uint height;
bool overscan;
double aspectRatio;
bool resettable;
struct Capability {
bool states;
bool cheats;
} capability;
} information;
struct Medium {
uint id;
string name;
string type; //extension
};
vector<Medium> media;
struct Device {
uint id;
string name;
struct Input {
uint type; //0 = digital, 1 = analog (relative), 2 = rumble
string name;
};
vector<Input> inputs;
};
struct Port {
uint id;
string name;
vector<Device> devices;
};
vector<Port> ports;
struct Bind {
virtual auto path(uint) -> string { return ""; }
virtual auto open(uint, string, vfs::file::mode, bool) -> vfs::shared::file { return {}; }
virtual auto load(uint, string, string) -> maybe<uint> { return nothing; }
virtual auto videoRefresh(const uint32*, uint, uint, uint) -> void {}
virtual auto audioSample(const double*, uint) -> void {}
virtual auto inputPoll(uint, uint, uint) -> int16 { return 0; }
virtual auto inputRumble(uint, uint, uint, bool) -> void {}
virtual auto dipSettings(Markup::Node) -> uint { return 0; }
virtual auto notify(string text) -> void { print(text, "\n"); }
};
Bind* bind = nullptr;
//callback bindings (provided by user interface)
auto path(uint id) -> string { return bind->path(id); }
auto open(uint id, string name, vfs::file::mode mode, bool required = false) -> vfs::shared::file { return bind->open(id, name, mode, required); }
auto load(uint id, string name, string type) -> maybe<uint> { return bind->load(id, name, type); }
auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void { return bind->videoRefresh(data, pitch, width, height); }
auto audioSample(const double* samples, uint channels) -> void { return bind->audioSample(samples, channels); }
auto inputPoll(uint port, uint device, uint input) -> int16 { return bind->inputPoll(port, device, input); }
auto inputRumble(uint port, uint device, uint input, bool enable) -> void { return bind->inputRumble(port, device, input, enable); }
auto dipSettings(Markup::Node node) -> uint { return bind->dipSettings(node); }
template<typename... P> auto notify(P&&... p) -> void { return bind->notify({forward<P>(p)...}); }
//information
virtual auto manifest() -> string = 0;
virtual auto title() -> string = 0;
//video information
virtual auto videoFrequency() -> double = 0;
virtual auto videoColors() -> uint32 = 0;
virtual auto videoColor(uint32 color) -> uint64 = 0;
//audio information
virtual auto audioFrequency() -> double = 0;
//media interface
virtual auto loaded() -> bool { return false; }
virtual auto sha256() -> string { return ""; }
virtual auto load(uint id) -> bool { return false; }
virtual auto save() -> void {}
virtual auto unload() -> void {}
//system interface
virtual auto connect(uint port, uint device) -> void {}
virtual auto power() -> void {}
virtual auto reset() -> void {}
virtual auto run() -> void {}
//time functions
virtual auto rtc() -> bool { return false; }
virtual auto rtcsync() -> void {}
//state functions
virtual auto serialize() -> serializer = 0;
virtual auto unserialize(serializer&) -> bool = 0;
//cheat functions
virtual auto cheatSet(const string_vector& = {}) -> void {}
//settings
virtual auto cap(const string& name) -> bool { return false; }
virtual auto get(const string& name) -> any { return {}; }
virtual auto set(const string& name, const any& value) -> bool { return false; }
//shared functions
auto videoColor(uint16 r, uint16 g, uint16 b) -> uint32;
};
//nall/vfs shorthand constants for open(), load()
struct File {
static const auto Read = vfs::file::mode::read;
static const auto Write = vfs::file::mode::write;
static const auto Optional = false;
static const auto Required = true;
};
}
-16
View File
@@ -1,16 +0,0 @@
processors += r6502
objects += fc-interface fc-system fc-scheduler fc-controller
objects += fc-memory fc-cartridge fc-cpu fc-apu fc-ppu
objects += fc-cheat
obj/fc-interface.o: fc/interface/interface.cpp $(call rwildcard,fc/interface/)
obj/fc-system.o: fc/system/system.cpp $(call rwildcard,fc/system/)
obj/fc-scheduler.o: fc/scheduler/scheduler.cpp $(call rwildcard,fc/scheduler/)
obj/fc-controller.o: fc/controller/controller.cpp $(call rwildcard,fc/controller/)
obj/fc-memory.o: fc/memory/memory.cpp $(call rwildcard,fc/memory/)
obj/fc-cartridge.o: fc/cartridge/cartridge.cpp $(call rwildcard,fc/cartridge/)
obj/fc-cpu.o: fc/cpu/cpu.cpp $(call rwildcard,fc/cpu/)
obj/fc-apu.o: fc/apu/apu.cpp $(call rwildcard,fc/apu/)
obj/fc-ppu.o: fc/ppu/ppu.cpp $(call rwildcard,fc/ppu/)
obj/fc-cheat.o: fc/cheat/cheat.cpp $(call rwildcard,fc/cheat/)
-346
View File
@@ -1,346 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
#include "envelope.cpp"
#include "sweep.cpp"
#include "pulse.cpp"
#include "triangle.cpp"
#include "noise.cpp"
#include "dmc.cpp"
#include "serialization.cpp"
APU apu;
APU::APU() {
for(uint amp : range(32)) {
if(amp == 0) {
pulseDAC[amp] = 0;
} else {
pulseDAC[amp] = 16384.0 * 95.88 / (8128.0 / amp + 100.0);
}
}
for(uint dmc_amp : range(128)) {
for(uint triangle_amp : range(16)) {
for(uint noise_amp : range(16)) {
if(dmc_amp == 0 && triangle_amp == 0 && noise_amp == 0) {
dmcTriangleNoiseDAC[dmc_amp][triangle_amp][noise_amp] = 0;
} else {
dmcTriangleNoiseDAC[dmc_amp][triangle_amp][noise_amp]
= 16384.0 * 159.79 / (100.0 + 1.0 / (triangle_amp / 8227.0 + noise_amp / 12241.0 + dmc_amp / 22638.0));
}
}
}
}
}
auto APU::Enter() -> void {
while(true) scheduler.synchronize(), apu.main();
}
auto APU::main() -> void {
uint pulse_output, triangle_output, noise_output, dmc_output;
pulse_output = pulse[0].clock();
pulse_output += pulse[1].clock();
triangle_output = triangle.clock();
noise_output = noise.clock();
dmc_output = dmc.clock();
clockFrameCounterDivider();
int output = pulseDAC[pulse_output] + dmcTriangleNoiseDAC[dmc_output][triangle_output][noise_output];
output = filter.runHipassStrong(output);
output += cartridgeSample;
output = filter.runHipassWeak(output);
//output = filter.runLopass(output);
output = sclamp<16>(output);
stream->sample(output / 32768.0);
tick();
}
auto APU::tick() -> void {
clock += 12;
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
}
auto APU::setIRQ() -> void {
cpu.apuLine(frame.irqPending || dmc.irqPending);
}
auto APU::setSample(int16 sample) -> void {
cartridgeSample = sample;
}
auto APU::power() -> void {
filter.hipassStrong = 0;
filter.hipassWeak = 0;
filter.lopass = 0;
pulse[0].power();
pulse[1].power();
triangle.power();
noise.power();
dmc.power();
}
auto APU::reset() -> void {
create(APU::Enter, 21'477'272);
stream = Emulator::audio.createStream(1, 21'477'272.0 / 12.0);
pulse[0].reset();
pulse[1].reset();
triangle.reset();
noise.reset();
dmc.reset();
frame.irqPending = 0;
frame.mode = 0;
frame.counter = 0;
frame.divider = 1;
enabledChannels = 0;
cartridgeSample = 0;
setIRQ();
}
auto APU::readIO(uint16 addr) -> uint8 {
switch(addr) {
case 0x4015: {
uint8 result = 0x00;
result |= pulse[0].lengthCounter ? 0x01 : 0;
result |= pulse[1].lengthCounter ? 0x02 : 0;
result |= triangle.lengthCounter ? 0x04 : 0;
result |= noise.lengthCounter ? 0x08 : 0;
result |= dmc.lengthCounter ? 0x10 : 0;
result |= frame.irqPending ? 0x40 : 0;
result |= dmc.irqPending ? 0x80 : 0;
frame.irqPending = false;
setIRQ();
return result;
}
}
return cpu.mdr();
}
auto APU::writeIO(uint16 addr, uint8 data) -> void {
const uint n = (addr >> 2) & 1; //pulse#
switch(addr) {
case 0x4000: case 0x4004: {
pulse[n].duty = data >> 6;
pulse[n].envelope.loopMode = data & 0x20;
pulse[n].envelope.useSpeedAsVolume = data & 0x10;
pulse[n].envelope.speed = data & 0x0f;
return;
}
case 0x4001: case 0x4005: {
pulse[n].sweep.enable = data & 0x80;
pulse[n].sweep.period = (data & 0x70) >> 4;
pulse[n].sweep.decrement = data & 0x08;
pulse[n].sweep.shift = data & 0x07;
pulse[n].sweep.reload = true;
return;
}
case 0x4002: case 0x4006: {
pulse[n].period = (pulse[n].period & 0x0700) | (data << 0);
pulse[n].sweep.pulsePeriod = (pulse[n].sweep.pulsePeriod & 0x0700) | (data << 0);
return;
}
case 0x4003: case 0x4007: {
pulse[n].period = (pulse[n].period & 0x00ff) | (data << 8);
pulse[n].sweep.pulsePeriod = (pulse[n].sweep.pulsePeriod & 0x00ff) | (data << 8);
pulse[n].dutyCounter = 7;
pulse[n].envelope.reloadDecay = true;
if(enabledChannels & (1 << n)) {
pulse[n].lengthCounter = lengthCounterTable[(data >> 3) & 0x1f];
}
return;
}
case 0x4008: {
triangle.haltLengthCounter = data & 0x80;
triangle.linearLength = data & 0x7f;
return;
}
case 0x400a: {
triangle.period = (triangle.period & 0x0700) | (data << 0);
return;
}
case 0x400b: {
triangle.period = (triangle.period & 0x00ff) | (data << 8);
triangle.reloadLinear = true;
if(enabledChannels & (1 << 2)) {
triangle.lengthCounter = lengthCounterTable[(data >> 3) & 0x1f];
}
return;
}
case 0x400c: {
noise.envelope.loopMode = data & 0x20;
noise.envelope.useSpeedAsVolume = data & 0x10;
noise.envelope.speed = data & 0x0f;
return;
}
case 0x400e: {
noise.shortMode = data & 0x80;
noise.period = data & 0x0f;
return;
}
case 0x400f: {
noise.envelope.reloadDecay = true;
if(enabledChannels & (1 << 3)) {
noise.lengthCounter = lengthCounterTable[(data >> 3) & 0x1f];
}
return;
}
case 0x4010: {
dmc.irqEnable = data & 0x80;
dmc.loopMode = data & 0x40;
dmc.period = data & 0x0f;
dmc.irqPending = dmc.irqPending && dmc.irqEnable && !dmc.loopMode;
setIRQ();
return;
}
case 0x4011: {
dmc.dacLatch = data & 0x7f;
return;
}
case 0x4012: {
dmc.addrLatch = data;
return;
}
case 0x4013: {
dmc.lengthLatch = data;
return;
}
case 0x4015: {
if((data & 0x01) == 0) pulse[0].lengthCounter = 0;
if((data & 0x02) == 0) pulse[1].lengthCounter = 0;
if((data & 0x04) == 0) triangle.lengthCounter = 0;
if((data & 0x08) == 0) noise.lengthCounter = 0;
(data & 0x10) ? dmc.start() : dmc.stop();
dmc.irqPending = false;
setIRQ();
enabledChannels = data & 0x1f;
return;
}
case 0x4017: {
frame.mode = data >> 6;
frame.counter = 0;
if(frame.mode & 2) clockFrameCounter();
if(frame.mode & 1) {
frame.irqPending = false;
setIRQ();
}
frame.divider = FrameCounter::NtscPeriod;
return;
}
}
}
auto APU::Filter::runHipassStrong(int sample) -> int {
hipassStrong += ((((int64)sample << 16) - (hipassStrong >> 16)) * HiPassStrong) >> 16;
return sample - (hipassStrong >> 32);
}
auto APU::Filter::runHipassWeak(int sample) -> int {
hipassWeak += ((((int64)sample << 16) - (hipassWeak >> 16)) * HiPassWeak) >> 16;
return sample - (hipassWeak >> 32);
}
auto APU::Filter::runLopass(int sample) -> int {
lopass += ((((int64)sample << 16) - (lopass >> 16)) * LoPass) >> 16;
return (lopass >> 32);
}
auto APU::clockFrameCounter() -> void {
frame.counter++;
if(frame.counter & 1) {
pulse[0].clockLength();
pulse[0].sweep.clock(0);
pulse[1].clockLength();
pulse[1].sweep.clock(1);
triangle.clockLength();
noise.clockLength();
}
pulse[0].envelope.clock();
pulse[1].envelope.clock();
triangle.clockLinearLength();
noise.envelope.clock();
if(frame.counter == 0) {
if(frame.mode & 2) frame.divider += FrameCounter::NtscPeriod;
if(frame.mode == 0) {
frame.irqPending = true;
setIRQ();
}
}
}
auto APU::clockFrameCounterDivider() -> void {
frame.divider -= 2;
if(frame.divider <= 0) {
clockFrameCounter();
frame.divider += FrameCounter::NtscPeriod;
}
}
const uint8 APU::lengthCounterTable[32] = {
0x0a, 0xfe, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, 0xa0, 0x08, 0x3c, 0x0a, 0x0e, 0x0c, 0x1a, 0x0e,
0x0c, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, 0xc0, 0x18, 0x48, 0x1a, 0x10, 0x1c, 0x20, 0x1e,
};
const uint16 APU::noisePeriodTableNTSC[16] = {
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068,
};
const uint16 APU::noisePeriodTablePAL[16] = {
4, 7, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778,
};
const uint16 APU::dmcPeriodTableNTSC[16] = {
428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54,
};
const uint16 APU::dmcPeriodTablePAL[16] = {
398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50,
};
}
-200
View File
@@ -1,200 +0,0 @@
struct APU : Thread {
shared_pointer<Emulator::Stream> stream;
APU();
static auto Enter() -> void;
auto main() -> void;
auto tick() -> void;
auto setIRQ() -> void;
auto setSample(int16 sample) -> void;
auto power() -> void;
auto reset() -> void;
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
auto serialize(serializer&) -> void;
struct Filter {
auto runHipassStrong(int sample) -> int;
auto runHipassWeak(int sample) -> int;
auto runLopass(int sample) -> int;
auto serialize(serializer&) -> void;
enum : int { HiPassStrong = 225574, HiPassWeak = 57593, LoPass = 86322413 };
int64 hipassStrong;
int64 hipassWeak;
int64 lopass;
};
struct Envelope {
auto volume() const -> uint;
auto clock() -> void;
auto power() -> void;
auto reset() -> void;
auto serialize(serializer&) -> void;
uint4 speed;
bool useSpeedAsVolume;
bool loopMode;
bool reloadDecay;
uint8 decayCounter;
uint4 decayVolume;
};
struct Sweep {
auto checkPeriod() -> bool;
auto clock(uint channel) -> void;
auto power() -> void;
auto reset() -> void;
auto serialize(serializer&) -> void;
uint8 shift;
bool decrement;
uint3 period;
uint8 counter;
bool enable;
bool reload;
uint11 pulsePeriod;
};
struct Pulse {
auto clockLength() -> void;
auto checkPeriod() -> bool;
auto clock() -> uint8;
auto power() -> void;
auto reset() -> void;
auto serialize(serializer&) -> void;
uint lengthCounter;
Envelope envelope;
Sweep sweep;
uint2 duty;
uint3 dutyCounter;
uint11 period;
uint periodCounter;
} pulse[2];
struct Triangle {
auto clockLength() -> void;
auto clockLinearLength() -> void;
auto clock() -> uint8;
auto power() -> void;
auto reset() -> void;
auto serialize(serializer&) -> void;
uint lengthCounter;
uint8 linearLength;
bool haltLengthCounter;
uint11 period;
uint periodCounter;
uint5 stepCounter;
uint8 linearLengthCounter;
bool reloadLinear;
} triangle;
struct Noise {
auto clockLength() -> void;
auto clock() -> uint8;
auto power() -> void;
auto reset() -> void;
auto serialize(serializer&) -> void;
uint lengthCounter;
Envelope envelope;
uint4 period;
uint periodCounter;
bool shortMode;
uint15 lfsr;
} noise;
struct DMC {
auto start() -> void;
auto stop() -> void;
auto clock() -> uint8;
auto power() -> void;
auto reset() -> void;
auto serialize(serializer&) -> void;
uint lengthCounter;
bool irqPending;
uint4 period;
uint periodCounter;
bool irqEnable;
bool loopMode;
uint8 dacLatch;
uint8 addrLatch;
uint8 lengthLatch;
uint15 readAddr;
uint dmaDelayCounter;
uint3 bitCounter;
bool dmaBufferValid;
uint8 dmaBuffer;
bool sampleValid;
uint8 sample;
} dmc;
struct FrameCounter {
auto serialize(serializer&) -> void;
enum : uint { NtscPeriod = 14915 }; //~(21.477MHz / 6 / 240hz)
bool irqPending;
uint2 mode;
uint2 counter;
int divider;
};
auto clockFrameCounter() -> void;
auto clockFrameCounterDivider() -> void;
Filter filter;
FrameCounter frame;
uint8 enabledChannels;
int16 cartridgeSample;
int16 pulseDAC[32];
int16 dmcTriangleNoiseDAC[128][16][16];
static const uint8 lengthCounterTable[32];
static const uint16 dmcPeriodTableNTSC[16];
static const uint16 dmcPeriodTablePAL[16];
static const uint16 noisePeriodTableNTSC[16];
static const uint16 noisePeriodTablePAL[16];
};
extern APU apu;
-92
View File
@@ -1,92 +0,0 @@
auto APU::DMC::start() -> void {
if(lengthCounter == 0) {
readAddr = 0x4000 + (addrLatch << 6);
lengthCounter = (lengthLatch << 4) + 1;
}
}
auto APU::DMC::stop() -> void {
lengthCounter = 0;
dmaDelayCounter = 0;
cpu.rdyLine(1);
cpu.rdyAddr(false);
}
auto APU::DMC::clock() -> uint8 {
uint8 result = dacLatch;
if(dmaDelayCounter > 0) {
dmaDelayCounter--;
if(dmaDelayCounter == 1) {
cpu.rdyAddr(true, 0x8000 | readAddr);
} else if(dmaDelayCounter == 0) {
cpu.rdyLine(1);
cpu.rdyAddr(false);
dmaBuffer = cpu.mdr();
dmaBufferValid = true;
lengthCounter--;
readAddr++;
if(lengthCounter == 0) {
if(loopMode) {
start();
} else if(irqEnable) {
irqPending = true;
apu.setIRQ();
}
}
}
}
if(--periodCounter == 0) {
if(sampleValid) {
int delta = (((sample >> bitCounter) & 1) << 2) - 2;
uint data = dacLatch + delta;
if((data & 0x80) == 0) dacLatch = data;
}
if(++bitCounter == 0) {
if(dmaBufferValid) {
sampleValid = true;
sample = dmaBuffer;
dmaBufferValid = false;
} else {
sampleValid = false;
}
}
periodCounter = dmcPeriodTableNTSC[period];
}
if(lengthCounter > 0 && !dmaBufferValid && dmaDelayCounter == 0) {
cpu.rdyLine(0);
dmaDelayCounter = 4;
}
return result;
}
auto APU::DMC::power() -> void {
}
auto APU::DMC::reset() -> void {
lengthCounter = 0;
irqPending = 0;
period = 0;
periodCounter = dmcPeriodTableNTSC[0];
irqEnable = 0;
loopMode = 0;
dacLatch = 0;
addrLatch = 0;
lengthLatch = 0;
readAddr = 0;
dmaDelayCounter = 0;
bitCounter = 0;
dmaBufferValid = 0;
dmaBuffer = 0;
sampleValid = 0;
sample = 0;
}
-29
View File
@@ -1,29 +0,0 @@
auto APU::Envelope::volume() const -> uint {
return useSpeedAsVolume ? speed : decayVolume;
}
auto APU::Envelope::clock() -> void {
if(reloadDecay) {
reloadDecay = false;
decayVolume = 0x0f;
decayCounter = speed + 1;
return;
}
if(--decayCounter == 0) {
decayCounter = speed + 1;
if(decayVolume || loopMode) decayVolume--;
}
}
auto APU::Envelope::power() -> void {
}
auto APU::Envelope::reset() -> void {
speed = 0;
useSpeedAsVolume = 0;
loopMode = 0;
reloadDecay = 0;
decayCounter = 0;
decayVolume = 0;
}
-45
View File
@@ -1,45 +0,0 @@
auto APU::Noise::clockLength() -> void {
if(envelope.loopMode == 0) {
if(lengthCounter > 0) lengthCounter--;
}
}
auto APU::Noise::clock() -> uint8 {
if(lengthCounter == 0) return 0;
uint8 result = (lfsr & 1) ? envelope.volume() : 0;
if(--periodCounter == 0) {
uint feedback;
if(shortMode) {
feedback = ((lfsr >> 0) & 1) ^ ((lfsr >> 6) & 1);
} else {
feedback = ((lfsr >> 0) & 1) ^ ((lfsr >> 1) & 1);
}
lfsr = (lfsr >> 1) | (feedback << 14);
periodCounter = apu.noisePeriodTableNTSC[period];
}
return result;
}
auto APU::Noise::power() -> void {
}
auto APU::Noise::reset() -> void {
lengthCounter = 0;
envelope.speed = 0;
envelope.useSpeedAsVolume = 0;
envelope.loopMode = 0;
envelope.reloadDecay = 0;
envelope.decayCounter = 0;
envelope.decayVolume = 0;
period = 0;
periodCounter = 1;
shortMode = 0;
lfsr = 1;
}
-38
View File
@@ -1,38 +0,0 @@
auto APU::Pulse::clockLength() -> void {
if(envelope.loopMode == 0) {
if(lengthCounter) lengthCounter--;
}
}
auto APU::Pulse::clock() -> uint8 {
if(!sweep.checkPeriod()) return 0;
if(lengthCounter == 0) return 0;
static const uint dutyTable[] = {1, 2, 4, 6};
uint8 result = (dutyCounter < dutyTable[duty]) ? envelope.volume() : 0;
if(sweep.pulsePeriod < 0x008) result = 0;
if(--periodCounter == 0) {
periodCounter = (sweep.pulsePeriod + 1) * 2;
dutyCounter++;
}
return result;
}
auto APU::Pulse::power() -> void {
envelope.power();
sweep.power();
}
auto APU::Pulse::reset() -> void {
envelope.reset();
sweep.reset();
lengthCounter = 0;
duty = 0;
dutyCounter = 0;
period = 0;
periodCounter = 1;
}
-112
View File
@@ -1,112 +0,0 @@
auto APU::serialize(serializer& s) -> void {
Thread::serialize(s);
filter.serialize(s);
pulse[0].serialize(s);
pulse[1].serialize(s);
triangle.serialize(s);
dmc.serialize(s);
frame.serialize(s);
s.integer(enabledChannels);
s.integer(cartridgeSample);
}
auto APU::Filter::serialize(serializer& s) -> void {
s.integer(hipassStrong);
s.integer(hipassWeak);
s.integer(lopass);
}
auto APU::Envelope::serialize(serializer& s) -> void {
s.integer(speed);
s.integer(useSpeedAsVolume);
s.integer(loopMode);
s.integer(reloadDecay);
s.integer(decayCounter);
s.integer(decayVolume);
}
auto APU::Sweep::serialize(serializer& s) -> void {
s.integer(shift);
s.integer(decrement);
s.integer(period);
s.integer(counter);
s.integer(enable);
s.integer(reload);
s.integer(pulsePeriod);
}
auto APU::Pulse::serialize(serializer& s) -> void {
s.integer(lengthCounter);
envelope.serialize(s);
sweep.serialize(s);
s.integer(duty);
s.integer(dutyCounter);
s.integer(period);
s.integer(periodCounter);
}
auto APU::Triangle::serialize(serializer& s) -> void {
s.integer(lengthCounter);
s.integer(linearLength);
s.integer(haltLengthCounter);
s.integer(period);
s.integer(periodCounter);
s.integer(stepCounter);
s.integer(linearLengthCounter);
s.integer(reloadLinear);
}
auto APU::Noise::serialize(serializer& s) -> void {
s.integer(lengthCounter);
envelope.serialize(s);
s.integer(period);
s.integer(periodCounter);
s.integer(shortMode);
s.integer(lfsr);
}
auto APU::DMC::serialize(serializer& s) -> void {
s.integer(lengthCounter);
s.integer(irqPending);
s.integer(period);
s.integer(periodCounter);
s.integer(irqEnable);
s.integer(loopMode);
s.integer(dacLatch);
s.integer(addrLatch);
s.integer(lengthLatch);
s.integer(readAddr);
s.integer(dmaDelayCounter);
s.integer(bitCounter);
s.integer(dmaBufferValid);
s.integer(dmaBuffer);
s.integer(sampleValid);
s.integer(sample);
}
auto APU::FrameCounter::serialize(serializer& s) -> void {
s.integer(irqPending);
s.integer(mode);
s.integer(counter);
s.integer(divider);
}
-43
View File
@@ -1,43 +0,0 @@
auto APU::Sweep::checkPeriod() -> bool {
if(pulsePeriod > 0x7ff) return false;
if(decrement == 0) {
if((pulsePeriod + (pulsePeriod >> shift)) & 0x800) return false;
}
return true;
}
auto APU::Sweep::clock(uint channel) -> void {
if(--counter == 0) {
counter = period + 1;
if(enable && shift && pulsePeriod > 8) {
int delta = pulsePeriod >> shift;
if(decrement) {
pulsePeriod -= delta;
if(channel == 0) pulsePeriod--;
} else if((pulsePeriod + delta) < 0x800) {
pulsePeriod += delta;
}
}
}
if(reload) {
reload = false;
counter = period + 1;
}
}
auto APU::Sweep::power() -> void {
shift = 0;
decrement = 0;
period = 0;
counter = 1;
enable = 0;
reload = 0;
pulsePeriod = 0;
}
auto APU::Sweep::reset() -> void {
}
-44
View File
@@ -1,44 +0,0 @@
auto APU::Triangle::clockLength() -> void {
if(haltLengthCounter == 0) {
if(lengthCounter > 0) lengthCounter--;
}
}
auto APU::Triangle::clockLinearLength() -> void {
if(reloadLinear) {
linearLengthCounter = linearLength;
} else if(linearLengthCounter) {
linearLengthCounter--;
}
if(haltLengthCounter == 0) reloadLinear = false;
}
auto APU::Triangle::clock() -> uint8 {
uint8 result = stepCounter & 0x0f;
if((stepCounter & 0x10) == 0) result ^= 0x0f;
if(lengthCounter == 0 || linearLengthCounter == 0) return result;
if(--periodCounter == 0) {
stepCounter++;
periodCounter = period + 1;
}
return result;
}
auto APU::Triangle::power() -> void {
reset();
}
auto APU::Triangle::reset() -> void {
lengthCounter = 0;
linearLength = 0;
haltLengthCounter = 0;
period = 0;
periodCounter = 1;
stepCounter = 0;
linearLengthCounter = 0;
reloadLinear = 0;
}
-109
View File
@@ -1,109 +0,0 @@
//BANDAI-FCG
struct BandaiFCG : Board {
BandaiFCG(Markup::Node& document) : Board(document) {
}
auto main() -> void {
if(irqCounterEnable) {
if(--irqCounter == 0xffff) {
cpu.irqLine(1);
irqCounterEnable = false;
}
}
tick();
}
auto addrCIRAM(uint addr) const -> uint {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
case 2: return 0x0000 | (addr & 0x03ff);
case 3: return 0x0400 | (addr & 0x03ff);
}
}
auto readPRG(uint addr) -> uint8 {
if(addr & 0x8000) {
bool region = addr & 0x4000;
uint bank = (region == 0 ? prgBank : (uint8)0x0f);
return prgrom.read((bank << 14) | (addr & 0x3fff));
}
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr >= 0x6000) {
switch(addr & 15) {
case 0x00: case 0x01: case 0x02: case 0x03:
case 0x04: case 0x05: case 0x06: case 0x07:
chrBank[addr & 7] = data;
break;
case 0x08:
prgBank = data & 0x0f;
break;
case 0x09:
mirror = data & 0x03;
break;
case 0x0a:
cpu.irqLine(0);
irqCounterEnable = data & 0x01;
irqCounter = irqLatch;
break;
case 0x0b:
irqLatch = (irqLatch & 0xff00) | (data << 0);
break;
case 0x0c:
irqLatch = (irqLatch & 0x00ff) | (data << 8);
break;
case 0x0d:
//todo: serial EEPROM support
break;
}
}
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM(addrCIRAM(addr));
addr = (chrBank[addr >> 10] << 10) | (addr & 0x03ff);
return Board::readCHR(addr);
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM(addrCIRAM(addr), data);
addr = (chrBank[addr >> 10] << 10) | (addr & 0x03ff);
return Board::writeCHR(addr, data);
}
auto power() -> void {
reset();
}
auto reset() -> void {
for(auto& n : chrBank) n = 0;
prgBank = 0;
mirror = 0;
irqCounterEnable = 0;
irqCounter = 0;
irqLatch = 0;
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.array(chrBank);
s.integer(prgBank);
s.integer(mirror);
s.integer(irqCounterEnable);
s.integer(irqCounter);
s.integer(irqLatch);
}
uint8 chrBank[8];
uint8 prgBank;
uint2 mirror;
bool irqCounterEnable;
uint16 irqCounter;
uint16 irqLatch;
};
-232
View File
@@ -1,232 +0,0 @@
#include "bandai-fcg.cpp"
#include "konami-vrc1.cpp"
#include "konami-vrc2.cpp"
#include "konami-vrc3.cpp"
#include "konami-vrc4.cpp"
#include "konami-vrc6.cpp"
#include "konami-vrc7.cpp"
#include "nes-axrom.cpp"
#include "nes-bnrom.cpp"
#include "nes-cnrom.cpp"
#include "nes-exrom.cpp"
#include "nes-fxrom.cpp"
#include "nes-gxrom.cpp"
#include "nes-hkrom.cpp"
#include "nes-nrom.cpp"
#include "nes-pxrom.cpp"
#include "nes-sxrom.cpp"
#include "nes-txrom.cpp"
#include "nes-uxrom.cpp"
#include "sunsoft-5b.cpp"
Board::Board(Markup::Node& document) {
cartridge.board = this;
auto board = document["board"];
information.type = board["id"].text();
information.battery = (bool)board["prg/ram/name"];
auto prom = board["prg/rom"];
auto pram = board["prg/ram"];
auto crom = board["chr/rom"];
auto cram = board["chr/ram"];
prgrom.size = prom["size"].natural();
prgram.size = pram["size"].natural();
chrrom.size = crom["size"].natural();
chrram.size = cram["size"].natural();
if(prgrom.size) prgrom.data = new uint8_t[prgrom.size]();
if(prgram.size) prgram.data = new uint8_t[prgram.size]();
if(chrrom.size) chrrom.data = new uint8_t[chrrom.size]();
if(chrram.size) chrram.data = new uint8_t[chrram.size]();
if(prgrom.name = prom["name"].text()) {
if(auto fp = interface->open(cartridge.pathID(), prgrom.name, File::Read, File::Required)) {
fp->read(prgrom.data, min(prgrom.size, fp->size()));
}
}
if(prgram.name = pram["name"].text()) {
if(auto fp = interface->open(cartridge.pathID(), prgram.name, File::Read)) {
fp->read(prgram.data, min(prgram.size, fp->size()));
}
}
if(chrrom.name = crom["name"].text()) {
if(auto fp = interface->open(cartridge.pathID(), chrrom.name, File::Read, File::Required)) {
fp->read(chrrom.data, min(chrrom.size, fp->size()));
}
}
if(chrram.name = cram["name"].text()) {
if(auto fp = interface->open(cartridge.pathID(), chrram.name, File::Read)) {
fp->read(chrram.data, min(chrram.size, fp->size()));
}
}
prgram.writable = true;
chrram.writable = true;
}
auto Board::save() -> void {
auto document = BML::unserialize(cartridge.manifest());
if(auto name = document["board/prg/ram/name"].text()) {
if(auto fp = interface->open(cartridge.pathID(), name, File::Write)) {
fp->write(prgram.data, prgram.size);
}
}
if(auto name = document["board/chr/ram/name"].text()) {
if(auto fp = interface->open(cartridge.pathID(), name, File::Write)) {
fp->write(chrram.data, chrram.size);
}
}
}
auto Board::Memory::read(uint addr) const -> uint8 {
return data[mirror(addr, size)];
}
auto Board::Memory::write(uint addr, uint8 byte) -> void {
if(writable) data[mirror(addr, size)] = byte;
}
auto Board::mirror(uint addr, uint size) -> uint {
uint base = 0;
if(size) {
uint mask = 1 << 23;
while(addr >= size) {
while(!(addr & mask)) mask >>= 1;
addr -= mask;
if(size > mask) {
size -= mask;
base += mask;
}
mask >>= 1;
}
base += addr;
}
return base;
}
auto Board::main() -> void {
cartridge.clock += 12 * 4095;
tick();
}
auto Board::tick() -> void {
cartridge.clock += 12;
if(cartridge.clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
}
auto Board::readCHR(uint addr) -> uint8 {
if(chrram.size) return chrram.data[mirror(addr, chrram.size)];
if(chrrom.size) return chrrom.data[mirror(addr, chrrom.size)];
return 0u;
}
auto Board::writeCHR(uint addr, uint8 data) -> void {
if(chrram.size) chrram.data[mirror(addr, chrram.size)] = data;
}
auto Board::power() -> void {
}
auto Board::reset() -> void {
}
auto Board::serialize(serializer& s) -> void {
if(prgram.size) s.array(prgram.data, prgram.size);
if(chrram.size) s.array(chrram.data, chrram.size);
}
auto Board::load(string manifest) -> Board* {
auto document = BML::unserialize(manifest);
cartridge.information.title = document["information/title"].text();
string type = document["board/id"].text();
if(type == "BANDAI-FCG" ) return new BandaiFCG(document);
if(type == "KONAMI-VRC-1") return new KonamiVRC1(document);
if(type == "KONAMI-VRC-2") return new KonamiVRC2(document);
if(type == "KONAMI-VRC-3") return new KonamiVRC3(document);
if(type == "KONAMI-VRC-4") return new KonamiVRC4(document);
if(type == "KONAMI-VRC-6") return new KonamiVRC6(document);
if(type == "KONAMI-VRC-7") return new KonamiVRC7(document);
if(type == "NES-AMROM" ) return new NES_AxROM(document);
if(type == "NES-ANROM" ) return new NES_AxROM(document);
if(type == "NES-AN1ROM" ) return new NES_AxROM(document);
if(type == "NES-AOROM" ) return new NES_AxROM(document);
if(type == "NES-BNROM" ) return new NES_BNROM(document);
if(type == "NES-CNROM" ) return new NES_CNROM(document);
if(type == "NES-EKROM" ) return new NES_ExROM(document);
if(type == "NES-ELROM" ) return new NES_ExROM(document);
if(type == "NES-ETROM" ) return new NES_ExROM(document);
if(type == "NES-EWROM" ) return new NES_ExROM(document);
if(type == "NES-FJROM" ) return new NES_FxROM(document);
if(type == "NES-FKROM" ) return new NES_FxROM(document);
if(type == "NES-GNROM" ) return new NES_GxROM(document);
if(type == "NES-MHROM" ) return new NES_GxROM(document);
if(type == "NES-HKROM" ) return new NES_HKROM(document);
if(type == "NES-NROM" ) return new NES_NROM(document);
if(type == "NES-NROM-128") return new NES_NROM(document);
if(type == "NES-NROM-256") return new NES_NROM(document);
if(type == "NES-PEEOROM" ) return new NES_PxROM(document);
if(type == "NES-PNROM" ) return new NES_PxROM(document);
if(type == "NES-SAROM" ) return new NES_SxROM(document);
if(type == "NES-SBROM" ) return new NES_SxROM(document);
if(type == "NES-SCROM" ) return new NES_SxROM(document);
if(type == "NES-SC1ROM" ) return new NES_SxROM(document);
if(type == "NES-SEROM" ) return new NES_SxROM(document);
if(type == "NES-SFROM" ) return new NES_SxROM(document);
if(type == "NES-SFEXPROM") return new NES_SxROM(document);
if(type == "NES-SGROM" ) return new NES_SxROM(document);
if(type == "NES-SHROM" ) return new NES_SxROM(document);
if(type == "NES-SH1ROM" ) return new NES_SxROM(document);
if(type == "NES-SIROM" ) return new NES_SxROM(document);
if(type == "NES-SJROM" ) return new NES_SxROM(document);
if(type == "NES-SKROM" ) return new NES_SxROM(document);
if(type == "NES-SLROM" ) return new NES_SxROM(document);
if(type == "NES-SL1ROM" ) return new NES_SxROM(document);
if(type == "NES-SL2ROM" ) return new NES_SxROM(document);
if(type == "NES-SL3ROM" ) return new NES_SxROM(document);
if(type == "NES-SLRROM" ) return new NES_SxROM(document);
if(type == "NES-SMROM" ) return new NES_SxROM(document);
if(type == "NES-SNROM" ) return new NES_SxROM(document);
if(type == "NES-SOROM" ) return new NES_SxROM(document);
if(type == "NES-SUROM" ) return new NES_SxROM(document);
if(type == "NES-SXROM" ) return new NES_SxROM(document);
if(type == "NES-TBROM" ) return new NES_TxROM(document);
if(type == "NES-TEROM" ) return new NES_TxROM(document);
if(type == "NES-TFROM" ) return new NES_TxROM(document);
if(type == "NES-TGROM" ) return new NES_TxROM(document);
if(type == "NES-TKROM" ) return new NES_TxROM(document);
if(type == "NES-TKSROM" ) return new NES_TxROM(document);
if(type == "NES-TLROM" ) return new NES_TxROM(document);
if(type == "NES-TL1ROM" ) return new NES_TxROM(document);
if(type == "NES-TL2ROM" ) return new NES_TxROM(document);
if(type == "NES-TLSROM" ) return new NES_TxROM(document);
if(type == "NES-TNROM" ) return new NES_TxROM(document);
if(type == "NES-TQROM" ) return new NES_TxROM(document);
if(type == "NES-TR1ROM" ) return new NES_TxROM(document);
if(type == "NES-TSROM" ) return new NES_TxROM(document);
if(type == "NES-TVROM" ) return new NES_TxROM(document);
if(type == "NES-UNROM" ) return new NES_UxROM(document);
if(type == "NES-UOROM" ) return new NES_UxROM(document);
if(type == "SUNSOFT-5B" ) return new Sunsoft5B(document);
return nullptr;
}
-50
View File
@@ -1,50 +0,0 @@
struct Board {
struct Memory {
inline Memory(uint8_t* data, uint size) : data(data), size(size) {}
inline Memory() : data(nullptr), size(0u), writable(false) {}
inline ~Memory() { if(data) delete[] data; }
inline auto read(uint addr) const -> uint8;
inline auto write(uint addr, uint8 data) -> void;
string name;
uint8_t* data = nullptr;
uint size = 0;
bool writable = false;
};
virtual ~Board() = default;
static auto mirror(uint addr, uint size) -> uint;
Board(Markup::Node& document);
auto save() -> void;
virtual auto main() -> void;
virtual auto tick() -> void;
virtual auto readPRG(uint addr) -> uint8 = 0;
virtual auto writePRG(uint addr, uint8 data) -> void = 0;
virtual auto readCHR(uint addr) -> uint8;
virtual auto writeCHR(uint addr, uint8 data) -> void;
virtual inline auto scanline(uint y) -> void {}
virtual auto power() -> void;
virtual auto reset() -> void;
virtual auto serialize(serializer&) -> void;
static auto load(string manifest) -> Board*;
struct Information {
string type;
bool battery;
} information;
Memory prgrom;
Memory prgram;
Memory chrrom;
Memory chrram;
};
-38
View File
@@ -1,38 +0,0 @@
struct KonamiVRC1 : Board {
KonamiVRC1(Markup::Node& document) : Board(document), vrc1(*this) {
}
auto readPRG(uint addr) -> uint8 {
if(addr & 0x8000) return prgrom.read(vrc1.addrPRG(addr));
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr & 0x8000) return vrc1.writeIO(addr, data);
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM(vrc1.addrCIRAM(addr));
return Board::readCHR(vrc1.addrCHR(addr));
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM(vrc1.addrCIRAM(addr), data);
return Board::writeCHR(vrc1.addrCHR(addr), data);
}
auto power() -> void {
vrc1.power();
}
auto reset() -> void {
vrc1.reset();
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
vrc1.serialize(s);
}
VRC1 vrc1;
};
-55
View File
@@ -1,55 +0,0 @@
struct KonamiVRC2 : Board {
KonamiVRC2(Markup::Node& document) : Board(document), vrc2(*this) {
settings.pinout.a0 = 1 << document["board/chip/pinout/a0"].natural();
settings.pinout.a1 = 1 << document["board/chip/pinout/a1"].natural();
}
auto readPRG(uint addr) -> uint8 {
if(addr < 0x6000) return cpu.mdr();
if(addr < 0x8000) return vrc2.readRAM(addr);
return prgrom.read(vrc2.addrPRG(addr));
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr < 0x6000) return;
if(addr < 0x8000) return vrc2.writeRAM(addr, data);
bool a0 = (addr & settings.pinout.a0);
bool a1 = (addr & settings.pinout.a1);
addr &= 0xfff0;
addr |= (a0 << 0) | (a1 << 1);
return vrc2.writeIO(addr, data);
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM(vrc2.addrCIRAM(addr));
return Board::readCHR(vrc2.addrCHR(addr));
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM(vrc2.addrCIRAM(addr), data);
return Board::writeCHR(vrc2.addrCHR(addr), data);
}
auto power() -> void {
vrc2.power();
}
auto reset() -> void {
vrc2.reset();
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
vrc2.serialize(s);
}
struct Settings {
struct Pinout {
uint a0;
uint a1;
} pinout;
} settings;
VRC2 vrc2;
};
-55
View File
@@ -1,55 +0,0 @@
struct KonamiVRC3 : Board {
KonamiVRC3(Markup::Node& document) : Board(document), vrc3(*this) {
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
}
auto main() -> void {
vrc3.main();
}
auto readPRG(uint addr) -> uint8 {
if((addr & 0xe000) == 0x6000) return prgram.read(addr & 0x1fff);
if(addr & 0x8000) return prgrom.read(vrc3.addrPRG(addr));
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if((addr & 0xe000) == 0x6000) return prgram.write(addr & 0x1fff, data);
if(addr & 0x8000) return vrc3.writeIO(addr, data);
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.readCIRAM(addr & 0x07ff);
}
return chrram.read(addr);
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.writeCIRAM(addr & 0x07ff, data);
}
return chrram.write(addr, data);
}
auto power() -> void {
vrc3.power();
}
auto reset() -> void {
vrc3.reset();
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
vrc3.serialize(s);
}
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
VRC3 vrc3;
};
-59
View File
@@ -1,59 +0,0 @@
struct KonamiVRC4 : Board {
KonamiVRC4(Markup::Node& document) : Board(document), vrc4(*this) {
settings.pinout.a0 = 1 << document["board/chip/pinout/a0"].natural();
settings.pinout.a1 = 1 << document["board/chip/pinout/a1"].natural();
}
auto main() -> void {
return vrc4.main();
}
auto readPRG(uint addr) -> uint8 {
if(addr < 0x6000) return cpu.mdr();
if(addr < 0x8000) return prgram.read(addr);
return prgrom.read(vrc4.addrPRG(addr));
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr < 0x6000) return;
if(addr < 0x8000) return prgram.write(addr, data);
bool a0 = (addr & settings.pinout.a0);
bool a1 = (addr & settings.pinout.a1);
addr &= 0xfff0;
addr |= (a1 << 1) | (a0 << 0);
return vrc4.writeIO(addr, data);
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM(vrc4.addrCIRAM(addr));
return Board::readCHR(vrc4.addrCHR(addr));
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM(vrc4.addrCIRAM(addr), data);
return Board::writeCHR(vrc4.addrCHR(addr), data);
}
auto power() -> void {
vrc4.power();
}
auto reset() -> void {
vrc4.reset();
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
vrc4.serialize(s);
}
struct Settings {
struct Pinout {
uint a0;
uint a1;
} pinout;
} settings;
VRC4 vrc4;
};
-40
View File
@@ -1,40 +0,0 @@
struct KonamiVRC6 : Board {
KonamiVRC6(Markup::Node& document) : Board(document), vrc6(*this) {
}
auto readPRG(uint addr) -> uint8{
if((addr & 0xe000) == 0x6000) return vrc6.readRAM(addr);
if(addr & 0x8000) return prgrom.read(vrc6.addrPRG(addr));
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if((addr & 0xe000) == 0x6000) return vrc6.writeRAM(addr, data);
if(addr & 0x8000) {
addr = (addr & 0xf003);
if(prgram.size) addr = (addr & ~3) | ((addr & 2) >> 1) | ((addr & 1) << 1);
return vrc6.writeIO(addr, data);
}
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM(vrc6.addrCIRAM(addr));
return Board::readCHR(vrc6.addrCHR(addr));
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM(vrc6.addrCIRAM(addr), data);
return Board::writeCHR(vrc6.addrCHR(addr), data);
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
vrc6.serialize(s);
}
auto main() -> void { vrc6.main(); }
auto power() -> void { vrc6.power(); }
auto reset() -> void { vrc6.reset(); }
VRC6 vrc6;
};
-45
View File
@@ -1,45 +0,0 @@
struct KonamiVRC7 : Board {
KonamiVRC7(Markup::Node& document) : Board(document), vrc7(*this) {
}
auto main() -> void {
return vrc7.main();
}
auto readPRG(uint addr) -> uint8 {
if(addr < 0x6000) return cpu.mdr();
if(addr < 0x8000) return prgram.read(addr);
return prgrom.read(vrc7.addrPRG(addr));
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr < 0x6000) return;
if(addr < 0x8000) return prgram.write(addr, data);
return vrc7.writeIO(addr, data);
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM(vrc7.addrCIRAM(addr));
return chrram.read(vrc7.addrCHR(addr));
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM(vrc7.addrCIRAM(addr), data);
return chrram.write(vrc7.addrCHR(addr), data);
}
auto power() -> void {
vrc7.power();
}
auto reset() -> void {
vrc7.reset();
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
vrc7.serialize(s);
}
VRC7 vrc7;
};
-49
View File
@@ -1,49 +0,0 @@
//NES-AMROM
//NES-ANROM
//NES-AN1ROM
//NES-AOROM
struct NES_AxROM : Board {
NES_AxROM(Markup::Node& document) : Board(document) {
}
auto readPRG(uint addr) -> uint8 {
if(addr & 0x8000) return prgrom.read((prgBank << 15) | (addr & 0x7fff));
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr & 0x8000) {
prgBank = data & 0x0f;
mirrorSelect = data & 0x10;
}
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM((mirrorSelect << 10) | (addr & 0x03ff));
return Board::readCHR(addr);
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM((mirrorSelect << 10) | (addr & 0x03ff), data);
return Board::writeCHR(addr, data);
}
auto power() -> void {
}
auto reset() -> void {
prgBank = 0x0f;
mirrorSelect = 0;
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.integer(prgBank);
s.integer(mirrorSelect);
}
uint4 prgBank;
bool mirrorSelect;
};
-50
View File
@@ -1,50 +0,0 @@
//NES-BN-ROM-01
struct NES_BNROM : Board {
NES_BNROM(Markup::Node& document) : Board(document) {
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
}
auto readPRG(uint addr) -> uint8 {
if(addr & 0x8000) return prgrom.read((prgBank << 15) | (addr & 0x7fff));
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr & 0x8000) prgBank = data & 0x03;
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.readCIRAM(addr);
}
return Board::readCHR(addr);
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.writeCIRAM(addr, data);
}
return Board::writeCHR(addr, data);
}
auto power() -> void {
}
auto reset() -> void {
prgBank = 0;
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.integer(prgBank);
}
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
uint2 prgBank;
};
-52
View File
@@ -1,52 +0,0 @@
//NES-CNROM
struct NES_CNROM : Board {
NES_CNROM(Markup::Node& document) : Board(document) {
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
}
auto readPRG(uint addr) -> uint8 {
if(addr & 0x8000) return prgrom.read(addr & 0x7fff);
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr & 0x8000) chrBank = data & 0x03;
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.readCIRAM(addr & 0x07ff);
}
addr = (chrBank * 0x2000) + (addr & 0x1fff);
return Board::readCHR(addr);
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.writeCIRAM(addr & 0x07ff, data);
}
addr = (chrBank * 0x2000) + (addr & 0x1fff);
Board::writeCHR(addr, data);
}
auto power() -> void {
}
auto reset() -> void {
chrBank = 0;
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.integer(chrBank);
}
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
uint2 chrBank;
};
-51
View File
@@ -1,51 +0,0 @@
struct NES_ExROM : Board {
NES_ExROM(Markup::Node& document) : Board(document), mmc5(*this) {
revision = Revision::ELROM;
}
auto main() -> void {
mmc5.main();
}
auto readPRG(uint addr) -> uint8 {
return mmc5.readPRG(addr);
}
auto writePRG(uint addr, uint8 data) -> void {
mmc5.writePRG(addr, data);
}
auto readCHR(uint addr) -> uint8 {
return mmc5.readCHR(addr);
}
auto writeCHR(uint addr, uint8 data) -> void {
mmc5.writeCHR(addr, data);
}
auto scanline(uint y) -> void {
mmc5.scanline(y);
}
auto power() -> void {
mmc5.power();
}
auto reset() -> void {
mmc5.reset();
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
mmc5.serialize(s);
}
enum class Revision : uint {
EKROM,
ELROM,
ETROM,
EWROM,
} revision;
MMC5 mmc5;
};
-89
View File
@@ -1,89 +0,0 @@
//MMC4
struct NES_FxROM : Board {
NES_FxROM(Markup::Node& document) : Board(document) {
revision = Revision::FKROM;
}
auto readPRG(uint addr) -> uint8 {
if(addr < 0x6000) return cpu.mdr();
if(addr < 0x8000) return prgram.read(addr);
uint bank = addr < 0xc000 ? prgBank : (uint4)0x0f;
return prgrom.read((bank * 0x4000) | (addr & 0x3fff));
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr < 0x6000) return;
if(addr < 0x8000) return prgram.write(addr, data);
switch(addr & 0xf000) {
case 0xa000: prgBank = data & 0x0f; break;
case 0xb000: chrBank[0][0] = data & 0x1f; break;
case 0xc000: chrBank[0][1] = data & 0x1f; break;
case 0xd000: chrBank[1][0] = data & 0x1f; break;
case 0xe000: chrBank[1][1] = data & 0x1f; break;
case 0xf000: mirror = data & 0x01; break;
}
}
auto addrCIRAM(uint addr) const -> uint {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
}
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM(addrCIRAM(addr));
bool region = addr & 0x1000;
uint bank = chrBank[region][latch[region]];
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
return Board::readCHR((bank * 0x1000) | (addr & 0x0fff));
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM(addrCIRAM(addr), data);
bool region = addr & 0x1000;
uint bank = chrBank[region][latch[region]];
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
return Board::writeCHR((bank * 0x1000) | (addr & 0x0fff), data);
}
auto power() -> void {
}
auto reset() -> void {
prgBank = 0;
chrBank[0][0] = 0;
chrBank[0][1] = 0;
chrBank[1][0] = 0;
chrBank[1][1] = 0;
mirror = 0;
latch[0] = 0;
latch[1] = 0;
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.integer(prgBank);
s.integer(chrBank[0][0]);
s.integer(chrBank[0][1]);
s.integer(chrBank[1][0]);
s.integer(chrBank[1][1]);
s.integer(mirror);
s.array(latch);
}
enum Revision : uint {
FJROM,
FKROM,
} revision;
uint4 prgBank;
uint5 chrBank[2][2];
bool mirror;
bool latch[2];
};
-59
View File
@@ -1,59 +0,0 @@
//NES-GNROM
//NES-MHROM
struct NES_GxROM : Board {
NES_GxROM(Markup::Node& document) : Board(document) {
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
}
auto readPRG(uint addr) -> uint8 {
if(addr & 0x8000) return prgrom.read((prgBank << 15) | (addr & 0x7fff));
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr & 0x8000) {
prgBank = (data & 0x30) >> 4;
chrBank = (data & 0x03) >> 0;
}
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.readCIRAM(addr & 0x07ff);
}
addr = (chrBank * 0x2000) + (addr & 0x1fff);
return Board::readCHR(addr);
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.writeCIRAM(addr & 0x07ff, data);
}
addr = (chrBank * 0x2000) + (addr & 0x1fff);
Board::writeCHR(addr, data);
}
auto power() -> void {
}
auto reset() -> void {
prgBank = 0;
chrBank = 0;
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.integer(prgBank);
s.integer(chrBank);
}
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
uint2 prgBank;
uint2 chrBank;
};
-46
View File
@@ -1,46 +0,0 @@
struct NES_HKROM : Board {
NES_HKROM(Markup::Node& document) : Board(document), mmc6(*this) {
}
auto main() -> void {
mmc6.main();
}
auto readPRG(uint addr) -> uint8 {
if((addr & 0xf000) == 0x7000) return mmc6.readRAM(addr);
if(addr & 0x8000) return prgrom.read(mmc6.addrPRG(addr));
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if((addr & 0xf000) == 0x7000) return mmc6.writeRAM(addr, data);
if(addr & 0x8000) return mmc6.writeIO(addr, data);
}
auto readCHR(uint addr) -> uint8 {
mmc6.irqTest(addr);
if(addr & 0x2000) return ppu.readCIRAM(mmc6.addrCIRAM(addr));
return Board::readCHR(mmc6.addrCHR(addr));
}
auto writeCHR(uint addr, uint8 data) -> void {
mmc6.irqTest(addr);
if(addr & 0x2000) return ppu.writeCIRAM(mmc6.addrCIRAM(addr), data);
return Board::writeCHR(mmc6.addrCHR(addr), data);
}
auto power() -> void {
mmc6.power();
}
auto reset() -> void {
mmc6.reset();
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
mmc6.serialize(s);
}
MMC6 mmc6;
};
-41
View File
@@ -1,41 +0,0 @@
//NES-NROM-128
//NES-NROM-256
struct NES_NROM : Board {
NES_NROM(Markup::Node& document) : Board(document) {
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
}
auto readPRG(uint addr) -> uint8 {
if(addr & 0x8000) return prgrom.read(addr);
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.readCIRAM(addr & 0x07ff);
}
if(chrram.size) return chrram.read(addr);
return chrrom.read(addr);
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.writeCIRAM(addr & 0x07ff, data);
}
if(chrram.size) return chrram.write(addr, data);
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
}
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
};
-95
View File
@@ -1,95 +0,0 @@
//MMC2
struct NES_PxROM : Board {
NES_PxROM(Markup::Node& document) : Board(document) {
revision = Revision::PNROM;
}
auto readPRG(uint addr) -> uint8 {
if(addr < 0x6000) return cpu.mdr();
if(addr < 0x8000) return prgram.read(addr);
uint bank = 0;
switch((addr / 0x2000) & 3) {
case 0: bank = prgBank; break;
case 1: bank = 0x0d; break;
case 2: bank = 0x0e; break;
case 3: bank = 0x0f; break;
}
return prgrom.read((bank * 0x2000) | (addr & 0x1fff));
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr < 0x6000) return;
if(addr < 0x8000) return prgram.write(addr, data);
switch(addr & 0xf000) {
case 0xa000: prgBank = data & 0x0f; break;
case 0xb000: chrBank[0][0] = data & 0x1f; break;
case 0xc000: chrBank[0][1] = data & 0x1f; break;
case 0xd000: chrBank[1][0] = data & 0x1f; break;
case 0xe000: chrBank[1][1] = data & 0x1f; break;
case 0xf000: mirror = data & 0x01; break;
}
}
auto addrCIRAM(uint addr) const -> uint {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
}
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM(addrCIRAM(addr));
bool region = addr & 0x1000;
uint bank = chrBank[region][latch[region]];
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
return Board::readCHR((bank * 0x1000) | (addr & 0x0fff));
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM(addrCIRAM(addr), data);
bool region = addr & 0x1000;
uint bank = chrBank[region][latch[region]];
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
return Board::writeCHR((bank * 0x1000) | (addr & 0x0fff), data);
}
auto power() -> void {
}
auto reset() -> void {
prgBank = 0;
chrBank[0][0] = 0;
chrBank[0][1] = 0;
chrBank[1][0] = 0;
chrBank[1][1] = 0;
mirror = 0;
latch[0] = 0;
latch[1] = 0;
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.integer(prgBank);
s.integer(chrBank[0][0]);
s.integer(chrBank[0][1]);
s.integer(chrBank[1][0]);
s.integer(chrBank[1][1]);
s.integer(mirror);
s.array(latch);
}
enum Revision : uint {
PEEOROM,
PNROM,
} revision;
uint4 prgBank;
uint5 chrBank[2][2];
bool mirror;
bool latch[2];
};
-99
View File
@@ -1,99 +0,0 @@
struct NES_SxROM : Board {
NES_SxROM(Markup::Node& document) : Board(document), mmc1(*this) {
revision = Revision::SXROM;
}
auto main() -> void {
return mmc1.main();
}
auto addrRAM(uint addr) -> uint {
uint bank = 0;
if(revision == Revision::SOROM) bank = (mmc1.chrBank[0] & 0x08) >> 3;
if(revision == Revision::SUROM) bank = (mmc1.chrBank[0] & 0x0c) >> 2;
if(revision == Revision::SXROM) bank = (mmc1.chrBank[0] & 0x0c) >> 2;
return (bank << 13) | (addr & 0x1fff);
}
auto readPRG(uint addr) -> uint8 {
if((addr & 0xe000) == 0x6000) {
if(revision == Revision::SNROM) {
if(mmc1.chrBank[0] & 0x10) return cpu.mdr();
}
if(mmc1.ramDisable) return 0x00;
return prgram.read(addrRAM(addr));
}
if(addr & 0x8000) {
addr = mmc1.addrPRG(addr);
if(revision == Revision::SXROM) {
addr |= ((mmc1.chrBank[0] & 0x10) >> 4) << 18;
}
return prgrom.read(addr);
}
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if((addr & 0xe000) == 0x6000) {
if(revision == Revision::SNROM) {
if(mmc1.chrBank[0] & 0x10) return;
}
if(mmc1.ramDisable) return;
return prgram.write(addrRAM(addr), data);
}
if(addr & 0x8000) return mmc1.writeIO(addr, data);
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM(mmc1.addrCIRAM(addr));
return Board::readCHR(mmc1.addrCHR(addr));
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM(mmc1.addrCIRAM(addr), data);
return Board::writeCHR(mmc1.addrCHR(addr), data);
}
auto power() -> void {
mmc1.power();
}
auto reset() -> void {
mmc1.reset();
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
mmc1.serialize(s);
}
enum class Revision : uint {
SAROM,
SBROM,
SCROM,
SC1ROM,
SEROM,
SFROM,
SGROM,
SHROM,
SH1ROM,
SIROM,
SJROM,
SKROM,
SLROM,
SL1ROM,
SL2ROM,
SL3ROM,
SLRROM,
SMROM,
SNROM,
SOROM,
SUROM,
SXROM,
} revision;
MMC1 mmc1;
};
-65
View File
@@ -1,65 +0,0 @@
struct NES_TxROM : Board {
NES_TxROM(Markup::Node& document) : Board(document), mmc3(*this) {
revision = Revision::TLROM;
}
auto main() -> void {
mmc3.main();
}
auto readPRG(uint addr) -> uint8 {
if((addr & 0xe000) == 0x6000) return mmc3.readRAM(addr);
if(addr & 0x8000) return prgrom.read(mmc3.addrPRG(addr));
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if((addr & 0xe000) == 0x6000) return mmc3.writeRAM(addr, data);
if(addr & 0x8000) return mmc3.writeIO(addr, data);
}
auto readCHR(uint addr) -> uint8 {
mmc3.irqTest(addr);
if(addr & 0x2000) return ppu.readCIRAM(mmc3.addrCIRAM(addr));
return Board::readCHR(mmc3.addrCHR(addr));
}
auto writeCHR(uint addr, uint8 data) -> void {
mmc3.irqTest(addr);
if(addr & 0x2000) return ppu.writeCIRAM(mmc3.addrCIRAM(addr), data);
return Board::writeCHR(mmc3.addrCHR(addr), data);
}
auto power() -> void {
mmc3.power();
}
auto reset() -> void {
mmc3.reset();
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
mmc3.serialize(s);
}
enum class Revision : uint {
TBROM,
TEROM,
TFROM,
TGROM,
TKROM,
TKSROM,
TLROM,
TL1ROM,
TL2ROM,
TLSROM,
TNROM,
TQROM,
TR1ROM,
TSROM,
TVROM,
} revision;
MMC3 mmc3;
};
-53
View File
@@ -1,53 +0,0 @@
//NES-UNROM
//NES-UOROM
struct NES_UxROM : Board {
NES_UxROM(Markup::Node& document) : Board(document) {
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
}
auto readPRG(uint addr) -> uint8 {
if((addr & 0xc000) == 0x8000) return prgrom.read((prgBank << 14) | (addr & 0x3fff));
if((addr & 0xc000) == 0xc000) return prgrom.read(( 0x0f << 14) | (addr & 0x3fff));
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr & 0x8000) prgBank = data & 0x0f;
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.readCIRAM(addr);
}
return Board::readCHR(addr);
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.writeCIRAM(addr, data);
}
return Board::writeCHR(addr, data);
}
auto power() -> void {
}
auto reset() -> void {
prgBank = 0;
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.integer(prgBank);
}
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
uint4 prgBank;
};
-218
View File
@@ -1,218 +0,0 @@
//SUNSOFT-5B
struct Sunsoft5B : Board {
Sunsoft5B(Markup::Node& document) : Board(document) {
}
struct Pulse {
auto clock() -> void {
if(--counter == 0) {
counter = frequency << 4;
duty ^= 1;
}
output = duty ? volume : (uint4)0;
if(disable) output = 0;
}
auto reset() -> void {
disable = 1;
frequency = 1;
volume = 0;
counter = 0;
duty = 0;
output = 0;
}
auto serialize(serializer& s) -> void {
s.integer(disable);
s.integer(frequency);
s.integer(volume);
s.integer(counter);
s.integer(duty);
s.integer(output);
}
bool disable;
uint12 frequency;
uint4 volume;
uint16 counter; //12-bit countdown + 4-bit phase
uint1 duty;
uint4 output;
} pulse[3];
auto main() -> void {
if(irqCounterEnable) {
if(--irqCounter == 0xffff) {
cpu.irqLine(irqEnable);
}
}
pulse[0].clock();
pulse[1].clock();
pulse[2].clock();
int16 output = dac[pulse[0].output] + dac[pulse[1].output] + dac[pulse[2].output];
apu.setSample(-output);
tick();
}
auto readPRG(uint addr) -> uint8 {
if(addr < 0x6000) return cpu.mdr();
uint8 bank = 0x3f; //((addr & 0xe000) == 0xe000
if((addr & 0xe000) == 0x6000) bank = prgBank[0];
if((addr & 0xe000) == 0x8000) bank = prgBank[1];
if((addr & 0xe000) == 0xa000) bank = prgBank[2];
if((addr & 0xe000) == 0xc000) bank = prgBank[3];
bool ramEnable = bank & 0x80;
bool ramSelect = bank & 0x40;
bank &= 0x3f;
if(ramSelect) {
if(!ramEnable) return cpu.mdr();
return prgram.data[addr & 0x1fff];
}
addr = (bank << 13) | (addr & 0x1fff);
return prgrom.read(addr);
}
auto writePRG(uint addr, uint8 data) -> void {
if((addr & 0xe000) == 0x6000) {
prgram.data[addr & 0x1fff] = data;
}
if(addr == 0x8000) {
mmuPort = data & 0x0f;
}
if(addr == 0xa000) {
switch(mmuPort) {
case 0: chrBank[0] = data; break;
case 1: chrBank[1] = data; break;
case 2: chrBank[2] = data; break;
case 3: chrBank[3] = data; break;
case 4: chrBank[4] = data; break;
case 5: chrBank[5] = data; break;
case 6: chrBank[6] = data; break;
case 7: chrBank[7] = data; break;
case 8: prgBank[0] = data; break;
case 9: prgBank[1] = data; break;
case 10: prgBank[2] = data; break;
case 11: prgBank[3] = data; break;
case 12: mirror = data & 3; break;
case 13:
irqEnable = data & 0x80;
irqCounterEnable = data & 0x01;
if(irqEnable == 0) cpu.irqLine(0);
break;
case 14: irqCounter = (irqCounter & 0xff00) | (data << 0); break;
case 15: irqCounter = (irqCounter & 0x00ff) | (data << 8); break;
}
}
if(addr == 0xc000) {
apuPort = data & 0x0f;
}
if(addr == 0xe000) {
switch(apuPort) {
case 0: pulse[0].frequency = (pulse[0].frequency & 0xff00) | (data << 0); break;
case 1: pulse[0].frequency = (pulse[0].frequency & 0x00ff) | (data << 8); break;
case 2: pulse[1].frequency = (pulse[1].frequency & 0xff00) | (data << 0); break;
case 3: pulse[1].frequency = (pulse[1].frequency & 0x00ff) | (data << 8); break;
case 4: pulse[2].frequency = (pulse[2].frequency & 0xff00) | (data << 0); break;
case 5: pulse[2].frequency = (pulse[2].frequency & 0x00ff) | (data << 8); break;
case 7:
pulse[0].disable = data & 0x01;
pulse[1].disable = data & 0x02;
pulse[2].disable = data & 0x04;
break;
case 8: pulse[0].volume = data & 0x0f; break;
case 9: pulse[1].volume = data & 0x0f; break;
case 10: pulse[2].volume = data & 0x0f; break;
}
}
}
auto addrCHR(uint addr) -> uint {
uint8 bank = (addr >> 10) & 7;
return (chrBank[bank] << 10) | (addr & 0x03ff);
}
auto addrCIRAM(uint addr) -> uint {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal
case 2: return 0x0000 | (addr & 0x03ff); //first
case 3: return 0x0400 | (addr & 0x03ff); //second
}
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM(addrCIRAM(addr));
return Board::readCHR(addrCHR(addr));
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM(addrCIRAM(addr), data);
return Board::writeCHR(addrCHR(addr), data);
}
auto power() -> void {
for(signed n : range(16)) {
double volume = 1.0 / pow(2, 1.0 / 2 * (15 - n));
dac[n] = volume * 8192.0;
}
}
auto reset() -> void {
mmuPort = 0;
apuPort = 0;
for(auto& n : prgBank) n = 0;
for(auto& n : chrBank) n = 0;
mirror = 0;
irqEnable = 0;
irqCounterEnable = 0;
irqCounter = 0;
pulse[0].reset();
pulse[1].reset();
pulse[2].reset();
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.integer(mmuPort);
s.integer(apuPort);
s.array(prgBank);
s.array(chrBank);
s.integer(mirror);
s.integer(irqEnable);
s.integer(irqCounterEnable);
s.integer(irqCounter);
pulse[0].serialize(s);
pulse[1].serialize(s);
pulse[2].serialize(s);
}
uint4 mmuPort;
uint4 apuPort;
uint8 prgBank[4];
uint8 chrBank[8];
uint2 mirror;
bool irqEnable;
bool irqCounterEnable;
uint16 irqCounter;
int16 dac[16];
};
-81
View File
@@ -1,81 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
#include "chip/chip.cpp"
#include "board/board.cpp"
Cartridge cartridge;
auto Cartridge::Enter() -> void {
while(true) scheduler.synchronize(), cartridge.main();
}
auto Cartridge::main() -> void {
board->main();
}
auto Cartridge::load() -> bool {
if(auto pathID = interface->load(ID::Famicom, "Famicom", "fc")) {
information.pathID = pathID();
} else return false;
if(auto fp = interface->open(pathID(), "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads();
} else {
return false;
}
Board::load(information.manifest); //this call will set Cartridge::board if successful
if(!board) return false;
Hash::SHA256 sha;
sha.data(board->prgrom.data, board->prgrom.size);
sha.data(board->chrrom.data, board->chrrom.size);
information.sha256 = sha.digest();
return true;
}
auto Cartridge::save() -> void {
board->save();
}
auto Cartridge::unload() -> void {
delete board;
board = nullptr;
}
auto Cartridge::power() -> void {
board->power();
}
auto Cartridge::reset() -> void {
create(Cartridge::Enter, 21'477'272);
board->reset();
}
auto Cartridge::readPRG(uint addr) -> uint8 {
return board->readPRG(addr);
}
auto Cartridge::writePRG(uint addr, uint8 data) -> void {
return board->writePRG(addr, data);
}
auto Cartridge::readCHR(uint addr) -> uint8 {
return board->readCHR(addr);
}
auto Cartridge::writeCHR(uint addr, uint8 data) -> void {
return board->writeCHR(addr, data);
}
auto Cartridge::scanline(uint y) -> void {
return board->scanline(y);
}
auto Cartridge::serialize(serializer& s) -> void {
Thread::serialize(s);
return board->serialize(s);
}
}
-43
View File
@@ -1,43 +0,0 @@
#include "chip/chip.hpp"
#include "board/board.hpp"
struct Cartridge : Thread {
static auto Enter() -> void;
auto main() -> void;
auto pathID() const -> uint { return information.pathID; }
auto sha256() const -> string { return information.sha256; }
auto manifest() const -> string { return information.manifest; }
auto title() const -> string { return information.title; }
auto load() -> bool;
auto save() -> void;
auto unload() -> void;
auto power() -> void;
auto reset() -> void;
auto serialize(serializer&) -> void;
struct Information {
uint pathID = 0;
string sha256;
string manifest;
string title;
} information;
//privileged:
Board* board = nullptr;
auto readPRG(uint addr) -> uint8;
auto writePRG(uint addr, uint8 data) -> void;
auto readCHR(uint addr) -> uint8;
auto writeCHR(uint addr, uint8 data) -> void;
//scanline() is for debugging purposes only:
//boards must detect scanline edges on their own
auto scanline(uint y) -> void;
};
extern Cartridge cartridge;
-17
View File
@@ -1,17 +0,0 @@
#include "mmc1.cpp"
#include "mmc3.cpp"
#include "mmc5.cpp"
#include "mmc6.cpp"
#include "vrc1.cpp"
#include "vrc2.cpp"
#include "vrc3.cpp"
#include "vrc4.cpp"
#include "vrc6.cpp"
#include "vrc7.cpp"
Chip::Chip(Board& board) : board(board) {
}
auto Chip::tick() -> void {
board.tick();
}
-8
View File
@@ -1,8 +0,0 @@
struct Board;
struct Chip {
Chip(Board& board);
auto tick() -> void;
Board& board;
};
-128
View File
@@ -1,128 +0,0 @@
struct MMC1 : Chip {
MMC1(Board& board) : Chip(board) {
revision = Revision::MMC1B2;
}
auto main() -> void {
if(writedelay) writedelay--;
tick();
}
auto addrPRG(uint addr) -> uint {
bool region = addr & 0x4000;
uint bank = (prgBank & ~1) + region;
if(prgSize) {
bank = (region == 0 ? 0x0 : 0xf);
if(region != prgMode) bank = prgBank;
}
return (bank << 14) | (addr & 0x3fff);
}
auto addrCHR(uint addr) -> uint {
bool region = addr & 0x1000;
uint bank = chrBank[region];
if(chrMode == 0) bank = (chrBank[0] & ~1) | region;
return (bank << 12) | (addr & 0x0fff);
}
auto addrCIRAM(uint addr) -> uint {
switch(mirror) {
case 0: return 0x0000 | (addr & 0x03ff);
case 1: return 0x0400 | (addr & 0x03ff);
case 2: return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
case 3: return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
}
}
auto writeIO(uint addr, uint8 data) -> void {
if(writedelay) return;
writedelay = 2;
if(data & 0x80) {
shiftaddr = 0;
prgSize = 1;
prgMode = 1;
} else {
shiftdata = ((data & 1) << 4) | (shiftdata >> 1);
if(++shiftaddr == 5) {
shiftaddr = 0;
switch((addr >> 13) & 3) {
case 0:
chrMode = (shiftdata & 0x10);
prgSize = (shiftdata & 0x08);
prgMode = (shiftdata & 0x04);
mirror = (shiftdata & 0x03);
break;
case 1:
chrBank[0] = (shiftdata & 0x1f);
break;
case 2:
chrBank[1] = (shiftdata & 0x1f);
break;
case 3:
ramDisable = (shiftdata & 0x10);
prgBank = (shiftdata & 0x0f);
break;
}
}
}
}
auto power() -> void {
}
auto reset() -> void {
writedelay = 0;
shiftaddr = 0;
shiftdata = 0;
chrMode = 0;
prgSize = 1;
prgMode = 1;
mirror = 0;
chrBank[0] = 0;
chrBank[1] = 1;
ramDisable = 0;
prgBank = 0;
}
auto serialize(serializer& s) -> void {
s.integer(writedelay);
s.integer(shiftaddr);
s.integer(shiftdata);
s.integer(chrMode);
s.integer(prgSize);
s.integer(prgMode);
s.integer(mirror);
s.array(chrBank);
s.integer(ramDisable);
s.integer(prgBank);
}
enum class Revision : uint {
MMC1,
MMC1A,
MMC1B1,
MMC1B2,
MMC1B3,
MMC1C,
} revision;
uint writedelay;
uint shiftaddr;
uint shiftdata;
bool chrMode;
bool prgSize; //0 = 32K, 1 = 16K
bool prgMode;
uint2 mirror; //0 = first, 1 = second, 2 = vertical, 3 = horizontal
uint5 chrBank[2];
bool ramDisable;
uint4 prgBank;
};
-181
View File
@@ -1,181 +0,0 @@
struct MMC3 : Chip {
MMC3(Board& board) : Chip(board) {
}
auto main() -> void {
if(irqDelay) irqDelay--;
cpu.irqLine(irqLine);
tick();
}
auto irqTest(uint addr) -> void {
if(!(chrAbus & 0x1000) && (addr & 0x1000)) {
if(irqDelay == 0) {
if(irqCounter == 0) {
irqCounter = irqLatch;
} else if(--irqCounter == 0) {
if(irqEnable) irqLine = 1;
}
}
irqDelay = 6;
}
chrAbus = addr;
}
auto addrPRG(uint addr) const -> uint {
switch((addr >> 13) & 3) {
case 0:
if(prgMode == 1) return (0x3e << 13) | (addr & 0x1fff);
return (prgBank[0] << 13) | (addr & 0x1fff);
case 1:
return (prgBank[1] << 13) | (addr & 0x1fff);
case 2:
if(prgMode == 0) return (0x3e << 13) | (addr & 0x1fff);
return (prgBank[0] << 13) | (addr & 0x1fff);
case 3:
return (0x3f << 13) | (addr & 0x1fff);
}
}
auto addrCHR(uint addr) const -> uint {
if(chrMode == 0) {
if(addr <= 0x07ff) return (chrBank[0] << 10) | (addr & 0x07ff);
if(addr <= 0x0fff) return (chrBank[1] << 10) | (addr & 0x07ff);
if(addr <= 0x13ff) return (chrBank[2] << 10) | (addr & 0x03ff);
if(addr <= 0x17ff) return (chrBank[3] << 10) | (addr & 0x03ff);
if(addr <= 0x1bff) return (chrBank[4] << 10) | (addr & 0x03ff);
if(addr <= 0x1fff) return (chrBank[5] << 10) | (addr & 0x03ff);
} else {
if(addr <= 0x03ff) return (chrBank[2] << 10) | (addr & 0x03ff);
if(addr <= 0x07ff) return (chrBank[3] << 10) | (addr & 0x03ff);
if(addr <= 0x0bff) return (chrBank[4] << 10) | (addr & 0x03ff);
if(addr <= 0x0fff) return (chrBank[5] << 10) | (addr & 0x03ff);
if(addr <= 0x17ff) return (chrBank[0] << 10) | (addr & 0x07ff);
if(addr <= 0x1fff) return (chrBank[1] << 10) | (addr & 0x07ff);
}
}
auto addrCIRAM(uint addr) const -> uint {
if(mirror == 0) return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
if(mirror == 1) return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
}
auto readRAM(uint addr) -> uint8 {
if(ramEnable) return board.prgram.data[addr & 0x1fff];
return 0x00;
}
auto writeRAM(uint addr, uint8 data) -> void {
if(ramEnable && !ramWriteProtect) board.prgram.data[addr & 0x1fff] = data;
}
auto writeIO(uint addr, uint8 data) -> void {
switch(addr & 0xe001) {
case 0x8000:
chrMode = data & 0x80;
prgMode = data & 0x40;
bankSelect = data & 0x07;
break;
case 0x8001:
switch(bankSelect) {
case 0: chrBank[0] = data & ~1; break;
case 1: chrBank[1] = data & ~1; break;
case 2: chrBank[2] = data; break;
case 3: chrBank[3] = data; break;
case 4: chrBank[4] = data; break;
case 5: chrBank[5] = data; break;
case 6: prgBank[0] = data & 0x3f; break;
case 7: prgBank[1] = data & 0x3f; break;
}
break;
case 0xa000:
mirror = data & 0x01;
break;
case 0xa001:
ramEnable = data & 0x80;
ramWriteProtect = data & 0x40;
break;
case 0xc000:
irqLatch = data;
break;
case 0xc001:
irqCounter = 0;
break;
case 0xe000:
irqEnable = false;
irqLine = 0;
break;
case 0xe001:
irqEnable = true;
break;
}
}
auto power() -> void {
}
auto reset() -> void {
chrMode = 0;
prgMode = 0;
bankSelect = 0;
prgBank[0] = 0;
prgBank[1] = 0;
chrBank[0] = 0;
chrBank[1] = 0;
chrBank[2] = 0;
chrBank[3] = 0;
chrBank[4] = 0;
chrBank[5] = 0;
mirror = 0;
ramEnable = 1;
ramWriteProtect = 0;
irqLatch = 0;
irqCounter = 0;
irqEnable = false;
irqDelay = 0;
irqLine = 0;
chrAbus = 0;
}
auto serialize(serializer& s) -> void {
s.integer(chrMode);
s.integer(prgMode);
s.integer(bankSelect);
s.array(prgBank);
s.array(chrBank);
s.integer(mirror);
s.integer(ramEnable);
s.integer(ramWriteProtect);
s.integer(irqLatch);
s.integer(irqCounter);
s.integer(irqEnable);
s.integer(irqDelay);
s.integer(irqLine);
s.integer(chrAbus);
}
bool chrMode;
bool prgMode;
uint3 bankSelect;
uint8 prgBank[2];
uint8 chrBank[6];
bool mirror;
bool ramEnable;
bool ramWriteProtect;
uint8 irqLatch;
uint8 irqCounter;
bool irqEnable;
uint irqDelay;
bool irqLine;
uint16 chrAbus;
};
-489
View File
@@ -1,489 +0,0 @@
struct MMC5 : Chip {
MMC5(Board& board) : Chip(board) {
revision = Revision::MMC5;
}
auto main() -> void {
//scanline() resets this; if no scanlines detected, enter video blanking period
if(++cpuCycleCounter >= 200) blank(); //113-114 normal; ~2500 across Vblank period
cpu.irqLine(irqEnable && irqPending);
tick();
}
auto scanline(uint y) -> void {
//used for testing only, to verify MMC5 scanline detection is accurate:
//if(y != vcounter && y <= 240) print(y, " vs ", vcounter, "\n");
}
auto accessPRG(bool write, uint addr, uint8 data = 0x00) -> uint8 {
uint bank;
if((addr & 0xe000) == 0x6000) {
bank = (ramSelect << 2) | ramBank;
addr &= 0x1fff;
} else if(prgMode == 0) {
bank = prgBank[3] & ~3;
addr &= 0x7fff;
} else if(prgMode == 1) {
if((addr & 0xc000) == 0x8000) bank = (prgBank[1] & ~1);
if((addr & 0xe000) == 0xc000) bank = (prgBank[3] & ~1);
addr &= 0x3fff;
} else if(prgMode == 2) {
if((addr & 0xe000) == 0x8000) bank = (prgBank[1] & ~1) | 0;
if((addr & 0xe000) == 0xa000) bank = (prgBank[1] & ~1) | 1;
if((addr & 0xe000) == 0xc000) bank = (prgBank[2]);
if((addr & 0xe000) == 0xe000) bank = (prgBank[3]);
addr &= 0x1fff;
} else if(prgMode == 3) {
if((addr & 0xe000) == 0x8000) bank = prgBank[0];
if((addr & 0xe000) == 0xa000) bank = prgBank[1];
if((addr & 0xe000) == 0xc000) bank = prgBank[2];
if((addr & 0xe000) == 0xe000) bank = prgBank[3];
addr &= 0x1fff;
}
bool rom = bank & 0x80;
bank &= 0x7f;
if(write == false) {
if(rom) {
return board.prgrom.read((bank << 13) | addr);
} else {
return board.prgram.read((bank << 13) | addr);
}
} else {
if(rom) {
board.prgrom.write((bank << 13) | addr, data);
} else {
if(prgramWriteProtect[0] == 2 && prgramWriteProtect[1] == 1) {
board.prgram.write((bank << 13) | addr, data);
}
}
return 0x00;
}
}
auto readPRG(uint addr) -> uint8 {
if((addr & 0xfc00) == 0x5c00) {
if(exramMode >= 2) return exram[addr & 0x03ff];
return cpu.mdr();
}
if(addr >= 0x6000) {
return accessPRG(0, addr);
}
switch(addr) {
case 0x5204: {
uint8 result = (irqPending << 7) | (inFrame << 6);
irqPending = false;
return result;
}
case 0x5205: return (multiplier * multiplicand) >> 0;
case 0x5206: return (multiplier * multiplicand) >> 8;
}
}
auto writePRG(uint addr, uint8 data) -> void {
if((addr & 0xfc00) == 0x5c00) {
//writes 0x00 *during* Vblank (not during screen rendering ...)
if(exramMode == 0 || exramMode == 1) exram[addr & 0x03ff] = inFrame ? data : (uint8)0x00;
if(exramMode == 2) exram[addr & 0x03ff] = data;
return;
}
if(addr >= 0x6000) {
accessPRG(1, addr, data);
return;
}
switch(addr) {
case 0x2000:
sprite8x16 = data & 0x20;
break;
case 0x2001:
//if BG+sprites are disabled; enter video blanking period
if((data & 0x18) == 0) blank();
break;
case 0x5100: prgMode = data & 3; break;
case 0x5101: chrMode = data & 3; break;
case 0x5102: prgramWriteProtect[0] = data & 3; break;
case 0x5103: prgramWriteProtect[1] = data & 3; break;
case 0x5104:
exramMode = data & 3;
break;
case 0x5105:
nametableMode[0] = (data & 0x03) >> 0;
nametableMode[1] = (data & 0x0c) >> 2;
nametableMode[2] = (data & 0x30) >> 4;
nametableMode[3] = (data & 0xc0) >> 6;
break;
case 0x5106:
fillmodeTile = data;
break;
case 0x5107:
fillmodeColor = data & 3;
fillmodeColor |= fillmodeColor << 2;
fillmodeColor |= fillmodeColor << 4;
break;
case 0x5113:
ramSelect = data & 0x04;
ramBank = data & 0x03;
break;
case 0x5114: prgBank[0] = data; break;
case 0x5115: prgBank[1] = data; break;
case 0x5116: prgBank[2] = data; break;
case 0x5117: prgBank[3] = data | 0x80; break;
case 0x5120: chrSpriteBank[0] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5121: chrSpriteBank[1] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5122: chrSpriteBank[2] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5123: chrSpriteBank[3] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5124: chrSpriteBank[4] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5125: chrSpriteBank[5] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5126: chrSpriteBank[6] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5127: chrSpriteBank[7] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5128: chrBGBank[0] = (chrBankHi << 8) | data; chrActive = 1; break;
case 0x5129: chrBGBank[1] = (chrBankHi << 8) | data; chrActive = 1; break;
case 0x512a: chrBGBank[2] = (chrBankHi << 8) | data; chrActive = 1; break;
case 0x512b: chrBGBank[3] = (chrBankHi << 8) | data; chrActive = 1; break;
case 0x5130:
chrBankHi = data & 3;
break;
case 0x5200:
vsEnable = data & 0x80;
vsSide = data & 0x40;
vsTile = data & 0x1f;
break;
case 0x5201:
vsScroll = data;
break;
case 0x5202:
vsBank = data;
break;
case 0x5203:
irqLine = data;
break;
case 0x5204:
irqEnable = data & 0x80;
break;
case 0x5205:
multiplicand = data;
break;
case 0x5206:
multiplier = data;
break;
}
}
auto chrSpriteAddr(uint addr) -> uint {
if(chrMode == 0) {
auto bank = chrSpriteBank[7];
return (bank * 0x2000) + (addr & 0x1fff);
}
if(chrMode == 1) {
auto bank = chrSpriteBank[(addr / 0x1000) * 4 + 3];
return (bank * 0x1000) + (addr & 0x0fff);
}
if(chrMode == 2) {
auto bank = chrSpriteBank[(addr / 0x0800) * 2 + 1];
return (bank * 0x0800) + (addr & 0x07ff);
}
if(chrMode == 3) {
auto bank = chrSpriteBank[(addr / 0x0400)];
return (bank * 0x0400) + (addr & 0x03ff);
}
}
auto chrBGAddr(uint addr) -> uint {
addr &= 0x0fff;
if(chrMode == 0) {
auto bank = chrBGBank[3];
return (bank * 0x2000) + (addr & 0x0fff);
}
if(chrMode == 1) {
auto bank = chrBGBank[3];
return (bank * 0x1000) + (addr & 0x0fff);
}
if(chrMode == 2) {
auto bank = chrBGBank[(addr / 0x0800) * 2 + 1];
return (bank * 0x0800) + (addr & 0x07ff);
}
if(chrMode == 3) {
auto bank = chrBGBank[(addr / 0x0400)];
return (bank * 0x0400) + (addr & 0x03ff);
}
}
auto chrVSAddr(uint addr) -> uint {
return (vsBank * 0x1000) + (addr & 0x0ff8) + (vsVpos & 7);
}
auto blank() -> void {
inFrame = false;
}
auto scanline() -> void {
hcounter = 0;
if(inFrame == false) {
inFrame = true;
irqPending = false;
vcounter = 0;
} else {
if(vcounter == irqLine) irqPending = true;
vcounter++;
}
cpuCycleCounter = 0;
}
auto readCIRAM(uint addr) -> uint8 {
if(vsFetch && (hcounter & 2) == 0) return exram[vsVpos / 8 * 32 + vsHpos / 8];
if(vsFetch && (hcounter & 2) != 0) return exram[vsVpos / 32 * 8 + vsHpos / 32 + 0x03c0];
switch(nametableMode[(addr >> 10) & 3]) {
case 0: return ppu.readCIRAM(0x0000 | (addr & 0x03ff));
case 1: return ppu.readCIRAM(0x0400 | (addr & 0x03ff));
case 2: return exramMode < 2 ? exram[addr & 0x03ff] : (uint8)0x00;
case 3: return (hcounter & 2) == 0 ? fillmodeTile : fillmodeColor;
}
}
auto readCHR(uint addr) -> uint8 {
chrAccess[0] = chrAccess[1];
chrAccess[1] = chrAccess[2];
chrAccess[2] = chrAccess[3];
chrAccess[3] = addr;
//detect two unused nametable fetches at end of each scanline
if((chrAccess[0] & 0x2000) == 0
&& (chrAccess[1] & 0x2000)
&& (chrAccess[2] & 0x2000)
&& (chrAccess[3] & 0x2000)) scanline();
if(inFrame == false) {
vsFetch = false;
if(addr & 0x2000) return readCIRAM(addr);
return board.chrrom.read(chrActive ? chrBGAddr(addr) : chrSpriteAddr(addr));
}
bool bgFetch = (hcounter < 256 || hcounter >= 320);
uint8 result = 0x00;
if((hcounter & 7) == 0) {
vsHpos = hcounter >= 320 ? hcounter - 320 : hcounter + 16;
vsVpos = vcounter + vsScroll;
vsFetch = vsEnable && bgFetch && exramMode < 2
&& (vsSide ? vsHpos / 8 >= vsTile : vsHpos / 8 < vsTile);
if(vsVpos >= 240) vsVpos -= 240;
result = readCIRAM(addr);
exbank = (chrBankHi << 6) | (exram[addr & 0x03ff] & 0x3f);
exattr = exram[addr & 0x03ff] >> 6;
exattr |= exattr << 2;
exattr |= exattr << 4;
} else if((hcounter & 7) == 2) {
result = readCIRAM(addr);
if(bgFetch && exramMode == 1) result = exattr;
} else {
if(vsFetch) result = board.chrrom.read(chrVSAddr(addr));
else if(sprite8x16 ? bgFetch : chrActive) result = board.chrrom.read(chrBGAddr(addr));
else result = board.chrrom.read(chrSpriteAddr(addr));
if(bgFetch && exramMode == 1) result = board.chrrom.read(exbank * 0x1000 + (addr & 0x0fff));
}
hcounter += 2;
return result;
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) {
switch(nametableMode[(addr >> 10) & 3]) {
case 0: return ppu.writeCIRAM(0x0000 | (addr & 0x03ff), data);
case 1: return ppu.writeCIRAM(0x0400 | (addr & 0x03ff), data);
case 2: exram[addr & 0x03ff] = data; break;
}
}
}
auto power() -> void {
}
auto reset() -> void {
for(auto& n : exram) n = 0xff;
prgMode = 3;
chrMode = 0;
for(auto& n : prgramWriteProtect) n = 0;
exramMode = 0;
for(auto& n : nametableMode) n = 0;
fillmodeTile = 0;
fillmodeColor = 0;
ramSelect = 0;
ramBank = 0;
prgBank[0] = 0x00;
prgBank[1] = 0x00;
prgBank[2] = 0x00;
prgBank[3] = 0xff;
for(auto& n : chrSpriteBank) n = 0;
for(auto& n : chrBGBank) n = 0;
chrBankHi = 0;
vsEnable = 0;
vsSide = 0;
vsTile = 0;
vsScroll = 0;
vsBank = 0;
irqLine = 0;
irqEnable = 0;
multiplicand = 0;
multiplier = 0;
cpuCycleCounter = 0;
irqCounter = 0;
irqPending = 0;
inFrame = 0;
vcounter = 0;
hcounter = 0;
for(auto& n : chrAccess) n = 0;
chrActive = 0;
sprite8x16 = 0;
exbank = 0;
exattr = 0;
vsFetch = 0;
vsVpos = 0;
vsHpos = 0;
}
auto serialize(serializer& s) -> void {
s.array(exram);
s.integer(prgMode);
s.integer(chrMode);
for(auto& n : prgramWriteProtect) s.integer(n);
s.integer(exramMode);
for(auto& n : nametableMode) s.integer(n);
s.integer(fillmodeTile);
s.integer(fillmodeColor);
s.integer(ramSelect);
s.integer(ramBank);
for(auto& n : prgBank) s.integer(n);
for(auto& n : chrSpriteBank) s.integer(n);
for(auto& n : chrBGBank) s.integer(n);
s.integer(chrBankHi);
s.integer(vsEnable);
s.integer(vsSide);
s.integer(vsTile);
s.integer(vsScroll);
s.integer(vsBank);
s.integer(irqLine);
s.integer(irqEnable);
s.integer(multiplicand);
s.integer(multiplier);
s.integer(cpuCycleCounter);
s.integer(irqCounter);
s.integer(irqPending);
s.integer(inFrame);
s.integer(vcounter);
s.integer(hcounter);
for(auto& n : chrAccess) s.integer(n);
s.integer(chrActive);
s.integer(sprite8x16);
s.integer(exbank);
s.integer(exattr);
s.integer(vsFetch);
s.integer(vsVpos);
s.integer(vsHpos);
}
enum class Revision : uint {
MMC5,
MMC5B,
} revision;
uint8 exram[1024];
//programmable registers
uint2 prgMode; //$5100
uint2 chrMode; //$5101
uint2 prgramWriteProtect[2]; //$5102,$5103
uint2 exramMode; //$5104
uint2 nametableMode[4]; //$5105
uint8 fillmodeTile; //$5106
uint8 fillmodeColor; //$5107
bool ramSelect; //$5113
uint2 ramBank; //$5113
uint8 prgBank[4]; //$5114-5117
uint10 chrSpriteBank[8]; //$5120-5127
uint10 chrBGBank[4]; //$5128-512b
uint2 chrBankHi; //$5130
bool vsEnable; //$5200
bool vsSide; //$5200
uint5 vsTile; //$5200
uint8 vsScroll; //$5201
uint8 vsBank; //$5202
uint8 irqLine; //$5203
bool irqEnable; //$5204
uint8 multiplicand; //$5205
uint8 multiplier; //$5206
//status registers
uint cpuCycleCounter;
uint irqCounter;
bool irqPending;
bool inFrame;
uint vcounter;
uint hcounter;
uint16 chrAccess[4];
bool chrActive;
bool sprite8x16;
uint8 exbank;
uint8 exattr;
bool vsFetch;
uint8 vsVpos;
uint8 vsHpos;
};
-192
View File
@@ -1,192 +0,0 @@
struct MMC6 : Chip {
MMC6(Board& board) : Chip(board) {
}
auto main() -> void {
if(irqDelay) irqDelay--;
cpu.irqLine(irqLine);
tick();
}
auto irqTest(uint addr) -> void {
if(!(chrAbus & 0x1000) && (addr & 0x1000)) {
if(irqDelay == 0) {
if(irqCounter == 0) {
irqCounter = irqLatch;
} else if(--irqCounter == 0) {
if(irqEnable) irqLine = 1;
}
}
irqDelay = 6;
}
chrAbus = addr;
}
auto addrPRG(uint addr) const -> uint {
switch((addr >> 13) & 3) {
case 0:
if(prgMode == 1) return (0x3e << 13) | (addr & 0x1fff);
return (prgBank[0] << 13) | (addr & 0x1fff);
case 1:
return (prgBank[1] << 13) | (addr & 0x1fff);
case 2:
if(prgMode == 0) return (0x3e << 13) | (addr & 0x1fff);
return (prgBank[0] << 13) | (addr & 0x1fff);
case 3:
return (0x3f << 13) | (addr & 0x1fff);
}
}
auto addrCHR(uint addr) const -> uint {
if(chrMode == 0) {
if(addr <= 0x07ff) return (chrBank[0] << 10) | (addr & 0x07ff);
if(addr <= 0x0fff) return (chrBank[1] << 10) | (addr & 0x07ff);
if(addr <= 0x13ff) return (chrBank[2] << 10) | (addr & 0x03ff);
if(addr <= 0x17ff) return (chrBank[3] << 10) | (addr & 0x03ff);
if(addr <= 0x1bff) return (chrBank[4] << 10) | (addr & 0x03ff);
if(addr <= 0x1fff) return (chrBank[5] << 10) | (addr & 0x03ff);
} else {
if(addr <= 0x03ff) return (chrBank[2] << 10) | (addr & 0x03ff);
if(addr <= 0x07ff) return (chrBank[3] << 10) | (addr & 0x03ff);
if(addr <= 0x0bff) return (chrBank[4] << 10) | (addr & 0x03ff);
if(addr <= 0x0fff) return (chrBank[5] << 10) | (addr & 0x03ff);
if(addr <= 0x17ff) return (chrBank[0] << 10) | (addr & 0x07ff);
if(addr <= 0x1fff) return (chrBank[1] << 10) | (addr & 0x07ff);
}
}
auto addrCIRAM(uint addr) const -> uint {
if(mirror == 0) return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
if(mirror == 1) return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
}
auto readRAM(uint addr) -> uint8 {
if(ramEnable == false) return cpu.mdr();
if(ramReadable[0] == false && ramReadable[1] == false) return cpu.mdr();
bool region = addr & 0x0200;
if(ramReadable[region] == false) return 0x00;
return board.prgram.read((region * 0x0200) + (addr & 0x01ff));
}
auto writeRAM(uint addr, uint8 data) -> void {
if(ramEnable == false) return;
bool region = addr & 0x0200;
if(ramWritable[region] == false) return;
return board.prgram.write((region * 0x0200) + (addr & 0x01ff), data);
}
auto writeIO(uint addr, uint8 data) -> void {
switch(addr & 0xe001) {
case 0x8000:
chrMode = data & 0x80;
prgMode = data & 0x40;
ramEnable = data & 0x20;
bankSelect = data & 0x07;
if(ramEnable == false) {
for(auto &n : ramReadable) n = false;
for(auto &n : ramWritable) n = false;
}
break;
case 0x8001:
switch(bankSelect) {
case 0: chrBank[0] = data & ~1; break;
case 1: chrBank[1] = data & ~1; break;
case 2: chrBank[2] = data; break;
case 3: chrBank[3] = data; break;
case 4: chrBank[4] = data; break;
case 5: chrBank[5] = data; break;
case 6: prgBank[0] = data & 0x3f; break;
case 7: prgBank[1] = data & 0x3f; break;
}
break;
case 0xa000:
mirror = data & 0x01;
break;
case 0xa001:
if(ramEnable == false) break;
ramReadable[1] = data & 0x80;
ramWritable[1] = data & 0x40;
ramReadable[0] = data & 0x20;
ramWritable[0] = data & 0x10;
break;
case 0xc000:
irqLatch = data;
break;
case 0xc001:
irqCounter = 0;
break;
case 0xe000:
irqEnable = false;
irqLine = 0;
break;
case 0xe001:
irqEnable = true;
break;
}
}
auto power() -> void {
}
auto reset() -> void {
chrMode = 0;
prgMode = 0;
ramEnable = 0;
bankSelect = 0;
for(auto& n : prgBank) n = 0;
for(auto& n : chrBank) n = 0;
mirror = 0;
for(auto& n : ramReadable) n = 0;
for(auto& n : ramWritable) n = 0;
irqLatch = 0;
irqCounter = 0;
irqEnable = 0;
irqDelay = 0;
irqLine = 0;
chrAbus = 0;
}
auto serialize(serializer& s) -> void {
s.integer(chrMode);
s.integer(prgMode);
s.integer(ramEnable);
s.integer(bankSelect);
for(auto& n : prgBank) s.integer(n);
for(auto& n : chrBank) s.integer(n);
s.integer(mirror);
for(auto& n : ramReadable) s.integer(n);
for(auto& n : ramWritable) s.integer(n);
s.integer(irqLatch);
s.integer(irqCounter);
s.integer(irqEnable);
s.integer(irqDelay);
s.integer(irqLine);
s.integer(chrAbus);
}
bool chrMode;
bool prgMode;
bool ramEnable;
uint3 bankSelect;
uint8 prgBank[2];
uint8 chrBank[6];
bool mirror;
bool ramReadable[2];
bool ramWritable[2];
uint8 irqLatch;
uint8 irqCounter;
bool irqEnable;
uint irqDelay;
bool irqLine;
uint16 chrAbus;
};
-78
View File
@@ -1,78 +0,0 @@
struct VRC1 : Chip {
VRC1(Board& board) : Chip(board) {
}
auto addrPRG(uint addr) const -> uint {
uint bank = 0x0f;
if((addr & 0xe000) == 0x8000) bank = prgBank[0];
if((addr & 0xe000) == 0xa000) bank = prgBank[1];
if((addr & 0xe000) == 0xc000) bank = prgBank[2];
return (bank * 0x2000) + (addr & 0x1fff);
}
auto addrCHR(uint addr) const -> uint {
uint bank = chrBankLo[(bool)(addr & 0x1000)];
bank |= chrBankHi[(bool)(addr & 0x1000)] << 4;
return (bank * 0x1000) + (addr & 0x0fff);
}
auto addrCIRAM(uint addr) const -> uint {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
}
throw;
}
auto writeIO(uint addr, uint8 data) -> void {
switch(addr & 0xf000) {
case 0x8000:
prgBank[0] = data & 0x0f;
break;
case 0x9000:
chrBankHi[1] = data & 0x04;
chrBankHi[0] = data & 0x02;
mirror = data & 0x01;
break;
case 0xa000:
prgBank[1] = data & 0x0f;
break;
case 0xc000:
prgBank[2] = data & 0x0f;
break;
case 0xe000:
chrBankLo[0] = data & 0x0f;
break;
case 0xf000:
chrBankLo[1] = data & 0x0f;
break;
}
}
auto power() -> void {
}
auto reset() -> void {
for(auto& n : prgBank) n = 0;
for(auto& n : chrBankLo) n = 0;
for(auto& n : chrBankHi) n = 0;
mirror = 0;
}
auto serialize(serializer& s) -> void {
for(auto& n : prgBank) s.integer(n);
for(auto& n : chrBankLo) s.integer(n);
for(auto& n : chrBankHi) s.integer(n);
s.integer(mirror);
}
uint4 prgBank[3];
uint4 chrBankLo[2];
bool chrBankHi[2];
bool mirror;
};
-108
View File
@@ -1,108 +0,0 @@
struct VRC2 : Chip {
VRC2(Board& board) : Chip(board) {
}
auto addrPRG(uint addr) const -> uint {
uint bank;
switch(addr & 0xe000) {
case 0x8000: bank = prgBank[0]; break;
case 0xa000: bank = prgBank[1]; break;
case 0xc000: bank = 0x1e; break;
case 0xe000: bank = 0x1f; break;
}
return (bank * 0x2000) + (addr & 0x1fff);
}
auto addrCHR(uint addr) const -> uint {
uint bank = chrBank[addr / 0x0400];
return (bank * 0x0400) + (addr & 0x03ff);
}
auto addrCIRAM(uint addr) const -> uint {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
}
throw;
}
auto readRAM(uint addr) -> uint8 {
if(board.prgram.size == 0) {
if((addr & 0xf000) == 0x6000) return cpu.mdr() | latch;
return cpu.mdr();
}
return board.prgram.read(addr & 0x1fff);
}
auto writeRAM(uint addr, uint8 data) -> void {
if(board.prgram.size == 0) {
if((addr & 0xf000) == 0x6000) latch = data & 0x01;
return;
}
return board.prgram.write(addr & 0x1fff, data);
}
auto writeIO(uint addr, uint8 data) -> void {
switch(addr) {
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
prgBank[0] = data & 0x1f;
break;
case 0x9000: case 0x9001: case 0x9002: case 0x9003:
mirror = data & 0x03;
break;
case 0xa000: case 0xa001: case 0xa002: case 0xa003:
prgBank[1] = data & 0x1f;
break;
case 0xb000: chrBank[0] = (chrBank[0] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xb001: chrBank[0] = (chrBank[0] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xb002: chrBank[1] = (chrBank[1] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xb003: chrBank[1] = (chrBank[1] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xc000: chrBank[2] = (chrBank[2] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xc001: chrBank[2] = (chrBank[2] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xc002: chrBank[3] = (chrBank[3] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xc003: chrBank[3] = (chrBank[3] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xd000: chrBank[4] = (chrBank[4] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xd001: chrBank[4] = (chrBank[4] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xd002: chrBank[5] = (chrBank[5] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xd003: chrBank[5] = (chrBank[5] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xe000: chrBank[6] = (chrBank[6] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xe001: chrBank[6] = (chrBank[6] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xe002: chrBank[7] = (chrBank[7] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xe003: chrBank[7] = (chrBank[7] & 0x0f) | ((data & 0x0f) << 4); break;
}
}
auto power() -> void {
}
auto reset() -> void {
for(auto& n : prgBank) n = 0;
for(auto& n : chrBank) n = 0;
mirror = 0;
latch = 0;
}
auto serialize(serializer& s) -> void {
for(auto& n : prgBank) s.integer(n);
for(auto& n : chrBank) s.integer(n);
s.integer(mirror);
s.integer(latch);
}
uint5 prgBank[2];
uint8 chrBank[8];
uint2 mirror;
bool latch;
};
-92
View File
@@ -1,92 +0,0 @@
struct VRC3 : Chip {
VRC3(Board& board) : Chip(board) {
}
auto main() -> void {
if(irqEnable) {
if(irqMode == 0) { //16-bit
if(++irqCounter.w == 0) {
irqLine = 1;
irqEnable = irqAcknowledge;
irqCounter.w = irqLatch;
}
}
if(irqMode == 1) { //8-bit
if(++irqCounter.l == 0) {
irqLine = 1;
irqEnable = irqAcknowledge;
irqCounter.l = irqLatch;
}
}
}
cpu.irqLine(irqLine);
tick();
}
auto addrPRG(uint addr) const -> uint {
uint bank = (addr < 0xc000 ? (uint)prgBank : 0x0f);
return (bank * 0x4000) + (addr & 0x3fff);
}
auto writeIO(uint addr, uint8 data) -> void {
switch(addr & 0xf000) {
case 0x8000: irqLatch = (irqLatch & 0xfff0) | ((data & 0x0f) << 0); break;
case 0x9000: irqLatch = (irqLatch & 0xff0f) | ((data & 0x0f) << 4); break;
case 0xa000: irqLatch = (irqLatch & 0xf0ff) | ((data & 0x0f) << 8); break;
case 0xb000: irqLatch = (irqLatch & 0x0fff) | ((data & 0x0f) << 12); break;
case 0xc000:
irqMode = data & 0x04;
irqEnable = data & 0x02;
irqAcknowledge = data & 0x01;
if(irqEnable) irqCounter.w = irqLatch;
break;
case 0xd000:
irqLine = 0;
irqEnable = irqAcknowledge;
break;
case 0xf000:
prgBank = data & 0x0f;
break;
}
}
auto power() -> void {
}
auto reset() -> void {
prgBank = 0;
irqMode = 0;
irqEnable = 0;
irqAcknowledge = 0;
irqLatch = 0;
irqCounter.w = 0;
irqLine = 0;
}
auto serialize(serializer& s) -> void {
s.integer(prgBank);
s.integer(irqMode);
s.integer(irqEnable);
s.integer(irqAcknowledge);
s.integer(irqLatch);
s.integer(irqCounter.w);
s.integer(irqLine);
}
uint4 prgBank;
bool irqMode;
bool irqEnable;
bool irqAcknowledge;
uint16 irqLatch;
struct {
union {
uint16_t w;
struct { uint8_t order_lsb2(l, h); };
};
} irqCounter;
bool irqLine;
};
-176
View File
@@ -1,176 +0,0 @@
struct VRC4 : Chip {
VRC4(Board& board) : Chip(board) {
}
auto main() -> void {
if(irqEnable) {
if(irqMode == 0) {
irqScalar -= 3;
if(irqScalar <= 0) {
irqScalar += 341;
if(irqCounter == 0xff) {
irqCounter = irqLatch;
irqLine = 1;
} else {
irqCounter++;
}
}
}
if(irqMode == 1) {
if(irqCounter == 0xff) {
irqCounter = irqLatch;
irqLine = 1;
} else {
irqCounter++;
}
}
}
cpu.irqLine(irqLine);
tick();
}
auto addrPRG(uint addr) const -> uint {
uint bank = 0, banks = board.prgrom.size / 0x2000;
switch(addr & 0xe000) {
case 0x8000: bank = prgMode == 0 ? (uint)prgBank[0] : banks - 2; break;
case 0xa000: bank = prgBank[1]; break;
case 0xc000: bank = prgMode == 0 ? banks - 2 : (uint)prgBank[0]; break;
case 0xe000: bank = banks - 1; break;
}
return (bank * 0x2000) + (addr & 0x1fff);
}
auto addrCHR(uint addr) const -> uint {
uint bank = chrBank[addr / 0x0400];
return (bank * 0x0400) + (addr & 0x03ff);
}
auto addrCIRAM(uint addr) const -> uint {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
}
throw;
}
auto writeIO(uint addr, uint8 data) -> void {
switch(addr) {
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
prgBank[0] = data & 0x1f;
break;
case 0x9000: case 0x9001:
mirror = data & 0x03;
break;
case 0x9002: case 0x9003:
prgMode = data & 0x02;
break;
case 0xa000: case 0xa001: case 0xa002: case 0xa003:
prgBank[1] = data & 0x1f;
break;
case 0xb000: chrBank[0] = (chrBank[0] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xb001: chrBank[0] = (chrBank[0] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xb002: chrBank[1] = (chrBank[1] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xb003: chrBank[1] = (chrBank[1] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xc000: chrBank[2] = (chrBank[2] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xc001: chrBank[2] = (chrBank[2] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xc002: chrBank[3] = (chrBank[3] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xc003: chrBank[3] = (chrBank[3] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xd000: chrBank[4] = (chrBank[4] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xd001: chrBank[4] = (chrBank[4] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xd002: chrBank[5] = (chrBank[5] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xd003: chrBank[5] = (chrBank[5] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xe000: chrBank[6] = (chrBank[6] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xe001: chrBank[6] = (chrBank[6] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xe002: chrBank[7] = (chrBank[7] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xe003: chrBank[7] = (chrBank[7] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xf000:
irqLatch = (irqLatch & 0xf0) | ((data & 0x0f) << 0);
break;
case 0xf001:
irqLatch = (irqLatch & 0x0f) | ((data & 0x0f) << 4);
break;
case 0xf002:
irqMode = data & 0x04;
irqEnable = data & 0x02;
irqAcknowledge = data & 0x01;
if(irqEnable) {
irqCounter = irqLatch;
irqScalar = 341;
}
irqLine = 0;
break;
case 0xf003:
irqEnable = irqAcknowledge;
irqLine = 0;
break;
}
}
auto power() -> void {
}
auto reset() -> void {
prgMode = 0;
for(auto& n : prgBank) n = 0;
mirror = 0;
for(auto& n : chrBank) n = 0;
irqLatch = 0;
irqMode = 0;
irqEnable = 0;
irqAcknowledge = 0;
irqCounter = 0;
irqScalar = 0;
irqLine = 0;
}
auto serialize(serializer& s) -> void {
s.integer(prgMode);
for(auto& n : prgBank) s.integer(n);
s.integer(mirror);
for(auto& n : chrBank) s.integer(n);
s.integer(irqLatch);
s.integer(irqMode);
s.integer(irqEnable);
s.integer(irqAcknowledge);
s.integer(irqCounter);
s.integer(irqScalar);
s.integer(irqLine);
}
bool prgMode;
uint5 prgBank[2];
uint2 mirror;
uint8 chrBank[8];
uint8 irqLatch;
bool irqMode;
bool irqEnable;
bool irqAcknowledge;
uint8 irqCounter;
int irqScalar;
bool irqLine;
};
-313
View File
@@ -1,313 +0,0 @@
struct VRC6 : Chip {
VRC6(Board& board) : Chip(board) {
}
struct Pulse {
auto clock() -> void {
if(--divider == 0) {
divider = frequency + 1;
cycle++;
output = (mode == 1 || cycle > duty) ? volume : (uint4)0;
}
if(enable == false) output = 0;
}
auto serialize(serializer& s) -> void {
s.integer(mode);
s.integer(duty);
s.integer(volume);
s.integer(enable);
s.integer(frequency);
s.integer(divider);
s.integer(cycle);
s.integer(output);
}
bool mode;
uint3 duty;
uint4 volume;
bool enable;
uint12 frequency;
uint12 divider;
uint4 cycle;
uint4 output;
} pulse1, pulse2;
struct Sawtooth {
auto clock() -> void {
if(--divider == 0) {
divider = frequency + 1;
if(++phase == 0) {
accumulator += rate;
if(++stage == 7) {
stage = 0;
accumulator = 0;
}
}
}
output = accumulator >> 3;
if(enable == false) output = 0;
}
auto serialize(serializer& s) -> void {
s.integer(rate);
s.integer(enable);
s.integer(frequency);
s.integer(divider);
s.integer(phase);
s.integer(stage);
s.integer(accumulator);
s.integer(output);
}
uint6 rate;
bool enable;
uint12 frequency;
uint12 divider;
uint1 phase;
uint3 stage;
uint8 accumulator;
uint5 output;
} sawtooth;
auto main() -> void {
if(irqEnable) {
if(irqMode == 0) {
irqScalar -= 3;
if(irqScalar <= 0) {
irqScalar += 341;
if(irqCounter == 0xff) {
irqCounter = irqLatch;
irqLine = 1;
} else {
irqCounter++;
}
}
}
if(irqMode == 1) {
if(irqCounter == 0xff) {
irqCounter = irqLatch;
irqLine = 1;
} else {
irqCounter++;
}
}
}
cpu.irqLine(irqLine);
pulse1.clock();
pulse2.clock();
sawtooth.clock();
int output = (pulse1.output + pulse2.output + sawtooth.output) << 7;
apu.setSample(-output);
tick();
}
auto addrPRG(uint addr) const -> uint {
if((addr & 0xc000) == 0x8000) return (prgBank[0] << 14) | (addr & 0x3fff);
if((addr & 0xe000) == 0xc000) return (prgBank[1] << 13) | (addr & 0x1fff);
if((addr & 0xe000) == 0xe000) return ( 0xff << 13) | (addr & 0x1fff);
}
auto addrCHR(uint addr) const -> uint {
uint bank = chrBank[(addr >> 10) & 7];
return (bank << 10) | (addr & 0x03ff);
}
auto addrCIRAM(uint addr) const -> uint {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
}
}
auto readRAM(uint addr) -> uint8 {
return board.prgram.data[addr & 0x1fff];
}
auto writeRAM(uint addr, uint8 data) -> void {
board.prgram.data[addr & 0x1fff] = data;
}
auto writeIO(uint addr, uint8 data) -> void {
switch(addr) {
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
prgBank[0] = data;
break;
case 0x9000:
pulse1.mode = data & 0x80;
pulse1.duty = (data & 0x70) >> 4;
pulse1.volume = data & 0x0f;
break;
case 0x9001:
pulse1.frequency = (pulse1.frequency & 0x0f00) | ((data & 0xff) << 0);
break;
case 0x9002:
pulse1.frequency = (pulse1.frequency & 0x00ff) | ((data & 0x0f) << 8);
pulse1.enable = data & 0x80;
break;
case 0xa000:
pulse2.mode = data & 0x80;
pulse2.duty = (data & 0x70) >> 4;
pulse2.volume = data & 0x0f;
break;
case 0xa001:
pulse2.frequency = (pulse2.frequency & 0x0f00) | ((data & 0xff) << 0);
break;
case 0xa002:
pulse2.frequency = (pulse2.frequency & 0x00ff) | ((data & 0x0f) << 8);
pulse2.enable = data & 0x80;
break;
case 0xb000:
sawtooth.rate = data & 0x3f;
break;
case 0xb001:
sawtooth.frequency = (sawtooth.frequency & 0x0f00) | ((data & 0xff) << 0);
break;
case 0xb002:
sawtooth.frequency = (sawtooth.frequency & 0x00ff) | ((data & 0x0f) << 8);
sawtooth.enable = data & 0x80;
break;
case 0xb003:
mirror = (data >> 2) & 3;
break;
case 0xc000: case 0xc001: case 0xc002: case 0xc003:
prgBank[1] = data;
break;
case 0xd000: case 0xd001: case 0xd002: case 0xd003:
chrBank[0 + (addr & 3)] = data;
break;
case 0xe000: case 0xe001: case 0xe002: case 0xe003:
chrBank[4 + (addr & 3)] = data;
break;
case 0xf000:
irqLatch = data;
break;
case 0xf001:
irqMode = data & 0x04;
irqEnable = data & 0x02;
irqAcknowledge = data & 0x01;
if(irqEnable) {
irqCounter = irqLatch;
irqScalar = 341;
}
irqLine = 0;
break;
case 0xf002:
irqEnable = irqAcknowledge;
irqLine = 0;
break;
}
}
auto power() -> void {
}
auto reset() -> void {
prgBank[0] = 0;
prgBank[1] = 0;
chrBank[0] = 0;
chrBank[1] = 0;
chrBank[2] = 0;
chrBank[3] = 0;
chrBank[4] = 0;
chrBank[5] = 0;
chrBank[6] = 0;
chrBank[7] = 0;
mirror = 0;
irqLatch = 0;
irqMode = 0;
irqEnable = 0;
irqAcknowledge = 0;
irqCounter = 0;
irqScalar = 0;
irqLine = 0;
pulse1.mode = 0;
pulse1.duty = 0;
pulse1.volume = 0;
pulse1.enable = 0;
pulse1.frequency = 0;
pulse1.divider = 1;
pulse1.cycle = 0;
pulse1.output = 0;
pulse2.mode = 0;
pulse2.duty = 0;
pulse2.volume = 0;
pulse2.enable = 0;
pulse2.frequency = 0;
pulse2.divider = 1;
pulse2.cycle = 0;
pulse2.output = 0;
sawtooth.rate = 0;
sawtooth.enable = 0;
sawtooth.frequency = 0;
sawtooth.divider = 1;
sawtooth.phase = 0;
sawtooth.stage = 0;
sawtooth.accumulator = 0;
sawtooth.output = 0;
}
auto serialize(serializer& s) -> void {
pulse1.serialize(s);
pulse2.serialize(s);
sawtooth.serialize(s);
s.array(prgBank);
s.array(chrBank);
s.integer(mirror);
s.integer(irqLatch);
s.integer(irqMode);
s.integer(irqEnable);
s.integer(irqAcknowledge);
s.integer(irqCounter);
s.integer(irqScalar);
s.integer(irqLine);
}
uint8 prgBank[2];
uint8 chrBank[8];
uint2 mirror;
uint8 irqLatch;
bool irqMode;
bool irqEnable;
bool irqAcknowledge;
uint8 irqCounter;
int irqScalar;
bool irqLine;
};
-146
View File
@@ -1,146 +0,0 @@
//Konami VRC7
//Yamaha YM2413 OPLL audio - not emulated
struct VRC7 : Chip {
VRC7(Board& board) : Chip(board) {
}
auto main() -> void {
if(irqEnable) {
if(irqMode == 0) {
irqScalar -= 3;
if(irqScalar <= 0) {
irqScalar += 341;
if(irqCounter == 0xff) {
irqCounter = irqLatch;
irqLine = 1;
} else {
irqCounter++;
}
}
}
if(irqMode == 1) {
if(irqCounter == 0xff) {
irqCounter = irqLatch;
irqLine = 1;
} else {
irqCounter++;
}
}
}
cpu.irqLine(irqLine);
tick();
}
auto writeIO(uint addr, uint8 data) -> void {
switch(addr) {
case 0x8000: prgBank[0] = data; break;
case 0x8010: prgBank[1] = data; break;
case 0x9000: prgBank[2] = data; break;
case 0x9010: break; //APU addr port
case 0x9030: break; //APU data port
case 0xa000: chrBank[0] = data; break;
case 0xa010: chrBank[1] = data; break;
case 0xb000: chrBank[2] = data; break;
case 0xb010: chrBank[3] = data; break;
case 0xc000: chrBank[4] = data; break;
case 0xc010: chrBank[5] = data; break;
case 0xd000: chrBank[6] = data; break;
case 0xd010: chrBank[7] = data; break;
case 0xe000: mirror = data & 0x03; break;
case 0xe010:
irqLatch = data;
break;
case 0xf000:
irqMode = data & 0x04;
irqEnable = data & 0x02;
irqAcknowledge = data & 0x01;
if(irqEnable) {
irqCounter = irqLatch;
irqScalar = 341;
}
irqLine = 0;
break;
case 0xf010:
irqEnable = irqAcknowledge;
irqLine = 0;
break;
}
}
auto addrPRG(uint addr) const -> uint {
uint bank = 0;
switch(addr & 0xe000) {
case 0x8000: bank = prgBank[0]; break;
case 0xa000: bank = prgBank[1]; break;
case 0xc000: bank = prgBank[2]; break;
case 0xe000: bank = 0xff; break;
}
return (bank * 0x2000) + (addr & 0x1fff);
}
auto addrCHR(uint addr) const -> uint {
uint bank = chrBank[addr / 0x0400];
return (bank * 0x0400) + (addr & 0x03ff);
}
auto addrCIRAM(uint addr) const -> uint {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
}
}
auto power() -> void {
}
auto reset() -> void {
for(auto& n : prgBank) n = 0;
for(auto& n : chrBank) n = 0;
mirror = 0;
irqLatch = 0;
irqMode = 0;
irqEnable = 0;
irqAcknowledge = 0;
irqCounter = 0;
irqScalar = 0;
irqLine = 0;
}
auto serialize(serializer& s) -> void {
s.array(prgBank);
s.array(chrBank);
s.integer(mirror);
s.integer(irqLatch);
s.integer(irqMode);
s.integer(irqEnable);
s.integer(irqAcknowledge);
s.integer(irqCounter);
s.integer(irqScalar);
s.integer(irqLine);
}
uint8 prgBank[3];
uint8 chrBank[8];
uint2 mirror;
uint8 irqLatch;
bool irqMode;
bool irqEnable;
bool irqAcknowledge;
uint8 irqCounter;
int irqScalar;
bool irqLine;
};
-28
View File
@@ -1,28 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
Cheat cheat;
auto Cheat::reset() -> void {
codes.reset();
}
auto Cheat::append(uint addr, uint data) -> void {
codes.append({addr, Unused, data});
}
auto Cheat::append(uint addr, uint comp, uint data) -> void {
codes.append({addr, comp, data});
}
auto Cheat::find(uint addr, uint comp) -> maybe<uint> {
for(auto& code : codes) {
if(code.addr == addr && (code.comp == Unused || code.comp == comp)) {
return code.data;
}
}
return nothing;
}
}
-17
View File
@@ -1,17 +0,0 @@
struct Cheat {
struct Code {
uint addr;
uint comp;
uint data;
};
vector<Code> codes;
enum : uint { Unused = ~0u };
alwaysinline auto enable() const -> bool { return codes.size() > 0; }
auto reset() -> void;
auto append(uint addr, uint data) -> void;
auto append(uint addr, uint comp, uint data) -> void;
auto find(uint addr, uint comp) -> maybe<uint>;
};
extern Cheat cheat;
-26
View File
@@ -1,26 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
#include "gamepad/gamepad.cpp"
Controller::Controller(bool port) : port(port) {
if(!thread) create(Controller::Enter, 1);
}
Controller::~Controller() {
}
auto Controller::Enter() -> void {
while(true) {
scheduler.synchronize();
if(co_active() == peripherals.controllerPort1->thread) peripherals.controllerPort1->main();
if(co_active() == peripherals.controllerPort2->thread) peripherals.controllerPort2->main();
}
}
auto Controller::main() -> void {
step(1);
}
}
-33
View File
@@ -1,33 +0,0 @@
//Famicom controller port pinout:
// ____
// | \
// |(7) \
// |(2)(1)|
// |(3)(5)|
// |(4)(6)|
// |______|
//
// pin name port1 port2
// 1: +5v
// 2: clock $4016 read $4016.d0 write
// 3: latch $4016.d0 write $4016.d0 write
// 4: data0 $4016.d0 read $4017.d0 read
// 5: data3 $4016.d3 read $4017.d3 read
// 6: data4 $4016.d4 read $4017.d4 read
// 7: gnd
struct Controller : Cothread {
enum : bool { Port1 = 0, Port2 = 1 };
Controller(bool port);
virtual ~Controller();
static auto Enter() -> void;
virtual auto main() -> void;
virtual auto data() -> uint3 { return 0; }
virtual auto latch(bool data) -> void {}
const bool port;
};
#include "gamepad/gamepad.hpp"
-36
View File
@@ -1,36 +0,0 @@
Gamepad::Gamepad(bool port) : Controller(port) {
}
auto Gamepad::data() -> uint3 {
if(counter >= 8) return 1;
if(latched == 1) return interface->inputPoll(port, ID::Device::Gamepad, A);
switch(counter++) {
case 0: return a;
case 1: return b;
case 2: return select;
case 3: return start;
case 4: return up && !down;
case 5: return down && !up;
case 6: return left && !right;
case 7: return right && !left;
}
unreachable;
}
auto Gamepad::latch(bool data) -> void {
if(latched == data) return;
latched = data;
counter = 0;
if(latched == 0) {
a = interface->inputPoll(port, ID::Device::Gamepad, A);
b = interface->inputPoll(port, ID::Device::Gamepad, B);
select = interface->inputPoll(port, ID::Device::Gamepad, Select);
start = interface->inputPoll(port, ID::Device::Gamepad, Start);
up = interface->inputPoll(port, ID::Device::Gamepad, Up);
down = interface->inputPoll(port, ID::Device::Gamepad, Down);
left = interface->inputPoll(port, ID::Device::Gamepad, Left);
right = interface->inputPoll(port, ID::Device::Gamepad, Right);
}
}
-22
View File
@@ -1,22 +0,0 @@
struct Gamepad : Controller {
enum : uint {
Up, Down, Left, Right, B, A, Select, Start,
};
Gamepad(bool port);
auto data() -> uint3;
auto latch(bool data) -> void;
private:
bool latched = 0;
uint counter = 0;
bool a = 0;
bool b = 0;
bool select = 0;
bool start = 0;
bool up = 0;
bool down = 0;
bool left = 0;
bool right = 0;
};
-56
View File
@@ -1,56 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
#include "memory.cpp"
#include "timing.cpp"
#include "serialization.cpp"
CPU cpu;
auto CPU::Enter() -> void {
while(true) scheduler.synchronize(), cpu.main();
}
auto CPU::main() -> void {
if(io.interruptPending) return interrupt();
instruction();
}
auto CPU::step(uint clocks) -> void {
apu.clock -= clocks;
if(apu.clock < 0) co_switch(apu.thread);
ppu.clock -= clocks;
if(ppu.clock < 0) co_switch(ppu.thread);
cartridge.clock -= clocks;
if(cartridge.clock < 0) co_switch(cartridge.thread);
for(auto peripheral : peripherals) {
peripheral->clock -= clocks * (uint64)peripheral->frequency;
if(peripheral->clock < 0) co_switch(peripheral->thread);
}
}
auto CPU::power() -> void {
R6502::power();
for(auto addr : range(0x0800)) ram[addr] = 0xff;
ram[0x0008] = 0xf7;
ram[0x0009] = 0xef;
ram[0x000a] = 0xdf;
ram[0x000f] = 0xbf;
}
auto CPU::reset() -> void {
R6502::reset();
create(CPU::Enter, 21'477'272);
regs.pc = bus.read(0xfffc) << 0;
regs.pc |= bus.read(0xfffd) << 8;
memory::fill(&io, sizeof(IO));
io.rdyLine = 1;
}
}
-56
View File
@@ -1,56 +0,0 @@
struct CPU : Processor::R6502, Thread {
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void;
auto power() -> void;
auto reset() -> void;
//memory.cpp
auto readRAM(uint11 addr) -> uint8;
auto writeRAM(uint11 addr, uint8 data) -> void;
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
auto readDebugger(uint16 addr) -> uint8 override;
auto serialize(serializer&) -> void;
//timing.cpp
auto read(uint16 addr) -> uint8 override;
auto write(uint16 addr, uint8 data) -> void override;
auto lastCycle() -> void override;
auto nmi(uint16& vector) -> void override;
auto oamdma() -> void;
auto nmiLine(bool) -> void;
auto irqLine(bool) -> void;
auto apuLine(bool) -> void;
auto rdyLine(bool) -> void;
auto rdyAddr(bool valid, uint16 value = 0) -> void;
//protected:
vector<Thread*> peripherals;
uint8 ram[0x0800];
struct IO {
bool interruptPending;
bool nmiPending;
bool nmiLine;
bool irqLine;
bool apuLine;
bool rdyLine;
bool rdyAddrValid;
uint16 rdyAddrValue;
bool oamdmaPending;
uint8 oamdmaPage;
} io;
};
extern CPU cpu;
-49
View File
@@ -1,49 +0,0 @@
auto CPU::readRAM(uint11 addr) -> uint8 {
return ram[addr];
}
auto CPU::writeRAM(uint11 addr, uint8 data) -> void {
ram[addr] = data;
}
auto CPU::readIO(uint16 addr) -> uint8 {
switch(addr) {
case 0x4016: {
auto data = Famicom::peripherals.controllerPort1->data();
return (mdr() & 0xc0) | data.bit(2) << 4 | data.bit(1) << 3 | data.bit(0) << 0;
}
case 0x4017: {
auto data = Famicom::peripherals.controllerPort2->data();
return (mdr() & 0xc0) | data.bit(2) << 4 | data.bit(1) << 3 | data.bit(0) << 0;
}
}
return apu.readIO(addr);
}
auto CPU::writeIO(uint16 addr, uint8 data) -> void {
switch(addr) {
case 0x4014: {
io.oamdmaPage = data;
io.oamdmaPending = true;
return;
}
case 0x4016: {
Famicom::peripherals.controllerPort1->latch(data.bit(0));
Famicom::peripherals.controllerPort2->latch(data.bit(0));
return;
}
}
return apu.writeIO(addr, data);
}
auto CPU::readDebugger(uint16 addr) -> uint8 {
return bus.read(addr);
}
-19
View File
@@ -1,19 +0,0 @@
auto CPU::serialize(serializer& s) -> void {
R6502::serialize(s);
Thread::serialize(s);
s.array(ram);
s.integer(io.interruptPending);
s.integer(io.nmiPending);
s.integer(io.nmiLine);
s.integer(io.irqLine);
s.integer(io.apuLine);
s.integer(io.rdyLine);
s.integer(io.rdyAddrValid);
s.integer(io.rdyAddrValue);
s.integer(io.oamdmaPending);
s.integer(io.oamdmaPage);
}
-64
View File
@@ -1,64 +0,0 @@
auto CPU::read(uint16 addr) -> uint8 {
if(io.oamdmaPending) {
io.oamdmaPending = false;
read(addr);
oamdma();
}
while(io.rdyLine == 0) {
regs.mdr = bus.read(io.rdyAddrValid ? io.rdyAddrValue : addr);
step(12);
}
regs.mdr = bus.read(addr);
step(12);
return regs.mdr;
}
auto CPU::write(uint16 addr, uint8 data) -> void {
bus.write(addr, regs.mdr = data);
step(12);
}
auto CPU::lastCycle() -> void {
io.interruptPending = ((io.irqLine | io.apuLine) & ~regs.p.i) | io.nmiPending;
}
auto CPU::nmi(uint16& vector) -> void {
if(io.nmiPending) {
io.nmiPending = false;
vector = 0xfffa;
}
}
auto CPU::oamdma() -> void {
for(uint n : range(256)) {
uint8 data = read(io.oamdmaPage << 8 | n);
write(0x2004, data);
}
}
auto CPU::nmiLine(bool line) -> void {
//edge-sensitive (0->1)
if(!io.nmiLine && line) io.nmiPending = true;
io.nmiLine = line;
}
auto CPU::irqLine(bool line) -> void {
//level-sensitive
io.irqLine = line;
}
auto CPU::apuLine(bool line) -> void {
//level-sensitive
io.apuLine = line;
}
auto CPU::rdyLine(bool line) -> void {
io.rdyLine = line;
}
auto CPU::rdyAddr(bool valid, uint16 value) -> void {
io.rdyAddrValid = valid;
io.rdyAddrValue = value;
}
-59
View File
@@ -1,59 +0,0 @@
#pragma once
//license: GPLv3
//started: 2011-09-05
#include <emulator/emulator.hpp>
#include <processor/r6502/r6502.hpp>
namespace Famicom {
using File = Emulator::File;
struct Thread {
~Thread() {
if(thread) co_delete(thread);
}
auto create(auto (*entrypoint)() -> void, uint frequency) -> void {
if(thread) co_delete(thread);
thread = co_create(65'536 * sizeof(void*), entrypoint);
this->frequency = frequency;
clock = 0;
}
auto serialize(serializer& s) -> void {
s.integer(frequency);
s.integer(clock);
}
cothread_t thread = nullptr;
uint frequency = 0;
int64 clock = 0;
};
struct Cothread : Thread {
auto step(uint clocks) -> void;
auto synchronizeCPU() -> void;
};
#include <fc/scheduler/scheduler.hpp>
#include <fc/controller/controller.hpp>
#include <fc/system/system.hpp>
#include <fc/memory/memory.hpp>
#include <fc/cartridge/cartridge.hpp>
#include <fc/cpu/cpu.hpp>
#include <fc/apu/apu.hpp>
#include <fc/ppu/ppu.hpp>
#include <fc/cheat/cheat.hpp>
inline auto Cothread::step(uint clocks) -> void {
clock += clocks * (uint64)cpu.frequency;
synchronizeCPU();
}
inline auto Cothread::synchronizeCPU() -> void {
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
}
}
#include <fc/interface/interface.hpp>
-200
View File
@@ -1,200 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
Interface* interface = nullptr;
Settings settings;
Interface::Interface() {
interface = this;
information.manufacturer = "Nintendo";
information.name = "Famicom";
information.width = 256;
information.height = 240;
information.overscan = true;
information.aspectRatio = 8.0 / 7.0;
information.resettable = true;
information.capability.states = true;
information.capability.cheats = true;
media.append({ID::Famicom, "Famicom", "fc"});
Port controllerPort1{ID::Port::Controller1, "Controller Port 1"};
Port controllerPort2{ID::Port::Controller2, "Controller Port 2"};
{ Device device{ID::Device::None, "None"};
controllerPort1.devices.append(device);
controllerPort2.devices.append(device);
}
{ Device device{ID::Device::Gamepad, "Gamepad"};
device.inputs.append({0, "Up" });
device.inputs.append({0, "Down" });
device.inputs.append({0, "Left" });
device.inputs.append({0, "Right" });
device.inputs.append({0, "B" });
device.inputs.append({0, "A" });
device.inputs.append({0, "Select"});
device.inputs.append({0, "Start" });
controllerPort1.devices.append(device);
controllerPort2.devices.append(device);
}
ports.append(move(controllerPort1));
ports.append(move(controllerPort2));
}
auto Interface::manifest() -> string {
return cartridge.manifest();
}
auto Interface::title() -> string {
return cartridge.title();
}
auto Interface::videoFrequency() -> double {
return 21477272.0 / (262.0 * 1364.0 - 4.0);
}
auto Interface::videoColors() -> uint32 {
return 1 << 9;
}
auto Interface::videoColor(uint32 n) -> uint64 {
double saturation = 2.0;
double hue = 0.0;
double contrast = 1.0;
double brightness = 1.0;
double gamma = settings.colorEmulation ? 1.8 : 2.2;
int color = (n & 0x0f), level = color < 0xe ? (n >> 4) & 3 : 1;
static const double black = 0.518, white = 1.962, attenuation = 0.746;
static const double levels[8] = {
0.350, 0.518, 0.962, 1.550,
1.094, 1.506, 1.962, 1.962,
};
double lo_and_hi[2] = {
levels[level + 4 * (color == 0x0)],
levels[level + 4 * (color < 0xd)],
};
double y = 0.0, i = 0.0, q = 0.0;
auto wave = [](int p, int color) { return (color + p + 8) % 12 < 6; };
for(int p : range(12)) {
double spot = lo_and_hi[wave(p, color)];
if(((n & 0x040) && wave(p, 12))
|| ((n & 0x080) && wave(p, 4))
|| ((n & 0x100) && wave(p, 8))
) spot *= attenuation;
double v = (spot - black) / (white - black);
v = (v - 0.5) * contrast + 0.5;
v *= brightness / 12.0;
y += v;
i += v * cos((Math::Pi / 6.0) * (p + hue));
q += v * sin((Math::Pi / 6.0) * (p + hue));
}
i *= saturation;
q *= saturation;
auto gammaAdjust = [=](double f) { return f < 0.0 ? 0.0 : pow(f, 2.2 / gamma); };
uint64 r = uclamp<16>(65535.0 * gammaAdjust(y + 0.946882 * i + 0.623557 * q));
uint64 g = uclamp<16>(65535.0 * gammaAdjust(y + -0.274788 * i + -0.635691 * q));
uint64 b = uclamp<16>(65535.0 * gammaAdjust(y + -1.108545 * i + 1.709007 * q));
return r << 32 | g << 16 | b << 0;
}
auto Interface::audioFrequency() -> double {
return 21477272.0 / 12.0;
}
auto Interface::loaded() -> bool {
return system.loaded();
}
auto Interface::sha256() -> string {
return cartridge.sha256();
}
auto Interface::load(uint id) -> bool {
return system.load();
}
auto Interface::save() -> void {
system.save();
}
auto Interface::unload() -> void {
save();
system.unload();
}
auto Interface::connect(uint port, uint device) -> void {
Famicom::peripherals.connect(port, device);
}
auto Interface::power() -> void {
system.power();
}
auto Interface::reset() -> void {
system.reset();
}
auto Interface::run() -> void {
system.run();
}
auto Interface::serialize() -> serializer {
system.runToSave();
return system.serialize();
}
auto Interface::unserialize(serializer& s) -> bool {
return system.unserialize(s);
}
auto Interface::cheatSet(const string_vector& list) -> void {
cheat.reset();
for(auto& codeset : list) {
auto codes = codeset.split("+");
for(auto& code : codes) {
auto part = code.split("/");
if(part.size() == 2) cheat.append(part[0].hex(), part[1].hex());
if(part.size() == 3) cheat.append(part[0].hex(), part[1].hex(), part[2].hex());
}
}
}
auto Interface::cap(const string& name) -> bool {
if(name == "Color Emulation") return true;
if(name == "Scanline Emulation") return true;
return false;
}
auto Interface::get(const string& name) -> any {
if(name == "Color Emulation") return settings.colorEmulation;
if(name == "Scanline Emulation") return settings.scanlineEmulation;
return {};
}
auto Interface::set(const string& name, const any& value) -> bool {
if(name == "Color Emulation" && value.is<bool>()) {
settings.colorEmulation = value.get<bool>();
system.configureVideoPalette();
return true;
}
if(name == "Scanline Emulation" && value.is<bool>()) return settings.scanlineEmulation = value.get<bool>(), true;
return false;
}
}
-66
View File
@@ -1,66 +0,0 @@
namespace Famicom {
struct ID {
enum : uint {
System,
Famicom,
};
struct Port { enum : uint {
Controller1,
Controller2,
Expansion,
};};
struct Device { enum : uint {
None,
Gamepad,
};};
};
struct Interface : Emulator::Interface {
using Emulator::Interface::load;
Interface();
auto manifest() -> string override;
auto title() -> string override;
auto videoFrequency() -> double override;
auto videoColors() -> uint32 override;
auto videoColor(uint32 color) -> uint64 override;
auto audioFrequency() -> double override;
auto loaded() -> bool override;
auto sha256() -> string override;
auto load(uint id) -> bool override;
auto save() -> void override;
auto unload() -> void override;
auto connect(uint port, uint device) -> void override;
auto power() -> void override;
auto reset() -> void override;
auto run() -> void override;
auto serialize() -> serializer override;
auto unserialize(serializer&) -> bool override;
auto cheatSet(const string_vector&) -> void override;
auto cap(const string& name) -> bool override;
auto get(const string& name) -> any override;
auto set(const string& name, const any& value) -> bool override;
};
struct Settings {
bool colorEmulation = true;
bool scanlineEmulation = true;
uint controllerPort1 = 0;
uint controllerPort2 = 0;
uint expansionPort = 0;
};
extern Interface* interface;
extern Settings settings;
}
-34
View File
@@ -1,34 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
Bus bus;
//$0000-07ff = RAM (2KB)
//$0800-1fff = RAM (mirror)
//$2000-2007 = PPU
//$2008-3fff = PPU (mirror)
//$4000-4017 = APU + I/O
//$4018-ffff = Cartridge
auto Bus::read(uint16 addr) -> uint8 {
uint8 data = cartridge.readPRG(addr);
if(addr <= 0x1fff) data = cpu.readRAM(addr);
else if(addr <= 0x3fff) data = ppu.readIO(addr);
else if(addr <= 0x4017) data = cpu.readIO(addr);
if(cheat.enable()) {
if(auto result = cheat.find(addr, data)) return result();
}
return data;
}
auto Bus::write(uint16 addr, uint8 data) -> void {
cartridge.writePRG(addr, data);
if(addr <= 0x1fff) return cpu.writeRAM(addr, data);
if(addr <= 0x3fff) return ppu.writeIO(addr, data);
if(addr <= 0x4017) return cpu.writeIO(addr, data);
}
}
-6
View File
@@ -1,6 +0,0 @@
struct Bus {
auto read(uint16 addr) -> uint8;
auto write(uint16 addr, uint8 data) -> void;
};
extern Bus bus;
-144
View File
@@ -1,144 +0,0 @@
auto PPU::readCIRAM(uint11 addr) -> uint8 {
return ciram[addr];
}
auto PPU::writeCIRAM(uint11 addr, uint8 data) -> void {
ciram[addr] = data;
}
auto PPU::readCGRAM(uint5 addr) -> uint8 {
if((addr & 0x13) == 0x10) addr &= ~0x10;
uint8 data = cgram[addr];
if(io.grayscale) data &= 0x30;
return data;
}
auto PPU::writeCGRAM(uint5 addr, uint8 data) -> void {
if((addr & 0x13) == 0x10) addr &= ~0x10;
cgram[addr] = data;
}
auto PPU::readIO(uint16 addr) -> uint8 {
uint8 result = 0x00;
switch(addr.bits(0,2)) {
//PPUSTATUS
case 2:
result |= io.mdr.bits(0,4);
result |= io.spriteOverflow << 5;
result |= io.spriteZeroHit << 6;
result |= io.nmiFlag << 7;
io.v.latch = 0;
io.nmiHold = 0;
cpu.nmiLine(io.nmiFlag = 0);
break;
//OAMDATA
case 4:
result = oam[io.oamAddress];
break;
//PPUDATA
case 7:
if(enable() && (io.ly <= 240 || io.ly == 261)) return 0x00;
addr = (uint14)io.v.address;
if(addr <= 0x1fff) {
result = io.busData;
io.busData = cartridge.readCHR(addr);
} else if(addr <= 0x3eff) {
result = io.busData;
io.busData = cartridge.readCHR(addr);
} else if(addr <= 0x3fff) {
result = readCGRAM(addr);
io.busData = cartridge.readCHR(addr);
}
io.v.address += io.vramIncrement;
break;
}
return result;
}
auto PPU::writeIO(uint16 addr, uint8 data) -> void {
io.mdr = data;
switch(addr.bits(0,2)) {
//PPUCTRL
case 0:
io.t.nametable = data.bits(0,1);
io.vramIncrement = data.bit (2) ? 32 : 1;
io.spriteAddress = data.bit (3) ? 0x1000 : 0x0000;
io.bgAddress = data.bit (4) ? 0x1000 : 0x0000;
io.spriteHeight = data.bit (5) ? 16 : 8;
io.masterSelect = data.bit (6);
io.nmiEnable = data.bit (7);
cpu.nmiLine(io.nmiEnable && io.nmiHold && io.nmiFlag);
break;
//PPUMASK
case 1:
io.grayscale = data.bit (0);
io.bgEdgeEnable = data.bit (1);
io.spriteEdgeEnable = data.bit (2);
io.bgEnable = data.bit (3);
io.spriteEnable = data.bit (4);
io.emphasis = data.bits(5,7);
break;
//PPUSTATUS
case 2:
break;
//OAMADDR
case 3:
io.oamAddress = data;
break;
//OAMDATA
case 4:
if(io.oamAddress.bits(0,1) == 2) data.bits(2,4) = 0; //clear non-existent bits (always read back as 0)
oam[io.oamAddress++] = data;
break;
//PPUSCROLL
case 5:
if(io.v.latch++ == 0) {
io.v.fineX = data.bits(0,2);
io.t.tileX = data.bits(3,7);
} else {
io.t.fineY = data.bits(0,2);
io.t.tileY = data.bits(3,7);
}
break;
//PPUADDR
case 6:
if(io.v.latch++ == 0) {
io.t.addressHi = data.bits(0,5);
} else {
io.t.addressLo = data.bits(0,7);
io.v.address = io.t.address;
}
break;
//PPUDATA
case 7:
if(enable() && (io.ly <= 240 || io.ly == 261)) return;
addr = (uint14)io.v.address;
if(addr <= 0x1fff) {
cartridge.writeCHR(addr, data);
} else if(addr <= 0x3eff) {
cartridge.writeCHR(addr, data);
} else if(addr <= 0x3fff) {
writeCGRAM(addr, data);
}
io.v.address += io.vramIncrement;
break;
}
}
-72
View File
@@ -1,72 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
PPU ppu;
#include "memory.cpp"
#include "render.cpp"
#include "serialization.cpp"
auto PPU::Enter() -> void {
while(true) scheduler.synchronize(), ppu.main();
}
auto PPU::main() -> void {
renderScanline();
}
auto PPU::step(uint clocks) -> void {
while(clocks--) {
if(io.ly == 240 && io.lx == 340) io.nmiHold = 1;
if(io.ly == 241 && io.lx == 0) io.nmiFlag = io.nmiHold;
if(io.ly == 241 && io.lx == 2) cpu.nmiLine(io.nmiEnable && io.nmiFlag);
if(io.ly == 260 && io.lx == 340) io.spriteZeroHit = 0, io.spriteOverflow = 0;
if(io.ly == 260 && io.lx == 340) io.nmiHold = 0;
if(io.ly == 261 && io.lx == 0) io.nmiFlag = io.nmiHold;
if(io.ly == 261 && io.lx == 2) cpu.nmiLine(io.nmiEnable && io.nmiFlag);
clock += 4;
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
io.lx++;
}
}
auto PPU::scanline() -> void {
io.lx = 0;
if(++io.ly == 262) {
io.ly = 0;
frame();
}
cartridge.scanline(io.ly);
}
auto PPU::frame() -> void {
io.field++;
scheduler.exit(Scheduler::Event::Frame);
}
auto PPU::refresh() -> void {
Emulator::video.refresh(buffer, 256 * sizeof(uint32), 256, 240);
}
auto PPU::power() -> void {
}
auto PPU::reset() -> void {
create(PPU::Enter, 21'477'272);
memory::fill(&io, sizeof(IO));
memory::fill(&latch, sizeof(Latches));
io.vramIncrement = 1;
for(auto& n : ciram ) n = 0;
for(auto& n : cgram ) n = 0;
for(auto& n : oam ) n = 0;
for(auto& n : buffer) n = 0;
}
}
-120
View File
@@ -1,120 +0,0 @@
struct PPU : Thread {
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void;
auto scanline() -> void;
auto frame() -> void;
auto refresh() -> void;
auto power() -> void;
auto reset() -> void;
//memory.cpp
auto readCIRAM(uint11 addr) -> uint8;
auto writeCIRAM(uint11 addr, uint8 data) -> void;
auto readCGRAM(uint5 addr) -> uint8;
auto writeCGRAM(uint5 addr, uint8 data) -> void;
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
//render.cpp
auto enable() const -> bool;
auto loadCHR(uint16 addr) -> uint8;
auto renderPixel() -> void;
auto renderSprite() -> void;
auto renderScanline() -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
struct IO {
//internal
uint8 mdr;
uint1 field;
uint lx;
uint ly;
uint8 busData;
union {
uint value;
NaturalBitField<uint, 0, 4> tileX;
NaturalBitField<uint, 5, 9> tileY;
NaturalBitField<uint,10,11> nametable;
NaturalBitField<uint,10,10> nametableX;
NaturalBitField<uint,11,11> nametableY;
NaturalBitField<uint,12,14> fineY;
NaturalBitField<uint, 0,14> address;
NaturalBitField<uint, 0, 7> addressLo;
NaturalBitField<uint, 8,14> addressHi;
NaturalBitField<uint,15,15> latch;
NaturalBitField<uint,16,18> fineX;
} v, t;
bool nmiHold;
bool nmiFlag;
//$2000
uint vramIncrement;
uint spriteAddress;
uint bgAddress;
uint spriteHeight;
bool masterSelect;
bool nmiEnable;
//$2001
bool grayscale;
bool bgEdgeEnable;
bool spriteEdgeEnable;
bool bgEnable;
bool spriteEnable;
uint3 emphasis;
//$2002
bool spriteOverflow;
bool spriteZeroHit;
//$2003
uint8 oamAddress;
} io;
struct OAM {
//serialization.cpp
auto serialize(serializer&) -> void;
uint8 id = 64;
uint8 y = 0xff;
uint8 tile = 0xff;
uint8 attr = 0xff;
uint8 x = 0xff;
uint8 tiledataLo = 0;
uint8 tiledataHi = 0;
};
struct Latches {
uint16 nametable;
uint16 attribute;
uint16 tiledataLo;
uint16 tiledataHi;
uint oamIterator;
uint oamCounter;
OAM oam[8]; //primary
OAM soam[8]; //secondary
} latch;
uint8 ciram[2048];
uint8 cgram[32];
uint8 oam[256];
uint32 buffer[256 * 262];
};
extern PPU ppu;
-211
View File
@@ -1,211 +0,0 @@
auto PPU::enable() const -> bool {
return io.bgEnable || io.spriteEnable;
}
auto PPU::loadCHR(uint16 addr) -> uint8 {
return enable() ? cartridge.readCHR(addr) : (uint8)0x00;
}
auto PPU::renderPixel() -> void {
uint32* output = buffer + io.ly * 256;
uint x = io.lx - 1;
uint mask = 0x8000 >> (io.v.fineX + (x & 7));
uint palette = 0;
uint objectPalette = 0;
bool objectPriority = 0;
palette |= latch.tiledataLo & mask ? 1 : 0;
palette |= latch.tiledataHi & mask ? 2 : 0;
if(palette) {
uint attr = latch.attribute;
if(mask >= 256) attr >>= 2;
palette |= (attr & 3) << 2;
}
if(!io.bgEnable) palette = 0;
if(!io.bgEdgeEnable && x < 8) palette = 0;
if(io.spriteEnable)
for(int sprite = 7; sprite >= 0; sprite--) {
if(!io.spriteEdgeEnable && x < 8) continue;
if(latch.oam[sprite].id == 64) continue;
uint spriteX = x - latch.oam[sprite].x;
if(spriteX >= 8) continue;
if(latch.oam[sprite].attr & 0x40) spriteX ^= 7;
uint mask = 0x80 >> spriteX;
uint spritePalette = 0;
spritePalette |= latch.oam[sprite].tiledataLo & mask ? 1 : 0;
spritePalette |= latch.oam[sprite].tiledataHi & mask ? 2 : 0;
if(spritePalette == 0) continue;
if(latch.oam[sprite].id == 0 && palette && x != 255) io.spriteZeroHit = 1;
spritePalette |= (latch.oam[sprite].attr & 3) << 2;
objectPriority = latch.oam[sprite].attr & 0x20;
objectPalette = 16 + spritePalette;
}
if(objectPalette) {
if(palette == 0 || objectPriority == 0) palette = objectPalette;
}
if(!enable()) palette = 0;
output[x] = io.emphasis << 6 | readCGRAM(palette);
}
auto PPU::renderSprite() -> void {
if(!enable()) return;
uint n = latch.oamIterator++;
int ly = io.ly == 261 ? -1 : io.ly;
uint y = ly - oam[n * 4 + 0];
if(y >= io.spriteHeight) return;
if(latch.oamCounter == 8) {
io.spriteOverflow = 1;
return;
}
auto& o = latch.soam[latch.oamCounter++];
o.id = n;
o.y = oam[n * 4 + 0];
o.tile = oam[n * 4 + 1];
o.attr = oam[n * 4 + 2];
o.x = oam[n * 4 + 3];
}
auto PPU::renderScanline() -> void {
//Vblank
if(io.ly >= 240 && io.ly <= 260) return step(341), scanline();
latch.oamIterator = 0;
latch.oamCounter = 0;
for(auto n : range(8)) latch.soam[n] = {};
// 0
step(1);
// 1-256
for(uint tile : range(32)) {
uint nametable = loadCHR(0x2000 | (uint12)io.v.address);
uint tileaddr = io.bgAddress | nametable << 4 | io.v.fineY;
renderPixel();
step(1);
renderPixel();
step(1);
uint attribute = loadCHR(0x23c0 | io.v.nametable << 10 | (io.v.tileY >> 2) << 3 | io.v.tileX >> 2);
if(io.v.tileY & 2) attribute >>= 4;
if(io.v.tileX & 2) attribute >>= 2;
renderPixel();
step(1);
if(enable() && ++io.v.tileX == 0) io.v.nametableX++;
if(enable() && tile == 31 && ++io.v.fineY == 0 && ++io.v.tileY == 30) io.v.nametableY++, io.v.tileY = 0;
renderPixel();
renderSprite();
step(1);
uint tiledataLo = loadCHR(tileaddr + 0);
renderPixel();
step(1);
renderPixel();
step(1);
uint tiledataHi = loadCHR(tileaddr + 8);
renderPixel();
step(1);
renderPixel();
renderSprite();
step(1);
latch.nametable = latch.nametable << 8 | nametable;
latch.attribute = latch.attribute << 2 | (attribute & 3);
latch.tiledataLo = latch.tiledataLo << 8 | tiledataLo;
latch.tiledataHi = latch.tiledataHi << 8 | tiledataHi;
}
for(auto n : range(8)) latch.oam[n] = latch.soam[n];
//257-320
for(uint sprite : range(8)) {
uint nametable = loadCHR(0x2000 | (uint12)io.v.address);
step(1);
if(enable() && sprite == 0) {
//258
io.v.nametableX = io.t.nametableX;
io.v.tileX = io.t.tileX;
}
step(1);
uint attribute = loadCHR(0x23c0 | io.v.nametable << 10 | (io.v.tileY >> 2) << 3 | io.v.tileX >> 2);
uint tileaddr = io.spriteHeight == 8
? io.spriteAddress + latch.oam[sprite].tile * 16
: (latch.oam[sprite].tile & ~1) * 16 + (latch.oam[sprite].tile & 1) * 0x1000;
step(2);
uint spriteY = (io.ly - latch.oam[sprite].y) & (io.spriteHeight - 1);
if(latch.oam[sprite].attr & 0x80) spriteY ^= io.spriteHeight - 1;
tileaddr += spriteY + (spriteY & 8);
latch.oam[sprite].tiledataLo = loadCHR(tileaddr + 0);
step(2);
latch.oam[sprite].tiledataHi = loadCHR(tileaddr + 8);
step(2);
if(enable() && sprite == 6 && io.ly == 261) {
//305
io.v.address = io.t.address;
}
}
//321-336
for(uint tile : range(2)) {
uint nametable = loadCHR(0x2000 | (uint12)io.v.address);
uint tileaddr = io.bgAddress | nametable << 4 | io.v.fineY;
step(2);
uint attribute = loadCHR(0x23c0 | io.v.nametable << 10 | (io.v.tileY >> 2) << 3 | io.v.tileX >> 2);
if(io.v.tileY & 2) attribute >>= 4;
if(io.v.tileX & 2) attribute >>= 2;
step(1);
if(enable() && ++io.v.tileX == 0) io.v.nametableX++;
step(1);
uint tiledataLo = loadCHR(tileaddr + 0);
step(2);
uint tiledataHi = loadCHR(tileaddr + 8);
step(2);
latch.nametable = latch.nametable << 8 | nametable;
latch.attribute = latch.attribute << 2 | (attribute & 3);
latch.tiledataLo = latch.tiledataLo << 8 | tiledataLo;
latch.tiledataHi = latch.tiledataHi << 8 | tiledataHi;
}
//337-338
loadCHR(0x2000 | (uint12)io.v.address);
step(1);
bool skip = enable() && io.field == 1 && io.ly == 261;
step(1);
//339
loadCHR(0x2000 | (uint12)io.v.address);
step(1);
//340
if(!skip) step(1);
return scanline();
}
-64
View File
@@ -1,64 +0,0 @@
auto PPU::serialize(serializer& s) -> void {
Thread::serialize(s);
s.integer(io.mdr);
s.integer(io.field);
s.integer(io.lx);
s.integer(io.ly);
s.integer(io.busData);
s.integer(io.v.value);
s.integer(io.t.value);
s.integer(io.nmiHold);
s.integer(io.nmiFlag);
s.integer(io.vramIncrement);
s.integer(io.spriteAddress);
s.integer(io.bgAddress);
s.integer(io.spriteHeight);
s.integer(io.masterSelect);
s.integer(io.nmiEnable);
s.integer(io.grayscale);
s.integer(io.bgEdgeEnable);
s.integer(io.spriteEdgeEnable);
s.integer(io.bgEnable);
s.integer(io.spriteEnable);
s.integer(io.emphasis);
s.integer(io.spriteOverflow);
s.integer(io.spriteZeroHit);
s.integer(io.oamAddress);
s.integer(latch.nametable);
s.integer(latch.attribute);
s.integer(latch.tiledataLo);
s.integer(latch.tiledataHi);
s.integer(latch.oamIterator);
s.integer(latch.oamCounter);
for(auto& o : latch.oam) o.serialize(s);
for(auto& o : latch.soam) o.serialize(s);
s.array(ciram);
s.array(cgram);
s.array(oam);
s.array(buffer);
}
auto PPU::OAM::serialize(serializer& s) -> void {
s.integer(id);
s.integer(y);
s.integer(tile);
s.integer(attr);
s.integer(x);
s.integer(tiledataLo);
s.integer(tiledataHi);
}
-44
View File
@@ -1,44 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
Scheduler scheduler;
auto Scheduler::reset() -> void {
host = co_active();
resume = cpu.thread;
}
auto Scheduler::enter(Mode mode_) -> Event {
mode = mode_;
host = co_active();
co_switch(resume);
if(event == Event::Frame) ppu.refresh();
return event;
}
auto Scheduler::exit(Event event_) -> void {
event = event_;
resume = co_active();
co_switch(host);
}
auto Scheduler::synchronize(cothread_t thread) -> void {
if(thread == cpu.thread) {
while(enter(Mode::SynchronizeCPU) != Event::Synchronize);
} else {
resume = thread;
while(enter(Mode::SynchronizeAll) != Event::Synchronize);
}
}
auto Scheduler::synchronize() -> void {
if(co_active() == cpu.thread && mode == Mode::SynchronizeCPU) return exit(Event::Synchronize);
if(co_active() != cpu.thread && mode == Mode::SynchronizeAll) return exit(Event::Synchronize);
}
auto Scheduler::synchronizing() const -> bool {
return mode == Mode::SynchronizeAll;
}
}
-28
View File
@@ -1,28 +0,0 @@
struct Scheduler {
enum class Mode : uint {
Run,
SynchronizeCPU,
SynchronizeAll,
};
enum class Event : uint {
Unknown,
Frame,
Synchronize,
};
auto reset() -> void;
auto enter(Mode = Mode::Run) -> Event;
auto exit(Event) -> void;
auto synchronize(cothread_t) -> void;
auto synchronize() -> void;
auto synchronizing() const -> bool;
private:
cothread_t host = nullptr;
cothread_t resume = nullptr;
Mode mode = Mode::Run;
Event event = Event::Unknown;
};
extern Scheduler scheduler;
-46
View File
@@ -1,46 +0,0 @@
Peripherals peripherals;
auto Peripherals::unload() -> void {
delete controllerPort1;
delete controllerPort2;
controllerPort1 = nullptr;
controllerPort2 = nullptr;
}
auto Peripherals::reset() -> void {
connect(ID::Port::Controller1, settings.controllerPort1);
connect(ID::Port::Controller2, settings.controllerPort2);
}
auto Peripherals::connect(uint port, uint device) -> void {
if(port == ID::Port::Controller1) {
settings.controllerPort1 = device;
if(!system.loaded()) return;
delete controllerPort1;
switch(device) { default:
case ID::Device::None: controllerPort1 = new Controller(0); break;
case ID::Device::Gamepad: controllerPort1 = new Gamepad(0); break;
}
}
if(port == ID::Port::Controller2) {
settings.controllerPort2 = device;
if(!system.loaded()) return;
delete controllerPort2;
switch(device) { default:
case ID::Device::None: controllerPort2 = new Controller(1); break;
case ID::Device::Gamepad: controllerPort2 = new Gamepad(1); break;
}
}
if(port == ID::Port::Expansion) {
settings.expansionPort = device;
if(!system.loaded()) return;
}
cpu.peripherals.reset();
cpu.peripherals.append(controllerPort1);
cpu.peripherals.append(controllerPort2);
}
-65
View File
@@ -1,65 +0,0 @@
auto System::serialize() -> serializer {
serializer s(_serializeSize);
uint signature = 0x31545342;
char version[16] = {0};
char hash[64] = {0};
char description[512] = {0};
memory::copy(&version, (const char*)Emulator::SerializerVersion, Emulator::SerializerVersion.size());
memory::copy(&hash, (const char*)cartridge.sha256(), 64);
s.integer(signature);
s.array(version);
s.array(hash);
s.array(description);
serializeAll(s);
return s;
}
auto System::unserialize(serializer& s) -> bool {
uint signature;
char version[16];
char hash[64];
char description[512];
s.integer(signature);
s.array(version);
s.array(hash);
s.array(description);
if(signature != 0x31545342) return false;
if(string{version} != Emulator::SerializerVersion) return false;
power();
serializeAll(s);
return true;
}
auto System::serialize(serializer& s) -> void {
}
auto System::serializeAll(serializer& s) -> void {
system.serialize(s);
cartridge.serialize(s);
cpu.serialize(s);
apu.serialize(s);
ppu.serialize(s);
}
auto System::serializeInit() -> void {
serializer s;
uint signature = 0;
char version[16];
char hash[64];
char description[512];
s.integer(signature);
s.array(version);
s.array(hash);
s.array(description);
serializeAll(s);
_serializeSize = s.size();
}
-80
View File
@@ -1,80 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
#include "peripherals.cpp"
#include "video.cpp"
#include "serialization.cpp"
System system;
auto System::run() -> void {
scheduler.enter();
}
auto System::runToSave() -> void {
scheduler.synchronize(cpu.thread);
scheduler.synchronize(apu.thread);
scheduler.synchronize(ppu.thread);
scheduler.synchronize(cartridge.thread);
for(auto peripheral : cpu.peripherals) {
scheduler.synchronize(peripheral->thread);
}
}
auto System::load() -> bool {
information = Information();
if(auto fp = interface->open(ID::System, "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads();
} else {
return false;
}
auto document = BML::unserialize(information.manifest);
if(!cartridge.load()) return false;
serializeInit();
return information.loaded = true;
}
auto System::save() -> void {
cartridge.save();
}
auto System::unload() -> void {
if(!loaded()) return;
peripherals.unload();
cartridge.unload();
information.loaded = false;
}
auto System::power() -> void {
cartridge.power();
cpu.power();
apu.power();
ppu.power();
reset();
}
auto System::reset() -> void {
Emulator::video.reset();
Emulator::video.setInterface(interface);
configureVideoPalette();
configureVideoEffects();
Emulator::audio.reset();
Emulator::audio.setInterface(interface);
cartridge.reset();
cpu.reset();
apu.reset();
ppu.reset();
scheduler.reset();
peripherals.reset();
}
auto System::init() -> void {
assert(interface != nullptr);
}
auto System::term() -> void {
}
}
-47
View File
@@ -1,47 +0,0 @@
struct System {
auto loaded() const -> bool { return information.loaded; }
auto run() -> void;
auto runToSave() -> void;
auto load() -> bool;
auto save() -> void;
auto unload() -> void;
auto power() -> void;
auto reset() -> void;
auto init() -> void;
auto term() -> void;
//video.cpp
auto configureVideoPalette() -> void;
auto configureVideoEffects() -> void;
//serialization.cpp
auto serialize() -> serializer;
auto unserialize(serializer&) -> bool;
auto serialize(serializer&) -> void;
auto serializeAll(serializer&) -> void;
auto serializeInit() -> void;
struct Information {
bool loaded = false;
string manifest;
} information;
private:
uint _serializeSize = 0;
};
struct Peripherals {
auto unload() -> void;
auto reset() -> void;
auto connect(uint port, uint device) -> void;
Controller* controllerPort1 = nullptr;
Controller* controllerPort2 = nullptr;
};
extern System system;
extern Peripherals peripherals;
-6
View File
@@ -1,6 +0,0 @@
auto System::configureVideoPalette() -> void {
Emulator::video.setPalette();
}
auto System::configureVideoEffects() -> void {
}
-16
View File
@@ -1,16 +0,0 @@
processors += lr35902
objects += gb-interface gb-system gb-scheduler
objects += gb-memory gb-cartridge
objects += gb-cpu gb-ppu gb-apu
objects += gb-cheat
obj/gb-interface.o: gb/interface/interface.cpp $(call rwildcard,gb/interface/)
obj/gb-system.o: gb/system/system.cpp $(call rwildcard,gb/system/)
obj/gb-scheduler.o: gb/scheduler/scheduler.cpp $(call rwildcard,gb/scheduler/)
obj/gb-cartridge.o: gb/cartridge/cartridge.cpp $(call rwildcard,gb/cartridge/)
obj/gb-memory.o: gb/memory/memory.cpp $(call rwildcard,gb/memory/)
obj/gb-cpu.o: gb/cpu/cpu.cpp $(call rwildcard,gb/cpu/)
obj/gb-ppu.o: gb/ppu/ppu.cpp $(call rwildcard,gb/ppu/)
obj/gb-apu.o: gb/apu/apu.cpp $(call rwildcard,gb/apu/)
obj/gb-cheat.o: gb/cheat/cheat.cpp $(call rwildcard,gb/cheat/)
-112
View File
@@ -1,112 +0,0 @@
#include <gb/gb.hpp>
namespace GameBoy {
#include "sequencer.cpp"
#include "square1.cpp"
#include "square2.cpp"
#include "wave.cpp"
#include "noise.cpp"
#include "serialization.cpp"
APU apu;
auto APU::Enter() -> void {
while(true) scheduler.synchronize(), apu.main();
}
auto APU::main() -> void {
square1.run();
square2.run();
wave.run();
noise.run();
sequencer.run();
hipass(sequencer.center, sequencer.centerBias);
hipass(sequencer.left, sequencer.leftBias);
hipass(sequencer.right, sequencer.rightBias);
if(!system.sgb()) {
stream->sample(sequencer.left / 32768.0, sequencer.right / 32768.0);
} else {
double samples[] = {sequencer.left / 32768.0, sequencer.right / 32768.0};
interface->audioSample(samples, 2);
}
if(cycle == 0) { //512hz
if(phase == 0 || phase == 2 || phase == 4 || phase == 6) { //256hz
square1.clockLength();
square2.clockLength();
wave.clockLength();
noise.clockLength();
}
if(phase == 2 || phase == 6) { //128hz
square1.clockSweep();
}
if(phase == 7) { //64hz
square1.clockEnvelope();
square2.clockEnvelope();
noise.clockEnvelope();
}
phase++;
}
cycle++;
clock += cpu.frequency;
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
}
//filter to remove DC bias
auto APU::hipass(int16& sample, int64& bias) -> void {
bias += ((((int64)sample << 16) - (bias >> 16)) * 57593) >> 16;
sample = sclamp<16>(sample - (bias >> 32));
}
auto APU::power() -> void {
create(Enter, 2 * 1024 * 1024);
if(!system.sgb()) stream = Emulator::audio.createStream(2, 2 * 1024 * 1024);
for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;
square1.power();
square2.power();
wave.power();
noise.power();
sequencer.power();
phase = 0;
cycle = 0;
LinearFeedbackShiftRegisterGenerator r;
for(auto& n : wave.pattern) n = r();
}
auto APU::readIO(uint16 addr) -> uint8 {
if(addr >= 0xff10 && addr <= 0xff14) return square1.read(addr);
if(addr >= 0xff15 && addr <= 0xff19) return square2.read(addr);
if(addr >= 0xff1a && addr <= 0xff1e) return wave.read(addr);
if(addr >= 0xff1f && addr <= 0xff23) return noise.read(addr);
if(addr >= 0xff24 && addr <= 0xff26) return sequencer.read(addr);
if(addr >= 0xff30 && addr <= 0xff3f) return wave.read(addr);
return 0xff;
}
auto APU::writeIO(uint16 addr, uint8 data) -> void {
if(!sequencer.enable) {
bool valid = addr == 0xff26; //NR52
if(!system.cgb()) {
//NRx1 length is writable only on DMG,SGB; not on CGB
if(addr == 0xff11) valid = true, data &= 0x3f; //NR11; duty is not writable (remains 0)
if(addr == 0xff16) valid = true, data &= 0x3f; //NR21; duty is not writable (remains 0)
if(addr == 0xff1b) valid = true; //NR31
if(addr == 0xff20) valid = true; //NR41
}
if(!valid) return;
}
if(addr >= 0xff10 && addr <= 0xff14) return square1.write(addr, data);
if(addr >= 0xff15 && addr <= 0xff19) return square2.write(addr, data);
if(addr >= 0xff1a && addr <= 0xff1e) return wave.write(addr, data);
if(addr >= 0xff1f && addr <= 0xff23) return noise.write(addr, data);
if(addr >= 0xff24 && addr <= 0xff26) return sequencer.write(addr, data);
if(addr >= 0xff30 && addr <= 0xff3f) return wave.write(addr, data);
}
}
-176
View File
@@ -1,176 +0,0 @@
struct APU : Thread, MMIO {
shared_pointer<Emulator::Stream> stream;
static auto Enter() -> void;
auto main() -> void;
auto hipass(int16& sample, int64& bias) -> void;
auto power() -> void;
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
auto serialize(serializer&) -> void;
//square1.cpp
struct Square1 {
auto dacEnable() const -> bool;
auto run() -> void;
auto sweep(bool update) -> void;
auto clockLength() -> void;
auto clockSweep() -> void;
auto clockEnvelope() -> void;
auto read(uint16 addr) -> uint8;
auto write(uint16 addr, uint8 data) -> void;
auto power(bool initializeLength = true) -> void;
auto serialize(serializer&) -> void;
bool enable;
uint3 sweepFrequency;
bool sweepDirection;
uint3 sweepShift;
bool sweepNegate;
uint2 duty;
uint length;
uint4 envelopeVolume;
bool envelopeDirection;
uint3 envelopeFrequency;
uint11 frequency;
bool counter;
int16 output;
bool dutyOutput;
uint3 phase;
uint period;
uint3 envelopePeriod;
uint3 sweepPeriod;
int frequencyShadow;
bool sweepEnable;
uint4 volume;
} square1;
//square2.cpp
struct Square2 {
auto dacEnable() const -> bool;
auto run() -> void;
auto clockLength() -> void;
auto clockEnvelope() -> void;
auto read(uint16 addr) -> uint8;
auto write(uint16 addr, uint8 data) -> void;
auto power(bool initializeLength = true) -> void;
auto serialize(serializer&) -> void;
bool enable;
uint2 duty;
uint length;
uint4 envelopeVolume;
bool envelopeDirection;
uint3 envelopeFrequency;
uint11 frequency;
bool counter;
int16 output;
bool dutyOutput;
uint3 phase;
uint period;
uint3 envelopePeriod;
uint4 volume;
} square2;
struct Wave {
auto getPattern(uint5 offset) const -> uint4;
auto run() -> void;
auto clockLength() -> void;
auto read(uint16 addr) -> uint8;
auto write(uint16 addr, uint8 data) -> void;
auto power(bool initializeLength = true) -> void;
auto serialize(serializer&) -> void;
bool enable;
bool dacEnable;
uint2 volume;
uint11 frequency;
bool counter;
uint8 pattern[16];
int16 output;
uint length;
uint period;
uint5 patternOffset;
uint4 patternSample;
uint patternHold;
} wave;
struct Noise {
auto dacEnable() const -> bool;
auto getPeriod() const -> uint;
auto run() -> void;
auto clockLength() -> void;
auto clockEnvelope() -> void;
auto read(uint16 addr) -> uint8;
auto write(uint16 addr, uint8 data) -> void;
auto power(bool initializeLength = true) -> void;
auto serialize(serializer&) -> void;
bool enable;
uint4 envelopeVolume;
bool envelopeDirection;
uint3 envelopeFrequency;
uint4 frequency;
bool narrow;
uint3 divisor;
bool counter;
int16 output;
uint length;
uint3 envelopePeriod;
uint4 volume;
uint period;
uint15 lfsr;
} noise;
struct Sequencer {
auto run() -> void;
auto read(uint16 addr) -> uint8;
auto write(uint16 addr, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
bool leftEnable;
uint3 leftVolume;
bool rightEnable;
uint3 rightVolume;
struct Channel {
bool leftEnable;
bool rightEnable;
} square1, square2, wave, noise;
bool enable;
int16 center;
int16 left;
int16 right;
int64 centerBias;
int64 leftBias;
int64 rightBias;
} sequencer;
uint3 phase; //high 3-bits of clock counter
uint12 cycle; //low 12-bits of clock counter
};
extern APU apu;
-140
View File
@@ -1,140 +0,0 @@
auto APU::Noise::dacEnable() const -> bool {
return (envelopeVolume || envelopeDirection);
}
auto APU::Noise::getPeriod() const -> uint {
static const uint table[] = {4, 8, 16, 24, 32, 40, 48, 56};
return table[divisor] << frequency;
}
auto APU::Noise::run() -> void {
if(period && --period == 0) {
period = getPeriod();
if(frequency < 14) {
bool bit = (lfsr ^ (lfsr >> 1)) & 1;
lfsr = (lfsr >> 1) ^ (bit << (narrow ? 6 : 14));
}
}
uint4 sample = lfsr & 1 ? 0 : (uint)volume;
if(!enable) sample = 0;
output = sample;
}
auto APU::Noise::clockLength() -> void {
if(counter) {
if(length && --length == 0) enable = false;
}
}
auto APU::Noise::clockEnvelope() -> void {
if(enable && envelopeFrequency && --envelopePeriod == 0) {
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
if(envelopeDirection == 0 && volume > 0) volume--;
if(envelopeDirection == 1 && volume < 15) volume++;
}
}
auto APU::Noise::read(uint16 addr) -> uint8 {
if(addr == 0xff1f) { //NR40
return 0xff;
}
if(addr == 0xff20) { //NR41
return 0xff;
}
if(addr == 0xff21) { //NR42
return envelopeVolume << 4 | envelopeDirection << 3 | envelopeFrequency;
}
if(addr == 0xff22) { //NR43
return frequency << 4 | narrow << 3 | divisor;
}
if(addr == 0xff23) { //NR44
return 0x80 | counter << 6 | 0x3f;
}
return 0xff;
}
auto APU::Noise::write(uint16 addr, uint8 data) -> void {
if(addr == 0xff20) { //NR41
length = 64 - (data & 0x3f);
}
if(addr == 0xff21) { //NR42
envelopeVolume = data.bits(7,4);
envelopeDirection = data.bit (3);
envelopeFrequency = data.bits(2,0);
if(!dacEnable()) enable = false;
}
if(addr == 0xff22) { //NR43
frequency = data.bits(7,4);
narrow = data.bit (3);
divisor = data.bits(2,0);
period = getPeriod();
}
if(addr == 0xff23) { //NR44
if((apu.phase & 1) && !counter && (data & 0x40)) {
if(length && --length == 0) enable = false;
}
counter = data.bit(6);
if(data.bit(7)) {
enable = dacEnable();
lfsr = -1;
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
volume = envelopeVolume;
if(!length) {
length = 64;
if(apu.phase.bit(0) && counter) length--;
}
}
}
}
auto APU::Noise::power(bool initializeLength) -> void {
enable = 0;
envelopeVolume = 0;
envelopeDirection = 0;
envelopeFrequency = 0;
frequency = 0;
narrow = 0;
divisor = 0;
counter = 0;
output = 0;
envelopePeriod = 0;
volume = 0;
period = 0;
lfsr = 0;
if(initializeLength) length = 64;
}
auto APU::Noise::serialize(serializer& s) -> void {
s.integer(enable);
s.integer(envelopeVolume);
s.integer(envelopeDirection);
s.integer(envelopeFrequency);
s.integer(frequency);
s.integer(narrow);
s.integer(divisor);
s.integer(counter);
s.integer(output);
s.integer(length);
s.integer(envelopePeriod);
s.integer(volume);
s.integer(period);
s.integer(lfsr);
}
-152
View File
@@ -1,152 +0,0 @@
auto APU::Sequencer::run() -> void {
if(enable == false) {
center = 0;
left = 0;
right = 0;
centerBias = leftBias = rightBias = 0;
return;
}
int sample = 0;
sample += apu.square1.output;
sample += apu.square2.output;
sample += apu.wave.output;
sample += apu.noise.output;
center = (sample * 512) - 16384;
sample = 0;
if(square1.leftEnable) sample += apu.square1.output;
if(square2.leftEnable) sample += apu.square2.output;
if( wave.leftEnable) sample += apu.wave.output;
if( noise.leftEnable) sample += apu.noise.output;
sample = (sample * 512) - 16384;
sample = (sample * (leftVolume + 1)) / 8;
left = sample;
sample = 0;
if(square1.rightEnable) sample += apu.square1.output;
if(square2.rightEnable) sample += apu.square2.output;
if( wave.rightEnable) sample += apu.wave.output;
if( noise.rightEnable) sample += apu.noise.output;
sample = (sample * 512) - 16384;
sample = (sample * (rightVolume + 1)) / 8;
right = sample;
//reduce audio volume
center >>= 1;
left >>= 1;
right >>= 1;
}
auto APU::Sequencer::read(uint16 addr) -> uint8 {
if(addr == 0xff24) { //NR50
return leftEnable << 7 | leftVolume << 4 | rightEnable << 3 | rightVolume;
}
if(addr == 0xff25) { //NR51
return noise.leftEnable << 7
| wave.leftEnable << 6
| square2.leftEnable << 5
| square1.leftEnable << 4
| noise.rightEnable << 3
| wave.rightEnable << 2
| square2.rightEnable << 1
| square1.rightEnable << 0;
}
if(addr == 0xff26) { //NR52
return enable << 7 | 0x70
| apu.noise.enable << 3
| apu.wave.enable << 2
| apu.square2.enable << 1
| apu.square1.enable << 0;
}
return 0xff;
}
auto APU::Sequencer::write(uint16 addr, uint8 data) -> void {
if(addr == 0xff24) { //NR50
leftEnable = data.bit (7);
leftVolume = data.bits(6,4);
rightEnable = data.bit (3);
rightVolume = data.bits(2,0);
}
if(addr == 0xff25) { //NR51
noise.leftEnable = data.bit(7);
wave.leftEnable = data.bit(6);
square2.leftEnable = data.bit(5);
square1.leftEnable = data.bit(4);
noise.rightEnable = data.bit(3);
wave.rightEnable = data.bit(2);
square2.rightEnable = data.bit(1);
square1.rightEnable = data.bit(0);
}
if(addr == 0xff26) { //NR52
if(enable != data.bit(7)) {
enable = data.bit(7);
if(!enable) {
//power(bool) resets length counters when true (eg for CGB only)
apu.square1.power(system.cgb());
apu.square2.power(system.cgb());
apu.wave.power(system.cgb());
apu.noise.power(system.cgb());
power();
} else {
apu.phase = 0;
}
}
}
}
auto APU::Sequencer::power() -> void {
leftEnable = 0;
leftVolume = 0;
rightEnable = 0;
rightVolume = 0;
noise.leftEnable = 0;
wave.leftEnable = 0;
square2.leftEnable = 0;
square1.leftEnable = 0;
noise.rightEnable = 0;
wave.rightEnable = 0;
square2.rightEnable = 0;
square1.rightEnable = 0;
enable = 0;
center = 0;
left = 0;
right = 0;
centerBias = 0;
leftBias = 0;
rightBias = 0;
}
auto APU::Sequencer::serialize(serializer& s) -> void {
s.integer(leftEnable);
s.integer(leftVolume);
s.integer(rightEnable);
s.integer(rightVolume);
s.integer(noise.leftEnable);
s.integer(wave.leftEnable);
s.integer(square2.leftEnable);
s.integer(square1.leftEnable);
s.integer(noise.rightEnable);
s.integer(wave.rightEnable);
s.integer(square2.rightEnable);
s.integer(square1.rightEnable);
s.integer(enable);
s.integer(center);
s.integer(left);
s.integer(right);
s.integer(centerBias);
s.integer(leftBias);
s.integer(rightBias);
}
-12
View File
@@ -1,12 +0,0 @@
auto APU::serialize(serializer& s) -> void {
Thread::serialize(s);
square1.serialize(s);
square2.serialize(s);
wave.serialize(s);
noise.serialize(s);
sequencer.serialize(s);
s.integer(phase);
s.integer(cycle);
}
-190
View File
@@ -1,190 +0,0 @@
auto APU::Square1::dacEnable() const -> bool {
return (envelopeVolume || envelopeDirection);
}
auto APU::Square1::run() -> void {
if(period && --period == 0) {
period = 2 * (2048 - frequency);
phase++;
switch(duty) {
case 0: dutyOutput = (phase == 6); break; //______-_
case 1: dutyOutput = (phase >= 6); break; //______--
case 2: dutyOutput = (phase >= 4); break; //____----
case 3: dutyOutput = (phase <= 5); break; //------__
}
}
uint4 sample = dutyOutput ? (uint)volume : 0;
if(!enable) sample = 0;
output = sample;
}
auto APU::Square1::sweep(bool update) -> void {
if(!sweepEnable) return;
sweepNegate = sweepDirection;
uint delta = frequencyShadow >> sweepShift;
int freq = frequencyShadow + (sweepNegate ? -delta : delta);
if(freq > 2047) {
enable = false;
} else if(sweepShift && update) {
frequencyShadow = freq;
frequency = freq & 2047;
period = 2 * (2048 - frequency);
}
}
auto APU::Square1::clockLength() -> void {
if(counter) {
if(length && --length == 0) enable = false;
}
}
auto APU::Square1::clockSweep() -> void {
if(--sweepPeriod == 0) {
sweepPeriod = sweepFrequency ? (uint)sweepFrequency : 8;
if(sweepEnable && sweepFrequency) {
sweep(1);
sweep(0);
}
}
}
auto APU::Square1::clockEnvelope() -> void {
if(enable && envelopeFrequency && --envelopePeriod == 0) {
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
if(envelopeDirection == 0 && volume > 0) volume--;
if(envelopeDirection == 1 && volume < 15) volume++;
}
}
auto APU::Square1::read(uint16 addr) -> uint8 {
if(addr == 0xff10) { //NR10
return 0x80 | sweepFrequency << 4 | sweepDirection << 3 | sweepShift;
}
if(addr == 0xff11) { //NR11
return duty << 6 | 0x3f;
}
if(addr == 0xff12) { //NR12
return envelopeVolume << 4 | envelopeDirection << 3 | envelopeFrequency;
}
if(addr == 0xff13) { //NR13
return 0xff;
}
if(addr == 0xff14) { //NR14
return 0x80 | counter << 6 | 0x3f;
}
return 0xff;
}
auto APU::Square1::write(uint16 addr, uint8 data) -> void {
if(addr == 0xff10) { //NR10
if(sweepEnable && sweepNegate && !data.bit(3)) enable = false;
sweepFrequency = data.bits(6,4);
sweepDirection = data.bit (3);
sweepShift = data.bits(2,0);
}
if(addr == 0xff11) { //NR11
duty = data.bits(7,6);
length = 64 - data.bits(5,0);
}
if(addr == 0xff12) { //NR12
envelopeVolume = data.bits(7,4);
envelopeDirection = data.bit (3);
envelopeFrequency = data.bits(2,0);
if(!dacEnable()) enable = false;
}
if(addr == 0xff13) { //NR13
frequency.bits(7,0) = data;
}
if(addr == 0xff14) { //NR14
if(apu.phase.bit(0) && !counter && data.bit(6)) {
if(length && --length == 0) enable = false;
}
counter = data.bit(6);
frequency.bits(10,8) = data.bits(2,0);
if(data.bit(7)) {
enable = dacEnable();
period = 2 * (2048 - frequency);
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
volume = envelopeVolume;
if(!length) {
length = 64;
if(apu.phase.bit(0) && counter) length--;
}
frequencyShadow = frequency;
sweepNegate = false;
sweepPeriod = sweepFrequency ? (uint)sweepFrequency : 8;
sweepEnable = sweepPeriod || sweepShift;
if(sweepShift) sweep(0);
}
}
}
auto APU::Square1::power(bool initializeLength) -> void {
enable = 0;
sweepFrequency = 0;
sweepDirection = 0;
sweepShift = 0;
sweepNegate = 0;
duty = 0;
envelopeVolume = 0;
envelopeDirection = 0;
envelopeFrequency = 0;
frequency = 0;
counter = 0;
output = 0;
dutyOutput = 0;
phase = 0;
period = 0;
envelopePeriod = 0;
sweepPeriod = 0;
frequencyShadow = 0;
sweepEnable = 0;
volume = 0;
if(initializeLength) length = 64;
}
auto APU::Square1::serialize(serializer& s) -> void {
s.integer(enable);
s.integer(sweepFrequency);
s.integer(sweepDirection);
s.integer(sweepShift);
s.integer(sweepNegate);
s.integer(duty);
s.integer(length);
s.integer(envelopeVolume);
s.integer(envelopeDirection);
s.integer(envelopeFrequency);
s.integer(frequency);
s.integer(counter);
s.integer(output);
s.integer(dutyOutput);
s.integer(phase);
s.integer(period);
s.integer(envelopePeriod);
s.integer(sweepPeriod);
s.integer(frequencyShadow);
s.integer(sweepEnable);
s.integer(volume);
}
-137
View File
@@ -1,137 +0,0 @@
auto APU::Square2::dacEnable() const -> bool {
return (envelopeVolume || envelopeDirection);
}
auto APU::Square2::run() -> void {
if(period && --period == 0) {
period = 2 * (2048 - frequency);
phase++;
switch(duty) {
case 0: dutyOutput = (phase == 6); break; //______-_
case 1: dutyOutput = (phase >= 6); break; //______--
case 2: dutyOutput = (phase >= 4); break; //____----
case 3: dutyOutput = (phase <= 5); break; //------__
}
}
uint4 sample = dutyOutput ? (uint)volume : 0;
if(!enable) sample = 0;
output = sample;
}
auto APU::Square2::clockLength() -> void {
if(counter) {
if(length && --length == 0) enable = false;
}
}
auto APU::Square2::clockEnvelope() -> void {
if(enable && envelopeFrequency && --envelopePeriod == 0) {
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
if(envelopeDirection == 0 && volume > 0) volume--;
if(envelopeDirection == 1 && volume < 15) volume++;
}
}
auto APU::Square2::read(uint16 addr) -> uint8 {
if(addr == 0xff15) { //NR20
return 0xff;
}
if(addr == 0xff16) { //NR21
return duty << 6 | 0x3f;
}
if(addr == 0xff17) { //NR22
return envelopeVolume << 4 | envelopeDirection << 3 | envelopeFrequency;
}
if(addr == 0xff18) { //NR23
return 0xff;
}
if(addr == 0xff19) { //NR24
return 0x80 | counter << 6 | 0x3f;
}
return 0xff;
}
auto APU::Square2::write(uint16 addr, uint8 data) -> void {
if(addr == 0xff16) { //NR21
duty = data.bits(7,6);
length = 64 - data.bits(5,0);
}
if(addr == 0xff17) { //NR22
envelopeVolume = data.bits(7,4);
envelopeDirection = data.bit (3);
envelopeFrequency = data.bits(2,0);
if(!dacEnable()) enable = false;
}
if(addr == 0xff18) { //NR23
frequency.bits(7,0) = data;
}
if(addr == 0xff19) { //NR24
if(apu.phase.bit(0) && !counter && data.bit(6)) {
if(length && --length == 0) enable = false;
}
counter = data.bit(6);
frequency.bits(10,8) = data.bits(2,0);
if(data.bit(7)) {
enable = dacEnable();
period = 2 * (2048 - frequency);
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
volume = envelopeVolume;
if(!length) {
length = 64;
if(apu.phase.bit(0) && counter) length--;
}
}
}
}
auto APU::Square2::power(bool initializeLength) -> void {
enable = 0;
duty = 0;
envelopeVolume = 0;
envelopeDirection = 0;
envelopeFrequency = 0;
frequency = 0;
counter = 0;
output = 0;
dutyOutput = 0;
phase = 0;
period = 0;
envelopePeriod = 0;
volume = 0;
if(initializeLength) length = 64;
}
auto APU::Square2::serialize(serializer& s) -> void {
s.integer(enable);
s.integer(duty);
s.integer(length);
s.integer(envelopeVolume);
s.integer(envelopeDirection);
s.integer(envelopeFrequency);
s.integer(frequency);
s.integer(counter);
s.integer(output);
s.integer(dutyOutput);
s.integer(phase);
s.integer(period);
s.integer(envelopePeriod);
s.integer(volume);
}
-156
View File
@@ -1,156 +0,0 @@
auto APU::Wave::getPattern(uint5 offset) const -> uint4 {
return pattern[offset >> 1] >> (offset & 1 ? 0 : 4);
}
auto APU::Wave::run() -> void {
if(patternHold) patternHold--;
if(period && --period == 0) {
period = 1 * (2048 - frequency);
patternSample = getPattern(++patternOffset);
patternHold = 1;
}
static const uint shift[] = {4, 0, 1, 2}; //0%, 100%, 50%, 25%
uint4 sample = patternSample >> shift[volume];
if(!enable) sample = 0;
output = sample;
}
auto APU::Wave::clockLength() -> void {
if(counter) {
if(length && --length == 0) enable = false;
}
}
auto APU::Wave::read(uint16 addr) -> uint8 {
if(addr == 0xff1a) { //NR30
return dacEnable << 7 | 0x7f;
}
if(addr == 0xff1b) { //NR31
return 0xff;
}
if(addr == 0xff1c) { //NR32
return 0x80 | volume << 5 | 0x1f;
}
if(addr == 0xff1d) { //NR33
return 0xff;
}
if(addr == 0xff1e) { //NR34
return 0x80 | counter << 6 | 0x3f;
}
if(addr >= 0xff30 && addr <= 0xff3f) {
if(enable) {
if(!system.cgb() && !patternHold) return 0xff;
return pattern[patternOffset >> 1];
} else {
return pattern[addr & 15];
}
}
return 0xff;
}
auto APU::Wave::write(uint16 addr, uint8 data) -> void {
if(addr == 0xff1a) { //NR30
dacEnable = data.bit(7);
if(!dacEnable) enable = false;
}
if(addr == 0xff1b) { //NR31
length = 256 - data;
}
if(addr == 0xff1c) { //NR32
volume = data.bits(6,5);
}
if(addr == 0xff1d) { //NR33
frequency.bits(7,0) = data;
}
if(addr == 0xff1e) { //NR34
if(apu.phase.bit(0) && !counter && data.bit(6)) {
if(length && --length == 0) enable = false;
}
counter = data.bit(6);
frequency.bits(10,8) = data.bits(2,0);
if(data.bit(7)) {
if(!system.cgb() && patternHold) {
//DMG,SGB trigger while channel is being read corrupts wave RAM
if((patternOffset >> 1) <= 3) {
//if current pattern is with 0-3; only byte 0 is corrupted
pattern[0] = pattern[patternOffset >> 1];
} else {
//if current pattern is within 4-15; pattern&~3 is copied to pattern[0-3]
pattern[0] = pattern[((patternOffset >> 1) & ~3) + 0];
pattern[1] = pattern[((patternOffset >> 1) & ~3) + 1];
pattern[2] = pattern[((patternOffset >> 1) & ~3) + 2];
pattern[3] = pattern[((patternOffset >> 1) & ~3) + 3];
}
}
enable = dacEnable;
period = 1 * (2048 - frequency);
patternOffset = 0;
patternSample = 0;
patternHold = 0;
if(!length) {
length = 256;
if(apu.phase.bit(0) && counter) length--;
}
}
}
if(addr >= 0xff30 && addr <= 0xff3f) {
if(enable) {
if(!system.cgb() && !patternHold) return;
pattern[patternOffset >> 1] = data;
} else {
pattern[addr & 15] = data;
}
}
}
auto APU::Wave::power(bool initializeLength) -> void {
enable = 0;
dacEnable = 0;
volume = 0;
frequency = 0;
counter = 0;
output = 0;
period = 0;
patternOffset = 0;
patternSample = 0;
patternHold = 0;
if(initializeLength) length = 256;
}
auto APU::Wave::serialize(serializer& s) -> void {
s.integer(enable);
s.integer(dacEnable);
s.integer(volume);
s.integer(frequency);
s.integer(counter);
s.array(pattern);
s.integer(output);
s.integer(length);
s.integer(period);
s.integer(patternOffset);
s.integer(patternSample);
s.integer(patternHold);
}
-178
View File
@@ -1,178 +0,0 @@
#include <gb/gb.hpp>
namespace GameBoy {
#include "mbc0/mbc0.cpp"
#include "mbc1/mbc1.cpp"
#include "mbc1m/mbc1m.cpp"
#include "mbc2/mbc2.cpp"
#include "mbc3/mbc3.cpp"
#include "mbc5/mbc5.cpp"
#include "mmm01/mmm01.cpp"
#include "huc1/huc1.cpp"
#include "huc3/huc3.cpp"
#include "serialization.cpp"
Cartridge cartridge;
auto Cartridge::load(System::Revision revision) -> bool {
information = Information();
switch(revision) {
case System::Revision::GameBoy:
if(auto pathID = interface->load(ID::GameBoy, "Game Boy", "gb")) {
information.pathID = pathID();
} else return false;
break;
case System::Revision::SuperGameBoy:
if(auto pathID = interface->load(ID::SuperGameBoy, "Game Boy", "gb")) {
information.pathID = pathID();
} else return false;
break;
case System::Revision::GameBoyColor:
if(auto pathID = interface->load(ID::GameBoyColor, "Game Boy Color", "gbc")) {
information.pathID = pathID();
} else return false;
break;
}
if(auto fp = interface->open(pathID(), "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads();
} else return false;
auto document = BML::unserialize(information.manifest);
auto board = document["board"];
information.title = document["information/title"].text();
auto mapperid = document["board/mapper"].text();
if(mapperid == "none" ) information.mapper = Mapper::MBC0;
if(mapperid == "MBC1" ) information.mapper = Mapper::MBC1;
if(mapperid == "MBC1M") information.mapper = Mapper::MBC1M;
if(mapperid == "MBC2" ) information.mapper = Mapper::MBC2;
if(mapperid == "MBC3" ) information.mapper = Mapper::MBC3;
if(mapperid == "MBC5" ) information.mapper = Mapper::MBC5;
if(mapperid == "MMM01") information.mapper = Mapper::MMM01;
if(mapperid == "HuC1" ) information.mapper = Mapper::HuC1;
if(mapperid == "HuC3" ) information.mapper = Mapper::HuC3;
information.rtc = false;
information.rumble = false;
rom.size = max(32768u, board["rom/size"].natural());
rom.data = (uint8*)memory::allocate(rom.size, 0xff);
ram.size = board["ram/size"].natural();
ram.data = (uint8*)memory::allocate(ram.size, 0xff);
if(auto name = board["rom/name"].text()) {
if(auto fp = interface->open(pathID(), name, File::Read, File::Required)) {
fp->read(rom.data, min(rom.size, fp->size()));
}
}
if(auto name = board["ram/name"].text()) {
if(auto fp = interface->open(pathID(), name, File::Read, File::Optional)) {
fp->read(ram.data, min(ram.size, fp->size()));
}
}
information.battery = (bool)board["ram/name"];
switch(information.mapper) { default:
case Mapper::MBC0: mapper = &mbc0; break;
case Mapper::MBC1: mapper = &mbc1; break;
case Mapper::MBC1M: mapper = &mbc1m; break;
case Mapper::MBC2: mapper = &mbc2; break;
case Mapper::MBC3: mapper = &mbc3; break;
case Mapper::MBC5: mapper = &mbc5; break;
case Mapper::MMM01: mapper = &mmm01; break;
case Mapper::HuC1: mapper = &huc1; break;
case Mapper::HuC3: mapper = &huc3; break;
}
information.sha256 = Hash::SHA256(rom.data, rom.size).digest();
return true;
}
auto Cartridge::save() -> void {
auto document = BML::unserialize(information.manifest);
if(auto name = document["board/ram/name"].text()) {
if(auto fp = interface->open(pathID(), name, File::Write)) {
fp->write(ram.data, ram.size);
}
}
}
auto Cartridge::unload() -> void {
delete[] rom.data;
delete[] ram.data;
rom = {};
ram = {};
}
auto Cartridge::readROM(uint addr) -> uint8 {
if(addr >= rom.size) addr %= rom.size;
return rom.data[addr];
}
auto Cartridge::writeROM(uint addr, uint8 data) -> void {
if(addr >= rom.size) addr %= rom.size;
rom.data[addr] = data;
}
auto Cartridge::readRAM(uint addr) -> uint8 {
if(ram.size == 0) return 0xff;
if(addr >= ram.size) addr %= ram.size;
return ram.data[addr];
}
auto Cartridge::writeRAM(uint addr, uint8 data) -> void {
if(ram.size == 0) return;
if(addr >= ram.size) addr %= ram.size;
ram.data[addr] = data;
}
auto Cartridge::readIO(uint16 addr) -> uint8 {
if(addr == 0xff50) return 0xff;
if(bootromEnable) {
const uint8* data = nullptr;
switch(system.revision()) { default:
case System::Revision::GameBoy: data = system.bootROM.dmg; break;
case System::Revision::SuperGameBoy: data = system.bootROM.sgb; break;
case System::Revision::GameBoyColor: data = system.bootROM.cgb; break;
}
if(addr >= 0x0000 && addr <= 0x00ff) return data[addr];
if(addr >= 0x0200 && addr <= 0x08ff && system.cgb()) return data[addr - 256];
}
return mapper->readIO(addr);
}
auto Cartridge::writeIO(uint16 addr, uint8 data) -> void {
if(bootromEnable && addr == 0xff50) {
bootromEnable = false;
return;
}
mapper->writeIO(addr, data);
}
auto Cartridge::power() -> void {
bootromEnable = true;
mbc0.power();
mbc1.power();
mbc1m.power();
mbc2.power();
mbc3.power();
mbc5.power();
mmm01.power();
huc1.power();
huc3.power();
for(uint n = 0x0000; n <= 0x7fff; n++) bus.mmio[n] = this;
for(uint n = 0xa000; n <= 0xbfff; n++) bus.mmio[n] = this;
bus.mmio[0xff50] = this;
}
}
-69
View File
@@ -1,69 +0,0 @@
struct Cartridge : MMIO {
auto pathID() const -> uint { return information.pathID; }
auto sha256() const -> string { return information.sha256; }
auto manifest() const -> string { return information.manifest; }
auto title() const -> string { return information.title; }
auto load(System::Revision revision) -> bool;
auto save() -> void;
auto unload() -> void;
auto readROM(uint addr) -> uint8;
auto writeROM(uint addr, uint8 data) -> void;
auto readRAM(uint addr) -> uint8;
auto writeRAM(uint addr, uint8 data) -> void;
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
#include "mbc0/mbc0.hpp"
#include "mbc1/mbc1.hpp"
#include "mbc1m/mbc1m.hpp"
#include "mbc2/mbc2.hpp"
#include "mbc3/mbc3.hpp"
#include "mbc5/mbc5.hpp"
#include "mmm01/mmm01.hpp"
#include "huc1/huc1.hpp"
#include "huc3/huc3.hpp"
enum Mapper : uint {
MBC0,
MBC1,
MBC1M,
MBC2,
MBC3,
MBC5,
MMM01,
HuC1,
HuC3,
Unknown,
};
struct Information {
uint pathID = 0;
string sha256;
string manifest;
string title;
Mapper mapper = Mapper::Unknown;
boolean ram;
boolean battery;
boolean rtc;
boolean rumble;
} information;
struct Memory {
uint8* data = nullptr;
uint size = 0;
} rom, ram;
MMIO* mapper = nullptr;
bool bootromEnable = true;
};
extern Cartridge cartridge;
Loaded 100 of 1843 files, more files were not shown because too many files have changed in this diff. Show more