From ad0805b168d419f2fb4b05b893d778d15db11813 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Sat, 3 Dec 2011 14:22:54 +1100 Subject: [PATCH] Update to v084r03 release. (r02 was not posted to the WIP thread) byuu says: Internally, all color is processed with 30-bit precision. The filters also operate at 30-bit depth. There's a new config file setting, video.depth, which defaults to 24. This causes the final output to downsample to 24-bit, as most will require. If you set it to 30-bit, the downsampling will not occur, and bsnes will ask ruby for a 30-bit surface. If you don't have one available, you're going to get bad colors. Or maybe even a crash with OpenGL. I don't yet have detection code to make sure you have an appropriate visual in place. 30-bit mode will really only work if you are running Linux, running Xorg at Depth 30, use the OpenGL or XShm driver, have an nVidia Quadro or AMD FireGL card with the official drivers, and have a 30-bit capable monitor. Lots of planning and work for very little gain here, but it's nice that it's finally finished. Oh, I had to change the contrast/brightness formulas a tiny bit, but they still work and look nice. --- bsnes/Makefile | 4 +- bsnes/nall/compositor.hpp | 77 ++- bsnes/nall/image.hpp | 433 +++++++++++++ bsnes/nall/interpolation.hpp | 59 ++ bsnes/nall/png.hpp | 127 +--- bsnes/nall/string.hpp | 2 + bsnes/nall/string/base.hpp | 4 +- bsnes/nall/string/cast.hpp | 6 +- bsnes/nall/string/math-fixed-point.hpp | 157 +++++ bsnes/nall/string/math-floating-point.hpp | 149 +++++ bsnes/nall/string/utility.hpp | 11 +- bsnes/nes/input/input.cpp | 1 - bsnes/nes/scheduler/scheduler.cpp | 1 - bsnes/phoenix/core/core.cpp | 81 +-- bsnes/phoenix/core/core.hpp | 17 +- bsnes/phoenix/gtk/widget/hex-edit.cpp | 12 +- bsnes/phoenix/phoenix.hpp | 3 +- bsnes/phoenix/qt/platform.moc | 42 +- bsnes/phoenix/qt/widget/canvas.cpp | 4 +- bsnes/ruby/audio.hpp | 3 +- bsnes/ruby/input.hpp | 3 +- bsnes/ruby/ruby.cpp | 11 + bsnes/ruby/ruby.hpp | 2 +- bsnes/ruby/ruby_impl.cpp | 4 + bsnes/ruby/video.hpp | 4 +- bsnes/ruby/video/glx.cpp | 54 +- bsnes/ruby/video/opengl.hpp | 12 +- bsnes/ruby/video/wgl.cpp | 2 + bsnes/ruby/video/xshm.cpp | 170 +++++ bsnes/snes/Makefile | 4 +- bsnes/snes/alt/ppu-parallel/mmio.cpp | 14 + bsnes/snes/alt/ppu-parallel/ppu.cpp | 81 +++ bsnes/snes/alt/ppu-parallel/ppu.hpp | 40 ++ bsnes/snes/chip/bsx/cartridge/cartridge.cpp | 1 - bsnes/snes/chip/bsx/flash/flash.cpp | 1 - .../snes/chip/bsx/satellaview/satellaview.cpp | 1 - bsnes/snes/chip/hitachidsp/hitachidsp.cpp | 1 - bsnes/snes/chip/icd2/icd2.cpp | 2 - bsnes/snes/chip/link/link.cpp | 1 - bsnes/snes/chip/msu1/msu1.cpp | 70 +- bsnes/snes/chip/necdsp/necdsp.cpp | 2 - bsnes/snes/chip/obc1/obc1.cpp | 1 - bsnes/snes/chip/sa1/sa1.cpp | 1 - bsnes/snes/chip/sdd1/sdd1.cpp | 1 - bsnes/snes/chip/spc7110/spc7110.cpp | 1 - bsnes/snes/chip/srtc/srtc.cpp | 1 - bsnes/snes/chip/st0018/st0018.cpp | 1 - bsnes/snes/chip/superfx/superfx.cpp | 1 - bsnes/snes/config/config.cpp | 2 +- bsnes/snes/cpu/cpu.cpp | 2 - bsnes/snes/dsp/dsp.cpp | 2 - bsnes/snes/ppu/ppu.cpp | 2 - bsnes/snes/smp/core/algorithms.cpp | 8 +- bsnes/snes/smp/core/registers.hpp | 14 +- bsnes/snes/smp/smp.cpp | 2 - bsnes/snes/{audio => system}/audio.cpp | 0 bsnes/snes/{audio => system}/audio.hpp | 0 bsnes/snes/{input => system}/input.cpp | 0 bsnes/snes/{input => system}/input.hpp | 0 bsnes/snes/system/system.cpp | 19 +- bsnes/snes/system/system.hpp | 6 +- bsnes/snes/{video => system}/video.cpp | 0 bsnes/snes/{video => system}/video.hpp | 0 bsnes/ui/config/config.cpp | 1 + bsnes/ui/config/config.hpp | 3 + bsnes/ui/interface/gameboy/gameboy.cpp | 2 +- bsnes/ui/interface/interface.cpp | 2 +- bsnes/ui/interface/nes/nes.cpp | 2 +- bsnes/ui/interface/palette.cpp | 53 +- bsnes/ui/interface/palette.hpp | 9 +- bsnes/ui/interface/snes/snes.cpp | 10 +- bsnes/ui/main.cpp | 5 +- snesfilter/HQ2x/HQ2x.cpp | 47 +- snesfilter/LQ2x/LQ2x.cpp | 9 +- snesfilter/Makefile | 4 +- snesfilter/Phosphor3x/Phosphor3x.cpp | 19 +- snesfilter/Scanline/Scanline-Light.cpp | 3 +- snesfilter/nall/Makefile | 7 +- snesfilter/nall/array.hpp | 32 +- snesfilter/nall/atoi.hpp | 88 +++ snesfilter/nall/base64.hpp | 3 +- snesfilter/nall/bit.hpp | 14 +- snesfilter/nall/bps/delta.hpp | 2 +- snesfilter/nall/compositor.hpp | 81 ++- snesfilter/nall/config.hpp | 4 +- snesfilter/nall/directory.hpp | 15 +- snesfilter/nall/dl.hpp | 6 +- snesfilter/nall/dsp.hpp | 5 + snesfilter/nall/dsp/core.hpp | 109 ++-- snesfilter/nall/dsp/resample/average.hpp | 71 ++- snesfilter/nall/dsp/resample/cosine.hpp | 41 +- snesfilter/nall/dsp/resample/cubic.hpp | 53 +- snesfilter/nall/dsp/resample/hermite.hpp | 51 +- snesfilter/nall/dsp/resample/lib/sinc.hpp | 600 ++++++++++++++++++ snesfilter/nall/dsp/resample/linear.hpp | 41 +- snesfilter/nall/dsp/resample/nearest.hpp | 43 ++ snesfilter/nall/dsp/resample/sinc.hpp | 54 ++ snesfilter/nall/dsp/settings.hpp | 33 +- snesfilter/nall/endian.hpp | 8 +- snesfilter/nall/function.hpp | 10 +- snesfilter/nall/gameboy/cartridge.hpp | 19 +- snesfilter/nall/gzip.hpp | 2 +- snesfilter/nall/image.hpp | 433 +++++++++++++ snesfilter/nall/inflate.hpp | 8 +- snesfilter/nall/interpolation.hpp | 59 ++ snesfilter/nall/intrinsics.hpp | 63 ++ snesfilter/nall/ips.hpp | 4 +- snesfilter/nall/lzss.hpp | 4 +- snesfilter/nall/platform.hpp | 4 + snesfilter/nall/png.hpp | 127 +--- snesfilter/nall/priorityqueue.hpp | 2 +- snesfilter/nall/property.hpp | 2 +- snesfilter/nall/reference_array.hpp | 57 +- snesfilter/nall/snes/cartridge.hpp | 598 ++++++++--------- snesfilter/nall/stack.hpp | 3 - snesfilter/nall/string.hpp | 26 +- snesfilter/nall/string/base.hpp | 73 ++- snesfilter/nall/string/bml.hpp | 151 +++++ snesfilter/nall/string/bsv.hpp | 119 ++-- snesfilter/nall/string/cast.hpp | 194 +++++- snesfilter/nall/string/compare.hpp | 43 +- snesfilter/nall/string/convert.hpp | 83 +-- snesfilter/nall/string/core.hpp | 13 +- snesfilter/nall/string/cstring.hpp | 21 + snesfilter/nall/string/filename.hpp | 3 +- snesfilter/nall/string/math-fixed-point.hpp | 157 +++++ .../nall/string/math-floating-point.hpp | 149 +++++ snesfilter/nall/string/math.hpp | 7 +- snesfilter/nall/string/platform.hpp | 3 +- snesfilter/nall/string/replace.hpp | 3 +- snesfilter/nall/string/split.hpp | 10 +- snesfilter/nall/string/strl.hpp | 3 +- snesfilter/nall/string/strpos.hpp | 3 +- snesfilter/nall/string/trim.hpp | 3 +- snesfilter/nall/string/utility.hpp | 58 +- snesfilter/nall/string/variadic.hpp | 3 +- snesfilter/nall/string/wildcard.hpp | 78 +++ snesfilter/nall/string/wrapper.hpp | 8 +- snesfilter/nall/string/xml.hpp | 7 +- snesfilter/nall/test/cc.sh | 4 +- snesfilter/nall/test/document.bml | 14 + snesfilter/nall/test/test | Bin 228157 -> 250304 bytes snesfilter/nall/test/test.cpp | 50 +- snesfilter/nall/utility.hpp | 1 + snesfilter/nall/vector.hpp | 159 ++++- 145 files changed, 4653 insertions(+), 1455 deletions(-) create mode 100755 bsnes/nall/image.hpp create mode 100755 bsnes/nall/interpolation.hpp create mode 100755 bsnes/nall/string/math-fixed-point.hpp create mode 100755 bsnes/nall/string/math-floating-point.hpp create mode 100755 bsnes/ruby/video/xshm.cpp create mode 100755 bsnes/snes/alt/ppu-parallel/mmio.cpp create mode 100755 bsnes/snes/alt/ppu-parallel/ppu.cpp create mode 100755 bsnes/snes/alt/ppu-parallel/ppu.hpp rename bsnes/snes/{audio => system}/audio.cpp (100%) rename bsnes/snes/{audio => system}/audio.hpp (100%) rename bsnes/snes/{input => system}/input.cpp (100%) rename bsnes/snes/{input => system}/input.hpp (100%) rename bsnes/snes/{video => system}/video.cpp (100%) rename bsnes/snes/{video => system}/video.hpp (100%) create mode 100755 snesfilter/nall/atoi.hpp create mode 100755 snesfilter/nall/dsp/resample/lib/sinc.hpp create mode 100755 snesfilter/nall/dsp/resample/nearest.hpp create mode 100755 snesfilter/nall/dsp/resample/sinc.hpp create mode 100755 snesfilter/nall/image.hpp create mode 100755 snesfilter/nall/interpolation.hpp create mode 100755 snesfilter/nall/intrinsics.hpp create mode 100755 snesfilter/nall/string/bml.hpp create mode 100755 snesfilter/nall/string/cstring.hpp create mode 100755 snesfilter/nall/string/math-fixed-point.hpp create mode 100755 snesfilter/nall/string/math-floating-point.hpp create mode 100755 snesfilter/nall/string/wildcard.hpp create mode 100755 snesfilter/nall/test/document.bml diff --git a/bsnes/Makefile b/bsnes/Makefile index ec15feae..bfeef843 100755 --- a/bsnes/Makefile +++ b/bsnes/Makefile @@ -12,7 +12,7 @@ ui := ui # compiler c := $(compiler) -std=gnu99 cpp := $(subst cc,++,$(compiler)) -std=gnu++0x -flags := -O3 -fomit-frame-pointer -I. +flags := -I. -O3 -fomit-frame-pointer link := objects := libco @@ -29,8 +29,6 @@ endif # platform ifeq ($(platform),x) - # tree vectorization causes code generation errors with Linux/GCC 4.6.1 - flags += -fno-tree-vectorize link += -s -ldl -lX11 -lXext else ifeq ($(platform),osx) else ifeq ($(platform),win) diff --git a/bsnes/nall/compositor.hpp b/bsnes/nall/compositor.hpp index ff6a6ea6..6b9245f6 100755 --- a/bsnes/nall/compositor.hpp +++ b/bsnes/nall/compositor.hpp @@ -8,11 +8,49 @@ namespace nall { struct compositor { inline static bool enabled(); inline static bool enable(bool status); + + #if defined(PLATFORM_X) + enum class Compositor : unsigned { Unknown, Metacity, Xfwm4 }; + inline static Compositor detect(); + + inline static bool enabled_metacity(); + inline static bool enable_metacity(bool status); + + inline static bool enabled_xfwm4(); + inline static bool enable_xfwm4(bool status); + #endif }; #if defined(PLATFORM_X) -bool compositor::enabled() { +//Metacity + +bool compositor::enabled_metacity() { + FILE *fp = popen("gconftool-2 --get /apps/metacity/general/compositing_manager", "r"); + if(fp == 0) return false; + + char buffer[512]; + if(fgets(buffer, sizeof buffer, fp) == 0) return false; + + if(!memcmp(buffer, "true", 4)) return true; + return false; +} + +bool compositor::enable_metacity(bool status) { + FILE *fp; + if(status) { + fp = popen("gconftool-2 --set --type bool /apps/metacity/general/compositing_manager true", "r"); + } else { + fp = popen("gconftool-2 --set --type bool /apps/metacity/general/compositing_manager false", "r"); + } + if(fp == 0) return false; + pclose(fp); + return true; +} + +//Xfwm4 + +bool compositor::enabled_xfwm4() { FILE *fp = popen("xfconf-query -c xfwm4 -p '/general/use_compositing'", "r"); if(fp == 0) return false; @@ -23,7 +61,7 @@ bool compositor::enabled() { return false; } -bool compositor::enable(bool status) { +bool compositor::enable_xfwm4(bool status) { FILE *fp; if(status) { fp = popen("xfconf-query -c xfwm4 -p '/general/use_compositing' -t 'bool' -s 'true'", "r"); @@ -35,6 +73,41 @@ bool compositor::enable(bool status) { return true; } +//General + +compositor::Compositor compositor::detect() { + Compositor result = Compositor::Unknown; + + FILE *fp; + char buffer[512]; + + fp = popen("pidof metacity", "r"); + if(fp && fgets(buffer, sizeof buffer, fp)) result = Compositor::Metacity; + pclose(fp); + + fp = popen("pidof xfwm4", "r"); + if(fp && fgets(buffer, sizeof buffer, fp)) result = Compositor::Xfwm4; + pclose(fp); + + return result; +} + +bool compositor::enabled() { + switch(detect()) { + case Compositor::Metacity: return enabled_metacity(); + case Compositor::Xfwm4: return enabled_xfwm4(); + default: return false; + } +} + +bool compositor::enable(bool status) { + switch(detect()) { + case Compositor::Metacity: return enable_metacity(status); + case Compositor::Xfwm4: return enable_xfwm4(status); + default: return false; + } +} + #elif defined(PLATFORM_WINDOWS) bool compositor::enabled() { diff --git a/bsnes/nall/image.hpp b/bsnes/nall/image.hpp new file mode 100755 index 00000000..c79655eb --- /dev/null +++ b/bsnes/nall/image.hpp @@ -0,0 +1,433 @@ +#ifndef NALL_IMAGE_HPP +#define NALL_IMAGE_HPP + +#include +#include +#include +#include +#include + +namespace nall { + +struct image { + uint8_t *data; + unsigned width; + unsigned height; + unsigned pitch; + + bool endian; //0 = little, 1 = big + unsigned depth; + unsigned stride; + + struct Channel { + uint64_t mask; + unsigned depth; + unsigned shift; + } alpha, red, green, blue; + + typedef double (*interpolation)(double, double, double, double, double); + static inline unsigned bitDepth(uint64_t color); + static inline unsigned bitShift(uint64_t color); + static inline uint64_t normalize(uint64_t color, unsigned sourceDepth, unsigned targetDepth); + + inline image& operator=(const image &source); + inline image& operator=(image &&source); + inline image(const image &source); + inline image(image &&source); + inline image(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask); + inline ~image(); + + inline uint64_t read(const uint8_t *data) const; + inline void write(uint8_t *data, uint64_t value) const; + + inline void free(); + inline void allocate(unsigned width, unsigned height); + inline bool load(const string &filename); + inline void scale(unsigned width, unsigned height, interpolation op); + inline void transform(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask); + inline void alphaBlend(uint64_t alphaColor); + +protected: + inline uint64_t interpolate(double mu, const uint64_t *s, interpolation op); + inline void scaleX(unsigned width, interpolation op); + inline void scaleY(unsigned height, interpolation op); + inline bool loadBMP(const string &filename); + inline bool loadPNG(const string &filename); +}; + +//static + +unsigned image::bitDepth(uint64_t color) { + unsigned depth = 0; + if(color) while((color & 1) == 0) color >>= 1; + while((color & 1) == 1) { color >>= 1; depth++; } + return depth; +} + +unsigned image::bitShift(uint64_t color) { + unsigned shift = 0; + if(color) while((color & 1) == 0) { color >>= 1; shift++; } + return shift; +} + +uint64_t image::normalize(uint64_t color, unsigned sourceDepth, unsigned targetDepth) { + while(sourceDepth < targetDepth) { + color = (color << sourceDepth) | color; + sourceDepth += sourceDepth; + } + if(targetDepth < sourceDepth) color >>= (sourceDepth - targetDepth); + return color; +} + +//public + +image& image::operator=(const image &source) { + free(); + + width = source.width; + height = source.height; + pitch = source.pitch; + + endian = source.endian; + stride = source.stride; + + alpha = source.alpha; + red = source.red; + green = source.green; + blue = source.blue; + + data = new uint8_t[width * height * stride]; + memcpy(data, source.data, width * height * stride); + return *this; +} + +image& image::operator=(image &&source) { + width = source.width; + height = source.height; + pitch = source.pitch; + + endian = source.endian; + stride = source.stride; + + alpha = source.alpha; + red = source.red; + green = source.green; + blue = source.blue; + + data = source.data; + source.data = nullptr; + return *this; +} + +image::image(const image &source) : data(nullptr) { + operator=(source); +} + +image::image(image &&source) : data(nullptr) { + operator=(std::forward(source)); +} + +image::image(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask) : data(nullptr) { + width = 0, height = 0, pitch = 0; + + this->endian = endian; + this->depth = depth; + this->stride = (depth / 8) + ((depth & 7) > 0); + + alpha.mask = alphaMask, red.mask = redMask, green.mask = greenMask, blue.mask = blueMask; + alpha.depth = bitDepth(alpha.mask), alpha.shift = bitShift(alpha.mask); + red.depth = bitDepth(red.mask), red.shift = bitShift(red.mask); + green.depth = bitDepth(green.mask), green.shift = bitShift(green.mask); + blue.depth = bitDepth(blue.mask), blue.shift = bitShift(blue.mask); +} + +image::~image() { + free(); +} + +uint64_t image::read(const uint8_t *data) const { + uint64_t result = 0; + if(endian == 0) { + for(signed n = stride - 1; n >= 0; n--) result = (result << 8) | data[n]; + } else { + for(signed n = 0; n < stride; n++) result = (result << 8) | data[n]; + } + return result; +} + +void image::write(uint8_t *data, uint64_t value) const { + if(endian == 0) { + for(signed n = 0; n < stride; n++) { data[n] = value; value >>= 8; } + } else { + for(signed n = stride - 1; n >= 0; n--) { data[n] = value; value >>= 8; } + } +} + +void image::free() { + if(data) delete[] data; + data = nullptr; +} + +void image::allocate(unsigned width, unsigned height) { + free(); + data = new uint8_t[width * height * stride](); + pitch = width * stride; + this->width = width; + this->height = height; +} + +bool image::load(const string &filename) { + if(loadBMP(filename) == true) return true; + if(loadPNG(filename) == true) return true; + return false; +} + +void image::scale(unsigned outputWidth, unsigned outputHeight, interpolation op) { + scaleX(outputWidth, op); + scaleY(outputHeight, op); +} + +void image::transform(bool outputEndian, unsigned outputDepth, uint64_t outputAlphaMask, uint64_t outputRedMask, uint64_t outputGreenMask, uint64_t outputBlueMask) { + image output(outputEndian, outputDepth, outputAlphaMask, outputRedMask, outputGreenMask, outputBlueMask); + output.allocate(width, height); + + #pragma omp parallel for + for(unsigned y = 0; y < height; y++) { + uint8_t *dp = output.data + output.pitch * y; + uint8_t *sp = data + pitch * y; + for(unsigned x = 0; x < width; x++) { + uint64_t color = read(sp); + sp += stride; + + uint64_t a = (color & alpha.mask) >> alpha.shift; + uint64_t r = (color & red.mask) >> red.shift; + uint64_t g = (color & green.mask) >> green.shift; + uint64_t b = (color & blue.mask) >> blue.shift; + + a = normalize(a, alpha.depth, output.alpha.depth); + r = normalize(r, red.depth, output.red.depth); + g = normalize(g, green.depth, output.green.depth); + b = normalize(b, blue.depth, output.blue.depth); + + output.write(dp, (a << output.alpha.shift) + (r << output.red.shift) + (g << output.green.shift) + (b << output.blue.shift)); + dp += output.stride; + } + } + + operator=(std::move(output)); +} + +void image::alphaBlend(uint64_t alphaColor) { + uint64_t alphaR = (alphaColor & red.mask) >> red.shift; + uint64_t alphaG = (alphaColor & green.mask) >> green.shift; + uint64_t alphaB = (alphaColor & blue.mask) >> blue.shift; + + #pragma omp parallel for + for(unsigned y = 0; y < height; y++) { + uint8_t *dp = data + pitch * y; + for(unsigned x = 0; x < width; x++) { + uint64_t color = read(dp); + + uint64_t colorA = (color & alpha.mask) >> alpha.shift; + uint64_t colorR = (color & red.mask) >> red.shift; + uint64_t colorG = (color & green.mask) >> green.shift; + uint64_t colorB = (color & blue.mask) >> blue.shift; + double alphaScale = (double)colorA / (double)((1 << alpha.depth) - 1); + + colorA = (1 << alpha.depth) - 1; + colorR = (colorR * alphaScale) + (alphaR * (1.0 - alphaScale)); + colorG = (colorG * alphaScale) + (alphaG * (1.0 - alphaScale)); + colorB = (colorB * alphaScale) + (alphaB * (1.0 - alphaScale)); + + write(dp, (colorA << alpha.shift) + (colorR << red.shift) + (colorG << green.shift) + (colorB << blue.shift)); + dp += stride; + } + } +} + +//protected + +uint64_t image::interpolate(double mu, const uint64_t *s, double (*op)(double, double, double, double, double)) { + uint64_t aa = (s[0] & alpha.mask) >> alpha.shift, ar = (s[0] & red.mask) >> red.shift, + ag = (s[0] & green.mask) >> green.shift, ab = (s[0] & blue.mask) >> blue.shift; + uint64_t ba = (s[1] & alpha.mask) >> alpha.shift, br = (s[1] & red.mask) >> red.shift, + bg = (s[1] & green.mask) >> green.shift, bb = (s[1] & blue.mask) >> blue.shift; + uint64_t ca = (s[2] & alpha.mask) >> alpha.shift, cr = (s[2] & red.mask) >> red.shift, + cg = (s[2] & green.mask) >> green.shift, cb = (s[2] & blue.mask) >> blue.shift; + uint64_t da = (s[3] & alpha.mask) >> alpha.shift, dr = (s[3] & red.mask) >> red.shift, + dg = (s[3] & green.mask) >> green.shift, db = (s[3] & blue.mask) >> blue.shift; + + int64_t A = op(mu, aa, ba, ca, da); + int64_t R = op(mu, ar, br, cr, dr); + int64_t G = op(mu, ag, bg, cg, dg); + int64_t B = op(mu, ab, bb, cb, db); + + A = max(0, min(A, (1 << alpha.depth) - 1)); + R = max(0, min(R, (1 << red.depth) - 1)); + G = max(0, min(G, (1 << green.depth) - 1)); + B = max(0, min(B, (1 << blue.depth) - 1)); + + return (A << alpha.shift) + (R << red.shift) + (G << green.shift) + (B << blue.shift); +} + +void image::scaleX(unsigned outputWidth, interpolation op) { + uint8_t *outputData = new uint8_t[outputWidth * height * stride]; + unsigned outputPitch = outputWidth * stride; + double step = (double)width / (double)outputWidth; + + #pragma omp parallel for + for(unsigned y = 0; y < height; y++) { + uint8_t *dp = outputData + outputPitch * y; + uint8_t *sp = data + pitch * y; + + double fraction = 0.0; + uint64_t s[4] = { read(sp), read(sp), read(sp), read(sp) }; + + for(unsigned x = 0; x < width; x++) { + if(sp >= data + pitch * height) break; + s[0] = s[1]; + s[1] = s[2]; + s[2] = s[3]; + s[3] = read(sp); + + while(fraction <= 1.0) { + if(dp >= outputData + outputPitch * height) break; + write(dp, interpolate(fraction, (const uint64_t*)&s, op)); + dp += stride; + fraction += step; + } + + sp += stride; + fraction -= 1.0; + } + } + + free(); + data = outputData; + width = outputWidth; + pitch = width * stride; +} + +void image::scaleY(unsigned outputHeight, interpolation op) { + uint8_t *outputData = new uint8_t[width * outputHeight * stride]; + double step = (double)height / (double)outputHeight; + + #pragma omp parallel for + for(unsigned x = 0; x < width; x++) { + uint8_t *dp = outputData + stride * x; + uint8_t *sp = data + stride * x; + + double fraction = 0.0; + uint64_t s[4] = { read(sp), read(sp), read(sp), read(sp) }; + + for(unsigned y = 0; y < height; y++) { + if(sp >= data + pitch * height) break; + s[0] = s[1]; + s[1] = s[2]; + s[2] = s[3]; + s[3] = read(sp); + + while(fraction <= 1.0) { + if(dp >= outputData + pitch * outputHeight) break; + write(dp, interpolate(fraction, (const uint64_t*)&s, op)); + dp += pitch; + fraction += step; + } + + sp += pitch; + fraction -= 1.0; + } + } + + free(); + data = outputData; + height = outputHeight; +} + +bool image::loadBMP(const string &filename) { + uint32_t *outputData; + unsigned outputWidth, outputHeight; + if(bmp::read(filename, outputData, outputWidth, outputHeight) == false) return false; + + allocate(outputWidth, outputHeight); + const uint32_t *sp = outputData; + uint8_t *dp = data; + + for(unsigned y = 0; y < outputHeight; y++) { + for(unsigned x = 0; x < outputWidth; x++) { + uint32_t color = *sp++; + uint64_t a = normalize((uint8_t)(color >> 24), 8, alpha.depth); + uint64_t r = normalize((uint8_t)(color >> 16), 8, red.depth); + uint64_t g = normalize((uint8_t)(color >> 8), 8, green.depth); + uint64_t b = normalize((uint8_t)(color >> 0), 8, blue.depth); + write(dp, (a << alpha.shift) + (r << red.shift) + (g << green.shift) + (b << blue.shift)); + dp += stride; + } + } + + delete[] outputData; + return true; +} + +bool image::loadPNG(const string &filename) { + png source; + if(source.decode(filename) == false) return false; + + allocate(source.info.width, source.info.height); + const uint8_t *sp = source.data; + uint8_t *dp = data; + + auto decode = [&]() -> uint64_t { + uint64_t p, r, g, b, a; + + switch(source.info.colorType) { + case 0: //L + r = g = b = source.readbits(sp); + a = (1 << source.info.bitDepth) - 1; + break; + case 2: //R,G,B + r = source.readbits(sp); + g = source.readbits(sp); + b = source.readbits(sp); + a = (1 << source.info.bitDepth) - 1; + break; + case 3: //P + p = source.readbits(sp); + r = source.info.palette[p][0]; + g = source.info.palette[p][1]; + b = source.info.palette[p][2]; + a = (1 << source.info.bitDepth) - 1; + break; + case 4: //L,A + r = g = b = source.readbits(sp); + a = source.readbits(sp); + break; + case 6: //R,G,B,A + r = source.readbits(sp); + g = source.readbits(sp); + b = source.readbits(sp); + a = source.readbits(sp); + break; + } + + a = normalize(a, source.info.bitDepth, alpha.depth); + r = normalize(r, source.info.bitDepth, red.depth); + g = normalize(g, source.info.bitDepth, green.depth); + b = normalize(b, source.info.bitDepth, blue.depth); + + return (a << alpha.shift) + (r << red.shift) + (g << green.shift) + (b << blue.shift); + }; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width; x++) { + write(dp, decode()); + dp += stride; + } + } + + return true; +} + +} + +#endif diff --git a/bsnes/nall/interpolation.hpp b/bsnes/nall/interpolation.hpp new file mode 100755 index 00000000..46a09a49 --- /dev/null +++ b/bsnes/nall/interpolation.hpp @@ -0,0 +1,59 @@ +#ifndef NALL_INTERPOLATION_HPP +#define NALL_INTERPOLATION_HPP + +namespace nall { + +struct Interpolation { + static inline double Nearest(double mu, double a, double b, double c, double d) { + return (mu < 0.5 ? c : d); + } + + static inline double Sublinear(double mu, double a, double b, double c, double d) { + mu = ((mu - 0.5) * 2.0) + 0.5; + if(mu < 0) mu = 0; + if(mu > 1) mu = 1; + return c * (1.0 - mu) + d * mu; + } + + static inline double Linear(double mu, double a, double b, double c, double d) { + return c * (1.0 - mu) + d * mu; + } + + static inline double Cosine(double mu, double a, double b, double c, double d) { + mu = (1.0 - cos(mu * 3.14159265)) / 2.0; + return c * (1.0 - mu) + d * mu; + } + + static inline double Cubic(double mu, double a, double b, double c, double d) { + double A = d - c - a + b; + double B = a - b - A; + double C = c - a; + double D = b; + return A * (mu * mu * mu) + B * (mu * mu) + C * mu + D; + } + + static inline double Hermite(double mu1, double a, double b, double c, double d) { + const double tension = 0.0; //-1 = low, 0 = normal, +1 = high + const double bias = 0.0; //-1 = left, 0 = even, +1 = right + double mu2, mu3, m0, m1, a0, a1, a2, a3; + + mu2 = mu1 * mu1; + mu3 = mu2 * mu1; + + m0 = (b - a) * (1.0 + bias) * (1.0 - tension) / 2.0; + m0 += (c - b) * (1.0 - bias) * (1.0 - tension) / 2.0; + m1 = (c - b) * (1.0 + bias) * (1.0 - tension) / 2.0; + m1 += (d - c) * (1.0 - bias) * (1.0 - tension) / 2.0; + + a0 = +2 * mu3 - 3 * mu2 + 1; + a1 = mu3 - 2 * mu2 + mu1; + a2 = mu3 - mu2; + a3 = -2 * mu3 + 3 * mu2; + + return (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c); + } +}; + +} + +#endif diff --git a/bsnes/nall/png.hpp b/bsnes/nall/png.hpp index a39c85c5..4b474724 100755 --- a/bsnes/nall/png.hpp +++ b/bsnes/nall/png.hpp @@ -10,9 +10,12 @@ namespace nall { struct png { - uint32_t *data; - unsigned size; - + //colorType: + //0 = L + //2 = R,G,B + //3 = P + //4 = L,A + //6 = R,G,B,A struct Info { unsigned width; unsigned height; @@ -28,13 +31,14 @@ struct png { uint8_t palette[256][3]; } info; - uint8_t *rawData; - unsigned rawSize; + uint8_t *data; + unsigned size; inline bool decode(const string &filename); inline bool decode(const uint8_t *sourceData, unsigned sourceSize); - inline void transform(); - inline void alphaTransform(uint32_t rgb = 0xffffff); + inline unsigned readbits(const uint8_t *&data); + unsigned bitpos; + inline png(); inline ~png(); @@ -46,16 +50,11 @@ protected: IEND = 0x49454e44, }; - unsigned bitpos; - inline unsigned interlace(unsigned pass, unsigned index); inline unsigned inflateSize(); inline bool deinterlace(const uint8_t *&inputData, unsigned pass); inline bool filter(uint8_t *outputData, const uint8_t *inputData, unsigned width, unsigned height); inline unsigned read(const uint8_t *data, unsigned length); - inline unsigned decode(const uint8_t *&data); - inline unsigned readbits(const uint8_t *&data); - inline unsigned scale(unsigned n); }; bool png::decode(const string &filename) { @@ -146,14 +145,14 @@ bool png::decode(const uint8_t *sourceData, unsigned sourceSize) { return false; } - rawSize = info.width * info.height * info.bytesPerPixel; - rawData = new uint8_t[rawSize]; + size = info.width * info.height * info.bytesPerPixel; + data = new uint8_t[size]; if(info.interlaceMethod == 0) { - if(filter(rawData, interlacedData, info.width, info.height) == false) { + if(filter(data, interlacedData, info.width, info.height) == false) { delete[] interlacedData; - delete[] rawData; - rawData = 0; + delete[] data; + data = 0; return false; } } else { @@ -161,8 +160,8 @@ bool png::decode(const uint8_t *sourceData, unsigned sourceSize) { for(unsigned pass = 0; pass < 7; pass++) { if(deinterlace(passData, pass) == false) { delete[] interlacedData; - delete[] rawData; - rawData = 0; + delete[] data; + data = 0; return false; } } @@ -216,7 +215,7 @@ bool png::deinterlace(const uint8_t *&inputData, unsigned pass) { const uint8_t *rd = outputData; for(unsigned y = yo; y < info.height; y += yd) { - uint8_t *wr = rawData + y * info.pitch; + uint8_t *wr = data + y * info.pitch; for(unsigned x = xo; x < info.width; x += xd) { for(unsigned b = 0; b < info.bytesPerPixel; b++) { wr[x * info.bytesPerPixel + b] = *rd++; @@ -298,42 +297,6 @@ unsigned png::read(const uint8_t *data, unsigned length) { return result; } -unsigned png::decode(const uint8_t *&data) { - unsigned p, r, g, b, a; - - switch(info.colorType) { - case 0: //L - r = g = b = scale(readbits(data)); - a = 0xff; - break; - case 2: //R,G,B - r = scale(readbits(data)); - g = scale(readbits(data)); - b = scale(readbits(data)); - a = 0xff; - break; - case 3: //P - p = readbits(data); - r = info.palette[p][0]; - g = info.palette[p][1]; - b = info.palette[p][2]; - a = 0xff; - break; - case 4: //L,A - r = g = b = scale(readbits(data)); - a = scale(readbits(data)); - break; - case 6: //R,G,B,A - r = scale(readbits(data)); - g = scale(readbits(data)); - b = scale(readbits(data)); - a = scale(readbits(data)); - break; - } - - return (a << 24) | (r << 16) | (g << 8) | (b << 0); -} - unsigned png::readbits(const uint8_t *&data) { unsigned result = 0; switch(info.bitDepth) { @@ -363,62 +326,12 @@ unsigned png::readbits(const uint8_t *&data) { return result; } -unsigned png::scale(unsigned n) { - switch(info.bitDepth) { - case 1: return n ? 0xff : 0x00; - case 2: return n * 0x55; - case 4: return n * 0x11; - case 8: return n; - case 16: return n >> 8; - } - return 0; -} - -void png::transform() { - if(data) delete[] data; - data = new uint32_t[info.width * info.height]; - +png::png() : data(nullptr) { bitpos = 0; - const uint8_t *rd = rawData; - for(unsigned y = 0; y < info.height; y++) { - uint32_t *wr = data + y * info.width; - for(unsigned x = 0; x < info.width; x++) { - wr[x] = decode(rd); - } - } -} - -void png::alphaTransform(uint32_t rgb) { - transform(); - - uint8_t ir = rgb >> 16; - uint8_t ig = rgb >> 8; - uint8_t ib = rgb >> 0; - - uint32_t *p = data; - for(unsigned y = 0; y < info.height; y++) { - for(unsigned x = 0; x < info.width; x++) { - uint32_t pixel = *p; - uint8_t a = pixel >> 24; - uint8_t r = pixel >> 16; - uint8_t g = pixel >> 8; - uint8_t b = pixel >> 0; - - r = (r * a) + (ir * (255 - a)) >> 8; - g = (g * a) + (ig * (255 - a)) >> 8; - b = (b * a) + (ib * (255 - a)) >> 8; - - *p++ = (255 << 24) | (r << 16) | (g << 8) | (b << 0); - } - } -} - -png::png() : data(nullptr), rawData(nullptr) { } png::~png() { if(data) delete[] data; - if(rawData) delete[] rawData; } } diff --git a/bsnes/nall/string.hpp b/bsnes/nall/string.hpp index 884e31e8..288424bc 100755 --- a/bsnes/nall/string.hpp +++ b/bsnes/nall/string.hpp @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/bsnes/nall/string/base.hpp b/bsnes/nall/string/base.hpp index 33ff1c7a..97000bef 100755 --- a/bsnes/nall/string/base.hpp +++ b/bsnes/nall/string/base.hpp @@ -183,8 +183,8 @@ namespace nall { template inline string ldecimal(uintmax_t value); template inline string hex(uintmax_t value); template inline string binary(uintmax_t value); - inline unsigned fp(char *str, double value); - inline string fp(double value); + inline unsigned fp(char *str, long double value); + inline string fp(long double value); //variadic.hpp template inline void print(Args&&... args); diff --git a/bsnes/nall/string/cast.hpp b/bsnes/nall/string/cast.hpp index 12f525ad..7c7e276b 100755 --- a/bsnes/nall/string/cast.hpp +++ b/bsnes/nall/string/cast.hpp @@ -101,19 +101,19 @@ template struct stringify> { template<> struct stringify { char data[256]; operator const char*() const { return data; } - stringify(float value) { snprintf(data, 255, "%f", value); } + stringify(float value) { fp(data, value); } }; template<> struct stringify { char data[256]; operator const char*() const { return data; } - stringify(double value) { snprintf(data, 255, "%f", value); } + stringify(double value) { fp(data, value); } }; template<> struct stringify { char data[256]; operator const char*() const { return data; } - stringify(long double value) { snprintf(data, 255, "%Lf", value); } + stringify(long double value) { fp(data, value); } }; // strings diff --git a/bsnes/nall/string/math-fixed-point.hpp b/bsnes/nall/string/math-fixed-point.hpp new file mode 100755 index 00000000..744621d1 --- /dev/null +++ b/bsnes/nall/string/math-fixed-point.hpp @@ -0,0 +1,157 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace fixedpoint { + +static nall::function eval_fallback; + +static intmax_t eval_integer(const char *& s) { + if(!*s) throw "unrecognized integer"; + intmax_t value = 0, x = *s, y = *(s + 1); + + //hexadecimal + if(x == '0' && (y == 'X' || y == 'x')) { + s += 2; + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 16 + (*s++ - '0'); continue; } + if(*s >= 'A' && *s <= 'F') { value = value * 16 + (*s++ - 'A' + 10); continue; } + if(*s >= 'a' && *s <= 'f') { value = value * 16 + (*s++ - 'a' + 10); continue; } + return value; + } + } + + //binary + if(x == '0' && (y == 'B' || y == 'b')) { + s += 2; + while(true) { + if(*s == '0' || *s == '1') { value = value * 2 + (*s++ - '0'); continue; } + return value; + } + } + + //octal (or decimal '0') + if(x == '0') { + s += 1; + while(true) { + if(*s >= '0' && *s <= '7') { value = value * 8 + (*s++ - '0'); continue; } + return value; + } + } + + //decimal + if(x >= '0' && x <= '9') { + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 10 + (*s++ - '0'); continue; } + return value; + } + } + + //char + if(x == '\'' && y != '\'') { + s += 1; + while(true) { + value = value * 256 + *s++; + if(*s == '\'') { s += 1; return value; } + if(!*s) throw "mismatched char"; + } + } + + throw "unrecognized integer"; +} + +static intmax_t eval(const char *&s, int depth = 0) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) throw "unrecognized token"; + intmax_t value = 0, x = *s, y = *(s + 1); + + if(*s == '(') { + value = eval(++s, 1); + if(*s++ != ')') throw "mismatched group"; + } + + else if(x == '!') value = !eval(++s, 13); + else if(x == '~') value = ~eval(++s, 13); + else if(x == '+') value = +eval(++s, 13); + else if(x == '-') value = -eval(++s, 13); + + else if((x >= '0' && x <= '9') || x == '\'') value = eval_integer(s); + + else if(eval_fallback) value = eval_fallback(s); //optional user-defined syntax parsing + + else throw "unrecognized token"; + + while(true) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) break; + x = *s, y = *(s + 1); + + if(depth >= 13) break; + if(x == '*') { value *= eval(++s, 13); continue; } + if(x == '/') { intmax_t result = eval(++s, 13); if(result == 0) throw "division by zero"; value /= result; continue; } + if(x == '%') { intmax_t result = eval(++s, 13); if(result == 0) throw "division by zero"; value %= result; continue; } + + if(depth >= 12) break; + if(x == '+') { value += eval(++s, 12); continue; } + if(x == '-') { value -= eval(++s, 12); continue; } + + if(depth >= 11) break; + if(x == '<' && y == '<') { value <<= eval(++++s, 11); continue; } + if(x == '>' && y == '>') { value >>= eval(++++s, 11); continue; } + + if(depth >= 10) break; + if(x == '<' && y == '=') { value = value <= eval(++++s, 10); continue; } + if(x == '>' && y == '=') { value = value >= eval(++++s, 10); continue; } + if(x == '<') { value = value < eval(++s, 10); continue; } + if(x == '>') { value = value > eval(++s, 10); continue; } + + if(depth >= 9) break; + if(x == '=' && y == '=') { value = value == eval(++++s, 9); continue; } + if(x == '!' && y == '=') { value = value != eval(++++s, 9); continue; } + + if(depth >= 8) break; + if(x == '&' && y != '&') { value = value & eval(++s, 8); continue; } + + if(depth >= 7) break; + if(x == '^' && y != '^') { value = value ^ eval(++s, 7); continue; } + + if(depth >= 6) break; + if(x == '|' && y != '|') { value = value | eval(++s, 6); continue; } + + if(depth >= 5) break; + if(x == '&' && y == '&') { value = eval(++++s, 5) && value; continue; } + + if(depth >= 4) break; + if(x == '^' && y == '^') { value = (!eval(++++s, 4) != !value); continue; } + + if(depth >= 3) break; + if(x == '|' && y == '|') { value = eval(++++s, 3) || value; continue; } + + if(x == '?') { + intmax_t lhs = eval(++s, 2); + if(*s != ':') throw "mismatched ternary"; + intmax_t rhs = eval(++s, 2); + value = value ? lhs : rhs; + continue; + } + if(depth >= 2) break; + + if(depth > 0 && x == ')') break; + + throw "unrecognized token"; + } + + return value; +} + +static bool eval(const char *s, intmax_t &result) { + try { + result = eval(s); + return true; + } catch(const char*) { + result = 0; + return false; + } +} + +} + +#endif diff --git a/bsnes/nall/string/math-floating-point.hpp b/bsnes/nall/string/math-floating-point.hpp new file mode 100755 index 00000000..85680aad --- /dev/null +++ b/bsnes/nall/string/math-floating-point.hpp @@ -0,0 +1,149 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace floatingpoint { + +static nall::function eval_fallback; + +static double eval_integer(const char *&s) { + if(!*s) throw "unrecognized integer"; + intmax_t value = 0, radix = 0, x = *s, y = *(s + 1); + + //hexadecimal + if(x == '0' && (y == 'X' || y == 'x')) { + s += 2; + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 16 + (*s++ - '0'); continue; } + if(*s >= 'A' && *s <= 'F') { value = value * 16 + (*s++ - 'A' + 10); continue; } + if(*s >= 'a' && *s <= 'f') { value = value * 16 + (*s++ - 'a' + 10); continue; } + return value; + } + } + + //binary + if(x == '0' && (y == 'B' || y == 'b')) { + s += 2; + while(true) { + if(*s == '0' || *s == '1') { value = value * 2 + (*s++ - '0'); continue; } + return value; + } + } + + //octal (or decimal '0') + if(x == '0' && y != '.') { + s += 1; + while(true) { + if(*s >= '0' && *s <= '7') { value = value * 8 + (*s++ - '0'); continue; } + return value; + } + } + + //decimal + if(x >= '0' && x <= '9') { + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 10 + (*s++ - '0'); continue; } + if(*s == '.') { s++; break; } + return value; + } + //floating-point + while(true) { + if(*s >= '0' && *s <= '9') { radix = radix * 10 + (*s++ - '0'); continue; } + return atof(nall::string{ nall::decimal(value), ".", nall::decimal(radix) }); + } + } + + //char + if(x == '\'' && y != '\'') { + s += 1; + while(true) { + value = value * 256 + *s++; + if(*s == '\'') { s += 1; return value; } + if(!*s) throw "mismatched char"; + } + } + + throw "unrecognized integer"; +} + +static double eval(const char *&s, int depth = 0) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) throw "unrecognized token"; + double value = 0, x = *s, y = *(s + 1); + + if(*s == '(') { + value = eval(++s, 1); + if(*s++ != ')') throw "mismatched group"; + } + + else if(x == '!') value = !eval(++s, 9); + else if(x == '+') value = +eval(++s, 9); + else if(x == '-') value = -eval(++s, 9); + + else if((x >= '0' && x <= '9') || x == '\'') value = eval_integer(s); + + else if(eval_fallback) value = eval_fallback(s); //optional user-defined syntax parsing + + else throw "unrecognized token"; + + while(true) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) break; + x = *s, y = *(s + 1); + + if(depth >= 9) break; + if(x == '*') { value *= eval(++s, 9); continue; } + if(x == '/') { double result = eval(++s, 9); if(result == 0.0) throw "division by zero"; value /= result; continue; } + + if(depth >= 8) break; + if(x == '+') { value += eval(++s, 8); continue; } + if(x == '-') { value -= eval(++s, 8); continue; } + + if(depth >= 7) break; + if(x == '<' && y == '=') { value = value <= eval(++++s, 7); continue; } + if(x == '>' && y == '=') { value = value >= eval(++++s, 7); continue; } + if(x == '<') { value = value < eval(++s, 7); continue; } + if(x == '>') { value = value > eval(++s, 7); continue; } + + if(depth >= 6) break; + if(x == '=' && y == '=') { value = value == eval(++++s, 6); continue; } + if(x == '!' && y == '=') { value = value != eval(++++s, 6); continue; } + + if(depth >= 5) break; + if(x == '&' && y == '&') { value = eval(++++s, 5) && value; continue; } + + if(depth >= 4) break; + if(x == '^' && y == '^') { value = (!eval(++++s, 4) != !value); continue; } + + if(depth >= 3) break; + if(x == '|' && y == '|') { value = eval(++++s, 3) || value; continue; } + + if(x == '?') { + double lhs = eval(++s, 2); + if(*s != ':') throw "mismatched ternary"; + double rhs = eval(++s, 2); + value = value ? lhs : rhs; + continue; + } + if(depth >= 2) break; + + if(depth > 0 && x == ')') break; + + throw "unrecognized token"; + } + + return value; +} + +static bool eval(const char *s, double &result) { + try { + result = eval(s); + return true; + } catch(const char*e) { +printf("%s\n", e); + result = 0; + return false; + } +} + +} + +#endif diff --git a/bsnes/nall/string/utility.hpp b/bsnes/nall/string/utility.hpp index e5aa0d11..2212688b 100755 --- a/bsnes/nall/string/utility.hpp +++ b/bsnes/nall/string/utility.hpp @@ -253,9 +253,14 @@ template string binary(uintmax_t value) { //using sprintf is certainly not the most ideal method to convert //a double to a string ... but attempting to parse a double by //hand, digit-by-digit, results in subtle rounding errors. -unsigned fp(char *str, double value) { +unsigned fp(char *str, long double value) { char buffer[256]; - sprintf(buffer, "%f", value); + #ifdef _WIN32 + //Windows C-runtime does not support long double via sprintf() + sprintf(buffer, "%f", (double)value); + #else + sprintf(buffer, "%Lf", value); + #endif //remove excess 0's in fraction (2.500000 -> 2.5) for(char *p = buffer; *p; p++) { @@ -274,7 +279,7 @@ unsigned fp(char *str, double value) { return length + 1; } -string fp(double value) { +string fp(long double value) { string temp; temp.reserve(fp(0, value)); fp(temp(), value); diff --git a/bsnes/nes/input/input.cpp b/bsnes/nes/input/input.cpp index d85fc93d..c1fd7de1 100755 --- a/bsnes/nes/input/input.cpp +++ b/bsnes/nes/input/input.cpp @@ -42,7 +42,6 @@ void Input::connect(bool port, Device device) { } void Input::power() { - reset(); } void Input::reset() { diff --git a/bsnes/nes/scheduler/scheduler.cpp b/bsnes/nes/scheduler/scheduler.cpp index eca5857d..9beca699 100755 --- a/bsnes/nes/scheduler/scheduler.cpp +++ b/bsnes/nes/scheduler/scheduler.cpp @@ -16,7 +16,6 @@ void Scheduler::exit(ExitReason reason) { } void Scheduler::power() { - reset(); } void Scheduler::reset() { diff --git a/bsnes/phoenix/core/core.cpp b/bsnes/phoenix/core/core.cpp index 0426ba10..fb21bdf4 100755 --- a/bsnes/phoenix/core/core.cpp +++ b/bsnes/phoenix/core/core.cpp @@ -27,85 +27,6 @@ Font::Font(const string &description): description(description) { } -//Image -//===== - -bool Image::load(const string &filename, const Color &alpha) { - if(data) { delete[] data; data = nullptr; } - - file fp; - if(fp.open(filename, file::mode::read) == false) return false; - uint8_t d0 = fp.read(); - uint8_t d1 = fp.read(); - uint8_t d2 = fp.read(); - uint8_t d3 = fp.read(); - fp.close(); - - if(d0 == 'B' && d1 == 'M') { - bmp::read(filename, data, width, height); - } - - if(d0 == 0x89 && d1 == 'P' && d2 == 'N' && d3 == 'G') { - png image; - if(image.decode(filename)) { - image.alphaTransform((alpha.red << 16) + (alpha.green << 8) + (alpha.blue << 0)); - width = image.info.width, height = image.info.height; - data = new uint32_t[width * height]; - memcpy(data, image.data, width * height * sizeof(uint32_t)); - } - } - - return data; -} - -void Image::load(const uint32_t *data, const Size &size) { - if(data) { delete[] data; data = nullptr; } - width = size.width, height = size.height; - this->data = new uint32_t[width * height]; - memcpy(this->data, data, width * height * sizeof(uint32_t)); -} - -Image& Image::operator=(const Image &source) { - if(this == &source) return *this; - if(data) { delete[] data; data = nullptr; } - if(source.data == nullptr) return *this; - width = source.width, height = source.height; - data = new uint32_t[width * height]; - memcpy(data, source.data, width * height * sizeof(uint32_t)); - return *this; -} - -Image& Image::operator=(Image &&source) { - if(this == &source) return *this; - if(data) { delete[] data; data = nullptr; } - data = source.data, width = source.width, height = source.height; - source.data = nullptr; - return *this; -} - -Image::Image() : data(nullptr) { -} - -Image::Image(const string &filename, const Color &alpha) : data(nullptr) { - load(filename, alpha); -} - -Image::Image(const uint32_t *data, const Size &size) { - load(data, size); -} - -Image::Image(const Image &source) : data(nullptr) { - operator=(source); -} - -Image::Image(Image &&source) : data(nullptr) { - operator=(std::forward(source)); -} - -Image::~Image() { - if(data) delete[] data; -} - //Object //====== @@ -739,7 +660,7 @@ uint32_t* Canvas::data() { return state.data; } -bool Canvas::setImage(const Image &image) { +bool Canvas::setImage(const nall::image &image) { if(image.data == nullptr || image.width == 0 || image.height == 0) return false; state.width = image.width; state.height = image.height; diff --git a/bsnes/phoenix/core/core.hpp b/bsnes/phoenix/core/core.hpp index d75b1efc..5d419112 100755 --- a/bsnes/phoenix/core/core.hpp +++ b/bsnes/phoenix/core/core.hpp @@ -72,21 +72,6 @@ struct Font { Font(const nall::string &description = ""); }; -struct Image { - uint32_t *data; - unsigned width, height; - bool load(const nall::string &filename, const Color &alpha = Color{255, 255, 255}); - void load(const uint32_t *data, const Size &size); - Image& operator=(const Image &source); - Image& operator=(Image &&source); - Image(); - Image(const nall::string &filename, const Color &alpha = Color{255, 255, 255}); - Image(const uint32_t *data, const Size &size); - Image(const Image &source); - Image(Image &&source); - ~Image(); -}; - struct Object { Object(pObject &p); Object& operator=(const Object&) = delete; @@ -330,7 +315,7 @@ struct Button : private nall::base_from_member, Widget { struct Canvas : private nall::base_from_member, Widget { uint32_t* data(); - bool setImage(const Image &image); + bool setImage(const nall::image &image); void setSize(const Size &size); Size size(); void update(); diff --git a/bsnes/phoenix/gtk/widget/hex-edit.cpp b/bsnes/phoenix/gtk/widget/hex-edit.cpp index 9187d33f..9c0dbdef 100755 --- a/bsnes/phoenix/gtk/widget/hex-edit.cpp +++ b/bsnes/phoenix/gtk/widget/hex-edit.cpp @@ -130,17 +130,17 @@ bool pHexEdit::keyPress(unsigned scancode) { unsigned cursorY = position / lineWidth; unsigned cursorX = position % lineWidth; - if(scancode == GDK_KEY_Home) { + if(scancode == GDK_Home) { setCursorPosition(cursorY * lineWidth + 10); return true; } - if(scancode == GDK_KEY_End) { + if(scancode == GDK_End) { setCursorPosition(cursorY * lineWidth + 10 + (hexEdit.state.columns * 3 - 1)); return true; } - if(scancode == GDK_KEY_Up) { + if(scancode == GDK_Up) { if(cursorY != 0) return false; signed newOffset = hexEdit.state.offset - hexEdit.state.columns; @@ -151,7 +151,7 @@ bool pHexEdit::keyPress(unsigned scancode) { return true; } - if(scancode == GDK_KEY_Down) { + if(scancode == GDK_Down) { if(cursorY != hexEdit.state.rows - 1) return false; signed newOffset = hexEdit.state.offset + hexEdit.state.columns; @@ -162,7 +162,7 @@ bool pHexEdit::keyPress(unsigned scancode) { return true; } - if(scancode == GDK_KEY_Page_Up) { + if(scancode == GDK_Page_Up) { signed newOffset = hexEdit.state.offset - hexEdit.state.columns * hexEdit.state.rows; if(newOffset >= 0) { hexEdit.setOffset(newOffset); @@ -173,7 +173,7 @@ bool pHexEdit::keyPress(unsigned scancode) { return true; } - if(scancode == GDK_KEY_Page_Down) { + if(scancode == GDK_Page_Down) { signed newOffset = hexEdit.state.offset + hexEdit.state.columns * hexEdit.state.rows; for(unsigned n = 0; n < hexEdit.state.rows; n++) { if(newOffset + hexEdit.state.columns * hexEdit.state.rows - (hexEdit.state.columns - 1) <= hexEdit.state.length) { diff --git a/bsnes/phoenix/phoenix.hpp b/bsnes/phoenix/phoenix.hpp index 24ffa185..543ff18b 100755 --- a/bsnes/phoenix/phoenix.hpp +++ b/bsnes/phoenix/phoenix.hpp @@ -2,10 +2,9 @@ #define PHOENIX_HPP #include -#include #include #include -#include +#include #include #include #include diff --git a/bsnes/phoenix/qt/platform.moc b/bsnes/phoenix/qt/platform.moc index b542700e..9df32e50 100755 --- a/bsnes/phoenix/qt/platform.moc +++ b/bsnes/phoenix/qt/platform.moc @@ -1,8 +1,8 @@ /**************************************************************************** ** Meta object code from reading C++ file 'platform.moc.hpp' ** -** Created: Wed Nov 9 02:07:41 2011 -** by: The Qt Meta Object Compiler version 62 (Qt 4.7.0) +** Created: Tue Nov 29 20:27:15 2011 +** by: The Qt Meta Object Compiler version 62 (Qt 4.6.3) ** ** WARNING! All changes made in this file will be lost! *****************************************************************************/ @@ -10,7 +10,7 @@ #if !defined(Q_MOC_OUTPUT_REVISION) #error "The header file 'platform.moc.hpp' doesn't include ." #elif Q_MOC_OUTPUT_REVISION != 62 -#error "This file was generated using the moc from 4.7.0. It" +#error "This file was generated using the moc from 4.6.3. It" #error "cannot be used with the include files from this version of Qt." #error "(The moc has changed too much.)" #endif @@ -19,7 +19,7 @@ QT_BEGIN_MOC_NAMESPACE static const uint qt_meta_data_pTimer[] = { // content: - 5, // revision + 4, // revision 0, // classname 0, 0, // classinfo 1, 14, // methods @@ -80,7 +80,7 @@ int pTimer::qt_metacall(QMetaObject::Call _c, int _id, void **_a) static const uint qt_meta_data_pWindow[] = { // content: - 5, // revision + 4, // revision 0, // classname 0, 0, // classinfo 0, 0, // methods @@ -131,7 +131,7 @@ int pWindow::qt_metacall(QMetaObject::Call _c, int _id, void **_a) static const uint qt_meta_data_pItem[] = { // content: - 5, // revision + 4, // revision 0, // classname 0, 0, // classinfo 1, 14, // methods @@ -192,7 +192,7 @@ int pItem::qt_metacall(QMetaObject::Call _c, int _id, void **_a) static const uint qt_meta_data_pCheckItem[] = { // content: - 5, // revision + 4, // revision 0, // classname 0, 0, // classinfo 1, 14, // methods @@ -253,7 +253,7 @@ int pCheckItem::qt_metacall(QMetaObject::Call _c, int _id, void **_a) static const uint qt_meta_data_pRadioItem[] = { // content: - 5, // revision + 4, // revision 0, // classname 0, 0, // classinfo 1, 14, // methods @@ -314,7 +314,7 @@ int pRadioItem::qt_metacall(QMetaObject::Call _c, int _id, void **_a) static const uint qt_meta_data_pButton[] = { // content: - 5, // revision + 4, // revision 0, // classname 0, 0, // classinfo 1, 14, // methods @@ -375,7 +375,7 @@ int pButton::qt_metacall(QMetaObject::Call _c, int _id, void **_a) static const uint qt_meta_data_pCanvas[] = { // content: - 5, // revision + 4, // revision 0, // classname 0, 0, // classinfo 0, 0, // methods @@ -426,7 +426,7 @@ int pCanvas::qt_metacall(QMetaObject::Call _c, int _id, void **_a) static const uint qt_meta_data_pCheckBox[] = { // content: - 5, // revision + 4, // revision 0, // classname 0, 0, // classinfo 1, 14, // methods @@ -487,7 +487,7 @@ int pCheckBox::qt_metacall(QMetaObject::Call _c, int _id, void **_a) static const uint qt_meta_data_pComboBox[] = { // content: - 5, // revision + 4, // revision 0, // classname 0, 0, // classinfo 1, 14, // methods @@ -548,7 +548,7 @@ int pComboBox::qt_metacall(QMetaObject::Call _c, int _id, void **_a) static const uint qt_meta_data_pHexEdit[] = { // content: - 5, // revision + 4, // revision 0, // classname 0, 0, // classinfo 1, 14, // methods @@ -609,7 +609,7 @@ int pHexEdit::qt_metacall(QMetaObject::Call _c, int _id, void **_a) static const uint qt_meta_data_pHorizontalScrollBar[] = { // content: - 5, // revision + 4, // revision 0, // classname 0, 0, // classinfo 1, 14, // methods @@ -670,7 +670,7 @@ int pHorizontalScrollBar::qt_metacall(QMetaObject::Call _c, int _id, void **_a) static const uint qt_meta_data_pHorizontalSlider[] = { // content: - 5, // revision + 4, // revision 0, // classname 0, 0, // classinfo 1, 14, // methods @@ -731,7 +731,7 @@ int pHorizontalSlider::qt_metacall(QMetaObject::Call _c, int _id, void **_a) static const uint qt_meta_data_pLineEdit[] = { // content: - 5, // revision + 4, // revision 0, // classname 0, 0, // classinfo 2, 14, // methods @@ -794,7 +794,7 @@ int pLineEdit::qt_metacall(QMetaObject::Call _c, int _id, void **_a) static const uint qt_meta_data_pListView[] = { // content: - 5, // revision + 4, // revision 0, // classname 0, 0, // classinfo 3, 14, // methods @@ -861,7 +861,7 @@ int pListView::qt_metacall(QMetaObject::Call _c, int _id, void **_a) static const uint qt_meta_data_pRadioBox[] = { // content: - 5, // revision + 4, // revision 0, // classname 0, 0, // classinfo 1, 14, // methods @@ -922,7 +922,7 @@ int pRadioBox::qt_metacall(QMetaObject::Call _c, int _id, void **_a) static const uint qt_meta_data_pTextEdit[] = { // content: - 5, // revision + 4, // revision 0, // classname 0, 0, // classinfo 1, 14, // methods @@ -983,7 +983,7 @@ int pTextEdit::qt_metacall(QMetaObject::Call _c, int _id, void **_a) static const uint qt_meta_data_pVerticalScrollBar[] = { // content: - 5, // revision + 4, // revision 0, // classname 0, 0, // classinfo 1, 14, // methods @@ -1044,7 +1044,7 @@ int pVerticalScrollBar::qt_metacall(QMetaObject::Call _c, int _id, void **_a) static const uint qt_meta_data_pVerticalSlider[] = { // content: - 5, // revision + 4, // revision 0, // classname 0, 0, // classinfo 1, 14, // methods diff --git a/bsnes/phoenix/qt/widget/canvas.cpp b/bsnes/phoenix/qt/widget/canvas.cpp index 4adc54e5..560e43a3 100755 --- a/bsnes/phoenix/qt/widget/canvas.cpp +++ b/bsnes/phoenix/qt/widget/canvas.cpp @@ -1,6 +1,6 @@ void pCanvas::setSize(const Size &size) { delete qtImage; - qtImage = new QImage(size.width, size.height, QImage::Format_RGB32); + qtImage = new QImage(size.width, size.height, QImage::Format_ARGB32); } void pCanvas::update() { @@ -10,7 +10,7 @@ void pCanvas::update() { void pCanvas::constructor() { qtWidget = qtCanvas = new QtCanvas(*this); - qtImage = new QImage(canvas.state.width, canvas.state.height, QImage::Format_RGB32); + qtImage = new QImage(canvas.state.width, canvas.state.height, QImage::Format_ARGB32); memcpy(qtImage->bits(), canvas.state.data, canvas.state.width * canvas.state.height * sizeof(uint32_t)); pWidget::synchronizeState(); diff --git a/bsnes/ruby/audio.hpp b/bsnes/ruby/audio.hpp index 45ae96ef..b28ed6d0 100755 --- a/bsnes/ruby/audio.hpp +++ b/bsnes/ruby/audio.hpp @@ -1,5 +1,4 @@ -class Audio { -public: +struct Audio { static const char *Handle; static const char *Synchronize; static const char *Frequency; diff --git a/bsnes/ruby/input.hpp b/bsnes/ruby/input.hpp index 5334c4da..827f9876 100755 --- a/bsnes/ruby/input.hpp +++ b/bsnes/ruby/input.hpp @@ -1,5 +1,4 @@ -class Input { -public: +struct Input { static const char *Handle; static const char *KeyboardSupport; static const char *MouseSupport; diff --git a/bsnes/ruby/ruby.cpp b/bsnes/ruby/ruby.cpp index 1a002006..0e142882 100755 --- a/bsnes/ruby/ruby.cpp +++ b/bsnes/ruby/ruby.cpp @@ -14,6 +14,7 @@ InputInterface input; const char *Video::Handle = "Handle"; const char *Video::Synchronize = "Synchronize"; +const char *Video::Depth = "Depth"; const char *Video::Filter = "Filter"; const char *Video::Shader = "Shader"; const char *Video::FragmentShader = "FragmentShader"; @@ -58,6 +59,10 @@ void VideoInterface::driver(const char *driver) { else if(!strcmp(driver, "OpenGL")) p = new VideoWGL(); #endif + #ifdef VIDEO_XSHM + else if(!strcmp(driver, "XShm")) p = new VideoXShm(); + #endif + #ifdef VIDEO_XV else if(!strcmp(driver, "X-Video")) p = new VideoXv(); #endif @@ -75,6 +80,8 @@ const char* VideoInterface::default_driver() { return "DirectDraw"; #elif defined(VIDEO_GDI) return "GDI"; + #elif defined(VIDEO_XSHM) + return "XShm"; #elif defined(VIDEO_QTOPENGL) return "Qt-OpenGL"; #elif defined(VIDEO_QTRASTER) @@ -126,6 +133,10 @@ const char* VideoInterface::driver_list() { "X-Video;" #endif + #if defined(VIDEO_XSHM) + "XShm;" + #endif + #if defined(VIDEO_QTRASTER) "Qt-Raster;" #endif diff --git a/bsnes/ruby/ruby.hpp b/bsnes/ruby/ruby.hpp index a8b08227..e0d5a690 100755 --- a/bsnes/ruby/ruby.hpp +++ b/bsnes/ruby/ruby.hpp @@ -1,6 +1,6 @@ /* ruby - version: 0.07 (2011-08-14) + version: 0.08 (2011-11-25) license: public domain */ diff --git a/bsnes/ruby/ruby_impl.cpp b/bsnes/ruby/ruby_impl.cpp index aca0e7af..d4b6c19e 100755 --- a/bsnes/ruby/ruby_impl.cpp +++ b/bsnes/ruby/ruby_impl.cpp @@ -82,6 +82,10 @@ using namespace nall; #include #endif +#ifdef VIDEO_XSHM + #include +#endif + #ifdef VIDEO_XV #include #endif diff --git a/bsnes/ruby/video.hpp b/bsnes/ruby/video.hpp index c79c5531..b71970c3 100755 --- a/bsnes/ruby/video.hpp +++ b/bsnes/ruby/video.hpp @@ -1,7 +1,7 @@ -class Video { -public: +struct Video { static const char *Handle; static const char *Synchronize; + static const char *Depth; static const char *Filter; static const char *Shader; static const char *FragmentShader; diff --git a/bsnes/ruby/video/glx.cpp b/bsnes/ruby/video/glx.cpp index 661703f0..40c9e038 100755 --- a/bsnes/ruby/video/glx.cpp +++ b/bsnes/ruby/video/glx.cpp @@ -27,11 +27,6 @@ namespace ruby { -//returns true once window is mapped (created and displayed onscreen) -static Bool glx_wait_for_map_notify(Display *d, XEvent *e, char *arg) { - return (e->type == MapNotify) && (e->xmap.window == (Window)arg); -} - class pVideoGLX : public OpenGL { public: int (*glSwapInterval)(int); @@ -52,15 +47,18 @@ public: struct { Window handle; bool synchronize; + unsigned depth; unsigned filter; unsigned width; unsigned height; + unsigned format; } settings; bool cap(const string& name) { if(name == Video::Handle) return true; if(name == Video::Synchronize) return true; + if(name == Video::Depth) return true; if(name == Video::Filter) return true; if(name == Video::Shader) return true; if(name == Video::FragmentShader) return true; @@ -71,6 +69,7 @@ public: any get(const string& name) { if(name == Video::Handle) return (uintptr_t)settings.handle; if(name == Video::Synchronize) return settings.synchronize; + if(name == Video::Depth) return settings.depth; if(name == Video::Filter) return settings.filter; return false; } @@ -89,6 +88,19 @@ public: } } + if(name == Video::Depth) { + unsigned depth = any_cast(value); + switch(depth) { + case 15u: ibpp = 2; iformat = GL_UNSIGNED_SHORT_1_5_5_5_REV; break; + case 16u: ibpp = 2; iformat = GL_UNSIGNED_SHORT_5_6_5_REV; break; + case 24u: ibpp = 4; iformat = GL_UNSIGNED_INT_8_8_8_8_REV; break; + case 30u: ibpp = 4; iformat = GL_UNSIGNED_INT_2_10_10_10_REV; break; + default: return false; + } + settings.depth = depth; + return true; + } + if(name == Video::Filter) { settings.filter = any_cast(value); return true; @@ -159,8 +171,21 @@ public: //let GLX determine the best Visual to use for GL output; provide a few hints //note: some video drivers will override double buffering attribute - int attributelist[] = { GLX_RGBA, GLX_DOUBLEBUFFER, None }; - XVisualInfo *vi = glXChooseVisual(display, screen, attributelist); + int attributeList[] = { + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, + GLX_RENDER_TYPE, GLX_RGBA_BIT, + GLX_DOUBLEBUFFER, True, + GLX_RED_SIZE, (settings.depth / 3), + GLX_GREEN_SIZE, (settings.depth / 3) + (settings.depth % 3), + GLX_BLUE_SIZE, (settings.depth / 3), + None, + }; + + int fbCount; + GLXFBConfig *fbConfig = glXChooseFBConfig(display, screen, attributeList, &fbCount); + if(fbCount == 0) return false; + + XVisualInfo *vi = glXGetVisualFromFBConfig(display, fbConfig[0]); //Window settings.handle has already been realized, most likely with DefaultVisual. //GLX requires that the GL output window has the same Visual as the GLX context. @@ -170,16 +195,19 @@ public: XSetWindowAttributes attributes; attributes.colormap = colormap; attributes.border_pixel = 0; - attributes.event_mask = StructureNotifyMask; xwindow = XCreateWindow(display, /* parent = */ settings.handle, /* x = */ 0, /* y = */ 0, window_attributes.width, window_attributes.height, /* border_width = */ 0, vi->depth, InputOutput, vi->visual, - CWColormap | CWBorderPixel | CWEventMask, &attributes); + CWColormap | CWBorderPixel, &attributes); XSetWindowBackground(display, xwindow, /* color = */ 0); XMapWindow(display, xwindow); - XEvent event; + XFlush(display); + //window must be realized (appear onscreen) before we make the context current - XIfEvent(display, &event, glx_wait_for_map_notify, (char*)xwindow); + while(XPending(display)) { + XEvent event; + XNextEvent(display, &event); + } glxcontext = glXCreateContext(display, vi, /* sharelist = */ 0, /* direct = */ GL_TRUE); glXMakeCurrent(display, glxwindow = xwindow, glxcontext); @@ -224,6 +252,10 @@ public: pVideoGLX() : glSwapInterval(0) { settings.handle = 0; settings.synchronize = false; + settings.depth = 24u; + + iformat = GL_UNSIGNED_INT_8_8_8_8_REV; + ibpp = 4; xwindow = 0; colormap = 0; glxcontext = 0; diff --git a/bsnes/ruby/video/opengl.hpp b/bsnes/ruby/video/opengl.hpp index a606c752..e780d0fe 100755 --- a/bsnes/ruby/video/opengl.hpp +++ b/bsnes/ruby/video/opengl.hpp @@ -34,7 +34,7 @@ public: bool shader_support; uint32_t *buffer; - unsigned iwidth, iheight; + unsigned iwidth, iheight, iformat, ibpp; void resize(unsigned width, unsigned height) { if(gltexture == 0) glGenTextures(1, &gltexture); @@ -46,18 +46,18 @@ public: glBindTexture(GL_TEXTURE_2D, gltexture); glPixelStorei(GL_UNPACK_ROW_LENGTH, iwidth); glTexImage2D(GL_TEXTURE_2D, - /* mip-map level = */ 0, /* internal format = */ GL_RGBA, + /* mip-map level = */ 0, /* internal format = */ GL_RGB10_A2, iwidth, iheight, /* border = */ 0, /* format = */ GL_BGRA, - GL_UNSIGNED_INT_8_8_8_8_REV, buffer); + iformat, buffer); } bool lock(uint32_t *&data, unsigned &pitch) { - pitch = iwidth * sizeof(uint32_t); + pitch = iwidth * ibpp; return data = buffer; } void clear() { - memset(buffer, 0, iwidth * iheight * sizeof(uint32_t)); + memset(buffer, 0, iwidth * iheight * ibpp); glClearColor(0.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); glFlush(); @@ -96,7 +96,7 @@ public: glPixelStorei(GL_UNPACK_ROW_LENGTH, iwidth); glTexSubImage2D(GL_TEXTURE_2D, /* mip-map level = */ 0, /* x = */ 0, /* y = */ 0, - inwidth, inheight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, buffer); + inwidth, inheight, GL_BGRA, iformat, buffer); //OpenGL projection sets 0,0 as *bottom-left* of screen. //therefore, below vertices flip image to support top-left source. diff --git a/bsnes/ruby/video/wgl.cpp b/bsnes/ruby/video/wgl.cpp index ce5d6be7..5d3765d0 100755 --- a/bsnes/ruby/video/wgl.cpp +++ b/bsnes/ruby/video/wgl.cpp @@ -141,6 +141,8 @@ public: settings.synchronize = false; settings.filter = 0; + iformat = GL_UNSIGNED_INT_8_8_8_8_REV; + ibpp = 4; window = 0; wglcontext = 0; glwindow = 0; diff --git a/bsnes/ruby/video/xshm.cpp b/bsnes/ruby/video/xshm.cpp new file mode 100755 index 00000000..35433b2a --- /dev/null +++ b/bsnes/ruby/video/xshm.cpp @@ -0,0 +1,170 @@ +#include +#include + +namespace ruby { + +struct pVideoXShm { + struct { + Display *display; + int screen; + Visual *visual; + int depth; + Window window; + + XShmSegmentInfo shmInfo; + XImage *image; + uint32_t *buffer; + unsigned width, height; + } device; + + struct { + uintptr_t handle; + + uint32_t *buffer; + unsigned width, height; + } settings; + + bool cap(const string &name) { + if(name == Video::Handle) return true; + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return settings.handle; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = any_cast(value); + return true; + } + return false; + } + + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { + if(settings.buffer == nullptr || settings.width != width || settings.height != height) { + if(settings.buffer) delete[] settings.buffer; + settings.width = width, settings.height = height; + settings.buffer = new uint32_t[width * height](); + } + + data = settings.buffer; + pitch = settings.width * sizeof(uint32_t); + return true; + } + + void unlock() { + } + + void clear() { + if(settings.buffer == nullptr) return; + memset(settings.buffer, 0, settings.width * settings.height * sizeof(uint32_t)); + refresh(); + } + + void refresh() { + if(settings.buffer == nullptr) return; + size(); + + float xRatio = (float)settings.width / (float)device.width; + float yRatio = (float)settings.height / (float)device.height; + float yStep = 0; + for(unsigned y = 0; y < device.height; y++) { + uint32_t *sp = settings.buffer + (unsigned)yStep * settings.width; + uint32_t *dp = device.buffer + y * device.width; + yStep += yRatio; + float xStep = 0; + for(unsigned x = 0; x < device.width; x++) { + uint32_t color = sp[(unsigned)xStep]; + xStep += xRatio; + *dp++ = ((color >> 20) & 0x000003ff) | ((color) & 0x000ffc00) | ((color << 20) & 0x3ff00000); + } + } + + GC gc = XCreateGC(device.display, device.window, 0, 0); + XShmPutImage( + device.display, device.window, gc, device.image, + 0, 0, 0, 0, device.width, device.height, False + ); + XFreeGC(device.display, gc); + XFlush(device.display); + } + + bool init() { + device.display = XOpenDisplay(0); + device.screen = DefaultScreen(device.display); + device.visual = DefaultVisual(device.display, device.screen); + device.depth = DefaultDepth(device.display, device.screen); + + XSetWindowAttributes attributes; + attributes.border_pixel = 0; + device.window = XCreateWindow(device.display, (Window)settings.handle, + 0, 0, 256, 256, + 0, device.depth, InputOutput, device.visual, + CWBorderPixel, &attributes + ); + XSetWindowBackground(device.display, device.window, 0); + XMapWindow(device.display, device.window); + XFlush(device.display); + + while(XPending(device.display)) { + XEvent event; + XNextEvent(device.display, &event); + } + + if(size() == false) return false; + return true; + } + + void term() { + free(); + } + + pVideoXShm() { + device.buffer = nullptr; + + settings.buffer = nullptr; + } + + ~pVideoXShm() { + term(); + } + +//internal: + bool size() { + XWindowAttributes windowAttributes; + XGetWindowAttributes(device.display, settings.handle, &windowAttributes); + + if(device.buffer && device.width == windowAttributes.width && device.height == windowAttributes.height) return true; + device.width = windowAttributes.width, device.height = windowAttributes.height; + XResizeWindow(device.display, device.window, device.width, device.height); + free(); + + //create + device.shmInfo.shmid = shmget(IPC_PRIVATE, device.width * device.height * sizeof(uint32_t), IPC_CREAT | 0777); + if(device.shmInfo.shmid < 0) return false; + + device.shmInfo.shmaddr = (char*)shmat(device.shmInfo.shmid, 0, 0); + device.shmInfo.readOnly = False; + XShmAttach(device.display, &device.shmInfo); + device.buffer = (uint32_t*)device.shmInfo.shmaddr; + device.image = XShmCreateImage(device.display, device.visual, device.depth, + ZPixmap, device.shmInfo.shmaddr, &device.shmInfo, device.width, device.height + ); + + return true; + } + + void free() { + if(device.buffer == nullptr) return; + device.buffer = nullptr; + XShmDetach(device.display, &device.shmInfo); + XDestroyImage(device.image); + shmdt(device.shmInfo.shmaddr); + shmctl(device.shmInfo.shmid, IPC_RMID, 0); + } +}; + +DeclareVideo(XShm) + +} diff --git a/bsnes/snes/Makefile b/bsnes/snes/Makefile index a1a3ef33..2b26fe25 100755 --- a/bsnes/snes/Makefile +++ b/bsnes/snes/Makefile @@ -26,12 +26,10 @@ else ifeq ($(profile),performance) snessmp := $(snes)/alt/smp snesdsp := $(snes)/alt/dsp snesppu := $(snes)/alt/ppu-performance -else - $(error Unknown profile: $(profile)) endif obj/snes-interface.o : $(snes)/interface/interface.cpp $(call rwildcard,$(snes)/interface) -obj/snes-system.o : $(snes)/system/system.cpp $(call rwildcard,$(snes)/system/) $(call rwildcard,$(snes)/video/) $(call rwildcard,$(snes)/audio/) $(call rwildcard,$(snes)/input/) +obj/snes-system.o : $(snes)/system/system.cpp $(call rwildcard,$(snes)/system/) obj/snes-controller.o: $(snes)/controller/controller.cpp $(call rwildcard,$(snes)/controller/) obj/snes-memory.o : $(snes)/memory/memory.cpp $(call rwildcard,$(snes)/memory/) obj/snes-cpucore.o : $(snes)/cpu/core/core.cpp $(call rwildcard,$(snes)/cpu/core/) diff --git a/bsnes/snes/alt/ppu-parallel/mmio.cpp b/bsnes/snes/alt/ppu-parallel/mmio.cpp new file mode 100755 index 00000000..8abfb74e --- /dev/null +++ b/bsnes/snes/alt/ppu-parallel/mmio.cpp @@ -0,0 +1,14 @@ +#ifdef PPU_CPP + +bool PPU::display_disable() const { return r[0x00] & 0x80; } +unsigned PPU::display_brightness() const { return r[0x00] & 0x0f; } + +uint8 PPU::mmio_read(unsigned addr) { + return cpu.regs.mdr; +} + +void PPU::mmio_write(unsigned addr, uint8 data) { + r[addr & 0x3f] = data; +} + +#endif diff --git a/bsnes/snes/alt/ppu-parallel/ppu.cpp b/bsnes/snes/alt/ppu-parallel/ppu.cpp new file mode 100755 index 00000000..1c3dcb70 --- /dev/null +++ b/bsnes/snes/alt/ppu-parallel/ppu.cpp @@ -0,0 +1,81 @@ +#include + +#define PPU_CPP +namespace SNES { + +#include "mmio.cpp" +PPU ppu; + +void PPU::latch_counters() { +} + +bool PPU::interlace() const { + return false; +} + +bool PPU::overscan() const { + return false; +} + +bool PPU::hires() const { + return false; +} + +void PPU::enter() { + scanline(); + clock += lineclocks(); + tick(lineclocks()); +} + +void PPU::scanline() { + if(vcounter() == 0) return frame(); + if(vcounter() > 224) return; +} + +void PPU::frame() { +} + +void PPU::enable() { + bus.map(Bus::MapMode::Direct, 0x00, 0x3f, 0x2100, 0x213f, { &PPU::mmio_read, this }, { &PPU::mmio_write, this }); + bus.map(Bus::MapMode::Direct, 0x80, 0xbf, 0x2100, 0x213f, { &PPU::mmio_read, this }, { &PPU::mmio_write, this }); +} + +void PPU::power() { + for(unsigned n = 0; n < 512 * 480; n++) output[n] = rand() & ((1 << 19) - 1); + + for(auto &n : vram) n = 0; + for(auto &n : oam) n = 0; + for(auto &n : cgram) n = 0; + for(auto &n : r) n = 0; + reset(); +} + +void PPU::reset() { + PPUcounter::reset(); +} + +void PPUcounter::serialize(serializer &s) { + s.integer(status.interlace); + s.integer(status.field); + s.integer(status.vcounter); + s.integer(status.hcounter); + + s.array(history.field); + s.array(history.vcounter); + s.array(history.hcounter); + s.integer(history.index); +} + +void PPU::serialize(serializer &s) { +} + +PPU::PPU() { + surface = new uint32[512 * 512]; + output = surface + 16 * 512; +} + +PPU::~PPU() { + delete[] surface; +} + +} diff --git a/bsnes/snes/alt/ppu-parallel/ppu.hpp b/bsnes/snes/alt/ppu-parallel/ppu.hpp new file mode 100755 index 00000000..3a47d33d --- /dev/null +++ b/bsnes/snes/alt/ppu-parallel/ppu.hpp @@ -0,0 +1,40 @@ +struct PPU : public Processor, public PPUcounter { + uint8 vram[64 * 1024]; + uint8 oam[544]; + uint8 cgram[512]; + uint8 r[64]; + + enum : bool { Threaded = false }; + + void latch_counters(); + bool interlace() const; + bool overscan() const; + bool hires() const; + + void enter(); + void enable(); + void power(); + void reset(); + + void scanline(); + void frame(); + + void serialize(serializer&); + PPU(); + ~PPU(); + +private: + uint32 *surface; + uint32 *output; + + //mmio.cpp + alwaysinline bool display_disable() const; + alwaysinline unsigned display_brightness() const; + + uint8 mmio_read(unsigned addr); + void mmio_write(unsigned addr, uint8 data); + + friend class Video; +}; + +extern PPU ppu; diff --git a/bsnes/snes/chip/bsx/cartridge/cartridge.cpp b/bsnes/snes/chip/bsx/cartridge/cartridge.cpp index ade0f11d..f76682f0 100755 --- a/bsnes/snes/chip/bsx/cartridge/cartridge.cpp +++ b/bsnes/snes/chip/bsx/cartridge/cartridge.cpp @@ -19,7 +19,6 @@ void BSXCartridge::unload() { } void BSXCartridge::power() { - reset(); } void BSXCartridge::reset() { diff --git a/bsnes/snes/chip/bsx/flash/flash.cpp b/bsnes/snes/chip/bsx/flash/flash.cpp index 316f55c2..17b525b6 100755 --- a/bsnes/snes/chip/bsx/flash/flash.cpp +++ b/bsnes/snes/chip/bsx/flash/flash.cpp @@ -16,7 +16,6 @@ void BSXFlash::unload() { } void BSXFlash::power() { - reset(); } void BSXFlash::reset() { diff --git a/bsnes/snes/chip/bsx/satellaview/satellaview.cpp b/bsnes/snes/chip/bsx/satellaview/satellaview.cpp index 7bfd13fb..386fb628 100755 --- a/bsnes/snes/chip/bsx/satellaview/satellaview.cpp +++ b/bsnes/snes/chip/bsx/satellaview/satellaview.cpp @@ -14,7 +14,6 @@ void BSXSatellaview::unload() { } void BSXSatellaview::power() { - reset(); } void BSXSatellaview::reset() { diff --git a/bsnes/snes/chip/hitachidsp/hitachidsp.cpp b/bsnes/snes/chip/hitachidsp/hitachidsp.cpp index 62f4bfa2..1042267e 100755 --- a/bsnes/snes/chip/hitachidsp/hitachidsp.cpp +++ b/bsnes/snes/chip/hitachidsp/hitachidsp.cpp @@ -52,7 +52,6 @@ void HitachiDSP::unload() { } void HitachiDSP::power() { - reset(); } void HitachiDSP::reset() { diff --git a/bsnes/snes/chip/icd2/icd2.cpp b/bsnes/snes/chip/icd2/icd2.cpp index c8a82df3..e28769f4 100755 --- a/bsnes/snes/chip/icd2/icd2.cpp +++ b/bsnes/snes/chip/icd2/icd2.cpp @@ -41,8 +41,6 @@ void ICD2::unload() { void ICD2::power() { audio.coprocessor_enable(true); audio.coprocessor_frequency(4 * 1024 * 1024); - - reset(); } void ICD2::reset() { diff --git a/bsnes/snes/chip/link/link.cpp b/bsnes/snes/chip/link/link.cpp index a5736933..1891f72e 100755 --- a/bsnes/snes/chip/link/link.cpp +++ b/bsnes/snes/chip/link/link.cpp @@ -40,7 +40,6 @@ void Link::unload() { void Link::power() { if(link_power) link_power(); - create(Link::Enter, frequency); } void Link::reset() { diff --git a/bsnes/snes/chip/msu1/msu1.cpp b/bsnes/snes/chip/msu1/msu1.cpp index af19acea..71700e60 100755 --- a/bsnes/snes/chip/msu1/msu1.cpp +++ b/bsnes/snes/chip/msu1/msu1.cpp @@ -60,8 +60,6 @@ void MSU1::unload() { void MSU1::power() { audio.coprocessor_enable(true); audio.coprocessor_frequency(44100.0); - - reset(); } void MSU1::reset() { @@ -78,60 +76,39 @@ void MSU1::reset() { } uint8 MSU1::mmio_read(unsigned addr) { - addr &= 0xffff; - - if(addr == 0x2000) { + switch(addr & 7) { + case 0: return (mmio.data_busy << 7) | (mmio.audio_busy << 6) | (mmio.audio_repeat << 5) | (mmio.audio_play << 4) | (Revision << 0); - } - - if(addr == 0x2001) { + case 1: if(mmio.data_busy) return 0x00; mmio.data_offset++; if(datafile.open()) return datafile.read(); return 0x00; + case 2: return 'S'; + case 3: return '-'; + case 4: return 'M'; + case 5: return 'S'; + case 6: return 'U'; + case 7: return '0' + Revision; } - - if(addr == 0x2002) return 'S'; - if(addr == 0x2003) return '-'; - if(addr == 0x2004) return 'M'; - if(addr == 0x2005) return 'S'; - if(addr == 0x2006) return 'U'; - if(addr == 0x2007) return '0' + Revision; - - return 0x00; + throw; } void MSU1::mmio_write(unsigned addr, uint8 data) { - addr &= 0xffff; - - if(addr == 0x2000) { - mmio.data_offset = (mmio.data_offset & 0xffffff00) | (data << 0); - } - - if(addr == 0x2001) { - mmio.data_offset = (mmio.data_offset & 0xffff00ff) | (data << 8); - } - - if(addr == 0x2002) { - mmio.data_offset = (mmio.data_offset & 0xff00ffff) | (data << 16); - } - - if(addr == 0x2003) { - mmio.data_offset = (mmio.data_offset & 0x00ffffff) | (data << 24); + switch(addr & 7) { + case 0: mmio.data_offset = (mmio.data_offset & 0xffffff00) | (data << 0); break; + case 1: mmio.data_offset = (mmio.data_offset & 0xffff00ff) | (data << 8); break; + case 2: mmio.data_offset = (mmio.data_offset & 0xff00ffff) | (data << 16); break; + case 3: mmio.data_offset = (mmio.data_offset & 0x00ffffff) | (data << 24); if(datafile.open()) datafile.seek(mmio.data_offset); mmio.data_busy = false; - } - - if(addr == 0x2004) { - mmio.audio_track = (mmio.audio_track & 0xff00) | (data << 0); - } - - if(addr == 0x2005) { - mmio.audio_track = (mmio.audio_track & 0x00ff) | (data << 8); + break; + case 4: mmio.audio_track = (mmio.audio_track & 0xff00) | (data << 0); + case 5: mmio.audio_track = (mmio.audio_track & 0x00ff) | (data << 8); if(audiofile.open()) audiofile.close(); if(audiofile.open(interface->path(Cartridge::Slot::Base, { "-", (unsigned)mmio.audio_track, ".pcm" }), file::mode::read)) { uint32 header = audiofile.readm(4); @@ -145,15 +122,14 @@ void MSU1::mmio_write(unsigned addr, uint8 data) { mmio.audio_busy = false; mmio.audio_repeat = false; mmio.audio_play = false; - } - - if(addr == 0x2006) { + break; + case 6: mmio.audio_volume = data; - } - - if(addr == 0x2007) { + break; + case 7: mmio.audio_repeat = data & 2; mmio.audio_play = data & 1; + break; } } diff --git a/bsnes/snes/chip/necdsp/necdsp.cpp b/bsnes/snes/chip/necdsp/necdsp.cpp index 0a07d1dc..04974a00 100755 --- a/bsnes/snes/chip/necdsp/necdsp.cpp +++ b/bsnes/snes/chip/necdsp/necdsp.cpp @@ -266,8 +266,6 @@ void NECDSP::power() { regs.rp.bits(11); regs.dp.bits(11); } - - reset(); } void NECDSP::reset() { diff --git a/bsnes/snes/chip/obc1/obc1.cpp b/bsnes/snes/chip/obc1/obc1.cpp index 6cfc11f3..8d7d3376 100755 --- a/bsnes/snes/chip/obc1/obc1.cpp +++ b/bsnes/snes/chip/obc1/obc1.cpp @@ -16,7 +16,6 @@ void OBC1::unload() { } void OBC1::power() { - reset(); } void OBC1::reset() { diff --git a/bsnes/snes/chip/sa1/sa1.cpp b/bsnes/snes/chip/sa1/sa1.cpp index 9620e4f9..71c6310a 100755 --- a/bsnes/snes/chip/sa1/sa1.cpp +++ b/bsnes/snes/chip/sa1/sa1.cpp @@ -128,7 +128,6 @@ void SA1::unload() { void SA1::power() { regs.a = regs.x = regs.y = 0x0000; regs.s = 0x01ff; - reset(); } void SA1::reset() { diff --git a/bsnes/snes/chip/sdd1/sdd1.cpp b/bsnes/snes/chip/sdd1/sdd1.cpp index 054f6e31..c9b8b1c4 100755 --- a/bsnes/snes/chip/sdd1/sdd1.cpp +++ b/bsnes/snes/chip/sdd1/sdd1.cpp @@ -22,7 +22,6 @@ void SDD1::unload() { } void SDD1::power() { - reset(); } void SDD1::reset() { diff --git a/bsnes/snes/chip/spc7110/spc7110.cpp b/bsnes/snes/chip/spc7110/spc7110.cpp index 835ddbb6..d2dc640b 100755 --- a/bsnes/snes/chip/spc7110/spc7110.cpp +++ b/bsnes/snes/chip/spc7110/spc7110.cpp @@ -22,7 +22,6 @@ void SPC7110::unload() { } void SPC7110::power() { - reset(); } void SPC7110::reset() { diff --git a/bsnes/snes/chip/srtc/srtc.cpp b/bsnes/snes/chip/srtc/srtc.cpp index 1dd8f86e..1b2fd2aa 100755 --- a/bsnes/snes/chip/srtc/srtc.cpp +++ b/bsnes/snes/chip/srtc/srtc.cpp @@ -21,7 +21,6 @@ void SRTC::unload() { } void SRTC::power() { - reset(); } void SRTC::reset() { diff --git a/bsnes/snes/chip/st0018/st0018.cpp b/bsnes/snes/chip/st0018/st0018.cpp index aefb278b..ee13e385 100755 --- a/bsnes/snes/chip/st0018/st0018.cpp +++ b/bsnes/snes/chip/st0018/st0018.cpp @@ -55,7 +55,6 @@ void ST0018::unload() { } void ST0018::power() { - reset(); } void ST0018::reset() { diff --git a/bsnes/snes/chip/superfx/superfx.cpp b/bsnes/snes/chip/superfx/superfx.cpp index fba0ff25..52060fa2 100755 --- a/bsnes/snes/chip/superfx/superfx.cpp +++ b/bsnes/snes/chip/superfx/superfx.cpp @@ -51,7 +51,6 @@ void SuperFX::unload() { void SuperFX::power() { clockmode = config.superfx.speed; - reset(); } void SuperFX::reset() { diff --git a/bsnes/snes/config/config.cpp b/bsnes/snes/config/config.cpp index 3fcb72e0..701af94c 100755 --- a/bsnes/snes/config/config.cpp +++ b/bsnes/snes/config/config.cpp @@ -7,7 +7,7 @@ Configuration::Configuration() { controller_port2 = Input::Device::Joypad; expansion_port = System::ExpansionPortDevice::BSX; region = System::Region::Autodetect; - random = false; //true; + random = true; cpu.version = 2; cpu.ntsc_frequency = 21477272; //315 / 88 * 6000000 diff --git a/bsnes/snes/cpu/cpu.cpp b/bsnes/snes/cpu/cpu.cpp index 4e83429f..f6ae9754 100755 --- a/bsnes/snes/cpu/cpu.cpp +++ b/bsnes/snes/cpu/cpu.cpp @@ -125,8 +125,6 @@ void CPU::power() { mmio_power(); dma_power(); timing_power(); - - reset(); } void CPU::reset() { diff --git a/bsnes/snes/dsp/dsp.cpp b/bsnes/snes/dsp/dsp.cpp index 4811269b..cd8818c6 100755 --- a/bsnes/snes/dsp/dsp.cpp +++ b/bsnes/snes/dsp/dsp.cpp @@ -274,8 +274,6 @@ void DSP::power() { voice[i].t_envx_out = 0; voice[i].hidden_env = 0; } - - reset(); } void DSP::reset() { diff --git a/bsnes/snes/ppu/ppu.cpp b/bsnes/snes/ppu/ppu.cpp index ec213664..8545175f 100755 --- a/bsnes/snes/ppu/ppu.cpp +++ b/bsnes/snes/ppu/ppu.cpp @@ -98,8 +98,6 @@ void PPU::power() { for(auto &n : vram) n = random(0x00); for(auto &n : oam) n = random(0x00); for(auto &n : cgram) n = random(0x00); - - reset(); } void PPU::reset() { diff --git a/bsnes/snes/smp/core/algorithms.cpp b/bsnes/snes/smp/core/algorithms.cpp index cbc31bea..69504cdf 100755 --- a/bsnes/snes/smp/core/algorithms.cpp +++ b/bsnes/snes/smp/core/algorithms.cpp @@ -94,13 +94,7 @@ uint8 SMPcore::op_ror(uint8 x) { } uint8 SMPcore::op_sbc(uint8 x, uint8 y) { - int r = x - y - !regs.p.c; - regs.p.n = r & 0x80; - regs.p.v = (x ^ y) & (x ^ r) & 0x80; - regs.p.h = !((x ^ y ^ r) & 0x10); - regs.p.z = (uint8)r == 0; - regs.p.c = r >= 0; - return r; + return op_adc(x, ~y); } uint8 SMPcore::op_st(uint8 x, uint8 y) { diff --git a/bsnes/snes/smp/core/registers.hpp b/bsnes/snes/smp/core/registers.hpp index 8a4916fc..48aa92c8 100755 --- a/bsnes/snes/smp/core/registers.hpp +++ b/bsnes/snes/smp/core/registers.hpp @@ -24,18 +24,20 @@ struct word_t { }; inline operator unsigned() const { return w; } - inline unsigned operator=(unsigned data) { w = data; return w; } + inline unsigned operator=(unsigned data) { return w = data; } inline unsigned operator++() { return ++w; } inline unsigned operator--() { return --w; } + inline unsigned operator++(int) { unsigned data = w++; return data; } inline unsigned operator--(int) { unsigned data = w--; return data; } - inline unsigned operator|=(unsigned data) { w |= data; return w; } - inline unsigned operator^=(unsigned data) { w ^= data; return w; } - inline unsigned operator&=(unsigned data) { w &= data; return w; } - inline unsigned operator+=(unsigned data) { w += data; return w; } - inline unsigned operator-=(unsigned data) { w -= data; return w; } + inline unsigned operator+=(unsigned data) { return w += data;; } + inline unsigned operator-=(unsigned data) { return w -= data;; } + + inline unsigned operator|=(unsigned data) { return w |= data; } + inline unsigned operator^=(unsigned data) { return w ^= data; } + inline unsigned operator&=(unsigned data) { return w &= data; } }; struct regs_t { diff --git a/bsnes/snes/smp/smp.cpp b/bsnes/snes/smp/smp.cpp index d01f6ffc..90806245 100755 --- a/bsnes/snes/smp/smp.cpp +++ b/bsnes/snes/smp/smp.cpp @@ -53,8 +53,6 @@ void SMP::power() { timer0.target = 0; timer1.target = 0; timer2.target = 0; - - reset(); } void SMP::reset() { diff --git a/bsnes/snes/audio/audio.cpp b/bsnes/snes/system/audio.cpp similarity index 100% rename from bsnes/snes/audio/audio.cpp rename to bsnes/snes/system/audio.cpp diff --git a/bsnes/snes/audio/audio.hpp b/bsnes/snes/system/audio.hpp similarity index 100% rename from bsnes/snes/audio/audio.hpp rename to bsnes/snes/system/audio.hpp diff --git a/bsnes/snes/input/input.cpp b/bsnes/snes/system/input.cpp similarity index 100% rename from bsnes/snes/input/input.cpp rename to bsnes/snes/system/input.cpp diff --git a/bsnes/snes/input/input.hpp b/bsnes/snes/system/input.hpp similarity index 100% rename from bsnes/snes/input/input.hpp rename to bsnes/snes/system/input.hpp diff --git a/bsnes/snes/system/system.cpp b/bsnes/snes/system/system.cpp index 68818109..c19a7c51 100755 --- a/bsnes/snes/system/system.cpp +++ b/bsnes/snes/system/system.cpp @@ -10,10 +10,9 @@ System system; #include #include -#include -#include -#include - +#include "video.cpp" +#include "audio.cpp" +#include "input.cpp" #include "serialization.cpp" void System::run() { @@ -181,17 +180,7 @@ void System::power() { if(cartridge.has_msu1()) msu1.power(); if(cartridge.has_link()) link.power(); - if(cartridge.mode() == Cartridge::Mode::SuperGameBoy) cpu.coprocessors.append(&icd2); - if(cartridge.has_superfx()) cpu.coprocessors.append(&superfx); - if(cartridge.has_sa1()) cpu.coprocessors.append(&sa1); - if(cartridge.has_necdsp()) cpu.coprocessors.append(&necdsp); - if(cartridge.has_hitachidsp()) cpu.coprocessors.append(&hitachidsp); - if(cartridge.has_msu1()) cpu.coprocessors.append(&msu1); - if(cartridge.has_link()) cpu.coprocessors.append(&link); - - scheduler.init(); - input.connect(0, config.controller_port1); - input.connect(1, config.controller_port2); + reset(); } void System::reset() { diff --git a/bsnes/snes/system/system.hpp b/bsnes/snes/system/system.hpp index 3d8d6e77..f5046e35 100755 --- a/bsnes/snes/system/system.hpp +++ b/bsnes/snes/system/system.hpp @@ -42,9 +42,9 @@ private: friend class Input; }; -#include -#include -#include +#include "video.hpp" +#include "audio.hpp" +#include "input.hpp" #include #include diff --git a/bsnes/snes/video/video.cpp b/bsnes/snes/system/video.cpp similarity index 100% rename from bsnes/snes/video/video.cpp rename to bsnes/snes/system/video.cpp diff --git a/bsnes/snes/video/video.hpp b/bsnes/snes/system/video.hpp similarity index 100% rename from bsnes/snes/video/video.hpp rename to bsnes/snes/system/video.hpp diff --git a/bsnes/ui/config/config.cpp b/bsnes/ui/config/config.cpp index d8f46b62..2d8e5579 100755 --- a/bsnes/ui/config/config.cpp +++ b/bsnes/ui/config/config.cpp @@ -3,6 +3,7 @@ Config *config = 0; Config::Config() { attach(video.driver = "", "Video::Driver"); + attach(video.depth = 24u, "Video::Depth"); attach(video.filter = "None", "Video::Filter"); attach(video.shader = "Blur", "Video::Shader"); attach(video.synchronize = true, "Video::Synchronize"); diff --git a/bsnes/ui/config/config.hpp b/bsnes/ui/config/config.hpp index a20041bd..71e8881f 100755 --- a/bsnes/ui/config/config.hpp +++ b/bsnes/ui/config/config.hpp @@ -1,8 +1,11 @@ struct Config : public configuration { struct Video { string driver; + unsigned depth; + string filter; string shader; + bool synchronize; bool correctAspectRatio; diff --git a/bsnes/ui/interface/gameboy/gameboy.cpp b/bsnes/ui/interface/gameboy/gameboy.cpp index dc26ec6e..f9282d23 100755 --- a/bsnes/ui/interface/gameboy/gameboy.cpp +++ b/bsnes/ui/interface/gameboy/gameboy.cpp @@ -28,7 +28,7 @@ bool InterfaceGameBoy::loadCartridge(GameBoy::System::Revision revision, const s } GameBoy::interface = this; - GameBoy::video.generate(GameBoy::Video::Format::RGB24); + GameBoy::video.generate(GameBoy::Video::Format::RGB30); interface->loadCartridge(::Interface::Mode::GameBoy); return true; } diff --git a/bsnes/ui/interface/interface.cpp b/bsnes/ui/interface/interface.cpp index 3c714e97..bae5cb61 100755 --- a/bsnes/ui/interface/interface.cpp +++ b/bsnes/ui/interface/interface.cpp @@ -231,7 +231,7 @@ void Interface::videoRefresh(const uint32_t *input, unsigned inputPitch, unsigne uint32_t *dp = output + y * outputPitch; for(unsigned x = 0; x < width; x++) { uint32_t color = *sp++; - *dp++ = (palette[color >> 16] << 16) + (palette[color >> 8] << 8) + (palette[color >> 0] << 0); + *dp++ = palette((color >> 20) & 1023, (color >> 10) & 1023, (color >> 0) & 1023); } } diff --git a/bsnes/ui/interface/nes/nes.cpp b/bsnes/ui/interface/nes/nes.cpp index 611656d8..e8c50963 100755 --- a/bsnes/ui/interface/nes/nes.cpp +++ b/bsnes/ui/interface/nes/nes.cpp @@ -45,7 +45,7 @@ bool InterfaceNES::loadCartridge(const string &filename) { } interface->loadCartridge(::Interface::Mode::NES); - NES::video.generate(NES::Video::Format::RGB24); + NES::video.generate(NES::Video::Format::RGB30); return true; } diff --git a/bsnes/ui/interface/palette.cpp b/bsnes/ui/interface/palette.cpp index c030dd62..0fba0406 100755 --- a/bsnes/ui/interface/palette.cpp +++ b/bsnes/ui/interface/palette.cpp @@ -1,7 +1,7 @@ Palette palette; -uint8_t Palette::operator[](uint8_t n) { - return color[n]; +unsigned Palette::operator()(unsigned r, unsigned g, unsigned b) const { + return red[r] + green[g] + blue[b]; } /* 5-bit -> 8-bit @@ -13,35 +13,38 @@ const uint8_t Palette::gammaRamp[32] = { }; */ -uint8_t Palette::contrastAdjust(uint8_t input) { - signed contrast = config->video.contrast - 100; - signed result = input - contrast + (2 * contrast * input + 127) / 255; - return max(0, min(255, result)); -} - -uint8_t Palette::brightnessAdjust(uint8_t input) { - signed brightness = config->video.brightness - 100; - signed result = input + brightness; - return max(0, min(255, result)); -} - -uint8_t Palette::gammaAdjust(uint8_t input) { - signed result = (signed)(pow(((double)input / 255.0), (double)config->video.gamma / 100.0) * 255.0 + 0.5); - return max(0, min(255, result)); -} - void Palette::update() { double exponent = 1.0 + (double)config->video.gamma * 0.01; - for(unsigned n = 0; n < 256; n++) { - unsigned result = (n < 128 ? 127 * pow(((double)n / 127), exponent) : n); + for(unsigned n = 0; n < 1024; n++) { + unsigned result = (n < 512 ? 511 * pow(((double)n / 511), exponent) : n); color[n] = result; } - for(unsigned n = 0; n < 256; n++) { - color[n] = contrastAdjust(color[n]); + double contrast = config->video.contrast * 0.01; + for(unsigned n = 0; n < 1024; n++) { + signed result = color[n] * contrast; + color[n] = max(0, min(1023, result)); } - for(unsigned n = 0; n < 256; n++) { - color[n] = brightnessAdjust(color[n]); + signed brightness = (config->video.brightness - 100) * 4; + for(unsigned n = 0; n < 1024; n++) { + signed result = color[n] + brightness; + color[n] = max(0, min(1023, result)); + } + + if(config->video.depth == 30) { + for(unsigned n = 0; n < 1024; n++) { + red[n] = color[n] << 20; + green[n] = color[n] << 10; + blue[n] = color[n] << 0; + } + } + + if(config->video.depth == 24) { + for(unsigned n = 0; n < 1024; n++) { + red[n] = (color[n] >> 2) << 16; + green[n] = (color[n] >> 2) << 8; + blue[n] = (color[n] >> 2) << 0; + } } } diff --git a/bsnes/ui/interface/palette.hpp b/bsnes/ui/interface/palette.hpp index c8f8a19f..c9072623 100755 --- a/bsnes/ui/interface/palette.hpp +++ b/bsnes/ui/interface/palette.hpp @@ -1,13 +1,10 @@ struct Palette { - alwaysinline uint8_t operator[](uint8_t color); - - uint8_t contrastAdjust(uint8_t); - uint8_t brightnessAdjust(uint8_t); - uint8_t gammaAdjust(uint8_t); + alwaysinline unsigned operator()(unsigned r, unsigned g, unsigned b) const; void update(); private: - uint32_t color[256]; + uint32_t color[1024]; + uint32_t red[1024], green[1024], blue[1024]; }; extern Palette palette; diff --git a/bsnes/ui/interface/snes/snes.cpp b/bsnes/ui/interface/snes/snes.cpp index 51d444db..07eb263b 100755 --- a/bsnes/ui/interface/snes/snes.cpp +++ b/bsnes/ui/interface/snes/snes.cpp @@ -51,7 +51,7 @@ bool InterfaceSNES::loadCartridge(const string &basename) { loadMemory(); interface->loadCartridge(::Interface::Mode::SNES); - SNES::video.generate(SNES::Video::Format::RGB24); + SNES::video.generate(SNES::Video::Format::RGB30); return true; } @@ -80,7 +80,7 @@ bool InterfaceSNES::loadSatellaviewSlottedCartridge(const string &basename, cons loadMemory(); interface->loadCartridge(::Interface::Mode::SNES); - SNES::video.generate(SNES::Video::Format::RGB24); + SNES::video.generate(SNES::Video::Format::RGB30); return true; } @@ -109,7 +109,7 @@ bool InterfaceSNES::loadSatellaviewCartridge(const string &basename, const strin loadMemory(); interface->loadCartridge(::Interface::Mode::SNES); - SNES::video.generate(SNES::Video::Format::RGB24); + SNES::video.generate(SNES::Video::Format::RGB30); return true; } @@ -143,7 +143,7 @@ bool InterfaceSNES::loadSufamiTurboCartridge(const string &basename, const strin loadMemory(); interface->loadCartridge(::Interface::Mode::SNES); - SNES::video.generate(SNES::Video::Format::RGB24); + SNES::video.generate(SNES::Video::Format::RGB30); return true; } @@ -176,7 +176,7 @@ bool InterfaceSNES::loadSuperGameBoyCartridge(const string &basename, const stri loadMemory(); interface->loadCartridge(::Interface::Mode::SNES); - SNES::video.generate(SNES::Video::Format::RGB24); + SNES::video.generate(SNES::Video::Format::RGB30); return true; } diff --git a/bsnes/ui/main.cpp b/bsnes/ui/main.cpp index 59bac117..1bf6c33a 100755 --- a/bsnes/ui/main.cpp +++ b/bsnes/ui/main.cpp @@ -27,12 +27,13 @@ void Application::run() { } Application::Application(int argc, char **argv) { - title = "bsnes v084.01"; + title = "bsnes v084.03"; application = this; quit = false; pause = false; autopause = false; + { char path[PATH_MAX]; auto unused = ::realpath(argv[0], path); @@ -46,6 +47,7 @@ Application::Application(int argc, char **argv) { } mkdir(userpath, 0755); } + config = new Config; interface = new Interface; inputManager = new InputManager; @@ -76,6 +78,7 @@ Application::Application(int argc, char **argv) { video.driver(config->video.driver); video.set(Video::Handle, mainWindow->viewport.handle()); video.set(Video::Synchronize, config->video.synchronize); + video.set(Video::Depth, config->video.depth); if(video.init() == false) { MessageWindow::critical(*mainWindow, { "Failed to initialize ", config->video.driver, " video driver." }); video.driver("None"); diff --git a/snesfilter/HQ2x/HQ2x.cpp b/snesfilter/HQ2x/HQ2x.cpp index 46f88c15..4d19d872 100755 --- a/snesfilter/HQ2x/HQ2x.cpp +++ b/snesfilter/HQ2x/HQ2x.cpp @@ -34,13 +34,14 @@ const uint8_t hqTable[256] = { 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 1, 12, 5, 3, 1, 14, }; -static uint16_t rgb555(uint32_t C) { - return ((C >> 9) & 0x7c00) + ((C >> 6) & 0x03e0) + ((C >> 3) & 0x001f); +static uint16_t rgb15(uint32_t C) { + return ((C >> 15) & 0x7c00) + ((C >> 10) & 0x03e0) + ((C >> 5) & 0x001f); } -static uint32_t rgb888(uint16_t C) { - return ((C & 0x7c00) << 9) + ((C & 0x03e0) << 6) + ((C & 0x001f) << 3) - + ((C & 0x7000) << 4) + ((C & 0x0380) << 1) + ((C & 0x001c) >> 2); +static uint32_t rgb30(uint16_t C) { + return ((C & 0x7c00) << 15) + ((C & 0x7c00) << 10) + + ((C & 0x03e0) << 10) + ((C & 0x03e0) << 5) + + ((C & 0x001f) << 5) + ((C & 0x001f) << 0); } static void initialize() { @@ -51,11 +52,13 @@ static void initialize() { yuvTable = new uint32_t[32768]; for(unsigned i = 0; i < 32768; i++) { - uint32_t C = rgb888(i); + uint8_t R = (i >> 10) & 31; + uint8_t G = (i >> 5) & 31; + uint8_t B = (i >> 0) & 31; - double r = (uint8_t)(C >> 16); - double g = (uint8_t)(C >> 8); - double b = (uint8_t)(C >> 0); + double r = (R << 3) | (R >> 2); + double g = (G << 3) | (G >> 2); + double b = (B << 3) | (B >> 2); double y = (r + g + b) * (0.25f * (63.5f / 48.0f)); double u = ((r - b) * 0.25f + 128.0f) * (7.5f / 7.0f); @@ -173,15 +176,15 @@ dllexport void filter_render( *out1++ = 0; *out1++ = 0; for(unsigned x = 1; x < width - 1; x++) { - uint16_t A = rgb555(*(in - prevline - 1)); - uint16_t B = rgb555(*(in - prevline + 0)); - uint16_t C = rgb555(*(in - prevline + 1)); - uint16_t D = rgb555(*(in - 1)); - uint16_t E = rgb555(*(in + 0)); - uint16_t F = rgb555(*(in + 1)); - uint16_t G = rgb555(*(in + nextline - 1)); - uint16_t H = rgb555(*(in + nextline + 0)); - uint16_t I = rgb555(*(in + nextline + 1)); + uint16_t A = rgb15(*(in - prevline - 1)); + uint16_t B = rgb15(*(in - prevline + 0)); + uint16_t C = rgb15(*(in - prevline + 1)); + uint16_t D = rgb15(*(in - 1)); + uint16_t E = rgb15(*(in + 0)); + uint16_t F = rgb15(*(in + 1)); + uint16_t G = rgb15(*(in + nextline - 1)); + uint16_t H = rgb15(*(in + nextline + 0)); + uint16_t I = rgb15(*(in + nextline + 1)); uint32_t e = yuvTable[E] + diff_offset; uint8_t pattern; @@ -194,10 +197,10 @@ dllexport void filter_render( pattern |= diff(e, H) << 6; pattern |= diff(e, I) << 7; - *(out0 + 0) = rgb888(blend(hqTable[pattern], E, A, B, D, F, H)); pattern = rotate[pattern]; - *(out0 + 1) = rgb888(blend(hqTable[pattern], E, C, F, B, H, D)); pattern = rotate[pattern]; - *(out1 + 1) = rgb888(blend(hqTable[pattern], E, I, H, F, D, B)); pattern = rotate[pattern]; - *(out1 + 0) = rgb888(blend(hqTable[pattern], E, G, D, H, B, F)); + *(out0 + 0) = rgb30(blend(hqTable[pattern], E, A, B, D, F, H)); pattern = rotate[pattern]; + *(out0 + 1) = rgb30(blend(hqTable[pattern], E, C, F, B, H, D)); pattern = rotate[pattern]; + *(out1 + 1) = rgb30(blend(hqTable[pattern], E, I, H, F, D, B)); pattern = rotate[pattern]; + *(out1 + 0) = rgb30(blend(hqTable[pattern], E, G, D, H, B, F)); in++; out0 += 2; diff --git a/snesfilter/LQ2x/LQ2x.cpp b/snesfilter/LQ2x/LQ2x.cpp index f5696266..29d17cff 100755 --- a/snesfilter/LQ2x/LQ2x.cpp +++ b/snesfilter/LQ2x/LQ2x.cpp @@ -17,6 +17,7 @@ dllexport void filter_render( const uint32_t *input, unsigned inputPitch, unsigned width, unsigned height ) { + enum : unsigned { Mask = (1 << 20) | (1 << 10) | (1 << 0) }; outputPitch >>= 2, inputPitch >>= 2; #pragma omp parallel for @@ -36,10 +37,10 @@ dllexport void filter_render( uint32_t E = *(in++ + nextline); if(A != E && B != D) { - *out0++ = (A == B ? C + A - ((C ^ A) & 0x010101) >> 1 : C); - *out0++ = (A == D ? C + A - ((C ^ A) & 0x010101) >> 1 : C); - *out1++ = (E == B ? C + E - ((C ^ E) & 0x010101) >> 1 : C); - *out1++ = (E == D ? C + E - ((C ^ E) & 0x010101) >> 1 : C); + *out0++ = (A == B ? C + A - ((C ^ A) & Mask) >> 1 : C); + *out0++ = (A == D ? C + A - ((C ^ A) & Mask) >> 1 : C); + *out1++ = (E == B ? C + E - ((C ^ E) & Mask) >> 1 : C); + *out1++ = (E == D ? C + E - ((C ^ E) & Mask) >> 1 : C); } else { *out0++ = C; *out0++ = C; diff --git a/snesfilter/Makefile b/snesfilter/Makefile index 1a29918a..24875c36 100755 --- a/snesfilter/Makefile +++ b/snesfilter/Makefile @@ -2,12 +2,12 @@ include nall/Makefile c := $(compiler) -std=gnu99 cpp := $(subst cc,++,$(compiler)) -std=gnu++0x -flags := -fPIC -O3 -I. -Iobj -fomit-frame-pointer +flags := -fPIC -I. -Iobj -O3 -fomit-frame-pointer link := -s objects := ifeq ($(platform),x) - flags += -fopenmp +# flags += -fopenmp endif objects += out/Pixellate2x.filter diff --git a/snesfilter/Phosphor3x/Phosphor3x.cpp b/snesfilter/Phosphor3x/Phosphor3x.cpp index 817fc9be..69480e8c 100755 --- a/snesfilter/Phosphor3x/Phosphor3x.cpp +++ b/snesfilter/Phosphor3x/Phosphor3x.cpp @@ -17,6 +17,7 @@ dllexport void filter_render( const uint32_t *input, unsigned inputPitch, unsigned width, unsigned height ) { + enum : unsigned { Mask = (1022 << 20) + (1022 << 10) + (1022 << 0) }; outputPitch >>= 2, inputPitch >>= 2; #pragma omp parallel for @@ -31,27 +32,27 @@ dllexport void filter_render( uint32_t B = *in; uint32_t C = (x == width - 1 ? 0 : *(in + 1)); - uint8_t Ar = A >> 16, Ag = A >> 8, Ab = A >> 0; - uint8_t Br = B >> 16, Bg = B >> 8, Bb = B >> 0; - uint8_t Cr = C >> 16, Cg = C >> 8, Cb = C >> 0; + unsigned Ar = (A >> 20) & 1023, Ag = (A >> 10) & 1023, Ab = (A >> 0) & 1023; + unsigned Br = (B >> 20) & 1023, Bg = (B >> 10) & 1023, Bb = (B >> 0) & 1023; + unsigned Cr = (C >> 20) & 1023, Cg = (C >> 10) & 1023, Cb = (C >> 0) & 1023; - A = ((Br >> 0) << 16) + ((Bg >> 1) << 8) + ((Ab >> 1) << 0); - B = ((Br >> 1) << 16) + ((Bg >> 0) << 8) + ((Bb >> 1) << 0); - C = ((Cr >> 1) << 16) + ((Bg >> 1) << 8) + ((Bb >> 0) << 0); + A = ((Br >> 0) << 20) + ((Bg >> 1) << 10) + ((Ab >> 1) << 0); + B = ((Br >> 1) << 20) + ((Bg >> 0) << 10) + ((Bb >> 1) << 0); + C = ((Cr >> 1) << 20) + ((Bg >> 1) << 10) + ((Bb >> 0) << 0); in++; *out0++ = A; *out1++ = A; - *out2++ = (A & 0xf8f8f8) >> 1; + *out2++ = (A & Mask) >> 1; *out0++ = B; *out1++ = B; - *out2++ = (B & 0xf8f8f8) >> 1; + *out2++ = (B & Mask) >> 1; *out0++ = C; *out1++ = C; - *out2++ = (C & 0xf8f8f8) >> 1; + *out2++ = (C & Mask) >> 1; } } } diff --git a/snesfilter/Scanline/Scanline-Light.cpp b/snesfilter/Scanline/Scanline-Light.cpp index f38b8ef9..5ce4e1b9 100755 --- a/snesfilter/Scanline/Scanline-Light.cpp +++ b/snesfilter/Scanline/Scanline-Light.cpp @@ -16,6 +16,7 @@ dllexport void filter_render( const uint32_t *input, unsigned inputPitch, unsigned width, unsigned height ) { + enum : unsigned { Mask = (1022 << 20) | (1022 << 10) | (1022 << 0) }; outputPitch >>= 2, inputPitch >>= 2; #pragma omp parallel for @@ -26,7 +27,7 @@ dllexport void filter_render( for(unsigned x = 0; x < width; x++) { *out0++ = *in; - *out1++ = (*in++ & 0xf8f8f8) >> 1; + *out1++ = (*in++ & Mask) >> 1; } } } diff --git a/snesfilter/nall/Makefile b/snesfilter/nall/Makefile index 9a93bd23..a542beeb 100755 --- a/snesfilter/nall/Makefile +++ b/snesfilter/nall/Makefile @@ -19,6 +19,9 @@ ifeq ($(platform),) ifeq ($(uname),) platform := win delete = del $(subst /,\,$1) + else ifneq ($(findstring CYGWIN,$(uname)),) + platform := win + delete = del $(subst /,\,$1) else ifneq ($(findstring Darwin,$(uname)),) platform := osx delete = rm -f $1 @@ -32,9 +35,9 @@ ifeq ($(compiler),) ifeq ($(platform),win) compiler := gcc else ifeq ($(platform),osx) - compiler := gcc-mp-4.5 + compiler := gcc-mp-4.6 else - compiler := gcc-4.5 + compiler := gcc-4.6 endif endif diff --git a/snesfilter/nall/array.hpp b/snesfilter/nall/array.hpp index e1fb1fcb..4847a297 100755 --- a/snesfilter/nall/array.hpp +++ b/snesfilter/nall/array.hpp @@ -2,13 +2,12 @@ #define NALL_ARRAY_HPP #include +#include #include #include #include #include #include -#include -#include #include namespace nall { @@ -26,7 +25,7 @@ namespace nall { void reset() { if(pool) free(pool); - pool = 0; + pool = nullptr; poolsize = 0; buffersize = 0; } @@ -54,21 +53,14 @@ namespace nall { operator[](buffersize) = data; } + void append(const T data[], unsigned length) { + for(unsigned n = 0; n < length; n++) operator[](buffersize) = data[n]; + } + void remove() { if(size > 0) resize(size - 1); //remove last element only } - template void insert(unsigned index, const U list) { - unsigned listsize = container_size(list); - resize(buffersize + listsize); - memmove(pool + index + listsize, pool + index, (buffersize - index) * sizeof(T)); - foreach(item, list) pool[index++] = item; - } - - void insert(unsigned index, const T item) { - insert(index, array{ item }); - } - void remove(unsigned index, unsigned count = 1) { for(unsigned i = index; count + i < buffersize; i++) { pool[i] = pool[count + i]; @@ -86,10 +78,10 @@ namespace nall { memset(pool, 0, buffersize * sizeof(T)); } - array() : pool(0), poolsize(0), buffersize(0) { + array() : pool(nullptr), poolsize(0), buffersize(0) { } - array(std::initializer_list list) : pool(0), poolsize(0), buffersize(0) { + array(std::initializer_list list) : pool(nullptr), poolsize(0), buffersize(0) { for(const T *p = list.begin(); p != list.end(); ++p) append(*p); } @@ -107,7 +99,7 @@ namespace nall { return *this; } - array(const array &source) : pool(0), poolsize(0), buffersize(0) { + array(const array &source) : pool(nullptr), poolsize(0), buffersize(0) { operator=(source); } @@ -117,12 +109,12 @@ namespace nall { pool = source.pool; poolsize = source.poolsize; buffersize = source.buffersize; - source.pool = 0; + source.pool = nullptr; source.reset(); return *this; } - array(array &&source) : pool(0), poolsize(0), buffersize(0) { + array(array &&source) : pool(nullptr), poolsize(0), buffersize(0) { operator=(std::move(source)); } @@ -144,8 +136,6 @@ namespace nall { const T* begin() const { return &pool[0]; } const T* end() const { return &pool[buffersize]; } }; - - template struct has_size> { enum { value = true }; }; } #endif diff --git a/snesfilter/nall/atoi.hpp b/snesfilter/nall/atoi.hpp new file mode 100755 index 00000000..cec3e72d --- /dev/null +++ b/snesfilter/nall/atoi.hpp @@ -0,0 +1,88 @@ +#ifndef NALL_ATOI_HPP +#define NALL_ATOI_HPP + +namespace nall { + +//note: this header is intended to form the base for user-defined literals; +//once they are supported by GCC. eg: +//unsigned operator "" b(const char *s) { return binary(s); } +//-> signed data = 1001b; +//(0b1001 is nicer, but is not part of the C++ standard) + +constexpr inline uintmax_t binary_(const char *s, uintmax_t sum = 0) { + return ( + *s == '0' || *s == '1' ? binary_(s + 1, (sum << 1) | *s - '0') : + sum + ); +} + +constexpr inline uintmax_t octal_(const char *s, uintmax_t sum = 0) { + return ( + *s >= '0' && *s <= '7' ? octal_(s + 1, (sum << 3) | *s - '0') : + sum + ); +} + +constexpr inline uintmax_t decimal_(const char *s, uintmax_t sum = 0) { + return ( + *s >= '0' && *s <= '9' ? decimal_(s + 1, (sum * 10) + *s - '0') : + sum + ); +} + +constexpr inline uintmax_t hex_(const char *s, uintmax_t sum = 0) { + return ( + *s >= 'A' && *s <= 'F' ? hex_(s + 1, (sum << 4) | *s - 'A' + 10) : + *s >= 'a' && *s <= 'f' ? hex_(s + 1, (sum << 4) | *s - 'a' + 10) : + *s >= '0' && *s <= '9' ? hex_(s + 1, (sum << 4) | *s - '0') : + sum + ); +} + +// + +constexpr inline uintmax_t binary(const char *s) { + return ( + *s == '0' && *(s + 1) == 'B' ? binary_(s + 2) : + *s == '0' && *(s + 1) == 'b' ? binary_(s + 2) : + *s == '%' ? binary_(s + 1) : + binary_(s) + ); +} + +constexpr inline uintmax_t octal(const char *s) { + return ( + octal_(s) + ); +} + +constexpr inline intmax_t integer(const char *s) { + return ( + *s == '+' ? +decimal_(s + 1) : + *s == '-' ? -decimal_(s + 1) : + decimal_(s) + ); +} + +constexpr inline uintmax_t decimal(const char *s) { + return ( + decimal_(s) + ); +} + +constexpr inline uintmax_t hex(const char *s) { + return ( + *s == '0' && *(s + 1) == 'X' ? hex_(s + 2) : + *s == '0' && *(s + 1) == 'x' ? hex_(s + 2) : + *s == '$' ? hex_(s + 1) : + hex_(s) + ); +} + +inline double fp(const char *s) { + return atof(s); +} + +} + +#endif diff --git a/snesfilter/nall/base64.hpp b/snesfilter/nall/base64.hpp index ee59c1be..a0afd8b1 100755 --- a/snesfilter/nall/base64.hpp +++ b/snesfilter/nall/base64.hpp @@ -5,8 +5,7 @@ #include namespace nall { - class base64 { - public: + struct base64 { static bool encode(char *&output, const uint8_t* input, unsigned inlength) { output = new char[inlength * 8 / 6 + 6](); diff --git a/snesfilter/nall/bit.hpp b/snesfilter/nall/bit.hpp index ca6ea29a..67a35ad6 100755 --- a/snesfilter/nall/bit.hpp +++ b/snesfilter/nall/bit.hpp @@ -2,39 +2,39 @@ #define NALL_BIT_HPP namespace nall { - template inline unsigned uclamp(const unsigned x) { + template constexpr inline unsigned uclamp(const unsigned x) { enum { y = (1U << (bits - 1)) + ((1U << (bits - 1)) - 1) }; return y + ((x - y) & -(x < y)); //min(x, y); } - template inline unsigned uclip(const unsigned x) { + template constexpr inline unsigned uclip(const unsigned x) { enum { m = (1U << (bits - 1)) + ((1U << (bits - 1)) - 1) }; return (x & m); } - template inline signed sclamp(const signed x) { + template constexpr inline signed sclamp(const signed x) { enum { b = 1U << (bits - 1), m = (1U << (bits - 1)) - 1 }; return (x > m) ? m : (x < -b) ? -b : x; } - template inline signed sclip(const signed x) { + template constexpr inline signed sclip(const signed x) { enum { b = 1U << (bits - 1), m = (1U << bits) - 1 }; return ((x & m) ^ b) - b; } namespace bit { //lowest(0b1110) == 0b0010 - template inline T lowest(const T x) { + template constexpr inline T lowest(const T x) { return x & -x; } //clear_lowest(0b1110) == 0b1100 - template inline T clear_lowest(const T x) { + template constexpr inline T clear_lowest(const T x) { return x & (x - 1); } //set_lowest(0b0101) == 0b0111 - template inline T set_lowest(const T x) { + template constexpr inline T set_lowest(const T x) { return x | (x + 1); } diff --git a/snesfilter/nall/bps/delta.hpp b/snesfilter/nall/bps/delta.hpp index a3af047c..6cee56a3 100755 --- a/snesfilter/nall/bps/delta.hpp +++ b/snesfilter/nall/bps/delta.hpp @@ -24,7 +24,7 @@ protected: struct Node { unsigned offset; Node *next; - inline Node() : offset(0), next(0) {} + inline Node() : offset(0), next(nullptr) {} inline ~Node() { if(next) delete next; } }; diff --git a/snesfilter/nall/compositor.hpp b/snesfilter/nall/compositor.hpp index 6d5c46c9..6b9245f6 100755 --- a/snesfilter/nall/compositor.hpp +++ b/snesfilter/nall/compositor.hpp @@ -1,18 +1,56 @@ #ifndef NALL_COMPOSITOR_HPP #define NALL_COMPOSITOR_HPP -#include +#include namespace nall { struct compositor { inline static bool enabled(); inline static bool enable(bool status); + + #if defined(PLATFORM_X) + enum class Compositor : unsigned { Unknown, Metacity, Xfwm4 }; + inline static Compositor detect(); + + inline static bool enabled_metacity(); + inline static bool enable_metacity(bool status); + + inline static bool enabled_xfwm4(); + inline static bool enable_xfwm4(bool status); + #endif }; #if defined(PLATFORM_X) -bool compositor::enabled() { +//Metacity + +bool compositor::enabled_metacity() { + FILE *fp = popen("gconftool-2 --get /apps/metacity/general/compositing_manager", "r"); + if(fp == 0) return false; + + char buffer[512]; + if(fgets(buffer, sizeof buffer, fp) == 0) return false; + + if(!memcmp(buffer, "true", 4)) return true; + return false; +} + +bool compositor::enable_metacity(bool status) { + FILE *fp; + if(status) { + fp = popen("gconftool-2 --set --type bool /apps/metacity/general/compositing_manager true", "r"); + } else { + fp = popen("gconftool-2 --set --type bool /apps/metacity/general/compositing_manager false", "r"); + } + if(fp == 0) return false; + pclose(fp); + return true; +} + +//Xfwm4 + +bool compositor::enabled_xfwm4() { FILE *fp = popen("xfconf-query -c xfwm4 -p '/general/use_compositing'", "r"); if(fp == 0) return false; @@ -23,7 +61,7 @@ bool compositor::enabled() { return false; } -bool compositor::enable(bool status) { +bool compositor::enable_xfwm4(bool status) { FILE *fp; if(status) { fp = popen("xfconf-query -c xfwm4 -p '/general/use_compositing' -t 'bool' -s 'true'", "r"); @@ -35,7 +73,42 @@ bool compositor::enable(bool status) { return true; } -#elif defined(PLATFORM_WIN) +//General + +compositor::Compositor compositor::detect() { + Compositor result = Compositor::Unknown; + + FILE *fp; + char buffer[512]; + + fp = popen("pidof metacity", "r"); + if(fp && fgets(buffer, sizeof buffer, fp)) result = Compositor::Metacity; + pclose(fp); + + fp = popen("pidof xfwm4", "r"); + if(fp && fgets(buffer, sizeof buffer, fp)) result = Compositor::Xfwm4; + pclose(fp); + + return result; +} + +bool compositor::enabled() { + switch(detect()) { + case Compositor::Metacity: return enabled_metacity(); + case Compositor::Xfwm4: return enabled_xfwm4(); + default: return false; + } +} + +bool compositor::enable(bool status) { + switch(detect()) { + case Compositor::Metacity: return enable_metacity(status); + case Compositor::Xfwm4: return enable_xfwm4(status); + default: return false; + } +} + +#elif defined(PLATFORM_WINDOWS) bool compositor::enabled() { HMODULE module = GetModuleHandleW(L"dwmapi"); diff --git a/snesfilter/nall/config.hpp b/snesfilter/nall/config.hpp index 99aaee08..0c6602df 100755 --- a/snesfilter/nall/config.hpp +++ b/snesfilter/nall/config.hpp @@ -70,7 +70,7 @@ namespace nall { else list[n].type = unknown_t; } - virtual bool load(const char *filename) { + virtual bool load(const string &filename) { string data; if(data.readfile(filename) == true) { data.replace("\r", ""); @@ -100,7 +100,7 @@ namespace nall { } } - virtual bool save(const char *filename) const { + virtual bool save(const string &filename) const { file fp; if(fp.open(filename, file::mode::write)) { for(unsigned i = 0; i < list.size(); i++) { diff --git a/snesfilter/nall/directory.hpp b/snesfilter/nall/directory.hpp index 7fbc15f4..31ca1e05 100755 --- a/snesfilter/nall/directory.hpp +++ b/snesfilter/nall/directory.hpp @@ -1,11 +1,12 @@ #ifndef NALL_DIRECTORY_HPP #define NALL_DIRECTORY_HPP -#include +#include #include #include +#include -#if defined(_WIN32) +#if defined(PLATFORM_WINDOWS) #include #else #include @@ -22,7 +23,7 @@ struct directory { static lstring contents(const string &pathname, const string &pattern = "*"); }; -#if defined(_WIN32) +#if defined(PLATFORM_WINDOWS) inline bool directory::exists(const string &pathname) { DWORD result = GetFileAttributes(utf16_t(pathname)); if(result == INVALID_FILE_ATTRIBUTES) return false; @@ -56,7 +57,7 @@ struct directory { FindClose(handle); } if(list.size() > 0) sort(&list[0], list.size()); - foreach(name, list) name.append("/"); //must append after sorting + for(auto &name : list) name.append("/"); //must append after sorting return list; } @@ -89,7 +90,7 @@ struct directory { inline lstring directory::contents(const string &pathname, const string &pattern) { lstring folders = directory::folders(pathname); //pattern search of contents() should only filter files lstring files = directory::files(pathname, pattern); - foreach(file, files) folders.append(file); + for(auto &file : files) folders.append(file); return folders; } #else @@ -116,7 +117,7 @@ struct directory { closedir(dp); } if(list.size() > 0) sort(&list[0], list.size()); - foreach(name, list) name.append("/"); //must append after sorting + for(auto &name : list) name.append("/"); //must append after sorting return list; } @@ -142,7 +143,7 @@ struct directory { inline lstring directory::contents(const string &pathname, const string &pattern) { lstring folders = directory::folders(pathname); //pattern search of contents() should only filter files lstring files = directory::files(pathname, pattern); - foreach(file, files) folders.append(file); + for(auto &file : files) folders.append(file); return folders; } #endif diff --git a/snesfilter/nall/dl.hpp b/snesfilter/nall/dl.hpp index c697958c..3bd7d4d2 100755 --- a/snesfilter/nall/dl.hpp +++ b/snesfilter/nall/dl.hpp @@ -3,14 +3,14 @@ //dynamic linking support -#include +#include #include #include #include #if defined(PLATFORM_X) || defined(PLATFORM_OSX) #include -#elif defined(PLATFORM_WIN) +#elif defined(PLATFORM_WINDOWS) #include #include #endif @@ -81,7 +81,7 @@ namespace nall { dlclose((void*)handle); handle = 0; } - #elif defined(PLATFORM_WIN) + #elif defined(PLATFORM_WINDOWS) inline bool library::open(const char *name, const char *path) { if(handle) close(); string filepath(path, *path && !strend(path, "/") && !strend(path, "\\") ? "\\" : "", name, ".dll"); diff --git a/snesfilter/nall/dsp.hpp b/snesfilter/nall/dsp.hpp index 009c8b6c..a2400ec7 100755 --- a/snesfilter/nall/dsp.hpp +++ b/snesfilter/nall/dsp.hpp @@ -1,6 +1,11 @@ #ifndef NALL_DSP_HPP #define NALL_DSP_HPP +#include +#ifdef __SSE__ + #include +#endif + #define NALL_DSP_INTERNAL_HPP #include #undef NALL_DSP_INTERNAL_HPP diff --git a/snesfilter/nall/dsp/core.hpp b/snesfilter/nall/dsp/core.hpp index a4c58c38..a5b967b1 100755 --- a/snesfilter/nall/dsp/core.hpp +++ b/snesfilter/nall/dsp/core.hpp @@ -5,24 +5,40 @@ namespace nall { +//precision: can be float, double or long double +#define real float + +struct DSP; + +struct Resampler { + DSP &dsp; + real frequency; + + virtual void setFrequency() = 0; + virtual void clear() = 0; + virtual void sample() = 0; + Resampler(DSP &dsp) : dsp(dsp) {} +}; + struct DSP { - enum class Resampler : unsigned { - Point, + enum class ResampleEngine : unsigned { + Nearest, Linear, Cosine, Cubic, Hermite, Average, + Sinc, }; inline void setChannels(unsigned channels); inline void setPrecision(unsigned precision); - inline void setFrequency(double frequency); //inputFrequency - inline void setVolume(double volume); - inline void setBalance(double balance); + inline void setFrequency(real frequency); //inputFrequency + inline void setVolume(real volume); + inline void setBalance(real balance); - inline void setResampler(Resampler resampler); - inline void setResamplerFrequency(double frequency); //outputFrequency + inline void setResampler(ResampleEngine resamplingEngine); + inline void setResamplerFrequency(real frequency); //outputFrequency inline void sample(signed channel[]); inline bool pending(); @@ -33,33 +49,28 @@ struct DSP { inline ~DSP(); protected: + friend class ResampleNearest; + friend class ResampleLinear; + friend class ResampleCosine; + friend class ResampleCubic; + friend class ResampleAverage; + friend class ResampleHermite; + friend class ResampleSinc; + struct Settings { unsigned channels; unsigned precision; - double frequency; - double volume; - double balance; + real frequency; + real volume; + real balance; + //internal - double intensity; + real intensity; + real intensityInverse; } settings; - struct ResamplerSettings { - Resampler engine; - double frequency; - //internal - double fraction; - double step; - } resampler; - - inline void resamplerRun(); - inline void resamplerWrite(double channel[]); - - inline void resamplePoint(); - inline void resampleLinear(); - inline void resampleCosine(); - inline void resampleCubic(); - inline void resampleHermite(); - inline void resampleAverage(); + Resampler *resampler; + inline void write(real channel[]); #include "buffer.hpp" Buffer buffer; @@ -70,14 +81,21 @@ protected: inline signed clamp(const unsigned bits, const signed x); }; +#include "resample/nearest.hpp" +#include "resample/linear.hpp" +#include "resample/cosine.hpp" +#include "resample/cubic.hpp" +#include "resample/hermite.hpp" +#include "resample/average.hpp" +#include "resample/sinc.hpp" #include "settings.hpp" void DSP::sample(signed channel[]) { for(unsigned c = 0; c < settings.channels; c++) { - buffer.write(c) = (double)channel[c] / settings.intensity; + buffer.write(c) = (real)channel[c] * settings.intensityInverse; } buffer.wroffset++; - resamplerRun(); + resampler->sample(); } bool DSP::pending() { @@ -94,31 +112,13 @@ void DSP::read(signed channel[]) { output.rdoffset++; } -void DSP::resamplerRun() { - switch(resampler.engine) { - case Resampler::Point: return resamplePoint(); - case Resampler::Linear: return resampleLinear(); - case Resampler::Cosine: return resampleCosine(); - case Resampler::Cubic: return resampleCubic(); - case Resampler::Hermite: return resampleHermite(); - case Resampler::Average: return resampleAverage(); - } -} - -void DSP::resamplerWrite(double channel[]) { +void DSP::write(real channel[]) { for(unsigned c = 0; c < settings.channels; c++) { output.write(c) = channel[c]; } output.wroffset++; } -#include "resample/point.hpp" -#include "resample/linear.hpp" -#include "resample/cosine.hpp" -#include "resample/cubic.hpp" -#include "resample/hermite.hpp" -#include "resample/average.hpp" - void DSP::adjustVolume() { for(unsigned c = 0; c < settings.channels; c++) { output.read(c) *= settings.volume; @@ -138,25 +138,30 @@ signed DSP::clamp(const unsigned bits, const signed x) { } void DSP::clear() { - resampler.fraction = 0.0; buffer.clear(); output.clear(); + resampler->clear(); } DSP::DSP() { + setResampler(ResampleEngine::Hermite); + setResamplerFrequency(44100.0); + setChannels(2); setPrecision(16); setFrequency(44100.0); setVolume(1.0); setBalance(0.0); - setResampler(Resampler::Hermite); - setResamplerFrequency(44100.0); + clear(); } DSP::~DSP() { + if(resampler) delete resampler; } +#undef real + } #endif diff --git a/snesfilter/nall/dsp/resample/average.hpp b/snesfilter/nall/dsp/resample/average.hpp index c5cdbca3..867b13bf 100755 --- a/snesfilter/nall/dsp/resample/average.hpp +++ b/snesfilter/nall/dsp/resample/average.hpp @@ -1,31 +1,72 @@ #ifdef NALL_DSP_INTERNAL_HPP -void DSP::resampleAverage() { +struct ResampleAverage : Resampler { + inline void setFrequency(); + inline void clear(); + inline void sample(); + inline void sampleLinear(); + ResampleAverage(DSP &dsp) : Resampler(dsp) {} + + real fraction; + real step; +}; + +void ResampleAverage::setFrequency() { + fraction = 0.0; + step = dsp.settings.frequency / frequency; +} + +void ResampleAverage::clear() { + fraction = 0.0; +} + +void ResampleAverage::sample() { //can only average if input frequency >= output frequency - if(resampler.step < 1.0) return resampleHermite(); + if(step < 1.0) return sampleLinear(); - resampler.fraction += 1.0; + fraction += 1.0; - double scalar = 1.0; - if(resampler.fraction > resampler.step) scalar = 1.0 - (resampler.fraction - resampler.step); + real scalar = 1.0; + if(fraction > step) scalar = 1.0 - (fraction - step); - for(unsigned c = 0; c < settings.channels; c++) { - output.write(c) += buffer.read(c) * scalar; + for(unsigned c = 0; c < dsp.settings.channels; c++) { + dsp.output.write(c) += dsp.buffer.read(c) * scalar; } - if(resampler.fraction >= resampler.step) { - for(unsigned c = 0; c < settings.channels; c++) { - output.write(c) /= resampler.step; + if(fraction >= step) { + for(unsigned c = 0; c < dsp.settings.channels; c++) { + dsp.output.write(c) /= step; } - output.wroffset++; + dsp.output.wroffset++; - resampler.fraction -= resampler.step; - for(unsigned c = 0; c < settings.channels; c++) { - output.write(c) = buffer.read(c) * resampler.fraction; + fraction -= step; + for(unsigned c = 0; c < dsp.settings.channels; c++) { + dsp.output.write(c) = dsp.buffer.read(c) * fraction; } } - buffer.rdoffset++; + dsp.buffer.rdoffset++; +} + +void ResampleAverage::sampleLinear() { + while(fraction <= 1.0) { + real channel[dsp.settings.channels]; + + for(unsigned n = 0; n < dsp.settings.channels; n++) { + real a = dsp.buffer.read(n, -1); + real b = dsp.buffer.read(n, -0); + + real mu = fraction; + + channel[n] = a * (1.0 - mu) + b * mu; + } + + dsp.write(channel); + fraction += step; + } + + dsp.buffer.rdoffset++; + fraction -= 1.0; } #endif diff --git a/snesfilter/nall/dsp/resample/cosine.hpp b/snesfilter/nall/dsp/resample/cosine.hpp index 5405b7f3..3363d5f6 100755 --- a/snesfilter/nall/dsp/resample/cosine.hpp +++ b/snesfilter/nall/dsp/resample/cosine.hpp @@ -1,25 +1,44 @@ #ifdef NALL_DSP_INTERNAL_HPP -void DSP::resampleCosine() { - while(resampler.fraction <= 1.0) { - double channel[settings.channels]; +struct ResampleCosine : Resampler { + inline void setFrequency(); + inline void clear(); + inline void sample(); + ResampleCosine(DSP &dsp) : Resampler(dsp) {} - for(unsigned n = 0; n < settings.channels; n++) { - double a = buffer.read(n, -1); - double b = buffer.read(n, -0); + real fraction; + real step; +}; - double mu = resampler.fraction; +void ResampleCosine::setFrequency() { + fraction = 0.0; + step = dsp.settings.frequency / frequency; +} + +void ResampleCosine::clear() { + fraction = 0.0; +} + +void ResampleCosine::sample() { + while(fraction <= 1.0) { + real channel[dsp.settings.channels]; + + for(unsigned n = 0; n < dsp.settings.channels; n++) { + real a = dsp.buffer.read(n, -1); + real b = dsp.buffer.read(n, -0); + + real mu = fraction; mu = (1.0 - cos(mu * 3.14159265)) / 2.0; channel[n] = a * (1.0 - mu) + b * mu; } - resamplerWrite(channel); - resampler.fraction += resampler.step; + dsp.write(channel); + fraction += step; } - buffer.rdoffset++; - resampler.fraction -= 1.0; + dsp.buffer.rdoffset++; + fraction -= 1.0; } #endif diff --git a/snesfilter/nall/dsp/resample/cubic.hpp b/snesfilter/nall/dsp/resample/cubic.hpp index 71e3766f..bc4cc955 100755 --- a/snesfilter/nall/dsp/resample/cubic.hpp +++ b/snesfilter/nall/dsp/resample/cubic.hpp @@ -1,31 +1,50 @@ #ifdef NALL_DSP_INTERNAL_HPP -void DSP::resampleCubic() { - while(resampler.fraction <= 1.0) { - double channel[settings.channels]; +struct ResampleCubic : Resampler { + inline void setFrequency(); + inline void clear(); + inline void sample(); + ResampleCubic(DSP &dsp) : Resampler(dsp) {} - for(unsigned n = 0; n < settings.channels; n++) { - double a = buffer.read(n, -3); - double b = buffer.read(n, -2); - double c = buffer.read(n, -1); - double d = buffer.read(n, -0); + real fraction; + real step; +}; - double mu = resampler.fraction; +void ResampleCubic::setFrequency() { + fraction = 0.0; + step = dsp.settings.frequency / frequency; +} - double A = d - c - a + b; - double B = a - b - A; - double C = c - a; - double D = b; +void ResampleCubic::clear() { + fraction = 0.0; +} + +void ResampleCubic::sample() { + while(fraction <= 1.0) { + real channel[dsp.settings.channels]; + + for(unsigned n = 0; n < dsp.settings.channels; n++) { + real a = dsp.buffer.read(n, -3); + real b = dsp.buffer.read(n, -2); + real c = dsp.buffer.read(n, -1); + real d = dsp.buffer.read(n, -0); + + real mu = fraction; + + real A = d - c - a + b; + real B = a - b - A; + real C = c - a; + real D = b; channel[n] = A * (mu * 3) + B * (mu * 2) + C * mu + D; } - resamplerWrite(channel); - resampler.fraction += resampler.step; + dsp.write(channel); + fraction += step; } - buffer.rdoffset++; - resampler.fraction -= 1.0; + dsp.buffer.rdoffset++; + fraction -= 1.0; } #endif diff --git a/snesfilter/nall/dsp/resample/hermite.hpp b/snesfilter/nall/dsp/resample/hermite.hpp index 6eed087d..0cc9ba0e 100755 --- a/snesfilter/nall/dsp/resample/hermite.hpp +++ b/snesfilter/nall/dsp/resample/hermite.hpp @@ -1,21 +1,40 @@ #ifdef NALL_DSP_INTERNAL_HPP -void DSP::resampleHermite() { - while(resampler.fraction <= 1.0) { - double channel[settings.channels]; +struct ResampleHermite : Resampler { + inline void setFrequency(); + inline void clear(); + inline void sample(); + ResampleHermite(DSP &dsp) : Resampler(dsp) {} - for(unsigned n = 0; n < settings.channels; n++) { - double a = buffer.read(n, -3); - double b = buffer.read(n, -2); - double c = buffer.read(n, -1); - double d = buffer.read(n, -0); + real fraction; + real step; +}; - const double tension = 0.0; //-1 = low, 0 = normal, +1 = high - const double bias = 0.0; //-1 = left, 0 = even, +1 = right +void ResampleHermite::setFrequency() { + fraction = 0.0; + step = dsp.settings.frequency / frequency; +} - double mu1, mu2, mu3, m0, m1, a0, a1, a2, a3; +void ResampleHermite::clear() { + fraction = 0.0; +} - mu1 = resampler.fraction; +void ResampleHermite::sample() { + while(fraction <= 1.0) { + real channel[dsp.settings.channels]; + + for(unsigned n = 0; n < dsp.settings.channels; n++) { + real a = dsp.buffer.read(n, -3); + real b = dsp.buffer.read(n, -2); + real c = dsp.buffer.read(n, -1); + real d = dsp.buffer.read(n, -0); + + const real tension = 0.0; //-1 = low, 0 = normal, +1 = high + const real bias = 0.0; //-1 = left, 0 = even, +1 = right + + real mu1, mu2, mu3, m0, m1, a0, a1, a2, a3; + + mu1 = fraction; mu2 = mu1 * mu1; mu3 = mu2 * mu1; @@ -32,12 +51,12 @@ void DSP::resampleHermite() { channel[n] = (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c); } - resamplerWrite(channel); - resampler.fraction += resampler.step; + dsp.write(channel); + fraction += step; } - buffer.rdoffset++; - resampler.fraction -= 1.0; + dsp.buffer.rdoffset++; + fraction -= 1.0; } #endif diff --git a/snesfilter/nall/dsp/resample/lib/sinc.hpp b/snesfilter/nall/dsp/resample/lib/sinc.hpp new file mode 100755 index 00000000..3e953679 --- /dev/null +++ b/snesfilter/nall/dsp/resample/lib/sinc.hpp @@ -0,0 +1,600 @@ +// If these types are changed to anything other than "float", you should comment out the SSE detection directives below +// so that the SSE code is not used. + +typedef float resample_coeff_t; // note: sizeof(resample_coeff_t) must be == to a power of 2, and not larger than 16 +typedef float resample_samp_t; + + +// ...but don't comment this single RESAMPLE_SSEREGPARM define out when disabling SSE. +#define RESAMPLE_SSEREGPARM + +#if defined(__SSE__) + #define SINCRESAMPLE_USE_SSE 1 + #ifndef __x86_64__ + #undef RESAMPLE_SSEREGPARM + #define RESAMPLE_SSEREGPARM __attribute__((sseregparm)) + #endif +#else + // TODO: altivec here +#endif + +namespace ResampleUtility +{ + inline void kaiser_window(double* io, int count, double beta); + inline void gen_sinc(double* out, int size, double cutoff, double kaiser); + inline void gen_sinc_os(double* out, int size, double cutoff, double kaiser); + inline void normalize(double* io, int size, double gain = 1.0); + + inline void* make_aligned(void* ptr, unsigned boundary); // boundary must be a power of 2 +} + +class SincResampleHR +{ + private: + + inline void Init(unsigned ratio_arg, double desired_bandwidth, double beta, double d); + + inline void write(resample_samp_t sample) RESAMPLE_SSEREGPARM; + inline resample_samp_t read(void) RESAMPLE_SSEREGPARM; + inline bool output_avail(void); + + private: + + inline resample_samp_t mac(const resample_samp_t *wave, const resample_coeff_t *coeff, unsigned count); + + unsigned ratio; + unsigned num_convolutions; + + resample_coeff_t *coeffs; + std::vector coeffs_mem; + + // second half of ringbuffer should be copy of first half. + resample_samp_t *rb; + std::vector rb_mem; + + signed rb_readpos; + signed rb_writepos; + signed rb_in; + signed rb_eff_size; + + friend class SincResample; +}; + +class SincResample +{ + public: + + enum + { + QUALITY_LOW = 0, + QUALITY_MEDIUM = 2, + QUALITY_HIGH = 4 + }; + + inline SincResample(double input_rate, double output_rate, double desired_bandwidth, unsigned quality = QUALITY_HIGH); + + inline void write(resample_samp_t sample) RESAMPLE_SSEREGPARM; + inline resample_samp_t read(void) RESAMPLE_SSEREGPARM; + inline bool output_avail(void); + + private: + + inline void Init(double input_rate, double output_rate, double desired_bandwidth, double beta, double d, unsigned pn_nume, unsigned phases_min); + + inline resample_samp_t mac(const resample_samp_t *wave, const resample_coeff_t *coeffs_a, const resample_coeff_t *coeffs_b, const double ffract, unsigned count) RESAMPLE_SSEREGPARM; + + unsigned num_convolutions; + unsigned num_phases; + + unsigned step_int; + double step_fract; + + double input_pos_fract; + + + std::vector coeffs; // Pointers into coeff_mem. + std::vector coeff_mem; + + + std::vector rb; // second half should be copy of first half. + signed rb_readpos; + signed rb_writepos; + signed rb_in; + + bool hr_used; + SincResampleHR hr; +}; + + +// +// Code: +// +//#include "resample.hpp" + +#if 0 +namespace bit +{ + inline unsigned round(unsigned x) { + if((x & (x - 1)) == 0) return x; + while(x & (x - 1)) x &= x - 1; + return x << 1; + } +} +#endif + +void SincResampleHR::Init(unsigned ratio_arg, double desired_bandwidth, double beta, double d) +{ + const unsigned align_boundary = 16; + std::vector coeffs_tmp; + double cutoff; // 1.0 = f/2 + + ratio = ratio_arg; + + //num_convolutions = ((unsigned)ceil(d / ((1.0 - desired_bandwidth) / ratio)) + 1) &~ 1; // round up to be even + num_convolutions = ((unsigned)ceil(d / ((1.0 - desired_bandwidth) / ratio)) | 1); + + cutoff = (1.0 / ratio) - (d / num_convolutions); + +//printf("%d %d %.20f\n", ratio, num_convolutions, cutoff); + assert(num_convolutions > ratio); + + + // Generate windowed sinc of POWER + coeffs_tmp.resize(num_convolutions); + //ResampleUtility::gen_sinc(&coeffs_tmp[0], num_convolutions, cutoff, beta); + ResampleUtility::gen_sinc_os(&coeffs_tmp[0], num_convolutions, cutoff, beta); + ResampleUtility::normalize(&coeffs_tmp[0], num_convolutions); + + // Copy from coeffs_tmp to coeffs~ + // We multiply many coefficients at a time in the mac loop, so make sure the last few that don't really + // exist are allocated, zero'd mem. + + coeffs_mem.resize(((num_convolutions + 7) &~ 7) * sizeof(resample_coeff_t) + (align_boundary - 1)); + coeffs = (resample_coeff_t *)ResampleUtility::make_aligned(&coeffs_mem[0], align_boundary); + + + for(unsigned i = 0; i < num_convolutions; i++) + coeffs[i] = coeffs_tmp[i]; + + rb_eff_size = nall::bit::round(num_convolutions * 2) >> 1; + rb_readpos = 0; + rb_writepos = 0; + rb_in = 0; + + rb_mem.resize(rb_eff_size * 2 * sizeof(resample_samp_t) + (align_boundary - 1)); + rb = (resample_samp_t *)ResampleUtility::make_aligned(&rb_mem[0], align_boundary); +} + + +inline bool SincResampleHR::output_avail(void) +{ + return(rb_in >= (signed)num_convolutions); +} + +inline void SincResampleHR::write(resample_samp_t sample) +{ + assert(!output_avail()); + + rb[rb_writepos] = sample; + rb[rb_writepos + rb_eff_size] = sample; + rb_writepos = (rb_writepos + 1) & (rb_eff_size - 1); + rb_in++; +} + +resample_samp_t SincResampleHR::mac(const resample_samp_t *wave, const resample_coeff_t *coeff, unsigned count) +{ +#if SINCRESAMPLE_USE_SSE + __m128 accum_veca[2] = { _mm_set1_ps(0), _mm_set1_ps(0) }; + + resample_samp_t accum; + + for(unsigned c = 0; c < count; c += 8) + { + for(unsigned i = 0; i < 2; i++) + { + __m128 co[2]; + __m128 w[2]; + + co[i] = _mm_load_ps(&coeff[c + i * 4]); + w[i] = _mm_load_ps(&wave[c + i * 4]); + + w[i] = _mm_mul_ps(w[i], co[i]); + + accum_veca[i] = _mm_add_ps(w[i], accum_veca[i]); + } + } + + __m128 accum_vec = _mm_add_ps(accum_veca[0], accum_veca[1]); //_mm_add_ps(_mm_add_ps(accum_veca[0], accum_veca[1]), _mm_add_ps(accum_veca[2], accum_veca[3])); + + accum_vec = _mm_add_ps(accum_vec, _mm_shuffle_ps(accum_vec, accum_vec, (3 << 0) | (2 << 2) | (1 << 4) | (0 << 6))); + accum_vec = _mm_add_ps(accum_vec, _mm_shuffle_ps(accum_vec, accum_vec, (1 << 0) | (0 << 2) | (1 << 4) | (0 << 6))); + + _mm_store_ss(&accum, accum_vec); + + return accum; +#else + resample_samp_t accum[4] = { 0, 0, 0, 0 }; + + for(unsigned c = 0; c < count; c+= 4) + { + accum[0] += wave[c + 0] * coeff[c + 0]; + accum[1] += wave[c + 1] * coeff[c + 1]; + accum[2] += wave[c + 2] * coeff[c + 2]; + accum[3] += wave[c + 3] * coeff[c + 3]; + } + + return (accum[0] + accum[1]) + (accum[2] + accum[3]); // don't mess with parentheses(assuming compiler doesn't already, which it may... + +#endif +} + + +resample_samp_t SincResampleHR::read(void) +{ + assert(output_avail()); + resample_samp_t ret; + + ret = mac(&rb[rb_readpos], &coeffs[0], num_convolutions); + + rb_readpos = (rb_readpos + ratio) & (rb_eff_size - 1); + rb_in -= ratio; + + return ret; +} + + +SincResample::SincResample(double input_rate, double output_rate, double desired_bandwidth, unsigned quality) +{ + const struct + { + double beta; + double d; + unsigned pn_nume; + unsigned phases_min; + } qtab[5] = + { + { 5.658, 3.62, 4096, 4 }, + { 6.764, 4.32, 8192, 4 }, + { 7.865, 5.0, 16384, 8 }, + { 8.960, 5.7, 32768, 16 }, + { 10.056, 6.4, 65536, 32 } + }; + + // Sanity checks + assert(ceil(input_rate) > 0); + assert(ceil(output_rate) > 0); + assert(ceil(input_rate / output_rate) <= 1024); + assert(ceil(output_rate / input_rate) <= 1024); + + // The simplistic number-of-phases calculation code doesn't work well enough for when desired_bandwidth is close to 1.0 and when + // upsampling. + assert(desired_bandwidth >= 0.25 && desired_bandwidth < 0.96); + assert(quality >= 0 && quality <= 4); + + hr_used = false; + +#if 1 + // Round down to the nearest multiple of 4(so wave buffer remains aligned) + // It also adjusts the effective intermediate sampling rate up slightly, so that the upper frequencies below f/2 + // aren't overly attenuated so much. In the future, we might want to do an FFT or something to choose the intermediate rate more accurately + // to virtually eliminate over-attenuation. + unsigned ioratio_rd = (unsigned)floor(input_rate / (output_rate * (1.0 + (1.0 - desired_bandwidth) / 2) )) & ~3; + + if(ioratio_rd >= 8) + { + hr.Init(ioratio_rd, desired_bandwidth, qtab[quality].beta, qtab[quality].d); //10.056, 6.4); + hr_used = true; + + input_rate /= ioratio_rd; + } +#endif + + Init(input_rate, output_rate, desired_bandwidth, qtab[quality].beta, qtab[quality].d, qtab[quality].pn_nume, qtab[quality].phases_min); +} + +void SincResample::Init(double input_rate, double output_rate, double desired_bandwidth, double beta, double d, unsigned pn_nume, unsigned phases_min) +{ + const unsigned max_mult_atatime = 8; // multiply "granularity". must be power of 2. + const unsigned max_mult_minus1 = (max_mult_atatime - 1); + const unsigned conv_alignment_bytes = 16; // must be power of 2 + const double input_to_output_ratio = input_rate / output_rate; + const double output_to_input_ratio = output_rate / input_rate; + double cutoff; // 1.0 = input_rate / 2 + std::vector coeff_init_buffer; + + // Round up num_convolutions to be even. + if(output_rate > input_rate) + num_convolutions = ((unsigned)ceil(d / (1.0 - desired_bandwidth)) + 1) & ~1; + else + num_convolutions = ((unsigned)ceil(d / (output_to_input_ratio * (1.0 - desired_bandwidth))) + 1) & ~1; + + if(output_rate > input_rate) // Upsampling + cutoff = desired_bandwidth; + else // Downsampling + cutoff = output_to_input_ratio * desired_bandwidth; + + // Round up to be even. + num_phases = (std::max(pn_nume / num_convolutions, phases_min) + 1) &~1; + + // Adjust cutoff to account for the multiple phases. + cutoff = cutoff / num_phases; + + assert((num_convolutions & 1) == 0); + assert((num_phases & 1) == 0); + +// fprintf(stderr, "num_convolutions=%u, num_phases=%u, total expected coeff byte size=%lu\n", num_convolutions, num_phases, +// (long)((num_phases + 2) * ((num_convolutions + max_mult_minus1) & ~max_mult_minus1) * sizeof(float) + conv_alignment_bytes)); + + coeff_init_buffer.resize(num_phases * num_convolutions); + + coeffs.resize(num_phases + 1 + 1); + + coeff_mem.resize((num_phases + 1 + 1) * ((num_convolutions + max_mult_minus1) &~ max_mult_minus1) * sizeof(resample_coeff_t) + conv_alignment_bytes); + + // Assign aligned pointers into coeff_mem + { + resample_coeff_t *base_ptr = (resample_coeff_t *)ResampleUtility::make_aligned(&coeff_mem[0], conv_alignment_bytes); + + for(unsigned phase = 0; phase < (num_phases + 1 + 1); phase++) + { + coeffs[phase] = base_ptr + (((num_convolutions + max_mult_minus1) & ~max_mult_minus1) * phase); + } + } + + ResampleUtility::gen_sinc(&coeff_init_buffer[0], num_phases * num_convolutions, cutoff, beta); + ResampleUtility::normalize(&coeff_init_buffer[0], num_phases * num_convolutions, num_phases); + + // Reorder coefficients to allow for more efficient convolution. + for(int phase = -1; phase < ((int)num_phases + 1); phase++) + { + for(int conv = 0; conv < (int)num_convolutions; conv++) + { + double coeff; + + if(phase == -1 && conv == 0) + coeff = 0; + else if(phase == (int)num_phases && conv == ((int)num_convolutions - 1)) + coeff = 0; + else + coeff = coeff_init_buffer[conv * num_phases + phase]; + + coeffs[phase + 1][conv] = coeff; + } + } + + // Free a bit of mem + coeff_init_buffer.resize(0); + + step_int = floor(input_to_output_ratio); + step_fract = input_to_output_ratio - step_int; + + input_pos_fract = 0; + + // Do NOT use rb.size() later in the code, since it'll include the padding. + // We should only need one "max_mult_minus1" here, not two, since it won't matter if it over-reads(due to doing "max_mult_atatime" multiplications at a time + // rather than just 1, in which case this over-read wouldn't happen), from the first half into the duplicated half, + // since those corresponding coefficients will be zero anyway; this is just to handle the case of reading off the end of the duplicated half to + // prevent illegal memory accesses. + rb.resize(num_convolutions * 2 + max_mult_minus1); + + rb_readpos = 0; + rb_writepos = 0; + rb_in = 0; +} + +resample_samp_t SincResample::mac(const resample_samp_t *wave, const resample_coeff_t *coeffs_a, const resample_coeff_t *coeffs_b, const double ffract, unsigned count) +{ + resample_samp_t accum = 0; +#if SINCRESAMPLE_USE_SSE + __m128 accum_vec_a[2] = { _mm_set1_ps(0), _mm_set1_ps(0) }; + __m128 accum_vec_b[2] = { _mm_set1_ps(0), _mm_set1_ps(0) }; + + for(unsigned c = 0; c < count; c += 8) //8) //4) + { + __m128 coeff_a[2]; + __m128 coeff_b[2]; + __m128 w[2]; + __m128 result_a[2], result_b[2]; + + for(unsigned i = 0; i < 2; i++) + { + coeff_a[i] = _mm_load_ps(&coeffs_a[c + (i * 4)]); + coeff_b[i] = _mm_load_ps(&coeffs_b[c + (i * 4)]); + w[i] = _mm_loadu_ps(&wave[c + (i * 4)]); + + result_a[i] = _mm_mul_ps(coeff_a[i], w[i]); + result_b[i] = _mm_mul_ps(coeff_b[i], w[i]); + + accum_vec_a[i] = _mm_add_ps(result_a[i], accum_vec_a[i]); + accum_vec_b[i] = _mm_add_ps(result_b[i], accum_vec_b[i]); + } + } + + __m128 accum_vec, av_a, av_b; + __m128 mult_a_vec = _mm_set1_ps(1.0 - ffract); + __m128 mult_b_vec = _mm_set1_ps(ffract); + + av_a = _mm_mul_ps(mult_a_vec, /*accum_vec_a[0]);*/ _mm_add_ps(accum_vec_a[0], accum_vec_a[1])); + av_b = _mm_mul_ps(mult_b_vec, /*accum_vec_b[0]);*/ _mm_add_ps(accum_vec_b[0], accum_vec_b[1])); + + accum_vec = _mm_add_ps(av_a, av_b); + + accum_vec = _mm_add_ps(accum_vec, _mm_shuffle_ps(accum_vec, accum_vec, (3 << 0) | (2 << 2) | (1 << 4) | (0 << 6))); + accum_vec = _mm_add_ps(accum_vec, _mm_shuffle_ps(accum_vec, accum_vec, (1 << 0) | (0 << 2) | (1 << 4) | (0 << 6))); + + _mm_store_ss(&accum, accum_vec); +#else + resample_coeff_t mult_a = 1.0 - ffract; + resample_coeff_t mult_b = ffract; + + for(unsigned c = 0; c < count; c += 4) + { + accum += wave[c + 0] * (coeffs_a[c + 0] * mult_a + coeffs_b[c + 0] * mult_b); + accum += wave[c + 1] * (coeffs_a[c + 1] * mult_a + coeffs_b[c + 1] * mult_b); + accum += wave[c + 2] * (coeffs_a[c + 2] * mult_a + coeffs_b[c + 2] * mult_b); + accum += wave[c + 3] * (coeffs_a[c + 3] * mult_a + coeffs_b[c + 3] * mult_b); + } +#endif + + return accum; +} + +inline bool SincResample::output_avail(void) +{ + return(rb_in >= (int)num_convolutions); +} + +resample_samp_t SincResample::read(void) +{ + assert(output_avail()); + double phase = input_pos_fract * num_phases - 0.5; + signed phase_int = (signed)floor(phase); + double phase_fract = phase - phase_int; + unsigned phase_a = num_phases - 1 - phase_int; + unsigned phase_b = phase_a - 1; + resample_samp_t ret; + + ret = mac(&rb[rb_readpos], &coeffs[phase_a + 1][0], &coeffs[phase_b + 1][0], phase_fract, num_convolutions); + + unsigned int_increment = step_int; + + input_pos_fract += step_fract; + int_increment += floor(input_pos_fract); + input_pos_fract -= floor(input_pos_fract); + + rb_readpos = (rb_readpos + int_increment) % num_convolutions; + rb_in -= int_increment; + + return ret; +} + +inline void SincResample::write(resample_samp_t sample) +{ + assert(!output_avail()); + + if(hr_used) + { + hr.write(sample); + + if(hr.output_avail()) + { + sample = hr.read(); + } + else + { + return; + } + } + + rb[rb_writepos + 0 * num_convolutions] = sample; + rb[rb_writepos + 1 * num_convolutions] = sample; + rb_writepos = (rb_writepos + 1) % num_convolutions; + rb_in++; +} + +void ResampleUtility::kaiser_window( double* io, int count, double beta) +{ + int const accuracy = 24; //16; //12; + + double* end = io + count; + + double beta2 = beta * beta * (double) -0.25; + double to_fract = beta2 / ((double) count * count); + double i = 0; + double rescale = 0; // Doesn't need an initializer, to shut up gcc + + for ( ; io < end; ++io, i += 1 ) + { + double x = i * i * to_fract - beta2; + double u = x; + double k = x + 1; + + double n = 2; + do + { + u *= x / (n * n); + n += 1; + k += u; + } + while ( k <= u * (1 << accuracy) ); + + if ( !i ) + rescale = 1 / k; // otherwise values get large + + *io *= k * rescale; + } +} + +void ResampleUtility::gen_sinc(double* out, int size, double cutoff, double kaiser) +{ + assert( size % 2 == 0 ); // size must be even + + int const half_size = size / 2; + double* const mid = &out [half_size]; + + // Generate right half of sinc + for ( int i = 0; i < half_size; i++ ) + { + double angle = (i * 2 + 1) * (M_PI / 2); + mid [i] = sin( angle * cutoff ) / angle; + } + + kaiser_window( mid, half_size, kaiser ); + + // Mirror for left half + for ( int i = 0; i < half_size; i++ ) + out [i] = mid [half_size - 1 - i]; +} + +void ResampleUtility::gen_sinc_os(double* out, int size, double cutoff, double kaiser) +{ + assert( size % 2 == 1); // size must be odd + + for(int i = 0; i < size; i++) + { + if(i == (size / 2)) + out[i] = 2 * M_PI * (cutoff / 2); //0.078478; //1.0; //sin(2 * M_PI * (cutoff / 2) * (i - size / 2)) / (i - (size / 2)); + else + out[i] = sin(2 * M_PI * (cutoff / 2) * (i - size / 2)) / (i - (size / 2)); + +// out[i] *= 0.3635819 - 0.4891775 * cos(2 * M_PI * i / (size - 1)) + 0.1365995 * cos(4 * M_PI * i / (size - 1)) - 0.0106411 * cos(6 * M_PI * i / (size - 1)); +//0.42 - 0.5 * cos(2 * M_PI * i / (size - 1)) + 0.08 * cos(4 * M_PI * i / (size - 1)); + +// printf("%d %f\n", i, out[i]); + } + + kaiser_window(&out[size / 2], size / 2 + 1, kaiser); + + // Mirror for left half + for ( int i = 0; i < size / 2; i++ ) + out [i] = out [size - 1 - i]; + +} + +void ResampleUtility::normalize(double* io, int size, double gain) +{ + double sum = 0; + for ( int i = 0; i < size; i++ ) + sum += io [i]; + + double scale = gain / sum; + for ( int i = 0; i < size; i++ ) + io [i] *= scale; +} + +void* ResampleUtility::make_aligned(void* ptr, unsigned boundary) +{ + unsigned char* null_ptr = (unsigned char *)NULL; + unsigned char* uc_ptr = (unsigned char *)ptr; + + uc_ptr += (boundary - ((uc_ptr - null_ptr) & (boundary - 1))) & (boundary - 1); + + //while((uc_ptr - null_ptr) & (boundary - 1)) + // uc_ptr++; + + //printf("%16llx %16llx\n", (unsigned long long)ptr, (unsigned long long)uc_ptr); + + assert((uc_ptr - (unsigned char *)ptr) < boundary && (uc_ptr >= (unsigned char *)ptr)); + + return uc_ptr; +} diff --git a/snesfilter/nall/dsp/resample/linear.hpp b/snesfilter/nall/dsp/resample/linear.hpp index 3dbda6a0..3c2dc9e6 100755 --- a/snesfilter/nall/dsp/resample/linear.hpp +++ b/snesfilter/nall/dsp/resample/linear.hpp @@ -1,24 +1,43 @@ #ifdef NALL_DSP_INTERNAL_HPP -void DSP::resampleLinear() { - while(resampler.fraction <= 1.0) { - double channel[settings.channels]; +struct ResampleLinear : Resampler { + inline void setFrequency(); + inline void clear(); + inline void sample(); + ResampleLinear(DSP &dsp) : Resampler(dsp) {} - for(unsigned n = 0; n < settings.channels; n++) { - double a = buffer.read(n, -1); - double b = buffer.read(n, -0); + real fraction; + real step; +}; - double mu = resampler.fraction; +void ResampleLinear::setFrequency() { + fraction = 0.0; + step = dsp.settings.frequency / frequency; +} + +void ResampleLinear::clear() { + fraction = 0.0; +} + +void ResampleLinear::sample() { + while(fraction <= 1.0) { + real channel[dsp.settings.channels]; + + for(unsigned n = 0; n < dsp.settings.channels; n++) { + real a = dsp.buffer.read(n, -1); + real b = dsp.buffer.read(n, -0); + + real mu = fraction; channel[n] = a * (1.0 - mu) + b * mu; } - resamplerWrite(channel); - resampler.fraction += resampler.step; + dsp.write(channel); + fraction += step; } - buffer.rdoffset++; - resampler.fraction -= 1.0; + dsp.buffer.rdoffset++; + fraction -= 1.0; } #endif diff --git a/snesfilter/nall/dsp/resample/nearest.hpp b/snesfilter/nall/dsp/resample/nearest.hpp new file mode 100755 index 00000000..14b401eb --- /dev/null +++ b/snesfilter/nall/dsp/resample/nearest.hpp @@ -0,0 +1,43 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +struct ResampleNearest : Resampler { + inline void setFrequency(); + inline void clear(); + inline void sample(); + ResampleNearest(DSP &dsp) : Resampler(dsp) {} + + real fraction; + real step; +}; + +void ResampleNearest::setFrequency() { + fraction = 0.0; + step = dsp.settings.frequency / frequency; +} + +void ResampleNearest::clear() { + fraction = 0.0; +} + +void ResampleNearest::sample() { + while(fraction <= 1.0) { + real channel[dsp.settings.channels]; + + for(unsigned n = 0; n < dsp.settings.channels; n++) { + real a = dsp.buffer.read(n, -1); + real b = dsp.buffer.read(n, -0); + + real mu = fraction; + + channel[n] = mu < 0.5 ? a : b; + } + + dsp.write(channel); + fraction += step; + } + + dsp.buffer.rdoffset++; + fraction -= 1.0; +} + +#endif diff --git a/snesfilter/nall/dsp/resample/sinc.hpp b/snesfilter/nall/dsp/resample/sinc.hpp new file mode 100755 index 00000000..a77a1eeb --- /dev/null +++ b/snesfilter/nall/dsp/resample/sinc.hpp @@ -0,0 +1,54 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +#include "lib/sinc.hpp" + +struct ResampleSinc : Resampler { + inline void setFrequency(); + inline void clear(); + inline void sample(); + inline ResampleSinc(DSP &dsp); + +private: + inline void remakeSinc(); + SincResample *sinc_resampler[8]; +}; + +void ResampleSinc::setFrequency() { + remakeSinc(); +} + +void ResampleSinc::clear() { + remakeSinc(); +} + +void ResampleSinc::sample() { + for(unsigned c = 0; c < dsp.settings.channels; c++) { + sinc_resampler[c]->write(dsp.buffer.read(c)); + } + + if(sinc_resampler[0]->output_avail()) { + do { + for(unsigned c = 0; c < dsp.settings.channels; c++) { + dsp.output.write(c) = sinc_resampler[c]->read(); + } + dsp.output.wroffset++; + } while(sinc_resampler[0]->output_avail()); + } + + dsp.buffer.rdoffset++; +} + +ResampleSinc::ResampleSinc(DSP &dsp) : Resampler(dsp) { + for(unsigned n = 0; n < 8; n++) sinc_resampler[n] = 0; +} + +void ResampleSinc::remakeSinc() { + assert(dsp.settings.channels < 8); + + for(unsigned c = 0; c < dsp.settings.channels; c++) { + if(sinc_resampler[c]) delete sinc_resampler[c]; + sinc_resampler[c] = new SincResample(dsp.settings.frequency, frequency, 0.85, SincResample::QUALITY_HIGH); + } +} + +#endif diff --git a/snesfilter/nall/dsp/settings.hpp b/snesfilter/nall/dsp/settings.hpp index dc422e39..3a8f24c6 100755 --- a/snesfilter/nall/dsp/settings.hpp +++ b/snesfilter/nall/dsp/settings.hpp @@ -10,30 +10,41 @@ void DSP::setChannels(unsigned channels) { void DSP::setPrecision(unsigned precision) { settings.precision = precision; settings.intensity = 1 << (settings.precision - 1); + settings.intensityInverse = 1.0 / settings.intensity; } -void DSP::setFrequency(double frequency) { +void DSP::setFrequency(real frequency) { settings.frequency = frequency; - resampler.fraction = 0; - resampler.step = settings.frequency / resampler.frequency; + resampler->setFrequency(); } -void DSP::setVolume(double volume) { +void DSP::setVolume(real volume) { settings.volume = volume; } -void DSP::setBalance(double balance) { +void DSP::setBalance(real balance) { settings.balance = balance; } -void DSP::setResampler(Resampler engine) { - resampler.engine = engine; +void DSP::setResampler(ResampleEngine engine) { + if(resampler) delete resampler; + + switch(engine) { + case ResampleEngine::Nearest: resampler = new ResampleNearest(*this); return; + case ResampleEngine::Linear: resampler = new ResampleLinear (*this); return; + case ResampleEngine::Cosine: resampler = new ResampleCosine (*this); return; + case ResampleEngine::Cubic: resampler = new ResampleCubic (*this); return; + case ResampleEngine::Hermite: resampler = new ResampleHermite(*this); return; + case ResampleEngine::Average: resampler = new ResampleAverage(*this); return; + case ResampleEngine::Sinc: resampler = new ResampleSinc (*this); return; + } + + throw; } -void DSP::setResamplerFrequency(double frequency) { - resampler.frequency = frequency; - resampler.fraction = 0; - resampler.step = settings.frequency / resampler.frequency; +void DSP::setResamplerFrequency(real frequency) { + resampler->frequency = frequency; + resampler->setFrequency(); } #endif diff --git a/snesfilter/nall/endian.hpp b/snesfilter/nall/endian.hpp index 40d15633..1f834b5b 100755 --- a/snesfilter/nall/endian.hpp +++ b/snesfilter/nall/endian.hpp @@ -1,7 +1,9 @@ #ifndef NALL_ENDIAN_HPP #define NALL_ENDIAN_HPP -#if !defined(ARCH_MSB) +#include + +#if defined(ENDIAN_LSB) //little-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x04030201 #define order_lsb2(a,b) a,b #define order_lsb3(a,b,c) a,b,c @@ -17,7 +19,7 @@ #define order_msb6(a,b,c,d,e,f) f,e,d,c,b,a #define order_msb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a #define order_msb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a -#else +#elif defined(ENDIAN_MSB) //big-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x01020304 #define order_lsb2(a,b) b,a #define order_lsb3(a,b,c) c,b,a @@ -33,6 +35,8 @@ #define order_msb6(a,b,c,d,e,f) a,b,c,d,e,f #define order_msb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g #define order_msb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h +#else + #error "Unknown endian. Please specify in nall/intrinsics.hpp" #endif #endif diff --git a/snesfilter/nall/function.hpp b/snesfilter/nall/function.hpp index 35b76881..ca574b8c 100755 --- a/snesfilter/nall/function.hpp +++ b/snesfilter/nall/function.hpp @@ -36,19 +36,19 @@ namespace nall { public: operator bool() const { return callback; } R operator()(P... p) const { return (*callback)(std::forward

