Compare commits

..
58 Commits
Author SHA1 Message Date
byuu a1980fab09 Update to bsnes v021 release.
This is a maintainence release. I am mostly releasing this for the sake of the recently released Der Langrisser translation.
Changelog:
Windows port can once again map joypads through the Input Configuration panel
Using enter or spacebar to assign a key should no longer instantly map those keys
F11 now toggles fullscreen mode
Esc now toggles menu on and off (use F11+Esc combined to hide UI completely)
Fixed a bug in King of Dragons (J, U, E), KOFF was not cleared during S-DSP power(), thanks to FitzRoy for the report, and blargg for assistance fixing the bug
Fixed serious crashing error with File->Load on Linux/amd64 port
Hopefully fixed min/max undefined error on GCC 4.2.0, but I am unable to test to verify
Fixed many cast const char* to char* warnings for GCC 4.2.0, but some probably remain, as again, I am unable to test as I lack GCC 4.2.0
Set XV_AUTO_COLORKEY to 1 for Video/Xv renderer. Should fix some video drivers where there was no output, especially after running mplayer, etc. Thanks to sinimas for the fix
Added clear_video() to Video/Xv renderer. Green edges at the bottom and right sides of the video output are now gone, and unloading a ROM will clear video
I have finally figured out how to poll the keyboard status in real-time through Xorg: the XQueryKeymap function. I will be rewriting the Linux key capture system to use this, instead of capturing window key up / down messages through GTK+. This will finally allow me to completely abstract the UI from the hardware video, audio and input interfaces: a necessary step toward Linux joypad support.
2007-06-10 19:27:46 +00:00
byuu ebb234ba5f Update to bsnes v020 01 release.
[No changelog available]
2007-06-05 15:50:59 +00:00
byuu 2cc7fe30b4 Update to bsnes v020 release.
Five months and 43 WIP releases in the making, today I am releasing bsnes v0.020. I'd really like to express my thanks to blargg, for he has written a new S-DSP emulator that is an impressive 32 times more precise than all existing S-DSP emulators. It is now bus-accurate, and should produce bit-perfect sound output to that of a real SNES, excepting very minor, very extreme edge cases. Not only did he do this, he went out of his way to develop a special version exclusively for bsnes to ease licensing concerns and take advantage of bsnes' unique features, notably cothreads. I can't thank him enough. Unfortunately, bsnes has taken a ~10% speed hit over v0.019 by using this new S-DSP emulator, but I must stress the speed hit is entirely due to the way bsnes is implemented. blargg's standalone S-DSP emulator is very, very fast. Anyone is free to take a look at his S-DSP emulator, as he has released it as open source under the LGPL, by visiting his homepage, here.
Unfortunately, the new cross-platform UI is not entirely finished. Some sacrifices had to be made to support libui. Specifically, the following features are missing from v0.019, but will hopefully be added back in future releases:
    - Fullscreen support
    - Input Configuration panel cannot capture joypad input. Joypad support is still present, but it must be mapped manually through the Advanced panel or through editing bsnes.cfg by hand
    - The Cheat Code Editor is missing, but cht files can still be used from bsnes v0.019, and created by hand
    - Sufami Turbo support is not accessible from the UI
    - The UI on Windows is slightly less polished due to compromises to allow the UI to be readable on Linux.
I am sorry for the rough edges listed above, but I wanted to get a new release out, as it has been over five months since the last release, and I really want the world to be able to experience blargg's new S-DSP emulator.
Changelog:
    - Added blargg's new S-DSP emulator, runs at 1.024mhz. Many thanks to blargg for this, as this puts all portions of SNES emulation except for the S-PPU at bus-accuracy
    - blargg's S-DSP core fixes bugs in both Koushien 2 (J) and Toy Story (U)
    - Corrected all S-SMP cycle timings to be hardware accurate. Thanks to blargg for creating an amazing test ROM that tested every possible opcode
    - Corrected S-CPU wai instruction timing, fixes Mortal Kombat II
    - Reverted HDMA sync emulation once more to fix Breath of Fire II (G) and Secret of Mana (U)
    - Completely rewrote user interface to use libui, which is a wrapper that allows the same code to produce the same UI on both Windows (through the Win32 API) and Linux (through the GTK+ API)
    - Corrected $2100.d7 OAM reset behavior, thanks to research from anomie
    - Massively revamped the Linux port, should compile with no warnings or errors now
    - Added 64-bit support to libco, tested on FreeBSD/amd64, should work on Linux as well
    - Revamped makefile with suggestions from Nach
    - Improved Linux Xv renderer to use the far more common YUY2 format, which should work on most Xorg drivers, allowing hardware accelerated video scaling
    - Completely rewrote config file system. bsnes.cfg is now saved to user's profile folder on both Windows and Linux, allowing multi-user support
    - A lot more work has been done behind the scenes, including massive code cleanups and portability improvements
You may download the new version on the main bsnes page.
2007-06-03 00:20:00 +00:00
byuu 5c3c872b78 Update to bsnes v019r41? release.
New WIP up.

 I've replaced the interface::input setup, since Visual C++ was having
problems with it. I wanted something that wasn't so seemingly directly
linked to SNESInterface, anyway. Now I have InputManager, which will
handle not only all of the joypad mappings, but the GUI shortcut keys
as well. Yes -- I finally have all the code in place to support user-
defined shortcut keys. See? Something good did come out of the rewrite
after all. Dynamic keyboard mapping works on Windows now, but there
probably won't be joypad capture support until v0.021.

 Further, I have added SHGetFolderPath to the Windows port. libbase.h
sadly requires shell32.lib now. I haven't tested this on 9x, but I
don't believe bsnes has worked on 9x in a long, long time now. I've
also heard you can copy shfolder.dll or something to use it on 9x
anyway.

 Anyway, the config file now saves in your 'Application Data' folder
on Windows, and in your local directory on Linux. There's no need to
worry about what happens when you update bsnes and don't delete the
file ... as I use a text-based config file, like ZSNES / PSR, no harm
will come of it. Old variables will be flushed out, new variables will
be added with default values upon first load of the new version.
Thanks again to Nach for the code and help with this.

             Lastly, I've added a bsnes license page. So instead of
debating whether to look up four letter English words in Perens',
Stallman's or Webster's dictionary, you can just link to that page
instead :)

Again, the license applies to current and previous versions of bsnes.
If and when it forks, the fork will likely be licensed in a way that
others can take over the old version.

 Opinions on how to fix contradictions / loopholes welcome, blanket
statements that it's totally flawed without describing why or how are
not. Thanks in advance.

[No archive available]
2007-05-30 05:00:00 +00:00
byuu 36bf915244 Update to bsnes v019r40 release.
Ok, here's a public WIP for everyone:







    http://byuu.cinnamonpirate.com/files/bsnes_v019_wip40.zip




               Please ... if you link to this post or file elsewhere,
please mirror it.

               Fixes since wip39:
               - menu enter event captured, audio no longer hangs when
entering the menu.
 - multiple click problems resolved for all menu items plus list box
controls. Behavior should now be the same on both Windows and Linux,
but further polish is definitely needed here.
               - buttons to set values on input config and advanced
panels are now disabled when no item is selected.

               Known problems:
 - Windows/VC++ port is still complaining about that
interface::input.bind() thing. I believe it is a compiler problem. I
am not working around it, as I prefer a real fix. If anyone can help,
please see src/ui/lui/settings/ui_inputconfig.cpp, look for that line.
It is #if !defined(_MSC_VER) blocked at the moment. Until this is
resolved, you must restart before input settings take effect.
 - Joypads cannot be auto polled on input config screen. You can set
the values manually on the advanced tab, they use the same values as
bsnes v019, IIRC.
 - When pressing enter (or spacebar on Linux) on the input config
panel, the dialog pops up and closes right away assigning that key. I
have no easy way to fix this, since I can't poll the realtime status
of those keys on Linux to wait for them to clear before showing the
input capture window. It would really be immensely useful to be able
to do that.
 - Linux with ati driver requires you to move the window one time to
make the image visible ... I have no idea why this is needed. nv and
nvidia drivers work fine. Use the gtk renderer if you don't like the
chroma blending that using YUY2 mode requires.
               - Linux port does not focus properly to panel list when
opening config screen.
 - Config file still saves to startup working directory, rather than
the user folder. Still planning to work on that.
 - UI is still pretty ugly on Windows, but overall it's not too bad.
Looks beautiful on Linux, though ... maybe if I could find a way to
enable theme support for Windows. I tried making a .manifest file and
using mt, and setting WINVER + _WIN32_WINNT to 0x0500, none of those
did anything.
 - Cheat code editor has not been reimplemented yet. Really the last
major thing holding back a new release, but the above are pretty
important, too.

               Let me know if anything else major pops up.







> Plus it prevents a smart user/admin from making their program
> directory read-only.




               Once again, blargg has the most convincing argument :)
               Wouldn't want that config file on a read-only medium,
eg CD-ROM.
               I was wanting to implement this on Windows anyway, but
this makes it something I simply have to do.

[No archive available]
2007-05-29 08:37:00 +00:00
byuu 045a0f7e79 Update to bsnes v019r24? release.
New WIP. This one adds a GDI renderer for windows. If
anyone wants to test it, edit bsnes.cfg and set system.video to "gdi".
It will be very slow, obviously. It's just there for the hell of it,
as another fallback I guess. I'd be interested if it didn't work for
someone.
I had to add the code to libui to support pixel buffer images, so now
I can add things like the controller art into the new lui port, and
it'll work on Windows and Linux. The best part is that I can make
these image buffers anywhere, so things like PPU VRAM / OAM / CGRAM
viewers in the debugger will now not only be possible, but trivial, to
add in the future.

 Refined libui a lot more, but I did not merge that into this bsnes
WIP, because it would break the source pretty bad. Still working on
the API, too, so I'll probably hold off a bit longer. After I get the
new libui merged in, I can start working on that configuration
settings window. That window is the only thing holding up a new
official release.

 I'm trying to figure out how the hell you enable WinXP themes now. I
tried making a manifest file, even attaching the manifest to the EXE
directly with the mt tool, but it's still drawing the controls using
the old win32 compatibility mode.







> I can see that he hesitates to add "MAXI" codes and has no multiple
> codes for any game, despite how prevalent I've found them to be.




 You have cartridges where it's the exact same game (eg bit-for-bit
identical ROM dumps), with the only difference being the PCB codes?
Care to cite an example? The last two digits may change for revisions
of the same game, obviously.

 That complicates things, but there's no harm in just picking one in
that case. If the game didn't work with that PCB, it wouldn't have
been released with it, so ...

[No archive available]
2007-04-11 12:51:00 +00:00
byuu a209e6ffbe Update to bsnes v019r23 release.
Ok, this is a very important WIP release. Note that
this file is rather large, please mirror it if you must link to it
elsewhere.







    http://byuu.cinnamonpirate.com/files/bsnes_v019_wip23.zip




               Included are two executables:
               bsnes_adsp.exe - This version uses anomie's S-DSP
emulator, clocked at 32khz
               bsnes_bdsp.exe - This version uses blargg's S-DSP
emulator, clocked at 1.024mhz

 Please note that blargg's code is experimental and in-progress. That
said, I have been unable to find any errors with it so far. I hope I
haven't missed anything blargg wanted me to do before release.
Everyone, please give your thanks to blargg for creating this emulator
and allowing me to use his code :)

 This day marks an important milestone, at least in bsnes, possibly in
the SNES emulation scene: the addition of a subsample-accurate S-DSP
emulator brings us one major step closer to the most faithful SNES
emulation that will ever be possible. Excepting bugs, this now gives
us bus-accurate S-CPU, S-SMP and S-DSP cores. It is not possible (nor
desirable) in software to get more precise than bus-level accesses.
The only core component remaining using an older, less faithful
approach is the S-PPU[1/2], and is not so coincidentally the source of
the only remaining bugs in bsnes. This will very likely be the biggest
leap forward in accuracy that will ever be seen for S-DSP emulation
from this date on.

 The old win32 interface is now completely broken, so I am forced to
distribute using lui. As such, I've fixed the NTSC/PAL mode switches,
and added software video filter selection to the UI. Any configuration
changes that are not in the menu will have to be done via the config
file for the time being. I have also added the log audio data option
back to the misc menu. If you are not able to get 60fps in bsnes, or
would like to analyze the audio output between adsp and bdsp in
another program, you can use this option. Also, I'm aware of the lui-
specific issues, such as audio repeating when entering menus. lui is
still a work in progress.

 Please test all of the games you can, and look for subtle audio
differences and the like. Bugs, improvements, whatever, would be very
useful to know. Please keep in mind that every commercial game ever
released was tested by both FitzRoy and tetsuo55, and there are
currently zero known problems with anomie's S-DSP emulator. Also note
that blargg's emulator will be slower, by nature of being more low-
level. I'll leave the decision on which core to enable by default to
you guys. Eventually, I'll have polymorphism fully functional, and
this will be a runtime-selectable option, and not require two separate
builds. But still, we unfortunately have to pick one to be the default
setting, which I hope does not offend anyone :(

 I'm very appreciative and in debt to both anomie and blargg for their
help with S-DSP emulation. They have both done a very large service to
us all by creating these cores, so I thank both of them again for all
their hard work, and for allowing me to use their work in bsnes.

[No archive available]
2007-03-07 10:27:00 +00:00
byuu 3bf672dd97 Update to bsnes v019r19? release.
Ok, added blargg's changes. Played four levels, seems to be working
fine.

 Posted a new WIP with this change. I also replaced libkeymap with a
new implementation of it, this one is designed to work with window key
messages, meaning we can finally have input configuration for GUI
events and such in the future, and Linux users will finally have input
support shortly.

             Still not now, though. Input on Windows might be a little
sketchy, as well.

             Just need to create an InputWM class for Linux.

[No archive available]
2007-03-06 10:15:00 +00:00
byuu 157ddf3e8f Update to bsnes v019r18? release.
Unfortunately, even an S/PDIF link from a real SNES isn't good enough,
as we can't verify/match its' CPU<>SMP communications. Our best bet
for verification is still the echo buffer.

I uploaded a new private WIP. This build is just demonstrating part of
the new UI. I'm trying to move back to putting everything commonly
used in the menubar, and moving all of the obscure/complex stuff out
into separate windows.

             So far, lots of stuff is still missing, and the speed
setting (not enable) doesn't work.

 How does the video mode configuration feel in this WIP compared to
v0.019's video settings panel? Easier, better, worse? I realize it
loses a bit of flexibility (eg with custom resolutions), but eh. I'd
rather go back to simplicity than feature bloat.

[No archive available]
2007-02-19 03:37:00 +00:00
byuu ea23bf53ae Update to bsnes v019r17 release.
Ok, as promised, a public WIP build:







    http://byuu.cinnamonpirate.com/files/bsnes_v019_wip17.zip




               As always, please be generous with this one. If you
must link to it elsewhere, please at least mirror it.

 This should finally take care of the Toy Story bug. Yes, the audio
should halt roughly. The developers felt the need to use an evil trick
to force the audio to release faster than it normally should.

[No archive available]
2007-02-18 04:29:00 +00:00
byuu d4598e1d01 Update to bsnes v019r13a release.
(Repost, since this got bumped by another page, but
updated message.)

 Ok, this build has TRAC's and my idea for an S-DSP EDL fix applied.
EDL writes take effect immediately, and echo index bounds checking
occurs before FIR filtering and echo buffer writes. Please test this
with all of the really really picky/sensitive audio games you're aware
of, and see if you notice a difference between this and v0.019
official. Obviously, the sound differences should only exist in echo
effects, but luckily just about every game out there uses the echo
buffer. Note any differences you find either way, but I'm particularly
interested if things get worse, which will imply this fix is incorrect
(assuming the difference is verified in hardware as being correct in
v0.019 official), and we can try out the fix idea suggested by DMV27.
If no one finds any new audio bugs, we'll assume the fix was correct.

 And no, there's no audio resampling in this. I think log audio data
might still work, if that'll make it easier. It might not, the win32
port is falling apart as I rewrite the cross-platform port.







    http://byuu.cinnamonpirate.com/files/bsnes_v019_wip13.zip




 If anyone insists on posting about this on some other site (I'd
prefer not, as always), please at least mirror the file.

 Update: audio logger works. I binary compared two files. The only
difference is that audio is being output four samples sooner now,
they're otherwise exact matches. Doesn't seem to be a bad thing by any
means.

[No archive available]
2007-02-09 08:50:00 +00:00
byuu f9a8564af0 Update to bsnes v019r11 release.
Ok, I tried my best to add the audio synchronization
method (drop video frames) yet again, and once again failed
completely.

 The below WIP is completely unusuable as it stands, so please don't
link to it, host it anywhere else, or even download it unless you can
help with the programming. I'm not going to be able to fix this myself
as I've tried countless times over the last two years in vain to fix
it.







    http://byuu.cinnamonpirate.com/files/bsnes_v019_wip11.zip




 The included config file is important: it uses the DirectDraw
renderer instead of the D3D renderer, and has triple buffering
enabled.

               The relevant code is in src/ui/video/ddraw.cpp and
src/ui/audio/dsound.cpp.

               The most important code is below, but obviously any
tests would need the above WIP to build and try out.







    void AudioDS::run(uint32 sample) {
                       uiVideo->tick();
                       data.buffer[data.buffer_pos++] = sample;

                       if(data.buffer_pos < latency)return;

                       uint32 ring_pos, pos, size;
                       do {
                       Sleep(1);
                       uiVideo->tick();
                       dsb_b->GetCurrentPosition(&pos, 0);
                       ring_pos = pos / data.ring_size;
                       } while(config::system.regulate_speed == true
    && ring_pos == data.ring_pos);

                       data.ring_pos = ring_pos;
                       void *output;
                       if(dsb_b->Lock(((data.ring_pos + 2) % 3) *
    data.ring_size,
                       data.ring_size, &output, &size, 0, 0, 0) ==
    DS_OK) {
                       //Audio::resample_hermite((uint32*)output,
    data.buffer, latency, data.buffer_pos);
                       memcpy(output, data.buffer, data.ring_size);
                       dsb_b->Unlock(output, size, 0, 0);
                       }

                       data.buffer_pos = 0;
                       }

                       bool VideoDD::lock(uint16 *&data, uint &pitch)
    {
                       if(video_buffer[video_active]->Lock(0, &ddsd,
    DDLOCK_WAIT, 0) != DD_OK) return false;
                       video_valid[video_active] = false;
                       pitch = ddsd.lPitch;
                       data = (uint16*)ddsd.lpSurface;
                       return data;
                       }

                       void VideoDD::unlock() {
                       video_buffer[video_active]->Unlock(0);
                       }

                       void VideoDD::refresh() {
                       video_valid[video_active] = true;
                       video_active ^= 1;
                       tick();
                       }

                       void VideoDD::tick() {
                       if(video_valid[0] == false && video_valid[1] ==
    false) return; //nothing to render
                       uint idx = video_valid[!video_active] == true ?
    !video_active : video_active;
                       // if(video_valid[!video_active] == false)
    return;
                       //uint idx = !video_active;

                       if(settings.triple_buffering == true) {
                       BOOL in_vblank;
                       lpdd7->GetVerticalBlankStatus(&in_vblank);
                       if(in_vblank == false) return;

                       //DWORD scanline;
                       // lpdd7->GetScanLine(&scanline);
                       // if(scanline < screen_height()) return;

                       //
    lpdd7->WaitForVerticalBlank(DDWAITVB_BLOCKBEGIN, 0);
                       }

                       HRESULT hr;
                       RECT rd, rs;
                       snes.get_video_info(&vi);
                       SetRect(&rs, 0, 0, vi.width, vi.height);

                       POINT p = { 0, 0 };
                       ClientToScreen(hwnd, &p);
                       GetClientRect(hwnd, &rd);
                       OffsetRect(&rd, p.x, p.y);

                       hr = screen->Blt(&rd, video_buffer[idx], &rs,
    DDBLT_WAIT, 0);
                       video_valid[idx] = false;

                       if(hr == DDERR_SURFACELOST) {
                       screen->Restore();
                       video_buffer[0]->Restore();
                       video_buffer[1]->Restore();
                       }
                       }




               What I'm basically doing is:
 Audio keeps a ring buffer, and waits until the temporary buffer fills
up before forcing the emulator to sleep until the audio playback
catches up. Every time an audio sample is generated, and every time
the emulator sleeps for one millisecond, it gives Video a chance to
run.

 Video has two backbuffers (a poor man's triple buffering, since that
doesn't work in windowed mode for DDraw). The PPU renders the entire
screen line by line, but it doesn't go from the PPU to the video card
until Video::video_lock is called. At this time, the current buffer
sets a flag to say it's contents are invalid, then it draws to the
frame, then sets a flag saying the current contents are again valid.
Finally, it calls the Video tick function to finish.

 Every time the Video tick function is called from Audio (well over
32,000 times a second, so it should have good precision for detecting
vblank edges).

 First, it will see if any frames have completely rendered. If not, it
will give up and return. Next, it will see if "triple buffering"
(really a vsync now, but emulates triple buffering at least) is
enabled. If so, it will return and do nothing if not in vblank.
Otherwise, or if triple buffering is disabled, it will continue. Next,
it finds the most recently rendered video frame that was valid and
blits that to the screen, and then sets that frame to invalid, so that
it is not rendered again (though it wouldn't hurt, it wastes CPU time
to blit the same image twice).

 I've tried even adding in a 1ms interrupt timer to try and help with
any emulation code that might be freezing the emulator for over an
entire vblank (nothing in bsnes should be that intensive), and this
did not help either.

 Basically, it's like I'm missing an unbelievable amount of frames,
like five out of six end up never getting drawn at all, so the video
is so choppy it's completely unusable. In reality, only one frame
should be dropped every 11 seconds. And when I enable the resampler,
that should change to only one frame every 66 seconds.

 As a side note, I added a four-tap hermite resampler in. It sounds
good too, but I have no idea if it's better or worse than cubic.

[No archive available]
2007-02-03 13:10:00 +00:00
byuu 6d66b1136d Update to bsnes v019r09 release.
Alright, I'm in a semi-good mood.







    http://byuu.cinnamonpirate.com/files/bsnes_v019_wip9.zip




 This one uses the old win32 interface, and adds a new feature I'd
like people with sound troubles to try out. The config file now
contains "audio.latency". Don't mess with "audio.frequency", it won't
do you any good and gets overridden by the speed regulation settings
for now.

 The audio.latency is a precise measurement of the millisecond delay
between sound being output by a real SNES and hearing that same sound
in bsnes. It takes into account the current playback frequency, as
well as the three-ring buffering system used by bsnes' audio system.
 Formula: sample_latency = CURRENT_playback_frequency / 1000 *
config_file_latency * 3 (so 32khz + 75ms latency means each ring
buffer is 800 samples long). The new formula should make latency sound
better (it's consistent now) on fast / slow emulation speed throttling
settings as well.
 I also cut out the fourth ring, since it was redundant. This should
make bsnes appear ~25% more responsive to sound with the same buffer
latency. As a result, I increased the latency to 75ms (it was at ~45
before).
 I'd like to know what the lowest good value is that works on 95% of
sound cards, so I can use that. I'll let people with cheap sound cards
increase their latency setting manually (eventually it will be an
option in the GUI).

               **NOTE:** this version does nothing for triple
buffering/vsync/whatever. You must _disable_ triple buffering to try
out the latency settings. This version is strictly to test audio
playback support.

 I also added in my audio point resampler. Good god, it sounds
terrible. Regardless of the latency setting (either really high or
really low), the pitch difference between each audio ring is
_extremely_ noticeable. The code is there now in
src/ui/audio/dsound.cpp : AudioDS::run_videosync(), if anyone would
like to take a look. I'll hold my breath ;)

               -----

 Comparisons against ZSNES at this point are rather silly. Aside from
much more flexible timings, it probably has a nice audio resampler,
which I don't. If I faked CPU/SMP clock timings, I could get the SNES
spitting out 60 frames a second and 32khz audio a second. I'm not
going to do that, so I have to figure out how to resample the two. All
of my attempts at resampling video _and_ audio have both failed
miserably to date. I really only need one of those to work to get
smooth video+audio, but both would be nice so the user can decide
what's more important to them.

I don't care to add 2xSaI. I'm planning on redoing the filter stuff
soon to support 32-bit output for Xv, so if someone wants to add 2xSaI
support to bsnes after that, I'll add it in. Otherwise, HQ2x is
superior and Scale2x looks about the same, yet is way faster.

 Regarding the IPS thing, exactly. As I said, IPS is a bad format. You
can't tell if you need to patch against a headered or unheadered ROM
unless you read the documentation that fuckheads like Cowering remove
in their ROM sets ("at least it's already prepatched"), or try
patching twice to see which one works. UPS will eliminate both of
these problems. Readmes will be included inside the patches, and UPS
will work regardless if your ROM has a header or not. It will also be
reversible. It'll be better in every regard over IPS, so I have no
reason to support IPS.

 Lastly, I don't have any intention of working on fixing DeJap's
patch, regardless of where the problem is, as I have no way to run the
game on my copier. Maybe when and if the last two serious bugs
(Uniracers and Koushien 2) get fixed, I'll take a look at it then.
2007-01-31 00:00:24 +00:00
byuu b01f18c34c Update to bsnes v019r01? release.
First screenshots of libui in bsnes:

             [image] [image]

 Same exact codebase. The current WIP is obviously missing any
semblance of a GUI, other than the menubar and a ROM file loader.

 Lots of issues on both ports, of course. I'm aware of the audio
repeating issue on the Windows port and already know how to fix it
(had the same problem with the old Windows UI). Linux of course simply
has no audio or input.

 I'm planning on moving the framerate counter to display inside the
image, rather than on the titlebar this time. That of course won't
happen anytime soon. I don't expect to be adding fullscreen support
back in anytime soon, either.

 Once this port gets stable enough, I intend to remove the "ui/win"
and "ui/sdl" ports completely. After that, I'm going to have to start
seriously rewriting a lot of internal stuff.

 I'm also planning to go with a simpler user interface this time
around. bsnes v0.019 had too many options and features. I think I may
scale back this time and make things a lot simpler. Move a lot of the
control settings back into the menubar, rather than in the custom
options panel (which will most likely still exist).

[No archive available]
2007-01-15 04:25:00 +00:00
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
2079 changed files with 61641 additions and 156473 deletions

No files matched your search

-3
View File
@@ -1,3 +0,0 @@
higan/profile/WonderSwan.sys/internal.ram
higan/profile/WonderSwan Color.sys/internal.ram
docs_build/
-47
View File
@@ -1,47 +0,0 @@
# NOTE: This file is not part of the official higan source, it's been added
# to help build WIP binaries with minimal fuss.
image: debian:stable
linux-x86_64-binaries:
script:
- apt-get update && apt-get -y install build-essential libgtk2.0-dev libpulse-dev mesa-common-dev libgtksourceview2.0-dev libcairo2-dev libsdl1.2-dev libxv-dev libao-dev libopenal-dev libudev-dev
- make -C icarus compiler=g++
- make -C higan compiler=g++
- mkdir higan-nightly
- cp -a icarus/out/icarus higan-nightly/icarus
- cp -a icarus/Database higan-nightly/
- cp -a higan/out/higan higan-nightly/higan
- cp -a higan/systems/* higan-nightly/
- cp -a shaders "higan-nightly/Video Shaders"
artifacts:
paths:
- higan-nightly/*
cache:
paths:
- icarus/obj/*.o
- higan/obj/*.o
windows-x86_64-binaries:
# This is a normal Windows cross-compile process, except that
# nall::chrono tries to use clock_gettime on Windows even
# though it's a POSIX function, and for some weird reason mingw has
# clock_gettime() in the pthread library.
script:
- apt-get update && apt-get -y install build-essential mingw-w64
- sed -i -e 's/-lole32/& -static -lpthread/' nall/GNUmakefile
- make -C icarus platform=windows compiler="x86_64-w64-mingw32-g++ -static-libgcc -static-libstdc++" windres="x86_64-w64-mingw32-windres"
- make -C higan platform=windows compiler="x86_64-w64-mingw32-g++ -static-libgcc -static-libstdc++" windres="x86_64-w64-mingw32-windres"
- mkdir higan-nightly
- cp -a icarus/out/icarus higan-nightly/icarus.exe
- cp -a icarus/Database higan-nightly/
- cp -a higan/out/higan higan-nightly/higan.exe
- cp -a higan/systems/* higan-nightly/
- cp -a shaders "higan-nightly/Video Shaders"
artifacts:
paths:
- higan-nightly/*
cache:
paths:
- icarus/obj/*.o
- higan/obj/*.o
-9
View File
@@ -1,9 +0,0 @@
Contributing to higan
=====================
If you would like to propose a change to higan,
you should create an account on the [official forums][f],
go to the "Projects" forum and the "higan" subforum,
and post your idea in a new topic there.
[f]: https://board.byuu.org/
-48
View File
@@ -1,48 +0,0 @@
The unofficial higan repository
===============================
higan emulates a number of classic videogame consoles of the 1980s and 1990s,
allowing you to play classic games on a modern general-purpose computer.
This repository includes
the source-code for
stable and WIP releases of higan,
starting during the development of v068.
It also includes community-maintained documentation.
Basically,
apart from `.gitignore` files,
anything in the
[higan](higan/),
[hiro](hiro/),
[icarus](icarus/),
[libco](libco/),
[nall](nall/),
[ruby](ruby/),
or [shaders](shaders/)
directories should be exactly as it appeared in official releases.
Everything else has been added for various reasons.
Official higan resources
------------------------
- [Official homepage](https://byuu.org/emulation/higan/)
- [Official forum](https://board.byuu.org/viewforum.php?f=4)
Unofficial higan resources
--------------------------
- Documentation for
[the current stable version][stadocs]
- [Source code repository](https://gitlab.com/higan/higan/)
archives official higan releases
and WIP snapshots
since approximately v067r21
- [Latest WIP build for Windows][wipwin]
- Documentation for
[the latest WIP version][wipdocs]
[wipwin]: https://gitlab.com/higan/higan/-/jobs/artifacts/master/download?job=windows-x86_64-binaries
[stadocs]: https://higan.readthedocs.io/
[wipdocs]: https://higan.readthedocs.io/en/latest/
BIN
View File
Binary file not shown.
-12
View File
@@ -1,12 +0,0 @@
#!/bin/sh
# This uses the official W3C link-checker tool:
#
# https://github.com/w3c/link-checker
#
checklink \
--summary \
--broken \
--location=http://127.0.0.1:8000/ \
--exclude 'github.com|board.byuu.org' \
http://127.0.0.1:8000/
-141
View File
@@ -1,141 +0,0 @@
What is a game folder?
----------------------
TODO
Why game folders?
-----------------
A game is more than just
the raw data originally encased in a game's ROM chip.
If a game allows you to save your progress,
that information needs to be stored somewhere.
If you use an emulator's [save states](#save-states),
those save states need to be stored somewhere.
If you use Game Genie or Pro Action Replay codes,
information about what codes exist,
what codes are enabled,
and what they do
needs to be stored somewhere.
On the technical side,
a physical game cartridge contains a circuit board
that makes the game data available to the console,
and different games used circuit boards that work differently.
That circuit-layout information needs to be stored somewhere.
Some games included custom processors
to do calculations the base console could not do quickly enough
(like the SuperFX chip used in _StarFox_ for the Super Famicom)
and information about extra chips needs to be stored somewhere.
Some of those custom processors require extra data to work
that's not part of the main game data
(like the DSP chip used in Super Mario Kart for the Super Famicom)
and that data needs to be stored somewhere too.
higan keeps all this game-related information together
in a single place:
a game folder in the higan library.
For a more detailed motivation for game folders,
see [Game Paks on the higan website][gp]
[gp]: https://byuu.org/emulation/higan/game-paks
What is a manifest?
-------------------
TODO
The most important file in a game folder is `manifest.bml`,
which describes how all the other files should be wired together
to create a runnable game cartridge.
However,
the manifest format has occasionally changed
as new emulation details were uncovered
that could not be represented in the old format.
Therefore,
icarus [defaults](#the-icarus-settings-dialog)
to not writing out manifests when it imports games,
and higan [defaults](#the-configuration-dialog)
to ignoring manifests that are present.
Instead,
when higan loads a game,
it will ask icarus to generate a temporary manifest in the latest format,
based on the files present in the game folder
and how they are likely to go together.
You can view this temporary manifest
in [the Manifest Viewer](#the-manifest-viewer).
What's in a game folder?
------------------------
As mentioned [above](#why-game-folders),
a game folder collects all the information relevant
to emulating a particular game.
Not all of the following files
are relevant to every emulated console,
or to every game on a given console,
but they may be relevantunder particular circumstances.
All the files directly in the game folder
are expected to be useful
to all emulators that support them:
- `manifest.bml`:
The [manifest](#what-is-a-manifest)
for this game folder.
- `program.rom`:
For most consoles,
this contains
the executable instructions and graphics data
from the cartridge's ROM chips.
For the Famicom,
this contains only the executable instructions.
- `character.rom`:
For the Famicom,
this contains only the graphics data
from the cartridge's ROM chips.
- `ines.rom`:
While other consoles typically include enough hints
in `program.rom` for icarus to generate a manifest,
the Famicom does not.
Famicom games not stored in game folders
typically include an "iNES header" to store that information,
which icarus preserves after import as `ines.rom`.
- `save.ram`:
Games that include a save feature
will create this file.
Note that it is only written to disk
when higan exits gracefully,
if higan crashes or is forced to quit,
in-game saves may be lost.
Other emulators sometimes call this an "SRAM file",
even though the same filename is used
for cartridges that use EEPROM or Flash storage,
not just battery-backed Static RAM.
- `rtc.ram`:
Games that include a calendar or real-time clock
will create this file.
- `*.data.rom`, `*.program.rom`:
Files named like this are usually
[co-processor firmware](#importing-and-playing-games-with-co-processor-firmware).
- `msu1.rom`:
Holds streamable data for
[the MSU-1](#importing-and-playing-MSU-1-games).
- `track-*.pcm`:
Holds streamable audio for
[the MSU-1](#importing-and-playing-MSU-1-games).
Files that are only useful to higan specifically
are placed in a `higan` subdirectory:
- `cheats.bml`:
All information present in
[the Cheat Editor](#the-cheat-editor)
is stored here.
- `states/quick/slot-*.bst`:
All the save states made to
[Quick state slots](#quick-states).
- `states/managed/slot-*.bst`:
All the save states made with
[the State Manager](#the-state-manager).
-56
View File
@@ -1,56 +0,0 @@
higan maintains a "game library"
containing all the games you've played.
- In Windows,
the default location of
the game library is the `Emulation` folder
inside your profile folder
(To find your profile folder,
press `Win+R` to open the Run dialog,
then type `%USERPROFILE%` and press Enter).
- In Linux,
the default location of
the game library is the `Emulation` directory
inside your home directory.
- On all platforms,
the game library location can be configured.
See [Moving the Game Library](#moving-the-game-library)
below.
Inside the library directory there is a subdirectory for each system,
and inside each system directory are the game folders
for each imported game.
For more information about game folders,
see [Why game folders?](#why-game-folders)
and [What's in a game folder?](#whats-in-a-game-folder)
below.
Moving the game library
-----------------------
Moving the game library is a little complicated,
because there's two parts to it:
telling icarus where to put imported games,
and telling higan where to find them.
1. If necessary,
create the folder you want higan to use
as its game library.
1. Launch icarus,
then click the "Settings ..." button in the lower-right,
to open the Settings dialog.
1. Click the "Change ..." button on the right.
A [filesystem browser](#the-filesystem-browser) window will appear,
allowing you to choose
where imported games will be stored.
1. Launch higan,
then from the Settings menu,
choose "Configuration ..."
to open [the Configuration dialog](#the-configuration-dialog).
1. Click the Advanced tab
then click the "Change ..." button.
A [filesystem browser](#the-filesystem-browser) will appear,
allowing you to choose the same directory again.
-129
View File
@@ -1,129 +0,0 @@
A real game console
is a complex piece of hardware,
with electricity flowing through it
in complex patterns that can't easily
be paused or recorded.
However,
an emulated console is pure software:
it only changes when the emulation software
deliberately updates it,
so the emulator software can save
the entire state of the emulated console
to disk,
and weeks or months later
bring it back to life
as though nothing had ever happened.
Save states versus in-game saves
--------------------------------
Some games include their own systems
for saving and restoring the player's progress.
Here are some of the differences
between save states and in-game saves:
<table>
<thead>
<tr>
<th>Save states</th>
<th>In-game saves</th>
</tr>
</thead>
<tbody>
<tr>
<td>
Work exactly the same way
in every game
</td>
<td>
Works differently in different games,
some games don't support it at all
</td>
</tr>
<tr>
<td>
Save at any time,
anywhere in the game
</td>
<td>
Save only at special save-points
</td>
</tr>
<tr>
<td>
Loading a save
puts the game back exactly how it was
when you pressed save
</td>
<td>
Loading a save
restores some things
(like the player's inventory
or remaining lives),
but may
forget others
(like taking you back to the beginning of the level)
</td>
</tr>
<tr>
<td>
You can have dozens of save states
</td>
<td>
Most games limit you to about 3 saves
</td>
</tr>
<tr>
<td>
Can only be loaded
by the same version of the same emulator
that created it
</td>
<td>
Works with any version of any emulator,
can sometimes even be copied to or from physical cartridges
</td>
</tr>
</tbody>
</table>
**Note:**
Loading a save state
will reset the entire emulated console
to the way it was when the save state was created,
*including in-game saves*.
If you create a save state,
then make an in-game save,
then load the save state,
**your in-game save will be lost**.
Don't do that.
Quick states
------------
higan has five Quick State slots,
which can be used from
[the Tools menu](../interface/higan.md#the-tools-menu),
or with the appropriate
[hotkeys](../interface/higan-config.md#hotkeys).
Quick states are useful
as extra checkpoints
in games that don't have them,
or where they aren't close enough together.
Map the "Save Quick State" and "Load Quick State" hotkeys
to your controller,
and you can cheese your way through just about anything.
Manager states
--------------
higan's
[State Manager](../interface/higan-tools.md#the-state-manager)
allows you to create over a hundred save states,
and add a helpful description to each one.
Manager States are more cumbersome to use than Quick States,
since they cannot be accessed with hotkeys,
but are useful when you want quick access
to many different parts of a game.
-279
View File
@@ -1,279 +0,0 @@
I see "tearing" when a game scrolls. How can I enable vsync?
------------------------------------------------------------
higan supports synchronizing video output
to the display's vertical-synchronization (or "vsync") signal,
but the option is hidden
because it often causes more problems than it solves
(see the next question).
To enable video synchronization:
- Open the higan's configuration file, `settings.bml`
- On Windows, look in `%LOCALAPPDATA%\higan`
or beside `higan.exe`
- On Linux, look in `~/.local/share/higan`
- Open it in your favourite text editor
(Windows Notepad will corrupt the file,
use WordPad if you don't have anything better)
- Find the line that says "Video"
- Somewhere below that, find an indented line
that says "Synchronize:false".
- Change "false" to "true"
- Save your changes to `settings.bml`
and restart higan
Why is video synchronization a problem for higan?
-------------------------------------------------
**The short version:**
Turning on video synchronization
cleans up video tearing,
turning on audio synchronization
cleans up audio glitches,
but turning on both
makes audio glitches worse.
**The long version:**
Enabling video synchronization
locks the frame-rate of the emulated console
to the frame-rate of your computer's display.
If your display's refresh rate exactly matches
the emulated console's,
games play at the correct speed
and everything's fine.
However,
modern 60Hz displays do not always match
the emulated console's refresh rate:
- The Super Famicom usually runs a little faster than 60Hz
- the PAL variants of most consoles run at 50Hz
- the WonderSwan runs at 75Hz
- While the Game Boy does run its LCD at 60Hz
it can turn it off and on at any time,
requiring emulation to pause
until it can get back in sync
with the computer display.
Because of these frame-rate differences,
enabling video synchronization
can force games to run
faster or slower than intended.
The consoles that higan emulates
produce video frames and audio samples at a particular rate.
If video synchronization causes
the emulated console to run, say, 5% faster than intended,
that means audio samples are also being produced 5% faster.
You might not notice the changed game speed,
but you'll almost certainly notice
the game's audio glitching constantly
as your sound card tries to keep up.
Enabling
[audio synchronization](interface/higan.md#the-settings-menu)
normally fixes this kind of audio glitching,
but with video synchronization it makes things worse:
audio is likely to glitch
while higan waits for a video frame to be shown,
and video is likely to stutter
while higan waits for an audio buffer to complete.
Games run too fast
------------------
higan runs as fast as it can,
but it will pause and wait
for the audio and video drivers to catch up
if [Synchronize Audio](interface/higan.md#the-settings-menu)
and [video synchronization][vsync]
are enabled, respectively.
If games are running way too fast, here's some things to check:
- Make sure "Synchronize Audio" is ticked in
[the Settings menu](interface/higan.md#the-settings-menu)
- Make sure the Audio driver is not set to "None"
in [the Advanced settings](interface/higan-config.md#advanced)
(remember to restart higan if you change driver settings)
- Make sure your computer has speakers or headphones connected
(some computers disable all audio if no ouputs are available)
- If you want the game to be silent,
tick "Mute Audio" in
[the Settings menu](interface/higan.md#the-settings-menu)
[vsync]: #i-see-tearing-when-a-game-scrolls-how-can-i-enable-vsync
Games run too slow
------------------
Of all the consoles higan can emulate,
higan's Super Famicom emulation
is the most resource intensive.
Full-speed emulation for the Super Famicom base unit
requires an Intel Core 2 Duo (or AMD equivalent),
full-speed for games with the SuperFX chip
requires an Intel Ivy Bridge (or equivalent),
full-speed for the wireframe animations in Mega Man X2
requires an even faster computer.
Low-power CPUs like ARM chips,
or Intel Atom and Celeron CPUS
generally aren't fast enough to emulate the Super Famicom with higan,
although other emulated consoles may work.
If your computer meets the general speed requirements
but games run too slowly,
try choosing a different
[audio driver](interface/higan-config.md#advanced),
since that's usually what drives higan's timing.
On some computers,
the operating system's power-management system
may be confused by higan's unusual pattern of CPU usage
(it runs furiously to calculate the next video frame
and the next few milliseconds of audio,
then stops dead as it waits for output to complete).
If holding down
the [fast forward hotkey](interface/higan-config.md#hotkeys)
runs too fast but the game normally runs too slow,
try disabling "power saver" mode
or enabling "performance" mode.
On Linux specifically,
if your desktop environment doesn't provide a way
to change the CPU usage governor,
you can temporarily force your CPUs to run at full-speed
by running the following command in a terminal:
echo "performance" | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
To put things back to normal,
reboot the computer, or run this command instead:
echo "ondemand" | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
Why is higan so much slower than other emulators?
-------------------------------------------------
The consoles higan emulates
generally contain different devices
(say, a CPU, an audio chip and a video chip)
that do their own thing,
but can interrupt each other at any time.
Games can and do depend on timing details like
"when the audio chip is done playing this sound,
it will interrupt the CPU at exactly the right time
for the CPU to fiddle with the video chip".
higan is therefore very cautious about timing:
while it's emulating the audio chip (for example),
at every point the emulated CPU *might* interrupt
the emulated audio chip,
higan switches to emulating the CPU up to the same point
to find out whether the CPU *will* interrupt it.
In this way,
higan is a little bit like
an office-worker trying to do the jobs of three other people
by running from desk to desk,
sending the same emails
that those three people would send to each other,
leaving themselves a note at each desk to remind themselves
where they were up to when they come back.
Although this constant switching
is slow and inefficient,
higan does it
to ensure the emulated console
always matches the behaviour
of the original.
Other emulators,
especially ones that are popular on phones and tablets,
typically work differently.
Although emulated devices *can* interrupt each other at any time,
usually they don't,
and often the only exceptions are a handful of unpopular games.
So it's pretty safe
(and much, much more efficient)
to let certain devices run "ahead" of the others.
For example,
letting the video chip render an entire scanline at a time
instead of working pixel-by-pixel.
The downside is that some games don't work properly
in these emulators,
with game-breaking bugs,
minor visual glitches,
or anything in between.
Some emulators try to recognise
when the user loads a specific problematic game,
and will adjust the shortcuts the emulator takes
so that the game runs correctly.
This is a good way to increase game compatibility
without losing efficiency,
but it means the emulator does not accurately reflect
how the original console worked,
and it requires the emulator authors to diagnose and work around
each individual supported game,
instead of having one emulator that works for everything.
Why can't higan use multiple CPU cores?
---------------------------------------
These days,
most computers contain multiple CPU cores,
allowing them to run different programs,
or different parts of the same program
at the same time.
Since higan requires high CPU performance,
sometimes people suggest that it should split its work
into multiple threads
so it can run across multiple cores
instead of stressing one core while the others lie idle.
After all,
the original consoles higan emulates
had separate devices running independently,
so why not run each emulated device in a separate thread?
The big problem with this approach is timing.
The devices in a physical Super Famicom (for example)
run at whatever speed they run at,
and talk to each other whenever they feel like.
Because every Super Famicom uses the exact same components,
every Super Famicom runs compatible games at the exact same speed,
and games can blindly assume that
when operation X is complete on device A,
device B will have finished operation Y
and be ready to do something new.
Meanwhile, higan's emulated components
take an unpredictable amount of time to do their work,
so without deliberate synchronization
things would break almost immediately.
It's not practical to make higan's emulated devices
do their work in exactly the same amount of time
as their hardware counterparts.
The problem is forty years of technology
designed to make programs run as fast as possible:
optimizing compilers and superscalar, out-of-order CPU architectures
change programs to make them faster,
speeding up some programs more than others
in ways that are very difficult to understand and predict.
Even if higan's emulated devices
ran at the exact, correct speed
on one particular computer,
they'd still run differently on any other computer,
or with a smarter compiler,
or with a smarter CPU.
Since higan needs its emulated components
to run at particular speeds,
and they won't run at those speeds naturally,
it must force them manually.
An emulated device runs for a little while,
then all the others are run until they catch up.
It's this careful management,
regular stopping and starting,
that makes higan slow,
not the actual emulation of each device,
and so it doesn't make sense
for higan to be multi-threaded.
-145
View File
@@ -1,145 +0,0 @@
Unfortunately,
there's no standard for
displaying video,
playing audio,
and accepting input from game controllers
that works on every operating system.
Or rather,
there's many standards,
and different ones work best
on different computers.
Therefore,
higan comes with "drivers"
for video, audio and input,
so you can find the one that works best for you.
To see what drivers you're currently using,
or to choose different ones,
go to
[the Advanced tab](../interface/higan-config.md#Advanced)
of the Settings window.
Here are the most notable drivers
for each platform
for each category.
If your copy of higan
includes a driver not listed here,
it's probably a reasonable choice,
so try it out and see how you like it.
**Note:** After changing any driver,
you must restart higan for the change to take effect.
**Note:**
Video, Audio and Input
all have a special driver named "None".
This is a dummy driver that does nothing
(draws no video,
plays no audio,
accepts no input),
so don't choose it unless you're
trying to diagnose problems with other drivers.
Video
-----
On Windows:
- **OpenGL** is usually the best choice,
since it supports [custom shaders](shaders.md),
however it does require support for OpenGL 3.2
which excludes some integrated graphics chipsets
and old graphics cards.
- **Direct3D** is a good choice
if OpenGL is unavailable.
It also allows
[Exclusive fullscreen](../interface/higan-config.md#video),
bypassing Windows' desktop compositor.
- **GDI** is the safest choice,
but performs very poorly at large sizes.
On Linux:
- **OpenGL** is the best choice,
since it's fast
and it supports [custom shaders](shaders.md),
but requires OpenGL 3.2.
You can check what version of OpenGL
your system supports by running
`glxinfo | grep 'core profile version'`
in a terminal.
- **XVideo** is also fast,
but may be low-quality,
and generally only supports the "Blur" shader,
not "None".
- **XShm** is the safest choice,
but performs very poorly at large sizes.
Audio
-----
On Windows:
- **ASIO** offers the lowest possible latency,
but is the least likely to work on any given computer.
- **WASAPI** offers low latency,
but is only slightly more likely to work.
It also offers
[Exclusive Mode](../interface/higan-config.md#audio),
which can improve audio quality and lower latency,
but may be better or worse than shared mode
in practice.
- **XAudio2** is a good choice,
but it requires the latest (June 2010) version
of the [DirectX 9 End-User Runtime][dx9]
to be installed.
- **DirectSound** is the safest choice,
and not a bad one.
[dx9]: https://www.microsoft.com/en-us/download/details.aspx?id=35
On Linux:
- **PulseAudio** or **PulseAudioSimple**
are almost certainly the drivers to use,
since almost every Linux distribution uses
[PulseAudio](https://en.wikipedia.org/wiki/PulseAudio)
to manage audio output.
The two drivers should behave identically,
but some users report one working better than the other.
- **ALSA** is a good choice
for Linux distributions that do not use PulseAudio.
If PulseAudio is running,
this will wind up using PulseAudio's ALSA emulation,
or failing due to "sound card already in use",
unless you use a tool like `pasuspender`.
- **OSS** may be useful
for non-Linux platforms,
or for Linux systems with the third-party OSSv4
kernel drivers installed.
For most Linux systems,
this will use ALSA's OSS emulation,
PulseAudio's OSS emulation,
or not work at all.
TODO: If the audio driver is set to None,
or you have no audio device,
Sync Audio does not work
and games will run in fast-forward
unless you enable Sync Video.
https://board.byuu.org/viewtopic.php?p=44138#p44138
Input
-----
On Windows,
"Windows" is the only driver available,
and uses RawInput for keyboard and mouse input,
XInput for Xbox controllers,
and DirectInput for other controllers.
On Linux:
- **udev** supports every input device,
but requires a modern Linux system.
- **Xlib** is the safest choice,
but only supports keyboard and mouse input.
-560
View File
@@ -1,560 +0,0 @@
Before it can load a game,
higan requires that all the game's data
be stored correctly in
[the Game Library](../concepts/game-library.md).
For [regular games](#regular-games)
this is simple,
but some games require special treatment,
especially games that make use of
unusual hardware.
Regular games
-------------
icarus supports importing games
in the most commonly-used formats
for each supported console,
and also those same formats inside `.zip` files.
More advanced compression formats
like RAR or 7-zip are not supported.
For most games
that do not use special chips or co-processors,
importing a game is straight-forward.
From [the Library menu](#the-library-menu)
choose "Load ROM File ..."
to open [a filesystem browser](#the-filesystem-browser),
choose the game you want to play,
and it will be imported into the library and loaded.
To play the game again
select the console the game runs on from
[the Library menu](#the-library-menu)
to open another [filesystem browser](#the-filesystem-browser)
that lists all the previously-imported games for that platform.
Games with co-processor firmware
--------------------------------
Many games included extra chips inside the game cartridge,
to provide enhanced capabilities of one kind or another.
Sometimes,
those extra chips were separate CPUs
running their own separate firmware,
and for those cases
higan requires a copy of the co-processor firmware
as well as the actual game.
Unfortunately,
like games themselves,
co-processor firmware cannot legally be distributed,
so you'll need to obtain
copies of the relevant firmware data
yourself.
To import a game that requires co-processor firmware,
you must copy the required firmware files
beside the game you want to import.
For example,
if you want to import Megaman X2,
which is stored in the file `mmx2.sfc`,
the file `cx4.data.rom`
must be placed in the same folder
for the import to succeed.
Wikipedia [lists which Super Famicom games use which co-processors][wpec],
although not all co-processors require separate firmware.
Once you've figured out which co-processor
(if any)
is used by the game you want to import,
here's the firmware files you'll need:
[wpec]: https://en.wikipedia.org/wiki/List_of_Super_NES_enhancement_chips#List_of_Super_NES_games_that_use_enhancement_chips
<table>
<thead>
<tr>
<th>Co-processor</th>
<th>Filename</th>
<th>Size (bytes)</th>
<th>SHA256</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">CX4</th>
<td><code>cx4.data.rom</code></td>
<td>3072</td>
<td><code>ae8d4d1961b93421ff00b3caa1d0f0ce7783e749772a3369c36b3dbf0d37ef18</code></td>
</tr>
<tr>
<th scope="row" rowspan=2>DSP1/1A<br><sup>See Note 1</sup></th>
<td><code>dsp1.data.rom</code></td>
<td>2048</td>
<td><code>0b5da6533e55852ee8fc397977ec5576c5b9f1fb2e05656d8f87123a121b076e</code></td>
</tr>
<tr>
<td><code>dsp1.program.rom</code></td>
<td>6144</td>
<td><code>269584b347a22953a2989494c850a7c1c027f4ca5add517a60e0c7d8833d0fac</code></td>
</tr>
<tr>
<th scope="row" rowspan=2>DSP1B<br><sup>See Note 2</sup></th>
<td><code>dsp1b.data.rom</code></td>
<td>2048</td>
<td><code>8546cbac530830446bb8a277f6b139d4ad64d650bdbac7e4e150e2f095665049</code></td>
</tr>
<tr>
<td><code>dsp1b.program.rom</code></td>
<td>6144</td>
<td><code>2eccb54a8f89374911f7e2db48f1b4cde855655e28103f7bda2982a5b418a187</code></td>
</tr>
<tr>
<th scope="row" rowspan=2>DSP2</th>
<td><code>dsp2.data.rom</code></td>
<td>2048</td>
<td><code>3beef9bffdc1e84c9f99f3301d8bd3e520d2e62909a995320f9faeae8f46ec11</code></td>
</tr>
<tr>
<td><code>dsp2.program.rom</code></td>
<td>6144</td>
<td><code>62a2ef8d2d7db638f4ec0fbcebf0e5bf18a75ee95be06e885d9519a10487f0da</code></td>
</tr>
<tr>
<th scope="row" rowspan="2">DSP3</th>
<td><code>dsp3.data.rom</code></td>
<td>2048</td>
<td><code>7fe51796e9c97fee1fa2aab40592b7c78997f67dd00333e69d0f79a12f3cb69f</code></td>
</tr>
<tr>
<td><code>dsp3.program.rom</code></td>
<td>6144</td>
<td><code>aea7b622e7c1de346cb15d16afcbedf92b6798507e179f83ed2a4cff40d0e663</code></td>
</tr>
<tr>
<th scope="row" rowspan="2">DSP4</th>
<td><code>dsp4.data.rom</code></td>
<td>2048</td>
<td><code>ef3ffb4256dd896a60213269b4e2d3bdd120c97e2fd623bddabbf43c2be422af</code></td>
</tr>
<tr>
<td><code>dsp4.program.rom</code></td>
<td>6144</td>
<td><code>89b1826e6038be3a0ea0f923e85d589ff6f02dc1a1819fb2ec8c0cea5b333dcd</code></td>
</tr>
<tr>
<th scope="row" rowspan="2">ST010</th>
<td><code>st010.data.rom</code></td>
<td>4096</td>
<td><code>dc7056a51b53993d7a8ba5bacf9501f785d2fce5e5be748e9ff2737c5938d4a5</code></td>
</tr>
<tr>
<td><code>st010.program.rom</code></td>
<td>49152</td>
<td><code>2c1f74bb5f466d81c64c326e71ac054489efe1abc9a5d6f91aac7747f2ddab67</code></td>
</tr>
<tr>
<th scope="row" rowspan="2">ST011</th>
<td><code>st011.data.rom</code></td>
<td>4096</td>
<td><code>b5377d1bebe8adc507a024b6e2b9b8fdf4877e451da84fbad05dff4e4a70311e</code></td>
</tr>
<tr>
<td><code>st011.program.rom</code></td>
<td>49152</td>
<td><code>d90a5cda380e81cb9ba11a9da7539b173c49b31bedc7a3ac9c3c8b3f97e89e14</code></td>
</tr>
<tr>
<th scope="row" rowspan="2">ST018</th>
<td><code>st018.data.rom</code></td>
<td>32768</td>
<td><code>b5377d1bebe8adc507a024b6e2b9b8fdf4877e451da84fbad05dff4e4a70311e</code></td>
</tr>
<tr>
<td><code>st018.program.rom</code></td>
<td>131072</td>
<td><code>d90a5cda380e81cb9ba11a9da7539b173c49b31bedc7a3ac9c3c8b3f97e89e14</code></td>
</tr>
</tbody>
</table>
**Note 1:**
The DSP1 and DSP1A are physically different,
but the firmware inside is identical.
**Note 2:**
The DSP1B is very similar to the DSP1A,
but has some bugs fixed.
Note that icarus' heuristics cannot distinguish between
a game that uses DSP1
and one that uses DSP1B,
so if it cannot find your game in its manifest database,
it will assume it uses DSP1B.
Many games work just as well with either DSP1 or DSP1B,
but Pilotwings is a notable exception.
If you try to import a game
using the "Import ROM Files ..." option
in [the Library menu](#the-library-menu)
(or using icarus directly)
but do not have the required firmware files
in the correct place,
a window will appear saying
"Import completed, but with 1 errors. View log?"
(or howevery many games were lacking the correct firmware).
If you press "Yes",
a new window will appear listing the games that couldn't be imported,
and at least one firmware file that was missing or incorrect, like this:
> [smk.zip] firmware (dsp1b.program.rom) missing or invalid
If you try to import a game
using the "Load ROM File ..." option
in [the Library menu](#the-library-menu)
but do not have the required firmware files
in the correct place,
nothing will happen,
and higan will just sit there
with "No cartridge loaded" in
[the status bar](#the-status-bar).
Once a game with co-processor firmware is imported,
you can play it just like any [regular game](#importing-and-playing-regular-games).
Satellaview games
-----------------
The [Satellaview][wpbsx]
was a satellite modem peripheral
released for the Super Famicom in Japan.
As well as the actual modem
(designed to sit underneath the Super Famicom),
it also included a cartridge
with software to control the modem,
browse online services,
and download games and data.
This control cartridge was called
*BS-X Sore wa Namae o Nusumareta Machi no Monogatari*,
which translates as
*BS-X The Story of The Town Whose Name Was Stolen*.
[wpbsx]: https://en.wikipedia.org/wiki/Satellaview
The control cartridge had a slot that accepted
rewritable "memory paks",
so that people could store the games and data they downloaded.
A small number of games that did not use the Satellaview modem
also had a memory pak slot,
so the game's publishers could
publish extra content for the game
via the Satellaview service
after the game's release.
For the benefit of people who didn't own a Satellaview
some read-only memory paks
were sold in retail stores
containing extra content for specific games.
Importing a game that has a slot for a memory pak
is just like [importing a regular game](#importing-and-playing-regular-games).
Importing a memory pak is like importing a regular game,
but the name of the memory pak file *must* end in `.bs`
(if it's in a `.zip` file,
that's OK,
but the name *inside* the `.zip` file
must end in `.bs`)
in order for it to be successfully imported.
Sometimes memory pak filenames end in `(BSROM).sfc`,
which will make higan try to import them as
regular Super Famicom games,
and fail miserably.
Rename the file and it should work beautifully.
Playing a game that has a slot for a memory pak
is just like playing a regular game,
but after you have selected which game you want to play
higan will open another
[filesystem browser](#the-filesystem-browser)
to let you pick which previously-imported memory pak
you want to insert into the game.
If you press "Cancel" at this point,
the game will load without any cartridge in its memory pak slot.
If you load the control cartridge into higan,
make sure the emulated Satellaview
is connected to the emulated Super Famicom's expansion port
by going to the "Super Famicom" menu,
selecting the "Expansion Port" sub-menu,
and choosing "Satellaview".
If the expansion port was previously
configured with a different option,
power-cycle the Super Famicom
(also in the "Super Famicom" menu)
to make sure the control cartridge will find the Satellaview
when it starts up.
Note that higan's Satellaview emulation is not very accurate,
so the control cartridge may not work as it should.
Playing a memory pak on its own doesn't make much sense,
it's not a standalone cartridge.
Play a game with a memory pak slot,
and choose which memory pak you want when higan asks for it.
For more information about the Satellaview service,
a translation patch for the control cartridge
and emulators that do a better job of Satellaview emulation,
see [the BS-X Project](https://bsxproj.superfamicom.org/).
Sufami Turbo games
------------------
The [Sufami Turbo][wpst]
was a special cartridge released
for the Super Famicom in Japan.
The Sufami Turbo on its own does nothing,
but it has two slots in the top
that accept Sufami Turbo mini-cartridges.
The game in slot A is the one that actually plays,
but some games can make use of additional data
from a game in slot B.
Importing the Sufami Turbo cartridge
is just like [importing a regular game](#importing-and-playing-regular-games).
Importing a mini-cartridge is like importing a regular game,
but the name of the memory pak file *must* end in `.st`
(if it's in a `.zip` file,
that's OK,
but the name *inside* the `.zip` file
must end in `.st`)
in order for it to be successfully imported.
Sometimes memory pak filenames end in `(ST).sfc`,
which will make higan try to import them as
regular Super Famicom games,
and fail miserably.
Rename the file and it should work beautifully.
To play a Sufami Turbo game,
load the Sufami Turbo cartridge like any other game.
higan will open another
[filesystem browser](#the-filesystem-browser)
to let you pick which previously-imported mini-cartridge
you want to insert into slot A.
If you press "Cancel" at this point,
the Sufami Turbo cartridge will boot without anything in slot A,
which just displays an image telling you
to turn off your Super Famicom,
insert a game into slot A,
and try again.
If you chose a cartridge for slot A,
higan will yet open another
filesystem browser
to let you choose a mini-cartridge for slot B.
If you press "Cancel" at this point,
the Sufami Turbo cartridge will boot without anything in slot B.
[wpst]: https://en.wikipedia.org/wiki/Sufami_Turbo
Super Game Boy games
--------------------
The Super Game Boy was a special cartridge
released for the Super Famicom
(and all its regional variants around the world)
that allowed Game Boy games to be played
via the Super Famicom's controllers and video output.
The Super Game Boy 2 was released in Japan,
and had some minor extra features
beyond the original Super Game Boy,
but importing and playing games
works the same way in higan.
The Super Game Boy cartrige includes
the complete hardware of an original
(black-and-white)
Game Boy,
so it needs a boot ROM:
<table>
<thead>
<tr>
<th>Cartridge</th>
<th>Filename</th>
<th>Size (bytes)</th>
<th>SHA256</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">SGB</th>
<td><code>sgb.boot.rom</code></td>
<td>256</td>
<td><code>0e4ddff32fc9d1eeaae812a157dd246459b00c9e14f2f61751f661f32361e360</code></td>
</tr>
<tr>
<th scope="row">SGB2</th>
<td><code>sgb.boot.rom</code></td>
<td>256</td>
<td><code>fd243c4fb27008986316ce3df29e9cfbcdc0cd52704970555a8bb76edbec3988</code></td>
</tr>
</tbody>
</table>
Yes,
the SGB and SGB2 have different firmware,
but higan expects the same filename for both.
To import the SGB base cartridge,
you must copy the required firmware file
into the same directory.
Then you may import it just like
[a regular game](#importing-and-playing-regular-games).
To play a Game Boy game in Super Game Boy mode,
load the Super Game Boy cartridge like any other game.
higan will open another
[filesystem browser](#the-filesystem-browser)
to let you pick which previously-imported Game Boy game
you want to insert into the Super Game Boy.
If you press "Cancel" at this point,
higan will crash, so don't do that.
Note that only games for the original, black-and-white Game Boy
can be used with the Super Game Boy.
Some games designed for the Game Boy Color
were backward compatible with the original Game Boy
and hence the Super Game Boy;
see [Playing Game Boy Colour games in Game Boy mode][blackcarts]
for details.
[blackcarts]: #playing-game-boy-color-games-in-game-boy-mode
MSU-1 games
-----------
The MSU-1 is a fictional expansion chip
invented by higan's author byuu
for use with Super Famicom games,
designed to allow streaming data and audio.
Although the MSU-1 is not specific
to any particular storage medium,
it gives the Super Famicom similar capabilities
to CD-based add-ons
like the Mega Drive's Mega CD
and the PC Engine's CD-ROM²,
such as CD-quality music and full-motion video.
One thing to be aware of
when importing an MSU-1 game
is that early firmware versions
of the [SD2SNES][sd2snes] programmable cartridge
had a bug that caused MSU-1 music to play too quietly.
Skipping over [the full details][msu1vol],
the short version is this:
- If offered the choice between "boosted" or non-boosted audio,
you want the non-boosted version.
- If an MSU-1 mod for a commercial game offers
"emulator" and "hardware" versions of the patch file,
it means the audio tracks are already boosted.
- Some
[third](https://www.zeldix.net/t1265-#18320)
[parties](https://www.zeldix.net/t1339-#19818)
have created replacement, non-boosted audio tracks
for the most popular MSU-1 mods.
If the mod you want to play has a replacement pack,
use it with the "hardware" version of the patch.
- Even without access to non-boosted audio tracks,
it may be that the existing audio is only slightly boosted,
so try the "hardware" version first, for best quality.
- If the audio tracks are heavily boosted,
the "hardware" patch may sound terrible,
distorting and clipping,
in which case try the "emulator" patch.
To import an MSU-1 game:
1. If you have a single, large file
with the `.msu1` extension,
that is a pack for use with [Mercurial Magic][mermag],
which can automatically set up a game folder
in the correct format.
Go read Mercurial Magic's documentation
instead of these instructions.
2. Otherwise,
import the Super Famicom ROM with icarus,
[like a regular game](#importing-and-playing-regular-games).
- If this is a homebrew game with MSU-1 support,
there will probably be an ordinary ROM
whose name ends in `.sfc`,
which is the file you want to import.
- If this is a commercial game modded for MSU-1 support,
there will probably be a patch file
whose name ends in `.ips` or `.bps`.
Get a copy of the correct version of the commercial game,
apply the patch with a tool like [Flips][flips],
then import the patched file.
- If there's "hardware" and "emulator" versions of the patch,
see "One thing to be aware of..." above.
3. Find the game folder in [the game library](#the-game-library)
that icarus created when it imported the game.
4. Copy the MSU-1 data file into the game folder.
- This should be named `msu1.rom`
- If there's no file by that name,
look for a file with a `.msu` extension
and rename it to `msu1.rom`.
- If there's no file ending in `.msu` either,
create an empty file named `msu1.rom`.
5. Copy the audio tracks into the game folder.
- If you have to choose between two sets of audio files,
see "One thing to be aware of..." above.
- These should be named
`track-1.pcm`,
`track-2.pcm`,
... `track-9.pcm`,
`track-10.pcm`,
etc.
- If there's no files with those names,
there should be other numbered `.pcm` files
that you can rename to match what higan expects.
- If the `.pcm` files have no numbers in the filenames,
there maybe a `.bml` or `.xml` file that lists
which number goes with which file.
- If there's no `.pcm` files at all,
that's OK,
this game probably just doesn't use the audio-playback feature.
Once the game folder is set up,
playing an MSU-1 game is just like
[a regular game](#importing-and-playing-regular-games).
[sd2snes]: https://sd2snes.de/
[flips]: http://www.romhacking.net/utilities/1040/
[msu1vol]: http://blog.qwertymodo.com/2017/07/the-msu-1-volume-fiasco-explained.html
[mermag]: https://github.com/hex-usr/Mercurial-Magic/
Patched games
-------------
The console emulation community
has a long and vibrant history of game modding,
or [ROM hacking][rhdn],
including fan-translations,
new levels for existing games,
and more.
Since distributing the modified versions of existing games
would be copyright infringement,
the changes are typically distributed as "patches",
a file containing a list of modifications to make,
that can be automatically applied by a "patcher" tool
like [Flips][flips].
higan does not support soft-patching,
so if you want to play a patched game in higan,
you will need to use a patcher to apply it yourself,
creating a new, patched copy of the game.
Then you can import and play the patched game just like
[a regular game](#importing-and-playing-regular-games).
[rhdn]: http://www.romhacking.net/
-126
View File
@@ -1,126 +0,0 @@
Most of the consoles higan emulates
were designed for the low resolution of NTSC televisions,
and their video output is often chunky and blocky
by today's standards.
Shaders customise how a console's video output
is drawn to the computer screen,
and can apply just about any effect you can imagine.
Most [drivers](drivers.md)
only support these shaders
(some only support one or the other):
- **None** draws each computer pixel
in the same colour as the nearest console pixel.
This is sometimes called "nearest neighbour scaling",
and produces crisp, blocky output.
- **Blur** draws each computer pixel
as the weighted average colour
of the four nearest console pixels.
This is sometimes called "bilinear scaling",
and hides some of the blockiness
at the expense of blurring edges.
However,
the OpenGL driver supports custom shaders,
in addition to the above.
**Note:**
For technical reasons,
higan's emulation of certain consoles
can produce surprising behaviour
in certain shaders,
particularly shaders that compare each console pixel
with its neigbours.
See [Console-specific Notes](../notes.md) for details.
# Where to get shaders
- higan includes some simple example shaders.
If your copy of higan did not come with shaders,
you can get them from
[the unofficial higan repository](https://gitlab.com/higan/higan/tree/master/shaders).
- [quark-shaders](https://github.com/hizzlekizzle/quark-shaders)
contains many high-quality shaders for use with higan.
- You can write your own.
# How to install shaders
Make sure the shader you want to install
is in the correct format:
it should be a folder whose name ends in `.shader`,
it should contain a file named `manifest.bml`,
and probably some `*.fs` or `*.vs` files.
Place the shader folder inside
the `Video Shaders` directory
of your higan installation.
If you don't have a `Video Shaders` directory,
create it beside the `*.sys` directories
like `Game Boy Advance.sys` and `Super Famicom.sys`.
- On Windows,
this is probably the directory containing `higan.exe`
- On Linux,
this is probably `~/.local/share/higan`
Launch higan,
open the Settings menu,
and choose "Advanced ..."
to open [the Advanced tab](../interface/higan-config.md#advanced)
of the Settings dialog.
Under "Driver Selection",
make sure "Video" is set to "OpenGL".
If you changed the video driver,
you'll need to restart higan
for the change to take effect.
Open the Settings menu again,
choose the "Video Shader" submenu,
and now the shaders you installed
should be listed at the bottom of the menu.
Load a game
(so you can see the results)
and switch between shaders
to see what they do
and pick your favourite!
# Notable examples
The quark-shaders repository
contains lots of carefully-crafted shaders,
but some are particularly noteworthy:
- **AANN** implements "anti-aliased nearest neighbour" scaling.
If the console's video is not displayed
at an exact multple of the console's native resolution,
rounding errors cause normal nearest-neighbour scaling
to draw some rows and columns wider than others,
which many people find ugly and distracting.
This is very common when
higan's aspect-ratio correction mode
is enabled.
AANN uses very slight anti-aliasing
to hide the rounding errors,
leaving the overall image as crisp as nearest-neighbour.
- **Gameboy** emulates the squarish aspect-ratio
greenish-colours
and limited palette
of the original Game Boy.
At larger scales,
you can even see the fine gaps between each pixel,
and the shadow that dark colours would cast
on the LCD background.
- **NTSC** performs NTSC encoding,
bandwidth limiting,
and NTSC decoding of the video image to recreate
the colour fringing,
blurring
and shimmer
that most game players would have seen
on real televisions.
This is important because
some games depended on NTSC artifacts
to display colours outside the console's official palette
or to create effects like transparency.
-117
View File
@@ -1,117 +0,0 @@
higan, the multi-system emulator
================================
higan emulates a number of classic videogame consoles of the 1980s and 1990s,
allowing you to play classic games on a modern general-purpose computer.
About higan
-----------
As of v102,
higan has top-tier support for the following consoles:
- Nintendo Super Famicom/Super Nintendo Entertainment System,
including addon hardware:
- Super Game Boy
- Sufami Turbo
- Nintendo Game Boy Advance
It also includes some level of support for these consoles:
- Satellaview addon for the Super Famicom
- Nintendo Famicom/Nintendo Entertainment System
- Nintendo Game Boy
- Nintendo Game Boy Color
- Sega Master System
- Sega Game Gear
- Sega Megadrive/Genesis
- NEC PC Engine/TurboGrafx 16 (but not the CD-ROM² System/TurboGrafx-CD)
- NEC SuperGrafx
- Bandai Wonderswan
- Bandai Wonderswan Color
**Note:** Some consoles were released under different names
in different geographic regions.
To avoid listing all possible names
every time such a console is mentioned,
higan uses the name from the console's region of origin.
In practice,
that means Japanese names:
"Famicom" and "Super Famicom" instead of NES and SNES,
"Mega Drive" instead of "Genesis",
"PC Engine" instead of "TurboGrafx-16".
higan is actively supported on
FreeBSD 10 and above, and
Microsoft Windows 7 and above.
It also includes some level of support
for GNU/Linux and macOS.
If you want to install higan and try it out,
see the [Quick Start](#quick-start) section below.
higan is officially spelled with a lowercase "h", not a capital.
About this document
-------------------
This is the unofficial higan README,
a community-maintained introduction and reference.
It may be out of date
by the time you read this,
and it may contain errors or omissions.
If you find something that's wrong,
or you have a suggestion,
see "Unofficial higan resources" below.
Official higan resources
------------------------
- [Official homepage](https://byuu.org/emulation/higan/)
- [Official forum](https://board.byuu.org/viewforum.php?f=4)
Unofficial higan resources
--------------------------
- [Source code repository](https://gitlab.com/higan/higan/)
archives official higan releases
and WIP snapshots
since approximately v067r21.
- [Quark shader repository](https://github.com/hizzlekizzle/quark-shaders)
collects shaders that higan can use
to add special effects like TV scanlines to its video output,
or smarter algorithms for scaling up to modern PC resolutions.
See [Installing custom shaders][shaders] below for details.
- [Mercurial Magic](https://github.com/hex-usr/Mercurial-Magic/)
is a tool for converting MSU-1 games and mods into a format
higan can use.
See [Importing MSU-1 games][msu1] below for details.
[shaders]: #installing-custom-shaders
[msu1]: #importing-msu-1-games
There are also other projects
based on current or older versions of higan,
in whole or in part,
that you might want to check out.
- [Mednafen](https://mednafen.github.io/)
is another multi-system emulator.
Its Super Famicom emulation is based on bsnes v059,
from the time before bsnes was renamed to higan.
- [BizHawk](http://tasvideos.org/BizHawk.html)
is another multi-system emulator,
specialising in the creation of
tool-assisted speedruns.
Its Super Famicom emulation is based on bsnes v087.
- [nSide](https://github.com/hex-usr/nSide)
is a fork of higan that greatly enhances
its NES emulation support,
and adds minor features to the other cores too.
It also restores the "balanced" Super Famicom emulation core
that was removed from higan in v099,
which is less CPU intensive
than the current accuracy-focussed core.
- [bsnes-plus](https://github.com/devinacker/bsnes-plus)
is a fork of bsnes v073
that adds improved support for debugging Super Famicom software.
-52
View File
@@ -1,52 +0,0 @@
Installing the GBA BIOS
-----------------------
For most of the systems higan emulates,
the console itself contains (almost) no actual software,
so emulating the system does not require
infringing the copyright of the hardware manufacturer.
However,
the Game Boy Advance is different:
every device contains a standard library of software routines
for [common functions games require][bios],
often called a "BIOS"
by analogy with the Basic Input/Output System
used in IBM PC compatibles.
For the same legal reasons that commercial games
cannot be distributed with emulators,
the GBA BIOS cannot be distributed with higan,
but is required for GBA software to run.
If you have a real GBA and a flashcart,
the Internet contains many tools
that will extract the BIOS image so it can be copied
to your desktop computer.
The correct GBA BIOS file is exactly 16384 bytes long,
and has the SHA-256 hash
fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570.
Once you have the correct BIOS file:
1. rename it to `bios.rom`
- if you're using Windows,
turn off "hide extensions for known file types"
so you don't wind up with a file called
`bios.rom.dat`
or whatever the file's original extension was.
2. Copy the file into higan's `Game Boy Advance.sys` directory,
alongside the `manifest.bml` file that is already there.
- In Windows,
find `Game Boy Advance.sys` in the same folder
as `higan.exe`
- In Linux,
find `Game Boy Advance.sys` in
`~/.local/share/higan/`
**Note:**
If you upgrade this version of higan to a newer version,
make sure the `bios.rom` file
winds up in the `Game Boy Advance.sys` directory
of the new version.
[bios]: http://problemkaputt.de/gbatek.htm#biosfunctions
-136
View File
@@ -1,136 +0,0 @@
Compiling from source on Linux
------------------------------
You will need a copy of the higan source-code.
If you download an official release from the higan homepage,
you will need [7-zip][7z] or a compatible tool to extract it.
Alternatively,
you may obtain higan source code from
[the unofficial git repo](https://gitlab.com/higan/higan/)
using the Git source-code management tool,
or by clicking the download button on the right-hand side of the web-page
and choosing an archive format.
You will also need GCC 4.9 or higher,
including the C and C++ compiler,
GNU Make,
and development files
(headers, etc.)
for the following libraries:
- GTK 2.x
- PulseAudio
- Mesa
- gtksourceview 2.x
- Cairo
- SDL 1.2
- libXv
- libAO
- OpenAL
- udev
On a Debian-derived Linux distribution,
you can install everything you need with a command like:
sudo apt-get install build-essential libgtk2.0-dev libpulse-dev \
mesa-common-dev libgtksourceview2.0-dev libcairo2-dev libsdl1.2-dev \
libxv-dev libao-dev libopenal-dev libudev-dev
Once you have all the dependencies installed,
you may build and install higan.
Note: Run these commands as yourself,
**do not run them as root**
(no `sudo`, no `su`, etc.),
because higan does not support
being installed system-wide.
1. Put the higan source code in some convenient location,
like `~/higan-src`
2. Open a terminal window
3. Type `cd ~/higan-src`
(or wherever you put the higan source)
and press Enter
4. Type `make -C icarus compiler=g++` and press Enter
to build the icarus import tool
5. Type `make -C higan compiler=g++` and press Enter
to build the main higan executable
Installing a compiled build on Linux
------------------------------------
Assuming you have successfully compiled higan
as described in the previous section:
1. Open a terminal window
2. Type `cd ~/higan-src`
(or wherever you put the higan source)
and press Enter
3. Type `make -C icarus install` and press Enter
to install icarus and its game database
4. Type `make -C higan install` and press Enter
to install higan and its supporting files
This installs higan and its associated data files
into the `~/.local` directory hierarchy.
To confirm higan is installed correctly,
type `higan` in a terminal and press Enter.
If the higan window appears,
everything is working.
On the other hand,
if you get an error message like "command not found",
you should double-check that the directory `~/.local/bin`
is included in your `$PATH` environment variable
by running the following command in a terminal:
echo "$PATH" | tr ':' '\n' | grep ~/.local/bin
If the above command prints the full path of `~/.local/bin`
(for example: `/home/yourname/.local/bin`)
then you should be good.
If it prints nothing,
you need to add the following line to `~/.profile`:
export PATH=~/.local/bin:$PATH
(this line must be in `~/.profile` because
most GUIs do not read any other files at login)
If you also have a `~/.bash_profile`,
make sure it reads the contents of `~/.profile`
with a line like this:
source ~/.profile
You will need to log out and log back in
for changes to `~/.profile` or `~/.bash_profile`
to take effect.
Before you can actually play games,
you'll need to [import them](#the-game-library)
and [configure higan](#configuring-higan).
If you want to play Game Boy Advance games,
you will need [a GBA BIOS](#installing-the-gba-bios).
Uninstalling a compiled build on Linux
--------------------------------------
To uninstall higan,
as installed by the above instructions:
1. Open a terminal window
2. Type `cd ~/higan-src`
(or wherever you put the higan source)
and press Enter
3. Type `make -C icarus uninstall` and press Enter
4. Type `make -C higan uninstall` and press Enter
To remove higan's configuration,
delete the directory `~/.config/higan` as well.
To remove the games imported into higan's library
(including in-game saves and save-states),
delete the directory `~/Emulation`.
You may also wish to delete the higan source directory.
-163
View File
@@ -1,163 +0,0 @@
Installing an official release on Windows
-----------------------------------------
Official higan releases are distributed in [7-zip][7z] archives.
You will need to install 7-zip,
or another compatible archiving tool,
to install higan.
[7z]: http://www.7-zip.org/
Once you have a suitable archiving tool,
extract the contents of the higan archive into a new folder.
When you're done,
the new folder should contain `higan.exe` and `icarus.exe`
along with other assorted files and directories
that describe the systems higan emulates.
You may put that folder wherever you like.
To run higan, open the `higan.exe` file.
Before you can actually play games,
you'll need to [import them](#the-game-library)
and [configure higan](#configuring-higan).
If you want to play Game Boy Advance games,
you will need [a GBA BIOS](#installing-the-gba-bios).
Uninstalling an official release on Windows
-------------------------------------------
Delete the folder containing `higan.exe`
and the other associated data from the original archive.
To remove higan's configuration:
1. Press Win+R to open the Run dialog
2. Type `%LOCALAPPDATA%` and press Enter
to open the folder where higan's configuration data lives
3. Delete the subdirectories named `icarus` and `higan`
if they exist.
You might also want to remove the games imported into higan's library
(including in-game saves and save-states):
1. Press Win+R to open the Run dialog
2. Type `%USERPROFILE%` and press Enter
to open the folder where higan keeps its game library
3. Delete the folder named `Emulation` if it exists
Compiling from source on Windows
--------------------------------
You will need a copy of the higan source-code.
If you download an official release from the higan homepage,
you will need [7-zip][7z] or a compatible tool to extract it.
Alternatively,
you may obtain higan source code from
[the unofficial git repo](https://gitlab.com/higan/higan/)
using the Git source-code management tool,
or by clicking the download button on the right-hand side of the web-page
and choosing an archive format.
You will need a C++ compiler to compile higan.
We recommend installing [TDM64-GCC][tdm],
preferably the latest version
but anything newer than 4.9 should be fine.
higan does not support building with clang++
(Clang is still not quite there yet for Windows)
nor Microsoft Visual C++
(last we checked, it didn't support all the C++ features higan uses).
**Note:** Make sure you get TDM64-GCC,
not TDM-GCC.
When compiled in x86 (32-bit) mode,
higan may crash at startup
because gcc targeting x86 does not support
Windows' structured exception handling (SEH).
Also,
historically in x86 mode
gcc has miscompiled a part of the NES emulation core.
See the higan forum
[for](https://board.byuu.org/viewtopic.php?p=41977#p41977)
[details](https://board.byuu.org/viewtopic.php?p=42253#p42253).
Once you've installed mingw-w64,
open a command-prompt window,
type `g++ --version`
then press Enter
to check it's installed correctly.
You should see a message like
g++ 1.2.3 20010101
Copyright (C) 2001 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
...except it should mention the version of mingw that you installed
and the corresponding dates.
If you see an error message like "command not found"
or "bad command or filename",
you may need to add mingw's "bin" folder
to your computer's `%PATH%`.
See the mingw documentation for help with that.
Once mingw is installed and available from the command prompt:
1. Put the higan source code in some convenient location,
like `C:\higan-src`
2. Open the command-prompt
3. Type `cd C:\higan-src`
(or wherever you put the higan source)
and press Enter
4. Type `mingw32-make -C icarus compiler=g++` and press Enter
to build the icarus import tool
5. Type `mingw32-make -C higan compiler=g++` and press Enter
to build the main higan executable
[tdm]: http://tdm-gcc.tdragon.net/download
Installing a compiled build on Windows
--------------------------------------
1. In Windows Explorer,
create the folder where you want higan to live
2. Assuming you built higan in `C:\higan-src`,
copy `C:\higan-src\icarus\out\icarus.exe`
into the new folder
3. Copy `C:\higan-src\icarus\Database` and its contents
into the new folder
4. Copy `C:\higan-src\higan\out\higan.exe`
into the new folder
5. Copy all the `*.sys` directories
in `C:\higan-src\higan\systems`
into the new folder
The new folder should now contain
`icarus.exe`,
`higan.exe`,
a folder named `Database`,
and half a dozen folders named after the systems higan emulates
with `.sys` at the end.
This is what you would get by downloading an official build,
as described under
[Installing an official release on Windows][instwin]
above.
[instwin]: #installing-an-official-release-on-windows
Before you can actually play games,
you'll need to [import them](#the-game-library)
and [configure higan](#configuring-higan).
If you want to play Game Boy Advance games,
you will need [a GBA BIOS](#installing-the-gba-bios).
Uninstalling a compiled build on Windows
----------------------------------------
The process is the same as
[Uninstalling an official release on Windows][uninstwin]
above. You may also wish to delete the higan source folder.
[uninstwin]: #uninstalling-an-official-release-on-windows
-61
View File
@@ -1,61 +0,0 @@
The Filesystem Browser
----------------------
Sometimes higan will need
to ask you to choose a file or folder.
For this, it uses a special Filesystem Browser dialog.
Although many operating systems provide a native filesystem browser,
they do not all allow the same customizations.
Therefore,
higan provides its own filesystem browser
that works the same way on every platform.
The filesystem browser shows the contents of some particular folder,
and allows you to select one of those items.
Across the top of the window,
a text-box displays the path of the current folder.
If you want to browse a specific path,
you may edit the contents of this box
and press Enter to switch to the new location.
The button with two blue arrows at the top-right is "Refresh".
Pressing this button will check for
added (or removed) items in the current folder,
and add (or remove) them to (or from) the list.
The button with the house at the top-right is "Home".
Pressing this button will switch to your home folder.
The button with the green up-arrow at the top right is "Parent".
Pressing this button will
switch to the parent of the current folder.
Most of the filesystem browser lists the contents
of the current directory.
Double-clicking a folder,
or selecting it and pressing Enter,
will switch to showing the contents of that directory.
If the list has keyboard focus,
typing any text will jump to the first inem in the list
whose name begins with the text you typed.
If a drop-down list appears in the bottom-left,
it allows you to choose which files appear in the list,
based on file-extension.
If this filesystem browser is asking for a file,
you can choose one
by double-clicking it,
by selecting it and pressing Enter,
or by selecting it and clicking the "Select" button in the bottom-right.
If this filesystem browser is asking for a directory,
you can choose one
by selecting it and clicking the "Select" button in the bottom-right.
Double-clicking
or selecting and pressing Enter don't work,
they just switch to viewing that directory.
The "Cancel" button in the bottom-right
closes the filesystem browser without selecting anything.
-40
View File
@@ -1,40 +0,0 @@
# Synopsis
> higan [*\-\-fullscreen*] [*PATH*]
# Description
TODO: Put `NTSC-J|`, `NTSC-U|` or `PAL|`
at the beginning of the path
to force a region
for consoles where it can't be detected.
When launched with `--fullscreen`,
higan will automatically enter full-screen mode
when it starts.
This is not much use unless you also specify `PATH`,
because you won't be able to load a game
until you exit full-screen mode
by pressing the "Toggle Fullscreen"
[hotkey](higan-config.md#hotkeys).
When `PATH` is the path to an existing
[game folder](../concepts/game-folders.md)
for any supported console,
that game will be loaded when higan starts.
When `PATH` is the path to a ROM file
for any supported console,
it will be imported into a game folder in
[the Game Library](../concepts/game-library.md),
and then loaded from there when higan starts.
# Examples
Play a previously-imported copy of Super Mario World
in full-screen (assuming Linux defaults):
```sh
higan --fullscreen ~/Emulation/"Super Famicom"/"Super Mario World.sfc"
```
-298
View File
@@ -1,298 +0,0 @@
TODO: Rename this file to "higan-settings.md"
The Settings window
appears when you choose
one of the items at the bottom of
[the Settings menu](higan.md#the-settings-menu).
It contains less-frequently-modified configuration options.
Most of these can be safely ignored,
or set once and never changed again.
This window has a tab for each main category of options:
Video
=====
This tab contains options that affect
how higan displays
the emulated console's video output.
- **Saturation**: adjusts the vibrancy of colours displayed,
where 0% makes things pure grey,
100% is normal,
and 200% is garishly brightly coloured.
- **Gamma**: adjusts how bright mid-range colours are
compared to the brightest colours,
where 100% is normal,
and 200% makes mid-range colours much darker.
- **Luminance**: adjusts the overall brightness,
where 100% is normal,
and 0% is totally black.
- **Overscan Mask**: hides parts of
the video output that would have been hidden
by the bezel around the edge of
a standard-definition television screen.
Some games (particularly on the Famicom)
displayed random glitchy output in this area,
which can be distracting.
The units are "pixels in the emulated console's standard video-mode".
For example, setting "Horizontal" to 8
will clip 8/256ths from the left and right sides
of the Super Famicom's video output,
whether the Super Famicom is in
lo-res (256px) or hi-res (512px)
mode.
- **Aspect Correction**:
(in both Windowed Mode and Fullscreen Mode)
stretches the image to match the aspect ratio
produced by the original console hardware,
but can cause a "ripple" effect,
due to rounding errors.
- **Resize Window to Viewport**:
(under "Windowed mode")
causes higan to resize its window
to fit snugly around the emulated console's video
whenever it changes size:
because a game was loaded for a different console
with a different display size or aspect ratio,
because the "Overscan Mask" controls were adjusted,
because the game switched to a different video mode,
because the user pressed the "Rotate Display" hotkey,
etc.
When this option is disabled,
the higan window stays at a fixed size,
large enough to contain the video for any supported console,
padded with black borders for all smaller video modes.
- **Resize Viewport to Window**:
(under "Fullscreen mode")
causes higan to stretch the emulated console's video output
to touch the edges of the screen.
Since most screens are not an exact multiple
of the size of all emulated consoles,
this may cause a "ripple" effect,
due to rounding errors.
When this option is disabled,
higan stretches the emulated console's video output
to the largest exact multiple
of the emulated console's video output
that is smaller than or equal to the screen size.
- TODO: Update this to match 103r11, or whatever the latest version is.
Audio
=====
This tab contains options that affect
how higan reproduces
the emulated console's audio output.
- **Device**: allows you to choose
which audio device higan sends
the emulated game's audio to.
- **Frequency**: controls the sample-rate that higan will use
when generating audio.
If your PC's audio hardware has a "native" sample-rate
and you know what it is,
pick that.
Otherwise,
44.1kHz or 48kHz should be fine.
- **Latency**: controls how much audio output higan calculates in advance.
Higher values reduce the chance of
"popping" or "glitching" noises,
but increase the delay between an action occurring on-screen
and the corresponding sound-effect being played.
- **Exclusive Mode**: appears
if the current audio driver
allows higan to take exclusive control of your PC's audio output,
so no other applications can play sounds.
This can improve audio quality,
and lower the effective audio latency.
- **Volume**: controls the overall loudness of
the emulated console's audio,
where 100% is normal volume,
and 0% is complete silence.
- **Balance**: controls the relative loudness of
the left and right speakers,
where 0% means only the left speaker produces sound,
50% means both speakers produce sound equally,
and 100% means only the right speaker produces sound.
- **Reverb**: adds a slight reverberation effect
to the emulated console's audio output,
as though the console were in a tunnel or small room.
Input
=====
This tab controls which PC inputs
are used for which emulated controllers.
The exact PC inputs that can be mapped
depend on [the input driver](#drivers).
- **Pause Emulation**: automatically pauses emulation
when the main higan window
is not the current foreground window.
- **Allow Input**: can be ticked
when "Pause Emulation" is *not* ticked,
and allows configured inputs to keep affecting higan
even when higan is running in the background.
This is particularly relevant if
you configure your PC keyboard to control higan:
if you tick this box,
and switch to a different application
leaving higan running in the background,
typing in that other application may affect
the emulated game running in higan
even though you can't see it!
- The console selector chooses which console's inputs
to display in the mapping list below.
- The port selector chooses which port of the selected console
to display in the mapping list below.
- The controller selector chooses which controller
associated with the given console and port
to display in the mapping list below.
- The mapping list includes
every button and axis on the selected controller,
and the PC inputs that are mapped to it
when it is connected to the selected port of the selected console.
- **Erase**: removes the mapping
for the selected button or axis.
- **Reset**: removes all the mappings currently in the list.
- TODO: Mention that controllers must be connected
in the console menu
before they can be used.
To map
a keyboard or gamepad button on your PC to
a controller button,
double-click the controller button in the list,
or select it and press Enter.
The window will grey out,
and a message will appear in the bottom left:
"Press a key or button to map [the button]".
Press the key or button you want to map,
and it should appear in the list
next to the controller button it is mapped to.
To map
a mouse button on your PC to
a controller button,
select the controller button in the list,
then click one of the "Mouse Left",
"Mouse Middle",
or "Mouse Right" buttons in the bottom-left of the window.
To map
a joystick axis on your PC to
a controller axis,
double-click the axis in the list,
or select it and press Enter.
The window will grey out,
and a message will appear in the bottom left:
"Press a key or button to map [the axis]".
Press the joystick in the direction you want to map,
and it should appear in the list
next to the controller button it is mapped to.
To map
a mouse axis on your PC to
a controller axis,
select the axis in the list,
then click one of the
"Mouse X-axis",
or "Mouse Y-axis"
buttons in the bottom-left of the window.
If you start mapping a button or axis,
but decide you don't want to,
you can press Escape
to exit the "Press a key or button to map..." mode
without actually mapping anything.
The "Rumble" setting
for the Game Boy Advance is treated like a button,
and can be mapped to a PC gamepad.
When the emulated Game Boy Advance
tries to use the rumble feature
of the Game Boy Player,
higan will turn on the force-feedback
of whatever gamepad the mapped button is part of.
Hotkeys
=======
This tab is like "Inputs" above,
except it contains controls for higan itself,
instead of for the emulated console.
- **Toggle Fullscreen**: puts higan into fullscreen mode,
where the menu and status bar are hidden,
and the emulated console's video output
is enlarged to cover the entire screen.
Toggling fullscreen also automatically captures the mouse.
- **Toggle Mouse Capture**: hides the usual mouse-cursor,
and captures the mouse so it cannot leave the higan window.
This is useful when the mouse is being used to emulate
a light-gun controller like the Super Scope.
- **Save Quick State**: saves the current state of the emulated console
to the currently-selected Quick State slot.
- **Load Quick State**: restores the emulated console
to the state saved in the currently-selected Quick State slot.
- **Decrement Quick State**: selects the previous Quick State slot.
The status bar will briefly display the new current slot number.
- **Increment Quick State**: selects the next Quick State slot.
The status bar will briefly display the new current slot number.
- **Pause Emulation**: pauses the emulated console
until the Pause Emulation hotkey is pressed a second time.
- **Fast Forward**: disables audio and video synchronisation
for as long as it's held down,
so emulation proceeds as quickly as possible.
If your PC struggles to hit "real time"
(60fps for most emulated consoles),
this likely won't have any effect.
- **Power Cycle**: turns the emulated console off and back on,
(a "hard reset"),
just like the "Power Cycle" menu item
in [the console menu](#the-console-menu).
- **Rotate Display**: will toggle the display
of the Game Boy Advance
and WonderSwan (Color)
between the usual landscape orientation
and a portrait orientation (90° counter-clockwise).
These consoles have games
that expect the player to hold the console
in a different way.
Advanced
========
This tab contains all the settings
that didn't fit into one of the other categories.
- **Video**: controls how higan will draw
the emulated console's video output
to the PC screen.
"None" means no video will be drawn.
See [Drivers](#drivers) for details.
- **Audio**: controls how higan will present
the emulated console's audio output.
"None" means no audio will be played.
See [Drivers](#drivers) for details.
- **Input**: controls how higan checks for input
from the PC's input devices.
"None" means the emulated console cannot be controlled.
See [Drivers](#drivers) for details.
- **Location**: selects where the [Game Library](#the-game-library)
looks for games to load.
See [Moving the Game Library](#moving-the-game-library)
for more information.
- **Ignore Manifests**: makes higan ignore the manifest file
in the a loaded game's [game folder](#why-game-folders)
in favour of asking icarus
to guess a manifest on the fly.
This means that incompatible or incorrect manifests
generated by old versions of icarus
won't cause problems,
but means you can't fix incorrect manifests
generated by the current version of icarus.
See also the "Create Manifests" option in
[the icarus Settings dialog](#the-icarus-settings-dialog).
-145
View File
@@ -1,145 +0,0 @@
The Tools window
appears when you choose
one of the items at the bottom of
[the Tools menu](higan.md#the-tools-menu).
The window has a tab for each tool:
The Cheat Editor
----------------
For some consoles,
higan supports applying temporary changes to the code of a running game.
For example,
you could disable the code that registers when the player takes damage,
resulting in an "invulnerability" mode.
Currently,
higan supports cheats for the following consoles:
- Famicom
- Super Famicom
- Game Boy
- Master System
- PC Engine
- Wonder Swan
A cheat code of the format `addr=data`
will cause the emulated console to obtain `data`
whenever it reads from memory address `addr`.
A cheat code of the format `addr=comp?data`
will cause reads from `addr` to obtain `data`,
but only if the true value at `addr` is `comp`.
In both formats,
`data` is a single byte expressed as two hexadecimal digits,
`comp` is also a single byte expressed as two hexadecimal digits,
and `addr` is a memory address in the emulated console,
expressed as however many hexadecimal digits are required
for the console in question
(typically 4 for 8-bit CPUs,
6 for 16-bit CPUs,
and 8 for 32-bit CPUs).
For compatibility with older versions of higan,
the older syntaxes of `addr/data` and `addr/comp/data`
are still supported.
For cheats that require more than a single-byte change,
higan allows multiple codes to be combined with `+`
so that all of them can have a single description
and be toggled with a single click.
For example,
in Super Mario World,
you can lock the time to 999 with these codes:
`7e0f31=09+7e0f32=09+7e0f33=09`.
Changes made in the Cheat Editor are saved to disk
when the game is unloaded,
or when higan exits.
higan stores the known cheats for a particular game
in `higan/cheats.bml`
inside the corresponding game folder
in [the Game Library](#the-game-library).
If your copy of higan includes a cheat database
(a file named `cheats.bml`
in the same directory as `Super Famicom.sys`
and the other `*.sys` directories),
you can click the "Find Codes ..." button in the bottom left
to load all known cheats for the currently-running game.
To add a new cheat,
select an unused row in the list,
then type the relevant codes in the "Code(s)" field at the bottom,
and a description in the "Description" field.
To enable or disable an existing cheat,
tick the checkbox in the first column of the list.
The code should take effect immediately.
To clear out an existing cheat,
select it from the list
and click the "Erase" button in the bottom right,
or just manually delete
the contents of the "Code(s)" and "Description" fields.
To clear out all existing cheats,
click the "Reset" button in the bottom right.
The State Manager
-----------------
The State Manager allows you to create,
load,
and remove Manager states.
For more information on Manager states,
quick states,
saved games
and how they compare,
see [Save States](#save-states).
To create a new manager state,
or to replace an existing one,
select the slot in the list
then click "Save" in the bottom-left corner.
You can then type a description in the "Description" field,
to help you find the state again later.
To rename a state,
select the slot in the list
and edit the "Description" field.
To load a state,
select the slot in the list
and click "Load" in the bottom-left corner,
or just double-click it.
To clear the state out of a slot,
select the slot in the list
and click "Erase" in the bottom-right corner.
To clear all the slots at once,
click "Reset" in the bottom-right corner.
The Manifest Viewer
-------------------
As mentioned in
[Why game folders?](#why-game-folders),
a game cartridge contains
more than just the raw data of the game.
higan uses a "manifest" to
describe how the various parts of a game cartridge
are wired up together,
and the Manifest Viewer lets you examine
the configuration higan is using for the currently-running game.
For some games,
an actual cartridge has been taken apart and carefully examined
and its configuration has been recorded in icarus' database,
so the manifest icarus produces
is guaranteed accurate.
For games that do not exist in icarus' database,
icarus will make a reasonable guess.
This is enough to get the game running,
but does not necessarily reflect the original cartridge.
-290
View File
@@ -1,290 +0,0 @@
When you launch higan,
the main window appears,
with a menu-bar across the top,
a status-bar across the bottom,
and a large area in the middle where the game's video output appears.
The Library menu
----------------
The Library menu allows you
to import games into higan's game library,
and to load games from the library.
higan organises the games in your library
according to which console they were intended to run on.
To play a game for a particular console from your library,
click on the Library menu,
click on the console manufacturer submenu
(Nintendo for the Super Famicom,
Bandai for the WonderSwan,
etc.)
then click on the console menu item.
A window will appear listing all the games in your library
for that particular console.
Select the game you want to play
and click the Open button,
or just double-click the game,
and it will begin playing as though you'd just turned on the console.
To add a new game to your library,
choose "Load ROM File ..." from the Library menu.
A [filesystem browser](#the-filesystem-browser) will appear,
allowing you to pick any ROM image for any supported system,
with any of the most common file extensions.
It also allows loading ROM images from `.zip` archives,
if the archive contains a single ROM image.
**Note:** Some games require extra steps to import correctly;
see [the Game Library](#the-game-library) for details.
TODO: Mention the region-picker.
To add many games at once,
run icarus,
or choose "Import ROM Files ..." from the Library menu
(which just runs icarus anyway).
See [the icarus interface](#the-icarus-interface)
for more information about bulk-importing.
For more information about the higan game library,
see [The Game Library](#the-game-library) below.
The console menu
---------------
**Note:**
The console menu does not appear
until a game is loaded.
Also,
it's not named "console",
it's named for the kind of console
the loaded game runs on.
For example,
when playing a Game Boy game,
you will have a "Game Boy" menu.
The console menu contains commands relevant
to the particular console being emulated.
All consoles will have some of the following items,
but few consoles have all of them.
- **Controller Port 1**
allows you
to connect different emulated controllers
to the first controller port,
if there is one.
- See [the Configuration dialog](#the-configuration-dialog)
for information about configuring
which host controller inputs are used
for the emulated controllers.
- This menu appears for the Famicom,
even though the Famicom did not support alternate controllers,
because the Famicom emulation core also emulates the NES,
which did.
- **Controller Port 2**
allows you
to connect different emulated controllers
to the second controller port,
if there is one.
- See [the Configuration dialog](#the-configuration-dialog)
for information about configuring
which host controller inputs are used
for the emulated controllers.
- This menu appears for the Famicom,
even though the Famicom did not support alternate controllers,
because the Famicom emulation core also emulates the NES,
which did.
- **Expansion Port**
allows you
to connect different emulated devices
to the console's expansion port,
if there is one.
- For the Super Famicom,
the [21fx][21fx] is a homebrew device
that allows a program running on a PC
to control a physical Super Famicom (or SNES).
This option allows the same program
to control the emulated SNES,
for development or testing.
- **Power Cycle**
restarts the loaded game
as though the emulated console were switched off and on again.
- **Unload**
stops the current game,
as though the emulated console were switched off.
You can load the same or a different game
from [the Library menu](#the-library-menu).
[21fx]: https://github.com/defparam/21FX
The Settings menu
-----------------
The Settings menu allows you to configure things
that aren't specific to any particular console.
- **Video Scale** determines the size and shape
of the emulated console's video output
in windowed mode
(as opposed to fullscreen).
- **Video Emulation** applies various effects
to the emulated console's video output
to reproduce some behaviours
that aren't technically part of the console itself.
- "Blurring" simulates the limited horizontal resolution
of standard-definition TVs
by blurring together horizontally-adjacent pixels.
Games like Jurassic Park for the Super Famicom
depend on this to emulate a transparency effect.
For hand-held consoles like the Game Boy Advance,
this simulates the slow response time
of the cheap LCD screens these consoles used
by blurring each output frame with the previous one.
- "Colors" simulates the way a console's display device
differs from modern computer monitor's colour reproduction.
In particular,
it simulates the slightly-different gamma correction
used by the Super Famicom,
the dim, washed out colours of the original Game Boy Advance,
and the pea-green display of the original Game Boy.
- **Video Shader** controls
how the low-resolution video output of the emulated console
is scaled up to suit modern high-resolution displays.
The availability of items in this submenu depends on
which video driver higan is using,
so see [Drivers](#drivers) for more information.
- "None" draws each output pixel according to
the colour of the single nearest input pixel,
sometimes called "nearest neighbour" scaling.
This produces unnaturally crisp and blocky images.
- "Blur" draws each output pixel by
averaging the colours of the four nearest input pixels,
sometimes called "bilinear" scaling.
This produces unnaturally blurry images.
- When using the OpenGL [driver](#drivers),
an additional item appears in this menu for
each installed Quark shader.
See [Installing custom shaders](#installing-custom-shaders)
for details.
- **Synchronize Audio**
causes higan to wait for audio playback to complete
before resuming emulation.
This should reduce popping and glitching noises,
and slows the emulation down to approximately the correct speed.
If your PC cannot emulate at full-speed,
(60fps for most consoles, 75fps for WonderSwan)
this has no noticable effect.
- **Mute Audio**
causes higan to not output sound from the emulated console.
The sound hardware is still emulated.
- **Show Status Bar**
causes higan to show or hide the status bar
at the bottom of the window.
This option has no effect in full-screen mode.
See [The status bar](#the-status-bar) for more information.
- **Video ...**
opens the Video tab of [the Configuration dialog][cfgdlg].
- **Audio ...**
opens the Audio tab of [the Configuration dialog][cfgdlg].
- **Input ...**
opens the Input tab of [the Configuration dialog][cfgdlg].
- **Hotkey ...**
opens the Hotkeys tab of [the Configuration dialog][cfgdlg].
- **Advanced ...**
opens the Advanced tab of [the Configuration dialog][cfgdlg].
[svsa]: #why-do-synchronize-video-and-synchronize-audio-conflict
[cfgdlg]: #the-configuration-dialog
The Tools menu
--------------
The Tools menu
contains features for manipulating the emulated console.
- **Save Quick State**
stores the current state of the emulated console
into one of the quick state slots.
See [Save States](#save-states) for more information.
- **Load Quick State**
restores the emulated console to
a state previously saved to one of the quick state slots.
See [Save States](#save-states) for more information.
- **Cheat Editor**
opens [the Cheat Editor window](#the-cheat-editor)
- **State Manager**
opens [the State Manager window](#the-state-manager)
- **Manifest Viewer**
opens [the Manifest Viewer window](#the-manifest-viewer)
The Help menu
-------------
The Help menu contains information about higan itself.
- **Documentation**
loads the official higan documentation
in your web-browser.
- **About**
opens the About dialog,
which displays basic information about higan,
including the version number.
The status bar
--------------
The status bar appears
at the bottom of the main higan window,
while "Show Status Bar" is ticked in [the Settings menu](#the-settings-menu).
Before any game is loaded,
the status bar displays "No cartridge loaded".
When a game is loaded and running,
the status bar displays the current emulation speeed
in frames-per-second.
For PAL-based consoles,
this should be around 50 FPS for "full speed" emulation,
for NTSC and most portable consoles the ideal speed is 60 FPS,
but the WonderSwan runs at 75 FPS.
If the number is too low,
you may need a faster computer,
or a faster [video driver](#drivers).
If the number is too high,
you may need to [Synchronize Audio](#the-settings-menu),
or you may have pressed the "turbo" [hotkey](#the-configuration-dialog).
The status bar displays "Paused"
if you have pressed the "pause" [hotkey](#the-configuration-dialog),
or if "When focus is lost: Pause Emulation" is ticked
in [the Input tab of the Configuration dialog](#the-configuration-dialog)
and the main higan window is not the foreground window.
To resume emulation,
make sure the main higan window is in the foreground,
and/or press the "pause" hotkey.
The status bar briefly displays "Selected quick state slot X"
(where X is one of the Quick State slot numbers)
when you press the "Increment Quick State"
or "Decrement Quick State"
hotkeys,
to show which Quick State slot will be used
the next time you press the "Save Quick State"
or "Load Quick State" hotkeys.
The status bar briefly displays "Slot X quick state does not exist"
(where X is one of the Quick State slot numbers)
when you choose a slot from the
[Tools](#the-tools-menu) → "Load Quick State"
sub-menu that has not had a save-state saved to it,
or when you press the "Load Quick State" hotkey
while the current Quick State slot has not had a save-state saved to it,
The status bar briefly displays "Power cycled"
when you choose "Power Cycle" from [the console menu](#the-console menu),
or press the "Power Cycle" hotkey.
The status bar briefly displays "Display rotation not supported"
when you press the "Rotate Display" hotkey
while the emulated console does not support display rotation.
-60
View File
@@ -1,60 +0,0 @@
When launching icarus,
directly or by picking "Import ROM Files ..."
from higan's [Library menu](#the-library-menu),
the main icarus window appears.
This is [a filesystem browser](#the-filesystem-browser),
with customisations:
- The filesystem browser only lists
files with extensions typically used for ROM dumps from
consoles higan emulates,
plus `.zip` files since ROM dumps are often compressed.
- Each matching file has a check-box next to it.
- You can tick the check-box next to every file at once
by pressing "Select All" in the bottom-left.
- You can un-tick all the check-boxes
by pressing "Unselect All" in the bottom-left.
Pressing "Settings ..." in the bottom-right
opens [the icarus Settings dialog](#the-icarus-settings-dialog).
Pressing "Import ..." in the bottom-right
will close the filesystem browser
then try to import all the files
whose check-boxes are ticked
into [the Game Library](#the-game-library).
icarus displays a progress dialog during the import process.
**Note:** Some games require extra steps to import correctly;
see [the Game Library](#the-game-library) for details.
The icarus Settings dialog
--------------------------
The icarus Settings dialog contains the following settings:
- **Library Location** determines
where icarus puts the games it imports.
See [Moving the Game Library](#moving-the-game-library)
for details.
- **Create Manifests** causes icarus
to write out a manifest file describing
each imported game
to that game's [game folder](#whats-in-a-game-folder).
This means that higan doesn't have to regenerate
the manifest each time an imported game is loaded,
but it means that a future version of higan
with an incompatible manifest format
may be unable to play these games.
Note that higan also has an "Ignore Manifests" option
in the Advanced tab of
[its Configuration dialog](#the-configuration-dialog).
- **Use Database** causes icarus to use manifest information
from its database of known-good manifests,
if it's importing a game it recognises.
For unrecognised games,
and for all games if this box is unticked,
icarus gueses the manifest data.
This option is still relevant when "Create Manifests" is unticked:
higan uses icarus to generate a manifest when a game is loaded,
not just at import-time.
-311
View File
@@ -1,311 +0,0 @@
The consoles that higan emulates
are similar in many ways,
but some of them do have particular quirks
that you should be aware of.
Video Shaders and TV-based consoles
-----------------------------------
[Video Shaders](guides/shaders.md)
customize how higan scales
the low-resolution video of the emulated console
up to the high-resolution of the computer display.
Simple shaders
(like "None"
and the third-party "AANN" shader)
just blindly scale up the images they're given,
but sophisticated shaders
(such as the third-party "xBR" shader)
try to produce higher-quality output
by recognising particular patterns of pixel,
like taking three diagonal pixels
and turning that into a smooth diagonal line.
These shaders assume that
each pixel drawn by the game's artists
becomes a single pixel in the video output they analyze.
Many of the consoles higan emulates
can only output video at one specific resolution,
so this "one pixel equals one pixel" rule holds true,
and pattern-based shaders like "xBR" work just fine.
Unfortunately,
this is not the case for the Super Famicom.
The "normal" video mode
draws 256 pixels across the width of the screen,
but the "high resolution" mode draws 512.
Since Super Famicom games can enable hi-res mode at any time
(even halfway through a frame),
higan always renders Super Famicom video output 512 pixels wide,
just in case.
This means that in "normal" mode,
each pixel drawn by the game's artists
becomes two pixels in the video output,
breaking the assumption
that pattern-based shaders are based on.
The Super Famicom has a similar issue in the vertical direction:
normally,
an NTSC-based Super Famicom draws about 240 rows of output every frame,
sometimes referred to as "240p" video.
When a game turns on "interlaced" mode,
it draws the 240 odd-numbered lines of one frame,
then the 240 even-numbered lines of the next,
and so forth.
This is sometimes referred to as "480i" video.
Although interlaced mode cannot be enabled mid-frame
like high-resolution mode,
resolution switching is still complex,
so higan always draws all 480 lines of video output.
This means for a normal, non-interlaced game,
each pixel drawn by the game's artists
becomes four pixels in the video output
(two horizontally and two vertically)
making pattern-based shaders even less useful.
It also breaks most scanline-emulation shaders,
since they typically draw a scanline
for each row of pixels in the video output.
The Mega Drive has similar problems
to the Super Famicom.
It has the same behaviour with interlacing,
but its high-resolution mode switches
from 256 pixels across to 320 pixels across.
Therefore in normal mode,
each pixel drawn by the game's artists
becomes five pixels in the video output,
while in high-resolution mode,
each pixel drawn by the game's artists
becomes four pixels in the video output
(or 10 and 8 pixels in non-interlaced mode).
The PC Engine does not support an interlaced mode,
but its horizontal resolution is much more flexible
than the Super Famicom or Mega Drive,
and so it has the same problems with shaders.
Music and Sound Effect Volume on the Mega Drive
-----------------------------------------------
The Mega Drive has two different audio-generating chips:
- the SN76489 or "PSG" chip,
inherited from the Master System,
mostly used for sound-effects
like Sonic picking up rings
- the YM2612 or "FM" chip,
mostly used for music
With two different sound sources,
it's important that they have similar volumes,
or the sound-effects will drown out the music,
or vice-versa.
Sega did *not* do this,
and different hardware revisions
used different relative volumes.
higan currently
sets the PSG volume to [125% of the FM volume][vol],
based on [a Sega Genesis model 1 VA6][va6] that byuu owns.
If you feel sound-effects in higan's Mega Drive core
are too loud or too quiet,
you may be comparing it
to a Mega Drive calibrated to a different scale
(or to an emulator tweaked to match such a Mega Drive).
[vol]: https://board.byuu.org/viewtopic.php?p=42482#p42482
[va6]: https://board.byuu.org/viewtopic.php?p=42195#p42195
Playing Game Boy Colour games in Game Boy mode
----------------------------------------------
Games for the original Game Boy
came in solid grey cartridges,
and only supported four-shade greyscale graphics.
ROM files for these games
typically have filenames ending in `.gb`.
The Game Boy Color played all the original Game Boy games,
but extended the hardware to support colour graphics.
Games that required
the extra hardware in the Game Boy Color
came in transparent cartridges,
and had a slightly different shape
to prevent them from being used in original Game Boys..
ROM files for these games
typically have filenames ending in `.gbc`.
However,
there were also some games
that could use colour if it was available,
but would stick to greyscale if it wasn't.
These games came in black cartridges.
ROM files for these games
typically have filenames ending in `.gbc`
(since they are genuinely designed for the Game Boy Color)
or `.gbb`.
Sometimes people ask
for higan to include these backwards-compatible Game Boy Color games
when asking for a Game Boy game to load.
However,
this would make higan much more complex
for not much benefit:
it's just the same game as in Color mode,
but with bits chopped off.
You might as well play backward-compatible games
in Game Boy Color mode
and get the full experience the developers intended.
If you really, really want to see
what a particular game's backward-compatible mode looked like,
change the filename to end with `.gb`
(instead of `.gbc` or `.gbb`)
before [importing it](guides/import.md).
If you want to experiment
with loading in-game saves from colour-mode in monochrome mode
or vice-versa,
you can import the game once with `.gb`
and once with `.gbc`,
then manually copy files between the
[game folders](concepts/game-folders.md)
in the "Game Boy" and "Game Boy Color" sub-folders
of the [Game Library](concepts/game-library.md) folder.
Do not expect save-states to be compatible between
Game Boy and Game Boy Color.
In-Game Saves and the Game Boy Advance
--------------------------------------
For most of the consoles that higan emulates,
in-game saves are simple:
the cartridge contains some battery-backed RAM
that the game accesses like any other memory,
and the game's internal header usually contains some hint
about where in memory the save data appears
and how large it is.
The Game Boy Advance is different.
By the time of the GBA,
many save-storage technologies were available,
most with a more complex interface than plain memory.
Frustratingly, the GBA's internal header
does *not* describe which storage variant the game expects.
Therefore,
when importing a GBA game,
higan must guess which storage type to use
and sometimes it guesses incorrectly.
If higan guesses incorrectly for a game you want to play,
you will need to turn on
"Create manifests" in
[the Icarus settings dialog](interface/icarus.md#the-icarus-settings-dialog),
turn off
"Ignore manifests" in
[higan's Advanced settings](interface/higan-config.md#advanced),
re-import the game,
and edit `manifest.bml` in
[the game folder](concepts/game-folders.md)
to describe the correct storage type.
Try importing other GBA games to see what save types they use.
For more discussion of the GBA save type mess,
see [What's the deal with... GBA save files?][gbasaves]
[gbasaves]: http://zork.net/~st/jottings/GBA_saves.html
Rumble compatibility for Game Boy (Color)
-----------------------------------------
The Game Boy and Game Boy Color did not natively support
any kind of rumble or force-feedback system,
but some game cartridges (such as Pokémon Pinball)
included a rumble motor within the cartridge itself.
Because higan does not currently support
game-specific controller features,
to experience the rumble effect in higan
you'll need to configure the console itself:
- Open
[higan's Input settings](interface/higan-config.md#input)
- In the list of consoles,
select Game Boy, or Game Boy Color
depending on which console you want to use to play the game
- In the list of inputs,
double-click "Rumble"
or select it and press Enter
- Press any button on the gamepad that should shake
when the game turns on the rumble effect.
Rumble compatibility for Game Boy Advance
-----------------------------------------
The original Game Boy Advance
and the Game Boy Advance SP
did not support any kind of rumble or force-feedback system,
but the Game Boy Player addon for the Gamecube
allowed Game Boy Advance games
to use the rumble feature in Gamecube controllers.
Because rumble is a feature of the Game Boy Player,
to experience the rumble effect in higan
you'll need to configure the console itself:
- Open
[higan's Input settings](interface/higan-config.md#input)
- In the list of consoles,
select Game Boy Advance
- In the list of inputs,
double-click "Rumble"
or select it and press Enter
- Press any button on the gamepad that should shake
when the game turns on the rumble effect.
As well as the Game Boy Player rumble feature,
some Game Boy Advance cartridges
included a rumble motor within the cartridge itself.
higan does not support this rumble technology,
but that's not a big deal:
the only two such games are *Drill Dozer*,
which can use Game Boy Player rumble,
and *WarioWare: Twisted*,
which doesn't work anyway
because it requires gyroscope hardware
that higan does not yet emulate.
Game Boy Advance rotation
-------------------------
Some Game Boy Advance homebrew games,
as well as a bonus mode in *Dr Mario + Puzzle League*,
expect the player to physically rotate the device
so the screen is tall rather than wide.
higan supports this feature with
a Rotate [hotkey](interface/higan-config.md#hotkeys).
When the user presses the Rotate hotkey,
the console's video output is rotated 90° anti-clockwise,
and the directional-pad controls are also rotated
so that (for example) pushing the button for "up"
sends the signal "right" to the emulated console,
so that the player character moves "up" on the rotated screen.
WonderSwan rotation
-------------------
The WonderSwan hardware
included multiple sets of buttons
so the player could hold the device
vertically or horizontally.
*Makaimura for WonderSwan* includes a level
that requires the player to repeatedly rotate
the device as they play.
higan supports this feature with
a Rotate [hotkey](interface/higan-config.md#hotkeys).
When the user presses the Rotate hotkey,
the console's video output is rotated 90° anti-clockwise,
and the X and Y button cluster mappings
are adjusted to match.
-7
View File
@@ -1,7 +0,0 @@
TODO
- install
- configure inputs
- load a game
- connect a controller
-55
View File
@@ -1,55 +0,0 @@
build := release
include ../nall/GNUmakefile
target := tomoko
objects := libco emulator audio video resource
# console := true
flags += -I. -I..
ifeq ($(platform),windows)
ifeq ($(console),true)
link += -mconsole
else
link += -mwindows
endif
link += -mthreads -lpthread -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/emulator.o: emulator/emulator.cpp $(call rwildcard,emulator)
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/*)
-99
View File
@@ -1,99 +0,0 @@
#include <emulator/emulator.hpp>
namespace Emulator {
#include "stream.cpp"
Audio audio;
auto Audio::reset(maybe<uint> channels_, maybe<double> frequency_) -> void {
interface = nullptr;
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::setFrequency(double frequency) -> void {
this->frequency = frequency;
for(auto& stream : streams) {
stream->setFrequency(stream->inputFrequency, frequency);
}
}
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];
for(auto& sample : samples) sample = 0.0;
for(auto& stream : streams) {
double buffer[16];
uint length = stream->read(buffer), offset = 0;
for(auto& sample : samples) {
sample += 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;
}
platform->audioSample(samples, channels);
}
}
}
-82
View File
@@ -1,82 +0,0 @@
#pragma once
#include <nall/dsp/iir/one-pole.hpp>
#include <nall/dsp/iir/biquad.hpp>
#include <nall/dsp/resampler/cubic.hpp>
namespace Emulator {
struct Interface;
struct Audio;
struct Filter;
struct Stream;
struct Audio {
auto reset(maybe<uint> channels = nothing, maybe<double> frequency = nothing) -> void;
auto setInterface(Interface* interface) -> void;
auto setFrequency(double frequency) -> 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 Filter {
enum class Order : uint { First, Second };
enum class Type : uint { LowPass, HighPass };
Order order;
DSP::IIR::OnePole onePole; //first-order
DSP::IIR::Biquad biquad; //second-order
};
struct Stream {
auto reset(uint channels, double inputFrequency, double outputFrequency) -> void;
auto setFrequency(double inputFrequency, maybe<double> outputFrequency = nothing) -> void;
auto addFilter(Filter::Order order, Filter::Type type, double cutoffFrequency, uint passes = 1) -> 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:
struct Channel {
vector<Filter> filters;
DSP::Resampler::Cubic resampler;
};
vector<Channel> channels;
double inputFrequency;
double outputFrequency;
friend class Audio;
};
extern Audio audio;
}
-70
View File
@@ -1,70 +0,0 @@
auto Stream::reset(uint channels_, double inputFrequency, double outputFrequency) -> void {
this->inputFrequency = inputFrequency;
this->outputFrequency = outputFrequency;
channels.reset();
channels.resize(channels_);
for(auto& channel : channels) {
channel.filters.reset();
channel.resampler.reset(inputFrequency, outputFrequency);
}
}
auto Stream::setFrequency(double inputFrequency, maybe<double> outputFrequency) -> void {
this->inputFrequency = inputFrequency;
if(outputFrequency) this->outputFrequency = outputFrequency();
for(auto& channel : channels) {
channel.resampler.reset(this->inputFrequency, this->outputFrequency);
}
}
auto Stream::addFilter(Filter::Order order, Filter::Type type, double cutoffFrequency, uint passes) -> void {
for(auto& channel : channels) {
for(auto pass : range(passes)) {
Filter filter{order};
if(order == Filter::Order::First) {
DSP::IIR::OnePole::Type _type;
if(type == Filter::Type::LowPass) _type = DSP::IIR::OnePole::Type::LowPass;
if(type == Filter::Type::HighPass) _type = DSP::IIR::OnePole::Type::HighPass;
filter.onePole.reset(_type, cutoffFrequency, inputFrequency);
}
if(order == Filter::Order::Second) {
DSP::IIR::Biquad::Type _type;
if(type == Filter::Type::LowPass) _type = DSP::IIR::Biquad::Type::LowPass;
if(type == Filter::Type::HighPass) _type = DSP::IIR::Biquad::Type::HighPass;
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
filter.biquad.reset(_type, cutoffFrequency, inputFrequency, q);
}
channel.filters.append(filter);
}
}
}
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& filter : channels[c].filters) {
switch(filter.order) {
case Filter::Order::First: sample = filter.onePole.process(sample); break;
case Filter::Order::Second: sample = filter.biquad.process(sample); break;
}
}
channels[c].resampler.write(sample);
}
audio.process();
}
-14
View File
@@ -1,14 +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>
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>false</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
</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>
<true/>
<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"
-48
View File
@@ -1,48 +0,0 @@
#pragma once
namespace Emulator {
struct Cheat {
struct Code {
uint addr;
uint data;
maybe<uint> comp;
};
explicit operator bool() const {
return codes.size() > 0;
}
auto reset() -> void {
codes.reset();
}
auto append(uint addr, uint data, maybe<uint> comp = nothing) -> void {
codes.append({addr, data, comp});
}
auto assign(const string_vector& list) -> void {
reset();
for(auto& entry : list) {
for(auto code : entry.split("+")) {
auto part = code.transform("=?", "//").split("/");
if(part.size() == 2) append(part[0].hex(), part[1].hex());
if(part.size() == 3) append(part[0].hex(), part[2].hex(), part[1].hex());
}
}
}
auto find(uint addr, uint comp) -> maybe<uint> {
for(auto& code : codes) {
if(code.addr == addr && (!code.comp || code.comp() == comp)) {
return code.data;
}
}
return nothing;
}
private:
vector<Code> codes;
};
}
-7
View File
@@ -1,7 +0,0 @@
#include <emulator/emulator.hpp>
namespace Emulator {
Platform* platform = nullptr;
}
-40
View File
@@ -1,40 +0,0 @@
#pragma once
#include <nall/nall.hpp>
#include <nall/vfs.hpp>
using namespace nall;
#include "types.hpp"
#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 = "104";
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 = "104";
namespace Constants {
namespace Colorburst {
static constexpr double NTSC = 315.0 / 88.0 * 1'000'000.0;
static constexpr double PAL = 283.75 * 15'625.0 + 25.0;
}
}
//nall/vfs shorthand constants for open(), load()
namespace 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;
};
}
#include "platform.hpp"
#include "interface.hpp"
-84
View File
@@ -1,84 +0,0 @@
#pragma once
namespace Emulator {
struct Interface {
struct Information {
string manufacturer;
string name;
bool overscan;
} 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;
//information
virtual auto manifest() -> string = 0;
virtual auto title() -> string = 0;
//video information
struct VideoResolution {
uint width;
uint height;
uint internalWidth;
uint internalHeight;
double aspectCorrection;
};
virtual auto videoResolution() -> VideoResolution = 0;
virtual auto videoColors() -> uint32 = 0;
virtual auto videoColor(uint32 color) -> uint64 = 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 run() -> void {}
//time functions
virtual auto rtc() -> bool { return false; }
virtual auto rtcSynchronize() -> 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;
};
}
-32
View File
@@ -1,32 +0,0 @@
#pragma once
namespace Emulator {
struct Platform {
struct Load {
Load() : _pathID(nothing) {}
Load(uint pathID, string option = "") : _pathID(pathID), _option(option) {}
explicit operator bool() const { return (bool)_pathID; }
auto pathID() const -> uint { return _pathID(); }
auto option() const -> string { return _option; }
private:
maybe<uint> _pathID;
string _option;
};
virtual auto path(uint id) -> string { return ""; }
virtual auto open(uint id, string name, vfs::file::mode mode, bool required = false) -> vfs::shared::file { return {}; }
virtual auto load(uint id, string name, string type, string_vector options = {}) -> Load { return {}; }
virtual auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void {}
virtual auto audioSample(const double* samples, uint channels) -> void {}
virtual auto inputPoll(uint port, uint device, uint input) -> int16 { return 0; }
virtual auto inputRumble(uint port, uint device, uint input, bool enable) -> void {}
virtual auto dipSettings(Markup::Node node) -> uint { return 0; }
virtual auto notify(string text) -> void { print(text, "\n"); }
};
extern Platform* platform;
}
-91
View File
@@ -1,91 +0,0 @@
#pragma once
namespace Emulator {
struct Scheduler {
enum class Mode : uint {
Run,
SynchronizeMaster,
SynchronizeSlave,
};
enum class Event : uint {
Step,
Frame,
Synchronize,
};
inline auto synchronizing() const -> bool { return _mode == Mode::SynchronizeSlave; }
auto reset() -> void {
_host = co_active();
_threads.reset();
}
auto primary(Thread& thread) -> void {
_master = _resume = thread.handle();
}
auto append(Thread& thread) -> bool {
if(_threads.find(&thread)) return false;
thread._clock += _threads.size(); //this bias prioritizes threads appended earlier first
return _threads.append(&thread), true;
}
auto remove(Thread& thread) -> bool {
if(auto offset = _threads.find(&thread)) return _threads.remove(*offset), true;
return false;
}
auto enter(Mode mode = Mode::Run) -> Event {
_mode = mode;
_host = co_active();
co_switch(_resume);
return _event;
}
inline auto resume(Thread& thread) -> void {
if(_mode != Mode::SynchronizeSlave) co_switch(thread.handle());
}
auto exit(Event event) -> void {
uintmax minimum = -1;
for(auto thread : _threads) {
if(thread->_clock < minimum) minimum = thread->_clock;
}
for(auto thread : _threads) {
thread->_clock -= minimum;
}
_event = event;
_resume = co_active();
co_switch(_host);
}
inline auto synchronize(Thread& thread) -> void {
if(thread.handle() == _master) {
while(enter(Mode::SynchronizeMaster) != Event::Synchronize);
} else {
_resume = thread.handle();
while(enter(Mode::SynchronizeSlave) != Event::Synchronize);
}
}
inline auto synchronize() -> void {
if(co_active() == _master) {
if(_mode == Mode::SynchronizeMaster) return exit(Event::Synchronize);
} else {
if(_mode == Mode::SynchronizeSlave) return exit(Event::Synchronize);
}
}
private:
cothread_t _host = nullptr; //program thread (used to exit scheduler)
cothread_t _resume = nullptr; //resume thread (used to enter scheduler)
cothread_t _master = nullptr; //primary thread (used to synchronize components)
Mode _mode = Mode::Run;
Event _event = Event::Step;
vector<Thread*> _threads;
};
}
-57
View File
@@ -1,57 +0,0 @@
#pragma once
namespace Emulator {
struct Thread {
enum : uintmax { Second = (uintmax)-1 >> 1 };
virtual ~Thread() {
if(_handle) co_delete(_handle);
}
inline auto active() const { return co_active() == _handle; }
inline auto handle() const { return _handle; }
inline auto frequency() const { return _frequency; }
inline auto scalar() const { return _scalar; }
inline auto clock() const { return _clock; }
auto setFrequency(double frequency) -> void {
_frequency = frequency + 0.5;
_scalar = Second / _frequency;
}
auto setScalar(uintmax scalar) -> void {
_scalar = scalar;
}
auto setClock(uintmax clock) -> void {
_clock = clock;
}
auto create(auto (*entrypoint)() -> void, double frequency) -> void {
if(_handle) co_delete(_handle);
_handle = co_create(64 * 1024 * sizeof(void*), entrypoint);
setFrequency(frequency);
setClock(0);
}
inline auto step(uint clocks) -> void {
_clock += _scalar * clocks;
}
auto serialize(serializer& s) -> void {
s.integer(_frequency);
s.integer(_scalar);
s.integer(_clock);
}
protected:
cothread_t _handle = nullptr;
uintmax _frequency = 0;
uintmax _scalar = 0;
uintmax _clock = 0;
friend class Scheduler;
};
}
-129
View File
@@ -1,129 +0,0 @@
using int1 = nall::Integer< 1>;
using int2 = nall::Integer< 2>;
using int3 = nall::Integer< 3>;
using int4 = nall::Integer< 4>;
using int5 = nall::Integer< 5>;
using int6 = nall::Integer< 6>;
using int7 = nall::Integer< 7>;
using int8 = nall::Integer< 8>;
using int9 = nall::Integer< 9>;
using int10 = nall::Integer<10>;
using int11 = nall::Integer<11>;
using int12 = nall::Integer<12>;
using int13 = nall::Integer<13>;
using int14 = nall::Integer<14>;
using int15 = nall::Integer<15>;
using int16 = nall::Integer<16>;
using int17 = nall::Integer<17>;
using int18 = nall::Integer<18>;
using int19 = nall::Integer<19>;
using int20 = nall::Integer<20>;
using int21 = nall::Integer<21>;
using int22 = nall::Integer<22>;
using int23 = nall::Integer<23>;
using int24 = nall::Integer<24>;
using int25 = nall::Integer<25>;
using int26 = nall::Integer<26>;
using int27 = nall::Integer<27>;
using int28 = nall::Integer<28>;
using int29 = nall::Integer<29>;
using int30 = nall::Integer<30>;
using int31 = nall::Integer<31>;
using int32 = nall::Integer<32>;
using int33 = nall::Integer<33>;
using int34 = nall::Integer<34>;
using int35 = nall::Integer<35>;
using int36 = nall::Integer<36>;
using int37 = nall::Integer<37>;
using int38 = nall::Integer<38>;
using int39 = nall::Integer<39>;
using int40 = nall::Integer<40>;
using int41 = nall::Integer<41>;
using int42 = nall::Integer<42>;
using int43 = nall::Integer<43>;
using int44 = nall::Integer<44>;
using int45 = nall::Integer<45>;
using int46 = nall::Integer<46>;
using int47 = nall::Integer<47>;
using int48 = nall::Integer<48>;
using int49 = nall::Integer<49>;
using int50 = nall::Integer<50>;
using int51 = nall::Integer<51>;
using int52 = nall::Integer<52>;
using int53 = nall::Integer<53>;
using int54 = nall::Integer<54>;
using int55 = nall::Integer<55>;
using int56 = nall::Integer<56>;
using int57 = nall::Integer<57>;
using int58 = nall::Integer<58>;
using int59 = nall::Integer<59>;
using int60 = nall::Integer<60>;
using int61 = nall::Integer<61>;
using int62 = nall::Integer<62>;
using int63 = nall::Integer<63>;
using int64 = nall::Integer<64>;
using uint1 = nall::Natural< 1>;
using uint2 = nall::Natural< 2>;
using uint3 = nall::Natural< 3>;
using uint4 = nall::Natural< 4>;
using uint5 = nall::Natural< 5>;
using uint6 = nall::Natural< 6>;
using uint7 = nall::Natural< 7>;
using uint8 = nall::Natural< 8>;
using uint9 = nall::Natural< 9>;
using uint10 = nall::Natural<10>;
using uint11 = nall::Natural<11>;
using uint12 = nall::Natural<12>;
using uint13 = nall::Natural<13>;
using uint14 = nall::Natural<14>;
using uint15 = nall::Natural<15>;
using uint16 = nall::Natural<16>;
using uint17 = nall::Natural<17>;
using uint18 = nall::Natural<18>;
using uint19 = nall::Natural<19>;
using uint20 = nall::Natural<20>;
using uint21 = nall::Natural<21>;
using uint22 = nall::Natural<22>;
using uint23 = nall::Natural<23>;
using uint24 = nall::Natural<24>;
using uint25 = nall::Natural<25>;
using uint26 = nall::Natural<26>;
using uint27 = nall::Natural<27>;
using uint28 = nall::Natural<28>;
using uint29 = nall::Natural<29>;
using uint30 = nall::Natural<30>;
using uint31 = nall::Natural<31>;
using uint32 = nall::Natural<32>;
using uint33 = nall::Natural<33>;
using uint34 = nall::Natural<34>;
using uint35 = nall::Natural<35>;
using uint36 = nall::Natural<36>;
using uint37 = nall::Natural<37>;
using uint38 = nall::Natural<38>;
using uint39 = nall::Natural<39>;
using uint40 = nall::Natural<40>;
using uint41 = nall::Natural<41>;
using uint42 = nall::Natural<42>;
using uint43 = nall::Natural<43>;
using uint44 = nall::Natural<44>;
using uint45 = nall::Natural<45>;
using uint46 = nall::Natural<46>;
using uint47 = nall::Natural<47>;
using uint48 = nall::Natural<48>;
using uint49 = nall::Natural<49>;
using uint50 = nall::Natural<50>;
using uint51 = nall::Natural<51>;
using uint52 = nall::Natural<52>;
using uint53 = nall::Natural<53>;
using uint54 = nall::Natural<54>;
using uint55 = nall::Natural<55>;
using uint56 = nall::Natural<56>;
using uint57 = nall::Natural<57>;
using uint58 = nall::Natural<58>;
using uint59 = nall::Natural<59>;
using uint60 = nall::Natural<60>;
using uint61 = nall::Natural<61>;
using uint62 = nall::Natural<62>;
using uint63 = nall::Natural<63>;
using uint64 = nall::Natural<64>;
-13
View File
@@ -1,13 +0,0 @@
processors += mos6502
objects += fc-interface fc-system fc-controller
objects += fc-memory fc-cartridge fc-cpu fc-apu fc-ppu
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-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/)
-319
View File
@@ -1,319 +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 = 0;
output += pulseDAC[pulse_output];
output += dmcTriangleNoiseDAC[dmc_output][triangle_output][noise_output];
output += cartridgeSample;
stream->sample(sclamp<16>(output) / 32768.0);
tick();
}
auto APU::tick() -> void {
Thread::step(rate());
synchronize(cpu);
}
auto APU::setIRQ() -> void {
cpu.apuLine(frame.irqPending || dmc.irqPending);
}
auto APU::setSample(int16 sample) -> void {
cartridgeSample = sample;
}
auto APU::power() -> void {
create(APU::Enter, system.frequency());
stream = Emulator::audio.createStream(1, frequency() / rate());
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 90.0);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 440.0);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::LowPass, 14000.0);
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
pulse[0].power();
pulse[1].power();
triangle.power();
noise.power();
dmc.power();
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 = 0;
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::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, 8, 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,
};
}
-182
View File
@@ -1,182 +0,0 @@
struct APU : Thread {
shared_pointer<Emulator::Stream> stream;
inline auto rate() const -> uint { return Region::PAL() ? 16 : 12; }
//apu.cpp
APU();
static auto Enter() -> void;
auto main() -> void;
auto tick() -> void;
auto setIRQ() -> void;
auto setSample(int16 sample) -> void;
auto power() -> void;
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
struct Envelope {
auto volume() const -> uint;
auto clock() -> void;
auto power() -> 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 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 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 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 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 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;
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;
-89
View File
@@ -1,89 +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 = Region::PAL() ? dmcPeriodTablePAL[period] : dmcPeriodTableNTSC[period];
}
if(lengthCounter > 0 && !dmaBufferValid && dmaDelayCounter == 0) {
cpu.rdyLine(0);
dmaDelayCounter = 4;
}
return result;
}
auto APU::DMC::power() -> void {
lengthCounter = 0;
irqPending = 0;
period = 0;
periodCounter = Region::PAL() ? dmcPeriodTablePAL[0] : 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;
}
-26
View File
@@ -1,26 +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 {
speed = 0;
useSpeedAsVolume = 0;
loopMode = 0;
reloadDecay = 0;
decayCounter = 0;
decayVolume = 0;
}
-42
View File
@@ -1,42 +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 = Region::PAL() ? apu.noisePeriodTablePAL[period] : apu.noisePeriodTableNTSC[period];
}
return result;
}
auto APU::Noise::power() -> 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[4][8] = {
{0, 0, 0, 0, 0, 0, 0, 1}, //12.5%
{0, 0, 0, 0, 0, 0, 1, 1}, //25.0%
{0, 0, 0, 0, 1, 1, 1, 1}, //50.0%
{1, 1, 1, 1, 1, 1, 0, 0}, //25.0% (negated)
};
uint8 result = dutyTable[duty][dutyCounter] ? 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();
lengthCounter = 0;
duty = 0;
dutyCounter = 0;
period = 0;
periodCounter = 1;
}
-104
View File
@@ -1,104 +0,0 @@
auto APU::serialize(serializer& s) -> void {
Thread::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::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);
}
-40
View File
@@ -1,40 +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;
}
-40
View File
@@ -1,40 +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 {
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;
};
-229
View File
@@ -1,229 +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 = platform->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 = platform->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 = platform->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 = platform->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 = platform->open(cartridge.pathID(), name, File::Write)) {
fp->write(prgram.data, prgram.size);
}
}
if(auto name = document["board/chr/ram/name"].text()) {
if(auto fp = platform->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.step(cartridge.rate() * 4095);
tick();
}
auto Board::tick() -> void {
cartridge.step(cartridge.rate());
cartridge.synchronize(cpu);
}
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::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;
}
-49
View File
@@ -1,49 +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 serialize(serializer&) -> void;
static auto load(string manifest) -> Board*;
struct Information {
string type;
bool battery;
} information;
Memory prgrom;
Memory prgram;
Memory chrrom;
Memory chrram;
};
-34
View File
@@ -1,34 +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 serialize(serializer& s) -> void {
Board::serialize(s);
vrc1.serialize(s);
}
VRC1 vrc1;
};
-51
View File
@@ -1,51 +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 serialize(serializer& s) -> void {
Board::serialize(s);
vrc2.serialize(s);
}
struct Settings {
struct Pinout {
uint a0;
uint a1;
} pinout;
} settings;
VRC2 vrc2;
};
-51
View File
@@ -1,51 +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 serialize(serializer& s) -> void {
Board::serialize(s);
vrc3.serialize(s);
}
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
VRC3 vrc3;
};
-55
View File
@@ -1,55 +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 serialize(serializer& s) -> void {
Board::serialize(s);
vrc4.serialize(s);
}
struct Settings {
struct Pinout {
uint a0;
uint a1;
} pinout;
} settings;
VRC4 vrc4;
};
-39
View File
@@ -1,39 +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(); }
VRC6 vrc6;
};
-41
View File
@@ -1,41 +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 serialize(serializer& s) -> void {
Board::serialize(s);
vrc7.serialize(s);
}
VRC7 vrc7;
};
-46
View File
@@ -1,46 +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 {
prgBank = 0x0f;
mirrorSelect = 0;
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.integer(prgBank);
s.integer(mirrorSelect);
}
uint4 prgBank;
bool mirrorSelect;
};
-47
View File
@@ -1,47 +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 {
prgBank = 0;
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.integer(prgBank);
}
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
uint2 prgBank;
};
-49
View File
@@ -1,49 +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 {
chrBank = 0;
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.integer(chrBank);
}
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
uint2 chrBank;
};
-47
View File
@@ -1,47 +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 serialize(serializer& s) -> void {
Board::serialize(s);
mmc5.serialize(s);
}
enum class Revision : uint {
EKROM,
ELROM,
ETROM,
EWROM,
} revision;
MMC5 mmc5;
};
-86
View File
@@ -1,86 +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 {
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];
};
-56
View File
@@ -1,56 +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 {
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;
};
-42
View File
@@ -1,42 +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 serialize(serializer& s) -> void {
Board::serialize(s);
mmc6.serialize(s);
}
MMC6 mmc6;
};
-44
View File
@@ -1,44 +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 power() -> void {
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
}
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
};
-92
View File
@@ -1,92 +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 {
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];
};
-95
View File
@@ -1,95 +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 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;
};
-61
View File
@@ -1,61 +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 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;
};
-50
View File
@@ -1,50 +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 {
prgBank = 0;
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.integer(prgBank);
}
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
uint4 prgBank;
};
-216
View File
@@ -1,216 +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 power() -> 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(int n : range(16)) {
double volume = 1.0 / pow(2, 1.0 / 2 * (15 - n));
dac[n] = volume * 8192.0;
}
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].power();
pulse[1].power();
pulse[2].power();
}
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];
};
-74
View File
@@ -1,74 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
#include "chip/chip.cpp"
#include "board/board.cpp"
#include "serialization.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 loaded = platform->load(ID::Famicom, "Famicom", "fc", {"NTSC-J", "NTSC-U", "PAL"})) {
information.pathID = loaded.pathID();
information.region = loaded.option();
} else return false;
if(auto fp = platform->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.input(board->prgrom.data, board->prgrom.size);
sha.input(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 {
create(Cartridge::Enter, system.frequency());
board->power();
}
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);
}
}
-48
View File
@@ -1,48 +0,0 @@
#include "chip/chip.hpp"
#include "board/board.hpp"
struct Cartridge : Thread {
inline auto rate() const -> uint { return Region::PAL() ? 16 : 12; }
//cartridge.cpp
static auto Enter() -> void;
auto main() -> void;
auto pathID() const -> uint { return information.pathID; }
auto region() const -> string { return information.region; }
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;
//serialization.cpp
auto serialize(serializer&) -> void;
struct Information {
uint pathID = 0;
string region;
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;
};
-125
View File
@@ -1,125 +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 {
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;
};
-178
View File
@@ -1,178 +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 {
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;
};
-486
View File
@@ -1,486 +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 {
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;
};
-189
View File
@@ -1,189 +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 {
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;
};
-75
View File
@@ -1,75 +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 {
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;
};
-105
View File
@@ -1,105 +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 {
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;
};
-89
View File
@@ -1,89 +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 {
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;
};
-173
View File
@@ -1,173 +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 {
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;
};
-310
View File
@@ -1,310 +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 {
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;
};
-143
View File
@@ -1,143 +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 {
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;
};
-4
View File
@@ -1,4 +0,0 @@
auto Cartridge::serialize(serializer& s) -> void {
Thread::serialize(s);
return board->serialize(s);
}
-58
View File
@@ -1,58 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
ControllerPort controllerPort1;
ControllerPort controllerPort2;
#include "gamepad/gamepad.cpp"
Controller::Controller(uint port) : port(port) {
if(!handle()) create(Controller::Enter, 1);
}
Controller::~Controller() {
scheduler.remove(*this);
}
auto Controller::Enter() -> void {
while(true) {
scheduler.synchronize();
if(controllerPort1.device->active()) controllerPort1.device->main();
if(controllerPort2.device->active()) controllerPort2.device->main();
}
}
auto Controller::main() -> void {
step(1);
synchronize(cpu);
}
//
auto ControllerPort::connect(uint deviceID) -> void {
if(!system.loaded()) return;
delete device;
switch(deviceID) { default:
case ID::Device::None: device = new Controller(port); break;
case ID::Device::Gamepad: device = new Gamepad(port); break;
}
cpu.peripherals.reset();
if(auto device = controllerPort1.device) cpu.peripherals.append(device);
if(auto device = controllerPort2.device) cpu.peripherals.append(device);
}
auto ControllerPort::power(uint port) -> void {
this->port = port;
}
auto ControllerPort::unload() -> void {
delete device;
device = nullptr;
}
auto ControllerPort::serialize(serializer& s) -> void {
}
}
-45
View File
@@ -1,45 +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 : Thread {
Controller(uint port);
virtual ~Controller();
static auto Enter() -> void;
virtual auto main() -> void;
virtual auto data() -> uint3 { return 0; }
virtual auto latch(bool data) -> void {}
const uint port;
};
struct ControllerPort {
auto connect(uint deviceID) -> void;
auto power(uint port) -> void;
auto unload() -> void;
auto serialize(serializer&) -> void;
uint port;
Controller* device = nullptr;
};
extern ControllerPort controllerPort1;
extern ControllerPort controllerPort2;
#include "gamepad/gamepad.hpp"
-36
View File
@@ -1,36 +0,0 @@
Gamepad::Gamepad(uint port) : Controller(port) {
}
auto Gamepad::data() -> uint3 {
if(counter >= 8) return 1;
if(latched == 1) return platform->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 = platform->inputPoll(port, ID::Device::Gamepad, A);
b = platform->inputPoll(port, ID::Device::Gamepad, B);
select = platform->inputPoll(port, ID::Device::Gamepad, Select);
start = platform->inputPoll(port, ID::Device::Gamepad, Start);
up = platform->inputPoll(port, ID::Device::Gamepad, Up);
down = platform->inputPoll(port, ID::Device::Gamepad, Down);
left = platform->inputPoll(port, ID::Device::Gamepad, Left);
right = platform->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(uint 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;
};
-45
View File
@@ -1,45 +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 {
Thread::step(clocks);
synchronize(apu);
synchronize(ppu);
synchronize(cartridge);
for(auto peripheral : peripherals) synchronize(*peripheral);
}
auto CPU::power() -> void {
MOS6502::BCD = 0;
MOS6502::power();
create(CPU::Enter, system.frequency());
for(auto addr : range(0x0800)) ram[addr] = 0xff;
ram[0x0008] = 0xf7;
ram[0x0009] = 0xef;
ram[0x000a] = 0xdf;
ram[0x000f] = 0xbf;
r.pc.byte(0) = bus.read(0xfffc);
r.pc.byte(1) = bus.read(0xfffd);
memory::fill(&io, sizeof(IO));
io.rdyLine = 1;
}
}
-58
View File
@@ -1,58 +0,0 @@
struct CPU : Processor::MOS6502, Thread {
inline auto rate() const -> uint { return Region::PAL() ? 16 : 12; }
//cpu.cpp
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void;
auto power() -> 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 = controllerPort1.device->data();
return (mdr() & 0xc0) | data.bit(2) << 4 | data.bit(1) << 3 | data.bit(0) << 0;
}
case 0x4017: {
auto data = controllerPort2.device->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: {
controllerPort1.device->latch(data.bit(0));
controllerPort2.device->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 {
MOS6502::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) {
r.mdr = bus.read(io.rdyAddrValid ? io.rdyAddrValue : addr);
step(rate());
}
r.mdr = bus.read(addr);
step(rate());
return r.mdr;
}
auto CPU::write(uint16 addr, uint8 data) -> void {
bus.write(addr, r.mdr = data);
step(rate());
}
auto CPU::lastCycle() -> void {
io.interruptPending = ((io.irqLine | io.apuLine) & ~r.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;
}
-47
View File
@@ -1,47 +0,0 @@
#pragma once
//license: GPLv3
//started: 2011-09-05
#include <emulator/emulator.hpp>
#include <emulator/thread.hpp>
#include <emulator/scheduler.hpp>
#include <emulator/cheat.hpp>
#include <processor/mos6502/mos6502.hpp>
namespace Famicom {
#define platform Emulator::platform
namespace File = Emulator::File;
using Scheduler = Emulator::Scheduler;
using Cheat = Emulator::Cheat;
extern Scheduler scheduler;
extern Cheat cheat;
struct Thread : Emulator::Thread {
auto create(auto (*entrypoint)() -> void, double frequency) -> void {
Emulator::Thread::create(entrypoint, frequency);
scheduler.append(*this);
}
inline auto synchronize(Thread& thread) -> void {
if(clock() >= thread.clock()) scheduler.resume(thread);
}
};
struct Region {
static inline auto NTSCJ() -> bool;
static inline auto NTSCU() -> bool;
static inline auto PAL() -> bool;
};
#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/interface/interface.hpp>
Loaded 100 of 2079 files, more files were not shown because too many files have changed in this diff. Show more