Compare commits

...
300 Commits
Author SHA1 Message Date
remutro 0aafd85789 Fix build warning 2026-05-28 13:42:30 +01:00
Luke Usher 67b0bf7aac ruby: improve SDL audio sync on non-stereo devices
Use the application stream format rather than the physical device format.

Some outputs, such as HDMI/TV devices with Dolby processing enabled,
may expose a multichannel device format even though ares is submitting
stereo float audio to SDL. Using the device format for queue sizing and
drain timing could make the blocking sync path sleep for the wrong
amount of time, causing the emulator to run too slowly.

General cleanup:
 - avoid heap allocation for every audio packet
 - add proper failure checks
 - correctly teardown SDL_Audio on shutdown
2026-05-28 08:47:04 +01:00
remutro 11d887c99c Fix aspect correction menu radio items in GTK 2026-05-28 08:10:27 +01:00
Luke Usher 7c03fb208f desktop-ui: add tape position slider 2026-05-25 16:23:20 +01:00
Luke Usher b33edf5f90 sg/fc/msx/spec: update serialization for tapes 2026-05-25 16:16:05 +01:00
Luke Usher 83369fd40f sg: add tape support to sc-3000 2026-05-25 16:11:50 +01:00
Luke Usher 6ee42f25b4 msx: hook up tape recording to tape manager 2026-05-25 15:39:46 +01:00
Luke Usher fe388a7fcc desktop-ui: fix tape recording button attribute lookup 2026-05-25 15:33:23 +01:00
Luke Usher c7d667fa07 ruby: fix keyboard mappings on macOS by adding missing left/right modifier keys 2026-05-25 11:36:27 +01:00
Luke Usher cc0e405780 msx: use the 'new' tape system rather than hard-coding the tape deck, add support for tsx/tzx and cas.
also prevent creating a .wav when not writing
2026-05-25 10:07:32 +01:00
Luke Usher c21adc7b57 spec: use the 'new' tape system rather than hard-coding the tape deck 2026-05-25 09:25:10 +01:00
Luke Usher 232f85e39c desktop-ui: move dynamic scale options to a vector rather than a grouo
Fixes being able to select multiple scaling-related options simultaneously
2026-05-24 23:40:30 +01:00
Luke Usher c951652efb desktop-ui: fix broken virtualpad overrides 2026-05-24 23:02:26 +01:00
Luke Usher f605d52032 desktop-ui: replace multiplier sizing with viewport sizing and re-add manual integer scaling ratios
Replace the old fixed multiplier window sizing model with configurable
viewport sizing.

Window sizes are now stored as target viewport dimensions rather than
being derived from the emulated screen. Standard presets have been
provided, as well as the option to specify a custom size.

Adaptive sizing has been reworked to treat the window size as target
bounds, the actual window will dynamically grow or shrink within this
boundary to preserve the correct aspect ratio.

Oversized window recovery has been changed to respect aspect ratio
and available workspace rather than a hardcoded reducton from the
monitors geometry.
2026-05-24 20:29:39 +01:00
Luke Usher bdd585a563 hiro: cleanup some macOS compile warnings and deprecation notices 2026-05-24 17:57:20 +01:00
Luke Usher 54fc194182 desktop-ui: persist SDL joypad mappings by GUID slot
Add an optional stable text identifier to HID devices and use it for
persisted input mappings when available.

SDL joypads now expose a GUID/slot identifier, so newly saved controller
mappings no longer depend on SDL serial or device path values that can
change across disconnects, reconnects, or app launches. This keeps
multiplayer controllers distinguishable by assigning slots per matching
GUID while avoiding unstable path-derived IDs.

The input mapping parser remains backward compatible with existing
numeric 0x<deviceID>/<group>/<input> mappings. Drivers that do not set a
stable identifier continue to save and load mappings using the existing
numeric HID device ID format.
2026-05-24 17:38:35 +01:00
user 4c39ca23eb gba: skip rendering sprites that are offscreen horizontally 2026-05-24 17:02:16 +01:00
user 8ea87c75e4 gba: implement OAM stage timings for OBJ engine 2026-05-24 17:02:16 +01:00
Morilli 7f0f91b3da sfc: fix ppu horizontal offset in high-resolution offset-per-tile mode 2026-05-23 16:00:34 +01:00
Luke Usher 164f5648bf ruby: stabilize joypad identity and duplicate device handling for the SDL driver 2026-05-21 14:45:20 +01:00
Luke Usher 19cce7dbba ld: fix LaserActive video seeks after CD-ROM refactor 2026-05-20 20:05:50 +01:00
Giovanni Bajo 765c0d4181 n64: fix partial load/stores in little-endian mode
Uncovered by new tests in n64-systemtest. The JIT wasn't tested because
little-endian is only supported in user-mode for now (RE=1), where
memory accesses must go through TLBs so they currently are not JIT'd.
Anyway the fix should be correct if we eventually uncover full
little-endian mode (with BE=0).
2026-05-18 19:44:25 +01:00
Luke Usher fbc8f22821 desktop-ui: default overscan to true
More natural window aspect by default, especially in PAL regions.
2026-05-17 19:41:04 +01:00
Luke Usher 14f4fc0173 desktop-ui: add direct input mapping mode with VirtualPad fallback
(AKA core-specific input mappings!)

Also adds support for direct input devices that bypass VirtualPad entirely, currently used for Mahjong
2026-05-17 19:40:33 +01:00
user 6920622b7e gba: add OAM to memory viewer 2026-05-17 19:17:02 +01:00
Giovanni Bajo c42bb2626d n64: implement VR4300 reverse-endian mode
This is a mode available only in the user mode, activated when RE=0. It
facilitates running MIPS binary code that was build for a little-endian
architecture, assuming that the code/data was 8-byte swapped in memory
before running it.

