Update to v102r09 release.

byuu says:

Changelog:

  - MD: restructured DMA to a subclass of VDP
  - MD: implemented VRAM copy mode (fixes Langrisser II ... mostly)
  - MS: implemened PSG support [Cydrak]
  - GG: implemented PSG stereo sound support
  - MS: use the new struct Model {} design that other cores use

The MS/GG PSG should be feature complete, but I don't have good tests
for Game Gear stereo mode, nor for the noise channel. There's also a
really weird behavior with when to reload the channel counters on volume
register writes. I can confirm what Cydrak observed in that following
the docs and reloading always creates serious audio distortion problems.
So, more research is needed there.

To get the correct sound out of the PSG, I have to run it at 3.58MHz /
16, which seems really weird to me. The docs make it sound like it's
supposed to run at the full 3.58MHz. If we can really run it at
223.7KHz, then that's help reduce the overhead of PSG emulation, which
will definitely come in handy for Mega Drive, and possibly later Mega
CD, emulation.

I have not implemented the PSG into the Mega Drive just yet. Nor have I
implemented save states or cheat code support into the MS/GG cores yet.
The latter is next on my list.
This commit is contained in:
Tim Allen 2017-02-21 22:07:33 +11:00
parent d76c0c7e82
commit 8071da4c6a
20 changed files with 319 additions and 77 deletions

View File

