mirror of https://github.com/bsnes-emu/bsnes.git
Update to v097 release.
byuu says: This release features improvements to all emulation cores, but most substantially for the Game Boy core. All of blargg's test ROMs that pass in gambatte now either pass in higan, or are off by 1-2 clocks (the actual behaviors are fully emulated.) I consider the Game Boy core to now be fairly accurate, but there's still more improvements to be had. Also, what's sure to be a major feature for some: higan now has full support for loading and playing ordinary ROM files, whether they have copier headers, weird extensions, or are inside compressed archives. You can load these games from the command-line, from the main Library menu (via Load ROM Image), or via drag-and-drop on the main higan window. Of course, fans of game folders and the library need not worry: that's still there as well. Also new, you can drop the (uncompressed) Game Boy Advance BIOS onto the higan main window to install it into the correct location with the correct file name. Lastly, this release technically restores Mac OS X support. However, it's still not very stable, so I have decided against releasing binaries at this time. I'd rather not rush this and leave a bad first impression for OS X users. Changelog (since v096): - higan: project source code hierarchy restructured; icarus directly integrated - higan: added software emulation of color-bleed, LCD-refresh, scanlines, interlacing - icarus: you can now load and import ROM files/archives from the main higan menu - NES: fixed manifest parsing for board mirroring and VRC pinouts - SNES: fixed manifest for Star Ocean - SNES: fixed manifest for Rockman X2,X3 - GB: enabling LCD restarts frame - GB: emulated extra OAM STAT IRQ quirk required for GBVideoPlayer (Shonumi) - GB: VBK, BGPI, OBPI are readable - GB: OAM DMA happens inside PPU core instead of CPU core - GB: fixed APU length and sweep operations - GB: emulated wave RAM quirks when accessing while channel is enabled - GB: improved timings of several CPU opcodes (gekkio) - GB: improved timings of OAM DMA refresh (gekkio) - GB: CPU uses open collector logic; return 0xFF for unmapped memory (gekkio) - GBA: fixed sequencer enable flags; fixes audio in Zelda - Minish Cap (Jonas Quinn) - GBA: fixed disassembler masking error (Lioncash) - hiro: Cocoa support added; higan can now be compiled on Mac OS X 10.7+ - nall: improved program path detection on Windows - higan/Windows: moved configuration data from %appdata% to %localappdata% - higan/Linux,BSD: moved configuration data from ~/.config/higan to ~/.local/higan
This commit is contained in:
parent
12df278c5b
commit
1fdd0582fc
|
@ -6,7 +6,7 @@ using namespace nall;
|
||||||
|
|
||||||
namespace Emulator {
|
namespace Emulator {
|
||||||
static const string Name = "higan";
|
static const string Name = "higan";
|
||||||
static const string Version = "096.08";
|
static const string Version = "097";
|
||||||
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/";
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
namespace GameBoy {
|
namespace GameBoy {
|
||||||
|
|
||||||
|
#include "sequencer/sequencer.cpp"
|
||||||
#include "square1/square1.cpp"
|
#include "square1/square1.cpp"
|
||||||
#include "square2/square2.cpp"
|
#include "square2/square2.cpp"
|
||||||
#include "wave/wave.cpp"
|
#include "wave/wave.cpp"
|
||||||
#include "noise/noise.cpp"
|
#include "noise/noise.cpp"
|
||||||
#include "master/master.cpp"
|
|
||||||
#include "serialization.cpp"
|
#include "serialization.cpp"
|
||||||
APU apu;
|
APU apu;
|
||||||
|
|
||||||
|
@ -20,7 +20,19 @@ auto APU::main() -> void {
|
||||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(stage == 0) { //512hz
|
square1.run();
|
||||||
|
square2.run();
|
||||||
|
wave.run();
|
||||||
|
noise.run();
|
||||||
|
sequencer.run();
|
||||||
|
|
||||||
|
hipass(sequencer.center, sequencer.centerBias);
|
||||||
|
hipass(sequencer.left, sequencer.leftBias);
|
||||||
|
hipass(sequencer.right, sequencer.rightBias);
|
||||||
|
|
||||||
|
interface->audioSample(sequencer.left, sequencer.right);
|
||||||
|
|
||||||
|
if(cycle == 0) { //512hz
|
||||||
if(phase == 0 || phase == 2 || phase == 4 || phase == 6) { //256hz
|
if(phase == 0 || phase == 2 || phase == 4 || phase == 6) { //256hz
|
||||||
square1.clockLength();
|
square1.clockLength();
|
||||||
square2.clockLength();
|
square2.clockLength();
|
||||||
|
@ -37,25 +49,16 @@ auto APU::main() -> void {
|
||||||
}
|
}
|
||||||
phase++;
|
phase++;
|
||||||
}
|
}
|
||||||
stage++;
|
cycle++;
|
||||||
|
|
||||||
square1.run();
|
|
||||||
square2.run();
|
|
||||||
wave.run();
|
|
||||||
noise.run();
|
|
||||||
master.run();
|
|
||||||
|
|
||||||
hipass(master.center, master.centerBias);
|
|
||||||
hipass(master.left, master.leftBias);
|
|
||||||
hipass(master.right, master.rightBias);
|
|
||||||
|
|
||||||
interface->audioSample(master.left, master.right);
|
|
||||||
|
|
||||||
clock += cpu.frequency;
|
clock += cpu.frequency;
|
||||||
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(scheduler.active_thread = cpu.thread);
|
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) {
|
||||||
|
co_switch(scheduler.active_thread = cpu.thread);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//filter to remove DC bias
|
||||||
auto APU::hipass(int16& sample, int64& bias) -> void {
|
auto APU::hipass(int16& sample, int64& bias) -> void {
|
||||||
bias += ((((int64)sample << 16) - (bias >> 16)) * 57593) >> 16;
|
bias += ((((int64)sample << 16) - (bias >> 16)) * 57593) >> 16;
|
||||||
sample = sclamp<16>(sample - (bias >> 32));
|
sample = sclamp<16>(sample - (bias >> 32));
|
||||||
|
@ -65,14 +68,13 @@ auto APU::power() -> void {
|
||||||
create(Main, 2 * 1024 * 1024);
|
create(Main, 2 * 1024 * 1024);
|
||||||
for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;
|
for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;
|
||||||
|
|
||||||
stage = 0;
|
|
||||||
phase = 0;
|
|
||||||
|
|
||||||
square1.power();
|
square1.power();
|
||||||
square2.power();
|
square2.power();
|
||||||
wave.power();
|
wave.power();
|
||||||
noise.power();
|
noise.power();
|
||||||
master.power();
|
sequencer.power();
|
||||||
|
phase = 0;
|
||||||
|
cycle = 0;
|
||||||
|
|
||||||
LinearFeedbackShiftRegisterGenerator r;
|
LinearFeedbackShiftRegisterGenerator r;
|
||||||
for(auto& n : wave.pattern) n = r();
|
for(auto& n : wave.pattern) n = r();
|
||||||
|
@ -83,16 +85,16 @@ auto APU::mmio_read(uint16 addr) -> uint8 {
|
||||||
if(addr >= 0xff15 && addr <= 0xff19) return square2.read(addr);
|
if(addr >= 0xff15 && addr <= 0xff19) return square2.read(addr);
|
||||||
if(addr >= 0xff1a && addr <= 0xff1e) return wave.read(addr);
|
if(addr >= 0xff1a && addr <= 0xff1e) return wave.read(addr);
|
||||||
if(addr >= 0xff1f && addr <= 0xff23) return noise.read(addr);
|
if(addr >= 0xff1f && addr <= 0xff23) return noise.read(addr);
|
||||||
if(addr >= 0xff24 && addr <= 0xff26) return master.read(addr);
|
if(addr >= 0xff24 && addr <= 0xff26) return sequencer.read(addr);
|
||||||
if(addr >= 0xff30 && addr <= 0xff3f) return wave.read(addr);
|
if(addr >= 0xff30 && addr <= 0xff3f) return wave.read(addr);
|
||||||
return 0xff;
|
return 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto APU::mmio_write(uint16 addr, uint8 data) -> void {
|
auto APU::mmio_write(uint16 addr, uint8 data) -> void {
|
||||||
if(!master.enable) {
|
if(!sequencer.enable) {
|
||||||
bool valid = addr == 0xff26; //NR52
|
bool valid = addr == 0xff26; //NR52
|
||||||
if(!system.cgb()) {
|
if(!system.cgb()) {
|
||||||
//NRx1 length is writable only on DMG/SGB; not on CGB
|
//NRx1 length is writable only on DMG,SGB; not on CGB
|
||||||
if(addr == 0xff11) valid = true, data &= 0x3f; //NR11; duty is not writable (remains 0)
|
if(addr == 0xff11) valid = true, data &= 0x3f; //NR11; duty is not writable (remains 0)
|
||||||
if(addr == 0xff16) valid = true, data &= 0x3f; //NR21; duty is not writable (remains 0)
|
if(addr == 0xff16) valid = true, data &= 0x3f; //NR21; duty is not writable (remains 0)
|
||||||
if(addr == 0xff1b) valid = true; //NR31
|
if(addr == 0xff1b) valid = true; //NR31
|
||||||
|
@ -105,7 +107,7 @@ auto APU::mmio_write(uint16 addr, uint8 data) -> void {
|
||||||
if(addr >= 0xff15 && addr <= 0xff19) return square2.write(addr, data);
|
if(addr >= 0xff15 && addr <= 0xff19) return square2.write(addr, data);
|
||||||
if(addr >= 0xff1a && addr <= 0xff1e) return wave.write(addr, data);
|
if(addr >= 0xff1a && addr <= 0xff1e) return wave.write(addr, data);
|
||||||
if(addr >= 0xff1f && addr <= 0xff23) return noise.write(addr, data);
|
if(addr >= 0xff1f && addr <= 0xff23) return noise.write(addr, data);
|
||||||
if(addr >= 0xff24 && addr <= 0xff26) return master.write(addr, data);
|
if(addr >= 0xff24 && addr <= 0xff26) return sequencer.write(addr, data);
|
||||||
if(addr >= 0xff30 && addr <= 0xff3f) return wave.write(addr, data);
|
if(addr >= 0xff30 && addr <= 0xff3f) return wave.write(addr, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,16 +13,16 @@ struct APU : Thread, MMIO {
|
||||||
#include "square2/square2.hpp"
|
#include "square2/square2.hpp"
|
||||||
#include "wave/wave.hpp"
|
#include "wave/wave.hpp"
|
||||||
#include "noise/noise.hpp"
|
#include "noise/noise.hpp"
|
||||||
#include "master/master.hpp"
|
#include "sequencer/sequencer.hpp"
|
||||||
|
|
||||||
uint12 stage;
|
|
||||||
uint3 phase;
|
|
||||||
|
|
||||||
Square1 square1;
|
Square1 square1;
|
||||||
Square2 square2;
|
Square2 square2;
|
||||||
Wave wave;
|
Wave wave;
|
||||||
Noise noise;
|
Noise noise;
|
||||||
Master master;
|
Sequencer sequencer;
|
||||||
|
|
||||||
|
uint3 phase; //high 3-bits of clock counter
|
||||||
|
uint12 cycle; //low 12-bits of clock counter
|
||||||
};
|
};
|
||||||
|
|
||||||
extern APU apu;
|
extern APU apu;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
auto APU::Master::run() -> void {
|
auto APU::Sequencer::run() -> void {
|
||||||
if(enable == false) {
|
if(enable == false) {
|
||||||
center = 0;
|
center = 0;
|
||||||
left = 0;
|
left = 0;
|
||||||
|
@ -39,7 +39,7 @@ auto APU::Master::run() -> void {
|
||||||
right >>= 1;
|
right >>= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto APU::Master::read(uint16 addr) -> uint8 {
|
auto APU::Sequencer::read(uint16 addr) -> uint8 {
|
||||||
if(addr == 0xff24) { //NR50
|
if(addr == 0xff24) { //NR50
|
||||||
return leftEnable << 7 | leftVolume << 4 | rightEnable << 3 | rightVolume;
|
return leftEnable << 7 | leftVolume << 4 | rightEnable << 3 | rightVolume;
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ auto APU::Master::read(uint16 addr) -> uint8 {
|
||||||
return 0xff;
|
return 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto APU::Master::write(uint16 addr, uint8 data) -> void {
|
auto APU::Sequencer::write(uint16 addr, uint8 data) -> void {
|
||||||
if(addr == 0xff24) { //NR50
|
if(addr == 0xff24) { //NR50
|
||||||
leftEnable = (uint1)(data >> 7);
|
leftEnable = (uint1)(data >> 7);
|
||||||
leftVolume = (uint3)(data >> 4);
|
leftVolume = (uint3)(data >> 4);
|
||||||
|
@ -103,7 +103,7 @@ auto APU::Master::write(uint16 addr, uint8 data) -> void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto APU::Master::power() -> void {
|
auto APU::Sequencer::power() -> void {
|
||||||
leftEnable = 0;
|
leftEnable = 0;
|
||||||
leftVolume = 0;
|
leftVolume = 0;
|
||||||
rightEnable = 0;
|
rightEnable = 0;
|
||||||
|
@ -127,7 +127,7 @@ auto APU::Master::power() -> void {
|
||||||
rightBias = 0;
|
rightBias = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto APU::Master::serialize(serializer& s) -> void {
|
auto APU::Sequencer::serialize(serializer& s) -> void {
|
||||||
s.integer(leftEnable);
|
s.integer(leftEnable);
|
||||||
s.integer(leftVolume);
|
s.integer(leftVolume);
|
||||||
s.integer(rightEnable);
|
s.integer(rightEnable);
|
|
@ -1,4 +1,4 @@
|
||||||
struct Master {
|
struct Sequencer {
|
||||||
auto run() -> void;
|
auto run() -> void;
|
||||||
auto read(uint16 addr) -> uint8;
|
auto read(uint16 addr) -> uint8;
|
||||||
auto write(uint16 addr, uint8 data) -> void;
|
auto write(uint16 addr, uint8 data) -> void;
|
|
@ -1,12 +1,12 @@
|
||||||
auto APU::serialize(serializer& s) -> void {
|
auto APU::serialize(serializer& s) -> void {
|
||||||
Thread::serialize(s);
|
Thread::serialize(s);
|
||||||
|
|
||||||
s.integer(stage);
|
|
||||||
s.integer(phase);
|
|
||||||
|
|
||||||
square1.serialize(s);
|
square1.serialize(s);
|
||||||
square2.serialize(s);
|
square2.serialize(s);
|
||||||
wave.serialize(s);
|
wave.serialize(s);
|
||||||
noise.serialize(s);
|
noise.serialize(s);
|
||||||
master.serialize(s);
|
sequencer.serialize(s);
|
||||||
|
|
||||||
|
s.integer(phase);
|
||||||
|
s.integer(cycle);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,12 @@ auto APU::Wave::getPattern(uint5 offset) const -> uint4 {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto APU::Wave::run() -> void {
|
auto APU::Wave::run() -> void {
|
||||||
|
if(patternHold) patternHold--;
|
||||||
|
|
||||||
if(period && --period == 0) {
|
if(period && --period == 0) {
|
||||||
period = 1 * (2048 - frequency);
|
period = 1 * (2048 - frequency);
|
||||||
patternSample = getPattern(++patternOffset);
|
patternSample = getPattern(++patternOffset);
|
||||||
|
patternHold = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const uint shift[] = {4, 0, 1, 2}; //0%, 100%, 50%, 25%
|
static const uint shift[] = {4, 0, 1, 2}; //0%, 100%, 50%, 25%
|
||||||
|
@ -43,7 +46,12 @@ auto APU::Wave::read(uint16 addr) -> uint8 {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr >= 0xff30 && addr <= 0xff3f) {
|
if(addr >= 0xff30 && addr <= 0xff3f) {
|
||||||
return pattern[addr & 15];
|
if(enable) {
|
||||||
|
if(!system.cgb() && !patternHold) return 0xff;
|
||||||
|
return pattern[patternOffset >> 1];
|
||||||
|
} else {
|
||||||
|
return pattern[addr & 15];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0xff;
|
return 0xff;
|
||||||
|
@ -77,9 +85,25 @@ auto APU::Wave::write(uint16 addr, uint8 data) -> void {
|
||||||
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
|
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
|
||||||
|
|
||||||
if(initialize) {
|
if(initialize) {
|
||||||
|
if(!system.cgb() && patternHold) {
|
||||||
|
//DMG,SGB trigger while channel is being read corrupts wave RAM
|
||||||
|
if((patternOffset >> 1) <= 3) {
|
||||||
|
//if current pattern is with 0-3; only byte 0 is corrupted
|
||||||
|
pattern[0] = pattern[patternOffset >> 1];
|
||||||
|
} else {
|
||||||
|
//if current pattern is within 4-15; pattern&~3 is copied to pattern[0-3]
|
||||||
|
pattern[0] = pattern[((patternOffset >> 1) & ~3) + 0];
|
||||||
|
pattern[1] = pattern[((patternOffset >> 1) & ~3) + 1];
|
||||||
|
pattern[2] = pattern[((patternOffset >> 1) & ~3) + 2];
|
||||||
|
pattern[3] = pattern[((patternOffset >> 1) & ~3) + 3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enable = dacEnable;
|
enable = dacEnable;
|
||||||
period = 1 * (2048 - frequency);
|
period = 1 * (2048 - frequency);
|
||||||
patternOffset = 0;
|
patternOffset = 0;
|
||||||
|
patternSample = 0;
|
||||||
|
patternHold = 0;
|
||||||
|
|
||||||
if(!length) {
|
if(!length) {
|
||||||
length = 256;
|
length = 256;
|
||||||
|
@ -89,7 +113,12 @@ auto APU::Wave::write(uint16 addr, uint8 data) -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr >= 0xff30 && addr <= 0xff3f) {
|
if(addr >= 0xff30 && addr <= 0xff3f) {
|
||||||
pattern[addr & 15] = data;
|
if(enable) {
|
||||||
|
if(!system.cgb() && !patternHold) return;
|
||||||
|
pattern[patternOffset >> 1] = data;
|
||||||
|
} else {
|
||||||
|
pattern[addr & 15] = data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +134,7 @@ auto APU::Wave::power(bool initializeLength) -> void {
|
||||||
period = 0;
|
period = 0;
|
||||||
patternOffset = 0;
|
patternOffset = 0;
|
||||||
patternSample = 0;
|
patternSample = 0;
|
||||||
|
patternHold = 0;
|
||||||
|
|
||||||
if(initializeLength) length = 256;
|
if(initializeLength) length = 256;
|
||||||
}
|
}
|
||||||
|
@ -123,4 +153,5 @@ auto APU::Wave::serialize(serializer& s) -> void {
|
||||||
s.integer(period);
|
s.integer(period);
|
||||||
s.integer(patternOffset);
|
s.integer(patternOffset);
|
||||||
s.integer(patternSample);
|
s.integer(patternSample);
|
||||||
|
s.integer(patternHold);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,4 +22,5 @@ struct Wave {
|
||||||
uint period;
|
uint period;
|
||||||
uint5 patternOffset;
|
uint5 patternOffset;
|
||||||
uint4 patternSample;
|
uint4 patternSample;
|
||||||
|
uint patternHold;
|
||||||
};
|
};
|
||||||
|
|
|
@ -117,7 +117,6 @@ auto CPU::power() -> void {
|
||||||
bus.mmio[0xff06] = this; //TMA
|
bus.mmio[0xff06] = this; //TMA
|
||||||
bus.mmio[0xff07] = this; //TAC
|
bus.mmio[0xff07] = this; //TAC
|
||||||
bus.mmio[0xff0f] = this; //IF
|
bus.mmio[0xff0f] = this; //IF
|
||||||
bus.mmio[0xff46] = this; //DMA
|
|
||||||
bus.mmio[0xffff] = this; //IE
|
bus.mmio[0xffff] = this; //IE
|
||||||
|
|
||||||
if(system.cgb()) {
|
if(system.cgb()) {
|
||||||
|
@ -199,10 +198,6 @@ auto CPU::power() -> void {
|
||||||
status.interrupt_enable_timer = 0;
|
status.interrupt_enable_timer = 0;
|
||||||
status.interrupt_enable_stat = 0;
|
status.interrupt_enable_stat = 0;
|
||||||
status.interrupt_enable_vblank = 0;
|
status.interrupt_enable_vblank = 0;
|
||||||
|
|
||||||
oamdma.active = false;
|
|
||||||
oamdma.clock = 0;
|
|
||||||
oamdma.bank = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,12 +107,6 @@ struct CPU : Processor::LR35902, Thread, MMIO {
|
||||||
bool interrupt_enable_vblank;
|
bool interrupt_enable_vblank;
|
||||||
} status;
|
} status;
|
||||||
|
|
||||||
struct OAMDMA {
|
|
||||||
bool active;
|
|
||||||
uint clock;
|
|
||||||
uint8 bank;
|
|
||||||
} oamdma;
|
|
||||||
|
|
||||||
uint8 wram[32768]; //GB=8192, GBC=32768
|
uint8 wram[32768]; //GB=8192, GBC=32768
|
||||||
uint8 hram[128];
|
uint8 hram[128];
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,16 +6,12 @@ auto CPU::op_io() -> void {
|
||||||
auto CPU::op_read(uint16 addr) -> uint8 {
|
auto CPU::op_read(uint16 addr) -> uint8 {
|
||||||
cycle_edge();
|
cycle_edge();
|
||||||
add_clocks(4);
|
add_clocks(4);
|
||||||
//OAM is inaccessible during OAMDMA transfer
|
|
||||||
if(oamdma.active && oamdma.clock >= 8 && addr >= 0xfe00 && addr <= 0xfe9f) return 0xff;
|
|
||||||
return bus.read(addr);
|
return bus.read(addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::op_write(uint16 addr, uint8 data) -> void {
|
auto CPU::op_write(uint16 addr, uint8 data) -> void {
|
||||||
cycle_edge();
|
cycle_edge();
|
||||||
add_clocks(4);
|
add_clocks(4);
|
||||||
//OAM is inaccessible during OAMDMA transfer
|
|
||||||
if(oamdma.active && oamdma.clock >= 8 && addr >= 0xfe00 && addr <= 0xfe9f) return;
|
|
||||||
bus.write(addr, data);
|
bus.write(addr, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -186,13 +186,6 @@ auto CPU::mmio_write(uint16 addr, uint8 data) -> void {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff46) { //DMA
|
|
||||||
oamdma.active = true;
|
|
||||||
oamdma.clock = 0;
|
|
||||||
oamdma.bank = data;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(addr == 0xff4d) { //KEY1
|
if(addr == 0xff4d) { //KEY1
|
||||||
status.speed_switch = data & 0x01;
|
status.speed_switch = data & 0x01;
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -53,8 +53,4 @@ auto CPU::serialize(serializer& s) -> void {
|
||||||
s.integer(status.interrupt_enable_timer);
|
s.integer(status.interrupt_enable_timer);
|
||||||
s.integer(status.interrupt_enable_stat);
|
s.integer(status.interrupt_enable_stat);
|
||||||
s.integer(status.interrupt_enable_vblank);
|
s.integer(status.interrupt_enable_vblank);
|
||||||
|
|
||||||
s.integer(oamdma.active);
|
|
||||||
s.integer(oamdma.clock);
|
|
||||||
s.integer(oamdma.bank);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,21 +6,6 @@ auto CPU::add_clocks(uint clocks) -> void {
|
||||||
if(system.sgb()) system.clocks_executed += clocks;
|
if(system.sgb()) system.clocks_executed += clocks;
|
||||||
|
|
||||||
while(clocks--) {
|
while(clocks--) {
|
||||||
if(oamdma.active) {
|
|
||||||
uint offset = oamdma.clock++;
|
|
||||||
if((offset & 3) == 0) {
|
|
||||||
offset >>= 2;
|
|
||||||
if(offset == 0) {
|
|
||||||
//warm-up
|
|
||||||
} else if(offset == 161) {
|
|
||||||
//cool-down; disable
|
|
||||||
oamdma.active = false;
|
|
||||||
} else {
|
|
||||||
bus.write(0xfe00 + offset - 1, bus.read((oamdma.bank << 8) + offset - 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(++status.clock == 0) {
|
if(++status.clock == 0) {
|
||||||
cartridge.mbc3.second();
|
cartridge.mbc3.second();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,14 @@ auto PPU::vram_addr(uint16 addr) const -> uint {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PPU::mmio_read(uint16 addr) -> uint8 {
|
auto PPU::mmio_read(uint16 addr) -> uint8 {
|
||||||
if(addr >= 0x8000 && addr <= 0x9fff) return vram[vram_addr(addr)];
|
if(addr >= 0x8000 && addr <= 0x9fff) {
|
||||||
if(addr >= 0xfe00 && addr <= 0xfe9f) return oam[addr & 0xff];
|
return vram[vram_addr(addr)];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(addr >= 0xfe00 && addr <= 0xfe9f) {
|
||||||
|
if(status.dma_active && status.dma_clock >= 8) return 0xff;
|
||||||
|
return oam[addr & 0xff];
|
||||||
|
}
|
||||||
|
|
||||||
if(addr == 0xff40) { //LCDC
|
if(addr == 0xff40) { //LCDC
|
||||||
return (status.display_enable << 7)
|
return (status.display_enable << 7)
|
||||||
|
@ -101,8 +107,16 @@ auto PPU::mmio_read(uint16 addr) -> uint8 {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PPU::mmio_write(uint16 addr, uint8 data) -> void {
|
auto PPU::mmio_write(uint16 addr, uint8 data) -> void {
|
||||||
if(addr >= 0x8000 && addr <= 0x9fff) { vram[vram_addr(addr)] = data; return; }
|
if(addr >= 0x8000 && addr <= 0x9fff) {
|
||||||
if(addr >= 0xfe00 && addr <= 0xfe9f) { oam[addr & 0xff] = data; return; }
|
vram[vram_addr(addr)] = data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(addr >= 0xfe00 && addr <= 0xfe9f) {
|
||||||
|
if(status.dma_active && status.dma_clock >= 8) return;
|
||||||
|
oam[addr & 0xff] = data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(addr == 0xff40) { //LCDC
|
if(addr == 0xff40) { //LCDC
|
||||||
if(status.display_enable == false && (data & 0x80)) {
|
if(status.display_enable == false && (data & 0x80)) {
|
||||||
|
@ -154,6 +168,13 @@ auto PPU::mmio_write(uint16 addr, uint8 data) -> void {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(addr == 0xff46) { //DMA
|
||||||
|
status.dma_active = true;
|
||||||
|
status.dma_clock = 0;
|
||||||
|
status.dma_bank = data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(addr == 0xff47) { //BGP
|
if(addr == 0xff47) { //BGP
|
||||||
bgp[3] = (data >> 6) & 3;
|
bgp[3] = (data >> 6) & 3;
|
||||||
bgp[2] = (data >> 4) & 3;
|
bgp[2] = (data >> 4) & 3;
|
||||||
|
|
|
@ -72,6 +72,22 @@ auto PPU::main() -> void {
|
||||||
|
|
||||||
auto PPU::add_clocks(uint clocks) -> void {
|
auto PPU::add_clocks(uint clocks) -> void {
|
||||||
while(clocks--) {
|
while(clocks--) {
|
||||||
|
if(status.dma_active) {
|
||||||
|
uint hi = status.dma_clock++;
|
||||||
|
uint lo = hi & (cpu.status.speed_double ? 1 : 3);
|
||||||
|
hi >>= cpu.status.speed_double ? 1 : 2;
|
||||||
|
if(lo == 0) {
|
||||||
|
if(hi == 0) {
|
||||||
|
//warm-up
|
||||||
|
} else if(hi == 161) {
|
||||||
|
//cool-down; disable
|
||||||
|
status.dma_active = false;
|
||||||
|
} else {
|
||||||
|
oam[hi - 1] = bus.read(status.dma_bank << 8 | hi - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
status.lx++;
|
status.lx++;
|
||||||
clock += cpu.frequency;
|
clock += cpu.frequency;
|
||||||
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) {
|
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) {
|
||||||
|
@ -107,6 +123,7 @@ auto PPU::power() -> void {
|
||||||
bus.mmio[0xff43] = this; //SCX
|
bus.mmio[0xff43] = this; //SCX
|
||||||
bus.mmio[0xff44] = this; //LY
|
bus.mmio[0xff44] = this; //LY
|
||||||
bus.mmio[0xff45] = this; //LYC
|
bus.mmio[0xff45] = this; //LYC
|
||||||
|
bus.mmio[0xff46] = this; //DMA
|
||||||
bus.mmio[0xff47] = this; //BGP
|
bus.mmio[0xff47] = this; //BGP
|
||||||
bus.mmio[0xff48] = this; //OBP0
|
bus.mmio[0xff48] = this; //OBP0
|
||||||
bus.mmio[0xff49] = this; //OBP1
|
bus.mmio[0xff49] = this; //OBP1
|
||||||
|
@ -149,6 +166,11 @@ auto PPU::power() -> void {
|
||||||
status.scx = 0;
|
status.scx = 0;
|
||||||
status.ly = 0;
|
status.ly = 0;
|
||||||
status.lyc = 0;
|
status.lyc = 0;
|
||||||
|
|
||||||
|
status.dma_active = false;
|
||||||
|
status.dma_clock = 0;
|
||||||
|
status.dma_bank = 0;
|
||||||
|
|
||||||
status.wy = 0;
|
status.wy = 0;
|
||||||
status.wx = 0;
|
status.wx = 0;
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,11 @@ struct PPU : Thread, MMIO {
|
||||||
//$ff45 LYC
|
//$ff45 LYC
|
||||||
uint8 lyc;
|
uint8 lyc;
|
||||||
|
|
||||||
|
//$ff46 DMA
|
||||||
|
bool dma_active;
|
||||||
|
uint dma_clock;
|
||||||
|
uint8 dma_bank;
|
||||||
|
|
||||||
//$ff4a WY
|
//$ff4a WY
|
||||||
uint8 wy;
|
uint8 wy;
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,10 @@ auto PPU::serialize(serializer& s) -> void {
|
||||||
s.integer(status.ly);
|
s.integer(status.ly);
|
||||||
s.integer(status.lyc);
|
s.integer(status.lyc);
|
||||||
|
|
||||||
|
s.integer(status.dma_active);
|
||||||
|
s.integer(status.dma_clock);
|
||||||
|
s.integer(status.dma_bank);
|
||||||
|
|
||||||
s.integer(status.wy);
|
s.integer(status.wy);
|
||||||
s.integer(status.wx);
|
s.integer(status.wx);
|
||||||
|
|
||||||
|
|
|
@ -18,11 +18,10 @@ Settings::Settings() {
|
||||||
set("Video/Synchronize", false);
|
set("Video/Synchronize", false);
|
||||||
set("Video/Scale", "Small");
|
set("Video/Scale", "Small");
|
||||||
set("Video/AspectCorrection", true);
|
set("Video/AspectCorrection", true);
|
||||||
set("Video/Filter", "Blur");
|
set("Video/Shader", "Blur");
|
||||||
set("Video/Shader", "None");
|
|
||||||
set("Video/BlurEmulation", true);
|
set("Video/BlurEmulation", true);
|
||||||
set("Video/ColorEmulation", true);
|
set("Video/ColorEmulation", true);
|
||||||
set("Video/ScanlineEmulation", true);
|
set("Video/ScanlineEmulation", false);
|
||||||
set("Video/Saturation", 100);
|
set("Video/Saturation", 100);
|
||||||
set("Video/Gamma", 100);
|
set("Video/Gamma", 100);
|
||||||
set("Video/Luminance", 100);
|
set("Video/Luminance", 100);
|
||||||
|
|
|
@ -80,8 +80,6 @@ Presentation::Presentation() {
|
||||||
settings["Video/Overscan/Mask"].setValue(maskOverscan.checked());
|
settings["Video/Overscan/Mask"].setValue(maskOverscan.checked());
|
||||||
});
|
});
|
||||||
videoShaderMenu.setText("Video Shader");
|
videoShaderMenu.setText("Video Shader");
|
||||||
if(settings["Video/Shader"].text() == "None") videoShaderNone.setChecked();
|
|
||||||
if(settings["Video/Shader"].text() == "Blur") videoShaderBlur.setChecked();
|
|
||||||
videoShaderNone.setText("None").onActivate([&] {
|
videoShaderNone.setText("None").onActivate([&] {
|
||||||
settings["Video/Shader"].setValue("None");
|
settings["Video/Shader"].setValue("None");
|
||||||
program->updateVideoShader();
|
program->updateVideoShader();
|
||||||
|
@ -277,20 +275,22 @@ auto Presentation::drawSplashScreen() -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Presentation::loadShaders() -> void {
|
auto Presentation::loadShaders() -> void {
|
||||||
if(settings["Video/Driver"].text() != "OpenGL") {
|
auto pathname = locate({localpath(), "higan/"}, "Video Shaders/");
|
||||||
return;
|
|
||||||
|
if(settings["Video/Driver"].text() == "OpenGL") {
|
||||||
|
for(auto shader : directory::folders(pathname, "*.shader")) {
|
||||||
|
if(videoShaders.objectCount() == 2) videoShaderMenu.append(MenuSeparator());
|
||||||
|
MenuRadioItem item{&videoShaderMenu};
|
||||||
|
item.setText(string{shader}.rtrim(".shader/", 1L)).onActivate([=] {
|
||||||
|
settings["Video/Shader"].setValue({pathname, shader});
|
||||||
|
program->updateVideoShader();
|
||||||
|
});
|
||||||
|
videoShaders.append(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pathname = locate({localpath(), "higan/"}, "Video Shaders/");
|
if(settings["Video/Shader"].text() == "None") videoShaderNone.setChecked();
|
||||||
for(auto shader : directory::folders(pathname, "*.shader")) {
|
if(settings["Video/Shader"].text() == "Blur") videoShaderBlur.setChecked();
|
||||||
if(videoShaders.objectCount() == 2) videoShaderMenu.append(MenuSeparator());
|
|
||||||
MenuRadioItem item{&videoShaderMenu};
|
|
||||||
item.setText(string{shader}.rtrim(".shader/", 1L)).onActivate([=] {
|
|
||||||
settings["Video/Shader"].setValue({pathname, shader});
|
|
||||||
program->updateVideoShader();
|
|
||||||
});
|
|
||||||
videoShaders.append(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(auto radioItem : videoShaders.objects<MenuRadioItem>()) {
|
for(auto radioItem : videoShaders.objects<MenuRadioItem>()) {
|
||||||
if(settings["Video/Shader"].text() == string{pathname, radioItem.text(), ".shader/"}) {
|
if(settings["Video/Shader"].text() == string{pathname, radioItem.text(), ".shader/"}) {
|
||||||
|
|
|
@ -84,7 +84,7 @@ auto pTextEdit::setEditable(bool editable) -> void {
|
||||||
|
|
||||||
auto pTextEdit::setEnabled(bool enabled) -> void {
|
auto pTextEdit::setEnabled(bool enabled) -> void {
|
||||||
pWidget::setEnabled(enabled);
|
pWidget::setEnabled(enabled);
|
||||||
setEditable(self().editable); //Cocoa lacks NSTextView::setEnabled; simulate via setEnabled()
|
setEditable(state().editable); //Cocoa lacks NSTextView::setEnabled; simulate via setEnabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pTextEdit::setFont(const Font& font) -> void {
|
auto pTextEdit::setFont(const Font& font) -> void {
|
||||||
|
|
|
@ -86,7 +86,7 @@ auto nall::main(lstring args) -> void {
|
||||||
Application::Cocoa::onPreferences([&] {
|
Application::Cocoa::onPreferences([&] {
|
||||||
scanDialog->settingsButton.doActivate();
|
scanDialog->settingsButton.doActivate();
|
||||||
});
|
});
|
||||||
Application::Cocoa::onQuit({
|
Application::Cocoa::onQuit([&] {
|
||||||
Application::quit();
|
Application::quit();
|
||||||
});
|
});
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue