Compare commits

...
76 Commits
Author SHA1 Message Date
Christopher Bonhage 41d1b7e57d Fix Controller "Nothing" checked state in portMenu 2025-08-26 09:40:01 +01:00
invertego c28ba81684 Revert "nall: clean up string conversions in cue/mmi decode"
This reverts commit 81dea4cf7e.
2025-08-26 09:39:42 +01:00
invertego 1561a1d0b4 Revert "nall: remove memory ownership from string_view"
This reverts commit 70fa70fff4.
2025-08-26 09:39:42 +01:00
jcm 0a302dcf0e desktop-ui: Create program guard for system menu items 2025-08-23 20:25:26 +01:00
TascoDLX 07051ab738 desktop-ui: avoid overwriting paused status message 2025-08-23 19:39:31 +01:00
jcm 973030ab67 ares: Set up color settings before calling colors() 2025-08-23 19:39:00 +01:00
jcm 67716bcedf hiro/cocoa: Don't autoenable menu items 2025-08-22 12:40:07 +01:00
Giovanni Bajo e06b748516 n64: activate pak menu on all four controllers 2025-08-22 11:16:13 +01:00
remutro 02dac5b4b7 Update controller pak menues for the N64 2025-08-22 08:30:17 +01:00
jcm a4b8571b61 desktop-ui: remove Apply buttons from driver settings 2025-08-21 14:25:51 +01:00
Roger Sanders a3fa22b5fc ld: Revert palette nullptr check 2025-08-21 14:25:45 +01:00
Roger Sanders 9e9b6f8642 ld: Apply a pixel offset patch to fix alignment under H40 mode 2025-08-21 14:25:45 +01:00
Roger Sanders 5f6a0983bd ld: Fixed some stop point resume and image blanking behaviour 2025-08-21 14:25:45 +01:00
Roger Sanders 2fcab5ca5f ld: Fix overscan mode, and address some redraw issues. 2025-08-21 14:25:45 +01:00
jcm 778a32700d screen: consistently refresh palette after reset() 2025-08-20 12:33:43 +01:00
Luke Usher a6577318b6 md: bump save state version 2025-08-19 13:57:24 +01:00
Roger Sanders badd476d3b ld: Fixed a double buffering issue which caused single-frame errors 2025-08-19 13:56:36 +01:00
Roger Sanders 5a3548402b ld: Fixed several aspects of video disable and stop point behaviour 2025-08-18 15:42:20 +01:00
User c1f0307a8d gba: allow running DMA during CPU idle cycles 2025-08-17 22:30:04 +01:00
Roger Sanders 77c0718df4 ld: Convert video composition to use 16.16 fixed point for a 3x speedup 2025-08-17 16:00:42 +01:00
Roger Sanders 30bff8dd06 ld: Cleaned up version of video compositing prior to optimization 2025-08-17 16:00:42 +01:00
Roger Sanders b1cf9bf4e5 ld: Added support for remaining analog audio mixing features 2025-08-17 07:52:41 +01:00
Roger Sanders bafc2b6ef4 ld: Fixed a potential out of bounds array index issue in the previous commit 2025-08-16 06:31:39 +01:00
Roger Sanders 565591568f ld: Fixed a few disc side changing issues 2025-08-16 06:31:39 +01:00
Roger Sanders 9df891606f ld: Removed region blocks for now since titles appear region free 2025-08-16 00:43:31 +01:00
Roger Sanders eea666317b ld: Fixed incorrect alignment of CLV seek operations. Improved media detection. 2025-08-16 00:43:31 +01:00
Luke Usher ceff1d47a6 ld: preliminary support for multi-disc/multi-side titles
Mostly untested, but verified going from "Disc 1 side 1" to "No Disc" to "Disc 1 Side 1" again in the bios works as expected.
2025-08-15 12:58:47 +01:00
invertego 81dea4cf7e nall: clean up string conversions in cue/mmi decode
Now that the usage issues with string_view have been resolved it can be
safely used to wrap a vector<u8>. I'll note that passing a vector<u8>
directly to the string constructor would work as well (thanks to a
stringify overload) but I'm preserving the wishes of the author of the
MegaLD code to keep the type conversions explicit.

I'll also note that we're still constructing a string from the
string_view when we call JSON::unserialize(). Ideally, it would take a
string_view instead of a const string& and we could avoid the copy.
Unfortunately, all of the markup decoders currently require the input to
be null-terminated, which is only guaranteed by the string class.
2025-08-14 08:56:08 +01:00
jcmandkev4cards 17fa3b58a6 desktop-ui: Fix hotkey behavior when unfocused
Co-authored-by: kev4cards <[email protected]>
2025-08-14 07:46:11 +01:00
invertego 70fa70fff4 nall: remove memory ownership from string_view
Previously, string_view had a constuctor that allocated a new string and
forwarded all arguments to the string constructor. Among other things,
this allowed construction of string_view objects using any of the
various stringify overloads. However, there were drawbacks:

1.  The constructor string_view(const char*, u32) shadowed the
    forwarding constructor, leading to very different behavior. In the
    forwarding version, the first parameter would be treated as a
    null-terminated string, and the second parameter would be formatted
    as a string and concatenated with the first.
2.  Having both owning and non-owning modes makes it difficult for
    someone reading the code to reason about lifetimes, as they depend
    on how the object was constructed. It also deviates from the
    behavior of the identically-named standard C++ string_view.
2025-08-14 07:45:14 +01:00
Roger Sanders 137afe5b5b ld: Fix memory issues with MegaLD games after merge. Some additional cleanup. 2025-08-14 07:44:37 +01:00
Luke Usher 545790f048 Revert "nall: in priority-queue, call event callbacks with correct timing"
This reverts commit 6992197fb8.
2025-08-13 17:34:12 +01:00
Luke Usher 807ef03005 Revert "n64: improve PI DMA accuracy with stricter timings"
This reverts commit bea395b24b.
2025-08-13 17:34:12 +01:00
Luke Usher ce89a31749 ld: pass system type through a pak attribute 2025-08-13 17:25:28 +01:00
Luke Usher 3e7675cafb ld: pass location through a pak attribute 2025-08-13 17:25:23 +01:00
Luke Usher 4132375f26 desktop-ui: fix a crash when opening the settings dialog from the "missing firmware" prompt 2025-08-13 17:25:17 +01:00
Luke Usher 5e0bd5b3b8 ld: nall-ize mmi; remove nlohmann/json
Likely still room for improvement; especially if multi-side games exist, but that's a later problem.

mia: minor cleanups/nallisms
2025-08-13 17:25:11 +01:00
Luke Usher d00c84ff1b mia: add laserdisc medium type 2025-08-13 17:25:06 +01:00
Luke Usher 97f3a7a6fb thirdparty: fix qoi/qon build on all platforms 2025-08-13 17:25:00 +01:00
Roger Sanders 8c656cdfd3 mld: Initial MegaLD support based on a skeleton by Luke Usher 2025-08-13 17:24:54 +01:00
Giovanni Bajo bea395b24b n64: improve PI DMA accuracy with stricter timings
This commit splits the PI DMA-Write transfers (that is, loads from ROM)
in multiple blocks, following what happens on real hardware. Moreover,
it tries to more accurately reflect timing that each block takes. This
is easier to be done at the block granularity, otherwise the timing
algorithm would need to figure it out again the whole block splitting
logic.

From the point of view of the running application, now a ROM con see
the two hardware registers PI_CART_ADDR and PI_RDRAM_ADDR increment
one block at a time during the transfer, which more accurately reflect
what happens on real hardware.