@ -12,7 +12,7 @@ using namespace nall;
namespace Emulator { namespace Emulator {
static const string Name = "higan"; static const string Name = "higan";
static const string Version = "102.08"; static const string Version = "102.09";
static const string Author = "byuu"; static const string Author = "byuu";
static const string License = "GPLv3"; static const string License = "GPLv3";
static const string Website = "http://byuu.org/"; static const string Website = "http://byuu.org/";

View File

@ -1,36 +1,45 @@
auto VDP::dmaRun() -> void { auto VDP::DMA::run() -> void {
if(!io.dmaEnable) return; if(!io.enable || io.wait) return;
if(!io.command.bit(5)) return; if(!vdp.io.command.bit(5)) return;
if(io.dmaMode <= 1) return dmaLoad(); if(io.mode <= 1) return load();
if(io.dmaMode == 2) return dmaFill(); if(io.mode == 2) return fill();
if(io.dmaMode == 3) return dmaCopy(); if(io.mode == 3) return copy();
} }
auto VDP::dmaLoad() -> void { auto VDP::DMA::load() -> void {
cpu.wait |= Wait::VDP_DMA; cpu.wait |= Wait::VDP_DMA;
auto data = busCPU.readWord(io.dmaMode.bit(0) << 23 | io.dmaSource << 1); auto data = busCPU.readWord(io.mode.bit(0) << 23 | io.source << 1);
writeDataPort(data); vdp.writeDataPort(data);
io.dmaSource.bits(0,15)++; io.source.bits(0,15)++;
if(--io.dmaLength == 0) { if(--io.length == 0) {
io.command.bit(5) = 0; vdp.io.command.bit(5) = 0;
cpu.wait &=~ Wait::VDP_DMA; cpu.wait &=~ Wait::VDP_DMA;
} }
} }
auto VDP::dmaFill() -> void { auto VDP::DMA::fill() -> void {
if(io.dmaFillWait) return; auto data = io.fill;
vdp.writeDataPort(data << 8 | data << 0);
auto data = io.dmaFillByte; io.source.bits(0,15)++;
writeDataPort(data << 8 | data << 0); if(--io.length == 0) {
vdp.io.command.bit(5) = 0;
io.dmaSource.bits(0,15)++;
if(--io.dmaLength == 0) {
io.command.bit(5) = 0;
} }
} }
auto VDP::dmaCopy() -> void { auto VDP::DMA::copy() -> void {
auto data = vdp.vram[io.source.bits(0,14)];
vdp.writeDataPort(data);
io.source.bits(0,15)++;
if(--io.length == 0) {
vdp.io.command.bit(5) = 0;
}
}
auto VDP::DMA::power() -> void {
memory::fill(&io, sizeof(IO));
} }

View File

@ -74,8 +74,9 @@ auto VDP::writeDataPort(uint16 data) -> void {
io.commandPending = false; io.commandPending = false;
//DMA VRAM fill //DMA VRAM fill
if(io.dmaFillWait.lower()) { if(dma.io.wait.lower()) {
io.dmaFillByte = data >> 8; dma.io.fill = data >> 8;
return;
} }
//VRAM write //VRAM write
@ -132,7 +133,7 @@ auto VDP::writeControlPort(uint16 data) -> void {
io.command.bits(2,5) = data.bits(4,7); io.command.bits(2,5) = data.bits(4,7);
io.address.bits(14,15) = data.bits(0,1); io.address.bits(14,15) = data.bits(0,1);
io.dmaFillWait = io.dmaMode == 2 && io.command.bits(4,5) == 2; if(dma.io.mode == 3) dma.io.wait = false;
return; return;
} }
@ -162,12 +163,12 @@ auto VDP::writeControlPort(uint16 data) -> void {
case 0x01: { case 0x01: {
io.videoMode = data.bit(2); io.videoMode = data.bit(2);
io.overscan = data.bit(3); io.overscan = data.bit(3);
io.dmaEnable = data.bit(4); dma.io.enable = data.bit(4);
io.verticalBlankInterruptEnable = data.bit(5); io.verticalBlankInterruptEnable = data.bit(5);
io.displayEnable = data.bit(6); io.displayEnable = data.bit(6);
io.externalVRAM = data.bit(7); io.externalVRAM = data.bit(7);
if(!io.dmaEnable) io.command.bit(5) = 0; if(!dma.io.enable) io.command.bit(5) = 0;
return; return;
} }
@ -280,32 +281,33 @@ auto VDP::writeControlPort(uint16 data) -> void {
//DMA length //DMA length
case 0x13: { case 0x13: {
io.dmaLength.bits(0,7) = data.bits(0,7); dma.io.length.bits(0,7) = data.bits(0,7);
return; return;
} }
//DMA length //DMA length
case 0x14: { case 0x14: {
io.dmaLength.bits(8,15) = data.bits(0,7); dma.io.length.bits(8,15) = data.bits(0,7);
return; return;
} }
//DMA source //DMA source
case 0x15: { case 0x15: {
io.dmaSource.bits(0,7) = data.bits(0,7); dma.io.source.bits(0,7) = data.bits(0,7);
return; return;
} }
//DMA source //DMA source
case 0x16: { case 0x16: {
io.dmaSource.bits(8,15) = data.bits(0,7); dma.io.source.bits(8,15) = data.bits(0,7);
return; return;
} }
//DMA source //DMA source
case 0x17: { case 0x17: {
io.dmaSource.bits(16,21) = data.bits(0,5); dma.io.source.bits(16,21) = data.bits(0,5);
io.dmaMode = data.bits(6,7); dma.io.mode = data.bits(6,7);
dma.io.wait = dma.io.mode.bit(1);
return; return;
} }

View File

@ -42,7 +42,7 @@ auto VDP::main() -> void {
auto VDP::step(uint clocks) -> void { auto VDP::step(uint clocks) -> void {
while(clocks--) { while(clocks--) {
dmaRun(); dma.run();
Thread::step(1); Thread::step(1);
synchronize(cpu); synchronize(cpu);
synchronize(apu); synchronize(apu);
@ -63,6 +63,7 @@ auto VDP::power() -> void {
window.power(); window.power();
planeB.power(); planeB.power();
sprite.power(); sprite.power();
dma.power();
} }
} }

View File

@ -19,10 +19,23 @@ struct VDP : Thread {
auto writeControlPort(uint16 data) -> void; auto writeControlPort(uint16 data) -> void;
//dma.cpp //dma.cpp
auto dmaRun() -> void; struct DMA {
auto dmaLoad() -> void; auto run() -> void;
auto dmaFill() -> void; auto load() -> void;
auto dmaCopy() -> void; auto fill() -> void;
auto copy() -> void;
auto power() -> void;
struct IO {
uint2 mode;
uint22 source;
uint16 length;
uint8 fill;
boolean enable;
boolean wait;
} io;
} dma;
//render.cpp //render.cpp
auto scanline() -> void; auto scanline() -> void;
@ -119,15 +132,10 @@ private:
auto screenHeight() const -> uint { return io.overscan ? 240 : 224; } auto screenHeight() const -> uint { return io.overscan ? 240 : 224; }
uint16 vram[32768]; uint16 vram[32768];
uint16 vramExpansion[32768]; //not present in stock Mega Drive hardware
uint9 cram[64]; uint9 cram[64];
uint10 vsram[40]; uint10 vsram[40];
struct IO { struct IO {
//internal state
boolean dmaFillWait;
uint8 dmaFillByte;
//command //command
uint6 command; uint6 command;
uint16 address; uint16 address;
@ -142,7 +150,6 @@ private:
//$01 mode register 2 //$01 mode register 2
uint1 videoMode; //0 = Master System; 1 = Mega Drive uint1 videoMode; //0 = Master System; 1 = Mega Drive
uint1 overscan; //0 = 224 lines; 1 = 240 lines uint1 overscan; //0 = 224 lines; 1 = 240 lines
uint1 dmaEnable;
uint1 verticalBlankInterruptEnable; uint1 verticalBlankInterruptEnable;
uint1 displayEnable; uint1 displayEnable;
uint1 externalVRAM; uint1 externalVRAM;
@ -170,13 +177,6 @@ private:
//$0f data port auto-increment value //$0f data port auto-increment value
uint8 dataIncrement; uint8 dataIncrement;
//$13-$14 DMA length
uint16 dmaLength;
//$15-$17 DMA source
uint22 dmaSource;
uint2 dmaMode;
} io; } io;
struct State { struct State {

View File

@ -19,7 +19,7 @@ auto Bus::in(uint8 addr) -> uint8 {
switch(addr >> 6) { switch(addr >> 6) {
case 0: { case 0: {
if(system.model() == Model::GameGear) { if(Model::GameGear()) {
bool start = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 6); bool start = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 6);
return start << 7 | 0x7f; return start << 7 | 0x7f;
} }
@ -36,7 +36,7 @@ auto Bus::in(uint8 addr) -> uint8 {
} }
case 3: { case 3: {
if(system.model() == Model::MasterSystem) { if(Model::MasterSystem()) {
bool reset = !platform->inputPoll(ID::Port::Hardware, ID::Device::MasterSystemControls, 0); bool reset = !platform->inputPoll(ID::Port::Hardware, ID::Device::MasterSystemControls, 0);
auto port1 = peripherals.controllerPort1->readData(); auto port1 = peripherals.controllerPort1->readData();
auto port2 = peripherals.controllerPort2->readData(); auto port2 = peripherals.controllerPort2->readData();
@ -46,7 +46,8 @@ auto Bus::in(uint8 addr) -> uint8 {
return port2.bits(2,5) << 0 | reset << 4 | 1 << 5 | port1.bit(6) << 6 | port2.bit(6) << 7; return port2.bits(2,5) << 0 | reset << 4 | 1 << 5 | port1.bit(6) << 6 | port2.bit(6) << 7;
} }
} }
if(system.model() == Model::GameGear) {
if(Model::GameGear()) {
bool up = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 0); bool up = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 0);
bool down = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 1); bool down = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 1);
bool left = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 2); bool left = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 2);
@ -61,6 +62,7 @@ auto Bus::in(uint8 addr) -> uint8 {
return 0xff; return 0xff;
} }
} }
return 0xff; return 0xff;
} }
@ -70,8 +72,16 @@ auto Bus::in(uint8 addr) -> uint8 {
} }
auto Bus::out(uint8 addr, uint8 data) -> void { auto Bus::out(uint8 addr, uint8 data) -> void {
if(addr == 0x06) {
if(Model::GameGear()) return psg.balance(data);
}
switch(addr >> 6) { switch(addr >> 6) {
case 1: {
return psg.write(data);
}
case 2: { case 2: {
return !addr.bit(0) ? vdp.data(data) : vdp.control(data); return !addr.bit(0) ? vdp.data(data) : vdp.control(data);
} }

View File

@ -8,17 +8,16 @@ Cartridge cartridge;
auto Cartridge::load() -> bool { auto Cartridge::load() -> bool {
information = {}; information = {};
switch(system.model()) { if(Model::MasterSystem()) {
case Model::MasterSystem:
if(auto pathID = platform->load(ID::MasterSystem, "Master System", "ms")) { if(auto pathID = platform->load(ID::MasterSystem, "Master System", "ms")) {
information.pathID = pathID(); information.pathID = pathID();
} else return false; } else return false;
break; }
case Model::GameGear:
if(Model::GameGear()) {
if(auto pathID = platform->load(ID::GameGear, "Game Gear", "gg")) { if(auto pathID = platform->load(ID::GameGear, "Game Gear", "gg")) {
information.pathID = pathID(); information.pathID = pathID();
} else return false; } else return false;
break;
} }
if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) { if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) {

View File

@ -31,7 +31,7 @@ auto CPU::step(uint clocks) -> void {
//called once per frame //called once per frame
auto CPU::pollPause() -> void { auto CPU::pollPause() -> void {
if(system.model() == Model::MasterSystem) { if(Model::MasterSystem()) {
static bool pause = 0; static bool pause = 0;
bool state = platform->inputPoll(ID::Port::Hardware, ID::Device::MasterSystemControls, 1); bool state = platform->inputPoll(ID::Port::Hardware, ID::Device::MasterSystemControls, 1);
if(!pause && state) setNMI(1); if(!pause && state) setNMI(1);

View File

@ -53,6 +53,6 @@ auto GameGearInterface::videoColor(uint32 color) -> uint64 {
} }
auto GameGearInterface::load(uint id) -> bool { auto GameGearInterface::load(uint id) -> bool {
if(id == ID::GameGear) return system.load(this, Model::GameGear); if(id == ID::GameGear) return system.load(this, System::Model::GameGear);
return false; return false;
} }

View File

@ -69,6 +69,6 @@ auto MasterSystemInterface::videoColor(uint32 color) -> uint64 {
} }
auto MasterSystemInterface::load(uint id) -> bool { auto MasterSystemInterface::load(uint id) -> bool {
if(id == ID::MasterSystem) return system.load(this, Model::MasterSystem); if(id == ID::MasterSystem) return system.load(this, System::Model::MasterSystem);
return false; return false;
} }

View File

@ -16,11 +16,6 @@ namespace MasterSystem {
extern Scheduler scheduler; extern Scheduler scheduler;
struct Interface; struct Interface;
enum class Model : uint {
MasterSystem,
GameGear,
};
struct Thread : Emulator::Thread { struct Thread : Emulator::Thread {
auto create(auto (*entrypoint)() -> void, double frequency) -> void { auto create(auto (*entrypoint)() -> void, double frequency) -> void {
Emulator::Thread::create(entrypoint, frequency); Emulator::Thread::create(entrypoint, frequency);
@ -32,6 +27,11 @@ namespace MasterSystem {
} }
}; };
struct Model {
inline static auto MasterSystem() -> bool;
inline static auto GameGear() -> bool;
};
#include <ms/controller/controller.hpp> #include <ms/controller/controller.hpp>
#include <ms/cpu/cpu.hpp> #include <ms/cpu/cpu.hpp>

82
higan/ms/psg/io.cpp Normal file
View File

@ -0,0 +1,82 @@
//note: tone is supposed to reload counters on volume writes
//however, if this is always done, the output is very grainy
//as such, this behavior is suppressed when pitch >= 2 (which is a hack)
auto PSG::write(uint8 data) -> void {
bool l = data.bit(7);
if(l) select = data.bits(4,6);
switch(select) {
case 0: {
if(l) tone0.pitch.bits(0,3) = data.bits(0,3);
else tone0.pitch.bits(4,9) = data.bits(0,5);
break;
}
case 1: {
tone0.volume = data.bits(0,3);
if(tone0.pitch < 2) {
tone0.output = 1;
tone0.counter = tone0.pitch;
}
break;
}
case 2: {
if(l) tone1.pitch.bits(0,3) = data.bits(0,3);
else tone1.pitch.bits(4,9) = data.bits(0,5);
break;
}
case 3: {
tone1.volume = data.bits(0,3);
if(tone1.pitch < 2) {
tone1.output = 1;
tone1.counter = tone1.pitch;
}
break;
}
case 4: {
if(l) tone2.pitch.bits(0,3) = data.bits(0,3);
else tone2.pitch.bits(4,9) = data.bits(0,5);
break;
}
case 5: {
tone2.volume = data.bits(0,3);
if(tone2.pitch < 2) {
tone2.output = 1;
tone2.counter = tone2.pitch;
}
break;
}
case 6: {
noise.rate = data.bits(0,1);
noise.enable = data.bit(2);
noise.lfsr = 0x8000;
break;
}
case 7: {
noise.volume = data.bits(0,3);
noise.output = 1;
break;
}
}
}
//Game Gear only
auto PSG::balance(uint8 data) -> void {
tone0.right = data.bit(0);
tone1.right = data.bit(1);
tone2.right = data.bit(2);
noise.right = data.bit(3);
tone0.left = data.bit(4);
tone1.left = data.bit(5);
tone2.left = data.bit(6);
noise.left = data.bit(7);
}

30
higan/ms/psg/noise.cpp Normal file
View File

@ -0,0 +1,30 @@
auto PSG::Noise::run() -> void {
auto latch = clock;
counter++;
if(rate < 3) {
clock ^= counter & ((16 << rate) - 1) == 0;
} else {
clock ^= psg.tone2.clock;
}
if(!latch && clock) {
auto eor = enable ? ~lfsr >> 3 : 0;
lfsr = (lfsr ^ eor) << 15 | lfsr >> 1;
}
output = lfsr.bit(0);
}
auto PSG::Noise::power() -> void {
volume = ~0;
counter = 0;
enable = 0;
rate = 0;
lfsr = 0x8000;
clock = 0;
output = 0;
left = 1;
right = 1;
}

View File

@ -3,14 +3,42 @@
namespace MasterSystem { namespace MasterSystem {
PSG psg; PSG psg;
#include "io.cpp"
#include "tone.cpp"
#include "noise.cpp"
auto PSG::Enter() -> void { auto PSG::Enter() -> void {
while(true) scheduler.synchronize(), psg.main(); while(true) scheduler.synchronize(), psg.main();
} }
auto PSG::main() -> void { auto PSG::main() -> void {
tone0.run();
tone1.run();
tone2.run();
noise.run();
int left = 0;
if(tone0.output && tone0.left) left += levels[tone0.volume];
if(tone1.output && tone1.left) left += levels[tone1.volume];
if(tone2.output && tone2.left) left += levels[tone2.volume];
if(noise.output && noise.left) left += levels[noise.volume];
lowpassLeft += (left - lowpassLeft) * 20.0 / 256.0;
left = left * 2.0 / 6.0 + lowpassLeft * 3.0 / 4.0;
left = sclamp<16>(left);
int right = 0;
if(tone0.output && tone0.right) right += levels[tone0.volume];
if(tone1.output && tone1.right) right += levels[tone1.volume];
if(tone2.output && tone2.right) right += levels[tone2.volume];
if(noise.output && noise.right) right += levels[noise.volume];
lowpassRight += (right - lowpassRight) * 20.0 / 256.0;
right = right * 2.0 / 6.0 + lowpassRight * 3.0 / 4.0;
right = sclamp<16>(right);
step(1); step(1);
stream->sample(0.0, 0.0); stream->sample(left / 32768.0, right / 32768.0);
} }
auto PSG::step(uint clocks) -> void { auto PSG::step(uint clocks) -> void {
@ -19,8 +47,23 @@ auto PSG::step(uint clocks) -> void {
} }
auto PSG::power() -> void { auto PSG::power() -> void {
create(PSG::Enter, system.colorburst()); //Master System is monaural; Game Gear is stereo
stream = Emulator::audio.createStream(2, system.colorburst()); //use stereo mode for both; output same sample to both channels for Master System
create(PSG::Enter, system.colorburst() / 16.0);
stream = Emulator::audio.createStream(2, frequency());
select = 0;
lowpassLeft = 0;
lowpassRight = 0;
for(auto n : range(15)) {
levels[n] = 0x3fff * pow(2, n * -2.0 / 6.0) + 0.5;
}
levels[15] = 0;
tone0.power();
tone1.power();
tone2.power();
noise.power();
} }
} }

View File

@ -8,6 +8,48 @@ struct PSG : Thread {
auto step(uint clocks) -> void; auto step(uint clocks) -> void;
auto power() -> void; auto power() -> void;
//io.cpp
auto write(uint8 data) -> void;
auto balance(uint8 data) -> void;
private:
struct Tone {
//tone.cpp
auto run() -> void;
auto power() -> void;
uint4 volume;
uint10 counter;
uint10 pitch;
uint1 clock;
uint1 output;
uint1 left;
uint1 right;
} tone0, tone1, tone2;
struct Noise {
//noise.cpp
auto run() -> void;
auto power() -> void;
uint4 volume;
uint6 counter;
uint1 enable;
uint2 rate;
uint16 lfsr;
uint1 clock;
uint1 output;
uint1 left;
uint1 right;
} noise;
uint3 select;
int lowpassLeft;
int lowpassRight;
uint16 levels[16];
}; };
extern PSG psg; extern PSG psg;

19
higan/ms/psg/tone.cpp Normal file
View File

@ -0,0 +1,19 @@
auto PSG::Tone::run() -> void {
clock = 0;
if(--counter) return;
clock = 1;
counter = pitch;
output ^= 1;
}
auto PSG::Tone::power() -> void {
volume = ~0;
counter = 0;
pitch = 0;
clock = 0;
output = 0;
left = 1;
right = 1;
}

View File

@ -15,7 +15,7 @@ auto Peripherals::reset() -> void {
auto Peripherals::connect(uint port, uint device) -> void { auto Peripherals::connect(uint port, uint device) -> void {
cpu.peripherals.reset(); cpu.peripherals.reset();
if(system.model() == Model::MasterSystem) { if(Model::MasterSystem()) {
if(port == ID::Port::Controller1) { if(port == ID::Port::Controller1) {
settings.controllerPort1 = device; settings.controllerPort1 = device;
if(!system.loaded()) return; if(!system.loaded()) return;

View File

@ -1,4 +1,6 @@
struct System { struct System {
enum class Model : uint { MasterSystem, GameGear };
auto loaded() const -> bool { return information.loaded; } auto loaded() const -> bool { return information.loaded; }
auto model() const -> Model { return information.model; } auto model() const -> Model { return information.model; }
auto colorburst() const -> double { return information.colorburst; } auto colorburst() const -> double { return information.colorburst; }
@ -33,3 +35,6 @@ struct Peripherals {
extern System system; extern System system;
extern Peripherals peripherals; extern Peripherals peripherals;
auto Model::MasterSystem() -> bool { return system.model() == System::Model::MasterSystem; }
auto Model::GameGear() -> bool { return system.model() == System::Model::GameGear; }

View File

@ -51,8 +51,8 @@ auto VDP::data(uint8 data) -> void {
vram[io.address++] = data; vram[io.address++] = data;
} else { } else {
uint mask = 0; uint mask = 0;
if(system.model() == Model::MasterSystem) mask = 0x1f; if(Model::MasterSystem()) mask = 0x1f;
if(system.model() == Model::GameGear) mask = 0x3f; if(Model::GameGear()) mask = 0x3f;
cram[io.address++ & mask] = data; cram[io.address++ & mask] = data;
} }
} }

View File

@ -72,7 +72,7 @@ auto VDP::step(uint clocks) -> void {
} }
auto VDP::refresh() -> void { auto VDP::refresh() -> void {
if(system.model() == Model::MasterSystem) { if(Model::MasterSystem()) {
//center the video output vertically in the viewport //center the video output vertically in the viewport
uint32* screen = buffer; uint32* screen = buffer;
if(vlines() == 224) screen += 16 * 256; if(vlines() == 224) screen += 16 * 256;
@ -81,7 +81,7 @@ auto VDP::refresh() -> void {
Emulator::video.refresh(screen, 256 * sizeof(uint32), 256, 240); Emulator::video.refresh(screen, 256 * sizeof(uint32), 256, 240);
} }
if(system.model() == Model::GameGear) { if(Model::GameGear()) {
Emulator::video.refresh(buffer + 48 * 256 + 48, 256 * sizeof(uint32), 160, 144); Emulator::video.refresh(buffer + 48 * 256 + 48, 256 * sizeof(uint32), 160, 144);
} }
} }
@ -107,11 +107,11 @@ auto VDP::power() -> void {
} }
auto VDP::palette(uint5 index) -> uint12 { auto VDP::palette(uint5 index) -> uint12 {
if(system.model() == Model::MasterSystem) { if(Model::MasterSystem()) {
return cram[index]; return cram[index];
} }
if(system.model() == Model::GameGear) { if(Model::GameGear()) {
return cram[index * 2 + 0] << 0 | cram[index * 2 + 1] << 8; return cram[index * 2 + 0] << 0 | cram[index * 2 + 1] << 8;
} }