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:
Tim Allen 2016-01-17 19:59:25 +11:00
parent 12df278c5b
commit 1fdd0582fc
22 changed files with 150 additions and 106 deletions

View File

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

View File

@ -2,11 +2,11 @@
namespace GameBoy {
#include "sequencer/sequencer.cpp"
#include "square1/square1.cpp"
#include "square2/square2.cpp"
#include "wave/wave.cpp"
#include "noise/noise.cpp"
#include "master/master.cpp"
#include "serialization.cpp"
APU apu;
@ -20,7 +20,19 @@ auto APU::main() -> void {
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
square1.clockLength();
square2.clockLength();
@ -37,25 +49,16 @@ auto APU::main() -> void {
}
phase++;
}
stage++;
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);
cycle++;
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 {
bias += ((((int64)sample << 16) - (bias >> 16)) * 57593) >> 16;
sample = sclamp<16>(sample - (bias >> 32));
@ -65,14 +68,13 @@ auto APU::power() -> void {
create(Main, 2 * 1024 * 1024);
for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;
stage = 0;
phase = 0;
square1.power();
square2.power();
wave.power();
noise.power();
master.power();
sequencer.power();
phase = 0;
cycle = 0;
LinearFeedbackShiftRegisterGenerator 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 >= 0xff1a && addr <= 0xff1e) return wave.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);
return 0xff;
}
auto APU::mmio_write(uint16 addr, uint8 data) -> void {
if(!master.enable) {
if(!sequencer.enable) {
bool valid = addr == 0xff26; //NR52
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 == 0xff16) valid = true, data &= 0x3f; //NR21; duty is not writable (remains 0)
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 >= 0xff1a && addr <= 0xff1e) return wave.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);
}

View File