(p)...); } - void reset() { if(callback) { delete callback; callback = 0; } } + void reset() { if(callback) { delete callback; callback = nullptr; } } function& operator=(const function &source) { if(this != &source) { - if(callback) { delete callback; callback = 0; } + if(callback) { delete callback; callback = nullptr; } if(source.callback) callback = source.callback->copy(); } return *this; } - function(const function &source) : callback(0) { operator=(source); } - function() : callback(0) {} - function(void *function) : callback(0) { if(function) callback = new global((R (*)(P...))function); } + function(const function &source) : callback(nullptr) { operator=(source); } + function() : callback(nullptr) {} + function(void *function) : callback(nullptr) { if(function) callback = new global((R (*)(P...))function); } function(R (*function)(P...)) { callback = new global(function); } template function(R (C::*function)(P...), C *object) { callback = new member(function, object); } template function(R (C::*function)(P...) const, C *object) { callback = new member((R (C::*)(P...))function, object); } diff --git a/snesfilter/nall/gameboy/cartridge.hpp b/snesfilter/nall/gameboy/cartridge.hpp index af04e0bb..19ff065d 100755 --- a/snesfilter/nall/gameboy/cartridge.hpp +++ b/snesfilter/nall/gameboy/cartridge.hpp @@ -5,7 +5,7 @@ namespace nall { class GameBoyCartridge { public: - string xml; + string markup; inline GameBoyCartridge(uint8_t *data, unsigned size); //private: @@ -22,7 +22,7 @@ public: }; GameBoyCartridge::GameBoyCartridge(uint8_t *romdata, unsigned romsize) { - xml = "\n"; + markup = ""; if(romsize < 0x4000) return; info.mapper = "unknown"; @@ -100,18 +100,15 @@ GameBoyCartridge::GameBoyCartridge(uint8_t *romdata, unsigned romsize) { if(info.mapper == "MBC2") info.ramsize = 512; //512 x 4-bit - xml.append("\n"); + markup.append("cartridge mapper=", info.mapper); + if(info.rtc) markup.append(" rtc"); + if(info.rumble) markup.append(" rumble"); + markup.append("\n"); - xml.append(" \n"); //TODO: trust/check info.romsize? + markup.append("\t" "rom size=", hex(romsize), "\n"); //TODO: trust/check info.romsize? if(info.ramsize > 0) - xml.append(" \n"); - - xml.append("\n"); - xml.transform("'", "\""); + markup.append("\t" "ram size=", hex(info.ramsize), info.battery ? " non-volatile\n" : "\n"); } } diff --git a/snesfilter/nall/gzip.hpp b/snesfilter/nall/gzip.hpp index 635d3277..8f5d206b 100755 --- a/snesfilter/nall/gzip.hpp +++ b/snesfilter/nall/gzip.hpp @@ -75,7 +75,7 @@ bool gzip::decompress(const uint8_t *data, unsigned size) { return inflate(this->data, this->size, data + p, size - p - 8); } -gzip::gzip() : data(0) { +gzip::gzip() : data(nullptr) { } gzip::~gzip() { diff --git a/snesfilter/nall/image.hpp b/snesfilter/nall/image.hpp new file mode 100755 index 00000000..c79655eb --- /dev/null +++ b/snesfilter/nall/image.hpp @@ -0,0 +1,433 @@ +#ifndef NALL_IMAGE_HPP +#define NALL_IMAGE_HPP + +#include +#include +#include +#include +#include + +namespace nall { + +struct image { + uint8_t *data; + unsigned width; + unsigned height; + unsigned pitch; + + bool endian; //0 = little, 1 = big + unsigned depth; + unsigned stride; + + struct Channel { + uint64_t mask; + unsigned depth; + unsigned shift; + } alpha, red, green, blue; + + typedef double (*interpolation)(double, double, double, double, double); + static inline unsigned bitDepth(uint64_t color); + static inline unsigned bitShift(uint64_t color); + static inline uint64_t normalize(uint64_t color, unsigned sourceDepth, unsigned targetDepth); + + inline image& operator=(const image &source); + inline image& operator=(image &&source); + inline image(const image &source); + inline image(image &&source); + inline image(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask); + inline ~image(); + + inline uint64_t read(const uint8_t *data) const; + inline void write(uint8_t *data, uint64_t value) const; + + inline void free(); + inline void allocate(unsigned width, unsigned height); + inline bool load(const string &filename); + inline void scale(unsigned width, unsigned height, interpolation op); + inline void transform(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask); + inline void alphaBlend(uint64_t alphaColor); + +protected: + inline uint64_t interpolate(double mu, const uint64_t *s, interpolation op); + inline void scaleX(unsigned width, interpolation op); + inline void scaleY(unsigned height, interpolation op); + inline bool loadBMP(const string &filename); + inline bool loadPNG(const string &filename); +}; + +//static + +unsigned image::bitDepth(uint64_t color) { + unsigned depth = 0; + if(color) while((color & 1) == 0) color >>= 1; + while((color & 1) == 1) { color >>= 1; depth++; } + return depth; +} + +unsigned image::bitShift(uint64_t color) { + unsigned shift = 0; + if(color) while((color & 1) == 0) { color >>= 1; shift++; } + return shift; +} + +uint64_t image::normalize(uint64_t color, unsigned sourceDepth, unsigned targetDepth) { + while(sourceDepth < targetDepth) { + color = (color << sourceDepth) | color; + sourceDepth += sourceDepth; + } + if(targetDepth < sourceDepth) color >>= (sourceDepth - targetDepth); + return color; +} + +//public + +image& image::operator=(const image &source) { + free(); + + width = source.width; + height = source.height; + pitch = source.pitch; + + endian = source.endian; + stride = source.stride; + + alpha = source.alpha; + red = source.red; + green = source.green; + blue = source.blue; + + data = new uint8_t[width * height * stride]; + memcpy(data, source.data, width * height * stride); + return *this; +} + +image& image::operator=(image &&source) { + width = source.width; + height = source.height; + pitch = source.pitch; + + endian = source.endian; + stride = source.stride; + + alpha = source.alpha; + red = source.red; + green = source.green; + blue = source.blue; + + data = source.data; + source.data = nullptr; + return *this; +} + +image::image(const image &source) : data(nullptr) { + operator=(source); +} + +image::image(image &&source) : data(nullptr) { + operator=(std::forward(source)); +} + +image::image(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask) : data(nullptr) { + width = 0, height = 0, pitch = 0; + + this->endian = endian; + this->depth = depth; + this->stride = (depth / 8) + ((depth & 7) > 0); + + alpha.mask = alphaMask, red.mask = redMask, green.mask = greenMask, blue.mask = blueMask; + alpha.depth = bitDepth(alpha.mask), alpha.shift = bitShift(alpha.mask); + red.depth = bitDepth(red.mask), red.shift = bitShift(red.mask); + green.depth = bitDepth(green.mask), green.shift = bitShift(green.mask); + blue.depth = bitDepth(blue.mask), blue.shift = bitShift(blue.mask); +} + +image::~image() { + free(); +} + +uint64_t image::read(const uint8_t *data) const { + uint64_t result = 0; + if(endian == 0) { + for(signed n = stride - 1; n >= 0; n--) result = (result << 8) | data[n]; + } else { + for(signed n = 0; n < stride; n++) result = (result << 8) | data[n]; + } + return result; +} + +void image::write(uint8_t *data, uint64_t value) const { + if(endian == 0) { + for(signed n = 0; n < stride; n++) { data[n] = value; value >>= 8; } + } else { + for(signed n = stride - 1; n >= 0; n--) { data[n] = value; value >>= 8; } + } +} + +void image::free() { + if(data) delete[] data; + data = nullptr; +} + +void image::allocate(unsigned width, unsigned height) { + free(); + data = new uint8_t[width * height * stride](); + pitch = width * stride; + this->width = width; + this->height = height; +} + +bool image::load(const string &filename) { + if(loadBMP(filename) == true) return true; + if(loadPNG(filename) == true) return true; + return false; +} + +void image::scale(unsigned outputWidth, unsigned outputHeight, interpolation op) { + scaleX(outputWidth, op); + scaleY(outputHeight, op); +} + +void image::transform(bool outputEndian, unsigned outputDepth, uint64_t outputAlphaMask, uint64_t outputRedMask, uint64_t outputGreenMask, uint64_t outputBlueMask) { + image output(outputEndian, outputDepth, outputAlphaMask, outputRedMask, outputGreenMask, outputBlueMask); + output.allocate(width, height); + + #pragma omp parallel for + for(unsigned y = 0; y < height; y++) { + uint8_t *dp = output.data + output.pitch * y; + uint8_t *sp = data + pitch * y; + for(unsigned x = 0; x < width; x++) { + uint64_t color = read(sp); + sp += stride; + + uint64_t a = (color & alpha.mask) >> alpha.shift; + uint64_t r = (color & red.mask) >> red.shift; + uint64_t g = (color & green.mask) >> green.shift; + uint64_t b = (color & blue.mask) >> blue.shift; + + a = normalize(a, alpha.depth, output.alpha.depth); + r = normalize(r, red.depth, output.red.depth); + g = normalize(g, green.depth, output.green.depth); + b = normalize(b, blue.depth, output.blue.depth); + + output.write(dp, (a << output.alpha.shift) + (r << output.red.shift) + (g << output.green.shift) + (b << output.blue.shift)); + dp += output.stride; + } + } + + operator=(std::move(output)); +} + +void image::alphaBlend(uint64_t alphaColor) { + uint64_t alphaR = (alphaColor & red.mask) >> red.shift; + uint64_t alphaG = (alphaColor & green.mask) >> green.shift; + uint64_t alphaB = (alphaColor & blue.mask) >> blue.shift; + + #pragma omp parallel for + for(unsigned y = 0; y < height; y++) { + uint8_t *dp = data + pitch * y; + for(unsigned x = 0; x < width; x++) { + uint64_t color = read(dp); + + uint64_t colorA = (color & alpha.mask) >> alpha.shift; + uint64_t colorR = (color & red.mask) >> red.shift; + uint64_t colorG = (color & green.mask) >> green.shift; + uint64_t colorB = (color & blue.mask) >> blue.shift; + double alphaScale = (double)colorA / (double)((1 << alpha.depth) - 1); + + colorA = (1 << alpha.depth) - 1; + colorR = (colorR * alphaScale) + (alphaR * (1.0 - alphaScale)); + colorG = (colorG * alphaScale) + (alphaG * (1.0 - alphaScale)); + colorB = (colorB * alphaScale) + (alphaB * (1.0 - alphaScale)); + + write(dp, (colorA << alpha.shift) + (colorR << red.shift) + (colorG << green.shift) + (colorB << blue.shift)); + dp += stride; + } + } +} + +//protected + +uint64_t image::interpolate(double mu, const uint64_t *s, double (*op)(double, double, double, double, double)) { + uint64_t aa = (s[0] & alpha.mask) >> alpha.shift, ar = (s[0] & red.mask) >> red.shift, + ag = (s[0] & green.mask) >> green.shift, ab = (s[0] & blue.mask) >> blue.shift; + uint64_t ba = (s[1] & alpha.mask) >> alpha.shift, br = (s[1] & red.mask) >> red.shift, + bg = (s[1] & green.mask) >> green.shift, bb = (s[1] & blue.mask) >> blue.shift; + uint64_t ca = (s[2] & alpha.mask) >> alpha.shift, cr = (s[2] & red.mask) >> red.shift, + cg = (s[2] & green.mask) >> green.shift, cb = (s[2] & blue.mask) >> blue.shift; + uint64_t da = (s[3] & alpha.mask) >> alpha.shift, dr = (s[3] & red.mask) >> red.shift, + dg = (s[3] & green.mask) >> green.shift, db = (s[3] & blue.mask) >> blue.shift; + + int64_t A = op(mu, aa, ba, ca, da); + int64_t R = op(mu, ar, br, cr, dr); + int64_t G = op(mu, ag, bg, cg, dg); + int64_t B = op(mu, ab, bb, cb, db); + + A = max(0, min(A, (1 << alpha.depth) - 1)); + R = max(0, min(R, (1 << red.depth) - 1)); + G = max(0, min(G, (1 << green.depth) - 1)); + B = max(0, min(B, (1 << blue.depth) - 1)); + + return (A << alpha.shift) + (R << red.shift) + (G << green.shift) + (B << blue.shift); +} + +void image::scaleX(unsigned outputWidth, interpolation op) { + uint8_t *outputData = new uint8_t[outputWidth * height * stride]; + unsigned outputPitch = outputWidth * stride; + double step = (double)width / (double)outputWidth; + + #pragma omp parallel for + for(unsigned y = 0; y < height; y++) { + uint8_t *dp = outputData + outputPitch * y; + uint8_t *sp = data + pitch * y; + + double fraction = 0.0; + uint64_t s[4] = { read(sp), read(sp), read(sp), read(sp) }; + + for(unsigned x = 0; x < width; x++) { + if(sp >= data + pitch * height) break; + s[0] = s[1]; + s[1] = s[2]; + s[2] = s[3]; + s[3] = read(sp); + + while(fraction <= 1.0) { + if(dp >= outputData + outputPitch * height) break; + write(dp, interpolate(fraction, (const uint64_t*)&s, op)); + dp += stride; + fraction += step; + } + + sp += stride; + fraction -= 1.0; + } + } + + free(); + data = outputData; + width = outputWidth; + pitch = width * stride; +} + +void image::scaleY(unsigned outputHeight, interpolation op) { + uint8_t *outputData = new uint8_t[width * outputHeight * stride]; + double step = (double)height / (double)outputHeight; + + #pragma omp parallel for + for(unsigned x = 0; x < width; x++) { + uint8_t *dp = outputData + stride * x; + uint8_t *sp = data + stride * x; + + double fraction = 0.0; + uint64_t s[4] = { read(sp), read(sp), read(sp), read(sp) }; + + for(unsigned y = 0; y < height; y++) { + if(sp >= data + pitch * height) break; + s[0] = s[1]; + s[1] = s[2]; + s[2] = s[3]; + s[3] = read(sp); + + while(fraction <= 1.0) { + if(dp >= outputData + pitch * outputHeight) break; + write(dp, interpolate(fraction, (const uint64_t*)&s, op)); + dp += pitch; + fraction += step; + } + + sp += pitch; + fraction -= 1.0; + } + } + + free(); + data = outputData; + height = outputHeight; +} + +bool image::loadBMP(const string &filename) { + uint32_t *outputData; + unsigned outputWidth, outputHeight; + if(bmp::read(filename, outputData, outputWidth, outputHeight) == false) return false; + + allocate(outputWidth, outputHeight); + const uint32_t *sp = outputData; + uint8_t *dp = data; + + for(unsigned y = 0; y < outputHeight; y++) { + for(unsigned x = 0; x < outputWidth; x++) { + uint32_t color = *sp++; + uint64_t a = normalize((uint8_t)(color >> 24), 8, alpha.depth); + uint64_t r = normalize((uint8_t)(color >> 16), 8, red.depth); + uint64_t g = normalize((uint8_t)(color >> 8), 8, green.depth); + uint64_t b = normalize((uint8_t)(color >> 0), 8, blue.depth); + write(dp, (a << alpha.shift) + (r << red.shift) + (g << green.shift) + (b << blue.shift)); + dp += stride; + } + } + + delete[] outputData; + return true; +} + +bool image::loadPNG(const string &filename) { + png source; + if(source.decode(filename) == false) return false; + + allocate(source.info.width, source.info.height); + const uint8_t *sp = source.data; + uint8_t *dp = data; + + auto decode = [&]() -> uint64_t { + uint64_t p, r, g, b, a; + + switch(source.info.colorType) { + case 0: //L + r = g = b = source.readbits(sp); + a = (1 << source.info.bitDepth) - 1; + break; + case 2: //R,G,B + r = source.readbits(sp); + g = source.readbits(sp); + b = source.readbits(sp); + a = (1 << source.info.bitDepth) - 1; + break; + case 3: //P + p = source.readbits(sp); + r = source.info.palette[p][0]; + g = source.info.palette[p][1]; + b = source.info.palette[p][2]; + a = (1 << source.info.bitDepth) - 1; + break; + case 4: //L,A + r = g = b = source.readbits(sp); + a = source.readbits(sp); + break; + case 6: //R,G,B,A + r = source.readbits(sp); + g = source.readbits(sp); + b = source.readbits(sp); + a = source.readbits(sp); + break; + } + + a = normalize(a, source.info.bitDepth, alpha.depth); + r = normalize(r, source.info.bitDepth, red.depth); + g = normalize(g, source.info.bitDepth, green.depth); + b = normalize(b, source.info.bitDepth, blue.depth); + + return (a << alpha.shift) + (r << red.shift) + (g << green.shift) + (b << blue.shift); + }; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width; x++) { + write(dp, decode()); + dp += stride; + } + } + + return true; +} + +} + +#endif diff --git a/snesfilter/nall/inflate.hpp b/snesfilter/nall/inflate.hpp index c989e3f1..cbbf6d29 100755 --- a/snesfilter/nall/inflate.hpp +++ b/snesfilter/nall/inflate.hpp @@ -199,17 +199,17 @@ inline int codes(state *s, huffman *lencode, huffman *distcode) { symbol = decode(s, distcode); if(symbol < 0) return symbol; dist = dists[symbol] + bits(s, dext[symbol]); -#ifndef INFLATE_ALLOW_INVALID_DISTANCE_TOO_FAR + #ifndef INFLATE_ALLOW_INVALID_DISTANCE_TOO_FAR if(dist > s->outcnt) return -11; -#endif + #endif if(s->out != 0) { if(s->outcnt + len > s->outlen) return 1; while(len--) { s->out[s->outcnt] = -#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOO_FAR + #ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOO_FAR dist > s->outcnt ? 0 : -#endif + #endif s->out[s->outcnt - dist]; s->outcnt++; } diff --git a/snesfilter/nall/interpolation.hpp b/snesfilter/nall/interpolation.hpp new file mode 100755 index 00000000..46a09a49 --- /dev/null +++ b/snesfilter/nall/interpolation.hpp @@ -0,0 +1,59 @@ +#ifndef NALL_INTERPOLATION_HPP +#define NALL_INTERPOLATION_HPP + +namespace nall { + +struct Interpolation { + static inline double Nearest(double mu, double a, double b, double c, double d) { + return (mu < 0.5 ? c : d); + } + + static inline double Sublinear(double mu, double a, double b, double c, double d) { + mu = ((mu - 0.5) * 2.0) + 0.5; + if(mu < 0) mu = 0; + if(mu > 1) mu = 1; + return c * (1.0 - mu) + d * mu; + } + + static inline double Linear(double mu, double a, double b, double c, double d) { + return c * (1.0 - mu) + d * mu; + } + + static inline double Cosine(double mu, double a, double b, double c, double d) { + mu = (1.0 - cos(mu * 3.14159265)) / 2.0; + return c * (1.0 - mu) + d * mu; + } + + static inline double Cubic(double mu, double a, double b, double c, double d) { + double A = d - c - a + b; + double B = a - b - A; + double C = c - a; + double D = b; + return A * (mu * mu * mu) + B * (mu * mu) + C * mu + D; + } + + static inline double Hermite(double mu1, double a, double b, double c, double d) { + const double tension = 0.0; //-1 = low, 0 = normal, +1 = high + const double bias = 0.0; //-1 = left, 0 = even, +1 = right + double mu2, mu3, m0, m1, a0, a1, a2, a3; + + mu2 = mu1 * mu1; + mu3 = mu2 * mu1; + + m0 = (b - a) * (1.0 + bias) * (1.0 - tension) / 2.0; + m0 += (c - b) * (1.0 - bias) * (1.0 - tension) / 2.0; + m1 = (c - b) * (1.0 + bias) * (1.0 - tension) / 2.0; + m1 += (d - c) * (1.0 - bias) * (1.0 - tension) / 2.0; + + a0 = +2 * mu3 - 3 * mu2 + 1; + a1 = mu3 - 2 * mu2 + mu1; + a2 = mu3 - mu2; + a3 = -2 * mu3 + 3 * mu2; + + return (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c); + } +}; + +} + +#endif diff --git a/snesfilter/nall/intrinsics.hpp b/snesfilter/nall/intrinsics.hpp new file mode 100755 index 00000000..413ef593 --- /dev/null +++ b/snesfilter/nall/intrinsics.hpp @@ -0,0 +1,63 @@ +#ifndef NALL_INTRINSICS_HPP +#define NALL_INTRINSICS_HPP + +struct Intrinsics { + enum class Compiler : unsigned { GCC, VisualC, Unknown }; + enum class Platform : unsigned { X, OSX, Windows, Unknown }; + enum class Endian : unsigned { LSB, MSB, Unknown }; + + static inline Compiler compiler(); + static inline Platform platform(); + static inline Endian endian(); +}; + +/* Compiler detection */ + +#if defined(__GNUC__) + #define COMPILER_GCC + Intrinsics::Compiler Intrinsics::compiler() { return Intrinsics::Compiler::GCC; } +#elif defined(_MSC_VER) + #define COMPILER_VISUALC + Intrinsics::Compiler Intrinsics::compiler() { return Intrinsics::Compiler::VisualC; } +#else + #warning "unable to detect compiler" + #define COMPILER_UNKNOWN + Intrinsics::Compiler Intrinsics::compiler() { return Intrinsics::Compiler::Unknown; } +#endif + +/* Platform detection */ + +#if defined(linux) || defined(__sun__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__) + #define PLATFORM_X + Intrinsics::Platform Intrinsics::platform() { return Intrinsics::Platform::X; } +#elif defined(__APPLE__) + #define PLATFORM_OSX + Intrinsics::Platform Intrinsics::platform() { return Intrinsics::Platform::OSX; } +#elif defined(_WIN32) + #define PLATFORM_WINDOWS + #define PLATFORM_WIN + Intrinsics::Platform Intrinsics::platform() { return Intrinsics::Platform::Windows; } +#else + #warning "unable to detect platform" + #define PLATFORM_UNKNOWN + Intrinsics::Platform Intrinsics::platform() { return Intrinsics::Platform::Unknown; } +#endif + +/* Endian detection */ + +#if defined(__i386__) || defined(__amd64__) || defined(_M_IX86) || defined(_M_AMD64) + #define ENDIAN_LSB + #define ARCH_LSB + Intrinsics::Endian Intrinsics::endian() { return Intrinsics::Endian::LSB; } +#elif defined(__powerpc__) || defined(_M_PPC) || defined(__BIG_ENDIAN__) + #define ENDIAN_MSB + #define ARCH_MSB + Intrinsics::Endian Intrinsics::endian() { return Intrinsics::Endian::MSB; } +#else + #warning "unable to detect endian" + #define ENDIAN_UNKNOWN + #define ARCH_UNKNOWN + Intrinsics::Endian Intrinsics::endian() { return Intrinsics::Endian::Unknown; } +#endif + +#endif diff --git a/snesfilter/nall/ips.hpp b/snesfilter/nall/ips.hpp index 87c7de25..3071d038 100755 --- a/snesfilter/nall/ips.hpp +++ b/snesfilter/nall/ips.hpp @@ -76,7 +76,7 @@ bool ips::apply() { } delete[] data; - data = 0; + data = nullptr; return false; } @@ -96,7 +96,7 @@ bool ips::modify(const string &filename) { return file::read(filename, modifyData, modifySize); } -ips::ips() : data(0), sourceData(0), modifyData(0) { +ips::ips() : data(nullptr), sourceData(nullptr), modifyData(nullptr) { } ips::~ips() { diff --git a/snesfilter/nall/lzss.hpp b/snesfilter/nall/lzss.hpp index 147e1e62..fb3e0ba6 100755 --- a/snesfilter/nall/lzss.hpp +++ b/snesfilter/nall/lzss.hpp @@ -25,7 +25,7 @@ protected: struct Node { unsigned offset; Node *next; - inline Node() : offset(0), next(0) {} + inline Node() : offset(0), next(nullptr) {} inline ~Node() { if(next) delete next; } } *tree[65536]; @@ -34,7 +34,7 @@ protected: unsigned sourceSize; public: - inline lzss() : sourceData(0), sourceSize(0) {} + inline lzss() : sourceData(nullptr), sourceSize(0) {} }; void lzss::source(const uint8_t *data, unsigned size) { diff --git a/snesfilter/nall/platform.hpp b/snesfilter/nall/platform.hpp index 539b2345..f3e4b3f5 100755 --- a/snesfilter/nall/platform.hpp +++ b/snesfilter/nall/platform.hpp @@ -104,6 +104,8 @@ SHGetFolderPathW(0, CSIDL_APPDATA | CSIDL_FLAG_CREATE, 0, 0, fp); strcpy(path, nall::utf8_t(fp)); for(unsigned n = 0; path[n]; n++) if(path[n] == '\\') path[n] = '/'; + unsigned length = strlen(path); + if(path[length] != '/') strcpy(path + length, "/"); return path; } @@ -112,6 +114,8 @@ _wgetcwd(fp, _MAX_PATH); strcpy(path, nall::utf8_t(fp)); for(unsigned n = 0; path[n]; n++) if(path[n] == '\\') path[n] = '/'; + unsigned length = strlen(path); + if(path[length] != '/') strcpy(path + length, "/"); return path; } #else diff --git a/snesfilter/nall/png.hpp b/snesfilter/nall/png.hpp index 025044b2..4b474724 100755 --- a/snesfilter/nall/png.hpp +++ b/snesfilter/nall/png.hpp @@ -10,9 +10,12 @@ namespace nall { struct png { - uint32_t *data; - unsigned size; - + //colorType: + //0 = L + //2 = R,G,B + //3 = P + //4 = L,A + //6 = R,G,B,A struct Info { unsigned width; unsigned height; @@ -28,13 +31,14 @@ struct png { uint8_t palette[256][3]; } info; - uint8_t *rawData; - unsigned rawSize; + uint8_t *data; + unsigned size; inline bool decode(const string &filename); inline bool decode(const uint8_t *sourceData, unsigned sourceSize); - inline void transform(); - inline void alphaTransform(uint32_t rgb = 0xffffff); + inline unsigned readbits(const uint8_t *&data); + unsigned bitpos; + inline png(); inline ~png(); @@ -46,16 +50,11 @@ protected: IEND = 0x49454e44, }; - unsigned bitpos; - inline unsigned interlace(unsigned pass, unsigned index); inline unsigned inflateSize(); inline bool deinterlace(const uint8_t *&inputData, unsigned pass); inline bool filter(uint8_t *outputData, const uint8_t *inputData, unsigned width, unsigned height); inline unsigned read(const uint8_t *data, unsigned length); - inline unsigned decode(const uint8_t *&data); - inline unsigned readbits(const uint8_t *&data); - inline unsigned scale(unsigned n); }; bool png::decode(const string &filename) { @@ -146,14 +145,14 @@ bool png::decode(const uint8_t *sourceData, unsigned sourceSize) { return false; } - rawSize = info.width * info.height * info.bytesPerPixel; - rawData = new uint8_t[rawSize]; + size = info.width * info.height * info.bytesPerPixel; + data = new uint8_t[size]; if(info.interlaceMethod == 0) { - if(filter(rawData, interlacedData, info.width, info.height) == false) { + if(filter(data, interlacedData, info.width, info.height) == false) { delete[] interlacedData; - delete[] rawData; - rawData = 0; + delete[] data; + data = 0; return false; } } else { @@ -161,8 +160,8 @@ bool png::decode(const uint8_t *sourceData, unsigned sourceSize) { for(unsigned pass = 0; pass < 7; pass++) { if(deinterlace(passData, pass) == false) { delete[] interlacedData; - delete[] rawData; - rawData = 0; + delete[] data; + data = 0; return false; } } @@ -216,7 +215,7 @@ bool png::deinterlace(const uint8_t *&inputData, unsigned pass) { const uint8_t *rd = outputData; for(unsigned y = yo; y < info.height; y += yd) { - uint8_t *wr = rawData + y * info.pitch; + uint8_t *wr = data + y * info.pitch; for(unsigned x = xo; x < info.width; x += xd) { for(unsigned b = 0; b < info.bytesPerPixel; b++) { wr[x * info.bytesPerPixel + b] = *rd++; @@ -298,42 +297,6 @@ unsigned png::read(const uint8_t *data, unsigned length) { return result; } -unsigned png::decode(const uint8_t *&data) { - unsigned p, r, g, b, a; - - switch(info.colorType) { - case 0: //L - r = g = b = scale(readbits(data)); - a = 0xff; - break; - case 2: //R,G,B - r = scale(readbits(data)); - g = scale(readbits(data)); - b = scale(readbits(data)); - a = 0xff; - break; - case 3: //P - p = readbits(data); - r = info.palette[p][0]; - g = info.palette[p][1]; - b = info.palette[p][2]; - a = 0xff; - break; - case 4: //L,A - r = g = b = scale(readbits(data)); - a = scale(readbits(data)); - break; - case 6: //R,G,B,A - r = scale(readbits(data)); - g = scale(readbits(data)); - b = scale(readbits(data)); - a = scale(readbits(data)); - break; - } - - return (a << 24) | (r << 16) | (g << 8) | (b << 0); -} - unsigned png::readbits(const uint8_t *&data) { unsigned result = 0; switch(info.bitDepth) { @@ -363,62 +326,12 @@ unsigned png::readbits(const uint8_t *&data) { return result; } -unsigned png::scale(unsigned n) { - switch(info.bitDepth) { - case 1: return n ? 0xff : 0x00; - case 2: return n * 0x55; - case 4: return n * 0x11; - case 8: return n; - case 16: return n >> 8; - } - return 0; -} - -void png::transform() { - if(data) delete[] data; - data = new uint32_t[info.width * info.height]; - +png::png() : data(nullptr) { bitpos = 0; - const uint8_t *rd = rawData; - for(unsigned y = 0; y < info.height; y++) { - uint32_t *wr = data + y * info.width; - for(unsigned x = 0; x < info.width; x++) { - wr[x] = decode(rd); - } - } -} - -void png::alphaTransform(uint32_t rgb) { - transform(); - - uint8_t ir = rgb >> 16; - uint8_t ig = rgb >> 8; - uint8_t ib = rgb >> 0; - - uint32_t *p = data; - for(unsigned y = 0; y < info.height; y++) { - for(unsigned x = 0; x < info.width; x++) { - uint32_t pixel = *p; - uint8_t a = pixel >> 24; - uint8_t r = pixel >> 16; - uint8_t g = pixel >> 8; - uint8_t b = pixel >> 0; - - r = (r * a) + (ir * (255 - a)) >> 8; - g = (g * a) + (ig * (255 - a)) >> 8; - b = (b * a) + (ib * (255 - a)) >> 8; - - *p++ = (255 << 24) | (r << 16) | (g << 8) | (b << 0); - } - } -} - -png::png() : data(0), rawData(0) { } png::~png() { if(data) delete[] data; - if(rawData) delete[] rawData; } } diff --git a/snesfilter/nall/priorityqueue.hpp b/snesfilter/nall/priorityqueue.hpp index 7104e791..443eac21 100755 --- a/snesfilter/nall/priorityqueue.hpp +++ b/snesfilter/nall/priorityqueue.hpp @@ -12,7 +12,7 @@ namespace nall { //priority queue implementation using binary min-heap array; //does not require normalize() function. //O(1) find (tick) - //O(log n) insert (enqueue) + //O(log n) append (enqueue) //O(log n) remove (dequeue) template class priority_queue { public: diff --git a/snesfilter/nall/property.hpp b/snesfilter/nall/property.hpp index 6fd33acd..665afcad 100755 --- a/snesfilter/nall/property.hpp +++ b/snesfilter/nall/property.hpp @@ -22,7 +22,7 @@ // readwrite y; //}; -//return types are const T& (byref) instead fo T (byval) to avoid major speed +//return types are const T& (byref) instead of T (byval) to avoid major speed //penalties for objects with expensive copy constructors //operator-> provides access to underlying object type: diff --git a/snesfilter/nall/reference_array.hpp b/snesfilter/nall/reference_array.hpp index 77d06d86..7c915090 100755 --- a/snesfilter/nall/reference_array.hpp +++ b/snesfilter/nall/reference_array.hpp @@ -1,15 +1,17 @@ #ifndef NALL_REFERENCE_ARRAY_HPP #define NALL_REFERENCE_ARRAY_HPP +#include #include #include -#include namespace nall { template struct reference_array { + struct exception_out_of_bounds{}; + protected: - typedef typename std::remove_reference::type *Tptr; - Tptr *pool; + typedef typename std::remove_reference::type type_t; + type_t **pool; unsigned poolsize, buffersize; public: @@ -18,7 +20,7 @@ namespace nall { void reset() { if(pool) free(pool); - pool = 0; + pool = nullptr; poolsize = 0; buffersize = 0; } @@ -26,7 +28,7 @@ namespace nall { void reserve(unsigned newsize) { if(newsize == poolsize) return; - pool = (Tptr*)realloc(pool, newsize * sizeof(T)); + pool = (type_t**)realloc(pool, sizeof(type_t*) * newsize); poolsize = newsize; buffersize = min(buffersize, newsize); } @@ -36,7 +38,14 @@ namespace nall { buffersize = newsize; } - bool append(const T data) { + template + bool append(type_t& data, Args&&... args) { + bool result = append(data); + append(std::forward(args)...); + return result; + } + + bool append(type_t& data) { for(unsigned index = 0; index < buffersize; index++) { if(pool[index] == &data) return false; } @@ -47,7 +56,7 @@ namespace nall { return true; } - bool remove(const T data) { + bool remove(type_t& data) { for(unsigned index = 0; index < buffersize; index++) { if(pool[index] == &data) { for(unsigned i = index; i < buffersize - 1; i++) pool[i] = pool[i + 1]; @@ -58,7 +67,7 @@ namespace nall { return false; } - template reference_array(Args&... args) : pool(0), poolsize(0), buffersize(0) { + template reference_array(Args&... args) : pool(nullptr), poolsize(0), buffersize(0) { construct(args...); } @@ -70,8 +79,8 @@ namespace nall { if(pool) free(pool); buffersize = source.buffersize; poolsize = source.poolsize; - pool = (Tptr*)malloc(sizeof(T) * poolsize); - memcpy(pool, source.pool, sizeof(T) * buffersize); + pool = (type_t**)malloc(sizeof(type_t*) * poolsize); + memcpy(pool, source.pool, sizeof(type_t*) * buffersize); return *this; } @@ -80,21 +89,37 @@ namespace nall { pool = source.pool; poolsize = source.poolsize; buffersize = source.buffersize; - source.pool = 0; + source.pool = nullptr; source.reset(); return *this; } - inline T operator[](unsigned index) { - if(index >= buffersize) throw "reference_array[] out of bounds"; + inline type_t& operator[](unsigned index) { + if(index >= buffersize) throw exception_out_of_bounds(); return *pool[index]; } - inline const T operator[](unsigned index) const { - if(index >= buffersize) throw "reference_array[] out of bounds"; + inline type_t& operator[](unsigned index) const { + if(index >= buffersize) throw exception_out_of_bounds(); return *pool[index]; } + //iteration + struct iterator { + bool operator!=(const iterator &source) const { return index != source.index; } + type_t& operator*() { return array.operator[](index); } + iterator& operator++() { index++; return *this; } + iterator(const reference_array &array, unsigned index) : array(array), index(index) {} + private: + const reference_array &array; + unsigned index; + }; + + iterator begin() { return iterator(*this, 0); } + iterator end() { return iterator(*this, buffersize); } + const iterator begin() const { return iterator(*this, 0); } + const iterator end() const { return iterator(*this, buffersize); } + private: void construct() { } @@ -112,8 +137,6 @@ namespace nall { construct(args...); } }; - - template struct has_size> { enum { value = true }; }; } #endif diff --git a/snesfilter/nall/snes/cartridge.hpp b/snesfilter/nall/snes/cartridge.hpp index 6847ba3a..cac3fb1a 100755 --- a/snesfilter/nall/snes/cartridge.hpp +++ b/snesfilter/nall/snes/cartridge.hpp @@ -3,10 +3,10 @@ namespace nall { -class SNESCartridge { +class SnesCartridge { public: - string xmlMemoryMap; - inline SNESCartridge(const uint8_t *data, unsigned size); + string markup; + inline SnesCartridge(const uint8_t *data, unsigned size); //private: inline void read_header(const uint8_t *data, unsigned size); @@ -105,436 +105,346 @@ public: bool has_st018; }; -SNESCartridge::SNESCartridge(const uint8_t *data, unsigned size) { +#define T "\t" + +SnesCartridge::SnesCartridge(const uint8_t *data, unsigned size) { read_header(data, size); - string xml = "\n"; + string xml; + markup = ""; if(type == TypeBsx) { - xml.append(""); - xmlMemoryMap = xml.transform("'", "\""); + markup.append("cartridge"); return; } if(type == TypeSufamiTurbo) { - xml.append(""); - xmlMemoryMap = xml.transform("'", "\""); + markup.append("cartridge"); return; } if(type == TypeGameBoy) { - xml.append("\n"); + markup.append("cartridge rtc=", gameboy_has_rtc(data, size), "\n"); if(gameboy_ram_size(data, size) > 0) { - xml.append(" \n"); + markup.append(T "ram size=0x", hex(gameboy_ram_size(data, size)), "\n"); } - xml.append("\n"); - xmlMemoryMap = xml.transform("'", "\""); return; } - xml.append("\n"); + markup.append("cartridge region=", region == NTSC ? "NTSC\n" : "PAL\n"); if(type == TypeSuperGameBoy1Bios) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "rom\n"); + markup.append(T T "map mode=linear address=00-7f:8000-ffff\n"); + markup.append(T T "map mode=linear address=80-ff:8000-ffff\n"); + markup.append(T "icd2 revision=1\n"); + markup.append(T T "map address=00-3f:6000-7fff\n"); + markup.append(T T "map address=80-bf:6000-7fff\n"); } else if(type == TypeSuperGameBoy2Bios) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "rom\n"); + markup.append(T T "map mode=linear address=00-7f:8000-ffff\n"); + markup.append(T T "map mode=linear address=80-ff:8000-ffff\n"); + markup.append(T "icd2 revision=1\n"); + markup.append(T T "map address=00-3f:6000-7fff\n"); + markup.append(T T "map address=80-bf:6000-7fff\n"); } else if(has_cx4) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "hitachidsp model=HG51B169 frequency=20000000 firmware=cx4.bin sha256=ae8d4d1961b93421ff00b3caa1d0f0ce7783e749772a3369c36b3dbf0d37ef18\n"); + markup.append(T T "rom\n"); + markup.append(T T T "map mode=linear address=00-7f:8000-ffff\n"); + markup.append(T T T "map mode=linear address=80-ff:8000-ffff\n"); + markup.append(T T "mmio\n"); + markup.append(T T T "map address=00-3f:6000-7fff\n"); + markup.append(T T T "map address=80-bf:6000-7fff\n"); } else if(has_spc7110) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "rom\n"); + markup.append(T T "map mode=shadow address=00-0f:8000-ffff\n"); + markup.append(T T "map mode=shadow address=80-bf:8000-ffff\n"); + markup.append(T T "map mode=linear address=c0-cf:0000-ffff\n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "spc7110\n"); + markup.append(T T "mcu\n"); + markup.append(T T T "map address=d0-ff:0000-ffff offset=0x100000 size=0x", hex(size - 0x100000), "\n"); + markup.append(T T "ram size=0x", hex(ram_size), "\n"); + markup.append(T T T "map mode=linear address=00:6000-7fff\n"); + markup.append(T T T "map mode=linear address=30:6000-7fff\n"); + markup.append(T T "mmio\n"); + markup.append(T T T "map address=00-3f:4800-483f\n"); + markup.append(T T T "map address=80-bf:4800-483f\n"); if(has_spc7110rtc) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T T "rtc\n"); + markup.append(T T T "map address=00-3f:4840-4842\n"); + markup.append(T T T "map address=80-bf:4840-4842\n"); } - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T T "dcu\n"); + markup.append(T T T "map address=50:0000-ffff\n"); } else if(mapper == LoROM) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "rom\n"); + markup.append(T T "map mode=linear address=00-7f:8000-ffff\n"); + markup.append(T T "map mode=linear address=80-ff:8000-ffff\n"); if(ram_size > 0) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "ram size=0x", hex(ram_size), "\n"); + markup.append(T T "map mode=linear address=20-3f:6000-7fff\n"); + markup.append(T T "map mode=linear address=a0-bf:6000-7fff\n"); if((rom_size > 0x200000) || (ram_size > 32 * 1024)) { - xml.append(" \n"); - xml.append(" \n"); + markup.append(T T "map mode=linear address=70-7f:0000-7fff\n"); + markup.append(T T "map mode=linear address=f0-ff:0000-7fff\n"); } else { - xml.append(" \n"); - xml.append(" \n"); + markup.append(T T "map mode=linear address=70-7f:0000-ffff\n"); + markup.append(T T "map mode=linear address=f0-ff:0000-ffff\n"); } - xml.append(" \n"); } } else if(mapper == HiROM) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "rom\n"); + markup.append(T T "map mode=shadow address=00-3f:8000-ffff\n"); + markup.append(T T "map mode=linear address=40-7f:0000-ffff\n"); + markup.append(T T "map mode=shadow address=80-bf:8000-ffff\n"); + markup.append(T T "map mode=linear address=c0-ff:0000-ffff\n"); if(ram_size > 0) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "ram size=0x", hex(ram_size), "\n"); + markup.append(T T "map mode=linear address=20-3f:6000-7fff\n"); + markup.append(T T "map mode=linear address=a0-bf:6000-7fff\n"); if((rom_size > 0x200000) || (ram_size > 32 * 1024)) { - xml.append(" \n"); + markup.append(T T "map mode=linear address=70-7f:0000-7fff\n"); } else { - xml.append(" \n"); + markup.append(T T "map mode=linear address=70-7f:0000-ffff\n"); } - xml.append(" \n"); } } else if(mapper == ExLoROM) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "rom\n"); + markup.append(T T "map mode=linear address=00-3f:8000-ffff\n"); + markup.append(T T "map mode=linear address=40-7f:0000-ffff\n"); + markup.append(T T "map mode=linear address=80-bf:8000-ffff\n"); if(ram_size > 0) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "ram size=0x", hex(ram_size), "\n"); + markup.append(T T "map mode=linear address=20-3f:6000-7fff\n"); + markup.append(T T "map mode=linear address=a0-bf:6000-7fff\n"); + markup.append(T T "map mode=linear address=70-7f:0000-7fff\n"); } } else if(mapper == ExHiROM) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "rom\n"); + markup.append(T T "map mode=shadow address=00-3f:8000-ffff offset=0x400000\n"); + markup.append(T T "map mode=linear address=40-7f:0000-ffff offset=0x400000\n"); + markup.append(T T "map mode=shadow address=80-bf:8000-ffff offset=0x000000\n"); + markup.append(T T "map mode=linear address=c0-ff:0000-ffff offset=0x000000\n"); if(ram_size > 0) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "ram size=0x", hex(ram_size), "\n"); + markup.append(T T "map mode=linear address=20-3f:6000-7fff\n"); + markup.append(T T "map mode=linear address=a0-bf:6000-7fff\n"); if((rom_size > 0x200000) || (ram_size > 32 * 1024)) { - xml.append(" \n"); + markup.append(T T "map mode=linear address=70-7f:0000-7fff\n"); } else { - xml.append(" \n"); + markup.append(T T "map mode=linear address=70-7f:0000-ffff\n"); } - xml.append(" \n"); } } else if(mapper == SuperFXROM) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "superfx revision=2\n"); + markup.append(T T "rom\n"); + markup.append(T T T "map mode=linear address=00-3f:8000-ffff\n"); + markup.append(T T T "map mode=linear address=40-5f:0000-ffff\n"); + markup.append(T T T "map mode=linear address=80-bf:8000-ffff\n"); + markup.append(T T T "map mode=linear address=c0-df:0000-ffff\n"); + markup.append(T T "ram size=0x", hex(ram_size), "\n"); + markup.append(T T T "map mode=linear address=00-3f:6000-7fff size=0x2000\n"); + markup.append(T T T "map mode=linear address=60-7f:0000-ffff\n"); + markup.append(T T T "map mode=linear address=80-bf:6000-7fff size=0x2000\n"); + markup.append(T T T "map mode=linear address=e0-ff:0000-ffff\n"); + markup.append(T T "mmio\n"); + markup.append(T T T "map address=00-3f:3000-32ff\n"); + markup.append(T T T "map address=80-bf:3000-32ff\n"); } else if(mapper == SA1ROM) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "sa1\n"); + markup.append(T T "mcu\n"); + markup.append(T T T "rom\n"); + markup.append(T T T T "map mode=direct address=00-3f:8000-ffff\n"); + markup.append(T T T T "map mode=direct address=80-bf:8000-ffff\n"); + markup.append(T T T T "map mode=direct address=c0-ff:0000-ffff\n"); + markup.append(T T T "ram\n"); + markup.append(T T T T "map mode=direct address=00-3f:6000-7fff\n"); + markup.append(T T T T "map mode=direct address=80-bf:6000-7fff\n"); + markup.append(T T "iram size=0x800\n"); + markup.append(T T T "map mode=linear address=00-3f:3000-37ff\n"); + markup.append(T T T "map mode=linear address=80-bf:3000-37ff\n"); + markup.append(T T "bwram size=0x", hex(ram_size), "\n"); + markup.append(T T T "map mode=linear address=40-4f:0000-ffff\n"); + markup.append(T T "mmio\n"); + markup.append(T T T "map address=00-3f:2200-23ff\n"); + markup.append(T T T "map address=80-bf:2200-23ff\n"); } else if(mapper == BSCLoROM) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "rom\n"); + markup.append(T T "map mode=linear address=00-1f:8000-ffff offset=0x000000\n"); + markup.append(T T "map mode=linear address=20-3f:8000-ffff offset=0x100000\n"); + markup.append(T T "map mode=linear address=80-9f:8000-ffff offset=0x200000\n"); + markup.append(T T "map mode=linear address=a0-bf:8000-ffff offset=0x100000\n"); + markup.append(T "ram size=0x", hex(ram_size), "\n"); + markup.append(T T "map mode=linear address=70-7f:0000-7fff\n"); + markup.append(T T "map mode=linear address=f0-ff:0000-7fff\n"); + markup.append(T "bsx\n"); + markup.append(T T "slot\n"); + markup.append(T T T "map mode=linear address=c0-ef:0000-ffff\n"); } else if(mapper == BSCHiROM) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "rom\n"); + markup.append(T T "map mode=shadow address=00-1f:8000-ffff\n"); + markup.append(T T "map mode=linear address=40-5f:0000-ffff\n"); + markup.append(T T "map mode=shadow address=80-9f:8000-ffff\n"); + markup.append(T T "map mode=linear address=c0-df:0000-ffff\n"); + markup.append(T "ram size=0x", hex(ram_size), "\n"); + markup.append(T T "map mode=linear address=20-3f:6000-7fff\n"); + markup.append(T T "map mode=linear address=a0-bf:6000-7fff\n"); + markup.append(T "bsx\n"); + markup.append(T T "slot\n"); + markup.append(T T T "map mode=shadow address=20-3f:8000-ffff\n"); + markup.append(T T T "map mode=linear address=60-7f:0000-ffff\n"); + markup.append(T T T "map mode=shadow address=a0-bf:8000-ffff\n"); + markup.append(T T T "map mode=linear address=e0-ff:0000-ffff\n"); } else if(mapper == BSXROM) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "bsx\n"); + markup.append(T T "mcu\n"); + markup.append(T T T "map address=00-3f:8000-ffff\n"); + markup.append(T T T "map address=80-bf:8000-ffff\n"); + markup.append(T T T "map address=40-7f:0000-ffff\n"); + markup.append(T T T "map address=c0-ff:0000-ffff\n"); + markup.append(T T T "map address=20-3f:6000-7fff\n"); + markup.append(T T "mmio\n"); + markup.append(T T T "map address=00-3f:5000-5fff\n"); + markup.append(T T T "map address=80-bf:5000-5fff\n"); } else if(mapper == STROM) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "rom\n"); + markup.append(T T "map mode=linear address=00-1f:8000-ffff\n"); + markup.append(T T "map mode=linear address=80-9f:8000-ffff\n"); + markup.append(T "sufamiturbo\n"); + markup.append(T T "slot id=A\n"); + markup.append(T T T "rom\n"); + markup.append(T T T T "map mode=linear address=20-3f:8000-ffff\n"); + markup.append(T T T T "map mode=linear address=a0-bf:8000-ffff\n"); + markup.append(T T T "ram size=0x20000\n"); + markup.append(T T T T "map mode=linear address=60-63:8000-ffff\n"); + markup.append(T T T T "map mode=linear address=e0-e3:8000-ffff\n"); + markup.append(T T "slot id=B\n"); + markup.append(T T T "rom\n"); + markup.append(T T T T "map mode=linear address=40-5f:8000-ffff\n"); + markup.append(T T T T "map mode=linear address=c0-df:8000-ffff\n"); + markup.append(T T T "ram size=0x20000\n"); + markup.append(T T T T "map mode=linear address=70-73:8000-ffff\n"); + markup.append(T T T T "map mode=linear address=f0-f3:8000-ffff\n"); } if(has_srtc) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "srtc\n"); + markup.append(T T "map address=00-3f:2800-2801\n"); + markup.append(T T "map address=80-bf:2800-2801\n"); } if(has_sdd1) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "sdd1\n"); + markup.append(T T "mcu\n"); + markup.append(T T T "map address=c0-ff:0000-ffff\n"); + markup.append(T T "mmio\n"); + markup.append(T T T "map address=00-3f:4800-4807\n"); + markup.append(T T T "map address=80-bf:4800-4807\n"); } if(has_dsp1) { - xml.append(" \n"); + markup.append(T "necdsp model=uPD7725 frequency=8000000 firmware=dsp1b.bin sha256=4d42db0f36faef263d6b93f508e8c1c4ae8fc2605fd35e3390ecc02905cd420c\n"); if(dsp1_mapper == DSP1LoROM1MB) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T T "dr\n"); + markup.append(T T T "map address=20-3f:8000-bfff\n"); + markup.append(T T T "map address=a0-bf:8000-bfff\n"); + markup.append(T T "sr\n"); + markup.append(T T T "map address=20-3f:c000-ffff\n"); + markup.append(T T T "map address=a0-bf:c000-ffff\n"); } else if(dsp1_mapper == DSP1LoROM2MB) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T T "dr\n"); + markup.append(T T T "map address=60-6f:0000-3fff\n"); + markup.append(T T T "map address=e0-ef:0000-3fff\n"); + markup.append(T T "sr\n"); + markup.append(T T T "map address=60-6f:4000-7fff\n"); + markup.append(T T T "map address=e0-ef:4000-7fff\n"); } else if(dsp1_mapper == DSP1HiROM) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T T "dr\n"); + markup.append(T T T "map address=00-1f:6000-6fff\n"); + markup.append(T T T "map address=80-9f:6000-6fff\n"); + markup.append(T T "sr\n"); + markup.append(T T T "map address=00-1f:7000-7fff\n"); + markup.append(T T T "map address=80-9f:7000-7fff\n"); } - xml.append(" \n"); } if(has_dsp2) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "necdsp model=uPD7725 frequency=8000000 firmware=dsp2.bin sha256=5efbdf96ed0652790855225964f3e90e6a4d466cfa64df25b110933c6cf94ea1\n"); + markup.append(T T "dr\n"); + markup.append(T T T "map address=20-3f:8000-bfff\n"); + markup.append(T T T "map address=a0-bf:8000-bfff\n"); + markup.append(T T "sr\n"); + markup.append(T T T "map address=20-3f:c000-ffff\n"); + markup.append(T T T "map address=a0-bf:c000-ffff\n"); } if(has_dsp3) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "necdsp model=uPD7725 frequency=8000000 firmware=dsp3.bin sha256=2e635f72e4d4681148bc35429421c9b946e4f407590e74e31b93b8987b63ba90\n"); + markup.append(T T "dr\n"); + markup.append(T T T "map address=20-3f:8000-bfff\n"); + markup.append(T T T "map address=a0-bf:8000-bfff\n"); + markup.append(T T "sr\n"); + markup.append(T T T "map address=20-3f:c000-ffff\n"); + markup.append(T T T "map address=a0-bf:c000-ffff\n"); } if(has_dsp4) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "necdsp model=uPD7725 frequency=8000000 firmware=dsp4.bin sha256=63ede17322541c191ed1fdf683872554a0a57306496afc43c59de7c01a6e764a\n"); + markup.append(T T "dr\n"); + markup.append(T T T "map address=30-3f:8000-bfff\n"); + markup.append(T T T "map address=b0-bf:8000-bfff\n"); + markup.append(T T "sr\n"); + markup.append(T T T "map address=30-3f:c000-ffff\n"); + markup.append(T T T "map address=b0-bf:c000-ffff\n"); } if(has_obc1) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "obc1\n"); + markup.append(T T "map address=00-3f:6000-7fff\n"); + markup.append(T T "map address=80-bf:6000-7fff\n"); } if(has_st010) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "necdsp model=uPD96050 frequency=10000000 firmware=st0010.bin sha256=55c697e864562445621cdf8a7bf6e84ae91361e393d382a3704e9aa55559041e\n"); + markup.append(T T "dr\n"); + markup.append(T T T "map address=60:0000\n"); + markup.append(T T T "map address=e0:0000\n"); + markup.append(T T "sr\n"); + markup.append(T T T "map address=60:0001\n"); + markup.append(T T T "map address=e0:0001\n"); + markup.append(T T "dp\n"); + markup.append(T T T "map address=68-6f:0000-0fff\n"); + markup.append(T T T "map address=e8-ef:0000-0fff\n"); } if(has_st011) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "necdsp model=uPD96050 frequency=15000000 firmware=st0011.bin sha256=651b82a1e26c4fa8dd549e91e7f923012ed2ca54c1d9fd858655ab30679c2f0e\n"); + markup.append(T T "dr\n"); + markup.append(T T T "map address=60:0000\n"); + markup.append(T T T "map address=e0:0000\n"); + markup.append(T T "sr\n"); + markup.append(T T T "map address=60:0001\n"); + markup.append(T T T "map address=e0:0001\n"); + markup.append(T T "dp\n"); + markup.append(T T T "map address=68-6f:0000-0fff\n"); + markup.append(T T T "map address=e8-ef:0000-0fff\n"); } if(has_st018) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); + markup.append(T "setarisc firmware=ST-0018\n"); + markup.append(T T "map address=00-3f:3800-38ff\n"); + markup.append(T T "map address=80-bf:3800-38ff\n"); } - - xml.append("\n"); - xmlMemoryMap = xml.transform("'", "\""); } -void SNESCartridge::read_header(const uint8_t *data, unsigned size) { +#undef T + +void SnesCartridge::read_header(const uint8_t *data, unsigned size) { type = TypeUnknown; mapper = LoROM; dsp1_mapper = DSP1Unmapped; @@ -762,7 +672,7 @@ void SNESCartridge::read_header(const uint8_t *data, unsigned size) { } } -unsigned SNESCartridge::find_header(const uint8_t *data, unsigned size) { +unsigned SnesCartridge::find_header(const uint8_t *data, unsigned size) { unsigned score_lo = score_header(data, size, 0x007fc0); unsigned score_hi = score_header(data, size, 0x00ffc0); unsigned score_ex = score_header(data, size, 0x40ffc0); @@ -777,7 +687,7 @@ unsigned SNESCartridge::find_header(const uint8_t *data, unsigned size) { } } -unsigned SNESCartridge::score_header(const uint8_t *data, unsigned size, unsigned addr) { +unsigned SnesCartridge::score_header(const uint8_t *data, unsigned size, unsigned addr) { if(size < addr + 64) return 0; //image too small to contain header at this location? int score = 0; @@ -858,7 +768,7 @@ unsigned SNESCartridge::score_header(const uint8_t *data, unsigned size, unsigne return score; } -unsigned SNESCartridge::gameboy_ram_size(const uint8_t *data, unsigned size) { +unsigned SnesCartridge::gameboy_ram_size(const uint8_t *data, unsigned size) { if(size < 512) return 0; switch(data[0x0149]) { case 0x00: return 0 * 1024; @@ -871,7 +781,7 @@ unsigned SNESCartridge::gameboy_ram_size(const uint8_t *data, unsigned size) { } } -bool SNESCartridge::gameboy_has_rtc(const uint8_t *data, unsigned size) { +bool SnesCartridge::gameboy_has_rtc(const uint8_t *data, unsigned size) { if(size < 512) return false; if(data[0x0147] == 0x0f ||data[0x0147] == 0x10) return true; return false; diff --git a/snesfilter/nall/stack.hpp b/snesfilter/nall/stack.hpp index a4aacfa7..fe8e16a1 100755 --- a/snesfilter/nall/stack.hpp +++ b/snesfilter/nall/stack.hpp @@ -1,7 +1,6 @@ #ifndef NALL_STACK_HPP #define NALL_STACK_HPP -#include #include namespace nall { @@ -22,8 +21,6 @@ namespace nall { return linear_vector::operator[](linear_vector::size() - 1); } }; - - template struct has_size> { enum { value = true }; }; } #endif diff --git a/snesfilter/nall/string.hpp b/snesfilter/nall/string.hpp index 91bee596..288424bc 100755 --- a/snesfilter/nall/string.hpp +++ b/snesfilter/nall/string.hpp @@ -1,20 +1,39 @@ #ifndef NALL_STRING_HPP #define NALL_STRING_HPP +#include +#include +#include +#include + +#include #include + #include +#include +#include #include #include +#include #include +#include +#include +#include + +#define NALL_STRING_INTERNAL_HPP #include +#include #include #include #include #include #include +#include #include #include +#include +#include #include #include #include @@ -23,12 +42,9 @@ #include #include #include +#include #include #include - -namespace nall { - template<> struct has_length { enum { value = true }; }; - template<> struct has_size { enum { value = true }; }; -} +#undef NALL_STRING_INTERNAL_HPP #endif diff --git a/snesfilter/nall/string/base.hpp b/snesfilter/nall/string/base.hpp index 86c42c20..97000bef 100755 --- a/snesfilter/nall/string/base.hpp +++ b/snesfilter/nall/string/base.hpp @@ -1,23 +1,27 @@ -#ifndef NALL_STRING_BASE_HPP -#define NALL_STRING_BASE_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include +#ifdef NALL_STRING_INTERNAL_HPP namespace nall { - class string; - class lstring; + struct cstring; + struct string; + struct lstring; template inline const char* to_string(T); - class string { - public: + struct cstring { + inline operator const char*() const; + inline unsigned length() const; + inline bool operator==(const char*) const; + inline bool operator!=(const char*) const; + inline optional position(const char *key) const; + inline optional iposition(const char *key) const; + inline cstring& operator=(const char *data); + inline cstring(const char *data); + inline cstring(); + + protected: + const char *data; + }; + + struct string { inline void reserve(unsigned); template inline string& assign(Args&&... args); @@ -32,6 +36,11 @@ namespace nall { inline unsigned length() const; + template inline lstring split(const char*) const; + template inline lstring isplit(const char*) const; + template inline lstring qsplit(const char*) const; + template inline lstring iqsplit(const char*) const; + inline bool equals(const char*) const; inline bool iequals(const char*) const; @@ -77,6 +86,11 @@ namespace nall { inline string(string&&); inline ~string(); + inline char* begin() { return &data[0]; } + inline char* end() { return &data[length()]; } + inline const char* begin() const { return &data[0]; } + inline const char* end() const { return &data[length()]; } + //internal functions inline string& assign_(const char*); inline string& append_(const char*); @@ -93,10 +107,7 @@ namespace nall { #endif }; - class lstring : public linear_vector { - public: - template inline lstring& operator<<(T value); - + struct lstring : vector { inline optional find(const char*) const; template inline lstring& split(const char*, const char*); template inline lstring& isplit(const char*, const char*); @@ -106,8 +117,8 @@ namespace nall { inline bool operator==(const lstring&) const; inline bool operator!=(const lstring&) const; - lstring(); - lstring(std::initializer_list); + inline lstring(); + inline lstring(std::initializer_list); protected: template inline lstring& usplit(const char*, const char*); @@ -117,8 +128,6 @@ namespace nall { inline char chrlower(char c); inline char chrupper(char c); inline int istrcmp(const char *str1, const char *str2); - inline bool wildcard(const char *str, const char *pattern); - inline bool iwildcard(const char *str, const char *pattern); inline bool strbegin(const char *str, const char *key); inline bool istrbegin(const char *str, const char *key); inline bool strend(const char *str, const char *key); @@ -130,11 +139,6 @@ namespace nall { inline char* qstrlower(char *str); inline char* qstrupper(char *str); inline char* strtr(char *dest, const char *before, const char *after); - inline uintmax_t hex(const char *str); - inline intmax_t integer(const char *str); - inline uintmax_t decimal(const char *str); - inline uintmax_t binary(const char *str); - inline double fp(const char *str); //math.hpp inline bool strint(const char *str, int &result); @@ -170,17 +174,24 @@ namespace nall { inline string substr(const char *src, unsigned start = 0, unsigned length = ~0u); inline string sha256(const uint8_t *data, unsigned size); + inline char* integer(char *result, intmax_t value); + inline char* decimal(char *result, uintmax_t value); + template inline string integer(intmax_t value); template inline string linteger(intmax_t value); template inline string decimal(uintmax_t value); template inline string ldecimal(uintmax_t value); template inline string hex(uintmax_t value); template inline string binary(uintmax_t value); - inline unsigned fp(char *str, double value); - inline string fp(double value); + inline unsigned fp(char *str, long double value); + inline string fp(long double value); //variadic.hpp template inline void print(Args&&... args); + + //wildcard.hpp + inline bool wildcard(const char *str, const char *pattern); + inline bool iwildcard(const char *str, const char *pattern); }; #endif diff --git a/snesfilter/nall/string/bml.hpp b/snesfilter/nall/string/bml.hpp new file mode 100755 index 00000000..4f8139cc --- /dev/null +++ b/snesfilter/nall/string/bml.hpp @@ -0,0 +1,151 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +//BML v1.0 parser +//revision 0.05 + +namespace nall { +namespace BML { + +inline static string indent(const char *s, unsigned depth) { + array output; + do { + for(unsigned n = 0; n < depth; n++) output.append('\t'); + do output.append(*s); while(*s && *s++ != '\n'); + } while(*s); + return output.get(); +} + +struct Node { + cstring name; + cstring value; + +private: + linear_vector children; + + inline bool valid(char p) const { //A-Za-z0-9-. + return p - 'A' < 26u | p - 'a' < 26u | p - '0' < 10u | p - '-' < 2u; + } + + inline unsigned parseDepth(char *&p) { + while(*p == '\n' || *p == '#') { + while(*p != '\n') *p++ = 0; + *p++ = 0; //'\n' + } + unsigned depth = 0; + while(p[depth] == '\t') depth++; + return depth; + } + + inline void parseName(char *&p) { + if(valid(*p) == false) throw "Missing node name"; + name = p; + while(valid(*p)) p++; + } + + inline void parseValue(char *&p) { + char terminal = *p == ':' ? '\n' : ' '; //':' or '=' + *p++ = 0; + value = p; + while(*p && *p != terminal && *p != '\n') p++; + } + + inline void parseBlock(char *&p, unsigned depth) { + value = p; + char *w = p; + while(parseDepth(p) > depth) { + p += depth + 1; + while(*p && *p != '\n') *w++ = *p++; + if(*p && *p != '\n') throw "Multi-line value missing line feed"; + *w++ = *p; + } + *(w - 1) = 0; //'\n' + } + + inline void parseLine(char *&p) { + unsigned depth = parseDepth(p); + while(*p == '\t') p++; + + parseName(p); + bool multiLine = *p == '~'; + if(multiLine) *p++ = 0; + else if(*p == ':' || *p == '=') parseValue(p); + if(*p && *p != ' ' && *p != '\n') throw "Invalid character encountered"; + + while(*p == ' ') { + *p++ = 0; + Node node; + node.parseName(p); + if(*p == ':' || *p == '=') node.parseValue(p); + if(*p && *p != ' ' && *p != '\n') throw "Invalid character after node"; + if(*p == '\n') *p++ = 0; + children.append(node); + } + + if(multiLine) return parseBlock(p, depth); + + while(parseDepth(p) > depth) { + Node node; + node.parseLine(p); + children.append(node); + } + } + + inline void parse(char *&p) { + while(*p) { + Node node; + node.parseLine(p); + children.append(node); + } + } + +public: + inline Node& operator[](const char *name) { + for(auto &node : children) { + if(node.name == name) return node; + } + static Node node; + node.name = nullptr; + return node; + } + + inline bool exists() const { return name; } + unsigned size() const { return children.size(); } + Node* begin() { return children.begin(); } + Node* end() { return children.end(); } + const Node* begin() const { return children.begin(); } + const Node* end() const { return children.end(); } + inline Node() : name(""), value("") {} + friend class Document; +}; + +struct Document : Node { + cstring error; + + inline bool load(const char *document) { + if(document == nullptr) return false; + this->document = strdup(document); + char *p = this->document; + try { + this->error = nullptr; + parse(p); + } catch(const char *error) { + this->error = error; + free(this->document); + this->document = nullptr; + children.reset(); + return false; + } + return true; + } + + inline Document(const char *document = "") : document(nullptr), error(nullptr) { if(*document) load(document); } + inline ~Document() { if(document) free(document); } + +private: + char *document; +}; + +} +} + +#endif diff --git a/snesfilter/nall/string/bsv.hpp b/snesfilter/nall/string/bsv.hpp index d4b919e0..d9415d53 100755 --- a/snesfilter/nall/string/bsv.hpp +++ b/snesfilter/nall/string/bsv.hpp @@ -1,74 +1,75 @@ -#ifndef NALL_STRING_BSV_HPP -#define NALL_STRING_BSV_HPP +#ifdef NALL_STRING_INTERNAL_HPP -//BSV parser -//version 0.01 +//BSV v1.0 parser +//revision 0.02 namespace nall { -inline string bsv_decode(const char *input) { - string output; - unsigned offset = 0; - while(*input) { - //illegal characters - if(*input == '}' ) return ""; - if(*input == '\r') return ""; - if(*input == '\n') return ""; +struct BSV { + static inline string decode(const char *input) { + string output; + unsigned offset = 0; + while(*input) { + //illegal characters + if(*input == '}' ) return ""; + if(*input == '\r') return ""; + if(*input == '\n') return ""; - //normal characters - if(*input != '{') { output[offset++] = *input++; continue; } + //normal characters + if(*input != '{') { output[offset++] = *input++; continue; } - //entities - if(strbegin(input, "{lf}")) { output[offset++] = '\n'; input += 4; continue; } - if(strbegin(input, "{lb}")) { output[offset++] = '{'; input += 4; continue; } - if(strbegin(input, "{rb}")) { output[offset++] = '}'; input += 4; continue; } + //entities + if(strbegin(input, "{lf}")) { output[offset++] = '\n'; input += 4; continue; } + if(strbegin(input, "{lb}")) { output[offset++] = '{'; input += 4; continue; } + if(strbegin(input, "{rb}")) { output[offset++] = '}'; input += 4; continue; } - //illegal entities - return ""; + //illegal entities + return ""; + } + output[offset] = 0; + return output; } - output[offset] = 0; - return output; -} -inline string bsv_encode(const char *input) { - string output; - unsigned offset = 0; - while(*input) { - //illegal characters - if(*input == '\r') return ""; + static inline string encode(const char *input) { + string output; + unsigned offset = 0; + while(*input) { + //illegal characters + if(*input == '\r') return ""; - if(*input == '\n') { - output[offset++] = '{'; - output[offset++] = 'l'; - output[offset++] = 'f'; - output[offset++] = '}'; - input++; - continue; + if(*input == '\n') { + output[offset++] = '{'; + output[offset++] = 'l'; + output[offset++] = 'f'; + output[offset++] = '}'; + input++; + continue; + } + + if(*input == '{') { + output[offset++] = '{'; + output[offset++] = 'l'; + output[offset++] = 'b'; + output[offset++] = '}'; + input++; + continue; + } + + if(*input == '}') { + output[offset++] = '{'; + output[offset++] = 'r'; + output[offset++] = 'b'; + output[offset++] = '}'; + input++; + continue; + } + + output[offset++] = *input++; } - - if(*input == '{') { - output[offset++] = '{'; - output[offset++] = 'l'; - output[offset++] = 'b'; - output[offset++] = '}'; - input++; - continue; - } - - if(*input == '}') { - output[offset++] = '{'; - output[offset++] = 'r'; - output[offset++] = 'b'; - output[offset++] = '}'; - input++; - continue; - } - - output[offset++] = *input++; + output[offset] = 0; + return output; } - output[offset] = 0; - return output; -} +}; } diff --git a/snesfilter/nall/string/cast.hpp b/snesfilter/nall/string/cast.hpp index 2d010bfa..7c7e276b 100755 --- a/snesfilter/nall/string/cast.hpp +++ b/snesfilter/nall/string/cast.hpp @@ -1,31 +1,185 @@ -#ifndef NALL_STRING_CAST_HPP -#define NALL_STRING_CAST_HPP +#ifdef NALL_STRING_INTERNAL_HPP namespace nall { -//this is needed, as C++0x does not support explicit template specialization inside classes -template<> inline const char* to_string (bool v) { return v ? "true" : "false"; } -template<> inline const char* to_string (signed int v) { static char temp[256]; snprintf(temp, 255, "%+d", v); return temp; } -template<> inline const char* to_string (unsigned int v) { static char temp[256]; snprintf(temp, 255, "%u", v); return temp; } -template<> inline const char* to_string (intmax_t v) { static char temp[256]; snprintf(temp, 255, "%+lld", (long long)v); return temp; } -template<> inline const char* to_string (uintmax_t v) { static char temp[256]; snprintf(temp, 255, "%llu", (unsigned long long)v); return temp; } -template<> inline const char* to_string (double v) { static char temp[256]; snprintf(temp, 255, "%f", v); return temp; } -template<> inline const char* to_string (char *v) { return v; } -template<> inline const char* to_string (const char *v) { return v; } -template<> inline const char* to_string (string v) { return v; } -template<> inline const char* to_string(const string &v) { return v; } +//convert any (supported) type to a const char* without constructing a new nall::string +//this is used inside istring(...) to build nall::string values +template struct stringify; -template lstring& lstring::operator<<(T value) { - operator[](size()).assign(to_string(value)); - return *this; -} +// base types + +template<> struct stringify { + bool value; + operator const char*() const { return value ? "true" : "false"; } + stringify(bool value) : value(value) {} +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(char value) { integer(data, value); } +}; + +// signed integers + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(signed char value) { integer(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(signed short value) { integer(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(signed int value) { integer(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(signed long value) { integer(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(signed long long value) { integer(data, value); } +}; + +template struct stringify> { + char data[256]; + operator const char*() const { return data; } + stringify(int_t value) { integer(data, value); } +}; + +// unsigned integers + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(unsigned char value) { decimal(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(unsigned short value) { decimal(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(unsigned int value) { decimal(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(unsigned long value) { decimal(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(unsigned long long value) { decimal(data, value); } +}; + +template struct stringify> { + char data[256]; + operator const char*() const { return data; } + stringify(uint_t value) { decimal(data, value); } +}; + +// floating-point + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(float value) { fp(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(double value) { fp(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(long double value) { fp(data, value); } +}; + +// strings + +template<> struct stringify { + const char *value; + operator const char*() const { return value; } + stringify(char *value) : value(value) {} +}; + +template<> struct stringify { + const char *value; + operator const char*() const { return value; } + stringify(const char *value) : value(value) {} +}; + +template<> struct stringify { + const string &value; + operator const char*() const { return value; } + stringify(const string &value) : value(value) {} +}; + +template<> struct stringify { + const string &value; + operator const char*() const { return value; } + stringify(const string &value) : value(value) {} +}; + +template<> struct stringify { + const char *value; + operator const char*() const { return value; } + stringify(const cstring &value) : value(value) {} +}; + +template<> struct stringify { + const char *value; + operator const char*() const { return value; } + stringify(const cstring &value) : value(value) {} +}; #if defined(QSTRING_H) -template<> inline const char* to_string(QString v) { return v.toUtf8().constData(); } -template<> inline const char* to_string(const QString &v) { return v.toUtf8().constData(); } -string::operator QString() const { return QString::fromUtf8(*this); } + +template<> struct stringify { + const QString &value; + operator const char*() const { return value.toUtf8().constData(); } + stringify(const QString &value) : value(value) {} +}; + +template<> struct stringify { + const QString &value; + operator const char*() const { return value.toUtf8().constData(); } + stringify(const QString &value) : value(value) {} +}; + +string::operator QString() const { + return QString::fromUtf8(*this); +} + #endif +// + +template stringify make_string(T value) { + return stringify(std::forward(value)); +} + } #endif diff --git a/snesfilter/nall/string/compare.hpp b/snesfilter/nall/string/compare.hpp index ad311d74..941c8e67 100755 --- a/snesfilter/nall/string/compare.hpp +++ b/snesfilter/nall/string/compare.hpp @@ -1,5 +1,4 @@ -#ifndef NALL_STRING_COMPARE_HPP -#define NALL_STRING_COMPARE_HPP +#ifdef NALL_STRING_INTERNAL_HPP namespace nall { @@ -19,46 +18,6 @@ int istrcmp(const char *str1, const char *str2) { return (int)chrlower(*str1) - (int)chrlower(*str2); } -bool wildcard(const char *s, const char *p) { - const char *cp = 0, *mp = 0; - while(*s && *p != '*') { - if(*p != '?' && *s != *p) return false; - p++, s++; - } - while(*s) { - if(*p == '*') { - if(!*++p) return true; - mp = p, cp = s + 1; - } else if(*p == '?' || *p == *s) { - p++, s++; - } else { - p = mp, s = cp++; - } - } - while(*p == '*') p++; - return !*p; -} - -bool iwildcard(const char *s, const char *p) { - const char *cp = 0, *mp = 0; - while(*s && *p != '*') { - if(*p != '?' && chrlower(*s) != chrlower(*p)) return false; - p++, s++; - } - while(*s) { - if(*p == '*') { - if(!*++p) return true; - mp = p, cp = s + 1; - } else if(*p == '?' || chrlower(*p) == chrlower(*s)) { - p++, s++; - } else { - p = mp, s = cp++; - } - } - while(*p == '*') p++; - return !*p; -} - bool strbegin(const char *str, const char *key) { int i, ssl = strlen(str), ksl = strlen(key); diff --git a/snesfilter/nall/string/convert.hpp b/snesfilter/nall/string/convert.hpp index 3dd487f6..f5a2a780 100755 --- a/snesfilter/nall/string/convert.hpp +++ b/snesfilter/nall/string/convert.hpp @@ -1,5 +1,4 @@ -#ifndef NALL_STRING_CONVERT_HPP -#define NALL_STRING_CONVERT_HPP +#ifdef NALL_STRING_INTERNAL_HPP namespace nall { @@ -60,86 +59,6 @@ char* strtr(char *dest, const char *before, const char *after) { return dest; } -uintmax_t hex(const char *str) { - if(!str) return 0; - uintmax_t result = 0; - - //skip hex identifiers 0x and $, if present - if(*str == '0' && (*(str + 1) == 'X' || *(str + 1) == 'x')) str += 2; - else if(*str == '$') str++; - - while(*str) { - uint8_t x = *str++; - if(x >= '0' && x <= '9') x -= '0'; - else if(x >= 'A' && x <= 'F') x -= 'A' - 10; - else if(x >= 'a' && x <= 'f') x -= 'a' - 10; - else break; //stop at first invalid character - result = result * 16 + x; - } - - return result; -} - -intmax_t integer(const char *str) { - if(!str) return 0; - intmax_t result = 0; - bool negate = false; - - //check for sign - if(*str == '+') { - negate = false; - str++; - } else if(*str == '-') { - negate = true; - str++; - } - - while(*str) { - uint8_t x = *str++; - if(x >= '0' && x <= '9') x -= '0'; - else break; //stop at first invalid character - result = result * 10 + x; - } - - return !negate ? result : -result; -} - -uintmax_t decimal(const char *str) { - if(!str) return 0; - uintmax_t result = 0; - - while(*str) { - uint8_t x = *str++; - if(x >= '0' && x <= '9') x -= '0'; - else break; //stop at first invalid character - result = result * 10 + x; - } - - return result; -} - -uintmax_t binary(const char *str) { - if(!str) return 0; - uintmax_t result = 0; - - //skip bin identifiers 0b and %, if present - if(*str == '0' && (*(str + 1) == 'B' || *(str + 1) == 'b')) str += 2; - else if(*str == '%') str++; - - while(*str) { - uint8_t x = *str++; - if(x == '0' || x == '1') x -= '0'; - else break; //stop at first invalid character - result = result * 2 + x; - } - - return result; -} - -double fp(const char *str) { - return atof(str); -} - } #endif diff --git a/snesfilter/nall/string/core.hpp b/snesfilter/nall/string/core.hpp index e2af4eea..5ae2bd48 100755 --- a/snesfilter/nall/string/core.hpp +++ b/snesfilter/nall/string/core.hpp @@ -1,5 +1,4 @@ -#ifndef NALL_STRING_CORE_HPP -#define NALL_STRING_CORE_HPP +#ifdef NALL_STRING_INTERNAL_HPP namespace nall { @@ -8,7 +7,7 @@ static void istring(string &output) { template static void istring(string &output, const T &value, Args&&... args) { - output.append_(to_string(value)); + output.append_(make_string(value)); istring(output, std::forward(args)...); } @@ -76,7 +75,7 @@ string& string::operator=(string &&source) { if(data) free(data); size = source.size; data = source.data; - source.data = 0; + source.data = nullptr; source.size = 0; return *this; } @@ -98,7 +97,7 @@ string::string(string &&source) { if(&source == this) return; size = source.size; data = source.data; - source.data = 0; + source.data = nullptr; } string::~string() { @@ -152,9 +151,7 @@ inline lstring::lstring() { } inline lstring::lstring(std::initializer_list list) { - for(const string *s = list.begin(); s != list.end(); ++s) { - operator<<(*s); - } + for(auto &data : list) append(data); } } diff --git a/snesfilter/nall/string/cstring.hpp b/snesfilter/nall/string/cstring.hpp new file mode 100755 index 00000000..13b508ff --- /dev/null +++ b/snesfilter/nall/string/cstring.hpp @@ -0,0 +1,21 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +//const string: +//bind a const char* pointer to an object that has various testing functionality; +//yet lacks the memory allocation and modification functionality of the string class + +namespace nall { + +cstring::operator const char*() const { return data; } +unsigned cstring::length() const { return strlen(data); } +bool cstring::operator==(const char *s) const { return !strcmp(data, s); } +bool cstring::operator!=(const char *s) const { return strcmp(data, s); } +optional cstring::position (const char *key) const { return strpos(data, key); } +optional cstring::iposition(const char *key) const { return istrpos(data, key); } +cstring& cstring::operator=(const char *data) { this->data = data; return *this; } +cstring::cstring(const char *data) : data(data) {} +cstring::cstring() : data("") {} + +} + +#endif diff --git a/snesfilter/nall/string/filename.hpp b/snesfilter/nall/string/filename.hpp index 93d605ae..6dea67fc 100755 --- a/snesfilter/nall/string/filename.hpp +++ b/snesfilter/nall/string/filename.hpp @@ -1,5 +1,4 @@ -#ifndef NALL_FILENAME_HPP -#define NALL_FILENAME_HPP +#ifdef NALL_STRING_INTERNAL_HPP namespace nall { diff --git a/snesfilter/nall/string/math-fixed-point.hpp b/snesfilter/nall/string/math-fixed-point.hpp new file mode 100755 index 00000000..744621d1 --- /dev/null +++ b/snesfilter/nall/string/math-fixed-point.hpp @@ -0,0 +1,157 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace fixedpoint { + +static nall::function eval_fallback; + +static intmax_t eval_integer(const char *& s) { + if(!*s) throw "unrecognized integer"; + intmax_t value = 0, x = *s, y = *(s + 1); + + //hexadecimal + if(x == '0' && (y == 'X' || y == 'x')) { + s += 2; + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 16 + (*s++ - '0'); continue; } + if(*s >= 'A' && *s <= 'F') { value = value * 16 + (*s++ - 'A' + 10); continue; } + if(*s >= 'a' && *s <= 'f') { value = value * 16 + (*s++ - 'a' + 10); continue; } + return value; + } + } + + //binary + if(x == '0' && (y == 'B' || y == 'b')) { + s += 2; + while(true) { + if(*s == '0' || *s == '1') { value = value * 2 + (*s++ - '0'); continue; } + return value; + } + } + + //octal (or decimal '0') + if(x == '0') { + s += 1; + while(true) { + if(*s >= '0' && *s <= '7') { value = value * 8 + (*s++ - '0'); continue; } + return value; + } + } + + //decimal + if(x >= '0' && x <= '9') { + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 10 + (*s++ - '0'); continue; } + return value; + } + } + + //char + if(x == '\'' && y != '\'') { + s += 1; + while(true) { + value = value * 256 + *s++; + if(*s == '\'') { s += 1; return value; } + if(!*s) throw "mismatched char"; + } + } + + throw "unrecognized integer"; +} + +static intmax_t eval(const char *&s, int depth = 0) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) throw "unrecognized token"; + intmax_t value = 0, x = *s, y = *(s + 1); + + if(*s == '(') { + value = eval(++s, 1); + if(*s++ != ')') throw "mismatched group"; + } + + else if(x == '!') value = !eval(++s, 13); + else if(x == '~') value = ~eval(++s, 13); + else if(x == '+') value = +eval(++s, 13); + else if(x == '-') value = -eval(++s, 13); + + else if((x >= '0' && x <= '9') || x == '\'') value = eval_integer(s); + + else if(eval_fallback) value = eval_fallback(s); //optional user-defined syntax parsing + + else throw "unrecognized token"; + + while(true) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) break; + x = *s, y = *(s + 1); + + if(depth >= 13) break; + if(x == '*') { value *= eval(++s, 13); continue; } + if(x == '/') { intmax_t result = eval(++s, 13); if(result == 0) throw "division by zero"; value /= result; continue; } + if(x == '%') { intmax_t result = eval(++s, 13); if(result == 0) throw "division by zero"; value %= result; continue; } + + if(depth >= 12) break; + if(x == '+') { value += eval(++s, 12); continue; } + if(x == '-') { value -= eval(++s, 12); continue; } + + if(depth >= 11) break; + if(x == '<' && y == '<') { value <<= eval(++++s, 11); continue; } + if(x == '>' && y == '>') { value >>= eval(++++s, 11); continue; } + + if(depth >= 10) break; + if(x == '<' && y == '=') { value = value <= eval(++++s, 10); continue; } + if(x == '>' && y == '=') { value = value >= eval(++++s, 10); continue; } + if(x == '<') { value = value < eval(++s, 10); continue; } + if(x == '>') { value = value > eval(++s, 10); continue; } + + if(depth >= 9) break; + if(x == '=' && y == '=') { value = value == eval(++++s, 9); continue; } + if(x == '!' && y == '=') { value = value != eval(++++s, 9); continue; } + + if(depth >= 8) break; + if(x == '&' && y != '&') { value = value & eval(++s, 8); continue; } + + if(depth >= 7) break; + if(x == '^' && y != '^') { value = value ^ eval(++s, 7); continue; } + + if(depth >= 6) break; + if(x == '|' && y != '|') { value = value | eval(++s, 6); continue; } + + if(depth >= 5) break; + if(x == '&' && y == '&') { value = eval(++++s, 5) && value; continue; } + + if(depth >= 4) break; + if(x == '^' && y == '^') { value = (!eval(++++s, 4) != !value); continue; } + + if(depth >= 3) break; + if(x == '|' && y == '|') { value = eval(++++s, 3) || value; continue; } + + if(x == '?') { + intmax_t lhs = eval(++s, 2); + if(*s != ':') throw "mismatched ternary"; + intmax_t rhs = eval(++s, 2); + value = value ? lhs : rhs; + continue; + } + if(depth >= 2) break; + + if(depth > 0 && x == ')') break; + + throw "unrecognized token"; + } + + return value; +} + +static bool eval(const char *s, intmax_t &result) { + try { + result = eval(s); + return true; + } catch(const char*) { + result = 0; + return false; + } +} + +} + +#endif diff --git a/snesfilter/nall/string/math-floating-point.hpp b/snesfilter/nall/string/math-floating-point.hpp new file mode 100755 index 00000000..85680aad --- /dev/null +++ b/snesfilter/nall/string/math-floating-point.hpp @@ -0,0 +1,149 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace floatingpoint { + +static nall::function eval_fallback; + +static double eval_integer(const char *&s) { + if(!*s) throw "unrecognized integer"; + intmax_t value = 0, radix = 0, x = *s, y = *(s + 1); + + //hexadecimal + if(x == '0' && (y == 'X' || y == 'x')) { + s += 2; + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 16 + (*s++ - '0'); continue; } + if(*s >= 'A' && *s <= 'F') { value = value * 16 + (*s++ - 'A' + 10); continue; } + if(*s >= 'a' && *s <= 'f') { value = value * 16 + (*s++ - 'a' + 10); continue; } + return value; + } + } + + //binary + if(x == '0' && (y == 'B' || y == 'b')) { + s += 2; + while(true) { + if(*s == '0' || *s == '1') { value = value * 2 + (*s++ - '0'); continue; } + return value; + } + } + + //octal (or decimal '0') + if(x == '0' && y != '.') { + s += 1; + while(true) { + if(*s >= '0' && *s <= '7') { value = value * 8 + (*s++ - '0'); continue; } + return value; + } + } + + //decimal + if(x >= '0' && x <= '9') { + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 10 + (*s++ - '0'); continue; } + if(*s == '.') { s++; break; } + return value; + } + //floating-point + while(true) { + if(*s >= '0' && *s <= '9') { radix = radix * 10 + (*s++ - '0'); continue; } + return atof(nall::string{ nall::decimal(value), ".", nall::decimal(radix) }); + } + } + + //char + if(x == '\'' && y != '\'') { + s += 1; + while(true) { + value = value * 256 + *s++; + if(*s == '\'') { s += 1; return value; } + if(!*s) throw "mismatched char"; + } + } + + throw "unrecognized integer"; +} + +static double eval(const char *&s, int depth = 0) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) throw "unrecognized token"; + double value = 0, x = *s, y = *(s + 1); + + if(*s == '(') { + value = eval(++s, 1); + if(*s++ != ')') throw "mismatched group"; + } + + else if(x == '!') value = !eval(++s, 9); + else if(x == '+') value = +eval(++s, 9); + else if(x == '-') value = -eval(++s, 9); + + else if((x >= '0' && x <= '9') || x == '\'') value = eval_integer(s); + + else if(eval_fallback) value = eval_fallback(s); //optional user-defined syntax parsing + + else throw "unrecognized token"; + + while(true) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) break; + x = *s, y = *(s + 1); + + if(depth >= 9) break; + if(x == '*') { value *= eval(++s, 9); continue; } + if(x == '/') { double result = eval(++s, 9); if(result == 0.0) throw "division by zero"; value /= result; continue; } + + if(depth >= 8) break; + if(x == '+') { value += eval(++s, 8); continue; } + if(x == '-') { value -= eval(++s, 8); continue; } + + if(depth >= 7) break; + if(x == '<' && y == '=') { value = value <= eval(++++s, 7); continue; } + if(x == '>' && y == '=') { value = value >= eval(++++s, 7); continue; } + if(x == '<') { value = value < eval(++s, 7); continue; } + if(x == '>') { value = value > eval(++s, 7); continue; } + + if(depth >= 6) break; + if(x == '=' && y == '=') { value = value == eval(++++s, 6); continue; } + if(x == '!' && y == '=') { value = value != eval(++++s, 6); continue; } + + if(depth >= 5) break; + if(x == '&' && y == '&') { value = eval(++++s, 5) && value; continue; } + + if(depth >= 4) break; + if(x == '^' && y == '^') { value = (!eval(++++s, 4) != !value); continue; } + + if(depth >= 3) break; + if(x == '|' && y == '|') { value = eval(++++s, 3) || value; continue; } + + if(x == '?') { + double lhs = eval(++s, 2); + if(*s != ':') throw "mismatched ternary"; + double rhs = eval(++s, 2); + value = value ? lhs : rhs; + continue; + } + if(depth >= 2) break; + + if(depth > 0 && x == ')') break; + + throw "unrecognized token"; + } + + return value; +} + +static bool eval(const char *s, double &result) { + try { + result = eval(s); + return true; + } catch(const char*e) { +printf("%s\n", e); + result = 0; + return false; + } +} + +} + +#endif diff --git a/snesfilter/nall/string/math.hpp b/snesfilter/nall/string/math.hpp index d4bc9d25..8356e162 100755 --- a/snesfilter/nall/string/math.hpp +++ b/snesfilter/nall/string/math.hpp @@ -1,5 +1,4 @@ -#ifndef NALL_STRING_MATH_HPP -#define NALL_STRING_MATH_HPP +#ifdef NALL_STRING_INTERNAL_HPP namespace nall { @@ -87,8 +86,8 @@ static int eval(const char *&s, int depth = 0) { if(depth >= 13) break; if(x == '*') { value *= eval(++s, 13); continue; } - if(x == '/') { value /= eval(++s, 13); continue; } - if(x == '%') { value %= eval(++s, 13); continue; } + if(x == '/') { int result = eval(++s, 13); if(result == 0) throw "division_by_zero"; value /= result; continue; } + if(x == '%') { int result = eval(++s, 13); if(result == 0) throw "division_by_zero"; value %= result; continue; } if(depth >= 12) break; if(x == '+') { value += eval(++s, 12); continue; } diff --git a/snesfilter/nall/string/platform.hpp b/snesfilter/nall/string/platform.hpp index 0deda430..83a5fbae 100755 --- a/snesfilter/nall/string/platform.hpp +++ b/snesfilter/nall/string/platform.hpp @@ -1,5 +1,4 @@ -#ifndef NALL_STRING_PLATFORM_HPP -#define NALL_STRING_PLATFORM_HPP +#ifdef NALL_STRING_INTERNAL_HPP namespace nall { diff --git a/snesfilter/nall/string/replace.hpp b/snesfilter/nall/string/replace.hpp index 7c7a09d4..2bd1412f 100755 --- a/snesfilter/nall/string/replace.hpp +++ b/snesfilter/nall/string/replace.hpp @@ -1,5 +1,4 @@ -#ifndef NALL_STRING_REPLACE_HPP -#define NALL_STRING_REPLACE_HPP +#ifdef NALL_STRING_INTERNAL_HPP namespace nall { diff --git a/snesfilter/nall/string/split.hpp b/snesfilter/nall/string/split.hpp index 1644401b..bb12a91b 100755 --- a/snesfilter/nall/string/split.hpp +++ b/snesfilter/nall/string/split.hpp @@ -1,5 +1,4 @@ -#ifndef NALL_STRING_SPLIT_HPP -#define NALL_STRING_SPLIT_HPP +#ifdef NALL_STRING_INTERNAL_HPP namespace nall { @@ -8,14 +7,13 @@ template lstring& lstring::usplit if(!key || !*key) return *this; const char *p = base; - unsigned counter = 0; while(*p) { - if(Limit) if(counter >= Limit) break; + if(Limit) if(size() >= Limit) break; if(quoteskip(p)) continue; for(unsigned n = 0;; n++) { if(key[n] == 0) { - strlcpy(operator[](counter++), base, (unsigned)(p - base + 1)); + append(substr(base, 0, p - base)); p += n; base = p; break; @@ -24,7 +22,7 @@ template lstring& lstring::usplit } } - operator[](counter) = base; + append(base); return *this; } diff --git a/snesfilter/nall/string/strl.hpp b/snesfilter/nall/string/strl.hpp index 84c841fa..44f6d6f7 100755 --- a/snesfilter/nall/string/strl.hpp +++ b/snesfilter/nall/string/strl.hpp @@ -1,5 +1,4 @@ -#ifndef NALL_STRING_STRL_HPP -#define NALL_STRING_STRL_HPP +#ifdef NALL_STRING_INTERNAL_HPP namespace nall { diff --git a/snesfilter/nall/string/strpos.hpp b/snesfilter/nall/string/strpos.hpp index 3b28923e..fe563a6c 100755 --- a/snesfilter/nall/string/strpos.hpp +++ b/snesfilter/nall/string/strpos.hpp @@ -1,5 +1,4 @@ -#ifndef NALL_STRING_STRPOS_HPP -#define NALL_STRING_STRPOS_HPP +#ifdef NALL_STRING_INTERNAL_HPP //usage example: //if(auto position = strpos(str, key)) print(position(), "\n"); diff --git a/snesfilter/nall/string/trim.hpp b/snesfilter/nall/string/trim.hpp index d1f15ee1..ba049d71 100755 --- a/snesfilter/nall/string/trim.hpp +++ b/snesfilter/nall/string/trim.hpp @@ -1,5 +1,4 @@ -#ifndef NALL_STRING_TRIM_HPP -#define NALL_STRING_TRIM_HPP +#ifdef NALL_STRING_INTERNAL_HPP namespace nall { diff --git a/snesfilter/nall/string/utility.hpp b/snesfilter/nall/string/utility.hpp index 13faaf64..2212688b 100755 --- a/snesfilter/nall/string/utility.hpp +++ b/snesfilter/nall/string/utility.hpp @@ -1,5 +1,4 @@ -#ifndef NALL_STRING_UTILITY_HPP -#define NALL_STRING_UTILITY_HPP +#ifdef NALL_STRING_INTERNAL_HPP namespace nall { @@ -65,15 +64,51 @@ string sha256(const uint8_t *data, unsigned size) { sha256_final(&sha); sha256_hash(&sha, hash); string result; - foreach(byte, hash) result.append(hex<2>(byte)); + for(auto &byte : hash) result.append(hex<2>(byte)); return result; } -/* arithmetic <> string */ +/* cast.hpp arithmetic -> string */ + +char* integer(char *result, intmax_t value) { + bool negative = value < 0; + if(negative) value = -value; + + char buffer[64]; + unsigned size = 0; + + do { + unsigned n = value % 10; + buffer[size++] = '0' + n; + value /= 10; + } while(value); + buffer[size++] = negative ? '-' : '+'; + + for(signed x = size - 1, y = 0; x >= 0 && y < size; x--, y++) result[x] = buffer[y]; + result[size] = 0; + return result; +} + +char* decimal(char *result, uintmax_t value) { + char buffer[64]; + unsigned size = 0; + + do { + unsigned n = value % 10; + buffer[size++] = '0' + n; + value /= 10; + } while(value); + + for(signed x = size - 1, y = 0; x >= 0 && y < size; x--, y++) result[x] = buffer[y]; + result[size] = 0; + return result; +} + +/* general-purpose arithmetic -> string */ template string integer(intmax_t value) { bool negative = value < 0; - if(negative) value = abs(value); + if(negative) value = -value; char buffer[64]; unsigned size = 0; @@ -100,7 +135,7 @@ template string integer(intmax_t value) { template string linteger(intmax_t value) { bool negative = value < 0; - if(negative) value = abs(value); + if(negative) value = -value; char buffer[64]; unsigned size = 0; @@ -218,9 +253,14 @@ template string binary(uintmax_t value) { //using sprintf is certainly not the most ideal method to convert //a double to a string ... but attempting to parse a double by //hand, digit-by-digit, results in subtle rounding errors. -unsigned fp(char *str, double value) { +unsigned fp(char *str, long double value) { char buffer[256]; - sprintf(buffer, "%f", value); + #ifdef _WIN32 + //Windows C-runtime does not support long double via sprintf() + sprintf(buffer, "%f", (double)value); + #else + sprintf(buffer, "%Lf", value); + #endif //remove excess 0's in fraction (2.500000 -> 2.5) for(char *p = buffer; *p; p++) { @@ -239,7 +279,7 @@ unsigned fp(char *str, double value) { return length + 1; } -string fp(double value) { +string fp(long double value) { string temp; temp.reserve(fp(0, value)); fp(temp(), value); diff --git a/snesfilter/nall/string/variadic.hpp b/snesfilter/nall/string/variadic.hpp index 6c027fc8..c43bfe86 100755 --- a/snesfilter/nall/string/variadic.hpp +++ b/snesfilter/nall/string/variadic.hpp @@ -1,5 +1,4 @@ -#ifndef NALL_STRING_VARIADIC_HPP -#define NALL_STRING_VARIADIC_HPP +#ifdef NALL_STRING_INTERNAL_HPP namespace nall { diff --git a/snesfilter/nall/string/wildcard.hpp b/snesfilter/nall/string/wildcard.hpp new file mode 100755 index 00000000..9d2359d5 --- /dev/null +++ b/snesfilter/nall/string/wildcard.hpp @@ -0,0 +1,78 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +bool wildcard(const char *s, const char *p) { + const char *cp = 0, *mp = 0; + while(*s && *p != '*') { + if(*p != '?' && *s != *p) return false; + p++, s++; + } + while(*s) { + if(*p == '*') { + if(!*++p) return true; + mp = p, cp = s + 1; + } else if(*p == '?' || *p == *s) { + p++, s++; + } else { + p = mp, s = cp++; + } + } + while(*p == '*') p++; + return !*p; +} + +bool iwildcard(const char *s, const char *p) { + const char *cp = 0, *mp = 0; + while(*s && *p != '*') { + if(*p != '?' && chrlower(*s) != chrlower(*p)) return false; + p++, s++; + } + while(*s) { + if(*p == '*') { + if(!*++p) return true; + mp = p, cp = s + 1; + } else if(*p == '?' || chrlower(*p) == chrlower(*s)) { + p++, s++; + } else { + p = mp, s = cp++; + } + } + while(*p == '*') p++; + return !*p; +} + +inline bool tokenize(const char *s, const char *p) { + while(*s) { + if(*p == '*') { + while(*s) if(tokenize(s++, p + 1)) return true; + return !*++p; + } + if(*s++ != *p++) return false; + } + while(*p == '*') p++; + return !*p; +} + +inline bool tokenize(lstring &list, const char *s, const char *p) { + while(*s) { + if(*p == '*') { + const char *b = s; + while(*s) { + if(tokenize(list, s++, p + 1)) { + list.prepend(substr(b, 0, --s - b)); + return true; + } + } + list.prepend(b); + return !*++p; + } + if(*s++ != *p++) return false; + } + while(*p == '*') { list.prepend(s); p++; } + return !*p; +} + +} + +#endif diff --git a/snesfilter/nall/string/wrapper.hpp b/snesfilter/nall/string/wrapper.hpp index a28c1ced..c02d5396 100755 --- a/snesfilter/nall/string/wrapper.hpp +++ b/snesfilter/nall/string/wrapper.hpp @@ -1,10 +1,14 @@ -#ifndef NALL_STRING_WRAPPER_HPP -#define NALL_STRING_WRAPPER_HPP +#ifdef NALL_STRING_INTERNAL_HPP namespace nall { unsigned string::length() const { return strlen(data); } +template lstring string::split(const char *key) const { lstring result; result.split(key, data); return result; } +template lstring string::isplit(const char *key) const { lstring result; result.isplit(key, data); return result; } +template lstring string::qsplit(const char *key) const { lstring result; result.qsplit(key, data); return result; } +template lstring string::iqsplit(const char *key) const { lstring result; result.iqsplit(key, data); return result; } + bool string::equals(const char *str) const { return !strcmp(data, str); } bool string::iequals(const char *str) const { return !istrcmp(data, str); } diff --git a/snesfilter/nall/string/xml.hpp b/snesfilter/nall/string/xml.hpp index 47653786..069639b0 100755 --- a/snesfilter/nall/string/xml.hpp +++ b/snesfilter/nall/string/xml.hpp @@ -1,8 +1,7 @@ -#ifndef NALL_STRING_XML_HPP -#define NALL_STRING_XML_HPP +#ifdef NALL_STRING_INTERNAL_HPP -//XML subset parser -//version 0.05 +//XML v1.0 subset parser +//revision 0.05 namespace nall { diff --git a/snesfilter/nall/test/cc.sh b/snesfilter/nall/test/cc.sh index d084a256..142a1b0c 100755 --- a/snesfilter/nall/test/cc.sh +++ b/snesfilter/nall/test/cc.sh @@ -1,2 +1,2 @@ -g++-4.5 -std=gnu++0x -g -o test test.cpp -I../.. -#g++-4.5 -std=gnu++0x -O3 -fomit-frame-pointer -s -o test test.cpp -I../.. +g++-4.6 -std=gnu++0x -g -o test test.cpp -I../.. +#g++-4.6 -std=gnu++0x -O3 -fomit-frame-pointer -s -o test test.cpp -I../.. diff --git a/snesfilter/nall/test/document.bml b/snesfilter/nall/test/document.bml new file mode 100755 index 00000000..25773f2c --- /dev/null +++ b/snesfilter/nall/test/document.bml @@ -0,0 +1,14 @@ +# bml version=1.0 +cartridge=SNES region=NTSC origin:United States + title:The Legend of Zelda: A Link to the Past + empty-line~ + description~ language:English + Gannon has kidnapped princess Zelda. + Help Link rescue her!! + pcb:SHVC-1A3M-30 + rom + map mode=linear address=00-3f:8000-ffff + map mode=linear address=80-bf:8000-ffff + ram battery + map mode=linear address=70-7f:0000-7fff + map mode=linear address=f0-ff:0000-7fff diff --git a/snesfilter/nall/test/test b/snesfilter/nall/test/test index 56b80ee4a07f105a3a3ddcf00161650511511448..d12c2b3737c9797c3ccd51db7448ae055f891415 100755 GIT binary patch literal 250304 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

HEW$$QumSRahOS1Y4H-=0MU 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 LkJDv|t}Oox=h-eW literal 228157 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 z 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 #include #include -#include #include #include #include #include #include #include -#include #include #include #include +#include #include #include +#include #include #include #include @@ -42,9 +42,49 @@ #include using namespace nall; -int main(int main, char **argv) { - lstring a = { "hey", "hi" }, b = { "hey", "hi" }; - print(a == b, "\n"); +int main() { + image x(0, 64, 65535ull << 48, 65535ull << 32, 65535ull << 16, 65535ull << 0); + x.allocate(4, 1); + + uint64_t *data = (uint64_t*)x.data; + data[0] = 0xffffffffffffffffull; + data[1] = 0x0000ffff00000000ull; + data[2] = 0x00000000ffff0000ull; + data[3] = 0x000000000000ffffull; + + x.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); + + uint32_t *output = (uint32_t*)x.data; + print(hex<8>(output[0]), "\n"); + print(hex<8>(output[1]), "\n"); + print(hex<8>(output[2]), "\n"); + print(hex<8>(output[3]), "\n"); return 0; } + +/* +int main(int argc, char **argv) { + string text; + text.readfile("document.bml"); + BML::Document document(text); + if(document.error) print(document.error, "\n"); + + for(auto &a : document) { + print("", a.name, ":", a.value, ";\n"); + for(auto &b : a) { + print(" ", b.name, ":", b.value, ";\n"); + for(auto &c : b) { + print(" ", c.name, ":", c.value, ";\n"); + for(auto &d : c) { + print(" ", d.name, ":", d.value, ";\n"); + } + } + } + } + +//print(document["cartridge"]["title"].value, "\n"); + + return 0; +} +*/ diff --git a/snesfilter/nall/utility.hpp b/snesfilter/nall/utility.hpp index 374b5469..ff8d8bee 100755 --- a/snesfilter/nall/utility.hpp +++ b/snesfilter/nall/utility.hpp @@ -21,6 +21,7 @@ namespace nall { }; template class optional { + public: bool valid; T value; public: diff --git a/snesfilter/nall/vector.hpp b/snesfilter/nall/vector.hpp index f98eb375..1af16d29 100755 --- a/snesfilter/nall/vector.hpp +++ b/snesfilter/nall/vector.hpp @@ -1,17 +1,131 @@ #ifndef NALL_VECTOR_HPP #define NALL_VECTOR_HPP +#include #include #include #include #include #include #include -#include -#include #include namespace nall { + template struct vector { + struct exception_out_of_bounds{}; + + protected: + T *pool; + unsigned poolsize; + unsigned objectsize; + + public: + unsigned size() const { return objectsize; } + unsigned capacity() const { return poolsize; } + + void reset() { + if(pool) { + for(unsigned n = 0; n < objectsize; n++) pool[n].~T(); + free(pool); + } + pool = nullptr; + poolsize = 0; + objectsize = 0; + } + + void reserve(unsigned size) { + size = bit::round(size); //amortize growth + T *copy = (T*)calloc(size, sizeof(T)); + for(unsigned n = 0; n < min(size, objectsize); n++) new(copy + n) T(pool[n]); + for(unsigned n = 0; n < objectsize; n++) pool[n].~T(); + free(pool); + pool = copy; + poolsize = size; + objectsize = min(size, objectsize); + } + + template + void append(const T& data, Args&&... args) { + append(data); + append(std::forward(args)...); + } + + void append(const T& data) { + if(objectsize + 1 > poolsize) reserve(objectsize + 1); + new(pool + objectsize++) T(data); + } + + void prepend(const T& data) { + append(data); + for(unsigned n = objectsize - 1; n; n--) swap(pool[n], pool[n - 1]); + } + + void remove(unsigned index, unsigned count = 1) { + for(unsigned n = index; count + n < objectsize; n++) { + pool[n] = pool[count + n]; + } + objectsize = (count + index >= objectsize) ? index : objectsize - count; + } + + //access + inline T& operator[](unsigned position) { + if(position >= objectsize) throw exception_out_of_bounds(); + return pool[position]; + } + + inline const T& operator[](unsigned position) const { + if(position >= objectsize) throw exception_out_of_bounds(); + return pool[position]; + } + + inline const T& operator()(unsigned position, const T& data) const { + if(position >= objectsize) return data; + return pool[position]; + } + + //iteration + T* begin() { return &pool[0]; } + T* end() { return &pool[objectsize]; } + const T* begin() const { return &pool[0]; } + const T* end() const { return &pool[objectsize]; } + + //copy + inline vector& operator=(const vector &source) { + reset(); + reserve(source.capacity()); + for(auto &data : source) append(data); + return *this; + } + + vector(const vector &source) : pool(nullptr), poolsize(0), objectsize(0) { + operator=(source); + } + + //move + inline vector& operator=(vector &&source) { + reset(); + pool = source.pool, poolsize = source.poolsize, objectsize = source.objectsize; + source.pool = nullptr, source.poolsize = 0, source.objectsize = 0; + return *this; + } + + vector(vector &&source) : pool(nullptr), poolsize(0), objectsize(0) { + operator=(std::move(source)); + } + + //construction + vector() : pool(nullptr), poolsize(0), objectsize(0) { + } + + vector(std::initializer_list list) : pool(nullptr), poolsize(0), objectsize(0) { + for(auto &data : list) append(data); + } + + ~vector() { + reset(); + } + }; + //linear_vector //memory: O(capacity * 2) // @@ -24,7 +138,7 @@ namespace nall { //if objects hold memory address references to themselves (introspection), a //valid copy constructor will be needed to keep pointers valid. - template class linear_vector { + template struct linear_vector { protected: T *pool; unsigned poolsize, objectsize; @@ -38,7 +152,7 @@ namespace nall { for(unsigned i = 0; i < objectsize; i++) pool[i].~T(); free(pool); } - pool = 0; + pool = nullptr; poolsize = 0; objectsize = 0; } @@ -77,7 +191,7 @@ namespace nall { template void insert(unsigned index, const U list) { linear_vector merged; for(unsigned i = 0; i < index; i++) merged.append(pool[i]); - foreach(item, list) merged.append(item); + for(auto &item : list) merged.append(item); for(unsigned i = index; i < objectsize; i++) merged.append(pool[i]); operator=(merged); } @@ -94,10 +208,10 @@ namespace nall { else resize(objectsize - count); } - linear_vector() : pool(0), poolsize(0), objectsize(0) { + linear_vector() : pool(nullptr), poolsize(0), objectsize(0) { } - linear_vector(std::initializer_list list) : pool(0), poolsize(0), objectsize(0) { + linear_vector(std::initializer_list list) : pool(nullptr), poolsize(0), objectsize(0) { for(const T *p = list.begin(); p != list.end(); ++p) append(*p); } @@ -114,7 +228,7 @@ namespace nall { return *this; } - linear_vector(const linear_vector &source) : pool(0), poolsize(0), objectsize(0) { + linear_vector(const linear_vector &source) : pool(nullptr), poolsize(0), objectsize(0) { operator=(source); } @@ -124,12 +238,12 @@ namespace nall { pool = source.pool; poolsize = source.poolsize; objectsize = source.objectsize; - source.pool = 0; + source.pool = nullptr; source.reset(); return *this; } - linear_vector(linear_vector &&source) : pool(0), poolsize(0), objectsize(0) { + linear_vector(linear_vector &&source) : pool(nullptr), poolsize(0), objectsize(0) { operator=(std::move(source)); } @@ -161,7 +275,7 @@ namespace nall { //by guaranteeing that the base memory address of each objects never changes, //this avoids the need for an object to have a valid copy constructor. - template class pointer_vector { + template struct pointer_vector { protected: T **pool; unsigned poolsize, objectsize; @@ -175,7 +289,7 @@ namespace nall { for(unsigned i = 0; i < objectsize; i++) { if(pool[i]) delete pool[i]; } free(pool); } - pool = 0; + pool = nullptr; poolsize = 0; objectsize = 0; } @@ -211,7 +325,7 @@ namespace nall { template void insert(unsigned index, const U list) { pointer_vector merged; for(unsigned i = 0; i < index; i++) merged.append(*pool[i]); - foreach(item, list) merged.append(item); + for(auto &item : list) merged.append(item); for(unsigned i = index; i < objectsize; i++) merged.append(*pool[i]); operator=(merged); } @@ -228,10 +342,10 @@ namespace nall { else resize(objectsize - count); } - pointer_vector() : pool(0), poolsize(0), objectsize(0) { + pointer_vector() : pool(nullptr), poolsize(0), objectsize(0) { } - pointer_vector(std::initializer_list list) : pool(0), poolsize(0), objectsize(0) { + pointer_vector(std::initializer_list list) : pool(nullptr), poolsize(0), objectsize(0) { for(const T *p = list.begin(); p != list.end(); ++p) append(*p); } @@ -248,7 +362,7 @@ namespace nall { return *this; } - pointer_vector(const pointer_vector &source) : pool(0), poolsize(0), objectsize(0) { + pointer_vector(const pointer_vector &source) : pool(nullptr), poolsize(0), objectsize(0) { operator=(source); } @@ -258,12 +372,12 @@ namespace nall { pool = source.pool; poolsize = source.poolsize; objectsize = source.objectsize; - source.pool = 0; + source.pool = nullptr; source.reset(); return *this; } - pointer_vector(pointer_vector &&source) : pool(0), poolsize(0), objectsize(0) { + pointer_vector(pointer_vector &&source) : pool(nullptr), poolsize(0), objectsize(0) { operator=(std::move(source)); } @@ -284,18 +398,17 @@ namespace nall { bool operator!=(const iterator &source) const { return index != source.index; } T& operator*() { return vector.operator[](index); } iterator& operator++() { index++; return *this; } - iterator(pointer_vector &vector, unsigned index) : vector(vector), index(index) {} + iterator(const pointer_vector &vector, unsigned index) : vector(vector), index(index) {} private: - pointer_vector &vector; + const pointer_vector &vector; unsigned index; }; iterator begin() { return iterator(*this, 0); } iterator end() { return iterator(*this, objectsize); } + const iterator begin() const { return iterator(*this, 0); } + const iterator end() const { return iterator(*this, objectsize); } }; - - template struct has_size> { enum { value = true }; }; - template struct has_size> { enum { value = true }; }; } #endif