Compare commits

...

77 Commits
v094 ... v097

Author SHA1 Message Date
Tim Allen
1fdd0582fc Update to v097 release.
byuu says:

This release features improvements to all emulation cores, but most
substantially for the Game Boy core. All of blargg's test ROMs that pass
in gambatte now either pass in higan, or are off by 1-2 clocks (the
actual behaviors are fully emulated.) I consider the Game Boy core to
now be fairly accurate, but there's still more improvements to be had.

Also, what's sure to be a major feature for some: higan now has full
support for loading and playing ordinary ROM files, whether they have
copier headers, weird extensions, or are inside compressed archives. You
can load these games from the command-line, from the main Library menu
(via Load ROM Image), or via drag-and-drop on the main higan window. Of
course, fans of game folders and the library need not worry: that's
still there as well.

Also new, you can drop the (uncompressed) Game Boy Advance BIOS onto the
higan main window to install it into the correct location with the
correct file name.

Lastly, this release technically restores Mac OS X support. However,
it's still not very stable, so I have decided against releasing binaries
at this time. I'd rather not rush this and leave a bad first impression
for OS X users.

Changelog (since v096):
- higan: project source code hierarchy restructured; icarus directly
  integrated
- higan: added software emulation of color-bleed, LCD-refresh,
  scanlines, interlacing
- icarus: you can now load and import ROM files/archives from the main
  higan menu
- NES: fixed manifest parsing for board mirroring and VRC pinouts
- SNES: fixed manifest for Star Ocean
- SNES: fixed manifest for Rockman X2,X3
- GB: enabling LCD restarts frame
- GB: emulated extra OAM STAT IRQ quirk required for GBVideoPlayer
  (Shonumi)
- GB: VBK, BGPI, OBPI are readable
- GB: OAM DMA happens inside PPU core instead of CPU core
- GB: fixed APU length and sweep operations
- GB: emulated wave RAM quirks when accessing while channel is enabled
- GB: improved timings of several CPU opcodes (gekkio)
- GB: improved timings of OAM DMA refresh (gekkio)
- GB: CPU uses open collector logic; return 0xFF for unmapped memory
  (gekkio)
- GBA: fixed sequencer enable flags; fixes audio in Zelda - Minish Cap
  (Jonas Quinn)
- GBA: fixed disassembler masking error (Lioncash)
- hiro: Cocoa support added; higan can now be compiled on Mac OS X 10.7+
- nall: improved program path detection on Windows
- higan/Windows: moved configuration data from %appdata% to
  %localappdata%
- higan/Linux,BSD: moved configuration data from ~/.config/higan to
  ~/.local/higan
2016-01-17 19:59:25 +11:00
Tim Allen
12df278c5b Update to v096r08 release.
byuu says:

Changelog:
- FC: scanline emulation support added
- SFC: balanced profile compiles again
- SFC: performance profile compiles again
- GB,GBC: more fixes to pass blargg's 07, 08, 11 APU tests
- tomoko: added input loss { pause, allow-input } options
- tomoko: refactored settings video menu options to { Video Scale, Video
  Emulation, Video Shader }
- icarus: connected { About, Preferences, Quit } application menu options
2016-01-15 21:28:51 +11:00
Tim Allen
cec33c1d0f Update to v096r07 release.
byuu says:

Changelog:
- configuration files are now stored in localpath() instead of configpath()
- Video gamma/saturation/luminance sliders are gone now, sorry
- added Video Filter->Blur Emulation [1]
- added Video Filter->Scanline Emulation [2]
- improvements to GBA audio emulation (fixes Minish Cap) [Jonas Quinn]

[1] For the Famicom, this does nothing. For the Super Famicom, this
performs horizontal blending for proper pseudo-hires translucency. For
the Game Boy, Game Boy Color, and Game Boy Advance, this performs
interframe blending (each frame is the average of the current and
previous frame), which is important for things like the GBVideoPlayer.

[2] Right now, this only applies to the Super Famicom, but it'll come to
the Famicom in the future. For the Super Famicom, this option doesn't
just add scanlines, it simulates the phosphor decay that's visible in
interlace mode. If you observe an interlaced game like RPM Racing on
a real SNES, you'll notice that even on perfectly still screens, the
image appears to shake. This option emulates that effect.

Note 1: the buffering right now is a little sub-optimal, so there will
be a slight speed hit with this new support. Since the core is now
generating native ARGB8888 colors, it might as well call out to the
interface to lock/unlock/refresh the video, that way it can render
directly to the screen. Although ... that might not be such a hot idea,
since the GBx interframe blending reads from the target buffer, and that
tends to be a catastrophic option for performance.

Note 2: the balanced and performance profiles for the SNES are
completely busted again. This WIP took 6 1/2 hours, and I'm exhausted.
Very much not looking forward to working on those, since those two have
all kinds of fucked up speedup tricks for non-interlaced and/or
non-hires video modes.

Note 3: if you're on Windows and you saved your system folders somewhere
else, now'd be a good time to move them to %localappdata%/higan
2016-01-15 21:07:57 +11:00
Tim Allen
3414c8c8df Update to v096r06 release.
byuu says:

This WIP finally achieves the vision I've had for icarus.

I also fixed a mapping issue with Cx4 that, oddly enough, only caused
the "2" from the Mega Man X2 title screen to disappear.

[Editor's note - "the vision for icarus" was described in a separate,
public forum post: http://board.byuu.org/phpbb3/viewtopic.php?p=20584
Quoting for posterity:

    icarus is now a full-fledged part of higan, and will be bundled with
    each higan WIP as well. This will ensure that in the future, the
    exact version of icarus you need to run higan will be included right
    along with it. As of this WIP, physical manifest files are now truly
    and entirely optional.

    From now on, you can associate your ROM image files with higan's
    main binary, or drop them directly on top of it, to load and play
    your games.

    Furthermore, there are two new menu options that appear under the
    library menu when icarus is present:

    - "Load ROM File ..." => gives you a single-file selection dialog to
      import (and if possible) run the game
    - "Import ROM Files ..." => gives you a multi-file import dialog
      with checkboxes to pull in multiple games at once

    Finally, as before, icarus can generate manifest.bml files for
    folders that lack them.

    For people who like the game folder and library system, nothing's
    changed. Keep using higan as you have been.

    For people who hate it, you can now use higan like your classic
    emulators. Treat the "Library->{System Name}" entries as your
    "favorites" list: the games you actually play. Treat the
    "Library->Load ROM" as your standard open file dialog in other
    emulators. And finally, treat "Advanced->Game Library" as your save
    data path for cheat codes, save states, save RAM, etc.

]
2016-01-15 21:07:37 +11:00
Tim Allen
82ec876302 Update to v096r05 release.
byuu says:

Changelog:
- GB: re-enabling the LCD resets the display to LY=0,LX=0 [1]
- GB: emulated new findings (as of today!) for a DMG quirk that triggers
  an extra OAM STAT IRQ when Vblank STAT IRQs are off
- GB: made VBK, BGPI, OBPI readable
- GB: fixed APU length operations
- GB: fixed APU sweep operations
- NES: fixed cartridge/ -> board/ manifest lookups for mirroring/pinous
- hiro/Cocoa: added endrift's plist keys

Fixed:
- Astro Rabby is fully playable, even the title screen works correctly
- Bomb Jack is fully playable
- Kirby's Dream Land 2 intro scrolling first scanline of Rick is now fixed
- GBVideoPlayer functions correctly [2]
- Shin Megami Tensei: Devichil series regression fixed

[1] doesn't pass oam_bug-2/1-lcd_sync; because it seems to want
LY=0,LX>0, and I can't step the PPU in a register write as it's not
a state machine; the effect is emulated, it just starts the frame a tiny
bit sooner. blargg's testing is brutal, you can't be even one cycle off
or the test will fail.

[2] note that you will need the GBC Display Emulation shader from
hunterk's repository, or it will look like absolute shit. The
inter-frame blending is absolutely critical here.
2016-01-12 22:08:34 +11:00
Tim Allen
72b6a8b32e Update to v096r04 release.
byuu says:

Changelog:
- fixed S-DD1 RAM writes (Star Ocean audio fixed)
- applied all of the DMG test ROM fixes discussed earlier; passes many
  more test ROMs now
- at least until the GBVideoPlayer is working: for debugging purposes,
  CPU/PPU single-step now instead of sync just-in-time (~30% slower)
- fixed OS X crash on NSTextView (hopefully, would be very odd if not)

