Compare commits
No commits in common. "master" and "gh-pages" have entirely different histories.
1911 changed files with 12891 additions and 410098 deletions
|
@ -1,5 +0,0 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
indent_size = 3
|
63
.gitattributes
vendored
63
.gitattributes
vendored
|
@ -1,63 +0,0 @@
|
|||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
|
@ -1 +0,0 @@
|
|||
patreon: Mesen
|
31
.github/workflows/documentation-build.yml
vendored
31
.github/workflows/documentation-build.yml
vendored
|
@ -1,31 +0,0 @@
|
|||
name: github pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master # Set a branch to deploy
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true # Fetch Hugo themes (true OR recursive)
|
||||
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
|
||||
|
||||
- name: Setup Hugo
|
||||
uses: peaceiris/actions-hugo@v2
|
||||
with:
|
||||
hugo-version: 'latest'
|
||||
# extended: true
|
||||
|
||||
- name: Build
|
||||
run: cd Docs && hugo --minify
|
||||
|
||||
- name: Deploy 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@4.1.6
|
||||
if: github.ref == 'refs/heads/master'
|
||||
with:
|
||||
branch: gh-pages # The branch the action should deploy to.
|
||||
folder: Docs/docs # The folder the action should deploy.
|
28
.github/workflows/linux-build.yml
vendored
28
.github/workflows/linux-build.yml
vendored
|
@ -1,28 +0,0 @@
|
|||
name: "Linux build"
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
linux-build:
|
||||
runs-on: ubuntu-latest
|
||||
container: ubuntu:bionic
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -yq --no-install-recommends gnupg ca-certificates
|
||||
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
|
||||
echo "deb https://download.mono-project.com/repo/ubuntu stable-bionic main" | tee /etc/apt/sources.list.d/mono-official-stable.list
|
||||
apt-get update
|
||||
apt-get install -yq --no-install-recommends zip unzip clang mono-devel libsdl2-dev libsdl2-2.0 gnome-themes-standard xvfb x11-apps
|
||||
apt-get clean && rm -rf /var/cache/apt/lists/*
|
||||
- name: Build
|
||||
run: |
|
||||
make -j$(nproc)
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: Mesen-Linux
|
||||
path: bin/x64/Release/Mesen.exe
|
27
.github/workflows/win-build.yml
vendored
27
.github/workflows/win-build.yml
vendored
|
@ -1,27 +0,0 @@
|
|||
name: "Windows build"
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
win-build:
|
||||
runs-on: windows-2019
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p "bin/Any CPU/Release"
|
||||
cp -v -r GUI.NET/Dependencies "bin/Any CPU/Release"
|
||||
git describe --tags --dirty --always >"bin\Any CPU\Release\Dependencies\DevBuild.txt"
|
||||
- name: Build core
|
||||
working-directory: bin
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
|
||||
msbuild ..\Mesen.sln /t:Build /p:Configuration=Release /p:Platform=x64
|
||||
copy "x64\Release\MesenCore.dll" "Any CPU\Release\Dependencies\MesenCore.x64.dll"
|
||||
msbuild ..\Mesen.sln /t:Build /p:Configuration=Release /p:Platform="Any CPU" /property:DefineConstants="HIDETESTMENU;AUTOBUILD"
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: Mesen-win
|
||||
path: bin/Any CPU/Release/Mesen.exe
|
183
.gitignore
vendored
183
.gitignore
vendored
|
@ -1,183 +0,0 @@
|
|||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.sln.docstates
|
||||
.vs/*
|
||||
|
||||
# Build results
|
||||
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
x64/
|
||||
build/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Oo]bj.x86/
|
||||
[Oo]bj.x64/
|
||||
|
||||
# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
|
||||
!packages/*/build/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.log
|
||||
*.scc
|
||||
|
||||
# Libretro build files
|
||||
*.o
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# NCrunch
|
||||
*.ncrunch*
|
||||
.*crunch*.local.xml
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.Publish.xml
|
||||
|
||||
# NuGet Packages Directory
|
||||
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
|
||||
#packages/
|
||||
|
||||
# Windows Azure Build Output
|
||||
csx
|
||||
*.build.csdef
|
||||
|
||||
# Windows Store app package directory
|
||||
AppPackages/
|
||||
|
||||
# Others
|
||||
sql/
|
||||
*.Cache
|
||||
ClientBin/
|
||||
[Ss]tyle[Cc]op.*
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.[Pp]ublish.xml
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file to a newer
|
||||
# Visual Studio version. Backup files are not needed, because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
App_Data/*.mdf
|
||||
App_Data/*.ldf
|
||||
|
||||
#LightSwitch generated files
|
||||
GeneratedArtifacts/
|
||||
_Pvt_Extensions/
|
||||
ModelManifest.xml
|
||||
|
||||
# =========================
|
||||
# Windows detritus
|
||||
# =========================
|
||||
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Mac desktop service store files
|
||||
.DS_Store
|
||||
*.nes
|
||||
*.sav
|
||||
*.svs
|
||||
*.trt
|
||||
*.rar
|
||||
|
||||
*.VC.opendb
|
||||
*.VC.db
|
||||
*.VC.db-wal
|
||||
*.VC.db-shm
|
||||
|
||||
Docs/docs/
|
||||
Docs/*.exe
|
||||
|
||||
PGOHelper/PGOMesenHome
|
||||
*.profraw
|
||||
*.profdata
|
||||
|
||||
packages/*
|
||||
|
||||
!Libretro/hakchi/bin
|
||||
Docs/docs.zip
|
35
404.html
Normal file
35
404.html
Normal file
|
@ -0,0 +1,35 @@
|
|||
<!doctype html><html lang=en class="js csstransforms3d">
|
||||
<head>
|
||||
<meta charset=utf-8> <meta name=description content>
|
||||
<link rel="shortcut icon" href=./images/favicon.png type=image/x-icon>
|
||||
<link rel=icon href=./images/favicon.png type=image/x-icon>
|
||||
<title>404 Page not found</title>
|
||||
<link href=./css/nucleus.css?1637878444 rel=stylesheet>
|
||||
<link href=./css/font-awesome.min.css?1637878444 rel=stylesheet>
|
||||
<link href=./css/hybrid.css?1637878444 rel=stylesheet>
|
||||
<link href=./css/featherlight.min.css?1637878444 rel=stylesheet>
|
||||
<link href=./css/perfect-scrollbar.min.css?1637878444 rel=stylesheet>
|
||||
<link href=./css/horsey.css?1637878444 rel=stylesheet>
|
||||
<link href=./css/theme.css?1637878444 rel=stylesheet>
|
||||
<link href=./css/hugo-theme.css?1637878444 rel=stylesheet>
|
||||
<link href=./css/theme-green.css?1637878444 rel=stylesheet>
|
||||
<style type=text/css>:root #header+#content>#left>#rlblock_left{display:none!important}p,li,ul{text-align:center}ul{list-style-type:none}</style>
|
||||
</head>
|
||||
<body>
|
||||
<body data-url=./>
|
||||
<section id=body style=margin-left:0>
|
||||
<div id=overlay></div>
|
||||
<div id=chapter>
|
||||
<div id=body-inner>
|
||||
<h1>Error</h1>
|
||||
<p>
|
||||
</p>
|
||||
<p>Woops. Looks like this page doesn't exist ¯\_(ツ)_/¯.</p>
|
||||
<p></p>
|
||||
<p><a href>Go to homepage</a></p>
|
||||
<p><img src=./images/gopher-404.jpg style=width:50%></img></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
41
COMPILING.md
41
COMPILING.md
|
@ -1,41 +0,0 @@
|
|||
### Windows
|
||||
|
||||
#### *Standalone*
|
||||
|
||||
1) Open the solution in Visual Studio 2019
|
||||
2) Set "GUI.NET" as the Startup Project
|
||||
3) Compile as Release/x64 or Release/x86
|
||||
4) Run the project from Visual Studio
|
||||
5) If you got an error, try running it a second time
|
||||
|
||||
Note: When loading the the solution in Visual Studio make sure all the projects are loaded successfully.
|
||||
Note: If you get an error about the project targeted .NET Framework 4.5 chose to download the missing packages then install ".NET Framework 4.5 targeting pack" from the Visual Studio Installer.
|
||||
Note: If you get a "compiler is out of heap space" error when building the project, then try running `set PreferredToolArchitecture=x64` and `devenv` in "x64 Native Tools Command Prompt for VS 2019"
|
||||
|
||||
#### *Libretro*
|
||||
|
||||
1) Open the solution in Visual Studio 2019
|
||||
2) Compile as Libretro/x64 or Libretro/x86
|
||||
3) Use the "mesen_libretro.dll" file in bin/(x64 or x86)/Libretro/mesen_libretro.dll
|
||||
|
||||
Note: It's also possible to build the Libretro core via MINGW by using the makefile in the Libretro subfolder.
|
||||
|
||||
### Linux
|
||||
|
||||
#### *Standalone*
|
||||
|
||||
To compile Mesen under Linux you will need clang 7.0+ or gcc 9.0+ (Mesen requires a C++17 compiler with support for the filesystem API.) Additionally, Mesen has the following dependencies:
|
||||
|
||||
* Mono 5.18+ (package: mono-devel)
|
||||
* SDL2 (package: libsdl2-dev)
|
||||
|
||||
**Note:** **Mono 5.18 or higher is recommended**, some older versions of Mono (e.g 4.2.2) have some stability and performance issues which can cause crashes and slow down the UI.
|
||||
The default Mono version in Ubuntu 18.04 is 4.6.2 (which also causes some layout issues in Mesen). To install the latest version of Mono, follow the instructions here: https://www.mono-project.com/download/stable/#download-lin
|
||||
|
||||
The makefile contains some more information at the top. Running "make" will build the x64 version by default, and then "make run" should start the emulator.
|
||||
LTO is supported under clang, which gives a large performance boost (25-30%+), so turning it on is highly recommended (see makefile for details).
|
||||
|
||||
#### *Libretro*
|
||||
|
||||
To compile the Libretro core you will need a version of clang/gcc that supports C++14.
|
||||
Run "make" from the "Libretro" subfolder to build the Libretro core.
|
|
@ -1,55 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "PPU.h"
|
||||
#include "Snapshotable.h"
|
||||
|
||||
enum class A12StateChange
|
||||
{
|
||||
None = 0,
|
||||
Rise = 1,
|
||||
Fall = 2
|
||||
};
|
||||
|
||||
class A12Watcher : public Snapshotable
|
||||
{
|
||||
private:
|
||||
uint32_t _lastCycle = 0;
|
||||
uint32_t _cyclesDown = 0;
|
||||
|
||||
public:
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
Stream(_lastCycle, _cyclesDown);
|
||||
}
|
||||
|
||||
template<uint8_t minDelay = 10>
|
||||
A12StateChange UpdateVramAddress(uint16_t addr, uint32_t frameCycle)
|
||||
{
|
||||
A12StateChange result = A12StateChange::None;
|
||||
|
||||
if(_cyclesDown > 0) {
|
||||
if(_lastCycle > frameCycle) {
|
||||
//We changed frames
|
||||
_cyclesDown += (89342 - _lastCycle) + frameCycle;
|
||||
} else {
|
||||
_cyclesDown += (frameCycle - _lastCycle);
|
||||
}
|
||||
}
|
||||
|
||||
if((addr & 0x1000) == 0) {
|
||||
if(_cyclesDown == 0) {
|
||||
_cyclesDown = 1;
|
||||
result = A12StateChange::Fall;
|
||||
}
|
||||
} else if(addr & 0x1000) {
|
||||
if(_cyclesDown > minDelay) {
|
||||
result = A12StateChange::Rise;
|
||||
}
|
||||
_cyclesDown = 0;
|
||||
}
|
||||
_lastCycle = frameCycle;
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
32
Core/A65AS.h
32
Core/A65AS.h
|
@ -1,32 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class A65AS : public BaseMapper
|
||||
{
|
||||
protected:
|
||||
uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
SelectCHRPage(0, 0);
|
||||
WriteRegister(0x8000, 0);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if(value & 0x40) {
|
||||
SelectPrgPage2x(0, value & 0x1E);
|
||||
} else {
|
||||
SelectPRGPage(0, ((value & 0x30) >> 1) | (value & 0x07));
|
||||
SelectPRGPage(1, ((value & 0x30) >> 1) | 0x07);
|
||||
}
|
||||
|
||||
if(value & 0x80) {
|
||||
SetMirroringType(value & 0x20 ? MirroringType::ScreenBOnly : MirroringType::ScreenAOnly);
|
||||
} else {
|
||||
SetMirroringType(value & 0x08 ? MirroringType::Horizontal : MirroringType::Vertical);
|
||||
}
|
||||
}
|
||||
};
|
289
Core/APU.cpp
289
Core/APU.cpp
|
@ -1,289 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
#include "APU.h"
|
||||
#include "CPU.h"
|
||||
#include "SquareChannel.h"
|
||||
#include "TriangleChannel.h"
|
||||
#include "NoiseChannel.h"
|
||||
#include "DeltaModulationChannel.h"
|
||||
#include "ApuFrameCounter.h"
|
||||
#include "EmulationSettings.h"
|
||||
#include "SoundMixer.h"
|
||||
#include "MemoryManager.h"
|
||||
|
||||
APU::APU(shared_ptr<Console> console)
|
||||
{
|
||||
_nesModel = NesModel::Auto;
|
||||
_apuEnabled = true;
|
||||
_needToRun = false;
|
||||
|
||||
_console = console;
|
||||
_mixer = _console->GetSoundMixer();
|
||||
_settings = _console->GetSettings();
|
||||
|
||||
_squareChannel[0].reset(new SquareChannel(AudioChannel::Square1, _console, _mixer.get(), true));
|
||||
_squareChannel[1].reset(new SquareChannel(AudioChannel::Square2, _console, _mixer.get(), false));
|
||||
_triangleChannel.reset(new TriangleChannel(AudioChannel::Triangle, _console, _mixer.get()));
|
||||
_noiseChannel.reset(new NoiseChannel(AudioChannel::Noise, _console, _mixer.get()));
|
||||
_deltaModulationChannel.reset(new DeltaModulationChannel(AudioChannel::DMC, _console, _mixer.get()));
|
||||
_frameCounter.reset(new ApuFrameCounter(_console));
|
||||
|
||||
_console->GetMemoryManager()->RegisterIODevice(_squareChannel[0].get());
|
||||
_console->GetMemoryManager()->RegisterIODevice(_squareChannel[1].get());
|
||||
_console->GetMemoryManager()->RegisterIODevice(_frameCounter.get());
|
||||
_console->GetMemoryManager()->RegisterIODevice(_triangleChannel.get());
|
||||
_console->GetMemoryManager()->RegisterIODevice(_noiseChannel.get());
|
||||
_console->GetMemoryManager()->RegisterIODevice(_deltaModulationChannel.get());
|
||||
|
||||
Reset(false);
|
||||
}
|
||||
|
||||
APU::~APU()
|
||||
{
|
||||
}
|
||||
|
||||
void APU::SetNesModel(NesModel model, bool forceInit)
|
||||
{
|
||||
if(_nesModel != model || forceInit) {
|
||||
//Finish the current apu frame before switching model
|
||||
Run();
|
||||
|
||||
_nesModel = model;
|
||||
_squareChannel[0]->SetNesModel(model);
|
||||
_squareChannel[1]->SetNesModel(model);
|
||||
_triangleChannel->SetNesModel(model);
|
||||
_noiseChannel->SetNesModel(model);
|
||||
_deltaModulationChannel->SetNesModel(model);
|
||||
_frameCounter->SetNesModel(model);
|
||||
|
||||
_mixer->SetNesModel(model);
|
||||
}
|
||||
}
|
||||
|
||||
void APU::FrameCounterTick(FrameType type)
|
||||
{
|
||||
//Quarter & half frame clock envelope & linear counter
|
||||
_squareChannel[0]->TickEnvelope();
|
||||
_squareChannel[1]->TickEnvelope();
|
||||
_triangleChannel->TickLinearCounter();
|
||||
_noiseChannel->TickEnvelope();
|
||||
|
||||
if(type == FrameType::HalfFrame) {
|
||||
//Half frames clock length counter & sweep
|
||||
_squareChannel[0]->TickLengthCounter();
|
||||
_squareChannel[1]->TickLengthCounter();
|
||||
_triangleChannel->TickLengthCounter();
|
||||
_noiseChannel->TickLengthCounter();
|
||||
|
||||
_squareChannel[0]->TickSweep();
|
||||
_squareChannel[1]->TickSweep();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t APU::GetStatus()
|
||||
{
|
||||
uint8_t status = 0;
|
||||
status |= _squareChannel[0]->GetStatus() ? 0x01 : 0x00;
|
||||
status |= _squareChannel[1]->GetStatus() ? 0x02 : 0x00;
|
||||
status |= _triangleChannel->GetStatus() ? 0x04 : 0x00;
|
||||
status |= _noiseChannel->GetStatus() ? 0x08 : 0x00;
|
||||
status |= _deltaModulationChannel->GetStatus() ? 0x10 : 0x00;
|
||||
status |= _console->GetCpu()->HasIrqSource(IRQSource::FrameCounter) ? 0x40 : 0x00;
|
||||
status |= _console->GetCpu()->HasIrqSource(IRQSource::DMC) ? 0x80 : 0x00;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t APU::ReadRAM(uint16_t addr)
|
||||
{
|
||||
//$4015 read
|
||||
Run();
|
||||
|
||||
uint8_t status = GetStatus();
|
||||
|
||||
//Reading $4015 clears the Frame Counter interrupt flag.
|
||||
_console->GetCpu()->ClearIrqSource(IRQSource::FrameCounter);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t APU::PeekRAM(uint16_t addr)
|
||||
{
|
||||
if(_console->GetEmulationThreadId() == std::this_thread::get_id()) {
|
||||
//Only run the APU (to catch up) if we're running this in the emulation thread (not 100% accurate, but we can't run the APU from any other thread without locking)
|
||||
Run();
|
||||
}
|
||||
return GetStatus();
|
||||
}
|
||||
|
||||
void APU::WriteRAM(uint16_t addr, uint8_t value)
|
||||
{
|
||||
//$4015 write
|
||||
Run();
|
||||
|
||||
//Writing to $4015 clears the DMC interrupt flag.
|
||||
//This needs to be done before setting the enabled flag for the DMC (because doing so can trigger an IRQ)
|
||||
_console->GetCpu()->ClearIrqSource(IRQSource::DMC);
|
||||
|
||||
_squareChannel[0]->SetEnabled((value & 0x01) == 0x01);
|
||||
_squareChannel[1]->SetEnabled((value & 0x02) == 0x02);
|
||||
_triangleChannel->SetEnabled((value & 0x04) == 0x04);
|
||||
_noiseChannel->SetEnabled((value & 0x08) == 0x08);
|
||||
_deltaModulationChannel->SetEnabled((value & 0x10) == 0x10);
|
||||
}
|
||||
|
||||
void APU::GetMemoryRanges(MemoryRanges &ranges)
|
||||
{
|
||||
ranges.AddHandler(MemoryOperation::Read, 0x4015);
|
||||
ranges.AddHandler(MemoryOperation::Write, 0x4015);
|
||||
}
|
||||
|
||||
void APU::Run()
|
||||
{
|
||||
//Update framecounter and all channels
|
||||
//This is called:
|
||||
//-At the end of a frame
|
||||
//-Before APU registers are read/written to
|
||||
//-When a DMC or FrameCounter interrupt needs to be fired
|
||||
int32_t cyclesToRun = _currentCycle - _previousCycle;
|
||||
|
||||
while(cyclesToRun > 0) {
|
||||
_previousCycle += _frameCounter->Run(cyclesToRun);
|
||||
|
||||
//Reload counters set by writes to 4003/4008/400B/400F after running the frame counter to allow the length counter to be clocked first
|
||||
//This fixes the test "len_reload_timing" (tests 4 & 5)
|
||||
_squareChannel[0]->ReloadCounter();
|
||||
_squareChannel[1]->ReloadCounter();
|
||||
_noiseChannel->ReloadCounter();
|
||||
_triangleChannel->ReloadCounter();
|
||||
|
||||
_squareChannel[0]->Run(_previousCycle);
|
||||
_squareChannel[1]->Run(_previousCycle);
|
||||
_noiseChannel->Run(_previousCycle);
|
||||
_triangleChannel->Run(_previousCycle);
|
||||
_deltaModulationChannel->Run(_previousCycle);
|
||||
}
|
||||
}
|
||||
|
||||
void APU::SetNeedToRun()
|
||||
{
|
||||
_needToRun = true;
|
||||
}
|
||||
|
||||
bool APU::NeedToRun(uint32_t currentCycle)
|
||||
{
|
||||
if(_deltaModulationChannel->NeedToRun() || _needToRun) {
|
||||
//Need to run whenever we alter the length counters
|
||||
//Need to run every cycle when DMC is running to get accurate emulation (CPU stalling, interaction with sprite DMA, etc.)
|
||||
_needToRun = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t cyclesToRun = currentCycle - _previousCycle;
|
||||
return _frameCounter->NeedToRun(cyclesToRun) || _deltaModulationChannel->IrqPending(cyclesToRun);
|
||||
}
|
||||
|
||||
void APU::Exec()
|
||||
{
|
||||
_currentCycle++;
|
||||
if(_currentCycle == SoundMixer::CycleLength - 1) {
|
||||
EndFrame();
|
||||
} else if(NeedToRun(_currentCycle)) {
|
||||
Run();
|
||||
}
|
||||
}
|
||||
|
||||
void APU::EndFrame()
|
||||
{
|
||||
Run();
|
||||
_squareChannel[0]->EndFrame();
|
||||
_squareChannel[1]->EndFrame();
|
||||
_triangleChannel->EndFrame();
|
||||
_noiseChannel->EndFrame();
|
||||
_deltaModulationChannel->EndFrame();
|
||||
|
||||
_mixer->PlayAudioBuffer(_currentCycle);
|
||||
|
||||
_currentCycle = 0;
|
||||
_previousCycle = 0;
|
||||
}
|
||||
|
||||
void APU::ProcessCpuClock()
|
||||
{
|
||||
if(_apuEnabled) {
|
||||
Exec();
|
||||
}
|
||||
}
|
||||
|
||||
void APU::Reset(bool softReset)
|
||||
{
|
||||
_apuEnabled = true;
|
||||
_currentCycle = 0;
|
||||
_previousCycle = 0;
|
||||
_squareChannel[0]->Reset(softReset);
|
||||
_squareChannel[1]->Reset(softReset);
|
||||
_triangleChannel->Reset(softReset);
|
||||
_noiseChannel->Reset(softReset);
|
||||
_deltaModulationChannel->Reset(softReset);
|
||||
_frameCounter->Reset(softReset);
|
||||
}
|
||||
|
||||
void APU::StreamState(bool saving)
|
||||
{
|
||||
if(saving) {
|
||||
//End the APU frame - makes it simpler to restore sound after a state reload
|
||||
EndFrame();
|
||||
} else {
|
||||
_previousCycle = 0;
|
||||
_currentCycle = 0;
|
||||
}
|
||||
|
||||
SnapshotInfo squareChannel0{ _squareChannel[0].get() };
|
||||
SnapshotInfo squareChannel1{ _squareChannel[1].get() };
|
||||
SnapshotInfo triangleChannel{ _triangleChannel.get() };
|
||||
SnapshotInfo noiseChannel{ _noiseChannel.get() };
|
||||
SnapshotInfo deltaModulationChannel{ _deltaModulationChannel.get() };
|
||||
SnapshotInfo frameCounter{ _frameCounter.get() };
|
||||
SnapshotInfo mixer{ _mixer.get() };
|
||||
Stream(_nesModel, squareChannel0, squareChannel1, triangleChannel, noiseChannel, deltaModulationChannel, frameCounter, mixer);
|
||||
}
|
||||
|
||||
void APU::AddExpansionAudioDelta(AudioChannel channel, int16_t delta)
|
||||
{
|
||||
_mixer->AddDelta(channel, _currentCycle, delta);
|
||||
}
|
||||
|
||||
void APU::SetApuStatus(bool enabled)
|
||||
{
|
||||
_apuEnabled = enabled;
|
||||
}
|
||||
|
||||
bool APU::IsApuEnabled()
|
||||
{
|
||||
//Adding extra lines before/after NMI temporarely turns off the APU
|
||||
//This appears to result in less side-effects than spreading out the APU's
|
||||
//load over the entire PPU frame, like what was done before.
|
||||
//This is most likely due to the timing of the Frame Counter & DMC IRQs.
|
||||
return _apuEnabled;
|
||||
}
|
||||
|
||||
uint16_t APU::GetDmcReadAddress()
|
||||
{
|
||||
return _deltaModulationChannel->GetDmcReadAddress();
|
||||
}
|
||||
|
||||
void APU::SetDmcReadBuffer(uint8_t value)
|
||||
{
|
||||
_deltaModulationChannel->SetDmcReadBuffer(value);
|
||||
}
|
||||
|
||||
ApuState APU::GetState()
|
||||
{
|
||||
ApuState state;
|
||||
state.Dmc = _deltaModulationChannel->GetState();
|
||||
state.FrameCounter = _frameCounter->GetState();
|
||||
state.Noise = _noiseChannel->GetState();
|
||||
state.Square1 = _squareChannel[0]->GetState();
|
||||
state.Square2 = _squareChannel[1]->GetState();
|
||||
state.Triangle = _triangleChannel->GetState();
|
||||
return state;
|
||||
}
|
77
Core/APU.h
77
Core/APU.h
|
@ -1,77 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "IMemoryHandler.h"
|
||||
#include "IAudioDevice.h"
|
||||
#include "Snapshotable.h"
|
||||
#include "EmulationSettings.h"
|
||||
|
||||
class Console;
|
||||
class SquareChannel;
|
||||
class TriangleChannel;
|
||||
class NoiseChannel;
|
||||
class DeltaModulationChannel;
|
||||
class ApuFrameCounter;
|
||||
class SoundMixer;
|
||||
class Debugger;
|
||||
enum class FrameType;
|
||||
enum class NesModel;
|
||||
|
||||
class APU : public Snapshotable, public IMemoryHandler
|
||||
{
|
||||
friend ApuFrameCounter;
|
||||
|
||||
private:
|
||||
bool _apuEnabled;
|
||||
bool _needToRun;
|
||||
|
||||
uint32_t _previousCycle;
|
||||
uint32_t _currentCycle;
|
||||
|
||||
unique_ptr<SquareChannel> _squareChannel[2];
|
||||
unique_ptr<TriangleChannel> _triangleChannel;
|
||||
unique_ptr<NoiseChannel> _noiseChannel;
|
||||
unique_ptr<DeltaModulationChannel> _deltaModulationChannel;
|
||||
unique_ptr<ApuFrameCounter> _frameCounter;
|
||||
|
||||
shared_ptr<Console> _console;
|
||||
shared_ptr<SoundMixer> _mixer;
|
||||
EmulationSettings* _settings;
|
||||
|
||||
NesModel _nesModel;
|
||||
|
||||
private:
|
||||
__forceinline bool NeedToRun(uint32_t currentCycle);
|
||||
|
||||
void FrameCounterTick(FrameType type);
|
||||
uint8_t GetStatus();
|
||||
|
||||
protected:
|
||||
void StreamState(bool saving) override;
|
||||
|
||||
public:
|
||||
APU(shared_ptr<Console> console);
|
||||
~APU();
|
||||
|
||||
void Reset(bool softReset);
|
||||
void SetNesModel(NesModel model, bool forceInit = false);
|
||||
|
||||
uint8_t ReadRAM(uint16_t addr) override;
|
||||
uint8_t PeekRAM(uint16_t addr) override;
|
||||
void WriteRAM(uint16_t addr, uint8_t value) override;
|
||||
void GetMemoryRanges(MemoryRanges &ranges) override;
|
||||
|
||||
ApuState GetState();
|
||||
|
||||
void Exec();
|
||||
void ProcessCpuClock();
|
||||
void Run();
|
||||
void EndFrame();
|
||||
|
||||
void AddExpansionAudioDelta(AudioChannel channel, int16_t delta);
|
||||
void SetApuStatus(bool enabled);
|
||||
bool IsApuEnabled();
|
||||
uint16_t GetDmcReadAddress();
|
||||
void SetDmcReadBuffer(uint8_t value);
|
||||
void SetNeedToRun();
|
||||
};
|
25
Core/AXROM.h
25
Core/AXROM.h
|
@ -1,25 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class AXROM : public BaseMapper
|
||||
{
|
||||
protected:
|
||||
virtual uint16_t GetPRGPageSize() override { return 0x8000; }
|
||||
virtual uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
SelectCHRPage(0, 0);
|
||||
WriteRegister(0, GetPowerOnByte());
|
||||
}
|
||||
|
||||
bool HasBusConflicts() override { return _romInfo.SubMapperID == 2; }
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
SelectPRGPage(0, value & 0x0F);
|
||||
|
||||
SetMirroringType(((value & 0x10) == 0x10) ? MirroringType::ScreenBOnly : MirroringType::ScreenAOnly);
|
||||
}
|
||||
};
|
54
Core/Ac08.h
54
Core/Ac08.h
|
@ -1,54 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Ac08 : public BaseMapper
|
||||
{
|
||||
private:
|
||||
uint8_t _reg;
|
||||
|
||||
protected:
|
||||
uint16_t GetPRGPageSize() override { return 0x2000; }
|
||||
uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
AddRegisterRange(0x4025, 0x4025, MemoryOperation::Write);
|
||||
|
||||
_reg = 0;
|
||||
|
||||
SelectPrgPage4x(0, -4);
|
||||
SelectCHRPage(0, 0);
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
Stream(_reg);
|
||||
if(!saving) {
|
||||
UpdateState();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateState()
|
||||
{
|
||||
SetCpuMemoryMapping(0x6000, 0x7FFF, _reg, PrgMemoryType::PrgRom);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if(addr == 0x4025) {
|
||||
SetMirroringType(value & 0x08 ? MirroringType::Horizontal : MirroringType::Vertical);
|
||||
} else {
|
||||
if(addr == 0x8001) {
|
||||
//Green beret
|
||||
_reg = (value >> 1) & 0x0F;
|
||||
} else {
|
||||
//Castlevania?
|
||||
_reg = value & 0x0F;
|
||||
}
|
||||
UpdateState();
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,91 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Action53 : public BaseMapper
|
||||
{
|
||||
private:
|
||||
uint8_t _selectedReg;
|
||||
uint8_t _regs[4];
|
||||
uint8_t _mirroringBit;
|
||||
|
||||
protected:
|
||||
virtual uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
virtual uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
_selectedReg = 0;
|
||||
_mirroringBit = 0;
|
||||
memset(_regs, 0, sizeof(_regs));
|
||||
|
||||
AddRegisterRange(0x5000, 0x5FFF, MemoryOperation::Write);
|
||||
|
||||
SelectPRGPage(1, -1);
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
|
||||
ArrayInfo<uint8_t> regs{ _regs,4 };
|
||||
Stream(_selectedReg, _mirroringBit, regs);
|
||||
}
|
||||
|
||||
void UpdateState()
|
||||
{
|
||||
uint8_t mirroring = _regs[2] & 0x03;
|
||||
if(!(mirroring & 0x02)) {
|
||||
mirroring = _mirroringBit;
|
||||
}
|
||||
|
||||
switch(mirroring) {
|
||||
case 0: SetMirroringType(MirroringType::ScreenAOnly); break;
|
||||
case 1: SetMirroringType(MirroringType::ScreenBOnly); break;
|
||||
case 2: SetMirroringType(MirroringType::Vertical); break;
|
||||
case 3: SetMirroringType(MirroringType::Horizontal); break;
|
||||
}
|
||||
|
||||
uint8_t gameSize = (_regs[2] & 0x30) >> 4;
|
||||
uint8_t prgSize = (_regs[2] & 0x08) >> 3;
|
||||
uint8_t slotSelect = (_regs[2] & 0x04) >> 2;
|
||||
uint8_t chrSelect = _regs[0] & 0x03;
|
||||
uint8_t prgSelect = _regs[1] & 0x0F;
|
||||
uint16_t outerPrgSelect = _regs[3] << 1;
|
||||
|
||||
SelectCHRPage(0, chrSelect);
|
||||
|
||||
if(prgSize) {
|
||||
uint8_t bank = (slotSelect ? 0 : 1);
|
||||
switch(gameSize) {
|
||||
case 0: SelectPRGPage(bank, (outerPrgSelect & 0x1FE) | (prgSelect & 0x01)); break;
|
||||
case 1: SelectPRGPage(bank, (outerPrgSelect & 0x1FC) | (prgSelect & 0x03)); break;
|
||||
case 2: SelectPRGPage(bank, (outerPrgSelect & 0x1F8) | (prgSelect & 0x07)); break;
|
||||
case 3: SelectPRGPage(bank, (outerPrgSelect & 0x1F0) | (prgSelect & 0x0F)); break;
|
||||
}
|
||||
SelectPRGPage(slotSelect ? 1 : 0, (outerPrgSelect & 0x1FE) | slotSelect);
|
||||
} else {
|
||||
prgSelect <<= 1;
|
||||
uint16_t outerAnd[4]{ 0x1FE, 0x1FC, 0x1F8, 0x1F0 };
|
||||
uint8_t innerAnd[4]{ 0x01, 0x03, 0x07, 0x0F };
|
||||
SelectPRGPage(0, (outerPrgSelect & outerAnd[gameSize]) | (prgSelect & innerAnd[gameSize]));
|
||||
SelectPRGPage(1, (outerPrgSelect & outerAnd[gameSize]) | ((prgSelect | 0x01) & innerAnd[gameSize]));
|
||||
}
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if(addr <= 0x5FFF) {
|
||||
_selectedReg = ((value & 0x80) >> 6) | (value & 0x01);
|
||||
} else if(addr >= 0x8000) {
|
||||
if(_selectedReg <= 1) {
|
||||
_mirroringBit = (value >> 4) & 0x01;
|
||||
} else if(_selectedReg == 2) {
|
||||
_mirroringBit = (value & 0x01);
|
||||
}
|
||||
|
||||
_regs[_selectedReg] = value;
|
||||
UpdateState();
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,43 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class ActionEnterprises : public BaseMapper
|
||||
{
|
||||
protected:
|
||||
virtual uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
virtual uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
WriteRegister(0x8000, 0);
|
||||
}
|
||||
|
||||
virtual void Reset(bool softReset) override
|
||||
{
|
||||
WriteRegister(0x8000, 0);
|
||||
}
|
||||
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
uint8_t chipSelect = (addr >> 11) & 0x03;
|
||||
|
||||
if(chipSelect == 3) {
|
||||
chipSelect = 2;
|
||||
}
|
||||
|
||||
uint8_t prgPage = ((addr >> 6) & 0x1F) | (chipSelect << 5);
|
||||
if(addr & 0x20) {
|
||||
SelectPRGPage(0, prgPage);
|
||||
SelectPRGPage(1, prgPage);
|
||||
} else {
|
||||
SelectPRGPage(0, prgPage & 0xFE);
|
||||
SelectPRGPage(1, (prgPage & 0xFE) + 1);
|
||||
}
|
||||
|
||||
SelectCHRPage(0, ((addr & 0x0F) << 2) | (value & 0x03));
|
||||
|
||||
SetMirroringType(addr & 0x2000 ? MirroringType::Horizontal : MirroringType::Vertical);
|
||||
}
|
||||
};
|
|
@ -1,97 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "ApuLengthCounter.h"
|
||||
#include "Console.h"
|
||||
|
||||
class ApuEnvelope : public ApuLengthCounter
|
||||
{
|
||||
private:
|
||||
bool _constantVolume = false;
|
||||
uint8_t _volume = 0;
|
||||
|
||||
uint8_t _envelopeCounter = 0;
|
||||
|
||||
bool _start = false;
|
||||
int8_t _divider = 0;
|
||||
uint8_t _counter = 0;
|
||||
|
||||
protected:
|
||||
ApuEnvelope(AudioChannel channel, shared_ptr<Console> console, SoundMixer* mixer) : ApuLengthCounter(channel, console, mixer)
|
||||
{
|
||||
}
|
||||
|
||||
void InitializeEnvelope(uint8_t regValue)
|
||||
{
|
||||
_constantVolume = (regValue & 0x10) == 0x10;
|
||||
_volume = regValue & 0x0F;
|
||||
}
|
||||
|
||||
void ResetEnvelope()
|
||||
{
|
||||
_start = true;
|
||||
}
|
||||
|
||||
uint32_t GetVolume()
|
||||
{
|
||||
if(_lengthCounter > 0) {
|
||||
if(_constantVolume) {
|
||||
return _volume;
|
||||
} else {
|
||||
return _counter;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
virtual void Reset(bool softReset) override
|
||||
{
|
||||
ApuLengthCounter::Reset(softReset);
|
||||
|
||||
_constantVolume = false;
|
||||
_volume = 0;
|
||||
_envelopeCounter = 0;
|
||||
_start = false;
|
||||
_divider = 0;
|
||||
_counter = 0;
|
||||
}
|
||||
|
||||
virtual void StreamState(bool saving) override
|
||||
{
|
||||
ApuLengthCounter::StreamState(saving);
|
||||
|
||||
Stream(_constantVolume, _volume, _envelopeCounter, _start, _divider, _counter);
|
||||
}
|
||||
|
||||
void TickEnvelope()
|
||||
{
|
||||
if(!_start) {
|
||||
_divider--;
|
||||
if(_divider < 0) {
|
||||
_divider = _volume;
|
||||
if(_counter > 0) {
|
||||
_counter--;
|
||||
} else if(_lengthCounterHalt) {
|
||||
_counter = 15;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_start = false;
|
||||
_counter = 15;
|
||||
_divider = _volume;
|
||||
}
|
||||
}
|
||||
|
||||
ApuEnvelopeState GetState()
|
||||
{
|
||||
ApuEnvelopeState state;
|
||||
state.ConstantVolume = _constantVolume;
|
||||
state.Counter = _counter;
|
||||
state.Divider = _divider;
|
||||
state.Loop = _lengthCounterHalt;
|
||||
state.StartFlag = _start;
|
||||
state.Volume = _volume;
|
||||
return state;
|
||||
}
|
||||
};
|
|
@ -1,208 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "IMemoryHandler.h"
|
||||
#include "EmulationSettings.h"
|
||||
|
||||
enum class FrameType
|
||||
{
|
||||
None = 0,
|
||||
QuarterFrame = 1,
|
||||
HalfFrame = 2,
|
||||
};
|
||||
|
||||
class ApuFrameCounter : public IMemoryHandler, public Snapshotable
|
||||
{
|
||||
private:
|
||||
const int32_t _stepCyclesNtsc[2][6] = { { 7457, 14913, 22371, 29828, 29829, 29830},
|
||||
{ 7457, 14913, 22371, 29829, 37281, 37282} };
|
||||
const int32_t _stepCyclesPal[2][6] = { { 8313, 16627, 24939, 33252, 33253, 33254},
|
||||
{ 8313, 16627, 24939, 33253, 41565, 41566} };
|
||||
const FrameType _frameType[2][6] = { { FrameType::QuarterFrame, FrameType::HalfFrame, FrameType::QuarterFrame, FrameType::None, FrameType::HalfFrame, FrameType::None },
|
||||
{ FrameType::QuarterFrame, FrameType::HalfFrame, FrameType::QuarterFrame, FrameType::None, FrameType::HalfFrame, FrameType::None } };
|
||||
|
||||
shared_ptr<Console> _console;
|
||||
int32_t _stepCycles[2][6];
|
||||
NesModel _nesModel;
|
||||
int32_t _previousCycle;
|
||||
uint32_t _currentStep;
|
||||
uint32_t _stepMode; //0: 4-step mode, 1: 5-step mode
|
||||
bool _inhibitIRQ;
|
||||
uint8_t _blockFrameCounterTick;
|
||||
int16_t _newValue;
|
||||
int8_t _writeDelayCounter;
|
||||
|
||||
public:
|
||||
ApuFrameCounter(shared_ptr<Console> console)
|
||||
{
|
||||
_console = console;
|
||||
Reset(false);
|
||||
}
|
||||
|
||||
void Reset(bool softReset)
|
||||
{
|
||||
_nesModel = NesModel::Auto;
|
||||
|
||||
_previousCycle = 0;
|
||||
|
||||
//"After reset: APU mode in $4017 was unchanged", so we need to keep whatever value _stepMode has for soft resets
|
||||
if(!softReset) {
|
||||
_stepMode = 0;
|
||||
}
|
||||
|
||||
_currentStep = 0;
|
||||
|
||||
|
||||
//"After reset or power-up, APU acts as if $4017 were written with $00 from 9 to 12 clocks before first instruction begins."
|
||||
//This is emulated in the CPU::Reset function
|
||||
//Reset acts as if $00 was written to $4017
|
||||
_newValue = _stepMode ? 0x80 : 0x00;
|
||||
_writeDelayCounter = 3;
|
||||
_inhibitIRQ = false;
|
||||
|
||||
_blockFrameCounterTick = 0;
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
Stream(_previousCycle, _currentStep, _stepMode, _inhibitIRQ, _nesModel, _blockFrameCounterTick, _writeDelayCounter, _newValue);
|
||||
|
||||
if(!saving) {
|
||||
SetNesModel(_nesModel);
|
||||
}
|
||||
}
|
||||
|
||||
void SetNesModel(NesModel model)
|
||||
{
|
||||
if(_nesModel != model) {
|
||||
_nesModel = model;
|
||||
switch(model) {
|
||||
case NesModel::Auto:
|
||||
//Auto should never be set here
|
||||
break;
|
||||
|
||||
case NesModel::NTSC:
|
||||
case NesModel::Dendy:
|
||||
memcpy(_stepCycles, _stepCyclesNtsc, sizeof(_stepCycles));
|
||||
break;
|
||||
|
||||
case NesModel::PAL:
|
||||
memcpy(_stepCycles, _stepCyclesPal, sizeof(_stepCycles));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t Run(int32_t &cyclesToRun)
|
||||
{
|
||||
uint32_t cyclesRan;
|
||||
|
||||
if(_previousCycle + cyclesToRun >= _stepCycles[_stepMode][_currentStep]) {
|
||||
if(!_inhibitIRQ && _stepMode == 0 && _currentStep >= 3) {
|
||||
//Set irq on the last 3 cycles for 4-step mode
|
||||
_console->GetCpu()->SetIrqSource(IRQSource::FrameCounter);
|
||||
}
|
||||
|
||||
FrameType type = _frameType[_stepMode][_currentStep];
|
||||
if(type != FrameType::None && !_blockFrameCounterTick) {
|
||||
_console->GetApu()->FrameCounterTick(type);
|
||||
|
||||
//Do not allow writes to 4017 to clock the frame counter for the next cycle (i.e this odd cycle + the following even cycle)
|
||||
_blockFrameCounterTick = 2;
|
||||
}
|
||||
|
||||
if(_stepCycles[_stepMode][_currentStep] < _previousCycle) {
|
||||
//This can happen when switching from PAL to NTSC, which can cause a freeze (endless loop in APU)
|
||||
cyclesRan = 0;
|
||||
} else {
|
||||
cyclesRan = _stepCycles[_stepMode][_currentStep] - _previousCycle;
|
||||
}
|
||||
|
||||
cyclesToRun -= cyclesRan;
|
||||
|
||||
_currentStep++;
|
||||
if(_currentStep == 6) {
|
||||
_currentStep = 0;
|
||||
_previousCycle = 0;
|
||||
} else {
|
||||
_previousCycle += cyclesRan;
|
||||
}
|
||||
} else {
|
||||
cyclesRan = cyclesToRun;
|
||||
cyclesToRun = 0;
|
||||
_previousCycle += cyclesRan;
|
||||
}
|
||||
|
||||
if(_newValue >= 0) {
|
||||
_writeDelayCounter--;
|
||||
if(_writeDelayCounter == 0) {
|
||||
//Apply new value after the appropriate number of cycles has elapsed
|
||||
_stepMode = ((_newValue & 0x80) == 0x80) ? 1 : 0;
|
||||
|
||||
_writeDelayCounter = -1;
|
||||
_currentStep = 0;
|
||||
_previousCycle = 0;
|
||||
_newValue = -1;
|
||||
|
||||
if(_stepMode && !_blockFrameCounterTick) {
|
||||
//"Writing to $4017 with bit 7 set will immediately generate a clock for both the quarter frame and the half frame units, regardless of what the sequencer is doing."
|
||||
_console->GetApu()->FrameCounterTick(FrameType::HalfFrame);
|
||||
_blockFrameCounterTick = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(_blockFrameCounterTick > 0) {
|
||||
_blockFrameCounterTick--;
|
||||
}
|
||||
|
||||
return cyclesRan;
|
||||
}
|
||||
|
||||
bool NeedToRun(uint32_t cyclesToRun)
|
||||
{
|
||||
//Run APU when:
|
||||
// -A new value is pending
|
||||
// -The "blockFrameCounterTick" process is running
|
||||
// -We're at the before-last or last tick of the current step
|
||||
return _newValue >= 0 || _blockFrameCounterTick > 0 || (_previousCycle + (int32_t)cyclesToRun >= _stepCycles[_stepMode][_currentStep] - 1);
|
||||
}
|
||||
|
||||
void GetMemoryRanges(MemoryRanges &ranges) override
|
||||
{
|
||||
ranges.AddHandler(MemoryOperation::Write, 0x4017);
|
||||
}
|
||||
|
||||
uint8_t ReadRAM(uint16_t addr) override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WriteRAM(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
_console->GetApu()->Run();
|
||||
_newValue = value;
|
||||
|
||||
//Reset sequence after $4017 is written to
|
||||
if(_console->GetCpu()->GetCycleCount() & 0x01) {
|
||||
//"If the write occurs between APU cycles, the effects occur 4 CPU cycles after the write cycle. "
|
||||
_writeDelayCounter = 4;
|
||||
} else {
|
||||
//"If the write occurs during an APU cycle, the effects occur 3 CPU cycles after the $4017 write cycle"
|
||||
_writeDelayCounter = 3;
|
||||
}
|
||||
|
||||
_inhibitIRQ = (value & 0x40) == 0x40;
|
||||
if(_inhibitIRQ) {
|
||||
_console->GetCpu()->ClearIrqSource(IRQSource::FrameCounter);
|
||||
}
|
||||
}
|
||||
|
||||
ApuFrameCounterState GetState()
|
||||
{
|
||||
ApuFrameCounterState state;
|
||||
state.IrqEnabled = !_inhibitIRQ;
|
||||
state.SequencePosition = std::min<uint8_t>(_currentStep, _stepMode ? 5 : 4);
|
||||
state.FiveStepMode = _stepMode == 1;
|
||||
return state;
|
||||
}
|
||||
};
|
|
@ -1,111 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseApuChannel.h"
|
||||
#include "Console.h"
|
||||
#include "APU.h"
|
||||
|
||||
class ApuLengthCounter : public BaseApuChannel
|
||||
{
|
||||
private:
|
||||
uint8_t _lcLookupTable[32] = { 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30 };
|
||||
bool _newHaltValue;
|
||||
|
||||
protected:
|
||||
bool _enabled = false;
|
||||
bool _lengthCounterHalt;
|
||||
uint8_t _lengthCounter;
|
||||
uint8_t _lengthCounterReloadValue;
|
||||
uint8_t _lengthCounterPreviousValue;
|
||||
|
||||
void InitializeLengthCounter(bool haltFlag)
|
||||
{
|
||||
_console->GetApu()->SetNeedToRun();
|
||||
_newHaltValue = haltFlag;
|
||||
}
|
||||
|
||||
void LoadLengthCounter(uint8_t value)
|
||||
{
|
||||
if(_enabled) {
|
||||
_lengthCounterReloadValue = _lcLookupTable[value];
|
||||
_lengthCounterPreviousValue = _lengthCounter;
|
||||
_console->GetApu()->SetNeedToRun();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
ApuLengthCounter(AudioChannel channel, shared_ptr<Console> console, SoundMixer* mixer) : BaseApuChannel(channel, console, mixer)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void Reset(bool softReset) override
|
||||
{
|
||||
BaseApuChannel::Reset(softReset);
|
||||
|
||||
if(softReset) {
|
||||
_enabled = false;
|
||||
if(GetChannel() != AudioChannel::Triangle) {
|
||||
//"At reset, length counters should be enabled, triangle unaffected"
|
||||
_lengthCounterHalt = false;
|
||||
_lengthCounter = 0;
|
||||
_newHaltValue = false;
|
||||
_lengthCounterReloadValue = 0;
|
||||
_lengthCounterPreviousValue = 0;
|
||||
}
|
||||
} else {
|
||||
_enabled = false;
|
||||
_lengthCounterHalt = false;
|
||||
_lengthCounter = 0;
|
||||
_newHaltValue = false;
|
||||
_lengthCounterReloadValue = 0;
|
||||
_lengthCounterPreviousValue = 0;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void StreamState(bool saving) override
|
||||
{
|
||||
BaseApuChannel::StreamState(saving);
|
||||
|
||||
Stream(_enabled, _lengthCounterHalt, _newHaltValue, _lengthCounter, _lengthCounterPreviousValue, _lengthCounterReloadValue);
|
||||
}
|
||||
|
||||
bool GetStatus() override
|
||||
{
|
||||
return _lengthCounter > 0;
|
||||
}
|
||||
|
||||
void ReloadCounter()
|
||||
{
|
||||
if(_lengthCounterReloadValue) {
|
||||
if(_lengthCounter == _lengthCounterPreviousValue) {
|
||||
_lengthCounter = _lengthCounterReloadValue;
|
||||
}
|
||||
_lengthCounterReloadValue = 0;
|
||||
}
|
||||
|
||||
_lengthCounterHalt = _newHaltValue;
|
||||
}
|
||||
|
||||
void TickLengthCounter()
|
||||
{
|
||||
if(_lengthCounter > 0 && !_lengthCounterHalt) {
|
||||
_lengthCounter--;
|
||||
}
|
||||
}
|
||||
|
||||
void SetEnabled(bool enabled)
|
||||
{
|
||||
if(!enabled) {
|
||||
_lengthCounter = 0;
|
||||
}
|
||||
_enabled = enabled;
|
||||
}
|
||||
|
||||
ApuLengthCounterState GetState()
|
||||
{
|
||||
ApuLengthCounterState state;
|
||||
state.Counter = _lengthCounter;
|
||||
state.Halt = _lengthCounterHalt;
|
||||
state.ReloadValue = _lengthCounterReloadValue;
|
||||
return state;
|
||||
}
|
||||
};
|
|
@ -1,87 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseControlDevice.h"
|
||||
#include "ControlManager.h"
|
||||
#include "PPU.h"
|
||||
#include "IKeyManager.h"
|
||||
#include "KeyManager.h"
|
||||
|
||||
class ArkanoidController : public BaseControlDevice
|
||||
{
|
||||
private:
|
||||
uint32_t _currentValue = (0xF4 - 0x54) / 2;
|
||||
uint32_t _stateBuffer = 0;
|
||||
enum Buttons { Fire };
|
||||
|
||||
protected:
|
||||
bool HasCoordinates() override { return true; }
|
||||
|
||||
string GetKeyNames() override
|
||||
{
|
||||
return "F";
|
||||
}
|
||||
|
||||
void InternalSetStateFromInput() override
|
||||
{
|
||||
if(_console->GetSettings()->InputEnabled()) {
|
||||
SetPressedState(Buttons::Fire, KeyManager::IsMouseButtonPressed(MouseButton::LeftButton));
|
||||
SetMovement(KeyManager::GetMouseMovement(_console->GetSettings()->GetMouseSensitivity(MouseDevice::ArkanoidController)));
|
||||
}
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseControlDevice::StreamState(saving);
|
||||
Stream(_stateBuffer, _currentValue);
|
||||
}
|
||||
|
||||
void RefreshStateBuffer() override
|
||||
{
|
||||
MouseMovement mov = GetMovement();
|
||||
|
||||
_currentValue += mov.dx;
|
||||
if(_currentValue < 0x54) {
|
||||
_currentValue = 0x54;
|
||||
} else if(_currentValue > 0xF4) {
|
||||
_currentValue = 0xF4;
|
||||
}
|
||||
|
||||
_stateBuffer = _currentValue;
|
||||
}
|
||||
|
||||
public:
|
||||
ArkanoidController(shared_ptr<Console> console, uint8_t port) : BaseControlDevice(console, port)
|
||||
{
|
||||
}
|
||||
|
||||
uint8_t ReadRAM(uint16_t addr) override
|
||||
{
|
||||
uint8_t output = 0;
|
||||
if(IsExpansionDevice()) {
|
||||
if(addr == 0x4016) {
|
||||
//Fire button is on port 1
|
||||
if(IsPressed(ArkanoidController::Buttons::Fire)) {
|
||||
output |= 0x02;
|
||||
}
|
||||
} else if(addr == 0x4017) {
|
||||
//Serial data is on port 2
|
||||
output |= ((~_stateBuffer) >> 6) & 0x02;
|
||||
_stateBuffer <<= 1;
|
||||
}
|
||||
} else if(IsCurrentPort(addr)) {
|
||||
output = ((~_stateBuffer) >> 3) & 0x10;
|
||||
_stateBuffer <<= 1;
|
||||
|
||||
if(IsPressed(ArkanoidController::Buttons::Fire)) {
|
||||
output |= 0x08;
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
void WriteRAM(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
StrobeProcessWrite(value);
|
||||
}
|
||||
};
|
|
@ -1,65 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "../Utilities/FolderUtilities.h"
|
||||
#include "Console.h"
|
||||
#include "BaseControlDevice.h"
|
||||
#include "IBattery.h"
|
||||
#include "BatteryManager.h"
|
||||
|
||||
class AsciiTurboFile : public BaseControlDevice, public IBattery
|
||||
{
|
||||
private:
|
||||
static constexpr int FileSize = 0x2000;
|
||||
static constexpr int BitCount = FileSize * 8;
|
||||
uint8_t _lastWrite = 0;
|
||||
uint16_t _position = 0;
|
||||
uint8_t _data[AsciiTurboFile::FileSize];
|
||||
|
||||
protected:
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseControlDevice::StreamState(saving);
|
||||
ArrayInfo<uint8_t> data{ _data, AsciiTurboFile::FileSize };
|
||||
Stream(_position, _lastWrite, data);
|
||||
}
|
||||
|
||||
public:
|
||||
AsciiTurboFile(shared_ptr<Console> console) : BaseControlDevice(console, BaseControlDevice::ExpDevicePort)
|
||||
{
|
||||
_console->GetBatteryManager()->LoadBattery(".tf", _data, AsciiTurboFile::FileSize);
|
||||
}
|
||||
|
||||
~AsciiTurboFile()
|
||||
{
|
||||
SaveBattery();
|
||||
}
|
||||
|
||||
void SaveBattery() override
|
||||
{
|
||||
_console->GetBatteryManager()->SaveBattery(".tf", _data, AsciiTurboFile::FileSize);
|
||||
}
|
||||
|
||||
uint8_t ReadRAM(uint16_t addr) override
|
||||
{
|
||||
if(addr == 0x4017) {
|
||||
return ((_data[_position / 8] >> (_position % 8)) & 0x01) << 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WriteRAM(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if(!(value & 0x02)) {
|
||||
_position = 0;
|
||||
}
|
||||
|
||||
if(!(value & 0x04) && (_lastWrite & 0x04)) {
|
||||
//Clock, perform write, increase position
|
||||
_data[_position / 8] &= ~(1 << (_position % 8));
|
||||
_data[_position / 8] |= (value & 0x01) << (_position % 8);
|
||||
_position = (_position + 1) & (AsciiTurboFile::BitCount - 1);
|
||||
}
|
||||
|
||||
_lastWrite = value;
|
||||
}
|
||||
};
|
|
@ -1,462 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
#include <regex>
|
||||
#include <unordered_map>
|
||||
#include "../Utilities/HexUtilities.h"
|
||||
#include "../Utilities/StringUtilities.h"
|
||||
#include "Assembler.h"
|
||||
#include "CPU.h"
|
||||
#include "DisassemblyInfo.h"
|
||||
#include "LabelManager.h"
|
||||
|
||||
static const std::regex instRegex = std::regex("^\\s*([a-zA-Z]{3})[*]{0,1}[\\s]*(#%|#){0,1}([(]{0,1})[\\s]*([$]{0,1})([^,)(;:]*)[\\s]*((,x\\)|\\),y|,x|,y|\\)){0,1})\\s*(;*)(.*)", std::regex_constants::icase);
|
||||
static const std::regex isCommentOrBlank = std::regex("^\\s*([;]+.*$|\\s*$)", std::regex_constants::icase);
|
||||
static const std::regex labelRegex = std::regex("^\\s*([@_a-zA-Z][@_a-zA-Z0-9]*):(.*)", std::regex_constants::icase);
|
||||
static const std::regex byteRegex = std::regex("^\\s*[.]byte\\s+((\\$[a-fA-F0-9]{1,2},)*)(\\$[a-fA-F0-9]{1,2})+\\s*(;*)(.*)$", std::regex_constants::icase);
|
||||
|
||||
static string opName[256] = {
|
||||
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
|
||||
"BRK", "ORA", "STP", "SLO", "NOP", "ORA", "ASL", "SLO", "PHP", "ORA", "ASL", "ANC", "NOP", "ORA", "ASL", "SLO", //0
|
||||
"BPL", "ORA", "STP", "SLO", "NOP", "ORA", "ASL", "SLO", "CLC", "ORA", "NOP", "SLO", "NOP", "ORA", "ASL", "SLO", //1
|
||||
"JSR", "AND", "STP", "RLA", "BIT", "AND", "ROL", "RLA", "PLP", "AND", "ROL", "ANC", "BIT", "AND", "ROL", "RLA", //2
|
||||
"BMI", "AND", "STP", "RLA", "NOP", "AND", "ROL", "RLA", "SEC", "AND", "NOP", "RLA", "NOP", "AND", "ROL", "RLA", //3
|
||||
"RTI", "EOR", "STP", "SRE", "NOP", "EOR", "LSR", "SRE", "PHA", "EOR", "LSR", "ALR", "JMP", "EOR", "LSR", "SRE", //4
|
||||
"BVC", "EOR", "STP", "SRE", "NOP", "EOR", "LSR", "SRE", "CLI", "EOR", "NOP", "SRE", "NOP", "EOR", "LSR", "SRE", //5
|
||||
"RTS", "ADC", "STP", "RRA", "NOP", "ADC", "ROR", "RRA", "PLA", "ADC", "ROR", "ARR", "JMP", "ADC", "ROR", "RRA", //6
|
||||
"BVS", "ADC", "STP", "RRA", "NOP", "ADC", "ROR", "RRA", "SEI", "ADC", "NOP", "RRA", "NOP", "ADC", "ROR", "RRA", //7
|
||||
"NOP", "STA", "NOP", "SAX", "STY", "STA", "STX", "SAX", "DEY", "NOP", "TXA", "XAA", "STY", "STA", "STX", "SAX", //8
|
||||
"BCC", "STA", "STP", "AHX", "STY", "STA", "STX", "SAX", "TYA", "STA", "TXS", "TAS", "SHY", "STA", "SHX", "AXA", //9
|
||||
"LDY", "LDA", "LDX", "LAX", "LDY", "LDA", "LDX", "LAX", "TAY", "LDA", "TAX", "LAX", "LDY", "LDA", "LDX", "LAX", //A
|
||||
"BCS", "LDA", "STP", "LAX", "LDY", "LDA", "LDX", "LAX", "CLV", "LDA", "TSX", "LAS", "LDY", "LDA", "LDX", "LAX", //B
|
||||
"CPY", "CMP", "NOP", "DCP", "CPY", "CMP", "DEC", "DCP", "INY", "CMP", "DEX", "AXS", "CPY", "CMP", "DEC", "DCP", //C
|
||||
"BNE", "CMP", "STP", "DCP", "NOP", "CMP", "DEC", "DCP", "CLD", "CMP", "NOP", "DCP", "NOP", "CMP", "DEC", "DCP", //D
|
||||
"CPX", "SBC", "NOP", "ISC", "CPX", "SBC", "INC", "ISC", "INX", "SBC", "NOP", "SBC", "CPX", "SBC", "INC", "ISC", //E
|
||||
"BEQ", "SBC", "STP", "ISC", "NOP", "SBC", "INC", "ISC", "SED", "SBC", "NOP", "ISC", "NOP", "SBC", "INC", "ISC" //F
|
||||
};
|
||||
|
||||
void Assembler::ProcessLine(string code, uint16_t &instructionAddress, vector<int16_t>& output, std::unordered_map<string, uint16_t> &labels, bool firstPass, std::unordered_map<string, uint16_t> ¤tPassLabels)
|
||||
{
|
||||
//Remove extra spaces as part of processing
|
||||
size_t offset = code.find_first_of(',', 0);
|
||||
if(offset != string::npos) {
|
||||
code.erase(std::remove(code.begin() + offset + 1, code.end(), ' '), code.end());
|
||||
}
|
||||
offset = code.find_first_of(')', 0);
|
||||
if(offset != string::npos) {
|
||||
code.erase(std::remove(code.begin() + offset + 1, code.end(), ' '), code.end());
|
||||
}
|
||||
|
||||
//Determine if the line is blank, a comment, a label or code
|
||||
std::smatch match;
|
||||
if(std::regex_search(code, match, byteRegex)) {
|
||||
vector<string> bytes = StringUtilities::Split(match.str(1) + match.str(3), ',');
|
||||
for(string &byte : bytes) {
|
||||
output.push_back((uint8_t)(HexUtilities::FromHex(byte.substr(1))));
|
||||
instructionAddress++;
|
||||
}
|
||||
output.push_back(AssemblerSpecialCodes::EndOfLine);
|
||||
} else if(std::regex_search(code, match, labelRegex)) {
|
||||
string label = match.str(1);
|
||||
string afterLabel = match.str(2);
|
||||
if(currentPassLabels.find(match.str(1)) != currentPassLabels.end()) {
|
||||
output.push_back(AssemblerSpecialCodes::LabelRedefinition);
|
||||
} else {
|
||||
labels[match.str(1)] = instructionAddress;
|
||||
currentPassLabels[match.str(1)] = instructionAddress;
|
||||
ProcessLine(afterLabel, instructionAddress, output, labels, firstPass, currentPassLabels);
|
||||
}
|
||||
return;
|
||||
} else if(std::regex_search(code, match, isCommentOrBlank)) {
|
||||
output.push_back(AssemblerSpecialCodes::EndOfLine);
|
||||
return;
|
||||
} else if(std::regex_search(code, match, instRegex) && match.size() > 1) {
|
||||
LineData lineData;
|
||||
AssemblerSpecialCodes result = GetLineData(match, lineData, labels, firstPass);
|
||||
if(result == AssemblerSpecialCodes::OK) {
|
||||
AssembleInstruction(lineData, instructionAddress, output, firstPass);
|
||||
} else {
|
||||
output.push_back(result);
|
||||
}
|
||||
} else {
|
||||
output.push_back(AssemblerSpecialCodes::ParsingError);
|
||||
}
|
||||
}
|
||||
|
||||
AssemblerSpecialCodes Assembler::GetLineData(std::smatch match, LineData &lineData, std::unordered_map<string, uint16_t> &labels, bool firstPass)
|
||||
{
|
||||
bool isBinary = match.str(2).length() > 1 && match.str(2)[1] == '%'; //Immediate + binary: "#%"
|
||||
|
||||
lineData.OpCode = match.str(1);
|
||||
lineData.IsImmediate = !match.str(2).empty();
|
||||
lineData.IsHex = !match.str(4).empty();
|
||||
lineData.HasComment = !match.str(8).empty();
|
||||
lineData.OperandSuffix = match.str(6);
|
||||
lineData.HasOpeningParenthesis = !match.str(3).empty();
|
||||
|
||||
std::transform(lineData.OperandSuffix.begin(), lineData.OperandSuffix.end(), lineData.OperandSuffix.begin(), ::toupper);
|
||||
std::transform(lineData.OpCode.begin(), lineData.OpCode.end(), lineData.OpCode.begin(), ::toupper);
|
||||
|
||||
bool foundSpace = false;
|
||||
for(char c : match.str(5)) {
|
||||
if(c != ' ' && c != '\t') {
|
||||
if(foundSpace) {
|
||||
//can't have spaces in operands (except at the very end)
|
||||
return AssemblerSpecialCodes::InvalidSpaces;
|
||||
} else if(lineData.IsHex && !((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))) {
|
||||
//invalid hex
|
||||
return AssemblerSpecialCodes::InvalidHex;
|
||||
} else if(isBinary && c != '0' && c != '1') {
|
||||
return AssemblerSpecialCodes::InvalidBinaryValue;
|
||||
}
|
||||
|
||||
lineData.Operand.push_back(c);
|
||||
} else {
|
||||
foundSpace = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(isBinary) {
|
||||
//Convert the binary value to hex
|
||||
if(lineData.Operand.size() == 0) {
|
||||
return AssemblerSpecialCodes::MissingOperand;
|
||||
} else if(lineData.Operand.size() <= 8) {
|
||||
lineData.IsHex = true;
|
||||
int value = 0;
|
||||
for(size_t i = 0; i < lineData.Operand.size(); i++) {
|
||||
value <<= 1;
|
||||
value |= lineData.Operand[i] == '1' ? 1 : 0;
|
||||
}
|
||||
lineData.Operand = HexUtilities::ToHex(value, false);
|
||||
} else {
|
||||
return AssemblerSpecialCodes::OperandOutOfRange;
|
||||
}
|
||||
}
|
||||
|
||||
if(!lineData.HasComment && !match.str(9).empty()) {
|
||||
//something is trailing at the end of the line, and it's not a comment
|
||||
return AssemblerSpecialCodes::TrailingText;
|
||||
}
|
||||
|
||||
if(!lineData.IsHex) {
|
||||
bool allNumeric = true;
|
||||
for(size_t i = 0; i < lineData.Operand.size(); i++) {
|
||||
if(lineData.Operand[i] == '-' && i == 0 && lineData.Operand.size() > 1) {
|
||||
//First char is a minus sign, and more characters follow, continue
|
||||
continue;
|
||||
}
|
||||
|
||||
if((lineData.Operand[i] < '0' || lineData.Operand[i] > '9')) {
|
||||
allNumeric = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(allNumeric && !lineData.Operand.empty()) {
|
||||
//Operand is not empty, and it only contains decimal values
|
||||
lineData.IsDecimal = true;
|
||||
} else {
|
||||
lineData.IsDecimal = false;
|
||||
}
|
||||
}
|
||||
|
||||
return GetAddrModeAndOperandSize(lineData, labels, firstPass);
|
||||
}
|
||||
|
||||
AssemblerSpecialCodes Assembler::GetAddrModeAndOperandSize(LineData &lineData, std::unordered_map<string, uint16_t> &labels, bool firstPass)
|
||||
{
|
||||
int opSize = 0;
|
||||
bool invalid = false;
|
||||
string operand = lineData.Operand;
|
||||
|
||||
if(lineData.IsHex) {
|
||||
if(operand.size() == 0) {
|
||||
return AssemblerSpecialCodes::MissingOperand;
|
||||
} else if(operand.size() <= 2) {
|
||||
opSize = 1;
|
||||
} else if(operand.size() <= 4) {
|
||||
opSize = 2;
|
||||
} else {
|
||||
return AssemblerSpecialCodes::OperandOutOfRange;
|
||||
}
|
||||
} else if(lineData.IsDecimal) {
|
||||
int value = std::stoi(operand.c_str());
|
||||
if(value < -32768) {
|
||||
//< -32768 is invalid
|
||||
return AssemblerSpecialCodes::OperandOutOfRange;
|
||||
} else if(value < -128) {
|
||||
//-32768 to -129 is 2 bytes
|
||||
opSize = 2;
|
||||
} else if(value <= 255) {
|
||||
//-128 to 255 is 2 bytes
|
||||
opSize = 1;
|
||||
} else if(value <= 65535) {
|
||||
//256 to 65535 is 2 bytes
|
||||
opSize = 2;
|
||||
} else {
|
||||
//> 65535 is invalid
|
||||
return AssemblerSpecialCodes::OperandOutOfRange;
|
||||
}
|
||||
} else if(!operand.empty()) {
|
||||
//Check if the operand is a known label
|
||||
auto findResult = labels.find(operand);
|
||||
if(findResult != labels.end()) {
|
||||
lineData.Operand = HexUtilities::ToHex((uint16_t)findResult->second);
|
||||
lineData.IsHex = true;
|
||||
opSize = 2;
|
||||
} else if(operand.size() == 1 && (operand[0] == 'A' || operand[0] == 'a') && lineData.OperandSuffix.empty() && !lineData.IsHex && !lineData.IsImmediate && !lineData.HasOpeningParenthesis) {
|
||||
//Allow optional "A" after AddrMode == Accumulator instructions
|
||||
lineData.Mode = AddrMode::Acc;
|
||||
opSize = 0;
|
||||
} else {
|
||||
int32_t addr = _labelManager->GetLabelRelativeAddress(operand);
|
||||
if(addr >= 256) {
|
||||
lineData.Operand = HexUtilities::ToHex((uint16_t)addr);
|
||||
lineData.IsHex = true;
|
||||
opSize = 2;
|
||||
} else if(addr >= 0) {
|
||||
lineData.Operand = HexUtilities::ToHex((uint8_t)addr);
|
||||
lineData.IsHex = true;
|
||||
opSize = 1;
|
||||
} else {
|
||||
if(firstPass) {
|
||||
//First pass, we couldn't find a matching label, so it might be defined later on
|
||||
//Pretend it exists for now
|
||||
_needSecondPass = true;
|
||||
lineData.Operand = "FFFF";
|
||||
lineData.IsHex = true;
|
||||
opSize = 2;
|
||||
} else {
|
||||
return AssemblerSpecialCodes::UnknownLabel;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//No operand
|
||||
opSize = 0;
|
||||
}
|
||||
|
||||
if(lineData.Mode == AddrMode::None) {
|
||||
if(lineData.IsImmediate) {
|
||||
if(lineData.HasOpeningParenthesis || opSize == 0) {
|
||||
invalid = true;
|
||||
} else if(opSize > 1) {
|
||||
if(lineData.IsHex && HexUtilities::FromHex(operand) > 0xFF) {
|
||||
//Can't be a 2-byte operand
|
||||
invalid = true;
|
||||
} else if(lineData.IsDecimal) {
|
||||
int value = std::stoi(operand.c_str());
|
||||
if(value < -128 || value > 255) {
|
||||
//Can't be a 2-byte operand
|
||||
invalid = true;
|
||||
}
|
||||
}
|
||||
opSize = 1;
|
||||
}
|
||||
lineData.Mode = AddrMode::Imm; //or Rel
|
||||
} else if(lineData.HasOpeningParenthesis) {
|
||||
if(lineData.OperandSuffix.compare(")") == 0) {
|
||||
opSize = 2;
|
||||
lineData.Mode = AddrMode::Ind;
|
||||
} else if(lineData.OperandSuffix.compare(",X)") == 0) {
|
||||
opSize = 1;
|
||||
lineData.Mode = AddrMode::IndX;
|
||||
} else if(lineData.OperandSuffix.compare("),Y") == 0) {
|
||||
opSize = 1;
|
||||
lineData.Mode = AddrMode::IndY;
|
||||
} else {
|
||||
invalid = true;
|
||||
}
|
||||
} else {
|
||||
if(lineData.OperandSuffix.compare(",X") == 0) {
|
||||
if(opSize == 2) {
|
||||
lineData.Mode = AddrMode::AbsX;
|
||||
} else if(opSize == 1) {
|
||||
//Sometimes zero page addressing is not available, even if the operand is in the zero page
|
||||
lineData.Mode = IsOpModeAvailable(lineData.OpCode, AddrMode::ZeroX) ? AddrMode::ZeroX : AddrMode::AbsX;
|
||||
} else {
|
||||
invalid = true;
|
||||
}
|
||||
} else if(lineData.OperandSuffix.compare(",Y") == 0) {
|
||||
if(opSize == 2) {
|
||||
lineData.Mode = AddrMode::AbsY;
|
||||
} else if(opSize == 1) {
|
||||
//Sometimes zero page addressing is not available, even if the operand is in the zero page
|
||||
lineData.Mode = IsOpModeAvailable(lineData.OpCode, AddrMode::ZeroY) ? AddrMode::ZeroY : AddrMode::AbsY;
|
||||
} else {
|
||||
invalid = true;
|
||||
}
|
||||
} else if(lineData.OperandSuffix.empty()) {
|
||||
if(opSize == 0) {
|
||||
lineData.Mode = AddrMode::Imp; //or Acc
|
||||
} else if(opSize == 2) {
|
||||
lineData.Mode = AddrMode::Abs;
|
||||
} else if(opSize == 1) {
|
||||
//Sometimes zero page addressing is not available, even if the operand is in the zero page
|
||||
lineData.Mode = IsOpModeAvailable(lineData.OpCode, AddrMode::Zero) ? AddrMode::Zero : AddrMode::Abs;
|
||||
} else {
|
||||
invalid = true;
|
||||
}
|
||||
} else {
|
||||
invalid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(lineData.Mode == AddrMode::None) {
|
||||
invalid = true;
|
||||
}
|
||||
|
||||
lineData.OperandSize = opSize;
|
||||
|
||||
return invalid ? AssemblerSpecialCodes::ParsingError : AssemblerSpecialCodes::OK;
|
||||
}
|
||||
|
||||
bool Assembler::IsOpModeAvailable(string &opCode, AddrMode mode)
|
||||
{
|
||||
return _availableModesByOpName[opCode].find((int)mode) != _availableModesByOpName[opCode].end();
|
||||
}
|
||||
|
||||
void Assembler::AssembleInstruction(LineData &lineData, uint16_t &instructionAddress, vector<int16_t>& output, bool firstPass)
|
||||
{
|
||||
bool foundMatch = false;
|
||||
if(lineData.Mode == AddrMode::Imp && lineData.OpCode.compare("NOP") == 0) {
|
||||
//NOP has multiple name+addressing type collisions, the "official" NOP is 0xEA
|
||||
output.push_back(0xEA);
|
||||
instructionAddress++;
|
||||
foundMatch = true;
|
||||
} else {
|
||||
for(int i = 0; i < 256; i++) {
|
||||
AddrMode opMode = DisassemblyInfo::OPMode[i];
|
||||
if(lineData.OpCode.compare(opName[i]) == 0) {
|
||||
bool modeMatch = opMode == lineData.Mode;
|
||||
if(!modeMatch) {
|
||||
if((lineData.Mode == AddrMode::Imp && opMode == AddrMode::Acc) ||
|
||||
(lineData.Mode == AddrMode::IndY && opMode == AddrMode::IndYW) ||
|
||||
(lineData.Mode == AddrMode::AbsY && opMode == AddrMode::AbsYW) ||
|
||||
(lineData.Mode == AddrMode::AbsX && opMode == AddrMode::AbsXW)) {
|
||||
modeMatch = true;
|
||||
} else if((lineData.Mode == AddrMode::Abs && opMode == AddrMode::Rel) ||
|
||||
(lineData.Mode == AddrMode::Imm && opMode == AddrMode::Rel)) {
|
||||
if(lineData.OperandSize == 2) {
|
||||
if(lineData.Mode == AddrMode::Imm) {
|
||||
//Hardcoded jump values must be 1-byte
|
||||
if(firstPass) {
|
||||
//Pretend this is ok on first pass, we're just trying to find all labels
|
||||
lineData.OperandSize = 1;
|
||||
lineData.IsHex = true;
|
||||
lineData.Operand = "0";
|
||||
modeMatch = true;
|
||||
} else {
|
||||
output.push_back(AssemblerSpecialCodes::OutOfRangeJump);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
modeMatch = true;
|
||||
|
||||
//Convert "absolute" jump to a relative jump
|
||||
int value = lineData.IsHex ? HexUtilities::FromHex(lineData.Operand) : std::stoi(lineData.Operand);
|
||||
|
||||
int16_t addressGap = value - (instructionAddress + 2);
|
||||
if(addressGap > 127 || addressGap < -128) {
|
||||
//Gap too long, can't jump that far
|
||||
if(!firstPass) {
|
||||
//Pretend this is ok on first pass, we're just trying to find all labels
|
||||
output.push_back(AssemblerSpecialCodes::OutOfRangeJump);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//Update data to match relative jump
|
||||
lineData.OperandSize = 1;
|
||||
lineData.IsHex = true;
|
||||
lineData.Operand = HexUtilities::ToHex((uint8_t)addressGap);
|
||||
}
|
||||
} else {
|
||||
//Accept 1-byte relative jumps
|
||||
modeMatch = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(modeMatch) {
|
||||
output.push_back(i);
|
||||
instructionAddress += (lineData.OperandSize + 1);
|
||||
|
||||
if(lineData.OperandSize == 1) {
|
||||
int value = lineData.IsHex ? HexUtilities::FromHex(lineData.Operand) : std::stoi(lineData.Operand);
|
||||
output.push_back(value & 0xFF);
|
||||
} else if(lineData.OperandSize == 2) {
|
||||
int value = lineData.IsHex ? HexUtilities::FromHex(lineData.Operand) : std::stoi(lineData.Operand);
|
||||
output.push_back(value & 0xFF);
|
||||
output.push_back((value >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
foundMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!foundMatch) {
|
||||
output.push_back(AssemblerSpecialCodes::InvalidInstruction);
|
||||
} else {
|
||||
output.push_back(AssemblerSpecialCodes::EndOfLine);
|
||||
}
|
||||
}
|
||||
|
||||
Assembler::Assembler(shared_ptr<LabelManager> labelManager)
|
||||
{
|
||||
_labelManager = labelManager;
|
||||
}
|
||||
|
||||
uint32_t Assembler::AssembleCode(string code, uint16_t startAddress, int16_t* assembledCode)
|
||||
{
|
||||
for(uint8_t i = 0; i < 255; i++) {
|
||||
if(_availableModesByOpName.find(opName[i]) == _availableModesByOpName.end()) {
|
||||
_availableModesByOpName[opName[i]] = std::unordered_set<int>();
|
||||
}
|
||||
_availableModesByOpName[opName[i]].emplace((int)DisassemblyInfo::OPMode[i]);
|
||||
}
|
||||
|
||||
std::unordered_map<string, uint16_t> temporaryLabels;
|
||||
std::unordered_map<string, uint16_t> currentPassLabels;
|
||||
|
||||
size_t i = 0;
|
||||
vector<int16_t> output;
|
||||
output.reserve(1000);
|
||||
|
||||
uint16_t originalStartAddr = startAddress;
|
||||
|
||||
vector<string> codeLines;
|
||||
codeLines.reserve(100);
|
||||
while(i < code.size()) {
|
||||
size_t offset = code.find_first_of('\n', i);
|
||||
string line;
|
||||
if(offset != string::npos) {
|
||||
line = code.substr(i, offset - i);
|
||||
i = offset + 1;
|
||||
} else {
|
||||
line = code.substr(i);
|
||||
i = code.size();
|
||||
}
|
||||
codeLines.push_back(line);
|
||||
}
|
||||
|
||||
//Make 2 passes - first one to find all labels, second to assemble
|
||||
_needSecondPass = false;
|
||||
for(string &line : codeLines) {
|
||||
ProcessLine(line, startAddress, output, temporaryLabels, true, currentPassLabels);
|
||||
}
|
||||
|
||||
if(_needSecondPass) {
|
||||
currentPassLabels.clear();
|
||||
output.clear();
|
||||
startAddress = originalStartAddr;
|
||||
for(string &line : codeLines) {
|
||||
ProcessLine(line, startAddress, output, temporaryLabels, false, currentPassLabels);
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(assembledCode, output.data(), output.size() * sizeof(uint16_t));
|
||||
return (uint32_t)output.size();
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include <unordered_set>
|
||||
#include <regex>
|
||||
#include "CPU.h"
|
||||
|
||||
class LabelManager;
|
||||
|
||||
struct LineData
|
||||
{
|
||||
string OpCode;
|
||||
string Operand;
|
||||
string OperandSuffix;
|
||||
AddrMode Mode = AddrMode::None;
|
||||
int OperandSize = 0;
|
||||
bool IsHex = false;
|
||||
bool IsDecimal = false;
|
||||
bool IsImmediate = false;
|
||||
bool HasComment = false;
|
||||
bool HasOpeningParenthesis = false;
|
||||
};
|
||||
|
||||
enum AssemblerSpecialCodes
|
||||
{
|
||||
OK = 0,
|
||||
EndOfLine = -1,
|
||||
ParsingError = -2,
|
||||
OutOfRangeJump = -3,
|
||||
LabelRedefinition = -4,
|
||||
MissingOperand = -5,
|
||||
OperandOutOfRange = -6,
|
||||
InvalidHex = -7,
|
||||
InvalidSpaces = -8,
|
||||
TrailingText = -9,
|
||||
UnknownLabel = -10,
|
||||
InvalidInstruction = -11,
|
||||
InvalidBinaryValue = -12,
|
||||
};
|
||||
|
||||
class Assembler
|
||||
{
|
||||
private:
|
||||
std::unordered_map<string, std::unordered_set<int>> _availableModesByOpName;
|
||||
bool _needSecondPass;
|
||||
|
||||
shared_ptr<LabelManager> _labelManager;
|
||||
void ProcessLine(string code, uint16_t &instructionAddress, vector<int16_t>& output, std::unordered_map<string, uint16_t> &labels, bool firstPass, std::unordered_map<string, uint16_t> ¤tPassLabels);
|
||||
AssemblerSpecialCodes GetLineData(std::smatch match, LineData &lineData, std::unordered_map<string, uint16_t> &labels, bool firstPass);
|
||||
AssemblerSpecialCodes GetAddrModeAndOperandSize(LineData &lineData, std::unordered_map<string, uint16_t> &labels, bool firstPass);
|
||||
void AssembleInstruction(LineData &lineData, uint16_t &instructionAddress, vector<int16_t>& output, bool firstPass);
|
||||
|
||||
bool IsOpModeAvailable(string &opCode, AddrMode mode);
|
||||
|
||||
public:
|
||||
Assembler(shared_ptr<LabelManager> labelManager);
|
||||
|
||||
uint32_t AssembleCode(string code, uint16_t startAddress, int16_t* assembledCode);
|
||||
};
|
|
@ -1,47 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
#include "AutoSaveManager.h"
|
||||
#include "Console.h"
|
||||
#include "EmulationSettings.h"
|
||||
#include "SaveStateManager.h"
|
||||
|
||||
AutoSaveManager::AutoSaveManager(shared_ptr<Console> console)
|
||||
{
|
||||
_stopThread = false;
|
||||
_timer.Reset();
|
||||
_autoSaveThread = std::thread([=]() {
|
||||
bool showMessage = false;
|
||||
double targetTime = (double)console->GetSettings()->GetAutoSaveDelay(showMessage) * 60 * 1000;
|
||||
while(!_stopThread) {
|
||||
uint32_t autoSaveDelay = console->GetSettings()->GetAutoSaveDelay(showMessage) * 60 * 1000;
|
||||
if(autoSaveDelay > 0) {
|
||||
if(targetTime >= 0 && !console->IsExecutionStopped()) {
|
||||
targetTime -= _timer.GetElapsedMS();
|
||||
_timer.Reset();
|
||||
if(targetTime <= 0) {
|
||||
if(!console->IsDebuggerAttached()) {
|
||||
console->GetSaveStateManager()->SaveState(_autoSaveSlot, showMessage);
|
||||
}
|
||||
targetTime = (double)console->GetSettings()->GetAutoSaveDelay(showMessage) * 60 * 1000;
|
||||
_timer.Reset();
|
||||
}
|
||||
} else {
|
||||
_timer.Reset();
|
||||
}
|
||||
} else {
|
||||
_timer.Reset();
|
||||
targetTime = autoSaveDelay;
|
||||
}
|
||||
|
||||
if(!_stopThread) {
|
||||
_signal.Wait(1000);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
AutoSaveManager::~AutoSaveManager()
|
||||
{
|
||||
_stopThread = true;
|
||||
_signal.Signal();
|
||||
_autoSaveThread.join();
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include <thread>
|
||||
#include "../Utilities/Timer.h"
|
||||
#include "../Utilities/AutoResetEvent.h"
|
||||
|
||||
class Console;
|
||||
|
||||
class AutoSaveManager
|
||||
{
|
||||
private:
|
||||
const uint32_t _autoSaveSlot = 11;
|
||||
std::thread _autoSaveThread;
|
||||
|
||||
atomic<bool> _stopThread;
|
||||
AutoResetEvent _signal;
|
||||
Timer _timer;
|
||||
|
||||
public:
|
||||
AutoSaveManager(shared_ptr<Console> console);
|
||||
~AutoSaveManager();
|
||||
};
|
|
@ -1,164 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
#include "../Utilities/FolderUtilities.h"
|
||||
#include "AutomaticRomTest.h"
|
||||
#include "EmulationSettings.h"
|
||||
#include "Console.h"
|
||||
#include "PPU.h"
|
||||
#include "VideoDecoder.h"
|
||||
#include "StandardController.h"
|
||||
#include "NotificationManager.h"
|
||||
|
||||
AutomaticRomTest::AutomaticRomTest()
|
||||
{
|
||||
_errorCode = 0;
|
||||
}
|
||||
|
||||
AutomaticRomTest::~AutomaticRomTest()
|
||||
{
|
||||
}
|
||||
|
||||
void AutomaticRomTest::ProcessNotification(ConsoleNotificationType type, void* parameter)
|
||||
{
|
||||
if(type == ConsoleNotificationType::PpuFrameDone) {
|
||||
uint16_t *frameBuffer = (uint16_t*)parameter;
|
||||
if(_console->GetFrameCount() == 5) {
|
||||
memcpy(_prevFrameBuffer, frameBuffer, sizeof(_prevFrameBuffer));
|
||||
} else if(_console->GetFrameCount() == 300) {
|
||||
if(memcmp(_prevFrameBuffer, frameBuffer, sizeof(_prevFrameBuffer)) == 0) {
|
||||
//No change
|
||||
_errorCode |= 0x20;
|
||||
}
|
||||
memcpy(_prevFrameBuffer, frameBuffer, sizeof(_prevFrameBuffer));
|
||||
_console->GetVideoDecoder()->TakeScreenshot();
|
||||
} else if(_console->GetFrameCount() == 900) {
|
||||
if(memcmp(_prevFrameBuffer, frameBuffer, sizeof(_prevFrameBuffer)) == 0) {
|
||||
//No change
|
||||
_errorCode |= 0x01;
|
||||
}
|
||||
|
||||
bool allZeros = true;
|
||||
for(int i = 0; i < 256 * 240; i++) {
|
||||
if(frameBuffer[i] != 0) {
|
||||
allZeros = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(allZeros) {
|
||||
_errorCode |= 0x04;
|
||||
}
|
||||
|
||||
memcpy(_prevFrameBuffer, frameBuffer, sizeof(_prevFrameBuffer));
|
||||
_console->GetVideoDecoder()->TakeScreenshot();
|
||||
} else if(_console->GetFrameCount() == 1800) {
|
||||
bool continueTest = false;
|
||||
if(memcmp(_prevFrameBuffer, frameBuffer, sizeof(_prevFrameBuffer)) == 0) {
|
||||
//No change, change input pattern and keep trying
|
||||
continueTest = true;
|
||||
}
|
||||
|
||||
bool allZeros = true;
|
||||
for(int i = 0; i < 256 * 240; i++) {
|
||||
if(frameBuffer[i] != 0) {
|
||||
allZeros = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(allZeros) {
|
||||
_errorCode |= 0x08;
|
||||
}
|
||||
|
||||
_console->GetVideoDecoder()->TakeScreenshot();
|
||||
|
||||
if(!continueTest) {
|
||||
//Stop test
|
||||
_signal.Signal();
|
||||
}
|
||||
} else if(_console->GetFrameCount() == 3600) {
|
||||
if(memcmp(_prevFrameBuffer, frameBuffer, sizeof(_prevFrameBuffer)) == 0) {
|
||||
//No change
|
||||
_errorCode |= 0x02;
|
||||
}
|
||||
|
||||
bool allZeros = true;
|
||||
for(int i = 0; i < 256 * 240; i++) {
|
||||
if(frameBuffer[i] != 0) {
|
||||
allZeros = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(allZeros) {
|
||||
_errorCode |= 0x40;
|
||||
}
|
||||
|
||||
_console->GetVideoDecoder()->TakeScreenshot();
|
||||
|
||||
//Stop test
|
||||
_signal.Signal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32_t AutomaticRomTest::Run(string filename)
|
||||
{
|
||||
_console.reset(new Console());
|
||||
EmulationSettings* settings = _console->GetSettings();
|
||||
settings->SetMasterVolume(0);
|
||||
_console->GetNotificationManager()->RegisterNotificationListener(shared_from_this());
|
||||
if(_console->Initialize(filename)) {
|
||||
_console->GetControlManager()->RegisterInputProvider(this);
|
||||
|
||||
settings->SetFlags(EmulationFlags::ForceMaxSpeed);
|
||||
settings->ClearFlags(EmulationFlags::Paused);
|
||||
_signal.Wait();
|
||||
|
||||
settings->SetFlags(EmulationFlags::Paused);
|
||||
|
||||
if(_console->GetFrameCount() < 1800) {
|
||||
//Finished early
|
||||
_errorCode |= 0x10;
|
||||
}
|
||||
|
||||
settings->ClearFlags(EmulationFlags::ForceMaxSpeed);
|
||||
settings->SetMasterVolume(1.0);
|
||||
|
||||
_console->GetControlManager()->UnregisterInputProvider(this);
|
||||
_console->Stop();
|
||||
|
||||
return _errorCode;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool AutomaticRomTest::SetInput(BaseControlDevice* device)
|
||||
{
|
||||
if(device->GetPort() == 0) {
|
||||
uint32_t frameNumber = _console->GetFrameCount();
|
||||
ControlDeviceState state;
|
||||
|
||||
if(frameNumber <= 1800) {
|
||||
if(frameNumber % 30 < 10) {
|
||||
//Press 1 button for 10 frames every second
|
||||
if((frameNumber / 30) % 8 != 1) {
|
||||
state.State.push_back(1 << ((frameNumber / 60) % 8));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(frameNumber % 30 < 10) {
|
||||
if((frameNumber / 30) % 2) {
|
||||
state.State.push_back(0x01);
|
||||
} else {
|
||||
state.State.push_back(0x08);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(state.State.empty()) {
|
||||
state.State.push_back(0);
|
||||
}
|
||||
device->SetRawState(state);
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "INotificationListener.h"
|
||||
#include "IInputProvider.h"
|
||||
#include "../Utilities/AutoResetEvent.h"
|
||||
|
||||
class Console;
|
||||
|
||||
class AutomaticRomTest : public INotificationListener, public IInputProvider, public std::enable_shared_from_this<AutomaticRomTest>
|
||||
{
|
||||
private:
|
||||
shared_ptr<Console> _console;
|
||||
|
||||
AutoResetEvent _signal;
|
||||
uint16_t _prevFrameBuffer[256 * 240];
|
||||
uint32_t _errorCode;
|
||||
|
||||
public:
|
||||
AutomaticRomTest();
|
||||
virtual ~AutomaticRomTest();
|
||||
|
||||
void ProcessNotification(ConsoleNotificationType type, void* parameter) override;
|
||||
int32_t Run(string filename);
|
||||
|
||||
// Inherited via IInputProvider
|
||||
virtual bool SetInput(BaseControlDevice * device) override;
|
||||
};
|
|
@ -1,65 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Ax5705 : public BaseMapper
|
||||
{
|
||||
private:
|
||||
uint8_t _chrReg[8];
|
||||
|
||||
protected:
|
||||
uint16_t GetPRGPageSize() override { return 0x2000; }
|
||||
uint16_t GetCHRPageSize() override { return 0x400; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
memset(_chrReg, 0, sizeof(_chrReg));
|
||||
|
||||
SelectPRGPage(2, -2);
|
||||
SelectPRGPage(3, -1);
|
||||
|
||||
for(int i = 0; i < 8; i++) {
|
||||
SelectCHRPage(i, _chrReg[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
ArrayInfo<uint8_t> chrReg{ _chrReg, 8 };
|
||||
Stream(chrReg);
|
||||
}
|
||||
|
||||
void UpdateChrReg(int index, uint8_t value, bool low)
|
||||
{
|
||||
if(low) {
|
||||
_chrReg[index] = (_chrReg[index] & 0xF0) | (value & 0x0F);
|
||||
} else {
|
||||
_chrReg[index] = (_chrReg[index] & 0x0F) | ((((value & 0x04) >> 1) | ((value & 0x02) << 1) | (value & 0x09)) << 4);
|
||||
}
|
||||
SelectCHRPage(index, _chrReg[index]);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if(addr >= 0xA008) {
|
||||
bool low = (addr & 0x01) == 0x00;
|
||||
switch(addr & 0xF00E) {
|
||||
case 0xA008: UpdateChrReg(0, value, low); break;
|
||||
case 0xA00A: UpdateChrReg(1, value, low); break;
|
||||
case 0xC000: UpdateChrReg(2, value, low); break;
|
||||
case 0xC002: UpdateChrReg(3, value, low); break;
|
||||
case 0xC008: UpdateChrReg(4, value, low); break;
|
||||
case 0xC00A: UpdateChrReg(5, value, low); break;
|
||||
case 0xE000: UpdateChrReg(6, value, low); break;
|
||||
case 0xE002: UpdateChrReg(7, value, low); break;
|
||||
}
|
||||
} else {
|
||||
switch(addr & 0xF00F) {
|
||||
case 0x8000: SelectPRGPage(0, ((value & 0x02) << 2) | ((value & 0x08) >> 2) | (value & 0x05)); break; // EPROM dump have mixed PRG and CHR banks, data lines to mapper seems to be mixed
|
||||
case 0x8008: SetMirroringType(value & 0x01 ? MirroringType::Horizontal : MirroringType::Vertical); break;
|
||||
case 0xA000: SelectPRGPage(1, ((value & 0x02) << 2) | ((value & 0x08) >> 2) | (value & 0x05)); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,51 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class BF9096 : public BaseMapper
|
||||
{
|
||||
private:
|
||||
uint8_t _prgBlock;
|
||||
uint8_t _prgPage;
|
||||
|
||||
protected:
|
||||
virtual uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
virtual uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
_prgPage = 0;
|
||||
_prgBlock = 0;
|
||||
|
||||
SelectPRGPage(0, 0);
|
||||
SelectPRGPage(1, 3);
|
||||
|
||||
SelectCHRPage(0, 0);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if(addr >= 0xC000) {
|
||||
_prgPage = value & 0x03;
|
||||
} else if(addr < 0xC000) {
|
||||
if(_romInfo.SubMapperID == 1) {
|
||||
//"232: 1 Aladdin Deck Enhancer"
|
||||
//"Aladdin Deck Enhancer variation.Swap the bits of the outer bank number."
|
||||
//But this seems to match the Pegasus 4-in-1 behavior? Wiki wrong?
|
||||
_prgBlock = ((value >> 4) & 0x01) | ((value >> 2) & 0x02);
|
||||
} else {
|
||||
_prgBlock = (value >> 3) & 0x03;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SelectPRGPage(0, (_prgBlock << 2) | _prgPage);
|
||||
SelectPRGPage(1, (_prgBlock << 2) | 3);
|
||||
}
|
||||
|
||||
virtual void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
Stream(_prgBlock, _prgPage);
|
||||
}
|
||||
};
|
|
@ -1,46 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class BF909x : public BaseMapper
|
||||
{
|
||||
private:
|
||||
bool _bf9097Mode = false; //Auto-detect for firehawk
|
||||
|
||||
protected:
|
||||
virtual uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
virtual uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
if(_romInfo.SubMapperID == 1) {
|
||||
_bf9097Mode = true;
|
||||
}
|
||||
|
||||
//First and last PRG page
|
||||
SelectPRGPage(0, 0);
|
||||
SelectPRGPage(1, -1);
|
||||
|
||||
SelectCHRPage(0, 0);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if(addr == 0x9000) {
|
||||
//Firehawk uses $9000 to change mirroring
|
||||
_bf9097Mode = true;
|
||||
}
|
||||
|
||||
if(addr >= 0xC000 || !_bf9097Mode) {
|
||||
SelectPRGPage(0, value);
|
||||
} else if(addr < 0xC000) {
|
||||
SetMirroringType((value & 0x10) ? MirroringType::ScreenAOnly : MirroringType::ScreenBOnly);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
Stream(_bf9097Mode);
|
||||
}
|
||||
};
|
|
@ -1,53 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Bandai74161_7432 : public BaseMapper
|
||||
{
|
||||
private:
|
||||
bool _enableMirroringControl;
|
||||
|
||||
protected:
|
||||
virtual uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
virtual uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
SelectPRGPage(0, 0);
|
||||
SelectPRGPage(1, -1);
|
||||
SelectCHRPage(0, 0);
|
||||
|
||||
//Hack to make Kamen Rider Club - Gekitotsu Shocker Land work correctly (bad header)
|
||||
SetMirroringType(MirroringType::Vertical);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
bool mirroringBit = (value & 0x80) == 0x80;
|
||||
if(mirroringBit) {
|
||||
//If any game tries to set the bit to true, assume it will use mirroring switches
|
||||
//This is a hack to make as many games as possible work without CRC checks
|
||||
_enableMirroringControl = true;
|
||||
}
|
||||
|
||||
if(_enableMirroringControl) {
|
||||
SetMirroringType(mirroringBit ? MirroringType::ScreenBOnly : MirroringType::ScreenAOnly);
|
||||
}
|
||||
|
||||
//Biggest PRG ROM I could find for mapper 70/152 is 128kb, so the 4th bit will never be used on those
|
||||
SelectPRGPage(0, (value >> 4) & 0x07);
|
||||
SelectCHRPage(0, value & 0x0F);
|
||||
}
|
||||
|
||||
virtual void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
Stream(_enableMirroringControl);
|
||||
}
|
||||
|
||||
public:
|
||||
Bandai74161_7432(bool enableMirroringControl) : _enableMirroringControl(enableMirroringControl)
|
||||
{
|
||||
//According to NesDev Wiki, Mapper 70 is meant to have mirroring forced (by the board) and Mapper 152 allows the code to specify the mirroring type
|
||||
}
|
||||
};
|
235
Core/BandaiFcg.h
235
Core/BandaiFcg.h
|
@ -1,235 +0,0 @@
|
|||
#pragma once
|
||||
#include "BaseMapper.h"
|
||||
#include "CPU.h"
|
||||
#include "Console.h"
|
||||
#include "MemoryManager.h"
|
||||
#include "DatachBarcodeReader.h"
|
||||
#include "BaseEeprom24C0X.h"
|
||||
#include "Eeprom24C01.h"
|
||||
#include "Eeprom24C02.h"
|
||||
|
||||
class BandaiFcg : public BaseMapper
|
||||
{
|
||||
private:
|
||||
bool _irqEnabled;
|
||||
uint16_t _irqCounter;
|
||||
uint16_t _irqReload;
|
||||
uint8_t _prgPage;
|
||||
uint8_t _prgBankSelect;
|
||||
uint8_t _chrRegs[8];
|
||||
shared_ptr<DatachBarcodeReader> _barcodeReader;
|
||||
|
||||
shared_ptr<BaseEeprom24C0X> _standardEeprom;
|
||||
shared_ptr<BaseEeprom24C0X> _extraEeprom;
|
||||
|
||||
protected:
|
||||
uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
uint16_t GetCHRPageSize() override { return 0x400; }
|
||||
uint16_t RegisterStartAddress() override { return 0x6000; }
|
||||
uint16_t RegisterEndAddress() override { return 0xFFFF; }
|
||||
bool AllowRegisterRead() override { return true; }
|
||||
ConsoleFeatures GetAvailableFeatures() override { return _romInfo.MapperID == 157 ? (ConsoleFeatures)((int)ConsoleFeatures::BarcodeReader | (int)ConsoleFeatures::DatachBarcodeReader) : ConsoleFeatures::None; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
memset(_chrRegs, 0, sizeof(_chrRegs));
|
||||
_irqEnabled = false;
|
||||
_irqCounter = 0;
|
||||
_irqReload = 0;
|
||||
_prgPage = 0;
|
||||
_prgBankSelect = 0;
|
||||
|
||||
//Only allow reads from 0x6000 to 0x7FFF
|
||||
RemoveRegisterRange(0x8000, 0xFFFF, MemoryOperation::Read);
|
||||
|
||||
if(_romInfo.MapperID == 157) {
|
||||
//"Mapper 157 is used for Datach Joint ROM System boards"
|
||||
_barcodeReader.reset(new DatachBarcodeReader(_console));
|
||||
_mapperControlDevice = _barcodeReader;
|
||||
|
||||
//Datach Joint ROM System
|
||||
//"It contains an internal 256-byte serial EEPROM (24C02) that is shared among all Datach games."
|
||||
//"One game, Battle Rush: Build up Robot Tournament, has an additional external 128-byte serial EEPROM (24C01) on the game cartridge."
|
||||
//"The NES 2.0 header's PRG-NVRAM field will only denote whether the game cartridge has an additional 128-byte serial EEPROM"
|
||||
if(!IsNes20() || _romInfo.NesHeader.GetSaveRamSize() == 128) {
|
||||
_extraEeprom.reset(new Eeprom24C01(_console));
|
||||
}
|
||||
|
||||
//All mapper 157 games have an internal 256-byte EEPROM
|
||||
_standardEeprom.reset(new Eeprom24C02(_console));
|
||||
} else if(_romInfo.MapperID == 159) {
|
||||
//LZ93D50 with 128 byte serial EEPROM (24C01)
|
||||
_standardEeprom.reset(new Eeprom24C01(_console));
|
||||
} else if(_romInfo.MapperID == 16) {
|
||||
//"INES Mapper 016 submapper 4: FCG-1/2 ASIC, no serial EEPROM, banked CHR-ROM"
|
||||
//"INES Mapper 016 submapper 5: LZ93D50 ASIC and no or 256-byte serial EEPROM, banked CHR-ROM"
|
||||
|
||||
//Add a 256 byte serial EEPROM (24C02)
|
||||
if(!IsNes20() || (_romInfo.SubMapperID == 5 && _romInfo.NesHeader.GetSaveRamSize() == 256)) {
|
||||
//Connect a 256-byte EEPROM for iNES roms, and when submapper 5 + 256 bytes of save ram in header
|
||||
_standardEeprom.reset(new Eeprom24C02(_console));
|
||||
}
|
||||
}
|
||||
|
||||
if(_romInfo.MapperID != 16) {
|
||||
//"For iNES Mapper 153 (with SRAM), the writeable ports must only be mirrored across $8000-$FFFF."
|
||||
//"Mappers 157 and 159 do not need to support the FCG-1 and -2 and so should only mirror the ports across $8000-$FFFF."
|
||||
if(_romInfo.MapperID == 153) {
|
||||
//Mapper 153 has regular save ram from $6000-$7FFF, need to remove the register for both read & writes
|
||||
RemoveRegisterRange(0x6000, 0x7FFF, MemoryOperation::Any);
|
||||
} else {
|
||||
RemoveRegisterRange(0x6000, 0x7FFF, MemoryOperation::Write);
|
||||
}
|
||||
} else if(_romInfo.MapperID == 16 && _romInfo.SubMapperID == 4) {
|
||||
RemoveRegisterRange(0x8000, 0xFFFF, MemoryOperation::Write);
|
||||
} else if(_romInfo.MapperID == 16 && _romInfo.SubMapperID == 5) {
|
||||
RemoveRegisterRange(0x6000, 0x7FFF, MemoryOperation::Write);
|
||||
}
|
||||
|
||||
//Last bank
|
||||
SelectPRGPage(1, 0x0F);
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
|
||||
ArrayInfo<uint8_t> chrRegs{ _chrRegs, 8 };
|
||||
Stream(_irqEnabled, _irqCounter, _irqReload, _prgPage, _prgBankSelect, chrRegs);
|
||||
|
||||
if(_standardEeprom) {
|
||||
SnapshotInfo eeprom { _standardEeprom.get() };
|
||||
Stream(eeprom);
|
||||
}
|
||||
|
||||
if(_extraEeprom) {
|
||||
SnapshotInfo eeprom { _extraEeprom.get() };
|
||||
Stream(eeprom);
|
||||
}
|
||||
}
|
||||
|
||||
void SaveBattery() override
|
||||
{
|
||||
if(_standardEeprom) {
|
||||
_standardEeprom->SaveBattery();
|
||||
}
|
||||
if(_extraEeprom) {
|
||||
_extraEeprom->SaveBattery();
|
||||
} else {
|
||||
//Do not call BaseMapper::SaveBattery when the extra EEPROM exists (prevent unused .sav file from being created)
|
||||
BaseMapper::SaveBattery();
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessCpuClock() override
|
||||
{
|
||||
if(_irqEnabled) {
|
||||
//Checking counter before decrementing seems to be the only way to get both
|
||||
//Famicom Jump II - Saikyou no 7 Nin (J) and Magical Taruruuto-kun 2 - Mahou Daibouken (J)
|
||||
//to work without glitches with the same code.
|
||||
if(_irqCounter == 0) {
|
||||
_console->GetCpu()->SetIrqSource(IRQSource::External);
|
||||
}
|
||||
_irqCounter--;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ReadRegister(uint16_t addr) override
|
||||
{
|
||||
uint8_t output = 0;
|
||||
|
||||
if(_barcodeReader) {
|
||||
output |= _barcodeReader->GetOutput();
|
||||
}
|
||||
|
||||
if(_extraEeprom && _standardEeprom) {
|
||||
output |= (_standardEeprom->Read() && _extraEeprom->Read()) << 4;
|
||||
} else if(_standardEeprom) {
|
||||
output |= (_standardEeprom->Read() << 4);
|
||||
}
|
||||
|
||||
return output | _console->GetMemoryManager()->GetOpenBus(0xE7);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
switch(addr & 0x000F) {
|
||||
case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07:
|
||||
_chrRegs[addr & 0x07] = value;
|
||||
if(_romInfo.MapperID == 153 || GetPRGPageCount() >= 0x20) {
|
||||
_prgBankSelect = 0;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
_prgBankSelect |= (_chrRegs[i] & 0x01) << 4;
|
||||
}
|
||||
SelectPRGPage(0, _prgPage | _prgBankSelect);
|
||||
SelectPRGPage(1, 0x0F | _prgBankSelect);
|
||||
} else if(!HasChrRam() && _romInfo.MapperID != 157) {
|
||||
SelectCHRPage(addr & 0x07, value);
|
||||
}
|
||||
|
||||
if(_extraEeprom && _romInfo.MapperID == 157 && (addr & 0x0F) <= 3) {
|
||||
_extraEeprom->WriteScl((value >> 3) & 0x01);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x08:
|
||||
_prgPage = value & 0x0F;
|
||||
SelectPRGPage(0, _prgPage | _prgBankSelect);
|
||||
break;
|
||||
|
||||
case 0x09:
|
||||
switch(value & 0x03) {
|
||||
case 0: SetMirroringType(MirroringType::Vertical); break;
|
||||
case 1: SetMirroringType(MirroringType::Horizontal); break;
|
||||
case 2: SetMirroringType(MirroringType::ScreenAOnly); break;
|
||||
case 3: SetMirroringType(MirroringType::ScreenBOnly); break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x0A:
|
||||
_irqEnabled = (value & 0x01) == 0x01;
|
||||
|
||||
//Wiki claims there is no reload value, however this seems to be the only way to make Famicom Jump II - Saikyou no 7 Nin work properly
|
||||
if(_romInfo.MapperID != 16 || !IsNes20() || _romInfo.SubMapperID == 5) {
|
||||
//"On the LZ93D50 (Submapper 5), writing to this register also copies the latch to the actual counter."
|
||||
_irqCounter = _irqReload;
|
||||
}
|
||||
|
||||
_console->GetCpu()->ClearIrqSource(IRQSource::External);
|
||||
break;
|
||||
|
||||
case 0x0B:
|
||||
if(_romInfo.MapperID != 16 || !IsNes20() || _romInfo.SubMapperID != 4) {
|
||||
//"On the LZ93D50 (Submapper 5), these registers instead modify a latch that will only be copied to the actual counter when register $800A is written to."
|
||||
_irqReload = (_irqReload & 0xFF00) | value;
|
||||
} else {
|
||||
//"On the FCG-1/2 (Submapper 4), writing to these two registers directly modifies the counter itself; all such games therefore disable counting before changing the counter value."
|
||||
_irqCounter = (_irqCounter & 0xFF00) | value;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x0C:
|
||||
if(_romInfo.MapperID != 16 || !IsNes20() || _romInfo.SubMapperID != 4) {
|
||||
_irqReload = (_irqReload & 0xFF) | (value << 8);
|
||||
} else {
|
||||
_irqCounter = (_irqCounter & 0xFF00) | value;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x0D:
|
||||
if(_romInfo.MapperID == 153) {
|
||||
SetCpuMemoryMapping(0x6000, 0x7FFF, 0, HasBattery() ? PrgMemoryType::SaveRam : PrgMemoryType::WorkRam, value & 0x20 ? MemoryAccessType::ReadWrite : MemoryAccessType::NoAccess);
|
||||
} else {
|
||||
uint8_t scl = (value & 0x20) >> 5;
|
||||
uint8_t sda = (value & 0x40) >> 6;
|
||||
if(_standardEeprom) {
|
||||
_standardEeprom->Write(scl, sda);
|
||||
}
|
||||
if(_extraEeprom) {
|
||||
_extraEeprom->WriteSda(sda);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,78 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "StandardController.h"
|
||||
#include "Zapper.h"
|
||||
#include "IKeyManager.h"
|
||||
#include "KeyManager.h"
|
||||
|
||||
class BandaiHyperShot : public StandardController
|
||||
{
|
||||
private:
|
||||
uint32_t _stateBuffer = 0;
|
||||
shared_ptr<Console> _console;
|
||||
|
||||
protected:
|
||||
enum ZapperButtons { Fire = 9 };
|
||||
|
||||
bool HasCoordinates() override { return true; }
|
||||
|
||||
string GetKeyNames() override
|
||||
{
|
||||
return StandardController::GetKeyNames() + "F";
|
||||
}
|
||||
|
||||
void InternalSetStateFromInput() override
|
||||
{
|
||||
StandardController::InternalSetStateFromInput();
|
||||
|
||||
if(_console->GetSettings()->InputEnabled()) {
|
||||
SetPressedState(ZapperButtons::Fire, KeyManager::IsMouseButtonPressed(MouseButton::LeftButton));
|
||||
|
||||
MousePosition pos = KeyManager::GetMousePosition();
|
||||
if(KeyManager::IsMouseButtonPressed(MouseButton::RightButton)) {
|
||||
pos.X = -1;
|
||||
pos.Y = -1;
|
||||
}
|
||||
SetCoordinates(pos);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsLightFound()
|
||||
{
|
||||
return Zapper::StaticIsLightFound(GetCoordinates(), _console);
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseControlDevice::StreamState(saving);
|
||||
Stream(_stateBuffer);
|
||||
}
|
||||
|
||||
public:
|
||||
BandaiHyperShot(shared_ptr<Console> console, KeyMappingSet keyMappings) : StandardController(console, BaseControlDevice::ExpDevicePort, keyMappings)
|
||||
{
|
||||
_console = console;
|
||||
}
|
||||
|
||||
void RefreshStateBuffer() override
|
||||
{
|
||||
_stateBuffer = (uint32_t)ToByte();
|
||||
}
|
||||
|
||||
uint8_t ReadRAM(uint16_t addr) override
|
||||
{
|
||||
if(addr == 0x4016) {
|
||||
StrobeProcessRead();
|
||||
uint8_t output = (_stateBuffer & 0x01) << 1;
|
||||
_stateBuffer >>= 1;
|
||||
return output;
|
||||
} else {
|
||||
return (IsLightFound() ? 0 : 0x08) | (IsPressed(BandaiHyperShot::ZapperButtons::Fire) ? 0x10 : 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteRAM(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
StrobeProcessWrite(value);
|
||||
}
|
||||
};
|
|
@ -1,51 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
#include "ControlManager.h"
|
||||
#include "StandardController.h"
|
||||
#include "BandaiMicrophone.h"
|
||||
|
||||
class BandaiKaraoke : public BaseMapper
|
||||
{
|
||||
protected:
|
||||
uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
bool AllowRegisterRead() override { return true; }
|
||||
bool HasBusConflicts() override { return true; }
|
||||
ConsoleFeatures GetAvailableFeatures() override { return ConsoleFeatures::BandaiMicrophone; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
AddRegisterRange(0x6000, 0x7FFF, MemoryOperation::Read);
|
||||
RemoveRegisterRange(0x8000, 0xFFFF, MemoryOperation::Read);
|
||||
|
||||
SelectPRGPage(0, 0);
|
||||
SelectPRGPage(1, 0x07);
|
||||
SelectCHRPage(0, 0);
|
||||
|
||||
_mapperControlDevice.reset(new BandaiMicrophone(_console, _console->GetSettings()->GetControllerKeys(0)));
|
||||
}
|
||||
|
||||
uint8_t ReadRegister(uint16_t addr) override
|
||||
{
|
||||
return _mapperControlDevice->ReadRAM(addr) | _console->GetMemoryManager()->GetOpenBus(0xF8);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if(value & 0x10) {
|
||||
//Select internal rom
|
||||
SelectPRGPage(0, value & 0x07);
|
||||
} else {
|
||||
//Select expansion rom
|
||||
if(_prgSize >= 0x40000) {
|
||||
SelectPRGPage(0, (value & 0x07) | 0x08);
|
||||
} else {
|
||||
//Open bus for roms that don't contain the expansion rom
|
||||
RemoveCpuMemoryMapping(0x8000, 0xBFFF);
|
||||
}
|
||||
}
|
||||
|
||||
SetMirroringType(value & 0x20 ? MirroringType::Horizontal : MirroringType::Vertical);
|
||||
}
|
||||
};
|
|
@ -1,51 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseControlDevice.h"
|
||||
#include "Console.h"
|
||||
|
||||
class BandaiMicrophone : public BaseControlDevice
|
||||
{
|
||||
protected:
|
||||
enum Buttons { A, B, Microphone };
|
||||
|
||||
string GetKeyNames() override
|
||||
{
|
||||
return "ABM";
|
||||
}
|
||||
|
||||
void InternalSetStateFromInput() override
|
||||
{
|
||||
//Make sure the key bindings are properly updated (not ideal, but good enough)
|
||||
_keyMappings = _console->GetSettings()->GetControllerKeys(0).GetKeyMappingArray();
|
||||
|
||||
for(KeyMapping keyMapping : _keyMappings) {
|
||||
SetPressedState(Buttons::A, keyMapping.BandaiMicrophoneButtons[0]);
|
||||
SetPressedState(Buttons::B, keyMapping.BandaiMicrophoneButtons[1]);
|
||||
if((_console->GetFrameCount() % 2) == 0) {
|
||||
//Alternate between 1 and 0s (not sure if the game does anything with this data?)
|
||||
SetPressedState(Buttons::Microphone, keyMapping.BandaiMicrophoneButtons[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
BandaiMicrophone(shared_ptr<Console> console, KeyMappingSet keyMappings) : BaseControlDevice(console, BaseControlDevice::MapperInputPort, keyMappings)
|
||||
{
|
||||
}
|
||||
|
||||
uint8_t ReadRAM(uint16_t addr) override
|
||||
{
|
||||
if(addr >= 0x6000 && addr <= 0x7FFF) {
|
||||
return
|
||||
(IsPressed(Buttons::A) ? 0 : 0x01) |
|
||||
(IsPressed(Buttons::B) ? 0 : 0x02) |
|
||||
(IsPressed(Buttons::Microphone) ? 0x04 : 0);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void WriteRAM(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
}
|
||||
};
|
|
@ -1,105 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseControlDevice.h"
|
||||
#include "IBarcodeReader.h"
|
||||
#include "MemoryManager.h"
|
||||
|
||||
class BarcodeBattlerReader : public BaseControlDevice, public IBarcodeReader
|
||||
{
|
||||
private:
|
||||
static constexpr int StreamSize = 200;
|
||||
uint64_t _newBarcode = 0;
|
||||
uint32_t _newBarcodeDigitCount = 0;
|
||||
|
||||
uint8_t _barcodeStream[BarcodeBattlerReader::StreamSize];
|
||||
uint64_t _insertCycle = 0;
|
||||
|
||||
protected:
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseControlDevice::StreamState(saving);
|
||||
|
||||
ArrayInfo<uint8_t> bitStream{ _barcodeStream, BarcodeBattlerReader::StreamSize };
|
||||
Stream(_newBarcode, _newBarcodeDigitCount, _insertCycle, bitStream);
|
||||
}
|
||||
|
||||
bool IsRawString() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void InitBarcodeStream()
|
||||
{
|
||||
vector<uint8_t> state = GetRawState().State;
|
||||
string barcodeText(state.begin(), state.end());
|
||||
|
||||
//Signature at the end, needed for code to be recognized
|
||||
barcodeText += "EPOCH\xD\xA";
|
||||
//Pad to 20 characters with spaces
|
||||
barcodeText.insert(0, 20 - barcodeText.size(), ' ');
|
||||
|
||||
int pos = 0;
|
||||
vector<uint8_t> bits;
|
||||
for(int i = 0; i < 20; i++) {
|
||||
_barcodeStream[pos++] = 1;
|
||||
for(int j = 0; j < 8; j++) {
|
||||
_barcodeStream[pos++] = ~((barcodeText[i] >> j) & 0x01);
|
||||
}
|
||||
_barcodeStream[pos++] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
BarcodeBattlerReader(shared_ptr<Console> console) : BaseControlDevice(console, BaseControlDevice::ExpDevicePort)
|
||||
{
|
||||
}
|
||||
|
||||
void InternalSetStateFromInput() override
|
||||
{
|
||||
ClearState();
|
||||
|
||||
if(_newBarcodeDigitCount > 0) {
|
||||
string barcodeText = std::to_string(_newBarcode);
|
||||
//Pad 8 or 13 character barcode with 0s at start
|
||||
barcodeText.insert(0, _newBarcodeDigitCount - barcodeText.size(), '0');
|
||||
SetTextState(barcodeText);
|
||||
|
||||
_newBarcode = 0;
|
||||
_newBarcodeDigitCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void OnAfterSetState() override
|
||||
{
|
||||
if(GetRawState().State.size() > 0) {
|
||||
InitBarcodeStream();
|
||||
if(_console) {
|
||||
_insertCycle = _console->GetCpu()->GetCycleCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputBarcode(uint64_t barcode, uint32_t digitCount) override
|
||||
{
|
||||
_newBarcode = barcode;
|
||||
_newBarcodeDigitCount = digitCount;
|
||||
}
|
||||
|
||||
uint8_t ReadRAM(uint16_t addr) override
|
||||
{
|
||||
if(addr == 0x4017) {
|
||||
uint64_t elapsedCycles = _console->GetCpu()->GetCycleCount() - _insertCycle;
|
||||
constexpr uint32_t cyclesPerBit = CPU::ClockRateNtsc / 1200;
|
||||
|
||||
uint32_t streamPosition = (uint32_t)(elapsedCycles / cyclesPerBit);
|
||||
if(streamPosition < BarcodeBattlerReader::StreamSize) {
|
||||
return _barcodeStream[streamPosition] << 2;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WriteRAM(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
}
|
||||
};
|
|
@ -1,109 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "IMemoryHandler.h"
|
||||
#include "EmulationSettings.h"
|
||||
#include "Snapshotable.h"
|
||||
#include "SoundMixer.h"
|
||||
#include "Console.h"
|
||||
|
||||
class BaseApuChannel : public IMemoryHandler, public Snapshotable
|
||||
{
|
||||
private:
|
||||
SoundMixer *_mixer;
|
||||
uint32_t _previousCycle;
|
||||
AudioChannel _channel;
|
||||
NesModel _nesModel;
|
||||
|
||||
protected:
|
||||
int8_t _lastOutput;
|
||||
uint16_t _timer = 0;
|
||||
uint16_t _period = 0;
|
||||
shared_ptr<Console> _console;
|
||||
|
||||
AudioChannel GetChannel()
|
||||
{
|
||||
return _channel;
|
||||
}
|
||||
|
||||
public:
|
||||
virtual void Clock() = 0;
|
||||
virtual bool GetStatus() = 0;
|
||||
|
||||
BaseApuChannel(AudioChannel channel, shared_ptr<Console> console, SoundMixer *mixer)
|
||||
{
|
||||
_channel = channel;
|
||||
_mixer = mixer;
|
||||
_console = console;
|
||||
_nesModel = NesModel::NTSC;
|
||||
|
||||
Reset(false);
|
||||
}
|
||||
|
||||
virtual void Reset(bool softReset)
|
||||
{
|
||||
_timer = 0;
|
||||
_period = 0;
|
||||
_lastOutput = 0;
|
||||
_previousCycle = 0;
|
||||
if(_mixer) {
|
||||
//_mixer is null/not needed for MMC5 square channels
|
||||
_mixer->Reset();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void StreamState(bool saving) override
|
||||
{
|
||||
if(!saving) {
|
||||
_previousCycle = 0;
|
||||
}
|
||||
|
||||
Stream(_lastOutput, _timer, _period, _nesModel);
|
||||
}
|
||||
|
||||
void SetNesModel(NesModel model)
|
||||
{
|
||||
_nesModel = model;
|
||||
}
|
||||
|
||||
NesModel GetNesModel()
|
||||
{
|
||||
if(_nesModel == NesModel::NTSC || _nesModel == NesModel::Dendy) {
|
||||
//Dendy APU works with NTSC timings
|
||||
return NesModel::NTSC;
|
||||
} else {
|
||||
return _nesModel;
|
||||
}
|
||||
}
|
||||
|
||||
void Run(uint32_t targetCycle)
|
||||
{
|
||||
int32_t cyclesToRun = targetCycle - _previousCycle;
|
||||
while(cyclesToRun > _timer) {
|
||||
cyclesToRun -= _timer + 1;
|
||||
_previousCycle += _timer + 1;
|
||||
Clock();
|
||||
_timer = _period;
|
||||
}
|
||||
|
||||
_timer -= cyclesToRun;
|
||||
_previousCycle = targetCycle;
|
||||
}
|
||||
|
||||
uint8_t ReadRAM(uint16_t addr) override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AddOutput(int8_t output)
|
||||
{
|
||||
if(output != _lastOutput) {
|
||||
_mixer->AddDelta(_channel, _previousCycle, output - _lastOutput);
|
||||
_lastOutput = output;
|
||||
}
|
||||
}
|
||||
|
||||
void EndFrame()
|
||||
{
|
||||
_previousCycle = 0;
|
||||
}
|
||||
};
|
|
@ -1,286 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
#include "BaseControlDevice.h"
|
||||
#include "KeyManager.h"
|
||||
#include "../Utilities/StringUtilities.h"
|
||||
#include "Console.h"
|
||||
#include "EmulationSettings.h"
|
||||
|
||||
BaseControlDevice::BaseControlDevice(shared_ptr<Console> console, uint8_t port, KeyMappingSet keyMappingSet)
|
||||
{
|
||||
_console = console;
|
||||
_port = port;
|
||||
_strobe = false;
|
||||
_keyMappings = keyMappingSet.GetKeyMappingArray();
|
||||
}
|
||||
|
||||
BaseControlDevice::~BaseControlDevice()
|
||||
{
|
||||
}
|
||||
|
||||
uint8_t BaseControlDevice::GetPort()
|
||||
{
|
||||
return _port;
|
||||
}
|
||||
|
||||
void BaseControlDevice::SetStateFromInput()
|
||||
{
|
||||
ClearState();
|
||||
InternalSetStateFromInput();
|
||||
}
|
||||
|
||||
void BaseControlDevice::InternalSetStateFromInput()
|
||||
{
|
||||
}
|
||||
|
||||
void BaseControlDevice::StreamState(bool saving)
|
||||
{
|
||||
auto lock = _stateLock.AcquireSafe();
|
||||
VectorInfo<uint8_t> state{ &_state.State };
|
||||
Stream(_strobe, state);
|
||||
}
|
||||
|
||||
bool BaseControlDevice::IsCurrentPort(uint16_t addr)
|
||||
{
|
||||
return _port == (addr - 0x4016);
|
||||
}
|
||||
|
||||
bool BaseControlDevice::IsExpansionDevice()
|
||||
{
|
||||
return _port == BaseControlDevice::ExpDevicePort;
|
||||
}
|
||||
|
||||
void BaseControlDevice::StrobeProcessRead()
|
||||
{
|
||||
if(_strobe) {
|
||||
RefreshStateBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
void BaseControlDevice::StrobeProcessWrite(uint8_t value)
|
||||
{
|
||||
bool prevStrobe = _strobe;
|
||||
_strobe = (value & 0x01) == 0x01;
|
||||
|
||||
if(prevStrobe && !_strobe) {
|
||||
RefreshStateBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
void BaseControlDevice::ClearState()
|
||||
{
|
||||
auto lock = _stateLock.AcquireSafe();
|
||||
_state = ControlDeviceState();
|
||||
}
|
||||
|
||||
ControlDeviceState BaseControlDevice::GetRawState()
|
||||
{
|
||||
auto lock = _stateLock.AcquireSafe();
|
||||
return _state;
|
||||
}
|
||||
|
||||
void BaseControlDevice::SetRawState(ControlDeviceState state)
|
||||
{
|
||||
auto lock = _stateLock.AcquireSafe();
|
||||
_state = state;
|
||||
}
|
||||
|
||||
void BaseControlDevice::SetTextState(string textState)
|
||||
{
|
||||
auto lock = _stateLock.AcquireSafe();
|
||||
ClearState();
|
||||
|
||||
if(IsRawString()) {
|
||||
_state.State.insert(_state.State.end(), textState.begin(), textState.end());
|
||||
} else {
|
||||
if(HasCoordinates()) {
|
||||
vector<string> data = StringUtilities::Split(textState, ' ');
|
||||
if(data.size() >= 3) {
|
||||
MousePosition pos;
|
||||
try {
|
||||
pos.X = (int16_t)std::stol(data[0]);
|
||||
pos.Y = (int16_t)std::stol(data[1]);
|
||||
} catch(std::exception&) {
|
||||
pos.X = -1;
|
||||
pos.Y = -1;
|
||||
}
|
||||
SetCoordinates(pos);
|
||||
textState = data[2];
|
||||
}
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for(char c : textState) {
|
||||
if(c != '.') {
|
||||
SetBit(i);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string BaseControlDevice::GetTextState()
|
||||
{
|
||||
auto lock = _stateLock.AcquireSafe();
|
||||
if(IsRawString()) {
|
||||
return string((char*)_state.State.data(), _state.State.size());
|
||||
} else {
|
||||
string keyNames = GetKeyNames();
|
||||
string output = "";
|
||||
|
||||
if(HasCoordinates()) {
|
||||
MousePosition pos = GetCoordinates();
|
||||
output += std::to_string(pos.X) + " " + std::to_string(pos.Y) + " ";
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < keyNames.size(); i++) {
|
||||
output += IsPressed((uint8_t)i) ? keyNames[i] : '.';
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
void BaseControlDevice::EnsureCapacity(int32_t minBitCount)
|
||||
{
|
||||
auto lock = _stateLock.AcquireSafe();
|
||||
uint32_t minByteCount = minBitCount / 8 + 1 + (HasCoordinates() ? 32 : 0);
|
||||
int32_t gap = minByteCount - (int32_t)_state.State.size();
|
||||
|
||||
if(gap > 0) {
|
||||
_state.State.insert(_state.State.end(), gap, 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool BaseControlDevice::IsKeyboard()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BaseControlDevice::HasCoordinates()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BaseControlDevice::IsRawString()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t BaseControlDevice::GetByteIndex(uint8_t bit)
|
||||
{
|
||||
return bit / 8 + (HasCoordinates() ? 4 : 0);
|
||||
}
|
||||
|
||||
bool BaseControlDevice::IsPressed(uint8_t bit)
|
||||
{
|
||||
auto lock = _stateLock.AcquireSafe();
|
||||
EnsureCapacity(bit);
|
||||
uint8_t bitMask = 1 << (bit % 8);
|
||||
return (_state.State[GetByteIndex(bit)] & bitMask) != 0;
|
||||
}
|
||||
|
||||
void BaseControlDevice::SetBitValue(uint8_t bit, bool set)
|
||||
{
|
||||
if(set) {
|
||||
SetBit(bit);
|
||||
} else {
|
||||
ClearBit(bit);
|
||||
}
|
||||
}
|
||||
|
||||
void BaseControlDevice::SetBit(uint8_t bit)
|
||||
{
|
||||
auto lock = _stateLock.AcquireSafe();
|
||||
EnsureCapacity(bit);
|
||||
uint8_t bitMask = 1 << (bit % 8);
|
||||
_state.State[GetByteIndex(bit)] |= bitMask;
|
||||
}
|
||||
|
||||
void BaseControlDevice::ClearBit(uint8_t bit)
|
||||
{
|
||||
auto lock = _stateLock.AcquireSafe();
|
||||
EnsureCapacity(bit);
|
||||
uint8_t bitMask = 1 << (bit % 8);
|
||||
_state.State[GetByteIndex(bit)] &= ~bitMask;
|
||||
}
|
||||
|
||||
void BaseControlDevice::InvertBit(uint8_t bit)
|
||||
{
|
||||
if(IsPressed(bit)) {
|
||||
ClearBit(bit);
|
||||
} else {
|
||||
SetBit(bit);
|
||||
}
|
||||
}
|
||||
|
||||
void BaseControlDevice::SetPressedState(uint8_t bit, uint32_t keyCode)
|
||||
{
|
||||
if(IsKeyboard() && keyCode < 0x200 && !_console->GetSettings()->IsKeyboardMode()) {
|
||||
//Prevent keyboard device input when keyboard mode is off
|
||||
return;
|
||||
}
|
||||
|
||||
if(_console->GetSettings()->InputEnabled() && (!_console->GetSettings()->IsKeyboardMode() || keyCode >= 0x200 || IsKeyboard()) && KeyManager::IsKeyPressed(keyCode)) {
|
||||
SetBit(bit);
|
||||
}
|
||||
}
|
||||
|
||||
void BaseControlDevice::SetPressedState(uint8_t bit, bool enabled)
|
||||
{
|
||||
if(enabled) {
|
||||
SetBit(bit);
|
||||
}
|
||||
}
|
||||
|
||||
void BaseControlDevice::SetCoordinates(MousePosition pos)
|
||||
{
|
||||
auto lock = _stateLock.AcquireSafe();
|
||||
EnsureCapacity(-1);
|
||||
|
||||
_state.State[0] = pos.X & 0xFF;
|
||||
_state.State[1] = (pos.X >> 8) & 0xFF;
|
||||
_state.State[2] = pos.Y & 0xFF;
|
||||
_state.State[3] = (pos.Y >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
MousePosition BaseControlDevice::GetCoordinates()
|
||||
{
|
||||
auto lock = _stateLock.AcquireSafe();
|
||||
EnsureCapacity(-1);
|
||||
|
||||
MousePosition pos;
|
||||
pos.X = _state.State[0] | (_state.State[1] << 8);
|
||||
pos.Y = _state.State[2] | (_state.State[3] << 8);
|
||||
return pos;
|
||||
}
|
||||
|
||||
void BaseControlDevice::SetMovement(MouseMovement mov)
|
||||
{
|
||||
MouseMovement prev = GetMovement();
|
||||
mov.dx += prev.dx;
|
||||
mov.dy += prev.dy;
|
||||
SetCoordinates({ mov.dx, mov.dy });
|
||||
}
|
||||
|
||||
MouseMovement BaseControlDevice::GetMovement()
|
||||
{
|
||||
MousePosition pos = GetCoordinates();
|
||||
SetCoordinates({ 0, 0 });
|
||||
return { pos.X, pos.Y };
|
||||
}
|
||||
|
||||
void BaseControlDevice::SwapButtons(shared_ptr<BaseControlDevice> state1, uint8_t button1, shared_ptr<BaseControlDevice> state2, uint8_t button2)
|
||||
{
|
||||
bool pressed1 = state1->IsPressed(button1);
|
||||
bool pressed2 = state2->IsPressed(button2);
|
||||
|
||||
state1->ClearBit(button1);
|
||||
state2->ClearBit(button2);
|
||||
|
||||
if(pressed1) {
|
||||
state2->SetBit(button2);
|
||||
}
|
||||
if(pressed2) {
|
||||
state1->SetBit(button1);
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "EmulationSettings.h"
|
||||
#include "Snapshotable.h"
|
||||
#include "ControlManager.h"
|
||||
#include "ControlDeviceState.h"
|
||||
#include "../Utilities/SimpleLock.h"
|
||||
|
||||
class Console;
|
||||
|
||||
class BaseControlDevice : public Snapshotable
|
||||
{
|
||||
private:
|
||||
ControlDeviceState _state;
|
||||
|
||||
protected:
|
||||
shared_ptr<Console> _console;
|
||||
vector<KeyMapping> _keyMappings;
|
||||
bool _strobe;
|
||||
uint8_t _port;
|
||||
SimpleLock _stateLock;
|
||||
|
||||
virtual void RefreshStateBuffer() { }
|
||||
virtual void StreamState(bool saving);
|
||||
|
||||
void EnsureCapacity(int32_t minBitCount);
|
||||
uint32_t GetByteIndex(uint8_t bit);
|
||||
virtual bool HasCoordinates();
|
||||
virtual bool IsRawString();
|
||||
|
||||
bool IsCurrentPort(uint16_t addr);
|
||||
bool IsExpansionDevice();
|
||||
void StrobeProcessRead();
|
||||
void StrobeProcessWrite(uint8_t value);
|
||||
|
||||
virtual string GetKeyNames() { return ""; }
|
||||
|
||||
void SetPressedState(uint8_t bit, uint32_t keyCode);
|
||||
void SetPressedState(uint8_t bit, bool enabled);
|
||||
|
||||
void SetCoordinates(MousePosition pos);
|
||||
|
||||
void SetMovement(MouseMovement mov);
|
||||
MouseMovement GetMovement();
|
||||
|
||||
virtual void InternalSetStateFromInput();
|
||||
|
||||
public:
|
||||
static constexpr uint8_t ExpDevicePort = 4;
|
||||
static constexpr uint8_t ConsoleInputPort = 5;
|
||||
static constexpr uint8_t MapperInputPort = 6;
|
||||
static constexpr uint8_t ExpDevicePort2 = 7;
|
||||
static constexpr uint8_t PortCount = ExpDevicePort2 + 1;
|
||||
|
||||
BaseControlDevice(shared_ptr<Console> console, uint8_t port, KeyMappingSet keyMappingSet = KeyMappingSet());
|
||||
virtual ~BaseControlDevice();
|
||||
|
||||
uint8_t GetPort();
|
||||
|
||||
bool IsPressed(uint8_t bit);
|
||||
MousePosition GetCoordinates();
|
||||
|
||||
virtual bool IsKeyboard();
|
||||
|
||||
void ClearState();
|
||||
void SetBit(uint8_t bit);
|
||||
void ClearBit(uint8_t bit);
|
||||
void InvertBit(uint8_t bit);
|
||||
void SetBitValue(uint8_t bit, bool set);
|
||||
|
||||
void SetTextState(string state);
|
||||
string GetTextState();
|
||||
|
||||
void SetStateFromInput();
|
||||
virtual void OnAfterSetState() { }
|
||||
|
||||
void SetRawState(ControlDeviceState state);
|
||||
ControlDeviceState GetRawState();
|
||||
|
||||
virtual uint8_t ReadRAM(uint16_t addr) = 0;
|
||||
virtual void WriteRAM(uint16_t addr, uint8_t value) = 0;
|
||||
|
||||
void static SwapButtons(shared_ptr<BaseControlDevice> state1, uint8_t button1, shared_ptr<BaseControlDevice> state2, uint8_t button2);
|
||||
};
|
|
@ -1,57 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "Snapshotable.h"
|
||||
|
||||
class BaseEeprom24C0X : public Snapshotable
|
||||
{
|
||||
protected:
|
||||
enum class Mode
|
||||
{
|
||||
Idle = 0,
|
||||
Address = 1,
|
||||
Read = 2,
|
||||
Write = 3,
|
||||
SendAck = 4,
|
||||
WaitAck = 5,
|
||||
ChipAddress = 6
|
||||
};
|
||||
|
||||
shared_ptr<Console> _console;
|
||||
|
||||
Mode _mode = Mode::Idle;
|
||||
Mode _nextMode = Mode::Idle;
|
||||
uint8_t _chipAddress = 0;
|
||||
uint8_t _address = 0;
|
||||
uint8_t _data = 0;
|
||||
uint8_t _counter = 0;
|
||||
uint8_t _output = 0;
|
||||
uint8_t _prevScl = 0;
|
||||
uint8_t _prevSda = 0;
|
||||
uint8_t _romData[256];
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
ArrayInfo<uint8_t> romData { _romData, 256 };
|
||||
Stream(_mode, _nextMode, _chipAddress, _address, _data, _counter, _output, _prevScl, _prevSda, romData);
|
||||
}
|
||||
|
||||
public:
|
||||
virtual void Write(uint8_t scl, uint8_t sda) = 0;
|
||||
virtual void SaveBattery() = 0;
|
||||
|
||||
uint8_t Read()
|
||||
{
|
||||
return _output;
|
||||
}
|
||||
|
||||
void WriteScl(uint8_t scl)
|
||||
{
|
||||
Write(scl, _prevSda);
|
||||
}
|
||||
|
||||
void WriteSda(uint8_t sda)
|
||||
{
|
||||
Write(_prevScl, sda);
|
||||
}
|
||||
};
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
#include "BaseExpansionAudio.h"
|
||||
#include "Console.h"
|
||||
#include "APU.h"
|
||||
|
||||
BaseExpansionAudio::BaseExpansionAudio(shared_ptr<Console> console)
|
||||
{
|
||||
_console = console;
|
||||
}
|
||||
|
||||
void BaseExpansionAudio::StreamState(bool saving)
|
||||
{
|
||||
}
|
||||
|
||||
void BaseExpansionAudio::Clock()
|
||||
{
|
||||
if(_console->GetApu()->IsApuEnabled()) {
|
||||
ClockAudio();
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "Snapshotable.h"
|
||||
#include "EmulationSettings.h"
|
||||
|
||||
class MemoryManager;
|
||||
|
||||
class BaseExpansionAudio : public Snapshotable
|
||||
{
|
||||
protected:
|
||||
shared_ptr<Console> _console = nullptr;
|
||||
|
||||
virtual void ClockAudio() = 0;
|
||||
void StreamState(bool saving) override;
|
||||
|
||||
public:
|
||||
BaseExpansionAudio(shared_ptr<Console> console);
|
||||
|
||||
void Clock();
|
||||
};
|
|
@ -1,89 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "Snapshotable.h"
|
||||
|
||||
class BaseFdsChannel : public Snapshotable
|
||||
{
|
||||
protected:
|
||||
uint8_t _speed = 0;
|
||||
uint8_t _gain = 0;
|
||||
bool _envelopeOff = false;
|
||||
bool _volumeIncrease = false;
|
||||
uint16_t _frequency = 0;
|
||||
|
||||
uint32_t _timer = 0;
|
||||
|
||||
//"Few FDS NSFs write to this register. The BIOS initializes this to $FF."
|
||||
uint8_t _masterSpeed = 0xFF;
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
Stream(_speed, _gain, _envelopeOff, _volumeIncrease, _frequency, _timer, _masterSpeed);
|
||||
}
|
||||
|
||||
public:
|
||||
void SetMasterEnvelopeSpeed(uint8_t masterSpeed)
|
||||
{
|
||||
_masterSpeed = masterSpeed;
|
||||
}
|
||||
|
||||
virtual void WriteReg(uint16_t addr, uint8_t value)
|
||||
{
|
||||
switch(addr & 0x03) {
|
||||
case 0:
|
||||
_speed = value & 0x3F;
|
||||
_volumeIncrease = (value & 0x40) == 0x40;
|
||||
_envelopeOff = (value & 0x80) == 0x80;
|
||||
|
||||
//"Writing to this register immediately resets the clock timer that ticks the volume envelope (delaying the next tick slightly)."
|
||||
ResetTimer();
|
||||
|
||||
if(_envelopeOff) {
|
||||
//Envelope is off, gain = speed
|
||||
_gain = _speed;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
_frequency = (_frequency & 0x0F00) | value;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
_frequency = (_frequency & 0xFF) | ((value & 0x0F) << 8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool TickEnvelope()
|
||||
{
|
||||
if(!_envelopeOff && _masterSpeed > 0) {
|
||||
_timer--;
|
||||
if(_timer == 0) {
|
||||
ResetTimer();
|
||||
|
||||
if(_volumeIncrease && _gain < 32) {
|
||||
_gain++;
|
||||
} else if(!_volumeIncrease && _gain > 0) {
|
||||
_gain--;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t GetGain()
|
||||
{
|
||||
return _gain;
|
||||
}
|
||||
|
||||
uint16_t GetFrequency()
|
||||
{
|
||||
return _frequency;
|
||||
}
|
||||
|
||||
void ResetTimer()
|
||||
{
|
||||
_timer = 8 * (_speed + 1) * _masterSpeed;
|
||||
}
|
||||
};
|
|
@ -1,22 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "MessageManager.h"
|
||||
|
||||
class BaseLoader
|
||||
{
|
||||
protected:
|
||||
bool _checkOnly;
|
||||
|
||||
void Log(string message)
|
||||
{
|
||||
if(!_checkOnly) {
|
||||
MessageManager::Log(message);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
BaseLoader(bool checkOnly = false)
|
||||
{
|
||||
_checkOnly = checkOnly;
|
||||
}
|
||||
};
|
1276
Core/BaseMapper.cpp
1276
Core/BaseMapper.cpp
File diff suppressed because it is too large
Load diff
|
@ -1,246 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "Snapshotable.h"
|
||||
#include "IMemoryHandler.h"
|
||||
#include "DebuggerTypes.h"
|
||||
#include "Debugger.h"
|
||||
#include "Types.h"
|
||||
#include "IBattery.h"
|
||||
#include "RomData.h"
|
||||
#include "Console.h"
|
||||
#include "CPU.h"
|
||||
#include "EPSMAudio.h"
|
||||
|
||||
class BaseControlDevice;
|
||||
|
||||
class BaseMapper : public IMemoryHandler, public Snapshotable, public IBattery
|
||||
{
|
||||
private:
|
||||
MirroringType _mirroringType;
|
||||
string _batteryFilename;
|
||||
|
||||
uint16_t InternalGetPrgPageSize();
|
||||
uint16_t InternalGetSaveRamPageSize();
|
||||
uint16_t InternalGetWorkRamPageSize();
|
||||
uint16_t InternalGetChrPageSize();
|
||||
uint16_t InternalGetChrRamPageSize();
|
||||
bool ValidateAddressRange(uint16_t startAddr, uint16_t endAddr);
|
||||
|
||||
uint8_t *_nametableRam = nullptr;
|
||||
uint8_t _nametableCount = 2;
|
||||
|
||||
bool _onlyChrRam = false;
|
||||
bool _hasBusConflicts = false;
|
||||
|
||||
bool _allowRegisterRead = false;
|
||||
bool _isReadRegisterAddr[0x10000];
|
||||
bool _isWriteRegisterAddr[0x10000];
|
||||
|
||||
MemoryAccessType _prgMemoryAccess[0x100];
|
||||
uint8_t* _prgPages[0x100];
|
||||
|
||||
MemoryAccessType _chrMemoryAccess[0x100];
|
||||
uint8_t* _chrPages[0x100];
|
||||
|
||||
int32_t _prgMemoryOffset[0x100];
|
||||
PrgMemoryType _prgMemoryType[0x100];
|
||||
|
||||
int32_t _chrMemoryOffset[0x100];
|
||||
ChrMemoryType _chrMemoryType[0x100];
|
||||
|
||||
vector<uint8_t> _originalPrgRom;
|
||||
vector<uint8_t> _originalChrRom;
|
||||
|
||||
protected:
|
||||
RomInfo _romInfo;
|
||||
|
||||
shared_ptr<BaseControlDevice> _mapperControlDevice;
|
||||
shared_ptr<Console> _console;
|
||||
|
||||
uint8_t* _prgRom = nullptr;
|
||||
uint8_t* _chrRom = nullptr;
|
||||
uint8_t* _chrRam = nullptr;
|
||||
uint32_t _prgSize = 0;
|
||||
uint32_t _chrRomSize = 0;
|
||||
uint32_t _chrRamSize = 0;
|
||||
|
||||
uint8_t* _saveRam = nullptr;
|
||||
uint32_t _saveRamSize = 0;
|
||||
uint32_t _workRamSize = 0;
|
||||
uint8_t* _workRam = nullptr;
|
||||
bool _hasChrBattery = false;
|
||||
int16_t _vramOpenBusValue = -1;
|
||||
|
||||
virtual void InitMapper() = 0;
|
||||
virtual void InitMapper(RomData &romData);
|
||||
virtual uint16_t GetPRGPageSize() = 0;
|
||||
virtual uint16_t GetCHRPageSize() = 0;
|
||||
|
||||
bool IsNes20();
|
||||
|
||||
virtual uint16_t GetChrRamPageSize() { return 0x2000; }
|
||||
|
||||
//Save ram is battery backed and saved to disk
|
||||
virtual uint32_t GetSaveRamSize() { return HasBattery() ? 0x2000 : 0; }
|
||||
virtual uint32_t GetSaveRamPageSize() { return 0x2000; }
|
||||
virtual bool ForceChrBattery() { return false; }
|
||||
|
||||
virtual bool ForceSaveRamSize() { return false; }
|
||||
virtual bool ForceWorkRamSize() { return false; }
|
||||
|
||||
virtual uint32_t GetChrRamSize() { return 0x0000; }
|
||||
|
||||
//Work ram is NOT saved - aka Expansion ram, etc.
|
||||
virtual uint32_t GetWorkRamSize() { return HasBattery() ? 0 : 0x2000; }
|
||||
virtual uint32_t GetWorkRamPageSize() { return 0x2000; }
|
||||
|
||||
virtual uint16_t RegisterStartAddress() { return 0x8000; }
|
||||
virtual uint16_t RegisterEndAddress() { return 0xFFFF; }
|
||||
virtual bool AllowRegisterRead() { return false; }
|
||||
|
||||
virtual uint32_t GetDipSwitchCount() { return 0; }
|
||||
|
||||
virtual bool HasBusConflicts() { return false; }
|
||||
|
||||
uint8_t InternalReadRam(uint16_t addr);
|
||||
|
||||
virtual void WriteRegister(uint16_t addr, uint8_t value);
|
||||
virtual void WriteEPSM(uint16_t addr, uint8_t value);
|
||||
virtual uint8_t ReadRegister(uint16_t addr);
|
||||
|
||||
void SelectPrgPage4x(uint16_t slot, uint16_t page, PrgMemoryType memoryType = PrgMemoryType::PrgRom);
|
||||
void SelectPrgPage2x(uint16_t slot, uint16_t page, PrgMemoryType memoryType = PrgMemoryType::PrgRom);
|
||||
virtual void SelectPRGPage(uint16_t slot, uint16_t page, PrgMemoryType memoryType = PrgMemoryType::PrgRom);
|
||||
void SetCpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, int16_t pageNumber, PrgMemoryType type, int8_t accessType = -1);
|
||||
void SetCpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, PrgMemoryType type, uint32_t sourceOffset, int8_t accessType);
|
||||
void SetCpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, uint8_t *source, int8_t accessType = -1);
|
||||
void RemoveCpuMemoryMapping(uint16_t startAddr, uint16_t endAddr);
|
||||
|
||||
virtual void SelectChrPage8x(uint16_t slot, uint16_t page, ChrMemoryType memoryType = ChrMemoryType::Default);
|
||||
virtual void SelectChrPage4x(uint16_t slot, uint16_t page, ChrMemoryType memoryType = ChrMemoryType::Default);
|
||||
virtual void SelectChrPage2x(uint16_t slot, uint16_t page, ChrMemoryType memoryType = ChrMemoryType::Default);
|
||||
virtual void SelectCHRPage(uint16_t slot, uint16_t page, ChrMemoryType memoryType = ChrMemoryType::Default);
|
||||
void SetPpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, uint16_t pageNumber, ChrMemoryType type = ChrMemoryType::Default, int8_t accessType = -1);
|
||||
void SetPpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, ChrMemoryType type, uint32_t sourceOffset, int8_t accessType);
|
||||
void SetPpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, uint8_t* sourceMemory, int8_t accessType = -1);
|
||||
void RemovePpuMemoryMapping(uint16_t startAddr, uint16_t endAddr);
|
||||
|
||||
bool HasBattery();
|
||||
virtual void LoadBattery();
|
||||
string GetBatteryFilename();
|
||||
|
||||
uint32_t GetPRGPageCount();
|
||||
uint32_t GetCHRPageCount();
|
||||
|
||||
uint8_t GetPowerOnByte(uint8_t defaultValue = 0);
|
||||
uint32_t GetDipSwitches();
|
||||
|
||||
void SetupDefaultWorkRam();
|
||||
|
||||
void InitializeChrRam(int32_t chrRamSize = -1);
|
||||
|
||||
void AddRegisterRange(uint16_t startAddr, uint16_t endAddr, MemoryOperation operation = MemoryOperation::Any);
|
||||
void RemoveRegisterRange(uint16_t startAddr, uint16_t endAddr, MemoryOperation operation = MemoryOperation::Any);
|
||||
|
||||
virtual void StreamState(bool saving) override;
|
||||
|
||||
void RestorePrgChrState();
|
||||
|
||||
uint8_t* GetNametable(uint8_t nametableIndex);
|
||||
void SetNametable(uint8_t index, uint8_t nametableIndex);
|
||||
void SetNametables(uint8_t nametable1Index, uint8_t nametable2Index, uint8_t nametable3Index, uint8_t nametable4Index);
|
||||
void SetMirroringType(MirroringType type);
|
||||
MirroringType GetMirroringType();
|
||||
|
||||
uint8_t InternalReadVRAM(uint16_t addr);
|
||||
|
||||
public:
|
||||
static constexpr uint32_t NametableCount = 0x10;
|
||||
static constexpr uint32_t NametableSize = 0x400;
|
||||
unique_ptr<EPSMAudio> _epsmaudio;
|
||||
void Initialize(RomData &romData);
|
||||
|
||||
virtual ~BaseMapper();
|
||||
virtual void Reset(bool softReset);
|
||||
|
||||
virtual ConsoleFeatures GetAvailableFeatures();
|
||||
|
||||
virtual void SetNesModel(NesModel model) { }
|
||||
virtual void ProcessCpuClock() { }
|
||||
virtual void ProcessEPSMClock() { _epsmaudio->Clock(); }
|
||||
virtual void NotifyVRAMAddressChange(uint16_t addr);
|
||||
virtual void GetMemoryRanges(MemoryRanges &ranges) override;
|
||||
|
||||
virtual void SaveBattery() override;
|
||||
|
||||
void SetConsole(shared_ptr<Console> console);
|
||||
|
||||
shared_ptr<BaseControlDevice> GetMapperControlDevice();
|
||||
RomInfo GetRomInfo();
|
||||
uint32_t GetMapperDipSwitchCount();
|
||||
|
||||
virtual void ApplySamples(int16_t* buffer, size_t sampleCount, double volume) {}
|
||||
|
||||
uint8_t ReadRAM(uint16_t addr) override;
|
||||
uint8_t PeekRAM(uint16_t addr) override;
|
||||
uint8_t DebugReadRAM(uint16_t addr);
|
||||
void WriteRAM(uint16_t addr, uint8_t value) override;
|
||||
void DebugWriteRAM(uint16_t addr, uint8_t value);
|
||||
void WritePrgRam(uint16_t addr, uint8_t value);
|
||||
|
||||
virtual uint8_t MapperReadVRAM(uint16_t addr, MemoryOperationType operationType);
|
||||
|
||||
__forceinline uint8_t ReadVRAM(uint16_t addr, MemoryOperationType type = MemoryOperationType::PpuRenderingRead)
|
||||
{
|
||||
uint8_t value = MapperReadVRAM(addr, type);
|
||||
_console->DebugProcessVramReadOperation(type, addr, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
void DebugWriteVRAM(uint16_t addr, uint8_t value, bool disableSideEffects = true);
|
||||
void WriteVRAM(uint16_t addr, uint8_t value);
|
||||
|
||||
uint8_t DebugReadVRAM(uint16_t addr, bool disableSideEffects = true);
|
||||
|
||||
void CopyChrTile(uint32_t address, uint8_t *dest);
|
||||
|
||||
//Debugger Helper Functions
|
||||
bool HasChrRam();
|
||||
bool HasChrRom();
|
||||
|
||||
CartridgeState GetState();
|
||||
uint8_t* GetPrgRom();
|
||||
uint8_t* GetWorkRam();
|
||||
uint8_t* GetSaveRam();
|
||||
|
||||
uint8_t GetMemoryValue(DebugMemoryType memoryType, uint32_t address);
|
||||
void SetMemoryValue(DebugMemoryType memoryType, uint32_t address, uint8_t value);
|
||||
uint32_t GetMemorySize(DebugMemoryType type);
|
||||
|
||||
uint32_t CopyMemory(DebugMemoryType type, uint8_t* buffer);
|
||||
void WriteMemory(DebugMemoryType type, uint8_t* buffer, int32_t length);
|
||||
|
||||
void GetAbsoluteAddressAndType(uint32_t relativeAddr, AddressTypeInfo *info);
|
||||
void GetPpuAbsoluteAddressAndType(uint32_t relativeAddr, PpuAddressTypeInfo *info);
|
||||
int32_t ToAbsoluteAddress(uint16_t addr);
|
||||
int32_t ToAbsoluteSaveRamAddress(uint16_t addr);
|
||||
int32_t ToAbsoluteWorkRamAddress(uint16_t addr);
|
||||
int32_t ToAbsoluteChrAddress(uint16_t addr);
|
||||
int32_t ToAbsoluteChrRamAddress(uint16_t addr);
|
||||
int32_t ToAbsoluteChrRomAddress(uint16_t addr);
|
||||
int32_t FromAbsoluteChrAddress(uint32_t addr);
|
||||
int32_t FromAbsoluteAddress(uint32_t addr, AddressType type = AddressType::PrgRom);
|
||||
int32_t FromAbsolutePpuAddress(uint32_t addr, PpuAddressType type);
|
||||
|
||||
bool IsWriteRegister(uint16_t addr);
|
||||
bool IsReadRegister(uint16_t addr);
|
||||
|
||||
void GetRomFileData(vector<uint8_t> &out, bool asIpsFile, uint8_t* header);
|
||||
|
||||
vector<uint8_t> GetPrgChrCopy();
|
||||
void RestorePrgChrBackup(vector<uint8_t>& backupData);
|
||||
void RevertPrgChrChanges();
|
||||
bool HasPrgChrChanges();
|
||||
void CopyPrgChrRom(shared_ptr<BaseMapper> mapper);
|
||||
};
|
|
@ -1,199 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
#include <cmath>
|
||||
#include "BaseRenderer.h"
|
||||
#include "Console.h"
|
||||
#include "EmulationSettings.h"
|
||||
#include "VideoDecoder.h"
|
||||
#include "PPU.h"
|
||||
|
||||
BaseRenderer::BaseRenderer(shared_ptr<Console> console, bool registerAsMessageManager)
|
||||
{
|
||||
_console = console;
|
||||
|
||||
if(registerAsMessageManager) {
|
||||
//Only display messages on the master CPU's screen
|
||||
MessageManager::RegisterMessageManager(this);
|
||||
}
|
||||
}
|
||||
|
||||
BaseRenderer::~BaseRenderer()
|
||||
{
|
||||
MessageManager::UnregisterMessageManager(this);
|
||||
}
|
||||
|
||||
void BaseRenderer::DisplayMessage(string title, string message)
|
||||
{
|
||||
shared_ptr<ToastInfo> toast(new ToastInfo(title, message, 4000));
|
||||
_toasts.push_front(toast);
|
||||
}
|
||||
|
||||
void BaseRenderer::RemoveOldToasts()
|
||||
{
|
||||
_toasts.remove_if([](shared_ptr<ToastInfo> toast) { return toast->IsToastExpired(); });
|
||||
}
|
||||
|
||||
void BaseRenderer::DrawToasts()
|
||||
{
|
||||
RemoveOldToasts();
|
||||
|
||||
int counter = 0;
|
||||
int lastHeight = 5;
|
||||
for(shared_ptr<ToastInfo> toast : _toasts) {
|
||||
if(counter < 6) {
|
||||
DrawToast(toast, lastHeight);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring BaseRenderer::WrapText(string utf8Text, float maxLineWidth, uint32_t &lineCount)
|
||||
{
|
||||
using std::wstring;
|
||||
wstring text = utf8::utf8::decode(utf8Text);
|
||||
wstring wrappedText;
|
||||
list<wstring> words;
|
||||
wstring currentWord;
|
||||
for(size_t i = 0, len = text.length(); i < len; i++) {
|
||||
if(text[i] == L' ' || text[i] == L'\n') {
|
||||
if(currentWord.length() > 0) {
|
||||
words.push_back(currentWord);
|
||||
currentWord.clear();
|
||||
}
|
||||
} else {
|
||||
currentWord += text[i];
|
||||
}
|
||||
}
|
||||
if(currentWord.length() > 0) {
|
||||
words.push_back(currentWord);
|
||||
}
|
||||
|
||||
lineCount = 1;
|
||||
float spaceWidth = MeasureString(L" ");
|
||||
|
||||
float lineWidth = 0.0f;
|
||||
for(wstring word : words) {
|
||||
for(unsigned int i = 0; i < word.size(); i++) {
|
||||
if(!ContainsCharacter(word[i])) {
|
||||
word[i] = L'?';
|
||||
}
|
||||
}
|
||||
|
||||
float wordWidth = MeasureString(word.c_str());
|
||||
|
||||
if(lineWidth + wordWidth < maxLineWidth) {
|
||||
wrappedText += word + L" ";
|
||||
lineWidth += wordWidth + spaceWidth;
|
||||
} else {
|
||||
wrappedText += L"\n" + word + L" ";
|
||||
lineWidth = wordWidth + spaceWidth;
|
||||
lineCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return wrappedText;
|
||||
}
|
||||
|
||||
void BaseRenderer::DrawToast(shared_ptr<ToastInfo> toast, int &lastHeight)
|
||||
{
|
||||
//Get opacity for fade in/out effect
|
||||
uint8_t opacity = (uint8_t)(toast->GetOpacity()*255);
|
||||
int textLeftMargin = 4;
|
||||
|
||||
int lineHeight = 25;
|
||||
string text = "[" + toast->GetToastTitle() + "] " + toast->GetToastMessage();
|
||||
uint32_t lineCount = 0;
|
||||
std::wstring wrappedText = WrapText(text, (float)(_screenWidth - textLeftMargin * 2 - 20), lineCount);
|
||||
lastHeight += lineCount * lineHeight;
|
||||
DrawString(wrappedText, textLeftMargin, _screenHeight - lastHeight, opacity, opacity, opacity, opacity);
|
||||
}
|
||||
|
||||
void BaseRenderer::DrawString(std::string message, int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t opacity)
|
||||
{
|
||||
std::wstring textStr = utf8::utf8::decode(message);
|
||||
DrawString(textStr, x, y, r, g, b, opacity);
|
||||
}
|
||||
|
||||
void BaseRenderer::ShowFpsCounter(int lineNumber)
|
||||
{
|
||||
int yPos = 13 + 24 * lineNumber;
|
||||
if(_fpsTimer.GetElapsedMS() > 1000) {
|
||||
//Update fps every sec
|
||||
uint32_t frameCount = _console->GetFrameCount();
|
||||
if(_lastFrameCount > frameCount) {
|
||||
_currentFPS = 0;
|
||||
} else {
|
||||
_currentFPS = (int)(std::round((double)(frameCount - _lastFrameCount) / (_fpsTimer.GetElapsedMS() / 1000)));
|
||||
_currentRenderedFPS = (int)(std::round((double)(_renderedFrameCount - _lastRenderedFrameCount) / (_fpsTimer.GetElapsedMS() / 1000)));
|
||||
}
|
||||
_lastFrameCount = frameCount;
|
||||
_lastRenderedFrameCount = _renderedFrameCount;
|
||||
_fpsTimer.Reset();
|
||||
}
|
||||
|
||||
if(_currentFPS > 5000) {
|
||||
_currentFPS = 0;
|
||||
}
|
||||
if(_currentRenderedFPS > 5000) {
|
||||
_currentRenderedFPS = 0;
|
||||
}
|
||||
|
||||
string fpsString = string("FPS: ") + std::to_string(_currentFPS) + " / " + std::to_string(_currentRenderedFPS);
|
||||
DrawString(fpsString, _screenWidth - 125, yPos, 250, 235, 215);
|
||||
}
|
||||
|
||||
void BaseRenderer::ShowGameTimer(int lineNumber)
|
||||
{
|
||||
int yPos = 13 + 24 * lineNumber;
|
||||
double frameCount = _console->GetFrameCount();
|
||||
double frameRate = _console->GetModel() == NesModel::NTSC ? 60.098811862348404716732985230828 : 50.006977968268290848936010226333;
|
||||
//uint32_t milliseconds = (uint32_t)(frameCount / 60.1 * 1000) % 1000;
|
||||
uint32_t seconds = (uint32_t)(frameCount / frameRate) % 60;
|
||||
uint32_t minutes = (uint32_t)(frameCount / frameRate / 60) % 60;
|
||||
uint32_t hours = (uint32_t)(frameCount / frameRate / 3600);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::setw(2) << std::setfill('0') << hours << ":";
|
||||
ss << std::setw(2) << std::setfill('0') << minutes << ":";
|
||||
ss << std::setw(2) << std::setfill('0') << seconds;
|
||||
//ss << "." << std::setw(3) << std::setfill('0') << milliseconds;
|
||||
DrawString(ss.str(), _screenWidth - 95, yPos, 250, 235, 215);
|
||||
}
|
||||
|
||||
void BaseRenderer::ShowLagCounter(int lineNumber)
|
||||
{
|
||||
int yPos = 13 + 24 * lineNumber;
|
||||
string lagCounter = MessageManager::Localize("Lag") + ": " + std::to_string(_console->GetLagCounter());
|
||||
DrawString(lagCounter, _screenWidth - 123, yPos, 250, 235, 215);
|
||||
}
|
||||
|
||||
void BaseRenderer::ShowFrameCounter(int lineNumber)
|
||||
{
|
||||
int yPos = 13 + 24 * lineNumber;
|
||||
string lagCounter = MessageManager::Localize("Frame") + ": " + std::to_string(_console->GetFrameCount());
|
||||
DrawString(lagCounter, _screenWidth - 146, yPos, 250, 235, 215);
|
||||
}
|
||||
|
||||
void BaseRenderer::DrawCounters()
|
||||
{
|
||||
int lineNumber = 0;
|
||||
EmulationSettings* settings = _console->GetSettings();
|
||||
if(settings->CheckFlag(EmulationFlags::ShowGameTimer)) {
|
||||
ShowGameTimer(lineNumber++);
|
||||
}
|
||||
if(settings->CheckFlag(EmulationFlags::ShowFPS)) {
|
||||
ShowFpsCounter(lineNumber++);
|
||||
}
|
||||
if(settings->CheckFlag(EmulationFlags::ShowLagCounter)) {
|
||||
ShowLagCounter(lineNumber++);
|
||||
}
|
||||
if(settings->CheckFlag(EmulationFlags::ShowFrameCounter)) {
|
||||
ShowFrameCounter(lineNumber++);
|
||||
}
|
||||
}
|
||||
|
||||
bool BaseRenderer::IsMessageShown()
|
||||
{
|
||||
return !_toasts.empty();
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
#pragma once
|
||||
#include "../Core/IMessageManager.h"
|
||||
#include "../Utilities/Timer.h"
|
||||
|
||||
class Console;
|
||||
|
||||
class BaseRenderer : public IMessageManager
|
||||
{
|
||||
private:
|
||||
list<shared_ptr<ToastInfo>> _toasts;
|
||||
|
||||
Timer _fpsTimer;
|
||||
uint32_t _lastFrameCount = 0;
|
||||
uint32_t _lastRenderedFrameCount = 0;
|
||||
uint32_t _currentFPS = 0;
|
||||
uint32_t _currentRenderedFPS = 0;
|
||||
|
||||
void RemoveOldToasts();
|
||||
std::wstring WrapText(string utf8Text, float maxLineWidth, uint32_t &lineCount);
|
||||
virtual float MeasureString(std::wstring text) = 0;
|
||||
virtual bool ContainsCharacter(wchar_t character) = 0;
|
||||
|
||||
protected:
|
||||
shared_ptr<Console> _console;
|
||||
|
||||
uint32_t _screenWidth = 0;
|
||||
uint32_t _screenHeight = 0;
|
||||
uint32_t _renderedFrameCount = 0;
|
||||
|
||||
BaseRenderer(shared_ptr<Console> console, bool registerAsMessageManager);
|
||||
virtual ~BaseRenderer();
|
||||
|
||||
bool IsMessageShown();
|
||||
|
||||
void DisplayMessage(string title, string message);
|
||||
void DrawToasts();
|
||||
|
||||
void DrawToast(shared_ptr<ToastInfo> toast, int &lastHeight);
|
||||
void DrawString(std::string message, int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t opacity = 255);
|
||||
virtual void DrawString(std::wstring message, int x, int y, uint8_t r = 255, uint8_t g = 255, uint8_t b = 255, uint8_t opacity = 255) = 0;
|
||||
|
||||
void ShowFpsCounter(int lineNumber);
|
||||
void ShowLagCounter(int lineNumber);
|
||||
void ShowFrameCounter(int lineNumber);
|
||||
void ShowGameTimer(int lineNumber);
|
||||
void DrawCounters();
|
||||
};
|
|
@ -1,50 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
#include "BaseSoundManager.h"
|
||||
|
||||
void BaseSoundManager::ProcessLatency(uint32_t readPosition, uint32_t writePosition)
|
||||
{
|
||||
//Record latency between read & write cursors once per frame
|
||||
int32_t cursorGap;
|
||||
if(writePosition < readPosition) {
|
||||
cursorGap = writePosition - readPosition + _bufferSize;
|
||||
} else {
|
||||
cursorGap = writePosition - readPosition;
|
||||
}
|
||||
|
||||
_cursorGaps[_cursorGapIndex] = cursorGap;
|
||||
_cursorGapIndex = (_cursorGapIndex + 1) % 60;
|
||||
if(_cursorGapIndex == 0) {
|
||||
_cursorGapFilled = true;
|
||||
}
|
||||
|
||||
if(_cursorGapFilled) {
|
||||
//Once we have 60+ frames worth of data to work with, adjust playback frequency by +/- 0.5%
|
||||
//To speed up or slow down playback in order to reach our latency goal.
|
||||
uint32_t bytesPerSample = _isStereo ? 4 : 2;
|
||||
|
||||
int32_t gapSum = 0;
|
||||
for(int i = 0; i < 60; i++) {
|
||||
gapSum += _cursorGaps[i];
|
||||
}
|
||||
int32_t gapAverage = gapSum / 60;
|
||||
|
||||
_averageLatency = (gapAverage / bytesPerSample) / (double)_sampleRate * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
AudioStatistics BaseSoundManager::GetStatistics()
|
||||
{
|
||||
AudioStatistics stats;
|
||||
stats.AverageLatency = _averageLatency;
|
||||
stats.BufferUnderrunEventCount = _bufferUnderrunEventCount;
|
||||
stats.BufferSize = _bufferSize;
|
||||
return stats;
|
||||
}
|
||||
|
||||
void BaseSoundManager::ResetStats()
|
||||
{
|
||||
_cursorGapIndex = 0;
|
||||
_cursorGapFilled = false;
|
||||
_bufferUnderrunEventCount = 0;
|
||||
_averageLatency = 0;
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
#pragma once
|
||||
#include "../Core/IAudioDevice.h"
|
||||
|
||||
class BaseSoundManager : public IAudioDevice
|
||||
{
|
||||
public:
|
||||
void ProcessLatency(uint32_t readPosition, uint32_t writePosition);
|
||||
AudioStatistics GetStatistics() override;
|
||||
|
||||
protected:
|
||||
bool _isStereo;
|
||||
uint32_t _sampleRate = 0;
|
||||
|
||||
double _averageLatency = 0;
|
||||
uint32_t _bufferSize = 0x10000;
|
||||
uint32_t _bufferUnderrunEventCount = 0;
|
||||
|
||||
int32_t _cursorGaps[60];
|
||||
int32_t _cursorGapIndex = 0;
|
||||
bool _cursorGapFilled = false;
|
||||
|
||||
void ResetStats();
|
||||
};
|
|
@ -1,144 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
#include "BaseVideoFilter.h"
|
||||
#include "MessageManager.h"
|
||||
#include "../Utilities/PNGHelper.h"
|
||||
#include "../Utilities/FolderUtilities.h"
|
||||
#include "StandardController.h"
|
||||
#include "ScaleFilter.h"
|
||||
#include "RotateFilter.h"
|
||||
#include "Console.h"
|
||||
|
||||
BaseVideoFilter::BaseVideoFilter(shared_ptr<Console> console)
|
||||
{
|
||||
_console = console;
|
||||
_overscan = _console->GetSettings()->GetOverscanDimensions();
|
||||
}
|
||||
|
||||
BaseVideoFilter::~BaseVideoFilter()
|
||||
{
|
||||
auto lock = _frameLock.AcquireSafe();
|
||||
if(_outputBuffer) {
|
||||
delete[] _outputBuffer;
|
||||
_outputBuffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void BaseVideoFilter::UpdateBufferSize()
|
||||
{
|
||||
uint32_t newBufferSize = GetFrameInfo().Width*GetFrameInfo().Height;
|
||||
if(_bufferSize != newBufferSize) {
|
||||
_frameLock.Acquire();
|
||||
if(_outputBuffer) {
|
||||
delete[] _outputBuffer;
|
||||
}
|
||||
|
||||
_bufferSize = newBufferSize;
|
||||
_outputBuffer = new uint32_t[newBufferSize];
|
||||
_frameLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
OverscanDimensions BaseVideoFilter::GetOverscan()
|
||||
{
|
||||
return _overscan;
|
||||
}
|
||||
|
||||
void BaseVideoFilter::OnBeforeApplyFilter()
|
||||
{
|
||||
}
|
||||
|
||||
bool BaseVideoFilter::IsOddFrame()
|
||||
{
|
||||
return _isOddFrame;
|
||||
}
|
||||
|
||||
void BaseVideoFilter::SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber)
|
||||
{
|
||||
_frameLock.Acquire();
|
||||
_overscan = _console->GetSettings()->GetOverscanDimensions();
|
||||
_isOddFrame = frameNumber % 2;
|
||||
UpdateBufferSize();
|
||||
OnBeforeApplyFilter();
|
||||
ApplyFilter(ppuOutputBuffer);
|
||||
|
||||
_frameLock.Release();
|
||||
}
|
||||
|
||||
uint32_t* BaseVideoFilter::GetOutputBuffer()
|
||||
{
|
||||
return _outputBuffer;
|
||||
}
|
||||
|
||||
void BaseVideoFilter::TakeScreenshot(VideoFilterType filterType, string filename, std::stringstream *stream, bool rawScreenshot)
|
||||
{
|
||||
uint32_t* pngBuffer;
|
||||
FrameInfo frameInfo;
|
||||
uint32_t* frameBuffer = nullptr;
|
||||
{
|
||||
auto lock = _frameLock.AcquireSafe();
|
||||
if(_bufferSize == 0 || !GetOutputBuffer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
frameBuffer = new uint32_t[_bufferSize];
|
||||
memcpy(frameBuffer, GetOutputBuffer(), _bufferSize * sizeof(frameBuffer[0]));
|
||||
frameInfo = GetFrameInfo();
|
||||
}
|
||||
|
||||
pngBuffer = frameBuffer;
|
||||
|
||||
shared_ptr<RotateFilter> rotateFilter;
|
||||
shared_ptr<ScaleFilter> scaleFilter = ScaleFilter::GetScaleFilter(filterType);
|
||||
if(!rawScreenshot) {
|
||||
uint32_t rotationAngle = _console->GetSettings()->GetScreenRotation();
|
||||
if(rotationAngle > 0) {
|
||||
rotateFilter.reset(new RotateFilter(rotationAngle));
|
||||
pngBuffer = rotateFilter->ApplyFilter(pngBuffer, frameInfo.Width, frameInfo.Height);
|
||||
frameInfo = rotateFilter->GetFrameInfo(frameInfo);
|
||||
}
|
||||
|
||||
if(scaleFilter) {
|
||||
pngBuffer = scaleFilter->ApplyFilter(pngBuffer, frameInfo.Width, frameInfo.Height, _console->GetSettings()->GetPictureSettings().ScanlineIntensity);
|
||||
frameInfo = scaleFilter->GetFrameInfo(frameInfo);
|
||||
}
|
||||
|
||||
VideoHud hud;
|
||||
hud.DrawHud(_console, pngBuffer, frameInfo, _console->GetSettings()->GetOverscanDimensions());
|
||||
}
|
||||
|
||||
if(!filename.empty()) {
|
||||
PNGHelper::WritePNG(filename, pngBuffer, frameInfo.Width, frameInfo.Height);
|
||||
} else {
|
||||
PNGHelper::WritePNG(*stream, pngBuffer, frameInfo.Width, frameInfo.Height);
|
||||
}
|
||||
|
||||
delete[] frameBuffer;
|
||||
}
|
||||
|
||||
void BaseVideoFilter::TakeScreenshot(string romName, VideoFilterType filterType)
|
||||
{
|
||||
string romFilename = FolderUtilities::GetFilename(romName, false);
|
||||
|
||||
int counter = 0;
|
||||
string baseFilename = FolderUtilities::CombinePath(FolderUtilities::GetScreenshotFolder(), romFilename);
|
||||
string ssFilename;
|
||||
while(true) {
|
||||
string counterStr = std::to_string(counter);
|
||||
while(counterStr.length() < 3) {
|
||||
counterStr = "0" + counterStr;
|
||||
}
|
||||
ssFilename = baseFilename + "_" + counterStr + ".png";
|
||||
ifstream file(ssFilename, ios::in);
|
||||
if(file) {
|
||||
file.close();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
|
||||
TakeScreenshot(filterType, ssFilename);
|
||||
|
||||
MessageManager::DisplayMessage("ScreenshotSaved", FolderUtilities::GetFilename(ssFilename, true));
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "../Utilities/SimpleLock.h"
|
||||
#include "EmulationSettings.h"
|
||||
#include "FrameInfo.h"
|
||||
#include "VideoHud.h"
|
||||
|
||||
class Console;
|
||||
|
||||
class BaseVideoFilter
|
||||
{
|
||||
private:
|
||||
uint32_t* _outputBuffer = nullptr;
|
||||
uint32_t _bufferSize = 0;
|
||||
SimpleLock _frameLock;
|
||||
OverscanDimensions _overscan;
|
||||
bool _isOddFrame;
|
||||
|
||||
void UpdateBufferSize();
|
||||
|
||||
protected:
|
||||
shared_ptr<Console> _console;
|
||||
|
||||
virtual void ApplyFilter(uint16_t *ppuOutputBuffer) = 0;
|
||||
virtual void OnBeforeApplyFilter();
|
||||
bool IsOddFrame();
|
||||
|
||||
public:
|
||||
BaseVideoFilter(shared_ptr<Console> console);
|
||||
virtual ~BaseVideoFilter();
|
||||
|
||||
uint32_t* GetOutputBuffer();
|
||||
void SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber);
|
||||
void TakeScreenshot(string romName, VideoFilterType filterType);
|
||||
void TakeScreenshot(VideoFilterType filterType, string filename, std::stringstream *stream = nullptr, bool rawScreenshot = false);
|
||||
|
||||
virtual OverscanDimensions GetOverscan();
|
||||
virtual FrameInfo GetFrameInfo() = 0;
|
||||
};
|
|
@ -1,80 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
#include "BatteryManager.h"
|
||||
#include "VirtualFile.h"
|
||||
#include "../Utilities/FolderUtilities.h"
|
||||
|
||||
void BatteryManager::Initialize(string romName)
|
||||
{
|
||||
_romName = romName;
|
||||
_saveEnabled = true;
|
||||
}
|
||||
|
||||
string BatteryManager::GetBasePath()
|
||||
{
|
||||
return FolderUtilities::CombinePath(FolderUtilities::GetSaveFolder(), _romName);
|
||||
}
|
||||
|
||||
void BatteryManager::SetSaveEnabled(bool enabled)
|
||||
{
|
||||
_saveEnabled = enabled;
|
||||
}
|
||||
|
||||
void BatteryManager::SetBatteryProvider(shared_ptr<IBatteryProvider> provider)
|
||||
{
|
||||
_provider = provider;
|
||||
}
|
||||
|
||||
void BatteryManager::SetBatteryRecorder(shared_ptr<IBatteryRecorder> recorder)
|
||||
{
|
||||
_recorder = recorder;
|
||||
}
|
||||
|
||||
void BatteryManager::SaveBattery(string extension, uint8_t* data, uint32_t length)
|
||||
{
|
||||
if(_saveEnabled) {
|
||||
#ifdef LIBRETRO
|
||||
if(extension == ".sav") {
|
||||
//Disable .sav files for libretro
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
ofstream out(GetBasePath() + extension, ios::binary);
|
||||
if(out) {
|
||||
out.write((char*)data, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vector<uint8_t> BatteryManager::LoadBattery(string extension)
|
||||
{
|
||||
shared_ptr<IBatteryProvider> provider = _provider.lock();
|
||||
|
||||
vector<uint8_t> batteryData;
|
||||
if(provider) {
|
||||
//Used by movie player to provider initial state of ram at startup
|
||||
batteryData = provider->LoadBattery(extension);
|
||||
} else {
|
||||
VirtualFile file = GetBasePath() + extension;
|
||||
if(file.IsValid()) {
|
||||
file.ReadFile(batteryData);
|
||||
}
|
||||
}
|
||||
|
||||
if(!batteryData.empty()) {
|
||||
shared_ptr<IBatteryRecorder> recorder = _recorder.lock();
|
||||
if(recorder) {
|
||||
//Used by movies to record initial state of battery-backed ram at power on
|
||||
recorder->OnLoadBattery(extension, batteryData);
|
||||
}
|
||||
}
|
||||
|
||||
return batteryData;
|
||||
}
|
||||
|
||||
void BatteryManager::LoadBattery(string extension, uint8_t* data, uint32_t length)
|
||||
{
|
||||
vector<uint8_t> batteryData = LoadBattery(extension);
|
||||
memset(data, 0, length);
|
||||
memcpy(data, batteryData.data(), std::min((uint32_t)batteryData.size(), length));
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
|
||||
class IBatteryProvider
|
||||
{
|
||||
public:
|
||||
virtual vector<uint8_t> LoadBattery(string extension) = 0;
|
||||
};
|
||||
|
||||
class IBatteryRecorder
|
||||
{
|
||||
public:
|
||||
virtual void OnLoadBattery(string extension, vector<uint8_t> batteryData) = 0;
|
||||
};
|
||||
|
||||
class BatteryManager
|
||||
{
|
||||
private:
|
||||
string _romName;
|
||||
bool _saveEnabled;
|
||||
string GetBasePath();
|
||||
|
||||
std::weak_ptr<IBatteryProvider> _provider;
|
||||
std::weak_ptr<IBatteryRecorder> _recorder;
|
||||
|
||||
public:
|
||||
void Initialize(string romName);
|
||||
|
||||
void SetSaveEnabled(bool enabled);
|
||||
|
||||
void SetBatteryProvider(shared_ptr<IBatteryProvider> provider);
|
||||
void SetBatteryRecorder(shared_ptr<IBatteryRecorder> recorder);
|
||||
|
||||
void SaveBattery(string extension, uint8_t* data, uint32_t length);
|
||||
|
||||
vector<uint8_t> LoadBattery(string extension);
|
||||
void LoadBattery(string extension, uint8_t* data, uint32_t length);
|
||||
};
|
118
Core/BattleBox.h
118
Core/BattleBox.h
|
@ -1,118 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "../Utilities/FolderUtilities.h"
|
||||
#include "Console.h"
|
||||
#include "BaseControlDevice.h"
|
||||
#include "IBattery.h"
|
||||
#include "BatteryManager.h"
|
||||
|
||||
class BattleBox : public BaseControlDevice, public IBattery
|
||||
{
|
||||
private:
|
||||
static constexpr int FileSize = 0x200;
|
||||
uint8_t _lastWrite = 0;
|
||||
uint8_t _address = 0;
|
||||
uint8_t _chipSelect = 0;
|
||||
uint16_t _data[BattleBox::FileSize/2];
|
||||
uint8_t _output = 0;
|
||||
bool _writeEnabled = false;
|
||||
|
||||
uint8_t _inputBitPosition = 0;
|
||||
uint16_t _inputData = 0;
|
||||
bool _isWrite = false;
|
||||
bool _isRead = false;
|
||||
|
||||
protected:
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseControlDevice::StreamState(saving);
|
||||
ArrayInfo<uint8_t> data{ (uint8_t*)_data, BattleBox::FileSize };
|
||||
Stream(_lastWrite, _address, _chipSelect, _output, _writeEnabled, _inputBitPosition, _isWrite, _isRead, _inputData, data);
|
||||
}
|
||||
|
||||
public:
|
||||
BattleBox(shared_ptr<Console> console) : BaseControlDevice(console, BaseControlDevice::ExpDevicePort)
|
||||
{
|
||||
_console->GetBatteryManager()->LoadBattery(".bb", (uint8_t*)_data, BattleBox::FileSize);
|
||||
}
|
||||
|
||||
~BattleBox()
|
||||
{
|
||||
SaveBattery();
|
||||
}
|
||||
|
||||
void SaveBattery() override
|
||||
{
|
||||
_console->GetBatteryManager()->SaveBattery(".bb", (uint8_t*)_data, BattleBox::FileSize);
|
||||
}
|
||||
|
||||
uint8_t ReadRAM(uint16_t addr) override
|
||||
{
|
||||
if(addr == 0x4017) {
|
||||
if(_lastWrite & 0x01) {
|
||||
_chipSelect ^= 0x01;
|
||||
_inputData = 0;
|
||||
_inputBitPosition = 0;
|
||||
}
|
||||
_output ^= 0x01;
|
||||
|
||||
uint8_t readBit = 0;
|
||||
if(_isRead) {
|
||||
readBit = ((_data[(_chipSelect ? 0x80 : 0) | _address] >> _inputBitPosition) & 0x01) << 3;
|
||||
}
|
||||
uint8_t writeBit = (_output << 4);
|
||||
return readBit | writeBit;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WriteRAM(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if(value & 0x01 && !(_lastWrite & 0x01)) {
|
||||
//Clock
|
||||
_inputData &= ~(1 << _inputBitPosition);
|
||||
_inputData |= (_output << _inputBitPosition);
|
||||
_inputBitPosition++;
|
||||
|
||||
if(_inputBitPosition > 15) {
|
||||
if(_isWrite) {
|
||||
_data[(_chipSelect ? 0x80 : 0) | _address] = _inputData;
|
||||
_isWrite = false;
|
||||
} else {
|
||||
_isRead = false;
|
||||
|
||||
//done reading addr/command or write data
|
||||
uint8_t address = (_inputData & 0x7F);
|
||||
|
||||
uint8_t cmd = ((_inputData & 0x7F00) >> 8) ^ 0x7F;
|
||||
switch(cmd) {
|
||||
case 0x01:
|
||||
//read
|
||||
_address = address;
|
||||
_isRead = true;
|
||||
break;
|
||||
case 0x06:
|
||||
//program
|
||||
if(_writeEnabled) {
|
||||
_address = address;
|
||||
_isWrite = true;
|
||||
}
|
||||
break;
|
||||
case 0x0C:
|
||||
//chip erase
|
||||
if(_writeEnabled) {
|
||||
memset(_data, 0, BattleBox::FileSize);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x0D: break; //busy monitor
|
||||
case 0x09: _writeEnabled = true; break; //erase/write enable
|
||||
case 0x0B: _writeEnabled = false; break; //erase/write disable
|
||||
}
|
||||
}
|
||||
_inputBitPosition = 0;
|
||||
}
|
||||
}
|
||||
_lastWrite = value;
|
||||
}
|
||||
};
|
50
Core/Bb.h
50
Core/Bb.h
|
@ -1,50 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Bb : public BaseMapper
|
||||
{
|
||||
private:
|
||||
uint8_t _prgReg;
|
||||
uint8_t _chrReg;
|
||||
|
||||
protected:
|
||||
uint16_t GetPRGPageSize() override { return 0x2000; }
|
||||
uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
_prgReg = -1;
|
||||
_chrReg = 0;
|
||||
|
||||
SelectPrgPage4x(0, -4);
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
Stream(_prgReg, _chrReg);
|
||||
if(!saving) {
|
||||
UpdateState();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateState()
|
||||
{
|
||||
SetCpuMemoryMapping(0x6000, 0x7FFF, _prgReg, PrgMemoryType::PrgRom);
|
||||
SelectCHRPage(0, _chrReg);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if((addr & 0x9000) == 0x8000 || addr >= 0xF000){
|
||||
//A version of Bubble Bobble expects writes to $F000+ to switch the PRG banks
|
||||
_prgReg = _chrReg = value;
|
||||
} else {
|
||||
//For ProWres
|
||||
_chrReg = value & 0x01;
|
||||
}
|
||||
UpdateState();
|
||||
}
|
||||
};
|
|
@ -1,293 +0,0 @@
|
|||
//NTSC filter based on Bisqwit's code/algorithm
|
||||
//As described here:
|
||||
//http://forums.nesdev.com/viewtopic.php?p=172329
|
||||
#include "stdafx.h"
|
||||
#include <cmath>
|
||||
#include "BisqwitNtscFilter.h"
|
||||
#include "PPU.h"
|
||||
#include "EmulationSettings.h"
|
||||
#include "Console.h"
|
||||
|
||||
BisqwitNtscFilter::BisqwitNtscFilter(shared_ptr<Console> console, int resDivider) : BaseVideoFilter(console)
|
||||
{
|
||||
_resDivider = resDivider;
|
||||
_stopThread = false;
|
||||
_workDone = false;
|
||||
|
||||
const int8_t signalLumaLow[4] = { -29, -15, 22, 71 };
|
||||
const int8_t signalLumaHigh[4] = { 32, 66, 105, 105 };
|
||||
|
||||
//Precalculate the low and high signal chosen for each 64 base colors
|
||||
for(int i = 0; i <= 0x3F; i++) {
|
||||
int r = (i & 0x0F) >= 0x0E ? 0x1D : i;
|
||||
|
||||
int m = signalLumaLow[r / 0x10];
|
||||
int q = signalLumaHigh[r / 0x10];
|
||||
if((r & 0x0F) == 13) {
|
||||
q = m;
|
||||
} else if((r & 0x0F) == 0) {
|
||||
m = q;
|
||||
}
|
||||
_signalLow[i] = m;
|
||||
_signalHigh[i] = q;
|
||||
}
|
||||
|
||||
_extraThread = std::thread([=]() {
|
||||
//Worker thread to improve decode speed
|
||||
while(!_stopThread) {
|
||||
_waitWork.Wait();
|
||||
if(_stopThread) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t* outputBuffer = GetOutputBuffer();
|
||||
|
||||
//Adjust outputbuffer to start at the middle of the picture
|
||||
if(_keepVerticalRes) {
|
||||
outputBuffer += GetOverscan().GetScreenWidth() * 8 / _resDivider * (120 - GetOverscan().Top);
|
||||
} else {
|
||||
outputBuffer += GetOverscan().GetScreenWidth() * 64 / _resDivider / _resDivider * (120 - GetOverscan().Top);
|
||||
}
|
||||
|
||||
DecodeFrame(120, 239 - GetOverscan().Bottom, _ppuOutputBuffer, outputBuffer, (IsOddFrame() ? 8 : 0) + 327360);
|
||||
|
||||
_workDone = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BisqwitNtscFilter::~BisqwitNtscFilter()
|
||||
{
|
||||
_stopThread = true;
|
||||
_waitWork.Signal();
|
||||
_extraThread.join();
|
||||
}
|
||||
|
||||
void BisqwitNtscFilter::ApplyFilter(uint16_t *ppuOutputBuffer)
|
||||
{
|
||||
_ppuOutputBuffer = ppuOutputBuffer;
|
||||
|
||||
_workDone = false;
|
||||
_waitWork.Signal();
|
||||
DecodeFrame(GetOverscan().Top, 120, ppuOutputBuffer, GetOutputBuffer(), (IsOddFrame() ? 8 : 0) + GetOverscan().Top*341*8);
|
||||
while(!_workDone) {}
|
||||
}
|
||||
|
||||
FrameInfo BisqwitNtscFilter::GetFrameInfo()
|
||||
{
|
||||
OverscanDimensions overscan = GetOverscan();
|
||||
if(_keepVerticalRes) {
|
||||
return { overscan.GetScreenWidth() * 8 / _resDivider, overscan.GetScreenHeight(), PPU::ScreenWidth, PPU::ScreenHeight, 4 };
|
||||
} else {
|
||||
return { overscan.GetScreenWidth() * 8 / _resDivider, overscan.GetScreenHeight() * 8 / _resDivider, PPU::ScreenWidth, PPU::ScreenHeight, 4 };
|
||||
}
|
||||
}
|
||||
|
||||
void BisqwitNtscFilter::OnBeforeApplyFilter()
|
||||
{
|
||||
PictureSettings pictureSettings = _console->GetSettings()->GetPictureSettings();
|
||||
NtscFilterSettings ntscSettings = _console->GetSettings()->GetNtscFilterSettings();
|
||||
|
||||
_keepVerticalRes = ntscSettings.KeepVerticalResolution;
|
||||
|
||||
const double pi = std::atan(1.0) * 4;
|
||||
int contrast = (int)((pictureSettings.Contrast + 1.0) * (pictureSettings.Contrast + 1.0) * 167941);
|
||||
int saturation = (int)((pictureSettings.Saturation + 1.0) * (pictureSettings.Saturation + 1.0) * 144044);
|
||||
for(int i = 0; i < 27; i++) {
|
||||
_sinetable[i] = (int8_t)(8 * std::sin(i * 2 * pi / 12 + pictureSettings.Hue * pi));
|
||||
}
|
||||
|
||||
_yWidth = (int)(12 + ntscSettings.YFilterLength * 22);
|
||||
_iWidth = (int)(12 + ntscSettings.IFilterLength * 22);
|
||||
_qWidth = (int)(12 + ntscSettings.QFilterLength * 22);
|
||||
|
||||
_y = contrast / _yWidth;
|
||||
|
||||
_ir = (int)(contrast * 1.994681e-6 * saturation / _iWidth);
|
||||
_qr = (int)(contrast * 9.915742e-7 * saturation / _qWidth);
|
||||
|
||||
_ig = (int)(contrast * 9.151351e-8 * saturation / _iWidth);
|
||||
_qg = (int)(contrast * -6.334805e-7 * saturation / _qWidth);
|
||||
|
||||
_ib = (int)(contrast * -1.012984e-6 * saturation / _iWidth);
|
||||
_qb = (int)(contrast * 1.667217e-6 * saturation / _qWidth);
|
||||
}
|
||||
|
||||
void BisqwitNtscFilter::RecursiveBlend(int iterationCount, uint64_t *output, uint64_t *currentLine, uint64_t *nextLine, int pixelsPerCycle, bool verticalBlend)
|
||||
{
|
||||
//Blend 2 pixels at once
|
||||
uint32_t width = GetOverscan().GetScreenWidth() * pixelsPerCycle / 2;
|
||||
|
||||
double scanlineIntensity = 1.0 - _console->GetSettings()->GetPictureSettings().ScanlineIntensity;
|
||||
if(scanlineIntensity < 1.0 && (iterationCount == 2 || _resDivider == 4)) {
|
||||
//Most likely extremely inefficient scanlines, but works
|
||||
for(uint32_t x = 0; x < width; x++) {
|
||||
uint64_t mixed;
|
||||
if(verticalBlend) {
|
||||
mixed = ((((currentLine[x] ^ nextLine[x]) & 0xfefefefefefefefeL) >> 1) + (currentLine[x] & nextLine[x]));
|
||||
} else {
|
||||
mixed = currentLine[x];
|
||||
}
|
||||
|
||||
uint8_t r = (mixed >> 16) & 0xFF, g = (mixed >> 8) & 0xFF, b = mixed & 0xFF;
|
||||
uint8_t r2 = (mixed >> 48) & 0xFF, g2 = (mixed >> 40) & 0xFF, b2 = (mixed >> 32) & 0xFF;
|
||||
r = (uint8_t)(r * scanlineIntensity);
|
||||
g = (uint8_t)(g * scanlineIntensity);
|
||||
b = (uint8_t)(b * scanlineIntensity);
|
||||
r2 = (uint8_t)(r2 * scanlineIntensity);
|
||||
g2 = (uint8_t)(g2 * scanlineIntensity);
|
||||
b2 = (uint8_t)(b2 * scanlineIntensity);
|
||||
|
||||
output[x] = ((uint64_t)r2 << 48) | ((uint64_t)g2 << 40) | ((uint64_t)b2 << 32) | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
} else {
|
||||
if(verticalBlend) {
|
||||
for(uint32_t x = 0; x < width; x++) {
|
||||
output[x] = ((((currentLine[x] ^ nextLine[x]) & 0xfefefefefefefefeL) >> 1) + (currentLine[x] & nextLine[x]));
|
||||
}
|
||||
} else {
|
||||
memcpy(output, currentLine, width * sizeof(uint64_t));
|
||||
}
|
||||
}
|
||||
|
||||
iterationCount /= 2;
|
||||
if(iterationCount > 0) {
|
||||
RecursiveBlend(iterationCount, output - width * iterationCount, currentLine, output, pixelsPerCycle, verticalBlend);
|
||||
RecursiveBlend(iterationCount, output + width * iterationCount, output, nextLine, pixelsPerCycle, verticalBlend);
|
||||
}
|
||||
}
|
||||
|
||||
void BisqwitNtscFilter::GenerateNtscSignal(int8_t *ntscSignal, int &phase, int rowNumber)
|
||||
{
|
||||
for(int x = -_paddingSize; x < 256 + _paddingSize; x++) {
|
||||
uint16_t color = _ppuOutputBuffer[(rowNumber << 8) | (x < 0 ? 0 : (x >= 256 ? 255 : x))];
|
||||
|
||||
int8_t low = _signalLow[color & 0x3F];
|
||||
int8_t high = _signalHigh[color & 0x3F];
|
||||
int8_t emphasis = color >> 6;
|
||||
|
||||
uint16_t phaseBitmask = _bitmaskLut[std::abs(phase - (color & 0x0F)) % 12];
|
||||
|
||||
uint8_t voltage;
|
||||
for(int j = 0; j < 8; j++) {
|
||||
phaseBitmask <<= 1;
|
||||
voltage = high;
|
||||
if(phaseBitmask >= 0x40) {
|
||||
if(phaseBitmask == 0x1000) {
|
||||
phaseBitmask = 1;
|
||||
} else {
|
||||
voltage = low;
|
||||
}
|
||||
}
|
||||
|
||||
if(phaseBitmask & emphasis) {
|
||||
voltage -= voltage / 4;
|
||||
}
|
||||
|
||||
ntscSignal[((x + _paddingSize) << 3) | j] = voltage;
|
||||
}
|
||||
|
||||
phase += _signalsPerPixel;
|
||||
}
|
||||
phase += (341 - 256 - _paddingSize * 2) * _signalsPerPixel;
|
||||
}
|
||||
|
||||
void BisqwitNtscFilter::DecodeFrame(int startRow, int endRow, uint16_t *ppuOutputBuffer, uint32_t* outputBuffer, int startPhase)
|
||||
{
|
||||
int pixelsPerCycle = 8 / _resDivider;
|
||||
int phase = startPhase;
|
||||
constexpr int lineWidth = 256 + _paddingSize * 2;
|
||||
int8_t rowSignal[lineWidth * _signalsPerPixel];
|
||||
uint32_t rowPixelGap = GetOverscan().GetScreenWidth() * pixelsPerCycle;
|
||||
if(!_keepVerticalRes) {
|
||||
rowPixelGap *= pixelsPerCycle;
|
||||
}
|
||||
|
||||
uint32_t* orgBuffer = outputBuffer;
|
||||
|
||||
for(int y = startRow; y <= endRow; y++) {
|
||||
int startCycle = phase % 12;
|
||||
|
||||
//Convert the PPU's output to an NTSC signal
|
||||
GenerateNtscSignal(rowSignal, phase, y);
|
||||
|
||||
//Convert the NTSC signal to RGB
|
||||
NtscDecodeLine(lineWidth * _signalsPerPixel, rowSignal, outputBuffer, (startCycle + 7) % 12);
|
||||
|
||||
outputBuffer += rowPixelGap;
|
||||
}
|
||||
|
||||
if(!_keepVerticalRes) {
|
||||
//Generate the missing vertical lines
|
||||
outputBuffer = orgBuffer;
|
||||
int lastRow = 239 - GetOverscan().Bottom;
|
||||
bool verticalBlend = _console->GetSettings()->GetNtscFilterSettings().VerticalBlend;
|
||||
for(int y = startRow; y <= endRow; y++) {
|
||||
uint64_t* currentLine = (uint64_t*)outputBuffer;
|
||||
uint64_t* nextLine = y == lastRow ? currentLine : (uint64_t*)(outputBuffer + rowPixelGap);
|
||||
uint64_t* buffer = (uint64_t*)(outputBuffer + rowPixelGap / 2);
|
||||
|
||||
RecursiveBlend(4 / _resDivider, buffer, currentLine, nextLine, pixelsPerCycle, verticalBlend);
|
||||
|
||||
outputBuffer += rowPixelGap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NTSC_DecodeLine(Width, Signal, Target, Phase0)
|
||||
*
|
||||
* Convert NES NTSC graphics signal into RGB using integer arithmetics only.
|
||||
*
|
||||
* Width: Number of NTSC signal samples.
|
||||
* For a 256 pixels wide screen, this would be 256*8. 283*8 if you include borders.
|
||||
*
|
||||
* Signal: An array of Width samples.
|
||||
* The following sample values are recognized:
|
||||
* -29 = Luma 0 low 32 = Luma 0 high (-38 and 6 when attenuated)
|
||||
* -15 = Luma 1 low 66 = Luma 1 high (-28 and 31 when attenuated)
|
||||
* 22 = Luma 2 low 105 = Luma 2 high ( -1 and 58 when attenuated)
|
||||
* 71 = Luma 3 low 105 = Luma 3 high ( 34 and 58 when attenuated)
|
||||
* In this scale, sync signal would be -59 and colorburst would be -40 and 19,
|
||||
* but these are not interpreted specially in this function.
|
||||
* The value is calculated from the relative voltage with:
|
||||
* floor((voltage-0.518)*1000/12)-15
|
||||
*
|
||||
* Target: Pointer to a storage for Width RGB32 samples (00rrggbb).
|
||||
* Note that the function will produce a RGB32 value for _every_ half-clock-cycle.
|
||||
* This means 2264 RGB samples if you render 283 pixels per scanline (incl. borders).
|
||||
* The caller can pick and choose those columns they want from the signal
|
||||
* to render the picture at their desired resolution.
|
||||
*
|
||||
* Phase0: An integer in range 0-11 that describes the phase offset into colors on this scanline.
|
||||
* Would be generated from the PPU clock cycle counter at the start of the scanline.
|
||||
* In essence it conveys in one integer the same information that real NTSC signal
|
||||
* would convey in the colorburst period in the beginning of each scanline.
|
||||
*/
|
||||
void BisqwitNtscFilter::NtscDecodeLine(int width, const int8_t* signal, uint32_t* target, int phase0)
|
||||
{
|
||||
auto Read = [=](int pos) -> char { return pos >= 0 ? signal[pos] : 0; };
|
||||
auto Cos = [=](int pos) -> char { return _sinetable[(pos + 36) % 12 + phase0]; };
|
||||
auto Sin = [=](int pos) -> char { return _sinetable[(pos + 36) % 12 + 3 + phase0]; };
|
||||
|
||||
int brightness = (int)(_console->GetSettings()->GetPictureSettings().Brightness * 750);
|
||||
int ysum = brightness, isum = 0, qsum = 0;
|
||||
int offset = _resDivider + 4;
|
||||
int leftOverscan = (GetOverscan().Left + _paddingSize) * 8 + offset;
|
||||
int rightOverscan = width - (GetOverscan().Right + _paddingSize) * 8 + offset;
|
||||
|
||||
for(int s = 0; s < rightOverscan; s++) {
|
||||
ysum += Read(s) - Read(s - _yWidth);
|
||||
isum += Read(s) * Cos(s) - Read(s - _iWidth) * Cos(s - _iWidth);
|
||||
qsum += Read(s) * Sin(s) - Read(s - _qWidth) * Sin(s - _qWidth);
|
||||
|
||||
if(!(s % _resDivider) && s >= leftOverscan) {
|
||||
int r = std::min(255, std::max(0, (ysum*_y + isum*_ir + qsum*_qr) / 65536));
|
||||
int g = std::min(255, std::max(0, (ysum*_y + isum*_ig + qsum*_qg) / 65536));
|
||||
int b = std::min(255, std::max(0, (ysum*_y + isum*_ib + qsum*_qb) / 65536));
|
||||
|
||||
*target = 0xFF000000 | (r << 16) | (g << 8) | b;
|
||||
target++;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
//NTSC filter based on Bisqwit's code/algorithm
|
||||
//As described here:
|
||||
//http://forums.nesdev.com/viewtopic.php?p=172329
|
||||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseVideoFilter.h"
|
||||
#include "../Utilities/AutoResetEvent.h"
|
||||
|
||||
class BisqwitNtscFilter : public BaseVideoFilter
|
||||
{
|
||||
private:
|
||||
const uint16_t _bitmaskLut[12] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200, 0x400, 0x800 };
|
||||
static constexpr int _paddingSize = 6;
|
||||
static constexpr int _signalsPerPixel = 8;
|
||||
static constexpr int _signalWidth = 258;
|
||||
|
||||
std::thread _extraThread;
|
||||
AutoResetEvent _waitWork;
|
||||
atomic<bool> _stopThread;
|
||||
atomic<bool> _workDone;
|
||||
|
||||
bool _keepVerticalRes = false;
|
||||
|
||||
int _resDivider = 1;
|
||||
uint16_t *_ppuOutputBuffer = nullptr;
|
||||
|
||||
/* Ywidth, Iwidth and Qwidth are the filter widths for Y,I,Q respectively.
|
||||
* All widths at 12 produce the best signal quality.
|
||||
* 12,24,24 would be the closest values matching the NTSC spec.
|
||||
* But off-spec values 12,22,26 are used here, to bring forth mild
|
||||
* "chroma dots", an artifacting common with badly tuned TVs.
|
||||
* Larger values = more horizontal blurring.
|
||||
*/
|
||||
int _yWidth, _iWidth, _qWidth;
|
||||
int _y;
|
||||
int _ir, _ig, _ib;
|
||||
int _qr, _qg, _qb;
|
||||
|
||||
//To finetune hue, you would have to recalculate sinetable[]. (Coarse changes can be made with Phase0.)
|
||||
int8_t _sinetable[27]; // 8*sin(x*2pi/12)
|
||||
int8_t _signalLow[0x40];
|
||||
int8_t _signalHigh[0x40];
|
||||
|
||||
void RecursiveBlend(int iterationCount, uint64_t *output, uint64_t *currentLine, uint64_t *nextLine, int pixelsPerCycle, bool verticalBlend);
|
||||
|
||||
void NtscDecodeLine(int width, const int8_t* signal, uint32_t* target, int phase0);
|
||||
|
||||
void GenerateNtscSignal(int8_t *ntscSignal, int &phase, int rowNumber);
|
||||
void DecodeFrame(int startRow, int endRow, uint16_t *ppuOutputBuffer, uint32_t* outputBuffer, int startPhase);
|
||||
void OnBeforeApplyFilter();
|
||||
|
||||
public:
|
||||
BisqwitNtscFilter(shared_ptr<Console> console, int resDivider);
|
||||
virtual ~BisqwitNtscFilter();
|
||||
|
||||
virtual void ApplyFilter(uint16_t *ppuOutputBuffer);
|
||||
virtual FrameInfo GetFrameInfo();
|
||||
};
|
|
@ -1,225 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
#include "ControlManager.h"
|
||||
#include "SystemActionManager.h"
|
||||
#include "FdsSystemActionManager.h"
|
||||
#include "VsSystemActionManager.h"
|
||||
#include "BizhawkMovie.h"
|
||||
#include "VsControlManager.h"
|
||||
#include "Console.h"
|
||||
#include "BatteryManager.h"
|
||||
#include "NotificationManager.h"
|
||||
|
||||
BizhawkMovie::BizhawkMovie(shared_ptr<Console> console)
|
||||
{
|
||||
_console = console;
|
||||
_originalPowerOnState = _console->GetSettings()->GetRamPowerOnState();
|
||||
}
|
||||
|
||||
BizhawkMovie::~BizhawkMovie()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
void BizhawkMovie::Stop()
|
||||
{
|
||||
if(_isPlaying) {
|
||||
MessageManager::DisplayMessage("Movies", "MovieEnded");
|
||||
|
||||
_console->GetNotificationManager()->SendNotification(ConsoleNotificationType::MovieEnded);
|
||||
if(_console->GetSettings()->CheckFlag(EmulationFlags::PauseOnMovieEnd)) {
|
||||
_console->GetSettings()->SetFlags(EmulationFlags::Paused);
|
||||
}
|
||||
|
||||
_console->GetSettings()->SetRamPowerOnState(_originalPowerOnState);
|
||||
_isPlaying = false;
|
||||
}
|
||||
_console->GetControlManager()->UnregisterInputProvider(this);
|
||||
}
|
||||
|
||||
bool BizhawkMovie::SetInput(BaseControlDevice *device)
|
||||
{
|
||||
SystemActionManager* actionManager = dynamic_cast<SystemActionManager*>(device);
|
||||
int32_t pollCounter = _console->GetControlManager()->GetPollCounter();
|
||||
if(actionManager) {
|
||||
if(pollCounter < (int32_t)_systemActionByFrame.size()) {
|
||||
uint32_t systemAction = _systemActionByFrame[pollCounter];
|
||||
if(systemAction & 0x01) {
|
||||
actionManager->SetBit(SystemActionManager::Buttons::PowerButton);
|
||||
}
|
||||
if(systemAction & 0x02) {
|
||||
actionManager->SetBit(SystemActionManager::Buttons::ResetButton);
|
||||
}
|
||||
|
||||
VsSystemActionManager* vsActionManager = dynamic_cast<VsSystemActionManager*>(device);
|
||||
if(vsActionManager) {
|
||||
if(systemAction & 0x04) {
|
||||
actionManager->SetBit(VsSystemActionManager::VsButtons::InsertCoin1);
|
||||
}
|
||||
if(systemAction & 0x08) {
|
||||
actionManager->SetBit(VsSystemActionManager::VsButtons::InsertCoin2);
|
||||
}
|
||||
if(systemAction & 0x10) {
|
||||
actionManager->SetBit(VsSystemActionManager::VsButtons::ServiceButton);
|
||||
}
|
||||
}
|
||||
|
||||
FdsSystemActionManager* fdsActionManager = dynamic_cast<FdsSystemActionManager*>(device);
|
||||
if(fdsActionManager) {
|
||||
//FDS timings between NesHawk & Mesen are currently significantly different
|
||||
//So FDS games will always go out of sync
|
||||
if(systemAction & 0x04) {
|
||||
fdsActionManager->SetBit(FdsSystemActionManager::FdsButtons::EjectDiskButton);
|
||||
}
|
||||
|
||||
if(systemAction >= 8) {
|
||||
systemAction >>= 3;
|
||||
uint32_t diskNumber = 0;
|
||||
while(!(systemAction & 0x01)) {
|
||||
systemAction >>= 1;
|
||||
diskNumber++;
|
||||
}
|
||||
|
||||
fdsActionManager->SetBit(FdsSystemActionManager::FdsButtons::InsertDisk1 + diskNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int port = device->GetPort();
|
||||
StandardController* controller = dynamic_cast<StandardController*>(device);
|
||||
if(controller) {
|
||||
if(pollCounter < (int32_t)_dataByFrame[port].size()) {
|
||||
controller->SetTextState(_dataByFrame[port][pollCounter]);
|
||||
} else {
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BizhawkMovie::InitializeGameData(ZipReader &reader)
|
||||
{
|
||||
stringstream fileData;
|
||||
if(!reader.GetStream("Header.txt", fileData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_console->GetControlManager()->SetPollCounter(0);
|
||||
|
||||
while(!fileData.eof()) {
|
||||
string line;
|
||||
std::getline(fileData, line);
|
||||
if(line.compare(0, 4, "SHA1", 4) == 0) {
|
||||
if(line.size() >= 45) {
|
||||
HashInfo hashInfo;
|
||||
hashInfo.Sha1 = line.substr(5, 40);
|
||||
if(_console->LoadMatchingRom("", hashInfo)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if(line.compare(0, 3, "MD5", 3) == 0) {
|
||||
if(line.size() >= 36) {
|
||||
HashInfo hashInfo;
|
||||
hashInfo.PrgChrMd5 = line.substr(4, 32);
|
||||
std::transform(hashInfo.PrgChrMd5.begin(), hashInfo.PrgChrMd5.end(), hashInfo.PrgChrMd5.begin(), ::toupper);
|
||||
if(_console->LoadMatchingRom("", hashInfo)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BizhawkMovie::InitializeInputData(ZipReader &reader)
|
||||
{
|
||||
stringstream inputData;
|
||||
if(!reader.GetStream("Input Log.txt", inputData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int systemActionCount = 2;
|
||||
shared_ptr<FdsSystemActionManager> fdsActionManager = _console->GetSystemActionManager<FdsSystemActionManager>();
|
||||
if(fdsActionManager) {
|
||||
//Eject disk + Insert Disk #XX
|
||||
systemActionCount += fdsActionManager->GetSideCount() + 1;
|
||||
} else {
|
||||
shared_ptr<VsSystemActionManager> vsActionManager = _console->GetSystemActionManager<VsSystemActionManager>();
|
||||
if(vsActionManager) {
|
||||
//Insert coin 1, 2 + service button
|
||||
systemActionCount += 3;
|
||||
}
|
||||
}
|
||||
|
||||
while(!inputData.eof()) {
|
||||
string line;
|
||||
std::getline(inputData, line);
|
||||
|
||||
if(line.size() > 0 && line[0] == '|') {
|
||||
line.erase(std::remove(line.begin(), line.end(), '|'), line.end());
|
||||
line = line.substr(0, line.size() - 1);
|
||||
|
||||
//Read power/reset/FDS/VS/etc. commands
|
||||
uint32_t systemAction = 0;
|
||||
for(int i = 0; i < systemActionCount; i++) {
|
||||
if(line[i] != '.') {
|
||||
systemAction |= (1 << i);
|
||||
}
|
||||
}
|
||||
_systemActionByFrame.push_back(systemAction);
|
||||
|
||||
line = line.substr(systemActionCount);
|
||||
int port = 0;
|
||||
while(line.size() >= 8) {
|
||||
_dataByFrame[port].push_back(line.substr(0, 8));
|
||||
line = line.substr(8);
|
||||
port++;
|
||||
}
|
||||
while(port < 4) {
|
||||
_dataByFrame[port].push_back("........");
|
||||
port++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _dataByFrame[0].size() > 0;
|
||||
}
|
||||
|
||||
bool BizhawkMovie::Play(VirtualFile &file)
|
||||
{
|
||||
_console->Pause();
|
||||
ZipReader reader;
|
||||
|
||||
std::stringstream ss;
|
||||
file.ReadFile(ss);
|
||||
|
||||
reader.LoadArchive(ss);
|
||||
|
||||
_console->GetNotificationManager()->RegisterNotificationListener(shared_from_this());
|
||||
_console->GetSettings()->SetRamPowerOnState(RamPowerOnState::AllOnes);
|
||||
_console->GetBatteryManager()->SetBatteryProvider(shared_from_this());
|
||||
if(InitializeInputData(reader) && InitializeGameData(reader)) {
|
||||
//NesHawk initializes memory to 1s
|
||||
_isPlaying = true;
|
||||
}
|
||||
_console->Resume();
|
||||
return _isPlaying;
|
||||
}
|
||||
|
||||
bool BizhawkMovie::IsPlaying()
|
||||
{
|
||||
return _isPlaying;
|
||||
}
|
||||
|
||||
void BizhawkMovie::ProcessNotification(ConsoleNotificationType type, void* parameter)
|
||||
{
|
||||
if(type == ConsoleNotificationType::GameLoaded) {
|
||||
_console->GetControlManager()->RegisterInputProvider(this);
|
||||
}
|
||||
}
|
||||
|
||||
vector<uint8_t> BizhawkMovie::LoadBattery(string extension)
|
||||
{
|
||||
return vector<uint8_t>();
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "MovieManager.h"
|
||||
#include "../Utilities/ZipReader.h"
|
||||
#include "INotificationListener.h"
|
||||
#include "BatteryManager.h"
|
||||
|
||||
class VirtualFile;
|
||||
class Console;
|
||||
|
||||
class BizhawkMovie : public IMovie, public INotificationListener, public IBatteryProvider, public std::enable_shared_from_this<BizhawkMovie>
|
||||
{
|
||||
private:
|
||||
bool InitializeGameData(ZipReader &reader);
|
||||
bool InitializeInputData(ZipReader &reader);
|
||||
void Stop();
|
||||
|
||||
protected:
|
||||
shared_ptr<Console> _console;
|
||||
|
||||
vector<uint32_t> _systemActionByFrame;
|
||||
vector<string> _dataByFrame[4];
|
||||
bool _isPlaying = false;
|
||||
RamPowerOnState _originalPowerOnState;
|
||||
|
||||
public:
|
||||
BizhawkMovie(shared_ptr<Console>);
|
||||
virtual ~BizhawkMovie();
|
||||
|
||||
bool SetInput(BaseControlDevice *device) override;
|
||||
bool Play(VirtualFile &file) override;
|
||||
bool IsPlaying() override;
|
||||
|
||||
// Inherited via INotificationListener
|
||||
virtual void ProcessNotification(ConsoleNotificationType type, void * parameter) override;
|
||||
virtual vector<uint8_t> LoadBattery(string extension) override;
|
||||
};
|
|
@ -1,28 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Bmc11160 : public BaseMapper
|
||||
{
|
||||
protected:
|
||||
uint16_t GetPRGPageSize() override { return 0x8000; }
|
||||
uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
}
|
||||
|
||||
void Reset(bool softReset) override
|
||||
{
|
||||
BaseMapper::Reset(softReset);
|
||||
WriteRegister(0x8000, 0);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
uint8_t bank = (value >> 4) & 0x07;
|
||||
SelectPRGPage(0, bank);
|
||||
SelectCHRPage(0, (bank << 2) | (value & 0x03));
|
||||
SetMirroringType(value & 0x80 ? MirroringType::Vertical : MirroringType::Horizontal);
|
||||
}
|
||||
};
|
|
@ -1,50 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Bmc12in1 : public BaseMapper
|
||||
{
|
||||
private:
|
||||
uint8_t _regs[2];
|
||||
uint8_t _mode;
|
||||
|
||||
protected:
|
||||
uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
uint16_t GetCHRPageSize() override { return 0x1000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
_regs[0] = _regs[1] = 0;
|
||||
_mode = 0;
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
Stream(_regs[0], _regs[1], _mode);
|
||||
}
|
||||
|
||||
void UpdateState()
|
||||
{
|
||||
uint8_t bank = (_mode & 0x03) << 3;
|
||||
SelectCHRPage(0, (_regs[0] >> 3) | (bank << 2));
|
||||
SelectCHRPage(1, (_regs[1] >> 3) | (bank << 2));
|
||||
if(_mode & 0x08) {
|
||||
SelectPrgPage2x(0, bank | (_regs[0] & 0x06));
|
||||
} else {
|
||||
SelectPRGPage(0, bank | (_regs[0] & 0x07));
|
||||
SelectPRGPage(1, bank | 0x07);
|
||||
}
|
||||
SetMirroringType(_mode & 0x04 ? MirroringType::Horizontal : MirroringType::Vertical);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
switch(addr & 0xE000) {
|
||||
case 0xA000: _regs[0] = value; UpdateState(); break;
|
||||
case 0xC000: _regs[1] = value; UpdateState(); break;
|
||||
case 0xE000: _mode = value & 0x0F; UpdateState(); break;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,23 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Bmc190in1 : public BaseMapper
|
||||
{
|
||||
protected:
|
||||
uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
WriteRegister(0x8000, 0);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
SelectPRGPage(0, (value >> 2) & 0x07);
|
||||
SelectPRGPage(1, (value >> 2) & 0x07);
|
||||
SelectCHRPage(0, (value >> 2) & 0x07);
|
||||
SetMirroringType(value & 0x01 ? MirroringType::Horizontal : MirroringType::Vertical);
|
||||
}
|
||||
};
|
|
@ -1,68 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Bmc235 : public BaseMapper
|
||||
{
|
||||
private:
|
||||
bool _openBus = false;
|
||||
protected:
|
||||
virtual uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
virtual uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
SelectPrgPage2x(0, 0);
|
||||
SelectCHRPage(0, 0);
|
||||
}
|
||||
|
||||
void Reset(bool softReset) override
|
||||
{
|
||||
SelectPrgPage2x(0, 0);
|
||||
_openBus = false;
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
Stream(_openBus);
|
||||
if(!saving && _openBus) {
|
||||
RemoveCpuMemoryMapping(0x8000, 0xFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
SetMirroringType((addr & 0x0400) ? MirroringType::ScreenAOnly : (addr & 0x2000) ? MirroringType::Horizontal : MirroringType::Vertical);
|
||||
|
||||
const uint8_t config[4][4][2] = {
|
||||
{ { 0x00, 0 }, { 0x00, 1 }, { 0x00, 1 }, { 0x00, 1 } },
|
||||
{ { 0x00, 0 }, { 0x00, 1 }, { 0x20, 0 }, { 0x00, 1 } },
|
||||
{ { 0x00, 0 }, { 0x00, 1 }, { 0x20, 0 }, { 0x40, 0 } },
|
||||
{ { 0x00, 0 }, { 0x20, 0 }, { 0x40, 0 }, { 0x60, 0 } }
|
||||
};
|
||||
|
||||
uint8_t mode;
|
||||
switch(GetPRGPageCount()) {
|
||||
case 64: mode = 0; break;
|
||||
case 128: mode = 1; break;
|
||||
case 256: mode = 2; break;
|
||||
default: mode = 3; break;
|
||||
};
|
||||
|
||||
uint8_t bank = config[mode][addr >> 8 & 0x03][0] | (addr & 0x1F);
|
||||
|
||||
_openBus = false;
|
||||
if(config[mode][addr >> 8 & 0x03][1]) {
|
||||
//Open bus
|
||||
_openBus = true;
|
||||
RemoveCpuMemoryMapping(0x8000, 0xFFFF);
|
||||
} else if(addr & 0x800) {
|
||||
bank = (bank << 1) | (addr >> 12 & 0x01);
|
||||
SelectPRGPage(0, bank);
|
||||
SelectPRGPage(1, bank);
|
||||
} else {
|
||||
SelectPrgPage2x(0, bank << 1);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,26 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Bmc255 : public BaseMapper
|
||||
{
|
||||
protected:
|
||||
virtual uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
virtual uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
WriteRegister(0x8000, 0);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
uint8_t prgBit = (addr & 0x1000) ? 0 : 1;
|
||||
uint8_t bank = ((addr >> 8) & 0x40) | ((addr >> 6) & 0x3F);
|
||||
|
||||
SelectPRGPage(0, bank & ~prgBit);
|
||||
SelectPRGPage(1, bank | prgBit);
|
||||
SelectCHRPage(0, ((addr >> 8) & 0x40) | (addr & 0x3F));
|
||||
SetMirroringType(addr & 0x2000 ? MirroringType::Horizontal : MirroringType::Vertical);
|
||||
}
|
||||
};
|
59
Core/Bmc51.h
59
Core/Bmc51.h
|
@ -1,59 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Bmc51 : public BaseMapper
|
||||
{
|
||||
private:
|
||||
uint8_t _bank;
|
||||
uint8_t _mode;
|
||||
|
||||
protected:
|
||||
virtual uint16_t GetPRGPageSize() override { return 0x2000; }
|
||||
virtual uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
uint16_t RegisterStartAddress() override { return 0x6000; }
|
||||
uint16_t RegisterEndAddress() override { return 0xFFFF; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
_bank = 0;
|
||||
_mode = 1;
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
Stream(_bank, _mode);
|
||||
if(!saving) {
|
||||
UpdateState();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateState()
|
||||
{
|
||||
if(_mode & 0x01) {
|
||||
SelectPrgPage4x(0, _bank << 2);
|
||||
SetCpuMemoryMapping(0x6000, 0x7FFF, (0x23 | (_bank << 2)), PrgMemoryType::PrgRom);
|
||||
} else {
|
||||
SelectPrgPage2x(0, (_bank << 2) | _mode);
|
||||
SelectPrgPage2x(1, _bank << 2 | 0x0E);
|
||||
SetCpuMemoryMapping(0x6000, 0x7FFF, (0x2F | (_bank << 2)), PrgMemoryType::PrgRom);
|
||||
}
|
||||
|
||||
SetMirroringType(_mode == 0x03 ? MirroringType::Horizontal : MirroringType::Vertical);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if(addr <= 0x7FFF) {
|
||||
_mode = ((value >> 3) & 0x02) | ((value >> 1) & 0x01);
|
||||
} else if(addr >= 0xC000 && addr <= 0xDFFF) {
|
||||
_bank = value & 0x0F;
|
||||
_mode = ((value >> 3) & 0x02) | (_mode & 0x01);
|
||||
} else {
|
||||
_bank = value & 0x0F;
|
||||
}
|
||||
UpdateState();
|
||||
}
|
||||
};
|
|
@ -1,83 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Bmc60311C : public BaseMapper
|
||||
{
|
||||
private:
|
||||
uint8_t _innerPrg;
|
||||
uint8_t _outerPrg;
|
||||
uint8_t _mode;
|
||||
|
||||
protected:
|
||||
uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
uint16_t RegisterStartAddress() override { return 0x6000; }
|
||||
uint16_t RegisterEndAddress() override { return 0xFFFF; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
_innerPrg = 0;
|
||||
_outerPrg = 0;
|
||||
_mode = 0;
|
||||
|
||||
UpdateState();
|
||||
SelectCHRPage(0, 0);
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
Stream(_innerPrg, _outerPrg, _mode);
|
||||
}
|
||||
|
||||
void UpdateState()
|
||||
{
|
||||
uint8_t page = _outerPrg | ((_mode & 0x04) ? 0 : _innerPrg);
|
||||
|
||||
switch(_mode & 0x03) {
|
||||
case 0:
|
||||
//0: NROM-128: Same inner/outer 16 KiB bank at CPU $8000-$BFFF and $C000-$FFFF
|
||||
SelectPRGPage(0, page);
|
||||
SelectPRGPage(1, page);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
//1: NROM-256: 32 kiB bank at CPU $8000-$FFFF (Selected inner/outer bank SHR 1)
|
||||
SelectPrgPage2x(0, page & 0xFE);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
//2: UNROM: Inner/outer bank at CPU $8000-BFFF, fixed inner bank 7 within outer bank at $C000-$FFFF
|
||||
SelectPRGPage(0, page);
|
||||
SelectPRGPage(1, _outerPrg | 7);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
//Unknown
|
||||
break;
|
||||
}
|
||||
|
||||
SetMirroringType(_mode & 0x08 ? MirroringType::Horizontal : MirroringType::Vertical);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if(addr >= 0x8000) {
|
||||
_innerPrg = value & 0x07;
|
||||
UpdateState();
|
||||
} else {
|
||||
switch(addr & 0xE001) {
|
||||
case 0x6000:
|
||||
_mode = value & 0x0F;
|
||||
UpdateState();
|
||||
break;
|
||||
|
||||
case 0x6001:
|
||||
_outerPrg = value;
|
||||
UpdateState();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
48
Core/Bmc63.h
48
Core/Bmc63.h
|
@ -1,48 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Bmc63 : public BaseMapper
|
||||
{
|
||||
private:
|
||||
bool _openBus;
|
||||
|
||||
protected:
|
||||
virtual uint16_t GetPRGPageSize() override { return 0x2000; }
|
||||
virtual uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
WriteRegister(0x8000, 0);
|
||||
}
|
||||
|
||||
void Reset(bool softReset) override
|
||||
{
|
||||
_openBus = false;
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
Stream(_openBus);
|
||||
if(!saving && _openBus) {
|
||||
RemoveCpuMemoryMapping(0x8000, 0xBFFF);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
_openBus = ((addr & 0x0300) == 0x0300);
|
||||
|
||||
if(_openBus) {
|
||||
RemoveCpuMemoryMapping(0x8000, 0xBFFF);
|
||||
} else {
|
||||
SelectPRGPage(0, (addr >> 1 & 0x1FC) | ((addr & 0x2) ? 0x0 : (addr >> 1 & 0x2) | 0x0));
|
||||
SelectPRGPage(1, (addr >> 1 & 0x1FC) | ((addr & 0x2) ? 0x1 : (addr >> 1 & 0x2) | 0x1));
|
||||
}
|
||||
SelectPRGPage(2, (addr >> 1 & 0x1FC) | ((addr & 0x2) ? 0x2 : (addr >> 1 & 0x2) | 0x0));
|
||||
SelectPRGPage(3, (addr & 0x800) ? ((addr & 0x07C) | ((addr & 0x06) ? 0x03 : 0x01)) : ((addr >> 1 & 0x01FC) | ((addr & 0x02) ? 0x03 : ((addr >> 1 & 0x02) | 0x01))));
|
||||
|
||||
SetMirroringType(addr & 0x01 ? MirroringType::Horizontal : MirroringType::Vertical);
|
||||
}
|
||||
};
|
|
@ -1,63 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Bmc64in1NoRepeat : public BaseMapper
|
||||
{
|
||||
private:
|
||||
uint8_t _regs[4];
|
||||
|
||||
protected:
|
||||
uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
AddRegisterRange(0x5000, 0x5003, MemoryOperation::Write);
|
||||
}
|
||||
|
||||
void Reset(bool softReset) override
|
||||
{
|
||||
BaseMapper::Reset(softReset);
|
||||
|
||||
_regs[0] = 0x80;
|
||||
_regs[1] = 0x43;
|
||||
_regs[2] = _regs[3] = 0;
|
||||
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
Stream(_regs[0], _regs[1], _regs[2], _regs[3]);
|
||||
}
|
||||
|
||||
void UpdateState()
|
||||
{
|
||||
if(_regs[0] & 0x80) {
|
||||
if(_regs[1] & 0x80) {
|
||||
SelectPrgPage2x(0, (_regs[1] & 0x1F) << 1);
|
||||
} else {
|
||||
int bank = ((_regs[1] & 0x1F) << 1) | ((_regs[1] >> 6) & 0x01);
|
||||
SelectPRGPage(0, bank);
|
||||
SelectPRGPage(1, bank);
|
||||
}
|
||||
} else {
|
||||
SelectPRGPage(1, ((_regs[1] & 0x1F) << 1) | ((_regs[1] >> 6) & 0x01));
|
||||
}
|
||||
SetMirroringType(_regs[0] & 0x20 ? MirroringType::Horizontal : MirroringType::Vertical);
|
||||
SelectCHRPage(0, (_regs[2] << 2) | ((_regs[0] >> 1) & 0x03));
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if(addr < 0x8000) {
|
||||
_regs[addr & 0x03] = value;
|
||||
} else {
|
||||
_regs[3] = value;
|
||||
}
|
||||
|
||||
UpdateState();
|
||||
}
|
||||
};
|
|
@ -1,97 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Bmc70in1 : public BaseMapper
|
||||
{
|
||||
private:
|
||||
uint8_t _bankMode;
|
||||
uint8_t _outerBank;
|
||||
uint8_t _prgReg;
|
||||
uint8_t _chrReg;
|
||||
bool _useOuterBank;
|
||||
|
||||
protected:
|
||||
uint32_t GetDipSwitchCount() override { return 4; }
|
||||
uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
bool AllowRegisterRead() override { return true; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
_prgReg = 0;
|
||||
_chrReg = 0;
|
||||
|
||||
if(HasChrRom()) {
|
||||
_useOuterBank = false;
|
||||
} else {
|
||||
_useOuterBank = true;
|
||||
}
|
||||
|
||||
SelectCHRPage(0, 0);
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
Stream(_bankMode, _outerBank, _prgReg, _chrReg);
|
||||
}
|
||||
|
||||
void Reset(bool softReset) override
|
||||
{
|
||||
BaseMapper::Reset(softReset);
|
||||
|
||||
_bankMode = 0;
|
||||
_outerBank = 0;
|
||||
}
|
||||
|
||||
void UpdateState()
|
||||
{
|
||||
switch(_bankMode) {
|
||||
case 0x00: case 0x10:
|
||||
SelectPRGPage(0, _outerBank | _prgReg);
|
||||
SelectPRGPage(1, _outerBank | 7);
|
||||
break;
|
||||
|
||||
case 0x20:
|
||||
SelectPrgPage2x(0, (_outerBank | _prgReg) & 0xFE);
|
||||
break;
|
||||
|
||||
case 0x30:
|
||||
SelectPRGPage(0, _outerBank | _prgReg);
|
||||
SelectPRGPage(1, _outerBank | _prgReg);
|
||||
break;
|
||||
}
|
||||
|
||||
if(!_useOuterBank) {
|
||||
SelectCHRPage(0, _chrReg);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ReadRegister(uint16_t addr) override
|
||||
{
|
||||
if(_bankMode == 0x10) {
|
||||
return InternalReadRam((addr & 0xFFF0) | GetDipSwitches());
|
||||
} else {
|
||||
return InternalReadRam(addr);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if(addr & 0x4000) {
|
||||
_bankMode = addr & 0x30;
|
||||
_prgReg = addr & 0x07;
|
||||
} else {
|
||||
SetMirroringType(addr & 0x20 ? MirroringType::Horizontal : MirroringType::Vertical);
|
||||
if(_useOuterBank) {
|
||||
_outerBank = (addr & 0x03) << 3;
|
||||
} else {
|
||||
_chrReg = addr & 0x07;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateState();
|
||||
}
|
||||
};
|
|
@ -1,54 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Bmc80013B : public BaseMapper
|
||||
{
|
||||
private:
|
||||
uint8_t _regs[2];
|
||||
uint8_t _mode;
|
||||
|
||||
protected:
|
||||
uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
SelectCHRPage(0, 0);
|
||||
}
|
||||
|
||||
void Reset(bool softReset) override
|
||||
{
|
||||
_regs[0] = _regs[1] = _mode = 0;
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
Stream(_regs[0], _regs[1], _mode);
|
||||
}
|
||||
|
||||
void UpdateState()
|
||||
{
|
||||
if(_mode & 0x02) {
|
||||
SelectPRGPage(0, (_regs[0] & 0x0F) | (_regs[1] & 0x70));
|
||||
} else {
|
||||
SelectPRGPage(0, _regs[0] & 0x03);
|
||||
}
|
||||
|
||||
SelectPRGPage(1, _regs[1] & 0x7F);
|
||||
SetMirroringType(_regs[0] & 0x10 ? MirroringType::Vertical : MirroringType::Horizontal);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
uint8_t reg = (addr >> 13) & 0x03;
|
||||
if(reg == 0) {
|
||||
_regs[0] = value;
|
||||
} else {
|
||||
_regs[1] = value;
|
||||
_mode = reg;
|
||||
}
|
||||
UpdateState();
|
||||
}
|
||||
};
|
|
@ -1,33 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Bmc810544CA1 : public BaseMapper
|
||||
{
|
||||
protected:
|
||||
uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
}
|
||||
|
||||
void Reset(bool softReset) override
|
||||
{
|
||||
BaseMapper::Reset(softReset);
|
||||
WriteRegister(0x8000, 0);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
uint16_t bank = (addr >> 6) & 0xFFFE;
|
||||
if(addr & 0x40) {
|
||||
SelectPrgPage2x(0, bank);
|
||||
} else {
|
||||
SelectPRGPage(0, bank | ((addr >> 5) & 0x01));
|
||||
SelectPRGPage(1, bank | ((addr >> 5) & 0x01));
|
||||
}
|
||||
SelectCHRPage(0, addr & 0x0F);
|
||||
SetMirroringType(addr & 0x10 ? MirroringType::Horizontal : MirroringType::Vertical);
|
||||
}
|
||||
};
|
|
@ -1,63 +0,0 @@
|
|||
#pragma once
|
||||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Bmc8157 : public BaseMapper
|
||||
{
|
||||
private:
|
||||
uint16_t _lastAddr;
|
||||
|
||||
protected:
|
||||
uint32_t GetDipSwitchCount() override { return 1; }
|
||||
uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
_lastAddr = 0;
|
||||
UpdateState();
|
||||
SelectCHRPage(0, 0);
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
Stream(_lastAddr);
|
||||
|
||||
if(!saving) {
|
||||
UpdateState();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateState()
|
||||
{
|
||||
uint8_t innerPrg0 = (_lastAddr >> 2) & 0x07;
|
||||
uint8_t innerPrg1 = ((_lastAddr >> 7) & 0x01) | ((_lastAddr >> 8) & 0x02);
|
||||
uint8_t outer128Prg = (_lastAddr >> 5) & 0x03;
|
||||
uint8_t outer512Prg = (_lastAddr >> 8) & 0x01;
|
||||
|
||||
int baseBank;
|
||||
if(innerPrg1 == 0) {
|
||||
baseBank = 0;
|
||||
} else if(innerPrg1 == 1) {
|
||||
baseBank = innerPrg0;
|
||||
} else {
|
||||
baseBank = 7;
|
||||
}
|
||||
|
||||
if(outer512Prg && _prgSize <= 1024 * 512 && GetDipSwitches() != 0) {
|
||||
RemoveCpuMemoryMapping(0x8000, 0xFFFF);
|
||||
} else {
|
||||
SelectPRGPage(0, (outer512Prg << 6) | (outer128Prg << 3) | innerPrg0);
|
||||
SelectPRGPage(1, (outer512Prg << 6) | (outer128Prg << 3) | baseBank);
|
||||
SetMirroringType(_lastAddr & 0x02 ? MirroringType::Horizontal : MirroringType::Vertical);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
_lastAddr = addr;
|
||||
UpdateState();
|
||||
}
|
||||
};
|
|
@ -1,59 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "MMC3.h"
|
||||
|
||||
class Bmc830118C : public MMC3
|
||||
{
|
||||
private:
|
||||
uint8_t _reg;
|
||||
|
||||
protected:
|
||||
void InitMapper() override
|
||||
{
|
||||
_reg = 0;
|
||||
MMC3::InitMapper();
|
||||
AddRegisterRange(0x6800, 0x68FF, MemoryOperation::Write);
|
||||
}
|
||||
|
||||
void Reset(bool softReset) override
|
||||
{
|
||||
_reg = 0;
|
||||
MMC3::Reset(softReset);
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
MMC3::StreamState(saving);
|
||||
Stream(_reg);
|
||||
}
|
||||
|
||||
void SelectCHRPage(uint16_t slot, uint16_t page, ChrMemoryType memoryType = ChrMemoryType::Default) override
|
||||
{
|
||||
MMC3::SelectCHRPage(slot, ((_reg & 0x0C) << 5) | (page & 0x7F), memoryType);
|
||||
}
|
||||
|
||||
void SelectPRGPage(uint16_t slot, uint16_t page, PrgMemoryType memoryType = PrgMemoryType::PrgRom) override
|
||||
{
|
||||
if((_reg & 0x0C) == 0x0C) {
|
||||
if(slot == 0) {
|
||||
MMC3::SelectPRGPage(0, ((_reg & 0x0C) << 2) | (page & 0x0F));
|
||||
MMC3::SelectPRGPage(2, 0x32 | (page & 0x0F));
|
||||
} else if(slot == 1) {
|
||||
MMC3::SelectPRGPage(1, ((_reg & 0x0C) << 2) | (page & 0x0F));
|
||||
MMC3::SelectPRGPage(3, 0x32 | (page & 0x0F));
|
||||
}
|
||||
} else {
|
||||
MMC3::SelectPRGPage(slot, ((_reg & 0x0C) << 2) | (page & 0x0F));
|
||||
}
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if(addr < 0x8000) {
|
||||
_reg = value;
|
||||
UpdateState();
|
||||
} else {
|
||||
MMC3::WriteRegister(addr, value);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,54 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Bmc830425C4391T : public BaseMapper
|
||||
{
|
||||
private:
|
||||
uint8_t _innerReg;
|
||||
uint8_t _outerReg;
|
||||
uint8_t _prgMode;
|
||||
|
||||
protected:
|
||||
virtual uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
virtual uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
_innerReg = 0;
|
||||
_outerReg = 0;
|
||||
_prgMode = 0;
|
||||
|
||||
SelectCHRPage(0, 0);
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
Stream(_innerReg, _outerReg, _prgMode);
|
||||
}
|
||||
|
||||
void UpdateState()
|
||||
{
|
||||
if(_prgMode) {
|
||||
//UNROM mode
|
||||
SelectPRGPage(0, (_innerReg & 0x07) | (_outerReg << 3));
|
||||
SelectPRGPage(1, 0x07 | (_outerReg << 3));
|
||||
} else {
|
||||
//UOROM mode
|
||||
SelectPRGPage(0, _innerReg | (_outerReg << 3));
|
||||
SelectPRGPage(1, 0x0F | (_outerReg << 3));
|
||||
}
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
_innerReg = value & 0x0F;
|
||||
if((addr & 0xFFE0) == 0xF0E0) {
|
||||
_outerReg = addr & 0x0F;
|
||||
_prgMode = (addr >> 4) & 0x01;
|
||||
}
|
||||
UpdateState();
|
||||
}
|
||||
};
|
|
@ -1,46 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "MMC3.h"
|
||||
|
||||
class Bmc8in1 : public MMC3
|
||||
{
|
||||
private:
|
||||
uint8_t _reg;
|
||||
|
||||
protected:
|
||||
void InitMapper() override
|
||||
{
|
||||
_reg = 0;
|
||||
MMC3::InitMapper();
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
MMC3::StreamState(saving);
|
||||
Stream(_reg);
|
||||
}
|
||||
|
||||
void SelectCHRPage(uint16_t slot, uint16_t page, ChrMemoryType memoryType = ChrMemoryType::Default) override
|
||||
{
|
||||
MMC3::SelectCHRPage(slot, ((_reg & 0x0C) << 5) | (page & 0x7F), memoryType);
|
||||
}
|
||||
|
||||
void SelectPRGPage(uint16_t slot, uint16_t page, PrgMemoryType memoryType = PrgMemoryType::PrgRom) override
|
||||
{
|
||||
if(_reg & 0x10) {
|
||||
MMC3::SelectPRGPage(slot, ((_reg & 0x0C) << 2) | (page & 0x0F));
|
||||
} else {
|
||||
SelectPrgPage4x(0, (_reg & 0x0F) << 2);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if(addr & 0x1000) {
|
||||
_reg = value;
|
||||
UpdateState();
|
||||
} else {
|
||||
MMC3::WriteRegister(addr, value);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,38 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class BmcG146 : public BaseMapper
|
||||
{
|
||||
protected:
|
||||
uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
}
|
||||
|
||||
void Reset(bool softReset) override
|
||||
{
|
||||
BaseMapper::Reset(softReset);
|
||||
|
||||
WriteRegister(0x8000, 0);
|
||||
SelectCHRPage(0, 0);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if(addr & 0x800) {
|
||||
SelectPRGPage(0, (addr & 0x1F) | (addr & ((addr & 0x40) >> 6)));
|
||||
SelectPRGPage(1, (addr & 0x18) | 0x07);
|
||||
} else {
|
||||
if(addr & 0x40) {
|
||||
SelectPRGPage(0, addr & 0x1F);
|
||||
SelectPRGPage(1, addr & 0x1F);
|
||||
} else {
|
||||
SelectPrgPage2x(0, addr & 0x1E);
|
||||
}
|
||||
}
|
||||
SetMirroringType(addr & 0x80 ? MirroringType::Horizontal : MirroringType::Vertical);
|
||||
}
|
||||
};
|
|
@ -1,64 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "MMC3.h"
|
||||
|
||||
class BmcGn45 : public MMC3
|
||||
{
|
||||
private:
|
||||
uint8_t _selectedBlock = 0;
|
||||
bool _wramEnabled = false;
|
||||
|
||||
protected:
|
||||
uint16_t RegisterStartAddress() override { return 0x6000; }
|
||||
uint16_t RegisterEndAddress() override { return 0xFFFF; }
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
MMC3::StreamState(saving);
|
||||
Stream(_selectedBlock, _wramEnabled);
|
||||
}
|
||||
|
||||
void Reset(bool softReset) override
|
||||
{
|
||||
MMC3::Reset(softReset);
|
||||
|
||||
if(softReset) {
|
||||
_selectedBlock = 0;
|
||||
_wramEnabled = false;
|
||||
ResetMmc3();
|
||||
UpdateState();
|
||||
}
|
||||
}
|
||||
|
||||
void SelectCHRPage(uint16_t slot, uint16_t page, ChrMemoryType memoryType = ChrMemoryType::Default) override
|
||||
{
|
||||
MMC3::SelectCHRPage(slot, (page & 0x7F) | (_selectedBlock << 3), memoryType);
|
||||
}
|
||||
|
||||
void SelectPRGPage(uint16_t slot, uint16_t page, PrgMemoryType memoryType = PrgMemoryType::PrgRom) override
|
||||
{
|
||||
MMC3::SelectPRGPage(slot, (page & 0x0F) | _selectedBlock, memoryType);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if(addr < 0x7000) {
|
||||
if(!_wramEnabled) {
|
||||
_selectedBlock = addr & 0x30;
|
||||
_wramEnabled = (addr & 0x80) != 0;
|
||||
UpdateState();
|
||||
} else {
|
||||
WritePrgRam(addr, value);
|
||||
}
|
||||
} else if(addr < 0x8000) {
|
||||
if(!_wramEnabled) {
|
||||
_selectedBlock = value & 0x30;
|
||||
UpdateState();
|
||||
} else {
|
||||
WritePrgRam(addr, value);
|
||||
}
|
||||
} else {
|
||||
MMC3::WriteRegister(addr, value);
|
||||
}
|
||||
}
|
||||
};
|
116
Core/BmcHpxx.h
116
Core/BmcHpxx.h
|
@ -1,116 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "MMC3.h"
|
||||
|
||||
class BmcHpxx : public MMC3
|
||||
{
|
||||
private:
|
||||
uint8_t _exRegs[5];
|
||||
bool _locked;
|
||||
|
||||
protected:
|
||||
uint32_t GetDipSwitchCount() override { return 4; }
|
||||
bool AllowRegisterRead() override { return true; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
memset(_exRegs, 0, sizeof(_exRegs));
|
||||
_locked = false;
|
||||
|
||||
MMC3::InitMapper();
|
||||
AddRegisterRange(0x5000, 0x5FFF, MemoryOperation::Any);
|
||||
RemoveRegisterRange(0x8000, 0xFFFF, MemoryOperation::Read);
|
||||
}
|
||||
|
||||
void Reset(bool softReset) override
|
||||
{
|
||||
MMC3::Reset(softReset);
|
||||
memset(_exRegs, 0, sizeof(_exRegs));
|
||||
_locked = false;
|
||||
MMC3::ResetMmc3();
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
MMC3::StreamState(saving);
|
||||
Stream(_exRegs[0], _exRegs[1], _exRegs[2], _exRegs[3], _exRegs[4], _locked);
|
||||
}
|
||||
|
||||
void SelectCHRPage(uint16_t slot, uint16_t page, ChrMemoryType memoryType = ChrMemoryType::Default) override
|
||||
{
|
||||
if(_exRegs[0] & 0x04) {
|
||||
switch(_exRegs[0] & 0x03) {
|
||||
case 0:
|
||||
case 1: SelectChrPage8x(0, (_exRegs[2] & 0x3F) << 3); break;
|
||||
case 2: SelectChrPage8x(0, ((_exRegs[2] & 0x3E) | (_exRegs[4] & 0x01)) << 3); break;
|
||||
case 3: SelectChrPage8x(0, ((_exRegs[2] & 0x3C) | (_exRegs[4] & 0x03)) << 3); break;
|
||||
}
|
||||
} else {
|
||||
uint8_t base, mask;
|
||||
if(_exRegs[0] & 0x01) {
|
||||
base = _exRegs[2] & 0x30;
|
||||
mask = 0x7F;
|
||||
} else {
|
||||
base = _exRegs[2] & 0x20;
|
||||
mask = 0xFF;
|
||||
}
|
||||
MMC3::SelectCHRPage(slot, (page & mask) | (base << 3));
|
||||
}
|
||||
}
|
||||
|
||||
void SelectPRGPage(uint16_t slot, uint16_t page, PrgMemoryType memoryType = PrgMemoryType::PrgRom) override
|
||||
{
|
||||
if(_exRegs[0] & 0x04) {
|
||||
if((_exRegs[0] & 0x0F) == 0x04) {
|
||||
SelectPrgPage2x(0, (_exRegs[1] & 0x1F) << 1);
|
||||
SelectPrgPage2x(1, (_exRegs[1] & 0x1F) << 1);
|
||||
} else {
|
||||
SelectPrgPage4x(0, (_exRegs[1] & 0x1E) << 1);
|
||||
}
|
||||
} else {
|
||||
uint8_t base, mask;
|
||||
if(_exRegs[0] & 0x02) {
|
||||
base = _exRegs[1] & 0x18;
|
||||
mask = 0x0F;
|
||||
} else {
|
||||
base = _exRegs[1] & 0x10;
|
||||
mask = 0x1F;
|
||||
}
|
||||
MMC3::SelectPRGPage(slot, (page & mask) | (base << 1));
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateMirroring() override
|
||||
{
|
||||
if(_exRegs[0] & 0x04) {
|
||||
SetMirroringType(_exRegs[4] & 0x04 ? MirroringType::Vertical : MirroringType::Horizontal);
|
||||
} else {
|
||||
MMC3::UpdateMirroring();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ReadRegister(uint16_t addr) override
|
||||
{
|
||||
return GetDipSwitches();
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if(addr < 0x8000) {
|
||||
if(!_locked) {
|
||||
_exRegs[addr & 0x03] = value;
|
||||
_locked = (value & 0x80) != 0;
|
||||
UpdatePrgMapping();
|
||||
UpdateChrMapping();
|
||||
}
|
||||
} else {
|
||||
if(_exRegs[0] & 0x04) {
|
||||
_exRegs[4] = value;
|
||||
UpdateChrMapping();
|
||||
} else {
|
||||
MMC3::WriteRegister(addr, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,26 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class BmcK3046 : public BaseMapper
|
||||
{
|
||||
protected:
|
||||
virtual uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
virtual uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
SelectPRGPage(0, 0);
|
||||
SelectPRGPage(1, 7);
|
||||
SelectCHRPage(0, 0);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
uint8_t inner = value & 0x07;
|
||||
uint8_t outer = value & 0x38;
|
||||
|
||||
SelectPRGPage(0, outer | inner);
|
||||
SelectPRGPage(1, outer | 7);
|
||||
}
|
||||
};
|
|
@ -1,35 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class BmcNtd03 : public BaseMapper
|
||||
{
|
||||
protected:
|
||||
uint16_t GetPRGPageSize() override { return 0x4000; }
|
||||
uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
}
|
||||
|
||||
void Reset(bool softReset) override
|
||||
{
|
||||
BaseMapper::Reset(softReset);
|
||||
WriteRegister(0x8000, 0);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
uint8_t prg = ((addr >> 10) & 0x1E);
|
||||
uint8_t chr= ((addr & 0x0300) >> 5) | (addr & 0x07);
|
||||
if(addr & 0x80) {
|
||||
SelectPRGPage(0, prg | ((addr >> 6) & 1));
|
||||
SelectPRGPage(1, prg | ((addr >> 6) & 1));
|
||||
} else {
|
||||
SelectPrgPage2x(0, prg & 0xFE);
|
||||
}
|
||||
|
||||
SelectCHRPage(0, chr);
|
||||
SetMirroringType(addr & 0x400 ? MirroringType::Horizontal : MirroringType::Vertical);
|
||||
}
|
||||
};
|
22
Core/BnRom.h
22
Core/BnRom.h
|
@ -1,22 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class BnRom : public BaseMapper
|
||||
{
|
||||
protected:
|
||||
virtual uint16_t GetPRGPageSize() override { return 0x8000; }
|
||||
virtual uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
SelectPRGPage(0, GetPowerOnByte());
|
||||
SelectCHRPage(0, 0);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
//"While the original BNROM board connects only 2 bits, it is recommended that emulators implement this as an 8-bit register allowing selection of up to 8 MB PRG ROM if present."
|
||||
SelectPRGPage(0, value);
|
||||
}
|
||||
};
|
|
@ -1,111 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
|
||||
#include "Breakpoint.h"
|
||||
|
||||
Breakpoint::Breakpoint()
|
||||
{
|
||||
}
|
||||
|
||||
Breakpoint::~Breakpoint()
|
||||
{
|
||||
}
|
||||
|
||||
bool Breakpoint::Matches(uint32_t memoryAddr, AddressTypeInfo &info)
|
||||
{
|
||||
if(_memoryType == DebugMemoryType::CpuMemory) {
|
||||
if(_startAddr == -1) {
|
||||
return true;
|
||||
} else if(_endAddr == -1) {
|
||||
return (int32_t)memoryAddr == _startAddr;
|
||||
} else {
|
||||
return (int32_t)memoryAddr >= _startAddr && (int32_t)memoryAddr <= _endAddr;
|
||||
}
|
||||
} else if(
|
||||
(_memoryType == DebugMemoryType::PrgRom && info.Type == AddressType::PrgRom) ||
|
||||
(_memoryType == DebugMemoryType::WorkRam && info.Type == AddressType::WorkRam) ||
|
||||
(_memoryType == DebugMemoryType::SaveRam && info.Type == AddressType::SaveRam)
|
||||
) {
|
||||
if(_startAddr == -1) {
|
||||
return true;
|
||||
} else if(_endAddr == -1) {
|
||||
return info.Address == _startAddr;
|
||||
} else {
|
||||
return info.Address >= _startAddr && info.Address <= _endAddr;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Breakpoint::Matches(uint32_t memoryAddr, PpuAddressTypeInfo &info)
|
||||
{
|
||||
if(_memoryType == DebugMemoryType::PpuMemory) {
|
||||
if(_startAddr == -1) {
|
||||
return true;
|
||||
} else if(_endAddr == -1) {
|
||||
return (int32_t)memoryAddr == _startAddr;
|
||||
} else {
|
||||
return (int32_t)memoryAddr >= _startAddr && (int32_t)memoryAddr <= _endAddr;
|
||||
}
|
||||
} else if(
|
||||
(_memoryType == DebugMemoryType::ChrRam && info.Type == PpuAddressType::ChrRam) ||
|
||||
(_memoryType == DebugMemoryType::ChrRom && info.Type == PpuAddressType::ChrRom) ||
|
||||
(_memoryType == DebugMemoryType::PaletteMemory && info.Type == PpuAddressType::PaletteRam) ||
|
||||
(_memoryType == DebugMemoryType::NametableRam && info.Type == PpuAddressType::NametableRam)
|
||||
) {
|
||||
if(_startAddr == -1) {
|
||||
return true;
|
||||
} else if(_endAddr == -1) {
|
||||
return info.Address == _startAddr;
|
||||
} else {
|
||||
return info.Address >= _startAddr && info.Address <= _endAddr;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Breakpoint::HasBreakpointType(BreakpointType type)
|
||||
{
|
||||
switch(type) {
|
||||
case BreakpointType::Global: return (_type == BreakpointTypeFlags::Global);
|
||||
case BreakpointType::Execute: return (_type & BreakpointTypeFlags::Execute) != 0;
|
||||
case BreakpointType::ReadRam: return (_type & BreakpointTypeFlags::ReadRam) != 0;
|
||||
case BreakpointType::WriteRam: return (_type & BreakpointTypeFlags::WriteRam) != 0;
|
||||
case BreakpointType::ReadVram: return (_type & BreakpointTypeFlags::ReadVram) != 0;
|
||||
case BreakpointType::WriteVram: return (_type & BreakpointTypeFlags::WriteVram) != 0;
|
||||
case BreakpointType::DummyReadRam: return (_type & BreakpointTypeFlags::ReadRam) != 0 && _processDummyReadWrites;
|
||||
case BreakpointType::DummyWriteRam: return (_type & BreakpointTypeFlags::WriteRam) != 0 && _processDummyReadWrites;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
string Breakpoint::GetCondition()
|
||||
{
|
||||
return _condition;
|
||||
}
|
||||
|
||||
bool Breakpoint::HasCondition()
|
||||
{
|
||||
return _condition[0] != 0;
|
||||
}
|
||||
|
||||
void Breakpoint::ClearCondition()
|
||||
{
|
||||
memset(_condition, 0, sizeof(_condition));
|
||||
}
|
||||
|
||||
uint32_t Breakpoint::GetId()
|
||||
{
|
||||
return _id;
|
||||
}
|
||||
|
||||
bool Breakpoint::IsEnabled()
|
||||
{
|
||||
return _enabled;
|
||||
}
|
||||
|
||||
bool Breakpoint::IsMarked()
|
||||
{
|
||||
return _markEvent;
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "DebuggerTypes.h"
|
||||
|
||||
class Breakpoint
|
||||
{
|
||||
private:
|
||||
enum BreakpointTypeFlags
|
||||
{
|
||||
Global = 0,
|
||||
Execute = 1,
|
||||
ReadRam = 2,
|
||||
WriteRam = 4,
|
||||
ReadVram = 8,
|
||||
WriteVram = 16,
|
||||
};
|
||||
|
||||
public:
|
||||
Breakpoint();
|
||||
~Breakpoint();
|
||||
|
||||
bool Matches(uint32_t memoryAddr, AddressTypeInfo &info);
|
||||
bool Matches(uint32_t memoryAddr, PpuAddressTypeInfo &info);
|
||||
bool HasBreakpointType(BreakpointType type);
|
||||
string GetCondition();
|
||||
bool HasCondition();
|
||||
void ClearCondition();
|
||||
|
||||
uint32_t GetId();
|
||||
bool IsEnabled();
|
||||
bool IsMarked();
|
||||
|
||||
private:
|
||||
uint32_t _id;
|
||||
DebugMemoryType _memoryType;
|
||||
BreakpointTypeFlags _type;
|
||||
int32_t _startAddr;
|
||||
int32_t _endAddr;
|
||||
bool _enabled;
|
||||
bool _markEvent;
|
||||
bool _processDummyReadWrites;
|
||||
char _condition[1000];
|
||||
};
|
35
Core/Bs5.h
35
Core/Bs5.h
|
@ -1,35 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Bs5 : public BaseMapper
|
||||
{
|
||||
protected:
|
||||
uint32_t GetDipSwitchCount() override { return 2; }
|
||||
uint16_t GetPRGPageSize() override { return 0x2000; }
|
||||
uint16_t GetCHRPageSize() override { return 0x800; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
for(int i = 0; i < 4; i++) {
|
||||
SelectPRGPage(i, -1);
|
||||
SelectCHRPage(i, -1);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
int bank = (addr >> 10) & 0x03;
|
||||
switch(addr & 0xF000) {
|
||||
case 0x8000:
|
||||
SelectCHRPage(bank, addr & 0x1F);
|
||||
break;
|
||||
|
||||
case 0xA000:
|
||||
if(addr & (1 << (GetDipSwitches() + 4))) {
|
||||
SelectPRGPage(bank, addr & 0x0F);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
52
Core/CNROM.h
52
Core/CNROM.h
|
@ -1,52 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class CNROM : public BaseMapper
|
||||
{
|
||||
private:
|
||||
bool _enableCopyProtection;
|
||||
|
||||
protected:
|
||||
virtual uint16_t GetPRGPageSize() override { return 0x8000; }
|
||||
virtual uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
SelectPRGPage(0, 0);
|
||||
SelectCHRPage(0, GetPowerOnByte());
|
||||
}
|
||||
|
||||
bool HasBusConflicts() override { return (_romInfo.MapperID == 3 && _romInfo.SubMapperID == 2) || _romInfo.MapperID == 185; }
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if(_enableCopyProtection) {
|
||||
//Submapper 0: Use heuristics - "if C AND $0F is nonzero, and if C does not equal $13: CHR is enabled"
|
||||
//Submapper 4: Enable CHR-ROM if bits 0..1 of the latch hold the value 0, otherwise disable CHR-ROM.
|
||||
//Submapper 5: Enable CHR-ROM if bits 0..1 of the latch hold the value 1, otherwise disable CHR-ROM.
|
||||
//Submapper 6: Enable CHR-ROM if bits 0..1 of the latch hold the value 2, otherwise disable CHR-ROM.
|
||||
//Submapper 7: Enable CHR-ROM if bits 0..1 of the latch hold the value 3, otherwise disable CHR-ROM.
|
||||
bool validAccess = (
|
||||
(_romInfo.SubMapperID == 0 && (value & 0x0F) != 0 && value != 0x13) ||
|
||||
(_romInfo.SubMapperID == 4 && (value & 0x03) == 0) ||
|
||||
(_romInfo.SubMapperID == 5 && (value & 0x03) == 1) ||
|
||||
(_romInfo.SubMapperID == 6 && (value & 0x03) == 2) ||
|
||||
(_romInfo.SubMapperID == 7 && (value & 0x03) == 3)
|
||||
);
|
||||
|
||||
if(validAccess) {
|
||||
SelectCHRPage(0, 0);
|
||||
} else {
|
||||
RemovePpuMemoryMapping(0x0000, 0x1FFF);
|
||||
}
|
||||
} else {
|
||||
SelectCHRPage(0, value);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
CNROM(bool enableCopyProtection) : _enableCopyProtection(enableCopyProtection)
|
||||
{
|
||||
}
|
||||
};
|
487
Core/CPU.cpp
487
Core/CPU.cpp
|
@ -1,487 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
#include <random>
|
||||
#include <assert.h>
|
||||
#include "CPU.h"
|
||||
#include "PPU.h"
|
||||
#include "APU.h"
|
||||
#include "DeltaModulationChannel.h"
|
||||
#include "Debugger.h"
|
||||
#include "NsfMapper.h"
|
||||
#include "Console.h"
|
||||
|
||||
CPU::CPU(shared_ptr<Console> console)
|
||||
{
|
||||
_console = console;
|
||||
_memoryManager = _console->GetMemoryManager();
|
||||
|
||||
Func opTable[] = {
|
||||
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
|
||||
&CPU::BRK, &CPU::ORA, &CPU::HLT, &CPU::SLO, &CPU::NOP, &CPU::ORA, &CPU::ASL_Memory, &CPU::SLO, &CPU::PHP, &CPU::ORA, &CPU::ASL_Acc, &CPU::AAC, &CPU::NOP, &CPU::ORA, &CPU::ASL_Memory, &CPU::SLO, //0
|
||||
&CPU::BPL, &CPU::ORA, &CPU::HLT, &CPU::SLO, &CPU::NOP, &CPU::ORA, &CPU::ASL_Memory, &CPU::SLO, &CPU::CLC, &CPU::ORA, &CPU::NOP, &CPU::SLO, &CPU::NOP, &CPU::ORA, &CPU::ASL_Memory, &CPU::SLO, //1
|
||||
&CPU::JSR, &CPU::AND, &CPU::HLT, &CPU::RLA, &CPU::BIT, &CPU::AND, &CPU::ROL_Memory, &CPU::RLA, &CPU::PLP, &CPU::AND, &CPU::ROL_Acc, &CPU::AAC, &CPU::BIT, &CPU::AND, &CPU::ROL_Memory, &CPU::RLA, //2
|
||||
&CPU::BMI, &CPU::AND, &CPU::HLT, &CPU::RLA, &CPU::NOP, &CPU::AND, &CPU::ROL_Memory, &CPU::RLA, &CPU::SEC, &CPU::AND, &CPU::NOP, &CPU::RLA, &CPU::NOP, &CPU::AND, &CPU::ROL_Memory, &CPU::RLA, //3
|
||||
&CPU::RTI, &CPU::EOR, &CPU::HLT, &CPU::SRE, &CPU::NOP, &CPU::EOR, &CPU::LSR_Memory, &CPU::SRE, &CPU::PHA, &CPU::EOR, &CPU::LSR_Acc, &CPU::ASR, &CPU::JMP_Abs, &CPU::EOR, &CPU::LSR_Memory, &CPU::SRE, //4
|
||||
&CPU::BVC, &CPU::EOR, &CPU::HLT, &CPU::SRE, &CPU::NOP, &CPU::EOR, &CPU::LSR_Memory, &CPU::SRE, &CPU::CLI, &CPU::EOR, &CPU::NOP, &CPU::SRE, &CPU::NOP, &CPU::EOR, &CPU::LSR_Memory, &CPU::SRE, //5
|
||||
&CPU::RTS, &CPU::ADC, &CPU::HLT, &CPU::RRA, &CPU::NOP, &CPU::ADC, &CPU::ROR_Memory, &CPU::RRA, &CPU::PLA, &CPU::ADC, &CPU::ROR_Acc, &CPU::ARR, &CPU::JMP_Ind, &CPU::ADC, &CPU::ROR_Memory, &CPU::RRA, //6
|
||||
&CPU::BVS, &CPU::ADC, &CPU::HLT, &CPU::RRA, &CPU::NOP, &CPU::ADC, &CPU::ROR_Memory, &CPU::RRA, &CPU::SEI, &CPU::ADC, &CPU::NOP, &CPU::RRA, &CPU::NOP, &CPU::ADC, &CPU::ROR_Memory, &CPU::RRA, //7
|
||||
&CPU::NOP, &CPU::STA, &CPU::NOP, &CPU::SAX, &CPU::STY, &CPU::STA, &CPU::STX, &CPU::SAX, &CPU::DEY, &CPU::NOP, &CPU::TXA, &CPU::UNK, &CPU::STY, &CPU::STA, &CPU::STX, &CPU::SAX, //8
|
||||
&CPU::BCC, &CPU::STA, &CPU::HLT, &CPU::AXA, &CPU::STY, &CPU::STA, &CPU::STX, &CPU::SAX, &CPU::TYA, &CPU::STA, &CPU::TXS, &CPU::TAS, &CPU::SYA, &CPU::STA, &CPU::SXA, &CPU::AXA, //9
|
||||
&CPU::LDY, &CPU::LDA, &CPU::LDX, &CPU::LAX, &CPU::LDY, &CPU::LDA, &CPU::LDX, &CPU::LAX, &CPU::TAY, &CPU::LDA, &CPU::TAX, &CPU::ATX, &CPU::LDY, &CPU::LDA, &CPU::LDX, &CPU::LAX, //A
|
||||
&CPU::BCS, &CPU::LDA, &CPU::HLT, &CPU::LAX, &CPU::LDY, &CPU::LDA, &CPU::LDX, &CPU::LAX, &CPU::CLV, &CPU::LDA, &CPU::TSX, &CPU::LAS, &CPU::LDY, &CPU::LDA, &CPU::LDX, &CPU::LAX, //B
|
||||
&CPU::CPY, &CPU::CPA, &CPU::NOP, &CPU::DCP, &CPU::CPY, &CPU::CPA, &CPU::DEC, &CPU::DCP, &CPU::INY, &CPU::CPA, &CPU::DEX, &CPU::AXS, &CPU::CPY, &CPU::CPA, &CPU::DEC, &CPU::DCP, //C
|
||||
&CPU::BNE, &CPU::CPA, &CPU::HLT, &CPU::DCP, &CPU::NOP, &CPU::CPA, &CPU::DEC, &CPU::DCP, &CPU::CLD, &CPU::CPA, &CPU::NOP, &CPU::DCP, &CPU::NOP, &CPU::CPA, &CPU::DEC, &CPU::DCP, //D
|
||||
&CPU::CPX, &CPU::SBC, &CPU::NOP, &CPU::ISB, &CPU::CPX, &CPU::SBC, &CPU::INC, &CPU::ISB, &CPU::INX, &CPU::SBC, &CPU::NOP, &CPU::SBC, &CPU::CPX, &CPU::SBC, &CPU::INC, &CPU::ISB, //E
|
||||
&CPU::BEQ, &CPU::SBC, &CPU::HLT, &CPU::ISB, &CPU::NOP, &CPU::SBC, &CPU::INC, &CPU::ISB, &CPU::SED, &CPU::SBC, &CPU::NOP, &CPU::ISB, &CPU::NOP, &CPU::SBC, &CPU::INC, &CPU::ISB //F
|
||||
};
|
||||
|
||||
typedef AddrMode M;
|
||||
AddrMode addrMode[] = {
|
||||
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
|
||||
M::Imp, M::IndX, M::None, M::IndX, M::Zero, M::Zero, M::Zero, M::Zero, M::Imp, M::Imm, M::Acc, M::Imm, M::Abs, M::Abs, M::Abs, M::Abs, //0
|
||||
M::Rel, M::IndY, M::None, M::IndYW, M::ZeroX, M::ZeroX, M::ZeroX, M::ZeroX, M::Imp, M::AbsY, M::Imp, M::AbsYW,M::AbsX, M::AbsX, M::AbsXW,M::AbsXW,//1
|
||||
M::Abs, M::IndX, M::None, M::IndX, M::Zero, M::Zero, M::Zero, M::Zero, M::Imp, M::Imm, M::Acc, M::Imm, M::Abs, M::Abs, M::Abs, M::Abs, //2
|
||||
M::Rel, M::IndY, M::None, M::IndYW, M::ZeroX, M::ZeroX, M::ZeroX, M::ZeroX, M::Imp, M::AbsY, M::Imp, M::AbsYW,M::AbsX, M::AbsX, M::AbsXW,M::AbsXW,//3
|
||||
M::Imp, M::IndX, M::None, M::IndX, M::Zero, M::Zero, M::Zero, M::Zero, M::Imp, M::Imm, M::Acc, M::Imm, M::Abs, M::Abs, M::Abs, M::Abs, //4
|
||||
M::Rel, M::IndY, M::None, M::IndYW, M::ZeroX, M::ZeroX, M::ZeroX, M::ZeroX, M::Imp, M::AbsY, M::Imp, M::AbsYW,M::AbsX, M::AbsX, M::AbsXW,M::AbsXW,//5
|
||||
M::Imp, M::IndX, M::None, M::IndX, M::Zero, M::Zero, M::Zero, M::Zero, M::Imp, M::Imm, M::Acc, M::Imm, M::Ind, M::Abs, M::Abs, M::Abs, //6
|
||||
M::Rel, M::IndY, M::None, M::IndYW, M::ZeroX, M::ZeroX, M::ZeroX, M::ZeroX, M::Imp, M::AbsY, M::Imp, M::AbsYW,M::AbsX, M::AbsX, M::AbsXW,M::AbsXW,//7
|
||||
M::Imm, M::IndX, M::Imm, M::IndX, M::Zero, M::Zero, M::Zero, M::Zero, M::Imp, M::Imm, M::Imp, M::Imm, M::Abs, M::Abs, M::Abs, M::Abs, //8
|
||||
M::Rel, M::IndYW, M::None, M::IndYW, M::ZeroX, M::ZeroX, M::ZeroY, M::ZeroY, M::Imp, M::AbsYW,M::Imp, M::AbsYW,M::AbsXW,M::AbsXW,M::AbsYW,M::AbsYW,//9
|
||||
M::Imm, M::IndX, M::Imm, M::IndX, M::Zero, M::Zero, M::Zero, M::Zero, M::Imp, M::Imm, M::Imp, M::Imm, M::Abs, M::Abs, M::Abs, M::Abs, //A
|
||||
M::Rel, M::IndY, M::None, M::IndY, M::ZeroX, M::ZeroX, M::ZeroY, M::ZeroY, M::Imp, M::AbsY, M::Imp, M::AbsY, M::AbsX, M::AbsX, M::AbsY, M::AbsY, //B
|
||||
M::Imm, M::IndX, M::Imm, M::IndX, M::Zero, M::Zero, M::Zero, M::Zero, M::Imp, M::Imm, M::Imp, M::Imm, M::Abs, M::Abs, M::Abs, M::Abs, //C
|
||||
M::Rel, M::IndY, M::None, M::IndYW, M::ZeroX, M::ZeroX, M::ZeroX, M::ZeroX, M::Imp, M::AbsY, M::Imp, M::AbsYW,M::AbsX, M::AbsX, M::AbsXW,M::AbsXW,//D
|
||||
M::Imm, M::IndX, M::Imm, M::IndX, M::Zero, M::Zero, M::Zero, M::Zero, M::Imp, M::Imm, M::Imp, M::Imm, M::Abs, M::Abs, M::Abs, M::Abs, //E
|
||||
M::Rel, M::IndY, M::None, M::IndYW, M::ZeroX, M::ZeroX, M::ZeroX, M::ZeroX, M::Imp, M::AbsY, M::Imp, M::AbsYW,M::AbsX, M::AbsX, M::AbsXW,M::AbsXW,//F
|
||||
};
|
||||
|
||||
memcpy(_opTable, opTable, sizeof(opTable));
|
||||
memcpy(_addrMode, addrMode, sizeof(addrMode));
|
||||
|
||||
_instAddrMode = AddrMode::None;
|
||||
_state = {};
|
||||
_cycleCount = 0;
|
||||
_operand = 0;
|
||||
_spriteDmaTransfer = false;
|
||||
_spriteDmaOffset = 0;
|
||||
_needHalt = false;
|
||||
_ppuOffset = 0;
|
||||
_startClockCount = 6;
|
||||
_endClockCount = 6;
|
||||
_masterClock = 0;
|
||||
_dmcDmaRunning = false;
|
||||
_cpuWrite = false;
|
||||
_irqMask = 0;
|
||||
_state = {};
|
||||
_prevRunIrq = false;
|
||||
_runIrq = false;
|
||||
}
|
||||
|
||||
void CPU::Reset(bool softReset, NesModel model)
|
||||
{
|
||||
_state.NMIFlag = false;
|
||||
_state.IRQFlag = 0;
|
||||
|
||||
_spriteDmaTransfer = false;
|
||||
_spriteDmaOffset = 0;
|
||||
_needHalt = false;
|
||||
_dmcDmaRunning = false;
|
||||
_lastCrashWarning = 0;
|
||||
|
||||
//Used by NSF code to disable Frame Counter & DMC interrupts
|
||||
_irqMask = 0xFF;
|
||||
|
||||
//Use _memoryManager->Read() directly to prevent clocking the PPU/APU when setting PC at reset
|
||||
_state.PC = _memoryManager->Read(CPU::ResetVector) | _memoryManager->Read(CPU::ResetVector+1) << 8;
|
||||
_state.DebugPC = _state.PC;
|
||||
_state.PreviousDebugPC = _state.PC;
|
||||
|
||||
if(softReset) {
|
||||
SetFlags(PSFlags::Interrupt);
|
||||
_state.SP -= 0x03;
|
||||
} else {
|
||||
_state.A = 0;
|
||||
_state.SP = 0xFD;
|
||||
_state.X = 0;
|
||||
_state.Y = 0;
|
||||
_state.PS = PSFlags::Interrupt;
|
||||
|
||||
_runIrq = false;
|
||||
}
|
||||
|
||||
uint8_t ppuDivider;
|
||||
uint8_t cpuDivider;
|
||||
switch(model) {
|
||||
default:
|
||||
case NesModel::NTSC:
|
||||
ppuDivider = 4;
|
||||
cpuDivider = 12;
|
||||
break;
|
||||
|
||||
case NesModel::PAL:
|
||||
ppuDivider = 5;
|
||||
cpuDivider = 16;
|
||||
break;
|
||||
|
||||
case NesModel::Dendy:
|
||||
ppuDivider = 5;
|
||||
cpuDivider = 15;
|
||||
break;
|
||||
}
|
||||
|
||||
_cycleCount = -1;
|
||||
_masterClock = 0;
|
||||
|
||||
uint8_t cpuOffset = 0;
|
||||
if(_console->GetSettings()->CheckFlag(EmulationFlags::RandomizeCpuPpuAlignment)) {
|
||||
std::random_device rd;
|
||||
std::mt19937 mt(rd());
|
||||
std::uniform_int_distribution<> distPpu(0, ppuDivider - 1);
|
||||
std::uniform_int_distribution<> distCpu(0, cpuDivider - 1);
|
||||
_ppuOffset = distPpu(mt);
|
||||
cpuOffset += distCpu(mt);
|
||||
|
||||
string ppuAlignment = " PPU: " + std::to_string(_ppuOffset) + "/" + std::to_string(ppuDivider - 1);
|
||||
string cpuAlignment = " CPU: " + std::to_string(cpuOffset) + "/" + std::to_string(cpuDivider - 1);
|
||||
MessageManager::Log("CPU/PPU alignment -" + ppuAlignment + cpuAlignment);
|
||||
} else {
|
||||
_ppuOffset = 1;
|
||||
cpuOffset = 0;
|
||||
}
|
||||
|
||||
_masterClock += cpuDivider + cpuOffset;
|
||||
|
||||
//The CPU takes 8 cycles before it starts executing the ROM's code after a reset/power up
|
||||
for(int i = 0; i < 8; i++) {
|
||||
StartCpuCycle(true);
|
||||
EndCpuCycle(true);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::Exec()
|
||||
{
|
||||
uint8_t opCode = GetOPCode();
|
||||
_instAddrMode = _addrMode[opCode];
|
||||
_operand = FetchOperand();
|
||||
(this->*_opTable[opCode])();
|
||||
|
||||
if(_prevRunIrq || _prevNeedNmi) {
|
||||
IRQ();
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::IRQ()
|
||||
{
|
||||
#ifndef DUMMYCPU
|
||||
uint16_t originalPc = PC();
|
||||
#endif
|
||||
|
||||
DummyRead(); //fetch opcode (and discard it - $00 (BRK) is forced into the opcode register instead)
|
||||
DummyRead(); //read next instruction byte (actually the same as above, since PC increment is suppressed. Also discarded.)
|
||||
Push((uint16_t)(PC()));
|
||||
|
||||
if(_needNmi) {
|
||||
_needNmi = false;
|
||||
Push((uint8_t)(PS() | PSFlags::Reserved));
|
||||
SetFlags(PSFlags::Interrupt);
|
||||
|
||||
SetPC(MemoryReadWord(CPU::NMIVector));
|
||||
|
||||
#ifndef DUMMYCPU
|
||||
_console->DebugAddTrace("NMI");
|
||||
_console->DebugProcessInterrupt(originalPc, _state.PC, true);
|
||||
#endif
|
||||
} else {
|
||||
Push((uint8_t)(PS() | PSFlags::Reserved));
|
||||
SetFlags(PSFlags::Interrupt);
|
||||
|
||||
SetPC(MemoryReadWord(CPU::IRQVector));
|
||||
|
||||
#ifndef DUMMYCPU
|
||||
_console->DebugAddTrace("IRQ");
|
||||
_console->DebugProcessInterrupt(originalPc, _state.PC, false);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::BRK() {
|
||||
Push((uint16_t)(PC() + 1));
|
||||
|
||||
uint8_t flags = PS() | PSFlags::Break | PSFlags::Reserved;
|
||||
if(_needNmi) {
|
||||
_needNmi = false;
|
||||
Push((uint8_t)flags);
|
||||
SetFlags(PSFlags::Interrupt);
|
||||
|
||||
SetPC(MemoryReadWord(CPU::NMIVector));
|
||||
|
||||
#ifndef DUMMYCPU
|
||||
_console->DebugAddTrace("NMI");
|
||||
#endif
|
||||
} else {
|
||||
Push((uint8_t)flags);
|
||||
SetFlags(PSFlags::Interrupt);
|
||||
|
||||
SetPC(MemoryReadWord(CPU::IRQVector));
|
||||
|
||||
#ifndef DUMMYCPU
|
||||
_console->DebugAddTrace("IRQ");
|
||||
#endif
|
||||
}
|
||||
|
||||
//Ensure we don't start an NMI right after running a BRK instruction (first instruction in IRQ handler must run first - needed for nmi_and_brk test)
|
||||
_prevNeedNmi = false;
|
||||
}
|
||||
|
||||
void CPU::MemoryWrite(uint16_t addr, uint8_t value, MemoryOperationType operationType)
|
||||
{
|
||||
#ifdef DUMMYCPU
|
||||
if(operationType == MemoryOperationType::Write || operationType == MemoryOperationType::DummyWrite) {
|
||||
_writeAddresses[_writeCounter] = addr;
|
||||
_isDummyWrite[_writeCounter] = operationType == MemoryOperationType::DummyWrite;
|
||||
_writeValue[_writeCounter] = value;
|
||||
_writeCounter++;
|
||||
}
|
||||
#else
|
||||
_cpuWrite = true;
|
||||
StartCpuCycle(false);
|
||||
_memoryManager->Write(addr, value, operationType);
|
||||
EndCpuCycle(false);
|
||||
_cpuWrite = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint8_t CPU::MemoryRead(uint16_t addr, MemoryOperationType operationType) {
|
||||
#ifdef DUMMYCPU
|
||||
uint8_t value = _memoryManager->DebugRead(addr);
|
||||
if(operationType == MemoryOperationType::Read || operationType == MemoryOperationType::DummyRead) {
|
||||
_readAddresses[_readCounter] = addr;
|
||||
_readValue[_readCounter] = value;
|
||||
_isDummyRead[_readCounter] = operationType == MemoryOperationType::DummyRead;
|
||||
_readCounter++;
|
||||
}
|
||||
return value;
|
||||
#else
|
||||
ProcessPendingDma(addr);
|
||||
|
||||
StartCpuCycle(true);
|
||||
uint8_t value = _memoryManager->Read(addr, operationType);
|
||||
EndCpuCycle(true);
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint16_t CPU::FetchOperand()
|
||||
{
|
||||
switch(_instAddrMode) {
|
||||
case AddrMode::Acc:
|
||||
case AddrMode::Imp: DummyRead(); return 0;
|
||||
case AddrMode::Imm:
|
||||
case AddrMode::Rel: return GetImmediate();
|
||||
case AddrMode::Zero: return GetZeroAddr();
|
||||
case AddrMode::ZeroX: return GetZeroXAddr();
|
||||
case AddrMode::ZeroY: return GetZeroYAddr();
|
||||
case AddrMode::Ind: return GetIndAddr();
|
||||
case AddrMode::IndX: return GetIndXAddr();
|
||||
case AddrMode::IndY: return GetIndYAddr(false);
|
||||
case AddrMode::IndYW: return GetIndYAddr(true);
|
||||
case AddrMode::Abs: return GetAbsAddr();
|
||||
case AddrMode::AbsX: return GetAbsXAddr(false);
|
||||
case AddrMode::AbsXW: return GetAbsXAddr(true);
|
||||
case AddrMode::AbsY: return GetAbsYAddr(false);
|
||||
case AddrMode::AbsYW: return GetAbsYAddr(true);
|
||||
default: break;
|
||||
}
|
||||
|
||||
#if !defined(LIBRETRO) && !defined(DUMMYCPU)
|
||||
if(_lastCrashWarning == 0 || _cycleCount - _lastCrashWarning > 5000000) {
|
||||
MessageManager::DisplayMessage("Error", "GameCrash", "Invalid OP code - CPU crashed.");
|
||||
_lastCrashWarning = _cycleCount;
|
||||
}
|
||||
|
||||
if(_console->GetSettings()->CheckFlag(EmulationFlags::BreakOnCrash)) {
|
||||
//When "Break on Crash" is enabled, open the debugger and break immediately if a crash occurs
|
||||
_console->GetDebugger(true)->BreakImmediately(BreakSource::BreakOnCpuCrash);
|
||||
}
|
||||
|
||||
if(_console->IsNsf()) {
|
||||
//Don't stop emulation on CPU crash when playing NSFs, reset cpu instead
|
||||
_console->Reset(true);
|
||||
return 0;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void CPU::EndCpuCycle(bool forRead)
|
||||
{
|
||||
_masterClock += forRead ? (_endClockCount + 1) : (_endClockCount - 1);
|
||||
_console->GetPpu()->Run(_masterClock - _ppuOffset);
|
||||
|
||||
//"The internal signal goes high during φ1 of the cycle that follows the one where the edge is detected,
|
||||
//and stays high until the NMI has been handled. "
|
||||
_prevNeedNmi = _needNmi;
|
||||
|
||||
//"This edge detector polls the status of the NMI line during φ2 of each CPU cycle (i.e., during the
|
||||
//second half of each cycle) and raises an internal signal if the input goes from being high during
|
||||
//one cycle to being low during the next"
|
||||
if(!_prevNmiFlag && _state.NMIFlag) {
|
||||
_needNmi = true;
|
||||
}
|
||||
_prevNmiFlag = _state.NMIFlag;
|
||||
|
||||
//"it's really the status of the interrupt lines at the end of the second-to-last cycle that matters."
|
||||
//Keep the irq lines values from the previous cycle. The before-to-last cycle's values will be used
|
||||
_prevRunIrq = _runIrq;
|
||||
_runIrq = ((_state.IRQFlag & _irqMask) > 0 && !CheckFlag(PSFlags::Interrupt));
|
||||
}
|
||||
|
||||
void CPU::StartCpuCycle(bool forRead)
|
||||
{
|
||||
_masterClock += forRead ? (_startClockCount - 1) : (_startClockCount + 1);
|
||||
_cycleCount++;
|
||||
_console->GetPpu()->Run(_masterClock - _ppuOffset);
|
||||
_console->ProcessCpuClock();
|
||||
}
|
||||
|
||||
void CPU::ProcessPendingDma(uint16_t readAddress)
|
||||
{
|
||||
if(!_needHalt) {
|
||||
return;
|
||||
}
|
||||
|
||||
//"If this cycle is a read, hijack the read, discard the value, and prevent all other actions that occur on this cycle (PC not incremented, etc)"
|
||||
StartCpuCycle(true);
|
||||
_memoryManager->Read(readAddress, MemoryOperationType::DummyRead);
|
||||
EndCpuCycle(true);
|
||||
_needHalt = false;
|
||||
|
||||
uint16_t spriteDmaCounter = 0;
|
||||
uint8_t spriteReadAddr = 0;
|
||||
uint8_t readValue = 0;
|
||||
bool skipDummyReads = (readAddress == 0x4016 || readAddress == 0x4017);
|
||||
|
||||
auto processCycle = [this] {
|
||||
//Sprite DMA cycles count as halt/dummy cycles for the DMC DMA when both run at the same time
|
||||
if(_needHalt) {
|
||||
_needHalt = false;
|
||||
} else if(_needDummyRead) {
|
||||
_needDummyRead = false;
|
||||
}
|
||||
StartCpuCycle(true);
|
||||
};
|
||||
|
||||
while(_dmcDmaRunning || _spriteDmaTransfer) {
|
||||
bool getCycle = (_cycleCount & 0x01) == 0;
|
||||
if(getCycle) {
|
||||
if(_dmcDmaRunning && !_needHalt && !_needDummyRead) {
|
||||
//DMC DMA is ready to read a byte (both halt and dummy read cycles were performed before this)
|
||||
processCycle();
|
||||
readValue = _memoryManager->Read(_console->GetApu()->GetDmcReadAddress(), MemoryOperationType::DmcRead);
|
||||
EndCpuCycle(true);
|
||||
_console->GetApu()->SetDmcReadBuffer(readValue);
|
||||
_dmcDmaRunning = false;
|
||||
} else if(_spriteDmaTransfer) {
|
||||
//DMC DMA is not running, or not ready, run sprite DMA
|
||||
processCycle();
|
||||
readValue = _memoryManager->Read(_spriteDmaOffset * 0x100 + spriteReadAddr);
|
||||
EndCpuCycle(true);
|
||||
spriteReadAddr++;
|
||||
spriteDmaCounter++;
|
||||
} else {
|
||||
//DMC DMA is running, but not ready (need halt/dummy read) and sprite DMA isn't runnnig, perform a dummy read
|
||||
assert(_needHalt || _needDummyRead);
|
||||
processCycle();
|
||||
if(!skipDummyReads) {
|
||||
_memoryManager->Read(readAddress, MemoryOperationType::DummyRead);
|
||||
}
|
||||
EndCpuCycle(true);
|
||||
}
|
||||
} else {
|
||||
if(_spriteDmaTransfer && (spriteDmaCounter & 0x01)) {
|
||||
//Sprite DMA write cycle (only do this if a sprite dma read was performed last cycle)
|
||||
processCycle();
|
||||
_memoryManager->Write(0x2004, readValue, MemoryOperationType::Write);
|
||||
EndCpuCycle(true);
|
||||
spriteDmaCounter++;
|
||||
if(spriteDmaCounter == 0x200) {
|
||||
_spriteDmaTransfer = false;
|
||||
}
|
||||
} else {
|
||||
//Align to read cycle before starting sprite DMA (or align to perform DMC read)
|
||||
processCycle();
|
||||
if(!skipDummyReads) {
|
||||
_memoryManager->Read(readAddress, MemoryOperationType::DummyRead);
|
||||
}
|
||||
EndCpuCycle(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::RunDMATransfer(uint8_t offsetValue)
|
||||
{
|
||||
_spriteDmaTransfer = true;
|
||||
_spriteDmaOffset = offsetValue;
|
||||
_needHalt = true;
|
||||
}
|
||||
|
||||
void CPU::StartDmcTransfer()
|
||||
{
|
||||
_dmcDmaRunning = true;
|
||||
_needDummyRead = true;
|
||||
_needHalt = true;
|
||||
}
|
||||
|
||||
uint32_t CPU::GetClockRate(NesModel model)
|
||||
{
|
||||
switch(model) {
|
||||
default:
|
||||
case NesModel::NTSC: return CPU::ClockRateNtsc; break;
|
||||
case NesModel::PAL: return CPU::ClockRatePal; break;
|
||||
case NesModel::Dendy: return CPU::ClockRateDendy; break;
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::SetMasterClockDivider(NesModel region)
|
||||
{
|
||||
switch(region) {
|
||||
default:
|
||||
case NesModel::NTSC:
|
||||
_startClockCount = 6;
|
||||
_endClockCount = 6;
|
||||
break;
|
||||
|
||||
case NesModel::PAL:
|
||||
_startClockCount = 8;
|
||||
_endClockCount = 8;
|
||||
break;
|
||||
|
||||
case NesModel::Dendy:
|
||||
_startClockCount = 7;
|
||||
_endClockCount = 8;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::StreamState(bool saving)
|
||||
{
|
||||
EmulationSettings* settings = _console->GetSettings();
|
||||
uint32_t extraScanlinesBeforeNmi = settings->GetPpuExtraScanlinesBeforeNmi();
|
||||
uint32_t extraScanlinesAfterNmi = settings->GetPpuExtraScanlinesAfterNmi();
|
||||
uint32_t dipSwitches = _console->GetSettings()->GetDipSwitches();
|
||||
|
||||
Stream(_state.PC, _state.SP, _state.PS, _state.A, _state.X, _state.Y, _cycleCount, _state.NMIFlag,
|
||||
_state.IRQFlag, _dmcDmaRunning, _spriteDmaTransfer,
|
||||
extraScanlinesBeforeNmi, extraScanlinesAfterNmi, dipSwitches,
|
||||
_needDummyRead, _needHalt, _startClockCount, _endClockCount, _ppuOffset, _masterClock,
|
||||
_prevNeedNmi, _prevNmiFlag, _needNmi);
|
||||
|
||||
if(!saving) {
|
||||
settings->SetPpuNmiConfig(extraScanlinesBeforeNmi, extraScanlinesAfterNmi);
|
||||
settings->SetDipSwitches(dipSwitches);
|
||||
}
|
||||
}
|
885
Core/CPU.h
885
Core/CPU.h
|
@ -1,885 +0,0 @@
|
|||
#if (defined(DUMMYCPU) && !defined(__DUMMYCPU__H)) || (!defined(DUMMYCPU) && !defined(__CPU__H))
|
||||
#ifdef DUMMYCPU
|
||||
#define __DUMMYCPU__H
|
||||
#else
|
||||
#define __CPU__H
|
||||
#endif
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "Snapshotable.h"
|
||||
#include "Types.h"
|
||||
|
||||
enum class NesModel;
|
||||
class Console;
|
||||
class MemoryManager;
|
||||
class DummyCpu;
|
||||
|
||||
class CPU : public Snapshotable
|
||||
{
|
||||
#ifndef DUMMYCPU
|
||||
friend DummyCpu;
|
||||
#endif
|
||||
|
||||
public:
|
||||
static constexpr uint16_t NMIVector = 0xFFFA;
|
||||
static constexpr uint16_t ResetVector = 0xFFFC;
|
||||
static constexpr uint16_t IRQVector = 0xFFFE;
|
||||
static constexpr uint32_t ClockRateNtsc = 1789773;
|
||||
static constexpr uint32_t ClockRatePal = 1662607;
|
||||
static constexpr uint32_t ClockRateDendy = 1773448;
|
||||
|
||||
private:
|
||||
typedef void(CPU::*Func)();
|
||||
|
||||
uint64_t _cycleCount;
|
||||
uint64_t _masterClock;
|
||||
uint8_t _ppuOffset;
|
||||
uint8_t _startClockCount;
|
||||
uint8_t _endClockCount;
|
||||
uint16_t _operand;
|
||||
|
||||
Func _opTable[256];
|
||||
AddrMode _addrMode[256];
|
||||
AddrMode _instAddrMode;
|
||||
|
||||
bool _needHalt = false;
|
||||
bool _spriteDmaTransfer = false;
|
||||
bool _dmcDmaRunning = false;
|
||||
bool _needDummyRead = false;
|
||||
uint8_t _spriteDmaOffset;
|
||||
|
||||
bool _cpuWrite = false;
|
||||
|
||||
uint8_t _irqMask;
|
||||
|
||||
State _state;
|
||||
shared_ptr<Console> _console;
|
||||
MemoryManager* _memoryManager;
|
||||
|
||||
bool _prevRunIrq = false;
|
||||
bool _runIrq = false;
|
||||
|
||||
bool _prevNmiFlag = false;
|
||||
bool _prevNeedNmi = false;
|
||||
bool _needNmi = false;
|
||||
|
||||
uint64_t _lastCrashWarning = 0;
|
||||
|
||||
#ifdef DUMMYCPU
|
||||
uint32_t _writeCounter = 0;
|
||||
uint16_t _writeAddresses[10];
|
||||
uint8_t _writeValue[10];
|
||||
bool _isDummyWrite[10];
|
||||
|
||||
uint32_t _readCounter = 0;
|
||||
uint16_t _readAddresses[10];
|
||||
uint8_t _readValue[10];
|
||||
bool _isDummyRead[10];
|
||||
#endif
|
||||
|
||||
__forceinline void StartCpuCycle(bool forRead);
|
||||
__forceinline void ProcessPendingDma(uint16_t readAddress);
|
||||
__forceinline uint16_t FetchOperand();
|
||||
__forceinline void EndCpuCycle(bool forRead);
|
||||
void IRQ();
|
||||
|
||||
uint8_t GetOPCode()
|
||||
{
|
||||
uint8_t opCode = MemoryRead(_state.PC, MemoryOperationType::ExecOpCode);
|
||||
_state.PC++;
|
||||
return opCode;
|
||||
}
|
||||
|
||||
void DummyRead()
|
||||
{
|
||||
MemoryRead(_state.PC, MemoryOperationType::DummyRead);
|
||||
}
|
||||
|
||||
uint8_t ReadByte()
|
||||
{
|
||||
uint8_t value = MemoryRead(_state.PC, MemoryOperationType::ExecOperand);
|
||||
_state.PC++;
|
||||
return value;
|
||||
}
|
||||
|
||||
uint16_t ReadWord()
|
||||
{
|
||||
uint16_t value = MemoryReadWord(_state.PC, MemoryOperationType::ExecOperand);
|
||||
_state.PC += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
void ClearFlags(uint8_t flags)
|
||||
{
|
||||
_state.PS &= ~flags;
|
||||
}
|
||||
|
||||
void SetFlags(uint8_t flags)
|
||||
{
|
||||
_state.PS |= flags;
|
||||
}
|
||||
|
||||
bool CheckFlag(uint8_t flag)
|
||||
{
|
||||
return (_state.PS & flag) == flag;
|
||||
}
|
||||
|
||||
void SetZeroNegativeFlags(uint8_t value)
|
||||
{
|
||||
if(value == 0) {
|
||||
SetFlags(PSFlags::Zero);
|
||||
} else if(value & 0x80) {
|
||||
SetFlags(PSFlags::Negative);
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckPageCrossed(uint16_t valA, int8_t valB)
|
||||
{
|
||||
return ((valA + valB) & 0xFF00) != (valA & 0xFF00);
|
||||
}
|
||||
|
||||
bool CheckPageCrossed(uint16_t valA, uint8_t valB)
|
||||
{
|
||||
return ((valA + valB) & 0xFF00) != (valA & 0xFF00);
|
||||
}
|
||||
|
||||
void MemoryWrite(uint16_t addr, uint8_t value, MemoryOperationType operationType = MemoryOperationType::Write);
|
||||
uint8_t MemoryRead(uint16_t addr, MemoryOperationType operationType = MemoryOperationType::Read);
|
||||
|
||||
uint16_t MemoryReadWord(uint16_t addr, MemoryOperationType operationType = MemoryOperationType::Read) {
|
||||
uint8_t lo = MemoryRead(addr, operationType);
|
||||
uint8_t hi = MemoryRead(addr + 1, operationType);
|
||||
return lo | hi << 8;
|
||||
}
|
||||
|
||||
void SetRegister(uint8_t ®, uint8_t value) {
|
||||
ClearFlags(PSFlags::Zero | PSFlags::Negative);
|
||||
SetZeroNegativeFlags(value);
|
||||
reg = value;
|
||||
}
|
||||
|
||||
void Push(uint8_t value) {
|
||||
MemoryWrite(SP() + 0x100, value);
|
||||
SetSP(SP() - 1);
|
||||
}
|
||||
|
||||
void Push(uint16_t value) {
|
||||
Push((uint8_t)(value >> 8));
|
||||
Push((uint8_t)value);
|
||||
}
|
||||
|
||||
uint8_t Pop() {
|
||||
SetSP(SP() + 1);
|
||||
return MemoryRead(0x100 + SP());
|
||||
}
|
||||
|
||||
uint16_t PopWord() {
|
||||
uint8_t lo = Pop();
|
||||
uint8_t hi = Pop();
|
||||
|
||||
return lo | hi << 8;
|
||||
}
|
||||
|
||||
uint8_t A() { return _state.A; }
|
||||
void SetA(uint8_t value) { SetRegister(_state.A, value); }
|
||||
uint8_t X() { return _state.X; }
|
||||
void SetX(uint8_t value) { SetRegister(_state.X, value); }
|
||||
uint8_t Y() { return _state.Y; }
|
||||
void SetY(uint8_t value) { SetRegister(_state.Y, value); }
|
||||
uint8_t SP() { return _state.SP; }
|
||||
void SetSP(uint8_t value) { _state.SP = value; }
|
||||
uint8_t PS() { return _state.PS; }
|
||||
void SetPS(uint8_t value) { _state.PS = value & 0xCF; }
|
||||
uint16_t PC() { return _state.PC; }
|
||||
void SetPC(uint16_t value) { _state.PC = value; }
|
||||
|
||||
uint16_t GetOperand()
|
||||
{
|
||||
return _operand;
|
||||
}
|
||||
|
||||
uint8_t GetOperandValue()
|
||||
{
|
||||
if(_instAddrMode >= AddrMode::Zero) {
|
||||
return MemoryRead(GetOperand());
|
||||
} else {
|
||||
return (uint8_t)GetOperand();
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t GetIndAddr() { return ReadWord(); }
|
||||
uint8_t GetImmediate() { return ReadByte(); }
|
||||
uint8_t GetZeroAddr() { return ReadByte(); }
|
||||
uint8_t GetZeroXAddr() {
|
||||
uint8_t value = ReadByte();
|
||||
MemoryRead(value, MemoryOperationType::DummyRead); //Dummy read
|
||||
return value + X();
|
||||
}
|
||||
uint8_t GetZeroYAddr() {
|
||||
uint8_t value = ReadByte();
|
||||
MemoryRead(value, MemoryOperationType::DummyRead); //Dummy read
|
||||
return value + Y();
|
||||
}
|
||||
uint16_t GetAbsAddr() { return ReadWord(); }
|
||||
|
||||
uint16_t GetAbsXAddr(bool dummyRead = true) {
|
||||
uint16_t baseAddr = ReadWord();
|
||||
bool pageCrossed = CheckPageCrossed(baseAddr, X());
|
||||
|
||||
if(pageCrossed || dummyRead) {
|
||||
//Dummy read done by the processor (only when page is crossed for READ instructions)
|
||||
MemoryRead(baseAddr + X() - (pageCrossed ? 0x100 : 0), MemoryOperationType::DummyRead);
|
||||
}
|
||||
return baseAddr + X();
|
||||
}
|
||||
|
||||
uint16_t GetAbsYAddr(bool dummyRead = true) {
|
||||
uint16_t baseAddr = ReadWord();
|
||||
bool pageCrossed = CheckPageCrossed(baseAddr, Y());
|
||||
|
||||
if(pageCrossed || dummyRead) {
|
||||
//Dummy read done by the processor (only when page is crossed for READ instructions)
|
||||
MemoryRead(baseAddr + Y() - (pageCrossed ? 0x100 : 0), MemoryOperationType::DummyRead);
|
||||
}
|
||||
|
||||
return baseAddr + Y();
|
||||
}
|
||||
|
||||
uint16_t GetInd() {
|
||||
uint16_t addr = GetOperand();
|
||||
if((addr & 0xFF) == 0xFF) {
|
||||
auto lo = MemoryRead(addr);
|
||||
auto hi = MemoryRead(addr - 0xFF);
|
||||
return (lo | hi << 8);
|
||||
} else {
|
||||
return MemoryReadWord(addr);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t GetIndXAddr() {
|
||||
uint8_t zero = ReadByte();
|
||||
|
||||
//Dummy read
|
||||
MemoryRead(zero, MemoryOperationType::DummyRead);
|
||||
|
||||
zero += X();
|
||||
|
||||
uint16_t addr;
|
||||
if(zero == 0xFF) {
|
||||
addr = MemoryRead(0xFF) | MemoryRead(0x00) << 8;
|
||||
} else {
|
||||
addr = MemoryReadWord(zero);
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
|
||||
uint16_t GetIndYAddr(bool dummyRead = true) {
|
||||
uint8_t zero = ReadByte();
|
||||
|
||||
uint16_t addr;
|
||||
if(zero == 0xFF) {
|
||||
addr = MemoryRead(0xFF) | MemoryRead(0x00) << 8;
|
||||
} else {
|
||||
addr = MemoryReadWord(zero);
|
||||
}
|
||||
|
||||
bool pageCrossed = CheckPageCrossed(addr, Y());
|
||||
if(pageCrossed || dummyRead) {
|
||||
//Dummy read done by the processor (only when page is crossed for READ instructions)
|
||||
MemoryRead(addr + Y() - (pageCrossed ? 0x100 : 0), MemoryOperationType::DummyRead);
|
||||
}
|
||||
return addr + Y();
|
||||
}
|
||||
|
||||
void AND() { SetA(A() & GetOperandValue()); }
|
||||
void EOR() { SetA(A() ^ GetOperandValue()); }
|
||||
void ORA() { SetA(A() | GetOperandValue()); }
|
||||
|
||||
void ADD(uint8_t value)
|
||||
{
|
||||
uint16_t result = (uint16_t)A() + (uint16_t)value + (CheckFlag(PSFlags::Carry) ? PSFlags::Carry : 0x00);
|
||||
|
||||
ClearFlags(PSFlags::Carry | PSFlags::Negative | PSFlags::Overflow | PSFlags::Zero);
|
||||
SetZeroNegativeFlags((uint8_t)result);
|
||||
if(~(A() ^ value) & (A() ^ result) & 0x80) {
|
||||
SetFlags(PSFlags::Overflow);
|
||||
}
|
||||
if(result > 0xFF) {
|
||||
SetFlags(PSFlags::Carry);
|
||||
}
|
||||
SetA((uint8_t)result);
|
||||
}
|
||||
|
||||
void ADC() { ADD(GetOperandValue()); }
|
||||
void SBC() { ADD(GetOperandValue() ^ 0xFF); }
|
||||
|
||||
void CMP(uint8_t reg, uint8_t value)
|
||||
{
|
||||
ClearFlags(PSFlags::Carry | PSFlags::Negative | PSFlags::Zero);
|
||||
|
||||
auto result = reg - value;
|
||||
|
||||
if(reg >= value) {
|
||||
SetFlags(PSFlags::Carry);
|
||||
}
|
||||
if(reg == value) {
|
||||
SetFlags(PSFlags::Zero);
|
||||
}
|
||||
if((result & 0x80) == 0x80) {
|
||||
SetFlags(PSFlags::Negative);
|
||||
}
|
||||
}
|
||||
|
||||
void CPA() { CMP(A(), GetOperandValue()); }
|
||||
void CPX() { CMP(X(), GetOperandValue()); }
|
||||
void CPY() { CMP(Y(), GetOperandValue()); }
|
||||
|
||||
void INC()
|
||||
{
|
||||
uint16_t addr = GetOperand();
|
||||
ClearFlags(PSFlags::Negative | PSFlags::Zero);
|
||||
uint8_t value = MemoryRead(addr);
|
||||
|
||||
MemoryWrite(addr, value, MemoryOperationType::DummyWrite); //Dummy write
|
||||
|
||||
value++;
|
||||
SetZeroNegativeFlags(value);
|
||||
MemoryWrite(addr, value);
|
||||
}
|
||||
|
||||
void DEC()
|
||||
{
|
||||
uint16_t addr = GetOperand();
|
||||
ClearFlags(PSFlags::Negative | PSFlags::Zero);
|
||||
uint8_t value = MemoryRead(addr);
|
||||
MemoryWrite(addr, value, MemoryOperationType::DummyWrite); //Dummy write
|
||||
|
||||
value--;
|
||||
SetZeroNegativeFlags(value);
|
||||
MemoryWrite(addr, value);
|
||||
}
|
||||
|
||||
uint8_t ASL(uint8_t value)
|
||||
{
|
||||
ClearFlags(PSFlags::Carry | PSFlags::Negative | PSFlags::Zero);
|
||||
if(value & 0x80) {
|
||||
SetFlags(PSFlags::Carry);
|
||||
}
|
||||
|
||||
uint8_t result = value << 1;
|
||||
SetZeroNegativeFlags(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint8_t LSR(uint8_t value) {
|
||||
ClearFlags(PSFlags::Carry | PSFlags::Negative | PSFlags::Zero);
|
||||
if(value & 0x01) {
|
||||
SetFlags(PSFlags::Carry);
|
||||
}
|
||||
|
||||
uint8_t result = value >> 1;
|
||||
SetZeroNegativeFlags(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint8_t ROL(uint8_t value) {
|
||||
bool carryFlag = CheckFlag(PSFlags::Carry);
|
||||
ClearFlags(PSFlags::Carry | PSFlags::Negative | PSFlags::Zero);
|
||||
|
||||
if(value & 0x80) {
|
||||
SetFlags(PSFlags::Carry);
|
||||
}
|
||||
|
||||
uint8_t result = (value << 1 | (carryFlag ? 0x01 : 0x00));
|
||||
SetZeroNegativeFlags(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint8_t ROR(uint8_t value) {
|
||||
bool carryFlag = CheckFlag(PSFlags::Carry);
|
||||
ClearFlags(PSFlags::Carry | PSFlags::Negative | PSFlags::Zero);
|
||||
if(value & 0x01) {
|
||||
SetFlags(PSFlags::Carry);
|
||||
}
|
||||
|
||||
uint8_t result = (value >> 1 | (carryFlag ? 0x80 : 0x00));
|
||||
SetZeroNegativeFlags(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ASLAddr() {
|
||||
uint16_t addr = GetOperand();
|
||||
uint8_t value = MemoryRead(addr);
|
||||
MemoryWrite(addr, value, MemoryOperationType::DummyWrite); //Dummy write
|
||||
MemoryWrite(addr, ASL(value));
|
||||
}
|
||||
|
||||
void LSRAddr() {
|
||||
uint16_t addr = GetOperand();
|
||||
uint8_t value = MemoryRead(addr);
|
||||
MemoryWrite(addr, value, MemoryOperationType::DummyWrite); //Dummy write
|
||||
MemoryWrite(addr, LSR(value));
|
||||
}
|
||||
|
||||
void ROLAddr() {
|
||||
uint16_t addr = GetOperand();
|
||||
uint8_t value = MemoryRead(addr);
|
||||
MemoryWrite(addr, value, MemoryOperationType::DummyWrite); //Dummy write
|
||||
MemoryWrite(addr, ROL(value));
|
||||
}
|
||||
|
||||
void RORAddr() {
|
||||
uint16_t addr = GetOperand();
|
||||
uint8_t value = MemoryRead(addr);
|
||||
MemoryWrite(addr, value, MemoryOperationType::DummyWrite); //Dummy write
|
||||
MemoryWrite(addr, ROR(value));
|
||||
}
|
||||
|
||||
void JMP(uint16_t addr) {
|
||||
SetPC(addr);
|
||||
}
|
||||
|
||||
void BranchRelative(bool branch) {
|
||||
int8_t offset = (int8_t)GetOperand();
|
||||
if(branch) {
|
||||
//"a taken non-page-crossing branch ignores IRQ/NMI during its last clock, so that next instruction executes before the IRQ"
|
||||
//Fixes "branch_delays_irq" test
|
||||
if(_runIrq && !_prevRunIrq) {
|
||||
_runIrq = false;
|
||||
}
|
||||
DummyRead();
|
||||
|
||||
if(CheckPageCrossed(PC(), offset)) {
|
||||
DummyRead();
|
||||
}
|
||||
|
||||
SetPC(PC() + offset);
|
||||
}
|
||||
}
|
||||
|
||||
void BIT() {
|
||||
uint8_t value = GetOperandValue();
|
||||
ClearFlags(PSFlags::Zero | PSFlags::Overflow | PSFlags::Negative);
|
||||
if((A() & value) == 0) {
|
||||
SetFlags(PSFlags::Zero);
|
||||
}
|
||||
if(value & 0x40) {
|
||||
SetFlags(PSFlags::Overflow);
|
||||
}
|
||||
if(value & 0x80) {
|
||||
SetFlags(PSFlags::Negative);
|
||||
}
|
||||
}
|
||||
|
||||
//OP Codes
|
||||
void LDA() { SetA(GetOperandValue()); }
|
||||
void LDX() { SetX(GetOperandValue()); }
|
||||
void LDY() { SetY(GetOperandValue()); }
|
||||
|
||||
void STA() { MemoryWrite(GetOperand(), A()); }
|
||||
void STX() { MemoryWrite(GetOperand(), X()); }
|
||||
void STY() { MemoryWrite(GetOperand(), Y()); }
|
||||
|
||||
void TAX() { SetX(A()); }
|
||||
void TAY() { SetY(A()); }
|
||||
void TSX() { SetX(SP()); }
|
||||
void TXA() { SetA(X()); }
|
||||
void TXS() { SetSP(X()); }
|
||||
void TYA() { SetA(Y()); }
|
||||
|
||||
void PHA() { Push(A()); }
|
||||
void PHP() {
|
||||
uint8_t flags = PS() | PSFlags::Break | PSFlags::Reserved;
|
||||
Push((uint8_t)flags);
|
||||
}
|
||||
void PLA() {
|
||||
DummyRead();
|
||||
SetA(Pop());
|
||||
}
|
||||
void PLP() {
|
||||
DummyRead();
|
||||
SetPS(Pop());
|
||||
}
|
||||
|
||||
void INX() { SetX(X() + 1); }
|
||||
void INY() { SetY(Y() + 1); }
|
||||
|
||||
void DEX() { SetX(X() - 1); }
|
||||
void DEY() { SetY(Y() - 1); }
|
||||
|
||||
void ASL_Acc() { SetA(ASL(A())); }
|
||||
void ASL_Memory() { ASLAddr(); }
|
||||
|
||||
void LSR_Acc() { SetA(LSR(A())); }
|
||||
void LSR_Memory() { LSRAddr(); }
|
||||
|
||||
void ROL_Acc() { SetA(ROL(A())); }
|
||||
void ROL_Memory() { ROLAddr(); }
|
||||
|
||||
void ROR_Acc() { SetA(ROR(A())); }
|
||||
void ROR_Memory() { RORAddr(); }
|
||||
|
||||
void JMP_Abs() {
|
||||
JMP(GetOperand());
|
||||
}
|
||||
void JMP_Ind() { JMP(GetInd()); }
|
||||
void JSR() {
|
||||
uint16_t addr = GetOperand();
|
||||
DummyRead();
|
||||
Push((uint16_t)(PC() - 1));
|
||||
JMP(addr);
|
||||
}
|
||||
void RTS() {
|
||||
uint16_t addr = PopWord();
|
||||
DummyRead();
|
||||
DummyRead();
|
||||
SetPC(addr + 1);
|
||||
}
|
||||
|
||||
void BCC() {
|
||||
BranchRelative(!CheckFlag(PSFlags::Carry));
|
||||
}
|
||||
|
||||
void BCS() {
|
||||
BranchRelative(CheckFlag(PSFlags::Carry));
|
||||
}
|
||||
|
||||
void BEQ() {
|
||||
BranchRelative(CheckFlag(PSFlags::Zero));
|
||||
}
|
||||
|
||||
void BMI() {
|
||||
BranchRelative(CheckFlag(PSFlags::Negative));
|
||||
}
|
||||
|
||||
void BNE() {
|
||||
BranchRelative(!CheckFlag(PSFlags::Zero));
|
||||
}
|
||||
|
||||
void BPL() {
|
||||
BranchRelative(!CheckFlag(PSFlags::Negative));
|
||||
}
|
||||
|
||||
void BVC() {
|
||||
BranchRelative(!CheckFlag(PSFlags::Overflow));
|
||||
}
|
||||
|
||||
void BVS() {
|
||||
BranchRelative(CheckFlag(PSFlags::Overflow));
|
||||
}
|
||||
|
||||
void CLC() { ClearFlags(PSFlags::Carry); }
|
||||
void CLD() { ClearFlags(PSFlags::Decimal); }
|
||||
void CLI() { ClearFlags(PSFlags::Interrupt); }
|
||||
void CLV() { ClearFlags(PSFlags::Overflow); }
|
||||
void SEC() { SetFlags(PSFlags::Carry); }
|
||||
void SED() { SetFlags(PSFlags::Decimal); }
|
||||
void SEI() { SetFlags(PSFlags::Interrupt); }
|
||||
|
||||
void BRK();
|
||||
|
||||
void RTI() {
|
||||
DummyRead();
|
||||
SetPS(Pop());
|
||||
SetPC(PopWord());
|
||||
}
|
||||
|
||||
void NOP() {
|
||||
//Make sure the nop operation takes as many cycles as meant to
|
||||
GetOperandValue();
|
||||
}
|
||||
|
||||
|
||||
//Unofficial OpCodes
|
||||
void SLO()
|
||||
{
|
||||
//ASL & ORA
|
||||
uint8_t value = GetOperandValue();
|
||||
MemoryWrite(GetOperand(), value, MemoryOperationType::DummyWrite); //Dummy write
|
||||
uint8_t shiftedValue = ASL(value);
|
||||
SetA(A() | shiftedValue);
|
||||
MemoryWrite(GetOperand(), shiftedValue);
|
||||
}
|
||||
|
||||
void SRE()
|
||||
{
|
||||
//ROL & AND
|
||||
uint8_t value = GetOperandValue();
|
||||
MemoryWrite(GetOperand(), value, MemoryOperationType::DummyWrite); //Dummy write
|
||||
uint8_t shiftedValue = LSR(value);
|
||||
SetA(A() ^ shiftedValue);
|
||||
MemoryWrite(GetOperand(), shiftedValue);
|
||||
}
|
||||
|
||||
void RLA()
|
||||
{
|
||||
//LSR & EOR
|
||||
uint8_t value = GetOperandValue();
|
||||
MemoryWrite(GetOperand(), value, MemoryOperationType::DummyWrite); //Dummy write
|
||||
uint8_t shiftedValue = ROL(value);
|
||||
SetA(A() & shiftedValue);
|
||||
MemoryWrite(GetOperand(), shiftedValue);
|
||||
}
|
||||
|
||||
void RRA()
|
||||
{
|
||||
//ROR & ADC
|
||||
uint8_t value = GetOperandValue();
|
||||
MemoryWrite(GetOperand(), value, MemoryOperationType::DummyWrite); //Dummy write
|
||||
uint8_t shiftedValue = ROR(value);
|
||||
ADD(shiftedValue);
|
||||
MemoryWrite(GetOperand(), shiftedValue);
|
||||
}
|
||||
|
||||
void SAX()
|
||||
{
|
||||
//STA & STX
|
||||
MemoryWrite(GetOperand(), A() & X());
|
||||
}
|
||||
|
||||
void LAX()
|
||||
{
|
||||
//LDA & LDX
|
||||
uint8_t value = GetOperandValue();
|
||||
SetX(value);
|
||||
SetA(value);
|
||||
}
|
||||
|
||||
void DCP()
|
||||
{
|
||||
//DEC & CMP
|
||||
uint8_t value = GetOperandValue();
|
||||
MemoryWrite(GetOperand(), value, MemoryOperationType::DummyWrite); //Dummy write
|
||||
value--;
|
||||
CMP(A(), value);
|
||||
MemoryWrite(GetOperand(), value);
|
||||
}
|
||||
|
||||
void ISB()
|
||||
{
|
||||
//INC & SBC
|
||||
uint8_t value = GetOperandValue();
|
||||
MemoryWrite(GetOperand(), value, MemoryOperationType::DummyWrite); //Dummy write
|
||||
value++;
|
||||
ADD(value ^ 0xFF);
|
||||
MemoryWrite(GetOperand(), value);
|
||||
}
|
||||
|
||||
void AAC()
|
||||
{
|
||||
SetA(A() & GetOperandValue());
|
||||
|
||||
ClearFlags(PSFlags::Carry);
|
||||
if(CheckFlag(PSFlags::Negative)) {
|
||||
SetFlags(PSFlags::Carry);
|
||||
}
|
||||
}
|
||||
|
||||
void ASR()
|
||||
{
|
||||
ClearFlags(PSFlags::Carry);
|
||||
SetA(A() & GetOperandValue());
|
||||
if(A() & 0x01) {
|
||||
SetFlags(PSFlags::Carry);
|
||||
}
|
||||
SetA(A() >> 1);
|
||||
}
|
||||
|
||||
void ARR()
|
||||
{
|
||||
SetA(((A() & GetOperandValue()) >> 1) | (CheckFlag(PSFlags::Carry) ? 0x80 : 0x00));
|
||||
ClearFlags(PSFlags::Carry | PSFlags::Overflow);
|
||||
if(A() & 0x40) {
|
||||
SetFlags(PSFlags::Carry);
|
||||
}
|
||||
if((CheckFlag(PSFlags::Carry) ? 0x01 : 0x00) ^ ((A() >> 5) & 0x01)) {
|
||||
SetFlags(PSFlags::Overflow);
|
||||
}
|
||||
}
|
||||
|
||||
void ATX()
|
||||
{
|
||||
//LDA & TAX
|
||||
uint8_t value = GetOperandValue();
|
||||
SetA(value); //LDA
|
||||
SetX(A()); //TAX
|
||||
SetA(A()); //Update flags based on A
|
||||
}
|
||||
|
||||
void AXS()
|
||||
{
|
||||
//CMP & DEX
|
||||
uint8_t opValue = GetOperandValue();
|
||||
uint8_t value = (A() & X()) - opValue;
|
||||
|
||||
ClearFlags(PSFlags::Carry);
|
||||
if((A() & X()) >= opValue) {
|
||||
SetFlags(PSFlags::Carry);
|
||||
}
|
||||
|
||||
SetX(value);
|
||||
}
|
||||
|
||||
void SYA()
|
||||
{
|
||||
uint8_t addrHigh = GetOperand() >> 8;
|
||||
uint8_t addrLow = GetOperand() & 0xFF;
|
||||
uint8_t value = Y() & (addrHigh + 1);
|
||||
|
||||
//From here: http://forums.nesdev.com/viewtopic.php?f=3&t=3831&start=30
|
||||
//Unsure if this is accurate or not
|
||||
//"the target address for e.g. SYA becomes ((y & (addr_high + 1)) << 8) | addr_low instead of the normal ((addr_high + 1) << 8) | addr_low"
|
||||
MemoryWrite(((Y() & (addrHigh + 1)) << 8) | addrLow, value);
|
||||
}
|
||||
|
||||
void SXA()
|
||||
{
|
||||
uint8_t addrHigh = GetOperand() >> 8;
|
||||
uint8_t addrLow = GetOperand() & 0xFF;
|
||||
uint8_t value = X() & (addrHigh + 1);
|
||||
MemoryWrite(((X() & (addrHigh + 1)) << 8) | addrLow, value);
|
||||
}
|
||||
|
||||
//Unimplemented/Incorrect Unofficial OP codes
|
||||
void HLT()
|
||||
{
|
||||
//normally freezes the cpu, we can probably assume nothing will ever call this
|
||||
GetOperandValue();
|
||||
}
|
||||
|
||||
void UNK()
|
||||
{
|
||||
//Make sure we take the right amount of cycles (not reliable for operations that write to memory, etc.)
|
||||
GetOperandValue();
|
||||
}
|
||||
|
||||
void AXA()
|
||||
{
|
||||
uint16_t addr = GetOperand();
|
||||
|
||||
//"This opcode stores the result of A AND X AND the high byte of the target address of the operand +1 in memory."
|
||||
//This may not be the actual behavior, but the read/write operations are needed for proper cycle counting
|
||||
MemoryWrite(GetOperand(), ((addr >> 8) + 1) & A() & X());
|
||||
}
|
||||
|
||||
void TAS()
|
||||
{
|
||||
//"AND X register with accumulator and store result in stack
|
||||
//pointer, then AND stack pointer with the high byte of the
|
||||
//target address of the argument + 1. Store result in memory."
|
||||
uint16_t addr = GetOperand();
|
||||
SetSP(X() & A());
|
||||
MemoryWrite(addr, SP() & ((addr >> 8) + 1));
|
||||
}
|
||||
|
||||
void LAS()
|
||||
{
|
||||
//"AND memory with stack pointer, transfer result to accumulator, X register and stack pointer."
|
||||
uint8_t value = GetOperandValue();
|
||||
SetA(value & SP());
|
||||
SetX(A());
|
||||
SetSP(A());
|
||||
}
|
||||
|
||||
protected:
|
||||
void StreamState(bool saving) override;
|
||||
|
||||
public:
|
||||
CPU(shared_ptr<Console> console);
|
||||
|
||||
uint64_t GetCycleCount() { return _cycleCount; }
|
||||
void SetMasterClockDivider(NesModel region);
|
||||
void SetNmiFlag() { _state.NMIFlag = true; }
|
||||
void ClearNmiFlag() { _state.NMIFlag = false; }
|
||||
void SetIrqMask(uint8_t mask) { _irqMask = mask; }
|
||||
void SetIrqSource(IRQSource source) { _state.IRQFlag |= (int)source; }
|
||||
bool HasIrqSource(IRQSource source) { return (_state.IRQFlag & (int)source) != 0; }
|
||||
void ClearIrqSource(IRQSource source) { _state.IRQFlag &= ~(int)source; }
|
||||
|
||||
void RunDMATransfer(uint8_t offsetValue);
|
||||
void StartDmcTransfer();
|
||||
|
||||
uint32_t GetClockRate(NesModel model);
|
||||
bool IsCpuWrite() { return _cpuWrite; }
|
||||
|
||||
//Used by debugger for "Set Next Statement"
|
||||
void SetDebugPC(uint16_t value) {
|
||||
SetPC(value);
|
||||
_state.PreviousDebugPC = _state.DebugPC;
|
||||
_state.DebugPC = value;
|
||||
}
|
||||
|
||||
void Reset(bool softReset, NesModel model);
|
||||
void Exec();
|
||||
|
||||
void GetState(State &state)
|
||||
{
|
||||
state = _state;
|
||||
state.CycleCount = _cycleCount;
|
||||
}
|
||||
|
||||
uint16_t GetDebugPC() { return _state.DebugPC; }
|
||||
uint16_t GetPC() { return _state.PC; }
|
||||
|
||||
void SetState(State state)
|
||||
{
|
||||
uint16_t originalPc = state.PC;
|
||||
uint16_t originalDebugPc = state.DebugPC;
|
||||
_state = state;
|
||||
_cycleCount = state.CycleCount;
|
||||
state.PC = originalPc;
|
||||
state.DebugPC = originalDebugPc;
|
||||
}
|
||||
|
||||
#ifdef DUMMYCPU
|
||||
#undef CPU
|
||||
void SetDummyState(CPU *c)
|
||||
{
|
||||
#define CPU DummyCpu
|
||||
_writeCounter = 0;
|
||||
_readCounter = 0;
|
||||
|
||||
_state = c->_state;
|
||||
|
||||
_cycleCount = c->_cycleCount;
|
||||
_operand = c->_operand;
|
||||
_spriteDmaTransfer = c->_spriteDmaTransfer;
|
||||
_needHalt = c->_needHalt;
|
||||
_dmcDmaRunning = c->_dmcDmaRunning;
|
||||
_cpuWrite = c->_cpuWrite;
|
||||
_needDummyRead = c->_needDummyRead;
|
||||
_needHalt = c->_needHalt;
|
||||
_spriteDmaOffset = c->_spriteDmaOffset;
|
||||
_irqMask = c->_irqMask;
|
||||
_prevRunIrq = c->_prevRunIrq;
|
||||
_runIrq = c->_runIrq;
|
||||
_cycleCount = c->_cycleCount;
|
||||
}
|
||||
|
||||
uint32_t GetWriteCount()
|
||||
{
|
||||
return _writeCounter;
|
||||
}
|
||||
|
||||
uint32_t GetReadCount()
|
||||
{
|
||||
return _readCounter;
|
||||
}
|
||||
|
||||
void GetWriteAddrValue(uint32_t index, uint16_t &addr, uint8_t &value, bool &isDummyWrite)
|
||||
{
|
||||
addr = _writeAddresses[index];
|
||||
value = _writeValue[index];
|
||||
isDummyWrite = _isDummyWrite[index];
|
||||
}
|
||||
|
||||
void GetReadAddr(uint32_t index, uint16_t &addr, uint8_t &value, bool &isDummyRead)
|
||||
{
|
||||
addr = _readAddresses[index];
|
||||
value = _readValue[index];
|
||||
isDummyRead = _isDummyRead[index];
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,52 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Caltron41 : public BaseMapper
|
||||
{
|
||||
private:
|
||||
uint8_t _prgBank;
|
||||
uint8_t _chrBank;
|
||||
|
||||
protected:
|
||||
virtual uint16_t GetPRGPageSize() override { return 0x8000; }
|
||||
virtual uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
virtual uint16_t RegisterStartAddress() override { return 0x8000; }
|
||||
virtual uint16_t RegisterEndAddress() override { return 0xFFFF; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
AddRegisterRange(0x6000, 0x67FF, MemoryOperation::Write);
|
||||
}
|
||||
|
||||
void Reset(bool softReset) override
|
||||
{
|
||||
_chrBank = 0;
|
||||
_prgBank = 0;
|
||||
WriteRegister(0x6000, 0);
|
||||
WriteRegister(0x8000, 0);
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
Stream(_prgBank, _chrBank);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if(addr <= 0x67FF) {
|
||||
_prgBank = addr & 0x07;
|
||||
_chrBank = (_chrBank & 0x03) | ((addr >> 1) & 0x0C);
|
||||
SelectPRGPage(0, _prgBank);
|
||||
SelectCHRPage(0, _chrBank);
|
||||
SetMirroringType(addr & 0x20 ? MirroringType::Horizontal : MirroringType::Vertical);
|
||||
} else {
|
||||
//"Note that the Inner CHR Bank Select only can be written while the PRG ROM bank is 4, 5, 6, or 7"
|
||||
if(_prgBank >= 4) {
|
||||
_chrBank = (_chrBank & 0x0C) | (value & 0x03);
|
||||
SelectCHRPage(0, _chrBank);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
35
Core/Cc21.h
35
Core/Cc21.h
|
@ -1,35 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
|
||||
class Cc21 : public BaseMapper
|
||||
{
|
||||
protected:
|
||||
uint16_t GetPRGPageSize() override { return 0x8000; }
|
||||
uint16_t GetCHRPageSize() override { return 0x1000; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
SelectPRGPage(0, 0);
|
||||
SelectCHRPage(0, 0);
|
||||
SelectCHRPage(1, 0);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
uint8_t latch = (uint8_t)addr;
|
||||
if(addr == 0x8000) {
|
||||
latch = value;
|
||||
}
|
||||
|
||||
if(_chrRomSize == 0x2000) {
|
||||
SelectCHRPage(0, latch & 0x01);
|
||||
SelectCHRPage(1, latch & 0x01);
|
||||
} else {
|
||||
//Overdumped roms
|
||||
SelectChrPage2x(0, (latch & 0x01) << 1);
|
||||
}
|
||||
|
||||
SetMirroringType(latch & 0x01 ? MirroringType::ScreenBOnly : MirroringType::ScreenAOnly);
|
||||
}
|
||||
};
|
|
@ -1,91 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
#include "FlashSST39SF040.h"
|
||||
#include "../Utilities/IpsPatcher.h"
|
||||
|
||||
class Cheapocabra : public BaseMapper
|
||||
{
|
||||
private:
|
||||
unique_ptr<FlashSST39SF040> _flash;
|
||||
uint8_t _prgReg = 0;
|
||||
vector<uint8_t> _orgPrgRom;
|
||||
|
||||
protected:
|
||||
uint16_t GetPRGPageSize() override { return 0x8000; }
|
||||
uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
uint32_t GetWorkRamSize() override { return 0; }
|
||||
uint32_t GetSaveRamSize() override { return 0; }
|
||||
uint16_t RegisterStartAddress() override { return 0x5000; }
|
||||
uint16_t RegisterEndAddress() override { return 0x5FFF; }
|
||||
uint32_t GetChrRamSize() override { return 0x4000; }
|
||||
bool AllowRegisterRead() override { return true; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
AddRegisterRange(0x7000, 0x7FFF, MemoryOperation::Write);
|
||||
|
||||
_flash.reset(new FlashSST39SF040(_prgRom, _prgSize));
|
||||
AddRegisterRange(0x8000, 0xFFFF, MemoryOperation::Any);
|
||||
RemoveRegisterRange(0x5000, 0x5FFF, MemoryOperation::Read);
|
||||
|
||||
WriteRegister(0x5000, GetPowerOnByte());
|
||||
|
||||
_orgPrgRom = vector<uint8_t>(_prgRom, _prgRom + _prgSize);
|
||||
ApplySaveData();
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
|
||||
SnapshotInfo flash { _flash.get() };
|
||||
Stream(_prgReg, flash);
|
||||
}
|
||||
|
||||
void ApplySaveData()
|
||||
{
|
||||
//Apply save data (saved as an IPS file), if found
|
||||
vector<uint8_t> ipsData = _console->GetBatteryManager()->LoadBattery(".ips");
|
||||
if(!ipsData.empty()) {
|
||||
vector<uint8_t> patchedPrgRom;
|
||||
if(IpsPatcher::PatchBuffer(ipsData, _orgPrgRom, patchedPrgRom)) {
|
||||
memcpy(_prgRom, patchedPrgRom.data(), _prgSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SaveBattery() override
|
||||
{
|
||||
vector<uint8_t> prgRom = vector<uint8_t>(_prgRom, _prgRom + _prgSize);
|
||||
vector<uint8_t> ipsData = IpsPatcher::CreatePatch(_orgPrgRom, prgRom);
|
||||
if(ipsData.size() > 8) {
|
||||
_console->GetBatteryManager()->SaveBattery(".ips", ipsData.data(), (uint32_t)ipsData.size());
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ReadRegister(uint16_t addr) override
|
||||
{
|
||||
int16_t value = _flash->Read(addr);
|
||||
if(value >= 0) {
|
||||
return (uint8_t)value;
|
||||
}
|
||||
|
||||
return BaseMapper::InternalReadRam(addr);
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
if(addr < 0x8000) {
|
||||
_prgReg = value & 0x0F;
|
||||
SelectPRGPage(0, _prgReg);
|
||||
|
||||
SelectCHRPage(0, (value >> 4) & 0x01);
|
||||
for(int i = 0; i < 8; i++) {
|
||||
SetNametable(i, ((value & 0x20) ? 8 : 0) + i);
|
||||
}
|
||||
} else {
|
||||
_flash->Write((_prgReg << 15) | (addr & 0x7FFF), value);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,218 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
|
||||
#include "CheatManager.h"
|
||||
#include "Console.h"
|
||||
#include "BaseMapper.h"
|
||||
#include "MessageManager.h"
|
||||
#include "NotificationManager.h"
|
||||
|
||||
CheatManager::CheatManager(shared_ptr<Console> console)
|
||||
{
|
||||
_console = console;
|
||||
|
||||
for(int i = 0; i <= 0xFFFF; i++) {
|
||||
_relativeCheatCodes.push_back(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t CheatManager::DecodeValue(uint32_t code, uint32_t* bitIndexes, uint32_t bitCount)
|
||||
{
|
||||
uint32_t result = 0;
|
||||
for(uint32_t i = 0; i < bitCount; i++) {
|
||||
result <<= 1;
|
||||
result |= (code >> bitIndexes[i]) & 0x01;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CodeInfo CheatManager::GetGGCodeInfo(string ggCode)
|
||||
{
|
||||
string ggLetters = "APZLGITYEOXUKSVN";
|
||||
|
||||
uint32_t rawCode = 0;
|
||||
for(size_t i = 0, len = ggCode.size(); i < len; i++) {
|
||||
rawCode |= ggLetters.find(ggCode[i]) << (i * 4);
|
||||
}
|
||||
|
||||
CodeInfo code = { };
|
||||
code.IsRelativeAddress = true;
|
||||
code.CompareValue = -1;
|
||||
uint32_t addressBits[15] = { 14, 13, 12, 19, 22, 21, 20, 7, 10, 9, 8, 15, 18, 17, 16 };
|
||||
uint32_t valueBits[8] = { 3, 6, 5, 4, 23, 2, 1, 0 };
|
||||
if(ggCode.size() == 8) {
|
||||
//Bit 5 of the value is stored in a different location for 8-character codes
|
||||
valueBits[4] = 31;
|
||||
|
||||
uint32_t compareValueBits[8] = { 27, 30, 29, 28, 23, 26, 25, 24 };
|
||||
code.CompareValue = DecodeValue(rawCode, compareValueBits, 8);
|
||||
}
|
||||
code.Address = DecodeValue(rawCode, addressBits, 15) + 0x8000;
|
||||
code.Value = DecodeValue(rawCode, valueBits, 8);
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
CodeInfo CheatManager::GetPARCodeInfo(uint32_t parCode)
|
||||
{
|
||||
uint32_t shiftValues[31] = {
|
||||
3, 13, 14, 1, 6, 9, 5, 0, 12, 7, 2, 8, 10, 11, 4, //address
|
||||
19, 21, 23, 22, 20, 17, 16, 18, //compare
|
||||
29, 31, 24, 26, 25, 30, 27, 28 //value
|
||||
};
|
||||
uint32_t key = 0x7E5EE93A;
|
||||
uint32_t xorValue = 0x5C184B91;
|
||||
|
||||
//Throw away bit 0, not used.
|
||||
parCode >>= 1;
|
||||
|
||||
uint32_t result = 0;
|
||||
for(int32_t i = 30; i >= 0; i--) {
|
||||
if(((key ^ parCode) >> 30) & 0x01) {
|
||||
result |= 0x01 << shiftValues[i];
|
||||
key ^= xorValue;
|
||||
}
|
||||
parCode <<= 1;
|
||||
key <<= 1;
|
||||
}
|
||||
|
||||
CodeInfo code = { };
|
||||
code.IsRelativeAddress = true;
|
||||
code.Address = (result & 0x7fff) + 0x8000;
|
||||
code.Value = (result >> 24) & 0xFF;
|
||||
code.CompareValue = (result >> 16) & 0xFF;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
void CheatManager::AddCode(CodeInfo &code)
|
||||
{
|
||||
if(code.IsRelativeAddress) {
|
||||
if(code.Address > 0xFFFF) {
|
||||
//Invalid cheat, ignore it
|
||||
return;
|
||||
}
|
||||
|
||||
if(_relativeCheatCodes[code.Address] == nullptr) {
|
||||
_relativeCheatCodes[code.Address].reset(new vector<CodeInfo>());
|
||||
}
|
||||
_relativeCheatCodes[code.Address]->push_back(code);
|
||||
} else {
|
||||
_absoluteCheatCodes.push_back(code);
|
||||
}
|
||||
_hasCode = true;
|
||||
_console->GetNotificationManager()->SendNotification(ConsoleNotificationType::CheatAdded);
|
||||
}
|
||||
|
||||
void CheatManager::AddGameGenieCode(string code)
|
||||
{
|
||||
CodeInfo info = GetGGCodeInfo(code);
|
||||
AddCode(info);
|
||||
}
|
||||
|
||||
void CheatManager::AddProActionRockyCode(uint32_t code)
|
||||
{
|
||||
CodeInfo info = GetPARCodeInfo(code);
|
||||
AddCode(info);
|
||||
}
|
||||
|
||||
void CheatManager::AddCustomCode(uint32_t address, uint8_t value, int32_t compareValue, bool isRelativeAddress)
|
||||
{
|
||||
CodeInfo code;
|
||||
code.Address = address;
|
||||
code.Value = value;
|
||||
code.CompareValue = compareValue;
|
||||
code.IsRelativeAddress = isRelativeAddress;
|
||||
|
||||
AddCode(code);
|
||||
}
|
||||
|
||||
void CheatManager::ClearCodes()
|
||||
{
|
||||
bool cheatRemoved = false;
|
||||
|
||||
for(int i = 0; i <= 0xFFFF; i++) {
|
||||
if(!_relativeCheatCodes[i]) {
|
||||
cheatRemoved = true;
|
||||
}
|
||||
_relativeCheatCodes[i].reset();
|
||||
}
|
||||
|
||||
cheatRemoved |= _absoluteCheatCodes.size() > 0;
|
||||
_absoluteCheatCodes.clear();
|
||||
_hasCode = false;
|
||||
|
||||
if(cheatRemoved) {
|
||||
_console->GetNotificationManager()->SendNotification(ConsoleNotificationType::CheatRemoved);
|
||||
}
|
||||
}
|
||||
|
||||
void CheatManager::ApplyCodes(uint16_t addr, uint8_t &value)
|
||||
{
|
||||
if(!_hasCode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(_relativeCheatCodes[addr] != nullptr) {
|
||||
for(uint32_t i = 0, len = i < _relativeCheatCodes[addr]->size(); i < len; i++) {
|
||||
CodeInfo code = _relativeCheatCodes[addr]->at(i);
|
||||
if(code.CompareValue == -1 || code.CompareValue == value) {
|
||||
value = code.Value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if(!_absoluteCheatCodes.empty()) {
|
||||
int32_t absAddr = _console->GetMapper()->ToAbsoluteAddress(addr);
|
||||
if(absAddr >= 0) {
|
||||
for(CodeInfo &code : _absoluteCheatCodes) {
|
||||
if(code.Address == (uint32_t)absAddr && (code.CompareValue == -1 || code.CompareValue == value)) {
|
||||
value = code.Value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vector<CodeInfo> CheatManager::GetCheats()
|
||||
{
|
||||
//Used by NetPlay
|
||||
vector<CodeInfo> cheats;
|
||||
for(unique_ptr<vector<CodeInfo>> &codes : _relativeCheatCodes) {
|
||||
if(codes) {
|
||||
std::copy(codes.get()->begin(), codes.get()->end(), std::back_inserter(cheats));
|
||||
}
|
||||
}
|
||||
std::copy(_absoluteCheatCodes.begin(), _absoluteCheatCodes.end(), std::back_inserter(cheats));
|
||||
return cheats;
|
||||
}
|
||||
|
||||
void CheatManager::SetCheats(CheatInfo cheats[], uint32_t length)
|
||||
{
|
||||
_console->Pause();
|
||||
|
||||
ClearCodes();
|
||||
|
||||
for(uint32_t i = 0; i < length; i++) {
|
||||
CheatInfo &cheat = cheats[i];
|
||||
switch(cheat.Type) {
|
||||
case CheatType::Custom: AddCustomCode(cheat.Address, cheat.Value, cheat.UseCompareValue ? cheat.CompareValue : -1, cheat.IsRelativeAddress); break;
|
||||
case CheatType::GameGenie: AddGameGenieCode(cheat.GameGenieCode); break;
|
||||
case CheatType::ProActionRocky: AddProActionRockyCode(cheat.ProActionRockyCode); break;
|
||||
}
|
||||
}
|
||||
|
||||
_console->Resume();
|
||||
}
|
||||
|
||||
void CheatManager::SetCheats(vector<CodeInfo> &cheats)
|
||||
{
|
||||
//Used by NetPlay
|
||||
ClearCodes();
|
||||
|
||||
if(cheats.size() > 0) {
|
||||
MessageManager::DisplayMessage("Cheats", cheats.size() > 1 ? "CheatsApplied" : "CheatApplied", std::to_string(cheats.size()));
|
||||
for(CodeInfo &cheat : cheats) {
|
||||
AddCode(cheat);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
class Console;
|
||||
|
||||
struct CodeInfo
|
||||
{
|
||||
uint32_t Address;
|
||||
uint8_t Value;
|
||||
int32_t CompareValue;
|
||||
bool IsRelativeAddress;
|
||||
};
|
||||
|
||||
enum class CheatType
|
||||
{
|
||||
GameGenie = 0,
|
||||
ProActionRocky = 1,
|
||||
Custom = 2
|
||||
};
|
||||
|
||||
struct CheatInfo
|
||||
{
|
||||
CheatType Type;
|
||||
uint32_t ProActionRockyCode;
|
||||
uint32_t Address;
|
||||
char GameGenieCode[9];
|
||||
uint8_t Value;
|
||||
uint8_t CompareValue;
|
||||
bool UseCompareValue;
|
||||
bool IsRelativeAddress;
|
||||
};
|
||||
|
||||
class CheatManager
|
||||
{
|
||||
private:
|
||||
shared_ptr<Console> _console;
|
||||
|
||||
bool _hasCode = false;
|
||||
vector<unique_ptr<vector<CodeInfo>>> _relativeCheatCodes;
|
||||
vector<CodeInfo> _absoluteCheatCodes;
|
||||
|
||||
uint32_t DecodeValue(uint32_t code, uint32_t* bitIndexes, uint32_t bitCount);
|
||||
CodeInfo GetGGCodeInfo(string ggCode);
|
||||
CodeInfo GetPARCodeInfo(uint32_t parCode);
|
||||
void AddCode(CodeInfo &code);
|
||||
|
||||
public:
|
||||
CheatManager(shared_ptr<Console> console);
|
||||
|
||||
void AddGameGenieCode(string code);
|
||||
void AddProActionRockyCode(uint32_t code);
|
||||
void AddCustomCode(uint32_t address, uint8_t value, int32_t compareValue = -1, bool isRelativeAddress = true);
|
||||
void ClearCodes();
|
||||
|
||||
vector<CodeInfo> GetCheats();
|
||||
void SetCheats(vector<CodeInfo> &cheats);
|
||||
void SetCheats(CheatInfo cheats[], uint32_t length);
|
||||
|
||||
void ApplyCodes(uint16_t addr, uint8_t &value);
|
||||
};
|
|
@ -1,115 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "BaseMapper.h"
|
||||
#include "MemoryManager.h"
|
||||
#include "CPU.h"
|
||||
|
||||
class CityFighter : public BaseMapper
|
||||
{
|
||||
private:
|
||||
uint8_t _prgReg;
|
||||
uint8_t _prgMode;
|
||||
uint8_t _mirroring;
|
||||
uint8_t _chrRegs[8];
|
||||
bool _irqEnabled;
|
||||
uint16_t _irqCounter;
|
||||
|
||||
protected:
|
||||
uint16_t GetPRGPageSize() override { return 0x2000; }
|
||||
uint16_t GetCHRPageSize() override { return 0x400; }
|
||||
|
||||
void InitMapper() override
|
||||
{
|
||||
_prgReg = 0;
|
||||
_prgMode = 0;
|
||||
_mirroring = 0;
|
||||
_irqCounter = 0;
|
||||
_irqEnabled = false;
|
||||
memset(_chrRegs, 0, sizeof(_chrRegs));
|
||||
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
ArrayInfo<uint8_t> chrRegs { _chrRegs, 8 };
|
||||
Stream(_prgReg, _prgMode, _mirroring, _irqEnabled, _irqCounter, chrRegs);
|
||||
}
|
||||
|
||||
void UpdateState()
|
||||
{
|
||||
SelectPrgPage4x(0x8000, _prgReg);
|
||||
if(!_prgMode) {
|
||||
SelectPRGPage(2, _prgReg);
|
||||
}
|
||||
|
||||
for(int i = 0; i < 8; i++) {
|
||||
SelectCHRPage(i, _chrRegs[i]);
|
||||
}
|
||||
|
||||
switch(_mirroring) {
|
||||
case 0: SetMirroringType(MirroringType::Vertical); break;
|
||||
case 1: SetMirroringType(MirroringType::Horizontal); break;
|
||||
case 2: SetMirroringType(MirroringType::ScreenAOnly); break;
|
||||
case 3: SetMirroringType(MirroringType::ScreenBOnly); break;
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessCpuClock() override
|
||||
{
|
||||
if(_irqEnabled) {
|
||||
_irqCounter--;
|
||||
if(_irqCounter == 0) {
|
||||
_console->GetCpu()->SetIrqSource(IRQSource::External);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
switch(addr & 0xF00C) {
|
||||
case 0x9000:
|
||||
_prgReg = value & 0x0C;
|
||||
_mirroring = value & 0x03;
|
||||
break;
|
||||
|
||||
case 0x9004: case 0x9008: case 0x900C:
|
||||
if(addr & 0x800) {
|
||||
_console->GetMemoryManager()->Write(0x4011, (value & 0x0F) << 3, MemoryOperationType::Write);
|
||||
} else {
|
||||
_prgReg = value & 0x0C;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xC000: case 0xC004: case 0xC008: case 0xC00C:
|
||||
_prgMode = value & 0x01;
|
||||
break;
|
||||
|
||||
case 0xD000: _chrRegs[0] = (_chrRegs[0] & 0xF0) | (value & 0x0F); break;
|
||||
case 0xD004: _chrRegs[0] = (_chrRegs[0] & 0x0F) | (value << 4); break;
|
||||
case 0xD008: _chrRegs[1] = (_chrRegs[1] & 0xF0) | (value & 0x0F); break;
|
||||
case 0xD00C: _chrRegs[1] = (_chrRegs[1] & 0x0F) | (value << 4); break;
|
||||
case 0xA000: _chrRegs[2] = (_chrRegs[2] & 0xF0) | (value & 0x0F); break;
|
||||
case 0xA004: _chrRegs[2] = (_chrRegs[2] & 0x0F) | (value << 4); break;
|
||||
case 0xA008: _chrRegs[3] = (_chrRegs[3] & 0xF0) | (value & 0x0F); break;
|
||||
case 0xA00C: _chrRegs[3] = (_chrRegs[3] & 0x0F) | (value << 4); break;
|
||||
case 0xB000: _chrRegs[4] = (_chrRegs[4] & 0xF0) | (value & 0x0F); break;
|
||||
case 0xB004: _chrRegs[4] = (_chrRegs[4] & 0x0F) | (value << 4); break;
|
||||
case 0xB008: _chrRegs[5] = (_chrRegs[5] & 0xF0) | (value & 0x0F); break;
|
||||
case 0xB00C: _chrRegs[5] = (_chrRegs[5] & 0x0F) | (value << 4); break;
|
||||
case 0xE000: _chrRegs[6] = (_chrRegs[6] & 0xF0) | (value & 0x0F); break;
|
||||
case 0xE004: _chrRegs[6] = (_chrRegs[6] & 0x0F) | (value << 4); break;
|
||||
case 0xE008: _chrRegs[7] = (_chrRegs[7] & 0xF0) | (value & 0x0F); break;
|
||||
case 0xE00C: _chrRegs[7] = (_chrRegs[7] & 0x0F) | (value << 4); break;
|
||||
case 0xF000: _irqCounter = ((_irqCounter & 0x1E0) | ((value & 0x0F) << 1)); break;
|
||||
case 0xF004: _irqCounter = ((_irqCounter & 0x1E) | ((value & 0x0F) << 5)); break;
|
||||
case 0xF008:
|
||||
_irqEnabled = (value & 0x02) != 0;
|
||||
_console->GetCpu()->ClearIrqSource(IRQSource::External);
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateState();
|
||||
}
|
||||
};
|
|
@ -1,24 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
class ClientConnectionData
|
||||
{
|
||||
public:
|
||||
string Host;
|
||||
uint16_t Port;
|
||||
string Password;
|
||||
string PlayerName;
|
||||
bool Spectator;
|
||||
|
||||
ClientConnectionData() {}
|
||||
|
||||
ClientConnectionData(string host, uint16_t port, string password, string playerName, bool spectator) :
|
||||
Host(host), Port(port), Password(password), PlayerName(playerName), Spectator(spectator)
|
||||
{
|
||||
}
|
||||
|
||||
~ClientConnectionData()
|
||||
{
|
||||
}
|
||||
};
|
|
@ -1,235 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
#include "CodeDataLogger.h"
|
||||
#include "Debugger.h"
|
||||
#include "LabelManager.h"
|
||||
|
||||
CodeDataLogger::CodeDataLogger(Debugger *debugger, uint32_t prgSize, uint32_t chrSize)
|
||||
{
|
||||
_debugger = debugger;
|
||||
_prgSize = prgSize;
|
||||
_chrSize = chrSize;
|
||||
_cdlData = new uint8_t[prgSize+chrSize];
|
||||
Reset();
|
||||
}
|
||||
|
||||
CodeDataLogger::~CodeDataLogger()
|
||||
{
|
||||
delete[] _cdlData;
|
||||
}
|
||||
|
||||
void CodeDataLogger::Reset()
|
||||
{
|
||||
_codeSize = 0;
|
||||
_dataSize = 0;
|
||||
_usedChrSize = 0;
|
||||
_drawnChrSize = 0;
|
||||
_readChrSize = 0;
|
||||
memset(_cdlData, 0, _prgSize + _chrSize);
|
||||
}
|
||||
|
||||
bool CodeDataLogger::LoadCdlFile(string cdlFilepath)
|
||||
{
|
||||
ifstream cdlFile(cdlFilepath, ios::in | ios::binary);
|
||||
if(cdlFile) {
|
||||
cdlFile.seekg(0, std::ios::end);
|
||||
size_t fileSize = (size_t)cdlFile.tellg();
|
||||
cdlFile.seekg(0, std::ios::beg);
|
||||
|
||||
if(fileSize == _prgSize + _chrSize) {
|
||||
Reset();
|
||||
|
||||
cdlFile.read((char*)_cdlData, _prgSize + _chrSize);
|
||||
cdlFile.close();
|
||||
|
||||
CalculateStats();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CodeDataLogger::CalculateStats()
|
||||
{
|
||||
_codeSize = 0;
|
||||
_dataSize = 0;
|
||||
_usedChrSize = 0;
|
||||
_drawnChrSize = 0;
|
||||
_readChrSize = 0;
|
||||
|
||||
for(int i = 0, len = _prgSize; i < len; i++) {
|
||||
if(IsCode(i)) {
|
||||
_codeSize++;
|
||||
} else if(IsData(i)) {
|
||||
_dataSize++;
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = 0, len = _chrSize; i < len; i++) {
|
||||
if(IsDrawn(i) || IsRead(i)) {
|
||||
_usedChrSize++;
|
||||
if(IsDrawn(i)) {
|
||||
_drawnChrSize++;
|
||||
} else if(IsRead(i)) {
|
||||
_readChrSize++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CodeDataLogger::SaveCdlFile(string cdlFilepath)
|
||||
{
|
||||
ofstream cdlFile(cdlFilepath, ios::out | ios::binary);
|
||||
if(cdlFile) {
|
||||
cdlFile.write((char*)_cdlData, _prgSize+_chrSize);
|
||||
cdlFile.close();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CodeDataLogger::SetFlag(int32_t absoluteAddr, CdlPrgFlags flag)
|
||||
{
|
||||
if(absoluteAddr >= 0 && absoluteAddr < (int32_t)_prgSize) {
|
||||
if((_cdlData[absoluteAddr] & (uint8_t)flag) != (uint8_t)flag) {
|
||||
if(flag == CdlPrgFlags::Code) {
|
||||
if(IsData(absoluteAddr)) {
|
||||
//Remove the data flag from bytes that we are flagging as code
|
||||
_cdlData[absoluteAddr] &= ~(uint8_t)CdlPrgFlags::Data;
|
||||
_dataSize--;
|
||||
}
|
||||
_cdlData[absoluteAddr] |= (uint8_t)flag;
|
||||
_codeSize++;
|
||||
} else if(flag == CdlPrgFlags::Data) {
|
||||
if(!IsCode(absoluteAddr)) {
|
||||
_cdlData[absoluteAddr] |= (uint8_t)flag;
|
||||
_dataSize++;
|
||||
}
|
||||
} else {
|
||||
_cdlData[absoluteAddr] |= (uint8_t)flag;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CodeDataLogger::SetFlag(int32_t chrAbsoluteAddr, CdlChrFlags flag)
|
||||
{
|
||||
if(chrAbsoluteAddr >= 0 && chrAbsoluteAddr < (int32_t)_chrSize) {
|
||||
if((_cdlData[_prgSize + chrAbsoluteAddr] & (uint8_t)flag) != (uint8_t)flag) {
|
||||
_usedChrSize++;
|
||||
if(flag == CdlChrFlags::Read) {
|
||||
_readChrSize++;
|
||||
} else if(flag == CdlChrFlags::Drawn) {
|
||||
_drawnChrSize++;
|
||||
}
|
||||
_cdlData[_prgSize + chrAbsoluteAddr] |= (uint8_t)flag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CdlRatios CodeDataLogger::GetRatios()
|
||||
{
|
||||
CdlRatios ratios;
|
||||
ratios.CodeRatio = (float)_codeSize / (float)_prgSize;
|
||||
ratios.DataRatio = (float)_dataSize / (float)_prgSize;
|
||||
ratios.PrgRatio = (float)(_codeSize + _dataSize) / (float)_prgSize;
|
||||
if(_chrSize > 0) {
|
||||
ratios.ChrRatio = (float)(_usedChrSize) / (float)_chrSize;
|
||||
ratios.ChrReadRatio = (float)(_readChrSize) / (float)_chrSize;
|
||||
ratios.ChrDrawnRatio = (float)(_drawnChrSize) / (float)_chrSize;
|
||||
} else {
|
||||
ratios.ChrRatio = -1;
|
||||
ratios.ChrReadRatio = -1;
|
||||
ratios.ChrDrawnRatio = -1;
|
||||
}
|
||||
|
||||
return ratios;
|
||||
}
|
||||
|
||||
bool CodeDataLogger::IsNone(uint32_t absoluteAddr)
|
||||
{
|
||||
return _cdlData[absoluteAddr] == (uint8_t)CdlPrgFlags::None;
|
||||
}
|
||||
|
||||
|
||||
bool CodeDataLogger::IsCode(uint32_t absoluteAddr)
|
||||
{
|
||||
return (_cdlData[absoluteAddr] & (uint8_t)CdlPrgFlags::Code) == (uint8_t)CdlPrgFlags::Code;
|
||||
}
|
||||
|
||||
bool CodeDataLogger::IsJumpTarget(uint32_t absoluteAddr)
|
||||
{
|
||||
return (_cdlData[absoluteAddr] & (uint8_t)CdlPrgFlags::JumpTarget) == (uint8_t)CdlPrgFlags::JumpTarget;
|
||||
}
|
||||
|
||||
bool CodeDataLogger::IsSubEntryPoint(uint32_t absoluteAddr)
|
||||
{
|
||||
return (_cdlData[absoluteAddr] & (uint8_t)CdlPrgFlags::SubEntryPoint) == (uint8_t)CdlPrgFlags::SubEntryPoint;
|
||||
}
|
||||
|
||||
bool CodeDataLogger::IsData(uint32_t absoluteAddr)
|
||||
{
|
||||
return (_cdlData[absoluteAddr] & (uint8_t)CdlPrgFlags::Data) == (uint8_t)CdlPrgFlags::Data;
|
||||
}
|
||||
|
||||
bool CodeDataLogger::IsRead(uint32_t absoluteAddr)
|
||||
{
|
||||
return (_cdlData[absoluteAddr + _prgSize] & (uint8_t)CdlChrFlags::Read) == (uint8_t)CdlChrFlags::Read;
|
||||
}
|
||||
|
||||
bool CodeDataLogger::IsDrawn(uint32_t absoluteAddr)
|
||||
{
|
||||
return (_cdlData[absoluteAddr + _prgSize] & (uint8_t)CdlChrFlags::Drawn) == (uint8_t)CdlChrFlags::Drawn;
|
||||
}
|
||||
|
||||
void CodeDataLogger::SetCdlData(uint8_t *cdlData, uint32_t length)
|
||||
{
|
||||
if(length <= _prgSize + _chrSize) {
|
||||
memcpy(_cdlData, cdlData, length);
|
||||
CalculateStats();
|
||||
}
|
||||
}
|
||||
|
||||
void CodeDataLogger::GetCdlData(uint32_t offset, uint32_t length, DebugMemoryType memoryType, uint8_t *cdlData)
|
||||
{
|
||||
if(memoryType == DebugMemoryType::PrgRom) {
|
||||
memcpy(cdlData, _cdlData + offset, length);
|
||||
} else if(memoryType == DebugMemoryType::ChrRom) {
|
||||
memcpy(cdlData, _cdlData + _prgSize + offset, length);
|
||||
} else if(memoryType == DebugMemoryType::CpuMemory) {
|
||||
for(uint32_t i = 0; i < length; i++) {
|
||||
int32_t absoluteAddress = _debugger->GetAbsoluteAddress(offset + i);
|
||||
cdlData[i] = absoluteAddress >= 0 ? _cdlData[absoluteAddress] : 0;
|
||||
}
|
||||
} else if(memoryType == DebugMemoryType::PpuMemory) {
|
||||
for(uint32_t i = 0; i < length; i++) {
|
||||
int32_t absoluteAddress = _debugger->GetAbsoluteChrAddress(offset + i);
|
||||
cdlData[i] = absoluteAddress >= 0 ? _cdlData[_prgSize + absoluteAddress] : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CodeDataLogger::StripData(uint8_t *romBuffer, CdlStripFlag flag)
|
||||
{
|
||||
if(flag == CdlStripFlag::StripUnused) {
|
||||
for(uint32_t i = 0; i < _prgSize + _chrSize; i++) {
|
||||
if(_cdlData[i] == 0) {
|
||||
romBuffer[i] = 0;
|
||||
}
|
||||
}
|
||||
} else if(flag == CdlStripFlag::StripUsed) {
|
||||
for(uint32_t i = 0; i < _prgSize + _chrSize; i++) {
|
||||
if(_cdlData[i] != 0) {
|
||||
romBuffer[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CodeDataLogger::MarkPrgBytesAs(uint32_t start, uint32_t end, CdlPrgFlags type)
|
||||
{
|
||||
for(uint32_t i = start; i <= end; i++) {
|
||||
_cdlData[i] = (_cdlData[i] & 0xFC) | (int)type;
|
||||
}
|
||||
_debugger->UpdateCdlCache();
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "../Utilities/SimpleLock.h"
|
||||
#include "DebuggerTypes.h"
|
||||
|
||||
class Debugger;
|
||||
|
||||
enum class CdlPrgFlags
|
||||
{
|
||||
None = 0x00,
|
||||
Code = 0x01,
|
||||
Data = 0x02,
|
||||
|
||||
//Bit 0x10 is used for "indirectly accessed as code" in FCEUX
|
||||
//Repurposed to mean the address is the target of a jump instruction
|
||||
JumpTarget = 0x10,
|
||||
|
||||
IndirectData = 0x20,
|
||||
PcmData = 0x40,
|
||||
|
||||
//Unused bit in original CDL spec
|
||||
//Used to denote that the byte is the start of function (sub)
|
||||
SubEntryPoint = 0x80
|
||||
};
|
||||
|
||||
enum class CdlChrFlags
|
||||
{
|
||||
Drawn = 0x01,
|
||||
Read = 0x02,
|
||||
};
|
||||
|
||||
enum class CdlStripFlag
|
||||
{
|
||||
StripNone = 0,
|
||||
StripUnused,
|
||||
StripUsed,
|
||||
};
|
||||
|
||||
struct CdlRatios
|
||||
{
|
||||
float CodeRatio;
|
||||
float DataRatio;
|
||||
float PrgRatio;
|
||||
|
||||
float ChrRatio;
|
||||
float ChrReadRatio;
|
||||
float ChrDrawnRatio;
|
||||
};
|
||||
|
||||
class CodeDataLogger
|
||||
{
|
||||
private:
|
||||
Debugger* _debugger = nullptr;
|
||||
uint8_t *_cdlData = nullptr;
|
||||
uint32_t _prgSize = 0;
|
||||
uint32_t _chrSize = 0;
|
||||
|
||||
uint32_t _codeSize = 0;
|
||||
uint32_t _dataSize = 0;
|
||||
uint32_t _usedChrSize = 0;
|
||||
uint32_t _readChrSize = 0;
|
||||
uint32_t _drawnChrSize = 0;
|
||||
|
||||
SimpleLock _lock;
|
||||
|
||||
void CalculateStats();
|
||||
|
||||
public:
|
||||
CodeDataLogger(Debugger *debugger, uint32_t prgSize, uint32_t chrSize);
|
||||
~CodeDataLogger();
|
||||
|
||||
void Reset();
|
||||
|
||||
bool LoadCdlFile(string cdlFilepath);
|
||||
bool SaveCdlFile(string cdlFilepath);
|
||||
|
||||
void SetFlag(int32_t absoluteAddr, CdlPrgFlags flag);
|
||||
void SetFlag(int32_t chrAbsoluteAddr, CdlChrFlags flag);
|
||||
|
||||
CdlRatios GetRatios();
|
||||
|
||||
bool IsNone(uint32_t absoluteAddr);
|
||||
bool IsCode(uint32_t absoluteAddr);
|
||||
bool IsJumpTarget(uint32_t absoluteAddr);
|
||||
bool IsSubEntryPoint(uint32_t absoluteAddr);
|
||||
bool IsData(uint32_t absoluteAddr);
|
||||
bool IsRead(uint32_t absoluteAddr);
|
||||
bool IsDrawn(uint32_t absoluteAddr);
|
||||
|
||||
void SetCdlData(uint8_t *cdlData, uint32_t length);
|
||||
void GetCdlData(uint32_t offset, uint32_t length, DebugMemoryType memoryType, uint8_t* cdlData);
|
||||
|
||||
void StripData(uint8_t* romBuffer, CdlStripFlag flag);
|
||||
|
||||
void MarkPrgBytesAs(uint32_t start, uint32_t end, CdlPrgFlags type);
|
||||
};
|
|
@ -1,48 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
#include "CodeRunner.h"
|
||||
#include "Debugger.h"
|
||||
#include "DisassemblyInfo.h"
|
||||
|
||||
CodeRunner::CodeRunner(vector<uint8_t> byteCode, Debugger *debugger)
|
||||
{
|
||||
_byteCode = byteCode;
|
||||
_debugger = debugger;
|
||||
_running = true;
|
||||
|
||||
if(_byteCode.size() < 0x1000) {
|
||||
//Fill the entire $3000-$3FFF range
|
||||
_byteCode.insert(_byteCode.end(), 0x1000 - _byteCode.size(), 0xEA); //0xEA = NOP
|
||||
}
|
||||
}
|
||||
|
||||
bool CodeRunner::IsRunning()
|
||||
{
|
||||
return _running;
|
||||
}
|
||||
|
||||
void CodeRunner::GetMemoryRanges(MemoryRanges & ranges)
|
||||
{
|
||||
ranges.SetAllowOverride();
|
||||
ranges.AddHandler(MemoryOperation::Any, CodeRunner::BaseAddress, CodeRunner::BaseAddress + 0xFFF);
|
||||
}
|
||||
|
||||
uint8_t CodeRunner::ReadRAM(uint16_t addr)
|
||||
{
|
||||
return _byteCode[addr - CodeRunner::BaseAddress];
|
||||
}
|
||||
|
||||
void CodeRunner::WriteRAM(uint16_t addr, uint8_t value)
|
||||
{
|
||||
_byteCode[addr - CodeRunner::BaseAddress] = value;
|
||||
|
||||
if(addr == CodeRunner::BaseAddress) {
|
||||
//Writing to $3000 stops the code runner and resumes normal execution
|
||||
_debugger->StopCodeRunner();
|
||||
_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
DisassemblyInfo CodeRunner::GetDisassemblyInfo(uint16_t cpuAddress)
|
||||
{
|
||||
return DisassemblyInfo(_byteCode.data() + cpuAddress - CodeRunner::BaseAddress, false);
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "IMemoryHandler.h"
|
||||
|
||||
class Debugger;
|
||||
class DisassemblyInfo;
|
||||
|
||||
class CodeRunner : public IMemoryHandler
|
||||
{
|
||||
private:
|
||||
vector<uint8_t> _byteCode;
|
||||
Debugger *_debugger;
|
||||
bool _running;
|
||||
|
||||
public:
|
||||
static constexpr uint16_t BaseAddress = 0x3000;
|
||||
|
||||
CodeRunner(vector<uint8_t> byteCode, Debugger *debugger);
|
||||
|
||||
bool IsRunning();
|
||||
DisassemblyInfo GetDisassemblyInfo(uint16_t cpuAddress);
|
||||
|
||||
void GetMemoryRanges(MemoryRanges &ranges) override;
|
||||
uint8_t ReadRAM(uint16_t addr) override;
|
||||
void WriteRAM(uint16_t addr, uint8_t value) override;
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue