mirror of
https://github.com/ares-emulator/ares.git
synced 2026-11-09 17:03:16 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39025af1fe | ||
|
|
5cdefb088d | ||
|
|
d0253599a6 | ||
|
|
1e83da0241 | ||
|
|
ed0a1c8852 | ||
|
|
dd9c728a12 | ||
|
|
7ec1e1430c | ||
|
|
16379aabff | ||
|
|
11f24b47c3 | ||
|
|
32f205975c | ||
|
|
21ca9cf76e | ||
|
|
d32b3ffe45 | ||
|
|
5864382c67 | ||
|
|
f392e4f428 | ||
|
|
d0075978f8 | ||
|
|
c1e5777dc1 | ||
|
|
ec6c9fc330 | ||
|
|
9bc95db1f0 | ||
|
|
6d80a8e940 | ||
|
|
2a0f9651b9 | ||
|
|
51a10b0bd0 | ||
|
|
7e2f490fd0 | ||
|
|
2063393f20 | ||
|
|
4c15ed753e | ||
|
|
704fc91b9c | ||
|
|
757d74bac0 | ||
|
|
ff36889d47 | ||
|
|
3d10cd34d6 | ||
|
|
acaa1cabb5 | ||
|
|
e8f38d15f7 | ||
|
|
527aaefef1 | ||
|
|
a938ef8aa8 |
No files matched your search
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,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) {
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
struct VDP : Thread {
|
||||
Node::Object node;
|
||||
Node::Video::Screen screen;
|
||||
Node::Setting::Boolean overscan;
|
||||
|
||||
struct Debugger {
|
||||
//debugger.cpp
|
||||
|
||||
+51
-35
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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,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
@@ -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,4 +1,5 @@
|
||||
auto CPU::read(n16 address) -> n8 {
|
||||
if(auto result = platform->cheat(address)) return *result;
|
||||
|
||||
// VIDEO
|
||||
if (address == 0xe000) return vdp.data();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
Vendored
+20
@@ -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
|
||||
|
||||
Vendored
+4
@@ -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
Reference in new issue
Block a user