Compare commits

...

18 Commits
v114 ... v115

Author SHA1 Message Date
byuu
8e80d2f8a4 v115 2020-03-03 19:56:48 +09:00
byuu
bd1759eb45 Add credits. 2020-02-28 17:31:25 +09:00
byuu
5296176151 v114.6
Fixed accuracy PPU rendering issue affecting Secret of Mana
2020-02-26 19:57:21 +09:00
byuu
64d20a062a Windows compilation fix. 2020-02-23 21:03:24 +09:00
byuu
c22ef09f13 PulseAudio compilation fix. 2020-02-23 20:36:28 +09:00
byuu
2223a843c9 Restore IOKit fix that was missing from higan ruby sync. 2020-02-23 20:34:10 +09:00
byuu
d2211d8818 v114.5
* improved appended firmware detection [devinacker]
* added dynamic rate control support to ALSA and PulseAudio drivers [RedDwarf]
* added option to use native file dialogs
2020-02-23 20:23:25 +09:00
byuu
c13745d753 v110.4
Merged Super Game Boy MLT_REQ fix [devinacker]
2020-02-18 20:07:05 +09:00
byuu
7053a0b605 Readme updated. 2020-01-18 14:23:10 +09:00
byuu
58eb6838b7 Fixed links in readme. 2020-01-18 01:25:55 +09:00
byuu
1f4f1223a1 v114.3
Dot PPU: latch fine BG Hscroll at H=0 instead of H=56
(fixes minor scanline issues in Full Throttle Racing scoring screen)
(note: exact latch position is not currently known)
2020-01-17 09:09:01 +09:00
byuu
3cc8c589cc Remove debugging variables. 2020-01-16 23:16:37 +09:00
byuu
52d5b3e2a2 v114.2
Serialize SDD1 PEM
(fixes run-ahead in Star Ocean)
Dot PPU: calculate nameTableIndex and characterIndex from ppu.hcounter()
(fixes scanline glitch in Great Battle IV)
Dot PPU: do not latch BG H/V scroll registers
(fixes Air Strike Patrol raster effects)
(fixes Septentrion glitchy scanline)
2020-01-16 23:12:37 +09:00
byuu
358a3ceed7 libretro: added cheat support [rtretiakov]
libretro: aspect ratio correction [rtretiakov]
libretro: MSU1 lookup fix [rtretiakov]
libretro: merged latest resources and overrides from upstream
libretro: changed audioFrame() from float to double [byuu]
2020-01-16 05:45:35 +09:00
byuu
c6918fc170 Fix "make clean" when using cmd.exe shell on Windows 2020-01-16 05:40:55 +09:00
byuu
1afd440c86 Revert Emulator::Audio to use doubles instead of floats
* fixes ODR violations, but is slightly slower (378fps->376fps)
2020-01-16 05:34:00 +09:00
byuu
702977f0b9 Mosaic cleanups. 2020-01-16 05:26:56 +09:00
byuu
fb463d34ef v114.1
Improved vertical mosaic emulation.
2020-01-16 05:09:52 +09:00
93 changed files with 2459 additions and 2188 deletions

55
CREDITS.md Normal file
View File

