Compare commits

...
36 Commits
Author SHA1 Message Date
Luke Usher dd73766522 ps1: implement GTE command timing 2026-11-06 21:32:31 +00:00
Luke Usher d3b8a29910 ps1: fix breakpoint and address-error exceptions 2026-11-06 21:31:44 +00:00
Luke Usher 05232171d4 ps1: fix spu envelopes and cd audio capture
Fix envelope timing and key-on delay, jump to the repeat address on sample end.
Capture CD audio before volume processing,
Fix sign handling for cd-rom volume and pitch modulation

ref: https://psx-spx.consoledev.net/soundprocessingunitspu/
2026-11-04 19:53:25 +00:00
Luke Usher 15ef1ac2d5 ps1: overhaul sio timing and fifo behavoir
Devices are now in charge of their own timing, fix Memory Card and Controller timing to be closer to hardware.

Fixes an infinite loop polling for memory cards in Ace Combat 3 (USA)
2026-11-04 19:23:03 +00:00
Luke Usher 234c99911f ps1: gpu write fifo should not be blocked when the gpu is busy drawing 2026-11-03 15:34:12 +00:00
Luke Usher 94e66350a1 ps1: sample external bus reads after wait states apply 2026-11-03 13:34:13 +00:00
Luke Usher c4f133ac0b ps1: abort pending MDEC decode on reset 2026-11-03 11:19:51 +00:00
Luke Usher 4307bf2eff ps1: fix motorOn command
it was responding but not actually turning the motor flag on.
2026-11-02 17:05:17 +00:00
PixelMechanic0 17f29369c4 n64: avoid marking an empty RDP queue busy
Clearing DPC_FREEZE calls flushCommands even when END equals CURRENT. Previously, this set pipeBusy and startGclk without executing any commands, leaving an initially idle RDP reporting busy.

Return before setting the busy flags when no forward command range is available. Preserve existing busy state for starved pipelines and leave START/END latching unchanged.
2026-11-01 22:40:37 +00:00
Luke Usher dc4f4659d4 neogeo: add option to allow loading MVS-only or AES-only games on the other system 2026-10-31 16:03:58 +00:00
Matthew Callis e9639ebae3 nall: handle partial TCP sends and reconnects 2026-10-30 10:52:35 +00:00
Matthew Callis bdf0b044e8 macos: enable ARC for hiro and ruby with CMake 2026-10-30 10:52:01 +00:00
Matthew Callis 26c56bb4bc desktop-ui: preserve window placement on resolution changes 2026-10-30 10:51:33 +00:00
Luke Usher 9c5ed3f68d hiro: fix tab frame races on gtk 2026-10-29 16:02:01 +00:00
Luke Usher b6890c9eff ci: use switch freebsd cmake pkg to cmake-core 2026-10-29 14:54:19 +00:00
Luke Usher 035fb15c71 tools: replace native mame2bml tool with Python scripts
The following are new behavoirs in the python tooling:

Extract DIP switch definitions, input mappings, and other metadata by parsing tables directly from the MAME source tree; this lifts metadata (such as dip switch names and bit-values and button labels) into Arcade.bml, no code is lifted.
Support custom overrides for information that is missing or insufficient in MAME's database.
Allow ROMs to be excluded from the generated database, including incomplete dumps, unsupported bootlegs, and non-game software.
2026-10-29 10:12:17 +00:00
Luke Usher 6fd11e291f arcade: add Sega System E
This is essentially just a master system with two VDPs and an optional encrypted z80
2026-10-29 10:12:17 +00:00
Luke Usher 1c07ebf70c desktop-ui: add per-game input overrides
Extend the existing per-system input override functionality to support game-specific controller mappings.

The intended workflow is for users to configure their Virtual Pad once. The Virtual Pad attempts to map inputs to each system's controller based on the physical position of its buttons.

Users can then customize mappings by adding overrides for specific inputs per-system, or per-game. Per-game overrides can only be configured while the relevant game is loaded.

ares attempts to strip common Disc, Disk, Side and Part suffixes from game titles, allowing multi-disc games to share the same input configuration.
2026-10-29 10:12:17 +00:00
Luke Usher 569acf7ba1 arcade: improve neo geo, centralize dip-switch handling and add support for sg1000a encryption
Introduce a shared DIP switch implementation for arcade systems, replacing system-specific implementations.

Neo Geo:

Implement the NEC uPD4990A real-time clock.
Implement the MVS hardware watchdog.
Fix IRQ acknowledgement register handling to that only pending IRQs can be acknowledged
Move ROM decryption and protection handling into runtime PCB emulation, more closely reflecting the behaviour of original hardware.
Add support for additional protection schemes and game-specific memory mappers.

SG-1000A:

Add support for the encrypted Z80 variant used by Championship Wrestling.
Introduce separate opcode fetch and data read paths for the Z80, allowing accurate emulation of hardware-dependent decryption.

Portions of the ROM decryption and protection implementations are derived from MAME. Only code distributed under permissive licences, including the BSD 3-Clause License, has been incorporated.

All MAME-derived implementations are explicitly identified in the source code, with the applicable copyright notices and licence terms preserved in the project's LICENSE file.
2026-10-29 09:49:27 +00:00
Luke Usher 47bab688e6 a26: fix crash with unusual display co-ordinates 2026-10-28 19:01:54 +00:00
remutro 460fec09a0 Fix layout on Cores settings UI 2026-10-27 22:43:31 +00:00
Luke Usher 5d093ad9e6 ps1: run textured draws in parallel to the cpu
To measure texture cache timing, textured draws previously waited for
the renderer to catch up before emulation could continue.

Now we wait only when the GPU becomes ready for another draw.

draw timing observed by the game is unaffected.
2026-10-27 09:00:05 +00:00
Luke Usher 3474c5c1bc a52: improve ANTIC playfield DMA timing
Moon Patrol's HSCROL write was landing too early in ares, corrupting the mountains

Altirra Hardware Reference Manual was used to determine correct behavoir, which I believe to be trustworthy as the introduction specifies that "This document attempts to describe the hardware in detail as the target to which the emulator aspires to imitate"
2026-10-27 10:57:05 +00:00
Luke Usher d400eee976 ps1: toggling 'overscan' should not change aspect ratio 2026-10-26 21:52:46 +00:00
Luke Usher 8128704f23 screen: add configurable deinterlacing modes
Weave (default): combines the current field with the previous field. this preserves detail but may introduce combing during motion. This is how ares behaved historically.

Bob: displays the current field and interpolates missing lines. This prevents motion combing but causes a noticable loss of vertical detail and may flicker thin lines.

Blend: displays an average of consecutive bobbed fields. Behaves similar to bob, but swaps flicker for motion blur.

Phosphor: displays only the currently displaying field while simulating phosphor decay. This Produces a darker image with visible scanlines and may flicker, but is closer to how a real CRT handles interlaced video.
2026-10-26 16:41:29 +00:00
Luke Usher a774671dc0 ps1: take texture cache into account when calculating GPU draw timing
nocash DIAG's hardware cache tests measure miss penalties of ~60 CPU
clocks for eight entries and ~120 clocks for sixteen entries. This is
approximately 12 GPU clocks per 8-byte refill.

We use 13 clocks here as it better fits JaCzekanski's GPU bandwidth
test; textured drawing timings now measure within ~1-2% of the hardware
baseline.
2026-10-26 11:40:08 +00:00
Luke Usher 3f268697d2 ps1: avoid overflow on cd-audio/xa-adpcm mixing 2026-10-26 09:44:56 +00:00
Luke Usher 26382de232 ps1: various bug-fixes/feedback amendments
- prevent discarded polygons from counting in draw timing calculations
- fix depth mode changes applying one frame too late
- fix cd-rom stop command
- prevent MDEC output fifo from losing data when full and reduce output size to a more realistic 32 words-
2026-10-25 22:15:09 +00:00
Luke Usher 9c65426aab ci: use clang-cl for ares-windows-x64, move mingw-clang64 to build-aux
Rationale is that the clang64 ci is resulting in a build that drops as low as 40vps in FMV sequences in the PS1 core, vs 90vps in the same scene for clang-cl

Pinning a known-performant version of clang was considered (local testing proved clang22 produces more performant builds than clang20), but this is not viable with how msys2 is managed. Investigation concluded that we cannot reliably update the clang msys environment; just running 'update:true' breaks the build due to a ymfm issue with the clang23 runtime, and installing the 'clang22' packages still uses the clang23 runtimes, it only pins the compiler itself.
2026-10-25 10:06:49 +00:00
Christopher Bonhage 4355718dda n64: commit the PC at the end of every JIT block
A block can end before an instruction that has a gdb breakpoint. Its
last instruction can then be straight-line code, which does not commit
the pipeline PC. The block exited with ipu.pc at the last commit point,
so the CPU ran the instructions after that point a second time. If that
point was an internal entry of the same block, the CPU looped forever
and the breakpoint never hit.

Commit the pipeline PC on the last instruction of every block. All other
block ends already commit there, so code without breakpoints is
unchanged.
2026-10-25 07:30:52 +00:00
Luke Usher 022805be81 ps1: overhaul timing and hardware accuracy
- more accurately model the CPU bus, write buffering and cache behavior
- improve DMA arbitration, chopping, transfer timing, and interaction with CPU
- rework CD-ROM command timing, deferred responses, interrupts, sector delivery, subchannel data, and disc session handling
- implement the GPU command FIFO, busy timing, texture caches, rasterization, and improve scanout accuracy
- improve timer synchronization, reset, interrupt, and blanking behavior
- improve peripheral, SPU, MDEC, and memory-mapped I/O timing
- add support for Datel Action Replay and Blaze Xplorer cartridges
- add support for the Pixel Accuracy toggle to enable per-pixel scanout (required by tajmiing demo)

These changes were validated against psxtest_cpx, psxtest_gpu, nocash diag.exe, JaCzekanski's tests, and the pcsx-redux CD-ROM torture tests.
Most of tests now pass, and known problematic games such as Monkey Hero are working.

Framebuffer output matches expected hardware output pixel-for-pixel in the nocash diagnostics and psxtest_gpu primitive tests.

Timer test results generally remain within a small margin of hardware baseline results (when compared against the psx.log reference point shipped with the tests)

psxtest_cpx and psxtest_gpu do not initialize controllers correctly when launched directly from the BIOS. You must run them them through software that initializes the hardware first, such as a Datel Action Replay or Blaze Xplorer cartridge. These devices can be configured in Core settings. This issue exists on real hardware and is correct behavoir.

ares can now run the tajmiing demo and all its effects end-to-end.

tajmiing changes display registers at individual-pixel boundaries, so it requires the Pixel Accuracy option in Video settings. Scanline-level synchronization remains the default as oridnary software does not depend on mid-scanline register changes.
2026-10-24 16:48:25 +01:00
Matthew Callis c76aaffa6e sfc: latch window edges once per scanline
Latch the left and right edge matches independently until the next scanline, preserving the inclusive right edge. This prevents mid-line bounds writes from reopening a stopped window, as demonstrated by Mickey Magic and MiSTer SNES PR #508.

Serialize the latches and bump the SNES state format. Fixes ares-emulator/ares#2645.
2026-10-22 23:02:05 +01:00
Reaper 9408cb43d4 desktop-ui: force 3-button mode for incompatible Mega Drive games 2026-10-06 15:03:27 +01:00
Reaper 4565c732bd desktop-ui: persist controller selection 2026-10-06 12:43:10 +01:00
Stefan Schlosser a776c509bd CI: Update FreeBSD runner to 15.1
With FreeBSD 15.0 going EOL very soon, it is time to update the runner
in the CI to FreeBSD 15.1. This version is supported by Cross-Platform
GitHub Actions since version 1.3.0, and the runner is updated to the
currently newest version 1.6.0, which corresponds to the commit
https://github.com/cross-platform-actions/action/commit/e0b9770014ba65d5e0815f15b74031c3a635f641.

Using the operating system's shell for running commands inside steps is
deprecated. Cross-Platform GitHub Actions provides and recommends the
new custom shell cpa.sh instead, which the runner is now using.

This requires to split the "Build: FreeBSD" step into at least one
separate step for setting up the VM before executing commands in the
following steps. This opens up the opportunity to be more in line with
the Linux runner by installing the build dependencies in its own step,
"Install FreeBSD Dependencies".

Before installing dependencies, the runner now updates the preinstalled
packages in the VM via pkg upgrade -y. This ensures that the whole VM is
cohesively up-to-date.

Last but not least, the TARGET_PRESET environment variable must be set
in the "Build: FreeBSD" step with the --environment-variables option of
cpa.sh, or else it won't be propagated to the build environment.
2026-10-04 23:35:10 +01:00
Akiteru 2976a1ab6b sfc: CX4 accuracy improvements
Ported directly from bsnes PRs 377 and 401.
- When triggering a cache preload by writing to $7f48, update the pb register with the prepared program page
- When preloading a page, ignore all runtime checks and reload the page immediately
- Step 1+WS on both reads for cache, not just one
- Remove extra cycle on bus reads/writes
- Remove extra cycle on DMA to internal RAM (fixes excessive slowdown in Morph Moth fight)
2026-10-04 23:34:57 +01:00
284 changed files with 126052 additions and 31224 deletions

No files matched your search