It passes the new RE tests in n64-systemtest
2026-05-17 19:16:48 +01:00
Morilli d45be3297f sfc: Improve autojoypad polling accuracy 2026-05-11 10:57:30 +01:00
remutro 54adf82264 Update libchdr to version 0.3.0 2026-05-11 10:56:14 +01:00
remutro 7d1bbf7c04 Update libchdr to version 0.3.0 2026-05-11 10:56:14 +01:00
remutro d17ae2f36d Update libchdr to version 0.3.0 2026-05-11 10:56:14 +01:00
remutro b9f8347d57 Update libchdr to version 0.3.0 2026-05-11 10:56:14 +01:00
Giovanni Bajo 41af52ec27 n64: improve CPU JIT documentation 2026-05-11 10:53:35 +01:00
Giovanni Bajo 87daf87fbb n64: decrease a bit jit interleaving (unfreeze conker) 2026-05-11 10:53:35 +01:00
Giovanni Bajo 2a5b9fe74e n64: fix interrupt generation to never happen mid-instruction 2026-05-11 10:53:35 +01:00
Giovanni Bajo bb91ff1136 n64: fix a few cases where interrupt latency was still higher than expected 2026-05-11 10:53:35 +01:00
Giovanni Bajo 5d7496c6f1 n64: change CPU JIT flush message 2026-05-11 10:53:35 +01:00
Giovanni Bajo 18692c3ced n64: increase JIT interleaving and add more forced syncs on MMIOs 2026-05-11 10:53:35 +01:00
Giovanni Bajo bc3af76716 n64: reduce interrupt latency to zero in JIT 2026-05-11 10:53:35 +01:00
Giovanni Bajo 03dd55bbda n64: in CPU JIT, correctly declare 6 scratch registers (and don't use 7th) 2026-05-11 10:53:35 +01:00
Giovanni Bajo ffab36ed99 n64: in CPU JIT, verify internal assumptions on block links 2026-05-11 10:53:35 +01:00
Giovanni Bajo 8ad8803765 n64: in CPU JIT, implement LL/SC/LLD/SCD 2026-05-11 10:53:35 +01:00
Giovanni Bajo 3198e0e88e n64: in CPU JIT, rewrite branches to use branchless code 2026-05-11 10:53:35 +01:00
Giovanni Bajo 999d858fa7 n64: in CPU JIT, extend blocks also beyond jump and links 2026-05-11 10:53:35 +01:00
Giovanni Bajo a752515d2d n64: bump serializer 2026-05-11 10:53:35 +01:00
Giovanni Bajo 2460b01b27 n64: in CPU jit, synchronize CPU/RSP on status and semaphore I/O 2026-05-11 10:53:35 +01:00
Giovanni Bajo 2af310528b n64: in CPU JIT, fix LWR.
Also fix it in interpreter to align to newly tested corner case
(non sign-extended input).
2026-05-11 10:53:35 +01:00
Giovanni Bajo a1be16873f n64: in CPU JIT, compute homebrew dcache dirty mask after stores
Avoid keeping the dirty-byte mask live across store fast paths in
jitMemoryOpcode. Compute it at the end from Rs+i16 so temporary
register clobbers (eg. SDC1/SWL/SWR) cannot corrupt dirty tracking and
lead to stale cache data.
2026-05-11 10:53:35 +01:00
Giovanni Bajo 8b4f1fa16e n64: CPU now has configurable interleaving with the rest of the hardware 2026-05-11 10:53:35 +01:00
Giovanni Bajo 882fc4df1f n64: split recompiler in three files 2026-05-11 10:53:35 +01:00
Giovanni Bajo 997077527b n64: in CPU JIT, cleanup to remove redundant structure fields 2026-05-11 10:53:35 +01:00
Giovanni Bajo dd834d2c5d n64: revisit integration between JIT and gdb to improve performance 2026-05-11 10:53:35 +01:00
Giovanni Bajo 50b9786587 n64: make sure events inserted in the queue affect JIT timings 2026-05-11 10:53:35 +01:00
Giovanni Bajo fbb6e72e02 n64: in CPU JIT, fix a bug in TLB handling (eg: Linux)
Now that the JIT can hardcode PC in generated code, each block is valid
for a single vaddr. It can't be reused.
2026-05-11 10:53:35 +01:00
Giovanni Bajo c152ef5639 n64: in CPU JIT, simplify decoder removing dead code 2026-05-11 10:53:35 +01:00
Giovanni Bajo 49af8ab6a2 n64: in CPU JIT, blocks now extend through branches
Terminals are now only hard jumps and the special opcodes that change
state key, timers, etc.

Internal branch targets in each block are also registered as JIT
entrypoints.

Opportunistic branch linking is now removed as not useful anymore.
2026-05-11 10:53:35 +01:00
Giovanni Bajo 68134837d4 n64: avoid excessive JIT trashing by reducing the 4K section granularity 2026-05-11 10:53:35 +01:00
Giovanni Bajo bb75c2f058 n64: in CPU JIT, do not emit pipeline prologue for linear instructions 2026-05-11 10:53:35 +01:00
Giovanni Bajo 105e8da924 n64: in CPU JIT, make epilogue code conditional in fast paths 2026-05-11 10:53:35 +01:00
Giovanni Bajo b0de6c11d2 n64: in CPU JIT, return pipeline information from emit functions 2026-05-11 10:53:35 +01:00
Giovanni Bajo b89a0bcce1 n64: in CPU JIT, add SDL/SDR/SWL/SWL/SWC1/SDC1 2026-05-11 10:53:35 +01:00
Giovanni Bajo 04f8b9cb4d n64: implement LWL, LWR, LDL, LDR, LWC1, LDC1 in JIT 2026-05-11 10:53:35 +01:00
Giovanni Bajo c139a4d4ad n64: in CPU JIT, use JIT-time PC for branches as much as possible 2026-05-11 10:53:35 +01:00
Giovanni Bajo 28f05b0297 n64: in FPU JIT, optimize comparisons 2026-05-11 10:53:35 +01:00
Giovanni Bajo 39b5d798b9 n64: in FPU JIT, implement compare opcodes (first draft) 2026-05-11 10:53:35 +01:00
Giovanni Bajo 7e5841d84a n64: in FPU JIT, add MOV.S/MOV.D implementation 2026-05-11 10:53:35 +01:00
Giovanni Bajo 8c3961cd6e n64: in FPU JIT, add comments and slight tweaks to underflow fixup 2026-05-11 10:53:35 +01:00
Giovanni Bajo e6517f5f57 n64: in FPU JIT, reduce input checks required for conversion opcodes 2026-05-11 10:53:35 +01:00
Giovanni Bajo cd604bf619 n64: in FPU JIT, reduce fallbacks to C++ for conversions in most occurrences 2026-05-11 10:53:35 +01:00
Giovanni Bajo 85008a663e n64: in FPU JIT, simplify a bit conversion opcodes 2026-05-11 10:53:35 +01:00
Giovanni Bajo 8031dcd3f2 n64: fix a benign typo in FCVT_D_L
It went unnoticed because it's basically re-running the same conversion
of the CHECK_FPE line, and probably rounding modes don't matter enough.
2026-05-11 10:53:35 +01:00
Giovanni Bajo f0b8a36b6a n64: implement float/int conversions in FPU JIT 2026-05-11 10:53:35 +01:00
Giovanni Bajo 36aed53124 n64: in FPU JIT, implement ALU double opcodes 2026-05-11 10:53:35 +01:00
Giovanni Bajo 1d8c89a50f n64: refactoring and cleanup of FPU JIT opcode emission 2026-05-11 10:53:35 +01:00
Giovanni Bajo 60007e8853 n64: in FPU JIT, implement FADD.S and FSUB.S 2026-05-11 10:53:35 +01:00
Giovanni Bajo 3542371b76 n64: in FPU JIT, unity 1/2 arity codepaths 2026-05-11 10:53:35 +01:00
Giovanni Bajo a8882a1d89 n64: in FPU JIT, add support for MUL.S and DIV.S 2026-05-11 10:53:35 +01:00
Giovanni Bajo 86fa83fc2b n64: in FPU JIT, implement passthrough of flag bits from host processor 2026-05-11 10:53:35 +01:00
Giovanni Bajo 71ddda52b5 n64: simplify FPU JIT by moving some code into nall/recompiler 2026-05-11 10:53:35 +01:00
Giovanni Bajo be0504e1c2 n64: start implementation of FPU JIT for amd64 too 2026-05-11 10:53:35 +01:00
Giovanni Bajo a34a6ab1df n64: implement initial FPU JIT, for ARM64 2026-05-11 10:53:35 +01:00
Giovanni Bajo 515cb06729 n64: in CPU JIT, remove the deferred pc/nextpc/pipeline materialization
While provably useful, the implementation is completely confusing. We
need to rework it from scratch at some point. The way it is written
now, it impedes further progress in other areas.
2026-05-11 10:53:35 +01:00
Giovanni Bajo 2d852d6a9b n64: refactor to remove obsolete decoder JIT flags 2026-05-11 10:53:35 +01:00
Giovanni Bajo 5b8abb5717 n64: in JIT, add optimizations for accesses via $sp/$gp 2026-05-11 10:53:35 +01:00
Giovanni Bajo 496409c199 n64: implement also memory store opcodes in JIT 2026-05-11 10:53:35 +01:00
Giovanni Bajo 9e6be60b9e n64: implement all aligned load opcodes in JIT 2026-05-11 10:53:35 +01:00
Giovanni Bajo fdbd036bd1 n64: in JIT, remove confusing callf macro 2026-05-11 10:53:35 +01:00
Giovanni Bajo f241c4095d n64: optimize dcache to use a single word with both tag and valid bit 2026-05-11 10:53:35 +01:00
Giovanni Bajo 094454e52f n64: add JIT for LW as a first experiment 2026-05-11 10:53:35 +01:00
Giovanni Bajo dabeb68387 n64 cpu: inline MFC1/MTC1/DMFC1/DMTC1 in the JIT when COP1 is enabled.
Emit loads and stores directly against fpu state via sreg(2), using the same
full- versus half-mode register layout as the interpreter (word selection and
pairing). COProcessor-unusable blocks still call the C++ helpers. Skip GPR
writes when MFC1/DMFC1 targets $zero. FPU word offsets use CPU::r64 in offsetof
to avoid colliding with the host Real type at file scope.
2026-05-11 10:53:35 +01:00
Giovanni Bajo cc82697f00 n64: in CPU JIT, inline icache management 2026-05-11 10:53:35 +01:00
Giovanni Bajo e3f4d6dc0d nall: add mov128 and mem0 for absolute addressing 2026-05-11 10:53:35 +01:00
Giovanni Bajo b698206d07 n64: in CPU JIT, inline fastpath of icache hit check 2026-05-11 10:53:35 +01:00
Giovanni Bajo 7451ccbc98 n64: in CPU JIT, simplify branch link management 2026-05-11 10:53:35 +01:00
Giovanni Bajo 6448413adf n64: JIT multiply/divide SPECIAL opcodes via SLJIT
Add generic LMUL and DIVMOD helpers (32/64, signed/unsigned) and emit fast paths for MULT/MULTU, DIV/DIVU, DMULT/DMULTU, DDIV/DDIVU. Reserved-instruction and divide-by-zero cases stay on interpreter slow paths; DDIV avoids host INT64_MIN/-1 idiv overflow. Decoder no longer marks these opcodes as JitUseCallf or JitAddsExtraCyclesInternally.
2026-05-11 10:53:35 +01:00
Giovanni Bajo d91f838e3b n64: lazily emit callf setup in CPU JIT
Move callf synchronization to the paths that actually emit helper calls, and keep slow-path callf state restoration explicit. Rename jitMayCallf to jitUseCallf so the flag name matches its eager-call semantics.
2026-05-11 10:53:35 +01:00
Giovanni Bajo fc820aba6b n64: centralize JIT callf setup
Route opcode helper calls through a shared setup path that materializes PC and flushes deferred state on demand, so callf overhead is emitted where calls actually occur.
2026-05-11 10:53:35 +01:00
Giovanni Bajo a61dba8ec8 n64: JIT 64-bit shifts and trap opcodes
Move trap exception cases to deferred slow paths and generate direct JIT code for the remaining 64-bit shift immediates, keeping reserved-instruction handling and R0 writeback behavior on the fast path.
2026-05-11 10:53:35 +01:00
Giovanni Bajo 025cde8961 n64: JIT more 64-bit integer ALU operations
Expand CPU JIT codegen for DADDIU, DSLLV/DSRLV/DSRAV, DADDU, DSUB, and DSUBU while preserving exact overflow and reserved-instruction behavior through helper fallbacks. Factor 64-bit reserved-instruction gating into a shared helper and collapse duplicated slow-path conditions to keep hot paths compact.
2026-05-11 10:53:35 +01:00
Giovanni Bajo 2f8659a375 n64: optimize CPU JIT ALU slow paths
Move ADD/ADDI/DADD/DADDI hot paths to direct codegen with deferred slow-path helpers so overflow and reserved-instruction behavior stay exact without polluting the hot block body. Centralize state-key bit semantics in a typed accessor wrapper so JIT-time folding no longer depends on hardcoded bit masks.
2026-05-11 10:53:35 +01:00
Giovanni Bajo f1eff7d2f0 n64: gate JIT pipeline state machinery on demand
Emit state test/update and endblock jumps only when branch/helper pipeline semantics are active, removing these control-flow overhead ops from the linear hot path.
2026-05-11 10:53:35 +01:00
Giovanni Bajo 0d3caa546c n64: defer nextpc materialization in JIT hot path
Accumulate linear nextpc advances and flush only when current PC is needed, removing per-opcode nextpc materialization on the fast path. Also rematerialize helper call PC directly from vaddr in the linear non-delay-slot case.
2026-05-11 10:53:35 +01:00
Giovanni Bajo e7444fd852 n64: lazily commit ipu.pc in JIT loop
Commit architectural PC only for branch/helper/terminal paths while preserving exception PC correctness for helper calls and delay slots. This removes unconditional per-opcode ipu.pc stores on the linear hot path.
2026-05-11 10:53:35 +01:00
Giovanni Bajo 55ac593102 n64: materialize CPU pipeline pc only when needed
Keep pipeline pc writes on branch/helper paths, and commit linear opcode ipu.pc from nextpc-4 to reduce per-opcode JIT overhead while preserving n64-systemtest behavior.
2026-05-11 10:53:35 +01:00
Giovanni Bajo 4ed8a7792c n64: flush deferred CPU cycles only on branch/helper opcodes
Skip per-opcode deferred-cycle flushes for straight-line instructions while keeping EndBlock checks unchanged, reducing hot-loop helper-call overhead without regressing system tests.
2026-05-11 10:53:35 +01:00
Giovanni Bajo 161f01fa0d n64: restore unconditional CPU JIT endblock checks
Revert conditional EndBlock test and epilogue gating in the CPU JIT loop to preserve control-flow behavior and keep the system tests passing.
2026-05-11 10:53:35 +01:00
Giovanni Bajo 606151f79d n64: add virtual PC materialization in CPU JIT loop 2026-05-11 10:53:35 +01:00
Giovanni Bajo b00b1778d1 n64: avoid unconditional JIT step flush in hot loop
Only flush deferred cycles at the per-opcode boundary when an EndBlock check is needed, reducing helper-call overhead on straight-line JIT code.
2026-05-11 10:53:35 +01:00
Giovanni Bajo dc4af1ddad n64: gate JIT EndBlock checks on branch and helper ops
Skip emitting EndBlock test/jump in straight-line instructions where state cannot request block termination, reducing hot-loop overhead while preserving control-flow correctness.
2026-05-11 10:53:35 +01:00
Giovanni Bajo 6b88fdfbbf n64: remove CPU JIT metrics instrumentation
Drop JIT metrics counters and reporting from the CPU recompiler to keep the hot-path code easier to read while preserving runtime behavior. This also keeps the architectural state commit path in emit() minimal by removing duplicated commit emission.
2026-05-11 10:53:35 +01:00
Giovanni Bajo edf712f2f6 n64: extend JIT branch linking to dual targets
Model taken and not-taken link edges per block so conditional branches can be linked by final PC, and add finer diagnostics for no-direct-link causes. Also inline BC1 JIT branch handling with deterministic cause clearing layout checks to reduce unsupported FPU branch cases without regressing exception behavior.
2026-05-11 10:53:35 +01:00
Giovanni Bajo 70f1cf07c5 n64: add safe intra-section JIT block linking
Link JIT blocks lazily within sections with state-key matching and pending backpatches to cut dispatcher overhead. Gate linked jumps on dirty sections, clock/queue budget checks, and unsafe delay slots while adding detailed diagnostics on link misses and candidate coverage.
2026-05-11 10:53:35 +01:00
Giovanni Bajo 6d6c5d5fad n64: refactor CPU JIT to introduce 4K sections and state key 2026-05-11 10:53:35 +01:00
Giovanni Bajo 3a44a460bd n64: add JIT documentation 2026-05-11 10:53:35 +01:00
Giovanni Bajo 8655d4027f n64: convert remaining CPU branch opcodes to JIT
Convert all remaining integer branch and branch-likely opcodes to direct JIT emission and update decoder JIT flags, while intentionally keeping BC1 on the callf path.
2026-05-11 10:53:35 +01:00
Giovanni Bajo 271e545a49 n64: convert four CPU branches to JIT and silence offsetof warnings
Convert BGTZ, JR, BLTZ, and BGEZ to direct JIT branch emission and update decoder Jit flags accordingly, then extend invalid-offsetof suppression across cpu/recompiler.cpp to match the RSP pattern.
2026-05-11 10:53:35 +01:00
Giovanni Bajo 6b97edc9db n64: convert four more CPU branches to JIT
Convert J, JAL, BNE and BLEZ to direct JIT emission and update decoder metadata so these opcodes no longer follow the callf flush path.
2026-05-11 10:53:35 +01:00
Giovanni Bajo e4f4a7d657 n64: convert BEQ to JIT 2026-05-11 10:53:35 +01:00
Giovanni Bajo 861020eeb7 n64: initial cleanup of CPU JIT, introduce opcode decoder 2026-05-11 10:53:35 +01:00
Dragorn421 0dc20693e2 Remove "eat stdin" behavior on exit 2026-05-06 12:39:56 +01:00
Dragorn421 08746292c7 Fixup nall/nall/gdb/Readme.md (fix #1360) 2026-05-04 13:08:55 +01:00
Dragorn421 e2636505e2 Fixup versionconfig.cmake: pass --no-show-signature to the git command for getting the commit date 2026-04-30 10:40:59 +01:00
Dragorn421 2219f48fca emux v1.1: remove dashes from instruction names (#2518)
New emux spec location, for reference: https://n64brew.dev/wiki/Emux
2026-04-29 10:31:26 +02:00
Luke Usher 91c64bf350 mia: refactor identify to support when multiple systems use the same file extension
Update desktop-ui so that it prompts the user for the desired system if multiple results were returned, ignoring hidden systems.
2026-04-28 16:23:02 +01:00
kev4cards 6f6786e04f n64: add comment to fastSUV use with respect to SPV 2026-04-27 18:47:08 +01:00
remutro 7974cabb43 Minor Qt6 on Linux cleanup 2026-04-27 18:45:33 +01:00
remutro a8ae8fe83f Add ISViewer RAM to save states 2026-04-23 21:06:11 +01:00
remutro e7cdf9c046 Update dependencies (post-v147-2) 2026-04-23 18:19:26 +01:00
Giovanni Bajo 93c397dca7 n64: bump serializer 2026-04-23 18:19:14 +01:00
Giovanni Bajo c9f2773685 n64: silence invalid offsetof warnings 2026-04-23 18:19:14 +01:00
Giovanni Bajo 652d653736 n64: RSP JIT cleanup and add emux tracing support
Verbose refactoring to only use decoder.cpp for extracting information
from opcodes (removing isTerminal and the return value of emit functions).

Added tracing to the cache index so that we basically version the JIT
blocks depending on whether it is enabled or not. This allows to
implement support for emux tracing without overhead.
2026-04-23 18:19:14 +01:00
Giovanni Bajo 219dff38ad n64: refactor load/store opcodes to make better use of constant addresses 2026-04-23 18:19:14 +01:00
Giovanni Bajo c5ca614492 n64: add LRV/SRV/SWV to RSP JIT 2026-04-23 18:19:14 +01:00
Giovanni Bajo 0fca58b98e n64: add LTV/STV to RSP JIT 2026-04-23 18:19:14 +01:00
Giovanni Bajo 5d04cd3808 n64: add RSP JIT for LFV/SFV 2026-04-23 18:19:14 +01:00
Giovanni Bajo 59a0a898e9 n64: add LHV/SHV in RSP JIT, and refactor LUV/LPV/SUV/SPV to always use SIMD 2026-04-23 18:19:14 +01:00
Giovanni Bajo a6499c3b49 nall: fix build on msvc and GCC 13 2026-04-23 18:19:14 +01:00
Giovanni Bajo 310b665160 n64: add LDV/SDV RSP JIT opcodes 2026-04-23 18:19:14 +01:00
Giovanni Bajo b96b8ac7da n64: add LSB/LSV/LLV/SSB/SSV/SLV RSP JIT opcodes 2026-04-23 18:19:14 +01:00
Giovanni Bajo d20a0e1b8e n64: add LUV/LPV/SUV/SPV RSP JIT opcodes 2026-04-23 18:19:14 +01:00
Giovanni Bajo 716d417359 n64: use sreg(3) in RSP JIT to hold the DMEM pointer 2026-04-23 18:19:14 +01:00
Giovanni Bajo 1b6893ea49 nall: allow a recompiler to define a different number of saved registers 2026-04-23 18:19:14 +01:00
Giovanni Bajo fef2ab4904 n64: in RSP, add JIT for lqv/sqv
This generates optimized codepaths for lqv and sqv. It also relies on
constant tracking to optimize situations where the base address is
know at JIT time (very common) and we can thus prove its alignment.
2026-04-23 18:19:14 +01:00
Giovanni Bajo cbf9f79e5f n64: add constant tracking in RSP JIT
The JIT now tracks constant registers within each recompiled blocks.
This is currently unused in the generated code, will be useful to
improve code generation for vector load/stores opcodes.
2026-04-23 18:19:14 +01:00
Giovanni Bajo 4c4c5460bc nall: add support for register arguments to callf
This is non trivial because input arguments might mistakenly override
ABI registers. Prevent register argument clobber by resolving move
dependencies and breaking cycles with a temporary register.
2026-04-23 18:19:14 +01:00
Giovanni Bajo 26d5ac3a80 n64: specialize R0 source propagation in RSP JIT
Fold R0-based scalar and branch fast paths into direct immediates/moves so the emitter skips unnecessary register reads and compares.
2026-04-23 18:19:14 +01:00
Giovanni Bajo f71568bad4 n64: annotate RSP op metadata for JIT state handling
Move UsesDmem and MayHalt traits into OpInfo so decoder metadata directly drives recompiler decisions, and simplify the JIT loop by unifying delay-slot state while documenting the current architecture and optimizations.
2026-04-23 18:19:14 +01:00
Giovanni Bajo a4ac042f27 n64: skip R0 writes in RSP JIT paths
Drop the JIT write-sink indirection and skip code emission when the destination is R0, while keeping side-effecting C++ helpers and guarding their writeback to preserve R0 semantics.
2026-04-23 18:19:14 +01:00
Giovanni Bajo 25e248a6c3 n64: centralize RSP JIT misaligned access capability
Define a single architecture capability macro for unaligned scalar memory access and use constexpr slowpath selection helpers for half/word DMEM ops.
2026-04-23 18:19:14 +01:00
Giovanni Bajo f43063803b n64: gate RSP halted checks to relevant opcodes
Emit status.halted exit checks only for instructions that can mutate halt state (BREAK and MTC0 to SP_STATUS), reducing per-opcode overhead in the main JIT loop.
2026-04-23 18:19:14 +01:00
Giovanni Bajo 9e20c21b35 n64: avoid VU preflush in RSP callf gate
Do not classify VU opcodes as callf preflush candidates so deferred clocks are only synchronized for paths that actually require clock-visible C++ calls.
2026-04-23 18:19:14 +01:00
Giovanni Bajo 44ab78ade5 n64: defer RSP JIT clock flushes to slow paths
Track deferred clock progress for slow-path entries so memory fast paths avoid callf-triggered flushes while preserving correct timing when deferred fallbacks execute.
2026-04-23 18:19:14 +01:00
Giovanni Bajo c88d2eba79 n64: JIT scalar RSP DMEM ops with deferred fallbacks
Emit direct fast paths for LB/LBU/LH/LHU/LWU/SB/SH/SW and keep unaligned half/word cases in deferred slow paths, reducing callf overhead while preserving interpreter behavior.
2026-04-23 18:19:14 +01:00
Giovanni Bajo b1b51d3070 n64: re-emit RSP JIT slow paths via emitEXECUTE
Keep deferred slow-path records minimal and re-enter opcode emission in slow mode, reducing per-opcode slow-path boilerplate while preserving the compact LW fast path.
2026-04-23 18:19:14 +01:00
Giovanni Bajo dc8ac7d3dc n64: switch RSP DMEM accesses to big-endian semantics
Implement DMEM read/write paths with MSB-first word semantics and byte-accurate unaligned wrapping. Adjust RSP DMA DMEM transfers to use word paths consistent with the new DMEM representation while preserving IMEM behavior.
2026-04-23 18:19:14 +01:00
Giovanni Bajo 3feb849778 n64: call branch epilogue directly in RSP delay slots
Replace the delay-slot call to instructionEpilogue<1> with explicit clock flush plus instructionBranchEpilogue, removing wrapper overhead while preserving delay-slot branch semantics.
2026-04-23 18:19:14 +01:00
Giovanni Bajo bc1009a5f1 n64: inline RSP JIT step clock update
Replace the JIT call to RSP::step with a direct thread clock increment while keeping deferred clock flushing behavior, reducing hot-path call overhead without changing interpreter behavior.
2026-04-23 18:19:14 +01:00
Giovanni Bajo 1f06bbc156 n64: batch fixed RSP JIT clocks between call sites
Defer fixed clock stepping across pure inlined opcodes and flush before callf paths, delay-slot handling, halts, and block exit to preserve observable timing while reducing hot-path step calls.
2026-04-23 18:19:14 +01:00
Giovanni Bajo 09bb1cbf3e n64: fold RSP conditional branch state masking writes
Emit the DelaySlot|EndBlock mask directly into branch state destinations for conditional branches, removing temporary state move instructions in the hot path.
2026-04-23 18:19:14 +01:00
Giovanni Bajo b66752b4fc n64: write RSP branch state directly outside delay slots
Emit branch state updates directly in non-delay-slot branch paths and avoid copying nstate into state in the non-delay-slot epilogue, reducing branch-state traffic in the hot path.
2026-04-23 18:19:14 +01:00
Giovanni Bajo 97b9490845 n64: route RSP JIT branch targets by delay-slot phase
Pass delay-slot phase into EXECUTE/SPECIAL/REGIMM emitters so branch-like ops can write the active destination directly and remove redundant delay-slot branch.begin state traffic.
2026-04-23 18:19:14 +01:00
Giovanni Bajo 6e7cf80f6e n64: virtualize RSP JIT block PC progression
Use compile-time next-PC values for non-delay-slot commits and avoid per-op branch.begin updates outside delay slots, reducing hot-path branch state traffic while preserving delay-slot behavior.
2026-04-23 18:19:14 +01:00
Giovanni Bajo d250931aae n64: simplify RSP JIT conditional target selection
Use a compact xor-mask select for conditional branch target/fallthrough selection, reducing generated hot-path instructions while preserving existing branch semantics.
2026-04-23 18:19:14 +01:00
Giovanni Bajo 7163fb4dbb n64: use virtual fallthrough PC in RSP conditional branches
Replace runtime fallthrough reads from branch.nextpc with compile-time PC+8 immediates for REGIMM and EXECUTE conditional branches to remove extra hot-path memory dependencies.
2026-04-23 18:19:14 +01:00
Giovanni Bajo 838f4c6ad2 n64: stabilize RSP virtual-PC branch targets in JIT
Include block base address in the RSP JIT hash and use compile-time branch targets for conditional branches so generated code stays correct while removing extra runtime target math.
2026-04-23 18:19:14 +01:00
Giovanni Bajo c5e5c38a8a n64: use compile-time PC for RSP link writes
Pass the current block PC into RSP JIT emitters and use compile-time PC+8 immediates for JAL/JALR/BLTZAL/BGEZAL link register writes while keeping existing branch flow unchanged.
2026-04-23 18:19:14 +01:00
Giovanni Bajo 9f98c24394 n64: trim RSP JIT non-branch epilogue overhead
Skip branch state/pc commit work for non-branch non-terminal opcodes and simplify branch begin emission, and fix BLTZAL/BGEZAL when Rs aliases RA by deferring link write until after condition evaluation.
2026-04-23 18:19:14 +01:00
Giovanni Bajo 86765b1706 n64: inline RSP JIT conditional branch opcodes
Emit BEQ/BNE/BLEZ/BGTZ and REGIMM conditional branches directly in JIT to remove per-opcode C++ calls while preserving delay-slot and link-register behavior.
2026-04-23 18:19:14 +01:00
Giovanni Bajo 2585b4667f n64: inline simple RSP JIT jump opcodes
Emit J/JAL/JR/JALR branch updates directly in JIT so these terminal opcodes avoid C++ calls while preserving current delay-slot behavior.
2026-04-23 18:19:14 +01:00
Giovanni Bajo bc4332bbb9 n64: remove temporary RSP epilogue A/B switch
Drop the fastInstructionEpilogue runtime toggle now that the specialized JIT epilogue path is stable. Keep the delay-slot fallback path while making the optimized path the only code path.
2026-04-23 18:19:14 +01:00
Giovanni Bajo a3d75ad5da n64: specialize RSP JIT epilogue by delay-slot position
Choose the fast epilogue path at compile time for non-delay-slot instructions and keep the legacy path only for delay slots. This removes per-opcode branch-state dispatch in the hot path while preserving behavior.
2026-04-23 18:19:14 +01:00
Giovanni Bajo 101ba34f28 n64: inline RSP epilogue slow-path clock accounting
Replace the slow-path JIT epilogue call with direct step/clocksTotal updates and a call to instructionBranchEpilogue, keeping branch exit semantics unchanged. This reduces call overhead while preserving behavior under the fast epilogue toggle.
2026-04-23 18:19:14 +01:00
Giovanni Bajo ece2001360 n64: widen RSP program counter storage
Store RSP IPU PC as 32-bit so JIT word-sized accesses cannot clobber adjacent state. This keeps the fast epilogue path behaviorally stable while preserving 12-bit address masking semantics.
2026-04-23 18:19:14 +01:00
Giovanni Bajo 3e913fda53 n64: add toggled fast RSP instruction epilogue path
Add an A/B switch for a fast JIT epilogue path while preserving the legacy path as fallback, and widen branch pc storage so JIT word-sized accesses are safe. This gives a stable way to compare behavior and performance during further tuning.
2026-04-23 18:19:14 +01:00
Giovanni Bajo ec29392c11 n64: align RSP branch state with CPU pipeline model
Model RSP branch flow with pc/nextpc and latched state/nstate so interpreter and JIT follow the same delay-slot and end-block semantics. This stabilizes branch behavior while keeping stall and dual-issue logic unchanged.
2026-04-23 18:19:14 +01:00
Giovanni Bajo 536fe6964f n64: factor RSP branch epilogue handling
Extract branch-state commit logic into a dedicated helper so we can iterate on JIT epilogue changes from a stable, behavior-preserving baseline.
2026-04-23 18:19:14 +01:00
remutro 18be35d9e0 Fix incorrect setting 2026-04-23 12:49:58 +01:00
remutro c6596bc468 Fix PlayStation extensions 2026-04-23 12:49:58 +01:00
remutro 00bdc72562 Fix MMI extension loading and detection 2026-04-23 12:49:58 +01:00
NovaSquirrel 0394fd90ad snes: update SerializerVersion to v145 2026-04-18 13:47:54 +01:00
NovaSquirrel f27d62f841 snes: fix SGB register mirroring to match hardware
According to fullsnes, SGB only decodes A0-A3,A11-A15,A22
2026-04-18 13:47:54 +01:00
NovaSquirrel 874d32eb42 snes: fix SGB logic for moving to next player
This is supposed to fix #1205
2026-04-18 13:47:54 +01:00
NovaSquirrel 8e10f41188 snes: Fix Super Game Boy $6002 and $700x registers
The Super Game Boy only stores one packet, and packet acknowledgement is done by reading $7000, not $6002
2026-04-18 13:47:54 +01:00
ZengGengSen ad98f53f3b refactor: move reset state from MOS6502 to system-level CPU
Refactor reset handling to align with project architecture by removing the
'resetting' flag from the MOS6502 core. The reset state is now managed via
'resetPending' within the specific A2600 and Famicom CPU implementations.

- MOS6502: Remove resetting flag and internal reset logic from power().
- CPU: Introduce io.resetPending to handle reset triggers in the main loop.
- Debugger: Add interrupt/reset notification support for better tracing.
- Serialization: Update A26 (v140) and FC (v152) versions for state changes.
2026-04-09 13:01:38 +01:00
ZengGengSen 6e0fa73249 fc: add support for Dendy region
- Implement Dendy-specific timings: CPU divider 15, 312 total scanlines, and vblank starting at scanline 291.
- Refactor CPU/APU rate calculation to use a dynamic system divider instead of hardcoded PAL/NTSC checks.
- Add Dendy to the system region enumeration and UI emulator list.
- Handle Dendy-specific hardware differences: disable microphone input and adjust PPU aspect ratio/pixel output offset to match PAL-like characteristics.
- Clean up controller and system unloading logic to prevent memory leaks.
2026-04-08 09:12:51 +01:00
User 722f104fa0 gba: allow re-triggering keypad IRQ when input changes 2026-04-08 09:09:05 +01:00
Giovanni Bajo d55f80d633 n64: in emux, implement xioctl fast/slow
These can be used by testroms to automatically turn on fast forward mode
2026-04-07 08:53:16 +01:00
User 649a29debe gba: fix wave RAM bank selection 2026-04-04 08:56:02 +01:00
Giovanni Bajo dedc446418 n64: in emux, add detect mask for xioctl 2026-04-02 09:35:33 +01:00
Giovanni Bajo 16c5da5dc5 n64: implement emux on RSP
This implements a first subset of the emux extensions on the RSP. The
implementation is similar to the CPU one, and forwards to it things
that have to be identical.
2026-03-31 09:12:22 +01:00
Giovanni Bajo a0f762985e n64: add initial emux support
Emux is a spec for emulator extensions to help homebrew development.
The spec can be found here at the time of writing:
https://hackmd.io/@rasky/r1k7na6Jn

Usage of emux is gated behind the "homebrew development mode" setting.

This commit introduces support for several extensions on the CPU, but
none on the RSP yet.
2026-03-31 09:12:22 +01:00
Giovanni Bajo f76dd51134 n64: introduce RBusDevice for tracking devices accessing the RAM bus
For homebrew profiling features, we need to track what hardware device
is accessing the shared RDRAM. We do have this already for debugging
purposes as a string. This commit changes that to an enum.
2026-03-31 09:12:22 +01:00
Giovanni Bajo 103dd93ae4 ares: add an event to allow a core to request emulator shutdown
Normally, cores shouldn't be able to request the emulator to shut down
on the host computer, but this might be required for some homebrew
development features.
2026-03-31 09:12:22 +01:00
remutro 5f216a7bf5 Add Sega PAC firmware hashes for auto detection 2026-03-30 10:09:08 +01:00
User dfa6ec0c79 gba: improve affine parameter timings 2026-03-26 10:50:22 +00:00
User e011cbecbb gba: window accuracy improvements 2026-03-20 11:20:42 +00:00
Scott Mansell c2b349aeb8 cmake: Add missing include sources 2026-03-19 08:27:52 +00:00
remutro 6f0f7be867 Increase PCE Serialization Version 2026-03-18 14:57:43 +00:00
awsms bc86249658 desktop-ui: fix kiosk crash, ignore hotkeys while Tools window is focused 2026-03-18 12:19:06 +00:00
ZengGengSen 0ac4d8220c cmake: fix XAudio2 detection logic for MSVC vs non-MSVC toolchains
Previously the condition checked for MINGW, treating MSVC and all other
toolchains as a single "no detection needed" case. This inverts the
guard to MSVC, so that MSVC correctly skips detection (Windows SDK
provides xaudio2 directly) while MinGW and other non-MSVC toolchains
go through the CheckCXXSourceCompiles probe as intended.
2026-03-17 16:11:06 +00:00
ZengGengSen 1ce484be52 cmake: add FindXAudio2 module and fix debug symbol detection on MinGW
Add cmake/finders/FindXAudio2.cmake to replace the ad-hoc XAudio2
link logic in os-windows.cmake. On MinGW, the module uses
check_cxx_source_compiles to probe for xaudio2_9 and xaudio2_8 in
the sysroot; on MSVC and other toolchains the Windows SDK umbrella
library is used directly. An imported target XAudio2::XAudio2 is
exported so call sites need no platform conditionals.

Replace the generator-expression-based -gcodeview selection in
compilerconfig.cmake with an explicit check_cxx_compiler_flag probe
at configure time (ARES_COMPILER_SUPPORTS_GCODEVIEW). This avoids
passing -gcodeview to toolchains that do not support it and makes
the fallback to DWARF visible in the configure log rather than
silently resolved at build time.

- ruby/cmake/os-windows.cmake: call find_package(XAudio2) and
  consume XAudio2::XAudio2 via a generator expression; remove the
  old MSVC/else block that unconditionally linked xaudio2/xaudio2_9
- cmake/windows/compilerconfig.cmake: add -gcodeview capability
  probe; rewrite Clang and GCC debug-symbol blocks to branch on the
  cached result instead of a generator expression
2026-03-17 16:11:06 +00:00
awsms 899ac2a2ed desktop-ui: prevent hotkey from firing when setting one 2026-03-17 16:09:33 +00:00
Florian-Simon-RedArtGames ca6974b66f Fix glitch in City Hunter ROM in the intro in the performance-mode VDP
We were rendering background and sprites in burst mode, which shouldn't
happen.

I'm not the author, I'm just upstreaming changes done a member in our
team.
2026-03-17 16:08:37 +00:00
remutro 3d966bda8f Updated n64 serilization 2026-03-16 17:25:29 +00:00
remutro b11ee3fbdb Update serialized fields in N64 save states 2026-03-16 17:25:29 +00:00
User 3f8b0cae72 gba: slightly improve PPU timings 2026-03-16 17:25:20 +00:00
User 8261563a10 gba: fix mid-scanline toggling of background mosaic 2026-03-16 17:25:20 +00:00
remutro b15ce04e5d XAudio2 latency adjustment 2026-03-10 10:52:42 +00:00
ZengGengSen 7a515cd489 feat(fc): add support for JY Company and SC-127 boards
- Implementation of JY Company (Mapper 90, 209, 211) with support for
  complex PRG/CHR banking, IRQ counter modes, and math registers.
- Implementation of SC-127 (Wario Land II Kirby hack) with A12-based
  IRQ filtering.
- Enhanced CPU with `isWriting()` check and PPU with `isRenderingRead()`
  status to support advanced mapper timing.
- Updated MIA database to recognize iNES mappers 35, 90, 209, and 211.
2026-03-10 10:51:44 +00:00
Stefan Schlosser 5b7dafe70a CI: Update FreeBSD runner to 15.0
Update cross-platform-actions to v0.32.0 by using the explicit commit
https://github.com/cross-platform-actions/action/commit/492b0c80085400348c599edace11141a4ee73524

The change from 14.3 to a string literal '15.0' is mandatory, otherwise
the FreeBSD VM image cannot be fetched.
2026-03-08 16:35:08 +00:00
Luke Usher eaa47fbf9c a26: improve RIOT emulation, fixes Berzerk (likely others) 2026-03-06 21:14:18 +00:00
remutro bbc0c1db3b [PS1] Update state serialization 2026-03-06 18:57:35 +00:00
Giovanni Bajo b35a639e33 n64: adjust eeprom timings
Further hardware tests confirmed the EEPROMs are faster than initially
thought.
2026-03-06 15:23:54 +00:00
Giovanni Bajo 48b8c8a3ff n64: implement eeprom write timing and status flags
Currently Ares implements an infinitely fast EEPROM, but alas real
EEPROMs can take even 20 ms to save a single block of 8 bytes.

This commit implement approximate timings and the related flag to
notify the CPU that the EEPROM is busy writing data.
2026-03-06 10:07:41 +00:00
awsms 5a80359443 ruby(audio): enable SDL_HINT_NO_SIGNAL_HANDLERS to avoid ctrl+C hijacks 2026-03-06 10:07:23 +00:00
awsms 67163a9fbe ruby: enable SDL_HINT_NO_SIGNAL_HANDLERS to avoid ctrl+C hijacks 2026-03-05 13:56:48 +00:00
remutro 0d1c90c616 Remove OpenGL macOS Video Driver 2026-03-04 13:06:56 +00:00
awsms 3678a4cc07 Introducing --kiosk mode (#2433)
Introduce a `--batch` command-line option to run with a minimal presentation for frontend-driven launches (eg EmulationStation, arcade cabinets etc).

Behavior
- `--batch` implies `--no-file-prompt`
- Requires at least one valid game path (otherwise exits with a clear error)
- Uses a minimal presentation:
  - hides menu bar
  - removes status bar
  - black background / centered viewport
  - keeps file-open and drag/drop support
- Skips constructing settings/tools/game-browser windows in batch

Runtime safety
- Batch mode avoids UI-only flows where possible (logs messages instead)
- Driver and load/identify failures trigger a clean batch exit path
- Runtime defocus checks now use `settings.input.defocus` instead of `driverSettings.*.checked()`:
  - avoids dereferencing non-constructed UI widgets in batch
  - fixes batch crash from UI-state reads on non-UI code paths
2026-03-04 13:06:36 +00:00
Luke Usher c0a0f34439 ps1: fix some gpu clocking issues 2026-03-01 04:11:18 +00:00
Luke Usher 640ea5e3fa ps1: fix spu canReadDMA/canWriteDMA flags 2026-02-28 23:32:36 +00:00
Luke Usher cb40c6487b ps1: fix block dma using incorrect timing 2026-02-28 23:32:16 +00:00
Luke Usher 1570af44e2 ps1: fix incorrect bit position for dma channel chopping enable 2026-02-28 23:31:52 +00:00
Luke Usher d00c9feeac ps1: fix an issue where configured io access timing for exp1, exp3, bios, cpu, cdrom and exp2 would not apply 2026-02-28 23:30:24 +00:00
Luke Usher b308958bf0 cdrom: refactor CD-ROM session generation and image file handling
- Stop mixing time/address bases; we inconsistently treat LBA 0 as both 00:00:00 and 00:02:00.
 - Clearly separate logical and physical domains:
    - MSF: The MSF implementation assumes absolute MSF (lba 0 = 00:02:00)
    - LBA: logical addressing  (LBA 0 = Track 1 Index 01 / 00:02:00)
    - ABA: absolute addressing (ABA 0 = Track 1 Index 00 / 00:00:00)
 - Fix subchannel generation and support negative MSF (90–99)
 - Fix discs with non-default track 01 index 01 lba
2026-02-28 21:27:54 +00:00
Luke Usher 9125d0c79f ps1: rework cd-rom emulation 2026-02-28 21:27:53 +00:00
User ee80782345 gba: refactor bus reads 2026-02-28 09:45:06 +00:00
meauxdal b5fd6611f2 Update serialization.cpp 2026-02-27 19:55:12 +00:00
Luke Usher 12558c7ef3 gpu: fix hblank timing 2026-02-24 20:54:53 +00:00
awsms 99733cda7c fix abort on exit caused by invalid mutex unlock
`Program::quit()` could call `unlock()` on a `unique_lock` that did not own
the mutex, triggering `std::system_error` ("Operation not permitted")
and terminating the process on exit.

Guard the `unlock()` call with `owns_lock()`.
2026-02-24 12:03:13 +00:00
awsms 1482689f09 ps1: enable scheduler synchronization
Call scheduler.setSynchronize(synchronize) during PS1 serialization
to match the behavior used by other cores.

Without synchronized serialization, coroutine/thread state may be
captured inconsistently, which can lead to crashes or undefined
behavior when loading savestates after relaunch.

Bump serializer version to v147 to invalidate incompatible v146
savestates created without scheduler synchronization.
2026-02-24 12:02:43 +00:00
Alex Bates a1d3fc19a2 n64: fix return GDB read result for RCP/PI bus path
The multi-byte read handler for the RCP/PI address range
(0xA4000000-0xBFFFFFFF) correctly fills the result buffer via
bus.read<Word> but then falls through to `return {}` instead
of returning the result. This causes GDB memory reads from ROM
cartridge addresses to return empty responses.
2026-02-23 19:15:23 +00:00
User 103770a995 gba: prevent instruction tracing from modifying open bus 2026-02-21 08:25:49 +00:00
Le-Syl21andClaude Opus 4.6 38a2e62603 Fix Linux shader install: copy contents instead of directory
CMake install(DIRECTORY "path") without a trailing slash copies the
directory itself, creating an extra shaders_slang/ level under Shaders/.

Adding a trailing slash makes it copy the contents directly, consistent
with macOS (ditto) and Windows (copy_directory) which both copy contents.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-02-16 08:22:39 +00:00
meauxdal ae41a43567 n64: fix audio pops when AI DMA ends
On a real console, when the AI DMA ends, even if the volume is not zero,
there is a decay happening on the analog path that doesn't cause any
audible pops. This can happen in games when they fail to meet the
audio deadline and schedule a new buffer before the previous one ends.
2026-02-15 22:53:40 +01:00
TascoDLX 08a7253e99 md: fix screen in interlace mode 1 2026-02-13 13:30:28 +00:00
remutro ba6069686f add missing file 2026-02-08 17:32:01 +00:00
remutro 6bb521a207 Import & export settings in the UI 2026-02-08 17:32:01 +00:00
remutro fac53a08ca Windows OpenGL 4.6 Core Profile Driver 2026-02-08 17:31:32 +00:00
izzy d67a92b2b4 make lambda more concise and strict 2026-02-08 17:31:12 +00:00
remutro ffee6d658d Fix fast forward getting stuck 2026-02-03 17:23:58 +00:00
remutro 58f3d7badf Exclude additional systems from using Run-Ahead 2026-02-03 17:23:36 +00:00
remutro 388e98fba9 Fix bug with disabling of secondary media 2026-01-30 06:40:08 +00:00
remutro f883420714 sort emulator cores by manufacturer 2026-01-30 06:40:08 +00:00
remutro d99f7598e1 macOS looks better using same value as Windows 2026-01-30 06:40:08 +00:00
remutro 64bfe58e03 Cleanup UI text and controls 2026-01-30 06:40:08 +00:00
meauxdal d05f7b64b4 Update vulkan.cpp 2026-01-28 07:55:22 +00:00
User bc036dcaf6 gba: improve handling of OBJ mosaic counters 2026-01-28 07:53:43 +00:00
User 64cebdad77 gba: fix interaction between OBJ mosaic and priority 2026-01-28 07:53:43 +00:00
remutro e594251208 add version string to end of help message 2026-01-27 10:12:38 +00:00
remutro cea26bd150 add option to print version string 2026-01-27 10:12:38 +00:00
remutro 27905a8fc7 Add ability to change disc for ldrom2 titles 2026-01-27 10:12:25 +00:00
remutro 2f2704b2b6 adjust string placement 2026-01-27 10:10:10 +00:00
remutro 5afa255ab8 logo alignment in about 2026-01-27 10:10:10 +00:00
remutro daa781706a Use lambda for sort comparer 2026-01-20 11:54:06 +00:00
remutro 88bd798427 Sort once when vector is created 2026-01-20 11:54:06 +00:00
remutro d77c8b1c56 Sort the emulators setting list also 2026-01-20 11:54:06 +00:00
remutro e07f18c172 Sort the emulator list in firmware settings 2026-01-20 11:54:06 +00:00
remutro 6bc13e8d66 Fix crash opening about window 2026-01-20 10:37:42 +00:00
remutro 25482bc539 Single xaudio2 source file, fix vector calls in asio.cpp 2026-01-19 14:34:34 +00:00
remutro 847e2a24cf Fix silly mistake 2026-01-19 14:34:34 +00:00
remutro 514933d459 Fix MSVC Linking 2026-01-19 14:34:34 +00:00
remutro 04e4f0ffa0 Fix MSVC builds 2026-01-19 14:34:34 +00:00
remutro 109c669056 Update XAudio2 from 2.1 to 2.9 2026-01-19 14:34:34 +00:00
Giovanni Bajo fb68c74143 n64: with homebrew header, don't default to install a controller pak
When the homebrew header was first introduced, in its first limited
spec, Ares defaulted to enable both rumble pak and controller pak:
https://github.com/ares-emulator/ares/commit/bc8c308ee96162f5a7068001a0ece83e5f821e16

Back at the time, there wasn't any way to specify that a controller pak
was needed by the ROM, and Ares didn't even provide a GUI to install it.
So basically this was the only way to allow a homebrew game to use
the controller pak at all.

Fast forward 4 years and now we have a GUI to select which pak to install
on each controller, and the homebrew header allows to pre-install whatever
you want into each port.

In case the user doesn't specify a suggested controller configuration
in the homebrew header, the default of using a controller pak has the
annoying side effect of creating a useless .sav file next to any ROM.
CPak is not very common in homebrew ROMs anyway, so enabling it by
default now seems to create more hassle than it solves.
2026-01-19 14:34:02 +00:00
remutro acdf31b40a Stay in pseudo-fullscreen when game is reset 2026-01-18 00:04:56 +00:00
remutro 48c611c90b Fix optnone attribute warning 2026-01-15 12:55:37 +00:00
remutro f338f50b4d some refactoring 2026-01-14 11:36:53 +00:00
remutro f0b119d90a Fix mapped drives not working after symlink fix (Windows) 2026-01-14 11:36:53 +00:00
remutro e8c268026c Update current save slot on successful save state load 2026-01-14 11:33:30 +00:00
remutro 9387eab814 Add command line option to specify a save state slot to load 2026-01-14 11:33:30 +00:00
remutro d769abafd9 Update hotkey text 2026-01-14 11:33:06 +00:00
remutro 742ab90d21 rogue spaces removed 2026-01-14 11:33:06 +00:00
remutro 2a346af4e2 Add hotkey to disable/enable selected shader 2026-01-14 11:33:06 +00:00
Giovanni Bajo df5ebc9245 n64: implement support for region-free ROMs (homebrew extension)
The advanced homebrew header (https://n64brew.dev/wiki/ROM_Header)
specifies a bit to mark a ROM as "region free", meaning that it can be
booted in all regions. Ares now has a region preference option, so
that option can be respected for the region free ROMs.
2026-01-11 17:04:57 +00:00
Luke Usher 71f17d9a2a ps1: fix crash when saving/loading state 2026-01-09 21:00:32 +00:00
Luke Usher 4e7ab5d3e3 ps1: refactor core to use libco threading
- cpu: implemented memory timing/control registers
 - cpu: update bus accesses to obey memory timing registers
 - cdrom: incorrect 150lba offset in command 0x14
 - cdrom: fix behavior when a command is issued before the previous has completed
 - cdrom: implement behavior where a new command cannot be executed until pending IRQ are acknowledged
 - cdrom: implement 'command/parameter busy' register
 - dma: update bus accesses to obey memory timing registers
 - dma: improve chopping/allowing the cpu to run between blocks of a long transfer
 - mdec: implement basic decode timing
 - mdec: implement correct input fifo behavior
 - mdec: fix behavior when an incomplete block is transferred via the input fifo
 - mdec: fix dma request registers, input/output full/empty registers, busy register
 - gpu: use correct clock frequency for PAL and NTSC
 - timer: improve behavior around timer overflow/target reached irq
2026-01-09 16:11:58 +00:00
remutro e5ffe61a07 Fix Dip Switches menu on Linux 2026-01-09 12:53:53 +00:00
remutro e601bccdfb Add pseduefullscreen command line argument 2026-01-09 10:27:41 +00:00
remutro 155c9b4154 Fix formatting 2026-01-09 10:27:31 +00:00
remutro 7b0e29baa9 Expose option to not prompt for secondary media in GUI 2026-01-09 10:27:31 +00:00
jsgroth 0152831319 m32x: adjust H32 pixel offset between Gen/MD VDP and 32X VDP
the adjusted offset is equal to 2.6 H32 pixels / 3.25 H40 pixels
2026-01-08 23:32:28 +00:00
remutro 17ce1bbe0f Fix loading shaders from the command line 2026-01-08 14:15:56 +00:00
remutro db953dbc62 minor format fixes 2026-01-08 09:27:05 +00:00
remutro 65ed77ecd7 Fix symlinks to files for rom loading 2026-01-08 09:27:05 +00:00
Luke Usher bdf4dc9ce1 desktop-ui: add '--settings-file' command line parameter 2026-01-07 10:32:43 +00:00
Luke Usher c01f3b885e desktop-ui: do not enable 'Arcade' if no arcade cores are compiled in. 2026-01-07 10:20:51 +00:00
Luke Usher 538078f6d0 ruby: prefer 'metal' video driver as both optimal and safest on macOS. 2026-01-07 10:11:43 +00:00
Luke Usher 2a4be17f56 ruby: remove erroneous include 2026-01-06 23:15:36 +00:00
ZengGengSen 9d647291fa add unload emulator to save the wav file 2026-01-06 16:32:21 +00:00
ZengGengSen 595de2e2aa fix new tape file resize bug 2026-01-06 16:32:21 +00:00
ZengGengSen 778e1f0b76 feat: add famicom data recorder support
1. add famicom data recorder support
2. refactor the tape control
3. fix the wav bug
2026-01-06 16:32:21 +00:00
User b5910c75f5 gba: prefetcher should fail reading across 128KiB boundaries 2026-01-06 16:21:35 +00:00
Luke Usher 23cd8772f5 ruby: allow drivers to set controller names and use deterministic device id in SDL driver 2026-01-06 12:09:44 +00:00
remutro 202db872de Add Cubivore English translation to N64 DB 2026-01-05 09:55:12 +00:00
remutro 934e9d36c3 Update dependencies (post-v147) 2026-01-02 16:21:26 +00:00
remutro 30066aa336 [SFC] Add support for GBC titles to the Super Game Boy 2025-12-31 12:33:22 +00:00
invertego cc4db18707 libco: limit scope of section hack to gcc
When placing co_swap_function byte array into the text section, libco
uses the trick of appending a # to the section name. This name string is
passed directly to the generated assembly and the # is treated as
comment token by the GNU assembler. This is a hack to avoid a warning
about changed section attributes.

This hack doesn't work with other compilers like Clang or TinyCC, but
unlike GCC they don't require such hacks. I've added some compile time
checks around the hack to try to isolate it to GCC and allow other
compilers to see the unmodified section name.

Validated on the following OSes/architectures/compilers:
- Windows/amd64: gcc, clang, cl, clang-cl, tcc
- Windows/aarch64: clang, cl, clang-cl
- Linux/amd64: gcc, clang, tcc[1]
- Linux/aarch64: gcc, clang, tcc[1]
- macOS/aarch64: gcc, clang

[1] TCC only works with a fix to glibc headers that was only made very
recently. Without it, you have to #undef __attribute__ after including
glibc headers.
2025-12-29 12:01:33 +00:00
invertego 30a3b125c8 libco: align co_active_buffer on amd64, aarch64
This is required on amd64 due to the aligned moves in co_swap. While
note technically needed on aarch64, I found information online that
suggested ldp/stp perform better on certain microarchitectures with
16-byte alignment.
2025-12-29 12:01:33 +00:00
User 084d8fb72b gba: only trigger keypad IRQ when input changes 2025-12-29 12:01:15 +00:00
User 463d44920b gba: fix handling of repeat bit during immediate DMA transfers 2025-12-27 22:38:59 +00:00
Stefan Schlosser adfc78f952 pce: fix missing qon linking 2025-12-23 14:44:52 +00:00
1363 changed files with 55637 additions and 271004 deletions

No files matched your search

+2 -2
View File
@@ -105,12 +105,12 @@ jobs:
TARGET_PRESET: ${{ matrix.platform.target-cmake-preset }}
- name: "Build: FreeBSD"
if: matrix.platform.name == 'freebsd'
uses: cross-platform-actions/action@e8a7b572196ff79ded1979dc2bb9ee67d1ddb252
uses: cross-platform-actions/action@492b0c80085400348c599edace11141a4ee73524
env:
TARGET_PRESET: ${{ matrix.platform.target-cmake-preset }}
with:
operating_system: freebsd
version: 14.3
version: '15.0'
environment_variables: TARGET_PRESET
run: |
sudo pkg install -y cmake pkgconf ccache ninja gtk3 libX11 libXext libXrandr libXrender libglvnd alsa-lib libao libudev-devd librashader openal-soft pulseaudio sdl3 vulkan-loader
+3
View File
@@ -41,6 +41,9 @@ desktop-ui/resource/resource.hpp
mia/resource/resource.cpp
mia/resource/resource.hpp
# Exclude Qt generated MOC file
hiro/qt/qt.moc
# Exclude fetched third-party processor test binaries
tests/arm7tdmi/tests
tests/m68000/tests
+6
View File
@@ -38,12 +38,18 @@ When started from the command-line, ares accepts a few options.
Usage: ./ares [options] game(s)
--help Displays available options and exit
--version Displays the version string of the application
--terminal Create new terminal window (Windows only)
--fullscreen Start in full screen mode
--pseudofullscreen Start in pseudo full screen mode
--kiosk Start in minimal UI mode (implies --no-file-prompt)
--system system Specify the system name
--shader shader Specify a slang shader to load (requires OpenGL or Metal)
--setting name=value Specify a value for a setting
--dump-all-settings Show a list of all existing settings and exit
--no-file-prompt Do not prompt to load (optional) additional roms (eg: 64DD)
--settings-file path Specify a settings file override (settings.bml)
--save-state slot Specify a save state slot to load (1-9)
```
The --system option is useful when the system type cannot be auto-detected.
+12
View File
@@ -99,6 +99,18 @@ target_sources(
ares/node/video/video.hpp
)
target_sources(
ares
PRIVATE
ares/node/debugger/debugger.hpp
ares/node/debugger/graphics.hpp
ares/node/debugger/memory.hpp
ares/node/debugger/properties.hpp
ares/node/debugger/tracer/tracer.hpp
ares/node/debugger/tracer/instruction.hpp
ares/node/debugger/tracer/notification.hpp
)
if(OS_WINDOWS)
include(cmake/os-windows.cmake)
elseif(OS_MACOS)
+8 -1
View File
@@ -19,6 +19,12 @@ auto CPU::unload() -> void {
}
auto CPU::main() -> void {
if(io.resetPending) {
debugger.interrupt("Reset");
reset();
io.resetPending = 0;
}
debugger.instruction();
instruction();
}
@@ -31,10 +37,11 @@ auto CPU::step(u32 clocks) -> void {
auto CPU::power(bool reset) -> void {
MOS6502::BCD = 1;
MOS6502::power();
if(!reset) MOS6502::power();
Thread::create(system.frequency() / 3, std::bind_front(&CPU::main, this));
io = {};
io.resetPending = 1;
}
}
+3
View File
@@ -5,9 +5,11 @@ struct CPU : MOS6502, Thread {
//debugger.cpp
auto load(Node::Object) -> void;
auto instruction() -> void;
auto interrupt(string_view) -> void;
struct Tracer {
Node::Debugger::Tracer::Instruction instruction;
Node::Debugger::Tracer::Notification interrupt;
} tracer;
} debugger;
@@ -39,6 +41,7 @@ struct CPU : MOS6502, Thread {
//protected:
struct IO {
n1 resetPending = 0;
n1 rdyLine = 1;
n32 scanlineCycles = 0;
n8 openBus = 0;
+5 -1
View File
@@ -9,4 +9,8 @@ auto CPU::Debugger::instruction() -> void {
}
}
auto CPU::Debugger::interrupt(string_view type) -> void {
if(tracer.interrupt->enabled()) {
tracer.interrupt->notify(type);
}
}
+1
View File
@@ -1,6 +1,7 @@
auto CPU::serialize(serializer& s) -> void {
MOS6502::serialize(s);
Thread::serialize(s);
s(io.resetPending);
s(io.rdyLine);
s(io.openBus);
}
+62 -43
View File
@@ -7,14 +7,24 @@ auto RIOT::writeRam(n8 address, n8 data) -> void {
}
auto RIOT::readIo(n8 address) -> n8 {
switch(address.bit(0, 2)) {
address &= 0x1f;
switch(address) {
case 0x00: return readPortA();
case 0x01: return port[0].direction;
case 0x02: return readPortB();
case 0x03: return port[1].direction;
case 0x04: timer.interruptFlag = address.bit(3); return timer.counter;
case 0x04: case 0x06: {
n8 data = timer.counter;
if(timer.interruptFlag && !timer.justWrapped) {
timer.interruptFlag = 0;
}
return data;
}
case 0x05: case 0x07: {
n8 output = 0;
n8 output;
output.bit(6) = 0; //TODO: PA7 IRQ Flag
output.bit(7) = timer.interruptFlag;
return output;
@@ -26,31 +36,38 @@ auto RIOT::readIo(n8 address) -> n8 {
}
auto RIOT::writeIo(n8 address, n8 data) -> void {
address &= 0x1f;
switch(address) {
case 0x00: writePortA(data); return;
case 0x01: port[0].direction = data; return;
case 0x02: writePortB(data); return;
case 0x03: port[1].direction = data; return;
case 0x14: case 0x15: case 0x16: case 0x17:
case 0x1c: case 0x1d: case 0x1e: case 0x1f: {
const n16 intervals[] = {1, 8, 64, 1024};
timer.counter = data;
timer.interval = intervals[address.bit(0, 1)];
timer.reload = timer.interval;
timer.interruptFlag = 0;
timer.interruptEnable = address.bit(3);
return; }
case 0x14: reloadTimer(data, 1, 0); return;
case 0x15: reloadTimer(data, 8, 0); return;
case 0x16: reloadTimer(data, 64, 0); return;
case 0x17: reloadTimer(data, 1024, 0); return;
case 0x1c: reloadTimer(data, 1, 1); return;
case 0x1d: reloadTimer(data, 8, 1); return;
case 0x1e: reloadTimer(data, 64, 1); return;
case 0x1f: reloadTimer(data, 1024, 1); return;
}
debug(unimplemented, "[RIOT] IO write ",hex(address), " = ", hex(data));
debug(unimplemented, "[RIOT] IO write ", hex(address), " = ", hex(data));
}
auto RIOT::readPortA() -> n8 {
n8 data;
// TODO: Handle when configured as outputs
data.bit(0, 3) = controllerPort2.read().bit(0, 4);
data.bit(4, 7) = controllerPort1.read().bit(0, 4);
return data;
n8 output = 0xff;
n8 input;
input.bit(0, 3) = controllerPort2.read().bit(0, 4);
input.bit(4, 7) = controllerPort1.read().bit(0, 4);
for(auto n : range(8)) {
output.bit(n) = port[0].direction.bit(n)? port[0].data.bit(n) : input.bit(n);
}
return output;
}
auto RIOT::writePortA(n8 data) -> void {
@@ -58,39 +75,41 @@ auto RIOT::writePortA(n8 data) -> void {
}
auto RIOT::readPortB() -> n8 {
system.controls.poll();
system.controls.poll();
n8 data;
n8 data = 0xff;
for(auto n : range(8)) if(port[1].direction.bit(n)) { data.bit(n) = port[1].data.bit(n); }
for(auto n : range(8)) {
if(port[1].direction.bit(n)) data.bit(n) = port[1].data.bit(n);
}
// Atari hard-wires these bits as input
data.bit(0) = !system.controls.reset->value();
data.bit(1) = !system.controls.select->value();
// Atari hard-wires these bits as input
data.bit(0) = !system.controls.reset->value();
data.bit(1) = !system.controls.select->value();
// These are toggle switches; flip the values on the rising edge
if(system.controls.tvType->value() && !tvTypeLatch) {
tvType = !tvType;
}
tvTypeLatch = system.controls.tvType->value();
// These are toggle switches; flip the values on the rising edge
if(system.controls.tvType->value() && !tvTypeLatch) {
tvType = !tvType;
}
tvTypeLatch = system.controls.tvType->value();
if(system.controls.leftDifficulty->value() && !leftDifficultyLatch) {
leftDifficulty = !leftDifficulty;
}
leftDifficultyLatch = system.controls.leftDifficulty->value();
if(system.controls.leftDifficulty->value() && !leftDifficultyLatch) {
leftDifficulty = !leftDifficulty;
}
leftDifficultyLatch = system.controls.leftDifficulty->value();
if(system.controls.rightDifficulty->value() && !rightDifficultyLatch) {
rightDifficulty = !rightDifficulty;
}
rightDifficultyLatch = system.controls.rightDifficulty->value();
if(system.controls.rightDifficulty->value() && !rightDifficultyLatch) {
rightDifficulty = !rightDifficulty;
}
rightDifficultyLatch = system.controls.rightDifficulty->value();
data.bit(3) = tvType;
data.bit(6) = leftDifficulty;
data.bit(7) = rightDifficulty;
data.bit(3) = tvType;
data.bit(6) = leftDifficulty;
data.bit(7) = rightDifficulty;
return data;
return data;
}
auto RIOT::writePortB(n8 data) -> void {
port[1].data = data;
}
port[1].data = data;
}
+49 -5
View File
@@ -9,7 +9,6 @@ RIOT riot;
auto RIOT::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("RIOT");
ram.allocate(128);
}
@@ -18,13 +17,52 @@ auto RIOT::unload() -> void {
node.reset();
}
auto RIOT::main() -> void {
if(--timer.counter == 0xff) {
auto RIOT::reloadTimer(n8 data, n16 interval, n1 interruptEnable) -> void {
timer.counter = data;
timer.interval = interval;
timer.prescaler = interval;
timer.interruptEnable = interruptEnable;
timer.interruptFlag = 0;
timer.underflow = 0;
timer.holdZero = 0;
timer.justWrapped = 0;
}
auto RIOT::clockTimer() -> void {
timer.justWrapped = 0;
if(timer.prescaler == 0) timer.prescaler = 1;
if(--timer.prescaler) return;
timer.prescaler = timer.interval;
if(timer.holdZero) {
timer.holdZero = 0;
timer.underflow = 1;
timer.counter = 0xff;
timer.interval = 1;
timer.prescaler = 1;
timer.interruptFlag = 1;
timer.justWrapped = 1;
return;
}
step(timer.interval);
if(timer.counter == 0x00) {
timer.holdZero = 1;
return;
}
timer.counter--;
if(timer.underflow) {
timer.interval = 1;
timer.prescaler = 1;
}
}
auto RIOT::main() -> void {
clockTimer();
step(1);
}
auto RIOT::step(u32 clocks) -> void {
@@ -35,13 +73,19 @@ auto RIOT::step(u32 clocks) -> void {
auto RIOT::power(bool reset) -> void {
Thread::create(system.frequency() / 3, std::bind_front(&RIOT::main, this));
timer = {};
timer.interval = 1;
timer.prescaler = 1;
timer.counter = 0x00;
port[0] = {};
port[0].data = 0xff;
port[1] = {};
port[1].data = 0xff;
leftDifficulty = 1;
leftDifficultyLatch = 0;
rightDifficulty = 1;
rightDifficultyLatch = 0;
tvType = 1;
tvTypeLatch = 0;
}
}
+9 -5
View File
@@ -10,7 +10,8 @@ struct RIOT : Thread {
auto step(u32 clocks) -> void;
auto power(bool reset) -> void;
auto decrementTimer() -> void;
auto clockTimer() -> void;
auto reloadTimer(n8 data, n16 interval, n1 interruptEnable) -> void;
//io.cpp
auto readRam(n8 address) -> n8;
@@ -27,11 +28,14 @@ struct RIOT : Thread {
auto serialize(serializer&) -> void;
struct {
u8 counter;
n8 counter;
n16 interval;
n16 reload;
n1 interruptEnable;
n1 interruptFlag;
n16 prescaler;
n1 interruptEnable;
n1 interruptFlag;
n1 underflow;
n1 holdZero;
n1 justWrapped;
} timer;
struct Port {
+5 -2
View File
@@ -1,13 +1,16 @@
//serialization.cpp
auto RIOT::serialize(serializer& s) -> void {
Thread::serialize(s);
s(ram);
s(timer.counter);
s(timer.interval);
s(timer.reload);
s(timer.prescaler);
s(timer.interruptEnable);
s(timer.interruptFlag);
s(timer.underflow);
s(timer.holdZero);
s(timer.justWrapped);
for(auto n : range(2)) {
s(port[n].data);
+1 -1
View File
@@ -1,4 +1,4 @@
static const string SerializerVersion = "v139";
static const string SerializerVersion = "v140";
auto System::serialize(bool synchronize) -> serializer {
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
@@ -28,6 +28,15 @@ struct Instruction : Tracer {
for(auto& history : _history) history = ~0ull;
}
auto setEnabled(bool enabled) -> void {
Tracer::setTerminal(enabled);
if(!enabled) {
_omitted = 0;
setMask(_mask);
setDepth(_depth);
}
}
auto address(u64 address) -> bool {
address &= ~0ull >> (64 - _addressBits); //mask upper bits of address
_address = address;
+3
View File
@@ -3,6 +3,7 @@ namespace ares::Core {
struct System;
struct Peripheral;
struct Port;
struct Tape;
namespace Component {
struct Component;
struct RealTimeClock;
@@ -49,6 +50,7 @@ namespace ares::Node {
using System = std::shared_ptr<Core::System>;
using Peripheral = std::shared_ptr<Core::Peripheral>;
using Port = std::shared_ptr<Core::Port>;
using Tape = std::shared_ptr<Core::Tape>;
namespace Component {
using Component = std::shared_ptr<Core::Component::Component>;
using RealTimeClock = std::shared_ptr<Core::Component::RealTimeClock>;
@@ -102,6 +104,7 @@ namespace ares::Core {
#include <ares/node/system.hpp>
#include <ares/node/peripheral.hpp>
#include <ares/node/port.hpp>
#include <ares/node/tape.hpp>
namespace Component {
#include <ares/node/component/component.hpp>
#include <ares/node/component/real-time-clock.hpp>
+79
View File
@@ -0,0 +1,79 @@
struct Tape : Peripheral {
DeclareClass(Tape, "tape") using Peripheral::Peripheral;
~Tape() {
unload();
}
auto play() -> void { _playing = true; }
auto record() -> void { _recording = true; }
auto stop() -> void {
_playing = false;
_recording = false;
}
auto playing() const -> bool { return _playing; }
auto recording() const -> bool { return _recording; }
auto loaded() const -> bool { return _loaded; }
auto load() -> bool {
if (!_loaded && _load && _load()) {
_loaded = true;
return true;
}
return _loaded;
}
auto unload() -> void {
if (_loaded) {
stop();
if (_unload) _unload();
_loaded = false;
}
}
auto setLoad(std::function<bool()> load) -> void { _load = load; }
auto setUnload(std::function<void()> unload) -> void { _unload = unload; }
auto setSupportPlay(bool support) -> void { _supportPlay = support; }
auto setSupportRecord(bool support) -> void { _supportRecord = support; }
auto supportPlay() const -> bool { return _supportPlay; }
auto supportRecord() const -> bool { return _supportRecord; }
auto length() const -> u64 { return _length; }
auto setLength(u64 length) -> void { _length = length; }
auto position() const -> u64 { return _position; }
auto setPosition(u64 position) -> void { _position = position; }
auto frequency() const -> u64 { return _frequency; }
auto setFrequency(u64 frequency) -> void { _frequency = frequency; }
auto serialize(string &output, string depth) -> void override {
Peripheral::serialize(output, depth);
output.append(depth, " supportPlay: ", _supportPlay, "\n");
output.append(depth, " supportRecord: ", _supportRecord, "\n");
output.append(depth, " playing: ", _playing, "\n");
output.append(depth, " recording: ", _recording, "\n");
}
auto unserialize(Markup::Node node) -> void override {
Peripheral::unserialize(node);
_supportPlay = node["supportPlay"].boolean();
_supportRecord = node["supportRecord"].boolean();
_playing = node["playing"].boolean();
_recording = node["recording"].boolean();
}
private:
bool _supportPlay = false;
bool _supportRecord = false;
bool _playing = false;
bool _recording = false;
bool _loaded = false;
u64 _length = 0;
u64 _position = 0;
u64 _frequency = 1;
std::function<bool()> _load;
std::function<void()> _unload;
};
+3
View File
@@ -8,6 +8,9 @@ enum class Event : u32 {
Frame,
Power,
Synchronize,
FastForwardOn,
FastForwardOff,
Shutdown
};
struct Platform {
@@ -25,12 +25,9 @@ auto MOS6502::reset() -> void {
I = 1;
PCL = read(vector++);
L PCH = read(vector++);
resetting = 0;
}
auto MOS6502::instruction() -> void {
if(resetting) reset();
switch(opcode()) {
op(0x00, None, addr(Implied), fp(BRK))
op(0x01, Load, addr(IndirectX), fp(ORA))
+1 -7
View File
@@ -35,19 +35,13 @@ namespace ares {
#undef N
#undef L
auto MOS6502::power(bool reset) -> void {
if(reset) {
resetting = 1;
return;
}
auto MOS6502::power() -> void {
A = 0x00;
X = 0x00;
Y = 0x00;
S = 0x00;
P = 0x04;
MDR = 0x00;
resetting = 1;
}
}
+1 -2
View File
@@ -18,7 +18,7 @@ struct MOS6502 {
virtual auto readDebugger(n16 addr) -> n8 { return 0; }
//mos6502.cpp
auto power(bool reset = false) -> void;
auto power() -> void;
//memory.cpp
auto idle() -> void;
@@ -189,7 +189,6 @@ struct MOS6502 {
n16 PC;
n16 MAR;
n8 MDR;
n1 resetting = 0;
};
}
@@ -13,5 +13,4 @@ auto MOS6502::serialize(serializer& s) -> void {
s(PC);
s(MAR);
s(MDR);
s(resetting);
}
+12
View File
@@ -115,8 +115,10 @@ ares_add_sources(
cartridge/board/taito-x1-017.cpp
cartridge/board/tengen-80003x.cpp
cartridge/board/unl-bmc.cpp
cartridge/board/unl-jy.cpp
cartridge/board/unl-sachen-74ls374n.cpp
cartridge/board/unl-sachen-sa0037.cpp
cartridge/board/unl-sc127.cpp
cartridge/board/unl-txc.cpp
cartridge/board/unrom-512.cpp
)
@@ -146,6 +148,14 @@ ares_add_sources(
controller/gamepad/gamepad.hpp
)
ares_add_sources(
CORE #
fc
INCLUDED #
controller/zapper/zapper.cpp
controller/zapper/zapper.hpp
)
ares_add_sources(
CORE #
fc
@@ -180,6 +190,8 @@ ares_add_sources(
INCLUDED #
expansion/family-keyboard/family-keyboard.cpp
expansion/family-keyboard/family-keyboard.hpp
expansion/family-keyboard/famicom-data-recorder.cpp
expansion/family-keyboard/famicom-data-recorder.hpp
)
ares_add_sources(
+1 -1
View File
@@ -2,7 +2,7 @@ struct APU : Thread {
Node::Object node;
Node::Audio::Stream stream;
auto rate() const -> u32 { return Region::PAL() ? 16 : 12; }
auto rate() const -> u32 { return system.cpuDivider(); }
//apu.cpp
auto load(Node::Object) -> void;
+4
View File
@@ -59,8 +59,10 @@ namespace Board {
#include "unrom-512.cpp"
#include "unl-bmc.cpp"
#include "unl-jy.cpp"
#include "unl-sachen-74ls374n.cpp"
#include "unl-sachen-sa0037.cpp"
#include "unl-sc127.cpp"
#include "unl-txc.cpp"
auto Interface::create(string board) -> Interface* {
@@ -124,8 +126,10 @@ auto Interface::create(string board) -> Interface* {
if(!p) p = UNROM512::create(board);
if(!p) p = BMC::create(board);
if(!p) p = JYCompany::create(board);
if(!p) p = Sachen74LS374N::create(board);
if(!p) p = SachenSA0037::create(board);
if(!p) p = Sc127::create(board);
if(!p) p = TXC::create(board);
if(!p) p = new Interface;
+548
View File
@@ -0,0 +1,548 @@
struct JYCompany : Interface {
static auto create(string id) -> Interface* {
if(id == "UNL-JY-COMPANY-A") return new JYCompany(Revision::JYTypeA);
if(id == "UNL-JY-COMPANY-B") return new JYCompany(Revision::JYTypeB);
if(id == "UNL-JY-COMPANY-C") return new JYCompany(Revision::JYTypeC);
return nullptr;
}
Memory::Readable<n8> programROM;
Memory::Readable<n8> characterROM;
enum class Revision : u32 {
JYTypeA,
JYTypeB,
JYTypeC,
} revision;
enum class IrqSource : u32 {
CpuM2Rise = 0,
PpuA12Rise = 1, // unfiltered, eight per scanline
PpuRead = 2, // 170 per scanline ?
CpuWrite = 3,
};
JYCompany(Revision revision) : revision(revision) {
}
auto load() -> void override {
Interface::load(programROM, "program.rom");
Interface::load(characterROM, "character.rom");
}
auto main() -> void override {
if(irqSource == IrqSource::CpuM2Rise) tickIrqCounter();
if(irqSource == IrqSource::CpuWrite && cpu.isWriting()) tickIrqCounter();
cpu.irqLine(irqLine);
tick();
}
auto updatePRGBanks() -> void {
n8 exPrg = exPrgMode << 6;
n8 last = (prgMode & 0x04) ? prgRegs[3] : n8(0x3f);
switch(prgMode & 0x03) {
case 0: // 32KB
programBanks[0] = ((last & 0x0f) | (exPrg >> 2)) << 2;
programBanks[1] = programBanks[0] + 1;
programBanks[2] = programBanks[0] + 2;
programBanks[3] = programBanks[0] + 3;
programAt6000Bank = (((prgRegs[3] * 4) + 3) & 0x3f) | (exPrg >> 2);
break;
case 1: // 16KB
programBanks[0] = ((prgRegs[1] & 0x1f) | (exPrg >> 1)) << 1;
programBanks[1] = programBanks[0] + 1;
programBanks[2] = ((last & 0x1f) | (exPrg >> 1)) << 1;
programBanks[3] = programBanks[2] + 1;
programAt6000Bank = (((prgRegs[3] * 2) + 1) & 0x1f) | (exPrg >> 1);
break;
case 2: // 8KB
programBanks[0] = prgRegs[0] | exPrg;
programBanks[1] = prgRegs[1] | exPrg;
programBanks[2] = prgRegs[2] | exPrg;
programBanks[3] = last | exPrg;
programAt6000Bank = prgRegs[3] | exPrg;
break;
case 3: // 8KB Alt (Scrambled)
programBanks[0] = (invertPrgBits(prgRegs[0]) & 0x3f) | exPrg;
programBanks[1] = (invertPrgBits(prgRegs[1]) & 0x3f) | exPrg;
programBanks[2] = (invertPrgBits(prgRegs[2]) & 0x3f) | exPrg;
if(prgMode & 0x04)
programBanks[3] = (invertPrgBits(prgRegs[3]) & 0x3f) | exPrg;
else
programBanks[3] = (invertPrgBits(last) & 0x3f) | exPrg;
programAt6000Bank = (invertPrgBits(prgRegs[3]) & 0x3f) | exPrg;
break;
}
}
// PRG read
auto readPRG(n32 address, n8 data) -> n8 override {
if(address < 0x5000) return data;
// multiply/accumulator registers at $5000-$5FFF
if(address >= 0x5000 && address < 0x6000) {
if(address < 0x5800) { return data & 0x3f; }
switch(address & 0x07) {
case 0: return (multiplyValue1 * multiplyValue2) & 0xff;
case 1: return ((multiplyValue1 * multiplyValue2) >> 8) & 0xff;
case 3: return regRamValue;
}
return data;
}
//$6000-$7FFF: optional PRG bank
if(address >= 0x6000 && address < 0x8000) {
if(!enablePrgAt6000) return data;
return programROM.read((u32)programAt6000Bank << 13 | (n13)address);
}
//$8000-$FFFF: PRG banking
if(address >= 0x8000) return programROM.read((u32)programBanks[address >> 13 & 0x03] << 13 | (n13)address);
unreachable;
}
// PRG write
auto writePRG(n32 address, n8 data) -> void override {
if(address < 0x5000) return;
if(address >= 0x5000 && address < 0x6000) {
if(address < 0x5800) return;
switch(address & 0x07) {
case 0: multiplyValue1 = data; break;
case 1: multiplyValue2 = data; break;
case 3: regRamValue = data; break;
}
return;
}
if(address < 0x8000) return;
switch(address & 0xf007) {
case 0x8000:
case 0x8001:
case 0x8002:
case 0x8003:
case 0x8004:
case 0x8005:
case 0x8006:
case 0x8007:
prgRegs[address & 0x03] = data & 0x3f;
updatePRGBanks();
break;
case 0x9000:
case 0x9001:
case 0x9002:
case 0x9003:
case 0x9004:
case 0x9005:
case 0x9006:
case 0x9007:
chrLowRegs[address & 0x07] = data;
updateCHRBanks();
break;
case 0xa000:
case 0xa001:
case 0xa002:
case 0xa003:
case 0xa004:
case 0xa005:
case 0xa006:
case 0xa007:
chrHighRegs[address & 0x07] = data;
updateCHRBanks();
break;
case 0xb000:
case 0xb001:
case 0xb002:
case 0xb003:
ntLowRegs[address & 0x03] = data;
updateCIRAMBanks();
break;
case 0xb004:
case 0xb005:
case 0xb006:
case 0xb007:
ntHighRegs[address & 0x03] = data;
updateCIRAMBanks();
break;
case 0xc000:
if(data & 0x01) {
irqEnabled = true;
} else {
irqEnabled = false;
irqLine = 0;
}
break;
case 0xc001:
irqCountDirection = (data >> 6) & 0x03;
irqFunkyMode = (data & 0x08) == 0x08;
irqSmallPrescaler = ((data >> 2) & 0x01) == 0x01;
irqSource = (IrqSource)(data & 0x03);
break;
case 0xc002:
irqEnabled = false;
irqLine = 0;
break;
case 0xc003: irqEnabled = true; break;
case 0xc004: irqPrescaler = data ^ irqXorReg; break;
case 0xc005: irqCounter = data ^ irqXorReg; break;
case 0xc006: irqXorReg = data; break;
case 0xc007: irqFunkyModeReg = data; break;
case 0xd000:
prgMode = data & 0x07;
chrMode = (data >> 3) & 0x03;
advancedNtControl = (data & 0x20) == 0x20;
disableNtRam = (data & 0x40) == 0x40;
enablePrgAt6000 = (data & 0x80) == 0x80;
updatePRGBanks();
updateCHRBanks();
updateCIRAMBanks();
break;
case 0xd001:
mirroringReg = data & 0x03;
updateCIRAMBanks();
break;
case 0xd002:
ntRamSelectBit = data & 0x80;
updateCIRAMBanks();
break;
case 0xd003:
mirrorChr = (data & 0x80) == 0x80;
chrBlockMode = (data & 0x20) == 0x00;
chrBlock = (data & 0x01) | ((data & 0x18) >> 2);
exPrgMode = (data & 0x06) >> 1;
updatePRGBanks();
updateCHRBanks();
break;
}
}
auto getCHRReg(u32 index) -> n16 {
if(chrMode >= 2 && mirrorChr) {
if(index == 2)
index = 0;
else if(index == 3)
index = 1;
}
if(chrBlockMode) {
switch(chrMode) {
default:
case 0: return (chrLowRegs[index] & 0x1f) | (chrBlock << 5);
case 1: return (chrLowRegs[index] & 0x3f) | (chrBlock << 6);
case 2: return (chrLowRegs[index] & 0x7f) | (chrBlock << 7);
case 3: return (chrLowRegs[index] & 0xff) | (chrBlock << 8);
}
} else {
return chrLowRegs[index] | (chrHighRegs[index] << 8);
}
}
auto updateCHRBanks() -> void {
n16 bank;
switch(chrMode) {
case 0:
bank = getCHRReg(0) << 3;
for(auto b : range(8)) characterBank[0 + b] = bank + b;
break;
case 1:
bank = getCHRReg(chrLatch[0]) << 2;
for(auto b : range(4)) characterBank[0 + b] = bank + b;
bank = getCHRReg(chrLatch[1]) << 2;
for(auto b : range(4)) characterBank[4 + b] = bank + b;
break;
case 2:
bank = getCHRReg(0) << 1;
for(auto b : range(2)) characterBank[0 + b] = bank + b;
bank = getCHRReg(2) << 1;
for(auto b : range(2)) characterBank[2 + b] = bank + b;
bank = getCHRReg(4) << 1;
for(auto b : range(2)) characterBank[4 + b] = bank + b;
bank = getCHRReg(6) << 1;
for(auto b : range(2)) characterBank[6 + b] = bank + b;
break;
case 3:
for(auto i : range(8)) characterBank[i] = getCHRReg(i);
break;
}
}
auto updateCIRAMBanks() -> void {
if(useAdvancedNt()) {
for(auto i : range(4)) ntBanks[i] = (ntLowRegs[i] & 0x01);
} else {
switch(mirroringReg) {
case 0:
ntBanks[0] = ntBanks[2] = 0;
ntBanks[1] = ntBanks[3] = 1;
break;
case 1:
ntBanks[0] = ntBanks[1] = 0;
ntBanks[2] = ntBanks[3] = 1;
break;
case 2: ntBanks[0] = ntBanks[1] = ntBanks[2] = ntBanks[3] = 0; break;
case 3: ntBanks[0] = ntBanks[1] = ntBanks[2] = ntBanks[3] = 1; break;
}
}
}
auto addressCHR(n32 address) const -> n32 {
return (characterBank[address >> 10 & 0x07] << 10) | (n10)address;
}
auto addressCIRAM(n32 address) const -> n32 {
return (ntBanks[(address >> 10) & 0x03] << 10) | (n10)address;
}
auto readCHR(n32 address, n8 data) -> n8 override {
if(irqSource == IrqSource::PpuRead && !ppu.isRenderingRead()) tickIrqCounter();
if(address & 0x2000) {
if(useAdvancedNt()) {
n8 ntIndex = (address >> 10) & 0x03;
if(disableNtRam || (ntLowRegs[ntIndex] & 0x80) != (ntRamSelectBit & 0x80)) {
// This behavior only affects reads, not writes.
// Additional info: https://forums.nesdev.com/viewtopic.php?f=3&t=17198
u16 chrPage = ntLowRegs[ntIndex] | ntHighRegs[ntIndex] << 8;
return characterROM.read(chrPage << 10 | (n10)address);
} else {
return 0;
}
}
return ppu.readCIRAM(addressCIRAM(address));
}
return characterROM.read(addressCHR(address));
}
auto writeCHR(n32 address, n8 data) -> void override {
if(address & 0x2000) return ppu.writeCIRAM(addressCIRAM(address), data);
}
auto ppuAddressBus(n14 address) -> void override {
if(irqSource == IrqSource::PpuA12Rise && (address & 0x1000) && !(characterAddress & 0x1000)) tickIrqCounter();
characterAddress = address;
if(revision == Revision::JYTypeC) {
switch(address & 0x0ff0) {
case 0x0fd0:
chrLatch[address >> 12 & 0x01] = (address >> 10 & 0x04);
updateCHRBanks();
break;
case 0x0fe0:
chrLatch[address >> 12 & 0x01] = (address >> 10 & 0x04) | 0x02;
updateCHRBanks();
break;
}
}
}
auto power() -> void override {
for(auto& r : prgRegs) r = 0;
exPrgMode = 0;
prgMode = 0;
enablePrgAt6000 = false;
programAt6000Bank = 0;
programBanks[0] = 0;
updatePRGBanks();
for(auto& r : chrLowRegs) r = 0;
for(auto& r : chrHighRegs) r = 0;
chrLatch[0] = 0;
chrLatch[1] = 4;
chrMode = 0;
chrBlockMode = false;
chrBlock = 0;
mirrorChr = false;
for(auto& c : characterBank) c = 0;
updateCHRBanks();
mirroringReg = 0;
advancedNtControl = false;
disableNtRam = false;
ntRamSelectBit = 0;
for(auto& r : ntLowRegs) r = 0;
for(auto& r : ntHighRegs) r = 0;
for(auto& c : ntBanks) c = 0;
updateCIRAMBanks();
irqEnabled = false;
irqSource = IrqSource::CpuM2Rise;
irqCountDirection = 0;
irqFunkyMode = false;
irqFunkyModeReg = 0;
irqSmallPrescaler = false;
irqPrescaler = 0;
irqCounter = 0;
irqXorReg = 0;
irqLine = 0;
characterAddress = 0;
multiplyValue1 = 0;
multiplyValue2 = 0;
regRamValue = 0;
}
auto serialize(serializer& s) -> void override {
s(prgRegs);
s(exPrgMode);
s(prgMode);
s(enablePrgAt6000);
s(programAt6000Bank);
s(programBanks);
s(chrLowRegs);
s(chrHighRegs);
s(chrLatch);
s(chrBlockMode);
s(chrBlock);
s(mirrorChr);
s(characterBank);
s(mirroringReg);
s(advancedNtControl);
s(disableNtRam);
s(ntRamSelectBit);
s(ntLowRegs);
s(ntHighRegs);
s(ntBanks);
s(irqEnabled);
s(irqSource);
s(irqCountDirection);
s(irqFunkyMode);
s(irqFunkyModeReg);
s(irqSmallPrescaler);
s(irqPrescaler);
s(irqCounter);
s(irqXorReg);
s(irqLine);
s(characterAddress);
s(multiplyValue1);
s(multiplyValue2);
s(regRamValue);
}
auto useAdvancedNt() const -> bool {
// Mapper 211 always uses advanced NT control
// Mapper 090 never uses it
// Mapper 209 uses the advancedNtControl flag
if(revision == Revision::JYTypeA) return false;
if(revision == Revision::JYTypeB) return true;
return advancedNtControl;
}
auto invertPrgBits(n8 reg) -> n8 {
return (reg & 0x01) << 6 | (reg & 0x7e) >> 1;
}
auto tickIrqCounter() -> void {
bool clockIrqCounter = false;
n8 mask = irqSmallPrescaler ? (n8)0x07 : (n8)0xff;
n8 prescaler = irqPrescaler & mask;
if(irqCountDirection == 0x01) { // Up
if(prescaler == mask) {
clockIrqCounter = true;
prescaler = 0;
} else {
prescaler++;
}
} else if(irqCountDirection == 0x02) { // Down
if(prescaler == 0) {
clockIrqCounter = true;
prescaler = mask;
} else {
prescaler--;
}
}
irqPrescaler = (irqPrescaler & ~mask) | (prescaler & mask);
if(clockIrqCounter) {
if(irqCountDirection == 0x01) { // Up
if(irqCounter == 0xff) {
irqCounter = 0;
if(irqEnabled) irqLine = 1;
} else {
irqCounter++;
}
} else if(irqCountDirection == 0x02) { // Down
if(irqCounter == 0) {
irqCounter = 0xff;
if(irqEnabled) irqLine = 1;
} else {
irqCounter--;
}
}
}
}
// PRG registers
n8 prgRegs[4];
n3 prgMode;
n2 exPrgMode;
n1 enablePrgAt6000;
n8 programAt6000Bank; // bank at $6000-$7FFF when enabled
n8 programBanks[4]; // 0. $8000-$9FFF
// 1. $A000-$BFFF
// 2. $C000-$DFFF
// 3. $E000-$FFFF
// CHR registers
n8 chrLowRegs[8];
n8 chrHighRegs[8];
n8 chrLatch[2];
n2 chrMode;
n1 chrBlockMode;
n3 chrBlock;
n1 mirrorChr;
n16 characterBank[8];
// Mirroring / Nametable
n2 mirroringReg;
n1 advancedNtControl;
n1 disableNtRam;
n8 ntRamSelectBit;
n8 ntLowRegs[4];
n8 ntHighRegs[4];
n2 ntBanks[4];
// IRQ
n1 irqEnabled;
IrqSource irqSource;
n2 irqCountDirection;
n1 irqFunkyMode;
n8 irqFunkyModeReg;
n1 irqSmallPrescaler;
n8 irqPrescaler;
n8 irqCounter;
n8 irqXorReg;
n1 irqLine;
n16 characterAddress;
// Misc
n8 multiplyValue1;
n8 multiplyValue2;
n8 regRamValue;
};
+133
View File
@@ -0,0 +1,133 @@
struct Sc127 : Interface {
static auto create(string id) -> Interface* {
// Wario Land II (Kirby hack)
if(id == "UNL-SC-127") return new Sc127;
return nullptr;
}
Memory::Readable<n8> programROM;
Memory::Writable<n8> programRAM;
Memory::Readable<n8> characterROM;
auto load() -> void override {
Interface::load(programROM, "program.rom");
Interface::load(programRAM, "save.ram");
Interface::load(characterROM, "character.rom");
}
auto save() -> void override {
Interface::save(programRAM, "save.ram");
}
auto main() -> void override {
if(irqDelay) irqDelay--;
cpu.irqLine(irqLine);
tick();
}
auto readPRG(n32 address, n8 data) -> n8 override {
if(address < 0x6000) return data;
if(address < 0x8000) {
if(!programRAM) return data;
return programRAM.read((n13)address);
}
n8 bank = programBank[(address >> 13) & 3];
return programROM.read(bank << 13 | (n13)address);
}
auto writePRG(n32 address, n8 data) -> void override {
if(address < 0x6000) return;
if(address < 0x8000) {
if(!programRAM) return;
return programRAM.write((n13)address, data);
}
switch(address & 0xf007) {
case 0x8000:
case 0x8001:
case 0x8002:
case 0x8003: programBank[address & 3] = data; break;
case 0x9000:
case 0x9001:
case 0x9002:
case 0x9003:
case 0x9004:
case 0x9005:
case 0x9006:
case 0x9007: characterBank[address & 7] = data; break;
case 0xc002:
irqEnable = 0;
irqLine = 0;
break;
case 0xc003: irqEnable = 1; break;
case 0xc005: irqCounter = data; break;
case 0xd001: mirror = data.bit(0); break;
}
}
auto addressCHR(n32 address) const -> n32 {
return characterBank[address >> 10 & 7] << 10 | (n10)address;
}
auto addressCIRAM(n32 address) const -> n32 {
return address >> mirror & 0x0400 | (n10)address;
}
auto readCHR(n32 address, n8 data) -> n8 override {
if(address & 0x2000) return ppu.readCIRAM(addressCIRAM(address));
return characterROM.read(addressCHR(address));
}
auto writeCHR(n32 address, n8 data) -> void override {
if(address & 0x2000) return ppu.writeCIRAM(addressCIRAM(address), data);
}
//A12 rising edge detection with delay filtering (Mesen A12Watcher style)
auto ppuAddressBus(n14 address) -> void override {
if(!(characterAddress & 0x1000) && (address & 0x1000)) {
if(irqDelay == 0) {
if(irqEnable) {
irqCounter--;
if(irqCounter == 0) {
irqEnable = 0;
irqLine = 1;
}
}
}
irqDelay = 6;
}
characterAddress = address;
}
auto power() -> void override {
programBank[3] = 0xff;
characterBank[0] = 0;
}
auto serialize(serializer& s) -> void override {
s(programRAM);
s(programBank);
s(characterBank);
s(mirror);
s(irqCounter);
s(irqEnable);
s(irqDelay);
s(irqLine);
s(characterAddress);
}
n8 programBank[4];
n8 characterBank[8];
n1 mirror; // 1: horizontal, 0: vertical
n8 irqCounter;
n1 irqEnable;
n8 irqDelay;
n1 irqLine;
n16 characterAddress;
};
+1 -1
View File
@@ -5,7 +5,7 @@ struct Cartridge : Thread {
Node::Peripheral node;
VFS::Pak pak;
auto rate() const -> u32 { return Region::PAL() ? 16 : 12; }
auto rate() const -> u32 { return system.cpuDivider(); }
auto title() const -> string { return information.title; }
auto region() const -> string { return information.region; }
+14 -3
View File
@@ -24,8 +24,17 @@ auto CPU::unload() -> void {
auto CPU::main() -> void {
if(io.interruptPending) {
debugger.interrupt("IRQ");
interrupt();
if(io.resetPending) {
debugger.interrupt("Reset");
reset();
io.resetPending = 0;
} else if(io.nmiPending) {
debugger.interrupt("NMI");
interrupt();
} else {
debugger.interrupt("IRQ");
interrupt();
}
}
debugger.instruction();
@@ -41,7 +50,7 @@ auto CPU::step(u32 clocks) -> void {
auto CPU::power(bool reset) -> void {
MOS6502::BCD = 0;
MOS6502::power(reset);
if(!reset) MOS6502::power();
Thread::create(system.frequency(), std::bind_front(&CPU::main, this));
if(!reset) {
@@ -49,6 +58,8 @@ auto CPU::power(bool reset) -> void {
}
io = {};
io.resetPending = 1;
io.interruptPending = 1;
}
}
+7 -1
View File
@@ -18,7 +18,7 @@ struct CPU : MOS6502, Thread {
} tracer;
} debugger;
auto rate() const -> u32 { return Region::PAL() ? 16 : 12; }
auto rate() const -> u32 { return system.cpuDivider(); }
//cpu.cpp
auto load(Node::Object) -> void;
@@ -56,9 +56,14 @@ struct CPU : MOS6502, Thread {
auto irqLine(bool) -> void;
auto apuLine(bool) -> void;
auto isWriting() -> bool {
return io.rwLine == 0;
}
//protected:
struct IO {
n1 interruptPending;
n1 resetPending;
n1 nmiPending;
n1 nmiLine;
n1 irqLine;
@@ -69,6 +74,7 @@ struct CPU : MOS6502, Thread {
n1 oamDMAPending;
n8 oamDMAPage;
n8 openBus;
n1 rwLine;
} io;
};
+2 -2
View File
@@ -35,10 +35,10 @@ auto CPU::readIO(n16 address) -> n8 {
case 0x4016: {
auto port1 = controllerPort1.data();
auto port3 = expansionPort.read1();
platform->input(system.controls.microphone);
if(system.controls.microphone) platform->input(system.controls.microphone);
data.bit(0) = port1.bit(0);
data.bit(1) = port3.bit(0);
data.bit(2) = system.controls.microphone->value() ? random().bit(0) : 0;
data.bit(2) = system.controls.microphone && system.controls.microphone->value() ? random().bit(0) : 0;
data.bit(3) = port1.bit(1);
data.bit(4) = port1.bit(2);
return data;
+1
View File
@@ -3,6 +3,7 @@ auto CPU::serialize(serializer& s) -> void {
Thread::serialize(s);
s(ram);
s(io.interruptPending);
s(io.resetPending);
s(io.nmiPending);
s(io.nmiLine);
s(io.irqLine);
+5 -2
View File
@@ -10,17 +10,19 @@ auto CPU::read(n16 address) -> n8 {
}
auto CPU::write(n16 address, n8 data) -> void {
io.rwLine = 1;
writeBus(address, data);
io.openBus = data;
step(rate());
io.rwLine = 0;
}
auto CPU::lastCycle() -> void {
io.interruptPending = ((io.irqLine | io.apuLine) & !P.i) | io.nmiPending;
io.interruptPending = irqPending() | io.nmiPending;
}
auto CPU::cancelNmi() -> void {
io.interruptPending = ((io.irqLine | io.apuLine) & !P.i);
io.interruptPending = irqPending();
}
auto CPU::delayIrq() -> void {
@@ -33,6 +35,7 @@ auto CPU::irqPending() -> bool {
auto CPU::nmi(n16& vector) -> void {
if(io.nmiPending) {
if(irqPending()) debugger.interrupt("Haijack IRQ by NMI");
io.nmiPending = false;
vector = 0xfffa;
}
+1
View File
@@ -37,6 +37,7 @@ struct Expansion {
// required for emulating the EPSM.
virtual auto write(n8 data) -> void {}
virtual auto writeIO(n16 address, n8 data) -> void {}
virtual auto serialize(serializer&) -> void {}
};
#include "port.hpp"
@@ -0,0 +1,155 @@
FamicomDataRecorder::FamicomDataRecorder(Node::Port port) {
node = port->append<Node::Tape>("Famicom Data Recorder");
node->setSupportPlay(true);
node->setSupportRecord(true);
node->setLoad([&] { return load(); });
node->setUnload([&] { unload(); });
node->setPosition(0);
node->setLength(0);
node->setFrequency(44100);
data.reset();
output = 0;
input = 0;
speed = system.frequency() / node->frequency();
Thread::create(system.frequency(), std::bind_front(&FamicomDataRecorder::main, this));
}
FamicomDataRecorder::~FamicomDataRecorder() {
Thread::destroy();
node.reset();
}
auto FamicomDataRecorder::read() -> n1 {
if (!node->playing())
return 0;
return output;
}
auto FamicomDataRecorder::write(n1 data) -> void {
if (!node->recording())
return;
input = data;
}
auto FamicomDataRecorder::load() -> bool {
if (!node->setPak(pak = platform->pak(node)))
return false;
node->setPosition(0);
node->setLength(pak->attribute("length").natural());
node->setFrequency(pak->attribute("frequency").natural());
if (node->length() != 0) {
auto fd = pak->read("program.tape");
if (!fd)
return false;
data.allocate(node->length());
data.load(fd);
fd.reset();
}
range = pak->attribute("range").natural();
speed = system.frequency() / node->frequency();
return true;
}
auto FamicomDataRecorder::unload() -> void {
if (!pak)
return;
if (data.size() != 0) {
auto fd = pak->write("program.tape");
fd->resize(data.size() * sizeof(u64));
data.save(fd);
fd.reset();
}
node->setFrequency(44100);
node->setPosition(0);
node->setLength(0);
pak.reset();
output = 0;
input = 0;
range = 0;
speed = system.frequency() / 44100;
data.reset();
}
auto FamicomDataRecorder::main() -> void {
u64 position = node->position();
u64 length = node->length();
if (node->playing()) {
if (position >= length) {
node->stop();
return step();
}
output = data.read(position++) > range / 2 ? 1 : 0;
node->setPosition(position);
return step();
}
if (node->recording()) {
u64 frequency = node->frequency();
if (length <= position) {
auto fd = pak->write("program.tape");
fd->resize(data.size() * sizeof(u64));
data.save(fd);
fd.reset();
length = (position / frequency + 1) * frequency;
fd = pak->read("program.tape");
data.allocate(length, 0);
data.load(fd);
fd.reset();
node->setLength(length);
}
data[position++] = (range >> 1) + (input ? 1 : 0);
node->setPosition(position);
return step();
}
return step();
}
auto FamicomDataRecorder::step() -> void {
Thread::step(speed);
Thread::synchronize();
}
auto FamicomDataRecorder::serialize(serializer& s) -> void {
u64 position = node ? node->position() : 0;
u64 length = node ? node->length() : 0;
u64 frequency = node ? node->frequency() : 44100;
bool playing = node ? node->playing() : false;
bool recording = node ? node->recording() : false;
Thread::serialize(s);
s(position);
s(length);
s(frequency);
s(playing);
s(recording);
s(output);
s(input);
s(speed);
s(range);
s(data);
if(s.reading() && node) {
node->setPosition(position);
node->setLength(length);
node->setFrequency(frequency);
node->stop();
if(playing) node->play();
if(recording) node->record();
}
}
@@ -0,0 +1,26 @@
struct FamicomDataRecorder : Tape, Thread {
Node::Tape node;
explicit FamicomDataRecorder(Node::Port);
~FamicomDataRecorder() override;
auto read() -> n1 override;
auto write(n1 data) -> void override;
auto load() -> bool;
auto unload() -> void;
auto main() -> void;
auto step() -> void;
auto serialize(serializer&) -> void override;
private:
VFS::Pak pak;
n1 output;
n1 input;
u64 speed;
u64 range;
Memory::Writable<u64> data;
};
@@ -1,3 +1,5 @@
#include "famicom-data-recorder.cpp"
FamilyKeyboard::FamilyKeyboard(Node::Port parent) {
node = parent->append<Node::Peripheral>("Family Keyboard");
@@ -80,12 +82,13 @@ FamilyKeyboard::FamilyKeyboard(Node::Port parent) {
key.down = node->append<Node::Input::Button>("Down");
key.left = node->append<Node::Input::Button>("Left");
key.right = node->append<Node::Input::Button>("Right");
tape = std::make_unique<FamicomDataRecorder>(parent);
}
auto FamilyKeyboard::read1() -> n1 {
n1 data;
//data recorder (unsupported)
return data;
if (!latch.bit(2)) return 0b0;
return tape->read();
}
auto FamilyKeyboard::read2() -> n5 {
@@ -219,4 +222,14 @@ auto FamilyKeyboard::write(n8 data) -> void {
if(column && !latch.bit(1)) row = (row + 1) % 10;
column = latch.bit(1);
if(latch.bit(0)) row = 0;
tape->write(data.bit(0));
}
auto FamilyKeyboard::serialize(serializer& s) -> void {
s(latch);
s(column);
s(row);
tape->serialize(s);
}
@@ -1,3 +1,10 @@
struct Tape {
virtual ~Tape() = default;
virtual auto read() -> n1 = 0;
virtual auto write(n1 data) -> void = 0;
virtual auto serialize(serializer&) -> void {}
};
struct FamilyKeyboard : Expansion {
struct Key {
Node::Input::Button f1, f2, f3, f4, f5, f6, f7, f8;
@@ -14,9 +21,13 @@ struct FamilyKeyboard : Expansion {
auto read1() -> n1 override;
auto read2() -> n5 override;
auto write(n8 data) -> void override;
auto serialize(serializer&) -> void override;
private:
std::unique_ptr<Tape> tape;
n3 latch;
n1 column;
n4 row;
};
#include "famicom-data-recorder.hpp"
+1
View File
@@ -26,4 +26,5 @@ auto ExpansionPort::allocate(string name) -> Node::Peripheral {
}
auto ExpansionPort::serialize(serializer& s) -> void {
if(device) device->serialize(s);
}
+2 -1
View File
@@ -19,7 +19,8 @@ namespace ares::Famicom {
struct Region {
static inline auto NTSCJ() -> bool;
static inline auto NTSCU() -> bool;
static inline auto PAL() -> bool;
static inline auto PAL() -> bool;
static inline auto Dendy() -> bool;
};
#include <fc/controller/controller.hpp>
+2
View File
@@ -54,7 +54,9 @@ auto PPU::readIO(n16 address) -> n8 {
address = io.busAddress;
result = var.latchData;
io.renderingRead = 0;
var.latchData = cartridge.readCHR(address);
io.renderingRead = 1;
if (address >= 0x3f00)
result = readCGRAM(address) | (io.mdr & 0xc0);
+6 -5
View File
@@ -23,7 +23,7 @@ auto PPU::load(Node::Object parent) -> void {
screen->colors(1 << 9, std::bind_front(&PPU::color, this));
screen->setSize(283, displayHeight());
screen->setScale(1.0, 1.0);
Region::PAL() ? screen->setAspect(55.0, 43.0) :screen->setAspect(8.0, 7.0);
(Region::PAL() || Region::Dendy()) ? screen->setAspect(55.0, 43.0) : screen->setAspect(8.0, 7.0);
screen->refreshRateHint(system.frequency() / rate(), 341, vlines());
debugger.load(node);
@@ -61,10 +61,11 @@ auto PPU::step(u32 clocks) -> void {
if (enable() && (io.ly < 240 || io.ly == L - 1))
cycleScroll();
if(io.ly == 240 && io.lx == 1) io.busAddress = var.address, cartridge.ppuAddressBus(io.busAddress);
if(io.ly == 240 && io.lx == 340) io.nmiHold = 1;
if(io.ly == 241 && io.lx == 0) io.nmiFlag = io.nmiHold;
if(io.ly == 241 && io.lx == 2) cpu.nmiLine(io.nmiEnable && io.nmiFlag);
u32 vbs = vblankScanline();
if(io.ly == vbs - 1 && io.lx == 1) io.busAddress = var.address, cartridge.ppuAddressBus(io.busAddress);
if(io.ly == vbs - 1 && io.lx == 340) io.nmiHold = 1;
if(io.ly == vbs && io.lx == 0) io.nmiFlag = io.nmiHold;
if(io.ly == vbs && io.lx == 2) cpu.nmiLine(io.nmiEnable && io.nmiFlag);
if(io.ly == L-2 && io.lx == 340) io.spriteZeroHit = 0, sprite.spriteOverflow = 0;
+9 -2
View File
@@ -19,9 +19,10 @@ struct PPU : Thread {
} memory;
} debugger;
auto rate() const -> u32 { return Region::PAL() ? 5 : 4; }
auto vlines() const -> u32 { return Region::PAL() ? 312 : 262; }
auto rate() const -> u32 { return Region::PAL() || Region::Dendy() ? 5 : 4; }
auto vlines() const -> u32 { return Region::PAL() || Region::Dendy() ? 312 : 262; }
auto displayHeight() const -> u32 { return Region::PAL() ? 288 : 242; }
auto vblankScanline() const -> u32 { return Region::Dendy() ? 291 : 241; }
//ppu.cpp
auto load(Node::Object) -> void;
@@ -71,6 +72,10 @@ struct PPU : Thread {
auto cycleSpriteEvaluation() -> void;
auto cyclePrepareSpriteEvaluation() -> void;
auto isRenderingRead() const -> bool {
return io.renderingRead;
}
struct ScrollRegisters {
n15 data;
@@ -121,6 +126,8 @@ struct PPU : Thread {
n1 nmiHold;
n1 nmiFlag;
n1 renderingRead;
//$2000
n6 vramIncrement = 1; //1 or 32
n16 spriteAddress; //0x0000 or 0x1000
+2 -2
View File
@@ -72,7 +72,7 @@ auto PPU::renderPixel() -> void {
color = io.emphasis << 6 | readCGRAM((n5)var.address);
}
if(Region::PAL())
if(Region::PAL() || Region::Dendy())
output[(x + 18) % 283] = color;
else
output[(x + 16) % 283] = color;
@@ -188,7 +188,7 @@ auto PPU::renderScanline() -> void {
//337-338
loadCHR(0x2000 | (n12)var.address);
bool skip = !Region::PAL() && enable() && io.field == 1 && io.ly == vlines() - 1;
bool skip = !Region::PAL() && !Region::Dendy() && enable() && io.field == 1 && io.ly == vlines() - 1;
step(2);
//339
+11 -3
View File
@@ -1,11 +1,19 @@
auto System::Controls::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("Controls");
reset = node->append<Node::Input::Button>("Reset");
microphone = node->append<Node::Input::Button>("Microphone");
reset = node->append<Node::Input::Button>("Reset");
if(system.region() != System::Region::Dendy) {
microphone = node->append<Node::Input::Button>("Microphone");
}
}
auto System::Controls::unload() -> void {
microphone.reset();
reset.reset();
node.reset();
}
auto System::Controls::poll() -> void {
platform->input(reset);
platform->input(microphone);
if(microphone) platform->input(microphone);
}
+2 -1
View File
@@ -1,4 +1,4 @@
static const string SerializerVersion = "v151";
static const string SerializerVersion = "v153";
auto System::serialize(bool synchronize) -> serializer {
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
@@ -46,4 +46,5 @@ auto System::serialize(serializer& s, bool synchronize) -> void {
if(fds.present) s(fds);
s(controllerPort1);
s(controllerPort2);
expansionPort.serialize(s);
}
+20 -9
View File
@@ -8,6 +8,7 @@ auto enumerate() -> std::vector<string> {
"[Nintendo] Famicom (NTSC-J)",
"[Nintendo] Famicom (NTSC-U)",
"[Nintendo] Famicom (PAL)",
"[Dendy] Dendy",
};
}
@@ -47,19 +48,28 @@ auto System::load(Node::System& root, string name) -> bool {
information = {};
if(name.find("NTSC-J")) {
information.name = "Famicom";
information.region = Region::NTSCJ;
information.frequency = Constants::Colorburst::NTSC * 6.0;
information.name = "Famicom";
information.region = Region::NTSCJ;
information.frequency = Constants::Colorburst::NTSC * 6.0;
information.cpuDivider = 12;
}
if(name.find("NTSC-U")) {
information.name = "Famicom";
information.region = Region::NTSCU;
information.frequency = Constants::Colorburst::NTSC * 6.0;
information.name = "Famicom";
information.region = Region::NTSCU;
information.frequency = Constants::Colorburst::NTSC * 6.0;
information.cpuDivider = 12;
}
if(name.find("PAL")) {
information.name = "Famicom";
information.region = Region::PAL;
information.frequency = Constants::Colorburst::PAL * 6.0;
information.name = "Famicom";
information.region = Region::PAL;
information.frequency = Constants::Colorburst::PAL * 6.0;
information.cpuDivider = 16;
}
if(name.find("Dendy")) {
information.name = "Famicom";
information.region = Region::Dendy;
information.frequency = Constants::Colorburst::PAL * 6.0;
information.cpuDivider = 15;
}
node = std::make_shared<Core::System>(information.name);
@@ -94,6 +104,7 @@ auto System::save() -> void {
auto System::unload() -> void {
if(!node) return;
save();
controls.unload();
cpu.unload();
apu.unload();
ppu.unload();
+12 -8
View File
@@ -10,14 +10,16 @@ struct System {
//controls.cpp
auto load(Node::Object) -> void;
auto unload() -> void;
auto poll() -> void;
} controls;
enum class Region : u32 { NTSCJ, NTSCU, PAL };
enum class Region : u32 { NTSCJ, NTSCU, PAL, Dendy };
auto name() const -> string { return information.name; }
auto region() const -> Region { return information.region; }
auto frequency() const -> f64 { return information.frequency; }
auto name() const -> string { return information.name; }
auto region() const -> Region { return information.region; }
auto frequency() const -> f64 { return information.frequency; }
auto cpuDivider() const -> u8 { return information.cpuDivider; }
//system.cpp
auto game() -> string;
@@ -34,9 +36,10 @@ struct System {
private:
struct Information {
string name = "Famicom";
Region region = Region::NTSCJ;
f64 frequency = Constants::Colorburst::NTSC * 6.0;
string name = "Famicom";
Region region = Region::NTSCJ;
f64 frequency = Constants::Colorburst::NTSC * 6.0;
u8 cpuDivider = 12;
} information;
//serialization.cpp
@@ -47,4 +50,5 @@ extern System system;
auto Region::NTSCJ() -> bool { return system.region() == System::Region::NTSCJ; }
auto Region::NTSCU() -> bool { return system.region() == System::Region::NTSCU; }
auto Region::PAL() -> bool { return system.region() == System::Region::PAL; }
auto Region::PAL() -> bool { return system.region() == System::Region::PAL; }
auto Region::Dendy() -> bool { return system.region() == System::Region::Dendy; }
+14
View File
@@ -36,6 +36,20 @@ ares_add_sources(
apu/wave.cpp
)
ares_add_sources(
CORE #
gb
INCLUDED #
ppu/cgb.cpp
ppu/color.cpp
ppu/debugger.cpp
ppu/dmg.cpp
ppu/io.cpp
ppu/ppu.hpp
ppu/serialization.cpp
ppu/timing.cpp
)
ares_add_sources(
CORE #
gb
+1 -1
View File
@@ -142,7 +142,7 @@ auto APU::readIO(n32 address) -> n8 {
}
return cpu.openBus.get(Byte, address);
return cpu.mdr >> (8 * (address & 3));
}
auto APU::writeIO(n32 address, n8 data) -> void {
+4 -4
View File
@@ -57,7 +57,7 @@ auto APU::serialize(serializer& s) -> void {
s(wave.frequency);
s(wave.counter);
s(wave.initialize);
s(wave.pattern);
for(auto& p : wave.pattern) s(p);
s(wave.enable);
s(wave.output);
s(wave.patternaddr);
@@ -85,8 +85,8 @@ auto APU::serialize(serializer& s) -> void {
s(sequencer.volume);
s(sequencer.lvolume);
s(sequencer.rvolume);
s(sequencer.lenable);
s(sequencer.renable);
for(auto& l :sequencer.lenable) s(l);
for(auto& r: sequencer.renable) s(r);
s(sequencer.masterenable);
s(sequencer.base);
s(sequencer.step);
@@ -97,7 +97,7 @@ auto APU::serialize(serializer& s) -> void {
for(auto& f : fifo) {
s(f.buffer);
s(f.samples);
for(auto& sa : f.samples) s(sa);
s(f.active);
s(f.output);
s(f.rdoffset);
+1 -1
View File
@@ -66,7 +66,7 @@ auto APU::Wave::write(u32 address, n8 byte) -> void {
}
auto APU::Wave::freeBank() const -> n1 {
return bank && apu.sequencer.masterenable;
return !(bank && apu.sequencer.masterenable);
}
auto APU::Wave::readRAM(u32 address) const -> n8 {
+1
View File
@@ -12,6 +12,7 @@ auto Cartridge::MROM::serialize(serializer& s) -> void {
s(mask);
s(pageAddr);
s(burst);
s(mirror);
}
auto Cartridge::SRAM::serialize(serializer& s) -> void {
+31 -53
View File
@@ -12,7 +12,7 @@ inline auto CPU::getBus(u32 mode, n32 address) -> n32 {
ARM7TDMI::irq = irq.synchronizer;
context.romAccess = false;
}
u32 word;
u32 word = mdr;
if(memory.biosSwap && address < 0x0400'0000) address ^= 0x0200'0000;
@@ -23,33 +23,36 @@ inline auto CPU::getBus(u32 mode, n32 address) -> n32 {
word = bios.read(mode, address);
break;
case 0x02: word = readEWRAM<UseDebugger>(mode, address); break;
case 0x02:
if(memory.ewram) word = readEWRAM<UseDebugger>(mode, address);
else word = readIWRAM<UseDebugger>(mode, address);
break;
case 0x03: word = readIWRAM<UseDebugger>(mode, address); break;
case 0x04:
if constexpr(!UseDebugger) prefetchStep(1);
if((address & 0xffff'fc00) == 0x0400'0000) word = bus.io[address & 0x3ff]->readIO(mode, address);
else if((address & 0xff00'fffc) == 0x0400'0800) word = ((IO*)this)->readIO(mode, 0x0400'0800 | (address & 3));
else return openBus.get(mode, address);
break;
case 0x05: word = readPRAM<UseDebugger>(mode, address); break;
case 0x06: word = readVRAM<UseDebugger>(mode, address); break;
case 0x07:
if constexpr(!UseDebugger) prefetchStep(1);
word = ppu.readOAM(mode, address);
break;
case 0x07: word = readOAM<UseDebugger>(mode, address); break;
case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d:
if constexpr(UseDebugger) return readROM<true>(mode, address);
context.burstActive = checkBurst<IsDMA>(mode);
context.romAccess = true;
if(mode & Prefetch && wait.prefetch) {
if(address == prefetch.addr && (!prefetch.empty() || prefetch.ahead)) {
prefetchStepInternal(1);
if(mode & Prefetch) {
if((address & 0x1fffe) && wait.prefetch && address == prefetch.addr && (!prefetch.empty() || prefetch.ahead)) {
prefetchStep(1);
word = prefetchRead();
if(mode & Word) word |= prefetchRead() << 16;
if(mode & Word) {
word |= prefetchRead() << 16;
} else {
word |= word << 16;
}
} else {
if(mode & Word) address &= ~3; //prevents misaligned PC from reading incorrect values
prefetchSync(mode, address);
@@ -65,19 +68,22 @@ inline auto CPU::getBus(u32 mode, n32 address) -> n32 {
case 0x0e: case 0x0f:
if constexpr(!UseDebugger) prefetchReset();
if constexpr(!UseDebugger) step(waitCartridge(address, false));
word = cartridge.readBackup(address) * 0x01010101;
word = cartridge.readBackup(address);
word |= word << 8;
word |= word << 16;
break;
default:
if constexpr(!UseDebugger) prefetchStep(1);
return openBus.get(mode, address);
break;
}
openBus.set(mode, address, word);
if(auto result = platform->cheat(address)) return *result;
return word;
if constexpr(!UseDebugger) mdr = word;
if(mode & Word) address &= ~3;
if(mode & Half) address &= ~1;
return word >> (8 * (address & 3));
}
auto CPU::get(u32 mode, n32 address) -> n32 {
@@ -102,6 +108,8 @@ auto CPU::setBus(u32 mode, n32 address, n32 word) -> void {
if(memory.biosSwap && address < 0x0400'0000) address ^= 0x0200'0000;
mdr = word;
switch(address >> 24) {
case 0x00: case 0x01:
@@ -109,7 +117,11 @@ auto CPU::setBus(u32 mode, n32 address, n32 word) -> void {
bios.write(mode, address, word);
break;
case 0x02: writeEWRAM(mode, address, word); break;
case 0x02:
if(memory.ewram) writeEWRAM(mode, address, word);
else writeIWRAM(mode, address, word);
break;
case 0x03: writeIWRAM(mode, address, word); break;
case 0x04:
@@ -120,12 +132,7 @@ auto CPU::setBus(u32 mode, n32 address, n32 word) -> void {
case 0x05: writePRAM(mode, address, word); break;
case 0x06: writeVRAM(mode, address, word); break;
case 0x07:
prefetchStep(1);
synchronize(ppu);
ppu.writeOAM(mode, address, word);
break;
case 0x07: writeOAM(mode, address, word); break;
case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d:
context.burstActive = checkBurst<IsDMA>(mode);
@@ -149,7 +156,6 @@ auto CPU::setBus(u32 mode, n32 address, n32 word) -> void {
}
openBus.set(mode, address, word);
if constexpr(!IsDMA) {
if(!context.romAccess) cartridge.mrom.burst = false;
}
@@ -195,31 +201,3 @@ auto CPU::checkBurst(u32 mode) -> bool {
if constexpr(IsDMA) return dmac.romBurst;
return !ARM7TDMI::nonsequential;
}
auto CPU::OpenBus::get(u32 mode, n32 address) -> n32 {
if(mode & Word) address &= ~3;
if(mode & Half) address &= ~1;
return data >> (8 * (address & 3));
}
auto CPU::OpenBus::set(u32 mode, n32 address, n32 word) -> void {
if(address >> 24 == 0x3) {
//open bus from IWRAM only overwrites part of the last IWRAM value accessed
if(mode & Word) {
iwramData = word;
} else if(mode & Half) {
if(address & 2) {
iwramData.bit(16,31) = (n16)word;
} else {
iwramData.bit( 0,15) = (n16)word;
}
} else if(mode & Byte) {
iwramData.byte(address & 3) = (n8)word;
}
data = iwramData;
} else {
if(mode & Byte) word = (word & 0xff) * 0x01010101;
if(mode & Half) word = (word & 0xffff) * 0x00010001;
data = word;
}
}
+1 -1
View File
@@ -12,5 +12,5 @@ auto CPU::Coprocessor::debugMCR() -> void {
}
auto CPU::Coprocessor::debugMRC() -> n32 {
return cpu.openBus.get(Word, 0);
return cpu.mdr;
}
+8 -9
View File
@@ -53,7 +53,6 @@ struct CPU : ARM7TDMI, Thread, IO {
//prefetch.cpp
auto prefetchSync(u32 mode, n32 address) -> void;
auto prefetchStepInternal(u32 clocks) -> void;
auto prefetchStep(u32 clocks) -> void;
auto prefetchReset() -> void;
auto prefetchRead() -> n16;
@@ -88,6 +87,9 @@ struct CPU : ARM7TDMI, Thread, IO {
template<bool UseDebugger> auto readVRAM(u32 mode, n32 address) -> n32;
auto writeVRAM(u32 mode, n32 address, n32 word) -> void;
template<bool UseDebugger> auto readOAM(u32 mode, n32 address) -> n32;
auto writeOAM(u32 mode, n32 address, n32 word) -> void;
template<bool UseDebugger> auto readROM(u32 mode, n32 address) -> n32;
auto writeROM(u32 mode, n32 address, n32 word) -> void;
@@ -202,8 +204,10 @@ struct CPU : ARM7TDMI, Thread, IO {
n1 enable;
n1 condition;
n1 flag[10];
n1 keyStates[10];
n1 conditionMet;
n1 irq;
} keypad;
struct Joybus {
@@ -255,17 +259,9 @@ struct CPU : ARM7TDMI, Thread, IO {
n4 unknown2;
} memory;
struct OpenBus {
auto get(u32 mode, n32 address) -> n32;
auto set(u32 mode, n32 address, n32 word) -> void;
n32 data;
n32 iwramData;
} openBus;
struct {
auto empty() const { return addr == load; }
auto full() const { return load - addr == 16; }
auto size() const { return (load - addr) >> 1; }
n16 slot[8];
n32 addr; //read location of slot buffer
@@ -293,6 +289,9 @@ struct CPU : ARM7TDMI, Thread, IO {
n1 burstActive;
n32 hcounter;
} context;
n32 iwramBus;
n32 mdr;
};
extern CPU cpu;
+9 -3
View File
@@ -59,7 +59,10 @@ auto CPU::DMAC::Channel::read() -> void {
if(mode & Word) addr &= ~3;
if(mode & Half) addr &= ~1;
latch.data = cpu.getDMA(mode, addr);
if(mode & Half) latch.data |= latch.data << 16;
if(mode & Half) {
latch.data &= 0xffff;
latch.data |= latch.data << 16;
}
}
switch(sourceMode) {
@@ -93,8 +96,11 @@ auto CPU::DMAC::Channel::write() -> void {
if(!latch.length()) {
active = false;
if(targetMode == 3) latch.target = target;
if(repeat == 1) latch.length = length;
if(repeat == 0) enable = false;
if(repeat == 0 || timingMode == 0) {
enable = false;
} else {
latch.length = length;
}
}
cpu.dmac.writeCycle = false;
+1 -1
View File
@@ -245,7 +245,7 @@ auto CPU::readIO(n32 address) -> n8 {
}
return cpu.openBus.get(Byte, address);
return mdr >> (8 * (address & 3));
}
auto CPU::writeIO(n32 address, n8 data) -> void {
+5 -2
View File
@@ -14,11 +14,14 @@ auto CPU::Keypad::run() -> void {
conditionMet = condition; //0 = OR, 1 = AND
for(u32 index : range(10)) {
if(lookup[index] != keyStates[index]) irq = false; //force IRQ line low temporarily when key state changes
keyStates[index] = lookup[index];
if(!flag[index]) continue;
n1 input = lookup[index];
if(condition == 0) conditionMet |= input;
if(condition == 1) conditionMet &= input;
}
if(conditionMet && enable) cpu.setInterruptFlag(CPU::Interrupt::Keypad);
bool irqPrev = irq;
irq = conditionMet && enable;
if(irq && !irqPrev) cpu.setInterruptFlag(CPU::Interrupt::Keypad);
}
+56 -27
View File
@@ -1,25 +1,33 @@
template <bool UseDebugger>
auto CPU::readIWRAM(u32 mode, n32 address) -> n32 {
if constexpr(!UseDebugger) prefetchStep(1);
n32 word;
if constexpr(UseDebugger) {
address &= 0x7ffc;
return iwram[address + 0] << 0 | iwram[address + 1] << 8 | iwram[address + 2] << 16 | iwram[address + 3] << 24;
}
prefetchStep(1);
if(mode & Word) {
address &= 0x7ffc;
word = iwram[address + 0] << 0 | iwram[address + 1] << 8 | iwram[address + 2] << 16 | iwram[address + 3] << 24;
iwramBus = iwram[address + 0] << 0 | iwram[address + 1] << 8 | iwram[address + 2] << 16 | iwram[address + 3] << 24;
} else if(mode & Half) {
address &= 0x7ffe;
word = iwram[address + 0] << 0 | iwram[address + 1] << 8;
n16 half = iwram[address + 0] << 0 | iwram[address + 1] << 8;
if(address & 2) {
iwramBus.bit(16,31) = half;
} else {
iwramBus.bit( 0,15) = half;
}
} else {
address &= 0x7fff;
word = iwram[address];
iwramBus.byte(address & 3) = iwram[address];
}
return word;
return iwramBus;
}
auto CPU::writeIWRAM(u32 mode, n32 address, n32 word) -> void {
prefetchStep(1);
if(mode & Word) {
address &= 0x7ffc;
iwramBus = word;
iwram[address + 0] = word >> 0;
iwram[address + 1] = word >> 8;
iwram[address + 2] = word >> 16;
@@ -27,33 +35,31 @@ auto CPU::writeIWRAM(u32 mode, n32 address, n32 word) -> void {
return;
} else if(mode & Half) {
address &= 0x7ffe;
if(address & 2) {
iwramBus.bit(16,31) = word;
} else {
iwramBus.bit( 0,15) = word;
}
iwram[address + 0] = word >> 0;
iwram[address + 1] = word >> 8;
} else {
address &= 0x7fff;
iwramBus.byte(address & 3) = word;
iwram[address] = word;
}
}
template <bool UseDebugger>
auto CPU::readEWRAM(u32 mode, n32 address) -> n32 {
if(!memory.ewram) return readIWRAM<UseDebugger>(mode, address);
if(mode & Word) return readEWRAM<UseDebugger>(Half, address &~ 2) << 0 | readEWRAM<UseDebugger>(Half, address | 2) << 16;
if(mode & Word) return (n16)readEWRAM<UseDebugger>(Half, address & ~2) << 0 | (n16)readEWRAM<UseDebugger>(Half, address | 2) << 16;
if constexpr(!UseDebugger) prefetchStep(16 - memory.ewramWait);
n16 half;
address &= 0x3ffff;
if(mode & Half) {
half = ewram[address & ~1] << 0 | ewram[address | 1] << 8;
} else {
half = ewram[address];
}
return half;
n16 half = ewram[address & ~1] << 0 | ewram[address | 1] << 8;
return half << 0 | half << 16;
}
auto CPU::writeEWRAM(u32 mode, n32 address, n32 word) -> void {
if(!memory.ewram) return writeIWRAM(mode, address, word);
if(mode & Word) {
writeEWRAM(Half, address &~2, word >> 0);
writeEWRAM(Half, address | 2, word >> 16);
@@ -72,7 +78,7 @@ auto CPU::writeEWRAM(u32 mode, n32 address, n32 word) -> void {
template <bool UseDebugger>
auto CPU::readPRAM(u32 mode, n32 address) -> n32 {
if(mode & Word) return readPRAM<UseDebugger>(Half, address & ~2) << 0 | readPRAM<UseDebugger>(Half, address | 2) << 16;
if(mode & Word) return (n16)readPRAM<UseDebugger>(Half, address & ~2) << 0 | (n16)readPRAM<UseDebugger>(Half, address | 2) << 16;
//stall until PPU is no longer accessing PRAM (minimum 1 cycle)
if constexpr(!UseDebugger) {
@@ -82,7 +88,8 @@ auto CPU::readPRAM(u32 mode, n32 address) -> n32 {
} while(ppu.pramContention());
}
return ppu.readPRAM(mode, address);
n16 half = ppu.readPRAM(mode, address);
return half << 0 | half << 16;
}
auto CPU::writePRAM(u32 mode, n32 address, n32 word) -> void {
@@ -103,7 +110,7 @@ auto CPU::writePRAM(u32 mode, n32 address, n32 word) -> void {
template <bool UseDebugger>
auto CPU::readVRAM(u32 mode, n32 address) -> n32 {
if(mode & Word) return readVRAM<UseDebugger>(Half, address & ~2) << 0 | readVRAM<UseDebugger>(Half, address | 2) << 16;
if(mode & Word) return (n16)readVRAM<UseDebugger>(Half, address & ~2) << 0 | (n16)readVRAM<UseDebugger>(Half, address | 2) << 16;
//stall until PPU is no longer accessing VRAM (minimum 1 cycle)
if constexpr(!UseDebugger) {
@@ -113,7 +120,8 @@ auto CPU::readVRAM(u32 mode, n32 address) -> n32 {
} while(ppu.vramContention(address));
}
return ppu.readVRAM(mode, address);
n16 half = ppu.readVRAM(mode, address);
return half << 0 | half << 16;
}
auto CPU::writeVRAM(u32 mode, n32 address, n32 word) -> void {
@@ -132,12 +140,35 @@ auto CPU::writeVRAM(u32 mode, n32 address, n32 word) -> void {
ppu.writeVRAM(mode, address, word);
}
template <bool UseDebugger>
auto CPU::readOAM(u32 mode, n32 address) -> n32 {
//stall until PPU is no longer accessing OAM (minimum 1 cycle)
if constexpr(!UseDebugger) {
do {
prefetchStep(1);
synchronize(ppu);
} while(ppu.oamContention());
}
return ppu.readOAM(mode, address);
}
auto CPU::writeOAM(u32 mode, n32 address, n32 word) -> void {
//stall until PPU is no longer accessing OAM (minimum 1 cycle)
do {
prefetchStep(1);
synchronize(ppu);
} while(ppu.oamContention());
ppu.writeOAM(mode, address, word);
}
template<bool UseDebugger>
auto CPU::readROM(u32 mode, n32 address) -> n32 {
if(mode & Word) {
n32 word = 0;
word |= readROM<UseDebugger>(Half, address & ~2) << 0;
word |= readROM<UseDebugger>(Half, address | 2) << 16;
word |= (n16)readROM<UseDebugger>(Half, address & ~2) << 0;
word |= (n16)readROM<UseDebugger>(Half, address | 2) << 16;
return word;
}
@@ -147,9 +178,7 @@ auto CPU::readROM(u32 mode, n32 address) -> n32 {
}
n16 half = cartridge.readRom<UseDebugger>(address);
if constexpr(!UseDebugger) context.burstActive = true;
if(mode & Byte) return half.byte(address & 1);
return half;
return half << 0 | half << 16;
}
auto CPU::writeROM(u32 mode, n32 address, n32 word) -> void {
+8 -10
View File
@@ -9,7 +9,7 @@ auto CPU::prefetchSync(u32 mode, n32 address) -> void {
prefetch.wait = waitCartridge(prefetch.load, true);
}
auto CPU::prefetchStepInternal(u32 clocks) -> void {
auto CPU::prefetchStep(u32 clocks) -> void {
step(clocks);
if(!wait.prefetch || prefetch.stopped) return;
@@ -19,19 +19,17 @@ auto CPU::prefetchStepInternal(u32 clocks) -> void {
prefetch.ahead = false;
break;
}
prefetch.ahead = true;
if(--prefetch.wait) continue;
cartridge.startBurst(prefetch.load); //todo: only start burst when necessary
prefetch.slot[prefetch.load >> 1 & 7] = cartridge.readRom<false>(prefetch.load);
prefetch.load += 2;
if(prefetch.load & 0x1fffe) {
cartridge.startBurst(prefetch.load); //todo: only start burst when necessary
prefetch.slot[prefetch.load >> 1 & 7] = cartridge.readRom<false>(prefetch.load);
prefetch.load += 2;
}
prefetch.wait = waitCartridge(prefetch.load, true);
}
}
auto CPU::prefetchStep(u32 clocks) -> void {
if(wait.prefetch && !prefetch.stopped && prefetch.empty()) prefetch.ahead = true;
prefetchStepInternal(clocks);
}
auto CPU::prefetchReset() -> void {
if(prefetch.wait == 1) step(1);
@@ -47,7 +45,7 @@ auto CPU::prefetchRead() -> n16 {
prefetch.stopped = false;
prefetch.wait = waitCartridge(prefetch.load, false);
}
if(prefetch.empty()) prefetchStepInternal(prefetch.wait);
if(prefetch.empty()) prefetchStep(prefetch.wait);
n16 word = prefetch.slot[prefetch.addr >> 1 & 7];
prefetch.addr += 2;
+6 -4
View File
@@ -66,7 +66,9 @@ auto CPU::serialize(serializer& s) -> void {
s(keypad.enable);
s(keypad.condition);
for(auto& flag : keypad.flag) s(flag);
for(auto& keyState : keypad.keyStates) s(keyState);
s(keypad.conditionMet);
s(keypad.irq);
s(joybus.sc);
s(joybus.sd);
@@ -106,10 +108,7 @@ auto CPU::serialize(serializer& s) -> void {
s(memory.ewramWait);
s(memory.unknown2);
s(openBus.data);
s(openBus.iwramData);
s(prefetch.slot);
for(auto& slot : prefetch.slot) s(slot);
s(prefetch.addr);
s(prefetch.load);
s(prefetch.wait);
@@ -125,4 +124,7 @@ auto CPU::serialize(serializer& s) -> void {
s(context.busLocked);
s(context.burstActive);
s(context.hcounter);
s(iwramBus);
s(mdr);
}
+1 -1
View File
@@ -20,7 +20,7 @@ auto Display::readIO(n32 address) -> n8 {
}
return cpu.openBus.get(Byte, address);
return cpu.mdr >> (8 * (address & 3));
}
auto Display::writeIO(n32 address, n8 data) -> void {
+5 -20
View File
@@ -17,8 +17,11 @@ auto IO::readIO(u32 mode, n32 address) -> n32 {
address &= ~1;
word.byte(0) = readIO(address + 0);
word.byte(1) = readIO(address + 1);
word |= word << 16;
} else if(mode & Byte) {
word.byte(0) = readIO(address + 0);
word = readIO(address + 0);
word |= word << 8;
word |= word << 16;
}
return word;
@@ -42,7 +45,7 @@ auto IO::writeIO(u32 mode, n32 address, n32 word) -> void {
struct UnmappedIO : IO {
auto readIO(n32 address) -> n8 override {
return cpu.openBus.get(Byte, address);
return cpu.mdr >> (8 * (address & 3));
}
auto writeIO(n32 address, n8 byte) -> void override {
@@ -51,24 +54,6 @@ struct UnmappedIO : IO {
static UnmappedIO unmappedIO;
auto Bus::mirror(n32 address, n32 size) -> n32 {
n32 base = 0;
if(size) {
n32 mask = 1 << 27; //28-bit bus
while(address >= size) {
while(!(address & mask)) mask >>= 1;
address -= mask;
if(size > mask) {
size -= mask;
base += mask;
}
mask >>= 1;
}
base += address;
}
return base;
}
auto Bus::power() -> void {
for(u32 n : range(0x400)) io[n] = &unmappedIO;
}
-2
View File
@@ -6,8 +6,6 @@ struct IO {
};
struct Bus {
static auto mirror(n32 address, n32 size) -> n32;
auto power() -> void;
IO* io[0x400] = {nullptr};
+2
View File
@@ -1,4 +1,6 @@
auto Player::serialize(serializer& s) -> void {
Thread::serialize(s);
s(status.enable);
s(status.rumble);
+17 -32
View File
@@ -13,13 +13,22 @@ auto PPU::Background::scanline(u32 y) -> void {
mosaicOffset = 0;
for(auto& pixel : output) pixel = {};
latch.active = false;
if(!io.mosaic || (y % (1 + io.mosaicHeight)) == 0) {
vmosaic = y;
affine.hmosaic = io.lx;
affine.vmosaic = io.ly;
io.lx += io.pb;
io.ly += io.pd;
}
}
auto PPU::Background::outputPixel(u32 x, u32 y) -> void {
//horizontal mosaic
if(!io.mosaic || !mosaicOffset) {
if(!mosaicOffset) {
mosaicOffset = 1 + io.mosaicWidth;
mosaic = output[x];
mosaicLatch = output[x];
} else if(!io.mosaic) {
mosaicLatch = output[x];
}
mosaicOffset--;
}
@@ -50,12 +59,6 @@ auto PPU::Background::run(s32 x, u32 y) -> void {
auto PPU::Background::linearFetchTileMap(s32 x, u32 y) -> void {
if(ppu.blank() || !io.enable[0]) return;
if(x == -7) {
if(!io.mosaic || (y % (1 + io.mosaicHeight)) == 0) {
vmosaic = y;
}
}
fx = x + io.hoffset;
fy = vmosaic + io.voffset;
@@ -128,12 +131,8 @@ auto PPU::Background::affineFetchTileMap(u32 x, u32 y) -> void {
if(ppu.blank() || !io.enable[0]) return;
if(x == 0) {
if(!io.mosaic || (y % (1 + io.mosaicHeight)) == 0) {
hmosaic = io.lx;
vmosaic = io.ly;
}
fx = hmosaic;
fy = vmosaic;
fx = affine.hmosaic;
fy = affine.vmosaic;
}
affine.screenSize = 16 << io.screenSize;
@@ -167,23 +166,14 @@ auto PPU::Background::affineFetchTileData(u32 x, u32 y) -> void {
fx += io.pa;
fy += io.pc;
if(x == 239) {
io.lx += io.pb;
io.ly += io.pd;
}
}
auto PPU::Background::bitmap(u32 x, u32 y) -> void {
if(ppu.blank() || !io.enable[0]) return;
if(x == 0) {
if(!io.mosaic || (y % (1 + io.mosaicHeight)) == 0) {
hmosaic = io.lx;
vmosaic = io.ly;
}
fx = hmosaic;
fy = vmosaic;
fx = affine.hmosaic;
fy = affine.vmosaic;
}
n1 depth = io.mode != 4; //0 = 8-bit (mode 4); 1 = 15-bit (mode 3, mode 5)
@@ -212,11 +202,6 @@ auto PPU::Background::bitmap(u32 x, u32 y) -> void {
fx += io.pa;
fy += io.pc;
if(x == 239) {
io.lx += io.pb;
io.ly += io.pd;
}
}
auto PPU::Background::power(u32 id) -> void {
@@ -224,10 +209,10 @@ auto PPU::Background::power(u32 id) -> void {
io = {};
latch = {};
affine = {};
for(auto& pixel : output) pixel = {};
mosaic = {};
mosaicLatch = {};
mosaicOffset = 0;
hmosaic = 0;
vmosaic = 0;
fx = 0;
fy = 0;
+8 -8
View File
@@ -13,17 +13,17 @@ auto PPU::DAC::upperLayer(u32 x, u32 y) -> void {
n1 active[6] = {true, true, true, true, true, true}; //enable all layers if no windows are enabled
if(ppu.window0.io.enable || ppu.window1.io.enable || ppu.window2.io.enable) {
memory::copy(&active, &ppu.window3.io.active, sizeof(active));
if(ppu.window2.io.enable && ppu.window2.output) memory::copy(&active, &ppu.window2.io.active, sizeof(active));
if(ppu.window1.io.enable && ppu.window1.output) memory::copy(&active, &ppu.window1.io.active, sizeof(active));
if(ppu.window0.io.enable && ppu.window0.output) memory::copy(&active, &ppu.window0.io.active, sizeof(active));
if(ppu.window2.io.enable && ppu.window2.output[x]) memory::copy(&active, &ppu.window2.io.active, sizeof(active));
if(ppu.window1.io.enable && ppu.window1.output[x]) memory::copy(&active, &ppu.window1.io.active, sizeof(active));
if(ppu.window0.io.enable && ppu.window0.output[x]) memory::copy(&active, &ppu.window0.io.active, sizeof(active));
}
//priority sorting: find topmost two pixels
layers[OBJ] = ppu.objects.mosaic;
layers[BG0] = ppu.bg0.mosaic;
layers[BG1] = ppu.bg1.mosaic;
layers[BG2] = ppu.bg2.mosaic;
layers[BG3] = ppu.bg3.mosaic;
layers[OBJ] = ppu.objects.mosaicLatch;
layers[BG0] = ppu.bg0.mosaicLatch;
layers[BG1] = ppu.bg1.mosaicLatch;
layers[BG2] = ppu.bg2.mosaicLatch;
layers[BG3] = ppu.bg3.mosaicLatch;
layers[SFX] = {true, 3, 0};
aboveLayer = 5;
+12 -1
View File
@@ -9,7 +9,7 @@ auto PPU::Debugger::load(Node::Object parent) -> void {
});
memory.pram = parent->append<Node::Debugger::Memory>("PPU PRAM");
memory.pram->setSize(ppu.pram.size());
memory.pram->setSize(ppu.pram.size() * 2);
memory.pram->setRead([&](u32 address) -> u8 {
return ppu.pram[address >> 1].byte(address & 1);
});
@@ -17,6 +17,15 @@ auto PPU::Debugger::load(Node::Object parent) -> void {
ppu.pram[address >> 1].byte(address & 1) = data;
});
memory.oam = parent->append<Node::Debugger::Memory>("PPU OAM");
memory.oam->setSize(ppu.oam.size() * 2);
memory.oam->setRead([&](u32 address) -> u8 {
return ppu.oam[address >> 1].byte(address & 1);
});
memory.oam->setWrite([&](u32 address, u8 data) -> void {
ppu.oam[address >> 1].byte(address & 1) = data;
});
graphics.tiles4bpp = parent->append<Node::Debugger::Graphics>("4 BPP Tiles");
graphics.tiles4bpp->setSize(512, 384);
graphics.tiles4bpp->setCapture([&]() -> std::vector<u32> {
@@ -134,6 +143,7 @@ auto PPU::Debugger::load(Node::Object parent) -> void {
auto PPU::Debugger::unload(Node::Object parent) -> void {
parent->remove(memory.vram);
parent->remove(memory.pram);
parent->remove(memory.oam);
parent->remove(graphics.tiles4bpp);
parent->remove(graphics.tiles8bpp);
parent->remove(graphics.mode3);
@@ -141,6 +151,7 @@ auto PPU::Debugger::unload(Node::Object parent) -> void {
parent->remove(graphics.mode5);
memory.vram.reset();
memory.pram.reset();
memory.oam.reset();
graphics.tiles4bpp.reset();
graphics.tiles8bpp.reset();
graphics.mode3.reset();
+1 -1
View File
@@ -84,7 +84,7 @@ auto PPU::readIO(n32 address) -> n8 {
}
return cpu.openBus.get(Byte, address);
return cpu.mdr >> (8 * (address & 3));
}
auto PPU::writeIO(n32 address, n8 data) -> void {
+21 -111
View File
@@ -1,8 +1,12 @@
auto PPU::releaseBus() -> void {
auto PPU::bgReleaseBus() -> void {
pramAccessed = false;
vramAccessedBG = false;
}
auto PPU::objReleaseBus() -> void {
oamAccessed = false;
}
auto PPU::pramContention() -> bool {
if(accurate) return pramAccessed;
@@ -17,34 +21,36 @@ auto PPU::pramContention() -> bool {
}
auto PPU::vramContention(n32 address) -> bool {
//todo: implement OBJ VRAM contention
address &= 0x1ffff;
if(Background::IO::mode < 3 && address >= 0x10000) return false;
else if(address >= 0x14000) return false;
return vramAccessedBG;
}
auto PPU::oamContention() -> bool {
return oamAccessed;
}
auto PPU::readVRAM_BG(u32 mode, n32 address) -> n16 {
if(Background::IO::mode < 3 && address >= 0x10000) return 0;
else if(address >= 0x14000) return 0;
vramAccessedBG = true;
return readVRAM(mode, address);
n16 half = readVRAM(mode, address);
if(mode & Byte) {
if(address & 1) half >>= 8;
half = (n8)half;
}
return half;
}
auto PPU::readVRAM(u32 mode, n32 address) -> n16 {
address &= 0x1ffff;
if(Background::IO::mode >= 3 && address < 0x1c000 && address >= 0x18000) return 0;
address &= (address & 0x10000) ? 0x17fff : 0x0ffff;
if(mode & Half) {
address &= ~1;
return vram[address + 0] << 0 | vram[address + 1] << 8;
} else if(mode & Byte) {
return vram[address];
}
unreachable;
address &= (address & 0x10000) ? 0x17ffe : 0x0fffe;
return vram[address + 0] << 0 | vram[address + 1] << 8;
}
auto PPU::writeVRAM(u32 mode, n32 address, n16 half) -> void {
@@ -69,7 +75,6 @@ auto PPU::writeVRAM(u32 mode, n32 address, n16 half) -> void {
}
auto PPU::readPRAM(u32 mode, n32 address) -> n16 {
if(mode & Byte) return readPRAM(Half, address) >> ((address & 1) * 8);
return pram[address >> 1 & 511];
}
@@ -83,49 +88,8 @@ auto PPU::writePRAM(u32 mode, n32 address, n16 half) -> void {
}
auto PPU::readOAM(u32 mode, n32 address) -> n32 {
if(mode & Word) return readOAM(Half, address & ~2) << 0 | readOAM(Half, address | 2) << 16;
if(mode & Byte) return readOAM(Half, address) >> ((address & 1) * 8);
auto& obj = object[address >> 3 & 127];
auto& par = objectParam[address >> 5 & 31];
switch(address & 6) {
case 0: return (
(obj.y << 0)
| (obj.affine << 8)
| (obj.affineSize << 9)
| (obj.mode << 10)
| (obj.mosaic << 12)
| (obj.colors << 13)
| (obj.shape << 14)
);
case 2: return (
(obj.x << 0)
| (obj.affineParam << 9)
| (obj.hflip << 12)
| (obj.vflip << 13)
| (obj.size << 14)
);
case 4: return (
(obj.character << 0)
| (obj.priority << 10)
| (obj.palette << 12)
);
case 6:
switch(address >> 3 & 3) {
case 0: return par.pa;
case 1: return par.pb;
case 2: return par.pc;
case 3: return par.pd;
}
}
unreachable;
address = (n10)address;
return oam[address >> 1 & ~1] << 0 | oam[address >> 1 | 1] << 16;
}
auto PPU::writeOAM(u32 mode, n32 address, n32 word) -> void {
@@ -136,61 +100,7 @@ auto PPU::writeOAM(u32 mode, n32 address, n32 word) -> void {
}
if(mode & Byte) return; //8-bit writes to OAM are ignored
auto& obj = object[address >> 3 & 127];
auto& par = objectParam[address >> 5 & 31];
switch(address & 6) {
case 0:
obj.y = word >> 0;
obj.affine = word >> 8;
obj.affineSize = word >> 9;
obj.mode = word >> 10;
obj.mosaic = word >> 12;
obj.colors = word >> 13;
obj.shape = word >> 14;
break;
case 2:
obj.x = word >> 0;
obj.affineParam = word >> 9;
obj.hflip = word >> 12;
obj.vflip = word >> 13;
obj.size = word >> 14;
break;
case 4:
obj.character = word >> 0;
obj.priority = word >> 10;
obj.palette = word >> 12;
break;
case 6:
switch(address >> 3 & 3) {
case 0: par.pa = word; break;
case 1: par.pb = word; break;
case 2: par.pc = word; break;
case 3: par.pd = word; break;
}
}
static u32 widths[] = {
8, 16, 32, 64,
16, 32, 32, 64,
8, 8, 16, 32,
8, 8, 8, 8, //invalid modes
};
static u32 heights[] = {
8, 16, 32, 64,
8, 8, 16, 32,
16, 32, 32, 64,
8, 8, 8, 8, //invalid modes
};
obj.width = widths [obj.shape * 4 + obj.size];
obj.height = heights[obj.shape * 4 + obj.size];
oam[address >> 1 & 511] = word;
}
auto PPU::readObjectVRAM(u32 address) const -> n8 {
+204 -86
View File
@@ -3,117 +3,235 @@ auto PPU::Objects::setEnable(n1 status) -> void {
for(auto& flag : io.enable) flag &= status;
}
auto PPU::Objects::goToNext() -> void {
if(!(++objIndex)) active = false;
state = State::ReadA01;
}
auto PPU::Objects::readA01(u32 y) -> void {
static const u32 widths[] = {
8, 16, 32, 64,
16, 32, 32, 64,
8, 8, 16, 32,
8, 8, 8, 8, //invalid shapes
};
static const u32 heights[] = {
8, 16, 32, 64,
8, 8, 16, 32,
16, 32, 32, 64,
8, 8, 8, 8, //invalid shapes
};
n16 attr0 = ppu.oam[objIndex << 2 | 0];
n8 ypos = attr0 >> 0;
latch.affine = attr0 >> 8;
latch.affineSize = attr0 >> 9;
latch.mode = attr0 >> 10;
latch.mosaic = attr0 >> 12;
latch.colors = attr0 >> 13; //0 = 16, 1 = 256
n2 shape = attr0 >> 14; //0 = square, 1 = horizontal, 2 = vertical
n16 attr1 = ppu.oam[objIndex << 2 | 1];
latch.x = attr1 >> 0;
latch.affineParam = attr1 >> 9;
latch.hflip = attr1 >> 12;
latch.vflip = attr1 >> 13;
n2 size = attr1 >> 14;
latch.width = widths [shape * 4 + size];
latch.height = heights[shape * 4 + size];
latch.py = y - ypos;
n9 pxMax = latch.x + (latch.width << latch.affineSize);
if((latch.affine == 0 && latch.affineSize == 1) || (latch.py >= latch.height << latch.affineSize) || (latch.x >= 240 && pxMax >= 240)) {
//object is hidden - skip rendering
goToNext();
} else {
if(latch.mosaic) {
latch.py = ypos >= 160 || mosaicY - ypos >= 0 ? u32(mosaicY - ypos) : 0;
}
state = State::ReadA2;
}
}
auto PPU::Objects::readA2() -> void {
n16 attr2 = ppu.oam[objIndex << 2 | 2];
latch.character = attr2 >> 0;
latch.priority = attr2 >> 10;
latch.palette = attr2 >> 12;
if(!latch.affine) {
drawObject(renderY);
} else {
state = State::ReadPA;
}
}
auto PPU::Objects::drawObject(u32 y) -> void {
auto& buffer = lineBuffers[y & 1];
//center-of-sprite coordinates
i16 centerX = latch.width >> 1;
i16 centerY = latch.height >> 1;
//origin coordinates (top-left of sprite)
i28 originX = -(centerX << latch.affineSize);
i28 originY = -(centerY << latch.affineSize) + latch.py;
//fractional pixel coordinates
i28 fx = originX * latch.pa + originY * latch.pb;
i28 fy = originX * latch.pc + originY * latch.pd;
for(u32 px : range(latch.width << latch.affineSize)) {
//calculate address within tile
u32 sx, sy;
if(!latch.affine) {
sx = px ^ (latch.hflip ? latch.width - 1 : 0);
sy = latch.py ^ (latch.vflip ? latch.height - 1 : 0);
} else {
sx = (fx >> 8) + centerX;
sy = (fy >> 8) + centerY;
}
n6 subTileAddr = ((sy & 7) * 8 + (sx & 7)) >> !latch.colors;
//calculate address of tile
n10 tileAddr;
if(io.mapping) {
u32 offset = (sy >> 3) * (latch.width >> 3) + (sx >> 3);
tileAddr = latch.character + (offset << latch.colors);
} else {
n5 row = (latch.character >> 5) + (sy >> 3);
n5 rowEntry = latch.character + ((sx >> 3) << latch.colors);
tileAddr = (row << 5) + rowEntry;
}
//output pixel
n8 color = ppu.readObjectVRAM((tileAddr << 5) + subTileAddr);
if(latch.colors == 0) color = sx & 1 ? color >> 4 : color & 15;
n9 bx = latch.x + px;
if(bx < 240 && sx < latch.width && sy < latch.height) {
if(latch.mode & 2) {
if(color) {
buffer[bx].window = true;
}
} else if(!buffer[bx].enable || latch.priority < buffer[bx].priority) {
buffer[bx].priority = latch.priority; //updated regardless of transparency
buffer[bx].mosaic = latch.mosaic; //updated regardless of transparency
if(color) {
if(latch.colors == 0) color = latch.palette * 16 + color;
buffer[bx].enable = true;
buffer[bx].color = 256 + color;
buffer[bx].translucent = latch.mode == 1;
}
}
}
fx += latch.pa;
fy += latch.pc;
}
goToNext();
}
auto PPU::Objects::step() -> void {
if(!active) return;
if(activeCycle) {
switch(state) {
case State::ReadA01:
readA01(renderY);
break;
case State::ReadA2:
readA2();
break;
case State::ReadPA:
latch.pa = ppu.oam[latch.affineParam << 4 | 0x3];
state = State::ReadPB;
break;
case State::ReadPB:
latch.pb = ppu.oam[latch.affineParam << 4 | 0x7];
state = State::ReadPC;
break;
case State::ReadPC:
latch.pc = ppu.oam[latch.affineParam << 4 | 0xb];
state = State::ReadPD;
break;
case State::ReadPD:
latch.pd = ppu.oam[latch.affineParam << 4 | 0xf];
drawObject(renderY);
break;
}
ppu.oamAccessed = true;
}
activeCycle = !activeCycle;
}
auto PPU::Objects::scanline(u32 y) -> void {
if(y >= 160) return;
mosaicOffset = 0;
hmosaicOffset = io.mosaicWidth;
if(y == 0 || vmosaicOffset == io.mosaicHeight) {
vmosaicOffset = 0;
mosaicY = y;
} else {
vmosaicOffset++;
}
auto& buffer = lineBuffers[y & 1];
for(auto& pixel : buffer) pixel = {};
if(ppu.io.forceBlank[1] || cpu.stopped() || !io.enable[1]) return; //checks if display conditions will be met next scanline
for(auto& object : ppu.object) {
n8 py = y - object.y;
if(object.affine == 0 && object.affineSize == 1) continue; //hidden
if(py >= object.height << object.affineSize) continue; //offscreen
renderY = y;
objIndex = 0;
active = true;
activeCycle = true;
state = State::ReadA01;
}
if(object.mosaic && io.mosaicHeight) {
s32 mosaicY = (y / (1 + io.mosaicHeight)) * (1 + io.mosaicHeight);
py = object.y >= 160 || mosaicY - object.y >= 0 ? u32(mosaicY - object.y) : 0;
}
i16 pa = ppu.objectParam[object.affineParam].pa;
i16 pb = ppu.objectParam[object.affineParam].pb;
i16 pc = ppu.objectParam[object.affineParam].pc;
i16 pd = ppu.objectParam[object.affineParam].pd;
//center-of-sprite coordinates
i16 centerX = object.width >> 1;
i16 centerY = object.height >> 1;
//origin coordinates (top-left of sprite)
i28 originX = -(centerX << object.affineSize);
i28 originY = -(centerY << object.affineSize) + py;
//fractional pixel coordinates
i28 fx = originX * pa + originY * pb;
i28 fy = originX * pc + originY * pd;
for(u32 px : range(object.width << object.affineSize)) {
//calculate address within tile
u32 sx, sy;
if(!object.affine) {
sx = px ^ (object.hflip ? object.width - 1 : 0);
sy = py ^ (object.vflip ? object.height - 1 : 0);
} else {
sx = (fx >> 8) + centerX;
sy = (fy >> 8) + centerY;
}
n6 subTileAddr = ((sy & 7) * 8 + (sx & 7)) >> !object.colors;
//calculate address of tile
n10 tileAddr;
if(io.mapping) {
u32 offset = (sy >> 3) * (object.width >> 3) + (sx >> 3);
tileAddr = object.character + (offset << object.colors);
} else {
n5 row = (object.character >> 5) + (sy >> 3);
n5 rowEntry = object.character + ((sx >> 3) << object.colors);
tileAddr = (row << 5) + rowEntry;
}
//output pixel
n8 color = ppu.readObjectVRAM((tileAddr << 5) + subTileAddr);
if(object.colors == 0) color = sx & 1 ? color >> 4 : color & 15;
n9 bx = object.x + px;
if(bx < 240 && sx < object.width && sy < object.height) {
if(object.mode & 2) {
if(color) {
buffer[bx].window = true;
}
} else if(!buffer[bx].enable || object.priority < buffer[bx].priority) {
buffer[bx].priority = object.priority; //updated regardless of transparency
buffer[bx].mosaic = object.mosaic; //updated regardless of transparency
if(color) {
if(object.colors == 0) color = object.palette * 16 + color;
buffer[bx].enable = true;
buffer[bx].color = 256 + color;
buffer[bx].translucent = object.mode == 1;
}
}
}
fx += pa;
fy += pc;
}
}
auto PPU::Objects::renderScanline(u32 y) -> void {
scanline(y);
for(auto _ : range(1232)) step();
active = false;
ppu.objReleaseBus();
}
auto PPU::Objects::outputPixel(u32 x, u32 y) -> void {
output = {};
if(ppu.blank() || !io.enable[0]) {
mosaic = {};
mosaicLatch = {};
return;
}
auto& buffer = lineBuffers[y & 1];
output = buffer[x];
//horizontal mosaic
if(!mosaicOffset) {
mosaicOffset = 1 + io.mosaicWidth;
mosaic = output;
} else if(!mosaic.mosaic || !output.mosaic) {
mosaic = output;
if(hmosaicOffset == io.mosaicWidth) {
hmosaicOffset = 0;
mosaicLatch = output;
} else {
hmosaicOffset++;
}
if(!mosaicLatch.mosaic || !output.mosaic || (output.priority < mosaicLatch.priority)) {
mosaicLatch = output;
}
mosaicOffset--;
}
auto PPU::Objects::power() -> void {
io = {};
latch = {};
for(auto& buffer : lineBuffers) {
for(auto& pixel : buffer) pixel = {};
}
output = {};
mosaic = {};
mosaicOffset = 0;
mosaicLatch = {};
renderY = 0;
mosaicY = 0;
hmosaicOffset = 0;
vmosaicOffset = 0;
objIndex = 0;
active = false;
activeCycle = false;
state = State::ReadA01;
}
+39 -19
View File
@@ -20,6 +20,7 @@ auto PPU::setAccurate(bool value) -> void {
auto PPU::load(Node::Object parent) -> void {
vram.allocate(96_KiB);
pram.allocate(512);
oam.allocate(512);
node = parent->append<Node::Object>("PPU");
@@ -72,8 +73,12 @@ inline auto PPU::blank() -> bool {
}
auto PPU::step(u32 clocks) -> void {
Thread::step(clocks);
Thread::synchronize(cpu, display);
for(auto _ : range(clocks)) {
objects.step();
Thread::step(1);
Thread::synchronize(cpu, display);
objReleaseBus();
}
}
template<s32 Cycle>
@@ -108,16 +113,18 @@ auto PPU::cycleBitmap(u32 x, u32 y) -> void {
if(mode >= 3 && mode <= 5) bg2.bitmap(x, y);
}
auto PPU::cycleWindow(u32 x, u32 y) -> void {
window0.run(x, y);
window1.run(x, y);
}
auto PPU::cycleUpperLayer(u32 x, u32 y) -> void {
ppu.bg0.outputPixel(x, y);
ppu.bg1.outputPixel(x, y);
ppu.bg2.outputPixel(x, y);
ppu.bg3.outputPixel(x, y);
ppu.objects.outputPixel(x, y);
window0.run(x, y);
window1.run(x, y);
window2.output = objects.output.window;
window3.output = true;
window2.output[x] = objects.output.window;
dac.upperLayer(x, y);
}
@@ -127,10 +134,11 @@ auto PPU::cycle(u32 y) -> void {
if constexpr(Cycle >= 3 && Cycle <= 1005 ) cycleLinearMap<(Cycle - 3) & 3>((Cycle - 31) >> 2, y);
if constexpr(Cycle >= 31 && Cycle <= 1005 ) cycleAffine<(Cycle - 31) & 3>((Cycle - 31) >> 2, y);
if constexpr(Cycle >= 31 && Cycle <= 1005 && (Cycle - 31) % 4 == 3) cycleBitmap((Cycle - 31) >> 2, y);
if constexpr(Cycle >= 3 && Cycle <= 1026 && (Cycle - 3) % 4 == 0) cycleWindow((Cycle - 3) / 4, y);
if constexpr(Cycle >= 46 && Cycle <= 1005 && (Cycle - 46) % 4 == 0) cycleUpperLayer((Cycle - 46) / 4, y);
if constexpr(Cycle >= 46 && Cycle <= 1005 && (Cycle - 46) % 4 == 2) dac.lowerLayer((Cycle - 46) / 4, y);
step(1);
releaseBus();
bgReleaseBus();
}
auto PPU::main() -> void {
@@ -144,8 +152,6 @@ auto PPU::main() -> void {
bg3.io.ly = bg3.io.y;
}
step(3);
u32 y = display.io.vcounter;
memory::move(io.forceBlank, io.forceBlank + 1, sizeof(io.forceBlank) - 1);
memory::move(bg0.io.enable, bg0.io.enable + 1, sizeof(bg0.io.enable) - 1);
@@ -157,9 +163,12 @@ auto PPU::main() -> void {
bg1.scanline(y);
bg2.scanline(y);
bg3.scanline(y);
objects.scanline((y + 1) % 228);
window0.scanline(y);
window1.scanline(y);
dac.scanline(y);
step(4);
if(y < 160) {
if(accurate) {
#define cycles01(index) cycle<index>(y)
@@ -178,9 +187,12 @@ auto PPU::main() -> void {
//cycle 31 - start rendering backgrounds unconditionally
cycles01( 31);
cycles02( 32);
cycles04( 34);
cycles08( 38);
cycles08( 32);
//cycle 40 - start rendering sprites
objects.scanline((y + 1) % 228);
cycles02( 40);
cycles04( 42);
//cycle 46 - start pixel output
cycles64( 46);
@@ -213,24 +225,34 @@ auto PPU::main() -> void {
#undef cycles64
} else {
step(renderingCycle);
objects.renderScanline((y + 1) % 228);
for(s32 x : range(247)) {
bg0.run(x - 7, y);
bg1.run(x - 7, y);
bg2.run(x - 7, y);
bg3.run(x - 7, y);
}
for(u32 x : range(256)) cycleWindow(x, y);
for(u32 x : range(240)) {
cycleUpperLayer(x, y);
dac.lowerLayer(x, y);
}
releaseBus();
bgReleaseBus();
step(1035 - renderingCycle);
}
} else {
step(1035);
if(accurate) {
step(37);
objects.scanline((y + 1) % 228);
step(998);
} else {
step(renderingCycle);
objects.renderScanline((y + 1) % 228);
step(1035 - renderingCycle);
}
}
step(194);
step(193);
}
auto PPU::frame() -> void {
@@ -250,8 +272,6 @@ auto PPU::power() -> void {
for(u32 n = 0; n < 1024; n += 2) writeOAM(Half, n, 0x0000);
io = {};
for(auto& object : this->object) object = {};
for(auto& param : this->objectParam) param = {};
bg0.power(BG0);
bg1.power(BG1);
@@ -264,7 +284,7 @@ auto PPU::power() -> void {
window3.power(OUT);
dac.power();
renderingCycle = 43; //by default, render at first cycle of pixel output
renderingCycle = 42; //by default, render at first cycle of pixel output
string gameID;
for(u32 index : range(4)) {
n32 address = 0xac + index;
+60 -46
View File
@@ -6,6 +6,7 @@ struct PPU : Thread, IO {
Node::Setting::String rotation;
Memory::Writable<n8 > vram; //96KB
Memory::Writable<n16> pram;
Memory::Writable<n16> oam;
bool accurate;
@@ -17,6 +18,7 @@ struct PPU : Thread, IO {
struct Memory {
Node::Debugger::Memory vram;
Node::Debugger::Memory pram;
Node::Debugger::Memory oam;
} memory;
struct Graphics {
@@ -41,6 +43,7 @@ struct PPU : Thread, IO {
template<s32> auto cycleLinearRender(s32 x, u32 y) -> void;
template<s32> auto cycleAffine(u32 x, u32 y) -> void;
auto cycleBitmap(u32 x, u32 y) -> void;
auto cycleWindow(u32 x, u32 y) -> void;
auto cycleUpperLayer(u32 x, u32 y) -> void;
template<s32> auto cycle(u32 y) -> void;
auto main() -> void;
@@ -54,9 +57,11 @@ struct PPU : Thread, IO {
auto writeIO(n32 address, n8 byte) -> void;
//memory.cpp
auto releaseBus() -> void;
auto bgReleaseBus() -> void;
auto objReleaseBus() -> void;
auto pramContention() -> bool;
auto vramContention(n32 address) -> bool;
auto oamContention() -> bool;
auto readVRAM(u32 mode, n32 address) -> n16;
auto readVRAM_BG(u32 mode, n32 address) -> n16;
@@ -169,6 +174,8 @@ private:
struct Affine {
u32 screenSize;
u32 screenWrap;
u32 hmosaic;
u32 vmosaic;
u32 cx;
u32 cy;
u32 tx;
@@ -177,10 +184,8 @@ private:
} affine;
Pixel output[240];
Pixel mosaic;
Pixel mosaicLatch;
u32 mosaicOffset;
u32 hmosaic;
u32 vmosaic;
i28 fx;
@@ -190,7 +195,13 @@ private:
struct Objects {
//object.cpp
auto setEnable(n1 status) -> void;
auto goToNext() -> void;
auto readA01(u32 y) -> void;
auto readA2() -> void;
auto drawObject(u32 y) -> void;
auto step() -> void;
auto scanline(u32 y) -> void;
auto renderScanline(u32 y) -> void;
auto outputPixel(u32 x, u32 y) -> void;
auto power() -> void;
@@ -202,18 +213,55 @@ private:
n1 hblank; //1 = allow access to OAM during Hblank
n1 mapping; //0 = two-dimensional, 1 = one-dimensional
n5 mosaicWidth;
n5 mosaicHeight;
n4 mosaicWidth;
n4 mosaicHeight;
} io;
struct Latch {
n1 affine;
n1 affineSize;
n2 mode;
n1 mosaic;
n1 colors;
n9 x;
n5 affineParam;
n1 hflip;
n1 vflip;
n10 character;
n2 priority;
n4 palette;
n32 width;
n32 height;
n8 py;
i16 pa;
i16 pb;
i16 pc;
i16 pd;
} latch;
Pixel lineBuffers[2][240];
Pixel output;
Pixel mosaic;
u32 mosaicOffset;
Pixel mosaicLatch;
u32 renderY;
s32 mosaicY;
n4 hmosaicOffset;
n4 vmosaicOffset;
n7 objIndex;
bool active;
bool activeCycle;
enum class State : u32 {
ReadA01, ReadA2, ReadPA, ReadPB, ReadPC, ReadPD
} state;
} objects;
struct Window {
//window.cpp
auto scanline(u32 y) -> void;
auto run(u32 x, u32 y) -> void;
auto power(u32 id) -> void;
@@ -233,7 +281,9 @@ private:
n8 y2;
} io;
n1 output; //IN0, IN1, IN2 only
n1 output[256]; //IN0, IN1, IN2 only
n1 h; //IN0, IN1 only
n1 v; //IN0, IN1 only
} window0, window1, window2, window3;
struct DAC {
@@ -268,45 +318,9 @@ private:
u32* line = nullptr;
} dac;
struct Object {
//serialization.cpp
auto serialize(serializer&) -> void;
n8 y;
n1 affine;
n1 affineSize;
n2 mode;
n1 mosaic;
n1 colors; //0 = 16, 1 = 256
n2 shape; //0 = square, 1 = horizontal, 2 = vertical
n9 x;
n5 affineParam;
n1 hflip;
n1 vflip;
n2 size;
n10 character;
n2 priority;
n4 palette;
//ancillary data
n32 width;
n32 height;
} object[128];
struct ObjectParam {
//serialization.cpp
auto serialize(serializer&) -> void;
i16 pa;
i16 pb;
i16 pc;
i16 pd;
} objectParam[32];
bool pramAccessed;
bool vramAccessedBG;
bool oamAccessed;
n32 renderingCycle;
};
+14 -32
View File
@@ -3,6 +3,7 @@ auto PPU::serialize(serializer& s) -> void {
s(vram);
s(pram);
s(oam);
s(io.gameBoyColorMode);
for(auto& flag : io.forceBlank) s(flag);
@@ -22,11 +23,10 @@ auto PPU::serialize(serializer& s) -> void {
s(window2);
s(window3);
s(dac);
for(auto& object : this->object) s(object);
for(auto& param : this->objectParam) s(param);
s(pramAccessed);
s(vramAccessedBG);
s(oamAccessed);
}
auto PPU::Background::serialize(serializer& s) -> void {
@@ -53,7 +53,6 @@ auto PPU::Background::serialize(serializer& s) -> void {
s(io.ly);
s(mosaicOffset);
s(hmosaic);
s(vmosaic);
s(fx);
s(fy);
@@ -66,7 +65,15 @@ auto PPU::Objects::serialize(serializer& s) -> void {
s(io.mosaicWidth);
s(io.mosaicHeight);
s(mosaicOffset);
s(renderY);
s(mosaicY);
s(hmosaicOffset);
s(vmosaicOffset);
s(objIndex);
s(active);
s(activeCycle);
s(state);
}
auto PPU::Window::serialize(serializer& s) -> void {
@@ -79,7 +86,9 @@ auto PPU::Window::serialize(serializer& s) -> void {
s(io.y1);
s(io.y2);
s(output);
for(auto& flag : output) s(flag);
s(h);
s(v);
}
auto PPU::DAC::serialize(serializer& s) -> void {
@@ -90,30 +99,3 @@ auto PPU::DAC::serialize(serializer& s) -> void {
s(io.blendEVB);
s(io.blendEVY);
}
auto PPU::Object::serialize(serializer& s) -> void {
s(y);
s(affine);
s(affineSize);
s(mode);
s(mosaic);
s(colors);
s(shape);
s(x);
s(affineParam);
s(hflip);
s(vflip);
s(size);
s(character);
s(priority);
s(palette);
s(width);
s(height);
}
auto PPU::ObjectParam::serialize(serializer& s) -> void {
s(pa);
s(pb);
s(pc);
s(pd);
}
+11 -8
View File
@@ -1,16 +1,19 @@
auto PPU::Window::scanline(u32 y) -> void {
if(y == io.y1) v = true;
if(y == io.y2) v = false;
}
auto PPU::Window::run(u32 x, u32 y) -> void {
auto x1 = io.x1, x2 = io.x2;
auto y1 = io.y1, y2 = io.y2;
if(x2 < x1 || x2 > 240) x2 = 240;
if(y2 < y1 || y2 > 160) y2 = 160;
output = (x >= x1 && x < x2 && y >= y1 && y < y2);
if(x == io.x1) h = true;
if(x == io.x2) h = false;
output[x] = h && v;
}
auto PPU::Window::power(u32 id) -> void {
this->id = id;
io = {};
output = 0;
for(auto& flag : output) flag = 0;
h = 0;
v = 0;
}
+2 -7
View File
@@ -16,9 +16,7 @@ auto BIOS::readROM(n25 address) -> n32 {
auto BIOS::read(u32 mode, n25 address) -> n32 {
//unmapped memory
if(address >= 0x0000'4000) {
return cpu.openBus.get(mode, address); //0000'4000-01ff'ffff
}
if(address >= 0x0000'4000) return cpu.mdr; //0000'4000-01ff'ffff
//GBA BIOS is read-protected; only the BIOS itself can read its own memory
//when accessed elsewhere; this should return the last value read by the BIOS program
@@ -27,10 +25,7 @@ auto BIOS::read(u32 mode, n25 address) -> n32 {
} else {
if(cpu.processor.r15 > 0x01ff'ffff && cpu.processor.r15 < 0x0200'4000) mdr = readROM(address);
}
if(mode & Word) address &= ~3;
if(mode & Half) address &= ~1;
return mdr >> (8 * (address & 3));
return mdr;
}
auto BIOS::write(u32 mode, n25 address, n32 word) -> void {
+1 -1
View File
@@ -1,4 +1,4 @@
static const string SerializerVersion = "v146.1";
static const string SerializerVersion = "v148.7";
auto System::serialize(bool synchronize) -> serializer {
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
+8
View File
@@ -107,6 +107,14 @@ ares_add_sources(
controller/fighting-pad/fighting-pad.hpp
)
ares_add_sources(
CORE #
md
INCLUDED #
controller/mega-mouse/mega-mouse.cpp
controller/mega-mouse/mega-mouse.hpp
)
ares_add_sources(
CORE #
md
+2 -2
View File
@@ -17,7 +17,7 @@ auto MCD::CDC::clock() -> void {
auto MCD::CDC::decode(s32 sector) -> void {
if(!decoder.enable || !mcd.fd) return;
auto [minute, second, frame] = CD::MSF(sector);
auto [minute, second, frame] = CD::MSF::fromLBA(sector);
header.minute = minute / 10 << 4 | minute % 10;
header.second = second / 10 << 4 | second % 10;
header.frame = frame / 10 << 4 | frame % 10;
@@ -32,7 +32,7 @@ auto MCD::CDC::decode(s32 sector) -> void {
transfer.target += 2352;
//the sync header is written at the tail instead of head.
mcd.fd->seek((abs(mcd.cdd.session.leadIn.lba) + sector) * 2448);
mcd.fd->seek(2448ull * (CD::LeadInSectors + CD::LBAtoABA(sector)));
for(u32 index = 0; index < 12; index += 2) {
ram[n13(transfer.pointer + index + 2340 >> 1)] = mcd.fd->readm(2);
}
+14 -15
View File
@@ -180,7 +180,7 @@ auto MCD::CDD::sample() -> void {
if (io.status == Status::Playing) {
if (MegaLD() || session.tracks[io.track].isAudio()) {
if (session.tracks[io.track].isAudio()) {
mcd.fd->seek((abs(session.leadIn.lba) + io.sector) * 2448 + io.sample);
mcd.fd->seek(2448ull * (CD::LeadInSectors + CD::LBAtoABA(io.sector)) + io.sample);
digitalSampleLeft = mcd.fd->readl(2);
digitalSampleRight = mcd.fd->readl(2);
}
@@ -301,8 +301,7 @@ auto MCD::CDD::position(s32 sector) -> double {
auto MCD::CDD::readSubcode() -> void {
if(!mcd.fd) return;
mcd.fd->seek(((abs(mcd.cdd.session.leadIn.lba) + io.sector) * 2448) + 2352);
mcd.fd->seek(2448ull * (CD::LeadInSectors + CD::LBAtoABA(io.sector)) + 2352);
std::vector<u8> subchannel;
subchannel.resize(96);
mcd.fd->read({subchannel.data(), 96});
@@ -360,7 +359,7 @@ auto MCD::CDD::process() -> void {
switch(command[3]) {
case Request::AbsoluteTime: {
auto [minute, second, frame] = CD::MSF(io.sector);
auto [minute, second, frame] = CD::MSF::fromLBA(io.sector);
status[1] = command[3];
status[2] = minute / 10; status[3] = minute % 10;
status[4] = second / 10; status[5] = second % 10;
@@ -369,7 +368,7 @@ auto MCD::CDD::process() -> void {
} break;
case Request::RelativeTime: {
auto [minute, second, frame] = CD::MSF(io.sector - session.tracks[io.track].indices[1].lba);
auto [minute, second, frame] = CD::MSF::fromABA(io.sector - session.tracks[io.track].indices[1].lba); //fromABA to offset by 2s
status[1] = command[3];
status[2] = minute / 10; status[3] = minute % 10;
status[4] = second / 10; status[5] = second % 10;
@@ -388,7 +387,7 @@ auto MCD::CDD::process() -> void {
} break;
case Request::DiscCompletionTime: { //time in mm:ss:ff
auto [minute, second, frame] = CD::MSF(session.leadOut.lba);
auto [minute, second, frame] = CD::MSF::fromLBA(session.leadOut.lba);
status[1] = command[3];
status[2] = minute / 10; status[3] = minute % 10;
status[4] = second / 10; status[5] = second % 10;
@@ -409,7 +408,7 @@ auto MCD::CDD::process() -> void {
case Request::TrackStartTime: {
if(command[4] > 9 || command[5] > 9) break;
u32 track = command[4] * 10 + command[5];
auto [minute, second, frame] = CD::MSF(session.tracks[track].indices[1].lba);
auto [minute, second, frame] = CD::MSF::fromLBA(session.tracks[track].indices[1].lba);
status[1] = command[3];
status[2] = minute / 10; status[3] = minute % 10;
status[4] = second / 10; status[5] = second % 10;
@@ -434,7 +433,7 @@ auto MCD::CDD::process() -> void {
u32 minute = command[2] * 10 + command[3];
u32 second = command[4] * 10 + command[5];
u32 frame = command[6] * 10 + command[7];
s32 lba = minute * 60 * 75 + second * 75 + frame - 3;
s32 lba = CD::MSF(minute, second, frame).toLBA() - 3;
counter = 0;
io.status = Status::Seeking;
@@ -454,7 +453,7 @@ auto MCD::CDD::process() -> void {
u32 minute = command[2] * 10 + command[3];
u32 second = command[4] * 10 + command[5];
u32 frame = command[6] * 10 + command[7];
s32 lba = minute * 60 * 75 + second * 75 + frame - 3;
s32 lba = CD::MSF(minute, second, frame).toLBA() - 3;
counter = 0;
io.status = Status::Seeking;
@@ -652,28 +651,28 @@ auto MCD::CDD::getCurrentSector() -> s32 {
}
auto MCD::CDD::getCurrentTimecode(u8& minute, u8& second, u8& frame) -> void {
auto [lminute, lsecond, lframe] = CD::MSF(io.sector);
auto [lminute, lsecond, lframe] = CD::MSF::fromLBA(io.sector);
minute = lminute;
second = lsecond;
frame = lframe;
}
auto MCD::CDD::getCurrentTrackRelativeTimecode(u8& minute, u8& second, u8& frame) -> void {
auto [lminute, lsecond, lframe] = CD::MSF(io.sector - session.tracks[io.track].indices[1].lba);
auto [lminute, lsecond, lframe] = CD::MSF::fromABA(io.sector - session.tracks[io.track].indices[1].lba);
minute = lminute;
second = lsecond;
frame = lframe;
}
auto MCD::CDD::getLeadOutTimecode(u8& minute, u8& second, u8& frame) -> void {
auto [lminute, lsecond, lframe] = CD::MSF(session.leadOut.lba);
auto [lminute, lsecond, lframe] = CD::MSF::fromLBA(session.leadOut.lba);
minute = lminute;
second = lsecond;
frame = lframe;
}
auto MCD::CDD::getTrackTocData(n7 track, u8& flags, u8& minute, u8& second, u8& frame) -> void {
auto [lminute, lsecond, lframe] = CD::MSF(session.tracks[track].indices[1].lba);
auto [lminute, lsecond, lframe] = CD::MSF::fromLBA(session.tracks[track].indices[1].lba);
minute = lminute;
second = lsecond;
frame = lframe;
@@ -681,8 +680,8 @@ auto MCD::CDD::getTrackTocData(n7 track, u8& flags, u8& minute, u8& second, u8&
}
auto MCD::CDD::lbaFromTime(u8 hour, u8 minute, u8 second, u8 frame) -> s32 {
s32 lba = ((((((s32)hour * 60) + (s32)minute) * 60) + (s32)second) * 75) + (s32)frame;
return lba;
s32 aba = ((((((s32)hour * 60) + (s32)minute) * 60) + (s32)second) * 75) + (s32)frame;
return aba - CD::Track1Pregap;
}
auto MCD::CDD::isTrackAudio(n7 track) -> bool {
+3 -1
View File
@@ -2456,6 +2456,8 @@ auto MCD::LD::zeroBasedFrameIndexFromLba(s32 lba, bool processLeadIn) -> s32 {
return 0;
}
lba = CD::LBAtoABA(lba);
// Turn the lba sector number into a frame number. Since there are 30 frames of video per second, and 75 sectors of CD
// data per second, this will work well enough.
auto frameIndex = (s32)std::round(((double)lba / 75.0) * videoFramesPerSecond);
@@ -2466,7 +2468,7 @@ auto MCD::LD::zeroBasedFrameIndexFromLba(s32 lba, bool processLeadIn) -> s32 {
// Convert the ZERO-BASED frame number to an LBA.
auto MCD::LD::lbaFromZeroBasedFrameIndex(s32 frameIndex) -> s32 {
auto lba = (s32)std::round(((double)frameIndex / videoFramesPerSecond) * 75.0);
return lba;
return CD::ABAtoLBA(lba);
}
auto MCD::LD::VideoTimeToRedbookTime(u8& hours, u8& minutes, u8& seconds, u8& frames) -> void {
+1 -1
View File
@@ -58,4 +58,4 @@ auto Region::PAL() -> bool { return system.region() == System::Region::PAL; }
auto Mega32X() -> bool { return system.mega32X(); }
auto MegaCD() -> bool { return system.megaCD(); }
auto MegaLD() -> bool { return system.megaLD(); }
auto MegaLD() -> bool { return system.megaLD(); }
+2 -2
View File
@@ -67,7 +67,7 @@ auto VDP::main() -> void {
} else if(hcounter() == 0x80) {
if(vcounter() < screenHeight() && !runAhead()) {
render();
if(Mega32X()) m32x.vdp.scanline(pixels() + 18, vcounter()); //approx 3 and 1/4 pixel offset
if(Mega32X()) m32x.vdp.scanline(pixels() + 13, vcounter()); //approx 3 and 1/4 pixel offset in H40 pixels
}
step(768);
@@ -81,7 +81,7 @@ auto VDP::main() -> void {
vedge();
if(vcounter() == 0) {
latch.interlace = io.interlaceMode == 3;
latch.interlace = io.interlaceMode.bit(0);
latch.overscan = io.overscan;
frame();
state.field ^= 1;
+2 -2
View File
@@ -168,7 +168,7 @@ auto VDP::main() -> void {
if(h40()) mainH40();
if(vcounter() == state.bottomline) {
screen->setColorBleedWidth(latch.displayWidth ? 4 : 5);
latch.interlace = io.interlaceMode == 3;
latch.interlace = io.interlaceMode.bit(0);
latch.overscan = io.overscan;
frame();
state.field ^= 1;
@@ -184,7 +184,7 @@ auto VDP::mainH32() -> void {
sprite.begin();
if(dac.pixels) {
blocks<false, true>();
if(Mega32X()) m32x.vdp.scanline(pixels + 18, vcounter()); //approx 3 and 1/4 pixel offset
if(Mega32X()) m32x.vdp.scanline(pixels + 13, vcounter()); //approx 3 and 1/4 pixel offset in H40 pixels
if(MegaLD()) mcd.ld.scanline(dac.pixels, vcounter());
} else {
blocks<false, false>();
+3
View File
@@ -82,6 +82,9 @@ auto PSG::writeIO(n1 port, n8 data) -> void {
// This bit controls which port is read through PSG register 14
controllerMux.select(data.bit(6));
// This bit drives the cassette output signal.
tapeDeck.write(data.bit(7));
}
}
+2 -1
View File
@@ -1,4 +1,4 @@
static const string SerializerVersion = "v132";
static const string SerializerVersion = "v133";
auto System::serialize(bool synchronize) -> serializer {
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
@@ -47,5 +47,6 @@ auto System::serialize(serializer& s, bool synchronize) -> void {
expansion.serialize(s);
controllerPort1.serialize(s);
controllerPort2.serialize(s);
tapeDeck.tray.tape.serialize(s);
rtc.serialize(s);
}
-7
View File
@@ -6,17 +6,10 @@ TapeDeck::TapeDeck(string name) : name(name) {
auto TapeDeck::load(Node::Object parent) -> void {
node = parent->append<Node::Peripheral>(name);
play = node->append<Node::Setting::Boolean>("Playing", false, [&](auto value) {
if(value == 0) state.playing = 0;
if(value == 1) state.playing = 1;
});
play->setDynamic(true);
tray.load(node);
}
auto TapeDeck::power() -> void {
state = {};
}
auto TapeDeck::unload() -> void {
+3 -8
View File
@@ -1,23 +1,18 @@
struct TapeDeck {
Node::Peripheral node;
Node::Setting::Boolean play;
TapeDeckTray tray;
TapeDeck(string name);
auto playing() -> bool { return state.playing; }
auto read() -> u1 { return state.output; }
auto playing() -> bool { return tray.tape.node && tray.tape.node->playing(); }
auto read() -> u1 { return tray.tape.read(); }
auto write(n1 data) -> void { tray.tape.write(data); }
auto load(Node::Object) -> void;
auto unload() -> void;
auto power() -> void;
const string name;
struct {
bool playing;
u1 output;
} state;
};
extern TapeDeck tapeDeck;
+142 -32
View File
@@ -6,56 +6,103 @@ namespace ares::MSX {
#include "tray.cpp"
auto Tape::allocate(Node::Port parent) -> Node::Peripheral {
node = parent->append<Node::Peripheral>("MSX Tape");
position = 0;
length = 0;
node = parent->append<Node::Tape>("MSX Tape");
node->setSupportPlay(true);
node->setSupportRecord(false);
node->setLoad([&] { return load(); });
node->setUnload([&] { unload(); });
node->setPosition(0);
node->setLength(0);
node->setFrequency(44100);
range = 0;
output = 0;
input = 0;
stream = node->append<Node::Audio::Stream>("Audio");
stream->setChannels(1);
stream->setFrequency(node->frequency());
Thread::create(node->frequency(), [&] { Tape::main(); });
return node;
}
auto Tape::connect() -> void {
if(!node->setPak(pak = platform->pak(node))) return;
auto Tape::connect() -> bool {
return node && node->load();
}
auto Tape::load() -> bool {
if(!node->setPak(pak = platform->pak(node))) return false;
information = {};
information.title = pak->attribute("title");
auto fd = pak->read("program.tape");
if(!fd) return disconnect();
data.allocate(fd->size());
data.load(fd);
fd.reset();
range = pak->attribute("range").natural();
frequency = pak->attribute("frequency").natural();
length = pak->attribute("length").natural();
pak->setAttribute("modified", false);
node->setFrequency(pak->attribute("frequency").natural());
node->setLength(pak->attribute("length").natural());
node->setPosition(0);
node->setSupportRecord(pak->attribute("writable").boolean());
stream->setFrequency(node->frequency());
Thread::setFrequency(node->frequency());
stream = node->append<Node::Audio::Stream>("Audio");
stream->setChannels(1);
stream->setFrequency(frequency);
Thread::create(frequency, [&] { Tape::main(); });
if(node->length() != 0) {
auto fd = pak->read("program.tape");
if(!fd) return false;
data.allocate(node->length());
data.load(fd);
fd.reset();
}
return true;
}
auto Tape::main() -> void {
if (!tapeDeck.state.playing) {
stream->frame(0.0f);
u64 position = node->position();
u64 length = node->length();
if(node->playing()) {
if(position >= length) {
output = 0;
node->stop();
step(1);
return;
}
u64 sample = data.read(position++);
node->setPosition(position);
stream->frame((float)sample / (float)range);
output = sample > (range / 2);
step(1);
return;
}
if (position > length) {
tapeDeck.state.playing = 0;
tapeDeck.play->setValue(false);
if(node->recording()) {
if(length <= position) {
auto fd = pak->write("program.tape");
fd->resize(data.size() * sizeof(u64));
data.save(fd);
fd.reset();
length = (position / node->frequency() + 1) * node->frequency();
fd = pak->read("program.tape");
data.allocate(length, 0);
data.load(fd);
fd.reset();
node->setLength(length);
}
data[position++] = (range >> 1) + (input ? 1 : 0);
pak->setAttribute("modified", true);
node->setPosition(position);
stream->frame(input ? 1.0f : 0.0f);
step(1);
return;
}
u64 sample = data.read(position);
position++;
stream->frame((float)sample / (float)range);
tapeDeck.state.output = sample > (range / 2);
output = 0;
stream->frame(0.0f);
step(1);
}
@@ -66,11 +113,74 @@ auto Tape::step(uint clocks) -> void {
auto Tape::disconnect() -> void {
if(!node) return;
node->unload();
Thread::destroy();
data.reset();
stream = {};
node = {};
position = 0;
length = 0;
}
auto Tape::unload() -> void {
if(!pak) return;
if(pak->attribute("modified").boolean() && data.size() != 0) {
auto fd = pak->write("program.tape");
fd->resize(data.size() * sizeof(u64));
data.save(fd);
fd.reset();
}
data.reset();
information = {};
pak.reset();
node->setSupportRecord(false);
node->setFrequency(44100);
stream->setFrequency(node->frequency());
Thread::setFrequency(node->frequency());
node->setPosition(0);
node->setLength(0);
range = 0;
output = 0;
input = 0;
}
auto Tape::read() -> n1 {
if(!node || !node->playing()) return 0;
return output;
}
auto Tape::write(n1 data) -> void {
if(!node || !node->recording()) return;
input = data;
}
auto Tape::serialize(serializer& s) -> void {
u64 position = node ? node->position() : 0;
u64 length = node ? node->length() : 0;
u64 frequency = node ? node->frequency() : 44100;
bool playing = node ? node->playing() : false;
bool recording = node ? node->recording() : false;
Thread::serialize(s);
s(position);
s(length);
s(frequency);
s(playing);
s(recording);
s(range);
s(output);
s(input);
s(data);
if(s.reading() && node) {
node->setPosition(position);
node->setLength(length);
node->setFrequency(frequency);
stream->setFrequency(frequency);
Thread::setFrequency(frequency);
node->stop();
if(playing) node->play();
if(recording) node->record();
}
}
}
+9 -5
View File
@@ -1,18 +1,23 @@
struct Tape : Thread {
Node::Peripheral node;
Node::Tape node;
Node::Audio::Stream stream;
auto title() const -> string { return information.title; }
//tape.cpp
auto allocate(Node::Port) -> Node::Peripheral;
auto connect() -> void;
auto connect() -> bool;
auto disconnect() -> void;
auto load() -> bool;
auto unload() -> void;
auto read() -> n1;
auto write(n1 data) -> void;
auto main() -> void;
auto step(uint clocks) -> void;
auto save() -> void;
auto serialize(serializer&) -> void;
private:
struct Information {
@@ -20,10 +25,9 @@ private:
} information;
VFS::Pak pak;
u64 position;
u64 length;
u64 range;
u64 frequency;
n1 output;
n1 input;
Memory::Writable<u64> data;
};
+29 -1
View File
@@ -42,6 +42,7 @@ ares_add_sources(
n64
INCLUDED #
n64.hpp
accuracy.hpp
CMakeLists.txt
)
@@ -156,6 +157,8 @@ ares_add_sources(
cpu/interpreter.cpp
cpu/memory.cpp
cpu/recompiler.cpp
cpu/recompiler-fpu.cpp
cpu/recompiler-ipu.cpp
cpu/serialization.cpp
cpu/tlb.cpp
)
@@ -360,12 +363,16 @@ endif()
ares_add_sources(
CORE #
n64
PRIMARY #
INCLUDED #
vulkan/parallel-rdp/parallel-rdp/luts.hpp
vulkan/parallel-rdp/parallel-rdp/command_ring.hpp
vulkan/parallel-rdp/parallel-rdp/rdp_common.hpp
vulkan/parallel-rdp/parallel-rdp/rdp_data_structures.hpp
vulkan/parallel-rdp/parallel-rdp/rdp_device.hpp
vulkan/parallel-rdp/parallel-rdp/rdp_dump_write.hpp
vulkan/parallel-rdp/parallel-rdp/rdp_renderer.hpp
vulkan/parallel-rdp/parallel-rdp/video_interface.hpp
vulkan/parallel-rdp/parallel-rdp/worker_thread.hpp
vulkan/parallel-rdp/vulkan/buffer.hpp
vulkan/parallel-rdp/vulkan/buffer_pool.hpp
vulkan/parallel-rdp/vulkan/command_buffer.hpp
@@ -388,6 +395,15 @@ ares_add_sources(
vulkan/parallel-rdp/vulkan/semaphore_manager.hpp
vulkan/parallel-rdp/vulkan/shader.hpp
vulkan/parallel-rdp/vulkan/texture/texture_format.hpp
vulkan/parallel-rdp/vulkan/quirks.hpp
vulkan/parallel-rdp/vulkan/vulkan_common.hpp
vulkan/parallel-rdp/vulkan/limits.hpp
vulkan/parallel-rdp/vulkan/wsi.hpp
vulkan/parallel-rdp/vulkan/type_to_string.hpp
vulkan/parallel-rdp/vulkan/vulkan_headers.hpp
vulkan/parallel-rdp/vulkan/vulkan_prerotate.hpp
vulkan/parallel-rdp/vulkan/format.hpp
vulkan/parallel-rdp/vulkan/device_fossilize.hpp
vulkan/parallel-rdp/util/arena_allocator.hpp
vulkan/parallel-rdp/util/logging.hpp
vulkan/parallel-rdp/util/thread_id.hpp
@@ -396,6 +412,18 @@ ares_add_sources(
vulkan/parallel-rdp/util/timeline_trace_file.hpp
vulkan/parallel-rdp/util/thread_name.hpp
vulkan/parallel-rdp/util/environment.hpp
vulkan/parallel-rdp/util/hash.hpp
vulkan/parallel-rdp/util/intrusive.hpp
vulkan/parallel-rdp/util/intrusive_hash_map.hpp
vulkan/parallel-rdp/util/read_write_lock.hpp
vulkan/parallel-rdp/util/bitops.hpp
vulkan/parallel-rdp/util/small_vector.hpp
vulkan/parallel-rdp/util/intrusive_list.hpp
vulkan/parallel-rdp/util/object_pool.hpp
vulkan/parallel-rdp/util/temporary_hashmap.hpp
vulkan/parallel-rdp/util/enum_cast.hpp
vulkan/parallel-rdp/util/stack_allocator.hpp
vulkan/parallel-rdp/util/dynamic_array.hpp
)
target_include_directories(
+3
View File
@@ -6,6 +6,9 @@ struct Accuracy {
static constexpr bool Interpreter = 0 | Reference | !recompiler::generic::supported;
static constexpr bool Recompiler = !Interpreter;
//Maximum number of cycles to run the CPU without synchronization
static constexpr s64 JitInterleaving = 2048 * 2;
//exceptions when the CPU accesses unaligned memory addresses
static constexpr bool AddressErrors = 1 | Reference;
};
+29 -19
View File
@@ -26,47 +26,57 @@ auto AI::unload() -> void {
auto AI::main() -> void {
while(Thread::clock < 0) {
f64 left = 0, right = 0;
sample(left, right);
stream->frame(left, right);
sample();
stream->frame(dac.left, dac.right);
step(dac.period);
}
}
auto AI::sample(f64& left, f64& right) -> void {
if(io.dmaCount == 0) return;
auto AI::sample() -> void {
bool active = false;
if(io.dmaLength[0] && io.dmaEnable) {
if(io.dmaCount && io.dmaLength[0] && io.dmaEnable) {
io.dmaAddress[0].bit(13,23) += io.dmaAddressCarry;
auto data = rdram.ram.read<Word>(io.dmaAddress[0], "AI");
auto l = s16(data >> 16);
auto r = s16(data >> 0);
left = l / 32768.0;
right = r / 32768.0;
auto data = rdram.ram.read<Word>(io.dmaAddress[0], RBusDevice::AI_DMA);
dac.left = (s16)(data >> 16) / 32768.0;
dac.right = (s16)(data >> 0) / 32768.0;
io.dmaAddress[0].bit(0,12) += 4;
io.dmaAddressCarry = io.dmaAddress[0].bit(0,12) == 0;
io.dmaLength[0] -= 4;
io.dmaAddressCarry = io.dmaAddress[0].bit(0,12) == 0;
io.dmaLength[0] -= 4;
active = true;
}
if(!io.dmaLength[0]) {
if(io.dmaCount && io.dmaLength[0] == 0) {
if(--io.dmaCount) {
io.dmaAddress[0] = io.dmaAddress[1];
io.dmaLength [0] = io.dmaLength [1];
io.dmaLength[0] = io.dmaLength[1];
io.dmaOriginPc[0] = io.dmaOriginPc[1];
mi.raise(MI::IRQ::AI);
}
}
if(!active) {
dac.left *= dac.decayFactor;
dac.right *= dac.decayFactor;
if(fabs(dac.left) < 1e-7) dac.left = 0.0;
if(fabs(dac.right) < 1e-7) dac.right = 0.0;
}
}
auto AI::updateDecay() -> void {
dac.decayFactor = exp(-1.0 / (dac.frequency * 0.003));
}
auto AI::power(bool reset) -> void {
Thread::reset();
fifo[0] = {};
fifo[1] = {};
io = {};
dac.left = 0.0;
dac.right = 0.0;
dac.frequency = 44100;
dac.precision = 16;
dac.period = system.frequency() / dac.frequency;
dac.period = system.frequency() / dac.frequency;
updateDecay();
}
}
+5 -1
View File
@@ -18,8 +18,9 @@ struct AI : Thread, Memory::RCP<AI> {
auto load(Node::Object) -> void;
auto unload() -> void;
auto main() -> void;
auto sample(f64& left, f64& right) -> void;
auto sample() -> void;
auto power(bool reset) -> void;
auto updateDecay() -> void;
//io.cpp
auto readWord(u32 address, Thread& thread) -> u32;
@@ -47,6 +48,9 @@ struct AI : Thread, Memory::RCP<AI> {
u32 frequency;
u32 precision;
u32 period;
f64 left;
f64 right;
f64 decayFactor;
} dac;
};
+5 -1
View File
@@ -15,6 +15,7 @@ auto AI::readWord(u32 address, Thread& thread) -> u32 {
data.bit(25) = io.dmaEnable;
data.bit(30) = io.dmaCount > 0;
data.bit(31) = io.dmaCount > 1;
cpu.forceSynchronize();
}
debugger.io(Read, address, data);
@@ -59,7 +60,10 @@ auto AI::writeWord(u32 address, u32 data_, Thread& thread) -> void {
io.dacRate = data.bit(0,13);
dac.frequency = max(1, system.videoFrequency() / (io.dacRate + 1));
dac.period = system.frequency() / dac.frequency;
if(frequency != dac.frequency) stream->setFrequency(dac.frequency);
if(frequency != dac.frequency) {
stream->setFrequency(dac.frequency);
updateDecay();
}
}
if(address == 5) {
+4
View File
@@ -16,8 +16,12 @@ auto AI::serialize(serializer& s) -> void {
s(dac.frequency);
s(dac.precision);
s(dac.period);
s(dac.left);
s(dac.right);
s(dac.decayFactor);
if(s.reading() && stream->frequency() != dac.frequency) {
stream->setFrequency(dac.frequency);
updateDecay();
}
}
+1
View File
@@ -26,6 +26,7 @@ namespace ares::Nintendo64 {
vram.allocate(4_KiB);
pram.allocate(4_KiB);
controls.load(parent);
mahjong.load(parent);
gameConfig.reset();
dipSwitchNode = parent->append<Node::Object>("DIP Switches");
+34 -23
View File
@@ -8,7 +8,7 @@ struct Aleck64 {
Writable(Aleck64& self) : self(self) {}
template<u32 Size>
auto writeBurst(u32 address, u32 *value, const char *peripheral) -> void {
auto writeBurst(u32 address, u32 *value, RBusDevice device) -> void {
address = address & 0x00ff'ffff;
if(address >= size) return;
Memory::Writable::write<Word>(address | 0x00, value[0]);
@@ -24,7 +24,7 @@ struct Aleck64 {
}
template<u32 Size>
auto readBurst(u32 address, u32 *value, const char *peripheral) -> void {
auto readBurst(u32 address, u32 *value, RBusDevice device) -> void {
address = address & 0x00ff'ffff;
if(address >= size) {
value[0] = value[1] = value[2] = value[3] = 0;
@@ -96,6 +96,7 @@ struct Aleck64 {
}
controls.poll();
mahjong.poll();
if(address <= 0xc080'0fff) {
n32 value = 0xffff'ffff;
@@ -184,36 +185,46 @@ struct Aleck64 {
Node::Input::Button p2start;
Node::Input::Button p2coin;
Node::Input::Button mahjongA;
Node::Input::Button mahjongB;
Node::Input::Button mahjongC;
Node::Input::Button mahjongD;
Node::Input::Button mahjongE;
Node::Input::Button mahjongF;
Node::Input::Button mahjongG;
Node::Input::Button mahjongH;
Node::Input::Button mahjongI;
Node::Input::Button mahjongJ;
Node::Input::Button mahjongK;
Node::Input::Button mahjongL;
Node::Input::Button mahjongM;
Node::Input::Button mahjongN;
Node::Input::Button mahjongKan;
Node::Input::Button mahjongPon;
Node::Input::Button mahjongChi;
Node::Input::Button mahjongReach;
Node::Input::Button mahjongRon;
//controls.cpp
auto load(Node::Object) -> void;
auto poll() -> void;
auto controllerButton(int playerIndex, string button) -> bool;
auto controllerAxis(int playerIndex, string axis) -> s64;
auto mahjong(n8 row) -> n8;
} controls{*this};
struct Mahjong {
Aleck64& self;
Node::Object node;
Node::Input::Button a;
Node::Input::Button b;
Node::Input::Button c;
Node::Input::Button d;
Node::Input::Button e;
Node::Input::Button f;
Node::Input::Button g;
Node::Input::Button h;
Node::Input::Button i;
Node::Input::Button j;
Node::Input::Button k;
Node::Input::Button l;
Node::Input::Button m;
Node::Input::Button n;
Node::Input::Button kan;
Node::Input::Button pon;
Node::Input::Button chi;
Node::Input::Button reach;
Node::Input::Button ron;
//controls.cpp
auto load(Node::Object) -> void;
auto poll() -> void;
auto read(n8 row) -> n8;
} mahjong{*this};
struct VDP {
auto readWord(u32 address) -> u32;
auto writeWord(u32 address, n32 data) -> void;
+67 -60
View File
@@ -32,25 +32,6 @@ auto Aleck64::Controls::load(Node::Object parent) -> void {
p2[n] = node->append<Node::Input::Button>(name);
}
mahjongA = node->append<Node::Input::Button>("Mahjong A");
mahjongB = node->append<Node::Input::Button>("Mahjong B");
mahjongC = node->append<Node::Input::Button>("Mahjong C");
mahjongD = node->append<Node::Input::Button>("Mahjong D");
mahjongE = node->append<Node::Input::Button>("Mahjong E");
mahjongF = node->append<Node::Input::Button>("Mahjong F");
mahjongG = node->append<Node::Input::Button>("Mahjong G");
mahjongH = node->append<Node::Input::Button>("Mahjong H");
mahjongI = node->append<Node::Input::Button>("Mahjong I");
mahjongJ = node->append<Node::Input::Button>("Mahjong J");
mahjongK = node->append<Node::Input::Button>("Mahjong K");
mahjongL = node->append<Node::Input::Button>("Mahjong L");
mahjongM = node->append<Node::Input::Button>("Mahjong M");
mahjongN = node->append<Node::Input::Button>("Mahjong N");
mahjongKan = node->append<Node::Input::Button>("Mahjong カン");
mahjongPon = node->append<Node::Input::Button>("Mahjong ポン");
mahjongChi = node->append<Node::Input::Button>("Mahjong チー");
mahjongReach = node->append<Node::Input::Button>("Mahjong リーチ");
mahjongRon = node->append<Node::Input::Button>("Mahjong ロン");
}
auto Aleck64::Controls::poll() -> void {
@@ -82,26 +63,6 @@ auto Aleck64::Controls::poll() -> void {
for(auto n : range(9)) {
platform->input(p2[n]);
}
platform->input(mahjongA);
platform->input(mahjongB);
platform->input(mahjongC);
platform->input(mahjongD);
platform->input(mahjongE);
platform->input(mahjongF);
platform->input(mahjongG);
platform->input(mahjongH);
platform->input(mahjongI);
platform->input(mahjongJ);
platform->input(mahjongK);
platform->input(mahjongL);
platform->input(mahjongM);
platform->input(mahjongN);
platform->input(mahjongKan);
platform->input(mahjongPon);
platform->input(mahjongChi);
platform->input(mahjongReach);
platform->input(mahjongRon);
}
auto Aleck64::Controls::controllerButton(int playerIndex, string button) -> bool {
@@ -146,40 +107,86 @@ auto Aleck64::Controls::controllerAxis(int playerIndex, string axis) -> s64 {
return 0;
}
auto Aleck64::Controls::mahjong(n8 row) -> n8 {
auto Aleck64::Mahjong::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("Mahjong");
a = node->append<Node::Input::Button>("A");
b = node->append<Node::Input::Button>("B");
c = node->append<Node::Input::Button>("C");
d = node->append<Node::Input::Button>("D");
e = node->append<Node::Input::Button>("E");
f = node->append<Node::Input::Button>("F");
g = node->append<Node::Input::Button>("G");
h = node->append<Node::Input::Button>("H");
i = node->append<Node::Input::Button>("I");
j = node->append<Node::Input::Button>("J");
k = node->append<Node::Input::Button>("K");
l = node->append<Node::Input::Button>("L");
m = node->append<Node::Input::Button>("M");
n = node->append<Node::Input::Button>("N");
kan = node->append<Node::Input::Button>("カン");
pon = node->append<Node::Input::Button>("ポン");
chi = node->append<Node::Input::Button>("チー");
reach = node->append<Node::Input::Button>("リーチ");
ron = node->append<Node::Input::Button>("ロン");
}
auto Aleck64::Mahjong::poll() -> void {
platform->input(a);
platform->input(b);
platform->input(c);
platform->input(d);
platform->input(e);
platform->input(f);
platform->input(g);
platform->input(h);
platform->input(i);
platform->input(j);
platform->input(k);
platform->input(l);
platform->input(m);
platform->input(n);
platform->input(kan);
platform->input(pon);
platform->input(chi);
platform->input(reach);
platform->input(ron);
}
auto Aleck64::Mahjong::read(n8 row) -> n8 {
n8 value = 0xff;
if(row.bit(0)) {
value.bit(1) &= !aleck64.controls.mahjongB->value();
value.bit(2) &= !aleck64.controls.mahjongF->value();
value.bit(3) &= !aleck64.controls.mahjongJ->value();
value.bit(4) &= !aleck64.controls.mahjongN->value();
value.bit(5) &= !aleck64.controls.mahjongReach->value();
value.bit(1) &= !b->value();
value.bit(2) &= !f->value();
value.bit(3) &= !j->value();
value.bit(4) &= !n->value();
value.bit(5) &= !reach->value();
}
if(row.bit(1)) {
value.bit(0) &= !aleck64.controls.p1start->value();
value.bit(1) &= !aleck64.controls.mahjongA->value();
value.bit(2) &= !aleck64.controls.mahjongE->value();
value.bit(3) &= !aleck64.controls.mahjongI->value();
value.bit(4) &= !aleck64.controls.mahjongM->value();
value.bit(5) &= !aleck64.controls.mahjongKan->value();
value.bit(1) &= !a->value();
value.bit(2) &= !e->value();
value.bit(3) &= !i->value();
value.bit(4) &= !m->value();
value.bit(5) &= !kan->value();
}
if(row.bit(2)) {
value.bit(1) &= !aleck64.controls.mahjongC->value();
value.bit(2) &= !aleck64.controls.mahjongG->value();
value.bit(3) &= !aleck64.controls.mahjongK->value();
value.bit(4) &= !aleck64.controls.mahjongChi->value();
value.bit(5) &= !aleck64.controls.mahjongRon->value();
value.bit(1) &= !c->value();
value.bit(2) &= !g->value();
value.bit(3) &= !k->value();
value.bit(4) &= !chi->value();
value.bit(5) &= !ron->value();
}
if(row.bit(3)) {
value.bit(1) &= !aleck64.controls.mahjongD->value();
value.bit(2) &= !aleck64.controls.mahjongH->value();
value.bit(3) &= !aleck64.controls.mahjongL->value();
value.bit(4) &= !aleck64.controls.mahjongPon->value();
value.bit(1) &= !d->value();
value.bit(2) &= !h->value();
value.bit(3) &= !l->value();
value.bit(4) &= !pon->value();
}
return value;
}
}
+1 -1
View File
@@ -66,7 +66,7 @@ struct hipai : Aleck64::GameConfig {
auto readExpansionPort() -> u32 {
n32 value = 0xffff'ffff;
value.bit(16, 23) = aleck64.controls.mahjong(mahjongRow);
value.bit(16, 23) = aleck64.mahjong.read(mahjongRow);
return value;
}
Loaded 100 of 1363 files, more files were not shown because too many files have changed in this diff. Show more