Compare commits

...

164 Commits
v093 ... v100

Author SHA1 Message Date
Tim Allen
07995c05a5 Update to v100 release.
byuu says:

higan has finally reached v100!

I feel it's important to stress right away that this is not "version
1.00", nor is it a major milestone release. Rather than arbitrary version
numbers, all of my software simply bumps version numbers by one for each
official release. As such, higan v100 is simply higan's 100th release.

That said, the primary focus of this release has been code
clean-ups. These are always somewhat dangerous in that regressions are
possible. We've tested through sixteen WIP revisions, one of which was
open to the public, to try and minimize any regressions. But all the same,
please report any regressions if you discover any.

Changelog (since v099):
FC: render during pixels 1-256 instead of 0-255 [hex_usr]
FC: rewrote controller emulation code
SFC: 8% speedup over the previous release thanks to PPU optimizations
SFC: fixed nasty DB address wrapping regression from v099
SFC: USART developer controller removed; superseded by 21fx
SFC: Super Multitap option removed from controller port 1; ports
    renamed 2-5
SFC: hidden option to experiment with 128KB VRAM (strictly for novelty)
higan: audio volume no longer divided by number of audio streams
higan: updated controller polling code to fix possible future mapping
    issues
higan: replaced nall/stream with nall/vfs for file-loading subsystem
tomoko: can now load multi-slotted games via command-line
tomoko: synchronize video removed from UI; still available in the
    settings file
tomoko, icarus: can navigate to root drive selection on Windows
all: major code cleanups and refactoring (~1MB diff against v099)

Note 1: the audio volume change means that SGB and MSU1 games won't
lose half the volume on the SNES sounds anymore. However, if one goes
overboard and drives the sound all the way to max volume with the MSU1,
clamping may occur. The obvious solution is not to drive volume that high
(it will vastly overpower the SNES audio, which usually never exceeds
25% volume.) Another option is to lower the volume in the audio settings
panel to 50%. In general, neither is likely to ever be necessary.

Note 2: the synchronize video option was hidden from the UI because it
is no longer useful. With the advent of compositors, the loss of the
complicated timing settings panel, support for the WonderSwan and its
75hz display, the need to emulate variable refresh rate behaviors in the
Game Boy, the unfortunate latency spike and audio distortion caused by
long Vsync pauses, and the arrival of adaptive sync technology ... it
no longer makes sense to present this option. However, as stated, you
can edit settings.bml to enable this option anyway if you insist and
understand the aforementioned risks.

Changelog (since v099r16 open beta):

- fixed MSU1 audio sign extension
- fixed compilation with SGB support disabled
- icarus can now navigate to root directory
- fixed compilation issues with OS X port
- (hopefully) fixed label height issue with hiro that affected icarus
  import dialog
- (mostly) fixed BS Memory, Sufami Turbo slot loading

Errata:

- forgot to remove the " - Slot A", " - Slot B" suffixes for Sufami
  Turbo slot loading
  - this means you have to navigate up one folder and then into Sufami
    Turbo/ to load games for this system
- moving WonderSwan orientation controls to the device slot is causing
  some nastiness
  - can now select orientation from the main menu, but it doesn't rotate
    the display
2016-07-08 22:04:59 +10:00
Tim Allen
13ad9644a2 Update to v099r16 release (public beta).
byuu says:

Changelog:
- hiro: BrowserDialog can navigate up to drive selection on Windows
- nall: (file,path,dir,base,prefix,suffix)name =>
  Location::(file,path,dir,base,prefix,suffix)
- higan/tomoko: rename audio filter label from "Sinc" to "IIR - Biquad"
- higan/tomoko: allow loading files via icarus on the command-line
  once again
- higan/tomoko: (begrudging) quick hack to fix presentation window focus
  on startup
- higan/audio: don't divide output audio volume by number of streams
- processor/r65816: fix a regression in (read,write)DB; fixes Taz-Mania
- fixed compilation regressions on Windows and Linux

I'm happy with where we are at with code cleanups and stability, so I'd
like to release v100. But even though I'm not assigning any special
significance to this version, we should probably test it more thoroughly
first.
2016-07-04 21:53:24 +10:00
Tim Allen
8d5cc0c35e Update to v099r15 release.
byuu says:

Changelog:
- nall::lstring -> nall::string_vector
- added IntegerBitField<type, lo, hi> -- hopefully it works correctly...
- Multitap 1-4 -> Super Multitap 2-5
- fixed SFC PPU CGRAM read regression
- huge amounts of SFC PPU IO register cleanups -- .bits really is lovely
- re-added the read/write(VRAM,OAM,CGRAM) helpers for the SFC PPU
  - but they're now optimized to the realities of the PPU (16-bit data
    sizes / no address parameter / where appropriate)
  - basically used to get the active-display overrides in a unified place;
    but also reduces duplicate code in (read,write)IO
2016-07-04 21:48:17 +10:00
Tim Allen
82293c95ae Update to v099r14 release.
byuu says:

Changelog:
- (u)int(max,ptr) abbreviations removed; use _t suffix now [didn't feel
  like they were contributing enough to be worth it]
- cleaned up nall::integer,natural,real functionality
  - toInteger, toNatural, toReal for parsing strings to numbers
  - fromInteger, fromNatural, fromReal for creating strings from numbers
  - (string,Markup::Node,SQL-based-classes)::(integer,natural,real)
    left unchanged
  - template<typename T> numeral(T value, long padding, char padchar)
    -> string for print() formatting
    - deduces integer,natural,real based on T ... cast the value if you
      want to override
    - there still exists binary,octal,hex,pointer for explicit print()
      formatting
- lstring -> string_vector [but using lstring = string_vector; is
  declared]
  - would be nice to remove the using lstring eventually ... but that'd
    probably require 10,000 lines of changes >_>
- format -> string_format [no using here; format was too ambiguous]
- using integer = Integer<sizeof(int)*8>; and using natural =
  Natural<sizeof(uint)*8>; declared
  - for consistency with boolean. These three are meant for creating
    zero-initialized values implicitly (various uses)
- R65816::io() -> idle() and SPC700::io() -> idle() [more clear; frees
  up struct IO {} io; naming]
- SFC CPU, PPU, SMP use struct IO {} io; over struct (Status,Registers) {}
  (status,registers); now
  - still some CPU::Status status values ... they didn't really fit into
    IO functionality ... will have to think about this more
- SFC CPU, PPU, SMP now use step() exclusively instead of addClocks()
  calling into step()
- SFC CPU joypad1_bits, joypad2_bits were unused; killed them
- SFC PPU CGRAM moved into PPU::Screen; since nothing else uses it
- SFC PPU OAM moved into PPU::Object; since nothing else uses it
  - the raw uint8[544] array is gone. OAM::read() constructs values from
    the OAM::Object[512] table now
  - this avoids having to determine how we want to sub-divide the two
    OAM memory sections
  - this also eliminates the OAM::synchronize() functionality
- probably more I'm forgetting

The FPS fluctuations are driving me insane. This WIP went from 128fps to
137fps. Settled on 133.5fps for the final build. But nothing I changed
should have affected performance at all. This level of fluctuation makes
it damn near impossible to know whether I'm speeding things up or slowing
things down with changes.
2016-07-01 21:50:32 +10:00
Tim Allen
67457fade4 Update to v099r13 release.
byuu says:

Changelog:
- GB core code cleanup completed
- GBA core code cleanup completed
- some more cleanup on missed processor/arm functions/variables
- fixed FC loading icarus bug
- "Load ROM File" icarus functionality restored
- minor code unification efforts all around (not perfect yet)
  - MMIO->IO
  - mmio.cpp->io.cpp
  - read,write->readIO,writeIO

It's been a very long work in progress ... starting all the way back with
v094r09, but the major part of the higan code cleanup is now completed! Of
course, it's very important to note that this is only for the basic style:

- under_score functions and variables are now camelCase
- return-type function-name() are now auto function-name() -> return-type
- Natural<T>/Integer<T> replace (u)intT_n types where possible
- signed/unsigned are now int/uint
- most of the x==true,x==false tests changed to x,!x

A lot of spot improvements to consistency, simplicity and quality have
gone in along the way, of course. But we'll probably never fully finishing
beautifying every last line of code in the entire codebase. Still,
this is a really great start. Going forward, WIP diffs should start
being smaller and of higher quality once again.

I know the joke is, "until my coding style changes again", but ... this
was way too stressful, way too time consuming, and way too risky. I'm
too old and tired now for extreme upheavel like this again. The only
major change I'm slowly mulling over would be renaming the using
Natural<T>/Integer<T> = (u)intT; shorthand to something that isn't as
easily confused with the (u)int_t types ... but we'll see. I'll definitely
continue to change small things all the time, but for the larger picture,
I need to just accept the style I have and live with it.
2016-06-29 21:10:28 +10:00
Tim Allen
7a68059f78 Update to v099r12 release.
byuu says:

Changelog:
- fixed FC AxROM / VRC7 regression
- BitField split to BooleanBitField/NaturalBitField (in preparation
  for IntegerBitField)
- BitFieldReference removed
- GB CPU cleaned up
- GB Cartridge + Mappers cleaned up
- SFC CGRAM is now emulated as uint15[256] instead of uint[512]
- sfc/ppu/memory.cpp no longer needed; removed
- purged SFC Debugger hooks for now (some of the operator[] calls were
  bypassing them anyway)

Unfortunately, for reasons that defy all semblance of logic, the CGRAM
change caused a slight speed hit. As have the last few changes. We're
now down to around 129.5fps compared to 123.fps for v099 and 134.5fps
at our peak (v099r01-r02).

I really like the style I came up with for the Game Boy mappers to settle
the purpose(ROM,RAM) vs (rom,ram)Purpose naming convention. If I ever get
around to redoing the NES mappers, that's likely the approach I'll take.
2016-06-28 20:43:47 +10:00
Tim Allen
3e807946b8 Update to v099r11 release.
byuu says:

Changelog:
- NES PPU core updated to use BitFields (absolutely massive improvement
  in code readability)
- NES APU core updated to new coding style
- NES cartridge/board and cartridge/chip updated to new coding style
- pushed NES PPU rendering one dot forward (doesn't fix King's Quest V
  yet, sadly)
- fixed SNES PPU BG tilemask for 128KiB VRAM mode (doesn't fix Yoshi's
  Island, though)

So ... I kind of went overboard with the fc/cartridge changes. This WIP
diff is 185KiB >_>
I didn't realize it was going to be as big a task as it was, but once
I started everything broke in a chain reaction, so I had to do it all
at once.