This commit is validated with n64_pi_dma_test test ROM
(https://github.com/rasky/n64_pi_dma_test) which we now pass in full,
including timing tests with the default 10% tolerance.
2025-08-12 14:09:16 +01:00
Giovanni Bajo 6992197fb8 nall: in priority-queue, call event callbacks with correct timing
This allows callbacks for repeated events (rescheduled at each callback)
to not have additional latency caused by the latency with which we call
step().
2025-08-12 14:09:16 +01:00
Giovanni Bajo 097698ab63 n64: support configuration of different paks via homebrew header
The homebrew header supports configuring a different controller and pak
for each port, but mia couldn't represent that in the manifest. The
required change is trivial though.

For backward compatibility, if the homebrew header doesn't declare
anything (which is the same as asking for a standard controller in all
four ports with no paks), we install a cpak in port 1 and a rpak in
port 2, which is also what we have been doing forever with commercial
games.
2025-08-12 14:08:59 +01:00
Giovanni Bajo 46abceebf5 n64: fix controller pak formatting
Controller pak formatting had two bugs: checksum calculation was wrong
(didn't really iterate on all bugs), and the FAT backup copy was in the
wrong position.
2025-08-08 09:03:54 +01:00
Giovanni Bajo 06130778b7 n64: fix readback of PI_CART_ADDR after direct I/O 2025-08-06 15:51:22 +01:00
jcm 85b8d6c5a5 nall: Dynamically load SetThreadDescription on Windows 2025-08-06 08:57:28 +01:00
Luke Usher 8436634bc1 desktop-ui: remove "Fixed" and "Pixel Perfect" scale factors
Pixel perfect (1.1 PAR) can still be achieved "Scale:Integer" combined with "Aspect: No correction"; this works better than the 'Pixel Perfect' setting ever did.
2025-07-29 10:12:10 +01:00
Giovanni Bajo 5972b4e1aa Implement aspect correction mode
Currently, Ares has an "Aspect Correction" checkbox that allows to
disable the PAR (pixel aspect ratio) configuration made by each core,
to basically display the framebuffer without any additional scaling,
with host PC aspect ratio.

In addition to this, it is common for people to request an
"anamorphic 16:9" option, to correctly display games the way they would
appear on a 16:9 TV when stretched. Sometimes, homebrew games are even
designed to be viewed like this.

This PR changes the aspect mode from a boolean to a 3-value option.
The standard option is now named "Standard (4:3 TV)", and a new
option called "Anamorphic (16:9 TV)" is added.
2025-07-29 09:45:52 +01:00
jcm 426606bd02 desktop-ui: Defer settings window initialization on startup 2025-07-27 19:04:22 +01:00
Giovanni Bajo f9604e29e6 n64: correctly render full resolution on PAL
Currently, we render PAL games as 640x480 which is wrong because PAL has
more vertical resolution. This causes PAL graphics to be reduced in
quality compared to a real output.

This commit renders PAL at 640x576 which is the commonly used active
display area used by PAL games, and then configure the aspect ratio to
make sure the output is correct.
2025-07-27 19:04:12 +01:00
jcm 7d7b8ea4b2 n64: Reinitialize paraLLEl-RDP on system reset 2025-07-23 15:52:10 +01:00
remutro 2ed529d564 Fix mouse capture on Windows devices 2025-07-23 13:35:56 +01:00
clippy 73148db456 prevent 64 bit access to RCP over gdb 2025-07-23 13:35:37 +01:00
User 2fbc9c391c arm7tdmi: fix cycle order for SWP instruction 2025-07-23 11:29:10 +01:00
png183 12f3d6d89d gba: add comments to clarify how the upper 8 bits of SIOMLT_SEND are used 2025-07-23 11:28:42 +01:00
User 78c13dddef gba: SIOMLT_SEND should be a 16-bit register 2025-07-23 11:28:42 +01:00
Luke Usher be19b58307 sfc: add support for Limited Run Games SNES rumble specification.
See https://github.com/LimitedRunGames-Tech/snes-rumble
2025-07-21 13:09:30 +01:00
jcm bca80dc784 desktop-ui: Make internal program methods private; add docstrings 2025-07-20 19:38:22 +01:00
jcm e6fe07b084 desktop-ui: Fix undefined behavior in exit handler 2025-07-20 19:38:22 +01:00
jcm c28e47cb93 deps: Update dependencies to 2025-07-14 release 2025-07-15 12:38:57 +01:00
jcm c217662211 desktop-ui: Prevent deadlock when exiting 2025-07-14 14:49:40 +01:00
jcm 975a35e7a7 build: Provide librashader header path hint on all OSs 2025-07-14 10:38:40 +01:00
jcm 2e9233e534 CI: Ignore signing errors on Windows 2025-07-14 10:38:21 +01:00
jcm 4c6d151f7f build: Statically link runtime library for Windows CI builds 2025-07-14 10:38:13 +01:00
invertego 383931bde9 build: remove extraneous msvc optimization flags
/Oi, /Ob2, and /Ot are implied by /O2, which CMake adds by default in
Release and RelWithDebInfo builds. In RelWithDebInfo builds CMake also
downgrades inlining by adding in /Ob1, but we already have a workaround
for this. MinSizeRel builds may see a change in behavior, where CMake
passes /O1 instead, but in any case that is the recommended flag for
minimizing binary size.

The removal of /GL is especially important for MSVC builds. CMake will
add it when IPO is enabled, and forcing it on for all non-debug builds
was not simply redundant; it was overriding the user preference
expressed with ENABLE_IPO. ClangCL requires other flags to enable the
equivalent LTO feature, and this is already addressed elsewhere.
2025-07-14 10:37:55 +01:00
TascoDLX 1ac8c602c9 ms: remove obsolete screen resize code 2025-07-13 15:31:15 +01:00
Giovanni Bajo d5135ca35f n64: improve gdb memory hooks
Now support reads and writes of arbitrary lengths in both RDRAM
and RCP ares (using respectively 8-bit and 32-bit accesses).
2025-07-13 15:30:52 +01:00
User 8265726879 gba: prefetcher cannot be read when R15 is misaligned 2025-07-12 13:43:20 +01:00
User 5261757f3c gba: prefetcher should not be marked full if read on the same cycle that it was filled 2025-07-12 13:43:20 +01:00
Luke Usher c099be2b85 pce: force enable Pixel Accuracy VDP
The scanline renderer in this case is too buggy and causes too many game issues at this time.
2025-07-10 19:27:26 +01:00
jcm de8143ef47 CI: Upload source archives with releases 2025-07-10 18:24:30 +01:00
jcm b80f08956b build: Fix ARES_VERSION_OVERRIDE in source archives 2025-07-10 18:24:30 +01:00
kev4cards a3088f6d4c node, n64, ps1: rework ps1 stick into a virtual square, adjust scaling for N64 stick, and move common code into Node::Input::Axis 2025-07-10 10:08:06 +01:00
jcm fdaf0b65a1 build: Run gersemi CMake formatting pass 2025-07-10 10:06:03 +01:00
jcm 71e0ad1f5b build: Improve build code for test harnesses 2025-07-10 10:06:03 +01:00
User c87be1b4f0 gba: more prefetcher timing improvements 2025-07-10 10:05:42 +01:00
132 changed files with 6179 additions and 777 deletions

No files matched your search

+5
View File
@@ -42,3 +42,8 @@ do
zip -r ../ares-${package}.zip ${outdir}
cd -
done
# Package the source archive
pushd src
git archive --format=tar.gz -o ../ares-source.tar.gz HEAD
popd
+1
View File
@@ -89,6 +89,7 @@ jobs:
TARGET_PRESET: ${{ matrix.platform.target-cmake-preset }}
- name: "Windows: sign"
if: runner.os == 'Windows' && inputs.codesign
continue-on-error: true
uses: DanaBear/code-sign-action@98c79121b376beab8d6a9484f445089db4461bca
with:
certificate: '${{ secrets.WIN_CERTIFICATE }}'
+1
View File
@@ -73,6 +73,7 @@ jobs:
name: ares ${{ steps.check.outputs.versionName }}
body: ${{ steps.check.outputs.description }}
files: |
${{ github.workspace }}/ares-source.tar.gz
${{ github.workspace }}/ares-macos-universal.zip
${{ github.workspace }}/ares-macos-universal-dSYMs.zip
${{ github.workspace }}/ares-windows-x64.zip
+2 -1
View File
@@ -102,7 +102,8 @@
"ARES_BUILD_LOCAL": false,
"ARES_BUILD_OPTIONAL_TARGETS": true,
"ARES_UNITY_CORES": true,
"ARES_PRECOMPILE_HEADERS": true
"ARES_PRECOMPILE_HEADERS": true,
"CMAKE_MSVC_RUNTIME_LIBRARY": {"type": "STRING", "value": "MultiThreaded"}
}
},
{
+29 -1
View File
@@ -828,4 +828,32 @@ You may add additional accurate notices of copyright ownership.
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
----------------------------------------------------------------------
----------------------------------------------------------------------
----------------------------------------------------------------------
qoi/qon
Copyright (c) 2025, Roger Sanders
Revised QOI format, achieving higher compression with equivalent performance.
Based on the "Quite OK Image" format for fast, lossless image compression
Copyright (c) 2021, Dominic Szablewski - https://phoboslab.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
----------------------------------------------------------------------
+51
View File
@@ -5,6 +5,57 @@ struct Axis : Input {
auto value() const -> s64 { return _value; }
auto setValue(s64 value) -> void { _value = value; }
auto setOperatingRange(s16 position, double saturationRadius, double offset) -> double {
return position * saturationRadius / 32767.0 + offset;
}
auto processDeadzoneAndResponseCurve(double position, double innerDeadzone, double cardinalMax, double offset) -> double {
auto lengthAbsolute = abs(position - offset);
if(lengthAbsolute <= innerDeadzone) {
position = offset;
} else {
lengthAbsolute = (lengthAbsolute - innerDeadzone) * (cardinalMax) / (cardinalMax - innerDeadzone) / lengthAbsolute;
position = (position - offset) * lengthAbsolute + offset;
}
return position;
}
auto revisePosition(double position, double length, double saturationRadius, double offset) -> double {
if(length > saturationRadius) {
length = saturationRadius / length;
position = (position - offset) * length + offset;
}
return position;
}
auto applyGateBoundaries(double innerDeadzone, double cardinalMax, double diagonalMax, double positionX, double positionY, double offset, double& boundedPositionX, double& boundedPositionY) -> void {
if(positionX != offset && positionY != offset) {
auto slope = (positionY - offset) / (positionX - offset);
auto edgex = copysign(cardinalMax / (abs(slope) + (cardinalMax - diagonalMax) / diagonalMax), positionX);
auto edgey = copysign(min(abs(edgex * slope), cardinalMax / (1.0 / abs(slope) + (cardinalMax - diagonalMax) / diagonalMax)), positionY);
edgex = edgey / slope;
auto distanceToEdge = hypot(edgex, edgey);
auto length = hypot(positionX - offset, positionY - offset);
if(length > distanceToEdge) {
positionX = edgex + offset;
positionY = edgey + offset;
}
}
boundedPositionX = positionX;
boundedPositionY = positionY;
}
auto clampAxisToNearestBoundary(double position, double offset, double cardinalMax) -> double {
if(abs(position - offset) > cardinalMax) position = copysign(cardinalMax, position - offset) + offset;
return position;
}
auto counteractPrecisionError(double position) -> double {
return copysign(abs(position) + 1e-09, position);
}
protected:
s64 _value = 0;
s64 _minimum = -32768;
+30 -1
View File
@@ -7,6 +7,8 @@ Screen::Screen(string name, u32 width, u32 height) : Video(name) {
_inputB = new u32[width * height]();
_output = new u32[width * height]();
_rotate = new u32[width * height]();
_lineOverrideActive.resize(width * height, false);
_lineOverride.resize(width * height, nullptr);
if constexpr(ares::Video::Threaded) {
_thread = nall::thread::create({&Screen::main, this});
@@ -50,6 +52,8 @@ auto Screen::power() -> void {
memory::fill<u32>(_inputB.data(), _canvasWidth * _canvasHeight, _fillColor);
memory::fill<u32>(_output.data(), _canvasWidth * _canvasHeight, _fillColor);
memory::fill<u32>(_rotate.data(), _canvasWidth * _canvasHeight, _fillColor);
memory::fill<n1>(_lineOverrideActive.data(), _canvasWidth * _canvasHeight, false);
memory::fill<const u32*>(_lineOverride.data(), _canvasWidth * _canvasHeight, nullptr);
}
auto Screen::pixels(bool frame) -> array_span<u32> {
@@ -61,6 +65,7 @@ auto Screen::pixels(bool frame) -> array_span<u32> {
auto Screen::resetPalette() -> void {
lock_guard<recursive_mutex> lock(_mutex);
_palette.reset();
refreshPalette();
}
auto Screen::resetSprites() -> void {
@@ -117,18 +122,21 @@ auto Screen::setSaturation(f64 saturation) -> void {
lock_guard<recursive_mutex> lock(_mutex);
_saturation = saturation;
_palette.reset();
refreshPalette();
}
auto Screen::setGamma(f64 gamma) -> void {
lock_guard<recursive_mutex> lock(_mutex);
_gamma = gamma;
_palette.reset();
refreshPalette();
}
auto Screen::setLuminance(f64 luminance) -> void {
lock_guard<recursive_mutex> lock(_mutex);
_luminance = luminance;
_palette.reset();
refreshPalette();
}
auto Screen::setFillColor(u32 fillColor) -> void {
@@ -187,6 +195,7 @@ auto Screen::colors(u32 colors, function<n64 (n32)> color) -> void {
_colors = colors;
_color = color;
_palette.reset();
refreshPalette();
}
auto Screen::frame() -> void {
@@ -226,7 +235,13 @@ auto Screen::refresh() -> void {
auto source = input + y * pitch;
auto target = output + y * width;
if(_interlace) {
if (_lineOverrideActive[y]) {
auto source = _lineOverride[y];
for(u32 x : range(width)) {
auto color = *source++;
*target++ = color;
}
} else if(_interlace) {
if((_interlaceField & 1) == (y & 1)) {
for(u32 x : range(width)) {
auto color = _palette[*source++];
@@ -334,6 +349,20 @@ auto Screen::refresh() -> void {
memory::fill<u32>(_inputB.data(), width * height, _fillColor);
}
auto Screen::lookupPalette(u32 index) -> u32 {
return _palette[index];
}
auto Screen::overrideLineDraw(u32 y, const u32* source) -> void {
_lineOverride[y] = source;
_lineOverrideActive[y] = true;
}
auto Screen::clearOverrideLineDraw(u32 y) -> void {
_lineOverrideActive[y] = false;
_lineOverride[y] = nullptr;
}
auto Screen::refreshPalette() -> void {
lock_guard<recursive_mutex> lock(_mutex);
if(_palette) return;
+5
View File
@@ -61,6 +61,9 @@ struct Screen : Video {
auto colors(u32 colors, function<n64 (n32)> color) -> void;
auto frame() -> void;
auto refresh() -> void;
auto lookupPalette(u32 index) -> u32;
auto overrideLineDraw(u32 y, const u32* source) -> void;
auto clearOverrideLineDraw(u32 y) -> void;
auto serialize(string& output, string depth) -> void override;
auto unserialize(Markup::Node node) -> void override;
@@ -94,6 +97,8 @@ protected:
unique_pointer<u32[]> _output;
unique_pointer<u32[]> _rotate;
unique_pointer<u32[]> _palette;
vector<n1> _lineOverrideActive;
vector<const u32*> _lineOverride;
vector<Node::Video::Sprite> _sprites;
//unserialized:
+4
View File
@@ -1,5 +1,9 @@
list(REMOVE_DUPLICATES ares.components)
set(ARES_COMPONENTS_LIST "${ares.components}" CACHE LIST "Enabled shared hardware components" FORCE)
mark_as_advanced(ARES_COMPONENTS_LIST)
ares_add_sources(INCLUDED CMakeLists.txt)
macro(component_sources)
@@ -104,12 +104,12 @@ auto ARM7TDMI::armInstructionDataRegisterShift
auto ARM7TDMI::armInstructionMemorySwap
(n4 m, n4 d, n4 n, n1 byte) -> void {
n32 rm = r(m) + (m == 15 ? 4 : 0);
lock();
n32 word = load((byte ? Byte : Word), r(n));
store((byte ? Byte : Word), r(n), rm);
r(d) = word;
store((byte ? Byte : Word), r(n), r(m) + (m == 15 ? 4 : 0));
unlock();
idle();
r(d) = word;
}
auto ARM7TDMI::armInstructionMoveHalfImmediate
@@ -123,7 +123,10 @@ auto ARM7TDMI::armInstructionMoveHalfImmediate
if(pre == 0) rn = up ? rn + immediate : rn - immediate;
if(pre == 0 || writeback) r(n) = rn + (n == 15 ? 4 : 0);
if(mode == 1) r(d) = rd;
if(mode == 1) {
idle();
r(d) = rd;
}
}
auto ARM7TDMI::armInstructionMoveHalfRegister
@@ -138,7 +141,10 @@ auto ARM7TDMI::armInstructionMoveHalfRegister
if(pre == 0) rn = up ? rn + rm : rn - rm;
if(pre == 0 || writeback) r(n) = rn + (n == 15 ? 4 : 0);
if(mode == 1) r(d) = rd;
if(mode == 1) {
idle();
r(d) = rd;
}
}
auto ARM7TDMI::armInstructionMoveImmediateOffset
@@ -152,7 +158,10 @@ auto ARM7TDMI::armInstructionMoveImmediateOffset
if(pre == 0) rn = up ? rn + immediate : rn - immediate;
if(pre == 0 || writeback) r(n) = rn;
if(mode == 1) r(d) = rd;
if(mode == 1) {
idle();
r(d) = rd;
}
}
auto ARM7TDMI::armInstructionMoveMultiple
@@ -219,7 +228,10 @@ auto ARM7TDMI::armInstructionMoveRegisterOffset
if(pre == 0) rn = up ? rn + rm : rn - rm;
if(pre == 0 || writeback) r(n) = rn;
if(mode == 1) r(d) = rd;
if(mode == 1) {
idle();
r(d) = rd;
}
}
auto ARM7TDMI::armInstructionMoveSignedImmediate
@@ -233,7 +245,10 @@ auto ARM7TDMI::armInstructionMoveSignedImmediate
if(pre == 0) rn = up ? rn + immediate : rn - immediate;
if(pre == 0 || writeback) r(n) = rn + (n == 15 ? 4 : 0);
if(mode == 1) r(d) = rd;
if(mode == 1) {
idle();
r(d) = rd;
}
}
auto ARM7TDMI::armInstructionMoveSignedRegister
@@ -248,7 +263,10 @@ auto ARM7TDMI::armInstructionMoveSignedRegister
if(pre == 0) rn = up ? rn + rm : rn - rm;
if(pre == 0 || writeback) r(n) = rn + (n == 15 ? 4 : 0);
if(mode == 1) r(d) = rd;
if(mode == 1) {
idle();
r(d) = rd;
}
}
auto ARM7TDMI::armInstructionMoveToCoprocessorFromRegister
@@ -106,13 +106,14 @@ auto ARM7TDMI::thumbInstructionLoadLiteral
(n8 displacement, n3 d) -> void {
n32 address = (r(15) & ~3) + (displacement << 2);
r(d) = load(Word, address);
idle();
}
auto ARM7TDMI::thumbInstructionMoveByteImmediate
(n3 d, n3 n, n5 offset, n1 mode) -> void {
switch(mode) {
case 0: store(Byte, r(n) + offset, r(d)); break; //STRB
case 1: r(d) = load(Byte, r(n) + offset); break; //LDRB
case 1: r(d) = load(Byte, r(n) + offset); idle(); break; //LDRB
}
}
@@ -120,7 +121,7 @@ auto ARM7TDMI::thumbInstructionMoveHalfImmediate
(n3 d, n3 n, n5 offset, n1 mode) -> void {
switch(mode) {
case 0: store(Half, r(n) + offset * 2, r(d)); break; //STRH
case 1: r(d) = load(Half, r(n) + offset * 2); break; //LDRH
case 1: r(d) = load(Half, r(n) + offset * 2); idle(); break; //LDRH
}
}
@@ -158,11 +159,11 @@ auto ARM7TDMI::thumbInstructionMoveRegisterOffset
case 0: store(Word, r(n) + r(m), r(d)); break; //STR
case 1: store(Half, r(n) + r(m), r(d)); break; //STRH
case 2: store(Byte, r(n) + r(m), r(d)); break; //STRB
case 3: r(d) = load(Byte | Signed, r(n) + r(m)); break; //LDSB
case 4: r(d) = load(Word, r(n) + r(m)); break; //LDR
case 5: r(d) = load(Half, r(n) + r(m)); break; //LDRH
case 6: r(d) = load(Byte, r(n) + r(m)); break; //LDRB
case 7: r(d) = load(Half | Signed, r(n) + r(m)); break; //LDSH
case 3: r(d) = load(Byte | Signed, r(n) + r(m)); idle(); break; //LDSB
case 4: r(d) = load(Word, r(n) + r(m)); idle(); break; //LDR
case 5: r(d) = load(Half, r(n) + r(m)); idle(); break; //LDRH
case 6: r(d) = load(Byte, r(n) + r(m)); idle(); break; //LDRB
case 7: r(d) = load(Half | Signed, r(n) + r(m)); idle(); break; //LDSH
}
}
@@ -170,7 +171,7 @@ auto ARM7TDMI::thumbInstructionMoveStack
(n8 immediate, n3 d, n1 mode) -> void {
switch(mode) {
case 0: store(Word, r(13) + immediate * 4, r(d)); break; //STR
case 1: r(d) = load(Word, r(13) + immediate * 4); break; //LDR
case 1: r(d) = load(Word, r(13) + immediate * 4); idle(); break; //LDR
}
}
@@ -178,7 +179,7 @@ auto ARM7TDMI::thumbInstructionMoveWordImmediate
(n3 d, n3 n, n5 offset, n1 mode) -> void {
switch(mode) {
case 0: store(Word, r(n) + offset * 4, r(d)); break; //STR
case 1: r(d) = load(Word, r(n) + offset * 4); break; //LDR
case 1: r(d) = load(Word, r(n) + offset * 4); idle(); break; //LDR
}
}
@@ -25,7 +25,6 @@ auto ARM7TDMI::load(u32 mode, n32 address) -> n32 {
} else {
word = ROR(word, address.bit(0,1) << 3);
}
idle();
return word;
}
+6 -6
View File
@@ -30,15 +30,15 @@ auto PPU::load(Node::Object parent) -> void {
screen->refreshRateHint(4 * 1024 * 1024, 456, 154);
if(Model::GameBoy()) {
screen->colors(1 << 2, {&PPU::colorGameBoy, this});
screen->setFillColor(0);
colorEmulationDMG = screen->append<Node::Setting::String>("Color Emulation", "Game Boy", [&](auto value) {
screen->resetPalette();
});
colorEmulationDMG->setAllowedValues({"Game Boy", "Game Boy Pocket", "RGB"});
colorEmulationDMG->setDynamic(true);
screen->colors(1 << 2, {&PPU::colorGameBoy, this});
screen->setFillColor(0);
interframeBlending = screen->append<Node::Setting::Boolean>("Interframe Blending", true, [&](auto value) {
screen->setInterframeBlending(value);
});
@@ -46,14 +46,14 @@ auto PPU::load(Node::Object parent) -> void {
}
if(Model::GameBoyColor()) {
screen->colors(1 << 15, {&PPU::colorGameBoyColor, this});
screen->setFillColor(0x7fff);
colorEmulationCGB = screen->append<Node::Setting::Boolean>("Color Emulation", true, [&](auto value) {
screen->resetPalette();
});
colorEmulationCGB->setDynamic(true);
screen->colors(1 << 15, {&PPU::colorGameBoyColor, this});
screen->setFillColor(0x7fff);
interframeBlending = screen->append<Node::Setting::Boolean>("Interframe Blending", true, [&](auto value) {
screen->setInterframeBlending(value);
});
+1 -4
View File
@@ -1,7 +1,4 @@
ares_components(
arm7tdmi
s3511a
)
ares_components(arm7tdmi s3511a)
ares_add_sources(
CORE #
+24 -19
View File
@@ -1,7 +1,8 @@
auto CPU::sleep() -> void {
dmaRun();
ARM7TDMI::irq = irq.synchronizer;
prefetchStep(1);
if(!dmac.step()) {
ARM7TDMI::irq = irq.synchronizer;
prefetchStep(1);
}
}
template <bool UseDebugger>
@@ -48,14 +49,18 @@ inline auto CPU::getBus(u32 mode, n32 address) -> n32 {
mode = cartMode(mode, address);
context.romAccess = true;
if(mode & Prefetch && wait.prefetch) {
if(mode & Word) address &= ~3; //prevents misaligned PC from reading incorrect values
if(mode & Nonsequential && prefetch.cycle == 0 && prefetch.empty()) prefetchReset();
prefetchSync(address);
prefetchStep(1);
word = prefetchRead();
if(mode & Word) word |= prefetchRead() << 16;
if(address == prefetch.addr && (!prefetch.empty() || prefetch.ahead)) {
prefetchStepInternal(1);
word = prefetchRead();
if(mode & Word) word |= prefetchRead() << 16;
} else {
if(mode & Word) address &= ~3; //prevents misaligned PC from reading incorrect values
prefetchSync(mode, address);
step(waitCartridge(mode, address));
word = cartridge.readRom<false>(mode, address);
}
} else {
if(context.dmaActive) context.dmaRomAccess = true;
if(mode & DMA) dmac.romBurst = true;
prefetchReset();
step(waitCartridge(mode, address));
word = cartridge.readRom<false>(mode, address);
@@ -81,11 +86,11 @@ inline auto CPU::getBus(u32 mode, n32 address) -> n32 {
}
auto CPU::get(u32 mode, n32 address) -> n32 {
dmaRun();
if(!(mode & DMA)) dmac.runPending();
ARM7TDMI::irq = irq.synchronizer;
context.romAccess = false;
u32 word = getBus<false>(mode, address);
if(!context.romAccess && !context.dmaActive) cartridge.mrom.burst = false;
if(!context.romAccess && !(mode & DMA)) cartridge.mrom.burst = false;
return word;
}
@@ -94,7 +99,7 @@ auto CPU::getDebugger(u32 mode, n32 address) -> n32 {
}
auto CPU::set(u32 mode, n32 address, n32 word) -> void {
dmaRun();
if(!(mode & DMA)) dmac.runPending();
ARM7TDMI::irq = irq.synchronizer;
context.romAccess = false;
@@ -136,7 +141,7 @@ auto CPU::set(u32 mode, n32 address, n32 word) -> void {
case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d:
mode = cartMode(mode, address);
context.romAccess = true;
if(context.dmaActive) context.dmaRomAccess = true;
if(mode & DMA) dmac.romBurst = true;
prefetchReset();
step(waitCartridge(mode, address));
cartridge.writeRom(mode, address, word);
@@ -155,11 +160,11 @@ auto CPU::set(u32 mode, n32 address, n32 word) -> void {
}
openBus.set(mode, address, word);
if(!context.romAccess && !context.dmaActive) cartridge.mrom.burst = false;
if(!context.romAccess && !(mode & DMA)) cartridge.mrom.burst = false;
}
auto CPU::lock() -> void {
dmaRun();
dmac.runPending();
context.busLocked = true;
}
@@ -194,11 +199,11 @@ auto CPU::cartMode(u32 mode, n32 address) -> u32 {
if(cartridge.mrom.burst == false) return mode | Nonsequential;
//determine whether sequential access may be performed
if(!context.dmaActive) {
u32 sequential = nonsequential ? Nonsequential : Sequential;
if(mode & DMA) {
u32 sequential = dmac.romBurst ? Sequential : Nonsequential;
mode |= sequential;
} else {
u32 sequential = context.dmaRomAccess ? Sequential : Nonsequential;
u32 sequential = ARM7TDMI::nonsequential ? Nonsequential : Sequential;
mode |= sequential;
}
+19 -39
View File
@@ -44,7 +44,7 @@ auto CPU::main() -> void {
}
if(halted()) {
dmaRun();
dmac.runPending();
if(!(irq.enable[0] & irq.flag[0])) {
return step(1);
}
@@ -56,32 +56,12 @@ auto CPU::main() -> void {
instruction();
}
auto CPU::dmaRun() -> void {
if(!context.dmaActive && !context.busLocked) {
context.dmaActive = true;
while(true) {
if(dma[0].run()) continue;
if(dma[1].run()) continue;
if(dma[2].run()) continue;
if(dma[3].run()) continue;
break;
}
if(context.dmaRan) {
idle();
context.dmaRan = false;
context.dmaRomAccess = false;
context.dmaActiveChannel = 0; //assign burst to a channel that cannot access ROM
}
context.dmaActive = false;
}
}
auto CPU::setInterruptFlag(u32 source) -> void {
irq.flag[1] |= source;
}
inline auto CPU::stepIRQ() -> void {
if(!context.dmaActive) {
if(!dmac.stallingCPU) {
irq.synchronizer = irq.ime[0] && (irq.enable[0] & irq.flag[0]);
irq.enable[0] = irq.enable[1];
irq.flag[0] = irq.flag[1];
@@ -93,10 +73,10 @@ auto CPU::step(u32 clocks) -> void {
if(!clocks) return;
context.hcounter = (context.hcounter + clocks) % 1232;
dma[0].waiting = max(0, dma[0].waiting - (s32)clocks);
dma[1].waiting = max(0, dma[1].waiting - (s32)clocks);
dma[2].waiting = max(0, dma[2].waiting - (s32)clocks);
dma[3].waiting = max(0, dma[3].waiting - (s32)clocks);
dmac.channel[0].waiting = max(0, dmac.channel[0].waiting - (s32)clocks);
dmac.channel[1].waiting = max(0, dmac.channel[1].waiting - (s32)clocks);
dmac.channel[2].waiting = max(0, dmac.channel[2].waiting - (s32)clocks);
dmac.channel[3].waiting = max(0, dmac.channel[3].waiting - (s32)clocks);
for(auto _ : range(clocks)) {
stepIRQ();
@@ -141,7 +121,7 @@ auto CPU::power() -> void {
for(auto& byte : iwram) byte = 0x00;
for(auto& byte : ewram) byte = 0x00;
for(auto n : range(4)) dma[n] = {n};
for(auto n : range(4)) dmac.channel[n] = {n};
for(auto n : range(4)) timer[n] = {n};
serial = {};
keypad = {};
@@ -152,21 +132,21 @@ auto CPU::power() -> void {
prefetch = {};
context = {};
dma[0].source.setBits(27); dma[0].latch.source.setBits(27);
dma[0].target.setBits(27); dma[0].latch.target.setBits(27);
dma[0].length.setBits(14); dma[0].latch.length.setBits(14);
dmac.channel[0].source.setBits(27); dmac.channel[0].latch.source.setBits(27);
dmac.channel[0].target.setBits(27); dmac.channel[0].latch.target.setBits(27);
dmac.channel[0].length.setBits(14); dmac.channel[0].latch.length.setBits(14);
dma[1].source.setBits(28); dma[1].latch.source.setBits(28);
dma[1].target.setBits(27); dma[1].latch.target.setBits(27);
dma[1].length.setBits(14); dma[1].latch.length.setBits(14);
dmac.channel[1].source.setBits(28); dmac.channel[1].latch.source.setBits(28);
dmac.channel[1].target.setBits(27); dmac.channel[1].latch.target.setBits(27);
dmac.channel[1].length.setBits(14); dmac.channel[1].latch.length.setBits(14);
dma[2].source.setBits(28); dma[2].latch.source.setBits(28);
dma[2].target.setBits(27); dma[2].latch.target.setBits(27);
dma[2].length.setBits(14); dma[2].latch.length.setBits(14);
dmac.channel[2].source.setBits(28); dmac.channel[2].latch.source.setBits(28);
dmac.channel[2].target.setBits(27); dmac.channel[2].latch.target.setBits(27);
dmac.channel[2].length.setBits(14); dmac.channel[2].latch.length.setBits(14);
dma[3].source.setBits(28); dma[3].latch.source.setBits(28);
dma[3].target.setBits(28); dma[3].latch.target.setBits(28);
dma[3].length.setBits(16); dma[3].latch.length.setBits(16);
dmac.channel[3].source.setBits(28); dmac.channel[3].latch.source.setBits(28);
dmac.channel[3].target.setBits(28); dmac.channel[3].latch.target.setBits(28);
dmac.channel[3].length.setBits(16); dmac.channel[3].latch.length.setBits(16);
for(u32 n = 0x0b0; n <= 0x0df; n++) bus.io[n] = this; //DMA
for(u32 n = 0x100; n <= 0x10f; n++) bus.io[n] = this; //Timers
+38 -30
View File
@@ -46,14 +46,14 @@ struct CPU : ARM7TDMI, Thread, IO {
auto unload() -> void;
auto main() -> void;
auto dmaRun() -> void;
auto setInterruptFlag(u32 source) -> void;
auto stepIRQ() -> void;
auto step(u32 clocks) -> void override;
auto power() -> void;
//prefetch.cpp
auto prefetchSync(n32 address) -> void;
auto prefetchSync(u32 mode, n32 address) -> void;
auto prefetchStepInternal(u32 clocks) -> void;
auto prefetchStep(u32 clocks) -> void;
auto prefetchReset() -> void;
auto prefetchRead() -> n16;
@@ -106,36 +106,48 @@ struct CPU : ARM7TDMI, Thread, IO {
n32 mask;
};
struct DMA {
struct DMAC {
//dma.cpp
auto run() -> bool;
auto transfer() -> void;
auto step() -> bool;
auto runPending() -> void;
n2 id;
n1 romBurst;
n1 active;
n2 activeChannel;
n1 stallingCPU;
n1 writeCycle;
n1 active;
i32 waiting;
struct Channel {
auto ready() -> bool;
auto read() -> void;
auto write() -> void;
n2 targetMode;
n2 sourceMode;
n1 repeat;
n1 size;
n1 drq;
n2 timingMode;
n1 irq;
n1 enable;
n2 id;
uintVN source;
uintVN target;
uintVN length;
n1 active;
i32 waiting;
n2 targetMode;
n2 sourceMode;
n1 repeat;
n1 size;
n1 drq;
n2 timingMode;
n1 irq;
n1 enable;
struct Latch {
uintVN source;
uintVN target;
uintVN length;
u32 data;
} latch;
} dma[4];
struct Latch {
uintVN source;
uintVN target;
uintVN length;
u32 data;
} latch;
} channel[4];
} dmac;
struct Timer {
//timer.cpp
@@ -175,7 +187,7 @@ struct CPU : ARM7TDMI, Thread, IO {
n1 irqEnable;
n16 data[4];
n8 data8;
n16 dataMulti;
} serial;
struct Keypad {
@@ -252,9 +264,9 @@ struct CPU : ARM7TDMI, Thread, IO {
n16 slot[8];
n32 addr; //read location of slot buffer
n32 load; //write location of slot buffer
i32 wait = 1; //wait states for current load
i32 cycle; //number of clocks elapsed on current load
i32 wait = 1; //number of clocks before next slot load
n1 stopped = 1;
n1 ahead;
} prefetch;
struct Coprocessor {
@@ -270,10 +282,6 @@ struct CPU : ARM7TDMI, Thread, IO {
n1 stopped;
n1 booted; //set to true by the GBA BIOS
n1 romAccess;
n1 dmaRomAccess;
n1 dmaRan;
n1 dmaActive;
n2 dmaActiveChannel;
n1 timerLatched;
n1 busLocked;
n32 hcounter;
+72 -31
View File
@@ -1,29 +1,59 @@
inline auto CPU::DMA::run() -> bool {
if(!active || waiting) return false;
//returns true if DMA is currently active
auto CPU::DMAC::step() -> bool {
if(!active) {
//DMA cannot start if bus is locked
if(cpu.context.busLocked) return false;
transfer();
if(irq) cpu.setInterruptFlag(CPU::Interrupt::DMA0 << id);
if(drq && id == 3) cpu.setInterruptFlag(CPU::Interrupt::Cartridge);
return true;
}
auto CPU::DMA::transfer() -> void {
u32 seek = size ? 4 : 2;
u32 mode = size ? Word : Half;
if(!cpu.context.dmaRan) {
cpu.context.dmaRan = true;
cpu.idle();
//check if DMA is ready
if(channel[0].ready() || channel[1].ready() || channel[2].ready() || channel[3].ready()) {
//transfer control of bus from CPU to DMA
active = true;
cpu.ARM7TDMI::nonsequential = true;
cpu.prefetchStep(1);
return true;
}
return false;
}
if(cpu.context.dmaActiveChannel != id) {
//run write transaction, if read has finished prior
if(writeCycle) { channel[activeChannel].write(); return true; }
//run read transaction
if(channel[0].ready()) { channel[0].read(); return true; }
if(channel[1].ready()) { channel[1].read(); return true; }
if(channel[2].ready()) { channel[2].read(); return true; }
if(channel[3].ready()) { channel[3].read(); return true; }
//DMA has finished - hand bus control back to CPU
cpu.prefetchStep(1);
romBurst = false;
activeChannel = 0; //assign burst to a channel that cannot access ROM
active = false;
return false;
}
auto CPU::DMAC::runPending() -> void {
stallingCPU = true;
while(step());
stallingCPU = false;
}
inline auto CPU::DMAC::Channel::ready() -> bool {
return active && !waiting;
}
auto CPU::DMAC::Channel::read() -> void {
u32 seek = size ? 4 : 2;
u32 mode = DMA | (size ? Word : Half );
if(cpu.dmac.activeChannel != id) {
//channel has switched - new burst transfer must be started
cpu.context.dmaRomAccess = 0;
cpu.context.dmaActiveChannel = id;
cpu.dmac.romBurst = 0;
cpu.dmac.activeChannel = id;
}
if(latch.source() < 0x0200'0000) {
cpu.idle(); //cannot access BIOS
cpu.prefetchStep(1); //cannot access BIOS
} else {
n32 addr = latch.source();
if(mode & Word) addr &= ~3;
@@ -32,8 +62,20 @@ auto CPU::DMA::transfer() -> void {
if(mode & Half) latch.data |= latch.data << 16;
}
switch(sourceMode) {
case 0: latch.source.data += seek; break;
case 1: latch.source.data -= seek; break;
}
cpu.dmac.writeCycle = true;
}
auto CPU::DMAC::Channel::write() -> void {
u32 seek = size ? 4 : 2;
u32 mode = DMA | (size ? Word : Half );
if(latch.target() < 0x0200'0000) {
cpu.idle(); //cannot access BIOS
cpu.prefetchStep(1); //cannot access BIOS
} else {
n32 addr = latch.target();
if(mode & Word) addr &= ~3;
@@ -41,11 +83,6 @@ auto CPU::DMA::transfer() -> void {
cpu.set(mode, addr, latch.data >> (addr & 2) * 8);
}
switch(sourceMode) {
case 0: latch.source.data += seek; break;
case 1: latch.source.data -= seek; break;
}
switch(targetMode) {
case 0: latch.target.data += seek; break;
case 1: latch.target.data -= seek; break;
@@ -59,21 +96,25 @@ auto CPU::DMA::transfer() -> void {
if(repeat == 1) latch.length = length;
if(repeat == 0) enable = false;
}
cpu.dmac.writeCycle = false;
if(irq) cpu.setInterruptFlag(CPU::Interrupt::DMA0 << id);
if(drq && id == 3) cpu.setInterruptFlag(CPU::Interrupt::Cartridge);
}
auto CPU::dmaVblank() -> void {
for(auto& dma : this->dma) {
if(dma.enable && dma.timingMode == 1) dma.active = true;
for(auto& channel : this->dmac.channel) {
if(channel.enable && channel.timingMode == 1) channel.active = true;
}
}
auto CPU::dmaHblank() -> void {
for(auto& dma : this->dma) {
if(dma.enable && dma.timingMode == 2) dma.active = true;
for(auto& channel : this->dmac.channel) {
if(channel.enable && channel.timingMode == 2) channel.active = true;
}
}
auto CPU::dmaHDMA() -> void {
auto& dma = this->dma[3];
if(dma.enable && dma.timingMode == 3) dma.active = true;
auto& channel = this->dmac.channel[3];
if(channel.enable && channel.timingMode == 3) channel.active = true;
}
+44 -44
View File
@@ -1,5 +1,5 @@
auto CPU::readIO(n32 address) -> n8 {
auto dma = [&]() -> DMA& { return this->dma[address / 12 & 3]; };
auto channel = [&]() -> DMAC::Channel& { return this->dmac.channel[address / 12 & 3]; };
auto timer = [&]() -> Timer& { return this->timer[address.bit(2,3)]; };
switch(address) {
@@ -10,17 +10,17 @@ auto CPU::readIO(n32 address) -> n8 {
//DMA0CNT_H, DMA1CNT_H, DMA2CNT_H, DMA3CNT_H
case 0x0400'00ba: case 0x0400'00c6: case 0x0400'00d2: case 0x0400'00de: return (
dma().targetMode << 5
| dma().sourceMode.bit(0) << 7
channel().targetMode << 5
| channel().sourceMode.bit(0) << 7
);
case 0x0400'00bb: case 0x0400'00c7: case 0x0400'00d3: case 0x0400'00df: return (
dma().sourceMode.bit(1) << 0
| dma().repeat << 1
| dma().size << 2
| dma().drq << 3
| dma().timingMode << 4
| dma().irq << 6
| dma().enable << 7
channel().sourceMode.bit(1) << 0
| channel().repeat << 1
| channel().size << 2
| channel().drq << 3
| channel().timingMode << 4
| channel().irq << 6
| channel().enable << 7
);
//TM0CNT_L, TM1CNT_L, TM2CNT_L, TM3CNT_L
@@ -61,8 +61,8 @@ auto CPU::readIO(n32 address) -> n8 {
);
//SIOMLT_SEND (SIODATA8)
case 0x0400'012a: return serial.data8;
case 0x0400'012b: return 0;
case 0x0400'012a: return serial.dataMulti.byte(0);
case 0x0400'012b: return serial.dataMulti.byte(1); //upper 8 bits are always readable, but only used in multiplayer mode
//KEYINPUT
case 0x04000130: {
@@ -248,54 +248,54 @@ auto CPU::readIO(n32 address) -> n8 {
}
auto CPU::writeIO(n32 address, n8 data) -> void {
auto dma = [&]() -> DMA& { return this->dma[address / 12 & 3]; };
auto channel = [&]() -> DMAC::Channel& { return this->dmac.channel[address / 12 & 3]; };
auto timer = [&]() -> Timer& { return this->timer[address.bit(2,3)]; };
switch(address) {
//DMA0SAD, DMA1SAD, DMA2SAD, DMA3SAD
case 0x0400'00b0: case 0x0400'00bc: case 0x0400'00c8: case 0x0400'00d4: dma().source.data.byte(0) = data; return;
case 0x0400'00b1: case 0x0400'00bd: case 0x0400'00c9: case 0x0400'00d5: dma().source.data.byte(1) = data; return;
case 0x0400'00b2: case 0x0400'00be: case 0x0400'00ca: case 0x0400'00d6: dma().source.data.byte(2) = data; return;
case 0x0400'00b3: case 0x0400'00bf: case 0x0400'00cb: case 0x0400'00d7: dma().source.data.byte(3) = data; return;
case 0x0400'00b0: case 0x0400'00bc: case 0x0400'00c8: case 0x0400'00d4: channel().source.data.byte(0) = data; return;
case 0x0400'00b1: case 0x0400'00bd: case 0x0400'00c9: case 0x0400'00d5: channel().source.data.byte(1) = data; return;
case 0x0400'00b2: case 0x0400'00be: case 0x0400'00ca: case 0x0400'00d6: channel().source.data.byte(2) = data; return;
case 0x0400'00b3: case 0x0400'00bf: case 0x0400'00cb: case 0x0400'00d7: channel().source.data.byte(3) = data; return;
//DMA0DAD, DMA1DAD, DMA2DAD, DMA3DAD
case 0x0400'00b4: case 0x0400'00c0: case 0x0400'00cc: case 0x0400'00d8: dma().target.data.byte(0) = data; return;
case 0x0400'00b5: case 0x0400'00c1: case 0x0400'00cd: case 0x0400'00d9: dma().target.data.byte(1) = data; return;
case 0x0400'00b6: case 0x0400'00c2: case 0x0400'00ce: case 0x0400'00da: dma().target.data.byte(2) = data; return;
case 0x0400'00b7: case 0x0400'00c3: case 0x0400'00cf: case 0x0400'00db: dma().target.data.byte(3) = data; return;
case 0x0400'00b4: case 0x0400'00c0: case 0x0400'00cc: case 0x0400'00d8: channel().target.data.byte(0) = data; return;
case 0x0400'00b5: case 0x0400'00c1: case 0x0400'00cd: case 0x0400'00d9: channel().target.data.byte(1) = data; return;
case 0x0400'00b6: case 0x0400'00c2: case 0x0400'00ce: case 0x0400'00da: channel().target.data.byte(2) = data; return;
case 0x0400'00b7: case 0x0400'00c3: case 0x0400'00cf: case 0x0400'00db: channel().target.data.byte(3) = data; return;
//DMA0CNT_L, DMA1CNT_L, DMA2CNT_L, DMA3CNT_L
case 0x0400'00b8: case 0x0400'00c4: case 0x0400'00d0: case 0x0400'00dc: dma().length.data.byte(0) = data; return;
case 0x0400'00b9: case 0x0400'00c5: case 0x0400'00d1: case 0x0400'00dd: dma().length.data.byte(1) = data; return;
case 0x0400'00b8: case 0x0400'00c4: case 0x0400'00d0: case 0x0400'00dc: channel().length.data.byte(0) = data; return;
case 0x0400'00b9: case 0x0400'00c5: case 0x0400'00d1: case 0x0400'00dd: channel().length.data.byte(1) = data; return;
//DMA0CNT_H, DMA1CNT_H, DMA2CNT_H, DMA3CNT_H
case 0x0400'00ba: case 0x0400'00c6: case 0x0400'00d2: case 0x0400'00de:
dma().targetMode = data.bit(5,6);
dma().sourceMode.bit(0) = data.bit(7);
channel().targetMode = data.bit(5,6);
channel().sourceMode.bit(0) = data.bit(7);
return;
case 0x0400'00bb: case 0x0400'00c7: case 0x0400'00d3: case 0x0400'00df: {
bool enable = dma().enable;
bool enable = channel().enable;
if(address != 0x0400'00df) data.bit(3) = 0; //gamepad DRQ valid for DMA3 only
dma().sourceMode.bit(1) = data.bit(0);
dma().repeat = data.bit(1);
dma().size = data.bit(2);
dma().drq = data.bit(3);
dma().timingMode = data.bit(4,5);
dma().irq = data.bit(6);
dma().enable = data.bit(7);
channel().sourceMode.bit(1) = data.bit(0);
channel().repeat = data.bit(1);
channel().size = data.bit(2);
channel().drq = data.bit(3);
channel().timingMode = data.bit(4,5);
channel().irq = data.bit(6);
channel().enable = data.bit(7);
if(!enable && dma().enable) { //0->1 transition
if(dma().timingMode == 0) {
dma().active = true; //immediate transfer mode
dma().waiting = 2;
if(!enable && channel().enable) { //0->1 transition
if(channel().timingMode == 0) {
channel().active = true; //immediate transfer mode
channel().waiting = 2;
}
dma().latch.source = dma().source;
dma().latch.target = dma().target;
dma().latch.length = dma().length;
} else if(!dma().enable) {
dma().active = false;
channel().latch.source = channel().source;
channel().latch.target = channel().target;
channel().latch.length = channel().length;
} else if(!channel().enable) {
channel().active = false;
}
return;
}
@@ -346,8 +346,8 @@ auto CPU::writeIO(n32 address, n8 data) -> void {
return;
//SIOMLT_SEND (SIODATA8)
case 0x0400'012a: serial.data8 = data; return;
case 0x0400'012b: return;
case 0x0400'012a: serial.dataMulti.byte(0) = data; return;
case 0x0400'012b: serial.dataMulti.byte(1) = data; return; //upper 8 bits are always writable, but only used in multiplayer mode
//KEYCNT
case 0x0400'0132:
+22 -18
View File
@@ -1,48 +1,52 @@
auto CPU::prefetchSync(n32 address) -> void {
if(address == prefetch.addr) return;
if(prefetch.wait - prefetch.cycle == 1) step(1);
auto CPU::prefetchSync(u32 mode, n32 address) -> void {
if(prefetch.wait == 1) step(1);
n32 size = (mode & Word) ? 4 : 2;
prefetch.stopped = false;
prefetch.addr = address;
prefetch.load = address;
prefetch.wait = waitCartridge(Half | Nonsequential, prefetch.load);
prefetch.cycle = 0;
prefetch.ahead = false;
prefetch.addr = address + size;
prefetch.load = address + size;
prefetch.wait = waitCartridge(Half | Sequential, prefetch.load);
}
auto CPU::prefetchStep(u32 clocks) -> void {
auto CPU::prefetchStepInternal(u32 clocks) -> void {
step(clocks);
if(!wait.prefetch || prefetch.stopped) return;
while(!prefetch.full() && clocks--) {
prefetch.cycle++;
if(prefetch.wait > prefetch.cycle) continue;
while(clocks--) {
if(prefetch.full()) {
prefetch.stopped = true;
prefetch.ahead = false;
break;
}
if(--prefetch.wait) continue;
prefetch.slot[prefetch.load >> 1 & 7] = cartridge.readRom<false>(Half | Nonsequential, prefetch.load); //todo: make access sequentiality consistent with timing used
prefetch.load += 2;
prefetch.wait = waitCartridge(Half | Sequential, prefetch.load);
prefetch.cycle = 0;
}
}
if(prefetch.full()) prefetch.stopped = 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 - prefetch.cycle == 1) step(1);
if(prefetch.wait == 1) step(1);
prefetch.stopped = true;
prefetch.ahead = false;
prefetch.wait = 0;
prefetch.addr = 0;
prefetch.load = 0;
prefetch.cycle = 0;
}
auto CPU::prefetchRead() -> n16 {
if(prefetch.stopped && prefetch.empty()) {
prefetch.stopped = false;
prefetch.wait = waitCartridge(Half | Nonsequential, prefetch.load);
prefetch.cycle = 0;
}
if(prefetch.empty()) prefetchStep(prefetch.wait - prefetch.cycle);
if(prefetch.empty()) prefetchStepInternal(prefetch.wait);
n16 word = prefetch.slot[prefetch.addr >> 1 & 7];
prefetch.addr += 2;
+32 -31
View File
@@ -5,31 +5,36 @@ auto CPU::serialize(serializer& s) -> void {
s(iwram);
s(ewram);
for(auto& dma : this->dma) {
s(dma.id);
s(dma.active);
s(dma.waiting);
s(dma.targetMode);
s(dma.sourceMode);
s(dma.repeat);
s(dma.size);
s(dma.drq);
s(dma.timingMode);
s(dma.irq);
s(dma.enable);
s(dma.source.data);
s(dma.source.mask);
s(dma.target.data);
s(dma.target.mask);
s(dma.length.data);
s(dma.length.mask);
s(dma.latch.source.data);
s(dma.latch.source.mask);
s(dma.latch.target.data);
s(dma.latch.target.mask);
s(dma.latch.length.data);
s(dma.latch.length.mask);
s(dma.latch.data);
s(dmac.romBurst);
s(dmac.active);
s(dmac.activeChannel);
s(dmac.stallingCPU);
s(dmac.writeCycle);
for(auto& channel : this->dmac.channel) {
s(channel.id);
s(channel.active);
s(channel.waiting);
s(channel.targetMode);
s(channel.sourceMode);
s(channel.repeat);
s(channel.size);
s(channel.drq);
s(channel.timingMode);
s(channel.irq);
s(channel.enable);
s(channel.source.data);
s(channel.source.mask);
s(channel.target.data);
s(channel.target.mask);
s(channel.length.data);
s(channel.length.mask);
s(channel.latch.source.data);
s(channel.latch.source.mask);
s(channel.latch.target.data);
s(channel.latch.target.mask);
s(channel.latch.length.data);
s(channel.latch.length.mask);
s(channel.latch.data);
}
for(auto& timer : this->timer) {
@@ -56,7 +61,7 @@ auto CPU::serialize(serializer& s) -> void {
s(serial.mode);
s(serial.irqEnable);
for(auto& value : serial.data) s(value);
s(serial.data8);
s(serial.dataMulti);
s(keypad.enable);
s(keypad.condition);
@@ -107,18 +112,14 @@ auto CPU::serialize(serializer& s) -> void {
s(prefetch.addr);
s(prefetch.load);
s(prefetch.wait);
s(prefetch.cycle);
s(prefetch.stopped);
s(prefetch.ahead);
s(context.clock);
s(context.halted);
s(context.stopped);
s(context.booted);
s(context.romAccess);
s(context.dmaRomAccess);
s(context.dmaRan);
s(context.dmaActive);
s(context.dmaActiveChannel);
s(context.timerLatched);
s(context.busLocked);
s(context.hcounter);
+7 -7
View File
@@ -59,12 +59,12 @@ auto CPU::runFIFO(u32 n) -> void {
apu.fifo[n].read();
if(apu.fifo[n].size() > 3) return;
auto& dma = this->dma[1 + n];
if(dma.enable && dma.timingMode == 3) {
dma.active = true;
dma.waiting = 2;
dma.targetMode = 2;
dma.size = 1;
dma.latch.length.data = 4;
auto& channel = this->dmac.channel[1 + n];
if(channel.enable && channel.timingMode == 3) {
channel.active = true;
channel.waiting = 2;
channel.targetMode = 2;
channel.size = 1;
channel.latch.length.data = 4;
}
}
+2 -2
View File
@@ -58,8 +58,8 @@ auto Display::main() -> void {
step(3);
if(io.vcounter == 162) {
if(videoCapture) cpu.dma[3].enable = 0;
videoCapture = !videoCapture && cpu.dma[3].timingMode == 3 && cpu.dma[3].enable;
if(videoCapture) cpu.dmac.channel[3].enable = 0;
videoCapture = !videoCapture && cpu.dmac.channel[3].timingMode == 3 && cpu.dmac.channel[3].enable;
}
if(io.vcounter >= 2 && io.vcounter < 162 && videoCapture) cpu.dmaHDMA();
+1
View File
@@ -22,6 +22,7 @@ namespace ares::GameBoyAdvance {
Signed = 64, //sign extended
Nonsequential = 128, //N cycle
Sequential = 256, //S cycle
DMA = 512, //DMA transaction
};
struct Model {
+6 -5
View File
@@ -24,6 +24,12 @@ auto PPU::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("PPU");
screen = node->append<Node::Video::Screen>("Screen", 240, 160);
colorEmulation = screen->append<Node::Setting::Boolean>("Color Emulation", true, [&](auto value) {
screen->resetPalette();
});
colorEmulation->setDynamic(true);
screen->colors(1 << 15, {&PPU::color, this});
screen->setSize(240, 160);
screen->setScale(1.0, 1.0);
@@ -31,11 +37,6 @@ auto PPU::load(Node::Object parent) -> void {
screen->setViewport(0, 0, 240, 160);
screen->refreshRateHint(system.frequency() / 4, 308, 228);
colorEmulation = screen->append<Node::Setting::Boolean>("Color Emulation", true, [&](auto value) {
screen->resetPalette();
});
colorEmulation->setDynamic(true);
interframeBlending = screen->append<Node::Setting::Boolean>("Interframe Blending", true, [&](auto value) {
screen->setInterframeBlending(value);
});
+1 -1
View File
@@ -1,4 +1,4 @@
static const string SerializerVersion = "v144.3";
static const string SerializerVersion = "v145.3";
auto System::serialize(bool synchronize) -> serializer {
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
+3
View File
@@ -8,6 +8,8 @@ ares_components(
m24c
)
target_link_libraries(ares PRIVATE qon)
ares_add_sources(
CORE #
md
@@ -142,6 +144,7 @@ ares_add_sources(
mcd/cdc.cpp
mcd/cdd-dac.cpp
mcd/cdd.cpp
mcd/megald.cpp
mcd/debugger.cpp
mcd/gpu.cpp
mcd/io-external.cpp
+16 -10
View File
@@ -1,6 +1,4 @@
auto MCD::read(n1 upper, n1 lower, n24 address, n16 data) -> n16 {
address.bit(20,23) = 0; //mirroring
if(address >= 0x000000 && address <= 0x07ffff) {
return pram[address >> 1];
}
@@ -27,17 +25,22 @@ auto MCD::read(n1 upper, n1 lower, n24 address, n16 data) -> n16 {
return data;
}
if(address >= 0x0e0000 && address <= 0x0effff) {
if(MegaLD() && address >= 0xfd0000 && address <= 0xfdffff) {
if (!lower) return data;
return ld.read(address | 0x1);
}
if(address >= 0xfe0000 && address <= 0xfeffff) {
if(!lower) return data;
return bram.read(address >> 1);
}
if(address >= 0x0f0000 && address <= 0x0f7fff) {
if(address >= 0xff0000 && address <= 0xff7fff) {
if(!lower) return data;
return pcm.read(address >> 1, data);
}
if(address >= 0x0f8000 && address <= 0x0fffff) {
if(address >= 0xff8000 && address <= 0xffffff) {
return readIO(upper, lower, address, data);
}
@@ -45,8 +48,6 @@ auto MCD::read(n1 upper, n1 lower, n24 address, n16 data) -> n16 {
}
auto MCD::write(n1 upper, n1 lower, n24 address, n16 data) -> void {
address.bit(20,23) = 0; //mirroring
if(address >= 0x000000 && address <= 0x07ffff
&& address >= (n24)io.pramProtect << 9) {
if(upper) pram[address >> 1].byte(1) = data.byte(1);
@@ -97,17 +98,22 @@ auto MCD::write(n1 upper, n1 lower, n24 address, n16 data) -> void {
}
}
if(address >= 0x0e0000 && address <= 0x0effff) {
if(MegaLD() && address >= 0xfd0000 && address <= 0xfdffff) {
if (!lower) return;
return ld.write(address | 0x1, data);
}
if(address >= 0xfe0000 && address <= 0xfeffff) {
if(!lower) return;
return bram.write(address >> 1, data);
}
if(address >= 0x0f0000 && address <= 0x0f7fff) {
if(address >= 0xff0000 && address <= 0xff7fff) {
if(!lower) return;
return pcm.write(address >> 1, data);
}
if(address >= 0x0f8000 && address <= 0x0fffff) {
if(address >= 0xff8000 && address <= 0xffffff) {
return writeIO(upper, lower, address, data);
}
+368 -16
View File
@@ -43,7 +43,7 @@ auto MCD::CDD::clock() -> void {
readSubcode();
if(session.tracks[io.track].isAudio()) break;
mcd.cdc.decode(io.sector);
advance();
if (!MegaLD()) advance();
} break;
case Status::Tracking: [[fallthrough]]; // TODO: implement tracking logic
@@ -57,30 +57,239 @@ auto MCD::CDD::clock() -> void {
}
auto MCD::CDD::advance() -> void {
if(auto track = session.inTrack(io.sector + 1)) {
// Calculate the sector advance amount based on the MegaLD playback modes, if applicable.
i32 sectorAdvanceOffset = 1;
if (MegaLD()) {
switch (mcd.ld.currentPlaybackMode) {
case 0x00:
// Normal playback. Nothing to do.
sectorAdvanceOffset = 1;
io.sectorRepeatCount = 0;
break;
case 0x01:
// Frame skipping. This only affects the analog video update rate, and is handled in MCD::LD::updateCurrentVideoFrameNumber.
sectorAdvanceOffset = 1;
io.sectorRepeatCount = 0;
break;
case 0x02:
// Frame stepping
switch (mcd.ld.currentPlaybackSpeed) {
case 0x00:
//-0x0: 0 frames. This pauses playback in frame stepping mode, and performs a normal playback in frame skipping mode.
sectorAdvanceOffset = 0;
io.sectorRepeatCount = 0;
break;
case 0x01:
//-0x1: 1 frame only. The image will not update after the initial frame. Note that under frame step mode, output register
// 0x07 will report this step speed as 0x1 only until the single frame step has been performed, after which, the output
// register will now state a value of 0x0. Also note that under frame skip mode, the output register will output 0x19 for
// an input register state of 0x11, in other words, the "direction" bit is set to "reverse" when a 1-frame frame skip mode
// is activated.
if (io.sectorRepeatCount == 0) {
sectorAdvanceOffset = 1;
io.sectorRepeatCount = 1;
} else {
sectorAdvanceOffset = 0;
}
break;
case 0x02:
//-0x2: 15 FPS instead of 30 (12 seconds for 180 frames) - Display frame 2x
sectorAdvanceOffset = (++io.sectorRepeatCount >= 2 ? 1 : 0);
break;
case 0x03:
//-0x3: 7.5 FPS instead of 30 (24 seconds for 180 frames) - Display frame 4x
sectorAdvanceOffset = (++io.sectorRepeatCount >= 4 ? 1 : 0);
break;
case 0x04:
//-0x4: 3.75 FPS instead of 30 (24 seconds for 90 frames) - Display frame 8x
sectorAdvanceOffset = (++io.sectorRepeatCount >= 8 ? 1 : 0);
break;
case 0x05:
//-0x5: 1.875 FPS instead of 30 (48 seconds for 90 frames) - Display frame 16x
sectorAdvanceOffset = (++io.sectorRepeatCount >= 16 ? 1 : 0);
break;
case 0x06:
//-0x6: 1 FPS instead of 30 (45 seconds for 45 frames) - Display frame 30x
sectorAdvanceOffset = (++io.sectorRepeatCount >= 30 ? 1 : 0);
break;
case 0x07:
//-0x7: ~0.33r FPS instead of 30 (30 seconds for 10 frames) - Display frame 90x
sectorAdvanceOffset = (++io.sectorRepeatCount >= 90 ? 1 : 0);
break;
}
sectorAdvanceOffset = (mcd.ld.currentPlaybackDirection ? (i32)(-sectorAdvanceOffset) : sectorAdvanceOffset);
io.sectorRepeatCount = (sectorAdvanceOffset != 0 ? (i32)0 : io.sectorRepeatCount);
break;
case 0x03:
// Fast forward
switch (mcd.ld.currentPlaybackSpeed) {
case 0x00:
sectorAdvanceOffset = 1;
break;
case 0x01:
sectorAdvanceOffset = 2;
break;
case 0x02:
sectorAdvanceOffset = 3;
break;
case 0x03:
sectorAdvanceOffset = 8;
break;
case 0x04:
sectorAdvanceOffset = 14;
break;
case 0x05:
sectorAdvanceOffset = 20;
break;
case 0x06:
case 0x07:
//##TODO## Unimplemented
// -0x7/0x06: Search mode. Plays 0.75 seconds of footage forwards in time, with audio, then jumps either forward or back
// 4 seconds from the resulting point. Note that setting this register to 0x07 is the same as 0x06, but 0x06 is the value
// that is reported back as the current mode in either case by output register 0x07.
break;
}
sectorAdvanceOffset = (mcd.ld.currentPlaybackDirection ? (i32)(-sectorAdvanceOffset) : sectorAdvanceOffset);
io.sectorRepeatCount = 0;
break;
}
}
if(auto track = session.inTrack(io.sector + sectorAdvanceOffset)) {
io.track = track();
io.sector++;
io.sector += sectorAdvanceOffset;
io.sample = 0;
if (MegaLD()) {
mcd.ld.updateCurrentVideoFrameNumber(io.sector);
if (stopPointEnabled && (io.sector == targetStopPoint)) {
mcd.ld.handleStopPointReached(io.sector);
}
}
return;
}
io.status = Status::LeadOut;
io.track = 0xaa;
if ((io.sector + sectorAdvanceOffset) < 0) {
io.status = Status::LeadIn;
io.track = 0xa00;
} else {
io.status = Status::LeadOut;
io.track = 0xaa;
}
}
auto MCD::CDD::sample() -> void {
i16 left = 0;
i16 right = 0;
if(io.status == Status::Playing) {
if(session.tracks[io.track].isAudio()) {
mcd.fd->seek((abs(session.leadIn.lba) + io.sector) * 2448 + io.sample);
left = mcd.fd->readl(2);
right = mcd.fd->readl(2);
i16 digitalSampleLeft = 0;
i16 digitalSampleRight = 0;
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);
digitalSampleLeft = mcd.fd->readl(2);
digitalSampleRight = mcd.fd->readl(2);
}
io.sample += 4;
if(io.sample >= 2352) advance();
}
}
dac.sample(left, right);
if (MegaLD()) {
// Determine the state of our overall audio mixing mode settings
//##FIX## Note that we don't take the unusual "latching" behaviour of the digital audio mixing disabled state
//persisting when switching into analog mixing mode 0 here, as described in the notes for input register 0x01. It
//is highly, highly unlikely anything relies on this however. If there was a scenario however where only VDP
//graphics were on the screen, and digital audio was intended to play, but was instead silent, it's possible this
//is the culprit. Fixing this would require latching a mute state into a register, rather than evaluating the
//input register state live like we do here.
auto analogMixingMode = mcd.ld.inputRegs[0x01].bit(7, 6);
auto audioMixingInputSelection = mcd.ld.inputRegs[0x0D].bit(7, 6);
bool digitalAudioMixingDisabled = (analogMixingMode > 0) && ((audioMixingInputSelection == 2) || ((analogMixingMode == 1) && mcd.ld.inputRegs[0x0D].bit(4)));
bool digitalAudioAttenuationDisabled = (analogMixingMode == 0) || (audioMixingInputSelection == 3);
bool analogAudioMixingDisabled = (analogMixingMode == 0) || ((analogMixingMode == 1) && !mcd.ld.inputRegs[0x0D].bit(4));
// Disable digital audio if it is turned off
if (digitalAudioMixingDisabled) {
digitalSampleLeft = 0;
digitalSampleRight = 0;
}
// Attenuate the digital audio using the digital audio fader
if (!digitalAudioAttenuationDisabled) {
float digitalAudioFader = (float)mcd.ld.inputRegs[0x0F] / (float)((1 << 8) - 1);
digitalSampleLeft = (int16_t)((float)digitalSampleLeft * digitalAudioFader);
digitalSampleRight = (int16_t)((float)digitalSampleRight * digitalAudioFader);
}
// Take digital left/right exclusive register state into account
if ((analogMixingMode != 0) && mcd.ld.inputRegs[0x0D].bit(0) && mcd.ld.inputRegs[0x0D].bit(1)) {
digitalSampleLeft /= 2;
digitalSampleRight /= 2;
} else if (mcd.ld.inputRegs[0x0D].bit(0)) {
digitalSampleRight = digitalSampleLeft;
} else if (mcd.ld.inputRegs[0x0D].bit(1)) {
digitalSampleLeft = digitalSampleRight;
}
// Retrieve the next analog audio sample
i16 analogSampleLeft = 0;
i16 analogSampleRight = 0;
auto analogAudioSamplePos = (io.sector * 2352) + io.sample + (mcd.ld.analogAudioLeadingAudioSamples * 4);
if ((analogAudioSamplePos + 3) < mcd.ld.analogAudioRawDataView.size()) {
analogSampleLeft = (i16)((u16)mcd.ld.analogAudioRawDataView[analogAudioSamplePos + 0] | (u16)(mcd.ld.analogAudioRawDataView[analogAudioSamplePos + 1] << 8));
analogSampleRight = (i16)((u16)mcd.ld.analogAudioRawDataView[analogAudioSamplePos + 2] | (u16)(mcd.ld.analogAudioRawDataView[analogAudioSamplePos + 3] << 8));
}
// Disable analog audio if it is turned off
if (analogAudioMixingDisabled) {
analogSampleLeft = 0;
analogSampleRight = 0;
}
// Take analog mute and left/right exclusive register state into account
if (mcd.ld.inputRegs[0x0E].bit(7) || (mcd.ld.inputRegs[0x0E].bit(0) && mcd.ld.inputRegs[0x0E].bit(1))) {
analogSampleLeft = 0;
analogSampleRight = 0;
} else if (mcd.ld.inputRegs[0x0E].bit(0)) {
analogSampleRight = analogSampleLeft;
} else if (mcd.ld.inputRegs[0x0E].bit(1)) {
analogSampleLeft = analogSampleRight;
}
// Attenuate analog audio by the current attenuation register settings. Note that as per the hardware, the
// maximum attenuation value of 0xFF doesn't give total silence.
//##FIX## We currently make the "fade to mute" flags apply immediately. This is techically incorrect, however
//since most likely nothing relies on this, and it's a bit of a pain to do, we just make them take effect
//immediately here. If we want to do this properly, we need to take accurate measurements on the hardware for
//the time taken for the fade, then tie a process into the clock event to reduce a secondary attenuation value
//by the correct amount to achieve that timing as the clock advances. The fact that triggering a fade for a
//second channel applies immediately if the other channel has already been faded to mute, shows there's in
//fact a single secondary attenuation counter/register behind the scenes which handles this, which both
//channels share.
unsigned int analogAudioVolumeScaleLeft = 0x100 - (unsigned int)mcd.ld.analogAudioAttenuationLeft;
unsigned int analogAudioVolumeScaleRight = 0x100 - (unsigned int)mcd.ld.analogAudioAttenuationRight;
analogAudioVolumeScaleLeft = (mcd.ld.analogAudioFadeToMutedLeft ? 0 : analogAudioVolumeScaleLeft);
analogAudioVolumeScaleRight = (mcd.ld.analogAudioFadeToMutedRight ? 0 : analogAudioVolumeScaleRight);
analogSampleLeft = (i16)(((int)analogSampleLeft * (int)analogAudioVolumeScaleLeft) >> 8);
analogSampleRight = (i16)(((int)analogSampleRight * (int)analogAudioVolumeScaleRight) >> 8);
// Audio is disabled in frame stepping mode
if (mcd.ld.currentPlaybackMode == 0x02) {
digitalSampleLeft = 0;
digitalSampleRight = 0;
analogSampleLeft = 0;
analogSampleRight = 0;
}
// Mix analog and digital audio together
i16 combinedSampleLeft = (int16_t)std::clamp((int)digitalSampleLeft + (int)analogSampleLeft, (int)std::numeric_limits<int16_t>::min(), (int)std::numeric_limits<int16_t>::max());
i16 combinedSampleRight = (int16_t)std::clamp((int)digitalSampleRight + (int)analogSampleRight, (int)std::numeric_limits<int16_t>::min(), (int)std::numeric_limits<int16_t>::max());
// Output the combined sample
dac.sample(combinedSampleLeft, combinedSampleRight);
} else {
dac.sample(digitalSampleLeft, digitalSampleRight);
}
}
//convert sector# to normalized sector position on the CD-ROM surface for seek latency calculation
@@ -143,7 +352,7 @@ auto MCD::CDD::process() -> void {
} break;
case Command::Stop: {
io.status = mcd.fd ? Status::Stopped : Status::NoDisc;
stop();
status[1] = 0x0;
status[2] = 0x0; status[3] = 0x0;
status[4] = 0x0; status[5] = 0x0;
@@ -266,11 +475,11 @@ auto MCD::CDD::process() -> void {
} break;
case Command::Pause: {
io.status = Status::Paused;
pause();
} break;
case Command::Play: {
io.status = Status::Playing;
play();
} break;
// TrackSkip command directs movement of the laser/sled by a specified radial track count.
@@ -351,6 +560,11 @@ auto MCD::CDD::insert() -> void {
io.sector = session.leadIn.lba;
io.sample = 0;
io.track = 0;
laserdiscLoaded = false;
if ((mcd.pak->attribute("system") == "MegaLD") && (mcd.ld.mmi.media().size() > 0)) {
laserdiscLoaded = mcd.ld.mmi.media()[0].type.match("LD");
}
}
auto MCD::CDD::eject() -> void {
@@ -360,6 +574,142 @@ auto MCD::CDD::eject() -> void {
io.sector = 0;
io.sample = 0;
io.track = 0;
// If this is a MegaLD system, release the analog audio stream when changing discs.
if (MegaLD()) {
mcd.ld.analogAudioRawDataView = {};
}
}
auto MCD::CDD::stop() -> void {
io.status = mcd.fd ? Status::Stopped : Status::NoDisc;
}
auto MCD::CDD::play() -> void {
if (io.status == Status::Seeking) {
io.seeking = Status::Playing;
} else {
io.status = Status::Playing;
}
}
auto MCD::CDD::pause() -> void {
if (io.status == Status::Seeking) {
io.seeking = Status::Paused;
} else {
io.status = Status::Paused;
}
}
auto MCD::CDD::seekToTime(u8 hour, u8 minute, u8 second, u8 frame, bool startPaused) -> void {
s32 lba = lbaFromTime(hour, minute, second, frame);
seekToSector(lba, startPaused);
}
auto MCD::CDD::seekToRelativeTime(n7 track, u8 minute, u8 second, u8 frame, bool startPaused) -> void {
auto firstTrack = session.firstTrack;
auto lastTrack = session.lastTrack;
if ((track < firstTrack) || (track > lastTrack)) {
return;
}
auto targetTrackLba = session.tracks[track].indices[1].lba;
s32 lba = targetTrackLba + ((s32)minute * 60 * 75 + (s32)second * 75 + (s32)frame);
seekToSector(lba, startPaused);
}
// Note that despite the generic name, this implementation is currently MegaLD specific.
auto MCD::CDD::seekToSector(s32 lba, bool startPaused) -> void {
counter = 0;
io.status = Status::Seeking;
io.seeking = (startPaused ? Status::Paused : Status::Playing);
//##TODO## Calculate correct latency for the LaserActive
io.latency = 20.0 + 10.0 * abs(position(io.sector) - position(lba));
io.sector = lba;
io.sample = 0;
if (auto track = session.inTrack(io.sector)) io.track = track();
}
auto MCD::CDD::seekToTrack(n7 track, bool startPaused) -> void {
auto lba = session.tracks[track].indices[1].lba;
seekToSector(lba, startPaused);
}
auto MCD::CDD::getTrackCount() -> n7 {
return (session.lastTrack - session.firstTrack) + 1;
}
auto MCD::CDD::getFirstTrack() -> n7 {
return session.firstTrack;
}
auto MCD::CDD::getLastTrack() -> n7 {
return session.lastTrack;
}
auto MCD::CDD::getCurrentTrack() -> n7 {
return io.track;
}
auto MCD::CDD::getCurrentSector() -> s32 {
return io.sector;
}
auto MCD::CDD::getCurrentTimecode(u8& minute, u8& second, u8& frame) -> void {
auto [lminute, lsecond, lframe] = CD::MSF(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);
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);
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);
minute = lminute;
second = lsecond;
frame = lframe;
flags = session.tracks[track].control;
}
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;
}
auto MCD::CDD::isTrackAudio(n7 track) -> bool {
return session.tracks[track].isAudio();
}
auto MCD::CDD::isDiscLoaded() -> bool {
return (mcd.disc && mcd.fd);
}
auto MCD::CDD::isDiscLaserdisc() -> bool {
if (!isDiscLoaded()) {
return false;
}
return laserdiscLoaded;
}
auto MCD::CDD::isLaserdiscClv() -> bool {
return isDiscLaserdisc() && mcd.ld.video.isCLV;
}
auto MCD::CDD::isLaserdiscDigitalAudioPresent() -> bool {
return mcd.ld.video.hasDigitalAudio;
}
auto MCD::CDD::power(bool reset) -> void {
@@ -373,4 +723,6 @@ auto MCD::CDD::power(bool reset) -> void {
for(auto& data : command) data = 0x0;
insert();
checksum();
stopPointEnabled = false;
targetStopPoint = 0;
}
+22 -4
View File
@@ -1,4 +1,7 @@
#include <md/md.hpp>
#include <string.h>
#include <string>
#include <optional>
namespace ares::MegaDrive {
@@ -14,6 +17,7 @@ MCD mcd;
#include "cdc-transfer.cpp"
#include "cdd.cpp"
#include "cdd-dac.cpp"
#include "megald.cpp"
#include "timer.cpp"
#include "gpu.cpp"
#include "pcm.cpp"
@@ -27,7 +31,7 @@ auto MCD::load(Node::Object parent) -> void {
tray->setFamily("Mega CD");
tray->setType("Compact Disc");
tray->setHotSwappable(true);
tray->setAllocate([&](auto name) { return allocate(tray); });
tray->setAllocate([&](auto name) { return allocate(tray, name); });
tray->setConnect([&] { return connect(); });
tray->setDisconnect([&] { return disconnect(); });
@@ -53,6 +57,7 @@ auto MCD::unload() -> void {
debugger = {};
cdd.unload(node);
pcm.unload(node);
ld.unload();
bios.reset();
pram.reset();
@@ -64,8 +69,10 @@ auto MCD::unload() -> void {
node.reset();
}
auto MCD::allocate(Node::Port parent) -> Node::Peripheral {
return disc = parent->append<Node::Peripheral>("Mega CD Disc");
auto MCD::allocate(Node::Port parent, string name) -> Node::Peripheral {
disc = parent->append<Node::Peripheral>("Mega CD Disc");
disc->setAttribute("media", name);
return disc;
}
auto MCD::connect() -> void {
@@ -78,7 +85,12 @@ auto MCD::connect() -> void {
information = {};
information.title = pak->attribute("title");
fd = pak->read("cd.rom");
if(MegaLD()) {
ld.load(pak->attribute("location"));
} else {
fd = pak->read("cd.rom");
}
if(!fd) return disconnect();
cdd.insert();
@@ -93,6 +105,9 @@ auto MCD::disconnect() -> void {
fd.reset();
pak.reset();
information = {};
if(MegaLD()) {
ld.notifyDiscEjected();
}
}
auto MCD::save() -> void {
@@ -192,6 +207,9 @@ auto MCD::power(bool reset) -> void {
gpu.power(reset);
pcm.power(reset);
resetPeripheral(reset);
if (MegaLD()) {
ld.power(reset);
}
}
auto MCD::resetCpu() -> void {
+144 -2
View File
@@ -33,10 +33,10 @@ struct MCD : M68000, Thread {
auto title() const -> string { return information.title; }
//mcd.cpp
auto load(Node::Object) -> void;
auto load(Node::Object parent) -> void;
auto unload() -> void;
auto allocate(Node::Port) -> Node::Peripheral;
auto allocate(Node::Port, string name) -> Node::Peripheral;
auto connect() -> void;
auto disconnect() -> void;
@@ -312,6 +312,28 @@ struct MCD : M68000, Thread {
auto checksum() -> void;
auto insert() -> void;
auto eject() -> void;
auto stop() -> void;
auto play() -> void;
auto pause() -> void;
auto seekToTime(u8 hour, u8 minute, u8 second, u8 frame, bool startPaused) -> void;
auto seekToRelativeTime(n7 track, u8 minute, u8 second, u8 frame, bool startPaused) -> void;
auto seekToSector(s32 lba, bool startPaused) -> void;
auto seekToTrack(n7 track, bool startPaused) -> void;
auto getTrackCount() -> n7;
auto getFirstTrack() -> n7;
auto getLastTrack() -> n7;
auto getCurrentTrack() -> n7;
auto getCurrentSector() -> s32;
auto getCurrentTimecode(u8& minute, u8& second, u8& frame) -> void;
auto getCurrentTrackRelativeTimecode(u8& minute, u8& second, u8& frame) -> void;
auto getLeadOutTimecode(u8& minute, u8& second, u8& frame) -> void;
auto getTrackTocData(n7 track, u8& flags, u8& minute, u8& second, u8& frame) -> void;
auto lbaFromTime(u8 hour, u8 minute, u8 second, u8 frame) -> s32;
auto isTrackAudio(n7 track) -> bool;
auto isDiscLoaded() -> bool;
auto isDiscLaserdisc() -> bool;
auto isLaserdiscClv() -> bool;
auto isLaserdiscDigitalAudioPresent() -> bool;
auto power(bool reset) -> void;
//serialization.cpp
@@ -343,6 +365,7 @@ struct MCD : M68000, Thread {
n4 seeking; //status after seeking (Playing or Paused)
n16 latency;
i32 sector; //current frame#
i32 sectorRepeatCount;
n16 sample; //current audio sample# within current frame
n7 track; //current track#
n1 tocRead;
@@ -354,8 +377,127 @@ struct MCD : M68000, Thread {
n4 status [10];
n4 command[10];
n16 subcode[64];
n1 stopPointEnabled;
s32 targetStopPoint;
bool laserdiscLoaded;
} cdd;
struct LD {
// megald.cpp
auto load(string sourceFile) -> void;
auto unload() -> void;
auto notifyDiscEjected() -> void;
auto read(n24 address) -> n8;
auto write(n24 address, n8 data) -> void;
auto getOutputRegisterValue(int regNum) -> n8;
auto processInputRegisterWrite(int regNum, n8 data, n8 previousData, bool wasDeferredRegisterWrite) -> void;
auto resetSeekTargetToDefault() -> void;
auto liveSeekRegistersContainsLatchableTarget() const -> bool;
auto latchSeekTargetFromCurrentState() -> bool;
auto performSeekWithLatchedState() -> void;
auto updateStopPointWithCurrentState() -> void;
auto zeroBasedFrameIndexFromLba(s32 lba, bool processLeadIn = false) -> s32;
auto lbaFromZeroBasedFrameIndex(s32 frameIndex) -> s32;
auto VideoTimeToRedbookTime(u8& hours, u8& minutes, u8& seconds, u8& frames) -> void;
auto handleStopPointReached(s32 lba) -> void;
auto updateCurrentVideoFrameNumber(s32 lba) -> void;
auto loadCurrentVideoFrameIntoBuffer(bool blankFrame) -> void;
auto power(bool reset) -> void;
auto scanline(u32 vdpPixelBuffer[1495], u32 vcounter) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
enum class SeekPointReg {
Chapter,
HoursOrFrameH,
MinutesOrFrameM,
SecondsOrFrameL,
Frames,
};
enum class SeekMode {
SeekToRedbookTime,
SeekToRedbookRelativeTime,
SeekToVideoFrame,
SeekToVideoTime,
};
struct AnalogVideoFrameIndex {
bool isCLV;
bool hasDigitalAudio;
size_t leadInFrameCount;
size_t activeVideoFrameCount;
size_t leadOutFrameCount;
std::vector<const unsigned char*> leadInFrames;
std::vector<const unsigned char*> activeVideoFrames;
std::vector<const unsigned char*> leadOutFrames;
s32 frameSkipBaseFrame;
s32 frameSkipCounter;
s32 currentVideoFrameIndex;
n1 currentVideoFrameLeadIn;
n1 currentVideoFrameLeadOut;
n1 currentVideoFrameFieldSelectionEnabled[2];
n1 currentVideoFrameFieldSelectionEvenField[2];
n1 currentVideoFrameOnEvenField;
qon_desc videoFileHeader;
qoi2_desc videoFrameHeader;
int drawIndex;
std::vector<unsigned char> videoFrameBuffers[2];
std::vector<unsigned char> dummyBlankLineBuffer;
static const size_t FrameBufferWidth = 1495;
static const size_t FrameBufferHeight = 525;
std::vector<u32> outputFramebuffer;
} video;
vector<u8> analogAudioDataBuffer;
array_view<u8> analogAudioRawDataView;
n32 analogAudioLeadingAudioSamples;
Decode::MMI mmi;
static constexpr double videoFramesPerSecond = 30.0 / 1.001; // Roughly 29.97
static const size_t inputRegisterCount = 0x20;
static const size_t outputRegisterCount = 0x20;
n8 inputRegs[inputRegisterCount];
n8 inputFrozenRegs[inputRegisterCount];
n8 outputRegs[outputRegisterCount];
n8 outputFrozenRegs[outputRegisterCount];
n1 areInputRegsFrozen;
n1 areOutputRegsFrozen;
n1 operationErrorFlag1;
n1 operationErrorFlag2;
n1 operationErrorFlag3;
n1 seekEnabled;
n3 currentSeekMode;
n1 currentSeekModeTimeFormat;
n1 currentSeekModeRepeat;
n8 analogAudioAttenuationLeft;
n8 analogAudioAttenuationRight;
n1 analogAudioFadeToMutedLeft;
n1 analogAudioFadeToMutedRight;
// Currently latched seek point
u8 activeSeekMode;
n8 seekPointRegs[5];
// Currently latched stop point
n8 stopPointRegs[5];
n1 reachedStopPoint;
n1 reachedStopPointPreviously;
u4 currentPlaybackMode;
u3 currentPlaybackSpeed;
n8 skippedFrameCount;
u1 currentPlaybackDirection;
n8 targetDriveState;
n8 currentDriveState;
n1 targetPauseState;
n1 currentPauseState;
n1 seekPerformedSinceLastFrameUpdate;
n8 driveStateChangeDelayCounter;
n8 selectedTrackInfo;
} ld;
struct Timer {
//timer.cpp
auto clock() -> void;
File diff suppressed because it is too large. Load diff
+53
View File
@@ -38,6 +38,7 @@ auto MCD::serialize(serializer& s) -> void {
s(cdc);
s(cdd);
s(ld);
s(timer);
s(gpu);
s(pcm);
@@ -145,6 +146,7 @@ auto MCD::CDD::serialize(serializer& s) -> void {
s(io.seeking);
s(io.latency);
s(io.sector);
s(io.sectorRepeatCount);
s(io.sample);
s(io.track);
s(io.tocRead);
@@ -155,6 +157,57 @@ auto MCD::CDD::serialize(serializer& s) -> void {
s(status);
s(command);
s(subcode);
s(stopPointEnabled);
s(targetStopPoint);
}
auto MCD::LD::serialize(serializer& s) -> void {
s(inputRegs);
s(inputFrozenRegs);
s(outputRegs);
s(outputFrozenRegs);
s(areInputRegsFrozen);
s(areOutputRegsFrozen);
s(operationErrorFlag1);
s(operationErrorFlag2);
s(operationErrorFlag3);
s(seekEnabled);
s(currentSeekMode);
s(currentSeekModeTimeFormat);
s(currentSeekModeRepeat);
s(analogAudioAttenuationLeft);
s(analogAudioAttenuationRight);
s(analogAudioFadeToMutedLeft);
s(analogAudioFadeToMutedRight);
s(stopPointRegs);
s(reachedStopPoint);
s(reachedStopPointPreviously);
s(activeSeekMode);
s(currentPlaybackMode);
s(currentPlaybackSpeed);
s(currentPlaybackDirection);
s(seekPointRegs);
s(targetDriveState);
s(currentDriveState);
s(targetPauseState);
s(currentPauseState);
s(seekPerformedSinceLastFrameUpdate);
s(driveStateChangeDelayCounter);
s(selectedTrackInfo);
s(video.frameSkipBaseFrame);
s(video.frameSkipCounter);
s(video.currentVideoFrameIndex);
s(video.currentVideoFrameLeadIn);
s(video.currentVideoFrameLeadOut);
s(video.currentVideoFrameFieldSelectionEnabled);
s(video.currentVideoFrameFieldSelectionEvenField);
s(video.currentVideoFrameOnEvenField);
// Restore the current video frame into the display buffer
if (MegaLD() && s.reading()) {
loadCurrentVideoFrameIntoBuffer(inputRegs[0x0C].bit(2));
}
}
auto MCD::Timer::serialize(serializer& s) -> void {
+7
View File
@@ -2,6 +2,12 @@
//started: 2016-07-08
#include <ares/ares.hpp>
#include <nall/decode/mmi.hpp>
#include <vector>
#include <cmath>
#include <qon/qon.h>
#include <qon/qoi2.h>
#include <component/processor/m68000/m68000.hpp>
#include <component/processor/z80/z80.hpp>
@@ -31,6 +37,7 @@ namespace ares::MegaDrive {
inline static auto Mega32X() -> bool;
inline static auto MegaCD() -> bool;
inline static auto MegaLD() -> bool;
#include <md/controller/controller.hpp>
#include <md/bus/bus.hpp>
+1 -1
View File
@@ -1,4 +1,4 @@
static const string SerializerVersion = "v144";
static const string SerializerVersion = "v145";
auto System::serialize(bool synchronize) -> serializer {
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
+15 -1
View File
@@ -20,6 +20,9 @@ auto enumerate() -> vector<string> {
"[Sega] Mega CD 32X (NTSC-J)",
"[Sega] Mega CD 32X (NTSC-U)",
"[Sega] Mega CD 32X (PAL)",
//Mega LD
"[Sega] Mega LD (NTSC-J)",
"[Sega] Mega LD (NTSC-U)",
};
}
@@ -74,25 +77,36 @@ auto System::load(Node::System& root, string name) -> bool {
information.name = "Mega Drive";
information.mega32X = 0;
information.megaCD = 0;
information.megaLD = 0;
cpu.minCyclesBetweenSyncs = 0; // sync every cycle
}
if(name.match("[Sega] Mega 32X (*)")) {
information.name = "Mega Drive";
information.mega32X = 1;
information.megaCD = 0;
information.megaLD = 0;
cpu.minCyclesBetweenSyncs = 14; // sync approx every 24-25 pixels
}
if(name.match("[Sega] Mega CD (*)")) {
information.name = "Mega Drive";
information.mega32X = 0;
information.megaCD = 1;
information.megaLD = 0;
cpu.minCyclesBetweenSyncs = 0; // sync every cycle
}
if(name.match("[Sega] Mega CD 32X (*)")) {
information.name = "Mega Drive";
information.mega32X = 1;
information.megaCD = 1;
cpu.minCyclesBetweenSyncs = 40; // sync approx every 70 pixels
information.megaLD = 0;
cpu.minCyclesBetweenSyncs = 10; // sync approx every 1 pixel
}
if(name.match("[Sega] Mega LD (*)")) {
information.name = "Mega Drive";
information.mega32X = 0;
information.megaCD = 1;
information.megaLD = 1;
cpu.minCyclesBetweenSyncs = 4; // sync approx every 7 pixels
}
if(name.find("NTSC-J")) {
information.region = Region::NTSCJ;
+3
View File
@@ -20,6 +20,7 @@ struct System {
auto region() const -> Region { return information.region; }
auto mega32X() const -> bool { return information.mega32X; }
auto megaCD() const -> bool { return information.megaCD; }
auto megaLD() const -> bool { return information.megaLD; }
auto frequency() const -> double { return information.frequency; }
auto game() -> string;
@@ -40,6 +41,7 @@ private:
Region region = Region::NTSCJ;
bool mega32X = false;
bool megaCD = false;
bool megaLD = false;
double frequency = Constants::Colorburst::NTSC * 15.0;
} information;
@@ -56,3 +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(); }
+4
View File
@@ -185,8 +185,10 @@ auto VDP::mainH32() -> void {
if(dac.pixels) {
blocks<false, true>();
if(Mega32X()) m32x.vdp.scanline(pixels + 18, vcounter()); //approx 3 and 1/4 pixel offset
if(MegaLD()) mcd.ld.scanline(dac.pixels, vcounter());
} else {
blocks<false, false>();
if(MegaLD()) mcd.ld.scanline(dac.pixels, vcounter());
}
tick<false>(); slot();
@@ -248,8 +250,10 @@ auto VDP::mainH40() -> void {
if(dac.pixels) {
blocks<true, true>();
if(Mega32X()) m32x.vdp.scanline(pixels, vcounter());
if(MegaLD()) mcd.ld.scanline(dac.pixels, vcounter());
} else {
blocks<true, false>();
if(MegaLD()) mcd.ld.scanline(dac.pixels, vcounter());
}
tick<true>(); slot();
+15 -11
View File
@@ -34,9 +34,9 @@ VDP vdp;
auto VDP::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("VDP");
screen = node->append<Node::Video::Screen>("Screen", 1415, visibleHeight() * 2);
screen = node->append<Node::Video::Screen>("Screen", 1495, visibleHeight() * 2);
screen->colors(1 << 16, {&VDP::color, this});
screen->setSize(1415, visibleHeight() * 2);
screen->setSize(1495, visibleHeight() * 2);
screen->setScale(0.25, 0.5);
Region::PAL() ? screen->setAspect(41.0, 37.0) : screen->setAspect(32.0, 35.0);
screen->refreshRateHint(system.frequency(), 3420, frameHeight());
@@ -80,18 +80,21 @@ auto VDP::pixels() -> u32* {
y = y % visibleHeight();
auto yScale = latch.interlace ? 2 : 1;
output = screen->pixels().data() + y * yScale * 1415;
if(latch.interlace) output += field() * 1415;
output = screen->pixels().data() + y * yScale * 1495;
if(latch.interlace) output += field() * 1495;
size_t megaLDExtraOverscanBorder = 40;
if(h40()) {
// H40 mode has slightly shorter lines, so sides are blanked.
for(auto n: range(13)) output[ n] = 0b101 << 9;
for(auto n: range(14)) output[1415-14+n] = 0b101 << 9;
for(auto n: range(13+megaLDExtraOverscanBorder)) output[ n] = 0b101 << 9;
for(auto n: range(14+megaLDExtraOverscanBorder)) output[1495-14-megaLDExtraOverscanBorder+n] = 0b101 << 9;
return output+13;
return output+13+megaLDExtraOverscanBorder;
}
for(auto n: range(megaLDExtraOverscanBorder)) output[ n] = 0b101 << 9;
for(auto n: range(megaLDExtraOverscanBorder)) output[1495-megaLDExtraOverscanBorder+n] = 0b101 << 9;
return output;
return output+megaLDExtraOverscanBorder;
}
auto VDP::frame() -> void {
@@ -100,11 +103,12 @@ auto VDP::frame() -> void {
auto yScale = latch.interlace ? 2 : 1;
screen->setScale(0.25, 1.0 / yScale);
size_t megaLDExtraOverscanBorder = 40;
if(screen->overscan()) {
screen->setSize(1415, visibleHeight() * yScale);
screen->setViewport(0, 0, screen->width(), screen->height());
screen->setSize(1495 - (!MegaLD() ? megaLDExtraOverscanBorder * 2 : 0), visibleHeight() * yScale);
screen->setViewport((!MegaLD() ? megaLDExtraOverscanBorder : 0), 0, screen->width(), screen->height());
} else {
int x = 13 * 5;
int x = 13 * 5 + (!MegaLD() ? megaLDExtraOverscanBorder : 0);
int y = Region::PAL() ? 30 + 8 * v28() : 11;
int width = 1280;
int height = screenHeight();
+1 -1
View File
@@ -52,7 +52,7 @@ struct VDP : Thread {
auto screenWidth() const -> u32 { return latch.displayWidth ? 320 : 256; }
auto screenHeight() const -> u32 { return io.overscan ? 240 : 224; }
auto frameHeight() const -> u32 { return Region::PAL() ? 313 : 262; }
auto visibleHeight() const -> u32 { return Region::PAL() ? 294 : 243; }
auto visibleHeight() const -> u32 { return MegaLD() ? frameHeight() : (Region::PAL() ? 294 : 243); }
//vdp.cpp
auto load(Node::Object) -> void;
-2
View File
@@ -119,7 +119,6 @@ auto VDP::registerWrite(n4 address, n8 data) -> void {
background.io.vscrollLock = data.bit(7);
irq.line.pending &= irq.line.enable;
irq.poll();
updateScreenSize();
return;
case 0x1: //mode control 2
@@ -131,7 +130,6 @@ auto VDP::registerWrite(n4 address, n8 data) -> void {
io.displayEnable = data.bit(6);
irq.frame.pending &= irq.frame.enable;
irq.poll();
updateScreenSize();
return;
case 0x2: //name table base address
+5 -14
View File
@@ -27,15 +27,15 @@ auto VDP::load(Node::Object parent) -> void {
screen->refreshRateHint(system.colorburst() * 15, 3420, Region::PAL() ? 313 : 262);
if(Display::CRT()) {
screen->colors(1 << 6, {&VDP::colorMasterSystem, this});
screen->setSize(284, screenHeight());
screen->setScale(1.0, 1.0);
Region::PAL() ? screen->setAspect(19.0, 14.0) : screen->setAspect(8.0, 7.0);
colorEmulation = screen->append<Node::Setting::Boolean>("Color Emulation", true, [&](auto value) {
screen->resetPalette();
});
colorEmulation->setDynamic(true);
screen->colors(1 << 6, {&VDP::colorMasterSystem, this});
screen->setSize(284, screenHeight());
screen->setScale(1.0, 1.0);
Region::PAL() ? screen->setAspect(19.0, 14.0) : screen->setAspect(8.0, 7.0);
}
if(Display::LCD()) {
@@ -149,15 +149,6 @@ auto VDP::step(u32 clocks) -> void {
}
}
auto VDP::updateScreenSize() -> void {
if(Display::CRT()) {
screen->setSize(284, screenHeight());
}
//if(Display::LCD()) {
// screen->setSize(160, 144);
//}
}
auto VDP::vlines() -> u32 {
if(revision->value() == 1) return 192;
-1
View File
@@ -38,7 +38,6 @@ struct VDP : Thread {
auto main() -> void;
auto step(u32 clocks) -> void;
auto updateScreenSize() -> void;
auto screenHeight() const -> u32 { return Region::PAL() ? 288 : 243; }
auto vlines() -> u32;
+31 -53
View File
@@ -14,6 +14,8 @@ Gamepad::Gamepad(Node::Port parent) {
bank = 0;
axis = node->append<Node::Input::Axis>("Axis");
x = node->append<Node::Input::Axis> ("X-Axis");
y = node->append<Node::Input::Axis> ("Y-Axis");
up = node->append<Node::Input::Button>("Up");
@@ -313,57 +315,37 @@ auto Gamepad::read() -> n32 {
auto cardinalMax = 85.0;
auto diagonalMax = 69.0;
auto innerDeadzone = 7.0; // default should remain 7 (~8.2% of 85) as the deadzone is axial in nature and fights cardinalMax
auto outerDeadzoneRadiusMax = 2.0 / sqrt(2.0) * (diagonalMax / cardinalMax * (cardinalMax - innerDeadzone) + innerDeadzone); //from linear scaling equation, substitute outerDeadzoneRadiusMax*sqrt(2)/2 for lengthAbsoluteX and set diagonalMax as the result then solve for outerDeadzoneRadiusMax
auto saturationRadius = (innerDeadzone + diagonalMax + sqrt(pow(innerDeadzone + diagonalMax, 2.0) - 2.0 * sqrt(2.0) * diagonalMax * innerDeadzone)) / sqrt(2.0); //from linear response curve function within axis->processDeadzoneAndResponseCurve, substitute saturationRadius * sqrt(2) / 2 for right-hand lengthAbsolute and set diagonalMax as the result then solve for saturationRadius
auto offset = 0.0;
//scale {-32768 ... +32767} to {-outerDeadzoneRadiusMax ... +outerDeadzoneRadiusMax}
auto ax = x->value() * outerDeadzoneRadiusMax / 32767.0;
auto ay = y->value() * outerDeadzoneRadiusMax / 32767.0;
//create inner axial dead-zone in range {-innerDeadzone ... +innerDeadzone} and scale from it up to outer circular dead-zone of radius outerDeadzoneRadiusMax
auto length = sqrt(ax * ax + ay * ay);
if(length <= outerDeadzoneRadiusMax) {
auto lengthAbsoluteX = abs(ax);
auto lengthAbsoluteY = abs(ay);
if(lengthAbsoluteX <= innerDeadzone) {
lengthAbsoluteX = 0.0;
} else {
lengthAbsoluteX = (lengthAbsoluteX - innerDeadzone) * cardinalMax / (cardinalMax - innerDeadzone) / lengthAbsoluteX;
}
ax *= lengthAbsoluteX;
if(lengthAbsoluteY <= innerDeadzone) {
lengthAbsoluteY = 0.0;
} else {
lengthAbsoluteY = (lengthAbsoluteY - innerDeadzone) * cardinalMax / (cardinalMax - innerDeadzone) / lengthAbsoluteY;
}
ay *= lengthAbsoluteY;
} else {
length = outerDeadzoneRadiusMax / length;
ax *= length;
ay *= length;
}
//bound diagonals to an octagonal range {-diagonalMax ... +diagonalMax}
if(ax != 0.0 && ay != 0.0) {
auto slope = ay / ax;
auto edgex = copysign(cardinalMax / (abs(slope) + (cardinalMax - diagonalMax) / diagonalMax), ax);
auto edgey = copysign(min(abs(edgex * slope), cardinalMax / (1.0 / abs(slope) + (cardinalMax - diagonalMax) / diagonalMax)), ay);
edgex = edgey / slope;
//scale {-32767 ... +32767} to {-saturationRadius + offset ... +saturationRadius + offset}
auto ax = axis->setOperatingRange(x->value(), saturationRadius, offset);
auto ay = axis->setOperatingRange(y->value(), saturationRadius, offset);
length = sqrt(ax * ax + ay * ay);
auto distanceToEdge = sqrt(edgex * edgex + edgey * edgey);
if(length > distanceToEdge) {
ax = edgex;
ay = edgey;
}
//create inner axial dead-zone in range {-innerDeadzone ... +innerDeadzone} and scale from it up to saturationRadius
ax = axis->processDeadzoneAndResponseCurve(ax, innerDeadzone, saturationRadius, offset);
ay = axis->processDeadzoneAndResponseCurve(ay, innerDeadzone, saturationRadius, offset);
auto scaledLength = hypot(ax - offset, ay - offset);
if(scaledLength > saturationRadius) {
ax = axis->revisePosition(ax, scaledLength, saturationRadius, offset);
ay = axis->revisePosition(ay, scaledLength, saturationRadius, offset);
}
//let cardinalMax and diagonalMax define boundaries and restrict to an octagonal gate
double axBounded = 0.0;
double ayBounded = 0.0;
axis->applyGateBoundaries(innerDeadzone, cardinalMax, diagonalMax, ax, ay, offset, axBounded, ayBounded);
ax = axBounded;
ay = ayBounded;
//keep cardinal input within positive and negative bounds of cardinalMax
if(abs(ax) > cardinalMax) ax = copysign(cardinalMax, ax);
if(abs(ay) > cardinalMax) ay = copysign(cardinalMax, ay);
ax = axis->clampAxisToNearestBoundary(ax, offset, cardinalMax);
ay = axis->clampAxisToNearestBoundary(ay, offset, cardinalMax);
//add epsilon to counteract floating point precision error
ax = copysign(abs(ax) + 1e-09, ax);
ay = copysign(abs(ay) + 1e-09, ay);
ax = axis->counteractPrecisionError(ax);
ay = axis->counteractPrecisionError(ay);
n32 data;
data.byte(0) = s8(-ay);
@@ -398,13 +380,9 @@ auto Gamepad::read() -> n32 {
auto Gamepad::getInodeChecksum(u8 bank) -> u8 {
if (bank < 62) {
u32 checksum = 0;
u32 i = bank == 0 ? 3 + ram.read<Byte>(0x20 + 0x1a) * 2 : 1; //first bank has 3 + bank * 2 system pages, other banks have 127.
for (i; i < 0x100; i++) {
checksum += ram.read<Byte>((1 + bank) * 0x100) + ram.read<Byte>((1 + bank) * 0x100 + 0x01);
}
u8 checksum = 0;
for (i32 i=2; i<0x100; i++)
checksum += ram.read<Byte>((1 + bank) * 0x100 + i);
return checksum;
}
@@ -442,7 +420,7 @@ auto Gamepad::formatControllerPak() -> void {
//pages 1 thru nBanks, nBanks+1 thru (nBanks*2) (inode table, inode table copy)
u8 nBanks = ram.read<Byte>(0x20 + 0x1a);
u32 inodeTablePage = 1;
u32 inodeTableCopyPage = 1 + nBanks * 2;
u32 inodeTableCopyPage = 1 + nBanks;
for(u32 bank : range(0,nBanks)) {
u32 firstDataPage = bank == 0 ? (3 + nBanks * 2) : 1; //first bank has 3 + bank * 2 system pages, other banks have 127.
for(u32 page : array<u32[2]>{inodeTablePage + bank, inodeTableCopyPage + bank}) {
+1
View File
@@ -5,6 +5,7 @@ struct Gamepad : Controller {
u8 bank;
Memory::Writable ram; //Toshiba TC55257DFL-85V
Node::Input::Rumble motor;
Node::Input::Axis axis;
Node::Input::Axis x;
Node::Input::Axis y;
+3 -3
View File
@@ -196,8 +196,8 @@ struct CPU : Thread {
template<u32 Size> auto write(u64 vaddr, u32 paddr, u64 data) -> void;
auto power(bool reset) -> void;
auto readDebug(u64 vaddr, u32 paddr) -> u8;
auto writeDebug(u64 vaddr, u32 paddr, u8 value) -> void;
template<u32 Size> auto readDebug(u64 vaddr, u32 paddr) -> u64;
template<u32 Size> auto writeDebug(u64 vaddr, u32 paddr, u64 value) -> void;
//8KB
struct Line {
@@ -318,7 +318,7 @@ struct CPU : Thread {
template<u32 Size> auto vaddrAlignedError(u64 vaddr, bool write) -> bool;
auto addressException(u64 vaddr) -> void;
auto readDebug(u64 vaddr) -> u8;
template <u32 Size> auto readDebug(u64 vaddr) -> u64;
template <u32 Size> auto writeDebug(u64 vaddr, u64 data) -> bool;
//serialization.cpp
+15 -8
View File
@@ -58,13 +58,16 @@ auto CPU::DataCache::read(u64 vaddr, u32 paddr) -> u64 {
return line.read<Size>(paddr);
}
auto CPU::DataCache::readDebug(u64 vaddr, u32 paddr) -> u8 {
template<u32 Size>
auto CPU::DataCache::readDebug(u64 vaddr, u32 paddr) -> u64 {
// This is used by the debugger to read memory through the cache but without
// actually causing side effects that modify the cache state (eg: no line fill)
auto& line = this->line(vaddr);
if(!line.hit(paddr)) {
Thread dummyThread{};
return bus.read<Byte>(paddr, dummyThread, "Ares Debugger");
return bus.read<Size>(paddr, dummyThread, "Ares Debugger");
}
return line.read<Byte>(paddr);
return line.read<Size>(paddr);
}
template<u32 Size>
@@ -79,13 +82,16 @@ auto CPU::DataCache::write(u64 vaddr, u32 paddr, u64 data) -> void {
line.write<Size>(paddr, data);
}
auto CPU::DataCache::writeDebug(u64 vaddr, u32 paddr, u8 data) -> void {
template<u32 Size>
auto CPU::DataCache::writeDebug(u64 vaddr, u32 paddr, u64 data) -> void {
// This is used by the debugger to write memory through the cache but without
// actually causing side effects that modify the cache state (eg: no line fill)
auto& line = this->line(vaddr);
if(!line.hit(paddr)) {
Thread dummyThread{};
return bus.write<Byte>(paddr, data, dummyThread, "Ares Debugger");
return bus.write<Size>(paddr, data, dummyThread, "Ares Debugger");
}
line.write<Byte>(paddr, data);
line.write<Size>(paddr, data);
}
auto CPU::DataCache::power(bool reset) -> void {
@@ -99,5 +105,6 @@ auto CPU::DataCache::power(bool reset) -> void {
}
}
template
auto CPU::DataCache::Line::write<Byte>(u32 paddr, u64 data) -> void;
template auto CPU::DataCache::Line::write<Byte>(u32 paddr, u64 data) -> void;
template auto CPU::DataCache::writeDebug<Byte>(u64 vaddr, u32 paddr, u64 data) -> void;
template auto CPU::DataCache::readDebug<Byte>(u64 vaddr, u32 paddr) -> u64;
+12 -6
View File
@@ -161,12 +161,13 @@ auto CPU::read(PhysAccess access) -> maybe<u64> {
return busRead<Size>(access.paddr);
}
auto CPU::readDebug(u64 vaddr) -> u8 {
template<u32 Size>
auto CPU::readDebug(u64 vaddr) -> u64 {
Thread dummyThread{};
auto access = devirtualize<Read, Byte>(vaddr, false, false);
auto access = devirtualize<Read, Size>(vaddr, false, false);
if(!access) return 0;
if(access.cache) return dcache.readDebug(access.vaddr, access.paddr);
return bus.read<Byte>(access.paddr, dummyThread, "Ares Debugger");
if(access.cache) return dcache.readDebug<Size>(access.vaddr, access.paddr);
return bus.read<Size>(access.paddr, dummyThread, "Ares Debugger");
}
@@ -180,11 +181,12 @@ auto CPU::write(PhysAccess access, u64 data) -> bool {
template<u32 Size>
auto CPU::writeDebug(u64 vaddr, u64 data) -> bool {
Thread dummyThread{};
auto access = devirtualize<Write, Size>(vaddr, false, false);
if(!access) return false;
GDB::server.reportMemWrite(access.vaddr, Size);
if(access.cache) return dcache.write<Size>(access.vaddr, access.paddr, data), true;
return busWrite<Size>(access.paddr, data), true;
if(access.cache) return dcache.writeDebug<Size>(access.vaddr, access.paddr, data), true;
return bus.write<Size>(access.paddr, data, dummyThread, "Ares Debugger"), true;
}
template<u32 Size>
@@ -221,3 +223,7 @@ template auto CPU::writeDebug<Byte>(u64, u64) -> bool;
template auto CPU::writeDebug<Half>(u64, u64) -> bool;
template auto CPU::writeDebug<Word>(u64, u64) -> bool;
template auto CPU::writeDebug<Dual>(u64, u64) -> bool;
template auto CPU::readDebug<Byte>(u64) -> u64;
template auto CPU::readDebug<Half>(u64) -> u64;
template auto CPU::readDebug<Word>(u64) -> u64;
template auto CPU::readDebug<Dual>(u64) -> u64;
+2
View File
@@ -8,6 +8,7 @@ inline auto PI::readWord(u32 address, Thread& thread) -> u32 {
}
thread.step(250 * 2);
io.busLatch = busRead<Word>(address);
io.pbusAddress = (address + 4) & ~1;
return io.busLatch;
}
@@ -59,6 +60,7 @@ inline auto PI::writeWord(u32 address, u32 data, Thread& thread) -> void {
if(io.ioBusy) return;
io.ioBusy = 1;
io.busLatch = data;
io.pbusAddress = (address + 4) & ~1;
queue.insert(Queue::PI_BUS_Write, 400);
return busWrite<Word>(address, data);
}
+1 -1
View File
@@ -17,7 +17,7 @@ auto RDRAM::Debugger::load(Node::Object parent) -> void {
memory.dcache->setSize(4_MiB + 4_MiB);
memory.dcache->setRead([&](u32 address) -> u8 {
u32 vaddr = address | 0x8000'0000;
return cpu.dcache.readDebug(vaddr, address);
return cpu.dcache.readDebug<Byte>(vaddr, address);
});
memory.dcache->setWrite([&](u32 address, u8 data) -> void {
u32 vaddr = address | 0x8000'0000;
+107 -41
View File
@@ -81,9 +81,6 @@ auto System::run() -> void {
vulkan.load(node);
_vulkanNeedsLoad = false;
}
if(_needsPower) {
_power(_reset);
}
cpu.main();
}
@@ -170,57 +167,130 @@ auto System::initDebugHooks() -> void {
res.resize(byteCount * 2);
char* resPtr = res.begin();
for(u32 i : range(byteCount)) {
auto val = cpu.readDebug(address++);
hexByte(resPtr, val);
resPtr += 2;
// For 8/16/32/64-bit reads, perform an actual read like the CPU would do.
// This makes sure we return the same value the CPU would get wrt to masking.
u64 value;
switch(byteCount) {
case Byte:
value = cpu.readDebug<Byte>(address);
hexByte(resPtr, value);
return res;
case Half:
value = cpu.readDebug<Half>(address);
hexByte(resPtr + 0, (value >> 8) & 0xff);
hexByte(resPtr + 2, value & 0xff);
return res;
case Word:
value = cpu.readDebug<Word>(address);
hexByte(resPtr + 0, (value >> 24) & 0xff);
hexByte(resPtr + 2, (value >> 16) & 0xff);
hexByte(resPtr + 4, (value >> 8) & 0xff);
hexByte(resPtr + 6, value & 0xff);
return res;
case Dual:
// Don't allow 64-bit access to RCP to prevent freezing
if (address < 0xffff'ffff'a400'0000ull || address + byteCount > 0xffff'ffff'bfff'ffffull) {
value = cpu.readDebug<Dual>(address);
hexByte(resPtr + 0, (value >> 56) & 0xff);
hexByte(resPtr + 2, (value >> 48) & 0xff);
hexByte(resPtr + 4, (value >> 40) & 0xff);
hexByte(resPtr + 6, (value >> 32) & 0xff);
hexByte(resPtr + 8, (value >> 24) & 0xff);
hexByte(resPtr + 10, (value >> 16) & 0xff);
hexByte(resPtr + 12, (value >> 8) & 0xff);
hexByte(resPtr + 14, value & 0xff);
return res;
}
}
return res;
// Handle reads of different/unaligned sizes only within the RDRAM area,
// where we are sure that the write size does not really matter
if(address >= 0xffff'ffff'8000'0000ull && address + byteCount <= 0xffff'ffff'83ef'ffffull) {
for(u32 i : range(byteCount)) {
auto val = cpu.readDebug<Byte>(address++);
hexByte(resPtr, val);
resPtr += 2;
}
return res;
} else if (address >= 0xffff'ffff'a000'0000ull && address + byteCount <= 0xffff'ffff'a3ef'ffffull) {
Thread dummyThread{};
for(u32 i : range(byteCount)) {
auto val = bus.read<Byte>(address & 0x1fff'ffff, dummyThread, "Ares Debugger");
hexByte(resPtr, val);
resPtr += 2;
address++;
}
return res;
} else if (address >= 0xffff'ffff'a400'0000ull && address + byteCount <= 0xffff'ffff'bfff'ffffull) {
// Otherwise, use 32-bit reads to read the data. This is the expected
// read for RCP, so it's the one that's most likely to return the data
// as the user would expect.
Thread dummyThread{};
for(u32 i : range(byteCount / 4)) {
u64 value = bus.read<Word>(address & 0x1fff'ffff, dummyThread, "Ares Debugger");
hexByte(resPtr + 0, (value >> 24) & 0xff);
hexByte(resPtr + 2, (value >> 16) & 0xff);
hexByte(resPtr + 4, (value >> 8) & 0xff);
hexByte(resPtr + 6, value & 0xff);
resPtr += 8;
address += 4;
}
}
return {};
};
GDB::server.hooks.write = [](u64 address, vector<u8> data) {
address = (s32)address;
// For 8/16/32/64-bit writes, perform an actual write like the CPU would do.
// This makes sure we write the same value the CPU would write wrt to masking.
u64 value;
switch(data.size()) {
case Byte:
value = (u64)data[0];
cpu.writeDebug<Byte>(address, value);
break;
case Half:
value = ((u64)data[0]<<8) | ((u64)data[1]<<0);
cpu.writeDebug<Half>(address, value);
break;
case Word:
value = ((u64)data[0]<<24) | ((u64)data[1]<<16) | ((u64)data[2]<<8) | ((u64)data[3]<<0);
cpu.writeDebug<Word>(address, value);
break;
case Dual:
value = ((u64)data[0]<<56) | ((u64)data[1]<<48) | ((u64)data[2]<<40) | ((u64)data[3]<<32);
value |= ((u64)data[4]<<24) | ((u64)data[5]<<16) | ((u64)data[6]<< 8) | ((u64)data[7]<< 0);
cpu.writeDebug<Dual>(address, value);
break;
}
// Handle writes of different/unaligned sizes only within the RDRAM area,
// where we are sure that the write size does not really matter
if(address >= 0xffff'ffff'8000'0000ull && address <= 0xffff'ffff'83ef'ffffull) {
if(address >= 0xffff'ffff'8000'0000ull && address + data.size() <= 0xffff'ffff'83ef'ffffull) {
for(auto b : data) {
cpu.dcache.writeDebug(address, address & 0x1fff'ffff, b);
cpu.dcache.writeDebug<Byte>(address, address & 0x1fff'ffff, b);
address++;
}
} else if (address >= 0xffff'ffff'a000'0000ull && address <= 0xffff'ffff'a3ef'ffffull) {
} else if (address >= 0xffff'ffff'a000'0000ull && address + data.size() <= 0xffff'ffff'a3ef'ffffull) {
Thread dummyThread{};
for(auto b : data) {
bus.write<Byte>(address & 0x1fff'ffff, b, dummyThread, "Ares Debugger");
address++;
}
} else {
// Otherwise, the write is expected to be of a set size to an aligned address.
u64 value;
switch(data.size()) {
case Byte:
value = (u64)data[0];
cpu.writeDebug<Byte>(address, value);
break;
case Half:
value = ((u64)data[0]<<8) | ((u64)data[1]<<0);
cpu.writeDebug<Half>(address, value);
break;
case Word:
value = ((u64)data[0]<<24) | ((u64)data[1]<<16) | ((u64)data[2]<<8) | ((u64)data[3]<<0);
cpu.writeDebug<Word>(address, value);
break;
case Dual:
value = ((u64)data[0]<<56) | ((u64)data[1]<<48) | ((u64)data[2]<<40) | ((u64)data[3]<<32);
value |= ((u64)data[4]<<24) | ((u64)data[5]<<16) | ((u64)data[6]<< 8) | ((u64)data[7]<< 0);
cpu.writeDebug<Dual>(address, value);
break;
} else if (address >= 0xffff'ffff'a400'0000ull && address + data.size() <= 0xffff'ffff'bfff'ffffull) {
// Otherwise, use 32-bit writes to write the data. This is the expected
// write for RCP, so it's the one that's most likely to write the data
// as the user would expect.
Thread dummyThread{};
for(u32 i = 0; i < data.size() / 4; i++) {
u64 value = ((u64)data[i*4+0]<<24) | ((u64)data[i*4+1]<<16) | ((u64)data[i*4+2]<<8) | ((u64)data[i*4+3]<<0);
bus.write<Word>(address & 0x1fff'ffff, value, dummyThread, "Ares Debugger");
address += 4;
}
}
};
GDB::server.hooks.regRead = [](u32 regIdx) {
if(regIdx < 32) {
return hex(cpu.ipu.r[regIdx].u64, 16, '0');
@@ -352,13 +422,6 @@ auto System::save() -> void {
}
auto System::power(bool reset) -> void {
_reset = reset;
// Re-initializing ParaLLEl-RDP from a thread that did not initialize it seems to occasionally cause a deadlock.
// Delay reset until the next iteration of the system's run loop on the worker thread.
_needsPower = true;
}
auto System::_power(bool reset) -> void {
for(auto& setting : node->find<Node::Setting::Setting>()) setting->setLatch();
if constexpr(Accuracy::CPU::Recompiler || Accuracy::RSP::Recompiler) {
@@ -370,6 +433,10 @@ auto System::_power(bool reset) -> void {
if(_DD()) dd.power(reset);
mi.power(reset);
vi.power(reset);
#if defined(VULKAN)
vulkan.unload();
_vulkanNeedsLoad = true;
#endif
ai.power(reset);
pi.power(reset);
pif.power(reset);
@@ -380,7 +447,6 @@ auto System::_power(bool reset) -> void {
rsp.power(reset);
rdp.power(reset);
if(model() == Model::Aleck64) aleck64.power(reset);
_needsPower = false;
}
}
-2
View File
@@ -39,8 +39,6 @@ private:
} information;
atomic<bool> _vulkanNeedsLoad = false;
atomic<bool> _needsPower = false;
bool _reset = false;
auto initDebugHooks() -> void;
auto _power(bool reset) -> void;
+10 -3
View File
@@ -44,19 +44,26 @@ auto VI::load(Node::Object parent) -> void {
}
});
int videoHeight = Region::PAL() ? 576 : 480;
#if defined(VULKAN)
if(vulkan.enable) {
screen->setSize(vulkan.outputUpscale * 640, vulkan.outputUpscale * 480);
screen->setSize(vulkan.outputUpscale * 640, vulkan.outputUpscale * videoHeight);
if(!vulkan.supersampleScanout) {
screen->setScale(1.0 / vulkan.outputUpscale, 1.0 / vulkan.outputUpscale);
}
} else {
screen->setSize(640, 480);
screen->setSize(640, videoHeight);
}
#else
screen->setSize(640, 480);
screen->setSize(640, videoHeight);
#endif
// Pedantic N64 NTSC aspect ratio is 120:119, but let's keep 120:120 to avoid slight scaling.
// Pedantic N64 PAL aspect ratio is 5900000:4965653, but let's use 12:10 to achieve the
// same aspect ratio as NTSC.
Region::PAL() ? screen->setAspect(12, 10) : screen->setAspect(120, 120);
debugger.load(node);
}
+1 -1
View File
@@ -18,7 +18,7 @@ auto load(Node::System& node, string name) -> bool {
}
auto option(string name, string value) -> bool {
if(name == "Pixel Accuracy") vdp.setAccurate(value.boolean());
if(name == "Pixel Accuracy") vdp.setAccurate(true); // Forced: scanline renderer is too buggy
return true;
}
+6 -5
View File
@@ -24,17 +24,18 @@ auto VDP::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("VDP");
screen = node->append<Node::Video::Screen>("Screen", 1365, 263);
screen->colors(1 << 10, {&VDP::color, this});
screen->setSize(1128, 263);
screen->setScale(0.25, 1.0);
screen->setAspect(8.0, 7.0);
screen->refreshRateHint(60); // TODO: More accurate refresh rate hint
colorEmulation = screen->append<Node::Setting::Boolean>("Color Emulation", true, [&](auto value) {
screen->resetPalette();
});
colorEmulation->setDynamic(true);
screen->colors(1 << 10, {&VDP::color, this});
screen->setSize(1128, 263);
screen->setScale(0.25, 1.0);
screen->setAspect(8.0, 7.0);
screen->refreshRateHint(60); // TODO: More accurate refresh rate hint
vce.debugger.load(vce, parent);
vdc0.debugger.load(vdc0, parent); if(Model::SuperGrafx())
vdc1.debugger.load(vdc1, parent);
+4 -5
View File
@@ -39,17 +39,16 @@ auto VDP::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("VDP");
screen = node->append<Node::Video::Screen>("Screen", 1365, 263);
colorEmulation = screen->append<Node::Setting::Boolean>("Color Emulation", true, [&](auto value) {
screen->resetPalette();
});
colorEmulation->setDynamic(true);
screen->colors(1 << 10, {&VDP::color, this});
screen->setSize(1128, 263);
screen->setScale(0.25, 1.0);
screen->setAspect(8.0, 7.0);
screen->refreshRateHint(60); // TODO: More accurate refresh rate hint
colorEmulation = screen->append<Node::Setting::Boolean>("Color Emulation", true, [&](auto value) {
screen->resetPalette();
});
colorEmulation->setDynamic(true);
vce.debugger.load(vce, node);
vdc0.debugger.load(vdc0, node); if(Model::SuperGrafx())
vdc1.debugger.load(vdc1, node);
+67 -4
View File
@@ -5,6 +5,8 @@
DualShock::DualShock(Node::Port parent) {
node = parent->append<Node::Peripheral>("DualShock");
axis = node->append<Node::Input::Axis>("Axis");
lx = node->append<Node::Input::Axis >("L-Stick X");
ly = node->append<Node::Input::Axis >("L-Stick Y");
rx = node->append<Node::Input::Axis >("R-Stick X");
@@ -213,15 +215,76 @@ auto DualShock::readPad() -> vector<u8> {
if(!analogMode && !configMode) return result;
auto cardinalMax = 127.5;
auto diagonalMax = 127.5;
auto innerDeadzone = 6.0; //missing information on deadzone for DualShock potentiometers; use arbitrary number roughly 4.7% of cardinalMax instead (common software default in gaming industry seems to be 5.0%)
auto saturationRadius = (innerDeadzone + diagonalMax + sqrt(pow(innerDeadzone + diagonalMax, 2.0) - 2.0 * sqrt(2.0) * diagonalMax * innerDeadzone)) / sqrt(2.0); //from linear response curve function within axis->processDeadzoneAndResponseCurve, substitute saturationRadius * sqrt(2) / 2 for right-hand lengthAbsolute and set diagonalMax as the result then solve for saturationRadius
auto offset = -0.5;
platform->input(rx);
platform->input(ry);
result.append((rx->value() + 32768) * 255 / 65535);
result.append((ry->value() + 32768) * 255 / 65535);
//scale {-32767 ... +32767} to {-saturationRadius + offset ... +saturationRadius + offset}
auto arx = axis->setOperatingRange(rx->value(), saturationRadius, offset);
auto ary = axis->setOperatingRange(ry->value(), saturationRadius, offset);
//create inner axial dead-zone in range {-innerDeadzone ... +innerDeadzone} and scale from it up to saturationRadius
arx = axis->processDeadzoneAndResponseCurve(arx, innerDeadzone, saturationRadius, offset);
ary = axis->processDeadzoneAndResponseCurve(ary, innerDeadzone, saturationRadius, offset);
auto scaledLengthRightStick = hypot(arx - offset, ary - offset);
if(scaledLengthRightStick > saturationRadius) {
arx = axis->revisePosition(arx, scaledLengthRightStick, saturationRadius, offset);
ary = axis->revisePosition(ary, scaledLengthRightStick, saturationRadius, offset);
}
//let cardinalMax and diagonalMax define boundaries and restrict to a square gate
double arxBounded = 0.0;
double aryBounded = 0.0;
axis->applyGateBoundaries(innerDeadzone, cardinalMax, diagonalMax, arx, ary, offset, arxBounded, aryBounded);
arx = arxBounded;
ary = aryBounded;
//keep cardinal input within positive and negative bounds of cardinalMax
arx = axis->clampAxisToNearestBoundary(arx, offset, cardinalMax);
ary = axis->clampAxisToNearestBoundary(ary, offset, cardinalMax);
//add epsilon to counteract floating point precision error
arx = axis->counteractPrecisionError(arx);
ary = axis->counteractPrecisionError(ary);
result.append(u8(arx + 128.0));
result.append(u8(ary + 128.0));
platform->input(lx);
platform->input(ly);
result.append((lx->value() + 32768) * 255 / 65535);
result.append((ly->value() + 32768) * 255 / 65535);
auto alx = axis->setOperatingRange(lx->value(), saturationRadius, offset);
auto aly = axis->setOperatingRange(ly->value(), saturationRadius, offset);
alx = axis->processDeadzoneAndResponseCurve(alx, innerDeadzone, saturationRadius, offset);
aly = axis->processDeadzoneAndResponseCurve(aly, innerDeadzone, saturationRadius, offset);
auto scaledLengthLeftStick = hypot(alx - offset, aly - offset);
if(scaledLengthLeftStick > saturationRadius) {
alx = axis->revisePosition(alx, scaledLengthLeftStick, saturationRadius, offset);
aly = axis->revisePosition(aly, scaledLengthLeftStick, saturationRadius, offset);
}
double alxBounded = 0.0;
double alyBounded = 0.0;
axis->applyGateBoundaries(innerDeadzone, cardinalMax, diagonalMax, alx , aly, offset, alxBounded, alyBounded);
alx = alxBounded;
aly = alyBounded;
alx = axis->clampAxisToNearestBoundary(alx, offset, cardinalMax);
aly = axis->clampAxisToNearestBoundary(aly, offset, cardinalMax);
alx = axis->counteractPrecisionError(alx);
aly = axis->counteractPrecisionError(aly);
result.append(u8(alx + 128.0));
result.append(u8(aly + 128.0));
return result;
}
@@ -1,4 +1,5 @@
struct DualShock : PeripheralDevice {
Node::Input::Axis axis;
Node::Input::Axis lx;
Node::Input::Axis ly;
Node::Input::Axis rx;
+1
View File
@@ -5,6 +5,7 @@ namespace ares::SuperFamicom {
#include "port.cpp"
#include "gamepad/gamepad.cpp"
#include "rumble-gamepad/rumble-gamepad.cpp"
#include "justifier/justifier.cpp"
#include "justifiers/justifiers.cpp"
#include "mouse/mouse.cpp"
+1
View File
@@ -24,6 +24,7 @@ struct Controller {
#include "port.hpp"
#include "gamepad/gamepad.hpp"
#include "rumble-gamepad/rumble-gamepad.hpp"
#include "justifier/justifier.hpp"
#include "justifiers/justifiers.hpp"
#include "mouse/mouse.hpp"
+2
View File
@@ -13,6 +13,7 @@ auto ControllerPort::load(Node::Object parent) -> void {
port->setDisconnect([&] { device.reset(); });
port->setSupported({
"Gamepad",
"Rumble Gamepad",
"Justifier",
"Justifiers",
"Mouse",
@@ -30,6 +31,7 @@ auto ControllerPort::unload() -> void {
auto ControllerPort::allocate(string name) -> Node::Peripheral {
if(name == "Gamepad" ) device = new Gamepad(port);
if(name == "Rumble Gamepad" ) device = new RumbleGamepad(port);
if(name == "Justifier" ) device = new Justifier(port);
if(name == "Justifiers" ) device = new Justifiers(port);
if(name == "Mouse" ) device = new Mouse(port);
@@ -0,0 +1,88 @@
RumbleGamepad::RumbleGamepad(Node::Port parent) {
node = parent->append<Node::Peripheral>("Rumble Gamepad");
up = node->append<Node::Input::Button>("Up");
down = node->append<Node::Input::Button>("Down");
left = node->append<Node::Input::Button>("Left");
right = node->append<Node::Input::Button>("Right");
b = node->append<Node::Input::Button>("B");
a = node->append<Node::Input::Button>("A");
y = node->append<Node::Input::Button>("Y");
x = node->append<Node::Input::Button>("X");
l = node->append<Node::Input::Button>("L");
r = node->append<Node::Input::Button>("R");
select = node->append<Node::Input::Button>("Select");
start = node->append<Node::Input::Button>("Start");
rumble = node->append<Node::Input::Rumble>("Rumble");
}
auto RumbleGamepad::data() -> n2 {
rumbleState = (rumbleState << 1) | iobit();
if(rumbleState.bit(8, 15) == 0x72) {
rumble->setValues((rumbleState.bit(0,3) * 65535) / 15, (rumbleState.bit(4,7) * 65535) / 15);
platform->input(rumble);
rumbleState = 0;
}
if(latched == 1) {
platform->input(b);
return b->value();
}
//note: D-pad physically prevents up+down and left+right from being pressed at the same time
switch(counter++) {
case 0: return b->value();
case 1: return y->value();
case 2: return select->value();
case 3: return start->value();
case 4: return upLatch;
case 5: return downLatch;
case 6: return leftLatch;
case 7: return rightLatch;
case 8: return a->value();
case 9: return x->value();
case 10: return l->value();
case 11: return r->value();
case 12: return 0; //4-bit device signature
case 13: return 0;
case 14: return 0;
case 15: return 0;
}
counter = 16;
return 1;
}
auto RumbleGamepad::latch(n1 data) -> void {
if(latched == data) return;
latched = data;
counter = 0;
if(latched == 0) {
platform->input(b);
platform->input(y);
platform->input(select);
platform->input(start);
platform->input(up);
platform->input(down);
platform->input(left);
platform->input(right);
platform->input(a);
platform->input(x);
platform->input(l);
platform->input(r);
if(!(up->value() & down->value())) {
yHold = 0, upLatch = up->value(), downLatch = down->value();
} else if(!yHold) {
yHold = 1, swap(upLatch, downLatch);
}
if(!(left->value() & right->value())) {
xHold = 0, leftLatch = left->value(), rightLatch = right->value();
} else if(!xHold) {
xHold = 1, swap(leftLatch, rightLatch);
}
}
}
@@ -0,0 +1,33 @@
struct RumbleGamepad : Controller {
Node::Input::Button up;
Node::Input::Button down;
Node::Input::Button left;
Node::Input::Button right;
Node::Input::Button b;
Node::Input::Button a;
Node::Input::Button y;
Node::Input::Button x;
Node::Input::Button l;
Node::Input::Button r;
Node::Input::Button select;
Node::Input::Button start;
Node::Input::Rumble rumble;
RumbleGamepad(Node::Port);
auto data() -> n2;
auto latch(n1 data) -> void;
private:
n1 latched;
n8 counter;
n1 yHold;
n1 upLatch;
n1 downLatch;
n1 xHold;
n1 leftLatch;
n1 rightLatch;
n16 rumbleState;
};
+4 -4
View File
@@ -23,6 +23,10 @@ auto PPU::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("PPU");
screen = node->append<Node::Video::Screen>("Screen", 564, height() * 2);
deepBlackBoost = screen->append<Node::Setting::Boolean>("Deep Black Boost", true, [&](auto value) {
screen->resetPalette();
});
deepBlackBoost->setDynamic(true);
screen->colors(1 << 19, {&PPU::color, this});
screen->setSize(564, height() * 2);
screen->setScale(0.5, 0.5);
@@ -32,10 +36,6 @@ auto PPU::load(Node::Object parent) -> void {
vramSize = node->append<Node::Setting::Natural>("VRAM", 64_KiB);
vramSize->setAllowedValues({64_KiB, 128_KiB});
deepBlackBoost = screen->append<Node::Setting::Boolean>("Deep Black Boost", true, [&](auto value) {
screen->resetPalette();
});
deepBlackBoost->setDynamic(true);
debugger.load(node);
}
+6 -5
View File
@@ -34,6 +34,12 @@ auto PPU::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("PPU");
screen = node->append<Node::Video::Screen>("Screen", 564, height() * 2);
deepBlackBoost = screen->append<Node::Setting::Boolean>("Deep Black Boost", true, [&](auto value) {
screen->resetPalette();
});
deepBlackBoost->setDynamic(true);
screen->colors(1 << 19, {&PPU::color, this});
screen->setSize(564, height() * 2);
screen->setScale(0.5, 0.5);
@@ -49,11 +55,6 @@ auto PPU::load(Node::Object parent) -> void {
vramSize = node->append<Node::Setting::Natural>("VRAM", 64_KiB);
vramSize->setAllowedValues({64_KiB, 128_KiB});
deepBlackBoost = screen->append<Node::Setting::Boolean>("Deep Black Boost", true, [&](auto value) {
screen->resetPalette();
});
deepBlackBoost->setDynamic(true);
debugger.load(node);
}
+2 -2
View File
@@ -62,9 +62,9 @@ ares_add_sources(
)
ares_add_sources(
CORE #
CORE #
sg
INCLUDED #
INCLUDED #
keyboard/keyboard.hpp
)
+6 -5
View File
@@ -28,17 +28,18 @@ auto PPU::load(Node::Object parent) -> void {
const u32 height = 144 + (Model::WonderSwan() ? 13 : 0);
screen = node->append<Node::Video::Screen>("Screen", width, height);
screen->colors(1 << 12, {&PPU::color, this});
screen->setSize(width, height);
screen->setScale(1.0, 1.0);
screen->setAspect(1.0, 1.0);
screen->setFillColor(SoC::ASWAN() ? 0xfff : 0);
colorEmulation = screen->append<Node::Setting::Boolean>("Color Emulation", true, [&](auto value) {
screen->resetPalette();
});
colorEmulation->setDynamic(true);
screen->colors(1 << 12, {&PPU::color, this});
screen->setSize(width, height);
screen->setScale(1.0, 1.0);
screen->setAspect(1.0, 1.0);
screen->setFillColor(SoC::ASWAN() ? 0xfff : 0);
interframeBlending = screen->append<Node::Setting::Boolean>("Interframe Blending", true, [&](auto value) {
screen->setInterframeBlending(value);
});
+8 -8
View File
@@ -77,14 +77,14 @@ function(ares_populate_version_info)
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
OUTPUT_STRIP_TRAILING_WHITESPACE
)
endif()
if(modified)
set(ares_git_shorthash "${ares_git_shorthash}-modified")
endif()
if(NOT ares_git_ref)
set(ARES_VERSION "${ares_git_shorthash} - ${ares_git_commit_date}")
else()
set(ARES_VERSION "${ares_git_ref} - (${ares_git_shorthash} - ${ares_git_commit_date})")
if(modified)
set(ares_git_shorthash "${ares_git_shorthash}-modified")
endif()
if(NOT ares_git_ref)
set(ARES_VERSION "${ares_git_shorthash} - ${ares_git_commit_date}")
else()
set(ARES_VERSION "${ares_git_ref} - (${ares_git_shorthash} - ${ares_git_commit_date})")
endif()
endif()
endif()
+1 -1
View File
@@ -64,7 +64,7 @@ macro(librashader_set_soname)
unset(_result)
endmacro()
if(OS_LINUX OR OS_FREEBSD OR OS_OPENBSD)
if(OS_LINUX OR OS_FREEBSD OR OS_OPENBSD OR ARES_SKIP_DEPS OR (NOT ARES_ENABLE_LIBRASHADER))
set(_librashader_path_hint "${CMAKE_SOURCE_DIR}/thirdparty/librashader/include")
endif()
-4
View File
@@ -55,11 +55,7 @@ set(
/Zc:__cplusplus
/utf-8
/permissive-
$<$<NOT:$<CONFIG:Debug>>:/GL>
$<$<NOT:$<CONFIG:Debug>>:/GS->
$<$<NOT:$<CONFIG:Debug>>:/Oi>
$<$<NOT:$<CONFIG:Debug>>:/Ob2>
$<$<NOT:$<CONFIG:Debug>>:/Ot>
)
set(
+5 -5
View File
@@ -1,14 +1,14 @@
{
"dependencies": {
"prebuilt": {
"version": "2025-05-22",
"version": "2025-07-14",
"baseUrl": "https://github.com/ares-emulator/ares-deps/releases/download",
"label": "Pre-Built ares-deps",
"hashes": {
"linux-universal": "d24162136d2a310ade86223e8cf099c6e6b93003b35c136e8cbe30ffc2390845",
"macos-universal": "2b7bc99370e360c9086ba863ac9cd810aa67928206b799af74830ba6e0a9ffc4",
"windows-arm64": "07181e8a56f2f17b26b70ef852a44e4193e5196adc5013fcd1d691d155a49f42",
"windows-x64": "a7086281f98d9f0fcad0a833a6410f90b4776a5d274c125aef99fe3a81d2eed5"
"linux-universal": "c4679098d2a9cd51c1cacb70555e302e78501e481dcaabc060ca16d6f620128f",
"macos-universal": "a93cdd3b82b9fcdc7cc0047480a3dcc93f5ca198f48593f52138bba1f5ba6d77",
"windows-arm64": "c7d2e1d8f72f5b6add4e68bbbc684248cdc9556f9a27dbdf62a961b890eb52ea",
"windows-x64": "5fe3200331dc5d397b4a3080d3d2c86e2ace48e14b51a6e304c5ca75328726b3"
}
}
},
+1
View File
@@ -73,6 +73,7 @@ target_sources(
emulator/mega-32x.cpp
emulator/mega-cd-32x.cpp
emulator/mega-cd.cpp
emulator/mega-ld.cpp
emulator/mega-drive.cpp
emulator/msx.cpp
emulator/msx2.cpp
+3 -6
View File
@@ -35,10 +35,10 @@ public:
if(program._isRunning && !program._programThread) {
program._interruptDepth += 1;
if(program._interruptDepth == 1) {
lock.lock();
program.lock.lock();
program._interruptWaiting = true;
program._programConditionVariable.notify_one();
program._programConditionVariable.wait(lock, [] { return program._interruptWorking; });
program._programConditionVariable.wait(program.lock, [] { return program._interruptWorking; });
}
}
}
@@ -49,14 +49,11 @@ public:
if(program._interruptDepth == 0) {
program._interruptWorking = false;
program._interruptWaiting = false;
lock.unlock();
program.lock.unlock();
program._programConditionVariable.notify_one();
}
}
}
private:
std::unique_lock<std::mutex> lock{program._programMutex, std::defer_lock};
};
auto locate(const string& name) -> string;
+2
View File
@@ -53,6 +53,7 @@ namespace ares::Atari2600 {
#include "mega-32x.cpp"
#include "mega-cd.cpp"
#include "mega-cd-32x.cpp"
#include "mega-ld.cpp"
#endif
#ifdef CORE_MS
@@ -230,6 +231,7 @@ auto Emulator::construct() -> void {
emulators.append(new Mega32X);
emulators.append(new MegaCD);
emulators.append(new MegaCD32X);
emulators.append(new MegaLD);
#endif
#ifdef CORE_SATURN
@@ -70,6 +70,7 @@ auto FamicomDiskSystem::load(Menu menu) -> void {
auto FamicomDiskSystem::changeDiskState(string state) -> void
{
Program::Guard guard;
print("Changing disk state to: ", state, "\n");
//Eject the disk and give the emulator time to react before re-inserting
print("Ejecting disk\n");
@@ -77,6 +78,7 @@ auto FamicomDiskSystem::changeDiskState(string state) -> void
if(state != "Ejected") {
print("Setting disk change timer\n");
diskChangeTimer->onActivate([=] {
Program::Guard guard;
print("Disk change timer activated, setting disk state to: ", state, "\n");
diskChangeTimer->setEnabled(false);
emulator->notify(state);
+1
View File
@@ -42,6 +42,7 @@ auto GameBoyAdvance::load(Menu menu) -> void {
MenuRadioItem item{&orientationMenu};
item.setText(orientation);
item.onActivate([=] {
Program::Guard guard;
if(auto orientations = root->find<ares::Node::Setting::String>("PPU/Screen/Orientation")) {
orientations->setValue(orientation);
}
+1
View File
@@ -62,6 +62,7 @@ auto GameBoy::load(Menu menu) -> void {
MenuRadioItem item{&colorEmulationMenu};
item.setText(setting);
item.onActivate([=] {
Program::Guard guard;
if(auto settings = root->find<ares::Node::Setting::String>("PPU/Screen/Color Emulation")) {
settings->setValue(setting);
}
+2
View File
@@ -116,6 +116,7 @@ auto MegaCD32X::load(Menu menu) -> void {
MenuItem changeDisc{&menu};
changeDisc.setIcon(Icon::Device::Optical);
changeDisc.setText("Change Disc").onActivate([&] {
Program::Guard guard;
save();
auto tray = root->find<ares::Node::Port>("Mega CD/Disc Tray");
tray->disconnect();
@@ -126,6 +127,7 @@ auto MegaCD32X::load(Menu menu) -> void {
//give the emulator core a few seconds to notice an empty drive state before reconnecting
discTrayTimer->onActivate([&] {
Program::Guard guard;
discTrayTimer->setEnabled(false);
auto tray = root->find<ares::Node::Port>("Mega CD/Disc Tray");
tray->allocate();
+2
View File
@@ -121,6 +121,7 @@ auto MegaCD::load(Menu menu) -> void {
MenuItem changeDisc{&menu};
changeDisc.setIcon(Icon::Device::Optical);
changeDisc.setText("Change Disc").onActivate([&] {
Program::Guard guard;
save();
auto tray = root->find<ares::Node::Port>("Mega CD/Disc Tray");
tray->disconnect();
@@ -131,6 +132,7 @@ auto MegaCD::load(Menu menu) -> void {
//give the emulator core a few seconds to notice an empty drive state before reconnecting
discTrayTimer->onActivate([&] {
Program::Guard guard;
discTrayTimer->setEnabled(false);
auto tray = root->find<ares::Node::Port>("Mega CD/Disc Tray");
tray->allocate();
+142
View File
@@ -0,0 +1,142 @@
struct MegaLD : Emulator {
MegaLD();
auto load() -> LoadResult override;
auto load(Menu) -> void override;
auto unload() -> void override;
auto save() -> bool override;
auto pak(ares::Node::Object) -> shared_pointer<vfs::directory> override;
auto changeDiskState(const string state) -> void;
u32 regionID = 0;
sTimer discTrayTimer;
};
MegaLD::MegaLD() {
manufacturer = "Sega";
name = "Mega LD";
firmware.append({"BIOS", "US"}); //NTSC-U
firmware.append({"BIOS", "Japan"}); //NTSC-J
for(auto id : range(2)) {
InputPort port{string{"Controller Port ", 1 + id}};
{ InputDevice device{"Fighting Pad"};
device.digital("Up", virtualPorts[id].pad.up);
device.digital("Down", virtualPorts[id].pad.down);
device.digital("Left", virtualPorts[id].pad.left);
device.digital("Right", virtualPorts[id].pad.right);
device.digital("A", virtualPorts[id].pad.west);
device.digital("B", virtualPorts[id].pad.south);
device.digital("C", virtualPorts[id].pad.east);
device.digital("X", virtualPorts[id].pad.l_bumper);
device.digital("Y", virtualPorts[id].pad.north);
device.digital("Z", virtualPorts[id].pad.r_bumper);
device.digital("Mode", virtualPorts[id].pad.select);
device.digital("Start", virtualPorts[id].pad.start);
port.append(device); }
ports.append(port);
}
}
auto MegaLD::load() -> LoadResult {
game = mia::Medium::create("Mega LD");
string location = Emulator::load(game, configuration.game);
if(!location) return noFileSelected;
LoadResult result = game->load(location);
if(result != successful) return result;
auto region = Emulator::region();
//if statements below are ordered by lowest to highest priority
if(region == "NTSC-J") regionID = 1;
if(region == "NTSC-U") regionID = 0;
system = mia::System::create("Mega LD");
result = system->load(firmware[regionID].location);
if(result != successful) {
result.firmwareSystemName = "Mega LD";
result.firmwareType = firmware[regionID].type;
result.firmwareRegion = firmware[regionID].region;
result.result = noFirmware;
return result;
}
if(!ares::MegaDrive::load(root, {"[Sega] Mega LD (", region, ")"})) return otherError;
if(auto port = root->find<ares::Node::Port>("Mega CD/Disc Tray")) {
port->allocate();
port->connect();
}
if(auto port = root->find<ares::Node::Port>("Controller Port 1")) {
port->allocate("Fighting Pad");
port->connect();
}
if(auto port = root->find<ares::Node::Port>("Controller Port 2")) {
port->allocate("Fighting Pad");
port->connect();
}
discTrayTimer = Timer{};
return successful;
}
auto MegaLD::load(Menu menu) -> void {
Group group;
Menu changeSideMenu{&menu};
changeSideMenu.setIcon(Icon::Device::Optical);
changeSideMenu.setText("Change Side");
auto sides = game->pak->attribute("medium").split(",").strip();
MenuRadioItem noDiscItem{&changeSideMenu};
noDiscItem.setText("No Disc").onActivate([&] {
changeDiskState("No Disc");
});
group.append(noDiscItem);
auto checkedItemIndex = (sides.size() > 0 ? group.objectCount() : 0);
for(auto side : sides) {
MenuRadioItem item{ &changeSideMenu };
group.append(item);
item.setText(side).onActivate([this, side] { changeDiskState(side); });
}
group.objects<MenuRadioItem>()[checkedItemIndex].setChecked();
}
auto MegaLD::changeDiskState(const string state) -> void {
Program::Guard guard;
discTrayTimer->setEnabled(false);
save();
auto tray = root->find<ares::Node::Port>("Mega CD/Disc Tray");
tray->disconnect();
if(state == "No Disc") return;
discTrayTimer->onActivate([&, state] {
Program::Guard guard;
discTrayTimer->setEnabled(false);
auto tray = root->find<ares::Node::Port>("Mega CD/Disc Tray");
tray->allocate(state);
tray->connect();
}).setInterval(3000).setEnabled();
}
auto MegaLD::unload() -> void {
Emulator::unload();
discTrayTimer.reset();
}
auto MegaLD::save() -> bool {
root->save();
system->save(game->location);
game->save(game->location);
return true;
}
auto MegaLD::pak(ares::Node::Object node) -> shared_pointer<vfs::directory> {
if(node->name() == "Mega Drive") return system->pak;
if(node->name() == "Mega CD Disc") return game->pak;
return {};
}
+93 -91
View File
@@ -140,7 +140,7 @@ auto Nintendo64::load() -> LoadResult {
port->connect();
bool transferPakConnected = false;
if(auto port = peripheral->find<ares::Node::Port>("Pak")) {
if(id == 0 && game->pak->attribute("tpak").boolean()) {
if(game->pak->attribute({"port", id+1, "/tpak"}).boolean()) {
#if defined(CORE_GB)
auto transferPak = port->allocate("Transfer Pak");
port->connect();
@@ -161,7 +161,7 @@ auto Nintendo64::load() -> LoadResult {
}
if(!transferPakConnected) {
if(id == 0 && game->pak->attribute("cpak").boolean()) {
if(game->pak->attribute({"port", id+1, "/cpak"}).boolean()) {
gamepad = mia::Pak::create("Nintendo 64");
//create maximum sized controller pak, file is resized later
@@ -169,7 +169,7 @@ auto Nintendo64::load() -> LoadResult {
gamepad->load("save.pak", ".pak", game->location);
port->allocate("Controller Pak");
port->connect();
} else if(game->pak->attribute("rpak").boolean()) {
} else if(game->pak->attribute({"port", id+1, "/rpak"}).boolean()) {
port->allocate("Rumble Pak");
port->connect();
}
@@ -188,6 +188,7 @@ auto Nintendo64::load(Menu menu) -> void {
MenuItem changeDisk{&menu};
changeDisk.setIcon(Icon::Device::Optical);
changeDisk.setText("Change Disk").onActivate([&] {
Program::Guard guard;
save();
auto drive = root->find<ares::Node::Port>("Nintendo 64DD/Disk Drive");
drive->disconnect();
@@ -198,6 +199,7 @@ auto Nintendo64::load(Menu menu) -> void {
//give the emulator core a few seconds to notice an empty drive state before reconnecting
diskInsertTimer->onActivate([&] {
Program::Guard guard;
diskInsertTimer->setEnabled(false);
auto drive = root->find<ares::Node::Port>("Nintendo 64DD/Disk Drive");
drive->allocate();
@@ -213,107 +215,107 @@ auto Nintendo64::portMenu(Menu& portMenu, ares::Node::Port port) -> void {
const string portNum = port->name()[port->name().length() - 1];
// remove this check to enable pak menu option for all 4 controllers
if(portNum == "1") {
if(portMenu.actionCount() > 0) portMenu.append(MenuSeparator());
Menu pakMenu{&portMenu};
pakMenu.setText("Pak");
Group pakGroup;
MenuRadioItem nothing{&pakMenu};;
nothing.setText("Nothing");
nothing.setAttribute<ares::Node::Port>("port", port);
nothing.onActivate([=] {
Program::Guard guard;
auto port = nothing.attribute<ares::Node::Port>("port");
const string portName = port->name();
if(auto port = emulator->root->find<ares::Node::Port>(portName)) {
port->disconnect();
auto peripheral = port->allocate("Gamepad");
port->connect();
}
});
pakGroup.append(nothing);
if(portMenu.actionCount() > 0) portMenu.append(MenuSeparator());
Menu pakMenu{&portMenu};
pakMenu.setText("Pak");
Group pakGroup;
MenuRadioItem nothing{&pakMenu};;
nothing.setText("Nothing");
nothing.setAttribute<ares::Node::Port>("port", port);
nothing.onActivate([=] {
auto port = nothing.attribute<ares::Node::Port>("port");
const string portName = port->name();
if(auto port = emulator->root->find<ares::Node::Port>(portName)) {
port->disconnect();
auto peripheral = port->allocate("Gamepad");
MenuRadioItem cpak{&pakMenu};
cpak.setAttribute<ares::Node::Port>("port", port);
cpak.setText("Controller Pak");
cpak.onActivate([=] {
Program::Guard guard;
auto port = cpak.attribute<ares::Node::Port>("port");
const string portName = port->name();
if(auto port = emulator->root->find<ares::Node::Port>(portName)) {
port->disconnect();
auto peripheral = port->allocate("Gamepad");
port->connect();
if(auto port = peripheral->find<ares::Node::Port>("Pak")) {
emulator->gamepad = mia::Pak::create("Nintendo 64");
emulator->gamepad->pak->append("save.pak", 32_KiB);
string pakExt = ".pak";
if(portNum != "1") { pakExt = string(".", portNum, ".pak");}
emulator->gamepad->load("save.pak", pakExt, emulator->game->location);
port->allocate("Controller Pak");
port->connect();
}
});
pakGroup.append(nothing);
}
});
pakGroup.append(cpak);
MenuRadioItem cpak{&pakMenu};
cpak.setAttribute<ares::Node::Port>("port", port);
cpak.setText("Controller Pak");
cpak.onActivate([=] {
auto port = cpak.attribute<ares::Node::Port>("port");
const string portName = port->name();
if(auto port = emulator->root->find<ares::Node::Port>(portName)) {
port->disconnect();
auto peripheral = port->allocate("Gamepad");
MenuRadioItem rpak{&pakMenu};
rpak.setAttribute<ares::Node::Port>("port", port);
rpak.setText("Rumble Pak");
rpak.onActivate([=] {
Program::Guard guard;
auto port = rpak.attribute<ares::Node::Port>("port");
const string portName = port->name();
if(auto port = emulator->root->find<ares::Node::Port>(portName)) {
port->disconnect();
auto peripheral = port->allocate("Gamepad");
port->connect();
if(auto port = peripheral->find<ares::Node::Port>("Pak")) {
port->allocate("Rumble Pak");
port->connect();
if(auto port = peripheral->find<ares::Node::Port>("Pak")) {
emulator->gamepad = mia::Pak::create("Nintendo 64");
emulator->gamepad->pak->append("save.pak", 32_KiB);
string pakExt = ".pak";
if(portNum != "1") { pakExt = string(".", portNum, ".pak");}
emulator->gamepad->load("save.pak", pakExt, emulator->game->location);
port->allocate("Controller Pak");
port->connect();
}
}
});
pakGroup.append(cpak);
}
});
pakGroup.append(rpak);
MenuRadioItem rpak{&pakMenu};
rpak.setAttribute<ares::Node::Port>("port", port);
rpak.setText("Rumble Pak");
rpak.onActivate([=] {
auto port = rpak.attribute<ares::Node::Port>("port");
const string portName = port->name();
if(auto port = emulator->root->find<ares::Node::Port>(portName)) {
port->disconnect();
auto peripheral = port->allocate("Gamepad");
port->connect();
if(auto port = peripheral->find<ares::Node::Port>("Pak")) {
port->allocate("Rumble Pak");
port->connect();
}
}
});
pakGroup.append(rpak);
MenuRadioItem tpak{&pakMenu};
tpak.setAttribute<ares::Node::Port>("port", port);
tpak.setText("Transfer Pak");
tpak.onActivate([=] {
auto port = tpak.attribute<ares::Node::Port>("port");
const string portName = port->name();
if(auto port = emulator->root->find<ares::Node::Port>(portName)) {
port->disconnect();
auto peripheral = port->allocate("Gamepad");
port->connect();
if(auto port = peripheral->find<ares::Node::Port>("Pak")) {
MenuRadioItem tpak{&pakMenu};
tpak.setAttribute<ares::Node::Port>("port", port);
tpak.setText("Transfer Pak");
tpak.onActivate([=] {
Program::Guard guard;
auto port = tpak.attribute<ares::Node::Port>("port");
const string portName = port->name();
if(auto port = emulator->root->find<ares::Node::Port>(portName)) {
port->disconnect();
auto peripheral = port->allocate("Gamepad");
port->connect();
if(auto port = peripheral->find<ares::Node::Port>("Pak")) {
#if defined(CORE_GB)
emulator->gb.reset();
auto transferPak = port->allocate("Transfer Pak");
port->connect();
emulator->gb.reset();
auto transferPak = port->allocate("Transfer Pak");
port->connect();
if(auto slot = transferPak->find<ares::Node::Port>("Cartridge Slot")) {
emulator->gb = mia::Medium::create("Game Boy Color");
string tmpPath;
if(emulator->gb->load(emulator->load(emulator->gb, tmpPath)) == successful) {
slot->allocate();
slot->connect();
} else {
port->disconnect();
emulator->gb.reset();
}
if(auto slot = transferPak->find<ares::Node::Port>("Cartridge Slot")) {
emulator->gb = mia::Medium::create("Game Boy Color");
string tmpPath;
if(emulator->gb->load(emulator->load(emulator->gb, tmpPath)) == successful) {
slot->allocate();
slot->connect();
} else {
port->disconnect();
emulator->gb.reset();
}
#endif
}
#endif
}
});
pakGroup.append(tpak);
}
});
pakGroup.append(tpak);
// set currently enabled pak
// Note: based on initialization routine in desktop-ui/emulator/nintendo-64.cpp & nintedo-64dd.cpp
if(emulator->game->pak->attribute("tpak").boolean() && portNum == "1") tpak.setChecked();
else if(emulator->game->pak->attribute("cpak").boolean() && portNum == "1") cpak.setChecked();
else if(emulator->game->pak->attribute("rpak").boolean()) rpak.setChecked();
else nothing.setChecked();
}
// set currently enabled pak
if(emulator->game->pak->attribute({"port", portNum, "/tpak"}).boolean()) tpak.setChecked();
else if(emulator->game->pak->attribute({"port", portNum, "/cpak"}).boolean()) cpak.setChecked();
else if(emulator->game->pak->attribute({"port", portNum, "/rpak"}).boolean()) rpak.setChecked();
else nothing.setChecked();
}
auto Nintendo64::unload() -> void {
+2
View File
@@ -164,6 +164,7 @@ auto Nintendo64DD::load(Menu menu) -> void {
MenuItem changeDisk{&menu};
changeDisk.setIcon(Icon::Device::Optical);
changeDisk.setText("Change Disk").onActivate([&] {
Program::Guard guard;
save();
auto drive = root->find<ares::Node::Port>("Nintendo 64DD/Disk Drive");
drive->disconnect();
@@ -174,6 +175,7 @@ auto Nintendo64DD::load(Menu menu) -> void {
//give the emulator core a few seconds to notice an empty drive state before reconnecting
diskInsertTimer->onActivate([&] {
Program::Guard guard;
diskInsertTimer->setEnabled(false);
auto drive = root->find<ares::Node::Port>("Nintendo 64DD/Disk Drive");
drive->allocate();
+2
View File
@@ -138,6 +138,7 @@ auto PlayStation::load(Menu menu) -> void {
MenuItem changeDisc{&menu};
changeDisc.setIcon(Icon::Device::Optical);
changeDisc.setText("Change Disc").onActivate([&] {
Program::Guard guard;
save();
auto tray = root->find<ares::Node::Port>("PlayStation/Disc Tray");
tray->disconnect();
@@ -148,6 +149,7 @@ auto PlayStation::load(Menu menu) -> void {
//give the emulator core a few seconds to notice an empty drive state before reconnecting
discTrayTimer->onActivate([&] {
Program::Guard guard;
discTrayTimer->setEnabled(false);
auto tray = root->find<ares::Node::Port>("PlayStation/Disc Tray");
tray->allocate();
+16
View File
@@ -29,6 +29,22 @@ SuperFamicom::SuperFamicom() {
device.digital("Start", virtualPorts[id].pad.start);
port.append(device); }
{ InputDevice device{"Rumble Gamepad"};
device.digital("Up", virtualPorts[id].pad.up);
device.digital("Down", virtualPorts[id].pad.down);
device.digital("Left", virtualPorts[id].pad.left);
device.digital("Right", virtualPorts[id].pad.right);
device.digital("B", virtualPorts[id].pad.south);
device.digital("A", virtualPorts[id].pad.east);
device.digital("Y", virtualPorts[id].pad.west);
device.digital("X", virtualPorts[id].pad.north);
device.digital("L", virtualPorts[id].pad.l_bumper);
device.digital("R", virtualPorts[id].pad.r_bumper);
device.digital("Select", virtualPorts[id].pad.select);
device.digital("Start", virtualPorts[id].pad.start);
device.rumble("Rumble", virtualPorts[id].pad.rumble);
port.append(device); }
{ InputDevice device{"Justifier"};
device.relative("X", virtualPorts[id].mouse.x);
device.relative("Y", virtualPorts[id].mouse.y);
+1
View File
@@ -39,6 +39,7 @@ auto WonderSwanColor::load(Menu menu) -> void {
MenuRadioItem item{&orientationMenu};
item.setText(orientation);
item.onActivate([=] {
Program::Guard guard;
if(auto orientations = root->find<ares::Node::Setting::String>("PPU/Screen/Orientation")) {
orientations->setValue(orientation);
}
+1
View File
@@ -39,6 +39,7 @@ auto WonderSwan::load(Menu menu) -> void {
MenuRadioItem item{&orientationMenu};
item.setText(orientation);
item.onActivate([=] {
Program::Guard guard;
if(auto orientations = root->find<ares::Node::Setting::String>("PPU/Screen/Orientation")) {
orientations->setValue(orientation);
}
+1 -1
View File
@@ -162,7 +162,7 @@ auto InputManager::createHotkeys() -> void {
auto InputManager::pollHotkeys() -> void {
if(Application::modal()) return;
if(!driverSettings.inputDefocusAllow.checked()) {
if(settings.input.defocus != "Allow") {
if (!presentation.focused() && !ruby::video.fullScreen()) return;
}
+4 -2
View File
@@ -517,8 +517,10 @@ auto InputManager::poll(bool force) -> void {
if(changed) {
this->devices = devices;
bind();
inputSettings.refresh();
hotkeySettings.refresh();
if(settingsWindow.initialized) {
inputSettings.refresh();
hotkeySettings.refresh();
}
}
}
+27 -19
View File
@@ -35,36 +35,43 @@ Presentation::Presentation() {
setAlignment(Alignment::Center);
});
videoOutputMenu.setText("Output").setIcon(Icon::Emblem::Image);
videoOutputPixelPerfect.setText("Pixel Perfect").onActivate([&] {
settings.video.output = "Perfect";
});
videoOutputFixedScale.setText("Scale (Fixed)").onActivate([&] {
settings.video.output = "Fixed";
});
videoOutputIntegerScale.setText("Scale (Integer)").onActivate([&] {
settings.video.output = "Integer";
});
videoOutputScale.setText("Scale (Best Fit)").onActivate([&] {
videoOutputScale.setText("Scale: Best Fit").onActivate([&] {
settings.video.output = "Scale";
});
videoOutputStretch.setText("Stretch").onActivate([&] {
videoOutputIntegerScale.setText("Scale: Integer").onActivate([&] {
settings.video.output = "Integer";
});
videoOutputStretch.setText("Scale: Stretch to Fill").onActivate([&] {
settings.video.output = "Stretch";
});
if(settings.video.output == "Perfect" ) videoOutputPixelPerfect.setChecked();
if(settings.video.output == "Fixed" ) videoOutputFixedScale.setChecked();
if(settings.video.output == "Integer" ) videoOutputIntegerScale.setChecked();
if(settings.video.output == "Scale" ) videoOutputScale.setChecked();
if(settings.video.output == "Stretch" ) videoOutputStretch.setChecked();
videoAspectCorrection.setText("Aspect Correction").setChecked(settings.video.aspectCorrection).onToggle([&] {
settings.video.aspectCorrection = videoAspectCorrection.checked();
videoAspectCorrectionNone.setText("Aspect: No correction").onActivate([&] {
settings.video.aspectCorrection = "None";
if(settings.video.adaptiveSizing) resizeWindow();
});
videoAdaptiveSizing.setText("Adaptive Sizing").setChecked(settings.video.adaptiveSizing).onToggle([&] {
videoAspectCorrectionStandard.setText("Aspect: Standard").onActivate([&] {
settings.video.aspectCorrection = "Standard";
if(settings.video.adaptiveSizing) resizeWindow();
});
videoAspectCorrectionAnamorphic.setText("Aspect: Anamorphic (16:9)").onActivate([&] {
settings.video.aspectCorrection = "Anamorphic";
if(settings.video.adaptiveSizing) resizeWindow();
});
if(settings.video.aspectCorrection == "None") videoAspectCorrectionNone.setChecked();
if(settings.video.aspectCorrection == "Standard") videoAspectCorrectionStandard.setChecked();
if(settings.video.aspectCorrection == "Anamorphic") videoAspectCorrectionAnamorphic.setChecked();
videoAdaptiveSizing.setText("Window: Auto resize").setChecked(settings.video.adaptiveSizing).onToggle([&] {
if(settings.video.adaptiveSizing = videoAdaptiveSizing.checked()) resizeWindow();
});
videoAutoCentering.setText("Auto Centering").setChecked(settings.video.autoCentering).onToggle([&] {
videoAutoCentering.setText("Window: Auto center").setChecked(settings.video.autoCentering).onToggle([&] {
if(settings.video.autoCentering = videoAutoCentering.checked()) resizeWindow();
});
videoShaderMenu.setText("Shader").setIcon(Icon::Emblem::Image);
@@ -295,7 +302,6 @@ Presentation::Presentation() {
auto Presentation::resizeWindow() -> void {
if(fullScreen()) setFullScreen(false);
if(maximized()) return;
if(settings.video.output == "Fixed") return;
u32 multiplier = settings.video.multiplier;
u32 viewportWidth = 320 * multiplier;
@@ -305,7 +311,8 @@ auto Presentation::resizeWindow() -> void {
auto& node = program.screens.first();
u32 videoWidth = node->width() * node->scaleX();
u32 videoHeight = node->height() * node->scaleY();
if(settings.video.aspectCorrection) videoWidth = videoWidth * node->aspectX() / node->aspectY();
if(settings.video.aspectCorrection != "None") videoWidth = videoWidth * node->aspectX() / node->aspectY();
if(settings.video.aspectCorrection == "Anamorphic") videoWidth = videoWidth * 4 / 3;
if(node->rotation() == 90 || node->rotation() == 270) swap(videoWidth, videoHeight);
viewportWidth = videoWidth * multiplier;
@@ -556,6 +563,7 @@ auto Presentation::refreshSystemMenu() -> void {
{ MenuRadioItem peripheralItem{&portMenu};
peripheralItem.setAttribute<ares::Node::Port>("port", port);
peripheralItem.setText("Nothing");
if(!port->connected()) peripheralItem.setChecked();
peripheralItem.onActivate([=] {
auto port = peripheralItem.attribute<ares::Node::Port>("port");
port->disconnect();
+8 -6
View File
@@ -19,15 +19,17 @@ struct Presentation : Window {
Menu videoSizeMenu{&settingsMenu};
Group videoSizeGroup;
Menu videoOutputMenu{&settingsMenu};
MenuRadioItem videoOutputPixelPerfect{&videoOutputMenu};
MenuRadioItem videoOutputFixedScale{&videoOutputMenu};
MenuRadioItem videoOutputIntegerScale{&videoOutputMenu};
MenuRadioItem videoOutputScale{&videoOutputMenu};
MenuRadioItem videoOutputIntegerScale{&videoOutputMenu};
MenuRadioItem videoOutputStretch{&videoOutputMenu};
Group videoOutputGroup{&videoOutputPixelPerfect, &videoOutputFixedScale, &videoOutputIntegerScale,
&videoOutputScale, &videoOutputStretch};
Group videoOutputGroup{&videoOutputIntegerScale, &videoOutputScale, &videoOutputStretch};
MenuSeparator videoOutputSeparator{&videoOutputMenu};
MenuCheckItem videoAspectCorrection{&videoOutputMenu};
MenuRadioItem videoAspectCorrectionNone{&videoOutputMenu};
MenuRadioItem videoAspectCorrectionStandard{&videoOutputMenu};
MenuRadioItem videoAspectCorrectionAnamorphic{&videoOutputMenu};
Group videoAspectCorrectionGroup{&videoAspectCorrectionNone, &videoAspectCorrectionStandard,
&videoAspectCorrectionAnamorphic};
MenuSeparator videoOutputSeparator2{&videoOutputMenu};
MenuCheckItem videoAdaptiveSizing{&videoOutputMenu};
MenuCheckItem videoAutoCentering{&videoOutputMenu};
Menu videoShaderMenu{&settingsMenu};
+2 -11
View File
@@ -74,7 +74,8 @@ auto Program::video(ares::Node::Video::Screen node, const u32* data, u32 pitch,
u32 videoWidth = node->width() * node->scaleX();
u32 videoHeight = node->height() * node->scaleY();
if(settings.video.aspectCorrection) videoWidth = videoWidth * node->aspectX() / node->aspectY();
if(settings.video.aspectCorrection != "None") videoWidth = videoWidth * node->aspectX() / node->aspectY();
if(settings.video.aspectCorrection == "Anamorphic") videoWidth = videoWidth * 4 / 3;
if(node->rotation() == 90 || node->rotation() == 270) swap(videoWidth, videoHeight);
ruby::video.lock();
@@ -86,16 +87,6 @@ auto Program::video(ares::Node::Video::Screen node, const u32* data, u32 pitch,
u32 outputWidth = videoWidth * multiplier;
u32 outputHeight = videoHeight * multiplier;
if(settings.video.output == "Perfect") {
outputWidth = videoWidth;
outputHeight = videoHeight;
}
if(settings.video.output == "Fixed") {
outputWidth = videoWidth * settings.video.multiplier;
outputHeight = videoHeight * settings.video.multiplier;
}
if(multiplier == 0 || settings.video.output == "Scale") {
f32 multiplierX = (f32)viewportWidth / (f32)videoWidth;
f32 multiplierY = (f32)viewportHeight / (f32)videoHeight;
+5 -5
View File
@@ -17,10 +17,6 @@ auto Program::create() -> void {
audioDriverUpdate();
inputDriverUpdate();
driverSettings.videoRefresh();
driverSettings.audioRefresh();
driverSettings.inputRefresh();
_isRunning = true;
worker = thread::create({&Program::emulatorRunLoop, this});
@@ -50,7 +46,7 @@ auto Program::waitForInterrupts() -> void {
std::unique_lock<std::mutex> lock(_programMutex);
_interruptWorking = true;
_programConditionVariable.notify_one();
_programConditionVariable.wait(lock, [this] { return !_interruptWorking; });
_programConditionVariable.wait(lock, [this] { return !_interruptWorking || _quitting; });
}
auto Program::emulatorRunLoop(uintptr_t) -> void {
@@ -60,6 +56,7 @@ auto Program::emulatorRunLoop(uintptr_t) -> void {
// Allow other threads to carry out tasks between emulator run loop iterations
if(_interruptWaiting) {
waitForInterrupts();
continue;
}
if(!emulator) {
usleep(20 * 1000);
@@ -141,7 +138,10 @@ auto Program::main() -> void {
}
auto Program::quit() -> void {
Program::Guard guard;
_quitting = true;
lock.unlock();
_programConditionVariable.notify_all();
worker.join();
program._isRunning = false;
unload();
+8 -3
View File
@@ -1,9 +1,7 @@
struct Program : ares::Platform {
auto create() -> void;
auto main() -> void;
auto emulatorRunLoop(uintptr_t) -> void;
auto quit() -> void;
auto waitForInterrupts() -> void;
//platform.cpp
auto attach(ares::Node::Object) -> void override;
@@ -110,9 +108,14 @@ struct Program : ares::Platform {
std::recursive_mutex inputMutex;
private:
/// Routine used while the emulator thread is waiting for work to complete that modifies its state.
auto waitForInterrupts() -> void;
/// The main run loop for the emulator thread spawned on startup.
auto emulatorRunLoop(uintptr_t) -> void;
atomic<bool> _quitting = false;
atomic<bool> _needsResize = false;
/// Mutex used to manage access to the status message queue.
std::recursive_mutex _messageMutex;
@@ -132,6 +135,8 @@ private:
NALL_USED auto getProgramThreadValue() -> bool {
return _programThread;
}
/// Unique lock used by `Program::Guard` instances to synchronize interrupts. Generally this should not be used directly, with synchronization happening via `Program::Guard` construction. However, it is still exposed for exceptions such as the exit handler in `Program::quit()`.
std::unique_lock<std::mutex> lock{_programMutex, std::defer_lock};
};
extern Program program;
+2 -2
View File
@@ -17,7 +17,7 @@ auto Program::updateMessage() -> void {
presentation.statusLeft.setText();
}
if(vblanksPerSecond > 0) {
if(vblanksPerSecond > 0 && !paused) {
presentation.statusRight.setText({ vblanksPerSecond.load(), " VPS" });
}
@@ -32,7 +32,7 @@ auto Program::updateMessage() -> void {
}
bool defocused = driverSettings.inputDefocusPause.checked() && !ruby::video.fullScreen() && !presentation.focused();
bool defocused = settings.input.defocus == "Pause" && !ruby::video.fullScreen() && !presentation.focused();
if(emulator && defocused) message.text = "Paused";
}
+1
View File
@@ -19,6 +19,7 @@ auto Program::mute() -> void {
auto Program::paletteUpdate() -> void {
Program::Guard guard;
if(!emulator) return;
if(!emulator->root) return;
for(auto& screen : emulator->root->find<ares::Node::Video::Screen>()) {
screen->setLuminance(settings.video.luminance);
screen->setSaturation(settings.video.saturation);
+24 -38
View File
@@ -4,17 +4,16 @@ auto DriverSettings::construct() -> void {
videoLabel.setText("Video").setFont(Font().setBold());
videoDriverList.onChange([&] {
bool enabled = false;
if(videoDriverList.selected().text() != settings.video.driver) { enabled = true; }
videoDriverAssign.setEnabled(enabled);
});
videoDriverLabel.setText("Driver:");
videoDriverAssign.setText("Apply").setEnabled(false).onActivate([&] {
settings.video.driver = videoDriverList.selected().text();
if (videoDriverUpdate()) {
videoDriverAssign.setEnabled(false);
if(videoDriverList.selected().text() != settings.video.driver) {
auto previous = settings.video.driver;
settings.video.driver = videoDriverList.selected().text();
if (!videoDriverUpdate()) {
settings.video.driver = previous;
videoRefresh();
}
}
});
videoDriverLabel.setText("Driver:");
videoMonitorLabel.setText("Fullscreen monitor:");
videoMonitorList.onChange([&] {
settings.video.monitor = videoMonitorList.selected().text();
@@ -59,17 +58,16 @@ auto DriverSettings::construct() -> void {
audioLabel.setText("Audio").setFont(Font().setBold());
audioDriverList.onChange([&] {
bool enabled = false;
if(audioDriverList.selected().text() != settings.audio.driver) { enabled = true; }
audioDriverAssign.setEnabled(enabled);
});
audioDriverLabel.setText("Driver:");
audioDriverAssign.setText("Apply").setEnabled(false).onActivate([&] {
settings.audio.driver = audioDriverList.selected().text();
if (audioDriverUpdate()) {
audioDriverAssign.setEnabled(false);
if(audioDriverList.selected().text() != settings.audio.driver) {
auto previous = settings.audio.driver;
settings.audio.driver = audioDriverList.selected().text();
if (!audioDriverUpdate()) {
settings.audio.driver = previous;
audioRefresh();
}
}
});
audioDriverLabel.setText("Driver:");
audioDeviceLabel.setText("Output device:");
audioDeviceList.onChange([&] {
settings.audio.device = audioDeviceList.selected().text();
@@ -106,17 +104,16 @@ auto DriverSettings::construct() -> void {
inputLabel.setText("Input").setFont(Font().setBold());
inputDriverList.onChange([&] {
bool enabled = false;
if(inputDriverList.selected().text() != settings.input.driver) { enabled = true; }
inputDriverAssign.setEnabled(enabled);
});
inputDriverLabel.setText("Driver:");
inputDriverAssign.setText("Apply").setEnabled(false).onActivate([&] {
settings.input.driver = inputDriverList.selected().text();
if (inputDriverUpdate()) {
inputDriverAssign.setEnabled(false);
if(inputDriverList.selected().text() != settings.input.driver) {
auto previous = settings.input.driver;
settings.input.driver = inputDriverList.selected().text();
if (!inputDriverUpdate()) {
settings.input.driver = previous;
inputRefresh();
}
}
});
inputDriverLabel.setText("Driver:");
inputDefocusLabel.setText("When focus is lost:");
inputDefocusPause.setText("Pause emulation").onActivate([&] {
settings.input.defocus = "Pause";
@@ -130,8 +127,6 @@ auto DriverSettings::construct() -> void {
if(settings.input.defocus == "Pause") inputDefocusPause.setChecked();
if(settings.input.defocus == "Block") inputDefocusBlock.setChecked();
if(settings.input.defocus == "Allow") inputDefocusAllow.setChecked();
driverApplyHint.setText("Note: You must click on the 'Apply' button to update and save changes to driver selection.").setFont(Font().setSize(8.0)).setForegroundColor(SystemColor::Sublabel);
videoDriverLayout.setPadding(12_sx, 0);
videoPropertyLayout.setPadding(12_sx, 0);
@@ -150,9 +145,6 @@ auto DriverSettings::videoRefresh() -> void {
ComboButtonItem item{&videoDriverList};
item.setText(driver);
if(driver == ruby::video.driver()) item.setSelected();
if(settings.video.driver == ruby::video.driver()) {
videoDriverAssign.setEnabled(false);
}
}
videoMonitorList.reset();
for(auto& monitor : ruby::video.hasMonitors()) {
@@ -198,9 +190,6 @@ auto DriverSettings::audioRefresh() -> void {
ComboButtonItem item{&audioDriverList};
item.setText(driver);
if(driver == ruby::audio.driver()) item.setSelected();
if(settings.audio.driver == ruby::audio.driver()) {
audioDriverAssign.setEnabled(false);
}
}
audioDeviceList.reset();
for(auto& device : ruby::audio.hasDevices()) {
@@ -244,9 +233,6 @@ auto DriverSettings::inputRefresh() -> void {
ComboButtonItem item{&inputDriverList};
item.setText(driver);
if(driver == ruby::input.driver()) item.setSelected();
if(settings.input.driver == ruby::input.driver()) {
inputDriverAssign.setEnabled(false);
}
}
VerticalLayout::resize();
}
+8 -2
View File
@@ -63,7 +63,7 @@ auto Settings::process(bool load) -> void {
bind(string, "Video/Shader", video.shader);
bind(natural, "Video/Multiplier", video.multiplier);
bind(string, "Video/Output", video.output);
bind(boolean, "Video/AspectCorrection", video.aspectCorrection);
bind(string, "Video/AspectCorrectionMode", video.aspectCorrection);
bind(boolean, "Video/AdaptiveSizing", video.adaptiveSizing);
bind(boolean, "Video/AutoCentering", video.autoCentering);
bind(real, "Video/Luminance", video.luminance);
@@ -178,7 +178,7 @@ auto Settings::process(bool load) -> void {
//
SettingsWindow::SettingsWindow() {
auto SettingsWindow::initialize() -> void {
onClose([&] {
settings.save();
setVisible(false);
@@ -231,9 +231,15 @@ SettingsWindow::SettingsWindow() {
setSize({700_sx, 425_sy});
setAlignment({0.0, 1.0});
setResizable(false);
driverSettings.videoRefresh();
driverSettings.audioRefresh();
driverSettings.inputRefresh();
initialized = true;
}
auto SettingsWindow::show(const string& panel) -> void {
if(!initialized) initialize();
for(auto item : panelList.items()) {
if(item.text() == panel) {
item.setSelected();
+6 -7
View File
@@ -18,7 +18,7 @@ struct Settings : Markup::Node {
string shader = "None";
u32 multiplier = 2;
string output = "Scale";
bool aspectCorrection = true;
string aspectCorrection = "Standard";
bool adaptiveSizing = true;
bool autoCentering = false;
@@ -355,7 +355,6 @@ struct DriverSettings : VerticalLayout {
HorizontalLayout videoDriverLayout{this, Size{~0, 0}};
Label videoDriverLabel{&videoDriverLayout, Size{0, 0}};
ComboButton videoDriverList{&videoDriverLayout, Size{0, 0}};
Button videoDriverAssign{&videoDriverLayout, Size{0, 0}};
Label videoDriverActive{&videoDriverLayout, Size{0, 0}};
HorizontalLayout videoPropertyLayout{this, Size{~0, 0}};
Label videoMonitorLabel{&videoPropertyLayout, Size{0, 0}};
@@ -378,7 +377,6 @@ struct DriverSettings : VerticalLayout {
HorizontalLayout audioDriverLayout{this, Size{~0, 0}};
Label audioDriverLabel{&audioDriverLayout, Size{0, 0}};
ComboButton audioDriverList{&audioDriverLayout, Size{0, 0}};
Button audioDriverAssign{&audioDriverLayout, Size{0, 0}};
Label audioDriverActive{&audioDriverLayout, Size{0, 0}};
HorizontalLayout audioDeviceLayout{this, Size{~0, 0}};
Label audioDeviceLabel{&audioDeviceLayout, Size{0, 0}};
@@ -397,7 +395,6 @@ struct DriverSettings : VerticalLayout {
HorizontalLayout inputDriverLayout{this, Size{~0, 0}};
Label inputDriverLabel{&inputDriverLayout, Size{0, 0}};
ComboButton inputDriverList{&inputDriverLayout, Size{0, 0}};
Button inputDriverAssign{&inputDriverLayout, Size{0, 0}};
Label inputDriverActive{&inputDriverLayout, Size{0, 0}};
HorizontalLayout inputDefocusLayout{this, Size{~0, 0}};
Label inputDefocusLabel{&inputDefocusLayout, Size{0, 0}};
@@ -405,8 +402,6 @@ struct DriverSettings : VerticalLayout {
RadioLabel inputDefocusBlock{&inputDefocusLayout, Size{0, 0}};
RadioLabel inputDefocusAllow{&inputDefocusLayout, Size{0, 0}};
Group inputDefocusGroup{&inputDefocusPause, &inputDefocusBlock, &inputDefocusAllow};
//
Label driverApplyHint{this, Size{0, 35}};
};
struct DebugSettings : VerticalLayout {
@@ -439,7 +434,6 @@ struct HomePanel : VerticalLayout {
};
struct SettingsWindow : Window {
SettingsWindow();
auto show(const string& panel) -> void;
auto eventChange() -> void;
@@ -457,6 +451,11 @@ struct SettingsWindow : Window {
DriverSettings driverSettings;
DebugSettings debugSettings;
HomePanel homePanel;
bool initialized = false;
private:
auto initialize() -> void;
};
extern Settings settings;
+1
View File
@@ -8,6 +8,7 @@
cocoaMenu = [[NSMenu alloc] initWithTitle:@""];
[self setSubmenu:cocoaMenu];
[cocoaMenu setAutoenablesItems:NO];
}
return self;
}
+2
View File
@@ -30,6 +30,7 @@ target_sources(
medium/medium.hpp
medium/mega-32x.cpp
medium/mega-cd.cpp
medium/mega-ld.cpp
medium/mega-drive.cpp
medium/msx.cpp
medium/msx2.cpp
@@ -89,6 +90,7 @@ target_sources(
system/mega-32x.cpp
system/mega-cd-32x.cpp
system/mega-cd.cpp
system/mega-ld.cpp
system/mega-drive.cpp
system/msx.cpp
system/msx2.cpp
Loaded 100 of 132 files, more files were not shown because too many files have changed in this diff. Show more