Compare commits

..
22 Commits
Author SHA1 Message Date
byuu 7dec0b2a3c Update to bsnes v014 release.
This version adds speed regulation, greatly improves PPU rendering, and increases speed by ~30% over the previous version.
Changelog:
    - Rewrote offset-per-tile mode emulation, should be correct now. Fixes Chrono Trigger, Contra III, Tetris Attack, etc.
    - Fixed a bug with HDMA occuring during interrupts. Fixes Tales of Phantasia souond test screen
    - Updated compiler to Visual Studio 2005, and enabled profile guided optimizations
    - Added conditional compilation of debugging functions (faster without them)
    - Added conditional compilation of core classes as pointers (allowing polymorphism) or objects (allowing inlining). The latter results in a speed increase
    - Small fixes to BG and OAM rendering routines
    - Corrected sprite tile bounds wrapping
    - Corrected sprite rendering in hires video modes
    - Rewrote color add/sub routines, should be correct now. Fixes Illusion of Gaia menu, etc.
    - Optimized video blitting routines, will temporarilly break mixed video mode screenshots
    - Prevented selecting menu options via return key from being recognized as keypresses by the emulator
    - Added system speed regulation (60hz/NTSC or 50hz/PAL)! Many thanks to kode54, GIGO, and Richard Bannister for their assistance
I disabled the debugger and polymorphism, and enabled profile guided optimizations for this build, to maximize speed. The debugger and polymorphism can be re-enabled via uncommenting the respective #defines in src/base.h and recompiling, or bsnes v0.013 can be used. I may start releasing two separate builds in the future... not sure yet.
2005-11-12 16:49:26 +00:00
byuu f288280ceb Update to bsnes v013r02 release.
[No changelog available]
2005-10-25 23:25:28 +00:00
byuu c6c5f4669c Update to bsnes v013 release.
- Greatly improved HDMA timing and accuracy with help from anomie and DMV27 -- fixes bugs in Energy Breaker and Street Fighter Alpha 2
    - Fixed a problem with color add/sub code -- fixes opening battle in Tales of Phantasia and clouds in Energy Breaker
    - Temporarily added DMV27's bugfix for the DSP KON register -- fixes sound in Der Langrisser, but this is not a hardware-accurate fix
    - Disabled VRAM writes outside of vblank -- fixes Hook, but breaks many PD ROMs and fan translations (Roto's BS Zelda hack, Gideon Zhi's Ys 4 translation, etc). I might add an option in the future to toggle this behavior, but for now these games will no longer work. Please keep in mind these games will not run properly on real SNES hardware, either.
    - Improved frameskipping code thanks to a suggestion from Richard Bannister
    - Misc. other code cleanups and improvements (notably in the color table generation code)
    - bsnes is now endian-safe and runs on Mac OS X
    - Added caching support for window clipping tables resulting in a slight speedup. Please let me know if you spot any errors as a result of this change.
2005-10-23 23:32:30 +00:00
byuu 397b9c4505 Update to bsnes v012 release.
Changelog:
    - Added S-DSP emulation
    - Added sound output support via DirectSound -- no sound buffering though, so sound is muted by default
    - Added option to record raw sound output to WAV files
    - Added multiple color adjustment filters to the video output
    - Added mode3/4 direct color support
    - Added mode7 direct color and mosaic support
    - Greatly improved mode7 rendering algorithm thanks to anomie
    - Fixed mode7 screen repitition and EXTBG effects
    - Greatly increased accuracy of NMI and IRQ timing, and emulated many newly discovered hardware quirks involving the two
    - A few speed improvements courtesy of Nach for profiling the code for me
I'm now looking for assistance with sound buffering. Specifically, I need help modifying the DirectSound code to allow the emulator to be ran between 50%-400% normal speed, while keeping the sound output relatively good. If you have experience with this and can help, please get in touch with me (setsunakun0 at hotmail dot com).
2005-10-02 00:38:34 +00:00
byuu 7e2cfb6d40 Update to bsnes v011 release.
- Fixed Mode 0 color palette index problem. Fixes ToP, DQ5, etc.
    - Improved LoROM memory mapper to support 32mbit images. Fixes Tokimeki Memorial, etc.
    - Added full S-DD1 support, SFA2 and Star Ocean are now playable. Special thanks to Andreas Naive
    - Updated BGnxOFS / Mode7 registers with anomie's latest findings
    - Added basic ROM mirroring support. Fixes copy protection issues in MMX, etc.
    - Rewrote string library to work better on gcc/linux
    - Cleaned up S-RTC/S-DD1 emulation to make way for future add-on chip emulation
    - Rewrote DMA code, now runs cycle-by-cycle
    - Rewrote HDMA code, now allows HDMA to be enabled mid-frame, fixes many games
    - Fixed a bug in Mode7 vertical screen flip mode. Fixes FF5 title screen, etc.
    - Greatly improved IRQ triggering. Fixes Der Langrisser, etc.
    - Added full support for open bus. This includes PPU1 and PPU2 open bus support
    - Modified CPU core back to cycle-based system. Slower, but improves debugger
    - Implemented temporary fix for debugger to handle new cycle-based cores
    - Modified CGRAM to ignore highest bit, since it is not used at all by the SNES, and is impossible to read on real hardware. Lowers memory usage by ~1.2mb
    - Added mostly accurate PAL timing support. This should increase compatibility by ~30% or so
    - More stuff I'm forgetting at the moment...
2005-08-26 20:38:00 +00:00
byuu 970dcea0ac Update to bsnes v010 release.
bsnes now supports SPC700 emulation (no DSP or sound support, however), and has greatly improved compatibility. It also now contains a keyboard-only joypad configuration tool.
2005-08-03 21:22:42 +00:00
byuu 402c146a53 Update to bsnes v009 release.
- Fixed non-interlaced display modes from not drawing every other frame
    - Changed OAM halve to skip every other scanline in 224-height modes
    - Updated renderer to properly support games that switch resolutions mid-frame
    - Fixed VRAM address remapping modes, fixes DQ3R, FF: MQ
    - Fixed a bug in main color window clipping affecting BGs
    - Added video color curve option, thanks to Overload for the idea + color table
    - Added vblank, FPS counter, and DDraw surface memory options to settings menu
    - Added fullscreen modes 640x480 and 1024x768 to video modes
    - Added option to toggle the menubar on and off by pressing the escape key
    - Mode3 was not rendering sprites
    - Priorities were wrong for modes 2-4, thanks to anomie for info
    - Fixed a serious bug in IRQ interrupts. May not be perfect, but helps many games
2005-06-26 11:07:38 +00:00
byuu a471c150c9 Update to bsnes v008 release.
moving the window + main color window clipping into the bg/oam/mode7 rendering routines themselves, I was able to greatly simplify the most complicated part of rendering: the final pass where color add/sub effects are applied. As a result, the new PPU core is not only ~35% faster (on graphics intensive screens, even faster on simpler screens), but more accurate as well. Awesome.
In celebration, I´m releasing bsnes v0.008. I can actually run all games I have at >60fps on my Athlon 1.67ghz PC. Probably not something to brag about, though ...
Oh, and I also updated the keyboard polling code to only capture keypresses if the main window has focus. I´ve been meaning to do this for the better part of a year now, but never got around to it.
If, for some reason, you still want to use the old renderer, you can uncomment the first line in src/ppu/bppu/bppu.h and recompile the emulator yourself. Or you can use v0.007a, I´ll leave it up for a bit.
2005-06-21 09:13:40 +00:00
byuu ea38ea2537 Update to bsnes v007a release.
[No changelog available]
2005-06-13 04:34:10 +00:00
byuu 09b326ae86 Update to bsnes v007 release.
I have done quite a bit, so I´ll try my best to recap most of the fixes since the last release...
    - HDMA was not running during DMA transfers
    - Emulator did not recognize any filetype other than .smc
    - Added configuration file support and imported my vector/string/config libraries into bsnes
    - Added option to use system RAM instead of video RAM for display, this can greatly increase speed on certain video cards
    - Increased speed by ~15% by adding 256x224 renderer (still very buggy when the SNES mixes video modes mid-frame)
    - mvn/mvp opcodes were not setting the DB register
    - Fixed joypad input in many games (Super Mario: All Stars, Dragon Quest III, etc.)
    - Major speedup with frakeskip option
    - Fixed default aspect ratio when emulator is first started
There´s probably a lot more, but that´s all I remember offhand... this release should be a lot closer to the quality of v0.005a, but still needs a bit more polishing.
2005-06-12 08:15:22 +00:00
byuu a60f667b25 Update to bsnes v006 release.
The rewrite is now complete. I finished adding frameskip, and fixed some crashing issues with loading multiple ROMs. I wrapped all malloc/free calls with memalloc/memfree, which are custom functions that log each call, and let you pass sprintf-style arguments to them to debug memory leaks. Don´t see any, nor have I noticed any in bsnes; but it makes a nice test tool anyway.
I found out today that the mvn/mvp instructions actually set the DB register to the destination bank. Weird. This fixed Final Fantasy V, Chrono Trigger, and Dragon Quest III (which now runs as a result) graphics. Not all of them, but quite a few. I had also previously been setting the M/X flags of the P register when xce was executed. I never anticipated a game using xce while in native mode to switch to... native mode... but apparently, Chrono Trigger does. And my code was incorrect. So now Chrono Trigger gets past the name select screen, but dies a few screens after still.
The only really major flaw I am aware of that bsnes v0.005 did not have is with the battle screens in Squaresoft games. I get horrible flickering and "crushed scanlines" every other frame on them, for some reason. I´ll try and track that down for the next release, but I didn´t want to hold up a new release any longer. As a result, I´ll leave up v0.005a for the time being until I get this problem fixed.
2005-05-13 12:03:02 +00:00
byuu 6f7cf1bd31 Update to bsnes v005a release.
[No changelog available]
2005-02-06 04:04:32 +00:00
byuu 8751bc433f Update to bsnes v005 release.
Released a new version of bsnes today. Version 0.0.005 is up on the main page now.
The changes since .004 wip10 include: DRAM timing fix (now occurs mid-opcode), memory address speed fix, added CPU cycle condition 4 to all opcodes that use it, and fixed a bug where DRAM refresh was not changing start positions every other scanline.
With the new version, Chrono Trigger should get a little further, and Final Fantasy 5 should get in-game now. The only problem FF5 seems to have is sprite corrution in the menus. I´m too busy with the CPU timing to mess around with the PPU right now, though. Also, the APU is not bridged to the CPU in this release, so compatibility is (still) really low.
After I get the APU working fairly well, I´ll (try and) start keeping a what´s new file so that people can know what all changed between public releases of the emulator without having to follow this page. Sorry for the inconvenience in the mean time.

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

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

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

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

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

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

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

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

[No archive available]
2004-10-14 00:00:00 +00:00
1734 changed files with 30957 additions and 283389 deletions

No files matched your search

