Compare commits

...

161 Commits
v103 ... v104

Author SHA1 Message Date
Tim Allen
722a2d797d Document the rotation feature. 2017-08-12 20:58:02 +10:00
Tim Allen
60345fe8b5 Describe Game Boy rumble support. 2017-08-12 20:58:02 +10:00
Tim Allen
f3af7f177b A brief discussion of GBA save types. 2017-08-12 20:58:02 +10:00
Tim Allen
5f67b2a8fc Finish describing video shader issues. 2017-08-12 20:58:02 +10:00
Tim Allen
1ae228663d GBC save-states don't work in GB mode, quelle surprise. 2017-08-12 20:58:02 +10:00
Tim Allen
59eb05226f Finish the section about Game Boy Color games. 2017-08-12 20:58:02 +10:00
Tim Allen
fe9820481f Start fleshing out the "console-specific notes" section. 2017-08-12 20:58:02 +10:00
Tim Allen
1faa263a4a Address the idea of multithreading. 2017-08-12 20:58:02 +10:00
Tim Allen
45c8f09330 Be more descriptive about higan's overhead. 2017-08-12 20:58:02 +10:00
Tim Allen
09c9a55588 Give some reasons why games might run slow. 2017-08-12 20:58:02 +10:00
Tim Allen
defb1eedd3 Another entry for the FAQ. 2017-08-12 20:58:02 +10:00
Tim Allen
accffa8d1b A tool to check our docs for broken links.
Annoyingly, mkdocs won't validate inter- or intra-document links,
so we'll need to use a third-party tool instead.
2017-08-12 20:58:02 +10:00
Tim Allen
4c4a1dcb67 Fill out the FAQ. 2017-08-12 20:58:02 +10:00
Tim Allen
c52b05a0de More mental notes. 2017-08-12 20:58:02 +10:00
Tim Allen
5af4f45a25 Document higan's video sync/audio sync issues. 2017-08-12 20:58:02 +10:00
Tim Allen
a350195c8c Describe save states. 2017-08-12 20:58:02 +10:00
Tim Allen
55bae99bb6 More notes. 2017-08-12 20:58:02 +10:00
Tim Allen
0ca6aaf6b7 I've been reminded that region-selection is a thing. 2017-08-12 20:58:02 +10:00
Tim Allen
bc9e8c8f4a Document the "no audio → fast forward" behaviour. 2017-08-12 20:58:01 +10:00
Tim Allen
b26db6c9b8 Fix a broken link. 2017-08-12 20:58:01 +10:00
Tim Allen
246887d65c Document which drivers people should use. 2017-08-12 20:58:01 +10:00
Tim Allen
0749009652 Be clearer that some drivers don't support the basic shaders. 2017-08-12 20:58:01 +10:00
Tim Allen
3a967430eb Missing last newline. 2017-08-12 20:58:01 +10:00
Tim Allen
ce8f008894 Document using shaders. 2017-08-12 20:58:01 +10:00
Tim Allen
f6fab1a502 Add higan command-line docs. 2017-08-12 20:58:01 +10:00
Tim Allen
a8363c41ba Fix broken link. 2017-08-12 20:58:01 +10:00
Tim Allen
e888a607c1 Add a "Manifests and Game Folders" document.
I've occasionally wished for a single "what is a manifest" section I
could link to from other places; this is where it will go.
2017-08-12 20:58:01 +10:00
Tim Allen
fb52220b1b Move 'concepts' docs into a concepts folder. 2017-08-12 20:58:01 +10:00
Tim Allen
7760931ffc Link to RTD docs from the README. 2017-08-12 20:58:01 +10:00
Tim Allen
df2492c75e Give the Tools doco an intro, like Settings. 2017-08-12 20:58:01 +10:00
Tim Allen
6e6c29138e Make Settings tabs top-level sections.
Just like the tabs of the Tools window.
2017-08-12 20:58:01 +10:00
Tim Allen
5a44f249a4 Separate "how to import" from "what's the game library".
Changed "Configuration" to "Guides", because we want narrative
documentation for all kinds of things, not just documentation.
2017-08-12 20:58:01 +10:00
Tim Allen
3c3f1de317 Convert README docs to MkDocs format.
The existing documentation was getting *way* too long to be a single
document.

I've just bulk moved the existing content into the new structure, but it
definitely needs another pass to make the prose match, fix hyperlinks,
etc. etc.
2017-08-12 20:58:01 +10:00
Tim Allen
a99e601c87 Describe the rest of the interesting files. 2017-08-12 20:58:01 +10:00
Tim Allen
a62425745b Reminders for the future. 2017-08-12 20:58:01 +10:00
Tim Allen
232bcc0fa7 Fix typos 2017-08-12 20:58:01 +10:00
Tim Allen
926d65cfe0 Describe some files that appear in game folders. 2017-08-12 20:58:01 +10:00
Tim Allen
37a738a1a4 Discuss game patching. 2017-08-12 20:58:01 +10:00
Tim Allen
3e0adb1798 Move the discussion of SD2SNES-optimized MSU-1 patches into a separate paragraph. 2017-08-12 20:58:01 +10:00
Tim Allen
f5333c333c I forgot, higan *does* have some Satellaview emulation.
At least, it lets you choose "Satellaview" from the Expansion Port
menu. I have no idea how complete that is.
2017-08-12 20:58:01 +10:00
Tim Allen
427ffd97b8 Update for v103r17. 2017-08-12 20:58:01 +10:00
Tim Allen
8df735e6e2 Minor typo fixes. 2017-08-12 20:58:01 +10:00
Tim Allen
b7edf2abcf Reflect the menu-structure of 103r16.
Now that we have a Synchronize Video option again, I've added a place
in the FAQ to address why it does not play well with Synchronize Audio.
2017-08-12 20:58:01 +10:00
Tim Allen
50d5beda53 First draft of MSU-1 import docs. 2017-08-12 20:58:01 +10:00
Tim Allen
65dadd1576 Update the "Audio" tab to match 103r15. 2017-08-12 20:58:01 +10:00
Tim Allen
6f9839df62 Begin the MSU-1 section. 2017-08-12 20:58:01 +10:00
Tim Allen
781f1ff4c0 We should talk about patches at some point. 2017-08-12 20:58:01 +10:00
Tim Allen
66ef548781 Link to the BS-X Project page. 2017-08-12 20:58:00 +10:00
Tim Allen
cabb865d5b Fix typo. 2017-08-12 20:58:00 +10:00
Tim Allen
54ffe85479 Document the Super Game Boy. 2017-08-12 20:58:00 +10:00
Tim Allen
362d29c6a7 Make a proper heading for black Game Boy cartridges.
Also, remove the sections for importing and playing Game Boy and GBA
games... the Game Boy section was just going to be about black cartridges
anyway, and I have no idea what the GBA section was going to be for.
2017-08-12 20:58:00 +10:00
Tim Allen
ecbc3f6693 Fix underlining for the Satellaview heading. 2017-08-12 20:58:00 +10:00
Tim Allen
1cdd539115 Document the Sufami Turbo. 2017-08-12 20:58:00 +10:00
Tim Allen
1870788cb8 Reword for clarity. 2017-08-12 20:58:00 +10:00
Tim Allen
1b77a4ec8d Use italics for titles. 2017-08-12 20:58:00 +10:00
Tim Allen
b02c35edae The official name is "Satellaview".
Not "BS-X", and definitely not "Bandai Satellaview".
2017-08-12 20:58:00 +10:00
Tim Allen
e721064fe9 Document how to use BS-X games. 2017-08-12 20:58:00 +10:00
Tim Allen
f1e48b90ff Figured out what happens if you're missing firmware files. 2017-08-12 20:58:00 +10:00
Tim Allen
6099424f0f More table cleanups.
Wrap SHA256 hashes in `<code/>` so they'll all be visually the same length.

Make the references from DSP1 and DSP1B to the notes below more obviously
references, not content.

Reword the DSP1 note so that it makes sense even if you didn't just come
from the DSP1/1A row of the table.
2017-08-12 20:58:00 +10:00
Tim Allen
43c5368a30 Clean up table markup.
Use code, not pre, since we want inline markup, not block-level.

The attribute we want is named "rowspan", not "rows".
2017-08-12 20:58:00 +10:00
Tim Allen
f4bf02ba02 Replace the awkward nested lists of firmware info with a table. 2017-08-12 20:58:00 +10:00
Tim Allen
8c3d076bf4 Link to the official game folder docs. 2017-08-12 20:58:00 +10:00
Tim Allen
788bdb0ae2 Document importing regular and co-processor games. 2017-08-12 20:58:00 +10:00
Tim Allen
39822bc123 A rewording inspired by a forum thread.
Introduce the idea of "the console a game was intended to run on",
so it doesn't come out of left field in the "To play a game" instructions.
2017-08-12 20:58:00 +10:00
Tim Allen
6ba931987b Fix typo. 2017-08-12 20:58:00 +10:00
Tim Allen
4c4339d313 Add pointers to importing hints. 2017-08-12 20:57:59 +10:00
Tim Allen
d166c280c7 Add some TODO markers for completeness. 2017-08-12 20:57:59 +10:00
Tim Allen
73a37beae1 Fix typo. 2017-08-12 20:57:59 +10:00
Tim Allen
b61ca253de Document icarus. 2017-08-12 20:57:59 +10:00
Tim Allen
0ed9737dd4 We should document what goes into a game folder. 2017-08-12 20:57:59 +10:00
Tim Allen
adddc1dc0a We're going to need to document icarus, too. 2017-08-12 20:57:59 +10:00
Tim Allen
b021f5186c Move "Moving the Game Library" into its own section.
Also, move the higher-level questions about the game library
and game folders to the end of the section,
so people can more easily find the bits about importing types of game.
2017-08-12 20:57:59 +10:00
Tim Allen
1a68cb65eb Document higan's custom filesystem browser. 2017-08-12 20:57:59 +10:00
Tim Allen
82a64bb3ab Describe the Manifest Viewer. 2017-08-12 20:57:59 +10:00
Tim Allen
75f842cdb2 At least link to Quick Start near the top of the doc. 2017-08-12 20:57:59 +10:00
Tim Allen
9362726c65 Found some more info about higan and vsync. 2017-08-12 20:57:59 +10:00
Tim Allen
a3e1983d50 Remind myself to update the Video tab docs when it settles. 2017-08-12 20:57:59 +10:00
Tim Allen
20e3fdd1e3 higan 103r11 removed the "Mask Overscan" option. 2017-08-12 20:57:59 +10:00
Tim Allen
0bb6a3b0bc Document the State Manager. 2017-08-12 20:57:59 +10:00
Tim Allen
752139e66c We have a save-state section now, better use it. 2017-08-12 20:57:59 +10:00
Tim Allen
2f08bc3b1c higan v103r10 moved the per-game cheats.bml into the higan folder. 2017-08-12 20:57:59 +10:00
Tim Allen
f4c043ce50 higan v103r10 standardized on "quick state". 2017-08-12 20:57:59 +10:00
Tim Allen
1bbe78f94e higan v103r09 changed the way video scaling works. 2017-08-12 20:57:59 +10:00
Tim Allen
ef5f0e9c21 Correct and expand the Cheat Editor docs. 2017-08-12 20:57:59 +10:00
Tim Allen
83ae1579a0 Add basic info about the cheat editor. 2017-08-12 20:57:59 +10:00
Tim Allen
e0b4fe6419 higan v103r08 updated how quickstates work.
Also, I found some more potential status bar messages.
2017-08-12 20:57:59 +10:00
Tim Allen
efbfe854d2 higan v103r08 changed how Mask Overscan works. 2017-08-12 20:57:59 +10:00
Tim Allen
5f2fbd0d72 Bust "Drivers" into its own section.
Also, add some details about Windows drivers.
2017-08-12 20:57:59 +10:00
Tim Allen
e0c021660a Document the configuration dialog. 2017-08-12 20:57:59 +10:00
Tim Allen
89192cee6c Finish documenting all the menus. 2017-08-12 20:57:59 +10:00
Tim Allen
e8b9b6cd7b The docs are growing large enough we'll need a quickstart at the beginning. 2017-08-12 20:57:59 +10:00
Tim Allen
3efe3cdfbf Make a note to talk about save-states at some point. 2017-08-12 20:57:59 +10:00
Tim Allen
224bbe03f4 Finish documenting the Settings menu. 2017-08-12 20:57:59 +10:00
Tim Allen
aad87da68a Add a warning about installing higan as root. 2017-08-12 20:57:59 +10:00
Tim Allen
444ec756bd "Aspect Correction" doesn't affect full-screen! 2017-08-12 20:57:59 +10:00
Tim Allen
96ebd87db4 Document the Video Shader menu. 2017-08-12 20:57:59 +10:00
Tim Allen
8ef704c16b Standardize on "The X menu allows you to...". 2017-08-12 20:57:58 +10:00
Tim Allen
380130fbd8 Document the $console menu. 2017-08-12 20:57:58 +10:00
Tim Allen
859c5b587b Let's standardise on "The X menu". 2017-08-12 20:57:58 +10:00
Tim Allen
7eceacf78e Remind myself what appears in the status bar. 2017-08-12 20:57:58 +10:00
Tim Allen
b3b3690948 We need to document all the parts of the higan UI. 2017-08-12 20:57:58 +10:00
Tim Allen
331c53c896 Be consistent about console naming like higan is. 2017-08-12 20:57:58 +10:00
Tim Allen
fd2cdc261c Describe how the higan game library works. 2017-08-12 20:57:58 +10:00
Tim Allen
d366c6ff43 Document higan's console naming policy. 2017-08-12 20:57:58 +10:00
Tim Allen
eef90a8f14 How to obtain and install the GBA BIOS. 2017-08-12 20:57:58 +10:00
Tim Allen
00fecd96a4 Remind people to set up the GBA BIOS after installing. 2017-08-12 20:57:58 +10:00
Tim Allen
f23e54f6b1 Refer to directories as "folders" in Windows.
Sure, probably everybody is familiar with either term,
but Windows users are probably more familiar with the term
that shows up all over their UI.
2017-08-12 20:57:58 +10:00
Tim Allen
54e48aabbe Describe how to contribute to higan.
Since higan does not follow the conventional git development model,
we should prominently document how people can contribute.
2017-08-12 20:57:58 +10:00
Tim Allen
0902869032 Fix intra-document links. 2017-08-12 20:57:58 +10:00
Tim Allen
7d1685f7f5 Finish the build, install and uninstall instructions for Linux. 2017-08-12 20:57:58 +10:00
Tim Allen
775be26350 Let's have separate install and uninstall instructions for compiled Windows builds. 2017-08-12 20:57:58 +10:00
Tim Allen
d38893008c Remind me to mention PSG volume for the Mega Drive. 2017-08-12 20:57:58 +10:00
Tim Allen
d250096e48 Fix formatting in the Windows build instructions. 2017-08-12 20:57:58 +10:00
Tim Allen
68d57dc46c Make the Linux build instructions match the Windows ones. 2017-08-12 20:57:58 +10:00
Tim Allen
acbc52ab07 Update the Windows build instructions. 2017-08-12 20:57:58 +10:00
Tim Allen
7406d903e1 Apparently higan writes to %LOCALAPPDATA% in Windows. 2017-08-12 20:57:58 +10:00
Tim Allen
d739e33243 higan requires Windows 7, not XP. 2017-08-12 20:57:58 +10:00
Tim Allen
1b62f18139 first info about building for Linux 2017-08-12 20:57:58 +10:00
Tim Allen
d0d7436d50 Notes about what to cover for control config. 2017-08-12 20:57:58 +10:00
Tim Allen
08fc1f659a Wrote some stuff about installing on Windows.
Let's hope it's vaguely plausible!
2017-08-12 20:57:58 +10:00
Tim Allen
e8b2f22623 WIP 2017-08-12 20:57:58 +10:00
Tim Allen
cec9d91b39 Add internal hyperlinks to other sections 2017-08-12 20:57:58 +10:00
Tim Allen
11a3195e84 Fix typo 2017-08-12 20:57:58 +10:00
Tim Allen
1fb37f087f Record some official and unofficial higan resources. 2017-08-12 20:57:58 +10:00
Tim Allen
8ece9ed16b Remember to talk about GBA saves 2017-08-12 20:57:58 +10:00
Tim Allen
302b369781 Outline of a README file. 2017-08-12 20:57:58 +10:00
Tim Allen
ba384a7c48 Update to v104 release.
byuu says:

Changelog:

  - emulator/interface: removed unused Region struct
  - gba/cpu: optimized CPU::step() as much as I could for a slight
    speedup¹
  - gba/cpu: synchronize the APU better during FIFO updates
  - higan/md, icarus: add automatic region detection; make it the
    default option [hex\_usr]
      - picks NTSC-J if there's more than one match ... eventually, this
        will be a setting
  - higan/md, icarus: support all three combinations of SRAM (8-bit low,
    8-bit high, 16-bit)
  - processor/arm7tdmi: fix bug when changing to THUMB mode via MSR
    [MerryMage]
  - tomoko: redesigned crash detector to only occur once for all three
    ruby drivers
      - this will reduce disk thrashing since the configuration file
        only needs to be written out one extra time
      - technically, it's twice ... but we should've always been writing
        one out on first run in case it crashes then
  - tomoko: defaulted back to the safest ruby drivers, given the optimal
    drivers have some stability concerns

¹: minor errata: spotted a typo saying `synchronize(cpu)` when the CPU
is stopped, instead of `synchronize(ppu)`. This will be fixed in the v104
official 7zip archives.

I'm kind of rushing here but, it's really good timing for me to push out
a new official release. The blocking issues are resolved or close to it,
and we need lots of testing of the new major changes.

I'm going to consider this a semi-stable testing release and leave links
to v103 just in case.
2017-08-12 20:53:13 +10:00
Tim Allen
55f19c3e0d Update to v103r32 release.
byuu says:

Changelog:

  - Master System: merged Bus into CPU
  - Mega Drive: merged BusCPU into CPU; BusAPU into AU
  - Mega Drive: added TMSS emulation; disabled by default [hex\_usr]
      - VDP lockout not yet emulated
  - processor/arm7tdmi: renamed interrupt() to exception()
  - processor/arm7tdmi: CPSR.F (FIQ disable) flag is set on reset
  - processor/arm7tdmi: pipeline decode stage caches CPSR.T (THUMB mode)
    [MerryMage]
      - fixes `msr_tests.gba` test F
  - processor/arm7tdmi/disassembler: add PC address to left of currently
    executing instruction
  - processor/arm7tdmi: stop forcing CPSR.M (mode flags) bit 4 high (I
    don't know what really happens here)
  - processor/arm7tdmi: undefined instructions now generate Undefined
    0x4 exception
  - processor/arm7tdmi: thumbInstructionAddRegister masks PC by &~3
    instead of &~2
      - hopefully this is correct; &~2 felt very wrong
  - processor/arm7tdmi: thumbInstructionStackMultiple can use sequential
    timing for PC/LR PUSH/POP [Cydrak]
  - systems/Mega Drive.sys: added tmss.rom; enable with cpu version=1
  - tomoko: detect when a ruby video/audio/input driver crashes higan;
    disable it on next program startup

v104 blockers:

  - Mega Drive: support 8-bit SRAM (even if we don't support 16-bit;
    don't force 8-bit to 16-bit)
  - Mega Drive: add region detection support to icarus
  - ruby: add default audio device information so certain drivers won't
    default to silence out of the box
2017-08-12 02:02:09 +10:00
Tim Allen
406b6a61a5 Update to v103r31 release.
byuu says:

Changelog:

  - gba/cpu: slight speedup to CPU::step()
  - processor/arm7tdmi: fixed about ten bugs, ST018 and GBA games are
    now playable once again
  - processor/arm: removed core from codebase
  - processor/v30mz: code cleanup (renamed functions; updated
    instruction() for consistency with other cores)

It turns out on my much faster system, the new ARM7TDMI core is very
slightly slower than the old one (by about 2% or so FPS.) But the
CPU::step() improvement basically made it a wash.

So yeah, I'm in really serious trouble with how slow my GBA core is now.
Sigh.

As for higan/processor ... this concludes the first phase of major
cleanups and rewrites.

There will always be work to do, and I have two more phases in mind.

One is that a lot of the instruction disassemblers are very old. One
even uses sprintf still. I'd like to modernize them all. Also, the
ARM7TDMI core (and the ARM core before it) can't really disassemble
because the PC address used for instruction execution is not known prior
to calling instruction(), due to pipeline reload fetches that may occur
inside of said function. I had a nasty hack for debugging the new core,
but I'd like to come up with a clean way to allow tracing the new
ARM7TDMI core.

Another is that I'd still like to rename a lot of instruction function
names in various cores to be more descriptive. I really liked how the
LR35902 core came out there, and would like to get that level of detail
in with the other cores as well.
2017-08-10 21:26:02 +10:00
Tim Allen
1067566834 Update to v103r30 release.
byuu says:

Changelog:

  - processor/arm7tdmi: completed implemented
  - gba/cpu, sfc/coprocessor/armdsp: use arm7tdmi instead of arm
  - sfc/cpu: experimental fix for newly discovered HDMA emulation issue

Notes:

The ARM7TDMI core crashes pretty quickly when trying to run GBA games,
and I'm certain the same will be the case with the ST018. It was never
all that likely I could rewrite 70KiB of code in 20 hours and have it
work perfectly on the first try. So, now it's time for lots and lots of
debugging. Any help would *really* be appreciated, if anyone were up for
comparing the two implementations for regressions =^-^= I often have a
really hard time spotting simple typos that I make.

Also, the SNES HDMA fix is temporary. I would like it if testers could
run through a bunch of games that are known for being tricky with HDMA
(or if these aren't known to said tester, any games are fine then.) If
we can confirm regressions, then we'll know the fix is either incorrect
or incomplete. But if we don't find any, then it's a good sign that
we're on the right path.
2017-08-09 21:11:59 +10:00
Tim Allen
559eeccc89 Update to v103r29 release.
byuu says:

Changelog:

  - processor/arm7tdmi: implementation all nine remaining ARM
    instructions
  - processor/arm7tdmi: implemented five more THUMB instructions
    (sixteen remain)
2017-08-08 21:51:41 +10:00
Tim Allen
a72ff8b7fa Update to v103r28 release.
byuu says:

Changelog:

  - processor/arm7tdmi: implemented 10 of 19 ARM instructions
  - processor/arm7tdmi: implemented 1 of 22 THUMB instructions

Today's WIP was 6 hours of work, and yesterday's was 5 hours.

Half of today was just trying to come up with the design to use a
lambda-based dispatcher to map both instructions and disassembly,
similar to the 68K core. The problem is that the ARM core has 28 unique
bits, which is just far too many bits to have a full lookup table like
the 16-bit 68K core.

The thing I wanted more than anything else was to perform the opcode
bitfield decoding once, and have it decoded for both instructions and
the disassembler. It took three hours to come up with a design that
worked for the ARM half ... relying on #defines being able to pull in
other #defines that were declared and changed later after the first
one. But, I'm happy with it. The decoding is in the table building, as
it is with the 68K core. The decoding does happen at run-time on each
instruction invocation, but it has to be done.

As to the THUMB core, I can create a 64K-entry lambda table to cover all
possible encodings, and ... even though it's a cache killer, I've
decided to go for it, given the outstanding performance it obtained in
the M68K core, as well as considering that THUMB mode is far more common
in GBA games.

As to both cores ... I'm a little torn between two extremes:

On the one hand, I can condense the number of ARM/THUMB instructions
further to eliminate more redundant code. On the other, I can split them
apart to reduce the number of conditional tests needed to execute each
instruction. It's really the disassembler that makes me not want to
split them up further ... as I have to split the disassembler functions
up equally to the instruction functions. But it may be worth it if it's
a speed improvement.
2017-08-07 22:20:35 +10:00
Tim Allen
0b6f1df987 Update to v103r27 release.
byuu says:

Changelog:

  - hiro/windows: set dpiAware=false, fixes icarus window sizes relative
    to higan window sizes
  - higan, icarus, hiro, ruby: add support for high resolution displays
    on macOS [ncbncb]
  - processor/lr35902-legacy: removed
  - processor/arm7tdmi: new processor core started; intended to one day
    be a replacement for processor/arm

It will probably take several WIPs to get the new ARM core up and
running. It's the last processor rewrite. After this, all processor
cores will be up to date with all my current programming conventions.
2017-08-06 23:36:26 +10:00
Tim Allen
020caa546d Update to v103r26 release.
byuu says:

Changelog:

  - processor/lr35902: completed rewrite

I'd appreciate regression testing of the Game Boy and Game Boy Color
emulation between v103r24 and v103r26 (skip r25) if anyone wouldn't
mind.

I fixed up processor/lr35902-legacy to compile and run, so that trace
logs can be created between the two cores to find errors. I'm going to
kill processor/lr35902-legacy with the next WIP release, as well as make
changes to the trace format (add flags externally from AF; much easier
to read them that way), which will make it more difficult to do these
comparisons in the future, hence r26 may prove important later on if we
miss regressions this time.

As for the speed of the new CPU core, not too much to report ... at
least it's not slower :)

    Mega Man II: 212.5 to 214.5fps
    Shiro no Sho: 191.5 to 191.5fps
    Oracle of Ages: 182.5 to 190.5fps
2017-08-06 09:13:26 +10:00
Tim Allen
c2975e6898 Update to v103r25 release.
byuu says:

Changelog:

  - gb/cpu: force STAT mode to 0 when LCD is disabled (fixes Pokemon
    Pinball, etc)
  - gb/ppu: when LCD is disabled, require at least one-frame wait to
    re-enable, display white during this time
      - todo: should step by a scanline at a time: worst-case is an
        extra 99% of a frame to enable again
  - gba/ppu: cache tilemap lookups and attribute parsing
      - it's more accurate because the GBA wouldn't read this for every
        pixel
      - but unfortunately, this didn't provide any speedup at all ...
        sigh
  - ruby/audio/alsa: fixed const issue with free()
  - ruby/video/cgl: removed `glDisable(GL_ALPHA_TEST)` [deprecated]
  - ruby/video/cgl: removed `glEnable(GL_TEXTURE_2D)` [unnecessary as
    we use shaders]
  - processor/lr35902: started rewrite¹

¹: so, the Game Boy and Game Boy Color cores will be completely
broken for at least the next two or three WIPs.

The old LR35902 was complete garbage, written in early 2011. So I'm
rewriting it to provide a massive cleanup and consistency with other
processor cores, especially the Z80 core.

I've got about 85% of the main instructions implemented, and then I have
to do the CB instructions. The CB instructions are easier because
they're mostly just a small number of opcodes in many small variations,
but it'll still be tedious.
2017-08-04 23:05:12 +10:00
Tim Allen
571760c747 Update to v103r24 release.
byuu says:

Changelog:

  - gb/mbc6: mapper is now functional, but Net de Get has some text
    corruption¹
  - gb/mbc7: mapper is now functional²
  - gb/cpu: HDMA syncs other components after each byte transfer now
  - gb/ppu: LY,LX forced to zero when LCDC.d7 is lowered (eg disabled),
    not when it's raised (eg enabled)
  - gb/ppu: the LCD does not run at all when LCDC.d7 is clear³
      - fixes graphical corruption between scene transitions in Legend
        of Zelda - Oracle of Ages
      - thanks to Cydrak, Shonumi, gekkio for their input on the cause
        of this issue
  - md/controller: renamed "Gamepad" to "Control Pad" per official
    terminology
  - md/controller: added "Fighting Pad" (6-button controller) emulation
    [hex\_usr]
  - processor/m68k: fixed TAS to set data.d7 when
    EA.mode==DataRegisterDirect; fixes Asterix
  - hiro/windows: removed carriage returns from mouse.cpp and
    desktop.cpp
  - ruby/audio/alsa: added device driver selection [SuperMikeMan]
  - ruby/audio/ao: set format.matrix=nullptr to prevent a crash on some
    systems [SuperMikeMan]
  - ruby/video/cgl: rename term() to terminate() to fix a crash on macOS
    [Sintendo]

¹: The observation that this mapper split $4000-7fff into two banks
came from MAME's implementation. But their implementation was quite
broken and incomplete, so I didn't actually use any of it. The
observation that this mapper split $a000-bfff into two banks came from
Tauwasser, and I did directly use that information, plus the knowledge
that $0400/$0800 are the RAM bank select registers.

The text corruption is due to a race condition with timing. The game is
transferring font letters via HDMA, but the game code ends up setting
the bank# with the font a bit too late after the HDMA has already
occurred. I'm not sure how to fix this ... as a whole, I assumed my Game
Boy timing was pretty good, but apparently it's not that good.

²: The entire design of this mapper comes from endrift's notes.
endrift gets full credit for higan being able to emulate this mapper.
Note that the accelerometer implementation is still not tested, and
probably won't work right until I tweak the sensitivity a lot.

³: So the fun part of this is ... it breaks the strict 60fps rate of
the Game Boy. This was always inevitable: certain timing conditions can
stretch frames, too. But this is pretty much an absolute deal breaker
for something like Vsync timing. This pretty much requires adaptive sync
to run well without audio stuttering during the transition.

There's currently one very important detail missing: when the LCD is
turned off, presumably the image on the screen fades to white. I do not
know how long this process takes, or how to really go about emulating
it. Right now as an incomplete patch, I'm simply leaving the last
displayed image on the screen until the LCD is turned on again. But I
will have to output white, as well as add code to break out of the
emulation loop periodically when the LCD is left off eg indefinitely, or
bad things would happen. I'll work something out and then implement.

Another detail is I'm not sure how long it takes for the LCD to start
rendering again once enabled. Right now, it's immediate. I've heard it's
as long as 1/60th of a second, but that really seems incredibly
excessive? I'd like to know at least a reasonably well-supported
estimate before I implement that.
2017-08-04 23:05:06 +10:00
Tim Allen
7022d1aa51 Update to v103r23 release.
byuu says:

Changelog:

  - gb: added accelerometer X-axis, Y-Axis inputs¹
  - gb: added rumble input¹
  - gb/mbc5: added rumble support²
  - gb/mbc6: added skeleton driver, but it doesn't boot Net de Get
  - gb/mbc7: added mostly complete driver (only missing EEPROM), but it
    doesn't boot Kirby Tilt 'n' Tumble
  - gb/tama: added leap year assignment
  - tomoko: fixed macOS compilation [MerryMage]
  - hiro/cocoa: fix table cell redrawing on updates and automatic column
    resizing [ncbncb]
  - hiro/cocoa: fix some weird issue with clicking table view checkboxes
    on Retina displays [ncbncb]
  - icarus: enhance Game Boy heuristics³
  - nall: fix three missing return statements [Jonas Quinn]
  - ruby: hopefully fixed all compilation errors reported by Screwtape
    et al⁴

¹: because there's no concept of a controller for cartridge inputs,
I'm attaching to the base platform for now. An idea I had was to make
separate ports for each cartridge type ... but this would duplicate the
rumble input between MBC5 and MBC7. And would also be less discoverable.
But it would be more clean in that users wouldn't think the Game Boy
hardware had this functionality. I'll think about it.

²: it probably won't work yet. Rumble isn't documented anywhere, but
I dug through an emulator named GEST and discovered that it seems to use
bit 3 of the RAM bank select to be rumble. I don't know if it sets the
bit for rumbling, then clears when finished, or if it sets it and then
after a few milliseconds it stops rumbling. I couldn't test on my
FreeBSD box because SDL 1.2 doesn't support rumble, udev doesn't exist
on FreeBSD, and nobody has ever posted any working code for how to use
evdev (or whatever it's called) on FreeBSD.

³: I'm still thinking about specifying the MBC7 RAM as EEPROM, since
it's not really static RAM.

⁴: if possible, please test all drivers if you can. I want to ensure
they're all working. Especially let me know if the following work:
macOS: input.carbon Linux: audio.pulseaudiosimple, audio.ao (libao)

If I can confirm these are working, I'm going to then remove them from
being included with stock higan builds.

I'm also considering dropping SDL video on Linux/BSD. XShm is much
faster and supports blurring. I may also drop SDL input on Linux, since
udev works better. That will free a dependency on SDL 1.2 for building
higan. FreeBSD is still going to need it for joypad support, however.
2017-07-30 23:00:31 +10:00
Tim Allen
e1223366a7 Update to v103r22 release.
byuu says:

Changelog:

  - ruby: ported all remaining drivers to new API¹
  - ruby/wasapi: fix for dropping one sample per period [SuperMikeMan]
  - gb: emulated most of the TAMA RTC; but RTC state is still volatile²

¹: the new ports are:

  - audio/{directsound, alsa, pulseaudio, pulseaudiosimple, ao}
  - input/{udev, quartz, carbon}

It's pretty much guaranteed many of them will have compilation errors.
Please paste the error logs and I'll try to fix them up. It may take a
WIP or two to get there.

It's also possible things broke from the updates. If so, I could use
help comparing the old file to the new file, looking for mistakes, since
I can't test on these platforms apart from audio/directsound.

Please report working drivers in this list, so we can mark them off the
list. I'll need both macOS and Linux testers.

audio/directsound.cpp:112:

    if(DirectSoundCreate(0, &_interface, 0) != DS_OK) return terminate(), false;

²: once I get this working, I'll add load/save support for the RTC
values. For now, the RTC data will be lost when you close the emulator.

Right now, you can set the date/time in real-time mode, and when you
start the game, the time will be correct, and the time will tick
forward. Note that it runs off emulated time instead of actual real
time, so if you fast-forward to 300%, one minute will be 20 seconds.

The really big limitation right now is that when you exit the game, and
restart it, and resume a new game, the hour spot gets corrupted, and
this seems to instantly kill your pet. Fun. This is crazy because the
commands the game sends to the TAMA interface are identical between
starting a new game and getting in-game versus loading a game.

It's likely going to require disassembling the game's code and seeing
what in the hell it's doing, but I am extremely bad at LR35092 assembly.
Hopefully endrift can help here :|
2017-07-28 21:42:24 +10:00
Tim Allen
80841deaa5 Update to v103r21 release.
byuu says:

Changelog:

  - gb: added TAMA emulation [thanks to endrift for the initial notes]
  - gb: save RTC memory to disk (MBC3 doesn't write to said memory yet;
    TAMA doesn't emulate it yet)
  - gb: expect MMM01 boot loader to be at end of ROM instead of start
  - gb: store MBC2 save RAM as 256-bytes (512x4-bit) instead of
    512-bytes (with padding)
  - gb: major cleanups to every cartridge mapper; moved to Mapper class
    instead of MMIO class
  - gb: don't serialize all mapper states with every save state; only
    serialize the active mapper
  - gb: serialize RAM even if a battery isn't present¹
  - gb/cartridge: removed unnecessary code; refactored other code to
    eliminate duplication of functions
  - icarus: improve GB(C) heuristics generation to not include filenames
    for cartridges without battery backup
  - icarus: remove incorrect rearrangement of MMM01 ROM data
  - md/vdp: fix CRAM reads -- fixes Sonic Spinball colors [hex\_usr]
  - tomoko: hide the main higan window when entering fullscreen
    exclusive mode; helps with multi-monitor setups
  - tomoko: destroy ruby drivers before calling Application::quit()
    [Screwtape]
  - libco: add settings.h and defines to fiber, ucontext [Screwtape]

¹: this is one of those crystal clear indications that nobody's
actually playing the higan DMG/CGB cores, or at least not with save
states. This was a major mistake.

Note: I can't find any official documentation that `GL_ALPHA_TEST` was
removed from OpenGL 3.2. Since it's not hurting anything except showing
some warnings in debug mode, I'm just going to leave it there for now.
2017-07-26 22:42:06 +10:00
Tim Allen
d5c09c9ab1 Update to v103r20 release.
byuu says:

Changelog:

  - ruby/audio/xaudio2: ported to new ruby API
  - ruby/video/cgl: ported to new ruby API (untested, won't compile)
  - ruby/video/directdraw: ported to new ruby API
  - ruby/video/gdi: ported to new ruby API
  - ruby/video/glx: ported to new ruby API
  - ruby/video/wgl: ported to new ruby API
  - ruby/video/opengl: code cleanups

The macOS CGL driver is sure to have compilation errors. If someone will
post the compilation error log, I can hopefully fix it in one or two
iterations of WIPs.

I am unable to test the Xorg GLX driver, because my FreeBSD desktop
video card drivers do not support OpenGL 3.2. If the driver doesn't
work, I'm going to need help tracking down what broke from the older
releases.

The real fun is still yet to come ... all the Linux-only drivers, where
I don't have a single Linux machine to test with.

Todo:

  - libco/fiber
  - libco/ucontext (I should really just delete this)
  - tomoko: hide main UI window when in exclusive fullscreen mode
2017-07-24 15:23:40 +10:00
Tim Allen
8be474b0ac Update to v103r19 release.
byuu says:

Changelog:

  - tomoko: Application::onMain assigned at end of Program::Program()
    [Screwtape]¹
  - libco: add `#define _XOPEN_SOURCE 500` to fix compilation of sjlj.c
    [Screwtape]
  - ruby/audio/openal: fixed device driver string list enumeration
  - ruby/audio/wasapi: changing device re-initializes the driver now
  - ruby/audio/wasapi: probably a pointless change, but don't fill the
    buffer beyond the queue size with silence
  - ruby/video/xvideo: renamed from ruby/video/xv
  - ruby/video/xvideo: check to see if `XV_AUTOPAINT_COLORKEY` exists
    before setting it [SuperMikeMan]
  - ruby/video/xvideo: align buffer sizes to be evenly divisible by four
    [SuperMikeMan]
  - ruby/video/xvideo: fail nicely without crashing (hopefully)
  - ruby/video/xvideo: add support for YV12 and I420 12-bit planar YUV
    formats²

¹: prevents crashes when drivers fail to initialize from running the
main loop that polls input drivers before the input driver is
initialized (or fails to initialize itself.) Some drivers still don't
block their main functions when initialization fails, so they will still
crash, but I'll work to fix them.

²: this was a **major** pain in the ass, heh. You only get one chroma
sample for every four luma samples, so the color reproduction is even
worse than UYVY and YUYV (which is two to four chroma to luma.) Further,
the planar format took forever to figure out. Apparently it doesn't care
what portion of the image you specify in XvShmPutImage, it expects you
to use the buffer dimensions to locate the U and V portions of the data.

This is probably the most thorough X-Video driver in existence now.

Notes:

  - forgot to rename the configuration settings dialog window title to
    just "Settings"
2017-07-23 19:18:16 +10:00
Tim Allen
284e4c043e Update to v103r18 release.
byuu says:

Changelog:

  - tomoko: improved handling of changing audio devices on the audio
    settings panel
  - ruby/audio/wasapi: added device enumeration and selection support¹
  - ruby/audio/wasapi: release property store handle from audio device
  - ruby/audio/wasapi: fix exclusive mode buffer filling
  - ruby/video/glx2: ported to new API -- tested and confirmed working
    great²
  - ruby/video/sdl: fixed initialization -- tested and confirmed working
    on FreeBSD now³
  - ruby/video/xv: ported to new API -- tested and mostly working great,
    sans fullscreen mode⁴

Errata:

  - accidentally changed "Driver Settings" label to "Driver" on the
    audio settings tab because I deleted the line and forgot the
    "Settings" part
  - need to use "return initialize();" from setDevice() in the WASAPI
    driver, instead of "return true;", so device selection is currently
    not functioning in this WIP for said driver

¹: for now, this will likely end up selecting the first available
endpoint device, which is probably wrong. I need to come up with a
system to expose good 'default values' when selecting new audio drivers,
or changing audio device settings.

²: glx2 is a fallback driver for system with only OpenGL 2.0 and no
OpenGL 3.2 drivers, such as FreeBSD 10.1 with AMD graphics cards.

³: although I really should track down why InputManager::poll() is
crashing the emulator when Video::ready() returns false ...

⁴: really bizarrely, when entering fullscreen mode, it looks like the
image was a triangle strip, and the bottom right triange is missing, and
the top left triangle skews the entire image into it. I'm suspecting
this is a Radeon driver bug when trying to create a 2560x1600 X-Video
surface. The glitch persists when exiting fullscreen, too.

If anyone can test the X-Video driver on their Linux/BSD system, it'd be
appreciated. If it's just my video card, I'll ignore it. If not,
hopefully someone can find the cause of the issue :|
2017-07-20 21:52:47 +10:00
Tim Allen
0b4e7fb5a5 Update to v103r17 release.
byuu says:

Changelog:

  - tomoko: re-hid the video sync option¹
  - tomoko: removed " Settings" duplication on all the individual
    settings tab options
  - ruby/audio/wasapi: finished port to new syntax; adapted to an
    event-driven model; support 32-bit integral audio²
  - ruby/video/sdl: ported to new syntax; disabled driver on FreeBSD³

¹: still contemplating a synchronize submenu of {none, video, audio},
but ... the fact that video can't work on PAL, WonderSwan games is a
real limitation for it

²: this driver actually received a ton of work. There's also a new
ring-buffer queue, and I added special handling for when exclusive mode
fails because the latency requested is lower than the hardware can
support. It'll pick the closest latency to the minimum that is possible
in this case.

On my Audigy Rx, the results for non-exclusive mode are the same. For
exclusive mode, the framerate drops from 60fps to ~50fps for smaller
buffers, and ~55fps for larger buffers (no matter how big, it never hits
60fps.) This is a lot better than before where it was hitting ~15fps,
but unfortunately it's the best I can do.

The event system used by WASAPI is really stupid. It just uses SetEvent
at some arbitrary time, and you have to query to see how many samples
it's waiting on. This makes it unknowable how many samples we should
buffer before calling `WaitForSingleObject(INFINITE)`, and it's also
unclear how we should handle cases where there's more samples available
than our queue has: either we can fill it with zeroes, or we can write
less samples. The former should prevent audio looping effects when
running too slowly, whereas the latter could potentially be too
ambitious when the audio could've recovered from a minor stall.

It's shocking to me how there's as many ways to send audio to a sound
card as there are sound card APIs, when all that's needed is a simple
double buffer and a callback event from another thread to do it right.
It's also terrifying how unbelievably shitty nearly all sound card
drivers apparently are.

Also, I don't know if cards can output an actual 24-bit mode with three
byte audio samples, or if they always just take 32-bit samples and
ignore the lower 8-bits. Whatever, it's all nonsense for the final
output to be >16-bits anyway (hi, `double[]` input from ruby.)

³: unfortunately, this driver always crashes on FreeBSD (even before
the rewrite), so I'll need someone on Linux to test it and make sure it
actually works. I'll also need testing for a lot of the other drivers as
well, once they're ported over (I don't have X-video, PulseAudio, ALSA,
or udev.)

Note that I forgot to set `_ready=true` at the end of `initialize()`,
and `_ready=false` in `terminate()`, but it shouldn't actually matter
beyond showing you a false warning message on startup about it failing
to initialize.
2017-07-19 23:14:00 +10:00
Tim Allen
f87c6b7ecb Update to v103r16 release.
byuu says:

Changelog:

  - emulator/audio: added the ability to change the output frequency at
    run-time without emulator reset
  - tomoko: display video synchronize option again¹
  - tomoko: Settings→Configuration expanded to Settings→{Video,
    Audio, Input, Hotkey, Advanced} Settings²
  - tomoko: fix default population of audio settings tab
  - ruby: Audio::frequency is a double now (to match both
    Emulator::Audio and ASIO)³
  - tomoko: changing the audio device will repopulate the frequency and
    latency lists
  - tomoko: changing the audio frequency can now be done in real-time
  - ruby/audio/asio: added missing device() information, so devices can
    be changed now
  - ruby/audio/openal: ported to new API; added device selection support
  - ruby/audio/wasapi: ported to new API, but did not test yet (it's
    assuredly still broken)⁴

¹: I'm uneasy about this ... but, I guess if people want to disable
audio and just have smooth scrolling video ... so be it. With
Screwtape's documentation, hopefully that'll help people understand that
video synchronization always breaks audio synchronization. I may change
this to a child menu that lets you pick between {no synchronization,
video synchronization, audio synchronization} as a radio selection.

²: given how much more useful the video and audio tabs are now, I
felt that four extra menu items were worth saving a click and going
right to the tab you want. This also matches the behavior of the Tools
menu displaying all tool options and taking you directly to each tab.
This is kind of a hard change to get used to ... but I think it's for
the better.

³: kind of stupid because I've never seen a hardware sound card where
floor(frequency) != frequency, but whatever. Yay consistency.

⁴: I'm going to move it to be event-driven, and try to support 24-bit
sample formats if possible. Who knows which cards that'll fix and which
cards that'll break. I may end up making multiple WASAPI drivers so
people can find one that actually works for them. We'll see.
2017-07-17 20:32:36 +10:00
Tim Allen
4129630d97 Update to v103r15 release.
byuu says:

Changelog:

  - ruby: rewrote the API interfaces for Video, Audio, Input
  - ruby/audio: can now select the number of output channels (not useful
    to higan, sorry)
  - ruby/asio: various improvements
  - tomoko: audio settings panel can now select separate audio devices
    (for ASIO, OSS so far)
  - tomoko: audio settings panel frequency and latency lists are
    dynamically populated now

Note: due to the ruby API rewrite, most drivers will not compile. Right
now, the following work:

  - video: Direct3D, XShm
  - audio: ASIO, OSS
  - input: Windows, SDL, Xlib

It takes a really long time to rewrite these (six hours to do the
above), so it's going to be a while before we're back at 100%
functionality again.

Errata:

  - ASIO needs device(), setDevice()
  - need to call setDevice() at program startup to populate
    frequency/latency settings properly
  - changing the device and/or frequency needs to update the emulator
    resampler rates

The really hard part is going to be the last one: the only way to change
the emulator frequency is to flush all the audio streams and then
recompute all the coefficients for the resamplers. If this is called
during emulation, all audio streams will be erased and thus no sound
will be output. I'll most likely be forced to simply ignore
device/frequency changes until the user loads another game. It is at
least possible to toggle the latency dynamically.
2017-07-17 15:11:18 +10:00
Tim Allen
17697317d4 Update to v103r14 release.
byuu says:

Changelog:

  - tomoko: by popular choice, default to adaptive mode on new installs
  - hiro/windows: fix bug that was preventing the escape key from
    closing some dialog windows
  - nall/registry: use "\\\\" as separator instead of "/" ... because
    some registry keys contain "/" in them >_>
  - ruby: add ASIO driver stub (so far it can only initialize and grab
    the driver name/version information)
2017-07-15 22:00:20 +10:00
Tim Allen
ed5ec58595 Update to v103r13 release.
byuu says:

Changelog:

  - gb/interface: fix Game Boy Color extension to be "gbc" and not "gb"
    [hex\_usr]
  - ms/interface: move Master System hardware controls below controller
    ports
  - sfc/ppu: improve latching behavior of BGnHOFS registers (not
    hardware verified) [AWJ]
  - tomoko/input: rework port/device mapping to support non-sequential
    ports and devices¹
      - todo: should add move() to inputDevice.mappings.append and
        inputPort.devices.append
      - note: there's a weird GCC 4.9 bug with brace initialization of
        InputEmulator; have to assign each field separately
  - tomoko: all windows sans the main presentation window can be
    dismissed with the escape key
  - icarus: the single file selection dialog ("Load ROM Image...") can
    be dismissed with the escape key
  - tomoko: do not pause emulation when FocusLoss/Pause is set during
    exclusive fullscreen mode
  - hiro/(windows,gtk,qt): implemented Window::setDismissable() function
    (missing from cocoa port, sorry)
  - nall/string: fixed printing of largest possible negative numbers (eg
    `INT_MIN`) [Sintendo]
      - only took eight months! :D

¹: When I tried to move the Master System hardware port below the
controller ports, I ran into a world of pain.

The input settings list expects every item in the
`InputEmulator<InputPort<InputDevice<InputMapping>>>>` arrays to be
populated with valid results. But these would be sparsely populated
based on the port and device IDs from inside higan. And that is done so
that the Interface::inputPoll can have O(1) lookup of ports and devices.
This worked because all the port and device IDs were sequential (they
left no gaps in the maps upon creating the lists.)

Unfortunately by changing the expectation of port ID to how it appears
in the list, inputs would not poll correctly. By leaving them alone and
just moving Hardware to the third position, the Game Gear would be
missing port IDs of 0 and 1 (the controller ports of the Master System).
Even by trying to make separate MasterSystemHardware and
GameGearHardware ports, things still fractured when the devices were no
longer contigious.

I got pretty sick of this and just decided to give up on O(1)
port/device lookup, and moved to O(n) lookup. It only knocked the
framerate down by maybe one frame per second, enough to be in the margin
of error. Inputs aren't polled *that* often for loops that usually
terminate after 1-2 cycles to be too detrimental to performance.

So the new input system now allows non-sequential port and device IDs.

Remember that I killed input IDs a while back. There's never any reason
for those to need IDs ... it was easier to just order the inputs in the
order you want to see them in the user interface. So the input lookup is
still O(1). Only now, everything's safer and I return a
maybe<InputMapping&>, and won't crash out the program trying to use a
mapping that isn't found for some reason.

Errata: the escape key isn't working on the browser/message dialogs on
Windows, because of course nothing can ever just be easy and work for
me. If anyone else wouldn't mind looking into that, I'd greatly
appreciate it.

Having the `WM_KEYDOWN` test inside the main `Application_sharedProc`, it
seems to not respond to the escape key on modal dialogs. If I put the
`WM_KEYDOWN` test in the main window proc, then it doesn't seem to get
called for `VK_ESCAPE` at all, and doesn't get called period for modal
windows. So I'm at a loss and it's past 4AM here >_>
2017-07-12 18:24:27 +10:00
Tim Allen
434e303ffb Update to v103r12 release.
byuu says:

Changelog:

  - ruby/video: cleaned up Direct3D9 driver and fixed catastrophic
    memory leak
  - ruby/video: added fullscreen exclusive mode support to the Direct3D9
    driver¹
  - ruby/video: minor cosmetic code cleanups to various drivers
  - tomoko: added support to always allow input when in fullscreen
    exclusive mode
  - tomoko: fixed window to not remove resizability flag when exiting
    fullscreen mode

¹: I am assuming that exclusive mode will try to capture the primary
monitor. I don't know what will happen in multi-monitor setups, however,
as I don't use such a setup here.

Also, I am using `D3DPRESENT_DISCARD` instead of `D3DPRESENT_FLIP`. I'm
not sure if this will prove better or worse, but I've heard it will
waste less memory, and having a BackBufferCount of 1 should still result
in page flipping anyway. The difference is supposedly just that you
can't rely on the back buffer being a valid copy of the previous frame
like you can with FLIP.

Lastly, if you want Vsync, you can edit the configuration file to enable
that, and then turn off audio sync.

Errata: "pause emulation when focus is lost" is not working with
exclusive mode. I need to add a check to never auto-pause when in
exclusive mode. Thanks to bun for catching that one.
2017-07-09 12:23:17 +10:00
Tim Allen
ee982f098a Update to v103r11 release.
byuu says:

Changelog:

  - tomoko: removed "Settings→Video Emulation→Overscan Mask" setting¹
  - tomoko: remove a few unnecessary calls to resizeViewport on startup
  - tomoko: only resize main window from video settings when in adaptive
    or toggling adaptive mode²
  - hiro/windows: add `SWP_NOACTIVATE` flag to prevent focus stealing on
    resizing invisible windows³
  - hiro/windows: suppress spurious API-generated `onSize()` callback
    when calling `setVisible()`

¹: it just seemed like bad design to default to overscan masking
being disabled with overscan masks of 8 horizontal, 8 vertical out of
the box. Users would adjust the sliders and not see anything happening.
Instead, I've set the default masks to zero. If you want to turn off
overscan masking, simply slide those to zero again.

²: I figure the only way we're going to be able to fairly evaluate
Screwtape's suggestion is to try it both ways. And I will admit, I kind
of like the way this works as well ... a lot more so than I thought I
would, so I think it was a great suggestion. Still, now's the time if
people have strong opinions on this. Be sure to try both r10 and r11 to
compare. Barring no other feedback, I'm going to keep it this way.

³: this fixes the blinking of the main window on startup.

Screwtape, thanks again for the improvement suggestions. At this point
though, I am not using a tiling window manager. If you are able to patch
hiro/gtk and/or hiro/qt (I mostly use GTK) to work with tiling window
managers better, I wouldn't mind applying said patches, so long as they
don't break things on my own Xfce desktop with xfwm4.

Also, I noticed one issue with Xfce ... if the window is maximized and I
try to call `Window::setSize()`, it's not actually removing the maximize
flag. We'll need to look into how to add that to GTK, but I don't think
it's a huge issue. A similar glitch happens on windows where the icon
still reflects being maximized, but it does actually shrink, it just
sticks to the top left corner of the screen. So this isn't really a
critical bug, but would be extra polish.
2017-07-08 11:02:01 +10:00
Tim Allen
cbbf5ec114 Update to v103r10 release.
byuu says:

Changelog:

  - tomoko: video scaling options are now resolutions in the
    configuration file, eg "640x480", "960x720", "1280x960"
  - tomoko: main window is now always resizable instead of fixed width
    (also supports maximizing)
  - tomoko: added support for non-integral scaling in windowed mode
  - tomoko: made the quick/managed state messaging more consistent
  - tomoko: hide "Find Codes ..." button from the cheat editor window if
    the cheat database is not present
  - tomoko: per-game cheats.bml file now goes into the higan/ subfolder
    instead of the root folder

So the way the new video system works is you have the following options
on the video settings panel:

Windowed mode: { Aspect correction, Integral scaling, Adaptive }

Fullscreen mode: { Aspect correction, Integral scaling } (and one day,
hopefully Exclusive will be added here)

Whenever you adjust the overscan masking, or you change any of the
windowed or fullscreen mode settings, or you choose a different video
scale from the main menu, or you load a new game, or you unload a game,
or you rotate the display of an emulated system, the resizeViewport
logic will be invoked. This logic will remember the last option you
chose for video scale, and base the new window size on that value as an
upper limit of the new window size.

If you are in windowed mode and have adaptive enabled, it will shrink
the window to fit the contents of the emulated system's video output.
Otherwise, if you are not in integral scaling mode, it will scale the
video as large as possible to fit into the video scaled size you have
selected. Otherwise, it will perform an integral scale and center the
video inside of the viewport.

If you are in fullscreen mode, it's much the same, only there is no
adaptive mode.

A major problem with Xorg is that it's basically impossible to change
the resizability attribute of a window post-creation. You can do it, but
all kinds of crazy issues start popping up. Like if you toggle
fullscreen, then you'll find that the window won't grow past a certain
fairly small size that it's already at, and cannot be shrunk. And the
multipliers will stop expanding the window as large as they should. And
sometimes the UI elements won't be placed in the correct position, or
the video will draw over them. It's a big mess. So I have to keep the
main window always resizable. Also, note that this is not a limitation
of hiro. It's just totally broken in Xorg itself. No amount of fiddling
has ever allowed this to work reliably for me on either GTK+ 2 or Qt 4.

So what this means is ... the adaptive mode window is also resizable.
What happens here is, whenever you drag the corners of the main window
to resize it, or toggle the maximize window button, higan will bypass
the video scale resizing code and instead act as though the adaptive
scaling mode were disabled. So if integral scaling is checked, it'll
begin scaling in integral mode. Otherwise, it'll begin scaling in
non-integral mode.

And because of this flexibility, it no longer made sense for the video
scale menu to be a radio box. I know, it sucks to not see what the
active selection is anymore, but ... say you set the scale to small,
then you accidentally resized the window a little, but want it snapped
back to the proper small resolution dimensions. If it were a radio item,
you couldn't reselect the same option again, because it's already active
and events don't propagate in said case. By turning them into regular
menu options, the video scale menu can be used to restore window sizing.

Errata:

On Windows, the main window blinks a few times on first load. The fix
for that is a safeguard in the video settings code, roughly like so ...
but note you'd need to make a few other changes for this to work against
v103r10:

    auto VideoSettings::updateViewport(bool firstRun) -> void {
      settings["Video/Overscan/Horizontal"].setValue(horizontalMaskSlider.position());
      settings["Video/Overscan/Vertical"].setValue(verticalMaskSlider.position());
      settings["Video/Windowed/AspectCorrection"].setValue(windowedModeAspectCorrection.checked());
      settings["Video/Windowed/IntegralScaling"].setValue(windowedModeIntegralScaling.checked());
      settings["Video/Windowed/AdaptiveSizing"].setValue(windowedModeAdaptiveSizing.checked());
      settings["Video/Fullscreen/AspectCorrection"].setValue(fullscreenModeAspectCorrection.checked());
      settings["Video/Fullscreen/IntegralScaling"].setValue(fullscreenModeIntegralScaling.checked());
      horizontalMaskValue.setText({horizontalMaskSlider.position()});
      verticalMaskValue.setText({verticalMaskSlider.position()});
      if(!firstRun) presentation->resizeViewport();
    }

That'll get it down to one blink, as with v103 official. Not sure I can
eliminate that one extra blink.

I forgot to remove the setResizable toggle on fullscreen mode exit. On
Windows, the main window will end up unresizable after toggling
fullscreen. I missed that one because like I said, toggling resizability
is totally broken on Xorg. You can fix that with the below change:

    auto Presentation::toggleFullScreen() -> void {
      if(!fullScreen()) {
        menuBar.setVisible(false);
        statusBar.setVisible(false);
      //setResizable(true);
        setFullScreen(true);
        if(!input->acquired()) input->acquire();
      } else {
        if(input->acquired()) input->release();
        setFullScreen(false);
      //setResizable(false);
        menuBar.setVisible(true);
        statusBar.setVisible(settings["UserInterface/ShowStatusBar"].boolean());
      }
      resizeViewport();
    }

Windows is stealing focus on calls to resizeViewport(), so we need to
deal with that somehow ...

I'm not really concerned about the behavior of shrinking the viewport
below the smallest multiplier for a given system. It might make sense to
snap it to the window size and forego all other scaling, but honestly
... meh. I don't really care. Nobody sane is going to play like that.
2017-07-07 13:38:46 +10:00
Tim Allen
7af270aa59 Update to v103r09 release.
byuu says:

Changelog:

  - gba/apu: fixed wave RAM nibble ordering (fixes audio in Castlevania,
    PocketNES)
  - emulator: restructured video information to just a single
    videoResolution() → VideoResolution function
      - returns "projected size" (between 160x144 and 320x240)
      - "internal buffer size" (up to 1280x480)
      - returns aspect correction multiplier that is to be applied to
        the width field
          - the value could be < 1.0 to handle systems with taller
            pixels; although higan doesn't emulate such a system
  - tomoko: all calculations for scaling and overscan masking are done
    by the GUI now
  - tomoko: aspect correction can be enabled in either windowed or
    fullscreen mode separately; moved to Video settings panel
  - tomoko: video scaling multipliers (against 320x240) can now me
    modified from the default (2,3,4) via the configuration file
      - use this as a really barebones way of supporting high DPI
        monitors; although the GUI elements won't scale nicely
      - if you set a value less than two, or greater than your
        resolution divided by 320x240, it's your own fault when things
        blow up. I'm not babysitting anyone with advanced config-file
        only options.
  - tomoko: added new adaptive windowed mode
      - when enabled, the window will shrink to eliminate any black
        borders when loading a game or changing video settings. The
        window will not reposition itself.
  - tomoko: added new adaptive fullscreen mode
      - when enabled, the integral scaling will be disabled for
        fullscreen mode, forcing the video to fill at least one
        direction of the video monitor completely.

I expect we will be bikeshedding for the next month on how to describe
the new video options, where they should appear in the GUI, changes
people want, etc ... but suffice to say, I'm happy with the
functionality, so I don't intend to make changes to -what- things do,
but I will entertain better ways to name things.
2017-07-06 18:29:12 +10:00
Tim Allen
191a71b291 Update to v103r08 release.
byuu says:

Changelog:

  - emulator: improved aspect correction accuracy by using
    floating-point calculations
  - emulator: added videoCrop() function, extended videoSize() to take
    cropping parameters¹
  - tomoko: the overscan masking function will now actually resize the
    viewport²
  - gba/cpu: fixed two-cycle delay on triggering DMAs; not running DMAs
    when the CPU is stopped
  - md/vdp: center video when overscan is disabled
  - pce/vce: resize video output from 1140x240 to 1120x240
  - tomoko: resize window scaling from 326x240 to 320x240
  - tomoko: changed save slot naming and status bar messages to indicate
    quick states vs managed states
  - tomoko: added increment/decrement quick state hotkeys
  - tomoko: save/load quick state hotkeys now save to slots 1-5 instead
    of always to 0
  - tomoko: increased overscan range from 0-16 to 0-24 (in case you want
    to mask the Master System to 240x192)

¹: the idea here was to decouple raw pixels from overscan masking.
Overscan was actually horrifically broken before. The Famicom outputs at
256x240, the Super Famicom at 512x480, and the Mega Drive at 1280x480.
Before, a horizontal overscan mask of 8 would not reduce the Super
Famicom or Mega Drive by nearly as much as the Famicom. WIth the new
videoCrop() function, the internals of pixel size distortions can be
handled by each individual core.

²: furthermore, by taking optional cropping information in
videoSize(), games can scale even larger into the viewport window. So
for example, before the Super Famicom could only scale to 1536x1440. But
by cropping the vertical resolution by 6 (228p effectively, still more
than NTSC can even show), I can now scale to 1792x1596. And wiht aspect
correction, that becomes a perfect 8:7 ratio of 2048x1596, giving me
perfectly crisp pixels without linear interpolation being required.

Errata: for some reason, when I save a new managed state with the SFC
core, the default description is being set to a string of what looks to
be hex numbers. I found the cause ... I'll fix this in the next release.

Note: I'd also like to hide the "find codes..." button if cheats.bml
isn't present, as well as update the SMP TEST register comment from
smp/timing.cpp
2017-07-05 16:39:14 +10:00
Tim Allen
d4876a831f Update to v103r07 release.
byuu says:

Changelog:

  - gba/cpu: massive code cleanup effort
  - gba/cpu: DMA can run in between active instructions¹
  - gba/cpu: added two-cycle startup delay between DMA activation and
    DMA transfers²
  - processor/spc700: BBC, BBC, CBNE cycle 4 is an idle cycle
  - processor/spc700: ADDW, SUBW, MOVW (read) cycle 4 is an idle cycle

¹: unfortunately, this causes yet another performance penalty for the
poor GBA core =( Also, I think I may have missed disabling DMAs while
the CPU is stopped. I'll fix that in the next WIP.

²: I put the waiting counter decrement at the wrong place, so this
doesn't actually work. Needs to be more like
this:

    auto CPU::step(uint clocks) -> void {
      for(auto _ : range(clocks)) {
        for(auto& timer : this->timer) timer.run();
        for(auto& dma : this->dma) if(dma.active && dma.waiting) dma.waiting--;
        context.clock++;
      }
      ...

    auto CPU::DMA::run() -> bool {
      if(cpu.stopped() || !active || waiting) return false;

      transfer();
      if(irq) cpu.irq.flag |= CPU::Interrupt::DMA0 << id;
      if(drq && id == 3) cpu.irq.flag |= CPU::Interrupt::Cartridge;
      return true;
    }

Of course, the real fix will be restructuring how DMA works, so that
it's always running in parallel with the CPU instead of this weird
design where it tries to run all channels in some kind of loop until no
channels are active anymore whenever one channel is activated.

Not really sure how to design that yet, however.
2017-07-05 15:29:27 +10:00
Tim Allen
16f736307e Update to v103r06 release.
byuu says:

Changelog:

  - processor/spc700: restored fetch/load/store/pull/push shorthand
    functions
  - processor/spc700: split functions that tested the algorithm used (`op
    != &SPC700:...`) to separate instructions
      - mostly for code clarity over code size: it was awkward having
        cycle counts change based on a function parameter
  - processor/spc700: implemented Overload's new findings on which
    cycles are truly internal (no bus reads)
  - sfc/smp: TEST register emulation has been vastly improved¹

¹: it turns out that TEST.d4,d5 is the external clock divider (used
when accessing RAM through the DSP), and TEST.d6,d7 is the internal
clock divider (used when accessing IPLROM, IO registers, or during idle
cycles.)

The DSP (24576khz) feeds its clock / 12 through to the SMP (2048khz).
The clock divider setting further divides the clock by 2, 4, 8, or 16.
Since 8 and 16 are not cleanly divislbe by 12, the SMP cycle count
glitches out and seems to take 10 and 2 clocks instead of 8 or 16. This
can on real hardware either cause the SMP to run very slowly, or more
likely, crash the SMP completely until reset.

What's even stranger is the timers aren't affected by this. They still
clock by 2, 4, 8, or 16.

Note that technically I could divide my own clock counters by 24 and
reduce these to {1,2,5,10} and {1,2,4,8}, I instead chose to divide by
12 to better illustrate this hardware issue and better model that the
SMP clock runs at 2048khz and not 1024khz.

Further, note that things aren't 100% perfect yet. This seems to throw
off some tests, such as blargg's `test_timer_speed`. I can't tell how
far off I am because blargg's test tragically doesn't print out fail
values. But you can see the improvements in that higan is now passing
all of Revenant's tests that were obviously completely wrong before.
2017-07-03 17:24:47 +10:00
Tim Allen
40802b0b9f Update to v103r05 release.
byuu says:

Changelog:

  - fc/controller: added ControllerPort class; removed Peripherals class
  - md/controller/gamepad: removed X,Y,Z buttons since this isn't a
    6-button controller
  - ms/controller: added ControllerPort class (not used in Game Gear
    mode); removed Peripherals class
  - pce/controller: added ControllerPort class; removed Peripherals
    class
  - processor/spc700: idle(address) is part of SMP class again, contains
    flag to detect mov (x)+ edge case
  - sfc/controller/super-scope,justifier: use CPU frequency instead of
    hard-coding NTSC frequency
  - sfc/cpu: move 4x8-bit SMP ports to SMP class
  - sfc/smp: move APU RAM to DSP class
  - sfc/smp: improved emulation of TEST registers bits 4-7 [information
    from nocash]
      - d4,d5 is RAM wait states (1,2,5,10)
      - d6,d7 is ROM/IO wait states (1,2,5,10)
  - sfc/smp: code cleanup to new style (order from lowest to highest
    bits; use .bit(s) functions)
  - sfc/smp: $00f8,$00f9 are P4/P5 auxiliary ports; named the registers
    better
2017-07-01 16:15:27 +10:00
Tim Allen
ff3750de4f Update to v103r04 release.
byuu says:

Changelog:

  - fc/apu: $4003,$4007 writes initialize duty counter to 0 instead of 7
  - fc/apu: corrected duty table entries for use with decrementing duty
    counter
  - processor/spc700: emulated the behavior of cycle 3 of (x)+
    instructions to not read I/O registers
      - specifically, this prevents reads from $fd-ff from resetting the
        timers, as observed on real hardware
  - sfc/controller: added ControllerPort class to match Mega Drive
    design
  - sfc/expansion: added ExpansionPort class to match Mega Drive design
  - sfc/system: removed Peripherals class
  - sfc/system: changed `colorburst()` to `cpuFrequency()`; added
    `apuFrequency()`
  - sfc: replaced calls to `system.region == System::Region::*` with
    `Region::*()`
  - sfc/expansion: remove thread from scheduler when device is destroyed
  - sfc/smp: `{read,write}Port` now use a separate 4x8-bit buffer instead
    of underlying APU RAM [hex\_usr]
2017-06-30 14:17:23 +10:00
Tim Allen
78f341489e Update to v103r03 release.
byuu says:

Changelog:

  - md/psg: fixed output frequency rate regression from v103r02
  - processor/m68k: fixed calculations for ABCD, NBCD, SBCD [hex\_usr,
    SuperMikeMan]
  - processor/spc700: renamed abbreviated instructions to functional
    descriptions (eg `XCN` → `ExchangeNibble`)
  - processor/spc700: removed memory.cpp shorthand functions (fetch,
    load, store, pull, push)
  - processor/spc700: updated all instructions to follow cycle behavior
    as documented by Overload with a logic analyzer

Once again, the changes to the SPC700 core are really quite massive. And
this time it's not just cosmetic: the idle cycles have been updated to
pull from various memory addresses. This is why I removed the shorthand
functions -- so that I could handle the at-times very bizarre addresses
the SPC700 has on its address bus during its idle cycles.

There is one behavior Overload mentioned that I don't emulate ... one of
the cycles of the (X) transfer functions seems to not actually access
the $f0-ff internal SMP registers? I don't fully understand what
Overload is getting at, so I haven't tried to support it just yet.

Also, there are limits to logic analyzers. In many cases the same
address is read from twice consecutively. It is unclear which of the two
reads the SPC700 actually utilizes. I tried to choose the most logical
values (usually the first one), but ... I don't know that we'll be able
to figure this one out. It's going to be virtually impossible to test
this through software, because the PC can't really execute out of
registers that have side effects on reads.
2017-06-28 17:24:46 +10:00
Tim Allen
3517d5c4a4 Update to v103r02 release.
byuu says:

Changelog:

  - fc/apu: improved phase duty cycle emulation (mode 3 is 25% phase
    inverted; counter decrements)
  - md/apu: power/reset do not cancel 68K bus requests
  - md/apu: 68K is not granted bus access on Z80 power/reset
  - md/controller: replaced System::Peripherals with ControllerPort
    concept
  - md/controller: CTRL port is now read-write, maintains value across
    controller changes (and soon, soft resets)
  - md/psg: PSG sampling rate unintentionally modified¹
  - processor/spc700: improve cycle timing of (indirect),y instructions
    [Overload]
  - processor/spc700: idle() cycles actually read from the program
    counter; much like the 6502 [Overload]
      - some of the idle() cycles should read from other addresses; this
        still needs to be supported
  - processor/spc700: various cleanups to instruction function naming
  - processor/z80: prefix state (HL→IX,IY override) can now be
    serialized
  - icarus: fix install rule for certain platforms (it wasn't buggy on
    FreeBSD, but was on Linux?)

¹: the clock speed of the PSG is oscillator/15. But I was setting the
sampling rate to oscillator/15/16, which was around 223KHz. I am not
sure whether the PSG should be outputting at 3MHz or 223KHz. Amazingly
... I don't really hear a difference either way `o_O` I didn't actually
mean to make this change; I just noticed it after comparing the diff
between r01 and r02. If this turns out to be wrong, set

    stream = Emulator::audio.createStream(1, frequency() / 16.0);

in md/psg.cpp to revert this change.
2017-06-27 11:18:28 +10:00
Tim Allen
ecc7e899e0 Update to v103r01 release.
byuu says:

Changelog:

  - nall/dsp: improve one pole coefficient calculations [Fatbag]
  - higan/audio: reworked filters to support selection of either one
    pole (first-order) or biquad (second-order) filters
      - note: the design is not stable yet; so forks should not put too
        much effort into synchronizing with this change yet
  - fc: added first-order filters as per NESdev wiki (90hz lowpass +
    440hz lowpass + 14khz highpass)
  - fc: created separate NTSC-J and NTSC-U regions
      - NESdev wiki says the Japanese Famicom uses a separate audio
        filtering strategy, but details are fuzzy
      - there's also cartridge audio output being disabled on NES units;
        and differences with controllers
      - this stuff will be supported in the future, just adding the
        support for it now
  - gba: corrected serious bugs in PSG wave channel emulation [Cydrak]
      - note that if there are still bugs here, it's my fault
  - md/psg,ym2612: added first-order low-pass 2840hz filter to match
    VA3-VA6 Mega Drives
  - md/psg: lowered volume relative to the YM2612
      - using 0x1400; multiple people agreed it was the closest to the
        hardware recordings against a VA6
  - ms,md/psg: don't serialize the volume levels array
  - md/vdp: Hblank bit acts the same during Vblank as outside of it (it
    isn't always set during Vblank)
  - md/vdp: return isPAL in bit 0 of control port reads
  - tomoko: change command-line option separator from : to |
      - [Editor's note: This change was present in the public v103,
        but it's in this changelog because it was made after the v103 WIP]
  - higan/all: change the 20hz high-pass filters from second-order
    three-pass to first-order one-pass
      - these filters are meant to remove DC bias, but I honestly can't
        hear a difference with or without them
      - so there's really no sense wasting CPU power with an extremely
        powerful filter here

Things I did not do:

  - change icarus install rule
  - work on 8-bit Mega Drive SRAM
  - work on Famicom or Mega Drive region detection heuristics in icarus

My long-term dream plan is to devise a special user-configurable
filtering system where you can set relative volumes and create your own
list of filters (any number of them in any order at any frequency), that
way people can make the systems sound however they want.

Right now, the sanest place to put this information is inside the
$system.sys/manifest.bml files. But that's not very user friendly, and
upgrading to new versions will lose these changes if you don't copy them
over manually. Of course, cluttering the GUI with a fancy filter editor
is probably supreme overkill for 99% of users, so maybe that's fine.
2017-06-26 11:41:58 +10:00
Tim Allen
f6d7922e62 Include the official shaders in build artifacts. 2017-06-22 16:28:21 +10:00
Tim Allen
a2baea248f Don't bundle the cheats database in built artifacts.
As of v103, higan no longer includes the cheat database.
2017-06-22 16:27:13 +10:00
416 changed files with 15965 additions and 12149 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
higan/profile/WonderSwan.sys/internal.ram
higan/profile/WonderSwan Color.sys/internal.ram
docs_build/

View File

@@ -12,8 +12,8 @@ linux-x86_64-binaries:
- cp -a icarus/out/icarus higan-nightly/icarus
- cp -a icarus/Database higan-nightly/
- cp -a higan/out/higan higan-nightly/higan
- cp -a higan/data/cheats.bml higan-nightly/
- cp -a higan/systems/* higan-nightly/
- cp -a shaders "higan-nightly/Video Shaders"
artifacts:
paths:
- higan-nightly/*
@@ -36,8 +36,8 @@ windows-x86_64-binaries:
- cp -a icarus/out/icarus higan-nightly/icarus.exe
- cp -a icarus/Database higan-nightly/
- cp -a higan/out/higan higan-nightly/higan.exe
- cp -a higan/data/cheats.bml higan-nightly/
- cp -a higan/systems/* higan-nightly/
- cp -a shaders "higan-nightly/Video Shaders"
artifacts:
paths:
- higan-nightly/*

9
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,9 @@
Contributing to higan
=====================
If you would like to propose a change to higan,
you should create an account on the [official forums][f],
go to the "Projects" forum and the "higan" subforum,
and post your idea in a new topic there.
[f]: https://board.byuu.org/

48
README.md Normal file
View File

@@ -0,0 +1,48 @@
The unofficial higan repository
===============================
higan emulates a number of classic videogame consoles of the 1980s and 1990s,
allowing you to play classic games on a modern general-purpose computer.
This repository includes
the source-code for
stable and WIP releases of higan,
starting during the development of v068.
It also includes community-maintained documentation.
Basically,
apart from `.gitignore` files,
anything in the
[higan](higan/),
[hiro](hiro/),
[icarus](icarus/),
[libco](libco/),
[nall](nall/),
[ruby](ruby/),
or [shaders](shaders/)
directories should be exactly as it appeared in official releases.
Everything else has been added for various reasons.
Official higan resources
------------------------
- [Official homepage](https://byuu.org/emulation/higan/)
- [Official forum](https://board.byuu.org/viewforum.php?f=4)
Unofficial higan resources
--------------------------
- Documentation for
[the current stable version][stadocs]
- [Source code repository](https://gitlab.com/higan/higan/)
archives official higan releases
and WIP snapshots
since approximately v067r21
- [Latest WIP build for Windows][wipwin]
- Documentation for
[the latest WIP version][wipdocs]
[wipwin]: https://gitlab.com/higan/higan/-/jobs/artifacts/master/download?job=windows-x86_64-binaries
[stadocs]: https://higan.readthedocs.io/
[wipdocs]: https://higan.readthedocs.io/en/latest/

12
docs/checklinks.sh Normal file
View File

@@ -0,0 +1,12 @@
#!/bin/sh
# This uses the official W3C link-checker tool:
#
# https://github.com/w3c/link-checker
#
checklink \
--summary \
--broken \
--location=http://127.0.0.1:8000/ \
--exclude 'github.com|board.byuu.org' \
http://127.0.0.1:8000/

View File

@@ -0,0 +1,141 @@
What is a game folder?
----------------------
TODO
Why game folders?
-----------------
A game is more than just
the raw data originally encased in a game's ROM chip.
If a game allows you to save your progress,
that information needs to be stored somewhere.
If you use an emulator's [save states](#save-states),
those save states need to be stored somewhere.
If you use Game Genie or Pro Action Replay codes,
information about what codes exist,
what codes are enabled,
and what they do
needs to be stored somewhere.
On the technical side,
a physical game cartridge contains a circuit board
that makes the game data available to the console,
and different games used circuit boards that work differently.
That circuit-layout information needs to be stored somewhere.
Some games included custom processors
to do calculations the base console could not do quickly enough
(like the SuperFX chip used in _StarFox_ for the Super Famicom)
and information about extra chips needs to be stored somewhere.
Some of those custom processors require extra data to work
that's not part of the main game data
(like the DSP chip used in Super Mario Kart for the Super Famicom)
and that data needs to be stored somewhere too.
higan keeps all this game-related information together
in a single place:
a game folder in the higan library.
For a more detailed motivation for game folders,
see [Game Paks on the higan website][gp]
[gp]: https://byuu.org/emulation/higan/game-paks
What is a manifest?
-------------------
TODO
The most important file in a game folder is `manifest.bml`,
which describes how all the other files should be wired together
to create a runnable game cartridge.
However,
the manifest format has occasionally changed
as new emulation details were uncovered
that could not be represented in the old format.
Therefore,
icarus [defaults](#the-icarus-settings-dialog)
to not writing out manifests when it imports games,
and higan [defaults](#the-configuration-dialog)
to ignoring manifests that are present.
Instead,
when higan loads a game,
it will ask icarus to generate a temporary manifest in the latest format,
based on the files present in the game folder
and how they are likely to go together.
You can view this temporary manifest
in [the Manifest Viewer](#the-manifest-viewer).
What's in a game folder?
------------------------
As mentioned [above](#why-game-folders),
a game folder collects all the information relevant
to emulating a particular game.
Not all of the following files
are relevant to every emulated console,
or to every game on a given console,
but they may be relevantunder particular circumstances.
All the files directly in the game folder
are expected to be useful
to all emulators that support them:
- `manifest.bml`:
The [manifest](#what-is-a-manifest)
for this game folder.
- `program.rom`:
For most consoles,
this contains
the executable instructions and graphics data
from the cartridge's ROM chips.
For the Famicom,
this contains only the executable instructions.
- `character.rom`:
For the Famicom,
this contains only the graphics data
from the cartridge's ROM chips.
- `ines.rom`:
While other consoles typically include enough hints
in `program.rom` for icarus to generate a manifest,
the Famicom does not.
Famicom games not stored in game folders
typically include an "iNES header" to store that information,
which icarus preserves after import as `ines.rom`.
- `save.ram`:
Games that include a save feature
will create this file.
Note that it is only written to disk
when higan exits gracefully,
if higan crashes or is forced to quit,
in-game saves may be lost.
Other emulators sometimes call this an "SRAM file",
even though the same filename is used
for cartridges that use EEPROM or Flash storage,
not just battery-backed Static RAM.
- `rtc.ram`:
Games that include a calendar or real-time clock
will create this file.
- `*.data.rom`, `*.program.rom`:
Files named like this are usually
[co-processor firmware](#importing-and-playing-games-with-co-processor-firmware).
- `msu1.rom`:
Holds streamable data for
[the MSU-1](#importing-and-playing-MSU-1-games).
- `track-*.pcm`:
Holds streamable audio for
[the MSU-1](#importing-and-playing-MSU-1-games).
Files that are only useful to higan specifically
are placed in a `higan` subdirectory:
- `cheats.bml`:
All information present in
[the Cheat Editor](#the-cheat-editor)
is stored here.
- `states/quick/slot-*.bst`:
All the save states made to
[Quick state slots](#quick-states).
- `states/managed/slot-*.bst`:
All the save states made with
[the State Manager](#the-state-manager).

View File

@@ -0,0 +1,56 @@
higan maintains a "game library"
containing all the games you've played.
- In Windows,
the default location of
the game library is the `Emulation` folder
inside your profile folder
(To find your profile folder,
press `Win+R` to open the Run dialog,
then type `%USERPROFILE%` and press Enter).
- In Linux,
the default location of
the game library is the `Emulation` directory
inside your home directory.
- On all platforms,
the game library location can be configured.
See [Moving the Game Library](#moving-the-game-library)
below.
Inside the library directory there is a subdirectory for each system,
and inside each system directory are the game folders
for each imported game.
For more information about game folders,
see [Why game folders?](#why-game-folders)
and [What's in a game folder?](#whats-in-a-game-folder)
below.
Moving the game library
-----------------------
Moving the game library is a little complicated,
because there's two parts to it:
telling icarus where to put imported games,
and telling higan where to find them.
1. If necessary,
create the folder you want higan to use
as its game library.
1. Launch icarus,
then click the "Settings ..." button in the lower-right,
to open the Settings dialog.
1. Click the "Change ..." button on the right.
A [filesystem browser](#the-filesystem-browser) window will appear,
allowing you to choose
where imported games will be stored.
1. Launch higan,
then from the Settings menu,
choose "Configuration ..."
to open [the Configuration dialog](#the-configuration-dialog).
1. Click the Advanced tab
then click the "Change ..." button.
A [filesystem browser](#the-filesystem-browser) will appear,
allowing you to choose the same directory again.

View File

@@ -0,0 +1,129 @@
A real game console
is a complex piece of hardware,
with electricity flowing through it
in complex patterns that can't easily
be paused or recorded.
However,
an emulated console is pure software:
it only changes when the emulation software
deliberately updates it,
so the emulator software can save
the entire state of the emulated console
to disk,
and weeks or months later
bring it back to life
as though nothing had ever happened.
Save states versus in-game saves
--------------------------------
Some games include their own systems
for saving and restoring the player's progress.
Here are some of the differences
between save states and in-game saves:
<table>
<thead>
<tr>
<th>Save states</th>
<th>In-game saves</th>
</tr>
</thead>
<tbody>
<tr>
<td>
Work exactly the same way
in every game
</td>
<td>
Works differently in different games,
some games don't support it at all
</td>
</tr>
<tr>
<td>
Save at any time,
anywhere in the game
</td>
<td>
Save only at special save-points
</td>
</tr>
<tr>
<td>
Loading a save
puts the game back exactly how it was
when you pressed save
</td>
<td>
Loading a save
restores some things
(like the player's inventory
or remaining lives),
but may
forget others
(like taking you back to the beginning of the level)
</td>
</tr>
<tr>
<td>
You can have dozens of save states
</td>
<td>
Most games limit you to about 3 saves
</td>
</tr>
<tr>
<td>
Can only be loaded
by the same version of the same emulator
that created it
</td>
<td>
Works with any version of any emulator,
can sometimes even be copied to or from physical cartridges
</td>
</tr>
</tbody>
</table>
**Note:**
Loading a save state
will reset the entire emulated console
to the way it was when the save state was created,
*including in-game saves*.
If you create a save state,
then make an in-game save,
then load the save state,
**your in-game save will be lost**.
Don't do that.
Quick states
------------
higan has five Quick State slots,
which can be used from
[the Tools menu](../interface/higan.md#the-tools-menu),
or with the appropriate
[hotkeys](../interface/higan-config.md#hotkeys).
Quick states are useful
as extra checkpoints
in games that don't have them,
or where they aren't close enough together.
Map the "Save Quick State" and "Load Quick State" hotkeys
to your controller,
and you can cheese your way through just about anything.
Manager states
--------------
higan's
[State Manager](../interface/higan-tools.md#the-state-manager)
allows you to create over a hundred save states,
and add a helpful description to each one.
Manager States are more cumbersome to use than Quick States,
since they cannot be accessed with hotkeys,
but are useful when you want quick access
to many different parts of a game.

279
docs/faq.md Normal file
View File

@@ -0,0 +1,279 @@
I see "tearing" when a game scrolls. How can I enable vsync?
------------------------------------------------------------
higan supports synchronizing video output
to the display's vertical-synchronization (or "vsync") signal,
but the option is hidden
because it often causes more problems than it solves
(see the next question).
To enable video synchronization:
- Open the higan's configuration file, `settings.bml`
- On Windows, look in `%LOCALAPPDATA%\higan`
or beside `higan.exe`
- On Linux, look in `~/.local/share/higan`
- Open it in your favourite text editor
(Windows Notepad will corrupt the file,
use WordPad if you don't have anything better)
- Find the line that says "Video"
- Somewhere below that, find an indented line
that says "Synchronize:false".
- Change "false" to "true"
- Save your changes to `settings.bml`
and restart higan
Why is video synchronization a problem for higan?
-------------------------------------------------
**The short version:**
Turning on video synchronization
cleans up video tearing,
turning on audio synchronization
cleans up audio glitches,
but turning on both
makes audio glitches worse.
**The long version:**
Enabling video synchronization
locks the frame-rate of the emulated console
to the frame-rate of your computer's display.
If your display's refresh rate exactly matches
the emulated console's,
games play at the correct speed
and everything's fine.
However,
modern 60Hz displays do not always match
the emulated console's refresh rate:
- The Super Famicom usually runs a little faster than 60Hz
- the PAL variants of most consoles run at 50Hz
- the WonderSwan runs at 75Hz
- While the Game Boy does run its LCD at 60Hz
it can turn it off and on at any time,
requiring emulation to pause
until it can get back in sync
with the computer display.
Because of these frame-rate differences,
enabling video synchronization
can force games to run
faster or slower than intended.
The consoles that higan emulates
produce video frames and audio samples at a particular rate.
If video synchronization causes
the emulated console to run, say, 5% faster than intended,
that means audio samples are also being produced 5% faster.
You might not notice the changed game speed,
but you'll almost certainly notice
the game's audio glitching constantly
as your sound card tries to keep up.
Enabling
[audio synchronization](interface/higan.md#the-settings-menu)
normally fixes this kind of audio glitching,
but with video synchronization it makes things worse:
audio is likely to glitch
while higan waits for a video frame to be shown,
and video is likely to stutter
while higan waits for an audio buffer to complete.
Games run too fast
------------------
higan runs as fast as it can,
but it will pause and wait
for the audio and video drivers to catch up
if [Synchronize Audio](interface/higan.md#the-settings-menu)
and [video synchronization][vsync]
are enabled, respectively.
If games are running way too fast, here's some things to check:
- Make sure "Synchronize Audio" is ticked in
[the Settings menu](interface/higan.md#the-settings-menu)
- Make sure the Audio driver is not set to "None"
in [the Advanced settings](interface/higan-config.md#advanced)
(remember to restart higan if you change driver settings)
- Make sure your computer has speakers or headphones connected
(some computers disable all audio if no ouputs are available)
- If you want the game to be silent,
tick "Mute Audio" in
[the Settings menu](interface/higan.md#the-settings-menu)
[vsync]: #i-see-tearing-when-a-game-scrolls-how-can-i-enable-vsync
Games run too slow
------------------
Of all the consoles higan can emulate,
higan's Super Famicom emulation
is the most resource intensive.
Full-speed emulation for the Super Famicom base unit
requires an Intel Core 2 Duo (or AMD equivalent),
full-speed for games with the SuperFX chip
requires an Intel Ivy Bridge (or equivalent),
full-speed for the wireframe animations in Mega Man X2
requires an even faster computer.
Low-power CPUs like ARM chips,
or Intel Atom and Celeron CPUS
generally aren't fast enough to emulate the Super Famicom with higan,
although other emulated consoles may work.
If your computer meets the general speed requirements
but games run too slowly,
try choosing a different
[audio driver](interface/higan-config.md#advanced),
since that's usually what drives higan's timing.
On some computers,
the operating system's power-management system
may be confused by higan's unusual pattern of CPU usage
(it runs furiously to calculate the next video frame
and the next few milliseconds of audio,
then stops dead as it waits for output to complete).
If holding down
the [fast forward hotkey](interface/higan-config.md#hotkeys)
runs too fast but the game normally runs too slow,
try disabling "power saver" mode
or enabling "performance" mode.
On Linux specifically,
if your desktop environment doesn't provide a way
to change the CPU usage governor,
you can temporarily force your CPUs to run at full-speed
by running the following command in a terminal:
echo "performance" | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
To put things back to normal,
reboot the computer, or run this command instead:
echo "ondemand" | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
Why is higan so much slower than other emulators?
-------------------------------------------------
The consoles higan emulates
generally contain different devices
(say, a CPU, an audio chip and a video chip)
that do their own thing,
but can interrupt each other at any time.
Games can and do depend on timing details like
"when the audio chip is done playing this sound,
it will interrupt the CPU at exactly the right time
for the CPU to fiddle with the video chip".
higan is therefore very cautious about timing:
while it's emulating the audio chip (for example),
at every point the emulated CPU *might* interrupt
the emulated audio chip,
higan switches to emulating the CPU up to the same point
to find out whether the CPU *will* interrupt it.
In this way,
higan is a little bit like
an office-worker trying to do the jobs of three other people
by running from desk to desk,
sending the same emails
that those three people would send to each other,
leaving themselves a note at each desk to remind themselves
where they were up to when they come back.
Although this constant switching
is slow and inefficient,
higan does it
to ensure the emulated console
always matches the behaviour
of the original.
Other emulators,
especially ones that are popular on phones and tablets,
typically work differently.
Although emulated devices *can* interrupt each other at any time,
usually they don't,
and often the only exceptions are a handful of unpopular games.
So it's pretty safe
(and much, much more efficient)
to let certain devices run "ahead" of the others.
For example,
letting the video chip render an entire scanline at a time
instead of working pixel-by-pixel.
The downside is that some games don't work properly
in these emulators,
with game-breaking bugs,
minor visual glitches,
or anything in between.
Some emulators try to recognise
when the user loads a specific problematic game,
and will adjust the shortcuts the emulator takes
so that the game runs correctly.
This is a good way to increase game compatibility
without losing efficiency,
but it means the emulator does not accurately reflect
how the original console worked,
and it requires the emulator authors to diagnose and work around
each individual supported game,
instead of having one emulator that works for everything.
Why can't higan use multiple CPU cores?
---------------------------------------
These days,
most computers contain multiple CPU cores,
allowing them to run different programs,
or different parts of the same program
at the same time.
Since higan requires high CPU performance,
sometimes people suggest that it should split its work
into multiple threads
so it can run across multiple cores
instead of stressing one core while the others lie idle.
After all,
the original consoles higan emulates
had separate devices running independently,
so why not run each emulated device in a separate thread?
The big problem with this approach is timing.
The devices in a physical Super Famicom (for example)
run at whatever speed they run at,
and talk to each other whenever they feel like.
Because every Super Famicom uses the exact same components,
every Super Famicom runs compatible games at the exact same speed,
and games can blindly assume that
when operation X is complete on device A,
device B will have finished operation Y
and be ready to do something new.
Meanwhile, higan's emulated components
take an unpredictable amount of time to do their work,
so without deliberate synchronization
things would break almost immediately.
It's not practical to make higan's emulated devices
do their work in exactly the same amount of time
as their hardware counterparts.
The problem is forty years of technology
designed to make programs run as fast as possible:
optimizing compilers and superscalar, out-of-order CPU architectures
change programs to make them faster,
speeding up some programs more than others
in ways that are very difficult to understand and predict.
Even if higan's emulated devices
ran at the exact, correct speed
on one particular computer,
they'd still run differently on any other computer,
or with a smarter compiler,
or with a smarter CPU.
Since higan needs its emulated components
to run at particular speeds,
and they won't run at those speeds naturally,
it must force them manually.
An emulated device runs for a little while,
then all the others are run until they catch up.
It's this careful management,
regular stopping and starting,
that makes higan slow,
not the actual emulation of each device,
and so it doesn't make sense
for higan to be multi-threaded.

145
docs/guides/drivers.md Normal file
View File

@@ -0,0 +1,145 @@
Unfortunately,
there's no standard for
displaying video,
playing audio,
and accepting input from game controllers
that works on every operating system.
Or rather,
there's many standards,
and different ones work best
on different computers.
Therefore,
higan comes with "drivers"
for video, audio and input,
so you can find the one that works best for you.
To see what drivers you're currently using,
or to choose different ones,
go to
[the Advanced tab](../interface/higan-config.md#Advanced)
of the Settings window.
Here are the most notable drivers
for each platform
for each category.
If your copy of higan
includes a driver not listed here,
it's probably a reasonable choice,
so try it out and see how you like it.
**Note:** After changing any driver,
you must restart higan for the change to take effect.
**Note:**
Video, Audio and Input
all have a special driver named "None".
This is a dummy driver that does nothing
(draws no video,
plays no audio,
accepts no input),
so don't choose it unless you're
trying to diagnose problems with other drivers.
Video
-----
On Windows:
- **OpenGL** is usually the best choice,
since it supports [custom shaders](shaders.md),
however it does require support for OpenGL 3.2
which excludes some integrated graphics chipsets
and old graphics cards.
- **Direct3D** is a good choice
if OpenGL is unavailable.
It also allows
[Exclusive fullscreen](../interface/higan-config.md#video),
bypassing Windows' desktop compositor.
- **GDI** is the safest choice,
but performs very poorly at large sizes.
On Linux:
- **OpenGL** is the best choice,
since it's fast
and it supports [custom shaders](shaders.md),
but requires OpenGL 3.2.
You can check what version of OpenGL
your system supports by running
`glxinfo | grep 'core profile version'`
in a terminal.
- **XVideo** is also fast,
but may be low-quality,
and generally only supports the "Blur" shader,
not "None".
- **XShm** is the safest choice,
but performs very poorly at large sizes.
Audio
-----
On Windows:
- **ASIO** offers the lowest possible latency,
but is the least likely to work on any given computer.
- **WASAPI** offers low latency,
but is only slightly more likely to work.
It also offers
[Exclusive Mode](../interface/higan-config.md#audio),
which can improve audio quality and lower latency,
but may be better or worse than shared mode
in practice.
- **XAudio2** is a good choice,
but it requires the latest (June 2010) version
of the [DirectX 9 End-User Runtime][dx9]
to be installed.
- **DirectSound** is the safest choice,
and not a bad one.
[dx9]: https://www.microsoft.com/en-us/download/details.aspx?id=35
On Linux:
- **PulseAudio** or **PulseAudioSimple**
are almost certainly the drivers to use,
since almost every Linux distribution uses
[PulseAudio](https://en.wikipedia.org/wiki/PulseAudio)
to manage audio output.
The two drivers should behave identically,
but some users report one working better than the other.
- **ALSA** is a good choice
for Linux distributions that do not use PulseAudio.
If PulseAudio is running,
this will wind up using PulseAudio's ALSA emulation,
or failing due to "sound card already in use",
unless you use a tool like `pasuspender`.
- **OSS** may be useful
for non-Linux platforms,
or for Linux systems with the third-party OSSv4
kernel drivers installed.
For most Linux systems,
this will use ALSA's OSS emulation,
PulseAudio's OSS emulation,
or not work at all.
TODO: If the audio driver is set to None,
or you have no audio device,
Sync Audio does not work
and games will run in fast-forward
unless you enable Sync Video.
https://board.byuu.org/viewtopic.php?p=44138#p44138
Input
-----
On Windows,
"Windows" is the only driver available,
and uses RawInput for keyboard and mouse input,
XInput for Xbox controllers,
and DirectInput for other controllers.
On Linux:
- **udev** supports every input device,
but requires a modern Linux system.
- **Xlib** is the safest choice,
but only supports keyboard and mouse input.

560
docs/guides/import.md Normal file
View File

@@ -0,0 +1,560 @@
Before it can load a game,
higan requires that all the game's data
be stored correctly in
[the Game Library](../concepts/game-library.md).
For [regular games](#regular-games)
this is simple,
but some games require special treatment,
especially games that make use of
unusual hardware.
Regular games
-------------
icarus supports importing games
in the most commonly-used formats
for each supported console,
and also those same formats inside `.zip` files.
More advanced compression formats
like RAR or 7-zip are not supported.
For most games
that do not use special chips or co-processors,
importing a game is straight-forward.
From [the Library menu](#the-library-menu)
choose "Load ROM File ..."
to open [a filesystem browser](#the-filesystem-browser),
choose the game you want to play,
and it will be imported into the library and loaded.
To play the game again
select the console the game runs on from
[the Library menu](#the-library-menu)
to open another [filesystem browser](#the-filesystem-browser)
that lists all the previously-imported games for that platform.
Games with co-processor firmware
--------------------------------
Many games included extra chips inside the game cartridge,
to provide enhanced capabilities of one kind or another.
Sometimes,
those extra chips were separate CPUs
running their own separate firmware,
and for those cases
higan requires a copy of the co-processor firmware
as well as the actual game.
Unfortunately,
like games themselves,
co-processor firmware cannot legally be distributed,
so you'll need to obtain
copies of the relevant firmware data
yourself.
To import a game that requires co-processor firmware,
you must copy the required firmware files
beside the game you want to import.
For example,
if you want to import Megaman X2,
which is stored in the file `mmx2.sfc`,
the file `cx4.data.rom`
must be placed in the same folder
for the import to succeed.
Wikipedia [lists which Super Famicom games use which co-processors][wpec],
although not all co-processors require separate firmware.
Once you've figured out which co-processor
(if any)
is used by the game you want to import,
here's the firmware files you'll need:
[wpec]: https://en.wikipedia.org/wiki/List_of_Super_NES_enhancement_chips#List_of_Super_NES_games_that_use_enhancement_chips
<table>
<thead>
<tr>
<th>Co-processor</th>
<th>Filename</th>
<th>Size (bytes)</th>
<th>SHA256</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">CX4</th>
<td><code>cx4.data.rom</code></td>
<td>3072</td>
<td><code>ae8d4d1961b93421ff00b3caa1d0f0ce7783e749772a3369c36b3dbf0d37ef18</code></td>
</tr>
<tr>
<th scope="row" rowspan=2>DSP1/1A<br><sup>See Note 1</sup></th>
<td><code>dsp1.data.rom</code></td>
<td>2048</td>
<td><code>0b5da6533e55852ee8fc397977ec5576c5b9f1fb2e05656d8f87123a121b076e</code></td>
</tr>
<tr>
<td><code>dsp1.program.rom</code></td>
<td>6144</td>
<td><code>269584b347a22953a2989494c850a7c1c027f4ca5add517a60e0c7d8833d0fac</code></td>
</tr>
<tr>
<th scope="row" rowspan=2>DSP1B<br><sup>See Note 2</sup></th>
<td><code>dsp1b.data.rom</code></td>
<td>2048</td>
<td><code>8546cbac530830446bb8a277f6b139d4ad64d650bdbac7e4e150e2f095665049</code></td>
</tr>
<tr>
<td><code>dsp1b.program.rom</code></td>
<td>6144</td>
<td><code>2eccb54a8f89374911f7e2db48f1b4cde855655e28103f7bda2982a5b418a187</code></td>
</tr>
<tr>
<th scope="row" rowspan=2>DSP2</th>
<td><code>dsp2.data.rom</code></td>
<td>2048</td>
<td><code>3beef9bffdc1e84c9f99f3301d8bd3e520d2e62909a995320f9faeae8f46ec11</code></td>
</tr>
<tr>
<td><code>dsp2.program.rom</code></td>
<td>6144</td>
<td><code>62a2ef8d2d7db638f4ec0fbcebf0e5bf18a75ee95be06e885d9519a10487f0da</code></td>
</tr>
<tr>
<th scope="row" rowspan="2">DSP3</th>
<td><code>dsp3.data.rom</code></td>
<td>2048</td>
<td><code>7fe51796e9c97fee1fa2aab40592b7c78997f67dd00333e69d0f79a12f3cb69f</code></td>
</tr>
<tr>
<td><code>dsp3.program.rom</code></td>
<td>6144</td>
<td><code>aea7b622e7c1de346cb15d16afcbedf92b6798507e179f83ed2a4cff40d0e663</code></td>
</tr>
<tr>
<th scope="row" rowspan="2">DSP4</th>
<td><code>dsp4.data.rom</code></td>
<td>2048</td>
<td><code>ef3ffb4256dd896a60213269b4e2d3bdd120c97e2fd623bddabbf43c2be422af</code></td>
</tr>
<tr>
<td><code>dsp4.program.rom</code></td>
<td>6144</td>
<td><code>89b1826e6038be3a0ea0f923e85d589ff6f02dc1a1819fb2ec8c0cea5b333dcd</code></td>
</tr>
<tr>
<th scope="row" rowspan="2">ST010</th>
<td><code>st010.data.rom</code></td>
<td>4096</td>
<td><code>dc7056a51b53993d7a8ba5bacf9501f785d2fce5e5be748e9ff2737c5938d4a5</code></td>
</tr>
<tr>
<td><code>st010.program.rom</code></td>
<td>49152</td>
<td><code>2c1f74bb5f466d81c64c326e71ac054489efe1abc9a5d6f91aac7747f2ddab67</code></td>
</tr>
<tr>
<th scope="row" rowspan="2">ST011</th>
<td><code>st011.data.rom</code></td>
<td>4096</td>
<td><code>b5377d1bebe8adc507a024b6e2b9b8fdf4877e451da84fbad05dff4e4a70311e</code></td>
</tr>
<tr>
<td><code>st011.program.rom</code></td>
<td>49152</td>
<td><code>d90a5cda380e81cb9ba11a9da7539b173c49b31bedc7a3ac9c3c8b3f97e89e14</code></td>
</tr>
<tr>
<th scope="row" rowspan="2">ST018</th>
<td><code>st018.data.rom</code></td>
<td>32768</td>
<td><code>b5377d1bebe8adc507a024b6e2b9b8fdf4877e451da84fbad05dff4e4a70311e</code></td>
</tr>
<tr>
<td><code>st018.program.rom</code></td>
<td>131072</td>
<td><code>d90a5cda380e81cb9ba11a9da7539b173c49b31bedc7a3ac9c3c8b3f97e89e14</code></td>
</tr>
</tbody>
</table>
**Note 1:**
The DSP1 and DSP1A are physically different,
but the firmware inside is identical.
**Note 2:**
The DSP1B is very similar to the DSP1A,
but has some bugs fixed.
Note that icarus' heuristics cannot distinguish between
a game that uses DSP1
and one that uses DSP1B,
so if it cannot find your game in its manifest database,
it will assume it uses DSP1B.
Many games work just as well with either DSP1 or DSP1B,
but Pilotwings is a notable exception.
If you try to import a game
using the "Import ROM Files ..." option
in [the Library menu](#the-library-menu)
(or using icarus directly)
but do not have the required firmware files
in the correct place,
a window will appear saying
"Import completed, but with 1 errors. View log?"
(or howevery many games were lacking the correct firmware).
If you press "Yes",
a new window will appear listing the games that couldn't be imported,
and at least one firmware file that was missing or incorrect, like this:
> [smk.zip] firmware (dsp1b.program.rom) missing or invalid
If you try to import a game
using the "Load ROM File ..." option
in [the Library menu](#the-library-menu)
but do not have the required firmware files
in the correct place,
nothing will happen,
and higan will just sit there
with "No cartridge loaded" in
[the status bar](#the-status-bar).
Once a game with co-processor firmware is imported,
you can play it just like any [regular game](#importing-and-playing-regular-games).
Satellaview games
-----------------
The [Satellaview][wpbsx]
was a satellite modem peripheral
released for the Super Famicom in Japan.
As well as the actual modem
(designed to sit underneath the Super Famicom),
it also included a cartridge
with software to control the modem,
browse online services,
and download games and data.
This control cartridge was called
*BS-X Sore wa Namae o Nusumareta Machi no Monogatari*,
which translates as
*BS-X The Story of The Town Whose Name Was Stolen*.
[wpbsx]: https://en.wikipedia.org/wiki/Satellaview
The control cartridge had a slot that accepted
rewritable "memory paks",
so that people could store the games and data they downloaded.
A small number of games that did not use the Satellaview modem
also had a memory pak slot,
so the game's publishers could
publish extra content for the game
via the Satellaview service
after the game's release.
For the benefit of people who didn't own a Satellaview
some read-only memory paks
were sold in retail stores
containing extra content for specific games.
Importing a game that has a slot for a memory pak
is just like [importing a regular game](#importing-and-playing-regular-games).
Importing a memory pak is like importing a regular game,
but the name of the memory pak file *must* end in `.bs`
(if it's in a `.zip` file,
that's OK,
but the name *inside* the `.zip` file
must end in `.bs`)
in order for it to be successfully imported.
Sometimes memory pak filenames end in `(BSROM).sfc`,
which will make higan try to import them as
regular Super Famicom games,
and fail miserably.
Rename the file and it should work beautifully.
Playing a game that has a slot for a memory pak
is just like playing a regular game,
but after you have selected which game you want to play
higan will open another
[filesystem browser](#the-filesystem-browser)
to let you pick which previously-imported memory pak
you want to insert into the game.
If you press "Cancel" at this point,
the game will load without any cartridge in its memory pak slot.
If you load the control cartridge into higan,
make sure the emulated Satellaview
is connected to the emulated Super Famicom's expansion port
by going to the "Super Famicom" menu,
selecting the "Expansion Port" sub-menu,
and choosing "Satellaview".
If the expansion port was previously
configured with a different option,
power-cycle the Super Famicom
(also in the "Super Famicom" menu)
to make sure the control cartridge will find the Satellaview
when it starts up.
Note that higan's Satellaview emulation is not very accurate,
so the control cartridge may not work as it should.
Playing a memory pak on its own doesn't make much sense,
it's not a standalone cartridge.
Play a game with a memory pak slot,
and choose which memory pak you want when higan asks for it.
For more information about the Satellaview service,
a translation patch for the control cartridge
and emulators that do a better job of Satellaview emulation,
see [the BS-X Project](https://bsxproj.superfamicom.org/).
Sufami Turbo games
------------------
The [Sufami Turbo][wpst]
was a special cartridge released
for the Super Famicom in Japan.
The Sufami Turbo on its own does nothing,
but it has two slots in the top
that accept Sufami Turbo mini-cartridges.
The game in slot A is the one that actually plays,
but some games can make use of additional data
from a game in slot B.
Importing the Sufami Turbo cartridge
is just like [importing a regular game](#importing-and-playing-regular-games).
Importing a mini-cartridge is like importing a regular game,
but the name of the memory pak file *must* end in `.st`
(if it's in a `.zip` file,
that's OK,
but the name *inside* the `.zip` file
must end in `.st`)
in order for it to be successfully imported.
Sometimes memory pak filenames end in `(ST).sfc`,
which will make higan try to import them as
regular Super Famicom games,
and fail miserably.
Rename the file and it should work beautifully.
To play a Sufami Turbo game,
load the Sufami Turbo cartridge like any other game.
higan will open another
[filesystem browser](#the-filesystem-browser)
to let you pick which previously-imported mini-cartridge
you want to insert into slot A.
If you press "Cancel" at this point,
the Sufami Turbo cartridge will boot without anything in slot A,
which just displays an image telling you
to turn off your Super Famicom,
insert a game into slot A,
and try again.
If you chose a cartridge for slot A,
higan will yet open another
filesystem browser
to let you choose a mini-cartridge for slot B.
If you press "Cancel" at this point,
the Sufami Turbo cartridge will boot without anything in slot B.
[wpst]: https://en.wikipedia.org/wiki/Sufami_Turbo
Super Game Boy games
--------------------
The Super Game Boy was a special cartridge
released for the Super Famicom
(and all its regional variants around the world)
that allowed Game Boy games to be played
via the Super Famicom's controllers and video output.
The Super Game Boy 2 was released in Japan,
and had some minor extra features
beyond the original Super Game Boy,
but importing and playing games
works the same way in higan.
The Super Game Boy cartrige includes
the complete hardware of an original
(black-and-white)
Game Boy,
so it needs a boot ROM:
<table>
<thead>
<tr>
<th>Cartridge</th>
<th>Filename</th>
<th>Size (bytes)</th>
<th>SHA256</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">SGB</th>
<td><code>sgb.boot.rom</code></td>
<td>256</td>
<td><code>0e4ddff32fc9d1eeaae812a157dd246459b00c9e14f2f61751f661f32361e360</code></td>
</tr>
<tr>
<th scope="row">SGB2</th>
<td><code>sgb.boot.rom</code></td>
<td>256</td>
<td><code>fd243c4fb27008986316ce3df29e9cfbcdc0cd52704970555a8bb76edbec3988</code></td>
</tr>
</tbody>
</table>
Yes,
the SGB and SGB2 have different firmware,
but higan expects the same filename for both.
To import the SGB base cartridge,
you must copy the required firmware file
into the same directory.
Then you may import it just like
[a regular game](#importing-and-playing-regular-games).
To play a Game Boy game in Super Game Boy mode,
load the Super Game Boy cartridge like any other game.
higan will open another
[filesystem browser](#the-filesystem-browser)
to let you pick which previously-imported Game Boy game
you want to insert into the Super Game Boy.
If you press "Cancel" at this point,
higan will crash, so don't do that.
Note that only games for the original, black-and-white Game Boy
can be used with the Super Game Boy.
Some games designed for the Game Boy Color
were backward compatible with the original Game Boy
and hence the Super Game Boy;
see [Playing Game Boy Colour games in Game Boy mode][blackcarts]
for details.
[blackcarts]: #playing-game-boy-color-games-in-game-boy-mode
MSU-1 games
-----------
The MSU-1 is a fictional expansion chip
invented by higan's author byuu
for use with Super Famicom games,
designed to allow streaming data and audio.
Although the MSU-1 is not specific
to any particular storage medium,
it gives the Super Famicom similar capabilities
to CD-based add-ons
like the Mega Drive's Mega CD
and the PC Engine's CD-ROM²,
such as CD-quality music and full-motion video.
One thing to be aware of
when importing an MSU-1 game
is that early firmware versions
of the [SD2SNES][sd2snes] programmable cartridge
had a bug that caused MSU-1 music to play too quietly.
Skipping over [the full details][msu1vol],
the short version is this:
- If offered the choice between "boosted" or non-boosted audio,
you want the non-boosted version.
- If an MSU-1 mod for a commercial game offers
"emulator" and "hardware" versions of the patch file,
it means the audio tracks are already boosted.
- Some
[third](https://www.zeldix.net/t1265-#18320)
[parties](https://www.zeldix.net/t1339-#19818)
have created replacement, non-boosted audio tracks
for the most popular MSU-1 mods.
If the mod you want to play has a replacement pack,
use it with the "hardware" version of the patch.
- Even without access to non-boosted audio tracks,
it may be that the existing audio is only slightly boosted,
so try the "hardware" version first, for best quality.
- If the audio tracks are heavily boosted,
the "hardware" patch may sound terrible,
distorting and clipping,
in which case try the "emulator" patch.
To import an MSU-1 game:
1. If you have a single, large file
with the `.msu1` extension,
that is a pack for use with [Mercurial Magic][mermag],
which can automatically set up a game folder
in the correct format.
Go read Mercurial Magic's documentation
instead of these instructions.
2. Otherwise,
import the Super Famicom ROM with icarus,
[like a regular game](#importing-and-playing-regular-games).
- If this is a homebrew game with MSU-1 support,
there will probably be an ordinary ROM
whose name ends in `.sfc`,
which is the file you want to import.
- If this is a commercial game modded for MSU-1 support,
there will probably be a patch file
whose name ends in `.ips` or `.bps`.
Get a copy of the correct version of the commercial game,
apply the patch with a tool like [Flips][flips],
then import the patched file.
- If there's "hardware" and "emulator" versions of the patch,
see "One thing to be aware of..." above.
3. Find the game folder in [the game library](#the-game-library)
that icarus created when it imported the game.
4. Copy the MSU-1 data file into the game folder.
- This should be named `msu1.rom`
- If there's no file by that name,
look for a file with a `.msu` extension
and rename it to `msu1.rom`.
- If there's no file ending in `.msu` either,
create an empty file named `msu1.rom`.
5. Copy the audio tracks into the game folder.
- If you have to choose between two sets of audio files,
see "One thing to be aware of..." above.
- These should be named
`track-1.pcm`,
`track-2.pcm`,
... `track-9.pcm`,
`track-10.pcm`,
etc.
- If there's no files with those names,
there should be other numbered `.pcm` files
that you can rename to match what higan expects.
- If the `.pcm` files have no numbers in the filenames,
there maybe a `.bml` or `.xml` file that lists
which number goes with which file.
- If there's no `.pcm` files at all,
that's OK,
this game probably just doesn't use the audio-playback feature.
Once the game folder is set up,
playing an MSU-1 game is just like
[a regular game](#importing-and-playing-regular-games).
[sd2snes]: https://sd2snes.de/
[flips]: http://www.romhacking.net/utilities/1040/
[msu1vol]: http://blog.qwertymodo.com/2017/07/the-msu-1-volume-fiasco-explained.html
[mermag]: https://github.com/hex-usr/Mercurial-Magic/
Patched games
-------------
The console emulation community
has a long and vibrant history of game modding,
or [ROM hacking][rhdn],
including fan-translations,
new levels for existing games,
and more.
Since distributing the modified versions of existing games
would be copyright infringement,
the changes are typically distributed as "patches",
a file containing a list of modifications to make,
that can be automatically applied by a "patcher" tool
like [Flips][flips].
higan does not support soft-patching,
so if you want to play a patched game in higan,
you will need to use a patcher to apply it yourself,
creating a new, patched copy of the game.
Then you can import and play the patched game just like
[a regular game](#importing-and-playing-regular-games).
[rhdn]: http://www.romhacking.net/

126
docs/guides/shaders.md Normal file
View File

@@ -0,0 +1,126 @@
Most of the consoles higan emulates
were designed for the low resolution of NTSC televisions,
and their video output is often chunky and blocky
by today's standards.
Shaders customise how a console's video output
is drawn to the computer screen,
and can apply just about any effect you can imagine.
Most [drivers](drivers.md)
only support these shaders
(some only support one or the other):
- **None** draws each computer pixel
in the same colour as the nearest console pixel.
This is sometimes called "nearest neighbour scaling",
and produces crisp, blocky output.
- **Blur** draws each computer pixel
as the weighted average colour
of the four nearest console pixels.
This is sometimes called "bilinear scaling",
and hides some of the blockiness
at the expense of blurring edges.
However,
the OpenGL driver supports custom shaders,
in addition to the above.
**Note:**
For technical reasons,
higan's emulation of certain consoles
can produce surprising behaviour
in certain shaders,
particularly shaders that compare each console pixel
with its neigbours.
See [Console-specific Notes](../notes.md) for details.
# Where to get shaders
- higan includes some simple example shaders.
If your copy of higan did not come with shaders,
you can get them from
[the unofficial higan repository](https://gitlab.com/higan/higan/tree/master/shaders).
- [quark-shaders](https://github.com/hizzlekizzle/quark-shaders)
contains many high-quality shaders for use with higan.
- You can write your own.
# How to install shaders
Make sure the shader you want to install
is in the correct format:
it should be a folder whose name ends in `.shader`,
it should contain a file named `manifest.bml`,
and probably some `*.fs` or `*.vs` files.
Place the shader folder inside
the `Video Shaders` directory
of your higan installation.
If you don't have a `Video Shaders` directory,
create it beside the `*.sys` directories
like `Game Boy Advance.sys` and `Super Famicom.sys`.
- On Windows,
this is probably the directory containing `higan.exe`
- On Linux,
this is probably `~/.local/share/higan`
Launch higan,
open the Settings menu,
and choose "Advanced ..."
to open [the Advanced tab](../interface/higan-config.md#advanced)
of the Settings dialog.
Under "Driver Selection",
make sure "Video" is set to "OpenGL".
If you changed the video driver,
you'll need to restart higan
for the change to take effect.
Open the Settings menu again,
choose the "Video Shader" submenu,
and now the shaders you installed
should be listed at the bottom of the menu.
Load a game
(so you can see the results)
and switch between shaders
to see what they do
and pick your favourite!
# Notable examples
The quark-shaders repository
contains lots of carefully-crafted shaders,
but some are particularly noteworthy:
- **AANN** implements "anti-aliased nearest neighbour" scaling.
If the console's video is not displayed
at an exact multple of the console's native resolution,
rounding errors cause normal nearest-neighbour scaling
to draw some rows and columns wider than others,
which many people find ugly and distracting.
This is very common when
higan's aspect-ratio correction mode
is enabled.
AANN uses very slight anti-aliasing
to hide the rounding errors,
leaving the overall image as crisp as nearest-neighbour.
- **Gameboy** emulates the squarish aspect-ratio
greenish-colours
and limited palette
of the original Game Boy.
At larger scales,
you can even see the fine gaps between each pixel,
and the shadow that dark colours would cast
on the LCD background.
- **NTSC** performs NTSC encoding,
bandwidth limiting,
and NTSC decoding of the video image to recreate
the colour fringing,
blurring
and shimmer
that most game players would have seen
on real televisions.
This is important because
some games depended on NTSC artifacts
to display colours outside the console's official palette
or to create effects like transparency.

117
docs/index.md Normal file
View File

@@ -0,0 +1,117 @@
higan, the multi-system emulator
================================
higan emulates a number of classic videogame consoles of the 1980s and 1990s,
allowing you to play classic games on a modern general-purpose computer.
About higan
-----------
As of v102,
higan has top-tier support for the following consoles:
- Nintendo Super Famicom/Super Nintendo Entertainment System,
including addon hardware:
- Super Game Boy
- Sufami Turbo
- Nintendo Game Boy Advance
It also includes some level of support for these consoles:
- Satellaview addon for the Super Famicom
- Nintendo Famicom/Nintendo Entertainment System
- Nintendo Game Boy
- Nintendo Game Boy Color
- Sega Master System
- Sega Game Gear
- Sega Megadrive/Genesis
- NEC PC Engine/TurboGrafx 16 (but not the CD-ROM² System/TurboGrafx-CD)
- NEC SuperGrafx
- Bandai Wonderswan
- Bandai Wonderswan Color
**Note:** Some consoles were released under different names
in different geographic regions.
To avoid listing all possible names
every time such a console is mentioned,
higan uses the name from the console's region of origin.
In practice,
that means Japanese names:
"Famicom" and "Super Famicom" instead of NES and SNES,
"Mega Drive" instead of "Genesis",
"PC Engine" instead of "TurboGrafx-16".
higan is actively supported on
FreeBSD 10 and above, and
Microsoft Windows 7 and above.
It also includes some level of support
for GNU/Linux and macOS.
If you want to install higan and try it out,
see the [Quick Start](#quick-start) section below.
higan is officially spelled with a lowercase "h", not a capital.
About this document
-------------------
This is the unofficial higan README,
a community-maintained introduction and reference.
It may be out of date
by the time you read this,
and it may contain errors or omissions.
If you find something that's wrong,
or you have a suggestion,
see "Unofficial higan resources" below.
Official higan resources
------------------------
- [Official homepage](https://byuu.org/emulation/higan/)
- [Official forum](https://board.byuu.org/viewforum.php?f=4)
Unofficial higan resources
--------------------------
- [Source code repository](https://gitlab.com/higan/higan/)
archives official higan releases
and WIP snapshots
since approximately v067r21.
- [Quark shader repository](https://github.com/hizzlekizzle/quark-shaders)
collects shaders that higan can use
to add special effects like TV scanlines to its video output,
or smarter algorithms for scaling up to modern PC resolutions.
See [Installing custom shaders][shaders] below for details.
- [Mercurial Magic](https://github.com/hex-usr/Mercurial-Magic/)
is a tool for converting MSU-1 games and mods into a format
higan can use.
See [Importing MSU-1 games][msu1] below for details.
[shaders]: #installing-custom-shaders
[msu1]: #importing-msu-1-games
There are also other projects
based on current or older versions of higan,
in whole or in part,
that you might want to check out.
- [Mednafen](https://mednafen.github.io/)
is another multi-system emulator.
Its Super Famicom emulation is based on bsnes v059,
from the time before bsnes was renamed to higan.
- [BizHawk](http://tasvideos.org/BizHawk.html)
is another multi-system emulator,
specialising in the creation of
tool-assisted speedruns.
Its Super Famicom emulation is based on bsnes v087.
- [nSide](https://github.com/hex-usr/nSide)
is a fork of higan that greatly enhances
its NES emulation support,
and adds minor features to the other cores too.
It also restores the "balanced" Super Famicom emulation core
that was removed from higan in v099,
which is less CPU intensive
than the current accuracy-focussed core.
- [bsnes-plus](https://github.com/devinacker/bsnes-plus)
is a fork of bsnes v073
that adds improved support for debugging Super Famicom software.

52
docs/install/general.md Normal file
View File

@@ -0,0 +1,52 @@
Installing the GBA BIOS
-----------------------
For most of the systems higan emulates,
the console itself contains (almost) no actual software,
so emulating the system does not require
infringing the copyright of the hardware manufacturer.
However,
the Game Boy Advance is different:
every device contains a standard library of software routines
for [common functions games require][bios],
often called a "BIOS"
by analogy with the Basic Input/Output System
used in IBM PC compatibles.
For the same legal reasons that commercial games
cannot be distributed with emulators,
the GBA BIOS cannot be distributed with higan,
but is required for GBA software to run.
If you have a real GBA and a flashcart,
the Internet contains many tools
that will extract the BIOS image so it can be copied
to your desktop computer.
The correct GBA BIOS file is exactly 16384 bytes long,
and has the SHA-256 hash
fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570.
Once you have the correct BIOS file:
1. rename it to `bios.rom`
- if you're using Windows,
turn off "hide extensions for known file types"
so you don't wind up with a file called
`bios.rom.dat`
or whatever the file's original extension was.
2. Copy the file into higan's `Game Boy Advance.sys` directory,
alongside the `manifest.bml` file that is already there.
- In Windows,
find `Game Boy Advance.sys` in the same folder
as `higan.exe`
- In Linux,
find `Game Boy Advance.sys` in
`~/.local/share/higan/`
**Note:**
If you upgrade this version of higan to a newer version,
make sure the `bios.rom` file
winds up in the `Game Boy Advance.sys` directory
of the new version.
[bios]: http://problemkaputt.de/gbatek.htm#biosfunctions

136
docs/install/linux.md Normal file
View File

@@ -0,0 +1,136 @@
Compiling from source on Linux
------------------------------
You will need a copy of the higan source-code.
If you download an official release from the higan homepage,
you will need [7-zip][7z] or a compatible tool to extract it.
Alternatively,
you may obtain higan source code from
[the unofficial git repo](https://gitlab.com/higan/higan/)
using the Git source-code management tool,
or by clicking the download button on the right-hand side of the web-page
and choosing an archive format.
You will also need GCC 4.9 or higher,
including the C and C++ compiler,
GNU Make,
and development files
(headers, etc.)
for the following libraries:
- GTK 2.x
- PulseAudio
- Mesa
- gtksourceview 2.x
- Cairo
- SDL 1.2
- libXv
- libAO
- OpenAL
- udev
On a Debian-derived Linux distribution,
you can install everything you need with a command like:
sudo apt-get install build-essential libgtk2.0-dev libpulse-dev \
mesa-common-dev libgtksourceview2.0-dev libcairo2-dev libsdl1.2-dev \
libxv-dev libao-dev libopenal-dev libudev-dev
Once you have all the dependencies installed,
you may build and install higan.
Note: Run these commands as yourself,
**do not run them as root**
(no `sudo`, no `su`, etc.),
because higan does not support
being installed system-wide.
1. Put the higan source code in some convenient location,
like `~/higan-src`
2. Open a terminal window
3. Type `cd ~/higan-src`
(or wherever you put the higan source)
and press Enter
4. Type `make -C icarus compiler=g++` and press Enter
to build the icarus import tool
5. Type `make -C higan compiler=g++` and press Enter
to build the main higan executable
Installing a compiled build on Linux
------------------------------------
Assuming you have successfully compiled higan
as described in the previous section:
1. Open a terminal window
2. Type `cd ~/higan-src`
(or wherever you put the higan source)
and press Enter
3. Type `make -C icarus install` and press Enter
to install icarus and its game database
4. Type `make -C higan install` and press Enter
to install higan and its supporting files
This installs higan and its associated data files
into the `~/.local` directory hierarchy.
To confirm higan is installed correctly,
type `higan` in a terminal and press Enter.
If the higan window appears,
everything is working.
On the other hand,
if you get an error message like "command not found",
you should double-check that the directory `~/.local/bin`
is included in your `$PATH` environment variable
by running the following command in a terminal:
echo "$PATH" | tr ':' '\n' | grep ~/.local/bin
If the above command prints the full path of `~/.local/bin`
(for example: `/home/yourname/.local/bin`)
then you should be good.
If it prints nothing,
you need to add the following line to `~/.profile`:
export PATH=~/.local/bin:$PATH
(this line must be in `~/.profile` because
most GUIs do not read any other files at login)
If you also have a `~/.bash_profile`,
make sure it reads the contents of `~/.profile`
with a line like this:
source ~/.profile
You will need to log out and log back in
for changes to `~/.profile` or `~/.bash_profile`
to take effect.
Before you can actually play games,
you'll need to [import them](#the-game-library)
and [configure higan](#configuring-higan).
If you want to play Game Boy Advance games,
you will need [a GBA BIOS](#installing-the-gba-bios).
Uninstalling a compiled build on Linux
--------------------------------------
To uninstall higan,
as installed by the above instructions:
1. Open a terminal window
2. Type `cd ~/higan-src`
(or wherever you put the higan source)
and press Enter
3. Type `make -C icarus uninstall` and press Enter
4. Type `make -C higan uninstall` and press Enter
To remove higan's configuration,
delete the directory `~/.config/higan` as well.
To remove the games imported into higan's library
(including in-game saves and save-states),
delete the directory `~/Emulation`.
You may also wish to delete the higan source directory.

163
docs/install/windows.md Normal file
View File

@@ -0,0 +1,163 @@
Installing an official release on Windows
-----------------------------------------
Official higan releases are distributed in [7-zip][7z] archives.
You will need to install 7-zip,
or another compatible archiving tool,
to install higan.
[7z]: http://www.7-zip.org/
Once you have a suitable archiving tool,
extract the contents of the higan archive into a new folder.
When you're done,
the new folder should contain `higan.exe` and `icarus.exe`
along with other assorted files and directories
that describe the systems higan emulates.
You may put that folder wherever you like.
To run higan, open the `higan.exe` file.
Before you can actually play games,
you'll need to [import them](#the-game-library)
and [configure higan](#configuring-higan).
If you want to play Game Boy Advance games,
you will need [a GBA BIOS](#installing-the-gba-bios).
Uninstalling an official release on Windows
-------------------------------------------
Delete the folder containing `higan.exe`
and the other associated data from the original archive.
To remove higan's configuration:
1. Press Win+R to open the Run dialog
2. Type `%LOCALAPPDATA%` and press Enter
to open the folder where higan's configuration data lives
3. Delete the subdirectories named `icarus` and `higan`
if they exist.
You might also want to remove the games imported into higan's library
(including in-game saves and save-states):
1. Press Win+R to open the Run dialog
2. Type `%USERPROFILE%` and press Enter
to open the folder where higan keeps its game library
3. Delete the folder named `Emulation` if it exists
Compiling from source on Windows
--------------------------------
You will need a copy of the higan source-code.
If you download an official release from the higan homepage,
you will need [7-zip][7z] or a compatible tool to extract it.
Alternatively,
you may obtain higan source code from
[the unofficial git repo](https://gitlab.com/higan/higan/)
using the Git source-code management tool,
or by clicking the download button on the right-hand side of the web-page
and choosing an archive format.
You will need a C++ compiler to compile higan.
We recommend installing [TDM64-GCC][tdm],
preferably the latest version
but anything newer than 4.9 should be fine.
higan does not support building with clang++
(Clang is still not quite there yet for Windows)
nor Microsoft Visual C++
(last we checked, it didn't support all the C++ features higan uses).
**Note:** Make sure you get TDM64-GCC,
not TDM-GCC.
When compiled in x86 (32-bit) mode,
higan may crash at startup
because gcc targeting x86 does not support
Windows' structured exception handling (SEH).
Also,
historically in x86 mode
gcc has miscompiled a part of the NES emulation core.
See the higan forum
[for](https://board.byuu.org/viewtopic.php?p=41977#p41977)
[details](https://board.byuu.org/viewtopic.php?p=42253#p42253).
Once you've installed mingw-w64,
open a command-prompt window,
type `g++ --version`
then press Enter
to check it's installed correctly.
You should see a message like
g++ 1.2.3 20010101
Copyright (C) 2001 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
...except it should mention the version of mingw that you installed
and the corresponding dates.
If you see an error message like "command not found"
or "bad command or filename",
you may need to add mingw's "bin" folder
to your computer's `%PATH%`.
See the mingw documentation for help with that.
Once mingw is installed and available from the command prompt:
1. Put the higan source code in some convenient location,
like `C:\higan-src`
2. Open the command-prompt
3. Type `cd C:\higan-src`
(or wherever you put the higan source)
and press Enter
4. Type `mingw32-make -C icarus compiler=g++` and press Enter
to build the icarus import tool
5. Type `mingw32-make -C higan compiler=g++` and press Enter
to build the main higan executable
[tdm]: http://tdm-gcc.tdragon.net/download
Installing a compiled build on Windows
--------------------------------------
1. In Windows Explorer,
create the folder where you want higan to live
2. Assuming you built higan in `C:\higan-src`,
copy `C:\higan-src\icarus\out\icarus.exe`
into the new folder
3. Copy `C:\higan-src\icarus\Database` and its contents
into the new folder
4. Copy `C:\higan-src\higan\out\higan.exe`
into the new folder
5. Copy all the `*.sys` directories
in `C:\higan-src\higan\systems`
into the new folder
The new folder should now contain
`icarus.exe`,
`higan.exe`,
a folder named `Database`,
and half a dozen folders named after the systems higan emulates
with `.sys` at the end.
This is what you would get by downloading an official build,
as described under
[Installing an official release on Windows][instwin]
above.
[instwin]: #installing-an-official-release-on-windows
Before you can actually play games,
you'll need to [import them](#the-game-library)
and [configure higan](#configuring-higan).
If you want to play Game Boy Advance games,
you will need [a GBA BIOS](#installing-the-gba-bios).
Uninstalling a compiled build on Windows
----------------------------------------
The process is the same as
[Uninstalling an official release on Windows][uninstwin]
above. You may also wish to delete the higan source folder.
[uninstwin]: #uninstalling-an-official-release-on-windows

61
docs/interface/common.md Normal file
View File

@@ -0,0 +1,61 @@
The Filesystem Browser
----------------------
Sometimes higan will need
to ask you to choose a file or folder.
For this, it uses a special Filesystem Browser dialog.
Although many operating systems provide a native filesystem browser,
they do not all allow the same customizations.
Therefore,
higan provides its own filesystem browser
that works the same way on every platform.
The filesystem browser shows the contents of some particular folder,
and allows you to select one of those items.
Across the top of the window,
a text-box displays the path of the current folder.
If you want to browse a specific path,
you may edit the contents of this box
and press Enter to switch to the new location.
The button with two blue arrows at the top-right is "Refresh".
Pressing this button will check for
added (or removed) items in the current folder,
and add (or remove) them to (or from) the list.
The button with the house at the top-right is "Home".
Pressing this button will switch to your home folder.
The button with the green up-arrow at the top right is "Parent".
Pressing this button will
switch to the parent of the current folder.
Most of the filesystem browser lists the contents
of the current directory.
Double-clicking a folder,
or selecting it and pressing Enter,
will switch to showing the contents of that directory.
If the list has keyboard focus,
typing any text will jump to the first inem in the list
whose name begins with the text you typed.
If a drop-down list appears in the bottom-left,
it allows you to choose which files appear in the list,
based on file-extension.
If this filesystem browser is asking for a file,
you can choose one
by double-clicking it,
by selecting it and pressing Enter,
or by selecting it and clicking the "Select" button in the bottom-right.
If this filesystem browser is asking for a directory,
you can choose one
by selecting it and clicking the "Select" button in the bottom-right.
Double-clicking
or selecting and pressing Enter don't work,
they just switch to viewing that directory.
The "Cancel" button in the bottom-right
closes the filesystem browser without selecting anything.

View File

@@ -0,0 +1,40 @@
# Synopsis
> higan [*\-\-fullscreen*] [*PATH*]
# Description
TODO: Put `NTSC-J|`, `NTSC-U|` or `PAL|`
at the beginning of the path
to force a region
for consoles where it can't be detected.
When launched with `--fullscreen`,
higan will automatically enter full-screen mode
when it starts.
This is not much use unless you also specify `PATH`,
because you won't be able to load a game
until you exit full-screen mode
by pressing the "Toggle Fullscreen"
[hotkey](higan-config.md#hotkeys).
When `PATH` is the path to an existing
[game folder](../concepts/game-folders.md)
for any supported console,
that game will be loaded when higan starts.
When `PATH` is the path to a ROM file
for any supported console,
it will be imported into a game folder in
[the Game Library](../concepts/game-library.md),
and then loaded from there when higan starts.
# Examples
Play a previously-imported copy of Super Mario World
in full-screen (assuming Linux defaults):
```sh
higan --fullscreen ~/Emulation/"Super Famicom"/"Super Mario World.sfc"
```

View File

@@ -0,0 +1,298 @@
TODO: Rename this file to "higan-settings.md"
The Settings window
appears when you choose
one of the items at the bottom of
[the Settings menu](higan.md#the-settings-menu).
It contains less-frequently-modified configuration options.
Most of these can be safely ignored,
or set once and never changed again.
This window has a tab for each main category of options:
Video
=====
This tab contains options that affect
how higan displays
the emulated console's video output.
- **Saturation**: adjusts the vibrancy of colours displayed,
where 0% makes things pure grey,
100% is normal,
and 200% is garishly brightly coloured.
- **Gamma**: adjusts how bright mid-range colours are
compared to the brightest colours,
where 100% is normal,
and 200% makes mid-range colours much darker.
- **Luminance**: adjusts the overall brightness,
where 100% is normal,
and 0% is totally black.
- **Overscan Mask**: hides parts of
the video output that would have been hidden
by the bezel around the edge of
a standard-definition television screen.
Some games (particularly on the Famicom)
displayed random glitchy output in this area,
which can be distracting.
The units are "pixels in the emulated console's standard video-mode".
For example, setting "Horizontal" to 8
will clip 8/256ths from the left and right sides
of the Super Famicom's video output,
whether the Super Famicom is in
lo-res (256px) or hi-res (512px)
mode.
- **Aspect Correction**:
(in both Windowed Mode and Fullscreen Mode)
stretches the image to match the aspect ratio
produced by the original console hardware,
but can cause a "ripple" effect,
due to rounding errors.
- **Resize Window to Viewport**:
(under "Windowed mode")
causes higan to resize its window
to fit snugly around the emulated console's video
whenever it changes size:
because a game was loaded for a different console
with a different display size or aspect ratio,
because the "Overscan Mask" controls were adjusted,
because the game switched to a different video mode,
because the user pressed the "Rotate Display" hotkey,
etc.
When this option is disabled,
the higan window stays at a fixed size,
large enough to contain the video for any supported console,
padded with black borders for all smaller video modes.
- **Resize Viewport to Window**:
(under "Fullscreen mode")
causes higan to stretch the emulated console's video output
to touch the edges of the screen.
Since most screens are not an exact multiple
of the size of all emulated consoles,
this may cause a "ripple" effect,
due to rounding errors.
When this option is disabled,
higan stretches the emulated console's video output
to the largest exact multiple
of the emulated console's video output
that is smaller than or equal to the screen size.
- TODO: Update this to match 103r11, or whatever the latest version is.
Audio
=====
This tab contains options that affect
how higan reproduces
the emulated console's audio output.
- **Device**: allows you to choose
which audio device higan sends
the emulated game's audio to.
- **Frequency**: controls the sample-rate that higan will use
when generating audio.
If your PC's audio hardware has a "native" sample-rate
and you know what it is,
pick that.
Otherwise,
44.1kHz or 48kHz should be fine.
- **Latency**: controls how much audio output higan calculates in advance.
Higher values reduce the chance of
"popping" or "glitching" noises,
but increase the delay between an action occurring on-screen
and the corresponding sound-effect being played.
- **Exclusive Mode**: appears
if the current audio driver
allows higan to take exclusive control of your PC's audio output,
so no other applications can play sounds.
This can improve audio quality,
and lower the effective audio latency.
- **Volume**: controls the overall loudness of
the emulated console's audio,
where 100% is normal volume,
and 0% is complete silence.
- **Balance**: controls the relative loudness of
the left and right speakers,
where 0% means only the left speaker produces sound,
50% means both speakers produce sound equally,
and 100% means only the right speaker produces sound.
- **Reverb**: adds a slight reverberation effect
to the emulated console's audio output,
as though the console were in a tunnel or small room.
Input
=====
This tab controls which PC inputs
are used for which emulated controllers.
The exact PC inputs that can be mapped
depend on [the input driver](#drivers).
- **Pause Emulation**: automatically pauses emulation
when the main higan window
is not the current foreground window.
- **Allow Input**: can be ticked
when "Pause Emulation" is *not* ticked,
and allows configured inputs to keep affecting higan
even when higan is running in the background.
This is particularly relevant if
you configure your PC keyboard to control higan:
if you tick this box,
and switch to a different application
leaving higan running in the background,
typing in that other application may affect
the emulated game running in higan
even though you can't see it!
- The console selector chooses which console's inputs
to display in the mapping list below.
- The port selector chooses which port of the selected console
to display in the mapping list below.
- The controller selector chooses which controller
associated with the given console and port
to display in the mapping list below.
- The mapping list includes
every button and axis on the selected controller,
and the PC inputs that are mapped to it
when it is connected to the selected port of the selected console.
- **Erase**: removes the mapping
for the selected button or axis.
- **Reset**: removes all the mappings currently in the list.
- TODO: Mention that controllers must be connected
in the console menu
before they can be used.
To map
a keyboard or gamepad button on your PC to
a controller button,
double-click the controller button in the list,
or select it and press Enter.
The window will grey out,
and a message will appear in the bottom left:
"Press a key or button to map [the button]".
Press the key or button you want to map,
and it should appear in the list
next to the controller button it is mapped to.
To map
a mouse button on your PC to
a controller button,
select the controller button in the list,
then click one of the "Mouse Left",
"Mouse Middle",
or "Mouse Right" buttons in the bottom-left of the window.
To map
a joystick axis on your PC to
a controller axis,
double-click the axis in the list,
or select it and press Enter.
The window will grey out,
and a message will appear in the bottom left:
"Press a key or button to map [the axis]".
Press the joystick in the direction you want to map,
and it should appear in the list
next to the controller button it is mapped to.
To map
a mouse axis on your PC to
a controller axis,
select the axis in the list,
then click one of the
"Mouse X-axis",
or "Mouse Y-axis"
buttons in the bottom-left of the window.
If you start mapping a button or axis,
but decide you don't want to,
you can press Escape
to exit the "Press a key or button to map..." mode
without actually mapping anything.
The "Rumble" setting
for the Game Boy Advance is treated like a button,
and can be mapped to a PC gamepad.
When the emulated Game Boy Advance
tries to use the rumble feature
of the Game Boy Player,
higan will turn on the force-feedback
of whatever gamepad the mapped button is part of.
Hotkeys
=======
This tab is like "Inputs" above,
except it contains controls for higan itself,
instead of for the emulated console.
- **Toggle Fullscreen**: puts higan into fullscreen mode,
where the menu and status bar are hidden,
and the emulated console's video output
is enlarged to cover the entire screen.
Toggling fullscreen also automatically captures the mouse.
- **Toggle Mouse Capture**: hides the usual mouse-cursor,
and captures the mouse so it cannot leave the higan window.
This is useful when the mouse is being used to emulate
a light-gun controller like the Super Scope.
- **Save Quick State**: saves the current state of the emulated console
to the currently-selected Quick State slot.
- **Load Quick State**: restores the emulated console
to the state saved in the currently-selected Quick State slot.
- **Decrement Quick State**: selects the previous Quick State slot.
The status bar will briefly display the new current slot number.
- **Increment Quick State**: selects the next Quick State slot.
The status bar will briefly display the new current slot number.
- **Pause Emulation**: pauses the emulated console
until the Pause Emulation hotkey is pressed a second time.
- **Fast Forward**: disables audio and video synchronisation
for as long as it's held down,
so emulation proceeds as quickly as possible.
If your PC struggles to hit "real time"
(60fps for most emulated consoles),
this likely won't have any effect.
- **Power Cycle**: turns the emulated console off and back on,
(a "hard reset"),
just like the "Power Cycle" menu item
in [the console menu](#the-console-menu).
- **Rotate Display**: will toggle the display
of the Game Boy Advance
and WonderSwan (Color)
between the usual landscape orientation
and a portrait orientation (90° counter-clockwise).
These consoles have games
that expect the player to hold the console
in a different way.
Advanced
========
This tab contains all the settings
that didn't fit into one of the other categories.
- **Video**: controls how higan will draw
the emulated console's video output
to the PC screen.
"None" means no video will be drawn.
See [Drivers](#drivers) for details.
- **Audio**: controls how higan will present
the emulated console's audio output.
"None" means no audio will be played.
See [Drivers](#drivers) for details.
- **Input**: controls how higan checks for input
from the PC's input devices.
"None" means the emulated console cannot be controlled.
See [Drivers](#drivers) for details.
- **Location**: selects where the [Game Library](#the-game-library)
looks for games to load.
See [Moving the Game Library](#moving-the-game-library)
for more information.
- **Ignore Manifests**: makes higan ignore the manifest file
in the a loaded game's [game folder](#why-game-folders)
in favour of asking icarus
to guess a manifest on the fly.
This means that incompatible or incorrect manifests
generated by old versions of icarus
won't cause problems,
but means you can't fix incorrect manifests
generated by the current version of icarus.
See also the "Create Manifests" option in
[the icarus Settings dialog](#the-icarus-settings-dialog).

View File

@@ -0,0 +1,145 @@
The Tools window
appears when you choose
one of the items at the bottom of
[the Tools menu](higan.md#the-tools-menu).
The window has a tab for each tool:
The Cheat Editor
----------------
For some consoles,
higan supports applying temporary changes to the code of a running game.
For example,
you could disable the code that registers when the player takes damage,
resulting in an "invulnerability" mode.
Currently,
higan supports cheats for the following consoles:
- Famicom
- Super Famicom
- Game Boy
- Master System
- PC Engine
- Wonder Swan
A cheat code of the format `addr=data`
will cause the emulated console to obtain `data`
whenever it reads from memory address `addr`.
A cheat code of the format `addr=comp?data`
will cause reads from `addr` to obtain `data`,
but only if the true value at `addr` is `comp`.
In both formats,
`data` is a single byte expressed as two hexadecimal digits,
`comp` is also a single byte expressed as two hexadecimal digits,
and `addr` is a memory address in the emulated console,
expressed as however many hexadecimal digits are required
for the console in question
(typically 4 for 8-bit CPUs,
6 for 16-bit CPUs,
and 8 for 32-bit CPUs).
For compatibility with older versions of higan,
the older syntaxes of `addr/data` and `addr/comp/data`
are still supported.
For cheats that require more than a single-byte change,
higan allows multiple codes to be combined with `+`
so that all of them can have a single description
and be toggled with a single click.
For example,
in Super Mario World,
you can lock the time to 999 with these codes:
`7e0f31=09+7e0f32=09+7e0f33=09`.
Changes made in the Cheat Editor are saved to disk
when the game is unloaded,
or when higan exits.
higan stores the known cheats for a particular game
in `higan/cheats.bml`
inside the corresponding game folder
in [the Game Library](#the-game-library).
If your copy of higan includes a cheat database
(a file named `cheats.bml`
in the same directory as `Super Famicom.sys`
and the other `*.sys` directories),
you can click the "Find Codes ..." button in the bottom left
to load all known cheats for the currently-running game.
To add a new cheat,
select an unused row in the list,
then type the relevant codes in the "Code(s)" field at the bottom,
and a description in the "Description" field.
To enable or disable an existing cheat,
tick the checkbox in the first column of the list.
The code should take effect immediately.
To clear out an existing cheat,
select it from the list
and click the "Erase" button in the bottom right,
or just manually delete
the contents of the "Code(s)" and "Description" fields.
To clear out all existing cheats,
click the "Reset" button in the bottom right.
The State Manager
-----------------
The State Manager allows you to create,
load,
and remove Manager states.
For more information on Manager states,
quick states,
saved games
and how they compare,
see [Save States](#save-states).
To create a new manager state,
or to replace an existing one,
select the slot in the list
then click "Save" in the bottom-left corner.
You can then type a description in the "Description" field,
to help you find the state again later.
To rename a state,
select the slot in the list
and edit the "Description" field.
To load a state,
select the slot in the list
and click "Load" in the bottom-left corner,
or just double-click it.
To clear the state out of a slot,
select the slot in the list
and click "Erase" in the bottom-right corner.
To clear all the slots at once,
click "Reset" in the bottom-right corner.
The Manifest Viewer
-------------------
As mentioned in
[Why game folders?](#why-game-folders),
a game cartridge contains
more than just the raw data of the game.
higan uses a "manifest" to
describe how the various parts of a game cartridge
are wired up together,
and the Manifest Viewer lets you examine
the configuration higan is using for the currently-running game.
For some games,
an actual cartridge has been taken apart and carefully examined
and its configuration has been recorded in icarus' database,
so the manifest icarus produces
is guaranteed accurate.
For games that do not exist in icarus' database,
icarus will make a reasonable guess.
This is enough to get the game running,
but does not necessarily reflect the original cartridge.

290
docs/interface/higan.md Normal file
View File

@@ -0,0 +1,290 @@
When you launch higan,
the main window appears,
with a menu-bar across the top,
a status-bar across the bottom,
and a large area in the middle where the game's video output appears.
The Library menu
----------------
The Library menu allows you
to import games into higan's game library,
and to load games from the library.
higan organises the games in your library
according to which console they were intended to run on.
To play a game for a particular console from your library,
click on the Library menu,
click on the console manufacturer submenu
(Nintendo for the Super Famicom,
Bandai for the WonderSwan,
etc.)
then click on the console menu item.
A window will appear listing all the games in your library
for that particular console.
Select the game you want to play
and click the Open button,
or just double-click the game,
and it will begin playing as though you'd just turned on the console.
To add a new game to your library,
choose "Load ROM File ..." from the Library menu.
A [filesystem browser](#the-filesystem-browser) will appear,
allowing you to pick any ROM image for any supported system,
with any of the most common file extensions.
It also allows loading ROM images from `.zip` archives,
if the archive contains a single ROM image.
**Note:** Some games require extra steps to import correctly;
see [the Game Library](#the-game-library) for details.
TODO: Mention the region-picker.
To add many games at once,
run icarus,
or choose "Import ROM Files ..." from the Library menu
(which just runs icarus anyway).
See [the icarus interface](#the-icarus-interface)
for more information about bulk-importing.
For more information about the higan game library,
see [The Game Library](#the-game-library) below.
The console menu
---------------
**Note:**
The console menu does not appear
until a game is loaded.
Also,
it's not named "console",
it's named for the kind of console
the loaded game runs on.
For example,
when playing a Game Boy game,
you will have a "Game Boy" menu.
The console menu contains commands relevant
to the particular console being emulated.
All consoles will have some of the following items,
but few consoles have all of them.
- **Controller Port 1**
allows you
to connect different emulated controllers
to the first controller port,
if there is one.
- See [the Configuration dialog](#the-configuration-dialog)
for information about configuring
which host controller inputs are used
for the emulated controllers.
- This menu appears for the Famicom,
even though the Famicom did not support alternate controllers,
because the Famicom emulation core also emulates the NES,
which did.
- **Controller Port 2**
allows you
to connect different emulated controllers
to the second controller port,
if there is one.
- See [the Configuration dialog](#the-configuration-dialog)
for information about configuring
which host controller inputs are used
for the emulated controllers.
- This menu appears for the Famicom,
even though the Famicom did not support alternate controllers,
because the Famicom emulation core also emulates the NES,
which did.
- **Expansion Port**
allows you
to connect different emulated devices
to the console's expansion port,
if there is one.
- For the Super Famicom,
the [21fx][21fx] is a homebrew device
that allows a program running on a PC
to control a physical Super Famicom (or SNES).
This option allows the same program
to control the emulated SNES,
for development or testing.
- **Power Cycle**
restarts the loaded game
as though the emulated console were switched off and on again.
- **Unload**
stops the current game,
as though the emulated console were switched off.
You can load the same or a different game
from [the Library menu](#the-library-menu).
[21fx]: https://github.com/defparam/21FX
The Settings menu
-----------------
The Settings menu allows you to configure things
that aren't specific to any particular console.
- **Video Scale** determines the size and shape
of the emulated console's video output
in windowed mode
(as opposed to fullscreen).
- **Video Emulation** applies various effects
to the emulated console's video output
to reproduce some behaviours
that aren't technically part of the console itself.
- "Blurring" simulates the limited horizontal resolution
of standard-definition TVs
by blurring together horizontally-adjacent pixels.
Games like Jurassic Park for the Super Famicom
depend on this to emulate a transparency effect.
For hand-held consoles like the Game Boy Advance,
this simulates the slow response time
of the cheap LCD screens these consoles used
by blurring each output frame with the previous one.
- "Colors" simulates the way a console's display device
differs from modern computer monitor's colour reproduction.
In particular,
it simulates the slightly-different gamma correction
used by the Super Famicom,
the dim, washed out colours of the original Game Boy Advance,
and the pea-green display of the original Game Boy.
- **Video Shader** controls
how the low-resolution video output of the emulated console
is scaled up to suit modern high-resolution displays.
The availability of items in this submenu depends on
which video driver higan is using,
so see [Drivers](#drivers) for more information.
- "None" draws each output pixel according to
the colour of the single nearest input pixel,
sometimes called "nearest neighbour" scaling.
This produces unnaturally crisp and blocky images.
- "Blur" draws each output pixel by
averaging the colours of the four nearest input pixels,
sometimes called "bilinear" scaling.
This produces unnaturally blurry images.
- When using the OpenGL [driver](#drivers),
an additional item appears in this menu for
each installed Quark shader.
See [Installing custom shaders](#installing-custom-shaders)
for details.
- **Synchronize Audio**
causes higan to wait for audio playback to complete
before resuming emulation.
This should reduce popping and glitching noises,
and slows the emulation down to approximately the correct speed.
If your PC cannot emulate at full-speed,
(60fps for most consoles, 75fps for WonderSwan)
this has no noticable effect.
- **Mute Audio**
causes higan to not output sound from the emulated console.
The sound hardware is still emulated.
- **Show Status Bar**
causes higan to show or hide the status bar
at the bottom of the window.
This option has no effect in full-screen mode.
See [The status bar](#the-status-bar) for more information.
- **Video ...**
opens the Video tab of [the Configuration dialog][cfgdlg].
- **Audio ...**
opens the Audio tab of [the Configuration dialog][cfgdlg].
- **Input ...**
opens the Input tab of [the Configuration dialog][cfgdlg].
- **Hotkey ...**
opens the Hotkeys tab of [the Configuration dialog][cfgdlg].
- **Advanced ...**
opens the Advanced tab of [the Configuration dialog][cfgdlg].
[svsa]: #why-do-synchronize-video-and-synchronize-audio-conflict
[cfgdlg]: #the-configuration-dialog
The Tools menu
--------------
The Tools menu
contains features for manipulating the emulated console.
- **Save Quick State**
stores the current state of the emulated console
into one of the quick state slots.
See [Save States](#save-states) for more information.
- **Load Quick State**
restores the emulated console to
a state previously saved to one of the quick state slots.
See [Save States](#save-states) for more information.
- **Cheat Editor**
opens [the Cheat Editor window](#the-cheat-editor)
- **State Manager**
opens [the State Manager window](#the-state-manager)
- **Manifest Viewer**
opens [the Manifest Viewer window](#the-manifest-viewer)
The Help menu
-------------
The Help menu contains information about higan itself.
- **Documentation**
loads the official higan documentation
in your web-browser.
- **About**
opens the About dialog,
which displays basic information about higan,
including the version number.
The status bar
--------------
The status bar appears
at the bottom of the main higan window,
while "Show Status Bar" is ticked in [the Settings menu](#the-settings-menu).
Before any game is loaded,
the status bar displays "No cartridge loaded".
When a game is loaded and running,
the status bar displays the current emulation speeed
in frames-per-second.
For PAL-based consoles,
this should be around 50 FPS for "full speed" emulation,
for NTSC and most portable consoles the ideal speed is 60 FPS,
but the WonderSwan runs at 75 FPS.
If the number is too low,
you may need a faster computer,
or a faster [video driver](#drivers).
If the number is too high,
you may need to [Synchronize Audio](#the-settings-menu),
or you may have pressed the "turbo" [hotkey](#the-configuration-dialog).
The status bar displays "Paused"
if you have pressed the "pause" [hotkey](#the-configuration-dialog),
or if "When focus is lost: Pause Emulation" is ticked
in [the Input tab of the Configuration dialog](#the-configuration-dialog)
and the main higan window is not the foreground window.
To resume emulation,
make sure the main higan window is in the foreground,
and/or press the "pause" hotkey.
The status bar briefly displays "Selected quick state slot X"
(where X is one of the Quick State slot numbers)
when you press the "Increment Quick State"
or "Decrement Quick State"
hotkeys,
to show which Quick State slot will be used
the next time you press the "Save Quick State"
or "Load Quick State" hotkeys.
The status bar briefly displays "Slot X quick state does not exist"
(where X is one of the Quick State slot numbers)
when you choose a slot from the
[Tools](#the-tools-menu) → "Load Quick State"
sub-menu that has not had a save-state saved to it,
or when you press the "Load Quick State" hotkey
while the current Quick State slot has not had a save-state saved to it,
The status bar briefly displays "Power cycled"
when you choose "Power Cycle" from [the console menu](#the-console menu),
or press the "Power Cycle" hotkey.
The status bar briefly displays "Display rotation not supported"
when you press the "Rotate Display" hotkey
while the emulated console does not support display rotation.

60
docs/interface/icarus.md Normal file
View File

@@ -0,0 +1,60 @@
When launching icarus,
directly or by picking "Import ROM Files ..."
from higan's [Library menu](#the-library-menu),
the main icarus window appears.
This is [a filesystem browser](#the-filesystem-browser),
with customisations:
- The filesystem browser only lists
files with extensions typically used for ROM dumps from
consoles higan emulates,
plus `.zip` files since ROM dumps are often compressed.
- Each matching file has a check-box next to it.
- You can tick the check-box next to every file at once
by pressing "Select All" in the bottom-left.
- You can un-tick all the check-boxes
by pressing "Unselect All" in the bottom-left.
Pressing "Settings ..." in the bottom-right
opens [the icarus Settings dialog](#the-icarus-settings-dialog).
Pressing "Import ..." in the bottom-right
will close the filesystem browser
then try to import all the files
whose check-boxes are ticked
into [the Game Library](#the-game-library).
icarus displays a progress dialog during the import process.
**Note:** Some games require extra steps to import correctly;
see [the Game Library](#the-game-library) for details.
The icarus Settings dialog
--------------------------
The icarus Settings dialog contains the following settings:
- **Library Location** determines
where icarus puts the games it imports.
See [Moving the Game Library](#moving-the-game-library)
for details.
- **Create Manifests** causes icarus
to write out a manifest file describing
each imported game
to that game's [game folder](#whats-in-a-game-folder).
This means that higan doesn't have to regenerate
the manifest each time an imported game is loaded,
but it means that a future version of higan
with an incompatible manifest format
may be unable to play these games.
Note that higan also has an "Ignore Manifests" option
in the Advanced tab of
[its Configuration dialog](#the-configuration-dialog).
- **Use Database** causes icarus to use manifest information
from its database of known-good manifests,
if it's importing a game it recognises.
For unrecognised games,
and for all games if this box is unticked,
icarus gueses the manifest data.
This option is still relevant when "Create Manifests" is unticked:
higan uses icarus to generate a manifest when a game is loaded,
not just at import-time.

311
docs/notes.md Normal file
View File

@@ -0,0 +1,311 @@
The consoles that higan emulates
are similar in many ways,
but some of them do have particular quirks
that you should be aware of.
Video Shaders and TV-based consoles
-----------------------------------
[Video Shaders](guides/shaders.md)
customize how higan scales
the low-resolution video of the emulated console
up to the high-resolution of the computer display.
Simple shaders
(like "None"
and the third-party "AANN" shader)
just blindly scale up the images they're given,
but sophisticated shaders
(such as the third-party "xBR" shader)
try to produce higher-quality output
by recognising particular patterns of pixel,
like taking three diagonal pixels
and turning that into a smooth diagonal line.
These shaders assume that
each pixel drawn by the game's artists
becomes a single pixel in the video output they analyze.
Many of the consoles higan emulates
can only output video at one specific resolution,
so this "one pixel equals one pixel" rule holds true,
and pattern-based shaders like "xBR" work just fine.
Unfortunately,
this is not the case for the Super Famicom.
The "normal" video mode
draws 256 pixels across the width of the screen,
but the "high resolution" mode draws 512.
Since Super Famicom games can enable hi-res mode at any time
(even halfway through a frame),
higan always renders Super Famicom video output 512 pixels wide,
just in case.
This means that in "normal" mode,
each pixel drawn by the game's artists
becomes two pixels in the video output,
breaking the assumption
that pattern-based shaders are based on.
The Super Famicom has a similar issue in the vertical direction:
normally,
an NTSC-based Super Famicom draws about 240 rows of output every frame,
sometimes referred to as "240p" video.
When a game turns on "interlaced" mode,
it draws the 240 odd-numbered lines of one frame,
then the 240 even-numbered lines of the next,
and so forth.
This is sometimes referred to as "480i" video.
Although interlaced mode cannot be enabled mid-frame
like high-resolution mode,
resolution switching is still complex,
so higan always draws all 480 lines of video output.
This means for a normal, non-interlaced game,
each pixel drawn by the game's artists
becomes four pixels in the video output
(two horizontally and two vertically)
making pattern-based shaders even less useful.
It also breaks most scanline-emulation shaders,
since they typically draw a scanline
for each row of pixels in the video output.
The Mega Drive has similar problems
to the Super Famicom.
It has the same behaviour with interlacing,
but its high-resolution mode switches
from 256 pixels across to 320 pixels across.
Therefore in normal mode,
each pixel drawn by the game's artists
becomes five pixels in the video output,
while in high-resolution mode,
each pixel drawn by the game's artists
becomes four pixels in the video output
(or 10 and 8 pixels in non-interlaced mode).
The PC Engine does not support an interlaced mode,
but its horizontal resolution is much more flexible
than the Super Famicom or Mega Drive,
and so it has the same problems with shaders.
Music and Sound Effect Volume on the Mega Drive
-----------------------------------------------
The Mega Drive has two different audio-generating chips:
- the SN76489 or "PSG" chip,
inherited from the Master System,
mostly used for sound-effects
like Sonic picking up rings
- the YM2612 or "FM" chip,
mostly used for music
With two different sound sources,
it's important that they have similar volumes,
or the sound-effects will drown out the music,
or vice-versa.
Sega did *not* do this,
and different hardware revisions
used different relative volumes.
higan currently
sets the PSG volume to [125% of the FM volume][vol],
based on [a Sega Genesis model 1 VA6][va6] that byuu owns.
If you feel sound-effects in higan's Mega Drive core
are too loud or too quiet,
you may be comparing it
to a Mega Drive calibrated to a different scale
(or to an emulator tweaked to match such a Mega Drive).
[vol]: https://board.byuu.org/viewtopic.php?p=42482#p42482
[va6]: https://board.byuu.org/viewtopic.php?p=42195#p42195
Playing Game Boy Colour games in Game Boy mode
----------------------------------------------
Games for the original Game Boy
came in solid grey cartridges,
and only supported four-shade greyscale graphics.
ROM files for these games
typically have filenames ending in `.gb`.
The Game Boy Color played all the original Game Boy games,
but extended the hardware to support colour graphics.
Games that required
the extra hardware in the Game Boy Color
came in transparent cartridges,
and had a slightly different shape
to prevent them from being used in original Game Boys..
ROM files for these games
typically have filenames ending in `.gbc`.
However,
there were also some games
that could use colour if it was available,
but would stick to greyscale if it wasn't.
These games came in black cartridges.
ROM files for these games
typically have filenames ending in `.gbc`
(since they are genuinely designed for the Game Boy Color)
or `.gbb`.
Sometimes people ask
for higan to include these backwards-compatible Game Boy Color games
when asking for a Game Boy game to load.
However,
this would make higan much more complex
for not much benefit:
it's just the same game as in Color mode,
but with bits chopped off.
You might as well play backward-compatible games
in Game Boy Color mode
and get the full experience the developers intended.
If you really, really want to see
what a particular game's backward-compatible mode looked like,
change the filename to end with `.gb`
(instead of `.gbc` or `.gbb`)
before [importing it](guides/import.md).
If you want to experiment
with loading in-game saves from colour-mode in monochrome mode
or vice-versa,
you can import the game once with `.gb`
and once with `.gbc`,
then manually copy files between the
[game folders](concepts/game-folders.md)
in the "Game Boy" and "Game Boy Color" sub-folders
of the [Game Library](concepts/game-library.md) folder.
Do not expect save-states to be compatible between
Game Boy and Game Boy Color.
In-Game Saves and the Game Boy Advance
--------------------------------------
For most of the consoles that higan emulates,
in-game saves are simple:
the cartridge contains some battery-backed RAM
that the game accesses like any other memory,
and the game's internal header usually contains some hint
about where in memory the save data appears
and how large it is.
The Game Boy Advance is different.
By the time of the GBA,
many save-storage technologies were available,
most with a more complex interface than plain memory.
Frustratingly, the GBA's internal header
does *not* describe which storage variant the game expects.
Therefore,
when importing a GBA game,
higan must guess which storage type to use
and sometimes it guesses incorrectly.
If higan guesses incorrectly for a game you want to play,
you will need to turn on
"Create manifests" in
[the Icarus settings dialog](interface/icarus.md#the-icarus-settings-dialog),
turn off
"Ignore manifests" in
[higan's Advanced settings](interface/higan-config.md#advanced),
re-import the game,
and edit `manifest.bml` in
[the game folder](concepts/game-folders.md)
to describe the correct storage type.
Try importing other GBA games to see what save types they use.
For more discussion of the GBA save type mess,
see [What's the deal with... GBA save files?][gbasaves]
[gbasaves]: http://zork.net/~st/jottings/GBA_saves.html
Rumble compatibility for Game Boy (Color)
-----------------------------------------
The Game Boy and Game Boy Color did not natively support
any kind of rumble or force-feedback system,
but some game cartridges (such as Pokémon Pinball)
included a rumble motor within the cartridge itself.
Because higan does not currently support
game-specific controller features,
to experience the rumble effect in higan
you'll need to configure the console itself:
- Open
[higan's Input settings](interface/higan-config.md#input)
- In the list of consoles,
select Game Boy, or Game Boy Color
depending on which console you want to use to play the game
- In the list of inputs,
double-click "Rumble"
or select it and press Enter
- Press any button on the gamepad that should shake
when the game turns on the rumble effect.
Rumble compatibility for Game Boy Advance
-----------------------------------------
The original Game Boy Advance
and the Game Boy Advance SP
did not support any kind of rumble or force-feedback system,
but the Game Boy Player addon for the Gamecube
allowed Game Boy Advance games
to use the rumble feature in Gamecube controllers.
Because rumble is a feature of the Game Boy Player,
to experience the rumble effect in higan
you'll need to configure the console itself:
- Open
[higan's Input settings](interface/higan-config.md#input)
- In the list of consoles,
select Game Boy Advance
- In the list of inputs,
double-click "Rumble"
or select it and press Enter
- Press any button on the gamepad that should shake
when the game turns on the rumble effect.
As well as the Game Boy Player rumble feature,
some Game Boy Advance cartridges
included a rumble motor within the cartridge itself.
higan does not support this rumble technology,
but that's not a big deal:
the only two such games are *Drill Dozer*,
which can use Game Boy Player rumble,
and *WarioWare: Twisted*,
which doesn't work anyway
because it requires gyroscope hardware
that higan does not yet emulate.
Game Boy Advance rotation
-------------------------
Some Game Boy Advance homebrew games,
as well as a bonus mode in *Dr Mario + Puzzle League*,
expect the player to physically rotate the device
so the screen is tall rather than wide.
higan supports this feature with
a Rotate [hotkey](interface/higan-config.md#hotkeys).
When the user presses the Rotate hotkey,
the console's video output is rotated 90° anti-clockwise,
and the directional-pad controls are also rotated
so that (for example) pushing the button for "up"
sends the signal "right" to the emulated console,
so that the player character moves "up" on the rotated screen.
WonderSwan rotation
-------------------
The WonderSwan hardware
included multiple sets of buttons
so the player could hold the device
vertically or horizontally.
*Makaimura for WonderSwan* includes a level
that requires the player to repeatedly rotate
the device as they play.
higan supports this feature with
a Rotate [hotkey](interface/higan-config.md#hotkeys).
When the user presses the Rotate hotkey,
the console's video output is rotated 90° anti-clockwise,
and the X and Y button cluster mappings
are adjusted to match.

7
docs/qs.md Normal file
View File

@@ -0,0 +1,7 @@
TODO
- install
- configure inputs
- load a game
- connect a controller

View File

@@ -31,6 +31,13 @@ auto Audio::setInterface(Interface* interface) -> void {
this->interface = interface;
}
auto Audio::setFrequency(double frequency) -> void {
this->frequency = frequency;
for(auto& stream : streams) {
stream->setFrequency(stream->inputFrequency, frequency);
}
}
auto Audio::setVolume(double volume) -> void {
this->volume = volume;
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include <nall/dsp/iir/one-pole.hpp>
#include <nall/dsp/iir/biquad.hpp>
#include <nall/dsp/resampler/cubic.hpp>
@@ -7,12 +8,14 @@ namespace Emulator {
struct Interface;
struct Audio;
struct Filter;
struct Stream;
struct Audio {
auto reset(maybe<uint> channels = nothing, maybe<double> frequency = nothing) -> void;
auto setInterface(Interface* interface) -> void;
auto setFrequency(double frequency) -> void;
auto setVolume(double volume) -> void;
auto setBalance(double balance) -> void;
auto setReverb(bool enabled) -> void;
@@ -37,15 +40,25 @@ private:
friend class Stream;
};
struct Filter {
enum class Order : uint { First, Second };
enum class Type : uint { LowPass, HighPass };
Order order;
DSP::IIR::OnePole onePole; //first-order
DSP::IIR::Biquad biquad; //second-order
};
struct Stream {
auto reset(uint channels, double inputFrequency, double outputFrequency) -> void;
auto addLowPassFilter(double cutoffFrequency, uint passes = 1) -> void;
auto addHighPassFilter(double cutoffFrequency, uint passes = 1) -> void;
auto setFrequency(double inputFrequency, maybe<double> outputFrequency = nothing) -> void;
auto addFilter(Filter::Order order, Filter::Type type, double cutoffFrequency, uint passes = 1) -> void;
auto pending() const -> bool;
auto read(double* samples) -> uint;
auto write(const double* samples) -> void;
auto read(double samples[]) -> uint;
auto write(const double samples[]) -> void;
template<typename... P> auto sample(P&&... p) -> void {
double samples[sizeof...(P)] = {forward<P>(p)...};
@@ -54,7 +67,7 @@ struct Stream {
private:
struct Channel {
vector<DSP::IIR::Biquad> filters;
vector<Filter> filters;
DSP::Resampler::Cubic resampler;
};
vector<Channel> channels;

View File

@@ -11,22 +11,36 @@ auto Stream::reset(uint channels_, double inputFrequency, double outputFrequency
}
}
auto Stream::addLowPassFilter(double cutoffFrequency, uint passes) -> void {
auto Stream::setFrequency(double inputFrequency, maybe<double> outputFrequency) -> void {
this->inputFrequency = inputFrequency;
if(outputFrequency) this->outputFrequency = outputFrequency();
for(auto& channel : channels) {
for(auto pass : range(passes)) {
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
channel.filters.append(DSP::IIR::Biquad{});
channel.filters.right().reset(DSP::IIR::Biquad::Type::LowPass, cutoffFrequency, inputFrequency, q);
}
channel.resampler.reset(this->inputFrequency, this->outputFrequency);
}
}
auto Stream::addHighPassFilter(double cutoffFrequency, uint passes) -> void {
auto Stream::addFilter(Filter::Order order, Filter::Type type, double cutoffFrequency, uint passes) -> void {
for(auto& channel : channels) {
for(auto pass : range(passes)) {
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
channel.filters.append(DSP::IIR::Biquad{});
channel.filters.right().reset(DSP::IIR::Biquad::Type::HighPass, cutoffFrequency, inputFrequency, q);
Filter filter{order};
if(order == Filter::Order::First) {
DSP::IIR::OnePole::Type _type;
if(type == Filter::Type::LowPass) _type = DSP::IIR::OnePole::Type::LowPass;
if(type == Filter::Type::HighPass) _type = DSP::IIR::OnePole::Type::HighPass;
filter.onePole.reset(_type, cutoffFrequency, inputFrequency);
}
if(order == Filter::Order::Second) {
DSP::IIR::Biquad::Type _type;
if(type == Filter::Type::LowPass) _type = DSP::IIR::Biquad::Type::LowPass;
if(type == Filter::Type::HighPass) _type = DSP::IIR::Biquad::Type::HighPass;
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
filter.biquad.reset(_type, cutoffFrequency, inputFrequency, q);
}
channel.filters.append(filter);
}
}
}
@@ -35,15 +49,20 @@ auto Stream::pending() const -> bool {
return channels && channels[0].resampler.pending();
}
auto Stream::read(double* samples) -> uint {
auto Stream::read(double samples[]) -> uint {
for(auto c : range(channels)) samples[c] = channels[c].resampler.read();
return channels.size();
}
auto Stream::write(const double* samples) -> void {
auto Stream::write(const double samples[]) -> void {
for(auto c : range(channels)) {
double sample = samples[c] + 1e-25; //constant offset used to suppress denormals
for(auto& filter : channels[c].filters) sample = filter.process(sample);
for(auto& filter : channels[c].filters) {
switch(filter.order) {
case Filter::Order::First: sample = filter.onePole.process(sample); break;
case Filter::Order::Second: sample = filter.biquad.process(sample); break;
}
}
channels[c].resampler.write(sample);
}

View File

@@ -6,4 +6,9 @@
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>false</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>

View File

@@ -11,7 +11,7 @@
<key>CFBundleIconFile</key>
<string>higan.icns</string>
<key>NSHighResolutionCapable</key>
<false/>
<true/>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
</dict>

View File

@@ -12,13 +12,13 @@ using namespace nall;
namespace Emulator {
static const string Name = "higan";
static const string Version = "103";
static const string Version = "104";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "http://byuu.org/";
//incremented only when serialization format changes
static const string SerializerVersion = "103";
static const string SerializerVersion = "104";
namespace Constants {
namespace Colorburst {

View File

@@ -9,11 +9,6 @@ struct Interface {
bool overscan;
} information;
struct Region {
string name;
};
vector<Region> regions;
struct Medium {
uint id;
string name;
@@ -43,9 +38,14 @@ struct Interface {
virtual auto title() -> string = 0;
//video information
struct VideoSize { uint width, height; };
virtual auto videoResolution() -> VideoSize = 0;
virtual auto videoSize(uint width, uint height, bool arc) -> VideoSize = 0;
struct VideoResolution {
uint width;
uint height;
uint internalWidth;
uint internalHeight;
double aspectCorrection;
};
virtual auto videoResolution() -> VideoResolution = 0;
virtual auto videoColors() -> uint32 = 0;
virtual auto videoColor(uint32 color) -> uint64 = 0;

View File

@@ -74,8 +74,10 @@ auto APU::setSample(int16 sample) -> void {
auto APU::power() -> void {
create(APU::Enter, system.frequency());
stream = Emulator::audio.createStream(1, frequency() / rate());
stream->addLowPassFilter(20000.0, 3);
stream->addHighPassFilter(20.0, 3);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 90.0);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 440.0);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::LowPass, 14000.0);
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
pulse[0].power();
pulse[1].power();
@@ -151,7 +153,7 @@ auto APU::writeIO(uint16 addr, uint8 data) -> void {
pulse[n].period = (pulse[n].period & 0x00ff) | (data << 8);
pulse[n].sweep.pulsePeriod = (pulse[n].sweep.pulsePeriod & 0x00ff) | (data << 8);
pulse[n].dutyCounter = 7;
pulse[n].dutyCounter = 0;
pulse[n].envelope.reloadDecay = true;
if(enabledChannels & (1 << n)) {

View File

@@ -1,7 +1,7 @@
struct APU : Thread {
shared_pointer<Emulator::Stream> stream;
inline auto rate() const -> uint { return Region::NTSC() ? 12 : 16; }
inline auto rate() const -> uint { return Region::PAL() ? 16 : 12; }
//apu.cpp
APU();

View File

@@ -57,7 +57,7 @@ auto APU::DMC::clock() -> uint8 {
}
}
periodCounter = Region::NTSC() ? dmcPeriodTableNTSC[period] : dmcPeriodTablePAL[period];
periodCounter = Region::PAL() ? dmcPeriodTablePAL[period] : dmcPeriodTableNTSC[period];
}
if(lengthCounter > 0 && !dmaBufferValid && dmaDelayCounter == 0) {
@@ -73,7 +73,7 @@ auto APU::DMC::power() -> void {
irqPending = 0;
period = 0;
periodCounter = Region::NTSC() ? dmcPeriodTableNTSC[0] : dmcPeriodTablePAL[0];
periodCounter = Region::PAL() ? dmcPeriodTablePAL[0] : dmcPeriodTableNTSC[0];
irqEnable = 0;
loopMode = 0;
dacLatch = 0;

View File

@@ -19,7 +19,7 @@ auto APU::Noise::clock() -> uint8 {
}
lfsr = (lfsr >> 1) | (feedback << 14);
periodCounter = Region::NTSC() ? apu.noisePeriodTableNTSC[period] : apu.noisePeriodTablePAL[period];
periodCounter = Region::PAL() ? apu.noisePeriodTablePAL[period] : apu.noisePeriodTableNTSC[period];
}
return result;

View File

@@ -8,13 +8,18 @@ auto APU::Pulse::clock() -> uint8 {
if(!sweep.checkPeriod()) return 0;
if(lengthCounter == 0) return 0;
static const uint dutyTable[] = {1, 2, 4, 6};
uint8 result = (dutyCounter < dutyTable[duty]) ? envelope.volume() : 0;
static const uint dutyTable[4][8] = {
{0, 0, 0, 0, 0, 0, 0, 1}, //12.5%
{0, 0, 0, 0, 0, 0, 1, 1}, //25.0%
{0, 0, 0, 0, 1, 1, 1, 1}, //50.0%
{1, 1, 1, 1, 1, 1, 0, 0}, //25.0% (negated)
};
uint8 result = dutyTable[duty][dutyCounter] ? envelope.volume() : 0;
if(sweep.pulsePeriod < 0x008) result = 0;
if(--periodCounter == 0) {
periodCounter = (sweep.pulsePeriod + 1) * 2;
dutyCounter++;
dutyCounter--;
}
return result;

View File

@@ -16,7 +16,7 @@ auto Cartridge::main() -> void {
}
auto Cartridge::load() -> bool {
if(auto loaded = platform->load(ID::Famicom, "Famicom", "fc", {"NTSC", "PAL"})) {
if(auto loaded = platform->load(ID::Famicom, "Famicom", "fc", {"NTSC-J", "NTSC-U", "PAL"})) {
information.pathID = loaded.pathID();
information.region = loaded.option();
} else return false;

View File

@@ -2,7 +2,7 @@
#include "board/board.hpp"
struct Cartridge : Thread {
inline auto rate() const -> uint { return Region::NTSC() ? 12 : 16; }
inline auto rate() const -> uint { return Region::PAL() ? 16 : 12; }
//cartridge.cpp
static auto Enter() -> void;

View File

@@ -2,9 +2,11 @@
namespace Famicom {
ControllerPort controllerPort1;
ControllerPort controllerPort2;
#include "gamepad/gamepad.cpp"
Controller::Controller(bool port) : port(port) {
Controller::Controller(uint port) : port(port) {
if(!handle()) create(Controller::Enter, 1);
}
@@ -15,8 +17,8 @@ Controller::~Controller() {
auto Controller::Enter() -> void {
while(true) {
scheduler.synchronize();
if(peripherals.controllerPort1->active()) peripherals.controllerPort1->main();
if(peripherals.controllerPort2->active()) peripherals.controllerPort2->main();
if(controllerPort1.device->active()) controllerPort1.device->main();
if(controllerPort2.device->active()) controllerPort2.device->main();
}
}
@@ -25,4 +27,32 @@ auto Controller::main() -> void {
synchronize(cpu);
}
//
auto ControllerPort::connect(uint deviceID) -> void {
if(!system.loaded()) return;
delete device;
switch(deviceID) { default:
case ID::Device::None: device = new Controller(port); break;
case ID::Device::Gamepad: device = new Gamepad(port); break;
}
cpu.peripherals.reset();
if(auto device = controllerPort1.device) cpu.peripherals.append(device);
if(auto device = controllerPort2.device) cpu.peripherals.append(device);
}
auto ControllerPort::power(uint port) -> void {
this->port = port;
}
auto ControllerPort::unload() -> void {
delete device;
device = nullptr;
}
auto ControllerPort::serialize(serializer& s) -> void {
}
}

View File

@@ -17,9 +17,7 @@
// 7: gnd
struct Controller : Thread {
enum : bool { Port1 = 0, Port2 = 1 };
Controller(bool port);
Controller(uint port);
virtual ~Controller();
static auto Enter() -> void;
@@ -27,7 +25,21 @@ struct Controller : Thread {
virtual auto data() -> uint3 { return 0; }
virtual auto latch(bool data) -> void {}
const bool port;
const uint port;
};
struct ControllerPort {
auto connect(uint deviceID) -> void;
auto power(uint port) -> void;
auto unload() -> void;
auto serialize(serializer&) -> void;
uint port;
Controller* device = nullptr;
};
extern ControllerPort controllerPort1;
extern ControllerPort controllerPort2;
#include "gamepad/gamepad.hpp"

View File

@@ -1,4 +1,4 @@
Gamepad::Gamepad(bool port) : Controller(port) {
Gamepad::Gamepad(uint port) : Controller(port) {
}
auto Gamepad::data() -> uint3 {

View File

@@ -3,7 +3,7 @@ struct Gamepad : Controller {
Up, Down, Left, Right, B, A, Select, Start,
};
Gamepad(bool port);
Gamepad(uint port);
auto data() -> uint3;
auto latch(bool data) -> void;

View File

@@ -1,5 +1,5 @@
struct CPU : Processor::MOS6502, Thread {
inline auto rate() const -> uint { return Region::NTSC() ? 12 : 16; }
inline auto rate() const -> uint { return Region::PAL() ? 16 : 12; }
//cpu.cpp
static auto Enter() -> void;

View File

@@ -10,12 +10,12 @@ auto CPU::readIO(uint16 addr) -> uint8 {
switch(addr) {
case 0x4016: {
auto data = Famicom::peripherals.controllerPort1->data();
auto data = controllerPort1.device->data();
return (mdr() & 0xc0) | data.bit(2) << 4 | data.bit(1) << 3 | data.bit(0) << 0;
}
case 0x4017: {
auto data = Famicom::peripherals.controllerPort2->data();
auto data = controllerPort2.device->data();
return (mdr() & 0xc0) | data.bit(2) << 4 | data.bit(1) << 3 | data.bit(0) << 0;
}
@@ -34,8 +34,8 @@ auto CPU::writeIO(uint16 addr, uint8 data) -> void {
}
case 0x4016: {
Famicom::peripherals.controllerPort1->latch(data.bit(0));
Famicom::peripherals.controllerPort2->latch(data.bit(0));
controllerPort1.device->latch(data.bit(0));
controllerPort2.device->latch(data.bit(0));
return;
}

View File

@@ -30,7 +30,8 @@ namespace Famicom {
};
struct Region {
static inline auto NTSC() -> bool;
static inline auto NTSCJ() -> bool;
static inline auto NTSCU() -> bool;
static inline auto PAL() -> bool;
};

View File

@@ -44,15 +44,8 @@ auto Interface::title() -> string {
return cartridge.title();
}
auto Interface::videoResolution() -> VideoSize {
return {256, 240};
}
auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize {
uint w = 256 * (arc ? 8.0 / 7.0 : 1.0);
uint h = 240;
uint m = min(width / w, height / h);
return {w * m, h * m};
auto Interface::videoResolution() -> VideoResolution {
return {256, 240, 256, 240, 8.0 / 7.0};
}
auto Interface::videoColors() -> uint32 {
@@ -132,7 +125,8 @@ auto Interface::unload() -> void {
}
auto Interface::connect(uint port, uint device) -> void {
peripherals.connect(port, device);
if(port == ID::Port::Controller1) controllerPort1.connect(settings.controllerPort1 = device);
if(port == ID::Port::Controller2) controllerPort2.connect(settings.controllerPort2 = device);
}
auto Interface::power() -> void {

View File

@@ -26,8 +26,7 @@ struct Interface : Emulator::Interface {
auto manifest() -> string override;
auto title() -> string override;
auto videoResolution() -> VideoSize override;
auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
auto videoResolution() -> VideoResolution override;
auto videoColors() -> uint32 override;
auto videoColor(uint32 color) -> uint64 override;

View File

@@ -1,6 +1,6 @@
struct PPU : Thread {
inline auto rate() const -> uint { return Region::NTSC() ? 4 : 5; }
inline auto vlines() const -> uint { return Region::NTSC() ? 262 : 312; }
inline auto rate() const -> uint { return Region::PAL() ? 5 : 4; }
inline auto vlines() const -> uint { return Region::PAL() ? 312 : 262; }
//ppu.cpp
static auto Enter() -> void;

View File

@@ -1,46 +0,0 @@
Peripherals peripherals;
auto Peripherals::unload() -> void {
delete controllerPort1;
delete controllerPort2;
controllerPort1 = nullptr;
controllerPort2 = nullptr;
}
auto Peripherals::reset() -> void {
connect(ID::Port::Controller1, settings.controllerPort1);
connect(ID::Port::Controller2, settings.controllerPort2);
}
auto Peripherals::connect(uint port, uint device) -> void {
if(port == ID::Port::Controller1) {
settings.controllerPort1 = device;
if(!system.loaded()) return;
delete controllerPort1;
switch(device) { default:
case ID::Device::None: controllerPort1 = new Controller(0); break;
case ID::Device::Gamepad: controllerPort1 = new Gamepad(0); break;
}
}
if(port == ID::Port::Controller2) {
settings.controllerPort2 = device;
if(!system.loaded()) return;
delete controllerPort2;
switch(device) { default:
case ID::Device::None: controllerPort2 = new Controller(1); break;
case ID::Device::Gamepad: controllerPort2 = new Gamepad(1); break;
}
}
if(port == ID::Port::Expansion) {
settings.expansionPort = device;
if(!system.loaded()) return;
}
cpu.peripherals.reset();
cpu.peripherals.append(controllerPort1);
cpu.peripherals.append(controllerPort2);
}

View File

@@ -45,6 +45,8 @@ auto System::serializeAll(serializer& s) -> void {
cpu.serialize(s);
apu.serialize(s);
ppu.serialize(s);
controllerPort1.serialize(s);
controllerPort2.serialize(s);
}
auto System::serializeInit() -> void {

View File

@@ -2,7 +2,6 @@
namespace Famicom {
#include "peripherals.cpp"
#include "video.cpp"
#include "serialization.cpp"
System system;
@@ -32,8 +31,12 @@ auto System::load(Emulator::Interface* interface) -> bool {
auto document = BML::unserialize(information.manifest);
if(!cartridge.load()) return false;
if(cartridge.region() == "NTSC") {
information.region = Region::NTSC;
if(cartridge.region() == "NTSC-J") {
information.region = Region::NTSCJ;
information.frequency = Emulator::Constants::Colorburst::NTSC * 6.0;
}
if(cartridge.region() == "NTSC-U") {
information.region = Region::NTSCU;
information.frequency = Emulator::Constants::Colorburst::NTSC * 6.0;
}
if(cartridge.region() == "PAL") {
@@ -52,7 +55,9 @@ auto System::save() -> void {
auto System::unload() -> void {
if(!loaded()) return;
peripherals.unload();
cpu.peripherals.reset();
controllerPort1.unload();
controllerPort2.unload();
cartridge.unload();
information.loaded = false;
}
@@ -72,7 +77,12 @@ auto System::power() -> void {
apu.power();
ppu.power();
scheduler.primary(cpu);
peripherals.reset();
controllerPort1.power(ID::Port::Controller1);
controllerPort2.power(ID::Port::Controller2);
controllerPort1.connect(settings.controllerPort1);
controllerPort2.connect(settings.controllerPort2);
}
auto System::init() -> void {

View File

@@ -1,5 +1,5 @@
struct System {
enum class Region : uint { NTSC, PAL };
enum class Region : uint { NTSCJ, NTSCU, PAL };
auto loaded() const -> bool { return information.loaded; }
auto region() const -> Region { return information.region; }
@@ -33,7 +33,7 @@ private:
struct Information {
bool loaded = false;
Region region = Region::NTSC;
Region region = Region::NTSCJ;
double frequency = Emulator::Constants::Colorburst::NTSC * 6.0;
string manifest;
} information;
@@ -41,17 +41,8 @@ private:
uint _serializeSize = 0;
};
struct Peripherals {
auto unload() -> void;
auto reset() -> void;
auto connect(uint port, uint device) -> void;
Controller* controllerPort1 = nullptr;
Controller* controllerPort2 = nullptr;
};
extern System system;
extern Peripherals peripherals;
auto Region::NTSC() -> bool { return system.region() == System::Region::NTSC; }
auto Region::NTSCJ() -> bool { return system.region() == System::Region::NTSCJ; }
auto Region::NTSCU() -> bool { return system.region() == System::Region::NTSCU; }
auto Region::PAL() -> bool { return system.region() == System::Region::PAL; }

View File

@@ -55,8 +55,8 @@ auto APU::power() -> void {
create(Enter, 2 * 1024 * 1024);
if(!Model::SuperGameBoy()) {
stream = Emulator::audio.createStream(2, frequency());
stream->addLowPassFilter(20000.0, 3);
stream->addHighPassFilter(20.0, 3);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
}
for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;

View File

@@ -2,17 +2,20 @@
namespace GameBoy {
Cartridge cartridge;
#include "mbc0/mbc0.cpp"
#include "mbc1/mbc1.cpp"
#include "mbc1m/mbc1m.cpp"
#include "mbc2/mbc2.cpp"
#include "mbc3/mbc3.cpp"
#include "mbc5/mbc5.cpp"
#include "mbc6/mbc6.cpp"
#include "mbc7/mbc7.cpp"
#include "mmm01/mmm01.cpp"
#include "huc1/huc1.cpp"
#include "huc3/huc3.cpp"
#include "tama/tama.cpp"
#include "serialization.cpp"
Cartridge cartridge;
auto Cartridge::load() -> bool {
information = {};
@@ -43,49 +46,46 @@ auto Cartridge::load() -> bool {
auto board = document["board"];
information.title = document["information/title"].text();
auto mapperid = document["board/mapper"].text();
if(mapperid == "none" ) information.mapper = Mapper::MBC0;
if(mapperid == "MBC1" ) information.mapper = Mapper::MBC1;
if(mapperid == "MBC1M") information.mapper = Mapper::MBC1M;
if(mapperid == "MBC2" ) information.mapper = Mapper::MBC2;
if(mapperid == "MBC3" ) information.mapper = Mapper::MBC3;
if(mapperid == "MBC5" ) information.mapper = Mapper::MBC5;
if(mapperid == "MMM01") information.mapper = Mapper::MMM01;
if(mapperid == "HuC1" ) information.mapper = Mapper::HuC1;
if(mapperid == "HuC3" ) information.mapper = Mapper::HuC3;
auto mapperID = document["board/mapper"].text();
if(mapperID == "MBC0" ) mapper = &mbc0;
if(mapperID == "MBC1" ) mapper = &mbc1;
if(mapperID == "MBC1M") mapper = &mbc1m;
if(mapperID == "MBC2" ) mapper = &mbc2;
if(mapperID == "MBC3" ) mapper = &mbc3;
if(mapperID == "MBC5" ) mapper = &mbc5;
if(mapperID == "MBC6" ) mapper = &mbc6;
if(mapperID == "MBC7" ) mapper = &mbc7;
if(mapperID == "MMM01") mapper = &mmm01;
if(mapperID == "HuC1" ) mapper = &huc1;
if(mapperID == "HuC3" ) mapper = &huc3;
if(mapperID == "TAMA" ) mapper = &tama;
if(!mapper) mapper = &mbc0;
information.rtc = false;
information.rumble = false;
accelerometer = (bool)document["board/accelerometer"];
rumble = (bool)document["board/rumble"];
rom.size = max(32768u, board["rom/size"].natural());
rom.size = max(0x4000, document["board/rom/size"].natural());
rom.data = (uint8*)memory::allocate(rom.size, 0xff);
ram.size = board["ram/size"].natural();
ram.data = (uint8*)memory::allocate(ram.size, 0xff);
if(auto name = board["rom/name"].text()) {
if(auto name = document["board/rom/name"].text()) {
if(auto fp = platform->open(pathID(), name, File::Read, File::Required)) {
fp->read(rom.data, min(rom.size, fp->size()));
}
}
if(auto name = board["ram/name"].text()) {
ram.size = document["board/ram/size"].natural();
ram.data = (uint8*)memory::allocate(ram.size, 0xff);
if(auto name = document["board/ram/name"].text()) {
if(auto fp = platform->open(pathID(), name, File::Read, File::Optional)) {
fp->read(ram.data, min(ram.size, fp->size()));
}
}
information.battery = (bool)board["ram/name"];
switch(information.mapper) { default:
case Mapper::MBC0: mapper = &mbc0; break;
case Mapper::MBC1: mapper = &mbc1; break;
case Mapper::MBC1M: mapper = &mbc1m; break;
case Mapper::MBC2: mapper = &mbc2; break;
case Mapper::MBC3: mapper = &mbc3; break;
case Mapper::MBC5: mapper = &mbc5; break;
case Mapper::MMM01: mapper = &mmm01; break;
case Mapper::HuC1: mapper = &huc1; break;
case Mapper::HuC3: mapper = &huc3; break;
rtc.size = document["board/rtc/size"].natural();
rtc.data = (uint8*)memory::allocate(rtc.size, 0xff);
if(auto name = document["board/rtc/name"].text()) {
if(auto fp = platform->open(pathID(), name, File::Read, File::Optional)) {
fp->read(rtc.data, min(rtc.size, fp->size()));
}
}
information.sha256 = Hash::SHA256(rom.data, rom.size).digest();
@@ -100,35 +100,21 @@ auto Cartridge::save() -> void {
fp->write(ram.data, ram.size);
}
}
if(auto name = document["board/rtc/name"].text()) {
if(auto fp = platform->open(pathID(), name, File::Write)) {
fp->write(rtc.data, rtc.size);
}
}
}
auto Cartridge::unload() -> void {
delete[] rom.data;
delete[] ram.data;
delete[] rtc.data;
rom = {};
ram = {};
}
auto Cartridge::readROM(uint addr) -> uint8 {
if(addr >= rom.size) addr %= rom.size;
return rom.data[addr];
}
auto Cartridge::writeROM(uint addr, uint8 data) -> void {
if(addr >= rom.size) addr %= rom.size;
rom.data[addr] = data;
}
auto Cartridge::readRAM(uint addr) -> uint8 {
if(ram.size == 0) return 0xff;
if(addr >= ram.size) addr %= ram.size;
return ram.data[addr];
}
auto Cartridge::writeRAM(uint addr, uint8 data) -> void {
if(ram.size == 0) return;
if(addr >= ram.size) addr %= ram.size;
ram.data[addr] = data;
rtc = {};
}
auto Cartridge::readIO(uint16 addr) -> uint8 {
@@ -140,10 +126,10 @@ auto Cartridge::readIO(uint16 addr) -> uint8 {
if(Model::GameBoyColor()) data = system.bootROM.cgb;
if(Model::SuperGameBoy()) data = system.bootROM.sgb;
if(addr >= 0x0000 && addr <= 0x00ff) return data[addr];
if(addr >= 0x0200 && addr <= 0x08ff && Model::GameBoyColor()) return data[addr - 256];
if(addr >= 0x0200 && addr <= 0x08ff && Model::GameBoyColor()) return data[addr - 0x100];
}
return mapper->readIO(addr);
return mapper->read(addr);
}
auto Cartridge::writeIO(uint16 addr, uint8 data) -> void {
@@ -152,25 +138,33 @@ auto Cartridge::writeIO(uint16 addr, uint8 data) -> void {
return;
}
mapper->writeIO(addr, data);
mapper->write(addr, data);
}
auto Cartridge::power() -> void {
bootromEnable = true;
mbc0.power();
mbc1.power();
mbc1m.power();
mbc2.power();
mbc3.power();
mbc5.power();
mmm01.power();
huc1.power();
huc3.power();
for(uint n = 0x0000; n <= 0x7fff; n++) bus.mmio[n] = this;
for(uint n = 0xa000; n <= 0xbfff; n++) bus.mmio[n] = this;
bus.mmio[0xff50] = this;
bootromEnable = true;
mapper->power();
}
auto Cartridge::second() -> void {
mapper->second();
}
auto Cartridge::Memory::read(uint address) const -> uint8 {
if(!size) return 0xff;
if(address >= size) address %= size;
return data[address];
}
auto Cartridge::Memory::write(uint address, uint8 byte) -> void {
if(!size) return;
if(address >= size) address %= size;
data[address] = byte;
}
}

View File

@@ -8,62 +8,55 @@ struct Cartridge : MMIO {
auto save() -> void;
auto unload() -> void;
auto readROM(uint addr) -> uint8;
auto writeROM(uint addr, uint8 data) -> void;
auto readRAM(uint addr) -> uint8;
auto writeRAM(uint addr, uint8 data) -> void;
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
auto readIO(uint16 address) -> uint8;
auto writeIO(uint16 address, uint8 data) -> void;
auto power() -> void;
auto second() -> void;
auto serialize(serializer&) -> void;
struct Information {
uint pathID = 0;
string sha256;
string manifest;
string title;
} information;
struct Memory {
auto read(uint address) const -> uint8;
auto write(uint address, uint8 data) -> void;
uint8* data = nullptr;
uint size = 0;
} rom, ram, rtc;
bool bootromEnable = true;
private:
struct Mapper {
virtual auto second() -> void {}
virtual auto read(uint16 address) -> uint8 = 0;
virtual auto write(uint16 address, uint8 data) -> void = 0;
virtual auto power() -> void = 0;
virtual auto serialize(serializer&) -> void = 0;
};
Mapper* mapper = nullptr;
bool accelerometer = false;
bool rumble = false;
#include "mbc0/mbc0.hpp"
#include "mbc1/mbc1.hpp"
#include "mbc1m/mbc1m.hpp"
#include "mbc2/mbc2.hpp"
#include "mbc3/mbc3.hpp"
#include "mbc5/mbc5.hpp"
#include "mbc6/mbc6.hpp"
#include "mbc7/mbc7.hpp"
#include "mmm01/mmm01.hpp"
#include "huc1/huc1.hpp"
#include "huc3/huc3.hpp"
enum Mapper : uint {
MBC0,
MBC1,
MBC1M,
MBC2,
MBC3,
MBC5,
MMM01,
HuC1,
HuC3,
Unknown,
};
struct Information {
uint pathID = 0;
string sha256;
string manifest;
string title;
Mapper mapper = Mapper::Unknown;
boolean ram;
boolean battery;
boolean rtc;
boolean rumble;
} information;
struct Memory {
uint8* data = nullptr;
uint size = 0;
} rom, ram;
MMIO* mapper = nullptr;
bool bootromEnable = true;
#include "tama/tama.hpp"
};
extern Cartridge cartridge;

View File

@@ -1,49 +1,54 @@
auto Cartridge::HuC1::readIO(uint16 addr) -> uint8 {
if((addr & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.readROM(addr);
auto Cartridge::HuC1::read(uint16 address) -> uint8 {
if((address & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom.read(address.bits(0,13));
}
if((addr & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.readROM(rom.select << 14 | (uint14)addr);
if((address & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom.read(io.rom.bank << 14 | address.bits(0,13));
}
if((addr & 0xe000) == 0xa000) { //$a000-bfff
return cartridge.readRAM(ram.select << 13 | (uint13)addr);
if((address & 0xe000) == 0xa000) { //$a000-bfff
return cartridge.ram.read(io.ram.bank << 13 | address.bits(0,12));
}
return 0xff;
}
auto Cartridge::HuC1::writeIO(uint16 addr, uint8 data) -> void {
if((addr & 0xe000) == 0x0000) { //$0000-1fff
ram.writable = data.bits(0,3) == 0x0a;
auto Cartridge::HuC1::write(uint16 address, uint8 data) -> void {
if((address & 0xe000) == 0x0000) { //$0000-1fff
io.ram.writable = data.bits(0,3) == 0x0a;
return;
}
if((addr & 0xe000) == 0x2000) { //$2000-3fff
rom.select = data + (data == 0);
if((address & 0xe000) == 0x2000) { //$2000-3fff
io.rom.bank = data;
if(!io.rom.bank) io.rom.bank = 0x01;
return;
}
if((addr & 0xe000) == 0x4000) { //$4000-5fff
ram.select = data;
if((address & 0xe000) == 0x4000) { //$4000-5fff
io.ram.bank = data;
return;
}
if((addr & 0xe000) == 0x6000) { //$6000-7fff
model = data.bit(0);
if((address & 0xe000) == 0x6000) { //$6000-7fff
io.model = data.bit(0);
return;
}
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(!ram.writable) return;
return cartridge.writeRAM(ram.select << 13 | (uint13)addr, data);
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.writable) return;
return cartridge.ram.write(io.ram.bank << 13 | address.bits(0,12), data);
}
}
auto Cartridge::HuC1::power() -> void {
rom.select = 0x01;
ram.writable = false;
ram.select = 0x00;
model = 0;
io = {};
}
auto Cartridge::HuC1::serialize(serializer& s) -> void {
s.integer(io.model);
s.integer(io.rom.bank);
s.integer(io.ram.writable);
s.integer(io.ram.bank);
}

View File

@@ -1,14 +1,17 @@
struct HuC1 : MMIO {
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
struct HuC1 : Mapper {
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
struct ROM {
uint8 select;
} rom;
struct RAM {
bool writable;
uint8 select;
} ram;
bool model;
struct IO {
uint1 model;
struct ROM {
uint8 bank = 0x01;
} rom;
struct RAM {
uint1 writable;
uint8 bank;
} ram;
} io;
} huc1;

View File

@@ -1,49 +1,48 @@
auto Cartridge::HuC3::readIO(uint16 addr) -> uint8 {
if((addr & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.readROM(addr);
auto Cartridge::HuC3::read(uint16 address) -> uint8 {
if((address & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom.read(address.bits(0,13));
}
if((addr & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.readROM(rom.select << 14 | (uint14)addr);
if((address & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom.read(io.rom.bank << 14 | address.bits(0,13));
}
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram.enable) return cartridge.readRAM(ram.select << 13 | (uint13)addr);
return 0x01; //does not return open collection
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.enable) return 0x01; //does not return open collection
return cartridge.ram.read(io.ram.bank << 13 | address.bits(0,12));
}
return 0xff;
}
auto Cartridge::HuC3::writeIO(uint16 addr, uint8 data) -> void {
if((addr & 0xe000) == 0x0000) { //$0000-1fff
ram.enable = data.bits(0,3) == 0x0a;
auto Cartridge::HuC3::write(uint16 address, uint8 data) -> void {
if((address & 0xe000) == 0x0000) { //$0000-1fff
io.ram.enable = data.bits(0,3) == 0x0a;
return;
}
if((addr & 0xe000) == 0x2000) { //$2000-3fff
rom.select = data;
if((address & 0xe000) == 0x2000) { //$2000-3fff
io.rom.bank = data;
return;
}
if((addr & 0xe000) == 0x4000) { //$4000-5fff
ram.select = data;
if((address & 0xe000) == 0x4000) { //$4000-5fff
io.ram.bank = data;
return;
}
if((addr & 0xe000) == 0x6000) { //$6000-7fff
//unknown purpose
return;
}
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram.enable) cartridge.writeRAM(ram.select << 13 | (uint13)addr, data);
return;
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.enable) return;
return cartridge.ram.write(io.ram.bank << 13 | address.bits(0,12), data);
}
}
auto Cartridge::HuC3::power() -> void {
rom.select = 0x01;
ram.enable = false;
ram.select = 0x00;
io = {};
}
auto Cartridge::HuC3::serialize(serializer& s) -> void {
s.integer(io.rom.bank);
s.integer(io.ram.enable);
s.integer(io.ram.bank);
}

View File

@@ -1,13 +1,16 @@
struct HuC3 : MMIO {
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
struct HuC3 : Mapper {
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
struct ROM {
uint8 select;
} rom;
struct RAM {
bool enable;
uint8 select;
} ram;
struct IO {
struct ROM {
uint8 bank = 0x01;
} rom;
struct RAM {
uint1 enable;
uint8 bank;
} ram;
} io;
} huc3;

View File

@@ -1,21 +1,24 @@
auto Cartridge::MBC0::readIO(uint16 addr) -> uint8 {
if((addr & 0x8000) == 0x0000) { //$0000-7fff
return cartridge.readROM(addr);
auto Cartridge::MBC0::read(uint16 address) -> uint8 {
if((address & 0x8000) == 0x0000) { //$0000-7fff
return cartridge.rom.read(address.bits(0,14));
}
if((addr & 0xe000) == 0xa000) { //$a000-bfff
return cartridge.readRAM((uint13)addr);
if((address & 0xe000) == 0xa000) { //$a000-bfff
return cartridge.ram.read(address.bits(0,12));
}
return 0xff;
}
auto Cartridge::MBC0::writeIO(uint16 addr, uint8 data) -> void {
if((addr & 0xe000) == 0xa000) { //$a000-bfff
cartridge.writeRAM((uint13)addr, data);
auto Cartridge::MBC0::write(uint16 address, uint8 data) -> void {
if((address & 0xe000) == 0xa000) { //$a000-bfff
cartridge.ram.write(address.bits(0,12), data);
return;
}
}
auto Cartridge::MBC0::power() -> void {
}
auto Cartridge::MBC0::serialize(serializer& s) -> void {
}

View File

@@ -1,5 +1,6 @@
struct MBC0 : MMIO {
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
struct MBC0 : Mapper {
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
} mbc0;

View File

@@ -1,66 +1,67 @@
auto Cartridge::MBC1::readIO(uint16 addr) -> uint8 {
if((addr & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.readROM(addr);
auto Cartridge::MBC1::read(uint16 address) -> uint8 {
if((address & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom.read(address.bits(0,13));
}
if((addr & 0xc000) == 0x4000) { //$4000-7fff
if(mode == 0) {
return cartridge.readROM(ram.select << 19 | rom.select << 14 | (uint14)addr);
if((address & 0xc000) == 0x4000) { //$4000-7fff
if(io.mode == 0) {
return cartridge.rom.read(io.ram.bank << 19 | io.rom.bank << 14 | address.bits(0,13));
} else {
return cartridge.readROM(rom.select << 14 | (uint14)addr);
return cartridge.rom.read(io.rom.bank << 14 | address.bits(0,13));
}
}
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram.enable) {
if(mode == 0) {
return cartridge.readRAM((uint13)addr);
} else {
return cartridge.readRAM(ram.select << 13 | (uint13)addr);
}
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.enable) return 0xff;
if(io.mode == 0) {
return cartridge.ram.read(address.bits(0,12));
} else {
return cartridge.ram.read(io.ram.bank << 13 | address.bits(0,12));
}
return 0xff;
}
return 0xff;
}
auto Cartridge::MBC1::writeIO(uint16 addr, uint8 data) -> void {
if((addr & 0xe000) == 0x0000) { //$0000-1fff
ram.enable = (data & 0x0f) == 0x0a;
auto Cartridge::MBC1::write(uint16 address, uint8 data) -> void {
if((address & 0xe000) == 0x0000) { //$0000-1fff
io.ram.enable = data.bits(0,3) == 0x0a;
return;
}
if((addr & 0xe000) == 0x2000) { //$2000-3fff
rom.select = (data & 0x1f) + ((data & 0x1f) == 0);
if((address & 0xe000) == 0x2000) { //$2000-3fff
io.rom.bank = data.bits(0,4);
if(!io.rom.bank) io.rom.bank = 0x01;
return;
}
if((addr & 0xe000) == 0x4000) { //$4000-5fff
ram.select = data & 0x03;
if((address & 0xe000) == 0x4000) { //$4000-5fff
io.ram.bank = data.bits(0,1);
return;
}
if((addr & 0xe000) == 0x6000) { //$6000-7fff
mode = data & 0x01;
if((address & 0xe000) == 0x6000) { //$6000-7fff
io.mode = data.bit(0);
return;
}
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram.enable) {
if(mode == 0) {
cartridge.writeRAM(addr & 0x1fff, data);
} else {
cartridge.writeRAM(ram.select << 13 | (uint13)addr, data);
}
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.enable) return;
if(io.mode == 0) {
return cartridge.ram.write(address.bits(0,12), data);
} else {
return cartridge.ram.write(io.ram.bank << 13 | address.bits(0,12), data);
}
return;
}
}
auto Cartridge::MBC1::power() -> void {
rom.select = 0x01;
ram.enable = false;
ram.select = 0x00;
mode = 0;
io = {};
}
auto Cartridge::MBC1::serialize(serializer& s) -> void {
s.integer(io.mode);
s.integer(io.rom.bank);
s.integer(io.ram.enable);
s.integer(io.ram.bank);
}

View File

@@ -1,14 +1,17 @@
struct MBC1 : MMIO {
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
struct MBC1 : Mapper {
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer& s) -> void;
struct ROM {
uint8 select;
} rom;
struct RAM {
bool enable;
uint8 select;
} ram;
bool mode;
struct IO {
uint1 mode;
struct ROM {
uint8 bank = 0x01;
} rom;
struct RAM {
uint1 enable;
uint8 bank;
} ram;
} io;
} mbc1;

View File

@@ -1,40 +1,43 @@
auto Cartridge::MBC1M::readIO(uint16 addr) -> uint8 {
if((addr & 0xc000) == 0x0000) { //$0000-3fff
if(mode == 0) return cartridge.readROM((uint14)addr);
return cartridge.readROM(rom.hi << 18 | (uint14)addr);
auto Cartridge::MBC1M::read(uint16 address) -> uint8 {
if((address & 0xc000) == 0x0000) { //$0000-3fff
if(io.mode == 0) return cartridge.rom.read(address.bits(0,13));
return cartridge.rom.read(io.rom.bank.bits(4,5) << 18 | address.bits(0,13));
}
if((addr & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.readROM(rom.hi << 18 | rom.lo << 14 | (uint14)addr);
if((address & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom.read(io.rom.bank << 14 | address.bits(0,13));
}
if((addr & 0xe000) == 0xa000) { //$a000-bfff
return cartridge.readRAM((uint13)addr);
if((address & 0xe000) == 0xa000) { //$a000-bfff
return cartridge.ram.read(address.bits(0,12));
}
return 0xff;
}
auto Cartridge::MBC1M::writeIO(uint16 addr, uint8 data) -> void {
if((addr & 0xe000) == 0x2000) { //$2000-3fff
rom.lo = data.bits(0,3);
auto Cartridge::MBC1M::write(uint16 address, uint8 data) -> void {
if((address & 0xe000) == 0x2000) { //$2000-3fff
io.rom.bank.bits(0,3) = data.bits(0,3);
}
if((addr & 0xe000) == 0x4000) { //$4000-5fff
rom.hi = data.bits(0,1);
if((address & 0xe000) == 0x4000) { //$4000-5fff
io.rom.bank.bits(4,5) = data.bits(0,1);
}
if((addr & 0xe000) == 0x6000) { //$6000-7fff
mode = data.bit(0);
if((address & 0xe000) == 0x6000) { //$6000-7fff
io.mode = data.bit(0);
}
if((addr & 0xe000) == 0xa000) { //$a000-bfff
cartridge.writeRAM((uint13)addr, data);
if((address & 0xe000) == 0xa000) { //$a000-bfff
cartridge.ram.write(address.bits(0,13), data);
}
}
auto Cartridge::MBC1M::power() -> void {
rom.lo = 1;
rom.hi = 0;
mode = 0;
io = {};
}
auto Cartridge::MBC1M::serialize(serializer& s) -> void {
s.integer(io.mode);
s.integer(io.rom.bank);
}

View File

@@ -1,11 +1,13 @@
struct MBC1M : MMIO {
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
struct MBC1M : Mapper {
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
struct ROM {
uint4 lo;
uint2 hi;
} rom;
uint1 mode;
struct IO {
uint1 mode;
struct ROM {
uint6 bank = 0x01;
} rom;
} io;
} mbc1m;

View File

@@ -1,38 +1,61 @@
auto Cartridge::MBC2::readIO(uint16 addr) -> uint8 {
if((addr & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.readROM(addr);
auto Cartridge::MBC2::read(uint16 address) -> uint8 {
if((address & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom.read(address.bits(0,13));
}
if((addr & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.readROM(rom.select << 14 | (uint14)addr);
if((address & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom.read(io.rom.bank << 14 | address.bits(0,13));
}
if((addr & 0xee00) == 0xa000) { //$a000-a1ff
if(ram.enable) return cartridge.readRAM((uint9)addr);
return 0xff;
if((address & 0xee01) == 0xa000) { //$a000-a1ff (even)
if(!io.ram.enable) return 0xff;
auto ram = cartridge.ram.read(address.bits(1,8));
return 0xf0 | ram.bits(0,3);
}
if((address & 0xee01) == 0xa001) { //$a000-a1ff (odd)
if(!io.ram.enable) return 0xff;
auto ram = cartridge.ram.read(address.bits(1,8));
return 0xf0 | ram.bits(4,7);
}
return 0xff;
}
auto Cartridge::MBC2::writeIO(uint16 addr, uint8 data) -> void {
if((addr & 0xe000) == 0x0000) { //$0000-1fff
if(!addr.bit(8)) ram.enable = data.bits(0,3) == 0x0a;
auto Cartridge::MBC2::write(uint16 address, uint8 data) -> void {
if((address & 0xe000) == 0x0000) { //$0000-1fff
if(!address.bit(8)) io.ram.enable = data.bits(0,3) == 0x0a;
return;
}
if((addr & 0xe000) == 0x2000) { //$2000-3fff
if( addr.bit(8)) rom.select = data.bits(0,3) + (data.bits(0,3) == 0);
if((address & 0xe000) == 0x2000) { //$2000-3fff
if(address.bit(8)) io.rom.bank = data.bits(0,3);
if(!io.rom.bank) io.rom.bank = 0x01;
return;
}
if((addr & 0xee00) == 0xa000) { //$a000-a1ff
if(ram.enable) cartridge.writeRAM((uint9)addr, data.bits(0,3));
if((address & 0xee01) == 0xa000) { //$a000-a1ff (even)
if(!io.ram.enable) return;
auto ram = cartridge.ram.read(address.bits(1,8));
ram.bits(0,3) = data.bits(0,3);
cartridge.ram.write(address.bits(1,8), ram);
return;
}
if((address & 0xee01) == 0xa001) { //$a000-a1ff (odd)
if(!io.ram.enable) return;
auto ram = cartridge.ram.read(address.bits(1,8));
ram.bits(4,7) = data.bits(0,3);
cartridge.ram.write(address.bits(1,8), ram);
return;
}
}
auto Cartridge::MBC2::power() -> void {
rom.select = 0x01;
ram.enable = false;
io = {};
}
auto Cartridge::MBC2::serialize(serializer& s) -> void {
s.integer(io.rom.bank);
s.integer(io.ram.enable);
}

View File

@@ -1,12 +1,15 @@
struct MBC2 : MMIO {
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
struct MBC2 : Mapper {
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
struct ROM {
uint8 select;
} rom;
struct RAM {
bool enable;
} ram;
struct IO {
struct ROM {
uint8 bank = 0x01;
} rom;
struct RAM {
uint1 enable = 0;
} ram;
} io;
} mbc2;

View File

@@ -1,118 +1,113 @@
auto Cartridge::MBC3::second() -> void {
if(!rtc.halt) {
if(++rtc.second >= 60) {
rtc.second = 0;
if(++rtc.minute >= 60) {
rtc.minute = 0;
if(++rtc.hour >= 24) {
rtc.hour = 0;
if(++rtc.day >= 512) {
rtc.day = 0;
rtc.dayCarry = true;
}
if(io.rtc.halt) return;
if(++io.rtc.second >= 60) {
io.rtc.second = 0;
if(++io.rtc.minute >= 60) {
io.rtc.minute = 0;
if(++io.rtc.hour >= 24) {
io.rtc.hour = 0;
if(++io.rtc.day == 0) {
io.rtc.dayCarry = true;
}
}
}
}
}
auto Cartridge::MBC3::readIO(uint16 addr) -> uint8 {
if((addr & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.readROM(addr);
auto Cartridge::MBC3::read(uint16 address) -> uint8 {
if((address & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom.read(address.bits(0,13));
}
if((addr & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.readROM(rom.select<< 14 | (uint14)addr);
if((address & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom.read(io.rom.bank << 14 | address.bits(0,13));
}
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram.enable) {
if(ram.select <= 0x03) {
return cartridge.readRAM(ram.select << 13 | (uint13)addr);
}
if(ram.select == 0x08) return rtc.latchSecond;
if(ram.select == 0x09) return rtc.latchMinute;
if(ram.select == 0x0a) return rtc.latchHour;
if(ram.select == 0x0b) return rtc.latchDay;
if(ram.select == 0x0c) return rtc.latchDayCarry << 7 | rtc.latchDay >> 8;
}
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.enable) return 0xff;
if(io.ram.bank <= 0x03) return cartridge.ram.read(io.ram.bank << 13 | address.bits(0,12));
if(io.ram.bank == 0x08) return io.rtc.latchSecond;
if(io.ram.bank == 0x09) return io.rtc.latchMinute;
if(io.ram.bank == 0x0a) return io.rtc.latchHour;
if(io.ram.bank == 0x0b) return io.rtc.latchDay;
if(io.ram.bank == 0x0c) return io.rtc.latchDayCarry << 7 | io.rtc.latchDay >> 8;
return 0xff;
}
return 0xff;
}
auto Cartridge::MBC3::writeIO(uint16 addr, uint8 data) -> void {
if((addr & 0xe000) == 0x0000) { //$0000-1fff
ram.enable = (data & 0x0f) == 0x0a;
auto Cartridge::MBC3::write(uint16 address, uint8 data) -> void {
if((address & 0xe000) == 0x0000) { //$0000-1fff
io.ram.enable = data.bits(0,3) == 0x0a;
return;
}
if((addr & 0xe000) == 0x2000) { //$2000-3fff
rom.select = (data & 0x7f) + ((data & 0x7f) == 0);
if((address & 0xe000) == 0x2000) { //$2000-3fff
io.rom.bank = data.bits(0,6);
if(!io.rom.bank) io.rom.bank = 0x01;
return;
}
if((addr & 0xe000) == 0x4000) { //$4000-5fff
ram.select = data;
if((address & 0xe000) == 0x4000) { //$4000-5fff
io.ram.bank = data;
return;
}
if((addr & 0xe000) == 0x6000) { //$6000-7fff
if(rtc.latch == 0 && data == 1) {
rtc.latchSecond = rtc.second;
rtc.latchMinute = rtc.minute;
rtc.latchHour = rtc.hour;
rtc.latchDay = rtc.day;
rtc.latchDayCarry = rtc.dayCarry;
if((address & 0xe000) == 0x6000) { //$6000-7fff
if(io.rtc.latch == 0 && data == 1) {
io.rtc.latchSecond = io.rtc.second;
io.rtc.latchMinute = io.rtc.minute;
io.rtc.latchHour = io.rtc.hour;
io.rtc.latchDay = io.rtc.day;
io.rtc.latchDayCarry = io.rtc.dayCarry;
}
rtc.latch = data;
io.rtc.latch = data;
return;
}
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram.enable) {
if(ram.select <= 0x03) {
cartridge.writeRAM(ram.select << 13 | (uint13)addr, data);
} else if(ram.select == 0x08) {
if(data >= 60) data = 0;
rtc.second = data;
} else if(ram.select == 0x09) {
if(data >= 60) data = 0;
rtc.minute = data;
} else if(ram.select == 0x0a) {
if(data >= 24) data = 0;
rtc.hour = data;
} else if(ram.select == 0x0b) {
rtc.day = (rtc.day & 0x0100) | data;
} else if(ram.select == 0x0c) {
rtc.day = ((data & 1) << 8) | (rtc.day & 0xff);
rtc.halt = data & 0x40;
rtc.dayCarry = data & 0x80;
}
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.enable) return;
if(io.ram.bank <= 0x03) {
cartridge.ram.write(io.ram.bank << 13 | address.bits(0,12), data);
} else if(io.ram.bank == 0x08) {
if(data >= 60) data = 0;
io.rtc.second = data;
} else if(io.ram.bank == 0x09) {
if(data >= 60) data = 0;
io.rtc.minute = data;
} else if(io.ram.bank == 0x0a) {
if(data >= 24) data = 0;
io.rtc.hour = data;
} else if(io.ram.bank == 0x0b) {
io.rtc.day.bits(0,7) = data.bits(0,7);
} else if(io.ram.bank == 0x0c) {
io.rtc.day.bit(8) = data.bit(0);
io.rtc.halt = data.bit(6);
io.rtc.dayCarry = data.bit(7);
}
return;
}
}
auto Cartridge::MBC3::power() -> void {
rom.select = 0x01;
ram.enable = false;
ram.select = 0x00;
rtc.latch = 0;
rtc.halt = true;
rtc.second = 0;
rtc.minute = 0;
rtc.hour = 0;
rtc.day = 0;
rtc.dayCarry = false;
rtc.latchSecond = 0;
rtc.latchMinute = 0;
rtc.latchHour = 0;
rtc.latchDay = 0;
rtc.latchDayCarry = false;
io = {};
}
auto Cartridge::MBC3::serialize(serializer& s) -> void {
s.integer(io.rom.bank);
s.integer(io.ram.enable);
s.integer(io.ram.bank);
s.integer(io.rtc.halt);
s.integer(io.rtc.latch);
s.integer(io.rtc.second);
s.integer(io.rtc.minute);
s.integer(io.rtc.hour);
s.integer(io.rtc.day);
s.integer(io.rtc.dayCarry);
s.integer(io.rtc.latchSecond);
s.integer(io.rtc.latchMinute);
s.integer(io.rtc.latchHour);
s.integer(io.rtc.latchDay);
s.integer(io.rtc.latchDayCarry);
}

View File

@@ -1,30 +1,33 @@
struct MBC3 : MMIO {
struct MBC3 : Mapper {
auto second() -> void;
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer& s) -> void;
struct ROM {
uint8 select;
} rom;
struct RAM {
bool enable;
uint8 select;
} ram;
struct RTC {
bool latch;
struct IO {
struct ROM {
uint8 bank = 0x01;
} rom;
struct RAM {
uint1 enable;
uint8 bank;
} ram;
struct RTC {
uint1 halt = true;
uint1 latch;
bool halt;
uint second;
uint minute;
uint hour;
uint day;
bool dayCarry;
uint8 second;
uint8 minute;
uint8 hour;
uint9 day;
uint1 dayCarry;
uint latchSecond;
uint latchMinute;
uint latchHour;
uint latchDay;
uint latchDayCarry;
} rtc;
uint8 latchSecond;
uint8 latchMinute;
uint8 latchHour;
uint9 latchDay;
uint1 latchDayCarry;
} rtc;
} io;
} mbc3;

View File

@@ -1,49 +1,54 @@
auto Cartridge::MBC5::readIO(uint16 addr) -> uint8 {
if((addr & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.readROM(addr);
auto Cartridge::MBC5::read(uint16 address) -> uint8 {
if((address & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom.read(address.bits(0,13));
}
if((addr & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.readROM(rom.select << 14 | (uint14)addr);
if((address & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom.read(io.rom.bank << 14 | address.bits(0,13));
}
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram.enable) return cartridge.readRAM(ram.select << 13 | (uint13)addr);
return 0xff;
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.enable) return 0xff;
return cartridge.ram.read(io.ram.bank << 13 | address.bits(0,12));
}
return 0xff;
}
auto Cartridge::MBC5::writeIO(uint16 addr, uint8 data) -> void {
if((addr & 0xe000) == 0x0000) { //$0000-1fff
ram.enable = data.bits(0,3) == 0x0a;
auto Cartridge::MBC5::write(uint16 address, uint8 data) -> void {
if((address & 0xe000) == 0x0000) { //$0000-1fff
io.ram.enable = data.bits(0,3) == 0x0a;
return;
}
if((addr & 0xf000) == 0x2000) { //$2000-2fff
rom.select.byte(0) = data;
if((address & 0xf000) == 0x2000) { //$2000-2fff
io.rom.bank.bits(0,7) = data.bits(0,7);
return;
}
if((addr & 0xf000) == 0x3000) { //$3000-3fff
rom.select.byte(1) = data.bit(0);
if((address & 0xf000) == 0x3000) { //$3000-3fff
io.rom.bank.bit(8) = data.bit(0);
return;
}
if((addr & 0xe000) == 0x4000) { //$4000-5fff
ram.select = data.bits(0,3);
if((address & 0xe000) == 0x4000) { //$4000-5fff
if(cartridge.rumble) platform->inputRumble(ID::Port::Hardware, ID::Device::Controls, 10, data.bit(3));
io.ram.bank = data.bits(0,3);
return;
}
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram.enable) cartridge.writeRAM(ram.select << 13 | (uint13)addr, data);
return;
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.enable) return;
return cartridge.ram.write(io.ram.bank << 13 | address.bits(0,12), data);
}
}
auto Cartridge::MBC5::power() -> void {
rom.select = 0x001;
ram.enable = false;
ram.select = 0x00;
io = {};
}
auto Cartridge::MBC5::serialize(serializer& s) -> void {
s.integer(io.rom.bank);
s.integer(io.ram.enable);
s.integer(io.ram.bank);
}

View File

@@ -1,13 +1,16 @@
struct MBC5 : MMIO {
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
struct MBC5 : Mapper {
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
struct ROM {
uint9 select;
} rom;
struct RAM {
bool enable;
uint4 select;
} ram;
struct IO {
struct ROM {
uint9 bank = 0x01;
} rom;
struct RAM {
uint1 enable;
uint4 bank;
} ram;
} io;
} mbc5;

View File

@@ -0,0 +1,74 @@
auto Cartridge::MBC6::read(uint16 address) -> uint8 {
if((address & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom.read(address.bits(0,13));
}
if((address & 0xe000) == 0x4000) { //$4000-5fff
return cartridge.rom.read(io.rom.bank[0] << 13 | address.bits(0,12));
}
if((address & 0xe000) == 0x6000) { //$6000-7fff
return cartridge.rom.read(io.rom.bank[1] << 13 | address.bits(0,12));
}
if((address & 0xf000) == 0xa000) { //$a000-afff
if(!io.ram.enable) return 0xff;
return cartridge.ram.read(io.ram.bank[0] << 12 | address.bits(0,11));
}
if((address & 0xf000) == 0xb000) { //$b000-bfff
if(!io.ram.enable) return 0xff;
return cartridge.ram.read(io.ram.bank[1] << 12 | address.bits(0,11));
}
return 0xff;
}
auto Cartridge::MBC6::write(uint16 address, uint8 data) -> void {
if((address & 0xfc00) == 0x0000) {
io.ram.enable = data.bits(0,3) == 0xa;
return;
}
if((address & 0xfc00) == 0x0400) {
io.ram.bank[0] = data;
return;
}
if((address & 0xfc00) == 0x0800) {
io.ram.bank[1] = data;
return;
}
if((address & 0xf800) == 0x2000) {
io.rom.bank[0] = data;
return;
}
if((address & 0xf800) == 0x3000) {
io.rom.bank[1] = data;
return;
}
if((address & 0xf000) == 0xa000) { //$a000-afff
if(!io.ram.enable) return;
return cartridge.ram.write(io.ram.bank[0] << 12 | address.bits(0,11), data);
}
if((address & 0xf000) == 0xb000) { //$b000-bfff
if(!io.ram.enable) return;
return cartridge.ram.write(io.ram.bank[1] << 12 | address.bits(0,11), data);
}
}
auto Cartridge::MBC6::power() -> void {
io = {};
}
auto Cartridge::MBC6::serialize(serializer& s) -> void {
s.integer(io.rom.bank[0]);
s.integer(io.rom.bank[1]);
s.integer(io.ram.enable);
s.integer(io.ram.bank[0]);
s.integer(io.ram.bank[1]);
}

View File

@@ -0,0 +1,16 @@
struct MBC6 : Mapper {
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
struct IO {
struct ROM {
uint8 bank[2];
} rom;
struct RAM {
uint1 enable;
uint8 bank[2];
} ram;
} io;
} mbc6;

View File

@@ -0,0 +1,86 @@
auto Cartridge::MBC7::read(uint16 address) -> uint8 {
if((address & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom.read(address.bits(0,13));
}
if((address & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom.read(io.rom.bank << 14 | address.bits(0,13));
}
if((address & 0xf000) == 0xa000) { //$a000-afff
if(!io.ram.enable[0] || !io.ram.enable[1]) return 0xff;
switch(address.bits(4,7)) {
case 2: return io.accelerometer.x.bits(0, 7);
case 3: return io.accelerometer.x.bits(8,15);
case 4: return io.accelerometer.y.bits(0, 7);
case 5: return io.accelerometer.y.bits(8,15);
case 6: return 0x00; //z?
case 7: return 0xff; //z?
case 8: return 0xff;
}
return 0xff;
}
return 0xff;
}
auto Cartridge::MBC7::write(uint16 address, uint8 data) -> void {
if((address & 0xe000) == 0x0000) { //$0000-1fff
io.ram.enable[0] = data.bits(0,3) == 0xa;
if(!io.ram.enable[0]) io.ram.enable[1] = false;
return;
}
if((address & 0xe000) == 0x2000) { //$2000-3fff
io.rom.bank = data;
if(!io.rom.bank) io.rom.bank = 1;
return;
}
if((address & 0xe000) == 0x4000) { //$4000-5fff
if(!io.ram.enable[0]) return;
io.ram.enable[1] = data == 0x40;
}
if((address & 0xf000) == 0xa000) { //$a000-afff
if(!io.ram.enable[0] || !io.ram.enable[1]) return;
switch(address.bits(4,7)) {
case 0: {
if(data != 0x55) break;
io.accelerometer.x = 0x8000;
io.accelerometer.y = 0x8000;
break;
}
case 1: {
if(data != 0xaa) break;
io.accelerometer.x = 0x8000 + platform->inputPoll(ID::Port::Hardware, ID::Device::Controls, 8);
io.accelerometer.y = 0x8000 + platform->inputPoll(ID::Port::Hardware, ID::Device::Controls, 9);
break;
}
case 8: {
break;
}
}
return;
}
}
auto Cartridge::MBC7::power() -> void {
io = {};
}
auto Cartridge::MBC7::serialize(serializer& s) -> void {
s.integer(io.rom.bank);
s.integer(io.ram.enable[0]);
s.integer(io.ram.enable[1]);
s.integer(io.accelerometer.x);
s.integer(io.accelerometer.y);
}

View File

@@ -0,0 +1,19 @@
struct MBC7 : Mapper {
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
struct IO {
struct ROM {
uint8 bank = 0x01;
} rom;
struct RAM {
uint1 enable[2];
} ram;
struct Accelerometer {
uint16 x = 0x8000;
uint16 y = 0x8000;
} accelerometer;
} io;
} mbc7;

View File

@@ -1,60 +1,65 @@
auto Cartridge::MMM01::readIO(uint16 addr) -> uint8 {
if((addr & 0x8000) == 0x0000) { //$0000-7fff
if(mode == 0) return cartridge.readROM(addr);
}
auto Cartridge::MMM01::read(uint16 address) -> uint8 {
if(io.mode == 0) {
if((address & 0x8000) == 0x0000) { //$0000-7fff
return cartridge.rom.read(cartridge.rom.size - 0x8000 + address.bits(0,14));
}
if((addr & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.readROM(0x8000 + (rom.base << 14) + (uint14)addr);
}
return 0xff;
} else {
if((address & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom.read((io.rom.base << 14) + address.bits(0,13));
}
if((addr & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.readROM(0x8000 + (rom.base << 14) + (rom.select<< 14) + (uint14)addr);
}
if((address & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom.read((io.rom.base << 14) + (io.rom.bank << 14) + address.bits(0,13));
}
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.enable) return 0xff;
return cartridge.ram.read(io.ram.bank << 13 | address.bits(0,12));
}
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram.enable) return cartridge.readRAM(ram.select << 13 | (uint13)addr);
return 0xff;
}
return 0xff;
}
auto Cartridge::MMM01::writeIO(uint16 addr, uint8 data) -> void {
if((addr & 0xe000) == 0x0000) { //$0000-1fff
if(mode == 0) {
mode = 1;
} else {
ram.enable= data.bits(0,3) == 0x0a;
auto Cartridge::MMM01::write(uint16 address, uint8 data) -> void {
if(io.mode == 0) {
if((address & 0xe000) == 0x0000) { //$0000-1fff
io.mode = 1;
}
}
if((addr & 0xe000) == 0x2000) { //$2000-3fff
if(mode == 0) {
rom.base = data.bits(0,5);
} else {
rom.select = data;
if((address & 0xe000) == 0x2000) { //$2000-3fff
io.rom.base = data.bits(0,5);
}
}
if((addr & 0xe000) == 0x4000) { //$4000-5fff
if(mode == 1) {
ram.select = data;
} else {
if((address & 0xe000) == 0x0000) { //$0000-1fff
io.ram.enable = data.bits(0,3) == 0x0a;
}
}
if((addr & 0xe000) == 0x6000) { //$6000-7fff
//unknown purpose
}
if((address & 0xe000) == 0x2000) { //$2000-3fff
io.rom.bank = data;
}
if((addr & 0xe000) == 0xa000) { //$a000-bfff
if(ram.enable) cartridge.writeRAM(ram.select << 13 | (uint13)addr, data);
if((address & 0xe000) == 0x4000) { //$4000-5fff
io.ram.bank = data;
}
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.enable) return;
cartridge.ram.write(io.ram.bank << 13 | address.bits(0,12), data);
}
}
}
auto Cartridge::MMM01::power() -> void {
rom.base = 0x00;
rom.select = 0x01;
ram.enable = false;
ram.select = 0x00;
mode = 0;
io = {};
}
auto Cartridge::MMM01::serialize(serializer& s) -> void {
s.integer(io.mode);
s.integer(io.rom.base);
s.integer(io.rom.bank);
s.integer(io.ram.enable);
s.integer(io.ram.bank);
}

View File

@@ -1,15 +1,18 @@
struct MMM01 : MMIO {
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
struct MMM01 : Mapper {
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer& s) -> void;
struct ROM {
uint6 base;
uint8 select;
} rom;
struct RAM {
bool enable;
uint8 select;
} ram;
bool mode;
struct IO {
uint1 mode;
struct ROM {
uint6 base;
uint8 bank = 0x01;
} rom;
struct RAM {
uint1 enable;
uint8 bank;
} ram;
} io;
} mmm01;

View File

@@ -1,51 +1,8 @@
auto Cartridge::serialize(serializer& s) -> void {
if(information.battery) s.array(ram.data, ram.size);
if(ram.size) s.array(ram.data, ram.size);
if(rtc.size) s.array(rtc.data, rtc.size);
s.integer(bootromEnable);
s.integer(mbc1.rom.select);
s.integer(mbc1.ram.enable);
s.integer(mbc1.ram.select);
s.integer(mbc1.mode);
s.integer(mbc1m.rom.lo);
s.integer(mbc1m.rom.hi);
s.integer(mbc1m.mode);
s.integer(mbc2.rom.select);
s.integer(mbc2.ram.enable);
s.integer(mbc3.rom.select);
s.integer(mbc3.ram.enable);
s.integer(mbc3.ram.select);
s.integer(mbc3.rtc.latch);
s.integer(mbc3.rtc.halt);
s.integer(mbc3.rtc.second);
s.integer(mbc3.rtc.minute);
s.integer(mbc3.rtc.hour);
s.integer(mbc3.rtc.day);
s.integer(mbc3.rtc.dayCarry);
s.integer(mbc3.rtc.latchSecond);
s.integer(mbc3.rtc.latchMinute);
s.integer(mbc3.rtc.latchHour);
s.integer(mbc3.rtc.latchDay);
s.integer(mbc3.rtc.latchDayCarry);
s.integer(mbc5.rom.select);
s.integer(mbc5.ram.enable);
s.integer(mbc5.ram.select);
s.integer(mmm01.rom.base);
s.integer(mmm01.rom.select);
s.integer(mmm01.ram.enable);
s.integer(mmm01.ram.select);
s.integer(mmm01.mode);
s.integer(huc1.rom.select);
s.integer(huc1.ram.writable);
s.integer(huc1.ram.select);
s.integer(huc1.model);
s.integer(huc3.rom.select);
s.integer(huc3.ram.enable);
s.integer(huc3.ram.select);
mapper->serialize(s);
}

View File

@@ -0,0 +1,236 @@
//U1: TAMA7: Mask ROM (512KB)
//U2: TAMA5: Game Boy cartridge connector interface
//U3: TAMA6: Toshiba TMP47C243M (4-bit MCU)
//U4: RTC: Toshiba TC8521AM
//note: the TMP47C243M's 2048 x 8-bit program ROM is currently undumped
//as such, high level emulation is used as a necessary evil
auto Cartridge::TAMA::second() -> void {
if(++rtc.second >= 60) {
rtc.second = 0;
if(++rtc.minute >= 60) {
rtc.minute = 0;
if(rtc.hourMode == 0 && ++rtc.hour >= 12) {
rtc.hour = 0;
rtc.meridian++;
}
if(rtc.hourMode == 1 && ++rtc.hour >= 24) {
rtc.hour = 0;
rtc.meridian = rtc.hour >= 12;
}
if((rtc.hourMode == 0 && rtc.hour == 0 && rtc.meridian == 0)
|| (rtc.hourMode == 1 && rtc.hour == 0)
) {
uint days[12] = {31, 28, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31};
if(rtc.leapYear == 0) days[1] = 29; //extra day in February for leap years
if(++rtc.day > days[(rtc.month - 1) % 12]) {
rtc.day = 1;
if(++rtc.month > 12) {
rtc.month = 1;
rtc.leapYear++;
if(++rtc.year >= 100) {
rtc.year = 0;
}
}
}
}
}
}
}
auto Cartridge::TAMA::read(uint16 address) -> uint8 {
if((address & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom.read(address.bits(0,13));
}
if((address & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom.read(io.rom.bank << 14 | address.bits(0,13));
}
if((address & 0xe001) == 0xa000) { //$a000-bfff (even)
if(io.select == 0x0a) {
return 0xf0 | io.ready;
}
if(io.mode == 0 || io.mode == 1) {
if(io.select == 0x0c) {
return 0xf0 | io.output.bits(0,3);
}
if(io.select == 0x0d) {
return 0xf0 | io.output.bits(4,7);
}
}
if(io.mode == 2 || io.mode == 4) {
if(io.select == 0x0c || io.select == 0x0d) {
uint4 data;
if(rtc.index == 0) data = rtc.minute % 10;
if(rtc.index == 1) data = rtc.minute / 10;
if(rtc.index == 2) data = rtc.hour % 10;
if(rtc.index == 3) data = rtc.hour / 10;
if(rtc.index == 4) data = rtc.day / 10;
if(rtc.index == 5) data = rtc.day % 10;
if(rtc.index == 6) data = rtc.month / 10;
if(rtc.index == 7) data = rtc.month % 10;
rtc.index++;
return 0xf0 | data;
}
}
return 0xff;
}
if((address & 0xe001) == 0xa001) { //$a000-bfff (odd)
return 0xff;
}
return 0xff;
}
auto Cartridge::TAMA::write(uint16 address, uint8 data) -> void {
auto toBCD = [](uint8 data) -> uint8 { return (data / 10) * 16 + (data % 10); };
auto fromBCD = [](uint8 data) -> uint8 { return (data / 16) * 10 + (data % 16); };
if((address & 0xe001) == 0xa000) { //$a000-bfff (even)
if(io.select == 0x00) {
io.rom.bank.bits(0,3) = data.bits(0,3);
}
if(io.select == 0x01) {
io.rom.bank.bit(4) = data.bit(0);
}
if(io.select == 0x04) {
io.input.bits(0,3) = data.bits(0,3);
}
if(io.select == 0x05) {
io.input.bits(4,7) = data.bits(0,3);
}
if(io.select == 0x06) {
io.index.bit(4) = data.bit(0);
io.mode = data.bits(1,3);
}
if(io.select == 0x07) {
io.index.bits(0,3) = data.bits(0,3);
if(io.mode == 0) {
cartridge.ram.write(io.index, io.input);
}
if(io.mode == 1) {
io.output = cartridge.ram.read(io.index);
}
if(io.mode == 2 && io.index == 0x04) {
rtc.minute = fromBCD(io.input);
}
if(io.mode == 2 && io.index == 0x05) {
rtc.hour = fromBCD(io.input);
rtc.meridian = rtc.hour >= 12;
}
if(io.mode == 4 && io.index == 0x00 && io.input.bits(0,3) == 0x7) {
uint8 day = toBCD(rtc.day);
day.bits(0,3) = io.input.bits(4,7);
rtc.day = fromBCD(day);
}
if(io.mode == 4 && io.index == 0x00 && io.input.bits(0,3) == 0x8) {
uint8 day = toBCD(rtc.day);
day.bits(4,7) = io.input.bits(4,7);
rtc.day = fromBCD(day);
}
if(io.mode == 4 && io.index == 0x00 && io.input.bits(0,3) == 0x9) {
uint8 month = toBCD(rtc.month);
month.bits(0,3) = io.input.bits(4,7);
rtc.month = fromBCD(month);
}
if(io.mode == 4 && io.index == 0x00 && io.input.bits(0,3) == 0xa) {
uint8 month = toBCD(rtc.month);
month.bits(4,7) = io.input.bits(4,7);
rtc.month = fromBCD(month);
}
if(io.mode == 4 && io.index == 0x00 && io.input.bits(0,3) == 0xb) {
uint8 year = toBCD(rtc.year);
year.bits(0,3) = io.input.bits(4,7);
rtc.year = fromBCD(year);
}
if(io.mode == 4 && io.index == 0x00 && io.input.bits(0,3) == 0xc) {
uint8 year = toBCD(rtc.year);
year.bits(4,7) = io.input.bits(4,7);
rtc.year = fromBCD(year);
}
if(io.mode == 4 && io.index == 0x02 && io.input.bits(0,3) == 0xa) {
rtc.hourMode = io.input.bit(4);
rtc.second = 0; //hack: unclear where this is really being set (if it is at all)
}
if(io.mode == 4 && io.index == 0x02 && io.input.bits(0,3) == 0xb) {
rtc.leapYear = data.bits(4,5);
}
if(io.mode == 4 && io.index == 0x02 && io.input.bits(0,3) == 0xe) {
rtc.test = io.input.bits(4,7);
}
if(io.mode == 2 && io.index == 0x06) {
rtc.index = 0;
}
}
return;
}
if((address & 0xe001) == 0xa001) { //$a000-bfff (odd)
io.select = data.bits(0,3);
if(io.select == 0x0a) {
io.ready = true;
}
return;
}
}
auto Cartridge::TAMA::power() -> void {
io = {};
}
auto Cartridge::TAMA::serialize(serializer& s) -> void {
s.integer(io.ready);
s.integer(io.select);
s.integer(io.mode);
s.integer(io.index);
s.integer(io.input);
s.integer(io.output);
s.integer(io.rom.bank);
s.integer(rtc.year);
s.integer(rtc.month);
s.integer(rtc.day);
s.integer(rtc.hour);
s.integer(rtc.minute);
s.integer(rtc.second);
s.integer(rtc.meridian);
s.integer(rtc.leapYear);
s.integer(rtc.hourMode);
s.integer(rtc.test);
}

View File

@@ -0,0 +1,33 @@
struct TAMA : Mapper {
auto second() -> void;
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
struct IO {
uint1 ready;
uint4 select;
uint3 mode;
uint5 index;
uint8 input;
uint8 output;
struct ROM {
uint5 bank;
} rom;
} io;
struct RTC {
uint8 year; //0 - 99
uint8 month; //1 - 12
uint8 day; //1 - 31
uint8 hour; //0 - 23
uint8 minute; //0 - 59
uint8 second; //0 - 59
uint1 meridian; //0 = AM; 1 = PM
uint2 leapYear; //0 = leap year; 1-3 = non-leap year
uint1 hourMode; //0 = 12-hour; 1 = 24-hour
uint4 test;
uint8 index;
} rtc;
} tama;

View File

@@ -123,13 +123,6 @@ auto CPU::power() -> void {
for(auto& n : wram) n = 0x00;
for(auto& n : hram) n = 0x00;
r[PC] = 0x0000;
r[SP] = 0x0000;
r[AF] = 0x0000;
r[BC] = 0x0000;
r[DE] = 0x0000;
r[HL] = 0x0000;
memory::fill(&status, sizeof(Status));
status.dmaCompleted = true;
status.wramBank = 1;

View File

@@ -17,13 +17,13 @@ struct CPU : Processor::LR35902, Thread, MMIO {
auto writeIO(uint16 addr, uint8 data) -> void;
//memory.cpp
auto io() -> void override;
auto idle() -> void override;
auto read(uint16 addr) -> uint8 override;
auto write(uint16 addr, uint8 data) -> void override;
auto cycleEdge() -> void;
auto readDMA(uint16 addr) -> uint8;
auto writeDMA(uint16 addr, uint8 data) -> void;
auto readDebugger(uint16 addr) -> uint8;
auto readDebugger(uint16 addr) -> uint8 override;
//timing.cpp
auto step(uint clocks) -> void;

View File

@@ -1,4 +1,4 @@
auto CPU::io() -> void {
auto CPU::idle() -> void {
cycleEdge();
step(4);
}

View File

@@ -5,7 +5,7 @@
auto CPU::step(uint clocks) -> void {
for(auto n : range(clocks)) {
if(++status.clock == 0) {
cartridge.mbc3.second();
cartridge.second();
}
//4MHz / N(hz) - 1 = mask
@@ -76,8 +76,8 @@ auto CPU::hblank() -> void {
if(status.dmaMode == 1 && status.dmaLength && ppu.status.ly < 144) {
for(auto n : range(16)) {
writeDMA(status.dmaTarget++, readDMA(status.dmaSource++));
status.dmaLength--;
if(n & 1) step(1 << status.speedDouble);
}
step(8 << status.speedDouble);
status.dmaLength -= 16;
}
}

View File

@@ -3,7 +3,7 @@ GameBoyColorInterface::GameBoyColorInterface() {
information.name = "Game Boy Color";
information.overscan = false;
media.append({ID::GameBoyColor, "Game Boy Color", "gb"});
media.append({ID::GameBoyColor, "Game Boy Color", "gbc"});
}
auto GameBoyColorInterface::videoColors() -> uint32 {

View File

@@ -19,6 +19,9 @@ Interface::Interface() {
device.inputs.append({0, "A" });
device.inputs.append({0, "Select"});
device.inputs.append({0, "Start" });
device.inputs.append({1, "X-axis"});
device.inputs.append({1, "Y-axis"});
device.inputs.append({2, "Rumble"});
hardwarePort.devices.append(device);
}
@@ -33,15 +36,8 @@ auto Interface::title() -> string {
return cartridge.title();
}
auto Interface::videoResolution() -> VideoSize {
return {160, 144};
}
auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize {
uint w = 160;
uint h = 144;
uint m = min(width / w, height / h);
return {w * m, h * m};
auto Interface::videoResolution() -> VideoResolution {
return {160, 144, 160, 144, 1.0};
}
auto Interface::loaded() -> bool {

View File

@@ -23,8 +23,7 @@ struct Interface : Emulator::Interface {
auto manifest() -> string override;
auto title() -> string override;
auto videoResolution() -> VideoSize override;
auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
auto videoResolution() -> VideoResolution override;
auto loaded() -> bool override;
auto sha256() -> string override;

View File

@@ -37,7 +37,6 @@ auto PPU::readTileCGB(bool select, uint x, uint y, uint& attr, uint& data) -> vo
auto PPU::scanlineCGB() -> void {
px = 0;
if(!enabled()) return;
const uint Height = (status.obSize == 0 ? 8 : 16);
sprites = 0;
@@ -69,24 +68,22 @@ auto PPU::runCGB() -> void {
ob.priority = 0;
uint color = 0x7fff;
if(enabled()) {
runBackgroundCGB();
if(status.windowDisplayEnable) runWindowCGB();
if(status.obEnable) runObjectsCGB();
runBackgroundCGB();
if(status.windowDisplayEnable) runWindowCGB();
if(status.obEnable) runObjectsCGB();
if(ob.palette == 0) {
color = bg.color;
} else if(bg.palette == 0) {
color = ob.color;
} else if(status.bgEnable == 0) {
color = ob.color;
} else if(bg.priority) {
color = bg.color;
} else if(ob.priority) {
color = ob.color;
} else {
color = bg.color;
}
if(ob.palette == 0) {
color = bg.color;
} else if(bg.palette == 0) {
color = ob.color;
} else if(status.bgEnable == 0) {
color = ob.color;
} else if(bg.priority) {
color = bg.color;
} else if(ob.priority) {
color = ob.color;
} else {
color = bg.color;
}
uint32* output = screen + status.ly * 160 + px++;

View File

@@ -19,7 +19,6 @@ auto PPU::readTileDMG(bool select, uint x, uint y, uint& data) -> void {
auto PPU::scanlineDMG() -> void {
px = 0;
if(!enabled()) return;
const uint Height = (status.obSize == 0 ? 8 : 16);
sprites = 0;
@@ -60,20 +59,18 @@ auto PPU::runDMG() -> void {
ob.palette = 0;
uint color = 0;
if(enabled()) {
if(status.bgEnable) runBackgroundDMG();
if(status.windowDisplayEnable) runWindowDMG();
if(status.obEnable) runObjectsDMG();
if(status.bgEnable) runBackgroundDMG();
if(status.windowDisplayEnable) runWindowDMG();
if(status.obEnable) runObjectsDMG();
if(ob.palette == 0) {
color = bg.color;
} else if(bg.palette == 0) {
color = ob.color;
} else if(ob.priority) {
color = ob.color;
} else {
color = bg.color;
}
if(ob.palette == 0) {
color = bg.color;
} else if(bg.palette == 0) {
color = ob.color;
} else if(ob.priority) {
color = ob.color;
} else {
color = bg.color;
}
uint32* output = screen + status.ly * 160 + px++;

View File

@@ -113,7 +113,8 @@ auto PPU::writeIO(uint16 addr, uint8 data) -> void {
}
if(addr == 0xff40) { //LCDC
if(!status.displayEnable && (data & 0x80)) {
if(status.displayEnable && !data.bit(7)) {
status.mode = 0;
status.ly = 0;
status.lx = 0;

View File

@@ -8,39 +8,45 @@ PPU ppu;
#include "cgb.cpp"
#include "serialization.cpp"
auto PPU::enabled() const -> bool { return status.displayEnable; }
auto PPU::Enter() -> void {
while(true) scheduler.synchronize(), ppu.main();
}
auto PPU::main() -> void {
if(!status.displayEnable) {
for(uint n : range(160 * 144)) screen[n] = Model::GameBoy() ? 0 : 0x7fff;
Thread::step(154 * 456);
synchronize(cpu);
scheduler.exit(Scheduler::Event::Frame);
return;
}
status.lx = 0;
if(Model::SuperGameBoy()) superGameBoy->lcdScanline();
if(status.ly <= 143) {
mode(2);
status.mode = 2;
scanline();
step(92);
mode(3);
status.mode = 3;
for(auto n : range(160)) {
run();
step(1);
}
mode(0);
if(enabled()) cpu.hblank();
status.mode = 0;
cpu.hblank();
step(204);
} else {
mode(1);
status.mode = 1;
step(456);
}
status.ly++;
if(status.ly == 144) {
if(enabled()) cpu.raise(CPU::Interrupt::Vblank);
cpu.raise(CPU::Interrupt::Vblank);
scheduler.exit(Scheduler::Event::Frame);
}
@@ -49,10 +55,6 @@ auto PPU::main() -> void {
}
}
auto PPU::mode(uint mode) -> void {
status.mode = mode;
}
auto PPU::stat() -> void {
bool irq = status.irq;

View File

@@ -1,9 +1,6 @@
struct PPU : Thread, MMIO {
auto enabled() const -> bool;
static auto Enter() -> void;
auto main() -> void;
auto mode(uint) -> void;
auto stat() -> void;
auto coincidence() -> bool;
auto refresh() -> void;

View File

@@ -1,4 +1,4 @@
processors += arm
processors += arm7tdmi
objects += gba-memory gba-interface gba-system
objects += gba-cartridge gba-player

View File

@@ -2,6 +2,7 @@
namespace GameBoyAdvance {
APU apu;
#include "io.cpp"
#include "square.cpp"
#include "square1.cpp"
@@ -11,7 +12,6 @@ namespace GameBoyAdvance {
#include "sequencer.cpp"
#include "fifo.cpp"
#include "serialization.cpp"
APU apu;
auto APU::Enter() -> void {
while(true) scheduler.synchronize(), apu.main();
@@ -65,7 +65,7 @@ auto APU::main() -> void {
if(regs.bias.amplitude == 2) lsample &= ~3, rsample &= ~3; //7-bit
if(regs.bias.amplitude == 3) lsample &= ~7, rsample &= ~7; //6-bit
if(cpu.regs.mode == CPU::Registers::Mode::Stop) lsample = 0, rsample = 0;
if(cpu.stopped()) lsample = 0, rsample = 0;
stream->sample((lsample << 5) / 32768.0, (rsample << 5) / 32768.0);
}
@@ -75,10 +75,10 @@ auto APU::step(uint clocks) -> void {
}
auto APU::power() -> void {
create(APU::Enter, 16'777'216);
create(APU::Enter, system.frequency());
stream = Emulator::audio.createStream(2, frequency() / 64.0);
stream->addLowPassFilter(20000.0, 3);
stream->addHighPassFilter(20.0, 3);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
clock = 0;
square1.power();

View File

@@ -1,8 +1,6 @@
struct APU : Thread, IO {
shared_pointer<Emulator::Stream> stream;
#include "registers.hpp"
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void;
@@ -16,6 +14,165 @@ struct APU : Thread, IO {
auto serialize(serializer&) -> void;
uint clock;
struct Registers {
struct SoundBias {
uint10 level;
uint2 amplitude;
} bias;
} regs;
struct Sweep {
uint3 shift;
uint1 direction;
uint3 frequency;
uint1 enable;
uint1 negate;
uint3 period;
};
struct Envelope {
uint3 frequency;
uint1 direction;
uint4 volume;
uint3 period;
auto dacEnable() const -> bool { return volume || direction; }
};
struct Square {
Envelope envelope;
uint1 enable;
uint6 length;
uint2 duty;
uint11 frequency;
uint1 counter;
uint1 initialize;
int shadowfrequency;
uint1 signal;
uint4 output;
uint period;
uint3 phase;
uint4 volume;
auto run() -> void;
auto clocklength() -> void;
auto clockenvelope() -> void;
};
struct Square1 : Square {
Sweep sweep;
auto runsweep(bool update) -> void;
auto clocksweep() -> void;
auto read(uint addr) const -> uint8;
auto write(uint addr, uint8 byte) -> void;
auto power() -> void;
} square1;
struct Square2 : Square {
auto read(uint addr) const -> uint8;
auto write(uint addr, uint8 byte) -> void;
auto power() -> void;
} square2;
struct Wave {
uint1 mode;
uint1 bank;
uint1 dacenable;
uint8 length;
uint3 volume;
uint11 frequency;
uint1 counter;
uint1 initialize;
uint4 pattern[2 * 32];
uint1 enable;
uint4 output;
uint5 patternaddr;
uint1 patternbank;
uint4 patternsample;
uint period;
auto run() -> void;
auto clocklength() -> void;
auto read(uint addr) const -> uint8;
auto write(uint addr, uint8 byte) -> void;
auto readram(uint addr) const -> uint8;
auto writeram(uint addr, uint8 byte) -> void;
auto power() -> void;
} wave;
struct Noise {
Envelope envelope;
uint6 length;
uint3 divisor;
uint1 narrowlfsr;
uint4 frequency;
uint1 counter;
uint1 initialize;
uint1 enable;
uint15 lfsr;
uint4 output;
uint period;
uint4 volume;
auto divider() const -> uint;
auto run() -> void;
auto clocklength() -> void;
auto clockenvelope() -> void;
auto read(uint addr) const -> uint8;
auto write(uint addr, uint8 byte) -> void;
auto power() -> void;
} noise;
struct Sequencer {
uint2 volume;
uint3 lvolume;
uint3 rvolume;
uint1 lenable[4];
uint1 renable[4];
uint1 masterenable;
uint12 base;
uint3 step;
int16 lsample;
int16 rsample;
uint10 loutput;
uint10 routput;
auto sample() -> void;
auto read(uint addr) const -> uint8;
auto write(uint addr, uint8 byte) -> void;
auto power() -> void;
} sequencer;
struct FIFO {
int8 samples[32];
int8 active;
int8 output;
uint5 rdoffset;
uint5 wroffset;
uint6 size;
uint1 volume; //0 = 50%, 1 = 100%
uint1 lenable;
uint1 renable;
uint1 timer;
auto sample() -> void;
auto read() -> void;
auto write(int8 byte) -> void;
auto reset() -> void;
auto power() -> void;
} fifo[2];
};
extern APU apu;

View File

@@ -1,158 +0,0 @@
struct Registers {
struct SoundBias {
uint10 level;
uint2 amplitude;
} bias;
} regs;
struct Sweep {
uint3 shift;
uint1 direction;
uint3 frequency;
uint1 enable;
uint1 negate;
uint3 period;
};
struct Envelope {
uint3 frequency;
uint1 direction;
uint4 volume;
uint3 period;
auto dacEnable() const -> bool { return volume || direction; }
};
struct Square {
Envelope envelope;
uint1 enable;
uint6 length;
uint2 duty;
uint11 frequency;
uint1 counter;
uint1 initialize;
int shadowfrequency;
uint1 signal;
uint4 output;
uint period;
uint3 phase;
uint4 volume;
auto run() -> void;
auto clocklength() -> void;
auto clockenvelope() -> void;
};
struct Square1 : Square {
Sweep sweep;
auto runsweep(bool update) -> void;
auto clocksweep() -> void;
auto read(uint addr) const -> uint8;
auto write(uint addr, uint8 byte) -> void;
auto power() -> void;
} square1;
struct Square2 : Square {
auto read(uint addr) const -> uint8;
auto write(uint addr, uint8 byte) -> void;
auto power() -> void;
} square2;
struct Wave {
uint1 mode;
uint1 bank;
uint1 dacenable;
uint8 length;
uint3 volume;
uint11 frequency;
uint1 counter;
uint1 initialize;
uint4 pattern[32];
uint1 enable;
uint4 output;
uint4 patternaddr;
uint1 patternbank;
uint4 patternsample;
uint period;
auto run() -> void;
auto clocklength() -> void;
auto read(uint addr) const -> uint8;
auto write(uint addr, uint8 byte) -> void;
auto readram(uint addr) const -> uint8;
auto writeram(uint addr, uint8 byte) -> void;
auto power() -> void;
} wave;
struct Noise {
Envelope envelope;
uint6 length;
uint3 divisor;
uint1 narrowlfsr;
uint4 frequency;
uint1 counter;
uint1 initialize;
uint1 enable;
uint15 lfsr;
uint4 output;
uint period;
uint4 volume;
auto divider() const -> uint;
auto run() -> void;
auto clocklength() -> void;
auto clockenvelope() -> void;
auto read(uint addr) const -> uint8;
auto write(uint addr, uint8 byte) -> void;
auto power() -> void;
} noise;
struct Sequencer {
uint2 volume;
uint3 lvolume;
uint3 rvolume;
uint1 lenable[4];
uint1 renable[4];
uint1 masterenable;
uint12 base;
uint3 step;
int16 lsample;
int16 rsample;
uint10 loutput;
uint10 routput;
auto sample() -> void;
auto read(uint addr) const -> uint8;
auto write(uint addr, uint8 byte) -> void;
auto power() -> void;
} sequencer;
struct FIFO {
int8 samples[32];
int8 active;
int8 output;
uint5 rdoffset;
uint5 wroffset;
uint6 size;
uint1 volume; //0 = 50%, 1 = 100%
uint1 lenable;
uint1 renable;
uint1 timer;
auto sample() -> void;
auto read() -> void;
auto write(int8 byte) -> void;
auto reset() -> void;
auto power() -> void;
} fifo[2];

View File

@@ -1,7 +1,7 @@
auto APU::Wave::run() -> void {
if(period && --period == 0) {
period = 1 * (2048 - frequency);
patternsample = pattern[patternbank * 16 + patternaddr++];
patternsample = pattern[patternbank << 5 | patternaddr++];
if(patternaddr == 0) patternbank ^= mode;
}
@@ -66,14 +66,14 @@ auto APU::Wave::write(uint addr, uint8 byte) -> void {
auto APU::Wave::readram(uint addr) const -> uint8 {
uint8 byte = 0;
byte |= pattern[addr * 2 + 0] << 0;
byte |= pattern[addr * 2 + 1] << 4;
byte |= pattern[!bank << 5 | addr << 1 | 0] << 4;
byte |= pattern[!bank << 5 | addr << 1 | 1] << 0;
return byte;
}
auto APU::Wave::writeram(uint addr, uint8 byte) -> void {
pattern[addr * 2 + 0] = byte >> 0;
pattern[addr * 2 + 1] = byte >> 4;
pattern[!bank << 5 | addr << 1 | 0] = byte >> 4;
pattern[!bank << 5 | addr << 1 | 1] = byte >> 0;
}
auto APU::Wave::power() -> void {

View File

@@ -2,12 +2,12 @@
namespace GameBoyAdvance {
Cartridge cartridge;
#include "mrom.cpp"
#include "sram.cpp"
#include "eeprom.cpp"
#include "flash.cpp"
#include "serialization.cpp"
Cartridge cartridge;
Cartridge::Cartridge() {
mrom.data = new uint8[mrom.size = 32 * 1024 * 1024];

View File

@@ -1,26 +1,26 @@
auto CPU::_idle() -> void {
auto CPU::sleep() -> void {
prefetchStep(1);
}
auto CPU::_read(uint mode, uint32 addr) -> uint32 {
uint wait = this->wait(mode, addr);
auto CPU::get(uint mode, uint32 addr) -> uint32 {
uint clocks = _wait(mode, addr);
uint word = pipeline.fetch.instruction;
if(addr >= 0x1000'0000) {
prefetchStep(wait);
prefetchStep(clocks);
} else if(addr & 0x0800'0000) {
if(mode & Prefetch && regs.wait.control.prefetch) {
if(mode & Prefetch && wait.prefetch) {
prefetchSync(addr);
word = prefetchRead();
if(mode & Word) word |= prefetchRead() << 16;
} else {
if(!active.dma) prefetchWait();
step(wait - 1);
if(!context.dmaActive) prefetchWait();
step(clocks - 1);
word = cartridge.read(mode, addr);
step(1);
}
} else {
prefetchStep(wait - 1);
prefetchStep(clocks - 1);
if(addr < 0x0200'0000) word = bios.read(mode, addr);
else if(addr < 0x0300'0000) word = readEWRAM(mode, addr);
else if(addr < 0x0400'0000) word = readIWRAM(mode, addr);
@@ -35,17 +35,17 @@ auto CPU::_read(uint mode, uint32 addr) -> uint32 {
return word;
}
auto CPU::_write(uint mode, uint32 addr, uint32 word) -> void {
uint wait = this->wait(mode, addr);
auto CPU::set(uint mode, uint32 addr, uint32 word) -> void {
uint clocks = _wait(mode, addr);
if(addr >= 0x1000'0000) {
prefetchStep(wait);
prefetchStep(clocks);
} else if(addr & 0x0800'0000) {
if(!active.dma) prefetchWait();
step(wait);
if(!context.dmaActive) prefetchWait();
step(clocks);
cartridge.write(mode, addr, word);
} else {
prefetchStep(wait);
prefetchStep(clocks);
if(addr < 0x0200'0000);
else if(addr < 0x0300'0000) writeEWRAM(mode, addr, word);
else if(addr < 0x0400'0000) writeIWRAM(mode, addr, word);
@@ -57,17 +57,17 @@ auto CPU::_write(uint mode, uint32 addr, uint32 word) -> void {
}
}
auto CPU::wait(uint mode, uint32 addr) -> uint {
auto CPU::_wait(uint mode, uint32 addr) -> uint {
if(addr >= 0x1000'0000) return 1; //unmapped
if(addr < 0x0200'0000) return 1;
if(addr < 0x0300'0000) return (16 - regs.memory.control.ewramwait) * (mode & Word ? 2 : 1);
if(addr < 0x0300'0000) return (16 - memory.ewramWait) * (mode & Word ? 2 : 1);
if(addr < 0x0500'0000) return 1;
if(addr < 0x0700'0000) return mode & Word ? 2 : 1;
if(addr < 0x0800'0000) return 1;
static uint timings[] = {5, 4, 3, 9};
uint n = timings[regs.wait.control.nwait[addr >> 25 & 3]];
uint s = regs.wait.control.swait[addr >> 25 & 3];
uint n = timings[wait.nwait[addr >> 25 & 3]];
uint s = wait.swait[addr >> 25 & 3];
switch(addr & 0x0e00'0000) {
case 0x0800'0000: s = s ? 2 : 3; break;

View File

@@ -2,172 +2,100 @@
namespace GameBoyAdvance {
CPU cpu;
#include "prefetch.cpp"
#include "bus.cpp"
#include "io.cpp"
#include "memory.cpp"
#include "dma.cpp"
#include "timer.cpp"
#include "keypad.cpp"
#include "serialization.cpp"
CPU cpu;
CPU::CPU() {
iwram = new uint8[ 32 * 1024];
ewram = new uint8[256 * 1024];
regs.dma[0].source.resize(27); regs.dma[0].run.source.resize(27);
regs.dma[0].target.resize(27); regs.dma[0].run.target.resize(27);
regs.dma[0].length.resize(14); regs.dma[0].run.length.resize(14);
regs.dma[1].source.resize(28); regs.dma[1].run.source.resize(28);
regs.dma[1].target.resize(27); regs.dma[1].run.target.resize(27);
regs.dma[1].length.resize(14); regs.dma[1].run.length.resize(14);
regs.dma[2].source.resize(28); regs.dma[2].run.source.resize(28);
regs.dma[2].target.resize(27); regs.dma[2].run.target.resize(27);
regs.dma[2].length.resize(14); regs.dma[2].run.length.resize(14);
regs.dma[3].source.resize(28); regs.dma[3].run.source.resize(28);
regs.dma[3].target.resize(28); regs.dma[3].run.target.resize(28);
regs.dma[3].length.resize(16); regs.dma[3].run.length.resize(16);
}
CPU::~CPU() {
delete[] iwram;
delete[] ewram;
}
auto CPU::Enter() -> void {
while(true) scheduler.synchronize(), cpu.main();
}
auto CPU::main() -> void {
#if defined(DEBUG)
if(crash) {
print(cpsr().t ? disassemble_thumb_instruction(pipeline.execute.address)
: disassemble_arm_instruction(pipeline.execute.address), "\n");
print(disassemble_registers(), "\n");
print("Executed: ", instructions, "\n");
while(true) step(frequency);
}
#endif
ARM7TDMI::irq = irq.ime && (irq.enable & irq.flag);
processor.irqline = regs.ime && (regs.irq.enable & regs.irq.flag);
if(regs.mode == Registers::Mode::Stop) {
if(!(regs.irq.enable & regs.irq.flag & Interrupt::Keypad)) {
syncStep(16); //STOP does not advance timers
} else {
regs.mode = Registers::Mode::Normal;
if(stopped()) {
if(!(irq.enable & irq.flag & Interrupt::Keypad)) {
Thread::step(16);
synchronize(cpu);
synchronize(apu);
}
return;
context.stopped = false;
}
dmaRun();
if(regs.mode == Registers::Mode::Halt) {
if(!(regs.irq.enable & regs.irq.flag)) {
step(16);
} else {
regs.mode = Registers::Mode::Normal;
if(halted()) {
if(!(irq.enable & irq.flag)) {
return step(16);
}
return;
context.halted = false;
}
exec();
instruction();
}
auto CPU::step(uint clocks) -> void {
timerStep(clocks);
syncStep(clocks);
}
dma[0].waiting = max(0, dma[0].waiting - (int)clocks);
dma[1].waiting = max(0, dma[1].waiting - (int)clocks);
dma[2].waiting = max(0, dma[2].waiting - (int)clocks);
dma[3].waiting = max(0, dma[3].waiting - (int)clocks);
if(!context.dmaActive) {
context.dmaActive = true;
while(dma[0].run() | dma[1].run() | dma[2].run() | dma[3].run());
context.dmaActive = false;
}
for(auto _ : range(clocks)) {
timer[0].run();
timer[1].run();
timer[2].run();
timer[3].run();
context.clock++;
}
auto CPU::syncStep(uint clocks) -> void {
Thread::step(clocks);
synchronize(ppu);
synchronize(apu);
}
auto CPU::keypadRun() -> void {
//lookup table to convert button indexes to Emulator::Interface indexes
static const uint lookup[] = {5, 4, 8, 9, 3, 2, 0, 1, 7, 6};
if(!regs.keypad.control.enable) return;
bool test = regs.keypad.control.condition; //0 = OR, 1 = AND
for(auto n : range(10)) {
if(!regs.keypad.control.flag[n]) continue;
bool input = platform->inputPoll(0, 0, lookup[n]);
if(regs.keypad.control.condition == 0) test |= input;
if(regs.keypad.control.condition == 1) test &= input;
}
if(test) regs.irq.flag |= Interrupt::Keypad;
}
auto CPU::power() -> void {
create(CPU::Enter, 16'777'216);
ARM7TDMI::power();
create(CPU::Enter, system.frequency());
ARM::power();
for(auto n : range( 32 * 1024)) iwram[n] = 0;
for(auto n : range(256 * 1024)) ewram[n] = 0;
for(auto& dma : regs.dma) {
dma.source = 0;
dma.target = 0;
dma.length = 0;
dma.data = 0;
dma.control.targetmode = 0;
dma.control.sourcemode = 0;
dma.control.repeat = 0;
dma.control.size = 0;
dma.control.drq = 0;
dma.control.timingmode = 0;
dma.control.irq = 0;
dma.control.enable = 0;
dma.pending = 0;
dma.run.target = 0;
dma.run.source = 0;
dma.run.length = 0;
}
for(auto& timer : regs.timer) {
timer.period = 0;
timer.reload = 0;
timer.pending = false;
timer.control.frequency = 0;
timer.control.cascade = 0;
timer.control.irq = 0;
timer.control.enable = 0;
}
regs.serial = {};
for(auto& flag : regs.keypad.control.flag) flag = 0;
regs.keypad.control.enable = 0;
regs.keypad.control.condition = 0;
regs.joybus = {};
regs.ime = 0;
regs.irq.enable = 0;
regs.irq.flag = 0;
for(auto& nwait : regs.wait.control.nwait) nwait = 0;
for(auto& swait : regs.wait.control.swait) swait = 0;
regs.wait.control.phi = 0;
regs.wait.control.prefetch = 0;
regs.wait.control.gametype = 0; //0 = GBA, 1 = GBC
regs.memory.control.disable = 0;
regs.memory.control.unknown1 = 0;
regs.memory.control.ewram = 1;
regs.memory.control.ewramwait = 13;
regs.memory.control.unknown2 = 0;
regs.postboot = 0;
regs.mode = Registers::Mode::Normal;
regs.clock = 0;
for(auto& byte : iwram) byte = 0x00;
for(auto& byte : ewram) byte = 0x00;
for(auto n : range(4)) dma[n] = {n};
for(auto n : range(4)) timer[n] = {n};
serial = {};
keypad = {};
joybus = {};
irq = {};
wait = {};
memory = {};
prefetch = {};
prefetch.wait = 1;
context = {};
pending.dma.vblank = 0;
pending.dma.hblank = 0;
pending.dma.hdma = 0;
dma[0].source.resize(27); dma[0].latch.source.resize(27);
dma[0].target.resize(27); dma[0].latch.target.resize(27);
dma[0].length.resize(14); dma[0].latch.length.resize(14);
active.dma = false;
dma[1].source.resize(28); dma[1].latch.source.resize(28);
dma[1].target.resize(27); dma[1].latch.target.resize(27);
dma[1].length.resize(14); dma[1].latch.length.resize(14);
dma[2].source.resize(28); dma[2].latch.source.resize(28);
dma[2].target.resize(27); dma[2].latch.target.resize(27);
dma[2].length.resize(14); dma[2].latch.length.resize(14);
dma[3].source.resize(28); dma[3].latch.source.resize(28);
dma[3].target.resize(28); dma[3].latch.target.resize(28);
dma[3].length.resize(16); dma[3].latch.length.resize(16);
for(uint n = 0x0b0; n <= 0x0df; n++) bus.io[n] = this; //DMA
for(uint n = 0x100; n <= 0x10f; n++) bus.io[n] = this; //Timers
@@ -176,7 +104,7 @@ auto CPU::power() -> void {
for(uint n = 0x134; n <= 0x159; n++) bus.io[n] = this; //Serial
for(uint n = 0x200; n <= 0x209; n++) bus.io[n] = this; //System
for(uint n = 0x300; n <= 0x301; n++) bus.io[n] = this; //System
//0x080-0x083 mirrored via gba/memory/memory.cpp //System
//0x080-0x083 mirrored via gba/memory/memory.cpp //System
}
}

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