diff --git a/bsnes/Makefile b/bsnes/Makefile index 8f9b1cce..1d0f84ab 100755 --- a/bsnes/Makefile +++ b/bsnes/Makefile @@ -1,9 +1,15 @@ include nall/Makefile + snes := snes gameboy := gameboy -profile := accuracy -ui := ui -# phoenix := gtk + +ifeq ($(profile),) + profile := accuracy +endif + +ifeq ($(ui),) + ui := ui +endif # options += debugger @@ -14,12 +20,15 @@ flags := -O3 -fomit-frame-pointer -I. link := objects := libco -# profile-guided instrumentation -# flags += -fprofile-generate -# link += -lgcov - -# profile-guided optimization -# flags += -fprofile-use +# profile-guided optimization mode +# pgo := instrument +# pgo := optimize +ifeq ($(pgo),instrument) + flags += -fprofile-generate + link += -lgcov +else ifeq ($(pgo),optimize) + flags += -fprofile-use +endif flags := $(flags) $(foreach o,$(call strupper,$(options)),-D$o) diff --git a/bsnes/gameboy/apu/noise/noise.cpp b/bsnes/gameboy/apu/noise/noise.cpp index 36d38bf5..9d8a5646 100755 --- a/bsnes/gameboy/apu/noise/noise.cpp +++ b/bsnes/gameboy/apu/noise/noise.cpp @@ -5,8 +5,7 @@ void APU::Noise::run() { period = divisor << frequency; if(frequency < 14) { bool bit = (lfsr ^ (lfsr >> 1)) & 1; - lfsr = (lfsr >> 1) ^ (bit << 14); - if(narrow_lfsr) lfsr |= (bit << 6); + lfsr = (lfsr >> 1) ^ (bit << (narrow_lfsr ? 6 : 14)); } } diff --git a/bsnes/gameboy/gameboy.hpp b/bsnes/gameboy/gameboy.hpp index 2e0a31f7..994dc896 100755 --- a/bsnes/gameboy/gameboy.hpp +++ b/bsnes/gameboy/gameboy.hpp @@ -5,8 +5,8 @@ namespace GameBoy { namespace Info { static const char Name[] = "bgameboy"; - static const char Version[] = "000.19"; - static unsigned SerializerVersion = 1; + static const char Version[] = "000.20"; + static unsigned SerializerVersion = 2; } } diff --git a/bsnes/gameboy/lcd/lcd.cpp b/bsnes/gameboy/lcd/lcd.cpp index c0644152..65b16863 100755 --- a/bsnes/gameboy/lcd/lcd.cpp +++ b/bsnes/gameboy/lcd/lcd.cpp @@ -18,6 +18,10 @@ void LCD::main() { } add_clocks(4); + if(status.display_enable == false) continue; + + status.lx += 4; + if(status.lx >= 456) scanline(); if(status.lx == 0) { if(status.interrupt_oam) cpu.interrupt_raise(CPU::Interrupt::Stat); @@ -30,9 +34,6 @@ void LCD::main() { } void LCD::add_clocks(unsigned clocks) { - status.lx += clocks; - if(status.lx >= 456) scanline(); - clock += clocks; if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) { co_switch(scheduler.active_thread = cpu.thread); @@ -65,7 +66,10 @@ void LCD::frame() { } void LCD::render() { - for(unsigned n = 0; n < 160; n++) line[n] = 0x00; + for(unsigned n = 0; n < 160; n++) { + line[n] = 0x00; + origin[n] = Origin::None; + } if(status.display_enable == true) { if(status.bg_enable == true) render_bg(); @@ -98,7 +102,9 @@ void LCD::render_bg() { for(unsigned ox = 0; ox < 160; ox++) { uint8 palette = ((data & (0x0080 >> tx)) ? 1 : 0) | ((data & (0x8000 >> tx)) ? 2 : 0); + line[ox] = status.bgp[palette]; + origin[ox] = Origin::BG; ix = (ix + 1) & 255; tx = (tx + 1) & 7; @@ -110,13 +116,16 @@ void LCD::render_bg() { void LCD::render_window() { if(status.ly - status.wy >= 144U) return; unsigned iy = status.ly - status.wy; - unsigned ix = (status.wx - 7) & 255, tx = ix & 7; + unsigned ix = (7 - status.wx) & 255, tx = ix & 7; unsigned data = read_tile(status.window_tilemap_select, ix, iy); for(unsigned ox = 0; ox < 160; ox++) { uint8 palette = ((data & (0x0080 >> tx)) ? 1 : 0) | ((data & (0x8000 >> tx)) ? 2 : 0); - if(ox - (status.wx - 7) < 160U) line[ox] = status.bgp[palette]; + if(ox - (status.wx - 7) < 160U) { + line[ox] = status.bgp[palette]; + origin[ox] = Origin::Window; + } ix = (ix + 1) & 255; tx = (tx + 1) & 7; @@ -126,6 +135,8 @@ void LCD::render_window() { } void LCD::render_obj() { + enum : unsigned { Priority = 0x80, YFlip = 0x40, XFlip = 0x20, Palette = 0x10 }; + unsigned obj_size = (status.obj_size == 0 ? 8 : 16); unsigned sprite[10], sprites = 0; @@ -165,26 +176,29 @@ void LCD::render_obj() { sy = status.ly - sy; if(sy >= obj_size) continue; - if(attribute & 0x40) sy ^= (obj_size - 1); + if(attribute & YFlip) sy ^= (obj_size - 1); unsigned tdaddr = (tile << 4) + (sy << 1); uint8 d0 = vram[tdaddr + 0]; uint8 d1 = vram[tdaddr + 1]; - unsigned xflip = attribute & 0x20 ? 7 : 0; + unsigned xflip = attribute & XFlip ? 7 : 0; for(unsigned tx = 0; tx < 8; tx++) { uint8 palette = ((d0 & (0x80 >> tx)) ? 1 : 0) | ((d1 & (0x80 >> tx)) ? 2 : 0); if(palette == 0) continue; - palette = status.obp[(bool)(attribute & 0x10)][palette]; + palette = status.obp[(bool)(attribute & Palette)][palette]; unsigned ox = sx + (tx ^ xflip); if(ox <= 159) { - if(attribute & 0x80) { - if(line[ox] > 0) continue; + if(attribute & Priority) { + if(origin[ox] == Origin::BG || origin[ox] == Origin::Window) { + if(line[ox] > 0) continue; + } } line[ox] = palette; + origin[ox] = Origin::OBJ; } } } diff --git a/bsnes/gameboy/lcd/lcd.hpp b/bsnes/gameboy/lcd/lcd.hpp index 8692d9c4..9bbe5c1a 100755 --- a/bsnes/gameboy/lcd/lcd.hpp +++ b/bsnes/gameboy/lcd/lcd.hpp @@ -51,6 +51,9 @@ struct LCD : Processor, MMIO { uint8 oam[160]; uint8 line[160]; + struct Origin { enum : unsigned { None, BG, Window, OBJ }; }; + uint8 origin[160]; + static void Main(); void main(); void add_clocks(unsigned clocks); diff --git a/bsnes/gameboy/lcd/mmio/mmio.cpp b/bsnes/gameboy/lcd/mmio/mmio.cpp index 9bf01b61..592177a0 100755 --- a/bsnes/gameboy/lcd/mmio/mmio.cpp +++ b/bsnes/gameboy/lcd/mmio/mmio.cpp @@ -17,10 +17,11 @@ uint8 LCD::mmio_read(uint16 addr) { if(addr == 0xff41) { //STAT unsigned mode; - if(status.ly >= 144) mode = 1; //Vblank - else if(status.lx < 80) mode = 2; //OAM + if(status.display_enable == false) mode = 1; //force blank + else if(status.ly >= 144) mode = 1; //Vblank + else if(status.lx < 80) mode = 2; //OAM else if(status.lx < 252) mode = 3; //LCD - else mode = 0; //Hblank + else mode = 0; //Hblank return (status.interrupt_lyc << 6) | (status.interrupt_oam << 5) diff --git a/bsnes/gameboy/lcd/serialization.cpp b/bsnes/gameboy/lcd/serialization.cpp index 1274fdd9..ba85443e 100755 --- a/bsnes/gameboy/lcd/serialization.cpp +++ b/bsnes/gameboy/lcd/serialization.cpp @@ -33,6 +33,7 @@ void LCD::serialize(serializer &s) { s.array(vram); s.array(oam); s.array(line); + s.array(origin); } #endif diff --git a/bsnes/nall/bps/delta.hpp b/bsnes/nall/bps/delta.hpp new file mode 100755 index 00000000..53bae28c --- /dev/null +++ b/bsnes/nall/bps/delta.hpp @@ -0,0 +1,206 @@ +#ifndef NALL_BPS_DELTA_HPP +#define NALL_BPS_DELTA_HPP + +#include +#include +#include +#include +#include + +namespace nall { + +struct bpsdelta { + inline bool source(const string &filename); + inline bool target(const string &filename); + inline bool create(const string &filename, const string &metadata = ""); + +protected: + enum : unsigned { SourceRead, TargetRead, SourceCopy, TargetCopy }; + enum : unsigned { Granularity = 1 }; + + struct Node { + unsigned offset; + Node *next; + inline Node() : offset(0), next(0) {} + inline ~Node() { if(next) delete next; } + }; + + filemap sourceFile; + const uint8_t *sourceData; + unsigned sourceSize; + + filemap targetFile; + const uint8_t *targetData; + unsigned targetSize; +}; + +bool bpsdelta::source(const string &filename) { + if(sourceFile.open(filename, filemap::mode::read) == false) return false; + sourceData = sourceFile.data(); + sourceSize = sourceFile.size(); + return true; +} + +bool bpsdelta::target(const string &filename) { + if(targetFile.open(filename, filemap::mode::read) == false) return false; + targetData = targetFile.data(); + targetSize = targetFile.size(); + return true; +} + +bool bpsdelta::create(const string &filename, const string &metadata) { + file modifyFile; + if(modifyFile.open(filename, file::mode::write) == false) return false; + + uint32_t sourceChecksum = ~0, modifyChecksum = ~0; + unsigned sourceRelativeOffset = 0, targetRelativeOffset = 0, outputOffset = 0; + + auto write = [&](uint8_t data) { + modifyFile.write(data); + modifyChecksum = crc32_adjust(modifyChecksum, data); + }; + + auto encode = [&](uint64_t data) { + while(true) { + uint64_t x = data & 0x7f; + data >>= 7; + if(data == 0) { + write(0x80 | x); + break; + } + write(x); + data--; + } + }; + + write('B'); + write('P'); + write('S'); + write('1'); + + encode(sourceSize); + encode(targetSize); + + unsigned markupSize = metadata.length(); + encode(markupSize); + for(unsigned n = 0; n < markupSize; n++) write(metadata[n]); + + Node *sourceTree[65536]; + Node *targetTree[65536]; + + for(unsigned n = 0; n < 65536; n++) sourceTree[n] = 0; + for(unsigned n = 0; n < 65536; n++) targetTree[n] = 0; + + //source tree creation + for(unsigned offset = 0; offset < sourceSize; offset++) { + uint16_t symbol = sourceData[offset + 0]; + sourceChecksum = crc32_adjust(sourceChecksum, symbol); + if(offset < sourceSize - 1) symbol |= sourceData[offset + 1] << 8; + Node *node = new Node; + node->offset = offset; + node->next = sourceTree[symbol]; + sourceTree[symbol] = node; + } + + unsigned targetReadLength = 0; + + auto targetReadFlush = [&]() { + if(targetReadLength) { + encode(TargetRead | ((targetReadLength - 1) << 2)); + unsigned offset = outputOffset - targetReadLength; + while(targetReadLength) write(targetData[offset++]), targetReadLength--; + } + }; + + while(outputOffset < targetSize) { + unsigned maxLength = 0, maxOffset = 0, mode = TargetRead; + + uint16_t symbol = targetData[outputOffset + 0]; + if(outputOffset < targetSize - 1) symbol |= targetData[outputOffset + 1] << 8; + + { //source copy + Node *node = sourceTree[symbol]; + while(node) { + unsigned length = 0, x = node->offset, y = outputOffset; + while(x < sourceSize && y < targetSize && sourceData[x++] == targetData[y++]) length++; + if(length > maxLength) maxLength = length, maxOffset = node->offset, mode = SourceCopy; + node = node->next; + } + } + + { //target copy + Node *node = targetTree[symbol]; + while(node) { + unsigned length = 0, x = node->offset, y = outputOffset; + while(y < targetSize && targetData[x++] == targetData[y++]) length++; + if(length > maxLength) maxLength = length, maxOffset = node->offset, mode = TargetCopy; + node = node->next; + } + + //target tree append + node = new Node; + node->offset = outputOffset; + node->next = targetTree[symbol]; + targetTree[symbol] = node; + } + + { //source read + unsigned length = 0, offset = outputOffset; + while(offset < sourceSize && offset < targetSize && sourceData[offset] == targetData[offset]) { + length++; + offset++; + } + if(length > maxLength) maxLength = length, mode = SourceRead; + } + + { //target read + if(maxLength < 4) { + maxLength = min((unsigned)Granularity, targetSize - outputOffset); + mode = TargetRead; + } + } + + if(mode != TargetRead) targetReadFlush(); + + switch(mode) { + case SourceRead: + encode(SourceRead | ((maxLength - 1) << 2)); + break; + case TargetRead: + //delay write to group sequential TargetRead commands into one + targetReadLength += maxLength; + break; + case SourceCopy: + case TargetCopy: + encode(mode | ((maxLength - 1) << 2)); + signed relativeOffset; + if(mode == SourceCopy) { + relativeOffset = maxOffset - sourceRelativeOffset; + sourceRelativeOffset = maxOffset + maxLength; + } else { + relativeOffset = maxOffset - targetRelativeOffset; + targetRelativeOffset = maxOffset + maxLength; + } + encode((relativeOffset < 0) | (abs(relativeOffset) << 1)); + break; + } + + outputOffset += maxLength; + } + + targetReadFlush(); + + sourceChecksum = ~sourceChecksum; + for(unsigned n = 0; n < 32; n += 8) write(sourceChecksum >> n); + uint32_t targetChecksum = crc32_calculate(targetData, targetSize); + for(unsigned n = 0; n < 32; n += 8) write(targetChecksum >> n); + uint32_t outputChecksum = ~modifyChecksum; + for(unsigned n = 0; n < 32; n += 8) write(outputChecksum >> n); + + modifyFile.close(); + return true; +} + +} + +#endif diff --git a/bsnes/nall/bps/linear.hpp b/bsnes/nall/bps/linear.hpp new file mode 100755 index 00000000..c22df39a --- /dev/null +++ b/bsnes/nall/bps/linear.hpp @@ -0,0 +1,141 @@ +#ifndef NALL_BPS_LINEAR_HPP +#define NALL_BPS_LINEAR_HPP + +#include +#include +#include +#include +#include + +namespace nall { + +struct bpslinear { + inline bool source(const string &filename); + inline bool target(const string &filename); + inline bool create(const string &filename, const string &metadata = ""); + +protected: + enum : unsigned { SourceRead, TargetRead, SourceCopy, TargetCopy }; + enum : unsigned { Granularity = 1 }; + + filemap sourceFile; + const uint8_t *sourceData; + unsigned sourceSize; + + filemap targetFile; + const uint8_t *targetData; + unsigned targetSize; +}; + +bool bpslinear::source(const string &filename) { + if(sourceFile.open(filename, filemap::mode::read) == false) return false; + sourceData = sourceFile.data(); + sourceSize = sourceFile.size(); + return true; +} + +bool bpslinear::target(const string &filename) { + if(targetFile.open(filename, filemap::mode::read) == false) return false; + targetData = targetFile.data(); + targetSize = targetFile.size(); + return true; +} + +bool bpslinear::create(const string &filename, const string &metadata) { + file modifyFile; + if(modifyFile.open(filename, file::mode::write) == false) return false; + + uint32_t modifyChecksum = ~0; + unsigned targetRelativeOffset = 0, outputOffset = 0; + + auto write = [&](uint8_t data) { + modifyFile.write(data); + modifyChecksum = crc32_adjust(modifyChecksum, data); + }; + + auto encode = [&](uint64_t data) { + while(true) { + uint64_t x = data & 0x7f; + data >>= 7; + if(data == 0) { + write(0x80 | x); + break; + } + write(x); + data--; + } + }; + + unsigned targetReadLength = 0; + + auto targetReadFlush = [&]() { + if(targetReadLength) { + encode(TargetRead | ((targetReadLength - 1) << 2)); + unsigned offset = outputOffset - targetReadLength; + while(targetReadLength) write(targetData[offset++]), targetReadLength--; + } + }; + + write('B'); + write('P'); + write('S'); + write('1'); + + encode(sourceSize); + encode(targetSize); + + unsigned markupSize = metadata.length(); + encode(markupSize); + for(unsigned n = 0; n < markupSize; n++) write(metadata[n]); + + while(outputOffset < targetSize) { + unsigned sourceLength = 0; + for(unsigned n = 0; outputOffset + n < sourceSize; n++) { + if(sourceData[outputOffset + n] != targetData[outputOffset + n]) break; + sourceLength++; + } + + unsigned rleLength = 0; + for(unsigned n = 1; outputOffset + n < targetSize; n++) { + if(targetData[outputOffset] != targetData[outputOffset + n]) break; + rleLength++; + } + + if(rleLength >= 4) { + //write byte to repeat + targetReadLength++; + outputOffset++; + targetReadFlush(); + + //copy starting from repetition byte + encode(TargetCopy | ((rleLength - 1) << 2)); + unsigned relativeOffset = (outputOffset - 1) - targetRelativeOffset; + encode(relativeOffset << 1); + outputOffset += rleLength; + targetRelativeOffset = outputOffset - 1; + } else if(sourceLength >= 4) { + targetReadFlush(); + encode(SourceRead | ((sourceLength - 1) << 2)); + outputOffset += sourceLength; + } else { + targetReadLength += Granularity; + outputOffset += Granularity; + } + } + + targetReadFlush(); + + uint32_t sourceChecksum = crc32_calculate(sourceData, sourceSize); + for(unsigned n = 0; n < 32; n += 8) write(sourceChecksum >> n); + uint32_t targetChecksum = crc32_calculate(targetData, targetSize); + for(unsigned n = 0; n < 32; n += 8) write(targetChecksum >> n); + uint32_t outputChecksum = ~modifyChecksum; + for(unsigned n = 0; n < 32; n += 8) write(outputChecksum >> n); + + modifyFile.close(); + return true; +} + +} + +#endif diff --git a/bsnes/nall/bps/patch.hpp b/bsnes/nall/bps/patch.hpp new file mode 100755 index 00000000..e24eba90 --- /dev/null +++ b/bsnes/nall/bps/patch.hpp @@ -0,0 +1,213 @@ +#ifndef NALL_BPS_PATCH_HPP +#define NALL_BPS_PATCH_HPP + +#include +#include +#include +#include +#include + +namespace nall { + +struct bpspatch { + inline bool modify(const uint8_t *data, unsigned size); + inline void source(const uint8_t *data, unsigned size); + inline void target(uint8_t *data, unsigned size); + + inline bool modify(const string &filename); + inline bool source(const string &filename); + inline bool target(const string &filename); + + inline unsigned size() const; + + enum result : unsigned { + unknown, + success, + patch_too_small, + patch_invalid_header, + source_too_small, + target_too_small, + source_checksum_invalid, + target_checksum_invalid, + patch_checksum_invalid, + }; + + inline result apply(); + +protected: + enum : unsigned { SourceRead, TargetRead, SourceCopy, TargetCopy }; + + filemap modifyFile; + const uint8_t *modifyData; + unsigned modifySize; + + filemap sourceFile; + const uint8_t *sourceData; + unsigned sourceSize; + + filemap targetFile; + uint8_t *targetData; + unsigned targetSize; + +public: + unsigned modifySourceSize; + unsigned modifyTargetSize; + unsigned modifyMarkupSize; + string metadata; +}; + +bool bpspatch::modify(const uint8_t *data, unsigned size) { + if(size < 19) return false; + modifyData = data; + modifySize = size; + + unsigned offset = 4; + auto decode = [&]() -> uint64_t { + uint64_t data = 0, shift = 1; + while(true) { + uint8_t x = modifyData[offset++]; + data += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + data += shift; + } + return data; + }; + + modifySourceSize = decode(); + modifyTargetSize = decode(); + modifyMarkupSize = decode(); + return true; +} + +void bpspatch::source(const uint8_t *data, unsigned size) { + sourceData = data; + sourceSize = size; +} + +void bpspatch::target(uint8_t *data, unsigned size) { + targetData = data; + targetSize = size; +} + +bool bpspatch::modify(const string &filename) { + if(modifyFile.open(filename, filemap::mode::read) == false) return false; + return modify(modifyFile.data(), modifyFile.size()); +} + +bool bpspatch::source(const string &filename) { + if(sourceFile.open(filename, filemap::mode::read) == false) return false; + source(sourceFile.data(), sourceFile.size()); + return true; +} + +bool bpspatch::target(const string &filename) { + file fp; + if(fp.open(filename, file::mode::write) == false) return false; + fp.truncate(modifyTargetSize); + fp.close(); + + if(targetFile.open(filename, filemap::mode::readwrite) == false) return false; + target(targetFile.data(), targetFile.size()); + return true; +} + +unsigned bpspatch::size() const { + return modifyTargetSize; +} + +bpspatch::result bpspatch::apply() { + if(modifySize < 19) return result::patch_too_small; + + uint32_t modifyChecksum = ~0, targetChecksum = ~0; + unsigned modifyOffset = 0, sourceRelativeOffset = 0, targetRelativeOffset = 0, outputOffset = 0; + + auto read = [&]() -> uint8_t { + uint8_t data = modifyData[modifyOffset++]; + modifyChecksum = crc32_adjust(modifyChecksum, data); + return data; + }; + + auto decode = [&]() -> uint64_t { + uint64_t data = 0, shift = 1; + while(true) { + uint8_t x = read(); + data += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + data += shift; + } + return data; + }; + + auto write = [&](uint8_t data) { + targetData[outputOffset++] = data; + targetChecksum = crc32_adjust(targetChecksum, data); + }; + + if(read() != 'B') return result::patch_invalid_header; + if(read() != 'P') return result::patch_invalid_header; + if(read() != 'S') return result::patch_invalid_header; + if(read() != '1') return result::patch_invalid_header; + + modifySourceSize = decode(); + modifyTargetSize = decode(); + + modifyMarkupSize = decode(); + char data[modifyMarkupSize + 1]; + for(unsigned n = 0; n < modifyMarkupSize; n++) data[n] = read(); + data[modifyMarkupSize] = 0; + metadata = (const char*)data; + + if(modifySourceSize > sourceSize) return result::source_too_small; + if(modifyTargetSize > targetSize) return result::target_too_small; + + while(modifyOffset < modifySize - 12) { + unsigned length = decode(); + unsigned mode = length & 3; + length = (length >> 2) + 1; + + switch(mode) { + case SourceRead: + while(length--) write(sourceData[outputOffset]); + break; + case TargetRead: + while(length--) write(read()); + break; + case SourceCopy: + case TargetCopy: + signed offset = decode(); + bool negative = offset & 1; + offset >>= 1; + if(negative) offset = -offset; + + if(mode == SourceCopy) { + sourceRelativeOffset += offset; + while(length--) write(sourceData[sourceRelativeOffset++]); + } else { + targetRelativeOffset += offset; + while(length--) write(targetData[targetRelativeOffset++]); + } + break; + } + } + + uint32_t modifySourceChecksum = 0, modifyTargetChecksum = 0, modifyModifyChecksum = 0; + for(unsigned n = 0; n < 32; n += 8) modifySourceChecksum |= read() << n; + for(unsigned n = 0; n < 32; n += 8) modifyTargetChecksum |= read() << n; + uint32_t checksum = ~modifyChecksum; + for(unsigned n = 0; n < 32; n += 8) modifyModifyChecksum |= read() << n; + + uint32_t sourceChecksum = crc32_calculate(sourceData, modifySourceSize); + targetChecksum = ~targetChecksum; + + if(sourceChecksum != modifySourceChecksum) return result::source_checksum_invalid; + if(targetChecksum != modifyTargetChecksum) return result::target_checksum_invalid; + if(checksum != modifyModifyChecksum) return result::patch_checksum_invalid; + + return result::success; +} + +} + +#endif diff --git a/bsnes/nall/lzss.hpp b/bsnes/nall/lzss.hpp index 46b8497f..147e1e62 100755 --- a/bsnes/nall/lzss.hpp +++ b/bsnes/nall/lzss.hpp @@ -1,80 +1,165 @@ #ifndef NALL_LZSS_HPP #define NALL_LZSS_HPP -#include +#include +#include #include +#include namespace nall { - class lzss { - public: - static bool encode(uint8_t *&output, unsigned &outlength, const uint8_t *input, unsigned inlength) { - output = new uint8_t[inlength * 9 / 8 + 9](); - unsigned i = 0, o = 0; - while(i < inlength) { - unsigned flagoffset = o++; - uint8_t flag = 0x00; +//19:5 pulldown +//8:1 marker: d7-d0 +//length: { 4 - 35 }, offset: { 1 - 0x80000 } +//4-byte file size header +//little-endian encoding +struct lzss { + inline void source(const uint8_t *data, unsigned size); + inline bool source(const string &filename); + inline unsigned size() const; + inline bool compress(const string &filename); + inline bool decompress(uint8_t *targetData, unsigned targetSize); + inline bool decompress(const string &filename); - for(unsigned b = 0; b < 8 && i < inlength; b++) { - unsigned longest = 0, pointer; - for(unsigned index = 1; index < 4096; index++) { - unsigned count = 0; - while(true) { - if(count >= 15 + 3) break; //verify pattern match is not longer than max length - if(i + count >= inlength) break; //verify pattern match does not read past end of input - if(i + count < index) break; //verify read is not before start of input - if(input[i + count] != input[i + count - index]) break; //verify pattern still matches - count++; - } +protected: + struct Node { + unsigned offset; + Node *next; + inline Node() : offset(0), next(0) {} + inline ~Node() { if(next) delete next; } + } *tree[65536]; - if(count > longest) { - longest = count; - pointer = index; - } - } + filemap sourceFile; + const uint8_t *sourceData; + unsigned sourceSize; - if(longest < 3) output[o++] = input[i++]; - else { - flag |= 1 << b; - uint16_t x = ((longest - 3) << 12) + pointer; - output[o++] = x; - output[o++] = x >> 8; - i += longest; - } +public: + inline lzss() : sourceData(0), sourceSize(0) {} +}; + +void lzss::source(const uint8_t *data, unsigned size) { + sourceData = data; + sourceSize = size; +} + +bool lzss::source(const string &filename) { + if(sourceFile.open(filename, filemap::mode::read) == false) return false; + sourceData = sourceFile.data(); + sourceSize = sourceFile.size(); + return true; +} + +unsigned lzss::size() const { + unsigned size = 0; + if(sourceSize < 4) return size; + for(unsigned n = 0; n < 32; n += 8) size |= sourceData[n >> 3] << n; + return size; +} + +bool lzss::compress(const string &filename) { + file targetFile; + if(targetFile.open(filename, file::mode::write) == false) return false; + + for(unsigned n = 0; n < 32; n += 8) targetFile.write(sourceSize >> n); + for(unsigned n = 0; n < 65536; n++) tree[n] = 0; + + uint8_t buffer[25]; + unsigned sourceOffset = 0; + + while(sourceOffset < sourceSize) { + uint8_t mask = 0x00; + unsigned bufferOffset = 1; + + for(unsigned iteration = 0; iteration < 8; iteration++) { + if(sourceOffset >= sourceSize) break; + + uint16_t symbol = sourceData[sourceOffset + 0]; + if(sourceOffset < sourceSize - 1) symbol |= sourceData[sourceOffset + 1] << 8; + Node *node = tree[symbol]; + unsigned maxLength = 0, maxOffset = 0; + + while(node) { + if(node->offset < sourceOffset - 0x80000) { + //out-of-range: all subsequent nodes will also be, so free up their memory + if(node->next) { delete node->next; node->next = 0; } + break; } - output[flagoffset] = flag; + unsigned length = 0, x = sourceOffset, y = node->offset; + while(length < 35 && x < sourceSize && sourceData[x++] == sourceData[y++]) length++; + if(length > maxLength) maxLength = length, maxOffset = node->offset; + if(length == 35) break; + + node = node->next; } - outlength = o; - return true; - } + //attach current symbol to top of tree for subsequent searches + node = new Node; + node->offset = sourceOffset; + node->next = tree[symbol]; + tree[symbol] = node; - static bool decode(uint8_t *&output, const uint8_t *input, unsigned length) { - output = new uint8_t[length](); - - unsigned i = 0, o = 0; - while(o < length) { - uint8_t flag = input[i++]; - - for(unsigned b = 0; b < 8 && o < length; b++) { - if(!(flag & (1 << b))) output[o++] = input[i++]; - else { - uint16_t offset = input[i++]; - offset += input[i++] << 8; - uint16_t lookuplength = (offset >> 12) + 3; - offset &= 4095; - for(unsigned index = 0; index < lookuplength && o + index < length; index++) { - output[o + index] = output[o + index - offset]; - } - o += lookuplength; - } - } + if(maxLength < 4) { + buffer[bufferOffset++] = sourceData[sourceOffset++]; + } else { + unsigned output = ((maxLength - 4) << 19) | (sourceOffset - 1 - maxOffset); + for(unsigned n = 0; n < 24; n += 8) buffer[bufferOffset++] = output >> n; + mask |= 0x80 >> iteration; + sourceOffset += maxLength; } - - return true; } - }; + + buffer[0] = mask; + targetFile.write(buffer, bufferOffset); + } + + sourceFile.close(); + targetFile.close(); + return true; +} + +bool lzss::decompress(uint8_t *targetData, unsigned targetSize) { + if(targetSize < size()) return false; + + unsigned sourceOffset = 4, targetOffset = 0; + while(sourceOffset < sourceSize) { + uint8_t mask = sourceData[sourceOffset++]; + + for(unsigned iteration = 0; iteration < 8; iteration++) { + if(sourceOffset >= sourceSize) break; + + if((mask & (0x80 >> iteration)) == 0) { + targetData[targetOffset++] = sourceData[sourceOffset++]; + } else { + unsigned code = 0; + for(unsigned n = 0; n < 24; n += 8) code |= sourceData[sourceOffset++] << n; + unsigned length = (code >> 19) + 4; + unsigned offset = targetOffset - 1 - (code & 0x7ffff); + while(length--) targetData[targetOffset++] = targetData[offset++]; + } + } + } +} + +bool lzss::decompress(const string &filename) { + if(sourceSize < 4) return false; + unsigned targetSize = size(); + + file fp; + if(fp.open(filename, file::mode::write) == false) return false; + fp.truncate(targetSize); + fp.close(); + + filemap targetFile; + if(targetFile.open(filename, filemap::mode::readwrite) == false) return false; + uint8_t *targetData = targetFile.data(); + + bool result = decompress(targetData, targetSize); + sourceFile.close(); + targetFile.close(); + return result; +} + } #endif diff --git a/bsnes/snes/snes.hpp b/bsnes/snes/snes.hpp index 85fe968c..699d65c1 100755 --- a/bsnes/snes/snes.hpp +++ b/bsnes/snes/snes.hpp @@ -1,7 +1,7 @@ namespace SNES { namespace Info { static const char Name[] = "bsnes"; - static const char Version[] = "081.02"; + static const char Version[] = "081.03"; static const unsigned SerializerVersion = 21; } } diff --git a/bsnes/ui-gameboy/base.hpp b/bsnes/ui-gameboy/base.hpp index 34892c9e..0cf51c0b 100755 --- a/bsnes/ui-gameboy/base.hpp +++ b/bsnes/ui-gameboy/base.hpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -29,4 +30,5 @@ struct Application { void main(int argc, char **argv); }; +extern nall::dsp dspaudio; extern Application application; diff --git a/bsnes/ui-gameboy/interface.cpp b/bsnes/ui-gameboy/interface.cpp index 1b1d27b8..77c13052 100755 --- a/bsnes/ui-gameboy/interface.cpp +++ b/bsnes/ui-gameboy/interface.cpp @@ -28,8 +28,13 @@ void Interface::video_refresh(const uint8_t *data) { } } -void Interface::audio_sample(int16_t center, int16_t left, int16_t right) { - audio.sample(left, right); +void Interface::audio_sample(int16_t center, int16_t lchannel, int16_t rchannel) { + dspaudio.sample(lchannel, rchannel); + while(dspaudio.pending()) { + signed lsample, rsample; + dspaudio.read(lsample, rsample); + audio.sample(lsample, rsample); + } } void Interface::input_poll() { diff --git a/bsnes/ui-gameboy/main.cpp b/bsnes/ui-gameboy/main.cpp index 0a43a0a5..5224d28e 100755 --- a/bsnes/ui-gameboy/main.cpp +++ b/bsnes/ui-gameboy/main.cpp @@ -1,4 +1,5 @@ #include "base.hpp" +nall::dsp dspaudio; Application application; #include "interface.cpp" @@ -51,13 +52,16 @@ void Application::main(int argc, char **argv) { #endif audio.set(Audio::Handle, (uintptr_t)mainWindow.viewport.handle()); audio.set(Audio::Synchronize, true); - audio.set(Audio::Volume, 100U); - audio.set(Audio::Latency, 80U); - audio.set(Audio::Frequency, 44100U); - audio.set(Audio::Resample, true); - audio.set(Audio::ResampleRatio, 4194304.0 / 44100.0); + audio.set(Audio::Latency, 80u); + audio.set(Audio::Frequency, 44100u); audio.init(); + dspaudio.set_precision(16); + dspaudio.set_volume(1.0); + dspaudio.set_balance(0.0); + dspaudio.set_frequency(4194304.0); + dspaudio.set_resampler_frequency(44100.0); + #if defined(PLATFORM_WIN) input.driver("RawInput"); #else diff --git a/bsnes/ui/base.hpp b/bsnes/ui/base.hpp index a86c1408..12e13dc4 100755 --- a/bsnes/ui/base.hpp +++ b/bsnes/ui/base.hpp @@ -4,12 +4,13 @@ #include #include #include +#include #include #include #include #include #include -#include +#include #include #include using namespace nall; diff --git a/bsnes/ui/cartridge/cartridge.cpp b/bsnes/ui/cartridge/cartridge.cpp index 21633afe..70904179 100755 --- a/bsnes/ui/cartridge/cartridge.cpp +++ b/bsnes/ui/cartridge/cartridge.cpp @@ -110,18 +110,24 @@ bool Cartridge::loadCartridge(SNES::MappedRAM &memory, string &XML, const char * fp.read(data, size); fp.close(); - filemap patch(string(nall::basename(filename), ".ups"), filemap::mode::read); - if(patch.open()) { - unsigned targetSize; - ups patcher; - if(patcher.apply(patch.data(), patch.size(), data, size, (uint8_t*)0, targetSize) == ups::result::target_too_small) { - uint8_t *targetData = new uint8_t[targetSize]; - if(patcher.apply(patch.data(), patch.size(), data, size, targetData, targetSize) == ups::result::success) { - delete[] data; - data = targetData; - size = targetSize; - patchApplied = true; - } + string patchName = { nall::basename(filename), ".bps" }; + if(file::exists(patchName)) { + bpspatch patch; + patch.modify(patchName); + + unsigned targetSize = patch.size(); + uint8_t *targetData = new uint8_t[targetSize]; + + patch.source(data, size); + patch.target(targetData, targetSize); + + if(patch.apply() == bpspatch::result::success) { + delete[] data; + data = targetData; + size = targetSize; + patchApplied = true; + } else { + delete[] targetData; } } diff --git a/bsnes/ui/config.cpp b/bsnes/ui/config.cpp index 853bd5c1..d101829e 100755 --- a/bsnes/ui/config.cpp +++ b/bsnes/ui/config.cpp @@ -37,7 +37,7 @@ void Configuration::create() { attach(audio.synchronize = true, "audio.synchronize"); attach(audio.mute = false, "audio.mute"); attach(audio.volume = 100, "audio.volume"); - attach(audio.balance = 0, "audio.balance"); + attach(audio.balance = 100, "audio.balance"); attach(audio.latency = 60, "audio.latency"); attach(audio.inputFrequency = 32000, "audio.inputFrequency"); attach(audio.outputFrequency = 44100, "audio.outputFrequency"); diff --git a/bsnes/ui/interface.cpp b/bsnes/ui/interface.cpp index 93519cfa..bedbb401 100755 --- a/bsnes/ui/interface.cpp +++ b/bsnes/ui/interface.cpp @@ -131,7 +131,7 @@ void Interface::video_refresh(const uint16_t *data, bool hires, bool interlace, decimal<4, '0'>(info->tm_year + 1900), "-", decimal<2, '0'>(info->tm_mon + 1), "-", decimal<2, '0'>(info->tm_mday), " ", decimal<2, '0'>(info->tm_hour), ".", decimal<2, '0'>(info->tm_min), ".", decimal<2, '0'>(info->tm_sec), ".bmp" }; - if(bmp::write(path(utility.slotPath(), filename), buffer, outwidth, outheight, outpitch, false)) { + if(bmp::write(path(utility.activeSlot(), filename), buffer, outwidth, outheight, outpitch, false)) { utility.showMessage("Screenshot captured"); } } diff --git a/bsnes/ui/path/path.cpp b/bsnes/ui/path/path.cpp index 4776c867..ae802880 100755 --- a/bsnes/ui/path/path.cpp +++ b/bsnes/ui/path/path.cpp @@ -49,9 +49,9 @@ void Path::save(const string &path, const string &value) { } string Path::load(SNES::Cartridge::Slot slot, const string &hint) { - string basePath = basepath(slot); - string baseName = notdir(basePath); - string filePath = dir(basePath); + string baseName = utility.baseName(slot); + string fileName = notdir(baseName); + string filePath = dir(baseName); if(hint == ".srm" && srm != "") filePath = srm; if(hint == ".bsp" && bsp != "") filePath = bsp; @@ -71,43 +71,8 @@ string Path::load(SNES::Cartridge::Slot slot, const string &hint) { if(hint.endswith(".log") && log != "") filePath = log; if(hint.endswith(".bmp") && bmp != "") filePath = bmp; - filePath = decode(filePath, basePath); - return { filePath, baseName, hint }; -} - -string Path::basepath(SNES::Cartridge::Slot slot) { - if(slot == SNES::Cartridge::Slot::Base) { - return cartridge.baseName; - } - - if(slot == SNES::Cartridge::Slot::Bsx) { - if(cartridge.bsxName == "") return cartridge.baseName; - return cartridge.bsxName; - } - - if(slot == SNES::Cartridge::Slot::SufamiTurbo) { - if(cartridge.sufamiTurboAName == "" && cartridge.sufamiTurboBName == "") return cartridge.baseName; - if(cartridge.sufamiTurboAName != "" && cartridge.sufamiTurboBName == "") return cartridge.sufamiTurboAName; - if(cartridge.sufamiTurboAName == "" && cartridge.sufamiTurboBName != "") return cartridge.sufamiTurboBName; - return { cartridge.sufamiTurboAName, "+", notdir(cartridge.sufamiTurboBName) }; - } - - if(slot == SNES::Cartridge::Slot::SufamiTurboA) { - if(cartridge.sufamiTurboAName == "") return cartridge.baseName; - return cartridge.sufamiTurboAName; - } - - if(slot == SNES::Cartridge::Slot::SufamiTurboB) { - if(cartridge.sufamiTurboBName == "") return cartridge.baseName; - return cartridge.sufamiTurboBName; - } - - if(slot == SNES::Cartridge::Slot::GameBoy) { - if(cartridge.gameBoyName == "") return cartridge.baseName; - return cartridge.gameBoyName; - } - - throw "Path::basepath(): invalid slot ID."; + filePath = decode(filePath, baseName); + return { filePath, fileName, hint }; } string Path::decode(const string &filePath, const string &basePath) { diff --git a/bsnes/ui/path/path.hpp b/bsnes/ui/path/path.hpp index 003fd64e..71039e99 100755 --- a/bsnes/ui/path/path.hpp +++ b/bsnes/ui/path/path.hpp @@ -32,8 +32,6 @@ struct Path : public configuration { string load(const string &path); void save(const string &path, const string &value); string load(SNES::Cartridge::Slot slot, const string &hint); - - string basepath(SNES::Cartridge::Slot slot); string decode(const string &filePath, const string &basePath); void load(); diff --git a/bsnes/ui/tools/cheat-editor.cpp b/bsnes/ui/tools/cheat-editor.cpp index 024936fa..38d00296 100755 --- a/bsnes/ui/tools/cheat-editor.cpp +++ b/bsnes/ui/tools/cheat-editor.cpp @@ -12,7 +12,7 @@ void CheatEditor::load() { unsigned n = 0; string data; - data.readfile(path.load(utility.slotPath(), ".cht")); + data.readfile(path.load(utility.activeSlot(), ".cht")); xml_element document = xml_parse(data); foreach(head, document.element) { if(head.name == "cartridge") { @@ -52,12 +52,12 @@ void CheatEditor::save() { } } if(lastSave == -1) { - unlink(path.load(utility.slotPath(), ".cht")); + unlink(path.load(utility.activeSlot(), ".cht")); return; } file fp; - if(fp.open(path.load(utility.slotPath(), ".cht"), file::mode::write)) { + if(fp.open(path.load(utility.activeSlot(), ".cht"), file::mode::write)) { fp.print("\n"); fp.print("\n"); for(unsigned i = 0; i <= lastSave; i++) { diff --git a/bsnes/ui/tools/state-manager.cpp b/bsnes/ui/tools/state-manager.cpp index 76520cfe..39d95d20 100755 --- a/bsnes/ui/tools/state-manager.cpp +++ b/bsnes/ui/tools/state-manager.cpp @@ -64,7 +64,7 @@ void StateManager::load() { } file fp; - if(fp.open(path.load(utility.slotPath(), ".bsa"), file::mode::read)) { + if(fp.open(path.load(utility.activeSlot(), ".bsa"), file::mode::read)) { if(fp.readl(4) == 0x31415342) { if(fp.readl(4) == SNES::Info::SerializerVersion) { for(unsigned i = 0; i < 32; i++) { @@ -89,10 +89,10 @@ void StateManager::save() { } if(hasSave == false) { - unlink(path.load(utility.slotPath(), ".bsa")); + unlink(path.load(utility.activeSlot(), ".bsa")); } else { file fp; - if(fp.open(path.load(utility.slotPath(), ".bsa"), file::mode::write)) { + if(fp.open(path.load(utility.activeSlot(), ".bsa"), file::mode::write)) { fp.writel(0x31415342, 4); //'BSA1' fp.writel(SNES::Info::SerializerVersion, 4); diff --git a/bsnes/ui/utility/utility.cpp b/bsnes/ui/utility/utility.cpp index 873deca8..e497ca97 100755 --- a/bsnes/ui/utility/utility.cpp +++ b/bsnes/ui/utility/utility.cpp @@ -160,10 +160,12 @@ void Utility::cartridgeLoaded() { cheatEditor.load(); stateManager.load(); mainWindow.synchronize(); - utility.setTitle(notdir(cartridge.baseName)); + + string name = baseName(activeSlot()); + utility.setTitle(notdir(name)); utility.showMessage({ - "Loaded ", notdir(cartridge.baseName), - cartridge.patchApplied ? ", and applied UPS patch" : "" + "Loaded ", notdir(name), + cartridge.patchApplied ? ", and applied BPS patch" : "" }); //NSS @@ -180,7 +182,7 @@ void Utility::cartridgeUnloaded() { mainWindow.synchronize(); } -SNES::Cartridge::Slot Utility::slotPath() { +SNES::Cartridge::Slot Utility::activeSlot() { SNES::Cartridge::Slot slot = SNES::Cartridge::Slot::Base; if(SNES::cartridge.mode() == SNES::Cartridge::Mode::Bsx) slot = SNES::Cartridge::Slot::Bsx; if(SNES::cartridge.mode() == SNES::Cartridge::Mode::SufamiTurbo) slot = SNES::Cartridge::Slot::SufamiTurbo; @@ -188,8 +190,26 @@ SNES::Cartridge::Slot Utility::slotPath() { return slot; } +string Utility::baseName(SNES::Cartridge::Slot slot) { + switch(slot) { + default: + return cartridge.baseName; + case SNES::Cartridge::Slot::Bsx: + if(cartridge.bsxName == "") return cartridge.baseName; + return cartridge.bsxName; + case SNES::Cartridge::Slot::SufamiTurbo: + if(cartridge.sufamiTurboAName == "" && cartridge.sufamiTurboBName == "") return cartridge.baseName; + if(cartridge.sufamiTurboBName == "") return cartridge.sufamiTurboAName; + if(cartridge.sufamiTurboAName == "") return cartridge.sufamiTurboBName; + return { cartridge.sufamiTurboAName, "+", cartridge.sufamiTurboBName }; + case SNES::Cartridge::Slot::GameBoy: + if(cartridge.gameBoyName == "") return cartridge.baseName; + return cartridge.gameBoyName; + } +} + void Utility::saveState(unsigned slot) { - string filename = path.load(slotPath(), { "-", slot, ".bst" }); + string filename = path.load(activeSlot(), { "-", slot, ".bst" }); SNES::system.runtosave(); serializer s = SNES::system.serialize(); file fp; @@ -203,7 +223,7 @@ void Utility::saveState(unsigned slot) { } void Utility::loadState(unsigned slot) { - string filename = path.load(slotPath(), { "-", slot, ".bst" }); + string filename = path.load(activeSlot(), { "-", slot, ".bst" }); file fp; if(fp.open(filename, file::mode::read)) { unsigned size = fp.size(); diff --git a/bsnes/ui/utility/utility.hpp b/bsnes/ui/utility/utility.hpp index b8b7cac6..9a3f9dad 100755 --- a/bsnes/ui/utility/utility.hpp +++ b/bsnes/ui/utility/utility.hpp @@ -15,7 +15,9 @@ struct Utility : property { void cartridgeLoaded(); void cartridgeUnloaded(); - SNES::Cartridge::Slot slotPath(); + SNES::Cartridge::Slot activeSlot(); + string baseName(SNES::Cartridge::Slot slot); + void saveState(unsigned slot); void loadState(unsigned slot);