Unfortunately passing these test ROMs caused my favorite GB/GBC game to
break all of its graphics =(
Shin Megami Tensei - Devichil - Kuro no Sho (Japan) is all garbled now.
I'm really quite bummed by this ... but I guess I'll go through and
revert r04's fixes one at a time until I find what's causing it.

On the plus side, Astro Rabby is playable now. Still acts weird when
pressing B/A on the first screen, but the start button will start the
game.

EDIT: got it. Shin Megami Tensei - Devichil requires FF4F (VBK) to be
readable. Before, it was always returning 0x00. With my return 0xFF
patch, that broke. But it should be returning the VBK value, which also
fixes it. Also need to handle FF68/FF6A reads. Was really hoping that'd
help GBVideoPlayer too, but nope. It doesn't read any of those three
registers.
2016-01-11 21:31:30 +11:00
Tim Allen
653bb378ee Update to v096r03 release.
byuu says:

Changelog:
- fixed icarus to save settings properly
- fixed higan's full screen toggle on OS X
- increased "Add Codes" button width to avoid text clipping
- implemented cocoa/canvas.cpp
- added 1s delay after mapping inputs before re-enabling the window
  (wasn't actually necessary, but already added it)
- fixed setEnabled(false) on Cocoa's ListView and TextEdit widgets
- updated nall::programpath() to use GetModuleFileName on Windows
- GB: system uses open collector logic, so unmapped reads return 0xFF,
  not 0x00 (passes blargg's cpu_instrs again) [gekkio]
2016-01-08 20:23:46 +11:00
Tim Allen
0b923489dd Update to 20160106 OS X Preview for Developers release.
byuu says:

New update. Most of the work today went into eliminating hiro::Image
from all objects in all ports, replacing with nall::image. That took an
eternity.

Changelog:
- fixed crashing bug when loading games [thanks endrift!!]
- toggling "show status bar" option adjusts window geometry (not
  supposed to recenter the window, though)
- button sizes improved; icon-only button icons no longer being cut off
2016-01-07 19:17:15 +11:00
Tim Allen
4d193d7d94 Update to v096r02 (OS X Preview for Developers) release.
byuu says:

Warning: this is not for the faint of heart. This is a very early,
unpolished, buggy release. But help testing/fixing bugs would be greatly
appreciated for anyone willing.

Requirements:
- Mac OS X 10.7+
- Xcode 7.2+

Installation Commands:

    cd higan
    gmake -j 4
    gmake install
    cd ../icarus
    gmake -j 4
    gmake install

(gmake install is absolutely required, sorry. You'll be missing key
files in key places if you don't run it, and nothing will work.)

(gmake uninstall also exists, or you can just delete the .app bundles
from your Applications folder, and the Dev folder on your desktop.)

If you want to use the GBA emulation, then you need to drop the GBA BIOS
into ~/Emulation/System/Game\ Boy\ Advance.sys\bios.rom

Usage:
You'll now find higan.app and icarus.app in your Applications folders.
First, run icarus.app, navigate to where you keep your game ROMs. Now
click the settings button at the bottom right, and check "Create
Manifests", and click OK. (You'll need to do this every time you run
icarus because there's some sort of bug on OSX saving the settings.) Now
click "Import", and let it bring in your games into ~/Emulation.

Note: "Create Manifests" is required. I don't yet have a pipe
implementation on OS X for higan to invoke icarus yet. If you don't
check this box, it won't create manifest.bml files, and your games won't
run at all.

Now you can run higan.app. The first thing you'll want to do is go to
higan->Preferences... and assign inputs for your gamepads. At the very
least, do it for the default controller for all the systems you want to
emulate.

Now this is very important ... close the application at this point so
that it writes your config file to disk. There's a serious crashing bug,
and if you trigger it, you'll lose your input bindings.

Now the really annoying part ... go to Library->{System} and pick the
game you want to play. Right now, there's a ~50% chance the application
will bomb. It seems the hiro::pListView object is getting destroyed, yet
somehow the internal Cocoa callbacks are being triggered anyway. I don't
know how this is possible, and my attempts to debug with lldb have been
a failure :(

If you're unlucky, the application will crash. Restart and try again. If
it crashes every single time, then you can try launching your game from
the command-line instead. Example:

    open /Applications/higan.app \
	--args ~/Emulation/Super\ Famicom/Zelda3.sfc/

Help wanted:
I could really, really, really use some help with that crashing on game
loading. There's a lot of rough edges, but they're all cosmetic. This
one thing is pretty much the only major show-stopping issue at the
moment, preventing a wider general audience pre-compiled binary preview.
2016-01-07 19:17:15 +11:00
Tim Allen
47d4bd4d81 Update to v096r01 release.
byuu says:

Changelog:

- restructured the project and removed a whole bunch of old/dead
  directives from higan/GNUmakefile
- huge amounts of work on hiro/cocoa (compiles but ~70% of the
  functionality is commented out)
- fixed a masking error in my ARM CPU disassembler [Lioncash]
- SFC: decided to change board cic=(411,413) back to board
  region=(ntsc,pal) ... the former was too obtuse

If you rename Boolean (it's a problem with an include from ruby, not
from hiro) and disable all the ruby drivers, you can compile an
OS X binary, but obviously it's not going to do anything.

It's a boring WIP, I just wanted to push out the project structure
change now at the start of this WIP cycle.
2015-12-30 17:54:59 +11:00
Tim Allen
27660505c8 Update to v096 release.
byuu says:

Changelog (since v095):

- higan: absolutely massive amounts of coding style updates; probably
  150 hours of work here
- higan: manifest format updated for much greater consistency and
  simplicity
- higan: wrote popen() replacement to suppress console flashing when
  loading games via icarus
- icarus: now includes external database with mapping information for
  all verified games
- icarus: added support for importing Campus Challenge '92 and Powerfest
  '94
- icarus: merged settings.bml with higan; changing library path in one
  affects the other now
- SFC: added MSU1 audio resume support
- SFC: added new expansion port device (eBoot); simulation of SNES-Boot
  hardware
- SFC: expansion port device can now be selected from system menu
- SFC: updated handling of open bus (thanks to Exophase for the design
  idea)
- SFC: "BS-X Satellaview" library folder renamed to "BS Memory"
- GBA: fixed 8-bit SRAM reading/writing
- GBA: PRAM is 16-bits wide
- GBA: VRAM OBJ 8-bit writes are ignored
- GBA: BGnCNT unused bits are writable
- GBA: BG(0,1)CNT can't set d13
- GBA: BLDALPHA is readable (fixes many games including Donkey Kong
  Country)
- GBA: DMA masks &~1/Half, &~3/Word
- GBA: fixed many other I/O register reads; gets perfect score on
  endrift's I/O tests
- GBA: fixed caching of r(d) to pass armwrestler tests (Jonas Quinn)
- GBA: blocked DMA to/from BIOS region (Cydrak)
- GBA: fixed sign-extend and rotate on ldrs instructions (Cydrak)
- tomoko: added "Ignore Manifests" option to advanced settings panel
- tomoko: re-added support for ruby/quark video shaders
- tomoko: improved aspect correction behavior
- tomoko: added new tool, "Manifest Viewer" (mostly useful for
  developers)
- ruby: fixed mouse capture clipping on Windows (Cydrak)
- ruby: won't crash when using OpenGL 3.2 Linux driver with only OpenGL
  2.0 available
- ruby: added Linux fallback OpenGL 2.0 driver (not compiled in by
  default)
- ruby: added preliminary WASAPI driver (not compiled in by default, due
  to bugginess)
- hiro: fixed the appearance of Button and ListView::CheckButton on
  Windows classic
- hiro: added missing return values from several functions (fixes
  crashes with Clang)
2015-12-21 23:21:31 +11:00
Tim Allen
702b657e75 Update to v095r18 release.
byuu says:

Changelog:
- replaced popen() with execvp() / CreateProcess()
- suppressed (hid) controllers with no mappable inputs from the input
  settings panel

This gets rid of the window flashing when loading games with
higan+icarus. And hiding of empty devices should be a huge usability
improvement, especially since "None" was appearing at the top of the
list before for the SNES.
2015-12-21 20:16:47 +11:00
Tim Allen
0253db8685 Update to higan and icarus v095r17 release.
byuu says:

higan supports Event mapping again.

Further, icarus can now detect Event ROMs and MSU1 games.

Event ROMs must be named "program.rom", "slot-(1,2,3).rom" MSU1 games
must contain "msu1.rom"; and tracks must be named "track-#.pcm"

When importing the CC'92, PF'94 ROMs, the program.rom and
slot-(1,2,3).rom files must be concatenated. The DSP firmware can
optionally be separate, but I'd recommend you go ahead and merge it all
to one file. Especially since that common "higan DSP pack" floating
around on the web left out the DSP1 ROMs (only has DSP1B) for god knows
what reason.

There is no support for loading "game.sfc+game.msu+game-*.pcm", because
I'm not going to support trying to pull in all of those files through
importing. Games will have to be distributed as game folders to use
MSU1. The MSU1 icarus support is simply so your game folders won't
require an unstable manifest.bml file to be played. So once they're in
there, they are good for life.

Note: the Event sizes in icarus' SFC heuristics are wrong for appended
firmware. Change from 0xXX8000 to 0xXX2000 and it works fine. Will be
fixed in r18.

Added Sintendo's flickering fixes. The window one's a big help for
regular controls, but the ListView double buffering does nothing for me
on Windows 7 :( Fairly sure I know why, but too lazy to try and fix that
now.

Also fixes the mMenu thing.
2015-12-20 13:53:40 +11:00
Tim Allen
2a4eb1cfc8 Update to higan and icarus v095r16 release.
byuu says (in the WIP forum):

    Changelog:
    - satellaviewcartridge/SatellaviewCartridge is now bsmemory/BSMemory
    - Emulation/BS-X Satellaview/ is now Emulation/BS Memory/
    - masking is in for MCC's mcu (awful hack in the code, but that's
      temporary)
    - BS Memory types are now "flash" or "mrom"
    - fixed loading Same Game - Tengai Hen
    - icarus fixed up a lot; can load database entries for any supported
      media type now (only the SFC DB exists currently)

    mMenu::remove() fix will be in the next WIP.

byuu says (in the public beta thread):

    Changelog:
    - GBA emulation accuracy improved quite a bit
    - video shaders are supported once again
    - icarus shares settings.bml with higan; changing library path in
      one now affects the other
    - icarus manifest generation now uses my SNES game dumping database
      for perfect mapping of US games
    - major overhaul to manifest file format. As long as you have
      v095-style folders without manifest.bml, you will be fine
      - if not, go to higan->settings->configuration->advanced and check
	"Ignore Manifests" before loading your first game
    - new "Manifest Viewer" tool (not really meant for regular users;
      more of a developer tool)
    - experimental (but disabled in the binary) WASAPI driver. Help
      stabilizing it would be *greatly* appreciated!
    - lots of other stuff
2015-12-19 21:42:18 +11:00
Tim Allen
bd628de3cf Update to higan and icarus v095r15 release.
r13 and r14 weren't posted as individual releases, but their changelogs
were posted.

byuu says about r13:

    I'm not going to be posting WIPs for r13 and above for a while.

    The reason is that I'm working on the major manifest overhaul I've
    discussed previously on the icarus subforum.

    I'm recreating my boards database from scratch using the map files
    and the new map analyzer. The only games that will load are ones
    I've created board definitions for, and updated
    sfc/cartridge/markup.cpp to parse. Once I've finished all the
    boards, then I'll update the heuristics.

    Then finally, I'll sync the syntax changes over to the fc, gb, gba
    cores.

    Once that's done, I'll start posting WIPs again, along with a new
    build of icarus.

    But I'll still post changelogs as I work through things.

    Changelog (r13):
    - preservation: created new database-builder tool (merges
      region-specific databases with boards)
    - icarus: support new, external database format
      (~/.config/icarus/Database/(Super Famicom.bml, ...)
    - added 1A3B-(10,11,12); 1A3B-20

byuu says about r14:

    r14 work:

    I successfully created mappings for every board used in the US set.

    I also updated icarus' heuristics to use the new mappings, and
    created ones there for the boards that are only in the JP set.

    Then I patched icarus to support pulling games out of the database
    when it's used on a game folder to generate a manifest file.

    Then I updated a lot of code in higan/sfc to support the new mapping
    syntax. sfc/cartridge/markup.cpp is about half the size it used to
    be with the new mappings, and I was able to kill off both map/id and
    map/select entirely.

    Then I updated all four emulated systems (and both subsystems) to
    use "board" as the root node, and harmonized their syntax (made them
    all more consistent with each other.)

    Then I added a manifest viewer to the tools window+menu. It's kind
    of an advanced user feature, but oh well. No reason to coddle people
    when the feature is very useful for developers. The viewer will show
    all manifests in order when you load multi-cart games as well.

    Still not going to call any syntax 100% done right now, but
    thankfully with the new manifest-free folders, nobody will have to
    do anything to use the new format. Just download the new version and
    go.

    The Super Famicom Event stuff is currently broken (CC92/PF94
    boards). That's gonna be fun to support.

byuu says about r15:

    EDIT: small bug in icarus with heuristics. Edit
    core/super-famicom.cpp line 27:

	if(/*auto*/ markup = cartridge.markup) {

    Gotta remove that "auto" so that it returns valid markup.

    Resolved the final concerns I had with the new manifest format.

    Right now there are two things that are definitely broken: MCC (BS-X
    Town cart) and Event (CC '92 and PF'94).
    And there are a few things that are untested: SPC7110, EpsonRTC,
    SharpRTC, SDD1+RAM, SufamiTurbo, BS-X slotted carts.
2015-12-19 20:02:06 +11:00
Tim Allen
2c53d5fbc0 Update to v095r12 release.
byuu says:

Got it. They broke in r05.

Changelog:
- fixed typo in sfc/cpu/timing.cpp that was breaking coprocessor games
  with clocks
- updated sfc/coprocessor/hitachidsp to not access Bus directly
2015-12-15 20:30:26 +11:00
Tim Allen
f2a416aea9 Update to v095r11 release.
byuu says:

Changelog:
- SFC: "uint8 read(uint addr)" -> "uint8 read(uint addr, uint8 data)"
- hiro: mHorizontalLayout::setGeometry() return value
- hiro/GTK: ListView,TreeView::setFocused() does not grab focus of first
  item

Notes:
- nall/windows/utf8.hpp needs using uint = unsigned; at the top to
  compile
- sfc/balanced, sfc/performance won't compile yet

Seems Cx4 games broke a while back. Not from this WIP, either. I'll go
back and find out what's wrong now.
2015-12-14 20:41:06 +11:00
Tim Allen
78d49d3873 Update to v095r10 release.
byuu says:

Changelog:

- int_t<bits> replaced with Integer<bits>
- uint_t<bits> replaced with Natural<bits>
- fixed "Synchronize Audio" menu option that broke recently
- all of sfc/performance ported to "auto function() -> return;" syntax

With this WIP, all of higan is finally ported over to the new function
declaration syntax. Thank the gods.

There's still going to be periodic disruption for diffs from porting
over signed->int, unsigned->uint, and whatever we come up with for the
new Natural<> and Integer<> classes. But the worst of it's behind us
now.
2015-12-07 08:11:41 +11:00
Tim Allen
65a3306ad5 Update to v095r09 release.
byuu says:

Changelog:

- all of fc/ ported to "auto function() -> return;" syntax
  - (includes all of cartridge/board and cartridge/chip as well; even
    though they're all deprecated)
- sfc balanced profile ported to "auto function() -> return;" syntax
- sfc balanced and performance profiles compile again
- Linux always gets -ldl
- removed arch=x86 logic from nall/GNUmakefile, as TDM/GCC64 can't
  produce bug-free 32-bit binaries anyway

The only code that continues to use the old function syntax is the SFC
performance core, obscure parts of nall that higan doesn't use, and the
pieces of code that weren't written by me (blargg's SFC-DSP, Ryphecha's
sinc resampler, and OV2's xaudio2 header file.)

I was too burned out to finish it tonight. The above was about four
hours straight of non-stop typing. Really can't wait to be done with
this once and for all.
2015-12-05 16:44:49 +11:00
Tim Allen
a219f9c121 Update to v095r08 release.
byuu says:

Changelog:
- added preliminary WASAPI driver (it's really terrible, though. Patches
  most welcome.)
- all of processor/ updated to auto fn() -> ret syntax
- all of gb/ updated to auto fn() -> ret syntax

If you want to test the WASAPI driver, then edit ui-tomoko/GNUmakefile,
and replace audio.xaudio2 with audio.wasapi Note that the two drivers
are incompatible and cannot co-exist (yet. We can probably make it work
in the future.)

All that's left for the auto fn() -> ret syntax is the NES core and the
balanced/performance SNES components. This is kind of a big deal because
this syntax change causes diffs between WIPs to go crazy. So the sooner
we get this done and out of the way, the better. It's also nice from
a consistency standpoint, of course.
2015-11-21 18:36:48 +11:00
Tim Allen
6adfe71836 Update to icarus 20151117.
byuu says:

This release adds a settings dialog that lets you control the library
path, optionally generate manifest.bml files, and optionally bypass the
internal games database (so far this is only the US SNES set.)

Also, the settings.bml file can exist in the same folder as the binary
now (portable mode). Plus it can share the same config file as
higan/tomoko itself does. This will allow you to change the library
location in either program and have it affect the other program as well.
It's a bit hackish, but it works >_>

Note: don't use this with higan v095.06 or earlier, or bad things will
happen.
2015-11-19 20:27:56 +11:00
Tim Allen
41c478ac4a Update to v095r07 release.
byuu says:

Changelog:
- entire GBA core ported to auto function() -> return; syntax
- fixed GBA BLDY bug that was causing flickering in a few games
- replaced nall/config usage with nall/string/markup/node
  - this merges all configuration files to a unified settings.bml file
- added "Ignore Manifests" option to the advanced setting tab
  - this lets you keep a manifest.bml for an older version of higan; if
    you want to do regression testing

Be sure to remap your controller/hotkey inputs, and for SNES, choose
"Gamepad" from "Controller Port 1" in the system menu. Otherwise you
won't get any input. No need to blow away your old config files, unless
you want to.
2015-11-16 19:38:05 +11:00
Tim Allen
40f4b91000 Update to v095r06 release.
byuu says:

Changelog:
- fixed I/O register reads; perfect score on endrift's I/O tests now
- fixed mouse capture clipping on Windows [Cydrak]
- several hours of code maintenance work done on the SFC core

All higan/sfc files should now use the auto fn() -> ret; syntax. Haven't
converted all unsigned->uint yet. Also, probably won't do sfc/alt as
that's mostly just speed hack stuff.

Errata:
- forgot auto& instead of just auto on SuperFamicom::Video::draw_cursor,
  which makes Super Scope / Justifier crash. Will be fixed in the next
  WIP.
2015-11-14 11:52:51 +11:00
Tim Allen
6d9f43a37b Update to v095r05 release.
byuu says:

Changelog:
- GBA: lots of emulation improvements
- PPU PRAM is 16-bits wide
- DMA masks &~1/Half, &~3/Word
- VRAM OBJ 8-bit writes are ignored
- OAM 8-bit writes are ignored
- BGnCNT unused bits are writable*
- BG(0,1)CNT can't set the d13
- BLDALPHA is readable (fixes Donkey Kong Country, etc)
- SNES: lots of code cleanups
- sfc/chip => sfc/coprocessor
- UI: save most recent controller selection

GBA test scores: 1552/1552, 37/38, 1020/1260

(* forgot to add the value to the read function, so endrift's I/O tests
for them will fail. Fixed locally.)

Note: SNES is the only system with multiple controller/expansion port
options, and as such is the only one with a "None" option. Because it's
shared by the controller and expansion port, it ends up sorted first in
the list. This means that on your first run, you'll need to go to Super
Famicom->Controller Port 1 and select "Gamepad", otherwise input won't
work.

Also note that changing the expansion port device requires loading a new
cart. Unlike controllers, you aren't meant to hotplug expansion port
devices.
2015-11-12 21:15:03 +11:00
Tim Allen
d1ffd59c29 Update to v095r04 release.
Changelog:
- S-SMP core code style updated
- S-SMP loads reset vector from IPLROM ($fffe-ffff)
- sfc/base => sfc/expansion
- system/input => system/device
- added expansion/eBoot (simulation of defparam's SNES-Boot device)
- expansion port device can now be selected from Super Famicom menu
  option
- improved GBA MROM/SRAM reading

endrift's memory test is up to 1388/1552.

Note: I added the expansion port devices to the same group as controller
ports. I also had to move "None" to the top of the list. Before v096,
I am going to have to add caching of port selections to the
configuration file, check the proper default item in the system menu,
and remove the items with no mappings from the input configuration
window. Lots of work >_>
2015-11-10 22:11:29 +11:00
Tim Allen
0fe55e3f5b Update to v095r03 release and icarus 20151107.
byuu says:

Note: you will need the new icarus (and please use the "no manifest"
system) to run GBA games with this WIP.

Changelog:
- fixed caching of r(d) to pass armwrestler tests [Jonas Quinn]
- DMA to/from GBA BIOS should fail [Cydrak]
- fixed sign-extend and rotate on ldrs instructions [Cydrak]
- fixed 8-bit SRAM reading/writing [byuu]
- refactored GBA/cartridge
  - cartridge/rom,ram.type is now cartridge/mrom,sram,eeprom,flash
  - things won't crash horribly if you specify a RAM size larger than
    the largest legal size in the manifest
  - specialized MROM / SRAM classes replace all the shared read/write
    functions that didn't work right anyway
- there's a new ruby/video.glx2 driver, which is not enabled by default
  - use this if you are running Linux/BSD, but don't have OpenGL 3.2 yet
  - I'm not going to support OpenGL2 on Windows/OS X, because these OSes
    don't ship ancient video card drivers
- probably more. What am I, clairvoyant? :P

For endrift's tests, this gets us to 1348/1552 memory and 1016/1260
timing. Overall, this puts us back in second place. Only no$ is ahead
on memory, but bgba is even more ahead on timing.
2015-11-10 22:11:29 +11:00
Tim Allen
b42ab2fcb3 Update to v095r02 release.
byuu says:

Aspect correction is fixed now. Works way better than in v095 official.

It's still force-enabled in fullscreen mode. The idea of disabling it is
that it looks bad at 2x scale. But when you're fullscreen with a minimum
of 4x scale, there's no reason not to enable it.

It won't turn on at all for GB/C/A anymore. And I dropped the cute
attempt at making the aspect prettier on 2560x1600 monitors, so it'll be
the stock 8:7 across the board now for S/NES.

Also, the aspect correction will affect the window even when a game's
not loaded now, so the size won't bounce around as you change games in
windowed mode between GB/C/A and S/NES.

...

I also enhanced the ruby/glx driver. It won't crash if OpenGL 3.2 isn't
available anymore (fails safely ... had to capture the Xlib error
handler to suppress that), and it defaults to the MESA glXSwapInterval
before the SGI version. Because apparently the MESA version defines the
SGI version, but makes it a no-op. What. The. Fuck. right? But whatever,
reordering the enumerations fixes the ability to toggle Vsync on AMD
GPUs now.

...

Video shaders are back again. If you are using the OpenGL driver, you'll
see a "Video Shaders" menu beneath the "Video Filters" menu (couldn't
merge it with the filters due to hiro now constructing menu ordering
inside the header files. This works fine though.)

You want either "higan.exe" + "Video Shaders/" or "~/.local/bin/tomoko"
+ "~/.local/tomoko/Video Shaders/"
2015-11-10 22:07:34 +11:00
Tim Allen
8476a12deb Update to v095r01 release (open beta).
byuu says:

Changelog:
- added MSU1 resume support
- updated sfc/dsp, sfc/controller to match my coding style
- fixed hiro/Windows Button and ListView::CheckButton in Windows Classic
  mode
2015-10-10 13:16:12 +11:00
Tim Allen
b0e862613b Update to v095 release.
byuu says:

After 20 months of development, higan v095 is released at long last!

The most notable feature is vastly improved Game Boy Advance emulation.
With many thanks to endrift, Cydrak, Jonas Quinn and jchadwick, this
release contains substantially improved CPU timings and many bugfixes.
Being one of only two GBA emulators to offer ROM prefetch emulation,
higan is very near mGBA in terms of accuracy, and far ahead of all
others. As a result of these fixes, compatibility is also much higher
than in v094.

There are also several improvements to SNES emulation. Most
significantly is support for mid-scanline changes to the background mode
in the accuracy profile.

Due to substantial changes to the user interface library used by higan,
this release features yet again a brand-new UI. With the exception of
video shaders and NSS DIP switch selection, it is at feature-parity with
the previous UI. It also offers some new features that v094 lacked.

The cheat code database has also been updated to the latest version by
mightymo.
2015-10-08 22:04:42 +11:00
Tim Allen
b113ecb5a3 Deleting ananke and shaders.
ananke has been superseded by icarus.

The new tomoko UI does not support shaders, and if it ever does it will
probably use another format, so not much point keeping the old files
around.
2015-10-08 22:02:22 +11:00
Tim Allen
bc5ad4a1cd Update to icarus_20151002.
byuu says:

- fixes checkboxes (-again- again [*again*])
- won't check folders with select all / unselect all
- won't crash anymore if the SNES ROM image is too small (Saturday Night
  Slam Masters was crashing it before due to DB size error)
- corrected heuristics for Sufami Turbo base cart (mirrors the
  absurdities of the real cart precisely, since it's one of a kind)
- corrected a few DB issues (BS-X name + PSRAM (again [*again*]), SNSM,
  LAH) (_again_)
  - these are temporary. Monkey patched in the generated .hpp source
    rather than the actual DB
  - not going to fix the SFT sizes because I want to verify what
    happened there first
2015-10-03 16:25:39 +10:00
Tim Allen
1a90e206e0 Update to v094r44b release (open beta).
byuu says:

With any luck, this will be the final WIP before v095. If all looks
good, this will be identical to v095. But if we hit some major issues,
I'll try and fix those first.

The most notable part of this release is probably Jonas Quinn's fix for
the unmapped regions of the GBA memory map. This allows games like Mario
& Luigi and Zelda: Minish Cap to (hopefully) be fully playable now.

icarus now supports my game database, so all games I've dumped will be
emulated with bit-perfect memory maps and native-language game titles.
2015-10-01 20:04:30 +10:00
Tim Allen
483fc81356 Update to v094r44 release.
byuu says:

Changelog:
- return open bus instead of mirroring addresses on the bus (fixes
  Mario&Luigi, Minish Cap, etc) [Jonas Quinn]
- add boolean flag to load requests for slotted game carts (fixes slot
  load prompts)
- rename BS-X Town cart from psram to ram
- icarus: add support for game database

Note: I didn't rename "bsx" to "mcc" in the database for icarus before
uploading that. But I just fixed it locally, so it'll be in the next
WIP. For now, make it create the manifest for you and then rename it
yourself. I did fix the PSRAM size to 256kbit.
2015-10-01 20:00:28 +10:00
Tim Allen
0c87bdabed Update to v094r43 release.
byuu says:

Updated to compile with all of the new hiro changes. My next step is to
write up hiro API documentation, and move the API from alpha (constantly
changing) to beta (rarely changing), in preparation for the first stable
release (backward-compatible changes only.)

Added "--fullscreen" command-line option. I like this over
a configuration file option. Lets you use the emulator in both modes
without having to modify the config file each time.

Also enhanced the command-line game loading. You can now use any of
these methods:

    higan /path/to/game-folder.sfc
    higan /path/to/game-folder.sfc/
    higan /path/to/game-folder.sfc/program.rom

The idea is to support launchers that insist on loading files only.

Technically, the file can be any name (manifest.bml also works); the
only criteria is that the file actually exists and is a file, and not
a directory. This is a requirement to support the first version (a
directory lacking the trailing / identifier), because I don't want my
nall::string class to query the file system to determine if the string
is an actual existing file or directory for its pathname() / dirname()
functions.

Anyway, every game folder I've made so far has program.rom, and that's
very unlikely to change, so this should be fine.

Now, of course, if you drop a regular "game.sfc" file on the emulator,
it won't even try to load it, unless it's in a folder that ends in .fc,
.sfc, etc. In which case, it'll bail out immediately by being unable to
produce a manifest for what is obviously not really a game folder.
2015-08-30 12:08:26 +10:00
Tim Allen
c45633550e Update to v094r42 release.
byuu says:

I imagine you guys will like this WIP very much.

Changelog:
- ListView check boxes on Windows
- ListView removal of columns on reset (changing input dropdowns)
- DirectSound audio duplication on latency change
- DirectSound crash on 20ms latency
- Fullscreen window sizing in multi-monitor setups
- Allow joypad bindings of hotkeys
- Allow triggers to be mapped (Xbox 360 / XInput / Windows only)
- Support joypad rumble for Game Boy Player
- Video scale settings modified from {1x,2x,3x} to {2x,3x,4x}
- System menu now renames to active emulation core
- Added fast forward hotkey

Not changing for v095:
- not adding input focus settings yet
- not adding shaders yet

Not changing at all:
- not implementing maximize
2015-08-24 19:42:11 +10:00
Tim Allen
7081f46e45 Added icarus 20150821. 2015-08-21 21:29:53 +10:00
Tim Allen
213879771e Update to v094r41 release (open beta).
byuu says:

Changelog (since the last open beta):
- icarus is now included. icarus is used to import game files/archives
  into game paks (folders)
- SNES: mid-scanline BGMODE changes now emulated correctly (used only by
  atx2.smc Anthrox Demo)
- GBA: fixed a CPU bug that was causing dozens of games to have
  distorted audio
- GBA: fixed default FlashROM ID; should allow much higher compatibility
- GBA: now using Cydrak's new, much improved, GBA color emulation filter
  (still a work-in-progress)
- re-added command-line loading support for game paks (not for game
  files/archives, sorry!)
- Qt port now compiles and runs again (may be a little buggy;
  Windows/GTK+ ports preferred)
- SNES performance profile now compiles and runs again
- much more
2015-08-21 20:57:03 +10:00
Tim Allen
4344b916b6 Update to v094r40 release.
byuu says:

Changelog:
- updated to newest hiro API
- SFC performance profile builds once again
- hiro: Qt port completed

Errata 1: the hiro/Qt target won't run tomoko just yet. Starts by
crashing inside InputSettings because hiro/Qt isn't forcefully selecting
the first item added to a ComboButton just yet. Even with a monkey patch
to get around that, the UI is incredibly unstable. Lots of geometry
calculation bugs, and a crash when you try and access certain folders in
the browser dialog. Lots of work left to be done there, sadly.

Errata 2: the hiro/Windows port has black backgrounds on all ListView
items. It's because I need to test for unassigned colors and grab the
default Windows brush colors in those cases.

Note: alternating row colors on multi-column ListView widgets is gone
now. Not a bug. May add it back later, but I'm not sure. It doesn't
interact nicely with per-cell background colors.

Things left to do:

First, I have to fix the Windows and Qt target bugs.

Next, I need to go through and revise the hiro API even more (nothing
too major.)

Next, I need to update icarus to use the new hiro API, and add support
for the SFC games database.

Next, I have to rewrite my TSV->BML cheat code tool.

Next, I need to post a final WIP of higan+icarus publicly and wait a few
days.

Next, I need to fix any bugs reported from the final WIP that I can.

Finally, I should be able to release v095.
2015-08-18 20:18:00 +10:00
Tim Allen
0271d6a12b Update to v094r39 release.
byuu says:

Changelog:
- SNES mid-scanline BGMODE fixes finally merged (can run
  atx2.zip{mode7.smc}+mtest(2).sfc properly now)
- Makefile now discards all built-in rules and variables
- switch on bool warning disabled for GCC now as well (was already
  disabled for Clang)
- when loading a game, if any required files are missing, display
  a warning message box (manifest.bml, program.rom, bios.rom, etc)
- when loading a game (or a game slot), if manifest.bml is missing, it
  will invoke icarus to try and generate it
  - if that fails (icarus is missing or the folder is bad), you will get
    a warning telling you that the manifest can't be loaded

The warning prompt on missing files work for both games and the .sys
folders and their files. For some reason, failing to load the DMG/CGB
BIOS is causing a crash before I can display the modal dialog. I have no
idea why, and the stack frame backtrace is junk.

I also can't seem to abort the failed loading process. If I call
Program::unloadMedia(), I get a nasty segfault. Again with a really
nasty stack trace. So for now, it'll just end up sitting there emulating
an empty ROM (solid black screen.) In time, I'd like to fix that too.

Lastly, I need a better method than popen for Windows. popen is kind of
ugly and flashes a console window for a brief second even if the
application launched is linked with -mwindows. Not sure if there even is
one (I need to read the stdout result, so CreateProcess may not work
unless I do something nasty like "> %tmp%/temp") I'm also using the
regular popen instead of _wpopen, so for this WIP, it won't work if your
game folder has non-English letters in the path.
2015-08-04 19:02:04 +10:00
Tim Allen
1b0b54a690 Update to v094r38 release.
byuu says:

I'll post more detailed changes later, but basically:
- fixed Baldur's Gate bug
- guess if no flash ROM ID present (fixes Magical Vacation, many many
  others)
- nall cleanups
- sfc/cartridge major cleanups
- bsxcartridge/"bsx" renamed to mcc/"mcc" after the logic chip it uses
  (consistency with SGB/ICD2)
- ... and more!
2015-08-04 19:01:59 +10:00
Tim Allen
092cac9073 Update to v094r37 release.
byuu says:

Changelog:
- synchronizes lots of nall changes
- changes displayed program title from tomoko to higan(*)
- browser dialog sort is case-insensitive
- .sys folders look at user-selected library path; no longer hard-coded

Tried to get rid of the file modes from the Windows browser dialog, but
it was being a bitch so I left it on for now.

- The storage locations and binary still use tomoko. I'm not really sure
  what to do here. The idea is there may be more than one "higan" UI in
  the future, but I don't want people to go around calling the entire
  program by the UI name. For official Windows releases, I can rename
  the binaries to "higan-{profile}.exe", and by putting the config files
  with the binary, they won't ever see the tomoko folder. Linux is of
  course trickier.

Note: Windows users will need to edit hiro/components.hpp and comment
out these lines:

 #define Hiro_Console
 #define Hiro_IconView
 #define Hiro_SourceView
 #define Hiro_TreeView

I forgot to do that, and too lazy to upload another WIP.
2015-07-14 19:32:43 +10:00
Tim Allen
ecb35cac33 Update to v094r36 release (open beta).
byuu says:

Changelog:
- GBA emulation accuracy has been substantially improved [Cydrak]
- GBA ldm bug fixed [jchadwick]
- SNES SuperFX timing has been improved [AWJ, ARM9, qwertymodo]
- SNES accuracy profile is now ~8% faster than before
- you no longer need to copy the .sys profile folders to
  ~/Emulation/System
    - you still need to put bios.rom (GBA BIOS) into Game Boy
      Advance.sys to use GBA emulation!!
- you no longer need to pre-configure inputs before first use
- loading games / changing window size won't recenter window
- checkboxes in cheat editor update correctly
- can't type into state manager description textbox on an empty slot
- typing in state manager description box works correctly; and updates
  list view correctly
- won't show files that match game extensions anymore (only game folders
  show up)
- libco Win64 port fixes with FPU^H^H^H XMM registers
- libco ARM port now available; so you too can play at 15fps on an RPi2!
  [jessedog3, Cydrak]
- controller selection will check the default item in the menu now on
  game load
- as usual, a whole lot of other stuff I'm forgetting

Known issues:
- type-ahead find does not work in list views (eg game selection
  dialog); I don't know how to fix this
- there's no game file importer yet
- there's no shader support yet
- there's no profiler available for the timing panel, you need to adjust
  values manually for now
2015-07-02 20:24:56 +10:00
Tim Allen
28a14198cb Update to v094r35 release.
byuu says:

GBA timings are *almost* perfect now. Off by 1-3 cycles on each test,
sans a few DMA ones that seem to not run at all according to the numbers
(crazy.)
2015-07-01 20:58:42 +10:00
Tim Allen
7ff7f64482 Update to v094r34 release.
byuu says:

Fixes SuperFX fmult, lmult timings; rambr, bramr and clsr assignment
masking. Implements true GBA ROM prefetch (buggy, lower test score, but
runs Mario & Luigi without crashing on battles anymore.)
2015-06-28 18:44:56 +10:00
Tim Allen
4c9266d18f Update to v094r33 release.
byuu says:

Small WIP, just fixes the timings for GSU multiply.

However, the actual product may still be wrong when CLSR and MS0 are
both set. Since I wasn't 'corrupting' the value in said case before,
then this behavior can only be better than before.

Turned the (cache,memory)_access_timing into functions that compute the
values; and pulled "clockspeed" into GSU.

Also, I'm thinking it might be kind of pointless to have clockspeed at
all. Supposedly even the Mario Chip can run at 21.48MHz anyway.
Enforcing 10.74MHz mode seems kind of silly. If we change it to just be
a "default value for CLSR", then we can just inline the memory access
tests without the need for the access_timing functions (literally just
clsr?2:1 then)

Slight compilation bug: go to processor/gsu/registers.hpp:33 and add

    reg16_t() = default;

I missed it due to a partial recompile. Too lazy to upload another WIP
just for that.

Probably not worth doing much SuperFX testing just yet, as it looks like
they're doing some other tests at the moment on NESdev.
2015-06-27 12:38:47 +10:00
Tim Allen
169e400437 Update to v094r32 release.
byuu says:

Lots more timing improvements to GBA emulation. We're now ahead of
everything but mGBA.

Mario & Luigi is still hanging in battles, so I guess my prefetch
simulation isn't as good as Cydrak's previous attempt, no surprise.
2015-06-27 12:38:08 +10:00
Tim Allen
ea02f1e36a Update to v094r31 release.
byuu says:

This WIP scores 448/920 tests passed.

Gave a shot at ROM prefetch that failed miserably (ranged from 409 to
494 tests passed. Nowhere near where it would be if it were implemented
correctly.)

Three remaining issues:
- ROM prefetch
- DMA timing
- timers (I suspect it's a 3-clock delay in starting, not a 3-clock into
  the future affair)

Probably only going to be able to get the timers working without heroic
amounts of effort.

MUL timing is fixed to use idle cycles.
STMIA is fixed to set sequential at the right moments.
DMA priority support is added, so DMA 0 can interrupt DMA 1 mid-transfer.

In other news ...

I'm calling gtk_widget_destroy on the GtkWindow now, so hopefully those
Window_configure issues go away.

I realize I was leaking Display* handles in the X-video driver while
I was looking at it, so I fixed those.

I added DT_NOPREFIX so the Windows ListView will show & characters
correctly now.
2015-06-25 19:52:32 +10:00
Tim Allen
310ff4fa3b Update to v094r30 release.
byuu says:

This WIP does substantially better on endrift's GBA timing tests. Still
not perfect, though. But hopefully enough to get me out of dead last
place. I also finally fixed the THUMB-mode ldmia bug that jchadwick
reported.

So, GBA emulation should be improved quite a bit, hopefully.
2015-06-24 23:21:24 +10:00
Tim Allen
83f684c66c Update to v094r29 release.
byuu says:

Note: for Windows users, please go to nall/intrinsics.hpp line 60 and
correct the typo from "DISPLAY_WINDOW" to "DISPLAY_WINDOWS" before
compiling, otherwise things won't work at all.

This will be a really major WIP for the core SNES emulation, so please
test as thoroughly as possible.

I rewrote the 65816 CPU core's dispatcher from a jump table to a switch
table. This was so that I could pass class variables as parameters to
opcodes without crazy theatrics.

With that, I killed the regs.r[N] stuff, the flag_t operator|=, &=, ^=
stuff, and all of the template versions of opcodes.

I also removed some stupid pointless flag tests in xcn and pflag that
would always be true.

I sure hope that AWJ is happy with this; because this change was so that
my flag assignments and branch tests won't need to build regs.P into
a full 8-bit variable anymore.

It does of course incur a slight performance hit when you pass in
variables by-value to functions, but it should help with binary size
(and thus cache) by reducing a lot of extra functions. (I know I could
have used template parameters for some things even with a switch table,
but chose not to for the aforementioned reasons.)

Overall, it's about a ~1% speedup from the previous build. The CPU core
instructions were never a bottleneck, but I did want to fix the P flag
building stuff because that really was a dumb mistake v_v'
2015-06-22 23:31:49 +10:00
Tim Allen
e0815b55b9 Update to v094r28 release.
byuu says:

This WIP substantially restructures the ruby API for the first time
since that project started.

It is my hope that with this restructuring, destruction of the ruby
objects should now be deterministic, which should fix the crashing on
closing the emulator on Linux. We'll see I guess ... either way, it
removed two layers of wrappers from ruby, so it's a pretty nice code
cleanup.

It won't compile on Windows due to a few issues I didn't see until
uploading the WIP, too lazy to upload another. But I fixed all the
compilation issues locally, so it'll work on Windows again with the next
WIP (unless I break something else.)

(Kind of annoying that Linux defines glActiveTexture but Windows
doesn't.)
2015-06-20 15:44:05 +10:00
Tim Allen
20cc6148cb Update to v094r27 release.
byuu says:

Added AWJ's fixes for alt/cpu (Tetris Attack framelines issue) and
alt/dsp (Thread::clock reset)

Added fix so that the taskbar entry appears when the application first
starts on Windows.

Fixed checkbox toggling inside of list views on Windows.

Updated nall/image to properly protect variables that should not be
written externally.

New Object syntax for hiro is in.

Fixed the backwards-typing on Windows with the state manager.
NOTE: the list view isn't redrawing when you change the description
text. It does so on the cheat editor because of the resizeColumns call;
but that shouldn't be necessary. I'll try and fix this for the next WIP.
2015-06-18 20:48:53 +10:00
Tim Allen
a21ff570ee Update to v094r26 release (open beta).
byuu says:

Obviously, this is a fairly major WIP. It's the first public release in
17 months. The entire UI has been rewritten (for the 74th time), and is
now internally called tomoko. The official releases will be named higan
(both the binaries and title bar.)

Missing features from v094:

- ananke is missing (this means you will need v094 to create game
  folders to be loaded)
- key assignments are limited to one physical button = one mapping (no
  multi-mapping)
- shader support is missing
- audio/video profiling is missing
- DIP switch window is missing (used by NSS Actraiser with a special
  manifest; that's about it)
- alternate paths for game system folders and configuration BML files

There's some new stuff, but not much. This isn't going to be an exciting
WIP in terms of features. It's more about being a brand new release with
the brand new hiro port and its shared memory model. The goal is to get
these WIPs stable, get v095 out, and then finally start improving the
actual emulation again after that.
2015-06-16 20:30:04 +10:00
Tim Allen
bb3c69a30d Update to v094r25 release.
byuu says:

Windows port should run mostly well now, although exiting fullscreen
breaks the application in a really bizarre way. (clicking on the window
makes it sink to background rather than come to the foreground o_O)

I also need to add the doModalChange => audio.clear() thing for the
accursed menu stuttering with DirectSound.

I also finished porting all of the ruby drivers over to the newer API
changes from nall.

Since I can't compile the Linux or OS X drivers, I have no idea if there
are any typos that will result in compilation errors. If so, please let
me know where they're at and I'll try and fix them. If they're simple,
please try and fix them on your end to test further if you can.

I'm hopeful the udev crash will be gone now that nall::string checks for
null char* values passed to its stringify function. Of course, it's
a problem it's getting a null value in the first place, so it may not
work at all.

If you can compile on Linux (or by some miracle, OS X), please test each
video/audio/input driver if you don't mind, to make sure there's no
"compiles okay but still typos exist" bugs.
2015-06-16 20:30:04 +10:00
Tim Allen
f0c17ffc0d Update to v094r24 release.
byuu says:

Finally!! Compilation works once again on Windows.

However, it's pretty buggy. Modality isn't really working right, you can
still poke at other windows, but when you select ListView items, they
redraw as empty boxes (need to process WM_DRAWITEM before checking
modality.)

The program crashes when you close it (probably a ruby driver's term()
function, that's what it usually is.)

The Layout::setEnabled(false) call isn't working right, so you get that
annoying chiming sound and cursor movement when mapping keyboard keys to
game inputs.

The column sizing seems off a bit on first display for the Hotkeys tab.

And probably lots more.
2015-06-16 20:30:04 +10:00
Tim Allen
314aee8c5c Update to v094r23 release.
byuu says:

The library window is gone, and replaced with
hiro::BrowserWindow::openFolder(). This gives navigation capabilities to
game loading, and it also completes our slotted cart selection code. As
an added bonus, it's less code this way, too.

I also set the window size to consistent sizes between all emulated
systems, so that switching between SFC and GB don't cause the window
size to keep changing, and so that the scaling size is consistent (eg at
normal scale, GB @ 3x is closer to SNES @ 2x.) This means black borders
in GB/GBA mode, but it doesn't look that bad, and it's not like many
people ever use these modes anyway.

Finally, added the placeholder tabs for video, audio and timing. I don't
intend to add the timing calculator code to v095 (it might be better as
a separate tool), but I'll add the ability to set video/audio rates, at
least.

Glitch 1: despite selecting the first item in the BrowserDialog list, if
you press enter when the window appears, it doesn't activate the item
until you press an arrow key first.

Glitch 2: in Game Boy mode, if you set the 4x window size, it's not
honoring the full requested height because the viewport is smaller than
the window. 8+ years of trying to get GTK+ and Qt to simply set the god
damned window size I ask for, and I still can't get them to do it
reliably.

Remaining issues:
- finish configuration panels (video, audio, timing)
- fix ruby driver compilation on Windows
- add DIP switch selection window (NSS) [I may end up punting this one
  to v096]
2015-06-16 20:29:47 +10:00
Tim Allen
7bf4cff946 Update to v094r22 release.
byuu says:

I fixed the hiro layout enable bug, so when you go to assign joypad
input, the window disables itself so your input doesn't mess with the
controls.

I added "reset" to the hotkeys, in case you feel like clearing all of
them at once.

I added device selection support and the ability to disable audio
synchronization (run > 60fps) to the ruby/OSS driver. This is exposed in
tomoko's configuration file.

I added checks to stringify so that assigning null char* strings to
nall::string won't cause crashes anymore (technically the crash was in
strlen(), which doesn't check for null strings, but whatever ... I'll do
the check myself.)

I hooked up BrowserDialog::folderSelect() to loading slotted media for
now. Tested it by loading a Game Boy game successfully through the Super
Game Boy. Definitely want to write a custom window for this though, that
looks more like the library dialog.

Remaining issues:
- finish slotted cart loader (SGB, BSX, ST)
- add DIP switch selection window (NSS) [I may end up punting this one
  to v096]
- add more configuration panels (video, audio, timing)
2015-05-30 21:40:07 +10:00
Tim Allen
99b2b4b57c Update to v094r21 release.
byuu says:

This updates ruby to return shared_pointer<HID::Device> objects instead
of HID::Device* objects. It also fixes an ID bug where joypads were
starting at ID# 2+, but mice were also set to ID# 2. I also revised
nall/hid a lot, with getters and setters instead of stabbing at internal
state. I didn't yet patch nall::string to safely consume nullptr const
char* values, though.
2015-05-24 19:44:28 +10:00
Tim Allen
4e0223d590 Update to v094r20 release.
byuu says:

Main reason for this WIP was because of all the added lines to hiro for
selective component disabling. May as well get all the diff-noise apart
from code changes.

It also merges something I've been talking to Cydrak about ... making
nall::string::(integer,decimal) do built-in binary,octal,hex decoding
instead of just failing on those. This will have fun little side effects
all over the place, like being able to view a topic on my forum via
"forum.byuu.org/topic/0b10010110", heh.

There are two small changes to higan itself, though. First up, I fixed
the resampler ratio when loading non-SNES games. Tested and I can play
Game Boy games fine now. Second, I hooked up menu option hiding for
reset and controller selection. Right now, this works like higan v094,
but I'm thinking I might want to show the "Device -> Controller" even if
that's all that's there. It kind of jives nicer with the input settings
window to see the labels there, I think. And if we ever do add more
stuff, it'll be nice that people already always expect that menu there.

Remaining issues:
* add slotted cart loader (SGB, BSX, ST)
* add DIP switch selection window (NSS)
* add timing configuration (video/audio sync)
2015-05-23 15:37:08 +10:00
Tim Allen
458775a481 Update to v094r19 release.
byuu says:

The input port menu was hooked up.

Alternate input support was added, although I wasn't able to test rumble
support because SDL doesn't support that, and I don't have XInput or
udev drivers on FreeBSD. This one's going to be tricky. Maybe I can test
via cross-compiling on Windows/GTK.

Added mouse capture hotkey, and auto capture/release on toggling
fullscreen (as a bonus it hides the mouse cursor.)

Added all possible video and input drivers to ruby for BSD systems.

Remaining issues before we can release v095:
- add slotted cart loader (SGB, BSX, ST)
- add DIP switch selection window (NSS)
- add timing configuration (video/audio sync)
- hide inapplicable options from system menu (eg controller ports and
  reset button from handheld systems)
2015-05-23 15:29:18 +10:00
Tim Allen
fc8eba133d Update to v094r18 release.
byuu says:

Okay yeah, lots of SNES coprocessor games were horribly broken. They
should be fixed now with the below changes:

Old syntax:

    auto programROM = root["rom[0]/name"].text();
    auto dataROM = root["rom[1]/name"].text();
    load_memory(root["ram[0]"]);

New syntax:

    auto rom = root.find("rom");
    auto ram = root.find("ram");
    auto programROM = rom(0)["name"].text();
    auto dataROM = rom(1)["name"].text();
    load_memory(ram(0));

Since I'm now relying on the XShm driver, which is multi-threaded, I'm
now compiling higan with -fopenmp. On FreeBSD, this requires linking
with -Wl,-rpath=/usr/local/lib -Wl,-rpath=/usr/local/lib/gcc49 to get
the right version of GOMP.

This gives a pretty nice speed boost for XShm, I go from around 101fps
to 111fps at 4x scale on the accuracy profile. The combination of
inlining the accuracy-PPU and parallelizing the XShm renderer about
evenly compensates now for the ~20% CPU overclock I gave up a while ago.

The WIP also has some other niceties from the newer version of nall.
Most noticeably, cheat code database searching is now instantaneous. No
more 3-second stall.
2015-05-16 17:37:13 +10:00
Tim Allen
39ca8a2fab Update to v094r17 release.
byuu says:

This updates higan to use the new Markup::Node changes. This is a really
big change, and one slight typo anywhere could break certain classes of
games from playing.

I don't have ananke hooked up again yet, so I don't have the ability to
test this much. If anyone with some v094 game folders wouldn't mind
testing, I'd help out a great deal.

I'm most concerned about testing one of each SNES special chip game.
Most notably, systems like the SA-1, HitachiDSP and NEC-DSP were using
the fancier lookups, eg node["rom[0]/name"], which I had to convert to
a rather ugly node["rom"].at(0)["name"], which I'm fairly confident
won't work. I'm going to blame that on the fumes from the shelves I just
stained >.> Might work with node.find("rom[0]/name")(0) though ...? But
so ugly ... ugh.

That aside, this WIP adds the accuracy-PPU inlining, so the accuracy
profile should run around 7.5% faster than before.
2015-05-16 17:36:22 +10:00
Tim Allen
c335ee9d80 Update to v094r16 release.
byuu says:

Finished the cheat code system, it'll now load and save cheats.bml to
disk.

Also hooked up overscan masking. But for now you can only configure the
amount it clips via the configuration file, since I don't have a video
settings dialog anymore.

And that's the last of the low-hanging fruit. The remaining items are
all going to be a pain in the ass for one reason or another.

Short-term:
- add input port changing support
- add other input types (mouse-based, etc)

Long-term:
- add slotted cart loader (SGB, BSX, ST)
- add DIP switch selection window (NSS)
- add timing configuration (video/audio sync)

Not planned:
- video color adjustments (will allow emulated color vs raw color; but
  no more sliders)
- pixel shaders
- ananke integration (will need to make a command-line version to get my
  games in)
- fancy audio adjustment controls (resampler, latency, volume)
- input focus settings
- localization support (not enough users)
- window geometry memory
- anything else not in higan v094
2015-04-21 21:58:59 +10:00
Tim Allen
2eb50fd70b Update to v094r15 release.
byuu says:

Implemented the cheat database dialog, and most of the cheat editor
dialog. I still have to handle loading and saving the cheats.bml file
for each game. I wanted to finish it today, but I burned out. It's a ton
of really annoying work to support cheat codes. There's also some issue
with the width calculation for the "code(s)" column in hiro/GTK.

Short-term:
- add input port changing support
- add other input types (mouse-based, etc)
- finish cheat codes

Long-term:
- add slotted cart loader (SGB, BSX, ST)
- add DIP switch selection window (NSS)
- add overscan masking
- add timing configuration (video/audio sync)

Not planned:
- video color adjustments (will allow emulated color vs raw color; but
  no more sliders)
- pixel shaders
- ananke integration (will need to make a command-line version to get my
  games in)
- fancy audio adjustment controls (resampler, latency, volume)
- input focus settings
- localization support (not enough users)
- window geometry memory
- anything else not in higan v094
2015-04-21 21:54:07 +10:00
Tim Allen
89d578bc7f Update to v094r14 release.
byuu says:

Man, over five weeks have passed without so much as touching the
codebase ... time is advancing so fast it's positively frightening. Oh
well, little by little, and we'll get there eventually.

Changelog:
- added save state slots (1-5 in the menu)
- added hotkeys settings dialog + mapping system
- added fullscreen toggle (with a cute aspect correction trick)

About three hours of work here.

Short-term:
- add input port changing support
- add other input types (mouse-based, etc)
- add cheat codes
- add timing configuration (video/audio sync)

Long-term:
- add slotted cart loader (SGB, BSX, ST)
- add DIP switch selection window (NSS)
- add cheat code database
- add state manager
- add overscan masking

Not planned:
- video color adjustments (will allow emulated color vs raw color; but
  no more sliders)
- pixel shaders
- ananke integration (will need to make a command-line version to get my
  games in)
- fancy audio adjustment controls (resampler, latency, volume)
- input focus settings
- relocating game library (not hard, just don't feel like it)
- localization support (not enough users)
- window geometry memory
- anything else not in higan v094
2015-04-13 21:16:33 +10:00
Tim Allen
b4ba95242f Update to v094r13 release.
byuu says:

This version polishes up the input dialogue (reset, erase, disable
button when item not focused, split device ID from mapping name), adds
color emulation toggle, and add dummy menu items for remaining features
(to be filled in later.)

Also, it now compiles cleanly on Windows with GTK.

I didn't test with TDM-GCC-32, because for god knows what reason, the
32-bit version ships with headers from Windows 95 OSR2 only. So I built
with TDM-GCC-64 with arch=x86.

And uh, apparently, moving or resizing a window causes a Visual C++
runtime exception in the GTK+ DLLs. This doesn't happen with trance or
renshuu built with TDM-GCC-32. So, yeah, like I said, don't use -m32.
2015-03-07 21:21:47 +11:00
Tim Allen
a1b2fb0124 Update to v094r12 release.
byuu says:

Changelog:
* added driver selection
* added video scale + aspect correction settings
* added A/V sync + audio mute settings
* added configuration file
* fixed compilation bugs under Windows and Linux
* fixed window sizing
* removed HSU1
* the system menu stays as "System", because "Game Boy Advance" was too
  long a string for the smallest scale size
* some more stuff

You guys probably won't be ecstatic about the video sizing options, but
it's basically your choice of 1x, 2x or 4x scale with optional aspect
correction. 3x was intentionally skipped because it looks horrible on
hires SNES games. The window is resized and recentered upon loading
games. The window doesn't resize otherwise. I never really liked the way
v094 always left you with black screen areas and left you with
off-centered window positions.

I might go ahead and add the pseudo-fullscreen toggle that will jump
into 4x mode (respecting your aspect setting.)

Short-term:
* add input port changing support
* add other input types (mouse-based, etc)
* add save states
* add cheat codes
* add timing configuration (video/audio sync)
* add hotkeys (single state)

We can probably do a new release once the short-term items are
completed.

Long-term:
* add slotted cart loader (SGB, BSX, ST)
* add DIP switch selection window (NSS)
* add cheat code database
* add state manager
* add overscan masking

Not planned:
* video color adjustments (will allow emulated color vs raw color; but
  no more sliders)
* pixel shaders
* ananke integration (will need to make a command-line version to get my
  games in)
* fancy audio adjustment controls (resampler, latency, volume)
* input focus settings
* relocating game library (not hard, just don't feel like it)
* localization support (not enough users)
* window geometry memory
* anything else not in higan v094
2015-03-03 21:26:44 +11:00
Tim Allen
4a069761f9 Update to v094r11 release.
byuu says:

I've hooked up the input subsystem, and the input manager to assign
hotkeys.

So far I only have digital buttons working (keyboard only), and I'm not
planning on supporting input groups again (mapping multiple physical
buttons to one emulated button), but it's progress. As with the rest of
tomoko, the code's a lot more compact. The nice thing about redoing code
so many times is that each time you get a little bit better at it.

The input configuration is saved to ~/.config/tomoko/settings.bml (just
realized that I'm an idiot and need to rename it to input.bml)

Also hooked up game saves and cartridge unloading. Active controller
changing isn't hooked up yet, and I'll probably do it differently.

Oh, and I declared the ruby lines for other platforms.

Still need to add Cydrak's Windows compilation fixes. I am nothing if
not lazy :P
2015-03-03 21:26:44 +11:00
Tim Allen
80c1c9c2ef Update to v094r10 release.
byuu says:

This starts the tomoko UI. So far I have basic library loading and
video+audio output. Basically just enough to take the below screenshot.
(aside from Library, the menus are empty stubs.)

The .sys (system) game folders are now going under ~/Emulation/System,
to avoid needing root privileges to stick them into /usr/share. The game
library now shows all bootable media types, and the drop-down subtype is
gone. I'm going to display a separate modal dialog for loading slotted
games this time around. Much cleaner this way, less clutter.

tomoko's starting off a lot cleaner than ethos was, and I'm scaling back
the number of abstracted classes. What was Utility, Interface, etc are
now being merged all into Program. Of course, the real hell is the input
system. That has so many layers of bullshit that there's really no sane
way to write it.
2015-03-03 21:26:44 +11:00
Tim Allen
a512d14628 Update to v094r09 release.
byuu says:

This will easily be the biggest diff in the history of higan. And not in
a good way.

* target-higan and target-loki have been blown away completely
* nall and ruby massively updated
* phoenix replaced with hiro (pretty near a total rewrite)
* target-higan restarted using hiro (just a window for now)
* all emulation cores updated to compile again
* installation changed to not require root privileges (installs locally)

For the foreseeable future (maybe even permanently?), the new higan UI
will only build under Linux/BSD with GTK+ 2.20+. Probably the most
likely route for Windows/OS X will be to try and figure out how to build
hiro/GTK on those platforms, as awful as that would be. The other
alternative would be to produce new UIs for those platforms ... which
would actually be a good opportunity to make something much more user
friendly.

Being that I just started on this a few hours ago, that means that for
at least a few weeks, don't expect to be able to actually play any
games. Right now, you can pretty much just compile the binary and that's
it. It's quite possible that some nall changes didn't produce
compilation errors, but will produce runtime errors. So until the UI can
actually load games, we won't know if anything is broken. But we should
mostly be okay. It was mostly just trim<1> -> trim changes, moving to
Hash::SHA256 (much cleaner), and patching some reckless memory copy
functions enough to compile.

Progress isn't going to be like it was before: I'm now dividing my time
much thinner between studying and other hobbies.

My aim this time is not to produce a binary for everyone to play games
on. Rather, it's to keep the emulator alive. I want to be able to apply
critical patches again. And I would also like the base of the emulator
to live on, for use in other emulator frontends that utilize higan.
2015-02-28 12:52:53 +11:00
Tim Allen
1a7bc6bb87 Update to v094r08 release.
byuu says:

Lots of changes this time around. FreeBSD stability and compilation is
still a work in progress.

FreeBSD 10 + Clang 3.3 = 108fps
FreeBSD 10 + GCC 4.7 = 130fps

Errata 1: I've been fighting that god-damned endian.h header for the
past nine WIPs now. The above WIP isn't building now because FreeBSD
isn't including headers before using certain types, and you end up with
a trillion error messages. So just delete all the endian.h includes from
nall/intrinsics.hpp to build.

Errata 2: I was trying to match g++ and g++47, so I used $(findstring
g++,$(compiler)), which ends up also matching clang++. Oops. Easy fix,
put Clang first and then else if g++ next. Not ideal, but oh well. All
it's doing for now is declaring -fwrapv twice, so you don't have to fix
it just yet. Probably just going to alias g++="g++47" and do exact
matching instead.

Errata 3: both OpenGL::term and VideoGLX::term are causing a core dump
on BSD. No idea why. The resources are initialized and valid, but
releasing them crashes the application.

Changelog:
- nall/Makefile is more flexible with overriding $(compiler), so you can
  build with GCC or Clang on BSD (defaults to GCC now)
- PLATFORM_X was renamed to PLATFORM_XORG, and it's also declared with
  PLATFORM_LINUX or PLATFORM_BSD
  - PLATFORM_XORG probably isn't the best name ... still thinking about
    what best to call LINUX|BSD|SOLARIS or ^(WINDOWS|MACOSX)
- fixed a few legitimate Clang warning messages in nall
- Compiler::VisualCPP is ugly as hell, renamed to Compiler::CL
- nall/platform includes nall/intrinsics first. Trying to move away from
  testing for _WIN32, etc directly in all files. Work in progress.
- nall turns off Clang warnings that I won't "fix", because they aren't
  broken. It's much less noisy to compile with warnings on now.
- phoenix gains the ability to set background and foreground colors on
  various text container widgets (GTK only for now.)
- rewrote a lot of the MSU1 code to try and simplify it. Really hope
  I didn't break anything ... I don't have any MSU1 test ROMs handy
- SNES coprocessor audio is now mixed as sclamp<16>(system_sample
  + coprocessor_sample) instead of sclamp<16>((sys + cop) / 2)
  - allows for greater chance of aliasing (still low, SNES audio is
    quiet), but doesn't cut base system volume in half anymore
- fixed Super Scope and Justifier cursor colors
- use input.xlib instead of input.x ... allows Xlib input driver to be
  visible on Linux and BSD once again
- make install and make uninstall must be run as root again; no longer
  using install but cp instead for BSD compatibility
- killed $(DESTDIR) ... use make prefix=$DESTDIR$prefix instead
- you can now set text/background colors for the loki console via (eg):
 - settings.terminal.background-color 0x000000
 - settings.terminal.foreground-color 0xffffff
2014-02-24 20:39:09 +11:00
Tim Allen
ecc651c88b Update to v094r07 release.
byuu says:

Changelog for loki:
- added command aliases (match with * [sorry, regex lib isn't available
  everywhere yet], replace with {1}+)
- added command hotkeys
- added window geometry saving
- added save state support
- added power/reset commands
- added an input manager, so you can remap keys (limiting it to the
  keyboard for now though)

The combination of aliases and hotkeys really makes things shine. Save
states will temporarily disable your breakpoints (run/step are
technically temporary breakpoints) so as to ensure the state is captured
at a good time. In practice, this should pose about as much of a problem
as higan desyncing and breaking when capturing states ... should be
exceedingly rare to ever even notice this behavior at all, with 99.9% of
state captures happening in half an instruction boundary. But still,
keep it in mind, as you might see the CPU step one instruction ahead.
Tracing and usage map functionality is still enabled during state
synchronization.

So at this point, I have 100% of the essential stuff in. All that's left
now is to add polish / wishlist features like bass and mosaic
integration.
2014-02-09 17:05:58 +11:00
Tim Allen
3016e595f0 Update to v094r06 release.
byuu says:

New terminal is in. Much nicer to use now. Command history makes a major
difference in usability.

The SMP is now fully traceable and debuggable. Basically they act as
separate entities, you can trace both at the same time, but for the most
part running and stepping is performed on the chip you select.

I'm going to put off CPU+SMP interleave support for a while. I don't
actually think it'll be too hard. Will get trickier if/when we support
coprocessor debugging.

Remaining tasks:
- aliases
- hotkeys
- save states
- window geometry

Basically, the debugger's done. Just have to add the UI fluff.

I also removed tracing/memory export from higan. It was always meant to
be temporary until the debugger was remade.
2014-02-09 17:05:58 +11:00
Tim Allen
423a6c6bf8 Update to v094r05 release.
byuu says:

Commands can be prefixed with: (cpu|smp|ppu|dsp|apu|vram|oam|cgram)/ to
set their source. Eg "vram/hex 0800" or "smp/breakpoints.append execute
ffc0"; default is cpu.

These overlap a little bit in odd ways, but that's just the way the SNES
works: it's not a very orthogonal system. CPU is both a processor and
the main bus (ROM, RAM, WRAM, etc), APU is the shared memory by the
SMP+DSP (eg use it to catch writes from either chip); PPU probably won't
ever be used since it's broken down into three separate buses (VRAM,
OAM, CGRAM), but DSP could be useful for tracking bugs like we found in
Koushien 2 with the DSP echo buffer corrupting SMP opcodes. Technically
the PPU memory pools are only ever tripped by the CPU poking at them, as
the PPU doesn't ever write.

I now have run.for, run.to, step.for, step.to. The difference is that
run only prints the next instruction after running, whereas step prints
all of the instructions along the way as well. run.to acts the same as
"step over" here. Although it's not quite as nice, since you have to
specify the address of the next instruction.

Logging the Field/Vcounter/Hcounter on instruction listings now, good
for timing information.

Added in the tracer mask, as well as memory export, as well as
VRAM/OAM/CGRAM/SMP read/write/execute breakpoints, as well as an APU
usage map (it tracks DSP reads/writes separately, although I don't
currently have debugger callbacks on DSP accesses just yet.)

Have not hooked up actual SMP debugging just yet, but I plan to soon.
Still thinking about how I want to allow / block interleaving of
instructions (terminal output and tracing.)

So ... remaining tasks at this point:
- full SMP debugging
- CPU+SMP interleave support
- aliases
- hotkeys
- save states (will be kind of tricky ... will have to suppress
  breakpoints during synchronization, or abort a save in a break event.)
- keep track of window geometry between runs
2014-02-09 17:05:58 +11:00
Tim Allen
10e2a6d497 Update to v094r04 release.
byuu says:

Changelog:
- target-ethos/ is now target-higan/ (will unfortunately screw up diffs
  pretty badly at this point.)
- had a serious bug in nall::optional<T>::operator=, which is now fixed.
- added tracer (no masking just yet, I need to write a nall::bitvector
  class because I don't want to hard-code those anymore.)
- added usage logging (keep track of RWX/EP states for all bus
  addresses.)
- added read/write to poke at memory (hex also works for reading, but
  this one can poke at MMIO regs and is for one address only.)
- added both run.for (# of instructions) and run.to (program counter
  address.)
- added read/write/execute breakpoints with counters for a given
  address, and with an optional compare byte (for read/write modes.)

About the only major things left now for loki is support for trace
masking, memory export, and VRAM/OAM/CGRAM access.
For phoenix/Console, I really need to add a history to up+down arrows,
and I should support left/right insert-at.
2014-02-09 17:05:58 +11:00
Tim Allen
187ba0eec6 Update to v094r02 release.
byuu says:

Changelog:
- ethos: use nall::programpath() instead of realpath(argv[0]) to get
  executable path
- loki: add presentation window
- loki: add terminal window
- loki: add interface to emulation core
- loki: add ruby
- loki: add enough support to run games and save data on exit
    - load game folders via command-line (or drop folder onto binary),
      use "r" to start, "p" to pause ... temporary command names

I'll probably have to say this several times, but for now, loki is only
available on Linux/GTK+, due to the use of the Console widget. Support
for other platforms can come later easily enough.
2014-02-09 17:05:58 +11:00
Tim Allen
c54be74832 Ignore loki binary too. 2014-02-09 17:05:57 +11:00
Tim Allen
04986d2bf7 Update to v094r01 release.
byuu says:

Changelog:
- port: various compilation fixes for OS X [kode54]
- nall: added programpath() function to return path to process binary
  [todo: need to have ethos use this function]
- ruby: XAudio2 will select default game sound device instead of first
  sound device
- ruby: DirectInput device IDs are no longer ambiguous when VID+PID are
  identical
- ruby: OpenGL won't try and terminate if it hasn't been initialized
- gb: D-pad up+down/left+right not masked in SGB mode
- sfc: rewrote ICD2 video rendering to output in real-time, work with
  cycle-based Game Boy renderer
- sfc: rewrote Bus::reduce(), reduces game loading time by about 500ms
- ethos: store save states in {game}/higan/* instead of {game}/bsnes/*
- loki: added target-loki/ (blank stub for now)
- Makefile: purge out/* on make clean
2014-01-28 21:04:58 +11:00
2015 changed files with 82674 additions and 69342 deletions

1
.gitignore vendored
View File

@@ -1 +0,0 @@
ananke/libananke.so

109
Makefile
View File

@@ -1,109 +0,0 @@
include nall/Makefile
fc := fc
sfc := sfc
gb := gb
gba := gba
profile := accuracy
target := ethos
# options += debugger
# arch := x86
# console := true
# compiler
flags += -I. -O3 -fomit-frame-pointer
link +=
objects := libco
# profile-guided optimization mode
# pgo := instrument
# pgo := optimize
ifeq ($(pgo),instrument)
flags += -fprofile-generate
link += -lgcov
else ifeq ($(pgo),optimize)
flags += -fprofile-use
endif
# platform
ifeq ($(platform),windows)
ifeq ($(arch),x86)
flags += -m32
link += -m32
endif
ifeq ($(console),true)
link += -mconsole
else
link += -mwindows
endif
link += -s -mthreads -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32 -lole32 -lws2_32
link += -Wl,-enable-auto-import -Wl,-enable-runtime-pseudo-reloc
else ifeq ($(platform),macosx)
flags += -march=native
else ifeq ($(platform),linux)
flags += -march=native
link += -s -Wl,-export-dynamic -lX11 -lXext -ldl
else ifeq ($(platform),bsd)
flags += -march=native
link += -s -Wl,-export-dynamic -lX11 -lXext
else
$(error unsupported platform.)
endif
ui := target-$(target)
# implicit rules
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 libco/*
include $(ui)/Makefile
flags := $(flags) $(foreach o,$(call strupper,$(options)),-D$o)
# targets
clean:
-@$(call delete,obj/*.o)
-@$(call delete,obj/*.a)
-@$(call delete,obj/*.so)
-@$(call delete,obj/*.dylib)
-@$(call delete,obj/*.dll)
-@$(call delete,*.res)
-@$(call delete,*.manifest)
archive:
if [ -f higan.tar.xz ]; then rm higan.tar.xz; fi
tar -cJf higan.tar.xz `ls`
sync:
ifeq ($(shell id -un),byuu)
if [ -d ./libco ]; then rm -r ./libco; fi
if [ -d ./nall ]; then rm -r ./nall; fi
if [ -d ./ruby ]; then rm -r ./ruby; fi
if [ -d ./phoenix ]; then rm -r ./phoenix; fi
cp -r ../libco ./libco
cp -r ../nall ./nall
cp -r ../ruby ./ruby
cp -r ../phoenix ./phoenix
rm -r libco/doc
rm -r libco/.test
rm -r nall/.test
rm -r ruby/.test
rm -r phoenix/.test
endif
help:;

View File

@@ -1,49 +0,0 @@
include ../nall/Makefile
include ../phoenix/Makefile
path := /usr/local/lib
flags := $(flags) -O3 -fomit-frame-pointer -I..
all:
$(compiler) $(cppflags) $(flags) -fPIC -o obj/ananke.o -c ananke.cpp
ifeq ($(platform),windows)
$(compiler) $(phoenixflags) -fPIC -o obj/phoenix.o -c ../phoenix/phoenix.cpp
$(compiler) $(link) -shared -o phoenix.dll obj/phoenix.o $(phoenixlink)
$(compiler) $(link) -shared -o ananke.dll obj/ananke.o -L. -lphoenix
else ifeq ($(platform),macosx)
$(compiler) $(link) -shared -dynamiclib -undefined suppress -flat_namespace -o libananke.dylib obj/ananke.o
else
$(compiler) $(link) -shared -Wl,-soname,libananke.so.1 -o libananke.so obj/ananke.o
endif
resource: force
sourcery resource/resource.bml resource/resource.cpp resource/resource.hpp
clean:
-@$(call delete,obj/*.o)
-@$(call delete,*.dll)
-@$(call delete,*.so)
install: uninstall
ifeq ($(platform),windows)
else ifeq ($(platform),macosx)
if [ ! -d ~/Library/Application\ Support/ananke ]; then mkdir ~/Library/Application\ Support/ananke; fi
sudo cp libananke.dylib $(path)/libananke.1.dylib
sudo ln -s $(path)/libananke.1.dylib $(path)/libananke.dylib
else
if [ ! -d ~/.config/ananke ]; then mkdir ~/.config/ananke; fi
sudo cp libananke.so $(path)/libananke.so.1
sudo ln -s $(path)/libananke.so.1 $(path)/libananke.so
endif
uninstall:
ifeq ($(platform),windows)
else ifeq ($(platform),macosx)
if [ -f $(path)/libananke.dylib ]; then sudo rm $(path)/libananke.dylib; fi
if [ -f $(path)/libananke.1.dylib ]; then sudo rm $(path)/libananke.1.dylib; fi
else
if [ -f $(path)/libananke.so ]; then sudo rm $(path)/libananke.so; fi
if [ -f $(path)/libananke.so.1 ]; then sudo rm $(path)/libananke.so.1; fi
endif
force:

View File

@@ -1,179 +0,0 @@
#include <nall/nall.hpp>
#include <nall/beat/patch.hpp>
#include "heuristics/famicom.hpp"
#include "heuristics/super-famicom.hpp"
#include "heuristics/game-boy.hpp"
#include "heuristics/game-boy-advance.hpp"
using namespace nall;
#include <phoenix/phoenix.hpp>
using namespace phoenix;
namespace Database {
#include "database/super-famicom.hpp"
#include "database/sufami-turbo.hpp"
#include "database/bsx-satellaview.hpp"
};
struct Ananke {
#include "configuration.cpp"
string libraryPath;
Ananke();
struct Information {
string path; //path to selected file
string name; //name of selected file (inside of archive if .zip)
string archive; //pathname of archive
string manifest; //manifest from successfully applied patch
} information;
//archive.cpp
vector<uint8_t> extractROM();
vector<uint8_t> extractFile(const string &filename);
//patch.cpp
void applyBeatPatch(vector<uint8_t> &buffer);
//famicom.cpp
void copyFamicomSaves(const string &pathname);
string createFamicomHeuristic(vector<uint8_t> &buffer);
string openFamicom(vector<uint8_t> &buffer);
string syncFamicom(const string &pathname);
//super-famicom.cpp
void copySuperFamicomSaves(const string &pathname);
string createSuperFamicomDatabase(vector<uint8_t> &buffer, Markup::Node &document, const string &manifest);
string createSuperFamicomHeuristic(vector<uint8_t> &buffer);
void createSuperFamicomHeuristicFirmware(vector<uint8_t> &buffer, const string &pathname, bool firmware_appended);
string openSuperFamicom(vector<uint8_t> &buffer);
string syncSuperFamicom(const string &pathname);
//sufami-turbo.cpp
void copySufamiTurboSaves(const string &pathname);
string createSufamiTurboDatabase(vector<uint8_t> &buffer, Markup::Node &document, const string &manifest);
string createSufamiTurboHeuristic(vector<uint8_t> &buffer);
string openSufamiTurbo(vector<uint8_t> &buffer);
string syncSufamiTurbo(const string &pathname);
//bsx-satellaview.cpp
string createBsxSatellaviewDatabase(vector<uint8_t> &buffer, Markup::Node &document, const string &manifest);
string createBsxSatellaviewHeuristic(vector<uint8_t> &buffer);
string openBsxSatellaview(vector<uint8_t> &buffer);
string syncBsxSatellaview(const string &pathname);
//game-boy.cpp
void copyGameBoySaves(const string &pathname);
string createGameBoyHeuristic(vector<uint8_t> &buffer);
string openGameBoy(vector<uint8_t> &buffer);
string syncGameBoy(const string &pathname);
//game-boy-advance.cpp
void copyGameBoyAdvanceSaves(const string &pathname);
string createGameBoyAdvanceHeuristic(vector<uint8_t> &buffer);
string openGameBoyAdvance(vector<uint8_t> &buffer);
string syncGameBoyAdvance(const string &pathname);
static bool supported(const string &filename);
string open(string filename = "");
string sync(string pathname);
};
#include "resource/resource.cpp"
#include "file-dialog.cpp"
#include "archive.cpp"
#include "patch.cpp"
#include "famicom.cpp"
#include "super-famicom.cpp"
#include "sufami-turbo.cpp"
#include "bsx-satellaview.cpp"
#include "game-boy.cpp"
#include "game-boy-advance.cpp"
FileDialog *fileDialog = nullptr;
Ananke::Ananke() {
libraryPath = string::read({configpath(), "higan/library.bml"}).strip().ltrim<1>("Path: ").replace("\\", "/");
if(libraryPath.empty()) libraryPath = {userpath(), "Emulation/"};
if(libraryPath.endsWith("/") == false) libraryPath.append("/");
}
bool Ananke::supported(const string &filename) {
string extension = nall::extension(filename);
if(extension == "fc" ) return true;
if(extension == "nes") return true;
if(extension == "sfc") return true;
if(extension == "smc") return true;
if(extension == "st" ) return true;
if(extension == "bs" ) return true;
if(extension == "gb" ) return true;
if(extension == "gbc") return true;
if(extension == "gba") return true;
if(extension == "zip") return true;
return false;
}
string Ananke::open(string filename) {
if(filename.empty()) {
if(!fileDialog) {
fileDialog = new FileDialog;
fileDialog->setGeometry(config.geometry);
}
fileDialog->setPath(config.path);
filename = fileDialog->open();
config.geometry = fileDialog->geometry().text();
}
if(filename.empty()) return "";
information.path = dir(filename);
information.name = notdir(filename);
config.path = information.path; //remember last used directory
vector<uint8_t> buffer;
if(filename.endsWith(".zip")) {
information.archive = filename;
buffer = extractROM();
} else {
buffer = file::read(filename);
}
if(buffer.size() == 0) return ""; //failed to read file
applyBeatPatch(buffer);
if(information.name.endsWith(".fc") || information.name.endsWith(".nes")) return openFamicom(buffer);
if(information.name.endsWith(".sfc") || information.name.endsWith(".smc")) return openSuperFamicom(buffer);
if(information.name.endsWith(".st")) return openSufamiTurbo(buffer);
if(information.name.endsWith(".bs")) return openBsxSatellaview(buffer);
if(information.name.endsWith(".gb") || information.name.endsWith(".gbc")) return openGameBoy(buffer);
if(information.name.endsWith(".gba")) return openGameBoyAdvance(buffer);
return "";
}
string Ananke::sync(string pathname) {
if(pathname.endsWith(".fc/")) return syncFamicom(pathname);
if(pathname.endsWith(".sfc/")) return syncSuperFamicom(pathname);
if(pathname.endsWith(".st/")) return syncSufamiTurbo(pathname);
if(pathname.endsWith(".bs/")) return syncBsxSatellaview(pathname);
if(pathname.endsWith(".gb/")) return syncGameBoy(pathname);
if(pathname.endsWith(".gbc/")) return syncGameBoy(pathname);
if(pathname.endsWith(".gba/")) return syncGameBoyAdvance(pathname);
return "";
}
extern "C" string ananke_browse(const string &filename) {
Ananke ananke;
return ananke.open();
}
extern "C" string ananke_open(const string &filename) {
Ananke ananke;
return ananke.open(filename);
}
extern "C" string ananke_sync(const string &pathname) {
Ananke ananke;
return ananke.sync(pathname);
}

View File

@@ -1,29 +0,0 @@
vector<uint8_t> Ananke::extractROM() {
unzip archive;
if(archive.open(information.archive)) {
for(auto& file : archive.file) {
if(file.name.endsWith(".fc") || file.name.endsWith(".nes")
|| file.name.endsWith(".sfc") || file.name.endsWith(".smc")
|| file.name.endsWith(".st") || file.name.endsWith(".bs")
|| file.name.endsWith(".gb") || file.name.endsWith(".gbc")
|| file.name.endsWith(".gba")
) {
information.name = notdir(file.name);
return archive.extract(file);
}
}
}
return vector<uint8_t>();
}
vector<uint8_t> Ananke::extractFile(const string& filename) {
unzip archive;
if(archive.open(information.archive)) {
for(auto& file : archive.file) {
if(notdir(file.name) == filename) {
return archive.extract(file);
}
}
}
return vector<uint8_t>();
}

View File

@@ -1,72 +0,0 @@
string Ananke::createBsxSatellaviewDatabase(vector<uint8_t> &buffer, Markup::Node &document, const string &manifest) {
string pathname = {
libraryPath, "BS-X Satellaview/",
document["release/information/name"].text(),
" (", document["release/information/region"].text(), ")",
" (", document["release/information/revision"].text(), ")",
".bs/"
};
directory::create(pathname);
//strip "release" root node from database entry (since a single game manifest isn't part of a database)
string markup = manifest;
markup.replace("\n ", "\n");
markup.replace("information", "\ninformation");
markup.ltrim<1>("release\n");
file::write({pathname, "manifest.bml"}, markup);
file::write({pathname, "program.rom"}, buffer);
return pathname;
}
string Ananke::createBsxSatellaviewHeuristic(vector<uint8_t> &buffer) {
string pathname = {
libraryPath, "BS-X Satellaview/",
nall::basename(information.name),
".bs/"
};
directory::create(pathname);
file::write({pathname, "manifest.bml"}, {
"unverified\n",
"\n",
"cartridge\n",
" rom name=program.rom size=0x", hex(buffer.size()), " type=FlashROM\n",
"\n",
"information\n",
" title: ", nall::basename(information.name), "\n"
});
file::write({pathname, "program.rom"}, buffer);
return pathname;
}
string Ananke::openBsxSatellaview(vector<uint8_t> &buffer) {
string sha256 = nall::sha256(buffer.data(), buffer.size());
string databaseText = string::read({configpath(), "ananke/database/BS-X Satellaview.bml"}).strip();
if(databaseText.empty()) databaseText = string{Database::BsxSatellaview}.strip();
lstring databaseItem = databaseText.split("\n\n");
for(auto &item : databaseItem) {
item.append("\n");
auto document = Markup::Document(item);
if(document["release/information/sha256"].text() == sha256) {
return createBsxSatellaviewDatabase(buffer, document, item);
}
}
return createBsxSatellaviewHeuristic(buffer);
}
string Ananke::syncBsxSatellaview(const string &pathname) {
auto buffer = file::read({pathname, "program.rom"});
if(buffer.size() == 0) return "";
directory::remove(pathname);
information.path = pathname;
information.name = notdir(string{pathname}.rtrim<1>("/"));
return openBsxSatellaview(buffer);
}

View File

@@ -1,17 +0,0 @@
struct Settings : Configuration::Document {
string path;
string geometry;
Settings() {
Configuration::Node node;
node.append(path = userpath(), "Path");
node.append(geometry = "64,64,480,600", "Geometry");
append(node, "Settings");
directory::create({configpath(), "ananke/"});
load({configpath(), "ananke/settings.bml"});
}
~Settings() {
save({configpath(), "ananke/settings.bml"});
}
} config;

View File

@@ -1,19 +0,0 @@
string BsxSatellaview = R"(
database revision=2013-01-22
release
cartridge
rom name=program.rom size=0x80000 type=MaskROM
information
title:
name: Same Game - Character Cassette
region: JP
revision: 1.0
board: BSMC-CR-01
serial: BSMC-ZS5J-JPN
sha256: 80c34b50817d58820bc8c88d2d9fa462550b4a76372e19c6467cbfbc8cf5d9ef
configuration
rom name=program.rom size=0x80000 type=MaskROM
)";

View File

@@ -1,162 +0,0 @@
string SufamiTurbo = R"(
database revision=2013-01-22
release
cartridge linkable
rom name=program.rom size=0x80000
ram name=save.ram size=0x800
information
title: SDウルトラバトル
name: SD Ultra Battle - Ultraman Densetsu
region: JP
revision: 1.0
serial: SFT-0101-JPN
sha256: 2bb55214fb668ca603d7b944b14f105dfb10b987a8902d420fe4ae1cb69c1d4a
configuration
rom name=program.rom size=0x80000
ram name=save.ram size=0x800
linkable
release
cartridge linkable
rom name=program.rom size=0x80000
ram name=save.ram size=0x800
information
title: SDウルトラバトル
name: SD Ultra Battle - Seven Densetsu
region: JP
revision: 1.0
serial: SFT-0102-JPN
sha256: 2fec5f2bc7dee010af10569a3d2bc18715a79a126940800c3eade5abbd625e3f
configuration
rom name=program.rom size=0x80000
ram name=save.ram size=0x800
linkable
release
cartridge linkable
rom name=program.rom size=0x80000
ram name=save.ram size=0x800
information
title:
name: Poi Poi Ninja World
region: JP
revision: 1.0
serial: SFT-0103-JPN
sha256: 602b20b788640f5743487108a10f3f77bca5ce2d24208b25b1ca498a96eb0d69
configuration
rom name=program.rom size=0x80000
ram name=save.ram size=0x800
linkable
release
cartridge linkable
rom name=program.rom size=0x80000
ram name=save.ram size=0x2000
information
title: SDガンダムジェネレーション
name: SD Gundam Generation - Ichinen Sensouki
region: JP
revision: 1.0
serial: SFT-0104-JPN
sha256: 3e82215bed08274874b30d461fc4a965c6bca932229da5d46d56e36f484d65eb
configuration
rom name=program.rom size=0x80000
ram name=save.ram size=0x2000
linkable
release
cartridge linkable
rom name=program.rom size=0x80000
ram name=save.ram size=0x2000
information
title: SDガンダムジェネレーション
name: SD Gundam Generation - Grips Senki
region: JP
revision: 1.0
serial: SFT-0105-JPN
sha256: 8547a08ed11fe408eac282a90ac46654bd2e5f49bda3aec8e5edf166a0a4b9af
configuration
rom name=program.rom size=0x80000
ram name=save.ram size=0x2000
linkable
release
cartridge
rom name=program.rom size=0x80000
information
title:
name: Gegege no Kitarou - Youkai Donjara
region: JP
revision: 1.0
serial: SFT-0106-JPN
sha256: d93b3a570e7cf343f680ab0768a50b77e3577f9c555007e2de3decd6bc4765c8
configuration
rom name=program.rom size=0x80000
release
cartridge linkable
rom name=program.rom size=0x80000
ram name=save.ram size=0x2000
information
title: SDガンダムジェネレーション
name: SD Gundam Generation - Axis Senki
region: JP
revision: 1.0
serial: SFT-0107-JPN
sha256: 2a9d7c9a61318861028a73ca03e32a48cff162d76cba36fbaab8690b212efe9b
configuration
rom name=program.rom size=0x80000
ram name=save.ram size=0x2000
linkable
release
cartridge linkable
rom name=program.rom size=0x80000
ram name=save.ram size=0x2000
information
title: SDガンダムジェネレーション
name: SD Gundam Generation - Babylonia Kenkoku Senki
region: JP
revision: 1.0
serial: SFT-0108-JPN
sha256: 60ac017c18f534e8cf24ca7f38e22ce92db95ea6c30b2d59d76f13c4f1c8a6e4
configuration
rom name=program.rom size=0x80000
ram name=save.ram size=0x2000
linkable
release
cartridge linkable
rom name=program.rom size=0x80000
ram name=save.ram size=0x2000
information
title: SDガンダムジェネレーション
name: SD Gundam Generation - Zanscar Senki
region: JP
revision: 1.0
serial: SFT-0110-JPN
sha256: 5951a58a91d8e397d0a237ccc2b1248e17c7312cb9cc11cbc350200a97b4e021
configuration
rom name=program.rom size=0x80000
ram name=save.ram size=0x2000
linkable
release
cartridge linkable
rom name=program.rom size=0x80000
ram name=save.ram size=0x800
information
title: SDガンダムジェネレーション
name: SD Gundam Generation - Colony Kakutouki
region: JP
revision: 1.0
serial: SFT-0111-JPN
sha256: e639b5d5d722432b6809ccc6801dc584e1a3016379f34b335ed2dfa73b1ebf69
configuration
rom name=program.rom size=0x80000
ram name=save.ram size=0x800
linkable
)";

File diff suppressed because it is too large Load Diff

View File

@@ -1,39 +0,0 @@
void Ananke::copyFamicomSaves(const string &pathname) {
if(!file::exists({pathname, "save.ram"})) {
if(file::exists({information.path, nall::basename(information.name), ".sav"})) {
file::copy({information.path, nall::basename(information.name), ".srm"}, {pathname, "save.ram"});
}
}
}
string Ananke::createFamicomHeuristic(vector<uint8_t> &buffer) {
string pathname = {
libraryPath, "Famicom/",
nall::basename(information.name),
".fc/"
};
directory::create(pathname);
FamicomCartridge info(buffer.data(), buffer.size());
string markup = {"unverified\n\n", info.markup};
markup.append("\ninformation\n title: ", nall::basename(information.name), "\n");
if(!information.manifest.empty()) markup = information.manifest; //override with embedded beat manifest, if one exists
file::write({pathname, "manifest.bml"}, markup);
file::write({pathname, "program.rom"}, buffer.data() + 16, info.prgrom);
if(info.chrrom > 0) file::write({pathname, "character.rom"}, buffer.data() + 16 + info.prgrom, info.chrrom);
copyFamicomSaves(pathname);
return pathname;
}
string Ananke::openFamicom(vector<uint8_t> &buffer) {
return createFamicomHeuristic(buffer);
}
//this currently cannot work:
//game folders discard iNES header required for heuristic detection
//a games database of all commercial Famicom software will be required
string Ananke::syncFamicom(const string &pathname) {
return "";
}

View File

@@ -1,106 +0,0 @@
struct FileDialog : Window {
VerticalLayout layout;
HorizontalLayout pathLayout;
LineEdit pathEdit;
Button homeButton;
Button upButton;
ListView fileList;
HorizontalLayout controlLayout;
Label filterLabel;
Button openButton;
string open() {
setVisible();
fileList.setFocused();
filename = "";
setModal();
return filename;
}
void setPath(const string &path) {
pathname = string{path}.transform("\\", "/");
if(pathname.empty()) pathname = userpath();
if(pathname.endsWith("/") == false) pathname.append("/");
pathEdit.setText(pathname);
fileList.reset();
filenameList.reset();
lstring folders = directory::ifolders(pathname);
for(auto &folder : folders) {
fileList.append(string{folder}.rtrim<1>("/"));
fileList.setImage(filenameList.size(), 0, {resource::folder, sizeof resource::folder});
filenameList.append({pathname, folder});
}
lstring files = directory::ifiles(pathname);
for(auto &file : files) {
if(Ananke::supported(file) == false) continue; //ignore unsupported extensions
fileList.append(file);
if(extension(file) == "zip") {
fileList.setImage(filenameList.size(), 0, {resource::archive, sizeof resource::archive});
} else {
fileList.setImage(filenameList.size(), 0, {resource::file, sizeof resource::file});
}
filenameList.append({pathname, file});
}
fileList.setSelection(0);
fileList.setSelected();
fileList.setFocused();
}
FileDialog() {
setTitle("Load Image");
layout.setMargin(5);
homeButton.setImage({resource::home, sizeof resource::home});
upButton.setImage({resource::up, sizeof resource::up});
filterLabel.setText("Filter: *.fc, *.sfc, *.st, *.bs, *.gb, *.gbc, *.gba, *.nes, *.smc, *.zip");
openButton.setText("Open");
append(layout);
layout.append(pathLayout, {~0, 0}, 5);
pathLayout.append(pathEdit, {~0, 0}, 5);
pathLayout.append(homeButton, {28, 28}, 5);
pathLayout.append(upButton, {28, 28});
layout.append(fileList, {~0, ~0}, 5);
layout.append(controlLayout, {~0, 0});
controlLayout.append(filterLabel, {~0, 0}, 5);
controlLayout.append(openButton, {80, 0});
pathEdit.onActivate = [&] {
string path = pathEdit.text();
setPath(path);
};
homeButton.onActivate = [&] {
setPath(userpath());
};
upButton.onActivate = [&] {
setPath(parentdir(pathname));
};
fileList.onActivate = openButton.onActivate = [&] {
if(fileList.selected() == false) return;
string name = filenameList(fileList.selection());
if(name.empty()) return;
if(name.endsWith("/")) return setPath(name);
filename = name;
onClose();
};
onClose = [&] {
setModal(false);
setVisible(false);
};
}
private:
string pathname;
string filename;
lstring filenameList;
};

View File

@@ -1,58 +0,0 @@
void Ananke::copyGameBoyAdvanceSaves(const string &pathname) {
if(!file::exists({pathname, "save.ram"})) {
if(file::exists({information.path, nall::basename(information.name), ".sav"})) {
file::copy({information.path, nall::basename(information.name), ".sav"}, {pathname, "save.ram"});
}
}
if(!file::exists({pathname, "rtc.ram"})) {
if(file::exists({information.path, nall::basename(information.name), ".rtc"})) {
file::copy({information.path, nall::basename(information.name), ".rtc"}, {pathname, "rtc.ram"});
}
}
}
string Ananke::createGameBoyAdvanceHeuristic(vector<uint8_t> &buffer) {
string pathname = {
libraryPath, "Game Boy Advance/",
nall::basename(information.name),
".gba/"
};
directory::create(pathname);
GameBoyAdvanceCartridge info(buffer.data(), buffer.size());
string markup = {"unverified\n\n", info.markup};
markup.append("\ninformation\n title: ", nall::basename(information.name), "\n");
if(!information.manifest.empty()) markup = information.manifest; //override with embedded beat manifest, if one exists
file::write({pathname, "manifest.bml"}, markup);
file::write({pathname, "program.rom"}, buffer);
copyGameBoyAdvanceSaves(pathname);
return pathname;
}
string Ananke::openGameBoyAdvance(vector<uint8_t> &buffer) {
return createGameBoyAdvanceHeuristic(buffer);
}
string Ananke::syncGameBoyAdvance(const string &pathname) {
auto buffer = file::read({pathname, "program.rom"});
if(buffer.size() == 0) return "";
auto save = file::read({pathname, "save.ram"});
if(save.size() == 0) save = file::read({pathname, "save.rwm"});
auto rtc = file::read({pathname, "rtc.ram"});
if(rtc.size() == 0) rtc = file::read({pathname, "rtc.rwm"});
directory::remove(pathname);
information.path = pathname;
information.name = notdir(string{pathname}.rtrim<1>("/"));
string outputPath = openGameBoyAdvance(buffer);
if(save.size()) file::write({outputPath, "save.ram"}, save);
if(rtc.size()) file::write({outputPath, "rtc.ram"}, rtc);
return outputPath;
}

View File

@@ -1,59 +0,0 @@
void Ananke::copyGameBoySaves(const string &pathname) {
if(!file::exists({pathname, "save.ram"})) {
if(file::exists({information.path, nall::basename(information.name), ".sav"})) {
file::copy({information.path, nall::basename(information.name), ".sav"}, {pathname, "save.ram"});
}
}
if(!file::exists({pathname, "rtc.ram"})) {
if(file::exists({information.path, nall::basename(information.name), ".rtc"})) {
file::copy({information.path, nall::basename(information.name), ".rtc"}, {pathname, "rtc.ram"});
}
}
}
string Ananke::createGameBoyHeuristic(vector<uint8_t> &buffer) {
GameBoyCartridge info(buffer.data(), buffer.size());
string pathname = {
libraryPath, "Game Boy", (info.info.cgb ? " Color" : ""), "/",
nall::basename(information.name),
".", (info.info.cgb ? "gbc" : "gb"), "/"
};
directory::create(pathname);
string markup = {"unverified\n\n", info.markup};
markup.append("\ninformation\n title: ", nall::basename(information.name), "\n");
if(!information.manifest.empty()) markup = information.manifest; //override with embedded beat manifest, if one exists
file::write({pathname, "manifest.bml"}, markup);
file::write({pathname, "program.rom"}, buffer);
copyGameBoySaves(pathname);
return pathname;
}
string Ananke::openGameBoy(vector<uint8_t> &buffer) {
return createGameBoyHeuristic(buffer);
}
string Ananke::syncGameBoy(const string &pathname) {
auto buffer = file::read({pathname, "program.rom"});
if(buffer.size() == 0) return "";
auto save = file::read({pathname, "save.ram"});
if(save.size() == 0) save = file::read({pathname, "save.rwm"});
auto rtc = file::read({pathname, "rtc.ram"});
if(rtc.size() == 0) rtc = file::read({pathname, "rtc.rwm"});
directory::remove(pathname);
information.path = pathname;
information.name = notdir(string{pathname}.rtrim<1>("/"));
string outputPath = openGameBoy(buffer);
if(save.size()) file::write({outputPath, "save.ram"}, save);
if(rtc.size()) file::write({outputPath, "rtc.ram"}, rtc);
return outputPath;
}

View File

@@ -1,23 +0,0 @@
#ifndef NALL_EMULATION_SATELLAVIEW_HPP
#define NALL_EMULATION_SATELLAVIEW_HPP
#include <nall/sha256.hpp>
#include <nall/string.hpp>
namespace nall {
struct SatellaviewCartridge {
string markup;
inline SatellaviewCartridge(const uint8_t *data, unsigned size);
};
SatellaviewCartridge::SatellaviewCartridge(const uint8_t *data, unsigned size) {
markup = "";
markup.append("cartridge\n");
markup.append(" rom name=program.rom size=0x", hex(size), " type=FlashROM\n");
}
}
#endif

View File

@@ -1,814 +0,0 @@
#ifndef NALL_EMULATION_SUPER_FAMICOM_HPP
#define NALL_EMULATION_SUPER_FAMICOM_HPP
#include <nall/sha256.hpp>
#include <nall/string.hpp>
namespace nall {
struct SuperFamicomCartridge {
string markup;
inline SuperFamicomCartridge(const uint8_t *data, unsigned size);
//private:
inline void read_header(const uint8_t *data, unsigned size);
inline unsigned find_header(const uint8_t *data, unsigned size);
inline unsigned score_header(const uint8_t *data, unsigned size, unsigned addr);
enum HeaderField {
CartName = 0x00,
Mapper = 0x15,
RomType = 0x16,
RomSize = 0x17,
RamSize = 0x18,
CartRegion = 0x19,
Company = 0x1a,
Version = 0x1b,
Complement = 0x1c, //inverse checksum
Checksum = 0x1e,
ResetVector = 0x3c,
};
enum Mode {
ModeNormal,
ModeBsxSlotted,
ModeBsx,
ModeSufamiTurbo,
ModeSuperGameBoy,
};
enum Type {
TypeNormal,
TypeBsxSlotted,
TypeBsxBios,
TypeBsx,
TypeSufamiTurboBios,
TypeSufamiTurbo,
TypeSuperGameBoy1Bios,
TypeSuperGameBoy2Bios,
TypeGameBoy,
TypeUnknown,
};
enum Region {
NTSC,
PAL,
};
enum MemoryMapper {
LoROM,
HiROM,
ExLoROM,
ExHiROM,
SuperFXROM,
SA1ROM,
SPC7110ROM,
BSCLoROM,
BSCHiROM,
BSXROM,
STROM,
};
enum DSP1MemoryMapper {
DSP1Unmapped,
DSP1LoROM1MB,
DSP1LoROM2MB,
DSP1HiROM,
};
bool loaded; //is a base cartridge inserted?
unsigned crc32; //crc32 of all cartridges (base+slot(s))
unsigned rom_size;
unsigned ram_size;
bool firmware_appended; //true if firmware is appended to end of ROM data
Mode mode;
Type type;
Region region;
MemoryMapper mapper;
DSP1MemoryMapper dsp1_mapper;
bool has_bsx_slot;
bool has_superfx;
bool has_sa1;
bool has_sharprtc;
bool has_epsonrtc;
bool has_sdd1;
bool has_spc7110;
bool has_cx4;
bool has_dsp1;
bool has_dsp2;
bool has_dsp3;
bool has_dsp4;
bool has_obc1;
bool has_st010;
bool has_st011;
bool has_st018;
};
SuperFamicomCartridge::SuperFamicomCartridge(const uint8_t *data, unsigned size) {
firmware_appended = false;
//skip copier header
if((size & 0x7fff) == 512) data += 512, size -= 512;
markup = "";
if(size < 0x8000) return;
read_header(data, size);
markup = "";
if(type == TypeGameBoy) return;
if(type == TypeBsx) return;
if(type == TypeSufamiTurbo) return;
const char *range = (rom_size > 0x200000) || (ram_size > 32 * 1024) ? "0000-7fff" : "0000-ffff";
markup.append("cartridge region=", region == NTSC ? "NTSC" : "PAL", "\n");
if(type == TypeSuperGameBoy1Bios || type == TypeSuperGameBoy2Bios) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
" map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000\n"
" icd2 revision=1\n"
" rom name=sgb.boot.rom size=0x100\n"
" map id=io address=00-3f,80-bf:6000-7fff\n"
);
if((rom_size & 0x7fff) == 0x100) {
firmware_appended = true;
rom_size -= 0x100;
}
}
else if(has_cx4) {
markup.append(
" hitachidsp model=HG51B169 frequency=20000000\n"
" rom id=program name=program.rom size=0x", hex(rom_size), "\n"
" rom id=data name=cx4.data.rom size=0xc00\n"
" ram id=data size=0xc00\n"
" map id=io address=00-3f,80-bf:6000-7fff\n"
" map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000\n"
" map id=ram address=70-77:0000-7fff\n"
);
if((rom_size & 0x7fff) == 0xc00) {
firmware_appended = true;
rom_size -= 0xc00;
}
}
else if(has_spc7110) {
markup.append(
" spc7110\n"
" rom id=program name=program.rom size=0x100000\n"
" rom id=data name=data.rom size=0x", hex(rom_size - 0x100000), "\n"
" ram name=save.ram size=0x", hex(ram_size), "\n"
" map id=io address=00-3f,80-bf:4800-483f\n"
" map id=io address=50:0000-ffff\n"
" map id=rom address=00-3f,80-bf:8000-ffff\n"
" map id=rom address=c0-ff:0000-ffff\n"
" map id=ram address=00-3f,80-bf:6000-7fff mask=0xe000\n"
);
}
else if(has_sdd1) {
markup.append(
" sdd1\n"
" rom name=program.rom size=0x", hex(rom_size), "\n"
);
if(ram_size > 0) markup.append(
" ram name=save.ram size=0x", hex(ram_size), "\n"
);
markup.append(
" map id=io address=00-3f,80-bf:4800-4807\n"
" map id=rom address=00-3f,80-bf:8000-ffff mask=0x8000\n"
" map id=rom address=c0-ff:0000-ffff\n"
);
if(ram_size > 0) markup.append(
" map id=ram address=20-3f,a0-bf:6000-7fff mask=0xe000\n"
" map id=ram address=70-7f:0000-7fff\n"
);
}
else if(mapper == LoROM) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
);
if(ram_size > 0) markup.append(
" ram name=save.ram size=0x", hex(ram_size), "\n"
);
markup.append(
" map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000\n"
);
if(ram_size > 0) markup.append(
" map id=ram address=70-7f,f0-ff:", range, "\n"
);
}
else if(mapper == HiROM) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
);
if(ram_size > 0) markup.append(
" ram name=save.ram size=0x", hex(ram_size), "\n"
);
markup.append(
" map id=rom address=00-3f,80-bf:8000-ffff\n"
" map id=rom address=40-7f,c0-ff:0000-ffff\n"
);
if(ram_size > 0) markup.append(
" map id=ram address=10-3f,90-bf:6000-7fff mask=0xe000\n"
);
}
else if(mapper == ExLoROM) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
);
if(ram_size > 0) markup.append(
" ram name=save.ram size=0x", hex(ram_size), "\n"
);
markup.append(
" map id=rom address=00-3f,80-bf:8000-ffff mask=0x8000\n"
" map id=rom address=40-7f:0000-ffff\n"
);
if(ram_size > 0) markup.append(
" map id=ram address=20-3f,a0-bf:6000-7fff\n"
" map id=ram address=70-7f:0000-7fff\n"
);
}
else if(mapper == ExHiROM) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
);
if(ram_size > 0) markup.append(
" ram name=save.ram size=0x", hex(ram_size), "\n"
);
markup.append(
" map id=rom address=00-3f:8000-ffff base=0x400000\n"
" map id=rom address=40-7f:0000-ffff base=0x400000\n"
" map id=rom address=80-bf:8000-ffff mask=0xc00000\n"
" map id=rom address=c0-ff:0000-ffff mask=0xc00000\n"
);
if(ram_size > 0) markup.append(
" map id=ram address=20-3f,a0-bf:6000-7fff mask=0xe000\n"
" map id=ram address=70-7f:", range, "\n"
);
}
else if(mapper == SuperFXROM) {
markup.append(
" superfx revision=3\n"
" rom name=program.rom size=0x", hex(rom_size), "\n"
);
if(ram_size > 0) markup.append(
" ram name=save.ram size=0x", hex(ram_size), "\n"
);
markup.append(
" map id=io address=00-3f,80-bf:3000-32ff\n"
" map id=rom address=00-3f,80-bf:8000-ffff mask=0x8000\n"
" map id=rom address=40-5f,c0-df:0000-ffff\n"
);
if(ram_size > 0) markup.append(
" map id=ram address=00-3f,80-bf:6000-7fff size=0x2000\n"
" map id=ram address=70-71,f0-f1:0000-ffff\n"
);
}
else if(mapper == SA1ROM) {
markup.append(
" sa1\n"
" rom name=program.rom size=0x", hex(rom_size), "\n"
);
if(ram_size > 0) markup.append(
" ram id=bitmap name=save.ram size=0x", hex(ram_size), "\n"
);
markup.append(
" ram id=internal size=0x800\n"
" map id=io address=00-3f,80-bf:2200-23ff\n"
" map id=rom address=00-3f,80-bf:8000-ffff\n"
" map id=rom address=c0-ff:0000-ffff\n"
);
if(ram_size > 0) markup.append(
" map id=bwram address=00-3f,80-bf:6000-7fff\n"
" map id=bwram address=40-4f:0000-ffff\n"
);
markup.append(
" map id=iram address=00-3f,80-bf:3000-37ff\n"
);
}
else if(mapper == BSCLoROM) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
" ram name=save.ram size=0x", hex(ram_size), "\n"
" map id=rom address=00-1f:8000-ffff base=0x000000 mask=0x8000\n"
" map id=rom address=20-3f:8000-ffff base=0x100000 mask=0x8000\n"
" map id=rom address=80-9f:8000-ffff base=0x200000 mask=0x8000\n"
" map id=rom address=a0-bf:8000-ffff base=0x100000 mask=0x8000\n"
" map id=ram address=70-7f,f0-ff:0000-7fff\n"
" bsxslot\n"
" map id=rom address=c0-ef:0000-ffff\n"
);
}
else if(mapper == BSCHiROM) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
" ram name=save.ram size=0x", hex(ram_size), "\n"
" map id=rom address=00-1f,80-9f:8000-ffff\n"
" map id=rom address=40-5f,c0-df:0000-ffff\n"
" map id=ram address=20-3f,a0-bf:6000-7fff\n"
" bsxslot\n"
" map id=rom address=20-3f,a0-bf:8000-ffff\n"
" map id=rom address=60-7f,e0-ff:0000-ffff\n"
);
}
else if(mapper == BSXROM) {
markup.append(
" bsx\n"
" rom name=program.rom size=0x", hex(rom_size), "\n"
" ram id=save name=save.ram size=0x", hex(ram_size), "\n"
" ram id=download name=bsx.ram size=0x40000\n"
" map id=io address=00-3f,80-bf:5000-5fff\n"
" map id=rom address=00-3f,80-bf:8000-ffff\n"
" map id=rom address=40-7f,c0-ff:0000-ffff\n"
" map id=ram address=20-3f:6000-7fff\n"
);
}
else if(mapper == STROM) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
" map id=rom address='00-1f,80-9f:8000-ffff mask=0x8000\n"
" sufamiturbo\n"
" slot id=A\n"
" map id=rom address=20-3f,a0-bf:8000-ffff mask=0x8000\n"
" map id=ram address=60-63,e0-e3:8000-ffff\n"
" slot id=B\n"
" map id=rom address=40-5f,c0-df:8000-ffff mask=0x8000\n"
" map id=ram address=70-73,f0-f3:8000-ffff\n"
);
}
if(has_sharprtc) {
markup.append(
" sharprtc\n"
" ram name=rtc.ram size=0x10\n"
" map id=io address=00-3f,80-bf:2800-2801\n"
);
}
if(has_epsonrtc) {
markup.append(
" epsonrtc\n"
" ram name=rtc.ram size=0x10\n"
" map id=io address=00-3f,80-bf:4840-4842\n"
);
}
if(has_obc1) {
markup.append(
" obc1\n"
" ram name=save.ram size=0x2000\n"
" map id=io address=00-3f,80-bf:6000-7fff\n"
);
}
if(has_dsp1) {
markup.append(
" necdsp model=uPD7725 frequency=8000000\n"
" rom id=program name=dsp1b.program.rom size=0x1800\n"
" rom id=data name=dsp1b.data.rom size=0x800\n"
" ram id=data size=0x200\n"
);
if(dsp1_mapper == DSP1LoROM1MB) markup.append(
" map id=io address=20-3f,a0-bf:8000-ffff select=0x4000\n"
);
if(dsp1_mapper == DSP1LoROM2MB) markup.append(
" map id=io address=60-6f,e0-ef:0000-7fff select=0x4000\n"
);
if(dsp1_mapper == DSP1HiROM) markup.append(
" map id=io address=00-1f,80-9f:6000-7fff select=0x1000\n"
);
if((size & 0x7fff) == 0x2000) {
firmware_appended = true;
rom_size -= 0x2000;
}
}
if(has_dsp2) {
markup.append(
" necdsp model=uPD7725 frequency=8000000\n"
" rom id=program name=dsp2.program.rom size=0x1800\n"
" rom id=data name=dsp2.data.rom size=0x800\n"
" ram id=data size=0x200\n"
" map id=io address=20-3f,a0-bf:8000-ffff select=0x4000\n"
);
if((size & 0x7fff) == 0x2000) {
firmware_appended = true;
rom_size -= 0x2000;
}
}
if(has_dsp3) {
markup.append(
" necdsp model=uPD7725 frequency=8000000\n"
" rom id=program name=dsp3.program.rom size=0x1800\n"
" rom id=data name=dsp3.data.rom size=0x800\n"
" ram id=data size=0x200\n"
" map id=io address=20-3f,a0-bf:8000-ffff select=0x4000\n"
);
if((size & 0x7fff) == 0x2000) {
firmware_appended = true;
rom_size -= 0x2000;
}
}
if(has_dsp4) {
markup.append(
" necdsp model=uPD7725 frequency=8000000\n"
" rom id=program name=dsp4.program.rom size=0x1800\n"
" rom id=data name=dsp4.data.rom size=0x800\n"
" ram id=data size=0x200\n"
" map id=io address=30-3f,b0-bf:8000-ffff select=0x4000\n"
);
if((size & 0x7fff) == 0x2000) {
firmware_appended = true;
rom_size -= 0x2000;
}
}
if(has_st010) {
markup.append(
" necdsp model=uPD96050 frequency=11000000\n"
" rom id=program name=st010.program.rom size=0xc000\n"
" rom id=data name=st010.data.rom size=0x1000\n"
" ram id=data name=save.ram size=0x1000\n"
" map id=io address=60-67,e0-e7:0000-3fff select=0x0001\n"
" map id=ram address=68-6f,e8-ef:0000-7fff\n"
);
if((size & 0xffff) == 0xd000) {
firmware_appended = true;
rom_size -= 0xd000;
}
}
if(has_st011) {
markup.append(
" necdsp model=uPD96050 frequency=15000000\n"
" rom id=program name=st011.program.rom size=0xc000\n"
" rom id=data name=st011.data.rom size=0x1000\n"
" ram id=data name=save.ram size=0x1000\n"
" map id=io address=60-67,e0-e7:0000-3fff select=0x0001\n"
" map id=ram address=68-6f,e8-ef:0000-7fff\n"
);
if((size & 0xffff) == 0xd000) {
firmware_appended = true;
rom_size -= 0xd000;
}
}
if(has_st018) {
markup.append(
" armdsp frequency=21477272\n"
" rom id=program name=st018.program.rom size=0x20000\n"
" rom id=data name=st018.data.rom size=0x8000\n"
" ram name=save.ram size=0x4000\n"
" map id=io address=00-3f,80-bf:3800-38ff\n"
);
if((size & 0x3ffff) == 0x28000) {
firmware_appended = true;
rom_size -= 0x28000;
}
}
}
void SuperFamicomCartridge::read_header(const uint8_t *data, unsigned size) {
type = TypeUnknown;
mapper = LoROM;
dsp1_mapper = DSP1Unmapped;
region = NTSC;
rom_size = size;
ram_size = 0;
has_bsx_slot = false;
has_superfx = false;
has_sa1 = false;
has_sharprtc = false;
has_epsonrtc = false;
has_sdd1 = false;
has_spc7110 = false;
has_cx4 = false;
has_dsp1 = false;
has_dsp2 = false;
has_dsp3 = false;
has_dsp4 = false;
has_obc1 = false;
has_st010 = false;
has_st011 = false;
has_st018 = false;
//=====================
//detect Game Boy carts
//=====================
if(size >= 0x0140) {
if(data[0x0104] == 0xce && data[0x0105] == 0xed && data[0x0106] == 0x66 && data[0x0107] == 0x66
&& data[0x0108] == 0xcc && data[0x0109] == 0x0d && data[0x010a] == 0x00 && data[0x010b] == 0x0b) {
type = TypeGameBoy;
return;
}
}
if(size < 32768) {
type = TypeUnknown;
return;
}
const unsigned index = find_header(data, size);
const uint8_t mapperid = data[index + Mapper];
const uint8_t rom_type = data[index + RomType];
const uint8_t rom_size = data[index + RomSize];
const uint8_t company = data[index + Company];
const uint8_t regionid = data[index + CartRegion] & 0x7f;
ram_size = 1024 << (data[index + RamSize] & 7);
if(ram_size == 1024) ram_size = 0; //no RAM present
if(rom_size == 0 && ram_size) ram_size = 0; //fix for Bazooka Blitzkrieg's malformed header (swapped ROM and RAM sizes)
//0, 1, 13 = NTSC; 2 - 12 = PAL
region = (regionid <= 1 || regionid >= 13) ? NTSC : PAL;
//=======================
//detect BS-X flash carts
//=======================
if(data[index + 0x13] == 0x00 || data[index + 0x13] == 0xff) {
if(data[index + 0x14] == 0x00) {
const uint8_t n15 = data[index + 0x15];
if(n15 == 0x00 || n15 == 0x80 || n15 == 0x84 || n15 == 0x9c || n15 == 0xbc || n15 == 0xfc) {
if(data[index + 0x1a] == 0x33 || data[index + 0x1a] == 0xff) {
type = TypeBsx;
mapper = BSXROM;
region = NTSC; //BS-X only released in Japan
return;
}
}
}
}
//=========================
//detect Sufami Turbo carts
//=========================
if(!memcmp(data, "BANDAI SFC-ADX", 14)) {
if(!memcmp(data + 16, "SFC-ADX BACKUP", 14)) {
type = TypeSufamiTurboBios;
} else {
type = TypeSufamiTurbo;
}
mapper = STROM;
region = NTSC; //Sufami Turbo only released in Japan
return; //RAM size handled outside this routine
}
//==========================
//detect Super Game Boy BIOS
//==========================
if(!memcmp(data + index, "Super GAMEBOY2", 14)) {
type = TypeSuperGameBoy2Bios;
return;
}
if(!memcmp(data + index, "Super GAMEBOY", 13)) {
type = TypeSuperGameBoy1Bios;
return;
}
//=====================
//detect standard carts
//=====================
//detect presence of BS-X flash cartridge connector (reads extended header information)
if(data[index - 14] == 'Z') {
if(data[index - 11] == 'J') {
uint8_t n13 = data[index - 13];
if((n13 >= 'A' && n13 <= 'Z') || (n13 >= '0' && n13 <= '9')) {
if(company == 0x33 || (data[index - 10] == 0x00 && data[index - 4] == 0x00)) {
has_bsx_slot = true;
}
}
}
}
if(has_bsx_slot) {
if(!memcmp(data + index, "Satellaview BS-X ", 21)) {
//BS-X base cart
type = TypeBsxBios;
mapper = BSXROM;
region = NTSC; //BS-X only released in Japan
return; //RAM size handled internally by load_cart_bsx() -> BSXCart class
} else {
type = TypeBsxSlotted;
mapper = (index == 0x7fc0 ? BSCLoROM : BSCHiROM);
region = NTSC; //BS-X slotted cartridges only released in Japan
}
} else {
//standard cart
type = TypeNormal;
if(index == 0x7fc0 && size >= 0x401000) {
mapper = ExLoROM;
} else if(index == 0x7fc0 && mapperid == 0x32) {
mapper = ExLoROM;
} else if(index == 0x7fc0) {
mapper = LoROM;
} else if(index == 0xffc0) {
mapper = HiROM;
} else { //index == 0x40ffc0
mapper = ExHiROM;
}
}
if(mapperid == 0x20 && (rom_type == 0x13 || rom_type == 0x14 || rom_type == 0x15 || rom_type == 0x1a)) {
has_superfx = true;
mapper = SuperFXROM;
ram_size = 1024 << (data[index - 3] & 7);
if(ram_size == 1024) ram_size = 0;
}
if(mapperid == 0x23 && (rom_type == 0x32 || rom_type == 0x34 || rom_type == 0x35)) {
has_sa1 = true;
mapper = SA1ROM;
}
if(mapperid == 0x35 && rom_type == 0x55) {
has_sharprtc = true;
}
if(mapperid == 0x32 && (rom_type == 0x43 || rom_type == 0x45)) {
has_sdd1 = true;
}
if(mapperid == 0x3a && (rom_type == 0xf5 || rom_type == 0xf9)) {
has_spc7110 = true;
has_epsonrtc = (rom_type == 0xf9);
mapper = SPC7110ROM;
}
if(mapperid == 0x20 && rom_type == 0xf3) {
has_cx4 = true;
}
if((mapperid == 0x20 || mapperid == 0x21) && rom_type == 0x03) {
has_dsp1 = true;
}
if(mapperid == 0x30 && rom_type == 0x05 && company != 0xb2) {
has_dsp1 = true;
}
if(mapperid == 0x31 && (rom_type == 0x03 || rom_type == 0x05)) {
has_dsp1 = true;
}
if(has_dsp1 == true) {
if((mapperid & 0x2f) == 0x20 && size <= 0x100000) {
dsp1_mapper = DSP1LoROM1MB;
} else if((mapperid & 0x2f) == 0x20) {
dsp1_mapper = DSP1LoROM2MB;
} else if((mapperid & 0x2f) == 0x21) {
dsp1_mapper = DSP1HiROM;
}
}
if(mapperid == 0x20 && rom_type == 0x05) {
has_dsp2 = true;
}
if(mapperid == 0x30 && rom_type == 0x05 && company == 0xb2) {
has_dsp3 = true;
}
if(mapperid == 0x30 && rom_type == 0x03) {
has_dsp4 = true;
}
if(mapperid == 0x30 && rom_type == 0x25) {
has_obc1 = true;
}
if(mapperid == 0x30 && rom_type == 0xf6 && rom_size >= 10) {
has_st010 = true;
}
if(mapperid == 0x30 && rom_type == 0xf6 && rom_size < 10) {
has_st011 = true;
}
if(mapperid == 0x30 && rom_type == 0xf5) {
has_st018 = true;
}
}
unsigned SuperFamicomCartridge::find_header(const uint8_t *data, unsigned size) {
unsigned score_lo = score_header(data, size, 0x007fc0);
unsigned score_hi = score_header(data, size, 0x00ffc0);
unsigned score_ex = score_header(data, size, 0x40ffc0);
if(score_ex) score_ex += 4; //favor ExHiROM on images > 32mbits
if(score_lo >= score_hi && score_lo >= score_ex) {
return 0x007fc0;
} else if(score_hi >= score_ex) {
return 0x00ffc0;
} else {
return 0x40ffc0;
}
}
unsigned SuperFamicomCartridge::score_header(const uint8_t *data, unsigned size, unsigned addr) {
if(size < addr + 64) return 0; //image too small to contain header at this location?
int score = 0;
uint16_t resetvector = data[addr + ResetVector] | (data[addr + ResetVector + 1] << 8);
uint16_t checksum = data[addr + Checksum ] | (data[addr + Checksum + 1] << 8);
uint16_t complement = data[addr + Complement ] | (data[addr + Complement + 1] << 8);
uint8_t resetop = data[(addr & ~0x7fff) | (resetvector & 0x7fff)]; //first opcode executed upon reset
uint8_t mapper = data[addr + Mapper] & ~0x10; //mask off irrelevent FastROM-capable bit
//$00:[000-7fff] contains uninitialized RAM and MMIO.
//reset vector must point to ROM at $00:[8000-ffff] to be considered valid.
if(resetvector < 0x8000) return 0;
//some images duplicate the header in multiple locations, and others have completely
//invalid header information that cannot be relied upon.
//below code will analyze the first opcode executed at the specified reset vector to
//determine the probability that this is the correct header.
//most likely opcodes
if(resetop == 0x78 //sei
|| resetop == 0x18 //clc (clc; xce)
|| resetop == 0x38 //sec (sec; xce)
|| resetop == 0x9c //stz $nnnn (stz $4200)
|| resetop == 0x4c //jmp $nnnn
|| resetop == 0x5c //jml $nnnnnn
) score += 8;
//plausible opcodes
if(resetop == 0xc2 //rep #$nn
|| resetop == 0xe2 //sep #$nn
|| resetop == 0xad //lda $nnnn
|| resetop == 0xae //ldx $nnnn
|| resetop == 0xac //ldy $nnnn
|| resetop == 0xaf //lda $nnnnnn
|| resetop == 0xa9 //lda #$nn
|| resetop == 0xa2 //ldx #$nn
|| resetop == 0xa0 //ldy #$nn
|| resetop == 0x20 //jsr $nnnn
|| resetop == 0x22 //jsl $nnnnnn
) score += 4;
//implausible opcodes
if(resetop == 0x40 //rti
|| resetop == 0x60 //rts
|| resetop == 0x6b //rtl
|| resetop == 0xcd //cmp $nnnn
|| resetop == 0xec //cpx $nnnn
|| resetop == 0xcc //cpy $nnnn
) score -= 4;
//least likely opcodes
if(resetop == 0x00 //brk #$nn
|| resetop == 0x02 //cop #$nn
|| resetop == 0xdb //stp
|| resetop == 0x42 //wdm
|| resetop == 0xff //sbc $nnnnnn,x
) score -= 8;
//at times, both the header and reset vector's first opcode will match ...
//fallback and rely on info validity in these cases to determine more likely header.
//a valid checksum is the biggest indicator of a valid header.
if((checksum + complement) == 0xffff && (checksum != 0) && (complement != 0)) score += 4;
if(addr == 0x007fc0 && mapper == 0x20) score += 2; //0x20 is usually LoROM
if(addr == 0x00ffc0 && mapper == 0x21) score += 2; //0x21 is usually HiROM
if(addr == 0x007fc0 && mapper == 0x22) score += 2; //0x22 is usually ExLoROM
if(addr == 0x40ffc0 && mapper == 0x25) score += 2; //0x25 is usually ExHiROM
if(data[addr + Company] == 0x33) score += 2; //0x33 indicates extended header
if(data[addr + RomType] < 0x08) score++;
if(data[addr + RomSize] < 0x10) score++;
if(data[addr + RamSize] < 0x08) score++;
if(data[addr + CartRegion] < 14) score++;
if(score < 0) score = 0;
return score;
}
}
#endif

View File

@@ -1,15 +0,0 @@
void Ananke::applyBeatPatch(vector<uint8_t> &buffer) {
string name = {information.path, nall::basename(information.name), ".bps"};
if(!file::exists(name)) return;
bpspatch patch;
if(patch.modify(name) == false) return;
patch.source(buffer.data(), buffer.size());
vector<uint8_t> output;
output.resize(patch.size());
patch.target(output.data(), output.size());
if(patch.apply() == bpspatch::result::success) {
buffer = output;
information.manifest = patch.metadata();
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 844 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,6 +0,0 @@
resource name=resource
binary id=home name=home.png
binary id=up name=up.png
binary id=folder name=folder.png
binary id=file name=file.png
binary id=archive name=archive.png

View File

@@ -1,156 +0,0 @@
namespace resource {
const uint8_t home[606] = {
137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,16,0,0,0,16,8,6,0,0,0,31,243,255,
97,0,0,0,6,98,75,71,68,0,0,0,0,0,0,249,67,187,127,0,0,0,9,112,72,89,115,0,0,13,215,0,
0,13,215,1,66,40,155,120,0,0,0,7,116,73,77,69,7,213,10,14,20,37,19,83,42,210,59,0,0,1,235,73,
68,65,84,56,203,149,147,191,107,83,81,20,128,191,123,251,222,75,211,64,242,36,160,85,135,100,81,123,19,104,85,172,
17,92,28,140,66,19,167,135,212,74,39,145,162,163,24,92,58,180,110,193,169,254,24,234,212,37,139,245,199,96,19,240,
15,240,63,16,121,91,92,196,90,219,240,98,81,137,33,121,215,33,246,217,151,80,33,103,186,92,206,247,221,115,14,231,
194,1,225,41,85,172,128,174,128,246,148,122,200,48,177,7,123,74,105,79,169,61,73,113,104,120,177,56,163,203,55,111,
252,87,34,250,225,154,235,110,20,148,98,253,220,89,182,39,78,113,228,240,56,39,188,38,83,107,107,212,92,151,130,82,
215,14,185,110,117,64,176,31,126,83,156,97,247,248,49,148,202,50,22,141,210,106,181,24,219,252,74,182,92,30,144,136,
126,184,122,251,22,157,100,146,116,42,77,42,149,2,64,107,104,183,219,236,214,235,156,44,149,66,18,177,31,126,247,160,
68,199,48,201,229,46,96,219,118,208,154,214,26,0,223,247,249,185,181,69,114,110,46,144,136,10,232,130,82,188,127,84,
198,107,54,57,63,157,35,30,143,35,132,8,9,124,223,15,206,134,16,140,230,243,212,92,23,3,184,3,172,78,157,62,
195,253,210,61,54,170,111,3,240,201,202,51,86,30,63,69,139,17,116,167,133,48,70,209,221,223,44,47,45,209,238,165,
204,202,121,120,190,48,153,33,17,79,0,160,212,4,153,140,34,155,205,244,94,20,146,145,244,149,222,196,83,121,144,22,
90,107,22,38,51,204,195,186,236,239,211,178,44,76,211,196,48,140,224,254,75,227,7,0,31,234,59,116,187,126,144,11,
48,32,144,82,34,165,196,146,159,1,232,96,241,241,83,3,128,111,222,47,58,34,18,90,36,99,96,179,254,14,207,231,
40,0,151,46,78,115,53,26,67,234,113,46,75,147,237,77,66,21,24,253,21,252,155,126,239,165,239,222,14,162,217,56,
112,245,3,65,36,18,193,182,109,18,137,4,2,137,16,16,139,197,112,28,103,0,178,44,43,252,23,174,207,58,175,0,
135,225,98,245,229,139,215,119,255,0,86,248,213,163,133,187,128,26,0,0,0,0,73,69,78,68,174,66,96,130,
};
const uint8_t up[652] = {
137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,16,0,0,0,16,8,6,0,0,0,31,243,255,
97,0,0,0,4,115,66,73,84,8,8,8,8,124,8,100,136,0,0,0,25,116,69,88,116,83,111,102,116,119,97,114,
101,0,119,119,119,46,105,110,107,115,99,97,112,101,46,111,114,103,155,238,60,26,0,0,2,30,73,68,65,84,56,141,
149,147,79,104,19,65,20,198,191,55,187,51,217,141,133,122,104,76,255,209,130,4,237,193,64,42,168,208,213,138,104,68,
98,69,45,20,114,107,74,201,73,144,98,22,193,171,199,82,145,160,23,15,69,79,30,165,66,241,226,77,42,94,68,80,
208,67,41,180,42,149,148,52,137,166,154,38,217,217,25,15,81,172,33,169,246,29,223,251,230,199,124,223,155,33,173,53,
218,213,201,155,124,30,0,150,102,189,233,118,26,214,110,224,184,60,221,223,21,73,246,117,29,76,58,46,79,239,9,224,
184,60,26,16,251,178,87,206,76,6,199,70,147,193,128,8,102,29,151,71,255,11,224,184,188,3,132,197,137,115,211,214,
150,151,199,150,183,137,248,200,37,11,132,69,199,229,29,255,4,16,225,209,232,112,162,167,55,52,72,159,75,31,240,177,
244,30,157,157,157,20,27,58,209,13,194,195,93,1,142,203,211,253,7,34,137,179,71,199,249,74,254,13,12,198,97,50,
142,87,107,11,136,13,29,23,161,253,61,137,230,60,216,142,195,81,193,237,108,42,225,218,235,229,101,104,242,32,132,137,
58,125,199,54,21,241,46,255,28,23,78,95,13,154,166,248,43,15,182,211,247,212,88,198,82,228,161,34,75,16,1,1,
30,224,40,203,117,8,219,64,65,174,162,168,86,113,254,212,69,139,216,159,60,216,111,223,241,99,227,221,135,250,98,148,
175,172,193,228,6,56,55,80,81,5,84,141,175,16,54,131,176,25,150,191,189,64,184,55,68,71,14,15,135,137,53,242,
160,145,140,153,30,232,142,100,111,76,220,177,5,15,64,65,194,135,135,215,185,5,188,45,60,131,100,21,16,35,36,6,
50,208,90,67,41,160,94,175,225,241,211,249,74,177,180,57,99,18,67,234,83,110,197,158,185,119,185,225,137,163,122,247,
250,19,171,44,115,240,141,109,112,193,192,76,130,193,9,115,15,110,87,101,77,91,191,236,7,137,33,101,46,205,122,78,
211,38,180,214,10,165,250,23,48,131,96,112,6,30,104,64,100,77,91,47,231,60,218,245,29,0,128,210,62,126,212,139,
141,43,251,26,178,174,160,85,235,63,99,182,106,250,74,162,90,219,134,50,53,160,21,124,73,240,189,61,0,164,47,17,
31,188,6,98,0,49,2,17,246,0,32,108,220,186,63,25,110,37,38,194,70,115,239,39,48,247,197,219,182,208,154,34,
0,0,0,0,73,69,78,68,174,66,96,130,
};
const uint8_t folder[1176] = {
137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,32,0,0,0,32,8,6,0,0,0,115,122,122,
244,0,0,0,4,115,66,73,84,8,8,8,8,124,8,100,136,0,0,0,9,112,72,89,115,0,0,13,215,0,0,13,
215,1,66,40,155,120,0,0,0,25,116,69,88,116,83,111,102,116,119,97,114,101,0,119,119,119,46,105,110,107,115,99,
97,112,101,46,111,114,103,155,238,60,26,0,0,4,21,73,68,65,84,88,133,237,151,61,111,28,69,24,199,127,51,187,
119,123,175,246,57,145,48,9,5,86,148,194,31,0,9,5,33,83,110,149,130,26,137,2,90,58,62,64,36,62,64,74,
132,68,133,104,161,67,72,167,64,23,201,138,2,138,210,69,194,198,196,40,22,198,247,186,119,222,151,153,157,25,138,123,
201,58,119,142,19,114,82,40,248,75,143,118,118,118,247,121,126,243,60,51,179,187,34,12,67,94,167,228,107,141,254,95,
0,240,1,110,220,184,225,43,165,126,54,198,188,119,209,3,66,136,220,90,251,249,195,135,15,191,92,25,64,154,166,91,
149,74,229,157,155,55,111,250,73,146,224,156,3,192,90,11,48,63,159,182,253,59,119,238,220,222,222,222,254,254,209,163,
71,199,43,1,80,74,101,213,106,213,29,29,29,209,233,116,112,206,45,53,0,33,4,155,155,155,249,193,193,193,71,192,
237,149,0,104,173,211,44,203,196,165,75,27,24,99,176,214,158,9,92,204,132,16,130,106,181,90,63,60,60,252,98,123,
123,251,214,172,127,153,156,115,218,90,251,241,222,222,222,143,207,5,0,178,44,203,188,78,167,75,167,211,153,3,204,142,
179,32,197,64,91,91,91,245,70,163,65,179,217,68,8,129,16,2,0,41,229,188,29,69,17,247,238,221,187,5,60,31,
224,242,229,203,105,191,223,247,54,54,90,104,173,23,0,150,149,98,166,56,142,1,230,16,69,139,227,24,99,76,237,188,
224,115,128,221,221,93,117,237,218,53,217,235,245,230,25,0,150,130,204,178,81,212,108,196,197,224,82,202,25,64,229,66,
0,0,99,140,109,54,215,100,154,102,220,63,180,220,221,63,27,228,188,58,63,171,25,204,228,153,22,206,93,185,206,219,
31,232,226,61,158,39,190,93,255,253,155,79,206,0,56,231,116,183,219,245,187,221,46,143,59,13,62,253,240,93,174,191,
181,81,112,54,61,62,47,250,18,200,105,207,60,206,147,206,152,175,190,187,255,254,236,188,152,1,221,104,212,171,90,107,
212,31,146,102,181,204,254,223,99,162,56,159,140,108,233,104,207,35,17,11,77,1,52,42,62,42,211,8,65,103,118,121,
190,21,27,99,116,191,63,160,219,237,18,103,150,122,181,132,53,147,27,228,212,193,179,134,59,207,220,83,179,19,179,214,
145,100,134,52,203,193,137,249,6,86,44,65,86,169,4,180,90,45,50,35,168,5,37,172,83,120,114,53,175,139,113,154,
83,111,250,140,134,57,214,186,39,11,0,121,158,171,40,26,209,239,15,208,230,13,170,129,143,115,14,79,158,155,231,23,
147,131,81,170,209,185,37,240,37,113,170,173,49,249,209,2,128,49,38,43,151,75,84,155,27,172,213,202,56,64,10,177,
188,248,47,40,227,28,163,84,147,91,135,231,9,130,178,199,105,146,41,33,196,201,2,128,181,54,27,141,198,28,15,20,
235,245,75,232,220,190,210,232,51,109,137,51,3,78,224,79,253,148,61,201,232,84,229,130,167,147,176,152,129,212,247,125,
188,32,160,73,153,76,217,11,131,76,230,152,195,186,201,62,97,236,196,84,110,151,173,72,156,131,193,40,181,185,115,139,
0,214,218,100,60,30,211,25,248,84,90,101,162,68,147,233,233,75,8,200,141,69,27,135,206,45,185,153,110,203,47,153,
149,68,25,134,227,196,19,210,91,90,130,84,8,129,12,26,148,74,62,163,68,19,37,154,84,25,148,182,47,29,108,153,
226,44,231,52,209,62,231,148,32,142,227,83,122,81,133,74,13,30,159,196,12,99,189,212,209,191,145,0,134,177,66,25,
83,250,245,234,94,47,124,252,12,128,181,54,6,129,245,107,100,185,37,138,53,74,95,60,15,94,84,82,10,78,134,9,
82,200,56,220,221,157,59,62,51,7,146,36,33,74,12,87,175,4,84,3,143,160,188,186,111,86,41,4,105,170,144,82,
12,139,253,197,18,36,0,154,18,235,85,143,122,105,21,85,127,42,231,28,253,36,65,88,59,88,0,104,183,219,98,103,
103,199,68,177,98,112,234,248,243,100,136,236,190,226,14,184,68,199,199,67,155,36,195,7,187,237,118,45,12,195,120,14,
0,120,81,20,253,244,203,161,248,76,173,227,255,182,255,202,31,187,103,228,192,2,88,157,30,28,63,248,225,235,226,53,
49,251,53,107,183,219,205,96,109,237,205,90,208,218,116,56,41,133,92,201,18,200,141,49,42,75,107,185,16,121,62,58,
233,1,127,133,97,216,91,0,152,66,72,32,96,146,153,124,21,0,83,5,64,10,168,48,12,207,44,45,241,255,207,233,
235,6,248,7,188,50,165,151,203,8,55,43,0,0,0,0,73,69,78,68,174,66,96,130,
};
const uint8_t file[844] = {
137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,32,0,0,0,32,8,6,0,0,0,115,122,122,
244,0,0,0,4,115,66,73,84,8,8,8,8,124,8,100,136,0,0,3,3,73,68,65,84,88,133,229,151,79,110,212,
48,24,197,127,182,227,56,81,39,116,36,132,0,169,167,226,2,101,209,93,239,192,150,37,171,46,43,245,4,92,164,183,
40,82,89,21,36,134,84,76,18,59,44,90,91,142,243,103,50,21,59,62,201,138,227,120,236,247,189,247,252,37,3,255,
123,136,116,224,234,234,234,67,150,101,55,64,165,148,66,74,137,115,14,107,109,104,93,215,133,107,220,95,26,3,118,93,
215,125,188,185,185,249,26,239,151,141,16,9,113,125,113,113,81,61,247,195,120,223,247,131,121,241,253,202,126,117,121,121,
121,13,44,3,176,214,110,1,110,111,111,17,66,32,165,12,87,41,37,74,41,148,82,8,33,80,74,177,221,110,209,90,
15,158,167,125,33,4,251,253,158,166,105,182,233,126,50,29,232,251,30,33,68,104,64,88,208,55,207,78,215,117,24,99,
70,191,247,109,77,140,24,112,206,133,13,98,6,210,38,165,164,40,138,48,127,42,60,16,159,200,20,168,17,3,206,185,
17,11,177,4,49,40,231,28,77,211,28,204,114,137,141,17,0,107,109,232,207,177,224,159,41,165,208,90,31,100,97,9,
200,36,3,233,230,233,189,181,150,60,207,41,203,146,186,174,233,186,46,204,91,202,124,21,0,207,192,156,9,1,140,49,
20,69,65,93,215,100,89,70,158,231,3,173,231,50,127,17,128,88,123,127,239,105,55,198,96,140,161,109,219,85,174,95,
13,96,202,132,41,35,62,218,182,5,240,213,110,113,211,163,25,72,179,23,66,208,52,13,90,235,80,92,252,243,199,199,
199,229,244,97,210,172,7,37,136,93,47,165,164,170,42,178,44,67,8,193,201,201,73,232,151,101,57,152,63,197,194,139,
37,136,77,168,181,198,90,27,42,97,215,117,97,222,210,113,92,205,128,215,50,205,222,103,16,75,226,156,11,12,120,64,
62,203,41,38,142,146,32,213,190,239,123,172,181,225,69,228,105,143,193,214,117,141,115,110,86,134,163,0,196,11,11,33,
200,178,140,170,170,6,115,235,186,14,114,120,79,40,165,70,155,28,205,64,74,181,7,32,165,28,248,195,24,67,158,231,
1,172,82,106,117,89,158,5,144,158,231,212,7,113,40,165,6,12,120,169,210,170,184,20,171,142,161,47,54,177,254,254,
236,123,205,227,58,177,219,237,86,3,152,250,34,26,80,37,132,96,179,217,80,150,229,200,96,155,205,6,173,245,96,190,
49,38,48,177,6,196,162,9,189,15,178,44,27,140,123,96,109,219,142,36,136,95,90,107,98,214,3,177,9,211,240,99,
190,8,77,197,161,162,52,11,32,149,96,238,37,3,79,18,164,0,60,232,135,135,135,17,107,171,1,132,135,82,6,157,
227,240,0,119,187,221,8,160,63,5,167,167,167,65,138,163,62,201,226,5,189,235,167,88,232,251,30,173,245,36,56,95,
71,252,247,229,82,164,167,160,146,82,254,234,251,254,213,217,217,89,24,156,211,185,40,138,197,197,211,242,155,231,249,14,
120,7,252,4,246,41,128,10,120,115,127,127,255,249,252,252,252,19,80,46,174,190,50,242,60,167,105,26,132,16,127,238,
238,238,190,0,239,1,5,124,7,92,156,90,14,188,5,94,3,91,192,60,79,252,23,241,135,167,100,107,224,7,240,13,
248,13,19,127,78,159,55,46,158,1,173,43,103,135,195,1,45,79,180,239,129,96,140,191,182,58,238,12,241,249,173,246,
0,0,0,0,73,69,78,68,174,66,96,130,
};
const uint8_t archive[1067] = {
137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,32,0,0,0,32,8,6,0,0,0,115,122,122,
244,0,0,0,4,115,66,73,84,8,8,8,8,124,8,100,136,0,0,3,226,73,68,65,84,88,133,237,150,95,136,212,
85,20,199,63,231,222,251,251,253,102,102,103,255,142,166,27,150,150,88,144,102,127,31,218,40,49,200,36,136,68,200,30,
20,218,212,13,89,84,8,138,30,138,222,10,122,232,205,212,44,89,55,145,192,146,32,168,232,37,42,208,212,84,212,165,
32,162,168,101,21,203,221,213,253,51,237,56,51,191,223,189,61,252,134,217,217,117,86,155,26,240,161,253,194,225,222,115,
239,253,157,251,189,231,158,223,57,23,102,49,139,255,59,228,90,147,189,47,164,151,26,63,120,51,12,243,107,156,184,60,
14,59,229,75,135,2,130,234,70,5,45,114,196,89,121,233,185,247,198,142,215,68,96,223,150,166,37,126,144,58,168,148,
190,115,249,163,107,82,205,109,237,68,81,136,32,24,63,0,229,163,76,2,209,1,202,76,138,232,4,202,11,80,58,32,
180,206,253,240,117,143,244,159,250,232,47,27,113,209,138,125,165,115,87,246,208,117,9,236,239,106,190,77,18,254,137,101,
43,58,91,231,45,186,67,141,12,156,117,103,15,127,38,78,0,231,112,81,30,163,4,163,193,211,130,209,130,167,193,76,
235,39,146,45,100,30,123,195,93,56,190,215,133,131,167,213,207,231,70,115,69,203,182,141,239,142,247,84,238,167,43,149,
3,93,201,5,36,147,199,239,93,221,221,182,248,129,213,122,252,66,31,167,191,249,88,10,133,43,216,40,196,218,8,37,
160,68,80,10,180,18,148,18,180,2,53,173,47,46,79,225,242,79,114,83,199,139,146,255,227,20,173,137,208,27,30,203,
175,122,250,65,51,240,233,201,98,223,85,30,120,191,171,97,94,50,149,60,185,108,229,166,246,246,37,29,218,141,253,138,
23,52,80,140,38,157,100,157,5,107,65,25,68,123,177,168,88,40,181,162,13,136,46,27,78,54,207,167,144,27,167,175,
119,45,162,52,191,93,24,155,112,214,110,234,220,147,61,88,38,208,179,49,61,55,104,76,29,107,91,176,116,161,56,114,
67,3,125,233,71,158,121,141,230,185,183,146,27,57,55,121,95,162,64,52,136,138,5,13,74,226,13,69,202,115,18,7,
40,0,65,67,134,200,193,151,59,214,227,108,33,151,12,252,68,54,151,15,113,172,239,220,147,61,36,123,55,55,181,25,
99,135,1,252,32,117,62,44,230,191,187,253,190,39,215,221,179,122,27,209,200,47,224,44,206,89,112,46,182,234,92,172,
151,250,255,100,204,207,220,69,118,228,34,95,125,240,242,69,92,120,38,178,246,9,0,177,108,85,198,68,111,3,160,100,
205,149,98,126,69,208,152,121,106,249,170,173,68,163,191,99,139,19,216,98,14,23,94,41,139,13,167,233,197,220,117,199,
242,127,158,165,113,206,34,238,94,185,161,69,180,30,182,74,47,22,56,234,20,59,167,252,5,7,182,207,57,252,248,230,
221,15,167,51,11,175,153,31,254,11,190,221,191,61,55,116,254,199,141,157,187,42,98,0,96,95,119,211,67,98,237,81,
165,117,81,68,121,162,52,206,70,53,152,118,51,206,136,196,182,28,46,111,163,40,0,120,126,79,86,166,16,112,32,31,
118,55,183,0,20,109,116,169,99,237,171,12,28,235,169,110,177,70,220,124,255,58,190,255,124,7,158,210,109,0,158,248,
197,103,119,13,102,1,76,153,37,56,118,143,94,6,232,221,146,38,145,74,147,76,165,235,66,32,72,54,2,176,161,100,
191,18,230,170,213,37,40,109,240,147,13,117,33,160,141,55,227,220,140,4,180,241,240,19,245,241,128,54,126,237,4,140,
246,8,18,245,241,128,249,247,30,184,145,4,60,143,160,78,49,96,252,242,21,248,64,225,90,4,2,160,21,226,138,103,
252,20,184,90,114,65,21,136,66,169,114,209,189,5,24,3,46,1,81,53,2,205,64,198,41,111,112,184,255,204,220,204,
194,142,210,112,69,146,113,149,9,199,149,117,87,117,77,220,14,245,247,97,197,27,2,230,151,246,140,74,36,166,16,208,
37,221,251,226,244,196,91,216,157,175,11,97,75,141,231,173,10,135,25,249,228,196,196,59,64,2,240,168,120,135,76,207,
249,173,64,166,212,6,196,119,86,15,20,0,5,100,137,79,126,30,40,86,35,0,144,4,90,74,155,207,24,164,53,34,
36,190,143,81,96,28,38,31,183,215,171,122,245,170,138,51,87,170,89,220,104,252,13,162,179,143,166,193,167,182,66,0,
0,0,0,73,69,78,68,174,66,96,130,
};
};

View File

@@ -1,7 +0,0 @@
namespace resource {
extern const uint8_t home[606];
extern const uint8_t up[652];
extern const uint8_t folder[1176];
extern const uint8_t file[844];
extern const uint8_t archive[1067];
};

View File

@@ -1,90 +0,0 @@
void Ananke::copySufamiTurboSaves(const string &pathname) {
if(!file::exists({pathname, "save.ram"})) {
if(file::exists({information.path, nall::basename(information.name), ".srm"})) {
file::copy({information.path, nall::basename(information.name), ".srm"}, {pathname, "save.ram"});
}
}
}
string Ananke::createSufamiTurboDatabase(vector<uint8_t> &buffer, Markup::Node &document, const string &manifest) {
string pathname = {
libraryPath, "Sufami Turbo/",
document["release/information/name"].text(),
" (", document["release/information/region"].text(), ")",
" (", document["release/information/revision"].text(), ")",
".st/"
};
directory::create(pathname);
//strip "release" root node from database entry (since a single game manifest isn't part of a database)
string markup = manifest;
markup.replace("\n ", "\n");
markup.replace("information", "\ninformation");
markup.ltrim<1>("release\n");
file::write({pathname, "manifest.bml"}, markup);
file::write({pathname, "program.rom"}, buffer);
copySufamiTurboSaves(pathname);
return pathname;
}
string Ananke::createSufamiTurboHeuristic(vector<uint8_t> &buffer) {
string pathname = {
libraryPath, "Sufami Turbo/",
nall::basename(information.name),
".st/"
};
directory::create(pathname);
file::write({pathname, "manifest.bml"}, {
"unverified\n",
"\n",
"cartridge\n",
" rom name=program.rom size=0x", hex(buffer.size()), "\n",
" ram name=save.ram size=0x2000\n",
"\n",
"information\n",
" title: ", nall::basename(information.name), "\n"
});
file::write({pathname, "program.rom"}, buffer);
copySufamiTurboSaves(pathname);
return pathname;
}
string Ananke::openSufamiTurbo(vector<uint8_t> &buffer) {
string sha256 = nall::sha256(buffer.data(), buffer.size());
string databaseText = string::read({configpath(), "ananke/database/Sufami Turbo.bml"}).strip();
if(databaseText.empty()) databaseText = string{Database::SufamiTurbo}.strip();
lstring databaseItem = databaseText.split("\n\n");
for(auto &item : databaseItem) {
item.append("\n");
auto document = Markup::Document(item);
if(document["release/information/sha256"].text() == sha256) {
return createSufamiTurboDatabase(buffer, document, item);
}
}
return createSufamiTurboHeuristic(buffer);
}
string Ananke::syncSufamiTurbo(const string &pathname) {
auto buffer = file::read({pathname, "program.rom"});
if(buffer.size() == 0) return "";
auto save = file::read({pathname, "save.ram"});
if(save.size() == 0) save = file::read({pathname, "save.rwm"});
directory::remove(pathname);
information.path = pathname;
information.name = notdir(string{pathname}.rtrim<1>("/"));
string outputPath = openSufamiTurbo(buffer);
if(save.size()) file::write({outputPath, "save.ram"}, save);
return outputPath;
}

View File

@@ -1,215 +0,0 @@
void Ananke::copySuperFamicomSaves(const string &pathname) {
if(!file::exists({pathname, "save.ram"})) {
if(file::exists({information.path, nall::basename(information.name), ".srm"})) {
file::copy({information.path, nall::basename(information.name), ".srm"}, {pathname, "save.ram"});
}
}
if(!file::exists({pathname, "rtc.ram"})) {
if(file::exists({information.path, nall::basename(information.name), ".rtc"})) {
file::copy({information.path, nall::basename(information.name), ".rtc"}, {pathname, "rtc.ram"});
}
}
}
string Ananke::createSuperFamicomDatabase(vector<uint8_t> &buffer, Markup::Node &document, const string &manifest) {
string pathname = {
libraryPath, "Super Famicom/",
document["release/information/name"].text(),
" (", document["release/information/region"].text(), ")",
" (", document["release/information/revision"].text(), ")",
".sfc/"
};
directory::create(pathname);
//strip "release" root node from database entry (since a single game manifest isn't part of a database)
string markup = manifest;
markup.replace("\n ", "\n");
markup.replace("information", "\ninformation");
markup.ltrim<1>("release\n");
file::write({pathname, "manifest.bml"}, markup);
unsigned offset = 0;
for(auto &node : document["release/information/configuration"]) {
if(node.name != "rom") continue;
string name = node["name"].text();
unsigned size = node["size"].decimal();
file::write({pathname, name}, buffer.data() + offset, size);
offset += size;
}
copySuperFamicomSaves(pathname);
return pathname;
}
string Ananke::createSuperFamicomHeuristic(vector<uint8_t> &buffer) {
string pathname = {
libraryPath, "Super Famicom/",
nall::basename(information.name),
".sfc/"
};
directory::create(pathname);
if((buffer.size() & 0x7fff) == 512) buffer.remove(0, 512); //strip copier header, if present
SuperFamicomCartridge info(buffer.data(), buffer.size());
string markup = {"unverified\n\n", info.markup};
markup.append("\ninformation\n title: ", nall::basename(information.name), "\n");
if(!information.manifest.empty()) markup = information.manifest; //override with embedded beat manifest, if one exists
information.manifest = markup; //save for use with firmware routine below
file::write({pathname, "manifest.bml"}, markup);
if(!markup.find("spc7110")) {
file::write({pathname, "program.rom"}, buffer.data(), info.rom_size);
} else {
file::write({pathname, "program.rom"}, buffer.data(), 0x100000);
file::write({pathname, "data.rom"}, buffer.data() + 0x100000, info.rom_size - 0x100000);
}
createSuperFamicomHeuristicFirmware(buffer, pathname, info.firmware_appended);
copySuperFamicomSaves(pathname);
return pathname;
}
void Ananke::createSuperFamicomHeuristicFirmware(vector<uint8_t> &buffer, const string &pathname, bool firmware_appended) {
auto copyFirmwareInternal = [&](const string &name, unsigned programSize, unsigned dataSize, unsigned bootSize) {
//firmware appended directly onto .sfc file
string basename = nall::basename(name);
if(programSize) file::write({pathname, basename, ".program.rom"}, buffer.data() + buffer.size() - programSize - dataSize - bootSize, programSize);
if(dataSize) file::write({pathname, basename, ".data.rom"}, buffer.data() + buffer.size() - dataSize - bootSize, dataSize);
if(bootSize) file::write({pathname, basename, ".boot.rom"}, buffer.data() + buffer.size() - bootSize, bootSize);
};
auto copyFirmwareExternal = [&](const string &name, unsigned programSize, unsigned dataSize, unsigned bootSize) {
//firmware stored in external file
auto buffer = file::read({information.path, name}); //try and read from the containing directory
if(buffer.size() == 0) buffer = extractFile(name); //try and read from the containing archive, if one exists
if(buffer.size() == 0) {
if(thread::primary()) MessageWindow().setText({
"Error: ", information.name, "\n\n",
"Required firmware ", name, " not found. Game will not be playable!\n\n",
"You must obtain this file, and place it in the same folder as this game."
}).error();
return;
}
string basename = nall::basename(name);
if(programSize) file::write({pathname, basename, ".program.rom"}, buffer.data(), programSize);
if(dataSize) file::write({pathname, basename, ".data.rom"}, buffer.data() + programSize, dataSize);
if(bootSize) file::write({pathname, basename, ".boot.rom"}, buffer.data() + programSize + dataSize, bootSize);
};
auto copyFirmware = [&](const string &name, unsigned programSize, unsigned dataSize, unsigned bootSize = 0) {
if(firmware_appended == 1) copyFirmwareInternal(name, programSize, dataSize, bootSize);
if(firmware_appended == 0) copyFirmwareExternal(name, programSize, dataSize, bootSize);
};
string markup = information.manifest;
if(markup.find("dsp1.program.rom" )) copyFirmware("dsp1.rom", 0x001800, 0x000800);
if(markup.find("dsp1b.program.rom")) copyFirmware("dsp1b.rom", 0x001800, 0x000800);
if(markup.find("dsp2.program.rom" )) copyFirmware("dsp2.rom", 0x001800, 0x000800);
if(markup.find("dsp3.program.rom" )) copyFirmware("dsp3.rom", 0x001800, 0x000800);
if(markup.find("dsp4.program.rom" )) copyFirmware("dsp4.rom", 0x001800, 0x000800);
if(markup.find("st010.program.rom")) copyFirmware("st010.rom", 0x00c000, 0x001000);
if(markup.find("st011.program.rom")) copyFirmware("st011.rom", 0x00c000, 0x001000);
if(markup.find("st018.program.rom")) copyFirmware("st018.rom", 0x020000, 0x008000);
if(markup.find("cx4.data.rom" )) copyFirmware("cx4.rom", 0x000000, 0x000c00);
if(markup.find("sgb.boot.rom" )) copyFirmware("sgb.rom", 0x000000, 0x000000, 0x000100);
}
string Ananke::openSuperFamicom(vector<uint8_t> &buffer) {
string sha256 = nall::sha256(buffer.data(), buffer.size());
string databaseText = string::read({configpath(), "ananke/database/Super Famicom.bml"}).strip();
if(databaseText.empty()) databaseText = string{Database::SuperFamicom}.strip();
lstring databaseItem = databaseText.split("\n\n");
for(auto &item : databaseItem) {
item.append("\n");
auto document = Markup::Document(item);
if(document["release/information/sha256"].text() == sha256) {
return createSuperFamicomDatabase(buffer, document, item);
}
}
return createSuperFamicomHeuristic(buffer);
}
string Ananke::syncSuperFamicom(const string &pathname) {
if(file::exists({pathname, "msu1.rom"})) return ""; //cannot update MSU1 games
vector<uint8_t> buffer;
auto append = [&](string filename) {
filename = {pathname, filename};
auto data = file::read(filename);
if(data.size() == 0) return; //file does not exist
unsigned position = buffer.size();
buffer.resize(buffer.size() + data.size());
memcpy(buffer.data() + position, data.data(), data.size());
};
append("program.rom");
append("data.rom");
append("dsp1.rom");
append("dsp1.program.rom");
append("dsp1.data.rom");
append("dsp1b.rom");
append("dsp1b.program.rom");
append("dsp1b.data.rom");
append("dsp2.rom");
append("dsp2.program.rom");
append("dsp2.data.rom");
append("dsp3.rom");
append("dsp3.program.rom");
append("dsp3.data.rom");
append("dsp4.rom");
append("dsp4.program.rom");
append("dsp4.data.rom");
append("st010.rom");
append("st010.program.rom");
append("st010.data.rom");
append("st011.rom");
append("st011.program.rom");
append("st011.data.rom");
append("st018.rom");
append("st018.program.rom");
append("st018.data.rom");
append("cx4.rom");
append("cx4.data.rom");
append("sgb.rom");
append("sgb.boot.rom");
if(buffer.size() == 0) return "";
auto save = file::read({pathname, "save.ram"});
if(save.size() == 0) save = file::read({pathname, "save.rwm"});
auto rtc = file::read({pathname, "rtc.ram"});
if(rtc.size() == 0) rtc= file::read({pathname, "rtc.rwm"});
directory::remove(pathname);
information.path = pathname;
information.name = notdir(string{pathname}.rtrim<1>("/"));
string outputPath = openSuperFamicom(buffer);
if(save.size()) file::write({outputPath, "save.ram"}, save);
if(rtc.size()) file::write({outputPath, "rtc.ram"}, save);
return outputPath;
}

View File

@@ -1,151 +0,0 @@
#ifndef EMULATOR_HPP
#define EMULATOR_HPP
namespace Emulator {
static const char Name[] = "higan";
static const char Version[] = "094";
static const char Author[] = "byuu";
static const char License[] = "GPLv3";
static const char Website[] = "http://byuu.org/";
#if defined(PROFILE_ACCURACY)
static const char Profile[] = "Accuracy";
#elif defined(PROFILE_BALANCED)
static const char Profile[] = "Balanced";
#elif defined(PROFILE_PERFORMANCE)
static const char Profile[] = "Performance";
#endif
}
#include <nall/platform.hpp>
#include <nall/algorithm.hpp>
#include <nall/base64.hpp>
#include <nall/directory.hpp>
#include <nall/dl.hpp>
#include <nall/dsp.hpp>
#include <nall/endian.hpp>
#include <nall/file.hpp>
#include <nall/function.hpp>
#include <nall/http.hpp>
#include <nall/image.hpp>
#include <nall/invoke.hpp>
#include <nall/priority-queue.hpp>
#include <nall/property.hpp>
#include <nall/random.hpp>
#include <nall/serializer.hpp>
#include <nall/set.hpp>
#include <nall/sha256.hpp>
#include <nall/stdint.hpp>
#include <nall/string.hpp>
#include <nall/utility.hpp>
#include <nall/varint.hpp>
#include <nall/vector.hpp>
#include <nall/stream/memory.hpp>
#include <nall/stream/vector.hpp>
using namespace nall;
#include "interface.hpp"
//debugging function hook:
//no overhead (and no debugger invocation) if not compiled with -DDEBUGGER
//wraps testing of function to allow invocation without a defined callback
template<typename T> struct hook;
template<typename R, typename... P> struct hook<R (P...)> {
function<R (P...)> callback;
R operator()(P... p) const {
#if defined(DEBUGGER)
if(callback) return callback(std::forward<P>(p)...);
#endif
return R();
}
hook() {}
hook(const hook& hook) { callback = hook.callback; }
hook(void* function) { callback = function; }
hook(R (*function)(P...)) { callback = function; }
template<typename C> hook(R (C::*function)(P...), C* object) { callback = {function, object}; }
template<typename C> hook(R (C::*function)(P...) const, C* object) { callback = {function, object}; }
template<typename L> hook(const L& function) { callback = function; }
hook& operator=(const hook& hook) { callback = hook.callback; return *this; }
};
#if defined(DEBUGGER)
#define privileged public
#else
#define privileged private
#endif
typedef int1_t int1;
typedef int2_t int2;
typedef int3_t int3;
typedef int4_t int4;
typedef int5_t int5;
typedef int6_t int6;
typedef int7_t int7;
typedef int8_t int8;
typedef int9_t int9;
typedef int10_t int10;
typedef int11_t int11;
typedef int12_t int12;
typedef int13_t int13;
typedef int14_t int14;
typedef int15_t int15;
typedef int16_t int16;
typedef int17_t int17;
typedef int18_t int18;
typedef int19_t int19;
typedef int20_t int20;
typedef int21_t int21;
typedef int22_t int22;
typedef int23_t int23;
typedef int24_t int24;
typedef int25_t int25;
typedef int26_t int26;
typedef int27_t int27;
typedef int28_t int28;
typedef int29_t int29;
typedef int30_t int30;
typedef int31_t int31;
typedef int32_t int32;
typedef int64_t int64;
typedef uint1_t uint1;
typedef uint2_t uint2;
typedef uint3_t uint3;
typedef uint4_t uint4;
typedef uint5_t uint5;
typedef uint6_t uint6;
typedef uint7_t uint7;
typedef uint8_t uint8;
typedef uint9_t uint9;
typedef uint10_t uint10;
typedef uint11_t uint11;
typedef uint12_t uint12;
typedef uint13_t uint13;
typedef uint14_t uint14;
typedef uint15_t uint15;
typedef uint16_t uint16;
typedef uint17_t uint17;
typedef uint18_t uint18;
typedef uint19_t uint19;
typedef uint20_t uint20;
typedef uint21_t uint21;
typedef uint22_t uint22;
typedef uint23_t uint23;
typedef uint24_t uint24;
typedef uint25_t uint25;
typedef uint26_t uint26;
typedef uint27_t uint27;
typedef uint28_t uint28;
typedef uint29_t uint29;
typedef uint30_t uint30;
typedef uint31_t uint31;
typedef uint32_t uint32;
typedef uint_t<33> uint33;
typedef uint64_t uint64;
typedef varuint_t<unsigned> varuint;
#endif

View File

@@ -1,122 +0,0 @@
#ifndef EMULATOR_INTERFACE_HPP
#define EMULATOR_INTERFACE_HPP
namespace Emulator {
struct Interface {
struct Information {
string name;
unsigned width;
unsigned height;
bool overscan;
double aspectRatio;
bool resettable;
struct Capability {
bool states;
bool cheats;
} capability;
} information;
struct Media {
unsigned id;
string name;
string type;
bool bootable; //false for cartridge slots (eg Sufami Turbo cartridges)
};
vector<Media> media;
struct Device {
unsigned id;
unsigned portmask;
string name;
struct Input {
unsigned id;
unsigned type; //0 = digital, 1 = analog (relative), 2 = rumble
string name;
unsigned guid;
};
vector<Input> input;
vector<unsigned> order;
};
struct Port {
unsigned id;
string name;
vector<Device> device;
};
vector<Port> port;
struct Bind {
virtual void loadRequest(unsigned, string, string) {}
virtual void loadRequest(unsigned, string) {}
virtual void saveRequest(unsigned, string) {}
virtual uint32_t videoColor(unsigned, uint16_t, uint16_t, uint16_t, uint16_t) { return 0u; }
virtual void videoRefresh(const uint32_t*, const uint32_t*, unsigned, unsigned, unsigned) {}
virtual void audioSample(int16_t, int16_t) {}
virtual int16_t inputPoll(unsigned, unsigned, unsigned) { return 0; }
virtual void inputRumble(unsigned, unsigned, unsigned, bool) {}
virtual unsigned dipSettings(const Markup::Node&) { return 0; }
virtual string path(unsigned) { return ""; }
virtual string server() { return ""; }
virtual void notify(string text) { print(text, "\n"); }
};
Bind* bind = nullptr;
//callback bindings (provided by user interface)
void loadRequest(unsigned id, string name, string type) { return bind->loadRequest(id, name, type); }
void loadRequest(unsigned id, string path) { return bind->loadRequest(id, path); }
void saveRequest(unsigned id, string path) { return bind->saveRequest(id, path); }
uint32_t videoColor(unsigned source, uint16_t alpha, uint16_t red, uint16_t green, uint16_t blue) { return bind->videoColor(source, alpha, red, green, blue); }
void videoRefresh(const uint32_t* palette, const uint32_t* data, unsigned pitch, unsigned width, unsigned height) { return bind->videoRefresh(palette, data, pitch, width, height); }
void audioSample(int16_t lsample, int16_t rsample) { return bind->audioSample(lsample, rsample); }
int16_t inputPoll(unsigned port, unsigned device, unsigned input) { return bind->inputPoll(port, device, input); }
void inputRumble(unsigned port, unsigned device, unsigned input, bool enable) { return bind->inputRumble(port, device, input, enable); }
unsigned dipSettings(const Markup::Node& node) { return bind->dipSettings(node); }
string path(unsigned group) { return bind->path(group); }
string server() { return bind->server(); }
template<typename... Args> void notify(Args&&... args) { return bind->notify({std::forward<Args>(args)...}); }
//information
virtual string title() = 0;
virtual double videoFrequency() = 0;
virtual double audioFrequency() = 0;
//media interface
virtual bool loaded() { return false; }
virtual string sha256() { return ""; }
virtual unsigned group(unsigned id) = 0;
virtual void load(unsigned id) {}
virtual void save() {}
virtual void load(unsigned id, const stream& memory) {}
virtual void save(unsigned id, const stream& memory) {}
virtual void unload() {}
//system interface
virtual void connect(unsigned port, unsigned device) {}
virtual void power() {}
virtual void reset() {}
virtual void run() {}
//time functions
virtual bool rtc() { return false; }
virtual void rtcsync() {}
//state functions
virtual serializer serialize() = 0;
virtual bool unserialize(serializer&) = 0;
//cheat functions
virtual void cheatSet(const lstring& = lstring{}) {}
//utility functions
enum class PaletteMode : unsigned { Literal, Channel, Standard, Emulation };
virtual void paletteUpdate(PaletteMode mode) {}
//debugger functions
virtual bool tracerEnable(bool) { return false; }
virtual void exportMemory() {}
};
}
#endif

View File

@@ -1,65 +0,0 @@
struct APU : Thread {
static void Main();
void main();
void tick();
void set_irq_line();
void set_sample(int16 sample);
void power();
void reset();
uint8 read(uint16 addr);
void write(uint16 addr, uint8 data);
void serialize(serializer&);
APU();
struct Filter {
enum : signed { HiPassStrong = 225574, HiPassWeak = 57593, LoPass = 86322413 };
int64 hipass_strong;
int64 hipass_weak;
int64 lopass;
signed run_hipass_strong(signed sample);
signed run_hipass_weak(signed sample);
signed run_lopass(signed sample);
void serialize(serializer&);
} filter;
#include "envelope.hpp"
#include "sweep.hpp"
#include "pulse.hpp"
#include "triangle.hpp"
#include "noise.hpp"
#include "dmc.hpp"
struct FrameCounter {
enum : unsigned { NtscPeriod = 14915 }; //~(21.477MHz / 6 / 240hz)
bool irq_pending;
uint2 mode;
uint2 counter;
signed divider;
void serialize(serializer&);
} frame;
void clock_frame_counter();
void clock_frame_counter_divider();
uint8 enabled_channels;
int16 cartridge_sample;
int16 pulse_dac[32];
int16 dmc_triangle_noise_dac[128][16][16];
static const uint8 length_counter_table[32];
static const uint16 ntsc_dmc_period_table[16];
static const uint16 pal_dmc_period_table[16];
static const uint16 ntsc_noise_period_table[16];
static const uint16 pal_noise_period_table[16];
};
extern APU apu;

View File

@@ -1,18 +0,0 @@
struct Noise {
unsigned length_counter;
Envelope envelope;
uint4 period;
unsigned period_counter;
bool short_mode;
uint15 lfsr;
void clock_length();
uint8 clock();
void power();
void reset();
void serialize(serializer&);
} noise;

View File

@@ -1,20 +0,0 @@
struct Pulse {
unsigned length_counter;
Envelope envelope;
Sweep sweep;
uint2 duty;
uint3 duty_counter;
uint11 period;
unsigned period_counter;
void clock_length();
bool check_period();
uint8 clock();
void power();
void reset();
void serialize(serializer&);
} pulse[2];

View File

@@ -1,16 +0,0 @@
struct Sweep {
uint8 shift;
bool decrement;
uint3 period;
uint8 counter;
bool enable;
bool reload;
uint11 pulse_period;
bool check_period();
void clock(unsigned channel);
void power();
void reset();
void serialize(serializer&);
};

View File

@@ -1,21 +0,0 @@
struct Triangle {
unsigned length_counter;
uint8 linear_length;
bool halt_length_counter;
uint11 period;
unsigned period_counter;
uint5 step_counter;
uint8 linear_length_counter;
bool reload_linear;
void clock_length();
void clock_linear_length();
uint8 clock();
void power();
void reset();
void serialize(serializer&);
} triangle;

View File

@@ -1,117 +0,0 @@
//BANDAI-FCG
struct BandaiFCG : Board {
uint8 chr_bank[8];
uint8 prg_bank;
uint2 mirror;
bool irq_counter_enable;
uint16 irq_counter;
uint16 irq_latch;
void main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_counter_enable) {
if(--irq_counter == 0xffff) {
cpu.set_irq_line(1);
irq_counter_enable = false;
}
}
tick();
}
}
unsigned ciram_addr(unsigned addr) const {
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);
}
}
uint8 prg_read(unsigned addr) {
if(addr & 0x8000) {
bool region = addr & 0x4000;
unsigned bank = (region == 0 ? prg_bank : 0x0f);
return prgrom.read((bank << 14) | (addr & 0x3fff));
}
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if(addr >= 0x6000) {
switch(addr & 15) {
case 0x00: case 0x01: case 0x02: case 0x03:
case 0x04: case 0x05: case 0x06: case 0x07:
chr_bank[addr & 7] = data;
break;
case 0x08:
prg_bank = data & 0x0f;
break;
case 0x09:
mirror = data & 0x03;
break;
case 0x0a:
cpu.set_irq_line(0);
irq_counter_enable = data & 0x01;
irq_counter = irq_latch;
break;
case 0x0b:
irq_latch = (irq_latch & 0xff00) | (data << 0);
break;
case 0x0c:
irq_latch = (irq_latch & 0x00ff) | (data << 8);
break;
case 0x0d:
//TODO: serial EEPROM support
break;
}
}
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read(ciram_addr(addr));
addr = (chr_bank[addr >> 10] << 10) | (addr & 0x03ff);
return Board::chr_read(addr);
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write(ciram_addr(addr), data);
addr = (chr_bank[addr >> 10] << 10) | (addr & 0x03ff);
return Board::chr_write(addr, data);
}
void power() {
reset();
}
void reset() {
for(auto &n : chr_bank) n = 0;
prg_bank = 0;
mirror = 0;
irq_counter_enable = 0;
irq_counter = 0;
irq_latch = 0;
}
void serialize(serializer& s) {
Board::serialize(s);
s.array(chr_bank);
s.integer(prg_bank);
s.integer(mirror);
s.integer(irq_counter_enable);
s.integer(irq_counter);
s.integer(irq_latch);
}
BandaiFCG(Markup::Node& document) : Board(document) {
}
};

View File

@@ -1,46 +0,0 @@
struct Board {
struct Memory {
uint8_t* data;
unsigned size;
bool writable;
inline uint8 read(unsigned addr) const;
inline void write(unsigned addr, uint8 data);
inline Memory(uint8_t* data, unsigned size) : data(data), size(size) {}
inline Memory() : data(nullptr), size(0u), writable(false) {}
inline ~Memory() { if(data) delete[] data; }
};
static unsigned mirror(unsigned addr, unsigned size);
virtual void main();
virtual void tick();
virtual uint8 prg_read(unsigned addr) = 0;
virtual void prg_write(unsigned addr, uint8 data) = 0;
virtual uint8 chr_read(unsigned addr);
virtual void chr_write(unsigned addr, uint8 data);
virtual inline void scanline(unsigned y) {}
virtual void power();
virtual void reset();
virtual void serialize(serializer&);
Board(Markup::Node& document);
virtual ~Board();
static Board* load(string manifest);
struct Information {
string type;
bool battery;
} information;
Memory prgrom;
Memory prgram;
Memory chrrom;
Memory chrram;
};

View File

@@ -1,40 +0,0 @@
struct KonamiVRC1 : Board {
VRC1 vrc1;
uint8 prg_read(unsigned addr) {
if(addr & 0x8000) return prgrom.read(vrc1.prg_addr(addr));
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if(addr & 0x8000) return vrc1.reg_write(addr, data);
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read(vrc1.ciram_addr(addr));
return Board::chr_read(vrc1.chr_addr(addr));
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write(vrc1.ciram_addr(addr), data);
return Board::chr_write(vrc1.chr_addr(addr), data);
}
void power() {
vrc1.power();
}
void reset() {
vrc1.reset();
}
void serialize(serializer& s) {
Board::serialize(s);
vrc1.serialize(s);
}
KonamiVRC1(Markup::Node& document) : Board(document), vrc1(*this) {
}
};

View File

@@ -1,57 +0,0 @@
struct KonamiVRC2 : Board {
struct Settings {
struct Pinout {
unsigned a0;
unsigned a1;
} pinout;
} settings;
VRC2 vrc2;
uint8 prg_read(unsigned addr) {
if(addr < 0x6000) return cpu.mdr();
if(addr < 0x8000) return vrc2.ram_read(addr);
return prgrom.read(vrc2.prg_addr(addr));
}
void prg_write(unsigned addr, uint8 data) {
if(addr < 0x6000) return;
if(addr < 0x8000) return vrc2.ram_write(addr, data);
bool a0 = (addr & settings.pinout.a0);
bool a1 = (addr & settings.pinout.a1);
addr &= 0xfff0;
addr |= (a0 << 0) | (a1 << 1);
return vrc2.reg_write(addr, data);
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read(vrc2.ciram_addr(addr));
return Board::chr_read(vrc2.chr_addr(addr));
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write(vrc2.ciram_addr(addr), data);
return Board::chr_write(vrc2.chr_addr(addr), data);
}
void power() {
vrc2.power();
}
void reset() {
vrc2.reset();
}
void serialize(serializer& s) {
Board::serialize(s);
vrc2.serialize(s);
}
KonamiVRC2(Markup::Node& document) : Board(document), vrc2(*this) {
settings.pinout.a0 = 1 << decimal(document["cartridge"]["chip"]["pinout"]["a0"].data);
settings.pinout.a1 = 1 << decimal(document["cartridge"]["chip"]["pinout"]["a1"].data);
}
};

View File

@@ -1,57 +0,0 @@
struct KonamiVRC3 : Board {
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
VRC3 vrc3;
void main() {
vrc3.main();
}
uint8 prg_read(unsigned addr) {
if((addr & 0xe000) == 0x6000) return prgram.read(addr & 0x1fff);
if(addr & 0x8000) return prgrom.read(vrc3.prg_addr(addr));
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if((addr & 0xe000) == 0x6000) return prgram.write(addr & 0x1fff, data);
if(addr & 0x8000) return vrc3.reg_write(addr, data);
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_read(addr & 0x07ff);
}
return chrram.read(addr);
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_write(addr & 0x07ff, data);
}
return chrram.write(addr, data);
}
void power() {
vrc3.power();
}
void reset() {
vrc3.reset();
}
void serialize(serializer& s) {
Board::serialize(s);
vrc3.serialize(s);
}
KonamiVRC3(Markup::Node& document) : Board(document), vrc3(*this) {
settings.mirror = document["cartridge"]["mirror"]["mode"].data == "vertical" ? 1 : 0;
}
};

View File

@@ -1,61 +0,0 @@
struct KonamiVRC4 : Board {
struct Settings {
struct Pinout {
unsigned a0;
unsigned a1;
} pinout;
} settings;
VRC4 vrc4;
void main() {
return vrc4.main();
}
uint8 prg_read(unsigned addr) {
if(addr < 0x6000) return cpu.mdr();
if(addr < 0x8000) return prgram.read(addr);
return prgrom.read(vrc4.prg_addr(addr));
}
void prg_write(unsigned addr, uint8 data) {
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.reg_write(addr, data);
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read(vrc4.ciram_addr(addr));
return Board::chr_read(vrc4.chr_addr(addr));
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write(vrc4.ciram_addr(addr), data);
return Board::chr_write(vrc4.chr_addr(addr), data);
}
void power() {
vrc4.power();
}
void reset() {
vrc4.reset();
}
void serialize(serializer& s) {
Board::serialize(s);
vrc4.serialize(s);
}
KonamiVRC4(Markup::Node& document) : Board(document), vrc4(*this) {
settings.pinout.a0 = 1 << decimal(document["cartridge"]["chip"]["pinout"]["a0"].data);
settings.pinout.a1 = 1 << decimal(document["cartridge"]["chip"]["pinout"]["a1"].data);
}
};

View File

@@ -1,42 +0,0 @@
struct KonamiVRC6 : Board {
VRC6 vrc6;
uint8 prg_read(unsigned addr) {
if((addr & 0xe000) == 0x6000) return vrc6.ram_read(addr);
if(addr & 0x8000) return prgrom.read(vrc6.prg_addr(addr));
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if((addr & 0xe000) == 0x6000) return vrc6.ram_write(addr, data);
if(addr & 0x8000) {
addr = (addr & 0xf003);
if(prgram.size) addr = (addr & ~3) | ((addr & 2) >> 1) | ((addr & 1) << 1);
return vrc6.reg_write(addr, data);
}
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read(vrc6.ciram_addr(addr));
return Board::chr_read(vrc6.chr_addr(addr));
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write(vrc6.ciram_addr(addr), data);
return Board::chr_write(vrc6.chr_addr(addr), data);
}
void serialize(serializer& s) {
Board::serialize(s);
vrc6.serialize(s);
}
void main() { vrc6.main(); }
void power() { vrc6.power(); }
void reset() { vrc6.reset(); }
KonamiVRC6(Markup::Node& document) : Board(document), vrc6(*this) {
}
};

View File

@@ -1,47 +0,0 @@
struct KonamiVRC7 : Board {
VRC7 vrc7;
void main() {
return vrc7.main();
}
uint8 prg_read(unsigned addr) {
if(addr < 0x6000) return cpu.mdr();
if(addr < 0x8000) return prgram.read(addr);
return prgrom.read(vrc7.prg_addr(addr));
}
void prg_write(unsigned addr, uint8 data) {
if(addr < 0x6000) return;
if(addr < 0x8000) return prgram.write(addr, data);
return vrc7.reg_write(addr, data);
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read(vrc7.ciram_addr(addr));
return chrram.read(vrc7.chr_addr(addr));
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write(vrc7.ciram_addr(addr), data);
return chrram.write(vrc7.chr_addr(addr), data);
}
void power() {
vrc7.power();
}
void reset() {
vrc7.reset();
}
void serialize(serializer& s) {
Board::serialize(s);
vrc7.serialize(s);
}
KonamiVRC7(Markup::Node& document) : Board(document), vrc7(*this) {
}
};

View File

@@ -1,51 +0,0 @@
//NES-AMROM
//NES-ANROM
//NES-AN1ROM
//NES-AOROM
struct NES_AxROM : Board {
uint4 prg_bank;
bool mirror_select;
uint8 prg_read(unsigned addr) {
if(addr & 0x8000) return prgrom.read((prg_bank << 15) | (addr & 0x7fff));
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if(addr & 0x8000) {
prg_bank = data & 0x0f;
mirror_select = data & 0x10;
}
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read((mirror_select << 10) | (addr & 0x03ff));
return Board::chr_read(addr);
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write((mirror_select << 10) | (addr & 0x03ff), data);
return Board::chr_write(addr, data);
}
void power() {
}
void reset() {
prg_bank = 0x0f;
mirror_select = 0;
}
void serialize(serializer& s) {
Board::serialize(s);
s.integer(prg_bank);
s.integer(mirror_select);
}
NES_AxROM(Markup::Node& document) : Board(document) {
}
};

View File

@@ -1,52 +0,0 @@
//NES-BN-ROM-01
struct NES_BNROM : Board {
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
uint2 prg_bank;
uint8 prg_read(unsigned addr) {
if(addr & 0x8000) return prgrom.read((prg_bank << 15) | (addr & 0x7fff));
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if(addr & 0x8000) prg_bank = data & 0x03;
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_read(addr);
}
return Board::chr_read(addr);
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_write(addr, data);
}
return Board::chr_write(addr, data);
}
void power() {
}
void reset() {
prg_bank = 0;
}
void serialize(serializer& s) {
Board::serialize(s);
s.integer(prg_bank);
}
NES_BNROM(Markup::Node& document) : Board(document) {
settings.mirror = document["cartridge"]["mirror"]["mode"].data == "vertical" ? 1 : 0;
}
};

View File

@@ -1,54 +0,0 @@
//NES-CNROM
struct NES_CNROM : Board {
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
uint2 chr_bank;
uint8 prg_read(unsigned addr) {
if(addr & 0x8000) return prgrom.read(addr & 0x7fff);
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if(addr & 0x8000) chr_bank = data & 0x03;
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_read(addr & 0x07ff);
}
addr = (chr_bank * 0x2000) + (addr & 0x1fff);
return Board::chr_read(addr);
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_write(addr & 0x07ff, data);
}
addr = (chr_bank * 0x2000) + (addr & 0x1fff);
Board::chr_write(addr, data);
}
void power() {
}
void reset() {
chr_bank = 0;
}
void serialize(serializer& s) {
Board::serialize(s);
s.integer(chr_bank);
}
NES_CNROM(Markup::Node& document) : Board(document) {
settings.mirror = document["cartridge"]["mirror"]["mode"].data == "vertical" ? 1 : 0;
}
};

View File

@@ -1,53 +0,0 @@
struct NES_ExROM : Board {
enum class Revision : unsigned {
EKROM,
ELROM,
ETROM,
EWROM,
} revision;
MMC5 mmc5;
void main() {
mmc5.main();
}
uint8 prg_read(unsigned addr) {
return mmc5.prg_read(addr);
}
void prg_write(unsigned addr, uint8 data) {
mmc5.prg_write(addr, data);
}
uint8 chr_read(unsigned addr) {
return mmc5.chr_read(addr);
}
void chr_write(unsigned addr, uint8 data) {
mmc5.chr_write(addr, data);
}
void scanline(unsigned y) {
mmc5.scanline(y);
}
void power() {
mmc5.power();
}
void reset() {
mmc5.reset();
}
void serialize(serializer& s) {
Board::serialize(s);
mmc5.serialize(s);
}
NES_ExROM(Markup::Node& document) : Board(document), mmc5(*this) {
revision = Revision::ELROM;
}
};

View File

@@ -1,91 +0,0 @@
//MMC4
struct NES_FxROM : Board {
enum Revision : unsigned {
FJROM,
FKROM,
} revision;
uint4 prg_bank;
uint5 chr_bank[2][2];
bool mirror;
bool latch[2];
uint8 prg_read(unsigned addr) {
if(addr < 0x6000) return cpu.mdr();
if(addr < 0x8000) return prgram.read(addr);
unsigned bank = addr < 0xc000 ? prg_bank : (uint4)0x0f;
return prgrom.read((bank * 0x4000) | (addr & 0x3fff));
}
void prg_write(unsigned addr, uint8 data) {
if(addr < 0x6000) return;
if(addr < 0x8000) return prgram.write(addr, data);
switch(addr & 0xf000) {
case 0xa000: prg_bank = data & 0x0f; break;
case 0xb000: chr_bank[0][0] = data & 0x1f; break;
case 0xc000: chr_bank[0][1] = data & 0x1f; break;
case 0xd000: chr_bank[1][0] = data & 0x1f; break;
case 0xe000: chr_bank[1][1] = data & 0x1f; break;
case 0xf000: mirror = data & 0x01; break;
}
}
unsigned ciram_addr(unsigned addr) const {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
}
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read(ciram_addr(addr));
bool region = addr & 0x1000;
unsigned bank = chr_bank[region][latch[region]];
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
return Board::chr_read((bank * 0x1000) | (addr & 0x0fff));
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write(ciram_addr(addr), data);
bool region = addr & 0x1000;
unsigned bank = chr_bank[region][latch[region]];
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
return Board::chr_write((bank * 0x1000) | (addr & 0x0fff), data);
}
void power() {
}
void reset() {
prg_bank = 0;
chr_bank[0][0] = 0;
chr_bank[0][1] = 0;
chr_bank[1][0] = 0;
chr_bank[1][1] = 0;
mirror = 0;
latch[0] = 0;
latch[1] = 0;
}
void serialize(serializer& s) {
Board::serialize(s);
s.integer(prg_bank);
s.integer(chr_bank[0][0]);
s.integer(chr_bank[0][1]);
s.integer(chr_bank[1][0]);
s.integer(chr_bank[1][1]);
s.integer(mirror);
s.array(latch);
}
NES_FxROM(Markup::Node& document) : Board(document) {
revision = Revision::FKROM;
}
};

View File

@@ -1,61 +0,0 @@
//NES-GNROM
//NES-MHROM
struct NES_GxROM : Board {
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
uint2 prg_bank;
uint2 chr_bank;
uint8 prg_read(unsigned addr) {
if(addr & 0x8000) return prgrom.read((prg_bank << 15) | (addr & 0x7fff));
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if(addr & 0x8000) {
prg_bank = (data & 0x30) >> 4;
chr_bank = (data & 0x03) >> 0;
}
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_read(addr & 0x07ff);
}
addr = (chr_bank * 0x2000) + (addr & 0x1fff);
return Board::chr_read(addr);
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_write(addr & 0x07ff, data);
}
addr = (chr_bank * 0x2000) + (addr & 0x1fff);
Board::chr_write(addr, data);
}
void power() {
}
void reset() {
prg_bank = 0;
chr_bank = 0;
}
void serialize(serializer& s) {
Board::serialize(s);
s.integer(prg_bank);
s.integer(chr_bank);
}
NES_GxROM(Markup::Node& document) : Board(document) {
settings.mirror = document["cartridge"]["mirror"]["mode"].data == "vertical" ? 1 : 0;
}
};

View File

@@ -1,48 +0,0 @@
struct NES_HKROM : Board {
MMC6 mmc6;
void main() {
mmc6.main();
}
uint8 prg_read(unsigned addr) {
if((addr & 0xf000) == 0x7000) return mmc6.ram_read(addr);
if(addr & 0x8000) return prgrom.read(mmc6.prg_addr(addr));
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if((addr & 0xf000) == 0x7000) return mmc6.ram_write(addr, data);
if(addr & 0x8000) return mmc6.reg_write(addr, data);
}
uint8 chr_read(unsigned addr) {
mmc6.irq_test(addr);
if(addr & 0x2000) return ppu.ciram_read(mmc6.ciram_addr(addr));
return Board::chr_read(mmc6.chr_addr(addr));
}
void chr_write(unsigned addr, uint8 data) {
mmc6.irq_test(addr);
if(addr & 0x2000) return ppu.ciram_write(mmc6.ciram_addr(addr), data);
return Board::chr_write(mmc6.chr_addr(addr), data);
}
void power() {
mmc6.power();
}
void reset() {
mmc6.reset();
}
void serialize(serializer& s) {
Board::serialize(s);
mmc6.serialize(s);
}
NES_HKROM(Markup::Node& document) : Board(document), mmc6(*this) {
}
};

View File

@@ -1,43 +0,0 @@
//NES-NROM-128
//NES-NROM-256
struct NES_NROM : Board {
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
uint8 prg_read(unsigned addr) {
if(addr & 0x8000) return prgrom.read(addr);
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_read(addr & 0x07ff);
}
if(chrram.size) return chrram.read(addr);
return chrrom.read(addr);
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_write(addr & 0x07ff, data);
}
if(chrram.size) return chrram.write(addr, data);
}
void serialize(serializer& s) {
Board::serialize(s);
}
NES_NROM(Markup::Node& document) : Board(document) {
settings.mirror = document["cartridge"]["mirror"]["mode"].data == "vertical" ? 1 : 0;
}
};

View File

@@ -1,97 +0,0 @@
//MMC2
struct NES_PxROM : Board {
enum Revision : unsigned {
PEEOROM,
PNROM,
} revision;
uint4 prg_bank;
uint5 chr_bank[2][2];
bool mirror;
bool latch[2];
uint8 prg_read(unsigned addr) {
if(addr < 0x6000) return cpu.mdr();
if(addr < 0x8000) return prgram.read(addr);
unsigned bank = 0;
switch((addr / 0x2000) & 3) {
case 0: bank = prg_bank; break;
case 1: bank = 0x0d; break;
case 2: bank = 0x0e; break;
case 3: bank = 0x0f; break;
}
return prgrom.read((bank * 0x2000) | (addr & 0x1fff));
}
void prg_write(unsigned addr, uint8 data) {
if(addr < 0x6000) return;
if(addr < 0x8000) return prgram.write(addr, data);
switch(addr & 0xf000) {
case 0xa000: prg_bank = data & 0x0f; break;
case 0xb000: chr_bank[0][0] = data & 0x1f; break;
case 0xc000: chr_bank[0][1] = data & 0x1f; break;
case 0xd000: chr_bank[1][0] = data & 0x1f; break;
case 0xe000: chr_bank[1][1] = data & 0x1f; break;
case 0xf000: mirror = data & 0x01; break;
}
}
unsigned ciram_addr(unsigned addr) const {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
}
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read(ciram_addr(addr));
bool region = addr & 0x1000;
unsigned bank = chr_bank[region][latch[region]];
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
return Board::chr_read((bank * 0x1000) | (addr & 0x0fff));
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write(ciram_addr(addr), data);
bool region = addr & 0x1000;
unsigned bank = chr_bank[region][latch[region]];
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
return Board::chr_write((bank * 0x1000) | (addr & 0x0fff), data);
}
void power() {
}
void reset() {
prg_bank = 0;
chr_bank[0][0] = 0;
chr_bank[0][1] = 0;
chr_bank[1][0] = 0;
chr_bank[1][1] = 0;
mirror = 0;
latch[0] = 0;
latch[1] = 0;
}
void serialize(serializer& s) {
Board::serialize(s);
s.integer(prg_bank);
s.integer(chr_bank[0][0]);
s.integer(chr_bank[0][1]);
s.integer(chr_bank[1][0]);
s.integer(chr_bank[1][1]);
s.integer(mirror);
s.array(latch);
}
NES_PxROM(Markup::Node& document) : Board(document) {
revision = Revision::PNROM;
}
};

View File

@@ -1,101 +0,0 @@
struct NES_SxROM : Board {
enum class Revision : unsigned {
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;
void main() {
return mmc1.main();
}
unsigned ram_addr(unsigned addr) {
unsigned bank = 0;
if(revision == Revision::SOROM) bank = (mmc1.chr_bank[0] & 0x08) >> 3;
if(revision == Revision::SUROM) bank = (mmc1.chr_bank[0] & 0x0c) >> 2;
if(revision == Revision::SXROM) bank = (mmc1.chr_bank[0] & 0x0c) >> 2;
return (bank << 13) | (addr & 0x1fff);
}
uint8 prg_read(unsigned addr) {
if((addr & 0xe000) == 0x6000) {
if(revision == Revision::SNROM) {
if(mmc1.chr_bank[0] & 0x10) return cpu.mdr();
}
if(mmc1.ram_disable) return 0x00;
return prgram.read(ram_addr(addr));
}
if(addr & 0x8000) {
addr = mmc1.prg_addr(addr);
if(revision == Revision::SXROM) {
addr |= ((mmc1.chr_bank[0] & 0x10) >> 4) << 18;
}
return prgrom.read(addr);
}
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if((addr & 0xe000) == 0x6000) {
if(revision == Revision::SNROM) {
if(mmc1.chr_bank[0] & 0x10) return;
}
if(mmc1.ram_disable) return;
return prgram.write(ram_addr(addr), data);
}
if(addr & 0x8000) return mmc1.mmio_write(addr, data);
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read(mmc1.ciram_addr(addr));
return Board::chr_read(mmc1.chr_addr(addr));
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write(mmc1.ciram_addr(addr), data);
return Board::chr_write(mmc1.chr_addr(addr), data);
}
void power() {
mmc1.power();
}
void reset() {
mmc1.reset();
}
void serialize(serializer& s) {
Board::serialize(s);
mmc1.serialize(s);
}
NES_SxROM(Markup::Node& document) : Board(document), mmc1(*this) {
revision = Revision::SXROM;
}
};

View File

@@ -1,67 +0,0 @@
struct NES_TxROM : Board {
enum class Revision : unsigned {
TBROM,
TEROM,
TFROM,
TGROM,
TKROM,
TKSROM,
TLROM,
TL1ROM,
TL2ROM,
TLSROM,
TNROM,
TQROM,
TR1ROM,
TSROM,
TVROM,
} revision;
MMC3 mmc3;
void main() {
mmc3.main();
}
uint8 prg_read(unsigned addr) {
if((addr & 0xe000) == 0x6000) return mmc3.ram_read(addr);
if(addr & 0x8000) return prgrom.read(mmc3.prg_addr(addr));
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if((addr & 0xe000) == 0x6000) return mmc3.ram_write(addr, data);
if(addr & 0x8000) return mmc3.reg_write(addr, data);
}
uint8 chr_read(unsigned addr) {
mmc3.irq_test(addr);
if(addr & 0x2000) return ppu.ciram_read(mmc3.ciram_addr(addr));
return Board::chr_read(mmc3.chr_addr(addr));
}
void chr_write(unsigned addr, uint8 data) {
mmc3.irq_test(addr);
if(addr & 0x2000) return ppu.ciram_write(mmc3.ciram_addr(addr), data);
return Board::chr_write(mmc3.chr_addr(addr), data);
}
void power() {
mmc3.power();
}
void reset() {
mmc3.reset();
}
void serialize(serializer& s) {
Board::serialize(s);
mmc3.serialize(s);
}
NES_TxROM(Markup::Node& document) : Board(document), mmc3(*this) {
revision = Revision::TLROM;
}
};

View File

@@ -1,55 +0,0 @@
//NES-UNROM
//NES-UOROM
struct NES_UxROM : Board {
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
uint4 prg_bank;
uint8 prg_read(unsigned addr) {
if((addr & 0xc000) == 0x8000) return prgrom.read((prg_bank << 14) | (addr & 0x3fff));
if((addr & 0xc000) == 0xc000) return prgrom.read(( 0x0f << 14) | (addr & 0x3fff));
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if(addr & 0x8000) prg_bank = data & 0x0f;
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_read(addr);
}
return Board::chr_read(addr);
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_write(addr, data);
}
return Board::chr_write(addr, data);
}
void power() {
}
void reset() {
prg_bank = 0;
}
void serialize(serializer& s) {
Board::serialize(s);
s.integer(prg_bank);
}
NES_UxROM(Markup::Node& document) : Board(document) {
settings.mirror = document["cartridge"]["mirror"]["mode"].data == "vertical" ? 1 : 0;
}
};

View File

@@ -1,226 +0,0 @@
//SUNSOFT-5B
struct Sunsoft5B : Board {
uint4 mmu_port;
uint4 apu_port;
uint8 prg_bank[4];
uint8 chr_bank[8];
uint2 mirror;
bool irq_enable;
bool irq_counter_enable;
uint16 irq_counter;
int16 dac[16];
struct Pulse {
bool disable;
uint12 frequency;
uint4 volume;
uint16 counter; //12-bit countdown + 4-bit phase
uint1 duty;
uint4 output;
void clock() {
if(--counter == 0) {
counter = frequency << 4;
duty ^= 1;
}
output = duty ? volume : (uint4)0;
if(disable) output = 0;
}
void reset() {
disable = 1;
frequency = 1;
volume = 0;
counter = 0;
duty = 0;
output = 0;
}
void serialize(serializer& s) {
s.integer(disable);
s.integer(frequency);
s.integer(volume);
s.integer(counter);
s.integer(duty);
s.integer(output);
}
} pulse[3];
void main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_counter_enable) {
if(--irq_counter == 0xffff) {
cpu.set_irq_line(irq_enable);
}
}
pulse[0].clock();
pulse[1].clock();
pulse[2].clock();
int16 output = dac[pulse[0].output] + dac[pulse[1].output] + dac[pulse[2].output];
apu.set_sample(-output);
tick();
}
}
uint8 prg_read(unsigned addr) {
if(addr < 0x6000) return cpu.mdr();
uint8 bank = 0x3f; //((addr & 0xe000) == 0xe000
if((addr & 0xe000) == 0x6000) bank = prg_bank[0];
if((addr & 0xe000) == 0x8000) bank = prg_bank[1];
if((addr & 0xe000) == 0xa000) bank = prg_bank[2];
if((addr & 0xe000) == 0xc000) bank = prg_bank[3];
bool ram_enable = bank & 0x80;
bool ram_select = bank & 0x40;
bank &= 0x3f;
if(ram_select) {
if(ram_enable == false) return cpu.mdr();
return prgram.data[addr & 0x1fff];
}
addr = (bank << 13) | (addr & 0x1fff);
return prgrom.read(addr);
}
void prg_write(unsigned addr, uint8 data) {
if((addr & 0xe000) == 0x6000) {
prgram.data[addr & 0x1fff] = data;
}
if(addr == 0x8000) {
mmu_port = data & 0x0f;
}
if(addr == 0xa000) {
switch(mmu_port) {
case 0: chr_bank[0] = data; break;
case 1: chr_bank[1] = data; break;
case 2: chr_bank[2] = data; break;
case 3: chr_bank[3] = data; break;
case 4: chr_bank[4] = data; break;
case 5: chr_bank[5] = data; break;
case 6: chr_bank[6] = data; break;
case 7: chr_bank[7] = data; break;
case 8: prg_bank[0] = data; break;
case 9: prg_bank[1] = data; break;
case 10: prg_bank[2] = data; break;
case 11: prg_bank[3] = data; break;
case 12: mirror = data & 3; break;
case 13:
irq_enable = data & 0x80;
irq_counter_enable = data & 0x01;
if(irq_enable == 0) cpu.set_irq_line(0);
break;
case 14: irq_counter = (irq_counter & 0xff00) | (data << 0); break;
case 15: irq_counter = (irq_counter & 0x00ff) | (data << 8); break;
}
}
if(addr == 0xc000) {
apu_port = data & 0x0f;
}
if(addr == 0xe000) {
switch(apu_port) {
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;
}
}
}
unsigned chr_addr(unsigned addr) {
uint8 bank = (addr >> 10) & 7;
return (chr_bank[bank] << 10) | (addr & 0x03ff);
}
unsigned ciram_addr(unsigned addr) {
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
}
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read(ciram_addr(addr));
return Board::chr_read(chr_addr(addr));
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write(ciram_addr(addr), data);
return Board::chr_write(chr_addr(addr), data);
}
void power() {
for(signed n = 0; n < 16; n++) {
double volume = 1.0 / pow(2, 1.0 / 2 * (15 - n));
dac[n] = volume * 8192.0;
}
}
void reset() {
mmu_port = 0;
apu_port = 0;
for(auto& n : prg_bank) n = 0;
for(auto& n : chr_bank) n = 0;
mirror = 0;
irq_enable = 0;
irq_counter_enable = 0;
irq_counter = 0;
pulse[0].reset();
pulse[1].reset();
pulse[2].reset();
}
void serialize(serializer& s) {
Board::serialize(s);
s.integer(mmu_port);
s.integer(apu_port);
s.array(prg_bank);
s.array(chr_bank);
s.integer(mirror);
s.integer(irq_enable);
s.integer(irq_counter_enable);
s.integer(irq_counter);
pulse[0].serialize(s);
pulse[1].serialize(s);
pulse[2].serialize(s);
}
Sunsoft5B(Markup::Node& document) : Board(document) {
}
};

View File

@@ -1,86 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
#include "chip/chip.cpp"
#include "board/board.cpp"
Cartridge cartridge;
string Cartridge::title() {
return information.title;
}
void Cartridge::Main() {
cartridge.main();
}
void Cartridge::main() {
board->main();
}
void Cartridge::load() {
interface->loadRequest(ID::Manifest, "manifest.bml");
Board::load(information.markup); //this call will set Cartridge::board if successful
if(board == nullptr) return;
sha256_ctx sha;
uint8 hash[32];
sha256_init(&sha);
sha256_chunk(&sha, board->prgrom.data, board->prgrom.size);
sha256_chunk(&sha, board->chrrom.data, board->chrrom.size);
sha256_final(&sha);
sha256_hash(&sha, hash);
string result;
for(auto& byte : hash) result.append(hex<2>(byte));
sha256 = result;
system.load();
loaded = true;
}
void Cartridge::unload() {
if(loaded == false) return;
loaded = false;
memory.reset();
}
void Cartridge::power() {
board->power();
}
void Cartridge::reset() {
create(Cartridge::Main, 21477272);
board->reset();
}
Cartridge::Cartridge() {
loaded = false;
}
uint8 Cartridge::prg_read(unsigned addr) {
return board->prg_read(addr);
}
void Cartridge::prg_write(unsigned addr, uint8 data) {
return board->prg_write(addr, data);
}
uint8 Cartridge::chr_read(unsigned addr) {
return board->chr_read(addr);
}
void Cartridge::chr_write(unsigned addr, uint8 data) {
return board->chr_write(addr, data);
}
void Cartridge::scanline(unsigned y) {
return board->scanline(y);
}
void Cartridge::serialize(serializer& s) {
Thread::serialize(s);
return board->serialize(s);
}
}

View File

@@ -1,47 +0,0 @@
#include "chip/chip.hpp"
#include "board/board.hpp"
struct Cartridge : Thread, property<Cartridge> {
static void Main();
void main();
void load();
void unload();
void power();
void reset();
readonly<bool> loaded;
readonly<string> sha256;
struct Information {
string markup;
string title;
} information;
string title();
struct Memory {
unsigned id;
string name;
};
vector<Memory> memory;
void serialize(serializer&);
Cartridge();
//privileged:
Board *board;
uint8 prg_read(unsigned addr);
void prg_write(unsigned addr, uint8 data);
uint8 chr_read(unsigned addr);
void chr_write(unsigned addr, uint8 data);
//scanline() is for debugging purposes only:
//boards must detect scanline edges on their own
void scanline(unsigned y);
};
extern Cartridge cartridge;

View File

@@ -1,136 +0,0 @@
struct MMC1 : Chip {
enum class Revision : unsigned {
MMC1,
MMC1A,
MMC1B1,
MMC1B2,
MMC1B3,
MMC1C,
} revision;
unsigned writedelay;
unsigned shiftaddr;
unsigned shiftdata;
bool chr_mode;
bool prg_size; //0 = 32K, 1 = 16K
bool prg_mode;
uint2 mirror; //0 = first, 1 = second, 2 = vertical, 3 = horizontal
uint5 chr_bank[2];
bool ram_disable;
uint4 prg_bank;
void main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(writedelay) writedelay--;
tick();
}
}
unsigned prg_addr(unsigned addr) {
bool region = addr & 0x4000;
unsigned bank = (prg_bank & ~1) + region;
if(prg_size) {
bank = (region == 0 ? 0x0 : 0xf);
if(region != prg_mode) bank = prg_bank;
}
return (bank << 14) | (addr & 0x3fff);
}
unsigned chr_addr(unsigned addr) {
bool region = addr & 0x1000;
unsigned bank = chr_bank[region];
if(chr_mode == 0) bank = (chr_bank[0] & ~1) | region;
return (bank << 12) | (addr & 0x0fff);
}
unsigned ciram_addr(unsigned addr) {
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);
}
}
void mmio_write(unsigned addr, uint8 data) {
if(writedelay) return;
writedelay = 2;
if(data & 0x80) {
shiftaddr = 0;
prg_size = 1;
prg_mode = 1;
} else {
shiftdata = ((data & 1) << 4) | (shiftdata >> 1);
if(++shiftaddr == 5) {
shiftaddr = 0;
switch((addr >> 13) & 3) {
case 0:
chr_mode = (shiftdata & 0x10);
prg_size = (shiftdata & 0x08);
prg_mode = (shiftdata & 0x04);
mirror = (shiftdata & 0x03);
break;
case 1:
chr_bank[0] = (shiftdata & 0x1f);
break;
case 2:
chr_bank[1] = (shiftdata & 0x1f);
break;
case 3:
ram_disable = (shiftdata & 0x10);
prg_bank = (shiftdata & 0x0f);
break;
}
}
}
}
void power() {
}
void reset() {
writedelay = 0;
shiftaddr = 0;
shiftdata = 0;
chr_mode = 0;
prg_size = 1;
prg_mode = 1;
mirror = 0;
chr_bank[0] = 0;
chr_bank[1] = 1;
ram_disable = 0;
prg_bank = 0;
}
void serialize(serializer& s) {
s.integer(writedelay);
s.integer(shiftaddr);
s.integer(shiftdata);
s.integer(chr_mode);
s.integer(prg_size);
s.integer(prg_mode);
s.integer(mirror);
s.array(chr_bank);
s.integer(ram_disable);
s.integer(prg_bank);
}
MMC1(Board& board) : Chip(board) {
revision = Revision::MMC1B2;
}
};

View File

@@ -1,189 +0,0 @@
struct MMC3 : Chip {
bool chr_mode;
bool prg_mode;
uint3 bank_select;
uint8 prg_bank[2];
uint8 chr_bank[6];
bool mirror;
bool ram_enable;
bool ram_write_protect;
uint8 irq_latch;
uint8 irq_counter;
bool irq_enable;
unsigned irq_delay;
bool irq_line;
uint16 chr_abus;
void main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_delay) irq_delay--;
cpu.set_irq_line(irq_line);
tick();
}
}
void irq_test(unsigned addr) {
if(!(chr_abus & 0x1000) && (addr & 0x1000)) {
if(irq_delay == 0) {
if(irq_counter == 0) {
irq_counter = irq_latch;
} else if(--irq_counter == 0) {
if(irq_enable) irq_line = 1;
}
}
irq_delay = 6;
}
chr_abus = addr;
}
unsigned prg_addr(unsigned addr) const {
switch((addr >> 13) & 3) {
case 0:
if(prg_mode == 1) return (0x3e << 13) | (addr & 0x1fff);
return (prg_bank[0] << 13) | (addr & 0x1fff);
case 1:
return (prg_bank[1] << 13) | (addr & 0x1fff);
case 2:
if(prg_mode == 0) return (0x3e << 13) | (addr & 0x1fff);
return (prg_bank[0] << 13) | (addr & 0x1fff);
case 3:
return (0x3f << 13) | (addr & 0x1fff);
}
}
unsigned chr_addr(unsigned addr) const {
if(chr_mode == 0) {
if(addr <= 0x07ff) return (chr_bank[0] << 10) | (addr & 0x07ff);
if(addr <= 0x0fff) return (chr_bank[1] << 10) | (addr & 0x07ff);
if(addr <= 0x13ff) return (chr_bank[2] << 10) | (addr & 0x03ff);
if(addr <= 0x17ff) return (chr_bank[3] << 10) | (addr & 0x03ff);
if(addr <= 0x1bff) return (chr_bank[4] << 10) | (addr & 0x03ff);
if(addr <= 0x1fff) return (chr_bank[5] << 10) | (addr & 0x03ff);
} else {
if(addr <= 0x03ff) return (chr_bank[2] << 10) | (addr & 0x03ff);
if(addr <= 0x07ff) return (chr_bank[3] << 10) | (addr & 0x03ff);
if(addr <= 0x0bff) return (chr_bank[4] << 10) | (addr & 0x03ff);
if(addr <= 0x0fff) return (chr_bank[5] << 10) | (addr & 0x03ff);
if(addr <= 0x17ff) return (chr_bank[0] << 10) | (addr & 0x07ff);
if(addr <= 0x1fff) return (chr_bank[1] << 10) | (addr & 0x07ff);
}
}
unsigned ciram_addr(unsigned addr) const {
if(mirror == 0) return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
if(mirror == 1) return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
}
uint8 ram_read(unsigned addr) {
if(ram_enable) return board.prgram.data[addr & 0x1fff];
return 0x00;
}
void ram_write(unsigned addr, uint8 data) {
if(ram_enable && !ram_write_protect) board.prgram.data[addr & 0x1fff] = data;
}
void reg_write(unsigned addr, uint8 data) {
switch(addr & 0xe001) {
case 0x8000:
chr_mode = data & 0x80;
prg_mode = data & 0x40;
bank_select = data & 0x07;
break;
case 0x8001:
switch(bank_select) {
case 0: chr_bank[0] = data & ~1; break;
case 1: chr_bank[1] = data & ~1; break;
case 2: chr_bank[2] = data; break;
case 3: chr_bank[3] = data; break;
case 4: chr_bank[4] = data; break;
case 5: chr_bank[5] = data; break;
case 6: prg_bank[0] = data & 0x3f; break;
case 7: prg_bank[1] = data & 0x3f; break;
}
break;
case 0xa000:
mirror = data & 0x01;
break;
case 0xa001:
ram_enable = data & 0x80;
ram_write_protect = data & 0x40;
break;
case 0xc000:
irq_latch = data;
break;
case 0xc001:
irq_counter = 0;
break;
case 0xe000:
irq_enable = false;
irq_line = 0;
break;
case 0xe001:
irq_enable = true;
break;
}
}
void power() {
}
void reset() {
chr_mode = 0;
prg_mode = 0;
bank_select = 0;
prg_bank[0] = 0;
prg_bank[1] = 0;
chr_bank[0] = 0;
chr_bank[1] = 0;
chr_bank[2] = 0;
chr_bank[3] = 0;
chr_bank[4] = 0;
chr_bank[5] = 0;
mirror = 0;
ram_enable = 1;
ram_write_protect = 0;
irq_latch = 0;
irq_counter = 0;
irq_enable = false;
irq_delay = 0;
irq_line = 0;
chr_abus = 0;
}
void serialize(serializer& s) {
s.integer(chr_mode);
s.integer(prg_mode);
s.integer(bank_select);
s.array(prg_bank);
s.array(chr_bank);
s.integer(mirror);
s.integer(ram_enable);
s.integer(ram_write_protect);
s.integer(irq_latch);
s.integer(irq_counter);
s.integer(irq_enable);
s.integer(irq_delay);
s.integer(irq_line);
s.integer(chr_abus);
}
MMC3(Board& board) : Chip(board) {
}
};

View File

@@ -1,497 +0,0 @@
struct MMC5 : Chip {
enum class Revision : unsigned {
MMC5,
MMC5B,
} revision;
uint8 exram[1024];
//programmable registers
uint2 prg_mode; //$5100
uint2 chr_mode; //$5101
uint2 prgram_write_protect[2]; //$5102,$5103
uint2 exram_mode; //$5104
uint2 nametable_mode[4]; //$5105
uint8 fillmode_tile; //$5106
uint8 fillmode_color; //$5107
bool ram_select; //$5113
uint2 ram_bank; //$5113
uint8 prg_bank[4]; //$5114-5117
uint10 chr_sprite_bank[8]; //$5120-5127
uint10 chr_bg_bank[4]; //$5128-512b
uint2 chr_bank_hi; //$5130
bool vs_enable; //$5200
bool vs_side; //$5200
uint5 vs_tile; //$5200
uint8 vs_scroll; //$5201
uint8 vs_bank; //$5202
uint8 irq_line; //$5203
bool irq_enable; //$5204
uint8 multiplicand; //$5205
uint8 multiplier; //$5206
//status registers
unsigned cpu_cycle_counter;
unsigned irq_counter;
bool irq_pending;
bool in_frame;
unsigned vcounter;
unsigned hcounter;
uint16 chr_access[4];
bool chr_active;
bool sprite_8x16;
uint8 exbank;
uint8 exattr;
bool vs_fetch;
uint8 vs_vpos;
uint8 vs_hpos;
void main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
//scanline() resets this; if no scanlines detected, enter video blanking period
if(++cpu_cycle_counter >= 200) blank(); //113-114 normal; ~2500 across Vblank period
cpu.set_irq_line(irq_enable && irq_pending);
tick();
}
}
void scanline(unsigned y) {
//used for testing only, to verify MMC5 scanline detection is accurate:
//if(y != vcounter && y <= 240) print(y, " vs ", vcounter, "\n");
}
uint8 prg_access(bool write, unsigned addr, uint8 data = 0x00) {
unsigned bank;
if((addr & 0xe000) == 0x6000) {
bank = (ram_select << 2) | ram_bank;
addr &= 0x1fff;
} else if(prg_mode == 0) {
bank = prg_bank[3] & ~3;
addr &= 0x7fff;
} else if(prg_mode == 1) {
if((addr & 0xc000) == 0x8000) bank = (prg_bank[1] & ~1);
if((addr & 0xe000) == 0xc000) bank = (prg_bank[3] & ~1);
addr &= 0x3fff;
} else if(prg_mode == 2) {
if((addr & 0xe000) == 0x8000) bank = (prg_bank[1] & ~1) | 0;
if((addr & 0xe000) == 0xa000) bank = (prg_bank[1] & ~1) | 1;
if((addr & 0xe000) == 0xc000) bank = (prg_bank[2]);
if((addr & 0xe000) == 0xe000) bank = (prg_bank[3]);
addr &= 0x1fff;
} else if(prg_mode == 3) {
if((addr & 0xe000) == 0x8000) bank = prg_bank[0];
if((addr & 0xe000) == 0xa000) bank = prg_bank[1];
if((addr & 0xe000) == 0xc000) bank = prg_bank[2];
if((addr & 0xe000) == 0xe000) bank = prg_bank[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(prgram_write_protect[0] == 2 && prgram_write_protect[1] == 1) {
board.prgram.write((bank << 13) | addr, data);
}
}
return 0x00;
}
}
uint8 prg_read(unsigned addr) {
if((addr & 0xfc00) == 0x5c00) {
if(exram_mode >= 2) return exram[addr & 0x03ff];
return cpu.mdr();
}
if(addr >= 0x6000) {
return prg_access(0, addr);
}
switch(addr) {
case 0x5204: {
uint8 result = (irq_pending << 7) | (in_frame << 6);
irq_pending = false;
return result;
}
case 0x5205: return (multiplier * multiplicand) >> 0;
case 0x5206: return (multiplier * multiplicand) >> 8;
}
}
void prg_write(unsigned addr, uint8 data) {
if((addr & 0xfc00) == 0x5c00) {
//writes 0x00 *during* Vblank (not during screen rendering ...)
if(exram_mode == 0 || exram_mode == 1) exram[addr & 0x03ff] = in_frame ? data : 0x00;
if(exram_mode == 2) exram[addr & 0x03ff] = data;
return;
}
if(addr >= 0x6000) {
prg_access(1, addr, data);
return;
}
switch(addr) {
case 0x2000:
sprite_8x16 = data & 0x20;
break;
case 0x2001:
//if BG+sprites are disabled; enter video blanking period
if((data & 0x18) == 0) blank();
break;
case 0x5100: prg_mode = data & 3; break;
case 0x5101: chr_mode = data & 3; break;
case 0x5102: prgram_write_protect[0] = data & 3; break;
case 0x5103: prgram_write_protect[1] = data & 3; break;
case 0x5104:
exram_mode = data & 3;
break;
case 0x5105:
nametable_mode[0] = (data & 0x03) >> 0;
nametable_mode[1] = (data & 0x0c) >> 2;
nametable_mode[2] = (data & 0x30) >> 4;
nametable_mode[3] = (data & 0xc0) >> 6;
break;
case 0x5106:
fillmode_tile = data;
break;
case 0x5107:
fillmode_color = data & 3;
fillmode_color |= fillmode_color << 2;
fillmode_color |= fillmode_color << 4;
break;
case 0x5113:
ram_select = data & 0x04;
ram_bank = data & 0x03;
break;
case 0x5114: prg_bank[0] = data; break;
case 0x5115: prg_bank[1] = data; break;
case 0x5116: prg_bank[2] = data; break;
case 0x5117: prg_bank[3] = data | 0x80; break;
case 0x5120: chr_sprite_bank[0] = (chr_bank_hi << 8) | data; chr_active = 0; break;
case 0x5121: chr_sprite_bank[1] = (chr_bank_hi << 8) | data; chr_active = 0; break;
case 0x5122: chr_sprite_bank[2] = (chr_bank_hi << 8) | data; chr_active = 0; break;
case 0x5123: chr_sprite_bank[3] = (chr_bank_hi << 8) | data; chr_active = 0; break;
case 0x5124: chr_sprite_bank[4] = (chr_bank_hi << 8) | data; chr_active = 0; break;
case 0x5125: chr_sprite_bank[5] = (chr_bank_hi << 8) | data; chr_active = 0; break;
case 0x5126: chr_sprite_bank[6] = (chr_bank_hi << 8) | data; chr_active = 0; break;
case 0x5127: chr_sprite_bank[7] = (chr_bank_hi << 8) | data; chr_active = 0; break;
case 0x5128: chr_bg_bank[0] = (chr_bank_hi << 8) | data; chr_active = 1; break;
case 0x5129: chr_bg_bank[1] = (chr_bank_hi << 8) | data; chr_active = 1; break;
case 0x512a: chr_bg_bank[2] = (chr_bank_hi << 8) | data; chr_active = 1; break;
case 0x512b: chr_bg_bank[3] = (chr_bank_hi << 8) | data; chr_active = 1; break;
case 0x5130:
chr_bank_hi = data & 3;
break;
case 0x5200:
vs_enable = data & 0x80;
vs_side = data & 0x40;
vs_tile = data & 0x1f;
break;
case 0x5201:
vs_scroll = data;
break;
case 0x5202:
vs_bank = data;
break;
case 0x5203:
irq_line = data;
break;
case 0x5204:
irq_enable = data & 0x80;
break;
case 0x5205:
multiplicand = data;
break;
case 0x5206:
multiplier = data;
break;
}
}
unsigned chr_sprite_addr(unsigned addr) {
if(chr_mode == 0) {
auto bank = chr_sprite_bank[7];
return (bank * 0x2000) + (addr & 0x1fff);
}
if(chr_mode == 1) {
auto bank = chr_sprite_bank[(addr / 0x1000) * 4 + 3];
return (bank * 0x1000) + (addr & 0x0fff);
}
if(chr_mode == 2) {
auto bank = chr_sprite_bank[(addr / 0x0800) * 2 + 1];
return (bank * 0x0800) + (addr & 0x07ff);
}
if(chr_mode == 3) {
auto bank = chr_sprite_bank[(addr / 0x0400)];
return (bank * 0x0400) + (addr & 0x03ff);
}
}
unsigned chr_bg_addr(unsigned addr) {
addr &= 0x0fff;
if(chr_mode == 0) {
auto bank = chr_bg_bank[3];
return (bank * 0x2000) + (addr & 0x0fff);
}
if(chr_mode == 1) {
auto bank = chr_bg_bank[3];
return (bank * 0x1000) + (addr & 0x0fff);
}
if(chr_mode == 2) {
auto bank = chr_bg_bank[(addr / 0x0800) * 2 + 1];
return (bank * 0x0800) + (addr & 0x07ff);
}
if(chr_mode == 3) {
auto bank = chr_bg_bank[(addr / 0x0400)];
return (bank * 0x0400) + (addr & 0x03ff);
}
}
unsigned chr_vs_addr(unsigned addr) {
return (vs_bank * 0x1000) + (addr & 0x0ff8) + (vs_vpos & 7);
}
void blank() {
in_frame = false;
}
void scanline() {
hcounter = 0;
if(in_frame == false) {
in_frame = true;
irq_pending = false;
vcounter = 0;
} else {
if(vcounter == irq_line) irq_pending = true;
vcounter++;
}
cpu_cycle_counter = 0;
}
uint8 ciram_read(unsigned addr) {
if(vs_fetch && (hcounter & 2) == 0) return exram[vs_vpos / 8 * 32 + vs_hpos / 8];
if(vs_fetch && (hcounter & 2) != 0) return exram[vs_vpos / 32 * 8 + vs_hpos / 32 + 0x03c0];
switch(nametable_mode[(addr >> 10) & 3]) {
case 0: return ppu.ciram_read(0x0000 | (addr & 0x03ff));
case 1: return ppu.ciram_read(0x0400 | (addr & 0x03ff));
case 2: return exram_mode < 2 ? exram[addr & 0x03ff] : 0x00;
case 3: return (hcounter & 2) == 0 ? fillmode_tile : fillmode_color;
}
}
uint8 chr_read(unsigned addr) {
chr_access[0] = chr_access[1];
chr_access[1] = chr_access[2];
chr_access[2] = chr_access[3];
chr_access[3] = addr;
//detect two unused nametable fetches at end of each scanline
if((chr_access[0] & 0x2000) == 0
&& (chr_access[1] & 0x2000)
&& (chr_access[2] & 0x2000)
&& (chr_access[3] & 0x2000)) scanline();
if(in_frame == false) {
vs_fetch = false;
if(addr & 0x2000) return ciram_read(addr);
return board.chrrom.read(chr_active ? chr_bg_addr(addr) : chr_sprite_addr(addr));
}
bool bg_fetch = (hcounter < 256 || hcounter >= 320);
uint8 result = 0x00;
if((hcounter & 7) == 0) {
vs_hpos = hcounter >= 320 ? hcounter - 320 : hcounter + 16;
vs_vpos = vcounter + vs_scroll;
vs_fetch = vs_enable && bg_fetch && exram_mode < 2
&& (vs_side ? vs_hpos / 8 >= vs_tile : vs_hpos / 8 < vs_tile);
if(vs_vpos >= 240) vs_vpos -= 240;
result = ciram_read(addr);
exbank = (chr_bank_hi << 6) | (exram[addr & 0x03ff] & 0x3f);
exattr = exram[addr & 0x03ff] >> 6;
exattr |= exattr << 2;
exattr |= exattr << 4;
} else if((hcounter & 7) == 2) {
result = ciram_read(addr);
if(bg_fetch && exram_mode == 1) result = exattr;
} else {
if(vs_fetch) result = board.chrrom.read(chr_vs_addr(addr));
else if(sprite_8x16 ? bg_fetch : chr_active) result = board.chrrom.read(chr_bg_addr(addr));
else result = board.chrrom.read(chr_sprite_addr(addr));
if(bg_fetch && exram_mode == 1) result = board.chrrom.read(exbank * 0x1000 + (addr & 0x0fff));
}
hcounter += 2;
return result;
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) {
switch(nametable_mode[(addr >> 10) & 3]) {
case 0: return ppu.ciram_write(0x0000 | (addr & 0x03ff), data);
case 1: return ppu.ciram_write(0x0400 | (addr & 0x03ff), data);
case 2: exram[addr & 0x03ff] = data; break;
}
}
}
void power() {
}
void reset() {
for(auto& n : exram) n = 0xff;
prg_mode = 3;
chr_mode = 0;
for(auto& n : prgram_write_protect) n = 0;
exram_mode = 0;
for(auto& n : nametable_mode) n = 0;
fillmode_tile = 0;
fillmode_color = 0;
ram_select = 0;
ram_bank = 0;
prg_bank[0] = 0x00;
prg_bank[1] = 0x00;
prg_bank[2] = 0x00;
prg_bank[3] = 0xff;
for(auto& n : chr_sprite_bank) n = 0;
for(auto& n : chr_bg_bank) n = 0;
chr_bank_hi = 0;
vs_enable = 0;
vs_side = 0;
vs_tile = 0;
vs_scroll = 0;
vs_bank = 0;
irq_line = 0;
irq_enable = 0;
multiplicand = 0;
multiplier = 0;
cpu_cycle_counter = 0;
irq_counter = 0;
irq_pending = 0;
in_frame = 0;
vcounter = 0;
hcounter = 0;
for(auto& n : chr_access) n = 0;
chr_active = 0;
sprite_8x16 = 0;
exbank = 0;
exattr = 0;
vs_fetch = 0;
vs_vpos = 0;
vs_hpos = 0;
}
void serialize(serializer& s) {
s.array(exram);
s.integer(prg_mode);
s.integer(chr_mode);
for(auto& n : prgram_write_protect) s.integer(n);
s.integer(exram_mode);
for(auto& n : nametable_mode) s.integer(n);
s.integer(fillmode_tile);
s.integer(fillmode_color);
s.integer(ram_select);
s.integer(ram_bank);
for(auto& n : prg_bank) s.integer(n);
for(auto& n : chr_sprite_bank) s.integer(n);
for(auto& n : chr_bg_bank) s.integer(n);
s.integer(chr_bank_hi);
s.integer(vs_enable);
s.integer(vs_side);
s.integer(vs_tile);
s.integer(vs_scroll);
s.integer(vs_bank);
s.integer(irq_line);
s.integer(irq_enable);
s.integer(multiplicand);
s.integer(multiplier);
s.integer(cpu_cycle_counter);
s.integer(irq_counter);
s.integer(irq_pending);
s.integer(in_frame);
s.integer(vcounter);
s.integer(hcounter);
for(auto& n : chr_access) s.integer(n);
s.integer(chr_active);
s.integer(sprite_8x16);
s.integer(exbank);
s.integer(exattr);
s.integer(vs_fetch);
s.integer(vs_vpos);
s.integer(vs_hpos);
}
MMC5(Board& board) : Chip(board) {
revision = Revision::MMC5;
}
};

View File

@@ -1,200 +0,0 @@
struct MMC6 : Chip {
bool chr_mode;
bool prg_mode;
bool ram_enable;
uint3 bank_select;
uint8 prg_bank[2];
uint8 chr_bank[6];
bool mirror;
bool ram_readable[2];
bool ram_writable[2];
uint8 irq_latch;
uint8 irq_counter;
bool irq_enable;
unsigned irq_delay;
bool irq_line;
uint16 chr_abus;
void main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_delay) irq_delay--;
cpu.set_irq_line(irq_line);
tick();
}
}
void irq_test(unsigned addr) {
if(!(chr_abus & 0x1000) && (addr & 0x1000)) {
if(irq_delay == 0) {
if(irq_counter == 0) {
irq_counter = irq_latch;
} else if(--irq_counter == 0) {
if(irq_enable) irq_line = 1;
}
}
irq_delay = 6;
}
chr_abus = addr;
}
unsigned prg_addr(unsigned addr) const {
switch((addr >> 13) & 3) {
case 0:
if(prg_mode == 1) return (0x3e << 13) | (addr & 0x1fff);
return (prg_bank[0] << 13) | (addr & 0x1fff);
case 1:
return (prg_bank[1] << 13) | (addr & 0x1fff);
case 2:
if(prg_mode == 0) return (0x3e << 13) | (addr & 0x1fff);
return (prg_bank[0] << 13) | (addr & 0x1fff);
case 3:
return (0x3f << 13) | (addr & 0x1fff);
}
}
unsigned chr_addr(unsigned addr) const {
if(chr_mode == 0) {
if(addr <= 0x07ff) return (chr_bank[0] << 10) | (addr & 0x07ff);
if(addr <= 0x0fff) return (chr_bank[1] << 10) | (addr & 0x07ff);
if(addr <= 0x13ff) return (chr_bank[2] << 10) | (addr & 0x03ff);
if(addr <= 0x17ff) return (chr_bank[3] << 10) | (addr & 0x03ff);
if(addr <= 0x1bff) return (chr_bank[4] << 10) | (addr & 0x03ff);
if(addr <= 0x1fff) return (chr_bank[5] << 10) | (addr & 0x03ff);
} else {
if(addr <= 0x03ff) return (chr_bank[2] << 10) | (addr & 0x03ff);
if(addr <= 0x07ff) return (chr_bank[3] << 10) | (addr & 0x03ff);
if(addr <= 0x0bff) return (chr_bank[4] << 10) | (addr & 0x03ff);
if(addr <= 0x0fff) return (chr_bank[5] << 10) | (addr & 0x03ff);
if(addr <= 0x17ff) return (chr_bank[0] << 10) | (addr & 0x07ff);
if(addr <= 0x1fff) return (chr_bank[1] << 10) | (addr & 0x07ff);
}
}
unsigned ciram_addr(unsigned addr) const {
if(mirror == 0) return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
if(mirror == 1) return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
}
uint8 ram_read(unsigned addr) {
if(ram_enable == false) return cpu.mdr();
if(ram_readable[0] == false && ram_readable[1] == false) return cpu.mdr();
bool region = addr & 0x0200;
if(ram_readable[region] == false) return 0x00;
return board.prgram.read((region * 0x0200) + (addr & 0x01ff));
}
void ram_write(unsigned addr, uint8 data) {
if(ram_enable == false) return;
bool region = addr & 0x0200;
if(ram_writable[region] == false) return;
return board.prgram.write((region * 0x0200) + (addr & 0x01ff), data);
}
void reg_write(unsigned addr, uint8 data) {
switch(addr & 0xe001) {
case 0x8000:
chr_mode = data & 0x80;
prg_mode = data & 0x40;
ram_enable = data & 0x20;
bank_select = data & 0x07;
if(ram_enable == false) {
for(auto &n : ram_readable) n = false;
for(auto &n : ram_writable) n = false;
}
break;
case 0x8001:
switch(bank_select) {
case 0: chr_bank[0] = data & ~1; break;
case 1: chr_bank[1] = data & ~1; break;
case 2: chr_bank[2] = data; break;
case 3: chr_bank[3] = data; break;
case 4: chr_bank[4] = data; break;
case 5: chr_bank[5] = data; break;
case 6: prg_bank[0] = data & 0x3f; break;
case 7: prg_bank[1] = data & 0x3f; break;
}
break;
case 0xa000:
mirror = data & 0x01;
break;
case 0xa001:
if(ram_enable == false) break;
ram_readable[1] = data & 0x80;
ram_writable[1] = data & 0x40;
ram_readable[0] = data & 0x20;
ram_writable[0] = data & 0x10;
break;
case 0xc000:
irq_latch = data;
break;
case 0xc001:
irq_counter = 0;
break;
case 0xe000:
irq_enable = false;
irq_line = 0;
break;
case 0xe001:
irq_enable = true;
break;
}
}
void power() {
}
void reset() {
chr_mode = 0;
prg_mode = 0;
ram_enable = 0;
bank_select = 0;
for(auto& n : prg_bank) n = 0;
for(auto& n : chr_bank) n = 0;
mirror = 0;
for(auto& n : ram_readable) n = 0;
for(auto& n : ram_writable) n = 0;
irq_latch = 0;
irq_counter = 0;
irq_enable = 0;
irq_delay = 0;
irq_line = 0;
chr_abus = 0;
}
void serialize(serializer& s) {
s.integer(chr_mode);
s.integer(prg_mode);
s.integer(ram_enable);
s.integer(bank_select);
for(auto& n : prg_bank) s.integer(n);
for(auto& n : chr_bank) s.integer(n);
s.integer(mirror);
for(auto& n : ram_readable) s.integer(n);
for(auto& n : ram_writable) s.integer(n);
s.integer(irq_latch);
s.integer(irq_counter);
s.integer(irq_enable);
s.integer(irq_delay);
s.integer(irq_line);
s.integer(chr_abus);
}
MMC6(Board& board) : Chip(board) {
}
};

View File

@@ -1,80 +0,0 @@
struct VRC1 : Chip {
uint4 prg_bank[3];
uint4 chr_banklo[2];
bool chr_bankhi[2];
bool mirror;
unsigned prg_addr(unsigned addr) const {
unsigned bank = 0x0f;
if((addr & 0xe000) == 0x8000) bank = prg_bank[0];
if((addr & 0xe000) == 0xa000) bank = prg_bank[1];
if((addr & 0xe000) == 0xc000) bank = prg_bank[2];
return (bank * 0x2000) + (addr & 0x1fff);
}
unsigned chr_addr(unsigned addr) const {
unsigned bank = chr_banklo[(bool)(addr & 0x1000)];
bank |= chr_bankhi[(bool)(addr & 0x1000)] << 4;
return (bank * 0x1000) + (addr & 0x0fff);
}
unsigned ciram_addr(unsigned addr) const {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
}
throw;
}
void reg_write(unsigned addr, uint8 data) {
switch(addr & 0xf000) {
case 0x8000:
prg_bank[0] = data & 0x0f;
break;
case 0x9000:
chr_bankhi[1] = data & 0x04;
chr_bankhi[0] = data & 0x02;
mirror = data & 0x01;
break;
case 0xa000:
prg_bank[1] = data & 0x0f;
break;
case 0xc000:
prg_bank[2] = data & 0x0f;
break;
case 0xe000:
chr_banklo[0] = data & 0x0f;
break;
case 0xf000:
chr_banklo[1] = data & 0x0f;
break;
}
}
void power() {
}
void reset() {
for(auto& n : prg_bank) n = 0;
for(auto& n : chr_banklo) n = 0;
for(auto& n : chr_bankhi) n = 0;
mirror = 0;
}
void serialize(serializer& s) {
for(auto& n : prg_bank) s.integer(n);
for(auto& n : chr_banklo) s.integer(n);
for(auto& n : chr_bankhi) s.integer(n);
s.integer(mirror);
}
VRC1(Board& board) : Chip(board) {
}
};

View File

@@ -1,110 +0,0 @@
struct VRC2 : Chip {
uint5 prg_bank[2];
uint8 chr_bank[8];
uint2 mirror;
bool latch;
unsigned prg_addr(unsigned addr) const {
unsigned bank;
switch(addr & 0xe000) {
case 0x8000: bank = prg_bank[0]; break;
case 0xa000: bank = prg_bank[1]; break;
case 0xc000: bank = 0x1e; break;
case 0xe000: bank = 0x1f; break;
}
return (bank * 0x2000) + (addr & 0x1fff);
}
unsigned chr_addr(unsigned addr) const {
unsigned bank = chr_bank[addr / 0x0400];
return (bank * 0x0400) + (addr & 0x03ff);
}
unsigned ciram_addr(unsigned addr) const {
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;
}
uint8 ram_read(unsigned addr) {
if(board.prgram.size == 0) {
if((addr & 0xf000) == 0x6000) return cpu.mdr() | latch;
return cpu.mdr();
}
return board.prgram.read(addr & 0x1fff);
}
void ram_write(unsigned addr, uint8 data) {
if(board.prgram.size == 0) {
if((addr & 0xf000) == 0x6000) latch = data & 0x01;
return;
}
return board.prgram.write(addr & 0x1fff, data);
}
void reg_write(unsigned addr, uint8 data) {
switch(addr) {
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
prg_bank[0] = data & 0x1f;
break;
case 0x9000: case 0x9001: case 0x9002: case 0x9003:
mirror = data & 0x03;
break;
case 0xa000: case 0xa001: case 0xa002: case 0xa003:
prg_bank[1] = data & 0x1f;
break;
case 0xb000: chr_bank[0] = (chr_bank[0] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xb001: chr_bank[0] = (chr_bank[0] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xb002: chr_bank[1] = (chr_bank[1] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xb003: chr_bank[1] = (chr_bank[1] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xc000: chr_bank[2] = (chr_bank[2] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xc001: chr_bank[2] = (chr_bank[2] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xc002: chr_bank[3] = (chr_bank[3] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xc003: chr_bank[3] = (chr_bank[3] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xd000: chr_bank[4] = (chr_bank[4] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xd001: chr_bank[4] = (chr_bank[4] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xd002: chr_bank[5] = (chr_bank[5] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xd003: chr_bank[5] = (chr_bank[5] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xe000: chr_bank[6] = (chr_bank[6] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xe001: chr_bank[6] = (chr_bank[6] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xe002: chr_bank[7] = (chr_bank[7] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xe003: chr_bank[7] = (chr_bank[7] & 0x0f) | ((data & 0x0f) << 4); break;
}
}
void power() {
}
void reset() {
for(auto& n : prg_bank) n = 0;
for(auto& n : chr_bank) n = 0;
mirror = 0;
latch = 0;
}
void serialize(serializer& s) {
for(auto& n : prg_bank) s.integer(n);
for(auto& n : chr_bank) s.integer(n);
s.integer(mirror);
s.integer(latch);
}
VRC2(Board& board) : Chip(board) {
}
};

View File

@@ -1,100 +0,0 @@
struct VRC3 : Chip {
uint4 prg_bank;
bool irq_mode;
bool irq_enable;
bool irq_acknowledge;
uint16 irq_latch;
struct {
union {
uint16 w;
struct { uint8 order_lsb2(l, h); };
};
} irq_counter;
bool irq_line;
void main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_enable) {
if(irq_mode == 0) { //16-bit
if(++irq_counter.w == 0) {
irq_line = 1;
irq_enable = irq_acknowledge;
irq_counter.w = irq_latch;
}
}
if(irq_mode == 1) { //8-bit
if(++irq_counter.l == 0) {
irq_line = 1;
irq_enable = irq_acknowledge;
irq_counter.l = irq_latch;
}
}
}
cpu.set_irq_line(irq_line);
tick();
}
}
unsigned prg_addr(unsigned addr) const {
unsigned bank = (addr < 0xc000 ? (unsigned)prg_bank : 0x0f);
return (bank * 0x4000) + (addr & 0x3fff);
}
void reg_write(unsigned addr, uint8 data) {
switch(addr & 0xf000) {
case 0x8000: irq_latch = (irq_latch & 0xfff0) | ((data & 0x0f) << 0); break;
case 0x9000: irq_latch = (irq_latch & 0xff0f) | ((data & 0x0f) << 4); break;
case 0xa000: irq_latch = (irq_latch & 0xf0ff) | ((data & 0x0f) << 8); break;
case 0xb000: irq_latch = (irq_latch & 0x0fff) | ((data & 0x0f) << 12); break;
case 0xc000:
irq_mode = data & 0x04;
irq_enable = data & 0x02;
irq_acknowledge = data & 0x01;
if(irq_enable) irq_counter.w = irq_latch;
break;
case 0xd000:
irq_line = 0;
irq_enable = irq_acknowledge;
break;
case 0xf000:
prg_bank = data & 0x0f;
break;
}
}
void power() {
}
void reset() {
prg_bank = 0;
irq_mode = 0;
irq_enable = 0;
irq_acknowledge = 0;
irq_latch = 0;
irq_counter.w = 0;
irq_line = 0;
}
void serialize(serializer& s) {
s.integer(prg_bank);
s.integer(irq_mode);
s.integer(irq_enable);
s.integer(irq_acknowledge);
s.integer(irq_latch);
s.integer(irq_counter.w);
s.integer(irq_line);
}
VRC3(Board& board) : Chip(board) {
}
};

View File

@@ -1,184 +0,0 @@
struct VRC4 : Chip {
bool prg_mode;
uint5 prg_bank[2];
uint2 mirror;
uint8 chr_bank[8];
uint8 irq_latch;
bool irq_mode;
bool irq_enable;
bool irq_acknowledge;
uint8 irq_counter;
signed irq_scalar;
bool irq_line;
void main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_enable) {
if(irq_mode == 0) {
irq_scalar -= 3;
if(irq_scalar <= 0) {
irq_scalar += 341;
if(irq_counter == 0xff) {
irq_counter = irq_latch;
irq_line = 1;
} else {
irq_counter++;
}
}
}
if(irq_mode == 1) {
if(irq_counter == 0xff) {
irq_counter = irq_latch;
irq_line = 1;
} else {
irq_counter++;
}
}
}
cpu.set_irq_line(irq_line);
tick();
}
}
unsigned prg_addr(unsigned addr) const {
unsigned bank = 0, banks = board.prgrom.size / 0x2000;
switch(addr & 0xe000) {
case 0x8000: bank = prg_mode == 0 ? (unsigned)prg_bank[0] : banks - 2; break;
case 0xa000: bank = prg_bank[1]; break;
case 0xc000: bank = prg_mode == 0 ? banks - 2 : (unsigned)prg_bank[0]; break;
case 0xe000: bank = banks - 1; break;
}
return (bank * 0x2000) + (addr & 0x1fff);
}
unsigned chr_addr(unsigned addr) const {
unsigned bank = chr_bank[addr / 0x0400];
return (bank * 0x0400) + (addr & 0x03ff);
}
unsigned ciram_addr(unsigned addr) const {
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;
}
void reg_write(unsigned addr, uint8 data) {
switch(addr) {
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
prg_bank[0] = data & 0x1f;
break;
case 0x9000: case 0x9001:
mirror = data & 0x03;
break;
case 0x9002: case 0x9003:
prg_mode = data & 0x02;
break;
case 0xa000: case 0xa001: case 0xa002: case 0xa003:
prg_bank[1] = data & 0x1f;
break;
case 0xb000: chr_bank[0] = (chr_bank[0] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xb001: chr_bank[0] = (chr_bank[0] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xb002: chr_bank[1] = (chr_bank[1] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xb003: chr_bank[1] = (chr_bank[1] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xc000: chr_bank[2] = (chr_bank[2] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xc001: chr_bank[2] = (chr_bank[2] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xc002: chr_bank[3] = (chr_bank[3] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xc003: chr_bank[3] = (chr_bank[3] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xd000: chr_bank[4] = (chr_bank[4] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xd001: chr_bank[4] = (chr_bank[4] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xd002: chr_bank[5] = (chr_bank[5] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xd003: chr_bank[5] = (chr_bank[5] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xe000: chr_bank[6] = (chr_bank[6] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xe001: chr_bank[6] = (chr_bank[6] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xe002: chr_bank[7] = (chr_bank[7] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xe003: chr_bank[7] = (chr_bank[7] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xf000:
irq_latch = (irq_latch & 0xf0) | ((data & 0x0f) << 0);
break;
case 0xf001:
irq_latch = (irq_latch & 0x0f) | ((data & 0x0f) << 4);
break;
case 0xf002:
irq_mode = data & 0x04;
irq_enable = data & 0x02;
irq_acknowledge = data & 0x01;
if(irq_enable) {
irq_counter = irq_latch;
irq_scalar = 341;
}
irq_line = 0;
break;
case 0xf003:
irq_enable = irq_acknowledge;
irq_line = 0;
break;
}
}
void power() {
}
void reset() {
prg_mode = 0;
for(auto& n : prg_bank) n = 0;
mirror = 0;
for(auto& n : chr_bank) n = 0;
irq_latch = 0;
irq_mode = 0;
irq_enable = 0;
irq_acknowledge = 0;
irq_counter = 0;
irq_scalar = 0;
irq_line = 0;
}
void serialize(serializer& s) {
s.integer(prg_mode);
for(auto& n : prg_bank) s.integer(n);
s.integer(mirror);
for(auto& n : chr_bank) s.integer(n);
s.integer(irq_latch);
s.integer(irq_mode);
s.integer(irq_enable);
s.integer(irq_acknowledge);
s.integer(irq_counter);
s.integer(irq_scalar);
s.integer(irq_line);
}
VRC4(Board& board) : Chip(board) {
}
};

View File

@@ -1,321 +0,0 @@
struct VRC6 : Chip {
uint8 prg_bank[2];
uint8 chr_bank[8];
uint2 mirror;
uint8 irq_latch;
bool irq_mode;
bool irq_enable;
bool irq_acknowledge;
uint8 irq_counter;
signed irq_scalar;
bool irq_line;
struct Pulse {
bool mode;
uint3 duty;
uint4 volume;
bool enable;
uint12 frequency;
uint12 divider;
uint4 cycle;
uint4 output;
void clock() {
if(--divider == 0) {
divider = frequency + 1;
cycle++;
output = (mode == 1 || cycle > duty) ? volume : (uint4)0;
}
if(enable == false) output = 0;
}
void serialize(serializer& s) {
s.integer(mode);
s.integer(duty);
s.integer(volume);
s.integer(enable);
s.integer(frequency);
s.integer(divider);
s.integer(cycle);
s.integer(output);
}
} pulse1, pulse2;
struct Sawtooth {
uint6 rate;
bool enable;
uint12 frequency;
uint12 divider;
uint1 phase;
uint3 stage;
uint8 accumulator;
uint5 output;
void clock() {
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;
}
void serialize(serializer& s) {
s.integer(rate);
s.integer(enable);
s.integer(frequency);
s.integer(divider);
s.integer(phase);
s.integer(stage);
s.integer(accumulator);
s.integer(output);
}
} sawtooth;
void main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_enable) {
if(irq_mode == 0) {
irq_scalar -= 3;
if(irq_scalar <= 0) {
irq_scalar += 341;
if(irq_counter == 0xff) {
irq_counter = irq_latch;
irq_line = 1;
} else {
irq_counter++;
}
}
}
if(irq_mode == 1) {
if(irq_counter == 0xff) {
irq_counter = irq_latch;
irq_line = 1;
} else {
irq_counter++;
}
}
}
cpu.set_irq_line(irq_line);
pulse1.clock();
pulse2.clock();
sawtooth.clock();
signed output = (pulse1.output + pulse2.output + sawtooth.output) << 7;
apu.set_sample(-output);
tick();
}
}
unsigned prg_addr(unsigned addr) const {
if((addr & 0xc000) == 0x8000) return (prg_bank[0] << 14) | (addr & 0x3fff);
if((addr & 0xe000) == 0xc000) return (prg_bank[1] << 13) | (addr & 0x1fff);
if((addr & 0xe000) == 0xe000) return ( 0xff << 13) | (addr & 0x1fff);
}
unsigned chr_addr(unsigned addr) const {
unsigned bank = chr_bank[(addr >> 10) & 7];
return (bank << 10) | (addr & 0x03ff);
}
unsigned ciram_addr(unsigned addr) const {
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)
}
}
uint8 ram_read(unsigned addr) {
return board.prgram.data[addr & 0x1fff];
}
void ram_write(unsigned addr, uint8 data) {
board.prgram.data[addr & 0x1fff] = data;
}
void reg_write(unsigned addr, uint8 data) {
switch(addr) {
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
prg_bank[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:
prg_bank[1] = data;
break;
case 0xd000: case 0xd001: case 0xd002: case 0xd003:
chr_bank[0 + (addr & 3)] = data;
break;
case 0xe000: case 0xe001: case 0xe002: case 0xe003:
chr_bank[4 + (addr & 3)] = data;
break;
case 0xf000:
irq_latch = data;
break;
case 0xf001:
irq_mode = data & 0x04;
irq_enable = data & 0x02;
irq_acknowledge = data & 0x01;
if(irq_enable) {
irq_counter = irq_latch;
irq_scalar = 341;
}
irq_line = 0;
break;
case 0xf002:
irq_enable = irq_acknowledge;
irq_line = 0;
break;
}
}
void power() {
}
void reset() {
prg_bank[0] = 0;
prg_bank[1] = 0;
chr_bank[0] = 0;
chr_bank[1] = 0;
chr_bank[2] = 0;
chr_bank[3] = 0;
chr_bank[4] = 0;
chr_bank[5] = 0;
chr_bank[6] = 0;
chr_bank[7] = 0;
mirror = 0;
irq_latch = 0;
irq_mode = 0;
irq_enable = 0;
irq_acknowledge = 0;
irq_counter = 0;
irq_scalar = 0;
irq_line = 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;
}
void serialize(serializer& s) {
pulse1.serialize(s);
pulse2.serialize(s);
sawtooth.serialize(s);
s.array(prg_bank);
s.array(chr_bank);
s.integer(mirror);
s.integer(irq_latch);
s.integer(irq_mode);
s.integer(irq_enable);
s.integer(irq_acknowledge);
s.integer(irq_counter);
s.integer(irq_scalar);
s.integer(irq_line);
}
VRC6(Board& board) : Chip(board) {
}
};

View File

@@ -1,154 +0,0 @@
//Konami VRC7
//Yamaha YM2413 OPLL audio - not emulated
struct VRC7 : Chip {
uint8 prg_bank[3];
uint8 chr_bank[8];
uint2 mirror;
uint8 irq_latch;
bool irq_mode;
bool irq_enable;
bool irq_acknowledge;
uint8 irq_counter;
signed irq_scalar;
bool irq_line;
void main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_enable) {
if(irq_mode == 0) {
irq_scalar -= 3;
if(irq_scalar <= 0) {
irq_scalar += 341;
if(irq_counter == 0xff) {
irq_counter = irq_latch;
irq_line = 1;
} else {
irq_counter++;
}
}
}
if(irq_mode == 1) {
if(irq_counter == 0xff) {
irq_counter = irq_latch;
irq_line = 1;
} else {
irq_counter++;
}
}
}
cpu.set_irq_line(irq_line);
tick();
}
}
void reg_write(unsigned addr, uint8 data) {
switch(addr) {
case 0x8000: prg_bank[0] = data; break;
case 0x8010: prg_bank[1] = data; break;
case 0x9000: prg_bank[2] = data; break;
case 0x9010: break; //APU addr port
case 0x9030: break; //APU data port
case 0xa000: chr_bank[0] = data; break;
case 0xa010: chr_bank[1] = data; break;
case 0xb000: chr_bank[2] = data; break;
case 0xb010: chr_bank[3] = data; break;
case 0xc000: chr_bank[4] = data; break;
case 0xc010: chr_bank[5] = data; break;
case 0xd000: chr_bank[6] = data; break;
case 0xd010: chr_bank[7] = data; break;
case 0xe000: mirror = data & 0x03; break;
case 0xe010:
irq_latch = data;
break;
case 0xf000:
irq_mode = data & 0x04;
irq_enable = data & 0x02;
irq_acknowledge = data & 0x01;
if(irq_enable) {
irq_counter = irq_latch;
irq_scalar = 341;
}
irq_line = 0;
break;
case 0xf010:
irq_enable = irq_acknowledge;
irq_line = 0;
break;
}
}
unsigned prg_addr(unsigned addr) const {
unsigned bank = 0;
switch(addr & 0xe000) {
case 0x8000: bank = prg_bank[0]; break;
case 0xa000: bank = prg_bank[1]; break;
case 0xc000: bank = prg_bank[2]; break;
case 0xe000: bank = 0xff; break;
}
return (bank * 0x2000) + (addr & 0x1fff);
}
unsigned chr_addr(unsigned addr) const {
unsigned bank = chr_bank[addr / 0x0400];
return (bank * 0x0400) + (addr & 0x03ff);
}
unsigned ciram_addr(unsigned addr) const {
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)
}
}
void power() {
}
void reset() {
for(auto& n : prg_bank) n = 0;
for(auto& n : chr_bank) n = 0;
mirror = 0;
irq_latch = 0;
irq_mode = 0;
irq_enable = 0;
irq_acknowledge = 0;
irq_counter = 0;
irq_scalar = 0;
irq_line = 0;
}
void serialize(serializer& s) {
s.array(prg_bank);
s.array(chr_bank);
s.integer(mirror);
s.integer(irq_latch);
s.integer(irq_mode);
s.integer(irq_enable);
s.integer(irq_acknowledge);
s.integer(irq_counter);
s.integer(irq_scalar);
s.integer(irq_line);
}
VRC7(Board& board) : Chip(board) {
}
};

View File

@@ -1,17 +0,0 @@
struct Cheat {
struct Code {
unsigned addr;
unsigned comp;
unsigned data;
};
vector<Code> codes;
enum : unsigned { Unused = ~0u };
alwaysinline bool enable() const { return codes.size() > 0; }
void reset();
void append(unsigned addr, unsigned data);
void append(unsigned addr, unsigned comp, unsigned data);
optional<unsigned> find(unsigned addr, unsigned comp);
};
extern Cheat cheat;

View File

@@ -1,56 +0,0 @@
struct CPU : Processor::R6502, Thread {
uint8 ram[0x0800];
struct Status {
bool interrupt_pending;
bool nmi_pending;
bool nmi_line;
bool irq_line;
bool irq_apu_line;
bool rdy_line;
bool rdy_addr_valid;
uint16 rdy_addr_value;
bool oam_dma_pending;
uint8 oam_dma_page;
bool controller_latch;
unsigned controller_port0;
unsigned controller_port1;
} status;
static void Enter();
void main();
void add_clocks(unsigned clocks);
void power();
void reset();
uint8 debugger_read(uint16 addr);
uint8 ram_read(uint16 addr);
void ram_write(uint16 addr, uint8 data);
uint8 read(uint16 addr);
void write(uint16 addr, uint8 data);
void serialize(serializer&);
//timing.cpp
uint8 op_read(uint16 addr);
void op_write(uint16 addr, uint8 data);
void last_cycle();
void nmi(uint16 &vector);
void oam_dma();
void set_nmi_line(bool);
void set_irq_line(bool);
void set_irq_apu_line(bool);
void set_rdy_line(bool);
void set_rdy_addr(bool valid, uint16 value = 0);
};
extern CPU cpu;

View File

@@ -1,25 +0,0 @@
struct Input {
enum class Device : unsigned {
Joypad,
None,
};
void latch(bool data);
bool data(bool port);
void connect(bool port, Device device);
void power();
void reset();
void serialize(serializer&);
private:
Device port1;
Device port2;
bool latchdata;
unsigned counter1;
unsigned counter2;
};
extern Input input;

View File

@@ -1,8 +0,0 @@
void Input::serialize(serializer& s) {
s.integer((unsigned&)port1);
s.integer((unsigned&)port2);
s.integer(latchdata);
s.integer(counter1);
s.integer(counter2);
}

View File

@@ -1,60 +0,0 @@
#ifndef FC_HPP
namespace Famicom {
#endif
struct ID {
enum : unsigned {
System,
Famicom,
};
enum : unsigned {
Manifest,
ProgramROM,
ProgramRAM,
CharacterROM,
CharacterRAM,
};
enum : unsigned {
Port1 = 1,
Port2 = 2,
};
};
struct Interface : Emulator::Interface {
string title();
double videoFrequency();
double audioFrequency();
bool loaded();
string sha256();
unsigned group(unsigned id);
void load(unsigned id);
void save();
void load(unsigned id, const stream& stream);
void save(unsigned id, const stream& stream);
void unload();
void power();
void reset();
void run();
serializer serialize();
bool unserialize(serializer&);
void cheatSet(const lstring&);
void paletteUpdate(PaletteMode mode);
Interface();
private:
vector<Device> device;
};
extern Interface* interface;
#ifndef FC_HPP
}
#endif

View File

@@ -1,6 +0,0 @@
struct Bus {
uint8 read(uint16 addr);
void write(uint16 addr, uint8 data);
};
extern Bus bus;

View File

@@ -1,107 +0,0 @@
struct PPU : Thread {
static void Main();
void main();
void tick();
void scanline();
void frame();
void power();
void reset();
uint8 read(uint16 addr);
void write(uint16 addr, uint8 data);
uint8 ciram_read(uint16 addr);
void ciram_write(uint16 addr, uint8 data);
uint8 cgram_read(uint16 addr);
void cgram_write(uint16 addr, uint8 data);
bool raster_enable() const;
unsigned nametable_addr() const;
unsigned scrollx() const;
unsigned scrolly() const;
unsigned sprite_height() const;
uint8 chr_load(uint16 addr);
void scrollx_increment();
void scrolly_increment();
void raster_pixel();
void raster_sprite();
void raster_scanline();
void serialize(serializer&);
struct Status {
uint8 mdr;
bool field;
unsigned lx;
unsigned ly;
uint8 bus_data;
bool address_latch;
uint15 vaddr;
uint15 taddr;
uint8 xaddr;
bool nmi_hold;
bool nmi_flag;
//$2000
bool nmi_enable;
bool master_select;
bool sprite_size;
unsigned bg_addr;
unsigned sprite_addr;
unsigned vram_increment;
//$2001
uint3 emphasis;
bool sprite_enable;
bool bg_enable;
bool sprite_edge_enable;
bool bg_edge_enable;
bool grayscale;
//$2002
bool sprite_zero_hit;
bool sprite_overflow;
//$2003
uint8 oam_addr;
} status;
struct Raster {
uint16 nametable;
uint16 attribute;
uint16 tiledatalo;
uint16 tiledatahi;
unsigned oam_iterator;
unsigned oam_counter;
struct OAM {
uint8 id;
uint8 y;
uint8 tile;
uint8 attr;
uint8 x;
uint8 tiledatalo;
uint8 tiledatahi;
} oam[8], soam[8];
} raster;
uint32 buffer[256 * 262];
uint8 ciram[2048];
uint8 cgram[32];
uint8 oam[256];
};
extern PPU ppu;

View File

@@ -1,22 +0,0 @@
struct System {
void run();
void runtosave();
void runthreadtosave();
void load();
void power();
void reset();
void init();
void term();
serializer serialize();
bool unserialize(serializer&);
void serialize(serializer&);
void serialize_all(serializer&);
void serialize_init();
unsigned serialize_size;
};
extern System system;

View File

@@ -1,85 +0,0 @@
#include <fc/fc.hpp>
#include <cmath>
#define VIDEO_CPP
namespace Famicom {
Video video;
void Video::generate_palette(Emulator::Interface::PaletteMode mode) {
for(unsigned color = 0; color < (1 << 9); color++) {
if(mode == Emulator::Interface::PaletteMode::Literal) {
palette[color] = color;
} else if(mode == Emulator::Interface::PaletteMode::Channel) {
unsigned emphasis = (color >> 6) & 7;
unsigned luma = (color >> 4) & 3;
unsigned chroma = (color >> 0) & 15;
emphasis = image::normalize(emphasis, 3, 16);
luma = image::normalize(luma, 2, 16);
chroma = image::normalize(chroma, 4, 16);
palette[color] = interface->videoColor(color, 0, emphasis, luma, chroma);
} else if(mode == Emulator::Interface::PaletteMode::Standard) {
palette[color] = generate_color(color, 2.0, 0.0, 1.0, 1.0, 2.2);
} else if(mode == Emulator::Interface::PaletteMode::Emulation) {
palette[color] = generate_color(color, 2.0, 0.0, 1.0, 1.0, 1.8);
}
}
}
Video::Video() {
palette = new uint32_t[1 << 9]();
}
Video::~Video() {
delete[] palette;
}
uint32_t Video::generate_color(
unsigned n, double saturation, double hue,
double contrast, double brightness, double gamma
) {
signed color = (n & 0x0f), level = color < 0xe ? (n >> 4) & 3 : 1;
static const double black = 0.518, white = 1.962, attenuation = 0.746;
static const double levels[8] = {
0.350, 0.518, 0.962, 1.550,
1.094, 1.506, 1.962, 1.962,
};
double lo_and_hi[2] = {
levels[level + 4 * (color == 0x0)],
levels[level + 4 * (color < 0xd)],
};
double y = 0.0, i = 0.0, q = 0.0;
auto wave = [](signed p, signed color) { return (color + p + 8) % 12 < 6; };
for(signed p = 0; p < 12; p++) {
double spot = lo_and_hi[wave(p, color)];
if(((n & 0x040) && wave(p, 12))
|| ((n & 0x080) && wave(p, 4))
|| ((n & 0x100) && wave(p, 8))
) spot *= attenuation;
double v = (spot - black) / (white - black);
v = (v - 0.5) * contrast + 0.5;
v *= brightness / 12.0;
y += v;
i += v * std::cos((3.141592653 / 6.0) * (p + hue));
q += v * std::sin((3.141592653 / 6.0) * (p + hue));
}
i *= saturation;
q *= saturation;
auto gammaAdjust = [=](double f) { return f < 0.0 ? 0.0 : std::pow(f, 2.2 / gamma); };
unsigned r = 65535.0 * gammaAdjust(y + 0.946882 * i + 0.623557 * q);
unsigned g = 65535.0 * gammaAdjust(y + -0.274788 * i + -0.635691 * q);
unsigned b = 65535.0 * gammaAdjust(y + -1.108545 * i + 1.709007 * q);
return interface->videoColor(n, 0, uclamp<16>(r), uclamp<16>(g), uclamp<16>(b));
}
}

View File

@@ -1,12 +0,0 @@
struct Video {
uint32_t* palette = nullptr;
void generate_palette(Emulator::Interface::PaletteMode mode);
Video();
~Video();
private:
uint32_t generate_color(unsigned, double, double, double, double, double);
};
extern Video video;

View File

@@ -1,116 +0,0 @@
#include <gb/gb.hpp>
#define APU_CPP
namespace GameBoy {
#include "square1/square1.cpp"
#include "square2/square2.cpp"
#include "wave/wave.cpp"
#include "noise/noise.cpp"
#include "master/master.cpp"
#include "serialization.cpp"
APU apu;
void APU::Main() {
apu.main();
}
void APU::main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(sequencer_base == 0) { //512hz
if(sequencer_step == 0 || sequencer_step == 2 || sequencer_step == 4 || sequencer_step == 6) { //256hz
square1.clock_length();
square2.clock_length();
wave.clock_length();
noise.clock_length();
}
if(sequencer_step == 2 || sequencer_step == 6) { //128hz
square1.clock_sweep();
}
if(sequencer_step == 7) { //64hz
square1.clock_envelope();
square2.clock_envelope();
noise.clock_envelope();
}
sequencer_step++;
}
sequencer_base++;
square1.run();
square2.run();
wave.run();
noise.run();
master.run();
hipass(master.center, master.center_bias);
hipass(master.left, master.left_bias);
hipass(master.right, master.right_bias);
interface->audioSample(master.left, master.right);
clock += cpu.frequency;
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(scheduler.active_thread = cpu.thread);
}
}
void APU::hipass(int16& sample, int64& bias) {
bias += ((((int64)sample << 16) - (bias >> 16)) * 57593) >> 16;
sample = sclamp<16>(sample - (bias >> 32));
}
void APU::power() {
create(Main, 2 * 1024 * 1024);
for(unsigned n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;
for(auto& n : mmio_data) n = 0x00;
sequencer_base = 0;
sequencer_step = 0;
square1.power();
square2.power();
wave.power();
noise.power();
master.power();
}
uint8 APU::mmio_read(uint16 addr) {
static const uint8 table[48] = {
0x80, 0x3f, 0x00, 0xff, 0xbf, //square1
0xff, 0x3f, 0x00, 0xff, 0xbf, //square2
0x7f, 0xff, 0x9f, 0xff, 0xbf, //wave
0xff, 0xff, 0x00, 0x00, 0xbf, //noise
0x00, 0x00, 0x70, //master
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, //unmapped
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //wave pattern
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //wave pattern
};
if(addr == 0xff26) {
uint8 data = master.enable << 7;
if(square1.enable) data |= 0x01;
if(square2.enable) data |= 0x02;
if( wave.enable) data |= 0x04;
if( noise.enable) data |= 0x08;
return data | table[addr - 0xff10];
}
if(addr >= 0xff10 && addr <= 0xff3f) return mmio_data[addr - 0xff10] | table[addr - 0xff10];
return 0xff;
}
void APU::mmio_write(uint16 addr, uint8 data) {
if(addr >= 0xff10 && addr <= 0xff3f) mmio_data[addr - 0xff10] = data;
if(addr >= 0xff10 && addr <= 0xff14) return square1.write (addr - 0xff10, data);
if(addr >= 0xff15 && addr <= 0xff19) return square2.write (addr - 0xff15, data);
if(addr >= 0xff1a && addr <= 0xff1e) return wave.write (addr - 0xff1a, data);
if(addr >= 0xff1f && addr <= 0xff23) return noise.write (addr - 0xff1f, data);
if(addr >= 0xff24 && addr <= 0xff26) return master.write (addr - 0xff24, data);
if(addr >= 0xff30 && addr <= 0xff3f) return wave.write_pattern(addr - 0xff30, data);
}
}

View File

@@ -1,29 +0,0 @@
struct APU : Thread, MMIO {
#include "square1/square1.hpp"
#include "square2/square2.hpp"
#include "wave/wave.hpp"
#include "noise/noise.hpp"
#include "master/master.hpp"
uint8 mmio_data[48];
uint12 sequencer_base;
uint3 sequencer_step;
Square1 square1;
Square2 square2;
Wave wave;
Noise noise;
Master master;
static void Main();
void main();
void hipass(int16& sample, int64& bias);
void power();
uint8 mmio_read(uint16 addr);
void mmio_write(uint16 addr, uint8 data);
void serialize(serializer&);
};
extern APU apu;

View File

@@ -1,116 +0,0 @@
#ifdef APU_CPP
void APU::Master::run() {
if(enable == false) {
center = 0;
left = 0;
right = 0;
center_bias = left_bias = right_bias = 0;
return;
}
signed sample = 0;
sample += apu.square1.output;
sample += apu.square2.output;
sample += apu.wave.output;
sample += apu.noise.output;
center = (sample * 512) - 16384;
sample = 0;
if(channel1_left_enable) sample += apu.square1.output;
if(channel2_left_enable) sample += apu.square2.output;
if(channel3_left_enable) sample += apu.wave.output;
if(channel4_left_enable) sample += apu.noise.output;
sample = (sample * 512) - 16384;
sample = (sample * (left_volume + 1)) / 8;
left = sample;
sample = 0;
if(channel1_right_enable) sample += apu.square1.output;
if(channel2_right_enable) sample += apu.square2.output;
if(channel3_right_enable) sample += apu.wave.output;
if(channel4_right_enable) sample += apu.noise.output;
sample = (sample * 512) - 16384;
sample = (sample * (right_volume + 1)) / 8;
right = sample;
//reduce audio volume
center >>= 1;
left >>= 1;
right >>= 1;
}
void APU::Master::write(unsigned r, uint8 data) {
if(r == 0) { //$ff24 NR50
left_in_enable = data & 0x80;
left_volume = (data >> 4) & 7;
right_in_enable = data & 0x08;
right_volume = (data >> 0) & 7;
}
if(r == 1) { //$ff25 NR51
channel4_left_enable = data & 0x80;
channel3_left_enable = data & 0x40;
channel2_left_enable = data & 0x20;
channel1_left_enable = data & 0x10;
channel4_right_enable = data & 0x08;
channel3_right_enable = data & 0x04;
channel2_right_enable = data & 0x02;
channel1_right_enable = data & 0x01;
}
if(r == 2) { //$ff26 NR52
enable = data & 0x80;
}
}
void APU::Master::power() {
left_in_enable = 0;
left_volume = 0;
right_in_enable = 0;
right_volume = 0;
channel4_left_enable = 0;
channel3_left_enable = 0;
channel2_left_enable = 0;
channel1_left_enable = 0;
channel4_right_enable = 0;
channel3_right_enable = 0;
channel2_right_enable = 0;
channel1_right_enable = 0;
enable = 0;
center = 0;
left = 0;
right = 0;
center_bias = 0;
left_bias = 0;
right_bias = 0;
}
void APU::Master::serialize(serializer& s) {
s.integer(left_in_enable);
s.integer(left_volume);
s.integer(right_in_enable);
s.integer(right_volume);
s.integer(channel4_left_enable);
s.integer(channel3_left_enable);
s.integer(channel2_left_enable);
s.integer(channel1_left_enable);
s.integer(channel4_right_enable);
s.integer(channel3_right_enable);
s.integer(channel2_right_enable);
s.integer(channel1_right_enable);
s.integer(enable);
s.integer(center);
s.integer(left);
s.integer(right);
s.integer(center_bias);
s.integer(left_bias);
s.integer(right_bias);
}
#endif

View File

@@ -1,28 +0,0 @@
struct Master {
bool left_in_enable;
uint3 left_volume;
bool right_in_enable;
uint3 right_volume;
bool channel4_left_enable;
bool channel3_left_enable;
bool channel2_left_enable;
bool channel1_left_enable;
bool channel4_right_enable;
bool channel3_right_enable;
bool channel2_right_enable;
bool channel1_right_enable;
bool enable;
int16 center;
int16 left;
int16 right;
int64 center_bias;
int64 left_bias;
int64 right_bias;
void run();
void write(unsigned r, uint8 data);
void power();
void serialize(serializer&);
};

View File

@@ -1,107 +0,0 @@
#ifdef APU_CPP
bool APU::Noise::dac_enable() {
return (envelope_volume || envelope_direction);
}
void APU::Noise::run() {
if(period && --period == 0) {
period = divisor << frequency;
if(frequency < 14) {
bool bit = (lfsr ^ (lfsr >> 1)) & 1;
lfsr = (lfsr >> 1) ^ (bit << (narrow_lfsr ? 6 : 14));
}
}
uint4 sample = (lfsr & 1) ? (uint4)0 : volume;
if(enable == false) sample = 0;
output = sample;
}
void APU::Noise::clock_length() {
if(enable && counter) {
if(++length == 0) enable = false;
}
}
void APU::Noise::clock_envelope() {
if(enable && envelope_frequency && --envelope_period == 0) {
envelope_period = envelope_frequency;
if(envelope_direction == 0 && volume > 0) volume--;
if(envelope_direction == 1 && volume < 15) volume++;
}
}
void APU::Noise::write(unsigned r, uint8 data) {
if(r == 1) { //$ff20 NR41
length = data & 0x3f;
}
if(r == 2) { //$ff21 NR42
envelope_volume = data >> 4;
envelope_direction = data & 0x08;
envelope_frequency = data & 0x07;
if(dac_enable() == false) enable = false;
}
if(r == 3) { //$ff22 NR43
frequency = data >> 4;
narrow_lfsr = data & 0x08;
divisor = (data & 0x07) << 3;
if(divisor == 0) divisor = 4;
period = divisor << frequency;
}
if(r == 4) { //$ff34 NR44
bool initialize = data & 0x80;
counter = data & 0x40;
if(initialize) {
enable = dac_enable();
lfsr = ~0U;
envelope_period = envelope_frequency;
volume = envelope_volume;
}
}
}
void APU::Noise::power() {
enable = 0;
envelope_volume = 0;
envelope_direction = 0;
envelope_frequency = 0;
frequency = 0;
narrow_lfsr = 0;
divisor = 0;
counter = 0;
output = 0;
length = 0;
envelope_period = 0;
volume = 0;
period = 0;
lfsr = 0;
}
void APU::Noise::serialize(serializer& s) {
s.integer(enable);
s.integer(envelope_volume);
s.integer(envelope_direction);
s.integer(envelope_frequency);
s.integer(frequency);
s.integer(narrow_lfsr);
s.integer(divisor);
s.integer(counter);
s.integer(output);
s.integer(length);
s.integer(envelope_period);
s.integer(volume);
s.integer(period);
s.integer(lfsr);
}
#endif

View File

@@ -1,27 +0,0 @@
struct Noise {
bool enable;
uint4 envelope_volume;
bool envelope_direction;
uint3 envelope_frequency;
uint4 frequency;
bool narrow_lfsr;
unsigned divisor;
bool counter;
int16 output;
uint6 length;
uint3 envelope_period;
uint4 volume;
unsigned period;
uint15 lfsr;
bool dac_enable();
void run();
void clock_length();
void clock_envelope();
void write(unsigned r, uint8 data);
void power();
void serialize(serializer&);
};

View File

@@ -1,17 +0,0 @@
#ifdef APU_CPP
void APU::serialize(serializer& s) {
Thread::serialize(s);
s.array(mmio_data);
s.integer(sequencer_base);
s.integer(sequencer_step);
square1.serialize(s);
square2.serialize(s);
wave.serialize(s);
noise.serialize(s);
master.serialize(s);
}
#endif

View File

@@ -1,158 +0,0 @@
#ifdef APU_CPP
bool APU::Square1::dac_enable() {
return (envelope_volume || envelope_direction);
}
void APU::Square1::run() {
if(period && --period == 0) {
period = 2 * (2048 - frequency);
phase++;
switch(duty) {
case 0: duty_output = (phase == 6); break; //______-_
case 1: duty_output = (phase >= 6); break; //______--
case 2: duty_output = (phase >= 4); break; //____----
case 3: duty_output = (phase <= 5); break; //------__
}
}
uint4 sample = (duty_output ? volume : (uint4)0);
if(enable == false) sample = 0;
output = sample;
}
void APU::Square1::sweep(bool update) {
if(sweep_enable == false) return;
sweep_negate = sweep_direction;
unsigned delta = frequency_shadow >> sweep_shift;
signed freq = frequency_shadow + (sweep_negate ? -delta : delta);
if(freq > 2047) {
enable = false;
} else if(sweep_shift && update) {
frequency_shadow = freq;
frequency = freq & 2047;
period = 2 * (2048 - frequency);
}
}
void APU::Square1::clock_length() {
if(counter && enable) {
if(++length == 0) enable = false;
}
}
void APU::Square1::clock_sweep() {
if(enable && sweep_frequency && --sweep_period == 0) {
sweep_period = sweep_frequency;
sweep(1);
sweep(0);
}
}
void APU::Square1::clock_envelope() {
if(enable && envelope_frequency && --envelope_period == 0) {
envelope_period = envelope_frequency;
if(envelope_direction == 0 && volume > 0) volume--;
if(envelope_direction == 1 && volume < 15) volume++;
}
}
void APU::Square1::write(unsigned r, uint8 data) {
if(r == 0) { //$ff10 NR10
if(sweep_negate && sweep_direction && !(data & 0x08)) enable = false;
sweep_frequency = (data >> 4) & 7;
sweep_direction = data & 0x08;
sweep_shift = data & 0x07;
}
if(r == 1) { //$ff11 NR11
duty = data >> 6;
length = data & 0x3f;
}
if(r == 2) { //$ff12 NR12
envelope_volume = data >> 4;
envelope_direction = data & 0x08;
envelope_frequency = data & 0x07;
if(dac_enable() == false) enable = false;
}
if(r == 3) { //$ff13 NR13
frequency = (frequency & 0x0700) | data;
}
if(r == 4) { //$ff14 NR14
bool initialize = data & 0x80;
counter = data & 0x40;
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
if(initialize) {
enable = dac_enable();
period = 2 * (2048 - frequency);
envelope_period = envelope_frequency;
volume = envelope_volume;
frequency_shadow = frequency;
sweep_period = sweep_frequency;
sweep_enable = sweep_period || sweep_shift;
sweep_negate = false;
if(sweep_shift) sweep(0);
}
}
}
void APU::Square1::power() {
enable = 0;
sweep_frequency = 0;
sweep_direction = 0;
sweep_shift = 0;
sweep_negate = 0;
duty = 0;
length = 0;
envelope_volume = 0;
envelope_direction = 0;
envelope_frequency = 0;
frequency = 0;
counter = 0;
output = 0;
duty_output = 0;
phase = 0;
period = 0;
envelope_period = 0;
sweep_period = 0;
frequency_shadow = 0;
sweep_enable = 0;
volume = 0;
}
void APU::Square1::serialize(serializer& s) {
s.integer(enable);
s.integer(sweep_frequency);
s.integer(sweep_direction);
s.integer(sweep_shift);
s.integer(sweep_negate);
s.integer(duty);
s.integer(length);
s.integer(envelope_volume);
s.integer(envelope_direction);
s.integer(envelope_frequency);
s.integer(frequency);
s.integer(counter);
s.integer(output);
s.integer(duty_output);
s.integer(phase);
s.integer(period);
s.integer(envelope_period);
s.integer(sweep_period);
s.integer(frequency_shadow);
s.integer(sweep_enable);
s.integer(volume);
}
#endif

View File

@@ -1,36 +0,0 @@
struct Square1 {
bool enable;
uint3 sweep_frequency;
bool sweep_direction;
uint3 sweep_shift;
bool sweep_negate;
uint2 duty;
uint6 length;
uint4 envelope_volume;
bool envelope_direction;
uint3 envelope_frequency;
uint11 frequency;
bool counter;
int16 output;
bool duty_output;
uint3 phase;
unsigned period;
uint3 envelope_period;
uint3 sweep_period;
signed frequency_shadow;
bool sweep_enable;
uint4 volume;
bool dac_enable();
void run();
void sweep(bool update);
void clock_length();
void clock_sweep();
void clock_envelope();
void write(unsigned r, uint8 data);
void power();
void serialize(serializer&);
};

View File

@@ -1,108 +0,0 @@
#ifdef APU_CPP
bool APU::Square2::dac_enable() {
return (envelope_volume || envelope_direction);
}
void APU::Square2::run() {
if(period && --period == 0) {
period = 2 * (2048 - frequency);
phase++;
switch(duty) {
case 0: duty_output = (phase == 6); break; //______-_
case 1: duty_output = (phase >= 6); break; //______--
case 2: duty_output = (phase >= 4); break; //____----
case 3: duty_output = (phase <= 5); break; //------__
}
}
uint4 sample = (duty_output ? volume : (uint4)0);
if(enable == false) sample = 0;
output = sample;
}
void APU::Square2::clock_length() {
if(counter && enable) {
if(++length == 0) enable = false;
}
}
void APU::Square2::clock_envelope() {
if(enable && envelope_frequency && --envelope_period == 0) {
envelope_period = envelope_frequency;
if(envelope_direction == 0 && volume > 0) volume--;
if(envelope_direction == 1 && volume < 15) volume++;
}
}
void APU::Square2::write(unsigned r, uint8 data) {
if(r == 1) { //$ff16 NR21
duty = data >> 6;
length = (data & 0x3f);
}
if(r == 2) { //$ff17 NR22
envelope_volume = data >> 4;
envelope_direction = data & 0x08;
envelope_frequency = data & 0x07;
if(dac_enable() == false) enable = false;
}
if(r == 3) { //$ff18 NR23
frequency = (frequency & 0x0700) | data;
}
if(r == 4) { //$ff19 NR24
bool initialize = data & 0x80;
counter = data & 0x40;
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
if(initialize) {
enable = dac_enable();
period = 2 * (2048 - frequency);
envelope_period = envelope_frequency;
volume = envelope_volume;
}
}
}
void APU::Square2::power() {
enable = 0;
duty = 0;
length = 0;
envelope_volume = 0;
envelope_direction = 0;
envelope_frequency = 0;
frequency = 0;
counter = 0;
output = 0;
duty_output = 0;
phase = 0;
period = 0;
envelope_period = 0;
volume = 0;
}
void APU::Square2::serialize(serializer& s) {
s.integer(enable);
s.integer(duty);
s.integer(length);
s.integer(envelope_volume);
s.integer(envelope_direction);
s.integer(envelope_frequency);
s.integer(frequency);
s.integer(counter);
s.integer(output);
s.integer(duty_output);
s.integer(phase);
s.integer(period);
s.integer(envelope_period);
s.integer(volume);
}
#endif

View File

@@ -1,27 +0,0 @@
struct Square2 {
bool enable;
uint2 duty;
uint6 length;
uint4 envelope_volume;
bool envelope_direction;
uint3 envelope_frequency;
uint11 frequency;
bool counter;
int16 output;
bool duty_output;
uint3 phase;
unsigned period;
uint3 envelope_period;
uint4 volume;
bool dac_enable();
void run();
void clock_length();
void clock_envelope();
void write(unsigned r, uint8 data);
void power();
void serialize(serializer&);
};

View File

@@ -1,97 +0,0 @@
#ifdef APU_CPP
void APU::Wave::run() {
if(period && --period == 0) {
period = 1 * (2048 - frequency);
pattern_sample = pattern[++pattern_offset];
}
uint4 sample = pattern_sample >> volume_shift;
if(enable == false) sample = 0;
output = sample;
}
void APU::Wave::clock_length() {
if(enable && counter) {
if(++length == 0) enable = false;
}
}
void APU::Wave::write(unsigned r, uint8 data) {
if(r == 0) { //$ff1a NR30
dac_enable = data & 0x80;
if(dac_enable == false) enable = false;
}
if(r == 1) { //$ff1b NR31
length = data;
}
if(r == 2) { //$ff1c NR32
switch((data >> 5) & 3) {
case 0: volume_shift = 4; break; // 0%
case 1: volume_shift = 0; break; //100%
case 2: volume_shift = 1; break; // 50%
case 3: volume_shift = 2; break; // 25%
}
}
if(r == 3) { //$ff1d NR33
frequency = (frequency & 0x0700) | data;
}
if(r == 4) { //$ff1e NR34
bool initialize = data & 0x80;
counter = data & 0x40;
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
if(initialize) {
enable = dac_enable;
period = 1 * (2048 - frequency);
pattern_offset = 0;
}
}
}
void APU::Wave::write_pattern(unsigned p, uint8 data) {
p <<= 1;
pattern[p + 0] = (data >> 4) & 15;
pattern[p + 1] = (data >> 0) & 15;
}
void APU::Wave::power() {
enable = 0;
dac_enable = 0;
volume_shift = 0;
frequency = 0;
counter = 0;
LinearFeedbackShiftRegisterGenerator r;
for(auto& n : pattern) n = r() & 15;
output = 0;
length = 0;
period = 0;
pattern_offset = 0;
pattern_sample = 0;
}
void APU::Wave::serialize(serializer& s) {
s.integer(enable);
s.integer(dac_enable);
s.integer(volume_shift);
s.integer(frequency);
s.integer(counter);
s.array(pattern);
s.integer(output);
s.integer(length);
s.integer(period);
s.integer(pattern_offset);
s.integer(pattern_sample);
}
#endif

View File

@@ -1,22 +0,0 @@
struct Wave {
bool enable;
bool dac_enable;
unsigned volume_shift;
uint11 frequency;
bool counter;
uint8 pattern[32];
int16 output;
uint8 length;
unsigned period;
uint5 pattern_offset;
uint4 pattern_sample;
void run();
void clock_length();
void write(unsigned r, uint8 data);
void write_pattern(unsigned p, uint8 data);
void power();
void serialize(serializer&);
};

View File

@@ -1,5 +0,0 @@
struct MBC0 : MMIO {
uint8 mmio_read(uint16 addr);
void mmio_write(uint16 addr, uint8 data);
void power();
} mbc0;

View File

@@ -1,8 +0,0 @@
struct MBC2 : MMIO {
bool ram_enable; //$0000-1fff
uint8 rom_select; //$2000-3fff
uint8 mmio_read(uint16 addr);
void mmio_write(uint16 addr, uint8 data);
void power();
} mbc2;

View File

@@ -1,24 +0,0 @@
struct MBC3 : MMIO {
bool ram_enable; //$0000-1fff
uint8 rom_select; //$2000-3fff
uint8 ram_select; //$4000-5fff
bool rtc_latch; //$6000-7fff
bool rtc_halt;
unsigned rtc_second;
unsigned rtc_minute;
unsigned rtc_hour;
unsigned rtc_day;
bool rtc_day_carry;
unsigned rtc_latch_second;
unsigned rtc_latch_minute;
unsigned rtc_latch_hour;
unsigned rtc_latch_day;
unsigned rtc_latch_day_carry;
void second();
uint8 mmio_read(uint16 addr);
void mmio_write(uint16 addr, uint8 data);
void power();
} mbc3;

View File

@@ -1,17 +0,0 @@
struct Cheat {
struct Code {
unsigned addr;
unsigned comp;
unsigned data;
};
vector<Code> codes;
enum : unsigned { Unused = ~0u };
alwaysinline bool enable() const { return codes.size() > 0; }
void reset();
void append(unsigned addr, unsigned data);
void append(unsigned addr, unsigned comp, unsigned data);
optional<unsigned> find(unsigned addr, unsigned comp);
};
extern Cheat cheat;

View File

@@ -1,47 +0,0 @@
#ifdef CPU_CPP
void CPU::op_io() {
cycle_edge();
add_clocks(4);
}
uint8 CPU::op_read(uint16 addr) {
cycle_edge();
add_clocks(4);
if(oamdma.active && (addr < 0xff80 || addr == 0xffff)) return 0x00;
return bus.read(addr);
}
void CPU::op_write(uint16 addr, uint8 data) {
cycle_edge();
add_clocks(4);
if(oamdma.active && (addr < 0xff80 || addr == 0xffff)) return;
bus.write(addr, data);
}
void CPU::cycle_edge() {
if(r.ei) {
r.ei = false;
r.ime = 1;
}
}
//VRAM DMA source can only be ROM or RAM
uint8 CPU::dma_read(uint16 addr) {
if(addr < 0x8000) return bus.read(addr); //0000-7fff
if(addr < 0xa000) return 0x00; //8000-9fff
if(addr < 0xe000) return bus.read(addr); //a000-dfff
return 0x00; //e000-ffff
}
//VRAM DMA target is always VRAM
void CPU::dma_write(uint16 addr, uint8 data) {
addr = 0x8000 | (addr & 0x1fff); //8000-9fff
return bus.write(addr, data);
}
uint8 CPU::debugger_read(uint16 addr) {
return bus.read(addr);
}
#endif

View File

@@ -1,73 +0,0 @@
#ifndef GB_HPP
namespace GameBoy {
#endif
struct ID {
enum : unsigned {
System,
GameBoy,
SuperGameBoy,
GameBoyColor,
};
enum : unsigned {
GameBoyBootROM,
SuperGameBoyBootROM,
GameBoyColorBootROM,
Manifest,
ROM,
RAM,
};
enum : unsigned {
Device = 1,
};
};
struct Interface : Emulator::Interface {
//Super Game Boy bindings
struct Hook {
virtual void lcdScanline() {}
virtual void joypWrite(bool p15, bool p14) {}
};
Hook* hook = nullptr;
void lcdScanline();
void joypWrite(bool p15, bool p14);
string title();
double videoFrequency();
double audioFrequency();
bool loaded();
string sha256();
unsigned group(unsigned id);
void load(unsigned id);
void save();
void load(unsigned id, const stream& stream);
void save(unsigned id, const stream& stream);
void unload();
void power();
void reset();
void run();
serializer serialize();
bool unserialize(serializer&);
void cheatSet(const lstring&);
void paletteUpdate(PaletteMode mode);
Interface();
private:
vector<Device> device;
};
extern Interface* interface;
#ifndef GB_HPP
}
#endif

View File

@@ -1,32 +0,0 @@
struct Memory {
uint8_t* data;
unsigned size;
uint8_t& operator[](unsigned addr);
void allocate(unsigned size);
void copy(const uint8_t* data, unsigned size);
void free();
Memory();
~Memory();
};
struct MMIO {
virtual uint8 mmio_read(uint16 addr) = 0;
virtual void mmio_write(uint16 addr, uint8 data) = 0;
};
struct Unmapped : MMIO {
uint8 mmio_read(uint16) { return 0x00; }
void mmio_write(uint16, uint8) {}
};
struct Bus {
MMIO* mmio[65536];
uint8 read(uint16 addr);
void write(uint16 addr, uint8 data);
void power();
};
extern Unmapped unmapped;
extern Bus bus;

View File

@@ -1,16 +0,0 @@
struct Scheduler : property<Scheduler> {
enum class SynchronizeMode : unsigned { None, CPU, All } sync;
enum class ExitReason : unsigned { UnknownEvent, StepEvent, FrameEvent, SynchronizeEvent };
readonly<ExitReason> exit_reason;
cothread_t host_thread;
cothread_t active_thread;
void enter();
void exit(ExitReason);
void init();
Scheduler();
};
extern Scheduler scheduler;

View File

@@ -1,49 +0,0 @@
class Interface;
enum class Input : unsigned {
Up, Down, Left, Right, B, A, Select, Start,
};
struct System {
enum class Revision : unsigned {
GameBoy,
SuperGameBoy,
GameBoyColor,
} revision;
inline bool dmg() const { return revision == Revision::GameBoy; }
inline bool sgb() const { return revision == Revision::SuperGameBoy; }
inline bool cgb() const { return revision == Revision::GameBoyColor; }
struct BootROM {
uint8 dmg[ 256];
uint8 sgb[ 256];
uint8 cgb[2048];
} bootROM;
void run();
void runtosave();
void runthreadtosave();
void init();
void load(Revision);
void power();
unsigned clocks_executed;
//serialization.cpp
unsigned serialize_size;
serializer serialize();
bool unserialize(serializer&);
void serialize(serializer&);
void serialize_all(serializer&);
void serialize_init();
System();
};
#include <gb/interface/interface.hpp>
extern System system;

View File

@@ -1,117 +0,0 @@
#include <gb/gb.hpp>
#define VIDEO_CPP
namespace GameBoy {
Video video;
void Video::generate_palette(Emulator::Interface::PaletteMode mode) {
this->mode = mode;
if(system.dmg()) for(unsigned n = 0; n < 4; n++) palette[n] = palette_dmg(n);
if(system.sgb()) for(unsigned n = 0; n < 4; n++) palette[n] = palette_sgb(n);
if(system.cgb()) for(unsigned n = 0; n < (1 << 15); n++) palette[n] = palette_cgb(n);
}
Video::Video() {
palette = new uint32_t[1 << 15]();
}
Video::~Video() {
delete[] palette;
}
unsigned Video::palette_dmg(unsigned color) const {
if(mode == Emulator::Interface::PaletteMode::Literal) {
return color;
}
if(mode == Emulator::Interface::PaletteMode::Channel) {
unsigned L = image::normalize(color, 2, 16);
return interface->videoColor(color, 0, 0, 0, L);
}
if(mode == Emulator::Interface::PaletteMode::Standard) {
unsigned L = image::normalize(3 - color, 2, 16);
return interface->videoColor(color, 0, L, L, L);
}
if(mode == Emulator::Interface::PaletteMode::Emulation) {
unsigned R = monochrome[color][0];
unsigned G = monochrome[color][1];
unsigned B = monochrome[color][2];
return interface->videoColor(color, 0, R, G, B);
}
return 0;
}
unsigned Video::palette_sgb(unsigned color) const {
return color;
}
unsigned Video::palette_cgb(unsigned color) const {
if(mode == Emulator::Interface::PaletteMode::Literal) {
return color;
}
unsigned r = (color >> 0) & 31;
unsigned g = (color >> 5) & 31;
unsigned b = (color >> 10) & 31;
if(mode == Emulator::Interface::PaletteMode::Channel) {
r = image::normalize(r, 5, 16);
g = image::normalize(g, 5, 16);
b = image::normalize(b, 5, 16);
return interface->videoColor(color, 0, r, g, b);
}
if(mode == Emulator::Interface::PaletteMode::Standard) {
r = image::normalize(r, 5, 16);
g = image::normalize(g, 5, 16);
b = image::normalize(b, 5, 16);
return interface->videoColor(color, 0, r, g, b);
}
if(mode == Emulator::Interface::PaletteMode::Emulation) {
unsigned R = (r * 26 + g * 4 + b * 2);
unsigned G = ( g * 24 + b * 8);
unsigned B = (r * 6 + g * 4 + b * 22);
R = min(960, R);
G = min(960, G);
B = min(960, B);
R = R << 6 | R >> 4;
G = G << 6 | G >> 4;
B = B << 6 | B >> 4;
return interface->videoColor(color, 0, R, G, B);
}
return 0;
}
#define DMG_PALETTE_GREEN
//#define DMG_PALETTE_YELLOW
//#define DMG_PALETTE_WHITE
const uint16 Video::monochrome[4][3] = {
#if defined(DMG_PALETTE_GREEN)
{0xaeae, 0xd9d9, 0x2727},
{0x5858, 0xa0a0, 0x2828},
{0x2020, 0x6262, 0x2929},
{0x1a1a, 0x4545, 0x2a2a},
#elif defined(DMG_PALETTE_YELLOW)
{0xffff, 0xf7f7, 0x7b7b},
{0xb5b5, 0xaeae, 0x4a4a},
{0x6b6b, 0x6969, 0x3131},
{0x2121, 0x2020, 0x1010},
#else //DMG_PALETTE_WHITE
{0xffff, 0xffff, 0xffff},
{0xaaaa, 0xaaaa, 0xaaaa},
{0x5555, 0x5555, 0x5555},
{0x0000, 0x0000, 0x0000},
#endif
};
}

View File

@@ -1,16 +0,0 @@
struct Video {
uint32_t* palette = nullptr;
void generate_palette(Emulator::Interface::PaletteMode mode);
Video();
~Video();
private:
Emulator::Interface::PaletteMode mode;
static const uint16 monochrome[4][3];
uint32_t palette_dmg(unsigned color) const;
uint32_t palette_sgb(unsigned color) const;
uint32_t palette_cgb(unsigned color) const;
};
extern Video video;

View File

@@ -1,17 +0,0 @@
struct APU : Thread, MMIO {
#include "registers.hpp"
static void Enter();
void main();
void step(unsigned clocks);
uint8 read(uint32 addr);
void write(uint32 addr, uint8 byte);
void power();
void runsequencer();
void serialize(serializer&);
};
extern APU apu;

Some files were not shown because too many files have changed in this diff Show More