Compare commits

...
32 Commits
Author SHA1 Message Date
Luke Usher 39025af1fe release ares v136 2024-02-23 17:02:48 +00:00
Luke Usher 5cdefb088d desktop-ui: cleanup resource search paths
As well as simplifying the code a little, this also  fixes an oversight where users could not override application resources on macOS.
2024-02-21 17:49:12 +00:00
Giovanni Bajo d0253599a6 n64: fix corner case in FPU exception handling (#1400)
Ares checked each of the two input arguments to three-ops functions
separately, but this is not correct. Conditions causing unimplemented
operation on either input (sNAN, subnormal) take precedence on
conditions causing invalid operation on either input (qNAN).

After this fix, Ares passes n64-systemtest COP1 stress test.
2024-02-20 23:34:38 +00:00
Luke Usher 1e83da0241 md: implement subchannel processing for mcd (CD+G) 2024-02-19 14:19:56 +00:00
Luke Usher ed0a1c8852 cheats: only create a cheat file when cheats are added
This also cleans up empty cheat files on unload.
2024-02-18 08:41:56 +00:00
Luke Usher dd9c728a12 ares: preliminary cheat support
Adds a cheat editor to Tools->Cheats when a game is loaded.

The cheat editor should work for all systems except N64 and PS1: support for these will come later.

The code has currently only been tested for MS, MD and SFC but it is assumed all the other systems are also functioning; the code paths are the same.

Code format is standard patch codes in the format X:Y where X is a memory address and Y is a value to place there; most emulators also support this format.

You can use multiple codes in a single cheat by separating codes with a + sign.

Cheats that require memory comparisons are not currently supported, only replacements

Cheats are saved alongside the roms, eg "Game.sfc" will create "Game.cheats.bml".
2024-02-16 19:01:57 +00:00
Luke Usher 7ec1e1430c fc: reduce overscan cropping similar to ms 2024-02-15 17:35:47 +00:00
Luke Usher 16379aabff ruby: use PRESENTINTERVAL_ONE rather than GetRasterStatus for VBlank with d3d9 2024-02-15 17:34:30 +00:00
Luke Usher 11f24b47c3 ms: adjust overscan cropping, it was a little too agressive 2024-02-15 17:33:51 +00:00
Luke Usher 32f205975c md: add CD PCM RAM to debug memory viewer/editor 2024-02-14 17:45:50 +00:00
png183 21ca9cf76e gba: timer 0 count-up bit cannot be set (#1392)
The count-up bit for timer 0 cannot be set on hardware ([test
ROM](https://github.com/png183/gba-tests/blob/master/timer/timer_check_count_up_presence.gba)).
This PR implements said behaviour in ares, preventing timer 0 from
incorrectly halting if this bit is written.
2024-02-14 10:14:39 +00:00
Giovanni Bajo d32b3ffe45 n64: revert previous commit (add HLE RDRAM initialization) (#1391)
This is actually still required as we still skip RDRAM initialization
anyway and proprietary IPL3 doesn't initialize the RAM size in this
case.
2024-02-14 10:14:20 +00:00
Luke Usher 5864382c67 ps1: various fixes to GPUSTAT register behavior 2024-02-13 22:34:03 +00:00
Luke Usher f392e4f428 libchdr: disable asm optimisation for zstd
Should fix the Ubuntu build.
2024-02-11 19:51:28 +00:00
Luke Usher d0075978f8 libchdr: rollback lzma to 19.00
This version still exists in the repo for libchdr
22.01 fails compilation on many platforms right now
2024-02-11 19:37:46 +00:00
Luke Usher c1e5777dc1 thirdparty: fix compilation after libchdr update 2024-02-11 19:16:41 +00:00
Luke Usher ec6c9fc330 Merge commit '9bc95db1f0c5591dfbcec23c8e326aa6c2bb3b29' 2024-02-11 19:16:32 +00:00
Luke Usher 9bc95db1f0 Squashed 'thirdparty/libchdr/' changes from 9108f34a8..2648c221c
2648c221c Merge pull request #108 from herbymachine/master
f7729588b Update libchdr_huffman.c
26d27ca49 Merge pull request #106 from zakk4223/zstd
d6f59e748 Add zstd codec

git-subtree-dir: thirdparty/libchdr
git-subtree-split: 2648c221c9ce6b49600ed7d2eb45c9e6e593790f
2024-02-11 19:16:32 +00:00
Luke Usher 6d80a8e940 Merge commit '2a0f9651b976a5431510222e04077fd360415d69' 2024-02-11 19:16:30 +00:00
Luke Usher 2a0f9651b9 Squashed 'thirdparty/sljit/' changes from c1cbaf09e..607158516
607158516 Improve serialize interface
dd73cb873 Implement serialization interface
5d2d7cd59 Improve PPC branch support
8b8b6b442 Improve ARM32 branch support
2cfed9ce4 Improve x86, ARM-T2 branch support
6c75e773c Improve riscv branch support
dfeb8cf68 Add extraction macros for operands
dc52b334e Fix typo (#229)
7a821cd71 Add LoongArch float to integer conversion special cases (#226)
cbde0bea6 regex: allow building with a C++ compiler (#224)
40680870e Fix masked shift on arm32
a0643952a Fix sljit_get_register_index on x86-32
80bdff333 Support temporary registers in sljit_get_register_index
4629814f8 riscv: fix getput_arg and load_addr_to_reg (#223)
a26427a6e define float to integer conversion special cases
42128180d build: make code mostly ANSI C89 compatible (#221)
57c212f58 loongarch: fix FTBFS when building pcre2test as part of PCRE2 (#220)
3faacd431 Optimize certain fop1 operations on x86
4f33321c1 Move float/call/simd tests to separate files
230429241 Add another temp register on s390x
fbf9c47f3 Support access to temporary registers
8c5c50463 Implement simd on s390x
02e76fb14 s390x: fix FTBFS with clang (#219)
0bd5147fc Implement simd2 operations

git-subtree-dir: thirdparty/sljit
git-subtree-split: 6071585164d9a38a8890cfd2722ca9d8492528bb
2024-02-11 19:16:29 +00:00
Giovanni Bajo 51a10b0bd0 n64: avoid HLE-ish memory init (#1388)
Not required anymore, possibly for a long time. IPL3 does that, plus
this creates correctness issues with open source IPL3 which *doesn't*
initialize those memory locations.
2024-02-11 11:09:07 +00:00
png183 7e2f490fd0 gba: improve horizontal sprite mosaic (#1387)
Addresses a few cases where the current horizontal sprite mosaic
implementation is incorrect:

- Transparent pixels should be affected by horizontal sprite mosaic
effects
- If the latched pixel does not have the sprite mosaic effect enabled,
the next pixel should always be latched
- Mosaic offset should not be reset to 0 at the start of a new
horizontal mosaic effect

Accounting for these cases improves visual accuracy of sprite mosaic
effects. This is easily visible in test cases such as
[sprite-hmosaic.gba](https://github.com/nba-emu/hw-test/tree/master/ppu/sprite-hmosaic),
and [Tonc's mosaic demo](https://www.coranac.com/projects/#tonc)
(mos_demo.gba, from tonc-bin.zip v1.4.2).
2024-02-11 11:08:41 +00:00
Luke Usher 2063393f20 sfc: fix SGB2 clocking issue
This fixes an issue where the SGB2 would reconfigure to SGB1 clock speeds when writing to the control port.
2024-02-06 00:15:49 +00:00
Giovanni Bajo 4c15ed753e n64: fix PI DMA implementation to handle a newly discovered behavior (#1385)
I've lately discovered that misaligned PI DMA is also affected by RDRAM
writes that must cross RDRAM row boundaries (2048 bytes). It is obvious
in retrospect that the DMA implementation cannot generate a write burst
across different rows, so its internal buffering must be segmented on
RDRAM row boundaries.

The PI DMA testsuite has been updated to cover this edge case as well:
https://github.com/rasky/n64_pi_dma_test

Ares now passes the updated testsuite in full (timing aside, which is
close but not perfect).
2024-02-05 16:14:07 +00:00
Luke Usher 704fc91b9c desktop-ui: 'hide until ready' for Windows only 2024-02-01 18:58:50 +00:00
Luke Usher 757d74bac0 desktop-ui: hide window until startup completes
This also avoids calling inode::exists on every recent game at load time, reducing initial wait time.
2024-02-01 18:40:45 +00:00
Luke Usher ff36889d47 desktop-ui: exclude rumble inputs from poll timeout
Fixes rumble issues in many games, notably, OOT
2024-01-29 13:12:06 +00:00
Luke Usher 3d10cd34d6 ruby: differentiate devices in SDL2 driver 2024-01-28 21:00:36 +00:00
Luke Usher acaa1cabb5 ares: border and overscan overhaul
This updates most pre-32bit systems to have hardware accurate borders and
aspect ratio where known, reasonable estimates are used where not known.

This also means that PAL games will use the correct PAL aspect rather than
using the NTSC pixel aspect ratio.

All TMS9918 VDP based consoles are thought to be be correct, as are Master
System, Mega Drive, and NES, while SNES and PC-Engine are (educated)
estimates.

The overscan option has been reworked to crop all non-desirable edges of the
screen, including borders and areas that often contain garbage data, previously
this option only cropped the bottom 16 lines of active display.

The reworked overscan is not yet implemented for Atari 2600, PC-Engine, N64 or
PlayStation.
2024-01-28 15:38:47 +00:00
png183 e8f38d15f7 gba: reset prefetch buffer on ROM accesses from CPU (#1377)
According to NanoBoyAdvance author fleroviux, the prefetch buffer should
be reset on any ROM accesses from the CPU or DMA. This PR implements
this behaviour for CPU ROM accesses. This allows ares to pass 1760/2020
of the timing tests in the mGBA test suite (up from 1608/2020).

Allowing DMA ROM accesses to clear the prefetch buffer seems to worsen
timings at the moment, possibly due to some other timing issues that
also come into effect in these situations. I've left comments to make a
note of the issue, so that resetting the prefetch buffer on ROM accesses
from DMA can be implemented once it becomes beneficial to do so.
2024-01-27 11:47:44 +00:00
Luke Usher 527aaefef1 ruby: use wglSwapInterval rather than DwmFlush
Despite GFWL, and other sources recommending the DwmFlush approach, actual users are setting better results the old way.
2024-01-25 16:11:57 +00:00
Dragorn421 a938ef8aa8 Fixup *nix SDL info and makefile indentation (#1376)
The readme references `libsdl2-0.0` which does not exist:
https://packages.ubuntu.com/search?suite=default&section=all&arch=any&keywords=libsdl2-0.0&searchon=names

I'm not sure it's even useful mentioning `libsdl2-2.0-0` since
`libsdl2-dev` depends on it. Maybe for distributing ares you would then
know people have to install `libsdl2-2.0-0` I guess

Also added a note that SDL2 input allows using a controller on *nix as I
haven't seen that documented anywhere

And I fixed a random makefile indentation issue that messed with my
head.
2024-01-25 13:37:32 +00:00
746 changed files with 182595 additions and 8473 deletions

No files matched your search

+2 -1
View File
@@ -34,7 +34,8 @@ ares supports additional audio drivers besides the ALSA drivers included above.
By default, GTK3 is used, but support for GTK2 is available. You will need to install the additional package `libgtk2.0-dev` as well as specifying the command line option `hiro=gtk2` at compile time.
###### SDL2 for input
If you would like to use SDL for input, you will need to install the following the `libsdl2-dev` and `libsdl2-0.0` packages and perform a clean build of ares. You should then be able to select SDL for input in the Settings > Drivers menu.
If you would like to use SDL for input (e.g. for using a controller), you will need to install the `libsdl2-dev` and `libsdl2-2.0-0` packages and perform a clean build of ares.
You should then be able to select SDL for input in the Settings > Drivers menu.
##### Building with clang
+2
View File
@@ -1,5 +1,7 @@
inline auto CPU::readBus(n16 address) -> n8 {
address &= 0x1fff;
if(auto result = platform->cheat(address)) return *result;
n8 data = cartridge.read(address);
if(address.bit(12) == 0 && address.bit(7) == 0) {
+2 -1
View File
@@ -72,8 +72,9 @@ auto TIA::write(n8 address, n8 data) -> void {
auto TIA::vsync(n1 state) -> void {
if(io.vsync && !state) {
if(!io.vsync && state) {
frame();
tia.io.vcounter = 0;
}
io.vsync = state;
+10 -15
View File
@@ -13,12 +13,12 @@ TIA tia;
auto TIA::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("TIA");
screen = node->append<Node::Video::Screen>("Screen", 160, vlines());
screen = node->append<Node::Video::Screen>("Screen", 180, displayHeight());
screen->colors(1 << 7, {&TIA::color, this});
screen->setSize(160, displayHeight());
screen->setScale(1.0, 1.0);
screen->setAspect(12.0, 7.0);
screen->setViewport(0, 0, 160, displayHeight());
screen->setSize(180, displayHeight());
screen->setScale(2.0, 1.0);
Region::PAL() ? screen->setAspect(38.0, 45.0) : screen->setAspect(4.0, 5.0);
screen->setViewport(0, 0, 180, displayHeight());
stream = node->append<Node::Audio::Stream>("Audio");
stream->setChannels(1);
@@ -46,7 +46,8 @@ auto TIA::scanline() -> void {
for(io.hcounter = 0; io.hcounter < 228; io.hcounter++) {
writeQueue.step();
auto x = io.hcounter - 68;
if(x >= 0 && io.vcounter < vlines()) pixel(x);
auto y = io.vcounter - voffset();
if(x >= 0 && y > 0 && y < vlines()) pixel(x);
runAudio();
step();
if(io.hcounter == 0) cpu.io.rdyLine = 1;
@@ -60,7 +61,9 @@ auto TIA::scanline() -> void {
}
auto TIA::pixel(n8 x) -> void {
auto output = screen->pixels().data() + (io.vcounter * 160);
auto y = io.vcounter - voffset();
auto output = screen->pixels().data() + (y * 180) + 10;
if(y >= displayHeight()) return;
// Output only black during vblank, or for the first 8px of a scanline where hmove was triggered
if (io.vblank || (io.hmoveTriggered == io.vcounter && x < 8)) {
@@ -190,14 +193,6 @@ auto TIA::runBall(n8 x) -> n1 {
}
auto TIA::frame() -> void {
auto height = io.vcounter;
if(height <= 0) {
debug(unusual, "invalid screen height");
height = vlines();
}
screen->setViewport(0, 0, 160, height);
screen->frame();
scheduler.exit(Event::Frame);
}
+2 -1
View File
@@ -4,7 +4,8 @@ struct TIA : Thread {
Node::Audio::Stream stream;
auto vlines() const -> u32 { return Region::NTSC() ? 262 : 312; }
auto displayHeight() const -> u32 { return Region::NTSC() ? 192 : 228; }
auto displayHeight() const -> u32 { return Region::PAL() ? 243 : 228; }
auto voffset() const -> s32 { return Region::PAL() ? 40 : 20; }
//tia.cpp
auto load(Node::Object) -> void;
+1 -1
View File
@@ -23,7 +23,7 @@ auto TIA::WriteQueue::step() -> void {
auto TIA::WriteQueue::QueueItem::commit() -> void {
switch(address) {
case 0x01: tia.io.vblank = data.bit(1); if(tia.io.vblank == 0) { tia.io.vcounter = 0; } return; // VBLANK
case 0x01: tia.io.vblank = data.bit(1); return; // VBLANK
case 0x0b: tia.player[0].reflect = data.bit(3); return; // REFP0
case 0x0c: tia.player[1].reflect = data.bit(3); return; // REFP1
case 0x0d: tia.playfield.graphics.bit(0, 3) = data.bit(4, 7); return; // PF0
+1 -1
View File
@@ -10,7 +10,7 @@ Platform* platform = nullptr;
bool _runAhead = false;
const string Name = "ares";
const string Version = "135";
const string Version = "136";
const string Copyright = "ares team, Near";
const string License = "ISC";
const string LicenseURI = "https://opensource.org/licenses/ISC";
+5
View File
@@ -76,6 +76,11 @@ auto Screen::setViewport(u32 x, u32 y, u32 width, u32 height) -> void {
_viewportHeight = height;
}
auto Screen::setOverscan(bool overscan) -> void {
lock_guard<recursive_mutex> lock(_mutex);
_overscan = overscan;
}
auto Screen::setSize(u32 width, u32 height) -> void {
lock_guard<recursive_mutex> lock(_mutex);
_width = width;
+3
View File
@@ -16,6 +16,7 @@ struct Screen : Video {
auto scaleY() const -> f64 { return _scaleY; }
auto aspectX() const -> f64 { return _aspectX; }
auto aspectY() const -> f64 { return _aspectY; }
auto overscan() const -> bool { return _overscan; }
auto colors() const -> u32 { return _colors; }
auto pixels(bool frame = 0) -> array_span<u32>;
@@ -37,6 +38,7 @@ struct Screen : Video {
auto setSize(u32 width, u32 height) -> void;
auto setScale(f64 scaleX, f64 scaleY) -> void;
auto setAspect(f64 aspectX, f64 aspectY) -> void;
auto setOverscan(bool overscan) -> void;
auto setSaturation(f64 saturation) -> void;
auto setGamma(f64 gamma) -> void;
@@ -81,6 +83,7 @@ protected:
bool _colorBleed = false;
bool _colorBleedWidth = 1;
bool _interframeBlending = false;
bool _overscan = true;
u32 _rotation = 0; //counter-clockwise (90 = left, 270 = right)
function<n64 (n32)> _color;
+1
View File
@@ -20,6 +20,7 @@ struct Platform {
virtual auto video(Node::Video::Screen, const u32* data, u32 pitch, u32 width, u32 height) -> void {}
virtual auto audio(Node::Audio::Stream) -> void {}
virtual auto input(Node::Input::Input) -> void {}
virtual auto cheat(u32 addr) -> maybe<u32> { return nothing; }
};
extern Platform* platform;
+1 -4
View File
@@ -14,10 +14,7 @@ auto TMS9918::Background::run(n8 hoffset, n8 voffset) -> void {
}
auto TMS9918::Background::text1(n8 hoffset, n8 voffset) -> void {
// Offset by 6px and blank the last 10px to centre the display
i32 x = hoffset - 6;
if (x < 0) return;
if (hoffset > 246) return;
i32 x = hoffset;
n14 nameAddress = (voffset.bit(3,7) * 40) + (x / 6);
nameAddress.bit(10,13) = io.nameTableAddress;
+5 -3
View File
@@ -1,5 +1,7 @@
auto TMS9918::DAC::setup(n8 y) -> void {
output = self.screen->pixels().data() + y * 256;
auto TMS9918::DAC::setup(n9 y) -> void {
y = (y + 27) % 243;
output = self.screen->pixels().data() + y * 284;
for (u32 x : range(284)) output[x] = io.colorBackground;
}
auto TMS9918::DAC::run(n8 x, n8 y) -> void {
@@ -8,7 +10,7 @@ auto TMS9918::DAC::run(n8 x, n8 y) -> void {
if(self.background.output.color) color = self.background.output.color;
if(self.sprite.output.color) color = self.sprite.output.color;
}
output[x] = color;
output[(x + 13) % 284] = color;
}
auto TMS9918::DAC::power() -> void {
+1 -1
View File
@@ -18,11 +18,11 @@ auto TMS9918::unload() -> void {
}
auto TMS9918::main() -> void {
dac.setup(io.vcounter);
if(io.vcounter < 192) {
n8 y = io.vcounter;
background.setup(y);
sprite.setup(y);
dac.setup(y);
for(n8 x : range(256)) {
background.run(x, y);
sprite.run(x, y);
+1 -1
View File
@@ -101,7 +101,7 @@ protected:
TMS9918& self;
//dac.cpp
auto setup(n8 voffset) -> void;
auto setup(n9 voffset) -> void;
auto run(n8 hoffset, n8 voffset) -> void;
auto power() -> void;
+1 -4
View File
@@ -20,10 +20,7 @@ auto V9938::Background::run(n8 x, n8 y) -> void {
}
auto V9938::Background::text1(n8 hoffset, n8 voffset) -> void {
// Offset by 6px and blank the last 10px to centre the display
i32 x = hoffset - 6;
if (x < 0) return;
if (hoffset > 246) return;
i32 x = hoffset;
n14 nameAddress = (voffset.bit(3,7) * 40) + (x / 6);
nameAddress.bit(10,16) = io.nameTableAddress.bit(10,16);
+7 -4
View File
@@ -1,7 +1,10 @@
auto V9938::DAC::setup(n8 voffset) -> void {
n9 vcenter = voffset + (!self.overscan() ? 10 : 0); //centers output within buffer
output = self.screen->pixels().data() + vcenter * 1024;
if(self.interlace() && self.field()) output += 512;
auto V9938::DAC::setup(n9 voffset) -> void {
//TODO: Verify border in overscan mode
voffset = (voffset + (self.latch.overscan ? 13 : 27)) % 243;
output = self.screen->pixels().data() + voffset * 1136;
if(self.interlace() && self.field()) output += 568;
for(auto n: range(568)) output[n] = self.pram[self.io.colorBackground];
output += 26;
}
auto V9938::DAC::run(n8 hoffset, n8 voffset) -> void {
+1 -1
View File
@@ -19,12 +19,12 @@ auto V9938::unload() -> void {
}
auto V9938::main() -> void {
dac.setup(io.vcounter);
if(io.vcounter < vlines()) {
n9 y = io.vcounter;
latch.vscroll = io.vscroll;
background.setup(y);
sprite.setup(y + latch.vscroll);
dac.setup(y);
while(io.hcounter < 256) {
n9 x = io.hcounter;
+1 -1
View File
@@ -154,7 +154,7 @@ protected:
V9938& self;
//dac.cpp
auto setup(n8 voffset) -> void;
auto setup(n9 voffset) -> void;
auto run(n8 hoffset, n8 voffset) -> void;
auto power() -> void;
+1
View File
@@ -1,5 +1,6 @@
auto CPU::read(n16 address) -> n8 {
n8 data = 0xff;
if(auto result = platform->cheat(address)) return *result;
if(address >= 0x0000 && address <= 0x1fff && io.replaceBIOS) return expansion.read(address);
if(address >= 0x2000 && address <= 0x7fff && io.replaceRAM ) return expansion.read(address);
if(address >= 0x0000 && address <= 0x1fff) return system.bios[address & 0x1fff];
+16 -3
View File
@@ -11,12 +11,12 @@ auto VDP::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("VDP");
screen = node->append<Node::Video::Screen>("Screen", 256, 192);
screen = node->append<Node::Video::Screen>("Screen", 284, 243);
screen->colors(1 << 4, {&VDP::color, this});
screen->setSize(256, 192);
screen->setSize(284, 243);
screen->setScale(1.0, 1.0);
screen->setAspect(8.0, 7.0);
screen->setViewport(0, 0, 256, 192);
screen->setViewport(0, 0, 284, 243);
TMS9918::load(screen);
}
@@ -39,6 +39,19 @@ auto VDP::irq(bool line) -> void {
}
auto VDP::frame() -> void {
if(screen->overscan()) {
screen->setSize(284, 243);
screen->setViewport(0, 0, 284, 243);
} else {
int x = 16;
int y = 16;
int width = 284 - 32;
int height = 243 - 32;
screen->setSize(width, height);
screen->setViewport(x, y, width, height);
}
screen->frame();
scheduler.exit(Event::Frame);
}
+1
View File
@@ -7,6 +7,7 @@
//$4018-ffff = Cartridge
inline auto CPU::readBus(n16 address) -> n8 {
if(auto result = platform->cheat(address)) return *result;
n8 data = cartridge.readPRG(address, MDR);
if(address <= 0x1fff) return ram.read(address);
if(address <= 0x3fff) return ppu.readIO(address);
+19 -12
View File
@@ -16,17 +16,11 @@ auto PPU::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("PPU");
screen = node->append<Node::Video::Screen>("Screen", 256, 240);
screen = node->append<Node::Video::Screen>("Screen", 282, displayHeight());
screen->colors(1 << 9, {&PPU::color, this});
screen->setSize(256, 240);
screen->setSize(282, displayHeight());
screen->setScale(1.0, 1.0);
screen->setAspect(8.0, 7.0);
overscan = screen->append<Node::Setting::Boolean>("Overscan", true, [&](auto value) {
if(value == 0) screen->setSize(256, 224);
if(value == 1) screen->setSize(256, 240);
});
overscan->setDynamic(true);
Region::PAL() ? screen->setAspect(55.0, 43.0) :screen->setAspect(8.0, 7.0);
debugger.load(node);
}
@@ -35,7 +29,6 @@ auto PPU::unload() -> void {
screen->quit();
node->remove(screen);
debugger.unload();
overscan.reset();
screen.reset();
node.reset();
ciram.reset();
@@ -80,8 +73,22 @@ auto PPU::scanline() -> void {
auto PPU::frame() -> void {
io.field++;
if(overscan->value() == 0) screen->setViewport(0, 8, 256, 224);
if(overscan->value() == 1) screen->setViewport(0, 0, 256, 240);
if(screen->overscan()) {
screen->setSize(282, displayHeight());
screen->setViewport(0, 0, 282, displayHeight());
} else {
int x = 16;
int y = 8;
int width = 282 - 32;
int height = displayHeight() - 16;
if(Region::PAL()) height -= 48;
screen->setSize(width, height);
screen->setViewport(x, y, width, height);
}
screen->frame();
scheduler.exit(Event::Frame);
}
+3 -1
View File
@@ -1,7 +1,6 @@
struct PPU : Thread {
Node::Object node;
Node::Video::Screen screen;
Node::Setting::Boolean overscan;
Memory::Writable<n8> ciram;
Memory::Writable<n8> cgram;
Memory::Writable<n8> oam;
@@ -20,6 +19,7 @@ struct PPU : Thread {
auto rate() const -> u32 { return Region::PAL() ? 5 : 4; }
auto vlines() const -> u32 { return Region::PAL() ? 312 : 262; }
auto displayHeight() const -> u32 { return Region::PAL() ? 288 : 242; }
//ppu.cpp
auto load(Node::Object) -> void;
@@ -133,6 +133,8 @@ struct PPU : Thread {
OAM oam[8]; //primary
OAM soam[8]; //secondary
} latch;
u32* output;
};
extern PPU ppu;
+15 -3
View File
@@ -7,9 +7,7 @@ auto PPU::loadCHR(n16 address) -> n8 {
}
auto PPU::renderPixel() -> void {
//todo: renderPixel() is called when Y=261 ... why?
if(io.ly >= screen->canvasHeight()) return;
auto output = screen->pixels().data() + io.ly * 256;
u32 x = io.lx - 1;
u32 mask = 0x8000 >> (io.v.fineX + (x & 7));
@@ -17,6 +15,9 @@ auto PPU::renderPixel() -> void {
u32 objectPalette = 0;
bool objectPriority = 0;
//PAL systems blank the topmost scanline and the first and last 2px of active display
if(Region::PAL()) if(io.ly == 0 || x < 2 || x > 254) return;
palette |= latch.tiledataLo & mask ? 1 : 0;
palette |= latch.tiledataHi & mask ? 2 : 0;
if(palette) {
@@ -55,7 +56,13 @@ auto PPU::renderPixel() -> void {
}
if(!enable()) palette = 0;
output[x] = io.emphasis << 6 | readCGRAM(palette);
if(Region::PAL()) {
output[(x + 18) % 282] = io.emphasis << 6 | readCGRAM(palette);
return;
}
output[(x + 15) % 282] = io.emphasis << 6 | readCGRAM(palette);
}
auto PPU::renderSprite() -> void {
@@ -80,6 +87,11 @@ auto PPU::renderSprite() -> void {
}
auto PPU::renderScanline() -> void {
if(io.ly < screen->canvasHeight()) {
output = screen->pixels().data() + io.ly * 282;
for(auto n : range(282)) output[n] = Region::PAL() ? 0x3f : (io.emphasis << 6 | readCGRAM(0));
}
//Vblank
if(io.ly >= 240 && io.ly <= vlines() - 2) return step(341), scanline();
+1
View File
@@ -5,6 +5,7 @@ namespace ares::GameBoy {
Bus bus;
auto Bus::read(u32 cycle, n16 address, n8 data) -> n8 {
if(auto result = platform->cheat(address)) return *result;
data &= cpu.readIO(cycle, address, data);
data &= apu.readIO(cycle, address, data);
data &= ppu.readIO(cycle, address, data);
+3 -2
View File
@@ -15,7 +15,7 @@ auto CPU::get(u32 mode, n32 address) -> n32 {
word = prefetchRead();
if(mode & Word) word |= prefetchRead() << 16;
} else {
if(!context.dmaActive) prefetchWait();
if(!context.dmaActive) prefetchReset(); //todo: Prefetch buffer should also be reset when DMA accesses ROM
step(clocks - 1);
word = cartridge.read(mode, address);
step(1);
@@ -23,6 +23,7 @@ auto CPU::get(u32 mode, n32 address) -> n32 {
} else {
if(memory.biosSwap && address < 0x0400'0000) address ^= 0x0200'0000;
prefetchStep(clocks);
if(auto result = platform->cheat(address)) return *result;
if(address < 0x0200'0000) word = bios.read(mode, address);
else if(address < 0x0300'0000) word = readEWRAM(mode, address);
else if(address < 0x0400'0000) word = readIWRAM(mode, address);
@@ -42,7 +43,7 @@ auto CPU::set(u32 mode, n32 address, n32 word) -> void {
if(address >= 0x1000'0000) {
prefetchStep(clocks);
} else if(address & 0x0800'0000) {
if(!context.dmaActive) prefetchWait();
if(!context.dmaActive) prefetchReset(); //todo: Prefetch buffer should also be reset when DMA accesses ROM
step(clocks);
cartridge.write(mode, address, word);
} else {
+1 -1
View File
@@ -52,7 +52,7 @@ struct CPU : ARM7TDMI, Thread, IO {
//prefetch.cpp
auto prefetchSync(n32 address) -> void;
auto prefetchStep(u32 clocks) -> void;
auto prefetchWait() -> void;
auto prefetchReset() -> void;
auto prefetchRead() -> n16;
//bus.cpp
+2 -1
View File
@@ -309,10 +309,11 @@ auto CPU::writeIO(n32 address, n8 data) -> void {
bool enable = timer().enable;
timer().frequency = data.bit(0,1);
timer().cascade = data.bit(2);
timer().irq = data.bit(6);
timer().enable = data.bit(7);
if(address != 0x0400'0102) timer().cascade = data.bit(2);
if(!enable && timer().enable) { //0->1 transition
timer().pending = true;
}
+3 -5
View File
@@ -18,11 +18,9 @@ auto CPU::prefetchStep(u32 clocks) -> void {
}
}
auto CPU::prefetchWait() -> void {
if(!wait.prefetch || context.dmaActive || prefetch.full()) return;
prefetchStep(prefetch.wait);
prefetch.wait = _wait(Half | Nonsequential, prefetch.load);
auto CPU::prefetchReset() -> void {
prefetch.addr = 0;
prefetch.load = 0;
}
auto CPU::prefetchRead() -> n16 {
+6 -3
View File
@@ -56,12 +56,12 @@ auto PPU::Objects::scanline(u32 y) -> void {
}
} else if(!buffer[bx].enable || object.priority < buffer[bx].priority) {
buffer[bx].priority = object.priority; //updated regardless of transparency
buffer[bx].mosaic = object.mosaic; //updated regardless of transparency
if(color) {
if(object.colors == 0) color = object.palette * 16 + color;
buffer[bx].enable = true;
buffer[bx].color = ppu.pram[256 + color];
buffer[bx].translucent = object.mode == 1;
buffer[bx].mosaic = object.mosaic;
}
}
}
@@ -80,12 +80,15 @@ auto PPU::Objects::run(u32 x, u32 y) -> void {
}
output = buffer[x];
//horizontal mosaic
if(!output.mosaic || ++mosaicOffset >= 1 + io.mosaicWidth) {
if(mosaicOffset >= 1 + io.mosaicWidth) {
mosaicOffset = 0;
mosaic = output;
} else if(!mosaic.mosaic || !output.mosaic) {
mosaic = output;
}
mosaicOffset++;
}
auto PPU::Objects::power() -> void {
+1
View File
@@ -1,4 +1,5 @@
alwaysinline auto Bus::read(n1 upper, n1 lower, n24 address, n16 data) -> n16 {
if(auto result = platform->cheat(address)) return *result;
if(address >= 0x000000 && address <= 0x3fffff) {
waitRefreshExternal();
if(!cpu.io.romEnable) {
+29
View File
@@ -60,6 +60,7 @@ auto MCD::CDD::advance() -> void {
io.track = track();
io.sector++;
io.sample = 0;
readSubcode();
return;
}
@@ -93,6 +94,34 @@ auto MCD::CDD::position(s32 sector) -> double {
return sqrt(sector / sectors * (outerRadius - innerRadius) + innerRadius) / radius;
}
auto MCD::CDD::readSubcode() -> void {
if(!mcd.fd) return;
mcd.fd->seek(((abs(mcd.cdd.session.leadIn.lba) + io.sector) * 2448) + 2352);
vector<u8> subchannel;
subchannel.resize(96);
mcd.fd->read({subchannel.data(), 96});
io.subcodePosition += 49;
n6 index = io.subcodePosition;
//subchannel data is stored by ares as 12x8 for each channel (PQRSTUVW)
//we need to convert this back to the raw subchannel encoding as per EMCA-130
for(int i = 0; i < 96; i+= 2) {
n16 data = 0;
for(auto channel : range(8)) {
n8 input = subchannel[(channel * 12) + (i / 8)];
n2 bits = input >> (6 - (i & 6));
data.bit( 7 - channel) = bits.bit(0);
data.bit(15 - channel) = bits.bit(1);
}
subcode[index++] = data;
}
mcd.irq.subcode.raise();
}
auto MCD::CDD::process() -> void {
//if(command[0]) print("CDD ", command[0], ":", command[3], "\n");
+9
View File
@@ -26,6 +26,15 @@ auto MCD::Debugger::load(Node::Object parent) -> void {
mcd.bram[address] = data;
});
memory.pcmRam = parent->append<Node::Debugger::Memory>("CD PCM RAM");
memory.pcmRam->setSize(64_KiB);
memory.pcmRam->setRead([&](u32 address) -> u8 {
return mcd.pcm.ram[address];
});
memory.bram->setWrite([&](u32 address, u8 data) -> void {
mcd.pcm.ram[address] = data;
});
tracer.instruction = parent->append<Node::Debugger::Tracer::Instruction>("Instruction", "MCD");
tracer.instruction->setAddressBits(24);
tracer.instruction->setDepth(16);
+8 -1
View File
@@ -176,8 +176,15 @@ auto MCD::readIO(n1 upper, n1 lower, n24 address, n16 data) -> n16 {
data.bit(1,15) = gpu.vector.base.bit(3,17);
}
if(address == 0xff8068) {
data.bit(0) = Unmapped;
data.bit(1,7) = cdd.io.subcodePosition.bit(0,6);
data.bit(8,15) = Unmapped;
}
if(address >= 0xff8100 && address <= 0xff81ff) {
debug(unusual, "[MCD::readIO] address=0x", hex(address, 6L));
n6 index = address - 0xff8100 >> 1;
data = cdd.subcode[index];
}
return data;
+4
View File
@@ -21,6 +21,7 @@ struct MCD : M68000, Thread {
Node::Debugger::Memory pram;
Node::Debugger::Memory wram;
Node::Debugger::Memory bram;
Node::Debugger::Memory pcmRam;
} memory;
struct Tracer {
@@ -306,6 +307,7 @@ struct MCD : M68000, Thread {
auto sample() -> void;
auto position(s32 sector) -> double;
auto process() -> void;
auto readSubcode() -> void;
auto valid() -> bool;
auto checksum() -> void;
auto insert() -> void;
@@ -344,12 +346,14 @@ struct MCD : M68000, Thread {
n16 sample; //current audio sample# within current frame
n7 track; //current track#
n1 tocRead;
n6 subcodePosition;
} io;
n1 hostClockEnable;
n1 statusPending;
n4 status [10];
n4 command[10];
n16 subcode[64];
} cdd;
struct Timer {
+2
View File
@@ -148,11 +148,13 @@ auto MCD::CDD::serialize(serializer& s) -> void {
s(io.sample);
s(io.track);
s(io.tocRead);
s(io.subcodePosition);
s(hostClockEnable);
s(statusPending);
s(status);
s(command);
s(subcode);
}
auto MCD::Timer::serialize(serializer& s) -> void {
+1 -1
View File
@@ -1,4 +1,4 @@
static const string SerializerVersion = "v134";
static const string SerializerVersion = "v135";
auto System::serialize(bool synchronize) -> serializer {
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
+3 -11
View File
@@ -1,19 +1,11 @@
auto VDP::pixels() -> u32* {
u32* output = nullptr;
if(overscan->value() == 0 && io.overscan == 0) {
if(state.vcounter >= 224) return nullptr;
output = screen->pixels().data() + (state.vcounter - 0) * 2 * 1280;
}
if(overscan->value() == 0 && io.overscan == 1) {
if(state.vcounter <= 7) return nullptr;
if(state.vcounter >= 232) return nullptr;
output = screen->pixels().data() + (state.vcounter - 8) * 2 * 1280;
}
if(overscan->value() == 1 && io.overscan == 0) {
if(io.overscan == 0) {
if(state.vcounter >= 232) return nullptr;
output = screen->pixels().data() + (state.vcounter + 8) * 2 * 1280;
}
if(overscan->value() == 1 && io.overscan == 1) {
if(io.overscan == 1) {
output = screen->pixels().data() + (state.vcounter + 0) * 2 * 1280;
}
if(latch.interlace) output += state.field * 1280;
-7
View File
@@ -26,12 +26,6 @@ auto VDP::load(Node::Object parent) -> void {
screen->setScale(0.25, 0.5);
screen->setAspect(32, 35);
overscan = screen->append<Node::Setting::Boolean>("Overscan", true, [&](auto value) {
if(value == 0) screen->setSize(1280, 448);
if(value == 1) screen->setSize(1280, 480);
});
overscan->setDynamic(true);
psg.load(node);
debugger.load(node);
}
@@ -39,7 +33,6 @@ auto VDP::load(Node::Object parent) -> void {
auto VDP::unload() -> void {
debugger = {};
psg.unload();
overscan.reset();
screen->quit();
node->remove(screen);
screen.reset();
-1
View File
@@ -2,7 +2,6 @@
struct VDP : Thread {
Node::Object node;
Node::Video::Screen screen;
Node::Setting::Boolean overscan;
struct Debugger {
//debugger.cpp
+51 -35
View File
@@ -34,17 +34,11 @@ VDP vdp;
auto VDP::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("VDP");
screen = node->append<Node::Video::Screen>("Screen", 1280, 480);
screen = node->append<Node::Video::Screen>("Screen", 1388, visibleHeight() * 2);
screen->colors(1 << 16, {&VDP::color, this});
screen->setSize(1280, 480);
screen->setSize(1388, visibleHeight() * 2);
screen->setScale(0.25, 0.5);
screen->setAspect(32, 35);
overscan = screen->append<Node::Setting::Boolean>("Overscan", true, [&](auto value) {
if(value == 0) screen->setSize(1280, 448);
if(value == 1) screen->setSize(1280, 480);
});
overscan->setDynamic(true);
Region::PAL() ? screen->setAspect(111.0, 100.0) : screen->setAspect(32.0, 35.0);
psg.load(node);
debugger.load(node);
@@ -55,7 +49,6 @@ auto VDP::load(Node::Object parent) -> void {
auto VDP::unload() -> void {
debugger.unload();
psg.unload();
overscan.reset();
screen->quit();
node->remove(screen);
screen.reset();
@@ -63,37 +56,60 @@ auto VDP::unload() -> void {
}
auto VDP::pixels() -> u32* {
//TODO: vcounter values of top border may not be correct here
u32* output = nullptr;
if(overscan->value() == 0 && latch.overscan == 0) {
if(vcounter() >= 224) return nullptr;
output = screen->pixels().data() + (vcounter() - 0) * 2 * 1280;
}
if(overscan->value() == 0 && latch.overscan == 1) {
if(vcounter() <= 7) return nullptr;
if(vcounter() >= 232) return nullptr;
output = screen->pixels().data() + (vcounter() - 8) * 2 * 1280;
}
if(overscan->value() == 1 && latch.overscan == 0) {
if(vcounter() >= 0x1f8) { // top border
output = screen->pixels().data() + (vcounter() - 0x1f8) * 2 * 1280;
} else if(vcounter() >= 232) {
return nullptr;
} else {
output = screen->pixels().data() + (vcounter() + 8) * 2 * 1280;
}
}
if(overscan->value() == 1 && latch.overscan == 1) {
if(vcounter() >= 240) return nullptr;
output = screen->pixels().data() + (vcounter() + 0) * 2 * 1280;
}
if(latch.interlace) output += field() * 1280;
return output;
if(Region::NTSC() && vcounter() >= 0x1ed) return nullptr;
if(Region::PAL() && vcounter() >= 0x1f0) return nullptr;
//account for vcounter jumps during blanking periods
n9 y = vcounter();
if(Region::NTSC() && v28() && vcounter() >= 0x1e5) y -= 250;
if(Region::PAL() && v28() && vcounter() >= 0x1ca) y -= 201;
if(v30() && Region::PAL() && vcounter() >= 0x1d2) y -= 201;
auto offset = Region::PAL() ? 38 : 11;
if(latch.overscan) offset -= 8;
y = (y + offset) % visibleHeight();
output = screen->pixels().data() + y * 2 * 1388;
if(latch.interlace) output += field() * 1388;
//TODO: this should probably be handled in DAC
n32 bg = 1 << 11 | 1 << 9 | cram.color(io.backgroundColor);
for(auto n: range(1388)) output[n] = bg;
return output + 52;
}
auto VDP::frame() -> void {
if(latch.interlace == 0) screen->setProgressive(1);
if(latch.interlace == 1) screen->setInterlace(field());
screen->setViewport(0, 0, screen->width(), screen->height());
if(screen->overscan()) {
screen->setSize(1388, visibleHeight() * 2);
screen->setViewport(0, 0, screen->width(), screen->height());
} else {
int x = 14 * 4;
int y = 12 * 2;
int width = 1388 - (28 * 4);
int height = (visibleHeight() * 2) - (24 * 2);
if(Region::PAL()) {
y += 28 * 2;
height -= 48 * 2;
if(v30()) {
y -= 8 * 2;
height += 16 * 2;
}
}
screen->setSize(width, height);
screen->setViewport(x, y, width, height);
}
screen->frame();
scheduler.exit(Event::Frame);
}
+1 -1
View File
@@ -5,7 +5,6 @@
struct VDP : Thread {
Node::Object node;
Node::Video::Screen screen;
Node::Setting::Boolean overscan;
struct Debugger {
VDP& self;
@@ -53,6 +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() ? 312 : 262; }
auto visibleHeight() const -> u32 { return Region::PAL() ? 294 : 243; }
//vdp.cpp
auto load(Node::Object) -> void;
+1
View File
@@ -3,6 +3,7 @@ auto CPU::mdr() const -> n8 {
}
auto CPU::read(n16 address) -> n8 {
if(auto result = platform->cheat(address)) return *result;
n8 data = mdr();
if(address >= 0xc000 && bus.ramEnable) data = ram.read(address);
if(Device::MasterSystem() && bus.biosEnable) data = bios.read(address, data);
+7 -4
View File
@@ -1,6 +1,9 @@
auto VDP::DAC::setup(n8 y) -> void {
self.screen->setFillColor(palette(16 | io.backdropColor));
output = self.screen->pixels().data() + ((240-self.vlines())/2 + y) * 256;
auto VDP::DAC::setup(n9 y) -> void {
auto offset = Region::PAL() ? 48 : 27;
y = (y + offset) % vdp.screenHeight();
output = self.screen->pixels().data() + y * 284;
for (u32 x : range(284)) output[x] = palette(16 | io.backdropColor);
}
auto VDP::DAC::run(n8 x, n8 y) -> void {
@@ -15,7 +18,7 @@ auto VDP::DAC::run(n8 x, n8 y) -> void {
}
}
output[x] = color;
output[(x + 13) % 284] = color;
}
auto VDP::DAC::palette(n5 index) -> n12 {
+25 -18
View File
@@ -18,7 +18,7 @@ auto VDP::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("VDP");
screen = node->append<Node::Video::Screen>("Screen", 256, 240);
screen = node->append<Node::Video::Screen>("Screen", 284, screenHeight());
u32 defaultRevision = 2;
if((Model::MarkIII() || Model::MasterSystemI()) && Region::NTSCJ()) defaultRevision = 1;
@@ -27,15 +27,9 @@ auto VDP::load(Node::Object parent) -> void {
if(Display::CRT()) {
screen->colors(1 << 6, {&VDP::colorMasterSystem, this});
screen->setSize(256, 240);
screen->setSize(284, screenHeight());
screen->setScale(1.0, 1.0);
screen->setAspect(8.0, 7.0);
overscan = screen->append<Node::Setting::Boolean>("Overscan", true, [&](auto value) {
if(value == 0) screen->setSize(256, vlines());
if(value == 1) screen->setSize(256, 240);
});
overscan->setDynamic(true);
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();
@@ -48,7 +42,7 @@ auto VDP::load(Node::Object parent) -> void {
screen->setSize(160, 144);
screen->setScale(1.0, 1.0);
screen->setAspect(6.0, 5.0);
screen->setViewport(0, 0, 256, 240);
screen->setViewport(0, 0, 284, screenHeight());
interframeBlending = screen->append<Node::Setting::Boolean>("Interframe Blending", true, [&](auto value) {
screen->setInterframeBlending(value);
@@ -65,7 +59,6 @@ auto VDP::unload() -> void {
revision.reset();
screen->quit();
node->remove(screen);
overscan.reset();
screen.reset();
node.reset();
vram.reset();
@@ -89,11 +82,11 @@ auto VDP::main() -> void {
}
//684 clocks/scanline
dac.setup(io.vcounter);
if(io.vcounter < vlines()) {
u8 y = io.vcounter;
background.setup(y);
sprite.setup(y);
dac.setup(y);
for(u8 x : range(256)) {
background.run(x, y);
sprite.run(x, y);
@@ -108,13 +101,28 @@ auto VDP::main() -> void {
if(io.vcounter == 240) {
if(Display::CRT()) {
if(overscan->value() == 0) screen->setViewport(0, (240-vlines())/2, 256, vlines());
if(overscan->value() == 1) screen->setViewport(0, 0, 256, 240);
if(screen->overscan()) {
screen->setSize(284, screenHeight());
screen->setViewport(0, 0, 284, screenHeight());
} else {
int x = 16;
int y = 24;
int width = 284 - 32;
int height = screenHeight() - 48;
if(Region::PAL()) {
y += 24;
height -= 48;
}
screen->setSize(width, height);
screen->setViewport(x, y, width, height);
}
} else if (Mode::MasterSystem()) {
screen->setViewport(0, (240-vlines())/2, 256, vlines());
screen->setViewport(21, 27, 248, 200);
}
if(Mode::GameGear()) {
screen->setViewport(48, 48, 160, 144);
screen->setViewport(61, 51, 160, 144);
}
screen->frame();
scheduler.exit(Event::Frame);
@@ -142,8 +150,7 @@ auto VDP::step(u32 clocks) -> void {
auto VDP::updateScreenSize() -> void {
if(Display::CRT()) {
if(overscan->value() == 0) screen->setSize(256, vlines());
if(overscan->value() == 1) screen->setSize(256, 240);
screen->setSize(284, screenHeight());
}
//if(Display::LCD()) {
// screen->setSize(160, 144);
+2 -2
View File
@@ -5,7 +5,6 @@
struct VDP : Thread {
Node::Object node;
Node::Video::Screen screen;
Node::Setting::Boolean overscan;
Node::Setting::Boolean colorEmulation;
Node::Setting::Natural revision;
Node::Setting::Boolean interframeBlending; //Game Gear only
@@ -40,6 +39,7 @@ struct VDP : Thread {
auto step(u32 clocks) -> void;
auto updateScreenSize() -> void;
auto screenHeight() const -> u32 { return Region::PAL() ? 288 : 243; }
auto vlines() -> u32;
auto vblank() -> bool;
@@ -137,7 +137,7 @@ struct VDP : Thread {
VDP& self;
//dac.cpp
auto setup(n8 voffset) -> void;
auto setup(n9 voffset) -> void;
auto run(n8 hoffset, n8 voffset) -> void;
auto palette(n5 index) -> n12;
auto power() -> void;
+1
View File
@@ -1,4 +1,5 @@
auto CPU::read(n16 address) -> n8 {
if(auto result = platform->cheat(address)) return *result;
n2 page = address.bit(14,15);
n2 primary = slot[page].primary;
+22 -7
View File
@@ -10,9 +10,9 @@ auto VDP::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("VDP");
if(Model::MSX()) {
screen = node->append<Node::Video::Screen>("Screen", 256, 192);
screen = node->append<Node::Video::Screen>("Screen", 284, 243);
screen->colors(1 << 4, {&VDP::colorMSX, this});
screen->setSize(256, 192);
screen->setSize(284, 243);
screen->setScale(1.0, 1.0);
screen->setAspect(8.0, 7.0);
TMS9918::vram.allocate(16_KiB, 0x00);
@@ -20,9 +20,9 @@ auto VDP::load(Node::Object parent) -> void {
}
if(Model::MSX2()) {
screen = node->append<Node::Video::Screen>("Screen", 512, 424);
screen = node->append<Node::Video::Screen>("Screen", 568, 486);
screen->colors(1 << 9, {&VDP::colorMSX2, this});
screen->setSize(512, 424);
screen->setSize(568, 486);
screen->setScale(0.5, 0.5);
screen->setAspect(8.0, 7.0);
V9938::vram.allocate(128_KiB, 0x00);
@@ -51,16 +51,33 @@ auto VDP::irq(bool line) -> void {
}
auto VDP::frame() -> void {
int screenScale = Model::MSX2() ? 2 : 1;
if(Model::MSX2()) {
if(V9938::interlace() == 0) screen->setProgressive(1);
if(V9938::interlace() == 1) screen->setInterlace(V9938::field());
}
screen->setViewport(0, 0, screen->width(), screen->height());
if(screen->overscan()) {
screen->setSize(284 * screenScale, 243 * screenScale);
screen->setViewport(0, 0, 284 * screenScale, 243 * screenScale);
} else {
int x = 16;
int y = 16;
int width = 284 - 32;
int height = 243 - 32;
screen->setSize(width * screenScale, height * screenScale);
screen->setViewport(x * screenScale, y * screenScale, width * screenScale, height * screenScale);
}
screen->frame();
scheduler.exit(Event::Frame);
}
auto VDP::power() -> void {
screen->power();
if(Model::MSX()) {
TMS9918::power();
Thread::create(system.colorburst() * 2, [&] { TMS9918::main(); });
@@ -70,8 +87,6 @@ auto VDP::power() -> void {
V9938::power();
Thread::create(system.colorburst() * 2, [&] { V9938::main(); });
}
screen->power();
}
/* Z80 I/O ports 0x98 - 0x9b */
+1
View File
@@ -1,4 +1,5 @@
auto CPU::read(n16 address) -> n8 {
if(auto result = platform->cheat(address)) return *result;
// VIDEO
if (address == 0xe000) return vdp.data();
+17 -4
View File
@@ -11,12 +11,12 @@ auto VDP::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("VDP");
screen = node->append<Node::Video::Screen>("Screen", 256, 192);
screen = node->append<Node::Video::Screen>("Screen", 284, 243);
screen->colors(1 << 4, {&VDP::color, this});
screen->setSize(256, 192);
screen->setViewport(0, 0, 256, 192);
screen->setSize(284, 243);
screen->setViewport(0, 0, 284, 243);
screen->setScale(1.0, 1.0);
screen->setAspect(1.0, 1.0);
screen->setAspect(8.0, 7.0);
TMS9918::load(screen);
}
@@ -39,6 +39,19 @@ auto VDP::irq(bool line) -> void {
}
auto VDP::frame() -> void {
if(screen->overscan()) {
screen->setSize(284, 243);
screen->setViewport(0, 0, 284, 243);
} else {
int x = 16;
int y = 16;
int width = 284 - 32;
int height = 243 - 32;
screen->setSize(width, height);
screen->setViewport(x, y, width, height);
}
screen->frame();
scheduler.exit(Event::Frame);
}
+4 -2
View File
@@ -700,8 +700,10 @@ struct CPU : Thread {
auto fpeInvalidOperation() -> bool;
auto fpeUnimplemented() -> bool;
auto fpuCheckStart() -> bool;
auto fpuCheckInput(f32& f) -> bool;
auto fpuCheckInput(f64& f) -> bool;
template <typename T>
auto fpuCheckInput(T& f) -> bool;
template <typename T>
auto fpuCheckInputs(T& f1, T& f2) -> bool;
auto fpuCheckOutput(f32& f) -> bool;
auto fpuCheckOutput(f64& f) -> bool;
auto fpuClearCause() -> void;
+23 -25
View File
@@ -281,7 +281,9 @@ auto CPU::fpuCheckStart() -> bool {
return true;
}
auto CPU::fpuCheckInput(f32& f) -> bool {
template <typename T>
auto CPU::fpuCheckInput(T& f) -> bool {
static_assert(std::is_same_v<T, f32> || std::is_same_v<T, f64>);
switch (fpclassify(f)) {
case FP_SUBNORMAL:
if(fpeUnimplemented()) return exception.floatingPoint(), false;
@@ -294,19 +296,23 @@ auto CPU::fpuCheckInput(f32& f) -> bool {
return true;
}
auto CPU::fpuCheckInput(f64& f) -> bool {
switch (fpclassify(f)) {
case FP_SUBNORMAL:
template <typename T>
auto CPU::fpuCheckInputs(T& f1, T& f2) -> bool {
static_assert(std::is_same_v<T, f32> || std::is_same_v<T, f64>);
int cl1 = fpclassify(f1), cl2 = fpclassify(f2);
if((cl1 == FP_NAN && !qnan(f1)) || (cl2 == FP_NAN && !qnan(f2))) {
if(fpeUnimplemented()) return exception.floatingPoint(), false;
return true;
case FP_NAN:
if(qnan(f) ? fpeInvalidOperation() : fpeUnimplemented())
return exception.floatingPoint(), false;
return true;
}
if(cl1 == FP_SUBNORMAL || cl2 == FP_SUBNORMAL) {
if(fpeUnimplemented()) return exception.floatingPoint(), false;
}
if((cl1 == FP_NAN && qnan(f1)) || (cl2 == FP_NAN && qnan(f2))) {
if(fpeInvalidOperation()) return exception.floatingPoint(), false;
}
return true;
}
template<typename T>
auto fpuFlushResult(T f, u32 roundMode) -> T
{
@@ -457,8 +463,7 @@ auto CPU::FABS_D(u8 fd, u8 fs) -> void {
auto CPU::FADD_S(u8 fd, u8 fs, u8 ft) -> void {
if(!fpuCheckStart()) return;
f32 ffs = FS(f32), fft = FT(f32);
if(!fpuCheckInput(ffs)) return;
if(!fpuCheckInput(fft)) return;
if(!fpuCheckInputs(ffs, fft)) return;
CHECK_FPE(f32, ffd, FS(f32) + FT(f32));
if(!fpuCheckOutput(ffd)) return;
FD(f32) = ffd;
@@ -468,8 +473,7 @@ auto CPU::FADD_S(u8 fd, u8 fs, u8 ft) -> void {
auto CPU::FADD_D(u8 fd, u8 fs, u8 ft) -> void {
if(!fpuCheckStart()) return;
auto ffs = FS(f64), fft = FT(f64);
if(!fpuCheckInput(ffs)) return;
if(!fpuCheckInput(fft)) return;
if(!fpuCheckInputs(ffs, fft)) return;
CHECK_FPE(f64, ffd, ffs + fft);
if(!fpuCheckOutput(ffd)) return;
FD(f64) = ffd;
@@ -799,8 +803,7 @@ auto CPU::FCVT_W_D(u8 fd, u8 fs) -> void {
auto CPU::FDIV_S(u8 fd, u8 fs, u8 ft) -> void {
if(!fpuCheckStart()) return;
auto ffs = FS(f32), fft = FT(f32);
if(!fpuCheckInput(ffs)) return;
if(!fpuCheckInput(fft)) return;
if(!fpuCheckInputs(ffs, fft)) return;
CHECK_FPE(f32, ffd, ffs / fft);
if(!fpuCheckOutput(ffd)) return;
FD(f32) = ffd;
@@ -810,8 +813,7 @@ auto CPU::FDIV_S(u8 fd, u8 fs, u8 ft) -> void {
auto CPU::FDIV_D(u8 fd, u8 fs, u8 ft) -> void {
if(!fpuCheckStart()) return;
auto ffs = FS(f64), fft = FT(f64);
if(!fpuCheckInput(ffs)) return;
if(!fpuCheckInput(fft)) return;
if(!fpuCheckInputs(ffs, fft)) return;
CHECK_FPE(f64, ffd, ffs / fft);
if(!fpuCheckOutput(ffd)) return;
FD(f64) = ffd;
@@ -866,8 +868,7 @@ auto CPU::FMOV_D(u8 fd, u8 fs) -> void {
auto CPU::FMUL_S(u8 fd, u8 fs, u8 ft) -> void {
if(!fpuCheckStart()) return;
auto ffs = FS(f32), fft = FT(f32);
if(!fpuCheckInput(ffs)) return;
if(!fpuCheckInput(fft)) return;
if(!fpuCheckInputs(ffs, fft)) return;
CHECK_FPE(f32, ffd, ffs * fft);
if(!fpuCheckOutput(ffd)) return;
FD(f32) = ffd;
@@ -877,8 +878,7 @@ auto CPU::FMUL_S(u8 fd, u8 fs, u8 ft) -> void {
auto CPU::FMUL_D(u8 fd, u8 fs, u8 ft) -> void {
if(!fpuCheckStart()) return;
auto ffs = FS(f64), fft = FT(f64);
if(!fpuCheckInput(ffs)) return;
if(!fpuCheckInput(fft)) return;
if(!fpuCheckInputs(ffs, fft)) return;
CHECK_FPE(f64, ffd, ffs * fft);
if(!fpuCheckOutput(ffd)) return;
FD(f64) = ffd;
@@ -966,8 +966,7 @@ auto CPU::FSQRT_D(u8 fd, u8 fs) -> void {
auto CPU::FSUB_S(u8 fd, u8 fs, u8 ft) -> void {
if(!fpuCheckStart()) return;
auto ffs = FS(f32), fft = FT(f32);
if(!fpuCheckInput(ffs)) return;
if(!fpuCheckInput(fft)) return;
if(!fpuCheckInputs(ffs, fft)) return;
CHECK_FPE(f32, ffd, ffs - fft);
if(!fpuCheckOutput(ffd)) return;
FD(f32) = ffd;
@@ -977,8 +976,7 @@ auto CPU::FSUB_S(u8 fd, u8 fs, u8 ft) -> void {
auto CPU::FSUB_D(u8 fd, u8 fs, u8 ft) -> void {
if(!fpuCheckStart()) return;
auto ffs = FS(f64), fft = FT(f64);
if(!fpuCheckInput(ffs)) return;
if(!fpuCheckInput(fft)) return;
if(!fpuCheckInputs(ffs, fft)) return;
CHECK_FPE(f64, ffd, ffs - fft);
if(!fpuCheckOutput(ffd)) return;
FD(f64) = ffd;
+25 -25
View File
@@ -10,43 +10,43 @@ auto PI::dmaRead() -> void {
auto PI::dmaWrite() -> void {
u8 mem[128];
bool first_block = true;
i32 length = io.writeLength+1;
i32 maxBlockSize = 128;
bool firstBlock = true;
io.writeLength = 0x7F;
if (length <= 8) io.writeLength -= io.dramAddress&7;
if constexpr(Accuracy::CPU::Recompiler) {
cpu.recompiler.invalidateRange(io.dramAddress, (length + 1) & ~1);
}
while (length > 0) {
u32 dest = io.dramAddress & 0x7FFFFE;
i32 misalign = dest & 7;
i32 block_len = 128 - misalign;
i32 cur_len = min(length, block_len);
i32 misalign = io.dramAddress & 7;
i32 distEndOfRow = 0x800-(io.dramAddress&0x7ff);
i32 blockLen = min(maxBlockSize-misalign, distEndOfRow);
i32 curLen = min(length, blockLen);
length -= cur_len;
if (length.bit(0)) length += 1;
i32 rom_len = (cur_len + 1) & ~1;
for (u32 i = 0; i < rom_len; i += 2) {
for (int i=0; i<curLen; i+=2) {
u16 data = busRead<Half>(io.pbusAddress);
mem[i + 0] = data >> 8;
mem[i + 1] = data & 0xFF;
mem[i+0] = data >> 8;
mem[i+1] = data >> 0;
io.pbusAddress += 2;
length -= 2;
}
if (first_block) {
if (cur_len == block_len-1) cur_len++;
cur_len = max(cur_len-misalign, 0);
if (firstBlock && curLen < 127-misalign) {
for (i32 i = 0; i < curLen-misalign; i++) {
rdram.ram.write<Byte>(io.dramAddress++, mem[i], "PI DMA");
}
} else {
for (i32 i = 0; i < curLen-misalign; i+=2) {
rdram.ram.write<Byte>(io.dramAddress++, mem[i+0], "PI DMA");
rdram.ram.write<Byte>(io.dramAddress++, mem[i+1], "PI DMA");
}
}
if constexpr(Accuracy::CPU::Recompiler) {
cpu.recompiler.invalidateRange(io.dramAddress, cur_len);
}
for (u32 i = 0; i < cur_len; i++)
rdram.ram.write<Byte>(io.dramAddress++, mem[i], "PI DMA");
io.dramAddress = (io.dramAddress + 7) & ~7;
first_block = false;
io.writeLength = curLen <= 8 ? 127-misalign : 127;
firstBlock = false;
maxBlockSize = distEndOfRow < 8 ? 128-misalign : 128;
}
}
+2
View File
@@ -6,6 +6,8 @@ auto CPU::read(n1 upper, n1 lower, n24 address, n16 data) -> n16 {
}
}
if(auto result = platform->cheat(address)) return *result;
//cartridge program ROM
if(address <= 0x0fffff) {
return cartridge.readP(upper, lower, address, data);
+1 -9
View File
@@ -17,12 +17,6 @@ auto LSPC::load(Node::Object parent) -> void {
screen->setScale(1.0, 1.0);
screen->setAspect(1.0, 1.0);
overscan = screen->append<Node::Setting::Boolean>("Overscan", true, [&](auto value) {
if(value == 0) screen->setSize(304, 224);
if(value == 1) screen->setSize(320, 256);
});
overscan->setDynamic(true);
vram.allocate(68_KiB >> 1);
pram.allocate(16_KiB >> 1);
@@ -52,7 +46,6 @@ auto LSPC::unload() -> void {
debugger.unload(node);
vram.reset();
pram.reset();
overscan.reset();
screen->quit();
node->remove(screen);
screen.reset();
@@ -101,8 +94,7 @@ auto LSPC::main() -> void {
}
auto LSPC::frame() -> void {
if(overscan->value() == 0) screen->setViewport(8, 16, 304, 224);
if(overscan->value() == 1) screen->setViewport(0, 0, 320, 256);
screen->setViewport(0, 0, 320, 256);
screen->frame();
scheduler.exit(Event::Frame);
}
-1
View File
@@ -3,7 +3,6 @@
struct LSPC : Thread {
Node::Object node;
Node::Video::Screen screen;
Node::Setting::Boolean overscan;
Memory::Writable<n16> vram;
Memory::Writable<n16> pram;
+1
View File
@@ -68,6 +68,7 @@ auto CPU::Bus::speed(u32 size, n24 address) -> n32 {
}
auto CPU::Bus::read(u32 size, n24 address) -> n32 {
if(auto result = platform->cheat(address)) return *result;
n32 data;
if(width == Byte) {
+2
View File
@@ -1,4 +1,6 @@
auto CPU::read(n8 bank, n13 address) -> n8 {
if(auto result = platform->cheat(bank.bit(0,1) << 13 | address)) return *result;
n8 data = 0xff;
//$00-7f HuCard or PC Engine Duo
+3 -3
View File
@@ -25,7 +25,7 @@ auto VDP::load(Node::Object parent) -> void {
screen = node->append<Node::Video::Screen>("Screen", 1365, 263);
screen->colors(1 << 10, {&VDP::color, this});
screen->setSize(1024, 239);
screen->setSize(1128, 263);
screen->setScale(0.25, 1.0);
screen->setAspect(8.0, 7.0);
@@ -64,7 +64,7 @@ auto VDP::main() -> void {
vdc1.hclock();
if(io.vcounter >= 21 && io.vcounter < 239 + 21) {
auto line = screen->pixels().data() + 1365 * io.vcounter;
auto line = screen->pixels().data() + 1365 * io.vcounter + 48;
auto clock = vce.clock();
if(Model::SuperGrafx() == 0) {
@@ -101,7 +101,7 @@ auto VDP::main() -> void {
io.hcounter = 0;
if(++io.vcounter >= 262 + vce.io.extraLine) {
io.vcounter = 0;
screen->setViewport(0, 21, 1024, 239);
screen->setViewport(0, 16, screen->width(), 242);
screen->frame();
scheduler.exit(Event::Frame);
}
+2 -10
View File
@@ -40,16 +40,10 @@ auto VDP::load(Node::Object parent) -> void {
screen = node->append<Node::Video::Screen>("Screen", 1365, 263);
screen->colors(1 << 10, {&VDP::color, this});
screen->setSize(1088, 239);
screen->setSize(1128, 263);
screen->setScale(0.25, 1.0);
screen->setAspect(8.0, 7.0);
overscan = screen->append<Node::Setting::Boolean>("Overscan", true, [&](auto value) {
if(value == 0) screen->setSize(1024, 239);
if(value == 1) screen->setSize(1088, 239);
});
overscan->setDynamic(true);
colorEmulation = screen->append<Node::Setting::Boolean>("Color Emulation", true, [&](auto value) {
screen->resetPalette();
});
@@ -64,7 +58,6 @@ auto VDP::unload() -> void {
vce.debugger = {};
vdc0.debugger = {}; if(Model::SuperGrafx())
vdc1.debugger = {};
overscan.reset();
screen->quit();
node->remove(screen);
screen.reset();
@@ -108,8 +101,7 @@ auto VDP::main() -> void {
io.hcounter = 0;
if(++io.vcounter >= 262 + vce.io.extraLine) {
io.vcounter = 0;
if(overscan->value() == 0) screen->setViewport(96, 21, 1024, 239);
if(overscan->value() == 1) screen->setViewport(96, 21, 1088, 239);
screen->setViewport(48, 16, screen->width(), 242);
screen->frame();
scheduler.exit(Event::Frame);
}
-1
View File
@@ -41,7 +41,6 @@ extern VDPBase vdp;
struct VDP : VDPBase::Implementation {
Node::Object node;
Node::Video::Screen screen;
Node::Setting::Boolean overscan;
Node::Setting::Boolean colorEmulation;
auto irqLine() const -> bool override { return vdc0.irqLine() | vdc1.irqLine(); }
-12
View File
@@ -11,12 +11,6 @@ auto GPU::Blitter::queue() -> void {
width = self.display.width;
height = self.display.height;
if(self.overscan->value() == 0) {
if(height == 240) height = 224;
if(height == 256) height = 240;
if(height == 480) height = 448;
if(height == 512) height = 480;
}
s32 offsetX1 = 0;
if(self.display.width == 256) offsetX1 = 0x228;
@@ -43,12 +37,6 @@ auto GPU::Blitter::queue() -> void {
if(tx + tw > 1023) tw = 1023 - tx;
if(ty + th > 511) th = 511 - ty;
//overscan offset may send ty out of bounds, but the rendering below will skip drawing these lines
if(self.overscan->value() == 0) {
if(self.display.height == 240 || self.display.height == 256) ty -= 8;
if(self.display.height == 480 || self.display.height == 512) ty -= 16;
}
if(tx != self.display.previous.x
|| ty != self.display.previous.y
|| tw != self.display.previous.width
+11 -3
View File
@@ -11,7 +11,7 @@ auto GPU::readGP1() -> u32 {
data.bit(12) = io.checkMaskBit;
data.bit(13) = !io.field || !io.interlace;
data.bit(14) = io.reverseFlag;
data.bit(15) = io.textureDisable;
data.bit(15) = 0; // io.textureDisable only when 2MB VRAM installed?
data.bit(16) = io.horizontalResolution.bit(2);
data.bit(17,18) = io.horizontalResolution.bit(0,1);
data.bit(19) = io.verticalResolution;
@@ -20,12 +20,20 @@ auto GPU::readGP1() -> u32 {
data.bit(22) = io.interlace;
data.bit(23) = io.displayDisable;
data.bit(24) = io.interrupt;
data.bit(25) = io.dmaDirection > 0; //todo
data.bit(25) = 0;
data.bit(26) = io.pcounter == 0; //ready to receive command
data.bit(27) = io.mode != Mode::CopyToVRAM;
data.bit(27) = io.mode == Mode::CopyFromVRAM;
data.bit(28) = io.pcounter == 0; //ready to receive DMA block
data.bit(29,30) = io.dmaDirection;
data.bit(31) = !vblank() && (interlace() ? !io.field : io.vcounter & 1);
switch(io.dmaDirection) {
case 0: data.bit(25) = 0; break;
case 1: data.bit(25) = queue.gp0.length < 16; break;
case 2: data.bit(25) = data.bit(28); break;
case 3: data.bit(25) = data.bit(27); break;
}
return data;
}
-8
View File
@@ -41,12 +41,6 @@ auto GPU::load(Node::Object parent) -> void {
});
screen->setSize(640, 512);
overscan = screen->append<Node::Setting::Boolean>("Overscan", true, [&](auto value) {
if(value == 0) screen->setSize(640, 480);
if(value == 1) screen->setSize(640, 512);
});
overscan->setDynamic(true);
vram.allocate(1_MiB);
for(u32 y : range(512)) {
vram2D[y] = (u16*)&vram.data[y * 2048];
@@ -61,7 +55,6 @@ auto GPU::unload() -> void {
renderer.kill();
debugger = {};
vram.reset();
overscan.reset();
node->remove(screen);
screen.reset();
node.reset();
@@ -92,7 +85,6 @@ auto GPU::main() -> void {
if(io.vcounter == vend()) {
timer.vsync(1);
io.interrupt = 1;
interrupt.raise(Interrupt::Vblank);
blitter.queue();
}
-1
View File
@@ -3,7 +3,6 @@
struct GPU : Thread, Memory::Interface {
Node::Object node;
Node::Video::Screen screen;
Node::Setting::Boolean overscan;
Memory::Writable vram;
struct Debugger {
+1 -1
View File
@@ -61,7 +61,7 @@ auto ICD::writeIO(n24 address, n8 data) -> void {
if(mltReq == 2) joypID &= ~3; //4-player mode (unverified; but the most likely behavior)
if(mltReq == 3) joypID &= ~3; //4-player mode
auto frequency = system.cpuFrequency();
auto frequency = clockFrequency();
switch(data.bit(0,1)) {
case 0: frequency /= 4; break; //fast (glitchy, even on real hardware)
case 1: frequency /= 5; break; //normal
+3
View File
@@ -24,6 +24,9 @@ alwaysinline auto Bus::reduce(u32 address, u32 mask) -> u32 {
}
alwaysinline auto Bus::read(n24 address, n8 data) -> n8 {
if(!(address & 0x40e000)) address = 0x7e0000 | (address & 0x1fff); //de-mirror WRAM
if(auto result = platform->cheat(address)) return *result;
return reader[lookup[address]](target[address], data);
}
+1 -3
View File
@@ -12,7 +12,7 @@ auto PPU::Background::render() -> void {
bool offsetPerTileMode = self.io.bgMode == 2 || self.io.bgMode == 4 || self.io.bgMode == 6;
bool directColorMode = self.dac.io.directColor && id == ID::BG1 && (self.io.bgMode == 3 || self.io.bgMode == 4);
u32 colorShift = 3 + io.mode;
s32 width = self.width() << hires;
s32 width = 256 << hires;
u32 tileHeight = 3 + io.tileSize;
u32 tileWidth = !hires ? tileHeight : 4;
@@ -43,8 +43,6 @@ auto PPU::Background::render() -> void {
s32 x1 = 0;
s32 x2 = 256;
if(self.width() == 352) x1 = -48, x2 = 304;
if(self.width() == 448) x1 = -96, x2 = 352;
x1 <<= hires;
x2 <<= hires;
+12 -5
View File
@@ -5,7 +5,7 @@ auto PPU::DAC::prepare() -> void {
n15 belowColor = hires ? cgram[0] : fixedColor();
if(self.io.displayDisable) aboveColor = 0, belowColor = 0;
for(u32 x : range(self.width())) {
for(u32 x : range(256)) {
above[x] = {PPU::Source::COL, 0, aboveColor};
below[x] = {PPU::Source::COL, 0, belowColor};
}
@@ -17,20 +17,27 @@ auto PPU::DAC::render() -> void {
auto vcounter = self.vcounter();
auto output = (n32*)self.screen->pixels().data();
//PAL systems have additional vertical border
if(Region::PAL()) output += 40 * 564;
if(!self.state.overscan) vcounter += 8;
if(vcounter < 240) {
output += vcounter * 2 * 896;
if(self.interlace() && self.field()) output += 896;
output += vcounter * 2 * 564;
if(self.interlace() && self.field()) output += 564;
//Offset for horizontal border
output += Region::PAL() ? 22 : 26;
u32 luma = self.io.displayBrightness << 15;
if(!self.hires()) {
for(u32 x : range(self.width())) {
for(u32 x : range(256)) {
u32 color = luma | pixel(x, above[x], below[x]);
*output++ = color;
*output++ = color;
}
} else {
for(u32 x : range(self.width())) {
for(u32 x : range(256)) {
*output++ = luma | pixel(x, below[x], above[x]);
*output++ = luma | pixel(x, above[x], below[x]);
}
+1 -2
View File
@@ -28,8 +28,7 @@ auto PPU::Background::renderMode7() -> void {
s32 x1 = 0;
s32 x2 = 255;
if(self.width() == 352) x1 = -48, x2 = 303;
if(self.width() == 448) x1 = -96, x2 = 351;
for(s32 X = x1; X <= x2; X++) {
s32 x = !self.mode7.hflip ? X : x2 - X;
+1 -3
View File
@@ -21,10 +21,8 @@ auto PPU::Object::render() -> void {
for(u32 n : range(32)) items[n].valid = false;
for(u32 n : range(34)) tiles[n].valid = false;
u32 width = self.width();
u32 width = 256;
s32 x1 = 0, x2 = 255;
if(self.width() == 352) x1 = -48, x2 = 303;
if(self.width() == 448) x1 = -96, x2 = 351;
for(u32 n : range(128)) {
Item item{true, io.firstSprite + n & 127};
+28 -17
View File
@@ -21,37 +21,25 @@ PPU ppuPerformanceImpl;
auto PPU::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("PPU");
screen = node->append<Node::Video::Screen>("Screen", 896, 480);
screen = node->append<Node::Video::Screen>("Screen", 564, height() * 2);
screen->colors(1 << 19, {&PPU::color, this});
screen->setSize(512, 480);
screen->setSize(564, height() * 2);
screen->setScale(0.5, 0.5);
screen->setAspect(8.0, 7.0);
Region::PAL() ? screen->setAspect(55.0, 43.0) :screen->setAspect(8.0, 7.0);
vramSize = node->append<Node::Setting::Natural>("VRAM", 64_KiB);
vramSize->setAllowedValues({64_KiB, 128_KiB});
overscanEnable = screen->append<Node::Setting::Boolean>("Overscan", true, [&](auto value) {
screen->setSize(screenWidth->value() * 2, overscanEnable->value() ? 480 : 448);
});
overscanEnable->setDynamic(true);
deepBlackBoost = screen->append<Node::Setting::Boolean>("Deep Black Boost", true, [&](auto value) {
screen->resetPalette();
});
deepBlackBoost->setDynamic(true);
screenWidth = screen->append<Node::Setting::Natural>("Width", 256, [&](auto value) {
screen->setSize(screenWidth->value() * 2, overscanEnable->value() ? 480 : 448);
});
screenWidth->setAllowedValues({256, 352, 448});
debugger.load(node);
}
auto PPU::unload() -> void {
debugger.unload(node);
vramSize.reset();
overscanEnable.reset();
deepBlackBoost.reset();
screen->quit();
node->remove(screen);
@@ -94,8 +82,31 @@ auto PPU::main() -> void {
if(vcounter() == 240) {
if(state.interlace == 0) screen->setProgressive(1);
if(state.interlace == 1) screen->setInterlace(field());
if(overscanEnable->value() == 0) screen->setViewport(0, 18, width() * 2, 448);
if(overscanEnable->value() == 1) screen->setViewport(0, 0, width() * 2, 480);
if(screen->overscan()) {
screen->setSize(564, height() * 2);
screen->setViewport(0, 0, 564, height() * 2);
} else {
int x = 24;
int y = 16;
int w = 564 - 48;
int h = height() - 20;
if(Region::PAL()) {
y += 25;
h -= 29;
w -= 6;
if(!io.overscan) {
y += 16;
h -= 16;
}
}
screen->setSize(w, h * 2);
screen->setViewport(x, y, w, h * 2);
}
screen->frame();
scheduler.exit(Event::Frame);
}
+1 -3
View File
@@ -3,9 +3,7 @@
struct PPU : PPUBase::Implementation, PPUcounter {
Node::Object node;
Node::Setting::Natural vramSize;
Node::Setting::Boolean overscanEnable;
Node::Setting::Boolean deepBlackBoost;
Node::Setting::Natural screenWidth;
struct Debugger {
PPU& self;
@@ -28,7 +26,7 @@ struct PPU : PPUBase::Implementation, PPUcounter {
} graphics;
} debugger{*this};
auto width() const -> u32 { return screenWidth->value(); }
auto height() const -> u32 { return Region::PAL() ? 288 : 242; }
auto hires() const -> bool { return io.pseudoHires || io.bgMode == 5 || io.bgMode == 6; }
auto interlace() const -> bool { return state.interlace; }
auto overscan() const -> bool { return state.overscan; }
+10 -24
View File
@@ -1,6 +1,6 @@
auto PPU::Window::render(Layer& layer, bool enable, bool output[448]) -> void {
if(!enable || (!layer.oneEnable && !layer.twoEnable)) {
memory::fill<bool>(output, self.width(), 0);
memory::fill<bool>(output, 256, 0);
return;
}
@@ -8,17 +8,10 @@ auto PPU::Window::render(Layer& layer, bool enable, bool output[448]) -> void {
s32 oneRight = io.oneRight;
s32 twoLeft = io.twoLeft;
s32 twoRight = io.twoRight;
if(self.width() != 256) {
s32 x1 = self.width() == 352 ? -40 * 2 : -16 * 2;
oneLeft = (oneLeft << 1) + x1;
oneRight = (oneRight << 1) + x1;
twoLeft = (twoLeft << 1) + x1;
twoRight = (twoRight << 1) + x1;
}
if(layer.oneEnable && !layer.twoEnable) {
bool set = 1 ^ layer.oneInvert, clear = !set;
for(s32 x : range(self.width())) {
for(s32 x : range(256)) {
output[x] = x >= oneLeft && x <= oneRight ? set : clear;
}
return;
@@ -26,13 +19,13 @@ auto PPU::Window::render(Layer& layer, bool enable, bool output[448]) -> void {
if(layer.twoEnable && !layer.oneEnable) {
bool set = 1 ^ layer.twoInvert, clear = !set;
for(s32 x : range(self.width())) {
for(s32 x : range(256)) {
output[x] = x >= twoLeft && x <= twoRight ? set : clear;
}
return;
}
for(s32 x : range(self.width())) {
for(s32 x : range(256)) {
bool oneMask = (x >= oneLeft && x <= oneRight) ^ layer.oneInvert;
bool twoMask = (x >= twoLeft && x <= twoRight) ^ layer.twoInvert;
switch(layer.mask) {
@@ -47,14 +40,14 @@ auto PPU::Window::render(Layer& layer, bool enable, bool output[448]) -> void {
auto PPU::Window::render(Color& color, u32 mask, bool output[448]) -> void {
bool set, clear;
switch(mask) {
case 0: memory::fill<bool>(output, self.width(), 1); return; //always
case 0: memory::fill<bool>(output, 256, 1); return; //always
case 1: set = 1, clear = 0; break; //inside
case 2: set = 0, clear = 1; break; //outside
case 3: memory::fill<bool>(output, self.width(), 0); return; //never
case 3: memory::fill<bool>(output, 256, 0); return; //never
}
if(!color.oneEnable && !color.twoEnable) {
memory::fill<bool>(output, self.width(), clear);
memory::fill<bool>(output, 256, clear);
return;
}
@@ -62,17 +55,10 @@ auto PPU::Window::render(Color& color, u32 mask, bool output[448]) -> void {
s32 oneRight = io.oneRight;
s32 twoLeft = io.twoLeft;
s32 twoRight = io.twoRight;
if(self.width() != 256) {
s32 x1 = self.width() == 352 ? -40 * 2 : -16 * 2;
oneLeft = (oneLeft << 1) + x1;
oneRight = (oneRight << 1) + x1;
twoLeft = (twoLeft << 1) + x1;
twoRight = (twoRight << 1) + x1;
}
if(color.oneEnable && !color.twoEnable) {
if(color.oneInvert) set ^= 1, clear ^= 1;
for(s32 x : range(self.width())) {
for(s32 x : range(256)) {
output[x] = x >= oneLeft && x <= oneRight ? set : clear;
}
return;
@@ -80,13 +66,13 @@ auto PPU::Window::render(Color& color, u32 mask, bool output[448]) -> void {
if(!color.oneEnable && color.twoEnable) {
if(color.twoInvert) set ^= 1, clear ^= 1;
for(s32 x : range(self.width())) {
for(s32 x : range(256)) {
output[x] = x >= twoLeft && x <= twoRight ? set : clear;
}
return;
}
for(s32 x : range(self.width())) {
for(s32 x : range(256)) {
bool oneMask = (x >= oneLeft && x <= oneRight) ^ color.oneInvert;
bool twoMask = (x >= twoLeft && x <= twoRight) ^ color.twoInvert;
switch(color.mask) {
+7 -2
View File
@@ -5,8 +5,13 @@ auto PPU::DAC::scanline() -> void {
auto output = self.screen->pixels().data();
if(!self.overscan()) vcounter += 8;
if(vcounter < 240) {
line = output + vcounter * 2 * 512;
if(self.interlace() && self.field()) line += 512;
line = output + vcounter * 2 * 564;
//PAL systems have additional vertical border
if(Region::PAL()) line += 40 * 564;
if(self.interlace() && self.field()) line += 564;
//Offset for horizontal border
line += Region::PAL() ? 22 : 26;
}
//the first hires pixel of each scanline is transparent
+25 -2
View File
@@ -21,8 +21,31 @@ auto PPU::main() -> void {
if(vcounter() == 240) {
if(state.interlace == 0) screen->setProgressive(1);
if(state.interlace == 1) screen->setInterlace(field());
if(overscanEnable->value() == 0) screen->setViewport(0, 18, 512, 448);
if(overscanEnable->value() == 1) screen->setViewport(0, 0, 512, 480);
if(screen->overscan()) {
screen->setSize(564, height() * 2);
screen->setViewport(0, 0, 564, height() * 2);
} else {
int x = 24;
int y = 16;
int w = 564 - 48;
int h = height() - 20;
if(Region::PAL()) {
y += 25;
h -= 29;
w -= 6;
if(!io.overscan) {
y += 16;
h -= 16;
}
}
screen->setSize(w, h * 2);
screen->setViewport(x, y, w, h * 2);
}
screen->frame();
scheduler.exit(Event::Frame);
}
+3 -10
View File
@@ -32,11 +32,11 @@ auto PPUBase::setAccurate(bool value) -> void {
auto PPU::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("PPU");
screen = node->append<Node::Video::Screen>("Screen", 512, 480);
screen = node->append<Node::Video::Screen>("Screen", 564, height() * 2);
screen->colors(1 << 19, {&PPU::color, this});
screen->setSize(512, 480);
screen->setSize(564, height() * 2);
screen->setScale(0.5, 0.5);
screen->setAspect(8.0, 7.0);
Region::PAL() ? screen->setAspect(55.0, 43.0) :screen->setAspect(8.0, 7.0);
versionPPU1 = node->append<Node::Setting::Natural>("PPU1 Version", 1);
versionPPU1->setAllowedValues({1});
@@ -47,12 +47,6 @@ auto PPU::load(Node::Object parent) -> void {
vramSize = node->append<Node::Setting::Natural>("VRAM", 64_KiB);
vramSize->setAllowedValues({64_KiB, 128_KiB});
overscanEnable = screen->append<Node::Setting::Boolean>("Overscan", true, [&](auto value) {
if(value == 0) screen->setSize(512, 448);
if(value == 1) screen->setSize(512, 480);
});
overscanEnable->setDynamic(true);
deepBlackBoost = screen->append<Node::Setting::Boolean>("Deep Black Boost", true, [&](auto value) {
screen->resetPalette();
});
@@ -66,7 +60,6 @@ auto PPU::unload() -> void {
versionPPU1.reset();
versionPPU2.reset();
vramSize.reset();
overscanEnable.reset();
deepBlackBoost.reset();
screen->quit();
node->remove(screen);
+1 -1
View File
@@ -48,7 +48,6 @@ struct PPU : PPUBase::Implementation, PPUcounter {
Node::Setting::Natural versionPPU1;
Node::Setting::Natural versionPPU2;
Node::Setting::Natural vramSize;
Node::Setting::Boolean overscanEnable;
Node::Setting::Boolean deepBlackBoost;
struct Debugger {
@@ -77,6 +76,7 @@ struct PPU : PPUBase::Implementation, PPUcounter {
} properties;
} debugger{*this};
auto height() const -> u32 { return Region::PAL() ? 288 : 242; }
auto interlace() const -> bool { return state.interlace; }
auto overscan() const -> bool { return state.overscan; }
auto vdisp() const -> u32 { return state.vdisp; }
+2
View File
@@ -1,4 +1,6 @@
auto CPU::read(n16 address) -> n8 {
if(auto result = platform->cheat(address)) return *result;
n8 data = 0xff;
if(auto result = cartridge.read(address)) {
data = result();
+17 -4
View File
@@ -11,12 +11,12 @@ auto VDP::load(Node::Object parent) -> void {
node = parent->append<Node::Object>("VDP");
screen = node->append<Node::Video::Screen>("Screen", 256, 192);
screen = node->append<Node::Video::Screen>("Screen", 284, 243);
screen->colors(1 << 4, {&VDP::color, this});
screen->setSize(256, 192);
screen->setViewport(0, 0, 256, 192);
screen->setSize(284, 243);
screen->setViewport(0, 0, 284, 243);
screen->setScale(1.0, 1.0);
screen->setAspect(1.0, 1.0);
screen->setAspect(8.0, 7.0);
TMS9918::load(screen);
}
@@ -39,6 +39,19 @@ auto VDP::irq(bool line) -> void {
}
auto VDP::frame() -> void {
if(screen->overscan()) {
screen->setSize(284, 243);
screen->setViewport(0, 0, 284, 243);
} else {
int x = 16;
int y = 16;
int width = 284 - 32;
int height = 243 - 32;
screen->setSize(width, height);
screen->setViewport(x, y, width, height);
}
screen->frame();
scheduler.exit(Event::Frame);
}
+2
View File
@@ -1,4 +1,6 @@
auto CPU::read(n16 address) -> n8 {
if(auto result = platform->cheat(address)) return *result;
if (address < 0x4000) {
if (expansionPort.connected() && expansionPort.romcs()) {
return expansionPort.read(address);
+1
View File
@@ -25,6 +25,7 @@ auto Bus::read(n20 address) -> n8 {
if(!cpu.io.cartridgeEnable && address >= 0x100000 - system.bootROM.size()) {
return system.bootROM.read(address);
}
if(auto result = platform->cheat(address)) return *result;
switch(address.bit(16,19)) { default:
case 0x0:
if(address.bit(14,15) && !system.color()) return 0x90;
+26 -26
View File
@@ -7,39 +7,36 @@ namespace ruby {
}
auto locate(const string& name) -> string {
// First, check the application directory
// This allows ares to function in 'portable' mode
// First check each path for the presence of the file we are looking for in the following order
// allowing users to override the default resources if they wish to do so.
// 1. The application directory
string location = {Path::program(), name};
if(inode::exists(location)) return location;
// On macOS, also check the AppBundle Resource path
#if defined(PLATFORM_MACOS)
location = {Path::resources(), name};
if(inode::exists(location)) return location;
#endif
// Check the userData directory, this is the default
// on non-windows platforms for any resouces that did not
// ship with the executable.
// On Windows, this allows settings from to be carried over
// from previous versions (pre-portable)
// 2. The user data directory
location = {Path::userData(), "ares/", name};
if(inode::exists(location)) return location;
// On non-windows platforms, this time check the shared
// data directory, on Windows, default to program dir,
// this ensures Portable mode is the default on Windows platforms.
#if !defined(PLATFORM_WINDOWS)
string shared_location = {Path::sharedData(), "ares/", name};
if(inode::exists(shared_location)) return shared_location;
// 3. The shared data directory
location = {Path::sharedData(), "ares/", name};
if(inode::exists(location)) return location;
// On non-windows platforms, after exhausting other options,
// default to userData.
directory::create({Path::userData(), "ares/"});
return {Path::userData(), "ares/", name};
#else
return {Path::program(), name};
#endif
// 4. The application bundle resource directory (macOS only)
#if defined(PLATFORM_MACOS)
location = {Path::resources(), name};
if(inode::exists(location)) return location;
#endif
// If the file was not found in any of the above locations, we may be intending to create it
// We must return a path to a user writable directory; on Windows, this is the executable directory
#if defined(PLATFORM_WINDOWS)
return {Path::program(), name};
#endif
// On other platforms, this is the "user data" directory
directory::create({Path::userData(), "ares/"});
return {Path::userData(), "ares/", name};
}
#include <nall/main.hpp>
@@ -151,6 +148,9 @@ auto nall::main(Arguments arguments) -> void {
program.create();
presentation.loadEmulators();
#if defined(PLATFORM_WINDOWS)
presentation.setVisible(true);
#endif
Application::onMain({&Program::main, &program});
Application::run();
+8 -7
View File
@@ -155,10 +155,8 @@ auto Emulator::setBoolean(const string& name, bool value) -> bool {
auto Emulator::setOverscan(bool value) -> bool {
if(auto screen = root->scan<ares::Node::Video::Screen>("Screen")) {
if(auto overscan = screen->find<ares::Node::Setting::Boolean>("Overscan")) {
overscan->setValue(value);
return true;
}
screen->setOverscan(value);
return true;
}
return false;
}
@@ -190,9 +188,12 @@ auto Emulator::errorFirmware(const Firmware& firmware, string system) -> void {
auto Emulator::input(ares::Node::Input::Input input) -> void {
//looking up inputs is very time-consuming; skip call if input was called too recently
auto thisPoll = chrono::millisecond();
if(thisPoll - input->lastPoll < 5) return;
input->lastPoll = thisPoll;
//note: allow rumble to be polled at full speed to prevent missed motor events
if(!input->cast<ares::Node::Input::Rumble>()) {
auto thisPoll = chrono::millisecond();
if(thisPoll - input->lastPoll < 5) return;
input->lastPoll = thisPoll;
}
auto device = ares::Node::parent(input);
if(!device) return;
+19 -1
View File
@@ -176,6 +176,9 @@ Presentation::Presentation() {
manifestViewerAction.setText("Manifest").setIcon(Icon::Emblem::Binary).onActivate([&] {
toolsWindow.show("Manifest");
});
cheatEditorAction.setText("Cheats").setIcon(Icon::Emblem::File).onActivate([&] {
toolsWindow.show("Cheats");
});
#if !defined(PLATFORM_MACOS)
// Cocoa hiro is missing the hex editor widget
memoryEditorAction.setText("Memory").setIcon(Icon::Device::Storage).onActivate([&] {
@@ -262,7 +265,10 @@ Presentation::Presentation() {
setAssociatedFile();
setBackgroundColor({0, 0, 0});
setAlignment(Alignment::Center);
#if !defined(PLATFORM_WINDOWS)
setVisible();
#endif
#if defined(PLATFORM_MACOS)
Application::Cocoa::onAbout([&] { aboutAction.doActivate(); });
@@ -329,7 +335,7 @@ auto Presentation::loadEmulators() -> void {
auto entry = settings.recent.game[index];
auto system = entry.split(";", 1L)(0);
auto location = entry.split(";", 1L)(1);
if(inode::exists(location)) { //remove missing games
if(location.length()) { //remove missing games
if(!recentGames.find(entry)) { //remove duplicate entries
recentGames.append(entry);
}
@@ -353,6 +359,18 @@ auto Presentation::loadEmulators() -> void {
item.setIconForFile(location);
item.setText({Location::base(location).trimRight("/"), " (", system, ")"});
item.onActivate([=] {
if(!inode::exists(location)) {
MessageDialog()
.setTitle("Error")
.setText({location, " does not exist"})
.setAlignment(presentation)
.error();
//remove the entry from the recent games list
settings.recent.game[index] = {};
loadEmulators();
return;
}
for(auto& emulator : emulators) {
if(emulator->name == system) {
return (void)program.load(emulator, location);
+1
View File
@@ -63,6 +63,7 @@ struct Presentation : Window {
MenuItem reloadGame{&toolsMenu};
MenuSeparator toolsMenuSeparatorB{&toolsMenu};
MenuItem manifestViewerAction{&toolsMenu};
MenuItem cheatEditorAction{&toolsMenu};
#if !defined(PLATFORM_MACOS)
// Cocoa hiro is missing the hex editor widget
MenuItem memoryEditorAction{&toolsMenu};
+2
View File
@@ -62,6 +62,7 @@ auto Program::load(string location) -> bool {
presentation.showIcon(false);
if(settings.video.adaptiveSizing) presentation.resizeWindow();
manifestViewer.reload();
cheatEditor.reload();
memoryEditor.reload();
graphicsViewer.reload();
streamManager.reload();
@@ -110,6 +111,7 @@ auto Program::unload() -> void {
toolsWindow.setVisible(false);
gameBrowserWindow.setVisible(false);
manifestViewer.unload();
cheatEditor.unload();
memoryEditor.unload();
graphicsViewer.unload();
streamManager.unload();
+4
View File
@@ -184,3 +184,7 @@ auto Program::input(ares::Node::Input::Input node) -> void {
emulator->input(node);
}
auto Program::cheat(u32 address) -> maybe<u32> {
return cheatEditor.find(address);
}
+1
View File
@@ -13,6 +13,7 @@ struct Program : ares::Platform {
auto video(ares::Node::Video::Screen, const u32* data, u32 pitch, u32 width, u32 height) -> void override;
auto audio(ares::Node::Audio::Stream) -> void override;
auto input(ares::Node::Input::Input) -> void override;
auto cheat(u32 address) -> maybe<u32> override;
//load.cpp
auto identify(const string& filename) -> shared_pointer<Emulator>;
+1 -1
View File
@@ -60,7 +60,7 @@ auto VideoSettings::construct() -> void {
if(emulator) emulator->setOverscan(settings.video.overscan);
});
overscanLayout.setAlignment(1).setPadding(12_sx, 0);
overscanHint.setText("Shows extended PAL CRT lines, but these are usually blank in most games").setFont(Font().setSize(7.0)).setForegroundColor(SystemColor::Sublabel);
overscanHint.setText("Displays the full frame without cropping 'undesirable' borders").setFont(Font().setSize(7.0)).setForegroundColor(SystemColor::Sublabel);
pixelAccuracyOption.setText("Pixel Accuracy Mode").setChecked(settings.video.pixelAccuracy).onToggle([&] {
settings.video.pixelAccuracy = pixelAccuracyOption.checked();
if(emulator) emulator->setBoolean("Pixel Accuracy", settings.video.pixelAccuracy);
+152
View File
@@ -0,0 +1,152 @@
auto CheatEditor::Cheat::update(string description, string code, bool enabled) -> Cheat& {
this->description = description;
this->code = code;
this->enabled = enabled;
//TODO: support other code formats based on the game system (e.g. GameShark, Game-Genie, etc.)
addressValuePairs.reset();
vector<string> codes = code.split("+");
for(auto& code : codes) {
auto parts = code.split(":");
if(parts.size() != 2) continue;
addressValuePairs.insert(string{"0x", parts[0]}.natural(), string{"0x",parts[1]}.natural());
}
return *this;
}
auto CheatEditor::construct() -> void {
setCollapsible();
setVisible(false);
cheatsLabel.setText("Cheats").setFont(Font().setBold());
descriptionLabel.setText("Description:");
codeLabel.setText("Code:");
deleteButton.setText("Delete").onActivate([&] {
if(auto item = cheatList.selected()) {
if(auto cheat = item.attribute<Cheat*>("cheat")) {
if(auto c = cheats.find([cheat](auto& c) { return &c == cheat; })) {
descriptionEdit.setText("");
codeEdit.setText("");
cheats.remove(*c);
deleteButton.setEnabled(false);
refresh();
}
}
}
}).setEnabled(false);
saveButton.setText("Save").onActivate([&] {
string description = descriptionEdit.text();
string code = codeEdit.text();
if(auto c = cheats.find([description](auto& c) { return c.description == description; })) {
cheats[*c].update(description, code);
} else {
cheats.append(Cheat().update(description, code));
}
refresh();
});
cheatList.onToggle([&](auto cell) {
if(auto item = cheatList.selected()) {
if(auto cheat = item.attribute<Cheat*>("cheat")) {
cheat->enabled = cell.checked();
}
}
});
cheatList.onChange([&] {
deleteButton.setEnabled(false);
descriptionEdit.setText("");
codeEdit.setText("");
if(auto item = cheatList.selected()) {
if(auto cheat = item.attribute<Cheat*>("cheat")) {
descriptionEdit.setText(cheat->description);
codeEdit.setText(cheat->code);
deleteButton.setEnabled(true);
}
}
});
}
auto CheatEditor::reload() -> void {
cheats.reset();
location = emulator->locate(emulator->game->location, {".cheats.bml"});
if(file::exists(location)) {
auto document = BML::unserialize(string::read(location), " ");
for(auto cheatNode : document.find("cheat")) {
cheats.append(Cheat().update(
cheatNode["description"].text(),
cheatNode["code"].text(),
cheatNode["enabled"].boolean()
));
}
}
refresh();
}
auto CheatEditor::refresh() -> void {
cheatList.reset();
cheatList.setHeadered();
cheatList.append(TableViewColumn());
cheatList.append(TableViewColumn().setText("Description").setExpandable());
cheatList.append(TableViewColumn().setText("Code").setAlignment(1.0));
for(auto& cheat : cheats) {
TableViewItem item{&cheatList};
item.setAttribute<Cheat*>("cheat", &cheat);
item.append(TableViewCell().setCheckable().setChecked(cheat.enabled));
item.append(TableViewCell().setText(cheat.description));
item.append(TableViewCell().setText(cheat.code));
}
cheatList.resizeColumns();
cheatList.column(0).setWidth(16);
}
auto CheatEditor::unload() -> void {
bool hasCheats = cheats.size() > 0;
bool isCheatLocation = location.endsWith(".cheats.bml");
if(hasCheats && isCheatLocation) {
auto fp = file::open(location, file::mode::write);
if(fp) {
fp.print("cheats\n");
fp.print(" revision: ", chrono::local::date(), "\n\n");
for(auto& cheat : cheats) {
fp.print("cheat\n");
fp.print(" description: ", cheat.description, "\n");
fp.print(" code: ", cheat.code, "\n");
fp.print(" enabled: ", cheat.enabled, "\n");
fp.print("\n");
}
}
}
if(!hasCheats && isCheatLocation && file::exists(location)) {
file::remove(location);
}
location = "";
}
auto CheatEditor::find(uint address) -> maybe<u32> {
for(auto& cheat : cheats) {
if(!cheat.enabled) continue;
if(auto result = cheat.addressValuePairs.find(address)) return result();
}
return nothing;
}
auto CheatEditor::setVisible(bool visible) -> CheatEditor& {
descriptionEdit.setText("");
codeEdit.setText("");
VerticalLayout::setVisible(visible);
return *this;
}
+7
View File
@@ -1,4 +1,5 @@
#include "../desktop-ui.hpp"
#include "cheats.cpp"
#include "manifest.cpp"
#include "memory.cpp"
#include "graphics.cpp"
@@ -9,6 +10,7 @@
namespace Instances { Instance<ToolsWindow> toolsWindow; }
ToolsWindow& toolsWindow = Instances::toolsWindow();
ManifestViewer& manifestViewer = toolsWindow.manifestViewer;
CheatEditor& cheatEditor = toolsWindow.cheatEditor;
MemoryEditor& memoryEditor = toolsWindow.memoryEditor;
GraphicsViewer& graphicsViewer = toolsWindow.graphicsViewer;
StreamManager& streamManager = toolsWindow.streamManager;
@@ -18,6 +20,7 @@ TraceLogger& traceLogger = toolsWindow.traceLogger;
ToolsWindow::ToolsWindow() {
panelList.append(ListViewItem().setText("Manifest").setIcon(Icon::Emblem::Binary));
panelList.append(ListViewItem().setText("Cheats").setIcon(Icon::Emblem::Text));
#if !defined(PLATFORM_MACOS)
// Cocoa hiro is missing the hex editor widget
panelList.append(ListViewItem().setText("Memory").setIcon(Icon::Device::Storage));
@@ -31,6 +34,7 @@ ToolsWindow::ToolsWindow() {
panelContainer.setPadding(5_sx, 5_sy);
panelContainer.append(manifestViewer, Size{~0, ~0});
panelContainer.append(cheatEditor, Size{~0, ~0});
panelContainer.append(memoryEditor, Size{~0, ~0});
panelContainer.append(graphicsViewer, Size{~0, ~0});
panelContainer.append(streamManager, Size{~0, ~0});
@@ -39,6 +43,7 @@ ToolsWindow::ToolsWindow() {
panelContainer.append(homePanel, Size{~0, ~0});
manifestViewer.construct();
cheatEditor.construct();
memoryEditor.construct();
graphicsViewer.construct();
streamManager.construct();
@@ -68,6 +73,7 @@ auto ToolsWindow::show(const string& panel) -> void {
auto ToolsWindow::eventChange() -> void {
manifestViewer.setVisible(false);
cheatEditor.setVisible(false);
memoryEditor.setVisible(false);
graphicsViewer.setVisible(false);
streamManager.setVisible(false);
@@ -78,6 +84,7 @@ auto ToolsWindow::eventChange() -> void {
bool found = false;
if(auto item = panelList.selected()) {
if(item.text() == "Manifest" ) found = true, manifestViewer.setVisible();
if(item.text() == "Cheats" ) found = true, cheatEditor.setVisible();
if(item.text() == "Memory" ) found = true, memoryEditor.setVisible();
if(item.text() == "Graphics" ) found = true, graphicsViewer.setVisible();
if(item.text() == "Streams" ) found = true, streamManager.setVisible();
+37
View File
@@ -11,6 +11,41 @@ struct ManifestViewer : VerticalLayout {
TextEdit manifestView{this, Size{~0, ~0}};
};
struct CheatEditor : VerticalLayout {
auto construct() -> void;
auto reload() -> void;
auto unload() -> void;
auto refresh() -> void;
auto setVisible(bool visible = true) -> CheatEditor&;
auto find(u32 address) -> maybe<u32>;
Label cheatsLabel{this, Size{~0, 0}, 5};
HorizontalLayout editLayout{this, Size{~0, 0}};
Label descriptionLabel{&editLayout, Size{0, 0}, 2};
LineEdit descriptionEdit{&editLayout, Size{~0, 0}};
Label codeLabel{&editLayout, Size{0, 0}, 2};
LineEdit codeEdit{&editLayout, Size{120, 0}};
Button saveButton{&editLayout, Size{60, 0}};
TableView cheatList{this, Size{~0, ~0}};
HorizontalLayout deleteLayout{this, Size{~0, 0}};
Button deleteButton{&deleteLayout, Size{80, 0}};
struct Cheat {
Cheat() = default;
auto update(string description, string code, bool enabled = false) -> Cheat&;
bool enabled;
string description;
string code;
map<u32, u32> addressValuePairs;
};
string location;
vector<Cheat> cheats;
};
struct MemoryEditor : VerticalLayout {
auto construct() -> void;
auto reload() -> void;
@@ -101,6 +136,7 @@ struct ToolsWindow : Window {
ListView panelList{&layout, Size{125_sx, ~0}};
VerticalLayout panelContainer{&layout, Size{~0, ~0}};
ManifestViewer manifestViewer;
CheatEditor cheatEditor;
MemoryEditor memoryEditor;
GraphicsViewer graphicsViewer;
StreamManager streamManager;
@@ -111,6 +147,7 @@ struct ToolsWindow : Window {
namespace Instances { extern Instance<ToolsWindow> toolsWindow; }
extern ToolsWindow& toolsWindow;
extern CheatEditor& cheatEditor;
extern ManifestViewer& manifestViewer;
extern MemoryEditor& memoryEditor;
extern GraphicsViewer& graphicsViewer;
+23 -26
View File
@@ -8,40 +8,37 @@ function<string ()> homeLocation = [] { return string{Path::user(), "Emulation/S
function<string ()> saveLocation = [] { return string{}; };
vector<string> media;
auto locate(const string& name) -> string {
// First, check the application directory
// This allows ares to function in 'portable' mode
string location = {Path::program(), name};
if(inode::exists(location)) return location;
auto locate(const string &name) -> string {
// First check each path for the presence of the file we are looking for in the following order
// allowing users to override the default resources if they wish to do so.
// On macOS, also check the AppBundle Resource path
// 1. The application directory
string location = {Path::program(), name};
if (inode::exists(location)) return location;
// 2. The user data directory
location = {Path::userData(), "ares/", name};
if (inode::exists(location)) return location;
// 3. The shared data directory
location = {Path::sharedData(), "ares/", name};
if (inode::exists(location)) return location;
// 4. The application bundle resource directory (macOS only)
#if defined(PLATFORM_MACOS)
location = {Path::resources(), name};
if(inode::exists(location)) return location;
#endif
// Check the userData directory, this is the default
// on non-windows platforms for any resouces that did not
// ship with the executable.
// On Windows, this allows settings from to be carried over
// from previous versions (pre-portable)
location = {Path::userData(), "ares/", name};
if(inode::exists(location)) return location;
// On non-windows platforms, this time check the shared
// data directory, on Windows, default to program dir
// this ensures Portable mode is the default on Windows platforms.
#if !defined(PLATFORM_WINDOWS)
string shared_location = {Path::sharedData(), "ares/", name};
if(inode::exists(shared_location)) return shared_location;
// On non-windows platforms, after exhausting other options,
// default to userData
directory::create({Path::userData(), "ares/"});
return {Path::userData(), "ares/", name};
#else
// If the file was not found in any of the above locations, we may be intending to create it
// We must return a path to a user writable directory; on Windows, this is the executable directory
#if defined(PLATFORM_WINDOWS)
return {Path::program(), name};
#endif
// On other platforms, this is the "user data" directory
directory::create({Path::userData(), "ares/"});
return {Path::userData(), "ares/", name};
}
auto operator+=(string& lhs, const string& rhs) -> string& {
+8 -8
View File
@@ -7,15 +7,15 @@ ifeq ($(ruby),)
ruby += audio.wasapi audio.xaudio2 audio.directsound audio.waveout #audio.asio
ruby += input.windows
ifeq ($(pkg_config),)
# TODO: Check presence of libSDL
else
pkg_check = $(if $(shell $(pkg_config) $1 && echo 1),$2)
ifeq ($(sdl2),true)
ruby += $(call pkg_check,sdl2,input.sdl)
ruby += $(call pkg_check,sdl2,audio.sdl)
ifeq ($(pkg_config),)
# TODO: Check presence of libSDL
else
pkg_check = $(if $(shell $(pkg_config) $1 && echo 1),$2)
ifeq ($(sdl2),true)
ruby += $(call pkg_check,sdl2,input.sdl)
ruby += $(call pkg_check,sdl2,audio.sdl)
endif
endif
endif
else ifeq ($(platform),macos)
ruby += video.cgl
ruby += audio.openal
+7 -2
View File
@@ -99,8 +99,13 @@ private:
u32 hats = SDL_JoystickNumHats(jp.handle) * 2;
u32 buttons = SDL_JoystickNumButtons(jp.handle);
jp.hid->setVendorID(HID::Joypad::GenericVendorID);
jp.hid->setProductID(HID::Joypad::GenericProductID);
u16 vid = SDL_JoystickGetVendor(jp.handle);
u16 pid = SDL_JoystickGetProduct(jp.handle);
if(vid == 0) vid = HID::Joypad::GenericVendorID;
if(pid == 0) pid = HID::Joypad::GenericProductID;
jp.hid->setVendorID(vid);
jp.hid->setProductID(pid);
jp.hid->setPathID(jp.id);
for(u32 n : range(axes)) jp.hid->axes().append(n);
for(u32 n : range(hats)) jp.hid->hats().append(n);
+2 -14
View File
@@ -28,7 +28,7 @@ struct VideoDirect3D9 : VideoDriver {
auto setMonitor(string monitor) -> bool override { return initialize(); }
auto setExclusive(bool exclusive) -> bool override { return initialize(); }
auto setContext(uintptr context) -> bool override { return initialize(); }
auto setBlocking(bool blocking) -> bool override { return true; }
auto setBlocking(bool blocking) -> bool override { return initialize(); }
auto setShader(string shader) -> bool override { return updateFilter(); }
auto focused() -> bool override {
@@ -115,18 +115,6 @@ struct VideoDirect3D9 : VideoDriver {
_device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
_device->EndScene();
if(self.blocking) {
D3DRASTER_STATUS status;
while(true) { //wait for a previous vblank to finish, if necessary
_device->GetRasterStatus(0, &status);
if(!status.InVBlank) break;
}
while(true) { //wait for next vblank to begin
_device->GetRasterStatus(0, &status);
if(status.InVBlank) break;
}
}
if(_device->Present(0, 0, 0, 0) == D3DERR_DEVICELOST) _lost = true;
}
@@ -292,7 +280,7 @@ private:
_presentation.MultiSampleQuality = 0;
_presentation.EnableAutoDepthStencil = false;
_presentation.AutoDepthStencilFormat = D3DFMT_UNKNOWN;
_presentation.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
_presentation.PresentationInterval = self.blocking ? D3DPRESENT_INTERVAL_ONE : D3DPRESENT_INTERVAL_IMMEDIATE;
_presentation.hDeviceWindow = _context;
_presentation.Windowed = !_exclusive;
_presentation.BackBufferFormat = _exclusive ? D3DFMT_X8R8G8B8 : D3DFMT_UNKNOWN;
+4 -3
View File
@@ -1,5 +1,4 @@
#include "opengl/opengl.hpp"
#include <dwmapi.h>
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
@@ -41,6 +40,9 @@ struct VideoWGL : VideoDriver, OpenGL {
}
auto setBlocking(bool blocking) -> bool override {
acquireContext();
if(wglSwapInterval) wglSwapInterval(blocking);
releaseContext();
return true;
}
@@ -105,7 +107,6 @@ struct VideoWGL : VideoDriver, OpenGL {
OpenGL::output();
SwapBuffers(_display);
if(self.blocking) DwmFlush(); // DwmFlush waits for VSYNC at compositor level, bypassing OpenGL driver bugs
if(self.flush) glFinish();
releaseContext();
}
@@ -188,7 +189,7 @@ private:
}
}
if(wglSwapInterval) wglSwapInterval(0);
if(wglSwapInterval) wglSwapInterval(self.blocking);
_ready = OpenGL::initialize(self.shader);
releaseContext();
return _ready;
+20
View File
@@ -11,6 +11,10 @@ libchdr.objects += lzma_alloc lzma_bra86 lzma_braia64 lzma_cpuarch lzma_delta lz
libchdr.objects += lzma_86dec lzma_dec lcma_enc lzma_sort
libchdr.objects += zlib_adler32 zlib_compress zlib_crc32 zlib_deflate zlib_infback zlib_inffast
libchdr.objects += zlib_inflate zlib_inftrees zlib_trees zlib_uncompr zlib_zutil
libchdr.objects += zstd_common_debug zstd_common_entropy_common zstd_common_error_private
libchdr.objects += zstd_common_fse_decompress zstd_common_pool zstd_common_threading
libchdr.objects += zstd_common_xxhash zstd_common_zstd_common zstd_decompress_huf_decompress
libchdr.objects += zstd_decompress_zstd_ddict zstd_decompress_zstd_decompress zstd_decompress_zstd_decompress_block
libchdr.objects := $(libchdr.objects:%=$(object.path)/%.o)
@@ -43,9 +47,25 @@ $(object.path)/zlib_trees.o: $(libchdr.path)/deps/zlib-1.2.13/trees.c
$(object.path)/zlib_uncompr.o: $(libchdr.path)/deps/zlib-1.2.13/uncompr.c
$(object.path)/zlib_zutil.o: $(libchdr.path)/deps/zlib-1.2.13/zutil.c
$(object.path)/zstd_common_debug.o: $(libchdr.path)/deps/zstd-1.5.5/lib/common/debug.c
$(object.path)/zstd_common_entropy_common.o: $(libchdr.path)/deps/zstd-1.5.5/lib/common/entropy_common.c
$(object.path)/zstd_common_error_private.o: $(libchdr.path)/deps/zstd-1.5.5/lib/common/error_private.c
$(object.path)/zstd_common_fse_decompress.o: $(libchdr.path)/deps/zstd-1.5.5/lib/common/fse_decompress.c
$(object.path)/zstd_common_pool.o: $(libchdr.path)/deps/zstd-1.5.5/lib/common/pool.c
$(object.path)/zstd_common_threading.o: $(libchdr.path)/deps/zstd-1.5.5/lib/common/threading.c
$(object.path)/zstd_common_xxhash.o: $(libchdr.path)/deps/zstd-1.5.5/lib/common/xxhash.c
$(object.path)/zstd_common_zstd_common.o: $(libchdr.path)/deps/zstd-1.5.5/lib/common/zstd_common.c
$(object.path)/zstd_decompress_huf_decompress.o: $(libchdr.path)/deps/zstd-1.5.5/lib/decompress/huf_decompress.c
$(object.path)/zstd_decompress_zstd_ddict.o: $(libchdr.path)/deps/zstd-1.5.5/lib/decompress/zstd_ddict.c
$(object.path)/zstd_decompress_zstd_decompress.o: $(libchdr.path)/deps/zstd-1.5.5/lib/decompress/zstd_decompress.c
$(object.path)/zstd_decompress_zstd_decompress_block.o: $(libchdr.path)/deps/zstd-1.5.5/lib/decompress/zstd_decompress_block.c
flags += -I$(libchdr.path)/include
flags += -I$(libchdr.path)/deps/lzma-19.00/include -D_7ZIP_ST
flags += -I$(libchdr.path)/deps/zlib-1.2.13
flags += -I$(libchdr.path)/deps/zstd-1.5.5/lib
flags += -DZSTD_DISABLE_ASM
# instruct glibc to declare fseeko/ftello
flags += -D_LARGEFILE_SOURCE
+4
View File
@@ -40,6 +40,10 @@ else()
list(APPEND CHDR_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/deps/zlib-1.2.13" "${CMAKE_CURRENT_BINARY_DIR}/deps/zlib-1.2.13")
endif()
# zstd
add_subdirectory(deps/zstd-1.5.5 EXCLUDE_FROM_ALL)
list(APPEND CHDR_LIBS zstd)
list(APPEND CHDR_INCLUDES zstd)
#--------------------------------------------------
# chdr
#--------------------------------------------------
Loaded 100 of 746 files, more files were not shown because too many files have changed in this diff. Show more