There's a massive chance we've broken a bunch of NES things. Any typos
in this WIP are going to be absolutely insidious to track down =(

But ... supposing I pulled it off, this means the Famicom core is now
fully converted to the new coding style as well. That leaves only the
GB and GBA cores. Once those are finished, then we'll finally be free
of these gigantic hellspawn diffs.
2016-06-27 23:07:57 +10:00
Tim Allen
a816998122 Update to v099r10 release.
byuu says:

Changelog:
- higan/profile/ => higan/systems/ [temporary; unless we can't think of
  a better base folder name]
- god-damn-better-have fixed the input polling bug
- re-added command-line and drag-and-drop loading
  - command-line loading can now load multiple folders at once (SGB+GB
    game; Sufami Turbo+Slot A+Slot B; etc)
  - if you load just the base cart, it'll present you with a dialog to
    optionally load slotted cart(s)
- MSU1 now goes through nall/vfs instead of directly accessing the
  filesystem
- Famicom Cartridge, PPU cores updated to newer programming style
  - there's countless opportunity for BitField and .bits() in the PPU
    ... but I'm worried about breaking things

If anyone has a working MSU1 game and can test the changes out, that'd
be appreciated. I still don't have a test ROM on my dev box.

I wouldn't worry too much about extensively testing the Famicom PPU
changes just yet ... I'm still struggling with what to name the structs
inside the classes between all of my emulators, and the BitField/.bits()
changes will be much more important to test at a later date.

The only use case left for Emulator::Interface::path(uint id) is for
21fx emulation. This peripheral loads a DLL/SO via LoadLibrary/dlopen,
which do not have any official ways to open a file in RAM. I'm
very hesitant to use the portable trick of writing the memory to a
temporary file, loading it, and deleting the temporary file once done
... it's a real waste of disk activity. I might make something like
vfs::file::isVirtual->bool,path()->string to get around this. But even
once I do, the underlying LoadLibrary/dlopen call is still going to be
direct disk access.
2016-06-26 18:54:12 +10:00
Tim Allen
3a9c7c6843 Update to v099r09 release.
byuu says:

Changelog:
- Emulator::Interface::Medium::bootable removed
- Emulator::Interface::load(bool required) argument removed
  [File::Required makes no sense on a folder]
- Super Famicom.sys now has user-configurable properties (CPU,PPU1,PPU2
  version; PPU1 VRAM size, Region override)
- old nall/property removed completely
- volatile flags supported on coprocessor RAM files now (still not in
  icarus, though)
- (hopefully) fixed SNES Multitap support (needs testing)
- fixed an OAM tiledata range clipping limit in 128KiB VRAM mode (doesn't
  fix Yoshi's Island, sadly)
- (hopefully, again) fixed the input polling bug hex_usr reported
- re-added dialog box for when File::Required files are missing
  - really cool: if you're missing a boot ROM, BIOS ROM, or IPL ROM,
    it warns you immediately
  - you don't have to select a game before seeing the error message
    anymore
- fixed cheats.bml load/save location
2016-06-25 18:53:11 +10:00
Tim Allen
f48b332c83 Update to v099r08 release.
byuu says:

Changelog:
- nall/vfs work 100% completed; even SGB games load now
- emulation cores now call load() for the base cartridges as well
- updated port/device handling; portmask is gone; device ID bug should
  be resolved now
- SNES controller port 1 multitap option was removed
- added support for 128KiB SNES PPU VRAM (for now, edit sfc/ppu/ppu.hpp
  VRAM::size=0x10000; to enable)

Overall, nall/vfs was a huge success!! We've substantially reduced
the amount of boilerplate code everywhere, while still allowing (even
easier than before) support for RAM-based game loading/saving. All of
nall/stream is dead and buried.

I am considering removing Emulator::Interface::Medium::id and/or
bootable flag. Or at least, doing something different with it. The
values for the non-bootable GB/BS/ST entries duplicate the ID that is
supposed to be unique. They are for GB/GBC and WS/WSC. Maybe I'll use
this as the hardware revision selection ID, and then gut non-bootable
options. There's really no reason for that to be there. I think at one
point I was using it to generate library tabs for non-bootable systems,
but we don't do that anymore anyway.

Emulator::Interface::load() may not need the required flag anymore ... it
doesn't really do anything right now anyway.

I have a few reasons for having the cores load the base cartridge. Most
importantly, it is going to enable a special mode for the WonderSwan /
WonderSwan Color in the future. If we ever get the IPLROMs dumped ... it's
possible to boot these systems with no games inserted to set user profile
information and such. There are also other systems that may accept being
booted without a cartridge. To reach this state, you would load a game and
then cancel the load dialog. Right now, this results in games not loading.

The second reason is this prevents nasty crashes when loading fails. So
if you're missing a required manifest, the emulator won't die a violent
death anymore. It's able to back out at any point.

The third reason is consistency: loading the base cartridge works the
same as the slot cartridges.

The fourth reason is Emulator::Interface::open(uint pathID)
values. Before, the GB, SB, GBC modes were IDs 1,2,3 respectively. This
complicated things because you had to pass the correct ID. But now
instead, Emulator::Interface::load() returns maybe<uint> that is nothing
when no game is selected, and a pathID for a valid game. And now open()
can take this ID to access this game's folder contents.

The downside, which is temporary, is that command-line loading is
currently broken. But I do intend on restoring it. In fact, I want to do
better than before and allow multi-cart booting from the command-line by
specifying the base cartridge and then slot cartridges. The idea should
be pretty simple: keep a queue of pending filenames that we fill from
the command-line and/or drag-and-drop operations on the main window,
and then empty out the queue or prompt for load dialogs from the UI
when booting a system. This also might be a bit more unorthodox compared
to the traditional emulator design of "loadGame(filename)", but ... oh
well. It's easy enough still.

The port/device changes are fun. We simplified things quite a bit. The
portmask stuff is gone entirely. While ports and devices keep IDs,
this is really just sugar-coating so UIs can use for(auto& port :
emulator->ports) and access port.id; rather than having to use for(auto
n : range(emulator->ports)) { auto& port = emulator->ports[n]; ... };
but they should otherwise generally be identical to the order they appear
in their respective ranges. Still, don't rely on that.

Input::id is gone. There was no point since we also got rid of the nasty
Input::order vector. Since I was in here, I went ahead and caved on the
pedantics and renamed Input::guid to Input::userData.

I removed the SNES controller port 1 multitap option. Basically, the only
game that uses this is N-warp Daisakusen and, no offense to d4s, it's
not really a good game anyway. It's just a quick demo to show 8-players
on the SNES. But in the UI, all it does is confuse people into wasting
time mapping a controller they're never going to use, and they're going
to wonder which port to use. If more compelling use cases for 8-players
comes about, we can reconsider this. I left all the code to support this
in place, so all you have to do is uncomment one line to enable it again.

We now have dsnes emulation! :D
If you change PPU::VRAM::size to 0x10000 (words), then you should now
have 128KiB of VRAM. Even better, it serializes the used-VRAM size,
so your save states shouldn't crash on you if you swap between the two
(though if you try this, you're nuts.)

Note that this option does break commercial software. Yoshi's Island in
particular. This game is setting A15 on some PPU register writes, but
not on others. The end result of this is things break horribly in-game.

Also, this option is causing a very tiny speed hit for obvious reasons
with the variable masking value (I'm even using size-1 for now.) Given
how niche this is, I may just leave it a compile-time constant to avoid
the overhead cost. Otherwise, if we keep the option, then it'll go into
Super Famicom.sys/manifest.bml ... I'll flesh that out in the near-future.

----

Finally, some fun for my OCD ... my monitor suddenly cut out on me
in the middle of working on this WIP, about six hours in of non-stop
work. Had to hit a bunch of ctrl+alt+fN commands (among other things)
and trying to log in headless on another TTY to do issue commands,
trying to recover the display. Finally power cycled the monitor and it
came back up. So all my typing ended up going to who knows where.

Usually this sort of thing terrifies me enough that I scrap a WIP and
start over to ensure I didn't screw anything up during the crashed screen
when hitting keys randomly.

Obviously, everything compiles and appears to work fine. And I know
it's extremely paranoid, but OCD isn't logical, so ... I'm going
to go over every line of the 100KiB r07->r08 diff looking for any
corruption/errors/whatever.

----

Review finished.

r08 diff review notes:
- fc/controller/gamepad/gamepad.cpp:
  use uint device = ID::Device::Gamepad; not id = ...;
- gb/cartridge/cartridge.hpp:
  remove redundant uint _pathID; (in Information::pathID already)
- gb/cartridge/cartridge.hpp:
  pull sha256 inside Information
- sfc/cartridge/load/cpp:
  add " - Slot (A,B)" to interface->load("Sufami Turbo"); to be more
  descriptive
- sfc/controller/gamepad/gamepad.cpp:
  use uint device = ID::Device::Gamepad; not id = ...;
- sfc/interface/interface.cpp:
  remove n variable from the Multitap device input generation loop
  (now unused)
- sfc/interface/interface.hpp:
  put struct Port above struct Device like the other classes
- ui-tomoko:
  cheats.bml is reading from/writing to mediumPaths(0) [system folder
  instead of game folder]
- ui-tomoko:
  instead of mediumPaths(1) - call emulator->metadataPathID() or something
  like that
2016-06-24 22:16:53 +10:00
Tim Allen
ccd8878d75 Update to v099r07 release.
byuu says:

Changelog:
- (hopefully) fixed BS Memory and Sufami Turbo slot loading
- ported GB, GBA, WS cores to use nall/vfs
- completely removed loadRequest, saveRequest functionality from
  Emulator::Interface and ui-tomoko
  - loadRequest(folder) is now load(folder)
- save states now use a shared Emulator::SerializerVersion string
  - whenever this is bumped, all older states will break; but this makes
    bumping state versions way easier
  - also, the version string makes it a lot easier to identify
    compatibility windows for save states
- SNES PPU now uses uint16 vram[32768] for memory accesses [hex_usr]

NOTE: Super Game Boy loading is currently broken, and I'm not entirely
sure how to fix it :/
The file loading handoff was -really- complicated, and so I'm kind of
at a loss ... so for now, don't try it.
Everything else should theoretically work, so please report any bugs
you find.

So, this is pretty much it. I'd be very curious to hear feedback from
people who objected to the old nall/stream design, whether they are
happy with the new file loading system or think it could use further
improvements.

The 16-bit VRAM turned out to be a wash on performance (roughly the same
as before. 1fps slower on Zelda 3, 1fps faster on Yoshi's Island.) The
main reason for this was because Yoshi's Island was breaking horribly
until I changed the vramRead, vramWrite functions to take uint15 instead
of uint16.

I suspect the issue is we're using uint16s in some areas now that need
to be uint15, and this game is setting the VRAM address to 0x8000+,
causing us to go out of bounds on memory accesses.

But ... I want to go ahead and do something cute for fun, and just because
we can ... and this new interface is so incredibly perfect for it!! I
want to support an SNES unit with 128KiB of VRAM. Not out of the box,
but as a fun little tweakable thing. The SNES was clearly designed to
support that, they just didn't use big enough VRAM chips, and left one
of the lines disconnected. So ... let's connect it anyway!

In the end, if we design it right, the only code difference should be
one area where we mask by 15-bits instead of by 16-bits.
2016-06-24 22:09:30 +10:00
Tim Allen
875f031182 Update to v099r06 release.
byuu says:

Changelog:
- Super Famicom core converted to use nall/vfs
  - excludes Super Game Boy; since that's invoked from inside the GB core

This was definitely the major obstacle to test nall/vfs'
applicability. Things worked out pretty great in the end.

We went from 22.0KiB (cartridge) + 18.6KiB (interface) to 24.5KiB
(cartridge) + 11.4KiB (interface). Or 40.7KiB to 36.0KiB. This removes
a very large source of indirection. Before it was: "coprocessor <=>
cartridge <=> interface" for loading and saving data, and now it's just
"coprocessor <=> cartridge". And it may make sense to eventually turn
this into just "cartridge -> coprocessor" by making each coprocessor
class handle its own markup parsing.

It's nice to have all the manifest parsing in one location (well, sans
MSU1); but it's also nice for loading/unloading to be handled by each
coprocessor itself. So I'll have to think longer about that one.

I've also started handling Interface::save() differently. Instead of
keeping track of memory IDs and filenames, and iterating through that
vector of objects ... instead I now have a system that mirrors the markup
parsing on loading, but handles saving instead. This was actually the
reason the code size savings weren't more significant, but I like this
style more. As before, it removes an extra level of indirection.

So ... next up, I need to port over the GB, then GBA, then WS
cores. These shouldn't take too long since they're all very simple with
just ROM+RAM(+RTC) right now. Then get the SGB callbacks using vfs. Then
after that, gut all the old stream stuff from nall and higan. Kill the
(load,save)Request stuff, rename the load(Gamepak)Request to something
simpler, and then we should be good.

Anyway ... these are some huge changes.
2016-06-24 22:01:03 +10:00
Tim Allen
f04d9d58f5 Update to v099r05 release.
byuu says:

Changelog:
- added nall/vfs
- converted Famicom core to use nall/vfs interface instead of nall/stream
  interface
2016-06-20 21:00:32 +10:00
Tim Allen
40abcfc4a5 Update to v099r04 release.
byuu says:

Changelog:
- lots of code cleanups to processor/r6502 (the switch.cpp file is only
  halfway done ...)
- lots of code cleanups to fc/cpu
- removed fc/input
- implemented fc/controller

hex_usr, you may not like this, but I want to keep the controller port
and expansion port interface separate, like I do with the SNES. I realize
the NES' is used more for controllers, and the SNES' more for hardware
expansions, but ... they're not compatible pinouts and you can't really
connect one to the other.

Right now, I've only implemented the controller portion. I'll have to
get to the peripheral portion later.

Also, the gamepad implementation there now may be wrong. It's based off
the Super Famicom version obviously. I'm not sure if the Famicom has
different behavior with latching $4016 writes, or not. But, it works in
Mega Man II, so it's a start.

Everyone, be sure to remap your controls, and then set port 1 -> gamepad
after loading your first Famicom game with the new WIP.
2016-06-18 16:04:32 +10:00
Tim Allen
44a8c5a2b4 Update to v099r03 release.
byuu says:

Changelog:
- finished cleaning up the SFC core to my new coding conventions
- removed sfc/controller/usart (superseded by 21fx)
- hid Synchronize Video option from the menu (still in the configuration
  file)

Pretty much the only minor detail left is some variable names in the
SA-1 core that really won't look good at all if I move to camelCase,
so I'll have to rethink how I handle those. It's probably a good area
to attempt using BitFields, to see how it impacts performance. But I'll
do that in a test branch first.

But for the most part, this should be the end of the gigantic diffs (this
one was 174KiB), at least for the SFC/WS cores. Still have the FC/GB/GBA
cores to clean up more fully. Assuming we don't spot any new regressions,
we should be ~95% out of the woods on code cleanups breaking things.
2016-06-17 23:03:54 +10:00
Tim Allen
f1a80075fa Update to v099r02 release.
byuu says:

Changelog:
- renamed sfc/ppu/sprite (OAM oam;) to sfc/ppu/object (Object obj;) [hex_usr]
- renamed sfc/ppu's memory {vram, oam, cgram} to just vram, oam, cgram
- fixed addr&=~1 regression [hex_usr]
- fixed 8bpp tiledata regression [hex_usr]
2016-06-15 21:32:17 +10:00
Tim Allen
ae5b4c3bb3 Update to v099r01 release.
byuu says:

Changelog:
- massive cleanups and optimizations on the PPU core
- ~9% speedup over v099 official

This is pretty much it for the low-hanging fruit of speeding up higan. Any
more gains from this point will be extremely hard-fought, unfortunately.
2016-06-14 20:51:54 +10:00
Tim Allen
c074c6e064 Update to v099 release.
byuu says:

Time for a new release. There are a few important emulation improvements
and a few new features; but for the most part, this release focuses on
major code refactoring, the details of which I will mostly spare you.

The major change is that, as of v099, the SNES balanced and performance
cores have been removed from higan. Basically, in addition to my five
other emulation cores, these were too much of a burden to maintain. And
they've come along as far as I was able to develop them. If you need to
use these cores, please use these two from the v098 release.

I'm very well aware that ~80% of the people using higan for SNES
emulation were using the two removed profiles. But they simply had
to go. Hopefully in the future, we can compensate for their loss by
increasing the performance of the accuracy core.

Changelog (since v098):

SFC: balanced profile removed
SFC: performance profile removed
SFC: expansion port devices can now be changed during gameplay (atlhough
    you shouldn't)
SFC: fixed bug in SharpRTC leap year calculations
SFC: emulated new research findings for the S-DD1 coprocessor
SFC: fixed CPU emulation-mode wrapping bug with pei, [dp], [dp]+y
    instructions [AWJ]
SFC: fixed Super Game Boy bug that caused the bottom tile-row to flicker
    in games
GB: added MBC1M (multi-cart) mapper; icarus can't detect these so manual
    manifests are needed for now
GB: corrected return value when HuC3 unmapped RAM is read; fixes Robopon
    [endrift]
GB: improved STAT IRQ emulation; fixes Altered Space, etc [endrift,
    gekkio]
GB: partial emulation of DMG STAT write IRQ bug; fixes Legend of Zerd,
    Road Rash, etc
nall: execute() fix, for some Linux platforms that had trouble detecting
    icarus
nall: new BitField class; which allows for simplifying flag/register
    emulation in various cores
ruby: added Windows WASAPI audio driver (experimental)
ruby: remove attempts to call glSwapIntervalEXT (fixes crashing on some
    Linux systems)
ui: timing settings panel removed
video: restored saturation, gamma, luminance settings
video: added new post-emulation sprite system; light gun cursors are
    now higher-resolution
audio: new resampler (6th-order Butterworth biquad IIR); quite a bit
    faster than the old one
audio: added optional basic reverb filter (for fun)
higan: refresh video outside cooperative threads (workaround for shoddy
    code in AMD graphics drivers)
higan: individual emulation cores no longer have unique names
higan: really substantial code refactoring; 43% reduction in binary size

Off the bat, here are the known bugs:

hiro/Windows: focus stealing bug on startup. Needs to be fixed in hiro,
    not with a cheap hack to tomoko.

higan/SFC: some of the coprocessors are saving some volatile memory to
    disk. Completely harmless, but still needs to be fixed.

ruby/WASAPI: some sound cards have a lot of issues with the current driver
    (eg FitzRoy's). We need to find a clean way to fix this before it
    can be made the default driver. Which would be a huge win because
    the latency improvements are substantial, and in exclusive mode,
    WASAPI allows G-sync to work very well.

[From the v099 WIP thread, here's the changelog since v098r19:

- GB: don't force mode 1 during force-blank; fixes v098r16 regression
  with many Game Boy games
- GB: only perform the STAT write IRQ bug during vblank, not hblank
  (still not hardware accurate, though)

-Ed.]
2016-06-11 11:13:18 +10:00
Tim Allen
50420e3dd2 Update to v098r19 release.
byuu says:

Changelog:
- added nall/bit-field.hpp
- updated all CPU cores (sans LR35902 due to some complexities) to use
  BitFields instead of bools
- updated as many CPU cores as I could to use BitFields instead of union {
  struct { uint8_t ... }; }; pairs

The speed changes are mostly a wash for this. In some instances,
I noticed a ~2-3% speedup (eg SNES emulation), and in others a 2-3%
slowdown (eg Famicom emulation.) It's within the margin of error, so
it's safe to say it has no impact.

This does give us a lot of new useful things, however:

- no more manual reconstruction of flag values from lots of left shifts
  and ORs
- no more manual deconstruction of flag values from lots of ANDs
- ability to get completely free aliases to flag groups (eg GSU can
  provide alt2, alt1 and also alt (which is alt2,alt1 combined)
- removes the need for the nasty order_lsbN macro hack (eventually will
  make higan 100% endian independent)
- saves us from insane compilers that try and do nasty things with
  alignment on union-structs
- saves us from insane compilers that try to store bit-field bits in
  reverse order
- will allow some really novel new use cases (I'm planning an
  instant-decode ARM opcode function, for instance.)
- reduces code size (we can serialize flag registers in one line instead
  of one for each flag)

However, I probably won't use it for super critical code that's constantly
reading out register values (eg PPU MMIO registers.) I think there we
would end up with a performance penalty.
2016-06-09 08:26:35 +10:00
Tim Allen
b08449215a Update to v098r18 release.
byuu says:

Changelog:
- hiro: fixed the BrowserDialog column resizing when navigating to new
  folders (prevents clipping of filenames)
  - note: this is kind of a quick-fix; but I have a good idea how to do
    the proper fix now
- nall: added BitField<T, Lo, Hi> class
  - note: not yet working on the SFC CPU class; need to go at it with
    a debugger to find out what's happening
- GB: emulated DMG/SGB STAT IRQ bug; fixes Zerd no Densetsu and Road Rash
  (won't fix anything else; don't get hopes up)
2016-06-07 21:55:03 +10:00
Tim Allen
9b452c9f5f Update to v098r17 release.
byuu says:

Changelog:
- fixed Super Game Boy regression from v096r04 with bottom tile row
  flickering
- fixed GB STAT IRQ regression from previous WIP
  - Altered Space is now playable
  - GBVideoPlayer isn't; but nobody seems to know exactly what weird
    hardware quirk that one relies on to work
- ~3-4% speed improvement in SuperFX games by eliminating function<>
  callback on register assignments
  - most noticeable in Doom in-game; least noticeable on Yoshi's Island
    title screen (darn)
- finished GSU core and SuperFX coprocessor code cleanups
- did some more work cleaning up the LR35902 core and GB CPU code

Just a fair warning: don't get your hopes up on these GB
fixes. Cliffhanger now hangs completely (har har), and none of the
other bugs are fixed. We pretty much did all this work just for Altered
Space. So, I hope you like playing Altered Space.
2016-06-06 08:10:01 +10:00
Tim Allen
3681961ca5 Update to v098r16 release.
byuu says:

Changelog:
- GNUmakefile: reverted $(call unique,) to $(strip)
- processor/r6502: removed templates; reduces object size from 146.5kb
  to 107.6kb
- processor/lr35902: removed templates; reduces object size from 386.2kb
  to 197.4kb
- processor/spc700: merged op macros for switch table declarations
- sfc/coprocessor/sa1: partial cleanups; flattened directory structure
- sfc/coprocessor/superfx: partial cleanups; flattened directory structure
- sfc/coprocessor/icd2: flattened directory structure
- gb/ppu: changed behavior of STAT IRQs

Major caveat! The GB/GBC STAT IRQ changes has a major bug in it somewhere
that's seriously breaking most games. I'm pushing the WIP anyway, because
I believe the changes to be mostly correct. I'd like to get more people
looking at these changes, and also try more heavy-handed hacking and
diff comparison logging between the previous WIP and this one.
2016-06-05 15:03:21 +10:00
Tim Allen
20ac95ee49 Update to v098r15 release.
byuu says:

Changelog:
- removed template usage from processor/spc700; cleaned up many function
  names and the switch table
  - object size: 176.8kb => 127.3kb
  - source code size: 43.5kb => 37.0kb
- fixed processor/r65816 BRK/COP vector regression [hex_usr]
- corrected HuC3 unmapped RAM read value; fixes Robopon [endrift]
- cosmetic: simplified the butterworth constant calculation
  [Wolfram|Alpha]

The SPC700 core changes took forever, about three hours of work.

Only the LR35902 and R6502 still need their template functions
removed. The point of this is that it doesn't cause any speed penalty
to do so, and it results in smaller binary sizes and faster compilation
times.
2016-06-05 14:52:43 +10:00
Tim Allen
fdc41611cf Update to v098r14 release.
byuu says:

Changelog:
- improved attenuation of biquad filter by computing butterworth Q
  coefficients correctly (instead of using the same constant)
- adding 1e-25 to each input sample into the biquad filters to try and
  prevent denormalization
- updated normalization from [0.0 to 1.0] to [-1.0 to +1.0]; volume/reverb
  happen in floating-point mode now
- good amount of work to make the base Emulator::Audio support any number
  of output channels
  - so that we don't have to do separate work on left/right channels;
    and can instead share the code for each channel
- Emulator::Interface::audioSample(int16 left, int16 right); changed to:
  - Emulator::Interface::audioSample(double* samples, uint channels);
  - samples are normalized [-1.0 to +1.0]
  - for now at least, channels will be the value given to
    Emulator::Audio::reset()
- fixed GUI crash on startup when audio driver is set to None

I'm probably going to be updating ruby to accept normalized doubles as
well; but I'm not sure if I will try and support anything other 2-channel
audio output. It'll depend on how easy it is to do so; perhaps it'll be
a per-driver setting.

The denormalization thing is fierce. If that happens, it drops the
emulator framerate from 220fps to about 20fps for Game Boy emulation. And
that happens basically whenever audio output is silent. I'm probably
also going to make a nall/denormal.hpp file at some point with
platform-specific functionality to set the CPU state to "denormals as
zero" where applicable. I'll still add the 1e-25 offset (inaudible)
as another fallback.
2016-06-01 21:23:22 +10:00
Tim Allen
839813d0f1 Update to v098r13 release.
byuu says:

Changelog:
- nall/dsp returns with new iir/biquad.hpp and resampler/cubic.hpp files
- nall/queue.hpp added (simple ring buffer ... nall/vector wouldn't
  cause too many moves with FIFO)
- audio streams now only buffer 20ms; so even if multiple audio streams
  desync, latency can never exceed 20ms
- replaced blackman windwed sinc FIR hermite audio filter with transposed
  direct form II biquadratic sixth-order IIR butterworth filter (better
  attenuation of frequencies above 20KHz, faster, no need for decimation,
  less code)
- put in experimental eight-tap echo filter (a lot better than what I
  had before, but still rather weak)
- substantial cleanups to the SuperFX GSU processor core (slightly
  faster, 479KB->100KB object file, 42.7KB->33.4KB source code size,
  way less code duplication)

We'll definitely want to test the whole SuperFX library (not many games)
just to make sure there's no regressions caused by this one.

Not sure what I want to do with audio processing effects yet. I've always
really wanted lots of fun controls to customize audio, and now finally
with this new biquad filter, I can finally start implementing real
effects. For instance, an equalizer wouldn't be too complicated anymore.

The new reverb effect is still a poor man's version. I need to find human
readable source for implementing a comb-filter properly. I'm pretty sure
I can already treat nall::queue as an all-pass filter since all that
does is phase shift (fancy audio term for "delay audio"). What's really
going to be hard is figuring out how to expose user-friendly settings for
controlling it. It looks like you need a bunch of coprime coefficients,
and I don't think casual users are going to be able to hand-enter coprime
values to get the echo effect they want. I uh ... don't even know how
to calculate coprime values dynamically right now >_> But we're going
to have to, as they are correlated to the output sampling rate.

We'll definitely want to make some audio profiles so that users can
quickly select pre-configured themes that sound nice, but expose the
underlying coefficients so that they can tweak stuff to their liking. This
isn't just about higan, this is about me trying to learn digital signal
processing, so please don't be too upset about feature creep or anything
on this.

Anyway ... I'm having some difficulties with my audio right now. When
the reverb effect is enabled, there's a bunch of static on system
reset for just a moment. But this should not be possible. nall::queue
is initializing all previous reverb sample elements to 0.0. I don't
understand where static is coming in from. Further, we have the same
issue with both the windowed sinc and the biquad filters ... a bit of
a popping sound when starting a game. Any help tracking this down would
be appreciated.

There's also one really annoying issue ... I can't seem to do reverb
or volume adjustments with normalized samples. If I say "volume *= 0.5"
in higan/audio/audio.cpp line 68, it doesn't just halve the volume, it
adds a whole bunch of distortion. This makes absolutely zero sense to
me. The sample values are between 0.0 (mute) and 1.0 (full volume) here,
so multiplying a double by 0.5 shouldn't cause distortion. So right now,
I'm doing these adjustments with less precision after denormalizing back
to int16. Anyone ever see something like that? :/
2016-06-01 08:29:36 +10:00
Tim Allen
7f3cfa17b9 Update to v098r12 release.
byuu says:

Changelog:
- higan/video: added support for Emulator::Sprite
- higan/resource: a new system for accessing embedded binary files
  inside the emulation cores; holds the sprites
- higan/sfc/superscope,justifier: re-enabled display of crosshairs
- higan/sfc/superscope: fixed turbo toggle (also shows different
  crosshair color when in turbo mode)
- higan/sfc/ppu: always outputs at 512x480 resolution now
  - causes a slight speed-hit from ~127fps to ~125fps;
  - but allows high-resolution 32x32 cursors that look way better;
  - also avoids the need to implement sprite scaling logic

Right now, the PPU code to always output at 480-height is a really gross
hack. Don't worry, I'll make that nicer before release.

Also, superscope.cpp and justifier.cpp are built around a 256x240
screen. But since we now have 512x480, we can make the cursor's movement
much smoother by doubling the resolution on both axes. The actual games
won't see any accuracy improvements when firing the light guns, but the
cursors will animate nicer so I think it's still worth it. I'll work on
that before the next release as well.

The current 32x32 cursors are nicer, but we can do better now with full
24-bit color. So feel free to submit alternatives. I'll probably reject
them, but you can always try :D

The sprites don't support alpha blending, just color keying (0x00000000
= transparent; anything else is 0xff......). We can revisit that later
if necessary.

The way I have it designed, the only files that do anything with
Emulator::Sprite at all are the superscope and justifier folders.
I didn't have to add any hooks anywhere else. Rendering the sprite is
a lot cleaner than the old code, too.
2016-05-26 21:20:15 +10:00
Tim Allen
ae5d380d06 Update to v098r11 release.
byuu says:

Changelog:
- fixed nall/path.hpp compilation issue
- fixed ruby/audio/xaudio header declaration compilation issue (again)
- cleaned up xaudio2.hpp file to match my coding syntax (12.5% of the
  file was whitespace overkill)
- added null terminator entry to nall/windows/utf8.hpp argc[] array
- nall/windows/guid.hpp uses the Windows API for generating the GUID
  - this should stop all the bug reports where two nall users were
    generating GUIDs at the exact same second
- fixed hiro/cocoa compilation issue with uint# types
- fixed major higan/sfc Super Game Boy audio latency issue
- fixed higan/sfc CPU core bug with pei, [dp], [dp]+y instructions
- major cleanups to higan/processor/r65816 core
  - merged emulation/native-mode opcodes
  - use camel-case naming on memory.hpp functions
  - simplify address masking code for memory.hpp functions
  - simplify a few opcodes themselves (avoid redundant copies, etc)
  - rename regs.* to r.* to match modern convention of other CPU cores
- removed device.order<> concept from Emulator::Interface
  - cores will now do the translation to make the job of the UI easier
- fixed plurality naming of arrays in Emulator::Interface
  - example: emulator.ports[p].devices[d].inputs[i]
  - example: vector<Medium> media
- probably more surprises

Major show-stoppers to the next official release:
- we need to work on GB core improvements: LY=153/0 case, multiple STAT
  IRQs case, GBC audio output regs, etc.
- we need to re-add software cursors for light guns (Super Scope,
  Justifier)
- after the above, we need to fix the turbo button for the Super Scope

I really have no idea how I want to implement the light guns. Ideally,
we'd want it in higan/video, so we can support the NES Zapper with the
same code. But this isn't going to be easy, because only the SNES knows
when its output is interlaced, and its resolutions can vary as
{256,512}x{224,240,448,480} which requires pixel doubling that was
hard-coded to the SNES-specific behavior, but isn't appropriate to be
exposed in higan/video.
2016-05-25 21:13:02 +10:00
Tim Allen
3ebc77c148 Update to v098r10 release.
byuu says:

Changelog:
- synchronized tomoko, loki, icarus with extensive changes to nall
  (118KiB diff)
2016-05-16 19:51:12 +10:00
Tim Allen
6ae0abe3d3 Update to v098r09 release.
byuu says:

Changelog:
- fixed major nall/vector/prepend bug
- renamed hiro/ListView to hiro/TableView
- added new hiro/ListView control which is a simplified abstraction of
  hiro/TableView
- updated higan's cheat database window and icarus' scan dialog to use
  the new ListView control
- compilation works once again on all platforms (Windows, Cocoa, GTK,
  Qt)
- the loki skeleton compiles once again (removed nall/DSP references;
  updated port/device ID names)

Small catch: need to capture layout resize events internally in Windows
to call resizeColumns. For now, just resize the icarus window to get it
to use the full window width for list view items.
2016-05-04 20:07:13 +10:00
Tim Allen
0955295475 Update to v098r08 release.
byuu says:

Changelog:
- nall/vector rewritten from scratch
- higan/audio uses nall/vector instead of raw pointers
- higan/sfc/coprocessor/sdd1 updated with new research information
- ruby/video/glx and ruby/video/glx2: fuck salt glXSwapIntervalEXT!

The big change here is definitely nall/vector. The Windows, OS X and Qt
ports won't compile until you change some first/last strings to
left/right, but GTK will compile.

I'd be really grateful if anyone could stress-test nall/vector. Pretty
much everything I do relies on this class. If we introduce a bug, the
worst case scenario is my entire SFC game dump database gets corrupted,
or the byuu.org server gets compromised. So it's really critical that we
test the hell out of this right now.

The S-DD1 changes mean you need to update your installation of icarus
again. Also, even though the Lunar FMV never really worked on the
accuracy core anyway (it didn't initialize the PPU properly), it really
won't work now that we emulate the hard-limit of 16MiB for S-DD1 games.
2016-05-02 19:57:04 +10:00
Tim Allen
7cdae5195a Update to v098r07 release.
byuu says:

Changelog:
- GB: support modeSelect and RAM for MBC1M (Momotarou Collection)
- audio: implemented native resampling support into Emulator::Stream
- audio: removed nall::DSP completely

Unfortunately, the new resampler didn't turn out quite as fast as I had
hoped. The final hermite resampling added some overhead; and I had to
bump up the kernel count to 500 from 400 to get the buzzing to go away
on my main PC. I think that's due to it running at 48000hz output
instead of 44100hz output, maybe?

Compared to Ryphecha's:
(NES) Mega Man 2: 167fps -> 166fps
(GB) Mega Man II: 224fps -> 200fps
(WSC) Riviera: 143fps -> 151fps

Odd that the WS/WSC ends up faster while the DMG/CGB ends up slower.

But this knocks 922 lines down to 146 lines. The only files left in all
of higan not written (or rewritten) by me are ruby/xaudio2.h and
libco/ppc.c
2016-04-23 17:55:59 +10:00
Tim Allen
e2ee6689a0 Update to v098r06 release.
byuu says:

Changelog:
- emulation cores now refresh video from host thread instead of
  cothreads (fix AMD crash)
- SFC: fixed another bug with leap year months in SharpRTC emulation
- SFC: cleaned up camelCase on function names for
  armdsp,epsonrtc,hitachidsp,mcc,nss,sharprtc classes
- GB: added MBC1M emulation (requires manually setting mapper=MBC1M in
  manifest.bml for now, sorry)
- audio: implemented Emulator::Audio mixer and effects processor
- audio: implemented Emulator::Stream interface
  - it is now possible to have more than two audio streams: eg SNES
    + SGB + MSU1 + Voicer-Kun (eventually)
- audio: added reverb delay + reverb level settings; exposed balance
  configuration in UI
- video: reworked palette generation to re-enable saturation, gamma,
  luminance adjustments
- higan/emulator.cpp is gone since there was nothing left in it

I know you guys are going to say the color adjust/balance/reverb stuff
is pointless. And indeed it mostly is. But I like the idea of allowing
some fun special effects and configurability that isn't system-wide.

Note: there seems to be some kind of added audio lag in the SGB
emulation now, and I don't really understand why. The code should be
effectively identical to what I had before. The only main thing is that
I'm sampling things to 48000hz instead of 32040hz before mixing. There's
no point where I'm intentionally introducing added latency though. I'm
kind of stumped, so if anyone wouldn't mind taking a look at it, it'd be
much appreciated :/

I don't have an MSU1 test ROM, but the latency issue may affect MSU1 as
well, and that would be very bad.
2016-04-22 23:35:51 +10:00
Tim Allen
55e507d5df Update to v098r05 release.
byuu says:

Changelog:
- WS/WSC: re-added support for screen rotation (code is inside WS core)
- ruby: changed sample(uint16_t left, uint16_t right) to sample(int16_t
  left, int16_t right);
  - requires casting to uint prior to shifting in each driver, but
    I felt it was misleading to use uint16_t just to avoid that
- ruby: WASAPI is now built in by default; has wareya's improvements,
  and now supports latency adjust
- tomoko: audio settings panel has new "Exclusive Mode" checkbox for
  WASAPI driver only
  - note: although the setting *does* take effect in real-time, I'd
    suggest restarting the emulator after changing it
- tomoko: audio latency can now be set to 0ms (which in reality means
  "the minimum supported by the driver")
- all: increased cothread size from 512KiB to 2MiB to see if it fixes
  bullshit AMD driver crashes
  - this appears to cause a slight speed penalty due to cache locality
    going down between threads, though
2016-04-18 20:49:45 +10:00
Tim Allen
a2d3b8ba15 Update to v098r04 release.
byuu says:

Changelog:
- SFC: fixed behavior of 21fx $21fe register when no device is connected
  (must return zero)
- SFC: reduced 21fx buffer size to 1024 bytes in both directions to
  mirror the FT232H we are using
- SFC: eliminated dsp/modulo-array.hpp [1]
- higan: implemented higan/video interface and migrated all cores to it
  [2]

[1] the echo history buffer was 8-bytes, so there was no need for it at
all here. Not sure what I was thinking. The BRR buffer was 12-bytes, and
has very weird behavior ... but there's only a single location in the
code where it actually writes to this buffer. It's much easier to just
write to the buffer three times there instead of implementing an entire
class just to abstract away two lines of code. This change actually
boosted the speed from ~124.5fps to around ~127.5fps, but that's within
the margin of error for GCC. I doubt it's actually faster this way.

The DSP core could really use a ton of work. It comes from a port of
blargg's spc_dsp to my coding style, but he was extremely fond of using
32-bit signed integers everywhere. There's a lot of opportunity to
remove red tape masking by resizing the variables to their actual state
sizes.

I really need to find where I put spc_dsp6.sfc from blargg. It's a great
test to verify if I've made any mistakes in my implementation that would
cause regressions. Don't suppose anyone has it?

[2] so again, the idea is that higan/audio and higan/video are going to
sit between the emulation cores and the user interfaces. The hope is to
output raw encoding data from the emulation cores without having to
worry about the video display format (generally 24-bit RGB) of the host
display. And also to avoid having to repeat myself with eg three
separate implementations of interframe blending, and so on.

Furthermore, the idea is that the user interface can configure its side
of the settings, and the emulation cores can configure their sides.
Thus, neither has to worry about the other end. And now we can spin off
new user interfaces much easier without having to mess with all of these
things.

Right now, I've implemented color emulation, interframe blending and
SNES horizontal color bleed. I did not implement scanlines (and
interlace effects for them) yet, but I probably will at some point.

Further, for right now, the WonderSwan/Color screen rotation is busted
and will only show games in the horizontal orientation. Obviously this
must be fixed before the next official release, but I'll want to think
about how to implement it.

Also, the SNES light gun pointers are missing for now.

Things are a bit messy right now as I've gone through several revisions
of how to handle these things, so a good house cleaning is in order once
everything is feature-complete again. I need to sit down and think
through how and where I want to handle things like light gun cursors,
LCD icons, and maybe even rasterized text messages.

And obviously ... higan/audio is still just nall::DSP's headers. I need
to revamp that whole interface. I want to make it quite powerful with
a true audio mixer so I can handle things like
SNES+SGB+MSU1+Voicer-Kun+SNES-CD (five separate audio streams at once.)

The video system has the concept of "effects" for things like color
bleed and interframe blending. I want to extend on this with useful
other effects, such as NTSC simulation, maybe bringing back my mini-HQ2x
filter, etc. I'd also like to restore the saturation/gamma/luma
adjustment sliders ... I always liked allowing people to compensate for
their displays without having to change settings system-wide. Lastly,
I've always wanted to see some audio effects. Although I doubt we'll
ever get my dream of CoreAudio-style profiles, I'd like to get some
basic equalizer settings and echo/reverb effects in there.
2016-04-12 07:29:56 +10:00
Tim Allen
1929ad47d2 Update to v098r03 release.
byuu says:

It took several hours, but I've rebuilt much of the SNES' bus memory
mapping architecture.

The new design unifies the cartridge string-based mapping
("00-3f,80-bf:8000-ffff") and internal bus.map calls. The map() function
now has an accompanying unmap() function, and instead of a fixed 256
callbacks, it'll scan to find the first available slot. unmap() will
free slots up when zero addresses reference a given slot.

The controllers and expansion port are now both entirely dynamic.
Instead of load/unload/power/reset, they only have the constructor
(power/reset/load) and destructor (unload). What this means is you can
now dynamically change even expansion port devices after the system is
loaded.

Note that this is incredibly dangerous and stupid, but ... oh well. The
whole point of this was for 21fx. There's no way to change the expansion
port device prior to loading a game, but if the 21fx isn't active, then
the reset vector hijack won't work. Now you can load a 21fx game, change
the expansion port device, and simply reset the system to active the
device.

The unification of design between controller port devices and expansion
port devices is nice, and overall this results in a reduction of code
(all of the Mapping stuff in Cartridge is gone, replaced with direct bus
mapping.) And there's always the potential to expand this system more in
the future now.

The big missing feature right now is the ability to push/pop mappings.
So if you look at how the 21fx does the reset vector, you might vomit
a little bit. But ... it works.

Also changed exit(0) to _exit(0) in the POSIX version of nall::execute.

[The _exit(0) thing is an attempt to make higan not crash when it tries
to launch icarus and it's not on $PATH. The theory is that higan forks,
then the child tries to exec icarus and fails, so it exits, all the
unique_ptrs clean up their resources and tell the X server to free
things the parent process is still using. Calling _exit() prevents
destructors from running, and seems to prevent the problem. -Ed.]
2016-04-09 20:21:18 +10:00
Tim Allen
7403e69307 Update to v098r02 release.
byuu says:

Changelog:
- SFC: fixed a regression on auto joypad polling due to missing
  parentheses
- SFC: exported new PPU::vdisp() const -> uint; function [1]
- SFC: merged PPU MMIO functions into the read/write handles (as
  I previously did for the CPU)
- higan: removed individual emulator core names (bnes, bsnes, bgb, bgba,
  bws) [2] Forgot:
- to remove /tomoko from the about dialog

[1] note that technically I was relying on the cached, per-frame
overscan setting when the CPU and light guns were polling the number of
active display scanlines per frame. This was technically incorrect as
you can change this value mid-frame and it'll kick in. I've never seen
any game toggle overscan every frame, we only know about this because
anomie tested this a long time ago. So, nothing should break, but ...
you know how the SNES is. You can't even look at the code without
something breaking, so I figured I'd mention it >_>

[2] I'll probably keep referring to the SNES core as bsnes anyway.
I don't mind if you guys use the b<system> names as shorthand. The
simplification is mostly to make the branding easier.
2016-04-09 15:20:41 +10:00
Tim Allen
19e1d89f00 Update to v098r01 release.
byuu says:

Changelog:
- SFC: balanced profile removed
- SFC: performance profile removed
- SFC: code for handling non-threaded CPU, SMP, DSP, PPU removed
- SFC: Coprocessor, Controller (and expansion port) shared Thread code
  merged to SFC::Cothread
  - Cothread here just means "Thread with CPU affinity" (couldn't think
    of a better name, sorry)
- SFC: CPU now has vector<Thread*> coprocessors, peripherals;
  - this is the beginning of work to allow expansion port devices to be
    dynamically changed at run-time
- ruby: all audio drivers default to 48000hz instead of 22050hz now if
  no frequency is assigned
  - note: the WASAPI driver can default to whatever the native frequency
    is; doesn't have to be 48000hz
- tomoko: removed the ability to change the frequency from the UI (but
  it will display the frequency used)
- tomoko: removed the timing settings panel
  - the goal is to work toward smooth video via adaptive sync
  - the model is broken by not being in control of the audio frequency
    anyway
  - it's further broken by PAL running at 50hz and WSC running at 75hz
  - it was always broken anyway by SNES interlace timing varying from
    progressive timing
- higan: audio/ stub created (for now, it's just nall/dsp/ moved here
  and included as a header)
- higan: video/ stub created
- higan/GNUmakefile: now includes build rules for essential components
  (libco, emulator, audio, video)

The audio changes are in preparation to merge wareya's awesome WASAPI
work without the need for the nall/dsp resampler.
2016-04-09 13:40:12 +10:00
Tim Allen
aff00506c5 Update to v098 hotfix release.
byuu says:

There was a minor SNES input regression spotted very shortly after
release.
2016-04-09 12:43:12 +10:00
Tim Allen
e846c83d47 Update to v098 release.
byuu says:

This release most notably adds WonderSwan and WonderSwan Color
emulation.

It is also the final release that will include the balanced and
performance profiles for bsnes.

Changelog (since v097):
- higan: added WonderSwan and WonderSwan Color emulation
- higan: simplified the coooperative-thread schedulers for all emulation
  cores
- higan: moved from native (u)int[8,16,32,64]_t types to
  Natural<T>/Integer<T> classes
- higan: major cleanups to the Makefiles; including auto-selection of
  processor cores
- loki: very barebones skeleton in place now; does absolutely nothing
  - these allow the removal of huge amounts of manual bit-twiddling with
    more readable alternatives
- FC: fixed PPU OAM reads (mask the correct bits when writing) [hex_usr]
- SFC: fixed expansion port device mapping on game load
- SFC: reworked the way SGB games were loaded -* SFC core can now be
  compiled without GB core (and thus without SGB support)
- SFC: added Super Disc expansion port device (although it's just
  a non-functional skeleton so far)
- SFC: bugfix to SharpRTC emulation regarding leap year extra day counts
  (Dai Kaijuu Monogatari II)
- SFC: major code cleanups to the CPU core and the R65816 processor base
  class
- SFC: added 21fx emulation (not the old 21fx that became MSu1; reusing
  the name for a new idea)
  - basic idea is to move the serial USART to the expansion port along
    with a reset vector hijack
- SFC: emulate reset vector pushing PC onto the stack on system soft
  reset
- GB: pass gekkio's if_ie_registers and boot_hwio-G test ROMs
- GBA: reworked all handling of MMIO functions: removed the get/set
  class functions
- nall: improved edge case return values for
  (basename,pathname,dirname,...)
- ruby: fixed ~AudioXAudio2() typo (now calls destructor on exit)
- ruby: if DirectSoundCreate fails (no sound device present), return
  false from init instead of crashing
- tomoko: added "All" option to filetype dropdown for ROM loading
  - allows loading GBC games in SGB mode
- tomoko: locate() updated to search multiple paths [2]
- tomoko: fixed some oddities when changing the audio frequency/latency
  settings
- icarus: can now work with WonderSwan and WonderSwan Color games

Note 1: 90% of the changelog for this release was related to the
WonderSwan emulation being in development. Doesn't make a lot of sense
to post about fixes since the code didn't exist publicly prior to this
release.
2016-04-09 12:38:50 +10:00
Tim Allen
06d44b4878 Update to v097r32 release.
byuu says:

Changelog:
- bsnes-accuracy emulates reset vector properly[1]
- bsnes-balanced compiles once more
- bsnes-performance compiles once more

The balanced and performance profiles are fixed for the last time. They
will be removed for v098r01.

Please test this WIP as much as you can. I intend to release v098 soon.
I know save states are a little unstable for the WS/WSC, but they work
well enough for a release. If I can't figure it out soon, I'm going to
post v098 anyway.

[1] this one's been a really long time coming, but ... one of the bugs
I found when I translated Tekkaman Blade was that my translation patch
would crash every now and again when you hit the reset button on a real
SNES, but it always worked upon power on.

Turns out that while power-on initializes the stack register to $01ff,
reset does things a little bit differently. Reset actually triggers the
reset interrupt vector after putting the CPU into emulation mode, but it
doesn't initialize the stack pointer. The net effect is that the stack
high byte is set to $01, and the low byte is left as it was. And then
the reset vector runs, which pushes the low 16-bits of the program
counter, plus the processor flags, onto the stack frame. So you can
actually tell where the game was at when the system was reset ... sort
of.

It's a really weird behavior to be sure. But here's the catch: say
you're hacking a game, and so you hook the reset vector with jsl
showMyTranslationCreditsSplashScreen, and inside this new subroutine,
you then perform whatever bytes you hijacked, and then initialize the
stack frame to go about your business drawing the screen, and when
you're done, you return via rtl.

Generally, this works fine. But if S={0100, 0101, or 0102}, then the
stack will wrap due to being in emulation mode at reset. So it will
write to {0100, 01ff, 01fe}. But now in your subroutine, you enable
native mode. So when you return from your subroutine hijack, it reads
the return address from {01ff, 0200, 0201} instead of the expected
{01ff, 0100, 0101}. Thus, you get an invalid address back, and you
"return" to the wrong location, and your program dies.

The odds of this happening depend on how the game handles S, but
generally speaking, it's a ~1:85 chance.

By emulating this behavior, I'll likely expose this bug in many ROM
hacks that do splash screen hooks like this, including my own Tekkaman
Blade translation. And it's also very possible that there are commercial
games that screw this up as well.

But, it's what the real system does. So if any crashes start happening
as of this WIP upon resetting the game, well ... it'd happen on real
hardware, too.
2016-04-03 21:17:20 +10:00
Tim Allen
25eaaa82f4 Update to v097r31 release.
byuu says:

Changelog:
- WS: fixed sprite window clipping (again)
- WS: don't set IRQ status bits of IRQ enable bits are clear
- SFC: signed/unsigned -> int/uint for DSP core
- SFC: removed eBoot
- SFC: added 21fx (not the same as the old precursor to MSU1; just
  reusing the name)

Note: XI Little doesn't seem to be fixed after all ... but the other
three are. So I guess we're at 13 bugs :( And holy shit that music when
you choose a menu option is one of the worst sounds I've ever heard in
my life >_<
2016-03-29 20:15:01 +11:00
Tim Allen
2d83300235 Update to v097r30 release.
byuu says:

Changelog:
- fixed sprite window attribute bit (Final Fantasy, Tekken Card
  Challenge, etc)
- rewrote renderer to support 2bpp color mode (Dark Eyes, Dokodemo
  Hamster, Flash Koibito-kun, etc)
2016-03-29 19:44:03 +11:00
Tim Allen
680d16561e Update to v097r29 release.
byuu says:

Changelog:
- fixed DAS instruction (Judgment Silversword score)
- fixed [VH]TMR_FREQ writes (Judgement Silversword audio after area 20)
- fixed initialization of SP (fixes seven games that were hanging on
  startup)
- added SER_STATUS and SER_DATA stubs (fixes four games that were
  hanging on startup)
- initialized IEEP data (fixes Super Robot Taisen Compact 2 series)
  - note: you'll need to delete your internal.com in WonderSwan
    (Color).sys folders
- fixed CMPS and SCAS termination condition (fixes serious bugs in four
  games)
- set read/writeCompleted flags for EEPROM status (fixes Tetsujin 28
  Gou)
- major code cleanups to SFC/R65816 and SFC/CPU
  - mostly refactored disassembler to output strings instead of using
    char* buffer
  - unrolled all the subfolders on sfc/cpu to a single directory
  - corrected casing for all of sfc/cpu and a large portion of
    processor/r65816

I kind of went overboard on the code cleanup with this WIP. Hopefully
nothing broke. Any testing one can do with the SFC accuracy core would
be greatly appreciated.

There's still an absolutely huge amount of work left to go, but I do
want to eventually refresh the entire codebase to my current coding
style, which is extremely different from stuff that's been in higan
mostly untouched since ~2006 or so. It's dangerous and fickle work, but
if I don't do it, then the code will be a jumbled mess of several
different styles.
2016-03-26 12:56:15 +11:00
Tim Allen
379ab6991f Update to v097r28 release.
byuu says:

Changelog: (all WSC unless otherwise noted)
- fixed LINECMP=0 interrupt case (fixes FF4 world map during airship
  sequence)
- improved CPU timing (fixes Magical Drop flickering and FF1 battle
  music)
- added per-frame OAM caching (fixes sprite glitchiness in Magical Drop,
  Riviera, etc.)
- added RTC emulation (fixes Dicing Knight and Judgement Silversword)
- added save state support
- added cheat code support (untested because I don't know of any cheat
  codes that exist for this system)
- icarus: can now detect games with RTC chips
- SFC: bugfix to SharpRTC emulation (Dai Kaijuu Monogatari II)
  - ( I was adding the extra leap year day to all 12 months instead of
    just February ... >_< )

Note that the RTC emulation is very incomplete. It's not really
documented at all, and the two games I've tried that use it never even
ask you to set the date/time (so they're probably just using it to count
seconds.) I'm not even sure if I've implement the level-sensitive
behavior correctly (actually, now that I think about it, I need to mask
the clear bit in INT_ACK for the level-sensitive interrupts ...)

A bit worried about the RTC alarm, because it seems like it'll fire
continuously for a full minute. Or even if you turn it off after it
fires, then that doesn't seem to be lowering the line until the next
second ticks on the RTC, so that likely needs to happen when changing
the alarm flag.

Also not sure on this RTC's weekday byte. On the SharpRTC, it actually
computes this for you. Because it's not at all an easy thing to
calculate yourself in 65816 or V30MZ assembler. About 40 lines of code
to do it in C. For now, I'm requiring the program to calculate the value
itself.

Also note that there's some gibberish tiles in Judgement Silversword,
sadly. Not sure what's up there, but the game's still fully playable at
least.

Finally, no surprise: Beat-Mania doesn't run :P
2016-03-25 17:19:08 +11:00
Tim Allen
d3413db04a Update to v097r27 release.
byuu says:

Absolutely major improvements to the WS/C emulation today.

Changelog: (all WS/C related)
- fixed channel 3 sweep pitch adjustment
- fixed channel 3 sweep value sign extension
- removed errant channel 5 speed setting (not what's really going on)
- fixed sign extension on channel 5 samples
- improved DAC mixing of all five audio channels
- fixed r26 regression with PPU timing loop
- fixed sprite windowing behavior (sprite attribute flag is window mode;
  not window enable)
- added per-scanline register latching to the PPU
- IRQs should terminate HLT even when the IRQ enable register bits are
  clear
- fixed PALMONO reads
- added blur emulation
- added color emulation (based on GBA, so it heavily desaturates colors;
  not entirely correct, but it helps a lot)
- no longer decimating audio to 24KHz; running at full 3.072MHz through
  the windowed sinc filter [1]
- cleaned up PPU portRead / portWrite functions significantly
- emulated a weird quirk as mentioned by trap15 regarding timer
  frequency writes enabling said timers [2]
- emulated LCD_CTRL sleep bit; screen can now be disabled (always draws
  black in this case for now)
- improved OAM caching; but it's still disabled because it causes huge
  amounts of sprite glitches (unsure why)
- fixed rendering of sprites that wrap around the screen edges back to
  the top/left of the display
- emulated keypad interrupts
- icarus: detect orientation bit in game header
- higan: use orientation setting in manifest to set default screen
  rotation

[1] the 24KHz -> 3.072MHz sound change is huge. Sound is substantially
improved over the previous WIPs. It does come at a pretty major speed
penalty, though. This is the highest frequency of any system in higan
running through an incredibly (amazing, yet) demanding sinc resampler.
Frame rate dropped from around 240fps to 150fps with the sinc filter on.
If you choose a different audio filter, you'll get most of that speed
back, but audio will sound worse again.

[2] we aren't sure if this is correct hardware behavior or not. It seems
to very slightly help Magical Drop, but not much.

The blur emulation is brutal. It's absolutely required for Riviera's
translucency simulation of selected menu items, but it causes serious
headaches due to the WS's ~75hz refresh rate running on ~60hz monitors
without vsync. It's probably best to leave it off and just deal with the
awful flickering on Riviera's menu options.

Overall, WS/C emulation is starting to get quite usable indeed. Couple
of major bugs that I'd really like to get fixed before releasing it,
though. But they're getting harder and harder to fix ...

Major Bugs:
- Final Fantasy battle background music is absent. Sound effects still
  work. Very weird.
- Final Fantasy IV scrolling during airship flight opening sequence is
  horribly broken. Scrolls one screen at a time.
- Magical Drop flickers like crazy in-game. Basically unplayable like
  this.
- Star Hearts character names don't appear in the smaller dialog box
  that pops up.

Minor Bugs:
- Occasional flickering during Riviera opening scenes.
- One-frame flicker of Leda's sprite at the start of the first stage.
2016-03-19 18:35:25 +11:00
Tim Allen
a7f7985581 Update to v097r26 release.
byuu says:

Changelog:
- WS: fixed 8-bit sign-extended imul (fixes Star Hearts completely,
  Final Fantasy world map)
- WS: fixed rcl/rcr carry shifting (fixes Crazy Climber, others)
- WS: added sound DMA emulation (Star Hearts rain sound for one example)
- WS: added OAM caching, but it's forced every line for now because
  otherwise there are too many sprite glitches
- WS: use headphoneEnable bit instead of speakerEnable bit (fixes muted
  audio in games)
- WS: various code cleanups (I/O mapping, audio channel naming, etc)

The hypervoice channel doesn't sound all that great just yet. But I'm
not sure how it's supposed to sound. I need a better example of some
more complex music.

What's left are some unknown register status bits (especially in the
sound area), keypad interrupts, RTC emulation, CPU prefetch emulation.
And then it's all just bugs. Lots and lots of bugs that need to be
fixed.

EDIT: oops, bad typo in the code.

ws/ppu/ppu.cpp line 20: change range(256) to range(224).

Also, delete the r.speed stuff from channel5.cpp to make the rain sound
a lot better in Star Hearts. Apparently that's outdated and not what the
bits really do.
2016-03-17 22:28:15 +11:00
Tim Allen
b586471562 Update to v097r25 release.
byuu says:

Changelog:
- WS: added HblankTimer and VblankTimer IRQs; although they don't appear
  to have any effect on any games that use them :/
- WS: added sound emulation; works perfectly in some games (eg Riviera);
  is completely silent in most games (eg GunPey)

The sound emulation only partially supports the hypervoice (headphone
only) channel. I need to implement the SDMA before it'll actually do
anything useful. I'm a bit confused about how exactly things work. It
looks like the speaker volume shift and clamp only applies to speaker
mode and not headphone mode, which is very weird. Then there's the
software possibility of muting the headphones and/or the speaker.
Preferably, I want to leave the emulator always in headphone mode for
the extra audio channel. If there are games that force-mute the
headphones, but not speakers, then I may need to force headphones back
on but with the hypervoice channel disabled. I guess we'll see how
things go.

Rough guess is probably that the channels default to enabled after the
IPLROM, and games aren't bothering to manually enable them or something.
2016-03-14 22:03:32 +11:00
Tim Allen
c33065fbd1 Update to v097r24 release.
byuu says:

Changelog:
- WS: fixed bug when IRQs triggered during a rep string instruction
- WS: added sprite attribute caching (per-scanline); absolutely massive
  speed-up
- WS: emulated limit of 32 sprites per scanline
- WS: emulated the extended PPU register bit behavior based on the
  DISP_CTRL tile bit-depth setting
- WS: added "Rotate" key binding; can be used to flip the WS display
  between horizontal and vertical in real-time

The prefix emulation may not be 100% hardware-accurate, but the edge
cases should be extreme enough to not come up in the WS library. No way
to get the emulation 100% down without intensive hardware testing.
trap15 pointed me at a workflow diagram for it, but that diagram is
impossible without a magic internal stack frame that grows with every
IRQ, and can thus grow infinitely large.

The rotation thing isn't exactly the most friendly set-up, but oh well.
I'll see about adding a default rotation setting to manifests, so that
games like GunPey can start in the correct orientation. After that, if
the LCD orientation icon turns out to be reliable, then I'll start using
that. But if there are cases where it's not reliable, then I'll leave it
to manual button presses.

Speaking of icons, I'll need a set of icons to render on the screen.
Going to put them to the top right on vertical orientation, and on the
bottom left for horizontal orientation. Just outside of the video
output, of course.

Overall, WS is getting pretty far along, but still some major bugs in
various games. I really need sound emulation, though. Nobody's going to
use this at all without that.
2016-03-13 11:22:15 +11:00
Tim Allen
79e7e6ab9e Update to v097r23 release.
byuu says:

Changelog:
- emulated SuperDisc $21e1 basic interface (NEC 4-bit MCU); all hardware
  tests pass now (but they don't test much)
- WS/V30MZ: fixed inc/dec reg flag calculation
- WS/V30MZ: fixed lds/les instructions

WS/C compatibility should be way up now. SuperDisc BIOS passes all tests
now (but they only test for the presence of the interface, nothing
more.)
2016-03-13 11:22:14 +11:00
Tim Allen
3d3ac8c1db Update to v097r22 release.
byuu says:

Changelog:
- WS: fixed lods, scas instructions
- WS: implemented missing GRP4 instructions
- WS: fixed transparency for screen one
- WSC: added color-mode PPU rendering
- WS+WSC: added packed pixel mode support
- WS+WSC: added dummy sound register reads/writes
- SFC: added threading to SuperDisc (it's hanging for right now; need to
  clear IRQ on $21e2 writes)

SuperDisc Timer and Sound Check were failing before due to not turning
off IRQs on $21e4 clear, so I'm happy that's fixed now.

Riviera starts now, and displays the first intro screen before crashing.
Huge, huge amounts of corrupted graphics, though. This game's really
making me work for it :(

No color games seem fully playable yet, but a lot of monochrome and
color games are now at least showing more intro screen graphics before
dying.

This build defaults to horizontal orientation, but I left the inputs
bound to vertical orientation. Whoops. I still need to implement
a screen flip key binding.
2016-03-13 11:22:14 +11:00
Tim Allen
b0d2f5033e Update to v097r21 release.
byuu says:

Changelog:
- icarus: WS/C detects RAM type/size heuristically now
- icarus: WS/C uses ram type=$type instead of $type
- WS: use back color instead of white for backdrop
- WS: fixed sprite count limit; removes all the garbled sprites from
  GunPey
- WS: hopefully fixed sprite priority with screen 2
- WS: implemented keypad polling; GunPey is now fully playable
- SNES: added Super Disc expansion port device (doesn't do anything,
  just for testing)

Note: WS is hard-coded to vertical orientation right now. But there's
basic code in there for all the horizontal stuff.
2016-03-13 11:22:14 +11:00
Tim Allen
570eb9c5f5 Update to v097r20 release.
byuu says:

Changelog:
- WS: fixed a major CPU bug where I was using the wrong bits for
  ModR/M's memory mode
- WS: added grayscale PPU emulation (exceptionally buggy)

GunPey now runs, as long as you add:

    eeprom name=save.ram size=0x800

to the manifest after importing with icarus.

Right now, you can't control the game due to missing keypad polling.
There's also a lot of glitchiness with the sprites. Seems like they're
not getting properly cleared sometimes or something.

Also, the PPU emulation is totally unrealistic bullshit. I decode and
evaluate every single tile and sprite on every single pixel of output.
No way in hell the hardware could ever come close to that. The speed's
around 500fps without the insane sprite evaluations, and around 90fps
with it. Obviously, I'll fix this in time.

Nothing else seems to run that I've tried. Not even far enough to
display any output whatsoever. Tried Langrisser Millenium, Rockman
& Forte and Riviera. I really need to update icarus to try and encode
eeprom/sram sizes, because that's going to break a lot of stuff if it's
missing.
2016-03-13 11:22:14 +11:00
Tim Allen
7dc62e3a69 Update to v097r19 release.
byuu says:

Changelog:
- fixed nall/windows/guard.hpp
- fixed hiro/(windows,gtk)/header.hpp
- fixed Famicom PPU OAM reads (mask the correct bits when writing)
  [hex_usr]
- removed the need for (system := system) lines from higan/GNUmakefile
- added "All" option to filetype dropdown for ROM loading
  - allows loading GBC games in SGB mode (and technically non-GB(C)
    games, which will obviously fail to do anything)
- loki can load and play game folders now (command-line only) (extremely
  unimpressive; don't waste your time :P)
  - the input is extremely hacked in as a quick placeholder; not sure
    how I'm going to do mapping yet for it
2016-03-13 11:22:14 +11:00
Tim Allen
fc7d5991ce Update to v097r18 release.
byuu says:

Changelog:
- fixed SNES sprite priority regression from r17
- added nall/windows/guard.hpp to guard against global namespace
  pollution (similar to nall/xorg/guard.hpp)
- almost fixed Windows compilation (still accuracy profile only, sorry)
- finished porting all of gba/ppu's registers over to the new .bit,.bits
  format ... all GBA registers.cpp files gone now
- the "processors :=" line in the target-$(ui)/GNUmakefile is no longer
  required
  - processors += added to each emulator core
  - duplicates are removed using the new nall/GNUmakefile's $(unique)
    function
- SFC core can be compiled without the GB core now
  - "-DSFC_SUPERGAMEBOY" is required to build in SGB support now (it's
    set in target-tomoko/GNUmakefile)
- started once again on loki (higan/target-loki/) [as before, loki is
  Linux/BSD only on account of needing hiro::Console]

loki shouldn't be too horrendous ... I hope. I just have the base
skeleton ready for now. But the code from v094r08 should be mostly
copyable over to it. It's just that it's about 50KiB of incredibly
tricky code that has to be just perfect, so it's not going to be quick.
But at least with the skeleton, it'll be a lot easier to pick away at it
as I want.

Windows compilation fix: move hiro/windows/header.hpp line 18 (header
guard) to line 16 instead.
2016-03-13 11:22:14 +11:00
Tim Allen
29be18ce0c Update to v097r17 release.
byuu says:

Changelog:
- ruby: if DirectSoundCreate fails (no sound device present), return
  false from init instead of crashing
- nall: improved edge case return values for
  (basename,pathname,dirname,...)
- nall: renamed file_system_object class to inode
- nall: varuint_t replaced with VariadicNatural; which contains
  .bit,.bits,.byte ala Natural/Integer
- nall: fixed boolean compilation error on Windows
- WS: popa should not restore SP
- GBA: rewrote the CPU/APU cores to use the .bit,.bits functions;
  removed registers.cpp from each

Note that the GBA changes are extremely major. This is about five hours
worth of extremely delicate work. Any slight errors could break
emulation in extremely bad ways. Let's hold off on extensive testing
until the next WIP, after I do the same to the PPU.

So far ... endrift's SOUNDCNT_X I/O test is failing, although that code
didn't change, so clearly I messed up SOUNDCNT_H somehow ...

To compile on Windows:

1. change nall/string/platform.hpp line 47 to

    return slice(result, 0, 3);

2. change ruby/video.wgl.cpp line 72 to

    auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool {

3. add this line to the very top of hiro/windows/header.cpp:

    #define boolean FuckYouMicrosoft
2016-03-13 11:22:14 +11:00
Tim Allen
810cbdafb4 Update to v097r16 release.
byuu says:

Changelog:
- sfc/ppu/sprite updated to use new .bit(s) functions; masked sizes
  better; added valid flags instead of using magic numbers
- ws/ppu updates to use new .bit(s) functions
- ws/ppu: added line compare interrupt support
- added ws/eeprom; emulation of WS/WSC internal EEPROM and cartridge
  EEPROM (1kbit - 16kbit supported)
- added basic read/write handlers for remaining WS/WSC PPU registers

WS EEPROM emulation is basically a direct copy of trap15's code. Still
some unknown areas in there, but hopefully it's enough to get further
into games that depend on EEPROM support. Note that you'll have to
manually add the eeprom line to the manifest for now, as icarus doesn't
know how to detect EEPROM/sizes yet.

I figured the changes to the SNES PPU sprites would slow it down a tad,
but it actually sped it up. Most of the impact from the integer classes
are gone now.
2016-03-13 11:22:10 +11:00
Tim Allen
4b29f4bad7 Update to v097r15 release.
byuu says:

Changelog:
- higan now uses Natural<Size>/Integer<Size> for its internal types
- Super Famicom emulation now uses uint24 instead of uint for bus
  addresses (it's a 24-bit bus)
- cleaned up gb/apu MMIO writes
- cleaned up sfc/coprocessor/msu1 MMIO writes
- ~3% speed penalty

I've wanted to do that 24-bit bus thing for so long, but have always
been afraid of the speed impact. It's probably going to hurt
balanced/performance once they compile again, but it wasn't significant
enough to harm the accuracy core's frame rate, thankfully. Only lost one
frame per second.

The GBA core handlers are clearly going to take a lot more work. The
bit-ranges will make it substantially easier to handle, though. Lots of
32-bit registers where certain values span multiple bytes, but we have
to be able to read/write at byte-granularity.
2016-02-16 20:32:49 +11:00
Tim Allen
ef65bb862a Update to 20160215 release.
byuu says:

Got it. Wow, that didn't hurt nearly as much as I thought it was going
to.

Dropped from 127.5fps to 123.5fps to use Natural/Integer for
(u)int(8,16,32,64).

That's totally worth the cost.
2016-02-16 20:27:55 +11:00
Tim Allen
0d0af39b44 Update to v097r14 release.
byuu says:

This is a few days old, but oh well.

This WIP changes nall,hiro,ruby,icarus back to (u)int(8,16,32,64)_t.

I'm slowly pushing for (u)int(8,16,32,64) to use my custom
Integer<Size>/Natural<Size> classes instead. But it's going to be one
hell of a struggle to get that into higan.
2016-02-16 20:11:58 +11:00
Tim Allen
6c83329cae Update to v097r13 release.
byuu says:

I refactored my schedulers. Added about ten lines to each scheduler, and
removed about 100 lines of calling into internal state in the scheduler
for the FC,SFC cores and about 30-40 lines for the other cores. All of
its state is now private.

Also reworked all of the entry points to static auto Enter() and auto
main(). Where Enter() handles all the synchronization stuff, and main()
doesn't need the while(true); loop forcing another layer of indentation
everywhere.

Took a few hours to do, but totally worth it. I'm surprised I didn't do
this sooner.

Also updated icarus gmake install rule to copy over the database.
2016-02-09 22:51:12 +11:00
Tim Allen
32a95a9761 Update to v097r12 release.
byuu says:

Nothing WS-related this time.

First, I fixed expansion port device mapping. On first load, it was
mapping the expansion port device too late, so it ended up not taking
effect. I had to spin out the logic for that into
Program::connectDevices(). This was proving to be quite annoying while
testing eBoot (SNES-Hook simulation.)

Second, I fixed the audio->set(Frequency, Latency) functions to take
(uint) parameters from the configuration file, so the weird behavior
around changing settings in the audio panel should hopefully be gone
now.

Third, I rewrote the interface->load,unload functions to call into the
(Emulator)::System::load,unload functions. And I have those call out to
Cartridge::load,unload. Before, this was inverted, and Cartridge::load()
was invoking System::load(), which I felt was kind of backward.

The Super Game Boy really didn't like this change, however. And it took
me a few hours to power through it. Before, I had the Game Boy core
dummying out all the interface->(load,save)Request calls, and having the
SNES core make them for it. This is because the folder paths and IDs
will be different between the two cores.

I've redesigned things so that ICD2's Emulator::Interface overloads
loadRequest and saveRequest, and translates the requests into new
requests for the SuperFamicom core. This allows the Game Boy code to do
its own loading for everything without a bunch of Super Game Boy special
casing, and without any awkwardness around powering on with no cartridge
inserted.

This also lets the SNES side of things simply call into higher-level
GameBoy::interface->load,save(id, stream) functions instead of stabbing
at the raw underlying state inside of various Game Boy core emulation
classes. So things are a lot better abstracted now.
2016-02-08 14:17:59 +11:00
Tim Allen
a89a3da77a Update to v097r11 release.
byuu says:

Alright, well interrupts are in. At least Vblank is.

I also fixed a bug in vector() indexing, MoDRM mod!=3&&reg==6 using SS
instead of DS, opcodes a0-a3 allowing segment override, and added the
"irq_disable" stuff to the relevant opcodes to suppress IRQs after
certain instructions.

But unfortunately ... still no go on Riviera. It's not reading any
unmapped ports, and although it enables Vblank IRQs and they set port
$b4's status bit, the game never sets the IE flag, so no interrupts ever
actually fire. The game does indeed appear to be sitting in a rather
huge loop, which is probably dependent upon some RAM variable being set
from the Vblank IRQ, but I don't know how I'm supposed to be triggering
it.

... I'm really quite stumped here >_>
2016-02-05 08:18:06 +11:00
Tim Allen
7a748e093e Update to v097r10 release.
byuu says:

All 256 instructions implemented fully. Fixed a major bug with
instructions that both read and write to ModRM with displacement.
Riviera now runs into an infinite loop ... possibly crashed, possibly
waiting on interrupts or in to return something. Added a bunch of PPU
settings registers, but nothing's actually rendering with them yet.
2016-02-04 21:29:08 +11:00
Tim Allen
d158c8f293 Update to v097r09 release.
244 of 256 opcodes implemented now, although the interrupt triggering
portions are missing from them still. Much better handling of prefixes
now.

I definitely have a newfound hatepreciation for x86 now >_>
2016-02-03 21:24:58 +11:00
Tim Allen
71bda4144a Update to v097r08 release.
byuu says:

Up to 211 opcodes implemented, with the caveat that the four opcodes
that make up group 3 and group 4 don't do anything yet. Both groups seem
to have some "illegal" instructions in them, so that'll be "fun".

I have a new mechanic in place for opcode prefixes, but it could use
some work still. I also only have it working to override ModRM mem
addressing, but of course it does it in a lot of other places like the
string operations.

Making it about 5.5 million instructions into Gunpey now, but of course
that doesn't mean much. Could be going off the rails at any point due to
CPU bugs or unimplemented ports. Riviera's still crashing.
2016-02-03 21:07:50 +11:00
Tim Allen
ad51f1478e Update to v097r07 release.
byuu says:

26 hours in, 173 instructions implemented. Although the four segment
prefix opcodes don't actually do anything yet. There's less than 256
actual instructions on the 80186, not sure of the exact count.

Gunpey gets around ~8,200 instructions in before hitting an unsupported
opcode (loop). Riviera goes off the rails on a retf and ends up
executing an endless stream of bad opcodes in RAM =( Both games hammer
the living shit out of the in/out ports pretty much immediately.
2016-02-02 21:51:17 +11:00
Tim Allen
d0ddd87e9c Update to v097r06 release.
byuu says:

Man, the 80186 is taking a lot longer to implement than I thought it
would. So far I'm 18 hours into this emulator. Whereas I had Super Mario
Bros fully playable (no sound) in 12 hours for the NES >_>

I refactored all the byte/word variant functions to single functions
that take a size parameter. Cuts the amount of code in half.

Also implemented repz/repnz + movsb/movsw, so Riviera now gets 299
instructions in before dying. Nobody really bothers to explain how the
CPU actually implements these instructions, but I think I have it right:
ignore non-string opcodes that follow rep, invoke the string operations
inside the rep opcodes to prevent interrupts from triggering between the
two (which will be even more fun for segment selector overrides ...)

The next opcode needed is 0xC7, which ... throws ModRM on its head. In
this mode, ModRM is only used to determine the target operand (and it
doesn't use the middle bits for that at all), and the source is an
immediate that follows it. Gonna have to waste a few more hours thinking
about how best to handle that.

Also, disabled HiDPI for higan as well on OS X.
2016-01-31 18:59:44 +11:00
Tim Allen
605a8aa3e9 Update to v097r05 release.
byuu says:

More V30MZ implemented, a lot more to go.

icarus now supports importing WS and WSC games. It expects them to have
the correct file extension, same for GB and GBC.

> Ugh, apparently HiDPI icarus doesn't let you press the check boxes.

I set the flag value in the plist to false for now. Forgot to do it for
higan, but hopefully I won't forget before release.
2016-01-30 17:40:35 +11:00
Tim Allen
a8323d0d2b Update to v097r04 release.
byuu says:

Lots of improvements. We're now able to start executing some V30MZ
instructions. 32 of 256 opcodes implemented so far.

I hope this goes without saying, but there's absolutely no point in
loading WS/WSC games right now. You won't see anything until I have the
full CPU and partial PPU implemented.

ROM bank 2 works properly now, the I/O map is 16-bit (address) x 16-bit
(data) as it should be*, and I have a basic disassembler in place
(adding to it as I emulate new opcodes.)

(* I don't know what happens if you access an 8-bit port in 16-bit mode
or vice versa, so for now I'm just treating the handlers as always being
16-bit, and discarding the upper 8-bits when not needed.)
2016-01-28 22:39:49 +11:00
Tim Allen
d7998b23ef Update to v097r03 release.
byuu says:

So, this WIP starts work on something new for higan. Obviously, I can't
keep it a secret until it's ready, because I want to continue daily WIP
releases, and of course, solicit feedback as I go along.
2016-01-27 22:31:39 +11:00
Tim Allen
344e63d928 Update to v097r02 release.
byuu says:

Note: balanced/performance profiles still broken, sorry.

Changelog:
- added nall/GNUmakefile unique() function; used on linking phase of
  higan
- added nall/unique_pointer
- target-tomoko and {System}::Video updated to use
  unique_pointer<ClassName> instead of ClassName* [1]
- locate() updated to search multiple paths [2]
- GB: pass gekkio's if_ie_registers and boot_hwio-G test ROMs
- FC, GB, GBA: merge video/ into the PPU cores
- ruby: fixed ~AudioXAudio2() typo

[1] I expected this to cause new crashes on exit due to changing the
order of destruction of objects (and deleting things that weren't
deleted before), but ... so far, so good. I guess we'll see what crops
up, especially on OS X (which is already crashing for unknown reasons on
exit.)

[2] right now, the search paths are: programpath(), {configpath(),
"higan/"}, {localpath(), "higan/"}; but we can add as many more as we
want, and we can also add platform-specific versions.
2016-01-25 22:27:18 +11:00
Tim Allen
f1ebef2ea8 Update to v097r01 release.
byuu says:

A minor WIP to get us started.

Changelog:
- System::Video merged to PPU::Video
- System::Audio merged to DSP::Audio
- System::Configuration merged to Interface::Settings
- created emulator/emulator.cpp and accompanying object file for shared
  code between all cores

Currently, emulator.cpp just holds a videoColor() function that takes
R16G16B16, performs gamma/saturation/luma adjust, and outputs
(currently) A8R8G8B8. It's basically an internal function call for cores
to use when generating palette entries. This code used to exist inside
ui-tomoko/program/interface.cpp, but we have to move it internal for
software display emulation. But in the future, we could add other useful
cross-core functionality here.
2016-01-23 18:29:34 +11:00
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
Tim Allen
10464b8c54 Update to v094 release.
byuu says:

This release adds support for game libraries, and substantially improves
Game Boy and Game Boy Color emulation with cycle-based renderers. Many
other changes are also present.

It's very important to note that this release now defaults to optimal
drivers rather than safe drivers. This is particularly important if you
do not have strong OpenGL 3.2 drivers. If performance is bad, go to
Settings -> Configuration -> Advanced, change the video driver, and
restart higan. In the rare case that you have trouble opening higan, you
can edit settings.bml directly and change the setting there. The Windows
safe driver is Direct3D, and the Linux safe driver is XShm.

Also note that although display emulation shaders are now supported,
they have not been included in this release as they are not ready yet.
The support has been built-in anyway, so that they can be tested by
everyone. Once refined, future releases of higan will come with built-in
shaders for each emulated system that simulates the unique display
characteristics of each.

Changelog (since v093):
- sfc: added SA-1 MDR support (fixes SD Gundam G-Next bug)
- sfc: remove random/ and config/, merge to system/ with better
  randomization
- gb: improved color emulation palette contrast
- gbc: do not sort sprites by X-priority
- gbc: allow transparency on BG priority pixels
- gbc: VRAM DMA timing and register fixes
- gbc: block invalid VRAM DMA transfer source and target addresses
- gba: added LCD color emulation (without it, colors are grossly
  over-saturated)
- gba: removed internal frame blending (use shaders to simulate motion
  blur if desired)
- gba: added Game Boy Player support (adds joypad rumble support to
  supported games)
- gba: SOUND_CTL_H is readable
- gb/gbc: PPU renderer is now cycle-based (major accuracy improvement)
- gb/gbc: OAM DMA runs in parallel with the CPU
- gb/gbc: only HRAM can be accessed during OAM DMA
- gb/gbc: fixed serialization of games with SRAM
- gb/gbc: disallow up+down or left+right at the same time
- gb/gbc: added weak hipass filter to remove DC bias
- gb/gbc: STAT OAM+Hblank IRQs only trigger during active display
- gb/gbc: fixed underflow in window clamping
- gb/gbc/gba: audio mixes internally at 2MHz now instead of 4MHz (does
  not affect accuracy)
- gb/gbc/gba: audio volume reduced for consistency with other systems
- fc/sfc/gb/gbc/gba: cheat codes are now stored in universal, decrypted
  format
- ethos: replaced file loader with a proper game library
- ethos: added display emulation shader support
- ethos: added color emulation option to video settings
- ethos: program icon upgraded from 48x48 to 512x512
- ethos: settings and tools windows now use tab frames (less wasted
  screen space)
- ethos: default to optimal (video, audio, input) drivers instead of
  safest drivers
- ethos: input mapping system completely rewritten to support
  hotplugging and unique device mappings
- ruby: added fixes for OpenGL 3.2 on AMD graphics cards
- ruby: quark shaders now support user settings inside of manifest
- ruby: quark shaders can use integral textures (allows display
  emulation shaders to work with raw colors)
- ruby: add joypad rumble support
- ruby: XInput (Xbox 360) controllers now support hotplugging
- ruby: added Linux udev joypad driver with hotplug support
- phoenix: fixed a rare null pointer dereference issue on Windows
- port: target -std=c++11 instead of -std=gnu++11 (do not rely on GNU
  C++ extensions)
- port: added out-of-the-box compilation support for BSD/Clang 3.3+
- port: applied a few Debian upstream patches
- cheats: updated to mightymo's 2014-01-02 release; decrypted all Game
  Genie codes
2014-01-20 19:55:17 +11:00
Tim Allen
fe85679321 Update to v093r13 release.
byuu says:

This WIP removes nall/input.hpp entirely, and implements the new
universal cheat format for FC/SFC/GB/GBC/SGB.

GBA is going to be tricky since there's some consternation around
byte/word/dword overrides.

It's also not immediately obvious to me how to implement the code search
in logarithmic time, due to the optional compare value.

Lastly, the cheat values inside cheats.bml seem to be broken for the
SFC. Likely there's a bug somewhere in the conversion process. Obviously
I'll have to fix that before v094.

I received no feedback on the universal cheat format. If nobody adds
anything before v094, then I don't want to hear any complaining about
the formatting :P
2014-01-13 20:35:46 +11:00
Tim Allen
2b81b630cb Update to v093r12a release.
byuu says:

Not an official WIP (a WIP WIP? A meta-WIP?), just throwing in the new
fullscreen code, and I noticed that OpenGL colors in 30-bit mode are all
fucked up now for some strange reason. So I'm just using this snapshot
to debug the issue.
2014-01-05 20:59:17 +11:00
Tim Allen
3ce1d19f7a Update to v093r12 release.
byuu says:

I've completely redone the ethos InputManager and ruby to work on
HID::Device objects instead of one giant scancode pool.

Currently only the udev driver supports the changes to ruby, so only
Linux users will be able to compile and run this WIP build.

The nice thing about the new system is that it's now possible to
uniquely identify controllers, so if you swap out gamepads, you won't
end up with it working but with all the mappings all screwed up. Since
higan lets you map multiple physical inputs to one emulated input, you
can now configure your keyboard and multiple gamepads to the same
emulated input, and then just use whatever controller you want.

Because USB gamepad makers failed to provide unique serial#s with each
controller, we have to limit the mapping to specific USB ports.
Otherwise, we couldn't distinguish two otherwise identical gamepads. So
basically your computer USB ports act like real game console input port
numbers. Which is kind of neat, I guess.

And the really nice thing about the new system is that we now have the
capability to support hotplugging input devices. I haven't yet added
this to any drivers, but I'm definitely going to add it to udev for v094
official.

Finally, with the device ID (vendor ID + product ID) exposed, we gain
one last really cool feature that we may be able to develop more in the
future. Say we created a joypad.bml file to include with higan. In it,
we'd store the Xbox 360 controller, and pre-defined button mappings for
each emulated system. So if higan detects you have an Xbox 360
controller, you can just plug it in and use it. Even better, we can
clearly specify the difference between triggers and analog axes, and
name each individual input. So you'd see "Xbox 360 Gamepad #1: Left
Trigger" instead of higan v093's "JP0::Axis2.Hi"

Note: for right now, ethos' input manager isn't filtering the device IDs
to look pretty. So you're going to see a 64-bit hex value for a device
ID right now instead of something like Joypad#N for now.
2013-12-23 22:43:51 +11:00
Tim Allen
73be2e729c Update to v093r11 release.
byuu says:

Changelog:
- GBA: SOUND_CTL_H is readable, fixes sound effects in Mario&Luigi
  Superstar Saga [Cydrak] (note: game is still unplayable due to other
  bugs)
- phoenix/Windows: workaround for Win32 API ListView bug, fixes slot
  loading behavior
- ruby: added udev driver for Linux with rumble support, and added
  rumble support to existing RawInput driver for XInput and DirectInput
- ethos: added new "Rumble" mapping to GBA input assignment, use it to
  tell higan which controller to rumble (clear it to disable rumble)
- GBA: Game Boy Player rumble is now fully emulated
- core: added new normalized raw-color palette mode for Display
  Emulation shaders

The way rumble was added to ethos was somewhat hackish. The support
doesn't really exist in nall.

I need to redesign the entire input system, but that's not a change
I want to make so close to a release.
2013-12-21 21:45:58 +11:00
Tim Allen
84fab07756 Update to v093r10 release.
byuu says:

Changelog:
- Game Boy (Color): STAT OAM+HBlank IRQs only trigger during LY=0-143
  with display enabled
  - fixes backgrounds and text in Wacky Races
- Game Boy (Color): fixed underflow in window clamping
  - fixes Wacky Races, Prehistorik Man, Alleyway, etc
- Game Boy (Color): LCD OAM DMA was running too slow
  - fixes Shin Megami Tensei - Devichil - Kuro no Sho
- Game Boy Advance: removed built-in frame blending; display emulation
  shaders will handle this going forward
- Game Boy Advance: added Game Boy Player emulation
  - currently the screen is tinted red during rumble, no actual gamepad
    rumble support yet
  - this is going to be slow, as we have to hash the frame to detect the
    GBP logo, it'll be optional later on
- Emulator::Interface::Palette can now output a raw palette (for Display
  Emulation shaders only)
  - color channels are not yet split up, it's just the raw packed value
2013-12-20 22:40:39 +11:00
Tim Allen
926a39d701 Update to v093r09 release.
byuu says:

Changelog:
- GB/C OAM DMA now runs in parallel with the CPU
- CPU can only access HRAM during OAM DMA
- fixed SGB mode again
- brand new config files will default to the optimal drivers now
  (OpenGL, etc) instead of the safest
- hopefully fixed remaining Windows UI issues
2013-12-14 17:25:12 +11:00
Tim Allen
1361820dd8 Update to v093r08 release.
byuu says:

Changelog:
- Game Boy and Game Boy Color now have a weak hipass filter to remove DC
  bias (or whatever)
- Game Boy and Game Boy Color now have cycle-based PPU renderers instead
  of scanline-based renderers
- improved Game Boy color emulation palette contrast
- fixed GTK+ ListView selection bug
- fixed a typo when saving states (should say "Saved to slot N", not
  "Save to slot N")
2013-12-11 22:19:17 +11:00
Tim Allen
0f78acffd7 Update to v093r07 release.
byuu says:

Changelog:
- importing a game won't show message box on success
- importing a game will select the game that was imported in the list
  - caveat: GTK+ port doesn't seem to be removing focus from item 0 even
    though the selection is on item 2
- Game Boy audio reduced in volume by 50%
- Game Boy Advance audio reduced in volume by 50%
- Game Boy internally mixes audio at 2MHz now
- Game Boy Advance's Game Boy audio hardware internally mixes audio at
  2MHz now
- Game Boy Color doesn't sort sprites by X-coordinate
- Game Boy Color allows transparency on BGpriority pixels
  - caveat: this seems to allow sprites to appear on top of windows
- Game Boy Color VRAM DMA transfers 16 bytes in 8 clocks (or 16 clocks
  in double speed mode)
- Game Boy Color VRAM DMA masks low 4-bits of source and destination
  address
- Game Boy Color VRAM DMA only allows reads from ROM or RAM
- Game Boy Color VRAM DMA only allows writes to VRAM
- fixed a bug in dereferencing a nullptr from pObject::find(), should
  fix crash when pressing enter key on blank windows
- fixed Windows RadioItem selection
- Game Boy Advance color emulation code added
2013-12-10 23:12:54 +11:00
Tim Allen
35f1605829 Update to v093r06 release.
byuu says:

Changelog:
- Windows port should compile out-of-the-box
- InputManager::scancode[] initialized at startup
- Library menu shows item for each bootable media type (notably Game Boy
  Color)
- Display Emulation menu selection fix
- LibraryManager load button works now
- Added hotkey to show library manager (defaults to L)
- Added color emulation to video settings (missing on GBA for now)
- SFC loading SGB without GB cartridge no longer segfaults
- GB/GBC system.load() after cartridge.load()
- GB/GBC BG-over-OAM fix
- GB/GBC disallow up+down and left+right
2013-12-07 20:12:37 +11:00
Tim Allen
ed4e87f65e Update to v093r05 release.
byuu says:

Library concept has been refined as per the general forum discussion.
2013-12-03 21:01:59 +11:00
Tim Allen
b4f18c3b47 Update to v093r04 release.
byuu says:

This version replaces the old folder-browser with a proper game library.
2013-11-28 21:32:53 +11:00
Tim Allen
68eaf53691 Update to v093r03 release.
byuu says:

Updated to support latest phoenix changes.
Converted Settings and Tools to TabFrame views.

Errata:
- phoenix/Windows ComboButton wasn't calling parent
  pWidget::setGeometry() [fixed locally]
- TRACKBAR_CLASS draws COLOR_3DFACE for the background even when its
  parent is a WC_TABCONTROL
2013-11-28 21:29:01 +11:00
Tim Allen
8c0b0fa4ad Update to v093r02 release.
byuu says:

Changelog:
- nall: fixed major memory leak in string class
- ruby: video shaders support #define-based settings now
- phoenix/GTK+: support > 256x256 icons for window / task bar / alt-tab
- sfc: remove random/ and config/, merge into system/
- ethos: delete higan.png (48x48), replace with higan512.png (512x512)
  as new higan.png
- ethos: default gamma to 100% (no color adjustment)
- ethos: use "Video Shaders/Display Emulation/" instead of "Video
  Shaders/Emulation/"
- use g++ instead of g++-4.7 (g++ -v must be >= 4.7)
- use -std=c++11 instead of -std=gnu++11
- applied a few patches from Debian upstream to make their packaging job
  easier

So because colors are normalized in GLSL, I won't be able to offer video
shaders absolute color literals. We will have to perform basic color
conversion inside the core.

As such, the current plan is to create some sort of Emulator::Settings
interface. With that, I'll connect an option for color correction, which
will be on by default. For FC/SFC, that will mean gamma correction
(darker / stronger colors), and for GB/GBC/GBA, it will mean simulating
the weird brightness levels of the displays. I am undecided on whether
to use pea soup green for the GB or not. By not doing so, it'll be
easier for the display emulation shader to do it.
2013-11-09 22:45:54 +11:00
Tim Allen
66f136718e Update to v093r01 release.
byuu says:

Changelog:
- added SA-1 MDR; fixes bug in SD Gundam G-Next where the main
  battleship was unable to fire
- added out-of-the-box support for any BSD running Clang 3.3+ (FreeBSD
  10+, notably)
- added new video shader, "Display Emulation", which changes the shader
  based on the emulated system
- fixed the home button to go to your default library path
- phoenix: Windows port won't send onActivate unless an item is selected
  (prevents crashing on pressing enter in file dialog)
- ruby: removed vec4 position from out Vertex {} (helps AMD cards)
- shaders: updated all shaders to use texture() instead of texture2D()
  (helps AMD cards)

The "Display Emulation" option works like this: when selected, it tries
to load "<path>/Video Shaders/Emulation/<systemName>.shader/"; otherwise
it falls back to the blur shader. <path> is the usual (next to binary,
then in <config>/higan, then in /usr/share/higan, etc); and <systemName>
is "Famicom", "Super Famicom", "Game Boy", "Game Boy Color", "Game Boy
Advance"

To support BSD, I had to modify the $(platform) variable to
differentiate between Linux and BSD.
As such, the new $(platform) values are:
win -> windows
osx -> macosx
x -> linux or bsd

I am also checking uname -s instead of uname -a now. No reason to
potentially match the hostname to the wrong OS type.
2013-10-21 22:45:39 +11:00
2404 changed files with 207056 additions and 200110 deletions

3
.gitignore vendored
View File

@@ -1 +1,2 @@
ananke/libananke.so
higan/profile/WonderSwan.sys/internal.ram
higan/profile/WonderSwan Color.sys/internal.ram

106
Makefile
View File

@@ -1,106 +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),x)
flags += -march=native
link += -s -Wl,-export-dynamic -ldl -lX11 -lXext
else ifeq ($(platform),osx)
flags += -march=native
else ifeq ($(platform),win)
ifeq ($(arch),win32)
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
$(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,46 +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),x)
$(compiler) $(link) -shared -Wl,-soname,libananke.so.1 -o libananke.so obj/ananke.o
else ifeq ($(platform),osx)
$(compiler) $(link) -shared -dynamiclib -undefined suppress -flat_namespace -o libananke.dylib obj/ananke.o
else ifeq ($(platform),win)
$(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
endif
resource: force
sourcery resource/resource.bml resource/resource.cpp resource/resource.hpp
clean:
-@$(call delete,obj/*.o)
-@$(call delete,*.so)
install: uninstall
ifeq ($(platform),x)
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
else ifeq ($(platform),osx)
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
endif
uninstall:
ifeq ($(platform),x)
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
else ifeq ($(platform),osx)
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
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,173 +0,0 @@
#ifndef NALL_EMULATION_FAMICOM_HPP
#define NALL_EMULATION_FAMICOM_HPP
#include <nall/sha256.hpp>
#include <nall/string.hpp>
namespace nall {
struct FamicomCartridge {
string markup;
inline FamicomCartridge(const uint8_t *data, unsigned size);
//private:
unsigned mapper;
unsigned mirror;
unsigned prgrom;
unsigned prgram;
unsigned chrrom;
unsigned chrram;
};
FamicomCartridge::FamicomCartridge(const uint8_t *data, unsigned size) {
markup = "";
if(size < 16) return;
if(data[0] != 'N') return;
if(data[1] != 'E') return;
if(data[2] != 'S') return;
if(data[3] != 26) return;
mapper = ((data[7] >> 4) << 4) | (data[6] >> 4);
mirror = ((data[6] & 0x08) >> 2) | (data[6] & 0x01);
prgrom = data[4] * 0x4000;
chrrom = data[5] * 0x2000;
prgram = 0u;
chrram = chrrom == 0u ? 8192u : 0u;
markup.append("cartridge\n");
switch(mapper) {
default:
markup.append(" board type=NES-NROM-256\n");
markup.append(" mirror mode=", mirror == 0 ? "horizontal" : "vertical", "\n");
break;
case 1:
markup.append(" board type=NES-SXROM\n");
markup.append(" chip type=MMC1B2\n");
prgram = 8192;
break;
case 2:
markup.append(" board type=NES-UOROM\n");
markup.append(" mirror mode=", mirror == 0 ? "horizontal" : "vertical", "\n");
break;
case 3:
markup.append(" board type=NES-CNROM\n");
markup.append(" mirror mode=", mirror == 0 ? "horizontal" : "vertical", "\n");
break;
case 4:
//MMC3
markup.append(" board type=NES-TLROM\n");
markup.append(" chip type=MMC3B\n");
prgram = 8192;
//MMC6
//markup.append(" board type=NES-HKROM\n");
//markup.append(" chip type=MMC6n");
//prgram = 1024;
break;
case 5:
markup.append(" board type=NES-ELROM\n");
markup.append(" chip type=MMC5\n");
prgram = 65536;
break;
case 7:
markup.append(" board type=NES-AOROM\n");
break;
case 9:
markup.append(" board type=NES-PNROM\n");
markup.append(" chip type=MMC2\n");
prgram = 8192;
break;
case 10:
markup.append(" board type=NES-FKROM\n");
markup.append(" chip type=MMC4\n");
prgram = 8192;
break;
case 16:
markup.append(" board type=BANDAI-FCG\n");
markup.append(" chip type=LZ93D50\n");
break;
case 21:
case 23:
case 25:
//VRC4
markup.append(" board type=KONAMI-VRC-4\n");
markup.append(" chip type=VRC4\n");
markup.append(" pinout a0=1 a1=0\n");
prgram = 8192;
break;
case 22:
//VRC2
markup.append(" board type=KONAMI-VRC-2\n");
markup.append(" chip type=VRC2\n");
markup.append(" pinout a0=0 a1=1\n");
break;
case 24:
markup.append(" board type=KONAMI-VRC-6\n");
markup.append(" chip type=VRC6\n");
break;
case 26:
markup.append(" board type=KONAMI-VRC-6\n");
markup.append(" chip type=VRC6\n");
prgram = 8192;
break;
case 34:
markup.append(" board type=NES-BNROM\n");
markup.append(" mirror mode=", mirror == 0 ? "horizontal" : "vertical", "\n");
break;
case 66:
markup.append(" board type=NES-GNROM\n");
markup.append(" mirror mode=", mirror == 0 ? "horizontal" : "vertical", "\n");
break;
case 69:
markup.append(" board type=SUNSOFT-5B\n");
markup.append(" chip type=5B\n");
prgram = 8192;
break;
case 73:
markup.append(" board type=KONAMI-VRC-3\n");
markup.append(" chip type=VRC3\n");
markup.append(" mirror mode=", mirror == 0 ? "horizontal" : "vertical", "\n");
prgram = 8192;
break;
case 75:
markup.append(" board type=KONAMI-VRC-1\n");
markup.append(" chip type=VRC1\n");
break;
case 85:
markup.append(" board type=KONAMI-VRC-7\n");
markup.append(" chip type=VRC7\n");
prgram = 8192;
break;
}
markup.append(" prg\n");
if(prgrom) markup.append(" rom name=program.rom size=0x", hex(prgrom), "\n");
if(prgram) markup.append(" ram name=save.ram size=0x", hex(prgram), "\n");
markup.append(" chr\n");
if(chrrom) markup.append(" rom name=character.rom size=0x", hex(chrrom), "\n");
if(chrram) markup.append(" ram size=0x", hex(chrram), "\n");
}
}
#endif

View File

@@ -1,63 +0,0 @@
#ifndef NALL_EMULATION_GAME_BOY_ADVANCE_HPP
#define NALL_EMULATION_GAME_BOY_ADVANCE_HPP
#include <nall/sha256.hpp>
#include <nall/string.hpp>
#include <nall/vector.hpp>
namespace nall {
struct GameBoyAdvanceCartridge {
string markup;
string identifiers;
inline GameBoyAdvanceCartridge(const uint8_t *data, unsigned size);
};
GameBoyAdvanceCartridge::GameBoyAdvanceCartridge(const uint8_t *data, unsigned size) {
struct Identifier {
string name;
unsigned size;
};
vector<Identifier> idlist;
idlist.append({"SRAM_V", 6});
idlist.append({"SRAM_F_V", 8});
idlist.append({"EEPROM_V", 8});
idlist.append({"FLASH_V", 7});
idlist.append({"FLASH512_V", 10});
idlist.append({"FLASH1M_V", 9});
lstring list;
for(auto &id : idlist) {
for(signed n = 0; n < size - 16; n++) {
if(!memcmp(data + n, (const char*)id.name, id.size)) {
const char *p = (const char*)data + n + id.size;
if(p[0] >= '0' && p[0] <= '9'
&& p[1] >= '0' && p[1] <= '9'
&& p[2] >= '0' && p[2] <= '9'
) {
char text[16];
memcpy(text, data + n, id.size + 3);
text[id.size + 3] = 0;
list.appendonce(text);
}
}
}
}
identifiers = list.concatenate(",");
markup = "";
markup.append("cartridge\n");
markup.append(" rom name=program.rom size=0x", hex(size), "\n");
if(0);
else if(identifiers.beginswith("SRAM_V" )) markup.append(" ram name=save.ram type=SRAM size=0x8000\n");
else if(identifiers.beginswith("SRAM_F_V" )) markup.append(" ram name=save.ram type=FRAM size=0x8000\n");
else if(identifiers.beginswith("EEPROM_V" )) markup.append(" ram name=save.ram type=EEPROM size=0x0\n");
else if(identifiers.beginswith("FLASH_V" )) markup.append(" ram name=save.ram type=FlashROM size=0x10000\n");
else if(identifiers.beginswith("FLASH512_V")) markup.append(" ram name=save.ram type=FlashROM size=0x10000\n");
else if(identifiers.beginswith("FLASH1M_V" )) markup.append(" ram name=save.ram type=FlashROM size=0x20000\n");
//if(identifiers.empty() == false) markup.append(" #detected: ", identifiers, "\n");
}
}
#endif

View File

@@ -1,120 +0,0 @@
#ifndef NALL_EMULATION_GAME_BOY_HPP
#define NALL_EMULATION_GAME_BOY_HPP
#include <nall/sha256.hpp>
#include <nall/string.hpp>
namespace nall {
struct GameBoyCartridge {
string markup;
inline GameBoyCartridge(uint8_t *data, unsigned size);
//private:
struct Information {
string mapper;
bool ram;
bool battery;
bool rtc;
bool rumble;
unsigned romsize;
unsigned ramsize;
bool cgb;
bool cgbonly;
} info;
};
GameBoyCartridge::GameBoyCartridge(uint8_t *romdata, unsigned romsize) {
markup = "";
if(romsize < 0x4000) return;
info.mapper = "unknown";
info.ram = false;
info.battery = false;
info.rtc = false;
info.rumble = false;
info.romsize = 0;
info.ramsize = 0;
unsigned base = romsize - 0x8000;
if(romdata[base + 0x0104] == 0xce && romdata[base + 0x0105] == 0xed
&& romdata[base + 0x0106] == 0x66 && romdata[base + 0x0107] == 0x66
&& romdata[base + 0x0108] == 0xcc && romdata[base + 0x0109] == 0x0d
&& romdata[base + 0x0147] >= 0x0b && romdata[base + 0x0147] <= 0x0d
) {
//MMM01 stores header at bottom of image
//flip this around for consistency with all other mappers
uint8_t header[0x8000];
memcpy(header, romdata + base, 0x8000);
memmove(romdata + 0x8000, romdata, romsize - 0x8000);
memcpy(romdata, header, 0x8000);
}
info.cgb = (romdata[0x0143] & 0x80) == 0x80;
info.cgbonly = (romdata[0x0143] & 0xc0) == 0xc0;
switch(romdata[0x0147]) {
case 0x00: info.mapper = "none"; break;
case 0x01: info.mapper = "MBC1"; break;
case 0x02: info.mapper = "MBC1"; info.ram = true; break;
case 0x03: info.mapper = "MBC1"; info.ram = true; info.battery = true; break;
case 0x05: info.mapper = "MBC2"; info.ram = true; break;
case 0x06: info.mapper = "MBC2"; info.ram = true; info.battery = true; break;
case 0x08: info.mapper = "none"; info.ram = true; break;
case 0x09: info.mapper = "MBC0"; info.ram = true; info.battery = true; break;
case 0x0b: info.mapper = "MMM01"; break;
case 0x0c: info.mapper = "MMM01"; info.ram = true; break;
case 0x0d: info.mapper = "MMM01"; info.ram = true; info.battery = true; break;
case 0x0f: info.mapper = "MBC3"; info.rtc = true; info.battery = true; break;
case 0x10: info.mapper = "MBC3"; info.rtc = true; info.ram = true; info.battery = true; break;
case 0x11: info.mapper = "MBC3"; break;
case 0x12: info.mapper = "MBC3"; info.ram = true; break;
case 0x13: info.mapper = "MBC3"; info.ram = true; info.battery = true; break;
case 0x19: info.mapper = "MBC5"; break;
case 0x1a: info.mapper = "MBC5"; info.ram = true; break;
case 0x1b: info.mapper = "MBC5"; info.ram = true; info.battery = true; break;
case 0x1c: info.mapper = "MBC5"; info.rumble = true; break;
case 0x1d: info.mapper = "MBC5"; info.rumble = true; info.ram = true; break;
case 0x1e: info.mapper = "MBC5"; info.rumble = true; info.ram = true; info.battery = true; break;
case 0xfc: break; //Pocket Camera
case 0xfd: break; //Bandai TAMA5
case 0xfe: info.mapper = "HuC3"; break;
case 0xff: info.mapper = "HuC1"; info.ram = true; info.battery = true; break;
}
switch(romdata[0x0148]) { default:
case 0x00: info.romsize = 2 * 16 * 1024; break;
case 0x01: info.romsize = 4 * 16 * 1024; break;
case 0x02: info.romsize = 8 * 16 * 1024; break;
case 0x03: info.romsize = 16 * 16 * 1024; break;
case 0x04: info.romsize = 32 * 16 * 1024; break;
case 0x05: info.romsize = 64 * 16 * 1024; break;
case 0x06: info.romsize = 128 * 16 * 1024; break;
case 0x07: info.romsize = 256 * 16 * 1024; break;
case 0x52: info.romsize = 72 * 16 * 1024; break;
case 0x53: info.romsize = 80 * 16 * 1024; break;
case 0x54: info.romsize = 96 * 16 * 1024; break;
}
switch(romdata[0x0149]) { default:
case 0x00: info.ramsize = 0 * 1024; break;
case 0x01: info.ramsize = 2 * 1024; break;
case 0x02: info.ramsize = 8 * 1024; break;
case 0x03: info.ramsize = 32 * 1024; break;
}
if(info.mapper == "MBC2") info.ramsize = 512; //512 x 4-bit
markup = "";
markup.append("cartridge\n");
markup.append(" board type=", info.mapper, "\n");
markup.append(" rom name=program.rom size=0x", hex(romsize), "\n");
if(info.ramsize > 0) markup.append(" ram name=save.ram size=0x", hex(info.ramsize), "\n");
}
}
#endif

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,31 +0,0 @@
#ifndef NALL_EMULATION_SUFAMI_TURBO_HPP
#define NALL_EMULATION_SUFAMI_TURBO_HPP
#include <nall/sha256.hpp>
#include <nall/string.hpp>
namespace nall {
struct SufamiTurboCartridge {
string markup;
inline SufamiTurboCartridge(const uint8_t *data, unsigned size);
};
SufamiTurboCartridge::SufamiTurboCartridge(const uint8_t *data, unsigned size) {
markup = "";
if(size < 0x20000) return; //too small to be a valid game?
if(memcmp(data, "BANDAI SFC-ADX", 14)) return; //missing required header?
unsigned romsize = data[0x36] * 0x20000; //128KB
unsigned ramsize = data[0x37] * 0x800; //2KB
bool linkable = data[0x35] != 0x00; //TODO: unconfirmed
markup.append("cartridge", linkable ? " linkable" : "", "\n");
markup.append(" rom name=program.rom size=0x", hex(romsize), "\n");
if(ramsize)
markup.append(" ram name=save.ram size=0x", hex(ramsize), "\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,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>higan</string>
<key>CFBundleExecutable</key>
<string>higan</string>
<key>CFBundleIconFile</key>
<string>higan.icns</string>
</dict>
</plist>

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -1,149 +0,0 @@
#ifndef EMULATOR_HPP
#define EMULATOR_HPP
namespace Emulator {
static const char Name[] = "higan";
static const char Version[] = "093";
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/invoke.hpp>
#include <nall/priority-queue.hpp>
#include <nall/property.hpp>
#include <nall/random.hpp>
#include <nall/serializer.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,119 +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 = analog (absolute)
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) { return 0u; }
virtual void videoRefresh(const uint32_t*, unsigned, unsigned, unsigned) {}
virtual void audioSample(int16_t, int16_t) {}
virtual int16_t inputPoll(unsigned, unsigned, unsigned) { return 0; }
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 red, uint16_t green, uint16_t blue) { return bind->videoColor(source, red, green, blue); }
void videoRefresh(const uint32_t* data, unsigned pitch, unsigned width, unsigned height) { return bind->videoRefresh(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); }
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
virtual void paletteUpdate() {}
//debugger functions
virtual bool tracerEnable(bool) { return false; }
virtual void exportMemory() {}
};
}
#endif

View File

@@ -1,16 +0,0 @@
fc_objects := fc-interface fc-system fc-scheduler fc-input
fc_objects += fc-memory fc-cartridge fc-cpu fc-apu fc-ppu
fc_objects += fc-cheat fc-video
objects += $(fc_objects)
obj/fc-interface.o: $(fc)/interface/interface.cpp $(call rwildcard,$(fc)/interface/)
obj/fc-system.o: $(fc)/system/system.cpp $(call rwildcard,$(fc)/system/)
obj/fc-scheduler.o: $(fc)/scheduler/scheduler.cpp $(call rwildcard,$(fc)/scheduler/)
obj/fc-input.o: $(fc)/input/input.cpp $(call rwildcard,$(fc)/input/)
obj/fc-memory.o: $(fc)/memory/memory.cpp $(call rwildcard,$(fc)/memory/)
obj/fc-cartridge.o: $(fc)/cartridge/cartridge.cpp $(call rwildcard,$(fc)/cartridge/)
obj/fc-cpu.o: $(fc)/cpu/cpu.cpp $(call rwildcard,$(fc)/cpu/)
obj/fc-apu.o: $(fc)/apu/apu.cpp $(call rwildcard,$(fc)/apu/)
obj/fc-ppu.o: $(fc)/ppu/ppu.cpp $(call rwildcard,$(fc)/ppu/)
obj/fc-cheat.o: $(fc)/cheat/cheat.cpp $(call rwildcard,$(fc)/cheat/)
obj/fc-video.o: $(fc)/video/video.cpp $(call rwildcard,$(fc)/video/)

View File

@@ -1,329 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
#include "envelope.cpp"
#include "sweep.cpp"
#include "pulse.cpp"
#include "triangle.cpp"
#include "noise.cpp"
#include "dmc.cpp"
#include "serialization.cpp"
APU apu;
const uint8 APU::length_counter_table[32] = {
0x0a, 0xfe, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, 0xa0, 0x08, 0x3c, 0x0a, 0x0e, 0x0c, 0x1a, 0x0e,
0x0c, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, 0xc0, 0x18, 0x48, 0x1a, 0x10, 0x1c, 0x20, 0x1e,
};
const uint16 APU::ntsc_noise_period_table[16] = {
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068,
};
const uint16 APU::pal_noise_period_table[16] = {
4, 7, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778,
};
const uint16 APU::ntsc_dmc_period_table[16] = {
428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54,
};
const uint16 APU::pal_dmc_period_table[16] = {
398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50,
};
void APU::Main() {
apu.main();
}
void APU::main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
unsigned pulse_output, triangle_output, noise_output, dmc_output;
pulse_output = pulse[0].clock();
pulse_output += pulse[1].clock();
triangle_output = triangle.clock();
noise_output = noise.clock();
dmc_output = dmc.clock();
clock_frame_counter_divider();
signed output = pulse_dac[pulse_output] + dmc_triangle_noise_dac[dmc_output][triangle_output][noise_output];
output = filter.run_hipass_strong(output);
output += cartridge_sample;
output = filter.run_hipass_weak(output);
//output = filter.run_lopass(output);
output = sclamp<16>(output);
interface->audioSample(output, output);
tick();
}
}
void APU::tick() {
clock += 12;
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
}
void APU::set_irq_line() {
cpu.set_irq_apu_line(frame.irq_pending || dmc.irq_pending);
}
void APU::set_sample(int16 sample) {
cartridge_sample = sample;
}
void APU::power() {
filter.hipass_strong = 0;
filter.hipass_weak = 0;
filter.lopass = 0;
pulse[0].power();
pulse[1].power();
triangle.power();
noise.power();
dmc.power();
}
void APU::reset() {
create(APU::Main, 21477272);
pulse[0].reset();
pulse[1].reset();
triangle.reset();
noise.reset();
dmc.reset();
frame.irq_pending = 0;
frame.mode = 0;
frame.counter = 0;
frame.divider = 1;
enabled_channels = 0;
cartridge_sample = 0;
set_irq_line();
}
uint8 APU::read(uint16 addr) {
if(addr == 0x4015) {
uint8 result = 0x00;
result |= pulse[0].length_counter ? 0x01 : 0;
result |= pulse[1].length_counter ? 0x02 : 0;
result |= triangle.length_counter ? 0x04 : 0;
result |= noise.length_counter ? 0x08 : 0;
result |= dmc.length_counter ? 0x10 : 0;
result |= frame.irq_pending ? 0x40 : 0;
result |= dmc.irq_pending ? 0x80 : 0;
frame.irq_pending = false;
set_irq_line();
return result;
}
return cpu.mdr();
}
void APU::write(uint16 addr, uint8 data) {
const unsigned n = (addr >> 2) & 1; //pulse#
switch(addr) {
case 0x4000: case 0x4004:
pulse[n].duty = data >> 6;
pulse[n].envelope.loop_mode = data & 0x20;
pulse[n].envelope.use_speed_as_volume = data & 0x10;
pulse[n].envelope.speed = data & 0x0f;
break;
case 0x4001: case 0x4005:
pulse[n].sweep.enable = data & 0x80;
pulse[n].sweep.period = (data & 0x70) >> 4;
pulse[n].sweep.decrement = data & 0x08;
pulse[n].sweep.shift = data & 0x07;
pulse[n].sweep.reload = true;
break;
case 0x4002: case 0x4006:
pulse[n].period = (pulse[n].period & 0x0700) | (data << 0);
pulse[n].sweep.pulse_period = (pulse[n].sweep.pulse_period & 0x0700) | (data << 0);
break;
case 0x4003: case 0x4007:
pulse[n].period = (pulse[n].period & 0x00ff) | (data << 8);
pulse[n].sweep.pulse_period = (pulse[n].sweep.pulse_period & 0x00ff) | (data << 8);
pulse[n].duty_counter = 7;
pulse[n].envelope.reload_decay = true;
if(enabled_channels & (1 << n)) {
pulse[n].length_counter = length_counter_table[(data >> 3) & 0x1f];
}
break;
case 0x4008:
triangle.halt_length_counter = data & 0x80;
triangle.linear_length = data & 0x7f;
break;
case 0x400a:
triangle.period = (triangle.period & 0x0700) | (data << 0);
break;
case 0x400b:
triangle.period = (triangle.period & 0x00ff) | (data << 8);
triangle.reload_linear = true;
if(enabled_channels & (1 << 2)) {
triangle.length_counter = length_counter_table[(data >> 3) & 0x1f];
}
break;
case 0x400c:
noise.envelope.loop_mode = data & 0x20;
noise.envelope.use_speed_as_volume = data & 0x10;
noise.envelope.speed = data & 0x0f;
break;
case 0x400e:
noise.short_mode = data & 0x80;
noise.period = data & 0x0f;
break;
case 0x400f:
noise.envelope.reload_decay = true;
if(enabled_channels & (1 << 3)) {
noise.length_counter = length_counter_table[(data >> 3) & 0x1f];
}
break;
case 0x4010:
dmc.irq_enable = data & 0x80;
dmc.loop_mode = data & 0x40;
dmc.period = data & 0x0f;
dmc.irq_pending = dmc.irq_pending && dmc.irq_enable && !dmc.loop_mode;
set_irq_line();
break;
case 0x4011:
dmc.dac_latch = data & 0x7f;
break;
case 0x4012:
dmc.addr_latch = data;
break;
case 0x4013:
dmc.length_latch = data;
break;
case 0x4015:
if((data & 0x01) == 0) pulse[0].length_counter = 0;
if((data & 0x02) == 0) pulse[1].length_counter = 0;
if((data & 0x04) == 0) triangle.length_counter = 0;
if((data & 0x08) == 0) noise.length_counter = 0;
(data & 0x10) ? dmc.start() : dmc.stop();
dmc.irq_pending = false;
set_irq_line();
enabled_channels = data & 0x1f;
break;
case 0x4017:
frame.mode = data >> 6;
frame.counter = 0;
if(frame.mode & 2) clock_frame_counter();
if(frame.mode & 1) {
frame.irq_pending = false;
set_irq_line();
}
frame.divider = FrameCounter::NtscPeriod;
break;
}
}
signed APU::Filter::run_hipass_strong(signed sample) {
hipass_strong += ((((int64)sample << 16) - (hipass_strong >> 16)) * HiPassStrong) >> 16;
return sample - (hipass_strong >> 32);
}
signed APU::Filter::run_hipass_weak(signed sample) {
hipass_weak += ((((int64)sample << 16) - (hipass_weak >> 16)) * HiPassWeak) >> 16;
return sample - (hipass_weak >> 32);
}
signed APU::Filter::run_lopass(signed sample) {
lopass += ((((int64)sample << 16) - (lopass >> 16)) * LoPass) >> 16;
return (lopass >> 32);
}
void APU::clock_frame_counter() {
frame.counter++;
if(frame.counter & 1) {
pulse[0].clock_length();
pulse[0].sweep.clock(0);
pulse[1].clock_length();
pulse[1].sweep.clock(1);
triangle.clock_length();
noise.clock_length();
}
pulse[0].envelope.clock();
pulse[1].envelope.clock();
triangle.clock_linear_length();
noise.envelope.clock();
if(frame.counter == 0) {
if(frame.mode & 2) frame.divider += FrameCounter::NtscPeriod;
if(frame.mode == 0) {
frame.irq_pending = true;
set_irq_line();
}
}
}
void APU::clock_frame_counter_divider() {
frame.divider -= 2;
if(frame.divider <= 0) {
clock_frame_counter();
frame.divider += FrameCounter::NtscPeriod;
}
}
APU::APU() {
for(unsigned amp = 0; amp < 32; amp++) {
if(amp == 0) {
pulse_dac[amp] = 0;
} else {
pulse_dac[amp] = 16384.0 * 95.88 / (8128.0 / amp + 100.0);
}
}
for(unsigned dmc_amp = 0; dmc_amp < 128; dmc_amp++) {
for(unsigned triangle_amp = 0; triangle_amp < 16; triangle_amp++) {
for(unsigned noise_amp = 0; noise_amp < 16; noise_amp++) {
if(dmc_amp == 0 && triangle_amp == 0 && noise_amp == 0) {
dmc_triangle_noise_dac[dmc_amp][triangle_amp][noise_amp] = 0;
} else {
dmc_triangle_noise_dac[dmc_amp][triangle_amp][noise_amp]
= 16384.0 * 159.79 / (100.0 + 1.0 / (triangle_amp / 8227.0 + noise_amp / 12241.0 + dmc_amp / 22638.0));
}
}
}
}
}
}

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,117 +0,0 @@
void APU::DMC::start() {
if(length_counter == 0) {
read_addr = 0x4000 + (addr_latch << 6);
length_counter = (length_latch << 4) + 1;
}
}
void APU::DMC::stop() {
length_counter = 0;
dma_delay_counter = 0;
cpu.set_rdy_line(1);
cpu.set_rdy_addr(false);
}
uint8 APU::DMC::clock() {
uint8 result = dac_latch;
if(dma_delay_counter > 0) {
dma_delay_counter--;
if(dma_delay_counter == 1) {
cpu.set_rdy_addr(true, 0x8000 | read_addr);
} else if(dma_delay_counter == 0) {
cpu.set_rdy_line(1);
cpu.set_rdy_addr(false);
dma_buffer = cpu.mdr();
have_dma_buffer = true;
length_counter--;
read_addr++;
if(length_counter == 0) {
if(loop_mode) {
start();
} else if(irq_enable) {
irq_pending = true;
apu.set_irq_line();
}
}
}
}
if(--period_counter == 0) {
if(have_sample) {
signed delta = (((sample >> bit_counter) & 1) << 2) - 2;
unsigned data = dac_latch + delta;
if((data & 0x80) == 0) dac_latch = data;
}
if(++bit_counter == 0) {
if(have_dma_buffer) {
have_sample = true;
sample = dma_buffer;
have_dma_buffer = false;
} else {
have_sample = false;
}
}
period_counter = ntsc_dmc_period_table[period];
}
if(length_counter > 0 && have_dma_buffer == false && dma_delay_counter == 0) {
cpu.set_rdy_line(0);
dma_delay_counter = 4;
}
return result;
}
void APU::DMC::power() {
}
void APU::DMC::reset() {
length_counter = 0;
irq_pending = 0;
period = 0;
period_counter = ntsc_dmc_period_table[0];
irq_enable = 0;
loop_mode = 0;
dac_latch = 0;
addr_latch = 0;
length_latch = 0;
read_addr = 0;
dma_delay_counter = 0;
bit_counter = 0;
have_dma_buffer = 0;
dma_buffer = 0;
have_sample = 0;
sample = 0;
}
void APU::DMC::serialize(serializer& s) {
s.integer(length_counter);
s.integer(irq_pending);
s.integer(period);
s.integer(period_counter);
s.integer(irq_enable);
s.integer(loop_mode);
s.integer(dac_latch);
s.integer(addr_latch);
s.integer(length_latch);
s.integer(read_addr);
s.integer(dma_delay_counter);
s.integer(bit_counter);
s.integer(have_dma_buffer);
s.integer(dma_buffer);
s.integer(have_sample);
s.integer(sample);
}

View File

@@ -1,32 +0,0 @@
struct DMC {
unsigned length_counter;
bool irq_pending;
uint4 period;
unsigned period_counter;
bool irq_enable;
bool loop_mode;
uint8 dac_latch;
uint8 addr_latch;
uint8 length_latch;
uint15 read_addr;
unsigned dma_delay_counter;
uint3 bit_counter;
bool have_dma_buffer;
uint8 dma_buffer;
bool have_sample;
uint8 sample;
void start();
void stop();
uint8 clock();
void power();
void reset();
void serialize(serializer&);
} dmc;

View File

@@ -1,39 +0,0 @@
unsigned APU::Envelope::volume() const {
return use_speed_as_volume ? speed : decay_volume;
}
void APU::Envelope::clock() {
if(reload_decay) {
reload_decay = false;
decay_volume = 0x0f;
decay_counter = speed + 1;
return;
}
if(--decay_counter == 0) {
decay_counter = speed + 1;
if(decay_volume || loop_mode) decay_volume--;
}
}
void APU::Envelope::power() {
}
void APU::Envelope::reset() {
speed = 0;
use_speed_as_volume = 0;
loop_mode = 0;
reload_decay = 0;
decay_counter = 0;
decay_volume = 0;
}
void APU::Envelope::serialize(serializer& s) {
s.integer(speed);
s.integer(use_speed_as_volume);
s.integer(loop_mode);
s.integer(reload_decay);
s.integer(decay_counter);
s.integer(decay_volume);
}

View File

@@ -1,16 +0,0 @@
struct Envelope {
uint4 speed;
bool use_speed_as_volume;
bool loop_mode;
bool reload_decay;
uint8 decay_counter;
uint4 decay_volume;
unsigned volume() const;
void clock();
void power();
void reset();
void serialize(serializer&);
};

View File

@@ -1,57 +0,0 @@
void APU::Noise::clock_length() {
if(envelope.loop_mode == 0) {
if(length_counter > 0) length_counter--;
}
}
uint8 APU::Noise::clock() {
if(length_counter == 0) return 0;
uint8 result = (lfsr & 1) ? envelope.volume() : 0;
if(--period_counter == 0) {
unsigned feedback;
if(short_mode) {
feedback = ((lfsr >> 0) & 1) ^ ((lfsr >> 6) & 1);
} else {
feedback = ((lfsr >> 0) & 1) ^ ((lfsr >> 1) & 1);
}
lfsr = (lfsr >> 1) | (feedback << 14);
period_counter = apu.ntsc_noise_period_table[period];
}
return result;
}
void APU::Noise::power() {
}
void APU::Noise::reset() {
length_counter = 0;
envelope.speed = 0;
envelope.use_speed_as_volume = 0;
envelope.loop_mode = 0;
envelope.reload_decay = 0;
envelope.decay_counter = 0;
envelope.decay_volume = 0;
period = 0;
period_counter = 1;
short_mode = 0;
lfsr = 1;
}
void APU::Noise::serialize(serializer& s) {
s.integer(length_counter);
envelope.serialize(s);
s.integer(period);
s.integer(period_counter);
s.integer(short_mode);
s.integer(lfsr);
}

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,51 +0,0 @@
void APU::Pulse::clock_length() {
if(envelope.loop_mode == 0) {
if(length_counter) length_counter--;
}
}
uint8 APU::Pulse::clock() {
if(sweep.check_period() == false) return 0;
if(length_counter == 0) return 0;
static const unsigned duty_table[] = {1, 2, 4, 6};
uint8 result = (duty_counter < duty_table[duty]) ? envelope.volume() : 0;
if(sweep.pulse_period < 0x008) result = 0;
if(--period_counter == 0) {
period_counter = (sweep.pulse_period + 1) * 2;
duty_counter++;
}
return result;
}
void APU::Pulse::power() {
envelope.power();
sweep.power();
}
void APU::Pulse::reset() {
envelope.reset();
sweep.reset();
length_counter = 0;
duty = 0;
duty_counter = 0;
period = 0;
period_counter = 1;
}
void APU::Pulse::serialize(serializer& s) {
s.integer(length_counter);
envelope.serialize(s);
sweep.serialize(s);
s.integer(duty);
s.integer(duty_counter);
s.integer(period);
s.integer(period_counter);
}

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,28 +0,0 @@
void APU::serialize(serializer& s) {
Thread::serialize(s);
filter.serialize(s);
pulse[0].serialize(s);
pulse[1].serialize(s);
triangle.serialize(s);
dmc.serialize(s);
frame.serialize(s);
s.integer(enabled_channels);
s.integer(cartridge_sample);
}
void APU::Filter::serialize(serializer& s) {
s.integer(hipass_strong);
s.integer(hipass_weak);
s.integer(lopass);
}
void APU::FrameCounter::serialize(serializer& s) {
s.integer(irq_pending);
s.integer(mode);
s.integer(counter);
s.integer(divider);
}

View File

@@ -1,53 +0,0 @@
bool APU::Sweep::check_period() {
if(pulse_period > 0x7ff) return false;
if(decrement == 0) {
if((pulse_period + (pulse_period >> shift)) & 0x800) return false;
}
return true;
}
void APU::Sweep::clock(unsigned channel) {
if(--counter == 0) {
counter = period + 1;
if(enable && shift && pulse_period > 8) {
signed delta = pulse_period >> shift;
if(decrement) {
pulse_period -= delta;
if(channel == 0) pulse_period--;
} else if((pulse_period + delta) < 0x800) {
pulse_period += delta;
}
}
}
if(reload) {
reload = false;
counter = period + 1;
}
}
void APU::Sweep::power() {
shift = 0;
decrement = 0;
period = 0;
counter = 1;
enable = 0;
reload = 0;
pulse_period = 0;
}
void APU::Sweep::reset() {
}
void APU::Sweep::serialize(serializer& s) {
s.integer(shift);
s.integer(decrement);
s.integer(period);
s.integer(counter);
s.integer(enable);
s.integer(reload);
s.integer(pulse_period);
}

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,58 +0,0 @@
void APU::Triangle::clock_length() {
if(halt_length_counter == 0) {
if(length_counter > 0) length_counter--;
}
}
void APU::Triangle::clock_linear_length() {
if(reload_linear) {
linear_length_counter = linear_length;
} else if(linear_length_counter) {
linear_length_counter--;
}
if(halt_length_counter == 0) reload_linear = false;
}
uint8 APU::Triangle::clock() {
uint8 result = step_counter & 0x0f;
if((step_counter & 0x10) == 0) result ^= 0x0f;
if(length_counter == 0 || linear_length_counter == 0) return result;
if(--period_counter == 0) {
step_counter++;
period_counter = period + 1;
}
return result;
}
void APU::Triangle::power() {
reset();
}
void APU::Triangle::reset() {
length_counter = 0;
linear_length = 0;
halt_length_counter = 0;
period = 0;
period_counter = 1;
step_counter = 0;
linear_length_counter = 0;
reload_linear = 0;
}
void APU::Triangle::serialize(serializer& s) {
s.integer(length_counter);
s.integer(linear_length);
s.integer(halt_length_counter);
s.integer(period);
s.integer(period_counter);
s.integer(step_counter);
s.integer(linear_length_counter);
s.integer(reload_linear);
}

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,212 +0,0 @@
#include "bandai-fcg.cpp"
#include "konami-vrc1.cpp"
#include "konami-vrc2.cpp"
#include "konami-vrc3.cpp"
#include "konami-vrc4.cpp"
#include "konami-vrc6.cpp"
#include "konami-vrc7.cpp"
#include "nes-axrom.cpp"
#include "nes-bnrom.cpp"
#include "nes-cnrom.cpp"
#include "nes-exrom.cpp"
#include "nes-fxrom.cpp"
#include "nes-gxrom.cpp"
#include "nes-hkrom.cpp"
#include "nes-nrom.cpp"
#include "nes-pxrom.cpp"
#include "nes-sxrom.cpp"
#include "nes-txrom.cpp"
#include "nes-uxrom.cpp"
#include "sunsoft-5b.cpp"
uint8 Board::Memory::read(unsigned addr) const {
return data[mirror(addr, size)];
}
void Board::Memory::write(unsigned addr, uint8 byte) {
if(writable) data[mirror(addr, size)] = byte;
}
unsigned Board::mirror(unsigned addr, unsigned size) {
unsigned base = 0;
if(size) {
unsigned mask = 1 << 23;
while(addr >= size) {
while(!(addr & mask)) mask >>= 1;
addr -= mask;
if(size > mask) {
size -= mask;
base += mask;
}
mask >>= 1;
}
base += addr;
}
return base;
}
void Board::main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
cartridge.clock += 12 * 4095;
tick();
}
}
void Board::tick() {
cartridge.clock += 12;
if(cartridge.clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
}
uint8 Board::chr_read(unsigned addr) {
if(chrram.size) return chrram.data[mirror(addr, chrram.size)];
if(chrrom.size) return chrrom.data[mirror(addr, chrrom.size)];
return 0u;
}
void Board::chr_write(unsigned addr, uint8 data) {
if(chrram.size) chrram.data[mirror(addr, chrram.size)] = data;
}
void Board::power() {
}
void Board::reset() {
}
void Board::serialize(serializer& s) {
if(prgram.size) s.array(prgram.data, prgram.size);
if(chrram.size) s.array(chrram.data, chrram.size);
}
Board::Board(Markup::Node& document) {
cartridge.board = this;
auto cartridge = document["cartridge"];
information.type = cartridge["board/type"].data;
information.battery = cartridge["prg/ram/name"].exists();
auto prom = cartridge["prg/rom"];
auto pram = cartridge["prg/ram"];
auto crom = cartridge["chr/rom"];
auto cram = cartridge["chr/ram"];
prgrom.size = numeral(prom["size"].data);
prgram.size = numeral(pram["size"].data);
chrrom.size = numeral(crom["size"].data);
chrram.size = numeral(cram["size"].data);
if(prgrom.size) prgrom.data = new uint8[prgrom.size]();
if(prgram.size) prgram.data = new uint8[prgram.size]();
if(chrrom.size) chrrom.data = new uint8[chrrom.size]();
if(chrram.size) chrram.data = new uint8[chrram.size]();
if(prom["name"].data) interface->loadRequest(ID::ProgramROM, prom["name"].data);
if(pram["name"].data) interface->loadRequest(ID::ProgramRAM, pram["name"].data);
if(crom["name"].data) interface->loadRequest(ID::CharacterROM, crom["name"].data);
if(cram["name"].data) interface->loadRequest(ID::CharacterRAM, cram["name"].data);
if(pram["name"].data) Famicom::cartridge.memory.append({ID::ProgramRAM, pram["name"].data});
if(cram["name"].data) Famicom::cartridge.memory.append({ID::CharacterRAM, cram["name"].data});
prgram.writable = true;
chrram.writable = true;
}
Board::~Board() {
}
Board* Board::load(string manifest) {
auto document = Markup::Document(manifest);
cartridge.information.title = document["information/title"].text();
string type = document["cartridge/board/type"].text();
if(type == "BANDAI-FCG" ) return new BandaiFCG(document);
if(type == "KONAMI-VRC-1") return new KonamiVRC1(document);
if(type == "KONAMI-VRC-2") return new KonamiVRC2(document);
if(type == "KONAMI-VRC-3") return new KonamiVRC3(document);
if(type == "KONAMI-VRC-4") return new KonamiVRC4(document);
if(type == "KONAMI-VRC-6") return new KonamiVRC6(document);
if(type == "KONAMI-VRC-7") return new KonamiVRC7(document);
if(type == "NES-AMROM" ) return new NES_AxROM(document);
if(type == "NES-ANROM" ) return new NES_AxROM(document);
if(type == "NES-AN1ROM" ) return new NES_AxROM(document);
if(type == "NES-AOROM" ) return new NES_AxROM(document);
if(type == "NES-BNROM" ) return new NES_BNROM(document);
if(type == "NES-CNROM" ) return new NES_CNROM(document);
if(type == "NES-EKROM" ) return new NES_ExROM(document);
if(type == "NES-ELROM" ) return new NES_ExROM(document);
if(type == "NES-ETROM" ) return new NES_ExROM(document);
if(type == "NES-EWROM" ) return new NES_ExROM(document);
if(type == "NES-FJROM" ) return new NES_FxROM(document);
if(type == "NES-FKROM" ) return new NES_FxROM(document);
if(type == "NES-GNROM" ) return new NES_GxROM(document);
if(type == "NES-MHROM" ) return new NES_GxROM(document);
if(type == "NES-HKROM" ) return new NES_HKROM(document);
if(type == "NES-NROM" ) return new NES_NROM(document);
if(type == "NES-NROM-128") return new NES_NROM(document);
if(type == "NES-NROM-256") return new NES_NROM(document);
if(type == "NES-PEEOROM" ) return new NES_PxROM(document);
if(type == "NES-PNROM" ) return new NES_PxROM(document);
if(type == "NES-SAROM" ) return new NES_SxROM(document);
if(type == "NES-SBROM" ) return new NES_SxROM(document);
if(type == "NES-SCROM" ) return new NES_SxROM(document);
if(type == "NES-SC1ROM" ) return new NES_SxROM(document);
if(type == "NES-SEROM" ) return new NES_SxROM(document);
if(type == "NES-SFROM" ) return new NES_SxROM(document);
if(type == "NES-SFEXPROM") return new NES_SxROM(document);
if(type == "NES-SGROM" ) return new NES_SxROM(document);
if(type == "NES-SHROM" ) return new NES_SxROM(document);
if(type == "NES-SH1ROM" ) return new NES_SxROM(document);
if(type == "NES-SIROM" ) return new NES_SxROM(document);
if(type == "NES-SJROM" ) return new NES_SxROM(document);
if(type == "NES-SKROM" ) return new NES_SxROM(document);
if(type == "NES-SLROM" ) return new NES_SxROM(document);
if(type == "NES-SL1ROM" ) return new NES_SxROM(document);
if(type == "NES-SL2ROM" ) return new NES_SxROM(document);
if(type == "NES-SL3ROM" ) return new NES_SxROM(document);
if(type == "NES-SLRROM" ) return new NES_SxROM(document);
if(type == "NES-SMROM" ) return new NES_SxROM(document);
if(type == "NES-SNROM" ) return new NES_SxROM(document);
if(type == "NES-SOROM" ) return new NES_SxROM(document);
if(type == "NES-SUROM" ) return new NES_SxROM(document);
if(type == "NES-SXROM" ) return new NES_SxROM(document);
if(type == "NES-TBROM" ) return new NES_TxROM(document);
if(type == "NES-TEROM" ) return new NES_TxROM(document);
if(type == "NES-TFROM" ) return new NES_TxROM(document);
if(type == "NES-TGROM" ) return new NES_TxROM(document);
if(type == "NES-TKROM" ) return new NES_TxROM(document);
if(type == "NES-TKSROM" ) return new NES_TxROM(document);
if(type == "NES-TLROM" ) return new NES_TxROM(document);
if(type == "NES-TL1ROM" ) return new NES_TxROM(document);
if(type == "NES-TL2ROM" ) return new NES_TxROM(document);
if(type == "NES-TLSROM" ) return new NES_TxROM(document);
if(type == "NES-TNROM" ) return new NES_TxROM(document);
if(type == "NES-TQROM" ) return new NES_TxROM(document);
if(type == "NES-TR1ROM" ) return new NES_TxROM(document);
if(type == "NES-TSROM" ) return new NES_TxROM(document);
if(type == "NES-TVROM" ) return new NES_TxROM(document);
if(type == "NES-UNROM" ) return new NES_UxROM(document);
if(type == "NES-UOROM" ) return new NES_UxROM(document);
if(type == "SUNSOFT-5B" ) return new Sunsoft5B(document);
return nullptr;
}

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,7 +0,0 @@
struct Board;
struct Chip {
Board& board;
void tick();
Chip(Board& board);
};

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,88 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
Cheat cheat;
bool Cheat::decode(string code_, unsigned& addr, unsigned& data, unsigned& comp) {
static bool initialize = false;
static uint8 mapProActionReplay[256], mapGameGenie[256];
if(initialize == false) {
initialize = true;
for(auto& n : mapProActionReplay) n = ~0;
mapProActionReplay['0'] = 0; mapProActionReplay['1'] = 1; mapProActionReplay['2'] = 2; mapProActionReplay['3'] = 3;
mapProActionReplay['4'] = 4; mapProActionReplay['5'] = 5; mapProActionReplay['6'] = 6; mapProActionReplay['7'] = 7;
mapProActionReplay['8'] = 8; mapProActionReplay['9'] = 9; mapProActionReplay['A'] = 10; mapProActionReplay['B'] = 11;
mapProActionReplay['C'] = 12; mapProActionReplay['D'] = 13; mapProActionReplay['E'] = 14; mapProActionReplay['F'] = 15;
for(auto& n : mapGameGenie) n = ~0;
mapGameGenie['A'] = 0; mapGameGenie['P'] = 1; mapGameGenie['Z'] = 2; mapGameGenie['L'] = 3;
mapGameGenie['G'] = 4; mapGameGenie['I'] = 5; mapGameGenie['T'] = 6; mapGameGenie['Y'] = 7;
mapGameGenie['E'] = 8; mapGameGenie['O'] = 9; mapGameGenie['X'] = 10; mapGameGenie['U'] = 11;
mapGameGenie['K'] = 12; mapGameGenie['S'] = 13; mapGameGenie['V'] = 14; mapGameGenie['N'] = 15;
}
string code = code_;
code.upper();
unsigned length = code.length(), bits = 0;
if(code.match("????:??")) {
code = {substr(code, 0, 4), substr(code, 5, 2)};
for(unsigned n = 0; n < 6; n++) if(mapProActionReplay[code[n]] > 15) return false;
bits = hex(code);
addr = (bits >> 8) & 0xffff;
data = (bits >> 0) & 0xff;
comp = ~0;
return true;
}
if(code.match("????:??:??")) {
code = {substr(code, 0, 4), substr(code, 5, 2), substr(code, 8, 2)};
for(unsigned n = 0; n < 8; n++) if(mapProActionReplay[code[n]] > 15) return false;
bits = hex(code);
addr = (bits >> 16) & 0xffff;
data = (bits >> 8) & 0xff;
comp = (bits >> 0) & 0xff;
return true;
}
if(length == 6) {
for(unsigned n = 0; n < 6; n++) if(mapGameGenie[code[n]] > 15) return false;
for(unsigned n = 0; n < 6; n++) bits |= mapGameGenie[code[n]] << (20 - n * 4);
unsigned addrTable[] = {10, 9, 8, 7, 2, 1, 0, 19, 14, 13, 12, 11, 6, 5, 4};
unsigned dataTable[] = {23, 18, 17, 16, 3, 22, 21, 20};
addr = 0x8000, data = 0x00, comp = ~0;
for(unsigned n = 0; n < 15; n++) addr |= bits & (1 << addrTable[n]) ? 0x4000 >> n : 0;
for(unsigned n = 0; n < 8; n++) data |= bits & (1 << dataTable[n]) ? 0x80 >> n : 0;
return true;
}
if(length == 8) {
for(unsigned n = 0; n < 8; n++) if(mapGameGenie[code[n]] > 15) return false;
for(unsigned n = 0; n < 8; n++) bits |= mapGameGenie[code[n]] << (28 - n * 4);
unsigned addrTable[] = {18, 17, 16, 15, 10, 9, 8, 27, 22, 21, 20, 19, 14, 13, 12};
unsigned dataTable[] = {31, 26, 25, 24, 3, 30, 29, 28};
unsigned compTable[] = {7, 2, 1, 0, 11, 6, 5, 4};
addr = 0x8000, data = 0x00, comp = 0x00;
for(unsigned n = 0; n < 15; n++) addr |= bits & (1 << addrTable[n]) ? 0x4000 >> n : 0;
for(unsigned n = 0; n < 8; n++) data |= bits & (1 << dataTable[n]) ? 0x80 >> n : 0;
for(unsigned n = 0; n < 8; n++) comp |= bits & (1 << compTable[n]) ? 0x80 >> n : 0;
return true;
}
return false;
}
void Cheat::synchronize() {
for(auto& n : override) n = false;
for(unsigned n = 0; n < size(); n++) {
override[operator[](n).addr] = true;
}
}
}

View File

@@ -1,14 +0,0 @@
struct CheatCode {
unsigned addr;
unsigned data;
unsigned comp;
};
struct Cheat : public vector<CheatCode> {
static bool decode(string code, unsigned& addr, unsigned& data, unsigned& comp);
void synchronize();
bool override[65536];
};
extern Cheat cheat;

View File

@@ -1,111 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
#include "timing.cpp"
#include "serialization.cpp"
CPU cpu;
void CPU::Enter() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
cpu.main();
}
}
void CPU::main() {
if(status.interrupt_pending) {
interrupt();
return;
}
exec();
}
void CPU::add_clocks(unsigned clocks) {
apu.clock -= clocks;
if(apu.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(apu.thread);
ppu.clock -= clocks;
if(ppu.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(ppu.thread);
cartridge.clock -= clocks;
if(cartridge.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cartridge.thread);
}
void CPU::power() {
R6502::power();
for(unsigned addr = 0; addr < 0x0800; addr++) ram[addr] = 0xff;
ram[0x0008] = 0xf7;
ram[0x0009] = 0xef;
ram[0x000a] = 0xdf;
ram[0x000f] = 0xbf;
}
void CPU::reset() {
R6502::reset();
create(CPU::Enter, 21477272);
regs.pc = bus.read(0xfffc) << 0;
regs.pc |= bus.read(0xfffd) << 8;
status.interrupt_pending = false;
status.nmi_pending = false;
status.nmi_line = 0;
status.irq_line = 0;
status.irq_apu_line = 0;
status.rdy_line = 1;
status.rdy_addr_valid = false;
status.rdy_addr_value = 0x0000;
status.oam_dma_pending = false;
status.oam_dma_page = 0x00;
status.controller_latch = false;
status.controller_port0 = 0;
status.controller_port1 = 0;
}
uint8 CPU::debugger_read(uint16 addr) {
return bus.read(addr);
}
uint8 CPU::ram_read(uint16 addr) {
return ram[addr & 0x07ff];
}
void CPU::ram_write(uint16 addr, uint8 data) {
ram[addr & 0x07ff] = data;
}
uint8 CPU::read(uint16 addr) {
if(addr == 0x4016) {
return (mdr() & 0xc0) | input.data(0);
}
if(addr == 0x4017) {
return (mdr() & 0xc0) | input.data(1);
}
return apu.read(addr);
}
void CPU::write(uint16 addr, uint8 data) {
if(addr == 0x4014) {
status.oam_dma_page = data;
status.oam_dma_pending = true;
}
if(addr == 0x4016) {
input.latch(data & 0x01);
}
return apu.write(addr, data);
}
}

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,23 +0,0 @@
void CPU::serialize(serializer& s) {
R6502::serialize(s);
Thread::serialize(s);
s.array(ram);
s.integer(status.interrupt_pending);
s.integer(status.nmi_pending);
s.integer(status.nmi_line);
s.integer(status.irq_line);
s.integer(status.irq_apu_line);
s.integer(status.rdy_line);
s.integer(status.rdy_addr_valid);
s.integer(status.rdy_addr_value);
s.integer(status.oam_dma_pending);
s.integer(status.oam_dma_page);
s.integer(status.controller_latch);
s.integer(status.controller_port0);
s.integer(status.controller_port1);
}

View File

@@ -1,64 +0,0 @@
uint8 CPU::op_read(uint16 addr) {
if(status.oam_dma_pending) {
status.oam_dma_pending = false;
op_read(addr);
oam_dma();
}
while(status.rdy_line == 0) {
regs.mdr = bus.read(status.rdy_addr_valid ? status.rdy_addr_value : addr);
add_clocks(12);
}
regs.mdr = bus.read(addr);
add_clocks(12);
return regs.mdr;
}
void CPU::op_write(uint16 addr, uint8 data) {
bus.write(addr, regs.mdr = data);
add_clocks(12);
}
void CPU::last_cycle() {
status.interrupt_pending = ((status.irq_line | status.irq_apu_line) & ~regs.p.i) | status.nmi_pending;
}
void CPU::nmi(uint16 &vector) {
if(status.nmi_pending) {
status.nmi_pending = false;
vector = 0xfffa;
}
}
void CPU::oam_dma() {
for(unsigned n = 0; n < 256; n++) {
uint8 data = op_read((status.oam_dma_page << 8) + n);
op_write(0x2004, data);
}
}
void CPU::set_nmi_line(bool line) {
//edge-sensitive (0->1)
if(!status.nmi_line && line) status.nmi_pending = true;
status.nmi_line = line;
}
void CPU::set_irq_line(bool line) {
//level-sensitive
status.irq_line = line;
}
void CPU::set_irq_apu_line(bool line) {
//level-sensitive
status.irq_apu_line = line;
}
void CPU::set_rdy_line(bool line) {
status.rdy_line = line;
}
void CPU::set_rdy_addr(bool valid, uint16 value) {
status.rdy_addr_valid = valid;
status.rdy_addr_value = value;
}

View File

@@ -1,62 +0,0 @@
#ifndef FC_HPP
#define FC_HPP
#include <emulator/emulator.hpp>
#include <processor/r6502/r6502.hpp>
namespace Famicom {
namespace Info {
static const char Name[] = "bnes";
static const unsigned SerializerVersion = 2;
}
}
/*
bnes - Famicom emulator
authors: byuu, Ryphecha
license: GPLv3
project started: 2011-09-05
*/
#include <libco/libco.h>
namespace Famicom {
struct Thread {
cothread_t thread;
unsigned frequency;
int64 clock;
inline void create(void (*entrypoint)(), unsigned frequency) {
if(thread) co_delete(thread);
thread = co_create(65536 * sizeof(void*), entrypoint);
this->frequency = frequency;
clock = 0;
}
inline void serialize(serializer& s) {
s.integer(frequency);
s.integer(clock);
}
inline Thread() : thread(nullptr) {
}
inline ~Thread() {
if(thread) co_delete(thread);
}
};
#include <fc/system/system.hpp>
#include <fc/scheduler/scheduler.hpp>
#include <fc/input/input.hpp>
#include <fc/memory/memory.hpp>
#include <fc/cartridge/cartridge.hpp>
#include <fc/cpu/cpu.hpp>
#include <fc/apu/apu.hpp>
#include <fc/ppu/ppu.hpp>
#include <fc/cheat/cheat.hpp>
#include <fc/video/video.hpp>
#include <fc/interface/interface.hpp>
}
#endif

View File

@@ -1,53 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
#include "serialization.cpp"
Input input;
void Input::latch(bool data) {
latchdata = data;
if(latchdata == 1) {
counter1 = 0;
counter2 = 0;
}
}
bool Input::data(bool port) {
bool result = 0;
if(port == 0) {
if(port1 == Device::Joypad) {
if(counter1 >= 8) return 1;
result = interface->inputPoll(0, 0u, counter1);
if(latchdata == 0) counter1++;
}
}
if(port == 1) {
if(port2 == Device::Joypad) {
if(counter2 >= 8) return 1;
result = interface->inputPoll(1, 0u, counter2);
if(latchdata == 0) counter2++;
}
}
return result;
}
void Input::connect(bool port, Device device) {
if(port == 0) port1 = device, counter1 = 0;
if(port == 1) port2 = device, counter2 = 0;
}
void Input::power() {
}
void Input::reset() {
latchdata = 0;
counter1 = 0;
counter2 = 0;
}
}

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,162 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
Interface* interface = nullptr;
string Interface::title() {
return cartridge.title();
}
double Interface::videoFrequency() {
return 21477272.0 / (262.0 * 1364.0 - 4.0);
}
double Interface::audioFrequency() {
return 21477272.0 / 12.0;
}
bool Interface::loaded() {
return cartridge.loaded();
}
string Interface::sha256() {
return cartridge.sha256();
}
unsigned Interface::group(unsigned id) {
switch(id) {
case ID::Manifest:
case ID::ProgramROM:
case ID::ProgramRAM:
case ID::CharacterROM:
case ID::CharacterRAM:
return 1;
}
throw;
}
void Interface::load(unsigned id) {
cartridge.load();
}
void Interface::save() {
for(auto& memory : cartridge.memory) {
saveRequest(memory.id, memory.name);
}
}
void Interface::load(unsigned id, const stream& stream) {
if(id == ID::Manifest) cartridge.information.markup = stream.text();
if(id == ID::ProgramROM) {
stream.read(cartridge.board->prgrom.data, min(cartridge.board->prgrom.size, stream.size()));
}
if(id == ID::ProgramRAM) {
stream.read(cartridge.board->prgram.data, min(cartridge.board->prgram.size, stream.size()));
}
if(id == ID::CharacterROM) {
stream.read(cartridge.board->chrrom.data, min(cartridge.board->chrrom.size, stream.size()));
}
if(id == ID::CharacterRAM) {
stream.read(cartridge.board->chrram.data, min(cartridge.board->chrram.size, stream.size()));
}
}
void Interface::save(unsigned id, const stream& stream) {
if(id == ID::ProgramRAM) {
stream.write(cartridge.board->prgram.data, cartridge.board->prgram.size);
}
if(id == ID::CharacterRAM) {
stream.write(cartridge.board->chrram.data, cartridge.board->chrram.size);
}
}
void Interface::unload() {
save();
cartridge.unload();
}
void Interface::power() {
system.power();
}
void Interface::reset() {
system.reset();
}
void Interface::run() {
system.run();
}
serializer Interface::serialize() {
system.runtosave();
return system.serialize();
}
bool Interface::unserialize(serializer& s) {
return system.unserialize(s);
}
void Interface::cheatSet(const lstring& list) {
cheat.reset();
for(auto& code : list) {
lstring codelist = code.split("+");
for(auto& part : codelist) {
unsigned addr, data, comp;
if(Cheat::decode(part, addr, data, comp)) cheat.append({addr, data, comp});
}
}
cheat.synchronize();
}
void Interface::paletteUpdate() {
video.generate_palette();
}
Interface::Interface() {
interface = this;
information.name = "Famicom";
information.width = 256;
information.height = 240;
information.overscan = true;
information.aspectRatio = 8.0 / 7.0;
information.resettable = true;
information.capability.states = true;
information.capability.cheats = true;
media.append({ID::Famicom, "Famicom", "fc", true});
{
Device device{0, ID::Port1 | ID::Port2, "Controller"};
device.input.append({0, 0, "A" });
device.input.append({1, 0, "B" });
device.input.append({2, 0, "Select"});
device.input.append({3, 0, "Start" });
device.input.append({4, 0, "Up" });
device.input.append({5, 0, "Down" });
device.input.append({6, 0, "Left" });
device.input.append({7, 0, "Right" });
device.order = {4, 5, 6, 7, 1, 0, 2, 3};
this->device.append(device);
}
port.append({0, "Port 1"});
port.append({1, "Port 2"});
for(auto& device : this->device) {
for(auto& port : this->port) {
if(device.portmask & (1 << port.id)) {
port.device.append(device);
}
}
}
}
}

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();
Interface();
private:
vector<Device> device;
};
extern Interface* interface;
#ifndef FC_HPP
}
#endif

View File

@@ -1,41 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
Bus bus;
//$0000-07ff = RAM (2KB)
//$0800-1fff = RAM (mirror)
//$2000-2007 = PPU
//$2008-3fff = PPU (mirror)
//$4000-4017 = APU + I/O
//$4018-ffff = Cartridge
uint8 Bus::read(uint16 addr) {
uint8 data = cartridge.prg_read(addr);
if(addr <= 0x1fff) data = cpu.ram_read(addr);
else if(addr <= 0x3fff) data = ppu.read(addr);
else if(addr <= 0x4017) data = cpu.read(addr);
if(cheat.override[addr]) {
for(unsigned n = 0; n < cheat.size(); n++) {
if(cheat[n].addr == addr) {
if(cheat[n].comp > 255 || cheat[n].comp == data) {
data = cheat[n].data;
break;
}
}
}
}
return data;
}
void Bus::write(uint16 addr, uint8 data) {
cartridge.prg_write(addr, data);
if(addr <= 0x1fff) return cpu.ram_write(addr, data);
if(addr <= 0x3fff) return ppu.write(addr, data);
if(addr <= 0x4017) return cpu.write(addr, data);
}
}

View File

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

View File

@@ -1,487 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
#include "serialization.cpp"
PPU ppu;
void PPU::Main() {
ppu.main();
}
void PPU::main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::PPU) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
raster_scanline();
}
}
void PPU::tick() {
if(status.ly == 240 && status.lx == 340) status.nmi_hold = 1;
if(status.ly == 241 && status.lx == 0) status.nmi_flag = status.nmi_hold;
if(status.ly == 241 && status.lx == 2) cpu.set_nmi_line(status.nmi_enable && status.nmi_flag);
if(status.ly == 260 && status.lx == 340) status.sprite_zero_hit = 0, status.sprite_overflow = 0;
if(status.ly == 260 && status.lx == 340) status.nmi_hold = 0;
if(status.ly == 261 && status.lx == 0) status.nmi_flag = status.nmi_hold;
if(status.ly == 261 && status.lx == 2) cpu.set_nmi_line(status.nmi_enable && status.nmi_flag);
clock += 4;
if(clock >= 0) co_switch(cpu.thread);
status.lx++;
}
void PPU::scanline() {
status.lx = 0;
if(++status.ly == 262) {
status.ly = 0;
frame();
}
cartridge.scanline(status.ly);
}
void PPU::frame() {
status.field ^= 1;
scheduler.exit(Scheduler::ExitReason::FrameEvent);
}
void PPU::power() {
}
void PPU::reset() {
create(PPU::Main, 21477272);
status.mdr = 0x00;
status.field = 0;
status.ly = 0;
status.bus_data = 0x00;
status.address_latch = 0;
status.vaddr = 0x0000;
status.taddr = 0x0000;
status.xaddr = 0x00;
status.nmi_hold = 0;
status.nmi_flag = 0;
//$2000
status.nmi_enable = false;
status.master_select = 0;
status.sprite_size = 0;
status.bg_addr = 0x0000;
status.sprite_addr = 0x0000;
status.vram_increment = 1;
//$2001
status.emphasis = 0;
status.sprite_enable = false;
status.bg_enable = false;
status.sprite_edge_enable = false;
status.bg_edge_enable = false;
status.grayscale = false;
//$2002
status.sprite_zero_hit = false;
status.sprite_overflow = false;
//$2003
status.oam_addr = 0x00;
for(auto& n : buffer) n = 0;
for(auto& n : ciram ) n = 0;
for(auto& n : cgram ) n = 0;
for(auto& n : oam ) n = 0;
}
uint8 PPU::read(uint16 addr) {
uint8 result = 0x00;
switch(addr & 7) {
case 2: //PPUSTATUS
result |= status.nmi_flag << 7;
result |= status.sprite_zero_hit << 6;
result |= status.sprite_overflow << 5;
result |= status.mdr & 0x1f;
status.address_latch = 0;
status.nmi_hold = 0;
cpu.set_nmi_line(status.nmi_flag = 0);
break;
case 4: //OAMDATA
result = oam[status.oam_addr];
if((status.oam_addr & 3) == 3) result &= 0xe3;
break;
case 7: //PPUDATA
if(raster_enable() && (status.ly <= 240 || status.ly == 261)) return 0x00;
addr = status.vaddr & 0x3fff;
if(addr <= 0x1fff) {
result = status.bus_data;
status.bus_data = cartridge.chr_read(addr);
} else if(addr <= 0x3eff) {
result = status.bus_data;
status.bus_data = cartridge.chr_read(addr);
} else if(addr <= 0x3fff) {
result = cgram_read(addr);
status.bus_data = cartridge.chr_read(addr);
}
status.vaddr += status.vram_increment;
break;
}
return result;
}
void PPU::write(uint16 addr, uint8 data) {
status.mdr = data;
switch(addr & 7) {
case 0: //PPUCTRL
status.nmi_enable = data & 0x80;
status.master_select = data & 0x40;
status.sprite_size = data & 0x20;
status.bg_addr = (data & 0x10) ? 0x1000 : 0x0000;
status.sprite_addr = (data & 0x08) ? 0x1000 : 0x0000;
status.vram_increment = (data & 0x04) ? 32 : 1;
status.taddr = (status.taddr & 0x73ff) | ((data & 0x03) << 10);
cpu.set_nmi_line(status.nmi_enable && status.nmi_hold && status.nmi_flag);
return;
case 1: //PPUMASK
status.emphasis = data >> 5;
status.sprite_enable = data & 0x10;
status.bg_enable = data & 0x08;
status.sprite_edge_enable = data & 0x04;
status.bg_edge_enable = data & 0x02;
status.grayscale = data & 0x01;
return;
case 2: //PPUSTATUS
return;
case 3: //OAMADDR
status.oam_addr = data;
return;
case 4: //OAMDATA
oam[status.oam_addr++] = data;
return;
case 5: //PPUSCROLL
if(status.address_latch == 0) {
status.xaddr = data & 0x07;
status.taddr = (status.taddr & 0x7fe0) | (data >> 3);
} else {
status.taddr = (status.taddr & 0x0c1f) | ((data & 0x07) << 12) | ((data >> 3) << 5);
}
status.address_latch ^= 1;
return;
case 6: //PPUADDR
if(status.address_latch == 0) {
status.taddr = (status.taddr & 0x00ff) | ((data & 0x3f) << 8);
} else {
status.taddr = (status.taddr & 0x7f00) | data;
status.vaddr = status.taddr;
}
status.address_latch ^= 1;
return;
case 7: //PPUDATA
if(raster_enable() && (status.ly <= 240 || status.ly == 261)) return;
addr = status.vaddr & 0x3fff;
if(addr <= 0x1fff) {
cartridge.chr_write(addr, data);
} else if(addr <= 0x3eff) {
cartridge.chr_write(addr, data);
} else if(addr <= 0x3fff) {
cgram_write(addr, data);
}
status.vaddr += status.vram_increment;
return;
}
}
uint8 PPU::ciram_read(uint16 addr) {
return ciram[addr & 0x07ff];
}
void PPU::ciram_write(uint16 addr, uint8 data) {
ciram[addr & 0x07ff] = data;
}
uint8 PPU::cgram_read(uint16 addr) {
if((addr & 0x13) == 0x10) addr &= ~0x10;
uint8 data = cgram[addr & 0x1f];
if(status.grayscale) data &= 0x30;
return data;
}
void PPU::cgram_write(uint16 addr, uint8 data) {
if((addr & 0x13) == 0x10) addr &= ~0x10;
cgram[addr & 0x1f] = data;
}
//
//vaddr = 0yyy VHYY YYYX XXXX
//yyy = fine Yscroll (y:d0-d2)
//V = V nametable (y:d8)
//H = H nametable (x:d8)
//YYYYY = Y nametable (y:d3-d7)
//XXXXX = X nametable (x:d3-d7)
bool PPU::raster_enable() const {
return (status.bg_enable || status.sprite_enable);
}
unsigned PPU::nametable_addr() const {
return 0x2000 + (status.vaddr & 0x0c00);
}
unsigned PPU::scrollx() const {
return ((status.vaddr & 0x1f) << 3) | status.xaddr;
}
unsigned PPU::scrolly() const {
return (((status.vaddr >> 5) & 0x1f) << 3) | ((status.vaddr >> 12) & 7);
}
unsigned PPU::sprite_height() const {
return status.sprite_size == 0 ? 8 : 16;
}
//
uint8 PPU::chr_load(uint16 addr) {
if(raster_enable() == false) return 0x00;
return cartridge.chr_read(addr);
}
//
void PPU::scrollx_increment() {
if(raster_enable() == false) return;
status.vaddr = (status.vaddr & 0x7fe0) | ((status.vaddr + 0x0001) & 0x001f);
if((status.vaddr & 0x001f) == 0x0000) {
status.vaddr ^= 0x0400;
}
}
void PPU::scrolly_increment() {
if(raster_enable() == false) return;
status.vaddr = (status.vaddr & 0x0fff) | ((status.vaddr + 0x1000) & 0x7000);
if((status.vaddr & 0x7000) == 0x0000) {
status.vaddr = (status.vaddr & 0x7c1f) | ((status.vaddr + 0x0020) & 0x03e0);
if((status.vaddr & 0x03e0) == 0x03c0) { //0x03c0 == 30 << 5; 30 * 8 = 240
status.vaddr &= 0x7c1f;
status.vaddr ^= 0x0800;
}
}
}
//
void PPU::raster_pixel() {
uint32* output = buffer + status.ly * 256;
unsigned mask = 0x8000 >> (status.xaddr + (status.lx & 7));
unsigned palette = 0, object_palette = 0;
bool object_priority = 0;
palette |= (raster.tiledatalo & mask) ? 1 : 0;
palette |= (raster.tiledatahi & mask) ? 2 : 0;
if(palette) {
unsigned attr = raster.attribute;
if(mask >= 256) attr >>= 2;
palette |= (attr & 3) << 2;
}
if(status.bg_enable == false) palette = 0;
if(status.bg_edge_enable == false && status.lx < 8) palette = 0;
if(status.sprite_enable == true)
for(signed sprite = 7; sprite >= 0; sprite--) {
if(status.sprite_edge_enable == false && status.lx < 8) continue;
if(raster.oam[sprite].id == 64) continue;
unsigned spritex = status.lx - raster.oam[sprite].x;
if(spritex >= 8) continue;
if(raster.oam[sprite].attr & 0x40) spritex ^= 7;
unsigned mask = 0x80 >> spritex;
unsigned sprite_palette = 0;
sprite_palette |= (raster.oam[sprite].tiledatalo & mask) ? 1 : 0;
sprite_palette |= (raster.oam[sprite].tiledatahi & mask) ? 2 : 0;
if(sprite_palette == 0) continue;
if(raster.oam[sprite].id == 0 && palette && status.lx != 255) status.sprite_zero_hit = 1;
sprite_palette |= (raster.oam[sprite].attr & 3) << 2;
object_priority = raster.oam[sprite].attr & 0x20;
object_palette = 16 + sprite_palette;
}
if(object_palette) {
if(palette == 0 || object_priority == 0) palette = object_palette;
}
if(raster_enable() == false) palette = 0;
output[status.lx] = video.palette[(status.emphasis << 6) | cgram_read(palette)];
}
void PPU::raster_sprite() {
if(raster_enable() == false) return;
unsigned n = raster.oam_iterator++;
signed ly = (status.ly == 261 ? -1 : status.ly);
unsigned y = ly - oam[(n * 4) + 0];
if(y >= sprite_height()) return;
if(raster.oam_counter == 8) {
status.sprite_overflow = 1;
return;
}
raster.soam[raster.oam_counter].id = n;
raster.soam[raster.oam_counter].y = oam[(n * 4) + 0];
raster.soam[raster.oam_counter].tile = oam[(n * 4) + 1];
raster.soam[raster.oam_counter].attr = oam[(n * 4) + 2];
raster.soam[raster.oam_counter].x = oam[(n * 4) + 3];
raster.oam_counter++;
}
void PPU::raster_scanline() {
if((status.ly >= 240 && status.ly <= 260)) {
for(unsigned x = 0; x < 341; x++) tick();
return scanline();
}
raster.oam_iterator = 0;
raster.oam_counter = 0;
for(unsigned n = 0; n < 8; n++) {
raster.soam[n].id = 64;
raster.soam[n].y = 0xff;
raster.soam[n].tile = 0xff;
raster.soam[n].attr = 0xff;
raster.soam[n].x = 0xff;
raster.soam[n].tiledatalo = 0;
raster.soam[n].tiledatahi = 0;
}
for(unsigned tile = 0; tile < 32; tile++) { // 0-255
unsigned nametable = chr_load(0x2000 | (status.vaddr & 0x0fff));
unsigned tileaddr = status.bg_addr + (nametable << 4) + (scrolly() & 7);
raster_pixel();
tick();
raster_pixel();
tick();
unsigned attribute = chr_load(0x23c0 | (status.vaddr & 0x0fc0) | ((scrolly() >> 5) << 3) | (scrollx() >> 5));
if(scrolly() & 16) attribute >>= 4;
if(scrollx() & 16) attribute >>= 2;
raster_pixel();
tick();
scrollx_increment();
if(tile == 31) scrolly_increment();
raster_pixel();
raster_sprite();
tick();
unsigned tiledatalo = chr_load(tileaddr + 0);
raster_pixel();
tick();
raster_pixel();
tick();
unsigned tiledatahi = chr_load(tileaddr + 8);
raster_pixel();
tick();
raster_pixel();
raster_sprite();
tick();
raster.nametable = (raster.nametable << 8) | nametable;
raster.attribute = (raster.attribute << 2) | (attribute & 3);
raster.tiledatalo = (raster.tiledatalo << 8) | tiledatalo;
raster.tiledatahi = (raster.tiledatahi << 8) | tiledatahi;
}
for(unsigned n = 0; n < 8; n++) raster.oam[n] = raster.soam[n];
for(unsigned sprite = 0; sprite < 8; sprite++) { //256-319
unsigned nametable = chr_load(0x2000 | (status.vaddr & 0x0fff));
tick();
if(raster_enable() && sprite == 0) status.vaddr = (status.vaddr & 0x7be0) | (status.taddr & 0x041f); //257
tick();
unsigned attribute = chr_load(0x23c0 | (status.vaddr & 0x0fc0) | ((scrolly() >> 5) << 3) | (scrollx() >> 5));
unsigned tileaddr = (sprite_height() == 8)
? status.sprite_addr + raster.oam[sprite].tile * 16
: ((raster.oam[sprite].tile & ~1) * 16) + ((raster.oam[sprite].tile & 1) * 0x1000);
tick();
tick();
unsigned spritey = (status.ly - raster.oam[sprite].y) & (sprite_height() - 1);
if(raster.oam[sprite].attr & 0x80) spritey ^= (sprite_height() - 1);
tileaddr += spritey + (spritey & 8);
raster.oam[sprite].tiledatalo = chr_load(tileaddr + 0);
tick();
tick();
raster.oam[sprite].tiledatahi = chr_load(tileaddr + 8);
tick();
tick();
if(raster_enable() && sprite == 6 && status.ly == 261) status.vaddr = status.taddr; //304
}
for(unsigned tile = 0; tile < 2; tile++) { //320-335
unsigned nametable = chr_load(0x2000 | (status.vaddr & 0x0fff));
unsigned tileaddr = status.bg_addr + (nametable << 4) + (scrolly() & 7);
tick();
tick();
unsigned attribute = chr_load(0x23c0 | (status.vaddr & 0x0fc0) | ((scrolly() >> 5) << 3) | (scrollx() >> 5));
if(scrolly() & 16) attribute >>= 4;
if(scrollx() & 16) attribute >>= 2;
tick();
scrollx_increment();
tick();
unsigned tiledatalo = chr_load(tileaddr + 0);
tick();
tick();
unsigned tiledatahi = chr_load(tileaddr + 8);
tick();
tick();
raster.nametable = (raster.nametable << 8) | nametable;
raster.attribute = (raster.attribute << 2) | (attribute & 3);
raster.tiledatalo = (raster.tiledatalo << 8) | tiledatalo;
raster.tiledatahi = (raster.tiledatahi << 8) | tiledatahi;
}
//336-339
chr_load(0x2000 | (status.vaddr & 0x0fff));
tick();
bool skip = (raster_enable() && status.field == 1 && status.ly == 261);
tick();
chr_load(0x2000 | (status.vaddr & 0x0fff));
tick();
tick();
//340
if(skip == false) tick();
return scanline();
}
}

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