mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-09-15 01:32:14 +02:00
Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
c13ae98863 | ||
|
e370a35d7d | ||
|
a721c7e91b | ||
|
14bd3077e5 | ||
|
7236499e2f | ||
|
9b03874f32 | ||
|
a9bff19b5b | ||
|
20be19f876 | ||
|
f748a34e49 | ||
|
0af5703c47 | ||
|
f73d0908c4 | ||
|
18389cb8f7 | ||
|
448a8336b1 | ||
|
233e645772 | ||
|
f0627239bb | ||
|
ae67f268a8 | ||
|
b2331ddb85 | ||
|
2a2f50a8bc | ||
|
30b19613d5 | ||
|
98fc865130 | ||
|
f6a04682f5 | ||
|
87b91f0ace | ||
|
0114e10ede | ||
|
8c591ce44a | ||
|
e2cc164f70 | ||
|
d09e54149b | ||
|
8e4f1be189 | ||
|
f529a84fd1 | ||
|
567d415290 | ||
|
435a194ccd | ||
|
df9de289b9 | ||
|
dd83559786 | ||
|
100ef3a271 | ||
|
bccc5b5a12 | ||
|
acee547da9 | ||
|
b1b146fd7d | ||
|
53e913e225 | ||
|
0cf16ce784 | ||
|
ce38d577ef | ||
|
0a87b99370 | ||
|
82d5761705 | ||
|
9133129209 | ||
|
7d83cde40a | ||
|
bbc77a6cf2 |
86
license.txt
86
license.txt
@@ -1,86 +0,0 @@
|
||||
bsnes (TM) Reference License
|
||||
Copyright (C) 2004 - 2008 byuu
|
||||
All rights reserved
|
||||
|
||||
1. Definitions
|
||||
|
||||
The terms "reproduce", "reproduction", "distribute" and "distribution" have the
|
||||
same meaning here as under U.S. copyright law.
|
||||
|
||||
"The software" means this software package as a whole, including, but not
|
||||
limited to, this license, binaries, source code, documentation, and data.
|
||||
|
||||
"You" means the licensee of the software.
|
||||
|
||||
"The licensor" means the copyright holder of the software, byuu.
|
||||
|
||||
2. Grant of Rights
|
||||
|
||||
Subject to the terms of this license, the licensor grants you a
|
||||
non-transferable, non-exclusive, worldwide, royalty-free copyright license to
|
||||
reproduce the software for non-commercial use only, provided the software
|
||||
remains unmodified, and there is no charge for the software itself, its' use,
|
||||
nor for the medium upon which the software is distributed. The reproduction of
|
||||
modified or derivative works of the software is strictly prohibited, except when
|
||||
transmitted solely to the licensor.
|
||||
|
||||
3. Limitations
|
||||
|
||||
This license does not grant you any rights to use the licensor's name, logo or
|
||||
trademarks.
|
||||
|
||||
The software is provided "as is", and any express or implied warranties,
|
||||
including, but not limited to, the implied warranties of merchantability and
|
||||
fitness for a particular purpose are disclaimed. In no event shall the licensor
|
||||
be liable for any direct, indirect, incidental, special, exemplary, or
|
||||
consequential damages (including, but not limited to, procurement of substitute
|
||||
goods or services; loss of use, data, or profits; or business interruption)
|
||||
however caused and on any theory of liability, whether in contract, strict
|
||||
liability, or tort (including negligence or otherwise) arising in any way out of
|
||||
the use of the software, even if advised of the possibility of such damage.
|
||||
|
||||
In the event that this license is determined to be invalid or unenforceable, the
|
||||
Grant of Rights will become null and void, and no rights shall be granted to the
|
||||
licensee, within the scope of U.S. copyright law.
|
||||
|
||||
4. Exemptions
|
||||
|
||||
The software includes the work of other copyright holders, which is licensed
|
||||
under different agreements, and exempt from this license. Below is a complete
|
||||
list of all such software, and their respective copyright holders and licenses.
|
||||
Further, respective source code files are labeled with their correct licensing
|
||||
information in the header. The lack of such a header indicates said file falls
|
||||
under the bsnes license.
|
||||
|
||||
HQ2x filter, author: MaxST, license: LGPL
|
||||
JMA decompressor, author: NSRT Team, license: GPL (*)
|
||||
NTSC filter, author: blargg, license: LGPL
|
||||
zlib decompressor, license: zlib license
|
||||
|
||||
(*) bsnes has received an exemption from the copyright holder to use this work.
|
||||
|
||||
The software also includes works which have been released to the public domain,
|
||||
which are not bound to any licensing agreements. Below is a complete list of all
|
||||
such software.
|
||||
|
||||
libco, author: byuu
|
||||
libui, author: byuu
|
||||
OBC-1 emu, author: byuu
|
||||
S-DD1 emu, author: Andreas Naive
|
||||
S-RTC emu, author: byuu
|
||||
|
||||
Any software listed above as exemptions may be relicensed individually from
|
||||
bsnes under their respective terms. However, no bsnes licensed portions can be
|
||||
combined with such a derivative work.
|
||||
|
||||
The software also includes the work of other copyright holders, which is
|
||||
licensed under the terms of the bsnes license, with permission to do so from the
|
||||
respective authors. Below is a complete list of all such software.
|
||||
|
||||
Cx4 emu, authors: anomie, Overload, zsKnight, Nach
|
||||
DSP-1 emu, authors: Overload, John Weidman, Neviksti, Andreas Naive
|
||||
DSP-2 emu, author: Overload
|
||||
DSP-3 emu, authors: John Weidman, Kris Bleakley, Lancer, z80 gaiden
|
||||
DSP-4 emu, authors: Dreamer Nom, John Weidman, Kris Bleakley, Nach, z80 gaiden
|
||||
S-DSP emu, author: blargg
|
||||
ST-010 emu, authors: John Weidman, Matthew Kendora, Overload, Feather
|
100
readme.txt
100
readme.txt
@@ -1,100 +0,0 @@
|
||||
bsnes
|
||||
Version: 0.032
|
||||
Author: byuu
|
||||
|
||||
--------
|
||||
General:
|
||||
--------
|
||||
bsnes is a Super Nintendo / Super Famicom emulator that began on
|
||||
October 14th, 2004.
|
||||
|
||||
The latest version can be downloaded from:
|
||||
http://byuu.org/
|
||||
|
||||
Please see license.txt for important licensing information.
|
||||
|
||||
--------------
|
||||
Configuration:
|
||||
--------------
|
||||
bsnes has two configuration files: bsnes.cfg, for program settings; and
|
||||
locale.cfg, for localization.
|
||||
|
||||
For each file, bsnes will start by looking inside the same folder where the
|
||||
bsnes executable is located. If said file is not found, it will then check your
|
||||
user profile folder. On Windows, this is located at "%APPDATA%/.bsnes". On all
|
||||
other operating systems, this is located at "~/.bsnes". If said file is still
|
||||
not found, it will automatically be created in your user profile folder.
|
||||
|
||||
If you wish to use bsnes in single-user mode, be sure that both files exist
|
||||
inside the same folder as the bsnes executable. If they do not, you can simply
|
||||
create new blank files and bsnes will use them in the future.
|
||||
|
||||
If you wish to use bsnes in multi-user mode, simply delete these two files from
|
||||
the bsnes executable directory if they exist.
|
||||
|
||||
If you wish to have multiple configuration profiles for the same user, you will
|
||||
need to make copies of the bsnes executable, and use each one in single-user
|
||||
mode.
|
||||
|
||||
------------------
|
||||
Known Limitations:
|
||||
------------------
|
||||
S-CPU
|
||||
- Multiply / divide register delays not implemented
|
||||
|
||||
S-PPU
|
||||
- Uses scanline-based renderer. This is very inaccurate, but few (if any)
|
||||
games rely on mid-scanline writes to function correctly
|
||||
- Does not support FirstSprite+Y priority
|
||||
- OAM / CGRAM accesses during active display not supported correctly
|
||||
- RTO flags are not calculated on frames that are skipped when frameskipping
|
||||
is enabled. This provides a major speedup, however it will cause in issues
|
||||
in games that test these flags, eg the SNES Test Program Electronics Test.
|
||||
Turning frameskipping off will allow RTO flag calculation on every frame
|
||||
|
||||
Hardware Bugs
|
||||
- S-CPU.r1 HDMA crashing bug not emulated
|
||||
- S-CPU<>S-SMP communication bus conflicts not emulated
|
||||
|
||||
---------------------
|
||||
Unsupported Hardware:
|
||||
---------------------
|
||||
SA-1
|
||||
Coprocessor used in many popular games, including:
|
||||
- Dragon Ball Z Hyper Dimension
|
||||
- Kirby Super Star
|
||||
- Kirby's Dreamland 3
|
||||
- Marvelous
|
||||
- SD Gundam G-NEXT
|
||||
- Super Mario RPG
|
||||
|
||||
Super FX
|
||||
Coprocessor used in many popular games, including:
|
||||
- Doom
|
||||
- Star Fox
|
||||
- Star Fox 2 (unreleased beta)
|
||||
- Super Mario World 2: Yoshi's Island
|
||||
|
||||
SPC7110
|
||||
Coprocessor used only by the following games:
|
||||
- Far East of Eden Zero
|
||||
- Far East of Eden Zero: Shounen Jump no Shou
|
||||
- Momotarou Densetsu Happy
|
||||
- Super Power League 4
|
||||
|
||||
ST-011
|
||||
SETA DSP used by Quick-move Shogi Match with Nidan Rank-holder Morita
|
||||
|
||||
ST-018
|
||||
SETA RISC CPU used by Quick-move Shogi Match with Nidan Rank-holder Morita 2
|
||||
|
||||
Super Gameboy
|
||||
Cartridge passthrough used for playing Gameboy games
|
||||
|
||||
------------------------
|
||||
Unsupported Controllers:
|
||||
------------------------
|
||||
Mouse
|
||||
Super Scope
|
||||
Justifier
|
||||
Multitap (4-port and 5-port)
|
37
src/Makefile
37
src/Makefile
@@ -6,7 +6,7 @@ prefix = /usr/local
|
||||
################
|
||||
|
||||
ifneq ($(findstring gcc,$(compiler)),) # GCC family
|
||||
flags = -O3 -fomit-frame-pointer -Ilib
|
||||
flags = -O3 -fomit-frame-pointer $(if $(call streq,$(platform),x),-mtune=native,) -Ilib
|
||||
c = $(compiler) $(flags)
|
||||
cpp = $(subst cc,++,$(compiler)) $(flags)
|
||||
obj = o
|
||||
@@ -34,7 +34,7 @@ endif
|
||||
##########
|
||||
|
||||
ifeq ($(platform),x) # X11
|
||||
ruby = video.glx video.xv video.sdl audio.openal audio.oss audio.alsa audio.ao input.sdl input.x
|
||||
ruby = video.glx video.xv video.sdl audio.alsa audio.openal audio.oss audio.pulseaudio audio.ao input.sdl input.x
|
||||
link += `pkg-config --libs gtk+-2.0`
|
||||
link += $(call mklib,Xtst)
|
||||
delete = rm -f $1
|
||||
@@ -70,6 +70,7 @@ link += $(if $(findstring audio.alsa,$(ruby)),$(call mklib,asound))
|
||||
link += $(if $(findstring audio.ao,$(ruby)),$(call mklib,ao))
|
||||
link += $(if $(findstring audio.directsound,$(ruby)),$(call mklib,dsound))
|
||||
link += $(if $(findstring audio.openal,$(ruby)),$(if $(call streq,$(platform),x),$(call mklib,openal),$(call mklib,openal32)))
|
||||
link += $(if $(findstring audio.pulseaudio,$(ruby)),$(call mklib,pulse-simple))
|
||||
link += $(if $(findstring input.directinput,$(ruby)),$(call mklib,dinput8) $(call mklib,dxguid))
|
||||
link += $(if $(findstring input.sdl,$(ruby)),`sdl-config --libs`)
|
||||
|
||||
@@ -77,9 +78,10 @@ link += $(if $(findstring input.sdl,$(ruby)),`sdl-config --libs`)
|
||||
### main target and dependencies ###
|
||||
####################################
|
||||
|
||||
objects = main libco hiro ruby libfilter string reader cart cheat \
|
||||
objects = main libco hiro ruby libfilter string \
|
||||
config reader cart cheat \
|
||||
memory smemory cpu scpu smp ssmp sdsp ppu bppu snes \
|
||||
bsx srtc sdd1 cx4 dsp1 dsp2 dsp3 dsp4 obc1 st010
|
||||
bsx srtc sdd1 spc7110 cx4 dsp1 dsp2 dsp3 dsp4 obc1 st010
|
||||
|
||||
ifeq ($(enable_gzip),true)
|
||||
objects += adler32 compress crc32 deflate gzio inffast inflate inftrees ioapi trees unzip zip zutil
|
||||
@@ -125,7 +127,7 @@ all: build;
|
||||
### main ###
|
||||
############
|
||||
|
||||
obj/main.$(obj): ui/main.cpp ui/* ui/base/* ui/loader/* ui/settings/*
|
||||
obj/main.$(obj): ui/main.cpp ui/* ui/base/* ui/event/* ui/loader/* ui/settings/*
|
||||
obj/bsnes.res: ui/bsnes.rc; rc /r /foobj/bsnes.res ui/bsnes.rc
|
||||
obj/bsnesrc.$(obj): ui/bsnes.rc; windres ui/bsnes.rc obj/bsnesrc.$(obj)
|
||||
|
||||
@@ -133,7 +135,7 @@ obj/bsnesrc.$(obj): ui/bsnes.rc; windres ui/bsnes.rc obj/bsnesrc.$(obj)
|
||||
### libraries ###
|
||||
#################
|
||||
|
||||
obj/ruby.$(obj): lib/ruby/ruby.cpp lib/ruby/*
|
||||
obj/ruby.$(obj): lib/ruby/ruby.cpp lib/ruby/* lib/ruby/video/* lib/ruby/audio/* lib/ruby/input/*
|
||||
$(call compile,$(rubydef) $(rubyflags))
|
||||
obj/hiro.$(obj): lib/hiro/hiro.cpp lib/hiro/* lib/hiro/gtk/* lib/hiro/win/*
|
||||
$(call compile,$(if $(call streq,$(platform),x),`pkg-config --cflags gtk+-2.0`))
|
||||
@@ -146,6 +148,7 @@ obj/string.$(obj): lib/nall/string.cpp lib/nall/*
|
||||
### utilities ###
|
||||
#################
|
||||
|
||||
obj/config.$(obj): config/config.cpp config/*
|
||||
obj/reader.$(obj): reader/reader.cpp reader/*
|
||||
obj/cart.$(obj) : cart/cart.cpp cart/*
|
||||
obj/cheat.$(obj) : cheat/cheat.cpp cheat/*
|
||||
@@ -176,7 +179,6 @@ obj/ssmp.$(obj): smp/ssmp/ssmp.cpp smp/ssmp/* smp/ssmp/core/* smp/ssmp/memory/*
|
||||
###########
|
||||
|
||||
obj/adsp.$(obj): dsp/adsp/adsp.cpp dsp/adsp/*
|
||||
obj/bdsp.$(obj): dsp/bdsp/bdsp.cpp dsp/bdsp/*
|
||||
obj/sdsp.$(obj): dsp/sdsp/sdsp.cpp dsp/sdsp/*
|
||||
|
||||
###########
|
||||
@@ -196,16 +198,17 @@ obj/snes.$(obj): snes/snes.cpp snes/* snes/scheduler/* snes/video/* snes/audio/*
|
||||
### special chips ###
|
||||
#####################
|
||||
|
||||
obj/bsx.$(obj) : chip/bsx/bsx.cpp chip/bsx/*
|
||||
obj/srtc.$(obj) : chip/srtc/srtc.cpp chip/srtc/*
|
||||
obj/sdd1.$(obj) : chip/sdd1/sdd1.cpp chip/sdd1/*
|
||||
obj/cx4.$(obj) : chip/cx4/cx4.cpp chip/cx4/*
|
||||
obj/dsp1.$(obj) : chip/dsp1/dsp1.cpp chip/dsp1/*
|
||||
obj/dsp2.$(obj) : chip/dsp2/dsp2.cpp chip/dsp2/*
|
||||
obj/dsp3.$(obj) : chip/dsp3/dsp3.cpp chip/dsp3/*
|
||||
obj/dsp4.$(obj) : chip/dsp4/dsp4.cpp chip/dsp4/*
|
||||
obj/obc1.$(obj) : chip/obc1/obc1.cpp chip/obc1/*
|
||||
obj/st010.$(obj): chip/st010/st010.cpp chip/st010/*
|
||||
obj/bsx.$(obj) : chip/bsx/bsx.cpp chip/bsx/*
|
||||
obj/srtc.$(obj) : chip/srtc/srtc.cpp chip/srtc/*
|
||||
obj/sdd1.$(obj) : chip/sdd1/sdd1.cpp chip/sdd1/*
|
||||
obj/spc7110.$(obj): chip/spc7110/spc7110.cpp chip/spc7110/*
|
||||
obj/cx4.$(obj) : chip/cx4/cx4.cpp chip/cx4/*
|
||||
obj/dsp1.$(obj) : chip/dsp1/dsp1.cpp chip/dsp1/*
|
||||
obj/dsp2.$(obj) : chip/dsp2/dsp2.cpp chip/dsp2/*
|
||||
obj/dsp3.$(obj) : chip/dsp3/dsp3.cpp chip/dsp3/*
|
||||
obj/dsp4.$(obj) : chip/dsp4/dsp4.cpp chip/dsp4/*
|
||||
obj/obc1.$(obj) : chip/obc1/obc1.cpp chip/obc1/*
|
||||
obj/st010.$(obj) : chip/st010/st010.cpp chip/st010/*
|
||||
|
||||
############
|
||||
### zlib ###
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#define BSNES_VERSION "0.032"
|
||||
#define BSNES_VERSION "0.038"
|
||||
#define BSNES_TITLE "bsnes v" BSNES_VERSION
|
||||
|
||||
#define BUSCORE sBus
|
||||
@@ -19,38 +19,34 @@
|
||||
//game genie + pro action replay code support (~2% speed hit)
|
||||
#define CHEAT_SYSTEM
|
||||
|
||||
#include <libco/libco.h>
|
||||
|
||||
#include <nall/algorithm.hpp>
|
||||
#include <nall/array.hpp>
|
||||
#include <nall/bit.hpp>
|
||||
#include <nall/config.hpp>
|
||||
#include <nall/detect.hpp>
|
||||
#include <nall/endian.hpp>
|
||||
#include <nall/file.hpp>
|
||||
#include <nall/function.hpp>
|
||||
#include <nall/modulo.hpp>
|
||||
#include <nall/new.hpp>
|
||||
#include <nall/platform.hpp>
|
||||
#include <nall/sort.hpp>
|
||||
#include <nall/stdint.hpp>
|
||||
#include <nall/string.hpp>
|
||||
#include <nall/utility.hpp>
|
||||
#include <nall/vector.hpp>
|
||||
using namespace nall;
|
||||
|
||||
#include <libco/libco.h>
|
||||
#include <bbase.h>
|
||||
typedef int8_t int8;
|
||||
typedef int16_t int16;
|
||||
typedef int32_t int32;
|
||||
typedef int64_t int64;
|
||||
typedef uint8_t uint8;
|
||||
typedef uint16_t uint16;
|
||||
typedef uint32_t uint32;
|
||||
typedef uint64_t uint64;
|
||||
typedef unsigned uint;
|
||||
|
||||
//platform-specific global functions
|
||||
void alert(const char*, ...);
|
||||
void dprintf(const char*, ...);
|
||||
void dprintf(uint, const char*, ...);
|
||||
|
||||
namespace source {
|
||||
enum {
|
||||
none = 0,
|
||||
debug,
|
||||
cpu,
|
||||
ppu,
|
||||
smp,
|
||||
dsp,
|
||||
bus,
|
||||
};
|
||||
};
|
||||
|
||||
#include "interface.h"
|
||||
#include "interface.hpp"
|
@@ -1,9 +1,10 @@
|
||||
#include "../base.h"
|
||||
#include <../base.hpp>
|
||||
#define CART_CPP
|
||||
|
||||
#include <nall/crc32.hpp>
|
||||
#include <nall/ups.hpp>
|
||||
|
||||
|
||||
#include "cart_load.cpp"
|
||||
#include "cart_normal.cpp"
|
||||
#include "cart_bsx.cpp"
|
||||
#include "cart_bsc.cpp"
|
||||
@@ -13,7 +14,7 @@
|
||||
#include "cart_header.cpp"
|
||||
|
||||
namespace memory {
|
||||
MappedRAM cartrom, cartram;
|
||||
MappedRAM cartrom, cartram, cartrtc;
|
||||
MappedRAM bscram;
|
||||
MappedRAM stArom, stAram;
|
||||
MappedRAM stBrom, stBram;
|
||||
@@ -21,58 +22,34 @@ namespace memory {
|
||||
|
||||
Cartridge cartridge;
|
||||
|
||||
const char* Cartridge::name() { return info.filename; }
|
||||
Cartridge::CartridgeMode Cartridge::mode() { return info.mode; }
|
||||
Cartridge::MemoryMapper Cartridge::mapper() { return info.mapper; }
|
||||
Cartridge::Region Cartridge::region() { return info.region; }
|
||||
|
||||
bool Cartridge::loaded() { return cart.loaded; }
|
||||
|
||||
void Cartridge::load_begin(CartridgeType cart_type) {
|
||||
cart.rom = cart.ram = 0;
|
||||
void Cartridge::load_begin(CartridgeMode mode) {
|
||||
cart.rom = cart.ram = cart.rtc = 0;
|
||||
bs.ram = 0;
|
||||
stA.rom = stA.ram = 0;
|
||||
stB.rom = stB.ram = 0;
|
||||
|
||||
cart.rom_size = cart.ram_size = 0;
|
||||
cart.rom_size = cart.ram_size = cart.rtc_size = 0;
|
||||
bs.ram_size = 0;
|
||||
stA.rom_size = stA.ram_size = 0;
|
||||
stB.rom_size = stB.ram_size = 0;
|
||||
|
||||
info.type = cart_type;
|
||||
info.mode = mode;
|
||||
info.patched = false;
|
||||
|
||||
info.bsxbase = false;
|
||||
info.bsxcart = false;
|
||||
info.bsxflash = false;
|
||||
info.st = false;
|
||||
|
||||
info.superfx = false;
|
||||
info.sa1 = false;
|
||||
info.spc7110 = false;
|
||||
info.srtc = false;
|
||||
info.sdd1 = false;
|
||||
info.cx4 = false;
|
||||
info.dsp1 = false;
|
||||
info.dsp2 = false;
|
||||
info.dsp3 = false;
|
||||
info.dsp4 = false;
|
||||
info.obc1 = false;
|
||||
info.st010 = false;
|
||||
info.st011 = false;
|
||||
info.st018 = false;
|
||||
|
||||
info.dsp1_mapper = DSP1Unmapped;
|
||||
|
||||
info.header_index = 0xffc0;
|
||||
info.mapper = LoROM;
|
||||
info.name[0] = 0;
|
||||
info.region = NTSC;
|
||||
|
||||
info.rom_size = 0;
|
||||
info.ram_size = 0;
|
||||
}
|
||||
|
||||
void Cartridge::load_end() {
|
||||
memory::cartrom.map(cart.rom, cart.rom_size);
|
||||
memory::cartram.map(cart.ram, cart.ram_size);
|
||||
memory::cartrtc.map(cart.rtc, cart.rtc_size);
|
||||
memory::bscram.map(bs.ram, bs.ram_size);
|
||||
memory::stArom.map(stA.rom, stA.rom_size);
|
||||
memory::stAram.map(stA.ram, stA.ram_size);
|
||||
@@ -87,7 +64,7 @@ void Cartridge::load_end() {
|
||||
memory::stBrom.write_protect(true);
|
||||
memory::stBram.write_protect(false);
|
||||
|
||||
if(fexists(get_cheat_filename(cart.fn, "cht"))) {
|
||||
if(file::exists(get_cheat_filename(cart.fn, "cht"))) {
|
||||
cheat.clear();
|
||||
cheat.load(cheatfn);
|
||||
}
|
||||
@@ -101,25 +78,23 @@ bool Cartridge::unload() {
|
||||
|
||||
bus.unload_cart();
|
||||
|
||||
switch(info.type) {
|
||||
case CartridgeNormal: unload_cart_normal(); break;
|
||||
case CartridgeBSX: unload_cart_bsx(); break;
|
||||
case CartridgeBSC: unload_cart_bsc(); break;
|
||||
case CartridgeSufamiTurbo: unload_cart_st(); break;
|
||||
switch(info.mode) {
|
||||
case ModeNormal: unload_cart_normal(); break;
|
||||
case ModeBSX: unload_cart_bsx(); break;
|
||||
case ModeBSC: unload_cart_bsc(); break;
|
||||
case ModeSufamiTurbo: unload_cart_st(); break;
|
||||
}
|
||||
|
||||
if(cart.rom) { delete[] cart.rom; cart.rom = 0; }
|
||||
if(cart.ram) { delete[] cart.ram; cart.ram = 0; }
|
||||
if(cart.rtc) { delete[] cart.rtc; cart.rtc = 0; }
|
||||
if(bs.ram) { delete[] bs.ram; bs.ram = 0; }
|
||||
if(stA.rom) { delete[] stA.rom; stA.rom = 0; }
|
||||
if(stA.ram) { delete[] stA.ram; stA.ram = 0; }
|
||||
if(stB.rom) { delete[] stB.rom; stB.rom = 0; }
|
||||
if(stB.ram) { delete[] stB.ram; stB.ram = 0; }
|
||||
|
||||
char fn[PATH_MAX];
|
||||
strcpy(fn, cart.fn);
|
||||
modify_extension(fn, "cht");
|
||||
if(cheat.count() > 0 || fexists(get_cheat_filename(cart.fn, "cht"))) {
|
||||
if(cheat.count() > 0 || file::exists(get_cheat_filename(cart.fn, "cht"))) {
|
||||
cheat.save(cheatfn);
|
||||
cheat.clear();
|
||||
}
|
||||
@@ -135,3 +110,58 @@ Cartridge::Cartridge() {
|
||||
Cartridge::~Cartridge() {
|
||||
if(cart.loaded == true) unload();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
void Cartridge::cartinfo_t::reset() {
|
||||
type = TypeUnknown;
|
||||
mapper = LoROM;
|
||||
dsp1_mapper = DSP1Unmapped;
|
||||
region = NTSC;
|
||||
|
||||
rom_size = 0;
|
||||
ram_size = 0;
|
||||
|
||||
bsxslot = false;
|
||||
superfx = false;
|
||||
sa1 = false;
|
||||
srtc = false;
|
||||
sdd1 = false;
|
||||
spc7110 = false;
|
||||
spc7110rtc = false;
|
||||
cx4 = false;
|
||||
dsp1 = false;
|
||||
dsp2 = false;
|
||||
dsp3 = false;
|
||||
dsp4 = false;
|
||||
obc1 = false;
|
||||
st010 = false;
|
||||
st011 = false;
|
||||
st018 = false;
|
||||
}
|
||||
|
||||
//apply cart-specific settings to current cartridge mode settings
|
||||
Cartridge::info_t& Cartridge::info_t::operator=(const Cartridge::cartinfo_t &source) {
|
||||
mapper = source.mapper;
|
||||
dsp1_mapper = source.dsp1_mapper;
|
||||
region = source.region;
|
||||
|
||||
bsxslot = source.bsxslot;
|
||||
superfx = source.superfx;
|
||||
sa1 = source.sa1;
|
||||
srtc = source.srtc;
|
||||
sdd1 = source.sdd1;
|
||||
spc7110 = source.spc7110;
|
||||
spc7110rtc = source.spc7110rtc;
|
||||
cx4 = source.cx4;
|
||||
dsp1 = source.dsp1;
|
||||
dsp2 = source.dsp2;
|
||||
dsp3 = source.dsp3;
|
||||
dsp4 = source.dsp4;
|
||||
obc1 = source.obc1;
|
||||
st010 = source.st010;
|
||||
st011 = source.st011;
|
||||
st018 = source.st018;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
@@ -1,25 +1,34 @@
|
||||
class Cartridge {
|
||||
public:
|
||||
enum CartridgeMode {
|
||||
ModeNormal,
|
||||
ModeBSC,
|
||||
ModeBSX,
|
||||
ModeSufamiTurbo,
|
||||
};
|
||||
|
||||
enum CartridgeType {
|
||||
CartridgeNormal,
|
||||
CartridgeBSX,
|
||||
CartridgeBSC,
|
||||
CartridgeSufamiTurbo,
|
||||
TypeNormal,
|
||||
TypeBSC,
|
||||
TypeBSXBIOS,
|
||||
TypeBSX,
|
||||
TypeSufamiTurboBIOS,
|
||||
TypeSufamiTurbo,
|
||||
TypeUnknown,
|
||||
};
|
||||
|
||||
enum HeaderField {
|
||||
CART_NAME = 0x00,
|
||||
MAPPER = 0x15,
|
||||
ROM_TYPE = 0x16,
|
||||
ROM_SIZE = 0x17,
|
||||
RAM_SIZE = 0x18,
|
||||
REGION = 0x19,
|
||||
COMPANY = 0x1a,
|
||||
VERSION = 0x1b,
|
||||
ICKSUM = 0x1c,
|
||||
CKSUM = 0x1e,
|
||||
RESL = 0x3c,
|
||||
RESH = 0x3d,
|
||||
CartName = 0x00,
|
||||
Mapper = 0x15,
|
||||
RomType = 0x16,
|
||||
RomSize = 0x17,
|
||||
RamSize = 0x18,
|
||||
CartRegion = 0x19,
|
||||
Company = 0x1a,
|
||||
Version = 0x1b,
|
||||
Complement = 0x1c, //inverse checksum
|
||||
Checksum = 0x1e,
|
||||
ResetVector = 0x3c,
|
||||
};
|
||||
|
||||
enum Region {
|
||||
@@ -32,9 +41,10 @@ public:
|
||||
HiROM,
|
||||
ExLoROM,
|
||||
ExHiROM,
|
||||
BSXROM,
|
||||
SPC7110ROM,
|
||||
BSCLoROM,
|
||||
BSCHiROM,
|
||||
BSXROM,
|
||||
STROM,
|
||||
};
|
||||
|
||||
@@ -45,11 +55,16 @@ public:
|
||||
DSP1HiROM,
|
||||
};
|
||||
|
||||
const char* name();
|
||||
CartridgeMode mode();
|
||||
MemoryMapper mapper();
|
||||
Region region();
|
||||
|
||||
struct {
|
||||
bool loaded;
|
||||
char fn[PATH_MAX];
|
||||
uint8 *rom, *ram;
|
||||
uint rom_size, ram_size;
|
||||
uint8 *rom, *ram, *rtc;
|
||||
uint rom_size, ram_size, rtc_size;
|
||||
} cart;
|
||||
|
||||
struct {
|
||||
@@ -64,27 +79,22 @@ public:
|
||||
uint rom_size, ram_size;
|
||||
} stA, stB;
|
||||
|
||||
struct {
|
||||
struct cartinfo_t {
|
||||
CartridgeType type;
|
||||
|
||||
uint32 crc32;
|
||||
char filename[PATH_MAX * 4];
|
||||
char name[128];
|
||||
|
||||
Region region;
|
||||
MemoryMapper mapper;
|
||||
uint rom_size;
|
||||
uint ram_size;
|
||||
DSP1MemoryMapper dsp1_mapper;
|
||||
Region region;
|
||||
|
||||
bool bsxbase;
|
||||
bool bsxcart;
|
||||
bool bsxflash;
|
||||
bool st;
|
||||
unsigned rom_size;
|
||||
unsigned ram_size;
|
||||
|
||||
bool bsxslot;
|
||||
bool superfx;
|
||||
bool sa1;
|
||||
bool srtc;
|
||||
bool sdd1;
|
||||
bool sdd1;
|
||||
bool spc7110;
|
||||
bool spc7110rtc;
|
||||
bool cx4;
|
||||
bool dsp1;
|
||||
bool dsp2;
|
||||
@@ -95,17 +105,53 @@ public:
|
||||
bool st011;
|
||||
bool st018;
|
||||
|
||||
DSP1MemoryMapper dsp1_mapper;
|
||||
void reset();
|
||||
};
|
||||
|
||||
uint header_index;
|
||||
struct info_t {
|
||||
char filename[PATH_MAX * 4];
|
||||
bool patched;
|
||||
|
||||
CartridgeMode mode;
|
||||
MemoryMapper mapper;
|
||||
DSP1MemoryMapper dsp1_mapper;
|
||||
Region region;
|
||||
|
||||
bool bsxcart; //is BS-X cart inserted?
|
||||
bool bsxflash; //is BS-X flash cart inserted into BS-X cart?
|
||||
|
||||
bool bsxslot;
|
||||
bool superfx;
|
||||
bool sa1;
|
||||
bool srtc;
|
||||
bool sdd1;
|
||||
bool spc7110;
|
||||
bool spc7110rtc;
|
||||
bool cx4;
|
||||
bool dsp1;
|
||||
bool dsp2;
|
||||
bool dsp3;
|
||||
bool dsp4;
|
||||
bool obc1;
|
||||
bool st010;
|
||||
bool st011;
|
||||
bool st018;
|
||||
|
||||
info_t& operator=(const cartinfo_t&);
|
||||
} info;
|
||||
|
||||
MemoryMapper mapper();
|
||||
Region region();
|
||||
struct {
|
||||
char fn[PATH_MAX];
|
||||
uint8_t *data;
|
||||
unsigned size;
|
||||
} image;
|
||||
bool load_image(const char*);
|
||||
bool inspect_image(cartinfo_t &cartinfo, const char *filename);
|
||||
bool load_ram(const char *filename, uint8_t *&data, unsigned size, uint8_t init);
|
||||
|
||||
void load_cart_normal(const char*);
|
||||
void load_cart_bsx(const char*, const char*);
|
||||
void load_cart_bsc(const char*, const char*);
|
||||
void load_cart_bsx(const char*, const char*);
|
||||
void load_cart_st(const char*, const char*, const char*);
|
||||
|
||||
void unload_cart_normal();
|
||||
@@ -114,13 +160,13 @@ public:
|
||||
void unload_cart_st();
|
||||
|
||||
bool loaded();
|
||||
void load_begin(CartridgeType);
|
||||
void load_begin(CartridgeMode);
|
||||
void load_end();
|
||||
bool unload();
|
||||
|
||||
void find_header();
|
||||
void read_header();
|
||||
void read_extended_header();
|
||||
void read_header(cartinfo_t &info, const uint8_t *data, unsigned size);
|
||||
unsigned find_header(const uint8_t *data, unsigned size);
|
||||
unsigned score_header(const uint8_t *data, unsigned size, unsigned addr);
|
||||
|
||||
enum CompressionMode {
|
||||
CompressionNone, //always load without compression
|
||||
@@ -142,12 +188,13 @@ public:
|
||||
|
||||
private:
|
||||
char patchfn[PATH_MAX];
|
||||
char savefn[PATH_MAX];
|
||||
char savefn[PATH_MAX];
|
||||
char rtcfn[PATH_MAX];
|
||||
char cheatfn[PATH_MAX];
|
||||
};
|
||||
|
||||
namespace memory {
|
||||
extern MappedRAM cartrom, cartram;
|
||||
extern MappedRAM cartrom, cartram, cartrtc;
|
||||
extern MappedRAM bscram;
|
||||
extern MappedRAM stArom, stAram;
|
||||
extern MappedRAM stBrom, stBram;
|
@@ -1,57 +1,36 @@
|
||||
#ifdef CART_CPP
|
||||
|
||||
void Cartridge::load_cart_bsc(const char *base, const char *slot) {
|
||||
if(!base || !*base) return;
|
||||
|
||||
strcpy(cart.fn, base);
|
||||
strcpy(bs.fn, slot ? slot : "");
|
||||
load_begin(CartridgeBSC);
|
||||
|
||||
uint8_t *data = 0;
|
||||
uint8_t *data;
|
||||
unsigned size;
|
||||
load_file(cart.fn, data, size, CompressionAuto);
|
||||
cart.rom = data, cart.rom_size = size;
|
||||
strcpy(cart.fn, base);
|
||||
strcpy(bs.fn, slot);
|
||||
|
||||
if(load_file(get_patch_filename(cart.fn, "ups"), data, size, CompressionInspect) == true) {
|
||||
apply_patch(data, size, cart.rom, cart.rom_size);
|
||||
delete[] data;
|
||||
load_begin(ModeBSC);
|
||||
if(load_image(base) == false) return;
|
||||
|
||||
cartinfo_t cartinfo;
|
||||
read_header(cartinfo, cart.rom = image.data, cart.rom_size = image.size);
|
||||
info = cartinfo;
|
||||
|
||||
if(load_image(slot) == true) {
|
||||
info.bsxflash = true;
|
||||
bs.ram = image.data;
|
||||
bs.ram_size = image.size;
|
||||
}
|
||||
|
||||
if(*bs.fn) {
|
||||
if(load_file(bs.fn, data, size, CompressionAuto) == true) {
|
||||
info.bsxflash = true;
|
||||
bs.ram = data, bs.ram_size = size;
|
||||
if(load_file(get_patch_filename(bs.fn, "ups"), data, size, CompressionInspect) == true) {
|
||||
apply_patch(data, size, bs.ram, bs.ram_size);
|
||||
delete[] data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
find_header();
|
||||
read_header();
|
||||
|
||||
info.mapper = cartridge.info.header_index == 0x7fc0 ? BSCLoROM : BSCHiROM;
|
||||
info.region = NTSC;
|
||||
|
||||
if(info.ram_size > 0) {
|
||||
cart.ram = new uint8_t[cart.ram_size = info.ram_size];
|
||||
memset(cart.ram, 0xff, cart.ram_size);
|
||||
|
||||
if(load_file(get_save_filename(cart.fn, "srm"), data, size, CompressionNone) == true) {
|
||||
memcpy(cart.ram, data, min(size, cart.ram_size));
|
||||
delete[] data;
|
||||
}
|
||||
if(cartinfo.ram_size > 0) {
|
||||
load_ram(get_save_filename(base, "srm"), cart.ram, cart.ram_size = cartinfo.ram_size, 0xff);
|
||||
}
|
||||
|
||||
load_end();
|
||||
|
||||
//set base filename
|
||||
strcpy(info.filename, cart.fn);
|
||||
strcpy(info.filename, base);
|
||||
get_base_filename(info.filename);
|
||||
if(*bs.fn) {
|
||||
if(*slot) {
|
||||
char filenameBS[PATH_MAX];
|
||||
strcpy(filenameBS, bs.fn);
|
||||
strcpy(filenameBS, slot);
|
||||
get_base_filename(filenameBS);
|
||||
strcat(info.filename, " + ");
|
||||
strcat(info.filename, filenameBS);
|
||||
|
@@ -1,27 +1,20 @@
|
||||
#ifdef CART_CPP
|
||||
|
||||
void Cartridge::load_cart_bsx(const char *base, const char *slot) {
|
||||
if(!base || !*base) return;
|
||||
|
||||
strcpy(cart.fn, base);
|
||||
strcpy(bs.fn, slot ? slot : "");
|
||||
|
||||
load_begin(CartridgeBSX);
|
||||
info.bsxbase = true;
|
||||
info.bsxcart = true;
|
||||
info.mapper = BSXROM;
|
||||
info.region = NTSC;
|
||||
|
||||
uint8_t *data = 0;
|
||||
uint8_t *data;
|
||||
unsigned size;
|
||||
load_file(cart.fn, data, size, CompressionAuto);
|
||||
cart.rom = data, cart.rom_size = size;
|
||||
cart.ram = 0, cart.ram_size = 0;
|
||||
strcpy(cart.fn, base);
|
||||
strcpy(bs.fn, slot);
|
||||
|
||||
if(load_file(get_patch_filename(cart.fn, "ups"), data, size, CompressionInspect) == true) {
|
||||
apply_patch(data, size, cart.rom, cart.rom_size);
|
||||
delete[] data;
|
||||
}
|
||||
load_begin(ModeBSX);
|
||||
if(load_image(base) == false) return;
|
||||
info.bsxcart = true;
|
||||
|
||||
cartinfo_t cartinfo;
|
||||
read_header(cartinfo, cart.rom = image.data, cart.rom_size = image.size);
|
||||
info = cartinfo;
|
||||
cart.ram = 0;
|
||||
cart.ram_size = 0;
|
||||
|
||||
memset(bsxcart.sram.handle (), 0x00, bsxcart.sram.size ());
|
||||
memset(bsxcart.psram.handle(), 0x00, bsxcart.psram.size());
|
||||
@@ -36,20 +29,15 @@ void Cartridge::load_cart_bsx(const char *base, const char *slot) {
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
if(*bs.fn) {
|
||||
if(load_file(bs.fn, data, size, CompressionAuto) == true) {
|
||||
info.bsxflash = true;
|
||||
bs.ram = data, bs.ram_size = size;
|
||||
if(load_file(get_patch_filename(bs.fn, "ups"), data, size, CompressionInspect) == true) {
|
||||
apply_patch(data, size, bs.ram, bs.ram_size);
|
||||
delete[] data;
|
||||
}
|
||||
}
|
||||
if(load_image(slot)) {
|
||||
info.bsxflash = true;
|
||||
bs.ram = image.data;
|
||||
bs.ram_size = image.size;
|
||||
}
|
||||
|
||||
load_end();
|
||||
|
||||
strcpy(info.filename, !*bs.fn ? cart.fn : bs.fn);
|
||||
strcpy(info.filename, !*slot ? base : slot);
|
||||
get_base_filename(info.filename);
|
||||
}
|
||||
|
||||
|
@@ -1,27 +1,27 @@
|
||||
#ifdef CART_CPP
|
||||
|
||||
#include "../reader/filereader.h"
|
||||
|
||||
#if defined(GZIP_SUPPORT)
|
||||
#include "../reader/gzreader.h"
|
||||
#include "../reader/zipreader.h"
|
||||
#endif
|
||||
|
||||
#if defined(JMA_SUPPORT)
|
||||
#include "../reader/jmareader.h"
|
||||
#endif
|
||||
|
||||
char* Cartridge::modify_extension(char *filename, const char *extension) {
|
||||
int i;
|
||||
for(i = strlen(filename); i >= 0; i--) {
|
||||
if(filename[i] == '.') break;
|
||||
if(filename[i] == '/') break;
|
||||
if(filename[i] == '\\') break;
|
||||
}
|
||||
if(i > 0 && filename[i] == '.') filename[i] = 0;
|
||||
strcat(filename, ".");
|
||||
strcat(filename, extension);
|
||||
return filename;
|
||||
#include "../reader/filereader.hpp"
|
||||
|
||||
#if defined(GZIP_SUPPORT)
|
||||
#include "../reader/gzreader.hpp"
|
||||
#include "../reader/zipreader.hpp"
|
||||
#endif
|
||||
|
||||
#if defined(JMA_SUPPORT)
|
||||
#include "../reader/jmareader.hpp"
|
||||
#endif
|
||||
|
||||
char* Cartridge::modify_extension(char *filename, const char *extension) {
|
||||
int i;
|
||||
for(i = strlen(filename); i >= 0; i--) {
|
||||
if(filename[i] == '.') break;
|
||||
if(filename[i] == '/') break;
|
||||
if(filename[i] == '\\') break;
|
||||
}
|
||||
if(i > 0 && filename[i] == '.') filename[i] = 0;
|
||||
strcat(filename, ".");
|
||||
strcat(filename, extension);
|
||||
return filename;
|
||||
}
|
||||
|
||||
//remove directory information and file extension ("/foo/bar.ext" -> "bar")
|
||||
@@ -47,36 +47,20 @@ char* Cartridge::get_base_filename(char *filename) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char* Cartridge::get_path_filename(char *filename, const char *path, const char *source, const char *extension) {
|
||||
strcpy(filename, source);
|
||||
for(char *p = filename; *p; p++) { if(*p == '\\') *p = '/'; }
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
char* Cartridge::get_path_filename(char *filename, const char *path, const char *source, const char *extension) {
|
||||
strcpy(filename, source);
|
||||
modify_extension(filename, extension);
|
||||
|
||||
//override path with user-specified folder, if one was defined
|
||||
if(*path) {
|
||||
lstring part;
|
||||
split(part, "/", filename);
|
||||
string fn = path;
|
||||
if(strend(fn, "/") == false) strcat(fn, "/");
|
||||
strcat(fn, part[count(part) - 1]);
|
||||
strcpy(filename, fn);
|
||||
|
||||
//resolve relative path, if found
|
||||
if(strbegin(fn, "./") == true) {
|
||||
ltrim(fn, "./");
|
||||
strcpy(filename, config::path.base);
|
||||
strcat(filename, fn);
|
||||
}
|
||||
}
|
||||
|
||||
return filename;
|
||||
strcpy(filename, config::filepath(filename, path));
|
||||
return filename;
|
||||
}
|
||||
|
||||
char* Cartridge::get_patch_filename(const char *source, const char *extension) {
|
||||
return get_path_filename(patchfn, config::path.patch, source, extension);
|
||||
}
|
||||
}
|
||||
|
||||
char* Cartridge::get_save_filename(const char *source, const char *extension) {
|
||||
return get_path_filename(savefn, config::path.save, source, extension);
|
||||
@@ -85,63 +69,52 @@ char* Cartridge::get_save_filename(const char *source, const char *extension) {
|
||||
char* Cartridge::get_cheat_filename(const char *source, const char *extension) {
|
||||
return get_path_filename(cheatfn, config::path.cheat, source, extension);
|
||||
}
|
||||
|
||||
bool Cartridge::load_file(const char *fn, uint8 *&data, uint &size, CompressionMode compression) {
|
||||
dprintf("* Loading \"%s\" ...", fn);
|
||||
|
||||
if(fexists(fn) == false) return false;
|
||||
|
||||
bool Cartridge::load_file(const char *fn, uint8 *&data, uint &size, CompressionMode compression) {
|
||||
if(file::exists(fn) == false) return false;
|
||||
|
||||
Reader::Type filetype = Reader::Normal;
|
||||
if(compression == CompressionInspect) filetype = Reader::detect(fn, true);
|
||||
if(compression == CompressionAuto) filetype = Reader::detect(fn, config::file.autodetect_type);
|
||||
|
||||
switch(filetype) {
|
||||
default:
|
||||
dprintf("* Warning: filetype detected as unsupported compression type.");
|
||||
dprintf("* Will attempt to load as uncompressed file -- may fail.");
|
||||
case Reader::Normal: {
|
||||
FileReader ff(fn);
|
||||
if(!ff.ready()) {
|
||||
alert("Error loading image file (%s)!", fn);
|
||||
return false;
|
||||
}
|
||||
size = ff.size();
|
||||
data = ff.read();
|
||||
} break;
|
||||
|
||||
#ifdef GZIP_SUPPORT
|
||||
case Reader::GZIP: {
|
||||
GZReader gf(fn);
|
||||
if(!gf.ready()) {
|
||||
alert("Error loading image file (%s)!", fn);
|
||||
return false;
|
||||
}
|
||||
size = gf.size();
|
||||
data = gf.read();
|
||||
} break;
|
||||
|
||||
case Reader::ZIP: {
|
||||
ZipReader zf(fn);
|
||||
size = zf.size();
|
||||
data = zf.read();
|
||||
} break;
|
||||
#endif
|
||||
|
||||
#ifdef JMA_SUPPORT
|
||||
case Reader::JMA: {
|
||||
try {
|
||||
JMAReader jf(fn);
|
||||
size = jf.size();
|
||||
data = jf.read();
|
||||
} catch(JMA::jma_errors jma_error) {
|
||||
alert("Error loading image file (%s)!", fn);
|
||||
return false;
|
||||
}
|
||||
} break;
|
||||
#endif
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
switch(filetype) { default:
|
||||
case Reader::Normal: {
|
||||
FileReader ff(fn);
|
||||
if(!ff.ready()) return false;
|
||||
size = ff.size();
|
||||
data = ff.read();
|
||||
} break;
|
||||
|
||||
#ifdef GZIP_SUPPORT
|
||||
case Reader::GZIP: {
|
||||
GZReader gf(fn);
|
||||
if(!gf.ready()) return false;
|
||||
size = gf.size();
|
||||
data = gf.read();
|
||||
} break;
|
||||
|
||||
case Reader::ZIP: {
|
||||
ZipReader zf(fn);
|
||||
if(!zf.ready()) return false;
|
||||
size = zf.size();
|
||||
data = zf.read();
|
||||
} break;
|
||||
#endif
|
||||
|
||||
#ifdef JMA_SUPPORT
|
||||
case Reader::JMA: {
|
||||
try {
|
||||
JMAReader jf(fn);
|
||||
size = jf.size();
|
||||
data = jf.read();
|
||||
} catch(JMA::jma_errors jma_error) {
|
||||
return false;
|
||||
}
|
||||
} break;
|
||||
#endif
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Cartridge::apply_patch(const uint8_t *pdata, const unsigned psize, uint8_t *&data, unsigned &size) {
|
||||
@@ -157,23 +130,23 @@ bool Cartridge::apply_patch(const uint8_t *pdata, const unsigned psize, uint8_t
|
||||
if(result == ups::output_crc32_invalid) apply = true;
|
||||
}
|
||||
|
||||
//if patch application was successful, replace old data, size with new data, size
|
||||
if(apply == true) {
|
||||
delete[] data;
|
||||
data = new uint8_t[size = outsize];
|
||||
memcpy(data, outdata, outsize);
|
||||
} else {
|
||||
dprintf("* Warning: patch application failed!");
|
||||
}
|
||||
|
||||
if(outdata) delete[] outdata;
|
||||
}
|
||||
|
||||
return apply;
|
||||
}
|
||||
|
||||
bool Cartridge::save_file(const char *fn, uint8 *data, uint size) {
|
||||
FILE *fp = fopen(fn, "wb");
|
||||
if(!fp) return false;
|
||||
fwrite(data, 1, size, fp);
|
||||
fclose(fp);
|
||||
file fp;
|
||||
if(!fp.open(fn, file::mode_write)) return false;
|
||||
fp.write(data, size);
|
||||
fp.close();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#endif //ifdef CART_CPP
|
||||
|
@@ -1,29 +1,70 @@
|
||||
#ifdef CART_CPP
|
||||
|
||||
void Cartridge::read_header() {
|
||||
uint8 *rom = cart.rom;
|
||||
uint index = info.header_index;
|
||||
uint8 mapper = rom[index + MAPPER];
|
||||
uint8 rom_type = rom[index + ROM_TYPE];
|
||||
uint8 company = rom[index + COMPANY];
|
||||
uint8 region = rom[index + REGION] & 0x7f;
|
||||
void Cartridge::read_header(cartinfo_t &info, const uint8_t *data, unsigned size) {
|
||||
info.reset();
|
||||
unsigned index = find_header(data, size);
|
||||
|
||||
//detect presence of BS-X flash cartridge connector (reads extended header information)
|
||||
bool has_bsxflash = false;
|
||||
if(rom[index - 14] == 'Z') {
|
||||
if(rom[index - 11] == 'J') {
|
||||
uint8 n13 = rom[index - 13];
|
||||
if((n13 >= 'A' && n13 <= 'Z') || (n13 >= '0' && n13 <= '9')) {
|
||||
if(company == 0x33 || (rom[index - 10] == 0x00 && rom[index - 4] == 0x00)) {
|
||||
has_bsxflash = true;
|
||||
//detect BS-X flash carts
|
||||
if(data[index + 0x13] == 0x00 || data[index + 0x13] == 0xff) {
|
||||
if(data[index + 0x14] == 0x00) {
|
||||
const uint8_t n15 = data[index + 0x15];
|
||||
if(n15 == 0x00 || n15 == 0x80 || n15 == 0x84 || n15 == 0x9c || n15 == 0xbc || n15 == 0xfc) {
|
||||
if(data[index + 0x1a] == 0x33 || data[index + 0x1a] == 0xff) {
|
||||
info.type = TypeBSX;
|
||||
info.mapper = BSXROM;
|
||||
info.region = NTSC; //BS-X only released in Japan
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(has_bsxflash == true) {
|
||||
info.mapper = index == 0x7fc0 ? BSCLoROM : BSCHiROM;
|
||||
} else if(index == 0x7fc0 && cart.rom_size >= 0x401000) {
|
||||
//detect Sufami Turbo carts
|
||||
if(!memcmp(data, "BANDAI SFC-ADX", 14)) {
|
||||
if(!memcmp(data + 16, "SFC-ADX BACKUP", 14)) {
|
||||
info.type = TypeSufamiTurboBIOS;
|
||||
} else {
|
||||
info.type = TypeSufamiTurbo;
|
||||
}
|
||||
info.mapper = STROM;
|
||||
info.region = NTSC; //Sufami Turbo only released in Japan
|
||||
return;
|
||||
}
|
||||
|
||||
//standard cart
|
||||
uint8 mapper = data[index + Mapper];
|
||||
uint8 rom_type = data[index + RomType];
|
||||
uint8 rom_size = data[index + RomSize];
|
||||
uint8 company = data[index + Company];
|
||||
uint8 region = data[index + CartRegion] & 0x7f;
|
||||
|
||||
//detect presence of BS-X flash cartridge connector (reads extended header information)
|
||||
if(data[index - 14] == 'Z') {
|
||||
if(data[index - 11] == 'J') {
|
||||
uint8 n13 = data[index - 13];
|
||||
if((n13 >= 'A' && n13 <= 'Z') || (n13 >= '0' && n13 <= '9')) {
|
||||
if(company == 0x33 || (data[index - 10] == 0x00 && data[index - 4] == 0x00)) {
|
||||
info.bsxslot = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(info.bsxslot == true) {
|
||||
if(!memcmp(data + index, "Satellaview BS-X ", 21)) {
|
||||
//BS-X base cart
|
||||
info.type = TypeBSXBIOS;
|
||||
info.mapper = BSXROM;
|
||||
} else {
|
||||
info.type = TypeBSC;
|
||||
info.mapper = (index == 0x7fc0 ? BSCLoROM : BSCHiROM);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
info.type = TypeNormal;
|
||||
|
||||
if(index == 0x7fc0 && size >= 0x401000) {
|
||||
info.mapper = ExLoROM;
|
||||
} else if(index == 0x7fc0 && mapper == 0x32) {
|
||||
info.mapper = ExLoROM;
|
||||
@@ -52,8 +93,9 @@ void Cartridge::read_header() {
|
||||
}
|
||||
|
||||
if(mapper == 0x3a && (rom_type == 0xf5 || rom_type == 0xf9)) {
|
||||
//rom_type: 0xf5 = no S-RTC, 0xf9 = S-RTC
|
||||
info.spc7110 = true;
|
||||
info.spc7110rtc = (rom_type == 0xf9);
|
||||
info.mapper = SPC7110ROM;
|
||||
}
|
||||
|
||||
if(mapper == 0x20 && rom_type == 0xf3) {
|
||||
@@ -73,7 +115,7 @@ void Cartridge::read_header() {
|
||||
}
|
||||
|
||||
if(info.dsp1 == true) {
|
||||
if((mapper & 0x2f) == 0x20 && cart.rom_size <= 0x100000) {
|
||||
if((mapper & 0x2f) == 0x20 && size <= 0x100000) {
|
||||
info.dsp1_mapper = DSP1LoROM1MB;
|
||||
} else if((mapper & 0x2f) == 0x20) {
|
||||
info.dsp1_mapper = DSP1LoROM2MB;
|
||||
@@ -98,97 +140,122 @@ void Cartridge::read_header() {
|
||||
info.obc1 = true;
|
||||
}
|
||||
|
||||
if(mapper == 0x30 && rom_type == 0xf6) {
|
||||
//TODO: both ST010 and ST011 share the same mapper + rom_type.
|
||||
//need way to determine which is which.
|
||||
//for now, default to supported ST010.
|
||||
if(mapper == 0x30 && rom_type == 0xf6 && rom_size >= 10) {
|
||||
info.st010 = true;
|
||||
}
|
||||
|
||||
if(mapper == 0x30 && rom_type == 0xf6 && rom_size < 10) {
|
||||
info.st011 = true;
|
||||
}
|
||||
|
||||
if(mapper == 0x30 && rom_type == 0xf5) {
|
||||
info.st018 = true;
|
||||
}
|
||||
|
||||
if(rom[info.header_index + RAM_SIZE] & 7) {
|
||||
info.ram_size = 1024 << (rom[info.header_index + RAM_SIZE] & 7);
|
||||
if(data[index + RamSize] & 7) {
|
||||
info.ram_size = 1024 << (data[index + RamSize] & 7);
|
||||
} else {
|
||||
info.ram_size = 0;
|
||||
}
|
||||
|
||||
//0, 1, 13 = NTSC; 2 - 12 = PAL
|
||||
info.region = (region <= 1 || region >= 13) ? NTSC : PAL;
|
||||
|
||||
memcpy(&info.name, &rom[info.header_index + CART_NAME], 21);
|
||||
info.name[21] = 0;
|
||||
trim(info.name);
|
||||
|
||||
//convert undisplayable characters (half-width katakana, etc) to '?' characters
|
||||
for(int i = 0; i < 21; i++) {
|
||||
if(info.name[i] & 0x80) info.name[i] = '?';
|
||||
}
|
||||
|
||||
//always display something
|
||||
if(!info.name[0]) strcpy(info.name, "(untitled)");
|
||||
}
|
||||
|
||||
void Cartridge::find_header() {
|
||||
int32 score_lo = 0, score_hi = 0, score_ex = 0;
|
||||
uint8_t *rom = cart.rom;
|
||||
|
||||
if(cart.rom_size < 0x010000) {
|
||||
//cart too small to be anything but lorom
|
||||
info.header_index = 0x007fc0;
|
||||
return;
|
||||
}
|
||||
|
||||
if((rom[0x7fc0 + MAPPER] & ~0x10) == 0x20) score_lo++;
|
||||
if((rom[0xffc0 + MAPPER] & ~0x10) == 0x21) score_hi++;
|
||||
|
||||
if(rom[0x7fc0 + ROM_TYPE] < 0x08) score_lo++;
|
||||
if(rom[0xffc0 + ROM_TYPE] < 0x08) score_hi++;
|
||||
|
||||
if(rom[0x7fc0 + ROM_SIZE] < 0x10) score_lo++;
|
||||
if(rom[0xffc0 + ROM_SIZE] < 0x10) score_hi++;
|
||||
|
||||
if(rom[0x7fc0 + RAM_SIZE] < 0x08) score_lo++;
|
||||
if(rom[0xffc0 + RAM_SIZE] < 0x08) score_hi++;
|
||||
|
||||
if(rom[0x7fc0 + REGION] < 14) score_lo++;
|
||||
if(rom[0xffc0 + REGION] < 14) score_hi++;
|
||||
|
||||
if(rom[0x7fc0 + COMPANY] < 3) score_lo++;
|
||||
if(rom[0xffc0 + COMPANY] < 3) score_hi++;
|
||||
|
||||
if(rom[0x7fc0 + RESH] & 0x80) score_lo += 2;
|
||||
if(rom[0xffc0 + RESH] & 0x80) score_hi += 2;
|
||||
|
||||
uint16 cksum, icksum;
|
||||
cksum = rom[0x7fc0 + CKSUM] | (rom[0x7fc0 + CKSUM + 1] << 8);
|
||||
icksum = rom[0x7fc0 + ICKSUM] | (rom[0x7fc0 + ICKSUM + 1] << 8);
|
||||
if((cksum + icksum) == 0xffff && (cksum != 0) && (icksum != 0)) {
|
||||
score_lo += 8;
|
||||
}
|
||||
|
||||
cksum = rom[0xffc0 + CKSUM] | (rom[0xffc0 + CKSUM + 1] << 8);
|
||||
icksum = rom[0xffc0 + ICKSUM] | (rom[0xffc0 + ICKSUM + 1] << 8);
|
||||
if((cksum + icksum) == 0xffff && (cksum != 0) && (icksum != 0)) {
|
||||
score_hi += 8;
|
||||
}
|
||||
|
||||
if(cart.rom_size < 0x401000) {
|
||||
score_ex = 0;
|
||||
} else {
|
||||
if(rom[0x7fc0 + MAPPER] == 0x32) score_lo++;
|
||||
else score_ex += 16;
|
||||
}
|
||||
unsigned Cartridge::find_header(const uint8_t *data, unsigned size) {
|
||||
unsigned score_lo = score_header(data, size, 0x007fc0);
|
||||
unsigned score_hi = score_header(data, size, 0x00ffc0);
|
||||
unsigned score_ex = score_header(data, size, 0x40ffc0);
|
||||
if(score_ex) score_ex += 4; //favor ExHiROM on images > 32mbits
|
||||
|
||||
if(score_lo >= score_hi && score_lo >= score_ex) {
|
||||
info.header_index = 0x007fc0;
|
||||
return 0x007fc0;
|
||||
} else if(score_hi >= score_ex) {
|
||||
info.header_index = 0x00ffc0;
|
||||
return 0x00ffc0;
|
||||
} else {
|
||||
info.header_index = 0x40ffc0;
|
||||
return 0x40ffc0;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned Cartridge::score_header(const uint8_t *data, unsigned size, unsigned addr) {
|
||||
if(size < addr + 64) return 0; //image too small to contain header at this location?
|
||||
int score = 0;
|
||||
|
||||
uint16 resetvector = data[addr + ResetVector] | (data[addr + ResetVector + 1] << 8);
|
||||
uint16 checksum = data[addr + Checksum ] | (data[addr + Checksum + 1] << 8);
|
||||
uint16 complement = data[addr + Complement ] | (data[addr + Complement + 1] << 8);
|
||||
|
||||
uint8 resetop = data[(addr & ~0x7fff) | (resetvector & 0x7fff)]; //first opcode executed upon reset
|
||||
uint8 mapper = data[addr + Mapper] & ~0x10; //mask off irrelevent FastROM-capable bit
|
||||
|
||||
//$00:[000-7fff] contains uninitialized RAM and MMIO.
|
||||
//reset vector must point to ROM at $00:[8000-ffff] to be considered valid.
|
||||
if(resetvector < 0x8000) return 0;
|
||||
|
||||
//some images duplicate the header in multiple locations, and others have completely
|
||||
//invalid header information that cannot be relied upon.
|
||||
//below code will analyze the first opcode executed at the specified reset vector to
|
||||
//determine the probability that this is the correct header.
|
||||
|
||||
//most likely opcodes
|
||||
if(resetop == 0x78 //sei
|
||||
|| resetop == 0x18 //clc (clc; xce)
|
||||
|| resetop == 0x38 //sec (sec; xce)
|
||||
|| resetop == 0x9c //stz $nnnn (stz $4200)
|
||||
|| resetop == 0x4c //jmp $nnnn
|
||||
|| resetop == 0x5c //jml $nnnnnn
|
||||
) score += 8;
|
||||
|
||||
//plausible opcodes
|
||||
if(resetop == 0xc2 //rep #$nn
|
||||
|| resetop == 0xe2 //sep #$nn
|
||||
|| resetop == 0xad //lda $nnnn
|
||||
|| resetop == 0xae //ldx $nnnn
|
||||
|| resetop == 0xac //ldy $nnnn
|
||||
|| resetop == 0xaf //lda $nnnnnn
|
||||
|| resetop == 0xa9 //lda #$nn
|
||||
|| resetop == 0xa2 //ldx #$nn
|
||||
|| resetop == 0xa0 //ldy #$nn
|
||||
|| resetop == 0x20 //jsr $nnnn
|
||||
|| resetop == 0x22 //jsl $nnnnnn
|
||||
) score += 4;
|
||||
|
||||
//implausible opcodes
|
||||
if(resetop == 0x40 //rti
|
||||
|| resetop == 0x60 //rts
|
||||
|| resetop == 0x6b //rtl
|
||||
|| resetop == 0xcd //cmp $nnnn
|
||||
|| resetop == 0xec //cpx $nnnn
|
||||
|| resetop == 0xcc //cpy $nnnn
|
||||
) score -= 4;
|
||||
|
||||
//least likely opcodes
|
||||
if(resetop == 0x00 //brk #$nn
|
||||
|| resetop == 0x02 //cop #$nn
|
||||
|| resetop == 0xdb //stp
|
||||
|| resetop == 0x42 //wdm
|
||||
|| resetop == 0xff //sbc $nnnnnn,x
|
||||
) score -= 8;
|
||||
|
||||
//at times, both the header and reset vector's first opcode will match ...
|
||||
//fallback and rely on info validity in these cases to determine more likely header.
|
||||
|
||||
//a valid checksum is the biggest indicator of a valid header.
|
||||
if((checksum + complement) == 0xffff && (checksum != 0) && (complement != 0)) score += 4;
|
||||
|
||||
if(addr == 0x007fc0 && mapper == 0x20) score += 2; //0x20 is usually LoROM
|
||||
if(addr == 0x00ffc0 && mapper == 0x21) score += 2; //0x21 is usually HiROM
|
||||
if(addr == 0x007fc0 && mapper == 0x22) score += 2; //0x22 is usually ExLoROM
|
||||
if(addr == 0x40ffc0 && mapper == 0x25) score += 2; //0x25 is usually ExHiROM
|
||||
|
||||
if(data[addr + Company] == 0x33) score += 2; //0x33 indicates extended header
|
||||
if(data[addr + RomType] < 0x08) score++;
|
||||
if(data[addr + RomSize] < 0x10) score++;
|
||||
if(data[addr + RamSize] < 0x08) score++;
|
||||
if(data[addr + CartRegion] < 14) score++;
|
||||
|
||||
if(score < 0) score = 0;
|
||||
return score;
|
||||
}
|
||||
|
||||
#endif //ifdef CART_CPP
|
||||
|
50
src/cart/cart_load.cpp
Normal file
50
src/cart/cart_load.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#ifdef CART_CPP
|
||||
|
||||
bool Cartridge::load_image(const char *filename) {
|
||||
if(!filename || !*filename) return false;
|
||||
|
||||
uint8_t *data;
|
||||
unsigned size;
|
||||
if(!load_file(filename, data, size, CompressionAuto)) return false;
|
||||
|
||||
if((size & 0x7fff) != 512) {
|
||||
image.data = data;
|
||||
image.size = size;
|
||||
} else {
|
||||
//remove 512-byte header
|
||||
image.data = new uint8_t[image.size = size - 512];
|
||||
memcpy(image.data, data + 512, image.size);
|
||||
}
|
||||
|
||||
if(load_file(get_patch_filename(filename, "ups"), data, size, CompressionInspect) == true) {
|
||||
apply_patch(data, size, image.data, image.size);
|
||||
delete[] data;
|
||||
info.patched = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Cartridge::inspect_image(cartinfo_t &cartinfo, const char *filename) {
|
||||
cartinfo.reset();
|
||||
if(!load_image(filename)) return false;
|
||||
|
||||
read_header(cartinfo, image.data, image.size);
|
||||
delete[] image.data;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Cartridge::load_ram(const char *filename, uint8_t *&data, unsigned size, uint8_t init) {
|
||||
data = new uint8_t[size];
|
||||
memset(data, init, size);
|
||||
|
||||
uint8_t *savedata;
|
||||
unsigned savesize;
|
||||
if(load_file(filename, savedata, savesize, CompressionNone) == false) return false;
|
||||
|
||||
memcpy(data, savedata, min(size, savesize));
|
||||
delete[] savedata;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif //ifdef CART_CPP
|
@@ -1,54 +1,35 @@
|
||||
#ifdef CART_CPP
|
||||
|
||||
void Cartridge::load_cart_normal(const char *filename) {
|
||||
if(!filename || !*filename) return;
|
||||
|
||||
uint8_t *data = 0;
|
||||
void Cartridge::load_cart_normal(const char *base) {
|
||||
uint8_t *data;
|
||||
unsigned size;
|
||||
if(load_file(filename, data, size, CompressionAuto) == false) return;
|
||||
strcpy(cart.fn, filename);
|
||||
strcpy(cart.fn, base);
|
||||
|
||||
load_begin(CartridgeNormal);
|
||||
load_begin(ModeNormal);
|
||||
if(load_image(base) == false) return;
|
||||
|
||||
//load ROM data, ignore 512-byte header if detected
|
||||
if((size & 0x7fff) != 512) {
|
||||
cart.rom = new uint8_t[cart.rom_size = size];
|
||||
memcpy(cart.rom, data, size);
|
||||
} else {
|
||||
cart.rom = new uint8_t[cart.rom_size = size - 512];
|
||||
memcpy(cart.rom, data + 512, size - 512);
|
||||
}
|
||||
delete[] data;
|
||||
cartinfo_t cartinfo;
|
||||
read_header(cartinfo, cart.rom = image.data, cart.rom_size = image.size);
|
||||
info = cartinfo;
|
||||
|
||||
if(load_file(get_patch_filename(cart.fn, "ups"), data, size, CompressionInspect) == true) {
|
||||
apply_patch(data, size, cart.rom, cart.rom_size);
|
||||
delete[] data;
|
||||
if(cartinfo.ram_size > 0) {
|
||||
load_ram(get_save_filename(base, "srm"), cart.ram, cart.ram_size = cartinfo.ram_size, 0xff);
|
||||
}
|
||||
|
||||
info.crc32 = crc32_calculate(cart.rom, cart.rom_size);
|
||||
|
||||
find_header();
|
||||
read_header();
|
||||
|
||||
if(info.ram_size > 0) {
|
||||
cart.ram = new uint8_t[cart.ram_size = info.ram_size];
|
||||
memset(cart.ram, 0xff, cart.ram_size);
|
||||
|
||||
if(load_file(get_save_filename(cart.fn, "srm"), data, size, CompressionNone) == true) {
|
||||
memcpy(cart.ram, data, min(size, cart.ram_size));
|
||||
delete[] data;
|
||||
}
|
||||
if(cartinfo.srtc || cartinfo.spc7110rtc) {
|
||||
load_ram(get_save_filename(base, "rtc"), cart.rtc, cart.rtc_size = 20, 0x00);
|
||||
}
|
||||
|
||||
load_end();
|
||||
|
||||
//set base filename
|
||||
strcpy(info.filename, cart.fn);
|
||||
strcpy(info.filename, base);
|
||||
get_base_filename(info.filename);
|
||||
}
|
||||
|
||||
void Cartridge::unload_cart_normal() {
|
||||
if(cart.ram) save_file(get_save_filename(cart.fn, "srm"), cart.ram, cart.ram_size);
|
||||
if(cart.rtc) save_file(get_save_filename(cart.fn, "rtc"), cart.rtc, cart.rtc_size);
|
||||
}
|
||||
|
||||
#endif //ifdef CART_CPP
|
||||
|
@@ -1,86 +1,52 @@
|
||||
#ifdef CART_CPP
|
||||
|
||||
void Cartridge::load_cart_st(const char *base, const char *slotA, const char *slotB) {
|
||||
if(!base || !*base) return;
|
||||
|
||||
strcpy(cart.fn, base);
|
||||
strcpy(stA.fn, slotA ? slotA : "");
|
||||
strcpy(stB.fn, slotB ? slotB : "");
|
||||
|
||||
load_begin(CartridgeSufamiTurbo);
|
||||
info.st = true;
|
||||
info.mapper = STROM;
|
||||
info.region = NTSC;
|
||||
|
||||
uint8_t *data = 0;
|
||||
uint8_t *data;
|
||||
unsigned size;
|
||||
if(load_file(cart.fn, data, size, CompressionAuto) == true) {
|
||||
cart.rom = new(zeromemory) uint8_t[cart.rom_size = 0x040000];
|
||||
memcpy(cart.rom, data, min(size, cart.rom_size));
|
||||
delete[] data;
|
||||
if(load_file(get_patch_filename(cart.fn, "ups"), data, size, CompressionInspect) == true) {
|
||||
apply_patch(data, size, cart.rom, cart.rom_size);
|
||||
delete[] data;
|
||||
}
|
||||
strcpy(cart.fn, base);
|
||||
strcpy(stA.fn, slotA);
|
||||
strcpy(stB.fn, slotB);
|
||||
|
||||
load_begin(ModeSufamiTurbo);
|
||||
if(load_image(base) == false) return;
|
||||
|
||||
cartinfo_t cartinfo;
|
||||
read_header(cartinfo, cart.rom = image.data, cart.rom_size = image.size);
|
||||
info = cartinfo;
|
||||
|
||||
if(load_image(slotA)) {
|
||||
stA.rom = new(zeromemory) uint8_t[stA.rom_size = 0x100000];
|
||||
memcpy(stA.rom, image.data, min(image.size, stA.rom_size));
|
||||
delete[] image.data;
|
||||
|
||||
load_ram(get_save_filename(slotA, "srm"), stA.ram, stA.ram_size = 0x020000, 0xff);
|
||||
}
|
||||
|
||||
if(*stA.fn) {
|
||||
if(load_file(stA.fn, data, size, CompressionAuto) == true) {
|
||||
stA.rom = new(zeromemory) uint8_t[stA.rom_size = 0x100000];
|
||||
memcpy(stA.rom, data, min(size, stA.rom_size));
|
||||
delete[] data;
|
||||
if(load_file(get_patch_filename(stA.fn, "ups"), data, size, CompressionInspect) == true) {
|
||||
apply_patch(data, size, stA.rom, stA.rom_size);
|
||||
delete[] data;
|
||||
}
|
||||
if(load_image(slotB)) {
|
||||
stB.rom = new(zeromemory) uint8_t[stB.rom_size = 0x100000];
|
||||
memcpy(stB.rom, image.data, min(image.size, stB.rom_size));
|
||||
delete[] image.data;
|
||||
|
||||
stA.ram = new uint8_t[stA.ram_size = 0x020000];
|
||||
memset(stA.ram, 0xff, stA.ram_size);
|
||||
|
||||
if(load_file(get_save_filename(stA.fn, "srm"), data, size, CompressionNone) == true) {
|
||||
memcpy(stA.ram, data, min(size, 0x020000U));
|
||||
delete[] data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(*stB.fn) {
|
||||
if(load_file(stB.fn, data, size, CompressionAuto) == true) {
|
||||
stB.rom = new(zeromemory) uint8_t[stB.rom_size = 0x100000];
|
||||
memcpy(stB.rom, data, min(size, stB.rom_size));
|
||||
delete[] data;
|
||||
if(load_file(get_patch_filename(stB.fn, "ups"), data, size, CompressionInspect) == true) {
|
||||
apply_patch(data, size, stB.rom, stB.rom_size);
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
stB.ram = new uint8_t[stB.ram_size = 0x020000];
|
||||
memset(stB.ram, 0xff, stB.ram_size);
|
||||
|
||||
if(load_file(get_save_filename(stB.fn, "srm"), data, size, CompressionNone) == true) {
|
||||
memcpy(stB.ram, data, min(size, 0x020000U));
|
||||
delete[] data;
|
||||
}
|
||||
}
|
||||
load_ram(get_save_filename(slotB, "srm"), stB.ram, stB.ram_size = 0x020000, 0xff);
|
||||
}
|
||||
|
||||
load_end();
|
||||
|
||||
//set base filename
|
||||
if(!*stA.fn && !*stB.fn) {
|
||||
if(!*slotA && !*slotB) {
|
||||
strcpy(info.filename, cart.fn);
|
||||
get_base_filename(info.filename);
|
||||
} else if(*stA.fn && !*stB.fn) {
|
||||
strcpy(info.filename, stA.fn);
|
||||
} else if(*slotA && !*slotB) {
|
||||
strcpy(info.filename, slotA);
|
||||
get_base_filename(info.filename);
|
||||
} else if(!*stA.fn && *stB.fn) {
|
||||
strcpy(info.filename, stB.fn);
|
||||
} else if(!*slotA && *slotB) {
|
||||
strcpy(info.filename, slotB);
|
||||
get_base_filename(info.filename);
|
||||
} else {
|
||||
char filenameA[PATH_MAX], filenameB[PATH_MAX];
|
||||
strcpy(filenameA, stA.fn);
|
||||
strcpy(filenameA, slotA);
|
||||
get_base_filename(filenameA);
|
||||
strcpy(filenameB, stB.fn);
|
||||
strcpy(filenameB, slotB);
|
||||
get_base_filename(filenameB);
|
||||
strcpy(info.filename, filenameA);
|
||||
strcat(info.filename, " + ");
|
||||
|
@@ -1,30 +1,53 @@
|
||||
#include "../base.h"
|
||||
#include "../reader/filereader.h"
|
||||
#include <../base.hpp>
|
||||
|
||||
Cheat cheat;
|
||||
|
||||
Cheat::cheat_t& Cheat::cheat_t::operator=(const Cheat::cheat_t& source) {
|
||||
enabled = source.enabled;
|
||||
addr = source.addr;
|
||||
data = source.data;
|
||||
code = source.code;
|
||||
desc = source.desc;
|
||||
return *this;
|
||||
}
|
||||
|
||||
//used to sort cheat code list by description
|
||||
bool Cheat::cheat_t::operator<(const Cheat::cheat_t& source) {
|
||||
return strcmp(desc, source.desc) < 0;
|
||||
}
|
||||
|
||||
/*****
|
||||
* string <> binary code translation routines
|
||||
* decode() "7e1234:56" -> 0x7e123456
|
||||
* encode() 0x7e123456 -> "7e1234:56"
|
||||
*****/
|
||||
|
||||
bool Cheat::decode(char *str, uint32 &addr, uint8 &data, uint8 &type) {
|
||||
string t, part;
|
||||
strcpy(t, str);
|
||||
strlower(t());
|
||||
if(strlen(t) == 8 || (strlen(t) == 9 && t()[6] == ':')) {
|
||||
bool Cheat::decode(const char *str, unsigned &addr, uint8 &data, type_t &type) {
|
||||
string t = str;
|
||||
strlower(t);
|
||||
|
||||
#define ischr(n) ((n >= '0' && n <= '9') || (n >= 'a' && n <= 'f'))
|
||||
|
||||
if(strlen(t) == 8 || (strlen(t) == 9 && t[6] == ':')) {
|
||||
//strip ':'
|
||||
if(strlen(t) == 9 && t[6] == ':') t = string() << substr(t, 0, 6) << substr(t, 7);
|
||||
//validate input
|
||||
for(unsigned i = 0; i < 8; i++) if(!ischr(t[i])) return false;
|
||||
|
||||
type = ProActionReplay;
|
||||
replace(t, ":", "");
|
||||
uint32 r = strhex((const char*)t);
|
||||
unsigned r = strhex((const char*)t);
|
||||
addr = r >> 8;
|
||||
data = r & 0xff;
|
||||
return true;
|
||||
} else if(strlen(t) == 9 && t()[4] == '-') {
|
||||
} else if(strlen(t) == 9 && t[4] == '-') {
|
||||
//strip '-'
|
||||
t = string() << substr(t, 0, 4) << substr(t, 5);
|
||||
//validate input
|
||||
for(unsigned i = 0; i < 8; i++) if(!ischr(t[i])) return false;
|
||||
|
||||
type = GameGenie;
|
||||
replace(t, "-", "");
|
||||
strtr(t, "df4709156bc8a23e", "0123456789abcdef");
|
||||
uint32 r = strhex((const char*)t);
|
||||
unsigned r = strhex((const char*)t);
|
||||
//8421 8421 8421 8421 8421 8421
|
||||
//abcd efgh ijkl mnop qrst uvwx
|
||||
//ijkl qrst opab cduv wxef ghmn
|
||||
@@ -42,16 +65,20 @@ bool Cheat::decode(char *str, uint32 &addr, uint8 &data, uint8 &type) {
|
||||
(!!(r & 0x000080) << 1) | (!!(r & 0x000040) << 0);
|
||||
data = r >> 24;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Cheat::encode(char *str, uint32 addr, uint8 data, uint8 type) {
|
||||
bool Cheat::encode(string &str, unsigned addr, uint8 data, type_t type) {
|
||||
char t[16];
|
||||
|
||||
if(type == ProActionReplay) {
|
||||
sprintf(str, "%0.6x:%0.2x", addr, data);
|
||||
sprintf(t, "%0.6x:%0.2x", addr, data);
|
||||
str = t;
|
||||
return true;
|
||||
} else if(type == GameGenie) {
|
||||
uint32 r = addr;
|
||||
unsigned r = addr;
|
||||
addr = (!!(r & 0x008000) << 23) | (!!(r & 0x004000) << 22) |
|
||||
(!!(r & 0x002000) << 21) | (!!(r & 0x001000) << 20) |
|
||||
(!!(r & 0x000080) << 19) | (!!(r & 0x000040) << 18) |
|
||||
@@ -64,11 +91,13 @@ bool Cheat::encode(char *str, uint32 addr, uint8 data, uint8 type) {
|
||||
(!!(r & 0x080000) << 5) | (!!(r & 0x040000) << 4) |
|
||||
(!!(r & 0x020000) << 3) | (!!(r & 0x010000) << 2) |
|
||||
(!!(r & 0x000800) << 1) | (!!(r & 0x000400) << 0);
|
||||
sprintf(str, "%0.2x%0.2x-%0.4x", data, addr >> 16, addr & 0xffff);
|
||||
strtr(str, "0123456789abcdef", "df4709156bc8a23e");
|
||||
sprintf(t, "%0.2x%0.2x-%0.4x", data, addr >> 16, addr & 0xffff);
|
||||
strtr(t, "0123456789abcdef", "df4709156bc8a23e");
|
||||
str = t;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*****
|
||||
@@ -78,21 +107,21 @@ bool Cheat::encode(char *str, uint32 addr, uint8 data, uint8 type) {
|
||||
* clear() disable specified address, mirror accordingly
|
||||
*****/
|
||||
|
||||
uint Cheat::mirror_address(uint addr) {
|
||||
unsigned Cheat::mirror_address(unsigned addr) const {
|
||||
if((addr & 0x40e000) != 0x0000) return addr;
|
||||
//8k WRAM mirror
|
||||
//$[00-3f|80-bf]:[0000-1fff] -> $7e:[0000-1fff]
|
||||
return (0x7e0000 + (addr & 0x1fff));
|
||||
}
|
||||
|
||||
void Cheat::set(uint32 addr) {
|
||||
void Cheat::set(unsigned addr) {
|
||||
addr = mirror_address(addr);
|
||||
|
||||
mask[addr >> 3] |= 1 << (addr & 7);
|
||||
if((addr & 0xffe000) == 0x7e0000) {
|
||||
//mirror $7e:[0000-1fff] to $[00-3f|80-bf]:[0000-1fff]
|
||||
uint mirror;
|
||||
for(int x = 0; x <= 0x3f; x++) {
|
||||
unsigned mirror;
|
||||
for(unsigned x = 0; x <= 0x3f; x++) {
|
||||
mirror = ((0x00 + x) << 16) + (addr & 0x1fff);
|
||||
mask[mirror >> 3] |= 1 << (mirror & 7);
|
||||
mirror = ((0x80 + x) << 16) + (addr & 0x1fff);
|
||||
@@ -101,20 +130,20 @@ void Cheat::set(uint32 addr) {
|
||||
}
|
||||
}
|
||||
|
||||
void Cheat::clear(uint32 addr) {
|
||||
void Cheat::clear(unsigned addr) {
|
||||
addr = mirror_address(addr);
|
||||
|
||||
//is there more than one cheat code using the same address
|
||||
//(and likely a different override value) that is enabled?
|
||||
//if so, do not clear code lookup table entry for this address.
|
||||
//if there is more than one cheat code using the same address,
|
||||
//(eg with a different override value) then do not clear code
|
||||
//lookup table entry.
|
||||
uint8 r;
|
||||
if(read(addr, r) == true)return;
|
||||
if(read(addr, r) == true) return;
|
||||
|
||||
mask[addr >> 3] &= ~(1 << (addr & 7));
|
||||
if((addr & 0xffe000) == 0x7e0000) {
|
||||
//mirror $7e:[0000-1fff] to $[00-3f|80-bf]:[0000-1fff]
|
||||
uint mirror;
|
||||
for(int x = 0; x <= 0x3f; x++) {
|
||||
unsigned mirror;
|
||||
for(unsigned x = 0; x <= 0x3f; x++) {
|
||||
mirror = ((0x00 + x) << 16) + (addr & 0x1fff);
|
||||
mask[mirror >> 3] &= ~(1 << (mirror & 7));
|
||||
mirror = ((0x80 + x) << 16) + (addr & 0x1fff);
|
||||
@@ -130,12 +159,12 @@ void Cheat::clear(uint32 addr) {
|
||||
* when true, cheat code substitution value is stored in data.
|
||||
*****/
|
||||
|
||||
bool Cheat::read(uint32 addr, uint8 &data) {
|
||||
bool Cheat::read(unsigned addr, uint8 &data) const {
|
||||
addr = mirror_address(addr);
|
||||
for(int i = 0; i < cheat_count; i++) {
|
||||
for(unsigned i = 0; i < code.size(); i++) {
|
||||
if(enabled(i) == false) continue;
|
||||
if(addr == mirror_address(index[i].addr)) {
|
||||
data = index[i].data;
|
||||
if(addr == mirror_address(code[i].addr)) {
|
||||
data = code[i].data;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -147,99 +176,77 @@ bool Cheat::read(uint32 addr, uint8 &data) {
|
||||
* update_cheat_status() will scan to see if any codes are
|
||||
* enabled. if any are, make sure the cheat system is on.
|
||||
* otherwise, turn cheat system off to speed up emulation.
|
||||
*****/
|
||||
*****/
|
||||
|
||||
void Cheat::update_cheat_status() {
|
||||
for(unsigned i = 0; i < cheat_count; i++) {
|
||||
if(index[i].enabled) {
|
||||
cheat_enabled = true;
|
||||
for(unsigned i = 0; i < code.size(); i++) {
|
||||
if(code[i].enabled) {
|
||||
cheat_system_enabled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
cheat_enabled = false;
|
||||
cheat_system_enabled = false;
|
||||
}
|
||||
|
||||
/*****
|
||||
* cheat list manipulation routines
|
||||
*****/
|
||||
|
||||
bool Cheat::add(bool enable, char *code, char *desc) {
|
||||
if(cheat_count >= CheatLimit) return false;
|
||||
bool Cheat::add(bool enable, const char *code_, const char *desc_) {
|
||||
unsigned addr;
|
||||
uint8 data;
|
||||
type_t type;
|
||||
if(decode(code_, addr, data, type) == false) return false;
|
||||
|
||||
uint32 addr, len;
|
||||
uint8 data, type;
|
||||
if(decode(code, addr, data, type) == false) return false;
|
||||
|
||||
index[cheat_count].enabled = enable;
|
||||
index[cheat_count].addr = addr;
|
||||
index[cheat_count].data = data;
|
||||
len = strlen(code);
|
||||
len = len > 16 ? 16 : len;
|
||||
memcpy(index[cheat_count].code, code, len);
|
||||
index[cheat_count].code[len] = 0;
|
||||
len = strlen(desc);
|
||||
len = len > 128 ? 128 : len;
|
||||
memcpy(index[cheat_count].desc, desc, len);
|
||||
index[cheat_count].desc[len] = 0;
|
||||
cheat_count++;
|
||||
unsigned n = code.size();
|
||||
code[n].enabled = enable;
|
||||
code[n].addr = addr;
|
||||
code[n].data = data;
|
||||
code[n].code = code_;
|
||||
code[n].desc = desc_;
|
||||
(enable) ? set(addr) : clear(addr);
|
||||
|
||||
update_cheat_status();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Cheat::edit(uint32 n, bool enable, char *code, char *desc) {
|
||||
if(n >= cheat_count) return false;
|
||||
|
||||
uint32 addr, len;
|
||||
uint8 data, type;
|
||||
if(decode(code, addr, data, type) == false) return false;
|
||||
bool Cheat::edit(unsigned n, bool enable, const char *code_, const char *desc_) {
|
||||
unsigned addr;
|
||||
uint8 data;
|
||||
type_t type;
|
||||
if(decode(code_, addr, data, type) == false) return false;
|
||||
|
||||
//disable current code and clear from code lookup table
|
||||
index[n].enabled = false;
|
||||
clear(index[n].addr);
|
||||
code[n].enabled = false;
|
||||
clear(code[n].addr);
|
||||
|
||||
//update code and enable in code lookup table
|
||||
index[n].enabled = enable;
|
||||
index[n].addr = addr;
|
||||
index[n].data = data;
|
||||
len = strlen(code);
|
||||
len = len > 16 ? 16 : len;
|
||||
memcpy(index[n].code, code, len);
|
||||
index[n].code[len] = 0;
|
||||
len = strlen(desc);
|
||||
len = len > 128 ? 128 : len;
|
||||
memcpy(index[n].desc, desc, len);
|
||||
index[n].desc[len] = 0;
|
||||
code[n].enabled = enable;
|
||||
code[n].addr = addr;
|
||||
code[n].data = data;
|
||||
code[n].code = code_;
|
||||
code[n].desc = desc_;
|
||||
set(addr);
|
||||
|
||||
update_cheat_status();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Cheat::remove(uint32 n) {
|
||||
if(n >= cheat_count) return false;
|
||||
bool Cheat::remove(unsigned n) {
|
||||
unsigned size = code.size();
|
||||
if(n >= size) return false; //also verifies size cannot be < 1
|
||||
|
||||
for(unsigned i = n; i < cheat_count; i++) {
|
||||
index[i].enabled = index[i + 1].enabled;
|
||||
index[i].addr = index[i + 1].addr;
|
||||
index[i].data = index[i + 1].data;
|
||||
strcpy(index[i].desc, index[i + 1].desc);
|
||||
}
|
||||
|
||||
cheat_count--;
|
||||
for(unsigned i = n; i < size - 1; i++) code[i] = code[i + 1];
|
||||
code.resize(size - 1);
|
||||
|
||||
update_cheat_status();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Cheat::get(uint32 n, bool &enable, uint32 &addr, uint8 &data, char *code, char *desc) {
|
||||
if(n >= cheat_count) return false;
|
||||
enable = index[n].enabled;
|
||||
addr = index[n].addr;
|
||||
data = index[n].data;
|
||||
strcpy(code, index[n].code);
|
||||
strcpy(desc, index[n].desc);
|
||||
bool Cheat::get(unsigned n, cheat_t &cheat) const {
|
||||
if(n >= code.size()) return false;
|
||||
|
||||
cheat = code[n];
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -247,22 +254,23 @@ bool Cheat::get(uint32 n, bool &enable, uint32 &addr, uint8 &data, char *code, c
|
||||
* code status modifier routines
|
||||
*****/
|
||||
|
||||
bool Cheat::enabled(uint32 n) {
|
||||
if(n >= cheat_count) return false;
|
||||
return index[n].enabled;
|
||||
bool Cheat::enabled(unsigned n) const {
|
||||
return (n < code.size()) ? code[n].enabled : false;
|
||||
}
|
||||
|
||||
void Cheat::enable(uint32 n) {
|
||||
if(n >= cheat_count) return;
|
||||
index[n].enabled = true;
|
||||
set(index[n].addr);
|
||||
void Cheat::enable(unsigned n) {
|
||||
if(n >= code.size()) return;
|
||||
|
||||
code[n].enabled = true;
|
||||
set(code[n].addr);
|
||||
update_cheat_status();
|
||||
}
|
||||
|
||||
void Cheat::disable(uint32 n) {
|
||||
if(n >= cheat_count) return;
|
||||
index[n].enabled = false;
|
||||
clear(index[n].addr);
|
||||
void Cheat::disable(unsigned n) {
|
||||
if(n >= code.size()) return;
|
||||
|
||||
code[n].enabled = false;
|
||||
clear(code[n].addr);
|
||||
update_cheat_status();
|
||||
}
|
||||
|
||||
@@ -288,40 +296,39 @@ bool Cheat::load(const char *fn) {
|
||||
split(part, ",", line[i]);
|
||||
if(::count(part) != 3) continue;
|
||||
trim(part[2], "\"");
|
||||
add(part[1] == "enabled", part[0](), part[2]());
|
||||
add(part[1] == "enabled", part[0], part[2]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Cheat::save(const char *fn) {
|
||||
FILE *fp = fopen(fn, "wb");
|
||||
if(!fp) return false;
|
||||
for(unsigned i = 0; i < cheat_count; i++) {
|
||||
fprintf(fp, "%9s = %8s, \"%s\"\r\n",
|
||||
index[i].code,
|
||||
index[i].enabled ? "enabled" : "disabled",
|
||||
index[i].desc);
|
||||
bool Cheat::save(const char *fn) const {
|
||||
file fp;
|
||||
if(!fp.open(fn, file::mode_write)) return false;
|
||||
for(unsigned i = 0; i < code.size(); i++) {
|
||||
fp.print(string()
|
||||
<< code[i].code << " = "
|
||||
<< (code[i].enabled ? "enabled" : "disabled") << ", "
|
||||
<< "\"" << code[i].desc << "\""
|
||||
<< "\r\n");
|
||||
}
|
||||
fclose(fp);
|
||||
fp.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
/*****
|
||||
* initialization routines
|
||||
*****/
|
||||
void Cheat::sort() {
|
||||
if(code.size() <= 1) return; //nothing to sort?
|
||||
cheat_t *buffer = new cheat_t[code.size()];
|
||||
for(unsigned i = 0; i < code.size(); i++) buffer[i] = code[i];
|
||||
nall::sort(buffer, code.size());
|
||||
for(unsigned i = 0; i < code.size(); i++) code[i] = buffer[i];
|
||||
delete[] buffer;
|
||||
}
|
||||
|
||||
void Cheat::clear() {
|
||||
cheat_enabled = false;
|
||||
cheat_count = 0;
|
||||
cheat_system_enabled = false;
|
||||
memset(mask, 0, 0x200000);
|
||||
for(unsigned i = 0; i <= CheatLimit; i++) {
|
||||
index[i].enabled = false;
|
||||
index[i].addr = 0x000000;
|
||||
index[i].data = 0x00;
|
||||
strcpy(index[i].code, "");
|
||||
strcpy(index[i].desc, "");
|
||||
}
|
||||
code.reset();
|
||||
}
|
||||
|
||||
Cheat::Cheat() {
|
||||
|
@@ -1,51 +0,0 @@
|
||||
class Cheat {
|
||||
public:
|
||||
enum { CheatLimit = 1024 };
|
||||
|
||||
enum Type {
|
||||
ProActionReplay,
|
||||
GameGenie,
|
||||
};
|
||||
|
||||
struct CheatIndex {
|
||||
bool enabled;
|
||||
uint32 addr;
|
||||
uint8 data;
|
||||
char code[ 16 + 1];
|
||||
char desc[128 + 1];
|
||||
} index[CheatLimit + 1];
|
||||
|
||||
bool cheat_enabled;
|
||||
uint32 cheat_count;
|
||||
uint8 mask[0x200000];
|
||||
|
||||
inline bool enabled() { return cheat_enabled; }
|
||||
inline uint count() { return cheat_count; }
|
||||
inline bool exists(uint32 addr) { return bool(mask[addr >> 3] & 1 << (addr & 7)); }
|
||||
|
||||
bool decode(char *str, uint32 &addr, uint8 &data, uint8 &type);
|
||||
bool encode(char *str, uint32 addr, uint8 data, uint8 type);
|
||||
|
||||
bool read(uint32 addr, uint8 &data);
|
||||
|
||||
void update_cheat_status();
|
||||
bool add(bool enable, char *code, char *desc);
|
||||
bool edit(uint32 n, bool enable, char *code, char *desc);
|
||||
bool get(uint32 n, bool &enable, uint32 &addr, uint8 &data, char *code, char *desc);
|
||||
bool remove(uint32 n);
|
||||
bool enabled(uint32 n);
|
||||
void enable(uint32 n);
|
||||
void disable(uint32 n);
|
||||
bool load(const char *fn);
|
||||
bool save(const char *fn);
|
||||
void clear();
|
||||
|
||||
Cheat();
|
||||
|
||||
private:
|
||||
uint mirror_address(uint addr);
|
||||
void set(uint32 addr);
|
||||
void clear(uint32 addr);
|
||||
};
|
||||
|
||||
extern Cheat cheat;
|
55
src/cheat/cheat.hpp
Normal file
55
src/cheat/cheat.hpp
Normal file
@@ -0,0 +1,55 @@
|
||||
class Cheat {
|
||||
public:
|
||||
enum type_t {
|
||||
ProActionReplay,
|
||||
GameGenie,
|
||||
};
|
||||
|
||||
struct cheat_t {
|
||||
bool enabled;
|
||||
unsigned addr;
|
||||
uint8 data;
|
||||
string code;
|
||||
string desc;
|
||||
|
||||
cheat_t& operator=(const cheat_t&);
|
||||
bool operator<(const cheat_t&);
|
||||
};
|
||||
|
||||
static bool decode(const char *str, unsigned &addr, uint8 &data, type_t &type);
|
||||
static bool encode(string &str, unsigned addr, uint8 data, type_t type);
|
||||
|
||||
inline bool enabled() const { return cheat_system_enabled; }
|
||||
inline unsigned count() const { return code.size(); }
|
||||
inline bool exists(unsigned addr) const { return bool(mask[addr >> 3] & 1 << (addr & 7)); }
|
||||
|
||||
bool read(unsigned addr, uint8 &data) const;
|
||||
|
||||
bool add(bool enable, const char *code, const char *desc);
|
||||
bool edit(unsigned n, bool enable, const char *code, const char *desc);
|
||||
bool get(unsigned n, cheat_t &cheat) const;
|
||||
bool remove(unsigned n);
|
||||
bool enabled(unsigned n) const;
|
||||
void enable(unsigned n);
|
||||
void disable(unsigned n);
|
||||
|
||||
bool load(const char *fn);
|
||||
bool save(const char *fn) const;
|
||||
|
||||
void sort();
|
||||
void clear();
|
||||
|
||||
Cheat();
|
||||
|
||||
private:
|
||||
bool cheat_system_enabled;
|
||||
uint8 mask[0x200000];
|
||||
vector<cheat_t> code;
|
||||
|
||||
void update_cheat_status();
|
||||
unsigned mirror_address(unsigned addr) const;
|
||||
void set(unsigned addr);
|
||||
void clear(unsigned addr);
|
||||
};
|
||||
|
||||
extern Cheat cheat;
|
@@ -1,4 +1,4 @@
|
||||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define BSX_CPP
|
||||
|
||||
#include "bsx_base.cpp"
|
||||
|
@@ -1,10 +0,0 @@
|
||||
#include "bsx/bsx.h"
|
||||
#include "srtc/srtc.h"
|
||||
#include "sdd1/sdd1.h"
|
||||
#include "cx4/cx4.h"
|
||||
#include "dsp1/dsp1.h"
|
||||
#include "dsp2/dsp2.h"
|
||||
#include "dsp3/dsp3.h"
|
||||
#include "dsp4/dsp4.h"
|
||||
#include "obc1/obc1.h"
|
||||
#include "st010/st010.h"
|
11
src/chip/chip.hpp
Normal file
11
src/chip/chip.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#include "bsx/bsx.hpp"
|
||||
#include "srtc/srtc.hpp"
|
||||
#include "sdd1/sdd1.hpp"
|
||||
#include "spc7110/spc7110.hpp"
|
||||
#include "cx4/cx4.hpp"
|
||||
#include "dsp1/dsp1.hpp"
|
||||
#include "dsp2/dsp2.hpp"
|
||||
#include "dsp3/dsp3.hpp"
|
||||
#include "dsp4/dsp4.hpp"
|
||||
#include "obc1/obc1.hpp"
|
||||
#include "st010/st010.hpp"
|
@@ -5,7 +5,7 @@
|
||||
Portions (c) anomie, Overload, zsKnight, Nach, byuu
|
||||
*/
|
||||
|
||||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define CX4_CPP
|
||||
|
||||
#include "cx4data.cpp"
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define DSP1_CPP
|
||||
|
||||
#include "dsp1emu.cpp"
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#include "dsp1emu.h"
|
||||
#include "dsp1emu.hpp"
|
||||
|
||||
class DSP1 : public Memory {
|
||||
private:
|
@@ -1,4 +1,4 @@
|
||||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define DSP2_CPP
|
||||
|
||||
#include "dsp2_op.cpp"
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define DSP3_CPP
|
||||
|
||||
namespace DSP3i {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define DSP4_CPP
|
||||
|
||||
namespace DSP4i {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
|
||||
void OBC1::init() {}
|
||||
void OBC1::enable() {}
|
||||
@@ -8,80 +8,61 @@ void OBC1::power() {
|
||||
}
|
||||
|
||||
void OBC1::reset() {
|
||||
for(uint i = 0x0000; i <= 0x1fff; i++) ram_write(i, 0xff);
|
||||
for(unsigned i = 0x0000; i <= 0x1fff; i++) ram_write(i, 0xff);
|
||||
|
||||
status.baseptr = (ram_read(0x1ff5) & 1) ? 0x1800 : 0x1c00;
|
||||
status.address = (ram_read(0x1ff6) & 0x7f);
|
||||
status.shift = (ram_read(0x1ff6) & 3) << 1;
|
||||
}
|
||||
|
||||
uint8 OBC1::read(uint addr) {
|
||||
uint8 OBC1::read(unsigned addr) {
|
||||
addr &= 0x1fff;
|
||||
if((addr & 0x1ff8) != 0x1ff0) return ram_read(addr);
|
||||
|
||||
switch(addr) {
|
||||
case 0x1ff0:
|
||||
return ram_read(status.baseptr + (status.address << 2) + 0);
|
||||
case 0x1ff1:
|
||||
return ram_read(status.baseptr + (status.address << 2) + 1);
|
||||
case 0x1ff2:
|
||||
return ram_read(status.baseptr + (status.address << 2) + 2);
|
||||
case 0x1ff3:
|
||||
return ram_read(status.baseptr + (status.address << 2) + 3);
|
||||
case 0x1ff4:
|
||||
return ram_read(status.baseptr + (status.address >> 2) + 0x200);
|
||||
case 0x1ff5:
|
||||
case 0x1ff6:
|
||||
case 0x1ff7:
|
||||
return ram_read(addr);
|
||||
switch(addr) { default: //never used, avoids compiler warning
|
||||
case 0x1ff0: return ram_read(status.baseptr + (status.address << 2) + 0);
|
||||
case 0x1ff1: return ram_read(status.baseptr + (status.address << 2) + 1);
|
||||
case 0x1ff2: return ram_read(status.baseptr + (status.address << 2) + 2);
|
||||
case 0x1ff3: return ram_read(status.baseptr + (status.address << 2) + 3);
|
||||
case 0x1ff4: return ram_read(status.baseptr + (status.address >> 2) + 0x200);
|
||||
case 0x1ff5: case 0x1ff6: case 0x1ff7: return ram_read(addr);
|
||||
}
|
||||
|
||||
return 0x00; //never used, avoids compiler warning
|
||||
}
|
||||
|
||||
void OBC1::write(uint addr, uint8 data) {
|
||||
void OBC1::write(unsigned addr, uint8 data) {
|
||||
addr &= 0x1fff;
|
||||
if((addr & 0x1ff8) != 0x1ff0) return ram_write(addr, data);
|
||||
|
||||
switch(addr) {
|
||||
case 0x1ff0:
|
||||
ram_write(status.baseptr + (status.address << 2) + 0, data);
|
||||
break;
|
||||
case 0x1ff1:
|
||||
ram_write(status.baseptr + (status.address << 2) + 1, data);
|
||||
break;
|
||||
case 0x1ff2:
|
||||
ram_write(status.baseptr + (status.address << 2) + 2, data);
|
||||
break;
|
||||
case 0x1ff3:
|
||||
ram_write(status.baseptr + (status.address << 2) + 3, data);
|
||||
break;
|
||||
case 0x1ff4: {
|
||||
uint8 temp;
|
||||
temp = ram_read(status.baseptr + (status.address >> 2) + 0x200);
|
||||
temp = (temp & ~(3 << status.shift)) | ((data & 3) << status.shift);
|
||||
ram_write(status.baseptr + (status.address >> 2) + 0x200, temp);
|
||||
} break;
|
||||
case 0x1ff5:
|
||||
status.baseptr = (data & 1) ? 0x1800 : 0x1c00;
|
||||
ram_write(addr, data);
|
||||
break;
|
||||
case 0x1ff6:
|
||||
status.address = (data & 0x7f);
|
||||
status.shift = (data & 3) << 1;
|
||||
ram_write(addr, data);
|
||||
break;
|
||||
case 0x1ff7:
|
||||
ram_write(addr, data);
|
||||
break;
|
||||
case 0x1ff0: ram_write(status.baseptr + (status.address << 2) + 0, data); break;
|
||||
case 0x1ff1: ram_write(status.baseptr + (status.address << 2) + 1, data); break;
|
||||
case 0x1ff2: ram_write(status.baseptr + (status.address << 2) + 2, data); break;
|
||||
case 0x1ff3: ram_write(status.baseptr + (status.address << 2) + 3, data); break;
|
||||
case 0x1ff4: {
|
||||
uint8 temp = ram_read(status.baseptr + (status.address >> 2) + 0x200);
|
||||
temp = (temp & ~(3 << status.shift)) | ((data & 3) << status.shift);
|
||||
ram_write(status.baseptr + (status.address >> 2) + 0x200, temp);
|
||||
} break;
|
||||
case 0x1ff5: {
|
||||
status.baseptr = (data & 1) ? 0x1800 : 0x1c00;
|
||||
ram_write(addr, data);
|
||||
} break;
|
||||
case 0x1ff6: {
|
||||
status.address = (data & 0x7f);
|
||||
status.shift = (data & 3) << 1;
|
||||
ram_write(addr, data);
|
||||
} break;
|
||||
case 0x1ff7: {
|
||||
ram_write(addr, data);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8 OBC1::ram_read(uint addr) {
|
||||
uint8 OBC1::ram_read(unsigned addr) {
|
||||
return memory::cartram.read(addr & 0x1fff);
|
||||
}
|
||||
|
||||
void OBC1::ram_write(uint addr, uint8 data) {
|
||||
void OBC1::ram_write(unsigned addr, uint8 data) {
|
||||
memory::cartram.write(addr & 0x1fff, data);
|
||||
}
|
||||
|
||||
|
@@ -5,15 +5,15 @@ public:
|
||||
void power();
|
||||
void reset();
|
||||
|
||||
uint8 read(uint addr);
|
||||
void write(uint addr, uint8 data);
|
||||
uint8 read(unsigned addr);
|
||||
void write(unsigned addr, uint8 data);
|
||||
|
||||
OBC1();
|
||||
~OBC1();
|
||||
|
||||
private:
|
||||
uint8 ram_read(uint addr);
|
||||
void ram_write(uint addr, uint8 data);
|
||||
uint8 ram_read(unsigned addr);
|
||||
void ram_write(unsigned addr, uint8 data);
|
||||
|
||||
struct {
|
||||
uint16 address;
|
@@ -1,4 +1,4 @@
|
||||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define SDD1_CPP
|
||||
|
||||
#include "sdd1emu.cpp"
|
||||
@@ -6,7 +6,17 @@
|
||||
void SDD1::init() {}
|
||||
|
||||
void SDD1::enable() {
|
||||
for(int i = 0x4800; i <= 0x4807; i++) memory::mmio.map(i, *this);
|
||||
//hook S-CPU DMA MMIO registers to gather information for struct dma[];
|
||||
//buffer address and transfer size information for use in SDD1::read()
|
||||
for(unsigned i = 0x4300; i <= 0x437f; i++) {
|
||||
cpu_mmio[i & 0x7f] = memory::mmio.get(i);
|
||||
memory::mmio.map(i, *this);
|
||||
}
|
||||
|
||||
//hook S-DD1 MMIO registers
|
||||
for(unsigned i = 0x4800; i <= 0x4807; i++) {
|
||||
memory::mmio.map(i, *this);
|
||||
}
|
||||
}
|
||||
|
||||
void SDD1::power() {
|
||||
@@ -14,97 +24,133 @@ void SDD1::power() {
|
||||
}
|
||||
|
||||
void SDD1::reset() {
|
||||
sdd1.dma_active = false;
|
||||
sdd1_enable = 0x00;
|
||||
xfer_enable = 0x00;
|
||||
|
||||
regs.r4800 = 0x00;
|
||||
regs.r4801 = 0x00;
|
||||
mmc[0] = 0 << 20;
|
||||
mmc[1] = 1 << 20;
|
||||
mmc[2] = 2 << 20;
|
||||
mmc[3] = 3 << 20;
|
||||
|
||||
regs.r4804 = 0x00;
|
||||
regs.r4805 = 0x01;
|
||||
regs.r4806 = 0x02;
|
||||
regs.r4807 = 0x03;
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
dma[i].addr = 0;
|
||||
dma[i].size = 0;
|
||||
}
|
||||
|
||||
bus.map(Bus::MapLinear, 0xc0, 0xcf, 0x0000, 0xffff, memory::cartrom, (regs.r4804 & 7) << 20);
|
||||
bus.map(Bus::MapLinear, 0xd0, 0xdf, 0x0000, 0xffff, memory::cartrom, (regs.r4805 & 7) << 20);
|
||||
bus.map(Bus::MapLinear, 0xe0, 0xef, 0x0000, 0xffff, memory::cartrom, (regs.r4806 & 7) << 20);
|
||||
bus.map(Bus::MapLinear, 0xf0, 0xff, 0x0000, 0xffff, memory::cartrom, (regs.r4807 & 7) << 20);
|
||||
buffer.ready = false;
|
||||
|
||||
bus.map(Bus::MapDirect, 0xc0, 0xff, 0x0000, 0xffff, *this);
|
||||
}
|
||||
|
||||
uint8 SDD1::mmio_read(uint addr) {
|
||||
switch(addr & 0xffff) {
|
||||
case 0x4804: return regs.r4804;
|
||||
case 0x4805: return regs.r4805;
|
||||
case 0x4806: return regs.r4806;
|
||||
case 0x4807: return regs.r4807;
|
||||
addr &= 0xffff;
|
||||
|
||||
if((addr & 0x4380) == 0x4300) {
|
||||
return cpu_mmio[addr & 0x7f]->mmio_read(addr);
|
||||
}
|
||||
|
||||
switch(addr) {
|
||||
case 0x4804: return (mmc[0] >> 20) & 7;
|
||||
case 0x4805: return (mmc[1] >> 20) & 7;
|
||||
case 0x4806: return (mmc[2] >> 20) & 7;
|
||||
case 0x4807: return (mmc[3] >> 20) & 7;
|
||||
}
|
||||
|
||||
return cpu.regs.mdr;
|
||||
}
|
||||
|
||||
void SDD1::mmio_write(uint addr, uint8 data) {
|
||||
switch(addr & 0xffff) {
|
||||
case 0x4800: {
|
||||
regs.r4800 = data;
|
||||
} break;
|
||||
addr &= 0xffff;
|
||||
|
||||
case 0x4801: {
|
||||
regs.r4801 = data;
|
||||
} break;
|
||||
if((addr & 0x4380) == 0x4300) {
|
||||
unsigned channel = (addr >> 4) & 7;
|
||||
switch(addr & 15) {
|
||||
case 2: dma[channel].addr = (dma[channel].addr & 0xffff00) + (data << 0); break;
|
||||
case 3: dma[channel].addr = (dma[channel].addr & 0xff00ff) + (data << 8); break;
|
||||
case 4: dma[channel].addr = (dma[channel].addr & 0x00ffff) + (data << 16); break;
|
||||
|
||||
case 0x4804: {
|
||||
if(regs.r4804 != data) {
|
||||
regs.r4804 = data;
|
||||
bus.map(Bus::MapLinear, 0xc0, 0xcf, 0x0000, 0xffff,
|
||||
memory::cartrom, (regs.r4804 & 7) << 20);
|
||||
}
|
||||
} break;
|
||||
case 5: dma[channel].size = (dma[channel].size & 0xff00) + (data << 0); break;
|
||||
case 6: dma[channel].size = (dma[channel].size & 0x00ff) + (data << 8); break;
|
||||
}
|
||||
return cpu_mmio[addr & 0x7f]->mmio_write(addr, data);
|
||||
}
|
||||
|
||||
case 0x4805: {
|
||||
if(regs.r4805 != data) {
|
||||
regs.r4805 = data;
|
||||
bus.map(Bus::MapLinear, 0xd0, 0xdf, 0x0000, 0xffff,
|
||||
memory::cartrom, (regs.r4805 & 7) << 20);
|
||||
}
|
||||
} break;
|
||||
switch(addr) {
|
||||
case 0x4800: sdd1_enable = data; break;
|
||||
case 0x4801: xfer_enable = data; break;
|
||||
|
||||
case 0x4806: {
|
||||
if(regs.r4806 != data) {
|
||||
regs.r4806 = data;
|
||||
bus.map(Bus::MapLinear, 0xe0, 0xef, 0x0000, 0xffff,
|
||||
memory::cartrom, (regs.r4806 & 7) << 20);
|
||||
}
|
||||
} break;
|
||||
|
||||
case 0x4807: {
|
||||
if(regs.r4807 != data) {
|
||||
regs.r4807 = data;
|
||||
bus.map(Bus::MapLinear, 0xf0, 0xff, 0x0000, 0xffff,
|
||||
memory::cartrom, (regs.r4807 & 7) << 20);
|
||||
}
|
||||
} break;
|
||||
case 0x4804: mmc[0] = (data & 7) << 20; break;
|
||||
case 0x4805: mmc[1] = (data & 7) << 20; break;
|
||||
case 0x4806: mmc[2] = (data & 7) << 20; break;
|
||||
case 0x4807: mmc[3] = (data & 7) << 20; break;
|
||||
}
|
||||
}
|
||||
|
||||
void SDD1::dma_begin(uint8 channel, uint32 addr, uint16 length) {
|
||||
if(regs.r4800 & (1 << channel) && regs.r4801 & (1 << channel)) {
|
||||
regs.r4801 &= ~(1 << channel);
|
||||
sdd1.dma_active = true;
|
||||
sdd1.buffer_index = 0;
|
||||
sdd1.buffer_size = length;
|
||||
sdd1emu.decompress(addr, (length) ? length : 65536, sdd1.buffer);
|
||||
}
|
||||
//SDD1::read() is mapped to $[c0-ff]:[0000-ffff]
|
||||
//the design is meant to be as close to the hardware design as possible, thus this code
|
||||
//avoids adding S-DD1 hooks inside S-CPU::DMA emulation.
|
||||
//
|
||||
//the real S-DD1 cannot see $420b (DMA enable) writes, as they are not placed on the bus.
|
||||
//however, $43x0-$43xf writes (DMAx channel settings) most likely do appear on the bus.
|
||||
//the S-DD1 also requires fixed addresses for transfers, which wouldn't be necessary if
|
||||
//it could see $420b writes (eg it would know when the transfer should begin.)
|
||||
//
|
||||
//the hardware needs a way to distinguish program code after $4801 writes from DMA
|
||||
//decompression that follows soon after.
|
||||
//
|
||||
//the only plausible design for hardware would be for the S-DD1 to spy on DMAx settings,
|
||||
//and begin spooling decompression on writes to $4801 that activate a channel. after that,
|
||||
//it feeds decompressed data only when the ROM read address matches the DMA channel address.
|
||||
//
|
||||
//the actual S-DD1 transfer can occur on any channel, but it is most likely limited to
|
||||
//one transfer per $420b write (for spooling purposes). however, this is not known for certain.
|
||||
uint8 SDD1::read(unsigned addr) {
|
||||
if(sdd1_enable & xfer_enable) {
|
||||
//at least one channel has S-DD1 decompression enabled ...
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
if(sdd1_enable & xfer_enable & (1 << i)) {
|
||||
//S-DD1 always uses fixed transfer mode, so address will not change during transfer
|
||||
if(addr == dma[i].addr) {
|
||||
if(!buffer.ready) {
|
||||
//first byte read for channel performs full decompression.
|
||||
//this really should stream byte-by-byte, but it's not necessary since the size is known
|
||||
buffer.offset = 0;
|
||||
buffer.size = dma[i].size ? dma[i].size : 65536;
|
||||
|
||||
//sdd1emu calls this function; it needs to access uncompressed data;
|
||||
//so temporarily disable decompression mode for decompress() call.
|
||||
uint8 temp = sdd1_enable;
|
||||
sdd1_enable = false;
|
||||
sdd1emu.decompress(addr, buffer.size, buffer.data);
|
||||
sdd1_enable = temp;
|
||||
|
||||
buffer.ready = true;
|
||||
}
|
||||
|
||||
//fetch a decompressed byte; once buffer is depleted, disable channel and invalidate buffer
|
||||
uint8 data = buffer.data[(uint16)buffer.offset++];
|
||||
if(buffer.offset >= buffer.size) {
|
||||
buffer.ready = false;
|
||||
xfer_enable &= ~(1 << i);
|
||||
}
|
||||
|
||||
return data;
|
||||
} //address matched
|
||||
} //channel enabled
|
||||
} //channel loop
|
||||
} //S-DD1 decompressor enabled
|
||||
|
||||
//S-DD1 decompression mode inactive; return ROM data
|
||||
return memory::cartrom.read(mmc[(addr >> 20) & 3] + (addr & 0x0fffff));
|
||||
}
|
||||
|
||||
bool SDD1::dma_active() {
|
||||
return sdd1.dma_active;
|
||||
void SDD1::write(unsigned addr, uint8 data) {
|
||||
}
|
||||
|
||||
uint8 SDD1::dma_read() {
|
||||
if(--sdd1.buffer_size == 0) sdd1.dma_active = false;
|
||||
|
||||
//sdd1.buffer[] is 65536 bytes, and sdd1.buffer_index
|
||||
//is of type uint16, so no buffer overflow is possible
|
||||
return sdd1.buffer[sdd1.buffer_index++];
|
||||
SDD1::SDD1() {
|
||||
buffer.data = new uint8[65536];
|
||||
}
|
||||
|
||||
SDD1::SDD1() {}
|
||||
SDD1::~SDD1() {
|
||||
delete[] buffer.data;
|
||||
}
|
||||
|
@@ -1,39 +0,0 @@
|
||||
#include "sdd1emu.h"
|
||||
|
||||
class SDD1 : public MMIO {
|
||||
public:
|
||||
void init();
|
||||
void enable();
|
||||
void power();
|
||||
void reset();
|
||||
|
||||
uint8 mmio_read (uint addr);
|
||||
void mmio_write(uint addr, uint8 data);
|
||||
|
||||
void dma_begin(uint8 channel, uint32 addr, uint16 length);
|
||||
bool dma_active();
|
||||
uint8 dma_read();
|
||||
|
||||
SDD1();
|
||||
|
||||
private:
|
||||
SDD1emu sdd1emu;
|
||||
|
||||
struct {
|
||||
uint8 buffer[65536]; //pointer to decompressed S-DD1 data, max DMA length is 65536
|
||||
uint16 buffer_index; //DMA read index into S-DD1 decompression buffer
|
||||
uint16 buffer_size;
|
||||
bool dma_active;
|
||||
} sdd1;
|
||||
|
||||
struct {
|
||||
uint8 r4800;
|
||||
uint8 r4801;
|
||||
uint8 r4804;
|
||||
uint8 r4805;
|
||||
uint8 r4806;
|
||||
uint8 r4807;
|
||||
} regs;
|
||||
};
|
||||
|
||||
extern SDD1 sdd1;
|
40
src/chip/sdd1/sdd1.hpp
Normal file
40
src/chip/sdd1/sdd1.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#include "sdd1emu.hpp"
|
||||
|
||||
class SDD1 : public MMIO, public Memory {
|
||||
public:
|
||||
void init();
|
||||
void enable();
|
||||
void power();
|
||||
void reset();
|
||||
|
||||
uint8 mmio_read(unsigned addr);
|
||||
void mmio_write(unsigned addr, uint8 data);
|
||||
|
||||
uint8 read(unsigned addr);
|
||||
void write(unsigned addr, uint8 data);
|
||||
|
||||
SDD1();
|
||||
~SDD1();
|
||||
|
||||
private:
|
||||
MMIO *cpu_mmio[0x80]; //bus spying hooks to glean information for struct dma[]
|
||||
|
||||
uint8 sdd1_enable; //channel bit-mask
|
||||
uint8 xfer_enable; //channel bit-mask
|
||||
unsigned mmc[4]; //memory map controller ROM indices
|
||||
|
||||
struct {
|
||||
unsigned addr; //$43x2-$43x4 -- DMA transfer address
|
||||
uint16 size; //$43x5-$43x6 -- DMA transfer size
|
||||
} dma[8];
|
||||
|
||||
SDD1emu sdd1emu;
|
||||
struct {
|
||||
uint8 *data; //pointer to decompressed S-DD1 data (65536 bytes)
|
||||
uint16 offset; //read index into S-DD1 decompression buffer
|
||||
unsigned size; //length of data buffer; reads decrement counter, set ready to false at 0
|
||||
bool ready; //true when data[] is valid; false to invoke sdd1emu.decompress()
|
||||
} buffer;
|
||||
};
|
||||
|
||||
extern SDD1 sdd1;
|
@@ -30,7 +30,7 @@ understood.
|
||||
|
||||
************************************************************************/
|
||||
|
||||
#define SDD1_read(__addr) (bus.read(__addr))
|
||||
#define SDD1_read(__addr) (sdd1.read(__addr))
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
|
511
src/chip/spc7110/decomp.cpp
Normal file
511
src/chip/spc7110/decomp.cpp
Normal file
@@ -0,0 +1,511 @@
|
||||
#ifdef SPC7110_CPP
|
||||
|
||||
uint8 SPC7110Decomp::read() {
|
||||
if(decomp_buffer_length == 0) {
|
||||
//decompress at least (decomp_buffer_size / 2) bytes to the buffer
|
||||
switch(decomp_mode) {
|
||||
case 0: mode0(false); break;
|
||||
case 1: mode1(false); break;
|
||||
case 2: mode2(false); break;
|
||||
default: return 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
uint8 data = decomp_buffer[decomp_buffer_rdoffset++];
|
||||
decomp_buffer_rdoffset &= decomp_buffer_size - 1;
|
||||
decomp_buffer_length--;
|
||||
return data;
|
||||
}
|
||||
|
||||
void SPC7110Decomp::write(uint8 data) {
|
||||
decomp_buffer[decomp_buffer_wroffset++] = data;
|
||||
decomp_buffer_wroffset &= decomp_buffer_size - 1;
|
||||
decomp_buffer_length++;
|
||||
}
|
||||
|
||||
uint8 SPC7110Decomp::dataread() {
|
||||
unsigned size = memory::cartrom.size() - 0x100000;
|
||||
while(decomp_offset >= size) decomp_offset -= size;
|
||||
return memory::cartrom.read(0x100000 + decomp_offset++);
|
||||
}
|
||||
|
||||
void SPC7110Decomp::init(unsigned mode, unsigned offset, unsigned index) {
|
||||
decomp_mode = mode;
|
||||
decomp_offset = offset;
|
||||
|
||||
decomp_buffer_rdoffset = 0;
|
||||
decomp_buffer_wroffset = 0;
|
||||
decomp_buffer_length = 0;
|
||||
|
||||
//reset context states
|
||||
for(unsigned i = 0; i < 32; i++) {
|
||||
context[i].index = 0;
|
||||
context[i].invert = 0;
|
||||
}
|
||||
|
||||
switch(decomp_mode) {
|
||||
case 0: mode0(true); break;
|
||||
case 1: mode1(true); break;
|
||||
case 2: mode2(true); break;
|
||||
}
|
||||
|
||||
//decompress up to requested output data index
|
||||
while(index--) read();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
void SPC7110Decomp::mode0(bool init) {
|
||||
static uint8 val, in, span;
|
||||
static int out, inverts, lps, in_count;
|
||||
|
||||
if(init == true) {
|
||||
out = inverts = lps = 0;
|
||||
span = 0xff;
|
||||
val = dataread();
|
||||
in = dataread();
|
||||
in_count = 8;
|
||||
return;
|
||||
}
|
||||
|
||||
while(decomp_buffer_length < (decomp_buffer_size >> 1)) {
|
||||
for(unsigned bit = 0; bit < 8; bit++) {
|
||||
//get context
|
||||
uint8 mask = (1 << (bit & 3)) - 1;
|
||||
uint8 con = mask + ((inverts & mask) ^ (lps & mask));
|
||||
if(bit > 3) con += 15;
|
||||
|
||||
//get prob and mps
|
||||
unsigned prob = probability(con);
|
||||
unsigned mps = (((out >> 15) & 1) ^ context[con].invert);
|
||||
|
||||
//get bit
|
||||
unsigned flag_lps;
|
||||
if(val <= span - prob) { //mps
|
||||
span = span - prob;
|
||||
out = (out << 1) + mps;
|
||||
flag_lps = 0;
|
||||
} else { //lps
|
||||
val = val - (span - (prob - 1));
|
||||
span = prob - 1;
|
||||
out = (out << 1) + 1 - mps;
|
||||
flag_lps = 1;
|
||||
}
|
||||
|
||||
//renormalize
|
||||
unsigned shift = 0;
|
||||
while(span < 0x7f) {
|
||||
shift++;
|
||||
|
||||
span = (span << 1) + 1;
|
||||
val = (val << 1) + (in >> 7);
|
||||
|
||||
in <<= 1;
|
||||
if(--in_count == 0) {
|
||||
in = dataread();
|
||||
in_count = 8;
|
||||
}
|
||||
}
|
||||
|
||||
//update processing info
|
||||
lps = (lps << 1) + flag_lps;
|
||||
inverts = (inverts << 1) + context[con].invert;
|
||||
|
||||
//update context state
|
||||
if(flag_lps & toggle_invert(con)) context[con].invert ^= 1;
|
||||
if(flag_lps) context[con].index = next_lps(con);
|
||||
else if(shift) context[con].index = next_mps(con);
|
||||
}
|
||||
|
||||
//save byte
|
||||
write(out);
|
||||
}
|
||||
}
|
||||
|
||||
void SPC7110Decomp::mode1(bool init) {
|
||||
static int pixelorder[4], realorder[4];
|
||||
static uint8 in, val, span;
|
||||
static int out, inverts, lps, in_count;
|
||||
|
||||
if(init == true) {
|
||||
for(unsigned i = 0; i < 4; i++) pixelorder[i] = i;
|
||||
out = inverts = lps = 0;
|
||||
span = 0xff;
|
||||
val = dataread();
|
||||
in = dataread();
|
||||
in_count = 8;
|
||||
return;
|
||||
}
|
||||
|
||||
while(decomp_buffer_length < (decomp_buffer_size >> 1)) {
|
||||
for(unsigned pixel = 0; pixel < 8; pixel++) {
|
||||
//get first symbol context
|
||||
unsigned a = ((out >> (1 * 2)) & 3);
|
||||
unsigned b = ((out >> (7 * 2)) & 3);
|
||||
unsigned c = ((out >> (8 * 2)) & 3);
|
||||
unsigned con = (a == b) ? (b != c) : (b == c) ? 2 : 4 - (a == c);
|
||||
|
||||
//update pixel order
|
||||
unsigned m, n;
|
||||
for(m = 0; m < 4; m++) if(pixelorder[m] == a) break;
|
||||
for(n = m; n > 0; n--) pixelorder[n] = pixelorder[n - 1];
|
||||
pixelorder[0] = a;
|
||||
|
||||
//calculate the real pixel order
|
||||
for(m = 0; m < 4; m++) realorder[m] = pixelorder[m];
|
||||
|
||||
//rotate reference pixel c value to top
|
||||
for(m = 0; m < 4; m++) if(realorder[m] == c) break;
|
||||
for(n = m; n > 0; n--) realorder[n] = realorder[n - 1];
|
||||
realorder[0] = c;
|
||||
|
||||
//rotate reference pixel b value to top
|
||||
for(m = 0; m < 4; m++) if(realorder[m] == b) break;
|
||||
for(n = m; n > 0; n--) realorder[n] = realorder[n - 1];
|
||||
realorder[0] = b;
|
||||
|
||||
//rotate reference pixel a value to top
|
||||
for(m = 0; m < 4; m++) if(realorder[m] == a) break;
|
||||
for(n = m; n > 0; n--) realorder[n] = realorder[n - 1];
|
||||
realorder[0] = a;
|
||||
|
||||
//get 2 symbols
|
||||
for(unsigned bit = 0; bit < 2; bit++) {
|
||||
//get prob
|
||||
unsigned prob = probability(con);
|
||||
|
||||
//get symbol
|
||||
unsigned flag_lps;
|
||||
if(val <= span - prob) { //mps
|
||||
span = span - prob;
|
||||
flag_lps = 0;
|
||||
} else { //lps
|
||||
val = val - (span - (prob - 1));
|
||||
span = prob - 1;
|
||||
flag_lps = 1;
|
||||
}
|
||||
|
||||
//renormalize
|
||||
unsigned shift = 0;
|
||||
while(span < 0x7f) {
|
||||
shift++;
|
||||
|
||||
span = (span << 1) + 1;
|
||||
val = (val << 1) + (in >> 7);
|
||||
|
||||
in <<= 1;
|
||||
if(--in_count == 0) {
|
||||
in = dataread();
|
||||
in_count = 8;
|
||||
}
|
||||
}
|
||||
|
||||
//update processing info
|
||||
lps = (lps << 1) + flag_lps;
|
||||
inverts = (inverts << 1) + context[con].invert;
|
||||
|
||||
//update context state
|
||||
if(flag_lps & toggle_invert(con)) context[con].invert ^= 1;
|
||||
if(flag_lps) context[con].index = next_lps(con);
|
||||
else if(shift) context[con].index = next_mps(con);
|
||||
|
||||
//get next context
|
||||
con = 5 + (con << 1) + ((lps ^ inverts) & 1);
|
||||
}
|
||||
|
||||
//get pixel
|
||||
b = realorder[(lps ^ inverts) & 3];
|
||||
out = (out << 2) + b;
|
||||
}
|
||||
|
||||
//turn pixel data into bitplanes
|
||||
unsigned data = morton_2x8(out);
|
||||
write(data >> 8);
|
||||
write(data >> 0);
|
||||
}
|
||||
}
|
||||
|
||||
void SPC7110Decomp::mode2(bool init) {
|
||||
static int pixelorder[16], realorder[16];
|
||||
static uint8 bitplanebuffer[16], buffer_index;
|
||||
static uint8 in, val, span;
|
||||
static int out0, out1, inverts, lps, in_count;
|
||||
|
||||
if(init == true) {
|
||||
for(unsigned i = 0; i < 16; i++) pixelorder[i] = i;
|
||||
buffer_index = 0;
|
||||
out0 = out1 = inverts = lps = 0;
|
||||
span = 0xff;
|
||||
val = dataread();
|
||||
in = dataread();
|
||||
in_count = 8;
|
||||
return;
|
||||
}
|
||||
|
||||
while(decomp_buffer_length < (decomp_buffer_size >> 1)) {
|
||||
for(unsigned pixel = 0; pixel < 8; pixel++) {
|
||||
//get first symbol context
|
||||
unsigned a = ((out0 >> (0 * 4)) & 15);
|
||||
unsigned b = ((out0 >> (7 * 4)) & 15);
|
||||
unsigned c = ((out1 >> (0 * 4)) & 15);
|
||||
unsigned con = 0;
|
||||
unsigned refcon = (a == b) ? (b != c) : (b == c) ? 2 : 4 - (a == c);
|
||||
|
||||
//update pixel order
|
||||
unsigned m, n;
|
||||
for(m = 0; m < 16; m++) if(pixelorder[m] == a) break;
|
||||
for(n = m; n > 0; n--) pixelorder[n] = pixelorder[n - 1];
|
||||
pixelorder[0] = a;
|
||||
|
||||
//calculate the real pixel order
|
||||
for(m = 0; m < 16; m++) realorder[m] = pixelorder[m];
|
||||
|
||||
//rotate reference pixel c value to top
|
||||
for(m = 0; m < 16; m++) if(realorder[m] == c) break;
|
||||
for(n = m; n > 0; n--) realorder[n] = realorder[n - 1];
|
||||
realorder[0] = c;
|
||||
|
||||
//rotate reference pixel b value to top
|
||||
for(m = 0; m < 16; m++) if(realorder[m] == b) break;
|
||||
for(n = m; n > 0; n--) realorder[n] = realorder[n - 1];
|
||||
realorder[0] = b;
|
||||
|
||||
//rotate reference pixel a value to top
|
||||
for(m = 0; m < 16; m++) if(realorder[m] == a) break;
|
||||
for(n = m; n > 0; n--) realorder[n] = realorder[n - 1];
|
||||
realorder[0] = a;
|
||||
|
||||
//get 4 symbols
|
||||
for(unsigned bit = 0; bit < 4; bit++) {
|
||||
//get prob
|
||||
unsigned prob = probability(con);
|
||||
|
||||
//get symbol
|
||||
unsigned flag_lps;
|
||||
if(val <= span - prob) { //mps
|
||||
span = span - prob;
|
||||
flag_lps = 0;
|
||||
} else { //lps
|
||||
val = val - (span - (prob - 1));
|
||||
span = prob - 1;
|
||||
flag_lps = 1;
|
||||
}
|
||||
|
||||
//renormalize
|
||||
unsigned shift = 0;
|
||||
while(span < 0x7f) {
|
||||
shift++;
|
||||
|
||||
span = (span << 1) + 1;
|
||||
val = (val << 1) + (in >> 7);
|
||||
|
||||
in <<= 1;
|
||||
if(--in_count == 0) {
|
||||
in = dataread();
|
||||
in_count = 8;
|
||||
}
|
||||
}
|
||||
|
||||
//update processing info
|
||||
lps = (lps << 1) + flag_lps;
|
||||
unsigned invertbit = context[con].invert;
|
||||
inverts = (inverts << 1) + invertbit;
|
||||
|
||||
//update context state
|
||||
if(flag_lps & toggle_invert(con)) context[con].invert ^= 1;
|
||||
if(flag_lps) context[con].index = next_lps(con);
|
||||
else if(shift) context[con].index = next_mps(con);
|
||||
|
||||
//get next context
|
||||
con = mode2_context_table[con][flag_lps ^ invertbit] + (con == 1 ? refcon : 0);
|
||||
}
|
||||
|
||||
//get pixel
|
||||
b = realorder[(lps ^ inverts) & 0x0f];
|
||||
out1 = (out1 << 4) + ((out0 >> 28) & 0x0f);
|
||||
out0 = (out0 << 4) + b;
|
||||
}
|
||||
|
||||
//convert pixel data into bitplanes
|
||||
unsigned data = morton_4x8(out0);
|
||||
write(data >> 24);
|
||||
write(data >> 16);
|
||||
bitplanebuffer[buffer_index++] = data >> 8;
|
||||
bitplanebuffer[buffer_index++] = data >> 0;
|
||||
|
||||
if(buffer_index == 16) {
|
||||
for(unsigned i = 0; i < 16; i++) write(bitplanebuffer[i]);
|
||||
buffer_index = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const uint8 SPC7110Decomp::evolution_table[53][4] = {
|
||||
//{ prob, nextlps, nextmps, toggle invert },
|
||||
|
||||
{ 0x5a, 1, 1, 1 },
|
||||
{ 0x25, 6, 2, 0 },
|
||||
{ 0x11, 8, 3, 0 },
|
||||
{ 0x08, 10, 4, 0 },
|
||||
{ 0x03, 12, 5, 0 },
|
||||
{ 0x01, 15, 5, 0 },
|
||||
|
||||
{ 0x5a, 7, 7, 1 },
|
||||
{ 0x3f, 19, 8, 0 },
|
||||
{ 0x2c, 21, 9, 0 },
|
||||
{ 0x20, 22, 10, 0 },
|
||||
{ 0x17, 23, 11, 0 },
|
||||
{ 0x11, 25, 12, 0 },
|
||||
{ 0x0c, 26, 13, 0 },
|
||||
{ 0x09, 28, 14, 0 },
|
||||
{ 0x07, 29, 15, 0 },
|
||||
{ 0x05, 31, 16, 0 },
|
||||
{ 0x04, 32, 17, 0 },
|
||||
{ 0x03, 34, 18, 0 },
|
||||
{ 0x02, 35, 5, 0 },
|
||||
|
||||
{ 0x5a, 20, 20, 1 },
|
||||
{ 0x48, 39, 21, 0 },
|
||||
{ 0x3a, 40, 22, 0 },
|
||||
{ 0x2e, 42, 23, 0 },
|
||||
{ 0x26, 44, 24, 0 },
|
||||
{ 0x1f, 45, 25, 0 },
|
||||
{ 0x19, 46, 26, 0 },
|
||||
{ 0x15, 25, 27, 0 },
|
||||
{ 0x11, 26, 28, 0 },
|
||||
{ 0x0e, 26, 29, 0 },
|
||||
{ 0x0b, 27, 30, 0 },
|
||||
{ 0x09, 28, 31, 0 },
|
||||
{ 0x08, 29, 32, 0 },
|
||||
{ 0x07, 30, 33, 0 },
|
||||
{ 0x05, 31, 34, 0 },
|
||||
{ 0x04, 33, 35, 0 },
|
||||
{ 0x04, 33, 36, 0 },
|
||||
{ 0x03, 34, 37, 0 },
|
||||
{ 0x02, 35, 38, 0 },
|
||||
{ 0x02, 36, 5, 0 },
|
||||
|
||||
{ 0x58, 39, 40, 1 },
|
||||
{ 0x4d, 47, 41, 0 },
|
||||
{ 0x43, 48, 42, 0 },
|
||||
{ 0x3b, 49, 43, 0 },
|
||||
{ 0x34, 50, 44, 0 },
|
||||
{ 0x2e, 51, 45, 0 },
|
||||
{ 0x29, 44, 46, 0 },
|
||||
{ 0x25, 45, 24, 0 },
|
||||
|
||||
{ 0x56, 47, 48, 1 },
|
||||
{ 0x4f, 47, 49, 0 },
|
||||
{ 0x47, 48, 50, 0 },
|
||||
{ 0x41, 49, 51, 0 },
|
||||
{ 0x3c, 50, 52, 0 },
|
||||
{ 0x37, 51, 43, 0 },
|
||||
};
|
||||
|
||||
const uint8 SPC7110Decomp::mode2_context_table[32][2] = {
|
||||
//{ next 0, next 1 },
|
||||
|
||||
{ 1, 2 },
|
||||
|
||||
{ 3, 8 },
|
||||
{ 13, 14 },
|
||||
|
||||
{ 15, 16 },
|
||||
{ 17, 18 },
|
||||
{ 19, 20 },
|
||||
{ 21, 22 },
|
||||
{ 23, 24 },
|
||||
{ 25, 26 },
|
||||
{ 25, 26 },
|
||||
{ 25, 26 },
|
||||
{ 25, 26 },
|
||||
{ 25, 26 },
|
||||
{ 27, 28 },
|
||||
{ 29, 30 },
|
||||
|
||||
{ 31, 31 },
|
||||
{ 31, 31 },
|
||||
{ 31, 31 },
|
||||
{ 31, 31 },
|
||||
{ 31, 31 },
|
||||
{ 31, 31 },
|
||||
{ 31, 31 },
|
||||
{ 31, 31 },
|
||||
{ 31, 31 },
|
||||
{ 31, 31 },
|
||||
{ 31, 31 },
|
||||
{ 31, 31 },
|
||||
{ 31, 31 },
|
||||
{ 31, 31 },
|
||||
{ 31, 31 },
|
||||
{ 31, 31 },
|
||||
|
||||
{ 31, 31 },
|
||||
};
|
||||
|
||||
uint8 SPC7110Decomp::probability (unsigned n) { return evolution_table[context[n].index][0]; }
|
||||
uint8 SPC7110Decomp::next_lps (unsigned n) { return evolution_table[context[n].index][1]; }
|
||||
uint8 SPC7110Decomp::next_mps (unsigned n) { return evolution_table[context[n].index][2]; }
|
||||
bool SPC7110Decomp::toggle_invert(unsigned n) { return evolution_table[context[n].index][3]; }
|
||||
|
||||
unsigned SPC7110Decomp::morton_2x8(unsigned data) {
|
||||
//reverse morton lookup: de-interleave two 8-bit values
|
||||
//15, 13, 11, 9, 7, 5, 3, 1 -> 15- 8
|
||||
//14, 12, 10, 8, 6, 4, 2, 0 -> 7- 0
|
||||
return morton16[0][(data >> 0) & 255] + morton16[1][(data >> 8) & 255];
|
||||
}
|
||||
|
||||
unsigned SPC7110Decomp::morton_4x8(unsigned data) {
|
||||
//reverse morton lookup: de-interleave four 8-bit values
|
||||
//31, 27, 23, 19, 15, 11, 7, 3 -> 31-24
|
||||
//30, 26, 22, 18, 14, 10, 6, 2 -> 23-16
|
||||
//29, 25, 21, 17, 13, 9, 5, 1 -> 15- 8
|
||||
//28, 24, 20, 16, 12, 8, 4, 0 -> 7- 0
|
||||
return morton32[0][(data >> 0) & 255] + morton32[1][(data >> 8) & 255]
|
||||
+ morton32[2][(data >> 16) & 255] + morton32[3][(data >> 24) & 255];
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
void SPC7110Decomp::reset() {
|
||||
//mode 3 is invalid; this is treated as a special case to always return 0x00
|
||||
//set to mode 3 so that reading decomp port before starting first decomp will return 0x00
|
||||
decomp_mode = 3;
|
||||
|
||||
decomp_buffer_rdoffset = 0;
|
||||
decomp_buffer_wroffset = 0;
|
||||
decomp_buffer_length = 0;
|
||||
}
|
||||
|
||||
SPC7110Decomp::SPC7110Decomp() {
|
||||
decomp_buffer = new uint8_t[decomp_buffer_size];
|
||||
reset();
|
||||
|
||||
//initialize reverse morton lookup tables
|
||||
for(unsigned i = 0; i < 256; i++) {
|
||||
#define map(x, y) (((i >> x) & 1) << y)
|
||||
//2x8-bit
|
||||
morton16[1][i] = map(7, 15) + map(6, 7) + map(5, 14) + map(4, 6)
|
||||
+ map(3, 13) + map(2, 5) + map(1, 12) + map(0, 4);
|
||||
morton16[0][i] = map(7, 11) + map(6, 3) + map(5, 10) + map(4, 2)
|
||||
+ map(3, 9) + map(2, 1) + map(1, 8) + map(0, 0);
|
||||
//4x8-bit
|
||||
morton32[3][i] = map(7, 31) + map(6, 23) + map(5, 15) + map(4, 7)
|
||||
+ map(3, 30) + map(2, 22) + map(1, 14) + map(0, 6);
|
||||
morton32[2][i] = map(7, 29) + map(6, 21) + map(5, 13) + map(4, 5)
|
||||
+ map(3, 28) + map(2, 20) + map(1, 12) + map(0, 4);
|
||||
morton32[1][i] = map(7, 27) + map(6, 19) + map(5, 11) + map(4, 3)
|
||||
+ map(3, 26) + map(2, 18) + map(1, 10) + map(0, 2);
|
||||
morton32[0][i] = map(7, 25) + map(6, 17) + map(5, 9) + map(4, 1)
|
||||
+ map(3, 24) + map(2, 16) + map(1, 8) + map(0, 0);
|
||||
#undef map
|
||||
}
|
||||
}
|
||||
|
||||
SPC7110Decomp::~SPC7110Decomp() {
|
||||
delete[] decomp_buffer;
|
||||
}
|
||||
|
||||
#endif
|
45
src/chip/spc7110/decomp.hpp
Normal file
45
src/chip/spc7110/decomp.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
class SPC7110Decomp {
|
||||
public:
|
||||
uint8 read();
|
||||
void init(unsigned mode, unsigned offset, unsigned index);
|
||||
void reset();
|
||||
|
||||
SPC7110Decomp();
|
||||
~SPC7110Decomp();
|
||||
|
||||
private:
|
||||
unsigned decomp_mode;
|
||||
unsigned decomp_offset;
|
||||
|
||||
//read() will spool chunks half the size of decomp_buffer_size
|
||||
enum { decomp_buffer_size = 64 }; //must be >= 64, and must be a power of two
|
||||
uint8 *decomp_buffer;
|
||||
unsigned decomp_buffer_rdoffset;
|
||||
unsigned decomp_buffer_wroffset;
|
||||
unsigned decomp_buffer_length;
|
||||
|
||||
void write(uint8 data);
|
||||
uint8 dataread();
|
||||
|
||||
void mode0(bool init);
|
||||
void mode1(bool init);
|
||||
void mode2(bool init);
|
||||
|
||||
static const uint8 evolution_table[53][4];
|
||||
static const uint8 mode2_context_table[32][2];
|
||||
|
||||
struct ContextState {
|
||||
uint8 index;
|
||||
uint8 invert;
|
||||
} context[32];
|
||||
|
||||
uint8 probability(unsigned n);
|
||||
uint8 next_lps(unsigned n);
|
||||
uint8 next_mps(unsigned n);
|
||||
bool toggle_invert(unsigned n);
|
||||
|
||||
unsigned morton16[2][256];
|
||||
unsigned morton32[4][256];
|
||||
unsigned morton_2x8(unsigned data);
|
||||
unsigned morton_4x8(unsigned data);
|
||||
};
|
658
src/chip/spc7110/spc7110.cpp
Normal file
658
src/chip/spc7110/spc7110.cpp
Normal file
@@ -0,0 +1,658 @@
|
||||
#include <../base.hpp>
|
||||
#define SPC7110_CPP
|
||||
|
||||
#include "decomp.cpp"
|
||||
|
||||
const unsigned SPC7110::months[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
||||
|
||||
void SPC7110::init() {}
|
||||
|
||||
void SPC7110::enable() {
|
||||
uint16_t limit = (cartridge.info.spc7110rtc ? 0x4842 : 0x483f);
|
||||
for(uint16_t i = 0x4800; i <= limit; i++) memory::mmio.map(i, *this);
|
||||
}
|
||||
|
||||
void SPC7110::power() {
|
||||
reset();
|
||||
}
|
||||
|
||||
void SPC7110::reset() {
|
||||
r4801 = 0x00;
|
||||
r4802 = 0x00;
|
||||
r4803 = 0x00;
|
||||
r4804 = 0x00;
|
||||
r4805 = 0x00;
|
||||
r4806 = 0x00;
|
||||
r4807 = 0x00;
|
||||
r4808 = 0x00;
|
||||
r4809 = 0x00;
|
||||
r480a = 0x00;
|
||||
r480b = 0x00;
|
||||
r480c = 0x00;
|
||||
|
||||
decomp.reset();
|
||||
|
||||
r4811 = 0x00;
|
||||
r4812 = 0x00;
|
||||
r4813 = 0x00;
|
||||
r4814 = 0x00;
|
||||
r4815 = 0x00;
|
||||
r4816 = 0x00;
|
||||
r4817 = 0x00;
|
||||
r4818 = 0x00;
|
||||
|
||||
r481x = 0x00;
|
||||
r4814_latch = false;
|
||||
r4815_latch = false;
|
||||
|
||||
r4820 = 0x00;
|
||||
r4821 = 0x00;
|
||||
r4822 = 0x00;
|
||||
r4823 = 0x00;
|
||||
r4824 = 0x00;
|
||||
r4825 = 0x00;
|
||||
r4826 = 0x00;
|
||||
r4827 = 0x00;
|
||||
r4828 = 0x00;
|
||||
r4829 = 0x00;
|
||||
r482a = 0x00;
|
||||
r482b = 0x00;
|
||||
r482c = 0x00;
|
||||
r482d = 0x00;
|
||||
r482e = 0x00;
|
||||
r482f = 0x00;
|
||||
|
||||
r4830 = 0x00;
|
||||
mmio_write(0x4831, 0);
|
||||
mmio_write(0x4832, 1);
|
||||
mmio_write(0x4833, 2);
|
||||
r4834 = 0x00;
|
||||
|
||||
r4840 = 0x00;
|
||||
r4841 = 0x00;
|
||||
r4842 = 0x00;
|
||||
|
||||
if(cartridge.info.spc7110rtc) {
|
||||
rtc_state = RTCS_Inactive;
|
||||
rtc_mode = RTCM_Linear;
|
||||
rtc_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned SPC7110::datarom_addr(unsigned addr) {
|
||||
unsigned size = memory::cartrom.size() - 0x100000;
|
||||
while(addr >= size) addr -= size;
|
||||
return addr + 0x100000;
|
||||
}
|
||||
|
||||
unsigned SPC7110::data_pointer() { return r4811 + (r4812 << 8) + (r4813 << 16); }
|
||||
unsigned SPC7110::data_adjust() { return r4814 + (r4815 << 8); }
|
||||
unsigned SPC7110::data_increment() { return r4816 + (r4817 << 8); }
|
||||
void SPC7110::set_data_pointer(unsigned addr) { r4811 = addr; r4812 = addr >> 8; r4813 = addr >> 16; }
|
||||
void SPC7110::set_data_adjust(unsigned addr) { r4814 = addr; r4815 = addr >> 8; }
|
||||
|
||||
void SPC7110::update_time(int offset) {
|
||||
time_t rtc_time;
|
||||
rtc_time = memory::cartrtc.read(16);
|
||||
rtc_time |= memory::cartrtc.read(17) << 8;
|
||||
rtc_time |= memory::cartrtc.read(18) << 16;
|
||||
rtc_time |= memory::cartrtc.read(19) << 24;
|
||||
|
||||
bool update = true;
|
||||
if(memory::cartrtc.read(13) & 1) update = false; //do not update if CR0 timer disable flag is set
|
||||
if(memory::cartrtc.read(15) & 3) update = false; //do not update if CR2 timer disable flags are set
|
||||
|
||||
time_t current_time = time(0) - offset;
|
||||
if(update && current_time > rtc_time) {
|
||||
unsigned second = memory::cartrtc.read( 0) + memory::cartrtc.read( 1) * 10;
|
||||
unsigned minute = memory::cartrtc.read( 2) + memory::cartrtc.read( 3) * 10;
|
||||
unsigned hour = memory::cartrtc.read( 4) + memory::cartrtc.read( 5) * 10;
|
||||
unsigned day = memory::cartrtc.read( 6) + memory::cartrtc.read( 7) * 10;
|
||||
unsigned month = memory::cartrtc.read( 8) + memory::cartrtc.read( 9) * 10;
|
||||
unsigned year = memory::cartrtc.read(10) + memory::cartrtc.read(11) * 10;
|
||||
unsigned weekday = memory::cartrtc.read(12);
|
||||
|
||||
day--;
|
||||
month--;
|
||||
year += (year >= 90) ? 1900 : 2000; //range = 1990-2089
|
||||
|
||||
second += (unsigned)(current_time - rtc_time);
|
||||
while(second >= 60) {
|
||||
second -= 60;
|
||||
|
||||
minute++;
|
||||
if(minute < 60) continue;
|
||||
minute = 0;
|
||||
|
||||
hour++;
|
||||
if(hour < 24) continue;
|
||||
hour = 0;
|
||||
|
||||
day++;
|
||||
weekday = (weekday + 1) % 7;
|
||||
unsigned days = months[month % 12];
|
||||
if(days == 28) {
|
||||
bool leapyear = false;
|
||||
if((year % 4) == 0) {
|
||||
leapyear = true;
|
||||
if((year % 100) == 0 && (year % 400) != 0) leapyear = false;
|
||||
}
|
||||
if(leapyear) days++;
|
||||
}
|
||||
if(day < days) continue;
|
||||
day = 0;
|
||||
|
||||
month++;
|
||||
if(month < 12) continue;
|
||||
month = 0;
|
||||
|
||||
year++;
|
||||
}
|
||||
|
||||
day++;
|
||||
month++;
|
||||
year %= 100;
|
||||
|
||||
memory::cartrtc.write( 0, second % 10);
|
||||
memory::cartrtc.write( 1, second / 10);
|
||||
memory::cartrtc.write( 2, minute % 10);
|
||||
memory::cartrtc.write( 3, minute / 10);
|
||||
memory::cartrtc.write( 4, hour % 10);
|
||||
memory::cartrtc.write( 5, hour / 10);
|
||||
memory::cartrtc.write( 6, day % 10);
|
||||
memory::cartrtc.write( 7, day / 10);
|
||||
memory::cartrtc.write( 8, month % 10);
|
||||
memory::cartrtc.write( 9, month / 10);
|
||||
memory::cartrtc.write(10, year % 10);
|
||||
memory::cartrtc.write(11, (year / 10) % 10);
|
||||
memory::cartrtc.write(12, weekday % 7);
|
||||
}
|
||||
|
||||
memory::cartrtc.write(16, current_time);
|
||||
memory::cartrtc.write(17, current_time >> 8);
|
||||
memory::cartrtc.write(18, current_time >> 16);
|
||||
memory::cartrtc.write(19, current_time >> 24);
|
||||
}
|
||||
|
||||
uint8 SPC7110::mmio_read(uint addr) {
|
||||
addr &= 0xffff;
|
||||
|
||||
switch(addr) {
|
||||
//==================
|
||||
//decompression unit
|
||||
//==================
|
||||
|
||||
case 0x4800: {
|
||||
uint16 counter = (r4809 + (r480a << 8));
|
||||
counter--;
|
||||
r4809 = counter;
|
||||
r480a = counter >> 8;
|
||||
return decomp.read();
|
||||
}
|
||||
case 0x4801: return r4801;
|
||||
case 0x4802: return r4802;
|
||||
case 0x4803: return r4803;
|
||||
case 0x4804: return r4804;
|
||||
case 0x4805: return r4805;
|
||||
case 0x4806: return r4806;
|
||||
case 0x4807: return r4807;
|
||||
case 0x4808: return r4808;
|
||||
case 0x4809: return r4809;
|
||||
case 0x480a: return r480a;
|
||||
case 0x480b: return r480b;
|
||||
case 0x480c: {
|
||||
uint8 status = r480c;
|
||||
r480c &= 0x7f;
|
||||
return status;
|
||||
}
|
||||
|
||||
//==============
|
||||
//data port unit
|
||||
//==============
|
||||
|
||||
case 0x4810: {
|
||||
if(r481x != 0x07) return 0x00;
|
||||
|
||||
unsigned addr = data_pointer();
|
||||
unsigned adjust = data_adjust();
|
||||
if(r4818 & 8) adjust = (int16)adjust; //16-bit sign extend
|
||||
|
||||
unsigned adjustaddr = addr;
|
||||
if(r4818 & 2) {
|
||||
adjustaddr += adjust;
|
||||
set_data_adjust(adjust + 1);
|
||||
}
|
||||
|
||||
uint8 data = memory::cartrom.read(datarom_addr(adjustaddr));
|
||||
if(!(r4818 & 2)) {
|
||||
unsigned increment = (r4818 & 1) ? data_increment() : 1;
|
||||
if(r4818 & 4) increment = (int16)increment; //16-bit sign extend
|
||||
|
||||
if((r4818 & 16) == 0) {
|
||||
set_data_pointer(addr + increment);
|
||||
} else {
|
||||
set_data_adjust(adjust + increment);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
case 0x4811: return r4811;
|
||||
case 0x4812: return r4812;
|
||||
case 0x4813: return r4813;
|
||||
case 0x4814: return r4814;
|
||||
case 0x4815: return r4815;
|
||||
case 0x4816: return r4816;
|
||||
case 0x4817: return r4817;
|
||||
case 0x4818: return r4818;
|
||||
case 0x481a: {
|
||||
if(r481x != 0x07) return 0x00;
|
||||
|
||||
unsigned addr = data_pointer();
|
||||
unsigned adjust = data_adjust();
|
||||
if(r4818 & 8) adjust = (int16)adjust; //16-bit sign extend
|
||||
|
||||
uint8 data = memory::cartrom.read(datarom_addr(addr + adjust));
|
||||
if((r4818 & 0x60) == 0x60) {
|
||||
if((r4818 & 16) == 0) {
|
||||
set_data_pointer(addr + adjust);
|
||||
} else {
|
||||
set_data_adjust(adjust + adjust);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
//=========
|
||||
//math unit
|
||||
//=========
|
||||
|
||||
case 0x4820: return r4820;
|
||||
case 0x4821: return r4821;
|
||||
case 0x4822: return r4822;
|
||||
case 0x4823: return r4823;
|
||||
case 0x4824: return r4824;
|
||||
case 0x4825: return r4825;
|
||||
case 0x4826: return r4826;
|
||||
case 0x4827: return r4827;
|
||||
case 0x4828: return r4828;
|
||||
case 0x4829: return r4829;
|
||||
case 0x482a: return r482a;
|
||||
case 0x482b: return r482b;
|
||||
case 0x482c: return r482c;
|
||||
case 0x482d: return r482d;
|
||||
case 0x482e: return r482e;
|
||||
case 0x482f: {
|
||||
uint8 status = r482f;
|
||||
r482f &= 0x7f;
|
||||
return status;
|
||||
}
|
||||
|
||||
//===================
|
||||
//memory mapping unit
|
||||
//===================
|
||||
|
||||
case 0x4830: return r4830;
|
||||
case 0x4831: return r4831;
|
||||
case 0x4832: return r4832;
|
||||
case 0x4833: return r4833;
|
||||
case 0x4834: return r4834;
|
||||
|
||||
//====================
|
||||
//real-time clock unit
|
||||
//====================
|
||||
|
||||
case 0x4840: return r4840;
|
||||
case 0x4841: {
|
||||
if(rtc_state == RTCS_Inactive || rtc_state == RTCS_ModeSelect) return 0x00;
|
||||
|
||||
r4842 = 0x80;
|
||||
uint8 data = memory::cartrtc.read(rtc_index);
|
||||
rtc_index = (rtc_index + 1) & 15;
|
||||
return data;
|
||||
}
|
||||
case 0x4842: {
|
||||
uint8 status = r4842;
|
||||
r4842 &= 0x7f;
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
return cpu.regs.mdr;
|
||||
}
|
||||
|
||||
void SPC7110::mmio_write(uint addr, uint8 data) {
|
||||
addr &= 0xffff;
|
||||
|
||||
switch(addr) {
|
||||
//==================
|
||||
//decompression unit
|
||||
//==================
|
||||
|
||||
case 0x4801: r4801 = data; break;
|
||||
case 0x4802: r4802 = data; break;
|
||||
case 0x4803: r4803 = data; break;
|
||||
case 0x4804: r4804 = data; break;
|
||||
case 0x4805: r4805 = data; break;
|
||||
case 0x4806: {
|
||||
r4806 = data;
|
||||
|
||||
unsigned table = (r4801 + (r4802 << 8) + (r4803 << 16));
|
||||
unsigned index = (r4804 << 2);
|
||||
unsigned length = (r4809 + (r480a << 8));
|
||||
unsigned addr = datarom_addr(table + index);
|
||||
unsigned mode = (memory::cartrom.read(addr + 0));
|
||||
unsigned offset = (memory::cartrom.read(addr + 1) << 16)
|
||||
+ (memory::cartrom.read(addr + 2) << 8)
|
||||
+ (memory::cartrom.read(addr + 3) << 0);
|
||||
|
||||
decomp.init(mode, offset, (r4805 + (r4806 << 8)) << mode);
|
||||
r480c = 0x80;
|
||||
} break;
|
||||
|
||||
case 0x4807: r4807 = data; break;
|
||||
case 0x4808: r4808 = data; break;
|
||||
case 0x4809: r4809 = data; break;
|
||||
case 0x480a: r480a = data; break;
|
||||
case 0x480b: r480b = data; break;
|
||||
|
||||
//==============
|
||||
//data port unit
|
||||
//==============
|
||||
|
||||
case 0x4811: r4811 = data; r481x |= 0x01; break;
|
||||
case 0x4812: r4812 = data; r481x |= 0x02; break;
|
||||
case 0x4813: r4813 = data; r481x |= 0x04; break;
|
||||
case 0x4814: {
|
||||
r4814 = data;
|
||||
r4814_latch = true;
|
||||
if(!r4815_latch) break;
|
||||
if(!(r4818 & 2)) break;
|
||||
if(r4818 & 0x10) break;
|
||||
|
||||
if((r4818 & 0x60) == 0x20) {
|
||||
unsigned increment = data_adjust() & 0xff;
|
||||
if(r4818 & 8) increment = (int8)increment; //8-bit sign extend
|
||||
set_data_pointer(data_pointer() + increment);
|
||||
} else if((r4818 & 0x60) == 0x40) {
|
||||
unsigned increment = data_adjust();
|
||||
if(r4818 & 8) increment = (int16)increment; //16-bit sign extend
|
||||
set_data_pointer(data_pointer() + increment);
|
||||
}
|
||||
} break;
|
||||
case 0x4815: {
|
||||
r4815 = data;
|
||||
r4815_latch = true;
|
||||
if(!r4814_latch) break;
|
||||
if(!(r4818 & 2)) break;
|
||||
if(r4818 & 0x10) break;
|
||||
|
||||
if((r4818 & 0x60) == 0x20) {
|
||||
unsigned increment = data_adjust() & 0xff;
|
||||
if(r4818 & 8) increment = (int8)increment; //8-bit sign extend
|
||||
set_data_pointer(data_pointer() + increment);
|
||||
} else if((r4818 & 0x60) == 0x40) {
|
||||
unsigned increment = data_adjust();
|
||||
if(r4818 & 8) increment = (int16)increment; //16-bit sign extend
|
||||
set_data_pointer(data_pointer() + increment);
|
||||
}
|
||||
} break;
|
||||
case 0x4816: r4816 = data; break;
|
||||
case 0x4817: r4817 = data; break;
|
||||
case 0x4818: {
|
||||
if(r481x != 0x07) break;
|
||||
|
||||
r4818 = data;
|
||||
r4814_latch = r4815_latch = false;
|
||||
} break;
|
||||
|
||||
//=========
|
||||
//math unit
|
||||
//=========
|
||||
|
||||
case 0x4820: r4820 = data; break;
|
||||
case 0x4821: r4821 = data; break;
|
||||
case 0x4822: r4822 = data; break;
|
||||
case 0x4823: r4823 = data; break;
|
||||
case 0x4824: r4824 = data; break;
|
||||
case 0x4825: {
|
||||
r4825 = data;
|
||||
|
||||
if(r482e & 1) {
|
||||
//signed 16-bit x 16-bit multiplication
|
||||
int16 r0 = (int16)(r4824 + (r4825 << 8));
|
||||
int16 r1 = (int16)(r4820 + (r4821 << 8));
|
||||
|
||||
signed result = r0 * r1;
|
||||
r4828 = result;
|
||||
r4829 = result >> 8;
|
||||
r482a = result >> 16;
|
||||
r482b = result >> 24;
|
||||
} else {
|
||||
//unsigned 16-bit x 16-bit multiplication
|
||||
uint16 r0 = (uint16)(r4824 + (r4825 << 8));
|
||||
uint16 r1 = (uint16)(r4820 + (r4821 << 8));
|
||||
|
||||
unsigned result = r0 * r1;
|
||||
r4828 = result;
|
||||
r4829 = result >> 8;
|
||||
r482a = result >> 16;
|
||||
r482b = result >> 24;
|
||||
}
|
||||
|
||||
r482f = 0x80;
|
||||
} break;
|
||||
case 0x4826: r4826 = data; break;
|
||||
case 0x4827: {
|
||||
r4827 = data;
|
||||
|
||||
if(r482e & 1) {
|
||||
//signed 32-bit x 16-bit division
|
||||
int32 dividend = (int32)(r4820 + (r4821 << 8) + (r4822 << 16) + (r4823 << 24));
|
||||
int16 divisor = (int16)(r4826 + (r4827 << 8));
|
||||
|
||||
int32 quotient;
|
||||
int16 remainder;
|
||||
|
||||
if(divisor) {
|
||||
quotient = (int32)(dividend / divisor);
|
||||
remainder = (int32)(dividend % divisor);
|
||||
} else {
|
||||
//illegal division by zero
|
||||
quotient = 0;
|
||||
remainder = dividend & 0xffff;
|
||||
}
|
||||
|
||||
r4828 = quotient;
|
||||
r4829 = quotient >> 8;
|
||||
r482a = quotient >> 16;
|
||||
r482b = quotient >> 24;
|
||||
|
||||
r482c = remainder;
|
||||
r482d = remainder >> 8;
|
||||
} else {
|
||||
//unsigned 32-bit x 16-bit division
|
||||
uint32 dividend = (uint32)(r4820 + (r4821 << 8) + (r4822 << 16) + (r4823 << 24));
|
||||
uint16 divisor = (uint16)(r4826 + (r4827 << 8));
|
||||
|
||||
uint32 quotient;
|
||||
uint16 remainder;
|
||||
|
||||
if(divisor) {
|
||||
quotient = (uint32)(dividend / divisor);
|
||||
remainder = (uint16)(dividend % divisor);
|
||||
} else {
|
||||
//illegal division by zero
|
||||
quotient = 0;
|
||||
remainder = dividend & 0xffff;
|
||||
}
|
||||
|
||||
r4828 = quotient;
|
||||
r4829 = quotient >> 8;
|
||||
r482a = quotient >> 16;
|
||||
r482b = quotient >> 24;
|
||||
|
||||
r482c = remainder;
|
||||
r482d = remainder >> 8;
|
||||
}
|
||||
|
||||
r482f = 0x80;
|
||||
} break;
|
||||
|
||||
case 0x482e: {
|
||||
//reset math unit
|
||||
r4820 = r4821 = r4822 = r4823 = 0;
|
||||
r4824 = r4825 = r4826 = r4827 = 0;
|
||||
r4828 = r4829 = r482a = r482b = 0;
|
||||
r482c = r482d = 0;
|
||||
|
||||
r482e = data;
|
||||
} break;
|
||||
|
||||
//===================
|
||||
//memory mapping unit
|
||||
//===================
|
||||
|
||||
case 0x4830: r4830 = data; break;
|
||||
|
||||
case 0x4831: {
|
||||
r4831 = data;
|
||||
dx_offset = datarom_addr((data & 7) * 0x100000);
|
||||
} break;
|
||||
|
||||
case 0x4832: {
|
||||
r4832 = data;
|
||||
ex_offset = datarom_addr((data & 7) * 0x100000);
|
||||
} break;
|
||||
|
||||
case 0x4833: {
|
||||
r4833 = data;
|
||||
fx_offset = datarom_addr((data & 7) * 0x100000);
|
||||
} break;
|
||||
|
||||
case 0x4834: r4834 = data; break;
|
||||
|
||||
//====================
|
||||
//real-time clock unit
|
||||
//====================
|
||||
|
||||
case 0x4840: {
|
||||
r4840 = data;
|
||||
if(!(r4840 & 1)) {
|
||||
//disable RTC
|
||||
rtc_state = RTCS_Inactive;
|
||||
update_time();
|
||||
} else {
|
||||
//enable RTC
|
||||
r4842 = 0x80;
|
||||
rtc_state = RTCS_ModeSelect;
|
||||
}
|
||||
} break;
|
||||
|
||||
case 0x4841: {
|
||||
r4841 = data;
|
||||
|
||||
switch(rtc_state) {
|
||||
case RTCS_ModeSelect: {
|
||||
if(data == RTCM_Linear || data == RTCM_Indexed) {
|
||||
r4842 = 0x80;
|
||||
rtc_state = RTCS_IndexSelect;
|
||||
rtc_mode = (RTC_Mode)data;
|
||||
rtc_index = 0;
|
||||
}
|
||||
} break;
|
||||
|
||||
case RTCS_IndexSelect: {
|
||||
r4842 = 0x80;
|
||||
rtc_index = data & 15;
|
||||
if(rtc_mode == RTCM_Linear) rtc_state = RTCS_Write;
|
||||
} break;
|
||||
|
||||
case RTCS_Write: {
|
||||
r4842 = 0x80;
|
||||
|
||||
//control register 0
|
||||
if(rtc_index == 13) {
|
||||
//increment second counter
|
||||
if(data & 2) update_time(+1);
|
||||
|
||||
//round minute counter
|
||||
if(data & 8) {
|
||||
update_time();
|
||||
|
||||
unsigned second = memory::cartrtc.read( 0) + memory::cartrtc.read( 1) * 10;
|
||||
//clear seconds
|
||||
memory::cartrtc.write(0, 0);
|
||||
memory::cartrtc.write(1, 0);
|
||||
|
||||
if(second >= 30) update_time(+60);
|
||||
}
|
||||
}
|
||||
|
||||
//control register 2
|
||||
if(rtc_index == 15) {
|
||||
//disable timer and clear second counter
|
||||
if((data & 1) && !(memory::cartrtc.read(15) & 1)) {
|
||||
update_time();
|
||||
|
||||
//clear seconds
|
||||
memory::cartrtc.write(0, 0);
|
||||
memory::cartrtc.write(1, 0);
|
||||
}
|
||||
|
||||
//disable timer
|
||||
if((data & 2) && !(memory::cartrtc.read(15) & 2)) {
|
||||
update_time();
|
||||
}
|
||||
}
|
||||
|
||||
memory::cartrtc.write(rtc_index, data & 15);
|
||||
rtc_index = (rtc_index + 1) & 15;
|
||||
} break;
|
||||
} //switch(rtc_state)
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8 SPC7110::read(uint addr) {
|
||||
//$[00-0f|80-8f]:[8000-ffff], $[c0-cf]:[0000-ffff] mapped directly to memory::cartrom
|
||||
|
||||
if((addr & 0xffe000) == 0x006000 || (addr & 0xffe000) == 0x306000) {
|
||||
//$[00|30]:[6000-7fff]
|
||||
return memory::cartram.read(addr & 0x1fff);
|
||||
}
|
||||
|
||||
if((addr & 0xff0000) == 0x500000) {
|
||||
//$[50]:[0000-ffff]
|
||||
return mmio_read(0x4800);
|
||||
}
|
||||
|
||||
if((addr & 0xf00000) == 0xd00000) {
|
||||
//$[d0-df]:[0000-ffff]
|
||||
return memory::cartrom.read(dx_offset + (addr & 0x0fffff));
|
||||
}
|
||||
|
||||
if((addr & 0xf00000) == 0xe00000) {
|
||||
//$[e0-ef]:[0000-ffff]
|
||||
return memory::cartrom.read(ex_offset + (addr & 0x0fffff));
|
||||
}
|
||||
|
||||
if((addr & 0xf00000) == 0xf00000) {
|
||||
//$[f0-ff]:[0000-ffff]
|
||||
return memory::cartrom.read(fx_offset + (addr & 0x0fffff));
|
||||
}
|
||||
|
||||
return cpu.regs.mdr;
|
||||
}
|
||||
|
||||
void SPC7110::write(uint addr, uint8 data) {
|
||||
if((addr & 0xffe000) == 0x006000 || (addr & 0xffe000) == 0x306000) {
|
||||
//$[00|30]:[6000-7fff]
|
||||
if(r4830 & 0x80) memory::cartram.write(addr & 0x1fff, data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SPC7110::SPC7110() {
|
||||
}
|
133
src/chip/spc7110/spc7110.hpp
Normal file
133
src/chip/spc7110/spc7110.hpp
Normal file
@@ -0,0 +1,133 @@
|
||||
/*****
|
||||
* SPC7110 emulator - version 0.03 (2008-08-10)
|
||||
* Copyright (c) 2008, byuu and neviksti
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* The software is provided "as is" and the author disclaims all warranties
|
||||
* with regard to this software including all implied warranties of
|
||||
* merchantibility and fitness, in no event shall the author be liable for
|
||||
* any special, direct, indirect, or consequential damages or any damages
|
||||
* whatsoever resulting from loss of use, data or profits, whether in an
|
||||
* action of contract, negligence or other tortious action, arising out of
|
||||
* or in connection with the use or performance of this software.
|
||||
*****/
|
||||
|
||||
#include "decomp.hpp"
|
||||
|
||||
class SPC7110 : public MMIO, public Memory {
|
||||
public:
|
||||
void init();
|
||||
void enable();
|
||||
void power();
|
||||
void reset();
|
||||
|
||||
unsigned datarom_addr(unsigned addr);
|
||||
|
||||
unsigned data_pointer();
|
||||
unsigned data_adjust();
|
||||
unsigned data_increment();
|
||||
void set_data_pointer(unsigned addr);
|
||||
void set_data_adjust(unsigned addr);
|
||||
|
||||
void update_time(int offset = 0);
|
||||
time_t create_time();
|
||||
|
||||
uint8 mmio_read (uint addr);
|
||||
void mmio_write(uint addr, uint8 data);
|
||||
|
||||
uint8 read (uint addr);
|
||||
void write(uint addr, uint8 data);
|
||||
|
||||
//spc7110decomp
|
||||
void decomp_init();
|
||||
uint8 decomp_read();
|
||||
|
||||
SPC7110();
|
||||
|
||||
private:
|
||||
//==================
|
||||
//decompression unit
|
||||
//==================
|
||||
uint8 r4801; //compression table low
|
||||
uint8 r4802; //compression table high
|
||||
uint8 r4803; //compression table bank
|
||||
uint8 r4804; //compression table index
|
||||
uint8 r4805; //decompression buffer index low
|
||||
uint8 r4806; //decompression buffer index high
|
||||
uint8 r4807; //???
|
||||
uint8 r4808; //???
|
||||
uint8 r4809; //compression length low
|
||||
uint8 r480a; //compression length high
|
||||
uint8 r480b; //decompression control register
|
||||
uint8 r480c; //decompression status
|
||||
|
||||
SPC7110Decomp decomp;
|
||||
|
||||
//==============
|
||||
//data port unit
|
||||
//==============
|
||||
uint8 r4811; //data pointer low
|
||||
uint8 r4812; //data pointer high
|
||||
uint8 r4813; //data pointer bank
|
||||
uint8 r4814; //data adjust low
|
||||
uint8 r4815; //data adjust high
|
||||
uint8 r4816; //data increment low
|
||||
uint8 r4817; //data increment high
|
||||
uint8 r4818; //data port control register
|
||||
|
||||
uint8 r481x;
|
||||
|
||||
bool r4814_latch;
|
||||
bool r4815_latch;
|
||||
|
||||
//=========
|
||||
//math unit
|
||||
//=========
|
||||
uint8 r4820; //16-bit multiplicand B0, 32-bit dividend B0
|
||||
uint8 r4821; //16-bit multiplicand B1, 32-bit dividend B1
|
||||
uint8 r4822; //32-bit dividend B2
|
||||
uint8 r4823; //32-bit dividend B3
|
||||
uint8 r4824; //16-bit multiplier B0
|
||||
uint8 r4825; //16-bit multiplier B1
|
||||
uint8 r4826; //16-bit divisor B0
|
||||
uint8 r4827; //16-bit divisor B1
|
||||
uint8 r4828; //32-bit product B0, 32-bit quotient B0
|
||||
uint8 r4829; //32-bit product B1, 32-bit quotient B1
|
||||
uint8 r482a; //32-bit product B2, 32-bit quotient B2
|
||||
uint8 r482b; //32-bit product B3, 32-bit quotient B3
|
||||
uint8 r482c; //16-bit remainder B0
|
||||
uint8 r482d; //16-bit remainder B1
|
||||
uint8 r482e; //math control register
|
||||
uint8 r482f; //math status
|
||||
|
||||
//===================
|
||||
//memory mapping unit
|
||||
//===================
|
||||
uint8 r4830; //SRAM write enable
|
||||
uint8 r4831; //$[d0-df]:[0000-ffff] mapping
|
||||
uint8 r4832; //$[e0-ef]:[0000-ffff] mapping
|
||||
uint8 r4833; //$[f0-ff]:[0000-ffff] mapping
|
||||
uint8 r4834; //???
|
||||
|
||||
unsigned dx_offset;
|
||||
unsigned ex_offset;
|
||||
unsigned fx_offset;
|
||||
|
||||
//====================
|
||||
//real-time clock unit
|
||||
//====================
|
||||
uint8 r4840; //RTC latch
|
||||
uint8 r4841; //RTC index/data port
|
||||
uint8 r4842; //RTC status
|
||||
|
||||
enum RTC_State { RTCS_Inactive, RTCS_ModeSelect, RTCS_IndexSelect, RTCS_Write } rtc_state;
|
||||
enum RTC_Mode { RTCM_Linear = 0x03, RTCM_Indexed = 0x0c } rtc_mode;
|
||||
unsigned rtc_index;
|
||||
|
||||
static const unsigned months[12];
|
||||
};
|
||||
|
||||
extern SPC7110 spc7110;
|
@@ -1,189 +1,213 @@
|
||||
/*
|
||||
S-RTC chip emulation
|
||||
Used by Hudson Soft in Dai Kaijuu Monogatari II and Far East of Eden Zero.
|
||||
Currently, only the former is supported by bsnes.
|
||||
|
||||
Original S-RTC emulation code via John Weidman/SNES9x
|
||||
Rewritten for compatibility with bsnes via byuu
|
||||
|
||||
The S-RTC is a real-time clock chip that was added to the above two carts
|
||||
to allow the games to maintain the current time, even when the game was not
|
||||
powered on. Thus allowing special events at certain times, and on certain
|
||||
dates. Hudson Soft called this the PLG (Player's Life Gameplay System).
|
||||
|
||||
This chip is a special case to the term 'emulation' itself.
|
||||
There are a few different ways to go about emulating this chip, and each
|
||||
result in a different style of emulation.
|
||||
|
||||
The first is to simply return the current PC system time when the S-RTC is
|
||||
read from. This emulates the original S-RTC in the sense that it always
|
||||
returns the true current time, ignoring the speed that the SNES itself is
|
||||
running at. The downside to this method is that you lose the ability to set
|
||||
the time to whatever you choose inside the game itself. It will always return
|
||||
the true time, regardless. This can be overcome by changing the PC system time,
|
||||
which actually adds a greater degree of control over event timing, very useful
|
||||
for emulation. It also has a timeshifting flaw discussed below.
|
||||
|
||||
The second is to run the S-RTC relative to the SNES speed. This means that
|
||||
if the emulator is sped up (via fast forward key, frameskipping, etc), or
|
||||
slowed down (via slowdown key, system bottlenecking, etc); the time increments
|
||||
slower, thus ~60 frames on the SNES equal one second. Without this, timeshifting
|
||||
will occur between the S-RTC and the real SNES.
|
||||
|
||||
The third and final method is to save a copy of the local system time when the
|
||||
S-RTC is initially set, and compare the current system time against this value
|
||||
when setting the S-RTC time. This overcomes the first methods' shortcoming of
|
||||
not allowing the player to set the time in-game, however a new problem arises.
|
||||
You now have to save the time when the RTC was initially set to both savestates
|
||||
and to save-game data. This would require an extra file, or the breaking of
|
||||
perhaps the only standard format (.srm savegame backups) in the entire SNES
|
||||
emulation scene. You also give up the control of being able to override the
|
||||
RTC clock at will via the PC system time outside of emulation.
|
||||
The first method has another advantage over the third: Dai Kaijuu Monogatari II
|
||||
only allows dates in the range of the years 1996-2199. The first method gets
|
||||
around this limitation. But who knows, maybe it will break something in the
|
||||
game if the date exceeds 2199... I guess we'll worry about that in two hundred
|
||||
years from now.
|
||||
|
||||
For my implementation, I chose to go with the first method. Both for simplicity
|
||||
and because I did not wish to create a new method for saving the system time
|
||||
whenever the RTC is set.
|
||||
*/
|
||||
|
||||
#include "../../base.h"
|
||||
|
||||
void SRTC::set_time() {
|
||||
time_t rawtime;
|
||||
tm *t;
|
||||
::time(&rawtime);
|
||||
t = localtime(&rawtime);
|
||||
|
||||
//see srtc.h for format of srtc.data[]
|
||||
srtc.data[0] = t->tm_sec % 10;
|
||||
srtc.data[1] = t->tm_sec / 10;
|
||||
srtc.data[2] = t->tm_min % 10;
|
||||
srtc.data[3] = t->tm_min / 10;
|
||||
srtc.data[4] = t->tm_hour % 10;
|
||||
srtc.data[5] = t->tm_hour / 10;
|
||||
srtc.data[6] = t->tm_mday % 10;
|
||||
srtc.data[7] = t->tm_mday / 10;
|
||||
srtc.data[8] = t->tm_mon + 1;
|
||||
srtc.data[9] = t->tm_year % 10;
|
||||
srtc.data[10] = (t->tm_year / 10) % 10;
|
||||
srtc.data[11] = 9 + (t->tm_year / 100);
|
||||
srtc.data[12] = t->tm_wday;
|
||||
}
|
||||
|
||||
void SRTC::init() {}
|
||||
|
||||
void SRTC::enable() {
|
||||
memory::mmio.map(0x2800, *this);
|
||||
memory::mmio.map(0x2801, *this);
|
||||
}
|
||||
|
||||
void SRTC::power() {
|
||||
memset(&srtc, 0, sizeof(srtc));
|
||||
reset();
|
||||
}
|
||||
|
||||
void SRTC::reset() {
|
||||
srtc.index = -1;
|
||||
srtc.mode = SRTC_READ;
|
||||
}
|
||||
|
||||
uint8 SRTC::mmio_read(uint addr) {
|
||||
switch(addr & 0xffff) {
|
||||
|
||||
case 0x2800: {
|
||||
if(srtc.mode == SRTC_READ) {
|
||||
if(srtc.index < 0) {
|
||||
set_time();
|
||||
srtc.index++;
|
||||
return 0x0f; //send start message
|
||||
} else if(srtc.index > MAX_SRTC_INDEX) {
|
||||
srtc.index = -1;
|
||||
return 0x0f; //send finished message
|
||||
} else {
|
||||
return srtc.data[srtc.index++];
|
||||
}
|
||||
} else {
|
||||
return 0x00;
|
||||
}
|
||||
} break;
|
||||
|
||||
case 0x2801: {
|
||||
} break;
|
||||
|
||||
}
|
||||
|
||||
return cpu.regs.mdr;
|
||||
}
|
||||
|
||||
//Please see notes above about the implementation of the S-RTC
|
||||
//Writes are stored the srtc.data[] array, but they are ignored
|
||||
//as reads will refresh the data array with the current system
|
||||
//time. The write method is only here for the sake of faux
|
||||
//emulation of the real hardware.
|
||||
void SRTC::mmio_write(uint addr, uint8 data) {
|
||||
switch(addr & 0xffff) {
|
||||
|
||||
case 0x2800: {
|
||||
} break;
|
||||
|
||||
case 0x2801: {
|
||||
data &= 0x0f; //only the low four bits are used
|
||||
|
||||
if(data >= 0x0d) {
|
||||
switch(data) {
|
||||
case 0x0d:
|
||||
srtc.mode = SRTC_READ;
|
||||
srtc.index = -1;
|
||||
break;
|
||||
case 0x0e:
|
||||
srtc.mode = SRTC_COMMAND;
|
||||
break;
|
||||
case 0x0f:
|
||||
//unknown behaviour
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(srtc.mode == SRTC_WRITE) {
|
||||
if(srtc.index >= 0 && srtc.index < MAX_SRTC_INDEX) {
|
||||
srtc.data[srtc.index++] = data;
|
||||
|
||||
if(srtc.index == MAX_SRTC_INDEX) {
|
||||
//all S-RTC data has been loaded by program
|
||||
srtc.data[srtc.index++] = 0x00; //day_of_week
|
||||
}
|
||||
}
|
||||
} else if(srtc.mode == SRTC_COMMAND) {
|
||||
switch(data) {
|
||||
case SRTC_COMMAND_CLEAR:
|
||||
memset(srtc.data, 0, MAX_SRTC_INDEX + 1);
|
||||
srtc.index = -1;
|
||||
srtc.mode = SRTC_READY;
|
||||
break;
|
||||
case SRTC_COMMAND_WRITE:
|
||||
srtc.index = 0;
|
||||
srtc.mode = SRTC_WRITE;
|
||||
break;
|
||||
default:
|
||||
//unknown behaviour
|
||||
srtc.mode = SRTC_READY;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if(srtc.mode == SRTC_READ) {
|
||||
//ignore writes while in read mode
|
||||
} else if(srtc.mode == SRTC_READY) {
|
||||
//unknown behaviour
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
SRTC::SRTC() {}
|
||||
#include <../base.hpp>
|
||||
|
||||
const unsigned SRTC::months[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
||||
|
||||
void SRTC::init() {
|
||||
}
|
||||
|
||||
void SRTC::enable() {
|
||||
memory::mmio.map(0x2800, *this);
|
||||
memory::mmio.map(0x2801, *this);
|
||||
}
|
||||
|
||||
void SRTC::power() {
|
||||
reset();
|
||||
}
|
||||
|
||||
void SRTC::reset() {
|
||||
rtc_mode = RTCM_Read;
|
||||
rtc_index = -1;
|
||||
update_time();
|
||||
}
|
||||
|
||||
void SRTC::update_time() {
|
||||
time_t rtc_time;
|
||||
rtc_time = memory::cartrtc.read(16);
|
||||
rtc_time |= memory::cartrtc.read(17) << 8;
|
||||
rtc_time |= memory::cartrtc.read(18) << 16;
|
||||
rtc_time |= memory::cartrtc.read(19) << 24;
|
||||
|
||||
time_t current_time = time(0);
|
||||
if(current_time > rtc_time) {
|
||||
unsigned second = memory::cartrtc.read( 0) + memory::cartrtc.read( 1) * 10;
|
||||
unsigned minute = memory::cartrtc.read( 2) + memory::cartrtc.read( 3) * 10;
|
||||
unsigned hour = memory::cartrtc.read( 4) + memory::cartrtc.read( 5) * 10;
|
||||
unsigned day = memory::cartrtc.read( 6) + memory::cartrtc.read( 7) * 10;
|
||||
unsigned month = memory::cartrtc.read( 8);
|
||||
unsigned year = memory::cartrtc.read( 9) + memory::cartrtc.read(10) * 10 + memory::cartrtc.read(11) * 100;
|
||||
unsigned weekday = memory::cartrtc.read(12);
|
||||
|
||||
day--;
|
||||
month--;
|
||||
year += 1000;
|
||||
|
||||
second += (unsigned)(current_time - rtc_time);
|
||||
|
||||
while(second >= 60) {
|
||||
second -= 60;
|
||||
|
||||
minute++;
|
||||
if(minute < 60) continue;
|
||||
minute = 0;
|
||||
|
||||
hour++;
|
||||
if(hour < 24) continue;
|
||||
hour = 0;
|
||||
|
||||
day++;
|
||||
weekday = (weekday + 1) % 7;
|
||||
unsigned days = months[month % 12];
|
||||
if(days == 28) {
|
||||
bool leapyear = false;
|
||||
if((year % 4) == 0) {
|
||||
leapyear = true;
|
||||
if((year % 100) == 0 && (year % 400) != 0) leapyear = false;
|
||||
}
|
||||
if(leapyear) days++;
|
||||
}
|
||||
if(day < days) continue;
|
||||
day = 0;
|
||||
|
||||
month++;
|
||||
if(month < 12) continue;
|
||||
month = 0;
|
||||
|
||||
year++;
|
||||
}
|
||||
|
||||
day++;
|
||||
month++;
|
||||
year -= 1000;
|
||||
|
||||
memory::cartrtc.write( 0, second % 10);
|
||||
memory::cartrtc.write( 1, second / 10);
|
||||
memory::cartrtc.write( 2, minute % 10);
|
||||
memory::cartrtc.write( 3, minute / 10);
|
||||
memory::cartrtc.write( 4, hour % 10);
|
||||
memory::cartrtc.write( 5, hour / 10);
|
||||
memory::cartrtc.write( 6, day % 10);
|
||||
memory::cartrtc.write( 7, day / 10);
|
||||
memory::cartrtc.write( 8, month);
|
||||
memory::cartrtc.write( 9, year % 10);
|
||||
memory::cartrtc.write(10, (year / 10) % 10);
|
||||
memory::cartrtc.write(11, year / 100);
|
||||
memory::cartrtc.write(12, weekday % 7);
|
||||
}
|
||||
|
||||
memory::cartrtc.write(16, current_time);
|
||||
memory::cartrtc.write(17, current_time >> 8);
|
||||
memory::cartrtc.write(18, current_time >> 16);
|
||||
memory::cartrtc.write(19, current_time >> 24);
|
||||
}
|
||||
|
||||
//returns day of week for specified date
|
||||
//eg 0 = Sunday, 1 = Monday, ... 6 = Saturday
|
||||
//usage: weekday(2008, 1, 1) returns weekday of January 1st, 2008
|
||||
unsigned SRTC::weekday(unsigned year, unsigned month, unsigned day) {
|
||||
unsigned y = 1900, m = 1; //epoch is 1900-01-01
|
||||
unsigned sum = 0; //number of days passed since epoch
|
||||
|
||||
year = max(1900, year);
|
||||
month = max(1, min(12, month));
|
||||
day = max(1, min(31, day));
|
||||
|
||||
while(y < year) {
|
||||
bool leapyear = false;
|
||||
if((y % 4) == 0) {
|
||||
leapyear = true;
|
||||
if((y % 100) == 0 && (y % 400) != 0) leapyear = false;
|
||||
}
|
||||
sum += leapyear ? 366 : 365;
|
||||
y++;
|
||||
}
|
||||
|
||||
while(m < month) {
|
||||
unsigned days = months[m - 1];
|
||||
if(days == 28) {
|
||||
bool leapyear = false;
|
||||
if((y % 4) == 0) {
|
||||
leapyear = true;
|
||||
if((y % 100) == 0 && (y % 400) != 0) leapyear = false;
|
||||
}
|
||||
if(leapyear) days++;
|
||||
}
|
||||
sum += days;
|
||||
m++;
|
||||
}
|
||||
|
||||
sum += day - 1;
|
||||
return (sum + 1) % 7; //1900-01-01 was a Monday
|
||||
}
|
||||
|
||||
uint8 SRTC::mmio_read(unsigned addr) {
|
||||
addr &= 0xffff;
|
||||
|
||||
if(addr == 0x2800) {
|
||||
if(rtc_mode != RTCM_Read) return 0x00;
|
||||
|
||||
if(rtc_index < 0) {
|
||||
update_time();
|
||||
rtc_index++;
|
||||
return 0x0f;
|
||||
} else if(rtc_index > 12) {
|
||||
rtc_index = -1;
|
||||
return 0x0f;
|
||||
} else {
|
||||
return memory::cartrtc.read(rtc_index++);
|
||||
}
|
||||
}
|
||||
|
||||
return cpu.regs.mdr;
|
||||
}
|
||||
|
||||
void SRTC::mmio_write(unsigned addr, uint8 data) {
|
||||
addr &= 0xffff;
|
||||
|
||||
if(addr == 0x2801) {
|
||||
data &= 0x0f; //only the low four bits are used
|
||||
|
||||
if(data == 0x0d) {
|
||||
rtc_mode = RTCM_Read;
|
||||
rtc_index = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if(data == 0x0e) {
|
||||
rtc_mode = RTCM_Command;
|
||||
return;
|
||||
}
|
||||
|
||||
if(data == 0x0f) return; //unknown behavior
|
||||
|
||||
if(rtc_mode == RTCM_Write) {
|
||||
if(rtc_index >= 0 && rtc_index < 12) {
|
||||
memory::cartrtc.write(rtc_index++, data);
|
||||
|
||||
if(rtc_index == 12) {
|
||||
//day of week is automatically calculated and written
|
||||
unsigned day = memory::cartrtc.read( 6) + memory::cartrtc.read( 7) * 10;
|
||||
unsigned month = memory::cartrtc.read( 8);
|
||||
unsigned year = memory::cartrtc.read( 9) + memory::cartrtc.read(10) * 10 + memory::cartrtc.read(11) * 100;
|
||||
year += 1000;
|
||||
|
||||
memory::cartrtc.write(rtc_index++, weekday(year, month, day));
|
||||
}
|
||||
}
|
||||
} else if(rtc_mode == RTCM_Command) {
|
||||
if(data == 0) {
|
||||
rtc_mode = RTCM_Write;
|
||||
rtc_index = 0;
|
||||
} else if(data == 4) {
|
||||
rtc_mode = RTCM_Ready;
|
||||
rtc_index = -1;
|
||||
for(unsigned i = 0; i < 13; i++) memory::cartrtc.write(i, 0);
|
||||
} else {
|
||||
//unknown behavior
|
||||
rtc_mode = RTCM_Ready;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SRTC::SRTC() {
|
||||
}
|
||||
|
@@ -1,52 +0,0 @@
|
||||
class SRTC : public MMIO {
|
||||
public:
|
||||
enum { MAX_SRTC_INDEX = 0x0c };
|
||||
|
||||
enum {
|
||||
SRTC_READ = 0,
|
||||
SRTC_WRITE,
|
||||
SRTC_COMMAND,
|
||||
SRTC_READY
|
||||
};
|
||||
|
||||
enum {
|
||||
SRTC_COMMAND_WRITE = 0,
|
||||
SRTC_COMMAND_CLEAR = 4
|
||||
};
|
||||
|
||||
/******************************
|
||||
[srtc.data structure]
|
||||
Index Description Range
|
||||
----- ----------- -----
|
||||
0 Seconds low 0-9
|
||||
1 Seconds high 0-5
|
||||
2 Minutes low 0-9
|
||||
3 Minutes high 0-5
|
||||
4 Hour low 0-9
|
||||
5 Hour high 0-2
|
||||
6 Day low 0-9
|
||||
7 Day high 0-3
|
||||
8 Month 1-12
|
||||
9 Year ones 0-9
|
||||
10 Year tens 0-9
|
||||
11 Year hundreds 9-11 (9=19xx, 10=20xx, 11=21xx)
|
||||
12 Day of week 0-6 (0=Sunday, ...)
|
||||
******************************/
|
||||
struct {
|
||||
int8 index;
|
||||
uint8 mode;
|
||||
uint8 data[MAX_SRTC_INDEX + 1];
|
||||
} srtc;
|
||||
void set_time();
|
||||
void init();
|
||||
void enable();
|
||||
void power();
|
||||
void reset();
|
||||
|
||||
uint8 mmio_read (uint addr);
|
||||
void mmio_write(uint addr, uint8 data);
|
||||
|
||||
SRTC();
|
||||
};
|
||||
|
||||
extern SRTC srtc;
|
22
src/chip/srtc/srtc.hpp
Normal file
22
src/chip/srtc/srtc.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
class SRTC : public MMIO {
|
||||
public:
|
||||
void update_time();
|
||||
unsigned weekday(unsigned year, unsigned month, unsigned day);
|
||||
|
||||
void init();
|
||||
void enable();
|
||||
void power();
|
||||
void reset();
|
||||
|
||||
uint8 mmio_read (unsigned addr);
|
||||
void mmio_write(unsigned addr, uint8 data);
|
||||
|
||||
SRTC();
|
||||
|
||||
private:
|
||||
static const unsigned months[12];
|
||||
enum RTC_Mode { RTCM_Ready, RTCM_Command, RTCM_Read, RTCM_Write } rtc_mode;
|
||||
signed rtc_index;
|
||||
};
|
||||
|
||||
extern SRTC srtc;
|
@@ -1,7 +1,7 @@
|
||||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define ST010_CPP
|
||||
|
||||
#include "st010_data.h"
|
||||
#include "st010_data.hpp"
|
||||
#include "st010_op.cpp"
|
||||
|
||||
int16 ST010::sin(int16 theta) {
|
||||
|
@@ -1,130 +1,105 @@
|
||||
#include <../base.hpp>
|
||||
|
||||
namespace config {
|
||||
|
||||
configuration& config() {
|
||||
static configuration config;
|
||||
return config;
|
||||
}
|
||||
|
||||
integral_setting File::autodetect_type(config(), "file.autodetect_type",
|
||||
"Auto-detect file type by inspecting file header, rather than by file extension.\n"
|
||||
"In other words, if a .zip file is renamed to .smc, it will still be correctly\n"
|
||||
"identified as a .zip file. However, there is an infinitesimal (1:~500,000,000)\n"
|
||||
"chance of a false detection when loading an uncompressed image file, if this\n"
|
||||
"option is enabled.",
|
||||
integral_setting::boolean, false);
|
||||
|
||||
integral_setting File::bypass_patch_crc32(config(), "file.bypass_patch_crc32",
|
||||
"UPS patches contain CRC32s to validate that a patch was applied successfully.\n"
|
||||
"By default, if this validation fails, said patch will not be applied.\n"
|
||||
"Setting this option to true will bypass the validation,\n"
|
||||
"which may or may not result in a working image.\n"
|
||||
"Enabling this option is strongly discouraged.",
|
||||
integral_setting::boolean, false);
|
||||
}
|
||||
|
||||
string file_updatepath(const char *req_file, const char *req_path) {
|
||||
string file(req_file);
|
||||
string filepath(const char *filename, const char *pathname) {
|
||||
//if no pathname, return filename as-is
|
||||
string file(filename);
|
||||
replace(file, "\\", "/");
|
||||
if(!req_path || strlen(req_path) == 0) { return file; }
|
||||
if(!pathname || !*pathname) return file;
|
||||
|
||||
string path(req_path);
|
||||
//ensure path ends with trailing '/'
|
||||
string path(pathname);
|
||||
replace(path, "\\", "/");
|
||||
if(!strend(path, "/")) { strcat(path, "/"); }
|
||||
if(!strend(path, "/")) strcat(path, "/");
|
||||
|
||||
//replace relative path with absolute path
|
||||
if(strbegin(path, "./")) {
|
||||
ltrim(path(), "./");
|
||||
string temp;
|
||||
strcpy(temp, config::path.base);
|
||||
strcat(temp, path);
|
||||
strcpy(path, temp);
|
||||
ltrim(path, "./");
|
||||
path = string() << config::path.base << path;
|
||||
}
|
||||
|
||||
//remove folder part of filename
|
||||
lstring part;
|
||||
split(part, "/", file);
|
||||
strcat(path, part[count(part) - 1]);
|
||||
return path;
|
||||
return path << part[count(part) - 1];
|
||||
}
|
||||
|
||||
integral_setting File::autodetect_type(config(), "file.autodetect_type",
|
||||
"Auto-detect file type by inspecting file header, rather than by file extension.\n"
|
||||
"In other words, if a .zip file is renamed to .smc, it will still be correctly "
|
||||
"identified as a .zip file. However, there is an infinitesimal (1:~500,000,000) "
|
||||
"chance of a false detection when loading an uncompressed image file, if this "
|
||||
"option is enabled.",
|
||||
integral_setting::boolean, false);
|
||||
|
||||
integral_setting File::bypass_patch_crc32(config(), "file.bypass_patch_crc32",
|
||||
"UPS patches contain CRC32s to validate that a patch was applied successfully.\n"
|
||||
"By default, if this validation fails, said patch will not be applied. "
|
||||
"Setting this option to true will bypass the validation, "
|
||||
"which may or may not result in a working image. "
|
||||
"Thus, enabling this option is strongly discouraged.",
|
||||
integral_setting::boolean, false);
|
||||
|
||||
string_setting Path::base("path.base", "Path that bsnes resides in", "");
|
||||
string_setting Path::user("path.user", "Path to user folder", "");
|
||||
|
||||
string_setting Path::rom(config(), "path.rom",
|
||||
"Default path to look for ROM files in (\"\" = use default directory)", "");
|
||||
string_setting Path::patch(config(), "path.patch",
|
||||
"Default path to look for ROM files in (\"\" = use default directory)", "");
|
||||
string_setting Path::save(config(), "path.save",
|
||||
"Default path for all save RAM files (\"\" = use current directory)", "");
|
||||
string_setting Path::patch(config(), "path.patch",
|
||||
"Default path for all UPS patch files (\"\" = use current directory)", "");
|
||||
string_setting Path::save(config(), "path.save",
|
||||
"Default path for all save RAM files (\"\" = use current directory)", "");
|
||||
string_setting Path::cheat(config(), "path.cheat",
|
||||
"Default path for all cheat files (\"\" = use current directory)", "");
|
||||
string_setting Path::cheat(config(), "path.cheat",
|
||||
"Default path for all cheat files (\"\" = use current directory)", "");
|
||||
string_setting Path::exportdata(config(), "path.export",
|
||||
"Default path for all exported data files\n", "");
|
||||
|
||||
string_setting Path::bsx(config(), "path.bsx", "", "");
|
||||
string_setting Path::st(config(), "path.st", "", "");
|
||||
|
||||
integral_setting SNES::controller_port0(config(), "snes.controller_port_1",
|
||||
"Controller attached to SNES port 1", integral_setting::decimal, ::SNES::Input::DeviceIDJoypad1);
|
||||
integral_setting SNES::controller_port1(config(), "snes.controller_port_2",
|
||||
"Controller attached to SNES port 2", integral_setting::decimal, ::SNES::Input::DeviceIDJoypad2);
|
||||
integral_setting SNES::controller_port1(config(), "snes.controller_port1",
|
||||
"Controller attached to SNES port 1", integral_setting::decimal, ::SNES::Input::DeviceJoypad);
|
||||
integral_setting SNES::controller_port2(config(), "snes.controller_port2",
|
||||
"Controller attached to SNES port 2", integral_setting::decimal, ::SNES::Input::DeviceJoypad);
|
||||
integral_setting SNES::expansion_port(config(), "snes.expansion_port",
|
||||
"Device attached to SNES expansion port\n"
|
||||
"0 = None\n"
|
||||
"1 = Satellaview BS-X",
|
||||
integral_setting::decimal, ::SNES::ExpansionBSX);
|
||||
integral_setting SNES::region(config(), "snes.region",
|
||||
"SNES regional model\n"
|
||||
"0 = Auto-detect based on cartridge\n"
|
||||
"1 = NTSC\n"
|
||||
"2 = PAL",
|
||||
integral_setting::decimal, ::SNES::Autodetect);
|
||||
|
||||
integral_setting CPU::ntsc_clock_rate(config(), "cpu.ntsc_clock_rate",
|
||||
"NTSC S-CPU clock rate (in hz)", integral_setting::decimal, 21477272);
|
||||
integral_setting CPU::pal_clock_rate(config(), "cpu.pal_clock_rate",
|
||||
"PAL S-CPU clock rate (in hz)", integral_setting::decimal, 21281370);
|
||||
"PAL S-CPU clock rate (in hz)", integral_setting::decimal, 21281370);
|
||||
integral_setting CPU::wram_init_value(config(), "cpu.wram_init_value",
|
||||
"Value to initialize 128k WRAM to upon power cycle.\n"
|
||||
"Note that on real hardware, this value is undefined; meaning it can vary\n"
|
||||
"per power-on, and per SNES unit. Such randomness is undesirable for an\n"
|
||||
"emulator, so a static value is needed. There is also some form of pattern\n"
|
||||
"Note that on real hardware, this value is undefined; meaning it can vary "
|
||||
"per power-on, and per SNES unit. Such randomness is undesirable for an "
|
||||
"emulator, so a static value is needed. There is also some form of pattern "
|
||||
"to the randomness that has yet to be determined, which some games rely upon.\n"
|
||||
"A value of 0x55 is safe for all known commercial software, and should be used.\n"
|
||||
"However, some software written for SNES copiers, or backup units, relies on\n"
|
||||
"WRAM being initialized to 0x00; which was a side-effect of the BIOS program\n"
|
||||
"which executed on these copiers. Using 0x00 will therefore fix many homebrew\n"
|
||||
"programs, but *will* break some poorly programmed commercial software titles,\n"
|
||||
"which do not properly initialize WRAM upon power cycle.\n",
|
||||
"A value of 0x55 is safe for all known commercial software, and should be used. "
|
||||
"However, some software written for SNES copiers, or backup units, relies on "
|
||||
"WRAM being initialized to 0x00; which was a side-effect of the BIOS program "
|
||||
"which executed on these copiers. Using 0x00 will therefore fix many homebrew "
|
||||
"programs, but *will* break some poorly programmed commercial software titles, "
|
||||
"which do not properly initialize WRAM upon power cycle.",
|
||||
integral_setting::hex, 0x55);
|
||||
|
||||
integral_setting CPU::hdma_enable("cpu.hdma_enable",
|
||||
"Enable HDMA effects", integral_setting::boolean, true);
|
||||
|
||||
integral_setting SMP::ntsc_clock_rate(config(), "smp.ntsc_clock_rate",
|
||||
"NTSC S-SMP clock rate (in hz)", integral_setting::decimal, 24606720);
|
||||
"NTSC S-SMP clock rate (in hz)", integral_setting::decimal, 32041 * 768);
|
||||
integral_setting SMP::pal_clock_rate(config(), "smp.pal_clock_rate",
|
||||
"PAL S-SMP clock rate (in hz)", integral_setting::decimal, 24606720);
|
||||
"PAL S-SMP clock rate (in hz)", integral_setting::decimal, 32041 * 768);
|
||||
|
||||
integral_setting PPU::Hack::render_scanline_position(config(), "ppu.hack.render_scanline_position",
|
||||
"Approximate HCLOCK position to render at for scanline-based renderers",
|
||||
integral_setting::decimal, 512);
|
||||
integral_setting PPU::Hack::obj_cache(config(), "ppu.hack.obj_cache",
|
||||
"Cache OAM OBJ attributes one scanline before rendering\n"
|
||||
"This is technically closer to the actual operation of the SNES,\n"
|
||||
"but can cause problems in some games if enabled",
|
||||
integral_setting::boolean, false);
|
||||
integral_setting PPU::Hack::oam_address_invalidation(config(), "ppu.hack.oam_address_invalidation",
|
||||
"OAM access address changes during active display, as the S-PPU reads\n"
|
||||
"data to render the display. Thusly, the address retrieved when accessing\n"
|
||||
"OAM during active display is unpredictable. Unfortunately, the exact\n"
|
||||
"algorithm for this is completely unknown at this time. It is more hardware\n"
|
||||
"accurate to enable this setting, but one must *not* rely on the actual\n"
|
||||
"address to match hardware under emulation.",
|
||||
integral_setting::boolean, true);
|
||||
integral_setting PPU::Hack::cgram_address_invalidation(config(), "ppu.hack.cgram_address_invalidation",
|
||||
"CGRAM access address changes during active display (excluding hblank), as\n"
|
||||
"the S-PPU reads data to render the display. Thusly, as with OAM, the access\n"
|
||||
"address is unpredictable. Again, enabling this setting is more hardware\n"
|
||||
"accurate, but one must *not* rely on the actual address to match hardware\n"
|
||||
"under emulation.",
|
||||
integral_setting::boolean, true);
|
||||
|
||||
integral_setting PPU::opt_enable("ppu.opt_enable", "Enable offset-per-tile effects", integral_setting::boolean, true);
|
||||
integral_setting PPU::bg1_pri0_enable("ppu.bg1_pri0_enable", "Enable BG1 Priority 0", integral_setting::boolean, true);
|
||||
integral_setting PPU::bg1_pri1_enable("ppu.bg1_pri1_enable", "Enable BG1 Priority 1", integral_setting::boolean, true);
|
||||
integral_setting PPU::bg2_pri0_enable("ppu.bg2_pri0_enable", "Enable BG2 Priority 0", integral_setting::boolean, true);
|
||||
integral_setting PPU::bg2_pri1_enable("ppu.bg2_pri1_enable", "Enable BG2 Priority 1", integral_setting::boolean, true);
|
||||
integral_setting PPU::bg3_pri0_enable("ppu.bg3_pri0_enable", "Enable BG3 Priority 0", integral_setting::boolean, true);
|
||||
integral_setting PPU::bg3_pri1_enable("ppu.bg3_pri1_enable", "Enable BG3 Priority 1", integral_setting::boolean, true);
|
||||
integral_setting PPU::bg4_pri0_enable("ppu.bg4_pri0_enable", "Enable BG4 Priority 0", integral_setting::boolean, true);
|
||||
integral_setting PPU::bg4_pri1_enable("ppu.bg4_pri1_enable", "Enable BG4 Priority 1", integral_setting::boolean, true);
|
||||
integral_setting PPU::oam_pri0_enable("ppu.oam_pri0_enable", "Enable OAM Priority 0", integral_setting::boolean, true);
|
||||
integral_setting PPU::oam_pri1_enable("ppu.oam_pri1_enable", "Enable OAM Priority 1", integral_setting::boolean, true);
|
||||
integral_setting PPU::oam_pri2_enable("ppu.oam_pri2_enable", "Enable OAM Priority 2", integral_setting::boolean, true);
|
||||
integral_setting PPU::oam_pri3_enable("ppu.oam_pri3_enable", "Enable OAM Priority 3", integral_setting::boolean, true);
|
||||
|
||||
} //namespace config
|
||||
} //namespace config
|
||||
|
@@ -1,49 +0,0 @@
|
||||
namespace config {
|
||||
|
||||
extern configuration& config();
|
||||
|
||||
string file_updatepath(const char*, const char*);
|
||||
|
||||
extern struct File {
|
||||
static integral_setting autodetect_type;
|
||||
static integral_setting bypass_patch_crc32;
|
||||
} file;
|
||||
|
||||
extern struct Path {
|
||||
static string_setting base, user, rom, patch, save, cheat;
|
||||
static string_setting bsx, st;
|
||||
} path;
|
||||
|
||||
extern struct SNES {
|
||||
static integral_setting controller_port0;
|
||||
static integral_setting controller_port1;
|
||||
} snes;
|
||||
|
||||
extern struct CPU {
|
||||
static integral_setting ntsc_clock_rate, pal_clock_rate;
|
||||
static integral_setting wram_init_value;
|
||||
static integral_setting hdma_enable;
|
||||
} cpu;
|
||||
|
||||
extern struct SMP {
|
||||
static integral_setting ntsc_clock_rate, pal_clock_rate;
|
||||
} smp;
|
||||
|
||||
extern struct PPU {
|
||||
struct Hack {
|
||||
static integral_setting render_scanline_position;
|
||||
static integral_setting obj_cache;
|
||||
static integral_setting oam_address_invalidation;
|
||||
static integral_setting cgram_address_invalidation;
|
||||
} hack;
|
||||
|
||||
static integral_setting opt_enable;
|
||||
static integral_setting bg1_pri0_enable, bg1_pri1_enable;
|
||||
static integral_setting bg2_pri0_enable, bg2_pri1_enable;
|
||||
static integral_setting bg3_pri0_enable, bg3_pri1_enable;
|
||||
static integral_setting bg4_pri0_enable, bg4_pri1_enable;
|
||||
static integral_setting oam_pri0_enable, oam_pri1_enable;
|
||||
static integral_setting oam_pri2_enable, oam_pri3_enable;
|
||||
} ppu;
|
||||
|
||||
};
|
34
src/config/config.hpp
Normal file
34
src/config/config.hpp
Normal file
@@ -0,0 +1,34 @@
|
||||
namespace config {
|
||||
|
||||
extern configuration& config();
|
||||
|
||||
string filepath(const char *filename, const char *pathname);
|
||||
|
||||
extern struct File {
|
||||
static integral_setting autodetect_type;
|
||||
static integral_setting bypass_patch_crc32;
|
||||
} file;
|
||||
|
||||
extern struct Path {
|
||||
static string_setting base, user;
|
||||
static string_setting rom, save, patch, cheat, exportdata;
|
||||
static string_setting bsx, st;
|
||||
} path;
|
||||
|
||||
extern struct SNES {
|
||||
static integral_setting controller_port1;
|
||||
static integral_setting controller_port2;
|
||||
static integral_setting expansion_port;
|
||||
static integral_setting region;
|
||||
} snes;
|
||||
|
||||
extern struct CPU {
|
||||
static integral_setting ntsc_clock_rate, pal_clock_rate;
|
||||
static integral_setting wram_init_value;
|
||||
} cpu;
|
||||
|
||||
extern struct SMP {
|
||||
static integral_setting ntsc_clock_rate, pal_clock_rate;
|
||||
} smp;
|
||||
|
||||
};
|
@@ -1,10 +1,10 @@
|
||||
#include "../base.h"
|
||||
#include <../base.hpp>
|
||||
#define CPU_CPP
|
||||
|
||||
#include "dcpu.cpp"
|
||||
|
||||
CPU::CPU() {
|
||||
cpu_version = 1;
|
||||
cpu_version = 2;
|
||||
}
|
||||
|
||||
CPU::~CPU() {
|
||||
|
@@ -1,5 +1,3 @@
|
||||
#include "cpuregs.h"
|
||||
|
||||
class CPU : public MMIO {
|
||||
public:
|
||||
virtual void enter() = 0;
|
||||
@@ -7,25 +5,16 @@ public:
|
||||
//CPU version number
|
||||
//* 1 and 2 are known
|
||||
//* reported by $4210
|
||||
//* affects DRAM refresh behavior
|
||||
//* affects timing (DRAM refresh, HDMA init, etc)
|
||||
uint8 cpu_version;
|
||||
|
||||
//timing
|
||||
virtual uint16 vcounter() = 0;
|
||||
virtual uint16 hcounter() = 0;
|
||||
virtual uint16 hdot() = 0;
|
||||
|
||||
virtual uint8 pio_status() = 0;
|
||||
virtual uint8 pio() = 0;
|
||||
virtual bool joylatch() = 0;
|
||||
virtual uint8 port_read(uint8 port) = 0;
|
||||
virtual void port_write(uint8 port, uint8 value) = 0;
|
||||
|
||||
CPURegs regs;
|
||||
enum {
|
||||
FLAG_N = 0x80, FLAG_V = 0x40,
|
||||
FLAG_M = 0x20, FLAG_X = 0x10,
|
||||
FLAG_D = 0x08, FLAG_I = 0x04,
|
||||
FLAG_Z = 0x02, FLAG_C = 0x01
|
||||
};
|
||||
|
||||
#include "cpuregs.hpp"
|
||||
regs_t regs;
|
||||
|
||||
virtual void scanline() = 0;
|
||||
virtual void frame() = 0;
|
@@ -1,88 +0,0 @@
|
||||
template<int mask>
|
||||
struct CPUFlag {
|
||||
uint8 &data;
|
||||
|
||||
inline operator bool() const { return data & mask; }
|
||||
inline CPUFlag& operator=(bool i) { data = (data & ~mask) | (-i & mask); return *this; }
|
||||
|
||||
CPUFlag(uint8 &data_) : data(data_) {}
|
||||
};
|
||||
|
||||
class CPURegFlags {
|
||||
public:
|
||||
uint8 data;
|
||||
CPUFlag<0x80> n;
|
||||
CPUFlag<0x40> v;
|
||||
CPUFlag<0x20> m;
|
||||
CPUFlag<0x10> x;
|
||||
CPUFlag<0x08> d;
|
||||
CPUFlag<0x04> i;
|
||||
CPUFlag<0x02> z;
|
||||
CPUFlag<0x01> c;
|
||||
|
||||
inline operator unsigned() const { return data; }
|
||||
inline unsigned operator = (unsigned i) { data = i; return data; }
|
||||
inline unsigned operator |= (unsigned i) { data |= i; return data; }
|
||||
inline unsigned operator ^= (unsigned i) { data ^= i; return data; }
|
||||
inline unsigned operator &= (unsigned i) { data &= i; return data; }
|
||||
|
||||
CPURegFlags() : data(0), n(data), v(data), m(data), x(data), d(data), i(data), z(data), c(data) {}
|
||||
};
|
||||
|
||||
class CPUReg16 {
|
||||
public:
|
||||
union {
|
||||
uint16 w;
|
||||
struct { uint8 order_lsb2(l, h); };
|
||||
};
|
||||
|
||||
inline operator unsigned() const { return w; }
|
||||
inline unsigned operator = (unsigned i) { w = i; return w; }
|
||||
inline unsigned operator |= (unsigned i) { w |= i; return w; }
|
||||
inline unsigned operator ^= (unsigned i) { w ^= i; return w; }
|
||||
inline unsigned operator &= (unsigned i) { w &= i; return w; }
|
||||
inline unsigned operator <<= (unsigned i) { w <<= i; return w; }
|
||||
inline unsigned operator >>= (unsigned i) { w >>= i; return w; }
|
||||
inline unsigned operator += (unsigned i) { w += i; return w; }
|
||||
inline unsigned operator -= (unsigned i) { w -= i; return w; }
|
||||
inline unsigned operator *= (unsigned i) { w *= i; return w; }
|
||||
inline unsigned operator /= (unsigned i) { w /= i; return w; }
|
||||
inline unsigned operator %= (unsigned i) { w %= i; return w; }
|
||||
|
||||
CPUReg16() : w(0) {}
|
||||
};
|
||||
|
||||
class CPUReg24 {
|
||||
public:
|
||||
union {
|
||||
uint32 d;
|
||||
struct { uint16 order_lsb2(w, wh); };
|
||||
struct { uint8 order_lsb4(l, h, b, bh); };
|
||||
};
|
||||
|
||||
inline operator unsigned() const { return d; }
|
||||
inline unsigned operator = (unsigned i) { d = uclip<24>(i); return d; }
|
||||
inline unsigned operator |= (unsigned i) { d = uclip<24>(d | i); return d; }
|
||||
inline unsigned operator ^= (unsigned i) { d = uclip<24>(d ^ i); return d; }
|
||||
inline unsigned operator &= (unsigned i) { d = uclip<24>(d & i); return d; }
|
||||
inline unsigned operator <<= (unsigned i) { d = uclip<24>(d << i); return d; }
|
||||
inline unsigned operator >>= (unsigned i) { d = uclip<24>(d >> i); return d; }
|
||||
inline unsigned operator += (unsigned i) { d = uclip<24>(d + i); return d; }
|
||||
inline unsigned operator -= (unsigned i) { d = uclip<24>(d - i); return d; }
|
||||
inline unsigned operator *= (unsigned i) { d = uclip<24>(d * i); return d; }
|
||||
inline unsigned operator /= (unsigned i) { d = uclip<24>(d / i); return d; }
|
||||
inline unsigned operator %= (unsigned i) { d = uclip<24>(d % i); return d; }
|
||||
|
||||
CPUReg24() : d(0) {}
|
||||
};
|
||||
|
||||
class CPURegs {
|
||||
public:
|
||||
CPUReg24 pc;
|
||||
CPUReg16 a, x, y, s, d;
|
||||
CPURegFlags p;
|
||||
uint8 db;
|
||||
uint8 mdr;
|
||||
bool e;
|
||||
CPURegs() : db(0), mdr(0), e(false) {}
|
||||
};
|
74
src/cpu/cpuregs.hpp
Normal file
74
src/cpu/cpuregs.hpp
Normal file
@@ -0,0 +1,74 @@
|
||||
struct flag_t {
|
||||
bool n, v, m, x, d, i, z, c;
|
||||
|
||||
inline operator unsigned() const {
|
||||
return (n << 7) + (v << 6) + (m << 5) + (x << 4)
|
||||
+ (d << 3) + (i << 2) + (z << 1) + (c << 0);
|
||||
}
|
||||
|
||||
inline unsigned operator=(uint8_t data) {
|
||||
n = data & 0x80; v = data & 0x40; m = data & 0x20; x = data & 0x10;
|
||||
d = data & 0x08; i = data & 0x04; z = data & 0x02; c = data & 0x01;
|
||||
return data;
|
||||
}
|
||||
|
||||
inline unsigned operator|=(unsigned data) { return operator=(operator unsigned() | data); }
|
||||
inline unsigned operator^=(unsigned data) { return operator=(operator unsigned() ^ data); }
|
||||
inline unsigned operator&=(unsigned data) { return operator=(operator unsigned() & data); }
|
||||
|
||||
flag_t() : n(0), v(0), m(0), x(0), d(0), i(0), z(0), c(0) {}
|
||||
};
|
||||
|
||||
struct reg16_t {
|
||||
union {
|
||||
uint16 w;
|
||||
struct { uint8 order_lsb2(l, h); };
|
||||
};
|
||||
|
||||
inline operator unsigned() const { return w; }
|
||||
inline unsigned operator = (unsigned i) { return w = i; }
|
||||
inline unsigned operator |= (unsigned i) { return w |= i; }
|
||||
inline unsigned operator ^= (unsigned i) { return w ^= i; }
|
||||
inline unsigned operator &= (unsigned i) { return w &= i; }
|
||||
inline unsigned operator <<= (unsigned i) { return w <<= i; }
|
||||
inline unsigned operator >>= (unsigned i) { return w >>= i; }
|
||||
inline unsigned operator += (unsigned i) { return w += i; }
|
||||
inline unsigned operator -= (unsigned i) { return w -= i; }
|
||||
inline unsigned operator *= (unsigned i) { return w *= i; }
|
||||
inline unsigned operator /= (unsigned i) { return w /= i; }
|
||||
inline unsigned operator %= (unsigned i) { return w %= i; }
|
||||
|
||||
reg16_t() : w(0) {}
|
||||
};
|
||||
|
||||
struct reg24_t {
|
||||
union {
|
||||
uint32 d;
|
||||
struct { uint16 order_lsb2(w, wh); };
|
||||
struct { uint8 order_lsb4(l, h, b, bh); };
|
||||
};
|
||||
|
||||
inline operator unsigned() const { return d; }
|
||||
inline unsigned operator = (unsigned i) { return d = uclip<24>(i); }
|
||||
inline unsigned operator |= (unsigned i) { return d = uclip<24>(d | i); }
|
||||
inline unsigned operator ^= (unsigned i) { return d = uclip<24>(d ^ i); }
|
||||
inline unsigned operator &= (unsigned i) { return d = uclip<24>(d & i); }
|
||||
inline unsigned operator <<= (unsigned i) { return d = uclip<24>(d << i); }
|
||||
inline unsigned operator >>= (unsigned i) { return d = uclip<24>(d >> i); }
|
||||
inline unsigned operator += (unsigned i) { return d = uclip<24>(d + i); }
|
||||
inline unsigned operator -= (unsigned i) { return d = uclip<24>(d - i); }
|
||||
inline unsigned operator *= (unsigned i) { return d = uclip<24>(d * i); }
|
||||
inline unsigned operator /= (unsigned i) { return d = uclip<24>(d / i); }
|
||||
inline unsigned operator %= (unsigned i) { return d = uclip<24>(d % i); }
|
||||
|
||||
reg24_t() : d(0) {}
|
||||
};
|
||||
|
||||
struct regs_t {
|
||||
reg24_t pc;
|
||||
reg16_t a, x, y, s, d;
|
||||
flag_t p;
|
||||
uint8_t db, mdr;
|
||||
bool e;
|
||||
regs_t() : db(0), mdr(0), e(false) {}
|
||||
};
|
@@ -103,7 +103,7 @@ uint32 r = 0;
|
||||
}
|
||||
|
||||
void CPU::disassemble_opcode(char *output) {
|
||||
static CPUReg24 pc;
|
||||
static reg24_t pc;
|
||||
char t[256];
|
||||
char *s = output;
|
||||
|
||||
@@ -425,7 +425,7 @@ uint8 op2 = dreadb(pc.d);
|
||||
strcat(s, t);
|
||||
strcat(s, " ");
|
||||
|
||||
sprintf(t, "V:%3d H:%4d", vcounter(), hcounter());
|
||||
sprintf(t, "V:%3d H:%4d", ppucounter.vcounter(), ppucounter.hcounter());
|
||||
strcat(s, t);
|
||||
}
|
||||
|
||||
|
@@ -2,7 +2,14 @@
|
||||
|
||||
#include "opfn.cpp"
|
||||
|
||||
void sCPU::enter() { loop:
|
||||
void sCPU::enter() {
|
||||
initialize:
|
||||
//initial latch values for $213c/$213d
|
||||
//[x]0035 : [y]0000 (53.0 -> 212) [lda $2137]
|
||||
//[x]0038 : [y]0000 (56.5 -> 226) [nop : lda $2137]
|
||||
add_clocks(186);
|
||||
|
||||
loop:
|
||||
if(event.irq) {
|
||||
event.irq = false;
|
||||
if(status.nmi_pending == true) {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
CPUReg24 aa, rd;
|
||||
reg24_t aa, rd;
|
||||
uint8_t dp, sp;
|
||||
|
||||
void op_irq();
|
@@ -5,52 +5,51 @@ void sCPU::dma_add_clocks(uint clocks) {
|
||||
add_clocks(clocks);
|
||||
}
|
||||
|
||||
/*****
|
||||
* used by both DMA and HDMA
|
||||
*
|
||||
* DMA address bus A cannot read from or write to the following addresses :
|
||||
* $[00-3f|80-bf]:43[00-7f] <DMA control registers>
|
||||
* $[00-3f|80-bf]:420b <DMA enable register>
|
||||
* $[00-3f|80-bf]:420c <HDMA enable register>
|
||||
*
|
||||
* WRAM<>WRAM transfers via $2180 are also illegal
|
||||
*****/
|
||||
bool sCPU::dma_addr_valid(uint32 abus) {
|
||||
//reads from B-bus or S-CPU registers are invalid
|
||||
if((abus & 0x40ff00) == 0x2100) return false; //$[00-3f|80-bf]:[2100-21ff]
|
||||
if((abus & 0x40fe00) == 0x4000) return false; //$[00-3f|80-bf]:[4000-41ff]
|
||||
if((abus & 0x40ffe0) == 0x4200) return false; //$[00-3f|80-bf]:[4200-421f]
|
||||
if((abus & 0x40ff80) == 0x4300) return false; //$[00-3f|80-bf]:[4300-437f]
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8 sCPU::dma_read(uint32 abus) {
|
||||
if(dma_addr_valid(abus) == false) return 0x00; //does not return S-CPU MDR
|
||||
return bus.read(abus);
|
||||
}
|
||||
|
||||
void sCPU::dma_transfer(bool direction, uint8 bbus, uint32 abus) {
|
||||
if(direction == 0) {
|
||||
//a->b transfer (to $21xx)
|
||||
if(bbus == 0x80 && ((abus & 0xfe0000) == 0x7e0000 || (abus & 0x40e000) == 0x0000)) {
|
||||
//illegal WRAM->WRAM transfer
|
||||
//illegal WRAM->WRAM transfer (bus conflict)
|
||||
//read most likely occurs; no write occurs
|
||||
//read is irrelevant, as it has no observable effect on emulation
|
||||
} else if((abus & 0x40ff00) == 0x2100 || (abus & 0x40ff80) == 0x4300
|
||||
|| (abus & 0x40ffff) == 0x420b || (abus & 0x40ffff) == 0x420c) {
|
||||
//illegal register access
|
||||
bus.write(0x2100 | bbus, regs.mdr); //TODO: verify if MDR is written here
|
||||
//read is irrelevent, as it cannot be observed by software
|
||||
dma_add_clocks(8);
|
||||
} else {
|
||||
//valid transfer
|
||||
bus.write(0x2100 | bbus, bus.read(abus));
|
||||
dma_add_clocks(4);
|
||||
uint8 data = dma_read(abus);
|
||||
dma_add_clocks(4);
|
||||
bus.write(0x2100 | bbus, data);
|
||||
}
|
||||
} else {
|
||||
//b->a transfer (from $21xx)
|
||||
if(bbus == 0x80 && ((abus & 0xfe0000) == 0x7e0000 || (abus & 0x40e000) == 0x0000)) {
|
||||
//illegal WRAM->WRAM transfer
|
||||
//illegal WRAM->WRAM transfer (bus conflict)
|
||||
//no read occurs; write does occur
|
||||
//does not write MDR as expected
|
||||
//TODO: 0x00 was observed on hardware; verify if other values are possible
|
||||
bus.write(abus, 0x00);
|
||||
} else if((abus & 0x40ff00) == 0x2100 || (abus & 0x40ff80) == 0x4300
|
||||
|| (abus & 0x40ffff) == 0x420b || (abus & 0x40ffff) == 0x420c) {
|
||||
//illegal register access
|
||||
bus.write(abus, regs.mdr); //TODO: verify if MDR is written here
|
||||
dma_add_clocks(8);
|
||||
bus.write(abus, 0x00); //does not write S-CPU MDR
|
||||
} else {
|
||||
//valid transfer
|
||||
bus.write(abus, bus.read(0x2100 | bbus));
|
||||
dma_add_clocks(4);
|
||||
uint8 data = bus.read(0x2100 | bbus);
|
||||
dma_add_clocks(4);
|
||||
if(dma_addr_valid(abus) == true) {
|
||||
bus.write(abus, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//each byte *always* consumes 8 clocks, even if transfer is invalid and no read and/or write occurs
|
||||
dma_add_clocks(8);
|
||||
cycle_edge();
|
||||
}
|
||||
|
||||
@@ -60,14 +59,14 @@ void sCPU::dma_transfer(bool direction, uint8 bbus, uint32 abus) {
|
||||
|
||||
uint8 sCPU::dma_bbus(uint8 i, uint8 index) {
|
||||
switch(channel[i].xfermode) { default:
|
||||
case 0: return (channel[i].destaddr); //0
|
||||
case 1: return (channel[i].destaddr + (index & 1)); //0,1
|
||||
case 2: return (channel[i].destaddr); //0,0
|
||||
case 3: return (channel[i].destaddr + ((index >> 1) & 1)); //0,0,1,1
|
||||
case 4: return (channel[i].destaddr + (index & 3)); //0,1,2,3
|
||||
case 5: return (channel[i].destaddr + (index & 1)); //0,1,0,1
|
||||
case 6: return (channel[i].destaddr); //0,0 [2]
|
||||
case 7: return (channel[i].destaddr + ((index >> 1) & 1)); //0,0,1,1 [3]
|
||||
case 0: return (channel[i].destaddr); //0
|
||||
case 1: return (channel[i].destaddr + (index & 1)); //0,1
|
||||
case 2: return (channel[i].destaddr); //0,0
|
||||
case 3: return (channel[i].destaddr + ((index >> 1) & 1)); //0,0,1,1
|
||||
case 4: return (channel[i].destaddr + (index & 3)); //0,1,2,3
|
||||
case 5: return (channel[i].destaddr + (index & 1)); //0,1,0,1
|
||||
case 6: return (channel[i].destaddr); //0,0 [2]
|
||||
case 7: return (channel[i].destaddr + ((index >> 1) & 1)); //0,0,1,1 [3]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,51 +96,27 @@ inline uint32 sCPU::hdma_iaddr(uint8 i) {
|
||||
* DMA functions
|
||||
*****/
|
||||
|
||||
void sCPU::dma_transfertobusb(uint8 i, uint8 bbus) {
|
||||
if(cartridge.info.sdd1 == true && sdd1.dma_active() == true) {
|
||||
bus.write(0x2100 | bbus, sdd1.dma_read());
|
||||
} else {
|
||||
dma_transfer(0, bbus, dma_addr(i));
|
||||
}
|
||||
channel[i].xfersize--;
|
||||
}
|
||||
|
||||
void sCPU::dma_transfertobusa(uint8 i, uint8 bbus) {
|
||||
dma_transfer(1, bbus, dma_addr(i));
|
||||
channel[i].xfersize--;
|
||||
}
|
||||
|
||||
inline void sCPU::dma_write(uint8 i, uint8 index) {
|
||||
//cannot use dma_transfer() directly, due to current S-DD1 implementation
|
||||
if(channel[i].direction == 0) {
|
||||
dma_transfertobusb(i, index);
|
||||
} else {
|
||||
dma_transfertobusa(i, index);
|
||||
uint8 sCPU::dma_enabled_channels() {
|
||||
uint8 r = 0;
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
if(channel[i].dma_enabled) r++;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
void sCPU::dma_run() {
|
||||
for(int i = 0; i < 8; i++) {
|
||||
dma_add_clocks(8);
|
||||
cycle_edge();
|
||||
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
if(channel[i].dma_enabled == false) continue;
|
||||
dma_add_clocks(8);
|
||||
cycle_edge();
|
||||
|
||||
if(cartridge.info.sdd1 == true) {
|
||||
sdd1.dma_begin(i, (channel[i].srcbank << 16) | (channel[i].srcaddr), channel[i].xfersize);
|
||||
}
|
||||
|
||||
if(tracer.enabled() == true && tracer.cpudma_enabled() == true) {
|
||||
tprintf("[DMA] channel:%d direction:%s reverse:%c fixed:%c mode:%d b_addr:$21%0.2x "
|
||||
"a_addr:$%0.2x%0.4x length:$%0.4x (%5d)",
|
||||
i, channel[i].direction ? "b->a" : "a->b", channel[i].reversexfer ? '1' : '0',
|
||||
channel[i].fixedxfer ? '1' : '0', channel[i].xfermode, channel[i].destaddr,
|
||||
channel[i].srcbank, channel[i].srcaddr,
|
||||
channel[i].xfersize, channel[i].xfersize ? channel[i].xfersize : 65536);
|
||||
}
|
||||
|
||||
uint index = 0;
|
||||
unsigned index = 0;
|
||||
do {
|
||||
dma_write(i, dma_bbus(i, index++));
|
||||
} while(channel[i].dma_enabled && channel[i].xfersize);
|
||||
dma_transfer(channel[i].direction, dma_bbus(i, index++), dma_addr(i));
|
||||
} while(channel[i].dma_enabled && --channel[i].xfersize);
|
||||
|
||||
channel[i].dma_enabled = false;
|
||||
}
|
||||
@@ -158,7 +133,7 @@ inline bool sCPU::hdma_active(uint8 i) {
|
||||
}
|
||||
|
||||
inline bool sCPU::hdma_active_after(uint8 i) {
|
||||
for(int n = i + 1; n < 8; n++) {
|
||||
for(unsigned n = i + 1; n < 8; n++) {
|
||||
if(hdma_active(n) == true) return true;
|
||||
}
|
||||
return false;
|
||||
@@ -166,7 +141,7 @@ inline bool sCPU::hdma_active_after(uint8 i) {
|
||||
|
||||
inline uint8 sCPU::hdma_enabled_channels() {
|
||||
uint8 r = 0;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
if(channel[i].hdma_enabled) r++;
|
||||
}
|
||||
return r;
|
||||
@@ -174,55 +149,57 @@ inline uint8 sCPU::hdma_enabled_channels() {
|
||||
|
||||
inline uint8 sCPU::hdma_active_channels() {
|
||||
uint8 r = 0;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
if(hdma_active(i) == true) r++;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
void sCPU::hdma_update(uint8 i) {
|
||||
channel[i].hdma_line_counter = bus.read(hdma_addr(i));
|
||||
channel[i].hdma_line_counter = dma_read(hdma_addr(i));
|
||||
dma_add_clocks(8);
|
||||
|
||||
channel[i].hdma_completed = (channel[i].hdma_line_counter == 0);
|
||||
channel[i].hdma_do_transfer = !channel[i].hdma_completed;
|
||||
|
||||
if(channel[i].hdma_indirect) {
|
||||
channel[i].hdma_iaddr = bus.read(hdma_addr(i)) << 8;
|
||||
channel[i].hdma_iaddr = dma_read(hdma_addr(i)) << 8;
|
||||
dma_add_clocks(8);
|
||||
|
||||
if(!channel[i].hdma_completed || hdma_active_after(i)) {
|
||||
channel[i].hdma_iaddr >>= 8;
|
||||
channel[i].hdma_iaddr |= bus.read(hdma_addr(i)) << 8;
|
||||
channel[i].hdma_iaddr |= dma_read(hdma_addr(i)) << 8;
|
||||
dma_add_clocks(8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sCPU::hdma_run() {
|
||||
static uint8 hdma_xferlen[8] = { 1, 2, 2, 4, 4, 4, 2, 4 };
|
||||
for(int i = 0; i < 8; i++) {
|
||||
dma_add_clocks(8);
|
||||
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
if(hdma_active(i) == false) continue;
|
||||
channel[i].dma_enabled = false; //HDMA run during DMA will stop DMA mid-transfer
|
||||
dma_add_clocks(8);
|
||||
channel[i].dma_enabled = false; //HDMA run during DMA will stop DMA mid-transfer
|
||||
|
||||
if(channel[i].hdma_do_transfer) {
|
||||
int xferlen = hdma_xferlen[channel[i].xfermode];
|
||||
for(int index = 0; index < xferlen; index++) {
|
||||
if(bool(config::cpu.hdma_enable) == true) {
|
||||
dma_transfer(channel[i].direction, dma_bbus(i, index),
|
||||
!channel[i].hdma_indirect ? hdma_addr(i) : hdma_iaddr(i));
|
||||
} else {
|
||||
dma_add_clocks(8);
|
||||
cycle_edge();
|
||||
}
|
||||
static const unsigned transfer_length[8] = { 1, 2, 2, 4, 4, 4, 2, 4 };
|
||||
unsigned length = transfer_length[channel[i].xfermode];
|
||||
for(unsigned index = 0; index < length; index++) {
|
||||
unsigned addr = !channel[i].hdma_indirect ? hdma_addr(i) : hdma_iaddr(i);
|
||||
dma_transfer(channel[i].direction, dma_bbus(i, index), addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
if(hdma_active(i) == false) continue;
|
||||
|
||||
channel[i].hdma_line_counter--;
|
||||
channel[i].hdma_do_transfer = bool(channel[i].hdma_line_counter & 0x80);
|
||||
if((channel[i].hdma_line_counter & 0x7f) == 0) {
|
||||
hdma_update(i);
|
||||
} else {
|
||||
dma_add_clocks(8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,16 +207,18 @@ void sCPU::hdma_run() {
|
||||
}
|
||||
|
||||
void sCPU::hdma_init_reset() {
|
||||
for(int i = 0; i < 8; i++) {
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
channel[i].hdma_completed = false;
|
||||
channel[i].hdma_do_transfer = false;
|
||||
}
|
||||
}
|
||||
|
||||
void sCPU::hdma_init() {
|
||||
for(int i = 0; i < 8; i++) {
|
||||
if(!channel[i].hdma_enabled)continue;
|
||||
channel[i].dma_enabled = false; //HDMA init during DMA will stop DMA mid-transfer
|
||||
dma_add_clocks(8);
|
||||
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
if(!channel[i].hdma_enabled) continue;
|
||||
channel[i].dma_enabled = false; //HDMA init during DMA will stop DMA mid-transfer
|
||||
|
||||
channel[i].hdma_addr = channel[i].srcaddr;
|
||||
hdma_update(i);
|
||||
@@ -253,7 +232,7 @@ void sCPU::hdma_init() {
|
||||
*****/
|
||||
|
||||
void sCPU::dma_power() {
|
||||
for(int i = 0; i < 8; i++) {
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
channel[i].dmap = 0xff;
|
||||
channel[i].direction = 1;
|
||||
channel[i].hdma_indirect = true;
|
||||
@@ -267,7 +246,7 @@ void sCPU::dma_power() {
|
||||
channel[i].srcbank = 0xff;
|
||||
|
||||
channel[i].xfersize = 0xffff;
|
||||
//channel[i].hdma_iaddr = 0xffff; //union with xfersize
|
||||
//channel[i].hdma_iaddr = 0xffff; //union with xfersize
|
||||
channel[i].hdma_ibank = 0xff;
|
||||
|
||||
channel[i].hdma_addr = 0xffff;
|
||||
@@ -277,7 +256,7 @@ void sCPU::dma_power() {
|
||||
}
|
||||
|
||||
void sCPU::dma_reset() {
|
||||
for(int i = 0; i < 8; i++) {
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
channel[i].dma_enabled = false;
|
||||
channel[i].hdma_enabled = false;
|
||||
|
||||
|
@@ -46,6 +46,8 @@
|
||||
} channel[8];
|
||||
|
||||
void dma_add_clocks(uint clocks);
|
||||
bool dma_addr_valid(uint32 abus);
|
||||
uint8 dma_read(uint32 abus);
|
||||
void dma_transfer(bool direction, uint8 bbus, uint32 abus);
|
||||
|
||||
uint8 dma_bbus(uint8 i, uint8 index);
|
||||
@@ -53,9 +55,7 @@
|
||||
uint32 hdma_addr(uint8 i);
|
||||
uint32 hdma_iaddr(uint8 i);
|
||||
|
||||
void dma_transfertobusb(uint8 i, uint8 bbus);
|
||||
void dma_transfertobusa(uint8 i, uint8 bbus);
|
||||
void dma_write(uint8 i, uint8 index);
|
||||
uint8 dma_enabled_channels();
|
||||
void dma_run();
|
||||
|
||||
bool hdma_active(uint8 i);
|
@@ -1,8 +1,7 @@
|
||||
#ifdef SCPU_CPP
|
||||
|
||||
uint8 sCPU::pio_status() {
|
||||
return status.pio;
|
||||
}
|
||||
uint8 sCPU::pio() { return status.pio; }
|
||||
bool sCPU::joylatch() { return status.joypad_strobe_latch; }
|
||||
|
||||
//WMDATA
|
||||
uint8 sCPU::mmio_r2180() {
|
||||
@@ -55,7 +54,7 @@ void sCPU::mmio_w4016(uint8 data) {
|
||||
//realtime or buffered status of joypadN.b
|
||||
uint8 sCPU::mmio_r4016() {
|
||||
uint8 r = regs.mdr & 0xfc;
|
||||
r |= (uint8)snes.input.port_read(0);
|
||||
r |= snes.input.port_read(0) & 3;
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -65,7 +64,7 @@ uint8 sCPU::mmio_r4016() {
|
||||
//1-0 = Joypad serial data
|
||||
uint8 sCPU::mmio_r4017() {
|
||||
uint8 r = (regs.mdr & 0xe0) | 0x1c;
|
||||
r |= (uint8)snes.input.port_read(1);
|
||||
r |= snes.input.port_read(1) & 3;
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -143,19 +142,16 @@ void sCPU::mmio_w420a(uint8 data) {
|
||||
|
||||
//DMAEN
|
||||
void sCPU::mmio_w420b(uint8 data) {
|
||||
for(int i = 0; i < 8; i++) {
|
||||
channel[i].dma_enabled = !!(data & (1 << i));
|
||||
}
|
||||
if(data) {
|
||||
status.dma_state = DMASTATE_DMASYNC;
|
||||
status.dma_pending = true;
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
channel[i].dma_enabled = data & (1 << i);
|
||||
}
|
||||
if(data) status.dma_pending = true;
|
||||
}
|
||||
|
||||
//HDMAEN
|
||||
void sCPU::mmio_w420c(uint8 data) {
|
||||
for(int i = 0; i < 8; i++) {
|
||||
channel[i].hdma_enabled = !!(data & (1 << i));
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
channel[i].hdma_enabled = data & (1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,13 +190,13 @@ uint8 sCPU::mmio_r4212() {
|
||||
uint16 vs = ppu.overscan() == false ? 225 : 240;
|
||||
|
||||
//auto joypad polling
|
||||
if(status.vcounter >= vs && status.vcounter <= (vs + 2))r |= 0x01;
|
||||
if(ppucounter.vcounter() >= vs && ppucounter.vcounter() <= (vs + 2))r |= 0x01;
|
||||
|
||||
//hblank
|
||||
if(status.hcounter <= 2 || status.hcounter >= 1096)r |= 0x40;
|
||||
if(ppucounter.hcounter() <= 2 || ppucounter.hcounter() >= 1096)r |= 0x40;
|
||||
|
||||
//vblank
|
||||
if(status.vcounter >= vs)r |= 0x80;
|
||||
if(ppucounter.vcounter() >= vs)r |= 0x80;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
@@ -3,7 +3,8 @@
|
||||
uint8 mmio_read(uint addr);
|
||||
void mmio_write(uint addr, uint8 data);
|
||||
|
||||
uint8 pio_status();
|
||||
uint8 pio();
|
||||
bool joylatch();
|
||||
|
||||
uint8 mmio_r2180();
|
||||
uint8 mmio_r4016();
|
@@ -1,4 +1,4 @@
|
||||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define SCPU_CPP
|
||||
|
||||
#include "core/core.cpp"
|
||||
|
@@ -1,133 +0,0 @@
|
||||
class sCPU : public CPU {
|
||||
public:
|
||||
void enter();
|
||||
|
||||
#include "core/core.h"
|
||||
#include "dma/dma.h"
|
||||
#include "memory/memory.h"
|
||||
#include "mmio/mmio.h"
|
||||
#include "timing/timing.h"
|
||||
|
||||
struct {
|
||||
bool wai;
|
||||
bool irq;
|
||||
uint16 irq_vector;
|
||||
} event;
|
||||
|
||||
struct {
|
||||
uint nmi_hold;
|
||||
uint irq_hold;
|
||||
|
||||
uint nmi_fire;
|
||||
uint irq_fire;
|
||||
uint irq_delay;
|
||||
uint hw_math;
|
||||
|
||||
alwaysinline void set(uint &ctr, uint clocks) {
|
||||
if(clocks >= ctr) { ctr = clocks; }
|
||||
}
|
||||
|
||||
alwaysinline void sub(uint &ctr, uint clocks) {
|
||||
if(ctr >= clocks) {
|
||||
ctr -= clocks;
|
||||
} else {
|
||||
ctr = 0;
|
||||
}
|
||||
}
|
||||
} counter;
|
||||
|
||||
enum {
|
||||
DMASTATE_INACTIVE,
|
||||
DMASTATE_DMASYNC,
|
||||
DMASTATE_RUN,
|
||||
DMASTATE_CPUSYNC,
|
||||
};
|
||||
|
||||
struct {
|
||||
//core
|
||||
uint8 opcode;
|
||||
bool in_opcode;
|
||||
|
||||
uint clock_count;
|
||||
|
||||
//timing
|
||||
uint16 vcounter, hcounter;
|
||||
uint16 field_lines, line_clocks;
|
||||
|
||||
bool line_rendered;
|
||||
uint16 line_render_position;
|
||||
|
||||
bool dram_refreshed;
|
||||
uint16 dram_refresh_position;
|
||||
|
||||
bool hdmainit_triggered;
|
||||
uint16 hdmainit_trigger_position;
|
||||
|
||||
bool hdma_triggered;
|
||||
|
||||
uint16 irq_delay;
|
||||
|
||||
bool nmi_valid;
|
||||
bool nmi_line;
|
||||
bool nmi_transition;
|
||||
bool nmi_pending;
|
||||
|
||||
uint16 virq_trigger_pos, hirq_trigger_pos;
|
||||
bool irq_valid;
|
||||
bool irq_line;
|
||||
bool irq_transition;
|
||||
bool irq_pending;
|
||||
|
||||
//dma
|
||||
uint dma_counter;
|
||||
uint dma_clocks;
|
||||
uint dma_state;
|
||||
bool dma_pending;
|
||||
bool hdma_pending;
|
||||
bool hdmainit_pending;
|
||||
|
||||
//mmio
|
||||
|
||||
//$2181-$2183
|
||||
uint32 wram_addr;
|
||||
|
||||
//$4016-$4017
|
||||
bool joypad_strobe_latch;
|
||||
uint32 joypad1_bits;
|
||||
uint32 joypad2_bits;
|
||||
|
||||
//$4200
|
||||
bool nmi_enabled;
|
||||
bool hirq_enabled, virq_enabled;
|
||||
bool auto_joypad_poll;
|
||||
|
||||
//$4201
|
||||
uint8 pio;
|
||||
|
||||
//$4202-$4203
|
||||
uint8 mul_a, mul_b;
|
||||
|
||||
//$4204-$4206
|
||||
uint16 div_a;
|
||||
uint8 div_b;
|
||||
|
||||
//$4207-$420a
|
||||
uint16 hirq_pos, virq_pos;
|
||||
|
||||
//$4214-$4217
|
||||
uint16 r4214;
|
||||
uint16 r4216;
|
||||
|
||||
//$4218-$421f
|
||||
uint8 joy1l, joy1h;
|
||||
uint8 joy2l, joy2h;
|
||||
uint8 joy3l, joy3h;
|
||||
uint8 joy4l, joy4h;
|
||||
} status;
|
||||
|
||||
void power();
|
||||
void reset();
|
||||
|
||||
sCPU();
|
||||
~sCPU();
|
||||
};
|
123
src/cpu/scpu/scpu.hpp
Normal file
123
src/cpu/scpu/scpu.hpp
Normal file
@@ -0,0 +1,123 @@
|
||||
class sCPU : public CPU {
|
||||
public:
|
||||
void enter();
|
||||
|
||||
#include "core/core.hpp"
|
||||
#include "dma/dma.hpp"
|
||||
#include "memory/memory.hpp"
|
||||
#include "mmio/mmio.hpp"
|
||||
#include "timing/timing.hpp"
|
||||
|
||||
struct {
|
||||
bool wai;
|
||||
bool irq;
|
||||
uint16 irq_vector;
|
||||
} event;
|
||||
|
||||
struct {
|
||||
unsigned nmi_hold;
|
||||
unsigned irq_hold;
|
||||
|
||||
unsigned nmi_fire;
|
||||
unsigned irq_fire;
|
||||
unsigned irq_delay;
|
||||
unsigned hw_math;
|
||||
|
||||
alwaysinline void set(uint &ctr, uint clocks) {
|
||||
if(clocks >= ctr) { ctr = clocks; }
|
||||
}
|
||||
|
||||
alwaysinline void sub(uint &ctr, uint clocks) {
|
||||
if(ctr >= clocks) {
|
||||
ctr -= clocks;
|
||||
} else {
|
||||
ctr = 0;
|
||||
}
|
||||
}
|
||||
} counter;
|
||||
|
||||
enum DMA_State { DMA_Inactive, DMA_Run, DMA_CPUsync };
|
||||
|
||||
struct {
|
||||
//core
|
||||
uint8 opcode;
|
||||
bool in_opcode;
|
||||
|
||||
unsigned clock_count;
|
||||
unsigned line_clocks;
|
||||
|
||||
//timing
|
||||
bool dram_refreshed;
|
||||
uint16 dram_refresh_position;
|
||||
|
||||
bool hdmainit_triggered;
|
||||
uint16 hdmainit_trigger_position;
|
||||
|
||||
bool hdma_triggered;
|
||||
|
||||
uint16 irq_delay;
|
||||
|
||||
bool nmi_valid;
|
||||
bool nmi_line;
|
||||
bool nmi_transition;
|
||||
bool nmi_pending;
|
||||
|
||||
uint16 virq_trigger_pos, hirq_trigger_pos;
|
||||
bool irq_valid;
|
||||
bool irq_line;
|
||||
bool irq_transition;
|
||||
bool irq_pending;
|
||||
|
||||
//dma
|
||||
unsigned dma_counter;
|
||||
unsigned dma_clocks;
|
||||
bool dma_pending;
|
||||
bool hdma_pending;
|
||||
bool hdma_mode; //0 = init, 1 = run
|
||||
DMA_State dma_state;
|
||||
|
||||
//mmio
|
||||
|
||||
//$2181-$2183
|
||||
uint32 wram_addr;
|
||||
|
||||
//$4016-$4017
|
||||
bool joypad_strobe_latch;
|
||||
uint32 joypad1_bits;
|
||||
uint32 joypad2_bits;
|
||||
|
||||
//$4200
|
||||
bool nmi_enabled;
|
||||
bool hirq_enabled, virq_enabled;
|
||||
bool auto_joypad_poll;
|
||||
|
||||
//$4201
|
||||
uint8 pio;
|
||||
|
||||
//$4202-$4203
|
||||
uint8 mul_a, mul_b;
|
||||
|
||||
//$4204-$4206
|
||||
uint16 div_a;
|
||||
uint8 div_b;
|
||||
|
||||
//$4207-$420a
|
||||
uint16 hirq_pos, virq_pos;
|
||||
|
||||
//$4214-$4217
|
||||
uint16 r4214;
|
||||
uint16 r4216;
|
||||
|
||||
//$4218-$421f
|
||||
uint8 joy1l, joy1h;
|
||||
uint8 joy2l, joy2h;
|
||||
uint8 joy3l, joy3h;
|
||||
uint8 joy4l, joy4h;
|
||||
} status;
|
||||
|
||||
void power();
|
||||
void reset();
|
||||
|
||||
sCPU();
|
||||
~sCPU();
|
||||
};
|
@@ -1,13 +1,20 @@
|
||||
#ifdef SCPU_CPP
|
||||
|
||||
#ifdef SCPU_CPP
|
||||
|
||||
void sCPU::update_interrupts() {
|
||||
if(irq_pos_valid() == true) {
|
||||
status.virq_trigger_pos = status.virq_pos;
|
||||
status.hirq_trigger_pos = 4 * ((status.hirq_enabled) ? (status.hirq_pos + 1) : 0);
|
||||
} else {
|
||||
status.virq_trigger_pos = IRQ_TRIGGER_NEVER;
|
||||
status.hirq_trigger_pos = IRQ_TRIGGER_NEVER;
|
||||
unsigned vtime = status.virq_pos;
|
||||
unsigned htime = status.hirq_enabled ? status.hirq_pos : 0;
|
||||
unsigned vlimit = (snes.region() == SNES::NTSC ? 525 : 625) >> 1;
|
||||
|
||||
//an IRQ for the very last dot of a field cannot trigger an IRQ
|
||||
if((vtime == (vlimit - 1) && htime == 339 && ppu.interlace() == false)
|
||||
|| (vtime == vlimit && htime == 339)
|
||||
) {
|
||||
vtime = 0x03ff;
|
||||
htime = 0x03ff;
|
||||
}
|
||||
|
||||
status.virq_trigger_pos = vtime;
|
||||
status.hirq_trigger_pos = 4 * (status.hirq_enabled ? htime + 1 : 0);
|
||||
}
|
||||
|
||||
alwaysinline void sCPU::poll_interrupts() {
|
||||
@@ -22,7 +29,8 @@ alwaysinline void sCPU::poll_interrupts() {
|
||||
}
|
||||
|
||||
//NMI test
|
||||
history.query(2, vpos, hpos);
|
||||
vpos = ppucounter.vcounter(2);
|
||||
hpos = ppucounter.hcounter(2);
|
||||
bool nmi_valid = (vpos >= (!ppu.overscan() ? 225 : 240));
|
||||
if(status.nmi_valid == false && nmi_valid == true) {
|
||||
//0->1 edge sensitive transition
|
||||
@@ -41,7 +49,8 @@ alwaysinline void sCPU::poll_interrupts() {
|
||||
}
|
||||
|
||||
//IRQ test
|
||||
history.query(10, vpos, hpos);
|
||||
vpos = ppucounter.vcounter(10);
|
||||
hpos = ppucounter.hcounter(10);
|
||||
bool irq_valid = (status.virq_enabled == true || status.hirq_enabled == true);
|
||||
if(irq_valid == true) {
|
||||
if(status.virq_enabled == true && vpos != status.virq_trigger_pos) irq_valid = false;
|
||||
@@ -103,23 +112,6 @@ bool sCPU::timeup() {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool sCPU::irq_pos_valid() {
|
||||
uint vpos = status.virq_pos;
|
||||
uint hpos = (status.hirq_enabled) ? status.hirq_pos : 0;
|
||||
uint vlimit = (snes.region() == SNES::NTSC ? 525 : 625) >> 1;
|
||||
//positions that can never be latched
|
||||
//vlimit = 262/NTSC, 312/PAL
|
||||
//PAL results are unverified on hardware
|
||||
if(vpos == 240 && hpos == 339 && ppu.interlace() == false && ppu.field() == 1) return false;
|
||||
if(vpos == (vlimit - 1) && hpos == 339 && ppu.interlace() == false) return false;
|
||||
if(vpos == vlimit && ppu.interlace() == false) return false;
|
||||
if(vpos == vlimit && hpos == 339) return false;
|
||||
if(vpos > vlimit) return false;
|
||||
if(hpos > 339) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
alwaysinline bool sCPU::nmi_test() {
|
||||
if(status.nmi_transition == false) return false;
|
||||
status.nmi_transition = false;
|
||||
@@ -131,7 +123,7 @@ alwaysinline bool sCPU::irq_test() {
|
||||
if(status.irq_transition == false) return false;
|
||||
status.irq_transition = false;
|
||||
event.wai = false;
|
||||
return regs.p.i ? false : true;
|
||||
return !regs.p.i;
|
||||
}
|
||||
|
||||
#endif //ifdef SCPU_CPP
|
||||
#endif //ifdef SCPU_CPP
|
||||
|
@@ -1,10 +1,15 @@
|
||||
#ifdef SCPU_CPP
|
||||
|
||||
void sCPU::run_auto_joypad_poll() {
|
||||
uint16_t joy1 = 0, joy2 = 0;
|
||||
uint16 joy1 = 0, joy2 = 0, joy3 = 0, joy4 = 0;
|
||||
for(unsigned i = 0; i < 16; i++) {
|
||||
joy1 |= (uint16_t)snes.input.port_read(0) ? (0x8000 >> i) : 0;
|
||||
joy2 |= (uint16_t)snes.input.port_read(1) ? (0x8000 >> i) : 0;
|
||||
uint8 port0 = snes.input.port_read(0);
|
||||
uint8 port1 = snes.input.port_read(1);
|
||||
|
||||
joy1 |= (port0 & 1) ? (0x8000 >> i) : 0;
|
||||
joy2 |= (port1 & 1) ? (0x8000 >> i) : 0;
|
||||
joy3 |= (port0 & 2) ? (0x8000 >> i) : 0;
|
||||
joy4 |= (port1 & 2) ? (0x8000 >> i) : 0;
|
||||
}
|
||||
|
||||
status.joy1l = joy1;
|
||||
@@ -13,11 +18,11 @@ void sCPU::run_auto_joypad_poll() {
|
||||
status.joy2l = joy2;
|
||||
status.joy2h = joy2 >> 8;
|
||||
|
||||
status.joy3l = 0x00;
|
||||
status.joy3h = 0x00;
|
||||
status.joy3l = joy3;
|
||||
status.joy3h = joy3 >> 8;
|
||||
|
||||
status.joy4l = 0x00;
|
||||
status.joy4h = 0x00;
|
||||
status.joy4l = joy4;
|
||||
status.joy4h = joy4 >> 8;
|
||||
}
|
||||
|
||||
#endif //ifdef SCPU_CPP
|
||||
#endif //ifdef SCPU_CPP
|
||||
|
@@ -1,255 +1,186 @@
|
||||
#ifdef SCPU_CPP
|
||||
|
||||
#define ntsc_color_burst_phase_shift_scanline() ( \
|
||||
snes.region() == SNES::NTSC && status.vcounter == 240 && \
|
||||
ppu.interlace() == false && ppu.field() == 1 \
|
||||
)
|
||||
|
||||
#include "irq.cpp"
|
||||
#include "joypad.cpp"
|
||||
|
||||
uint16 sCPU::vcounter() { return status.vcounter; }
|
||||
uint16 sCPU::hcounter() { return status.hcounter; }
|
||||
uint sCPU::dma_counter() { return (status.dma_counter + status.hcounter) & 7; }
|
||||
|
||||
/*****
|
||||
* One PPU dot = 4 CPU clocks
|
||||
*
|
||||
* PPU dots 323 and 327 are 6 CPU clocks long.
|
||||
* This does not apply to NTSC non-interlace scanline 240 on odd fields. This is
|
||||
* because the PPU skips one dot to alter the color burst phase of the video signal.
|
||||
*
|
||||
* Dot 323 range = { 1292, 1294, 1296 }
|
||||
* Dot 327 range = { 1310, 1312, 1314 }
|
||||
*****/
|
||||
uint16 sCPU::hdot() {
|
||||
if(ntsc_color_burst_phase_shift_scanline() == true) return (status.hcounter >> 2);
|
||||
return (status.hcounter - ((status.hcounter > 1292) << 1) - ((status.hcounter > 1310) << 1)) >> 2;
|
||||
}
|
||||
|
||||
void sCPU::add_clocks(uint clocks) {
|
||||
if(status.dram_refreshed == false) {
|
||||
if(status.hcounter + clocks >= status.dram_refresh_position) {
|
||||
status.dram_refreshed = true;
|
||||
clocks += 40;
|
||||
}
|
||||
}
|
||||
|
||||
counter.sub(counter.irq_delay, clocks);
|
||||
scheduler.addclocks_cpu(clocks);
|
||||
|
||||
clocks >>= 1;
|
||||
while(clocks--) {
|
||||
history.enqueue(status.vcounter, status.hcounter);
|
||||
status.hcounter += 2;
|
||||
if(status.hcounter >= status.line_clocks) scanline();
|
||||
poll_interrupts();
|
||||
}
|
||||
}
|
||||
|
||||
void sCPU::scanline() {
|
||||
status.hcounter = 0;
|
||||
status.dma_counter = (status.dma_counter + status.line_clocks) & 7;
|
||||
if(++status.vcounter >= status.field_lines) frame();
|
||||
status.line_clocks = (ntsc_color_burst_phase_shift_scanline() == false) ? 1364 : 1360;
|
||||
|
||||
//dram refresh occurs once every scanline
|
||||
status.dram_refreshed = false;
|
||||
if(cpu_version == 2) {
|
||||
if(ntsc_color_burst_phase_shift_scanline() == false) {
|
||||
if(status.dram_refresh_position == 534) {
|
||||
status.dram_refresh_position = 538;
|
||||
} else {
|
||||
status.dram_refresh_position = 534;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//hdma triggers once every visible scanline
|
||||
status.line_rendered = false;
|
||||
status.hdma_triggered = (status.vcounter <= (ppu.overscan() == false ? 224 : 239)) ? false : true;
|
||||
|
||||
ppu.scanline();
|
||||
snes.scanline();
|
||||
|
||||
update_interrupts();
|
||||
|
||||
if(status.auto_joypad_poll == true && status.vcounter == (ppu.overscan() == false ? 227 : 242)) {
|
||||
snes.input.poll();
|
||||
run_auto_joypad_poll();
|
||||
}
|
||||
}
|
||||
|
||||
void sCPU::frame() {
|
||||
ppu.frame();
|
||||
snes.frame();
|
||||
|
||||
status.vcounter = 0;
|
||||
status.field_lines = (snes.region() == SNES::NTSC ? 525 : 625) >> 1;
|
||||
//interlaced even fields have one extra scanline
|
||||
//(263+262=525 NTSC, 313+312=625 PAL)
|
||||
if(ppu.interlace() == true && ppu.field() == 0) status.field_lines++;
|
||||
|
||||
status.hdmainit_triggered = false;
|
||||
if(cpu_version == 1) {
|
||||
status.hdmainit_trigger_position = 12 + 8 - dma_counter();
|
||||
} else {
|
||||
status.hdmainit_trigger_position = 12 + dma_counter();
|
||||
}
|
||||
}
|
||||
|
||||
/*****
|
||||
* precycle_edge()
|
||||
*
|
||||
* Used for DMA/HDMA bus synchronization
|
||||
*****/
|
||||
alwaysinline void sCPU::precycle_edge() {
|
||||
if(status.dma_state == DMASTATE_CPUSYNC) {
|
||||
status.dma_state = DMASTATE_INACTIVE;
|
||||
uint n = status.clock_count - (status.dma_clocks % status.clock_count);
|
||||
add_clocks(n ? n : status.clock_count);
|
||||
}
|
||||
}
|
||||
|
||||
/*****
|
||||
* cycle_edge()
|
||||
*
|
||||
* Used to test for HDMA, which can trigger on the edge of every opcode cycle.
|
||||
*****/
|
||||
void sCPU::cycle_edge() {
|
||||
if(status.line_rendered == false) {
|
||||
if(status.hcounter >= status.line_render_position) {
|
||||
status.line_rendered = true;
|
||||
ppu.render_scanline();
|
||||
}
|
||||
}
|
||||
|
||||
if(status.hdmainit_triggered == false) {
|
||||
if(status.hcounter >= status.hdmainit_trigger_position || status.vcounter) {
|
||||
status.hdmainit_triggered = true;
|
||||
hdma_init_reset();
|
||||
if(hdma_enabled_channels()) {
|
||||
if(status.dma_state == DMASTATE_INACTIVE) {
|
||||
status.dma_state = DMASTATE_DMASYNC;
|
||||
status.hdmainit_pending = true;
|
||||
} else {
|
||||
hdma_init();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(status.hdma_triggered == false) {
|
||||
if(status.hcounter >= 1106) {
|
||||
status.hdma_triggered = true;
|
||||
if(hdma_active_channels()) {
|
||||
if(status.dma_state == DMASTATE_INACTIVE) {
|
||||
status.dma_state = DMASTATE_DMASYNC;
|
||||
status.hdma_pending = true;
|
||||
} else {
|
||||
hdma_run();
|
||||
}
|
||||
}
|
||||
}
|
||||
#include "irq.cpp"
|
||||
#include "joypad.cpp"
|
||||
|
||||
unsigned sCPU::dma_counter() {
|
||||
return (status.dma_counter + ppucounter.hcounter()) & 7;
|
||||
}
|
||||
|
||||
void sCPU::add_clocks(unsigned clocks) {
|
||||
if(status.dram_refreshed == false) {
|
||||
if(ppucounter.hcounter() + clocks >= status.dram_refresh_position) {
|
||||
status.dram_refreshed = true;
|
||||
clocks += 40;
|
||||
}
|
||||
}
|
||||
|
||||
switch(status.dma_state) {
|
||||
case DMASTATE_INACTIVE: break;
|
||||
counter.sub(counter.irq_delay, clocks);
|
||||
unsigned ticks = clocks >> 1;
|
||||
while(ticks--) {
|
||||
ppucounter.tick();
|
||||
snes.input.tick();
|
||||
poll_interrupts();
|
||||
}
|
||||
|
||||
case DMASTATE_DMASYNC: {
|
||||
status.dma_state = DMASTATE_RUN;
|
||||
} break;
|
||||
scheduler.addclocks_cpu(clocks);
|
||||
}
|
||||
|
||||
case DMASTATE_RUN: {
|
||||
status.dma_state = DMASTATE_CPUSYNC;
|
||||
status.dma_clocks = 8 - dma_counter() + 8;
|
||||
add_clocks(status.dma_clocks);
|
||||
void sCPU::scanline() {
|
||||
status.dma_counter = (status.dma_counter + status.line_clocks) & 7;
|
||||
status.line_clocks = ppucounter.lineclocks();
|
||||
if(ppucounter.vcounter() == 0) frame();
|
||||
|
||||
if(status.hdmainit_pending) { hdma_init(); status.hdmainit_pending = false; }
|
||||
if(status.hdma_pending) { hdma_run(); status.hdma_pending = false; }
|
||||
if(status.dma_pending) { dma_run(); status.dma_pending = false; }
|
||||
//dram refresh occurs once every scanline
|
||||
status.dram_refreshed = false;
|
||||
if(cpu_version == 2) status.dram_refresh_position = 530 + 8 - dma_counter();
|
||||
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
/*****
|
||||
* last_cycle()
|
||||
*
|
||||
* Used to test for NMI/IRQ, which can trigger on the edge of every opcode.
|
||||
* Test one cycle early to simulate two-stage pipeline of x816 CPU.
|
||||
*
|
||||
* status.irq_delay is used to simulate hardware delay before interrupts can
|
||||
* trigger during certain events (immediately after DMA, writes to $4200, etc)
|
||||
*****/
|
||||
void sCPU::last_cycle() {
|
||||
if(counter.irq_delay) return;
|
||||
|
||||
status.nmi_pending |= nmi_test();
|
||||
status.irq_pending |= irq_test();
|
||||
|
||||
event.irq = (status.nmi_pending || status.irq_pending);
|
||||
}
|
||||
|
||||
void sCPU::timing_power() {
|
||||
}
|
||||
|
||||
void sCPU::timing_reset() {
|
||||
counter.nmi_hold = 0;
|
||||
counter.irq_hold = 0;
|
||||
|
||||
counter.nmi_fire = 0;
|
||||
counter.irq_fire = 0;
|
||||
counter.irq_delay = 0;
|
||||
counter.hw_math = 0;
|
||||
|
||||
status.clock_count = 0;
|
||||
|
||||
status.vcounter = 0;
|
||||
status.hcounter = 0;
|
||||
|
||||
status.field_lines = (snes.region() == SNES::NTSC ? 525 : 625) >> 1;
|
||||
status.line_clocks = 1364;
|
||||
|
||||
status.line_rendered = false;
|
||||
status.line_render_position = min(1112U, (uint)config::ppu.hack.render_scanline_position);
|
||||
|
||||
status.dram_refreshed = false;
|
||||
status.dram_refresh_position = (cpu_version == 1) ? 530 : 538;
|
||||
|
||||
status.hdmainit_triggered = false;
|
||||
status.hdmainit_trigger_position = 0;
|
||||
|
||||
status.hdma_triggered = false;
|
||||
|
||||
status.irq_delay = 0;
|
||||
|
||||
status.nmi_valid = false;
|
||||
status.nmi_line = false;
|
||||
status.nmi_transition = false;
|
||||
status.nmi_pending = false;
|
||||
|
||||
status.irq_valid = false;
|
||||
status.irq_line = false;
|
||||
status.irq_transition = false;
|
||||
status.irq_pending = false;
|
||||
|
||||
update_interrupts();
|
||||
|
||||
status.dma_counter = 0;
|
||||
status.dma_state = DMASTATE_INACTIVE;
|
||||
status.dma_pending = false;
|
||||
status.hdma_pending = false;
|
||||
status.hdmainit_pending = false;
|
||||
//hdma triggers once every visible scanline
|
||||
status.hdma_triggered = (ppucounter.vcounter() <= (ppu.overscan() == false ? 224 : 239)) ? false : true;
|
||||
|
||||
history.reset();
|
||||
|
||||
//initial latch values for $213c/$213d
|
||||
//[x]0035 : [y]0000 (53.0 -> 212) [lda $2137]
|
||||
//[x]0038 : [y]0000 (56.5 -> 226) [nop : lda $2137]
|
||||
add_clocks(186);
|
||||
}
|
||||
|
||||
#undef ntsc_color_burst_phase_shift_scanline
|
||||
update_interrupts();
|
||||
|
||||
#endif //ifdef SCPU_CPP
|
||||
if(status.auto_joypad_poll == true && ppucounter.vcounter() == (ppu.overscan() == false ? 227 : 242)) {
|
||||
snes.input.poll();
|
||||
run_auto_joypad_poll();
|
||||
}
|
||||
}
|
||||
|
||||
void sCPU::frame() {
|
||||
status.hdmainit_triggered = false;
|
||||
if(cpu_version == 1) {
|
||||
status.hdmainit_trigger_position = 12 + 8 - dma_counter();
|
||||
} else {
|
||||
status.hdmainit_trigger_position = 12 + dma_counter();
|
||||
}
|
||||
}
|
||||
|
||||
//used for H/DMA bus synchronization
|
||||
void sCPU::precycle_edge() {
|
||||
if(status.dma_state == DMA_CPUsync) {
|
||||
add_clocks(status.clock_count - (status.dma_clocks % status.clock_count));
|
||||
status.dma_state = DMA_Inactive;
|
||||
}
|
||||
}
|
||||
|
||||
//used to test for H/DMA, which can trigger on the edge of every opcode cycle.
|
||||
void sCPU::cycle_edge() {
|
||||
if(status.hdmainit_triggered == false) {
|
||||
if(ppucounter.hcounter() >= status.hdmainit_trigger_position || ppucounter.vcounter()) {
|
||||
status.hdmainit_triggered = true;
|
||||
hdma_init_reset();
|
||||
if(hdma_enabled_channels()) {
|
||||
status.hdma_pending = true;
|
||||
status.hdma_mode = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(status.hdma_triggered == false) {
|
||||
if(ppucounter.hcounter() >= 1104) {
|
||||
status.hdma_triggered = true;
|
||||
if(hdma_active_channels()) {
|
||||
status.hdma_pending = true;
|
||||
status.hdma_mode = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//H/DMA pending && DMA inactive?
|
||||
//.. Run one full CPU cycle
|
||||
//.. HDMA pending && HDMA enabled ? DMA sync + HDMA run
|
||||
//.. DMA pending && DMA enabled ? DMA sync + DMA run
|
||||
//.... HDMA during DMA && HDMA enabled ? DMA sync + HDMA run
|
||||
//.. Run one bus CPU cycle
|
||||
//.. CPU sync
|
||||
|
||||
if(status.dma_state == DMA_Run) {
|
||||
if(status.hdma_pending) {
|
||||
status.hdma_pending = false;
|
||||
if(hdma_enabled_channels()) {
|
||||
dma_add_clocks(8 - dma_counter()); //DMA sync
|
||||
status.hdma_mode == 0 ? hdma_init() : hdma_run();
|
||||
if(!dma_enabled_channels()) status.dma_state = DMA_CPUsync;
|
||||
}
|
||||
}
|
||||
|
||||
if(status.dma_pending) {
|
||||
status.dma_pending = false;
|
||||
if(dma_enabled_channels()) {
|
||||
dma_add_clocks(8 - dma_counter()); //DMA sync
|
||||
dma_run();
|
||||
status.dma_state = DMA_CPUsync;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(status.dma_state == DMA_Inactive) {
|
||||
if(status.dma_pending || status.hdma_pending) {
|
||||
status.dma_clocks = 0;
|
||||
status.dma_state = DMA_Run;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//used to test for NMI/IRQ, which can trigger on the edge of every opcode.
|
||||
//test one cycle early to simulate two-stage pipeline of x816 CPU.
|
||||
//
|
||||
//status.irq_delay is used to simulate hardware delay before interrupts can
|
||||
//trigger during certain events (immediately after DMA, writes to $4200, etc)
|
||||
void sCPU::last_cycle() {
|
||||
if(counter.irq_delay) return;
|
||||
|
||||
status.nmi_pending |= nmi_test();
|
||||
status.irq_pending |= irq_test();
|
||||
|
||||
event.irq = (status.nmi_pending || status.irq_pending);
|
||||
}
|
||||
|
||||
void sCPU::timing_power() {
|
||||
}
|
||||
|
||||
void sCPU::timing_reset() {
|
||||
counter.nmi_hold = 0;
|
||||
counter.irq_hold = 0;
|
||||
|
||||
counter.nmi_fire = 0;
|
||||
counter.irq_fire = 0;
|
||||
counter.irq_delay = 0;
|
||||
counter.hw_math = 0;
|
||||
|
||||
status.clock_count = 0;
|
||||
status.line_clocks = ppucounter.lineclocks();
|
||||
|
||||
status.dram_refreshed = false;
|
||||
status.dram_refresh_position = (cpu_version == 1) ? 530 : 538;
|
||||
|
||||
status.hdmainit_triggered = false;
|
||||
status.hdmainit_trigger_position = 0;
|
||||
|
||||
status.hdma_triggered = false;
|
||||
|
||||
status.irq_delay = 0;
|
||||
|
||||
status.nmi_valid = false;
|
||||
status.nmi_line = false;
|
||||
status.nmi_transition = false;
|
||||
status.nmi_pending = false;
|
||||
|
||||
status.irq_valid = false;
|
||||
status.irq_line = false;
|
||||
status.irq_transition = false;
|
||||
status.irq_pending = false;
|
||||
|
||||
update_interrupts();
|
||||
|
||||
status.dma_counter = 0;
|
||||
status.dma_clocks = 0;
|
||||
status.dma_pending = false;
|
||||
status.hdma_pending = false;
|
||||
status.hdma_mode = 0;
|
||||
status.dma_state = DMA_Inactive;
|
||||
}
|
||||
|
||||
#undef ntsc_color_burst_phase_shift_scanline
|
||||
|
||||
#endif //ifdef SCPU_CPP
|
||||
|
@@ -1,57 +0,0 @@
|
||||
uint16 vcounter();
|
||||
uint16 hcounter();
|
||||
uint16 hdot();
|
||||
uint dma_counter();
|
||||
|
||||
void add_clocks(uint clocks);
|
||||
void scanline();
|
||||
void frame();
|
||||
|
||||
void precycle_edge();
|
||||
void cycle_edge();
|
||||
void last_cycle();
|
||||
uint32 clocks_executed();
|
||||
|
||||
void timing_power();
|
||||
void timing_reset();
|
||||
|
||||
//timeshifting -- needed by NMI and IRQ timing
|
||||
struct History {
|
||||
struct Time {
|
||||
uint16 vcounter;
|
||||
uint16 hcounter;
|
||||
} time[32];
|
||||
unsigned index;
|
||||
alwaysinline void enqueue(uint16 vcounter, uint16 hcounter) {
|
||||
Time &t = time[index++];
|
||||
index &= 31;
|
||||
t.vcounter = vcounter;
|
||||
t.hcounter = hcounter;
|
||||
}
|
||||
alwaysinline void query(unsigned offset, uint16 &vcounter, uint16 &hcounter) {
|
||||
Time &t = time[(index - (offset >> 1)) & 31];
|
||||
vcounter = t.vcounter;
|
||||
hcounter = t.hcounter;
|
||||
}
|
||||
void reset() {
|
||||
index = 0;
|
||||
for(unsigned i = 0; i < 32; i++) time[i].vcounter = time[i].hcounter = 0;
|
||||
}
|
||||
History() { reset(); }
|
||||
} history;
|
||||
|
||||
//irq.cpp
|
||||
enum { IRQ_TRIGGER_NEVER = 0x3fff };
|
||||
void update_interrupts();
|
||||
void poll_interrupts();
|
||||
void nmitimen_update(uint8 data);
|
||||
void hvtime_update(uint16 addr);
|
||||
bool rdnmi();
|
||||
bool timeup();
|
||||
|
||||
bool irq_pos_valid();
|
||||
bool nmi_test();
|
||||
bool irq_test();
|
||||
|
||||
//joypad.cpp
|
||||
void run_auto_joypad_poll();
|
27
src/cpu/scpu/timing/timing.hpp
Normal file
27
src/cpu/scpu/timing/timing.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
//timing.cpp
|
||||
unsigned dma_counter();
|
||||
|
||||
void add_clocks(unsigned clocks);
|
||||
void scanline();
|
||||
void frame();
|
||||
|
||||
void precycle_edge();
|
||||
void cycle_edge();
|
||||
void last_cycle();
|
||||
|
||||
void timing_power();
|
||||
void timing_reset();
|
||||
|
||||
//irq.cpp
|
||||
void update_interrupts();
|
||||
void poll_interrupts();
|
||||
void nmitimen_update(uint8 data);
|
||||
void hvtime_update(uint16 addr);
|
||||
bool rdnmi();
|
||||
bool timeup();
|
||||
|
||||
bool nmi_test();
|
||||
bool irq_test();
|
||||
|
||||
//joypad.cpp
|
||||
void run_auto_joypad_poll();
|
@@ -1,4 +1,4 @@
|
||||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define ADSP_CPP
|
||||
|
||||
#include "adsp_tables.cpp"
|
||||
|
@@ -1,681 +0,0 @@
|
||||
/* This code is heavily customized for bsnes and requires cothreads.
|
||||
Original portable snes_spc library available at http://www.slack.net/~ant/
|
||||
Copyright (C) 2007 Shay Green. See license.txt. */
|
||||
|
||||
#include "../../base.h"
|
||||
|
||||
int const brr_block_size = 9;
|
||||
|
||||
// Accesses global DSP register
|
||||
#define REG(n) m.regs [r_##n]
|
||||
|
||||
// Accesses voice DSP register
|
||||
#define VREG(r,n) r [v_##n]
|
||||
|
||||
// Volume registers and efb are signed! Easy to forget int8 cast.
|
||||
// Prefixes are to avoid accidental use of locals with same names.
|
||||
|
||||
// Gaussian interpolation
|
||||
|
||||
static short const gauss [512] =
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2,
|
||||
2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5,
|
||||
6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10,
|
||||
11, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17,
|
||||
18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26, 27, 27,
|
||||
28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 36, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
|
||||
58, 59, 60, 61, 62, 64, 65, 66, 67, 69, 70, 71, 73, 74, 76, 77,
|
||||
78, 80, 81, 83, 84, 86, 87, 89, 90, 92, 94, 95, 97, 99, 100, 102,
|
||||
104, 106, 107, 109, 111, 113, 115, 117, 118, 120, 122, 124, 126, 128, 130, 132,
|
||||
134, 137, 139, 141, 143, 145, 147, 150, 152, 154, 156, 159, 161, 163, 166, 168,
|
||||
171, 173, 175, 178, 180, 183, 186, 188, 191, 193, 196, 199, 201, 204, 207, 210,
|
||||
212, 215, 218, 221, 224, 227, 230, 233, 236, 239, 242, 245, 248, 251, 254, 257,
|
||||
260, 263, 267, 270, 273, 276, 280, 283, 286, 290, 293, 297, 300, 304, 307, 311,
|
||||
314, 318, 321, 325, 328, 332, 336, 339, 343, 347, 351, 354, 358, 362, 366, 370,
|
||||
374, 378, 381, 385, 389, 393, 397, 401, 405, 410, 414, 418, 422, 426, 430, 434,
|
||||
439, 443, 447, 451, 456, 460, 464, 469, 473, 477, 482, 486, 491, 495, 499, 504,
|
||||
508, 513, 517, 522, 527, 531, 536, 540, 545, 550, 554, 559, 563, 568, 573, 577,
|
||||
582, 587, 592, 596, 601, 606, 611, 615, 620, 625, 630, 635, 640, 644, 649, 654,
|
||||
659, 664, 669, 674, 678, 683, 688, 693, 698, 703, 708, 713, 718, 723, 728, 732,
|
||||
737, 742, 747, 752, 757, 762, 767, 772, 777, 782, 787, 792, 797, 802, 806, 811,
|
||||
816, 821, 826, 831, 836, 841, 846, 851, 855, 860, 865, 870, 875, 880, 884, 889,
|
||||
894, 899, 904, 908, 913, 918, 923, 927, 932, 937, 941, 946, 951, 955, 960, 965,
|
||||
969, 974, 978, 983, 988, 992, 997,1001,1005,1010,1014,1019,1023,1027,1032,1036,
|
||||
1040,1045,1049,1053,1057,1061,1066,1070,1074,1078,1082,1086,1090,1094,1098,1102,
|
||||
1106,1109,1113,1117,1121,1125,1128,1132,1136,1139,1143,1146,1150,1153,1157,1160,
|
||||
1164,1167,1170,1174,1177,1180,1183,1186,1190,1193,1196,1199,1202,1205,1207,1210,
|
||||
1213,1216,1219,1221,1224,1227,1229,1232,1234,1237,1239,1241,1244,1246,1248,1251,
|
||||
1253,1255,1257,1259,1261,1263,1265,1267,1269,1270,1272,1274,1275,1277,1279,1280,
|
||||
1282,1283,1284,1286,1287,1288,1290,1291,1292,1293,1294,1295,1296,1297,1297,1298,
|
||||
1299,1300,1300,1301,1302,1302,1303,1303,1303,1304,1304,1304,1304,1304,1305,1305,
|
||||
};
|
||||
|
||||
|
||||
//// Counters
|
||||
|
||||
int const simple_counter_range = 2048 * 5 * 3; // 30720
|
||||
|
||||
static unsigned const counter_rates [32] =
|
||||
{
|
||||
simple_counter_range + 1, // never fires
|
||||
2048, 1536,
|
||||
1280, 1024, 768,
|
||||
640, 512, 384,
|
||||
320, 256, 192,
|
||||
160, 128, 96,
|
||||
80, 64, 48,
|
||||
40, 32, 24,
|
||||
20, 16, 12,
|
||||
10, 8, 6,
|
||||
5, 4, 3,
|
||||
2,
|
||||
1
|
||||
};
|
||||
|
||||
static unsigned const counter_offsets [32] =
|
||||
{
|
||||
1, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
0,
|
||||
0
|
||||
};
|
||||
|
||||
inline unsigned bDSP::read_counter( int rate )
|
||||
{
|
||||
return ((unsigned) m.counter + counter_offsets [rate]) % counter_rates [rate];
|
||||
}
|
||||
|
||||
|
||||
//// Envelope
|
||||
|
||||
inline void bDSP::run_envelope( voice_t* const v )
|
||||
{
|
||||
int env = v->env;
|
||||
if ( v->env_mode == env_release )
|
||||
{
|
||||
if ( (env -= 0x8) < 0 )
|
||||
env = 0;
|
||||
v->env = env;
|
||||
}
|
||||
else
|
||||
{
|
||||
int rate;
|
||||
int env_data = VREG(v->regs,adsr1);
|
||||
if ( m.t_adsr0 & 0x80 ) // ADSR
|
||||
{
|
||||
if ( v->env_mode >= env_decay )
|
||||
{
|
||||
env--;
|
||||
env -= asr<8>( env );
|
||||
rate = env_data & 0x1F;
|
||||
if ( v->env_mode == env_decay )
|
||||
rate = (m.t_adsr0 >> 3 & 0x0E) + 0x10;
|
||||
}
|
||||
else // env_attack
|
||||
{
|
||||
rate = (m.t_adsr0 & 0x0F) * 2 + 1;
|
||||
env += rate < 31 ? 0x20 : 0x400;
|
||||
}
|
||||
}
|
||||
else // GAIN
|
||||
{
|
||||
env_data = VREG(v->regs,gain);
|
||||
int mode = env_data >> 5;
|
||||
if ( mode < 4 ) // direct
|
||||
{
|
||||
env = env_data * 0x10;
|
||||
rate = 31;
|
||||
}
|
||||
else
|
||||
{
|
||||
rate = env_data & 0x1F;
|
||||
if ( mode == 4 ) // 4: linear decrease
|
||||
{
|
||||
env -= 0x20;
|
||||
}
|
||||
else if ( mode < 6 ) // 5: exponential decrease
|
||||
{
|
||||
env--;
|
||||
env -= asr<8>( env );
|
||||
}
|
||||
else // 6,7: linear increase
|
||||
{
|
||||
env += 0x20;
|
||||
if ( mode > 6 && (unsigned) v->hidden_env >= 0x600 )
|
||||
env += 0x8 - 0x20; // 7: two-slope linear increase
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sustain level
|
||||
if ( (env >> 8) == (env_data >> 5) && v->env_mode == env_decay )
|
||||
v->env_mode = env_sustain;
|
||||
|
||||
v->hidden_env = env;
|
||||
|
||||
// unsigned cast because linear decrease going negative also triggers this
|
||||
if ( (unsigned) env > 0x7FF )
|
||||
{
|
||||
env = (env < 0 ? 0 : 0x7FF);
|
||||
if ( v->env_mode == env_attack )
|
||||
v->env_mode = env_decay;
|
||||
}
|
||||
|
||||
if ( !read_counter( rate ) )
|
||||
v->env = env; // nothing else is controlled by the counter
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// BRR Decoding
|
||||
|
||||
inline void bDSP::decode_brr( voice_t* v )
|
||||
{
|
||||
// Arrange the four input nybbles in 0xABCD order for easy decoding
|
||||
int nybbles = m.t_brr_byte * 0x100 + ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF];
|
||||
|
||||
// Write to next four samples in circular buffer
|
||||
int* pos = &v->buf [v->buf_pos];
|
||||
if ( (v->buf_pos += 4) >= brr_buf_size )
|
||||
v->buf_pos = 0;
|
||||
|
||||
// Decode four samples
|
||||
for ( int* end = pos + 4; pos < end; pos++ )
|
||||
{
|
||||
// Extract nybble and sign-extend
|
||||
int s = asr<12>( sclip<16>( nybbles ) );
|
||||
nybbles <<= 4;
|
||||
|
||||
// Shift sample based on header
|
||||
int const shift = m.t_brr_header >> 4;
|
||||
s = asr<1>( s << shift );
|
||||
if ( shift >= 0xD ) // handle invalid range
|
||||
s = (s < 0 ? -0x800 : 0);
|
||||
|
||||
// Apply IIR filter (8 is the most commonly used)
|
||||
int const p1 = pos [brr_buf_size - 1];
|
||||
int const p2 = asr<1>( pos [brr_buf_size - 2] );
|
||||
switch ( m.t_brr_header >> 2 & 3 )
|
||||
{
|
||||
case 1: s += asr<1>( p1 ) + asr<5>( -p1 ); break; // s += p1 * 0.4687500
|
||||
case 2: s += p1 + asr<6>( p1 * -3 ) - p2 + asr<4>( p2 ); break; // s += p1 * 0.9531250 - p2 * 0.46875
|
||||
case 3: s += p1 + asr<7>( p1 * -13 ) - p2 + asr<4>( p2 * 3 ); break; // s += p1 * 0.8984375 - p2 * 0.40625
|
||||
}
|
||||
|
||||
// Adjust and write sample
|
||||
s = sclip<16>( sclamp<16>( s ) * 2 );
|
||||
pos [brr_buf_size] = pos [0] = s; // second copy simplifies wrap-around
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// Voices
|
||||
|
||||
#define VOICE_CLOCK( n ) void bDSP::voice_##n( voice_t* const v )
|
||||
|
||||
inline VOICE_CLOCK( V1 )
|
||||
{
|
||||
m.t_dir_addr = m.t_dir * 0x100 + m.t_srcn * 4;
|
||||
m.t_srcn = VREG(v->regs,srcn);
|
||||
}
|
||||
inline VOICE_CLOCK( V2 )
|
||||
{
|
||||
// Read sample pointer (ignored if not needed)
|
||||
uint8 const* entry = &ram [m.t_dir_addr];
|
||||
if ( !v->kon_delay )
|
||||
entry += 2;
|
||||
m.t_brr_next_addr = entry [0] | entry [1] << 8;
|
||||
|
||||
m.t_adsr0 = VREG(v->regs,adsr0);
|
||||
|
||||
// Read pitch, spread over two clocks
|
||||
m.t_pitch = VREG(v->regs,pitchl);
|
||||
}
|
||||
inline VOICE_CLOCK( V3a )
|
||||
{
|
||||
m.t_pitch += (VREG(v->regs,pitchh) & 0x3F) << 8;
|
||||
}
|
||||
inline VOICE_CLOCK( V3b )
|
||||
{
|
||||
// Read BRR header and byte
|
||||
m.t_brr_byte = ram [(v->brr_addr + v->brr_offset) & 0xFFFF];
|
||||
m.t_brr_header = ram [v->brr_addr]; // brr_addr doesn't need masking
|
||||
}
|
||||
VOICE_CLOCK( V3c )
|
||||
{
|
||||
// Pitch modulation using previous voice's output
|
||||
if ( m.t_pmon & v->vbit )
|
||||
m.t_pitch += asr<10>( asr<5>( m.t_output ) * m.t_pitch );
|
||||
|
||||
if ( v->kon_delay )
|
||||
{
|
||||
// Get ready to start BRR decoding on next sample
|
||||
if ( v->kon_delay == 5 )
|
||||
{
|
||||
v->brr_addr = m.t_brr_next_addr;
|
||||
v->brr_offset = 1;
|
||||
v->buf_pos = 0;
|
||||
m.t_brr_header = 0; // header is ignored on this sample
|
||||
}
|
||||
|
||||
// Envelope is never run during KON
|
||||
v->env = 0;
|
||||
v->hidden_env = 0;
|
||||
|
||||
// Disable BRR decoding until last three samples
|
||||
v->interp_pos = 0;
|
||||
if ( --v->kon_delay & 3 )
|
||||
v->interp_pos = 0x4000;
|
||||
|
||||
// Pitch is never added during KON
|
||||
m.t_pitch = 0;
|
||||
}
|
||||
|
||||
// Gaussian interpolation
|
||||
{
|
||||
// Make pointers into gaussian based on fractional position between samples
|
||||
int offset = v->interp_pos >> 4 & 0xFF;
|
||||
short const* fwd = gauss + 255 - offset;
|
||||
short const* rev = gauss + offset; // mirror left half of gaussian
|
||||
|
||||
int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos];
|
||||
int out;
|
||||
out = asr<11>( fwd [ 0] * in [0] );
|
||||
out += asr<11>( fwd [256] * in [1] );
|
||||
out += asr<11>( rev [256] * in [2] );
|
||||
out = sclip<16>( out );
|
||||
out += asr<11>( rev [ 0] * in [3] );
|
||||
|
||||
out = sclamp<16>( out ) & ~1;
|
||||
|
||||
// Noise
|
||||
if ( m.t_non & v->vbit )
|
||||
out = sclip<16>( m.noise * 2 );
|
||||
|
||||
// Apply envelope
|
||||
m.t_output = asr<11>( out * v->env ) & ~1;
|
||||
v->t_envx_out = (uint8) (v->env >> 4);
|
||||
}
|
||||
|
||||
// Immediate silence due to end of sample or soft reset
|
||||
if ( REG(flg) & 0x80 || (m.t_brr_header & 3) == 1 )
|
||||
{
|
||||
v->env_mode = env_release;
|
||||
v->env = 0;
|
||||
}
|
||||
|
||||
if ( m.every_other_sample )
|
||||
{
|
||||
// KOFF
|
||||
if ( m.t_koff & v->vbit )
|
||||
v->env_mode = env_release;
|
||||
|
||||
// KON
|
||||
if ( m.kon & v->vbit )
|
||||
{
|
||||
v->kon_delay = 5;
|
||||
v->env_mode = env_attack;
|
||||
}
|
||||
}
|
||||
|
||||
// Run envelope for next sample
|
||||
if ( !v->kon_delay )
|
||||
run_envelope( v );
|
||||
}
|
||||
inline void bDSP::voice_output( voice_t const* v, int ch )
|
||||
{
|
||||
// Apply left/right volume
|
||||
int amp = asr<7>( m.t_output * (int8) VREG(v->regs,voll + ch) );
|
||||
|
||||
// Add to output total
|
||||
m.t_main_out [ch] = sclamp<16>( m.t_main_out [ch] + amp );
|
||||
|
||||
// Optionally add to echo total
|
||||
if ( m.t_eon & v->vbit )
|
||||
m.t_echo_out [ch] = sclamp<16>( m.t_echo_out [ch] + amp );
|
||||
}
|
||||
VOICE_CLOCK( V4 )
|
||||
{
|
||||
// Decode BRR
|
||||
m.t_looped = 0;
|
||||
if ( v->interp_pos >= 0x4000 )
|
||||
{
|
||||
decode_brr( v );
|
||||
|
||||
if ( (v->brr_offset += 2) >= brr_block_size )
|
||||
{
|
||||
// Start decoding next BRR block
|
||||
assert( v->brr_offset == brr_block_size );
|
||||
v->brr_addr = (v->brr_addr + brr_block_size) & 0xFFFF;
|
||||
if ( m.t_brr_header & 1 )
|
||||
{
|
||||
v->brr_addr = m.t_brr_next_addr;
|
||||
m.t_looped = v->vbit;
|
||||
}
|
||||
v->brr_offset = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply pitch
|
||||
v->interp_pos = (v->interp_pos & 0x3FFF) + m.t_pitch;
|
||||
|
||||
// Keep from getting too far ahead (when using pitch modulation)
|
||||
if ( v->interp_pos > 0x7FFF )
|
||||
v->interp_pos = 0x7FFF;
|
||||
|
||||
// Output left
|
||||
voice_output( v, 0 );
|
||||
}
|
||||
inline VOICE_CLOCK( V5 )
|
||||
{
|
||||
// Output right
|
||||
voice_output( v, 1 );
|
||||
|
||||
// ENDX, OUTX, and ENVX won't update if you wrote to them 1-2 clocks earlier
|
||||
int endx_buf = REG(endx) | m.t_looped;
|
||||
|
||||
// Clear bit in ENDX if KON just began
|
||||
if ( v->kon_delay == 5 )
|
||||
endx_buf &= ~v->vbit;
|
||||
m.endx_buf = (uint8) endx_buf;
|
||||
}
|
||||
inline VOICE_CLOCK( V6 )
|
||||
{
|
||||
(void) v; // avoid compiler warning about unused v
|
||||
m.outx_buf = (uint8) (m.t_output >> 8);
|
||||
}
|
||||
inline VOICE_CLOCK( V7 )
|
||||
{
|
||||
// Update ENDX
|
||||
REG(endx) = m.endx_buf;
|
||||
|
||||
m.envx_buf = v->t_envx_out;
|
||||
}
|
||||
inline VOICE_CLOCK( V8 )
|
||||
{
|
||||
// Update OUTX
|
||||
VREG(v->regs,outx) = m.outx_buf;
|
||||
}
|
||||
inline VOICE_CLOCK( V9 )
|
||||
{
|
||||
// Update ENVX
|
||||
VREG(v->regs,envx) = m.envx_buf;
|
||||
}
|
||||
|
||||
// Most voices do all these in one clock, so make a handy composite
|
||||
inline VOICE_CLOCK( V3 )
|
||||
{
|
||||
voice_V3a( v );
|
||||
voice_V3b( v );
|
||||
voice_V3c( v );
|
||||
}
|
||||
|
||||
// Common combinations of voice steps on different voices. This greatly reduces
|
||||
// code size and allows everything to be inlined in these functions.
|
||||
VOICE_CLOCK(V7_V4_V1) { voice_V7(v); voice_V1(v+3); voice_V4(v+1); }
|
||||
VOICE_CLOCK(V8_V5_V2) { voice_V8(v); voice_V5(v+1); voice_V2(v+2); }
|
||||
VOICE_CLOCK(V9_V6_V3) { voice_V9(v); voice_V6(v+1); voice_V3(v+2); }
|
||||
|
||||
|
||||
//// Echo
|
||||
|
||||
// Current echo buffer pointer for left/right channel
|
||||
#define ECHO_PTR( ch ) (&ram [echo_ptr + ch * 2])
|
||||
|
||||
// Sample in echo history buffer, where 0 is the oldest
|
||||
#define ECHO_FIR( i ) (m.echo_hist [(m.echo_hist_pos + (i)) & (echo_hist_size - 1)])
|
||||
|
||||
// Calculate FIR point for left/right channel
|
||||
#define CALC_FIR( i, ch ) asr<6>( ECHO_FIR( i + 1 ) [ch] * (int8) REG(fir + i * 0x10) )
|
||||
|
||||
inline int get_echo_sample( void const* p )
|
||||
{
|
||||
return ((uint8 const*) p) [0] |
|
||||
(( int8 const*) p) [1] << 8;
|
||||
}
|
||||
|
||||
inline void set_echo_sample( void* p, unsigned n )
|
||||
{
|
||||
((uint8*) p) [0] = (uint8) n;
|
||||
((uint8*) p) [1] = (uint8) (n >> 8);
|
||||
}
|
||||
|
||||
inline int bDSP::calc_echo_output( int ch, int echo_in )
|
||||
{
|
||||
return sclamp<16>(
|
||||
sclip<16>( asr<7>( m.t_main_out [ch] * (int8) REG(mvoll + ch * 0x10) ) ) +
|
||||
sclip<16>( asr<7>( echo_in * (int8) REG(evoll + ch * 0x10) ) ) );
|
||||
}
|
||||
|
||||
|
||||
//// Timing
|
||||
|
||||
void bDSP::enter()
|
||||
{
|
||||
int t_esa = REG(esa);
|
||||
|
||||
while ( 1 )
|
||||
{
|
||||
// n is currently ignored
|
||||
#define NEXT_CLOCK( n ) \
|
||||
scheduler.addclocks_dsp( 3 * 8 );
|
||||
|
||||
// Execute clock for a particular voice
|
||||
#define V( clock, voice ) voice_##clock( &m.voices [voice] );
|
||||
|
||||
/* The most common sequence of clocks uses composite operations
|
||||
for efficiency. For example, the following are equivalent to the
|
||||
individual steps on the right:
|
||||
|
||||
V(V7_V4_V1,2) -> V(V7,2) V(V4,3) V(V1,5)
|
||||
V(V8_V5_V2,2) -> V(V8,2) V(V5,3) V(V2,4)
|
||||
V(V9_V6_V3,2) -> V(V9,2) V(V6,3) V(V3,4) */
|
||||
|
||||
NEXT_CLOCK( 0) V(V5,0)V(V2,1)
|
||||
NEXT_CLOCK( 1) V(V6,0)V(V3,1)
|
||||
NEXT_CLOCK( 2) V(V7_V4_V1,0)
|
||||
NEXT_CLOCK( 3) V(V8_V5_V2,0)
|
||||
NEXT_CLOCK( 4) V(V9_V6_V3,0)
|
||||
NEXT_CLOCK( 5) V(V7_V4_V1,1)
|
||||
NEXT_CLOCK( 6) V(V8_V5_V2,1)
|
||||
NEXT_CLOCK( 7) V(V9_V6_V3,1)
|
||||
NEXT_CLOCK( 8) V(V7_V4_V1,2)
|
||||
NEXT_CLOCK( 9) V(V8_V5_V2,2)
|
||||
NEXT_CLOCK(10) V(V9_V6_V3,2)
|
||||
NEXT_CLOCK(11) V(V7_V4_V1,3)
|
||||
NEXT_CLOCK(12) V(V8_V5_V2,3)
|
||||
NEXT_CLOCK(13) V(V9_V6_V3,3)
|
||||
NEXT_CLOCK(14) V(V7_V4_V1,4)
|
||||
NEXT_CLOCK(15) V(V8_V5_V2,4)
|
||||
NEXT_CLOCK(16) V(V9_V6_V3,4)
|
||||
NEXT_CLOCK(17) V(V1,0) V(V7,5)V(V4,6)
|
||||
NEXT_CLOCK(18) V(V8_V5_V2,5)
|
||||
NEXT_CLOCK(19) V(V9_V6_V3,5)
|
||||
NEXT_CLOCK(20) V(V1,1) V(V7,6)V(V4,7)
|
||||
NEXT_CLOCK(21) V(V8,6)V(V5,7) V(V2,0) /* t_brr_next_addr order dependency */
|
||||
NEXT_CLOCK(22) V(V3a,0) V(V9,6)V(V6,7)
|
||||
|
||||
// History
|
||||
if ( ++m.echo_hist_pos >= echo_hist_size )
|
||||
m.echo_hist_pos = 0;
|
||||
|
||||
int const echo_ptr = (t_esa * 0x100 + m.echo_offset) & 0xFFFF;
|
||||
|
||||
// FIR
|
||||
int echo_in_l = CALC_FIR( 0, 0 );
|
||||
int echo_in_r = CALC_FIR( 0, 1 );
|
||||
|
||||
ECHO_FIR( 0 ) [0] = asr<1>( get_echo_sample( ECHO_PTR( 0 ) ) );
|
||||
|
||||
NEXT_CLOCK(23) V(V7,7)
|
||||
|
||||
echo_in_l += CALC_FIR( 1, 0 ) + CALC_FIR( 2, 0 );
|
||||
echo_in_r += CALC_FIR( 1, 1 ) + CALC_FIR( 2, 1 );
|
||||
|
||||
ECHO_FIR( 0 ) [1] = asr<1>( get_echo_sample( ECHO_PTR( 1 ) ) );
|
||||
|
||||
NEXT_CLOCK(24) V(V8,7)
|
||||
|
||||
echo_in_l += CALC_FIR( 3, 0 ) + CALC_FIR( 4, 0 ) + CALC_FIR( 5, 0 );
|
||||
echo_in_r += CALC_FIR( 3, 1 ) + CALC_FIR( 4, 1 ) + CALC_FIR( 5, 1 );
|
||||
|
||||
NEXT_CLOCK(25) V(V3b,0) V(V9,7)
|
||||
|
||||
echo_in_l = sclip<16>( echo_in_l + CALC_FIR( 6, 0 ) ) + sclip<16>( CALC_FIR( 7, 0 ) );
|
||||
echo_in_r = sclip<16>( echo_in_r + CALC_FIR( 6, 1 ) ) + sclip<16>( CALC_FIR( 7, 1 ) );
|
||||
|
||||
echo_in_l = sclamp<16>( echo_in_l ) & ~1;
|
||||
echo_in_r = sclamp<16>( echo_in_r ) & ~1;
|
||||
|
||||
NEXT_CLOCK(26)
|
||||
|
||||
// Echo feedback
|
||||
int echo_out_l = m.t_echo_out [0] + sclip<16>( asr<7>( echo_in_l * (int8) REG(efb) ) );
|
||||
int echo_out_r = m.t_echo_out [1] + sclip<16>( asr<7>( echo_in_r * (int8) REG(efb) ) );
|
||||
|
||||
echo_out_l = sclamp<16>( echo_out_l ) & ~1;
|
||||
echo_out_r = sclamp<16>( echo_out_r ) & ~1;
|
||||
|
||||
// Output
|
||||
int main_out_l = calc_echo_output( 0, echo_in_l );
|
||||
|
||||
NEXT_CLOCK(27)
|
||||
|
||||
int main_out_r = calc_echo_output( 1, echo_in_r );
|
||||
|
||||
// TODO: global muting isn't this simple (turns DAC on and off
|
||||
// or something, causing small ~37-sample pulse when first muted)
|
||||
if ( REG(flg) & 0x40 )
|
||||
{
|
||||
main_out_l = 0;
|
||||
main_out_r = 0;
|
||||
}
|
||||
|
||||
// Output sample to DAC
|
||||
snes.audio.update( main_out_l, main_out_r );
|
||||
|
||||
m.t_main_out [0] = 0;
|
||||
m.t_main_out [1] = 0;
|
||||
|
||||
m.t_pmon = REG(pmon) & 0xFE; // voice 0 doesn't support PMON
|
||||
|
||||
NEXT_CLOCK(28)
|
||||
|
||||
m.t_non = REG(non);
|
||||
m.t_eon = REG(eon);
|
||||
m.t_dir = REG(dir);
|
||||
|
||||
int echo_disabled = REG(flg);
|
||||
|
||||
NEXT_CLOCK(29)
|
||||
|
||||
// Write left echo
|
||||
if ( !(echo_disabled & 0x20) )
|
||||
set_echo_sample( ECHO_PTR( 0 ), echo_out_l );
|
||||
m.t_echo_out [0] = 0;
|
||||
|
||||
t_esa = REG(esa);
|
||||
|
||||
if ( !m.echo_offset )
|
||||
m.echo_length = (REG(edl) & 0x0F) * 0x800;
|
||||
|
||||
m.echo_offset += 4;
|
||||
if ( m.echo_offset >= m.echo_length )
|
||||
m.echo_offset = 0;
|
||||
|
||||
if ( (m.every_other_sample ^= 1) != 0 )
|
||||
m.new_kon &= ~m.kon; // clears KON 63 clocks after it was last read
|
||||
|
||||
echo_disabled = REG(flg);
|
||||
|
||||
NEXT_CLOCK(30)
|
||||
|
||||
// Write right echo
|
||||
if ( !(echo_disabled & 0x20) )
|
||||
set_echo_sample( ECHO_PTR( 1 ), echo_out_r );
|
||||
m.t_echo_out [1] = 0;
|
||||
|
||||
if ( m.every_other_sample )
|
||||
{
|
||||
m.kon = m.new_kon;
|
||||
m.t_koff = REG(koff);
|
||||
}
|
||||
|
||||
if ( --m.counter < 0 )
|
||||
m.counter = simple_counter_range - 1;
|
||||
|
||||
// Noise
|
||||
if ( !read_counter( REG(flg) & 0x1F ) )
|
||||
{
|
||||
int feedback = (m.noise << 13) ^ (m.noise << 14);
|
||||
m.noise = (feedback & 0x4000) ^ (m.noise >> 1);
|
||||
}
|
||||
|
||||
V(V3c,0)
|
||||
|
||||
NEXT_CLOCK(31) V(V4,0) V(V1,2)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// Setup
|
||||
|
||||
bDSP::bDSP() { }
|
||||
|
||||
bDSP::~bDSP() { }
|
||||
|
||||
void bDSP::reset()
|
||||
{
|
||||
REG(flg) = 0xE0;
|
||||
|
||||
m.noise = 0x4000;
|
||||
m.echo_hist_pos = 0;
|
||||
m.every_other_sample = 1;
|
||||
m.echo_offset = 0;
|
||||
m.counter = 0;
|
||||
}
|
||||
|
||||
static uint8 const initial_regs [bDSP::register_count] =
|
||||
{
|
||||
0x45,0x8B,0x5A,0x9A,0xE4,0x82,0x1B,0x78,0x00,0x00,0xAA,0x96,0x89,0x0E,0xE0,0x80,
|
||||
0x2A,0x49,0x3D,0xBA,0x14,0xA0,0xAC,0xC5,0x00,0x00,0x51,0xBB,0x9C,0x4E,0x7B,0xFF,
|
||||
0xF4,0xFD,0x57,0x32,0x37,0xD9,0x42,0x22,0x00,0x00,0x5B,0x3C,0x9F,0x1B,0x87,0x9A,
|
||||
0x6F,0x27,0xAF,0x7B,0xE5,0x68,0x0A,0xD9,0x00,0x00,0x9A,0xC5,0x9C,0x4E,0x7B,0xFF,
|
||||
0xEA,0x21,0x78,0x4F,0xDD,0xED,0x24,0x14,0x00,0x00,0x77,0xB1,0xD1,0x36,0xC1,0x67,
|
||||
0x52,0x57,0x46,0x3D,0x59,0xF4,0x87,0xA4,0x00,0x00,0x7E,0x44,0x9C,0x4E,0x7B,0xFF,
|
||||
0x75,0xF5,0x06,0x97,0x10,0xC3,0x24,0xBB,0x00,0x00,0x7B,0x7A,0xE0,0x60,0x12,0x0F,
|
||||
0xF7,0x74,0x1C,0xE5,0x39,0x3D,0x73,0xC1,0x00,0x00,0x7A,0xB3,0xFF,0x4E,0x7B,0xFF
|
||||
};
|
||||
|
||||
void bDSP::power()
|
||||
{
|
||||
ram = (uint8*) smp.get_spcram_handle();
|
||||
memset( &m, 0, sizeof m );
|
||||
//memcpy( m.regs, initial_regs, sizeof m.regs );
|
||||
memset(m.regs, 0, sizeof m.regs);
|
||||
REG(flg) = 0xe0;
|
||||
|
||||
// Internal state
|
||||
for ( int i = voice_count; --i >= 0; )
|
||||
{
|
||||
voice_t* v = &m.voices [i];
|
||||
v->brr_offset = 1;
|
||||
v->vbit = 1 << i;
|
||||
v->regs = &m.regs [i * 0x10];
|
||||
}
|
||||
m.new_kon = REG(kon);
|
||||
m.t_dir = REG(dir);
|
||||
|
||||
reset();
|
||||
}
|
@@ -1,172 +0,0 @@
|
||||
class bDSP : public DSP {
|
||||
public:
|
||||
void enter();
|
||||
|
||||
uint8 read( uint8 addr );
|
||||
void write( uint8 addr, uint8 data );
|
||||
|
||||
void power();
|
||||
void reset();
|
||||
|
||||
bDSP();
|
||||
~bDSP();
|
||||
|
||||
template<int n, typename T> inline T asr(const T x) {
|
||||
enum { bits = (sizeof(T) << 3) - n };
|
||||
return sclip<bits>(x >> n);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
enum { echo_hist_size = 8 };
|
||||
enum { register_count = 128 };
|
||||
enum { voice_count = 8 };
|
||||
|
||||
enum env_mode_t { env_release, env_attack, env_decay, env_sustain };
|
||||
enum { brr_buf_size = 12 };
|
||||
struct voice_t
|
||||
{
|
||||
int buf [brr_buf_size*2];// decoded samples (twice the size to simplify wrap handling)
|
||||
int buf_pos; // place in buffer where next samples will be decoded
|
||||
int interp_pos; // relative fractional position in sample (0x1000 = 1.0)
|
||||
int brr_addr; // address of current BRR block
|
||||
int brr_offset; // current decoding offset in BRR block
|
||||
uint8* regs; // pointer to voice's DSP registers
|
||||
int vbit; // bitmask for voice: 0x01 for voice 0, 0x02 for voice 1, etc.
|
||||
int kon_delay; // KON delay/current setup phase
|
||||
env_mode_t env_mode;
|
||||
int env; // current envelope level
|
||||
int hidden_env; // used by GAIN mode 7, very obscure quirk
|
||||
uint8 t_envx_out;
|
||||
};
|
||||
private:
|
||||
|
||||
struct state_t
|
||||
{
|
||||
uint8 regs [register_count];
|
||||
|
||||
// Echo history keeps most recent 8 samples
|
||||
int echo_hist [echo_hist_size] [2];
|
||||
int echo_hist_pos;
|
||||
|
||||
int every_other_sample; // toggles every sample
|
||||
int kon; // KON value when last checked
|
||||
int noise;
|
||||
int counter;
|
||||
int echo_offset; // offset from ESA in echo buffer
|
||||
int echo_length; // number of bytes that echo_offset will stop at
|
||||
|
||||
// Hidden registers also written to when main register is written to
|
||||
int new_kon;
|
||||
uint8 endx_buf;
|
||||
uint8 envx_buf;
|
||||
uint8 outx_buf;
|
||||
|
||||
// Temporary state between clocks
|
||||
|
||||
// read once per sample
|
||||
int t_pmon;
|
||||
int t_non;
|
||||
int t_eon;
|
||||
int t_dir;
|
||||
int t_koff;
|
||||
|
||||
// read a few clocks ahead then used
|
||||
int t_brr_next_addr;
|
||||
int t_adsr0;
|
||||
int t_brr_header;
|
||||
int t_brr_byte;
|
||||
int t_srcn;
|
||||
|
||||
// internal state that is recalculated every sample
|
||||
int t_dir_addr;
|
||||
int t_pitch;
|
||||
int t_output;
|
||||
int t_looped;
|
||||
|
||||
// left/right sums
|
||||
int t_main_out [2];
|
||||
int t_echo_out [2];
|
||||
|
||||
voice_t voices [voice_count];
|
||||
};
|
||||
state_t m;
|
||||
uint8* ram;
|
||||
|
||||
unsigned read_counter( int rate );
|
||||
|
||||
void run_envelope( voice_t* const v );
|
||||
void decode_brr( voice_t* v );
|
||||
|
||||
void voice_output( voice_t const* v, int ch );
|
||||
void voice_V1( voice_t* const );
|
||||
void voice_V2( voice_t* const );
|
||||
void voice_V3( voice_t* const );
|
||||
void voice_V3a( voice_t* const );
|
||||
void voice_V3b( voice_t* const );
|
||||
void voice_V3c( voice_t* const );
|
||||
void voice_V4( voice_t* const );
|
||||
void voice_V5( voice_t* const );
|
||||
void voice_V6( voice_t* const );
|
||||
void voice_V7( voice_t* const );
|
||||
void voice_V8( voice_t* const );
|
||||
void voice_V9( voice_t* const );
|
||||
void voice_V7_V4_V1( voice_t* const );
|
||||
void voice_V8_V5_V2( voice_t* const );
|
||||
void voice_V9_V6_V3( voice_t* const );
|
||||
|
||||
int calc_echo_output( int ch, int sample );
|
||||
|
||||
// Global registers
|
||||
enum {
|
||||
r_mvoll = 0x0C, r_mvolr = 0x1C,
|
||||
r_evoll = 0x2C, r_evolr = 0x3C,
|
||||
r_kon = 0x4C, r_koff = 0x5C,
|
||||
r_flg = 0x6C, r_endx = 0x7C,
|
||||
r_efb = 0x0D, r_pmon = 0x2D,
|
||||
r_non = 0x3D, r_eon = 0x4D,
|
||||
r_dir = 0x5D, r_esa = 0x6D,
|
||||
r_edl = 0x7D,
|
||||
r_fir = 0x0F // 8 coefficients at 0x0F, 0x1F ... 0x7F
|
||||
};
|
||||
|
||||
// Voice registers
|
||||
enum {
|
||||
v_voll = 0x00, v_volr = 0x01,
|
||||
v_pitchl = 0x02, v_pitchh = 0x03,
|
||||
v_srcn = 0x04, v_adsr0 = 0x05,
|
||||
v_adsr1 = 0x06, v_gain = 0x07,
|
||||
v_envx = 0x08, v_outx = 0x09
|
||||
};
|
||||
};
|
||||
|
||||
inline uint8 bDSP::read( uint8 addr )
|
||||
{
|
||||
return m.regs [addr];
|
||||
}
|
||||
|
||||
inline void bDSP::write( uint8 addr, uint8 data )
|
||||
{
|
||||
m.regs [addr] = data;
|
||||
switch ( addr & 0x0F )
|
||||
{
|
||||
case v_envx:
|
||||
m.envx_buf = data;
|
||||
break;
|
||||
|
||||
case v_outx:
|
||||
m.outx_buf = data;
|
||||
break;
|
||||
|
||||
case 0x0C:
|
||||
if ( addr == r_kon )
|
||||
m.new_kon = data;
|
||||
|
||||
if ( addr == r_endx ) // always cleared, regardless of data written
|
||||
{
|
||||
m.endx_buf = 0;
|
||||
m.regs [r_endx] = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
@@ -1,32 +0,0 @@
|
||||
#include "../../base.h"
|
||||
#include "spc_dsp.h"
|
||||
|
||||
void bDSP::power() {
|
||||
spc_dsp_init(r_smp->get_spcram_handle());
|
||||
spc_dsp_reset();
|
||||
}
|
||||
|
||||
void bDSP::reset() {
|
||||
spc_dsp_soft_reset();
|
||||
}
|
||||
|
||||
uint8 bDSP::read(uint8 addr) {
|
||||
return spc_dsp_read(addr);
|
||||
}
|
||||
|
||||
void bDSP::write(uint8 addr, uint8 data) {
|
||||
spc_dsp_write(addr, data);
|
||||
}
|
||||
|
||||
#define SPC_DSP_CUSTOM_RUN 1 //causes spc_dsp_run() to not be defined since it's huge and we don't need it
|
||||
#define SPC_DSP_OUT_HOOK(left, right) snes.audio_update(left, right);
|
||||
#include "spc_dsp.cpp"
|
||||
|
||||
void bDSP::enter() { loop:
|
||||
#define PHASE(n) scheduler.addclocks_dsp(3);
|
||||
#include "spc_dsp_timing.h"
|
||||
goto loop;
|
||||
}
|
||||
|
||||
bDSP::bDSP() {}
|
||||
bDSP::~bDSP() {}
|
@@ -1,10 +0,0 @@
|
||||
class bDSP : public DSP { public:
|
||||
void enter();
|
||||
uint8 read(uint8 addr);
|
||||
void write(uint8 addr, uint8 data);
|
||||
void power();
|
||||
void reset();
|
||||
|
||||
bDSP();
|
||||
~bDSP();
|
||||
};
|
@@ -1,64 +0,0 @@
|
||||
Overall operation
|
||||
-----------------
|
||||
This DSP emulator fundamentally emulates the different options the DSP
|
||||
performs on each clock. The pattern of operations repeats every 32
|
||||
clocks (except one minor detail, which repeats every 64 clocks instead).
|
||||
There are three main types of operations:
|
||||
|
||||
- Miscellaneous processing
|
||||
- Voice processing
|
||||
- Echo processing
|
||||
|
||||
Each is done over several clocks, and several operations are done on
|
||||
each clock. Each clock is defined as a separate function, then called
|
||||
from a large switch block in a loop.
|
||||
|
||||
Many times a value is read on one clock but not used until a later
|
||||
clock, so many non-local temporary variables are used in the code to
|
||||
store these values. These are named with t_ to make it clear that they
|
||||
don't store long-term state.
|
||||
|
||||
|
||||
Circular buffers
|
||||
----------------
|
||||
Two circular buffers are used in the code (echo history and BRR decode).
|
||||
Both need efficient index-based access with wrap-around. Things are
|
||||
greatly simplified by repeating the contents of buffer twice, so instead
|
||||
of
|
||||
|
||||
0 1 2 3 4 5 6 7
|
||||
|
||||
it stores
|
||||
|
||||
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
|
||||
|
||||
The position in this case would always be 0 to 7, so reading up to +8
|
||||
won't go outside buffer. This duplication is maintained by simply
|
||||
writing data twice when filling buffer:
|
||||
|
||||
0 1 2 3 4 # 6 7 0 1 2 3 4 # 6 7
|
||||
new data -----^---------------^
|
||||
|
||||
No wrap checking needs to be done when writing either, since the above
|
||||
reasoning holds. When making a state snapshot, only the first copy needs
|
||||
to be saved. When restoring, simply duplicate the data twice.
|
||||
|
||||
|
||||
Code
|
||||
----
|
||||
- Currently all state is in static variables. They have either a t_ or
|
||||
m_ prefix to allow easy migration to a structure.
|
||||
|
||||
- Static state that persists over several samples or more is prefixed
|
||||
with m_.
|
||||
|
||||
- State which is temporary to the current sample is prefixed with t_.
|
||||
These are usually just overwritten with new data on the next sample.
|
||||
These generally correspond to temporaries/registers in actual DSP
|
||||
itself.
|
||||
|
||||
- Minimal stdint.h included in case your system doesn't have one.
|
||||
|
||||
|
||||
--
|
||||
Shay Green <gblargg@gmail.com>
|
@@ -1,828 +0,0 @@
|
||||
// http://www.slack.net/~ant/
|
||||
|
||||
#include "spc_dsp.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Copyright (C) 2007 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
// Volume registers and efb are signed! Easy to forget int8_t cast.
|
||||
// Prefixes are to avoid accidental use of locals with same names.
|
||||
|
||||
// Global registers
|
||||
enum {
|
||||
r_mvoll = 0x0C, r_mvolr = 0x1C,
|
||||
r_evoll = 0x2C, r_evolr = 0x3C,
|
||||
r_kon = 0x4C, r_koff = 0x5C,
|
||||
r_flg = 0x6C, r_endx = 0x7C,
|
||||
r_efb = 0x0D, r_pmon = 0x2D,
|
||||
r_non = 0x3D, r_eon = 0x4D,
|
||||
r_dir = 0x5D, r_esa = 0x6D,
|
||||
r_edl = 0x7D,
|
||||
r_fir = 0x0F // 8 coefficients at 0x0F, 0x1F ... 0x7F
|
||||
};
|
||||
|
||||
// Voice registers
|
||||
enum {
|
||||
v_voll = 0x00, v_volr = 0x01,
|
||||
v_pitchl = 0x02, v_pitchh = 0x03,
|
||||
v_srcn = 0x04, v_adsr0 = 0x05,
|
||||
v_adsr1 = 0x06, v_gain = 0x07,
|
||||
v_envx = 0x08, v_outx = 0x09
|
||||
};
|
||||
|
||||
// Internal envelope modes
|
||||
enum env_mode_t { env_release, env_attack, env_decay, env_sustain };
|
||||
|
||||
// Internal voice state
|
||||
enum { brr_buf_size = 12 };
|
||||
enum { brr_block_size = 9 };
|
||||
typedef struct voice_t
|
||||
{
|
||||
int buf [brr_buf_size*2];// decoded samples (twice the size to simplify wrap handling)
|
||||
int buf_pos; // place in buffer where next samples will be decoded
|
||||
int interp_pos; // relative fractional position in sample (0x1000 = 1.0)
|
||||
int brr_addr; // address of current BRR block
|
||||
int brr_offset; // current decoding offset in BRR block
|
||||
uint8_t* regs; // pointer to voice's DSP registers
|
||||
int vbit; // bitmask for voice: 0x01 for voice 0, 0x02 for voice 1, etc.
|
||||
int kon_delay; // KON delay/current setup phase
|
||||
enum env_mode_t env_mode;
|
||||
int env; // current envelope level
|
||||
int t_envx_out;
|
||||
int hidden_env; // used by GAIN mode 7, very obscure quirk
|
||||
} voice_t;
|
||||
|
||||
static voice_t m_voice_state [spc_dsp_voice_count];
|
||||
static uint8_t* m_ram; // 64K shared RAM between DSP and SMP
|
||||
spc_dsp_t m_spc_dsp;
|
||||
spc_dsp_sample_t* m_spc_dsp_out_begin;
|
||||
spc_dsp_sample_t* m_spc_dsp_out;
|
||||
spc_dsp_sample_t* m_spc_dsp_out_end;
|
||||
|
||||
// "Member" access
|
||||
#define m m_spc_dsp
|
||||
|
||||
// Access global DSP register
|
||||
#define REG(n) m.regs [r_##n]
|
||||
|
||||
// Access voice DSP register
|
||||
#define VREG(r,n) r [v_##n]
|
||||
|
||||
// if ( io < -32768 ) io = -32768;
|
||||
// if ( io > 32767 ) io = 32767;
|
||||
#define CLAMP16( io )\
|
||||
{\
|
||||
if ( (int16_t) io != io )\
|
||||
io = 0x7FFF ^ (io >> 31);\
|
||||
}
|
||||
|
||||
// Gaussian interpolation
|
||||
|
||||
static short const gauss [512] =
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2,
|
||||
2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5,
|
||||
6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10,
|
||||
11, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17,
|
||||
18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26, 27, 27,
|
||||
28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 36, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
|
||||
58, 59, 60, 61, 62, 64, 65, 66, 67, 69, 70, 71, 73, 74, 76, 77,
|
||||
78, 80, 81, 83, 84, 86, 87, 89, 90, 92, 94, 95, 97, 99, 100, 102,
|
||||
104, 106, 107, 109, 111, 113, 115, 117, 118, 120, 122, 124, 126, 128, 130, 132,
|
||||
134, 137, 139, 141, 143, 145, 147, 150, 152, 154, 156, 159, 161, 163, 166, 168,
|
||||
171, 173, 175, 178, 180, 183, 186, 188, 191, 193, 196, 199, 201, 204, 207, 210,
|
||||
212, 215, 218, 221, 224, 227, 230, 233, 236, 239, 242, 245, 248, 251, 254, 257,
|
||||
260, 263, 267, 270, 273, 276, 280, 283, 286, 290, 293, 297, 300, 304, 307, 311,
|
||||
314, 318, 321, 325, 328, 332, 336, 339, 343, 347, 351, 354, 358, 362, 366, 370,
|
||||
374, 378, 381, 385, 389, 393, 397, 401, 405, 410, 414, 418, 422, 426, 430, 434,
|
||||
439, 443, 447, 451, 456, 460, 464, 469, 473, 477, 482, 486, 491, 495, 499, 504,
|
||||
508, 513, 517, 522, 527, 531, 536, 540, 545, 550, 554, 559, 563, 568, 573, 577,
|
||||
582, 587, 592, 596, 601, 606, 611, 615, 620, 625, 630, 635, 640, 644, 649, 654,
|
||||
659, 664, 669, 674, 678, 683, 688, 693, 698, 703, 708, 713, 718, 723, 728, 732,
|
||||
737, 742, 747, 752, 757, 762, 767, 772, 777, 782, 787, 792, 797, 802, 806, 811,
|
||||
816, 821, 826, 831, 836, 841, 846, 851, 855, 860, 865, 870, 875, 880, 884, 889,
|
||||
894, 899, 904, 908, 913, 918, 923, 927, 932, 937, 941, 946, 951, 955, 960, 965,
|
||||
969, 974, 978, 983, 988, 992, 997,1001,1005,1010,1014,1019,1023,1027,1032,1036,
|
||||
1040,1045,1049,1053,1057,1061,1066,1070,1074,1078,1082,1086,1090,1094,1098,1102,
|
||||
1106,1109,1113,1117,1121,1125,1128,1132,1136,1139,1143,1146,1150,1153,1157,1160,
|
||||
1164,1167,1170,1174,1177,1180,1183,1186,1190,1193,1196,1199,1202,1205,1207,1210,
|
||||
1213,1216,1219,1221,1224,1227,1229,1232,1234,1237,1239,1241,1244,1246,1248,1251,
|
||||
1253,1255,1257,1259,1261,1263,1265,1267,1269,1270,1272,1274,1275,1277,1279,1280,
|
||||
1282,1283,1284,1286,1287,1288,1290,1291,1292,1293,1294,1295,1296,1297,1297,1298,
|
||||
1299,1300,1300,1301,1302,1302,1303,1303,1303,1304,1304,1304,1304,1304,1305,1305,
|
||||
};
|
||||
|
||||
static inline int interpolate( voice_t const* const v )
|
||||
{
|
||||
// Make pointers into gaussian based on fractional position between samples
|
||||
int offset = v->interp_pos >> 4 & 0xFF;
|
||||
short const* fwd = gauss + 255 - offset;
|
||||
short const* rev = gauss + offset; // mirror left half of gaussian
|
||||
|
||||
int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos];
|
||||
int out;
|
||||
out = (fwd [ 0] * in [0]) >> 11;
|
||||
out += (fwd [256] * in [1]) >> 11;
|
||||
out += (rev [256] * in [2]) >> 11;
|
||||
out = (int16_t) out;
|
||||
out += (rev [ 0] * in [3]) >> 11;
|
||||
|
||||
CLAMP16( out );
|
||||
out &= ~1;
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
//// Counters
|
||||
|
||||
enum { simple_counter_range = 2048 * 5 * 3 }; // 30720
|
||||
|
||||
static unsigned short const counter_rates [32] =
|
||||
{
|
||||
simple_counter_range + 1, // never fires
|
||||
2048, 1536,
|
||||
1280, 1024, 768,
|
||||
640, 512, 384,
|
||||
320, 256, 192,
|
||||
160, 128, 96,
|
||||
80, 64, 48,
|
||||
40, 32, 24,
|
||||
20, 16, 12,
|
||||
10, 8, 6,
|
||||
5, 4, 3,
|
||||
2,
|
||||
1
|
||||
};
|
||||
|
||||
static unsigned short const counter_offsets [32] =
|
||||
{
|
||||
1, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
0,
|
||||
0
|
||||
};
|
||||
|
||||
static inline void init_counters( void ) { }
|
||||
|
||||
static inline void run_counters( void )
|
||||
{
|
||||
if ( --m.counter < 0 )
|
||||
m.counter = simple_counter_range - 1;
|
||||
}
|
||||
|
||||
static inline unsigned read_counter( int rate )
|
||||
{
|
||||
return ((unsigned) m.counter + counter_offsets [rate]) % counter_rates [rate];
|
||||
}
|
||||
|
||||
|
||||
//// Envelope
|
||||
|
||||
static inline void run_envelope( voice_t* const v )
|
||||
{
|
||||
int env = v->env;
|
||||
if ( v->env_mode == env_release ) // 60%
|
||||
{
|
||||
if ( (env -= 0x8) < 0 )
|
||||
env = 0;
|
||||
v->env = env;
|
||||
}
|
||||
else
|
||||
{
|
||||
int rate;
|
||||
int env_data = VREG(v->regs,adsr1);
|
||||
if ( m.t_adsr0 & 0x80 ) // 99% ADSR
|
||||
{
|
||||
if ( v->env_mode >= env_decay ) // 99%
|
||||
{
|
||||
env--;
|
||||
env -= env >> 8;
|
||||
rate = env_data & 0x1F;
|
||||
if ( v->env_mode == env_decay ) // 1%
|
||||
rate = (m.t_adsr0 >> 3 & 0x0E) + 0x10;
|
||||
}
|
||||
else // env_attack
|
||||
{
|
||||
rate = (m.t_adsr0 & 0x0F) * 2 + 1;
|
||||
env += rate < 31 ? 0x20 : 0x400;
|
||||
}
|
||||
}
|
||||
else // GAIN
|
||||
{
|
||||
env_data = VREG(v->regs,gain);
|
||||
int mode = env_data >> 5;
|
||||
if ( mode < 4 ) // direct
|
||||
{
|
||||
env = env_data * 0x10;
|
||||
rate = 31;
|
||||
}
|
||||
else
|
||||
{
|
||||
rate = env_data & 0x1F;
|
||||
if ( mode == 4 ) // 4: linear decrease
|
||||
{
|
||||
env -= 0x20;
|
||||
}
|
||||
else if ( mode < 6 ) // 5: exponential decrease
|
||||
{
|
||||
env--;
|
||||
env -= env >> 8;
|
||||
}
|
||||
else // 6,7: linear increase
|
||||
{
|
||||
env += 0x20;
|
||||
if ( mode > 6 && (unsigned) v->hidden_env >= 0x600 )
|
||||
env += 0x8 - 0x20; // 7: two-slope linear increase
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sustain level
|
||||
if ( (env >> 8) == (env_data >> 5) && v->env_mode == env_decay )
|
||||
v->env_mode = env_sustain;
|
||||
|
||||
v->hidden_env = env;
|
||||
|
||||
// unsigned cast because linear decrease going negative also triggers this
|
||||
if ( (unsigned) env > 0x7FF )
|
||||
{
|
||||
env = (env < 0 ? 0 : 0x7FF);
|
||||
if ( v->env_mode == env_attack )
|
||||
v->env_mode = env_decay;
|
||||
}
|
||||
|
||||
if ( !read_counter( rate ) )
|
||||
v->env = env; // nothing else is controlled by the counter
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// BRR Decoding
|
||||
|
||||
static inline void decode_brr( voice_t* v )
|
||||
{
|
||||
// Arrange the four input nybbles in 0xABCD order for easy decoding
|
||||
int nybbles = m.t_brr_byte * 0x100 + m_ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF];
|
||||
|
||||
int const header = m.t_brr_header;
|
||||
|
||||
// 0: >>1 1: <<0 2: <<1 ... 12: <<11 13-15: >>4 <<11
|
||||
static unsigned char const shifts [16 * 2] = {
|
||||
13,12,12,12,12,12,12,12,12,12,12, 12, 12, 16, 16, 16,
|
||||
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 11, 11, 11
|
||||
};
|
||||
int const scale = header >> 4;
|
||||
int const right_shift = shifts [scale];
|
||||
int const left_shift = shifts [scale + 16];
|
||||
|
||||
// Write to next four samples in circular buffer
|
||||
int* pos = &v->buf [v->buf_pos];
|
||||
if ( (v->buf_pos += 4) >= brr_buf_size )
|
||||
v->buf_pos = 0;
|
||||
|
||||
// Decode four samples
|
||||
for ( int* end = pos + 4; pos < end; pos++ )
|
||||
{
|
||||
// Extract upper nybble and scale appropriately
|
||||
int s = ((int16_t) nybbles >> right_shift) << left_shift;
|
||||
nybbles <<= 4;
|
||||
|
||||
// Apply IIR filter (8 is the most commonly used)
|
||||
int const filter = header & 0x0C;
|
||||
int const p1 = pos [brr_buf_size - 1];
|
||||
int const p2 = pos [brr_buf_size - 2] >> 1;
|
||||
if ( filter >= 8 )
|
||||
{
|
||||
s += p1;
|
||||
s -= p2;
|
||||
if ( filter == 8 ) // s += p1 * 0.953125 - p2 * 0.46875
|
||||
{
|
||||
s += p2 >> 4;
|
||||
s += (p1 * -3) >> 6;
|
||||
}
|
||||
else // s += p1 * 0.8984375 - p2 * 0.40625
|
||||
{
|
||||
s += (p1 * -13) >> 7;
|
||||
s += (p2 * 3) >> 4;
|
||||
}
|
||||
}
|
||||
else if ( filter ) // s += p1 * 0.46875
|
||||
{
|
||||
s += p1 >> 1;
|
||||
s += (-p1) >> 5;
|
||||
}
|
||||
|
||||
// Adjust and write sample
|
||||
CLAMP16( s );
|
||||
s = (int16_t) (s * 2);
|
||||
pos [brr_buf_size] = pos [0] = s; // second copy simplifies wrap-around
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// Misc
|
||||
|
||||
#define MISC_CLOCK( n ) inline static void misc_##n( void )
|
||||
|
||||
MISC_CLOCK( 27 )
|
||||
{
|
||||
m.t_pmon = REG(pmon) & 0xFE; // voice 0 doesn't support PMON
|
||||
}
|
||||
MISC_CLOCK( 28 )
|
||||
{
|
||||
m.t_non = REG(non);
|
||||
m.t_eon = REG(eon);
|
||||
m.t_dir = REG(dir);
|
||||
}
|
||||
MISC_CLOCK( 29 )
|
||||
{
|
||||
if ( (m.every_other_sample ^= 1) != 0 )
|
||||
m.new_kon &= ~m.kon; // clears KON 63 clocks after it was last read
|
||||
}
|
||||
MISC_CLOCK( 30 )
|
||||
{
|
||||
if ( m.every_other_sample )
|
||||
{
|
||||
m.kon = m.new_kon;
|
||||
m.t_koff = REG(koff) | m.mute_mask;
|
||||
}
|
||||
|
||||
run_counters();
|
||||
|
||||
// Noise
|
||||
if ( !read_counter( REG(flg) & 0x1F ) )
|
||||
{
|
||||
int feedback = (m.noise << 13) ^ (m.noise << 14);
|
||||
m.noise = (feedback & 0x4000) ^ (m.noise >> 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// Voices
|
||||
|
||||
#define VOICE_CLOCK( n ) static void voice_##n( voice_t* const v )
|
||||
|
||||
inline VOICE_CLOCK( V1 )
|
||||
{
|
||||
m.t_dir_addr = m.t_dir * 0x100 + m.t_srcn * 4;
|
||||
m.t_srcn = VREG(v->regs,srcn);
|
||||
}
|
||||
inline VOICE_CLOCK( V2 )
|
||||
{
|
||||
// Read sample pointer (ignored if not needed)
|
||||
uint8_t const* entry = &m_ram [m.t_dir_addr];
|
||||
if ( !v->kon_delay )
|
||||
entry += 2;
|
||||
m.t_brr_next_addr = entry [1] * 0x100 + entry [0];
|
||||
|
||||
m.t_adsr0 = VREG(v->regs,adsr0);
|
||||
|
||||
// Read pitch, spread over two clocks
|
||||
m.t_pitch = VREG(v->regs,pitchl);
|
||||
}
|
||||
inline VOICE_CLOCK( V3a )
|
||||
{
|
||||
m.t_pitch += (VREG(v->regs,pitchh) & 0x3F) << 8;
|
||||
}
|
||||
inline VOICE_CLOCK( V3b )
|
||||
{
|
||||
// Read BRR header and byte
|
||||
m.t_brr_byte = m_ram [(v->brr_addr + v->brr_offset) & 0xFFFF];
|
||||
m.t_brr_header = m_ram [v->brr_addr]; // brr_addr doesn't need masking
|
||||
}
|
||||
VOICE_CLOCK( V3c )
|
||||
{
|
||||
// Pitch modulation using previous voice's output
|
||||
if ( m.t_pmon & v->vbit )
|
||||
m.t_pitch += ((m.t_output >> 5) * m.t_pitch) >> 10;
|
||||
|
||||
if ( v->kon_delay )
|
||||
{
|
||||
// Get ready to start BRR decoding on next sample
|
||||
if ( v->kon_delay == 5 )
|
||||
{
|
||||
v->brr_addr = m.t_brr_next_addr;
|
||||
v->brr_offset = 1;
|
||||
v->buf_pos = 0;
|
||||
m.t_brr_header = 0; // header is ignored on this sample
|
||||
}
|
||||
|
||||
// Envelope is never run during KON
|
||||
v->env = 0;
|
||||
v->hidden_env = 0;
|
||||
|
||||
// Disable BRR decoding until last three samples
|
||||
v->interp_pos = 0;
|
||||
if ( --v->kon_delay & 3 )
|
||||
v->interp_pos = 0x4000;
|
||||
|
||||
// Pitch is never added during KON
|
||||
m.t_pitch = 0;
|
||||
}
|
||||
|
||||
// Gaussian interpolation
|
||||
int output = interpolate( v );
|
||||
|
||||
// Noise
|
||||
if ( m.t_non & v->vbit )
|
||||
output = (int16_t) (m.noise * 2);
|
||||
|
||||
// Apply envelope
|
||||
m.t_output = (output * v->env) >> 11 & ~1;
|
||||
v->t_envx_out = v->env >> 4;
|
||||
|
||||
// Immediate silence due to end of sample or soft reset
|
||||
if ( REG(flg) & 0x80 || (m.t_brr_header & 3) == 1 )
|
||||
{
|
||||
v->env_mode = env_release;
|
||||
v->env = 0;
|
||||
}
|
||||
|
||||
if ( m.every_other_sample )
|
||||
{
|
||||
// KOFF
|
||||
if ( m.t_koff & v->vbit )
|
||||
v->env_mode = env_release;
|
||||
|
||||
// KON
|
||||
if ( m.kon & v->vbit )
|
||||
{
|
||||
v->kon_delay = 5;
|
||||
v->env_mode = env_attack;
|
||||
}
|
||||
}
|
||||
|
||||
// Run envelope for next sample
|
||||
if ( !v->kon_delay )
|
||||
run_envelope( v );
|
||||
}
|
||||
static inline void voice_output( voice_t const* v, int ch )
|
||||
{
|
||||
// Apply left/right volume
|
||||
int amp = (m.t_output * (int8_t) VREG(v->regs,voll + ch)) >> 7;
|
||||
|
||||
// Add to output total
|
||||
m.t_main_out [ch] += amp;
|
||||
CLAMP16( m.t_main_out [ch] );
|
||||
|
||||
// Optionally add to echo total
|
||||
if ( m.t_eon & v->vbit )
|
||||
{
|
||||
m.t_echo_out [ch] += amp;
|
||||
CLAMP16( m.t_echo_out [ch] );
|
||||
}
|
||||
}
|
||||
VOICE_CLOCK( V4 )
|
||||
{
|
||||
// Decode BRR
|
||||
m.t_looped = 0;
|
||||
if ( v->interp_pos >= 0x4000 )
|
||||
{
|
||||
decode_brr( v );
|
||||
|
||||
if ( (v->brr_offset += 2) >= brr_block_size )
|
||||
{
|
||||
// Start decoding next BRR block
|
||||
assert( v->brr_offset == brr_block_size );
|
||||
v->brr_addr = (v->brr_addr + brr_block_size) & 0xFFFF;
|
||||
if ( m.t_brr_header & 1 )
|
||||
{
|
||||
v->brr_addr = m.t_brr_next_addr;
|
||||
m.t_looped = v->vbit;
|
||||
}
|
||||
v->brr_offset = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply pitch
|
||||
v->interp_pos = (v->interp_pos & 0x3FFF) + m.t_pitch;
|
||||
|
||||
// Keep from getting too far ahead (when using pitch modulation)
|
||||
if ( v->interp_pos > 0x7FFF )
|
||||
v->interp_pos = 0x7FFF;
|
||||
|
||||
// Output left
|
||||
voice_output( v, 0 );
|
||||
}
|
||||
inline VOICE_CLOCK( V5 )
|
||||
{
|
||||
// Output right
|
||||
voice_output( v, 1 );
|
||||
|
||||
// ENDX, OUTX, and ENVX won't update if you wrote to them 1-2 clocks earlier
|
||||
|
||||
m.endx_buf = REG(endx) | m.t_looped;
|
||||
|
||||
// Clear bit in ENDX if KON just began
|
||||
if ( v->kon_delay == 5 )
|
||||
m.endx_buf &= ~v->vbit;
|
||||
}
|
||||
inline VOICE_CLOCK( V6 )
|
||||
{
|
||||
m.outx_buf = m.t_output >> 8;
|
||||
}
|
||||
inline VOICE_CLOCK( V7 )
|
||||
{
|
||||
// Update ENDX
|
||||
REG(endx) = (uint8_t) m.endx_buf;
|
||||
|
||||
m.envx_buf = v->t_envx_out;
|
||||
}
|
||||
inline VOICE_CLOCK( V8 )
|
||||
{
|
||||
// Update OUTX
|
||||
VREG(v->regs,outx) = (uint8_t) m.outx_buf;
|
||||
}
|
||||
inline VOICE_CLOCK( V9 )
|
||||
{
|
||||
// Update ENVX
|
||||
VREG(v->regs,envx) = (uint8_t) m.envx_buf;
|
||||
}
|
||||
|
||||
// Most voices do all these in one clock, so make a handy composite
|
||||
inline VOICE_CLOCK( V3 )
|
||||
{
|
||||
voice_V3a( v );
|
||||
voice_V3b( v );
|
||||
voice_V3c( v );
|
||||
}
|
||||
|
||||
// Common combinations of voice steps on different voices. This greatly reduces
|
||||
// code size and allows everything to be inlined in these functions.
|
||||
VOICE_CLOCK(V7_V4_V1) { voice_V7(v); voice_V1(v+3); voice_V4(v+1); }
|
||||
VOICE_CLOCK(V8_V5_V2) { voice_V8(v); voice_V5(v+1); voice_V2(v+2); }
|
||||
VOICE_CLOCK(V9_V6_V3) { voice_V9(v); voice_V6(v+1); voice_V3(v+2); }
|
||||
|
||||
|
||||
//// Echo
|
||||
|
||||
// Current echo buffer pointer for left/right channel
|
||||
#define ECHO_PTR( ch ) (&m_ram [m.t_echo_ptr + ch * 2])
|
||||
|
||||
// Sample in echo history buffer, where 0 is the oldest
|
||||
#define ECHO_FIR( i ) (m.echo_hist_pos [i])
|
||||
|
||||
// Calculate FIR point for left/right channel
|
||||
#define CALC_FIR( i, ch ) ((ECHO_FIR( i + 1 ) [ch] * (int8_t) REG(fir + i * 0x10)) >> 6)
|
||||
|
||||
#define ECHO_CLOCK( n ) inline static void echo_##n( void )
|
||||
|
||||
static inline void echo_read( int ch )
|
||||
{
|
||||
uint8_t const* in = ECHO_PTR( ch );
|
||||
int s = (int8_t) in [1] * 0x100 + in [0];
|
||||
// second copy simplifies wrap-around handling
|
||||
ECHO_FIR( 0 ) [ch] = ECHO_FIR( 8 ) [ch] = s >> 1;
|
||||
}
|
||||
|
||||
ECHO_CLOCK( 22 )
|
||||
{
|
||||
// History
|
||||
if ( ++m.echo_hist_pos >= &m.echo_hist [spc_dsp_echo_hist_size] )
|
||||
m.echo_hist_pos = m.echo_hist;
|
||||
|
||||
m.t_echo_ptr = (m.t_esa * 0x100 + m.echo_offset) & 0xFFFF;
|
||||
echo_read( 0 );
|
||||
|
||||
// FIR (using l and r temporaries below helps compiler optimize)
|
||||
int l = CALC_FIR( 0, 0 );
|
||||
int r = CALC_FIR( 0, 1 );
|
||||
|
||||
m.t_echo_in [0] = l;
|
||||
m.t_echo_in [1] = r;
|
||||
}
|
||||
ECHO_CLOCK( 23 )
|
||||
{
|
||||
int l = CALC_FIR( 1, 0 ) + CALC_FIR( 2, 0 );
|
||||
int r = CALC_FIR( 1, 1 ) + CALC_FIR( 2, 1 );
|
||||
|
||||
m.t_echo_in [0] += l;
|
||||
m.t_echo_in [1] += r;
|
||||
|
||||
echo_read( 1 );
|
||||
}
|
||||
ECHO_CLOCK( 24 )
|
||||
{
|
||||
int l = CALC_FIR( 3, 0 ) + CALC_FIR( 4, 0 ) + CALC_FIR( 5, 0 );
|
||||
int r = CALC_FIR( 3, 1 ) + CALC_FIR( 4, 1 ) + CALC_FIR( 5, 1 );
|
||||
|
||||
m.t_echo_in [0] += l;
|
||||
m.t_echo_in [1] += r;
|
||||
}
|
||||
ECHO_CLOCK( 25 )
|
||||
{
|
||||
int l = m.t_echo_in [0] + CALC_FIR( 6, 0 );
|
||||
int r = m.t_echo_in [1] + CALC_FIR( 6, 1 );
|
||||
|
||||
l = (int16_t) l;
|
||||
r = (int16_t) r;
|
||||
|
||||
l += (int16_t) CALC_FIR( 7, 0 );
|
||||
r += (int16_t) CALC_FIR( 7, 1 );
|
||||
|
||||
CLAMP16( l );
|
||||
CLAMP16( r );
|
||||
|
||||
m.t_echo_in [0] = l & ~1;
|
||||
m.t_echo_in [1] = r & ~1;
|
||||
}
|
||||
static inline int echo_output( int ch )
|
||||
{
|
||||
int out = (int16_t) ((m.t_main_out [ch] * (int8_t) REG(mvoll + ch * 0x10)) >> 7) +
|
||||
(int16_t) ((m.t_echo_in [ch] * (int8_t) REG(evoll + ch * 0x10)) >> 7);
|
||||
CLAMP16( out );
|
||||
return out;
|
||||
}
|
||||
ECHO_CLOCK( 26 )
|
||||
{
|
||||
// Left output volumes
|
||||
// (save sample for next clock so we can output both together)
|
||||
m.t_main_out [0] = echo_output( 0 );
|
||||
|
||||
// Echo feedback
|
||||
int l = m.t_echo_out [0] + (int16_t) ((m.t_echo_in [0] * (int8_t) REG(efb)) >> 7);
|
||||
int r = m.t_echo_out [1] + (int16_t) ((m.t_echo_in [1] * (int8_t) REG(efb)) >> 7);
|
||||
|
||||
CLAMP16( l );
|
||||
CLAMP16( r );
|
||||
|
||||
m.t_echo_out [0] = l & ~1;
|
||||
m.t_echo_out [1] = r & ~1;
|
||||
}
|
||||
ECHO_CLOCK( 27 )
|
||||
{
|
||||
// Output
|
||||
int outl = m.t_main_out [0];
|
||||
int outr = echo_output( 1 );
|
||||
m.t_main_out [0] = 0;
|
||||
m.t_main_out [1] = 0;
|
||||
|
||||
// TODO: global muting isn't this simple (turns DAC on and off
|
||||
// or something, causing small ~37-sample pulse when first muted)
|
||||
if ( REG(flg) & 0x40 )
|
||||
{
|
||||
outl = 0;
|
||||
outr = 0;
|
||||
}
|
||||
|
||||
// Output sample to DAC
|
||||
#ifdef SPC_DSP_OUT_HOOK
|
||||
SPC_DSP_OUT_HOOK( outl, outr );
|
||||
#else
|
||||
spc_dsp_sample_t* out = m_spc_dsp_out;
|
||||
assert( !out || out < m_spc_dsp_out_end ); // fails if output buffer is too small
|
||||
if ( out != m_spc_dsp_out_end )
|
||||
{
|
||||
out [0] = outl;
|
||||
out [1] = outr;
|
||||
m_spc_dsp_out = out + 2;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
ECHO_CLOCK( 28 )
|
||||
{
|
||||
m.t_echo_enabled = REG(flg);
|
||||
}
|
||||
static inline void echo_write( int ch )
|
||||
{
|
||||
if ( !(m.t_echo_enabled & 0x20) )
|
||||
{
|
||||
uint8_t* out = ECHO_PTR( ch );
|
||||
int s = m.t_echo_out [ch];
|
||||
out [0] = (uint8_t) s;
|
||||
out [1] = (uint8_t) (s >> 8);
|
||||
}
|
||||
m.t_echo_out [ch] = 0;
|
||||
}
|
||||
ECHO_CLOCK( 29 )
|
||||
{
|
||||
m.t_esa = REG(esa);
|
||||
|
||||
if ( !m.echo_offset )
|
||||
m.echo_length = (REG(edl) & 0x0F) * 0x800;
|
||||
|
||||
m.echo_offset += 4;
|
||||
if ( m.echo_offset >= m.echo_length )
|
||||
m.echo_offset = 0;
|
||||
|
||||
// Write left echo
|
||||
echo_write( 0 );
|
||||
|
||||
m.t_echo_enabled = REG(flg);
|
||||
}
|
||||
ECHO_CLOCK( 30 )
|
||||
{
|
||||
// Write right echo
|
||||
echo_write( 1 );
|
||||
}
|
||||
|
||||
|
||||
//// Timing
|
||||
|
||||
#if !SPC_DSP_CUSTOM_RUN
|
||||
|
||||
void spc_dsp_run( int clocks_remain )
|
||||
{
|
||||
assert( clocks_remain > 0 );
|
||||
|
||||
int const phase = m.phase;
|
||||
m.phase = (phase + clocks_remain) & 31;
|
||||
switch ( phase )
|
||||
{
|
||||
loop:
|
||||
|
||||
#define PHASE( n ) if ( n && !--clocks_remain ) break; case n:
|
||||
#include "spc_dsp_timing.h"
|
||||
#undef PHASE
|
||||
|
||||
if ( --clocks_remain )
|
||||
goto loop;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
//// Setup
|
||||
|
||||
void spc_dsp_reset( void )
|
||||
{
|
||||
// Clear everything to zero, then set things which must be non-zero
|
||||
memset( m_voice_state, 0, sizeof m_voice_state );
|
||||
memset( &m_spc_dsp, 0, sizeof m_spc_dsp );
|
||||
|
||||
m.noise = 1;
|
||||
m.echo_hist_pos = m.echo_hist;
|
||||
m.every_other_sample = 1;
|
||||
init_counters();
|
||||
|
||||
int i;
|
||||
for ( i = spc_dsp_voice_count; --i >= 0; )
|
||||
{
|
||||
voice_t* v = &m_voice_state [i];
|
||||
v->regs = &m.regs [i * 0x10];
|
||||
v->vbit = 1 << i;
|
||||
v->brr_offset = 1;
|
||||
}
|
||||
|
||||
REG(flg) = 0xE0;
|
||||
}
|
||||
|
||||
void spc_dsp_soft_reset( void )
|
||||
{
|
||||
// TODO: doesn't reset everything
|
||||
spc_dsp_reset();
|
||||
}
|
||||
|
||||
void spc_dsp_init( void* ram_64k )
|
||||
{
|
||||
m_ram = (uint8_t*) ram_64k;
|
||||
spc_dsp_reset();
|
||||
|
||||
#if INT_MAX < 0x7FFFFFFF
|
||||
#error "Requires that int have at least 32 bits"
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
// be sure this sign-extends
|
||||
assert( (int16_t) 0x8000 == -0x8000 );
|
||||
|
||||
// be sure right shift preserves sign
|
||||
assert( (-1 >> 1) == -1 );
|
||||
|
||||
// check clamp macro
|
||||
int i;
|
||||
i = +0x8000; CLAMP16( i ); assert( i == +0x7FFF );
|
||||
i = -0x8001; CLAMP16( i ); assert( i == -0x8000 );
|
||||
#endif
|
||||
}
|
||||
|
||||
void spc_dsp_load( uint8_t const regs [spc_dsp_register_count] )
|
||||
{
|
||||
int i;
|
||||
for ( i = 0; i < 0x80; i++ )
|
||||
spc_dsp_write( i, regs [i] );
|
||||
m.t_esa = regs [r_esa];
|
||||
m.t_dir = regs [r_dir];
|
||||
}
|
@@ -1,162 +0,0 @@
|
||||
// SNES SPC-700 DSP emulator
|
||||
|
||||
#ifndef SPC_DSP_H
|
||||
#define SPC_DSP_H
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//// Setup
|
||||
|
||||
// Initializes DSP and has it use the 64K RAM provided
|
||||
void spc_dsp_init( void* ram_64k );
|
||||
|
||||
// Restores DSP registers using supplied values
|
||||
enum { spc_dsp_register_count = 128 };
|
||||
void spc_dsp_load( uint8_t const regs [spc_dsp_register_count] );
|
||||
|
||||
// Mutes voice n if bit 1<<b is set
|
||||
enum { spc_dsp_voice_count = 8 };
|
||||
static void spc_dsp_mute_voices( int mask );
|
||||
|
||||
// Sets destination for output samples. If out is NULL or out_size is 0,
|
||||
// doesn't generate any.
|
||||
typedef short spc_dsp_sample_t;
|
||||
static void spc_dsp_set_output( spc_dsp_sample_t* out, int out_size );
|
||||
|
||||
// Number of samples written to output since it was last set, always
|
||||
// a multiple of 2
|
||||
static int spc_dsp_sample_count( void );
|
||||
|
||||
|
||||
//// Emulation
|
||||
|
||||
// Resets DSP to power-on state. Does not affect anything set by above functions.
|
||||
void spc_dsp_reset( void );
|
||||
|
||||
// Emulates pressing reset switch on SNES
|
||||
void spc_dsp_soft_reset( void );
|
||||
|
||||
// Reads/writes DSP registers. For accuracy, you must first call spc_run_dsp()
|
||||
// to catch the DSP up to present.
|
||||
static int spc_dsp_read( int addr );
|
||||
static void spc_dsp_write( int addr, int data );
|
||||
|
||||
// Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks
|
||||
// a pair of samples will be generated.
|
||||
void spc_dsp_run( int clock_count );
|
||||
|
||||
|
||||
//// private
|
||||
|
||||
enum { spc_dsp_echo_hist_size = 8 };
|
||||
|
||||
extern spc_dsp_sample_t* m_spc_dsp_out_begin;
|
||||
extern spc_dsp_sample_t* m_spc_dsp_out;
|
||||
extern spc_dsp_sample_t* m_spc_dsp_out_end;
|
||||
|
||||
typedef struct spc_dsp_t
|
||||
{
|
||||
uint8_t regs [spc_dsp_register_count];
|
||||
|
||||
// Echo history keeps most recent 8 samples (twice the size to simplify wrap handling)
|
||||
int echo_hist [spc_dsp_echo_hist_size * 2] [2];
|
||||
int (*echo_hist_pos) [2]; // &echo_hist [0 to 7]
|
||||
int mute_mask;
|
||||
|
||||
int every_other_sample; // toggles every sample
|
||||
int kon; // KON value when last checked
|
||||
int noise;
|
||||
int counter;
|
||||
int echo_offset; // offset from ESA in echo buffer
|
||||
int echo_length; // number of bytes that echo_offset will stop at
|
||||
int phase; // next clock cycle to run (0-31)
|
||||
|
||||
// Hidden registers also written to when main register is written to
|
||||
int new_kon;
|
||||
int endx_buf;
|
||||
int envx_buf;
|
||||
int outx_buf;
|
||||
|
||||
// Temporary state between clocks
|
||||
|
||||
// read once per sample
|
||||
int t_pmon;
|
||||
int t_non;
|
||||
int t_eon;
|
||||
int t_dir;
|
||||
int t_koff;
|
||||
|
||||
// read a few clocks ahead then used
|
||||
int t_brr_next_addr;
|
||||
int t_adsr0;
|
||||
int t_brr_header;
|
||||
int t_brr_byte;
|
||||
int t_srcn;
|
||||
int t_esa;
|
||||
int t_echo_enabled;
|
||||
|
||||
// internal state that is recalculated every sample
|
||||
int t_dir_addr;
|
||||
int t_pitch;
|
||||
int t_output;
|
||||
int t_looped;
|
||||
int t_echo_ptr;
|
||||
|
||||
// left/right sums
|
||||
int t_main_out [2];
|
||||
int t_echo_out [2];
|
||||
int t_echo_in [2];
|
||||
|
||||
} spc_dsp_t;
|
||||
extern spc_dsp_t m_spc_dsp;
|
||||
|
||||
static inline int spc_dsp_read( int addr )
|
||||
{
|
||||
assert( (unsigned) addr < spc_dsp_register_count );
|
||||
return m_spc_dsp.regs [addr];
|
||||
}
|
||||
|
||||
static inline void spc_dsp_write( int addr, int data )
|
||||
{
|
||||
assert( (unsigned) addr < spc_dsp_register_count );
|
||||
|
||||
m_spc_dsp.regs [addr] = data;
|
||||
switch ( addr & 0x0F )
|
||||
{
|
||||
case 0x08:
|
||||
m_spc_dsp.envx_buf = data;
|
||||
break;
|
||||
|
||||
case 0x09:
|
||||
m_spc_dsp.outx_buf = data;
|
||||
break;
|
||||
|
||||
case 0x0C:
|
||||
if ( addr == 0x4C ) // KON
|
||||
m_spc_dsp.new_kon = data;
|
||||
|
||||
if ( addr == 0x7C ) // ENDX, write always clears it regardless of data
|
||||
{
|
||||
m_spc_dsp.endx_buf = 0;
|
||||
m_spc_dsp.regs [0x7C] = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void spc_dsp_mute_voices( int mask ) { m_spc_dsp.mute_mask = mask; }
|
||||
|
||||
static inline void spc_dsp_set_output( spc_dsp_sample_t* out, int out_size )
|
||||
{
|
||||
assert( out_size % 2 == 0 ); // must be even
|
||||
m_spc_dsp_out_begin = out;
|
||||
m_spc_dsp_out = out;
|
||||
if ( out )
|
||||
out += out_size;
|
||||
m_spc_dsp_out_end = out;
|
||||
}
|
||||
|
||||
static inline int spc_dsp_sample_count( void ) { return m_spc_dsp_out - m_spc_dsp_out_begin; }
|
||||
|
||||
#endif
|
@@ -1,46 +0,0 @@
|
||||
// Execute clock for a particular voice
|
||||
#define V( clock, voice ) voice_##clock( &m_voice_state [voice] );
|
||||
|
||||
/* The most common sequence of clocks uses composite operations
|
||||
for efficiency. For example, the following are equivalent to the
|
||||
individual steps on the right:
|
||||
|
||||
V(2_31 ,2) -> V( 2,2) V(31,3)
|
||||
V(3_0_29,2) -> V( 3,2) V( 0,3) V(29,4)
|
||||
V(4_1_30,2) -> V( 4,2) V( 1,3) V(30,4) */
|
||||
|
||||
// Voice 0 1 2 3 4 5 6 7
|
||||
PHASE( 0) V(V5,0)V(V2,1)
|
||||
PHASE( 1) V(V6,0)V(V3,1)
|
||||
PHASE( 2) V(V7_V4_V1,0)
|
||||
PHASE( 3) V(V8_V5_V2,0)
|
||||
PHASE( 4) V(V9_V6_V3,0)
|
||||
PHASE( 5) V(V7_V4_V1,1)
|
||||
PHASE( 6) V(V8_V5_V2,1)
|
||||
PHASE( 7) V(V9_V6_V3,1)
|
||||
PHASE( 8) V(V7_V4_V1,2)
|
||||
PHASE( 9) V(V8_V5_V2,2)
|
||||
PHASE(10) V(V9_V6_V3,2)
|
||||
PHASE(11) V(V7_V4_V1,3)
|
||||
PHASE(12) V(V8_V5_V2,3)
|
||||
PHASE(13) V(V9_V6_V3,3)
|
||||
PHASE(14) V(V7_V4_V1,4)
|
||||
PHASE(15) V(V8_V5_V2,4)
|
||||
PHASE(16) V(V9_V6_V3,4)
|
||||
PHASE(17) V(V1,0) V(V7,5)V(V4,6)
|
||||
PHASE(18) V(V8_V5_V2,5)
|
||||
PHASE(19) V(V9_V6_V3,5)
|
||||
PHASE(20) V(V1,1) V(V7,6)V(V4,7)
|
||||
PHASE(21) V(V8,6)V(V5,7) V(V2,0) // t_brr_next_addr order dependency
|
||||
PHASE(22) V(V3a,0) V(V9,6)V(V6,7) echo_22();
|
||||
PHASE(23) V(V7,7) echo_23();
|
||||
PHASE(24) V(V8,7) echo_24();
|
||||
PHASE(25) V(V3b,0) V(V9,7) echo_25();
|
||||
PHASE(26) echo_26();
|
||||
PHASE(27) misc_27(); echo_27();
|
||||
PHASE(28) misc_28(); echo_28();
|
||||
PHASE(29) misc_29(); echo_29();
|
||||
PHASE(30) misc_30();V(V3c,0) echo_30();
|
||||
PHASE(31) V(V4,0) V(V1,2)
|
||||
|
||||
#undef V
|
@@ -1,43 +0,0 @@
|
||||
// Byte order handling
|
||||
|
||||
#ifndef SPC_ENDIAN_H
|
||||
#define SPC_ENDIAN_H
|
||||
|
||||
//#include <stdint.h>
|
||||
|
||||
static inline unsigned get_le16( void const* p )
|
||||
{
|
||||
return ((uint8_t const*) p) [1] * 0x100u +
|
||||
((uint8_t const*) p) [0];
|
||||
}
|
||||
|
||||
static inline int get_le16s( void const* p )
|
||||
{
|
||||
return ((int8_t const*) p) [1] * 0x100 +
|
||||
((uint8_t const*) p) [0];
|
||||
}
|
||||
|
||||
static inline void set_le16( void* p, unsigned n )
|
||||
{
|
||||
((uint8_t*) p) [1] = (uint8_t) (n >> 8);
|
||||
((uint8_t*) p) [0] = (uint8_t) n;
|
||||
}
|
||||
|
||||
// *A versions are used where data is aligned
|
||||
// Sometimes BIG_ENDIAN is defined to 0x1234 or something, so treat values
|
||||
// other than 1 as false.
|
||||
#if BIG_ENDIAN != 1
|
||||
#define GET_LE16A( addr ) (*(uint16_t const*) (addr))
|
||||
#define GET_LE16SA( addr ) (*( int16_t const*) (addr))
|
||||
#define SET_LE16A( addr, data ) (void) (*(uint16_t*) (addr) = (data))
|
||||
|
||||
#else
|
||||
#define GET_LE16A( addr ) get_le16 ( addr )
|
||||
#define GET_LE16SA( addr ) get_le16s( addr )
|
||||
#define SET_LE16A( addr, data ) set_le16 ( addr, data )
|
||||
#endif
|
||||
|
||||
#define GET_LE16( addr ) GET_LE16A( addr )
|
||||
#define SET_LE16( addr, data ) SET_LE16A( addr, data )
|
||||
|
||||
#endif
|
@@ -2,7 +2,7 @@
|
||||
|
||||
void sDSP::brr_decode(voice_t &v) {
|
||||
//state.t_brr_byte = ram[v.brr_addr + v.brr_offset] cached from previous clock cycle
|
||||
int nybbles = (state.t_brr_byte << 8) + ram[(uint16)(v.brr_addr + v.brr_offset + 1)];
|
||||
int nybbles = (state.t_brr_byte << 8) + memory::apuram[(uint16)(v.brr_addr + v.brr_offset + 1)];
|
||||
|
||||
const int filter = (state.t_brr_header >> 2) & 3;
|
||||
const int scale = (state.t_brr_header >> 4);
|
||||
|
@@ -12,17 +12,19 @@ int sDSP::echo_output(bool channel) {
|
||||
}
|
||||
|
||||
void sDSP::echo_read(bool channel) {
|
||||
uint8 *in = &ram[state.t_echo_ptr + channel * 2];
|
||||
int s = (int16)((in[1] << 8) + in[0]);
|
||||
unsigned addr = state.t_echo_ptr + channel * 2;
|
||||
uint8 lo = memory::apuram[(uint16)(addr + 0)];
|
||||
uint8 hi = memory::apuram[(uint16)(addr + 1)];
|
||||
int s = (int16)((hi << 8) + lo);
|
||||
state.echo_hist[channel].write(state.echo_hist_pos, s >> 1);
|
||||
}
|
||||
|
||||
void sDSP::echo_write(bool channel) {
|
||||
if(!(state.t_echo_disabled & 0x20)) {
|
||||
uint8 *out = &ram[state.t_echo_ptr + channel * 2];
|
||||
unsigned addr = state.t_echo_ptr + channel * 2;
|
||||
int s = state.t_echo_out[channel];
|
||||
out[0] = (uint8)(s);
|
||||
out[1] = (uint8)(s >> 8);
|
||||
memory::apuram[(uint16)(addr + 0)] = s;
|
||||
memory::apuram[(uint16)(addr + 1)] = s >> 8;
|
||||
}
|
||||
|
||||
state.t_echo_out[channel] = 0;
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
/*
|
||||
S-DSP emulator
|
||||
license: LGPLv2
|
||||
@@ -7,7 +6,7 @@
|
||||
The actual algorithms, timing information, tables, variable names, etc were all from him.
|
||||
*/
|
||||
|
||||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define SDSP_CPP
|
||||
|
||||
#define REG(n) state.regs[r_##n]
|
||||
@@ -249,8 +248,6 @@ void sDSP::write(uint8 addr, uint8 data) {
|
||||
/* initialization */
|
||||
|
||||
void sDSP::power() {
|
||||
ram = (uint8*)smp.get_spcram_handle(); //TODO: move to sMemory
|
||||
|
||||
memset(&state.regs, 0, sizeof state.regs);
|
||||
state.echo_hist_pos = 0;
|
||||
state.every_other_sample = false;
|
||||
|
@@ -12,9 +12,6 @@ public:
|
||||
~sDSP();
|
||||
|
||||
private:
|
||||
//external
|
||||
uint8 *ram;
|
||||
|
||||
//USE_STATE_MACHINE variable
|
||||
unsigned phase_index;
|
||||
|
@@ -22,9 +22,11 @@ void sDSP::voice_1(voice_t &v) {
|
||||
|
||||
void sDSP::voice_2(voice_t &v) {
|
||||
//read sample pointer (ignored if not needed)
|
||||
const uint8 *entry = &ram[state.t_dir_addr];
|
||||
if(!v.kon_delay) entry += 2;
|
||||
state.t_brr_next_addr = (entry[1] << 8) + entry[0];
|
||||
uint16 addr = state.t_dir_addr;
|
||||
if(!v.kon_delay) addr += 2;
|
||||
uint8 lo = memory::apuram[(uint16)(addr + 0)];
|
||||
uint8 hi = memory::apuram[(uint16)(addr + 1)];
|
||||
state.t_brr_next_addr = ((hi << 8) + lo);
|
||||
|
||||
state.t_adsr0 = VREG(adsr0);
|
||||
|
||||
@@ -43,8 +45,8 @@ void sDSP::voice_3a(voice_t &v) {
|
||||
}
|
||||
|
||||
void sDSP::voice_3b(voice_t &v) {
|
||||
state.t_brr_byte = ram[(uint16)(v.brr_addr + v.brr_offset)];
|
||||
state.t_brr_header = ram[(uint16)(v.brr_addr)];
|
||||
state.t_brr_byte = memory::apuram[(uint16)(v.brr_addr + v.brr_offset)];
|
||||
state.t_brr_header = memory::apuram[(uint16)(v.brr_addr)];
|
||||
}
|
||||
|
||||
void sDSP::voice_3c(voice_t &v) {
|
||||
|
@@ -1,39 +0,0 @@
|
||||
#include "reader/reader.h"
|
||||
#include "cheat/cheat.h"
|
||||
#include "config/config.h"
|
||||
|
||||
#include "memory/memory.h"
|
||||
#include "memory/smemory/smemory.h"
|
||||
|
||||
#include "cart/cart.h"
|
||||
|
||||
#include "cpu/cpu.h"
|
||||
#include "cpu/scpu/scpu.h"
|
||||
|
||||
#include "smp/smp.h"
|
||||
#include "smp/ssmp/ssmp.h"
|
||||
|
||||
#include "dsp/dsp.h"
|
||||
#include "dsp/sdsp/sdsp.h"
|
||||
|
||||
#include "ppu/ppu.h"
|
||||
#include "ppu/bppu/bppu.h"
|
||||
|
||||
#ifdef INTERFACE_MAIN
|
||||
#define extern
|
||||
#endif
|
||||
|
||||
extern BUSCORE bus;
|
||||
extern CPUCORE cpu;
|
||||
extern SMPCORE smp;
|
||||
extern DSPCORE dsp;
|
||||
extern PPUCORE ppu;
|
||||
|
||||
#undef extern
|
||||
|
||||
#include "snes/snes.h"
|
||||
#include "chip/chip.h"
|
||||
|
||||
#ifdef INTERFACE_MAIN
|
||||
#include "config/config.cpp"
|
||||
#endif
|
29
src/interface.hpp
Normal file
29
src/interface.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "reader/reader.hpp"
|
||||
#include "cheat/cheat.hpp"
|
||||
#include "config/config.hpp"
|
||||
|
||||
#include "memory/memory.hpp"
|
||||
#include "memory/smemory/smemory.hpp"
|
||||
|
||||
#include "cart/cart.hpp"
|
||||
|
||||
#include "cpu/cpu.hpp"
|
||||
#include "cpu/scpu/scpu.hpp"
|
||||
|
||||
#include "smp/smp.hpp"
|
||||
#include "smp/ssmp/ssmp.hpp"
|
||||
|
||||
#include "dsp/dsp.hpp"
|
||||
#include "dsp/sdsp/sdsp.hpp"
|
||||
|
||||
#include "ppu/ppu.hpp"
|
||||
#include "ppu/bppu/bppu.hpp"
|
||||
|
||||
extern BUSCORE bus;
|
||||
extern CPUCORE cpu;
|
||||
extern SMPCORE smp;
|
||||
extern DSPCORE dsp;
|
||||
extern PPUCORE ppu;
|
||||
|
||||
#include "snes/snes.hpp"
|
||||
#include "chip/chip.hpp"
|
205
src/lib/bbase.h
205
src/lib/bbase.h
@@ -1,205 +0,0 @@
|
||||
/*
|
||||
bbase : version 0.14 ~byuu (2008-04-16)
|
||||
license: public domain
|
||||
*/
|
||||
|
||||
#ifndef BBASE_H
|
||||
#define BBASE_H
|
||||
|
||||
#include <nall/stdint.hpp>
|
||||
typedef int8_t int8;
|
||||
typedef int16_t int16;
|
||||
typedef int32_t int32;
|
||||
typedef int64_t int64;
|
||||
typedef uint8_t uint8;
|
||||
typedef uint16_t uint16;
|
||||
typedef uint32_t uint32;
|
||||
typedef uint64_t uint64;
|
||||
typedef unsigned int uint;
|
||||
|
||||
#include <algorithm>
|
||||
using std::min;
|
||||
using std::max;
|
||||
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
#include <io.h>
|
||||
#include <direct.h>
|
||||
#include <shlobj.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
//disable libc deprecation warnings in MSVC 2k5+
|
||||
#pragma warning(disable:4996)
|
||||
|
||||
#define NOMINMAX
|
||||
#define PATH_MAX _MAX_PATH
|
||||
#define va_copy(dst, src) ((dst) = (src))
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
#define getcwd _getcwd
|
||||
#define ftruncate _chsize
|
||||
#define mkdir _mkdir
|
||||
#define putenv _putenv
|
||||
#define rmdir _rmdir
|
||||
#define vsnprintf _vsnprintf
|
||||
#define usleep(n) Sleep(n / 1000)
|
||||
|
||||
static char *realpath(const char *file_name, char *resolved_name) {
|
||||
return _fullpath(resolved_name, file_name, PATH_MAX);
|
||||
}
|
||||
#else
|
||||
#define mkdir(path) (mkdir)(path, 0755);
|
||||
#endif
|
||||
|
||||
/*****
|
||||
* inline expansion
|
||||
*****/
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define noinline __declspec(noinline)
|
||||
#define inline inline
|
||||
#define alwaysinline __forceinline
|
||||
#elif defined(__GNUC__)
|
||||
#define noinline __attribute__((noinline))
|
||||
#define inline inline
|
||||
#define alwaysinline __attribute__((always_inline))
|
||||
#else
|
||||
#define noinline
|
||||
#define inline inline
|
||||
#define alwaysinline inline
|
||||
#endif
|
||||
|
||||
/*****
|
||||
* OS localization
|
||||
*****/
|
||||
|
||||
//userpath(output) retrieves path to user's home folder
|
||||
//output must be at least as large as PATH_MAX
|
||||
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
static char *userpath(char *output) {
|
||||
strcpy(output, "."); //failsafe
|
||||
SHGetFolderPath(0, CSIDL_APPDATA | CSIDL_FLAG_CREATE, 0, 0, output);
|
||||
return output;
|
||||
}
|
||||
#else
|
||||
static char *userpath(char *output) {
|
||||
strcpy(output, "."); //failsafe
|
||||
struct passwd *userinfo = getpwuid(getuid());
|
||||
if(userinfo) { strcpy(output, userinfo->pw_dir); }
|
||||
return output;
|
||||
}
|
||||
#endif
|
||||
|
||||
template<int min, int max, typename T> inline T minmax(const T x) {
|
||||
return (x < (T)min) ? (T)min : (x > (T)max) ? (T)max : x;
|
||||
}
|
||||
|
||||
/*****
|
||||
* endian wrappers
|
||||
*****/
|
||||
|
||||
#ifndef ARCH_MSB
|
||||
//little-endian: uint8[] { 0x01, 0x02, 0x03, 0x04 } == 0x04030201
|
||||
#define order_lsb2(a,b) a,b
|
||||
#define order_lsb3(a,b,c) a,b,c
|
||||
#define order_lsb4(a,b,c,d) a,b,c,d
|
||||
#define order_lsb5(a,b,c,d,e) a,b,c,d,e
|
||||
#define order_lsb6(a,b,c,d,e,f) a,b,c,d,e,f
|
||||
#define order_lsb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g
|
||||
#define order_lsb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h
|
||||
#define order_msb2(a,b) b,a
|
||||
#define order_msb3(a,b,c) c,b,a
|
||||
#define order_msb4(a,b,c,d) d,c,b,a
|
||||
#define order_msb5(a,b,c,d,e) e,d,c,b,a
|
||||
#define order_msb6(a,b,c,d,e,f) f,e,d,c,b,a
|
||||
#define order_msb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a
|
||||
#define order_msb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a
|
||||
#else
|
||||
//big-endian: uint8[] { 0x01, 0x02, 0x03, 0x04 } == 0x01020304
|
||||
#define order_lsb2(a,b) b,a
|
||||
#define order_lsb3(a,b,c) c,b,a
|
||||
#define order_lsb4(a,b,c,d) d,c,b,a
|
||||
#define order_lsb5(a,b,c,d,e) e,d,c,b,a
|
||||
#define order_lsb6(a,b,c,d,e,f) f,e,d,c,b,a
|
||||
#define order_lsb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a
|
||||
#define order_lsb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a
|
||||
#define order_msb2(a,b) a,b
|
||||
#define order_msb3(a,b,c) a,b,c
|
||||
#define order_msb4(a,b,c,d) a,b,c,d
|
||||
#define order_msb5(a,b,c,d,e) a,b,c,d,e
|
||||
#define order_msb6(a,b,c,d,e,f) a,b,c,d,e,f
|
||||
#define order_msb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g
|
||||
#define order_msb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h
|
||||
#endif
|
||||
|
||||
/*****
|
||||
* libc extensions
|
||||
*****/
|
||||
|
||||
//pseudo-random number generator
|
||||
static unsigned prng() {
|
||||
static unsigned n = 0;
|
||||
return n = (n >> 1) ^ (((n & 1) - 1) & 0xedb88320);
|
||||
}
|
||||
|
||||
static uint64 fget(FILE *fp, unsigned length = 1) {
|
||||
uint64 data = 0;
|
||||
for(unsigned i = 0; i < length; i++) {
|
||||
data |= fgetc(fp) << (i << 3);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static void fput(FILE *fp, uint64 data, unsigned length = 1) {
|
||||
for(unsigned i = 0; i < length; i++) {
|
||||
fputc(data >> (i << 3), fp);
|
||||
}
|
||||
}
|
||||
|
||||
static bool fexists(const char *fn) {
|
||||
FILE *fp = fopen(fn, "rb");
|
||||
if(!fp) return false;
|
||||
fclose(fp);
|
||||
fp = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static unsigned fsize(FILE *fp) {
|
||||
if(!fp) return 0;
|
||||
unsigned pos = ftell(fp);
|
||||
fseek(fp, 0, SEEK_END);
|
||||
unsigned size = ftell(fp);
|
||||
fseek(fp, pos, SEEK_SET);
|
||||
return size;
|
||||
}
|
||||
|
||||
static unsigned fsize(const char *fn) {
|
||||
FILE *fp = fopen(fn, "rb");
|
||||
if(!fp) return 0;
|
||||
fseek(fp, 0, SEEK_END);
|
||||
unsigned size = ftell(fp);
|
||||
fclose(fp);
|
||||
fp = 0;
|
||||
return size;
|
||||
}
|
||||
|
||||
static int fresize(FILE *fp, long size) {
|
||||
return ftruncate(fileno(fp), size);
|
||||
}
|
||||
|
||||
#endif //ifndef BBASE_H
|
@@ -1,8 +1,8 @@
|
||||
void hiro_pbutton_tick(pButton *p) {
|
||||
if(p->self.on_tick) p->self.on_tick(Event(Event::Tick, 0, &p->self));
|
||||
if(p->self.on_tick) p->self.on_tick(event_t(event_t::Tick, 0, &p->self));
|
||||
}
|
||||
|
||||
void pButton::create(uint style, uint width, uint height, const char *text) {
|
||||
void pButton::create(unsigned style, unsigned width, unsigned height, const char *text) {
|
||||
button = gtk_button_new_with_label(text ? text : "");
|
||||
set_default_font(button);
|
||||
gtk_widget_set_size_request(button, width, height);
|
||||
@@ -13,6 +13,7 @@ void pButton::create(uint style, uint width, uint height, const char *text) {
|
||||
void pButton::set_text(const char *text) {
|
||||
if(!button) return;
|
||||
gtk_button_set_label(GTK_BUTTON(button), text ? text : "");
|
||||
set_default_font(button);
|
||||
}
|
||||
|
||||
pButton::pButton(Button &self_) : pFormControl(self_), self(self_) {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
class pButton : public pFormControl {
|
||||
public:
|
||||
Button &self;
|
||||
void create(uint style, uint width, uint height, const char *text = "");
|
||||
void create(unsigned style, unsigned width, unsigned height, const char *text = "");
|
||||
void set_text(const char *text = "");
|
||||
|
||||
pButton(Button&);
|
@@ -1,8 +1,8 @@
|
||||
void hiro_pcanvas_expose(pCanvas *p) {
|
||||
uint32_t *f = p->fbuffer;
|
||||
uint32_t *r = p->rbuffer;
|
||||
for(uint y = p->canvas->allocation.height; y; y--) {
|
||||
for(uint x = p->canvas->allocation.width; x; x--) {
|
||||
for(unsigned y = p->canvas->allocation.height; y; y--) {
|
||||
for(unsigned x = p->canvas->allocation.width; x; x--) {
|
||||
uint32_t p = *f++;
|
||||
*r++ = ((p << 16) & 0xff0000) + (p & 0x00ff00) + ((p >> 16) & 0x0000ff);
|
||||
}
|
||||
@@ -14,15 +14,32 @@ void hiro_pcanvas_expose(pCanvas *p) {
|
||||
GDK_RGB_DITHER_NONE, (guchar*)p->rbuffer, p->bpitch);
|
||||
}
|
||||
|
||||
void pCanvas::create(uint style, uint width, uint height) {
|
||||
gboolean hiro_pcanvas_button_press(GtkWidget *widget, GdkEventButton *event, pCanvas *p) {
|
||||
if(p->self.on_input && event->button < mouse::buttons) {
|
||||
p->self.on_input(event_t(event_t::Input, (mouse::button + event->button) + (1 << 16), &p->self));
|
||||
}
|
||||
return false; //do not propogate the event to other handlers
|
||||
}
|
||||
|
||||
gboolean hiro_pcanvas_button_release(GtkWidget *widget, GdkEventButton *event, pCanvas *p) {
|
||||
if(p->self.on_input && event->button < mouse::buttons) {
|
||||
p->self.on_input(event_t(event_t::Input, (mouse::button + event->button) + (0 << 16), &p->self));
|
||||
}
|
||||
return false; //do not propogate the event to other handlers
|
||||
}
|
||||
|
||||
void pCanvas::create(unsigned style, unsigned width, unsigned height) {
|
||||
canvas = gtk_drawing_area_new();
|
||||
resize(width, height);
|
||||
GdkColor color;
|
||||
color.pixel = color.red = color.green = color.blue = 0;
|
||||
gtk_widget_modify_bg(canvas, GTK_STATE_NORMAL, &color);
|
||||
gtk_widget_set_double_buffered(canvas, false);
|
||||
gtk_widget_add_events(canvas, GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
|
||||
gtk_widget_show(canvas);
|
||||
g_signal_connect_swapped(G_OBJECT(canvas), "expose_event", G_CALLBACK(hiro_pcanvas_expose), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(canvas), "button_press_event", G_CALLBACK(hiro_pcanvas_button_press), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(canvas), "button_release_event", G_CALLBACK(hiro_pcanvas_button_release), (gpointer)this);
|
||||
}
|
||||
|
||||
void pCanvas::redraw() {
|
||||
@@ -54,7 +71,7 @@ pCanvas::~pCanvas() {
|
||||
|
||||
/* internal */
|
||||
|
||||
void pCanvas::resize(uint width, uint height) {
|
||||
void pCanvas::resize(unsigned width, unsigned height) {
|
||||
if(fbuffer) free(fbuffer);
|
||||
if(rbuffer) free(rbuffer);
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
class pCanvas : public pFormControl {
|
||||
public:
|
||||
void create(uint style, uint width, uint height);
|
||||
void create(unsigned style, unsigned width, unsigned height);
|
||||
void redraw();
|
||||
uint32_t* buffer();
|
||||
|
||||
@@ -13,7 +13,7 @@ public:
|
||||
//GTK+ RGB drawing function draws in xBGR format, so two buffers are needed ...
|
||||
uint32_t *fbuffer; //one for the xRGB image
|
||||
uint32_t *rbuffer; //one for the xBGR image
|
||||
uint bpitch;
|
||||
void resize(uint width, uint height);
|
||||
unsigned bpitch;
|
||||
void resize(unsigned width, unsigned height);
|
||||
GtkWidget* gtk_handle();
|
||||
};
|
@@ -1,8 +1,8 @@
|
||||
void hiro_pcheckbox_tick(pCheckbox *p) {
|
||||
if(!p->locked && p->self.on_tick) p->self.on_tick(Event(Event::Tick, p->checked(), &p->self));
|
||||
if(!p->locked && p->self.on_tick) p->self.on_tick(event_t(event_t::Tick, p->checked(), &p->self));
|
||||
}
|
||||
|
||||
void pCheckbox::create(uint style, uint width, uint height, const char *text) {
|
||||
void pCheckbox::create(unsigned style, unsigned width, unsigned height, const char *text) {
|
||||
checkbox = gtk_check_button_new_with_label(text ? text : "");
|
||||
set_default_font(checkbox);
|
||||
gtk_widget_set_size_request(checkbox, width, height);
|
||||
|
@@ -1,6 +1,6 @@
|
||||
class pCheckbox : public pFormControl {
|
||||
public:
|
||||
void create(uint style, uint width, uint height, const char *text = "");
|
||||
void create(unsigned style, unsigned width, unsigned height, const char *text = "");
|
||||
void set_text(const char *text = "");
|
||||
void check(bool state = true);
|
||||
void uncheck();
|
@@ -1,8 +1,8 @@
|
||||
void hiro_pcombobox_change(pCombobox *p) {
|
||||
if(p->self.on_change) p->self.on_change(Event(Event::Change, p->get_selection(), &p->self));
|
||||
if(p->self.on_change) p->self.on_change(event_t(event_t::Change, p->get_selection(), &p->self));
|
||||
}
|
||||
|
||||
void pCombobox::create(uint style, uint width, uint height, const char *text) {
|
||||
void pCombobox::create(unsigned style, unsigned width, unsigned height, const char *text) {
|
||||
combobox = gtk_combo_box_new_text();
|
||||
set_default_font(combobox);
|
||||
gtk_widget_set_size_request(combobox, width, height);
|
||||
|
@@ -1,6 +1,6 @@
|
||||
class pCombobox : public pFormControl {
|
||||
public:
|
||||
void create(uint style, uint width, uint height, const char *text = "");
|
||||
void create(unsigned style, unsigned width, unsigned height, const char *text = "");
|
||||
void add_item(const char *text);
|
||||
int get_selection();
|
||||
void set_selection(int index);
|
||||
@@ -11,6 +11,6 @@ public:
|
||||
|
||||
/* internal */
|
||||
GtkWidget *combobox;
|
||||
uint counter;
|
||||
unsigned counter;
|
||||
GtkWidget* gtk_handle();
|
||||
};
|
@@ -1,4 +1,10 @@
|
||||
void pEditbox::create(uint style, uint width, uint height, const char *text) {
|
||||
static void hiro_peditbox_change(pEditbox *p) {
|
||||
if(p->self.on_change) {
|
||||
p->self.on_change(event_t(event_t::Change, 0, &p->self));
|
||||
}
|
||||
}
|
||||
|
||||
void pEditbox::create(unsigned style, unsigned width, unsigned height, const char *text) {
|
||||
multiline = bool(style & Editbox::Multiline);
|
||||
|
||||
if(multiline == false) {
|
||||
@@ -7,24 +13,28 @@ void pEditbox::create(uint style, uint width, uint height, const char *text) {
|
||||
gtk_entry_set_text(GTK_ENTRY(editbox), text ? text : "");
|
||||
gtk_widget_set_size_request(editbox, width, height);
|
||||
gtk_widget_show(editbox);
|
||||
g_signal_connect_swapped(G_OBJECT(editbox), "changed", G_CALLBACK(hiro_peditbox_change), (gpointer)this);
|
||||
} else {
|
||||
GtkPolicyType hscroll = (style & Editbox::HorizontalScrollAlways) ? GTK_POLICY_ALWAYS :
|
||||
(style & Editbox::HorizontalScrollNever) ? GTK_POLICY_NEVER :
|
||||
(style & Editbox::HorizontalScrollNever ) ? GTK_POLICY_NEVER :
|
||||
GTK_POLICY_AUTOMATIC;
|
||||
GtkPolicyType vscroll = (style & Editbox::VerticalScrollAlways) ? GTK_POLICY_ALWAYS :
|
||||
(style & Editbox::VerticalScrollNever) ? GTK_POLICY_NEVER :
|
||||
(style & Editbox::VerticalScrollNever ) ? GTK_POLICY_NEVER :
|
||||
GTK_POLICY_AUTOMATIC;
|
||||
scrollbox = gtk_scrolled_window_new(0, 0);
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollbox), hscroll, vscroll);
|
||||
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollbox), GTK_SHADOW_ETCHED_IN);
|
||||
gtk_widget_set_size_request(scrollbox, width, height);
|
||||
editbox = gtk_text_view_new();
|
||||
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(editbox),
|
||||
(hscroll == GTK_POLICY_NEVER ? GTK_WRAP_WORD_CHAR : GTK_WRAP_NONE));
|
||||
gtk_container_add(GTK_CONTAINER(scrollbox), editbox);
|
||||
buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(editbox));
|
||||
if(style & Editbox::Readonly) { gtk_text_view_set_editable(GTK_TEXT_VIEW(editbox), false); }
|
||||
gtk_text_buffer_set_text(buffer, text ? text : "", -1);
|
||||
gtk_widget_show(editbox);
|
||||
gtk_widget_show(scrollbox);
|
||||
g_signal_connect_swapped(G_OBJECT(buffer), "changed", G_CALLBACK(hiro_peditbox_change), (gpointer)this);
|
||||
}
|
||||
|
||||
set_default_font(editbox);
|
||||
@@ -38,7 +48,7 @@ void pEditbox::set_text(const char *text) {
|
||||
}
|
||||
}
|
||||
|
||||
uint pEditbox::get_text(char *text, uint length) {
|
||||
unsigned pEditbox::get_text(char *text, unsigned length) {
|
||||
if(multiline == false) {
|
||||
const char *temp = gtk_entry_get_text(GTK_ENTRY(editbox));
|
||||
return strlcpy(text, temp ? temp : "", length);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user