@ -13,16 +13,16 @@ struct APU : Thread, MMIO {
#include "square2/square2.hpp"
#include "wave/wave.hpp"
#include "noise/noise.hpp"
#include "master/master.hpp"
uint12 stage;
uint3 phase;
#include "sequencer/sequencer.hpp"
Square1 square1;
Square2 square2;
Wave wave;
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;

View File

@ -1,4 +1,4 @@
auto APU::Master::run() -> void {
auto APU::Sequencer::run() -> void {
if(enable == false) {
center = 0;
left = 0;
@ -39,7 +39,7 @@ auto APU::Master::run() -> void {
right >>= 1;
}
auto APU::Master::read(uint16 addr) -> uint8 {
auto APU::Sequencer::read(uint16 addr) -> uint8 {
if(addr == 0xff24) { //NR50
return leftEnable << 7 | leftVolume << 4 | rightEnable << 3 | rightVolume;
}
@ -66,7 +66,7 @@ auto APU::Master::read(uint16 addr) -> uint8 {
return 0xff;
}
auto APU::Master::write(uint16 addr, uint8 data) -> void {
auto APU::Sequencer::write(uint16 addr, uint8 data) -> void {
if(addr == 0xff24) { //NR50
leftEnable = (uint1)(data >> 7);
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;
leftVolume = 0;
rightEnable = 0;
@ -127,7 +127,7 @@ auto APU::Master::power() -> void {
rightBias = 0;
}
auto APU::Master::serialize(serializer& s) -> void {
auto APU::Sequencer::serialize(serializer& s) -> void {
s.integer(leftEnable);
s.integer(leftVolume);
s.integer(rightEnable);

View File

@ -1,4 +1,4 @@
struct Master {
struct Sequencer {
auto run() -> void;
auto read(uint16 addr) -> uint8;
auto write(uint16 addr, uint8 data) -> void;

View File

@ -1,12 +1,12 @@
auto APU::serialize(serializer& s) -> void {
Thread::serialize(s);
s.integer(stage);
s.integer(phase);
square1.serialize(s);
square2.serialize(s);
wave.serialize(s);
noise.serialize(s);
master.serialize(s);
sequencer.serialize(s);
s.integer(phase);
s.integer(cycle);
}

View File

@ -3,9 +3,12 @@ auto APU::Wave::getPattern(uint5 offset) const -> uint4 {
}
auto APU::Wave::run() -> void {
if(patternHold) patternHold--;
if(period && --period == 0) {
period = 1 * (2048 - frequency);
patternSample = getPattern(++patternOffset);
patternHold = 1;
}
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) {
return pattern[addr & 15];
if(enable) {
if(!system.cgb() && !patternHold) return 0xff;
return pattern[patternOffset >> 1];
} else {
return pattern[addr & 15];
}
}
return 0xff;
@ -77,9 +85,25 @@ auto APU::Wave::write(uint16 addr, uint8 data) -> void {
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
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;
period = 1 * (2048 - frequency);
patternOffset = 0;
patternSample = 0;
patternHold = 0;
if(!length) {
length = 256;
@ -89,7 +113,12 @@ auto APU::Wave::write(uint16 addr, uint8 data) -> void {
}
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;
patternOffset = 0;
patternSample = 0;
patternHold = 0;
if(initializeLength) length = 256;
}
@ -123,4 +153,5 @@ auto APU::Wave::serialize(serializer& s) -> void {
s.integer(period);
s.integer(patternOffset);
s.integer(patternSample);
s.integer(patternHold);
}

View File

@ -22,4 +22,5 @@ struct Wave {
uint period;
uint5 patternOffset;
uint4 patternSample;
uint patternHold;
};

View File

@ -117,7 +117,6 @@ auto CPU::power() -> void {
bus.mmio[0xff06] = this; //TMA
bus.mmio[0xff07] = this; //TAC
bus.mmio[0xff0f] = this; //IF
bus.mmio[0xff46] = this; //DMA
bus.mmio[0xffff] = this; //IE
if(system.cgb()) {
@ -199,10 +198,6 @@ auto CPU::power() -> void {
status.interrupt_enable_timer = 0;
status.interrupt_enable_stat = 0;
status.interrupt_enable_vblank = 0;
oamdma.active = false;
oamdma.clock = 0;
oamdma.bank = 0;
}
}

View File

@ -107,12 +107,6 @@ struct CPU : Processor::LR35902, Thread, MMIO {
bool interrupt_enable_vblank;
} status;
struct OAMDMA {
bool active;
uint clock;
uint8 bank;
} oamdma;
uint8 wram[32768]; //GB=8192, GBC=32768
uint8 hram[128];
};

View File

@ -6,16 +6,12 @@ auto CPU::op_io() -> void {
auto CPU::op_read(uint16 addr) -> uint8 {
cycle_edge();
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);
}
auto CPU::op_write(uint16 addr, uint8 data) -> void {
cycle_edge();
add_clocks(4);
//OAM is inaccessible during OAMDMA transfer
if(oamdma.active && oamdma.clock >= 8 && addr >= 0xfe00 && addr <= 0xfe9f) return;
bus.write(addr, data);
}

View File

@ -186,13 +186,6 @@ auto CPU::mmio_write(uint16 addr, uint8 data) -> void {
return;
}
if(addr == 0xff46) { //DMA
oamdma.active = true;
oamdma.clock = 0;
oamdma.bank = data;
return;
}
if(addr == 0xff4d) { //KEY1
status.speed_switch = data & 0x01;
return;

View File

@ -53,8 +53,4 @@ auto CPU::serialize(serializer& s) -> void {
s.integer(status.interrupt_enable_timer);
s.integer(status.interrupt_enable_stat);
s.integer(status.interrupt_enable_vblank);
s.integer(oamdma.active);
s.integer(oamdma.clock);
s.integer(oamdma.bank);
}

View File

@ -6,21 +6,6 @@ auto CPU::add_clocks(uint clocks) -> void {
if(system.sgb()) system.clocks_executed += 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) {
cartridge.mbc3.second();
}

View File

@ -3,8 +3,14 @@ auto PPU::vram_addr(uint16 addr) const -> uint {
}
auto PPU::mmio_read(uint16 addr) -> uint8 {
if(addr >= 0x8000 && addr <= 0x9fff) return vram[vram_addr(addr)];
if(addr >= 0xfe00 && addr <= 0xfe9f) return oam[addr & 0xff];
if(addr >= 0x8000 && addr <= 0x9fff) {
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
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 {
if(addr >= 0x8000 && addr <= 0x9fff) { vram[vram_addr(addr)] = data; return; }
if(addr >= 0xfe00 && addr <= 0xfe9f) { oam[addr & 0xff] = data; return; }
if(addr >= 0x8000 && addr <= 0x9fff) {
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(status.display_enable == false && (data & 0x80)) {
@ -154,6 +168,13 @@ auto PPU::mmio_write(uint16 addr, uint8 data) -> void {
return;
}
if(addr == 0xff46) { //DMA
status.dma_active = true;
status.dma_clock = 0;
status.dma_bank = data;
return;
}
if(addr == 0xff47) { //BGP
bgp[3] = (data >> 6) & 3;
bgp[2] = (data >> 4) & 3;

View File

@ -72,6 +72,22 @@ auto PPU::main() -> void {
auto PPU::add_clocks(uint clocks) -> void {
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++;
clock += cpu.frequency;
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) {
@ -107,6 +123,7 @@ auto PPU::power() -> void {
bus.mmio[0xff43] = this; //SCX
bus.mmio[0xff44] = this; //LY
bus.mmio[0xff45] = this; //LYC
bus.mmio[0xff46] = this; //DMA
bus.mmio[0xff47] = this; //BGP
bus.mmio[0xff48] = this; //OBP0
bus.mmio[0xff49] = this; //OBP1
@ -149,6 +166,11 @@ auto PPU::power() -> void {
status.scx = 0;
status.ly = 0;
status.lyc = 0;
status.dma_active = false;
status.dma_clock = 0;
status.dma_bank = 0;
status.wy = 0;
status.wx = 0;

View File

@ -71,6 +71,11 @@ struct PPU : Thread, MMIO {
//$ff45 LYC
uint8 lyc;
//$ff46 DMA
bool dma_active;
uint dma_clock;
uint8 dma_bank;
//$ff4a WY
uint8 wy;

View File

@ -31,6 +31,10 @@ auto PPU::serialize(serializer& s) -> void {
s.integer(status.ly);
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.wx);

View File

@ -18,11 +18,10 @@ Settings::Settings() {
set("Video/Synchronize", false);
set("Video/Scale", "Small");
set("Video/AspectCorrection", true);
set("Video/Filter", "Blur");
set("Video/Shader", "None");
set("Video/Shader", "Blur");
set("Video/BlurEmulation", true);
set("Video/ColorEmulation", true);
set("Video/ScanlineEmulation", true);
set("Video/ScanlineEmulation", false);
set("Video/Saturation", 100);
set("Video/Gamma", 100);
set("Video/Luminance", 100);

View File

@ -80,8 +80,6 @@ Presentation::Presentation() {
settings["Video/Overscan/Mask"].setValue(maskOverscan.checked());
});
videoShaderMenu.setText("Video Shader");
if(settings["Video/Shader"].text() == "None") videoShaderNone.setChecked();
if(settings["Video/Shader"].text() == "Blur") videoShaderBlur.setChecked();
videoShaderNone.setText("None").onActivate([&] {
settings["Video/Shader"].setValue("None");
program->updateVideoShader();
@ -277,20 +275,22 @@ auto Presentation::drawSplashScreen() -> void {
}
auto Presentation::loadShaders() -> void {
if(settings["Video/Driver"].text() != "OpenGL") {
return;
auto pathname = locate({localpath(), "higan/"}, "Video Shaders/");
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/");
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);
}
if(settings["Video/Shader"].text() == "None") videoShaderNone.setChecked();
if(settings["Video/Shader"].text() == "Blur") videoShaderBlur.setChecked();
for(auto radioItem : videoShaders.objects<MenuRadioItem>()) {
if(settings["Video/Shader"].text() == string{pathname, radioItem.text(), ".shader/"}) {

View File

@ -84,7 +84,7 @@ auto pTextEdit::setEditable(bool editable) -> void {
auto pTextEdit::setEnabled(bool enabled) -> void {
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 {

View File

@ -86,7 +86,7 @@ auto nall::main(lstring args) -> void {
Application::Cocoa::onPreferences([&] {
scanDialog->settingsButton.doActivate();
});
Application::Cocoa::onQuit({
Application::Cocoa::onQuit([&] {
Application::quit();
});
#endif