@@ -0,0 +1,55 @@
bsnes was created and is maintained by byuu (Near), but many people have contributed to the project.
This software would not be where it is today without the help and support of the following individuals:
- Andreas Naive
- Ange Albertini
- anomie
- AWJ
- Bisqwit
- blargg
- Łukasz Krawczyk
- Danish
- DerKoun
- DMV27
- Dr. Decapitator
- endrift
- Fatbag
- FitzRoy
- gekkio
- GIGO
- Hendricks266
- hex_usr
- ikari_01
- jchadwick
- Jonas Quinn
- kode54
- krom
- Lioncash
- Lord Nightmare
- lowkey
- Matthew Callis
- Max833
- MerryMage
- mightymo
- Nach
- ncbncb
- neviksti
- OV2
- Overload
- p4plus2
- quequotion
- RedDwarf
- Richard Bannister
- Ryphecha
- segher
- Sintendo
- SuperMikeMan
- Talarubi
- tetsuo55
- TmEE
- TRAC
- wareya
- zones
If your contributions have been missed here, please reach out [here](https://byuu.org/contact) to have this corrected, thank you!

View File

@@ -4,13 +4,8 @@ bsnes
![bsnes logo © byuu](https://byuu.org/images/bsnes/github/byuu-bsnes-logo.png)
bsnes is a multi-platform Super Nintendo (Super Famicom) emulator from
[byuu](https://byuu.org) that focuses on performance, features, and ease of use.
bsnes currently enjoys 100% known, bug-free compatibility with the entire SNES
library when configured to its most accurate settings, giving it the same
accuracy level as higan. Accuracy can also optionally be traded for performance,
allowing bsnes to operate more than 300% faster than higan while still remaining
almost as accurate.
[byuu](https://byuu.org/about) that focuses on performance, features, and ease
of use.
Development
-----------
@@ -20,12 +15,11 @@ environment. As bsnes is rather mature, things should generally be quite stable.
However, bugs will exist, regressions will occur, so proceed at your own risk.
If stability is required, please download the latest stable release from the
[official website.](https://bsnes.byuu.org)
[official website.](https://byuu.org/bsnes)
Unique Features
---------------
- 100% (known) bug-free compatibility with the entire officially licensed SNES games library
- True Super Game Boy emulation (using the SameBoy core by Lior Halphon)
- HD mode 7 graphics with optional supersampling (by DerKoun)
- Low-level emulation of all SNES coprocessors (DSP-n, ST-01n, Cx4)
@@ -74,7 +68,6 @@ Links
- [Official website](https://byuu.org/bsnes)
- [Official git repository](https://github.com/byuu/bsnes)
- [Developer resources](https://byuu.net)
- [Donations](https://patreon.com/byuu)
Release Builds

View File

@@ -1,5 +1,3 @@
#define double float
namespace Emulator {
#include "stream.cpp"

View File

@@ -1,5 +1,4 @@
#pragma once
#define double float
#include <nall/dsp/iir/dc-removal.hpp>
#include <nall/dsp/iir/one-pole.hpp>

View File

@@ -28,14 +28,14 @@ using namespace nall;
#include <emulator/audio/audio.hpp>
namespace Emulator {
static const string Name = "bsnes";
static const string Version = "114";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "https://byuu.org";
static const string Name = "bsnes";
static const string Version = "115";
static const string Copyright = "byuu";
static const string License = "GPLv3";
static const string Website = "https://byuu.org";
//incremented only when serialization format changes
static const string SerializerVersion = "112";
static const string SerializerVersion = "115";
namespace Constants {
namespace Colorburst {

View File

@@ -17,7 +17,7 @@ struct Platform {
virtual auto open(uint id, string name, vfs::file::mode mode, bool required = false) -> shared_pointer<vfs::file> { return {}; }
virtual auto load(uint id, string name, string type, vector<string> options = {}) -> Load { return {}; }
virtual auto videoFrame(const uint16* data, uint pitch, uint width, uint height, uint scale) -> void {}
virtual auto audioFrame(const float* samples, uint channels) -> void {}
virtual auto audioFrame(const double* samples, uint channels) -> void {}
virtual auto inputPoll(uint port, uint device, uint input) -> int16 { return 0; }
virtual auto inputRumble(uint port, uint device, uint input, bool enable) -> void {}
virtual auto dipSettings(Markup::Node node) -> uint { return 0; }

View File

@@ -433,13 +433,7 @@ auto SuperFamicom::serial() const -> string {
}
auto SuperFamicom::romSize() const -> uint {
//subtract appended firmware size, if firmware is present
if((size() & 0x7fff) == 0x100) return size() - 0x100;
if((size() & 0x7fff) == 0xc00) return size() - 0xc00;
if((size() & 0x7fff) == 0x2000) return size() - 0x2000;
if((size() & 0xffff) == 0xd000) return size() - 0xd000;
if((size() & 0x3ffff) == 0x28000) return size() - 0x28000;
return size();
return size() - firmwareRomSize();
}
auto SuperFamicom::programRomSize() const -> uint {
@@ -459,8 +453,38 @@ auto SuperFamicom::expansionRomSize() const -> uint {
return 0;
}
//detect if any firmware is appended to the ROM image, and return its size if so
auto SuperFamicom::firmwareRomSize() const -> uint {
return size() - romSize();
auto cartridgeTypeLo = data[headerAddress + 0x26] & 15;
auto cartridgeTypeHi = data[headerAddress + 0x26] >> 4;
auto cartridgeSubType = data[headerAddress + 0x0f];
if(serial() == "042J" || (cartridgeTypeLo == 0x3 && cartridgeTypeHi == 0xe)) {
//Game Boy
if((size() & 0x7fff) == 0x100) return 0x100;
}
if(cartridgeTypeLo >= 0x3 && cartridgeTypeHi == 0xf && cartridgeSubType == 0x10) {
//Hitachi HG51BS169
if((size() & 0x7fff) == 0xc00) return 0xc00;
}
if(cartridgeTypeLo >= 0x3 && cartridgeTypeHi == 0x0) {
//NEC uPD7725
if((size() & 0x7fff) == 0x2000) return 0x2000;
}
if(cartridgeTypeLo >= 0x3 && cartridgeTypeHi == 0xf && cartridgeSubType == 0x01) {
//NEC uPD96050
if((size() & 0xffff) == 0xd000) return 0xd000;
}
if(cartridgeTypeLo >= 0x3 && cartridgeTypeHi == 0xf && cartridgeSubType == 0x02) {
//ARM6
if((size() & 0x3ffff) == 0x28000) return 0x28000;
}
return 0;
}
auto SuperFamicom::ramSize() const -> uint {

View File

@@ -20,7 +20,7 @@ auto ICD::ppuWrite(uint2 color) -> void {
}
auto ICD::apuWrite(float left, float right) -> void {
float samples[] = {left, right};
double samples[] = {left, right};
if(!system.runAhead) stream->write(samples);
}
@@ -88,14 +88,6 @@ auto ICD::joypWrite(bool p14, bool p15) -> void {
if(packetLock == 1) {
if(p14 == 0 && p15 == 1) {
if((joypPacket[0] >> 3) == 0x11) {
mltReq = joypPacket[1] & 3;
if(mltReq == 0) joypID &= 0; //1-player mode
if(mltReq == 1) joypID &= 1; //2-player mode
if(mltReq == 2) joypID &= 3; //4-player mode (unverified; but the most likely behavior)
if(mltReq == 3) joypID &= 3; //4-player mode
}
if(packetSize < 64) packet[packetSize++] = joypPacket;
packetLock = 0;
pulseLock = 1;

View File

@@ -55,6 +55,13 @@ auto ICD::writeIO(uint addr, uint8 data) -> void {
if((r6003 & 0x80) == 0x00 && (data & 0x80) == 0x80) {
power(true); //soft reset
}
mltReq = data >> 4 & 3;
if(mltReq == 0) joypID &= ~0; //1-player mode
if(mltReq == 1) joypID &= ~1; //2-player mode
if(mltReq == 2) joypID &= ~3; //4-player mode (unverified; but the most likely behavior)
if(mltReq == 3) joypID &= ~3; //4-player mode
auto frequency = clockFrequency();
switch(data & 3) {
case 0: this->frequency = frequency / 4; break; //fast (glitchy, even on real hardware)

View File

@@ -6,9 +6,9 @@ auto SDD1::serialize(serializer& s) -> void {
s.integer(r4806);
s.integer(r4807);
for(auto n : range(8)) {
s.integer(dma[n].addr);
s.integer(dma[n].size);
for(auto& channel : dma) {
s.integer(channel.addr);
s.integer(channel.size);
}
s.integer(dmaReady);
@@ -26,6 +26,7 @@ auto SDD1::Decompressor::serialize(serializer& s) -> void {
bg5.serialize(s);
bg6.serialize(s);
bg7.serialize(s);
pem.serialize(s);
cm.serialize(s);
ol.serialize(s);
}

View File

@@ -1,15 +1,3 @@
//single-threaded
auto PPU::Line::cacheBackground(PPU::IO::Background& bg) -> void {
if(y == 1) {
bg.mosaicCounter = ppu.io.mosaicSize + 1;
bg.mosaicOffset = 1;
} else if(--bg.mosaicCounter == 0) {
bg.mosaicCounter = ppu.io.mosaicSize + 1;
bg.mosaicOffset += ppu.io.mosaicSize + 1;
}
}
//parallelized
auto PPU::Line::renderBackground(PPU::IO::Background& self, uint8 source) -> void {
if(!self.aboveEnable && !self.belowEnable) return;
if(self.tileMode == TileMode::Mode7) return renderMode7(self, source);
@@ -39,10 +27,14 @@ auto PPU::Line::renderBackground(PPU::IO::Background& self, uint8 source) -> voi
uint hmask = (width << self.tileSize << !!(self.screenSize & 1)) - 1;
uint vmask = (width << self.tileSize << !!(self.screenSize & 2)) - 1;
uint y = self.mosaicEnable ? self.mosaicOffset : this->y;
uint y = this->y;
if(self.mosaicEnable) y -= io.mosaic.size - io.mosaic.counter;
if(hires) {
hscroll <<= 1;
if(io.interlace) y = y << 1 | field();
if(io.interlace) {
y = y << 1 | field();
if(self.mosaicEnable) y -= io.mosaic.size - io.mosaic.counter + field();
}
}
uint mosaicCounter = 1;
@@ -103,7 +95,7 @@ auto PPU::Line::renderBackground(PPU::IO::Background& self, uint8 source) -> voi
for(uint tileX = 0; tileX < 8; tileX++, x++) {
if(x & width) continue; //x < 0 || x >= width
if(!self.mosaicEnable || --mosaicCounter == 0) {
if(--mosaicCounter == 0) {
uint color, shift = mirrorX ? tileX : 7 - tileX;
/*if(self.tileMode >= TileMode::BPP2)*/ {
color = data >> shift + 0 & 1;
@@ -120,7 +112,7 @@ auto PPU::Line::renderBackground(PPU::IO::Background& self, uint8 source) -> voi
color += data >> shift + 49 & 128;
}
mosaicCounter = 1 + io.mosaicSize;
mosaicCounter = self.mosaicEnable ? io.mosaic.size : 1;
mosaicPalette = color;
mosaicPriority = tilePriority;
if(directColorMode) {

View File

@@ -248,11 +248,16 @@ auto PPU::writeIO(uint address, uint8 data) -> void {
}
case 0x2106: { //MOSAIC
bool mosaicEnable = io.bg1.mosaicEnable || io.bg2.mosaicEnable || io.bg3.mosaicEnable || io.bg4.mosaicEnable;
io.bg1.mosaicEnable = data >> 0 & 1;
io.bg2.mosaicEnable = data >> 1 & 1;
io.bg3.mosaicEnable = data >> 2 & 1;
io.bg4.mosaicEnable = data >> 3 & 1;
io.mosaicSize = data >> 4 & 15;
io.mosaic.size = (data >> 4 & 15) + 1;
if(!mosaicEnable && (data >> 0 & 15)) {
//mosaic vcounter is reloaded when mosaic becomes enabled
io.mosaic.counter = io.mosaic.size + 1;
}
return;
}

View File

@@ -26,11 +26,6 @@ auto PPU::Line::flush() -> void {
}
auto PPU::Line::cache() -> void {
cacheBackground(ppu.io.bg1);
cacheBackground(ppu.io.bg2);
cacheBackground(ppu.io.bg3);
cacheBackground(ppu.io.bg4);
uint y = ppu.vcounter();
if(ppu.io.displayDisable || y >= ppu.vdisp()) {
io.displayDisable = true;

View File

@@ -1,10 +1,11 @@
auto PPU::Line::renderMode7(PPU::IO::Background& self, uint8 source) -> void {
//HD mode 7 support
if(!ppu.hdMosaic() || !self.mosaicEnable || !io.mosaicSize) {
if(!ppu.hdMosaic() || !self.mosaicEnable || io.mosaic.size == 1) {
if(ppu.hdScale() > 1) return renderMode7HD(self, source);
}
int Y = self.mosaicEnable ? self.mosaicOffset : this->y;
int Y = this->y;
if(self.mosaicEnable) Y -= io.mosaic.size - io.mosaic.counter;
int y = !io.mode7.vflip ? Y : 255 - Y;
int a = (int16)io.mode7.a;
@@ -50,8 +51,8 @@ auto PPU::Line::renderMode7(PPU::IO::Background& self, uint8 source) -> void {
palette &= 0x7f;
}
if(!self.mosaicEnable || --mosaicCounter == 0) {
mosaicCounter = 1 + io.mosaicSize;
if(--mosaicCounter == 0) {
mosaicCounter = self.mosaicEnable ? io.mosaic.size : 1;
mosaicPalette = palette;
mosaicPriority = priority;
if(io.col.directColor && source == Source::BG1) {

View File

@@ -142,7 +142,7 @@ auto PPU::readObject(uint10 address) -> uint8 {
uint n = address >> 2; //object#
address &= 3;
if(address == 0) return objects[n].x;
if(address == 1) return objects[n].y - 1;
if(address == 1) return objects[n].y - 1; //-1 => rendering happens one scanline late
if(address == 2) return objects[n].character;
return (
objects[n].nameselect << 0

View File

@@ -85,6 +85,13 @@ auto PPU::main() -> void {
uint y = vcounter();
if(y >= 1 && y <= 239) {
step(renderCycle());
bool mosaicEnable = io.bg1.mosaicEnable || io.bg2.mosaicEnable || io.bg3.mosaicEnable || io.bg4.mosaicEnable;
if(y == 1) {
io.mosaic.counter = mosaicEnable ? io.mosaic.size + 1 : 0;
}
if(io.mosaic.counter && !--io.mosaic.counter) {
io.mosaic.counter = mosaicEnable ? io.mosaic.size + 0 : 0;
}
lines[y].cache();
}
}

View File

@@ -85,7 +85,6 @@ public:
bool oamPriority = 0;
bool bgPriority = 0;
uint8 bgMode = 0;
uint8 mosaicSize = 0;
bool vramIncrementMode = 0;
uint8 vramMapping = 0;
uint8 vramIncrementSize = 0;
@@ -99,6 +98,14 @@ public:
bool pseudoHires = 0;
bool extbg = 0;
struct Mosaic {
//serialization.cpp
auto serialize(serializer&) -> void;
uint8 size = 1;
uint8 counter = 0;
} mosaic;
struct Mode7 {
//serialization.cpp
auto serialize(serializer&) -> void;
@@ -161,8 +168,6 @@ public:
bool aboveEnable = 0;
bool belowEnable = 0;
bool mosaicEnable = 0;
uint16 mosaicCounter = 0;
uint16 mosaicOffset = 0;
uint16 tiledataAddress = 0;
uint16 screenAddress = 0;
uint8 screenSize = 0;
@@ -293,7 +298,6 @@ public:
alwaysinline auto plotHD(Pixel*, uint x, uint8 source, uint8 priority, uint16 color, bool hires, bool subpixel) -> void;
//background.cpp
auto cacheBackground(PPU::IO::Background&) -> void;
auto renderBackground(PPU::IO::Background&, uint8 source) -> void;
auto getTile(PPU::IO::Background&, uint hoffset, uint voffset) -> uint;

View File

@@ -44,7 +44,6 @@ auto PPU::IO::serialize(serializer& s) -> void {
s.integer(oamPriority);
s.integer(bgPriority);
s.integer(bgMode);
s.integer(mosaicSize);
s.integer(vramIncrementMode);
s.integer(vramMapping);
s.integer(vramIncrementSize);
@@ -58,6 +57,7 @@ auto PPU::IO::serialize(serializer& s) -> void {
s.integer(pseudoHires);
s.integer(extbg);
mosaic.serialize(s);
mode7.serialize(s);
window.serialize(s);
bg1.serialize(s);
@@ -68,6 +68,11 @@ auto PPU::IO::serialize(serializer& s) -> void {
col.serialize(s);
}
auto PPU::IO::Mosaic::serialize(serializer& s) -> void {
s.integer(size);
s.integer(counter);
}
auto PPU::IO::Mode7::serialize(serializer& s) -> void {
s.integer(hflip);
s.integer(vflip);
@@ -114,8 +119,6 @@ auto PPU::IO::Background::serialize(serializer& s) -> void {
s.integer(aboveEnable);
s.integer(belowEnable);
s.integer(mosaicEnable);
s.integer(mosaicCounter);
s.integer(mosaicOffset);
s.integer(tiledataAddress);
s.integer(screenAddress);
s.integer(screenSize);

View File

@@ -1,5 +1,4 @@
#include "mode7.cpp"
uint4 PPU::Background::Mosaic::size;
auto PPU::Background::hires() const -> bool {
return ppu.io.bgMode == 5 || ppu.io.bgMode == 6;
@@ -11,30 +10,11 @@ auto PPU::Background::frame() -> void {
//H = 0
auto PPU::Background::scanline() -> void {
if(ppu.vcounter() == 1) {
mosaic.vcounter = mosaic.size + 1;
mosaic.voffset = 1;
latch.hoffset = io.hoffset;
latch.voffset = io.voffset;
} else if(--mosaic.vcounter == 0) {
mosaic.vcounter = mosaic.size + 1;
mosaic.voffset += mosaic.size + 1;
latch.hoffset = io.hoffset;
latch.voffset = io.voffset;
}
mosaic.hcounter = mosaic.size + 1;
mosaic.hcounter = ppu.mosaic.size;
mosaic.hoffset = 0;
if(io.mode == Mode::Mode7) return beginMode7();
if(mosaic.size == 0) {
latch.hoffset = io.hoffset;
latch.voffset = io.voffset;
}
nameTableIndex = 0;
characterIndex = 0;
renderingIndex = 0;
pixelCounter = (io.hoffset & 7) << hires();
opt.hoffset = 0;
opt.voffset = 0;
@@ -43,22 +23,27 @@ auto PPU::Background::scanline() -> void {
//H = 56
auto PPU::Background::begin() -> void {
//remove partial tile columns that have been scrolled offscreen
pixelCounter = io.hoffset & 7;
for(auto& data : tiles[0].data) data >>= pixelCounter << 1;
}
auto PPU::Background::fetchNameTable() -> void {
if(ppu.vcounter() == 0) return;
uint nameTableIndex = ppu.hcounter() >> 5 << hires();
int x = (ppu.hcounter() & ~31) >> 2;
uint hpixel = x << hires();
uint vpixel = mosaic.enable ? (uint)mosaic.voffset : ppu.vcounter();
uint hscroll = mosaic.enable ? latch.hoffset : io.hoffset;
uint vscroll = mosaic.enable ? latch.voffset : io.voffset;
uint hpixel = x << hires();
uint vpixel = ppu.vcounter();
uint hscroll = io.hoffset;
uint vscroll = io.voffset;
if(mosaic.enable) vpixel -= ppu.mosaic.voffset();
if(hires()) {
hscroll <<= 1;
if(ppu.io.interlace) vpixel = vpixel << 1 | (ppu.field() && !mosaic.enable);
if(ppu.io.interlace) {
vpixel = vpixel << 1 | ppu.field();
if(mosaic.enable) vpixel -= ppu.mosaic.voffset() + ppu.field();
}
}
bool repeated = false;
@@ -141,6 +126,7 @@ auto PPU::Background::fetchNameTable() -> void {
auto PPU::Background::fetchOffset(uint y) -> void {
if(ppu.vcounter() == 0) return;
uint characterIndex = ppu.hcounter() >> 5 << hires();
uint x = characterIndex << 3;
uint hoffset = x + (io.hoffset & ~7);
@@ -162,13 +148,13 @@ auto PPU::Background::fetchOffset(uint y) -> void {
uint16 address = io.screenAddress + offset;
if(y == 0) opt.hoffset = ppu.vram[address];
if(y == 8) opt.voffset = ppu.vram[address];
if(y == 0) characterIndex++;
}
auto PPU::Background::fetchCharacter(uint index) -> void {
auto PPU::Background::fetchCharacter(uint index, bool half) -> void {
if(ppu.vcounter() == 0) return;
uint characterIndex = (ppu.hcounter() >> 5 << hires()) + half;
auto& tile = tiles[characterIndex];
uint16 data = ppu.vram[tile.address + (index << 3)];
@@ -184,8 +170,6 @@ auto PPU::Background::fetchCharacter(uint index) -> void {
((uint8(data >> 0) * 0x0101010101010101ull & 0x8040201008040201ull) * 0x0102040810204081ull >> 49) & 0x5555
| ((uint8(data >> 8) * 0x0101010101010101ull & 0x8040201008040201ull) * 0x0102040810204081ull >> 48) & 0xaaaa
);
if(index == 0) characterIndex++;
}
auto PPU::Background::run(bool screen) -> void {
@@ -214,10 +198,10 @@ auto PPU::Background::run(bool screen) -> void {
uint x = ppu.hcounter() - 56 >> 2;
if(x == 0) {
mosaic.hcounter = mosaic.size + 1;
mosaic.hcounter = ppu.mosaic.size;
mosaic.pixel = pixel;
} else if((!hires() || screen == Screen::Below) && --mosaic.hcounter == 0) {
mosaic.hcounter = mosaic.size + 1;
mosaic.hcounter = ppu.mosaic.size;
mosaic.pixel = pixel;
} else if(mosaic.enable) {
pixel = mosaic.pixel;
@@ -240,12 +224,9 @@ auto PPU::Background::power() -> void {
io.hoffset = random();
io.voffset = random();
latch = {};
output.above = {};
output.below = {};
mosaic = {};
mosaic.size = random();
mosaic.enable = random();
}

View File

@@ -9,13 +9,12 @@ struct Background {
auto begin() -> void;
auto fetchNameTable() -> void;
auto fetchOffset(uint y) -> void;
auto fetchCharacter(uint index) -> void;
auto fetchCharacter(uint index, bool half = 0) -> void;
auto run(bool screen) -> void;
auto power() -> void;
//mode7.cpp
alwaysinline auto clip(int n) -> int;
auto beginMode7() -> void;
auto runMode7() -> void;
auto serialize(serializer&) -> void;
@@ -44,11 +43,6 @@ struct Background {
uint16 voffset;
} io;
struct Latch {
uint16 hoffset;
uint16 voffset;
} latch;
struct Pixel {
uint8 priority; //0 = none (transparent)
uint8 palette;
@@ -61,15 +55,9 @@ struct Background {
} output;
struct Mosaic {
static uint4 size;
uint1 enable;
uint16 vcounter;
uint16 hcounter;
uint16 voffset;
uint16 hoffset;
Pixel pixel;
} mosaic;
@@ -90,8 +78,6 @@ struct Background {
uint16 data[4];
} tiles[66];
uint7 nameTableIndex;
uint7 characterIndex;
uint7 renderingIndex;
uint3 pixelCounter;

View File

@@ -262,11 +262,16 @@ auto PPU::writeIO(uint addr, uint8 data) -> void {
//MOSAIC
case 0x2106: {
bool mosaicEnable = mosaic.enable();
bg1.mosaic.enable = data >> 0 & 1;
bg2.mosaic.enable = data >> 1 & 1;
bg3.mosaic.enable = data >> 2 & 1;
bg4.mosaic.enable = data >> 3 & 1;
Background::Mosaic::size = data >> 4 & 15;
mosaic.size = (data >> 4 & 15) + 1;
if(!mosaicEnable && mosaic.enable()) {
//mosaic vcounter is reloaded when mosaic becomes enabled
mosaic.vcounter = mosaic.size + 1;
}
return;
}

View File

@@ -17,6 +17,7 @@ auto PPU::main() -> void {
obj.frame();
}
mosaic.scanline();
bg1.scanline();
bg2.scanline();
bg3.scanline();
@@ -94,60 +95,60 @@ auto PPU::cycleBackgroundFetch() -> void {
if constexpr(Cycle == 1) bg2.fetchNameTable();
if constexpr(Cycle == 2) bg1.fetchNameTable();
if constexpr(Cycle == 3) bg3.fetchCharacter(0);
if constexpr(Cycle == 4) bg2.fetchCharacter(1);
if constexpr(Cycle == 5) bg2.fetchCharacter(0);
if constexpr(Cycle == 6) bg1.fetchCharacter(1);
if constexpr(Cycle == 7) bg1.fetchCharacter(0);
if constexpr(Cycle == 4) bg2.fetchCharacter(0);
if constexpr(Cycle == 5) bg2.fetchCharacter(1);
if constexpr(Cycle == 6) bg1.fetchCharacter(0);
if constexpr(Cycle == 7) bg1.fetchCharacter(1);
break;
case 2:
if constexpr(Cycle == 0) bg2.fetchNameTable();
if constexpr(Cycle == 1) bg1.fetchNameTable();
if constexpr(Cycle == 2) bg3.fetchOffset(8);
if constexpr(Cycle == 3) bg3.fetchOffset(0);
if constexpr(Cycle == 4) bg2.fetchCharacter(1);
if constexpr(Cycle == 5) bg2.fetchCharacter(0);
if constexpr(Cycle == 6) bg1.fetchCharacter(1);
if constexpr(Cycle == 7) bg1.fetchCharacter(0);
if constexpr(Cycle == 2) bg3.fetchOffset(0);
if constexpr(Cycle == 3) bg3.fetchOffset(8);
if constexpr(Cycle == 4) bg2.fetchCharacter(0);
if constexpr(Cycle == 5) bg2.fetchCharacter(1);
if constexpr(Cycle == 6) bg1.fetchCharacter(0);
if constexpr(Cycle == 7) bg1.fetchCharacter(1);
break;
case 3:
if constexpr(Cycle == 0) bg2.fetchNameTable();
if constexpr(Cycle == 1) bg1.fetchNameTable();
if constexpr(Cycle == 2) bg2.fetchCharacter(1);
if constexpr(Cycle == 3) bg2.fetchCharacter(0);
if constexpr(Cycle == 4) bg1.fetchCharacter(3);
if constexpr(Cycle == 5) bg1.fetchCharacter(2);
if constexpr(Cycle == 6) bg1.fetchCharacter(1);
if constexpr(Cycle == 7) bg1.fetchCharacter(0);
if constexpr(Cycle == 2) bg2.fetchCharacter(0);
if constexpr(Cycle == 3) bg2.fetchCharacter(1);
if constexpr(Cycle == 4) bg1.fetchCharacter(0);
if constexpr(Cycle == 5) bg1.fetchCharacter(1);
if constexpr(Cycle == 6) bg1.fetchCharacter(2);
if constexpr(Cycle == 7) bg1.fetchCharacter(3);
break;
case 4:
if constexpr(Cycle == 0) bg2.fetchNameTable();
if constexpr(Cycle == 1) bg1.fetchNameTable();
if constexpr(Cycle == 2) bg3.fetchOffset(0);
if constexpr(Cycle == 3) bg2.fetchCharacter(0);
if constexpr(Cycle == 4) bg1.fetchCharacter(3);
if constexpr(Cycle == 5) bg1.fetchCharacter(2);
if constexpr(Cycle == 6) bg1.fetchCharacter(1);
if constexpr(Cycle == 7) bg1.fetchCharacter(0);
if constexpr(Cycle == 4) bg1.fetchCharacter(0);
if constexpr(Cycle == 5) bg1.fetchCharacter(1);
if constexpr(Cycle == 6) bg1.fetchCharacter(2);
if constexpr(Cycle == 7) bg1.fetchCharacter(3);
break;
case 5:
if constexpr(Cycle == 0) bg2.fetchNameTable();
if constexpr(Cycle == 1) bg1.fetchNameTable();
if constexpr(Cycle == 2) bg2.fetchCharacter(0);
if constexpr(Cycle == 3) bg2.fetchCharacter(0);
if constexpr(Cycle == 4) bg1.fetchCharacter(1);
if constexpr(Cycle == 5) bg1.fetchCharacter(0);
if constexpr(Cycle == 6) bg1.fetchCharacter(1);
if constexpr(Cycle == 7) bg1.fetchCharacter(0);
if constexpr(Cycle == 2) bg2.fetchCharacter(0, 0);
if constexpr(Cycle == 3) bg2.fetchCharacter(0, 1);
if constexpr(Cycle == 4) bg1.fetchCharacter(0, 0);
if constexpr(Cycle == 5) bg1.fetchCharacter(1, 0);
if constexpr(Cycle == 6) bg1.fetchCharacter(0, 1);
if constexpr(Cycle == 7) bg1.fetchCharacter(1, 1);
break;
case 6:
if constexpr(Cycle == 0) bg2.fetchNameTable();
if constexpr(Cycle == 1) bg1.fetchNameTable();
if constexpr(Cycle == 2) bg3.fetchOffset(8);
if constexpr(Cycle == 3) bg3.fetchOffset(0);
if constexpr(Cycle == 4) bg1.fetchCharacter(1);
if constexpr(Cycle == 5) bg1.fetchCharacter(0);
if constexpr(Cycle == 6) bg1.fetchCharacter(1);
if constexpr(Cycle == 7) bg1.fetchCharacter(0);
if constexpr(Cycle == 2) bg3.fetchOffset(0);
if constexpr(Cycle == 3) bg3.fetchOffset(8);
if constexpr(Cycle == 4) bg1.fetchCharacter(0, 0);
if constexpr(Cycle == 5) bg1.fetchCharacter(1, 0);
if constexpr(Cycle == 6) bg1.fetchCharacter(0, 1);
if constexpr(Cycle == 7) bg1.fetchCharacter(1, 1);
break;
case 7:
//handled separately by mode7.cpp

View File

@@ -3,12 +3,6 @@ auto PPU::Background::clip(int n) -> int {
return n & 0x2000 ? (n | ~1023) : (n & 1023);
}
//H = 0
auto PPU::Background::beginMode7() -> void {
latch.hoffset = ppu.io.hoffsetMode7;
latch.voffset = ppu.io.voffsetMode7;
}
auto PPU::Background::runMode7() -> void {
int a = (int16)ppu.io.m7a;
int b = (int16)ppu.io.m7b;
@@ -17,17 +11,18 @@ auto PPU::Background::runMode7() -> void {
int hcenter = (int13)ppu.io.m7x;
int vcenter = (int13)ppu.io.m7y;
int hoffset = (int13)latch.hoffset;
int voffset = (int13)latch.voffset;
int hoffset = (int13)ppu.io.hoffsetMode7;
int voffset = (int13)ppu.io.voffsetMode7;
uint x = mosaic.hoffset;
uint y = !mosaic.enable ? ppu.vcounter() : ppu.bg1.mosaic.voffset; //BG2 vertical mosaic uses BG1 mosaic size
uint y = ppu.vcounter();
if(ppu.bg1.mosaic.enable) y -= ppu.mosaic.voffset(); //BG2 vertical mosaic uses BG1 mosaic enable
if(!mosaic.enable) {
mosaic.hoffset += 1;
} else if(--mosaic.hcounter == 0) {
mosaic.hcounter = mosaic.size + 1;
mosaic.hoffset += mosaic.size + 1;
mosaic.hcounter = ppu.mosaic.size;
mosaic.hoffset += ppu.mosaic.size;
}
if(ppu.io.hflipMode7) x = 255 - x;

26
bsnes/sfc/ppu/mosaic.cpp Normal file
View File

@@ -0,0 +1,26 @@
auto PPU::Mosaic::enable() const -> bool {
if(ppu.bg1.mosaic.enable) return true;
if(ppu.bg2.mosaic.enable) return true;
if(ppu.bg3.mosaic.enable) return true;
if(ppu.bg4.mosaic.enable) return true;
return false;
}
auto PPU::Mosaic::voffset() const -> uint {
return size - vcounter;
}
//H = 0
auto PPU::Mosaic::scanline() -> void {
if(ppu.vcounter() == 1) {
vcounter = enable() ? size + 1 : 0;
}
if(vcounter && !--vcounter) {
vcounter = enable() ? size + 0 : 0;
}
}
auto PPU::Mosaic::power() -> void {
size = (random() & 15) + 1;
vcounter = 0;
}

13
bsnes/sfc/ppu/mosaic.hpp Normal file
View File

@@ -0,0 +1,13 @@
struct Mosaic {
//mosaic.cpp
alwaysinline auto enable() const -> bool;
alwaysinline auto voffset() const -> uint;
auto scanline() -> void;
auto power() -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
uint5 size;
uint5 vcounter;
};

View File

@@ -5,6 +5,7 @@ namespace SuperFamicom {
PPU ppu;
#include "main.cpp"
#include "io.cpp"
#include "mosaic.cpp"
#include "background.cpp"
#include "object.cpp"
#include "window.cpp"
@@ -173,6 +174,7 @@ auto PPU::power(bool reset) -> void {
//$213d OPVCT
io.vcounter = 0;
mosaic.power();
bg1.power();
bg2.power();
bg3.power();

View File

@@ -153,11 +153,13 @@ private:
uint16 vcounter;
} io;
#include "mosaic.hpp"
#include "background.hpp"
#include "object.hpp"
#include "window.hpp"
#include "screen.hpp"
Mosaic mosaic;
Background bg1;
Background bg2;
Background bg3;

View File

@@ -73,6 +73,7 @@ auto PPU::serialize(serializer& s) -> void {
s.integer(io.hcounter);
s.integer(io.vcounter);
mosaic.serialize(s);
bg1.serialize(s);
bg2.serialize(s);
bg3.serialize(s);
@@ -82,6 +83,11 @@ auto PPU::serialize(serializer& s) -> void {
screen.serialize(s);
}
auto PPU::Mosaic::serialize(serializer& s) -> void {
s.integer(size);
s.integer(vcounter);
}
auto PPU::Background::serialize(serializer& s) -> void {
s.integer(io.tiledataAddress);
s.integer(io.screenAddress);
@@ -94,9 +100,6 @@ auto PPU::Background::serialize(serializer& s) -> void {
s.integer(io.hoffset);
s.integer(io.voffset);
s.integer(latch.hoffset);
s.integer(latch.voffset);
s.integer(output.above.priority);
s.integer(output.above.palette);
s.integer(output.above.paletteGroup);
@@ -105,11 +108,8 @@ auto PPU::Background::serialize(serializer& s) -> void {
s.integer(output.below.palette);
s.integer(output.below.paletteGroup);
s.integer(mosaic.size);
s.integer(mosaic.enable);
s.integer(mosaic.vcounter);
s.integer(mosaic.hcounter);
s.integer(mosaic.voffset);
s.integer(mosaic.hoffset);
s.integer(mosaic.pixel.priority);
@@ -130,8 +130,6 @@ auto PPU::Background::serialize(serializer& s) -> void {
s.array(tile.data);
}
s.integer(nameTableIndex);
s.integer(characterIndex);
s.integer(renderingIndex);
s.integer(pixelCounter);
}

View File

@@ -52,22 +52,6 @@ auto nall::main(Arguments arguments) -> void {
emulator = new SuperFamicom::Interface;
program.create();
if(Emulator::Version.find(".") && settings.general.betaWarning && 0) {
MessageDialog dialog;
dialog.setTitle(Emulator::Name);
dialog.setText(
"This is a nightly release. Bugs and regressions are possible!\n"
"If you experience issues, please report them to me.\n"
"If stability is required, please use a stable release.\n"
);
dialog.setOption("Don't show this message again");
dialog.information();
if(dialog.checked()) {
settings.general.betaWarning = false;
settings.save();
}
}
Application::run();
Instances::presentation.destruct();
Instances::settingsWindow.destruct();

View File

@@ -31,12 +31,11 @@ auto InputManager::bindHotkeys() -> void {
hotkeys.append(InputHotkey("Rewind").onPress([&] {
if(!emulator->loaded() || program.fastForwarding) return;
program.rewinding = true;
if(program.rewind.frequency == 0) {
program.showMessage("Please enable rewind support in Settings->Emulator first");
} else {
program.rewindMode(Program::Rewind::Mode::Rewinding);
return program.showMessage("Please enable rewind support in Settings->Emulator first");
}
program.rewinding = true;
program.rewindMode(Program::Rewind::Mode::Rewinding);
volume = Emulator::audio.volume();
if(settings.rewind.mute) {
program.mute |= Program::Mute::Rewind;

View File

@@ -187,7 +187,7 @@ auto Presentation::create() -> void {
.setLogo(Resource::SameBoy)
.setDescription("Super Game Boy emulator")
.setVersion("0.12.1")
.setAuthor("Lior Halphon")
.setCopyright("Lior Halphon")
.setLicense("MIT")
.setWebsite("https://sameboy.github.io")
.setAlignment(*this)
@@ -199,7 +199,7 @@ auto Presentation::create() -> void {
.setLogo(Resource::Logo)
.setDescription("Super Nintendo emulator")
.setVersion(Emulator::Version)
.setAuthor("byuu")
.setCopyright("byuu")
.setLicense("GPLv3")
.setWebsite("https://byuu.org")
.setAlignment(*this)

View File

@@ -25,7 +25,7 @@ auto Program::moviePlay() -> void {
dialog.setTitle("Play Movie");
dialog.setPath(Path::desktop());
dialog.setFilters({string{"Movies (.bsv)|*.bsv"}});
if(auto location = dialog.openFile()) {
if(auto location = openFile(dialog)) {
if(auto fp = file::open(location, file::mode::read)) {
bool failed = false;
if(fp.read() != 'B') failed = true;
@@ -92,7 +92,7 @@ auto Program::movieStop() -> void {
dialog.setTitle("Save Movie");
dialog.setPath(Path::desktop());
dialog.setFilters({string{"Movies (.bsv)|*.bsv"}});
if(auto location = dialog.saveFile()) {
if(auto location = saveFile(dialog)) {
if(!location.endsWith(".bsv")) location.append(".bsv");
if(auto fp = file::open(location, file::mode::write)) {
fp.write('B');

View File

@@ -113,7 +113,7 @@ auto Program::load(uint id, string name, string type, vector<string> options) ->
dialog.setTitle("Load SNES ROM");
dialog.setPath(path("Games", settings.path.recent.superFamicom));
dialog.setFilters({string{"SNES ROMs|*.sfc:*.smc:*.zip:*.7z:*.SFC:*.SMC:*.ZIP:*.7Z:*.Sfc:*.Smc:*.Zip"}, string{"All Files|*"}});
superFamicom.location = dialog.openObject();
superFamicom.location = openGame(dialog);
superFamicom.option = dialog.option();
}
if(inode::exists(superFamicom.location)) {
@@ -133,7 +133,7 @@ auto Program::load(uint id, string name, string type, vector<string> options) ->
dialog.setTitle("Load Game Boy ROM");
dialog.setPath(path("Games", settings.path.recent.gameBoy));
dialog.setFilters({string{"Game Boy ROMs|*.gb:*.gbc:*.zip:*.7z:*.GB:*.GBC:*.ZIP:*.7Z:*.Gb:*.Gbc:*.Zip"}, string{"All Files|*"}});
gameBoy.location = dialog.openObject();
gameBoy.location = openGame(dialog);
gameBoy.option = dialog.option();
}
if(inode::exists(gameBoy.location)) {
@@ -153,7 +153,7 @@ auto Program::load(uint id, string name, string type, vector<string> options) ->
dialog.setTitle("Load BS Memory ROM");
dialog.setPath(path("Games", settings.path.recent.bsMemory));
dialog.setFilters({string{"BS Memory ROMs|*.bs:*.zip:*.7z:*.BS:*.ZIP:*.7Z:*.Bs:*.Zip"}, string{"All Files|*"}});
bsMemory.location = dialog.openObject();
bsMemory.location = openGame(dialog);
bsMemory.option = dialog.option();
}
if(inode::exists(bsMemory.location)) {
@@ -173,7 +173,7 @@ auto Program::load(uint id, string name, string type, vector<string> options) ->
dialog.setTitle("Load Sufami Turbo ROM - Slot A");
dialog.setPath(path("Games", settings.path.recent.sufamiTurboA));
dialog.setFilters({string{"Sufami Turbo ROMs|*.st:*.zip:*.7z:*.ST:*.ZIP:*.7Z:*.St:*.Zip"}, string{"All Files|*"}});
sufamiTurboA.location = dialog.openObject();
sufamiTurboA.location = openGame(dialog);
sufamiTurboA.option = dialog.option();
}
if(inode::exists(sufamiTurboA.location)) {
@@ -193,7 +193,7 @@ auto Program::load(uint id, string name, string type, vector<string> options) ->
dialog.setTitle("Load Sufami Turbo ROM - Slot B");
dialog.setPath(path("Games", settings.path.recent.sufamiTurboB));
dialog.setFilters({string{"Sufami Turbo ROMs|*.st:*.zip:*.7z:*.ST:*.ZIP:*.7Z:*.St:*.Zip"}, string{"All Files|*"}});
sufamiTurboB.location = dialog.openObject();
sufamiTurboB.location = openGame(dialog);
sufamiTurboB.option = dialog.option();
}
if(inode::exists(sufamiTurboB.location)) {
@@ -253,14 +253,13 @@ auto Program::videoFrame(const uint16* data, uint pitch, uint width, uint height
}
}
auto Program::audioFrame(const float* samples, uint channels) -> void {
auto Program::audioFrame(const double* samples, uint channels) -> void {
if(mute) {
double silence[] = {0.0, 0.0};
return audio.output(silence);
audio.output(silence);
} else {
audio.output(samples);
}
double frame[] = {samples[0], samples[1]};
audio.output(frame);
}
auto Program::inputPoll(uint port, uint device, uint input) -> int16 {

View File

@@ -10,7 +10,7 @@ struct Program : Lock, Emulator::Platform {
auto open(uint id, string name, vfs::file::mode mode, bool required) -> shared_pointer<vfs::file> override;
auto load(uint id, string name, string type, vector<string> options = {}) -> Emulator::Platform::Load override;
auto videoFrame(const uint16* data, uint pitch, uint width, uint height, uint scale) -> void override;
auto audioFrame(const float* samples, uint channels) -> void override;
auto audioFrame(const double* samples, uint channels) -> void override;
auto inputPoll(uint port, uint device, uint input) -> int16 override;
auto inputRumble(uint port, uint device, uint input, bool enable) -> void override;
@@ -114,6 +114,10 @@ struct Program : Lock, Emulator::Platform {
auto updateInputDriver(Window parent) -> void;
//utility.cpp
auto openGame(BrowserDialog& dialog) -> string;
auto openFile(BrowserDialog& dialog) -> string;
auto saveFile(BrowserDialog& dialog) -> string;
auto selectPath() -> string;
auto showMessage(string text) -> void;
auto showFrameRate(string text) -> void;
auto updateStatus() -> void;

View File

@@ -13,6 +13,18 @@ auto Program::rewindReset() -> void {
auto Program::rewindRun() -> void {
if(rewind.frequency == 0) return; //rewind disabled?
if(rewind.mode == Rewind::Mode::Playing) {
if(++rewind.counter < rewind.frequency) return;
rewind.counter = 0;
if(rewind.history.size() >= rewind.length) {
rewind.history.takeFirst();
}
auto s = emulator->serialize(0);
rewind.history.append(s);
return;
}
if(rewind.mode == Rewind::Mode::Rewinding) {
if(rewind.history.size() == 0) return rewindMode(Rewind::Mode::Playing); //nothing left to rewind?
if(++rewind.counter < rewind.frequency / 4) return;
@@ -27,16 +39,4 @@ auto Program::rewindRun() -> void {
emulator->unserialize(s);
return;
}
if(rewind.mode == Rewind::Mode::Playing) {
if(++rewind.counter < rewind.frequency) return;
rewind.counter = 0;
if(rewind.history.size() >= rewind.length) {
rewind.history.takeFirst();
}
auto s = emulator->serialize(0);
rewind.history.append(s);
return;
}
}

View File

@@ -1,3 +1,54 @@
auto Program::openGame(BrowserDialog& dialog) -> string {
if(!settings.general.nativeFileDialogs) {
return dialog.openObject();
}
BrowserWindow window;
window.setTitle(dialog.title());
window.setPath(dialog.path());
window.setFilters(dialog.filters());
window.setParent(dialog.alignmentWindow());
return window.open();
}
auto Program::openFile(BrowserDialog& dialog) -> string {
if(!settings.general.nativeFileDialogs) {
return dialog.openFile();
}
BrowserWindow window;
window.setTitle(dialog.title());
window.setPath(dialog.path());
window.setFilters(dialog.filters());
window.setParent(dialog.alignmentWindow());
return window.open();
}
auto Program::saveFile(BrowserDialog& dialog) -> string {
if(!settings.general.nativeFileDialogs) {
return dialog.saveFile();
}
BrowserWindow window;
window.setTitle(dialog.title());
window.setPath(dialog.path());
window.setFilters(dialog.filters());
window.setParent(dialog.alignmentWindow());
return window.save();
}
auto Program::selectPath() -> string {
if(!settings.general.nativeFileDialogs) {
BrowserDialog dialog;
dialog.setPath(Path::desktop());
return dialog.selectFolder();
}
BrowserWindow window;
window.setPath(Path::desktop());
return window.directory();
}
auto Program::showMessage(string text) -> void {
statusTime = chrono::millisecond();
statusMessage = text;

View File

@@ -20,6 +20,9 @@ auto EmulatorSettings::create() -> void {
autoLoadStateOnLoad.setText("Auto-resume on load").setChecked(settings.emulator.autoLoadStateOnLoad).onToggle([&] {
settings.emulator.autoLoadStateOnLoad = autoLoadStateOnLoad.checked();
});
nativeFileDialogs.setText("Use native file dialogs").setChecked(settings.general.nativeFileDialogs).onToggle([&] {
settings.general.nativeFileDialogs = nativeFileDialogs.checked();
});
optionsSpacer.setColor({192, 192, 192});
fastForwardLabel.setText("Fast Forward").setFont(Font().setBold());

View File

@@ -8,7 +8,7 @@ auto PathSettings::create() -> void {
gamesLabel.setText("Games:");
gamesPath.setEditable(false);
gamesAssign.setText("Assign ...").onActivate([&] {
if(auto location = BrowserDialog().selectFolder()) {
if(auto location = program.selectPath()) {
settings.path.games = location;
refreshPaths();
}
@@ -21,7 +21,7 @@ auto PathSettings::create() -> void {
patchesLabel.setText("Patches:");
patchesPath.setEditable(false);
patchesAssign.setText("Assign ...").onActivate([&] {
if(auto location = BrowserDialog().selectFolder()) {
if(auto location = program.selectPath()) {
settings.path.patches = location;
refreshPaths();
}
@@ -34,7 +34,7 @@ auto PathSettings::create() -> void {
savesLabel.setText("Saves:");
savesPath.setEditable(false);
savesAssign.setText("Assign ...").onActivate([&] {
if(auto location = BrowserDialog().selectFolder()) {
if(auto location = program.selectPath()) {
settings.path.saves = location;
refreshPaths();
}
@@ -47,7 +47,7 @@ auto PathSettings::create() -> void {
cheatsLabel.setText("Cheats:");
cheatsPath.setEditable(false);
cheatsAssign.setText("Assign ...").onActivate([&] {
if(auto location = BrowserDialog().selectFolder()) {
if(auto location = program.selectPath()) {
settings.path.cheats = location;
refreshPaths();
}
@@ -60,7 +60,7 @@ auto PathSettings::create() -> void {
statesLabel.setText("States:");
statesPath.setEditable(false);
statesAssign.setText("Assign ...").onActivate([&] {
if(auto location = BrowserDialog().selectFolder()) {
if(auto location = program.selectPath()) {
settings.path.states = location;
refreshPaths();
}
@@ -73,7 +73,7 @@ auto PathSettings::create() -> void {
screenshotsLabel.setText("Screenshots:");
screenshotsPath.setEditable(false);
screenshotsAssign.setText("Assign ...").onActivate([&] {
if(auto location = BrowserDialog().selectFolder()) {
if(auto location = program.selectPath()) {
settings.path.screenshots = location;
refreshPaths();
}

View File

@@ -24,7 +24,7 @@ SettingsWindow& settingsWindow = Instances::settingsWindow();
auto Settings::load() -> void {
Markup::Node::operator=(BML::unserialize(string::read(location), " "));
process(true);
file::write(locate("settings.bml"), BML::serialize(*this, " "));
save();
}
auto Settings::save() -> void {
@@ -137,11 +137,11 @@ auto Settings::process(bool load) -> void {
bind(natural, "Emulator/Hack/SuperFX/Overclock", emulator.hack.superfx.overclock);
bind(boolean, "Emulator/Cheats/Enable", emulator.cheats.enable);
bind(boolean, "General/StatusBar", general.statusBar);
bind(boolean, "General/ScreenSaver", general.screenSaver);
bind(boolean, "General/ToolTips", general.toolTips);
bind(boolean, "General/Crashed", general.crashed);
bind(boolean, "General/BetaWarning", general.betaWarning);
bind(boolean, "General/StatusBar", general.statusBar);
bind(boolean, "General/ScreenSaver", general.screenSaver);
bind(boolean, "General/ToolTips", general.toolTips);
bind(boolean, "General/Crashed", general.crashed);
bind(boolean, "General/NativeFileDialogs", general.nativeFileDialogs);
#undef bind
}

View File

@@ -145,7 +145,7 @@ struct Settings : Markup::Node {
bool screenSaver = false;
bool toolTips = true;
bool crashed = false;
bool betaWarning = true;
bool nativeFileDialogs = false;
} general;
};
@@ -303,11 +303,12 @@ struct EmulatorSettings : VerticalLayout {
public:
Label optionsLabel{this, Size{~0, 0}, 2};
CheckLabel warnOnUnverifiedGames{this, Size{~0, 0}};
CheckLabel autoSaveMemory{this, Size{~0, 0}};
HorizontalLayout autoStateLayout{this, Size{~0, 0}};
CheckLabel warnOnUnverifiedGames{this, Size{~0, 0}, 2};
CheckLabel autoSaveMemory{this, Size{~0, 0}, 2};
HorizontalLayout autoStateLayout{this, Size{~0, 0}, 2};
CheckLabel autoSaveStateOnUnload{&autoStateLayout, Size{0, 0}};
CheckLabel autoLoadStateOnLoad{&autoStateLayout, Size{0, 0}};
CheckLabel nativeFileDialogs{this, Size{~0, 0}};
Canvas optionsSpacer{this, Size{~0, 1}};
//
Label fastForwardLabel{this, Size{~0, 0}, 2};

View File

@@ -32,6 +32,8 @@ static void audio_queue(int16_t left, int16_t right)
#include "program.cpp"
static string sgb_bios;
static vector<string> cheatList;
static int aspect_ratio_mode = 0;
#define RETRO_DEVICE_JOYPAD_MULTITAP RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 0)
#define RETRO_DEVICE_LIGHTGUN_SUPER_SCOPE RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_LIGHTGUN, 0)
@@ -42,9 +44,42 @@ static string sgb_bios;
#define RETRO_MEMORY_SGB_SRAM ((1 << 8) | RETRO_MEMORY_SAVE_RAM)
#define RETRO_MEMORY_GB_SRAM ((2 << 8) | RETRO_MEMORY_SAVE_RAM)
static double get_aspect_ratio()
{
if (aspect_ratio_mode == 0 && program->superFamicom.region == "NTSC")
return 1.306122;
else if (aspect_ratio_mode == 0 && program->superFamicom.region == "PAL")
return 1.584216;
else if (aspect_ratio_mode == 1) // 8:7
return 8.0/7.0;
else if (aspect_ratio_mode == 2) // 4:3
return 4.0/3.0;
else if (aspect_ratio_mode == 3) // NTSC
return 1.306122;
else if (aspect_ratio_mode == 4) // PAL
return 1.584216;
else
return 8.0/7.0; // Default
}
static void flush_variables()
{
retro_variable variable = { "bsnes_blur_emulation", nullptr };
retro_variable variable = { "bsnes_aspect_ratio", nullptr };
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &variable) && variable.value)
{
if (strcmp(variable.value, "8:7") == 0)
aspect_ratio_mode = 1;
else if (strcmp(variable.value, "4:3") == 0)
aspect_ratio_mode = 2;
else if (strcmp(variable.value, "NTSC") == 0)
aspect_ratio_mode = 3;
else if (strcmp(variable.value, "PAL") == 0)
aspect_ratio_mode = 4;
else
aspect_ratio_mode = 0;
}
variable = { "bsnes_blur_emulation", nullptr };
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &variable) && variable.value)
{
if (strcmp(variable.value, "ON") == 0)
@@ -243,6 +278,12 @@ static void flush_variables()
else
run_ahead_frames = atoi(variable.value);
}
// Refresh Geometry
struct retro_system_av_info avinfo;
retro_get_system_av_info(&avinfo);
avinfo.geometry.aspect_ratio = get_aspect_ratio();
environ_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, &avinfo);
}
static void check_variables()
@@ -399,6 +440,7 @@ static void set_environment_info(retro_environment_t cb)
cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, const_cast<retro_input_descriptor *>(desc));
static const retro_variable vars[] = {
{ "bsnes_aspect_ratio", "Aspect Ratio; Auto|8:7|4:3|NTSC|PAL" },
{ "bsnes_blur_emulation", "Blur emulation; OFF|ON" },
{ "bsnes_entropy", "Entropy (randomization); Low|High|None" },
{ "bsnes_hotfixes", "Hotfixes; OFF|ON" },
@@ -494,7 +536,9 @@ RETRO_API void retro_get_system_av_info(struct retro_system_av_info *info)
info->geometry.base_height = program->overscan ? 480 : 448; // accurate ppu
info->geometry.max_width = 2048; // 8x 256 for hd mode 7
info->geometry.max_height = 1920; // 8x 240
info->geometry.aspect_ratio = get_aspect_ratio();
info->timing.sample_rate = SAMPLERATE;
if (retro_get_region() == RETRO_REGION_NTSC) {
info->timing.fps = 21477272.0 / 357366.0;
audio_buffer_max = (SAMPLERATE/60) * 2;
@@ -563,10 +607,29 @@ RETRO_API bool retro_unserialize(const void *data, size_t size)
RETRO_API void retro_cheat_reset()
{
cheatList.reset();
emulator->cheats(cheatList);
}
RETRO_API void retro_cheat_set(unsigned index, bool enabled, const char *code)
{
string cheat = string(code);
bool decoded = false;
if (program->gameBoy.program)
{
decoded = decodeGB(cheat);
}
else
{
decoded = decodeSNES(cheat);
}
if (enabled && decoded)
{
cheatList.append(cheat);
emulator->cheats(cheatList);
}
}
RETRO_API bool retro_load_game(const retro_game_info *game)

View File

@@ -28,7 +28,7 @@ struct Program : Emulator::Platform
auto open(uint id, string name, vfs::file::mode mode, bool required) -> shared_pointer<vfs::file> override;
auto load(uint id, string name, string type, vector<string> options = {}) -> Emulator::Platform::Load override;
auto videoFrame(const uint16* data, uint pitch, uint width, uint height, uint scale) -> void override;
auto audioFrame(const float* samples, uint channels) -> void override;
auto audioFrame(const double* samples, uint channels) -> void override;
auto inputPoll(uint port, uint device, uint input) -> int16 override;
auto inputRumble(uint port, uint device, uint input, bool enable) -> void override;
@@ -143,12 +143,31 @@ auto Program::load() -> void {
auto title = superFamicom.title;
auto region = superFamicom.region;
//sometimes menu options are skipped over in the main menu with cycle-based joypad polling
if(title == "Arcades Greatest Hits") emulator->configure("Hacks/CPU/FastJoypadPolling", true);
//the start button doesn't work in this game with cycle-based joypad polling
if(title == "TAIKYOKU-IGO Goliath") emulator->configure("Hacks/CPU/FastJoypadPolling", true);
//holding up or down on the menu quickly cycles through options instead of stopping after each button press
if(title == "WORLD MASTERS GOLF") emulator->configure("Hacks/CPU/FastJoypadPolling", true);
//relies on mid-scanline rendering techniques
if(title == "AIR STRIKE PATROL" || title == "DESERT FIGHTER") emulator->configure("Hacks/PPU/Fast", false);
//the dialogue text is blurry due to an issue in the scanline-based renderer's color math support
if(title == "マーヴェラス") emulator->configure("Hacks/PPU/Fast", false);
//stage 2 uses pseudo-hires in a way that's not compatible with the scanline-based renderer
if(title == "SFC クレヨンシンチャン") emulator->configure("Hacks/PPU/Fast", false);
//title screen game select (after choosing a game) changes OAM tiledata address mid-frame
//this is only supported by the cycle-based PPU renderer
if(title == "Winter olympics") emulator->configure("Hacks/PPU/Fast", false);
//title screen shows remnants of the flag after choosing a language with the scanline-based renderer
if(title == "WORLD CUP STRIKER") emulator->configure("Hacks/PPU/Fast", false);
//relies on cycle-accurate writes to the echo buffer
if(title == "KOUSHIEN_2") emulator->configure("Hacks/DSP/Fast", false);
@@ -162,13 +181,22 @@ auto Program::load() -> void {
if(title == "ADVENTURES OF FRANKEN" && region == "PAL") emulator->configure("Hacks/PPU/RenderCycle", 32);
//fixes an errant scanline on the title screen due to writing to PPU registers too late
if(title == "FIREPOWER 2000") emulator->configure("Hacks/PPU/RenderCycle", 32);
if(title == "FIREPOWER 2000" || title == "SUPER SWIV") emulator->configure("Hacks/PPU/RenderCycle", 32);
//fixes an errant scanline on the title screen due to writing to PPU registers too late
if(title == "NHL '94" || title == "NHL PROHOCKEY'94") emulator->configure("Hacks/PPU/RenderCycle", 32);
//fixes an errant scanline on the title screen due to writing to PPU registers too late
if(title == "Sugoro Quest++") emulator->configure("Hacks/PPU/RenderCycle", 128);
if (emulator->configuration("Hacks/Hotfixes")) {
if (title == "The Hurricanes") emulator->configure("Hacks/Entropy", "None");
//this game transfers uninitialized memory into video RAM: this can cause a row of invalid tiles
//to appear in the background of stage 12. this one is a bug in the original game, so only enable
//it if the hotfixes option has been enabled.
if(title == "The Hurricanes") emulator->configure("Hacks/Entropy", "None");
//Frisky Tom attract sequence sometimes hangs when WRAM is initialized to pseudo-random patterns
if (title == "ニチブツ・アーケード・クラシックス") emulator->configure("Hacks/Entropy", "None");
}
emulator->power();
@@ -213,7 +241,7 @@ static int16_t d2i16(double v)
return int16_t(floor(v + 0.5));
}
auto Program::audioFrame(const float* samples, uint channels) -> void
auto Program::audioFrame(const double* samples, uint channels) -> void
{
int16_t left = d2i16(samples[0]);
int16_t right = d2i16(samples[1]);
@@ -318,6 +346,17 @@ auto Program::openRomSuperFamicom(string name, vfs::file::mode mode) -> shared_p
return vfs::memory::file::open(superFamicom.expansion.data(), superFamicom.expansion.size());
}
if(name == "msu1/data.rom")
{
return vfs::fs::file::open({Location::notsuffix(superFamicom.location), ".msu"}, mode);
}
if(name.match("msu1/track*.pcm"))
{
name.trimLeft("msu1/track", 1L);
return vfs::fs::file::open({Location::notsuffix(superFamicom.location), name}, mode);
}
if(name == "save.ram")
{
string save_path;
@@ -482,3 +521,172 @@ auto Program::hackPatchMemory(vector<uint8_t>& data) -> void
if(data[0x4e9a] == 0x10) data[0x4e9a] = 0x80;
}
}
auto decodeSNES(string& code) -> bool {
//Game Genie
if(code.size() == 9 && code[4u] == '-') {
//strip '-'
code = {code.slice(0, 4), code.slice(5, 4)};
//validate
for(uint n : code) {
if(n >= '0' && n <= '9') continue;
if(n >= 'a' && n <= 'f') continue;
return false;
}
//decode
code.transform("df4709156bc8a23e", "0123456789abcdef");
uint32_t r = toHex(code);
//abcd efgh ijkl mnop qrst uvwx
//ijkl qrst opab cduv wxef ghmn
uint address =
(!!(r & 0x002000) << 23) | (!!(r & 0x001000) << 22)
| (!!(r & 0x000800) << 21) | (!!(r & 0x000400) << 20)
| (!!(r & 0x000020) << 19) | (!!(r & 0x000010) << 18)
| (!!(r & 0x000008) << 17) | (!!(r & 0x000004) << 16)
| (!!(r & 0x800000) << 15) | (!!(r & 0x400000) << 14)
| (!!(r & 0x200000) << 13) | (!!(r & 0x100000) << 12)
| (!!(r & 0x000002) << 11) | (!!(r & 0x000001) << 10)
| (!!(r & 0x008000) << 9) | (!!(r & 0x004000) << 8)
| (!!(r & 0x080000) << 7) | (!!(r & 0x040000) << 6)
| (!!(r & 0x020000) << 5) | (!!(r & 0x010000) << 4)
| (!!(r & 0x000200) << 3) | (!!(r & 0x000100) << 2)
| (!!(r & 0x000080) << 1) | (!!(r & 0x000040) << 0);
uint data = r >> 24;
code = {hex(address, 6L), "=", hex(data, 2L)};
return true;
}
//Pro Action Replay
if(code.size() == 8) {
//validate
for(uint n : code) {
if(n >= '0' && n <= '9') continue;
if(n >= 'a' && n <= 'f') continue;
return false;
}
//decode
uint32_t r = toHex(code);
uint address = r >> 8;
uint data = r & 0xff;
code = {hex(address, 6L), "=", hex(data, 2L)};
return true;
}
//higan: address=data
if(code.size() == 9 && code[6u] == '=') {
string nibbles = {code.slice(0, 6), code.slice(7, 2)};
//validate
for(uint n : nibbles) {
if(n >= '0' && n <= '9') continue;
if(n >= 'a' && n <= 'f') continue;
return false;
}
//already in decoded form
return true;
}
//higan: address=compare?data
if(code.size() == 12 && code[6u] == '=' && code[9u] == '?') {
string nibbles = {code.slice(0, 6), code.slice(7, 2), code.slice(10, 2)};
//validate
for(uint n : nibbles) {
if(n >= '0' && n <= '9') continue;
if(n >= 'a' && n <= 'f') continue;
return false;
}
//already in decoded form
return true;
}
//unrecognized code format
return false;
}
auto decodeGB(string& code) -> bool {
auto nibble = [&](const string& s, uint index) -> uint {
if(index >= s.size()) return 0;
if(s[index] >= '0' && s[index] <= '9') return s[index] - '0';
return s[index] - 'a' + 10;
};
//Game Genie
if(code.size() == 7 && code[3u] == '-') {
code = {code.slice(0, 3), code.slice(4, 3)};
//validate
for(uint n : code) {
if(n >= '0' && n <= '9') continue;
if(n >= 'a' && n <= 'f') continue;
return false;
}
uint data = nibble(code, 0) << 4 | nibble(code, 1) << 0;
uint address = (nibble(code, 5) ^ 15) << 12 | nibble(code, 2) << 8 | nibble(code, 3) << 4 | nibble(code, 4) << 0;
code = {hex(address, 4L), "=", hex(data, 2L)};
return true;
}
//Game Genie
if(code.size() == 11 && code[3u] == '-' && code[7u] == '-') {
code = {code.slice(0, 3), code.slice(4, 3), code.slice(8, 3)};
//validate
for(uint n : code) {
if(n >= '0' && n <= '9') continue;
if(n >= 'a' && n <= 'f') continue;
return false;
}
uint data = nibble(code, 0) << 4 | nibble(code, 1) << 0;
uint address = (nibble(code, 5) ^ 15) << 12 | nibble(code, 2) << 8 | nibble(code, 3) << 4 | nibble(code, 4) << 0;
uint8_t t = nibble(code, 6) << 4 | nibble(code, 8) << 0;
t = t >> 2 | t << 6;
uint compare = t ^ 0xba;
code = {hex(address, 4L), "=", hex(compare, 2L), "?", hex(data, 2L)};
return true;
}
//GameShark
if(code.size() == 8) {
//validate
for(uint n : code) {
if(n >= '0' && n <= '9') continue;
if(n >= 'a' && n <= 'f') continue;
return false;
}
//first two characters are the code type / VRAM bank, which is almost always 01.
//other values are presumably supported, but I have no info on them, so they're not supported.
if(code[0u] != '0') return false;
if(code[1u] != '1') return false;
uint data = toHex(code.slice(2, 2));
uint16_t address = toHex(code.slice(4, 4));
address = address >> 8 | address << 8;
code = {hex(address, 4L), "=", hex(data, 2L)};
return true;
}
//higan: address=data
if(code.size() == 7 && code[4u] == '=') {
string nibbles = {code.slice(0, 4), code.slice(5, 2)};
//validate
for(uint n : nibbles) {
if(n >= '0' && n <= '9') continue;
if(n >= 'a' && n <= 'f') continue;
return false;
}
//already in decoded form
return true;
}
//higan: address=compare?data
if(code.size() == 10 && code[4u] == '=' && code[7u] == '?') {
string nibbles = {code.slice(0, 4), code.slice(5, 2), code.slice(8, 2)};
//validate
for(uint n : nibbles) {
if(n >= '0' && n <= '9') continue;
if(n >= 'a' && n <= 'f') continue;
return false;
}
//already in decoded form
return true;
}
//unrecognized code format
return false;
}

File diff suppressed because it is too large Load Diff

View File

@@ -37,6 +37,11 @@ NSTimer* applicationTimer = nullptr;
namespace hiro {
auto pApplication::exit() -> void {
quit();
::exit(EXIT_SUCCESS);
}
auto pApplication::modal() -> bool {
return Application::state().modal > 0;
}

View File

@@ -11,6 +11,7 @@
namespace hiro {
struct pApplication {
static auto exit() -> void;
static auto modal() -> bool;
static auto run() -> void;
static auto pendingEvents() -> bool;

View File

@@ -26,7 +26,7 @@ auto pBrowserWindow::open(BrowserWindow::State& state) -> string {
@autoreleasepool {
NSMutableArray* filters = [[NSMutableArray alloc] init];
for(auto& rule : state.filters) {
string pattern = rule.split("(", 1L)(1).trimRight(")", 1L);
string pattern = rule.split("|", 1L)(1).transform(":", ";");
if(pattern) [filters addObject:[NSString stringWithUTF8String:pattern]];
}
NSOpenPanel* panel = [NSOpenPanel openPanel];
@@ -51,7 +51,7 @@ auto pBrowserWindow::save(BrowserWindow::State& state) -> string {
@autoreleasepool {
NSMutableArray* filters = [[NSMutableArray alloc] init];
for(auto& rule : state.filters) {
string pattern = rule.split("(", 1L)(1).trimRight(")", 1L);
string pattern = rule.split("|", 1L)(1).transform(":", ";");
if(pattern) [filters addObject:[NSString stringWithUTF8String:pattern]];
}
NSSavePanel* panel = [NSSavePanel savePanel];

View File

@@ -10,8 +10,8 @@ auto Application::doMain() -> void {
}
auto Application::exit() -> void {
quit();
::exit(EXIT_SUCCESS);
state().quit = true;
return pApplication::exit();
}
auto Application::font() -> Font {

View File

@@ -12,8 +12,8 @@ auto AboutDialog::setAlignment(sWindow relativeTo, Alignment alignment) -> type&
return *this;
}
auto AboutDialog::setAuthor(const string& author) -> type& {
state.author = author;
auto AboutDialog::setCopyright(const string& copyright) -> type& {
state.copyright = copyright;
return *this;
}
@@ -64,7 +64,7 @@ auto AboutDialog::show() -> void {
nameLabel.setText(state.name ? state.name : Application::name());
nameLabel.setVisible((bool)state.name && !(bool)state.logo);
Canvas logoCanvas{&layout, Size{~0, 0}};
Canvas logoCanvas{&layout, Size{~0, 0}, 5_sy};
logoCanvas.setCollapsible();
if(state.logo) {
image logo{state.logo};
@@ -95,19 +95,19 @@ auto AboutDialog::show() -> void {
versionValue.setText(state.version);
if(!state.version) versionLayout.setVisible(false);
HorizontalLayout authorLayout{&layout, Size{~0, 0}, 0};
authorLayout.setCollapsible();
Label authorLabel{&authorLayout, Size{~0, 0}, 3_sx};
authorLabel.setAlignment(1.0);
authorLabel.setFont(Font().setBold());
authorLabel.setForegroundColor({0, 0, 0});
authorLabel.setText("Author:");
Label authorValue{&authorLayout, Size{~0, 0}};
authorValue.setAlignment(0.0);
authorValue.setFont(Font().setBold());
authorValue.setForegroundColor({0, 0, 0});
authorValue.setText(state.author);
if(!state.author) authorLayout.setVisible(false);
HorizontalLayout copyrightLayout{&layout, Size{~0, 0}, 0};
copyrightLayout.setCollapsible();
Label copyrightLabel{&copyrightLayout, Size{~0, 0}, 3_sx};
copyrightLabel.setAlignment(1.0);
copyrightLabel.setFont(Font().setBold());
copyrightLabel.setForegroundColor({0, 0, 0});
copyrightLabel.setText("Copyright:");
Label copyrightValue{&copyrightLayout, Size{~0, 0}};
copyrightValue.setAlignment(0.0);
copyrightValue.setFont(Font().setBold());
copyrightValue.setForegroundColor({0, 0, 0});
copyrightValue.setText(state.copyright);
if(!state.copyright) copyrightLayout.setVisible(false);
HorizontalLayout licenseLayout{&layout, Size{~0, 0}, 0};
licenseLayout.setCollapsible();
@@ -151,7 +151,7 @@ auto AboutDialog::show() -> void {
window.setTitle({"About ", state.name ? state.name : Application::name(), " ..."});
window.setBackgroundColor({255, 255, 240});
window.setSize({max(360_sx, layout.minimumSize().width()), layout.minimumSize().height()});
window.setSize({max(320_sx, layout.minimumSize().width()), layout.minimumSize().height()});
window.setResizable(false);
window.setAlignment(state.relativeTo, state.alignment);
window.setDismissable();

View File

@@ -5,7 +5,7 @@ struct AboutDialog {
auto setAlignment(Alignment = Alignment::Center) -> type&;
auto setAlignment(sWindow relativeTo, Alignment = Alignment::Center) -> type&;
auto setAuthor(const string& author = "") -> type&;
auto setCopyright(const string& copyright = "") -> type&;
auto setDescription(const string& description = "") -> type&;
auto setLicense(const string& license = "") -> type&;
auto setLogo(const image& logo = {}) -> type&;
@@ -17,7 +17,7 @@ struct AboutDialog {
private:
struct State {
Alignment alignment = Alignment::Center;
string author;
string copyright;
string description;
string license;
image logo;

View File

@@ -241,12 +241,8 @@ auto BrowserDialogWindow::run() -> BrowserDialog::Response {
return (void)window.setModal(false);
}
if(state.action == "openObject" && isObject(name)) {
if(isMatch(name)) {
response.selected.append({state.path, name});
return (void)window.setModal(false);
} else if(isFolder(name)) {
return setPath({state.path, name});
}
response.selected.append({state.path, name});
return (void)window.setModal(false);
}
if(state.action == "saveFile") return accept();
setPath(state.path, name);
@@ -417,6 +413,18 @@ auto BrowserDialogWindow::setPath(string path, const string& contains) -> void {
BrowserDialog::BrowserDialog() {
}
auto BrowserDialog::alignment() const -> Alignment {
return state.alignment;
}
auto BrowserDialog::alignmentWindow() const -> Window {
return state.relativeTo;
}
auto BrowserDialog::filters() const -> vector<string> {
return state.filters;
}
auto BrowserDialog::openFile() -> string {
state.action = "openFile";
if(!state.title) state.title = "Open File";
@@ -449,6 +457,10 @@ auto BrowserDialog::option() -> string {
return response.option;
}
auto BrowserDialog::path() const -> string {
return state.path;
}
auto BrowserDialog::saveFile() -> string {
state.action = "saveFile";
if(!state.title) state.title = "Save File";
@@ -504,6 +516,10 @@ auto BrowserDialog::setTitle(const string& title) -> type& {
return *this;
}
auto BrowserDialog::title() const -> string {
return state.title;
}
auto BrowserDialog::_run() -> vector<string> {
if(!state.path) state.path = Path::user();
response = BrowserDialogWindow(state).run();

View File

@@ -6,11 +6,15 @@ struct BrowserDialog {
using type = BrowserDialog;
BrowserDialog();
auto alignment() const -> Alignment;
auto alignmentWindow() const -> Window;
auto filters() const -> vector<string>;
auto openFile() -> string; //one existing file
auto openFiles() -> vector<string>; //any existing files
auto openFolder() -> string; //one existing folder
auto openObject() -> string; //one existing file or folder
auto option() -> string;
auto path() const -> string;
auto saveFile() -> string; //one file
auto selected() -> vector<string>;
auto selectFolder() -> string; //one existing folder
@@ -21,6 +25,7 @@ struct BrowserDialog {
auto setOptions(const vector<string>& options = {}) -> type&;
auto setPath(const string& path = "") -> type&;
auto setTitle(const string& title = "") -> type&;
auto title() const -> string;
private:
struct State {

View File

@@ -15,6 +15,11 @@ auto Log_Filter(const char* logDomain, GLogLevelFlags logLevel, const char* mess
print(terminal::color::yellow("hiro: "), logDomain, "::", message, "\n");
}
auto pApplication::exit() -> void {
quit();
::exit(EXIT_SUCCESS);
}
auto pApplication::modal() -> bool {
return Application::state().modal > 0;
}

View File

@@ -3,6 +3,7 @@
namespace hiro {
struct pApplication {
static auto exit() -> void;
static auto modal() -> bool;
static auto run() -> void;
static auto pendingEvents() -> bool;

View File

@@ -4,9 +4,12 @@ namespace hiro {
static auto BrowserWindow_addFilters(GtkWidget* dialog, vector<string> filters) -> void {
for(auto& filter : filters) {
auto part = filter.split("|", 1L);
if(part.size() != 2) continue;
GtkFileFilter* gtkFilter = gtk_file_filter_new();
gtk_file_filter_set_name(gtkFilter, filter);
auto patterns = filter.split("(", 1L)(1).trimRight(")", 1L).split(",").strip();
gtk_file_filter_set_name(gtkFilter, part[0]);
auto patterns = part[1].split(":");
for(auto& pattern : patterns) gtk_file_filter_add_pattern(gtkFilter, pattern);
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), gtkFilter);
}

View File

@@ -32,6 +32,9 @@ auto pTableViewItem::setBackgroundColor(Color color) -> void {
auto pTableViewItem::setFocused() -> void {
if(auto parent = _parent()) {
//calling setSelected() and then setFocused() right after sometimes fails to set focus
Application::processEvents();
auto lock = parent->acquire();
GtkTreePath* path = gtk_tree_path_new_from_string(string{self().offset()});
gtk_tree_view_set_cursor(parent->gtkTreeView, path, nullptr, false);

View File

@@ -127,6 +127,8 @@ auto pWidget::setGeometry(Geometry geometry) -> void {
}
}
pSizable::setGeometry(geometry);
//this is needed to prevent some repainting issues (specifically with a Label which has a background color set for it)
gtk_widget_queue_draw(gtkWidget);
}
auto pWidget::setMouseCursor(const MouseCursor& mouseCursor) -> void {

View File

@@ -2,6 +2,11 @@
namespace hiro {
auto pApplication::exit() -> void {
quit();
::exit(EXIT_SUCCESS);
}
auto pApplication::modal() -> bool {
return Application::state().modal > 0;
}

View File

@@ -3,6 +3,7 @@
namespace hiro {
struct pApplication {
static auto exit() -> void;
static auto modal() -> bool;
static auto run() -> void;
static auto pendingEvents() -> bool;

View File

@@ -3,63 +3,48 @@
namespace hiro {
auto pBrowserWindow::directory(BrowserWindow::State& state) -> string {
return {};
/*
QString directory = QFileDialog::getExistingDirectory(
state.parent ? state.parent->p.qtWindow : nullptr,
state.title ? state.title : "Select Directory",
state.parent ? state.parent->self()->qtWindow : nullptr,
state.title ? QString::fromUtf8(state.title) : "Select Directory",
QString::fromUtf8(state.path), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks
);
string name = directory.toUtf8().constData();
if(name && name.endsWith("/") == false) name.append("/");
return name;
*/
}
auto pBrowserWindow::open(BrowserWindow::State& state) -> string {
return {};
/*
string filters = state.filters.merge(";;");
//convert filter list from phoenix to Qt format, example:
//"Text, XML files (*.txt,*.xml)" -> "Text, XML files (*.txt *.xml)"
signed parentheses = 0;
for(auto& n : filters) {
if(n == '(') parentheses++;
if(n == ')') parentheses--;
if(n == ',' && parentheses) n = ' ';
string filters;
for(auto& filter : state.filters) {
auto part = filter.split("|", 1L);
if(part.size() != 2) continue;
filters.append(part[0], " (", part[1].transform(":", " "), ");;");
}
filters.trimRight(";;", 1L);
QString filename = QFileDialog::getOpenFileName(
state.parent ? state.parent->p.qtWindow : nullptr,
state.title ? state.title : "Open File",
state.parent ? state.parent->self()->qtWindow : nullptr,
state.title ? QString::fromUtf8(state.title) : "Open File",
QString::fromUtf8(state.path), QString::fromUtf8(filters)
);
return filename.toUtf8().constData();
*/
}
auto pBrowserWindow::save(BrowserWindow::State& state) -> string {
return {};
/*
string filters = state.filters.merge(";;");
//convert filter list from phoenix to Qt format, example:
//"Text, XML files (*.txt,*.xml)" -> "Text, XML files (*.txt *.xml)"
signed parentheses = 0;
for(auto& n : filters) {
if(n == '(') parentheses++;
if(n == ')') parentheses--;
if(n == ',' && parentheses) n = ' ';
string filters;
for(auto& filter : state.filters) {
auto part = filter.split("|", 1L);
if(part.size() != 2) continue;
filters.append(part[0], " (", part[1].transform(":", " "), ");;");
}
filters.trimRight(";;", 1L);
QString filename = QFileDialog::getSaveFileName(
state.parent ? state.parent->p.qtWindow : nullptr,
state.title ? state.title : "Save File",
state.parent ? state.parent->self()->qtWindow : nullptr,
state.title ? QString::fromUtf8(state.title) : "Save File",
QString::fromUtf8(state.path), QString::fromUtf8(filters)
);
return filename.toUtf8().constData();
*/
}
}

View File

@@ -27,35 +27,31 @@ static auto MessageWindow_response(MessageWindow::Buttons buttons, QMessageBox::
}
auto pMessageWindow::error(MessageWindow::State& state) -> MessageWindow::Response {
return {};
// return MessageWindow_response(
// state.buttons, QMessageBox::critical(state.parent ? state.parent->p.qtWindow : nullptr, state.title ? state.title : " ",
// QString::fromUtf8(state.text), MessageWindow_buttons(state.buttons))
// );
return MessageWindow_response(
state.buttons, QMessageBox::critical(state.parent ? state.parent->self()->qtWindow : nullptr, state.title ? QString::fromUtf8(state.title) : " ",
QString::fromUtf8(state.text), MessageWindow_buttons(state.buttons))
);
}
auto pMessageWindow::information(MessageWindow::State& state) -> MessageWindow::Response {
return {};
// return MessageWindow_response(
// state.buttons, QMessageBox::information(state.parent ? state.parent->p.qtWindow : nullptr, state.title ? state.title : " ",
// QString::fromUtf8(state.text), MessageWindow_buttons(state.buttons))
// );
return MessageWindow_response(
state.buttons, QMessageBox::information(state.parent ? state.parent->self()->qtWindow : nullptr, state.title ? QString::fromUtf8(state.title) : " ",
QString::fromUtf8(state.text), MessageWindow_buttons(state.buttons))
);
}
auto pMessageWindow::question(MessageWindow::State& state) -> MessageWindow::Response {
return {};
// return MessageWindow_response(
// state.buttons, QMessageBox::question(state.parent ? state.parent->p.qtWindow : nullptr, state.title ? state.title : " ",
// QString::fromUtf8(state.text), MessageWindow_buttons(state.buttons))
// );
return MessageWindow_response(
state.buttons, QMessageBox::question(state.parent ? state.parent->self()->qtWindow : nullptr, state.title ? QString::fromUtf8(state.title) : " ",
QString::fromUtf8(state.text), MessageWindow_buttons(state.buttons))
);
}
auto pMessageWindow::warning(MessageWindow::State& state) -> MessageWindow::Response {
return {};
// return MessageWindow_response(
// state.buttons, QMessageBox::warning(state.parent ? state.parent->p.qtWindow : nullptr, state.title ? state.title : " ",
// QString::fromUtf8(state.text), MessageWindow_buttons(state.buttons))
// );
return MessageWindow_response(
state.buttons, QMessageBox::warning(state.parent ? state.parent->self()->qtWindow : nullptr, state.title ? QString::fromUtf8(state.title) : " ",
QString::fromUtf8(state.text), MessageWindow_buttons(state.buttons))
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,13 @@ static auto Application_keyboardProc(HWND, UINT, WPARAM, LPARAM) -> bool;
static auto Application_processDialogMessage(MSG&) -> void;
static auto CALLBACK Window_windowProc(HWND, UINT, WPARAM, LPARAM) -> LRESULT;
auto pApplication::exit() -> void {
quit();
auto processID = GetCurrentProcessId();
auto handle = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, true, processID);
TerminateProcess(handle, 0);
}
auto pApplication::modal() -> bool {
return state().modalCount > 0;
}

View File

@@ -3,6 +3,7 @@
namespace hiro {
struct pApplication {
static auto exit() -> void;
static auto modal() -> bool;
static auto run() -> void;
static auto pendingEvents() -> bool;

View File

@@ -19,12 +19,9 @@ static auto BrowserWindow_fileDialog(bool save, BrowserWindow::State& state) ->
string filters;
for(auto& filter : state.filters) {
auto part = filter.split("(");
auto part = filter.split("|", 1L);
if(part.size() != 2) continue;
part[1].trimRight(")", 1L);
part[1].replace(" ", "");
part[1].transform(",", ";");
filters.append(filter, "\t", part[1], "\t");
filters.append(filter, "\t", part[1].transform(":", ";"), "\t");
}
utf16_t wfilters(filters);

View File

@@ -3,7 +3,9 @@
namespace hiro {
auto pFrame::construct() -> void {
hwnd = CreateWindow(L"BUTTON", L"",
hwnd = CreateWindowEx(
//WS_EX_TRANSPARENT fixes rendering issues caused by Windows using WS_CLIPCHILDREN
WS_EX_TRANSPARENT, L"BUTTON", L"",
WS_CHILD | BS_GROUPBOX,
0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0);
pWidget::construct();

View File

@@ -94,22 +94,6 @@ auto pLabel::windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> ma
return msg == WM_ERASEBKGND;
}
if(msg == WM_LBUTTONDOWN || msg == WM_MBUTTONDOWN || msg == WM_RBUTTONDOWN) {
switch(msg) {
case WM_LBUTTONDOWN: self().doMousePress(Mouse::Button::Left); break;
case WM_MBUTTONDOWN: self().doMousePress(Mouse::Button::Middle); break;
case WM_RBUTTONDOWN: self().doMousePress(Mouse::Button::Right); break;
}
}
if(msg == WM_LBUTTONUP || msg == WM_MBUTTONUP || msg == WM_RBUTTONUP) {
switch(msg) {
case WM_LBUTTONUP: self().doMouseRelease(Mouse::Button::Left); break;
case WM_MBUTTONUP: self().doMouseRelease(Mouse::Button::Middle); break;
case WM_RBUTTONUP: self().doMouseRelease(Mouse::Button::Right); break;
}
}
return pWidget::windowProc(hwnd, msg, wparam, lparam);
}

View File

@@ -74,6 +74,8 @@ auto pWidget::setGeometry(Geometry geometry) -> void {
geometry.setY(geometry.y() - displacement.y());
}
SetWindowPos(hwnd, nullptr, geometry.x(), geometry.y(), geometry.width(), geometry.height(), SWP_NOZORDER);
//RedrawWindow fixes painting problems when adjusting Layouts manually
RedrawWindow(hwnd, nullptr, nullptr, RDW_INVALIDATE | RDW_ALLCHILDREN);
pSizable::setGeometry(geometry);
}

View File

@@ -16,10 +16,9 @@ static auto CALLBACK Window_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARA
return Shared_windowProc(DefWindowProc, hwnd, msg, wparam, lparam);
}
//warning: do not add WS_CLIPCHILDREN; this will break painting of Frame ("BUTTON" BS_GROUPBOX) controls
static const uint PopupStyle = WS_POPUP;
static const uint FixedStyle = WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_BORDER;
static const uint ResizableStyle = WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME;
static const uint PopupStyle = WS_POPUP | WS_CLIPCHILDREN;
static const uint FixedStyle = WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_BORDER | WS_CLIPCHILDREN;
static const uint ResizableStyle = WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CLIPCHILDREN;
uint pWindow::minimumStatusHeight = 0;

View File

@@ -34,15 +34,17 @@ ifeq ($(platform),)
else
$(error unknown platform, please specify manually.)
endif
endif
# common commands
ifeq ($(uname),)
delete = $(info Deleting $1 ...) @del /q $(subst /,\,$1)
rdelete = $(info Deleting $1 ...) @del /s /q $(subst /,\,$1) && if exist $(subst /,\,$1) (rmdir /s /q $(subst /,\,$1))
else
delete = $(info Deleting $1 ...) @rm -f $1
rdelete = $(info Deleting $1 ...) @rm -rf $1
endif
# common commands
ifeq ($(shell echo ^^),^)
# cmd
delete = $(info Deleting $1 ...) @del /q $(subst /,\,$1)
rdelete = $(info Deleting $1 ...) @del /s /q $(subst /,\,$1) && if exist $(subst /,\,$1) (rmdir /s /q $(subst /,\,$1))
else
# sh
delete = $(info Deleting $1 ...) @rm -f $1
rdelete = $(info Deleting $1 ...) @rm -rf $1
endif
compiler.c = $(compiler) -x c -std=c11
@@ -106,7 +108,7 @@ ifeq ($(findstring clang++,$(compiler)),clang++)
flags += -fno-strict-aliasing -fwrapv -Wno-everything
# gcc settings
else ifeq ($(findstring g++,$(compiler)),g++)
flags += -fno-strict-aliasing -fwrapv
flags += -fno-strict-aliasing -fwrapv -Wno-trigraphs
endif
# windows settings

View File

@@ -49,6 +49,55 @@ inline auto timestamp() -> uint64_t {
return ::time(nullptr);
}
//0 = failure condition
inline auto timestamp(const string& datetime) -> uint64_t {
static const uint monthDays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
uint64_t timestamp = 0;
if(datetime.match("??????????")) {
return datetime.natural();
}
if(datetime.match("????*")) {
uint year = datetime.slice(0, 4).natural();
if(year < 1970 || year > 2199) return 0;
for(uint y = 1970; y < year && y < 2999; y++) {
uint daysInYear = 365;
if(y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) daysInYear++;
timestamp += daysInYear * 24 * 60 * 60;
}
}
if(datetime.match("????-??*")) {
uint y = datetime.slice(0, 4).natural();
uint month = datetime.slice(5, 2).natural();
if(month < 1 || month > 12) return 0;
for(uint m = 1; m < month && m < 12; m++) {
uint daysInMonth = monthDays[m - 1];
if(m == 2 && y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) daysInMonth++;
timestamp += daysInMonth * 24 * 60 * 60;
}
}
if(datetime.match("????-??-??*")) {
uint day = datetime.slice(8, 2).natural();
if(day < 1 || day > 31) return 0;
timestamp += (day - 1) * 24 * 60 * 60;
}
if(datetime.match("????-??-?? ??*")) {
uint hour = datetime.slice(11, 2).natural();
if(hour > 23) return 0;
timestamp += hour * 60 * 60;
}
if(datetime.match("????-??-?? ??:??*")) {
uint minute = datetime.slice(14, 2).natural();
if(minute > 59) return 0;
timestamp += minute * 60;
}
if(datetime.match("????-??-?? ??:??:??*")) {
uint second = datetime.slice(17, 2).natural();
if(second > 59) return 0;
timestamp += second;
}
return timestamp;
}
namespace utc {
inline auto timeinfo(uint64_t time = 0) -> chrono::timeinfo {
auto stamp = time ? (time_t)time : (time_t)timestamp();

View File

@@ -1,9 +1,7 @@
#pragma once
/* SQLite3 C++ RAII wrapper for nall
*
* Note on code below: it is safe (no-op) to call sqlite3_* functions on null sqlite3 objects
*/
//SQLite3 C++ RAII wrapper for nall
//note: it is safe (no-op) to call sqlite3_* functions on null sqlite3 objects
#include <sqlite3.h>
@@ -34,24 +32,28 @@ struct SQLite3 {
return sqlite3_data_count(statement());
}
auto columns() -> unsigned {
auto columns() -> uint {
return sqlite3_column_count(statement());
}
auto integer(unsigned column) -> int64_t {
auto boolean(uint column) -> bool {
return sqlite3_column_int64(statement(), column) != 0;
}
auto integer(uint column) -> int64_t {
return sqlite3_column_int64(statement(), column);
}
auto natural(unsigned column) -> uint64_t {
auto natural(uint column) -> uint64_t {
return sqlite3_column_int64(statement(), column);
}
auto real(unsigned column) -> double {
auto real(uint column) -> double {
return sqlite3_column_double(statement(), column);
}
auto text(unsigned column) -> string {
string result;
auto string(uint column) -> nall::string {
nall::string result;
if(auto text = sqlite3_column_text(statement(), column)) {
result.resize(sqlite3_column_bytes(statement(), column));
memory::copy(result.get(), text, result.size());
@@ -59,7 +61,7 @@ struct SQLite3 {
return result;
}
auto data(unsigned column) -> vector<uint8_t> {
auto data(uint column) -> vector<uint8_t> {
vector<uint8_t> result;
if(auto data = sqlite3_column_blob(statement(), column)) {
result.resize(sqlite3_column_bytes(statement(), column));
@@ -68,18 +70,19 @@ struct SQLite3 {
return result;
}
auto boolean() -> bool { return boolean(_output++); }
auto integer() -> int64_t { return integer(_output++); }
auto natural() -> uint64_t { return natural(_output++); }
auto real() -> double { return real(_output++); }
auto text() -> string { return text(_output++); }
auto string() -> nall::string { return string(_output++); }
auto data() -> vector<uint8_t> { return data(_output++); }
protected:
virtual auto statement() -> sqlite3_stmt* { return _statement; }
sqlite3_stmt* _statement = nullptr;
signed _response = SQLITE_OK;
unsigned _output = 0;
int _response = SQLITE_OK;
uint _output = 0;
};
struct Query : Statement {
@@ -102,22 +105,34 @@ struct SQLite3 {
return *this;
}
auto& bind(unsigned column, nullptr_t) { sqlite3_bind_null(_statement, 1 + column); return *this; }
auto& bind(unsigned column, int32_t value) { sqlite3_bind_int(_statement, 1 + column, value); return *this; }
auto& bind(unsigned column, uint32_t value) { sqlite3_bind_int(_statement, 1 + column, value); return *this; }
auto& bind(unsigned column, int64_t value) { sqlite3_bind_int64(_statement, 1 + column, value); return *this; }
auto& bind(unsigned column, uint64_t value) { sqlite3_bind_int64(_statement, 1 + column, value); return *this; }
auto& bind(unsigned column, double value) { sqlite3_bind_double(_statement, 1 + column, value); return *this; }
auto& bind(unsigned column, const string& value) { sqlite3_bind_text(_statement, 1 + column, value.data(), value.size(), SQLITE_TRANSIENT); return *this; }
auto& bind(unsigned column, const vector<uint8_t>& value) { sqlite3_bind_blob(_statement, 1 + column, value.data(), value.size(), SQLITE_TRANSIENT); return *this; }
auto& bind(uint column, nullptr_t) { sqlite3_bind_null(_statement, 1 + column); return *this; }
auto& bind(uint column, bool value) { sqlite3_bind_int(_statement, 1 + column, value); return *this; }
auto& bind(uint column, int32_t value) { sqlite3_bind_int(_statement, 1 + column, value); return *this; }
auto& bind(uint column, uint32_t value) { sqlite3_bind_int(_statement, 1 + column, value); return *this; }
auto& bind(uint column, int64_t value) { sqlite3_bind_int64(_statement, 1 + column, value); return *this; }
auto& bind(uint column, uint64_t value) { sqlite3_bind_int64(_statement, 1 + column, value); return *this; }
auto& bind(uint column, intmax value) { sqlite3_bind_int64(_statement, 1 + column, value); return *this; }
auto& bind(uint column, uintmax value) { sqlite3_bind_int64(_statement, 1 + column, value); return *this; }
auto& bind(uint column, nall::boolean value) { sqlite3_bind_int64(_statement, 1 + column, value); return *this; }
auto& bind(uint column, nall::integer value) { sqlite3_bind_int64(_statement, 1 + column, value); return *this; }
auto& bind(uint column, nall::natural value) { sqlite3_bind_int64(_statement, 1 + column, value); return *this; }
auto& bind(uint column, double value) { sqlite3_bind_double(_statement, 1 + column, value); return *this; }
auto& bind(uint column, const nall::string& value) { sqlite3_bind_text(_statement, 1 + column, value.data(), value.size(), SQLITE_TRANSIENT); return *this; }
auto& bind(uint column, const vector<uint8_t>& value) { sqlite3_bind_blob(_statement, 1 + column, value.data(), value.size(), SQLITE_TRANSIENT); return *this; }
auto& bind(nullptr_t) { return bind(_input++, nullptr); }
auto& bind(bool value) { return bind(_input++, value); }
auto& bind(int32_t value) { return bind(_input++, value); }
auto& bind(uint32_t value) { return bind(_input++, value); }
auto& bind(int64_t value) { return bind(_input++, value); }
auto& bind(uint64_t value) { return bind(_input++, value); }
auto& bind(intmax value) { return bind(_input++, value); }
auto& bind(uintmax value) { return bind(_input++, value); }
auto& bind(nall::boolean value) { return bind(_input++, value); }
auto& bind(nall::integer value) { return bind(_input++, value); }
auto& bind(nall::natural value) { return bind(_input++, value); }
auto& bind(double value) { return bind(_input++, value); }
auto& bind(const string& value) { return bind(_input++, value); }
auto& bind(const nall::string& value) { return bind(_input++, value); }
auto& bind(const vector<uint8_t>& value) { return bind(_input++, value); }
auto step() -> bool {
@@ -145,7 +160,7 @@ struct SQLite3 {
return _statement;
}
unsigned _input = 0;
uint _input = 0;
bool _stepped = false;
};

View File

@@ -7,6 +7,9 @@
namespace nall::DSP::Resampler {
struct Cubic {
inline auto inputFrequency() const -> double { return _inputFrequency; }
inline auto outputFrequency() const -> double { return _outputFrequency; }
inline auto reset(double inputFrequency, double outputFrequency = 0, uint queueSize = 0) -> void;
inline auto setInputFrequency(double inputFrequency) -> void;
inline auto pending() const -> bool;
@@ -15,41 +18,41 @@ struct Cubic {
inline auto serialize(serializer&) -> void;
private:
double inputFrequency;
double outputFrequency;
double _inputFrequency;
double _outputFrequency;
double ratio;
double fraction;
double history[4];
queue<double> samples;
double _ratio;
double _fraction;
double _history[4];
queue<double> _samples;
};
auto Cubic::reset(double inputFrequency, double outputFrequency, uint queueSize) -> void {
this->inputFrequency = inputFrequency;
this->outputFrequency = outputFrequency ? outputFrequency : this->inputFrequency;
_inputFrequency = inputFrequency;
_outputFrequency = outputFrequency ? outputFrequency : _inputFrequency;
ratio = inputFrequency / outputFrequency;
fraction = 0.0;
for(auto& sample : history) sample = 0.0;
samples.resize(queueSize ? queueSize : this->outputFrequency * 0.02); //default to 20ms max queue size
_ratio = _inputFrequency / _outputFrequency;
_fraction = 0.0;
for(auto& sample : _history) sample = 0.0;
_samples.resize(queueSize ? queueSize : _outputFrequency * 0.02); //default to 20ms max queue size
}
auto Cubic::setInputFrequency(double inputFrequency) -> void {
this->inputFrequency = inputFrequency;
ratio = inputFrequency / outputFrequency;
_inputFrequency = inputFrequency;
_ratio = _inputFrequency / _outputFrequency;
}
auto Cubic::pending() const -> bool {
return samples.pending();
return _samples.pending();
}
auto Cubic::read() -> double {
return samples.read();
return _samples.read();
}
auto Cubic::write(double sample) -> void {
auto& mu = fraction;
auto& s = history;
auto& mu = _fraction;
auto& s = _history;
s[0] = s[1];
s[1] = s[2];
@@ -62,20 +65,20 @@ auto Cubic::write(double sample) -> void {
double C = s[2] - s[0];
double D = s[1];
samples.write(A * mu * mu * mu + B * mu * mu + C * mu + D);
mu += ratio;
_samples.write(A * mu * mu * mu + B * mu * mu + C * mu + D);
mu += _ratio;
}
mu -= 1.0;
}
auto Cubic::serialize(serializer& s) -> void {
s.real(inputFrequency);
s.real(outputFrequency);
s.real(ratio);
s.real(fraction);
s.array(history);
samples.serialize(s);
s.real(_inputFrequency);
s.real(_outputFrequency);
s.real(_ratio);
s.real(_fraction);
s.array(_history);
_samples.serialize(s);
}
}

52
nall/encode/wav.hpp Normal file
View File

@@ -0,0 +1,52 @@
#pragma once
namespace nall::Encode {
struct WAV {
static auto stereo_16bit(const string& filename, array_view<int16_t> left, array_view<int16_t> right, uint frequency) -> bool {
if(left.size() != right.size()) return false;
static uint channels = 2;
static uint bits = 16;
static uint samples = left.size();
file_buffer fp;
if(!fp.open(filename, file::mode::write)) return false;
fp.write('R');
fp.write('I');
fp.write('F');
fp.write('F');
fp.writel(4 + (8 + 16) + (8 + samples * 4), 4);
fp.write('W');
fp.write('A');
fp.write('V');
fp.write('E');
fp.write('f');
fp.write('m');
fp.write('t');
fp.write(' ');
fp.writel(16, 4);
fp.writel(1, 2);
fp.writel(channels, 2);
fp.writel(frequency, 4);
fp.writel(frequency * channels * bits, 4);
fp.writel(channels * bits, 2);
fp.writel(bits, 2);
fp.write('d');
fp.write('a');
fp.write('t');
fp.write('a');
fp.writel(samples * 4, 4);
for(uint sample : range(samples)) {
fp.writel(left[sample], 2);
fp.writel(right[sample], 2);
}
return true;
}
};
}

View File

@@ -38,6 +38,7 @@ namespace nall {
#pragma clang diagnostic ignored "-Wtautological-compare"
#pragma clang diagnostic ignored "-Wabsolute-value"
#pragma clang diagnostic ignored "-Wshift-count-overflow"
#pragma clang diagnostic ignored "-Wtrigraphs"
//temporary
#pragma clang diagnostic ignored "-Winconsistent-missing-override"
@@ -51,6 +52,7 @@ namespace nall {
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
#pragma GCC diagnostic ignored "-Wpragmas"
#pragma GCC diagnostic ignored "-Wswitch-bool"
#pragma GCC diagnostic ignored "-Wtrigraphs"
#elif defined(_MSC_VER)
#define COMPILER_MICROSOFT
constexpr auto compiler() -> Compiler { return Compiler::Microsoft; }

View File

@@ -291,7 +291,7 @@ public:
inline auto remove(uint offset, uint length) -> type&;
inline auto reverse() -> type&;
inline auto size(int length, char fill = ' ') -> type&;
inline auto slice(int offset = 0, int length = -1) -> string;
inline auto slice(int offset = 0, int length = -1) const -> string;
};
template<> struct vector<string> : vector_base<string> {

View File

@@ -9,16 +9,12 @@
namespace nall {
struct DML {
inline auto title() const -> string { return state.title; }
inline auto subtitle() const -> string { return state.subtitle; }
inline auto description() const -> string { return state.description; }
inline auto content() const -> string { return state.output; }
auto content() const -> string { return state.output; }
auto& setAllowHTML(bool allowHTML) { settings.allowHTML = allowHTML; return *this; }
auto& setHost(const string& hostname) { settings.host = {hostname, "/"}; return *this; }
auto& setHost(const string& hostname) { settings.host = hostname; return *this; }
auto& setPath(const string& pathname) { settings.path = pathname; return *this; }
auto& setReader(const function<string (string)>& reader) { settings.reader = reader; return *this; }
auto& setSectioned(bool sectioned) { settings.sectioned = sectioned; return *this; }
auto parse(const string& filedata, const string& pathname) -> string;
auto parse(const string& filename) -> string;
@@ -28,18 +24,13 @@ struct DML {
private:
struct Settings {
bool allowHTML = true;
string host = "localhost/";
string host = "localhost";
string path;
function<string (string)> reader;
bool sectioned = true;
} settings;
struct State {
string title;
string subtitle;
string description;
string output;
uint sections = 0;
} state;
struct Attribute {
@@ -52,6 +43,7 @@ private:
auto parseBlock(string& block, const string& pathname, uint depth) -> bool;
auto count(const string& text, char value) -> uint;
auto address(string text) -> string;
auto escape(const string& text) -> string;
auto markup(const string& text) -> string;
};
@@ -83,7 +75,6 @@ inline auto DML::parseDocument(const string& filedata, const string& pathname, u
auto blocks = filedata.split("\n\n");
for(auto& block : blocks) parseBlock(block, pathname, depth);
if(settings.sectioned && state.sections && depth == 0) state.output.append("</section>\n");
return true;
}
@@ -98,6 +89,18 @@ inline auto DML::parseBlock(string& block, const string& pathname, uint depth) -
parseDocument(document, Location::path(filename), depth + 1);
}
//attribute
else if(block.beginsWith("? ")) {
for(auto n : range(lines.size())) {
if(!lines[n].beginsWith("? ")) continue;
auto part = lines[n].trimLeft("? ", 1L).split(":", 1L);
if(part.size() != 2) continue;
auto name = part[0].strip();
auto value = part[1].strip();
attributes.append({name, value});
}
}
//html
else if(block.beginsWith("<html>\n") && settings.allowHTML) {
for(auto n : range(lines.size())) {
@@ -106,52 +109,18 @@ inline auto DML::parseBlock(string& block, const string& pathname, uint depth) -
}
}
//attribute
else if(block.beginsWith("! ")) {
for(auto& line : lines) {
auto parts = line.trimLeft("! ", 1L).split(":", 1L);
if(parts.size() == 2) attributes.append({parts[0].strip(), parts[1].strip()});
}
}
//description
else if(block.beginsWith("? ")) {
while(lines) {
state.description.append(lines.takeLeft().trimLeft("? ", 1L), " ");
}
state.description.strip();
}
//section
else if(block.beginsWith("# ")) {
if(settings.sectioned) {
if(state.sections++) state.output.append("</section>");
state.output.append("<section>");
}
auto content = lines.takeLeft().trimLeft("# ", 1L).split("::", 1L).strip();
auto data = markup(content[0]);
auto name = escape(content(1, data.hash()));
state.subtitle = content[0];
state.output.append("<h2 id=\"", name, "\">", data);
for(auto& line : lines) {
if(!line.beginsWith("# ")) continue;
state.output.append("<span>", line.trimLeft("# ", 1L), "</span>");
}
state.output.append("</h2>\n");
}
//header
else if(auto depth = count(block, '=')) {
else if(auto depth = count(block, '#')) {
auto content = slice(lines.takeLeft(), depth + 1).split("::", 1L).strip();
auto data = markup(content[0]);
auto name = escape(content(1, data.hash()));
if(depth <= 4) {
state.output.append("<h", depth + 2, " id=\"", name, "\">", data);
if(depth <= 5) {
state.output.append("<h", depth + 1, " id=\"", name, "\">", data);
for(auto& line : lines) {
if(count(line, '=') != depth) continue;
if(count(line, '#') != depth) continue;
state.output.append("<span>", slice(line, depth + 1), "</span>");
}
state.output.append("</h", depth + 2, ">\n");
state.output.append("</h", depth + 1, ">\n");
}
}
@@ -239,6 +208,29 @@ inline auto DML::count(const string& text, char value) -> uint {
return 0;
}
// . => domain
// ./* => domain/*
// ../subdomain => subdomain.domain
// ../subdomain/* => subdomain.domain/*
inline auto DML::address(string s) -> string {
if(s.beginsWith("../")) {
s.trimLeft("../", 1L);
if(auto p = s.find("/")) {
return {"//", s.slice(0, *p), ".", settings.host, s.slice(*p)};
} else {
return {"//", s, ".", settings.host};
}
}
if(s.beginsWith("./")) {
s.trimLeft(".", 1L);
return {"//", settings.host, s};
}
if(s == ".") {
return {"//", settings.host};
}
return s;
}
inline auto DML::escape(const string& text) -> string {
string output;
for(auto c : text) {
@@ -281,8 +273,8 @@ inline auto DML::markup(const string& s) -> string {
if(link && !image && a == ']' && b == ']') {
auto list = slice(s, link(), n - link()).split("::", 1L);
string uri = list.last();
string name = list.size() == 2 ? list.first() : list.last().split("//", 1L).last();
string uri = address(list.last());
string name = list.size() == 2 ? list.first() : uri.split("//", 1L).last();
t.append("<a href=\"", escape(uri), "\">", escape(name), "</a>");
@@ -294,8 +286,8 @@ inline auto DML::markup(const string& s) -> string {
if(image && !link && a == '}' && b == '}') {
auto side = slice(s, image(), n - image()).split("}{", 1L);
auto list = side(0).split("::", 1L);
string uri = list.last();
string name = list.size() == 2 ? list.first() : list.last().split("//", 1L).last();
string uri = address(list.last());
string name = list.size() == 2 ? list.first() : uri.split("//", 1L).last();
list = side(1).split("; ");
boolean link, title, caption;
string width, height;
@@ -322,7 +314,6 @@ inline auto DML::markup(const string& s) -> string {
if(link) t.append("<a href=\"", escape(uri), "\">");
t.append("<img loading=\"lazy\" src=\"", escape(uri), "\" alt=\"", escape(name ? name : uri.hash()), "\"");
if(title) t.append(" title=\"", escape(name), "\"");
if(width && height) t.append(" style=\"width: ", escape(width), "px; max-height: ", escape(height), "px;\"");
if(width) t.append(" width=\"", escape(width), "\"");
if(height) t.append(" height=\"", escape(height), "\"");
t.append(">");

View File

@@ -95,7 +95,7 @@ auto slice(string_view self, int offset, int length) -> string {
return result;
}
auto string::slice(int offset, int length) -> string {
auto string::slice(int offset, int length) const -> string {
return nall::slice(*this, offset, length);
}

View File

@@ -1,7 +1,7 @@
ifeq ($(ruby),)
ifeq ($(platform),windows)
ruby += video.wgl video.direct3d video.directdraw video.gdi
ruby += audio.wasapi audio.xaudio2 audio.directsound audio.waveout #audio.asio
ruby += audio.asio audio.wasapi audio.xaudio2 audio.directsound audio.waveout
ruby += input.windows
else ifeq ($(platform),macos)
ruby += video.cgl
@@ -13,7 +13,7 @@ ifeq ($(ruby),)
ruby += input.sdl input.xlib input.udev
else ifeq ($(platform),bsd)
ruby += video.glx video.glx2 video.xvideo video.xshm
ruby += audio.oss #audio.pulseaudio
ruby += audio.oss audio.openal #audio.pulseaudio
ruby += input.sdl input.xlib
endif
endif
@@ -65,7 +65,8 @@ endif
ifeq ($(platform),bsd)
ruby.options += -lX11 -lXext -lXrandr
ruby.options += $(if $(findstring audio.openal,$(ruby)),-lopenal)
ruby.options += $(if $(findstring audio.openal,$(ruby)),-lopenal -fuse-ld=bfd)
# -fuse-ld=bfd: see FreeBSD bug 219089
endif
ruby.objects := $(object.path)/ruby.o

View File

@@ -17,6 +17,7 @@ struct AudioALSA : AudioDriver {
auto ready() -> bool override { return _ready; }
auto hasBlocking() -> bool override { return true; }
auto hasDynamic() -> bool override { return true; }
auto hasDevices() -> vector<string> override {
vector<string> devices;
@@ -55,20 +56,27 @@ struct AudioALSA : AudioDriver {
auto setLatency(uint latency) -> bool override { return initialize(); }
auto level() -> double override {
snd_pcm_sframes_t available = snd_pcm_avail_update(_interface);
if(available < 0) return 0.5;
snd_pcm_sframes_t available;
for(uint timeout : range(256)) {
available = snd_pcm_avail_update(_interface);
if(available >= 0) break;
snd_pcm_recover(_interface, available, 1);
}
return (double)(_bufferSize - available) / _bufferSize;
}
auto output(const double samples[]) -> void override {
_buffer[_offset] = (uint16_t)sclamp<16>(samples[0] * 32767.0) << 0;
_buffer[_offset] |= (uint16_t)sclamp<16>(samples[1] * 32767.0) << 16;
_offset++;
if(++_offset < _periodSize) return;
snd_pcm_sframes_t available;
do {
available = snd_pcm_avail_update(_interface);
if(available < 0) snd_pcm_recover(_interface, available, 1);
if(available < 0) {
snd_pcm_recover(_interface, available, 1);
continue;
}
if(available < _offset) {
if(!self.blocking) {
_offset = 0;

View File

@@ -52,6 +52,11 @@ struct AudioASIO : AudioDriver {
}
latencies.append(latency);
}
//it is possible that no latencies in the hard-coded list above will match; so ensure driver-declared latencies are available
if(!latencies.find(self.activeDevice.minimumBufferSize)) latencies.append(self.activeDevice.minimumBufferSize);
if(!latencies.find(self.activeDevice.maximumBufferSize)) latencies.append(self.activeDevice.maximumBufferSize);
if(!latencies.find(self.activeDevice.preferredBufferSize)) latencies.append(self.activeDevice.preferredBufferSize);
latencies.sort();
return latencies;
}
@@ -75,6 +80,15 @@ struct AudioASIO : AudioDriver {
auto output(const double samples[]) -> void override {
if(!ready()) return;
//defer call to IASIO::start(), because the drivers themselves will sometimes crash internally.
//if software initializes AudioASIO but does not play music at startup, this can prevent a crash loop.
if(!_started) {
_started = true;
if(_asio->start() != ASE_OK) {
_ready = false;
return;
}
}
if(self.blocking) {
while(_queue.count >= self.latency);
}
@@ -151,13 +165,14 @@ private:
}
_ready = true;
_started = false;
clear();
if(_asio->start() != ASE_OK) return _ready = false;
return true;
}
auto terminate() -> void {
_ready = false;
_started = false;
self.activeDevice = {};
if(_asio) {
_asio->stop();
@@ -244,6 +259,7 @@ private:
}
bool _ready = false;
bool _started = false;
struct Queue {
double samples[65536][8];

View File

@@ -242,10 +242,10 @@ auto Audio::hasDrivers() -> vector<string> {
}
auto Audio::optimalDriver() -> string {
#if defined(AUDIO_ASIO)
return "ASIO";
#elif defined(AUDIO_WASAPI)
#if defined(AUDIO_WASAPI)
return "WASAPI";
#elif defined(AUDIO_ASIO)
return "ASIO";
#elif defined(AUDIO_XAUDIO2)
return "XAudio 2.1";
#elif defined(AUDIO_DIRECTSOUND)

View File

@@ -15,6 +15,7 @@ struct AudioPulseAudio : AudioDriver {
auto ready() -> bool override { return _ready; }
auto hasBlocking() -> bool override { return true; }
auto hasDynamic() -> bool override { return true; }
auto hasFrequencies() -> vector<uint> override {
return {44100, 48000, 96000};
@@ -28,6 +29,12 @@ struct AudioPulseAudio : AudioDriver {
auto setFrequency(uint frequency) -> bool override { return initialize(); }
auto setLatency(uint latency) -> bool override { return initialize(); }
auto level() -> double override {
pa_mainloop_iterate(_mainLoop, 0, nullptr);
auto length = pa_stream_writable_size(_stream);
return (double)(_bufferSize - length) / _bufferSize;
}
auto output(const double samples[]) -> void override {
pa_stream_begin_write(_stream, (void**)&_buffer, &_period);
_buffer[_offset] = (uint16_t)sclamp<16>(samples[0] * 32767.0) << 0;
@@ -35,13 +42,8 @@ struct AudioPulseAudio : AudioDriver {
if((++_offset + 1) * pa_frame_size(&_specification) <= _period) return;
while(true) {
if(_first) {
_first = false;
pa_mainloop_iterate(_mainLoop, 0, nullptr);
} else {
pa_mainloop_iterate(_mainLoop, 1, nullptr);
}
uint length = pa_stream_writable_size(_stream);
pa_mainloop_iterate(_mainLoop, 0, nullptr);
auto length = pa_stream_writable_size(_stream);
if(length >= _offset * pa_frame_size(&_specification)) break;
if(!self.blocking) {
_offset = 0;
@@ -91,9 +93,10 @@ private:
if(!PA_STREAM_IS_GOOD(streamState)) return false;
} while(streamState != PA_STREAM_READY);
_period = 960;
const pa_buffer_attr* attributes = pa_stream_get_buffer_attr(_stream);
_period = attributes->minreq;
_bufferSize = attributes->tlength;
_offset = 0;
_first = true;
return _ready = true;
}
@@ -127,11 +130,11 @@ private:
uint32_t* _buffer = nullptr;
size_t _period = 0;
size_t _bufferSize = 0;
uint _offset = 0;
pa_mainloop* _mainLoop = nullptr;
pa_context* _context = nullptr;
pa_stream* _stream = nullptr;
pa_sample_spec _specification;
bool _first = true;
};

View File

@@ -66,6 +66,10 @@ struct VideoCGL : VideoDriver, OpenGL {
return true;
}
auto focused() -> bool override {
return true;
}
auto clear() -> void override {
@autoreleasepool {
[view lockFocus];

View File

@@ -34,6 +34,12 @@ struct VideoDirect3D : VideoDriver {
auto setBlocking(bool blocking) -> bool override { return true; }
auto setShader(string shader) -> bool override { return updateFilter(); }
auto focused() -> bool override {
if(self.fullScreen && self.exclusive) return true;
auto focused = GetFocus();
return _context == focused || IsChild(_context, focused);
}
auto clear() -> void override {
if(_lost && !recover()) return;

View File

@@ -40,6 +40,12 @@ struct VideoDirectDraw : VideoDriver {
return true;
}
auto focused() -> bool override {
if(self.fullScreen && self.exclusive) return true;
auto focused = GetFocus();
return _context == focused || IsChild(_context, focused);
}
auto clear() -> void override {
DDBLTFX fx{};
fx.dwSize = sizeof(DDBLTFX);

View File

@@ -24,6 +24,12 @@ struct VideoGDI : VideoDriver {
auto setMonitor(string monitor) -> bool override { return initialize(); }
auto setContext(uintptr context) -> bool override { return initialize(); }
auto focused() -> bool override {
if(self.fullScreen && self.exclusive) return true;
auto focused = GetFocus();
return _context == focused || IsChild(_context, focused);
}
auto size(uint& width, uint& height) -> void override {
RECT rectangle;
GetClientRect(_context, &rectangle);

View File

@@ -74,6 +74,10 @@ struct VideoGLX : VideoDriver, OpenGL {
return true;
}
auto focused() -> bool override {
return true;
}
auto clear() -> void override {
OpenGL::clear();
if(_doubleBuffer) glXSwapBuffers(_display, _glXWindow);

View File

@@ -83,6 +83,10 @@ struct VideoGLX2 : VideoDriver {
return true;
}
auto focused() -> bool override {
return true;
}
auto clear() -> void override {
memory::fill<uint32_t>(_glBuffer, _glWidth * _glHeight);
glClearColor(0.0, 0.0, 0.0, 1.0);

View File

@@ -94,6 +94,10 @@ auto Video::setShader(string shader) -> bool {
//
auto Video::focused() -> bool {
return instance->focused();
}
auto Video::clear() -> void {
return instance->clear();
}

View File

@@ -28,6 +28,7 @@ struct VideoDriver {
virtual auto setFormat(string format) -> bool { return true; }
virtual auto setShader(string shader) -> bool { return true; }
virtual auto focused() -> bool { return true; }
virtual auto clear() -> void {}
virtual auto size(uint& width, uint& height) -> void {}
virtual auto acquire(uint32_t*& data, uint& pitch, uint width, uint height) -> bool { return false; }
@@ -108,6 +109,7 @@ struct Video {
auto setFormat(string format) -> bool;
auto setShader(string shader) -> bool;
auto focused() -> bool;
auto clear() -> void;
struct Size {
uint width = 0;

View File

@@ -53,6 +53,12 @@ struct VideoWGL : VideoDriver, OpenGL {
return true;
}
auto focused() -> bool override {
if(self.fullScreen && self.exclusive) return true;
auto focused = GetFocus();
return _context == focused || IsChild(_context, focused);
}
auto clear() -> void override {
OpenGL::clear();
SwapBuffers(_display);

View File

@@ -31,6 +31,10 @@ struct VideoXShm : VideoDriver {
auto setContext(uintptr context) -> bool override { return initialize(); }
auto setShader(string shader) -> bool override { return true; }
auto focused() -> bool override {
return true;
}
auto clear() -> void override {
auto dp = _inputBuffer;
uint length = _inputWidth * _inputHeight;

View File

@@ -57,6 +57,10 @@ struct VideoXVideo : VideoDriver {
return initialize();
}
auto focused() -> bool override {
return true;
}
auto clear() -> void override {
memory::fill<uint32_t>(_buffer, _bufferWidth * _bufferHeight);
//clear twice in case video is double buffered ...