-3
View File
@@ -1,3 +0,0 @@
purify/*.o
purify/purify
purify/analyze-gba
+139
View File
@@ -0,0 +1,139 @@
# Applies contrast adjust filter to video output when enabled
# Works by lowering the brightness of darker colors,
# while leaving brighter colors alone; thus reducing saturation
# (default = true)
snes.video_color_curve = true
# Selects color adjustment filter for video output
# 0 = Normal (no filter, rgb555)
# 1 = Grayscale mode (l5)
# 2 = VGA mode (rgb332)
# 3 = Genesis mode (rgb333)
# (default = 0)
snes.video_color_adjust_mode = 0
# Mutes SNES audio output when enabled
# (default = true)
snes.mute = false
# Regulate speed to 60hz (NTSC) / 50hz (PAL)
# (default = true)
system.regulate_speed = true
# Video mode
# 0 = 256x224w
# 1 = 512x448w
# 2 = 960x720w
# 3 = 640x480f
# 4 = 1024x768f
# (default = 1)
video.mode = 1
# Use Video RAM instead of System RAM
# (default = true)
video.use_vram = true
# Wait for vertical retrace when updating screen
# (default = false)
video.vblank = false
# Show framerate in window title
# (default = true)
gui.show_fps = true
# Joypad1 up
# (default = 0x26)
input.joypad1.up = 0x26
# Joypad1 down
# (default = 0x28)
input.joypad1.down = 0x28
# Joypad1 left
# (default = 0x25)
input.joypad1.left = 0x25
# Joypad1 right
# (default = 0x27)
input.joypad1.right = 0x27
# Joypad1 A
# (default = 0x58)
input.joypad1.a = 0x58
# Joypad1 B
# (default = 0x5a)
input.joypad1.b = 0x5a
# Joypad1 X
# (default = 0x53)
input.joypad1.x = 0x53
# Joypad1 Y
# (default = 0x41)
input.joypad1.y = 0x41
# Joypad1 L
# (default = 0x44)
input.joypad1.l = 0x44
# Joypad1 R
# (default = 0x43)
input.joypad1.r = 0x43
# Joypad1 select
# (default = 0x10)
input.joypad1.select = 0x10
# Joypad1 start
# (default = 0xd)
input.joypad1.start = 0xd
# Joypad2 up
# (default = 0x54)
input.joypad2.up = 0x54
# Joypad2 down
# (default = 0x47)
input.joypad2.down = 0x47
# Joypad2 left
# (default = 0x46)
input.joypad2.left = 0x46
# Joypad2 right
# (default = 0x48)
input.joypad2.right = 0x48
# Joypad2 A
# (default = 0x4b)
input.joypad2.a = 0x4b
# Joypad2 B
# (default = 0x4a)
input.joypad2.b = 0x4a
# Joypad2 X
# (default = 0x49)
input.joypad2.x = 0x49
# Joypad2 Y
# (default = 0x55)
input.joypad2.y = 0x55
# Joypad2 L
# (default = 0x4f)
input.joypad2.l = 0x4f
# Joypad2 R
# (default = 0x4c)
input.joypad2.r = 0x4c
# Joypad2 select
# (default = 0x5b)
input.joypad2.select = 0x5b
# Joypad2 start
# (default = 0x5d)
input.joypad2.start = 0x5d
BIN
View File
Binary file not shown.
-111
View File
@@ -1,111 +0,0 @@
include nall/Makefile
fc := fc
sfc := sfc
gb := gb
gba := gba
nds := nds
profile := accuracy
target := ethos
# options += debugger
# arch := win32
# console := true
# compiler
c := $(compiler) -std=gnu99
cpp := $(subst cc,++,$(compiler)) -std=gnu++0x
flags := -I. -O3 -fomit-frame-pointer
link := -s
objects := libco
# profile-guided optimization mode
# pgo := instrument
# pgo := optimize
ifeq ($(pgo),instrument)
flags += -fprofile-generate
link += -lgcov
else ifeq ($(pgo),optimize)
flags += -fprofile-use
endif
# platform
ifeq ($(platform),x)
flags += -march=native
link += -ldl -lX11 -lXext
else ifeq ($(platform),osx)
else ifeq ($(platform),win)
ifeq ($(arch),win32)
flags += -m32
link += -m32
endif
ifeq ($(console),true)
link += -mconsole
else
link += -mwindows
endif
link += -mthreads -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32 -lole32
link += -Wl,-enable-auto-import -Wl,-enable-runtime-pseudo-reloc
else
unknown_platform: help;
endif
ui := target-$(target)
# implicit rules
compile = \
$(strip \
$(if $(filter %.c,$<), \
$(c) $(flags) $1 -c $< -o $@, \
$(if $(filter %.cpp,$<), \
$(cpp) $(flags) $1 -c $< -o $@ \
) \
) \
)
%.o: $<; $(call compile)
all: build;
obj/libco.o: libco/libco.c libco/*
include $(ui)/Makefile
flags := $(flags) $(foreach o,$(call strupper,$(options)),-D$o)
# targets
clean:
-@$(call delete,obj/*.o)
-@$(call delete,obj/*.a)
-@$(call delete,obj/*.so)
-@$(call delete,obj/*.dylib)
-@$(call delete,obj/*.dll)
-@$(call delete,*.res)
-@$(call delete,*.pgd)
-@$(call delete,*.pgc)
-@$(call delete,*.ilk)
-@$(call delete,*.pdb)
-@$(call delete,*.manifest)
sync:
if [ -d ./libco ]; then rm -r ./libco; fi
if [ -d ./nall ]; then rm -r ./nall; fi
if [ -d ./ruby ]; then rm -r ./ruby; fi
if [ -d ./phoenix ]; then rm -r ./phoenix; fi
cp -r ../libco ./libco
cp -r ../nall ./nall
cp -r ../ruby ./ruby
cp -r ../phoenix ./phoenix
rm -r libco/doc
rm -r libco/test
rm -r nall/test
rm -r ruby/_test
rm -r phoenix/nall
rm -r phoenix/test
archive:
if [ -f bsnes.tar.xz ]; then rm bsnes.tar.xz; fi
tar -cJf bsnes.tar.xz `ls`
help:;
-9
View File
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="bsnes" version="1.0.0.0" processorArchitecture="*"/>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
</assembly>
-8
View File
@@ -1,8 +0,0 @@
[Desktop Entry]
Name=bsnes
Comment=SNES emulator
Exec=bsnes
Icon=bsnes
Terminal=false
Type=Application
Categories=Game;Emulator;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

-106582
View File
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
-137
View File
@@ -1,137 +0,0 @@
#ifndef EMULATOR_HPP
#define EMULATOR_HPP
namespace Emulator {
static const char Name[] = "bsnes";
static const char Version[] = "090";
static const char Author[] = "byuu";
static const char License[] = "GPLv3";
}
#include <nall/platform.hpp>
#include <nall/algorithm.hpp>
#include <nall/directory.hpp>
#include <nall/dl.hpp>
#include <nall/dsp.hpp>
#include <nall/endian.hpp>
#include <nall/file.hpp>
#include <nall/function.hpp>
#include <nall/priority-queue.hpp>
#include <nall/property.hpp>
#include <nall/random.hpp>
#include <nall/serializer.hpp>
#include <nall/sha256.hpp>
#include <nall/stdint.hpp>
#include <nall/string.hpp>
#include <nall/utility.hpp>
#include <nall/varint.hpp>
#include <nall/vector.hpp>
#include <nall/stream/memory.hpp>
#include <nall/stream/vector.hpp>
using namespace nall;
#include "interface.hpp"
//debugging function hook:
//no overhead (and no debugger invocation) if not compiled with -DDEBUGGER
//wraps testing of function to allow invocation without a defined callback
template<typename T> struct hook;
template<typename R, typename... P> struct hook<R (P...)> {
function<R (P...)> callback;
R operator()(P... p) const {
#if defined(DEBUGGER)
if(callback) return callback(std::forward<P>(p)...);
#endif
return R();
}
hook() {}
hook(const hook &hook) { callback = hook.callback; }
hook(void *function) { callback = function; }
hook(R (*function)(P...)) { callback = function; }
template<typename C> hook(R (C::*function)(P...), C *object) { callback = {function, object}; }
template<typename C> hook(R (C::*function)(P...) const, C *object) { callback = {function, object}; }
template<typename L> hook(const L& function) { callback = function; }
hook& operator=(const hook& hook) { callback = hook.callback; return *this; }
};
#if defined(DEBUGGER)
#define privileged public
#else
#define privileged private
#endif
typedef int1_t int1;
typedef int2_t int2;
typedef int3_t int3;
typedef int4_t int4;
typedef int5_t int5;
typedef int6_t int6;
typedef int7_t int7;
typedef int8_t int8;
typedef int9_t int9;
typedef int10_t int10;
typedef int11_t int11;
typedef int12_t int12;
typedef int13_t int13;
typedef int14_t int14;
typedef int15_t int15;
typedef int16_t int16;
typedef int17_t int17;
typedef int18_t int18;
typedef int19_t int19;
typedef int20_t int20;
typedef int21_t int21;
typedef int22_t int22;
typedef int23_t int23;
typedef int24_t int24;
typedef int25_t int25;
typedef int26_t int26;
typedef int27_t int27;
typedef int28_t int28;
typedef int29_t int29;
typedef int30_t int30;
typedef int31_t int31;
typedef int32_t int32;
typedef int64_t int64;
typedef uint1_t uint1;
typedef uint2_t uint2;
typedef uint3_t uint3;
typedef uint4_t uint4;
typedef uint5_t uint5;
typedef uint6_t uint6;
typedef uint7_t uint7;
typedef uint8_t uint8;
typedef uint9_t uint9;
typedef uint10_t uint10;
typedef uint11_t uint11;
typedef uint12_t uint12;
typedef uint13_t uint13;
typedef uint14_t uint14;
typedef uint15_t uint15;
typedef uint16_t uint16;
typedef uint17_t uint17;
typedef uint18_t uint18;
typedef uint19_t uint19;
typedef uint20_t uint20;
typedef uint21_t uint21;
typedef uint22_t uint22;
typedef uint23_t uint23;
typedef uint24_t uint24;
typedef uint25_t uint25;
typedef uint26_t uint26;
typedef uint27_t uint27;
typedef uint28_t uint28;
typedef uint29_t uint29;
typedef uint30_t uint30;
typedef uint31_t uint31;
typedef uint32_t uint32;
typedef uint_t<33> uint33;
typedef uint64_t uint64;
typedef varuint_t<unsigned> varuint;
#endif
-118
View File
@@ -1,118 +0,0 @@
#ifndef EMULATOR_INTERFACE_HPP
#define EMULATOR_INTERFACE_HPP
namespace Emulator {
struct Interface {
struct Information {
string name;
unsigned width;
unsigned height;
bool overscan;
double aspectRatio;
bool resettable;
struct Capability {
bool states;
bool cheats;
} capability;
} information;
struct Media {
unsigned id;
string name;
string type;
string load;
};
vector<Media> media;
struct Device {
unsigned id;
unsigned portmask;
string name;
struct Input {
unsigned id;
unsigned type; //0 = digital, 1 = analog (relative), 2 = analog (absolute)
string name;
unsigned guid;
};
vector<Input> input;
vector<unsigned> order;
};
struct Port {
unsigned id;
string name;
vector<Device> device;
};
vector<Port> port;
struct Bind {
virtual void loadRequest(unsigned, const string&, const string&) {}
virtual void loadRequest(unsigned, const string&) {}
virtual void saveRequest(unsigned, const string&) {}
virtual uint32_t videoColor(unsigned, uint16_t, uint16_t, uint16_t) { return 0u; }
virtual void videoRefresh(const uint32_t*, unsigned, unsigned, unsigned) {}
virtual void audioSample(int16_t, int16_t) {}
virtual int16_t inputPoll(unsigned, unsigned, unsigned) { return 0; }
virtual unsigned dipSettings(const XML::Node&) { return 0; }
virtual string path(unsigned) { return ""; }
virtual void notify(const string &text) { print(text, "\n"); }
} *bind;
//callback bindings (provided by user interface)
void loadRequest(unsigned id, const string &name, const string &type) { return bind->loadRequest(id, name, type); }
void loadRequest(unsigned id, const string &path) { return bind->loadRequest(id, path); }
void saveRequest(unsigned id, const string &path) { return bind->saveRequest(id, path); }
uint32_t videoColor(unsigned source, uint16_t red, uint16_t green, uint16_t blue) { return bind->videoColor(source, red, green, blue); }
void videoRefresh(const uint32_t *data, unsigned pitch, unsigned width, unsigned height) { return bind->videoRefresh(data, pitch, width, height); }
void audioSample(int16_t lsample, int16_t rsample) { return bind->audioSample(lsample, rsample); }
int16_t inputPoll(unsigned port, unsigned device, unsigned input) { return bind->inputPoll(port, device, input); }
unsigned dipSettings(const XML::Node &node) { return bind->dipSettings(node); }
string path(unsigned group) { return bind->path(group); }
template<typename... Args> void notify(Args&... args) { return bind->notify({std::forward<Args>(args)...}); }
//information
virtual double videoFrequency() = 0;
virtual double audioFrequency() = 0;
//media interface
virtual bool loaded() { return false; }
virtual string sha256() { return ""; }
virtual unsigned group(unsigned id) = 0;
virtual void load(unsigned id, const string &manifest) {}
virtual void save() {}
virtual void load(unsigned id, const stream &memory, const string &markup = "") {}
virtual void save(unsigned id, const stream &memory) {}
virtual void unload() {}
//system interface
virtual void connect(unsigned port, unsigned device) {}
virtual void power() {}
virtual void reset() {}
virtual void run() {}
//time functions
virtual bool rtc() { return false; }
virtual void rtcsync() {}
//state functions
virtual serializer serialize() = 0;
virtual bool unserialize(serializer&) = 0;
//cheat functions
virtual void cheatSet(const lstring& = lstring{}) {}
//utility functions
virtual void paletteUpdate() {}
//debugger functions
virtual bool tracerEnable(bool) { return false; }
virtual void exportMemory() {}
Interface() : bind(nullptr) {}
};
}
#endif
-16
View File
@@ -1,16 +0,0 @@
fc_objects := fc-interface fc-system fc-scheduler fc-input
fc_objects += fc-memory fc-cartridge fc-cpu fc-apu fc-ppu
fc_objects += fc-cheat fc-video
objects += $(fc_objects)
obj/fc-interface.o: $(fc)/interface/interface.cpp $(call rwildcard,$(fc)/interface/)
obj/fc-system.o: $(fc)/system/system.cpp $(call rwildcard,$(fc)/system/)
obj/fc-scheduler.o: $(fc)/scheduler/scheduler.cpp $(call rwildcard,$(fc)/scheduler/)
obj/fc-input.o: $(fc)/input/input.cpp $(call rwildcard,$(fc)/input/)
obj/fc-memory.o: $(fc)/memory/memory.cpp $(call rwildcard,$(fc)/memory/)
obj/fc-cartridge.o: $(fc)/cartridge/cartridge.cpp $(call rwildcard,$(fc)/cartridge/)
obj/fc-cpu.o: $(fc)/cpu/cpu.cpp $(call rwildcard,$(fc)/cpu/)
obj/fc-apu.o: $(fc)/apu/apu.cpp $(call rwildcard,$(fc)/apu/)
obj/fc-ppu.o: $(fc)/ppu/ppu.cpp $(call rwildcard,$(fc)/ppu/)
obj/fc-cheat.o: $(fc)/cheat/cheat.cpp $(call rwildcard,$(fc)/cheat/)
obj/fc-video.o: $(fc)/video/video.cpp $(call rwildcard,$(fc)/video/)
-329
View File
@@ -1,329 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
#include "envelope.cpp"
#include "sweep.cpp"
#include "pulse.cpp"
#include "triangle.cpp"
#include "noise.cpp"
#include "dmc.cpp"
#include "serialization.cpp"
APU apu;
const uint8 APU::length_counter_table[32] = {
0x0a, 0xfe, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, 0xa0, 0x08, 0x3c, 0x0a, 0x0e, 0x0c, 0x1a, 0x0e,
0x0c, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, 0xc0, 0x18, 0x48, 0x1a, 0x10, 0x1c, 0x20, 0x1e,
};
const uint16 APU::ntsc_noise_period_table[16] = {
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068,
};
const uint16 APU::pal_noise_period_table[16] = {
4, 7, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778,
};
const uint16 APU::ntsc_dmc_period_table[16] = {
428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54,
};
const uint16 APU::pal_dmc_period_table[16] = {
398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50,
};
void APU::Main() {
apu.main();
}
void APU::main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
unsigned pulse_output, triangle_output, noise_output, dmc_output;
pulse_output = pulse[0].clock();
pulse_output += pulse[1].clock();
triangle_output = triangle.clock();
noise_output = noise.clock();
dmc_output = dmc.clock();
clock_frame_counter_divider();
signed output = pulse_dac[pulse_output] + dmc_triangle_noise_dac[dmc_output][triangle_output][noise_output];
output = filter.run_hipass_strong(output);
output += cartridge_sample;
output = filter.run_hipass_weak(output);
//output = filter.run_lopass(output);
output = sclamp<16>(output);
interface->audioSample(output, output);
tick();
}
}
void APU::tick() {
clock += 12;
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
}
void APU::set_irq_line() {
cpu.set_irq_apu_line(frame.irq_pending || dmc.irq_pending);
}
void APU::set_sample(int16 sample) {
cartridge_sample = sample;
}
void APU::power() {
filter.hipass_strong = 0;
filter.hipass_weak = 0;
filter.lopass = 0;
pulse[0].power();
pulse[1].power();
triangle.power();
noise.power();
dmc.power();
}
void APU::reset() {
create(APU::Main, 21477272);
pulse[0].reset();
pulse[1].reset();
triangle.reset();
noise.reset();
dmc.reset();
frame.irq_pending = 0;
frame.mode = 0;
frame.counter = 0;
frame.divider = 1;
enabled_channels = 0;
cartridge_sample = 0;
set_irq_line();
}
uint8 APU::read(uint16 addr) {
if(addr == 0x4015) {
uint8 result = 0x00;
result |= pulse[0].length_counter ? 0x01 : 0;
result |= pulse[1].length_counter ? 0x02 : 0;
result |= triangle.length_counter ? 0x04 : 0;
result |= noise.length_counter ? 0x08 : 0;
result |= dmc.length_counter ? 0x10 : 0;
result |= frame.irq_pending ? 0x40 : 0;
result |= dmc.irq_pending ? 0x80 : 0;
frame.irq_pending = false;
set_irq_line();
return result;
}
return cpu.mdr();
}
void APU::write(uint16 addr, uint8 data) {
const unsigned n = (addr >> 2) & 1; //pulse#
switch(addr) {
case 0x4000: case 0x4004:
pulse[n].duty = data >> 6;
pulse[n].envelope.loop_mode = data & 0x20;
pulse[n].envelope.use_speed_as_volume = data & 0x10;
pulse[n].envelope.speed = data & 0x0f;
break;
case 0x4001: case 0x4005:
pulse[n].sweep.enable = data & 0x80;
pulse[n].sweep.period = (data & 0x70) >> 4;
pulse[n].sweep.decrement = data & 0x08;
pulse[n].sweep.shift = data & 0x07;
pulse[n].sweep.reload = true;
break;
case 0x4002: case 0x4006:
pulse[n].period = (pulse[n].period & 0x0700) | (data << 0);
pulse[n].sweep.pulse_period = (pulse[n].sweep.pulse_period & 0x0700) | (data << 0);
break;
case 0x4003: case 0x4007:
pulse[n].period = (pulse[n].period & 0x00ff) | (data << 8);
pulse[n].sweep.pulse_period = (pulse[n].sweep.pulse_period & 0x00ff) | (data << 8);
pulse[n].duty_counter = 7;
pulse[n].envelope.reload_decay = true;
if(enabled_channels & (1 << n)) {
pulse[n].length_counter = length_counter_table[(data >> 3) & 0x1f];
}
break;
case 0x4008:
triangle.halt_length_counter = data & 0x80;
triangle.linear_length = data & 0x7f;
break;
case 0x400a:
triangle.period = (triangle.period & 0x0700) | (data << 0);
break;
case 0x400b:
triangle.period = (triangle.period & 0x00ff) | (data << 8);
triangle.reload_linear = true;
if(enabled_channels & (1 << 2)) {
triangle.length_counter = length_counter_table[(data >> 3) & 0x1f];
}
break;
case 0x400c:
noise.envelope.loop_mode = data & 0x20;
noise.envelope.use_speed_as_volume = data & 0x10;
noise.envelope.speed = data & 0x0f;
break;
case 0x400e:
noise.short_mode = data & 0x80;
noise.period = data & 0x0f;
break;
case 0x400f:
noise.envelope.reload_decay = true;
if(enabled_channels & (1 << 3)) {
noise.length_counter = length_counter_table[(data >> 3) & 0x1f];
}
break;
case 0x4010:
dmc.irq_enable = data & 0x80;
dmc.loop_mode = data & 0x40;
dmc.period = data & 0x0f;
dmc.irq_pending = dmc.irq_pending && dmc.irq_enable && !dmc.loop_mode;
set_irq_line();
break;
case 0x4011:
dmc.dac_latch = data & 0x7f;
break;
case 0x4012:
dmc.addr_latch = data;
break;
case 0x4013:
dmc.length_latch = data;
break;
case 0x4015:
if((data & 0x01) == 0) pulse[0].length_counter = 0;
if((data & 0x02) == 0) pulse[1].length_counter = 0;
if((data & 0x04) == 0) triangle.length_counter = 0;
if((data & 0x08) == 0) noise.length_counter = 0;
(data & 0x10) ? dmc.start() : dmc.stop();
dmc.irq_pending = false;
set_irq_line();
enabled_channels = data & 0x1f;
break;
case 0x4017:
frame.mode = data >> 6;
frame.counter = 0;
if(frame.mode & 2) clock_frame_counter();
if(frame.mode & 1) {
frame.irq_pending = false;
set_irq_line();
}
frame.divider = FrameCounter::NtscPeriod;
break;
}
}
signed APU::Filter::run_hipass_strong(signed sample) {
hipass_strong += ((((int64)sample << 16) - (hipass_strong >> 16)) * HiPassStrong) >> 16;
return sample - (hipass_strong >> 32);
}
signed APU::Filter::run_hipass_weak(signed sample) {
hipass_weak += ((((int64)sample << 16) - (hipass_weak >> 16)) * HiPassWeak) >> 16;
return sample - (hipass_weak >> 32);
}
signed APU::Filter::run_lopass(signed sample) {
lopass += ((((int64)sample << 16) - (lopass >> 16)) * LoPass) >> 16;
return (lopass >> 32);
}
void APU::clock_frame_counter() {
frame.counter++;
if(frame.counter & 1) {
pulse[0].clock_length();
pulse[0].sweep.clock(0);
pulse[1].clock_length();
pulse[1].sweep.clock(1);
triangle.clock_length();
noise.clock_length();
}
pulse[0].envelope.clock();
pulse[1].envelope.clock();
triangle.clock_linear_length();
noise.envelope.clock();
if(frame.counter == 0) {
if(frame.mode & 2) frame.divider += FrameCounter::NtscPeriod;
if(frame.mode == 0) {
frame.irq_pending = true;
set_irq_line();
}
}
}
void APU::clock_frame_counter_divider() {
frame.divider -= 2;
if(frame.divider <= 0) {
clock_frame_counter();
frame.divider += FrameCounter::NtscPeriod;
}
}
APU::APU() {
for(unsigned amp = 0; amp < 32; amp++) {
if(amp == 0) {
pulse_dac[amp] = 0;
} else {
pulse_dac[amp] = 16384.0 * 95.88 / (8128.0 / amp + 100.0);
}
}
for(unsigned dmc_amp = 0; dmc_amp < 128; dmc_amp++) {
for(unsigned triangle_amp = 0; triangle_amp < 16; triangle_amp++) {
for(unsigned noise_amp = 0; noise_amp < 16; noise_amp++) {
if(dmc_amp == 0 && triangle_amp == 0 && noise_amp == 0) {
dmc_triangle_noise_dac[dmc_amp][triangle_amp][noise_amp] = 0;
} else {
dmc_triangle_noise_dac[dmc_amp][triangle_amp][noise_amp]
= 16384.0 * 159.79 / (100.0 + 1.0 / (triangle_amp / 8227.0 + noise_amp / 12241.0 + dmc_amp / 22638.0));
}
}
}
}
}
}
-65
View File
@@ -1,65 +0,0 @@
struct APU : Thread {
static void Main();
void main();
void tick();
void set_irq_line();
void set_sample(int16 sample);
void power();
void reset();
uint8 read(uint16 addr);
void write(uint16 addr, uint8 data);
void serialize(serializer&);
APU();
struct Filter {
enum : signed { HiPassStrong = 225574, HiPassWeak = 57593, LoPass = 86322413 };
int64 hipass_strong;
int64 hipass_weak;
int64 lopass;
signed run_hipass_strong(signed sample);
signed run_hipass_weak(signed sample);
signed run_lopass(signed sample);
void serialize(serializer&);
} filter;
#include "envelope.hpp"
#include "sweep.hpp"
#include "pulse.hpp"
#include "triangle.hpp"
#include "noise.hpp"
#include "dmc.hpp"
struct FrameCounter {
enum : unsigned { NtscPeriod = 14915 }; //~(21.477MHz / 6 / 240hz)
bool irq_pending;
uint2 mode;
uint2 counter;
signed divider;
void serialize(serializer&);
} frame;
void clock_frame_counter();
void clock_frame_counter_divider();
uint8 enabled_channels;
int16 cartridge_sample;
int16 pulse_dac[32];
int16 dmc_triangle_noise_dac[128][16][16];
static const uint8 length_counter_table[32];
static const uint16 ntsc_dmc_period_table[16];
static const uint16 pal_dmc_period_table[16];
static const uint16 ntsc_noise_period_table[16];
static const uint16 pal_noise_period_table[16];
};
extern APU apu;
-117
View File
@@ -1,117 +0,0 @@
void APU::DMC::start() {
if(length_counter == 0) {
read_addr = 0x4000 + (addr_latch << 6);
length_counter = (length_latch << 4) + 1;
}
}
void APU::DMC::stop() {
length_counter = 0;
dma_delay_counter = 0;
cpu.set_rdy_line(1);
cpu.set_rdy_addr({ false, 0u });
}
uint8 APU::DMC::clock() {
uint8 result = dac_latch;
if(dma_delay_counter > 0) {
dma_delay_counter--;
if(dma_delay_counter == 1) {
cpu.set_rdy_addr({ true, uint16(0x8000 | read_addr) });
} else if(dma_delay_counter == 0) {
cpu.set_rdy_line(1);
cpu.set_rdy_addr({ false, 0u });
dma_buffer = cpu.mdr();
have_dma_buffer = true;
length_counter--;
read_addr++;
if(length_counter == 0) {
if(loop_mode) {
start();
} else if(irq_enable) {
irq_pending = true;
apu.set_irq_line();
}
}
}
}
if(--period_counter == 0) {
if(have_sample) {
signed delta = (((sample >> bit_counter) & 1) << 2) - 2;
unsigned data = dac_latch + delta;
if((data & 0x80) == 0) dac_latch = data;
}
if(++bit_counter == 0) {
if(have_dma_buffer) {
have_sample = true;
sample = dma_buffer;
have_dma_buffer = false;
} else {
have_sample = false;
}
}
period_counter = ntsc_dmc_period_table[period];
}
if(length_counter > 0 && have_dma_buffer == false && dma_delay_counter == 0) {
cpu.set_rdy_line(0);
dma_delay_counter = 4;
}
return result;
}
void APU::DMC::power() {
}
void APU::DMC::reset() {
length_counter = 0;
irq_pending = 0;
period = 0;
period_counter = ntsc_dmc_period_table[0];
irq_enable = 0;
loop_mode = 0;
dac_latch = 0;
addr_latch = 0;
length_latch = 0;
read_addr = 0;
dma_delay_counter = 0;
bit_counter = 0;
have_dma_buffer = 0;
dma_buffer = 0;
have_sample = 0;
sample = 0;
}
void APU::DMC::serialize(serializer &s) {
s.integer(length_counter);
s.integer(irq_pending);
s.integer(period);
s.integer(period_counter);
s.integer(irq_enable);
s.integer(loop_mode);
s.integer(dac_latch);
s.integer(addr_latch);
s.integer(length_latch);
s.integer(read_addr);
s.integer(dma_delay_counter);
s.integer(bit_counter);
s.integer(have_dma_buffer);
s.integer(dma_buffer);
s.integer(have_sample);
s.integer(sample);
}
-32
View File
@@ -1,32 +0,0 @@
struct DMC {
unsigned length_counter;
bool irq_pending;
uint4 period;
unsigned period_counter;
bool irq_enable;
bool loop_mode;
uint8 dac_latch;
uint8 addr_latch;
uint8 length_latch;
uint15 read_addr;
unsigned dma_delay_counter;
uint3 bit_counter;
bool have_dma_buffer;
uint8 dma_buffer;
bool have_sample;
uint8 sample;
void start();
void stop();
uint8 clock();
void power();
void reset();
void serialize(serializer&);
} dmc;
-39
View File
@@ -1,39 +0,0 @@
unsigned APU::Envelope::volume() const {
return use_speed_as_volume ? speed : decay_volume;
}
void APU::Envelope::clock() {
if(reload_decay) {
reload_decay = false;
decay_volume = 0x0f;
decay_counter = speed + 1;
return;
}
if(--decay_counter == 0) {
decay_counter = speed + 1;
if(decay_volume || loop_mode) decay_volume--;
}
}
void APU::Envelope::power() {
}
void APU::Envelope::reset() {
speed = 0;
use_speed_as_volume = 0;
loop_mode = 0;
reload_decay = 0;
decay_counter = 0;
decay_volume = 0;
}
void APU::Envelope::serialize(serializer &s) {
s.integer(speed);
s.integer(use_speed_as_volume);
s.integer(loop_mode);
s.integer(reload_decay);
s.integer(decay_counter);
s.integer(decay_volume);
}
-16
View File
@@ -1,16 +0,0 @@
struct Envelope {
uint4 speed;
bool use_speed_as_volume;
bool loop_mode;
bool reload_decay;
uint8 decay_counter;
uint4 decay_volume;
unsigned volume() const;
void clock();
void power();
void reset();
void serialize(serializer&);
};
-57
View File
@@ -1,57 +0,0 @@
void APU::Noise::clock_length() {
if(envelope.loop_mode == 0) {
if(length_counter > 0) length_counter--;
}
}
uint8 APU::Noise::clock() {
if(length_counter == 0) return 0;
uint8 result = (lfsr & 1) ? envelope.volume() : 0;
if(--period_counter == 0) {
unsigned feedback;
if(short_mode) {
feedback = ((lfsr >> 0) & 1) ^ ((lfsr >> 6) & 1);
} else {
feedback = ((lfsr >> 0) & 1) ^ ((lfsr >> 1) & 1);
}
lfsr = (lfsr >> 1) | (feedback << 14);
period_counter = apu.ntsc_noise_period_table[period];
}
return result;
}
void APU::Noise::power() {
}
void APU::Noise::reset() {
length_counter = 0;
envelope.speed = 0;
envelope.use_speed_as_volume = 0;
envelope.loop_mode = 0;
envelope.reload_decay = 0;
envelope.decay_counter = 0;
envelope.decay_volume = 0;
period = 0;
period_counter = 1;
short_mode = 0;
lfsr = 1;
}
void APU::Noise::serialize(serializer &s) {
s.integer(length_counter);
envelope.serialize(s);
s.integer(period);
s.integer(period_counter);
s.integer(short_mode);
s.integer(lfsr);
}
-18
View File
@@ -1,18 +0,0 @@
struct Noise {
unsigned length_counter;
Envelope envelope;
uint4 period;
unsigned period_counter;
bool short_mode;
uint15 lfsr;
void clock_length();
uint8 clock();
void power();
void reset();
void serialize(serializer&);
} noise;
-51
View File
@@ -1,51 +0,0 @@
void APU::Pulse::clock_length() {
if(envelope.loop_mode == 0) {
if(length_counter) length_counter--;
}
}
uint8 APU::Pulse::clock() {
if(sweep.check_period() == false) return 0;
if(length_counter == 0) return 0;
static const unsigned duty_table[] = { 1, 2, 4, 6 };
uint8 result = (duty_counter < duty_table[duty]) ? envelope.volume() : 0;
if(sweep.pulse_period < 0x008) result = 0;
if(--period_counter == 0) {
period_counter = (sweep.pulse_period + 1) * 2;
duty_counter++;
}
return result;
}
void APU::Pulse::power() {
envelope.power();
sweep.power();
}
void APU::Pulse::reset() {
envelope.reset();
sweep.reset();
length_counter = 0;
duty = 0;
duty_counter = 0;
period = 0;
period_counter = 1;
}
void APU::Pulse::serialize(serializer &s) {
s.integer(length_counter);
envelope.serialize(s);
sweep.serialize(s);
s.integer(duty);
s.integer(duty_counter);
s.integer(period);
s.integer(period_counter);
}
-20
View File
@@ -1,20 +0,0 @@
struct Pulse {
unsigned length_counter;
Envelope envelope;
Sweep sweep;
uint2 duty;
uint3 duty_counter;
uint11 period;
unsigned period_counter;
void clock_length();
bool check_period();
uint8 clock();
void power();
void reset();
void serialize(serializer&);
} pulse[2];
-28
View File
@@ -1,28 +0,0 @@
void APU::serialize(serializer &s) {
Thread::serialize(s);
filter.serialize(s);
pulse[0].serialize(s);
pulse[1].serialize(s);
triangle.serialize(s);
dmc.serialize(s);
frame.serialize(s);
s.integer(enabled_channels);
s.integer(cartridge_sample);
}
void APU::Filter::serialize(serializer &s) {
s.integer(hipass_strong);
s.integer(hipass_weak);
s.integer(lopass);
}
void APU::FrameCounter::serialize(serializer &s) {
s.integer(irq_pending);
s.integer(mode);
s.integer(counter);
s.integer(divider);
}
-53
View File
@@ -1,53 +0,0 @@
bool APU::Sweep::check_period() {
if(pulse_period > 0x7ff) return false;
if(decrement == 0) {
if((pulse_period + (pulse_period >> shift)) & 0x800) return false;
}
return true;
}
void APU::Sweep::clock(unsigned channel) {
if(--counter == 0) {
counter = period + 1;
if(enable && shift && pulse_period > 8) {
signed delta = pulse_period >> shift;
if(decrement) {
pulse_period -= delta;
if(channel == 0) pulse_period--;
} else if((pulse_period + delta) < 0x800) {
pulse_period += delta;
}
}
}
if(reload) {
reload = false;
counter = period + 1;
}
}
void APU::Sweep::power() {
shift = 0;
decrement = 0;
period = 0;
counter = 1;
enable = 0;
reload = 0;
pulse_period = 0;
}
void APU::Sweep::reset() {
}
void APU::Sweep::serialize(serializer &s) {
s.integer(shift);
s.integer(decrement);
s.integer(period);
s.integer(counter);
s.integer(enable);
s.integer(reload);
s.integer(pulse_period);
}
-16
View File
@@ -1,16 +0,0 @@
struct Sweep {
uint8 shift;
bool decrement;
uint3 period;
uint8 counter;
bool enable;
bool reload;
uint11 pulse_period;
bool check_period();
void clock(unsigned channel);
void power();
void reset();
void serialize(serializer&);
};
-58
View File
@@ -1,58 +0,0 @@
void APU::Triangle::clock_length() {
if(halt_length_counter == 0) {
if(length_counter > 0) length_counter--;
}
}
void APU::Triangle::clock_linear_length() {
if(reload_linear) {
linear_length_counter = linear_length;
} else if(linear_length_counter) {
linear_length_counter--;
}
if(halt_length_counter == 0) reload_linear = false;
}
uint8 APU::Triangle::clock() {
uint8 result = step_counter & 0x0f;
if((step_counter & 0x10) == 0) result ^= 0x0f;
if(length_counter == 0 || linear_length_counter == 0) return result;
if(--period_counter == 0) {
step_counter++;
period_counter = period + 1;
}
return result;
}
void APU::Triangle::power() {
reset();
}
void APU::Triangle::reset() {
length_counter = 0;
linear_length = 0;
halt_length_counter = 0;
period = 0;
period_counter = 1;
step_counter = 0;
linear_length_counter = 0;
reload_linear = 0;
}
void APU::Triangle::serialize(serializer &s) {
s.integer(length_counter);
s.integer(linear_length);
s.integer(halt_length_counter);
s.integer(period);
s.integer(period_counter);
s.integer(step_counter);
s.integer(linear_length_counter);
s.integer(reload_linear);
}
-21
View File
@@ -1,21 +0,0 @@
struct Triangle {
unsigned length_counter;
uint8 linear_length;
bool halt_length_counter;
uint11 period;
unsigned period_counter;
uint5 step_counter;
uint8 linear_length_counter;
bool reload_linear;
void clock_length();
void clock_linear_length();
uint8 clock();
void power();
void reset();
void serialize(serializer&);
} triangle;
-117
View File
@@ -1,117 +0,0 @@
//BANDAI-FCG
struct BandaiFCG : Board {
uint8 chr_bank[8];
uint8 prg_bank;
uint2 mirror;
bool irq_counter_enable;
uint16 irq_counter;
uint16 irq_latch;
void main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_counter_enable) {
if(--irq_counter == 0xffff) {
cpu.set_irq_line(1);
irq_counter_enable = false;
}
}
tick();
}
}
unsigned ciram_addr(unsigned addr) const {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
case 2: return 0x0000 | (addr & 0x03ff);
case 3: return 0x0400 | (addr & 0x03ff);
}
}
uint8 prg_read(unsigned addr) {
if(addr & 0x8000) {
bool region = addr & 0x4000;
unsigned bank = (region == 0 ? prg_bank : 0x0f);
return prgrom.read((bank << 14) | (addr & 0x3fff));
}
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if(addr >= 0x6000) {
switch(addr & 15) {
case 0x00: case 0x01: case 0x02: case 0x03:
case 0x04: case 0x05: case 0x06: case 0x07:
chr_bank[addr & 7] = data;
break;
case 0x08:
prg_bank = data & 0x0f;
break;
case 0x09:
mirror = data & 0x03;
break;
case 0x0a:
cpu.set_irq_line(0);
irq_counter_enable = data & 0x01;
irq_counter = irq_latch;
break;
case 0x0b:
irq_latch = (irq_latch & 0xff00) | (data << 0);
break;
case 0x0c:
irq_latch = (irq_latch & 0x00ff) | (data << 8);
break;
case 0x0d:
//TODO: serial EEPROM support
break;
}
}
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read(ciram_addr(addr));
addr = (chr_bank[addr >> 10] << 10) | (addr & 0x03ff);
return Board::chr_read(addr);
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write(ciram_addr(addr), data);
addr = (chr_bank[addr >> 10] << 10) | (addr & 0x03ff);
return Board::chr_write(addr, data);
}
void power() {
reset();
}
void reset() {
for(auto &n : chr_bank) n = 0;
prg_bank = 0;
mirror = 0;
irq_counter_enable = 0;
irq_counter = 0;
irq_latch = 0;
}
void serialize(serializer &s) {
Board::serialize(s);
s.array(chr_bank);
s.integer(prg_bank);
s.integer(mirror);
s.integer(irq_counter_enable);
s.integer(irq_counter);
s.integer(irq_latch);
}
BandaiFCG(XML::Document &document) : Board(document) {
}
};
-174
View File
@@ -1,174 +0,0 @@
#include "bandai-fcg.cpp"
#include "konami-vrc1.cpp"
#include "konami-vrc2.cpp"
#include "konami-vrc3.cpp"
#include "konami-vrc4.cpp"
#include "konami-vrc6.cpp"
#include "konami-vrc7.cpp"
#include "nes-axrom.cpp"
#include "nes-bnrom.cpp"
#include "nes-cnrom.cpp"
#include "nes-exrom.cpp"
#include "nes-fxrom.cpp"
#include "nes-gxrom.cpp"
#include "nes-hkrom.cpp"
#include "nes-nrom.cpp"
#include "nes-pxrom.cpp"
#include "nes-sxrom.cpp"
#include "nes-txrom.cpp"
#include "nes-uxrom.cpp"
#include "sunsoft-5b.cpp"
uint8 Board::Memory::read(unsigned addr) const {
return data[mirror(addr, size)];
}
void Board::Memory::write(unsigned addr, uint8 byte) {
if(writable) data[mirror(addr, size)] = byte;
}
unsigned Board::mirror(unsigned addr, unsigned size) {
unsigned base = 0;
if(size) {
unsigned mask = 1 << 23;
while(addr >= size) {
while(!(addr & mask)) mask >>= 1;
addr -= mask;
if(size > mask) {
size -= mask;
base += mask;
}
mask >>= 1;
}
base += addr;
}
return base;
}
void Board::main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
cartridge.clock += 12 * 4095;
tick();
}
}
void Board::tick() {
cartridge.clock += 12;
if(cartridge.clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
}
uint8 Board::chr_read(unsigned addr) {
if(chrram.size) return chrram.data[mirror(addr, chrram.size)];
if(chrrom.size) return chrrom.data[mirror(addr, chrrom.size)];
return 0u;
}
void Board::chr_write(unsigned addr, uint8 data) {
if(chrram.size) chrram.data[mirror(addr, chrram.size)] = data;
}
void Board::power() {
}
void Board::reset() {
}
void Board::serialize(serializer &s) {
if(prgram.size) s.array(prgram.data, prgram.size);
if(chrram.size) s.array(chrram.data, chrram.size);
}
Board::Board(XML::Document &document) {
cartridge.board = this;
auto &cartridge = document["cartridge"];
information.type = cartridge["board"]["type"].data;
information.battery = cartridge["prg"]["ram"]["nonvolatile"].data == "true";
auto &prom = cartridge["prg"]["rom"];
auto &pram = cartridge["prg"]["ram"];
auto &crom = cartridge["chr"]["rom"];
auto &cram = cartridge["chr"]["ram"];
prgrom.size = numeral(prom["size"].data);
prgram.size = numeral(pram["size"].data);
chrrom.size = numeral(crom["size"].data);
chrram.size = numeral(cram["size"].data);
if(prgrom.size) prgrom.data = new uint8[prgrom.size]();
if(prgram.size) prgram.data = new uint8[prgram.size]();
if(chrrom.size) chrrom.data = new uint8[chrrom.size]();
if(chrram.size) chrram.data = new uint8[chrram.size]();
if(prom["name"].data) interface->loadRequest(ID::ProgramROM, prom["name"].data);
if(pram["name"].data) interface->loadRequest(ID::ProgramRAM, pram["name"].data);
if(crom["name"].data) interface->loadRequest(ID::CharacterROM, crom["name"].data);
if(cram["name"].data) interface->loadRequest(ID::CharacterRAM, cram["name"].data);
if(pram["name"].data) Famicom::cartridge.memory.append({ID::ProgramRAM, pram["name"].data});
if(cram["name"].data) Famicom::cartridge.memory.append({ID::CharacterRAM, cram["name"].data});
prgram.writable = true;
chrram.writable = true;
}
Board::~Board() {
}
Board* Board::load(const string &manifest) {
XML::Document document(manifest);
string type = document["cartridge"]["board"]["type"].data;
if(type == "BANDAI-FCG" ) return new BandaiFCG(document);
if(type == "KONAMI-VRC-1") return new KonamiVRC1(document);
if(type == "KONAMI-VRC-2") return new KonamiVRC2(document);
if(type == "KONAMI-VRC-3") return new KonamiVRC3(document);
if(type == "KONAMI-VRC-4") return new KonamiVRC4(document);
if(type == "KONAMI-VRC-6") return new KonamiVRC6(document);
if(type == "KONAMI-VRC-7") return new KonamiVRC7(document);
if(type == "NES-AMROM" ) return new NES_AxROM(document);
if(type == "NES-ANROM" ) return new NES_AxROM(document);
if(type == "NES-AN1ROM" ) return new NES_AxROM(document);
if(type == "NES-AOROM" ) return new NES_AxROM(document);
if(type == "NES-BNROM" ) return new NES_BNROM(document);
if(type == "NES-CNROM" ) return new NES_CNROM(document);
if(type == "NES-EKROM" ) return new NES_ExROM(document);
if(type == "NES-ELROM" ) return new NES_ExROM(document);
if(type == "NES-ETROM" ) return new NES_ExROM(document);
if(type == "NES-EWROM" ) return new NES_ExROM(document);
if(type == "NES-FJROM" ) return new NES_FxROM(document);
if(type == "NES-FKROM" ) return new NES_FxROM(document);
if(type == "NES-GNROM" ) return new NES_GxROM(document);
if(type == "NES-MHROM" ) return new NES_GxROM(document);
if(type == "NES-HKROM" ) return new NES_HKROM(document);
if(type == "NES-NROM-128") return new NES_NROM(document);
if(type == "NES-NROM-256") return new NES_NROM(document);
if(type == "NES-PEEOROM" ) return new NES_PxROM(document);
if(type == "NES-PNROM" ) return new NES_PxROM(document);
if(type == "NES-SNROM" ) return new NES_SxROM(document);
if(type == "NES-SXROM" ) return new NES_SxROM(document);
if(type == "NES-TLROM" ) return new NES_TxROM(document);
if(type == "NES-UNROM" ) return new NES_UxROM(document);
if(type == "NES-UOROM" ) return new NES_UxROM(document);
if(type == "SUNSOFT-5B" ) return new Sunsoft5B(document);
return nullptr;
}
-46
View File
@@ -1,46 +0,0 @@
struct Board {
struct Memory {
uint8_t *data;
unsigned size;
bool writable;
inline uint8 read(unsigned addr) const;
inline void write(unsigned addr, uint8 data);
inline Memory(uint8_t *data, unsigned size) : data(data), size(size) {}
inline Memory() : data(nullptr), size(0u), writable(false) {}
inline ~Memory() { if(data) delete[] data; }
};
static unsigned mirror(unsigned addr, unsigned size);
virtual void main();
virtual void tick();
virtual uint8 prg_read(unsigned addr) = 0;
virtual void prg_write(unsigned addr, uint8 data) = 0;
virtual uint8 chr_read(unsigned addr);
virtual void chr_write(unsigned addr, uint8 data);
virtual inline void scanline(unsigned y) {}
virtual void power();
virtual void reset();
virtual void serialize(serializer&);
Board(XML::Document &document);
virtual ~Board();
static Board* load(const string &manifest);
struct Information {
string type;
bool battery;
} information;
Memory prgrom;
Memory prgram;
Memory chrrom;
Memory chrram;
};
-40
View File
@@ -1,40 +0,0 @@
struct KonamiVRC1 : Board {
VRC1 vrc1;
uint8 prg_read(unsigned addr) {
if(addr & 0x8000) return prgrom.read(vrc1.prg_addr(addr));
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if(addr & 0x8000) return vrc1.reg_write(addr, data);
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read(vrc1.ciram_addr(addr));
return Board::chr_read(vrc1.chr_addr(addr));
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write(vrc1.ciram_addr(addr), data);
return Board::chr_write(vrc1.chr_addr(addr), data);
}
void power() {
vrc1.power();
}
void reset() {
vrc1.reset();
}
void serialize(serializer &s) {
Board::serialize(s);
vrc1.serialize(s);
}
KonamiVRC1(XML::Document &document) : Board(document), vrc1(*this) {
}
};
-57
View File
@@ -1,57 +0,0 @@
struct KonamiVRC2 : Board {
struct Settings {
struct Pinout {
unsigned a0;
unsigned a1;
} pinout;
} settings;
VRC2 vrc2;
uint8 prg_read(unsigned addr) {
if(addr < 0x6000) return cpu.mdr();
if(addr < 0x8000) return vrc2.ram_read(addr);
return prgrom.read(vrc2.prg_addr(addr));
}
void prg_write(unsigned addr, uint8 data) {
if(addr < 0x6000) return;
if(addr < 0x8000) return vrc2.ram_write(addr, data);
bool a0 = (addr & settings.pinout.a0);
bool a1 = (addr & settings.pinout.a1);
addr &= 0xfff0;
addr |= (a0 << 0) | (a1 << 1);
return vrc2.reg_write(addr, data);
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read(vrc2.ciram_addr(addr));
return Board::chr_read(vrc2.chr_addr(addr));
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write(vrc2.ciram_addr(addr), data);
return Board::chr_write(vrc2.chr_addr(addr), data);
}
void power() {
vrc2.power();
}
void reset() {
vrc2.reset();
}
void serialize(serializer &s) {
Board::serialize(s);
vrc2.serialize(s);
}
KonamiVRC2(XML::Document &document) : Board(document), vrc2(*this) {
settings.pinout.a0 = 1 << decimal(document["cartridge"]["chip"]["pinout"]["a0"].data);
settings.pinout.a1 = 1 << decimal(document["cartridge"]["chip"]["pinout"]["a1"].data);
}
};
-57
View File
@@ -1,57 +0,0 @@
struct KonamiVRC3 : Board {
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
VRC3 vrc3;
void main() {
vrc3.main();
}
uint8 prg_read(unsigned addr) {
if((addr & 0xe000) == 0x6000) return prgram.read(addr & 0x1fff);
if(addr & 0x8000) return prgrom.read(vrc3.prg_addr(addr));
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if((addr & 0xe000) == 0x6000) return prgram.write(addr & 0x1fff, data);
if(addr & 0x8000) return vrc3.reg_write(addr, data);
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_read(addr & 0x07ff);
}
return chrram.read(addr);
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_write(addr & 0x07ff, data);
}
return chrram.write(addr, data);
}
void power() {
vrc3.power();
}
void reset() {
vrc3.reset();
}
void serialize(serializer &s) {
Board::serialize(s);
vrc3.serialize(s);
}
KonamiVRC3(XML::Document &document) : Board(document), vrc3(*this) {
settings.mirror = document["cartridge"]["mirror"]["mode"].data == "vertical" ? 1 : 0;
}
};
-61
View File
@@ -1,61 +0,0 @@
struct KonamiVRC4 : Board {
struct Settings {
struct Pinout {
unsigned a0;
unsigned a1;
} pinout;
} settings;
VRC4 vrc4;
void main() {
return vrc4.main();
}
uint8 prg_read(unsigned addr) {
if(addr < 0x6000) return cpu.mdr();
if(addr < 0x8000) return prgram.read(addr);
return prgrom.read(vrc4.prg_addr(addr));
}
void prg_write(unsigned addr, uint8 data) {
if(addr < 0x6000) return;
if(addr < 0x8000) return prgram.write(addr, data);
bool a0 = (addr & settings.pinout.a0);
bool a1 = (addr & settings.pinout.a1);
addr &= 0xfff0;
addr |= (a1 << 1) | (a0 << 0);
return vrc4.reg_write(addr, data);
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read(vrc4.ciram_addr(addr));
return Board::chr_read(vrc4.chr_addr(addr));
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write(vrc4.ciram_addr(addr), data);
return Board::chr_write(vrc4.chr_addr(addr), data);
}
void power() {
vrc4.power();
}
void reset() {
vrc4.reset();
}
void serialize(serializer &s) {
Board::serialize(s);
vrc4.serialize(s);
}
KonamiVRC4(XML::Document &document) : Board(document), vrc4(*this) {
settings.pinout.a0 = 1 << decimal(document["cartridge"]["chip"]["pinout"]["a0"].data);
settings.pinout.a1 = 1 << decimal(document["cartridge"]["chip"]["pinout"]["a1"].data);
}
};
-42
View File
@@ -1,42 +0,0 @@
struct KonamiVRC6 : Board {
VRC6 vrc6;
uint8 prg_read(unsigned addr) {
if((addr & 0xe000) == 0x6000) return vrc6.ram_read(addr);
if(addr & 0x8000) return prgrom.read(vrc6.prg_addr(addr));
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if((addr & 0xe000) == 0x6000) return vrc6.ram_write(addr, data);
if(addr & 0x8000) {
addr = (addr & 0xf003);
if(prgram.size) addr = (addr & ~3) | ((addr & 2) >> 1) | ((addr & 1) << 1);
return vrc6.reg_write(addr, data);
}
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read(vrc6.ciram_addr(addr));
return Board::chr_read(vrc6.chr_addr(addr));
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write(vrc6.ciram_addr(addr), data);
return Board::chr_write(vrc6.chr_addr(addr), data);
}
void serialize(serializer &s) {
Board::serialize(s);
vrc6.serialize(s);
}
void main() { vrc6.main(); }
void power() { vrc6.power(); }
void reset() { vrc6.reset(); }
KonamiVRC6(XML::Document &document) : Board(document), vrc6(*this) {
}
};
-47
View File
@@ -1,47 +0,0 @@
struct KonamiVRC7 : Board {
VRC7 vrc7;
void main() {
return vrc7.main();
}
uint8 prg_read(unsigned addr) {
if(addr < 0x6000) return cpu.mdr();
if(addr < 0x8000) return prgram.read(addr);
return prgrom.read(vrc7.prg_addr(addr));
}
void prg_write(unsigned addr, uint8 data) {
if(addr < 0x6000) return;
if(addr < 0x8000) return prgram.write(addr, data);
return vrc7.reg_write(addr, data);
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read(vrc7.ciram_addr(addr));
return chrram.read(vrc7.chr_addr(addr));
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write(vrc7.ciram_addr(addr), data);
return chrram.write(vrc7.chr_addr(addr), data);
}
void power() {
vrc7.power();
}
void reset() {
vrc7.reset();
}
void serialize(serializer &s) {
Board::serialize(s);
vrc7.serialize(s);
}
KonamiVRC7(XML::Document &document) : Board(document), vrc7(*this) {
}
};
-51
View File
@@ -1,51 +0,0 @@
//NES-AMROM
//NES-ANROM
//NES-AN1ROM
//NES-AOROM
struct NES_AxROM : Board {
uint4 prg_bank;
bool mirror_select;
uint8 prg_read(unsigned addr) {
if(addr & 0x8000) return prgrom.read((prg_bank << 15) | (addr & 0x7fff));
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if(addr & 0x8000) {
prg_bank = data & 0x0f;
mirror_select = data & 0x10;
}
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read((mirror_select << 10) | (addr & 0x03ff));
return Board::chr_read(addr);
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write((mirror_select << 10) | (addr & 0x03ff), data);
return Board::chr_write(addr, data);
}
void power() {
}
void reset() {
prg_bank = 0x0f;
mirror_select = 0;
}
void serialize(serializer &s) {
Board::serialize(s);
s.integer(prg_bank);
s.integer(mirror_select);
}
NES_AxROM(XML::Document &document) : Board(document) {
}
};
-52
View File
@@ -1,52 +0,0 @@
//NES-BN-ROM-01
struct NES_BNROM : Board {
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
uint2 prg_bank;
uint8 prg_read(unsigned addr) {
if(addr & 0x8000) return prgrom.read((prg_bank << 15) | (addr & 0x7fff));
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if(addr & 0x8000) prg_bank = data & 0x03;
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_read(addr);
}
return Board::chr_read(addr);
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_write(addr, data);
}
return Board::chr_write(addr, data);
}
void power() {
}
void reset() {
prg_bank = 0;
}
void serialize(serializer &s) {
Board::serialize(s);
s.integer(prg_bank);
}
NES_BNROM(XML::Document &document) : Board(document) {
settings.mirror = document["cartridge"]["mirror"]["mode"].data == "vertical" ? 1 : 0;
}
};
-54
View File
@@ -1,54 +0,0 @@
//NES-CNROM
struct NES_CNROM : Board {
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
uint2 chr_bank;
uint8 prg_read(unsigned addr) {
if(addr & 0x8000) return prgrom.read(addr & 0x7fff);
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if(addr & 0x8000) chr_bank = data & 0x03;
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_read(addr & 0x07ff);
}
addr = (chr_bank * 0x2000) + (addr & 0x1fff);
return Board::chr_read(addr);
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_write(addr & 0x07ff, data);
}
addr = (chr_bank * 0x2000) + (addr & 0x1fff);
Board::chr_write(addr, data);
}
void power() {
}
void reset() {
chr_bank = 0;
}
void serialize(serializer &s) {
Board::serialize(s);
s.integer(chr_bank);
}
NES_CNROM(XML::Document &document) : Board(document) {
settings.mirror = document["cartridge"]["mirror"]["mode"].data == "vertical" ? 1 : 0;
}
};
-53
View File
@@ -1,53 +0,0 @@
struct NES_ExROM : Board {
enum class Revision : unsigned {
EKROM,
ELROM,
ETROM,
EWROM,
} revision;
MMC5 mmc5;
void main() {
mmc5.main();
}
uint8 prg_read(unsigned addr) {
return mmc5.prg_read(addr);
}
void prg_write(unsigned addr, uint8 data) {
mmc5.prg_write(addr, data);
}
uint8 chr_read(unsigned addr) {
return mmc5.chr_read(addr);
}
void chr_write(unsigned addr, uint8 data) {
mmc5.chr_write(addr, data);
}
void scanline(unsigned y) {
mmc5.scanline(y);
}
void power() {
mmc5.power();
}
void reset() {
mmc5.reset();
}
void serialize(serializer &s) {
Board::serialize(s);
mmc5.serialize(s);
}
NES_ExROM(XML::Document &document) : Board(document), mmc5(*this) {
revision = Revision::ELROM;
}
};
-91
View File
@@ -1,91 +0,0 @@
//MMC4
struct NES_FxROM : Board {
enum Revision : unsigned {
FJROM,
FKROM,
} revision;
uint4 prg_bank;
uint5 chr_bank[2][2];
bool mirror;
bool latch[2];
uint8 prg_read(unsigned addr) {
if(addr < 0x6000) return cpu.mdr();
if(addr < 0x8000) return prgram.read(addr);
unsigned bank = addr < 0xc000 ? prg_bank : (uint4)0x0f;
return prgrom.read((bank * 0x4000) | (addr & 0x3fff));
}
void prg_write(unsigned addr, uint8 data) {
if(addr < 0x6000) return;
if(addr < 0x8000) return prgram.write(addr, data);
switch(addr & 0xf000) {
case 0xa000: prg_bank = data & 0x0f; break;
case 0xb000: chr_bank[0][0] = data & 0x1f; break;
case 0xc000: chr_bank[0][1] = data & 0x1f; break;
case 0xd000: chr_bank[1][0] = data & 0x1f; break;
case 0xe000: chr_bank[1][1] = data & 0x1f; break;
case 0xf000: mirror = data & 0x01; break;
}
}
unsigned ciram_addr(unsigned addr) const {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
}
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read(ciram_addr(addr));
bool region = addr & 0x1000;
unsigned bank = chr_bank[region][latch[region]];
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
return Board::chr_read((bank * 0x1000) | (addr & 0x0fff));
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write(ciram_addr(addr), data);
bool region = addr & 0x1000;
unsigned bank = chr_bank[region][latch[region]];
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
return Board::chr_write((bank * 0x1000) | (addr & 0x0fff), data);
}
void power() {
}
void reset() {
prg_bank = 0;
chr_bank[0][0] = 0;
chr_bank[0][1] = 0;
chr_bank[1][0] = 0;
chr_bank[1][1] = 0;
mirror = 0;
latch[0] = 0;
latch[1] = 0;
}
void serialize(serializer &s) {
Board::serialize(s);
s.integer(prg_bank);
s.integer(chr_bank[0][0]);
s.integer(chr_bank[0][1]);
s.integer(chr_bank[1][0]);
s.integer(chr_bank[1][1]);
s.integer(mirror);
s.array(latch);
}
NES_FxROM(XML::Document &document) : Board(document) {
revision = Revision::FKROM;
}
};
-61
View File
@@ -1,61 +0,0 @@
//NES-GNROM
//NES-MHROM
struct NES_GxROM : Board {
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
uint2 prg_bank;
uint2 chr_bank;
uint8 prg_read(unsigned addr) {
if(addr & 0x8000) return prgrom.read((prg_bank << 15) | (addr & 0x7fff));
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if(addr & 0x8000) {
prg_bank = (data & 0x30) >> 4;
chr_bank = (data & 0x03) >> 0;
}
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_read(addr & 0x07ff);
}
addr = (chr_bank * 0x2000) + (addr & 0x1fff);
return Board::chr_read(addr);
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_write(addr & 0x07ff, data);
}
addr = (chr_bank * 0x2000) + (addr & 0x1fff);
Board::chr_write(addr, data);
}
void power() {
}
void reset() {
prg_bank = 0;
chr_bank = 0;
}
void serialize(serializer &s) {
Board::serialize(s);
s.integer(prg_bank);
s.integer(chr_bank);
}
NES_GxROM(XML::Document &document) : Board(document) {
settings.mirror = document["cartridge"]["mirror"]["mode"].data == "vertical" ? 1 : 0;
}
};
-48
View File
@@ -1,48 +0,0 @@
struct NES_HKROM : Board {
MMC6 mmc6;
void main() {
mmc6.main();
}
uint8 prg_read(unsigned addr) {
if((addr & 0xf000) == 0x7000) return mmc6.ram_read(addr);
if(addr & 0x8000) return prgrom.read(mmc6.prg_addr(addr));
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if((addr & 0xf000) == 0x7000) return mmc6.ram_write(addr, data);
if(addr & 0x8000) return mmc6.reg_write(addr, data);
}
uint8 chr_read(unsigned addr) {
mmc6.irq_test(addr);
if(addr & 0x2000) return ppu.ciram_read(mmc6.ciram_addr(addr));
return Board::chr_read(mmc6.chr_addr(addr));
}
void chr_write(unsigned addr, uint8 data) {
mmc6.irq_test(addr);
if(addr & 0x2000) return ppu.ciram_write(mmc6.ciram_addr(addr), data);
return Board::chr_write(mmc6.chr_addr(addr), data);
}
void power() {
mmc6.power();
}
void reset() {
mmc6.reset();
}
void serialize(serializer &s) {
Board::serialize(s);
mmc6.serialize(s);
}
NES_HKROM(XML::Document &document) : Board(document), mmc6(*this) {
}
};
-43
View File
@@ -1,43 +0,0 @@
//NES-NROM-128
//NES-NROM-256
struct NES_NROM : Board {
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
uint8 prg_read(unsigned addr) {
if(addr & 0x8000) return prgrom.read(addr);
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_read(addr & 0x07ff);
}
if(chrram.size) return chrram.read(addr);
return chrrom.read(addr);
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_write(addr & 0x07ff, data);
}
if(chrram.size) return chrram.write(addr, data);
}
void serialize(serializer &s) {
Board::serialize(s);
}
NES_NROM(XML::Document &document) : Board(document) {
settings.mirror = document["cartridge"]["mirror"]["mode"].data == "vertical" ? 1 : 0;
}
};
-97
View File
@@ -1,97 +0,0 @@
//MMC2
struct NES_PxROM : Board {
enum Revision : unsigned {
PEEOROM,
PNROM,
} revision;
uint4 prg_bank;
uint5 chr_bank[2][2];
bool mirror;
bool latch[2];
uint8 prg_read(unsigned addr) {
if(addr < 0x6000) return cpu.mdr();
if(addr < 0x8000) return prgram.read(addr);
unsigned bank = 0;
switch((addr / 0x2000) & 3) {
case 0: bank = prg_bank; break;
case 1: bank = 0x0d; break;
case 2: bank = 0x0e; break;
case 3: bank = 0x0f; break;
}
return prgrom.read((bank * 0x2000) | (addr & 0x1fff));
}
void prg_write(unsigned addr, uint8 data) {
if(addr < 0x6000) return;
if(addr < 0x8000) return prgram.write(addr, data);
switch(addr & 0xf000) {
case 0xa000: prg_bank = data & 0x0f; break;
case 0xb000: chr_bank[0][0] = data & 0x1f; break;
case 0xc000: chr_bank[0][1] = data & 0x1f; break;
case 0xd000: chr_bank[1][0] = data & 0x1f; break;
case 0xe000: chr_bank[1][1] = data & 0x1f; break;
case 0xf000: mirror = data & 0x01; break;
}
}
unsigned ciram_addr(unsigned addr) const {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
}
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read(ciram_addr(addr));
bool region = addr & 0x1000;
unsigned bank = chr_bank[region][latch[region]];
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
return Board::chr_read((bank * 0x1000) | (addr & 0x0fff));
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write(ciram_addr(addr), data);
bool region = addr & 0x1000;
unsigned bank = chr_bank[region][latch[region]];
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
return Board::chr_write((bank * 0x1000) | (addr & 0x0fff), data);
}
void power() {
}
void reset() {
prg_bank = 0;
chr_bank[0][0] = 0;
chr_bank[0][1] = 0;
chr_bank[1][0] = 0;
chr_bank[1][1] = 0;
mirror = 0;
latch[0] = 0;
latch[1] = 0;
}
void serialize(serializer &s) {
Board::serialize(s);
s.integer(prg_bank);
s.integer(chr_bank[0][0]);
s.integer(chr_bank[0][1]);
s.integer(chr_bank[1][0]);
s.integer(chr_bank[1][1]);
s.integer(mirror);
s.array(latch);
}
NES_PxROM(XML::Document &document) : Board(document) {
revision = Revision::PNROM;
}
};
-101
View File
@@ -1,101 +0,0 @@
struct NES_SxROM : Board {
enum class Revision : unsigned {
SAROM,
SBROM,
SCROM,
SC1ROM,
SEROM,
SFROM,
SGROM,
SHROM,
SH1ROM,
SIROM,
SJROM,
SKROM,
SLROM,
SL1ROM,
SL2ROM,
SL3ROM,
SLRROM,
SMROM,
SNROM,
SOROM,
SUROM,
SXROM,
} revision;
MMC1 mmc1;
void main() {
return mmc1.main();
}
unsigned ram_addr(unsigned addr) {
unsigned bank = 0;
if(revision == Revision::SOROM) bank = (mmc1.chr_bank[0] & 0x08) >> 3;
if(revision == Revision::SUROM) bank = (mmc1.chr_bank[0] & 0x0c) >> 2;
if(revision == Revision::SXROM) bank = (mmc1.chr_bank[0] & 0x0c) >> 2;
return (bank << 13) | (addr & 0x1fff);
}
uint8 prg_read(unsigned addr) {
if((addr & 0xe000) == 0x6000) {
if(revision == Revision::SNROM) {
if(mmc1.chr_bank[0] & 0x10) return cpu.mdr();
}
if(mmc1.ram_disable) return 0x00;
return prgram.read(ram_addr(addr));
}
if(addr & 0x8000) {
addr = mmc1.prg_addr(addr);
if(revision == Revision::SXROM) {
addr |= ((mmc1.chr_bank[0] & 0x10) >> 4) << 18;
}
return prgrom.read(addr);
}
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if((addr & 0xe000) == 0x6000) {
if(revision == Revision::SNROM) {
if(mmc1.chr_bank[0] & 0x10) return;
}
if(mmc1.ram_disable) return;
return prgram.write(ram_addr(addr), data);
}
if(addr & 0x8000) return mmc1.mmio_write(addr, data);
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read(mmc1.ciram_addr(addr));
return Board::chr_read(mmc1.chr_addr(addr));
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write(mmc1.ciram_addr(addr), data);
return Board::chr_write(mmc1.chr_addr(addr), data);
}
void power() {
mmc1.power();
}
void reset() {
mmc1.reset();
}
void serialize(serializer &s) {
Board::serialize(s);
mmc1.serialize(s);
}
NES_SxROM(XML::Document &document) : Board(document), mmc1(*this) {
revision = Revision::SXROM;
}
};
-67
View File
@@ -1,67 +0,0 @@
struct NES_TxROM : Board {
enum class Revision : unsigned {
TBROM,
TEROM,
TFROM,
TGROM,
TKROM,
TKSROM,
TLROM,
TL1ROM,
TL2ROM,
TLSROM,
TNROM,
TQROM,
TR1ROM,
TSROM,
TVROM,
} revision;
MMC3 mmc3;
void main() {
mmc3.main();
}
uint8 prg_read(unsigned addr) {
if((addr & 0xe000) == 0x6000) return mmc3.ram_read(addr);
if(addr & 0x8000) return prgrom.read(mmc3.prg_addr(addr));
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if((addr & 0xe000) == 0x6000) return mmc3.ram_write(addr, data);
if(addr & 0x8000) return mmc3.reg_write(addr, data);
}
uint8 chr_read(unsigned addr) {
mmc3.irq_test(addr);
if(addr & 0x2000) return ppu.ciram_read(mmc3.ciram_addr(addr));
return Board::chr_read(mmc3.chr_addr(addr));
}
void chr_write(unsigned addr, uint8 data) {
mmc3.irq_test(addr);
if(addr & 0x2000) return ppu.ciram_write(mmc3.ciram_addr(addr), data);
return Board::chr_write(mmc3.chr_addr(addr), data);
}
void power() {
mmc3.power();
}
void reset() {
mmc3.reset();
}
void serialize(serializer &s) {
Board::serialize(s);
mmc3.serialize(s);
}
NES_TxROM(XML::Document &document) : Board(document), mmc3(*this) {
revision = Revision::TLROM;
}
};
-55
View File
@@ -1,55 +0,0 @@
//NES-UNROM
//NES-UOROM
struct NES_UxROM : Board {
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
uint4 prg_bank;
uint8 prg_read(unsigned addr) {
if((addr & 0xc000) == 0x8000) return prgrom.read((prg_bank << 14) | (addr & 0x3fff));
if((addr & 0xc000) == 0xc000) return prgrom.read(( 0x0f << 14) | (addr & 0x3fff));
return cpu.mdr();
}
void prg_write(unsigned addr, uint8 data) {
if(addr & 0x8000) prg_bank = data & 0x0f;
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_read(addr);
}
return Board::chr_read(addr);
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_write(addr, data);
}
return Board::chr_write(addr, data);
}
void power() {
}
void reset() {
prg_bank = 0;
}
void serialize(serializer &s) {
Board::serialize(s);
s.integer(prg_bank);
}
NES_UxROM(XML::Document &document) : Board(document) {
settings.mirror = document["cartridge"]["mirror"]["mode"].data == "vertical" ? 1 : 0;
}
};
-226
View File
@@ -1,226 +0,0 @@
//SUNSOFT-5B
struct Sunsoft5B : Board {
uint4 mmu_port;
uint4 apu_port;
uint8 prg_bank[4];
uint8 chr_bank[8];
uint2 mirror;
bool irq_enable;
bool irq_counter_enable;
uint16 irq_counter;
int16 dac[16];
struct Pulse {
bool disable;
uint12 frequency;
uint4 volume;
uint16 counter; //12-bit countdown + 4-bit phase
uint1 duty;
uint4 output;
void clock() {
if(--counter == 0) {
counter = frequency << 4;
duty ^= 1;
}
output = duty ? volume : (uint4)0;
if(disable) output = 0;
}
void reset() {
disable = 1;
frequency = 1;
volume = 0;
counter = 0;
duty = 0;
output = 0;
}
void serialize(serializer &s) {
s.integer(disable);
s.integer(frequency);
s.integer(volume);
s.integer(counter);
s.integer(duty);
s.integer(output);
}
} pulse[3];
void main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_counter_enable) {
if(--irq_counter == 0xffff) {
cpu.set_irq_line(irq_enable);
}
}
pulse[0].clock();
pulse[1].clock();
pulse[2].clock();
int16 output = dac[pulse[0].output] + dac[pulse[1].output] + dac[pulse[2].output];
apu.set_sample(-output);
tick();
}
}
uint8 prg_read(unsigned addr) {
if(addr < 0x6000) return cpu.mdr();
uint8 bank = 0x3f; //((addr & 0xe000) == 0xe000
if((addr & 0xe000) == 0x6000) bank = prg_bank[0];
if((addr & 0xe000) == 0x8000) bank = prg_bank[1];
if((addr & 0xe000) == 0xa000) bank = prg_bank[2];
if((addr & 0xe000) == 0xc000) bank = prg_bank[3];
bool ram_enable = bank & 0x80;
bool ram_select = bank & 0x40;
bank &= 0x3f;
if(ram_select) {
if(ram_enable == false) return cpu.mdr();
return prgram.data[addr & 0x1fff];
}
addr = (bank << 13) | (addr & 0x1fff);
return prgrom.read(addr);
}
void prg_write(unsigned addr, uint8 data) {
if((addr & 0xe000) == 0x6000) {
prgram.data[addr & 0x1fff] = data;
}
if(addr == 0x8000) {
mmu_port = data & 0x0f;
}
if(addr == 0xa000) {
switch(mmu_port) {
case 0: chr_bank[0] = data; break;
case 1: chr_bank[1] = data; break;
case 2: chr_bank[2] = data; break;
case 3: chr_bank[3] = data; break;
case 4: chr_bank[4] = data; break;
case 5: chr_bank[5] = data; break;
case 6: chr_bank[6] = data; break;
case 7: chr_bank[7] = data; break;
case 8: prg_bank[0] = data; break;
case 9: prg_bank[1] = data; break;
case 10: prg_bank[2] = data; break;
case 11: prg_bank[3] = data; break;
case 12: mirror = data & 3; break;
case 13:
irq_enable = data & 0x80;
irq_counter_enable = data & 0x01;
if(irq_enable == 0) cpu.set_irq_line(0);
break;
case 14: irq_counter = (irq_counter & 0xff00) | (data << 0); break;
case 15: irq_counter = (irq_counter & 0x00ff) | (data << 8); break;
}
}
if(addr == 0xc000) {
apu_port = data & 0x0f;
}
if(addr == 0xe000) {
switch(apu_port) {
case 0: pulse[0].frequency = (pulse[0].frequency & 0xff00) | (data << 0); break;
case 1: pulse[0].frequency = (pulse[0].frequency & 0x00ff) | (data << 8); break;
case 2: pulse[1].frequency = (pulse[1].frequency & 0xff00) | (data << 0); break;
case 3: pulse[1].frequency = (pulse[1].frequency & 0x00ff) | (data << 8); break;
case 4: pulse[2].frequency = (pulse[2].frequency & 0xff00) | (data << 0); break;
case 5: pulse[2].frequency = (pulse[2].frequency & 0x00ff) | (data << 8); break;
case 7:
pulse[0].disable = data & 0x01;
pulse[1].disable = data & 0x02;
pulse[2].disable = data & 0x04;
break;
case 8: pulse[0].volume = data & 0x0f; break;
case 9: pulse[1].volume = data & 0x0f; break;
case 10: pulse[2].volume = data & 0x0f; break;
}
}
}
unsigned chr_addr(unsigned addr) {
uint8 bank = (addr >> 10) & 7;
return (chr_bank[bank] << 10) | (addr & 0x03ff);
}
unsigned ciram_addr(unsigned addr) {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal
case 2: return 0x0000 | (addr & 0x03ff); //first
case 3: return 0x0400 | (addr & 0x03ff); //second
}
}
uint8 chr_read(unsigned addr) {
if(addr & 0x2000) return ppu.ciram_read(ciram_addr(addr));
return Board::chr_read(chr_addr(addr));
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) return ppu.ciram_write(ciram_addr(addr), data);
return Board::chr_write(chr_addr(addr), data);
}
void power() {
for(signed n = 0; n < 16; n++) {
double volume = 1.0 / pow(2, 1.0 / 2 * (15 - n));
dac[n] = volume * 8192.0;
}
}
void reset() {
mmu_port = 0;
apu_port = 0;
for(auto &n : prg_bank) n = 0;
for(auto &n : chr_bank) n = 0;
mirror = 0;
irq_enable = 0;
irq_counter_enable = 0;
irq_counter = 0;
pulse[0].reset();
pulse[1].reset();
pulse[2].reset();
}
void serialize(serializer &s) {
Board::serialize(s);
s.integer(mmu_port);
s.integer(apu_port);
s.array(prg_bank);
s.array(chr_bank);
s.integer(mirror);
s.integer(irq_enable);
s.integer(irq_counter_enable);
s.integer(irq_counter);
pulse[0].serialize(s);
pulse[1].serialize(s);
pulse[2].serialize(s);
}
Sunsoft5B(XML::Document &document) : Board(document) {
}
};
-82
View File
@@ -1,82 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
#include "chip/chip.cpp"
#include "board/board.cpp"
Cartridge cartridge;
void Cartridge::Main() {
cartridge.main();
}
void Cartridge::main() {
board->main();
}
void Cartridge::load(const string &manifest) {
information.markup = manifest;
Board::load(manifest); //this call will set Cartridge::board if successful
if(board == nullptr) return;
sha256_ctx sha;
uint8 hash[32];
sha256_init(&sha);
sha256_chunk(&sha, board->prgrom.data, board->prgrom.size);
sha256_chunk(&sha, board->chrrom.data, board->chrrom.size);
sha256_final(&sha);
sha256_hash(&sha, hash);
string result;
for(auto &byte : hash) result.append(hex<2>(byte));
sha256 = result;
system.load();
loaded = true;
}
void Cartridge::unload() {
if(loaded == false) return;
loaded = false;
memory.reset();
}
void Cartridge::power() {
board->power();
}
void Cartridge::reset() {
create(Cartridge::Main, 21477272);
board->reset();
}
Cartridge::Cartridge() {
loaded = false;
}
uint8 Cartridge::prg_read(unsigned addr) {
return board->prg_read(addr);
}
void Cartridge::prg_write(unsigned addr, uint8 data) {
return board->prg_write(addr, data);
}
uint8 Cartridge::chr_read(unsigned addr) {
return board->chr_read(addr);
}
void Cartridge::chr_write(unsigned addr, uint8 data) {
return board->chr_write(addr, data);
}
void Cartridge::scanline(unsigned y) {
return board->scanline(y);
}
void Cartridge::serialize(serializer &s) {
Thread::serialize(s);
return board->serialize(s);
}
}
-44
View File
@@ -1,44 +0,0 @@
#include "chip/chip.hpp"
#include "board/board.hpp"
struct Cartridge : Thread, property<Cartridge> {
static void Main();
void main();
void load(const string &manifest);
void unload();
void power();
void reset();
readonly<bool> loaded;
readonly<string> sha256;
struct Information {
string markup;
} information;
struct Memory {
unsigned id;
string name;
};
vector<Memory> memory;
void serialize(serializer&);
Cartridge();
//privileged:
Board *board;
uint8 prg_read(unsigned addr);
void prg_write(unsigned addr, uint8 data);
uint8 chr_read(unsigned addr);
void chr_write(unsigned addr, uint8 data);
//scanline() is for debugging purposes only:
//boards must detect scanline edges on their own
void scanline(unsigned y);
};
extern Cartridge cartridge;
-17
View File
@@ -1,17 +0,0 @@
#include "mmc1.cpp"
#include "mmc3.cpp"
#include "mmc5.cpp"
#include "mmc6.cpp"
#include "vrc1.cpp"
#include "vrc2.cpp"
#include "vrc3.cpp"
#include "vrc4.cpp"
#include "vrc6.cpp"
#include "vrc7.cpp"
void Chip::tick() {
board.tick();
}
Chip::Chip(Board &board) : board(board) {
}
-7
View File
@@ -1,7 +0,0 @@
struct Board;
struct Chip {
Board &board;
void tick();
Chip(Board &board);
};
-136
View File
@@ -1,136 +0,0 @@
struct MMC1 : Chip {
enum class Revision : unsigned {
MMC1,
MMC1A,
MMC1B1,
MMC1B2,
MMC1B3,
MMC1C,
} revision;
unsigned writedelay;
unsigned shiftaddr;
unsigned shiftdata;
bool chr_mode;
bool prg_size; //0 = 32K, 1 = 16K
bool prg_mode;
uint2 mirror; //0 = first, 1 = second, 2 = vertical, 3 = horizontal
uint5 chr_bank[2];
bool ram_disable;
uint4 prg_bank;
void main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(writedelay) writedelay--;
tick();
}
}
unsigned prg_addr(unsigned addr) {
bool region = addr & 0x4000;
unsigned bank = (prg_bank & ~1) + region;
if(prg_size) {
bank = (region == 0 ? 0x0 : 0xf);
if(region != prg_mode) bank = prg_bank;
}
return (bank << 14) | (addr & 0x3fff);
}
unsigned chr_addr(unsigned addr) {
bool region = addr & 0x1000;
unsigned bank = chr_bank[region];
if(chr_mode == 0) bank = (chr_bank[0] & ~1) | region;
return (bank << 12) | (addr & 0x0fff);
}
unsigned ciram_addr(unsigned addr) {
switch(mirror) {
case 0: return 0x0000 | (addr & 0x03ff);
case 1: return 0x0400 | (addr & 0x03ff);
case 2: return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
case 3: return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
}
}
void mmio_write(unsigned addr, uint8 data) {
if(writedelay) return;
writedelay = 2;
if(data & 0x80) {
shiftaddr = 0;
prg_size = 1;
prg_mode = 1;
} else {
shiftdata = ((data & 1) << 4) | (shiftdata >> 1);
if(++shiftaddr == 5) {
shiftaddr = 0;
switch((addr >> 13) & 3) {
case 0:
chr_mode = (shiftdata & 0x10);
prg_size = (shiftdata & 0x08);
prg_mode = (shiftdata & 0x04);
mirror = (shiftdata & 0x03);
break;
case 1:
chr_bank[0] = (shiftdata & 0x1f);
break;
case 2:
chr_bank[1] = (shiftdata & 0x1f);
break;
case 3:
ram_disable = (shiftdata & 0x10);
prg_bank = (shiftdata & 0x0f);
break;
}
}
}
}
void power() {
}
void reset() {
writedelay = 0;
shiftaddr = 0;
shiftdata = 0;
chr_mode = 0;
prg_size = 1;
prg_mode = 1;
mirror = 0;
chr_bank[0] = 0;
chr_bank[1] = 1;
ram_disable = 0;
prg_bank = 0;
}
void serialize(serializer &s) {
s.integer(writedelay);
s.integer(shiftaddr);
s.integer(shiftdata);
s.integer(chr_mode);
s.integer(prg_size);
s.integer(prg_mode);
s.integer(mirror);
s.array(chr_bank);
s.integer(ram_disable);
s.integer(prg_bank);
}
MMC1(Board &board) : Chip(board) {
revision = Revision::MMC1B2;
}
};
-189
View File
@@ -1,189 +0,0 @@
struct MMC3 : Chip {
bool chr_mode;
bool prg_mode;
uint3 bank_select;
uint8 prg_bank[2];
uint8 chr_bank[6];
bool mirror;
bool ram_enable;
bool ram_write_protect;
uint8 irq_latch;
uint8 irq_counter;
bool irq_enable;
unsigned irq_delay;
bool irq_line;
uint16 chr_abus;
void main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_delay) irq_delay--;
cpu.set_irq_line(irq_line);
tick();
}
}
void irq_test(unsigned addr) {
if(!(chr_abus & 0x1000) && (addr & 0x1000)) {
if(irq_delay == 0) {
if(irq_counter == 0) {
irq_counter = irq_latch;
} else if(--irq_counter == 0) {
if(irq_enable) irq_line = 1;
}
}
irq_delay = 6;
}
chr_abus = addr;
}
unsigned prg_addr(unsigned addr) const {
switch((addr >> 13) & 3) {
case 0:
if(prg_mode == 1) return (0x3e << 13) | (addr & 0x1fff);
return (prg_bank[0] << 13) | (addr & 0x1fff);
case 1:
return (prg_bank[1] << 13) | (addr & 0x1fff);
case 2:
if(prg_mode == 0) return (0x3e << 13) | (addr & 0x1fff);
return (prg_bank[0] << 13) | (addr & 0x1fff);
case 3:
return (0x3f << 13) | (addr & 0x1fff);
}
}
unsigned chr_addr(unsigned addr) const {
if(chr_mode == 0) {
if(addr <= 0x07ff) return (chr_bank[0] << 10) | (addr & 0x07ff);
if(addr <= 0x0fff) return (chr_bank[1] << 10) | (addr & 0x07ff);
if(addr <= 0x13ff) return (chr_bank[2] << 10) | (addr & 0x03ff);
if(addr <= 0x17ff) return (chr_bank[3] << 10) | (addr & 0x03ff);
if(addr <= 0x1bff) return (chr_bank[4] << 10) | (addr & 0x03ff);
if(addr <= 0x1fff) return (chr_bank[5] << 10) | (addr & 0x03ff);
} else {
if(addr <= 0x03ff) return (chr_bank[2] << 10) | (addr & 0x03ff);
if(addr <= 0x07ff) return (chr_bank[3] << 10) | (addr & 0x03ff);
if(addr <= 0x0bff) return (chr_bank[4] << 10) | (addr & 0x03ff);
if(addr <= 0x0fff) return (chr_bank[5] << 10) | (addr & 0x03ff);
if(addr <= 0x17ff) return (chr_bank[0] << 10) | (addr & 0x07ff);
if(addr <= 0x1fff) return (chr_bank[1] << 10) | (addr & 0x07ff);
}
}
unsigned ciram_addr(unsigned addr) const {
if(mirror == 0) return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
if(mirror == 1) return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
}
uint8 ram_read(unsigned addr) {
if(ram_enable) return board.prgram.data[addr & 0x1fff];
return 0x00;
}
void ram_write(unsigned addr, uint8 data) {
if(ram_enable && !ram_write_protect) board.prgram.data[addr & 0x1fff] = data;
}
void reg_write(unsigned addr, uint8 data) {
switch(addr & 0xe001) {
case 0x8000:
chr_mode = data & 0x80;
prg_mode = data & 0x40;
bank_select = data & 0x07;
break;
case 0x8001:
switch(bank_select) {
case 0: chr_bank[0] = data & ~1; break;
case 1: chr_bank[1] = data & ~1; break;
case 2: chr_bank[2] = data; break;
case 3: chr_bank[3] = data; break;
case 4: chr_bank[4] = data; break;
case 5: chr_bank[5] = data; break;
case 6: prg_bank[0] = data & 0x3f; break;
case 7: prg_bank[1] = data & 0x3f; break;
}
break;
case 0xa000:
mirror = data & 0x01;
break;
case 0xa001:
ram_enable = data & 0x80;
ram_write_protect = data & 0x40;
break;
case 0xc000:
irq_latch = data;
break;
case 0xc001:
irq_counter = 0;
break;
case 0xe000:
irq_enable = false;
irq_line = 0;
break;
case 0xe001:
irq_enable = true;
break;
}
}
void power() {
}
void reset() {
chr_mode = 0;
prg_mode = 0;
bank_select = 0;
prg_bank[0] = 0;
prg_bank[1] = 0;
chr_bank[0] = 0;
chr_bank[1] = 0;
chr_bank[2] = 0;
chr_bank[3] = 0;
chr_bank[4] = 0;
chr_bank[5] = 0;
mirror = 0;
ram_enable = 1;
ram_write_protect = 0;
irq_latch = 0;
irq_counter = 0;
irq_enable = false;
irq_delay = 0;
irq_line = 0;
chr_abus = 0;
}
void serialize(serializer &s) {
s.integer(chr_mode);
s.integer(prg_mode);
s.integer(bank_select);
s.array(prg_bank);
s.array(chr_bank);
s.integer(mirror);
s.integer(ram_enable);
s.integer(ram_write_protect);
s.integer(irq_latch);
s.integer(irq_counter);
s.integer(irq_enable);
s.integer(irq_delay);
s.integer(irq_line);
s.integer(chr_abus);
}
MMC3(Board &board) : Chip(board) {
}
};
-497
View File
@@ -1,497 +0,0 @@
struct MMC5 : Chip {
enum class Revision : unsigned {
MMC5,
MMC5B,
} revision;
uint8 exram[1024];
//programmable registers
uint2 prg_mode; //$5100
uint2 chr_mode; //$5101
uint2 prgram_write_protect[2]; //$5102,$5103
uint2 exram_mode; //$5104
uint2 nametable_mode[4]; //$5105
uint8 fillmode_tile; //$5106
uint8 fillmode_color; //$5107
bool ram_select; //$5113
uint2 ram_bank; //$5113
uint8 prg_bank[4]; //$5114-5117
uint10 chr_sprite_bank[8]; //$5120-5127
uint10 chr_bg_bank[4]; //$5128-512b
uint2 chr_bank_hi; //$5130
bool vs_enable; //$5200
bool vs_side; //$5200
uint5 vs_tile; //$5200
uint8 vs_scroll; //$5201
uint8 vs_bank; //$5202
uint8 irq_line; //$5203
bool irq_enable; //$5204
uint8 multiplicand; //$5205
uint8 multiplier; //$5206
//status registers
unsigned cpu_cycle_counter;
unsigned irq_counter;
bool irq_pending;
bool in_frame;
unsigned vcounter;
unsigned hcounter;
uint16 chr_access[4];
bool chr_active;
bool sprite_8x16;
uint8 exbank;
uint8 exattr;
bool vs_fetch;
uint8 vs_vpos;
uint8 vs_hpos;
void main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
//scanline() resets this; if no scanlines detected, enter video blanking period
if(++cpu_cycle_counter >= 200) blank(); //113-114 normal; ~2500 across Vblank period
cpu.set_irq_line(irq_enable && irq_pending);
tick();
}
}
void scanline(unsigned y) {
//used for testing only, to verify MMC5 scanline detection is accurate:
//if(y != vcounter && y <= 240) print(y, " vs ", vcounter, "\n");
}
uint8 prg_access(bool write, unsigned addr, uint8 data = 0x00) {
unsigned bank;
if((addr & 0xe000) == 0x6000) {
bank = (ram_select << 2) | ram_bank;
addr &= 0x1fff;
} else if(prg_mode == 0) {
bank = prg_bank[3] & ~3;
addr &= 0x7fff;
} else if(prg_mode == 1) {
if((addr & 0xc000) == 0x8000) bank = (prg_bank[1] & ~1);
if((addr & 0xe000) == 0xc000) bank = (prg_bank[3] & ~1);
addr &= 0x3fff;
} else if(prg_mode == 2) {
if((addr & 0xe000) == 0x8000) bank = (prg_bank[1] & ~1) | 0;
if((addr & 0xe000) == 0xa000) bank = (prg_bank[1] & ~1) | 1;
if((addr & 0xe000) == 0xc000) bank = (prg_bank[2]);
if((addr & 0xe000) == 0xe000) bank = (prg_bank[3]);
addr &= 0x1fff;
} else if(prg_mode == 3) {
if((addr & 0xe000) == 0x8000) bank = prg_bank[0];
if((addr & 0xe000) == 0xa000) bank = prg_bank[1];
if((addr & 0xe000) == 0xc000) bank = prg_bank[2];
if((addr & 0xe000) == 0xe000) bank = prg_bank[3];
addr &= 0x1fff;
}
bool rom = bank & 0x80;
bank &= 0x7f;
if(write == false) {
if(rom) {
return board.prgrom.read((bank << 13) | addr);
} else {
return board.prgram.read((bank << 13) | addr);
}
} else {
if(rom) {
board.prgrom.write((bank << 13) | addr, data);
} else {
if(prgram_write_protect[0] == 2 && prgram_write_protect[1] == 1) {
board.prgram.write((bank << 13) | addr, data);
}
}
return 0x00;
}
}
uint8 prg_read(unsigned addr) {
if((addr & 0xfc00) == 0x5c00) {
if(exram_mode >= 2) return exram[addr & 0x03ff];
return cpu.mdr();
}
if(addr >= 0x6000) {
return prg_access(0, addr);
}
switch(addr) {
case 0x5204: {
uint8 result = (irq_pending << 7) | (in_frame << 6);
irq_pending = false;
return result;
}
case 0x5205: return (multiplier * multiplicand) >> 0;
case 0x5206: return (multiplier * multiplicand) >> 8;
}
}
void prg_write(unsigned addr, uint8 data) {
if((addr & 0xfc00) == 0x5c00) {
//writes 0x00 *during* Vblank (not during screen rendering ...)
if(exram_mode == 0 || exram_mode == 1) exram[addr & 0x03ff] = in_frame ? data : 0x00;
if(exram_mode == 2) exram[addr & 0x03ff] = data;
return;
}
if(addr >= 0x6000) {
prg_access(1, addr, data);
return;
}
switch(addr) {
case 0x2000:
sprite_8x16 = data & 0x20;
break;
case 0x2001:
//if BG+sprites are disabled; enter video blanking period
if((data & 0x18) == 0) blank();
break;
case 0x5100: prg_mode = data & 3; break;
case 0x5101: chr_mode = data & 3; break;
case 0x5102: prgram_write_protect[0] = data & 3; break;
case 0x5103: prgram_write_protect[1] = data & 3; break;
case 0x5104:
exram_mode = data & 3;
break;
case 0x5105:
nametable_mode[0] = (data & 0x03) >> 0;
nametable_mode[1] = (data & 0x0c) >> 2;
nametable_mode[2] = (data & 0x30) >> 4;
nametable_mode[3] = (data & 0xc0) >> 6;
break;
case 0x5106:
fillmode_tile = data;
break;
case 0x5107:
fillmode_color = data & 3;
fillmode_color |= fillmode_color << 2;
fillmode_color |= fillmode_color << 4;
break;
case 0x5113:
ram_select = data & 0x04;
ram_bank = data & 0x03;
break;
case 0x5114: prg_bank[0] = data; break;
case 0x5115: prg_bank[1] = data; break;
case 0x5116: prg_bank[2] = data; break;
case 0x5117: prg_bank[3] = data | 0x80; break;
case 0x5120: chr_sprite_bank[0] = (chr_bank_hi << 8) | data; chr_active = 0; break;
case 0x5121: chr_sprite_bank[1] = (chr_bank_hi << 8) | data; chr_active = 0; break;
case 0x5122: chr_sprite_bank[2] = (chr_bank_hi << 8) | data; chr_active = 0; break;
case 0x5123: chr_sprite_bank[3] = (chr_bank_hi << 8) | data; chr_active = 0; break;
case 0x5124: chr_sprite_bank[4] = (chr_bank_hi << 8) | data; chr_active = 0; break;
case 0x5125: chr_sprite_bank[5] = (chr_bank_hi << 8) | data; chr_active = 0; break;
case 0x5126: chr_sprite_bank[6] = (chr_bank_hi << 8) | data; chr_active = 0; break;
case 0x5127: chr_sprite_bank[7] = (chr_bank_hi << 8) | data; chr_active = 0; break;
case 0x5128: chr_bg_bank[0] = (chr_bank_hi << 8) | data; chr_active = 1; break;
case 0x5129: chr_bg_bank[1] = (chr_bank_hi << 8) | data; chr_active = 1; break;
case 0x512a: chr_bg_bank[2] = (chr_bank_hi << 8) | data; chr_active = 1; break;
case 0x512b: chr_bg_bank[3] = (chr_bank_hi << 8) | data; chr_active = 1; break;
case 0x5130:
chr_bank_hi = data & 3;
break;
case 0x5200:
vs_enable = data & 0x80;
vs_side = data & 0x40;
vs_tile = data & 0x1f;
break;
case 0x5201:
vs_scroll = data;
break;
case 0x5202:
vs_bank = data;
break;
case 0x5203:
irq_line = data;
break;
case 0x5204:
irq_enable = data & 0x80;
break;
case 0x5205:
multiplicand = data;
break;
case 0x5206:
multiplier = data;
break;
}
}
unsigned chr_sprite_addr(unsigned addr) {
if(chr_mode == 0) {
auto bank = chr_sprite_bank[7];
return (bank * 0x2000) + (addr & 0x1fff);
}
if(chr_mode == 1) {
auto bank = chr_sprite_bank[(addr / 0x1000) * 4 + 3];
return (bank * 0x1000) + (addr & 0x0fff);
}
if(chr_mode == 2) {
auto bank = chr_sprite_bank[(addr / 0x0800) * 2 + 1];
return (bank * 0x0800) + (addr & 0x07ff);
}
if(chr_mode == 3) {
auto bank = chr_sprite_bank[(addr / 0x0400)];
return (bank * 0x0400) + (addr & 0x03ff);
}
}
unsigned chr_bg_addr(unsigned addr) {
addr &= 0x0fff;
if(chr_mode == 0) {
auto bank = chr_bg_bank[3];
return (bank * 0x2000) + (addr & 0x0fff);
}
if(chr_mode == 1) {
auto bank = chr_bg_bank[3];
return (bank * 0x1000) + (addr & 0x0fff);
}
if(chr_mode == 2) {
auto bank = chr_bg_bank[(addr / 0x0800) * 2 + 1];
return (bank * 0x0800) + (addr & 0x07ff);
}
if(chr_mode == 3) {
auto bank = chr_bg_bank[(addr / 0x0400)];
return (bank * 0x0400) + (addr & 0x03ff);
}
}
unsigned chr_vs_addr(unsigned addr) {
return (vs_bank * 0x1000) + (addr & 0x0ff8) + (vs_vpos & 7);
}
void blank() {
in_frame = false;
}
void scanline() {
hcounter = 0;
if(in_frame == false) {
in_frame = true;
irq_pending = false;
vcounter = 0;
} else {
if(vcounter == irq_line) irq_pending = true;
vcounter++;
}
cpu_cycle_counter = 0;
}
uint8 ciram_read(unsigned addr) {
if(vs_fetch && (hcounter & 2) == 0) return exram[vs_vpos / 8 * 32 + vs_hpos / 8];
if(vs_fetch && (hcounter & 2) != 0) return exram[vs_vpos / 32 * 8 + vs_hpos / 32 + 0x03c0];
switch(nametable_mode[(addr >> 10) & 3]) {
case 0: return ppu.ciram_read(0x0000 | (addr & 0x03ff));
case 1: return ppu.ciram_read(0x0400 | (addr & 0x03ff));
case 2: return exram_mode < 2 ? exram[addr & 0x03ff] : 0x00;
case 3: return (hcounter & 2) == 0 ? fillmode_tile : fillmode_color;
}
}
uint8 chr_read(unsigned addr) {
chr_access[0] = chr_access[1];
chr_access[1] = chr_access[2];
chr_access[2] = chr_access[3];
chr_access[3] = addr;
//detect two unused nametable fetches at end of each scanline
if((chr_access[0] & 0x2000) == 0
&& (chr_access[1] & 0x2000)
&& (chr_access[2] & 0x2000)
&& (chr_access[3] & 0x2000)) scanline();
if(in_frame == false) {
vs_fetch = false;
if(addr & 0x2000) return ciram_read(addr);
return board.chrrom.read(chr_active ? chr_bg_addr(addr) : chr_sprite_addr(addr));
}
bool bg_fetch = (hcounter < 256 || hcounter >= 320);
uint8 result = 0x00;
if((hcounter & 7) == 0) {
vs_hpos = hcounter >= 320 ? hcounter - 320 : hcounter + 16;
vs_vpos = vcounter + vs_scroll;
vs_fetch = vs_enable && bg_fetch && exram_mode < 2
&& (vs_side ? vs_hpos / 8 >= vs_tile : vs_hpos / 8 < vs_tile);
if(vs_vpos >= 240) vs_vpos -= 240;
result = ciram_read(addr);
exbank = (chr_bank_hi << 6) | (exram[addr & 0x03ff] & 0x3f);
exattr = exram[addr & 0x03ff] >> 6;
exattr |= exattr << 2;
exattr |= exattr << 4;
} else if((hcounter & 7) == 2) {
result = ciram_read(addr);
if(bg_fetch && exram_mode == 1) result = exattr;
} else {
if(vs_fetch) result = board.chrrom.read(chr_vs_addr(addr));
else if(sprite_8x16 ? bg_fetch : chr_active) result = board.chrrom.read(chr_bg_addr(addr));
else result = board.chrrom.read(chr_sprite_addr(addr));
if(bg_fetch && exram_mode == 1) result = board.chrrom.read(exbank * 0x1000 + (addr & 0x0fff));
}
hcounter += 2;
return result;
}
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) {
switch(nametable_mode[(addr >> 10) & 3]) {
case 0: return ppu.ciram_write(0x0000 | (addr & 0x03ff), data);
case 1: return ppu.ciram_write(0x0400 | (addr & 0x03ff), data);
case 2: exram[addr & 0x03ff] = data; break;
}
}
}
void power() {
}
void reset() {
for(auto &n : exram) n = 0xff;
prg_mode = 3;
chr_mode = 0;
for(auto &n : prgram_write_protect) n = 0;
exram_mode = 0;
for(auto &n : nametable_mode) n = 0;
fillmode_tile = 0;
fillmode_color = 0;
ram_select = 0;
ram_bank = 0;
prg_bank[0] = 0x00;
prg_bank[1] = 0x00;
prg_bank[2] = 0x00;
prg_bank[3] = 0xff;
for(auto &n : chr_sprite_bank) n = 0;
for(auto &n : chr_bg_bank) n = 0;
chr_bank_hi = 0;
vs_enable = 0;
vs_side = 0;
vs_tile = 0;
vs_scroll = 0;
vs_bank = 0;
irq_line = 0;
irq_enable = 0;
multiplicand = 0;
multiplier = 0;
cpu_cycle_counter = 0;
irq_counter = 0;
irq_pending = 0;
in_frame = 0;
vcounter = 0;
hcounter = 0;
for(auto &n : chr_access) n = 0;
chr_active = 0;
sprite_8x16 = 0;
exbank = 0;
exattr = 0;
vs_fetch = 0;
vs_vpos = 0;
vs_hpos = 0;
}
void serialize(serializer &s) {
s.array(exram);
s.integer(prg_mode);
s.integer(chr_mode);
for(auto &n : prgram_write_protect) s.integer(n);
s.integer(exram_mode);
for(auto &n : nametable_mode) s.integer(n);
s.integer(fillmode_tile);
s.integer(fillmode_color);
s.integer(ram_select);
s.integer(ram_bank);
for(auto &n : prg_bank) s.integer(n);
for(auto &n : chr_sprite_bank) s.integer(n);
for(auto &n : chr_bg_bank) s.integer(n);
s.integer(chr_bank_hi);
s.integer(vs_enable);
s.integer(vs_side);
s.integer(vs_tile);
s.integer(vs_scroll);
s.integer(vs_bank);
s.integer(irq_line);
s.integer(irq_enable);
s.integer(multiplicand);
s.integer(multiplier);
s.integer(cpu_cycle_counter);
s.integer(irq_counter);
s.integer(irq_pending);
s.integer(in_frame);
s.integer(vcounter);
s.integer(hcounter);
for(auto &n : chr_access) s.integer(n);
s.integer(chr_active);
s.integer(sprite_8x16);
s.integer(exbank);
s.integer(exattr);
s.integer(vs_fetch);
s.integer(vs_vpos);
s.integer(vs_hpos);
}
MMC5(Board &board) : Chip(board) {
revision = Revision::MMC5;
}
};
-200
View File
@@ -1,200 +0,0 @@
struct MMC6 : Chip {
bool chr_mode;
bool prg_mode;
bool ram_enable;
uint3 bank_select;
uint8 prg_bank[2];
uint8 chr_bank[6];
bool mirror;
bool ram_readable[2];
bool ram_writable[2];
uint8 irq_latch;
uint8 irq_counter;
bool irq_enable;
unsigned irq_delay;
bool irq_line;
uint16 chr_abus;
void main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_delay) irq_delay--;
cpu.set_irq_line(irq_line);
tick();
}
}
void irq_test(unsigned addr) {
if(!(chr_abus & 0x1000) && (addr & 0x1000)) {
if(irq_delay == 0) {
if(irq_counter == 0) {
irq_counter = irq_latch;
} else if(--irq_counter == 0) {
if(irq_enable) irq_line = 1;
}
}
irq_delay = 6;
}
chr_abus = addr;
}
unsigned prg_addr(unsigned addr) const {
switch((addr >> 13) & 3) {
case 0:
if(prg_mode == 1) return (0x3e << 13) | (addr & 0x1fff);
return (prg_bank[0] << 13) | (addr & 0x1fff);
case 1:
return (prg_bank[1] << 13) | (addr & 0x1fff);
case 2:
if(prg_mode == 0) return (0x3e << 13) | (addr & 0x1fff);
return (prg_bank[0] << 13) | (addr & 0x1fff);
case 3:
return (0x3f << 13) | (addr & 0x1fff);
}
}
unsigned chr_addr(unsigned addr) const {
if(chr_mode == 0) {
if(addr <= 0x07ff) return (chr_bank[0] << 10) | (addr & 0x07ff);
if(addr <= 0x0fff) return (chr_bank[1] << 10) | (addr & 0x07ff);
if(addr <= 0x13ff) return (chr_bank[2] << 10) | (addr & 0x03ff);
if(addr <= 0x17ff) return (chr_bank[3] << 10) | (addr & 0x03ff);
if(addr <= 0x1bff) return (chr_bank[4] << 10) | (addr & 0x03ff);
if(addr <= 0x1fff) return (chr_bank[5] << 10) | (addr & 0x03ff);
} else {
if(addr <= 0x03ff) return (chr_bank[2] << 10) | (addr & 0x03ff);
if(addr <= 0x07ff) return (chr_bank[3] << 10) | (addr & 0x03ff);
if(addr <= 0x0bff) return (chr_bank[4] << 10) | (addr & 0x03ff);
if(addr <= 0x0fff) return (chr_bank[5] << 10) | (addr & 0x03ff);
if(addr <= 0x17ff) return (chr_bank[0] << 10) | (addr & 0x07ff);
if(addr <= 0x1fff) return (chr_bank[1] << 10) | (addr & 0x07ff);
}
}
unsigned ciram_addr(unsigned addr) const {
if(mirror == 0) return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
if(mirror == 1) return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
}
uint8 ram_read(unsigned addr) {
if(ram_enable == false) return cpu.mdr();
if(ram_readable[0] == false && ram_readable[1] == false) return cpu.mdr();
bool region = addr & 0x0200;
if(ram_readable[region] == false) return 0x00;
return board.prgram.read((region * 0x0200) + (addr & 0x01ff));
}
void ram_write(unsigned addr, uint8 data) {
if(ram_enable == false) return;
bool region = addr & 0x0200;
if(ram_writable[region] == false) return;
return board.prgram.write((region * 0x0200) + (addr & 0x01ff), data);
}
void reg_write(unsigned addr, uint8 data) {
switch(addr & 0xe001) {
case 0x8000:
chr_mode = data & 0x80;
prg_mode = data & 0x40;
ram_enable = data & 0x20;
bank_select = data & 0x07;
if(ram_enable == false) {
for(auto &n : ram_readable) n = false;
for(auto &n : ram_writable) n = false;
}
break;
case 0x8001:
switch(bank_select) {
case 0: chr_bank[0] = data & ~1; break;
case 1: chr_bank[1] = data & ~1; break;
case 2: chr_bank[2] = data; break;
case 3: chr_bank[3] = data; break;
case 4: chr_bank[4] = data; break;
case 5: chr_bank[5] = data; break;
case 6: prg_bank[0] = data & 0x3f; break;
case 7: prg_bank[1] = data & 0x3f; break;
}
break;
case 0xa000:
mirror = data & 0x01;
break;
case 0xa001:
if(ram_enable == false) break;
ram_readable[1] = data & 0x80;
ram_writable[1] = data & 0x40;
ram_readable[0] = data & 0x20;
ram_writable[0] = data & 0x10;
break;
case 0xc000:
irq_latch = data;
break;
case 0xc001:
irq_counter = 0;
break;
case 0xe000:
irq_enable = false;
irq_line = 0;
break;
case 0xe001:
irq_enable = true;
break;
}
}
void power() {
}
void reset() {
chr_mode = 0;
prg_mode = 0;
ram_enable = 0;
bank_select = 0;
for(auto &n : prg_bank) n = 0;
for(auto &n : chr_bank) n = 0;
mirror = 0;
for(auto &n : ram_readable) n = 0;
for(auto &n : ram_writable) n = 0;
irq_latch = 0;
irq_counter = 0;
irq_enable = 0;
irq_delay = 0;
irq_line = 0;
chr_abus = 0;
}
void serialize(serializer &s) {
s.integer(chr_mode);
s.integer(prg_mode);
s.integer(ram_enable);
s.integer(bank_select);
for(auto &n : prg_bank) s.integer(n);
for(auto &n : chr_bank) s.integer(n);
s.integer(mirror);
for(auto &n : ram_readable) s.integer(n);
for(auto &n : ram_writable) s.integer(n);
s.integer(irq_latch);
s.integer(irq_counter);
s.integer(irq_enable);
s.integer(irq_delay);
s.integer(irq_line);
s.integer(chr_abus);
}
MMC6(Board &board) : Chip(board) {
}
};
-80
View File
@@ -1,80 +0,0 @@
struct VRC1 : Chip {
uint4 prg_bank[3];
uint4 chr_banklo[2];
bool chr_bankhi[2];
bool mirror;
unsigned prg_addr(unsigned addr) const {
unsigned bank = 0x0f;
if((addr & 0xe000) == 0x8000) bank = prg_bank[0];
if((addr & 0xe000) == 0xa000) bank = prg_bank[1];
if((addr & 0xe000) == 0xc000) bank = prg_bank[2];
return (bank * 0x2000) + (addr & 0x1fff);
}
unsigned chr_addr(unsigned addr) const {
unsigned bank = chr_banklo[(bool)(addr & 0x1000)];
bank |= chr_bankhi[(bool)(addr & 0x1000)] << 4;
return (bank * 0x1000) + (addr & 0x0fff);
}
unsigned ciram_addr(unsigned addr) const {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
}
throw;
}
void reg_write(unsigned addr, uint8 data) {
switch(addr & 0xf000) {
case 0x8000:
prg_bank[0] = data & 0x0f;
break;
case 0x9000:
chr_bankhi[1] = data & 0x04;
chr_bankhi[0] = data & 0x02;
mirror = data & 0x01;
break;
case 0xa000:
prg_bank[1] = data & 0x0f;
break;
case 0xc000:
prg_bank[2] = data & 0x0f;
break;
case 0xe000:
chr_banklo[0] = data & 0x0f;
break;
case 0xf000:
chr_banklo[1] = data & 0x0f;
break;
}
}
void power() {
}
void reset() {
for(auto &n : prg_bank) n = 0;
for(auto &n : chr_banklo) n = 0;
for(auto &n : chr_bankhi) n = 0;
mirror = 0;
}
void serialize(serializer &s) {
for(auto &n : prg_bank) s.integer(n);
for(auto &n : chr_banklo) s.integer(n);
for(auto &n : chr_bankhi) s.integer(n);
s.integer(mirror);
}
VRC1(Board &board) : Chip(board) {
}
};
-110
View File
@@ -1,110 +0,0 @@
struct VRC2 : Chip {
uint5 prg_bank[2];
uint8 chr_bank[8];
uint2 mirror;
bool latch;
unsigned prg_addr(unsigned addr) const {
unsigned bank;
switch(addr & 0xe000) {
case 0x8000: bank = prg_bank[0]; break;
case 0xa000: bank = prg_bank[1]; break;
case 0xc000: bank = 0x1e; break;
case 0xe000: bank = 0x1f; break;
}
return (bank * 0x2000) + (addr & 0x1fff);
}
unsigned chr_addr(unsigned addr) const {
unsigned bank = chr_bank[addr / 0x0400];
return (bank * 0x0400) + (addr & 0x03ff);
}
unsigned ciram_addr(unsigned addr) const {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
}
throw;
}
uint8 ram_read(unsigned addr) {
if(board.prgram.size == 0) {
if((addr & 0xf000) == 0x6000) return cpu.mdr() | latch;
return cpu.mdr();
}
return board.prgram.read(addr & 0x1fff);
}
void ram_write(unsigned addr, uint8 data) {
if(board.prgram.size == 0) {
if((addr & 0xf000) == 0x6000) latch = data & 0x01;
return;
}
return board.prgram.write(addr & 0x1fff, data);
}
void reg_write(unsigned addr, uint8 data) {
switch(addr) {
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
prg_bank[0] = data & 0x1f;
break;
case 0x9000: case 0x9001: case 0x9002: case 0x9003:
mirror = data & 0x03;
break;
case 0xa000: case 0xa001: case 0xa002: case 0xa003:
prg_bank[1] = data & 0x1f;
break;
case 0xb000: chr_bank[0] = (chr_bank[0] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xb001: chr_bank[0] = (chr_bank[0] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xb002: chr_bank[1] = (chr_bank[1] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xb003: chr_bank[1] = (chr_bank[1] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xc000: chr_bank[2] = (chr_bank[2] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xc001: chr_bank[2] = (chr_bank[2] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xc002: chr_bank[3] = (chr_bank[3] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xc003: chr_bank[3] = (chr_bank[3] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xd000: chr_bank[4] = (chr_bank[4] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xd001: chr_bank[4] = (chr_bank[4] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xd002: chr_bank[5] = (chr_bank[5] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xd003: chr_bank[5] = (chr_bank[5] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xe000: chr_bank[6] = (chr_bank[6] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xe001: chr_bank[6] = (chr_bank[6] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xe002: chr_bank[7] = (chr_bank[7] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xe003: chr_bank[7] = (chr_bank[7] & 0x0f) | ((data & 0x0f) << 4); break;
}
}
void power() {
}
void reset() {
for(auto &n : prg_bank) n = 0;
for(auto &n : chr_bank) n = 0;
mirror = 0;
latch = 0;
}
void serialize(serializer &s) {
for(auto &n : prg_bank) s.integer(n);
for(auto &n : chr_bank) s.integer(n);
s.integer(mirror);
s.integer(latch);
}
VRC2(Board &board) : Chip(board) {
}
};
-100
View File
@@ -1,100 +0,0 @@
struct VRC3 : Chip {
uint4 prg_bank;
bool irq_mode;
bool irq_enable;
bool irq_acknowledge;
uint16 irq_latch;
struct {
union {
uint16 w;
struct { uint8 order_lsb2(l, h); };
};
} irq_counter;
bool irq_line;
void main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_enable) {
if(irq_mode == 0) { //16-bit
if(++irq_counter.w == 0) {
irq_line = 1;
irq_enable = irq_acknowledge;
irq_counter.w = irq_latch;
}
}
if(irq_mode == 1) { //8-bit
if(++irq_counter.l == 0) {
irq_line = 1;
irq_enable = irq_acknowledge;
irq_counter.l = irq_latch;
}
}
}
cpu.set_irq_line(irq_line);
tick();
}
}
unsigned prg_addr(unsigned addr) const {
unsigned bank = (addr < 0xc000 ? (unsigned)prg_bank : 0x0f);
return (bank * 0x4000) + (addr & 0x3fff);
}
void reg_write(unsigned addr, uint8 data) {
switch(addr & 0xf000) {
case 0x8000: irq_latch = (irq_latch & 0xfff0) | ((data & 0x0f) << 0); break;
case 0x9000: irq_latch = (irq_latch & 0xff0f) | ((data & 0x0f) << 4); break;
case 0xa000: irq_latch = (irq_latch & 0xf0ff) | ((data & 0x0f) << 8); break;
case 0xb000: irq_latch = (irq_latch & 0x0fff) | ((data & 0x0f) << 12); break;
case 0xc000:
irq_mode = data & 0x04;
irq_enable = data & 0x02;
irq_acknowledge = data & 0x01;
if(irq_enable) irq_counter.w = irq_latch;
break;
case 0xd000:
irq_line = 0;
irq_enable = irq_acknowledge;
break;
case 0xf000:
prg_bank = data & 0x0f;
break;
}
}
void power() {
}
void reset() {
prg_bank = 0;
irq_mode = 0;
irq_enable = 0;
irq_acknowledge = 0;
irq_latch = 0;
irq_counter.w = 0;
irq_line = 0;
}
void serialize(serializer &s) {
s.integer(prg_bank);
s.integer(irq_mode);
s.integer(irq_enable);
s.integer(irq_acknowledge);
s.integer(irq_latch);
s.integer(irq_counter.w);
s.integer(irq_line);
}
VRC3(Board &board) : Chip(board) {
}
};
-184
View File
@@ -1,184 +0,0 @@
struct VRC4 : Chip {
bool prg_mode;
uint5 prg_bank[2];
uint2 mirror;
uint8 chr_bank[8];
uint8 irq_latch;
bool irq_mode;
bool irq_enable;
bool irq_acknowledge;
uint8 irq_counter;
signed irq_scalar;
bool irq_line;
void main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_enable) {
if(irq_mode == 0) {
irq_scalar -= 3;
if(irq_scalar <= 0) {
irq_scalar += 341;
if(irq_counter == 0xff) {
irq_counter = irq_latch;
irq_line = 1;
} else {
irq_counter++;
}
}
}
if(irq_mode == 1) {
if(irq_counter == 0xff) {
irq_counter = irq_latch;
irq_line = 1;
} else {
irq_counter++;
}
}
}
cpu.set_irq_line(irq_line);
tick();
}
}
unsigned prg_addr(unsigned addr) const {
unsigned bank = 0, banks = board.prgrom.size / 0x2000;
switch(addr & 0xe000) {
case 0x8000: bank = prg_mode == 0 ? (unsigned)prg_bank[0] : banks - 2; break;
case 0xa000: bank = prg_bank[1]; break;
case 0xc000: bank = prg_mode == 0 ? banks - 2 : (unsigned)prg_bank[0]; break;
case 0xe000: bank = banks - 1; break;
}
return (bank * 0x2000) + (addr & 0x1fff);
}
unsigned chr_addr(unsigned addr) const {
unsigned bank = chr_bank[addr / 0x0400];
return (bank * 0x0400) + (addr & 0x03ff);
}
unsigned ciram_addr(unsigned addr) const {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
}
throw;
}
void reg_write(unsigned addr, uint8 data) {
switch(addr) {
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
prg_bank[0] = data & 0x1f;
break;
case 0x9000: case 0x9001:
mirror = data & 0x03;
break;
case 0x9002: case 0x9003:
prg_mode = data & 0x02;
break;
case 0xa000: case 0xa001: case 0xa002: case 0xa003:
prg_bank[1] = data & 0x1f;
break;
case 0xb000: chr_bank[0] = (chr_bank[0] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xb001: chr_bank[0] = (chr_bank[0] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xb002: chr_bank[1] = (chr_bank[1] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xb003: chr_bank[1] = (chr_bank[1] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xc000: chr_bank[2] = (chr_bank[2] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xc001: chr_bank[2] = (chr_bank[2] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xc002: chr_bank[3] = (chr_bank[3] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xc003: chr_bank[3] = (chr_bank[3] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xd000: chr_bank[4] = (chr_bank[4] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xd001: chr_bank[4] = (chr_bank[4] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xd002: chr_bank[5] = (chr_bank[5] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xd003: chr_bank[5] = (chr_bank[5] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xe000: chr_bank[6] = (chr_bank[6] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xe001: chr_bank[6] = (chr_bank[6] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xe002: chr_bank[7] = (chr_bank[7] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xe003: chr_bank[7] = (chr_bank[7] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xf000:
irq_latch = (irq_latch & 0xf0) | ((data & 0x0f) << 0);
break;
case 0xf001:
irq_latch = (irq_latch & 0x0f) | ((data & 0x0f) << 4);
break;
case 0xf002:
irq_mode = data & 0x04;
irq_enable = data & 0x02;
irq_acknowledge = data & 0x01;
if(irq_enable) {
irq_counter = irq_latch;
irq_scalar = 341;
}
irq_line = 0;
break;
case 0xf003:
irq_enable = irq_acknowledge;
irq_line = 0;
break;
}
}
void power() {
}
void reset() {
prg_mode = 0;
for(auto &n : prg_bank) n = 0;
mirror = 0;
for(auto &n : chr_bank) n = 0;
irq_latch = 0;
irq_mode = 0;
irq_enable = 0;
irq_acknowledge = 0;
irq_counter = 0;
irq_scalar = 0;
irq_line = 0;
}
void serialize(serializer &s) {
s.integer(prg_mode);
for(auto &n : prg_bank) s.integer(n);
s.integer(mirror);
for(auto &n : chr_bank) s.integer(n);
s.integer(irq_latch);
s.integer(irq_mode);
s.integer(irq_enable);
s.integer(irq_acknowledge);
s.integer(irq_counter);
s.integer(irq_scalar);
s.integer(irq_line);
}
VRC4(Board &board) : Chip(board) {
}
};
-321
View File
@@ -1,321 +0,0 @@
struct VRC6 : Chip {
uint8 prg_bank[2];
uint8 chr_bank[8];
uint2 mirror;
uint8 irq_latch;
bool irq_mode;
bool irq_enable;
bool irq_acknowledge;
uint8 irq_counter;
signed irq_scalar;
bool irq_line;
struct Pulse {
bool mode;
uint3 duty;
uint4 volume;
bool enable;
uint12 frequency;
uint12 divider;
uint4 cycle;
uint4 output;
void clock() {
if(--divider == 0) {
divider = frequency + 1;
cycle++;
output = (mode == 1 || cycle > duty) ? volume : (uint4)0;
}
if(enable == false) output = 0;
}
void serialize(serializer &s) {
s.integer(mode);
s.integer(duty);
s.integer(volume);
s.integer(enable);
s.integer(frequency);
s.integer(divider);
s.integer(cycle);
s.integer(output);
}
} pulse1, pulse2;
struct Sawtooth {
uint6 rate;
bool enable;
uint12 frequency;
uint12 divider;
uint1 phase;
uint3 stage;
uint8 accumulator;
uint5 output;
void clock() {
if(--divider == 0) {
divider = frequency + 1;
if(++phase == 0) {
accumulator += rate;
if(++stage == 7) {
stage = 0;
accumulator = 0;
}
}
}
output = accumulator >> 3;
if(enable == false) output = 0;
}
void serialize(serializer &s) {
s.integer(rate);
s.integer(enable);
s.integer(frequency);
s.integer(divider);
s.integer(phase);
s.integer(stage);
s.integer(accumulator);
s.integer(output);
}
} sawtooth;
void main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_enable) {
if(irq_mode == 0) {
irq_scalar -= 3;
if(irq_scalar <= 0) {
irq_scalar += 341;
if(irq_counter == 0xff) {
irq_counter = irq_latch;
irq_line = 1;
} else {
irq_counter++;
}
}
}
if(irq_mode == 1) {
if(irq_counter == 0xff) {
irq_counter = irq_latch;
irq_line = 1;
} else {
irq_counter++;
}
}
}
cpu.set_irq_line(irq_line);
pulse1.clock();
pulse2.clock();
sawtooth.clock();
signed output = (pulse1.output + pulse2.output + sawtooth.output) << 7;
apu.set_sample(-output);
tick();
}
}
unsigned prg_addr(unsigned addr) const {
if((addr & 0xc000) == 0x8000) return (prg_bank[0] << 14) | (addr & 0x3fff);
if((addr & 0xe000) == 0xc000) return (prg_bank[1] << 13) | (addr & 0x1fff);
if((addr & 0xe000) == 0xe000) return ( 0xff << 13) | (addr & 0x1fff);
}
unsigned chr_addr(unsigned addr) const {
unsigned bank = chr_bank[(addr >> 10) & 7];
return (bank << 10) | (addr & 0x03ff);
}
unsigned ciram_addr(unsigned addr) const {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
}
}
uint8 ram_read(unsigned addr) {
return board.prgram.data[addr & 0x1fff];
}
void ram_write(unsigned addr, uint8 data) {
board.prgram.data[addr & 0x1fff] = data;
}
void reg_write(unsigned addr, uint8 data) {
switch(addr) {
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
prg_bank[0] = data;
break;
case 0x9000:
pulse1.mode = data & 0x80;
pulse1.duty = (data & 0x70) >> 4;
pulse1.volume = data & 0x0f;
break;
case 0x9001:
pulse1.frequency = (pulse1.frequency & 0x0f00) | ((data & 0xff) << 0);
break;
case 0x9002:
pulse1.frequency = (pulse1.frequency & 0x00ff) | ((data & 0x0f) << 8);
pulse1.enable = data & 0x80;
break;
case 0xa000:
pulse2.mode = data & 0x80;
pulse2.duty = (data & 0x70) >> 4;
pulse2.volume = data & 0x0f;
break;
case 0xa001:
pulse2.frequency = (pulse2.frequency & 0x0f00) | ((data & 0xff) << 0);
break;
case 0xa002:
pulse2.frequency = (pulse2.frequency & 0x00ff) | ((data & 0x0f) << 8);
pulse2.enable = data & 0x80;
break;
case 0xb000:
sawtooth.rate = data & 0x3f;
break;
case 0xb001:
sawtooth.frequency = (sawtooth.frequency & 0x0f00) | ((data & 0xff) << 0);
break;
case 0xb002:
sawtooth.frequency = (sawtooth.frequency & 0x00ff) | ((data & 0x0f) << 8);
sawtooth.enable = data & 0x80;
break;
case 0xb003:
mirror = (data >> 2) & 3;
break;
case 0xc000: case 0xc001: case 0xc002: case 0xc003:
prg_bank[1] = data;
break;
case 0xd000: case 0xd001: case 0xd002: case 0xd003:
chr_bank[0 + (addr & 3)] = data;
break;
case 0xe000: case 0xe001: case 0xe002: case 0xe003:
chr_bank[4 + (addr & 3)] = data;
break;
case 0xf000:
irq_latch = data;
break;
case 0xf001:
irq_mode = data & 0x04;
irq_enable = data & 0x02;
irq_acknowledge = data & 0x01;
if(irq_enable) {
irq_counter = irq_latch;
irq_scalar = 341;
}
irq_line = 0;
break;
case 0xf002:
irq_enable = irq_acknowledge;
irq_line = 0;
break;
}
}
void power() {
}
void reset() {
prg_bank[0] = 0;
prg_bank[1] = 0;
chr_bank[0] = 0;
chr_bank[1] = 0;
chr_bank[2] = 0;
chr_bank[3] = 0;
chr_bank[4] = 0;
chr_bank[5] = 0;
chr_bank[6] = 0;
chr_bank[7] = 0;
mirror = 0;
irq_latch = 0;
irq_mode = 0;
irq_enable = 0;
irq_acknowledge = 0;
irq_counter = 0;
irq_scalar = 0;
irq_line = 0;
pulse1.mode = 0;
pulse1.duty = 0;
pulse1.volume = 0;
pulse1.enable = 0;
pulse1.frequency = 0;
pulse1.divider = 1;
pulse1.cycle = 0;
pulse1.output = 0;
pulse2.mode = 0;
pulse2.duty = 0;
pulse2.volume = 0;
pulse2.enable = 0;
pulse2.frequency = 0;
pulse2.divider = 1;
pulse2.cycle = 0;
pulse2.output = 0;
sawtooth.rate = 0;
sawtooth.enable = 0;
sawtooth.frequency = 0;
sawtooth.divider = 1;
sawtooth.phase = 0;
sawtooth.stage = 0;
sawtooth.accumulator = 0;
sawtooth.output = 0;
}
void serialize(serializer &s) {
pulse1.serialize(s);
pulse2.serialize(s);
sawtooth.serialize(s);
s.array(prg_bank);
s.array(chr_bank);
s.integer(mirror);
s.integer(irq_latch);
s.integer(irq_mode);
s.integer(irq_enable);
s.integer(irq_acknowledge);
s.integer(irq_counter);
s.integer(irq_scalar);
s.integer(irq_line);
}
VRC6(Board &board) : Chip(board) {
}
};
-154
View File
@@ -1,154 +0,0 @@
//Konami VRC7
//Yamaha YM2413 OPLL audio - not emulated
struct VRC7 : Chip {
uint8 prg_bank[3];
uint8 chr_bank[8];
uint2 mirror;
uint8 irq_latch;
bool irq_mode;
bool irq_enable;
bool irq_acknowledge;
uint8 irq_counter;
signed irq_scalar;
bool irq_line;
void main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_enable) {
if(irq_mode == 0) {
irq_scalar -= 3;
if(irq_scalar <= 0) {
irq_scalar += 341;
if(irq_counter == 0xff) {
irq_counter = irq_latch;
irq_line = 1;
} else {
irq_counter++;
}
}
}
if(irq_mode == 1) {
if(irq_counter == 0xff) {
irq_counter = irq_latch;
irq_line = 1;
} else {
irq_counter++;
}
}
}
cpu.set_irq_line(irq_line);
tick();
}
}
void reg_write(unsigned addr, uint8 data) {
switch(addr) {
case 0x8000: prg_bank[0] = data; break;
case 0x8010: prg_bank[1] = data; break;
case 0x9000: prg_bank[2] = data; break;
case 0x9010: break; //APU addr port
case 0x9030: break; //APU data port
case 0xa000: chr_bank[0] = data; break;
case 0xa010: chr_bank[1] = data; break;
case 0xb000: chr_bank[2] = data; break;
case 0xb010: chr_bank[3] = data; break;
case 0xc000: chr_bank[4] = data; break;
case 0xc010: chr_bank[5] = data; break;
case 0xd000: chr_bank[6] = data; break;
case 0xd010: chr_bank[7] = data; break;
case 0xe000: mirror = data & 0x03; break;
case 0xe010:
irq_latch = data;
break;
case 0xf000:
irq_mode = data & 0x04;
irq_enable = data & 0x02;
irq_acknowledge = data & 0x01;
if(irq_enable) {
irq_counter = irq_latch;
irq_scalar = 341;
}
irq_line = 0;
break;
case 0xf010:
irq_enable = irq_acknowledge;
irq_line = 0;
break;
}
}
unsigned prg_addr(unsigned addr) const {
unsigned bank = 0;
switch(addr & 0xe000) {
case 0x8000: bank = prg_bank[0]; break;
case 0xa000: bank = prg_bank[1]; break;
case 0xc000: bank = prg_bank[2]; break;
case 0xe000: bank = 0xff; break;
}
return (bank * 0x2000) + (addr & 0x1fff);
}
unsigned chr_addr(unsigned addr) const {
unsigned bank = chr_bank[addr / 0x0400];
return (bank * 0x0400) + (addr & 0x03ff);
}
unsigned ciram_addr(unsigned addr) const {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
}
}
void power() {
}
void reset() {
for(auto &n : prg_bank) n = 0;
for(auto &n : chr_bank) n = 0;
mirror = 0;
irq_latch = 0;
irq_mode = 0;
irq_enable = 0;
irq_acknowledge = 0;
irq_counter = 0;
irq_scalar = 0;
irq_line = 0;
}
void serialize(serializer &s) {
s.array(prg_bank);
s.array(chr_bank);
s.integer(mirror);
s.integer(irq_latch);
s.integer(irq_mode);
s.integer(irq_enable);
s.integer(irq_acknowledge);
s.integer(irq_counter);
s.integer(irq_scalar);
s.integer(irq_line);
}
VRC7(Board &board) : Chip(board) {
}
};
-88
View File
@@ -1,88 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
Cheat cheat;
bool Cheat::decode(const string &code_, unsigned &addr, unsigned &data, unsigned &comp) {
static bool initialize = false;
static uint8 mapProActionReplay[256], mapGameGenie[256];
if(initialize == false) {
initialize = true;
for(auto &n : mapProActionReplay) n = ~0;
mapProActionReplay['0'] = 0; mapProActionReplay['1'] = 1; mapProActionReplay['2'] = 2; mapProActionReplay['3'] = 3;
mapProActionReplay['4'] = 4; mapProActionReplay['5'] = 5; mapProActionReplay['6'] = 6; mapProActionReplay['7'] = 7;
mapProActionReplay['8'] = 8; mapProActionReplay['9'] = 9; mapProActionReplay['A'] = 10; mapProActionReplay['B'] = 11;
mapProActionReplay['C'] = 12; mapProActionReplay['D'] = 13; mapProActionReplay['E'] = 14; mapProActionReplay['F'] = 15;
for(auto &n : mapGameGenie) n = ~0;
mapGameGenie['A'] = 0; mapGameGenie['P'] = 1; mapGameGenie['Z'] = 2; mapGameGenie['L'] = 3;
mapGameGenie['G'] = 4; mapGameGenie['I'] = 5; mapGameGenie['T'] = 6; mapGameGenie['Y'] = 7;
mapGameGenie['E'] = 8; mapGameGenie['O'] = 9; mapGameGenie['X'] = 10; mapGameGenie['U'] = 11;
mapGameGenie['K'] = 12; mapGameGenie['S'] = 13; mapGameGenie['V'] = 14; mapGameGenie['N'] = 15;
}
string code = code_;
code.upper();
unsigned length = code.length(), bits = 0;
if(code.wildcard("????:??")) {
code = { substr(code, 0, 4), substr(code, 5, 2) };
for(unsigned n = 0; n < 6; n++) if(mapProActionReplay[code[n]] > 15) return false;
bits = hex(code);
addr = (bits >> 8) & 0xffff;
data = (bits >> 0) & 0xff;
comp = ~0;
return true;
}
if(code.wildcard("????:??:??")) {
code = { substr(code, 0, 4), substr(code, 5, 2), substr(code, 8, 2) };
for(unsigned n = 0; n < 8; n++) if(mapProActionReplay[code[n]] > 15) return false;
bits = hex(code);
addr = (bits >> 16) & 0xffff;
data = (bits >> 8) & 0xff;
comp = (bits >> 0) & 0xff;
return true;
}
if(length == 6) {
for(unsigned n = 0; n < 6; n++) if(mapGameGenie[code[n]] > 15) return false;
for(unsigned n = 0; n < 6; n++) bits |= mapGameGenie[code[n]] << (20 - n * 4);
unsigned addrTable[] = { 10, 9, 8, 7, 2, 1, 0, 19, 14, 13, 12, 11, 6, 5, 4 };
unsigned dataTable[] = { 23, 18, 17, 16, 3, 22, 21, 20 };
addr = 0x8000, data = 0x00, comp = ~0;
for(unsigned n = 0; n < 15; n++) addr |= bits & (1 << addrTable[n]) ? 0x4000 >> n : 0;
for(unsigned n = 0; n < 8; n++) data |= bits & (1 << dataTable[n]) ? 0x80 >> n : 0;
return true;
}
if(length == 8) {
for(unsigned n = 0; n < 8; n++) if(mapGameGenie[code[n]] > 15) return false;
for(unsigned n = 0; n < 8; n++) bits |= mapGameGenie[code[n]] << (28 - n * 4);
unsigned addrTable[] = { 18, 17, 16, 15, 10, 9, 8, 27, 22, 21, 20, 19, 14, 13, 12 };
unsigned dataTable[] = { 31, 26, 25, 24, 3, 30, 29, 28 };
unsigned compTable[] = { 7, 2, 1, 0, 11, 6, 5,4 };
addr = 0x8000, data = 0x00, comp = 0x00;
for(unsigned n = 0; n < 15; n++) addr |= bits & (1 << addrTable[n]) ? 0x4000 >> n : 0;
for(unsigned n = 0; n < 8; n++) data |= bits & (1 << dataTable[n]) ? 0x80 >> n : 0;
for(unsigned n = 0; n < 8; n++) comp |= bits & (1 << compTable[n]) ? 0x80 >> n : 0;
return true;
}
return false;
}
void Cheat::synchronize() {
for(auto &n : override) n = false;
for(unsigned n = 0; n < size(); n++) {
override[operator[](n).addr] = true;
}
}
}
-14
View File
@@ -1,14 +0,0 @@
struct CheatCode {
unsigned addr;
unsigned data;
unsigned comp;
};
struct Cheat : public vector<CheatCode> {
static bool decode(const string &code, unsigned &addr, unsigned &data, unsigned &comp);
void synchronize();
bool override[65536];
};
extern Cheat cheat;
-110
View File
@@ -1,110 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
#include "timing.cpp"
#include "serialization.cpp"
CPU cpu;
void CPU::Enter() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
cpu.main();
}
}
void CPU::main() {
if(status.interrupt_pending) {
interrupt();
return;
}
exec();
}
void CPU::add_clocks(unsigned clocks) {
apu.clock -= clocks;
if(apu.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(apu.thread);
ppu.clock -= clocks;
if(ppu.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(ppu.thread);
cartridge.clock -= clocks;
if(cartridge.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cartridge.thread);
}
void CPU::power() {
R6502::power();
for(unsigned addr = 0; addr < 0x0800; addr++) ram[addr] = 0xff;
ram[0x0008] = 0xf7;
ram[0x0009] = 0xef;
ram[0x000a] = 0xdf;
ram[0x000f] = 0xbf;
}
void CPU::reset() {
R6502::reset();
create(CPU::Enter, 21477272);
regs.pc = bus.read(0xfffc) << 0;
regs.pc |= bus.read(0xfffd) << 8;
status.interrupt_pending = false;
status.nmi_pending = false;
status.nmi_line = 0;
status.irq_line = 0;
status.irq_apu_line = 0;
status.rdy_line = 1;
status.rdy_addr = { false, 0x0000 };
status.oam_dma_pending = false;
status.oam_dma_page = 0x00;
status.controller_latch = false;
status.controller_port0 = 0;
status.controller_port1 = 0;
}
uint8 CPU::debugger_read(uint16 addr) {
return bus.read(addr);
}
uint8 CPU::ram_read(uint16 addr) {
return ram[addr & 0x07ff];
}
void CPU::ram_write(uint16 addr, uint8 data) {
ram[addr & 0x07ff] = data;
}
uint8 CPU::read(uint16 addr) {
if(addr == 0x4016) {
return (mdr() & 0xc0) | input.data(0);
}
if(addr == 0x4017) {
return (mdr() & 0xc0) | input.data(1);
}
return apu.read(addr);
}
void CPU::write(uint16 addr, uint8 data) {
if(addr == 0x4014) {
status.oam_dma_page = data;
status.oam_dma_pending = true;
}
if(addr == 0x4016) {
input.latch(data & 0x01);
}
return apu.write(addr, data);
}
}
-55
View File
@@ -1,55 +0,0 @@
struct CPU : Processor::R6502, Thread {
uint8 ram[0x0800];
struct Status {
bool interrupt_pending;
bool nmi_pending;
bool nmi_line;
bool irq_line;
bool irq_apu_line;
bool rdy_line;
optional<uint16> rdy_addr;
bool oam_dma_pending;
uint8 oam_dma_page;
bool controller_latch;
unsigned controller_port0;
unsigned controller_port1;
} status;
static void Enter();
void main();
void add_clocks(unsigned clocks);
void power();
void reset();
uint8 debugger_read(uint16 addr);
uint8 ram_read(uint16 addr);
void ram_write(uint16 addr, uint8 data);
uint8 read(uint16 addr);
void write(uint16 addr, uint8 data);
void serialize(serializer&);
//timing.cpp
uint8 op_read(uint16 addr);
void op_write(uint16 addr, uint8 data);
void last_cycle();
void nmi(uint16 &vector);
void oam_dma();
void set_nmi_line(bool);
void set_irq_line(bool);
void set_irq_apu_line(bool);
void set_rdy_line(bool);
void set_rdy_addr(optional<uint16>);
};
extern CPU cpu;
-23
View File
@@ -1,23 +0,0 @@
void CPU::serialize(serializer &s) {
R6502::serialize(s);
Thread::serialize(s);
s.array(ram);
s.integer(status.interrupt_pending);
s.integer(status.nmi_pending);
s.integer(status.nmi_line);
s.integer(status.irq_line);
s.integer(status.irq_apu_line);
s.integer(status.rdy_line);
s.integer(status.rdy_addr.valid);
s.integer(status.rdy_addr.value);
s.integer(status.oam_dma_pending);
s.integer(status.oam_dma_page);
s.integer(status.controller_latch);
s.integer(status.controller_port0);
s.integer(status.controller_port1);
}
-63
View File
@@ -1,63 +0,0 @@
uint8 CPU::op_read(uint16 addr) {
if(status.oam_dma_pending) {
status.oam_dma_pending = false;
op_read(addr);
oam_dma();
}
while(status.rdy_line == 0) {
regs.mdr = bus.read(status.rdy_addr ? status.rdy_addr() : addr);
add_clocks(12);
}
regs.mdr = bus.read(addr);
add_clocks(12);
return regs.mdr;
}
void CPU::op_write(uint16 addr, uint8 data) {
bus.write(addr, regs.mdr = data);
add_clocks(12);
}
void CPU::last_cycle() {
status.interrupt_pending = ((status.irq_line | status.irq_apu_line) & ~regs.p.i) | status.nmi_pending;
}
void CPU::nmi(uint16 &vector) {
if(status.nmi_pending) {
status.nmi_pending = false;
vector = 0xfffa;
}
}
void CPU::oam_dma() {
for(unsigned n = 0; n < 256; n++) {
uint8 data = op_read((status.oam_dma_page << 8) + n);
op_write(0x2004, data);
}
}
void CPU::set_nmi_line(bool line) {
//edge-sensitive (0->1)
if(!status.nmi_line && line) status.nmi_pending = true;
status.nmi_line = line;
}
void CPU::set_irq_line(bool line) {
//level-sensitive
status.irq_line = line;
}
void CPU::set_irq_apu_line(bool line) {
//level-sensitive
status.irq_apu_line = line;
}
void CPU::set_rdy_line(bool line) {
status.rdy_line = line;
}
void CPU::set_rdy_addr(optional<uint16> addr) {
status.rdy_addr = addr;
}
-62
View File
@@ -1,62 +0,0 @@
#ifndef FC_HPP
#define FC_HPP
#include <emulator/emulator.hpp>
#include <processor/r6502/r6502.hpp>
namespace Famicom {
namespace Info {
static const char Name[] = "bnes";
static const unsigned SerializerVersion = 2;
}
}
/*
bnes - Famicom emulator
authors: byuu, Ryphecha
license: GPLv3
project started: 2011-09-05
*/
#include <libco/libco.h>
namespace Famicom {
struct Thread {
cothread_t thread;
unsigned frequency;
int64 clock;
inline void create(void (*entrypoint)(), unsigned frequency) {
if(thread) co_delete(thread);
thread = co_create(65536 * sizeof(void*), entrypoint);
this->frequency = frequency;
clock = 0;
}
inline void serialize(serializer &s) {
s.integer(frequency);
s.integer(clock);
}
inline Thread() : thread(nullptr) {
}
inline ~Thread() {
if(thread) co_delete(thread);
}
};
#include <fc/system/system.hpp>
#include <fc/scheduler/scheduler.hpp>
#include <fc/input/input.hpp>
#include <fc/memory/memory.hpp>
#include <fc/cartridge/cartridge.hpp>
#include <fc/cpu/cpu.hpp>
#include <fc/apu/apu.hpp>
#include <fc/ppu/ppu.hpp>
#include <fc/cheat/cheat.hpp>
#include <fc/video/video.hpp>
#include <fc/interface/interface.hpp>
}
#endif
-53
View File
@@ -1,53 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
#include "serialization.cpp"
Input input;
void Input::latch(bool data) {
latchdata = data;
if(latchdata == 1) {
counter1 = 0;
counter2 = 0;
}
}
bool Input::data(bool port) {
bool result = 0;
if(port == 0) {
if(port1 == Device::Joypad) {
if(counter1 >= 8) return 1;
result = interface->inputPoll(0, 0u, counter1);
if(latchdata == 0) counter1++;
}
}
if(port == 1) {
if(port2 == Device::Joypad) {
if(counter2 >= 8) return 1;
result = interface->inputPoll(1, 0u, counter2);
if(latchdata == 0) counter2++;
}
}
return result;
}
void Input::connect(bool port, Device device) {
if(port == 0) port1 = device, counter1 = 0;
if(port == 1) port2 = device, counter2 = 0;
}
void Input::power() {
}
void Input::reset() {
latchdata = 0;
counter1 = 0;
counter2 = 0;
}
}
-25
View File
@@ -1,25 +0,0 @@
struct Input {
enum class Device : unsigned {
Joypad,
None,
};
void latch(bool data);
bool data(bool port);
void connect(bool port, Device device);
void power();
void reset();
void serialize(serializer &s);
private:
Device port1;
Device port2;
bool latchdata;
unsigned counter1;
unsigned counter2;
};
extern Input input;
-8
View File
@@ -1,8 +0,0 @@
void Input::serialize(serializer &s) {
s.integer((unsigned&)port1);
s.integer((unsigned&)port2);
s.integer(latchdata);
s.integer(counter1);
s.integer(counter2);
}
-155
View File
@@ -1,155 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
Interface *interface = nullptr;
double Interface::videoFrequency() {
return 21477272.0 / (262.0 * 1364.0 - 4.0);
}
double Interface::audioFrequency() {
return 21477272.0 / 12.0;
}
bool Interface::loaded() {
return cartridge.loaded();
}
string Interface::sha256() {
return cartridge.sha256();
}
unsigned Interface::group(unsigned id) {
switch(id) {
case ID::ProgramROM:
case ID::ProgramRAM:
case ID::CharacterROM:
case ID::CharacterRAM:
return 1;
}
throw;
}
void Interface::load(unsigned id, const string &manifest) {
cartridge.load(manifest);
}
void Interface::save() {
for(auto &memory : cartridge.memory) {
saveRequest(memory.id, memory.name);
}
}
void Interface::load(unsigned id, const stream &stream, const string &manifest) {
if(id == ID::ProgramROM) {
stream.read(cartridge.board->prgrom.data, min(cartridge.board->prgrom.size, stream.size()));
}
if(id == ID::ProgramRAM) {
stream.read(cartridge.board->prgram.data, min(cartridge.board->prgram.size, stream.size()));
}
if(id == ID::CharacterROM) {
stream.read(cartridge.board->chrrom.data, min(cartridge.board->chrrom.size, stream.size()));
}
if(id == ID::CharacterRAM) {
stream.read(cartridge.board->chrram.data, min(cartridge.board->chrram.size, stream.size()));
}
}
void Interface::save(unsigned id, const stream &stream) {
if(id == ID::ProgramRAM) {
stream.write(cartridge.board->prgram.data, cartridge.board->prgram.size);
}
if(id == ID::CharacterRAM) {
stream.write(cartridge.board->chrram.data, cartridge.board->chrram.size);
}
}
void Interface::unload() {
save();
cartridge.unload();
}
void Interface::power() {
system.power();
}
void Interface::reset() {
system.reset();
}
void Interface::run() {
system.run();
}
serializer Interface::serialize() {
system.runtosave();
return system.serialize();
}
bool Interface::unserialize(serializer &s) {
return system.unserialize(s);
}
void Interface::cheatSet(const lstring &list) {
cheat.reset();
for(auto &code : list) {
lstring codelist = code.split("+");
for(auto &part : codelist) {
unsigned addr, data, comp;
if(Cheat::decode(part, addr, data, comp)) cheat.append({addr, data, comp});
}
}
cheat.synchronize();
}
void Interface::paletteUpdate() {
video.generate_palette();
}
Interface::Interface() {
interface = this;
information.name = "Famicom";
information.width = 256;
information.height = 240;
information.overscan = true;
information.aspectRatio = 8.0 / 7.0;
information.resettable = true;
information.capability.states = true;
information.capability.cheats = true;
media.append({ID::Famicom, "Famicom", "fc"});
{
Device device{0, ID::Port1 | ID::Port2, "Controller"};
device.input.append({0, 0, "A" });
device.input.append({1, 0, "B" });
device.input.append({2, 0, "Select"});
device.input.append({3, 0, "Start" });
device.input.append({4, 0, "Up" });
device.input.append({5, 0, "Down" });
device.input.append({6, 0, "Left" });
device.input.append({7, 0, "Right" });
device.order = {4, 5, 6, 7, 1, 0, 2, 3};
this->device.append(device);
}
port.append({0, "Port 1"});
port.append({1, "Port 2"});
for(auto &device : this->device) {
for(auto &port : this->port) {
if(device.portmask & (1 << port.id)) {
port.device.append(device);
}
}
}
}
}
-58
View File
@@ -1,58 +0,0 @@
#ifndef FC_HPP
namespace Famicom {
#endif
struct ID {
enum : unsigned {
System,
Famicom,
};
enum : unsigned {
ProgramROM,
ProgramRAM,
CharacterROM,
CharacterRAM,
};
enum : unsigned {
Port1 = 1,
Port2 = 2,
};
};
struct Interface : Emulator::Interface {
double videoFrequency();
double audioFrequency();
bool loaded();
string sha256();
unsigned group(unsigned id);
void load(unsigned id, const string &manifest);
void save();
void load(unsigned id, const stream &stream, const string &manifest = "");
void save(unsigned id, const stream &stream);
void unload();
void power();
void reset();
void run();
serializer serialize();
bool unserialize(serializer&);
void cheatSet(const lstring&);
void paletteUpdate();
Interface();
private:
vector<Device> device;
};
extern Interface *interface;
#ifndef FC_HPP
}
#endif
-41
View File
@@ -1,41 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
Bus bus;
//$0000-07ff = RAM (2KB)
//$0800-1fff = RAM (mirror)
//$2000-2007 = PPU
//$2008-3fff = PPU (mirror)
//$4000-4017 = APU + I/O
//$4018-ffff = Cartridge
uint8 Bus::read(uint16 addr) {
uint8 data = cartridge.prg_read(addr);
if(addr <= 0x1fff) data = cpu.ram_read(addr);
else if(addr <= 0x3fff) data = ppu.read(addr);
else if(addr <= 0x4017) data = cpu.read(addr);
if(cheat.override[addr]) {
for(unsigned n = 0; n < cheat.size(); n++) {
if(cheat[n].addr == addr) {
if(cheat[n].comp > 255 || cheat[n].comp == data) {
data = cheat[n].data;
break;
}
}
}
}
return data;
}
void Bus::write(uint16 addr, uint8 data) {
cartridge.prg_write(addr, data);
if(addr <= 0x1fff) return cpu.ram_write(addr, data);
if(addr <= 0x3fff) return ppu.write(addr, data);
if(addr <= 0x4017) return cpu.write(addr, data);
}
}
-6
View File
@@ -1,6 +0,0 @@
struct Bus {
uint8 read(uint16 addr);
void write(uint16 addr, uint8 data);
};
extern Bus bus;
-487
View File
@@ -1,487 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
#include "serialization.cpp"
PPU ppu;
void PPU::Main() {
ppu.main();
}
void PPU::main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::PPU) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
raster_scanline();
}
}
void PPU::tick() {
if(status.ly == 240 && status.lx == 340) status.nmi_hold = 1;
if(status.ly == 241 && status.lx == 0) status.nmi_flag = status.nmi_hold;
if(status.ly == 241 && status.lx == 2) cpu.set_nmi_line(status.nmi_enable && status.nmi_flag);
if(status.ly == 260 && status.lx == 340) status.sprite_zero_hit = 0, status.sprite_overflow = 0;
if(status.ly == 260 && status.lx == 340) status.nmi_hold = 0;
if(status.ly == 261 && status.lx == 0) status.nmi_flag = status.nmi_hold;
if(status.ly == 261 && status.lx == 2) cpu.set_nmi_line(status.nmi_enable && status.nmi_flag);
clock += 4;
if(clock >= 0) co_switch(cpu.thread);
status.lx++;
}
void PPU::scanline() {
status.lx = 0;
if(++status.ly == 262) {
status.ly = 0;
frame();
}
cartridge.scanline(status.ly);
}
void PPU::frame() {
status.field ^= 1;
scheduler.exit(Scheduler::ExitReason::FrameEvent);
}
void PPU::power() {
}
void PPU::reset() {
create(PPU::Main, 21477272);
status.mdr = 0x00;
status.field = 0;
status.ly = 0;
status.bus_data = 0x00;
status.address_latch = 0;
status.vaddr = 0x0000;
status.taddr = 0x0000;
status.xaddr = 0x00;
status.nmi_hold = 0;
status.nmi_flag = 0;
//$2000
status.nmi_enable = false;
status.master_select = 0;
status.sprite_size = 0;
status.bg_addr = 0x0000;
status.sprite_addr = 0x0000;
status.vram_increment = 1;
//$2001
status.emphasis = 0;
status.sprite_enable = false;
status.bg_enable = false;
status.sprite_edge_enable = false;
status.bg_edge_enable = false;
status.grayscale = false;
//$2002
status.sprite_zero_hit = false;
status.sprite_overflow = false;
//$2003
status.oam_addr = 0x00;
for(auto &n : buffer) n = 0;
for(auto &n : ciram ) n = 0;
for(auto &n : cgram ) n = 0;
for(auto &n : oam ) n = 0;
}
uint8 PPU::read(uint16 addr) {
uint8 result = 0x00;
switch(addr & 7) {
case 2: //PPUSTATUS
result |= status.nmi_flag << 7;
result |= status.sprite_zero_hit << 6;
result |= status.sprite_overflow << 5;
result |= status.mdr & 0x1f;
status.address_latch = 0;
status.nmi_hold = 0;
cpu.set_nmi_line(status.nmi_flag = 0);
break;
case 4: //OAMDATA
result = oam[status.oam_addr];
if((status.oam_addr & 3) == 3) result &= 0xe3;
break;
case 7: //PPUDATA
if(raster_enable() && (status.ly <= 240 || status.ly == 261)) return 0x00;
addr = status.vaddr & 0x3fff;
if(addr <= 0x1fff) {
result = status.bus_data;
status.bus_data = cartridge.chr_read(addr);
} else if(addr <= 0x3eff) {
result = status.bus_data;
status.bus_data = cartridge.chr_read(addr);
} else if(addr <= 0x3fff) {
result = cgram_read(addr);
status.bus_data = cartridge.chr_read(addr);
}
status.vaddr += status.vram_increment;
break;
}
return result;
}
void PPU::write(uint16 addr, uint8 data) {
status.mdr = data;
switch(addr & 7) {
case 0: //PPUCTRL
status.nmi_enable = data & 0x80;
status.master_select = data & 0x40;
status.sprite_size = data & 0x20;
status.bg_addr = (data & 0x10) ? 0x1000 : 0x0000;
status.sprite_addr = (data & 0x08) ? 0x1000 : 0x0000;
status.vram_increment = (data & 0x04) ? 32 : 1;
status.taddr = (status.taddr & 0x73ff) | ((data & 0x03) << 10);
cpu.set_nmi_line(status.nmi_enable && status.nmi_hold && status.nmi_flag);
return;
case 1: //PPUMASK
status.emphasis = data >> 5;
status.sprite_enable = data & 0x10;
status.bg_enable = data & 0x08;
status.sprite_edge_enable = data & 0x04;
status.bg_edge_enable = data & 0x02;
status.grayscale = data & 0x01;
return;
case 2: //PPUSTATUS
return;
case 3: //OAMADDR
status.oam_addr = data;
return;
case 4: //OAMDATA
oam[status.oam_addr++] = data;
return;
case 5: //PPUSCROLL
if(status.address_latch == 0) {
status.xaddr = data & 0x07;
status.taddr = (status.taddr & 0x7fe0) | (data >> 3);
} else {
status.taddr = (status.taddr & 0x0c1f) | ((data & 0x07) << 12) | ((data >> 3) << 5);
}
status.address_latch ^= 1;
return;
case 6: //PPUADDR
if(status.address_latch == 0) {
status.taddr = (status.taddr & 0x00ff) | ((data & 0x3f) << 8);
} else {
status.taddr = (status.taddr & 0x7f00) | data;
status.vaddr = status.taddr;
}
status.address_latch ^= 1;
return;
case 7: //PPUDATA
if(raster_enable() && (status.ly <= 240 || status.ly == 261)) return;
addr = status.vaddr & 0x3fff;
if(addr <= 0x1fff) {
cartridge.chr_write(addr, data);
} else if(addr <= 0x3eff) {
cartridge.chr_write(addr, data);
} else if(addr <= 0x3fff) {
cgram_write(addr, data);
}
status.vaddr += status.vram_increment;
return;
}
}
uint8 PPU::ciram_read(uint16 addr) {
return ciram[addr & 0x07ff];
}
void PPU::ciram_write(uint16 addr, uint8 data) {
ciram[addr & 0x07ff] = data;
}
uint8 PPU::cgram_read(uint16 addr) {
if((addr & 0x13) == 0x10) addr &= ~0x10;
uint8 data = cgram[addr & 0x1f];
if(status.grayscale) data &= 0x30;
return data;
}
void PPU::cgram_write(uint16 addr, uint8 data) {
if((addr & 0x13) == 0x10) addr &= ~0x10;
cgram[addr & 0x1f] = data;
}
//
//vaddr = 0yyy VHYY YYYX XXXX
//yyy = fine Yscroll (y:d0-d2)
//V = V nametable (y:d8)
//H = H nametable (x:d8)
//YYYYY = Y nametable (y:d3-d7)
//XXXXX = X nametable (x:d3-d7)
bool PPU::raster_enable() const {
return (status.bg_enable || status.sprite_enable);
}
unsigned PPU::nametable_addr() const {
return 0x2000 + (status.vaddr & 0x0c00);
}
unsigned PPU::scrollx() const {
return ((status.vaddr & 0x1f) << 3) | status.xaddr;
}
unsigned PPU::scrolly() const {
return (((status.vaddr >> 5) & 0x1f) << 3) | ((status.vaddr >> 12) & 7);
}
unsigned PPU::sprite_height() const {
return status.sprite_size == 0 ? 8 : 16;
}
//
uint8 PPU::chr_load(uint16 addr) {
if(raster_enable() == false) return 0x00;
return cartridge.chr_read(addr);
}
//
void PPU::scrollx_increment() {
if(raster_enable() == false) return;
status.vaddr = (status.vaddr & 0x7fe0) | ((status.vaddr + 0x0001) & 0x001f);
if((status.vaddr & 0x001f) == 0x0000) {
status.vaddr ^= 0x0400;
}
}
void PPU::scrolly_increment() {
if(raster_enable() == false) return;
status.vaddr = (status.vaddr & 0x0fff) | ((status.vaddr + 0x1000) & 0x7000);
if((status.vaddr & 0x7000) == 0x0000) {
status.vaddr = (status.vaddr & 0x7c1f) | ((status.vaddr + 0x0020) & 0x03e0);
if((status.vaddr & 0x03e0) == 0x03c0) { //0x03c0 == 30 << 5; 30 * 8 = 240
status.vaddr &= 0x7c1f;
status.vaddr ^= 0x0800;
}
}
}
//
void PPU::raster_pixel() {
uint32 *output = buffer + status.ly * 256;
unsigned mask = 0x8000 >> (status.xaddr + (status.lx & 7));
unsigned palette = 0, object_palette = 0;
bool object_priority = 0;
palette |= (raster.tiledatalo & mask) ? 1 : 0;
palette |= (raster.tiledatahi & mask) ? 2 : 0;
if(palette) {
unsigned attr = raster.attribute;
if(mask >= 256) attr >>= 2;
palette |= (attr & 3) << 2;
}
if(status.bg_enable == false) palette = 0;
if(status.bg_edge_enable == false && status.lx < 8) palette = 0;
if(status.sprite_enable == true)
for(signed sprite = 7; sprite >= 0; sprite--) {
if(status.sprite_edge_enable == false && status.lx < 8) continue;
if(raster.oam[sprite].id == 64) continue;
unsigned spritex = status.lx - raster.oam[sprite].x;
if(spritex >= 8) continue;
if(raster.oam[sprite].attr & 0x40) spritex ^= 7;
unsigned mask = 0x80 >> spritex;
unsigned sprite_palette = 0;
sprite_palette |= (raster.oam[sprite].tiledatalo & mask) ? 1 : 0;
sprite_palette |= (raster.oam[sprite].tiledatahi & mask) ? 2 : 0;
if(sprite_palette == 0) continue;
if(raster.oam[sprite].id == 0 && palette && status.lx != 255) status.sprite_zero_hit = 1;
sprite_palette |= (raster.oam[sprite].attr & 3) << 2;
object_priority = raster.oam[sprite].attr & 0x20;
object_palette = 16 + sprite_palette;
}
if(object_palette) {
if(palette == 0 || object_priority == 0) palette = object_palette;
}
if(raster_enable() == false) palette = 0;
output[status.lx] = video.palette[(status.emphasis << 6) | cgram_read(palette)];
}
void PPU::raster_sprite() {
if(raster_enable() == false) return;
unsigned n = raster.oam_iterator++;
signed ly = (status.ly == 261 ? -1 : status.ly);
unsigned y = ly - oam[(n * 4) + 0];
if(y >= sprite_height()) return;
if(raster.oam_counter == 8) {
status.sprite_overflow = 1;
return;
}
raster.soam[raster.oam_counter].id = n;
raster.soam[raster.oam_counter].y = oam[(n * 4) + 0];
raster.soam[raster.oam_counter].tile = oam[(n * 4) + 1];
raster.soam[raster.oam_counter].attr = oam[(n * 4) + 2];
raster.soam[raster.oam_counter].x = oam[(n * 4) + 3];
raster.oam_counter++;
}
void PPU::raster_scanline() {
if((status.ly >= 240 && status.ly <= 260)) {
for(unsigned x = 0; x < 341; x++) tick();
return scanline();
}
raster.oam_iterator = 0;
raster.oam_counter = 0;
for(unsigned n = 0; n < 8; n++) {
raster.soam[n].id = 64;
raster.soam[n].y = 0xff;
raster.soam[n].tile = 0xff;
raster.soam[n].attr = 0xff;
raster.soam[n].x = 0xff;
raster.soam[n].tiledatalo = 0;
raster.soam[n].tiledatahi = 0;
}
for(unsigned tile = 0; tile < 32; tile++) { // 0-255
unsigned nametable = chr_load(0x2000 | (status.vaddr & 0x0fff));
unsigned tileaddr = status.bg_addr + (nametable << 4) + (scrolly() & 7);
raster_pixel();
tick();
raster_pixel();
tick();
unsigned attribute = chr_load(0x23c0 | (status.vaddr & 0x0fc0) | ((scrolly() >> 5) << 3) | (scrollx() >> 5));
if(scrolly() & 16) attribute >>= 4;
if(scrollx() & 16) attribute >>= 2;
raster_pixel();
tick();
scrollx_increment();
if(tile == 31) scrolly_increment();
raster_pixel();
raster_sprite();
tick();
unsigned tiledatalo = chr_load(tileaddr + 0);
raster_pixel();
tick();
raster_pixel();
tick();
unsigned tiledatahi = chr_load(tileaddr + 8);
raster_pixel();
tick();
raster_pixel();
raster_sprite();
tick();
raster.nametable = (raster.nametable << 8) | nametable;
raster.attribute = (raster.attribute << 2) | (attribute & 3);
raster.tiledatalo = (raster.tiledatalo << 8) | tiledatalo;
raster.tiledatahi = (raster.tiledatahi << 8) | tiledatahi;
}
for(unsigned n = 0; n < 8; n++) raster.oam[n] = raster.soam[n];
for(unsigned sprite = 0; sprite < 8; sprite++) { //256-319
unsigned nametable = chr_load(0x2000 | (status.vaddr & 0x0fff));
tick();
if(raster_enable() && sprite == 0) status.vaddr = (status.vaddr & 0x7be0) | (status.taddr & 0x041f); //257
tick();
unsigned attribute = chr_load(0x23c0 | (status.vaddr & 0x0fc0) | ((scrolly() >> 5) << 3) | (scrollx() >> 5));
unsigned tileaddr = (sprite_height() == 8)
? status.sprite_addr + raster.oam[sprite].tile * 16
: ((raster.oam[sprite].tile & ~1) * 16) + ((raster.oam[sprite].tile & 1) * 0x1000);
tick();
tick();
unsigned spritey = (status.ly - raster.oam[sprite].y) & (sprite_height() - 1);
if(raster.oam[sprite].attr & 0x80) spritey ^= (sprite_height() - 1);
tileaddr += spritey + (spritey & 8);
raster.oam[sprite].tiledatalo = chr_load(tileaddr + 0);
tick();
tick();
raster.oam[sprite].tiledatahi = chr_load(tileaddr + 8);
tick();
tick();
if(raster_enable() && sprite == 6 && status.ly == 261) status.vaddr = status.taddr; //304
}
for(unsigned tile = 0; tile < 2; tile++) { //320-335
unsigned nametable = chr_load(0x2000 | (status.vaddr & 0x0fff));
unsigned tileaddr = status.bg_addr + (nametable << 4) + (scrolly() & 7);
tick();
tick();
unsigned attribute = chr_load(0x23c0 | (status.vaddr & 0x0fc0) | ((scrolly() >> 5) << 3) | (scrollx() >> 5));
if(scrolly() & 16) attribute >>= 4;
if(scrollx() & 16) attribute >>= 2;
tick();
scrollx_increment();
tick();
unsigned tiledatalo = chr_load(tileaddr + 0);
tick();
tick();
unsigned tiledatahi = chr_load(tileaddr + 8);
tick();
tick();
raster.nametable = (raster.nametable << 8) | nametable;
raster.attribute = (raster.attribute << 2) | (attribute & 3);
raster.tiledatalo = (raster.tiledatalo << 8) | tiledatalo;
raster.tiledatahi = (raster.tiledatahi << 8) | tiledatahi;
}
//336-339
chr_load(0x2000 | (status.vaddr & 0x0fff));
tick();
bool skip = (raster_enable() && status.field == 1 && status.ly == 261);
tick();
chr_load(0x2000 | (status.vaddr & 0x0fff));
tick();
tick();
//340
if(skip == false) tick();
return scanline();
}
}
-107
View File
@@ -1,107 +0,0 @@
struct PPU : Thread {
static void Main();
void main();
void tick();
void scanline();
void frame();
void power();
void reset();
uint8 read(uint16 addr);
void write(uint16 addr, uint8 data);
uint8 ciram_read(uint16 addr);
void ciram_write(uint16 addr, uint8 data);
uint8 cgram_read(uint16 addr);
void cgram_write(uint16 addr, uint8 data);
bool raster_enable() const;
unsigned nametable_addr() const;
unsigned scrollx() const;
unsigned scrolly() const;
unsigned sprite_height() const;
uint8 chr_load(uint16 addr);
void scrollx_increment();
void scrolly_increment();
void raster_pixel();
void raster_sprite();
void raster_scanline();
void serialize(serializer&);
struct Status {
uint8 mdr;
bool field;
unsigned lx;
unsigned ly;
uint8 bus_data;
bool address_latch;
uint15 vaddr;
uint15 taddr;
uint8 xaddr;
bool nmi_hold;
bool nmi_flag;
//$2000
bool nmi_enable;
bool master_select;
bool sprite_size;
unsigned bg_addr;
unsigned sprite_addr;
unsigned vram_increment;
//$2001
uint3 emphasis;
bool sprite_enable;
bool bg_enable;
bool sprite_edge_enable;
bool bg_edge_enable;
bool grayscale;
//$2002
bool sprite_zero_hit;
bool sprite_overflow;
//$2003
uint8 oam_addr;
} status;
struct Raster {
uint16 nametable;
uint16 attribute;
uint16 tiledatalo;
uint16 tiledatahi;
unsigned oam_iterator;
unsigned oam_counter;
struct OAM {
uint8 id;
uint8 y;
uint8 tile;
uint8 attr;
uint8 x;
uint8 tiledatalo;
uint8 tiledatahi;
} oam[8], soam[8];
} raster;
uint32 buffer[256 * 262];
uint8 ciram[2048];
uint8 cgram[32];
uint8 oam[256];
};
extern PPU ppu;
-74
View File
@@ -1,74 +0,0 @@
void PPU::serialize(serializer &s) {
Thread::serialize(s);
s.integer(status.mdr);
s.integer(status.field);
s.integer(status.lx);
s.integer(status.ly);
s.integer(status.bus_data);
s.integer(status.address_latch);
s.integer(status.vaddr);
s.integer(status.taddr);
s.integer(status.xaddr);
s.integer(status.nmi_hold);
s.integer(status.nmi_flag);
s.integer(status.nmi_enable);
s.integer(status.master_select);
s.integer(status.sprite_size);
s.integer(status.bg_addr);
s.integer(status.sprite_addr);
s.integer(status.vram_increment);
s.integer(status.emphasis);
s.integer(status.sprite_enable);
s.integer(status.bg_enable);
s.integer(status.sprite_edge_enable);
s.integer(status.bg_edge_enable);
s.integer(status.grayscale);
s.integer(status.sprite_zero_hit);
s.integer(status.sprite_overflow);
s.integer(status.oam_addr);
s.integer(raster.nametable);
s.integer(raster.attribute);
s.integer(raster.tiledatalo);
s.integer(raster.tiledatahi);
s.integer(raster.oam_iterator);
s.integer(raster.oam_counter);
for(unsigned n = 0; n < 8; n++) {
s.integer(raster.oam[n].id);
s.integer(raster.oam[n].y);
s.integer(raster.oam[n].tile);
s.integer(raster.oam[n].attr);
s.integer(raster.oam[n].x);
s.integer(raster.oam[n].tiledatalo);
s.integer(raster.oam[n].tiledatahi);
}
for(unsigned n = 0; n < 8; n++) {
s.integer(raster.soam[n].id);
s.integer(raster.soam[n].y);
s.integer(raster.soam[n].tile);
s.integer(raster.soam[n].attr);
s.integer(raster.soam[n].x);
s.integer(raster.soam[n].tiledatalo);
s.integer(raster.soam[n].tiledatahi);
}
s.array(buffer);
s.array(ciram);
s.array(cgram);
s.array(oam);
}
-28
View File
@@ -1,28 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
Scheduler scheduler;
void Scheduler::enter() {
host_thread = co_active();
co_switch(thread);
}
void Scheduler::exit(ExitReason reason) {
exit_reason = reason;
thread = co_active();
co_switch(host_thread);
}
void Scheduler::power() {
}
void Scheduler::reset() {
host_thread = co_active();
thread = cpu.thread;
sync = SynchronizeMode::None;
exit_reason = ExitReason::UnknownEvent;
}
}
-16
View File
@@ -1,16 +0,0 @@
struct Scheduler : property<Scheduler> {
enum class SynchronizeMode : unsigned { None, PPU, All } sync;
enum class ExitReason : unsigned { UnknownEvent, FrameEvent, SynchronizeEvent };
readonly<ExitReason> exit_reason;
cothread_t host_thread; //program thread (used to exit emulation)
cothread_t thread; //active emulation thread (used to enter emulation)
void enter();
void exit(ExitReason);
void power();
void reset();
};
extern Scheduler scheduler;
-60
View File
@@ -1,60 +0,0 @@
serializer System::serialize() {
serializer s(serialize_size);
unsigned signature = 0x31545342, version = Info::SerializerVersion;
char hash[64], description[512];
memcpy(&hash, (const char*)cartridge.sha256(), 64);
memset(&description, 0, sizeof description);
s.integer(signature);
s.integer(version);
s.array(hash);
s.array(description);
serialize_all(s);
return s;
}
bool System::unserialize(serializer &s) {
unsigned signature, version;
char hash[64], description[512];
s.integer(signature);
s.integer(version);
s.array(hash);
s.array(description);
if(signature != 0x31545342) return false;
if(version != Info::SerializerVersion) return false;
power();
serialize_all(s);
return true;
}
void System::serialize(serializer &s) {
}
void System::serialize_all(serializer &s) {
system.serialize(s);
input.serialize(s);
cartridge.serialize(s);
cpu.serialize(s);
apu.serialize(s);
ppu.serialize(s);
}
void System::serialize_init() {
serializer s;
unsigned signature = 0, version = 0;
char hash[64], description[512];
s.integer(signature);
s.integer(version);
s.array(hash);
s.array(description);
serialize_all(s);
serialize_size = s.size();
}
-76
View File
@@ -1,76 +0,0 @@
#include <fc/fc.hpp>
namespace Famicom {
#include "serialization.cpp"
System system;
void System::run() {
scheduler.enter();
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) {
interface->videoRefresh(ppu.buffer, 4 * 256, 256, 240);
}
}
void System::runtosave() {
scheduler.sync = Scheduler::SynchronizeMode::PPU;
runthreadtosave();
scheduler.sync = Scheduler::SynchronizeMode::All;
scheduler.thread = cpu.thread;
runthreadtosave();
scheduler.sync = Scheduler::SynchronizeMode::All;
scheduler.thread = apu.thread;
runthreadtosave();
scheduler.sync = Scheduler::SynchronizeMode::All;
scheduler.thread = cartridge.thread;
runthreadtosave();
scheduler.sync = Scheduler::SynchronizeMode::None;
}
void System::runthreadtosave() {
while(true) {
scheduler.enter();
if(scheduler.exit_reason() == Scheduler::ExitReason::SynchronizeEvent) break;
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) {
interface->videoRefresh(ppu.buffer, 4 * 256, 256, 240);
}
}
}
void System::load() {
serialize_init();
}
void System::power() {
cartridge.power();
cpu.power();
apu.power();
ppu.power();
input.reset();
scheduler.power();
reset();
}
void System::reset() {
cartridge.reset();
cpu.reset();
apu.reset();
ppu.reset();
input.reset();
scheduler.reset();
}
void System::init() {
assert(interface != 0);
input.connect(0, Input::Device::Joypad);
input.connect(1, Input::Device::None);
}
void System::term() {
}
}
-22
View File
@@ -1,22 +0,0 @@
struct System {
void run();
void runtosave();
void runthreadtosave();
void load();
void power();
void reset();
void init();
void term();
serializer serialize();
bool unserialize(serializer&);
void serialize(serializer&);
void serialize_all(serializer&);
void serialize_init();
unsigned serialize_size;
};
extern System system;
-68
View File
@@ -1,68 +0,0 @@
#include <fc/fc.hpp>
#define VIDEO_CPP
namespace Famicom {
Video video;
void Video::generate_palette() {
for(unsigned n = 0; n < (1 << 9); n++) palette[n] = generate_color(n, 2.0, 0.0, 1.0, 1.0, 1.8);
}
Video::Video() {
palette = new unsigned[1 << 9];
}
Video::~Video() {
delete[] palette;
}
uint32_t Video::generate_color(
unsigned n, double saturation, double hue,
double contrast, double brightness, double gamma
) {
signed color = (n & 0x0f), level = color < 0xe ? (n >> 4) & 3 : 1;
static const double black = 0.518, white = 1.962, attenuation = 0.746;
static const double levels[8] = {
0.350, 0.518, 0.962, 1.550,
1.094, 1.506, 1.962, 1.962,
};
double lo_and_hi[2] = {
levels[level + 4 * (color == 0x0)],
levels[level + 4 * (color < 0xd)],
};
double y = 0.0, i = 0.0, q = 0.0;
auto wave = [](signed p, signed color) { return (color + p + 8) % 12 < 6; };
for(signed p = 0; p < 12; p++) {
double spot = lo_and_hi[wave(p, color)];
if(((n & 0x040) && wave(p, 12))
|| ((n & 0x080) && wave(p, 4))
|| ((n & 0x100) && wave(p, 8))
) spot *= attenuation;
double v = (spot - black) / (white - black);
v = (v - 0.5) * contrast + 0.5;
v *= brightness / 12.0;
y += v;
i += v * std::cos((3.141592653 / 6.0) * (p + hue));
q += v * std::sin((3.141592653 / 6.0) * (p + hue));
}
i *= saturation;
q *= saturation;
auto gammaAdjust = [=](double f) { return f < 0.0 ? 0.0 : std::pow(f, 2.2 / gamma); };
unsigned r = 65535.0 * gammaAdjust(y + 0.946882 * i + 0.623557 * q);
unsigned g = 65535.0 * gammaAdjust(y + -0.274788 * i + -0.635691 * q);
unsigned b = 65535.0 * gammaAdjust(y + -1.108545 * i + 1.709007 * q);
return interface->videoColor(n, uclamp<16>(r), uclamp<16>(g), uclamp<16>(b));
}
}
-12
View File
@@ -1,12 +0,0 @@
struct Video {
unsigned *palette;
void generate_palette();
Video();
~Video();
private:
uint32_t generate_color(unsigned, double, double, double, double, double);
};
extern Video video;
-16
View File
@@ -1,16 +0,0 @@
gb_objects := gb-interface gb-system gb-scheduler
gb_objects += gb-memory gb-cartridge
gb_objects += gb-cpu gb-ppu gb-apu
gb_objects += gb-cheat gb-video
objects += $(gb_objects)
obj/gb-interface.o: $(gb)/interface/interface.cpp $(call rwildcard,$(gb)/interface/)
obj/gb-system.o: $(gb)/system/system.cpp $(call rwildcard,$(gb)/system/)
obj/gb-scheduler.o: $(gb)/scheduler/scheduler.cpp $(call rwildcard,$(gb)/scheduler/)
obj/gb-cartridge.o: $(gb)/cartridge/cartridge.cpp $(call rwildcard,$(gb)/cartridge/)
obj/gb-memory.o: $(gb)/memory/memory.cpp $(call rwildcard,$(gb)/memory/)
obj/gb-cpu.o: $(gb)/cpu/cpu.cpp $(call rwildcard,$(gb)/cpu/)
obj/gb-ppu.o: $(gb)/ppu/ppu.cpp $(call rwildcard,$(gb)/ppu/)
obj/gb-apu.o: $(gb)/apu/apu.cpp $(call rwildcard,$(gb)/apu/)
obj/gb-cheat.o: $(gb)/cheat/cheat.cpp $(call rwildcard,$(gb)/cheat/)
obj/gb-video.o: $(gb)/video/video.cpp $(call rwildcard,$(gb)/video/)
-107
View File
@@ -1,107 +0,0 @@
#include <gb/gb.hpp>
#define APU_CPP
namespace GameBoy {
#include "square1/square1.cpp"
#include "square2/square2.cpp"
#include "wave/wave.cpp"
#include "noise/noise.cpp"
#include "master/master.cpp"
#include "serialization.cpp"
APU apu;
void APU::Main() {
apu.main();
}
void APU::main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(sequencer_base == 0) { //512hz
if(sequencer_step == 0 || sequencer_step == 2 || sequencer_step == 4 || sequencer_step == 6) { //256hz
square1.clock_length();
square2.clock_length();
wave.clock_length();
noise.clock_length();
}
if(sequencer_step == 2 || sequencer_step == 6) { //128hz
square1.clock_sweep();
}
if(sequencer_step == 7) { //64hz
square1.clock_envelope();
square2.clock_envelope();
noise.clock_envelope();
}
sequencer_step++;
}
sequencer_base++;
square1.run();
square2.run();
wave.run();
noise.run();
master.run();
interface->audioSample(master.left, master.right);
clock += cpu.frequency;
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(scheduler.active_thread = cpu.thread);
}
}
void APU::power() {
create(Main, 4 * 1024 * 1024);
for(unsigned n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;
for(auto &n : mmio_data) n = 0x00;
sequencer_base = 0;
sequencer_step = 0;
square1.power();
square2.power();
wave.power();
noise.power();
master.power();
}
uint8 APU::mmio_read(uint16 addr) {
static const uint8 table[48] = {
0x80, 0x3f, 0x00, 0xff, 0xbf, //square1
0xff, 0x3f, 0x00, 0xff, 0xbf, //square2
0x7f, 0xff, 0x9f, 0xff, 0xbf, //wave
0xff, 0xff, 0x00, 0x00, 0xbf, //noise
0x00, 0x00, 0x70, //master
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, //unmapped
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //wave pattern
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //wave pattern
};
if(addr == 0xff26) {
uint8 data = master.enable << 7;
if(square1.enable) data |= 0x01;
if(square2.enable) data |= 0x02;
if( wave.enable) data |= 0x04;
if( noise.enable) data |= 0x08;
return data | table[addr - 0xff10];
}
if(addr >= 0xff10 && addr <= 0xff3f) return mmio_data[addr - 0xff10] | table[addr - 0xff10];
return 0xff;
}
void APU::mmio_write(uint16 addr, uint8 data) {
if(addr >= 0xff10 && addr <= 0xff3f) mmio_data[addr - 0xff10] = data;
if(addr >= 0xff10 && addr <= 0xff14) return square1.write (addr - 0xff10, data);
if(addr >= 0xff15 && addr <= 0xff19) return square2.write (addr - 0xff15, data);
if(addr >= 0xff1a && addr <= 0xff1e) return wave.write (addr - 0xff1a, data);
if(addr >= 0xff1f && addr <= 0xff23) return noise.write (addr - 0xff1f, data);
if(addr >= 0xff24 && addr <= 0xff26) return master.write (addr - 0xff24, data);
if(addr >= 0xff30 && addr <= 0xff3f) return wave.write_pattern(addr - 0xff30, data);
}
}
-28
View File
@@ -1,28 +0,0 @@
struct APU : Thread, MMIO {
#include "square1/square1.hpp"
#include "square2/square2.hpp"
#include "wave/wave.hpp"
#include "noise/noise.hpp"
#include "master/master.hpp"
uint8 mmio_data[48];
uint13 sequencer_base;
uint3 sequencer_step;
Square1 square1;
Square2 square2;
Wave wave;
Noise noise;
Master master;
static void Main();
void main();
void power();
uint8 mmio_read(uint16 addr);
void mmio_write(uint16 addr, uint8 data);
void serialize(serializer&);
};
extern APU apu;
-101
View File
@@ -1,101 +0,0 @@
#ifdef APU_CPP
void APU::Master::run() {
if(enable == false) {
center = 0;
left = 0;
right = 0;
return;
}
signed sample = 0;
sample += apu.square1.output;
sample += apu.square2.output;
sample += apu.wave.output;
sample += apu.noise.output;
center = (sample * 512) - 16384;
sample = 0;
if(channel1_left_enable) sample += apu.square1.output;
if(channel2_left_enable) sample += apu.square2.output;
if(channel3_left_enable) sample += apu.wave.output;
if(channel4_left_enable) sample += apu.noise.output;
sample = (sample * 512) - 16384;
sample = (sample * (left_volume + 1)) / 8;
left = sample;
sample = 0;
if(channel1_right_enable) sample += apu.square1.output;
if(channel2_right_enable) sample += apu.square2.output;
if(channel3_right_enable) sample += apu.wave.output;
if(channel4_right_enable) sample += apu.noise.output;
sample = (sample * 512) - 16384;
sample = (sample * (right_volume + 1)) / 8;
right = sample;
}
void APU::Master::write(unsigned r, uint8 data) {
if(r == 0) { //$ff24 NR50
left_in_enable = data & 0x80;
left_volume = (data >> 4) & 7;
right_in_enable = data & 0x08;
right_volume = (data >> 0) & 7;
}
if(r == 1) { //$ff25 NR51
channel4_left_enable = data & 0x80;
channel3_left_enable = data & 0x40;
channel2_left_enable = data & 0x20;
channel1_left_enable = data & 0x10;
channel4_right_enable = data & 0x08;
channel3_right_enable = data & 0x04;
channel2_right_enable = data & 0x02;
channel1_right_enable = data & 0x01;
}
if(r == 2) { //$ff26 NR52
enable = data & 0x80;
}
}
void APU::Master::power() {
left_in_enable = 0;
left_volume = 0;
right_in_enable = 0;
right_volume = 0;
channel4_left_enable = 0;
channel3_left_enable = 0;
channel2_left_enable = 0;
channel1_left_enable = 0;
channel4_right_enable = 0;
channel3_right_enable = 0;
channel2_right_enable = 0;
channel1_right_enable = 0;
enable = 0;
center = 0;
left = 0;
right = 0;
}
void APU::Master::serialize(serializer &s) {
s.integer(left_in_enable);
s.integer(left_volume);
s.integer(right_in_enable);
s.integer(right_volume);
s.integer(channel4_left_enable);
s.integer(channel3_left_enable);
s.integer(channel2_left_enable);
s.integer(channel1_left_enable);
s.integer(channel4_right_enable);
s.integer(channel3_right_enable);
s.integer(channel2_right_enable);
s.integer(channel1_right_enable);
s.integer(enable);
s.integer(center);
s.integer(left);
s.integer(right);
}
#endif
-24
View File
@@ -1,24 +0,0 @@
struct Master {
bool left_in_enable;
uint3 left_volume;
bool right_in_enable;
uint3 right_volume;
bool channel4_left_enable;
bool channel3_left_enable;
bool channel2_left_enable;
bool channel1_left_enable;
bool channel4_right_enable;
bool channel3_right_enable;
bool channel2_right_enable;
bool channel1_right_enable;
bool enable;
int16 center;
int16 left;
int16 right;
void run();
void write(unsigned r, uint8 data);
void power();
void serialize(serializer&);
};
-112
View File
@@ -1,112 +0,0 @@
#ifdef APU_CPP
bool APU::Noise::dac_enable() {
return (envelope_volume || envelope_direction);
}
void APU::Noise::run() {
if(period && --period == 0) {
period = divisor << frequency;
if(frequency < 14) {
bool bit = (lfsr ^ (lfsr >> 1)) & 1;
lfsr = (lfsr >> 1) ^ (bit << (narrow_lfsr ? 6 : 14));
}
}
uint4 sample = (lfsr & 1) ? (uint4)0 : volume;
if(enable == false) sample = 0;
output = sample;
}
void APU::Noise::clock_length() {
//if(counter && length) {
// if(--length == 0) enable = false;
//}
if(enable && counter) {
if(++length == 0) enable = false;
}
}
void APU::Noise::clock_envelope() {
if(enable && envelope_frequency && --envelope_period == 0) {
envelope_period = envelope_frequency;
if(envelope_direction == 0 && volume > 0) volume--;
if(envelope_direction == 1 && volume < 15) volume++;
}
}
void APU::Noise::write(unsigned r, uint8 data) {
if(r == 1) { //$ff20 NR41
//length = 64 - (data & 0x3f);
length = data & 0x3f;
}
if(r == 2) { //$ff21 NR42
envelope_volume = data >> 4;
envelope_direction = data & 0x08;
envelope_frequency = data & 0x07;
if(dac_enable() == false) enable = false;
}
if(r == 3) { //$ff22 NR43
frequency = data >> 4;
narrow_lfsr = data & 0x08;
divisor = (data & 0x07) << 4;
if(divisor == 0) divisor = 8;
period = divisor << frequency;
}
if(r == 4) { //$ff34 NR44
bool initialize = data & 0x80;
counter = data & 0x40;
if(initialize) {
enable = dac_enable();
lfsr = ~0U;
envelope_period = envelope_frequency;
volume = envelope_volume;
//if(length == 0) length = 64;
}
}
}
void APU::Noise::power() {
enable = 0;
envelope_volume = 0;
envelope_direction = 0;
envelope_frequency = 0;
frequency = 0;
narrow_lfsr = 0;
divisor = 0;
counter = 0;
output = 0;
length = 0;
envelope_period = 0;
volume = 0;
period = 0;
lfsr = 0;
}
void APU::Noise::serialize(serializer &s) {
s.integer(enable);
s.integer(envelope_volume);
s.integer(envelope_direction);
s.integer(envelope_frequency);
s.integer(frequency);
s.integer(narrow_lfsr);
s.integer(divisor);
s.integer(counter);
s.integer(output);
s.integer(length);
s.integer(envelope_period);
s.integer(volume);
s.integer(period);
s.integer(lfsr);
}
#endif
-27
View File
@@ -1,27 +0,0 @@
struct Noise {
bool enable;
uint4 envelope_volume;
bool envelope_direction;
uint3 envelope_frequency;
uint4 frequency;
bool narrow_lfsr;
unsigned divisor;
bool counter;
int16 output;
uint6 length;
uint3 envelope_period;
uint4 volume;
unsigned period;
uint15 lfsr;
bool dac_enable();
void run();
void clock_length();
void clock_envelope();
void write(unsigned r, uint8 data);
void power();
void serialize(serializer&);
};
-17
View File
@@ -1,17 +0,0 @@
#ifdef APU_CPP
void APU::serialize(serializer &s) {
Thread::serialize(s);
s.array(mmio_data);
s.integer(sequencer_base);
s.integer(sequencer_step);
square1.serialize(s);
square2.serialize(s);
wave.serialize(s);
noise.serialize(s);
master.serialize(s);
}
#endif
-164
View File
@@ -1,164 +0,0 @@
#ifdef APU_CPP
bool APU::Square1::dac_enable() {
return (envelope_volume || envelope_direction);
}
void APU::Square1::run() {
if(period && --period == 0) {
period = 4 * (2048 - frequency);
phase++;
switch(duty) {
case 0: duty_output = (phase == 6); break; //______-_
case 1: duty_output = (phase >= 6); break; //______--
case 2: duty_output = (phase >= 4); break; //____----
case 3: duty_output = (phase <= 5); break; //------__
}
}
uint4 sample = (duty_output ? volume : (uint4)0);
if(enable == false) sample = 0;
output = sample;
}
void APU::Square1::sweep(bool update) {
if(sweep_enable == false) return;
sweep_negate = sweep_direction;
unsigned delta = frequency_shadow >> sweep_shift;
signed freq = frequency_shadow + (sweep_negate ? -delta : delta);
if(freq > 2047) {
enable = false;
} else if(sweep_shift && update) {
frequency_shadow = freq;
frequency = freq & 2047;
period = 4 * (2048 - frequency);
}
}
void APU::Square1::clock_length() {
//if(counter && length) {
// if(--length == 0) enable = false;
//}
if(counter && enable) {
if(++length == 0) enable = false;
}
}
void APU::Square1::clock_sweep() {
if(enable && sweep_frequency && --sweep_period == 0) {
sweep_period = sweep_frequency;
sweep(1);
sweep(0);
}
}
void APU::Square1::clock_envelope() {
if(enable && envelope_frequency && --envelope_period == 0) {
envelope_period = envelope_frequency;
if(envelope_direction == 0 && volume > 0) volume--;
if(envelope_direction == 1 && volume < 15) volume++;
}
}
void APU::Square1::write(unsigned r, uint8 data) {
if(r == 0) { //$ff10 NR10
if(sweep_negate && sweep_direction && !(data & 0x08)) enable = false;
sweep_frequency = (data >> 4) & 7;
sweep_direction = data & 0x08;
sweep_shift = data & 0x07;
}
if(r == 1) { //$ff11 NR11
duty = data >> 6;
//length = 64 - (data & 0x3f);
length = data & 0x3f;
}
if(r == 2) { //$ff12 NR12
envelope_volume = data >> 4;
envelope_direction = data & 0x08;
envelope_frequency = data & 0x07;
if(dac_enable() == false) enable = false;
}
if(r == 3) { //$ff13 NR13
frequency = (frequency & 0x0700) | data;
}
if(r == 4) { //$ff14 NR14
bool initialize = data & 0x80;
counter = data & 0x40;
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
if(initialize) {
enable = dac_enable();
period = 4 * (2048 - frequency);
envelope_period = envelope_frequency;
volume = envelope_volume;
frequency_shadow = frequency;
sweep_period = sweep_frequency;
sweep_enable = sweep_period || sweep_shift;
sweep_negate = false;
if(sweep_shift) sweep(0);
//if(length == 0) length = 64;
}
}
}
void APU::Square1::power() {
enable = 0;
sweep_frequency = 0;
sweep_direction = 0;
sweep_shift = 0;
sweep_negate = 0;
duty = 0;
length = 0;
envelope_volume = 0;
envelope_direction = 0;
envelope_frequency = 0;
frequency = 0;
counter = 0;
output = 0;
duty_output = 0;
phase = 0;
period = 0;
envelope_period = 0;
sweep_period = 0;
frequency_shadow = 0;
sweep_enable = 0;
volume = 0;
}
void APU::Square1::serialize(serializer &s) {
s.integer(enable);
s.integer(sweep_frequency);
s.integer(sweep_direction);
s.integer(sweep_shift);
s.integer(sweep_negate);
s.integer(duty);
s.integer(length);
s.integer(envelope_volume);
s.integer(envelope_direction);
s.integer(envelope_frequency);
s.integer(frequency);
s.integer(counter);
s.integer(output);
s.integer(duty_output);
s.integer(phase);
s.integer(period);
s.integer(envelope_period);
s.integer(sweep_period);
s.integer(frequency_shadow);
s.integer(sweep_enable);
s.integer(volume);
}
#endif
-36
View File
@@ -1,36 +0,0 @@
struct Square1 {
bool enable;
uint3 sweep_frequency;
bool sweep_direction;
uint3 sweep_shift;
bool sweep_negate;
uint2 duty;
uint6 length;
uint4 envelope_volume;
bool envelope_direction;
uint3 envelope_frequency;
uint11 frequency;
bool counter;
int16 output;
bool duty_output;
uint3 phase;
unsigned period;
uint3 envelope_period;
uint3 sweep_period;
signed frequency_shadow;
bool sweep_enable;
uint4 volume;
bool dac_enable();
void run();
void sweep(bool update);
void clock_length();
void clock_sweep();
void clock_envelope();
void write(unsigned r, uint8 data);
void power();
void serialize(serializer&);
};
-114
View File
@@ -1,114 +0,0 @@
#ifdef APU_CPP
bool APU::Square2::dac_enable() {
return (envelope_volume || envelope_direction);
}
void APU::Square2::run() {
if(period && --period == 0) {
period = 4 * (2048 - frequency);
phase++;
switch(duty) {
case 0: duty_output = (phase == 6); break; //______-_
case 1: duty_output = (phase >= 6); break; //______--
case 2: duty_output = (phase >= 4); break; //____----
case 3: duty_output = (phase <= 5); break; //------__
}
}
uint4 sample = (duty_output ? volume : (uint4)0);
if(enable == false) sample = 0;
output = sample;
}
void APU::Square2::clock_length() {
//if(counter && length) {
// if(--length == 0) enable = false;
//}
if(counter && enable) {
if(++length == 0) enable = false;
}
}
void APU::Square2::clock_envelope() {
if(enable && envelope_frequency && --envelope_period == 0) {
envelope_period = envelope_frequency;
if(envelope_direction == 0 && volume > 0) volume--;
if(envelope_direction == 1 && volume < 15) volume++;
}
}
void APU::Square2::write(unsigned r, uint8 data) {
if(r == 1) { //$ff16 NR21
duty = data >> 6;
//length = 64 - (data & 0x3f);
length = (data & 0x3f);
}
if(r == 2) { //$ff17 NR22
envelope_volume = data >> 4;
envelope_direction = data & 0x08;
envelope_frequency = data & 0x07;
if(dac_enable() == false) enable = false;
}
if(r == 3) { //$ff18 NR23
frequency = (frequency & 0x0700) | data;
}
if(r == 4) { //$ff19 NR24
bool initialize = data & 0x80;
counter = data & 0x40;
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
if(initialize) {
enable = dac_enable();
period = 4 * (2048 - frequency);
envelope_period = envelope_frequency;
volume = envelope_volume;
//if(length == 0) length = 64;
}
}
}
void APU::Square2::power() {
enable = 0;
duty = 0;
length = 0;
envelope_volume = 0;
envelope_direction = 0;
envelope_frequency = 0;
frequency = 0;
counter = 0;
output = 0;
duty_output = 0;
phase = 0;
period = 0;
envelope_period = 0;
volume = 0;
}
void APU::Square2::serialize(serializer &s) {
s.integer(enable);
s.integer(duty);
s.integer(length);
s.integer(envelope_volume);
s.integer(envelope_direction);
s.integer(envelope_frequency);
s.integer(frequency);
s.integer(counter);
s.integer(output);
s.integer(duty_output);
s.integer(phase);
s.integer(period);
s.integer(envelope_period);
s.integer(volume);
}
#endif
-27
View File
@@ -1,27 +0,0 @@
struct Square2 {
bool enable;
uint2 duty;
uint6 length;
uint4 envelope_volume;
bool envelope_direction;
uint3 envelope_frequency;
uint11 frequency;
bool counter;
int16 output;
bool duty_output;
uint3 phase;
unsigned period;
uint3 envelope_period;
uint4 volume;
bool dac_enable();
void run();
void clock_length();
void clock_envelope();
void write(unsigned r, uint8 data);
void power();
void serialize(serializer&);
};
Loaded 100 of 1734 files, more files were not shown because too many files have changed in this diff. Show more