mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-01-17 20:58:28 +01:00
Update to v098r08 release.
byuu says: Changelog: - nall/vector rewritten from scratch - higan/audio uses nall/vector instead of raw pointers - higan/sfc/coprocessor/sdd1 updated with new research information - ruby/video/glx and ruby/video/glx2: fuck salt glXSwapIntervalEXT! The big change here is definitely nall/vector. The Windows, OS X and Qt ports won't compile until you change some first/last strings to left/right, but GTK will compile. I'd be really grateful if anyone could stress-test nall/vector. Pretty much everything I do relies on this class. If we introduce a bug, the worst case scenario is my entire SFC game dump database gets corrupted, or the byuu.org server gets compromised. So it's really critical that we test the hell out of this right now. The S-DD1 changes mean you need to update your installation of icarus again. Also, even though the Lunar FMV never really worked on the accuracy core anyway (it didn't initialize the PPU properly), it really won't work now that we emulate the hard-limit of 16MiB for S-DD1 games.
This commit is contained in:
parent
7cdae5195a
commit
0955295475
@ -76,8 +76,8 @@ auto Audio::poll() -> void {
|
||||
if(reverbDelay) {
|
||||
reverbLeft.append(ileft);
|
||||
reverbRight.append(iright);
|
||||
ileft += reverbLeft.takeFirst() * reverbLevel;
|
||||
iright += reverbRight.takeFirst() * reverbLevel;
|
||||
ileft += reverbLeft.takeLeft() * reverbLevel;
|
||||
iright += reverbRight.takeLeft() * reverbLevel;
|
||||
}
|
||||
|
||||
interface->audioSample(sclamp<16>(ileft), sclamp<16>(iright));
|
||||
|
@ -36,7 +36,6 @@ private:
|
||||
|
||||
struct Stream {
|
||||
Stream(uint channels, double inputFrequency);
|
||||
~Stream();
|
||||
|
||||
auto reset() -> void;
|
||||
auto setFrequency(double outputFrequency) -> void;
|
||||
@ -56,21 +55,20 @@ private:
|
||||
double outputFrequency = 0.0;
|
||||
double cutoffFrequency = 0.0;
|
||||
|
||||
double* tap = nullptr;
|
||||
uint taps = 0;
|
||||
vector<double> taps;
|
||||
|
||||
uint decimationRate = 0;
|
||||
uint decimationOffset = 0;
|
||||
|
||||
double** input = nullptr;
|
||||
vector<vector<double>> input;
|
||||
uint inputOffset = 0;
|
||||
|
||||
double resamplerFrequency = 0.0;
|
||||
double resamplerFraction = 0.0;
|
||||
double resamplerStep = 0.0;
|
||||
double** queue = nullptr;
|
||||
vector<vector<double>> queue;
|
||||
|
||||
double** output = nullptr;
|
||||
vector<vector<double>> output;
|
||||
uint outputs = 0;
|
||||
uint outputReadOffset = 0;
|
||||
uint outputWriteOffset = 0;
|
||||
|
@ -7,18 +7,11 @@
|
||||
Stream::Stream(uint channels, double inputFrequency) : channels(channels), inputFrequency(inputFrequency) {
|
||||
}
|
||||
|
||||
Stream::~Stream() {
|
||||
reset();
|
||||
}
|
||||
|
||||
auto Stream::reset() -> void {
|
||||
if(tap) delete[] tap, tap = nullptr;
|
||||
if(input) for(auto c : range(channels)) delete[] input[c];
|
||||
delete[] input, input = nullptr;
|
||||
if(queue) for(auto c : range(channels)) delete[] queue[c];
|
||||
delete[] queue, queue = nullptr;
|
||||
if(output) for(auto c : range(channels)) delete[] output[c];
|
||||
delete[] output, output = nullptr;
|
||||
taps.reset();
|
||||
input.reset();
|
||||
queue.reset();
|
||||
output.reset();
|
||||
}
|
||||
|
||||
auto Stream::setFrequency(double outputFrequency_) -> void {
|
||||
@ -34,45 +27,43 @@ auto Stream::setFrequency(double outputFrequency_) -> void {
|
||||
cutoffFrequency = outputFrequency / inputFrequency;
|
||||
if(cutoffFrequency < 0.5) {
|
||||
double transitionBandwidth = 0.008; //lower = higher quality; more taps (slower)
|
||||
taps = (uint)ceil(4.0 / transitionBandwidth) | 1;
|
||||
tap = new double[taps];
|
||||
taps.resize((uint)ceil(4.0 / transitionBandwidth) | 1);
|
||||
|
||||
double sum = 0.0;
|
||||
for(uint t : range(taps)) {
|
||||
//sinc filter
|
||||
double s = sinc(2.0 * cutoffFrequency * (t - (taps - 1) / 2.0));
|
||||
double s = sinc(2.0 * cutoffFrequency * (t - (taps.size() - 1) / 2.0));
|
||||
|
||||
//blackman window
|
||||
double b = 0.42 - 0.5 * cos(2.0 * pi * t / (taps - 1)) + 0.08 * cos(4.0 * pi * t / (taps - 1));
|
||||
double b = 0.42 - 0.5 * cos(2.0 * pi * t / (taps.size() - 1)) + 0.08 * cos(4.0 * pi * t / (taps.size() - 1));
|
||||
|
||||
tap[t] = s * b;
|
||||
sum += tap[t];
|
||||
taps[t] = s * b;
|
||||
sum += taps[t];
|
||||
}
|
||||
|
||||
//normalize so that the sum of all coefficients is 1.0
|
||||
for(auto t : range(taps)) tap[t] /= sum;
|
||||
for(auto& tap : taps) tap /= sum;
|
||||
} else {
|
||||
taps = 1;
|
||||
tap = new double[taps];
|
||||
tap[0] = 1.0;
|
||||
taps.resize(1);
|
||||
taps[0] = 1.0;
|
||||
}
|
||||
|
||||
decimationRate = max(1, (uint)floor(inputFrequency / outputFrequency));
|
||||
decimationOffset = 0;
|
||||
|
||||
input = new double*[channels];
|
||||
for(auto c : range(channels)) input[c] = new double[taps * 2]();
|
||||
input.resize(channels);
|
||||
for(auto c : range(channels)) input[c].resize(taps.size() * 2);
|
||||
inputOffset = 0;
|
||||
|
||||
resamplerFrequency = inputFrequency / decimationRate;
|
||||
resamplerFraction = 0.0;
|
||||
resamplerStep = resamplerFrequency / outputFrequency;
|
||||
queue = new double*[channels];
|
||||
for(auto c : range(channels)) queue[c] = new double[4]();
|
||||
queue.resize(channels);
|
||||
for(auto c : range(channels)) queue[c].resize(4);
|
||||
|
||||
output = new double*[channels];
|
||||
output.resize(channels);
|
||||
outputs = inputFrequency * 0.02;
|
||||
for(auto c : range(channels)) output[c] = new double[outputs]();
|
||||
for(auto c : range(channels)) output[c].resize(outputs);
|
||||
outputReadOffset = 0;
|
||||
outputWriteOffset = 0;
|
||||
}
|
||||
@ -90,10 +81,10 @@ auto Stream::read(double* samples) -> void {
|
||||
}
|
||||
|
||||
auto Stream::write(int16* samples) -> void {
|
||||
inputOffset = !inputOffset ? taps - 1 : inputOffset - 1;
|
||||
inputOffset = !inputOffset ? taps.size() - 1 : inputOffset - 1;
|
||||
for(auto c : range(channels)) {
|
||||
auto sample = (samples[c] + 32768.0) / 65535.0; //normalize
|
||||
input[c][inputOffset] = input[c][inputOffset + taps] = sample;
|
||||
input[c][inputOffset] = input[c][inputOffset + taps.size()] = sample;
|
||||
}
|
||||
|
||||
if(++decimationOffset >= decimationRate) {
|
||||
@ -101,7 +92,7 @@ auto Stream::write(int16* samples) -> void {
|
||||
|
||||
for(auto c : range(channels)) {
|
||||
double sample = 0.0;
|
||||
for(auto t : range(taps)) sample += input[c][inputOffset + t] * tap[t];
|
||||
for(auto t : range(taps)) sample += input[c][inputOffset + t] * taps[t];
|
||||
|
||||
auto& q = queue[c];
|
||||
q[0] = q[1];
|
||||
|
@ -8,7 +8,7 @@ using namespace nall;
|
||||
|
||||
namespace Emulator {
|
||||
static const string Name = "higan";
|
||||
static const string Version = "098.07";
|
||||
static const string Version = "098.08";
|
||||
static const string Author = "byuu";
|
||||
static const string License = "GPLv3";
|
||||
static const string Website = "http://byuu.org/";
|
||||
|
@ -376,7 +376,7 @@ auto V30MZ::disassemble(uint16 cs, uint16 ip, bool registers, bool bytes) -> str
|
||||
if(bytes) {
|
||||
b = " ";
|
||||
while(bytesRead) {
|
||||
b.append(hex(bytesRead.takeFirst(), 2L), " ");
|
||||
b.append(hex(bytesRead.takeLeft(), 2L), " ");
|
||||
}
|
||||
b.rstrip();
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
//36 ss:
|
||||
//3e ds:
|
||||
auto V30MZ::opSegment(uint16 segment) {
|
||||
if(prefixes.size() >= 7) prefixes.removeLast();
|
||||
if(prefixes.size() >= 7) prefixes.removeRight();
|
||||
prefixes.prepend(opcode);
|
||||
state.prefix = true;
|
||||
state.poll = false;
|
||||
@ -12,7 +12,7 @@ auto V30MZ::opSegment(uint16 segment) {
|
||||
//f2 repnz:
|
||||
//f3 repz:
|
||||
auto V30MZ::opRepeat(bool flag) {
|
||||
if(prefixes.size() >= 7) prefixes.removeLast();
|
||||
if(prefixes.size() >= 7) prefixes.removeRight();
|
||||
prefixes.prepend(opcode);
|
||||
wait(4);
|
||||
state.prefix = true;
|
||||
@ -21,7 +21,7 @@ auto V30MZ::opRepeat(bool flag) {
|
||||
|
||||
//f0 lock:
|
||||
auto V30MZ::opLock() {
|
||||
if(prefixes.size() >= 7) prefixes.removeLast();
|
||||
if(prefixes.size() >= 7) prefixes.removeRight();
|
||||
prefixes.prepend(opcode);
|
||||
state.prefix = true;
|
||||
state.poll = false;
|
||||
|
@ -358,11 +358,11 @@ auto Cartridge::parseMarkupSDD1(Markup::Node root) -> void {
|
||||
}
|
||||
|
||||
for(auto node : root["rom"].find("map")) {
|
||||
parseMarkupMap(node, {&SDD1::mcurom_read, &sdd1}, {&SDD1::mcurom_write, &sdd1});
|
||||
parseMarkupMap(node, {&SDD1::mcuromRead, &sdd1}, {&SDD1::mcuromWrite, &sdd1});
|
||||
}
|
||||
|
||||
for(auto node : root["ram"].find("map")) {
|
||||
parseMarkupMap(node, {&SDD1::mcuram_read, &sdd1}, {&SDD1::mcuram_write, &sdd1});
|
||||
parseMarkupMap(node, {&SDD1::mcuramRead, &sdd1}, {&SDD1::mcuramWrite, &sdd1});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,20 +8,20 @@
|
||||
|
||||
//input manager
|
||||
|
||||
auto SDD1::Decomp::IM::init(uint offset_) -> void {
|
||||
auto SDD1::Decompressor::IM::init(uint offset_) -> void {
|
||||
offset = offset_;
|
||||
bit_count = 4;
|
||||
}
|
||||
|
||||
auto SDD1::Decomp::IM::get_codeword(uint8 code_length) -> uint8 {
|
||||
auto SDD1::Decompressor::IM::get_codeword(uint8 code_length) -> uint8 {
|
||||
uint8 codeword;
|
||||
uint8 comp_count;
|
||||
|
||||
codeword = sdd1.mmc_read(offset) << bit_count;
|
||||
codeword = sdd1.mmcRead(offset) << bit_count;
|
||||
bit_count++;
|
||||
|
||||
if(codeword & 0x80) {
|
||||
codeword |= sdd1.mmc_read(offset + 1) >> (9 - bit_count);
|
||||
codeword |= sdd1.mmcRead(offset + 1) >> (9 - bit_count);
|
||||
bit_count += code_length;
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ auto SDD1::Decomp::IM::get_codeword(uint8 code_length) -> uint8 {
|
||||
|
||||
//golomb-code decoder
|
||||
|
||||
const uint8 SDD1::Decomp::GCD::run_count[] = {
|
||||
const uint8 SDD1::Decompressor::GCD::run_count[] = {
|
||||
0x00, 0x00, 0x01, 0x00, 0x03, 0x01, 0x02, 0x00,
|
||||
0x07, 0x03, 0x05, 0x01, 0x06, 0x02, 0x04, 0x00,
|
||||
0x0f, 0x07, 0x0b, 0x03, 0x0d, 0x05, 0x09, 0x01,
|
||||
@ -70,7 +70,7 @@ const uint8 SDD1::Decomp::GCD::run_count[] = {
|
||||
0x70, 0x30, 0x50, 0x10, 0x60, 0x20, 0x40, 0x00,
|
||||
};
|
||||
|
||||
auto SDD1::Decomp::GCD::get_run_count(uint8 code_number, uint8& mps_count, bool& lps_index) -> void {
|
||||
auto SDD1::Decompressor::GCD::get_run_count(uint8 code_number, uint8& mps_count, bool& lps_index) -> void {
|
||||
uint8 codeword = self.im.get_codeword(code_number);
|
||||
|
||||
if(codeword & 0x80) {
|
||||
@ -83,12 +83,12 @@ auto SDD1::Decomp::GCD::get_run_count(uint8 code_number, uint8& mps_count, bool&
|
||||
|
||||
//bits generator
|
||||
|
||||
auto SDD1::Decomp::BG::init() -> void {
|
||||
auto SDD1::Decompressor::BG::init() -> void {
|
||||
mps_count = 0;
|
||||
lps_index = 0;
|
||||
}
|
||||
|
||||
auto SDD1::Decomp::BG::get_bit(bool& end_of_run) -> uint8 {
|
||||
auto SDD1::Decompressor::BG::get_bit(bool& end_of_run) -> uint8 {
|
||||
if(!(mps_count || lps_index)) self.gcd.get_run_count(code_number, mps_count, lps_index);
|
||||
|
||||
uint8 bit;
|
||||
@ -106,7 +106,7 @@ auto SDD1::Decomp::BG::get_bit(bool& end_of_run) -> uint8 {
|
||||
|
||||
//probability estimation module
|
||||
|
||||
const SDD1::Decomp::PEM::State SDD1::Decomp::PEM::evolution_table[33] = {
|
||||
const SDD1::Decompressor::PEM::State SDD1::Decompressor::PEM::evolution_table[33] = {
|
||||
{0, 25, 25},
|
||||
{0, 2, 1},
|
||||
{0, 3, 1},
|
||||
@ -142,18 +142,18 @@ const SDD1::Decomp::PEM::State SDD1::Decomp::PEM::evolution_table[33] = {
|
||||
{7, 24, 22},
|
||||
};
|
||||
|
||||
auto SDD1::Decomp::PEM::init() -> void {
|
||||
auto SDD1::Decompressor::PEM::init() -> void {
|
||||
for(auto n : range(32)) {
|
||||
context_info[n].status = 0;
|
||||
context_info[n].mps = 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto SDD1::Decomp::PEM::get_bit(uint8 context) -> uint8 {
|
||||
auto SDD1::Decompressor::PEM::get_bit(uint8 context) -> uint8 {
|
||||
ContextInfo& info = context_info[context];
|
||||
uint8 current_status = info.status;
|
||||
uint8 current_mps = info.mps;
|
||||
const State& s = SDD1::Decomp::PEM::evolution_table[current_status];
|
||||
const State& s = SDD1::Decompressor::PEM::evolution_table[current_status];
|
||||
|
||||
uint8 bit;
|
||||
bool end_of_run;
|
||||
@ -182,9 +182,9 @@ auto SDD1::Decomp::PEM::get_bit(uint8 context) -> uint8 {
|
||||
|
||||
//context model
|
||||
|
||||
auto SDD1::Decomp::CM::init(uint offset) -> void {
|
||||
bitplanes_info = sdd1.mmc_read(offset) & 0xc0;
|
||||
context_bits_info = sdd1.mmc_read(offset) & 0x30;
|
||||
auto SDD1::Decompressor::CM::init(uint offset) -> void {
|
||||
bitplanes_info = sdd1.mmcRead(offset) & 0xc0;
|
||||
context_bits_info = sdd1.mmcRead(offset) & 0x30;
|
||||
bit_number = 0;
|
||||
for(auto n : range(8)) previous_bitplane_bits[n] = 0;
|
||||
switch(bitplanes_info) {
|
||||
@ -194,7 +194,7 @@ auto SDD1::Decomp::CM::init(uint offset) -> void {
|
||||
}
|
||||
}
|
||||
|
||||
auto SDD1::Decomp::CM::get_bit() -> uint8 {
|
||||
auto SDD1::Decompressor::CM::get_bit() -> uint8 {
|
||||
switch(bitplanes_info) {
|
||||
case 0x00:
|
||||
current_bitplane ^= 0x01;
|
||||
@ -230,12 +230,12 @@ auto SDD1::Decomp::CM::get_bit() -> uint8 {
|
||||
|
||||
//output logic
|
||||
|
||||
auto SDD1::Decomp::OL::init(uint offset) -> void {
|
||||
bitplanes_info = sdd1.mmc_read(offset) & 0xc0;
|
||||
auto SDD1::Decompressor::OL::init(uint offset) -> void {
|
||||
bitplanes_info = sdd1.mmcRead(offset) & 0xc0;
|
||||
r0 = 0x01;
|
||||
}
|
||||
|
||||
auto SDD1::Decomp::OL::decompress() -> uint8 {
|
||||
auto SDD1::Decompressor::OL::decompress() -> uint8 {
|
||||
switch(bitplanes_info) {
|
||||
case 0x00: case 0x40: case 0x80:
|
||||
if(r0 == 0) {
|
||||
@ -257,14 +257,14 @@ auto SDD1::Decomp::OL::decompress() -> uint8 {
|
||||
|
||||
//core
|
||||
|
||||
SDD1::Decomp::Decomp():
|
||||
SDD1::Decompressor::Decompressor():
|
||||
im(*this), gcd(*this),
|
||||
bg0(*this, 0), bg1(*this, 1), bg2(*this, 2), bg3(*this, 3),
|
||||
bg4(*this, 4), bg5(*this, 5), bg6(*this, 6), bg7(*this, 7),
|
||||
pem(*this), cm(*this), ol(*this) {
|
||||
}
|
||||
|
||||
auto SDD1::Decomp::init(uint offset) -> void {
|
||||
auto SDD1::Decompressor::init(uint offset) -> void {
|
||||
im.init(offset);
|
||||
bg0.init();
|
||||
bg1.init();
|
||||
@ -279,6 +279,6 @@ auto SDD1::Decomp::init(uint offset) -> void {
|
||||
ol.init(offset);
|
||||
}
|
||||
|
||||
auto SDD1::Decomp::read() -> uint8 {
|
||||
auto SDD1::Decompressor::read() -> uint8 {
|
||||
return ol.decompress();
|
||||
}
|
@ -1,43 +1,43 @@
|
||||
struct Decomp {
|
||||
struct Decompressor {
|
||||
struct IM { //input manager
|
||||
IM(SDD1::Decomp& self) : self(self) {}
|
||||
IM(SDD1::Decompressor& self) : self(self) {}
|
||||
auto init(uint offset) -> void;
|
||||
auto get_codeword(uint8 code_length) -> uint8;
|
||||
|
||||
private:
|
||||
Decomp& self;
|
||||
Decompressor& self;
|
||||
uint offset;
|
||||
uint bit_count;
|
||||
};
|
||||
|
||||
struct GCD { //golomb-code decoder
|
||||
GCD(SDD1::Decomp& self) : self(self) {}
|
||||
GCD(SDD1::Decompressor& self) : self(self) {}
|
||||
auto get_run_count(uint8 code_number, uint8& mps_count, bool& lps_index) -> void;
|
||||
|
||||
private:
|
||||
Decomp& self;
|
||||
Decompressor& self;
|
||||
static const uint8 run_count[256];
|
||||
};
|
||||
|
||||
struct BG { //bits generator
|
||||
BG(SDD1::Decomp& self, uint8 code_number) : self(self), code_number(code_number) {}
|
||||
BG(SDD1::Decompressor& self, uint8 code_number) : self(self), code_number(code_number) {}
|
||||
auto init() -> void;
|
||||
auto get_bit(bool& end_of_run) -> uint8;
|
||||
|
||||
private:
|
||||
Decomp& self;
|
||||
Decompressor& self;
|
||||
const uint8 code_number;
|
||||
uint8 mps_count;
|
||||
bool lps_index;
|
||||
};
|
||||
|
||||
struct PEM { //probability estimation module
|
||||
PEM(SDD1::Decomp& self) : self(self) {}
|
||||
PEM(SDD1::Decompressor& self) : self(self) {}
|
||||
auto init() -> void;
|
||||
auto get_bit(uint8 context) -> uint8;
|
||||
|
||||
private:
|
||||
Decomp& self;
|
||||
Decompressor& self;
|
||||
struct State {
|
||||
uint8 code_number;
|
||||
uint8 next_if_mps;
|
||||
@ -51,12 +51,12 @@ struct Decomp {
|
||||
};
|
||||
|
||||
struct CM { //context model
|
||||
CM(SDD1::Decomp& self) : self(self) {}
|
||||
CM(SDD1::Decompressor& self) : self(self) {}
|
||||
auto init(uint offset) -> void;
|
||||
uint8 get_bit();
|
||||
|
||||
private:
|
||||
Decomp& self;
|
||||
Decompressor& self;
|
||||
uint8 bitplanes_info;
|
||||
uint8 context_bits_info;
|
||||
uint8 bit_number;
|
||||
@ -65,17 +65,17 @@ struct Decomp {
|
||||
};
|
||||
|
||||
struct OL { //output logic
|
||||
OL(SDD1::Decomp& self) : self(self) {}
|
||||
OL(SDD1::Decompressor& self) : self(self) {}
|
||||
auto init(uint offset) -> void;
|
||||
auto decompress() -> uint8;
|
||||
|
||||
private:
|
||||
Decomp& self;
|
||||
Decompressor& self;
|
||||
uint8 bitplanes_info;
|
||||
uint8 r0, r1, r2;
|
||||
};
|
||||
|
||||
Decomp();
|
||||
Decompressor();
|
||||
auto init(uint offset) -> void;
|
||||
auto read() -> uint8;
|
||||
|
@ -4,7 +4,7 @@ namespace SuperFamicom {
|
||||
|
||||
SDD1 sdd1;
|
||||
|
||||
#include "decomp.cpp"
|
||||
#include "decompressor.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto SDD1::init() -> void {
|
||||
@ -24,113 +24,106 @@ auto SDD1::power() -> void {
|
||||
auto SDD1::reset() -> void {
|
||||
//hook S-CPU DMA MMIO registers to gather information for struct dma[];
|
||||
//buffer address and transfer size information for use in SDD1::mcu_read()
|
||||
bus.map({&SDD1::dma_read, &sdd1}, {&SDD1::dma_write, &sdd1}, "00-3f,80-bf:4300-437f");
|
||||
bus.map({&SDD1::dmaRead, &sdd1}, {&SDD1::dmaWrite, &sdd1}, "00-3f,80-bf:4300-437f");
|
||||
|
||||
sdd1_enable = 0x00;
|
||||
xfer_enable = 0x00;
|
||||
dma_ready = false;
|
||||
|
||||
mmc[0] = 0 << 20;
|
||||
mmc[1] = 1 << 20;
|
||||
mmc[2] = 2 << 20;
|
||||
mmc[3] = 3 << 20;
|
||||
r4800 = 0x00;
|
||||
r4801 = 0x00;
|
||||
r4804 = 0x00;
|
||||
r4805 = 0x01;
|
||||
r4806 = 0x02;
|
||||
r4807 = 0x03;
|
||||
|
||||
for(auto n : range(8)) {
|
||||
dma[n].addr = 0;
|
||||
dma[n].size = 0;
|
||||
}
|
||||
dmaReady = false;
|
||||
}
|
||||
|
||||
auto SDD1::read(uint24 addr, uint8 data) -> uint8 {
|
||||
addr = 0x4800 | (addr & 7);
|
||||
addr = 0x4800 | addr.bits(0,3);
|
||||
|
||||
switch(addr) {
|
||||
case 0x4804: return mmc[0] >> 20;
|
||||
case 0x4805: return mmc[1] >> 20;
|
||||
case 0x4806: return mmc[2] >> 20;
|
||||
case 0x4807: return mmc[3] >> 20;
|
||||
case 0x4800: return r4800;
|
||||
case 0x4801: return r4801;
|
||||
case 0x4804: return r4804;
|
||||
case 0x4805: return r4805;
|
||||
case 0x4806: return r4806;
|
||||
case 0x4807: return r4807;
|
||||
}
|
||||
|
||||
return data;
|
||||
//00-3f,80-bf:4802-4803,4808-480f falls through to ROM
|
||||
return rom.read(addr);
|
||||
}
|
||||
|
||||
auto SDD1::write(uint24 addr, uint8 data) -> void {
|
||||
addr = 0x4800 | (addr & 7);
|
||||
addr = 0x4800 | addr.bits(0,3);
|
||||
|
||||
switch(addr) {
|
||||
case 0x4800: sdd1_enable = data; break;
|
||||
case 0x4801: xfer_enable = data; break;
|
||||
|
||||
case 0x4804: mmc[0] = data << 20; break;
|
||||
case 0x4805: mmc[1] = data << 20; break;
|
||||
case 0x4806: mmc[2] = data << 20; break;
|
||||
case 0x4807: mmc[3] = data << 20; break;
|
||||
case 0x4800: r4800 = data; break;
|
||||
case 0x4801: r4801 = data; break;
|
||||
case 0x4804: r4804 = data & 0x8f; break;
|
||||
case 0x4805: r4805 = data & 0x8f; break;
|
||||
case 0x4806: r4806 = data & 0x8f; break;
|
||||
case 0x4807: r4807 = data & 0x8f; break;
|
||||
}
|
||||
}
|
||||
|
||||
auto SDD1::dma_read(uint24 addr, uint8 data) -> uint8 {
|
||||
auto SDD1::dmaRead(uint24 addr, uint8 data) -> uint8 {
|
||||
return cpu.dmaPortRead(addr, data);
|
||||
}
|
||||
|
||||
auto SDD1::dma_write(uint24 addr, uint8 data) -> void {
|
||||
uint channel = (addr >> 4) & 7;
|
||||
switch(addr & 15) {
|
||||
case 2: dma[channel].addr = (dma[channel].addr & 0xffff00) + (data << 0); break;
|
||||
case 3: dma[channel].addr = (dma[channel].addr & 0xff00ff) + (data << 8); break;
|
||||
case 4: dma[channel].addr = (dma[channel].addr & 0x00ffff) + (data << 16); break;
|
||||
|
||||
case 5: dma[channel].size = (dma[channel].size & 0xff00) + (data << 0); break;
|
||||
case 6: dma[channel].size = (dma[channel].size & 0x00ff) + (data << 8); break;
|
||||
auto SDD1::dmaWrite(uint24 addr, uint8 data) -> void {
|
||||
uint channel = addr.bits(4,6);
|
||||
switch(addr.bits(0,3)) {
|
||||
case 2: dma[channel].addr.byte(0) = data; break;
|
||||
case 3: dma[channel].addr.byte(1) = data; break;
|
||||
case 4: dma[channel].addr.byte(2) = data; break;
|
||||
case 5: dma[channel].size.byte(0) = data; break;
|
||||
case 6: dma[channel].size.byte(1) = data; break;
|
||||
}
|
||||
return cpu.dmaPortWrite(addr, data);
|
||||
}
|
||||
|
||||
auto SDD1::mmc_read(uint24 addr) -> uint8 {
|
||||
return rom.read(mmc[(addr >> 20) & 3] + (addr & 0x0fffff));
|
||||
auto SDD1::mmcRead(uint24 addr) -> uint8 {
|
||||
switch(addr.bits(20,21)) {
|
||||
case 0: return rom.read(r4804.bits(0,3) << 20 | addr.bits(0,19)); //c0-cf:0000-ffff
|
||||
case 1: return rom.read(r4805.bits(0,3) << 20 | addr.bits(0,19)); //d0-df:0000-ffff
|
||||
case 2: return rom.read(r4806.bits(0,3) << 20 | addr.bits(0,19)); //e0-ef:0000-ffff
|
||||
case 3: return rom.read(r4807.bits(0,3) << 20 | addr.bits(0,19)); //f0-ff:0000-ffff
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
|
||||
//SDD1::mcu_read() is mapped to $c0-ff:0000-ffff
|
||||
//the design is meant to be as close to the hardware design as possible, thus this code
|
||||
//avoids adding S-DD1 hooks inside S-CPU::DMA emulation.
|
||||
//
|
||||
//the real S-DD1 cannot see $420b (DMA enable) writes, as they are not placed on the bus.
|
||||
//however, $43x0-$43xf writes (DMAx channel settings) most likely do appear on the bus.
|
||||
//the S-DD1 also requires fixed addresses for transfers, which wouldn't be necessary if
|
||||
//it could see $420b writes (eg it would know when the transfer should begin.)
|
||||
//
|
||||
//the hardware needs a way to distinguish program code after $4801 writes from DMA
|
||||
//decompression that follows soon after.
|
||||
//
|
||||
//the only plausible design for hardware would be for the S-DD1 to spy on DMAx settings,
|
||||
//and begin spooling decompression on writes to $4801 that activate a channel. after that,
|
||||
//it feeds decompressed data only when the ROM read address matches the DMA channel address.
|
||||
//
|
||||
//the actual S-DD1 transfer can occur on any channel, but it is most likely limited to
|
||||
//one transfer per $420b write (for spooling purposes). however, this is not known for certain.
|
||||
auto SDD1::mcurom_read(uint24 addr, uint8) -> uint8 {
|
||||
//map address=00-3f,80-bf:8000-ffff
|
||||
//map address=c0-ff:0000-ffff
|
||||
auto SDD1::mcuromRead(uint24 addr, uint8 data) -> uint8 {
|
||||
//map address=00-3f,80-bf:8000-ffff mask=0x808000 => 00-1f:0000-ffff
|
||||
if(addr < 0x200000) {
|
||||
if(!addr.bit(22)) {
|
||||
if(!addr.bit(23) && addr.bit(21) && r4805.bit(7)) addr.bit(21) = 0; //20-3f:8000-ffff
|
||||
if( addr.bit(23) && addr.bit(21) && r4807.bit(7)) addr.bit(21) = 0; //a0-bf:8000-ffff
|
||||
addr = addr.bits(16,21) << 15 | addr.bits(0,14);
|
||||
return rom.read(addr);
|
||||
}
|
||||
|
||||
//map address=c0-ff:0000-ffff
|
||||
if(sdd1_enable & xfer_enable) {
|
||||
if(r4800 & r4801) {
|
||||
//at least one channel has S-DD1 decompression enabled ...
|
||||
for(auto n : range(8)) {
|
||||
if(sdd1_enable & xfer_enable & (1 << n)) {
|
||||
if(r4800.bit(n) && r4801.bit(n)) {
|
||||
//S-DD1 always uses fixed transfer mode, so address will not change during transfer
|
||||
if(addr == dma[n].addr) {
|
||||
if(!dma_ready) {
|
||||
if(!dmaReady) {
|
||||
//prepare streaming decompression
|
||||
decomp.init(addr);
|
||||
dma_ready = true;
|
||||
decompressor.init(addr);
|
||||
dmaReady = true;
|
||||
}
|
||||
|
||||
//fetch a decompressed byte; once finished, disable channel and invalidate buffer
|
||||
uint8 data = decomp.read();
|
||||
data = decompressor.read();
|
||||
if(--dma[n].size == 0) {
|
||||
dma_ready = false;
|
||||
xfer_enable &= ~(1 << n);
|
||||
dmaReady = false;
|
||||
r4801.bit(n) = 0;
|
||||
}
|
||||
|
||||
return data;
|
||||
@ -140,20 +133,20 @@ auto SDD1::mcurom_read(uint24 addr, uint8) -> uint8 {
|
||||
} //S-DD1 decompressor enabled
|
||||
|
||||
//S-DD1 decompression mode inactive; return ROM data
|
||||
return mmc_read(addr);
|
||||
return mmcRead(addr);
|
||||
}
|
||||
|
||||
auto SDD1::mcurom_write(uint24 addr, uint8 data) -> void {
|
||||
auto SDD1::mcuromWrite(uint24 addr, uint8 data) -> void {
|
||||
}
|
||||
|
||||
//map address=00-3f,80-bf:6000-7fff mask=0xe000
|
||||
//map address=70-7d:0000-7fff mask=0x8000
|
||||
auto SDD1::mcuram_read(uint24 addr, uint8 data) -> uint8 {
|
||||
return ram.read(addr & 0x1fff, data);
|
||||
//map address=70-73:0000-ffff mask=0x8000
|
||||
auto SDD1::mcuramRead(uint24 addr, uint8 data) -> uint8 {
|
||||
return ram.read(addr.bits(0,12), data);
|
||||
}
|
||||
|
||||
auto SDD1::mcuram_write(uint24 addr, uint8 data) -> void {
|
||||
return ram.write(addr & 0x1fff, data);
|
||||
auto SDD1::mcuramWrite(uint24 addr, uint8 data) -> void {
|
||||
return ram.write(addr.bits(0,12), data);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,16 +8,16 @@ struct SDD1 {
|
||||
auto read(uint24 addr, uint8 data) -> uint8;
|
||||
auto write(uint24 addr, uint8 data) -> void;
|
||||
|
||||
auto dma_read(uint24 addr, uint8 data) -> uint8;
|
||||
auto dma_write(uint24 addr, uint8 data) -> void;
|
||||
auto dmaRead(uint24 addr, uint8 data) -> uint8;
|
||||
auto dmaWrite(uint24 addr, uint8 data) -> void;
|
||||
|
||||
auto mmc_read(uint24 addr) -> uint8;
|
||||
auto mmcRead(uint24 addr) -> uint8;
|
||||
|
||||
auto mcurom_read(uint24 addr, uint8 data) -> uint8;
|
||||
auto mcurom_write(uint24 addr, uint8 data) -> void;
|
||||
auto mcuromRead(uint24 addr, uint8 data) -> uint8;
|
||||
auto mcuromWrite(uint24 addr, uint8 data) -> void;
|
||||
|
||||
auto mcuram_read(uint24 addr, uint8 data) -> uint8;
|
||||
auto mcuram_write(uint24 addr, uint8 data) -> void;
|
||||
auto mcuramRead(uint24 addr, uint8 data) -> uint8;
|
||||
auto mcuramWrite(uint24 addr, uint8 data) -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
@ -25,19 +25,22 @@ struct SDD1 {
|
||||
MappedRAM ram;
|
||||
|
||||
private:
|
||||
uint8 sdd1_enable; //channel bit-mask
|
||||
uint8 xfer_enable; //channel bit-mask
|
||||
bool dma_ready; //used to initialize decompression module
|
||||
uint mmc[4]; //memory map controller ROM indices
|
||||
uint8 r4800; //hard enable
|
||||
uint8 r4801; //soft enable
|
||||
uint8 r4804; //MMC bank 0
|
||||
uint8 r4805; //MMC bank 1
|
||||
uint8 r4806; //MMC bank 2
|
||||
uint8 r4807; //MMC bank 3
|
||||
|
||||
struct {
|
||||
uint addr; //$43x2-$43x4 -- DMA transfer address
|
||||
uint16 size; //$43x5-$43x6 -- DMA transfer size
|
||||
struct DMA {
|
||||
uint24 addr; //$43x2-$43x4 -- DMA transfer address
|
||||
uint16 size; //$43x5-$43x6 -- DMA transfer size
|
||||
} dma[8];
|
||||
bool dmaReady; //used to initialize decompression module
|
||||
|
||||
public:
|
||||
#include "decomp.hpp"
|
||||
Decomp decomp;
|
||||
#include "decompressor.hpp"
|
||||
Decompressor decompressor;
|
||||
};
|
||||
|
||||
extern SDD1 sdd1;
|
||||
|
@ -1,13 +1,16 @@
|
||||
auto SDD1::serialize(serializer& s) -> void {
|
||||
s.array(ram.data(), ram.size());
|
||||
|
||||
s.integer(sdd1_enable);
|
||||
s.integer(xfer_enable);
|
||||
s.integer(dma_ready);
|
||||
s.array(mmc);
|
||||
s.integer(r4800);
|
||||
s.integer(r4801);
|
||||
s.integer(r4804);
|
||||
s.integer(r4805);
|
||||
s.integer(r4806);
|
||||
s.integer(r4807);
|
||||
|
||||
for(auto n : range(8)) {
|
||||
s.integer(dma[n].addr);
|
||||
s.integer(dma[n].size);
|
||||
}
|
||||
s.integer(dmaReady);
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ auto S21FX::read(uint24 addr, uint8 data) -> uint8 {
|
||||
|
||||
if(addr == 0x21ff) {
|
||||
if(linkBuffer.size() > 0) {
|
||||
return linkBuffer.takeFirst();
|
||||
return linkBuffer.takeLeft();
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,7 +123,7 @@ auto S21FX::writable() -> bool {
|
||||
auto S21FX::read() -> uint8 {
|
||||
step(1);
|
||||
if(snesBuffer.size() > 0) {
|
||||
return snesBuffer.takeFirst();
|
||||
return snesBuffer.takeLeft();
|
||||
}
|
||||
return 0x00;
|
||||
}
|
||||
|
@ -60,8 +60,8 @@ Interface::Interface() {
|
||||
device.input.append({n + 9, 0, {"Port ", p, " - ", "X" }});
|
||||
device.input.append({n + 10, 0, {"Port ", p, " - ", "L" }});
|
||||
device.input.append({n + 11, 0, {"Port ", p, " - ", "R" }});
|
||||
device.order.append(n + 4, n + 5, n + 6, n + 7, n + 0, n + 8);
|
||||
device.order.append(n + 1, n + 9, n + 10, n + 11, n + 2, n + 3);
|
||||
device.order.append({n + 4, n + 5, n + 6, n + 7, n + 0, n + 8});
|
||||
device.order.append({n + 1, n + 9, n + 10, n + 11, n + 2, n + 3});
|
||||
}
|
||||
this->device.append(device);
|
||||
}
|
||||
@ -100,12 +100,12 @@ Interface::Interface() {
|
||||
device.input.append({1, 1, "Port 1 - Y-axis" });
|
||||
device.input.append({2, 0, "Port 1 - Trigger"});
|
||||
device.input.append({3, 0, "Port 1 - Start" });
|
||||
device.order.append(0, 1, 2, 3);
|
||||
device.order.append({0, 1, 2, 3});
|
||||
device.input.append({4, 1, "Port 2 - X-axis" });
|
||||
device.input.append({5, 1, "Port 2 - Y-axis" });
|
||||
device.input.append({6, 0, "Port 2 - Trigger"});
|
||||
device.input.append({7, 0, "Port 2 - Start" });
|
||||
device.order.append(4, 5, 6, 7);
|
||||
device.order.append({4, 5, 6, 7});
|
||||
this->device.append(device);
|
||||
}
|
||||
|
||||
|
@ -147,21 +147,21 @@ InputManager::InputManager() {
|
||||
|
||||
for(auto& emulator : program->emulators) {
|
||||
emulators.append(InputEmulator());
|
||||
auto& inputEmulator = emulators.last();
|
||||
auto& inputEmulator = emulators.right();
|
||||
inputEmulator.name = emulator->information.name;
|
||||
|
||||
for(auto& port : emulator->port) {
|
||||
inputEmulator.ports.append(InputPort());
|
||||
auto& inputPort = inputEmulator.ports.last();
|
||||
auto& inputPort = inputEmulator.ports.right();
|
||||
inputPort.name = port.name;
|
||||
for(auto& device : port.device) {
|
||||
inputPort.devices.append(InputDevice());
|
||||
auto& inputDevice = inputPort.devices.last();
|
||||
auto& inputDevice = inputPort.devices.right();
|
||||
inputDevice.name = device.name;
|
||||
for(auto number : device.order) {
|
||||
auto& input = device.input[number];
|
||||
inputDevice.mappings.append(new InputMapping());
|
||||
auto& inputMapping = inputDevice.mappings.last();
|
||||
auto& inputMapping = inputDevice.mappings.right();
|
||||
inputMapping->name = input.name;
|
||||
inputMapping->link = &input;
|
||||
input.guid = (uintptr)inputMapping;
|
||||
|
@ -52,7 +52,7 @@ Program::Program(lstring args) {
|
||||
updateAudioDriver();
|
||||
updateAudioEffects();
|
||||
|
||||
args.takeFirst(); //ignore program location in argument parsing
|
||||
args.takeLeft(); //ignore program location in argument parsing
|
||||
for(auto& argument : args) {
|
||||
if(argument == "--fullscreen") {
|
||||
presentation->toggleFullScreen();
|
||||
|
@ -48,7 +48,7 @@ auto mMenu::remove(sAction action) -> type& {
|
||||
}
|
||||
|
||||
auto mMenu::reset() -> type& {
|
||||
while(state.actions) remove(state.actions.last());
|
||||
while(state.actions) remove(state.actions.right());
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ auto mLayout::remove(sSizable sizable) -> type& {
|
||||
}
|
||||
|
||||
auto mLayout::reset() -> type& {
|
||||
while(state.sizables) remove(state.sizables.last());
|
||||
while(state.sizables) remove(state.sizables.right());
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ auto mMenuBar::remove(sMenu menu) -> type& {
|
||||
}
|
||||
|
||||
auto mMenuBar::reset() -> type& {
|
||||
while(state.menus) remove(state.menus.last());
|
||||
while(state.menus) remove(state.menus.right());
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ auto mPopupMenu::remove(sAction action) -> type& {
|
||||
}
|
||||
|
||||
auto mPopupMenu::reset() -> type& {
|
||||
while(state.actions) remove(state.actions.last());
|
||||
while(state.actions) remove(state.actions.right());
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,7 @@ struct Group : sGroup {
|
||||
template<typename T = Object> auto objects() const -> vector<T> {
|
||||
vector<T> objects;
|
||||
for(auto object : self().objects()) {
|
||||
if(auto cast = object.cast<T>()) objects.append(cast);
|
||||
if(auto casted = object.cast<T>()) objects.append(casted);
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ auto mTabFrame::remove(sTabFrameItem item) -> type& {
|
||||
}
|
||||
|
||||
auto mTabFrame::reset() -> type& {
|
||||
while(state.items) remove(state.items.last());
|
||||
while(state.items) remove(state.items.right());
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -59,9 +59,9 @@ auto mTreeViewItem::icon() const -> image {
|
||||
auto mTreeViewItem::item(const string& path) const -> TreeViewItem {
|
||||
if(path.empty()) return {};
|
||||
auto paths = path.split("/");
|
||||
unsigned position = paths.takeFirst().natural();
|
||||
unsigned position = paths.takeLeft().natural();
|
||||
if(position >= itemCount()) return {};
|
||||
if(paths.empty()) return state.items[position];
|
||||
if(!paths) return state.items[position];
|
||||
return state.items[position]->item(paths.merge("/"));
|
||||
}
|
||||
|
||||
|
@ -45,9 +45,9 @@ auto mTreeView::foregroundColor() const -> Color {
|
||||
auto mTreeView::item(const string& path) const -> TreeViewItem {
|
||||
if(path.empty()) return {};
|
||||
auto paths = path.split("/");
|
||||
unsigned position = paths.takeFirst().natural();
|
||||
unsigned position = paths.takeLeft().natural();
|
||||
if(position >= itemCount()) return {};
|
||||
if(paths.empty()) return state.items[position];
|
||||
if(!paths) return state.items[position];
|
||||
return state.items[position]->item(paths.merge("/"));
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@ auto BrowserDialogWindow::accept() -> void {
|
||||
auto batched = view.batched();
|
||||
|
||||
if(state.action == "openFile" && batched) {
|
||||
string name = batched.first()->cell(0)->text();
|
||||
string name = batched.left()->cell(0)->text();
|
||||
if(isFolder(name)) return setPath({state.path, name});
|
||||
state.response.append(string{state.path, name});
|
||||
}
|
||||
@ -48,14 +48,14 @@ auto BrowserDialogWindow::accept() -> void {
|
||||
}
|
||||
|
||||
if(state.action == "openFolder" && batched) {
|
||||
string name = batched.first()->cell(0)->text();
|
||||
string name = batched.left()->cell(0)->text();
|
||||
if(!isMatch(name)) return setPath({state.path, name});
|
||||
state.response.append(string{state.path, name, "/"});
|
||||
}
|
||||
|
||||
if(state.action == "saveFile") {
|
||||
string name = fileName.text();
|
||||
if(!name && batched) name = batched.first()->cell(0)->text();
|
||||
if(!name && batched) name = batched.left()->cell(0)->text();
|
||||
if(!name || isFolder(name)) return;
|
||||
if(file::exists({state.path, name})) {
|
||||
if(MessageDialog("File already exists; overwrite it?").question() != "Yes") return;
|
||||
@ -64,7 +64,7 @@ auto BrowserDialogWindow::accept() -> void {
|
||||
}
|
||||
|
||||
if(state.action == "selectFolder" && batched) {
|
||||
string name = batched.first()->cell(0)->text();
|
||||
string name = batched.left()->cell(0)->text();
|
||||
if(isFolder(name)) state.response.append(string{state.path, name, "/"});
|
||||
}
|
||||
|
||||
@ -125,7 +125,7 @@ auto BrowserDialogWindow::run() -> lstring {
|
||||
filterList.setVisible(state.action != "selectFolder").onChange([&] { setPath(state.path); });
|
||||
for(auto& filter : state.filters) {
|
||||
auto part = filter.split("|", 1L);
|
||||
filterList.append(ComboButtonItem().setText(part.first()));
|
||||
filterList.append(ComboButtonItem().setText(part.left()));
|
||||
}
|
||||
fileName.setVisible(state.action == "saveFile").onActivate([&] { accept(); });
|
||||
acceptButton.onActivate([&] { accept(); });
|
||||
@ -137,7 +137,7 @@ auto BrowserDialogWindow::run() -> lstring {
|
||||
if(!state.filters) state.filters.append("All|*");
|
||||
for(auto& filter : state.filters) {
|
||||
auto part = filter.split("|", 1L);
|
||||
filters.append(part.last().split(":"));
|
||||
filters.append(part.right().split(":"));
|
||||
}
|
||||
|
||||
setPath(state.path);
|
||||
@ -201,7 +201,7 @@ BrowserDialog::BrowserDialog() {
|
||||
auto BrowserDialog::openFile() -> string {
|
||||
state.action = "openFile";
|
||||
if(!state.title) state.title = "Open File";
|
||||
if(auto result = _run()) return result.first();
|
||||
if(auto result = _run()) return result.left();
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -215,21 +215,21 @@ auto BrowserDialog::openFiles() -> lstring {
|
||||
auto BrowserDialog::openFolder() -> string {
|
||||
state.action = "openFolder";
|
||||
if(!state.title) state.title = "Open Folder";
|
||||
if(auto result = _run()) return result.first();
|
||||
if(auto result = _run()) return result.left();
|
||||
return {};
|
||||
}
|
||||
|
||||
auto BrowserDialog::saveFile() -> string {
|
||||
state.action = "saveFile";
|
||||
if(!state.title) state.title = "Save File";
|
||||
if(auto result = _run()) return result.first();
|
||||
if(auto result = _run()) return result.left();
|
||||
return {};
|
||||
}
|
||||
|
||||
auto BrowserDialog::selectFolder() -> string {
|
||||
state.action = "selectFolder";
|
||||
if(!state.title) state.title = "Select Folder";
|
||||
if(auto result = _run()) return result.first();
|
||||
if(auto result = _run()) return result.left();
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ auto mHorizontalLayout::minimumSize() const -> Size {
|
||||
} else {
|
||||
width += child.width;
|
||||
}
|
||||
if(&child != &properties.last()) width += child.spacing;
|
||||
if(&child != &properties.right()) width += child.spacing;
|
||||
}
|
||||
|
||||
for(auto n : range(sizableCount())) {
|
||||
@ -94,7 +94,7 @@ auto mHorizontalLayout::setGeometry(Geometry containerGeometry) -> type& {
|
||||
for(auto& child : properties) {
|
||||
if(child.width == Size::Maximum) maximumWidthCounter++;
|
||||
if(child.width != Size::Maximum) minimumWidth += child.width;
|
||||
if(&child != &properties.last()) minimumWidth += child.spacing;
|
||||
if(&child != &properties.right()) minimumWidth += child.spacing;
|
||||
}
|
||||
|
||||
for(auto& child : properties) {
|
||||
|
@ -35,7 +35,7 @@ auto mVerticalLayout::minimumSize() const -> Size {
|
||||
} else {
|
||||
height += child.height;
|
||||
}
|
||||
if(&child != &properties.last()) height += child.spacing;
|
||||
if(&child != &properties.right()) height += child.spacing;
|
||||
}
|
||||
|
||||
return {settings.margin * 2 + width, settings.margin * 2 + height};
|
||||
@ -94,7 +94,7 @@ auto mVerticalLayout::setGeometry(Geometry containerGeometry) -> type& {
|
||||
for(auto& child : properties) {
|
||||
if(child.height == Size::Maximum) maximumHeightCounter++;
|
||||
if(child.height != Size::Maximum) minimumHeight += child.height;
|
||||
if(&child != &properties.last()) minimumHeight += child.spacing;
|
||||
if(&child != &properties.right()) minimumHeight += child.spacing;
|
||||
}
|
||||
|
||||
for(auto& child : properties) {
|
||||
|
@ -6,7 +6,7 @@ static auto Canvas_drop(GtkWidget* widget, GdkDragContext* context, signed x, si
|
||||
GtkSelectionData* data, unsigned type, unsigned timestamp, pCanvas* p) -> void {
|
||||
if(!p->state().droppable) return;
|
||||
lstring paths = DropPaths(data);
|
||||
if(paths.empty()) return;
|
||||
if(!paths) return;
|
||||
p->self().doDrop(paths);
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ auto pConsole::_keyPress(unsigned scancode, unsigned mask) -> bool {
|
||||
gtk_text_buffer_insert(textBuffer, &end, string{"\n", state().prompt}, -1);
|
||||
self().doActivate(s);
|
||||
if(s) history.prepend(s);
|
||||
if(history.size() > 128) history.removeLast();
|
||||
if(history.size() > 128) history.removeRight();
|
||||
historyOffset = 0;
|
||||
_seekToEnd();
|
||||
return true;
|
||||
|
@ -133,10 +133,10 @@ auto pTreeView::_doDataFunc(GtkTreeViewColumn* column, GtkCellRenderer* renderer
|
||||
auto parts = string{path}.split(":");
|
||||
g_free(path);
|
||||
|
||||
auto item = self().item(parts.takeFirst().natural());
|
||||
auto item = self().item(parts.takeLeft().natural());
|
||||
if(!item) return;
|
||||
while(parts) {
|
||||
item = item.item(parts.takeFirst().natural());
|
||||
item = item.item(parts.takeLeft().natural());
|
||||
if(!item) return;
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ static auto Viewport_dropEvent(GtkWidget* widget, GdkDragContext* context, signe
|
||||
GtkSelectionData* data, unsigned type, unsigned timestamp, pViewport* p) -> void {
|
||||
if(!p->state().droppable) return;
|
||||
lstring paths = DropPaths(data);
|
||||
if(paths.empty()) return;
|
||||
if(!paths) return;
|
||||
p->self().doDrop(paths);
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,7 @@ static auto Window_drop(GtkWidget* widget, GdkDragContext* context, signed x, si
|
||||
GtkSelectionData* data, unsigned type, unsigned timestamp, pWindow* p) -> void {
|
||||
if(!p->state().droppable) return;
|
||||
lstring paths = DropPaths(data);
|
||||
if(paths.empty()) return;
|
||||
if(!paths) return;
|
||||
p->self().doDrop(paths);
|
||||
}
|
||||
|
||||
|
@ -7376,9 +7376,9 @@ cartridge sha256:b4626cf0c876a124b50f9421c48a7d762e9ed808ad336c799d543d60b484897
|
||||
cartridge sha256:910a29f834199c63c22beddc749baba746da9922196a553255deade59f4fc127
|
||||
:board region=ntsc
|
||||
: sdd1
|
||||
: map address=00-3f,80-bf:4800-4807
|
||||
: map address=00-3f,80-bf:4800-480f
|
||||
: rom name=program.rom size=0x400000
|
||||
: map address=00-3f,80-bf:8000-ffff mask=0x808000
|
||||
: map address=00-3f,80-bf:8000-ffff
|
||||
: map address=c0-ff:0000-ffff
|
||||
:
|
||||
:information
|
||||
|
@ -30,7 +30,7 @@ GameBoyAdvanceCartridge::GameBoyAdvanceCartridge(const uint8_t* data, unsigned s
|
||||
char text[16];
|
||||
memcpy(text, data + n, id.size + 3);
|
||||
text[id.size + 3] = 0;
|
||||
list.appendOnce(text);
|
||||
if(!list.find(text)) list.append(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -253,15 +253,15 @@ SuperFamicomCartridge::SuperFamicomCartridge(const uint8_t* data, uint size, boo
|
||||
else if(has_sdd1) {
|
||||
markup.append(
|
||||
" sdd1\n"
|
||||
" map address=00-3f,80-bf:4800-4807\n"
|
||||
" map address=00-3f,80-bf:4800-480f\n"
|
||||
" rom name=program.rom size=0x", hex(rom_size), "\n"
|
||||
" map address=00-3f,80-bf:8000-ffff mask=0x808000\n"
|
||||
" map address=00-3f,80-bf:8000-ffff\n"
|
||||
" map address=c0-ff:0000-ffff\n"
|
||||
);
|
||||
if(ram_size > 0) markup.append(
|
||||
" ram name=save.ram size=0x", hex(ram_size), "\n"
|
||||
" map address=20-3f,a0-bf:6000-7fff mask=0xe000\n"
|
||||
" map address=70-7d:0000-7fff mask=0x8000\n"
|
||||
" map address=00-3f,80-bf:6000-7fff mask=0xe000\n"
|
||||
" map address=70-73:0000-ffff mask=0x8000\n"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ struct Node {
|
||||
|
||||
auto find(const string& path) -> maybe<Node&> {
|
||||
auto p = path.split("/");
|
||||
auto name = p.takeFirst();
|
||||
auto name = p.takeLeft();
|
||||
for(auto& child : children) {
|
||||
if(child.name == name) {
|
||||
if(p.size() == 0) return child;
|
||||
|
134
nall/emulation/21fx.hpp
Normal file
134
nall/emulation/21fx.hpp
Normal file
@ -0,0 +1,134 @@
|
||||
#pragma once
|
||||
|
||||
#include <nall/nall.hpp>
|
||||
#include <nall/serial.hpp>
|
||||
using namespace nall;
|
||||
|
||||
struct FX {
|
||||
auto open(lstring& args) -> bool;
|
||||
auto close() -> void;
|
||||
auto readable() -> bool;
|
||||
auto read() -> uint8_t;
|
||||
auto writable() -> bool;
|
||||
auto write(uint8_t data) -> void;
|
||||
|
||||
auto read(uint offset, uint length) -> vector<uint8_t>;
|
||||
auto write(uint offset, const vector<uint8_t>& buffer) -> void;
|
||||
auto execute(uint offset) -> void;
|
||||
|
||||
auto read(uint offset) -> uint8_t;
|
||||
auto write(uint offset, uint8_t data) -> void;
|
||||
|
||||
serial device;
|
||||
};
|
||||
|
||||
auto FX::open(lstring& args) -> bool {
|
||||
//device name override support
|
||||
string name;
|
||||
for(uint n : range(args)) {
|
||||
if(args[n].beginsWith("--device=")) {
|
||||
name = args.take(n).ltrim("--device=", 1L);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!device.open(name)) {
|
||||
print("[21fx] error: unable to open hardware device\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
//flush the device (to clear floating inputs)
|
||||
while(true) {
|
||||
while(readable()) read();
|
||||
auto iplrom = read(0x2184, 122);
|
||||
auto sha256 = Hash::SHA256(iplrom.data(), iplrom.size()).digest();
|
||||
if(sha256 == "41b79712a4a2d16d39894ae1b38cde5c41dad22eadc560df631d39f13df1e4b9") break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto FX::close() -> void {
|
||||
device.close();
|
||||
}
|
||||
|
||||
auto FX::readable() -> bool {
|
||||
return device.readable();
|
||||
}
|
||||
|
||||
//1000ns delay avoids burning CPU core at 100%; does not slow down max transfer rate at all
|
||||
auto FX::read() -> uint8_t {
|
||||
while(!readable()) usleep(1000);
|
||||
uint8_t buffer[1] = {0};
|
||||
device.read(buffer, 1);
|
||||
return buffer[0];
|
||||
}
|
||||
|
||||
auto FX::writable() -> bool {
|
||||
return device.writable();
|
||||
}
|
||||
|
||||
auto FX::write(uint8_t data) -> void {
|
||||
while(!writable()) usleep(1000);
|
||||
uint8_t buffer[1] = {data};
|
||||
device.write(buffer, 1);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto FX::read(uint offset, uint length) -> vector<uint8_t> {
|
||||
write(0x21);
|
||||
write(0x66);
|
||||
write(0x78);
|
||||
write(offset >> 16);
|
||||
write(offset >> 8);
|
||||
write(offset >> 0);
|
||||
write(0x01);
|
||||
write(length >> 8);
|
||||
write(length >> 0);
|
||||
write(0x00);
|
||||
|
||||
vector<uint8_t> buffer;
|
||||
while(length--) buffer.append(read());
|
||||
return buffer;
|
||||
}
|
||||
|
||||
auto FX::write(uint offset, const vector<uint8_t>& buffer) -> void {
|
||||
auto length = buffer.size();
|
||||
|
||||
write(0x21);
|
||||
write(0x66);
|
||||
write(0x78);
|
||||
write(offset >> 16);
|
||||
write(offset >> 8);
|
||||
write(offset >> 0);
|
||||
write(0x01);
|
||||
write(buffer.size() >> 8);
|
||||
write(buffer.size() >> 0);
|
||||
write(0x01);
|
||||
|
||||
for(auto data : buffer) write(data);
|
||||
write(0x00);
|
||||
}
|
||||
|
||||
auto FX::execute(uint offset) -> void {
|
||||
write(0x21);
|
||||
write(0x66);
|
||||
write(0x78);
|
||||
write(offset >> 16);
|
||||
write(offset >> 8);
|
||||
write(offset >> 0);
|
||||
write(0x00);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto FX::read(uint offset) -> uint8_t {
|
||||
auto buffer = read(offset, 1);
|
||||
return buffer[0];
|
||||
}
|
||||
|
||||
auto FX::write(uint offset, uint8_t data) -> void {
|
||||
vector<uint8_t> buffer = {data};
|
||||
write(offset, buffer);
|
||||
}
|
@ -34,14 +34,14 @@ inline auto Base64(const uint8_t* data, unsigned size, const string& format = "M
|
||||
|
||||
case 1:
|
||||
buffer |= data[i] >> 4;
|
||||
result.last() = lookup[buffer];
|
||||
result.right() = lookup[buffer];
|
||||
buffer = (data[i] & 15) << 2;
|
||||
result.append(lookup[buffer]);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
buffer |= data[i] >> 6;
|
||||
result.last() = lookup[buffer];
|
||||
result.right() = lookup[buffer];
|
||||
buffer = (data[i] & 63);
|
||||
result.append(lookup[buffer]);
|
||||
break;
|
||||
|
@ -2,38 +2,57 @@
|
||||
|
||||
#include <nall/range.hpp>
|
||||
|
||||
namespace nall {
|
||||
struct string;
|
||||
namespace Hash {
|
||||
namespace nall { struct string; }
|
||||
|
||||
namespace nall { namespace Hash {
|
||||
|
||||
struct CRC16 {
|
||||
CRC16() { reset(); }
|
||||
CRC16(const void* values, unsigned size) : CRC16() { data(values, size); }
|
||||
CRC16(const void* values, uint size) : CRC16() { data(values, size); }
|
||||
CRC16(const vector<uint8_t>& values) : CRC16() { data(values); }
|
||||
|
||||
auto reset() -> void {
|
||||
checksum = ~0;
|
||||
}
|
||||
|
||||
auto data(uint8_t value) -> void {
|
||||
for(auto n : range(8)) {
|
||||
if((checksum & 1) ^ (value & 1)) checksum = (checksum >> 1) ^ 0x8408;
|
||||
else checksum >>= 1;
|
||||
value >>= 1;
|
||||
}
|
||||
checksum = (checksum >> 8) ^ table(checksum ^ value);
|
||||
}
|
||||
|
||||
auto data(const void* values, unsigned size) -> void {
|
||||
auto data(const void* values, uint size) -> void {
|
||||
auto p = (const uint8_t*)values;
|
||||
while(size--) data(*p++);
|
||||
}
|
||||
|
||||
auto value() -> uint16_t {
|
||||
auto data(const vector<uint8_t>& values) -> void {
|
||||
for(auto value : values) data(value);
|
||||
}
|
||||
|
||||
auto value() const -> uint16_t {
|
||||
return ~checksum;
|
||||
}
|
||||
|
||||
inline auto digest() -> string;
|
||||
inline auto digest() const -> string;
|
||||
|
||||
private:
|
||||
static auto table(uint8_t index) -> uint16_t {
|
||||
static uint16_t table[256] = {0};
|
||||
static bool initialized = false;
|
||||
|
||||
if(!initialized) {
|
||||
initialized = true;
|
||||
for(auto index : range(256)) {
|
||||
uint16_t crc = index;
|
||||
for(auto bit : range(8)) {
|
||||
crc = (crc >> 1) ^ (crc & 1 ? 0x8408 : 0);
|
||||
}
|
||||
table[index] = crc;
|
||||
}
|
||||
}
|
||||
|
||||
return table[index];
|
||||
}
|
||||
|
||||
uint16_t checksum;
|
||||
};
|
||||
|
||||
|
@ -2,80 +2,57 @@
|
||||
|
||||
#include <nall/range.hpp>
|
||||
|
||||
namespace nall {
|
||||
struct string;
|
||||
namespace Hash {
|
||||
namespace nall { struct string; }
|
||||
|
||||
const uint32_t _crc32_table[256] = {
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
|
||||
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
|
||||
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
|
||||
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
||||
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
|
||||
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
|
||||
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
|
||||
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
||||
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
|
||||
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
||||
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
|
||||
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
||||
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
|
||||
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
||||
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
|
||||
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
||||
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
|
||||
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
|
||||
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
|
||||
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
|
||||
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
|
||||
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
|
||||
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
||||
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
|
||||
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
|
||||
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
|
||||
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
|
||||
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
||||
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
|
||||
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
|
||||
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
|
||||
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
|
||||
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
|
||||
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
||||
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
|
||||
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
|
||||
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
|
||||
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
||||
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
|
||||
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
||||
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
|
||||
};
|
||||
namespace nall { namespace Hash {
|
||||
|
||||
struct CRC32 {
|
||||
CRC32() { reset(); }
|
||||
CRC32(const void* values, unsigned size) : CRC32() { data(values, size); }
|
||||
CRC32(const void* values, uint size) : CRC32() { data(values, size); }
|
||||
CRC32(const vector<uint8_t>& values) : CRC32() { data(values); }
|
||||
|
||||
auto reset() -> void {
|
||||
checksum = ~0;
|
||||
}
|
||||
|
||||
auto data(uint8_t value) -> void {
|
||||
checksum = ((checksum >> 8) & 0xffffff) ^ _crc32_table[(checksum ^ value) & 0xff];
|
||||
checksum = (checksum >> 8) ^ table(checksum ^ value);
|
||||
}
|
||||
|
||||
auto data(const void* values, unsigned size) -> void {
|
||||
auto data(const void* values, uint size) -> void {
|
||||
auto p = (const uint8_t*)values;
|
||||
while(size--) data(*p++);
|
||||
}
|
||||
|
||||
auto data(const vector<uint8_t>& values) -> void {
|
||||
for(auto value : values) data(value);
|
||||
}
|
||||
|
||||
auto value() const -> uint32_t {
|
||||
return ~checksum;
|
||||
}
|
||||
|
||||
inline auto digest() -> string;
|
||||
inline auto digest() const -> string;
|
||||
|
||||
private:
|
||||
static auto table(uint8_t index) -> uint32_t {
|
||||
static uint32_t table[256] = {0};
|
||||
static bool initialized = false;
|
||||
|
||||
if(!initialized) {
|
||||
initialized = true;
|
||||
for(auto index : range(256)) {
|
||||
uint32_t crc = index;
|
||||
for(auto bit : range(8)) {
|
||||
crc = (crc >> 1) ^ (crc & 1 ? 0xedb8'8320 : 0);
|
||||
}
|
||||
table[index] = crc;
|
||||
}
|
||||
}
|
||||
|
||||
return table[index];
|
||||
}
|
||||
|
||||
uint32_t checksum;
|
||||
};
|
||||
|
||||
|
59
nall/hash/crc64.hpp
Normal file
59
nall/hash/crc64.hpp
Normal file
@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <nall/range.hpp>
|
||||
|
||||
namespace nall { struct string; }
|
||||
|
||||
namespace nall { namespace Hash {
|
||||
|
||||
struct CRC64 {
|
||||
CRC64() { reset(); }
|
||||
CRC64(const void* values, uint size) : CRC64() { data(values, size); }
|
||||
CRC64(const vector<uint8_t>& values) : CRC64() { data(values); }
|
||||
|
||||
auto reset() -> void {
|
||||
checksum = ~0;
|
||||
}
|
||||
|
||||
auto data(uint8_t value) -> void {
|
||||
checksum = (checksum >> 8) ^ table(checksum ^ value);
|
||||
}
|
||||
|
||||
auto data(const void* values, uint size) -> void {
|
||||
auto p = (const uint8_t*)values;
|
||||
while(size--) data(*p++);
|
||||
}
|
||||
|
||||
auto data(const vector<uint8_t>& values) -> void {
|
||||
for(auto value : values) data(value);
|
||||
}
|
||||
|
||||
auto value() const -> uint64_t {
|
||||
return ~checksum;
|
||||
}
|
||||
|
||||
inline auto digest() const -> string;
|
||||
|
||||
private:
|
||||
static auto table(uint8_t index) -> uint64_t {
|
||||
static uint64_t table[256] = {0};
|
||||
static bool initialized = false;
|
||||
|
||||
if(!initialized) {
|
||||
initialized = true;
|
||||
for(auto index : range(256)) {
|
||||
uint64_t crc = index;
|
||||
for(auto bit : range(8)) {
|
||||
crc = (crc >> 1) ^ (crc & 1 ? 0xc96c'5795'd787'0f42 : 0);
|
||||
}
|
||||
table[index] = crc;
|
||||
}
|
||||
}
|
||||
|
||||
return table[index];
|
||||
}
|
||||
|
||||
uint64_t checksum;
|
||||
};
|
||||
|
||||
}}
|
@ -2,13 +2,14 @@
|
||||
|
||||
#include <nall/range.hpp>
|
||||
|
||||
namespace nall {
|
||||
struct string;
|
||||
namespace Hash {
|
||||
namespace nall { struct string; }
|
||||
|
||||
namespace nall { namespace Hash {
|
||||
|
||||
struct SHA256 {
|
||||
SHA256() { reset(); }
|
||||
SHA256(const void* values, unsigned size) : SHA256() { data(values, size); }
|
||||
SHA256(const void* values, uint size) : SHA256() { data(values, size); }
|
||||
SHA256(const vector<uint8_t>& values) : SHA256() { data(values); }
|
||||
|
||||
auto reset() -> void {
|
||||
for(auto n : input) n = 0;
|
||||
@ -22,12 +23,16 @@ struct SHA256 {
|
||||
length++;
|
||||
}
|
||||
|
||||
auto data(const void* values, unsigned size) -> void {
|
||||
auto data(const void* values, uint size) -> void {
|
||||
length += size;
|
||||
auto p = (const uint8_t*)values;
|
||||
while(size--) byte(*p++);
|
||||
}
|
||||
|
||||
auto data(const vector<uint8_t>& values) -> void {
|
||||
for(auto value : values) data(value);
|
||||
}
|
||||
|
||||
auto value() const -> vector<uint8_t> {
|
||||
SHA256 self(*this);
|
||||
self.finish();
|
||||
@ -77,14 +82,14 @@ private:
|
||||
return (x >> n) | (x << 32 - n);
|
||||
}
|
||||
|
||||
auto square(unsigned n) -> uint32_t {
|
||||
auto square(uint n) -> uint32_t {
|
||||
static const uint32_t value[8] = {
|
||||
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
||||
};
|
||||
return value[n];
|
||||
}
|
||||
|
||||
auto cube(unsigned n) -> uint32_t {
|
||||
auto cube(uint n) -> uint32_t {
|
||||
static const uint32_t value[64] = {
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
|
@ -20,7 +20,7 @@ struct Group : vector<Input> {
|
||||
|
||||
auto name() const -> string { return _name; }
|
||||
auto input(unsigned id) -> Input& { return operator[](id); }
|
||||
auto append(const string& name) -> void { vector::append({name}); }
|
||||
auto append(const string& name) -> void { vector::append(Input{name}); }
|
||||
|
||||
auto find(const string& name) const -> maybe<unsigned> {
|
||||
for(auto id : range(size())) {
|
||||
@ -51,7 +51,7 @@ struct Device : vector<Group> {
|
||||
auto id() const -> uint64_t { return _id; }
|
||||
auto setID(uint64_t id) -> void { _id = id; }
|
||||
auto group(unsigned id) -> Group& { return operator[](id); }
|
||||
auto append(const string& name) -> void { vector::append({name}); }
|
||||
auto append(const string& name) -> void { vector::append(Group{name}); }
|
||||
|
||||
auto find(const string& name) const -> maybe<unsigned> {
|
||||
for(auto id : range(size())) {
|
||||
|
@ -70,7 +70,7 @@ auto Request::head(const function<bool (const uint8_t*, unsigned)>& callback) co
|
||||
|
||||
auto Request::setHead() -> bool {
|
||||
lstring headers = _head.split("\n");
|
||||
string request = headers.takeFirst().rtrim("\r", 1L);
|
||||
string request = headers.takeLeft().rtrim("\r", 1L);
|
||||
string requestHost;
|
||||
|
||||
if(request.iendsWith(" HTTP/1.0")) request.irtrim(" HTTP/1.0", 1L);
|
||||
@ -146,7 +146,7 @@ auto Request::setBody() -> bool {
|
||||
auto boundary = contentType.iltrim("multipart/form-data; boundary=", 1L).trim("\"", "\"", 1L);
|
||||
auto blocks = _body.split({"--", boundary}, 1024L); //limit blocks to prevent memory exhaustion
|
||||
for(auto& block : blocks) block.trim("\r\n", "\r\n", 1L);
|
||||
if(blocks.size() < 2 || (blocks.takeFirst(), !blocks.takeLast().beginsWith("--"))) return false;
|
||||
if(blocks.size() < 2 || (blocks.takeLeft(), !blocks.takeRight().beginsWith("--"))) return false;
|
||||
for(auto& block : blocks) {
|
||||
string name;
|
||||
string filename;
|
||||
|
@ -88,7 +88,7 @@ auto Response::head(const function<bool (const uint8_t*, unsigned)>& callback) c
|
||||
|
||||
auto Response::setHead() -> bool {
|
||||
lstring headers = _head.split("\n");
|
||||
string response = headers.takeFirst().rtrim("\r");
|
||||
string response = headers.takeLeft().rtrim("\r");
|
||||
|
||||
if(response.ibeginsWith("HTTP/1.0 ")) response.iltrim("HTTP/1.0 ", 1L);
|
||||
else if(response.ibeginsWith("HTTP/1.1 ")) response.iltrim("HTTP/1.1 ", 1L);
|
||||
|
@ -1,3 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <nall/range.hpp>
|
||||
#include <nall/stdint.hpp>
|
||||
#include <nall/memory/memory.hpp>
|
||||
|
@ -64,6 +64,7 @@
|
||||
#include <nall/encode/url.hpp>
|
||||
#include <nall/hash/crc16.hpp>
|
||||
#include <nall/hash/crc32.hpp>
|
||||
#include <nall/hash/crc64.hpp>
|
||||
#include <nall/hash/sha256.hpp>
|
||||
|
||||
#if defined(PLATFORM_WINDOWS)
|
||||
|
@ -39,12 +39,4 @@ inline auto rrange(int size) {
|
||||
return range_t{size - 1, -1, -1};
|
||||
}
|
||||
|
||||
template<typename T> inline auto range(const vector<T>& container) {
|
||||
return range_t{0, (int)container.size(), 1};
|
||||
}
|
||||
|
||||
template<typename T> inline auto rrange(const vector<T>& container) {
|
||||
return range_t{(int)container.size() - 1, -1, -1};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,17 +16,12 @@
|
||||
namespace nall {
|
||||
|
||||
struct serial {
|
||||
serial() {
|
||||
port = -1;
|
||||
port_open = false;
|
||||
}
|
||||
|
||||
~serial() {
|
||||
close();
|
||||
}
|
||||
|
||||
auto readable() -> bool {
|
||||
if(port_open == false) return false;
|
||||
if(!opened) return false;
|
||||
fd_set fdset;
|
||||
FD_ZERO(&fdset);
|
||||
FD_SET(port, &fdset);
|
||||
@ -40,12 +35,12 @@ struct serial {
|
||||
|
||||
//-1 on error, otherwise return bytes read
|
||||
auto read(uint8_t* data, uint length) -> int {
|
||||
if(port_open == false) return -1;
|
||||
if(!opened) return -1;
|
||||
return ::read(port, (void*)data, length);
|
||||
}
|
||||
|
||||
auto writable() -> bool {
|
||||
if(port_open == false) return false;
|
||||
if(!opened) return false;
|
||||
fd_set fdset;
|
||||
FD_ZERO(&fdset);
|
||||
FD_SET(port, &fdset);
|
||||
@ -59,14 +54,17 @@ struct serial {
|
||||
|
||||
//-1 on error, otherwise return bytes written
|
||||
auto write(const uint8_t* data, uint length) -> int {
|
||||
if(port_open == false) return -1;
|
||||
if(!opened) return -1;
|
||||
return ::write(port, (void*)data, length);
|
||||
}
|
||||
|
||||
auto open(const string& portname, uint rate, bool flowcontrol) -> bool {
|
||||
//rate==0: use flow control (synchronous mode)
|
||||
//rate!=0: baud-rate (asynchronous mode)
|
||||
auto open(string device, uint rate = 0) -> bool {
|
||||
close();
|
||||
|
||||
port = ::open(portname, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);
|
||||
if(!device) device = "/dev/ttyU0"; //note: default device name is for FreeBSD 10+
|
||||
port = ::open(device, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);
|
||||
if(port == -1) return false;
|
||||
|
||||
if(ioctl(port, TIOCEXCL) == -1) { close(); return false; }
|
||||
@ -75,7 +73,7 @@ struct serial {
|
||||
|
||||
termios attr = original_attr;
|
||||
cfmakeraw(&attr);
|
||||
cfsetspeed(&attr, rate);
|
||||
cfsetspeed(&attr, rate ? rate : 57600); //rate value has no effect in synchronous mode
|
||||
|
||||
attr.c_lflag &=~ (ECHO | ECHONL | ISIG | ICANON | IEXTEN);
|
||||
attr.c_iflag &=~ (BRKINT | PARMRK | INPCK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY);
|
||||
@ -83,7 +81,7 @@ struct serial {
|
||||
attr.c_oflag &=~ (OPOST);
|
||||
attr.c_cflag &=~ (CSIZE | CSTOPB | PARENB | CLOCAL);
|
||||
attr.c_cflag |= (CS8 | CREAD);
|
||||
if(flowcontrol == false) {
|
||||
if(rate) {
|
||||
attr.c_cflag &= ~CRTSCTS;
|
||||
} else {
|
||||
attr.c_cflag |= CRTSCTS;
|
||||
@ -91,15 +89,15 @@ struct serial {
|
||||
attr.c_cc[VTIME] = attr.c_cc[VMIN] = 0;
|
||||
|
||||
if(tcsetattr(port, TCSANOW, &attr) == -1) { close(); return false; }
|
||||
return port_open = true;
|
||||
return opened = true;
|
||||
}
|
||||
|
||||
auto close() -> void {
|
||||
if(port != -1) {
|
||||
tcdrain(port);
|
||||
if(port_open == true) {
|
||||
if(opened) {
|
||||
tcsetattr(port, TCSANOW, &original_attr);
|
||||
port_open = false;
|
||||
opened = false;
|
||||
}
|
||||
::close(port);
|
||||
port = -1;
|
||||
@ -107,8 +105,8 @@ struct serial {
|
||||
}
|
||||
|
||||
private:
|
||||
int port;
|
||||
bool port_open;
|
||||
int port = -1;
|
||||
bool opened = false;
|
||||
termios original_attr;
|
||||
};
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <nall/vector.hpp>
|
||||
#include <nall/hash/crc16.hpp>
|
||||
#include <nall/hash/crc32.hpp>
|
||||
#include <nall/hash/crc64.hpp>
|
||||
#include <nall/hash/sha256.hpp>
|
||||
|
||||
#include <nall/string/base.hpp>
|
||||
|
@ -32,6 +32,7 @@ inline auto real(long double value) -> string;
|
||||
//hash.hpp
|
||||
inline auto crc16(rstring self) -> string;
|
||||
inline auto crc32(rstring self) -> string;
|
||||
inline auto crc64(rstring self) -> string;
|
||||
inline auto sha256(rstring self) -> string;
|
||||
|
||||
//match.hpp
|
||||
|
@ -45,7 +45,7 @@ inline auto parse(Node*& node, const char*& s, uint depth) -> void {
|
||||
while(whitespace(s[0])) s++;
|
||||
if(!s[0]) return;
|
||||
|
||||
if(s[0] == '(' && node->link.empty()) {
|
||||
if(s[0] == '(' && !node->link) {
|
||||
parse(node, s += 1, 1);
|
||||
if(*s++ != ')') throw "mismatched group";
|
||||
}
|
||||
@ -55,7 +55,7 @@ inline auto parse(Node*& node, const char*& s, uint depth) -> void {
|
||||
node->literal = literal(s);
|
||||
}
|
||||
|
||||
#define p() (node->literal.empty() && node->link.empty())
|
||||
#define p() (!node->literal && !node->link)
|
||||
while(true) {
|
||||
while(whitespace(s[0])) s++;
|
||||
if(!s[0]) return;
|
||||
|
@ -3,14 +3,18 @@
|
||||
namespace nall {
|
||||
|
||||
namespace Hash {
|
||||
auto CRC16::digest() -> string {
|
||||
auto CRC16::digest() const -> string {
|
||||
return hex(value(), 4L);
|
||||
}
|
||||
|
||||
auto CRC32::digest() -> string {
|
||||
auto CRC32::digest() const -> string {
|
||||
return hex(value(), 8L);
|
||||
}
|
||||
|
||||
auto CRC64::digest() const -> string {
|
||||
return hex(value(), 16L);
|
||||
}
|
||||
|
||||
auto SHA256::digest() const -> string {
|
||||
string result;
|
||||
for(auto n : value()) result.append(hex(n, 2L));
|
||||
@ -26,6 +30,10 @@ auto crc32(rstring self) -> string {
|
||||
return Hash::CRC32(self.data(), self.size()).digest();
|
||||
}
|
||||
|
||||
auto crc64(rstring self) -> string {
|
||||
return Hash::CRC64(self.data(), self.size()).digest();
|
||||
}
|
||||
|
||||
auto sha256(rstring self) -> string {
|
||||
return Hash::SHA256(self.data(), self.size()).digest();
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ auto lstring::operator!=(const lstring& source) const -> bool {
|
||||
}
|
||||
|
||||
auto lstring::isort() -> lstring& {
|
||||
nall::sort(pool, objectsize, [](const string& x, const string& y) {
|
||||
sort([](const string& x, const string& y) {
|
||||
return memory::icompare(x.data(), x.size(), y.data(), y.size()) < 0;
|
||||
});
|
||||
return *this;
|
||||
|
@ -119,13 +119,13 @@ auto ManagedNode::_create(const string& path) -> Node {
|
||||
}
|
||||
}
|
||||
_children.append(new ManagedNode(name));
|
||||
return _children.last()->_create(slice(path, *position + 1));
|
||||
return _children.right()->_create(slice(path, *position + 1));
|
||||
}
|
||||
for(auto& node : _children) {
|
||||
if(path == node->_name) return node;
|
||||
}
|
||||
_children.append(new ManagedNode(path));
|
||||
return _children.last();
|
||||
return _children.right();
|
||||
}
|
||||
|
||||
}}
|
||||
|
@ -55,7 +55,7 @@ auto CML::parseDocument(const string& filedata, const string& pathname, uint dep
|
||||
|
||||
for(auto& block : filedata.split("\n\n")) {
|
||||
lstring lines = block.rstrip().split("\n");
|
||||
string name = lines.takeFirst();
|
||||
string name = lines.takeLeft();
|
||||
|
||||
if(name.beginsWith("include ")) {
|
||||
name.ltrim("include ", 1L);
|
||||
|
@ -85,7 +85,7 @@ auto DML::parseBlock(string& block, const string& pathname, uint depth) -> bool
|
||||
if(state.sections++) state.output.append("</section>");
|
||||
state.output.append("<section>");
|
||||
}
|
||||
auto content = lines.takeFirst().ltrim("# ", 1L).split(" => ", 1L);
|
||||
auto content = lines.takeLeft().ltrim("# ", 1L).split(" => ", 1L);
|
||||
auto data = markup(content[0]);
|
||||
auto name = escape(content(1, crc32(data)));
|
||||
state.output.append("<header id=\"", name, "\">", data);
|
||||
@ -98,7 +98,7 @@ auto DML::parseBlock(string& block, const string& pathname, uint depth) -> bool
|
||||
|
||||
//header
|
||||
else if(auto depth = count(block, '=')) {
|
||||
auto content = slice(lines.takeFirst(), depth + 1).split(" => ", 1L);
|
||||
auto content = slice(lines.takeLeft(), depth + 1).split(" => ", 1L);
|
||||
auto data = markup(content[0]);
|
||||
auto name = escape(content(1, crc32(data)));
|
||||
if(depth <= 6) {
|
||||
|
359
nall/vector.hpp
359
nall/vector.hpp
@ -1,282 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <initializer_list>
|
||||
#include <new>
|
||||
#include <utility>
|
||||
#include <nall/algorithm.hpp>
|
||||
|
||||
#include <nall/bit.hpp>
|
||||
#include <nall/function.hpp>
|
||||
#include <nall/maybe.hpp>
|
||||
#include <nall/memory.hpp>
|
||||
#include <nall/range.hpp>
|
||||
#include <nall/sort.hpp>
|
||||
#include <nall/utility.hpp>
|
||||
#include <nall/traits.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
template<typename T> struct vector {
|
||||
struct exception_out_of_bounds{};
|
||||
template<typename T> struct vector_iterator;
|
||||
template<typename T> struct vector_iterator_const;
|
||||
|
||||
explicit operator bool() const { return objectsize; }
|
||||
auto data() -> T* { return pool + poolbase; }
|
||||
auto data() const -> const T* { return pool + poolbase; }
|
||||
|
||||
auto empty() const -> bool { return objectsize == 0; }
|
||||
auto size() const -> unsigned { return objectsize; }
|
||||
auto capacity() const -> unsigned { return poolsize; }
|
||||
|
||||
auto release() -> T* {
|
||||
T* result = pool + poolbase;
|
||||
pool = nullptr;
|
||||
poolbase = 0;
|
||||
poolsize = 0;
|
||||
objectsize = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
if(pool) {
|
||||
for(unsigned n = 0; n < objectsize; n++) pool[poolbase + n].~T();
|
||||
memory::free(pool);
|
||||
}
|
||||
pool = nullptr;
|
||||
poolbase = 0;
|
||||
poolsize = 0;
|
||||
objectsize = 0;
|
||||
}
|
||||
|
||||
auto reserve(unsigned size) -> void {
|
||||
if(size <= poolsize) return;
|
||||
size = bit::round(size); //amortize growth
|
||||
|
||||
T* copy = (T*)memory::allocate(size * sizeof(T));
|
||||
for(unsigned n = 0; n < objectsize; n++) new(copy + n) T(move(pool[poolbase + n]));
|
||||
free(pool);
|
||||
pool = copy;
|
||||
poolbase = 0;
|
||||
poolsize = size;
|
||||
}
|
||||
|
||||
auto resize(unsigned size, T value = T()) -> void {
|
||||
T* copy = (T*)memory::allocate(size * sizeof(T));
|
||||
for(unsigned n = 0; n < size && n < objectsize; n++) new(copy + n) T(move(pool[poolbase + n]));
|
||||
for(unsigned n = objectsize; n < size; n++) new(copy + n) T(value);
|
||||
reset();
|
||||
pool = copy;
|
||||
poolbase = 0;
|
||||
poolsize = size;
|
||||
objectsize = size;
|
||||
}
|
||||
|
||||
auto reallocate(unsigned size, T value = T()) -> void {
|
||||
reset();
|
||||
resize(size, value);
|
||||
}
|
||||
|
||||
template<typename... Args> auto prepend(const T& data, Args&&... args) -> void {
|
||||
prepend(forward<Args>(args)...);
|
||||
prepend(data);
|
||||
}
|
||||
|
||||
auto prepend(const T& data) -> T& {
|
||||
reserve(objectsize + 1);
|
||||
if(poolbase == 0) {
|
||||
unsigned available = poolsize - objectsize;
|
||||
poolbase = max(1u, available >> 1);
|
||||
for(signed n = objectsize - 1; n >= 0; n--) {
|
||||
pool[poolbase + n] = move(pool[n]);
|
||||
}
|
||||
}
|
||||
new(pool + --poolbase) T(data);
|
||||
objectsize++;
|
||||
return first();
|
||||
}
|
||||
|
||||
template<typename... Args> auto append(const T& data, Args&&... args) -> void {
|
||||
append(data);
|
||||
append(forward<Args>(args)...);
|
||||
}
|
||||
|
||||
auto append(const T& data) -> T& {
|
||||
reserve(poolbase + objectsize + 1);
|
||||
new(pool + poolbase + objectsize++) T(data);
|
||||
return last();
|
||||
}
|
||||
|
||||
auto appendOnce(const T& data) -> bool {
|
||||
if(find(data)) return false;
|
||||
return append(data), true;
|
||||
}
|
||||
|
||||
auto insert(unsigned position, const T& data) -> void {
|
||||
if(position == 0) {
|
||||
prepend(data);
|
||||
return;
|
||||
}
|
||||
append(data);
|
||||
if(position == ~0u) return;
|
||||
for(signed n = objectsize - 1; n > position; n--) {
|
||||
pool[poolbase + n] = move(pool[poolbase + n - 1]);
|
||||
}
|
||||
pool[poolbase + position] = data;
|
||||
}
|
||||
|
||||
auto remove(unsigned position = ~0u, unsigned length = 1) -> void {
|
||||
if(position == ~0u) position = objectsize - 1;
|
||||
if(position + length > objectsize) throw exception_out_of_bounds{};
|
||||
|
||||
if(position == 0) {
|
||||
for(unsigned n = 0; n < length; n++) pool[poolbase + n].~T();
|
||||
poolbase += length;
|
||||
} else {
|
||||
for(unsigned n = position; n < objectsize; n++) {
|
||||
if(n + length < objectsize) {
|
||||
pool[poolbase + n] = move(pool[poolbase + n + length]);
|
||||
} else {
|
||||
pool[poolbase + n].~T();
|
||||
}
|
||||
}
|
||||
}
|
||||
objectsize -= length;
|
||||
}
|
||||
|
||||
auto removeFirst() -> void { return remove(0); }
|
||||
auto removeLast() -> void { return remove(~0u); }
|
||||
|
||||
auto take(unsigned position = ~0u) -> T {
|
||||
if(position == ~0u) position = objectsize - 1;
|
||||
T object = pool[poolbase + position];
|
||||
remove(position);
|
||||
return object;
|
||||
}
|
||||
|
||||
auto takeFirst() -> T { return take(0); }
|
||||
auto takeLast() -> T { return take(~0u); }
|
||||
|
||||
auto reverse() -> void {
|
||||
unsigned pivot = size() / 2;
|
||||
for(unsigned l = 0, r = size() - 1; l < pivot; l++, r--) {
|
||||
swap(pool[poolbase + l], pool[poolbase + r]);
|
||||
}
|
||||
}
|
||||
|
||||
auto sort(const function<bool (const T& lhs, const T& rhs)>& comparator = [](const T& lhs, const T& rhs) -> bool {
|
||||
return lhs < rhs;
|
||||
}) -> void {
|
||||
nall::sort(pool + poolbase, objectsize, comparator);
|
||||
}
|
||||
|
||||
auto find(const T& data) const -> maybe<unsigned> {
|
||||
for(unsigned n = 0; n < objectsize; n++) if(pool[poolbase + n] == data) return n;
|
||||
return nothing;
|
||||
}
|
||||
|
||||
auto first() -> T& {
|
||||
if(objectsize == 0) throw exception_out_of_bounds();
|
||||
return pool[poolbase];
|
||||
}
|
||||
|
||||
auto first() const -> const T& {
|
||||
if(objectsize == 0) throw exception_out_of_bounds();
|
||||
return pool[poolbase];
|
||||
}
|
||||
|
||||
auto last() -> T& {
|
||||
if(objectsize == 0) throw exception_out_of_bounds();
|
||||
return pool[poolbase + objectsize - 1];
|
||||
}
|
||||
|
||||
auto last() const -> const T& {
|
||||
if(objectsize == 0) throw exception_out_of_bounds();
|
||||
return pool[poolbase + objectsize - 1];
|
||||
}
|
||||
|
||||
//access
|
||||
inline auto operator[](unsigned position) -> T& {
|
||||
if(position >= objectsize) throw exception_out_of_bounds();
|
||||
return pool[poolbase + position];
|
||||
}
|
||||
|
||||
inline auto operator[](unsigned position) const -> const T& {
|
||||
if(position >= objectsize) throw exception_out_of_bounds();
|
||||
return pool[poolbase + position];
|
||||
}
|
||||
|
||||
inline auto operator()(unsigned position) -> T& {
|
||||
if(position >= poolsize) reserve(position + 1);
|
||||
while(position >= objectsize) append(T());
|
||||
return pool[poolbase + position];
|
||||
}
|
||||
|
||||
inline auto operator()(unsigned position, const T& data) const -> const T& {
|
||||
if(position >= objectsize) return data;
|
||||
return pool[poolbase + position];
|
||||
}
|
||||
|
||||
//iteration
|
||||
struct iterator {
|
||||
iterator(vector& source, unsigned position) : source(source), position(position) {}
|
||||
auto operator*() -> T& { return source.operator[](position); }
|
||||
auto operator!=(const iterator& source) const -> bool { return position != source.position; }
|
||||
auto operator++() -> iterator& { position++; return *this; }
|
||||
|
||||
private:
|
||||
vector& source;
|
||||
unsigned position;
|
||||
};
|
||||
|
||||
auto begin() -> iterator { return iterator(*this, 0); }
|
||||
auto end() -> iterator { return iterator(*this, size()); }
|
||||
|
||||
struct constIterator {
|
||||
constIterator(const vector& source, unsigned position) : source(source), position(position) {}
|
||||
auto operator*() const -> const T& { return source.operator[](position); }
|
||||
auto operator!=(const constIterator& source) const -> bool { return position != source.position; }
|
||||
auto operator++() -> constIterator& { position++; return *this; }
|
||||
|
||||
private:
|
||||
const vector& source;
|
||||
unsigned position;
|
||||
};
|
||||
|
||||
auto begin() const -> const constIterator { return constIterator(*this, 0); }
|
||||
auto end() const -> const constIterator { return constIterator(*this, size()); }
|
||||
|
||||
//copy
|
||||
inline auto operator=(const vector& source) -> vector& {
|
||||
if(this == &source) return *this;
|
||||
reset();
|
||||
reserve(source.size());
|
||||
for(auto& data : source) append(data);
|
||||
return *this;
|
||||
}
|
||||
|
||||
//move
|
||||
inline auto operator=(vector&& source) -> vector& {
|
||||
if(this == &source) return *this;
|
||||
reset();
|
||||
pool = source.pool;
|
||||
poolbase = source.poolbase;
|
||||
poolsize = source.poolsize;
|
||||
objectsize = source.objectsize;
|
||||
source.pool = nullptr;
|
||||
source.poolbase = 0;
|
||||
source.poolsize = 0;
|
||||
source.objectsize = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
//construction and destruction
|
||||
template<typename T>
|
||||
struct vector {
|
||||
//core.hpp
|
||||
vector() = default;
|
||||
vector(initializer_list<T> list) { for(auto& data : list) append(data); }
|
||||
vector(const vector& source) { operator=(source); }
|
||||
vector(vector&& source) { operator=(move(source)); }
|
||||
~vector() { reset(); }
|
||||
vector(const initializer_list<T>& values);
|
||||
vector(const vector& source);
|
||||
vector(vector&& source);
|
||||
~vector();
|
||||
|
||||
protected:
|
||||
T* pool = nullptr;
|
||||
unsigned poolbase = 0;
|
||||
unsigned poolsize = 0;
|
||||
unsigned objectsize = 0;
|
||||
explicit operator bool() const;
|
||||
auto capacity() const -> uint;
|
||||
auto size() const -> uint;
|
||||
auto data() -> T*;
|
||||
auto data() const -> const T*;
|
||||
|
||||
//assign.hpp
|
||||
auto operator=(const vector& source) -> vector&;
|
||||
auto operator=(vector&& source) -> vector&;
|
||||
|
||||
//memory.hpp
|
||||
auto reset() -> void;
|
||||
auto release() -> T*;
|
||||
|
||||
auto reserveLeft(uint capacity) -> bool;
|
||||
auto reserveRight(uint capacity) -> bool;
|
||||
auto reserve(uint capacity) -> bool { return reserveRight(capacity); }
|
||||
|
||||
auto resizeLeft(uint size, const T& value = T()) -> bool;
|
||||
auto resizeRight(uint size, const T& value = T()) -> bool;
|
||||
auto resize(uint size, const T& value = T()) -> bool { return resizeRight(size, value); }
|
||||
|
||||
//access.hpp
|
||||
alwaysinline auto operator[](uint offset) -> T&;
|
||||
alwaysinline auto operator[](uint offset) const -> const T&;
|
||||
|
||||
alwaysinline auto operator()(uint offset) -> T&;
|
||||
alwaysinline auto operator()(uint offset, const T& value) const -> const T&;
|
||||
|
||||
alwaysinline auto left() -> T&;
|
||||
alwaysinline auto left() const -> const T&;
|
||||
|
||||
alwaysinline auto right() -> T&;
|
||||
alwaysinline auto right() const -> const T&;
|
||||
|
||||
//modify.hpp
|
||||
auto prepend(const T& value) -> void;
|
||||
auto prepend(T&& value) -> void;
|
||||
auto prepend(const vector<T>& values) -> void;
|
||||
auto prepend(vector<T>&& values) -> void;
|
||||
|
||||
auto append(const T& value) -> void;
|
||||
auto append(T&& value) -> void;
|
||||
auto append(const vector<T>& values) -> void;
|
||||
auto append(vector<T>&& values) -> void;
|
||||
|
||||
auto insert(uint offset, const T& value) -> void;
|
||||
|
||||
auto removeLeft(uint length = 1) -> void;
|
||||
auto removeRight(uint length = 1) -> void;
|
||||
auto remove(uint offset, uint length = 1) -> void;
|
||||
|
||||
auto takeLeft() -> T;
|
||||
auto takeRight() -> T;
|
||||
auto take(uint offset) -> T;
|
||||
|
||||
//iterator.hpp
|
||||
auto begin() { return vector_iterator<T>{*this, 0}; }
|
||||
auto end() { return vector_iterator<T>{*this, size()}; }
|
||||
|
||||
auto begin() const { return vector_iterator_const<T>{*this, 0}; }
|
||||
auto end() const { return vector_iterator_const<T>{*this, size()}; }
|
||||
|
||||
//utility.hpp
|
||||
auto sort(const function<bool (const T& lhs, const T& rhs)>& comparator = {}) -> void;
|
||||
auto find(const T& value) const -> maybe<uint>;
|
||||
|
||||
private:
|
||||
T* _pool = nullptr; //pointer to first initialized element in pool
|
||||
uint _size = 0; //number of initialized elements in pool
|
||||
uint _left = 0; //number of allocated elements free on the left of pool
|
||||
uint _right = 0; //number of allocated elements free on the right of pool
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#include <nall/vector/core.hpp>
|
||||
#include <nall/vector/assign.hpp>
|
||||
#include <nall/vector/memory.hpp>
|
||||
#include <nall/vector/access.hpp>
|
||||
#include <nall/vector/modify.hpp>
|
||||
#include <nall/vector/iterator.hpp>
|
||||
#include <nall/vector/utility.hpp>
|
||||
|
39
nall/vector/access.hpp
Normal file
39
nall/vector/access.hpp
Normal file
@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
|
||||
template<typename T> auto vector<T>::operator[](uint offset) -> T& {
|
||||
return _pool[offset];
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::operator[](uint offset) const -> const T& {
|
||||
return _pool[offset];
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::operator()(uint offset) -> T& {
|
||||
while(offset >= size()) append(T());
|
||||
return _pool[offset];
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::operator()(uint offset, const T& value) const -> const T& {
|
||||
if(offset >= size()) return value;
|
||||
return _pool[offset];
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::left() -> T& {
|
||||
return _pool[0];
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::left() const -> const T& {
|
||||
return _pool[0];
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::right() -> T& {
|
||||
return _pool[_size - 1];
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::right() const -> const T& {
|
||||
return _pool[_size - 1];
|
||||
}
|
||||
|
||||
}
|
28
nall/vector/assign.hpp
Normal file
28
nall/vector/assign.hpp
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
|
||||
template<typename T> auto vector<T>::operator=(const vector<T>& source) -> vector<T>& {
|
||||
if(this == &source) return *this;
|
||||
_pool = (T*)memory::allocate(sizeof(T) * source._size);
|
||||
_size = source._size;
|
||||
_left = 0;
|
||||
_right = 0;
|
||||
for(uint n : range(_size)) new(_pool + n) T(source._pool[n]);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::operator=(vector<T>&& source) -> vector<T>& {
|
||||
if(this == &source) return *this;
|
||||
_pool = source._pool;
|
||||
_size = source._size;
|
||||
_left = source._left;
|
||||
_right = source._right;
|
||||
source._pool = nullptr;
|
||||
source._size = 0;
|
||||
source._left = 0;
|
||||
source._right = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
}
|
42
nall/vector/core.hpp
Normal file
42
nall/vector/core.hpp
Normal file
@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
|
||||
template<typename T> vector<T>::vector(const initializer_list<T>& values) {
|
||||
reserveRight(values.size());
|
||||
for(auto& value : values) append(value);
|
||||
}
|
||||
|
||||
template<typename T> vector<T>::vector(const vector<T>& source) {
|
||||
operator=(source);
|
||||
}
|
||||
|
||||
template<typename T> vector<T>::vector(vector<T>&& source) {
|
||||
operator=(move(source));
|
||||
}
|
||||
|
||||
template<typename T> vector<T>::~vector() {
|
||||
reset();
|
||||
}
|
||||
|
||||
template<typename T> vector<T>::operator bool() const {
|
||||
return _size;
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::capacity() const -> uint {
|
||||
return _left + _size + _right;
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::size() const -> uint {
|
||||
return _size;
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::data() -> T* {
|
||||
return _pool;
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::data() const -> const T* {
|
||||
return _pool;
|
||||
}
|
||||
|
||||
}
|
37
nall/vector/iterator.hpp
Normal file
37
nall/vector/iterator.hpp
Normal file
@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
|
||||
template<typename T>
|
||||
struct vector_iterator {
|
||||
vector_iterator(vector<T>& self, uint offset) : self(self), offset(offset) {}
|
||||
auto operator*() -> T& { return self.operator[](offset); }
|
||||
auto operator!=(const vector_iterator& source) const -> bool { return offset != source.offset; }
|
||||
auto operator++() -> vector_iterator& { return offset++, *this; }
|
||||
|
||||
private:
|
||||
vector<T>& self;
|
||||
uint offset;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct vector_iterator_const {
|
||||
vector_iterator_const(const vector<T>& self, uint offset) : self(self), offset(offset) {}
|
||||
auto operator*() -> const T& { return self.operator[](offset); }
|
||||
auto operator!=(const vector_iterator_const& source) const -> bool { return offset != source.offset; }
|
||||
auto operator++() -> vector_iterator_const& { return offset++, *this; }
|
||||
|
||||
private:
|
||||
const vector<T>& self;
|
||||
uint offset;
|
||||
};
|
||||
|
||||
template<typename T> inline auto range(const vector<T>& container) {
|
||||
return range_t{0, (int)container.size(), 1};
|
||||
}
|
||||
|
||||
template<typename T> inline auto rrange(const vector<T>& container) {
|
||||
return range_t{(int)container.size() - 1, -1, -1};
|
||||
}
|
||||
|
||||
}
|
90
nall/vector/memory.hpp
Normal file
90
nall/vector/memory.hpp
Normal file
@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
|
||||
template<typename T> auto vector<T>::reset() -> void {
|
||||
if(!_pool) return;
|
||||
|
||||
for(uint n : range(_size)) _pool[n].~T();
|
||||
memory::free(_pool - _left);
|
||||
|
||||
_pool = nullptr;
|
||||
_size = 0;
|
||||
_left = 0;
|
||||
_right = 0;
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::release() -> T* {
|
||||
auto pool = _pool;
|
||||
_pool = nullptr;
|
||||
_size = 0;
|
||||
_left = 0;
|
||||
_right = 0;
|
||||
return pool;
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::reserveLeft(uint capacity) -> bool {
|
||||
if(_size + _left >= capacity) return false;
|
||||
|
||||
uint left = bit::round(capacity);
|
||||
auto pool = (T*)memory::allocate(sizeof(T) * (left + _right)) + left;
|
||||
for(uint n : range(_size)) new(pool + n) T(move(_pool[n]));
|
||||
memory::free(_pool - _left);
|
||||
|
||||
_pool = pool;
|
||||
_left = left - _size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::reserveRight(uint capacity) -> bool {
|
||||
if(_size + _right >= capacity) return false;
|
||||
|
||||
uint right = bit::round(capacity);
|
||||
auto pool = (T*)memory::allocate(sizeof(T) * (_left + right)) + _left;
|
||||
for(uint n : range(_size)) new(pool + n) T(move(_pool[n]));
|
||||
memory::free(_pool - _left);
|
||||
|
||||
_pool = pool;
|
||||
_right = right - _size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::resizeLeft(uint size, const T& value) -> bool {
|
||||
if(size < _size) { //shrink
|
||||
for(uint n : range(_size - size)) _pool[n].~T();
|
||||
_pool += _size - size;
|
||||
_left += _size - size;
|
||||
_size = size;
|
||||
return true;
|
||||
}
|
||||
if(size > _size) { //grow
|
||||
reserveLeft(size);
|
||||
_pool -= size - _size;
|
||||
for(uint n : rrange(size - _size)) new(_pool + n) T(value);
|
||||
_left -= size - _size;
|
||||
_size = size;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::resizeRight(uint size, const T& value) -> bool {
|
||||
if(size < _size) { //shrink
|
||||
for(uint n = size; n < _size; n++) _pool[n].~T();
|
||||
_right += _size - size;
|
||||
_size = size;
|
||||
return true;
|
||||
}
|
||||
if(size > _size) { //grow
|
||||
reserveRight(size);
|
||||
for(uint n = _size; n < size; n++) new(_pool + n) T(value);
|
||||
_right -= size - _size;
|
||||
_size = size;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
127
nall/vector/modify.hpp
Normal file
127
nall/vector/modify.hpp
Normal file
@ -0,0 +1,127 @@
|
||||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
|
||||
template<typename T> auto vector<T>::prepend(const T& value) -> void {
|
||||
reserveLeft(size() + 1);
|
||||
new(--_pool) T(value);
|
||||
_left--;
|
||||
_size++;
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::prepend(T&& value) -> void {
|
||||
reserveLeft(size() + 1);
|
||||
new(--_pool) T(move(value));
|
||||
_left--;
|
||||
_size++;
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::prepend(const vector<T>& values) -> void {
|
||||
reserveLeft(size() + values.size());
|
||||
_pool -= values.size();
|
||||
for(uint n : range(values)) new(_pool + n) T(values[n]);
|
||||
_left -= values.size();
|
||||
_size += values.size();
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::prepend(vector<T>&& values) -> void {
|
||||
reserveLeft(size() + values.size());
|
||||
_pool -= values.size();
|
||||
for(uint n : range(values)) new(_pool + n) T(move(values[n]));
|
||||
_left -= values.size();
|
||||
_size += values.size();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
template<typename T> auto vector<T>::append(const T& value) -> void {
|
||||
reserveRight(size() + 1);
|
||||
new(_pool + _size) T(value);
|
||||
_right--;
|
||||
_size++;
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::append(T&& value) -> void {
|
||||
reserveRight(size() + 1);
|
||||
new(_pool + _size) T(move(value));
|
||||
_right--;
|
||||
_size++;
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::append(const vector<T>& values) -> void {
|
||||
reserveRight(size() + values.size());
|
||||
for(uint n : range(values)) new(_pool + _size + n) T(values[n]);
|
||||
_right -= values.size();
|
||||
_size += values.size();
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::append(vector<T>&& values) -> void {
|
||||
reserveRight(size() + values.size());
|
||||
for(uint n : range(values)) new(_pool + _size + n) T(move(values[n]));
|
||||
_right -= values.size();
|
||||
_size += values.size();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
template<typename T> auto vector<T>::insert(uint offset, const T& value) -> void {
|
||||
if(offset == 0) return prepend(value);
|
||||
if(offset == size() - 1) return append(value);
|
||||
reserveRight(size() + 1);
|
||||
_size++;
|
||||
for(int n = size() - 1; n > offset; n--) {
|
||||
_pool[n] = move(_pool[n - 1]);
|
||||
}
|
||||
new(_pool + offset) T(value);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
template<typename T> auto vector<T>::removeLeft(uint length) -> void {
|
||||
if(length > size()) length = size();
|
||||
resizeLeft(size() - length);
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::removeRight(uint length) -> void {
|
||||
if(length > size()) length = size();
|
||||
resizeRight(size() - length);
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::remove(uint offset, uint length) -> void {
|
||||
if(offset == 0) return removeLeft(length);
|
||||
if(offset == size() - 1) return removeRight(length);
|
||||
|
||||
for(uint n = offset; n < size(); n++) {
|
||||
if(n + length < size()) {
|
||||
_pool[n] = move(_pool[n + length]);
|
||||
} else {
|
||||
_pool[n].~T();
|
||||
}
|
||||
}
|
||||
_size -= length;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
template<typename T> auto vector<T>::takeLeft() -> T {
|
||||
T value = move(_pool[0]);
|
||||
removeLeft();
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::takeRight() -> T {
|
||||
T value = move(_pool[size() - 1]);
|
||||
removeRight();
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::take(uint offset) -> T {
|
||||
if(offset == 0) return takeLeft();
|
||||
if(offset == size() - 1) return takeRight();
|
||||
|
||||
T value = move(_pool[offset]);
|
||||
remove(offset);
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
15
nall/vector/utility.hpp
Normal file
15
nall/vector/utility.hpp
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
|
||||
template<typename T> auto vector<T>::sort(const function<bool (const T& lhs, const T& rhs)>& comparator) -> void {
|
||||
if(!comparator) return nall::sort(_pool, _size, [](const T& lhs, const T& rhs) { return lhs < rhs; });
|
||||
nall::sort(_pool, _size, comparator);
|
||||
}
|
||||
|
||||
template<typename T> auto vector<T>::find(const T& value) const -> maybe<uint> {
|
||||
for(uint n : range(size())) if(_pool[n] == value) return n;
|
||||
return nothing;
|
||||
}
|
||||
|
||||
}
|
@ -182,7 +182,6 @@ struct VideoGLX : Video, OpenGL {
|
||||
|
||||
//glXSwapInterval is used to toggle Vsync
|
||||
//note that the ordering is very important! MESA declares SGI, but the SGI function does nothing
|
||||
glXSwapInterval = (signed (*)(signed))glGetProcAddress("glXSwapIntervalEXT");
|
||||
if(!glXSwapInterval) glXSwapInterval = (signed (*)(signed))glGetProcAddress("glXSwapIntervalMESA");
|
||||
if(!glXSwapInterval) glXSwapInterval = (signed (*)(signed))glGetProcAddress("glXSwapIntervalSGI");
|
||||
|
||||
|
@ -163,7 +163,6 @@ struct VideoGLX2 : Video {
|
||||
glxcontext = glXCreateContext(display, vi, 0, GL_TRUE);
|
||||
glXMakeCurrent(display, glxwindow = xwindow, glxcontext);
|
||||
|
||||
glXSwapInterval = (signed (*)(signed))glGetProcAddress("glXSwapIntervalEXT");
|
||||
if(!glXSwapInterval) glXSwapInterval = (signed (*)(signed))glGetProcAddress("glXSwapIntervalMESA");
|
||||
if(!glXSwapInterval) glXSwapInterval = (signed (*)(signed))glGetProcAddress("glXSwapIntervalSGI");
|
||||
|
||||
|
@ -167,7 +167,7 @@ auto OpenGL::refresh() -> void {
|
||||
render(sources[0].width, sources[0].height, outputWidth, outputHeight);
|
||||
|
||||
if(history.size() > 0) {
|
||||
OpenGLTexture frame = history.takeLast();
|
||||
OpenGLTexture frame = history.takeRight();
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, frame.texture);
|
||||
if(width == frame.width && height == frame.height) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user