+21 -14
View File
@@ -17,11 +17,13 @@ jobs:
msystem: mingw64
install: make git cmake ccache ninja mingw-w64-x86_64-toolchain
target-cmake-preset: windows-ci-mingw-native
- name: windows-clang-cl-x64
- name: windows-mingw-clang64-x64
os: windows-latest
windres: rc
shell: bash
target-cmake-preset: windows-ci-cl-native
compiler: clang++
shell: 'msys2 {0}'
msystem: clang64
install: make git cmake ccache ninja mingw-w64-clang-x86_64-clang
target-cmake-preset: windows-ci-mingw-native
- name: windows-arm64
os: windows-11-arm
compiler: clang++
@@ -62,7 +64,7 @@ jobs:
target-cmake-preset: ubuntu-ci-qt
- name: freebsd
os: ubuntu-24.04
shell: bash
shell: cpa.sh {0}
target-cmake-preset: ubuntu-ci-clang
name: ${{ matrix.program }}-${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }}
@@ -106,18 +108,23 @@ jobs:
CROSS_COMPILE: ${{ matrix.platform.native-cmake-preset != '' }}
NATIVE_PRESET: ${{ matrix.platform.native-cmake-preset }}
TARGET_PRESET: ${{ matrix.platform.target-cmake-preset }}
- name: "Build: FreeBSD"
- name: "Start FreeBSD VM"
if: matrix.platform.name == 'freebsd'
uses: cross-platform-actions/action@492b0c80085400348c599edace11141a4ee73524
env:
TARGET_PRESET: ${{ matrix.platform.target-cmake-preset }}
uses: cross-platform-actions/action@e0b9770014ba65d5e0815f15b74031c3a635f641
with:
operating_system: freebsd
version: '15.0'
environment_variables: TARGET_PRESET
run: |
sudo pkg install -y cmake pkgconf ccache ninja gtk3 sdl3 librashader
.github/scripts/build_ubuntu.sh
version: '15.1'
- name: "Install FreeBSD Dependencies"
if: matrix.platform.name == 'freebsd'
run: |
sudo pkg upgrade -y
sudo pkg install -y cmake-core pkgconf ccache ninja gtk3 sdl3 librashader
- name: "Build: FreeBSD"
if: matrix.platform.name == 'freebsd'
env:
TARGET_PRESET: ${{ matrix.platform.target-cmake-preset }}
shell: cpa.sh {0} --environment-variables TARGET_PRESET
run: .github/scripts/build_ubuntu.sh
- name: "Compress Build Artifacts (Windows)"
if: runner.os != 'macOS' && runner.os != 'Linux'
run: |
+3 -5
View File
@@ -24,11 +24,9 @@ jobs:
platform:
- name: windows-x64
os: windows-latest
compiler: clang++
shell: 'msys2 {0}'
msystem: clang64
install: make git cmake ccache ninja mingw-w64-clang-x86_64-clang
target-cmake-preset: windows-ci-mingw-native
windres: rc
shell: bash
target-cmake-preset: windows-ci-cl-native
- name: windows-clang-cl-arm64
os: windows-latest
windres: rc
-2
View File
@@ -40,13 +40,11 @@ if(ARES_BUILD_OPTIONAL_TARGETS)
else()
target_disable_subproject(genius "genius (database editor)")
endif()
add_subdirectory(tools/mame2bml)
else()
target_disable_subproject(arm7tdmi "arm7tdmi processor test harness")
target_disable_subproject(armv6m "armv6m processor test harness")
target_disable_subproject(i8080 "i8080 processor test harness")
target_disable_subproject(m68000 "m68000 processor test harness")
target_disable_subproject(mame2bml "mame2bml (MAME manifest converter)")
target_disable_subproject(genius "genius (database editor)")
endif()
+34
View File
@@ -16,6 +16,40 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
----------------------------------------------------------------------
----------------------------------------------------------------------
MAME-derived ROM decryption and arcade database
Portions of the ROM decryption code are derived from MAME source files
licensed under the BSD 3-Clause License. The arcade database is generated
from MAME machine data.
Copyright (c) 1997-2026 MAMEdev and contributors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
----------------------------------------------------------------------
Stack-less Just-In-Time compiler
+2 -2
View File
@@ -1,11 +1,11 @@
auto Video::clock(i16 x, n7 pixel, n1 hblank, n1 vblank) -> void {
auto Video::clock(s16 x, n7 pixel, n1 hblank, n1 vblank) -> void {
auto outputY = y();
if(x < 0 || x >= 160 || outputY <= 0 || outputY >= displayHeight()) return;
if(hblank || vblank) pixel = 0;
screen->pixels().data()[outputY * 180 + x + 10] = pixel;
}
auto Video::fill(i16 x, n7 pixel) -> void {
auto Video::fill(s16 x, n7 pixel) -> void {
auto outputY = y();
if(x >= 160 || outputY <= 0 || outputY >= displayHeight()) return;
auto output = screen->pixels().data() + outputY * 180 + 10;
+3 -3
View File
@@ -9,7 +9,7 @@ struct Video {
auto verticalOffset() const -> s32 { return Region::NTSC() ? 20 : 40; }
auto line() const -> u32 { return lineCounter; }
auto y() const -> i32 { return (i32)lineCounter - verticalOffset(); }
auto y() const -> s32 { return (s32)lineCounter - verticalOffset(); }
//video.cpp
auto load(Node::Object) -> void;
@@ -18,8 +18,8 @@ struct Video {
auto setPhosphor(bool) -> void;
//receiver.cpp
auto clock(i16 x, n7 pixel, n1 hblank, n1 vblank) -> void;
auto fill(i16 x, n7 pixel) -> void;
auto clock(s16 x, n7 pixel, n1 hblank, n1 vblank) -> void;
auto fill(s16 x, n7 pixel) -> void;
auto endScanline() -> void;
auto vsync(n1 level) -> void;
auto frame() -> void;
+3
View File
@@ -196,6 +196,8 @@ struct ANTIC : Thread {
auto serialize(serializer&) -> void;
n8 dmaClock;
n2 startPending;
n2 stopPending;
n8 dmaName[8];
n6 lineWrite;
n6 lineRead;
@@ -227,6 +229,7 @@ struct ANTIC : Thread {
auto serialize(serializer&) -> void;
n1 active;
n2 stallDelay;
n9 scanline;
} wsync{*this};
+2 -2
View File
@@ -75,11 +75,11 @@ auto ANTIC::DMA::arbitrate(Requests& requests) -> void {
cpu.rdyLine(0);
data = cpu.readDMA(address);
self.step(Timing::ColorClocksPerMachineCycle);
cpu.rdyLine(!self.wsync.active);
cpu.rdyLine(!self.wsync.active || self.wsync.stallDelay);
} else if(refreshPending) {
cpu.rdyLine(0);
self.step(Timing::ColorClocksPerMachineCycle);
cpu.rdyLine(!self.wsync.active);
cpu.rdyLine(!self.wsync.active || self.wsync.stallDelay);
refreshPending = 0;
data = cpu.dataBus();
} else {
+5 -1
View File
@@ -35,19 +35,23 @@ auto ANTIC::Interrupt::clock() -> void {
auto ANTIC::WSYNC::power() -> void {
active = 0;
stallDelay = 0;
scanline = 0;
}
auto ANTIC::WSYNC::clock() -> void {
if(active && scanline == self.counter.scanline && self.counter.machineCycle == Timing::WSYNCReleaseCycle) {
active = 0;
stallDelay = 0;
cpu.rdyLine(1);
} else if(active && stallDelay && !--stallDelay) {
cpu.rdyLine(0);
}
}
auto ANTIC::WSYNC::wait() -> void {
active = 1;
stallDelay = 2;
scanline = self.counter.scanline + (self.counter.machineCycle >= 104);
if(scanline == Timing::ScanlinesPerFrame) scanline = 0;
cpu.rdyLine(0);
}
+22 -5
View File
@@ -1,5 +1,7 @@
auto ANTIC::Playfield::power() -> void {
dmaClock = 0;
startPending = 0;
stopPending = 0;
for(auto& name : dmaName) name = 0;
lineWrite = 0;
lineRead = 0;
@@ -163,19 +165,33 @@ auto ANTIC::Playfield::dmaStop() const -> u8 {
auto ANTIC::Playfield::clearDMARing() -> void {
dmaClock = 0;
startPending = 0;
stopPending = 0;
for(auto& name : dmaName) name = 0;
}
auto ANTIC::Playfield::clockDMARing() -> void {
bool startPulse = startPending.bit(0);
bool stopPulse = stopPending.bit(0);
startPending >>= 1;
stopPending >>= 1;
auto start = dmaStart();
auto stop = dmaStop();
auto cycle = (u8)self.counter.machineCycle;
if(start != 0xff && cycle + 2 == start) startPending.bit(1) = 1;
if(stop != 0xff && cycle + 2 == stop) stopPending.bit(1) = 1;
if(self.counter.machineCycle == start) {
auto width = fetchWidth();
auto wideStop = 106 + (self.displayList.instruction.bit(4) ? (u8)self.io.hscroll >> 1 : 0);
if(width && cycle + 2 == wideStop) stopPending.bit(1) = 1;
if(startPulse) {
dmaClock.bit(0) = 1;
lineWrite = 0;
lineRead = 0;
}
if(self.counter.machineCycle == stop) {
if(stopPulse) {
dmaClock.bit(0) = 0;
dmaName[0] = 0;
}
@@ -264,13 +280,14 @@ auto ANTIC::Playfield::queueDMA(DMA::Requests& requests) -> void {
clockDMARing();
bool enabled = (self.io.dmactl & 3) != 0;
auto physical = enabled && self.counter.machineCycle >= 10 && self.counter.machineCycle <= 105;
bool earlyAbnormal = self.counter.machineCycle < 10;
auto physical = earlyAbnormal || (enabled && self.counter.machineCycle >= 10 && self.counter.machineCycle <= 105);
auto& properties = Modes[mode];
if(properties.character && dmaClock.bit(0)) {
auto readPhase = lineRead;
auto name = lineBufferRead(readPhase);
if(self.displayList.firstScanline) {
if(self.displayList.firstScanline || earlyAbnormal) {
auto writePhase = lineWrite;
requests.append(
self.displayList.memoryScan,
@@ -287,7 +304,7 @@ auto ANTIC::Playfield::queueDMA(DMA::Requests& requests) -> void {
if(!properties.character && dmaClock.bit(2)) {
bool next = dmaClock.bit(1);
auto readPhase = bitmapLineBufferPhase(next);
if(self.displayList.firstScanline) {
if(self.displayList.firstScanline || earlyAbnormal) {
auto writePhase = lineWrite;
requests.append(
self.displayList.memoryScan,
+3
View File
@@ -51,6 +51,8 @@ auto ANTIC::DMA::serialize(serializer& s) -> void {
auto ANTIC::Playfield::serialize(serializer& s) -> void {
s(dmaClock);
s(startPending);
s(stopPending);
for(auto& name : dmaName) s(name);
s(lineWrite);
s(lineRead);
@@ -70,6 +72,7 @@ auto ANTIC::Playfield::serialize(serializer& s) -> void {
auto ANTIC::WSYNC::serialize(serializer& s) -> void {
s(active);
s(stallDelay);
s(scanline);
}
+2 -4
View File
@@ -1,4 +1,4 @@
static const string SerializerVersion = "v148";
static const string SerializerVersion = "v149";
auto System::serialize(bool synchronize) -> serializer {
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
@@ -29,10 +29,8 @@ auto System::unserialize(serializer& s) -> bool {
s(version);
s(description);
char expectedVersion[16] = {};
memory::copy(&expectedVersion, (const char*)SerializerVersion, SerializerVersion.size());
if(signature != SerializerSignature) return false;
if(memory::compare(version, expectedVersion, sizeof(version))) return false;
if(string{version} != SerializerVersion) return false;
if(synchronize) power(/* reset = */ false);
serialize(s, synchronize);
+73 -9
View File
@@ -56,6 +56,7 @@ auto Screen::power() -> void {
memory::fill<n1>(_lineOverrideActive.data(), _canvasWidth * _canvasHeight, false);
memory::fill<const u32*>(_lineOverride.data(), _canvasWidth * _canvasHeight, nullptr);
_phosphorHistoryValid = false;
_deinterlaceHistoryValid = false;
}
auto Screen::pixels(bool frame) -> std::span<u32> {
@@ -69,6 +70,7 @@ auto Screen::resetPalette() -> void {
_palette.reset();
refreshPalette();
_phosphorHistoryValid = false;
_deinterlaceHistoryValid = false;
}
auto Screen::resetSprites() -> void {
@@ -92,6 +94,7 @@ auto Screen::refreshRateHint(double refreshRate) -> void {
auto Screen::setViewport(u32 x, u32 y, u32 width, u32 height) -> void {
lock_guard<recursive_mutex> lock(_mutex);
if(_viewportX != x || _viewportY != y || _viewportWidth != width || _viewportHeight != height) _deinterlaceHistoryValid = false;
_viewportX = x;
_viewportY = y;
_viewportWidth = width;
@@ -105,6 +108,7 @@ auto Screen::setOverscan(bool overscan) -> void {
auto Screen::setSize(u32 width, u32 height) -> void {
lock_guard<recursive_mutex> lock(_mutex);
if(_width != width || _height != height) _deinterlaceHistoryValid = false;
_width = width;
_height = height;
_phosphorHistoryValid = false;
@@ -183,6 +187,17 @@ auto Screen::setProgressive(bool progressiveDouble) -> void {
_interlace = false;
_progressive = true;
_progressiveDouble = progressiveDouble;
_deinterlaceHistoryValid = false;
}
auto Screen::setDeinterlace(string deinterlace) -> void {
lock_guard<recursive_mutex> lock(_mutex);
if(deinterlace != "Weave" && deinterlace != "Bob"
&& deinterlace != "Blend" && deinterlace != "Phosphor") deinterlace = "Weave";
_deinterlace = deinterlace;
_deinterlaceHistory.reset();
_deinterlaceHistoryValid = false;
if(_output) memory::fill<u32>(_output.get(), _canvasWidth * _canvasHeight, 0);
}
auto Screen::setInterlace(bool interlaceField) -> void {
@@ -246,6 +261,20 @@ auto Screen::refresh() -> void {
auto input = _inputB.get();
auto output = _output.get();
auto phosphor = [](u32 current, u32 previous) -> u32 {
u32 color = current & 0xff000000;
for(u32 shift : {0, 8, 16}) {
auto rise = current >> shift & 0xff;
auto decay = (previous >> shift & 0xff) >> 1;
color |= max(rise, decay) << shift;
}
return color;
};
if(_interlace && _deinterlace == "Blend" && !_deinterlaceHistory) {
_deinterlaceHistory = std::make_unique<u32[]>(width * height);
}
for(u32 y : range(height)) {
auto source = input + y * pitch;
auto target = output + y * width;
@@ -257,7 +286,35 @@ auto Screen::refresh() -> void {
*target++ = color;
}
} else if(_interlace) {
if((_interlaceField & 1) == (y & 1)) {
if(_deinterlace == "Phosphor") {
for(u32 x : range(width)) {
auto current = (y & 1) == u32(_interlaceField) ? _palette[*source++] : 0xff000000;
*target = phosphor(current, *target);
target++;
}
} else if(_deinterlace != "Weave") {
u32 sourceY = y, nextY = y;
if((y & 1) != u32(_interlaceField)) {
sourceY = y ? y - 1 : min(1u, height - 1);
nextY = y + 1 < height ? y + 1 : sourceY;
}
source = input + sourceY * pitch;
auto next = input + nextY * pitch;
for(u32 x : range(width)) {
auto color = _palette[*source++];
if(sourceY != nextY) {
auto other = _palette[*next++];
color = (color & other) + (((color ^ other) & 0xfefefefe) >> 1);
}
if(_deinterlace == "Blend") {
auto& previous = _deinterlaceHistory[y * width + x];
auto current = color;
if(_deinterlaceHistoryValid) color = (color & previous) + (((color ^ previous) & 0xfefefefe) >> 1);
previous = current;
}
*target++ = color;
}
} else if((_interlaceField & 1) == (y & 1)) {
for(u32 x : range(width)) {
auto color = _palette[*source++];
*target++ = color;
@@ -277,14 +334,8 @@ auto Screen::refresh() -> void {
continue;
}
auto previous = *target;
u32 color = current & 0xff000000;
for(u32 shift : {0, 8, 16}) {
auto rise = current >> shift & 0xff;
auto decay = (previous >> shift & 0xff) >> 1;
color |= max(rise, decay) << shift;
}
*target++ = color;
*target = phosphor(current, *target);
target++;
}
} else if(_interframeBlending) {
n32 mask = 1 << 24 | 1 << 16 | 1 << 8 | 1 << 0;
@@ -302,6 +353,7 @@ auto Screen::refresh() -> void {
}
if(_phosphor) _phosphorHistoryValid = true;
if(_interlace && _deinterlace == "Blend") _deinterlaceHistoryValid = true;
if (_colorBleed) {
n32 mask = 1 << 24 | 1 << 16 | 1 << 8 | 1 << 0;
@@ -349,6 +401,9 @@ auto Screen::refresh() -> void {
}
}
output = _rotate.get();
auto x = viewX;
viewX = viewY;
viewY = width - x - viewWidth;
swap(width, height);
swap(viewWidth, viewHeight);
}
@@ -363,6 +418,8 @@ auto Screen::refresh() -> void {
}
}
output = _rotate.get();
viewX = width - viewX - viewWidth;
viewY = height - viewY - viewHeight;
}
if(_rotation == 270) {
@@ -375,6 +432,9 @@ auto Screen::refresh() -> void {
}
}
output = _rotate.get();
auto y = viewY;
viewY = viewX;
viewX = height - y - viewHeight;
swap(width, height);
swap(viewWidth, viewHeight);
}
@@ -400,6 +460,7 @@ auto Screen::clearOverrideLineDraw(u32 y) -> void {
auto Screen::refreshPalette() -> void {
lock_guard<recursive_mutex> lock(_mutex);
if(_palette) return;
_deinterlaceHistoryValid = false;
//generate the color lookup palettes to convert native colors to ARGB8888
_palette = std::make_unique<u32[]>(_colors);
@@ -454,6 +515,7 @@ auto Screen::serialize(string& output, string depth) -> void {
output.append(depth, " fillColor: ", _fillColor, "\n");
output.append(depth, " colorBleed: ", _colorBleed, "\n");
output.append(depth, " interlace: ", _interlace, "\n");
output.append(depth, " deinterlace: ", _deinterlace, "\n");
output.append(depth, " phosphor: ", _phosphor, "\n");
output.append(depth, " interframeBlending: ", _interframeBlending, "\n");
output.append(depth, " rotation: ", _rotation, "\n");
@@ -474,9 +536,11 @@ auto Screen::unserialize(Markup::Node node) -> void {
_fillColor = node["fillColor"].natural();
_colorBleed = node["colorBleed"].boolean();
_interlace = node["interlace"].natural();
_deinterlace = node["deinterlace"].string();
_phosphor = node["phosphor"].boolean();
_interframeBlending = node["interframeBlending"].boolean();
_rotation = node["rotation"].natural();
resetPalette();
resetSprites();
setDeinterlace(_deinterlace);
}
+5
View File
@@ -29,6 +29,7 @@ struct Screen : Video {
auto phosphor() const -> bool { return _phosphor; }
auto interframeBlending() const -> bool { return _interframeBlending; }
auto rotation() const -> u32 { return _rotation; }
auto deinterlace() const -> string { return _deinterlace; }
auto resetPalette() -> void;
auto resetSprites() -> void;
@@ -56,6 +57,7 @@ struct Screen : Video {
auto setProgressive(bool progressiveDouble = false) -> void;
auto setInterlace(bool interlaceField) -> void;
auto setDeinterlace(string deinterlace) -> void;
auto attach(Node::Video::Sprite) -> void;
auto detach(Node::Video::Sprite) -> void;
@@ -100,6 +102,7 @@ protected:
std::unique_ptr<u32[]> _output;
std::unique_ptr<u32[]> _rotate;
std::unique_ptr<u32[]> _palette;
std::unique_ptr<u32[]> _deinterlaceHistory;
std::vector<n1> _lineOverrideActive;
std::vector<const u32*> _lineOverride;
std::vector<Node::Video::Sprite> _sprites;
@@ -116,6 +119,8 @@ protected:
bool _progressiveDouble = false;
bool _interlace = false;
bool _interlaceField = false;
string _deinterlace = "Weave";
bool _deinterlaceHistoryValid = false;
u32 _viewportX = 0;
u32 _viewportY = 0;
u32 _viewportWidth = 0;
-4
View File
@@ -23,10 +23,6 @@ inline Thread::~Thread() {
}
inline auto Thread::active() const -> bool { return co_active() == _handle; }
inline auto Thread::handle() const -> cothread_t { return _handle; }
inline auto Thread::frequency() const -> u64 { return _frequency; }
inline auto Thread::scalar() const -> u64 { return _scalar; }
inline auto Thread::clock() const -> u64 { return _clock; }
inline auto Thread::setHandle(cothread_t handle) -> void {
_handle = handle;
+4 -4
View File
@@ -19,10 +19,10 @@ struct Thread {
explicit operator bool() const { return _handle; }
auto active() const -> bool;
auto handle() const -> cothread_t;
auto frequency() const -> u64;
auto scalar() const -> u64;
auto clock() const -> u64;
auto handle() const -> cothread_t { return _handle; }
auto frequency() const -> u64 { return _frequency; }
auto scalar() const -> u64 { return _scalar; }
auto clock() const -> u64 { return _clock; }
auto setHandle(cothread_t handle) -> void;
auto setFrequency(double frequency) -> void;
+32
View File
@@ -23,6 +23,15 @@ macro(component_sources)
set(ARES_HEADER_SOURCES ${ARES_HEADER_SOURCES} PARENT_SCOPE)
endmacro()
component_sources(
COMPONENT
arcadedip
PRIMARY
arcade/dip-switches.cpp
INCLUDED
arcade/dip-switches.hpp
)
component_sources(
COMPONENT
ay38910
@@ -448,6 +457,13 @@ component_sources(
processor/z80/registers.hpp
processor/z80/serialization.cpp
processor/z80/z80.hpp
processor/z80/sega/base.hpp
processor/z80/sega/s315-5114.hpp
processor/z80/sega/s315-5114.cpp
processor/z80/sega/s315-5177.hpp
processor/z80/sega/s315-5177.cpp
processor/z80/sega/mc8123.hpp
processor/z80/sega/mc8123.cpp
)
component_sources(
@@ -460,6 +476,16 @@ component_sources(
rtc/s3511a/serialization.cpp
)
component_sources(
COMPONENT
upd4990a
PRIMARY
rtc/upd4990a/upd4990a.cpp
INCLUDED
rtc/upd4990a/upd4990a.hpp
rtc/upd4990a/serialization.cpp
)
component_sources(
COMPONENT
sst39sf0x0
@@ -497,3 +523,9 @@ component_sources(
video/v9938/sprite.cpp
video/v9938/v9938.hpp
)
component_sources(
COMPONENT parallel-flash
PRIMARY eeprom/parallel-flash/parallel-flash.cpp
INCLUDED eeprom/parallel-flash/parallel-flash.hpp
)
+129
View File
@@ -0,0 +1,129 @@
#include <ares/ares.hpp>
#include "dip-switches.hpp"
namespace ares::Arcade {
auto Dip::load(Node::Object parent, VFS::Pak pak, std::initializer_list<string> names, u32 excluded, bool reuseNode, string manifest) -> void {
unload();
for(auto name : names) ports.push_back({name});
if(!pak) return;
this->pak = pak;
Markup::Node saved;
if(auto state = pak->read("dips.bml")) saved = BML::unserialize(state->reads());
auto fp = pak->read(manifest);
if(!fp) return;
auto document = BML::unserialize(fp->reads());
auto games = document.find("game");
if(games.size() != 1) return;
ownsNode = !reuseNode;
node = reuseNode ? parent : parent->append<Node::Object>("DIP Switches");
for(auto dip : games.front()) {
if(dip.name() != "dip") continue;
auto portName = dip["port"].string();
u32 port = 0;
while(port < ports.size() && ports[port].name != portName) port++;
if(port == ports.size()) continue;
auto maskValue = dip["mask"].natural();
auto defaultValue = dip["default"].natural();
if(!maskValue || maskValue > 0xffff'ffff || (defaultValue & ~maskValue) || (maskValue & excluded)) continue;
u32 mask = maskValue;
ports[port].value = (ports[port].value & ~mask) | defaultValue;
auto name = dip["name"].string();
if(!name || name == "Unused" || name == "Unknown") continue;
std::vector<Option> options;
string defaultName;
for(auto option : dip) {
if(option.name() != "option") continue;
auto optionName = option["name"].string();
auto optionValue = option["value"].natural();
if(!optionName || optionValue > 0xffff'ffff || (optionValue & ~maskValue)) continue;
for(auto& existing : options) {
if(existing.name == optionName) {
optionName.append(" (", optionValue, ")");
break;
}
}
if(optionValue == defaultValue) defaultName = optionName;
options.push_back({optionName, (u32)optionValue});
}
if(options.empty() || !defaultName) continue;
for(auto stored : saved.find("dip")) {
if(stored["port"].string() != portName || stored["mask"].natural() != mask) continue;
auto value = stored["value"].natural();
for(auto& option : options) {
if(option.value != value) continue;
defaultName = option.name;
ports[port].value = (ports[port].value & ~mask) | option.value;
break;
}
break;
}
auto index = settings.size();
settings.push_back({{}, port, mask, std::move(options)});
auto& setting = settings.back();
std::vector<string> allowed;
for(auto& option : setting.options) allowed.push_back(option.name);
setting.node = node->append<Node::Setting::String>(name, defaultName, [this, index](auto value) {
update(index, value);
});
setting.node->setAllowedValues(std::move(allowed));
setting.node->setDynamic(true);
}
}
auto Dip::unload() -> void {
if(ownsNode) {
if(auto parent = Node::parent(node)) parent->remove(node);
}
node.reset();
pak.reset();
ownsNode = false;
settings.clear();
ports.clear();
}
auto Dip::read(string name) const -> u32 {
for(auto& port : ports) {
if(port.name == name) return port.value;
}
return 0xffff'ffff;
}
auto Dip::update(u32 index, string value) -> void {
if(index >= settings.size()) return;
auto& setting = settings[index];
for(auto& option : setting.options) {
if(option.name != value) continue;
auto& port = ports[setting.port];
port.value = (port.value & ~setting.mask) | option.value;
persist();
return;
}
}
auto Dip::persist() -> void {
if(!pak) return;
auto fp = pak->write("dips.bml");
if(!fp) return;
string state;
for(auto& setting : settings) {
auto& port = ports[setting.port];
state.append("dip\n");
state.append(" port: ", port.name, "\n");
state.append(" mask: ", setting.mask, "\n");
state.append(" value: ", port.value & setting.mask, "\n");
}
fp->resize(state.size());
fp->seek(0);
fp->writes(state);
}
}
+36
View File
@@ -0,0 +1,36 @@
#pragma once
namespace ares::Arcade {
struct Dip {
struct Option {
string name;
u32 value;
};
struct Setting {
Node::Setting::String node;
u32 port;
u32 mask;
std::vector<Option> options;
};
struct Port {
string name;
u32 value = 0xffff'ffff;
};
auto load(Node::Object parent, VFS::Pak pak, std::initializer_list<string> names, u32 excluded = 0, bool reuseNode = false, string manifest = "manifest.bml") -> void;
auto unload() -> void;
auto read(string name) const -> u32;
auto update(u32 index, string value) -> void;
auto persist() -> void;
Node::Object node;
VFS::Pak pak;
bool ownsNode = false;
std::vector<Port> ports;
std::vector<Setting> settings;
};
}
@@ -0,0 +1,125 @@
#include <ares/ares.hpp>
#include "parallel-flash.hpp"
namespace ares {
ParallelFlash::ParallelFlash(Model model) {
switch(model) {
case Model::SST29EE010: capacity = 128_KiB; pageSize = 128; manufacturerID = 0xbf; deviceID = 0x07; break;
case Model::SST29EE020: capacity = 256_KiB; pageSize = 128; manufacturerID = 0xbf; deviceID = 0x10; break;
case Model::W29C040: capacity = 512_KiB; pageSize = 256; manufacturerID = 0xda; deviceID = 0x46; break;
case Model::AT29C040A: capacity = 512_KiB; pageSize = 256; manufacturerID = 0x1f; deviceID = 0xa4; break;
}
}
auto ParallelFlash::power() -> void {
phase = Phase::Read;
command = 0;
remaining = 0;
id = false;
toggle = false;
}
auto ParallelFlash::read(u32 address) -> u8 {
if(memory.size() != capacity) return 0xff;
address &= capacity - 1;
if(phase != Phase::Read) {
toggle = !toggle;
return (lastData ^ 0x80) & 0x80 | (toggle ? 0x40 : 0);
}
if(id) {
if((address & 0x7fff) == 0) return manufacturerID;
if((address & 0x7fff) == 1) return deviceID;
return 0xff;
}
return memory[address];
}
auto ParallelFlash::write(u32 address, u8 data) -> void {
if(memory.size() != capacity || phase == Phase::Program || phase == Phase::Erase) return;
address &= capacity - 1;
auto load = [&] {
if(phase != Phase::Load) nall::memory::fill<u8>(page, pageSize, 0xff);
phase = Phase::Load;
pageAddress = address & ~(pageSize - 1);
page[address & (pageSize - 1)] = lastData = data;
remaining = 200;
command = 0;
};
if(phase == Phase::Load || command == 3) return load();
u32 low = address & 0x7fff;
switch(command) {
case 0:
if(low == 0x5555 && data == 0xaa) command = 1;
else if(!protection && !id) load();
break;
case 1:
command = low == 0x2aaa && data == 0x55 ? 2 : 0;
break;
case 2:
command = 0;
if(low != 0x5555) break;
if(data == 0xa0) {
protection = true;
id = false;
command = 3;
}
if(data == 0x90) id = true;
if(data == 0xf0) id = false;
if(data == 0x80) command = 4;
break;
case 4:
command = low == 0x5555 && data == 0xaa ? 5 : 0;
break;
case 5:
command = low == 0x2aaa && data == 0x55 ? 6 : 0;
break;
case 6:
command = 0;
if(low != 0x5555) break;
if(data == 0x10) {
phase = Phase::Erase;
remaining = 20'000;
lastData = 0xff;
id = false;
}
if(data == 0x20) {
protection = false;
id = false;
}
if(data == 0x60) id = true;
if(data == 0xf0) id = false;
break;
}
}
auto ParallelFlash::step(u32 clocks) -> void {
while(remaining && clocks >= remaining) {
clocks -= remaining;
if(phase == Phase::Load) {
phase = Phase::Program;
remaining = 5'000;
} else {
if(phase == Phase::Program) nall::memory::copy(memory.data() + pageAddress, page, pageSize);
if(phase == Phase::Erase) nall::memory::fill<u8>(memory.data(), memory.size(), 0xff);
phase = Phase::Read;
remaining = 0;
}
}
if(remaining) remaining -= clocks;
}
auto ParallelFlash::serialize(serializer& s) -> void {
if(!memory.empty()) s(std::span<u8>{memory.data(), memory.size()});
s(page);
s(phase);
s(command);
s(pageAddress);
s(remaining);
s(lastData);
s(id);
s(protection);
s(toggle);
}
}
@@ -0,0 +1,29 @@
#pragma once
namespace ares {
struct ParallelFlash {
enum class Model : u32 { SST29EE010, SST29EE020, W29C040, AT29C040A };
std::vector<u8> memory;
u32 capacity;
u32 pageSize;
u8 deviceID;
u8 manufacturerID;
u8 page[256] = {};
enum class Phase : u32 { Read, Load, Program, Erase } phase = Phase::Read;
u32 command = 0;
u32 pageAddress = 0;
u32 remaining = 0;
u8 lastData = 0xff;
bool id = false;
bool protection = false;
bool toggle = false;
ParallelFlash(Model model);
auto power() -> void;
auto read(u32 address) -> u8;
auto write(u32 address, u8 data) -> void;
auto step(u32 microseconds) -> void;
auto serialize(serializer&) -> void;
};
}
+14 -10
View File
@@ -20,7 +20,7 @@ auto HG51B::halt() -> void {
auto HG51B::wait(n24 address) -> u32 {
if(isROM(address)) return 1 + io.wait.rom;
if(isRAM(address)) return 1 + io.wait.ram;
return 1;
return 0;
}
auto HG51B::main() -> void {
@@ -73,20 +73,24 @@ auto HG51B::suspend() -> void {
auto HG51B::cache() -> bool {
n24 address = io.cache.base + r.pb * 512;
//try to use the current page ...
if(io.cache.address[io.cache.page] == address) return io.cache.enable = 0, true;
//if it's not valid, try to use the other page ...
io.cache.page ^= 1;
if(io.cache.address[io.cache.page] == address) return io.cache.enable = 0, true;
//if it's not valid, try to load into the other page ...
if(io.cache.lock[io.cache.page]) io.cache.page ^= 1;
//if it's locked, try to load into the first page ...
if(io.cache.lock[io.cache.page]) return io.cache.enable = 0, false;
if(io.cache.preload == 0) {
//try to use the current page ...
if(io.cache.address[io.cache.page] == address) return io.cache.enable = 0, true;
//if it's not valid, try to use the other page ...
io.cache.page ^= 1;
if(io.cache.address[io.cache.page] == address) return io.cache.enable = 0, true;
//if it's not valid, try to load into the other page ...
if(io.cache.lock[io.cache.page]) io.cache.page ^= 1;
//if it's locked, try to load into the first page ...
if(io.cache.lock[io.cache.page]) return io.cache.enable = 0, false;
}
io.cache.preload = 0;
io.cache.address[io.cache.page] = address;
for(u32 offset : range(256)) {
step(wait(address));
programRAM[io.cache.page][offset].byte(0) = read(address++);
step(wait(address));
programRAM[io.cache.page][offset].byte(1) = read(address++);
}
return io.cache.enable = 0, true;
+1
View File
@@ -156,6 +156,7 @@ protected:
struct Cache {
n1 enable;
n1 page;
n1 preload;
n1 lock[2];
n24 address[2]; //cache address is in bytes; so 24-bit
n24 base; //base address is also in bytes
+4 -4
View File
@@ -12,13 +12,13 @@ auto HG51B::readRegister(n7 address) -> n24 {
case 0x2e:
io.bus.enable = 1;
io.bus.reading = 1;
io.bus.pending = 1 + io.wait.rom;
io.bus.pending = io.wait.rom;
io.bus.address = r.mar;
return 0x000000;
case 0x2f:
io.bus.enable = 1;
io.bus.reading = 1;
io.bus.pending = 1 + io.wait.ram;
io.bus.pending = io.wait.ram;
io.bus.address = r.mar;
return 0x000000;
@@ -76,13 +76,13 @@ auto HG51B::writeRegister(n7 address, n24 data) -> void {
case 0x2e:
io.bus.enable = 1;
io.bus.writing = 1;
io.bus.pending = 1 + io.wait.rom;
io.bus.pending = io.wait.rom;
io.bus.address = r.mar;
return;
case 0x2f:
io.bus.enable = 1;
io.bus.writing = 1;
io.bus.pending = 1 + io.wait.ram;
io.bus.pending = io.wait.ram;
io.bus.address = r.mar;
return;
@@ -4,11 +4,11 @@ auto Z80::disassembleInstruction(maybe<n16> _pc) -> string {
string s, output;
n8 prefix = 0x00;
auto code = bus->read(pc++);
auto code = bus->fetch(pc++);
if(code == 0xdd || code == 0xfd) {
prefix = code;
code = bus->read(pc++);
code = bus->fetch(pc++);
if(code == 0xdd || code == 0xfd) {
if(prefix == 0xdd) {
s.append("ix:");
@@ -23,13 +23,13 @@ auto Z80::disassembleInstruction(maybe<n16> _pc) -> string {
if(code == 0xcb && prefix) {
auto d = (i8)bus->read(pc++);
code = bus->read(pc++);
code = bus->read(pc++); //DD/FD CB's final byte is fetched without M1
output = disassembleCBd(pc, prefix, d, code);
} else if(code == 0xcb) {
code = bus->read(pc++);
code = bus->fetch(pc++);
output = disassembleCB(pc, prefix, code);
} else if(code == 0xed) {
code = bus->read(pc++);
code = bus->fetch(pc++);
output = disassembleED(pc, prefix, code);
} else {
output = disassemble(pc, prefix, code);
+1 -1
View File
@@ -4,7 +4,7 @@ auto Z80::wait(u32 clocks) -> void {
auto Z80::opcode() -> n8 {
step(4);
return bus->read(PC++);
return bus->fetch(PC++);
}
auto Z80::operand() -> n8 {
@@ -0,0 +1,12 @@
#pragma once
#include <component/processor/z80/z80.hpp>
namespace ares {
struct SegaZ80 {
virtual ~SegaZ80() = default;
virtual auto decrypt(n16 address, n8 source, bool opcode) const -> n8 = 0;
};
}
@@ -0,0 +1,295 @@
// MC-8123 decryption algorithm derived from MAME (BSD-3-Clause).
// See the MAME section in LICENSE.
auto MC8123::reorder(n8 source, const u8 (&bits)[8]) const -> n8 {
n8 result = 0;
for(u32 index : range(8)) result.bit(7 - index) = source.bit(bits[index]);
return result;
}
auto MC8123::permute(n8 source, n3 type, n2 permutation) const -> n8 {
static constexpr u8 positions[7][4][8] = {
{
{7,5,3,1,2,0,6,4},
{5,3,7,2,1,0,4,6},
{0,3,4,6,7,1,5,2},
{0,7,3,2,6,4,1,5},
},
{ // Type1a
{4,2,6,5,3,7,1,0},
{6,0,5,4,3,2,1,7},
{2,3,6,1,4,0,7,5},
{6,5,1,3,2,7,0,4},
},
{ // Type1b
{1,0,3,2,5,6,4,7},
{2,0,5,1,7,4,6,3},
{6,4,7,2,0,5,1,3},
{7,1,3,6,0,2,5,4},
},
{ // Type2a
{0,1,4,3,5,6,2,7},
{6,3,0,5,7,4,1,2},
{1,6,4,5,0,3,7,2},
{4,6,7,5,2,3,1,0},
},
{ // Type2b
{1,3,4,6,5,7,0,2},
{0,1,5,4,7,3,2,6},
{3,5,4,1,6,2,0,7},
{5,2,3,0,4,7,6,1},
},
{ // Type3a
{5,3,1,7,0,2,6,4},
{3,1,2,5,4,7,0,6},
{5,6,1,2,7,0,4,3},
{5,6,7,0,4,2,1,3},
},
{ // Type3b
{3,7,5,4,0,6,2,1},
{7,5,4,6,1,2,0,3},
{7,4,3,0,5,1,6,2},
{2,6,4,1,3,7,0,5},
},
};
return reorder(source, positions[type][permutation]);
}
auto MC8123::type0(n8 value, n4 parameter, n2 permutation) const -> n8 {
value = permute(value, 0, permutation);
if(parameter.bit(3) && value.bit(7)) value ^= 0x29;
if(parameter.bit(2) && value.bit(6)) value ^= 0x86;
if(value.bit(6)) value ^= 0x80;
if(parameter.bit(1) && value.bit(7)) value ^= 0x40;
if(value.bit(2)) value ^= 0x21;
value ^= 0x1a;
if(parameter.bit(2)) value ^= 0x25;
if(parameter.bit(1)) value ^= 0xc0;
if(parameter.bit(0)) value ^= 0x21;
if(parameter.bit(0)) value = reorder(value, {7,6,5,1,4,3,2,0});
return value;
}
auto MC8123::type1a(n8 value, n4 parameter, n2 permutation) const -> n8 {
value = permute(value, 1, permutation);
if(parameter.bit(2)) value = reorder(value, {7,6,1,5,3,2,4,0});
if(value.bit(1)) value ^= 0x01;
if(value.bit(6)) value ^= 0x08;
if(value.bit(7)) value ^= 0x48;
if(value.bit(2)) value ^= 0x4a;
if(value.bit(4)) value ^= 0xc4;
if(value.bit(7) ^ value.bit(2)) value ^= 0x10;
value ^= 0x4b;
if(parameter.bit(3)) value ^= 0x84;
if(parameter.bit(1)) value ^= 0x48;
if(parameter.bit(0)) value = reorder(value, {7,6,1,4,3,2,5,0});
return value;
}
auto MC8123::type1b(n8 value, n4 parameter, n2 permutation) const -> n8 {
value = permute(value, 2, permutation);
if(value.bit(2) && value.bit(0)) value ^= 0x90;
if(value.bit(7)) value ^= 0x04;
if(value.bit(5)) value ^= 0x84;
if(value.bit(1)) value ^= 0x20;
if(value.bit(6)) value ^= 0x02;
if(value.bit(4)) value ^= 0x60;
if(value.bit(0)) value ^= 0x46;
if(value.bit(3)) value ^= 0xc7;
value ^= 0x51;
if(parameter.bit(3)) value ^= 0x12;
if(parameter.bit(2)) value ^= 0xc9;
if(parameter.bit(1)) value ^= 0x18;
if(parameter.bit(0)) value ^= 0x47;
return value;
}
auto MC8123::type2a(n8 value, n4 parameter, n2 permutation) const -> n8 {
value = permute(value, 3, permutation);
if(value.bit(3) || (parameter.bit(1) && value.bit(2)))
value = reorder(value, {6,0,7,4,3,2,1,5});
if(value.bit(5)) value ^= 0x80;
if(value.bit(6)) value ^= 0x20;
if(value.bit(0)) value ^= 0x40;
if(value.bit(4)) value ^= 0x09;
if(value.bit(1)) value ^= 0x04;
value ^= 0xf2;
if(parameter.bit(2)) value ^= 0x1f;
if(parameter.bit(3)) {
if(parameter.bit(0)) value = reorder(value, {7,6,5,3,4,1,2,0});
else value = reorder(value, {7,6,5,1,2,4,3,0});
} else if(parameter.bit(0)) {
value = reorder(value, {7,6,5,2,1,3,4,0});
}
return value;
}
auto MC8123::type2b(n8 value, n4 parameter, n2 permutation) const -> n8 {
value = permute(value, 4, permutation);
if(value.bit(7) && value.bit(3)) value ^= 0x51;
if(value.bit(7)) value ^= 0x04;
if(value.bit(5)) value ^= 0x88;
if(value.bit(1)) value ^= 0x20;
if(value.bit(4)) value ^= 0xaa;
if(value.bit(7) && value.bit(5)) value ^= 0x11;
if(value.bit(5) && value.bit(1)) value ^= 0x11;
if(value.bit(6)) value ^= 0xa0;
if(value.bit(3)) value ^= 0xe2;
if(value.bit(2)) value ^= 0x0a;
value ^= 0x8e;
if(parameter.bit(3)) value ^= 0x4a;
if(parameter.bit(2)) value ^= 0xee;
if(parameter.bit(1)) value ^= 0x80;
if(parameter.bit(0)) value ^= 0x24;
return value;
}
auto MC8123::type3a(n8 value, n4 parameter, n2 permutation) const -> n8 {
value = permute(value, 5, permutation);
if(value.bit(2)) value ^= 0xb0;
if(value.bit(3)) value ^= 0x01;
if(parameter.bit(0)) value = reorder(value, {7,2,5,4,3,1,0,6});
if(value.bit(1)) value ^= 0x41;
if(value.bit(3)) value ^= 0x16;
if(parameter.bit(3)) value ^= 0x18;
if(value.bit(3)) value = reorder(value, {5,6,7,4,3,2,1,0});
if(value.bit(5)) value ^= 0x06;
value ^= 0x78;
if(parameter.bit(2)) value ^= 0x80;
if(parameter.bit(1)) value ^= 0x10;
if(parameter.bit(0)) value ^= 0x01;
return value;
}
auto MC8123::type3b(n8 value, n4 parameter, n2 permutation) const -> n8 {
value = permute(value, 6, permutation);
if(value.bit(2)) value ^= 0x80;
if(value.bit(7)) value = reorder(value, {7,6,3,4,5,2,1,0});
if(parameter.bit(3)) value ^= 0x80;
if(value.bit(4)) value ^= 0x40;
if(value.bit(1)) value ^= 0x54;
if(value.bit(7) && value.bit(6)) value ^= 0x02;
if(value.bit(7)) value ^= 0x02;
if(parameter.bit(3)) value ^= 0x80;
if(parameter.bit(2)) value ^= 0x01;
if(parameter.bit(3)) value = reorder(value, {4,6,3,2,5,0,1,7});
if(value.bit(4)) value ^= 0x02;
if(value.bit(5)) value ^= 0x10;
if(value.bit(7)) value ^= 0x04;
value ^= 0x2c;
if(parameter.bit(1)) value ^= 0x80;
if(parameter.bit(0)) value ^= 0x08;
return value;
}
auto MC8123::transform(n8 value, n8 key, bool opcode) const -> n8 {
key ^= 0xff;
if(!key) return value;
n3 type = 0;
type.bit(0) = key.bit(0) ^ key.bit(2);
type.bit(1) = key.bit(0) ^ key.bit(1) ^ key.bit(2) ^ key.bit(4);
type.bit(2) = key.bit(4) ^ key.bit(5);
n2 permutation = 0;
permutation.bit(0) = key.bit(0) ^ key.bit(1);
permutation.bit(1) = key.bit(2) ^ key.bit(3);
n4 parameter = 0;
parameter.bit(0) = key.bit(0);
parameter.bit(1) = key.bit(0) ^ key.bit(2) ^ key.bit(3);
parameter.bit(2) = key.bit(0) ^ key.bit(1) ^ key.bit(6);
parameter.bit(3) = key.bit(1) ^ key.bit(6) ^ key.bit(7);
if(!opcode) {
parameter.bit(0) = !parameter.bit(0);
type.bit(0) = !type.bit(0);
}
switch(type) {
case 0:
case 1: return type0(value, parameter, permutation);
case 2: return type1a(value, parameter, permutation);
case 3: return type1b(value, parameter, permutation);
case 4: return type2a(value, parameter, permutation);
case 5: return type2b(value, parameter, permutation);
case 6: return type3a(value, parameter, permutation);
case 7: return type3b(value, parameter, permutation);
}
return value;
}
auto MC8123::decrypt(n16 address, n8 source, bool opcode) const -> n8 {
if(address >= encryptedSize) return source;
n12 index = 0;
index.bit(0) = address.bit(0);
index.bit(1) = address.bit(1);
index.bit(2) = address.bit(2);
index.bit(3) = address.bit(4);
index.bit(4) = address.bit(6);
index.bit(5) = address.bit(8);
index.bit(6) = address.bit(10);
index.bit(7) = address.bit(11);
index.bit(8) = address.bit(12);
index.bit(9) = address.bit(13);
index.bit(10) = address.bit(14);
index.bit(11) = address.bit(15);
return transform(source, keyData[index | (opcode ? 0 : 0x1000)], opcode);
}
@@ -0,0 +1,26 @@
#pragma once
#include <component/processor/z80/sega/base.hpp>
namespace ares {
struct MC8123 : SegaZ80 {
u8 keyData[0x2000] = {};
u16 encryptedSize = 0xc000;
auto decrypt(n16 address, n8 source, bool opcode) const -> n8 override;
private:
auto reorder(n8 source, const u8 (&bits)[8]) const -> n8;
auto permute(n8 source, n3 type, n2 permutation) const -> n8;
auto type0(n8 value, n4 parameter, n2 permutation) const -> n8;
auto type1a(n8 value, n4 parameter, n2 permutation) const -> n8;
auto type1b(n8 value, n4 parameter, n2 permutation) const -> n8;
auto type2a(n8 value, n4 parameter, n2 permutation) const -> n8;
auto type2b(n8 value, n4 parameter, n2 permutation) const -> n8;
auto type3a(n8 value, n4 parameter, n2 permutation) const -> n8;
auto type3b(n8 value, n4 parameter, n2 permutation) const -> n8;
auto transform(n8 value, n8 key, bool opcode) const -> n8;
};
}
@@ -0,0 +1,44 @@
// Sega 315-5114 decryption table derived from MAME (BSD-3-Clause).
// See the MAME section in LICENSE.
auto S315_5114::decrypt(n16 address, n8 source, bool opcode) const -> n8 {
if(address >= 0x8000) return source;
static constexpr u8 table[32][4] = {
{0x28,0x08,0xa8,0x88}, {0x88,0x80,0x08,0x00},
{0x28,0x08,0xa8,0x88}, {0x28,0xa8,0x08,0x88},
{0x88,0x80,0x08,0x00}, {0x88,0x08,0x80,0x00},
{0x88,0x08,0x80,0x00}, {0x28,0xa8,0x08,0x88},
{0x28,0x08,0xa8,0x88}, {0x88,0x80,0x08,0x00},
{0x88,0x80,0x08,0x00}, {0x88,0x80,0x08,0x00},
{0x88,0x08,0x80,0x00}, {0x88,0x08,0x80,0x00},
{0xa0,0x80,0xa8,0x88}, {0xa0,0x80,0xa8,0x88},
{0x80,0xa0,0x00,0x20}, {0x28,0x08,0xa8,0x88},
{0x28,0xa8,0x08,0x88}, {0x28,0x08,0xa8,0x88},
{0x80,0xa0,0x00,0x20}, {0x80,0xa0,0x00,0x20},
{0x28,0xa8,0x08,0x88}, {0x80,0xa0,0x00,0x20},
{0xa0,0x80,0xa8,0x88}, {0x28,0x08,0xa8,0x88},
{0x80,0xa0,0x00,0x20}, {0xa0,0x80,0xa8,0x88},
{0xa0,0x80,0xa8,0x88}, {0x80,0xa0,0x00,0x20},
{0xa0,0x80,0xa8,0x88}, {0xa0,0x80,0xa8,0x88},
};
n4 row = 0;
row.bit(0) = address.bit(0);
row.bit(1) = address.bit(4);
row.bit(2) = address.bit(8);
row.bit(3) = address.bit(12);
n2 column = 0;
column.bit(0) = source.bit(3);
column.bit(1) = source.bit(5);
n8 invert = 0;
if(source.bit(7)) {
column = 3 - column;
invert = 0xa8;
}
n8 decoded = table[2 * row + !opcode][column] ^ invert;
n8 result = source;
result.bit(3) = decoded.bit(3);
result.bit(5) = decoded.bit(5);
result.bit(7) = decoded.bit(7);
return result;
}
@@ -0,0 +1,11 @@
#pragma once
#include <component/processor/z80/sega/base.hpp>
namespace ares {
struct S315_5114 : SegaZ80 {
auto decrypt(n16 address, n8 source, bool opcode) const -> n8 override;
};
}
@@ -0,0 +1,55 @@
// Sega 315-5177 decryption algorithm derived from MAME (BSD-3-Clause).
// See the MAME section in LICENSE.
auto S315_5177::decrypt(n16 address, n8 source, bool opcode) const -> n8 {
if(address >= 0x8000) return source;
static constexpr u8 permutations[24][4] = {
{6,4,2,0}, {4,6,2,0}, {2,4,6,0}, {0,4,2,6},
{6,2,4,0}, {6,0,2,4}, {6,4,0,2}, {2,6,4,0},
{4,2,6,0}, {4,6,0,2}, {6,0,4,2}, {0,6,4,2},
{4,0,6,2}, {0,4,6,2}, {6,2,0,4}, {2,6,0,4},
{0,6,2,4}, {2,0,6,4}, {0,2,6,4}, {4,2,0,6},
{2,4,0,6}, {4,0,2,6}, {2,0,4,6}, {0,2,4,6},
};
static constexpr u8 pattern[24] = {
0x04,0x54,0x51,0x15,0x40,0x44,0x01,0x51,
0x55,0x10,0x44,0x41,0x05,0x55,0x50,0x14,
0x41,0x45,0x00,0x50,0x54,0x11,0x45,0x40,
};
static constexpr u8 permutationPattern[64] = {
0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3,
3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6,
6, 7, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9,10,
10,10,10,10,11,11,11,11,11,12,12,12,12,12,13,13,
};
n6 row = 0;
row.bit(0) = address.bit(0);
row.bit(1) = address.bit(3);
row.bit(2) = address.bit(6);
row.bit(3) = address.bit(9);
row.bit(4) = address.bit(12);
row.bit(5) = address.bit(14);
n7 index = 0;
index.bit(0) = !opcode;
index.bit(1,6) = row;
n6 patternIndex = index.bit(0,5);
unsigned permutationIndex = permutationPattern[patternIndex] + (index.bit(6) ? 8 : 0);
n8 mask = pattern[patternIndex % 24];
if(permutationIndex == 0) return source ^ mask;
const u8* positions = permutations[permutationIndex];
n8 result = 0;
result.bit(7) = source.bit(7);
result.bit(6) = source.bit(positions[0]);
result.bit(5) = source.bit(5);
result.bit(4) = source.bit(positions[1]);
result.bit(3) = source.bit(3);
result.bit(2) = source.bit(positions[2]);
result.bit(1) = source.bit(1);
result.bit(0) = source.bit(positions[3]);
return result ^ mask;
}
@@ -0,0 +1,11 @@
#pragma once
#include <component/processor/z80/sega/base.hpp>
namespace ares {
struct S315_5177 : SegaZ80 {
auto decrypt(n16 address, n8 source, bool opcode) const -> n8 override;
};
}
+6
View File
@@ -1,5 +1,8 @@
#include <ares/ares.hpp>
#include "z80.hpp"
#include <component/processor/z80/sega/s315-5114.hpp>
#include <component/processor/z80/sega/s315-5177.hpp>
#include <component/processor/z80/sega/mc8123.hpp>
namespace ares {
@@ -10,6 +13,9 @@ namespace ares {
#include "algorithms.cpp"
#include "instructions.cpp"
#include "serialization.cpp"
#include <component/processor/z80/sega/s315-5114.cpp>
#include <component/processor/z80/sega/s315-5177.cpp>
#include <component/processor/z80/sega/mc8123.cpp>
auto Z80::power(MOSFET mosfet) -> void {
this->mosfet = mosfet;
+1
View File
@@ -7,6 +7,7 @@ namespace ares {
struct Z80 {
struct Bus {
virtual auto read(n16 address) -> n8 = 0;
virtual auto fetch(n16 address) -> n8 { return read(address); }
virtual auto write(n16 address, n8 data) -> void = 0;
virtual auto in(n16 address) -> n8 = 0;
@@ -0,0 +1,13 @@
auto uPD4990A::serialize(serializer& s) -> void {
s(data);
s(clockPin);
s(strobePin);
s(shiftRegister);
s(commandRegister);
s(command);
s(outputBit);
s(timePulseOutput);
s(counter);
s(timePulseMode);
s(timePulseCounter);
}
+163
View File
@@ -0,0 +1,163 @@
#include <ares/ares.hpp>
#include "upd4990a.hpp"
namespace ares {
#include "serialization.cpp"
auto uPD4990A::load() -> void {
n64 saved = timestamp();
if(!saved || !(saved + 1)) {
time_t t = time(0);
struct tm tmm = *localtime(&t);
second() = BCD::encode(tmm.tm_sec);
minute() = BCD::encode(tmm.tm_min);
hour() = BCD::encode(tmm.tm_hour);
day() = BCD::encode(tmm.tm_mday);
setWeekday(tmm.tm_wday);
setMonth(tmm.tm_mon + 1);
year() = BCD::encode(tmm.tm_year % 100);
return;
}
//catch up elapsed seconds since last save
time_t now = time(0);
time_t prev = (time_t)saved;
if(now > prev) {
n64 elapsed = now - prev;
while(elapsed--) tickSecond();
}
}
auto uPD4990A::save() -> void {
n64 t = time(0);
for(auto n : range(8)) data[8 + n] = t.byte(n);
}
auto uPD4990A::power() -> void {
clockPin = 0;
strobePin = 0;
for(auto& b : shiftRegister) b = 0;
commandRegister = 0;
command = RegisterHold;
outputBit = 0;
timePulseOutput = 0;
counter = 0;
timePulseMode = TimePulse64Hz;
timePulseCounter = 0;
}
auto uPD4990A::tick() -> void {
if(++counter == 0) tickSecond();
u32 halfPeriod = 256;
if(timePulseMode == TimePulse256Hz) halfPeriod = 64;
if(timePulseMode == TimePulse2048Hz) halfPeriod = 8;
if(timePulseMode == 0x7) halfPeriod = 4;
if(timePulseMode == TimePulse1s) halfPeriod = 16'384;
if(timePulseMode == TimePulse10s) halfPeriod = 163'840;
if(timePulseMode == TimePulse30s) halfPeriod = 491'520;
if(timePulseMode == TimePulse60s) halfPeriod = 983'040;
if(++timePulseCounter >= halfPeriod) {
timePulseCounter = 0;
timePulseOutput ^= 1;
}
if(command == RegisterHold || command == TimeRead) {
outputBit = counter.bit(14);
}
}
auto uPD4990A::tickSecond() -> void {
auto bcdIncrement = [](n8& data) -> n8 {
if((data & 0x0f) >= 0x09) {
data = (data & 0xf0) + 0x10;
} else {
data++;
}
return data;
};
if(bcdIncrement(second()) < 0x60) return;
second() = 0;
if(bcdIncrement(minute()) < 0x60) return;
minute() = 0;
if(bcdIncrement(hour()) < 0x24) return;
hour() = 0;
setWeekday((getWeekday() + 1) % 7);
n8 bcdDaysInMonth[12] = {0x31,0x28,0x31,0x30,0x31,0x30,0x31,0x31,0x30,0x31,0x30,0x31};
u32 y = BCD::decode(year());
if(!(y % 4)) bcdDaysInMonth[1] = 0x29;
u32 m = getMonth();
if(m >= 1 && m <= 12) {
if(bcdIncrement(day()) <= bcdDaysInMonth[m - 1]) return;
}
day() = 0x01;
m++;
if(m <= 12) {
setMonth(m);
return;
}
setMonth(1);
if(bcdIncrement(year()) > 0x99) year() = 0;
}
auto uPD4990A::dataOut() -> n1 {
return outputBit;
}
auto uPD4990A::timePulse() -> n1 {
return timePulseOutput;
}
auto uPD4990A::write(n1 din, n1 clock, n1 strobe) -> void {
if(strobe && !strobePin) {
command = commandRegister;
if(command == TimeSet) {
for(u32 index : range(6)) data[index] = shiftRegister[index];
}
if(command == TimeRead) {
for(u32 index : range(6)) shiftRegister[index] = data[index];
outputBit = counter.bit(14);
}
if(command == RegisterShift) {
outputBit = shiftRegister[0] & 1;
}
if(command == RegisterHold) outputBit = counter.bit(14);
if(command >= TimePulse64Hz && command <= TimePulse60s) {
timePulseMode = command;
timePulseCounter = 0;
timePulseOutput = 0;
}
}
strobePin = strobe;
if(clock && !clockPin) {
n1 cmdOut = commandRegister.bit(0);
commandRegister >>= 1;
commandRegister.bit(3) = din;
if(command == RegisterShift) {
for(u32 index : range(6)) {
n1 nextBit = (index < 5) ? n1(shiftRegister[index + 1] & 1) : n1(cmdOut);
shiftRegister[index] >>= 1;
shiftRegister[index] |= (nextBit << 7);
}
outputBit = shiftRegister[0] & 1;
}
}
clockPin = clock;
}
}
+72
View File
@@ -0,0 +1,72 @@
#pragma once
namespace ares {
//NEC uPD4990A Calendar & Clock
struct uPD4990A {
//upd4990a.cpp
auto load() -> void;
auto save() -> void;
auto power() -> void;
auto tick() -> void;
auto tickSecond() -> void;
auto dataOut() -> n1;
auto timePulse() -> n1;
auto write(n1 din, n1 clock, n1 strobe) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
static constexpr u32 size = 16;
n8 data[size];
auto second() -> n8& { return data[0]; } //BCD 00-59
auto minute() -> n8& { return data[1]; } //BCD 00-59
auto hour() -> n8& { return data[2]; } //BCD 00-23
auto day() -> n8& { return data[3]; } //BCD 01-31
auto monthWeekday() -> n8& { return data[4]; } //(month << 4) | weekday
auto year() -> n8& { return data[5]; } //BCD 00-99
auto getMonth() -> u8 { return data[4] >> 4; }
auto getWeekday() -> u8 { return data[4] & 0x0f; }
auto setMonth(u8 m) -> void { data[4] = (m << 4) | (data[4] & 0x0f); }
auto setWeekday(u8 w) -> void { data[4] = (data[4] & 0xf0) | (w & 0x0f); }
n1 clockPin;
n1 strobePin;
n8 shiftRegister[6];
n4 commandRegister;
n4 command;
n1 outputBit;
n1 timePulseOutput;
n15 counter;
n4 timePulseMode;
u32 timePulseCounter;
auto timestamp() -> n64 {
n64 value = 0;
for(auto index : range(8)) value.byte(index) = data[8 + index];
return value;
}
enum Command : u32 {
RegisterHold = 0x0,
RegisterShift = 0x1,
TimeSet = 0x2,
TimeRead = 0x3,
TimePulse64Hz = 0x4,
TimePulse256Hz = 0x5,
TimePulse2048Hz = 0x6,
TimePulse1s = 0x8,
TimePulse10s = 0x9,
TimePulse30s = 0xa,
TimePulse60s = 0xb,
Test = 0xf,
};
};
}
+1 -1
View File
@@ -1,4 +1,5 @@
ares_components(
arcadedip
mos6502
ym2149
ym2413
@@ -33,7 +34,6 @@ ares_add_sources(
INCLUDED #
vs/unisystem.hpp
vs/controls.cpp
vs/dip-switches.cpp
vs/io.cpp
vs/standard-input.cpp
vs/zapper-input.cpp
+1
View File
@@ -9,6 +9,7 @@
#include <component/audio/ym2413/ym2413.hpp>
#include <component/eeprom/m24c/m24c.hpp>
#include <component/flash/sst39sf0x0/sst39sf0x0.hpp>
#include <component/arcade/dip-switches.hpp>
#include "ymfm_opn.h"
namespace ares::Famicom {
+1 -1
View File
@@ -1,4 +1,4 @@
static const string SerializerVersion = "v156";
static const string SerializerVersion = "v157";
auto System::serialize(bool synchronize) -> serializer {
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
-116
View File
@@ -1,116 +0,0 @@
static auto parseDipByte(string text, u8& value) -> bool {
if(!text) return false;
u32 base = 10;
u32 offset = 0;
if(text.size() > 2 && text[0] == '0' && (text[1] == 'x' || text[1] == 'X')) {
base = 16;
offset = 2;
}
if(offset == text.size()) return false;
u32 result = 0;
for(auto index : range(offset, text.size())) {
u32 digit;
auto character = text[(u32)index];
if(character >= '0' && character <= '9') digit = character - '0';
else if(character >= 'a' && character <= 'f') digit = character - 'a' + 10;
else if(character >= 'A' && character <= 'F') digit = character - 'A' + 10;
else return false;
if(digit >= base || result > (255 - digit) / base) return false;
result = result * base + digit;
}
value = result;
return true;
}
auto VsUniSystem::DIPSwitches::load(Node::Object parent) -> bool {
unload();
if(!cartridge.pak) return false;
auto manifestFile = cartridge.pak->read("manifest.bml");
if(!manifestFile) return false;
auto document = BML::unserialize(manifestFile->reads());
auto games = document.find("game");
if(games.size() != 1) return false;
u8 usedMasks = 0;
std::vector<Definition> definitions;
for(auto node : games.front()) {
if(node.name() != "dip") continue;
Definition definition;
definition.name = node["name"].string();
if(!definition.name) return false;
if(node["port"].string() != "DSW0") return false;
if(!parseDipByte(node["mask"].string(), definition.mask) || !definition.mask) return false;
if(usedMasks & definition.mask) return false;
if(!parseDipByte(node["default"].string(), definition.defaultValue)) return false;
if(definition.defaultValue & ~definition.mask) return false;
bool hasDefault = false;
for(auto optionNode : node) {
if(optionNode.name() != "option") continue;
Option option;
option.name = optionNode["name"].string();
if(!option.name) return false;
if(!parseDipByte(optionNode["value"].string(), option.value)) return false;
if(option.value & ~definition.mask) return false;
for(auto& existing : definition.options) {
if(existing.name == option.name) return false;
}
if(option.value == definition.defaultValue) hasDefault = true;
definition.options.push_back(std::move(option));
}
if(definition.options.empty() || !hasDefault) return false;
usedMasks |= definition.mask;
definitions.push_back(std::move(definition));
}
create(parent, std::move(definitions));
return true;
}
auto VsUniSystem::DIPSwitches::create(Node::Object parent, std::vector<Definition> definitions) -> void {
node = parent->append<Node::Object>("DIP Switches");
settings.reserve(definitions.size());
for(auto& definition : definitions) {
value |= definition.defaultValue;
auto index = settings.size();
std::vector<string> allowedValues;
for(auto& option : definition.options) allowedValues.push_back(option.name);
string defaultValue;
for(auto& option : definition.options) {
if(option.value == definition.defaultValue) defaultValue = option.name;
}
settings.push_back({{}, definition.mask, std::move(definition.options)});
auto& setting = settings.back();
setting.node = node->append<Node::Setting::String>(definition.name, defaultValue, [index](auto value) {
vsUniSystem.dipSwitches.update(index, value);
});
setting.node->setAllowedValues(std::move(allowedValues));
}
}
auto VsUniSystem::DIPSwitches::unload() -> void {
if(auto parent = Node::parent(node)) parent->remove(node);
node.reset();
settings.clear();
value = 0;
}
auto VsUniSystem::DIPSwitches::update(u32 index, string value) -> void {
if(index >= settings.size()) return;
auto& setting = settings[index];
for(auto& option : setting.options) {
if(option.name != value) continue;
this->value = (this->value & ~setting.mask) | (option.value & setting.mask);
return;
}
}
+3 -2
View File
@@ -8,14 +8,15 @@ auto VsUniSystem::IO::read(n16 address, n8 data) -> n8 {
data.bit(0) = readStream(address == 0x4017);
data.bit(1) = 0;
n8 dips = vsUniSystem.dips.read("DSW0");
if(address == 0x4016) {
data.bit(2) = servicePulse.frames != 0;
data.bit(3, 4) = vsUniSystem.dipSwitches.value.bit(0, 1);
data.bit(3, 4) = dips.bit(0, 1);
data.bit(5) = coinPulses[0].frames != 0;
data.bit(6) = coinPulses[1].frames != 0;
data.bit(7) = 0;
} else {
data.bit(2, 7) = vsUniSystem.dipSwitches.value.bit(2, 7);
data.bit(2, 7) = dips.bit(2, 7);
}
return data;
-5
View File
@@ -6,10 +6,6 @@ auto VsUniSystem::Controls::serialize(serializer& s) -> void {
if(input) input->serialize(s);
}
auto VsUniSystem::DIPSwitches::serialize(serializer& s) -> void {
s(value);
}
auto VsUniSystem::IO::serialize(serializer& s) -> void {
s(streams);
s(positions);
@@ -24,6 +20,5 @@ auto VsUniSystem::IO::serialize(serializer& s) -> void {
auto VsUniSystem::serialize(serializer& s) -> void {
s(controls);
s(dipSwitches);
s(io);
}
+2 -6
View File
@@ -7,7 +7,6 @@ VsUniSystem vsUniSystem;
#include "standard-input.cpp"
#include "zapper-input.cpp"
#include "controls.cpp"
#include "dip-switches.cpp"
#include "io.cpp"
#include "serialization.cpp"
@@ -17,7 +16,7 @@ auto VsUniSystem::load(Node::Object parent) -> void {
}
auto VsUniSystem::unload() -> void {
dipSwitches.unload();
dips.unload();
controls.unload();
io.power();
node.reset();
@@ -29,10 +28,7 @@ auto VsUniSystem::power(bool reset) -> void {
if(reset) return;
if(!controls.load(node)) return;
if(!dipSwitches.load(parent)) {
controls.unload();
return;
}
dips.load(parent, cartridge.pak, {"DSW0"});
}
auto VsUniSystem::frame() -> void {
+1 -31
View File
@@ -67,37 +67,7 @@ struct VsUniSystem {
std::unique_ptr<Input> input;
} controls;
struct DIPSwitches {
struct Option {
string name;
u8 value = 0;
};
struct Setting {
Node::Setting::String node;
u8 mask = 0;
std::vector<Option> options;
};
Node::Object node;
std::vector<Setting> settings;
n8 value = 0;
auto load(Node::Object parent) -> bool;
auto unload() -> void;
auto update(u32 index, string value) -> void;
auto serialize(serializer&) -> void;
private:
struct Definition {
string name;
u8 mask = 0;
u8 defaultValue = 0;
std::vector<Option> options;
};
auto create(Node::Object parent, std::vector<Definition> definitions) -> void;
} dipSwitches;
Arcade::Dip dips;
struct IO {
struct Pulse {
+12 -1
View File
@@ -1,4 +1,4 @@
ares_components(z80 sn76489 ym2413)
ares_components(arcadedip z80 sn76489 ym2413 i8255)
ares_add_sources(
CORE #
@@ -10,6 +10,8 @@ ares_add_sources(
vdp/vdp.cpp
psg/psg.cpp
opll/opll.cpp
systeme/ppi.cpp
systeme/memory.cpp
system/system.cpp
cartridge/cartridge.cpp
controller/controller.cpp
@@ -48,6 +50,7 @@ ares_add_sources(
cartridge/board/korea.cpp
cartridge/board/pak4.cpp
cartridge/board/sega.cpp
cartridge/board/system-e.cpp
cartridge/board/zemina.cpp
)
@@ -151,6 +154,14 @@ ares_add_sources(
opll/serialization.cpp
)
ares_add_sources(
CORE #
ms
INCLUDED #
systeme/ppi.hpp
systeme/memory.hpp
)
ares_add_sources(
CORE #
ms
+1
View File
@@ -1,6 +1,7 @@
namespace Board {
#include "sega.cpp"
#include "system-e.cpp"
#include "codemasters.cpp"
#include "korea.cpp"
#include "zemina.cpp"
+1
View File
@@ -10,6 +10,7 @@ struct Interface {
virtual auto unload() -> void {}
virtual auto read(n16 address, n8 data) -> n8 { return data; }
virtual auto write(n16 address, n8 data) -> void {}
virtual auto out(n16 address, n8 data) -> void {}
virtual auto power() -> void {}
virtual auto serialize(serializer&) -> void {}
+33
View File
@@ -0,0 +1,33 @@
struct SystemE : Interface {
using Interface::Interface;
Memory::Readable<n8> rom;
n8 bank;
auto load() -> void override {
Interface::load(rom, "program.rom");
}
auto read(n16 address, n8 data) -> n8 override {
if(address < 0x8000) return rom.read(address);
if(address < 0xc000) return rom.read(0x10000 + ((u32)(bank & 15) << 14) + (address & 0x3fff));
return data;
}
auto write(n16 address, n8 data) -> void override {
if(address < 0x8000 || address >= 0xc000) return;
auto select = bank >> 5;
auto& target = (select & 1) ? vdp2 : vdp;
auto offset = (select & 1) ? ((select & 4) ? 0 : 0x4000) : ((select & 2) ? 0 : 0x4000);
target.vram[offset | (address & 0x3fff)] = data;
}
auto out(n16 address, n8 data) -> void override {
if((address & 0xff) != 0xf7) return;
bank = data;
vdp2.vramBank = data.bit(7);
vdp.vramBank = data.bit(6);
}
auto power() -> void override { bank = 0; }
auto serialize(serializer& s) -> void override { s(bank); }
};
+31
View File
@@ -19,7 +19,29 @@ auto Cartridge::connect() -> void {
information.region = pak->attribute("region");
information.board = pak->attribute("board");
cpu.encryption.reset();
if(Model::SystemE()) {
system.dips.load(system.node, pak, {"f2", "f3"});
auto rotation = pak->attribute("rotation").natural();
vdp.screen->setRotation(rotation ? 360 - rotation : 0); // MAME rotates clockwise; ares rotates counter-clockwise.
ppi.game = pak->attribute("name");
auto cpuName = pak->attribute("cpu");
if(cpuName == "Sega 315-5177") cpu.encryption = std::make_unique<S315_5177>();
if(cpuName == "MC-8123") {
if(auto fp = pak->read("cpu.key")) {
if(fp->size() == 0x2000) {
auto chip = std::make_unique<MC8123>();
// Fantasy Zone II encrypts only its fixed ROM.
chip->encryptedSize = pak->attribute("name") == "fantzn2" ? 0x8000 : 0xc000;
fp->read(chip->keyData, sizeof(chip->keyData));
cpu.encryption = std::move(chip);
}
}
}
}
if(information.board == "Sega") board = std::make_unique<Board::Sega>(*this);
if(information.board == "sega/segae") board = std::make_unique<Board::SystemE>(*this);
if(information.board == "Codemasters") board = std::make_unique<Board::Codemasters>(*this);
if(information.board == "Korea") board = std::make_unique<Board::Korea>(*this);
if(information.board == "Zemina") board = std::make_unique<Board::Zemina>(*this, 0); // Zemina made games
@@ -43,6 +65,11 @@ auto Cartridge::connect() -> void {
auto Cartridge::disconnect() -> void {
if(!node || !board) return;
cpu.encryption.reset();
if(Model::SystemE()) {
system.dips.unload();
ppi.game = {};
}
board->unload();
board->pak.reset();
board.reset();
@@ -67,4 +94,8 @@ auto Cartridge::write(n16 address, n8 data) -> void {
if(board) return board->write(address, data);
}
auto Cartridge::out(n16 address, n8 data) -> void {
if(board) board->out(address, data);
}
}
+1
View File
@@ -18,6 +18,7 @@ struct Cartridge {
auto read(n16 address, n8 data) -> n8;
auto write(n16 address, n8 data) -> void;
auto out(n16 address, n8 data) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
+4 -2
View File
@@ -8,7 +8,7 @@ CPU cpu;
#include "serialization.cpp"
auto CPU::load(Node::Object parent) -> void {
ram.allocate(8_KiB);
ram.allocate(Model::SystemE() ? 16_KiB : 8_KiB);
node = parent->append<Node::Object>("CPU");
@@ -56,7 +56,9 @@ auto CPU::setIRQ(bool value) -> void {
auto CPU::power() -> void {
Z80::bus = this;
Z80::power();
Thread::create(system.colorburst(), std::bind_front(&CPU::main, this));
auto frequency = system.colorburst();
if(Model::SystemE()) frequency *= 1.5;
Thread::create(frequency, std::bind_front(&CPU::main, this));
PC = 0x0000; //reset vector address
SP = 0xfffd; //initial stack pointer location
+4
View File
@@ -1,5 +1,6 @@
struct CPU : Z80, Z80::Bus, Thread {
Node::Object node;
std::unique_ptr<SegaZ80> encryption;
Memory::Writable<n8> ram; //8KB
struct Debugger {
@@ -38,10 +39,13 @@ struct CPU : Z80, Z80::Bus, Thread {
//memory.cpp
auto mdr() const -> n8;
auto read(n16 address) -> n8 override;
auto fetch(n16 address) -> n8 override;
auto write(n16 address, n8 data) -> void override;
auto in(n16 address) -> n8 override;
auto out(n16 address, n8 data) -> void override;
auto cheat(n16 address) -> maybe<n8>;
auto readMapped(n16 address) -> n8;
//serialization.cpp
auto serialize(serializer&) -> void;
+24 -4
View File
@@ -1,9 +1,26 @@
auto CPU::read(n16 address) -> n8 {
if(auto result = cheat(address)) return *result;
n8 data = readMapped(address);
return encryption ? encryption->decrypt(address, data, false) : data;
}
auto CPU::fetch(n16 address) -> n8 {
if(auto result = cheat(address)) return *result;
n8 data = readMapped(address);
return encryption ? encryption->decrypt(address, data, true) : data;
}
auto CPU::mdr() const -> n8 {
return (bus.mdr | bus.pullup) & ~bus.pulldown;
}
auto CPU::read(n16 address) -> n8 {
if(auto result = platform->cheat(address)) return *result;
auto CPU::cheat(n16 address) -> maybe<n8> {
if(auto result = platform->cheat(address)) return (n8)*result;
return nothing;
}
auto CPU::readMapped(n16 address) -> n8 {
if(Model::SystemE()) return SystemE::memory.read(address);
n8 data = mdr();
if(address >= 0xc000 && bus.ramEnable) data = ram.read(address);
if(Device::MasterSystem() && bus.biosEnable) data = bios.read(address, data);
@@ -14,6 +31,7 @@ auto CPU::read(n16 address) -> n8 {
}
auto CPU::write(n16 address, n8 data) -> void {
if(Model::SystemE()) return SystemE::memory.write(address, data);
bus.mdr = data;
if(address >= 0xc000 && bus.ramEnable) ram.write(address, data);
if(Device::MasterSystem() && bus.biosEnable) bios.write(address, data);
@@ -26,6 +44,7 @@ auto CPU::write(n16 address, n8 data) -> void {
//since I don't have explicit confirmation of this, I haven't implemented this yet.
auto CPU::in(n16 address) -> n8 {
if(Model::SystemE()) return SystemE::memory.in(address);
n8 data = mdr();
if(0);
@@ -81,7 +100,7 @@ auto CPU::in(n16 address) -> n8 {
data = mdr();
}
else if((address & 0xc1) == 0x01) {
else if((address & 0xc1) == 0x01 && Device::MasterSystem()) {
if(bus.ioEnable && Display::CRT() && !Region::NTSCJ()) {
data.bit(0) = controllerPort1.trDirection;
data.bit(1) = controllerPort1.thDirection;
@@ -174,6 +193,7 @@ auto CPU::in(n16 address) -> n8 {
}
auto CPU::out(n16 address, n8 data) -> void {
if(Model::SystemE()) return SystemE::memory.out(address, data);
if(0);
else if((address & 0xff) == 0x00 && Display::LCD()) {
@@ -219,7 +239,7 @@ auto CPU::out(n16 address, n8 data) -> void {
bus.expansionEnable = !data.bit(7);
}
else if((address & 0xc1) == 0x01) {
else if((address & 0xc1) == 0x01 && Device::MasterSystem()) {
auto thLevel1 = controllerPort1.thLevel;
auto thLevel2 = controllerPort2.thLevel;
+2
View File
@@ -4,6 +4,8 @@
#include <ms/vdp/vdp.cpp>
#include <ms/psg/psg.cpp>
#include <ms/opll/opll.cpp>
#include <ms/systeme/ppi.cpp>
#include <ms/systeme/memory.cpp>
#include <ms/system/system.cpp>
#include <ms/cartridge/cartridge.cpp>
#include <ms/controller/controller.cpp>
+7 -1
View File
@@ -4,7 +4,10 @@
#include <ares/ares.hpp>
#include <vector>
#include <component/processor/z80/z80.hpp>
#include <component/processor/z80/sega/s315-5177.hpp>
#include <component/processor/z80/sega/mc8123.hpp>
#include <component/io/i8255/i8255.hpp>
#include <component/arcade/dip-switches.hpp>
#include <component/audio/sn76489/sn76489.hpp>
#include <component/audio/ym2413/ym2413.hpp>
@@ -18,6 +21,7 @@ namespace ares::MasterSystem {
inline static auto MasterSystemI() -> bool;
inline static auto MasterSystemII() -> bool;
inline static auto GameGear() -> bool;
inline static auto SystemE() -> bool;
};
struct Device {
@@ -48,6 +52,8 @@ namespace ares::MasterSystem {
#include <ms/vdp/vdp.hpp>
#include <ms/psg/psg.hpp>
#include <ms/opll/opll.hpp>
#include <ms/systeme/ppi.hpp>
#include <ms/systeme/memory.hpp>
#include <ms/system/system.hpp>
#include <ms/cartridge/cartridge.hpp>
+64 -2
View File
@@ -1,7 +1,7 @@
auto System::Controls::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("Controls");
if(Device::MasterSystem()) {
if(Device::MasterSystem() && !MasterSystem::Model::SystemE()) {
if(MasterSystem::Region::NTSCJ() == 1) rapid = node->append<Node::Input::Button>("Rapid");
if(MasterSystem::Region::NTSCJ() == 0) reset = node->append<Node::Input::Button>("Reset");
pause = node->append<Node::Input::Button>("Pause");
@@ -19,7 +19,7 @@ auto System::Controls::load(Node::Object parent) -> void {
}
auto System::Controls::poll() -> void {
if(Device::MasterSystem()) {
if(Device::MasterSystem() && !MasterSystem::Model::SystemE()) {
if(MasterSystem::Region::NTSCJ() == 1) platform->input(rapid);
if(MasterSystem::Region::NTSCJ() == 0) platform->input(reset);
auto paused = pause->value();
@@ -54,3 +54,65 @@ auto System::Controls::poll() -> void {
}
}
}
auto System::ArcadeControls::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("Controls");
service = node->append<Node::Input::Button>("Service");
for(u32 player : range(2)) {
auto prefix = string{"Player ", player + 1, " "};
coin[player] = node->append<Node::Input::Button>(string{prefix, "Coin"});
start[player] = node->append<Node::Input::Button>(string{prefix, "Start"});
up[player] = node->append<Node::Input::Button>(string{prefix, "Up"});
down[player] = node->append<Node::Input::Button>(string{prefix, "Down"});
left[player] = node->append<Node::Input::Button>(string{prefix, "Left"});
right[player] = node->append<Node::Input::Button>(string{prefix, "Right"});
button1[player] = node->append<Node::Input::Button>(string{prefix, "Button 1"});
button2[player] = node->append<Node::Input::Button>(string{prefix, "Button 2"});
}
analogX = node->append<Node::Input::Axis>("Player 1 X-Axis");
analogY = node->append<Node::Input::Axis>("Player 1 Y-Axis");
accelerator = node->append<Node::Input::Axis>("Player 1 Accelerator");
}
auto System::ArcadeControls::poll() -> void {
platform->input(service);
for(u32 player : range(2)) {
platform->input(coin[player]);
platform->input(start[player]);
platform->input(up[player]);
platform->input(down[player]);
platform->input(left[player]);
platform->input(right[player]);
platform->input(button1[player]);
platform->input(button2[player]);
}
platform->input(analogX);
platform->input(analogY);
platform->input(accelerator);
if(ppi.game == "ridleofp" || ppi.game == "megrescu") {
ppi.x += (i32)analogX->value() >> 8;
ppi.y += (i32)analogY->value() >> 8;
}
}
auto System::ArcadeControls::coinPort() const -> n8 {
n8 data = 0xff;
data.bit(0) = !coin[0]->value();
data.bit(1) = !coin[1]->value();
data.bit(3) = !service->value();
if(cartridge.pak && cartridge.pak->attribute("name") == "hangonjr") data.bit(4) = !start[0]->value();
else data.bit(6) = !start[0]->value();
data.bit(7) = !start[1]->value();
return data;
}
auto System::ArcadeControls::playerPort(u32 player) const -> n8 {
n8 data = 0xff;
data.bit(0) = !up[player]->value();
data.bit(1) = !down[player]->value();
data.bit(2) = !left[player]->value();
data.bit(3) = !right[player]->value();
data.bit(4) = !button1[player]->value();
data.bit(5) = !button2[player]->value();
return data;
}
+7 -3
View File
@@ -1,4 +1,4 @@
static const string SerializerVersion = "v131";
static const string SerializerVersion = "v132";
auto System::serialize(bool synchronize) -> serializer {
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
@@ -43,9 +43,13 @@ auto System::serialize(serializer& s, bool synchronize) -> void {
s(cartridge);
s(cpu);
s(vdp);
if(MasterSystem::Model::SystemE()) s(vdp2);
s(psg);
s(controllerPort1);
s(controllerPort2);
if(MasterSystem::Model::SystemE()) s(ppi);
if(Device::MasterSystem() && !MasterSystem::Model::SystemE()) {
s(controllerPort1);
s(controllerPort2);
}
if(Device::MasterSystem()) {
if(MasterSystem::Region::NTSCJ()) {
if(MasterSystem::Model::MarkIII()) {
+33 -11
View File
@@ -13,6 +13,7 @@ auto enumerate() -> std::vector<string> {
"[Sega] Master System II (PAL)",
"[Sega] Game Gear (NTSC-J)",
"[Sega] Game Gear (NTSC-U)",
"[Sega] System E",
};
}
@@ -43,7 +44,8 @@ auto System::game() -> string {
auto System::run() -> void {
scheduler.enter();
controls.poll();
if(MasterSystem::Model::SystemE()) arcadeControls.poll();
else controls.poll();
}
auto System::load(Node::System& root, string name) -> bool {
@@ -66,6 +68,12 @@ auto System::load(Node::System& root, string name) -> bool {
information.name = "Game Gear";
information.model = Model::GameGear;
}
if(name == "[Sega] System E") {
information.name = "System E";
information.model = Model::SystemE;
information.region = Region::NTSCU;
information.colorburst = Constants::Colorburst::NTSC;
}
if(name.find("NTSC-J")) {
information.region = Region::NTSCJ;
information.colorburst = Constants::Colorburst::NTSC;
@@ -92,14 +100,21 @@ auto System::load(Node::System& root, string name) -> bool {
if(!node->setPak(pak = platform->pak(node))) return false;
scheduler.reset();
controls.load(node);
bios.load(node);
if(MasterSystem::Model::SystemE()) arcadeControls.load(node->append<Node::Object>("Arcade"));
else controls.load(node);
if(!MasterSystem::Model::SystemE()) bios.load(node);
cartridgeSlot.load(node);
cpu.load(node);
vdp.load(node);
if(MasterSystem::Model::SystemE()) {
vdp2.backLayer = true;
vdp2.load(node);
}
psg.load(node);
controllerPort1.load(node);
controllerPort2.load(node);
if(Device::MasterSystem() && !MasterSystem::Model::SystemE()) {
controllerPort1.load(node);
controllerPort2.load(node);
}
if(Device::MasterSystem()) {
if(MasterSystem::Region::NTSCJ()) {
if(MasterSystem::Model::MarkIII()) {
@@ -127,9 +142,12 @@ auto System::unload() -> void {
cartridgeSlot.unload();
cpu.unload();
vdp.unload();
if(MasterSystem::Model::SystemE()) vdp2.unload();
psg.unload();
controllerPort1.unload();
controllerPort2.unload();
if(Device::MasterSystem() && !MasterSystem::Model::SystemE()) {
controllerPort1.unload();
controllerPort2.unload();
}
if(Device::MasterSystem()) {
if(MasterSystem::Region::NTSCJ()) {
if(MasterSystem::Model::MarkIII()) {
@@ -143,7 +161,7 @@ auto System::unload() -> void {
}
}
}
bios.unload();
if(!MasterSystem::Model::SystemE()) bios.unload();
node.reset();
}
@@ -154,13 +172,17 @@ auto System::power(bool reset) -> void {
information.ms = cartridge.pak->attribute("ms").boolean();
}
bios.power();
if(!MasterSystem::Model::SystemE()) bios.power();
cartridge.power();
cpu.power();
vdp.power();
if(MasterSystem::Model::SystemE()) vdp2.power();
psg.power();
controllerPort1.power();
controllerPort2.power();
if(MasterSystem::Model::SystemE()) ppi.power();
if(Device::MasterSystem() && !MasterSystem::Model::SystemE()) {
controllerPort1.power();
controllerPort2.power();
}
if(Device::MasterSystem()) {
if(MasterSystem::Region::NTSCJ()) {
if(MasterSystem::Model::MarkIII()) {
+14 -1
View File
@@ -17,6 +17,7 @@ struct BIOS {
struct System {
Node::System node;
VFS::Pak pak;
Arcade::Dip dips;
struct Controls {
Node::Object node;
@@ -46,7 +47,18 @@ struct System {
n1 rightLatch;
} controls;
enum class Model : u32 { MarkIII, MasterSystemI, MasterSystemII, GameGear };
struct ArcadeControls {
Node::Object node;
Node::Input::Button coin[2], start[2], service;
Node::Input::Button up[2], down[2], left[2], right[2], button1[2], button2[2];
Node::Input::Axis analogX, analogY, accelerator;
auto load(Node::Object) -> void;
auto poll() -> void;
auto coinPort() const -> n8;
auto playerPort(u32 player) const -> n8;
} arcadeControls;
enum class Model : u32 { MarkIII, MasterSystemI, MasterSystemII, GameGear, SystemE };
enum class Region : u32 { NTSCJ, NTSCU, PAL };
auto name() const -> string { return information.name; }
@@ -88,6 +100,7 @@ auto Model::MarkIII() -> bool { return system.model() == System::Model::MarkIII;
auto Model::MasterSystemI() -> bool { return system.model() == System::Model::MasterSystemI; }
auto Model::MasterSystemII() -> bool { return system.model() == System::Model::MasterSystemII; }
auto Model::GameGear() -> bool { return system.model() == System::Model::GameGear; }
auto Model::SystemE() -> bool { return system.model() == System::Model::SystemE; }
auto Device::MasterSystem() -> bool { return system.model() != System::Model::GameGear; }
auto Device::GameGear() -> bool { return system.model() == System::Model::GameGear; }
+56
View File
@@ -0,0 +1,56 @@
#include <ms/ms.hpp>
namespace ares::MasterSystem::SystemE {
Memory memory;
auto Memory::read(n16 address) -> n8 {
if(address < 0xc000) return cartridge.read(address, 0xff);
return cpu.ram.read(address);
}
auto Memory::write(n16 address, n8 data) -> void {
if(address >= 0xc000) cpu.ram.write(address, data);
else if(address >= 0x8000) cartridge.write(address, data);
}
auto Memory::in(n16 address) -> n8 {
n8 data = 0xff;
switch(address & 0xff) {
case 0x7e: data = vdp2.vcounterQuery(); break;
case 0x7f: data = vdp.hcounterQuery(); break;
case 0xba: data = vdp2.data(); break;
case 0xbb: data = vdp2.status(); break;
case 0xbe: data = vdp.data(); break;
case 0xbf: data = vdp.status(); break;
case 0xe0: data = system.arcadeControls.coinPort(); break;
case 0xe1: data = system.arcadeControls.playerPort(0); break;
case 0xe2: data = system.arcadeControls.playerPort(1); break;
case 0xf2: data = system.dips.read("f2"); break;
case 0xf3: data = system.dips.read("f3"); break;
case 0xf8: case 0xf9: case 0xfa: case 0xfb:
data = ppi.read(address & 3);
break;
}
cpu.debugger.in(address, data);
return data;
}
auto Memory::out(n16 address, n8 data) -> void {
switch(address & 0xff) {
case 0x7b: case 0x7e: case 0x7f: psg.write(data); break;
case 0xba: vdp2.data(data); break;
case 0xbb: vdp2.control(data); break;
case 0xbe: vdp.data(data); break;
case 0xbf: vdp.control(data); break;
case 0xf7:
cartridge.out(address, data);
break;
case 0xf8: case 0xf9: case 0xfa: case 0xfb:
ppi.write(address & 3, data);
break;
}
cpu.debugger.out(address, data);
}
}
+12
View File
@@ -0,0 +1,12 @@
namespace SystemE {
struct Memory {
auto read(n16 address) -> n8;
auto write(n16 address, n8 data) -> void;
auto in(n16 address) -> n8;
auto out(n16 address, n8 data) -> void;
};
extern Memory memory;
}
+60
View File
@@ -0,0 +1,60 @@
#include <ms/ms.hpp>
namespace ares::MasterSystem {
PPI ppi;
auto PPI::power() -> void {
I8255::power();
control = 0xff;
x = y = startX = startY = latchX = latchY = 0;
switches = 0;
}
auto PPI::readPortA() -> n8 {
if(game == "hangonjr") {
if(control.bit(0)) {
return std::clamp((i32)system.arcadeControls.accelerator->value() >> 7, 0, 255);
}
auto angle = ((i32)system.arcadeControls.analogX->value() + 32768) >> 8;
if(angle < 0x20) angle = 0x20;
if(angle > 0xe0) angle = 0xe0;
return angle;
}
if(game == "ridleofp" || game == "megrescu") {
if(control.bit(4)) return 0xff;
u16 value = control.bit(3) ? latchY : latchX;
value |= (u16)switches << 12;
return control.bit(2) ? value >> 8 : value & 0xff;
}
return 0xff;
}
auto PPI::readPortC() -> n8 {
if(game == "hangonjr") return 0xef;
return 0xff;
}
auto PPI::writePortC(n8 data) -> void {
if((game == "ridleofp" || game == "megrescu") && control.bit(4) && !data.bit(4)) {
latchX = (x - startX) & 0x0fff;
latchY = (y - startY) & 0x0fff;
switches = 0;
if(system.arcadeControls.button1[0]->value()) switches |= 4;
if(system.arcadeControls.button2[0]->value()) switches |= 1;
if(switches) switches |= 8;
}
if(!control.bit(1) && data.bit(1)) startX = x;
if(!control.bit(0) && data.bit(0)) startY = y;
control = data;
}
auto PPI::serialize(serializer& s) -> void {
I8255::serialize(s);
s(control);
s(x); s(y); s(startX); s(startY); s(latchX); s(latchY);
s(switches);
}
}
+14
View File
@@ -0,0 +1,14 @@
struct PPI : I8255 {
string game;
n8 control;
u16 x, y, startX, startY, latchX, latchY;
n4 switches;
auto power() -> void;
auto readPortA() -> n8 override;
auto readPortC() -> n8 override;
auto writePortC(n8 data) -> void override;
auto serialize(serializer&) -> void;
};
extern PPI ppi;
+12 -12
View File
@@ -30,7 +30,7 @@ auto VDP::Background::graphics1(n8 hoffset, n9 voffset) -> void {
nameTableAddress.bit( 0, 4) = hoffset.bit(3,7);
nameTableAddress.bit( 5, 9) = voffset.bit(3,7);
nameTableAddress.bit(10,13) = io.nameTableAddress;
n8 pattern = self.vram[nameTableAddress];
n8 pattern = self.readVRAM(nameTableAddress);
n14 patternAddress;
patternAddress.bit( 0, 2) = voffset.bit(0,2);
@@ -41,9 +41,9 @@ auto VDP::Background::graphics1(n8 hoffset, n9 voffset) -> void {
colorAddress.bit(0, 4) = pattern.bit(3,7);
colorAddress.bit(6,13) = io.colorTableAddress;
n8 color = self.vram[colorAddress];
n8 color = self.readVRAM(colorAddress);
n3 index = hoffset ^ 7;
if(!self.vram[patternAddress].bit(index)) {
if(!self.readVRAM(patternAddress).bit(index)) {
output.color = color.bit(0,3);
} else {
output.color = color.bit(4,7);
@@ -55,7 +55,7 @@ auto VDP::Background::graphics2(n8 hoffset, n9 voffset) -> void {
nameTableAddress.bit( 0, 4) = hoffset.bit(3,7);
nameTableAddress.bit( 5, 9) = voffset.bit(3,7);
nameTableAddress.bit(10,13) = io.nameTableAddress;
n8 pattern = self.vram[nameTableAddress];
n8 pattern = self.readVRAM(nameTableAddress);
n14 patternAddress;
patternAddress.bit(0, 2) = voffset.bit(0,2);
@@ -67,9 +67,9 @@ auto VDP::Background::graphics2(n8 hoffset, n9 voffset) -> void {
colorAddress.bit(13) = io.colorTableAddress.bit(7);
n8 colorMask = io.colorTableAddress.bit(0,6) << 1 | 1;
n8 color = self.vram[colorAddress];
n8 color = self.readVRAM(colorAddress);
n3 index = hoffset ^ 7;
if(!self.vram[patternAddress].bit(index)) {
if(!self.readVRAM(patternAddress).bit(index)) {
output.color = color.bit(0,3);
} else {
output.color = color.bit(4,7);
@@ -100,8 +100,8 @@ auto VDP::Background::graphics3(n8 hoffset, n9 voffset, u32 vlines) -> void {
}
n16 pattern;
pattern.byte(0) = self.vram[nameTableAddress | 0];
pattern.byte(1) = self.vram[nameTableAddress | 1];
pattern.byte(0) = self.readVRAM(nameTableAddress | 0);
pattern.byte(1) = self.readVRAM(nameTableAddress | 1);
if(pattern.bit( 9)) hoffset ^= 7; //hflip
if(pattern.bit(10)) voffset ^= 7; //vflip
@@ -113,10 +113,10 @@ auto VDP::Background::graphics3(n8 hoffset, n9 voffset, u32 vlines) -> void {
patternAddress.bit(5,13) = pattern.bit(0,8);
n3 index = hoffset ^ 7;
output.color.bit(0) = self.vram[patternAddress | 0].bit(index);
output.color.bit(1) = self.vram[patternAddress | 1].bit(index);
output.color.bit(2) = self.vram[patternAddress | 2].bit(index);
output.color.bit(3) = self.vram[patternAddress | 3].bit(index);
output.color.bit(0) = self.readVRAM(patternAddress | 0).bit(index);
output.color.bit(1) = self.readVRAM(patternAddress | 1).bit(index);
output.color.bit(2) = self.readVRAM(patternAddress | 2).bit(index);
output.color.bit(3) = self.readVRAM(patternAddress | 3).bit(index);
if(output.color == 0) output.priority = 0;
}
+6 -1
View File
@@ -1,7 +1,7 @@
auto VDP::DAC::setup(n9 y) -> void {
auto offset = Region::PAL() ? 48 : 27;
y = (y + offset) % vdp.screenHeight();
output = self.screen->pixels().data() + y * 284;
output = self.backLayer ? self.backPixels : self.screen->pixels().data() + y * 284;
for (u32 x : range(284)) output[x] = palette(16 | io.backdropColor);
}
@@ -18,6 +18,11 @@ auto VDP::DAC::run(n8 x, n8 y) -> void {
}
}
if(Model::SystemE() && !self.backLayer) {
bool opaque = self.displayEnable() && (!io.leftClip || x >= 8)
&& (self.sprite.output.color || self.background.output.color);
if(!opaque) color = vdp2.backPixels[(x + 13) % 284];
}
output[(x + 13) % 284] = color;
}
+7 -7
View File
@@ -1,20 +1,20 @@
auto VDP::Debugger::load(Node::Object parent) -> void {
memory.vram = parent->append<Node::Debugger::Memory>("VDP VRAM");
memory.vram->setSize(vdp.vram.size());
memory.vram->setSize(self.vram.size());
memory.vram->setRead([&](u32 address) -> u8 {
return vdp.vram[address];
return self.vram[address];
});
memory.vram->setWrite([&](u32 address, u8 data) -> void {
vdp.vram[address] = data;
self.vram[address] = data;
});
memory.cram = parent->append<Node::Debugger::Memory>("VDP CRAM");
memory.cram->setSize(vdp.cram.size());
memory.cram->setSize(self.cram.size());
memory.cram->setRead([&](u32 address) -> u8 {
return vdp.cram[address];
return self.cram[address];
});
memory.cram->setWrite([&](u32 address, u8 data) -> void {
vdp.cram[address] = data;
self.cram[address] = data;
});
tracer.io = parent->append<Node::Debugger::Tracer::Notification>("I/O", "VDP");
@@ -42,7 +42,7 @@ auto VDP::Debugger::io(n4 register, n8 data) -> void {
};
tracer.io->notify({
"$", hex(register, 2L), " = #$", hex(data, 2L),
" @ ", pad(vdp.vcounter(), 3L), ",", pad(vdp.hcounter(), 3L),
" @ ", pad(self.vcounter(), 3L), ",", pad(self.hcounter(), 3L),
" ", name[register]
});
}
+3 -3
View File
@@ -36,7 +36,7 @@ auto VDP::data() -> n8 {
latch.control = 0;
auto data = latch.vram;
latch.vram = vram[io.address++];
latch.vram = readVRAM(io.address++);
return data;
}
@@ -66,7 +66,7 @@ auto VDP::data(n8 data) -> void {
latch.vram = data;
if(io.code <= 2) {
vram[io.address] = data;
writeVRAM(io.address, data);
} else {
if(Mode::MasterSystem()) {
//writes immediate store 6-bits into CRAM
@@ -96,7 +96,7 @@ auto VDP::control(n8 data) -> void {
io.code.bit(0,1) = data.bit(6,7);
if(io.code == 0) {
latch.vram = vram[io.address++];
latch.vram = readVRAM(io.address++);
}
if(io.code == 2) {
+1
View File
@@ -1,4 +1,5 @@
auto VDP::IRQ::poll() -> void {
if(self.backLayer) return;
if(line.pending && line.enable) return cpu.setIRQ(1);
if(frame.pending && frame.enable) return cpu.setIRQ(1);
return cpu.setIRQ(0);
+1
View File
@@ -1,6 +1,7 @@
auto VDP::serialize(serializer& s) -> void {
Thread::serialize(s);
s(vram);
s(vramBank);
s(cram);
s(background);
s(sprite);
+12 -12
View File
@@ -7,13 +7,13 @@ auto VDP::Sprite::setup(n9 voffset) -> void {
n14 attributeAddress;
attributeAddress.bit(7,13) = io.attributeTableAddress;
for(u32 index : range(32)) {
i9 y = self.vram[attributeAddress++];
i9 y = self.readVRAM(attributeAddress++);
if(y == 0xd0) break;
if(y >= 0xe0) y -= 0x100;
i9 x = self.vram[attributeAddress++];
n8 pattern = self.vram[attributeAddress++];
n8 extra = self.vram[attributeAddress++];
i9 x = self.readVRAM(attributeAddress++);
n8 pattern = self.readVRAM(attributeAddress++);
n8 extra = self.readVRAM(attributeAddress++);
if(extra.bit(7)) x -= 32;
y += 1;
@@ -35,12 +35,12 @@ auto VDP::Sprite::setup(n9 voffset) -> void {
attributeAddress.bit(8,13) = io.attributeTableAddress.bit(1,6);
for(u32 index : range(64)) {
i9 y = self.vram[attributeAddress + index];
i9 y = self.readVRAM(attributeAddress + index);
if(self.vlines() == 192 && y == 0xd0) break;
if(y >= 0xf0) y -= 0x100;
i9 x = self.vram[attributeAddress + 0x80 + (index << 1)];
n8 pattern = self.vram[attributeAddress + 0x81 + (index << 1)];
i9 x = self.readVRAM(attributeAddress + 0x80 + (index << 1));
n8 pattern = self.readVRAM(attributeAddress + 0x81 + (index << 1));
if(io.shift) x -= 8;
y += 1;
@@ -102,7 +102,7 @@ auto VDP::Sprite::graphics2(n8 hoffset, n9 voffset) -> void {
address.bit(11,13) = io.patternTableAddress;
n3 index = x ^ 7;
if(self.vram[address].bit(index)) {
if(self.readVRAM(address).bit(index)) {
if(output.color && self.displayEnable()) { io.collision = 1; break; }
output.color = o.color;
}
@@ -126,10 +126,10 @@ auto VDP::Sprite::graphics3(n8 hoffset, n9 voffset, u32 vlines) -> void {
n3 index = x ^ 7;
n4 color;
color.bit(0) = self.vram[address | 0].bit(index);
color.bit(1) = self.vram[address | 1].bit(index);
color.bit(2) = self.vram[address | 2].bit(index);
color.bit(3) = self.vram[address | 3].bit(index);
color.bit(0) = self.readVRAM(address | 0).bit(index);
color.bit(1) = self.readVRAM(address | 1).bit(index);
color.bit(2) = self.readVRAM(address | 2).bit(index);
color.bit(3) = self.readVRAM(address | 3).bit(index);
if(color == 0) continue;
if(output.color && self.displayEnable()) { io.collision = 1; break; }
+45 -9
View File
@@ -3,6 +3,7 @@
namespace ares::MasterSystem {
VDP vdp;
VDP vdp2;
#include "io.cpp"
#include "irq.cpp"
#include "background.cpp"
@@ -13,17 +14,25 @@ VDP vdp;
#include "serialization.cpp"
auto VDP::load(Node::Object parent) -> void {
vram.allocate(16_KiB);
vram.allocate(Model::SystemE() ? 32_KiB : 16_KiB);
cram.allocate(32);
node = parent->append<Node::Object>("VDP");
node = parent->append<Node::Object>(backLayer ? "Back VDP" : "VDP");
if(backLayer) {
revision = node->append<Node::Setting::Natural>("Revision", 1);
revision->setAllowedValues({1});
debugger.load(node);
return;
}
screen = node->append<Node::Video::Screen>("Screen", 284, screenHeight());
u32 defaultRevision = 2;
if((Model::MarkIII() || Model::MasterSystemI()) && Region::NTSCJ()) defaultRevision = 1;
if(((Model::MarkIII() || Model::MasterSystemI()) && Region::NTSCJ()) || Model::SystemE()) defaultRevision = 1;
revision = node->append<Node::Setting::Natural>("Revision", defaultRevision);
revision->setAllowedValues({1, 2});
if(Model::SystemE()) revision->setAllowedValues({1});
else revision->setAllowedValues({1, 2});
screen->refreshRateHint(system.colorburst() * 15, 3420, Region::PAL() ? 313 : 262);
if(Display::CRT()) {
@@ -55,11 +64,12 @@ auto VDP::load(Node::Object parent) -> void {
}
auto VDP::unload() -> void {
debugger = {};
debugger.memory = {};
debugger.tracer = {};
interframeBlending.reset();
revision.reset();
screen->quit();
node->remove(screen);
if(screen) screen->quit();
if(screen) node->remove(screen);
screen.reset();
node.reset();
vram.reset();
@@ -84,11 +94,34 @@ auto VDP::main() -> void {
//684 clocks/scanline
dac.setup(io.vcounter);
if(Model::SystemE()) {
vdp2.io.vcounter = io.vcounter;
vdp2.io.hcounter = io.hcounter;
if(vdp2.io.vcounter <= vdp2.vlines()) {
if(vdp2.irq.line.counter-- == 0) {
vdp2.irq.line.counter = vdp2.irq.line.coincidence;
vdp2.irq.line.pending = 1;
}
} else {
vdp2.irq.line.counter = vdp2.irq.line.coincidence;
}
if(vdp2.io.vcounter == vdp2.vlines() + 1) vdp2.irq.frame.pending = 1;
vdp2.dac.setup(io.vcounter);
}
if(io.vcounter < vlines()) {
u8 y = io.vcounter;
background.setup(y);
sprite.setup(y);
if(Model::SystemE()) {
vdp2.background.setup(y);
vdp2.sprite.setup(y);
}
for(u8 x : range(256)) {
if(Model::SystemE()) {
vdp2.background.run(x, y);
vdp2.sprite.run(x, y);
vdp2.dac.run(x, y);
}
background.run(x, y);
sprite.run(x, y);
dac.run(x, y);
@@ -164,8 +197,10 @@ auto VDP::vblank() -> bool {
}
auto VDP::power() -> void {
Thread::create(system.colorburst() * 15.0 / 5.0, std::bind_front(&VDP::main, this));
screen->power();
if(!backLayer) {
Thread::create(system.colorburst() * 15.0 / 5.0, std::bind_front(&VDP::main, this));
screen->power();
}
for(auto& byte : vram) byte = 0x00;
for(auto& byte : cram) byte = 0x00;
@@ -176,6 +211,7 @@ auto VDP::power() -> void {
irq.power();
io = {};
latch = {};
vramBank = 0;
}
}
+9 -2
View File
@@ -8,10 +8,16 @@ struct VDP : Thread {
Node::Setting::Boolean colorEmulation;
Node::Setting::Natural revision;
Node::Setting::Boolean interframeBlending; //Game Gear only
Memory::Writable<n8 > vram; //16KB
Memory::Writable<n8 > vram; //16KB; 32KB on System E
n1 vramBank;
bool backLayer = false;
u32 backPixels[284] = {};
auto readVRAM(n14 address) const -> n8 { return vram[(Model::SystemE() ? (u32)vramBank << 14 : 0) | address]; }
auto writeVRAM(n14 address, n8 data) -> void { vram[(Model::SystemE() ? (u32)vramBank << 14 : 0) | address] = data; }
Memory::Writable<n12> cram; //Master System = 6-bit; Game Gear = 12-bit
struct Debugger {
VDP& self;
//debugger.cpp
auto load(Node::Object) -> void;
auto io(n4 register, n8 data) -> void;
@@ -24,7 +30,7 @@ struct VDP : Thread {
struct Tracer {
Node::Debugger::Tracer::Notification io;
} tracer;
} debugger;
} debugger{*this};
auto displayEnable() const -> n1 { return io.displayEnable; }
auto videoMode() const -> n4 { return io.videoMode; }
@@ -207,3 +213,4 @@ private:
};
extern VDP vdp;
extern VDP vdp2;
+1 -7
View File
@@ -1,4 +1,4 @@
ares_components(sm5k)
ares_components(sm5k arcadedip)
ares_add_sources(
CORE #
@@ -67,12 +67,6 @@ ares_add_sources(
aleck64/io.cpp
aleck64/vdp.cpp
aleck64/game-config/11beat.hpp
aleck64/game-config/doncdoon.hpp
aleck64/game-config/kurufev.hpp
aleck64/game-config/mayjin3.hpp
aleck64/game-config/starsldr.hpp
aleck64/game-config/twrshaft.hpp
aleck64/game-config/vivdolls.hpp
aleck64/game-config/hipai.hpp
aleck64/game-config/hipai2.hpp
aleck64/game-config/srmvs.hpp
+2 -16
View File
@@ -10,12 +10,6 @@ namespace ares::Nintendo64 {
#include "serialization.cpp"
#include "game-config/11beat.hpp"
#include "game-config/starsldr.hpp"
#include "game-config/doncdoon.hpp"
#include "game-config/kurufev.hpp"
#include "game-config/mayjin3.hpp"
#include "game-config/vivdolls.hpp"
#include "game-config/twrshaft.hpp"
#include "game-config/hipai.hpp"
#include "game-config/hipai2.hpp"
#include "game-config/srmvs.hpp"
@@ -34,6 +28,7 @@ namespace ares::Nintendo64 {
}
auto Aleck64::unload() -> void {
dips.unload();
sdram.reset();
debugger.unload();
}
@@ -44,19 +39,10 @@ namespace ares::Nintendo64 {
auto Aleck64::power(bool reset) -> void {
if(!reset) {
dipSwitch[0] = 0xffff'ffff;
dipSwitch[1] = 0xffff'ffff;
//NOTE: We can't do this at 'load' time because cartridges are not attached yet...
auto name = cartridge.pak->attribute("name");
if(name == "11beat" ) gameConfig = std::make_shared<_11beat>();
if(name == "starsldr") gameConfig = std::make_shared<starsldr>();
if(name == "doncdoon") gameConfig = std::make_shared<doncdoon>();
if(name == "kurufev" ) gameConfig = std::make_shared<kurufev>();
if(name == "mayjin3" ) gameConfig = std::make_shared<mayjin3>();
if(name == "vivdolls") gameConfig = std::make_shared<vivdolls>();
if(name == "twrshaft") gameConfig = std::make_shared<twrshaft>();
if(name == "hipai" ) gameConfig = std::make_shared<hipai>();
if(name == "hipai2" ) gameConfig = std::make_shared<hipai2>();
if(name == "srmvs" ) gameConfig = std::make_shared<srmvs>();
@@ -64,7 +50,7 @@ namespace ares::Nintendo64 {
if(name == "mtetrisc") gameConfig = std::make_shared<mtetrisc>();
if(!gameConfig) gameConfig = std::make_shared<GameConfig>(); //Fallback to default implementation
gameConfig->dipSwitches(dipSwitchNode);
dips.load(dipSwitchNode, cartridge.pak, {"IN0"}, 0, true);
sdram.fill();
vram.fill();
+1 -3
View File
@@ -1,5 +1,6 @@
struct Aleck64 {
Node::Object dipSwitchNode;
Arcade::Dip dips;
enum BoardType { E90, E92 };
struct Writable : public Memory::Writable {
@@ -235,13 +236,10 @@ struct Aleck64 {
} io;
} vdp;
n8 dipSwitch[2];
struct GameConfig {
virtual ~GameConfig() = default;
virtual auto type() -> BoardType { return BoardType::E92; }
virtual auto dpadDisabled() -> n1 { return 0; }
virtual auto dipSwitches(Node::Object parent) -> void {};
virtual auto readExpansionPort() -> u32 { return 0xffff'ffff; }
virtual auto writeExpansionPort(n32 data) -> void {};
};
-8
View File
@@ -1,11 +1,3 @@
struct _11beat : Aleck64::GameConfig {
auto dpadDisabled() -> n1 { return true; }
auto dipSwitches(Node::Object parent) -> void {
Node::Setting::Boolean testMode = parent->append<Node::Setting::Boolean>("Test Mode", false, [&](auto value) {
aleck64.dipSwitch[1].bit(7) = !value;
});
testMode->setDynamic(true);
testMode->modify(testMode->value());
}
};
@@ -1,9 +0,0 @@
struct doncdoon : Aleck64::GameConfig {
auto dipSwitches(Node::Object parent) -> void {
Node::Setting::Boolean testMode = parent->append<Node::Setting::Boolean>("Test Mode", false, [&](auto value) {
aleck64.dipSwitch[0].bit(0) = !value;
});
testMode->setDynamic(true);
testMode->modify(testMode->value());
}
};
-63
View File
@@ -1,69 +1,6 @@
struct hipai : Aleck64::GameConfig {
u8 mahjongRow = 0;
auto dipSwitches(Node::Object parent) -> void {
Node::Setting::Boolean testMode = parent->append<Node::Setting::Boolean>("Test Mode", false, [&](auto value) {
aleck64.dipSwitch[1].bit(7) = !value;
});
testMode->setDynamic(true);
testMode->modify(testMode->value());
Node::Setting::Boolean freePlay = parent->append<Node::Setting::Boolean>("Free Play", false, [&](auto value) {
aleck64.dipSwitch[0].bit(7) = !value;
});
freePlay->setDynamic(true);
freePlay->modify(freePlay->value());
Node::Setting::String coinage = parent->append<Node::Setting::String>("Coinage", "1 Coin 1 Credit", [&](auto value) {
if(value == "1 Coin 1 Credit" ) aleck64.dipSwitch[0].bit(0,2) = 7;
if(value == "1 Coin 2 Credits" ) aleck64.dipSwitch[0].bit(0,2) = 3;
if(value == "1 Coin 3 Credits" ) aleck64.dipSwitch[0].bit(0,2) = 5;
if(value == "1 Coin 4 Credits" ) aleck64.dipSwitch[0].bit(0,2) = 4;
if(value == "1 Coins 2 Credits") aleck64.dipSwitch[0].bit(0,2) = 6;
if(value == "3 Coins 1 Credit" ) aleck64.dipSwitch[0].bit(0,2) = 2;
if(value == "4 Coins 1 Credit" ) aleck64.dipSwitch[0].bit(0,2) = 1;
if(value == "5 Coins 1 Credit" ) aleck64.dipSwitch[0].bit(0,2) = 0;
});
coinage->setAllowedValues({"1 Coin 1 Credit" , "1 Coin 2 Credits" , "1 Coin 3 Credits" , "1 Coin 4 Credits",
"1 Coins 2 Credit", "3 Coins 1 Credit", "4 Coins 1 Credit", "5 Coins 1 Credit",
});
coinage->setDynamic(true);
coinage->modify(coinage->value());
Node::Setting::Boolean demoSounds = parent->append<Node::Setting::Boolean>("Demo Sounds", true, [&](auto value) {
aleck64.dipSwitch[1].bit(5) = !value;
});
demoSounds->setDynamic(true);
demoSounds->modify(demoSounds->value());
Node::Setting::Boolean allowContinue = parent->append<Node::Setting::Boolean>("Allow Continue", false, [&](auto value) {
aleck64.dipSwitch[1].bit(4) = value;
});
allowContinue->setDynamic(true);
allowContinue->modify(allowContinue->value());
Node::Setting::Boolean kuitan = parent->append<Node::Setting::Boolean>("Kuitan", false, [&](auto value) {
aleck64.dipSwitch[1].bit(3) = value;
});
kuitan->setDynamic(true);
kuitan->modify(kuitan->value());
Node::Setting::String difficulty = parent->append<Node::Setting::String>("Difficulty", "Normal", [&](auto value) {
if(value == "Normal" ) aleck64.dipSwitch[1].bit(0,2) = 7;
if(value == "Easiest" ) aleck64.dipSwitch[1].bit(0,2) = 6;
if(value == "Very Easy") aleck64.dipSwitch[1].bit(0,2) = 5;
if(value == "Easy" ) aleck64.dipSwitch[1].bit(0,2) = 4;
if(value == "Normal+" ) aleck64.dipSwitch[1].bit(0,2) = 3;
if(value == "Hard" ) aleck64.dipSwitch[1].bit(0,2) = 2;
if(value == "Very Hard") aleck64.dipSwitch[1].bit(0,2) = 1;
if(value == "Most Hard") aleck64.dipSwitch[1].bit(0,2) = 0;
});
difficulty->setAllowedValues({"Normal", "Easiest", "Very Easy", "Easy", "Normal+", "Hard", "Very Hard", "Most Hard"});
difficulty->setDynamic(true);
difficulty->modify(difficulty->value());
}
auto readExpansionPort() -> u32 {
n32 value = 0xffff'ffff;
value.bit(16, 23) = aleck64.mahjong.read(mahjongRow);
-68
View File
@@ -1,74 +1,6 @@
struct hipai2 : Aleck64::GameConfig {
u8 mahjongRow = 0;
auto dipSwitches(Node::Object parent) -> void {
Node::Setting::Boolean testMode = parent->append<Node::Setting::Boolean>("Test Mode", false, [&](auto value) {
aleck64.dipSwitch[1].bit(7) = !value;
});
testMode->setDynamic(true);
testMode->modify(testMode->value());
Node::Setting::Boolean freePlay = parent->append<Node::Setting::Boolean>("Free Play", false, [&](auto value) {
aleck64.dipSwitch[0].bit(7) = !value;
});
freePlay->setDynamic(true);
freePlay->modify(freePlay->value());
Node::Setting::Boolean backupSettings = parent->append<Node::Setting::Boolean>("Backup Settings", false, [&](auto value) {
aleck64.dipSwitch[0].bit(6) = !value;
});
backupSettings->setDynamic(true);
backupSettings->modify(backupSettings->value());
Node::Setting::String coinage = parent->append<Node::Setting::String>("Coinage", "1 Coin 1 Credit", [&](auto value) {
if(value == "1 Coin 1 Credit" ) aleck64.dipSwitch[0].bit(0,2) = 7;
if(value == "1 Coin 2 Credits" ) aleck64.dipSwitch[0].bit(0,2) = 3;
if(value == "1 Coin 3 Credits" ) aleck64.dipSwitch[0].bit(0,2) = 5;
if(value == "1 Coin 4 Credits" ) aleck64.dipSwitch[0].bit(0,2) = 4;
if(value == "1 Coins 2 Credits") aleck64.dipSwitch[0].bit(0,2) = 6;
if(value == "3 Coins 1 Credit" ) aleck64.dipSwitch[0].bit(0,2) = 2;
if(value == "4 Coins 1 Credit" ) aleck64.dipSwitch[0].bit(0,2) = 1;
if(value == "5 Coins 1 Credit" ) aleck64.dipSwitch[0].bit(0,2) = 0;
});
coinage->setAllowedValues({"1 Coin 1 Credit" , "1 Coin 2 Credits" , "1 Coin 3 Credits" , "1 Coin 4 Credits",
"1 Coins 2 Credit", "3 Coins 1 Credit", "4 Coins 1 Credit", "5 Coins 1 Credit",
});
coinage->setDynamic(true);
coinage->modify(coinage->value());
Node::Setting::Boolean demoSounds = parent->append<Node::Setting::Boolean>("Demo Sounds", true, [&](auto value) {
aleck64.dipSwitch[1].bit(5) = !value;
});
demoSounds->setDynamic(true);
demoSounds->modify(demoSounds->value());
Node::Setting::Boolean allowContinue = parent->append<Node::Setting::Boolean>("Allow Continue", false, [&](auto value) {
aleck64.dipSwitch[1].bit(4) = value;
});
allowContinue->setDynamic(true);
allowContinue->modify(allowContinue->value());
Node::Setting::Boolean kuitan = parent->append<Node::Setting::Boolean>("Kuitan", false, [&](auto value) {
aleck64.dipSwitch[1].bit(3) = value;
});
kuitan->setDynamic(true);
kuitan->modify(kuitan->value());
Node::Setting::String difficulty = parent->append<Node::Setting::String>("Difficulty", "Normal", [&](auto value) {
if(value == "Normal" ) aleck64.dipSwitch[1].bit(0,2) = 7;
if(value == "Easiest" ) aleck64.dipSwitch[1].bit(0,2) = 6;
if(value == "Very Easy") aleck64.dipSwitch[1].bit(0,2) = 5;
if(value == "Easy" ) aleck64.dipSwitch[1].bit(0,2) = 4;
if(value == "Normal+" ) aleck64.dipSwitch[1].bit(0,2) = 3;
if(value == "Hard" ) aleck64.dipSwitch[1].bit(0,2) = 2;
if(value == "Very Hard") aleck64.dipSwitch[1].bit(0,2) = 1;
if(value == "Most Hard") aleck64.dipSwitch[1].bit(0,2) = 0;
});
difficulty->setAllowedValues({"Normal", "Easiest", "Very Easy", "Easy", "Normal+", "Hard", "Very Hard", "Most Hard"});
difficulty->setDynamic(true);
difficulty->modify(difficulty->value());
}
auto readExpansionPort() -> u32 {
n32 value = 0xffff'ffff;
value.bit(16, 23) = aleck64.mahjong.read(mahjongRow);
-9
View File
@@ -1,9 +0,0 @@
struct kurufev : Aleck64::GameConfig {
auto dipSwitches(Node::Object parent) -> void {
Node::Setting::Boolean testMode = parent->append<Node::Setting::Boolean>("Test Mode", false, [&](auto value) {
aleck64.dipSwitch[0].bit(0) = !value;
});
testMode->setDynamic(true);
testMode->modify(testMode->value());
}
};
-9
View File
@@ -1,9 +0,0 @@
struct mayjin3 : Aleck64::GameConfig {
auto dipSwitches(Node::Object parent) -> void {
Node::Setting::Boolean testMode = parent->append<Node::Setting::Boolean>("Test Mode", false, [&](auto value) {
aleck64.dipSwitch[1].bit(7) = !value;
});
testMode->setDynamic(true);
testMode->modify(testMode->value());
}
};
@@ -1,11 +1,3 @@
struct mtetrisc : Aleck64::GameConfig {
auto type() -> Aleck64::BoardType { return Aleck64::BoardType::E90; }
auto dipSwitches(Node::Object parent) -> void {
Node::Setting::Boolean testMode = parent->append<Node::Setting::Boolean>("Test Mode", false, [&](auto value) {
aleck64.dipSwitch[1].bit(7) = !value;
});
testMode->setDynamic(true);
testMode->modify(testMode->value());
}
};
-8
View File
@@ -1,14 +1,6 @@
struct srmvs : Aleck64::GameConfig {
u8 mahjongRow = 0;
auto dipSwitches(Node::Object parent) -> void {
Node::Setting::Boolean testMode = parent->append<Node::Setting::Boolean>("Test Mode", false, [&](auto value) {
aleck64.dipSwitch[1].bit(7) = !value;
});
testMode->setDynamic(true);
testMode->modify(testMode->value());
}
auto readExpansionPort() -> u32 {
n32 value = 0xffff'ffff;
value.bit(16, 23) = aleck64.mahjong.read(mahjongRow);
-92
View File
@@ -1,92 +0,0 @@
struct starsldr : Aleck64::GameConfig {
auto dipSwitches(Node::Object parent) -> void {
Node::Setting::Boolean testMode = parent->append<Node::Setting::Boolean>("Test Mode", false, [&](auto value) {
aleck64.dipSwitch[1].bit(7) = !value;
});
testMode->setDynamic(true);
testMode->modify(testMode->value());
Node::Setting::String joystickType = parent->append<Node::Setting::String>("Joystick Type", "2D", [&](auto value) {
aleck64.dipSwitch[0].bit(7) = value == "3D";
});
joystickType->setAllowedValues({ "2D", "3D" });
joystickType->setDynamic(true);
joystickType->modify(joystickType->value());
Node::Setting::String player = parent->append<Node::Setting::String>("Player", "3", [&](auto value) {
if(value == "3") aleck64.dipSwitch[0].bit(3,4) = 3;
if(value == "4") aleck64.dipSwitch[0].bit(3,4) = 2;
if(value == "2") aleck64.dipSwitch[0].bit(3,4) = 1;
if(value == "1") aleck64.dipSwitch[0].bit(3,4) = 0;
});
player->setAllowedValues({"4", "3", "2", "1"});
player->setDynamic(true);
player->modify(player->value());
Node::Setting::String autoLevel = parent->append<Node::Setting::String>("Auto Level", "Normal", [&](auto value) {
if(value == "Normal") aleck64.dipSwitch[0].bit(5,6) = 3;
if(value == "Slow" ) aleck64.dipSwitch[0].bit(5,6) = 2;
if(value == "Fast1" ) aleck64.dipSwitch[0].bit(5,6) = 1;
if(value == "Fast2" ) aleck64.dipSwitch[0].bit(5,6) = 0;
});
autoLevel->setAllowedValues({ "Normal", "Slow", "Fast1", "Fast2" });
autoLevel->setDynamic(true);
autoLevel->modify(autoLevel->value());
Node::Setting::String coinage = parent->append<Node::Setting::String>("Coinage", "1 Coin 1 Credit", [&](auto value) {
if(value == "1 Coin 1 Credit" ) aleck64.dipSwitch[0].bit(0,2) = 7;
if(value == "1 Coin 2 Credits" ) aleck64.dipSwitch[0].bit(0,2) = 6;
if(value == "1 Coin 3 Credits" ) aleck64.dipSwitch[0].bit(0,2) = 5;
if(value == "1 Coin 4 Credits" ) aleck64.dipSwitch[0].bit(0,2) = 4;
if(value == "2 Coins 1 Credit" ) aleck64.dipSwitch[0].bit(0,2) = 3;
if(value == "3 Coins 1 Credit") aleck64.dipSwitch[0].bit(0,2) = 2;
if(value == "4 Coins 1 Credit") aleck64.dipSwitch[0].bit(0,2) = 1;
if(value == "5 Coins 1 Credit") aleck64.dipSwitch[0].bit(0,2) = 0;
});
coinage->setAllowedValues({ "1 Coin 1 Credit", "1 Coin 2 Credits", "1 Coin 3 Credits", "1 Coin 4 Credits",
"2 Coins 1 Credit", "3 Coins 1 Credit", "4 Coins 1 Credit", "5 Coins 1 Credit" });
coinage->setDynamic(true);
coinage->modify(coinage->value());
Node::Setting::String language = parent->append<Node::Setting::String>("Language", "English", [&](auto value) {
aleck64.dipSwitch[1].bit(6) = value == "English";
});
language->setAllowedValues({ "English", "Japanese" });
language->setDynamic(true);
language->modify(language->value());
Node::Setting::Boolean demoSound = parent->append<Node::Setting::Boolean>("Demo Sound", true, [&](auto value) {
aleck64.dipSwitch[1].bit(5) = !value;
});
demoSound->setDynamic(true);
demoSound->modify(demoSound->value());
Node::Setting::Boolean rapid = parent->append<Node::Setting::Boolean>("Rapid", false, [&](auto value) {
aleck64.dipSwitch[1].bit(4) = value;
});
rapid->setDynamic(true);
rapid->modify(rapid->value());
Node::Setting::String extend = parent->append<Node::Setting::String>("Extend", "30000000", [&](auto value) {
if(value == "30,000,000") aleck64.dipSwitch[1].bit(2,3) = 3;
if(value == "50,000,000") aleck64.dipSwitch[1].bit(2,3) = 2;
if(value == "70,000,000") aleck64.dipSwitch[1].bit(2,3) = 1;
if(value == "None" ) aleck64.dipSwitch[1].bit(2,3) = 0;
});
extend->setAllowedValues({ "30,000,000", "50,000,000", "70,000,000", "None" });
extend->setDynamic(true);
extend->modify(extend->value());
Node::Setting::String difficulty = parent->append<Node::Setting::String>("Difficulty", "Normal", [&](auto value) {
if(value == "Normal") aleck64.dipSwitch[1].bit(0,1) = 3;
if(value == "Easy" ) aleck64.dipSwitch[1].bit(0,1) = 2;
if(value == "Hard1" ) aleck64.dipSwitch[1].bit(0,1) = 1;
if(value == "Hard2" ) aleck64.dipSwitch[1].bit(0,1) = 0;
});
difficulty->setAllowedValues({ "Normal", "Easy", "Hard1", "Hard2" });
difficulty->setDynamic(true);
difficulty->modify(difficulty->value());
}
};
@@ -1,9 +0,0 @@
struct twrshaft : Aleck64::GameConfig {
auto dipSwitches(Node::Object parent) -> void {
Node::Setting::Boolean testMode = parent->append<Node::Setting::Boolean>("Test Mode", false, [&](auto value) {
aleck64.dipSwitch[1].bit(7) = !value;
});
testMode->setDynamic(true);
testMode->modify(testMode->value());
}
};
-61
View File
@@ -1,61 +0,0 @@
struct vivdolls : Aleck64::GameConfig {
auto dipSwitches(Node::Object parent) -> void {
Node::Setting::Boolean testMode = parent->append<Node::Setting::Boolean>("Test Mode", false, [&](auto value) {
aleck64.dipSwitch[1].bit(7) = !value;
});
testMode->setDynamic(true);
testMode->modify(testMode->value());
Node::Setting::String coinage = parent->append<Node::Setting::String>("Coinage", "1 Coin 1 Credit", [&](auto value) {
if(value == "1 Coin 1 Credit" ) aleck64.dipSwitch[0].bit(0,2) = 7;
if(value == "1 Coin 2 Credits") aleck64.dipSwitch[0].bit(0,2) = 3;
if(value == "1 Coin 3 Credits") aleck64.dipSwitch[0].bit(0,2) = 5;
if(value == "1 Coin 4 Credits") aleck64.dipSwitch[0].bit(0,2) = 1;
if(value == "2 Coins 1 Credit") aleck64.dipSwitch[0].bit(0,2) = 6;
if(value == "3 Coins 1 Credit") aleck64.dipSwitch[0].bit(0,2) = 2;
if(value == "4 Coins 1 Credit") aleck64.dipSwitch[0].bit(0,2) = 4;
if(value == "5 Coins 1 Credit") aleck64.dipSwitch[0].bit(0,2) = 0;
});
coinage->setAllowedValues({ "1 Coin 1 Credit", "1 Coin 2 Credits", "1 Coin 3 Credits", "1 Coin 4 Credits",
"2 Coins 1 Credit", "3 Coins 1 Credit", "4 Coins 1 Credit", "5 Coins 1 Credit" });
coinage->setDynamic(true);
coinage->modify(coinage->value());
Node::Setting::String qualifyArea = parent->append<Node::Setting::String>("Qualify Area", "70%", [&](auto value) {
if(value == "70%") aleck64.dipSwitch[1].bit(5) = 0;
if(value == "80%") aleck64.dipSwitch[1].bit(5) = 1;
});
qualifyArea->setAllowedValues({ "70%", "80%" });
qualifyArea->setDynamic(true);
qualifyArea->modify(qualifyArea->value());
Node::Setting::String controls = parent->append<Node::Setting::String>("Controls", "JOYSTICK", [&](auto value) {
if(value == "JAMMASTICK") aleck64.dipSwitch[1].bit(5) = 0;
if(value == "JOYSTICK" ) aleck64.dipSwitch[1].bit(5) = 1;
});
controls->setAllowedValues({ "JAMMASTICK", "JOYSTICK" });
controls->setDynamic(true);
controls->modify(controls->value());
Node::Setting::String lives = parent->append<Node::Setting::String>("Lives", "4", [&](auto value) {
if(value == "4") aleck64.dipSwitch[1].bit(2,3) = 3;
if(value == "3") aleck64.dipSwitch[1].bit(2,3) = 2;
if(value == "2") aleck64.dipSwitch[1].bit(2,3) = 1;
if(value == "1") aleck64.dipSwitch[1].bit(2,3) = 0;
});
lives->setAllowedValues({ "4", "3", "2", "1" });
lives->setDynamic(true);
lives->modify(lives->value());
Node::Setting::String difficulty = parent->append<Node::Setting::String>("Difficulty", "Normal", [&](auto value) {
if(value == "Normal" ) aleck64.dipSwitch[1].bit(0,1) = 3;
if(value == "Easy" ) aleck64.dipSwitch[1].bit(0,1) = 2;
if(value == "Hard" ) aleck64.dipSwitch[1].bit(0,1) = 1;
if(value == "Hardest") aleck64.dipSwitch[1].bit(0,1) = 0;
});
difficulty->setAllowedValues({ "Normal", "Easy", "Hard", "Hardest" });
difficulty->setDynamic(true);
difficulty->modify(difficulty->value());
}
};
+1 -2
View File
@@ -35,8 +35,7 @@ auto Aleck64::readPort1() -> u32 {
value.bit(15) = !aleck64.controls.p2start->value();
}
value.bit(16, 23) = dipSwitch[1];
value.bit(24, 31) = dipSwitch[0];
value.bit(16, 31) = dips.read("IN0") >> 16;
return value;
}
-1
View File
@@ -4,5 +4,4 @@ auto Aleck64::serialize(serializer& s) -> void {
s(pram);
s(vdp.io.enable);
s(dipSwitch);
}
+5 -1
View File
@@ -683,6 +683,7 @@ auto CPU::Recompiler::emit(u64 vaddr, u32 address, u64 stateKey) -> Block* {
for(size_t idx = 0; idx < plan.instructions.size(); idx++) {
auto& ii = plan.instructions[idx];
bool firstInstruction = (idx == 0);
bool lastInstruction = (idx + 1 == plan.instructions.size());
bool delaySlot = prevBranched;
bool isInternalEntry = findInternalLabel(ii.vaddr) != nullptr;
bool sectionBoundary = sectionIndex(ii.address + 4) != startSection;
@@ -752,9 +753,12 @@ auto CPU::Recompiler::emit(u64 vaddr, u32 address, u64 stateKey) -> Block* {
// EndBlock must be observed at entry, delay-slot completion, likely-branch flows and helpers.
bool needEndBlockCheck = firstInstruction || delaySlot || info.likelyBranch() || emitCallfEmitted;
// Commit architectural pipeline state only where correctness requires it.
// The block exit after the last instruction needs a commit too: a window
// that ends before a breakpoint can end on straight-line code.
bool needPipelineCommit = needEndBlockCheck || info.branch() || sectionBoundary
|| info.jitStateKeyMayChange()
|| info.countCompareWrite();
|| info.countCompareWrite()
|| lastInstruction;
if(needPipelineCommit) setupPipeline();
if(needEndBlockCheck) {
test32(PipelineReg(state), imm(Pipeline::EndBlock), set_z);
+1
View File
@@ -10,6 +10,7 @@
#include <nall/queue.hpp>
#include <nall/recompiler/generic/generic.hpp>
#include <component/processor/sm5k/sm5k.hpp>
#include <component/arcade/dip-switches.hpp>
#include <functional>
#include <span>
#include <vector>
+1
View File
@@ -194,6 +194,7 @@ auto RDP::IO::writeWord(u32 address, u32 data_, Thread& thread) -> void {
auto RDP::flushCommands() -> void {
if(command.freeze || command.crashed) return;
if(command.end <= command.current) return;
command.bufferBusy = 1;
command.pipeBusy = 1;
command.startGclk = 1;
+13 -1
View File
@@ -1,4 +1,4 @@
ares_components(m68000 z80)
ares_components(m68000 z80 arcadedip upd4990a)
target_link_libraries(ares PRIVATE ymfm)
@@ -57,6 +57,18 @@ ares_add_sources(
cartridge/slot.hpp
cartridge/board/board.cpp
cartridge/board/board.hpp
cartridge/board/cmc.cpp
cartridge/board/cmc-tables.hpp
cartridge/board/fatfury2.cpp
cartridge/board/k2k2.cpp
cartridge/board/k2k2-tables.hpp
cartridge/board/kof98.cpp
cartridge/board/pcm2.cpp
cartridge/board/pcm2-tables.hpp
cartridge/board/pvc.cpp
cartridge/board/pvc-tables.hpp
cartridge/board/sma.cpp
cartridge/board/sma-tables.hpp
cartridge/board/jockey-gp.cpp
cartridge/board/mslugx.cpp
cartridge/board/rom.cpp
+7
View File
@@ -2,6 +2,13 @@ namespace Board {
#include "rom.cpp"
#include "mslugx.cpp"
#include "cmc.cpp"
#include "sma.cpp"
#include "pcm2.cpp"
#include "pvc.cpp"
#include "k2k2.cpp"
#include "fatfury2.cpp"
#include "kof98.cpp"
#include "jockey-gp.cpp"
auto Interface::load(Memory::Readable<n8>& memory, string name) -> bool {
+1
View File
@@ -15,6 +15,7 @@ struct Interface {
virtual auto readS(n32 address) -> n8 { return 0xff; }
virtual auto readVA(n32 address) -> n8 { return 0xff; }
virtual auto readVB(n32 address) -> n8 { return 0xff; }
virtual auto fixBankType() -> u32 { return 0; }
virtual auto power() -> void {}
virtual auto serialize(serializer&) -> void {}
@@ -1,4 +1,14 @@
u8 kof99_type0_t03[256] = {
#pragma once
// Decryption algorithms heavily based on/borrowed from MAME
// See https://github.com/mamedev/mame/tree/master/src/devices/bus/neogeo
// and the MAME section in LICENCE in the ares license text.
namespace CmcTables {
namespace Cmc42 {
static constexpr u8 type0High[256] = {
0xfb, 0x86, 0x9d, 0xf1, 0xbf, 0x80, 0xd5, 0x43, 0xab, 0xb3, 0x9f, 0x6a, 0x33, 0xd9, 0xdb, 0xb6,
0x66, 0x08, 0x69, 0x88, 0xcc, 0xb7, 0xde, 0x49, 0x97, 0x64, 0x1f, 0xa6, 0xc0, 0x2f, 0x52, 0x42,
0x44, 0x5a, 0xf2, 0x28, 0x98, 0x87, 0x96, 0x8a, 0x83, 0x0b, 0x03, 0x61, 0x71, 0x99, 0x6b, 0xb5,
@@ -17,7 +27,7 @@ u8 kof99_type0_t03[256] = {
0xc6, 0xce, 0x18, 0x36, 0xdf, 0xa9, 0x8c, 0xd8, 0xa4, 0xf0, 0x3b, 0x51, 0x4a, 0x02, 0x3e, 0x53,
};
u8 kof99_type0_t12[256] = {
static constexpr u8 type0Low[256] = {
0x1f, 0xac, 0x4d, 0xcd, 0xca, 0x70, 0x02, 0x6b, 0x18, 0x40, 0x62, 0xb2, 0x3f, 0x9b, 0x5b, 0xef,
0x69, 0x68, 0x71, 0x3b, 0xcb, 0xd4, 0x30, 0xbc, 0x47, 0x72, 0x74, 0x5e, 0x84, 0x4c, 0x1b, 0xdb,
0x6a, 0x35, 0x1d, 0xf5, 0xa1, 0xb3, 0x87, 0x5d, 0x57, 0x28, 0x2f, 0xc4, 0xfd, 0x24, 0x26, 0x36,
@@ -36,7 +46,7 @@ u8 kof99_type0_t12[256] = {
0x67, 0xae, 0x09, 0x89, 0xdc, 0x4e, 0xc6, 0xc0, 0x8a, 0xb1, 0x59, 0x8b, 0x41, 0x56, 0x8d, 0xba,
};
u8 kof99_type1_t03[256] = {
static constexpr u8 type1High[256] = {
0xa9, 0x17, 0xaf, 0x0d, 0x34, 0x6e, 0x53, 0xb6, 0x7f, 0x58, 0xe9, 0x14, 0x5f, 0x55, 0xdb, 0xd4,
0x42, 0x80, 0x99, 0x59, 0xa8, 0x3a, 0x57, 0x5d, 0xd5, 0x6f, 0x4c, 0x68, 0x35, 0x46, 0xa6, 0xe7,
0x7b, 0x71, 0xe0, 0x93, 0xa2, 0x1f, 0x64, 0x21, 0xe3, 0xb1, 0x98, 0x26, 0xab, 0xad, 0xee, 0xe5,
@@ -55,7 +65,7 @@ u8 kof99_type1_t03[256] = {
0x63, 0x40, 0x2d, 0xe8, 0xb2, 0xe6, 0x39, 0x03, 0xeb, 0x8e, 0xe1, 0x0c, 0xe4, 0xe2, 0xf8, 0xac,
};
u8 kof99_type1_t12[256] = {
static constexpr u8 type1Low[256] = {
0xea, 0xe6, 0x5e, 0xa7, 0x8e, 0xac, 0x34, 0x03, 0x30, 0x97, 0x52, 0x53, 0x76, 0xf2, 0x62, 0x0b,
0x0a, 0xfc, 0x94, 0xb8, 0x67, 0x36, 0x11, 0xbc, 0xae, 0xca, 0xfa, 0x15, 0x04, 0x2b, 0x17, 0xc4,
0x3e, 0x5b, 0x59, 0x01, 0x57, 0xe2, 0xba, 0xb7, 0xd1, 0x3f, 0xf0, 0x6a, 0x9c, 0x2a, 0xcb, 0xa9,
@@ -74,9 +84,7 @@ u8 kof99_type1_t12[256] = {
0xd2, 0xd3, 0xfd, 0x1d, 0xf5, 0x3b, 0xa5, 0x7f, 0xef, 0x9a, 0xb6, 0x42, 0xfb, 0x3c, 0xf7, 0x41,
};
// underlined values are wrong (not enough evidence, FF fill in kof99 and garou)
// they correspond to tiles 7d000-7efff
u8 kof99_address_8_15_xor1[256] = {
static constexpr u8 address8To15Xor1[256] = {
0x00, 0xb1, 0x1e, 0xc5, 0x3d, 0x40, 0x45, 0x5e, 0xf2, 0xf8, 0x04, 0x63, 0x36, 0x87, 0x88, 0xbf,
0xab, 0xcc, 0x78, 0x08, 0xdd, 0x20, 0xd4, 0x35, 0x09, 0x8e, 0x44, 0xae, 0x33, 0xa9, 0x9e, 0xcd,
0xb3, 0xe5, 0xad, 0x41, 0xda, 0xbe, 0xf4, 0x16, 0x57, 0x2e, 0x53, 0x67, 0xaf, 0xdb, 0x8a, 0xd8,
@@ -93,10 +101,9 @@ u8 kof99_address_8_15_xor1[256] = {
0x3a, 0x8d, 0x32, 0x5d, 0x68, 0xb9, 0x9f, 0x75, 0x19, 0x3f, 0xac, 0x37, 0x4f, 0xe7, 0x93, 0x89,
0x7e, 0x4a, 0x3b, 0xea, 0x74, 0x72, 0x43, 0xbd, 0x24, 0xef, 0xb6, 0xff, 0x64, 0x58, 0x84, 0x8b,
0xa7, 0xbb, 0xb2, 0xe1, 0x26, 0x2b, 0x50, 0xca, 0x21, 0xf9, 0x98, 0xa1, 0xe2, 0x42, 0x82, 0x48,
// ^^^^ ^^^^ ^^^^ ^^^^
};
u8 kof99_address_8_15_xor2[256] = {
static constexpr u8 address8To15Xor2[256] = {
0x9b, 0x9d, 0xc1, 0x3d, 0xa9, 0xb8, 0xf4, 0x6f, 0xf6, 0x25, 0xc7, 0x47, 0xd5, 0x97, 0xdf, 0x6b,
0xeb, 0x90, 0xa4, 0xb2, 0x5d, 0xf5, 0x66, 0xb0, 0xb9, 0x8b, 0x93, 0x64, 0xec, 0x7b, 0x65, 0x8c,
0xf1, 0x43, 0x42, 0x6e, 0x45, 0x9f, 0xb3, 0x35, 0x06, 0x71, 0x96, 0xdb, 0xa0, 0xfb, 0x0b, 0x3a,
@@ -115,7 +122,7 @@ u8 kof99_address_8_15_xor2[256] = {
0x87, 0x07, 0x10, 0x60, 0x14, 0x73, 0x3c, 0x51, 0x19, 0xa3, 0xb5, 0xcf, 0x13, 0xf0, 0x15, 0x4e,
};
u8 kof99_address_16_23_xor1[256] = {
static constexpr u8 address16To23Xor1[256] = {
0x00, 0x5f, 0x03, 0x52, 0xce, 0xe3, 0x7d, 0x8f, 0x6b, 0xf8, 0x20, 0xde, 0x7b, 0x7e, 0x39, 0xbe,
0xf5, 0x94, 0x18, 0x78, 0x80, 0xc9, 0x7f, 0x7a, 0x3e, 0x63, 0xf2, 0xe0, 0x4e, 0xf7, 0x87, 0x27,
0x69, 0x6c, 0xa4, 0x1d, 0x85, 0x5b, 0xe6, 0x44, 0x25, 0x0c, 0x98, 0xc7, 0x01, 0x02, 0xa3, 0x26,
@@ -134,7 +141,7 @@ u8 kof99_address_16_23_xor1[256] = {
0x65, 0xf4, 0x42, 0xa0, 0x8d, 0xa7, 0x48, 0x55, 0x21, 0x93, 0x24, 0xd2, 0xa6, 0xa5, 0xa8, 0x82,
};
u8 kof99_address_16_23_xor2[256] = {
static constexpr u8 address16To23Xor2[256] = {
0x29, 0x97, 0x1a, 0x2c, 0x0b, 0x94, 0x3e, 0x75, 0x01, 0x0d, 0x1b, 0xe1, 0x4d, 0x38, 0x39, 0x8f,
0xe7, 0xd0, 0x60, 0x90, 0xb2, 0x0f, 0xbb, 0x70, 0x1f, 0xe6, 0x5b, 0x87, 0xb4, 0x43, 0xfd, 0xf5,
0xf6, 0xf9, 0xad, 0xc0, 0x98, 0x17, 0x9f, 0x91, 0x15, 0x51, 0x55, 0x64, 0x6c, 0x18, 0x61, 0x0e,
@@ -153,7 +160,7 @@ u8 kof99_address_16_23_xor2[256] = {
0xb7, 0xb3, 0xd8, 0xcf, 0x3b, 0x00, 0x92, 0xb8, 0x86, 0xac, 0x49, 0x7c, 0xf1, 0xd5, 0xcb, 0x7d,
};
u8 kof99_address_0_7_xor[256] = {
static constexpr u8 address0To7Xor[256] = {
0x74, 0xad, 0x5d, 0x1d, 0x9e, 0xc3, 0xfa, 0x4e, 0xf7, 0xdb, 0xca, 0xa2, 0x64, 0x36, 0x56, 0x0c,
0x4f, 0xcf, 0x43, 0x66, 0x1e, 0x91, 0xe3, 0xa5, 0x58, 0xc2, 0xc1, 0xd4, 0xb9, 0xdd, 0x76, 0x16,
0xce, 0x61, 0x75, 0x01, 0x2b, 0x22, 0x38, 0x55, 0x50, 0xef, 0x6c, 0x99, 0x05, 0xe9, 0xe8, 0xe0,
@@ -172,7 +179,11 @@ u8 kof99_address_0_7_xor[256] = {
0x48, 0xac, 0x7f, 0x3f, 0x95, 0xdc, 0x98, 0x9b, 0xbe, 0x23, 0x57, 0x3e, 0x5b, 0xd0, 0x3d, 0xa6,
};
u8 kof2000_type0_t03[256] = {
}
namespace Cmc50 {
static constexpr u8 type0High[256] = {
0x10, 0x61, 0xf1, 0x78, 0x85, 0x52, 0x68, 0xe3, 0x12, 0x0d, 0xfa, 0xf0, 0xc9, 0x36, 0x5e, 0x3d,
0xf9, 0xa6, 0x01, 0x2e, 0xc7, 0x84, 0xea, 0x2b, 0x6d, 0x14, 0x38, 0x4f, 0x55, 0x1c, 0x9d, 0xa7,
0x7a, 0xc6, 0xf8, 0x9a, 0xe6, 0x42, 0xb5, 0xed, 0x7d, 0x3a, 0xb1, 0x05, 0x43, 0x4a, 0x22, 0xfd,
@@ -191,7 +202,7 @@ u8 kof2000_type0_t03[256] = {
0x6e, 0x7f, 0x54, 0x3f, 0x02, 0xd1, 0x5b, 0x50, 0x56, 0x48, 0xf4, 0xbd, 0x62, 0x47, 0x04, 0xcd,
};
u8 kof2000_type0_t12[256] = {
static constexpr u8 type0Low[256] = {
0xf4, 0x28, 0xb4, 0x8f, 0xfa, 0xeb, 0x8e, 0x54, 0x2b, 0x49, 0xd1, 0x76, 0x71, 0x47, 0x8b, 0x57,
0x92, 0x85, 0x7c, 0xb8, 0x5c, 0x22, 0xf9, 0x26, 0xbc, 0x5b, 0x6d, 0x67, 0xae, 0x5f, 0x6f, 0xf5,
0x9f, 0x48, 0x66, 0x40, 0x0d, 0x11, 0x4e, 0xb2, 0x6b, 0x35, 0x15, 0x0f, 0x18, 0x25, 0x1d, 0xba,
@@ -210,7 +221,7 @@ u8 kof2000_type0_t12[256] = {
0xdc, 0xc4, 0x32, 0xe7, 0x84, 0x3e, 0x30, 0xa1, 0x1e, 0xb5, 0x6e, 0x65, 0xe9, 0xad, 0xa7, 0x96,
};
u8 kof2000_type1_t03[256] = {
static constexpr u8 type1High[256] = {
0x9a, 0x2f, 0xcc, 0x4e, 0x40, 0x69, 0xac, 0xca, 0xa5, 0x7b, 0x0a, 0x61, 0x91, 0x0d, 0x55, 0x74,
0xcd, 0x8b, 0x0b, 0x80, 0x09, 0x5e, 0x38, 0xc7, 0xda, 0xbf, 0xf5, 0x37, 0x23, 0x31, 0x33, 0xe9,
0xae, 0x87, 0xe5, 0xfa, 0x6e, 0x5c, 0xad, 0xf4, 0x76, 0x62, 0x9f, 0x2e, 0x01, 0xe2, 0xf6, 0x47,
@@ -229,7 +240,7 @@ u8 kof2000_type1_t03[256] = {
0xaf, 0x17, 0xf8, 0xfb, 0x60, 0x50, 0xeb, 0x4b, 0x99, 0x63, 0xba, 0xb2, 0x71, 0xcf, 0x10, 0xbe,
};
u8 kof2000_type1_t12[256] = {
static constexpr u8 type1Low[256] = {
0xda, 0xa7, 0xd6, 0x6e, 0x2f, 0x5e, 0xf0, 0x3f, 0xa4, 0xce, 0xd3, 0xfd, 0x46, 0x2a, 0xac, 0xc9,
0xbe, 0xeb, 0x9f, 0xd5, 0x3c, 0x61, 0x96, 0x11, 0xd0, 0x38, 0xca, 0x06, 0xed, 0x1b, 0x65, 0xe7,
0x23, 0xdd, 0xd9, 0x05, 0xbf, 0x5b, 0x5d, 0xa5, 0x95, 0x00, 0xec, 0xf1, 0x01, 0xa9, 0xa6, 0xfc,
@@ -248,7 +259,7 @@ u8 kof2000_type1_t12[256] = {
0x1f, 0x49, 0x8f, 0x4c, 0xc1, 0x44, 0x17, 0x12, 0x6f, 0x32, 0xb7, 0x3e, 0x74, 0x1e, 0x21, 0x6a,
};
u8 kof2000_address_8_15_xor1[256] = {
static constexpr u8 address8To15Xor1[256] = {
0xfc, 0x9b, 0x1c, 0x35, 0x72, 0x53, 0xd6, 0x7d, 0x84, 0xa4, 0xc5, 0x93, 0x7b, 0xe7, 0x47, 0xd5,
0x24, 0xa2, 0xfa, 0x19, 0x0c, 0xb1, 0x8c, 0xb9, 0x9d, 0xd8, 0x59, 0x4f, 0x3c, 0xb2, 0x78, 0x4a,
0x2a, 0x96, 0x9a, 0xf1, 0x1f, 0x22, 0xa8, 0x5b, 0x67, 0xa3, 0x0f, 0x00, 0xfb, 0xdf, 0xeb, 0x0a,
@@ -267,7 +278,7 @@ u8 kof2000_address_8_15_xor1[256] = {
0x2e, 0x08, 0x1d, 0xf9, 0xb6, 0xa6, 0x64, 0x07, 0x82, 0x38, 0x98, 0x3a, 0x9f, 0xde, 0xbc, 0xd9,
};
u8 kof2000_address_8_15_xor2[256] = {
static constexpr u8 address8To15Xor2[256] = {
0x00, 0xbe, 0x06, 0x5a, 0xfa, 0x42, 0x15, 0xf2, 0x3f, 0x0a, 0x84, 0x93, 0x4e, 0x78, 0x3b, 0x89,
0x32, 0x98, 0xa2, 0x87, 0x73, 0xdd, 0x26, 0xe5, 0x05, 0x71, 0x08, 0x6e, 0x9b, 0xe0, 0xdf, 0x9e,
0xfc, 0x83, 0x81, 0xef, 0xb2, 0xc0, 0xc3, 0xbf, 0xa7, 0x6d, 0x1b, 0x95, 0xed, 0xb9, 0x3e, 0x13,
@@ -286,7 +297,7 @@ u8 kof2000_address_8_15_xor2[256] = {
0xe2, 0xc9, 0xaa, 0x4b, 0x8d, 0x4f, 0xe6, 0x64, 0xda, 0x74, 0xb6, 0x72, 0x57, 0x62, 0xfd, 0x58,
};
u8 kof2000_address_16_23_xor1[256] = {
static constexpr u8 address16To23Xor1[256] = {
0x45, 0x9f, 0x6e, 0x2f, 0x28, 0xbc, 0x5e, 0x6d, 0xda, 0xb5, 0x0d, 0xb8, 0xc0, 0x8e, 0xa2, 0x32,
0xee, 0xcd, 0x8d, 0x48, 0x8c, 0x27, 0x14, 0xeb, 0x65, 0xd7, 0xf2, 0x93, 0x99, 0x90, 0x91, 0xfc,
0x5f, 0xcb, 0xfa, 0x75, 0x3f, 0x26, 0xde, 0x72, 0x33, 0x39, 0xc7, 0x1f, 0x88, 0x79, 0x73, 0xab,
@@ -305,7 +316,7 @@ u8 kof2000_address_16_23_xor1[256] = {
0xdf, 0x83, 0x74, 0xf5, 0x38, 0x20, 0xfd, 0xad, 0xc4, 0xb9, 0x55, 0x51, 0xb0, 0xef, 0x00, 0x57,
};
u8 kof2000_address_16_23_xor2[256] = {
static constexpr u8 address16To23Xor2[256] = {
0x00, 0xb8, 0xf0, 0x34, 0xca, 0x21, 0x3c, 0xf9, 0x01, 0x8e, 0x75, 0x70, 0xec, 0x13, 0x27, 0x96,
0xf4, 0x5b, 0x88, 0x1f, 0xeb, 0x4a, 0x7d, 0x9d, 0xbe, 0x02, 0x14, 0xaf, 0xa2, 0x06, 0xc6, 0xdb,
0x35, 0x6b, 0x74, 0x45, 0x7b, 0x29, 0xd2, 0xfe, 0xb6, 0x15, 0xd0, 0x8a, 0xa9, 0x2d, 0x19, 0xf6,
@@ -324,7 +335,7 @@ u8 kof2000_address_16_23_xor2[256] = {
0x3f, 0xb7, 0x22, 0x97, 0x09, 0xe0, 0xa5, 0x20, 0x23, 0x2c, 0x72, 0xd6, 0x39, 0xab, 0x0a, 0xee,
};
u8 kof2000_address_0_7_xor[256] = {
static constexpr u8 address0To7Xor[256] = {
0x26, 0x48, 0x06, 0x9b, 0x21, 0xa9, 0x1b, 0x76, 0xc9, 0xf8, 0xb4, 0x67, 0xe4, 0xff, 0x99, 0xf7,
0x15, 0x9e, 0x62, 0x00, 0x72, 0x4d, 0xa0, 0x4f, 0x02, 0xf1, 0xea, 0xef, 0x0b, 0xf3, 0xeb, 0xa6,
0x93, 0x78, 0x6f, 0x7c, 0xda, 0xd4, 0x7b, 0x05, 0xe9, 0xc6, 0xd6, 0xdb, 0x50, 0xce, 0xd2, 0x01,
@@ -343,7 +354,11 @@ u8 kof2000_address_0_7_xor[256] = {
0x32, 0x3e, 0x45, 0xaf, 0x1e, 0x43, 0x44, 0x8c, 0x53, 0x86, 0x6b, 0xee, 0xa8, 0x8a, 0x8f, 0x17,
};
u8 m1_address_8_15_xor[256] = {
}
namespace Music {
static constexpr u8 address8To15Xor[256] = {
0x0a, 0x72, 0xb7, 0xaf, 0x67, 0xde, 0x1d, 0xb1, 0x78, 0xc4, 0x4f, 0xb5, 0x4b, 0x18, 0x76, 0xdd,
0x11, 0xe2, 0x36, 0xa1, 0x82, 0x03, 0x98, 0xa0, 0x10, 0x5f, 0x3f, 0xd6, 0x1f, 0x90, 0x6a, 0x0b,
0x70, 0xe0, 0x64, 0xcb, 0x9f, 0x38, 0x8b, 0x53, 0x04, 0xca, 0xf8, 0xd0, 0x07, 0x68, 0x56, 0x32,
@@ -362,7 +377,7 @@ u8 m1_address_8_15_xor[256] = {
0x7d, 0x1a, 0x02, 0x65, 0x54, 0x5e, 0x19, 0xcc, 0xdc, 0xdb, 0x73, 0xed, 0xad, 0x59, 0x2f, 0xa3,
};
u8 m1_address_0_7_xor[256] = {
static constexpr u8 address0To7Xor[256] = {
0xf4, 0xbc, 0x02, 0xf7, 0x2c, 0x3d, 0xe8, 0xd9, 0x50, 0x62, 0xec, 0xbd, 0x53, 0x73, 0x79, 0x61,
0x00, 0x34, 0xcf, 0xa2, 0x63, 0x28, 0x90, 0xaf, 0x44, 0x3b, 0xc5, 0x8d, 0x3a, 0x46, 0x07, 0x70,
0x66, 0xbe, 0xd8, 0x8b, 0xe9, 0xa0, 0x4b, 0x98, 0xdc, 0xdf, 0xe2, 0x16, 0x74, 0xf1, 0x37, 0xf5,
@@ -381,22 +396,6 @@ u8 m1_address_0_7_xor[256] = {
0x76, 0xea, 0x5c, 0x82, 0x1a, 0x4f, 0xaa, 0xca, 0xe1, 0x0b, 0x4e, 0xcb, 0x6a, 0xef, 0xd1, 0xd6,
};
#define BITSWAP16(n, \
bit15, bit14, bit13, bit12, bit11, bit10, bit09, bit08, \
bit07, bit06, bit05, bit04, bit03, bit02, bit01, bit00) \
(((((n) >> (bit15)) & 1) << 15) | \
((((n) >> (bit14)) & 1) << 14) | \
((((n) >> (bit13)) & 1) << 13) | \
((((n) >> (bit12)) & 1) << 12) | \
((((n) >> (bit11)) & 1) << 11) | \
((((n) >> (bit10)) & 1) << 10) | \
((((n) >> (bit09)) & 1) << 9) | \
((((n) >> (bit08)) & 1) << 8) | \
((((n) >> (bit07)) & 1) << 7) | \
((((n) >> (bit06)) & 1) << 6) | \
((((n) >> (bit05)) & 1) << 5) | \
((((n) >> (bit04)) & 1) << 4) | \
((((n) >> (bit03)) & 1) << 3) | \
((((n) >> (bit02)) & 1) << 2) | \
((((n) >> (bit01)) & 1) << 1) | \
((((n) >> (bit00)) & 1) << 0))
}
}
+156
View File
@@ -0,0 +1,156 @@
#include "cmc-tables.hpp"
struct CmcContext {
const u8* type0High;
const u8* type0Low;
const u8* type1High;
const u8* type1Low;
const u8* address8To15Xor1;
const u8* address8To15Xor2;
const u8* address16To23Xor1;
const u8* address16To23Xor2;
const u8* address0To7Xor;
};
static auto decryptCmcGraphicsInternal(u8* r0, u8* r1, u8 c0, u8 c1, const u8* table0High, const u8* table0Low,
const u8* table1, CmcContext& context, int base, int invert) -> void {
u8 tmp = table1[(base & 0xff) ^ context.address0To7Xor[(base >> 8) & 0xff]];
u8 xor0 = (table0High[(base >> 8) & 0xff] & 0xfe) | (tmp & 0x01);
u8 xor1 = (tmp & 0xfe) | (table0Low[(base >> 8) & 0xff] & 0x01);
if(invert) {
*r0 = c1 ^ xor0;
*r1 = c0 ^ xor1;
return;
}
*r0 = c0 ^ xor0;
*r1 = c1 ^ xor1;
}
static auto reorderCmcMusic(n16 source, const u8 (&bits)[16]) -> n16 {
n16 result = 0;
for(u32 index : range(16)) result.bit(15 - index) = source.bit(bits[index]);
return result;
}
static auto decryptCmcMusicDescramble(u32 address, u16 key) -> u32 {
static constexpr u8 positions[8][16] = {
{ 4, 5, 9, 6,13,11,12, 0, 8, 3, 2, 1, 7,10,14,15},
{12, 6, 0,10,14, 4,13, 5, 3, 2, 9,15,11, 8, 1, 7},
{ 9,12,11,13, 5, 2, 0, 4, 1,15, 7,10, 3,14, 6, 8},
{ 5,14,12, 1,10, 0, 6,13, 7,11, 4, 3, 9,15, 8, 2},
{11, 0, 2, 5,12, 7, 4, 9,10, 8, 3,14,15, 6,13, 1},
{13,10, 5, 8, 1,12,14, 6, 2, 9, 0, 7, 4, 3,15,11},
{ 2, 4, 7,12, 0, 3, 9,11,14, 1,15, 6, 8,13, 5,10},
{ 6, 1,13,15, 8, 5,10,14,11, 4,12, 2, 0, 7, 3, 9},
};
int block = (address >> 16) & 7;
int aux = address & 0xffff;
aux ^= reorderCmcMusic(key, {12,0,2,4,8,15,7,13,10,1,3,6,11,9,14,5});
aux = reorderCmcMusic(aux, positions[block]);
aux ^= CmcTables::Music::address0To7Xor[(aux >> 8) & 0xff];
aux ^= CmcTables::Music::address8To15Xor[aux & 0xff] << 8;
aux = reorderCmcMusic(aux, {7,15,14,6,5,13,12,4,11,3,10,2,9,1,8,0});
return (block << 16) | aux;
}
struct Cmc42 : Rom {
u8 cmcKey;
CmcContext context;
Cmc42(Cartridge& cartridge, u8 key) : Rom(cartridge), cmcKey(key) {
context.type0High = CmcTables::Cmc42::type0High;
context.type0Low = CmcTables::Cmc42::type0Low;
context.type1High = CmcTables::Cmc42::type1High;
context.type1Low = CmcTables::Cmc42::type1Low;
context.address8To15Xor1 = CmcTables::Cmc42::address8To15Xor1;
context.address8To15Xor2 = CmcTables::Cmc42::address8To15Xor2;
context.address16To23Xor1 = CmcTables::Cmc42::address16To23Xor1;
context.address16To23Xor2 = CmcTables::Cmc42::address16To23Xor2;
context.address0To7Xor = CmcTables::Cmc42::address0To7Xor;
}
auto decryptCromByte(n32 address) -> n8 {
u32 wordAddress = address / 4;
u32 byteIndex = address & 3;
int sourceAddress = wordAddress;
sourceAddress ^= cmcKey;
sourceAddress ^= context.address8To15Xor1[(sourceAddress >> 16) & 0xff] << 8;
sourceAddress ^= context.address8To15Xor2[sourceAddress & 0xff] << 8;
sourceAddress ^= context.address16To23Xor1[sourceAddress & 0xff] << 16;
sourceAddress ^= context.address16To23Xor2[(sourceAddress >> 8) & 0xff] << 16;
sourceAddress ^= context.address0To7Xor[(sourceAddress >> 8) & 0xff];
u32 cromSize = crom.size();
if(cromSize == 0x3000000) {
if(wordAddress < 0x2000000 / 4) sourceAddress &= (0x2000000 / 4) - 1;
else sourceAddress = 0x2000000 / 4 + (sourceAddress & ((0x1000000 / 4) - 1));
} else if(cromSize == 0x6000000) {
if(wordAddress < 0x4000000 / 4) sourceAddress &= (0x4000000 / 4) - 1;
else sourceAddress = 0x4000000 / 4 + (sourceAddress & ((0x1000000 / 4) - 1));
} else {
sourceAddress &= (cromSize / 4) - 1;
}
u8 source0 = crom[sourceAddress * 4 + 0];
u8 source1 = crom[sourceAddress * 4 + 1];
u8 source2 = crom[sourceAddress * 4 + 2];
u8 source3 = crom[sourceAddress * 4 + 3];
u8 decoded[4];
decryptCmcGraphicsInternal(&decoded[0], &decoded[3], source0, source3,
context.type0High, context.type0Low, context.type1High, context, sourceAddress, (sourceAddress >> 8) & 1);
decryptCmcGraphicsInternal(&decoded[1], &decoded[2], source1, source2,
context.type0Low, context.type0High, context.type1Low, context, sourceAddress,
((sourceAddress >> 16) ^ context.address16To23Xor2[(sourceAddress >> 8) & 0xff]) & 1);
return decoded[byteIndex];
}
auto readC(n32 address) -> n8 override {
return decryptCromByte(address);
}
auto readS(n32 address) -> n8 override {
u32 cromSize = crom.size();
u32 sromSize = srom.size();
u32 i = address;
u32 cromAddr = (cromSize - sromSize) + ((i & ~0x1f) + ((i & 7) << 2) + ((~i & 8) >> 2) + ((i & 0x10) >> 4));
return decryptCromByte(cromAddr);
}
};
struct Cmc50 : Cmc42 {
u16 musicKey;
u32 fixBank;
Cmc50(Cartridge& cartridge, u8 key, u32 fixBank = 0) : Cmc42(cartridge, key), fixBank(fixBank) {
context.type0High = CmcTables::Cmc50::type0High;
context.type0Low = CmcTables::Cmc50::type0Low;
context.type1High = CmcTables::Cmc50::type1High;
context.type1Low = CmcTables::Cmc50::type1Low;
context.address8To15Xor1 = CmcTables::Cmc50::address8To15Xor1;
context.address8To15Xor2 = CmcTables::Cmc50::address8To15Xor2;
context.address16To23Xor1 = CmcTables::Cmc50::address16To23Xor1;
context.address16To23Xor2 = CmcTables::Cmc50::address16To23Xor2;
context.address0To7Xor = CmcTables::Cmc50::address0To7Xor;
}
auto load() -> void override {
Rom::load();
musicKey = 0;
for(u32 i = 0; i < 0x10000; i++) musicKey += mrom[i];
}
auto fixBankType() -> u32 override { return srom.size() > 0x20000 ? fixBank : 0; }
auto readM(n32 address) -> n8 override {
return mrom[decryptCmcMusicDescramble(address, musicKey)];
}
};
+64
View File
@@ -0,0 +1,64 @@
//PRO-CT0 (ALPHA-8921 / SNK-9201)
struct ProCT0 {
auto clock(n1 load, n1 h, n32 c) -> void {
if(load) {
sr = c;
} else if(h) {
sr = sr.bit(24,29) << 26 | sr.bit(16,21) << 18 | sr.bit(8,13) << 10 | sr.bit(0,5) << 2;
} else {
sr = sr.bit(26,31) << 24 | sr.bit(18,23) << 16 | sr.bit(10,15) << 8 | sr.bit(2,7);
}
}
auto output(n1 even, n1 h) const -> std::pair<n4, n4> {
n4 gad, gbd;
if(h) {
gbd = sr.bit(30) << 3 | sr.bit(22) << 2 | sr.bit(14) << 1 | sr.bit(6);
gad = sr.bit(31) << 3 | sr.bit(23) << 2 | sr.bit(15) << 1 | sr.bit(7);
} else {
gbd = sr.bit(25) << 3 | sr.bit(17) << 2 | sr.bit(9) << 1 | sr.bit(1);
gad = sr.bit(24) << 3 | sr.bit(16) << 2 | sr.bit(8) << 1 | sr.bit(0);
}
if(even) std::swap(gad, gbd);
return {gad, gbd};
}
n32 sr;
};
struct FatFury2 : Rom {
using Rom::Rom;
static auto interleave(n16 value) -> n16 {
static const u8 bits[16] = {15,11,14,10,13,9,12,8,7,3,6,2,5,1,4,0};
return smaSwap(value, bits, 16);
}
auto readP(n1 upper, n1 lower, n24 address, n16 data) -> n16 override {
if(address <= 0x0fffff) return prom[address >> 1];
if(address >= 0x200000 && address <= 0x2fffff) {
auto [gad, gbd] = proct0.output(address.bit(2), address.bit(3));
return gbd.bit(0,1) << 6 | gbd.bit(2,3) << 4 | gad.bit(0,1) << 2 | gad.bit(2,3);
}
return data;
}
auto writeP(n1 upper, n1 lower, n24 address, n16 data) -> void override {
if(address < 0x200000 || address > 0x2fffff) return;
n32 c = interleave(address.bit(4,19)) << 16 | interleave(data);
proct0.clock(address.bit(1), address.bit(3), c);
}
auto power() -> void override {
Rom::power();
proct0 = {};
}
auto serialize(serializer& s) -> void override {
Rom::serialize(s);
s(proct0.sr);
}
ProCT0 proct0;
};
+6 -44
View File
@@ -1,31 +1,15 @@
struct JockeyGP : Interface {
using Interface::Interface;
Memory::Readable<n16> prom;
Memory::Readable<n8 > mrom;
Memory::Readable<n8 > crom;
Memory::Readable<n8 > srom;
Memory::Readable<n8 > vromA;
Memory::Readable<n8 > vromB;
struct JockeyGP : Cmc50 {
Memory::Writable<n16> ram;
JockeyGP(Cartridge& cartridge) : Cmc50(cartridge, 0xac) {}
auto load() -> void override {
Interface::load(prom, "program.rom");
Interface::load(mrom, "music.rom");
Interface::load(crom, "character.rom");
Interface::load(srom, "static.rom");
Interface::load(vromA, "voice-a.rom");
Interface::load(vromB, "voice-b.rom");
Cmc50::load();
ram.allocate(0x100000 >> 1);
}
auto unload() -> void override {
prom.reset();
mrom.reset();
crom.reset();
srom.reset();
vromA.reset();
vromB.reset();
Cmc50::unload();
ram.reset();
}
@@ -39,31 +23,9 @@ struct JockeyGP : Interface {
if(lower && address >= 0x200000 && address <= 0x2fffff) ram[address >> 1] = data;
}
auto readM(n32 address) -> n8 override {
return mrom[address];
}
auto readC(n32 address) -> n8 override {
return crom[address];
}
auto readS(n32 address) -> n8 override {
return srom[address];
}
auto readVA(n32 address) -> n8 override {
return vromA[address];
}
auto readVB(n32 address) -> n8 override {
return vromB ? vromB[address] : vromA[address];
}
auto power() -> void override {
}
auto power() -> void override {}
auto serialize(serializer& s) -> void override {
s(ram);
}
};
Loaded 100 of 284 files, more files were not shown because too many files have changed in this diff. Show more