mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-08-11 04:54:24 +02:00
Update to v081r03 release.
byuu says: - GameBoy: fixed window behavior for Contra 3 first boss [Jonas Quinn] - GameBoy: fixed noise channel for Zelda: LA intro [Jonas Quinn] - GameBoy: completely disable LCD when turned off, no interrupts; fixes Super Mario Land 2 [thanks to Jonas Quinn] - GameBoy: keep track of where pixels come from for OBJ priority mode [Jonas Quinn] - updated mode+slot-dependent name handling: simplifies Path class, allows SGB/BSX/ST games to show the slot title (instead of BIOS name) on the title bar - Makefile allows command-line definitions for ui and profile now (make profile=compatibility ui=ui-libsnes) - Makefile now allows (make pgo=instrument) and (make pgo=optimize) - added BPS patching support, removed UPS patching support
This commit is contained in:
@@ -1,9 +1,15 @@
|
|||||||
include nall/Makefile
|
include nall/Makefile
|
||||||
|
|
||||||
snes := snes
|
snes := snes
|
||||||
gameboy := gameboy
|
gameboy := gameboy
|
||||||
profile := accuracy
|
|
||||||
ui := ui
|
ifeq ($(profile),)
|
||||||
# phoenix := gtk
|
profile := accuracy
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(ui),)
|
||||||
|
ui := ui
|
||||||
|
endif
|
||||||
|
|
||||||
# options += debugger
|
# options += debugger
|
||||||
|
|
||||||
@@ -14,12 +20,15 @@ flags := -O3 -fomit-frame-pointer -I.
|
|||||||
link :=
|
link :=
|
||||||
objects := libco
|
objects := libco
|
||||||
|
|
||||||
# profile-guided instrumentation
|
# profile-guided optimization mode
|
||||||
# flags += -fprofile-generate
|
# pgo := instrument
|
||||||
# link += -lgcov
|
# pgo := optimize
|
||||||
|
ifeq ($(pgo),instrument)
|
||||||
# profile-guided optimization
|
flags += -fprofile-generate
|
||||||
# flags += -fprofile-use
|
link += -lgcov
|
||||||
|
else ifeq ($(pgo),optimize)
|
||||||
|
flags += -fprofile-use
|
||||||
|
endif
|
||||||
|
|
||||||
flags := $(flags) $(foreach o,$(call strupper,$(options)),-D$o)
|
flags := $(flags) $(foreach o,$(call strupper,$(options)),-D$o)
|
||||||
|
|
||||||
|
@@ -5,8 +5,7 @@ void APU::Noise::run() {
|
|||||||
period = divisor << frequency;
|
period = divisor << frequency;
|
||||||
if(frequency < 14) {
|
if(frequency < 14) {
|
||||||
bool bit = (lfsr ^ (lfsr >> 1)) & 1;
|
bool bit = (lfsr ^ (lfsr >> 1)) & 1;
|
||||||
lfsr = (lfsr >> 1) ^ (bit << 14);
|
lfsr = (lfsr >> 1) ^ (bit << (narrow_lfsr ? 6 : 14));
|
||||||
if(narrow_lfsr) lfsr |= (bit << 6);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,8 +5,8 @@
|
|||||||
namespace GameBoy {
|
namespace GameBoy {
|
||||||
namespace Info {
|
namespace Info {
|
||||||
static const char Name[] = "bgameboy";
|
static const char Name[] = "bgameboy";
|
||||||
static const char Version[] = "000.19";
|
static const char Version[] = "000.20";
|
||||||
static unsigned SerializerVersion = 1;
|
static unsigned SerializerVersion = 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -18,6 +18,10 @@ void LCD::main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
add_clocks(4);
|
add_clocks(4);
|
||||||
|
if(status.display_enable == false) continue;
|
||||||
|
|
||||||
|
status.lx += 4;
|
||||||
|
if(status.lx >= 456) scanline();
|
||||||
|
|
||||||
if(status.lx == 0) {
|
if(status.lx == 0) {
|
||||||
if(status.interrupt_oam) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
if(status.interrupt_oam) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
||||||
@@ -30,9 +34,6 @@ void LCD::main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LCD::add_clocks(unsigned clocks) {
|
void LCD::add_clocks(unsigned clocks) {
|
||||||
status.lx += clocks;
|
|
||||||
if(status.lx >= 456) scanline();
|
|
||||||
|
|
||||||
clock += clocks;
|
clock += clocks;
|
||||||
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) {
|
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) {
|
||||||
co_switch(scheduler.active_thread = cpu.thread);
|
co_switch(scheduler.active_thread = cpu.thread);
|
||||||
@@ -65,7 +66,10 @@ void LCD::frame() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LCD::render() {
|
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.display_enable == true) {
|
||||||
if(status.bg_enable == true) render_bg();
|
if(status.bg_enable == true) render_bg();
|
||||||
@@ -98,7 +102,9 @@ void LCD::render_bg() {
|
|||||||
for(unsigned ox = 0; ox < 160; ox++) {
|
for(unsigned ox = 0; ox < 160; ox++) {
|
||||||
uint8 palette = ((data & (0x0080 >> tx)) ? 1 : 0)
|
uint8 palette = ((data & (0x0080 >> tx)) ? 1 : 0)
|
||||||
| ((data & (0x8000 >> tx)) ? 2 : 0);
|
| ((data & (0x8000 >> tx)) ? 2 : 0);
|
||||||
|
|
||||||
line[ox] = status.bgp[palette];
|
line[ox] = status.bgp[palette];
|
||||||
|
origin[ox] = Origin::BG;
|
||||||
|
|
||||||
ix = (ix + 1) & 255;
|
ix = (ix + 1) & 255;
|
||||||
tx = (tx + 1) & 7;
|
tx = (tx + 1) & 7;
|
||||||
@@ -110,13 +116,16 @@ void LCD::render_bg() {
|
|||||||
void LCD::render_window() {
|
void LCD::render_window() {
|
||||||
if(status.ly - status.wy >= 144U) return;
|
if(status.ly - status.wy >= 144U) return;
|
||||||
unsigned iy = status.ly - status.wy;
|
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);
|
unsigned data = read_tile(status.window_tilemap_select, ix, iy);
|
||||||
|
|
||||||
for(unsigned ox = 0; ox < 160; ox++) {
|
for(unsigned ox = 0; ox < 160; ox++) {
|
||||||
uint8 palette = ((data & (0x0080 >> tx)) ? 1 : 0)
|
uint8 palette = ((data & (0x0080 >> tx)) ? 1 : 0)
|
||||||
| ((data & (0x8000 >> tx)) ? 2 : 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;
|
ix = (ix + 1) & 255;
|
||||||
tx = (tx + 1) & 7;
|
tx = (tx + 1) & 7;
|
||||||
@@ -126,6 +135,8 @@ void LCD::render_window() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LCD::render_obj() {
|
void LCD::render_obj() {
|
||||||
|
enum : unsigned { Priority = 0x80, YFlip = 0x40, XFlip = 0x20, Palette = 0x10 };
|
||||||
|
|
||||||
unsigned obj_size = (status.obj_size == 0 ? 8 : 16);
|
unsigned obj_size = (status.obj_size == 0 ? 8 : 16);
|
||||||
|
|
||||||
unsigned sprite[10], sprites = 0;
|
unsigned sprite[10], sprites = 0;
|
||||||
@@ -165,26 +176,29 @@ void LCD::render_obj() {
|
|||||||
|
|
||||||
sy = status.ly - sy;
|
sy = status.ly - sy;
|
||||||
if(sy >= obj_size) continue;
|
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);
|
unsigned tdaddr = (tile << 4) + (sy << 1);
|
||||||
uint8 d0 = vram[tdaddr + 0];
|
uint8 d0 = vram[tdaddr + 0];
|
||||||
uint8 d1 = vram[tdaddr + 1];
|
uint8 d1 = vram[tdaddr + 1];
|
||||||
unsigned xflip = attribute & 0x20 ? 7 : 0;
|
unsigned xflip = attribute & XFlip ? 7 : 0;
|
||||||
|
|
||||||
for(unsigned tx = 0; tx < 8; tx++) {
|
for(unsigned tx = 0; tx < 8; tx++) {
|
||||||
uint8 palette = ((d0 & (0x80 >> tx)) ? 1 : 0)
|
uint8 palette = ((d0 & (0x80 >> tx)) ? 1 : 0)
|
||||||
| ((d1 & (0x80 >> tx)) ? 2 : 0);
|
| ((d1 & (0x80 >> tx)) ? 2 : 0);
|
||||||
if(palette == 0) continue;
|
if(palette == 0) continue;
|
||||||
|
|
||||||
palette = status.obp[(bool)(attribute & 0x10)][palette];
|
palette = status.obp[(bool)(attribute & Palette)][palette];
|
||||||
unsigned ox = sx + (tx ^ xflip);
|
unsigned ox = sx + (tx ^ xflip);
|
||||||
|
|
||||||
if(ox <= 159) {
|
if(ox <= 159) {
|
||||||
if(attribute & 0x80) {
|
if(attribute & Priority) {
|
||||||
if(line[ox] > 0) continue;
|
if(origin[ox] == Origin::BG || origin[ox] == Origin::Window) {
|
||||||
|
if(line[ox] > 0) continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
line[ox] = palette;
|
line[ox] = palette;
|
||||||
|
origin[ox] = Origin::OBJ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -51,6 +51,9 @@ struct LCD : Processor, MMIO {
|
|||||||
uint8 oam[160];
|
uint8 oam[160];
|
||||||
uint8 line[160];
|
uint8 line[160];
|
||||||
|
|
||||||
|
struct Origin { enum : unsigned { None, BG, Window, OBJ }; };
|
||||||
|
uint8 origin[160];
|
||||||
|
|
||||||
static void Main();
|
static void Main();
|
||||||
void main();
|
void main();
|
||||||
void add_clocks(unsigned clocks);
|
void add_clocks(unsigned clocks);
|
||||||
|
@@ -17,10 +17,11 @@ uint8 LCD::mmio_read(uint16 addr) {
|
|||||||
|
|
||||||
if(addr == 0xff41) { //STAT
|
if(addr == 0xff41) { //STAT
|
||||||
unsigned mode;
|
unsigned mode;
|
||||||
if(status.ly >= 144) mode = 1; //Vblank
|
if(status.display_enable == false) mode = 1; //force blank
|
||||||
else if(status.lx < 80) mode = 2; //OAM
|
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 if(status.lx < 252) mode = 3; //LCD
|
||||||
else mode = 0; //Hblank
|
else mode = 0; //Hblank
|
||||||
|
|
||||||
return (status.interrupt_lyc << 6)
|
return (status.interrupt_lyc << 6)
|
||||||
| (status.interrupt_oam << 5)
|
| (status.interrupt_oam << 5)
|
||||||
|
@@ -33,6 +33,7 @@ void LCD::serialize(serializer &s) {
|
|||||||
s.array(vram);
|
s.array(vram);
|
||||||
s.array(oam);
|
s.array(oam);
|
||||||
s.array(line);
|
s.array(line);
|
||||||
|
s.array(origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
206
bsnes/nall/bps/delta.hpp
Executable file
206
bsnes/nall/bps/delta.hpp
Executable file
@@ -0,0 +1,206 @@
|
|||||||
|
#ifndef NALL_BPS_DELTA_HPP
|
||||||
|
#define NALL_BPS_DELTA_HPP
|
||||||
|
|
||||||
|
#include <nall/crc32.hpp>
|
||||||
|
#include <nall/file.hpp>
|
||||||
|
#include <nall/filemap.hpp>
|
||||||
|
#include <nall/stdint.hpp>
|
||||||
|
#include <nall/string.hpp>
|
||||||
|
|
||||||
|
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
|
141
bsnes/nall/bps/linear.hpp
Executable file
141
bsnes/nall/bps/linear.hpp
Executable file
@@ -0,0 +1,141 @@
|
|||||||
|
#ifndef NALL_BPS_LINEAR_HPP
|
||||||
|
#define NALL_BPS_LINEAR_HPP
|
||||||
|
|
||||||
|
#include <nall/crc32.hpp>
|
||||||
|
#include <nall/file.hpp>
|
||||||
|
#include <nall/filemap.hpp>
|
||||||
|
#include <nall/stdint.hpp>
|
||||||
|
#include <nall/string.hpp>
|
||||||
|
|
||||||
|
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
|
213
bsnes/nall/bps/patch.hpp
Executable file
213
bsnes/nall/bps/patch.hpp
Executable file
@@ -0,0 +1,213 @@
|
|||||||
|
#ifndef NALL_BPS_PATCH_HPP
|
||||||
|
#define NALL_BPS_PATCH_HPP
|
||||||
|
|
||||||
|
#include <nall/crc32.hpp>
|
||||||
|
#include <nall/file.hpp>
|
||||||
|
#include <nall/filemap.hpp>
|
||||||
|
#include <nall/stdint.hpp>
|
||||||
|
#include <nall/string.hpp>
|
||||||
|
|
||||||
|
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
|
@@ -1,80 +1,165 @@
|
|||||||
#ifndef NALL_LZSS_HPP
|
#ifndef NALL_LZSS_HPP
|
||||||
#define NALL_LZSS_HPP
|
#define NALL_LZSS_HPP
|
||||||
|
|
||||||
#include <nall/array.hpp>
|
#include <nall/file.hpp>
|
||||||
|
#include <nall/filemap.hpp>
|
||||||
#include <nall/stdint.hpp>
|
#include <nall/stdint.hpp>
|
||||||
|
#include <nall/string.hpp>
|
||||||
|
|
||||||
namespace nall {
|
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;
|
//19:5 pulldown
|
||||||
while(i < inlength) {
|
//8:1 marker: d7-d0
|
||||||
unsigned flagoffset = o++;
|
//length: { 4 - 35 }, offset: { 1 - 0x80000 }
|
||||||
uint8_t flag = 0x00;
|
//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++) {
|
protected:
|
||||||
unsigned longest = 0, pointer;
|
struct Node {
|
||||||
for(unsigned index = 1; index < 4096; index++) {
|
unsigned offset;
|
||||||
unsigned count = 0;
|
Node *next;
|
||||||
while(true) {
|
inline Node() : offset(0), next(0) {}
|
||||||
if(count >= 15 + 3) break; //verify pattern match is not longer than max length
|
inline ~Node() { if(next) delete next; }
|
||||||
if(i + count >= inlength) break; //verify pattern match does not read past end of input
|
} *tree[65536];
|
||||||
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++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(count > longest) {
|
filemap sourceFile;
|
||||||
longest = count;
|
const uint8_t *sourceData;
|
||||||
pointer = index;
|
unsigned sourceSize;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(longest < 3) output[o++] = input[i++];
|
public:
|
||||||
else {
|
inline lzss() : sourceData(0), sourceSize(0) {}
|
||||||
flag |= 1 << b;
|
};
|
||||||
uint16_t x = ((longest - 3) << 12) + pointer;
|
|
||||||
output[o++] = x;
|
void lzss::source(const uint8_t *data, unsigned size) {
|
||||||
output[o++] = x >> 8;
|
sourceData = data;
|
||||||
i += longest;
|
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;
|
//attach current symbol to top of tree for subsequent searches
|
||||||
return true;
|
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) {
|
if(maxLength < 4) {
|
||||||
output = new uint8_t[length]();
|
buffer[bufferOffset++] = sourceData[sourceOffset++];
|
||||||
|
} else {
|
||||||
unsigned i = 0, o = 0;
|
unsigned output = ((maxLength - 4) << 19) | (sourceOffset - 1 - maxOffset);
|
||||||
while(o < length) {
|
for(unsigned n = 0; n < 24; n += 8) buffer[bufferOffset++] = output >> n;
|
||||||
uint8_t flag = input[i++];
|
mask |= 0x80 >> iteration;
|
||||||
|
sourceOffset += maxLength;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
#endif
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
namespace SNES {
|
namespace SNES {
|
||||||
namespace Info {
|
namespace Info {
|
||||||
static const char Name[] = "bsnes";
|
static const char Name[] = "bsnes";
|
||||||
static const char Version[] = "081.02";
|
static const char Version[] = "081.03";
|
||||||
static const unsigned SerializerVersion = 21;
|
static const unsigned SerializerVersion = 21;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
#include <nall/dsp.hpp>
|
||||||
#include <nall/file.hpp>
|
#include <nall/file.hpp>
|
||||||
#include <nall/foreach.hpp>
|
#include <nall/foreach.hpp>
|
||||||
#include <nall/stdint.hpp>
|
#include <nall/stdint.hpp>
|
||||||
@@ -29,4 +30,5 @@ struct Application {
|
|||||||
void main(int argc, char **argv);
|
void main(int argc, char **argv);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extern nall::dsp dspaudio;
|
||||||
extern Application application;
|
extern Application application;
|
||||||
|
@@ -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) {
|
void Interface::audio_sample(int16_t center, int16_t lchannel, int16_t rchannel) {
|
||||||
audio.sample(left, right);
|
dspaudio.sample(lchannel, rchannel);
|
||||||
|
while(dspaudio.pending()) {
|
||||||
|
signed lsample, rsample;
|
||||||
|
dspaudio.read(lsample, rsample);
|
||||||
|
audio.sample(lsample, rsample);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interface::input_poll() {
|
void Interface::input_poll() {
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
#include "base.hpp"
|
#include "base.hpp"
|
||||||
|
nall::dsp dspaudio;
|
||||||
Application application;
|
Application application;
|
||||||
|
|
||||||
#include "interface.cpp"
|
#include "interface.cpp"
|
||||||
@@ -51,13 +52,16 @@ void Application::main(int argc, char **argv) {
|
|||||||
#endif
|
#endif
|
||||||
audio.set(Audio::Handle, (uintptr_t)mainWindow.viewport.handle());
|
audio.set(Audio::Handle, (uintptr_t)mainWindow.viewport.handle());
|
||||||
audio.set(Audio::Synchronize, true);
|
audio.set(Audio::Synchronize, true);
|
||||||
audio.set(Audio::Volume, 100U);
|
audio.set(Audio::Latency, 80u);
|
||||||
audio.set(Audio::Latency, 80U);
|
audio.set(Audio::Frequency, 44100u);
|
||||||
audio.set(Audio::Frequency, 44100U);
|
|
||||||
audio.set(Audio::Resample, true);
|
|
||||||
audio.set(Audio::ResampleRatio, 4194304.0 / 44100.0);
|
|
||||||
audio.init();
|
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)
|
#if defined(PLATFORM_WIN)
|
||||||
input.driver("RawInput");
|
input.driver("RawInput");
|
||||||
#else
|
#else
|
||||||
|
@@ -4,12 +4,13 @@
|
|||||||
#include <nall/bmp.hpp>
|
#include <nall/bmp.hpp>
|
||||||
#include <nall/compositor.hpp>
|
#include <nall/compositor.hpp>
|
||||||
#include <nall/config.hpp>
|
#include <nall/config.hpp>
|
||||||
|
#include <nall/crc32.hpp>
|
||||||
#include <nall/directory.hpp>
|
#include <nall/directory.hpp>
|
||||||
#include <nall/dsp.hpp>
|
#include <nall/dsp.hpp>
|
||||||
#include <nall/filemap.hpp>
|
#include <nall/filemap.hpp>
|
||||||
#include <nall/input.hpp>
|
#include <nall/input.hpp>
|
||||||
#include <nall/resource.hpp>
|
#include <nall/resource.hpp>
|
||||||
#include <nall/ups.hpp>
|
#include <nall/bps/patch.hpp>
|
||||||
#include <nall/snes/cartridge.hpp>
|
#include <nall/snes/cartridge.hpp>
|
||||||
#include <nall/gameboy/cartridge.hpp>
|
#include <nall/gameboy/cartridge.hpp>
|
||||||
using namespace nall;
|
using namespace nall;
|
||||||
|
@@ -110,18 +110,24 @@ bool Cartridge::loadCartridge(SNES::MappedRAM &memory, string &XML, const char *
|
|||||||
fp.read(data, size);
|
fp.read(data, size);
|
||||||
fp.close();
|
fp.close();
|
||||||
|
|
||||||
filemap patch(string(nall::basename(filename), ".ups"), filemap::mode::read);
|
string patchName = { nall::basename(filename), ".bps" };
|
||||||
if(patch.open()) {
|
if(file::exists(patchName)) {
|
||||||
unsigned targetSize;
|
bpspatch patch;
|
||||||
ups patcher;
|
patch.modify(patchName);
|
||||||
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];
|
unsigned targetSize = patch.size();
|
||||||
if(patcher.apply(patch.data(), patch.size(), data, size, targetData, targetSize) == ups::result::success) {
|
uint8_t *targetData = new uint8_t[targetSize];
|
||||||
delete[] data;
|
|
||||||
data = targetData;
|
patch.source(data, size);
|
||||||
size = targetSize;
|
patch.target(targetData, targetSize);
|
||||||
patchApplied = true;
|
|
||||||
}
|
if(patch.apply() == bpspatch::result::success) {
|
||||||
|
delete[] data;
|
||||||
|
data = targetData;
|
||||||
|
size = targetSize;
|
||||||
|
patchApplied = true;
|
||||||
|
} else {
|
||||||
|
delete[] targetData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -37,7 +37,7 @@ void Configuration::create() {
|
|||||||
attach(audio.synchronize = true, "audio.synchronize");
|
attach(audio.synchronize = true, "audio.synchronize");
|
||||||
attach(audio.mute = false, "audio.mute");
|
attach(audio.mute = false, "audio.mute");
|
||||||
attach(audio.volume = 100, "audio.volume");
|
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.latency = 60, "audio.latency");
|
||||||
attach(audio.inputFrequency = 32000, "audio.inputFrequency");
|
attach(audio.inputFrequency = 32000, "audio.inputFrequency");
|
||||||
attach(audio.outputFrequency = 44100, "audio.outputFrequency");
|
attach(audio.outputFrequency = 44100, "audio.outputFrequency");
|
||||||
|
@@ -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<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"
|
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");
|
utility.showMessage("Screenshot captured");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -49,9 +49,9 @@ void Path::save(const string &path, const string &value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
string Path::load(SNES::Cartridge::Slot slot, const string &hint) {
|
string Path::load(SNES::Cartridge::Slot slot, const string &hint) {
|
||||||
string basePath = basepath(slot);
|
string baseName = utility.baseName(slot);
|
||||||
string baseName = notdir(basePath);
|
string fileName = notdir(baseName);
|
||||||
string filePath = dir(basePath);
|
string filePath = dir(baseName);
|
||||||
|
|
||||||
if(hint == ".srm" && srm != "") filePath = srm;
|
if(hint == ".srm" && srm != "") filePath = srm;
|
||||||
if(hint == ".bsp" && bsp != "") filePath = bsp;
|
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(".log") && log != "") filePath = log;
|
||||||
if(hint.endswith(".bmp") && bmp != "") filePath = bmp;
|
if(hint.endswith(".bmp") && bmp != "") filePath = bmp;
|
||||||
|
|
||||||
filePath = decode(filePath, basePath);
|
filePath = decode(filePath, baseName);
|
||||||
return { filePath, baseName, hint };
|
return { filePath, fileName, 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.";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string Path::decode(const string &filePath, const string &basePath) {
|
string Path::decode(const string &filePath, const string &basePath) {
|
||||||
|
@@ -32,8 +32,6 @@ struct Path : public configuration {
|
|||||||
string load(const string &path);
|
string load(const string &path);
|
||||||
void save(const string &path, const string &value);
|
void save(const string &path, const string &value);
|
||||||
string load(SNES::Cartridge::Slot slot, const string &hint);
|
string load(SNES::Cartridge::Slot slot, const string &hint);
|
||||||
|
|
||||||
string basepath(SNES::Cartridge::Slot slot);
|
|
||||||
string decode(const string &filePath, const string &basePath);
|
string decode(const string &filePath, const string &basePath);
|
||||||
|
|
||||||
void load();
|
void load();
|
||||||
|
@@ -12,7 +12,7 @@ void CheatEditor::load() {
|
|||||||
|
|
||||||
unsigned n = 0;
|
unsigned n = 0;
|
||||||
string data;
|
string data;
|
||||||
data.readfile(path.load(utility.slotPath(), ".cht"));
|
data.readfile(path.load(utility.activeSlot(), ".cht"));
|
||||||
xml_element document = xml_parse(data);
|
xml_element document = xml_parse(data);
|
||||||
foreach(head, document.element) {
|
foreach(head, document.element) {
|
||||||
if(head.name == "cartridge") {
|
if(head.name == "cartridge") {
|
||||||
@@ -52,12 +52,12 @@ void CheatEditor::save() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(lastSave == -1) {
|
if(lastSave == -1) {
|
||||||
unlink(path.load(utility.slotPath(), ".cht"));
|
unlink(path.load(utility.activeSlot(), ".cht"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
file fp;
|
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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
|
fp.print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
|
||||||
fp.print("<cartridge sha256=\"", SNES::cartridge.sha256(), "\">\n");
|
fp.print("<cartridge sha256=\"", SNES::cartridge.sha256(), "\">\n");
|
||||||
for(unsigned i = 0; i <= lastSave; i++) {
|
for(unsigned i = 0; i <= lastSave; i++) {
|
||||||
|
@@ -64,7 +64,7 @@ void StateManager::load() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
file fp;
|
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) == 0x31415342) {
|
||||||
if(fp.readl(4) == SNES::Info::SerializerVersion) {
|
if(fp.readl(4) == SNES::Info::SerializerVersion) {
|
||||||
for(unsigned i = 0; i < 32; i++) {
|
for(unsigned i = 0; i < 32; i++) {
|
||||||
@@ -89,10 +89,10 @@ void StateManager::save() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(hasSave == false) {
|
if(hasSave == false) {
|
||||||
unlink(path.load(utility.slotPath(), ".bsa"));
|
unlink(path.load(utility.activeSlot(), ".bsa"));
|
||||||
} else {
|
} else {
|
||||||
file fp;
|
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(0x31415342, 4); //'BSA1'
|
||||||
fp.writel(SNES::Info::SerializerVersion, 4);
|
fp.writel(SNES::Info::SerializerVersion, 4);
|
||||||
|
|
||||||
|
@@ -160,10 +160,12 @@ void Utility::cartridgeLoaded() {
|
|||||||
cheatEditor.load();
|
cheatEditor.load();
|
||||||
stateManager.load();
|
stateManager.load();
|
||||||
mainWindow.synchronize();
|
mainWindow.synchronize();
|
||||||
utility.setTitle(notdir(cartridge.baseName));
|
|
||||||
|
string name = baseName(activeSlot());
|
||||||
|
utility.setTitle(notdir(name));
|
||||||
utility.showMessage({
|
utility.showMessage({
|
||||||
"Loaded ", notdir(cartridge.baseName),
|
"Loaded ", notdir(name),
|
||||||
cartridge.patchApplied ? ", and applied UPS patch" : ""
|
cartridge.patchApplied ? ", and applied BPS patch" : ""
|
||||||
});
|
});
|
||||||
|
|
||||||
//NSS
|
//NSS
|
||||||
@@ -180,7 +182,7 @@ void Utility::cartridgeUnloaded() {
|
|||||||
mainWindow.synchronize();
|
mainWindow.synchronize();
|
||||||
}
|
}
|
||||||
|
|
||||||
SNES::Cartridge::Slot Utility::slotPath() {
|
SNES::Cartridge::Slot Utility::activeSlot() {
|
||||||
SNES::Cartridge::Slot slot = SNES::Cartridge::Slot::Base;
|
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::Bsx) slot = SNES::Cartridge::Slot::Bsx;
|
||||||
if(SNES::cartridge.mode() == SNES::Cartridge::Mode::SufamiTurbo) slot = SNES::Cartridge::Slot::SufamiTurbo;
|
if(SNES::cartridge.mode() == SNES::Cartridge::Mode::SufamiTurbo) slot = SNES::Cartridge::Slot::SufamiTurbo;
|
||||||
@@ -188,8 +190,26 @@ SNES::Cartridge::Slot Utility::slotPath() {
|
|||||||
return slot;
|
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) {
|
void Utility::saveState(unsigned slot) {
|
||||||
string filename = path.load(slotPath(), { "-", slot, ".bst" });
|
string filename = path.load(activeSlot(), { "-", slot, ".bst" });
|
||||||
SNES::system.runtosave();
|
SNES::system.runtosave();
|
||||||
serializer s = SNES::system.serialize();
|
serializer s = SNES::system.serialize();
|
||||||
file fp;
|
file fp;
|
||||||
@@ -203,7 +223,7 @@ void Utility::saveState(unsigned slot) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Utility::loadState(unsigned slot) {
|
void Utility::loadState(unsigned slot) {
|
||||||
string filename = path.load(slotPath(), { "-", slot, ".bst" });
|
string filename = path.load(activeSlot(), { "-", slot, ".bst" });
|
||||||
file fp;
|
file fp;
|
||||||
if(fp.open(filename, file::mode::read)) {
|
if(fp.open(filename, file::mode::read)) {
|
||||||
unsigned size = fp.size();
|
unsigned size = fp.size();
|
||||||
|
@@ -15,7 +15,9 @@ struct Utility : property<Utility> {
|
|||||||
void cartridgeLoaded();
|
void cartridgeLoaded();
|
||||||
void cartridgeUnloaded();
|
void cartridgeUnloaded();
|
||||||
|
|
||||||
SNES::Cartridge::Slot slotPath();
|
SNES::Cartridge::Slot activeSlot();
|
||||||
|
string baseName(SNES::Cartridge::Slot slot);
|
||||||
|
|
||||||
void saveState(unsigned slot);
|
void saveState(unsigned slot);
|
||||||
void loadState(unsigned slot);
|
void loadState(unsigned slot);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user