diff --git a/src/Makefile b/src/Makefile index 15079f6c..0774319a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,5 +1,6 @@ include lib/nall/Makefile.string prefix = /usr/local +ui = ui_qt ################ ### compiler ### @@ -7,23 +8,33 @@ prefix = /usr/local ifneq ($(findstring gcc,$(compiler)),) # GCC family flags = -O3 -fomit-frame-pointer -Ilib - c = $(compiler) $(flags) - cpp = $(subst cc,++,$(compiler)) $(flags) + # note: libco *requires* -fomit-frame-pointer on i386 arch + libcoflags := $(flags) -static + c = $(compiler) + cpp = $(subst cc,++,$(compiler)) obj = o rule = -c $< -o $@ link = -s mkbin = -o$1 mkdef = -D$1 + mkinc = -I$1 mklib = -l$1 + + # profile-guided optimization: + # flags += -fprofile-generate + # link += -lgcov + # flags += -fprofile-use else ifeq ($(compiler),cl) # Visual C++ flags = /nologo /wd4355 /wd4805 /wd4996 /Ox /GL /EHsc /Ilib - c = cl $(flags) - cpp = cl $(flags) + libcoflags = $(flags) + c = cl + cpp = cl obj = obj rule = /c $< /Fo$@ link = /link mkbin = /Fe$1 mkdef = /D$1 + mkinc = /I$1 mklib = $1.lib else unknown_compiler: help; @@ -35,21 +46,19 @@ endif ifeq ($(platform),x) # X11 ruby = video.glx video.xv video.sdl audio.alsa audio.openal audio.oss audio.pulseaudio audio.ao input.sdl input.x - link += `pkg-config --libs gtk+-2.0` - link += $(call mklib,Xtst) delete = rm -f $1 else ifeq ($(platform),win) # Windows + # enable static linking to Qt for Windows build + mingw_link_flags = -mwindows -enable-stdcall-fixup -Wl,-s -Wl,-enable-auto-import -Wl,-enable-runtime-pseudo-reloc + ruby = video.direct3d video.wgl video.directdraw video.gdi audio.directsound input.directinput - link += $(if $(findstring mingw,$(compiler)),-mwindows) + delete = $(if $(findstring i586-mingw-gcc,$(compiler)),rm -f $1,del $(subst /,\,$1)) + link += $(if $(findstring mingw,$(compiler)),$(mingw_link_flags)) link += $(call mklib,uuid) link += $(call mklib,kernel32) link += $(call mklib,user32) link += $(call mklib,gdi32) link += $(call mklib,shell32) - link += $(call mklib,winmm) - link += $(call mklib,comdlg32) - link += $(call mklib,comctl32) - delete = $(if $(findstring i586-mingw-gcc,$(compiler)),rm -f $1,del $(subst /,\,$1)) else unknown_platform: help; endif @@ -58,8 +67,7 @@ endif ### ruby ### ############ -rubyflags = -rubyflags += $(if $(findstring .sdl,$(ruby)),`sdl-config --cflags`) +rubyflags = $(if $(findstring .sdl,$(ruby)),`sdl-config --cflags`) link += $(if $(findstring video.direct3d,$(ruby)),$(call mklib,d3d9)) link += $(if $(findstring video.directdraw,$(ruby)),$(call mklib,ddraw)) @@ -74,11 +82,11 @@ link += $(if $(findstring audio.pulseaudio,$(ruby)),$(call mklib,pulse-simple)) link += $(if $(findstring input.directinput,$(ruby)),$(call mklib,dinput8) $(call mklib,dxguid)) link += $(if $(findstring input.sdl,$(ruby)),`sdl-config --libs`) -#################################### -### main target and dependencies ### -#################################### +#################### +### core objects ### +#################### -objects = main libco hiro ruby libfilter string \ +objects = libco ruby libfilter string \ reader cart cheat \ memory smemory cpu scpu smp ssmp sdsp ppu bppu snes \ bsx srtc sdd1 spc7110 cx4 dsp1 dsp2 dsp3 dsp4 obc1 st010 @@ -93,38 +101,27 @@ ifeq ($(enable_jma),true) flags += $(call mkdef,JMA_SUPPORT) endif -objects := $(patsubst %,obj/%.$(obj),$(objects)) -rubydef := $(foreach c,$(subst .,_,$(call strupper,$(ruby))),$(call mkdef,$c)) - -# Windows resource file -ifeq ($(platform),win) - objects += obj/resource.$(obj) -endif - -################ -### implicit ### -################ +###################### +### implicit rules ### +###################### compile = \ $(strip \ $(if $(filter %.c,$<), \ - $(c) $1 $(rule), \ + $(c) $(flags) $1 $(rule), \ $(if $(filter %.cpp,$<), \ - $(cpp) $1 $(rule) \ + $(cpp) $(flags) $1 $(rule) \ ) \ ) \ ) -%.$(obj): $<; $(call compile) +%.$(obj): $<; $(call compile) all: build; -############ -### main ### -############ - -obj/main.$(obj) : ui/main.cpp ui/* ui/base/* ui/event/* ui/loader/* ui/settings/* -obj/resource.$(obj): ui/bsnes.rc; windres ui/bsnes.rc obj/resource.$(obj) +include $(ui)/Makefile +objects := $(patsubst %,obj/%.$(obj),$(objects)) +rubydef := $(foreach c,$(subst .,_,$(call strupper,$(ruby))),$(call mkdef,$c)) ################# ### libraries ### @@ -132,10 +129,8 @@ obj/resource.$(obj): ui/bsnes.rc; windres ui/bsnes.rc obj/resource.$(obj) obj/ruby.$(obj): lib/ruby/ruby.cpp lib/ruby/* lib/ruby/video/* lib/ruby/audio/* lib/ruby/input/* $(call compile,$(rubydef) $(rubyflags)) -obj/hiro.$(obj): lib/hiro/hiro.cpp lib/hiro/* lib/hiro/gtk/* lib/hiro/win/* - $(call compile,$(if $(call streq,$(platform),x),`pkg-config --cflags gtk+-2.0`)) obj/libco.$(obj): lib/libco/libco.c lib/libco/* - $(call compile,$(if $(call strne,$(compiler),cl),-static)) + $(c) $(libcoflags) $(rule) obj/libfilter.$(obj): lib/libfilter/libfilter.cpp lib/libfilter/* obj/string.$(obj): lib/nall/string.cpp lib/nall/* @@ -239,14 +234,15 @@ obj/winout.$(obj) : reader/jma/winout.cpp reader/jma/* ### targets ### ############### -build: $(objects) +build: ui_build $(objects) $(strip $(cpp) $(call mkbin,../bsnes) $(objects) $(link)) install: install -D -m 755 ../bsnes $(DESTDIR)$(prefix)/bin/bsnes - install -D -m 644 data/bsnes.png $(DESTDIR)$(prefix)/share/icons/bsnes.png + install -D -m 644 data/bsnes.png $(DESTDIR)$(prefix)/share/pixmaps/bsnes.png + install -D -m 644 data/bsnes.desktop $(DESTDIR)$(prefix)/share/applications/bsnes.desktop -clean: +clean: ui_clean -@$(call delete,obj/*.$(obj)) -@$(call delete,*.res) -@$(call delete,*.pgd) diff --git a/src/base.hpp b/src/base.hpp index dfe6b493..3cdbc645 100644 --- a/src/base.hpp +++ b/src/base.hpp @@ -1,4 +1,4 @@ -#define BSNES_VERSION "0.039" +#define BSNES_VERSION "0.040" #define BSNES_TITLE "bsnes v" BSNES_VERSION #define BUSCORE sBus @@ -27,10 +27,10 @@ #include #include #include -#include #include #include #include +#include #include #include #include diff --git a/src/bsnes.lnk b/src/bsnes.lnk new file mode 100644 index 00000000..7e2d0376 Binary files /dev/null and b/src/bsnes.lnk differ diff --git a/src/cart/cart.cpp b/src/cart/cart.cpp index 6c69d59f..60ea8328 100644 --- a/src/cart/cart.cpp +++ b/src/cart/cart.cpp @@ -7,14 +7,9 @@ #include #include "cart.hpp" -#include "cart_load.cpp" -#include "cart_normal.cpp" -#include "cart_bsx.cpp" -#include "cart_bsc.cpp" -#include "cart_st.cpp" - #include "cart_file.cpp" #include "cart_header.cpp" +#include "cart_loader.cpp" namespace memory { MappedRAM cartrom, cartram, cartrtc; @@ -25,13 +20,7 @@ namespace memory { Cartridge cartridge; -const char* Cartridge::name() const { return info.filename; } -Cartridge::CartridgeMode Cartridge::mode() const { return info.mode; } -Cartridge::MemoryMapper Cartridge::mapper() const { return info.mapper; } -Cartridge::Region Cartridge::region() const { return info.region; } -bool Cartridge::loaded() const { return cart.loaded; } - -void Cartridge::load_begin(CartridgeMode mode) { +void Cartridge::load_begin(Mode cartridge_mode) { cart.rom = cart.ram = cart.rtc = 0; bs.ram = 0; stA.rom = stA.ram = 0; @@ -42,11 +31,10 @@ void Cartridge::load_begin(CartridgeMode mode) { stA.rom_size = stA.ram_size = 0; stB.rom_size = stB.ram_size = 0; - info.mode = mode; - info.patched = false; - - info.bsxcart = false; - info.bsxflash = false; + set(loaded, false); + set(bsx_flash_loaded, false); + set(patched, false); + set(mode, cartridge_mode); } void Cartridge::load_end() { @@ -67,25 +55,25 @@ void Cartridge::load_end() { memory::stBrom.write_protect(true); memory::stBram.write_protect(false); - if(file::exists(get_cheat_filename(cart.fn, "cht"))) { + string cheat_file = get_filename(cart.filename, "cht", snes.config.path.cheat); + if(file::exists(cheat_file)) { cheat.clear(); - cheat.load(cheatfn); + cheat.load(cheat_file); } - cart.loaded = true; bus.load_cart(); + set(loaded, true); } -bool Cartridge::unload() { - if(cart.loaded == false) return false; - +void Cartridge::unload() { + if(loaded() == false) return; bus.unload_cart(); - switch(info.mode) { - case ModeNormal: unload_cart_normal(); break; - case ModeBSX: unload_cart_bsx(); break; - case ModeBSC: unload_cart_bsc(); break; - case ModeSufamiTurbo: unload_cart_st(); break; + switch(mode()) { + case ModeNormal: unload_normal(); break; + case ModeBsxSlotted: unload_bsx_slotted(); break; + case ModeBsx: unload_bsx(); break; + case ModeSufamiTurbo: unload_sufami_turbo(); break; } if(cart.rom) { delete[] cart.rom; cart.rom = 0; } @@ -97,21 +85,44 @@ bool Cartridge::unload() { if(stB.rom) { delete[] stB.rom; stB.rom = 0; } if(stB.ram) { delete[] stB.ram; stB.ram = 0; } - if(cheat.count() > 0 || file::exists(get_cheat_filename(cart.fn, "cht"))) { - cheat.save(cheatfn); + string cheat_file = get_filename(cart.filename, "cht", snes.config.path.cheat); + if(cheat.count() > 0 || file::exists(cheat_file)) { + cheat.save(cheat_file); cheat.clear(); } - cart.loaded = false; - return true; + set(loaded, false); } Cartridge::Cartridge() { - cart.loaded = false; + set(loaded, false); } Cartridge::~Cartridge() { - if(cart.loaded == true) unload(); + if(loaded() == true) unload(); +} + +void Cartridge::set_cartinfo(const Cartridge::cartinfo_t &source) { + set(region, source.region); + set(mapper, source.mapper); + set(dsp1_mapper, source.dsp1_mapper); + + set(has_bsx_slot, source.bsx_slot); + set(has_superfx, source.superfx); + set(has_sa1, source.sa1); + set(has_srtc, source.srtc); + set(has_sdd1, source.sdd1); + set(has_spc7110, source.spc7110); + set(has_spc7110rtc, source.spc7110rtc); + set(has_cx4, source.cx4); + set(has_dsp1, source.dsp1); + set(has_dsp2, source.dsp2); + set(has_dsp3, source.dsp3); + set(has_dsp4, source.dsp4); + set(has_obc1, source.obc1); + set(has_st010, source.st010); + set(has_st011, source.st011); + set(has_st018, source.st018); } //========== @@ -127,7 +138,7 @@ void Cartridge::cartinfo_t::reset() { rom_size = 0; ram_size = 0; - bsxslot = false; + bsx_slot = false; superfx = false; sa1 = false; srtc = false; @@ -145,46 +156,24 @@ void Cartridge::cartinfo_t::reset() { st018 = false; } -//apply cart-specific settings to current cartridge mode settings -Cartridge::info_t& Cartridge::info_t::operator=(const Cartridge::cartinfo_t &source) { - mapper = source.mapper; - dsp1_mapper = source.dsp1_mapper; - region = source.region; - - bsxslot = source.bsxslot; - superfx = source.superfx; - sa1 = source.sa1; - srtc = source.srtc; - sdd1 = source.sdd1; - spc7110 = source.spc7110; - spc7110rtc = source.spc7110rtc; - cx4 = source.cx4; - dsp1 = source.dsp1; - dsp2 = source.dsp2; - dsp3 = source.dsp3; - dsp4 = source.dsp4; - obc1 = source.obc1; - st010 = source.st010; - st011 = source.st011; - st018 = source.st018; - - return *this; +Cartridge::cartinfo_t::cartinfo_t() { + reset(); } //======= //utility //======= +//ensure file path is absolute (eg resolve relative paths) string Cartridge::filepath(const char *filename, const char *pathname) { //if no pathname, return filename as-is string file(filename); - replace(file, "\\", "/"); - if(!pathname || !*pathname) return file; + file.replace("\\", "/"); + string path = (!pathname || !*pathname) ? (const char*)snes.config.path.current : pathname; //ensure path ends with trailing '/' - string path(pathname); - replace(path, "\\", "/"); - if(!strend(path, "/")) strcat(path, "/"); + path.replace("\\", "/"); + if(!strend(path, "/")) path.append("/"); //replace relative path with absolute path if(strbegin(path, "./")) { @@ -194,6 +183,52 @@ string Cartridge::filepath(const char *filename, const char *pathname) { //remove folder part of filename lstring part; - split(part, "/", file); - return path << part[count(part) - 1]; + part.split("/", file); + return path << part[part.size() - 1]; +} + +//remove directory information and file extension ("/foo/bar.ext" -> "bar") +string Cartridge::basename(const char *filename) { + string name(filename); + + //remove extension + for(signed i = strlen(name) - 1; i >= 0; i--) { + if(name[i] == '.') { + name[i] = 0; + break; + } + } + + //remove directory information + for(signed i = strlen(name) - 1; i >= 0; i--) { + if(name[i] == '/' || name[i] == '\\') { + i++; + char *output = name(); + while(true) { + *output++ = name[i]; + if(!name[i]) break; + i++; + } + break; + } + } + + return name; +} + +//remove filename and return path only ("/foo/bar.ext" -> "/foo/bar/") +string Cartridge::basepath(const char *filename) { + string path(filename); + path.replace("\\", "/"); + + //remove filename + for(signed i = strlen(path) - 1; i >= 0; i--) { + if(path[i] == '/') { + path[i] = 0; + break; + } + } + + if(!strend(path, "/")) path.append("/"); + return path; } diff --git a/src/cart/cart.hpp b/src/cart/cart.hpp index 5b6b91d1..f2f399a2 100644 --- a/src/cart/cart.hpp +++ b/src/cart/cart.hpp @@ -1,36 +1,22 @@ -class Cartridge { +class Cartridge : public property { public: - enum CartridgeMode { + enum Mode { ModeNormal, - ModeBSC, - ModeBSX, + ModeBsxSlotted, + ModeBsx, ModeSufamiTurbo, }; - enum CartridgeType { + enum Type { TypeNormal, - TypeBSC, - TypeBSXBIOS, - TypeBSX, - TypeSufamiTurboBIOS, + TypeBsxSlotted, + TypeBsxBios, + TypeBsx, + TypeSufamiTurboBios, TypeSufamiTurbo, TypeUnknown, }; - enum HeaderField { - CartName = 0x00, - Mapper = 0x15, - RomType = 0x16, - RomSize = 0x17, - RamSize = 0x18, - CartRegion = 0x19, - Company = 0x1a, - Version = 0x1b, - Complement = 0x1c, //inverse checksum - Checksum = 0x1e, - ResetVector = 0x3c, - }; - enum Region { NTSC, PAL, @@ -55,144 +41,131 @@ public: DSP1HiROM, }; - const char* name() const; - CartridgeMode mode() const; - MemoryMapper mapper() const; - Region region() const; + //properties can be read via operator(), eg "if(cartridge.loaded() == true)"; + //warning: if loaded() == false, no other property is considered valid! - struct { - bool loaded; - char fn[PATH_MAX]; - uint8 *rom, *ram, *rtc; - unsigned rom_size, ram_size, rtc_size; - } cart; + property_t loaded; //is a base cartridge inserted? + property_t bsx_flash_loaded; //is a BS-X flash cart connected? + property_t patched; //has a UPS patch been applied? + property_t name; //display name (filename sans path and extension) - struct { - char fn[PATH_MAX]; - uint8 *ram; - unsigned ram_size; - } bs; + property_t mode; + property_t region; + property_t mapper; + property_t dsp1_mapper; - struct { - char fn[PATH_MAX]; - uint8 *rom, *ram; - unsigned rom_size, ram_size; - } stA, stB; + property_t has_bsx_slot; + property_t has_superfx; + property_t has_sa1; + property_t has_srtc; + property_t has_sdd1; + property_t has_spc7110, has_spc7110rtc; + property_t has_cx4; + property_t has_dsp1, has_dsp2, has_dsp3, has_dsp4; + property_t has_obc1; + property_t has_st010, has_st011, has_st018; + + //main interface + bool load_normal (const char *base); + bool load_bsx_slotted (const char *base, const char *slot = ""); + bool load_bsx (const char *base, const char *slot = ""); + bool load_sufami_turbo(const char *base, const char *slotA = "", const char *slotB = ""); + void unload(); + + //utility functions + static string filepath(const char *filename, const char *pathname); //"./bar.ext" -> "/foo/bar.ext" + static string basename(const char *filename); //"/foo/bar.ext" -> "bar" + static string basepath(const char *filename); //"/foo/bar.ext" -> "/foo/bar/" + //this function will load 'filename', decompress it if needed, and determine what type of + //image file 'filename' refers to (eg normal cart, BS-X flash cart, Sufami Turbo cart, etc.) + //warning: this operation is very expensive, use sparingly! + Type detect_image_type(const char *filename) const; + + Cartridge(); + ~Cartridge(); + +private: + void load_begin(Mode); + void load_end(); + void unload_normal(); + void unload_bsx_slotted(); + void unload_bsx(); + void unload_sufami_turbo(); struct cartinfo_t { - CartridgeType type; + Type type; + Region region; MemoryMapper mapper; DSP1MemoryMapper dsp1_mapper; - Region region; + unsigned rom_size, ram_size; - unsigned rom_size; - unsigned ram_size; - - bool bsxslot; + bool bsx_slot; bool superfx; bool sa1; bool srtc; bool sdd1; - bool spc7110; - bool spc7110rtc; + bool spc7110, spc7110rtc; bool cx4; - bool dsp1; - bool dsp2; - bool dsp3; - bool dsp4; + bool dsp1, dsp2, dsp3, dsp4; bool obc1; - bool st010; - bool st011; - bool st018; + bool st010, st011, st018; void reset(); + cartinfo_t(); }; - struct info_t { - char filename[PATH_MAX * 4]; - bool patched; + enum HeaderField { + CartName = 0x00, + Mapper = 0x15, + RomType = 0x16, + RomSize = 0x17, + RamSize = 0x18, + CartRegion = 0x19, + Company = 0x1a, + Version = 0x1b, + Complement = 0x1c, //inverse checksum + Checksum = 0x1e, + ResetVector = 0x3c, + }; - CartridgeMode mode; - MemoryMapper mapper; - DSP1MemoryMapper dsp1_mapper; - Region region; + void read_header(cartinfo_t &info, const uint8_t *data, unsigned size) const; + unsigned find_header(const uint8_t *data, unsigned size) const; + unsigned score_header(const uint8_t *data, unsigned size, unsigned addr) const; + void set_cartinfo(const cartinfo_t&); - bool bsxcart; //is BS-X cart inserted? - bool bsxflash; //is BS-X flash cart inserted into BS-X cart? - - bool bsxslot; - bool superfx; - bool sa1; - bool srtc; - bool sdd1; - bool spc7110; - bool spc7110rtc; - bool cx4; - bool dsp1; - bool dsp2; - bool dsp3; - bool dsp4; - bool obc1; - bool st010; - bool st011; - bool st018; - - info_t& operator=(const cartinfo_t&); - } info; - - struct { - char fn[PATH_MAX]; - uint8_t *data; - unsigned size; - } image; - bool load_image(const char*); - bool inspect_image(cartinfo_t &cartinfo, const char *filename); - bool load_ram(const char *filename, uint8_t *&data, unsigned size, uint8_t init); - - void load_cart_normal(const char*); - void load_cart_bsc(const char*, const char*); - void load_cart_bsx(const char*, const char*); - void load_cart_st(const char*, const char*, const char*); - - void unload_cart_normal(); - void unload_cart_bsx(); - void unload_cart_bsc(); - void unload_cart_st(); - - bool loaded() const; - void load_begin(CartridgeMode); - void load_end(); - bool unload(); - - void read_header(cartinfo_t &info, const uint8_t *data, unsigned size); - unsigned find_header(const uint8_t *data, unsigned size); - unsigned score_header(const uint8_t *data, unsigned size, unsigned addr); + bool load_image(const char *filename, uint8_t *&data, unsigned &size, bool &patched) const; + bool load_ram (const char *filename, uint8_t *&data, unsigned size, uint8_t init_value) const; enum CompressionMode { CompressionNone, //always load without compression CompressionInspect, //use file header inspection CompressionAuto, //use file extension or file header inspection (configured by user) }; - bool load_file(const char *fn, uint8 *&data, unsigned &size, CompressionMode compression = CompressionNone); - bool save_file(const char *fn, uint8 *data, unsigned size); - bool apply_patch(const uint8_t *pdata, unsigned psize, uint8_t *&data, unsigned &size); - char* modify_extension(char *filename, const char *extension); - char* get_base_filename(char *filename); - char* get_path_filename(char *filename, const char *path, const char *source, const char *extension); - char* get_patch_filename(const char *source, const char *extension); - char* get_save_filename(const char *source, const char *extension); - char* get_cheat_filename(const char *source, const char *extension); - static string filepath(const char *filename, const char *pathname); + bool load_file(const char *fn, uint8 *&data, unsigned &size, CompressionMode compression = CompressionNone) const; + bool save_file(const char *fn, uint8 *data, unsigned size) const; + bool apply_patch(const uint8_t *pdata, unsigned psize, uint8_t *&data, unsigned &size) const; - Cartridge(); - ~Cartridge(); + string modify_extension(const char *filename, const char *extension) const; + string get_filename(const char *source, const char *extension, const char *path) const; -private: - char patchfn[PATH_MAX]; - char savefn[PATH_MAX]; - char rtcfn[PATH_MAX]; - char cheatfn[PATH_MAX]; + struct { + string filename; + uint8_t *rom, *ram, *rtc; + unsigned rom_size, ram_size, rtc_size; + } cart; + + struct { + string filename; + uint8_t *ram; + unsigned ram_size; + } bs; + + struct { + string filename; + uint8_t *rom, *ram; + unsigned rom_size, ram_size; + } stA, stB; }; namespace memory { diff --git a/src/cart/cart_bsc.cpp b/src/cart/cart_bsc.cpp deleted file mode 100644 index 218be630..00000000 --- a/src/cart/cart_bsc.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#ifdef CART_CPP - -void Cartridge::load_cart_bsc(const char *base, const char *slot) { - uint8_t *data; - unsigned size; - strcpy(cart.fn, base); - strcpy(bs.fn, slot); - - load_begin(ModeBSC); - if(load_image(base) == false) return; - - cartinfo_t cartinfo; - read_header(cartinfo, cart.rom = image.data, cart.rom_size = image.size); - info = cartinfo; - - if(load_image(slot) == true) { - info.bsxflash = true; - bs.ram = image.data; - bs.ram_size = image.size; - } - - if(cartinfo.ram_size > 0) { - load_ram(get_save_filename(base, "srm"), cart.ram, cart.ram_size = cartinfo.ram_size, 0xff); - } - - load_end(); - - //set base filename - strcpy(info.filename, base); - get_base_filename(info.filename); - if(*slot) { - char filenameBS[PATH_MAX]; - strcpy(filenameBS, slot); - get_base_filename(filenameBS); - strcat(info.filename, " + "); - strcat(info.filename, filenameBS); - } -} - -void Cartridge::unload_cart_bsc() { - if(cart.ram) save_file(get_save_filename(cart.fn, "srm"), cart.ram, cart.ram_size); -} - -#endif diff --git a/src/cart/cart_bsx.cpp b/src/cart/cart_bsx.cpp deleted file mode 100644 index 91774236..00000000 --- a/src/cart/cart_bsx.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#ifdef CART_CPP - -void Cartridge::load_cart_bsx(const char *base, const char *slot) { - uint8_t *data; - unsigned size; - strcpy(cart.fn, base); - strcpy(bs.fn, slot); - - load_begin(ModeBSX); - if(load_image(base) == false) return; - info.bsxcart = true; - - cartinfo_t cartinfo; - read_header(cartinfo, cart.rom = image.data, cart.rom_size = image.size); - info = cartinfo; - cart.ram = 0; - cart.ram_size = 0; - - memset(bsxcart.sram.handle (), 0x00, bsxcart.sram.size ()); - memset(bsxcart.psram.handle(), 0x00, bsxcart.psram.size()); - - if(load_file(get_save_filename(cart.fn, "srm"), data, size, CompressionNone) == true) { - memcpy(bsxcart.sram.handle (), data, min(bsxcart.sram.size (), size)); - delete[] data; - } - - if(load_file(get_save_filename(cart.fn, "psr"), data, size, CompressionNone) == true) { - memcpy(bsxcart.psram.handle(), data, min(bsxcart.psram.size(), size)); - delete[] data; - } - - if(load_image(slot)) { - info.bsxflash = true; - bs.ram = image.data; - bs.ram_size = image.size; - } - - load_end(); - - strcpy(info.filename, !*slot ? base : slot); - get_base_filename(info.filename); -} - -void Cartridge::unload_cart_bsx() { - save_file(get_save_filename(cart.fn, "srm"), bsxcart.sram.handle (), bsxcart.sram.size ()); - save_file(get_save_filename(cart.fn, "psr"), bsxcart.psram.handle(), bsxcart.psram.size()); -} - -#endif diff --git a/src/cart/cart_file.cpp b/src/cart/cart_file.cpp index f55eaf69..14102299 100644 --- a/src/cart/cart_file.cpp +++ b/src/cart/cart_file.cpp @@ -11,66 +11,23 @@ #include "../reader/jmareader.hpp" #endif -char* Cartridge::modify_extension(char *filename, const char *extension) { +string Cartridge::modify_extension(const char *filename_, const char *extension) const { + string filename = filename_; int i; for(i = strlen(filename); i >= 0; i--) { - if(filename[i] == '.') break; - if(filename[i] == '/') break; + if(filename[i] == '.') break; + if(filename[i] == '/') break; if(filename[i] == '\\') break; } if(i > 0 && filename[i] == '.') filename[i] = 0; - strcat(filename, "."); - strcat(filename, extension); - return filename; + return filename << "." << extension; } -//remove directory information and file extension ("/foo/bar.ext" -> "bar") -char* Cartridge::get_base_filename(char *filename) { - //remove extension - for(int i = strlen(filename) - 1; i >= 0; i--) { - if(filename[i] == '.') { - filename[i] = 0; - break; - } - } - - //remove directory information - for(int i = strlen(filename) - 1; i >= 0; i--) { - if(filename[i] == '/' || filename[i] == '\\') { - i++; - char *output = filename; - while(true) { - *output++ = filename[i]; - if(!filename[i]) break; - i++; - } - break; - } - } - - return filename; +string Cartridge::get_filename(const char *source, const char *extension, const char *path) const { + return filepath(modify_extension(source, extension), path); } -char* Cartridge::get_path_filename(char *filename, const char *path, const char *source, const char *extension) { - strcpy(filename, source); - modify_extension(filename, extension); - strcpy(filename, filepath(filename, path)); - return filename; -} - -char* Cartridge::get_patch_filename(const char *source, const char *extension) { - return get_path_filename(patchfn, snes.config.path.patch, source, extension); -} - -char* Cartridge::get_save_filename(const char *source, const char *extension) { - return get_path_filename(savefn, snes.config.path.save, source, extension); -} - -char* Cartridge::get_cheat_filename(const char *source, const char *extension) { - return get_path_filename(cheatfn, snes.config.path.cheat, source, extension); -} - -bool Cartridge::load_file(const char *fn, uint8 *&data, unsigned &size, CompressionMode compression) { +bool Cartridge::load_file(const char *fn, uint8 *&data, unsigned &size, CompressionMode compression) const { if(file::exists(fn) == false) return false; Reader::Type filetype = Reader::Normal; @@ -117,7 +74,7 @@ bool Cartridge::load_file(const char *fn, uint8 *&data, unsigned &size, Compress return true; } -bool Cartridge::apply_patch(const uint8_t *pdata, const unsigned psize, uint8_t *&data, unsigned &size) { +bool Cartridge::apply_patch(const uint8_t *pdata, const unsigned psize, uint8_t *&data, unsigned &size) const { uint8_t *outdata = 0; unsigned outsize; ups patcher; @@ -126,7 +83,7 @@ bool Cartridge::apply_patch(const uint8_t *pdata, const unsigned psize, uint8_t bool apply = false; if(result == ups::ok) apply = true; if(snes.config.file.bypass_patch_crc32 == true) { - if(result == ups::input_crc32_invalid) apply = true; + if(result == ups::input_crc32_invalid) apply = true; if(result == ups::output_crc32_invalid) apply = true; } @@ -141,7 +98,7 @@ bool Cartridge::apply_patch(const uint8_t *pdata, const unsigned psize, uint8_t return apply; } -bool Cartridge::save_file(const char *fn, uint8 *data, unsigned size) { +bool Cartridge::save_file(const char *fn, uint8 *data, unsigned size) const { file fp; if(!fp.open(fn, file::mode_write)) return false; fp.write(data, size); diff --git a/src/cart/cart_header.cpp b/src/cart/cart_header.cpp index 05e201dc..b99a1d4c 100644 --- a/src/cart/cart_header.cpp +++ b/src/cart/cart_header.cpp @@ -1,6 +1,6 @@ #ifdef CART_CPP -void Cartridge::read_header(cartinfo_t &info, const uint8_t *data, unsigned size) { +void Cartridge::read_header(cartinfo_t &info, const uint8_t *data, unsigned size) const { info.reset(); unsigned index = find_header(data, size); @@ -13,7 +13,7 @@ void Cartridge::read_header(cartinfo_t &info, const uint8_t *data, unsigned size const uint8_t n15 = data[index + 0x15]; if(n15 == 0x00 || n15 == 0x80 || n15 == 0x84 || n15 == 0x9c || n15 == 0xbc || n15 == 0xfc) { if(data[index + 0x1a] == 0x33 || data[index + 0x1a] == 0xff) { - info.type = TypeBSX; + info.type = TypeBsx; info.mapper = BSXROM; info.region = NTSC; //BS-X only released in Japan return; @@ -28,7 +28,7 @@ void Cartridge::read_header(cartinfo_t &info, const uint8_t *data, unsigned size if(!memcmp(data, "BANDAI SFC-ADX", 14)) { if(!memcmp(data + 16, "SFC-ADX BACKUP", 14)) { - info.type = TypeSufamiTurboBIOS; + info.type = TypeSufamiTurboBios; } else { info.type = TypeSufamiTurbo; } @@ -53,22 +53,21 @@ void Cartridge::read_header(cartinfo_t &info, const uint8_t *data, unsigned size uint8 n13 = data[index - 13]; if((n13 >= 'A' && n13 <= 'Z') || (n13 >= '0' && n13 <= '9')) { if(company == 0x33 || (data[index - 10] == 0x00 && data[index - 4] == 0x00)) { - info.bsxslot = true; + info.bsx_slot = true; } } } } - if(info.bsxslot == true) { + if(info.bsx_slot == true) { if(!memcmp(data + index, "Satellaview BS-X ", 21)) { //BS-X base cart - info.type = TypeBSXBIOS; + info.type = TypeBsxBios; info.mapper = BSXROM; info.region = NTSC; //BS-X only released in Japan return; //RAM size handled internally by load_cart_bsx() -> BSXCart class } else { - //BS-X slotted cart - info.type = TypeBSC; + info.type = TypeBsxSlotted; info.mapper = (index == 0x7fc0 ? BSCLoROM : BSCHiROM); } } else { @@ -174,7 +173,7 @@ void Cartridge::read_header(cartinfo_t &info, const uint8_t *data, unsigned size info.region = (region <= 1 || region >= 13) ? NTSC : PAL; } -unsigned Cartridge::find_header(const uint8_t *data, unsigned size) { +unsigned Cartridge::find_header(const uint8_t *data, unsigned size) const { unsigned score_lo = score_header(data, size, 0x007fc0); unsigned score_hi = score_header(data, size, 0x00ffc0); unsigned score_ex = score_header(data, size, 0x40ffc0); @@ -189,7 +188,7 @@ unsigned Cartridge::find_header(const uint8_t *data, unsigned size) { } } -unsigned Cartridge::score_header(const uint8_t *data, unsigned size, unsigned addr) { +unsigned Cartridge::score_header(const uint8_t *data, unsigned size, unsigned addr) const { if(size < addr + 64) return 0; //image too small to contain header at this location? int score = 0; diff --git a/src/cart/cart_load.cpp b/src/cart/cart_load.cpp deleted file mode 100644 index 4a4037ec..00000000 --- a/src/cart/cart_load.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#ifdef CART_CPP - -bool Cartridge::load_image(const char *filename) { - if(!filename || !*filename) return false; - - uint8_t *data; - unsigned size; - if(!load_file(filename, data, size, CompressionAuto)) return false; - - if((size & 0x7fff) != 512) { - image.data = data; - image.size = size; - } else { - //remove 512-byte header - image.data = new uint8_t[image.size = size - 512]; - memcpy(image.data, data + 512, image.size); - } - - if(load_file(get_patch_filename(filename, "ups"), data, size, CompressionInspect) == true) { - apply_patch(data, size, image.data, image.size); - delete[] data; - info.patched = true; - } - - return true; -} - -bool Cartridge::inspect_image(cartinfo_t &cartinfo, const char *filename) { - cartinfo.reset(); - if(!load_image(filename)) return false; - - read_header(cartinfo, image.data, image.size); - delete[] image.data; - return true; -} - -bool Cartridge::load_ram(const char *filename, uint8_t *&data, unsigned size, uint8_t init) { - data = new uint8_t[size]; - memset(data, init, size); - - uint8_t *savedata; - unsigned savesize; - if(load_file(filename, savedata, savesize, CompressionNone) == false) return false; - - memcpy(data, savedata, min(size, savesize)); - delete[] savedata; - return true; -} - -#endif diff --git a/src/cart/cart_loader.cpp b/src/cart/cart_loader.cpp new file mode 100644 index 00000000..8627221d --- /dev/null +++ b/src/cart/cart_loader.cpp @@ -0,0 +1,244 @@ +#ifdef CART_CPP + +//================ +//Normal cartridge +//================ + +bool Cartridge::load_normal(const char *base) { + uint8_t *data; + unsigned size; + bool patch_applied; + cart.filename = base; + + load_begin(ModeNormal); + if(load_image(base, data, size, patch_applied) == false) return false; + + snes.config.path.current = basepath(cart.filename); + if(patch_applied) set(patched, true); + + cartinfo_t cartinfo; + read_header(cartinfo, cart.rom = data, cart.rom_size = size); + set_cartinfo(cartinfo); + + if(cartinfo.ram_size > 0) { + load_ram(get_filename(base, "srm", snes.config.path.save), cart.ram, cart.ram_size = cartinfo.ram_size, 0xff); + } + + if(cartinfo.srtc || cartinfo.spc7110rtc) { + load_ram(get_filename(base, "rtc", snes.config.path.save), cart.rtc, cart.rtc_size = 20, 0x00); + } + + load_end(); + set(name, basename(base)); + return true; +} + +void Cartridge::unload_normal() { + if(cart.ram) save_file(get_filename(cart.filename, "srm", snes.config.path.save), cart.ram, cart.ram_size); + if(cart.rtc) save_file(get_filename(cart.filename, "rtc", snes.config.path.save), cart.rtc, cart.rtc_size); +} + +//====================== +//BS-X slotted cartridge +//====================== + +bool Cartridge::load_bsx_slotted(const char *base, const char *slot) { + uint8_t *data; + unsigned size; + bool patch_applied; + cart.filename = base; + bs.filename = slot; + + load_begin(ModeBsxSlotted); + if(load_image(base, data, size, patch_applied) == false) return false; + + snes.config.path.current = basepath(cart.filename); + if(patch_applied) set(patched, true); + + cartinfo_t cartinfo; + read_header(cartinfo, cart.rom = data, cart.rom_size = size); + set_cartinfo(cartinfo); + + if(load_image(slot, data, size, patch_applied) == true) { + set(bsx_flash_loaded, true); + if(patch_applied) set(patched, true); + bs.ram = data; + bs.ram_size = size; + } + + if(cartinfo.ram_size > 0) { + load_ram(get_filename(base, "srm", snes.config.path.save), cart.ram, cart.ram_size = cartinfo.ram_size, 0xff); + } + + load_end(); + string filename = basename(base); + if(*slot) filename << " + " << basename(slot); + set(name, filename); + return true; +} + +void Cartridge::unload_bsx_slotted() { + if(cart.ram) save_file(get_filename(cart.filename, "srm", snes.config.path.save), cart.ram, cart.ram_size); +} + +//==================== +//BS-X flash cartridge +//==================== + +bool Cartridge::load_bsx(const char *base, const char *slot) { + uint8_t *data; + unsigned size; + bool patch_applied; + cart.filename = base; + bs.filename = slot; + + load_begin(ModeBsx); + if(load_image(base, data, size, patch_applied) == false) return false; + + snes.config.path.current = basepath(cart.filename); + if(patch_applied) set(patched, true); + + cartinfo_t cartinfo; + read_header(cartinfo, cart.rom = data, cart.rom_size = size); + set_cartinfo(cartinfo); + + cart.ram = 0; + cart.ram_size = 0; + + memset(bsxcart.sram.handle (), 0x00, bsxcart.sram.size ()); + memset(bsxcart.psram.handle(), 0x00, bsxcart.psram.size()); + + if(load_file(get_filename(base, "srm", snes.config.path.save), data, size, CompressionNone) == true) { + memcpy(bsxcart.sram.handle (), data, min(bsxcart.sram.size (), size)); + delete[] data; + } + + if(load_file(get_filename(base, "psr", snes.config.path.save), data, size, CompressionNone) == true) { + memcpy(bsxcart.psram.handle(), data, min(bsxcart.psram.size(), size)); + delete[] data; + } + + if(load_image(slot, data, size, patch_applied)) { + set(bsx_flash_loaded, true); + if(patch_applied) set(patched, true); + bs.ram = data; + bs.ram_size = size; + } + + load_end(); + set(name, !*slot ? basename(base) : basename(slot)); + return true; +} + +void Cartridge::unload_bsx() { + save_file(get_filename(cart.filename, "srm", snes.config.path.save), bsxcart.sram.handle (), bsxcart.sram.size ()); + save_file(get_filename(cart.filename, "psr", snes.config.path.save), bsxcart.psram.handle(), bsxcart.psram.size()); +} + +//============================ +//Sufami Turbo flash cartridge +//============================ + +bool Cartridge::load_sufami_turbo(const char *base, const char *slotA, const char *slotB) { + uint8_t *data; + unsigned size; + bool patch_applied; + cart.filename = base; + stA.filename = slotA; + stB.filename = slotB; + + load_begin(ModeSufamiTurbo); + if(load_image(base, data, size, patch_applied) == false) return false; + + snes.config.path.current = basepath(cart.filename); + if(patch_applied) set(patched, true); + + cartinfo_t cartinfo; + read_header(cartinfo, cart.rom = data, cart.rom_size = size); + set_cartinfo(cartinfo); + + if(load_image(slotA, data, size, patch_applied)) { + if(patch_applied) set(patched, true); + stA.rom = new(zeromemory) uint8_t[stA.rom_size = 0x100000]; + memcpy(stA.rom, data, min(size, stA.rom_size)); + delete[] data; + + load_ram(get_filename(slotA, "srm", snes.config.path.save), stA.ram, stA.ram_size = 0x020000, 0xff); + } + + if(load_image(slotB, data, size, patch_applied)) { + if(patch_applied) set(patched, true); + stB.rom = new(zeromemory) uint8_t[stB.rom_size = 0x100000]; + memcpy(stB.rom, data, min(size, stB.rom_size)); + delete[] data; + + load_ram(get_filename(slotB, "srm", snes.config.path.save), stB.ram, stB.ram_size = 0x020000, 0xff); + } + + load_end(); + string filename; + if(!*slotA && !*slotB) filename << basename(base); + else if( *slotA && !*slotB) filename << basename(slotA); + else if(!*slotA && *slotB) filename << basename(slotB); + else filename << basename(slotA) << " + " << basename(slotB); + set(name, filename); + return true; +} + +void Cartridge::unload_sufami_turbo() { + if(stA.ram) save_file(get_filename(stA.filename, "srm", snes.config.path.save), stA.ram, stA.ram_size); + if(stB.ram) save_file(get_filename(stB.filename, "srm", snes.config.path.save), stB.ram, stB.ram_size); +} + +//================= +//utility functions +//================= + +Cartridge::Type Cartridge::detect_image_type(const char *filename) const { + uint8_t *data; + unsigned size; + bool patch_applied; + if(!load_image(filename, data, size, patch_applied)) return TypeUnknown; + + cartinfo_t info; + read_header(info, data, size); + delete[] data; + return info.type; +} + +bool Cartridge::load_image(const char *filename, uint8_t *&data, unsigned &size, bool &patched) const { + if(!filename || !*filename) return false; + if(!load_file(filename, data, size, CompressionAuto)) return false; + + if((size & 0x7fff) == 512) { + //remove 512-byte header + memmove(data, data + 512, size -= 512); + } + + uint8_t *pdata; + unsigned psize; + if(load_file(get_filename(filename, "ups", snes.config.path.patch), pdata, psize, CompressionInspect) == true) { + apply_patch(pdata, psize, data, size); + delete[] pdata; + patched = true; + } else { + patched = false; + } + + return true; +} + +bool Cartridge::load_ram(const char *filename, uint8_t *&data, unsigned size, uint8_t init) const { + data = new uint8_t[size]; + memset(data, init, size); + + uint8_t *savedata; + unsigned savesize; + if(load_file(filename, savedata, savesize, CompressionNone) == false) return false; + + memcpy(data, savedata, min(size, savesize)); + delete[] savedata; + return true; +} + +#endif diff --git a/src/cart/cart_normal.cpp b/src/cart/cart_normal.cpp deleted file mode 100644 index 79861f3f..00000000 --- a/src/cart/cart_normal.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#ifdef CART_CPP - -void Cartridge::load_cart_normal(const char *base) { - uint8_t *data; - unsigned size; - strcpy(cart.fn, base); - - load_begin(ModeNormal); - if(load_image(base) == false) return; - - cartinfo_t cartinfo; - read_header(cartinfo, cart.rom = image.data, cart.rom_size = image.size); - info = cartinfo; - - if(cartinfo.ram_size > 0) { - load_ram(get_save_filename(base, "srm"), cart.ram, cart.ram_size = cartinfo.ram_size, 0xff); - } - - if(cartinfo.srtc || cartinfo.spc7110rtc) { - load_ram(get_save_filename(base, "rtc"), cart.rtc, cart.rtc_size = 20, 0x00); - } - - load_end(); - - //set base filename - strcpy(info.filename, base); - get_base_filename(info.filename); -} - -void Cartridge::unload_cart_normal() { - if(cart.ram) save_file(get_save_filename(cart.fn, "srm"), cart.ram, cart.ram_size); - if(cart.rtc) save_file(get_save_filename(cart.fn, "rtc"), cart.rtc, cart.rtc_size); -} - -#endif diff --git a/src/cart/cart_st.cpp b/src/cart/cart_st.cpp deleted file mode 100644 index 0c5fc7af..00000000 --- a/src/cart/cart_st.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#ifdef CART_CPP - -void Cartridge::load_cart_st(const char *base, const char *slotA, const char *slotB) { - uint8_t *data; - unsigned size; - strcpy(cart.fn, base); - strcpy(stA.fn, slotA); - strcpy(stB.fn, slotB); - - load_begin(ModeSufamiTurbo); - if(load_image(base) == false) return; - - cartinfo_t cartinfo; - read_header(cartinfo, cart.rom = image.data, cart.rom_size = image.size); - info = cartinfo; - - if(load_image(slotA)) { - stA.rom = new(zeromemory) uint8_t[stA.rom_size = 0x100000]; - memcpy(stA.rom, image.data, min(image.size, stA.rom_size)); - delete[] image.data; - - load_ram(get_save_filename(slotA, "srm"), stA.ram, stA.ram_size = 0x020000, 0xff); - } - - if(load_image(slotB)) { - stB.rom = new(zeromemory) uint8_t[stB.rom_size = 0x100000]; - memcpy(stB.rom, image.data, min(image.size, stB.rom_size)); - delete[] image.data; - - load_ram(get_save_filename(slotB, "srm"), stB.ram, stB.ram_size = 0x020000, 0xff); - } - - load_end(); - - //set base filename - if(!*slotA && !*slotB) { - strcpy(info.filename, cart.fn); - get_base_filename(info.filename); - } else if(*slotA && !*slotB) { - strcpy(info.filename, slotA); - get_base_filename(info.filename); - } else if(!*slotA && *slotB) { - strcpy(info.filename, slotB); - get_base_filename(info.filename); - } else { - char filenameA[PATH_MAX], filenameB[PATH_MAX]; - strcpy(filenameA, slotA); - get_base_filename(filenameA); - strcpy(filenameB, slotB); - get_base_filename(filenameB); - strcpy(info.filename, filenameA); - strcat(info.filename, " + "); - strcat(info.filename, filenameB); - } -} - -void Cartridge::unload_cart_st() { - if(stA.ram) save_file(get_save_filename(stA.fn, "srm"), stA.ram, stA.ram_size); - if(stB.ram) save_file(get_save_filename(stB.fn, "srm"), stB.ram, stB.ram_size); -} - -#endif diff --git a/src/cheat/cheat.cpp b/src/cheat/cheat.cpp index 30da603b..b6f95e33 100644 --- a/src/cheat/cheat.cpp +++ b/src/cheat/cheat.cpp @@ -1,5 +1,4 @@ #include <../base.hpp> -#include Cheat cheat; @@ -31,7 +30,7 @@ bool Cheat::decode(const char *s, Cheat::cheat_t &item) const { item.count = 0; lstring list; - split(list, "+", s); + list.split("+", s); for(unsigned n = 0; n < list.size(); n++) { unsigned addr; @@ -67,6 +66,28 @@ bool Cheat::read(unsigned addr, uint8_t &data) const { return false; } +//============== +//master control +//============== + +//global cheat system enable/disable: +//if disabled, *all* cheat codes are disabled; +//otherwise only individually disabled codes are. + +bool Cheat::enabled() const { + return cheat_system_enabled; +} + +void Cheat::enable() { + cheat_system_enabled = true; + cheat_enabled = (cheat_system_enabled && cheat_enabled_code_exists); +} + +void Cheat::disable() { + cheat_system_enabled = false; + cheat_enabled = false; +} + //================================ //cheat list manipulation routines //================================ @@ -159,15 +180,15 @@ void Cheat::disable(unsigned i) { bool Cheat::load(const char *fn) { string data; - if(!fread(data, fn)) return false; - replace(data, "\r\n", "\n"); - qreplace(data, " ", ""); + if(!data.readfile(fn)) return false; + data.replace("\r\n", "\n"); + data.qreplace(" ", ""); lstring line; - split(line, "\n", data); + line.split("\n", data); for(unsigned i = 0; i < line.size(); i++) { lstring part; - qsplit(part, ",", line[i]); + part.qsplit(",", line[i]); if(part.size() != 3) continue; trim(part[0], "\""); add(part[1] == "enabled", /* code = */ part[2], /* desc = */ part[0]); @@ -189,22 +210,13 @@ bool Cheat::save(const char *fn) const { return true; } -void Cheat::sort() { - if(code.size() <= 1) return; //nothing to sort? - cheat_t *buffer = new cheat_t[code.size()]; - for(unsigned i = 0; i < code.size(); i++) buffer[i] = code[i]; - nall::sort(buffer, code.size()); - for(unsigned i = 0; i < code.size(); i++) code[i] = buffer[i]; - delete[] buffer; -} - void Cheat::clear() { - cheat_system_enabled = false; + cheat_enabled_code_exists = false; memset(mask, 0, 0x200000); code.reset(); } -Cheat::Cheat() { +Cheat::Cheat() : cheat_system_enabled(true) { clear(); } @@ -294,17 +306,18 @@ bool Cheat::encode(string &s, unsigned addr, uint8_t data, type_t type) const { } } -//update_cheat_status() will scan to see if any codes are -//enabled. if any are, make sure the cheat system is on. -//otherwise, turn cheat system off to speed up emulation. +//speed up S-CPU memory reads by disabling cheat code lookup when either: +//a) cheat system is disabled by user, or b) no enabled cheat codes exist void Cheat::update_cheat_status() { for(unsigned i = 0; i < code.size(); i++) { if(code[i].enabled) { - cheat_system_enabled = true; + cheat_enabled_code_exists = true; + cheat_enabled = (cheat_system_enabled && cheat_enabled_code_exists); return; } } - cheat_system_enabled = false; + cheat_enabled_code_exists = false; + cheat_enabled = false; } //address lookup table manipulation and mirroring @@ -367,13 +380,13 @@ void Cheat::clear(unsigned addr) { //these two functions are used to safely store description text inside .cfg file format. string& Cheat::encode_description(string &desc) const { - replace(desc, "\"", "\\q"); - replace(desc, "\n", "\\n"); + desc.replace("\"", "\\q"); + desc.replace("\n", "\\n"); return desc; } string& Cheat::decode_description(string &desc) const { - replace(desc, "\\q", "\""); - replace(desc, "\\n", "\n"); + desc.replace("\\q", "\""); + desc.replace("\\n", "\n"); return desc; } diff --git a/src/cheat/cheat.hpp b/src/cheat/cheat.hpp index 9cc92a40..58fa8b6c 100644 --- a/src/cheat/cheat.hpp +++ b/src/cheat/cheat.hpp @@ -21,8 +21,12 @@ public: bool decode(const char *s, cheat_t &item) const; bool read(unsigned addr, uint8_t &data) const; - inline bool enabled() const { return cheat_system_enabled; } + bool enabled() const; + void enable(); + void disable(); + inline unsigned count() const { return code.size(); } + inline bool active() const { return cheat_enabled; } inline bool exists(unsigned addr) const { return mask[addr >> 3] & 1 << (addr & 7); } bool add(bool enable, const char *code, const char *desc); @@ -36,14 +40,15 @@ public: bool load(const char *fn); bool save(const char *fn) const; - - void sort(); void clear(); Cheat(); private: + bool cheat_enabled; //cheat_enabled == (cheat_enabled_code_exists && cheat_system_enabled); + bool cheat_enabled_code_exists; bool cheat_system_enabled; + uint8_t mask[0x200000]; vector code; diff --git a/src/chip/dsp1/dsp1.cpp b/src/chip/dsp1/dsp1.cpp index 1e67af74..323c88be 100644 --- a/src/chip/dsp1/dsp1.cpp +++ b/src/chip/dsp1/dsp1.cpp @@ -28,7 +28,7 @@ void DSP1::reset() { * of expected ranges *****/ bool DSP1::addr_decode(uint16 addr) { - switch(cartridge.info.dsp1_mapper) { + switch(cartridge.dsp1_mapper()) { case Cartridge::DSP1LoROM1MB: { //$[20-3f]:[8000-bfff] = DR, $[20-3f]:[c000-ffff] = SR return (addr >= 0xc000); diff --git a/src/chip/spc7110/spc7110.cpp b/src/chip/spc7110/spc7110.cpp index c33dda1b..b5c59ae4 100644 --- a/src/chip/spc7110/spc7110.cpp +++ b/src/chip/spc7110/spc7110.cpp @@ -10,7 +10,7 @@ const unsigned SPC7110::months[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 3 void SPC7110::init() {} void SPC7110::enable() { - uint16_t limit = (cartridge.info.spc7110rtc ? 0x4842 : 0x483f); + uint16_t limit = (cartridge.has_spc7110rtc() ? 0x4842 : 0x483f); for(uint16_t i = 0x4800; i <= limit; i++) memory::mmio.map(i, *this); } @@ -74,7 +74,7 @@ void SPC7110::reset() { r4841 = 0x00; r4842 = 0x00; - if(cartridge.info.spc7110rtc) { + if(cartridge.has_spc7110rtc()) { rtc_state = RTCS_Inactive; rtc_mode = RTCM_Linear; rtc_index = 0; @@ -99,7 +99,7 @@ void SPC7110::update_time(int offset) { | (memory::cartrtc.read(17) << 8) | (memory::cartrtc.read(18) << 16) | (memory::cartrtc.read(19) << 24); - time_t current_time = time(0); + time_t current_time = time(0) - offset; //sizeof(time_t) is platform-dependent; though memory::cartrtc needs to be platform-agnostic. //yet platforms with 32-bit signed time_t will overflow every ~68 years. handle this by diff --git a/src/cpu/cpu.cpp b/src/cpu/cpu.cpp index 9c634fa8..0d10758a 100644 --- a/src/cpu/cpu.cpp +++ b/src/cpu/cpu.cpp @@ -3,8 +3,14 @@ #include "dcpu.cpp" +void CPU::power() { + cpu_version = snes.config.cpu.version; +} + +void CPU::reset() { +} + CPU::CPU() { - cpu_version = 2; } CPU::~CPU() { diff --git a/src/cpu/cpu.hpp b/src/cpu/cpu.hpp index aa462078..e13a4303 100644 --- a/src/cpu/cpu.hpp +++ b/src/cpu/cpu.hpp @@ -17,8 +17,8 @@ public: regs_t regs; virtual void scanline() = 0; - virtual void power() = 0; - virtual void reset() = 0; + virtual void power(); + virtual void reset(); /***** * in opcode-based CPU emulators, the main emulation routine diff --git a/src/cpu/scpu/scpu.cpp b/src/cpu/scpu/scpu.cpp index 870171c6..2927cd50 100644 --- a/src/cpu/scpu/scpu.cpp +++ b/src/cpu/scpu/scpu.cpp @@ -11,6 +11,8 @@ priority_queue event(512, bind(&sCPU::queue_event, &cpu)); #include "timing/timing.cpp" void sCPU::power() { + CPU::power(); + regs.a = regs.x = regs.y = 0x0000; regs.s = 0x01ff; @@ -22,6 +24,8 @@ void sCPU::power() { } void sCPU::reset() { + CPU::reset(); + regs.pc.d = 0x000000; regs.pc.l = bus.read(0xfffc); regs.pc.h = bus.read(0xfffd); diff --git a/src/ui/bsnes.Manifest b/src/data/bsnes.Manifest similarity index 100% rename from src/ui/bsnes.Manifest rename to src/data/bsnes.Manifest diff --git a/src/data/bsnes.desktop b/src/data/bsnes.desktop new file mode 100644 index 00000000..d527e973 --- /dev/null +++ b/src/data/bsnes.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Name=bsnes +Comment=SNES emulator +Exec=bsnes +Icon=bsnes +Terminal=false +Type=Application +Categories=Game;Emulator; diff --git a/src/data/controller.h b/src/data/controller.h deleted file mode 100644 index 90b12068..00000000 --- a/src/data/controller.h +++ /dev/null @@ -1,1039 +0,0 @@ -#if defined(_MSC_VER) -//TODO: Visual C++ cannot handle strings > 65536 bytes. Need to find workaround. -static char enc_controller[65536 * 2] = { 0 }; -#else -static char enc_controller[] = { - "_v8B8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHw" - "AfB_AfAB8AHwAfAB8AHwAeD9AP396OjomJiZAHBwcG5ubmpqAGpYWFhLS0tBAEFB" - "Ojo6MzMz8QYAy8vLOfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHw" - "AfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB" - "8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfABwAD8_PzU" - "1NR8fAB8cXFxb29vaQBpaVtbW05OTgBEREQ8PDw3N4A3UlJS3t7eOfD_AfAB8AHw" - "AfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB" - "8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_" - "AfAB8AHwAfAB8AHwAfAB8AcB8AHwAcD-_v7FxCTFcwEAcnK7CGZmAGZZWVlPT09J" - "AEhIPz4-PT0-wHh4eO_v7znwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHw" - "AfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB" - "8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8AMB8Fz0" - "r7Gwd3d3CHZ1drsIZWVmWQBaWVNSUUtLSghCQUIaDZ2dnPP88_M58AHwAfAB8AHw" - "AfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB" - "8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHw" - "AfAB8AHwAfA_AfAB8AHwAfAB8AHA-_wA_JiYmXt8e3oMeXm7CLgIW1tZUSBQTkpK" - "SFwERUXwRbGxsTbwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHw" - "AfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB" - "8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfAA9fb2hYaH" - "fX0AfHl3eHJycWcAZ2hbXFlPUlEAR0dJQUBASkrwSsLCwjbwAfAB8AHw_wHwAfAB" - "8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHw" - "AfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw" - "_wHwAfAB8AHwAfAB8AHwEf0A6erqgYCAfn4AfnZ3dnFycmkAaGlaWlhSUVAAR0ZG" - "QkJCTEzwTMrKyjnwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHw" - "AfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB" - "8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAcAA_f392tra" - "gYAAgoCBgXh3eW8Ab29laGdYWFUATk1ORkVDREOAQ1JSUtbW1jnw_wHwAfAB8AHw" - "AfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB" - "8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHw" - "AfAB8AHwAfAB8AHwAfAHAfAB8Lj42NjYgYEAgYKCgnp6em8Abm5iZGJUVVUATUtO" - "RkZGRUSARFZWVuPj4znw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB" - "8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHw" - "AfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfAHAfAB8AHw09PT" - "g4NihAMAent-uAhcBFWAV1VNTEtHSAIAgEddXV3v7-828P8B8AHwAfAB8AHwAfAB" - "8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHw" - "AfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB" - "8AHwAfAB8AHwBwHwAfAB8MzMzIWFAIWEg4R9fHxyAHJxZGNjV1VVAE9OTUhJSEpL" - "gEtoaGj29vY28P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB" - "8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_" - "AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHwBwHwAfAB8MrJyoWFAIaF" - "hIV-fX1yAHJzZWRlWVlXAFBOT0xMTE5PgE9ra2v4-Pg28P8B8AHwAfAB8AHwAfAB" - "8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHw" - "AfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB" - "8AHwAfAB8AHwBwHwAfAB8MbGxoeHAodfBH9-gHN0dABlZWZaWFdRUABQTk1NT05O" - "cuBycvn5-TbwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_" - "AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHw" - "AfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHwAwHwXASJiImFhoYA" - "f39_dHR0Z2cAZ1pbWVJRUE4ATk5QUFB3d3f_XPQB8AHwAfAB8AHwAfAB8P8B8AHw" - "AfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B" - "8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB" - "8B8B8AHwAfAB8Fw0jY2NAImJiYB_gHZ3CHZpaVwEWFJSUgBOT05QUVGAgPB--_v7" - "NvAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB" - "8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_v4B" - "8AHwAfAB8AHwAfAB4P-Q8AHwAfAB8AHwAfAB8AHwAwHwXASOjo6JiIgAgYCAd3Z3" - "aWoAalhbWlRSUk8ET09cBIGAgff3_vc28AHwAfAB8AHwAfAB8P8B8AHwevEB8AHw" - "AfAB8AHw_wHwkPAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB" - "8AHwAfAB8AHwrwHwAfAB8Fjy_QGA_AHg_4MHAfAB8AHwAfAB8AEwcsD_jWCI8gHw" - "AfAB8AHwAfAB8AMB8AFgz8_PioqKAIuLi4GBgXd2AHhpa2tcW11VDFVUEQ0BAHt7" - "e_b89vY28AHwAfAB8AHwAfD_AfAB8O_x6fEB8AHwAfAB8P8B8Onx7MHvkfLxAfAB" - "8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8L8B8AHwAfAB8FvC" - "T5L6AVD6-QEg-AGAgAcB8AEwPwP_AfAB8AHwAQBLwG9ggTCKAP-LYmX0AfAB8AHw" - "AfAB8AHwIwHwATDY2NhZBIyMAIyCg4J4eHlrAG1sX2BfWFlYgVw0dXV09fX1NvD_" - "AfAB8AHwAfAB8AHw-PH4Yf_1Ye_B4PEB8AHwAfDg8ezx_-_BATCAlPjxAfAB8AHw" - "AfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfBV8lgyR2RP" - "Yg0CDANVAQD0ASDzASDyAVDx8QHw8fHwAfAB8AHwAVD_S5BmMHIAfjCHAJ0yZfRn" - "8n8B8AHwAfAB8AHwAfBcxIgEiIhcFIGCeXl6EG1vbWFcBFhXUsRRU1wEaWlp0gA2" - "8P8B8AHwAfAB8AHwAfAEklP0__Vh8mHs8bbxAfAB8OPx7PH_7zHykfVhfTRxZGv0" - "BPIB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwU8RQ" - "lE1kVWJSMgFGMu_v7-3t7exVASDrASDqASDpAVDn3wHwAfAB8AHwAYDoASBdAI9p" - "AHIAewCEAO7u7poy_58AaPTNmAHwAfAB8AHwAfADAfABwOPj44SEhAVcFIJcFG9t" - "bWFiAGJYWFlTUlNP4FBQW1xcvQA28AHw_wHwAfAB8AHwAfBQ9PthnAD_9TFrAfUx" - "7zGJAZIxqvEB8P8B8AHw7PHvMQEAZDJwMvUx_4IC-GFjlmtkaJRl9AHwAfD_AfAB" - "8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwosZVwlCUTWRKNFJi5jH_" - "4AFzMlQ2ATDdMSX7AfAB8D8B8AHwAZBSAn4AkwDm5vDm5eXlogCoAGWU0sb_AfAB" - "8AHwAfAB8AHwAZBfAUFcZICAgHp6XARuAGVlZVpaWlNTgFRSUlJVVVXDAP858AHw" - "AfAB8AHwAfAB8J6a_1OU-zFfAaIAawFxAXcBjPH_AfAB8AHwAfAjzQwAu5hQNP-L" - "YmlmjjJrNCoAaJRj9mX0_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB" - "8P8B8AHwpvijyKA4ngGlNrMB_8IxywHXAbQzcv8pnS_NAfA_AfAB8AHwAfD6Mp8A" - "4eGA4d7e3svLy_0C_6swfALAAI4yZ5IB8AHwAfAfAfAB8AHwAcBcZIOEgwh-gX5c" - "BGhpaFkAWFhTUlRQUE_4VFRUwwA28AHwAfAB8H8B8AHwAfBRxsNjUwFZAcn8ycmi" - "AGsBcQEraAHwAfD_AfAB8AHwAWBJYsgxffRgNv-pMqgA-TZmliEAzfjK-AHw_wHw" - "AfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P9W9KmYpmhTlFJipwFfBBUA" - "_5gxU2SFYimdAWB7z-MBAfB_AfAB8AHwAcCcAKgAPQvfAN_f0NDQrKysANzc3ODg" - "4OTk_uQVAL0AzJYM8wHwAfAB8B8B8AHwAfABAFxkgYCBAHl5eXJycmdo8GdbXFuy" - "CBcNXDQ28P8B8AHwAfAB8AHwAfD5nDsBj9Y-TQFTAVkBp6enZQH_awEVAHcBkzwB" - "8AHwAfAB8P8B8AEwQzJSMgFgImtxZHdk_wkANgC9AKUAaDRea2WUYpT_x_gB8AHw" - "AfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwVvSp-CM0UzT4OvoFljP_jAFQ" - "lJdohWIQOAEAGzB4bP8JABKWAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfAf" - "AZBfASYB1CUD8P38-_78owHxMjzwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B" - "8AHwZJteMgxv0jaANDJt_5QyazQkAFYE0ALODSkHaWb_y2rH-GL0AfAB8AHwAfAB" - "8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfBW9Fb0qTioNqY4gwH_AQAMABsAI50la2M_" - "FQBFAP9Q-gHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfC_AfD-B2UBgwGS" - "MVAU_dMS_0ExUwEGAOg7PPAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB" - "8Nj_rwJKNCU7_-g4awTWOAEADAB9AYMBZQT_Mj0vPdMyYvTW8gHwAfAB8P8B8AHw" - "AfAB8AHwAfAB8AHw_wHwAfAB8AHwVvT_nPE1-Dr_OwoJAGgBUzTQaANmUMRp8P8B" - "8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfCBAQDY2Njd3d1pB_PqSWQB" - "-PmNAVwB4jLcOP_xAjzwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB" - "8OuSqQJNAd8yD29tO8d6AQ8AwQji4uL0AqML_yEwgDFTN2L0AfAB8AHwAfD_AfAB" - "8AHwAfAB8AHwAfAB8P8B8AHwAfBuYUw-LAGsmFZk_2UBYgGpyFM0lz4VAGjxNvb_" - "ZmABYKEBAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHwxwHwAfABANPT018EMgeH" - "zQXWAgEQ6-zq6wgg_OvrhgF0ARsAmwE88AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB" - "8AHwAfAB8AGQYWJzMv86-5eSJwDTAnALZZTNOBUA8WsB4-PjAAMJAH0BKT3_lzv3" - "YmMwWvMB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfBZZOczNWRoMVY0HQr_" - "UwEYAFPEBgASAFOU9M4-ZP84neiYJDbCMQHwAfAB8AHw_wHwAfAB8AHwAfAB8AHw" - "AfD_AfAB8AHA3wIB8AEAKvAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB" - "8AEweWj_AQBkOLsyamiy8rKSLQA-BP_EBQ5tVAAGAMQ4KT0PA-I4_10wwZhg8wHw" - "AfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P9Z9FmUrzg4AcoCJgcCPaEx_xE0zQW1" - "NRHKU_1TbTs0OQD_VwABALkB6z4BMELzAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHw" - "AfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfABAF5i" - "_20yhTh3NGc-czjEYseSxGL_MAADMwYABgNXAAYADwDECPjn5-d6NCQAUQDBmF_0" - "_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8K78eT4ONP99AYYBlwWdBakFf24J" - "AMQ1_6aV5JxCYIM0TTREZP7xAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHw" - "AfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwcWRr" - "NNk4aPTWkiQAAOTk5Nzc3OHhjuEJAFkECQDg4ODBCP90BE4ASwAgnXrNAfAB8AHw" - "_wHwAfAB8AHwAfAB8AHwAfD_AfAB8Fz0RGEdAZc4gwFZBP-aBaw1aASjNUEBXp7u" - "-FD0_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHw" - "AfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8Gj00DjNAh9lxAwA9wJf" - "B18Ezc3NAtEBIMzMzNra2v9fNEsAXzQ8kGzzAfAB8AHw_wHwAfAB8AHwAfAB8AHw" - "AfD_AfAB8FyUIDQdAeo_WQSgBf-jNQIKpgU7ATiRpshTlMLx_wHwAfAB8AHwAfAB" - "8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHw" - "AfAB8AHwAfAjAcCUAtfX19sDvr4gvre3t64BUKamRKaeAVCrq6sYYLzgvLzBwcEU" - "BBoELwc_UfBoZGVkyvhEZyQAysoAyrm5uampqb3gvb3f39---CoAGv3_AfAB8AHw" - "AfAB8AHwAfAB8P8B8AHwAfAB8AHwXJQmNLIFH5oFOwGjCC8B8wnS0tL4x8fHjwHt" - "aQEwiWQ-Pf-m-AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHw" - "AfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHwRwHwAcDkBtDQ0M8DrXytrT5kAfAB" - "8AHwAQCiAKKir6-vwsLC-NbW1kQHYvRlZN8Ci5gPATBQN1MESwCsrKzY_NjYKgOA" - "B180mAT6OJI0_x39AfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHw-QAOnTjx8fEF" - "DU0EqQjd3fDd6OjoI5qpyC0Aqfj_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AEA" - "7QAOAQEA__kAJQ4PADIB_zBJPhUA8zb_HD4zMAwAATAMABIArwIPMP8JADvBGzAn" - "AFpgCTA5kHEx_zwwxDsBMG8wJDCBANY-GzD_hzAbADkzM2DGAHtgEjM5AP9sMAHw" - "AfAB8AHwAfAB8AHwjwEwhQLbBnMIp6enIPQ_AfAB8AHwAfABAGgExMT-xAkJvwHH" - "-CYBx_hcNzIBgYMH1dXV2dnZXzT_lQdrMTZjX_QB8AHwAfAB8P8B8AHwAfAB8AHw" - "AfAB8AHw_1n0Dj21OA5tCD0PAFBhVmT_xzJW9AHwAfAB8AHwAfAB8P8B8AHwAfCT" - "ZhjwAfAB8K4DgRQBs7OztLS0tAaBBQHe3t7AwMB_BT-UAnMLBQckAF8HDwO_v_6_" - "DwBkAiEDJAASAAEAkQvxNQHJyckMABUAKgCNYP8JAEUAGwA5AK8CgAFRAFoAPz8w" - "5QU5AEEKHjD9BcbGHsbHAuULlAtgMMvLyxFCAMXFxQwAu7u74_ECVwDDw8OeBzAA" - "UQD_MwAJAAEAEgA2ABsAxgBfNPGoANTU1DwMYAx1AF_0fwHwAfAB8AHwAfAB8KIA" - "0-DT07a2tgX0AfAB8I8B8AHwAfABMKSkpG4x_2L0YjT9AhLzGACJB18EBgD_PgHA" - "AC0AKgBRA-s7YwNi9P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwWWS1OA7NkT7_" - "CzpHMaz4ovMB8AHwAfAB8P8B8AHwAfAB8AHA3gDPANc3HzM_JPAB8LKYOAR3d3cA" - "NTU1MTExb2_Gby4CjgIyMjKxBo4CEQkAmZmZLAFmZmaBDAA2NjY_Pz8sBAPSAw8A" - "Nzc3OTk5I5UESABDQ0NFADQ08DQ6OjrKAlYBYDB3AcBISEgwMDA2AFoD8TMA4uLi" - "CAQ_ADkAJAD4QEBAewCSBFQAKgB4AMdsACcAYDB5eXk5DHUAgXsAODg4mpqaOwQx" - "aQA7OzvxAjUEVlZGVrwBswQzMzM2MF38XV1IBvsBBwWWADYAFQD4iYmJjWkB8AHw" - "AfAB8AcB8AHwAQDa2tq-vv6-sQz28wHwAfAB8AHwAfDjAfABAKqqqmUBSwZi9P_E" - "yAYATwISAFMEbAazAWw2_18065hf9AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfDn" - "MP-1OB0BDm1cBAkAWWQ7MXo0_10DVvQB8AHwAfAB8AHwAfA_AfAB8AHwsQDAAJMD" - "j4-Aj3R0dHp6er4CP1QDLTwB8AHwAQAlAp-fAp97A87Ozm5ubghLS0sRAX9_f0J8" - "QkIvAQcIFATAAyABPBw8POcDIAQSAJCQkB8NCCQAYQhzCyoAuLi4_0UAUwSEACEA" - "7gtfAZM84QPHFwT5A7YHlJSU7QNjAD8nMBIAHgxZBP8DMgSKijCKUlJSrQFNBJGR" - "8JFJSUneANIAiwjCBBGPBNjY2B4Aubm5ETwAPj4-lgCVlZWIaGho8gGNjY1mAMDP" - "z8-Tk5NaMGYAQFtbW2NjYxUDgeCBgVBQUDwAfPUB8D8B8AHwAfAB8AEAZgmtrf6t" - "5PMB8AHwAfAB8AHwAfD_AfABAAUKLwRixAkDNQEkw_8PAOABIAoBAF80JwBfNB09" - "_1_0AfAB8AHwAfAB8AHwAfD_AfAB8AHwAfBZ9Kg58AA7NP8OPeA3Kz5WZCABVvQB" - "8AHw_wHwAfAB8AHwAfAB8AHwDWJx0gCbm5tjA0sDAWBg_GBg7QMbADPwAfD8ACAB" - "QGdnZ1RUVAsBWWBZWWFhYQkAkwNlxGVlRwGdnZ1UAHcHPyQDkQhrBPYDNAhuBIiI" - "cIheXl5EAWwAUwGAjICArwgMALW1tTMAx8MMhDCaArq6ujMA2wMxRwRycnKUCFoA" - "g4PGgzAAgQCxsbEUBIEAgSQJZGRkdnZ2tQI_IQAOBCoApwEMAIkETEw-TNYCbwAP" - "AEgJjwROTuJOdwRqamqKAPgBYgT_wQgJAC0AUQBiDW8AigAPDPEZAoaGhs0IbwBc" - "9AHwHwHwAfAB8AHAfgCpqan_1fMB8AHwAfAB8AHwAfAB8P8BwJsBlAhi9Pk8LZPO" - "AW4B__4BBgBIPySQIJ2W8wHwAfD_AfAB8AHwAfAB8AHwAfAB8P-v-FY00jkObYcz" - "CDEBAAWd_xsAWfQB8AHwAfAB8AHwAfAfAfAB8AHwAQDaAcrKyv8rCFmUATDzAzg0" - "MWI_8Fxkf8sH2AMLDboDqANwAjAAPRw9PfwDVgEyBEFBQY9JAjIExQT8Cbu7u58M" - "48MGCAR7e3uGPUoBjASIWFhYegGSkpKEAI-iDxUAYwAhAKCgoEUAxykEhQgkAIWF" - "hacB0QTHCwQeAGcIXFxcGgTfCB9BBBIJHgCWAAwAhISEj3k7yDQaNDMAfn5-MwnH" - "LAT1BI0ApaWl3QG8BPhwcHA8AOAN8QjyATkDx4oAsQAeAFNTU64wDvH_AfAB8AHw" - "AfBWlLcJyfMB8P8B8AHwAfAB8AHwAfAB8AFg_5oOsAEuYgEAI502Y1MBmAH_tQVf" - "ZFczIP1i9AHwAfAB8P8B8AHwAfAB8AHwAfAB8Fn0_1k08AAOPY4CAgEjYQX9AfD_" - "AfAB8AHwAfAB8AHwAfAB8H8B8LoA0gAFDVn0AQDUB8H8wcEZAgIBFjL5MPDwBQHA" - "6urqSEhIDg0XAcdfBAYAwzyzs7M2ANIDP0AIYgHMDK4MSwCLAlVVAFXn5-dNTU2a" - "PJqaawFFAHQEzwN1df51hgHbA1EAEgBiMUUAYg2IcXFxHgDMzMwbABi2trZmAAkA" - "c3NzYws0wgRra2tfDdQEr_yvr8UBewnGAK0NzgEtAI_1BMhkAQBWBGJiYhAF-MTE" - "xCwNBgDuAnQNmASPkACzBLkEWgCcnJwOAfjZ2dluDU4ArjBN8QHw_wHwAfAB8AEA" - "ZgwrDr3zAfD_AfAB8AHwAfAB8AHwAfAB8P9rAXMODQIj_ZWXjDr-ASow_2ADRD1f" - "9F_0AfAB8AHwAfD_AfAB8AHwAfAB8AHwWcQFMf9ZNLI4XTOvyBnyAfAB8AHwfwHw" - "AfAB8AHwAfAB8LzKofyhoUIMGwxf9DgHFAF1A4iRkZH-B4yMjNIDxxL8eTXkAzs7" - "O4EDLDH_8AxnCP4KIAQCBNkLSwAkAMcBAG4BNAhtbW3nBscI-MnJyRIANgA8AEIJ" - "oAjjKgBlBGxsbBUApTAGABGyCJaWlj8Aubm5iEtLS_kG4ODg7QMj0Q1vAF9fX3oN" - "aWl-aT4HJg2ABMgBKgDGALyMvLy5AccCT09P0gCPbADXAT8AlgCKiopuBB8VADMA" - "EgCKADAAsLCwf9UAGQXoCC0AIQCtBIQAvsS-vh4AuLi4JwNN8f8B8AHwAfABwNEH" - "MQ608wHw_wHwAfAB8AHwAfAB8AHwAfD_AZBQAf0OZ5LByF-U0AWNBv9XP5gBITBf" - "9AHwAfAB8AHw_wHwAfAB8AHwAfAB8Fn0WTT_QAJZNAIBtGluNAUBr_gB8P8B8AHw" - "AfAB8AHwAfAB8AHwfwHwATAzNuYBzANcxEgDseCxsTQ0NBWQaAQwAOP_wBcBxcXF" - "HgBEBJ82D-8HYAwnAIoPPDw8wxzDw0QBtwyrA9TU1ABGRkaVlZXR0X7RXQAvBLkE" - "mQxzCEIAYYxhYRIAfgzIyMhXAP-8NJ8AEgCOCBsJDQVzCIYB8VcJNzc3PAygAm4E" - "zgTjHgA7B0BAQG4NzgHoAv9RAEIJsQDIBIcAoQSnDVoAiN3d3b8EsrKykAD4jY2N" - "aAQIAWAAMAAlDh8zAAkAJg1HB2wAWlpaEQkAo6OjFQDT09N_GDMB8AHwAfAB8AFg" - "kAOi_KKiq_MB8AHwAfAB8AHwfwHwAfAB8AHwAfABAAAP5nzm5saWPmQBAPo7GwDo" - "_wEgVQUkAOIIIQDiOF_0AfD_AfAB8AHwAfAB8AHwAfAB8P8B8Fn0WTQOAd87WTQI" - "_QHw_wHwAfAB8AHwAfAB8AHwAfD_AfBqmHID1QDtMAYAYQhIA_FEZD8_P2wDkAMj" - "bQEAHwwwsgu8x1EATgBkZGT4YGBgQwJHAcMMyQOTA_jAwMCdCJQIQAICBNwI-EJC" - "QhsAUgJgABsA3gMfzwwOBNsz3gxEDVdXV_-aAtw1dAF6BP8DWAiaCIkER8kMsAHt" - "A0dHRyEA0CDQ0ExMTDIEcHD-cHgAGg3eAMgBVwBgAJgEI64AHgA4ODiiCXx8gHzP" - "z8-_v78yBP89BcgENgCQAAQCNwWvCKgAH6cEigCxCdgACAF4eHj_KAKVBL0ATgC3" - "yQHwAfAB8P8BkGcCEQqf8wHwAfAB8AHw_wHwAfAB8AHwAfAB8AGQoQH_pgUg_VGT" - "_ABTAQEAfwJf9P9f9AHwAfAB8AHwAfAB8AHw_wHwAfBZ9FnE5wALPQgxOQP_DADr" - "Aln0AfAB8AHwAfAB8H8B8AHwAfAB8AHA3gA_A8v8y8taA0UDBgAMAOoDDgF_4gI-" - "NJMDGwznAyn9AQBV_FVVQgBjwyYBHQSABE4AYy8BpwSGhoZUAKsMTfxNTTUBmAec" - "A_QLNQQPAPF0AYKCgqYIdwEBAA8A_4QMeABIACQAkQhQBGoCsTAYu7u7bgRCAGVl" - "ZfFfBI6OjloAOwQ_AL8EQK2trWJiYjkJasRqaiEAmpqaTgCfAP-kBOwECQAkAKkL" - "ngQMAOkBx_IBXw1TBLq6uq4JzwA_HQ1RAPcItgQeAFQJMzOOM-8EhACnDaqqqqIA" - "f2wAeg0T_gHwAfAB8AFg3Pzc3K_4AfAB8AHwAfAB8P8B8AHwAfAB8AHwAfABMKMF" - "_y4CX2RFA9g2X2QYAF9kSzb_eDNf9MDzAfAB8AHwAfAB8P8B8AHwAfBZ9FmU8mrh" - "MAEA_1lkVvQB8AHwAfAB8AHwAfA_AfAB8AHwAcDMAEkIVlbwVjU1NSxkDAD4B-IC" - "f-gCVANeCJA8dQNZ9AEAOeA5OeDg4FU4NDI7AcevCHgMFASrq6tDAvYM488DZQF9" - "fX3wA2IBJQt4PT09sQNaAEACSQJFfEVFXwT5A0IAPwAnAHn8eXkzAPkDUA0oO2AA" - "XQDHoQRTBFwEY2NjWQ05Cf_tM6YIMADVDJEIiwgeALQAjy4FuAIuBXMIfn5-DADx" - "LQCHh4c5ABs2UQBpAPESAL6-vlwEHQ08AOMBQFRUVJiYmFEAdPx0dBwFJwDUBBAF" - "1w1fB_9vCW8ATgAH8gHwAfAB8AEA_ysO2wAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB" - "8AHwAWB1Bvjk5OQuYl9kYDNfNHIG8QEA6urqJAAhMF_0X_T_AfAB8AHwAfAB8AHw" - "AfAB8P9Z9FmU5wDhAP0C_DAGAPcC_1n0AfAB8AHwAfAB8AHwAfD_AfAB8AHwzwB4" - "A_cLL_QBMPioqKhmA2IEKgBOA4IIwKysrGhoaCfAbwMfRwRcNBcBNwJAAt3d3WM4" - "BBsAubm5HgBNDcJ8wsJWAQIEQQRxAbcDToxOTlwBHDvX19dLABicnJxcNGsEU1NT" - "-JubmyoAtwxoBLU4AQDHngFCAKQE1NTUOghFABjHx8d-AAkAmZmZgTAAdXV1jIyM" - "eAAj5wydCNPT08AA29v-2ygFlws_AGAA9wjbDBgAYz43YAClpaW4CPEITwxPT7oA" - "OAdBQUGyBLKyJABYWFiJiQKJWgCfn59KSkr_CwFMBYIFMwBcBFnxAfAB8P8BwPAA" - "IgWH8wHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_iQFYAl-UVwNjk2UBSQJf9P9f" - "9MzzAfAB8AHwAfAB8AHwjwHwAfBZ9Fk06enp2AD_XAfzAGI3BDv8AAHwAfAB8P8B" - "8AHwAfAB8AHwAfAB8AGQ--cApAovAfABUAwMFwH2AB8CAQEAFAEdAQgBkZGRPyeQ" - "4QNOACwBBgAeALy8jrw5DDoC8wM6OjoJAPjBwcHeDJADIQAGCZMD_1cAYg20DBc6" - "NgDmBEsAPADHDABsAC0AQEBADg11APHMDIWFhXQEZjAeAOAE_1cJrQQPCQEAhQIw" - "AL8EHQT_rgAwABoHGQWpCPEIoAICBP9zOMIEUQD2D2wAuQQCAc4BgSEAUVFRycnJ" - "-A0RdQBmZmbmAc7Ozv8wAJgE3AJlBFUFtADRBH0E_zYA7Ql1CSMNjQBZ8QHwAfDj" - "AZDAAKSkpIHzAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfABMKkFiQECMQQCYzb_" - "aWNZAXsD5QVf9F_EzPMB8P8B8AHwAfAB8AHwAfBZ9FnE_94A-D0GALE5WfQB8AHw" - "AfD_AfAB8AHwAfAB8AHwAfABkIGTOZCQkDQ0NFn0-G9vb-0A_ADgAfUHSAM_JgTv" - "ASYBGACNAypgsbE-sSABsGTDDFMNUwGmpv6mOQw1AcIE7wcbAM0IJg34xcXFMABV" - "CIcMFQA2MPHqAzExMUkCiQF1AIYN_ywEdQALDXwI5wYgBCoAewD_3gyuDD8M8AN-" - "AEgA5wa_BDGbDdnZ2R4AMADPz_DPmpqaHgAXDTYA1wH_DwCEAMAASADTCBsA-Q-l" - "APE8ANjY2I8NewCxABgARycJhABGCERERC0A0RzR0aMIUQABAllZWce0ADYATgBN" - "TU2nPVnx_wHwAfABYGINZgN78wHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAZAZ" - "AlkBX_QfAto38YED4uLiX_Rf9AHwAfD_AfAB8AHwAfAB8AHwWfRZZP_GANgA8wB8" - "Ag8AWfQB8AHw_wHwAfAB8AHwAfAB8AHwAfAfAfDSANsAYQhiBHBwcANJCFMEioqK" - "tbW1A9oB_gdra2tDQ0NHtwMBMKgMi4uLIwR6_Hp6hwN1A34MDj1cZO0MP34MYgR-" - "A50IWQEeOZKS_pLqA3IM4QNcAfgHVANUPDE2AH5-fiQAGQiUlP6UdAF2CFwEUgIv" - "BBgAkTh_YABsABIAPABoDSoAGwBt_G1tYzCfAFoMcAgtCTkA_8YMHgAgDakC9AgJ" - "AAgNPwDH5gE7DRsAcXFxzAAqAD_TCHcEdQBRMJYJEwKOjiKOcwWGhoZQBFtb4lsK" - "MmRkZMAABwV3BP8mAZMAEPIB8AHwAWBICboA_woOAfAB8AHwAfAB8AHwAfAHAfAB" - "8AHAsK-v1tRG1DoCDzC6ubkPAL_8vb0q8AHwAcAyCl_0AQD_cQESANUJ7AFfZB4w" - "X_TY8_8B8AHwAfAB8AHwAfAB8Fk0_1w0qACCAusCRzTMAJYGWWT_CQBZ9AHwAfAB" - "8AHwAfAB8P8B8AHwAfAB8AHwNjP5AHgD_3I8CAGzAfnMAZCKAxIDvAc4xsbGLAG8" - "lwwA5eX-5XgDtwywBEcEBwIGAFkE-GxsbNsMJQhgAF4IEgAfDABdPwYA_QjqA39_" - "f_iXl5cPAPAMrAgJAC0J-JOTk1QAmDESAP8M3gPHDwCZDDMAv7-_rgBNDf8mDfA8" - "nAAnAFANHgBIAG8AcQ8AqampMQXnAA8AvsS-vhMFgYGBhQixAP87Bx4AGAD9CBgA" - "qQgNAnEEP8YAkAB-AHIAQgBdANLS_tJVBYIFQAU5AHJmU_EB8P8B8E4AvAds8wHw" - "AfAB8AHwPwHwAfAB8AHwAfABMNHPAs8lBr69vauqqoEJAODd3aGhoSfw_wHwAfBJ" - "Al_EoAhfMYcDuwj_X_TLMaoBXDRf9AHwAfAB8P8B8AHwAfAB8FlknQJZlAwD__0C" - "ZgP3AuQAP2PnAAHwAfD_AfAB8AHwAfAB8AHwAfAB8N8B8AHwAZDqMOYHLgHwAVD4" - "p6enOAEgARfBDwDWCDjT09PSBrQDqAPLyxrLBQ3QASCnB8zMzA8PABsADAD5DMrK" - "ys08zc0kAL0GNgBuBNTUPtQRBH8IJgQtABgA1dX-1UgADAAwAC0AGwA_ABgA_5wD" - "UTAYAEIAXWAYACQAMwD_cgAJABUANgABADYADwAJAP9pMDAADwA5ACoAYwCxABsA" - "_xgANgAvDRsADAABADYAPzD_KgCuAF0AMAAGAAwAbAAVAP-5DRzyAfABwGAAY_MB" - "8AHw_wHwAfAB8AHwAfAB8AHwAZDxSgTf3d1WBAYwYgQk8P8B8AHwATBKAUNibwNf" - "NNQ3_40D3wVfNJsKHjBfZLtoX_T_AfAB8AHwAfAB8AHwXPRZlP9EN70AwwAfBVlk" - "mTYf8gHw_wHwAfAB8AHwAfAB8AHwAfB_AfAB8AHwsTmbB1P0ATBLPEtLAgRUNlnE" - "OQB5eeJ5Tgxzc3O7CAkAqwzx8AxcXFwJAEQN9QfGDBEJADY2NgkAUVFRHyQAYwx-" - "DHsMGABGRkYRcAhUVFQYADIyMsBgYGBFRUUYACMN458MFQA4ODg5AFEAFQDHDwAP" - "CQwAdnZ2VDBYCIhYWFgYADs7Owg9_wwAGACEAD8wATAtADsNsw0D5AwPAD4-Plpa" - "Wo-zDSoAqAAYAF9fXzlg_2AADACJDRgAIg48ABIAMADxewA_Pz9GDiICRPEB8P8B" - "YN0EXfMB8AHwAfAB8AHwXwHwAfAB8AHwAfCqsgjm4uYDAL68vB7wAfAB8P8BYMMJ" - "VQJJYr5oBgCNA5MD-N7e3sEITg9fNO86X8T_5PMB8AHwAfAB8AHwAfBZ9B9XCbwN" - "8grJALQA4-Pj_70AtThZNAs9IvIB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8CMB8Dxm" - "urq6WgNQUAZQqAPbA0FBQWdnPmeuBqgMWfQ2MBgAcXHicYAEra2t-jg_ACIIP4EM" - "mDQFBJkDCQCrA5WVDpVXAAwAyQO4uLhmHGZmDABmABcNNzc3PwwAXARzAlwEDAAG" - "AH19jn0JAPwDmQB7e3sMAMfzA88Mng2oqKgkMEgAA0IADABiYmLDw8OIdHR0eDCJ" - "iYm2BOMilQ8AmZmZLTCHALEAGF5eXgwA8g2vr6-ITExMJzCEhIQ5MIhTU1P1BDAw" - "MAwAgU0NoKCgt7e3gQD_QwVc9AHwAWAOAVrzAfAB8P8B8AHwAfAB8AHwAfAB8AHw" - "xwEAUwRcNNDPzx7wAfD_AfABkM0OHQFJMh1thwONA_-TA7gIEwufA18EJAAeALED" - "_18EvQPDA1_0AfAB8AHwAfA_AfAB8A79jQBZlMMA5OT-5IEJtwDSANUwWfQB8AHw" - "_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwFAEBAMsBkjrXAR8LAc4BS_MBYA0CcnJy" - "cRoEkpKS4wfkM0ENhRyFhaszIAQYME5OTjiIiIgZCAwAEAilpT6lSQgMANUD9Agt" - "MHh4Pnh4DOczkgT0CPwzu7sGu1w0oQSsrKy1tX61QgAPAOcDoAJ9BE00tvy2trA0" - "fziqBFyUFQA4BP9cNEgAOAQYMC8E_QgBNSQAh2kAOTBdAG5ubi8BIDFmCbm5uWMA" - "GABra75r2voB8AEwfQeiAJsB8P8B8AHwAfAB8AHwAfAB8AHwAwHwAeCqqano5uYY" - "39zceg4JAL28vB8k8AHwAfABkLIF39_f_1-UijMaPRsAQQGlMyEAnwP_oAK9A180" - "Gv0B8AHwAfAB8H8B8AHwtfgOPbwNFQOxAOX85eXSALQAsmjnADQFAfD_AfAB8AHw" - "AfAB8AHwAQBWASjV1dWUDr0BIL6-LL6xASAJAL8BIMzM8MzW1tYFBDzwAfAB8B8B" - "8AHwAfABwFw0aWlpADMzM4ODgzExgDEtLS1WVlbDDBhhYWEMAAEAZWVljwkw9gy0" - "DAwwXV1dJADxDDBISEioDAwwZQQkAIhAQEAwMEdHRwkA4wYANwhVVVUMAI8ErQT_" - "DwAJAA8AyAT2Azwwsgg7BD9uDQwAigCBYAFgOw2MjP6MiAgMAOcMfQ0YMAkANQT4" - "QkJCOTApDRgw2gRxBGMMMA4NjY2NgwQMAGT8ZGTkCesCEfEBwJ4B9gn_UfMB8AHw" - "AfAB8AHwAfABAAE3AsfHx9ra2tv829uZMAkADwAVABsAMPDjAfAFNN7c3FMEYgQM" - "kB8q8AHwAfABkIMK6enp_08yvmjmN58DNAsMAwkAJAD_EgAbAL0DGv0B8AHwAfAB" - "8P8B8AHwWfQOPZYAiQcZBRwC8fsB6urqzJBZ9AHwAfAfAfAB8AHwAfCgBcnJyeGV" - "B6Ojo58BUMgBATAbpgIBMKIBUCcApKSk_5sBUwdrBIABVPAB8AHwAfAfAfAB8A0y" - "ZgOlDIKCgviRkZHzAwsN4QO1CGsEPwwAGAAMAAkAAQAJAI-P_o8SACRgGGAtMAYw" - "cAgJMH8YAC1gAQAVYA8wPDBZBJD8kJAbAGkwVGASAAxgAWD_NgkgBCFgRWAYAL0A" - "OZB4MPEYAI6OjgwAXTC0YHIJ_7U4EfEBkAwDwAlL8wHwAfD_AfAB8AHwAcA9AgUK" - "FvIBwB8hACcALQBC8AGQvbu7QNTT07i3tw8wqcSoqA8AysnJKvAB8P8B8AHA2Q5S" - "YuyXnwO4CAoF8asD5ubmIQBwAr0DwwP_yQMa_QHwAfAB8AHwAfAB8McO_Q49twDi" - "4uK1aAEA_4r2AfAB8AHwAfAB8AHAmgU4xsbGQgY9C1Nkq6uwq7S0tKA4ATDZASAi" - "zQFQwcHBkgGurhau6wIBAKYBILe3t_jKysqPAWbwAfAB8AHw_wHwAfCqOg0CUz3U" - "OgEAD2D_GJAM8AGQFZAMwAEAsQZI8P94wD_wRcAh8EWQsfDV8C0A9wXxAfCMAZoB" - "8AHwAfAB8AcB8AHwAYCcnJzDw_7D0w6W8AHwYmR5DkvwAfD_AfAB8AHwAfABwNMC" - "ijCHA_-NA5Yzu2gBALEDrg99BL0D_xr9X_QB8AHwAfAB8AHwtfj_Dm20AA4NtTjG" - "AKsABgCHNv8SAAHwAfAB8AHwAfABwNgG8QECr6-vUwQBMIgIkQJ40dHRYT4B8AHw" - "1z3SANLSvLy8qqqqjykHAQDKCHcEyMjIYwD_ePAB8AHwAfAB8AHwAfAB8P8B8AHw" - "AfAB8AHwAfAB8AHw_wHwAfAB8AHwAcAqAz_zAfD_AfAB8AHwAfABkGMG8AyQwADt" - "4c30yIT3vQBk-LZX-bFL9wC6Z_XBfO_Yu_EnwM_Pz5gEVPAB8AHwjwHwAfAB8BCV" - "3d3dhAD_EGuZMxIAqwNWBzIHLgXxAv99BMMDGv388wHwAfAB8AHw_wHwtWgOPeYE" - "Dj2xAMYAwAP_BDUBALI4IvIB8AHwAfAB8OMBwH8FwMDACwRTNA0CwL6-vtTU1BgA" - "O_THAfAB8AFg1tbWsDSoDP9gAPoIYgQYAITwAfAB8AHw_wHwAfAB8AHwAfAB8AHw" - "AfD_AfAB8AHwAfAB8AHwAfAB8P8BMF4CovMB8AHwAfAB8AHwMQGQvb2975HWAvPX" - "AKn7x3P9xG79AMR5_cJ6_r93AP68dv6-ev-7AHX_rVP9ojn04MCF6-fiM2DGAzcL" - "_1rwAfAB8AHwAfAB8AFgFwd_vTC-OJwzpQOrAy4FmQDf_N_fSgcTOBo9uzjhA7v4" - "_wHwAfAB8AHwAfC1mA7NqwD_tThyCQ8ABgDAACLyAfAB8H8B8AHwAcDtA1gIVjQI" - "DdX81dUj9AHwAfAB8AGQiQGIurq6WQ2srKxiBP-QBorwAfAB8AHwAfAB8AHw_wHw" - "AfAB8AHwAfAB8AHwAfB_AfAB8AHwAfAB8H0BLQaZ_wHwAfAB8AHwAfAB8AEgzgQD" - "OQ-KkPDk0v3TkAD_5sT_7tn_1QCr_8mU_7-B_wCyav-uZ_--hgD_xpj_xJT_swB1" - "_qFM_Zs68PzOsDlglALqA2PwAfAB8I8B8AHwAfABYMLCwoQwB5MDn2O7ONfX19vb" - "8Nvh4eEa_RptAvQB8P8B8AHwAfBZ9A7NKg-WAKIA_18EHAW3AFlkPwwB8AHwAfAP" - "AfAB8AEwfAWysrKd_wEgCQAsASYBAfAB8AHwAfAfAfABAHIALA1fBK2trf8bCVAH" - "lvAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AGQkgoz8_8B" - "8AHwAfAB8AHwATBOD-wBAYeQ8uDC_uK1_wD68__kyf_JkwD_v4L_voD_uwB-_7d4" - "_61o_wC1ev-9if-7hwD_p2j-j0D_h4Au_4cc8r6SP2D4x8fHZvAB8AHwAfAB8AMB" - "8AFgm5ub3Nzc_8w_XzQBAKsDsQMvBzMDxAL_Gm3PAxr9CPQB8AHwAfAB8ONc9A7N" - "4uLiWgmcMLcAfwYAWfQB8AHwAfAB8AGQxgTGxlY0paWl0ND-0Aj0AfAB8AHwAfAB" - "8G4Nx7sIKQ0BAM7OzpzwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB" - "8AHw4wHwATDe3t7y9wHwAfCPAfAB8AHwATCwsLB7AwGHkPHjzv_nwv8A9-3_1q3_" - "wIIA_8KH_8OL_8EIi__AAwCM_7iAAP-sbP-xd_-xAHn_nVr_ij3_AIQ2_4Iy_YIq" - "-PG9l0Vg9Qps8AHwAfCPAfAB8AHwAWCurq6BMH-ZA6VjsQO3A1EJYgTECOb85uYa" - "_Vz0AfAB8AHwAfDjWfS1yPHx8QoFnAD4BP8RPbQAEwVc9AHwAfAB8AHw4wEwCAG4" - "uLhWBAUNPQj_BwL_8wHwAfAB8AHwAfABMI91AIoGhQKGDcXFxRIA_6jwAfAB8AHw" - "AfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8O8B8AHwAfBiAZgB8AHwAfCPAfAB8AHg" - "igPBwcGEwADs5tz-47r_-4b2WRSvCMKJ_8JZVAC_i_--i_-4gwD_oFz_l0__lQBO" - "_4tA_4Q4_gCBN_5_N_59M8D-fiXryrTJA0gA_yoDcvAB8AHwAfAB8AHwAZD40dHR" - "XDSfA180DAC3A_jW1tZfBNkIIggaPdUD_7v4DvQB8AHwAfAB8LX4tTj_ogCTABEN" - "7QO0MAYAwwBKAf8B8AHwAfAB8AHwbQWTDFkE-KKioq8IewAB8AHwAfAfAfAB8AHw" - "AcCbAbW1tf-PDeMECwGu8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfA_AfAB8AHw" - "AfAB8AGQzc3-zSfzAfAB8AHwAfAB8KsGiNLS0ofw-M6QshjQ69f_w1YEilZ0AwAA" - "vYv_uoj_sHgA_5ZP_4k8_4UAOP-DOP-ANv8Afjb_fDb-ezcA_3gw-YQ94N3_JBNL" - "AEMIePAB8AHwAfAB8P8B8AFg4w07BOM0q2OTAL0Dx_MAGg0gCuXl5SI4u_g_DvQB" - "8AHwAfAB8LXI9_eA9_T09PDw8OYEwNjY2OHh4bQADAD_AQCyOJ7xAfAB8AHwAfAc" - "DvipqakEAl8EDADt8wHwfwHwAfAB8AHwAfABMFAKvcS9vSMNvLy8DAC08P8B8AHw" - "AfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfDHAfABMBoKoKCgJ_MB8H8B8AHw" - "AfABwFgC2z8BwOwA4M7_1Zn_9u0I_8ybr7iK_7mFAQUdpmj_mFP_iwA__4M3_4E2" - "_wB_Nv99Nv97NgD_eTb_dzb_dYA2_3Qt4KuQvQPxLgLMzMx78AHwAfAB8McB8AHw" - "AZDLy8tcNKUDD64zDwC9A7gI09PT2vza2gwAuzjbA-ED5wO7-D8B8AHwAfAB8Fz0" - "tWjy8v7y4ASNAOcJBAKuMLX4AfD_AfAB8AHwATC6CX8FWQSHDB_PCefzAfABMNcB" - "qqqq8I6OjoAB8AHgJwAtAB8KMgHwAfABAAIBwsLC8cEIubm5X_QB8AHwAfD_AfAB" - "8AHwAfAB8AHwAfAB8H8B8AHwAfAB8AHwAfABMMP4w8OXAfAB8AHwAfAB8DEBsLq6" - "utc3AfD1wwB-_-bK_-HF_wTDjq94u4f_rnMA_51Y_5JH_4yAQP-GOv-CN69oAP96" - "Nv94Nv92AVYENv9yNv9wM8Dxf0nOzs4cAt0K_-cDgfAB8AHwAfAB8AHwAWD4pKSk" - "u5gKOLsCwwMyDf-7OA8AHgDbA-EDu_gB8AHw_wHwAfBZ9LU4iQ21aJAAPTj_yD1Z" - "NMAAAfAB8AHwAfABkBFZBKWlpVkEt7e3HwIBwAkB8AHwATChoaH4fX19mAQIB00N" - "AfABYP8hACcALQAzAEjwAfABYJMAwMbGxra2tr44wPD_AfAB8AHwAfAB8AHwAfAB" - "8P8B8AHwAfAB8AHwAfAB8AHw4wHAPAacnJwh8wHwAfAHAfAB8AFgmZmZz88Gz4Hw" - "AWD8uF__6oDW_8uf_8CMrBgAvYn_t4D_pWQA_5JG_4k7_4UGN6_4r0hzNv9xNiD_" - "bzb_bAMALsKMtK_DA1wEpqamh_D_AfAB8AHwAfAB8AGQNglflP-3A70DwwOZALto" - "EgC7-F_0_wHwAfAB8AHwtfjaBIQATgn_SDmoMFn0AfAB8AHwAfCtCh8LPXsMWfQB" - "8AHAeHh4gHl5eZOTk74B8P8B4CcALQAzAGsBAQB7AHLwAwHwATDFxcW4uLj4v7-_" - "QwLG8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHwPwHwAfAB8AHwAfABkMHB_MGWAfAB" - "8AHwAfAB8AFQOKKioloDfvABYOnkAN__v3D_4cj_BMWVrxi6hf-sbwD_mlL_jT__" - "h4A5_4Q2_4I2r_gBr1g2_242_2s2AP9pNv9oMcqbxovdCiUIs7OzivAB8B8B8AHw" - "AfAB8AGQp6enP9gwqwMQOAwAFz1hBdbW_tYOAbv4u_gB8AHwAfAB8CNcNLWY8fHx" - "2gTb2_7buAhFM6gAkAAJADD8AfCPAfAB8AGQtAOwsLBZBD8GAM8G_AAB8AHwAZBm" - "ZgBmcHBwe3t7ifiJiYIB8AGAIQAnAC0AAzMACgLExMTNzc3_YgR48AHwAQAnADMA" - "Gg0GAP-3AMzwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8PJhRAob" - "8wHwAfAB8GMB8AEwr6-vpPEBwOwA2sX_yIv_068I_8GQCB2rb_-VwkuvKDb_gzav" - "-K_YAmpWBDb_Zjb_ZfAz0Y141wpPAgYAjfD_AfAB8AHwAfAB8AHARATYAPk3Fenp" - "twMJAMMDyQOzDfjS0tK7mOcD7QPzA7s4f1z0AfAB8AHwtfhdALU48-Dz8-_v75kA" - "RQmoDP9RA2M5BgCb8QHwAfAB8AHAgasDn5-fqqqqbAyPz_MB8AHwKQRvb281BH9N" - "9AHwJwAtADMABAKADb38vb0QAos-AfAB8KoBdAfAu7u7y8vLzPAB8P8B8AHwAfAB" - "8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwATDwyMjIlQHwAfAB8AHweQHwlZVjA2IK" - "ePAB8O0A1bn_tmr_w5QA_7R8_6dp_5RwS_-IO6_4r_ivSGcBVgQ2_2M1_2I0GNOJ" - "dB0HsQaysrJ_k_AB8AHwAfAB8AHwAcCs_Kys2zC3Y7UCyQN0BLs4_-syGwDnA-0D" - "uzgX_QHwAfCPAfBc9BE9wgT19fW1OP9CA1wEMDyfAFk0JPwB8AHwIwHwAZDJyclZ" - "BKGhfqFNCsnzAfAB8Fz0ATB8iHx8ZQEgfX19XPT_XDRfNIUIfvAB8AFgWQqSBPi8" - "vLxrCtLwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfA_AfAB8AHwAfAB8AEApqY-phXz" - "AfAB8AHwAfCcnB6cpwd18AHwAQDq1bxA_5cy_6VkrxiNXkEIHa_4r_ivaGRWBDUA" - "_2A1_1820okCdtgDurq6q6ur-JSUlJnwAfAB8AHwAfD_AfBYxXIMsTm6M7ICyQPP" - "A_jMzMwGALs40Ai7OPMDf_kDF_0B8AHwAfBZ9BE99_z397U4SwmeAeMEPzmfAH8O" - "PZvxAfAB8AHwAWClA6TEpKRcBMrKym8MAfCPAfAB8AEAXPSAgIAVIN5wAwAJAA8A" - "XPS1AVCICPjY2NhX8AHwAWBWBF8E_zkAIwHY8AHwAfAB8AHwAfD_AfAB8AHwAfAB" - "8AHwAfAB8P8B8AHwAcDHBRLzAfAB8AHwcQHwp6enkvEB8AFg6ADbz_-KHf-YUsD_" - "jkT_hTiv-K_4Ra-oYVYENf9dAwA3OMiRhVwErgYpB5KS_pKc8AHwAfAB8AHwAfAB" - "wP-aAlw0twMWOBQBzwNqC7sI59sDuwhYFerquzj5Axc9P1_0AfAB8AHwtcgRPfLy" - "8PLt7e2QAJ4N4GS1-B8B8AHwAfAB8DQIoKCgPwYAvfMB8AHwXPRBZHNz4nMSAHZ2" - "dwMACQAPAD9c9Fz0AfAB8AFgqgG_v_6_UA3Y8AHwAfAB8AHwAfD_AfAB8AHwAfAB" - "8AHwAfAB8P8B8AHwAcBPDtcHAfAB8AHwPwHwAZBaAG_wAfABwP2PACz_ijz_i0P_" - "HIE3r_iv-K-oXjX_AFw1_1o1_1s5OLain1wETwJZBJCQ_pCf8AHwAfAB8AHwAfAB" - "wH9UA9swGWiHDM8DuAgBANT81NTZCBIAGwDtA_MDFz1_u_gB8AHwAfBclLU4GAn0" - "APT08PDw6-vrwODg4NDQ0IEw_jT_qAAb_AHwAfAB8AFg5wAFAf-4CA0OuvMB8AHw" - "XPRBZFk0-Hd3eAMAXzRc9Fz0AfCPAfABYGgKXQ_AwMA8AP_e8AHwAfAB8AHwAfAB" - "8AHw_wHwAfAB8AHwAfAB8AHwAfDxAZCenp4P8wHwAfAB8D8BYAkMsgJs8AHwAfD1" - "qIBn_4Iu_4E4r_gDr_iv2Fs1_1k1_2BYNvBnUAsNAQCv4K-vg4ODXPQB8AHw_wHw" - "AfAB8AHwAQBYC9sAtwMfcjzJA88DIwEhDNHR0f-yAuQDGADtA_MD-QP_A7s4f1z0" - "AfAB8AHwEW22BBE97iDu7unp6T8J0tL-0jkJQgOBAAEAkvEB8AHwIwHwAZCxsbFc" - "BMfHfsf5AAHwAfAB8AFgXMR6xHp6qQh4eHkDYA8A-xUAXMS0AVAgBCkKV_AB8P8B" - "wMIBXwTBCN7wAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfC_AfAB8AHwAfABYJQLkwHw" - "_wHwAfAB8AFQRgJdD2zwAfADAfABAOrMtf9-JAev-K_4r_j_WDX_VuEDADnVgXVc" - "NDoFZAL4j4-PqPAB8AHwAfAB8OMB8AHwurq63jDAM8kD488D1QPIyMhqC7gCMjH_" - "7QPzAxc9BQQX_QHwAfAB8IERzfX19fHx8SQJ_-kEAQI5CVw0sji29wHwAfD_AfAB" - "YFMEEwJECncNtPMB8FcB8Fz0XGR5ARB6A-B5f1z0XPQB8AHwAWC9AK0Bw_zDw44F" - "5PAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AEwbgEJ8wHwAfDjAfAB" - "MLCwsIkBafAB8AMB8AFg9pla_3ouB6_4r_ivqFc1_1U1AP9UN_hcR66mBKWqASCH" - "h4eEhP6EuAir8AHwAfAB8AHwAfDxAfChoaHbML44XDTVA-N4AwsH09PTIw2-Au0D" - "__MD-QP_Axf9AfAB8AHwEf3jEW2NAMnJyWwD9Atc9B8B8AHwAfAB8LUIoqKif-cA" - "4QAB8AHwAfABkFyUgACAgHx8fHp6ey8DwBUAGwBclLMBUL-_Pr8dBFfwAfAB8CkB" - "xMT-xOAN5PAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfDHAfAB8AEwnJycCfMB" - "8McB8AHwAQDGxsZj8AHwAwHwAcDoz7_-eCk4_3Uzr_iv-K94VDWE_1IDADvMh4BZ" - "NMcvB4gPXwSSkpKu8AHwDwHwAfAB8AGQw8HB4CDd3dbU1Bvw2dn-2VyUzwOsAtsD" - "DgcdDRcN_xIAGwAX_Vz0AfAB8Fz0XDTxEZ3c3NzzA4cAuDicAP-w9wHwAfAB8AFg" - "9Q1YBUEKf_UNrvMB8AHwXPRcZO4OfsR-fwMwf3-AAwAPYP8bAFz0XPQB8AHwAWDD" - "ALAB-MXFxa0B6vAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8IsLdQMB" - "8AHw4wHwAcCZmZlx8QHwAfADAfABMOiwkv9xLjj_cDSv-K_4rxhTNYT_UVkU429j" - "qQFQ470GXgKLi4ux8AHwAfAHAfAB8AGQl5eX393w3dXT0wYA2gQewPMG_9swHDjP" - "A9UD2wORBRdtGAD_F51c9AHwAfAB8BH9ET05Cf97APUEuDgBAInxAfAB8AHw8QFg" - "tbW1tQifCZkDAfCPAfAB8AHAXGSIiIjrDkiFhYY8BoeHDgCJfwMACQANABUAGwAT" - "AFxksvEBUL6-vmsBV_AB8AHw_wEAmATMAEkO6vAB8AHwAfD_AfAB8AHwAfAB8AHw" - "AfAB8P8B8AHwAfAB8GgBIfMB8AHw8QHAqKiodwFg8AHwAfAHAfABMHsD5KKD_2ww" - "MP9qNK_4r_j_UAA2_1E75G1iqwSnp1k0kZGRc3OAc4GBgY6OjrTwA8DACwHR0dHS" - "0tL_ojYJAA8AFQAbADDwAfABMEC4t7ff3NwtCdrg2NjLysoewC4-MW7_zwPVA9sD" - "dATdB3IAF_0X_R8B8AHwAfBcZBGd5ubm-M7OzicDeAC_AVz0AfAPAfAB8AGQGAmj" - "o6Ol_KWlNwWo8wHwAfAB8FxkgegulI2Njo-PCwD-kQNgDwAVABsAFgBc9Fz0_wHw" - "AfABYLYBuwgBAAkA8PD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfABwFwH" - "_4EDAfAB8AHwAWBlClrwAfAPAfAB8AGQ3z7Vo5D7GbUIZTSv-K-oUTn_IFE-13py" - "smiamuKaVQJ4eHjGBnoSAfAAkZGVlZW8vLyPAAN-8CHDJwCWlpZC8IEBwNrX19DO" - "zgkAJTAA3mUEn58ekJ6e_p5clKkC1QPbA1wEF53zAz8XnVz0AfAB8Fz0EZ3v7_7v" - "Hgk_CXgAQwVcNHdhAfCPAfAB8AHwAQDDw8NcBPi4uLjYAAHwAfAB8AHw_1xk9gwu" - "C8oFdA0bCQFgDwCPFQAbACEAXPS9vb2uCf9X8AHwAfABMKoBXARZ_QHw_wHwAfAB" - "8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwMPMB8P8B8AGQdwdX8AHwAfAB8AHwA-0G" - "WQe_sKvcgWUA_mI2_182_10QNv9aNq9YNv9TADf_Ujr_Uj7oYGlduZiWWWTQAnLE" - "cnK1OI2NjbfwRgh4ubm5KDgB8AHwAQDbYNvbsbGxS_ABYK4Ara3e29vU0tJAysnJ" - "z87ODADB_L-_JMBFAEsAxjNcNNsD_1wEww9KAa8C8wDzA_kD_wM_F_0B8AHwAfBc" - "ZAYJ9vYQ9vLy8hgJ6Ojo44oAeADT09OWAFw09vAPAfAB8AHwAWC0tLSk_KSkQAWi" - "8wHwAfAB8FyU_oIB8AHgXPRc9AHwAfABkP9uCmgKuQHw8AHwAfAB8AHw_wHwAfAB" - "8AHwAfAB8AHwAfD_AfAB8AGQGgrRBwHwAfAB8A8BABgGaAdUwMfgxoAA235g1l1U" - "1FAASNNDYtVfdtbwc7PdsX7wAfAB8PECB8gB3w6jArKjn8uLAHzmclvuaVH-AFk9" - "_1Y98WNQAOtnWNN-dracBplWZFk0dnZ2cXGAcXx8fImJiXgPcbrAnZ2dGQJdAGyQ" - "0gDS5aur7JiY7wCOjvCFhfGVlYDuoKDtxMTmk8AxjQOTk5MO8QEA1NGA0dnW1srI" - "yAMwjw8AXwQ_CSSQzMzMSDAfXDTVA9sDVADgB8vLy__nMxsA-QP_AwUECwQRBFz0" - "fwHwAfABkBE9SwBoDW4N7Izs7B4J2gTGxsaMPf8zA3ExAfAB8AHwAfABANYO8VkE" - "p6enMQWi8wHwAfD_AfBc9AHwXPRc9FfwAfAB8P8BMNgArQHLAQwA9vAB8AHw_wHw" - "AfAB8AHwAfAB8AHwAfD_AfAB8AHwAfABYNMCIfMB8D8B8AEA8QhSCEtglwKk4QCj" - "cOJvaeRoaADoaGTnZF_mXwBb5Vtk5GNh4QBfR9dBNcwrfuDTeNnf2IfwAfCgwu_O" - "BwMDXwTCDagB8AGwzAMYbm5utQhZBIiIiIiOjo66wLOzsxOSAboAwcHtpqf4nwCg" - "_Jma_ZOV_QCPkP2LjP2SkwD9kJH8f3_4cOBw8p6e6W8IM2CGBwv_AFfAqEID29u7" - "uv66EvAnkCwNeGCHANUD2wPx3gDAwMBnC_IETAUeAP_5A_8DBQQLBBEEFz0y9AHw" - "fwHwtThcNGINaA1uDXQN5fzl5V0GewDRNFz0AfAB8AcB8AGQKQGlpaW1tf61xQEB" - "8AHwAfAB8AEAkjr4fX19SvQB8CcALQAzAP90CgEwXATzAFfwAfAB8AFg_6oBaArw" - "OQHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8McB8AHwATC3t7fUBwHwHwHwAcAk" - "A7gFSGDK48oAj-iOv_W_0fkA0ZTylHbudl4A6l5B5EE94j0AZedlfOl8d-gAd1Tf" - "UzbRMDTgxyin1qKK8AHwX8RA19fXyMjItw-vDK-vYvQBMKampofEh4elBnR0dFIC" - "DABHDg23kAgNwsLCWpDWANbourr61dX-AN_f_7Gx_5ycAP-Kiv91df9ygHL_j4__" - "oKASEACDg_5sbfhpafDvt7fkOWD_AAUBXZBxSgS2tbXMCRWQEgCyxLCwEpCurq5c" - "lLg48eEDv7-_nwmNA-oAGAD_-QP_AwUECwQRBFw0u_gB8D8B8FxkXA1iDWgNbg3q" - "6v7qeg1vAFw0uDiWAKT3AfD_AfAB8AEwMQWVAfINnPMzNvt6BP0FgAHwAfABUOUO" - "NgbxAQCFhYVK9AHwJwAwMP85AE7wAfABYJkAnwClALrw_70wXAcGAPbwAfAB8AHw" - "AfD_AfAB8AHwAfAB8AHwAfAB8McB8AHwAWCrq6s49AHwDwHwfQdCMI4CvOS8tQDx" - "tPH98br3ugB173Vg62Bd6QBdWuhaU-VTPwDhP1XjVWjlaABm42Y-2z4LzwAMBscB" - "FbwFiXzPgY3wAfCGytEHzgHBAMHBtra2sLCwOKysrGVkCA18C35-4n5WBHp6elIC" - "Cw0cAmO0kPYD0NDQUWBWJ-sA0dH99vb_zc0A_5ub_4yM_4kAif-Hh_-Cgv8AdHT_" - "hIT_kZEI_5CQaBRLTP5DAEP4S0vsn5_kH0Jg_wYa8QHwAcCioqL_gWBclE4ARwoL" - "B68CQgz5A___AwUECwQRBBcEXPQB8AHwH1xkVg1FAGINUQDu7u7xdA3j4-MiBbIF" - "IQy4OP9c9AHwAfAB8AFgIwoRDXcBY9IAAfCcnJwDBsEOoHygoKYOAfAB8AEAQQGf" - "4J-fmJiY0QFK9AHw_ycALQAzADkATvAB8AEwkwD_mQCfAKUAuvABAI4FmgW8Af_2" - "8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAWCPcQH98gHwAcChoaFCYADI" - "5MfC88Hq_ADqlvOWX-xfZgDsZmvra2vqawBr6Wts6Gxd5ABdQ99DUd9RVADeVC3V" - "LQjMCAAAyAADxAANvXADjc2FkPAB8AHA3Izc3DA26Te6urp7CY8BAGAGBwJcBIOD" - "gwgNH68IWcQ-B5s3AWDV1eoA2dn98fH_s7PI_4uLRBSUlANwZQQgd3f_gYFoBP5j" - "AGP-RET8Ozv7ADk5-Dw-8J-f_uJFYAsBIPEB8OPEXMTbA-PhA1wEw8PDww8PAOcA" - "__kD_wMFBAsEEQQXBB0EXPQfAfAB8FxkXA1LAPLy8sDt7e3n5-c5CSoJ_wEAFD2-" - "BcQ1AfAB8AHwAfA_AQD9BVwEegGc8wEAeHgAeHl5eZOTk77_AfAB8AGAZQ16AT8G" - "SvQB8P8nAC0AMwBI8AHwAZCZAJ8A_6UAtQ4jAVAKmAG9YAwAXAT_NwX28AHwAfAB" - "8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw8QFglJSUhAMB8AHwAZAD2wM_MNLf0rnx" - "uIj0_fRZNGnsaVlEAOhra-dra-ZrAGHjYS_ZLx7UAB4d0h0NzQ0CAMgCAMUBAMMB" - "AAS_ARy0B6rPfqSkAZbwAfABwNcBxAjVfNXV8gH3Dn4J_wxTBIrgioqMjIxZNAFg" - "zwMDfQRCwNnZ4tPT_OT4-FlEkpJZpAMAxAgAaGj_Wlr-V1cI_UdHWRQ1NvkwADH4" - "LzDzQEDm_LKyZBJIAF4CIPEB8JDAP4QwyTO4OOEDCwqTCcrK_srKAo0D-QP_AwUE" - "CwQRBP8XBFz0AfAB8AEw9AhWDUUAAPX19fHx8ezsMOzm5ubdBK4Mycl-yVxkAfAB" - "8AHwAfABMNh82NhZBAQOsAEB8AEAZgBmZnBwcHt7e-Mm9AFgdXV15Q7PDBvwAQGQ" - "bm5uW1tbUgBSUlBQUFpaWvhnZ2dpBifwAWBRAFcAP10AG_CSZJkAnwClALu7_rv6" - "BT0-KQHAYKoBfwIJAP_88AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAQA_" - "wQL68gHwAcB7DEJgjeACi7IIzPnMae1pCGrsalnEZ-RnUwDfUx_THwfMByADygMC" - "x1kEAAAAwwAAwQAAvgEACbgBM7Md0dT-0JvxAfAB8AEwSwa5CqXwjwFgewYTCDnw" - "tLTzsgjk2tpWFJOTWdQLDRcNAVkERUX9Pj78OgA6-jMz-S8v9wAqKvUlJvQoKIDu" - "S0vg0dHVujD_EQF48AHwAZC3AIpgXDThA__BAooJrgCvAjoF-QP_AwUE_wsEEQQX" - "BB0EIwQ49AHwAfABXDT4-Pj09PTwYPDw6-vrXQDdBMD8wMByAM40WPgB8AHwAfD_" - "AWDUB68OMQhW9Fxk4QPDAw_b8wEA3w4jBGVlZW3gbW1ra2sh8AEwsAoIX19fVgRT" - "U1NUAFRUVVVVV1dXAFlZWV1dXXZ2_nYw8AEwUQBXAF0AYwBpAB8h8AEAmQCfAKUA" - "r6-v-LGxsaIGXwQ3C8BgYgr_O_EB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB" - "8AFgH9MC4wcB8AHwAZDCwsIBPzDF3cSY6pXoYPzogO-ACG1ZBGoA5mpj5GNU4FQA" - "P9s_JNQkDM2ADAHJAQDHAFkUEMIAAMBZBAAAvAABALkBEbABhTy7eww2n_AB8AHA" - "y8swy5aWlqLwAWCengae_gE58NDQ4r29wPvu7v-jo1OkZQQAjo7_g4P_c3MQ_l1d" - "_q8IPDz7ADY2-jIy-C0tAPYoKPUjI_MeAB7xGBjvKCjn_I2NbB-6ABEBe_AB8AGQ" - "_3UDt5-gAuEDXASIC8EIDAn_fQT5A_8DBQQLBBEEFwRc9A8B8AHwET1clO_v7-rg" - "6url5eXaBI8HXPQfAfAB8AHwAfChAampqT-4DiYBAfABAFz0VmRpaQJp_AN1dXZ3" - "d3gPAwAqAPzzAQB_f39cAFxcVlZWWFhYB1AEuwgPAF5eXmBgAGBhYWFiYmJ3fHd3" - "NvABAFEAWjBjAG18bW5vAGL0XGR7CQEAsPywsFwK-gXAYDo7O_EB8P8B8AHwAfAB" - "8AHwAfAB8AHwPwHwAfAB8AHwAfABMM3NPs338gHwAcDpCj8wetgAdsP1wrX2tW8E" - "7G9ZZGbmZkzgAEwq2SoV0xUMAM8MBcwFAcgBoADGAADEWYS7WRQAALcBBrIBPar-" - "JpcLpAHKngHwAfCyAgEIH5_wAWAKAlTwATCjo-0A1dX-ycn_l5cBWXSQkP99ff9k" - "AGT_U1P-Skr9BEFBrwj7NTX5MAAw-Csr9iYm9AAhIfIcHPEWFgDvERHtFBTpSvxK" - "1rowCAR-8AHwAZD5DBFc9Lm5ubsCxcXF_3MOHQf5A_8DBQQLBFyUF_0HAfBc9DkA" - "-vr69_cQ9_Pz81EA6enp-OTk5H4AcgAqaVj4AfCPAfAB8AFgjQmqqqq_BEdW9Fz0" - "qDxzc3NSCGr4amp2VQT8IwMw_AMg9B9_OFAEowhQNE0EY2NjEY44aGhooTSAgID_" - "OfAbAGCQbwB1AE4AgQCAbf9cxAEAdgIYBsBgpwG9Bjvx_wHwAfAB8AHwAfAB8AHw" - "AfD_AfAB8AHwAfAB8AHwATAjB46MAfAB8AGAnJycPDABlwJd2FbM-MyEAO-EbOps" - "aulqAGjnaF3kXTrdADoT1BMFzwUBAMwBAcoBAMgAQ1lECA2_AAC9WSS4AAEAtgEA" - "tAEAALEBHKcBpbKi_4oAogOi8AHwAQDdBGoLnPBxAWCPj4_2AznwumCRgJH029v_" - "pqasSEECDYmJ_3BwVgT_AEdH_kFB_T09CPw4OAiNJfQfHwDyGhrwFRXuEAAQ7AsL" - "6wcH6cAmJt-mpre6MBcB_4fwAfABYEEEkGCaAtsD4QP_uAK7CLoA9AWzBKgA_wMF" - "BP8LBBEEFwQdBFz0AfAB8DMAiPz8_Fxk7u7uVwD44-PjfgB1AMMAXPQB8I8B8AHw" - "AcBCDKurq3cNj0AFAfABAFyUfn5-DAAj2wPqA3h4eQPAbm4ebiD0NQRNBAEAZGRk" - "D1AETTTtPDAAcHBwceBxcXJychj5AQAhAP9m8HsAgQCHAI0AXPQBAAAG_1YKwGD7" - "AaoBO_EB8AHwAfD_AfD0kiICMw-BACUCgTbdBP8q8AHwAfAB8GzwbPAq8AHwfwFg" - "NgD38gHwAZDjCmpO3QDUbd9nufW5eATreK8IZOZkR-AARyLYIgrSCgIAzgIAzAAA" - "ygCLWRQIDcNZZAEAullUArNZFACvARemAXh_p3azYaXwAcA2BpmMmZmZ8AFgl5eX" - "BwgDOfA7gd-bm_nMzAj_nZ1cFI-P_3oAev9fX_9MTP4AQ0P-Pj78OzsDCP0IHRMT" - "7g4O7AAKCuoGBukDA8DoGxvggYG5ALoQf-z0AfBc9FyU4QNLAOoDw-DDw8zMzA4H" - "GwD_A_8FBAsEXGQjBFz0AfBc9Fw0QPb29vLy8lEA6CDo6OLi4jECuLj-uAIEgQBS" - "AizxAfAB8AHw8WGSrKysNA5W9Fz0kwzxVAR6ensD8AMAsfwBAIh6enpNNGxsbEgA" - "R00ESgQqAHZ2dp8UefB5fX19tfh3bWbwAzD_PABvMFz0XMQ1AbsIXPQB8D8B8AHw" - "AfABAKQB5wOxsYCxs7Oztra2sAH_PD9WAQIEtvEB8AHwAfBs8N9s8GzwAfABYDMA" - "iwHwAfARAYCvr688ML3bvACF5ICZ8Jlx6QZxCA1ZBBnWGQXRAAUAzQAAywAAsMkA" - "AMdZhAgNvFlUArVZVK4BAKwBE-ClAW2jXrYBsAe7-I-x8JQIlvABYKSkpKH0AQHA" - "ysrjqan6tTC1_5iYCB1ZBFhYyP9ISK9IOTkI_QhNAA0N6wgI6gUFAOkCAugAAOcU" - "4BTgbGy9ujCeB43w_wHwAWDLByeW2wPhA8sBAQDx5ADLy8vGD-kB_wMFBH8LBBEE" - "FwRc9AHwAfBcZPkE-flcNO3t7efnEOfh4eEsAbe3t_97ADADfQEPALf8AfAB8AHw" - "PwEwXPRc9FxkCwQJAHx80n0DAH19OQh_AzAMAD8SAEcEIPQwAE0Eagh1dcZ1Qg0k" - "AHx8fDoA1QwYhoaGaAE2D5GRkfiQkJA88C0gafB-EFcA_28wXPRc9DgBXAT88AHw" - "AfAfAfABwLkEUg7tA7KysjidnZ3YAEAFAQCEhP6EcwJzDuMKfvAB8AHwAfD_bPBs" - "8GzwAfABMKQE9_IB8AcBkHsAPDCx2a5f2wBZd-p3WONYQIDeQBrWGgXQWeQQwgAA" - "wFlUuQEAQrdZJLIBALBZJKsAAQCpAROiAWjgoVenp6e2AcqeAcB_yAHaAZDwAZD1" - "BznwAfDAAMDijY34nJz_YIaG_3R0WRQIDUAwQP08PAj9CN0EBDDoAQHnWQQDABER" - "8OBlZb26MM8JkPAB8P8BYHoB2GCdAq8C4QPnA1wEwMHBwcrKynsJGwD__wMFBAsE" - "XDQdBFz0AfAB8P9c9FxkcgD1OlsCXPQB8AHw_wHwAWDVALYBXASUAgHwAQAxXJSD" - "g4MRBAEAhYXhBQCHh4eIAzAMABIA4xAAIPR_f3-PJFAUoAhxQgCFhYUXClAEvwqb" - "AJubnp6eoKCg_-kKPPBRAGnwEiABEEsAXJT-rQFQLwEsAcBgXAQBADjx_wHwAfAB" - "8AHAFgI9CHIDeAA_QAWiCecDPgf-A7IFlpb-l9sAlgbjCjzwAfAB8Gzwv2zwbPA8" - "8AHwAQDHAooB8A8B8AGAKwI8MLTXsiUAyxs53jka1xrADdMNBNAEWUQIDVDGAADE" - "WYS7WVS0BVlUrVlEAKYBFKCAAWqfWaWlpS4F_7AHGfUBAMUHJQKN8AFgTAIPSAw5" - "8AHw9iDgYGD0AG9v_1lZ_05O-P5FRQj9CP0ITVY0AzBAEBDfZWW7ujCh4KGhiYmJ" - "mfAB8AEw__kMXGTVA68C4QNcBAEA0wg_JAZc9Fz0AfAB8Fz08fEA8ezs7Obm5uD8" - "4OBuAXIA_AOGPQ8AW_j_AfAB8AHwATDJADECSQVW9Adc9F80GgSTk5OUlICUkJCR" - "j4-QAwAAjo6PjY2OkpJ-kiD0MzBQBCEEVgGmCJX8lZVTBAkDmAHxBXEHLQP4mJiY" - "OfBmAFcAXQBmMP9vAHUAFQCBAFk0XPRc9Fw0PzjxAfAB8AHwAZAHArCwRrBbAswA" - "bm5uQAi1xLW2FQC8vLwFBIoAwKSkpZqam-QAxgP_UghC8AHwAfBs8GzwbPBC8H8B" - "8FUC9_IB8AGQpwc8MMUA18MTwgUi2SIAENMQAs4CAMwYAADKWYQIDb8AALa9WSQI" - "DbZZJAgNr1lUAqhZFACkARmcAcB5n2yjo6OFAmAD4z43AZDHx8c7BIrwAWA4nJyc" - "NvAB8AFgy8sA3E5O8F9f_1DwUP5CQgj9CP0IfQOQwBER3HR0tLowJgf_3zIB8AHw" - "AQAnbJcC1QPbA-USALXSAba2igOTAyQG_xsA_wMFBAsEEQRc9AHwAfDxXMT19fVc" - "9EYChj0nM_8B8AHwAfAB8AEA2ABFD6MC_4QzAfBcxAFg6wPXASYEMgd4lJSVbQQM" - "ACD0AQCI4IiIjY2NIQQaBCQAMeUFqKioDwODB7S0frRQAeEA9wU58C0AxCSV_2MA" - "DABvADv9XGS8BwEwXAR_XQPDkPbwAfAB8AHwAWDVfNXVMgHtA8MA2ABDCH8gf4C7" - "u7sZAri4_rlQAXMCLgUQBQELbwkqAP8kAzYAS_AB8GzwbPBs8Gzw40vwAcDExMR3" - "AQHwAfAHAWBfCj9gJr8WDdCACxDREAHMAVlERQgNw1lkAQC6WVSzIVlUrAEAqllU" - "owEAAKEBIpgBlaH-kMMAYwblAuICopAcAtUAP4fwAWDtANjwAfABwFdXwOtLS_xM" - "TFkUCP0HCP0IbQOQFBTXkpL-qbow4gI-B5_wAfABMKEBj_lm8wDbA_wAs7OzXDT4" - "yMjIXPQXBFz0AfAB8I9c9FxkfgAtA76-vio5_yrzAfAB8AHwAWDGAKEBNA6PVvRc" - "9AHwRgiioqKaC_ienp_8AyHwAQB3ASEEiJqamnQHpqam_wPHgAdQBCEPvb29TQFu" - "BP828AEAUQBXAF0AYwBpACHw_1z0XPSyaPzwAfAB8AHwFQk_YwbqA2wA7A3tA4oJ" - "wMB-wcwAFQA6COcAEgBwAnrgenpycnMGAMINTwj_OQBdD1HwAfBs8GzwbPBs8P9R" - "8AGQUgL38gHwAZB0Bz07AQEAXsZTCcUCA4DMAwDJAADHWYRVCA28WVS1WVSuWVSn" - "CAEApVlEB50BPoyXHp8DAQCqqqoKAv9cZBwF-AGB8AFgdAToCNXwAwHwAfB9feRB" - "QfU8PT0I_Qj9r9gDMAQEMOQtLcu6MNMCeXn-eVz0AfABYNEHXGTVA4kBH-EDXATs" - "AQkAXATQ0NA_GwBc9Fz0AfAB8DMA-_v--1z0YwD5A7oG7ARyAP8J___zAfAB8AHw" - "ATBRAKYC2AAfiwIB8AEAXDS1AoGBgf_b82Jk0gb_Dxvwu2gGA0AC_8gEQQFWBEkC" - "CAo7BxoNMPD_ATBRAFcAVvQBYJMAmQBcNP8_BgEwtgS8AcBgVgTZDvnw_wHwAfAB" - "8LQAKwgyAc8AeAB4cHBxcTfSABwLmQycIJydkZGSagJ2dgJ3HgBsbGxoaGgYdXV1" - "oglmD8_Pz__C8QHwbPBs8GzwbPBs8AGQf4AEmAEB8AHwAZD9Aj8wqiDQphK6All0" - "wgCEAMBZVLkBALdZJLCyAQCwWSQIDalZhACgAQCeARaYAPhmm1I5BgEAKAUTAj43" - "_78BjQZ78AFgHgAqMAHwAfCBAfC1tdw-PuwI_QcI_Vb0AzAKCtxaWsa6ujDcBXh4" - "eKwCqPD_AfABML1goQHVA00E4QPpAf9c9FzEIwRc9AHwXPRc9Fw0_wELTwKmBXIA" - "0AUs8QHwAfD_AfABYOkHXATwAOoAAfCsAg-1AgHwAfABkG5ubnQcdHRVCJJkfQGe" - "np7_aQB0B1kHzwAFBEsPJmQtAP8zADkATvAB8AHAXJRZCmUK_70wXARZBFz0AfAB" - "8AHAegEjiQHtM46OjkMIgIDGgCU11g2pqap-CbwNAIuLjH9_f3NzAckAb2tra2Zm" - "Z8BjY2Nqamo5APAA_y8EKQqWAGDwbPBs8GzwbPB_bPBg8AEwggj38gHwAcCfBJ-f" - "QmBQvUAKvqgBAMRZhLtZVLRZVIatWVQIDaQBAKJZRAAImgE0kw2coT6aojO5AbEA" - "kwAfAoyM_ox48AFgjQY09QHwAfABkMBqauI0NPAI_Qj9AQPwAwPjHBzOm8abTxsB" - "AH5-fuEG4gX_q_AB8AEwlWHpAdUDDAPdASN3N1yU39_fBQTr6_7rXPQB8AHwXPRc" - "9G4B_wP_gQBpBlz0AfAB8AHwAZALB3HqBsrKyln0AQDcAlsAW1teXl5ZWVkOWAHw" - "AfABIFpaWmE8YWEpBMoLbcjOCpCQ_pDlyCcALQAzADkATvAB8B8BkJwA3QFc9IIy" - "3Nzc_1z0AfAB8AGQZQ0jAf4BYAAHqwAfCJyMsqamp50OnUMYkAwfC3Fxcm1EbW0k" - "AGRkZdIAX-BfX3JycgcOCj4BC__F8WzwbPBs8GzwbPBs8AGQ8I2NjYcB8AHwAbAr" - "AgE_MLTQsB6wBgXAvQAAvwAABgBZBK0IDbZZJAgNr1lUqFmEAJ8BAJ0BAJsBwBmV" - "AHKbYZ8zWgzxignGxsbaCm_wAWCWAx-EA8bwAfAB8AHAurrawDo64yYm8Aj9CP2B" - "A8AJCdpmZrW6MPiYmJiAAacBjwGx8AHw8QEAkpKSRZbVA9sD4QP_EgZc9Fz0XPQB" - "8Fz0XPRcNP_8A3UA1gUsCtkFjzcB8AHw_wHwAfABAMwAzQJgAFn0AQB3dQCvArUC" - "TAHwAfABUFL8UlImBE0H9gPM_AHwJwA_LQAzADkATvAB8AFgVlb-VpUE4wFc9AEA" - "0w5cBBcN__bwAfAB8AEwZgn6Au0DswcBnMytra2jo6SZIJmajY2OxgB1dRJ2nDxn" - "Z0MYX19gwF1dXlxcXIUClgD_PQJFAHwITzgB8GzwbPBs8I9s8GzwAfABMJeXl_3y" - "IwHwAcCamppCYIfBAH0RsgEEugEACwMAWTSzWVSsAQCqC1lUCA2hWXQYlABSjJY1" - "fgkBMKSkpLEA_8UKbPABYAcF654B8AHwAfABAZCRkdcpKecZHBntCP0D8ANgCAjb" - "CD09wbpgd3d3cfxxcVYKsfAB8D7BoALPA__VAHsD4QP4ART9FP1c9AHw8Vz0_Pz8" - "XPRjANcESwz_KAIwBro8KfEB8AHwAfABMP9_AtUAvgJGBQHwAQCrAyEA_1QGAfAB" - "8AFguDhc9AHwXDT_uDhI8AHwAfABkKsAsQC6MPFZBN3d3UQB9vAB8AHwcW4Bubm5" - "9QGc_Jw8oACgoJWVlomJiuMZC5ycYmJijgXqA_MJGJSUlO0DvgWsrKzjQgNaD8_P" - "z2zwbPBs8P9s8GzwbPBs8AFgiQRxBwHwHwHwAcBlAUIwHgN4t2tQFa0BCFMEtVlU" - "riFZVKcBAKVZhJwBAAKaARmTAFGVcDOeoZ2fM4QAbwN9HH19BwJp8AEwnJycP2kA" - "wPAB8AHwAfC6YH9_gNEiIuMTE-gI_QED8AEB5ggI2jvgO8GdnaK6MEgJpwH4dnZ2" - "uvAB8AFg1A1cZP_1AdUD4wHhA1z0XPQjBFz0_wHwXPRc9F0AegfaCnACEwv_dTAz" - "A174AfAB8AHwAWDEAn9DC-oAWfRc9AHwAfABAGbEZmZ6B3t7e0r0AfD_JwAtADMA" - "SPAB8AHwXPQmMfje3t5cBPDwAfAB8HwCf6UDogNUAJ8AnPyc_GoCZTxlZiIFpw3q" - "A0YOjIz-jO0DAQB2CIII8DMHC2zw_2zwbPBs8GzwbPDb8AFgKgD__fIB8AHwAQBj" - "CUIAaAF6BwCFsHonogkRqXgBALBZJAgNDABZZKAIAQCeWRQRlgEj4JIAY5ZLKAUB" - "YMsH4_sBXASFhYVm8AEAeAM_uvAB8AHwAfABkLowh4eAwyws2RMT4k30AQOQBgbe" - "CgrUUfxRubdgZgniMrrwAfABkN-vDoFgzwPVA-YhsYwBvAH_aAQSAO8H-QNc9Fz0" - "AfAB8PFcZPb29lzEewbUBNUGiMTExI0P09PT__M_AfAB8AHwAWCWCfINx8e-x-UC" - "AfBbBfYA9wKpAfD_AfABUFz0AfBc9EjwAfAB8H8BYKUAqwCxALcA-gVcBNv829vw" - "8AHwAcDMOZ8DnAxxeAxwcHCc_JzMEQRkAGRkYGBhXFxd4-0DAQCbm5uyBQEARgj_" - "-DSoDEUAITZs8GzwbPBs8I9s8GzwAfABMLu7uxgDxwHwAfAB8IuLiwYDRQABkwbD" - "w8OhrZ5XAKBDJZwDFqEBEAukAQhTBKQBAACiAQeeAQqbAQAVlgAkkgBLlDArip2C" - "WZT4AWpq4mr7B4GBgWPwAQDCBD-38AHwAfAB8AHwukChsgBWVsMgINgREYDeCAji" - "BQXkUDQAAwPkBATiBwcA3AsL1DMzw4T8hKm3YGkJ3AW68AHwAfD_lQF-MMkDzwPV" - "A7A6AQBcND9MCFz0XPQB8AHwXPTy8gDy7e3t5-fn4eDh4dra2gIBKAJ4AP8CBGoF" - "DwA5AynxAfAB8AHw_wEwVwDhAJAJWfQBAPwG8wD_hABf9AHwXPR9Ab4rAfABEP8b" - "ACEAXPQB8AHwWfSlAKsAx7EAtwCyCN_f32IE8PA_AfABwNgDSwD4Ck4PfX0Qfru7" - "vJw8qqqr_5z8nMxDCPkDKwKvBUY47TP_9gMPAJADHsZs8GzwbPBs8P9s8GzwAcCF" - "Bf3yAfAB8AEw-IqKiqsAawEkA30HeAAAlaCSb51gS5oAMECYISqVAikAkwA9lBpH" - "lSbAZphRjp2IVpSKCYhycnIlBXNzc94AiISEhF3AjY2NlwIDrpDsB7vT1nbL1gBW" - "x9VKxdU9wgDUWMbVbMnVpyc_9QHwAfDV1bqQlJQApmpqtj8_xTEAMcoVFdMREdMA" - "JyfKMDDFV1fwuImJp7qQWQQ4Crrw_wHwAfABAJ0CezDJA-EA1QP_qgFoOhsGegRc" - "BBsA-QP_A_8FBAsEEQQXBFz0AfAB8Fz0_1zE2AYaCtQ0XPQB8AHwAfD_AcBdAOUC" - "JAmOAgHAFj56BD-HMAHwAfABAFxkMwNubvBud3d4A_ADABsAIQD9XGSoAVBuAXID" - "V_AB8AFg_68-qwCxAIgCWQS4COrwAfB_cvNJNUMCmQCuDF0MnAynjKeonPyc_Hp6" - "ei4C_-0DSZ7tAw0LhgdiAWzwbPD_bPBs8GzwbPDe8AHAIAQeA_8B8AHwAfABMK0B" - "ogMnA7oAcbYBoqKiipMB8GMJg2CDg2dnZ88A-wF8fHx8kwlUwJYAUghWlJwA1dxs" - "1uNk2ekAZd3uYd3wXdwA8Fnb8GHb7l4A2OtAzOEpwNbwccfUzRfzAfABsN8CB7rw" - "AfABYJKSkmVlHmW68AHwAfABAJCQkP_XZFczeAO7ArYBXDS2AcEyH1z0XPQB8AHw" - "XGT6-voBXDTu7u7o6Oji_OLi0wv_APYDewDCCrcMf_gEEgBe-AHwAfAB8AFgxfzF" - "xWAAoglc9F80QALaBI8B8AHwATBcZHd3dz4K-Hh4eQPwAwAbACEAXPT_0ztX8AHw" - "AZClAKsAsQCbAX9TCj4B6vAB8AHA4AHvAY48jo6fDAkAvQOuA6SkgKWbm5yOjo-c" - "_AmcDF1d7RNaWlqV_JWVMAAFCkIP7WO4Ag8A__gEd2ps8GzwbPBs8GzwbPDjAfAB" - "kJycnAPzAfAB8BEBkIiIiDMJxsbG43gAqQKlpaWHA-MNAcCP8gfSALBq-Ad9fX2f" - "8wcBAA85RDfB2NqL3gDpvvD30fb8lADr-nbl917g9gBB2vQ92fNl4AD1fOT2d-P1" - "UwDZ7y7J4ia70_yZyvPwAfABoEA4uvABwP_rBaYIuvAB8AHwAWC_BHUw_6MCyQPP" - "A3UDeADxMiQGxwj4y8vLHgC7Av8DBQQLBD9cNB0EXPQB8AHwXGT39xD38_PzUQDp" - "6enA4-Pj3NzcigDEBf9zAnsACQA_MyPxAfAB8AHw_wFg4w0dCgEAog9gAAHwAfCf" - "AfABkFxkgwHlBXl5ZAe-ewPAFQAbACEAXGSnAVD_egfTC1fwAfAB8M8AWQT3Bf_w" - "BurwAfABwMoFtAOvAPMDQLCwsaamp1sCluCWl4uLi5z8nJxgA_97Bpyc7TPMAO3z" - "bPBs8Gzw_2zwbPBs8AHwAWA_AEYIAfAfAfAB8AHAyAHvBKSkpB-gDqIDxQEMABIA" - "k5OT8QwMdHR08gevCE4G7AE_UQbhADMASPAcBVlktNkA3rPr8vH8_roA8_x15vhg" - "4fYAXeD1Wt_1U90A9D_Y8VXc82gA3_Rm3vM-1e8AC8nqAL_gArDwzHrE0OfwAfAB" - "kOozP1kHYwMRAbowpg5smXV1_nWJAbrwAfAB8AFgXwrRZP_JA88DcgDbAzIB9gAn" - "BhIA_7wBIQD5A_8DBQQLBBEEFwSPXPQB8AHwXDT9_f1cZAjv7-9XAOTk5N383d1i" - "AW8AfgAxC9AFMwn__zNe-AHwAfAB8AFguwiXAn9MBa7zAfAB8Fz0XGQGAHaKdp0I" - "fAMAfHx9AwD_DDCyOFz0XPQB8AHwAWD0Bf9ZBJ4B5PAB8AHwYQXmAYoM42gHQAiW" - "lpZDCHUGrgDxnGxkZGWc_CgCpAHkA_-tAQEACQCpArYxwgG-BWzw_2zwbPBs8Gzw" - "bPBs8AHw9TEHA_MBAIsIqaiopaMioxJgm5qaEgCRkMaQLfABkIGBgXsD0gD_TQSN" - "CcMAkwMBAAkA4wHyB_h4eHj1BycAn_MBAIwEAZxgv9ndwO716gD7_Zbs-l_h9kBm" - "4vdr4vYDEOEA9Wzh9V3d80MA1_BR2fFU2fAALdDsCMfoAMMA5gC_4QC31H3-w9wb" - "AfAB8AGQNQE0CN4D_0wISgFJCFMBAQBZBLcAuvD_AfAB8AHAWwsGPMMDET1pAP-_" - "MfECOTx0BPMD-QP_AwUEfwsEXGQjBFz0AfBc9DkA-wD7-_j4-PT09AFRAOrq6uXl" - "5d783t59B1UF5AAXPXoKDwD_CAFU8wHwAfAB8AFgVwDtAP-KDKgPYAAB8AHwAfAB" - "YFyUIwEAkgGDg4QDAISE_oUDAAwwpzFc9AEAxwXmBP9X8AHwAcC1aKEB5PAB8AHw" - "I5gK8wOHh4dMCIuLjoyoAM8DQwhvb3AkAwF6B2NjY19fYFzEXF2cPJSUlO0z3Tr_" - "ATBCP2s67TNp8GzwbPBs8B9s8GzwafAB8AFgjIyMA4YxAcC3tbXPzc0DyTwkAMrI" - "yMXDw4-rACrwAfDaAX9_f4oDH9oB0gC0GlgmdwSAgIAP-GEB8BEBljDH1NW3oOvy" - "9P3-WTRpVlQFAxDgAwD0Yd3zLwDR7h7N7B3M6xANx-gCWQTC5QAAweQAvN4FrMj8" - "msQI8QHwAfD2E94D-QP_fwJsA7UItwC68AHwAfABwPiQkJBjMJkAbANdABE9GKam" - "pnEKZQS8vLz_6wUbAPMD-QP_AwUECwQRBH8XBB0EXPQB8AHwAQBclPAg8PDr6-td" - "AN_f8N_X19ftA1ICgQAHC_99AUACuvwB8AHwAfABwFAB_1wE9gC08wHwAfBc9Fxk" - "lAL_oAI_AwNgAQCpYlz0XPQB8P8B8AEArwipDgYA3vAB8AHw4wEAWALBwcH1AaIA" - "FAModnZ2QyhuQwhlZf5mnMzRCpsB7AqeAe2T9gb_NQSoBqoB5fJs8GzwbPBs8P9s" - "8GzwAfABwD8DCfOzMU0EA1MECQC7urrPzMx_HgMn8AHwAfAB8AHwAQCYBJiYlmCG" - "1eDq-gD9zPX8aeL3agFZpPRr4PNn3vIAU9nwH83rB8cg6APE5gJWNMDjAAC_4wC-" - "4gC2wNgbq8XFyeJLAfDHAfAB8MQIl5eXpfAB8P8B8AHwAQCtAb9kwwPJA88D_8sB" - "XAQ6CNcBNjztA_MD-QP__wMFBAsEXJQ49AHwAfBcNAD5-fn19fXx8YDx7Ozs5ubm" - "gwc42dnZMQLzA3IDs7P-s4EARgJbOBHxAfAB8AHw_wFgVACWCb80t_MB8AHwXPTj" - "XGHvCpGRkgNgDwAbYP9c9Fz0AfAB8JUBWQQ1AXo6_wHwAfAB8GQ1AQC3A9kLQwgA" - "bGxsaGhoY2PAZGBgYF1dQxjtA__3CxACHwKVAdcK6jMJAAIE_6cxnQLc8gHwbPBs" - "8GzwbPD_bPAB8AHwAQAvB48HAfABAACVlJTOzMzAvv6-BgCVASHwAfAB8AHwAfAH" - "ATA6BZMwu9HUlOKA7ej7_oDn-K-YAGrf9GPd81TZAPE_1O4kzesMGMfoAa9oWRTi" - "AL4A4QC94AC83wDwrs50sioTF_EB8AHw8cELioqKovAB8AHwAfD_ATBFBmMzvQOZ" - "ALYBvAFWATFcBK6urvwAdQDIyP7IIQDzA8QC_wMFBAsEEQQfFwRc9AHwAfAXbfz8" - "_AFFAPb29vLy8u0g7e3n5-fcC9ra_trhNoQwnw85CbcJpTYB8P8B8AHwAfABYHM-" - "YwACAWMAHwHwAfAB8AEAXPSTk5PAmpqblpaXAwAJAP8PAFz05AABMFwEPwlX8AHw" - "_wFgJgGSAVwEjjUB8AHwAfCPATD8AOYBdQONjY6cDP-ZAJyc8wOcnBBl7QOhARUA" - "_50F7TNa8AHwbPBs8GzwbPD_WvAB8AHwAQA8AEjzAWD7AsDOy8u6ubkb8AHwHwHw" - "AfAB8AFg0mZwy9cAwvH4tfD7b-MBr3hm3_NM2PAqANDtFcvqDMjoEAXE5gGvyL3h" - "AAC84AC73wC63cAAtdckpb0tAzkD_x3xAfABkFkB1z0B8AHwAfD_AfABAJgKbmG9" - "A2ADnABpAPikpKQnBnoKywF9BK4A_8cC8wP5A_8DBQQLBBEEFwQfHQQR_QHwAfBc" - "ZPr6-gFcNO7u7ujo6OPE4-PfC9TU1HcBYz__wAaEMLs4AfAB8AHwAfABkP_rAvwA" - "BgDA8wHwAfBc9AEwiISEhEoBnp6fAwB_CQAPAFz0XPQB8AHwATDd_N3dLwEGANLw" - "AfAB8AHwA8MJ8wOQkJFubm4RRgheXl5SCIuLi_9vBu0DIQ9tCO2ToQEwADgBP5c1" - "UfAB8GzAKw5skIaG_oZs8GzwbPAB8AHwAZBUAAaAAfABIJSTk83L_suSARvwAfAB" - "8AHwAfABMAdxDY0wXQNUzNzM9AD7hOb3bOH1agDg9Gjf9F3c8gA61O8TyusFxjDo" - "AcTnr_ivSLreAAC53QC43AC3gNsApMSaqawuDo-F-wHwATD2A5ycnJzw_wHwAfAB" - "8AGQugNLZgk_yQMRFQCioqLaAbKysvi6urrUB6sA7QPzA8oC__8DBQRcNBcEXPQB" - "8AHwXGQD9AhFAPf39_Pz8wDv7-_p6enk5GLkLQPW1tb2AEwCpfylpVUyeAMSAFv4" - "AfAB8P8B8AHAVwCmC2MAEQcMAAHwjwHwAfBc9AEAiYmJ0Dv_CQAYkFxk4QABMMoF" - "PDkB8P8B8AEArAiPAbgI0D4B8AHw_wHwAfCEAIoMewwYD2YG8gT_5wMBAAkAB2WZ" - "Ax8CEgCYAfHsCs7OzkjwAfAB8Gzw_2zwbPBI8AHwAfABwNoED_MhATCTkpLNNAul" - "pT8b8AHwAfAB8AHwATCdnQKdjTDJ0dJm1eUgufH6eOOvGGTeAPNH1_AizuwKwMjp" - "AsXnAFakr-gAudwAuNsAttoAALXZAKfHb6A-qjAD3gAp8QHwJwCNjf6NmfAB8AHw" - "AfABwN0BWjP_twNaA78BmQDoAmUH7wFCBv8SAJA87QPzA_kD_wMFBAsE_xEEFwQd" - "BFz0AfAB8FxkVg0A-Pj49PT08PAA8Ovr6-Xl5d_g39_X19fwM14CWwL_ggLjBLs4" - "I_EB8AHwAfABwP_kAG8AFwfCNAHwAfBc9Fz0HwHwXPRc9AHwAcDV1dX_WQQBAAkA" - "zPAB8AHwWJUBkP__AHUM4QPtM5YACQDqYwEw4w4EWwjAwMDtM5_wAfD_AfBs8Gzw" - "bPBF8AEw2PAB8EsBwDcIfwHwf39cBMz8ysqSARvwAfAB8AHwAfARATCjo6OKMLLQ" - "1AB_3eqZ6vhx4ST0ZlkkGcyvGADED1nEr_ivKFk0tNgAp_DJWZ2rugB-DDc-AfDj" - "AWBWAYODg5bwAfAB8OMB8AHAlZWVXmK3A70Dj8MDyQO1CNQBra2txAj4vr6-cgMh" - "AO0D8wP5A___AwUECwQRBBcEXPQB8AHwBwFgXDRcDfX19fHxgPHs7Ozm5uaMB_-V" - "B04JTwJwAjA86gmKAI4C_2HyAfAB8AHwAfCPAXIAAQAfZjAB8AHwAZBcNHp6ev9t" - "Dk30AZAhACcAXDTHBQEwj29mAfABwEQK4uLi6AX429vbxvAB8AHwAfABYP9gAAIB" - "sArVA-fDAQCBCagA_x4AcjaM8UVgATAMkBWQbPD_bPBs8GnwAfAB8AHwATBbAv8V" - "8wHwAfAB8AHwAfAB8AUEAYowpM3SWNLldwDi9Fjb8UDV7-AazOoFxq_4r_hWhCCz" - "1wCy1lwEUpzwq56enkwy8_B3YeQD_5DwAfAB8AHwAfABAEoEaTDHsze9A4w6oKCg" - "1zEhAP8wNucD7QPzA_kD_wMFBAsE_xEEXDQy9AHwAfC7aFYNSwDjYg1oDe3t7XQN" - "OQM2A_jT09PtA90ECAFFCbs4_xIAZPIB8AHwAfAB8AEA_g0fZwVmMAHwAfABkHV1" - "dY5sAfAB8AGAa2trXPT_AfABkBcBWTSjDsDwAfAB8D8B8GPABQHPA_wGRgipqf6p" - "6jMJAG8GZAjlNSQAnPD_AcACOgYAAcB0lGzwbPBs8A8B8NXwAfA2-YWFhX7_AfAB" - "8AHwAfAB8AHwAbDZAgGKMKjL0Bm_2TmC1FYE6w3I6QQI3QOv-K-4stcAsdYAALDV" - "AKXIVJuqeJycnMADugOC-ww8kvySko3wAfAB8AHwAfABAPiKiorjYUsDtTjDA_AA" - "4wEAvgiysrIzBs8z0AL_7QPTAvkD_wMFBAsEEQQXBP-1-AHwAfBcxFwNYg1oDW4N" - "_3QNOQOADYYNHQF5At8Fu5j_avIB8AHwAfAB8O5i8wAGAAcV8wHwAZCOjo5bWwBb" - "Xl5eWVlZWMcB8AGAIWBwcHAZCwEw_10DbgFX8AGQWWQgAbrwAfAfAfAB8AHwBTFr" - "Ab-_v_8WAhsPAQAJAOgFjwF8Bdj5_wHwXDQGMFz0bPBs8GzwAfCPAfAB8AHwAWCn" - "p6cb8z8B8AHwAfAB8AHwAcCbmwKbhzC4y84DtdGAIs7rEMnpAq_4A6_4r8ix1QCw" - "1AAAr9MAocRlmqV4mpqaqwa9Az4B8MC7_Lu7T_UB8AHwAfAB8AFgf8oC2pG1aFEJ" - "zAOkCjYGvPy8vI865wPNAtMC-QP_A_8FBAsEEQRc9AHwAfBcxBc9P2INaA1uDXQN" - "HgmADdjY4tiwDcjIyG0CSwnvBP-7OHDyAfAB8AHwAfAVkwHwBwHwAcCcAISEhFpa" - "jFpMAfAB4FZWVhENOI2NjVz0AfABMNXV8NXj4-OyOLTwAfAB8B8B8ARiAQBrDQYP" - "n5-fxzQIJD97A6GhoUYIHwL_hgEuPgUEHwKSAakC0QoGAH-9AC4CRgI5ADAANQcP" - "ANL80tIGNl0wAcB48HgwcwKPQjCIAnUARQDCwsLWCI94AJMAhAAbALa2tv0F_zwD" - "EwKKMLP0AfAB8AEAVADwg4ODfQHwAfAB8AHwHwHwAfABgP4BimATtMyAC8XlEMfo" - "Aa_4A6_4r8iv1ACu0wAArdIAm76Imp7_XAQfCC0DPAPtkC8BrgyH8P8B8AHwAfAB" - "8AEwvATgMbMx_7U4ewCnAeMBUgKgC8IBRgL_nwDhA-cD7QPzA_kD_wMFBP8LBBE9" - "XPQB8AHwXPQGCWgNA24NGAnn5-fh4eEY2traWwKfAMHBwY9wAosFIQ9wAsbGxlsC" - "_3vzAfAB8AHwAfCpkjRuAfD_AfBfNCMEL2QB8AHwXPQB8P-EAJQOVgQBAEEKIfAB" - "8AHwDwHwAWBlAeYBkZGRaiBqakhISH4Jk5MAk5SUlERERG0EbW2QCZCQkENDAkPW" - "NYmJiS8vLwg7OzuKBpeXl3Ngc3NgYGBSBYMEYwBjYywsLGlpaf9LAOAN_DlZ9Egw" - "ePB4AH8CgUsAODg4X19fUwQP7AEVAAEA1DRJSUlcAFxcU1NThoaGAUIDTU1NPj4-" - "efx5ecsEVGAB8AHwAZDuAv8h8wHwAfAB8AHwAfABkOMHA2ADhzBNuswButs8A8Kv" - "-K_4r-hWBKzRwACozBuYtBcBAQCPMAPuNV0wFQaYmJiB8P8B8AHwAfAB8AGQIQZ1" - "MKUD_78BsQO3A70DLQbyBCUCxAj_2gEzBrMB4QPnA-0D8wP5A___AwUECwRc9AHw" - "AfAB8BfN-O7u7h4JoQcqCaoHFz3_eAOcAP4E8gSBAywBtPwB8P8B8AHwAfDSk_oC" - "ATAk8wHw_wEAMgFcATj0AfAB8DkAPwAjVPDgBN7e3lk019d-16jwAfAB8AHwXMTt" - "AFCMUFBUBlwBlZWVmgJ4aGho9gNVCLYKog9y_HJyCQCpCNENgwEXMXoEYxsAFQCd" - "nZ1FAEgPV_xXV9MCNgC3AAM2BgABwI9clHjAYwALAVJSUv4NxyEAlwgMAGVlZYQD" - "XQAIVFRUCQB3d3d6_Hp6YwABBZ8DFAFUkAHw7wHwAZB4AG4KfAHwAfAB8D8B8AHw" - "AfABgNUAhzCdxfDMALHPr_iv-K_4WRQAq9AAoMNOmakHXGQLClw0wMDAiIj-iHvw" - "AfAB8AHwAfABkGkA__UxuAJZASgCsQO3A70DIgL_Ef3hA4UL7QPzA_kDEf0B8I8B" - "8AHwXPQXneXl5Red_34P-DSFApkwTALGAIrzAfD_AfAB8AHwATD_AIEAPAMBAP8t" - "AyHwAcBNAaIApgU79AHwPwGQMwA5AD8AUcBsA-Li4uJWBODg4IQAcvAB8P8B8AHw" - "AcBKAecADQiOCGgB43cB8wNKSkooCNgDZgYPHwjUCmYGigZGRkZRIFFRgICAvgI3" - "Nz43KwIaAZwAMwDiCHFx_nEJAEdkXTAB8HjweDAVAD-lDDsEUQBvAH4A9gZeXsZe" - "eAA8CTY2NgwAbgT_iQ0eACcJPAlFAMcCEALY8P8B8AHwAQBjACfzAfAB8AHwHwHw" - "AfABkB8Fh2A7tMl4ALbXr_iv-K-oVgSqAM8ApsoNlrWS-JmamQEgFG0lCKoBePD_" - "AfAB8AHwAfABwA0C1zGPAY-lA6sD1gKNAJubm0AC_xE9mQARze0DEZ1c9AHwAfD_" - "AfAX_RfNwAarANEH8gR_Av9vPyYNnABc9AHwAfAB8AHw_1_0hwApB9ACOQMk8AFg" - "AwP_OwH5CV_0AfBZlDMAOQA_AP9LYF0DdwGvONwFePAB8AHw_wHwAfByaQ0C5Awv" - "NJQCyQP_cgYHAhsAigz_BgIBuAJoBPi4uLgYALQAJAAaATww8YQGPDw8uga6AF4y" - "8DMfCTABwHjweDBrAWRkZB9HBPsNFQPTCJoIPz8_AfIBb29vgYGBjRyNjRQN_gE5" - "AE5OTv-KAJUBDwBc9AHwAfABYH4J_nsB8AHwAfAB8AHwAfABgAM4B4QwpsXLBanG" - "OAC42q_4r_hZpKnOwACdwFyXpVk0uAj_Igh_BTADb_AB8AHwAfAB8P8B8FoGM2aS" - "AbMBqwOxA4cAj1wE6AJPAqgJurq6vAH_JAA6AhH9Ef0B8AHwAfBclOOqBFcA9vb2" - "F21UAxdt43UDLAGcnJztCTwMSgf_Gj2-OAHwAfAB8AHwAfABYP_bA8QCjQABABQB" - "6vNWYZkA_yYEoAWoAAHwAfABAM01OQD_LjWcA_kAKQoCAQ4NXjUB8D8B8AHwAfAB" - "8AEAeQh0dAJ0XwGzs7NISEg_bgFoAQkAkwBTBBACNTUGNSgC9gCHh4dubo5ubQsR" - "BDQCOTk5CQDHsQbNAiABW1tbSQhOAD9EZGVkAcB48HgwFQBNTT5NeAC8BCEJJg0t" - "Az09_j38BhgAigD1BK8IOQDaBP_BCMcyXDTY8AHwAfABMOcA-H19fTDzAfAB8AHw" - "AfAHAfCTk4dgdbfEAK1wzgC22a_4r_hWRKgAzQCdwTKWrZj_AVDJA64GqgFs8AHw" - "AfAB8P8B8AHwPgElBe8xDj2lA9wC40kC7ASfn58ZAg8A7Ar4vb296QERPegC5QIR" - "bf__AwUECwRc9AHwAfAB8Bf98Rc95ubmCQw2CWwDrgD__gFPAjgBxwJkAskA1gIY" - "AP8o8gHwAfAB8AHwAfCKALwH_6gM5A8aASY9JMAOOpoFuAX_AfAB8AEAvDc8AF0A" - "TQRWNPHQBdnZ2YTwAfAB8AHwAwHwtMCLi4tiYmJxHAiCgoKrAwcCugNg4GBghYWF" - "MwwrCAwAgU8IiYmJWVlZDwAH4AqBBrQMeXl5dnb-dm4ETwLeDM8ArGieMQYAjwHA" - "CGR48HYCeHh4vgj_UwS4CLI1uwjZCIYEZgCIBf8qACcAkgQJAJIBXPQB8AHw_wHw" - "BgOMDQHwAfAB8AHwAfAfAfABYNkChDDJAGSvvsAAqsoAsdSv-K_4AACnzACcwDCV" - "cK2UmJlZNDgBpwFz4HNzd3d3afAB8AHw_wHwAfAB8P0FwzDgAc0CnwP_pQNQBLED" - "XAQeAOQASQJoCv_JAyQAuQHbA-ED5wMR_Vn0HwHwAfAB8BfNvATy8vKBJAno6Oji" - "4uIcC-NyA2ECxMTEuzgvB10J_0cHpQBtAqwyAfAB8AHwAfD_AfBx9xcBWQQaAfwD" - "AQCfAP9lBKzyAfAB8Kk48gdIAO0A_1YEAQALDV0A8vcB8AHwAfD_AfAB8AEASAbp" - "NCcJUwEcAv9xAQEACQAbAP4EjTYqAHcB_wkAegEYMAwAEgNwBSoAbQj_CzGVMQZg" - "AcApnXjAEgBRAP9cBBgAfgBmMFcAVAAPAEQ0_wkAIQAzAAkAXJQB8AHwAfD_AWBF" - "AOwHNvMB8AHwAfAB8D8B8AFg-AfRBywxXwFzqICzB6C-AKnLr_gBr6igxACXu0eV" - "Bqh5BQFgf39_ZmbwZnFxcfQFafAB8AHw_wHwAfABwDgB7GGzAcMAnwMfpQOrA-8E" - "BgAQCKysrPH0Dry8vLYBAQI2A9sD__QC5wPtA_MDtTgR_QHwAfBHAfAX_bYE9_f3" - "HgnvAO_v6urq5eXl_8gHJQiyAvsBZAKiAJoFYwb4rq6uawTZMl_0AfAB8P8B8AHw" - "AfABYN8CYgQxDgEA_yYBAQARBDIB7gI8DET0hMzjSwOQA9zc3FNkAQAPAP9UAGzw" - "AfAB8AHwAfAB8GFo_wHwAfAB8AHwAZDzM_wzAZD_sGHNyIrwAfAB8FEwBPIB8P8B" - "8AHArwvgDTzzAfAB8AHw_wHwAfABYBQEeAOEAJwDTgwAlaSnP5yvAp0AvQCkxgCo" - "zAAEqc1ZRKfLAKXJAACfwgCYuimVMK57lp1ZlK8IYWHwYWxsbLX4AfAB8AHw_wHw" - "AfAyATgBvDGMAZMDmQMfnwOlA6sDtQgJAKioqP-1CPkPwwMkAAAD1QPbA-ED_-cD" - "7QPzA_kDtThc9AHwAfAHAfC7-BgJ9fX18fGA8ezs7Ofn5xsMj70DigOTA7cwnZ2d" - "tAD_CgV7D9UA3jZz8gHwAfAB8P8B8AHwg8d2CCYEpQAaBAEA_0MOAQA4AQEAepQB" - "AB4ABQr_0gABAFMEATAMABsAUwShB_9g8AHwAfAB8AHwAfAB8AHw_wHwAfAB8PnD" - "AfAn8AHwAfA_AfAB8AHwAfAB8AGQq6v-q_IHAfAB8AHwAfAB8AHwfwFgXwTaByAB" - "DgpRDJMDigCYm1uYpSyYsAAemLMCmLkAlwC5GZazJJWwTCCWp4CXnFaUlJSAlGho" - "aF9fXwYA_2IEWA5g8AHwAfAB8AHwAcD_MgE7MYcDjQOTA5kDnwOlA4iWlpa1CKSk" - "pBMI-LOzsw04yQPPAxgD-gL_4QPnA-0D8wP5A7X4AfAB8B8B8Fz0uzgXPcgE7u7u" - "wOnp6eTk5KUDvQP_7AS9APgEdwG0AB0Nwg1KCv-BCX4PsvIB8AHwAfAB8AHw_wHw" - "ATCvCI44OAG0ANsMAQD_TQQBMFM0ATB9BAUNUATVAP-t9wHwAfAB8AHwAfAB8AHw" - "_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwATBeAv9C8wHwAfAB8AHwAfAB" - "wNkCx-8N1gKiA6KioqIDRJQxAfCSkpJTDVYEYGDiYFkEcnJyWfQB8AHw_wHwAfAB" - "8CwBRWaHA7cAkwMjmQPGAJWVlbVor6_-r7U4wwP0AiQDIQPbA-ED_-cD7QMRPbX4" - "AfAB8AHwAfBHVAAXPcIE9PT0uzjm_ObmuzgfCLtoCgUTNTQFiLi4uNIAxcXFtwD_" - "I_cB8AHwAfAB8AHwAfCe9_-LCAHAy_cB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHw" - "AfD_AfAB8AHwAfAB8AHwAfAB8McB8AEwfQeFhYWiAwHwPwHwAfAB8AHwAfABkH19" - "_n2TA-gC7AcBCKgDRATxAgcBwFYE3gNeXl5dXQJdCw1paWlxcXF_Dg1R8AHwAfAB" - "8AHwAcCE_ISELAEyAXsDgQOHA40D_5MDmQOiMAkAwAC1yMkDtTj_2wO1OO0D8wMR" - "PVn0AfAB8I8B8Fz0Fz27OPLy8rto_-kH7AQZCLs4BAXQNdkFEwX_tzA3BV_0AfAB" - "8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHw" - "AfAB8AHwAfAB8I8B8AHwAWDZC35-fk7zfwHwAfAB8AHwAfAB8AEAisSKiugCn5-f" - "6wKoAwCbm5uNjY2LiwCLfHx8a2trXAxcXFkEVgRmZmZt_G1tCw1Z9AHwAfAB8AHw" - "_wHwAQV4A1k0ewP1N40DkwNxrgCTk5OuAAwAtQiw_LCwtWjDA8kDzwO1aOcD_7U4" - "Ef0B8AHwAfAB8BedwgSI9_f3zgTw8PC7-MfGMLEAZA6YmJjECCYN-Ly8vF80KPIB" - "8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHw" - "AfAB8AHwAfD_AfAB8AHwAfABYIMHwwOuAw8B8AHwAfABwIuKip7EnZ0DkIGBgSfw" - "AfCIdnZ25wxubm6XCGFEBGNjY2IBIAkAZRxlZRUAVgSvCHV1df888AHwAfAB8AHw" - "AfAwCYsI_ykxWTSOBYcDpgiTA1EJEzXxtTi0tLS1-LU44QPnA__tA7U4XPQB8AHw" - "AfAB8Lv4f7v4rQ3PAH45ugDGANQNpvympuoA3gC-aAHwAfAB8P8B8AHwAfAB8AHw" - "AfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB" - "8AHwATAtA0UDD1rzAfAB8AHAnZycxATCwgOQv729hoWOhSrwAfBiNHR0dGUE_UcE" - "cAFQDwCsCBsAMPAB8P8B8AHwAfAB8Bs5oAgjMVlkD4EDhwONA5MDlZWVnQCdnaOj" - "o6mpqfixsbGrA7EDtwO9A7X4_7X4AfAB8AHwAfBc9Lv44AQI5-fnuzjT09PMjMzM" - "5g2pCLW1tXgJx345ewkPAL6-vl_0AfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB" - "8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfB_AfAB8AHwAfAB8AHwAcCq_Kqq2zMB" - "8AHwAfABYFwUIMHBiomJDzCUk_6TDwAVACrwAfAB8AHwAfD_AfAB8AHwAfAB8EQE" - "WWSKAxF1A7a2tkcEnJycB40DXASQA6CgoKen8Keurq6WA6sDJwC3A_-9A7U4zwO1" - "-Fz0AfAB8AHwRwHwu_i7OPPz8-AE6gTq6uwE3t7e19cA19DQ0MnJycHgwcG5ubkW" - "BSUF0gDH2ADeAF9kxcXF-QAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB" - "8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AEwH7cGX_QB8AHw" - "AZCcm5vAw8HBpqSkCQADAAC1s7O-vLyAgP6AKvAB8AHwAfAB8AHwAfD_AfAB8AHA" - "6gwXMWMDaQNZZCOpCIcDkJCQDg2enoCepKSkq6ur-Qn_pQOrA7EDtwO9A8MDyQPP" - "A__VA9sDtfgB8AHwAfAB8AHwAbvI-Pj49fX18QTx8bs44uLi29uA29TU1M3Nzd4A" - "8dgAt7e30Q3SANgA0gD_zwDqAF_0AfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw" - "_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfCABwkMafMB" - "8AHwCVx0wMADkK-uroT8g4Mq8AHwAfAB8AHwAfCPAfAB8AHwAZCioqJZxAeBM3sD" - "gQOPj4-Wlo6W5gcnAG4EsLCwHgD_JAAwALEDtwO9A8MDtTjVA__bA-EDtfgB8AHw" - "AfAB8AHwI7v44ATv7-_sBObmAObf39_Z2dnSYNLSy8vL3gBvCbT8tLR-CdgAXzTq" - "ANIwX_T_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHw" - "AfAB8AHwAfD_AfAB8AHwAfAB8AHwAfABwICzs7N7e3t1AfATAfAB8HV1JwzCwMA4" - "iYiIDzBWBA8Aqqj-qCrwAfAB8AHwAfAB8AHw_wHwAfABMFkEwAlZ9G8DygjAlJSU" - "jo6OBgAObfitra2ZA58DKgCrA7ED47cDvQPl5eXJA88Dtfh_CPQB8AHwAfAB8Fz0" - "u5jyAPLy7e3t6OjoAOPj493d3dbWgNbPz8_IyMgaPTF7CaOjo94wvmi9vfC9wsLC" - "6Q0LAQHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB" - "8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwATBLAzh9fX148wHwAfCbmuKaTQSlo6MJ" - "AAMACQD_DzAq8AHwAfAB8AHwAfAB8P8B8AHwAQATDroJBTFRA1cDH10DYwNpA28D" - "WTSSkpKImpqaaASlpaUOnf-lA6sDsQO3A70DwwPJA7X4f1z0AfAB8AHwAfAB8LuY" - "9wD39_T09PDw8CNLCbsI4eHhGp2_vwK_9QGxsbGhoaH4jIyMHZ1f9AHwAfAB8P8B" - "8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB" - "8B8B8AHwAfAB8AHwu7u7OIWFhX7zAfBc9MK_Ar8DkKmoqHl5ef8q8AHwAfAB8AHw" - "AfAB8AHw8QHwenp6KQRZ9Fk0bwMBdQOQkJCZmZmdxJ2d4weqqqoO_bED_7cDvQPD" - "A8kDzwPVA7X4AfD_AfAB8AHwAfBc9NQEfgDgBH8_CbsIGj1XCRqd8ADtALUgtbWv" - "r6_sDYuL_otDBfYA5AAVAF_0AfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHw" - "ImIBAK0KGgH9AQDAAYCpBQHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfABYAKR" - "FDEdASYxOvIeAP1RA3QB8AHwAfAB8AHwAfB_AfAB8AHwAfAB8AHwAbCG4IaGurq6" - "WcRXA10DB2MDaQNvA4-Pj5iY4ph7A6KiojgNhwN4M-MwAA797u7uyQPPA9UD_9sD" - "tfgB8AHwAfAB8AHwX_QHGp1LCRqd19fX0NDw0MnJyfMAUQbgDboJ40AF8ACRkZHt" - "AOoAJw__GwA0MjGSAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHwP0pkTTSnCk4D" - "CQ80Ari4_rgHAiMB9glsBhYCQQEB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHw" - "AWD5AP8ABQH_CwERAXQH0AsjAXoHOmI9wv9bAioMjfMB8AHwAfAB8AHw_wHwAfAB" - "8AHwAfAB8AEwMgT_9jA6MgsBSwMjAfwAXQNjA2NpAw4Nl5eXDwA2DKfgp6etra2H" - "AycAMAD_mQOfA6UDDv0O_QHwAfAB8D8B8AHwAfBcNBr9Gj3n5wDn4uLi3Nzc1eDV" - "1c7OztQNMwMtAxH5AKysrPgNioqK46gJCQCmpqbSCRsAUQP_X_QB8AHwAfAB8AHw" - "AfAB8P8B8AHwAfAB8AHwVvRTZFAEjyYBUQPUCiUCpKSkbwb_GQKHBgEA1woBACsC" - "AfAB8P8B8AHwAfAB8AHwAfAB8AHwHwHwAfABwPAw_DCgoKD_CAERASQDmAcjASkB" - "aAQqAw87AUEBQMKABIeHh3P_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfABsK0NSAPg" - "mgIB_0UDSwNRA1cDXQNjA7IIWQT4m5ubOwEYADMAMgEXAf8sAZMDmQOfA6UDqwOx" - "Aw79_1k0XPQB8AHwAfAB8AHwu_hju2hLCfPz8xo9vA3g4ODg2trazg3UDdoN__8A" - "OQP_AOQANwL8AFU1BA7_KAIlAqgJh5YB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw" - "HwGQbzaABxkC2DmpqanHTQRLA-EJlJSU1wooAv6NASCHBgEAOAEB8AHwAfD_AfAB" - "8AHwAfAB8AHwAfAB8I8B8AHwAQD2MIyMjAIB_y0DjwcXAYyXNQE7ATMDQGKBQ5Kl" - "paV4eHif8_8B8AHwAfAB8AHwAfAB8AHwxwHwAfABMImJiS4CQJL_-QA_AwgBSwMa" - "AVcDJAAsAfiWlpZZNAUBqwZ7A4ED_4cDjQOTA5kDnwOlA6sDsQP_twO9Aw49tfgB" - "8AHwAfAB8D8B8AHwX_QabbANtg3p6Q7pwg3IDc4N09PTzPzMzBMORQPeAN0K6gA_" - "A_8CATQCKwL_ABIArjkf8gHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8Fb0PwZWlFoG" - "FAEcAgPOOnQHhISEgoKCCn8BIH0BIHx8fHv_AfAB8AHwAfAB8AHwAfAB8P8B8AHw" - "AfAB8AHwASDzAPww8QgxgYGBFwEdASMBdgX_PwM1Ac0I7jtiBCnNAQASAP-jBaXz" - "AfAB8AHwAfAB8AHw_wHwAfAB8AHACwHLCkBiCG0f-QA_A0UDSwNRA4iIiOMaMVkE" - "n5-fWTR1A4AExwsBhwONA9LS0pkDnwP_pQOrA7EDtwMO_fDzAfAB8P8B8AHwAfAB" - "8Br9Vwm2DbwNAcIN5OTk3t7e2ADY2NHR0cvLy_9fNOQAXzT5AAgBnAb7AfsK_zgB" - "WwUfMuNqAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfBW9OPkABYCtbW1VgR9ByEDjxkC" - "mwdTNEIDgICABgDvAQBfBBIAATCFAVAS8AHwvwHwAfAB8AHwAfABkIYBIP-3AAEA" - "zgEBADMGAQBwmEoH_0cH8QIBADoFATD1AQHwAfB_KgA2AD8ARQArAloAIzGD_IOD" - "BgAPAGAAOwHHCGIE38Q4rwVD8lsCSgFyAfAB8P8B8AHwAfAB8AHwAfAB8AHw4HJy" - "dHR0dQZzCEPC_y0D2AA5Az8DLAEgAR0BMgH_Cz1ZxN8IRA2HA40DkwOZA_-fA6UD" - "qwOxAw79XPQB8AHw_wHwAfAB8AHwu8hfNFcJqAAHvA3CDcgN4-Pj3d3w3dfX1180" - "3gDDOV80j9cKfwIRAToCmJiY1QnxgQa2trZFbyIyAfAB8P8B8AHwAfAB8AHwAfAB" - "8AHwjwHwzAACbeQAr6-vrAj_FgJvNjMzAQCrA5xjn2MBAP-oYwEwbzMB8AHwAfAB" - "8AHwfwHwAcCBAAHAPjRBNBo0k_STk94AlQFQTQcBMP0C_wHwAfABMDZgRTBRAFcA" - "bwD_3jngBAYAHQEVAGIETgPEOP9lASY9Q5J0Af0IvfMB8AHw_wHwAfAB8AHwAfAB" - "8AGQDQL_hAZDYgEAGwMhA0MC6gAzAx85A48B3DV0AVmUqqqqx2kDKgBZNMrKyocD" - "jQP_kwOZA58DpQOrAw49WTS1-P8B8AHwAfAB8AHwAfAB8BrNR6gArgDCDevr68AA" - "4gDi4tzc3NbW1vjQ0NDeAF808ABfNAIB_xEBUQMrAjcCwgeIBRsAPg3_HPIB8AHw" - "AfAB8AHwAfAB8B8B8AHwAfATwlgFsLCw_-QA_gEwA6xoAjEPAKUzuWf_n2NjY5xj" - "q_MB8AHwAfAB8C8B8AHw0wIBkJkBsJqa_priAgEw8wMBkE8FAfABwP8zwEtgVwBg" - "MHIA-QAaASwB_7AHPDljPz8AtQVHBGQCxDj_RsKAARcKxvMB8AHwAfAB8P8B8AHw" - "AfAB8DIElwicxhUD_xsDIQNDAu86NwL_AAUBDAD_WTQgDbJoWTTuCHsDgQOHA-ON" - "A5MD5ubmnwOlA1k0_w79AfAB8AHwAfAB8AHwAfA_Gv1fZLwNwg1fNMYA4eH-4V-U" - "6gC-mAoCKwJYO1oD_8Fo4Gpi9AHwAfAB8AHwAfAfAfAB8AHwAfBWxLy8vP8TMksG" - "rgkEAmw23At6Bx8C__kA7wdKNDYDrmOcMzADSQL_ATCVBAEAQAIB8AHwAfAB8L8B" - "8AHwAfABwNUAAZCiAeD_1AEB8AHwAfBCYFcAaQD8AP8FAYA0HQEjAU0BhQXgB6MF" - "_1MBoAVBAScApTZnAn0BB_vDtwOQA3Nzc3EB8AHw_wHwAfAB8AHwAfAB4FYEDQL_" - "FATy-g8DFQOvaCcA8ABZBP8ZPhIAFAFZ9FnEjQNZZKUD_6sDWcRc9AHwAfAB8AHw" - "AfAfAfAB8F9kVwlflO7u7hjq6upf9B2drKys_18EMQIfMvIBKwImARUAQg__EQpi" - "NB8CZTQB8AHwAfAB8P8B8AHwAfAB8AHwDTKxALcA_6w4VjT4AVY0vwoCPVYEegd_" - "UwSaCBsATTRKNLEzRgKkjKSkLgIOAaenpzAD_QEAqQFQVAAB8AHwAfAB8P8B8AHw" - "AfAB8AEw3AIB8AHw_wHwAZBOYO0w-QACAQgBDgH_FAFuNEQELwE1AVoDNwJZAf-v" - "BWY_UAFEATkAqQhpP6g240bCPAB8fHzk8wHwAfB_AfAB8AHwAfABMIcAmQm5_Lm5" - "RvIDAwkDDwP_ABsD_-oAJwPvOpMGPQI6AggBEQGPIQBRA1U1WcTb29tZNP-TA5kD" - "WZS3A1n0AfAB8AHw_wHwAfAB8AHwGv3-BKgAvA349_f3X_TeAF-UNQHOB_9CBosF" - "NwIuAjUBfjbgCjIB_yM9HJJi9AHwAfAB8AHwAfD_AfAB8AHwVpS3AL0ArDj-AeNH" - "BFY0goKC4QBWZBgD_1UCNALwAFA0PwMRARgDYwP_HQFVAuULSwA0AgEAMgEBAP6z" - "AfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AGA6jABAP_2AP8ABQELAREBejQcAksD" - "_2gEZTRBAUoBwgcLCmIEawH_2gdiNC0APwBvA2L0ATDSA_E5AHl5efPzAfAB8AHw" - "xwHwAfABMHZ2dgcC2wD_RvJGkgU9LgI6Akg_XwQ5AP-lCT0CGwA_AzQCHQEvAVcD" - "_zwAYwNZ9FmUpQOrA7EDDv3_AfAB8AHwAfAB8AHwAfAB8IFf9PT09PHx8b74gfAA" - "y8vLxcXFQQH_1wFGAjQCSwMoAiICzgd-9v9iNMEFjgVi9AHwAfAB8AHw_wHwAfAB" - "8BCSnwClAFbEXQb_ljlWBBMC_zxWNKk45AAcAv9TBEMCNgAiAm0CUQMaAQcL_04A" - "GAMxAiwBXQABAHMCATD_8QsB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHA4WDwMPkA" - "__8ABQELAcIHFAFCA2IBaDT_ZTSUBUcBTQFsA6U2AQBiBP_EBWgBiQFiNEYyqPYB" - "MBsAAagDgYGBdHR0cJ8B8AHwAfAB8AHwcHBjAP8BAtkCsG1G8kYyJADyakg__wYA" - "1QAIPUACWAU5AwUBRQP_MQIsAd4AXQNjA1n0jQOTA_-ZA1n00vMB8AHwAfAB8AHw" - "PwHwAfBf9F_0X_TwANHR4tH8AMbGxk0BST5OA__-AWADBwKOBfAJxDhiZJcF_z4B" - "KgBnAnYCZTQiMgHwAfD_AfAB8AHwAfABwIQwjQCTAP-ZAJ8ApQCrABAyOwesOEwC" - "_wYArJhWZBUDGwNTNEwCHwL_PwNIAxcBZgMGM3YCew_jCv8BAJAGOgIBkEcBAfAB" - "8AHw_wHwAfAB8AHwAfAB8AHwAfD_AQDbwO0AAQD2AP8ABQHON_8XAR0BbgEpAS8B" - "ZTRBAXcB_8c4IARfAccFzQViNMRoRvIfRvIBMNsDMACCAnp6eg8d9AHwAfABkHJy" - "coD8gIARBIEAeABG8kbyRmL_VjQuNbgIrzgIbTMD_AA_A_82ABcBTgBZNGMDaQNv" - "A1n0_1n0zPMB8AHwAfAB8AHwAfAfAfAB8LM9X_Rf9NfX1wDS0tLMzMzHx_7HNAL8" - "OaoHLg4gPTgBEAL_HDJiNFABFQBKAasGKgBYAv-jBX5jHPIB8AHwAfAB8AHw_xBi" - "6DKHAI0AkwCZAJ8ApQD_qwAQYsMAKAXMAFb0CQPtAP8ZAkkCPwAcArg7ZAJgAFED" - "4yUCIAHIyMjdCi4CLwH6zQGAzgHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfABUI_q" - "YPkA_wAFAcnJyREB_xcBHQE3AjY8ZTTKCEcBgwH_YpTECI0DYmSoNkbyRvIB8H_F" - "BEMIPAAhBtEBKQQBMHX8dXVBZIYEGGB_Cz8J5gH_lgBjAFHwRvJG8kaS-wpRAP9D" - "YghtMwM2AD8DCwEUASAB_y8BXQNjA1n0WfS0M8zzAfD_AfAB8AHwAfAB8AHwAfBf" - "9ANf9OoA4uLi3d3dwNjY2NPT0zoCPAN_lgZRA1oDIG1fAZgBRAGN_I2N9QeiMygC" - "-gUxAjoC_15uVgGYAUs_hPYB8AHwAfD_AfBgltMydQB7AIEAhwCNAP-TAJkAnwCl" - "AKsAsQC3AFbE_yEAVpRGAu0AQgDiMlNkvwfHRQNOAx0Bz8_P2gorAuEsAdTU1NUB" - "IJAGAfD_ATBSAgHwAfAB8AHwAfAB8P8B8AHwAfABwMbwAWDwMPkA__8ABQGMBxEB" - "bjRoNNYINQH_RWxAAsIHKT1iNHEB4wfECP9GYlICRvJG8v7xAfAB8AHw_wHwRvJG" - "8kaSBW0ZBUNiIQP_JwMIPTkDAgFFA0sDHQEsAf9dA2MDaQNZ9Fn0xvMB8AHw_wHw" - "AfAB8AHwAfAB8F_0X8QI8vLy5ADr6-vnAOfn4-Pj3t7e-NnZ2TcCLgKTBiUCcQH_" - "ID3mCoE2MAxPDn42ZQFaPMfBBWsB1gWcnJy7lek6_0ucIvIB8AHwCvKLy2kAY5n_" - "hwCNAJMAmQD_PLcGEDJW9P_SAFaU_z8ZAnIGjwccAlMEP7MHRQMiAlcDewMoAtra" - "_to2D4cGAQA0AgEwQQEB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AEA52D2MP8A" - "BQH_MQIRARcBHQEjASkBZTQ7Af9aA6QBYsTEOHcBYpRG8kby_0byAfAB8AHwRvJG" - "8kbyRvL_BT0kAA8DQ2InAwg9WgA_A_9FA0sDUQMmAUEBYwNpA28D_3UDewNZ9Fn0" - "AfAB8AHwAfB_AfAB8AHwAfAB8F_0XzT1RPX15ADv7-9fNOTg5OTf398xAisCmwf_" - "JQKwB2MDfQHBaDAMoAV6AQPgOgQIg4ODf39_8XoBhYWFKwISAL0DGwD_dAFGAq4G" - "rQF0AT8ADQglMv8eBlQAAQDDA90BAWDvMQFg_xuQMDA5ANjPXQAtBq8ydQD_ewCB" - "AIcAjQCTAJkAaQZ8C_iRkZFW9BMyAj1WZGwA_xUDqThyBggBNgOwB1AETgPxQTTg" - "4ODaCoQGLgIBAPs4AQFg5QHwAfAB8AHwAfB_AfAB8AHwAfAB8AHwAfDl_uXkYPYw" - "_wAFAQsBNAIXAf8dAUIDKQEvATUBOwFaA0cB_2L0pWZiNI8BaQxG8kYyUzr_4wFG" - "8kYyQ_JD8gEwKgBG8v9G8kbyRsJWNAVtDwNDMq84_2AAMwP5AD8DCAFLA1EDIwH_" - "OAFZNG8DdQN7A1n0WfQB8P8B8AHwAfAB8AHwAfAB8AHwA1_0X_Ts7Ozo6Oj_QwIx" - "ApYGjQbyCiICYwMVDP8fMn8-iQEyBFMBQj9lAYwB_9wF2TUfMg8AMQKJATUKIQDx" - "3DWmpqYlMmECSADPA_8rMgEAeQLsAQHwGAAeAAwG_y0AMwA5AAcySwBCOV0AYwD_" - "aQBvAHUADTJgP207Eg-8Cv8QAhU_VvQCPVaUFQP5AIYH_3IGU2RFA000IwHXCigC" - "LwH9AQDpASDmCgHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AcDwMAEA_AAFAQsB" - "dzQdAf8jAXkFLwE1AVQDxzhi9HEB_3cBYsSbAacBRvJG8kbyHwL_QzLbA0PyQzJH" - "NEbyRvJG8v9GklZk_QIDAzs3VABDYi0D_5Y2PwNFA0sDUQMjAS8BYwP_WfRZ9AHw" - "AfAB8AHwAfAB8D8B8AHwAfAB8F_03gD4-AL46gDz8_Pw8PD47e3tNAIuAigCIJ0V" - "DP_BOPoFOzrrNUJvYjR3AdkF44wB6AV-fn5ObwAGswH_GwA3OOFjOQDaAaEB4wEB" - "AP9OAAEA8gEBMA8wGzAnACEG_4ViRQBLAFEAVwBdAAZvdQDjAQACB4uLi6loNAIQ" - "kv-xAFbEYA8bP1aUGwMhAxkC_3IGHAI8A1A0RzQlAiwBAQD_3QoBADECAZBKAQHw" - "AfAB8P8B8AHwAfAB8AHwAfAB8AHw_wFg4cD2MAIxCwGMB2s0IwH_ZTQ1ATsBQAJH" - "AWMDYvRixP9G8kbyRvIwABMCGQJxNAEA_0sAQ_IbMGcCJwBmAEbyRvL_RvJG8pFr" - "sz1FP-QALQMInf8OARcBIAEsAUoBaQNvA3UD_1n0rvMB8AHwAfAB8AHwAfAfAfAB" - "8AHwX_RflPr6-hHqAPb29v4N8fHx_zQCIJ2EBsGYQw6pDlMKIz3_ADbEOOMBAAZ_" - "DlMKtgGVAf-wAaEBqjEABgwADwavDhgA-IyMjCgyAQAPBhI2AQD_wgEBAD8AAQAM" - "MBUAHgA2Bv8EMjkAPwBFAEsAUQBXAGAw_xMCHAIeAA1iqTiNABI_VmTHzQJWxBMC" - "xcXFVpQWAv9WNBkCcgZTNBcBTTQiAikB_wEAgQYrAgEwPgEB8AHwAfD_AfAB8AHw" - "AfAB8AHwAfAB8P8B8AHA82D_AAEACAF9NGg0_3kFzTjhCccI8Ali9GI0fQH_gwFi" - "ZKhmRvJG8kYyTwi6DP_yMSUC9TE3MgFgWDJkMnAy_0byRvJwAkbymQACPVY0_QL_" - "BT0PAxUDGwOvmDkDCJ03Av8pAT4BaQNvA3UDWfQB8AHw_wHwAfAB8AHwAfAB8AHw" - "AfD_AfBf9PAApQn8ACD9H2I9DvFDDsjIyPYJyg5ixGJq_xgG3AUfYuABAQKsDhUG" - "igbAfHx8fX19yAEMAOPUARUAhISE0QEBAOwB_ycAAZASAB4A_gEnAC0AyTb_PwBF" - "AJxvXTazOmA2DWLYNv_VNhBizQI1B1aUEzJWlBUD_xsDIQMFAS0DNgOOOE00JgH_" - "ewYlAgEANQEBMEEBAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfABwPNgAjELAXE0" - "aDT_ZTROA8c4YsRlAWsBxPiolv9G8kbyBAJGYtk4JQIrAhAC_wHwGAABACQARvJG" - "8kbyRpL_jG1WZAkDDwMVAxsDIQNDMv_wAAj9JgEoMln0ovMB8AHw_wHwAfAB8AHw" - "AfAB8AHwAfD_YsTeAGI08AClCfwAIJ2ENkcfMnIDjgXT09MjPceMx8dilCY9ra2t" - "bAz_HAjHOB9i6TqtAUI2JQI2Nvc4NDM2ATB6AeAYMBkygATjXgKuBpCQkFo2rmZd" - "AP-zOg1iYzb_PJkAnwAQAscC_xU_Av0CPVaUqTgZAjMDyzr_TTRjMy8BNQEBYC4C" - "AfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfABwPaQBQFXP2s0PTL_SANAAgE7Yz9D" - "YmL0YsRG8o9G8kbCyZwyBHt7e0Dy_0byRgJNNEbyRvJGkusCVpT_CQMFPRsDIQND" - "YjkDljYIbf8jATQyVjRZNJbzAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfB_wfgoMiBt" - "wTgfMh4MPQ7a4Nra1tbW4DpixHMO_-M6x2gfMuU-UArlDiIyDwb_JTKzOtcBAQC2" - "OgEA7AEB8P8bMCow-DoHMksACjJdAPk8_28ADTJjZpMAEj8QMlb0VjT_wgpWxKk4" - "pjhTNBwyYzMyAf8BMCgyAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwkGb_ATBx" - "ZGWUATti9GL0YmRGYv-tAUbyRvJG8kZiQPJDYkby_0byRvJGMt8CRmJWZAkDDwP_" - "FQMbAyEDJwPqAPAArzgInf8gAa_4AfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8F_0" - "JWL_wfiIBSNtYsQRCm0Ocw5mDOOBBsc4sbGxHzLxBek6_04PBgYiMhUGJWIoMisy" - "AWD_9QH4MQFgJDAwMAcyRQAKMv9XAF0A-TxvAA0ygQCHAKk4_5kAEj8QYgKdVvRW" - "ZKk4U2T_TZQ4MQFgWfEB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw__nAa5RAkmL0" - "Jp1iZKiWRpL_uQFG8kbyRvJG8kbyRvJG8v9GktMCRpLxAlbEBW2v-EDC_1n0AfAB" - "8AHwAfAB8AHwAfD_AfAB8AHwAfAB8F_EwfgfMuPBOGI05OTkYpQmPW0OQMnJycXF" - "xYE2vOC8vLm5uR8y8QXpOv8iYoo2yAEeBigyfQoBAOMB_zEyAcAYMCQwBDI5AD8A" - "RQDxCmKrq6uzOqY4ewCBAP-HAKk4mQCfAKUAqwAQMlY0_wL9VvQWkhnyAfAB8AHw" - "AfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwQPKi9mL0YvSoNkby_0by4wHpAWhkRvJG" - "8kOSRvL_RjKRAkbyRvJGkvcCVpQVA_9WNK84Q2KvyEDyAfAB8AHw_wHwAfAB8AHw" - "AfAB8AHwAfD_AfAi8sH4wWjeCRwCI21ilPFnDtTU1OM6xzgfkvEF4y89ImKurq5r" - "ZHQK1wH_cTQuAjEyAcAYMCEABDIzAP8HYksACmJjAGA2DZJjNpkA-NDQ0KUAqwAQ" - "Mlb0VvT_VvQW8gHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_0DyYvRi9GsB" - "YsRGkqcBRvL_RvJGYms0RjIiMkbyQ2JGMv9wMkYyhQJG8mkARvJGMuUC_0aSVjQF" - "bSEDJwOv-K_4AfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8B_ywfgjbWL0gWcO" - "29vb2NjY4wqP0AUfwpoOSgq-vr4iYuMPBiUys7OzdArXAX0K_wEA4wExMgHwGAAk" - "MC0AMwD_B2JLAApiYwBpAG8AU8QUB_-ZAJ8ApQCrABBiVvQT8lb0_wHwAfAB8AHw" - "AfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P9D8mL0YmRG8kYyswFG8kby_2tkRvJD" - "8hgwRpJ_AkbyRvL_RjLZAkbyVpQFba_4Q_IB8P8B8AHwAfAB8AHwAfAB8AHw_wHw" - "AfAB8AHwH_LB-COdYpTjCwpnDuLi4oFmH8LpOmNODyIywcHBDwYlMrv8u7vRAXcK" - "2gFzCOMBMTL_AcAYMCEAJwAEMjkABzJLAD9RAAoyYwCzOlPEYzbf3_7fpQASP7cA" - "2QIQMlaU5AD_VvQT8gHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfBi" - "9GL0Jp1GkqcBRvL_RvJG8hwyRvJG8icAcwJG8v9G8kby5QJGklZkBc2v-CPx_wHw" - "AfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8BzyYvQc8v9iNGcO4zqBNh-SlA4vPaMO" - "ESIyysrKJTLGxsb4xMTE0QF3CtoBLQbjAf8xMgHAGDAhACcALQAzADkA_wcyWgZR" - "AAoyYwCzOnUAU5T_qZgSPxDCVvQT8gHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHw" - "AfAB8AHwAfAB8GL0RjL_awFG8kbyRvJGwvsBazRG8v9G8hUwRvKFAk00RvJG8kby" - "_0j_VvRG8gHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8D8B8BzyYvQcMgUKYQ7v" - "7z7v42qBNkgPH2JECtnZjtkiYgkGJTLOzs4oMv93CtoBdDQxAgHwAQAbAAEy_y0A" - "BDI_AAcyUQAKMgYPaQD_bwANYocAqZilAKsAEj8Q8v9W9AHwAfAB8AHwAfAB8AHw" - "_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwRvJG8kbyRvJGMv_jAUbyRvJA8kOSRvJG" - "kqkC_0byRvJG8kbyRvIB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8Bzy" - "Js1ODGUBH4FmH5KENkQKImLc3Nz_CQZrZG4K0QHKDgEALjIBAP-JCgHAGDAhAAEA" - "KgAzADkA_01kUQBXAApibwBgNg2SY2b_Es8Q8hDyAfAB8AHwAfAB8P8B8AHwAfAB" - "8AHwAfAB8AHw_wHwAfAB8AHwAfBG8kbyRvL_RvJGwms0RmIiMkbyRsJwMv9GYk00" - "RvJG8kbyRvJG8gHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAc8v8c" - "koHGH5LpaiIyija1DsIB-N7e3igyJAbaAS4yATD_7wEBkBUwJDAtAAQyPwBFAP9L" - "AFo2XQBdBrNqDZJjxhDy_xDyAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB" - "8P8B8AHwAfAB8EbyRvJG8kaS_-MBRvJG8kPyW2JG8kbCrDL_RvJG8kbyRvIB8AHw" - "AfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwHPIckh_C0AX_H5LpOp0OImKK" - "NiiSJAaWNv8xMgHwATAhMCoASpRaNgpi_2kAszoNkpMADcIQ8urwAfD_AfAB8AHw" - "AfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfBG8kbyRvJG8kbyQ_L_AfBD" - "YkbyRvJG8kbyRvIB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_" - "HPIf8nEBhPbpOiJiijYlYv9uCgEAKzIBMOkxAfD-MQEy_zYwB5JXAApiszoNwmP2" - "w2D_3vAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfBG" - "8mJkZfT_RvJG8kPyQ_JG8kbyRvJTZP9G8kbyAfAB8AHwAfAB8AHw_wHwAfAB8AHw" - "AfAB8AHwAfD_AfAB8AHwAfCB9mX0ZTQiYv8lwigyKzIuMgHwAWAkMDAw_zwwB5IK" - "YrPKDWJj9gHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHw" - "AfAB8EbyRvJG8kbyRvL_RvJG8kbyRvJG8kbyRvIB8P8B8AHwAfAB8AHwAfAB8AHw" - "_wHwAfAB8AHwAfAB8AHwAfD_AfAc8h_y6ZoiYiViKMIBMP_jAQHwAWAnYASSB2Kj" - "-A3y_xDyAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHw" - "AfAB8Eby_0byRvJG8jryRvJG8kbyRvL_RvIB8AHwAfAB8AHwAfAB8P8B8AHwAfAB" - "8AHwAfAB8AHw_wHwAfAB8AHwAfAi8iLyJZL_KJIBYOkxAfAkkARiB5IK8v8K8gHw" - "AfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P9G" - "8kbyRvJG8kbyRvJG8kby_0byqPAB8AHwAfAB8AHwAfAPAfAB8AHwATA" -}; -#endif diff --git a/src/data/documentation.html b/src/data/documentation.html new file mode 100644 index 00000000..65fc74d7 --- /dev/null +++ b/src/data/documentation.html @@ -0,0 +1,81 @@ + + + + + +

bsnes™ Usage Documentation


+ +bsnes is a Super Nintendo / Super Famicom emulator that strives to provide +the most faithful emulation experience possible. It focuses on accuracy and +clean code; over speed and features. +
+ +

Modes of Operation


+ +bsnes is capable of running both in its default multi-user mode, as well as +in single-user mode.
+
+ +In multi-user mode, configuration data is stored inside the user's home +directory. On Windows, this is located at "%APPDATA%/.bsnes". On other operating +systems, this is located at "~/.bsnes".
+
+ +To enable single-user mode, create a blank "bsnes.cfg" file inside the same +folder as the bsnes executable. bsnes will then use this file to store +configuration data. +
+ +

Supported Filetypes


+ +SFC, SMC, SWC, FIG: SNES cartridge — ROM image.
+BS: Satellaview BS-X flash cartridge — EEPROM image.
+ST: Sufami Turbo cartridge — ROM image.
+SRM, PSR: non-volatile memory, often used to save game data — (P)SRAM image.
+RTC: real-time clock non-volatile memory.
+UPS: patch data, used to dynamically modify cartridge of same base filename upon load.
+CHT: plain-text list of "Game Genie" / "Pro Action Replay" codes. +
+ +

Known Limitations


+ +Cartridge co-processors: certain cartridges contain special co-processor chips to enhance +their functionality. Some of these are either partially or completely unsupported. A message box +warning will pop up when attempting to load such a cartridge.
+
+ +Satellaview BS-X emulation: this hardware is only partially supported. As a result, +most BS-X software will not function correctly.
+
+ +Savestates: due to the design of bsnes, it is not plausible to +implement support for savestate and/or rewind functionality.
+
+ +Netplay: internet multiplay is not currently supported nor planned. +
+ +

Contributors

+• Andreas Naive
+• anomie
+• Derrick Sobodash
+• DMV27
+• FirebrandX
+• FitzRoy
+• GIGO
+• Jonas Quinn
+• kode54
+• krom
+• Matthew Callis
+• Nach
+• neviksti
+• Overload
+• RedDwarf
+• Richard Bannister
+• Shay Green
+• tetsuo55
+• TRAC
+• zones
+ + + diff --git a/src/data/icon48.h b/src/data/icon48.h deleted file mode 100644 index 4d1daf76..00000000 --- a/src/data/icon48.h +++ /dev/null @@ -1,41 +0,0 @@ -static char enc_icon48[] = { - "_gAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHw" - "AfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB" - "8AHwAfD_AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHw" - "AfAB8AHwAfD_AfAB8AHwAfAB8AHwAfABAAD_qgAD_p8VOwD_oRaI_qEWthD-oBbL" - "CADE_qEAFaP_oBZm_p38GBU28AHwAfAB8AHwAfBfAfAB8AFQuBCoAMe0AP4twAD_" - "BPAEIPAkAIX_-JkZCkLwAfAB8AHwAfBHAfAB8HSB_wABtABvPQQA-7DwBPAEECwA" - "0P74oBIbTvAB8AHwAfAB8EcB8AHweEGgFWwkAv0HqPAE8MSwFdX-oRr-E1LwAfAB" - "8AHwAfAB8Lzg6KEUMRAC96DwBPAE8P3EIKXAEVrwAfAB8AHwAfDzAfDAsBa3mPAE" - "8ATwwED_uAE4E1rwAfAB8AHwAfAB8PG8YKIVJDjyBPAE8ATA-KAWoF7wAfAB8AHw" - "AfB7AfD4gmmQ8ATwBPDM4-Z_XvAB8AHwAfAB8AHwwICPH_wSePQE8ATwBMClDxEB" - "YNBERP8PPz_-oEw-Pv56CACMCACCgggAXT8__yD2BA808wHwAfDAkKMSDv6AmxwS" - "_qQYH8QC6kjIAJTUAOug8ATwhOFDRABYUDw8_hWsAI9QPj7_8KwA_wTw_oL6BAC1" - "PT3-MpbwjwHwAfAB8AxmpRkUFAOOkxjzBPAEoJ8PEEAQdZQAOrwA47TwBPAoAPh9" - "yAB0hvAB8AHwAfBcsha6XFwC-KzwBPDsAOQ0EHWQAD0EAPSs8ATwBMCAn4LwAfAB" - "8AHwXLUVakz4gwTwKACbQkL-F0wBTuac8ATwiPH-_AQATh-C8AHwAfAB8ExifwAC" - "DcAAtbTwfKT8_qETejSAAJOc8ATwBPAUY9p9CAAIhvAB8AHwAfCEYZ4MGDWE8YCn" - "nEs8_3oRgAD1lPAE8ATwBKBVh4LwAQAUCwDNACQEAKBNAMwAaggAewgAoH8AywBz" - "CABbCABgNwDIAA458Nxp2IO88Hwhzf-ZEQ-0A15XjPAE8ATwhOGlfaDOVAA1oACY" - "sADhCAD9jQgA_wTwBEDMAPQEAHq7NABjCAAcq2QRvPAW9sUMXHwAiIzwBPAE8MDg" - "otZxIMsAGYQArQQATv6s8ATwzPAA4wQAUVcKAGQmYAR9MILpBAB3sP-qKgbgUPwC" - "mwQAevuAAP6U8ATwBPDAYO3QANEAHIQA3JjwBPCrBPAEoPwEAGyAQVRwAeCV_qIX" - "WPQWZNJ8AKoQgAQbSAI2hABtzAC6xAgA_aDwBPCIxOc4Ab6mjPAE8ATwBPAEAPYE" - "AK9YZwHwAfA2QwLAAEgEAK7dqPAE8MCAx8AA5Ijw_wTwBPAE8ExCFPQB8AHwAQDQ" - "Q0P-E5QAw6zwBPD1wECLcAC2iPAE8ATwBPD1BAD70AMqmvAB8AHwATDQODj-EpgA" - "1LDwxPbFBAAxcCDNAOyM8ATwewTwBOCGkvAB8AHwIOM8t-AzuPC8g66UasQwyJDw" - "VwTwBPAEIPHEBG4KAAJBDcYKAP7-LAQAT1UEAGUEAHAEAGoEAFd1BAA5BAASJAAY" - "-8Qg_gaytPB8Yeg7O_8eUTGgvwAEyABUUAK3XZAA9aTw6OJsFdgIAIOQAM8AG_11" - "_guUAKpgCAC1BADvDAD_BPAxBED-_vgIAEoA_nzDBAD4o0BA_lO88Lwgjuy4FPLw" - "AYDPABCYAKpCoABtBACKBACbEACKngQAk4wnzQBXoCd3qBe5plgh4azwBPDM8P76" - "9QQAiwwA1CjgGeQSvMD6yAQAKbHwAfAB8AHwvMC9RBH-nPAE8ATwBKDAxCFVVDXu" - "sADzCAC9dEgEX3nwAfAB8AHwvMAxBAD8X4zwBPAE8ATwTAB-3Ewh_bQADHHwAfAB" - "8AHwAfDAYL50iPAE8ATwBPDAQMRl8L8B8AHwAfAB8AHwcABMiPDvBPAE8ATwwECc" - "ZfAB8AHwrwHwAfAB8HQAAnQAsIzw9wTwBPC84OQIABy1AfAB8O8B8AHwAfDs1ZUI" - "8wTwBPD1vGDHBAAkXfAB8AHwAfCnAfAB8Mjw_zCEAJ8EAK7rpPAE8CgA9wQAvAQA" - "_lQQAJC1AfAB8AHwAfAB8FcB8MjwkBE6BABsBACPVQQApAQArwQAqQQAl_UgAHgI" - "AEsEANQsAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_AfAB" - "8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHwAfD_" - "AfAB8AHwAfAB8AHwAfAB8P8B8AHwAfAB8AHwAfAB8AHw_wHwAfAB8AHwAfAB8AHw" - "AfD_AfAB8AHwAfAB8AHwAfAB8A8B8AHwAfABsA" -}; diff --git a/src/data/joypad.png b/src/data/joypad.png new file mode 100644 index 00000000..3bd2638d Binary files /dev/null and b/src/data/joypad.png differ diff --git a/src/data/license.html b/src/data/license.html new file mode 100644 index 00000000..6f5591ec --- /dev/null +++ b/src/data/license.html @@ -0,0 +1,87 @@ + + + + + +

bsnes™ Reference License


+Copyright © 2004–2009 byuu
+All rights reserved
+
+ +

1. Definitions


+ +The terms "reproduce", "reproduction", "distribute" and "distribution" have the +same meaning here as under U.S. copyright law.

+ +"The software" means this software package as a whole, including, but not +limited to, this license, binaries, source code, documentation, and data.

+ +"You" means the licensee of the software.

+ +"The licensor" means the copyright holder of the software, byuu. +
+ +

2. Grant of Rights


+ +Subject to the terms of this license, the licensor grants you a +non-transferable, non-exclusive, worldwide, royalty-free copyright license to +reproduce the software for non-commercial use only, provided the software +remains unmodified, and there is no charge for the software itself, nor for the +medium upon which the software is distributed. The reproduction of modified or +derivative works of the software is strictly prohibited without the express +consent of the licensor. +
+ +

3. Limitations


+ +This license does not grant you any rights to use the licensor's name, logo or +trademarks.
+
+ +The software is provided "as is", and any express or implied warranties, +including, but not limited to, the implied warranties of merchantability and +fitness for a particular purpose are disclaimed. In no event shall the licensor +be liable for any direct, indirect, incidental, special, exemplary, or +consequential damages (including, but not limited to, procurement of sbustitute +goods or services; loss of use, data, or profits; or business interruption) +however caused and on any theory of liability, whether in contract, strict +liability, or tort (including negligence or otherwise) arising in any way out of +the use of the software, even if advised of the possibility of such damage.
+
+ +In the event that this license is determined to be invalid or unenforceable, the +Grant of Rights will become null and void, and no rights shall be granted to the +licensee, within the scope of U.S. copyright law. +
+ +

4. Exemptions


+ +The software includes the work of other copyrights holders, which is licensed +under different agreements, and exempt from this license. Below is a complete +list of all such software, and their respective copyright holders and licenses. +Note that explicit permission has been granted to the licensor to use included +software which is ordinarily not compatible with this license, such as the GPL. +
+ + + + + + + + + + + + + + + + + + + +
NameLicenseAuthor(s)
Cx4 emulatoranomie, Kris Bleakley, Nach, zsKnight
DSP-1 emulatorAndreas Naive, John Weidman, Kris Bleakley, neviksti
DSP-2 emulatorKris Bleakley
DSP-3 emulatorJohn Weidman, Kris Bleakley, Lancer, z80 gaiden
DSP-4 emulatorDreamer Nom, John Weidman, Kris Bleakley, Nach, z80 gaiden
S-DD1 decompressorPublic DomainAndreas Naive
S-DSP emulatorLGPL 2.1Shay Green
SPC7110 decompressorPublic Domainneviksti
ST-0010 emulatorFeather, John Weidman, Kris Bleakley, Matthew Kendora
Qt toolkitLGPL 2.1Nokia
HQ2x filterLGPL 2.1MaxST
JMA decompressorGPL 2NSRT team
NTSC filterLGPL 2.1Shay Green
zlib decompressorzlib licensezlib team
+ + + diff --git a/src/data/logo.png b/src/data/logo.png new file mode 100644 index 00000000..eaa33cd0 Binary files /dev/null and b/src/data/logo.png differ diff --git a/src/lib/hiro/gtk/hiro.cpp b/src/lib/hiro/gtk/hiro.cpp index c7b31e33..b252cd80 100644 --- a/src/lib/hiro/gtk/hiro.cpp +++ b/src/lib/hiro/gtk/hiro.cpp @@ -131,15 +131,15 @@ bool pHiro::file_open(Window *focus, char *filename, const char *path, const cha if(filter && *filter) { lstring filterlist; - split(filterlist, "\n", filter); - for(unsigned i = 0; i < count(filterlist); i++) { + filterlist.split("\n", filter); + for(unsigned i = 0; i < filterlist.size(); i++) { GtkFileFilter *filter = gtk_file_filter_new(); lstring filterpart; - split(filterpart, "\t", filterlist[i]); + filterpart.split("\t", filterlist[i]); gtk_file_filter_set_name(filter, string() << filterpart[0] << " (" << filterpart[1] << ")"); lstring patternlist; - split(patternlist, ",", filterpart[1]); - for(unsigned l = 0; l < count(patternlist); l++) { + patternlist.split(",", filterpart[1]); + for(unsigned l = 0; l < patternlist.size(); l++) { gtk_file_filter_add_pattern(filter, patternlist[l]); } gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter); @@ -174,15 +174,15 @@ bool pHiro::file_save(Window *focus, char *filename, const char *path, const cha if(filter && *filter) { lstring filterlist; - split(filterlist, "\n", filter); - for(unsigned i = 0; i < count(filterlist); i++) { + filterlist.split("\n", filter); + for(unsigned i = 0; i < filterlist.size(); i++) { GtkFileFilter *filter = gtk_file_filter_new(); lstring filterpart; - split(filterpart, "\t", filterlist[i]); + filterpart.split("\t", filterlist[i]); gtk_file_filter_set_name(filter, string() << filterpart[0] << " (" << filterpart[1] << ")"); lstring patternlist; - split(patternlist, ",", filterpart[1]); - for(unsigned l = 0; l < count(patternlist); l++) { + patternlist.split(",", filterpart[1]); + for(unsigned l = 0; l < patternlist.size(); l++) { gtk_file_filter_add_pattern(filter, patternlist[l]); } gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter); diff --git a/src/lib/hiro/gtk/listbox.cpp b/src/lib/hiro/gtk/listbox.cpp index 55589fef..ac02b8e7 100644 --- a/src/lib/hiro/gtk/listbox.cpp +++ b/src/lib/hiro/gtk/listbox.cpp @@ -29,11 +29,11 @@ void pListbox::create(unsigned style, unsigned width, unsigned height, const cha gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollbox), GTK_SHADOW_ETCHED_IN); lstring list; - split(list, "\t", columns); + list.split("\t", columns); - GType *v = (GType*)malloc(count(list) * sizeof(GType)); - for(unsigned i = 0; i < count(list); i++) v[i] = G_TYPE_STRING; - store = gtk_list_store_newv(count(list), v); + GType *v = (GType*)malloc(list.size() * sizeof(GType)); + for(unsigned i = 0; i < list.size(); i++) v[i] = G_TYPE_STRING; + store = gtk_list_store_newv(list.size(), v); free(v); listbox = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); @@ -44,8 +44,8 @@ void pListbox::create(unsigned style, unsigned width, unsigned height, const cha gtk_widget_show(scrollbox); //alternate colors for each listbox entry if there are multiple columns - gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(listbox), count(list) >= 2 ? true : false); - for(unsigned i = 0; i < count(list); i++) { + gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(listbox), list.size() >= 2 ? true : false); + for(unsigned i = 0; i < list.size(); i++) { unsigned i = column.size(); column[i].renderer = gtk_cell_renderer_text_new(); column[i].column = gtk_tree_view_column_new_with_attributes( @@ -61,8 +61,8 @@ void pListbox::create(unsigned style, unsigned width, unsigned height, const cha } if(text && *text) { - split(list, "\n", text); - for(unsigned i = 0; i < count(list); i++) add_item(list[i]); + list.split("\n", text); + for(unsigned i = 0; i < list.size(); i++) add_item(list[i]); } gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(listbox), header); @@ -85,9 +85,9 @@ void pListbox::set_column_width(unsigned index, unsigned width) { void pListbox::add_item(const char *text) { lstring list; - split(list, "\t", text); + list.split("\t", text); gtk_list_store_append(store, &iter); - for(unsigned i = 0; i < count(list); i++) { + for(unsigned i = 0; i < list.size(); i++) { gtk_list_store_set(store, &iter, i, (const char*)list[i], -1); } } @@ -101,8 +101,8 @@ void pListbox::set_item(unsigned index, const char *text) { } lstring list; - split(list, "\t", text); - for(unsigned i = 0; i < count(list); i++) { + list.split("\t", text); + for(unsigned i = 0; i < list.size(); i++) { gtk_list_store_set(store, &iter, i, (const char*)list[i], -1); } } diff --git a/src/lib/hiro/win/editbox.cpp b/src/lib/hiro/win/editbox.cpp index 6949e7bc..9f3e2fa7 100644 --- a/src/lib/hiro/win/editbox.cpp +++ b/src/lib/hiro/win/editbox.cpp @@ -28,8 +28,8 @@ void pEditbox::resize(unsigned width, unsigned height) { void pEditbox::set_text(const char *text) { string temp = text ? text : ""; - replace(temp, "\r", ""); - replace(temp, "\n", "\r\n"); + temp.replace("\r", ""); + temp.replace("\n", "\r\n"); SetWindowText(hwnd, utf16(temp)); update(); } @@ -39,7 +39,7 @@ unsigned pEditbox::get_text(char *text, unsigned length) { GetWindowText(hwnd, buffer, length); string temp = (const char*)utf8(buffer); delete[] buffer; - replace(temp, "\r", ""); + temp.replace("\r", ""); strlcpy(text, temp, length); return strlen(text); } diff --git a/src/lib/hiro/win/hiro.cpp b/src/lib/hiro/win/hiro.cpp index 6a28643e..36f55e27 100644 --- a/src/lib/hiro/win/hiro.cpp +++ b/src/lib/hiro/win/hiro.cpp @@ -106,23 +106,22 @@ bool pHiro::folder_select(Window *focus, char *filename, const char *path) { bool pHiro::file_open(Window *focus, char *filename, const char *path, const char *filter) { string dir, f; - strcpy(dir, path ? path : ""); - replace(dir, "/", "\\"); + dir = path ? path : ""; + dir.replace("/", "\\"); lstring type, part; - strcpy(f, ""); - split(type, "\n", filter); - for(int i = 0; i < count(type); i++) { - split(part, "\t", type[i]); - if(count(part) != 2) continue; + type.split("\n", filter); + for(int i = 0; i < type.size(); i++) { + part.split("\t", type[i]); + if(part.size() != 2) continue; - strcat(f, part[0]); - strcat(f, " ("); - strcat(f, part[1]); - strcat(f, ")\t"); - replace(part[1], ",", ";"); - strcat(f, part[1]); - strcat(f, "\t"); + f.append(part[0]); + f.append(" ("); + f.append(part[1]); + f.append(")\t"); + part[1].replace(",", ";"); + f.append(part[1]); + f.append("\t"); } utf16 wfilter(f); @@ -154,23 +153,22 @@ bool pHiro::file_open(Window *focus, char *filename, const char *path, const cha bool pHiro::file_save(Window *focus, char *filename, const char *path, const char *filter) { string dir, f; - strcpy(dir, path ? path : ""); - replace(dir, "/", "\\"); + dir = path ? path : ""; + dir.replace("/", "\\"); lstring type, part; - strcpy(f, ""); - split(type, "\n", filter); - for(int i = 0; i < count(type); i++) { - split(part, "\t", type[i]); - if(count(part) != 2) continue; + type.split("\n", filter); + for(int i = 0; i < type.size(); i++) { + part.split("\t", type[i]); + if(part.size() != 2) continue; - strcat(f, part[0]); - strcat(f, " ("); - strcat(f, part[1]); - strcat(f, ")\t"); - replace(part[1], ",", ";"); - strcat(f, part[1]); - strcat(f, "\t"); + f.append(part[0]); + f.append(" ("); + f.append(part[1]); + f.append(")\t"); + part[1].replace(",", ";"); + f.append(part[1]); + f.append("\t"); } utf16 wfilter(f); diff --git a/src/lib/hiro/win/hiro.hpp b/src/lib/hiro/win/hiro.hpp index 7461d695..4a688050 100644 --- a/src/lib/hiro/win/hiro.hpp +++ b/src/lib/hiro/win/hiro.hpp @@ -27,8 +27,8 @@ using nall::min; using nall::max; #include -using nall::utf8; -using nall::utf16; +#define utf8 nall::utf8_t +#define utf16 nall::utf16_t extern int hiromain(int argc, const char *const argv[]); diff --git a/src/lib/hiro/win/listbox.cpp b/src/lib/hiro/win/listbox.cpp index e8f19851..4d32e6f5 100644 --- a/src/lib/hiro/win/listbox.cpp +++ b/src/lib/hiro/win/listbox.cpp @@ -16,21 +16,21 @@ void pListbox::create(unsigned style, unsigned width, unsigned height, const cha ListView_SetExtendedListViewStyle(hwnd, LVS_EX_FULLROWSELECT); lstring list; - split(list, "\t", columns ? columns : ""); - column_count = count(list); - for(unsigned i = 0; i < count(list); i++) { + list.split("\t", columns ? columns : ""); + column_count = list.size(); + for(unsigned i = 0; i < list.size(); i++) { LVCOLUMN column; column.mask = LVCF_FMT | LVCF_TEXT | LVCF_SUBITEM; column.fmt = LVCFMT_LEFT; - column.iSubItem = count(list); + column.iSubItem = list.size(); utf16 ulist(list[i]); column.pszText = ulist; ListView_InsertColumn(hwnd, i, &column); } if(text && *text) { - split(list, "\n", text); - for(unsigned i = 0; i < count(list); i++) add_item(list[i]); + list.split("\n", text); + for(unsigned i = 0; i < list.size(); i++) add_item(list[i]); } autosize_columns(); } @@ -47,7 +47,7 @@ void pListbox::set_column_width(unsigned column, unsigned width) { void pListbox::add_item(const char *text) { lstring list; - split(list, "\t", text ? text : ""); + list.split("\t", text ? text : ""); LVITEM item; unsigned pos = ListView_GetItemCount(hwnd); item.mask = LVIF_TEXT; @@ -57,7 +57,7 @@ void pListbox::add_item(const char *text) { item.pszText = wtext; ListView_InsertItem(hwnd, &item); - for(unsigned i = 1; i < count(list); i++) { + for(unsigned i = 1; i < list.size(); i++) { utf16 wtext(list[i]); ListView_SetItemText(hwnd, pos, i, wtext); } @@ -65,8 +65,8 @@ void pListbox::add_item(const char *text) { void pListbox::set_item(unsigned index, const char *text) { lstring list; - split(list, "\t", text ? text : ""); - for(unsigned i = 0; i < count(list); i++) { + list.split("\t", text ? text : ""); + for(unsigned i = 0; i < list.size(); i++) { utf16 wtext(list[i]); ListView_SetItemText(hwnd, index, i, wtext); } diff --git a/src/lib/nall/array.hpp b/src/lib/nall/array.hpp index d2f1947d..62d5486c 100644 --- a/src/lib/nall/array.hpp +++ b/src/lib/nall/array.hpp @@ -48,6 +48,11 @@ namespace nall { operator[](buffersize) = data; } + signed find(const T data) { + for(unsigned i = 0; i < size(); i++) if(pool[i] == data) return i; + return -1; //not found + } + void clear() { memset(pool, 0, buffersize * sizeof(T)); } diff --git a/src/lib/nall/config.hpp b/src/lib/nall/config.hpp index 0dde4a15..4dacd524 100644 --- a/src/lib/nall/config.hpp +++ b/src/lib/nall/config.hpp @@ -16,24 +16,28 @@ namespace nall { template struct is_unsigned { enum { value = false }; }; template<> struct is_unsigned { enum { value = true }; }; + template struct is_double { enum { value = false }; }; + template<> struct is_double { enum { value = true }; }; + template struct is_string { enum { value = false }; }; template<> struct is_string { enum { value = true }; }; } class configuration { public: - enum type_t { boolean_t, signed_t, unsigned_t, string_t, unknown_t }; + enum type_t { boolean_t, signed_t, unsigned_t, double_t, string_t, unknown_t }; struct item_t { uintptr_t data; string name; string desc; type_t type; - string get() { + string get() const { switch(type) { - case boolean_t: return (*(bool*)data == false ? "false" : "true"); - case signed_t: return string() << (int)*(signed*)data; - case unsigned_t: return string() << (int)*(unsigned*)data; + case boolean_t: return string() << *(bool*)data; + case signed_t: return string() << *(signed*)data; + case unsigned_t: return string() << *(unsigned*)data; + case double_t: return string() << *(double*)data; case string_t: return string() << "\"" << *(string*)data << "\""; } return "???"; @@ -42,8 +46,9 @@ namespace nall { void set(string s) { switch(type) { case boolean_t: *(bool*)data = (s == "true"); break; - case signed_t: *(signed*)data = s; break; - case unsigned_t: *(unsigned*)data = s; break; + case signed_t: *(signed*)data = strsigned(s); break; + case unsigned_t: *(unsigned*)data = strunsigned(s); break; + case double_t: *(double*)data = strdouble(s); break; case string_t: trim(s, "\""); *(string*)data = s; break; } } @@ -60,24 +65,25 @@ namespace nall { if(configuration_traits::is_boolean::value) list[n].type = boolean_t; else if(configuration_traits::is_signed::value) list[n].type = signed_t; else if(configuration_traits::is_unsigned::value) list[n].type = unsigned_t; + else if(configuration_traits::is_double::value) list[n].type = double_t; else if(configuration_traits::is_string::value) list[n].type = string_t; else list[n].type = unknown_t; } - bool load(const char *filename) { + virtual bool load(const char *filename) { string data; - if(fread(data, filename) == true) { - replace(data, "\r", ""); + if(data.readfile(filename) == true) { + data.replace("\r", ""); lstring line; - split(line, "\n", data); + line.split("\n", data); - for(unsigned i = 0; i < count(line); i++) { + for(unsigned i = 0; i < line.size(); i++) { int position = qstrpos(line[i], "#"); if(position >= 0) line[i][position] = 0; if(qstrpos(line[i], " = ") < 0) continue; lstring part; - qsplit(part, " = ", line[i]); + part.qsplit(" = ", line[i]); trim(part[0]); trim(part[1]); @@ -95,11 +101,15 @@ namespace nall { } } - bool save(const char *filename) { + virtual bool save(const char *filename) const { file fp; if(fp.open(filename, file::mode_write)) { for(unsigned i = 0; i < list.size(); i++) { - fp.print(string() << list[i].name << " = " << list[i].get() << " # " << list[i].desc << "\r\n"); + string output; + output << list[i].name << " = " << list[i].get(); + if(list[i].desc != "") output << " # " << list[i].desc; + output << "\r\n"; + fp.print(output); } fp.close(); diff --git a/src/lib/nall/config.txt b/src/lib/nall/config.txt deleted file mode 100644 index a59bc1c9..00000000 --- a/src/lib/nall/config.txt +++ /dev/null @@ -1,176 +0,0 @@ -#ifndef NALL_CONFIG_HPP -#define NALL_CONFIG_HPP - -#include -#include -#include -#include - -namespace nall { - class setting; - - class configuration { - public: - array list; - - bool load(const char *fn); - bool save(const char *fn) const; - void add(setting *setting_) { list.add(setting_); } - }; - - class setting { - public: - enum setting_type { - integral_type, - string_type, - } type; - - const char *name; - const char *description; - - virtual void set(const char *input) = 0; - virtual void get(string &output) const = 0; - virtual void get_default(string &output) const = 0; - }; - - class integral_setting : public setting { - public: - enum integral_type { - boolean, - decimal, - hex, - } type; - - intmax_t value; - intmax_t default_value; - - void set(const char *input) { - if(type == boolean) { value = !strcmp(input, "true"); } - if(type == decimal) { value = strdec(input); } - if(type == hex) { value = strhex(input); } - } - - void get(string &output) const { - if(type == boolean) { output = value ? "true" : "false"; } - if(type == decimal) { output = strdec(value); } - if(type == hex) { output = string() << "0x" << strhex(value); } - } - - void get_default(string &output) const { - if(type == boolean) { output = default_value ? "true" : "false"; } - if(type == decimal) { output = strdec(default_value); } - if(type == hex) { output = string() << "0x" << strhex(default_value); } - } - - operator intmax_t() const { return value; } - integral_setting& operator=(intmax_t value_) { value = value_; return *this; } - - integral_setting(const char *name_, const char *description_, integral_type type_, intmax_t value_) { - initialize(name_, description_, type_, value_); - } - - integral_setting(configuration &parent, const char *name_, const char *description_, integral_type type_, intmax_t value_) { - initialize(name_, description_, type_, value_); - parent.add(this); - } - - private: - void initialize(const char *name_, const char *description_, integral_type type_, intmax_t value_) { - setting::type = setting::integral_type; - name = name_; - description = description_; - type = type_; - value = default_value = value_; - } - }; - - class string_setting : public setting { - public: - string value; - string default_value; - - void set(const char *input) { value = input; trim(value(), "\""); } - void get(string &output) const { output = string() << "\"" << value << "\""; } - void get_default(string &output) const { output = string() << "\"" << default_value << "\""; } - - operator const char*() const { return value; } - string_setting& operator=(const char *value_) { value = value_; return *this; } - bool operator==(const char *value_) const { return value == value_; } - bool operator!=(const char *value_) const { return value != value_; } - - string_setting(const char *name_, const char *description_, const char *value_) { - initialize(name_, description_, value_); - } - - string_setting(configuration &parent, const char *name_, const char *description_, const char *value_) { - initialize(name_, description_, value_); - parent.add(this); - } - - private: - void initialize(const char *name_, const char *description_, const char *value_) { - setting::type = setting::string_type; - name = name_; - description = description_; - value = default_value = value_; - } - }; - - inline bool configuration::load(const char *fn) { - //load the config file into memory - string data; - if(!fread(data, fn)) return false; - - //split the file into lines - replace(data, "\r\n", "\n"); - qreplace(data, "\t", ""); - qreplace(data, " ", ""); - - lstring line, part, subpart; - split(line, "\n", data); - - for(unsigned i = 0; i < count(line); i++) { - if(strlen(line[i]) == 0) continue; - if(strbegin(line[i], "#")) continue; - - qsplit(part, "=", line[i]); - for(unsigned l = 0; l < list.size(); l++) { - if(part[0] == list[l]->name) { - list[l]->set(part[1]); - break; - } - } - } - - return true; - } - - inline bool configuration::save(const char *fn) const { - file fp; - if(!fp.open(fn, file::mode_write)) return false; - - for(unsigned i = 0; i < list.size(); i++) { - string data; - lstring line, part, subpart; - strcpy(data, list[i]->description); - replace(data, "\r\n", "\n"); - split(line, "\n", data); - - string temp; - for(unsigned l = 0; l < count(line); l++) { - if(line[l] != "") fp.print(string() << "# " << line[l] << "\r\n"); - } - - string default_, value_; - list[i]->get_default(default_); - fp.print(string() << "# (default = " << default_ << ")\r\n"); - list[i]->get(value_); - fp.print(string() << list[i]->name << " = " << value_ << "\r\n\r\n"); - } - - fp.close(); - return true; - } -} - -#endif diff --git a/src/lib/nall/dictionary.hpp b/src/lib/nall/dictionary.hpp index 02edddc4..35128c2f 100644 --- a/src/lib/nall/dictionary.hpp +++ b/src/lib/nall/dictionary.hpp @@ -27,17 +27,17 @@ namespace nall { bool import(const char *filename) { string data; - if(fread(data, filename) == false) return false; + if(data.readfile(filename) == false) return false; ltrim_once(data, "\xef\xbb\xbf"); //remove UTF-8 marker, if it exists - replace(data, "\r", ""); + data.replace("\r", ""); lstring line; - split(line, "\n", data); - for(unsigned i = 0; i < count(line); i++) { + line.split("\n", data); + for(unsigned i = 0; i < line.size(); i++) { lstring part; //format: "Input" = "Output" - qsplit(part, "=", line[i]); - if(count(part) != 2) continue; + part.qsplit("=", line[i]); + if(part.size() != 2) continue; //remove whitespace trim(part[0]); diff --git a/src/lib/nall/file.hpp b/src/lib/nall/file.hpp index 596b74a5..dc4da3a6 100644 --- a/src/lib/nall/file.hpp +++ b/src/lib/nall/file.hpp @@ -121,7 +121,7 @@ namespace nall { #if !defined(_WIN32) FILE *fp = fopen(fn, "rb"); #else - FILE *fp = _wfopen(utf16(fn), L"rb"); + FILE *fp = _wfopen(utf16_t(fn), L"rb"); #endif if(fp) { fclose(fp); @@ -144,10 +144,10 @@ namespace nall { case mode_readwrite: fp = fopen(fn, "rb+"); break; case mode_writeread: fp = fopen(fn, "wb+"); break; #else - case mode_read: fp = _wfopen(utf16(fn), L"rb"); break; - case mode_write: fp = _wfopen(utf16(fn), L"wb+"); break; - case mode_readwrite: fp = _wfopen(utf16(fn), L"rb+"); break; - case mode_writeread: fp = _wfopen(utf16(fn), L"wb+"); break; + case mode_read: fp = _wfopen(utf16_t(fn), L"rb"); break; + case mode_write: fp = _wfopen(utf16_t(fn), L"wb+"); break; + case mode_readwrite: fp = _wfopen(utf16_t(fn), L"rb+"); break; + case mode_writeread: fp = _wfopen(utf16_t(fn), L"wb+"); break; #endif } if(!fp) return false; diff --git a/src/lib/nall/filemap.hpp b/src/lib/nall/filemap.hpp index c73efad4..a05f0eb7 100644 --- a/src/lib/nall/filemap.hpp +++ b/src/lib/nall/filemap.hpp @@ -72,7 +72,7 @@ namespace nall { break; } - p_filehandle = CreateFileW(utf16(filename), desired_access, FILE_SHARE_READ, NULL, + p_filehandle = CreateFileW(utf16_t(filename), desired_access, FILE_SHARE_READ, NULL, creation_disposition, FILE_ATTRIBUTE_NORMAL, NULL); if(p_filehandle == INVALID_HANDLE_VALUE) return false; diff --git a/src/lib/nall/property.hpp b/src/lib/nall/property.hpp new file mode 100644 index 00000000..0099939c --- /dev/null +++ b/src/lib/nall/property.hpp @@ -0,0 +1,45 @@ +#ifndef NALL_PROPERTY_HPP +#define NALL_PROPERTY_HPP + +//nall::property implements a variable container that disallows write access +//to non-derived objects. This requires use of property::set(), as C++ lacks +//the ability to make this implementation completely transparent. + +namespace nall { + class property { + public: + template class property_t; + + protected: + template T& get(property_t&); + template property_t& set(property_t&, const T); + + public: + template + class property_t { + public: + const T& operator()() const { return value; } + property_t() : value() {} + property_t(const T value_) : value(value_) {} + + protected: + T value; + operator T&() { return value; } + property_t& operator=(const T newValue) { value = newValue; return *this; } + friend T& property::get(property_t&); + friend property_t& property::set(property_t&, const T); + }; + }; + + template + T& property::get(property::property_t &p) { + return p.operator T&(); + } + + template + property::property_t& property::set(property::property_t &p, const T value) { + return p.operator=(value); + } +} + +#endif diff --git a/src/lib/nall/string.cpp b/src/lib/nall/string.cpp index 565cbc7c..7894c7c4 100644 --- a/src/lib/nall/string.cpp +++ b/src/lib/nall/string.cpp @@ -7,14 +7,18 @@ #include #include -#include #include #include #include #include -#include -#include #include #include +#include + +namespace nall { + #include + #include + #include +} #endif diff --git a/src/lib/nall/string.hpp b/src/lib/nall/string.hpp index d1748a4a..a47e2443 100644 --- a/src/lib/nall/string.hpp +++ b/src/lib/nall/string.hpp @@ -7,113 +7,169 @@ #include #include -//compare +//=============== +//libc extensions +//=============== + +//compare.cpp char chrlower(char c); char chrupper(char c); + int stricmp(const char *dest, const char *src); -int strpos(const char *str, const char *key); + +int strpos (const char *str, const char *key); int qstrpos(const char *str, const char *key); -bool strbegin(const char *str, const char *key); + +bool strbegin (const char *str, const char *key); bool stribegin(const char *str, const char *key); -bool strend(const char *str, const char *key); + +bool strend (const char *str, const char *key); bool striend(const char *str, const char *key); -//convert +//convert.cpp char* strlower(char *str); char* strupper(char *str); -char* strtr(char *dest, const char *before, const char *after); -uintmax_t strhex(const char *str); -intmax_t strdec(const char *str); -uintmax_t strbin(const char *str); -double strdouble(const char *str); -size_t strhex(char *str, uintmax_t value, size_t length = 0); -size_t strdec(char *str, intmax_t value, size_t length = 0); -size_t strbin(char *str, uintmax_t value, size_t length = 0); -size_t strdouble(char *str, double value, size_t length = 0); -//match +char* strtr(char *dest, const char *before, const char *after); + +uintmax_t strhex (const char *str); +intmax_t strsigned (const char *str); +uintmax_t strunsigned(const char *str); +uintmax_t strbin (const char *str); +double strdouble (const char *str); + +size_t strhex (char *str, uintmax_t value, size_t length = 0); +size_t strsigned (char *str, intmax_t value, size_t length = 0); +size_t strunsigned(char *str, uintmax_t value, size_t length = 0); +size_t strbin (char *str, uintmax_t value, size_t length = 0); +size_t strdouble (char *str, double value, size_t length = 0); + +//match.cpp bool match(const char *pattern, const char *str); -//math +//math.cpp bool strint (const char *str, int &result); bool strmath(const char *str, int &result); -//strl +//strl.cpp size_t strlcpy(char *dest, const char *src, size_t length); size_t strlcat(char *dest, const char *src, size_t length); -//trim +//trim.cpp char* ltrim(char *str, const char *key = " "); char* rtrim(char *str, const char *key = " "); char* trim (char *str, const char *key = " "); + char* ltrim_once(char *str, const char *key = " "); char* rtrim_once(char *str, const char *key = " "); char* trim_once (char *str, const char *key = " "); +//================ +//string + lstring +//================ + namespace nall { + class string; + template inline string to_string(T); + class string { public: - char *data; - size_t size; + void reserve(size_t); + unsigned length() const; - void reserve(size_t size_); + string& assign(const char*); + string& append(const char*); + template string& operator= (T value) { return assign(to_string(value)); } + template string& operator<<(T value) { return append(to_string(value)); } - operator int() const; operator const char*() const; char* operator()(); char& operator[](int); - string& operator=(int num); - string& operator=(double num); - string& operator=(const char *str); - string& operator=(const string &str); - string& operator<<(int num); - string& operator<<(double num); - string& operator<<(const char *str); - string& operator<<(const string& str); - - bool operator==(const char *str) const; - bool operator!=(const char *str) const; - bool operator< (const char *str) const; - bool operator<=(const char *str) const; - bool operator> (const char *str) const; - bool operator>=(const char *str) const; + bool operator==(const char*) const; + bool operator!=(const char*) const; + bool operator< (const char*) const; + bool operator<=(const char*) const; + bool operator> (const char*) const; + bool operator>=(const char*) const; string(); - string(int num); - string(double num); - string(const char *source); - string(const string &source); + string(const char*); + string(const string&); + string& operator=(const string&); ~string(); + + //core.cpp + bool readfile(const char*); + + //replace.cpp + string& replace (const char*, const char*); + string& qreplace(const char*, const char*); + + protected: + char *data; + size_t size; }; - typedef vector lstring; + class lstring : public vector { + public: + template lstring& operator<<(T value) { + operator[](size()).assign(to_string(value)); + return *this; + } + + //core.cpp + int find(const char*); + + //split.cpp + void split (const char*, const char*, unsigned = 0); + void qsplit(const char*, const char*, unsigned = 0); + }; } -size_t count(nall::lstring&); -int find(nall::lstring &str, const char *key); +//===================== +//string<>libc wrappers +//===================== + size_t strlcpy(nall::string &dest, const char *src, size_t length); size_t strlcat(nall::string &dest, const char *src, size_t length); -void strcpy(nall::string &dest, const char *src); -void strcat(nall::string &dest, const char *src); -nall::string substr(const char *src, size_t start = 0, size_t length = 0); + nall::string& strlower(nall::string &str); nall::string& strupper(nall::string &str); + nall::string& strtr(nall::string &dest, const char *before, const char *after); -nall::string strhex(uintmax_t value); -nall::string strdec(intmax_t value); -nall::string strbin(uintmax_t value); -nall::string strdouble(double value); -bool fread(nall::string &str, const char *filename); -nall::string& replace (nall::string &str, const char *key, const char *token); -nall::string& qreplace(nall::string &str, const char *key, const char *token); -void split (nall::lstring &dest, const char *key, const char *src, size_t limit = 0); -void qsplit(nall::lstring &dest, const char *key, const char *src, size_t limit = 0); + nall::string& ltrim(nall::string &str, const char *key = " "); nall::string& rtrim(nall::string &str, const char *key = " "); nall::string& trim (nall::string &str, const char *key = " "); + nall::string& ltrim_once(nall::string &str, const char *key = " "); nall::string& rtrim_once(nall::string &str, const char *key = " "); nall::string& trim_once (nall::string &str, const char *key = " "); +//============== +//misc functions +//============== + +nall::string substr(const char *src, size_t start = 0, size_t length = 0); + +nall::string strhex (uintmax_t value); +nall::string strsigned (intmax_t value); +nall::string strunsigned(uintmax_t value); +nall::string strbin (uintmax_t value); +nall::string strdouble (double value); + +namespace nall { + //this is needed, as C++98 does not support explicit template specialization inside classes; + //redundant memory allocation should hopefully be avoided via compiler optimizations. + template<> inline string to_string (bool v) { return v ? "true" : "false"; } + template<> inline string to_string (signed int v) { return strsigned(v); } + template<> inline string to_string (unsigned int v) { return strunsigned(v); } + template<> inline string to_string (double v) { return strdouble(v); } + template<> inline string to_string (char *v) { return v; } + template<> inline string to_string (const char *v) { return v; } + template<> inline string to_string (string v) { return v; } + template<> inline string to_string(const string &v) { return v; } +} + #endif diff --git a/src/lib/nall/string/class.cpp b/src/lib/nall/string/class.cpp deleted file mode 100644 index 9a159741..00000000 --- a/src/lib/nall/string/class.cpp +++ /dev/null @@ -1,232 +0,0 @@ -#ifdef NALL_STRING_CPP - -size_t count(nall::lstring &str) { - return str.size(); -} - -int find(nall::lstring &str, const char *key) { - for(size_t i = 0; i < count(str); i++) { - if(str[i] == key) return i; - } - return -1; -} - -namespace nall { - -void string::reserve(size_t size_) { - if(size_ > size) { - size = size_; - data = (char*)realloc(data, size + 1); - data[size] = 0; - } -} - -//implicit-conversion, read-only -string::operator int() const { - return strdec(data); -} - -//implicit-conversion, read-only -string::operator const char*() const { - return data; -} - -//explicit-coversion, read-write -char* string::operator()() { - return data; -} - -//index, read-write -char& string::operator[](int index) { - reserve(index); - return data[index]; -} - -string& string::operator=(int num) { - operator=(strdec(num)); - return *this; -} - -string& string::operator=(double num) { - operator=(strdouble(num)); - return *this; -} - -string& string::operator=(const char *str) { - strcpy(*this, str); - return *this; -} - -string& string::operator=(const string &str) { - strcpy(*this, str); - return *this; -} - -string& string::operator<<(int num) { - string temp(num); - strcat(*this, temp); - return *this; -} - -string& string::operator<<(double num) { - string temp(num); - strcat(*this, temp); - return *this; -} - -string& string::operator<<(const char *str) { - strcat(*this, str); - return *this; -} - -string& string::operator<<(const string &str) { - strcat(*this, str); - return *this; -} - -bool string::operator==(const char *str) const { return strcmp(data, str) == 0; } -bool string::operator!=(const char *str) const { return strcmp(data, str) != 0; } -bool string::operator< (const char *str) const { return strcmp(data, str) < 0; } -bool string::operator<=(const char *str) const { return strcmp(data, str) <= 0; } -bool string::operator> (const char *str) const { return strcmp(data, str) > 0; } -bool string::operator>=(const char *str) const { return strcmp(data, str) >= 0; } - -string::string() { - size = 64; - data = (char*)malloc(size + 1); - *data = 0; -} - -string::string(int source) { - size = 64; - data = (char*)malloc(size + 1); - *data = 0; - operator=(strdec(source)); -} - -string::string(double source) { - size = 64; - data = (char*)malloc(size + 1); - *data = 0; - operator=(strdouble(source)); -} - -string::string(const char *source) { - size = strlen(source); - data = (char*)malloc(size + 1); - strcpy(data, source); -} - -string::string(const string &source) { - size = strlen(source); - data = (char*)malloc(size + 1); - strcpy(data, source); -} - -string::~string() { - if(data) free(data); -} - -} //namespace nall - -void strcpy(nall::string &dest, const char *src) { - int srclen = strlen(src); - dest.reserve(srclen); - strcpy(dest(), src); -} - -void strcat(nall::string &dest, const char *src) { - int srclen = strlen(src); - int destlen = strlen(dest); - dest.reserve(srclen + destlen); - strcat(dest(), src); -} - -size_t strlcpy(nall::string &dest, const char *src, size_t length) { - dest.reserve(length); - return strlcpy(dest(), src, length); -} - -size_t strlcat(nall::string &dest, const char *src, size_t length) { - dest.reserve(length); - return strlcat(dest(), src, length); -} - -nall::string substr(const char *src, size_t start, size_t length) { - nall::string dest; - if(length == 0) { //copy entire string - strcpy(dest, src + start); - } else { //copy partial string - strlcpy(dest, src + start, length + 1); - } - return dest; -} - -/* very simplistic wrappers to return nall::string& instead of char* type */ - -nall::string& strlower(nall::string &str) { strlower(str()); return str; } -nall::string& strupper(nall::string &str) { strupper(str()); return str; } -nall::string& strtr(nall::string &dest, const char *before, const char *after) { strtr(dest(), before, after); return dest; } -nall::string& ltrim(nall::string &str, const char *key) { ltrim(str(), key); return str; } -nall::string& rtrim(nall::string &str, const char *key) { rtrim(str(), key); return str; } -nall::string& trim (nall::string &str, const char *key) { trim (str(), key); return str; } -nall::string& ltrim_once(nall::string &str, const char *key) { ltrim_once(str(), key); return str; } -nall::string& rtrim_once(nall::string &str, const char *key) { rtrim_once(str(), key); return str; } -nall::string& trim_once (nall::string &str, const char *key) { trim_once (str(), key); return str; } - -/* arithmetic <> string */ - -nall::string strhex(uintmax_t value) { - nall::string temp; - temp.reserve(strhex(0, value)); - strhex(temp(), value); - return temp; -} - -nall::string strdec(intmax_t value) { - nall::string temp; - temp.reserve(strdec(0, value)); - strdec(temp(), value); - return temp; -} - -nall::string strbin(uintmax_t value) { - nall::string temp; - temp.reserve(strbin(0, value)); - strbin(temp(), value); - return temp; -} - -nall::string strdouble(double value) { - nall::string temp; - temp.reserve(strdouble(0, value)); - strdouble(temp(), value); - return temp; -} - -/* ... */ - -bool fread(nall::string &str, const char *filename) { - strcpy(str, ""); - - #if !defined(_WIN32) - FILE *fp = fopen(filename, "rb"); - #else - FILE *fp = _wfopen(nall::utf16(filename), L"rb"); - #endif - if(!fp) return false; - - fseek(fp, 0, SEEK_END); - size_t size = ftell(fp); - rewind(fp); - char *fdata = (char*)malloc(size + 1); - unsigned unused = fread(fdata, 1, size, fp); - fclose(fp); - fdata[size] = 0; - strcpy(str, fdata); - free(fdata); - - return true; -} - -#endif diff --git a/src/lib/nall/string/convert.cpp b/src/lib/nall/string/convert.cpp index 46744638..c08ccaeb 100644 --- a/src/lib/nall/string/convert.cpp +++ b/src/lib/nall/string/convert.cpp @@ -37,18 +37,6 @@ char* strtr(char *dest, const char *before, const char *after) { return dest; } -//note: ISO C++ only guarantees that 0-9 is contigious, -//it does not guarantee that either A-F or a-f are also contigious -//however, A-F and a-f are contigious on virtually every platform in existence -//the optimizations and simplifications made possible by this are therefore unignorable -//however, just to be safe, this is tested at compile-time with the below assertion ... -//if false, a compiler error will be thrown - -static nall::static_assert< - ('A' == 'B' - 1) && ('B' == 'C' - 1) && ('C' == 'D' - 1) && ('D' == 'E' - 1) && ('E' == 'F' - 1) && - ('a' == 'b' - 1) && ('b' == 'c' - 1) && ('c' == 'd' - 1) && ('d' == 'e' - 1) && ('e' == 'f' - 1) -> hex_contigious_assertion_; - uintmax_t strhex(const char *str) { if(!str) return 0; uintmax_t result = 0; @@ -69,7 +57,7 @@ uintmax_t strhex(const char *str) { return result; } -intmax_t strdec(const char *str) { +intmax_t strsigned(const char *str) { if(!str) return 0; intmax_t result = 0; bool negate = false; @@ -90,6 +78,20 @@ intmax_t strdec(const char *str) { return !negate ? result : -result; } +uintmax_t strunsigned(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 strbin(const char *str) { if(!str) return 0; uintmax_t result = 0; @@ -143,6 +145,8 @@ double strdouble(const char *str) { return !negate ? result : -result; } +// + size_t strhex(char *str, uintmax_t value, size_t length /* = 0 */) { if(length == 0) length -= 1U; //"infinite" length size_t initial_length = length; @@ -168,7 +172,7 @@ size_t strhex(char *str, uintmax_t value, size_t length /* = 0 */) { return nall::min(initial_length, digits + 1); } -size_t strdec(char *str, intmax_t value_, size_t length /* = 0 */) { +size_t strsigned(char *str, intmax_t value_, size_t length /* = 0 */) { if(length == 0) length = -1U; //"infinite" length size_t initial_length = length; @@ -200,6 +204,31 @@ size_t strdec(char *str, intmax_t value_, size_t length /* = 0 */) { return nall::min(initial_length, digits + 1); } +size_t strunsigned(char *str, uintmax_t value, size_t length /* = 0 */) { + if(length == 0) length = -1U; //"infinite" length + size_t initial_length = length; + + //count number of digits in value + int digits_integral = 1; + uintmax_t digits_integral_ = value; + while(digits_integral_ /= 10) digits_integral++; + + int digits = digits_integral; + if(!str) return digits_integral + 1; //only computing required length? + + length = nall::min(digits, length - 1); + str += length; //seek to end of target string + *str = 0; //set null terminator + + while(length--) { + uint8_t x = '0' + (value % 10); + value /= 10; + *--str = x; //iterate backwards to write string + } + + return nall::min(initial_length, digits + 1); +} + size_t strbin(char *str, uintmax_t value, size_t length /* = 0 */) { if(length == 0) length = -1U; //"infinite" length size_t initial_length = length; diff --git a/src/lib/nall/string/core.cpp b/src/lib/nall/string/core.cpp new file mode 100644 index 00000000..119e50bb --- /dev/null +++ b/src/lib/nall/string/core.cpp @@ -0,0 +1,103 @@ +#ifdef NALL_STRING_CPP + +void string::reserve(size_t size_) { + if(size_ > size) { + size = size_; + data = (char*)realloc(data, size + 1); + data[size] = 0; + } +} + +unsigned string::length() const { + return strlen(data); +} + +string& string::assign(const char *s) { + unsigned length = strlen(s); + reserve(length); + strcpy(data, s); + return *this; +} + +string& string::append(const char *s) { + unsigned length = strlen(data) + strlen(s); + reserve(length); + strcat(data, s); + return *this; +} + +string::operator const char*() const { + return data; +} + +char* string::operator()() { + return data; +} + +char& string::operator[](int index) { + reserve(index); + return data[index]; +} + +bool string::operator==(const char *str) const { return strcmp(data, str) == 0; } +bool string::operator!=(const char *str) const { return strcmp(data, str) != 0; } +bool string::operator< (const char *str) const { return strcmp(data, str) < 0; } +bool string::operator<=(const char *str) const { return strcmp(data, str) <= 0; } +bool string::operator> (const char *str) const { return strcmp(data, str) > 0; } +bool string::operator>=(const char *str) const { return strcmp(data, str) >= 0; } + +string::string() { + size = 64; + data = (char*)malloc(size + 1); + *data = 0; +} + +string::string(const char *value) { + size = strlen(value); + data = strdup(value); +} + +string::string(const string &value) { + size = strlen(value); + data = strdup(value); +} + +string& string::operator=(const string &value) { + assign(value); +} + +string::~string() { + free(data); +} + +bool string::readfile(const char *filename) { + assign(""); + + #if !defined(_WIN32) + FILE *fp = fopen(filename, "rb"); + #else + FILE *fp = _wfopen(nall::utf16_t(filename), L"rb"); + #endif + if(!fp) return false; + + fseek(fp, 0, SEEK_END); + size_t size = ftell(fp); + rewind(fp); + char *fdata = new char[size + 1]; + unsigned unused = fread(fdata, 1, size, fp); + fclose(fp); + fdata[size] = 0; + assign(fdata); + delete[] fdata; + + return true; +} + +int lstring::find(const char *key) { + for(unsigned i = 0; i < size(); i++) { + if(operator[](i) == key) return i; + } + return -1; +} + +#endif diff --git a/src/lib/nall/string/replace.cpp b/src/lib/nall/string/replace.cpp index 34acf7c8..7c13cec4 100644 --- a/src/lib/nall/string/replace.cpp +++ b/src/lib/nall/string/replace.cpp @@ -1,90 +1,98 @@ #ifdef NALL_STRING_CPP -nall::string &replace(nall::string &str, const char *key, const char *token) { - int i, z, ksl = strlen(key), tsl = strlen(token), ssl = strlen(str); +string& string::replace(const char *key, const char *token) { + int i, z, ksl = strlen(key), tsl = strlen(token), ssl = length(); unsigned int replace_count = 0, size = ssl; - char *data; + char *buffer; - if(ksl > ssl)return str; - if(tsl > ksl) { //the new string may be longer than the old string... - for(i = 0; i <= ssl - ksl;) { //so let's find out how big of a string we'll need... - if(!memcmp(str() + i, key, ksl)) { - replace_count++; - i += ksl; - } else i++; + if(ksl <= ssl) { + if(tsl > ksl) { //the new string may be longer than the old string... + for(i = 0; i <= ssl - ksl;) { //so let's find out how big of a string we'll need... + if(!memcmp(data + i, key, ksl)) { + replace_count++; + i += ksl; + } else i++; + } + size = ssl + ((tsl - ksl) * replace_count); + reserve(size); } - size = ssl + ((tsl - ksl) * replace_count); - str.reserve(size); + + buffer = new char[size + 1]; + for(i = z = 0; i < ssl;) { + if(i <= ssl - ksl) { + if(!memcmp(data + i, key, ksl)) { + memcpy(buffer + z, token, tsl); + z += tsl; + i += ksl; + } else buffer[z++] = data[i++]; + } else buffer[z++] = data[i++]; + } + buffer[z] = 0; + + assign(buffer); + delete[] buffer; } - data = (char*)malloc(size + 1); - for(i = z = 0; i < ssl;) { - if(i <= ssl - ksl) { - if(!memcmp(str() + i, key, ksl)) { - memcpy(data + z, token, tsl); - z += tsl; - i += ksl; - } else data[z++] = str()[i++]; - } else data[z++] = str()[i++]; - } - data[z] = 0; - strcpy(str, data); - free(data); - return str; + + return *this; } -nall::string &qreplace(nall::string &str, const char *key, const char *token) { - int i, l, z, ksl = strlen(key), tsl = strlen(token), ssl = strlen(str); +string& string::qreplace(const char *key, const char *token) { + int i, l, z, ksl = strlen(key), tsl = strlen(token), ssl = length(); unsigned int replace_count = 0, size = ssl; uint8_t x; - char *data; + char *buffer; - if(ksl > ssl)return str; - if(tsl > ksl) { - for(i = 0; i <= ssl - ksl;) { - x = str()[i]; - if(x == '\"' || x == '\'') { - l = i; - i++; - while(str()[i++] != x) { - if(i == ssl) { - i = l; - break; + if(ksl <= ssl) { + if(tsl > ksl) { + for(i = 0; i <= ssl - ksl;) { + x = data[i]; + if(x == '\"' || x == '\'') { + l = i; + i++; + while(data[i++] != x) { + if(i == ssl) { + i = l; + break; + } } } + if(!memcmp(data + i, key, ksl)) { + replace_count++; + i += ksl; + } else i++; } - if(!memcmp(str() + i, key, ksl)) { - replace_count++; - i += ksl; - } else i++; + size = ssl + ((tsl - ksl) * replace_count); + reserve(size); } - size = ssl + ((tsl - ksl) * replace_count); - str.reserve(size); - } - data = (char*)malloc(size + 1); - for(i = z = 0; i < ssl;) { - x = str()[i]; - if(x == '\"' || x == '\'') { - l = i++; - while(str()[i] != x && i < ssl)i++; - if(i >= ssl)i = l; - else { - memcpy(data + z, str() + l, i - l); - z += i - l; + + buffer = new char[size + 1]; + for(i = z = 0; i < ssl;) { + x = data[i]; + if(x == '\"' || x == '\'') { + l = i++; + while(data[i] != x && i < ssl)i++; + if(i >= ssl)i = l; + else { + memcpy(buffer + z, data + l, i - l); + z += i - l; + } } + if(i <= ssl - ksl) { + if(!memcmp(data + i, key, ksl)) { + memcpy(buffer + z, token, tsl); + z += tsl; + i += ksl; + replace_count++; + } else buffer[z++] = data[i++]; + } else buffer[z++] = data[i++]; } - if(i <= ssl - ksl) { - if(!memcmp(str() + i, key, ksl)) { - memcpy(data + z, token, tsl); - z += tsl; - i += ksl; - replace_count++; - } else data[z++] = str()[i++]; - } else data[z++] = str()[i++]; + buffer[z] = 0; + + assign(buffer); + delete[] buffer; } - data[z] = 0; - strcpy(str, data); - free(data); - return str; + + return *this; } #endif diff --git a/src/lib/nall/string/split.cpp b/src/lib/nall/string/split.cpp index 7905dfa0..b97068b4 100644 --- a/src/lib/nall/string/split.cpp +++ b/src/lib/nall/string/split.cpp @@ -1,25 +1,25 @@ #ifdef NALL_STRING_CPP -void split(nall::lstring &dest, const char *key, const char *src, size_t limit) { - dest.reset(); +void lstring::split(const char *key, const char *src, unsigned limit) { + reset(); int ssl = strlen(src), ksl = strlen(key); int lp = 0, split_count = 0; for(int i = 0; i <= ssl - ksl;) { if(!memcmp(src + i, key, ksl)) { - strlcpy(dest[split_count++], src + lp, i - lp + 1); + strlcpy(operator[](split_count++), src + lp, i - lp + 1); i += ksl; lp = i; if(!--limit) break; } else i++; } - strcpy(dest[split_count++], src + lp); + operator[](split_count++) = src + lp; } -void qsplit(nall::lstring &dest, const char *key, const char *src, size_t limit) { - dest.reset(); +void lstring::qsplit(const char *key, const char *src, unsigned limit) { + reset(); int ssl = strlen(src), ksl = strlen(key); int lp = 0, split_count = 0; @@ -28,24 +28,24 @@ void qsplit(nall::lstring &dest, const char *key, const char *src, size_t limit) uint8_t x = src[i]; if(x == '\"' || x == '\'') { - int z = i++; //skip opening quote + int z = i++; //skip opening quote while(i < ssl && src[i] != x) i++; - if(i >= ssl) i = z; //failed match, rewind i + if(i >= ssl) i = z; //failed match, rewind i else { - i++; //skip closing quote - continue; //restart in case next char is also a quote + i++; //skip closing quote + continue; //restart in case next char is also a quote } } if(!memcmp(src + i, key, ksl)) { - strlcpy(dest[split_count++], src + lp, i - lp + 1); + strlcpy(operator[](split_count++), src + lp, i - lp + 1); i += ksl; lp = i; if(!--limit) break; } else i++; } - strcpy(dest[split_count++], src + lp); + operator[](split_count++) = src + lp; } #endif diff --git a/src/lib/nall/string/utility.cpp b/src/lib/nall/string/utility.cpp new file mode 100644 index 00000000..1a782812 --- /dev/null +++ b/src/lib/nall/string/utility.cpp @@ -0,0 +1,74 @@ +#ifdef NALL_STRING_CPP + +size_t strlcpy(nall::string &dest, const char *src, size_t length) { + dest.reserve(length); + return strlcpy(dest(), src, length); +} + +size_t strlcat(nall::string &dest, const char *src, size_t length) { + dest.reserve(length); + return strlcat(dest(), src, length); +} + +nall::string substr(const char *src, size_t start, size_t length) { + nall::string dest; + if(length == 0) { + //copy entire string + dest = src + start; + } else { + //copy partial string + strlcpy(dest, src + start, length + 1); + } + return dest; +} + +/* very simplistic wrappers to return nall::string& instead of char* type */ + +nall::string& strlower(nall::string &str) { strlower(str()); return str; } +nall::string& strupper(nall::string &str) { strupper(str()); return str; } +nall::string& strtr(nall::string &dest, const char *before, const char *after) { strtr(dest(), before, after); return dest; } +nall::string& ltrim(nall::string &str, const char *key) { ltrim(str(), key); return str; } +nall::string& rtrim(nall::string &str, const char *key) { rtrim(str(), key); return str; } +nall::string& trim (nall::string &str, const char *key) { trim (str(), key); return str; } +nall::string& ltrim_once(nall::string &str, const char *key) { ltrim_once(str(), key); return str; } +nall::string& rtrim_once(nall::string &str, const char *key) { rtrim_once(str(), key); return str; } +nall::string& trim_once (nall::string &str, const char *key) { trim_once (str(), key); return str; } + +/* arithmetic <> string */ + +nall::string strhex(uintmax_t value) { + nall::string temp; + temp.reserve(strhex(0, value)); + strhex(temp(), value); + return temp; +} + +nall::string strsigned(intmax_t value) { + nall::string temp; + temp.reserve(strsigned(0, value)); + strsigned(temp(), value); + return temp; +} + +nall::string strunsigned(uintmax_t value) { + nall::string temp; + temp.reserve(strunsigned(0, value)); + strunsigned(temp(), value); + return temp; +} + +nall::string strbin(uintmax_t value) { + nall::string temp; + temp.reserve(strbin(0, value)); + strbin(temp(), value); + return temp; +} + +nall::string strdouble(double value) { + nall::string temp; + temp.reserve(strdouble(0, value)); + strdouble(temp(), value); + return temp; +} + +#endif diff --git a/src/lib/nall/utf8.hpp b/src/lib/nall/utf8.hpp index ad23fb73..cae4e1ba 100644 --- a/src/lib/nall/utf8.hpp +++ b/src/lib/nall/utf8.hpp @@ -14,7 +14,7 @@ namespace nall { //UTF-8 to UTF-16 - class utf16 { + class utf16_t { public: operator wchar_t*() { return buffer; @@ -24,14 +24,14 @@ namespace nall { return buffer; } - utf16(const char *s = "") { + utf16_t(const char *s = "") { if(!s) s = ""; unsigned length = MultiByteToWideChar(CP_UTF8, 0, s, -1, 0, 0); buffer = new(zeromemory) wchar_t[length + 1]; MultiByteToWideChar(CP_UTF8, 0, s, -1, buffer, length); } - ~utf16() { + ~utf16_t() { delete[] buffer; } @@ -40,7 +40,7 @@ namespace nall { }; //UTF-16 to UTF-8 - class utf8 { + class utf8_t { public: operator char*() { return buffer; @@ -50,14 +50,14 @@ namespace nall { return buffer; } - utf8(const wchar_t *s = L"") { + utf8_t(const wchar_t *s = L"") { if(!s) s = L""; unsigned length = WideCharToMultiByte(CP_UTF8, 0, s, -1, 0, 0, (const char*)0, (BOOL*)0); buffer = new(zeromemory) char[length + 1]; WideCharToMultiByte(CP_UTF8, 0, s, -1, buffer, length, (const char*)0, (BOOL*)0); } - ~utf8() { + ~utf8_t() { delete[] buffer; } diff --git a/src/lib/ruby/input/sdl.cpp b/src/lib/ruby/input/sdl.cpp index b8f9642a..fa094f9f 100644 --- a/src/lib/ruby/input/sdl.cpp +++ b/src/lib/ruby/input/sdl.cpp @@ -3,7 +3,7 @@ //================ //Keyboard and mouse are controlled directly via Xlib, //as SDL cannot capture input from windows it does not create itself. -//SDL is used only to handle joysticks. +//SDL is used only to handle joysticks / gamepads. #include #include @@ -15,21 +15,21 @@ namespace ruby { #include "sdl.hpp" -using namespace nall; -class pInputSDL { -public: +struct pInputSDL { #include "xlibkeys.hpp" InputSDL &self; - Display *display; - Window rootwindow; - unsigned screenwidth, screenheight; - unsigned relativex, relativey; - bool mouseacquired; - Cursor InvisibleCursor; - SDL_Joystick *gamepad[joypad<>::count]; struct { + Display *display; + Window rootwindow; + Cursor InvisibleCursor; + SDL_Joystick *gamepad[joypad<>::count]; + + unsigned screenwidth, screenheight; + unsigned relativex, relativey; + bool mouseacquired; + //mouse device settings int accel_numerator; int accel_denominator; @@ -73,35 +73,35 @@ public: bool acquire() { if(acquired()) return true; - if(XGrabPointer(display, settings.handle, True, 0, GrabModeAsync, GrabModeAsync, - rootwindow, InvisibleCursor, CurrentTime) == GrabSuccess) { + if(XGrabPointer(device.display, settings.handle, True, 0, GrabModeAsync, GrabModeAsync, + device.rootwindow, device.InvisibleCursor, CurrentTime) == GrabSuccess) { //backup existing cursor acceleration settings - XGetPointerControl(display, &device.accel_numerator, &device.accel_denominator, &device.threshold); + XGetPointerControl(device.display, &device.accel_numerator, &device.accel_denominator, &device.threshold); //disable cursor acceleration - XChangePointerControl(display, True, False, 1, 1, 0); + XChangePointerControl(device.display, True, False, 1, 1, 0); //center cursor (so that first relative poll returns 0, 0 if mouse has not moved) - XWarpPointer(display, None, rootwindow, 0, 0, 0, 0, screenwidth / 2, screenheight / 2); + XWarpPointer(device.display, None, device.rootwindow, 0, 0, 0, 0, device.screenwidth / 2, device.screenheight / 2); - return mouseacquired = true; + return device.mouseacquired = true; } else { - return mouseacquired = false; + return device.mouseacquired = false; } } bool unacquire() { if(acquired()) { //restore cursor acceleration and release cursor - XChangePointerControl(display, True, True, device.accel_numerator, device.accel_denominator, device.threshold); - XUngrabPointer(display, CurrentTime); - mouseacquired = false; + XChangePointerControl(device.display, True, True, device.accel_numerator, device.accel_denominator, device.threshold); + XUngrabPointer(device.display, CurrentTime); + device.mouseacquired = false; } return true; } bool acquired() { - return mouseacquired; + return device.mouseacquired; } bool poll(int16_t *table) { @@ -112,7 +112,7 @@ public: //======== char state[32]; - XQueryKeymap(display, state); + XQueryKeymap(device.display, state); for(unsigned i = 0; i < keyboard::limit; i++) { uint8_t code = keycode[i]; @@ -128,28 +128,28 @@ public: int root_x_return = 0, root_y_return = 0; int win_x_return = 0, win_y_return = 0; unsigned int mask_return = 0; - XQueryPointer(display, settings.handle, + XQueryPointer(device.display, settings.handle, &root_return, &child_return, &root_x_return, &root_y_return, &win_x_return, &win_y_return, &mask_return); if(acquired()) { XWindowAttributes attributes; - XGetWindowAttributes(display, settings.handle, &attributes); + XGetWindowAttributes(device.display, settings.handle, &attributes); //absolute -> relative conversion - table[mouse::x] = (int16_t)(root_x_return - screenwidth / 2); - table[mouse::y] = (int16_t)(root_y_return - screenheight / 2); + table[mouse::x] = (int16_t)(root_x_return - device.screenwidth / 2); + table[mouse::y] = (int16_t)(root_y_return - device.screenheight / 2); if(table[mouse::x] != 0 || table[mouse::y] != 0) { //if mouse movement occurred, re-center mouse for next poll - XWarpPointer(display, None, rootwindow, 0, 0, 0, 0, screenwidth / 2, screenheight / 2); + XWarpPointer(device.display, None, device.rootwindow, 0, 0, 0, 0, device.screenwidth / 2, device.screenheight / 2); } } else { - table[mouse::x] = (int16_t)(root_x_return - relativex); - table[mouse::y] = (int16_t)(root_y_return - relativey); + table[mouse::x] = (int16_t)(root_x_return - device.relativex); + table[mouse::y] = (int16_t)(root_y_return - device.relativey); - relativex = root_x_return; - relativey = root_y_return; + device.relativex = root_x_return; + device.relativey = root_y_return; } //manual device polling is limited to only five buttons ... @@ -165,7 +165,7 @@ public: SDL_JoystickUpdate(); for(unsigned i = 0; i < joypad<>::count; i++) { - if(!gamepad[i]) continue; + if(!device.gamepad[i]) continue; unsigned index = joypad<>::index(i, joypad<>::none); table[index + joypad<>::up ] = false; @@ -177,11 +177,12 @@ public: resistance = max(1, min(99, resistance)); resistance = (int)((double)resistance * 32768.0 / 100.0); - unsigned axes = min((unsigned)joypad<>::axes, SDL_JoystickNumAxes(gamepad[i])); + //axes + unsigned axes = min((unsigned)joypad<>::axes, SDL_JoystickNumAxes(device.gamepad[i])); for(unsigned axis = 0; axis < axes; axis++) { - int16_t value = (int16_t)SDL_JoystickGetAxis(gamepad[i], axis); + int16_t value = (int16_t)SDL_JoystickGetAxis(device.gamepad[i], axis); table[index + joypad<>::axis + axis] = value; - if(axis == 0) { //X-axis + if(axis == 0) { //X-axis table[index + joypad<>::left ] |= value < -resistance; table[index + joypad<>::right] |= value > +resistance; } else if(axis == 1) { //Y-axis @@ -190,8 +191,18 @@ public: } } + //POV hats + if(SDL_JoystickNumHats(device.gamepad[i]) >= 1) { + uint8_t state = SDL_JoystickGetHat(device.gamepad[i], 0); + table[index + joypad<>::up ] |= state & SDL_HAT_UP; + table[index + joypad<>::down ] |= state & SDL_HAT_DOWN; + table[index + joypad<>::left ] |= state & SDL_HAT_LEFT; + table[index + joypad<>::right] |= state & SDL_HAT_RIGHT; + } + + //buttons for(unsigned button = 0; button < joypad<>::buttons; button++) { - table[index + joypad<>::button + button] = SDL_JoystickGetButton(gamepad[i], button); + table[index + joypad<>::button + button] = SDL_JoystickGetButton(device.gamepad[i], button); } } @@ -203,12 +214,12 @@ public: SDL_InitSubSystem(SDL_INIT_JOYSTICK); SDL_JoystickEventState(SDL_IGNORE); - display = XOpenDisplay(0); - rootwindow = DefaultRootWindow(display); + device.display = XOpenDisplay(0); + device.rootwindow = DefaultRootWindow(device.display); XWindowAttributes attributes; - XGetWindowAttributes(display, rootwindow, &attributes); - screenwidth = attributes.width; - screenheight = attributes.height; + XGetWindowAttributes(device.display, device.rootwindow, &attributes); + device.screenwidth = attributes.width; + device.screenheight = attributes.height; //Xlib: "because XShowCursor(false) would be too easy." //create a fully transparent cursor named InvisibleCursor, @@ -216,19 +227,19 @@ public: Pixmap pixmap; XColor black, unused; static char invisible_data[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; - Colormap colormap = DefaultColormap(display, DefaultScreen(display)); - XAllocNamedColor(display, colormap, "black", &black, &unused); - pixmap = XCreateBitmapFromData(display, settings.handle, invisible_data, 8, 8); - InvisibleCursor = XCreatePixmapCursor(display, pixmap, pixmap, &black, &black, 0, 0); - XFreePixmap(display, pixmap); - XFreeColors(display, colormap, &black.pixel, 1, 0); + Colormap colormap = DefaultColormap(device.display, DefaultScreen(device.display)); + XAllocNamedColor(device.display, colormap, "black", &black, &unused); + pixmap = XCreateBitmapFromData(device.display, settings.handle, invisible_data, 8, 8); + device.InvisibleCursor = XCreatePixmapCursor(device.display, pixmap, pixmap, &black, &black, 0, 0); + XFreePixmap(device.display, pixmap); + XFreeColors(device.display, colormap, &black.pixel, 1, 0); - mouseacquired = false; - relativex = 0; - relativey = 0; + device.mouseacquired = false; + device.relativex = 0; + device.relativey = 0; for(unsigned i = 0; i < joypad<>::count && i < SDL_NumJoysticks(); i++) { - gamepad[i] = SDL_JoystickOpen(i); + device.gamepad[i] = SDL_JoystickOpen(i); } return true; @@ -236,18 +247,18 @@ public: void term() { unacquire(); - XFreeCursor(display, InvisibleCursor); + XFreeCursor(device.display, device.InvisibleCursor); for(unsigned i = 0; i < joypad<>::count; i++) { - if(gamepad[i]) SDL_JoystickClose(gamepad[i]); - gamepad[i] = 0; + if(device.gamepad[i]) SDL_JoystickClose(device.gamepad[i]); + device.gamepad[i] = 0; } SDL_QuitSubSystem(SDL_INIT_JOYSTICK); } pInputSDL(InputSDL &self_) : self(self_) { - for(unsigned i = 0; i < joypad<>::count; i++) gamepad[i] = 0; + for(unsigned i = 0; i < joypad<>::count; i++) device.gamepad[i] = 0; settings.analog_axis_resistance = 75; } }; @@ -265,4 +276,4 @@ void InputSDL::term() { p.term(); } InputSDL::InputSDL() : p(*new pInputSDL(*this)) {} InputSDL::~InputSDL() { delete &p; } -} //namespace ruby +} diff --git a/src/lib/ruby/ruby.cpp b/src/lib/ruby/ruby.cpp index fa67d734..2a8bd1b9 100644 --- a/src/lib/ruby/ruby.cpp +++ b/src/lib/ruby/ruby.cpp @@ -1,4 +1,6 @@ #include +using namespace nall; + #include namespace ruby { diff --git a/src/lib/ruby/ruby.hpp b/src/lib/ruby/ruby.hpp index 9aff63b2..0ac6d269 100644 --- a/src/lib/ruby/ruby.hpp +++ b/src/lib/ruby/ruby.hpp @@ -12,10 +12,6 @@ #include #include #include -using nall::min; -using nall::max; -using nall::sclamp; -using nall::zeromemory; namespace ruby { diff --git a/src/lib/ruby/video/xv.cpp b/src/lib/ruby/video/xv.cpp index 2198ed51..62614fe6 100644 --- a/src/lib/ruby/video/xv.cpp +++ b/src/lib/ruby/video/xv.cpp @@ -3,9 +3,9 @@ #include #include #include +#include #include #include -#include extern "C" XvImage* XvShmCreateImage(Display*, XvPortID, int, char*, int, int, XShmSegmentInfo*); @@ -16,18 +16,35 @@ namespace ruby { class pVideoXv { public: VideoXv &self; - Display *display; - GC gc; - int screen, xv_port, xv_depth, xv_visualid; - XvImage *xvimage; - XShmSegmentInfo shminfo; + uint32_t *buffer; + uint8_t *ytable, *utable, *vtable; - bool use_child_window; - Window xwindow; - Colormap colormap; + enum XvFormat { + XvFormatRGB32, + XvFormatRGB24, + XvFormatRGB16, + XvFormatRGB15, + XvFormatYUY2, + XvFormatUYVY, + XvFormatUnknown + }; - uint8_t *ytable, *utable, *vtable; + struct { + Display *display; + GC gc; + Window window; + Colormap colormap; + XShmSegmentInfo shminfo; + + int port; + int depth; + int visualid; + + XvImage *image; + XvFormat format; + uint32_t fourcc; + } device; struct { Window handle; @@ -57,9 +74,9 @@ public: if(setting == Video::Synchronize) { Display *display = XOpenDisplay(0); Atom atom = XInternAtom(display, "XV_SYNC_TO_VBLANK", true); - if(atom != None) { + if(atom != None && device.port >= 0) { settings.synchronize = param; - XvSetPortAttribute(display, xv_port, atom, settings.synchronize); + XvSetPortAttribute(display, device.port, atom, settings.synchronize); return true; } return false; @@ -85,136 +102,181 @@ public: void refresh(unsigned width, unsigned height) { XWindowAttributes target; - XGetWindowAttributes(display, xwindow, &target); + XGetWindowAttributes(device.display, device.window, &target); - if(use_child_window) { - //we must ensure that the child window is the same size as the parent window. - //unfortunately, we cannot hook the parent window resize event notification, - //as we did not create the parent window, nor have any knowledge of the toolkit used. - //therefore, inelegant as it may be, we query each window size and resize as needed. - XWindowAttributes parent; - XGetWindowAttributes(display, settings.handle, &parent); - if(target.width != parent.width || target.height != parent.height) { - XResizeWindow(display, xwindow, parent.width, parent.height); - } - - //update target width and height attributes - XGetWindowAttributes(display, xwindow, &target); - } - - uint32_t *input = (uint32_t*)buffer; - uint16_t *output = (uint16_t*)xvimage->data; - for(unsigned y = 0; y < height; y++) { - for(unsigned x = 0; x < width >> 1; x++) { - uint32_t p0 = *input++; - uint32_t p1 = *input++; - p0 = ((p0 >> 8) & 0xf800) + ((p0 >> 5) & 0x07e0) + ((p0 >> 3) & 0x001f); - p1 = ((p1 >> 8) & 0xf800) + ((p1 >> 5) & 0x07e0) + ((p1 >> 3) & 0x001f); - - uint8_t u = (utable[p0] + utable[p1]) >> 1; - uint8_t v = (vtable[p0] + vtable[p1]) >> 1; - - *output++ = (u << 8) | ytable[p0]; - *output++ = (v << 8) | ytable[p1]; - } - input += 1024 - width; - output += 1024 - width; + //we must ensure that the child window is the same size as the parent window. + //unfortunately, we cannot hook the parent window resize event notification, + //as we did not create the parent window, nor have any knowledge of the toolkit used. + //therefore, query each window size and resize as needed. + XWindowAttributes parent; + XGetWindowAttributes(device.display, settings.handle, &parent); + if(target.width != parent.width || target.height != parent.height) { + XResizeWindow(device.display, device.window, parent.width, parent.height); } - XvShmPutImage(display, xv_port, xwindow, gc, xvimage, + //update target width and height attributes + XGetWindowAttributes(device.display, device.window, &target); + + switch(device.format) { + case XvFormatRGB32: render_rgb32(width, height); break; + case XvFormatRGB24: render_rgb24(width, height); break; + case XvFormatRGB16: render_rgb16(width, height); break; + case XvFormatRGB15: render_rgb15(width, height); break; + case XvFormatYUY2: render_yuy2 (width, height); break; + case XvFormatUYVY: render_uyvy (width, height); break; + } + + XvShmPutImage(device.display, device.port, device.window, device.gc, device.image, 0, 0, width, height, 0, 0, target.width, target.height, true); } bool init() { - display = XOpenDisplay(0); - screen = DefaultScreen(display); + device.display = XOpenDisplay(0); - //XShm is required for rendering - if(!XShmQueryExtension(display)) { + if(!XShmQueryExtension(device.display)) { fprintf(stderr, "VideoXv: XShm extension not found.\n"); return false; } - //find an appropriate port, if possible - xv_port = -1; + //find an appropriate Xv port + device.port = -1; XvAdaptorInfo *adaptor_info; unsigned adaptor_count; - XvQueryAdaptors(display, DefaultRootWindow(display), &adaptor_count, &adaptor_info); + XvQueryAdaptors(device.display, DefaultRootWindow(device.display), &adaptor_count, &adaptor_info); for(unsigned i = 0; i < adaptor_count; i++) { //find adaptor that supports both input (memory->drawable) and image (drawable->screen) masks if(adaptor_info[i].num_formats < 1) continue; if(!(adaptor_info[i].type & XvInputMask)) continue; if(!(adaptor_info[i].type & XvImageMask)) continue; - xv_port = adaptor_info[i].base_id; - xv_depth = adaptor_info[i].formats->depth; - xv_visualid = adaptor_info[i].formats->visual_id; + device.port = adaptor_info[i].base_id; + device.depth = adaptor_info[i].formats->depth; + device.visualid = adaptor_info[i].formats->visual_id; break; } XvFreeAdaptorInfo(adaptor_info); - if(xv_port == -1) { + if(device.port < 0) { fprintf(stderr, "VideoXv: failed to find valid XvPort.\n"); return false; } + //create child window to attach to parent window. + //this is so that even if parent window visual depth doesn't match Xv visual + //(common with composited windows), Xv can still render to child window. XWindowAttributes window_attributes; - XGetWindowAttributes(display, settings.handle, &window_attributes); + XGetWindowAttributes(device.display, settings.handle, &window_attributes); - if(xv_depth == window_attributes.depth) { - //Xv port is depth-compatible with target output window - use_child_window = false; - xwindow = settings.handle; - } else { - //Xv port is not depth-compatible with target output window - //this is often the case when a 32bpp composited window is used with a 24bpp-only Xv adaptor - //the only way to render to target is to create a child window with the Xv ports' depth - use_child_window = true; - XVisualInfo visualtemplate; - visualtemplate.visualid = xv_visualid; - visualtemplate.screen = screen; - visualtemplate.depth = xv_depth; - visualtemplate.visual = 0; - int visualmatches = 0; - XVisualInfo *visualinfo = XGetVisualInfo(display, VisualIDMask | VisualScreenMask | VisualDepthMask, &visualtemplate, &visualmatches); - if(visualmatches < 1 || !visualinfo->visual) { - if(visualinfo) XFree(visualinfo); - fprintf(stderr, "VideoXv: unable to find Xv-compatible visual.\n"); - return false; - } - - colormap = XCreateColormap(display, settings.handle, visualinfo->visual, AllocNone); - 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, xv_depth, InputOutput, visualinfo->visual, - CWColormap | CWBorderPixel | CWEventMask, &attributes); - XFree(visualinfo); - XSetWindowBackground(display, xwindow, /* color = */ 0); - XMapWindow(display, xwindow); + XVisualInfo visualtemplate; + visualtemplate.visualid = device.visualid; + visualtemplate.screen = DefaultScreen(device.display); + visualtemplate.depth = device.depth; + visualtemplate.visual = 0; + int visualmatches = 0; + XVisualInfo *visualinfo = XGetVisualInfo(device.display, VisualIDMask | VisualScreenMask | VisualDepthMask, &visualtemplate, &visualmatches); + if(visualmatches < 1 || !visualinfo->visual) { + if(visualinfo) XFree(visualinfo); + fprintf(stderr, "VideoXv: unable to find Xv-compatible visual.\n"); + return false; } - gc = XCreateGC(display, xwindow, 0, 0); + device.colormap = XCreateColormap(device.display, settings.handle, visualinfo->visual, AllocNone); + XSetWindowAttributes attributes; + attributes.colormap = device.colormap; + attributes.border_pixel = 0; + attributes.event_mask = StructureNotifyMask; + device.window = XCreateWindow(device.display, /* parent = */ settings.handle, + /* x = */ 0, /* y = */ 0, window_attributes.width, window_attributes.height, + /* border_width = */ 0, device.depth, InputOutput, visualinfo->visual, + CWColormap | CWBorderPixel | CWEventMask, &attributes); + XFree(visualinfo); + XSetWindowBackground(device.display, device.window, /* color = */ 0); + XMapWindow(device.display, device.window); + + device.gc = XCreateGC(device.display, device.window, 0, 0); //set colorkey to auto paint, so that Xv video output is always visible - Atom atom = XInternAtom(display, "XV_AUTOPAINT_COLORKEY", true); - if(atom != None) XvSetPortAttribute(display, xv_port, atom, 1); + Atom atom = XInternAtom(device.display, "XV_AUTOPAINT_COLORKEY", true); + if(atom != None) XvSetPortAttribute(device.display, device.port, atom, 1); - //0x32595559 = 16-bit Y8U8,Y8V8 (YUY2) - xvimage = XvShmCreateImage(display, xv_port, 0x32595559, 0, 1024, 1024, &shminfo); - if(!xvimage) { + //find optimal rendering format + device.format = XvFormatUnknown; + signed format_count; + XvImageFormatValues *format = XvListImageFormats(device.display, device.port, &format_count); + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvRGB && format[i].bits_per_pixel == 32) { + device.format = XvFormatRGB32; + device.fourcc = format[i].id; + break; + } + } + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvRGB && format[i].bits_per_pixel == 24) { + device.format = XvFormatRGB24; + device.fourcc = format[i].id; + break; + } + } + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvRGB && format[i].bits_per_pixel == 16) { + device.format = XvFormatRGB16; + device.fourcc = format[i].id; + break; + } + } + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvRGB && format[i].bits_per_pixel == 15) { + device.format = XvFormatRGB15; + device.fourcc = format[i].id; + break; + } + } + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvYUV && format[i].bits_per_pixel == 16 && format[i].format == XvPacked) { + if(format[i].component_order[0] == 'Y' && format[i].component_order[1] == 'U' + && format[i].component_order[2] == 'Y' && format[i].component_order[3] == 'V' + ) { + device.format = XvFormatYUY2; + device.fourcc = format[i].id; + break; + } + } + } + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvYUV && format[i].bits_per_pixel == 16 && format[i].format == XvPacked) { + if(format[i].component_order[0] == 'U' && format[i].component_order[1] == 'Y' + && format[i].component_order[2] == 'V' && format[i].component_order[3] == 'Y' + ) { + device.format = XvFormatUYVY; + device.fourcc = format[i].id; + break; + } + } + } + + free(format); + if(device.format == XvFormatUnknown) { + fprintf(stderr, "VideoXv: unable to find a supported image format.\n"); + return false; + } + + device.image = XvShmCreateImage(device.display, device.port, device.fourcc, 0, 1024, 1024, &device.shminfo); + if(!device.image) { fprintf(stderr, "VideoXv: XShmCreateImage failed.\n"); return false; } - shminfo.shmid = shmget(IPC_PRIVATE, xvimage->data_size, IPC_CREAT | 0777); - shminfo.shmaddr = xvimage->data = (char*)shmat(shminfo.shmid, 0, 0); - shminfo.readOnly = false; - if(!XShmAttach(display, &shminfo)) { + device.shminfo.shmid = shmget(IPC_PRIVATE, device.image->data_size, IPC_CREAT | 0777); + device.shminfo.shmaddr = device.image->data = (char*)shmat(device.shminfo.shmid, 0, 0); + device.shminfo.readOnly = false; + if(!XShmAttach(device.display, &device.shminfo)) { fprintf(stderr, "VideoXv: XShmAttach failed.\n"); return false; } @@ -226,18 +288,16 @@ public: } void term() { - XShmDetach(display, &shminfo); + XShmDetach(device.display, &device.shminfo); - if(use_child_window) { - if(xwindow) { - XUnmapWindow(display, xwindow); - xwindow = 0; - } + if(device.window) { + XUnmapWindow(device.display, device.window); + device.window = 0; + } - if(colormap) { - XFreeColormap(display, colormap); - colormap = 0; - } + if(device.colormap) { + XFreeColormap(device.display, device.colormap); + device.colormap = 0; } if(buffer) { delete[] buffer; buffer = 0; } @@ -246,6 +306,110 @@ public: if(vtable) { delete[] vtable; vtable = 0; } } + void render_rgb32(unsigned width, unsigned height) { + uint32_t *input = (uint32_t*)buffer; + uint32_t *output = (uint32_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + memcpy(output, input, width * 4); + input += 1024 - width; + output += 1024 - width; + } + } + + void render_rgb24(unsigned width, unsigned height) { + uint32_t *input = (uint32_t*)buffer; + uint8_t *output = (uint8_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width; x++) { + uint32_t p = *input++; + *output++ = p; + *output++ = p >> 8; + *output++ = p >> 16; + } + + input += (1024 - width); + output += (1024 - width) * 3; + } + } + + void render_rgb16(unsigned width, unsigned height) { + uint32_t *input = (uint32_t*)buffer; + uint16_t *output = (uint16_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width; x++) { + uint32_t p = *input++; + *output++ = ((p >> 8) & 0xf800) | ((p >> 5) & 0x07e0) | ((p >> 3) & 0x001f); //RGB32->RGB16 + } + + input += 1024 - width; + output += 1024 - width; + } + } + + void render_rgb15(unsigned width, unsigned height) { + uint32_t *input = (uint32_t*)buffer; + uint16_t *output = (uint16_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width; x++) { + uint32_t p = *input++; + *output++ = ((p >> 9) & 0x7c00) | ((p >> 6) & 0x03e0) | ((p >> 3) & 0x001f); //RGB32->RGB15 + } + + input += 1024 - width; + output += 1024 - width; + } + } + + void render_yuy2(unsigned width, unsigned height) { + uint32_t *input = (uint32_t*)buffer; + uint16_t *output = (uint16_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width >> 1; x++) { + uint32_t p0 = *input++; + uint32_t p1 = *input++; + p0 = ((p0 >> 8) & 0xf800) + ((p0 >> 5) & 0x07e0) + ((p0 >> 3) & 0x001f); //RGB32->RGB16 + p1 = ((p1 >> 8) & 0xf800) + ((p1 >> 5) & 0x07e0) + ((p1 >> 3) & 0x001f); //RGB32->RGB16 + + uint8_t u = (utable[p0] + utable[p1]) >> 1; + uint8_t v = (vtable[p0] + vtable[p1]) >> 1; + + *output++ = (u << 8) | ytable[p0]; + *output++ = (v << 8) | ytable[p1]; + } + + input += 1024 - width; + output += 1024 - width; + } + } + + void render_uyvy(unsigned width, unsigned height) { + uint32_t *input = (uint32_t*)buffer; + uint16_t *output = (uint16_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width >> 1; x++) { + uint32_t p0 = *input++; + uint32_t p1 = *input++; + p0 = ((p0 >> 8) & 0xf800) + ((p0 >> 5) & 0x07e0) + ((p0 >> 3) & 0x001f); + p1 = ((p1 >> 8) & 0xf800) + ((p1 >> 5) & 0x07e0) + ((p1 >> 3) & 0x001f); + + uint8_t u = (utable[p0] + utable[p1]) >> 1; + uint8_t v = (vtable[p0] + vtable[p1]) >> 1; + + *output++ = (ytable[p0] << 8) | u; + *output++ = (ytable[p1] << 8) | v; + } + + input += 1024 - width; + output += 1024 - width; + } + } + void init_yuv_tables() { ytable = new uint8_t[65536]; utable = new uint8_t[65536]; @@ -254,17 +418,18 @@ public: for(unsigned i = 0; i < 65536; i++) { //extract RGB565 color data from i uint8_t r = (i >> 11) & 31, g = (i >> 5) & 63, b = (i) & 31; - r = (r << 3) | (r >> 2); //R5->R8 - g = (g << 2) | (g >> 4); //G6->G8 - b = (b << 3) | (b >> 2); //B5->B8 + r = (r << 3) | (r >> 2); //R5->R8 + g = (g << 2) | (g >> 4); //G6->G8 + b = (b << 3) | (b >> 2); //B5->B8 - //RGB->YUV conversion + //ITU-R Recommendation BT.601 + //double lr = 0.299, lg = 0.587, lb = 0.114; int y = int( +(double(r) * 0.257) + (double(g) * 0.504) + (double(b) * 0.098) + 16.0 ); int u = int( -(double(r) * 0.148) - (double(g) * 0.291) + (double(b) * 0.439) + 128.0 ); int v = int( +(double(r) * 0.439) - (double(g) * 0.368) - (double(b) * 0.071) + 128.0 ); - //RGB->YCbCr conversion - //double lr = 0.2126, lb = 0.0722, lg = (1.0 - lr - lb); + //ITU-R Recommendation BT.709 + //double lr = 0.2126, lg = 0.7152, lb = 0.0722; //int y = int( double(r) * lr + double(g) * lg + double(b) * lb ); //int u = int( (double(b) - y) / (2.0 - 2.0 * lb) + 128.0 ); //int v = int( (double(r) - y) / (2.0 - 2.0 * lr) + 128.0 ); @@ -276,15 +441,15 @@ public: } pVideoXv(VideoXv &self_) : self(self_) { - use_child_window = false; - xwindow = 0; - colormap = 0; + device.window = 0; + device.colormap = 0; + device.port = -1; ytable = 0; utable = 0; vtable = 0; - settings.handle = 0; + settings.handle = 0; settings.synchronize = false; } }; @@ -301,4 +466,4 @@ void VideoXv::term() { p.term(); } VideoXv::VideoXv() : p(*new pVideoXv(*this)) {} VideoXv::~VideoXv() { delete &p; } -} //namespace ruby +} diff --git a/src/memory/memory.cpp b/src/memory/memory.cpp index f9a27a53..2c607b1f 100644 --- a/src/memory/memory.cpp +++ b/src/memory/memory.cpp @@ -1,8 +1,6 @@ #include <../base.hpp> #define MEMORY_CPP -#include "memory_rw.cpp" - namespace memory { MMIOAccess mmio; StaticRAM wram(128 * 1024); diff --git a/src/memory/memory.hpp b/src/memory/memory.hpp index ae84c817..03bb628f 100644 --- a/src/memory/memory.hpp +++ b/src/memory/memory.hpp @@ -1,14 +1,7 @@ struct Memory { - virtual unsigned size() { return 0; } + virtual unsigned size() const { return 0; } virtual uint8 read(unsigned addr) = 0; virtual void write(unsigned addr, uint8 data) = 0; - - //deprecated, still used by S-CPU, S-SMP disassemblers - enum { WRAP_NONE = 0, WRAP_BANK = 1, WRAP_PAGE = 2 }; - virtual uint16 read_word(unsigned addr, unsigned wrap = WRAP_NONE); - virtual void write_word(unsigned addr, uint16 data, unsigned wrap = WRAP_NONE); - virtual uint32 read_long(unsigned addr, unsigned wrap = WRAP_NONE); - virtual void write_long(unsigned addr, uint32 data, unsigned wrap = WRAP_NONE); }; struct MMIO { @@ -28,7 +21,7 @@ struct UnmappedMMIO : MMIO { struct StaticRAM : Memory { uint8* handle() { return data; } - unsigned size() { return datasize; } + unsigned size() const { return datasize; } inline uint8 read(unsigned addr) { return data[addr]; } inline void write(unsigned addr, uint8 n) { data[addr] = n; } @@ -47,7 +40,7 @@ struct MappedRAM : Memory { void map(uint8 *source, unsigned length) { data = source; datasize = length > 0 ? length : -1U; } void write_protect(bool status) { write_protection = status; } uint8* handle() { return data; } - unsigned size() { return datasize; } + unsigned size() const { return datasize; } inline uint8 read(unsigned addr) { return data[addr]; } inline void write(unsigned addr, uint8 n) { if(!write_protection) data[addr] = n; } @@ -83,7 +76,7 @@ public: alwaysinline uint8 read(unsigned addr) { #if defined(CHEAT_SYSTEM) - if(cheat.enabled() && cheat.exists(addr)) { + if(cheat.active() && cheat.exists(addr)) { uint8 r; if(cheat.read(addr, r)) return r; } @@ -112,9 +105,8 @@ public: return 12; } - virtual void load_cart() = 0; + virtual bool load_cart() = 0; virtual void unload_cart() = 0; - virtual bool cart_loaded() = 0; virtual void power() = 0; virtual void reset() = 0; @@ -131,12 +123,12 @@ protected: }; namespace memory { - extern MMIOAccess mmio; //S-CPU, S-PPU - extern StaticRAM wram; //S-CPU - extern StaticRAM apuram; //S-SMP, S-DSP - extern StaticRAM vram; //S-PPU - extern StaticRAM oam; //S-PPU - extern StaticRAM cgram; //S-PPU + extern MMIOAccess mmio; //S-CPU, S-PPU + extern StaticRAM wram; //S-CPU + extern StaticRAM apuram; //S-SMP, S-DSP + extern StaticRAM vram; //S-PPU + extern StaticRAM oam; //S-PPU + extern StaticRAM cgram; //S-PPU extern UnmappedMemory memory_unmapped; extern UnmappedMMIO mmio_unmapped; diff --git a/src/memory/memory_rw.cpp b/src/memory/memory_rw.cpp deleted file mode 100644 index 48623fa7..00000000 --- a/src/memory/memory_rw.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#ifdef MEMORY_CPP - -uint16 Memory::read_word(unsigned addr, unsigned wrap) { - uint16 r; - switch(wrap) { - case WRAP_NONE: { - r = read(addr); - r |= read(addr + 1) << 8; - } break; - case WRAP_BANK: { - r = read(addr); - r |= read((addr & 0xff0000) | ((addr + 1) & 0xffff)) << 8; - } break; - case WRAP_PAGE: { - r = read(addr); - r |= read((addr & 0xffff00) | ((addr + 1) & 0xff)) << 8; - } break; - } - return r; -} - -void Memory::write_word(unsigned addr, uint16 data, unsigned wrap) { - switch(wrap) { - case WRAP_NONE: { - write(addr, data); - write(addr + 1, data >> 8); - } return; - case WRAP_BANK: { - write(addr, data); - write((addr & 0xff0000) | ((addr + 1) & 0xffff), data >> 8); - } return; - case WRAP_PAGE: { - write(addr, data); - write((addr & 0xffff00) | ((addr + 1) & 0xff), data >> 8); - } return; - } -} - -uint32 Memory::read_long(unsigned addr, unsigned wrap) { - uint32 r; - switch(wrap) { - case WRAP_NONE: { - r = read(addr); - r |= read(addr + 1) << 8; - r |= read(addr + 2) << 16; - } break; - case WRAP_BANK: { - r = read(addr); - r |= read((addr & 0xff0000) | ((addr + 1) & 0xffff)) << 8; - r |= read((addr & 0xff0000) | ((addr + 2) & 0xffff)) << 16; - } break; - case WRAP_PAGE: { - r = read(addr); - r |= read((addr & 0xffff00) | ((addr + 1) & 0xff)) << 8; - r |= read((addr & 0xffff00) | ((addr + 2) & 0xff)) << 16; - } break; - } - return r; -} - -void Memory::write_long(unsigned addr, uint32 data, unsigned wrap) { - switch(wrap) { - case WRAP_NONE: { - write(addr, data); - write(addr + 1, data >> 8); - write(addr + 2, data >> 16); - } return; - case WRAP_BANK: { - write(addr, data); - write((addr & 0xff0000) | ((addr + 1) & 0xffff), data >> 8); - write((addr & 0xff0000) | ((addr + 2) & 0xffff), data >> 16); - } return; - case WRAP_PAGE: { - write(addr, data); - write((addr & 0xffff00) | ((addr + 1) & 0xff), data >> 8); - write((addr & 0xffff00) | ((addr + 2) & 0xff), data >> 16); - } return; - } -} - -#endif //ifdef MEMORY_CPP diff --git a/src/memory/smemory/mapper/chip.cpp b/src/memory/smemory/mapper/chip.cpp index 9c65cba7..dd23782c 100644 --- a/src/memory/smemory/mapper/chip.cpp +++ b/src/memory/smemory/mapper/chip.cpp @@ -6,7 +6,7 @@ void sBus::map_cx4() { } void sBus::map_dsp1() { - switch(cartridge.info.dsp1_mapper) { + switch(cartridge.dsp1_mapper()) { case Cartridge::DSP1LoROM1MB: { map(MapDirect, 0x20, 0x3f, 0x8000, 0xffff, dsp1); map(MapDirect, 0xa0, 0xbf, 0x8000, 0xffff, dsp1); @@ -51,4 +51,4 @@ void sBus::map_st010() { map(MapDirect, 0xe8, 0xef, 0x0000, 0x0fff, st010); } -#endif //ifdef SMEMORY_CPP +#endif diff --git a/src/memory/smemory/mapper/generic.cpp b/src/memory/smemory/mapper/generic.cpp index aab7e2ca..cf15d320 100644 --- a/src/memory/smemory/mapper/generic.cpp +++ b/src/memory/smemory/mapper/generic.cpp @@ -33,13 +33,13 @@ void sBus::map_generic() { } break; case Cartridge::SPC7110ROM: { - map(MapDirect, 0x00, 0x00, 0x6000, 0x7fff, spc7110); //save RAM w/custom logic - map(MapShadow, 0x00, 0x0f, 0x8000, 0xffff, memory::cartrom); //program ROM - map(MapDirect, 0x30, 0x30, 0x6000, 0x7fff, spc7110); //save RAM w/custom logic - map(MapDirect, 0x50, 0x50, 0x0000, 0xffff, spc7110); //decompression MMIO port - map(MapShadow, 0x80, 0x8f, 0x8000, 0xffff, memory::cartrom); //program ROM - map(MapLinear, 0xc0, 0xcf, 0x0000, 0xffff, memory::cartrom); //program ROM - map(MapDirect, 0xd0, 0xff, 0x0000, 0xffff, spc7110); //MMC-controlled data ROM + map(MapDirect, 0x00, 0x00, 0x6000, 0x7fff, spc7110); //save RAM w/custom logic + map(MapShadow, 0x00, 0x0f, 0x8000, 0xffff, memory::cartrom); //program ROM + map(MapDirect, 0x30, 0x30, 0x6000, 0x7fff, spc7110); //save RAM w/custom logic + map(MapDirect, 0x50, 0x50, 0x0000, 0xffff, spc7110); //decompression MMIO port + map(MapShadow, 0x80, 0x8f, 0x8000, 0xffff, memory::cartrom); //program ROM + map(MapLinear, 0xc0, 0xcf, 0x0000, 0xffff, memory::cartrom); //program ROM + map(MapDirect, 0xd0, 0xff, 0x0000, 0xffff, spc7110); //MMC-controlled data ROM } break; case Cartridge::BSXROM: { @@ -97,8 +97,8 @@ void sBus::map_generic_sram() { //otherwise, default to safer, larger SRAM address window uint16 addr_hi = (memory::cartrom.size() > 0x200000 || memory::cartram.size() > 32 * 1024) ? 0x7fff : 0xffff; map(MapLinear, 0x70, 0x7f, 0x0000, addr_hi, memory::cartram); - if(cartridge.info.mapper != Cartridge::LoROM) return; + if(cartridge.mapper() != Cartridge::LoROM) return; map(MapLinear, 0xf0, 0xff, 0x0000, addr_hi, memory::cartram); } -#endif //ifdef SMEMORY_CPP +#endif diff --git a/src/memory/smemory/mapper/system.cpp b/src/memory/smemory/mapper/system.cpp index ef089f9d..42d62b04 100644 --- a/src/memory/smemory/mapper/system.cpp +++ b/src/memory/smemory/mapper/system.cpp @@ -18,4 +18,4 @@ void sBus::map_system() { map(MapLinear, 0x7e, 0x7f, 0x0000, 0xffff, memory::wram); } -#endif //ifdef SMEMORY_CPP +#endif diff --git a/src/memory/smemory/smemory.cpp b/src/memory/smemory/smemory.cpp index a9ffce6a..b3f1e2a8 100644 --- a/src/memory/smemory/smemory.cpp +++ b/src/memory/smemory/smemory.cpp @@ -17,38 +17,29 @@ void sBus::reset() { set_speed(false); } -void sBus::load_cart() { - if(is_cart_loaded == true) return; +bool sBus::load_cart() { + if(cartridge.loaded() == true) return false; map_reset(); map_generic(); map_system(); - if(cartridge.info.cx4) map_cx4(); - if(cartridge.info.dsp1) map_dsp1(); - if(cartridge.info.dsp2) map_dsp2(); - if(cartridge.info.dsp3) map_dsp3(); - if(cartridge.info.dsp4) map_dsp4(); - if(cartridge.info.obc1) map_obc1(); - if(cartridge.info.st010) map_st010(); + if(cartridge.has_cx4()) map_cx4(); + if(cartridge.has_dsp1()) map_dsp1(); + if(cartridge.has_dsp2()) map_dsp2(); + if(cartridge.has_dsp3()) map_dsp3(); + if(cartridge.has_dsp4()) map_dsp4(); + if(cartridge.has_obc1()) map_obc1(); + if(cartridge.has_st010()) map_st010(); - is_cart_loaded = true; + return true; } void sBus::unload_cart() { - if(is_cart_loaded == false) return; - - is_cart_loaded = false; -} - -bool sBus::cart_loaded() { - return is_cart_loaded; } sBus::sBus() { - is_cart_loaded = false; } sBus::~sBus() { - unload_cart(); } diff --git a/src/memory/smemory/smemory.hpp b/src/memory/smemory/smemory.hpp index 79f35e7e..0e8b07b6 100644 --- a/src/memory/smemory/smemory.hpp +++ b/src/memory/smemory/smemory.hpp @@ -1,8 +1,7 @@ class sBus : public Bus { public: - void load_cart(); + bool load_cart(); void unload_cart(); - bool cart_loaded(); void power(); void reset(); @@ -11,8 +10,6 @@ public: ~sBus(); private: - bool is_cart_loaded; - void map_reset(); void map_system(); void map_generic(); diff --git a/src/ppu/bppu/bppu_render_mode7.cpp b/src/ppu/bppu/bppu_render_mode7.cpp index 4134b51c..4145f642 100644 --- a/src/ppu/bppu/bppu_render_mode7.cpp +++ b/src/ppu/bppu/bppu_render_mode7.cpp @@ -70,7 +70,7 @@ void bPPU::render_line_mode7(uint8 bg, uint8 pri0_pos, uint8 pri1_pos) { py >>= 8; switch(regs.mode7_repeat) { - case 0: //screen repitition outside of screen area + case 0: //screen repetition outside of screen area case 1: { //same as case 0 px &= 1023; py &= 1023; diff --git a/src/ppu/bppu/bppu_render_oam.cpp b/src/ppu/bppu/bppu_render_oam.cpp index ee0e9042..f0da95cb 100644 --- a/src/ppu/bppu/bppu_render_oam.cpp +++ b/src/ppu/bppu/bppu_render_oam.cpp @@ -39,7 +39,7 @@ void bPPU::build_sprite_list() { } sprite_list[i].x = (x << 8) + tableA[0]; - sprite_list[i].y = tableA[1] + 1; + sprite_list[i].y = (tableA[1] + 1) & 0xff; sprite_list[i].character = tableA[2]; sprite_list[i].vflip = !!(tableA[3] & 0x80); sprite_list[i].hflip = !!(tableA[3] & 0x40); @@ -66,7 +66,7 @@ bool bPPU::is_sprite_on_scanline() { void bPPU::load_oam_tiles() { uint16 tile_width = spr->width >> 3; int x = spr->x; - int y = line - spr->y; + int y = (line - spr->y) & 0xff; if(regs.oam_interlace == true) { y <<= 1; } diff --git a/src/ppu/ppu.cpp b/src/ppu/ppu.cpp index 2ddf84cd..d933b67a 100644 --- a/src/ppu/ppu.cpp +++ b/src/ppu/ppu.cpp @@ -25,6 +25,8 @@ void PPU::frame() { } void PPU::power() { + ppu1_version = snes.config.ppu1.version; + ppu2_version = snes.config.ppu2.version; } void PPU::reset() { @@ -38,9 +40,6 @@ PPU::PPU() { status.frames_updated = false; status.frames_rendered = 0; status.frames_executed = 0; - - ppu1_version = 1; - ppu2_version = 3; } PPU::~PPU() { diff --git a/src/reader/gzreader.cpp b/src/reader/gzreader.cpp index 0b30eeb2..3bed61d4 100644 --- a/src/reader/gzreader.cpp +++ b/src/reader/gzreader.cpp @@ -38,7 +38,7 @@ GZReader::GZReader(const char *fn) : gp(0) { #if !defined(_WIN32) fp = fopen(fn, "rb"); #else - fp = _wfopen(utf16(fn), L"rb"); + fp = _wfopen(utf16_t(fn), L"rb"); #endif if(!fp) return; diff --git a/src/snes/interface/interface.hpp b/src/snes/interface/interface.hpp index 8b846cbe..2656c209 100644 --- a/src/snes/interface/interface.hpp +++ b/src/snes/interface/interface.hpp @@ -1,17 +1,12 @@ -/***** - * SNES Interface class - * - * Interfaces SNES core with platform-specific functionality - * (video, audio, input, ...) - *****/ +//==================== +//SNES interface class +//==================== +//Interfaces SNES core with platform-specific functionality (video, audio, input, ...) class SNESInterface { public: void video_refresh(uint16_t *data, unsigned pitch, unsigned *line, unsigned width, unsigned height); - void audio_sample(uint16_t l_sample, uint16_t r_sample); - - function input_ready; void input_poll(); int16_t input_poll(unsigned deviceid, unsigned id); diff --git a/src/snes/snes.cpp b/src/snes/snes.cpp index d10441eb..52880043 100644 --- a/src/snes/snes.cpp +++ b/src/snes/snes.cpp @@ -80,20 +80,20 @@ void SNES::power() { ppu.power(); bus.power(); - if(expansion() == ExpansionBSX) bsxbase.power(); + if(expansion() == ExpansionBSX) bsxbase.power(); + if(cartridge.mode() == Cartridge::ModeBsx) bsxcart.power(); + if(cartridge.bsx_flash_loaded()) bsxflash.power(); - if(cartridge.info.bsxcart) bsxcart.power(); - if(cartridge.info.bsxflash) bsxflash.power(); - if(cartridge.info.srtc) srtc.power(); - if(cartridge.info.sdd1) sdd1.power(); - if(cartridge.info.spc7110) spc7110.power(); - if(cartridge.info.cx4) cx4.power(); - if(cartridge.info.dsp1) dsp1.power(); - if(cartridge.info.dsp2) dsp2.power(); - if(cartridge.info.dsp3) dsp3.power(); - if(cartridge.info.dsp4) dsp4.power(); - if(cartridge.info.obc1) obc1.power(); - if(cartridge.info.st010) st010.power(); + if(cartridge.has_srtc()) srtc.power(); + if(cartridge.has_sdd1()) sdd1.power(); + if(cartridge.has_spc7110()) spc7110.power(); + if(cartridge.has_cx4()) cx4.power(); + if(cartridge.has_dsp1()) dsp1.power(); + if(cartridge.has_dsp2()) dsp2.power(); + if(cartridge.has_dsp3()) dsp3.power(); + if(cartridge.has_dsp4()) dsp4.power(); + if(cartridge.has_obc1()) obc1.power(); + if(cartridge.has_st010()) st010.power(); for(unsigned i = 0x2100; i <= 0x213f; i++) memory::mmio.map(i, ppu); for(unsigned i = 0x2140; i <= 0x217f; i++) memory::mmio.map(i, cpu); @@ -102,20 +102,20 @@ void SNES::power() { for(unsigned i = 0x4200; i <= 0x421f; i++) memory::mmio.map(i, cpu); for(unsigned i = 0x4300; i <= 0x437f; i++) memory::mmio.map(i, cpu); - if(expansion() == ExpansionBSX) bsxbase.enable(); + if(expansion() == ExpansionBSX) bsxbase.enable(); + if(cartridge.mode() == Cartridge::ModeBsx) bsxcart.enable(); + if(cartridge.bsx_flash_loaded()) bsxflash.enable(); - if(cartridge.info.bsxcart) bsxcart.enable(); - if(cartridge.info.bsxflash) bsxflash.enable(); - if(cartridge.info.srtc) srtc.enable(); - if(cartridge.info.sdd1) sdd1.enable(); - if(cartridge.info.spc7110) spc7110.enable(); - if(cartridge.info.cx4) cx4.enable(); - if(cartridge.info.dsp1) dsp1.enable(); - if(cartridge.info.dsp2) dsp2.enable(); - if(cartridge.info.dsp3) dsp3.enable(); - if(cartridge.info.dsp4) dsp4.enable(); - if(cartridge.info.obc1) obc1.enable(); - if(cartridge.info.st010) st010.enable(); + if(cartridge.has_srtc()) srtc.enable(); + if(cartridge.has_sdd1()) sdd1.enable(); + if(cartridge.has_spc7110()) spc7110.enable(); + if(cartridge.has_cx4()) cx4.enable(); + if(cartridge.has_dsp1()) dsp1.enable(); + if(cartridge.has_dsp2()) dsp2.enable(); + if(cartridge.has_dsp3()) dsp3.enable(); + if(cartridge.has_dsp4()) dsp4.enable(); + if(cartridge.has_obc1()) obc1.enable(); + if(cartridge.has_st010()) st010.enable(); input.port_set_device(0, snes.config.controller_port1); input.port_set_device(1, snes.config.controller_port2); @@ -133,20 +133,20 @@ void SNES::reset() { ppu.reset(); bus.reset(); - if(expansion() == ExpansionBSX) bsxbase.reset(); + if(expansion() == ExpansionBSX) bsxbase.reset(); + if(cartridge.mode() == Cartridge::ModeBsx) bsxcart.reset(); + if(cartridge.bsx_flash_loaded()) bsxflash.reset(); - if(cartridge.info.bsxcart) bsxcart.reset(); - if(cartridge.info.bsxflash) bsxflash.reset(); - if(cartridge.info.srtc) srtc.reset(); - if(cartridge.info.sdd1) sdd1.reset(); - if(cartridge.info.spc7110) spc7110.reset(); - if(cartridge.info.cx4) cx4.reset(); - if(cartridge.info.dsp1) dsp1.reset(); - if(cartridge.info.dsp2) dsp2.reset(); - if(cartridge.info.dsp3) dsp3.reset(); - if(cartridge.info.dsp4) dsp4.reset(); - if(cartridge.info.obc1) obc1.reset(); - if(cartridge.info.st010) st010.reset(); + if(cartridge.has_srtc()) srtc.reset(); + if(cartridge.has_sdd1()) sdd1.reset(); + if(cartridge.has_spc7110()) spc7110.reset(); + if(cartridge.has_cx4()) cx4.reset(); + if(cartridge.has_dsp1()) dsp1.reset(); + if(cartridge.has_dsp2()) dsp2.reset(); + if(cartridge.has_dsp3()) dsp3.reset(); + if(cartridge.has_dsp4()) dsp4.reset(); + if(cartridge.has_obc1()) obc1.reset(); + if(cartridge.has_st010()) st010.reset(); input.port_set_device(0, snes.config.controller_port1); input.port_set_device(1, snes.config.controller_port2); @@ -184,16 +184,18 @@ SNES::SNES() : snes_region(NTSC), snes_expansion(ExpansionNone) { config.file.autodetect_type = false; config.file.bypass_patch_crc32 = false; - config.path.base = ""; - config.path.user = ""; - config.path.rom = ""; - config.path.save = ""; - config.path.patch = ""; - config.path.cheat = ""; - config.path.exportdata = ""; - config.path.bsx = ""; - config.path.st = ""; + config.path.base = ""; + config.path.user = ""; + config.path.current = ""; + config.path.rom = ""; + config.path.save = ""; + config.path.patch = ""; + config.path.cheat = ""; + config.path.data = ""; + config.path.bsx = ""; + config.path.st = ""; + config.cpu.version = 2; config.cpu.ntsc_clock_rate = 21477272; config.cpu.pal_clock_rate = 21281370; config.cpu.alu_mul_delay = 2; @@ -202,4 +204,7 @@ SNES::SNES() : snes_region(NTSC), snes_expansion(ExpansionNone) { config.smp.ntsc_clock_rate = 32041 * 768; config.smp.pal_clock_rate = 32041 * 768; + + config.ppu1.version = 1; + config.ppu2.version = 3; } diff --git a/src/snes/snes.hpp b/src/snes/snes.hpp index 8336046b..20fd7424 100644 --- a/src/snes/snes.hpp +++ b/src/snes/snes.hpp @@ -22,12 +22,15 @@ public: } file; struct Path { - string base, user; - string rom, save, patch, cheat, exportdata; + string base; //binary path + string user; //user profile path (bsnes.cfg, ...) + string current; //current working directory (path to currently loaded cartridge) + string rom, save, patch, cheat, data; string bsx, st; } path; struct CPU { + unsigned version; unsigned ntsc_clock_rate; unsigned pal_clock_rate; unsigned alu_mul_delay; @@ -39,6 +42,14 @@ public: unsigned ntsc_clock_rate; unsigned pal_clock_rate; } smp; + + struct PPU1 { + unsigned version; + } ppu1; + + struct PPU2 { + unsigned version; + } ppu2; } config; //system functions diff --git a/src/snes/tracer/tracer.cpp b/src/snes/tracer/tracer.cpp index 2526ed8f..dbfdb7ba 100644 --- a/src/snes/tracer/tracer.cpp +++ b/src/snes/tracer/tracer.cpp @@ -47,7 +47,7 @@ void Tracer::trace_smpop() { void Tracer::enable(bool en) { if(en == true && enabled() == false) { - fp = fopen(Cartridge::filepath("trace.log", snes.config.path.exportdata), "wb"); + fp = fopen(Cartridge::filepath("trace.log", snes.config.path.data), "wb"); } else if(en == false && enabled() == true) { fclose(fp); fp = 0; diff --git a/src/ui/resource.cpp b/src/ui/resource.cpp deleted file mode 100644 index d43743d4..00000000 --- a/src/ui/resource.cpp +++ /dev/null @@ -1,36 +0,0 @@ -namespace resource { - -#include "../data/icon48.h" -#include "../data/controller.h" - -static uint8_t *icon48; -static uint8_t *controller; - -//call once at program startup -void init() { - uint8_t *lzssdata; - uint8_t *rawdata; - unsigned length; - - base64::decode(lzssdata, length, enc_icon48); - lzss::decode(icon48, lzssdata, 48 * 48 * 4); - delete[] lzssdata; - - //controller data stored as 24-bit RGB888 - //expand to 32-bit ARGB8888 for direct use with hiro::Canvas - base64::decode(lzssdata, length, enc_controller); - lzss::decode(rawdata, lzssdata, 372 * 178 * 3); - delete[] lzssdata; - controller = new uint8_t[372 * 178 * 4]; - for(unsigned dp = 0, sp = 0, y = 0; y < 178; y++) { - for(unsigned x = 0; x < 372; x++) { - controller[dp++] = rawdata[sp++]; //blue - controller[dp++] = rawdata[sp++]; //green - controller[dp++] = rawdata[sp++]; //red - controller[dp++] = 255; //alpha - } - } - delete[] rawdata; -} - -} //namespace resource diff --git a/src/ui_hiro/Makefile b/src/ui_hiro/Makefile new file mode 100644 index 00000000..2bddc60c --- /dev/null +++ b/src/ui_hiro/Makefile @@ -0,0 +1,31 @@ +############################## +### platform configuration ### +############################## + +objects := main hiro $(if $(call streq,$(platform),win),resource) $(objects) + +ifeq ($(platform),x) + link += `pkg-config --libs gtk+-2.0` + link += $(call mklib,Xtst) + hiroflags = `pkg-config --cflags gtk+-2.0` +else ifeq ($(platform),win) + link += $(call mklib,comctl32) + link += $(call mklib,comdlg32) + hiroflags = +endif + +############# +### rules ### +############# + +obj/main.$(obj): $(ui)/main.cpp $(ui)/* $(ui)/base/* $(ui)/loader/* $(ui)/settings/* $(ui)/event/* +obj/hiro.$(obj): lib/hiro/hiro.cpp lib/hiro/* + $(call compile,$(hiroflags)) +obj/resource.$(obj): $(ui)/bsnes.rc; windres $(ui)/bsnes.rc obj/resource.$(obj) + +############### +### targets ### +############### + +ui_build:; +ui_clean:; diff --git a/src/ui/base/about.cpp b/src/ui_hiro/base/about.cpp similarity index 100% rename from src/ui/base/about.cpp rename to src/ui_hiro/base/about.cpp diff --git a/src/ui/base/about.hpp b/src/ui_hiro/base/about.hpp similarity index 100% rename from src/ui/base/about.hpp rename to src/ui_hiro/base/about.hpp diff --git a/src/ui/base/main.cpp b/src/ui_hiro/base/main.cpp similarity index 100% rename from src/ui/base/main.cpp rename to src/ui_hiro/base/main.cpp diff --git a/src/ui/base/main.hpp b/src/ui_hiro/base/main.hpp similarity index 100% rename from src/ui/base/main.hpp rename to src/ui_hiro/base/main.hpp diff --git a/src/ui/base/textview.cpp b/src/ui_hiro/base/textview.cpp similarity index 100% rename from src/ui/base/textview.cpp rename to src/ui_hiro/base/textview.cpp diff --git a/src/ui/base/textview.hpp b/src/ui_hiro/base/textview.hpp similarity index 100% rename from src/ui/base/textview.hpp rename to src/ui_hiro/base/textview.hpp diff --git a/src/ui/bsnes.rc b/src/ui_hiro/bsnes.rc similarity index 70% rename from src/ui/bsnes.rc rename to src/ui_hiro/bsnes.rc index c53b45aa..83058c63 100644 --- a/src/ui/bsnes.rc +++ b/src/ui_hiro/bsnes.rc @@ -1,4 +1,4 @@ #define IDI_APP_ICON 100 -1 24 "ui/bsnes.Manifest" +1 24 "data/bsnes.Manifest" IDI_APP_ICON ICON DISCARDABLE "data/bsnes.ico" diff --git a/src/ui/config.cpp b/src/ui_hiro/config.cpp similarity index 93% rename from src/ui/config.cpp rename to src/ui_hiro/config.cpp index 818944f1..ccd258bd 100644 --- a/src/ui/config.cpp +++ b/src/ui_hiro/config.cpp @@ -79,7 +79,6 @@ public: struct Misc { bool start_in_fullscreen_mode; unsigned window_opacity; - unsigned cheat_autosort; bool show_advanced_options; } misc; @@ -96,13 +95,13 @@ public: attach(snes.config.file.autodetect_type = false, "file.autodetect_type", "Detect filetype by header, rather than file extension"); attach(snes.config.file.bypass_patch_crc32 = false, "file.bypass_patch_crc32", "Apply UPS patches even when checksum match fails"); - attach(snes.config.path.rom = "", "path.rom"); - attach(snes.config.path.save = "", "path.save"); - attach(snes.config.path.patch = "", "path.patch"); - attach(snes.config.path.cheat = "", "path.cheat"); - attach(snes.config.path.exportdata = "", "path.exportdata"); - attach(snes.config.path.bsx = "", "path.bsx"); - attach(snes.config.path.st = "", "path.st"); + attach(snes.config.path.rom = "", "path.rom"); + attach(snes.config.path.save = "", "path.save"); + attach(snes.config.path.patch = "", "path.patch"); + attach(snes.config.path.cheat = "", "path.cheat"); + attach(snes.config.path.data = "", "path.data"); + attach(snes.config.path.bsx = "", "path.bsx"); + attach(snes.config.path.st = "", "path.st"); attach(snes.config.cpu.ntsc_clock_rate = 21477272, "cpu.ntsc_clock_rate"); attach(snes.config.cpu.pal_clock_rate = 21281370, "cpu.pal_clock_rate"); @@ -242,7 +241,6 @@ public: attach(misc.start_in_fullscreen_mode = false, "misc.start_in_fullscreen_mode"); attach(misc.window_opacity = 100, "misc.window_opacity", "Translucency percentage of helper windows (50%-100%)"); - attach(misc.cheat_autosort = false, "misc.cheat_autosort"); attach(misc.show_advanced_options = false, "misc.show_advanced_options", "Enable developer-oriented GUI options"); } diff --git a/src/ui/event/debugger.cpp b/src/ui_hiro/event/debugger.cpp similarity index 77% rename from src/ui/event/debugger.cpp rename to src/ui_hiro/event/debugger.cpp index ee76b868..e99e0ad1 100644 --- a/src/ui/event/debugger.cpp +++ b/src/ui_hiro/event/debugger.cpp @@ -1,23 +1,23 @@ void export_memory() { file fp; - fp.open(Cartridge::filepath("wram.bin", snes.config.path.exportdata), file::mode_write); + fp.open(Cartridge::filepath("wram.bin", snes.config.path.data), file::mode_write); for(unsigned i = 0; i < memory::wram.size(); i++) fp.write(memory::wram[i]); fp.close(); - fp.open(Cartridge::filepath("apuram.bin", snes.config.path.exportdata), file::mode_write); + fp.open(Cartridge::filepath("apuram.bin", snes.config.path.data), file::mode_write); for(unsigned i = 0; i < memory::apuram.size(); i++) fp.write(memory::apuram[i]); fp.close(); - fp.open(Cartridge::filepath("vram.bin", snes.config.path.exportdata), file::mode_write); + fp.open(Cartridge::filepath("vram.bin", snes.config.path.data), file::mode_write); for(unsigned i = 0; i < memory::vram.size(); i++) fp.write(memory::vram[i]); fp.close(); - fp.open(Cartridge::filepath("oam.bin", snes.config.path.exportdata), file::mode_write); + fp.open(Cartridge::filepath("oam.bin", snes.config.path.data), file::mode_write); for(unsigned i = 0; i < memory::oam.size(); i++) fp.write(memory::oam[i]); fp.close(); - fp.open(Cartridge::filepath("cgram.bin", snes.config.path.exportdata), file::mode_write); + fp.open(Cartridge::filepath("cgram.bin", snes.config.path.data), file::mode_write); for(unsigned i = 0; i < memory::cgram.size(); i++) fp.write(memory::cgram[i]); fp.close(); diff --git a/src/ui/event/debugger.hpp b/src/ui_hiro/event/debugger.hpp similarity index 100% rename from src/ui/event/debugger.hpp rename to src/ui_hiro/event/debugger.hpp diff --git a/src/ui/event/event.cpp b/src/ui_hiro/event/event.cpp similarity index 93% rename from src/ui/event/event.cpp rename to src/ui_hiro/event/event.cpp index 15279460..9cafbfc8 100644 --- a/src/ui/event/event.cpp +++ b/src/ui_hiro/event/event.cpp @@ -155,20 +155,20 @@ void modify_system_state(system_state_t state) { status.flush(); string t = translate["Loaded $."]; - replace(t, "$", cartridge.info.filename); + t.replace("$", cartridge.name()); status.enqueue(t); - if(cartridge.info.patched) status.enqueue(translate["UPS patch applied."]); + if(cartridge.patched()) status.enqueue(translate["UPS patch applied."]); //warn if unsupported hardware detected string message; message = translate["Warning: unsupported $ chip detected."]; - if(cartridge.info.superfx) { replace(message, "$", "SuperFX"); status.enqueue(message); } - if(cartridge.info.sa1) { replace(message, "$", "SA-1"); status.enqueue(message); } - if(cartridge.info.st011) { replace(message, "$", "ST011"); status.enqueue(message); } - if(cartridge.info.st018) { replace(message, "$", "ST018"); status.enqueue(message); } + if(cartridge.has_superfx()) { message.replace("$", "SuperFX"); status.enqueue(message); } + if(cartridge.has_sa1()) { message.replace("$", "SA-1"); status.enqueue(message); } + if(cartridge.has_st011()) { message.replace("$", "ST011"); status.enqueue(message); } + if(cartridge.has_st018()) { message.replace("$", "ST018"); status.enqueue(message); } message = translate["Warning: partially supported $ chip detected."]; - if(cartridge.info.dsp3) { replace(message, "$", "DSP-3"); status.enqueue(message); } + if(cartridge.has_dsp3()) { message.replace("$", "DSP-3"); status.enqueue(message); } } break; case UnloadCart: { @@ -180,7 +180,7 @@ void modify_system_state(system_state_t state) { status.flush(); string t = translate["Unloaded $."]; - replace(t, "$", cartridge.info.filename); + t.replace("$", cartridge.name()); status.enqueue(t); } break; @@ -368,16 +368,16 @@ bool load_cart(char *fn) { lstring dir; strcpy(fn, ""); - strcpy(dir[0], snes.config.path.rom); - replace(dir[0], "\\", "/"); - if(strlen(dir[0]) && !strend(dir[0], "/")) strcat(dir[0], "/"); + dir[0] = snes.config.path.rom; + dir[0].replace("\\", "/"); + if(dir[0].length() && !strend(dir[0], "/")) dir[0].append("/"); //append base path if rom path is relative if(strbegin(dir[0], "./")) { ltrim(dir[0], "./"); - strcpy(dir[1], dir[0]); - strcpy(dir[0], snes.config.path.base); - strcat(dir[0], dir[1]); + dir[1].assign(dir[0]); + dir[0].assign(snes.config.path.base); + dir[0].append(dir[1]); } return hiro().file_open(0, fn, @@ -404,15 +404,12 @@ void load_cart() { } void load_image(const char *filename) { - Cartridge::cartinfo_t cartinfo; - if(!cartridge.inspect_image(cartinfo, filename)) return; - - switch(cartinfo.type) { + switch(cartridge.detect_image_type(filename)) { case Cartridge::TypeNormal: { load_cart_normal(filename); } break; - case Cartridge::TypeBSC: { + case Cartridge::TypeBsxSlotted: { window_bsxloader.mode = BSXLoaderWindow::ModeBSC; window_bsxloader.set_text(translate["Load BS-X Slotted Cartridge"]); window_bsxloader.tbase.set_text(filename); @@ -421,7 +418,7 @@ void load_image(const char *filename) { window_bsxloader.focus(); } break; - case Cartridge::TypeBSXBIOS: { + case Cartridge::TypeBsxBios: { window_bsxloader.mode = BSXLoaderWindow::ModeBSX; window_bsxloader.set_text(translate["Load BS-X Cartridge"]); window_bsxloader.tbase.set_text(filename); @@ -430,7 +427,7 @@ void load_image(const char *filename) { window_bsxloader.focus(); } break; - case Cartridge::TypeBSX: { + case Cartridge::TypeBsx: { window_bsxloader.mode = BSXLoaderWindow::ModeBSX; window_bsxloader.set_text(translate["Load BS-X Cartridge"]); window_bsxloader.tbase.set_text(snes.config.path.bsx); @@ -439,7 +436,7 @@ void load_image(const char *filename) { window_bsxloader.focus(); } break; - case Cartridge::TypeSufamiTurboBIOS: { + case Cartridge::TypeSufamiTurboBios: { window_stloader.tbase.set_text(filename); window_stloader.tslotA.set_text(""); window_stloader.tslotB.set_text(""); @@ -461,7 +458,7 @@ void load_cart_normal(const char *base) { if(!base || !*base) return; unload_cart(); - cartridge.load_cart_normal(base); + cartridge.load_normal(base); if(cartridge.loaded() == false) return; modify_system_state(LoadCart); } @@ -470,7 +467,7 @@ void load_cart_bsc(const char *base, const char *slot) { if(!base || !*base) return; unload_cart(); - cartridge.load_cart_bsc(base, slot); + cartridge.load_bsx_slotted(base, slot); if(cartridge.loaded() == false) return; modify_system_state(LoadCart); } @@ -479,7 +476,7 @@ void load_cart_bsx(const char *base, const char *slot) { if(!base || !*base) return; unload_cart(); - cartridge.load_cart_bsx(base, slot); + cartridge.load_bsx(base, slot); if(cartridge.loaded() == false) return; modify_system_state(LoadCart); } @@ -488,7 +485,7 @@ void load_cart_st(const char *base, const char *slotA, const char *slotB) { if(!base || !*base) return; unload_cart(); - cartridge.load_cart_st(base, slotA, slotB); + cartridge.load_sufami_turbo(base, slotA, slotB); if(cartridge.loaded() == false) return; modify_system_state(LoadCart); } diff --git a/src/ui/event/event.hpp b/src/ui_hiro/event/event.hpp similarity index 100% rename from src/ui/event/event.hpp rename to src/ui_hiro/event/event.hpp diff --git a/src/ui/inputdevices.cpp b/src/ui_hiro/inputdevices.cpp similarity index 100% rename from src/ui/inputdevices.cpp rename to src/ui_hiro/inputdevices.cpp diff --git a/src/ui/inputmanager.cpp b/src/ui_hiro/inputmanager.cpp similarity index 100% rename from src/ui/inputmanager.cpp rename to src/ui_hiro/inputmanager.cpp diff --git a/src/ui/inputui.cpp b/src/ui_hiro/inputui.cpp similarity index 100% rename from src/ui/inputui.cpp rename to src/ui_hiro/inputui.cpp diff --git a/src/ui/interface.cpp b/src/ui_hiro/interface.cpp similarity index 94% rename from src/ui/interface.cpp rename to src/ui_hiro/interface.cpp index 16e05f68..2806494a 100644 --- a/src/ui/interface.cpp +++ b/src/ui_hiro/interface.cpp @@ -51,7 +51,7 @@ void SNESInterface::audio_sample(uint16 l_sample, uint16 r_sample) { //input void SNESInterface::input_poll() { - if(input_ready && input_ready() == false) { + if(window_main.input_ready() == false) { input_manager.clear(); } else { input_manager.poll(); diff --git a/src/license.hpp b/src/ui_hiro/license.hpp similarity index 100% rename from src/license.hpp rename to src/ui_hiro/license.hpp diff --git a/src/ui/loader/bsxloader.cpp b/src/ui_hiro/loader/bsxloader.cpp similarity index 100% rename from src/ui/loader/bsxloader.cpp rename to src/ui_hiro/loader/bsxloader.cpp diff --git a/src/ui/loader/bsxloader.hpp b/src/ui_hiro/loader/bsxloader.hpp similarity index 100% rename from src/ui/loader/bsxloader.hpp rename to src/ui_hiro/loader/bsxloader.hpp diff --git a/src/ui/loader/stloader.cpp b/src/ui_hiro/loader/stloader.cpp similarity index 100% rename from src/ui/loader/stloader.cpp rename to src/ui_hiro/loader/stloader.cpp diff --git a/src/ui/loader/stloader.hpp b/src/ui_hiro/loader/stloader.hpp similarity index 100% rename from src/ui/loader/stloader.hpp rename to src/ui_hiro/loader/stloader.hpp diff --git a/src/ui/main.cpp b/src/ui_hiro/main.cpp similarity index 100% rename from src/ui/main.cpp rename to src/ui_hiro/main.cpp diff --git a/src/ui/main.hpp b/src/ui_hiro/main.hpp similarity index 100% rename from src/ui/main.hpp rename to src/ui_hiro/main.hpp diff --git a/src/readme.hpp b/src/ui_hiro/readme.hpp similarity index 100% rename from src/readme.hpp rename to src/ui_hiro/readme.hpp diff --git a/src/ui_hiro/resource.cpp b/src/ui_hiro/resource.cpp new file mode 100644 index 00000000..512215a4 --- /dev/null +++ b/src/ui_hiro/resource.cpp @@ -0,0 +1,12 @@ +namespace resource { + +static uint8_t *icon48; +static uint8_t *controller; + +void init() { + //note: resources were removed, as hiro port is deprecated + icon48 = new(zeromemory) uint8_t[48 * 48 * 4]; + controller = new(zeromemory) uint8_t[372 * 178 * 4]; +} + +} //namespace resource diff --git a/src/ui/settings/advanced.cpp b/src/ui_hiro/settings/advanced.cpp similarity index 95% rename from src/ui/settings/advanced.cpp rename to src/ui_hiro/settings/advanced.cpp index c4ea45e5..3c4d56d9 100644 --- a/src/ui/settings/advanced.cpp +++ b/src/ui_hiro/settings/advanced.cpp @@ -69,7 +69,6 @@ void AdvancedWindow::load() { if(strbegin(name, "input.justifier")) continue; if(strbegin(name, "input.gui")) continue; if(strbegin(name, "input.debugger")) continue; - if(name == "misc.cheat_autosort") continue; list.add_item(string() << name << "\t" diff --git a/src/ui/settings/advanced.hpp b/src/ui_hiro/settings/advanced.hpp similarity index 100% rename from src/ui/settings/advanced.hpp rename to src/ui_hiro/settings/advanced.hpp diff --git a/src/ui/settings/audiosettings.cpp b/src/ui_hiro/settings/audiosettings.cpp similarity index 100% rename from src/ui/settings/audiosettings.cpp rename to src/ui_hiro/settings/audiosettings.cpp diff --git a/src/ui/settings/audiosettings.hpp b/src/ui_hiro/settings/audiosettings.hpp similarity index 100% rename from src/ui/settings/audiosettings.hpp rename to src/ui_hiro/settings/audiosettings.hpp diff --git a/src/ui/settings/cheateditor.cpp b/src/ui_hiro/settings/cheateditor.cpp similarity index 84% rename from src/ui/settings/cheateditor.cpp rename to src/ui_hiro/settings/cheateditor.cpp index 37b1c029..c7c50591 100644 --- a/src/ui/settings/cheateditor.cpp +++ b/src/ui_hiro/settings/cheateditor.cpp @@ -5,25 +5,20 @@ void CheatEditorWindow::setup() { create(0, 451, 370); - list.create(Listbox::Header | Listbox::VerticalScrollAlways, 451, 317, + list.create(Listbox::Header | Listbox::VerticalScrollAlways, 451, 340, string() << translate["Status"] << "\t" << translate["Code"] << "\t" << translate["Description"]); - autosort.create (0, 451, 18, translate["Keep cheat code list sorted by description"]); add_code.create (0, 147, 25, translate["Add Code"]); edit_code.create (0, 147, 25, translate["Edit Code"]); delete_code.create(0, 147, 25, translate["Delete Code"]); unsigned y = 0; - attach(list, 0, y); y += 317 + 5; - attach(autosort, 0, y); y += 18 + 5; + attach(list, 0, y); y += 340 + 5; attach(add_code, 0, y); attach(edit_code, 152, y); attach(delete_code, 304, y); y += 25 + 5; - autosort.check(config.misc.cheat_autosort); - list.on_activate = bind(&CheatEditorWindow::toggle_code_state, this); list.on_change = bind(&CheatEditorWindow::list_change, this); - autosort.on_tick = bind(&CheatEditorWindow::autosort_tick, this); add_code.on_tick = bind(&CheatEditorWindow::add_tick, this); edit_code.on_tick = bind(&CheatEditorWindow::edit_tick, this); delete_code.on_tick = bind(&CheatEditorWindow::delete_tick, this); @@ -46,20 +41,18 @@ string CheatEditorWindow::read_code(unsigned index) { s << (item.enabled ? translate["Enabled"] : translate["Disabled"]) << "\t"; lstring line; - split(line, "+", item.code); - if(count(line) > 1) line[0] << "+..."; + line.split("+", item.code); + if(line.size() > 1) line[0] << "+..."; s << line[0] << "\t"; - split(line, "\n", item.desc); - if(count(line) > 1) line[0] << " ..."; + line.split("\n", item.desc); + if(line.size() > 1) line[0] << " ..."; s << line[0]; return s; } void CheatEditorWindow::refresh() { - if(config.misc.cheat_autosort == true) cheat.sort(); - list.reset(); for(unsigned i = 0; i < cheat.count(); i++) list.add_item(read_code(i)); list.autosize_columns(); @@ -67,12 +60,6 @@ void CheatEditorWindow::refresh() { sync_ui(); } -uintptr_t CheatEditorWindow::autosort_tick(event_t) { - config.misc.cheat_autosort = autosort.checked(); - if(config.misc.cheat_autosort == true) refresh(); - return true; -} - uintptr_t CheatEditorWindow::toggle_code_state(event_t) { int index = list.get_selection(); if(index >= 0 && index < cheat.count()) { @@ -131,7 +118,7 @@ uintptr_t CheatCodeEditorWindow::validate(event_t) { string s_code = s_codes; strtr(s_code, " ,;&|\t\n", "+++++++"); - while(strpos(s_code, "++") >= 0) replace(s_code, "++", "+"); + while(strpos(s_code, "++") >= 0) s_code.replace("++", "+"); trim(s_code, "+"); Cheat::cheat_t item; @@ -149,7 +136,7 @@ uintptr_t CheatCodeEditorWindow::ok_tick(event_t) { string s_code = s_codes; strtr(s_code, " ,;&|\t\n", "+++++++"); - while(strpos(s_code, "++") >= 0) replace(s_code, "++", "+"); + while(strpos(s_code, "++") >= 0) s_code.replace("++", "+"); trim(s_code, "+"); if(active_mode == false) { @@ -194,7 +181,7 @@ void CheatCodeEditorWindow::show(bool editmode, unsigned codenumber) { set_text(translate["Modify existing cheat code"]); description.set_text(item.desc); string s = item.code; - replace(s, "+", " + "); + s.replace("+", " + "); codes.set_text(s); enabled.check(item.enabled); } diff --git a/src/ui/settings/cheateditor.hpp b/src/ui_hiro/settings/cheateditor.hpp similarity index 90% rename from src/ui/settings/cheateditor.hpp rename to src/ui_hiro/settings/cheateditor.hpp index 81c5882c..c94e33c5 100644 --- a/src/ui/settings/cheateditor.hpp +++ b/src/ui_hiro/settings/cheateditor.hpp @@ -2,7 +2,6 @@ class CheatEditorWindow : public Window { public: Listbox list; - Checkbox autosort; Button add_code; Button edit_code; Button delete_code; @@ -15,7 +14,6 @@ public: uintptr_t toggle_code_state(event_t); uintptr_t list_change(event_t); - uintptr_t autosort_tick(event_t); uintptr_t add_tick(event_t); uintptr_t edit_tick(event_t); uintptr_t delete_tick(event_t); diff --git a/src/ui/settings/driverselect.cpp b/src/ui_hiro/settings/driverselect.cpp similarity index 89% rename from src/ui/settings/driverselect.cpp rename to src/ui_hiro/settings/driverselect.cpp index 54611141..5f8aeb31 100644 --- a/src/ui/settings/driverselect.cpp +++ b/src/ui_hiro/settings/driverselect.cpp @@ -1,20 +1,20 @@ uintptr_t DriverSelectWindow::video_change(event_t) { lstring part; - split(part, ";", video.driver_list()); + part.split(";", video.driver_list()); config.system.video = part[cvideo.get_selection()]; return true; } uintptr_t DriverSelectWindow::audio_change(event_t) { lstring part; - split(part, ";", audio.driver_list()); + part.split(";", audio.driver_list()); config.system.audio = part[caudio.get_selection()]; return true; } uintptr_t DriverSelectWindow::input_change(event_t) { lstring part; - split(part, ";", input.driver_list()); + part.split(";", input.driver_list()); config.system.input = part[cinput.get_selection()]; return true; } @@ -33,8 +33,8 @@ void DriverSelectWindow::setup() { lvideo.create(0, 147, 18, translate["Video driver:"]); cvideo.create(0, 147, 25); - split(part, ";", video.driver_list()); - for(unsigned i = 0; i < count(part); i++) { + part.split(";", video.driver_list()); + for(unsigned i = 0; i < part.size(); i++) { cvideo.add_item(translate[string() << "{{videodriver}}" << part[i]]); if(part[i] == config.system.video) cvideo.set_selection(i); } @@ -42,8 +42,8 @@ void DriverSelectWindow::setup() { laudio.create(0, 147, 18, translate["Audio driver:"]); caudio.create(0, 147, 25); - split(part, ";", audio.driver_list()); - for(unsigned i = 0; i < count(part); i++) { + part.split(";", audio.driver_list()); + for(unsigned i = 0; i < part.size(); i++) { caudio.add_item(translate[string() << "{{audiodriver}}" << part[i]]); if(part[i] == config.system.audio) caudio.set_selection(i); } @@ -51,8 +51,8 @@ void DriverSelectWindow::setup() { linput.create(0, 147, 18, translate["Input driver:"]); cinput.create(0, 147, 25); - split(part, ";", input.driver_list()); - for(unsigned i = 0; i < count(part); i++) { + part.split(";", input.driver_list()); + for(unsigned i = 0; i < part.size(); i++) { cinput.add_item(translate[string() << "{{inputdriver}}" << part[i]]); if(part[i] == config.system.input) cinput.set_selection(i); } @@ -88,14 +88,14 @@ void DriverSelectWindow::setup() { bool crashed = config.system.invoke_crash_handler; string t = translate["Capabilities of active video driver ($):"]; - replace(t, "$", config.system.video); + t.replace("$", config.system.video); video_caps.set_text(t); video_sync.check(video.cap(Video::Synchronize)); video_filter.check(video.cap(Video::Filter)); t = translate["Capabilities of active audio driver ($):"]; - replace(t, "$", config.system.audio); + t.replace("$", config.system.audio); audio_caps.set_text(t); audio_sync.check(audio.cap(Audio::Synchronize)); @@ -103,7 +103,7 @@ void DriverSelectWindow::setup() { audio_latency.check(audio.cap(Audio::Latency)); t = translate["Capabilities of active input driver ($):"]; - replace(t, "$", config.system.input); + t.replace("$", config.system.input); input_caps.set_text(t); input_keyboard.check(input.cap(Input::KeyboardSupport)); diff --git a/src/ui/settings/driverselect.hpp b/src/ui_hiro/settings/driverselect.hpp similarity index 100% rename from src/ui/settings/driverselect.hpp rename to src/ui_hiro/settings/driverselect.hpp diff --git a/src/ui/settings/inputconfig.cpp b/src/ui_hiro/settings/inputconfig.cpp similarity index 96% rename from src/ui/settings/inputconfig.cpp rename to src/ui_hiro/settings/inputconfig.cpp index 3fd0cf5a..cc38e4a3 100644 --- a/src/ui/settings/inputconfig.cpp +++ b/src/ui_hiro/settings/inputconfig.cpp @@ -188,7 +188,7 @@ uintptr_t InputConfigWindow::set_tick(event_t) { message = translate["Move mouse or analog joypad axis to assign to $ ..."]; } - replace(message, "$", translate[group->list[pos]->name]); + message.replace("$", translate[group->list[pos]->name]); window_input_capture.label.set_text(message); bool show_controller_graphic = false; diff --git a/src/ui/settings/inputconfig.hpp b/src/ui_hiro/settings/inputconfig.hpp similarity index 100% rename from src/ui/settings/inputconfig.hpp rename to src/ui_hiro/settings/inputconfig.hpp diff --git a/src/ui/settings/pathsettings.cpp b/src/ui_hiro/settings/pathsettings.cpp similarity index 97% rename from src/ui/settings/pathsettings.cpp rename to src/ui_hiro/settings/pathsettings.cpp index 40908289..c0e19d96 100644 --- a/src/ui/settings/pathsettings.cpp +++ b/src/ui_hiro/settings/pathsettings.cpp @@ -81,14 +81,14 @@ uintptr_t PathSettingsWindow::defaultpath_cheat(event_t) { uintptr_t PathSettingsWindow::selectpath_export(event_t) { char t[PATH_MAX]; if(hiro().folder_select(&window_settings, t) == true) { - snes.config.path.exportdata = t; + snes.config.path.data = t; update_paths(); } return true; } uintptr_t PathSettingsWindow::defaultpath_export(event_t) { - snes.config.path.exportdata = ""; + snes.config.path.data = ""; update_paths(); return true; } @@ -110,7 +110,7 @@ void PathSettingsWindow::update_paths() { if(snes.config.path.cheat != "") cheatpath.set_text(snes.config.path.cheat); else cheatpath.set_text(translate[""]); - if(snes.config.path.exportdata != "") exportpath.set_text(snes.config.path.exportdata); + if(snes.config.path.data != "") exportpath.set_text(snes.config.path.data); else exportpath.set_text(translate[""]); } diff --git a/src/ui/settings/pathsettings.hpp b/src/ui_hiro/settings/pathsettings.hpp similarity index 100% rename from src/ui/settings/pathsettings.hpp rename to src/ui_hiro/settings/pathsettings.hpp diff --git a/src/ui/settings/settings.cpp b/src/ui_hiro/settings/settings.cpp similarity index 100% rename from src/ui/settings/settings.cpp rename to src/ui_hiro/settings/settings.cpp diff --git a/src/ui/settings/settings.hpp b/src/ui_hiro/settings/settings.hpp similarity index 100% rename from src/ui/settings/settings.hpp rename to src/ui_hiro/settings/settings.hpp diff --git a/src/ui/settings/videosettings.cpp b/src/ui_hiro/settings/videosettings.cpp similarity index 100% rename from src/ui/settings/videosettings.cpp rename to src/ui_hiro/settings/videosettings.cpp diff --git a/src/ui/settings/videosettings.hpp b/src/ui_hiro/settings/videosettings.hpp similarity index 100% rename from src/ui/settings/videosettings.hpp rename to src/ui_hiro/settings/videosettings.hpp diff --git a/src/ui/status.cpp b/src/ui_hiro/status.cpp similarity index 100% rename from src/ui/status.cpp rename to src/ui_hiro/status.cpp diff --git a/src/ui/status.hpp b/src/ui_hiro/status.hpp similarity index 100% rename from src/ui/status.hpp rename to src/ui_hiro/status.hpp diff --git a/src/ui/ui.cpp b/src/ui_hiro/ui.cpp similarity index 96% rename from src/ui/ui.cpp rename to src/ui_hiro/ui.cpp index 100e89af..9fe824fd 100644 --- a/src/ui/ui.cpp +++ b/src/ui_hiro/ui.cpp @@ -97,8 +97,7 @@ void ui_init() { event::update_video_settings(); //call second time to update video class settings - //UI setup complete, hook input callbacks - snesinterface.input_ready = bind(&MainWindow::input_ready, &window_main); + //UI setup complete, hook input callback input_manager.on_input = bind(&event::input_event); } diff --git a/src/ui/ui.hpp b/src/ui_hiro/ui.hpp similarity index 88% rename from src/ui/ui.hpp rename to src/ui_hiro/ui.hpp index f68afdc9..f7c38801 100644 --- a/src/ui/ui.hpp +++ b/src/ui_hiro/ui.hpp @@ -4,8 +4,8 @@ nall::dictionary translate; -#include <../readme.hpp> -#include <../license.hpp> +#include "readme.hpp" +#include "license.hpp" #include "base/main.hpp" #include "base/textview.hpp" diff --git a/src/ui_qt/Makefile b/src/ui_qt/Makefile new file mode 100644 index 00000000..28c94b7f --- /dev/null +++ b/src/ui_qt/Makefile @@ -0,0 +1,75 @@ +############################## +### platform configuration ### +############################## + +objects := main $(if $(call streq,$(platform),win),resource) $(objects) + +moc = moc +rcc = rcc + +ifeq ($(platform),x) # X11 + link += `pkg-config --libs QtCore QtGui` + qtflags = `pkg-config --cflags QtCore QtGui` +else ifeq ($(platform),win) # Windows + qtdir = c:/qt450 + link += -L$(qtdir)/lib + link += $(call mklib,mingw32) + link += $(call mklib,qtmain) + link += $(call mklib,QtGui) + link += $(call mklib,comdlg32) + link += $(call mklib,oleaut32) + link += $(call mklib,imm32) + link += $(call mklib,winmm) + link += $(call mklib,winspool) + link += $(call mklib,msimg32) + link += $(call mklib,QtCore) + link += $(call mklib,ole32) + link += $(call mklib,advapi32) + link += $(call mklib,ws2_32) + link += $(call mklib,uuid) + qtflags = $(call mkinc,$(qtdir)/include) + qtflags += $(call mkinc,$(qtdir)/include/QtCore) + qtflags += $(call mkinc,$(qtdir)/include/QtGui) +endif + +moc_objects = \ + $(ui)/base/main.moc \ + $(ui)/base/loader.moc \ + $(ui)/base/htmlviewer.moc \ + $(ui)/base/about.moc \ + $(ui)/settings/settings.moc \ + $(ui)/settings/video.moc \ + $(ui)/settings/audio.moc \ + $(ui)/settings/input.moc \ + $(ui)/settings/paths.moc \ + $(ui)/settings/cheateditor.moc \ + $(ui)/settings/advanced.moc \ + $(ui)/settings/utility/inputcapture.moc \ + $(ui)/settings/utility/codeeditor.moc \ + +############# +### rules ### +############# + +%.moc: $<; $(moc) $(patsubst %.moc,%.hpp,$@) -o $@ +$(foreach object,$(moc_objects),$(eval $(object): $(patsubst %.moc,%.hpp,$(object)))) + +obj/main.$(obj): $(ui)/main.cpp \ +$(ui)/* $(ui)/input/* $(ui)/utility/* $(ui)/base/* $(ui)/settings/* $(ui)/settings/utility/* \ +data/* + $(call compile,$(qtflags)) + +$(ui)/resource/resource.rcc: $(ui)/resource/resource.qrc data/* + $(rcc) $(ui)/resource/resource.qrc -o $(ui)/resource/resource.rcc + +obj/resource.$(obj): $(ui)/resource/resource.rc + windres $(ui)/resource/resource.rc obj/resource.$(obj) + +############### +### targets ### +############### + +ui_build: $(ui)/resource/resource.rcc $(moc_objects); +ui_clean: + -$(foreach object,$(moc_objects),@$(call delete,$(object))) + -@$(call delete,$(ui)/resource/resource.rcc) diff --git a/src/ui_qt/base/about.cpp b/src/ui_qt/base/about.cpp new file mode 100644 index 00000000..79f1cd8e --- /dev/null +++ b/src/ui_qt/base/about.cpp @@ -0,0 +1,49 @@ +void AboutWindow::setup() { + window = new QWidget; + window->setObjectName("about-window"); + window->setWindowTitle("About bsnes ..."); + + layout = new QVBoxLayout; + layout->setMargin(Style::WindowMargin); + layout->setSpacing(0); { + logo = new Logo; + logo->setFixedSize(600, 106); + layout->addWidget(logo); + layout->addSpacing(Style::WidgetSpacing); + + info = new QLabel( + "" + "" + "" + "" + "
Version: " BSNES_VERSION "
Author: byuu
Homepage: http://byuu.org/
" + ); + layout->addWidget(info); + + spacer = new QWidget; + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + layout->addWidget(spacer); + } + + window->setLayout(layout); +} + +void AboutWindow::show() { + window->show(); + + static bool firstShow = true; + if(firstShow == true) { + firstShow = false; + utility.centerWindow(window); + } + + application.processEvents(); + window->activateWindow(); + window->raise(); +} + +void AboutWindow::Logo::paintEvent(QPaintEvent*) { + QPainter painter(this); + QPixmap pixmap(":/logo.png"); + painter.drawPixmap(0, 0, pixmap); +} diff --git a/src/ui_qt/base/about.hpp b/src/ui_qt/base/about.hpp new file mode 100644 index 00000000..c71f39fd --- /dev/null +++ b/src/ui_qt/base/about.hpp @@ -0,0 +1,17 @@ +class AboutWindow : public QObject { + Q_OBJECT + +public: + QWidget *window; + QVBoxLayout *layout; + struct Logo : public QWidget { + void paintEvent(QPaintEvent*); + } *logo; + QLabel *info; + QWidget *spacer; + + void setup(); + void show(); + +public slots: +} *winAbout; diff --git a/src/ui_qt/base/htmlviewer.cpp b/src/ui_qt/base/htmlviewer.cpp new file mode 100644 index 00000000..8fa87f3e --- /dev/null +++ b/src/ui_qt/base/htmlviewer.cpp @@ -0,0 +1,30 @@ +void HtmlViewerWindow::setup() { + window = new QWidget; + window->setObjectName("html-window"); + + layout = new QVBoxLayout; + layout->setMargin(Style::WindowMargin); + layout->setSpacing(0); { + document = new QTextBrowser; + layout->addWidget(document); + } + + window->setLayout(layout); + window->resize(560, 480); +} + +void HtmlViewerWindow::show(const char *title, const char *htmlData) { + document->setHtml(utf8() << htmlData); + window->setWindowTitle(title); + window->show(); + + static bool firstShow = true; + if(firstShow == true) { + firstShow = false; + utility.centerWindow(window); + } + + application.processEvents(); + window->activateWindow(); + window->raise(); +} diff --git a/src/ui_qt/base/htmlviewer.hpp b/src/ui_qt/base/htmlviewer.hpp new file mode 100644 index 00000000..b76fdfca --- /dev/null +++ b/src/ui_qt/base/htmlviewer.hpp @@ -0,0 +1,13 @@ +class HtmlViewerWindow : public QObject { + Q_OBJECT + +public: + QWidget *window; + QVBoxLayout *layout; + QTextBrowser *document; + + void setup(); + void show(const char *title, const char *htmlData); + +public slots: +} *winHtmlViewer; diff --git a/src/ui_qt/base/loader.cpp b/src/ui_qt/base/loader.cpp new file mode 100644 index 00000000..059ddd5f --- /dev/null +++ b/src/ui_qt/base/loader.cpp @@ -0,0 +1,201 @@ +void LoaderWindow::setup() { + window = new QWidget; + window->setObjectName("loader-window"); + window->setMinimumWidth(520); + + layout = new QVBoxLayout; + layout->setMargin(Style::WindowMargin); + layout->setSpacing(0); + + grid = new QGridLayout; { + baseLabel = new QLabel("Base cartridge:"); + grid->addWidget(baseLabel, 0, 0); + + baseFile = new QLineEdit; + baseFile->setReadOnly(true); + grid->addWidget(baseFile, 0, 1); + + baseBrowse = new QPushButton("Browse ..."); + grid->addWidget(baseBrowse, 0, 2); + + baseClear = new QPushButton("Clear"); + grid->addWidget(baseClear, 0, 3); + + slot1Label = new QLabel("Slot A cartridge:"); + grid->addWidget(slot1Label, 1, 0); + + slot1File = new QLineEdit; + slot1File->setReadOnly(true); + grid->addWidget(slot1File, 1, 1); + + slot1Browse = new QPushButton("Browse ..."); + grid->addWidget(slot1Browse, 1, 2); + + slot1Clear = new QPushButton("Clear"); + grid->addWidget(slot1Clear, 1, 3); + + slot2Label = new QLabel("Slot B cartridge:"); + grid->addWidget(slot2Label, 2, 0); + + slot2File = new QLineEdit; + slot2File->setReadOnly(true); + grid->addWidget(slot2File, 2, 1); + + slot2Browse = new QPushButton("Browse ..."); + grid->addWidget(slot2Browse, 2, 2); + + slot2Clear = new QPushButton("Clear"); + grid->addWidget(slot2Clear, 2, 3); + } + grid->setSpacing(Style::WidgetSpacing); + layout->addLayout(grid); + layout->addSpacing(Style::WidgetSpacing); + + controls = new QHBoxLayout; { + load = new QPushButton("Load"); + controls->addWidget(load); + + cancel = new QPushButton("Cancel"); + controls->addWidget(cancel); + } + controls->setSpacing(Style::WidgetSpacing); + layout->addLayout(controls); + + spacer = new QWidget; + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + layout->addWidget(spacer); + + window->setLayout(layout); + connect(baseBrowse, SIGNAL(released()), this, SLOT(selectBaseCartridge())); + connect(baseClear, SIGNAL(released()), this, SLOT(clearBaseCartridge())); + connect(slot1Browse, SIGNAL(released()), this, SLOT(selectSlot1Cartridge())); + connect(slot1Clear, SIGNAL(released()), this, SLOT(clearSlot1Cartridge())); + connect(slot2Browse, SIGNAL(released()), this, SLOT(selectSlot2Cartridge())); + connect(slot2Clear, SIGNAL(released()), this, SLOT(clearSlot2Cartridge())); + connect(load, SIGNAL(released()), this, SLOT(onLoad())); + connect(cancel, SIGNAL(released()), this, SLOT(onCancel())); +} + +void LoaderWindow::syncUi() { + //only allow load when a base file is specified ... + load->setEnabled(baseFile->text().length() > 0); +} + +void LoaderWindow::loadBsxSlottedCartridge(const char *filebase, const char *fileSlot1) { + window->hide(); + baseLabel->show(), baseFile->show(), baseBrowse->show(), baseClear->show(); + slot1Label->show(), slot1File->show(), slot1Browse->show(), slot1Clear->show(); + slot2Label->hide(), slot2File->hide(), slot2Browse->hide(), slot2Clear->hide(); + + slot1Label->setText("Slot cartridge:"); + + baseFile->setText(filebase); + slot1File->setText(fileSlot1); + + syncUi(); + mode = ModeBsxSlotted; + showWindow("Load BS-X Slotted Cartridge"); +} + +void LoaderWindow::loadBsxCartridge(const char *fileBase, const char *fileSlot1) { + window->hide(); + baseLabel->show(), baseFile->show(), baseBrowse->show(), baseClear->show(); + slot1Label->show(), slot1File->show(), slot1Browse->show(), slot1Clear->show(); + slot2Label->hide(), slot2File->hide(), slot2Browse->hide(), slot2Clear->hide(); + + slot1Label->setText("Slot cartridge:"); + + baseFile->setText(fileBase); + slot1File->setText(fileSlot1); + + syncUi(); + mode = ModeBsx; + showWindow("Load BS-X Cartridge"); +} + +void LoaderWindow::loadSufamiTurboCartridge(const char *fileBase, const char *fileSlot1, const char *fileSlot2) { + window->hide(); + baseLabel->show(), baseFile->show(), baseBrowse->show(), baseClear->show(); + slot1Label->show(), slot1File->show(), slot1Browse->show(), slot1Clear->show(); + slot2Label->show(), slot2File->show(), slot2Browse->show(), slot2Clear->show(); + + slot1Label->setText("Slot A cartridge:"); + slot2Label->setText("Slot B cartridge:"); + + baseFile->setText(fileBase); + slot1File->setText(fileSlot1); + slot2File->setText(fileSlot2); + + syncUi(); + mode = ModeSufamiTurbo; + showWindow("Load Sufami Turbo Cartridge"); +} + +void LoaderWindow::showWindow(const char *title) { + window->setWindowTitle(title); + window->resize(0, 0); //shrink window as much as possible (visible widgets will forcefully increase size) + window->show(); + window->resize(0, 0); + load->setFocus(); + + static bool firstShow = true; + if(firstShow) { + //center window, but only on first show (to save user positioning for later shows) + firstShow = false; + utility.centerWindow(window); + } + + application.processEvents(); + window->activateWindow(); + window->raise(); +} + +void LoaderWindow::selectBaseCartridge() { + string filename = utility.selectCartridge(); + if(filename.length() > 0) baseFile->setText(utf8() << filename); + syncUi(); +} + +void LoaderWindow::clearBaseCartridge() { + baseFile->setText(""); + syncUi(); +} + +void LoaderWindow::selectSlot1Cartridge() { + string filename = utility.selectCartridge(); + if(filename.length() > 0) slot1File->setText(utf8() << filename); + syncUi(); +} + +void LoaderWindow::clearSlot1Cartridge() { + slot1File->setText(""); + syncUi(); +} + +void LoaderWindow::selectSlot2Cartridge() { + string filename = utility.selectCartridge(); + if(filename.length() > 0) slot2File->setText(utf8() << filename); + syncUi(); +} + +void LoaderWindow::clearSlot2Cartridge() { + slot2File->setText(""); + syncUi(); +} + +void LoaderWindow::onLoad() { + window->hide(); + string base = baseFile->text().toUtf8().data(); + string slot1 = slot1File->text().toUtf8().data(); + string slot2 = slot2File->text().toUtf8().data(); + + switch(mode) { + case ModeBsxSlotted: utility.loadCartridgeBsxSlotted(base, slot1); break; + case ModeBsx: utility.loadCartridgeBsx(snes.config.path.bsx = base, slot1); break; + case ModeSufamiTurbo: utility.loadCartridgeSufamiTurbo(snes.config.path.st = base, slot1, slot2); break; + } +} + +void LoaderWindow::onCancel() { + window->hide(); +} diff --git a/src/ui_qt/base/loader.hpp b/src/ui_qt/base/loader.hpp new file mode 100644 index 00000000..fdf6d2e9 --- /dev/null +++ b/src/ui_qt/base/loader.hpp @@ -0,0 +1,45 @@ +class LoaderWindow : public QObject { + Q_OBJECT + +public: + QWidget *window; + QVBoxLayout *layout; + QGridLayout *grid; + QLabel *baseLabel; + QLineEdit *baseFile; + QPushButton *baseBrowse; + QPushButton *baseClear; + QLabel *slot1Label; + QLineEdit *slot1File; + QPushButton *slot1Browse; + QPushButton *slot1Clear; + QLabel *slot2Label; + QLineEdit *slot2File; + QPushButton *slot2Browse; + QPushButton *slot2Clear; + QHBoxLayout *controls; + QPushButton *load; + QPushButton *cancel; + QWidget *spacer; + + void setup(); + void syncUi(); + void loadBsxSlottedCartridge(const char*, const char*); + void loadBsxCartridge(const char*, const char*); + void loadSufamiTurboCartridge(const char*, const char*, const char*); + +public slots: + void selectBaseCartridge(); + void clearBaseCartridge(); + void selectSlot1Cartridge(); + void clearSlot1Cartridge(); + void selectSlot2Cartridge(); + void clearSlot2Cartridge(); + + void onLoad(); + void onCancel(); + +private: + enum mode_t { ModeBsxSlotted, ModeBsx, ModeSufamiTurbo } mode; + void showWindow(const char *title); +} *winLoader; diff --git a/src/ui_qt/base/main.cpp b/src/ui_qt/base/main.cpp new file mode 100644 index 00000000..cef4e132 --- /dev/null +++ b/src/ui_qt/base/main.cpp @@ -0,0 +1,373 @@ +void MainWindow::setup() { + window = new Window; + window->setObjectName("main-window"); + window->setWindowTitle(BSNES_TITLE); + + system = window->menuBar()->addMenu("&System"); + system_load = system->addAction("&Load Cartridge ..."); + system->addSeparator(); + system_power = system->addMenu("&Power"); + system_power_on = system_power->addAction("On"); + system_power_on->setCheckable(true); + system_power_off = system_power->addAction("Off"); + system_power_off->setCheckable(true); + system_reset = system->addAction("&Reset"); + system->addSeparator(); + system_port1 = system->addMenu("Controller Port 1"); + system_port1_none = system_port1->addAction("&None"); + system_port1_none->setCheckable(true); + system_port1_joypad = system_port1->addAction("&Joypad"); + system_port1_joypad->setCheckable(true); + system_port1_multitap = system_port1->addAction("&Multitap"); + system_port1_multitap->setCheckable(true); + system_port1_mouse = system_port1->addAction("M&ouse"); + system_port1_mouse->setCheckable(true); + system_port2 = system->addMenu("Controller Port 2"); + system_port2_none = system_port2->addAction("&None"); + system_port2_none->setCheckable(true); + system_port2_joypad = system_port2->addAction("&Joypad"); + system_port2_joypad->setCheckable(true); + system_port2_multitap = system_port2->addAction("&Multitap"); + system_port2_multitap->setCheckable(true); + system_port2_mouse = system_port2->addAction("M&ouse"); + system_port2_mouse->setCheckable(true); + system_port2_superscope = system_port2->addAction("&Super Scope"); + system_port2_superscope->setCheckable(true); + system_port2_justifier = system_port2->addAction("&Justifier"); + system_port2_justifier->setCheckable(true); + system_port2_justifiers = system_port2->addAction("&Two Justifiers"); + system_port2_justifiers->setCheckable(true); + system->addSeparator(); + system_exit = system->addAction("E&xit"); + system_exit->setMenuRole(QAction::QuitRole); + + settings = window->menuBar()->addMenu("S&ettings"); + settings_videoMode = settings->addMenu("&Video Mode"); + settings_videoMode_1x = settings_videoMode->addAction("Scale &1x"); + settings_videoMode_1x->setCheckable(true); + settings_videoMode_2x = settings_videoMode->addAction("Scale &2x"); + settings_videoMode_2x->setCheckable(true); + settings_videoMode_3x = settings_videoMode->addAction("Scale &3x"); + settings_videoMode_3x->setCheckable(true); + settings_videoMode_4x = settings_videoMode->addAction("Scale &4x"); + settings_videoMode_4x->setCheckable(true); + settings_videoMode_max = settings_videoMode->addAction("Scale &Max"); + settings_videoMode_max->setCheckable(true); + settings_videoMode_max->setStatusTip("Scale video output to fill as much of the screen as possible"); + settings_videoMode->addSeparator(); + settings_videoMode_correctAspectRatio = settings_videoMode->addAction("&Correct Aspect Ratio"); + settings_videoMode_correctAspectRatio->setStatusTip("Match pixel width-to-height ratio of TV"); + settings_videoMode_correctAspectRatio->setCheckable(true); + settings_videoMode_fullscreen = settings_videoMode->addAction("&Fullscreen"); + settings_videoMode_fullscreen->setCheckable(true); + settings_videoMode->addSeparator(); + settings_videoMode_ntsc = settings_videoMode->addAction("&NTSC"); + settings_videoMode_ntsc->setCheckable(true); + settings_videoMode_ntsc->setStatusTip("Size video output window to match NTSC TV spec"); + settings_videoMode_pal = settings_videoMode->addAction("&PAL"); + settings_videoMode_pal->setCheckable(true); + settings_videoMode_pal->setStatusTip("Size video output window to match PAL TV spec"); + settings_videoFilter = settings->addMenu("Video &Filter"); + settings_videoFilter_point = settings_videoFilter->addAction("&Point"); + settings_videoFilter_point->setCheckable(true); + settings_videoFilter_point->setStatusTip("Use pixellated hardware video scaling"); + settings_videoFilter_linear = settings_videoFilter->addAction("&Linear"); + settings_videoFilter_linear->setCheckable(true); + settings_videoFilter_linear->setStatusTip("Use smoothed hardware video scaling"); + settings_videoFilter->addSeparator(); + settings_videoFilter_none = settings_videoFilter->addAction("&None"); + settings_videoFilter_none->setCheckable(true); + settings_videoFilter_scanline = settings_videoFilter->addAction("&Scanline"); + settings_videoFilter_scanline->setCheckable(true); + settings_videoFilter_scale2x = settings_videoFilter->addAction("S&cale2x"); + settings_videoFilter_scale2x->setCheckable(true); + settings_videoFilter_hq2x = settings_videoFilter->addAction("&HQ2x"); + settings_videoFilter_hq2x->setCheckable(true); + settings_videoFilter_ntsc = settings_videoFilter->addAction("N&TSC"); + settings_videoFilter_ntsc->setCheckable(true); + settings->addSeparator(); + settings_muteAudio = settings->addAction("&Mute Audio Output"); + settings_muteAudio->setCheckable(true); + settings->addSeparator(); + settings_emulationSpeed = settings->addMenu("&Emulation Speed"); + settings_emulationSpeed_slowest = settings_emulationSpeed->addAction("50%"); + settings_emulationSpeed_slowest->setCheckable(true); + settings_emulationSpeed_slow = settings_emulationSpeed->addAction("75%"); + settings_emulationSpeed_slow->setCheckable(true); + settings_emulationSpeed_normal = settings_emulationSpeed->addAction("100%"); + settings_emulationSpeed_normal->setCheckable(true); + settings_emulationSpeed_fast = settings_emulationSpeed->addAction("150%"); + settings_emulationSpeed_fast->setCheckable(true); + settings_emulationSpeed_fastest = settings_emulationSpeed->addAction("200%"); + settings_emulationSpeed_fastest->setCheckable(true); + settings_emulationSpeed->addSeparator(); + settings_emulationSpeed_syncVideo = settings_emulationSpeed->addAction("Sync &Video"); + settings_emulationSpeed_syncVideo->setCheckable(true); + settings_emulationSpeed_syncVideo->setStatusTip("Sync video output to vertical refresh rate"); + settings_emulationSpeed_syncAudio = settings_emulationSpeed->addAction("Sync &Audio"); + settings_emulationSpeed_syncAudio->setCheckable(true); + settings_emulationSpeed_syncAudio->setStatusTip("Sync audio output to sound card output rate"); + settings_configuration = settings->addAction("&Configuration ..."); + settings_configuration->setMenuRole(QAction::PreferencesRole); + + help = window->menuBar()->addMenu("&Help"); + help_documentation = help->addAction("Documentation ..."); + help_license = help->addAction("License ..."); + help->addSeparator(); + help_about = help->addAction("&About ..."); + help_about->setMenuRole(QAction::AboutRole); + + canvasContainer = new CanvasObject; + canvasContainer->setAcceptDrops(true); { + canvasContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + canvasContainer->setObjectName("backdrop"); + + canvasLayout = new QVBoxLayout; { + canvasLayout->setMargin(0); + canvasLayout->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); + + canvas = new CanvasWidget; + canvas->setAcceptDrops(true); + canvas->setAttribute(Qt::WA_PaintOnScreen, true); //disable Qt painting on focus / resize + + QPalette palette; + palette.setColor(QPalette::Window, QColor(0, 0, 0)); + canvas->setPalette(palette); + canvas->setAutoFillBackground(true); + } + canvasLayout->addWidget(canvas); + } + canvasContainer->setLayout(canvasLayout); + + window->statusBar()->showMessage(""); + systemState = new QLabel; + window->statusBar()->addPermanentWidget(systemState); + + window->setCentralWidget(canvasContainer); + + //slots + connect(system_load, SIGNAL(triggered()), this, SLOT(loadCartridge())); + connect(system_power_on, SIGNAL(triggered()), this, SLOT(powerOn())); + connect(system_power_off, SIGNAL(triggered()), this, SLOT(powerOff())); + connect(system_reset, SIGNAL(triggered()), this, SLOT(reset())); + connect(system_port1_none, SIGNAL(triggered()), this, SLOT(setPort1None())); + connect(system_port1_joypad, SIGNAL(triggered()), this, SLOT(setPort1Joypad())); + connect(system_port1_multitap, SIGNAL(triggered()), this, SLOT(setPort1Multitap())); + connect(system_port1_mouse, SIGNAL(triggered()), this, SLOT(setPort1Mouse())); + connect(system_port2_none, SIGNAL(triggered()), this, SLOT(setPort2None())); + connect(system_port2_joypad, SIGNAL(triggered()), this, SLOT(setPort2Joypad())); + connect(system_port2_multitap, SIGNAL(triggered()), this, SLOT(setPort2Multitap())); + connect(system_port2_mouse, SIGNAL(triggered()), this, SLOT(setPort2Mouse())); + connect(system_port2_superscope, SIGNAL(triggered()), this, SLOT(setPort2SuperScope())); + connect(system_port2_justifier, SIGNAL(triggered()), this, SLOT(setPort2Justifier())); + connect(system_port2_justifiers, SIGNAL(triggered()), this, SLOT(setPort2Justifiers())); + connect(system_exit, SIGNAL(triggered()), this, SLOT(quit())); + connect(settings_videoMode_1x, SIGNAL(triggered()), this, SLOT(setVideoMode1x())); + connect(settings_videoMode_2x, SIGNAL(triggered()), this, SLOT(setVideoMode2x())); + connect(settings_videoMode_3x, SIGNAL(triggered()), this, SLOT(setVideoMode3x())); + connect(settings_videoMode_4x, SIGNAL(triggered()), this, SLOT(setVideoMode4x())); + connect(settings_videoMode_max, SIGNAL(triggered()), this, SLOT(setVideoModeMax())); + connect(settings_videoMode_correctAspectRatio, SIGNAL(triggered()), this, SLOT(toggleAspectCorrection())); + connect(settings_videoMode_fullscreen, SIGNAL(triggered()), this, SLOT(toggleFullscreen())); + connect(settings_videoMode_ntsc, SIGNAL(triggered()), this, SLOT(setVideoNtsc())); + connect(settings_videoMode_pal, SIGNAL(triggered()), this, SLOT(setVideoPal())); + connect(settings_videoFilter_point, SIGNAL(triggered()), this, SLOT(setPointFilter())); + connect(settings_videoFilter_linear, SIGNAL(triggered()), this, SLOT(setLinearFilter())); + connect(settings_videoFilter_none, SIGNAL(triggered()), this, SLOT(setNoFilter())); + connect(settings_videoFilter_scanline, SIGNAL(triggered()), this, SLOT(setScanlineFilter())); + connect(settings_videoFilter_scale2x, SIGNAL(triggered()), this, SLOT(setScale2xFilter())); + connect(settings_videoFilter_hq2x, SIGNAL(triggered()), this, SLOT(setHq2xFilter())); + connect(settings_videoFilter_ntsc, SIGNAL(triggered()), this, SLOT(setNtscFilter())); + connect(settings_muteAudio, SIGNAL(triggered()), this, SLOT(muteAudio())); + connect(settings_emulationSpeed_slowest, SIGNAL(triggered()), this, SLOT(setSpeedSlowest())); + connect(settings_emulationSpeed_slow, SIGNAL(triggered()), this, SLOT(setSpeedSlow())); + connect(settings_emulationSpeed_normal, SIGNAL(triggered()), this, SLOT(setSpeedNormal())); + connect(settings_emulationSpeed_fast, SIGNAL(triggered()), this, SLOT(setSpeedFast())); + connect(settings_emulationSpeed_fastest, SIGNAL(triggered()), this, SLOT(setSpeedFastest())); + connect(settings_emulationSpeed_syncVideo, SIGNAL(triggered()), this, SLOT(syncVideo())); + connect(settings_emulationSpeed_syncAudio, SIGNAL(triggered()), this, SLOT(syncAudio())); + connect(settings_configuration, SIGNAL(triggered()), this, SLOT(showConfigWindow())); + connect(help_documentation, SIGNAL(triggered()), this, SLOT(showDocumentation())); + connect(help_license, SIGNAL(triggered()), this, SLOT(showLicense())); + connect(help_about, SIGNAL(triggered()), this, SLOT(showAbout())); + + utility.resizeMainWindow(); + utility.centerWindow(window); + syncUi(); +} + +void MainWindow::syncUi() { + system_power->setEnabled(cartridge.loaded()); + system_power_on->setChecked (application.power == true); + system_power_off->setChecked(application.power == false); + system_reset->setEnabled(cartridge.loaded() && application.power); + + system_port1_none->setChecked (snes.config.controller_port1 == SNES::Input::DeviceNone); + system_port1_joypad->setChecked (snes.config.controller_port1 == SNES::Input::DeviceJoypad); + system_port1_multitap->setChecked (snes.config.controller_port1 == SNES::Input::DeviceMultitap); + system_port1_mouse->setChecked (snes.config.controller_port1 == SNES::Input::DeviceMouse); + system_port2_none->setChecked (snes.config.controller_port2 == SNES::Input::DeviceNone); + system_port2_joypad->setChecked (snes.config.controller_port2 == SNES::Input::DeviceJoypad); + system_port2_multitap->setChecked (snes.config.controller_port2 == SNES::Input::DeviceMultitap); + system_port2_mouse->setChecked (snes.config.controller_port2 == SNES::Input::DeviceMouse); + system_port2_superscope->setChecked(snes.config.controller_port2 == SNES::Input::DeviceSuperScope); + system_port2_justifier->setChecked (snes.config.controller_port2 == SNES::Input::DeviceJustifier); + system_port2_justifiers->setChecked(snes.config.controller_port2 == SNES::Input::DeviceJustifiers); + + settings_videoMode_1x->setChecked (config.video.context->multiplier == 1); + settings_videoMode_2x->setChecked (config.video.context->multiplier == 2); + settings_videoMode_3x->setChecked (config.video.context->multiplier == 3); + settings_videoMode_4x->setChecked (config.video.context->multiplier == 4); + settings_videoMode_max->setChecked(config.video.context->multiplier >= 5); + + settings_videoMode_correctAspectRatio->setChecked(config.video.context->correctAspectRatio); + settings_videoMode_fullscreen->setChecked(config.video.isFullscreen); + settings_videoMode_ntsc->setChecked(config.video.context->region == 0); + settings_videoMode_pal->setChecked (config.video.context->region == 1); + + settings_videoFilter_point->setChecked (config.video.context->hwFilter == 0); + settings_videoFilter_linear->setChecked (config.video.context->hwFilter == 1); + settings_videoFilter_none->setChecked (config.video.context->swFilter == 0); + settings_videoFilter_scanline->setChecked(config.video.context->swFilter == 1); + settings_videoFilter_scale2x->setChecked (config.video.context->swFilter == 2); + settings_videoFilter_hq2x->setChecked (config.video.context->swFilter == 3); + settings_videoFilter_ntsc->setChecked (config.video.context->swFilter == 4); + + settings_muteAudio->setChecked(config.audio.mute); + + settings_emulationSpeed_slowest->setChecked(config.system.speed == 0); + settings_emulationSpeed_slow->setChecked (config.system.speed == 1); + settings_emulationSpeed_normal->setChecked (config.system.speed == 2); + settings_emulationSpeed_fast->setChecked (config.system.speed == 3); + settings_emulationSpeed_fastest->setChecked(config.system.speed == 4); + + settings_emulationSpeed_syncVideo->setChecked(config.video.synchronize); + settings_emulationSpeed_syncAudio->setChecked(config.audio.synchronize); +} + +void MainWindow::loadCartridge() { + string filename = utility.selectCartridge(); + if(filename.length() > 0) utility.loadCartridge(filename); +} + +void MainWindow::powerOn() { utility.modifySystemState(Utility::PowerOn); } +void MainWindow::powerOff() { utility.modifySystemState(Utility::PowerOff); } +void MainWindow::reset() { utility.modifySystemState(Utility::Reset); } + +void MainWindow::setPort1None() { snes.config.controller_port1 = SNES::Input::DeviceNone; utility.updateControllers(); syncUi(); } +void MainWindow::setPort1Joypad() { snes.config.controller_port1 = SNES::Input::DeviceJoypad; utility.updateControllers(); syncUi(); } +void MainWindow::setPort1Multitap() { snes.config.controller_port1 = SNES::Input::DeviceMultitap; utility.updateControllers(); syncUi(); } +void MainWindow::setPort1Mouse() { snes.config.controller_port1 = SNES::Input::DeviceMouse; utility.updateControllers(); syncUi(); } +void MainWindow::setPort2None() { snes.config.controller_port2 = SNES::Input::DeviceNone; utility.updateControllers(); syncUi(); } +void MainWindow::setPort2Joypad() { snes.config.controller_port2 = SNES::Input::DeviceJoypad; utility.updateControllers(); syncUi(); } +void MainWindow::setPort2Multitap() { snes.config.controller_port2 = SNES::Input::DeviceMultitap; utility.updateControllers(); syncUi(); } +void MainWindow::setPort2Mouse() { snes.config.controller_port2 = SNES::Input::DeviceMouse; utility.updateControllers(); syncUi(); } +void MainWindow::setPort2SuperScope() { snes.config.controller_port2 = SNES::Input::DeviceSuperScope; utility.updateControllers(); syncUi(); } +void MainWindow::setPort2Justifier() { snes.config.controller_port2 = SNES::Input::DeviceJustifier; utility.updateControllers(); syncUi(); } +void MainWindow::setPort2Justifiers() { snes.config.controller_port2 = SNES::Input::DeviceJustifiers; utility.updateControllers(); syncUi(); } + +void MainWindow::quit() { + application.terminate = true; +} + +void MainWindow::setVideoMode1x() { config.video.context->multiplier = 1; utility.resizeMainWindow(); syncUi(); } +void MainWindow::setVideoMode2x() { config.video.context->multiplier = 2; utility.resizeMainWindow(); syncUi(); } +void MainWindow::setVideoMode3x() { config.video.context->multiplier = 3; utility.resizeMainWindow(); syncUi(); } +void MainWindow::setVideoMode4x() { config.video.context->multiplier = 4; utility.resizeMainWindow(); syncUi(); } +void MainWindow::setVideoModeMax() { config.video.context->multiplier = 9; utility.resizeMainWindow(); syncUi(); } + +void MainWindow::toggleAspectCorrection() { + config.video.context->correctAspectRatio = settings_videoMode_correctAspectRatio->isChecked(); + utility.resizeMainWindow(); +} + +void MainWindow::toggleFullscreen() { config.video.isFullscreen = settings_videoMode_fullscreen->isChecked(); utility.updateFullscreenState(); syncUi(); } + +void MainWindow::setVideoNtsc() { config.video.context->region = 0; utility.updateVideoMode(); utility.resizeMainWindow(); syncUi(); } +void MainWindow::setVideoPal() { config.video.context->region = 1; utility.updateVideoMode(); utility.resizeMainWindow(); syncUi(); } + +void MainWindow::setPointFilter() { config.video.context->hwFilter = 0; utility.updateHardwareFilter(); syncUi(); } +void MainWindow::setLinearFilter() { config.video.context->hwFilter = 1; utility.updateHardwareFilter(); syncUi(); } +void MainWindow::setNoFilter() { config.video.context->swFilter = 0; utility.updateSoftwareFilter(); syncUi(); } +void MainWindow::setScanlineFilter() { config.video.context->swFilter = 1; utility.updateSoftwareFilter(); syncUi(); } +void MainWindow::setScale2xFilter() { config.video.context->swFilter = 2; utility.updateSoftwareFilter(); syncUi(); } +void MainWindow::setHq2xFilter() { config.video.context->swFilter = 3; utility.updateSoftwareFilter(); syncUi(); } +void MainWindow::setNtscFilter() { config.video.context->swFilter = 4; utility.updateSoftwareFilter(); syncUi(); } + +void MainWindow::muteAudio() { config.audio.mute = settings_muteAudio->isChecked(); } + +void MainWindow::setSpeedSlowest() { config.system.speed = 0; utility.updateEmulationSpeed(); syncUi(); } +void MainWindow::setSpeedSlow() { config.system.speed = 1; utility.updateEmulationSpeed(); syncUi(); } +void MainWindow::setSpeedNormal() { config.system.speed = 2; utility.updateEmulationSpeed(); syncUi(); } +void MainWindow::setSpeedFast() { config.system.speed = 3; utility.updateEmulationSpeed(); syncUi(); } +void MainWindow::setSpeedFastest() { config.system.speed = 4; utility.updateEmulationSpeed(); syncUi(); } + +void MainWindow::syncVideo() { config.video.synchronize = settings_emulationSpeed_syncVideo->isChecked(); utility.updateAvSync(); } +void MainWindow::syncAudio() { config.audio.synchronize = settings_emulationSpeed_syncAudio->isChecked(); utility.updateAvSync(); } + +void MainWindow::showConfigWindow() { + winSettings->show(); +} + +void MainWindow::showDocumentation() { + QFile file(":/documentation.html"); + if(file.open(QIODevice::ReadOnly | QIODevice::Text)) { + winHtmlViewer->show("Usage Documentation", file.readAll().constData()); + file.close(); + } +} + +void MainWindow::showLicense() { + QFile file(":/license.html"); + if(file.open(QIODevice::ReadOnly | QIODevice::Text)) { + winHtmlViewer->show("License Agreement", file.readAll().constData()); + file.close(); + } +} +void MainWindow::showAbout() { + winAbout->show(); +} + +void MainWindow::Window::closeEvent(QCloseEvent*) { + winMain->quit(); +} + +//============ +//CanvasObject +//============ +//implement drag-and-drop support: +//drag cartridge image onto main window canvas area to load + +void CanvasObject::dragEnterEvent(QDragEnterEvent *event) { + if(event->mimeData()->hasUrls()) { + //do not accept multiple files at once + if(event->mimeData()->urls().count() == 1) event->acceptProposedAction(); + } +} + +void CanvasObject::dropEvent(QDropEvent *event) { + if(event->mimeData()->hasUrls()) { + QList list = event->mimeData()->urls(); + if(list.count() == 1) utility.loadCartridge(list.at(0).toLocalFile().toUtf8().constData()); + } +} + +//=========== +//CanvasWidget +//============ +//custom video render and mouse capture functionality + +QPaintEngine* CanvasWidget::paintEngine() const { + if(cartridge.loaded()) return 0; + return QWidget::paintEngine(); +} + +void CanvasWidget::mouseReleaseEvent(QMouseEvent *event) { + //acquire exclusive mode access to mouse when video output widget is clicked + //(will only acquire if cart is loaded, and mouse / lightgun is in use.) + utility.acquireMouse(); +} + +void CanvasWidget::paintEvent(QPaintEvent *event) { + event->ignore(); +} diff --git a/src/ui_qt/base/main.hpp b/src/ui_qt/base/main.hpp new file mode 100644 index 00000000..f24638a4 --- /dev/null +++ b/src/ui_qt/base/main.hpp @@ -0,0 +1,129 @@ +class CanvasObject : public QWidget { +public: + void dragEnterEvent(QDragEnterEvent*); + void dropEvent(QDropEvent*); +}; + +class CanvasWidget : public CanvasObject { +public: + QPaintEngine* paintEngine() const; + void mouseReleaseEvent(QMouseEvent*); + void paintEvent(QPaintEvent*); +}; + +class MainWindow : public QObject { + Q_OBJECT + +public: + struct Window : public QMainWindow { + void closeEvent(QCloseEvent*); + } *window; + QVBoxLayout *layout; + QMenu *system; + QAction *system_load; + QMenu *system_power; + QAction *system_power_on; + QAction *system_power_off; + QAction *system_reset; + QMenu *system_port1; + QAction *system_port1_none; + QAction *system_port1_joypad; + QAction *system_port1_multitap; + QAction *system_port1_mouse; + QMenu *system_port2; + QAction *system_port2_none; + QAction *system_port2_joypad; + QAction *system_port2_multitap; + QAction *system_port2_mouse; + QAction *system_port2_superscope; + QAction *system_port2_justifier; + QAction *system_port2_justifiers; + QAction *system_exit; + QMenu *settings; + QMenu *settings_videoMode; + QAction *settings_videoMode_1x; + QAction *settings_videoMode_2x; + QAction *settings_videoMode_3x; + QAction *settings_videoMode_4x; + QAction *settings_videoMode_max; + QAction *settings_videoMode_correctAspectRatio; + QAction *settings_videoMode_fullscreen; + QAction *settings_videoMode_ntsc; + QAction *settings_videoMode_pal; + QMenu *settings_videoFilter; + QAction *settings_videoFilter_point; + QAction *settings_videoFilter_linear; + QAction *settings_videoFilter_none; + QAction *settings_videoFilter_scanline; + QAction *settings_videoFilter_scale2x; + QAction *settings_videoFilter_hq2x; + QAction *settings_videoFilter_ntsc; + QAction *settings_muteAudio; + QMenu *settings_emulationSpeed; + QAction *settings_emulationSpeed_slowest; + QAction *settings_emulationSpeed_slow; + QAction *settings_emulationSpeed_normal; + QAction *settings_emulationSpeed_fast; + QAction *settings_emulationSpeed_fastest; + QAction *settings_emulationSpeed_syncVideo; + QAction *settings_emulationSpeed_syncAudio; + QAction *settings_configuration; + QMenu *help; + QAction *help_documentation; + QAction *help_license; + QAction *help_about; + // + CanvasObject *canvasContainer; + QVBoxLayout *canvasLayout; + CanvasWidget *canvas; + QLabel *systemState; + + void setup(); + void syncUi(); + +public slots: + void loadCartridge(); + void powerOn(); + void powerOff(); + void reset(); + void setPort1None(); + void setPort1Joypad(); + void setPort1Multitap(); + void setPort1Mouse(); + void setPort2None(); + void setPort2Joypad(); + void setPort2Multitap(); + void setPort2Mouse(); + void setPort2SuperScope(); + void setPort2Justifier(); + void setPort2Justifiers(); + void quit(); + void setVideoMode1x(); + void setVideoMode2x(); + void setVideoMode3x(); + void setVideoMode4x(); + void setVideoModeMax(); + void toggleAspectCorrection(); + void toggleFullscreen(); + void setVideoNtsc(); + void setVideoPal(); + void setPointFilter(); + void setLinearFilter(); + void setNoFilter(); + void setScanlineFilter(); + void setScale2xFilter(); + void setHq2xFilter(); + void setNtscFilter(); + void muteAudio(); + void setSpeedSlowest(); + void setSpeedSlow(); + void setSpeedNormal(); + void setSpeedFast(); + void setSpeedFastest(); + void syncVideo(); + void syncAudio(); + void showConfigWindow(); + void showDocumentation(); + void showLicense(); + void showAbout(); +} *winMain; diff --git a/src/ui_qt/config.cpp b/src/ui_qt/config.cpp new file mode 100644 index 00000000..0cd53833 --- /dev/null +++ b/src/ui_qt/config.cpp @@ -0,0 +1,236 @@ +class Configuration : public configuration { +public: + struct System { + string video, audio, input; + bool crashedOnLastRun; + unsigned speed; + } system; + + struct Video { + bool isFullscreen; + bool synchronize; + signed contrastAdjust, brightnessAdjust, gammaAdjust; + bool enableGammaRamp; + bool enableNtscMergeFields; + double ntscAspectRatio, palAspectRatio; + + struct Context { + bool correctAspectRatio; + unsigned multiplier, region; + unsigned hwFilter, swFilter; + } *context, windowed, fullscreen; + } video; + + struct Audio { + bool synchronize; + bool mute; + unsigned volume, latency, outputFrequency, inputFrequency; + } audio; + + struct Input { + enum policy_t { FocusPolicyPauseEmulation, FocusPolicyIgnoreInput, FocusPolicyAllowInput }; + unsigned focusPolicy; + unsigned analogAxisResistance; + bool allowInvalidInput; + + struct Joypad { + string up, down, left, right, a, b, x, y, l, r, select, start; + } joypad1, joypad2, + multitap1a, multitap1b, multitap1c, multitap1d, + multitap2a, multitap2b, multitap2c, multitap2d; + + struct Mouse { + string x, y, left, right; + } mouse1, mouse2; + + struct SuperScope { + string x, y, trigger, turbo, cursor, pause; + } superscope; + + struct Justifier { + string x, y, trigger, start; + } justifier1, justifier2; + + struct UiGeneral { + string loadCartridge; + string pauseEmulation; + string resetSystem; + string powerCycleSystem; + string lowerSpeed; + string raiseSpeed; + string toggleCheatSystem; + string toggleFullscreen; + string toggleMenu; + string toggleStatus; + string exitEmulator; + } uiGeneral; + } input; + + Configuration() { + //======== + //external + //======== + + attach(snes.config.controller_port1 = SNES::Input::DeviceJoypad, "snes.controller_port1"); + attach(snes.config.controller_port2 = SNES::Input::DeviceJoypad, "snes.controller_port2"); + attach(snes.config.expansion_port = SNES::ExpansionBSX, "snes.expansion_port"); + attach(snes.config.region = SNES::Autodetect, "snes.region"); + + attach(snes.config.file.autodetect_type = false, "file.autodetect_type"); + attach(snes.config.file.bypass_patch_crc32 = false, "file.bypass_patch_crc32"); + + attach(snes.config.path.rom = "", "path.rom"); + attach(snes.config.path.save = "", "path.save"); + attach(snes.config.path.patch = "", "path.patch"); + attach(snes.config.path.cheat = "", "path.cheat"); + attach(snes.config.path.data = "", "path.data"); + attach(snes.config.path.bsx = "", "path.bsx"); + attach(snes.config.path.st = "", "path.st"); + + attach(snes.config.cpu.version = 2, "cpu.version", "Valid version(s) are: 1, 2"); + attach(snes.config.cpu.ntsc_clock_rate = 21477272, "cpu.ntsc_clock_rate"); + attach(snes.config.cpu.pal_clock_rate = 21281370, "cpu.pal_clock_rate"); + attach(snes.config.cpu.alu_mul_delay = 2, "cpu.alu_mul_delay"); + attach(snes.config.cpu.alu_div_delay = 2, "cpu.alu_div_delay"); + attach(snes.config.cpu.wram_init_value = 0x55, "cpu.wram_init_value"); + + attach(snes.config.smp.ntsc_clock_rate = 32041 * 768, "smp.ntsc_clock_rate"); + attach(snes.config.smp.pal_clock_rate = 32041 * 768, "smp.pal_clock_rate"); + + attach(snes.config.ppu1.version = 1, "ppu1.version", "Valid version(s) are: 1"); + attach(snes.config.ppu2.version = 3, "ppu2.version", "Valid version(s) are: 1, 2, 3"); + + //======== + //internal + //======== + + attach(system.video = "", "system.video"); + attach(system.audio = "", "system.audio"); + attach(system.input = "", "system.input"); + attach(system.crashedOnLastRun = false, "system.crashedOnLastRun"); + attach(system.speed = 2, "system.speed"); + + video.context = &video.windowed; + attach(video.isFullscreen = false, "video.isFullscreen"); + attach(video.synchronize = false, "video.synchronize"); + + attach(video.contrastAdjust = 0, "video.contrastAdjust"); + attach(video.brightnessAdjust = 0, "video.brightnessAdjust"); + attach(video.gammaAdjust = 0, "video.gammaAdjust"); + + attach(video.enableGammaRamp = true, "video.enableGammaRamp"); + attach(video.enableNtscMergeFields = false, "video.enableNtscMergeFields"); + + attach(video.ntscAspectRatio = 54.0 / 47.0, "video.ntscAspectRatio", "NTSC aspect ratio (x / y)"); + attach(video.palAspectRatio = 32.0 / 23.0, "video.palAspectRatio", "PAL aspect ratio (x / y)"); + + attach(video.windowed.correctAspectRatio = true, "video.windowed.correctAspectRatio"); + attach(video.windowed.multiplier = 2, "video.windowed.multiplier"); + attach(video.windowed.region = 0, "video.windowed.region"); + + attach(video.windowed.hwFilter = 1, "video.windowed.hwFilter"); + attach(video.windowed.swFilter = 0, "video.windowed.swFilter"); + + attach(video.fullscreen.correctAspectRatio = true, "video.fullscreen.correctAspectRatio"); + attach(video.fullscreen.multiplier = 9, "video.fullscreen.multiplier"); + attach(video.fullscreen.region = 0, "video.fullscreen.region"); + + attach(video.fullscreen.hwFilter = 1, "video.fullscreen.hwFilter"); + attach(video.fullscreen.swFilter = 0, "video.fullscreen.swFilter"); + + attach(audio.synchronize = true, "audio.synchronize"); + attach(audio.mute = false, "audio.mute"); + + attach(audio.volume = 100, "audio.volume"); + attach(audio.latency = 80, "audio.latency"); + attach(audio.outputFrequency = 48000, "audio.outputFrequency"); + attach(audio.inputFrequency = 32000, "audio.inputFrequency"); + + attach(input.focusPolicy = Input::FocusPolicyPauseEmulation, "input.focusPolicy"); + attach(input.analogAxisResistance = 50, "input.analogAxisResistance", "Percentage; lower = less resistance"); + attach(input.allowInvalidInput = false, "input.allowInvalidInput", "Allow up+down / left+right combinations; may trigger bugs in some games"); + + attach(input.joypad1.up = "up", "input.joypad1.up"); + attach(input.joypad1.down = "down", "input.joypad1.down"); + attach(input.joypad1.left = "left", "input.joypad1.left"); + attach(input.joypad1.right = "right", "input.joypad1.right"); + attach(input.joypad1.a = "x", "input.joypad1.a"); + attach(input.joypad1.b = "z", "input.joypad1.b"); + attach(input.joypad1.x = "s", "input.joypad1.x"); + attach(input.joypad1.y = "a", "input.joypad1.y"); + attach(input.joypad1.l = "d", "input.joypad1.l"); + attach(input.joypad1.r = "c", "input.joypad1.r"); + attach(input.joypad1.select = "rshift", "input.joypad1.select"); + attach(input.joypad1.start = "return", "input.joypad1.start"); + + attachJoypad(input.joypad2, "input.joypad2"); + attachJoypad(input.multitap1a, "input.multitap1a"); + attachJoypad(input.multitap1b, "input.multitap1b"); + attachJoypad(input.multitap1c, "input.multitap1c"); + attachJoypad(input.multitap1d, "input.multitap1d"); + attachJoypad(input.multitap2a, "input.multitap2a"); + attachJoypad(input.multitap2b, "input.multitap2b"); + attachJoypad(input.multitap2c, "input.multitap2c"); + attachJoypad(input.multitap2d, "input.multitap2d"); + + attach(input.mouse1.x = "mouse.x", "input.mouse1.x"); + attach(input.mouse1.y = "mouse.y", "input.mouse1.y"); + attach(input.mouse1.left = "mouse.button00", "input.mouse1.left"); + attach(input.mouse1.right = "mouse.button02", "input.mouse1.right"); + + attach(input.mouse2.x = "mouse.x", "input.mouse2.x"); + attach(input.mouse2.y = "mouse.y", "input.mouse2.y"); + attach(input.mouse2.left = "mouse.button00", "input.mouse2.left"); + attach(input.mouse2.right = "mouse.button02", "input.mouse2.right"); + + attach(input.superscope.x = "mouse.x", "input.superscope.x"); + attach(input.superscope.y = "mouse.y", "input.superscope.y"); + attach(input.superscope.trigger = "mouse.button00", "input.superscope.trigger"); + attach(input.superscope.cursor = "mouse.button02", "input.superscope.cursor"); + attach(input.superscope.turbo = "t", "input.superscope.turbo"); + attach(input.superscope.pause = "p", "input.superscope.pause"); + + attach(input.justifier1.x = "mouse.x", "input.justifier1.x"); + attach(input.justifier1.y = "mouse.y", "input.justifier1.y"); + attach(input.justifier1.trigger = "mouse.button00", "input.justifier1.trigger"); + attach(input.justifier1.start = "mouse.button02", "input.jusitifer1.start"); + + attach(input.justifier2.x = "none", "input.justifier2.x"); + attach(input.justifier2.y = "none", "input.justifier2.y"); + attach(input.justifier2.trigger = "none", "input.justifier2.trigger"); + attach(input.justifier2.start = "none", "input.justifier2.start"); + + attach(input.uiGeneral.loadCartridge = "none", "input.uiGeneral.loadCartridge"); + attach(input.uiGeneral.pauseEmulation = "pause", "input.uiGeneral.pauseEmulation"); + attach(input.uiGeneral.resetSystem = "none", "input.uiGeneral.resetSystem"); + attach(input.uiGeneral.powerCycleSystem = "none", "input.uiGeneral.powerCycleSystem"); + attach(input.uiGeneral.lowerSpeed = "subtract", "input.uiGeneral.lowerSpeed"); + attach(input.uiGeneral.raiseSpeed = "add", "input.uiGeneral.raiseSpeed"); + attach(input.uiGeneral.toggleCheatSystem = "none", "input.uiGeneral.toggleCheatSystem"); + attach(input.uiGeneral.toggleFullscreen = "f11", "input.uiGeneral.toggleFullscreen"); + attach(input.uiGeneral.toggleMenu = "escape", "input.uiGeneral.toggleMenu"); + attach(input.uiGeneral.toggleStatus = "escape", "input.uiGeneral.toggleStatus"); + attach(input.uiGeneral.exitEmulator = "none", "input.uiGeneral.exitEmulator"); + } + + void attachJoypad(Input::Joypad &joypad, const char *name) { + attach(joypad.up = "none", string() << name << ".up"); + attach(joypad.down = "none", string() << name << ".down"); + attach(joypad.left = "none", string() << name << ".left"); + attach(joypad.right = "none", string() << name << ".right"); + attach(joypad.a = "none", string() << name << ".a"); + attach(joypad.b = "none", string() << name << ".b"); + attach(joypad.x = "none", string() << name << ".x"); + attach(joypad.y = "none", string() << name << ".y"); + attach(joypad.l = "none", string() << name << ".l"); + attach(joypad.r = "none", string() << name << ".r"); + attach(joypad.select = "none", string() << name << ".select"); + attach(joypad.start = "none", string() << name << ".start"); + } + + bool load(const char *filename) { + if(configuration::load(filename) == false) return false; + video.context = (video.isFullscreen == false) ? &video.windowed : &video.fullscreen; + return true; + } +} config; diff --git a/src/ui_qt/input/device.cpp b/src/ui_qt/input/device.cpp new file mode 100644 index 00000000..6b41ba1b --- /dev/null +++ b/src/ui_qt/input/device.cpp @@ -0,0 +1,261 @@ +//=========== +//InputDevice +//=========== + +InputDevice::InputDevice(SNES::Input::DeviceID i, bool p, const char *n) : InputGroup(n), id(i), port(p) { +} + +//====== +//Joypad +//====== + +int16_t Joypad::state(unsigned index) const { + if(config.input.allowInvalidInput == false) { + //SNES D-pads have central pivot point, making up+down or left+right combinations impossible. + //some software programs rely on this, and will crash if these combinations are allowed. + if(index == SNES::Input::JoypadDown && up.state ) return 0; + if(index == SNES::Input::JoypadRight && left.state) return 0; + } + + switch(index) { + case SNES::Input::JoypadUp: return up.state; + case SNES::Input::JoypadDown: return down.state; + case SNES::Input::JoypadLeft: return left.state; + case SNES::Input::JoypadRight: return right.state; + case SNES::Input::JoypadA: return a.state; + case SNES::Input::JoypadB: return b.state; + case SNES::Input::JoypadX: return x.state; + case SNES::Input::JoypadY: return y.state; + case SNES::Input::JoypadL: return l.state; + case SNES::Input::JoypadR: return r.state; + case SNES::Input::JoypadSelect: return select.state; + case SNES::Input::JoypadStart: return start.state; + } + + return 0; +} + +Joypad::Joypad(SNES::Input::DeviceID id, bool port, const char *name, +string &up_t, string &down_t, string &left_t, string &right_t, string &a_t, string &b_t, +string &x_t, string &y_t, string &l_t, string &r_t, string &select_t, string &start_t +) : +InputDevice(id, port, name), +up (InputObject::Button, "Up", up_t), +down (InputObject::Button, "Down", down_t), +left (InputObject::Button, "Left", left_t), +right (InputObject::Button, "Right", right_t), +a (InputObject::Button, "A", a_t), +b (InputObject::Button, "B", b_t), +x (InputObject::Button, "X", x_t), +y (InputObject::Button, "Y", y_t), +l (InputObject::Button, "L", l_t), +r (InputObject::Button, "R", r_t), +select(InputObject::Button, "Select", select_t), +start (InputObject::Button, "Start", start_t) { + attach(up); attach(down); attach(left); attach(right); attach(a); attach(b); + attach(x); attach(y); attach(l); attach(r); attach(select); attach(start); +} + +//===== +//Mouse +//===== + +int16_t Mouse::state(unsigned index) const { + switch(index) { + case SNES::Input::MouseX: return x.state; + case SNES::Input::MouseY: return y.state; + case SNES::Input::MouseLeft: return left.state; + case SNES::Input::MouseRight: return right.state; + } + + return 0; +} + +Mouse::Mouse(SNES::Input::DeviceID id, bool port, const char *name, +string &x_t, string &y_t, string &left_t, string &right_t +) : +InputDevice(id, port, name), +x (InputObject::Axis, "X-axis", x_t), +y (InputObject::Axis, "Y-axis", y_t), +left (InputObject::Button, "Left button", left_t), +right(InputObject::Button, "Right button", right_t) { + attach(x); attach(y); attach(left); attach(right); +} + +//========== +//SuperScope +//========== + +int16_t SuperScope::state(unsigned index) const { + switch(index) { + case SNES::Input::SuperScopeX: return x.state; + case SNES::Input::SuperScopeY: return y.state; + case SNES::Input::SuperScopeTrigger: return trigger.state; + case SNES::Input::SuperScopeCursor: return cursor.state; + case SNES::Input::SuperScopeTurbo: return turbo.state; + case SNES::Input::SuperScopePause: return pause.state; + } + + return 0; +} + +SuperScope::SuperScope(SNES::Input::DeviceID id, bool port, const char *name, +string &x_t, string &y_t, string &trigger_t, string &cursor_t, string &turbo_t, string &pause_t +) : +InputDevice(id, port, name), +x (InputObject::Axis, "X-axis", x_t), +y (InputObject::Axis, "Y-axis", y_t), +trigger(InputObject::Button, "Trigger", trigger_t), +cursor (InputObject::Button, "Cursor", cursor_t), +turbo (InputObject::Button, "Turbo", turbo_t), +pause (InputObject::Button, "Pause", pause_t) { + attach(x); attach(y); attach(trigger); attach(cursor); attach(turbo); attach(pause); +} + +//========= +//Justifier +//========= + +int16_t Justifier::state(unsigned index) const { + switch(index) { + case SNES::Input::JustifierX: return x.state; + case SNES::Input::JustifierY: return y.state; + case SNES::Input::JustifierTrigger: return trigger.state; + case SNES::Input::JustifierStart: return start.state; + } + + return 0; +} + +Justifier::Justifier(SNES::Input::DeviceID id, bool port, const char *name, +string &x_t, string &y_t, string &trigger_t, string &start_t +) : +InputDevice(id, port, name), +x (InputObject::Axis, "X-axis", x_t), +y (InputObject::Axis, "Y-axis", y_t), +trigger(InputObject::Button, "Trigger", trigger_t), +start (InputObject::Button, "Start", start_t) { + attach(x); attach(y); attach(trigger); attach(start); +} + +//=============== +//InputDevicePool +//=============== + +void InputDevicePool::attach(InputDevice &device) { + list.add(&device); +} + +void InputDevicePool::bind() { + for(unsigned i = 0; i < list.size(); i++) list[i]->bind(); +} + +void InputDevicePool::clear() { + for(unsigned i = 0; i < list.size(); i++) list[i]->clear(); +} + +void InputDevicePool::poll(const int16_t *table) { + for(unsigned i = 0; i < list.size(); i++) list[i]->poll(table); +} + +InputDevice* InputDevicePool::find(SNES::Input::DeviceID id) { + for(unsigned i = 0; i < list.size(); i++) { + if(list[i]->id == id) return list[i]; + } + + return 0; +} + +InputDevicePool::InputDevicePool() : list(*this) { +} + +// + +Joypad joypad1(SNES::Input::DeviceIDJoypad1, InputDevice::Port1, "Joypad", +config.input.joypad1.up, config.input.joypad1.down, config.input.joypad1.left, config.input.joypad1.right, +config.input.joypad1.a, config.input.joypad1.b, config.input.joypad1.x, config.input.joypad1.y, +config.input.joypad1.l, config.input.joypad1.r, config.input.joypad1.select, config.input.joypad1.start); + +Joypad joypad2(SNES::Input::DeviceIDJoypad2, InputDevice::Port2, "Joypad", +config.input.joypad2.up, config.input.joypad2.down, config.input.joypad2.left, config.input.joypad2.right, +config.input.joypad2.a, config.input.joypad2.b, config.input.joypad2.x, config.input.joypad2.y, +config.input.joypad2.l, config.input.joypad2.r, config.input.joypad2.select, config.input.joypad2.start); + +Joypad multitap1a(SNES::Input::DeviceIDMultitap1A, InputDevice::Port1, "Multitap - Port 1", +config.input.multitap1a.up, config.input.multitap1a.down, config.input.multitap1a.left, config.input.multitap1a.right, +config.input.multitap1a.a, config.input.multitap1a.b, config.input.multitap1a.x, config.input.multitap1a.y, +config.input.multitap1a.l, config.input.multitap1a.r, config.input.multitap1a.select, config.input.multitap1a.start); + +Joypad multitap1b(SNES::Input::DeviceIDMultitap1B, InputDevice::Port1, "Multitap - Port 2", +config.input.multitap1b.up, config.input.multitap1b.down, config.input.multitap1b.left, config.input.multitap1b.right, +config.input.multitap1b.a, config.input.multitap1b.b, config.input.multitap1b.x, config.input.multitap1b.y, +config.input.multitap1b.l, config.input.multitap1b.r, config.input.multitap1b.select, config.input.multitap1b.start); + +Joypad multitap1c(SNES::Input::DeviceIDMultitap1C, InputDevice::Port1, "Multitap - Port 3", +config.input.multitap1c.up, config.input.multitap1c.down, config.input.multitap1c.left, config.input.multitap1c.right, +config.input.multitap1c.a, config.input.multitap1c.b, config.input.multitap1c.x, config.input.multitap1c.y, +config.input.multitap1c.l, config.input.multitap1c.r, config.input.multitap1c.select, config.input.multitap1c.start); + +Joypad multitap1d(SNES::Input::DeviceIDMultitap1D, InputDevice::Port1, "Multitap - Port 4", +config.input.multitap1d.up, config.input.multitap1d.down, config.input.multitap1d.left, config.input.multitap1d.right, +config.input.multitap1d.a, config.input.multitap1d.b, config.input.multitap1d.x, config.input.multitap1d.y, +config.input.multitap1d.l, config.input.multitap1d.r, config.input.multitap1d.select, config.input.multitap1d.start); + +Joypad multitap2a(SNES::Input::DeviceIDMultitap2A, InputDevice::Port2, "Multitap - Port 1", +config.input.multitap2a.up, config.input.multitap2a.down, config.input.multitap2a.left, config.input.multitap2a.right, +config.input.multitap2a.a, config.input.multitap2a.b, config.input.multitap2a.x, config.input.multitap2a.y, +config.input.multitap2a.l, config.input.multitap2a.r, config.input.multitap2a.select, config.input.multitap2a.start); + +Joypad multitap2b(SNES::Input::DeviceIDMultitap2B, InputDevice::Port2, "Multitap - Port 2", +config.input.multitap2b.up, config.input.multitap2b.down, config.input.multitap2b.left, config.input.multitap2b.right, +config.input.multitap2b.a, config.input.multitap2b.b, config.input.multitap2b.x, config.input.multitap2b.y, +config.input.multitap2b.l, config.input.multitap2b.r, config.input.multitap2b.select, config.input.multitap2b.start); + +Joypad multitap2c(SNES::Input::DeviceIDMultitap2C, InputDevice::Port2, "Multitap - Port 3", +config.input.multitap2c.up, config.input.multitap2c.down, config.input.multitap2c.left, config.input.multitap2c.right, +config.input.multitap2c.a, config.input.multitap2c.b, config.input.multitap2c.x, config.input.multitap2c.y, +config.input.multitap2c.l, config.input.multitap2c.r, config.input.multitap2c.select, config.input.multitap2c.start); + +Joypad multitap2d(SNES::Input::DeviceIDMultitap2D, InputDevice::Port2, "Multitap - Port 4", +config.input.multitap2d.up, config.input.multitap2d.down, config.input.multitap2d.left, config.input.multitap2d.right, +config.input.multitap2d.a, config.input.multitap2d.b, config.input.multitap2d.x, config.input.multitap2d.y, +config.input.multitap2d.l, config.input.multitap2d.r, config.input.multitap2d.select, config.input.multitap2d.start); + +Mouse mouse1(SNES::Input::DeviceIDMouse1, InputDevice::Port1, "Mouse", +config.input.mouse1.x, config.input.mouse1.y, config.input.mouse1.left, config.input.mouse1.right); + +Mouse mouse2(SNES::Input::DeviceIDMouse2, InputDevice::Port2, "Mouse", +config.input.mouse2.x, config.input.mouse2.y, config.input.mouse2.left, config.input.mouse2.right); + +SuperScope superscope(SNES::Input::DeviceIDSuperScope, InputDevice::Port2, "Super Scope", +config.input.superscope.x, config.input.superscope.y, +config.input.superscope.trigger, config.input.superscope.cursor, +config.input.superscope.turbo, config.input.superscope.pause); + +Justifier justifier1(SNES::Input::DeviceIDJustifier1, InputDevice::Port2, "Justifier 1", +config.input.justifier1.x, config.input.justifier1.y, +config.input.justifier1.trigger, config.input.justifier1.start); + +Justifier justifier2(SNES::Input::DeviceIDJustifier2, InputDevice::Port2, "Justifier 2", +config.input.justifier2.x, config.input.justifier2.y, +config.input.justifier2.trigger, config.input.justifier2.start); + +InputSnesDevicePool inputPool; + +InputSnesDevicePool::InputSnesDevicePool() { + attach(joypad1); + attach(joypad2); + attach(multitap1a); + attach(multitap1b); + attach(multitap1c); + attach(multitap1d); + attach(multitap2a); + attach(multitap2b); + attach(multitap2c); + attach(multitap2d); + attach(mouse1); + attach(mouse2); + attach(superscope); + attach(justifier1); + attach(justifier2); +} diff --git a/src/ui_qt/input/device.hpp b/src/ui_qt/input/device.hpp new file mode 100644 index 00000000..5ac95f74 --- /dev/null +++ b/src/ui_qt/input/device.hpp @@ -0,0 +1,73 @@ +struct InputDevice : InputGroup { + SNES::Input::DeviceID id; + enum Port { Port1, Port2 }; + const bool port; + + InputDevice(SNES::Input::DeviceID i, bool p, const char *n); +}; + +struct Joypad : InputDevice { + InputObject up, down, left, right, a, b, x, y, l, r, select, start; + + int16_t state(unsigned index) const; + Joypad(SNES::Input::DeviceID id, bool port, const char *name, + string&, string&, string&, string&, string&, string&, + string&, string&, string&, string&, string&, string&); +}; + +struct Mouse : InputDevice { + InputObject x, y, left, right; + + int16_t state(unsigned index) const; + Mouse(SNES::Input::DeviceID id, bool port, const char *name, + string&, string&, string&, string&); +}; + +struct SuperScope : InputDevice { + InputObject x, y, trigger, cursor, turbo, pause; + + int16_t state(unsigned index) const; + SuperScope(SNES::Input::DeviceID id, bool port, const char *name, + string&, string&, string&, string&, string&, string&); +}; + +struct Justifier : InputDevice { + InputObject x, y, trigger, start; + + int16_t state(unsigned index) const; + Justifier(SNES::Input::DeviceID id, bool port, const char *name, + string&, string&, string&, string&); +}; + +struct InputDevicePool : public array { + void attach(InputDevice &device); + void bind(); + void clear(); + void poll(const int16_t *table); + InputDevice* find(SNES::Input::DeviceID id); + InputDevicePool(); + +private: + array &list; +}; + +struct InputSnesDevicePool : public InputDevicePool { + InputSnesDevicePool(); +}; + +extern Joypad joypad1; +extern Joypad joypad2; +extern Joypad multitap1a; +extern Joypad multitap1b; +extern Joypad multitap1c; +extern Joypad multitap1d; +extern Joypad multitap2a; +extern Joypad multitap2b; +extern Joypad multitap2c; +extern Joypad multitap2d; +extern Mouse mouse1; +extern Mouse mouse2; +extern SuperScope superscope; +extern Justifier justifier1; +extern Justifier justifier2; +extern InputSnesDevicePool inputPool; diff --git a/src/ui_qt/input/input.cpp b/src/ui_qt/input/input.cpp new file mode 100644 index 00000000..db4d139c --- /dev/null +++ b/src/ui_qt/input/input.cpp @@ -0,0 +1,139 @@ +#include "device.cpp" +#include "userinterface.cpp" + +//============ +//InputManager +//============ + +void InputManager::bind() { + inputPool.bind(); + inputUiPool.bind(); +} + +void InputManager::poll() { + if(config.input.focusPolicy == Configuration::Input::FocusPolicyIgnoreInput + && winMain->window->isActiveWindow() == false) { + inputPool.clear(); + } else { + inputPool.poll(stateTable[activeState]); + } +} + +void InputManager::clear() { + inputPool.clear(); +} + +void InputManager::flush() { + for(unsigned i = 0; i < nall::input_limit; i++) { + stateTable[0][i] = 0; + stateTable[1][i] = 0; + } +} + +int16_t InputManager::state(uint16_t code) { + return stateTable[activeState][code]; +} + +int16_t InputManager::getStatus(unsigned deviceid, unsigned id) { + InputDevice *device = inputPool.find((SNES::Input::DeviceID)deviceid); + if(device) return device->state(id); + return 0; +} + +void InputManager::refresh() { + bool last = activeState; + activeState = !activeState; + bool next = activeState; + + input.poll(stateTable[next]); + for(unsigned i = 0; i < nall::input_limit; i++) { + //call on_input() whenever button is pressed down; ignore axes + if(!stateTable[last][i] && stateTable[next][i] && InputCode::isButton(i) && onInput) onInput(i); + } +} + +InputManager::InputManager() { + activeState = 0; + flush(); +} + +//========= +//InputCode +//========= + +InputCode::type_t InputCode::type(uint16_t code) { + if(code < keyboard::limit) return KeyboardButton; + if(code >= mouse::x && code <= mouse::z) return MouseAxis; + if(code < mouse::limit) return MouseButton; + for(unsigned i = 0; i < joypad<>::count; i++) { + unsigned index = joypad<>::index(i, joypad<>::axis); + if(code >= index && code < index + joypad<>::axes) return JoypadAxis; + if(code < joypad<>::index(i, joypad<>::limit)) return JoypadButton; + } + return Unknown; +} + +bool InputCode::isButton(uint16_t code) { + type_t n = type(code); + return (n == KeyboardButton || n == MouseButton || n == JoypadButton); +} + +bool InputCode::isAxis(uint16_t code) { + type_t n = type(code); + return (n == MouseAxis || n == JoypadAxis); +} + +//=========== +//InputObject +//=========== + +void InputObject::bind() { + code = nall::input_find((const char*)id); +} + +InputObject::InputObject(InputObject::type_t t, const char *n, string &s) : parent(0), type(t), name(n), id(s) { +} + +//========== +//InputGroup +//========== + +void InputGroup::attach(InputObject &object) { + list.add(&object); + object.parent = this; +} + +void InputGroup::bind() { + for(unsigned i = 0; i < list.size(); i++) list[i]->bind(); +} + +void InputGroup::clear() { + for(unsigned i = 0; i < list.size(); i++) list[i]->state = 0; +} + +void InputGroup::poll(const int16_t *table) { + for(unsigned i = 0; i < list.size(); i++) { + InputCode::type_t type = InputCode::type(list[i]->code); + + if(type == InputCode::MouseAxis && !input.acquired()) { + //mouse must be acquired (locked to window) to move axes + list[i]->state = 0; + } else if(type == InputCode::MouseButton && !input.acquired()) { + //same for buttons + list[i]->state = 0; + } else if(type == InputCode::JoypadAxis) { + //joypad axis range = -32768 to +32767, scale to -8 to +7 to roughly match mouse delta + list[i]->state = table[list[i]->code] / 4096; + } else { + list[i]->state = table[list[i]->code]; + } + } +} + +int16_t InputGroup::state(unsigned index) const { + if(index < list.size()) return list[index]->state; + return 0; +} + +InputGroup::InputGroup(const char *n) : list(*this), name(n) { +} diff --git a/src/ui_qt/input/input.hpp b/src/ui_qt/input/input.hpp new file mode 100644 index 00000000..203058f6 --- /dev/null +++ b/src/ui_qt/input/input.hpp @@ -0,0 +1,65 @@ +class InputManager { +public: + void bind(); + void poll(); + void clear(); + void flush(); + + int16_t state(uint16_t code); + int16_t getStatus(unsigned deviceid, unsigned id); + + void refresh(); + function onInput; + + InputManager(); + +private: + bool activeState; + int16_t stateTable[2][nall::input_limit]; +} inputManager; + +struct InputCode { + enum type_t { + KeyboardButton, + MouseAxis, + MouseButton, + JoypadAxis, + JoypadButton, + Unknown, + }; + + static type_t type(uint16_t code); + static bool isButton(uint16_t code); + static bool isAxis(uint16_t code); +}; + +struct InputGroup; + +struct InputObject { + InputGroup *parent; + enum type_t { Button, Axis } type; + const char *name; + string &id; + uint16_t code; + int16_t state; + + void bind(); + InputObject(type_t t, const char *n, string &s); +}; + +struct InputGroup : public array { + const char *name; + + void attach(InputObject &object); + void bind(); + void clear(); + void poll(const int16_t *table); + virtual int16_t state(unsigned index) const; + InputGroup(const char *n); + +private: + array &list; +}; + +#include "device.hpp" +#include "userinterface.hpp" diff --git a/src/ui_qt/input/userinterface.cpp b/src/ui_qt/input/userinterface.cpp new file mode 100644 index 00000000..5740a4eb --- /dev/null +++ b/src/ui_qt/input/userinterface.cpp @@ -0,0 +1,56 @@ +//============== +//InputGroupPool +//============== + +void InputGroupPool::attach(InputGroup &group) { + list.add(&group); +} + +void InputGroupPool::bind() { + for(unsigned i = 0; i < list.size(); i++) list[i]->bind(); +} + +void InputGroupPool::clear() { + for(unsigned i = 0; i < list.size(); i++) list[i]->clear(); +} + +void InputGroupPool::poll(const int16_t *table) { + for(unsigned i = 0; i < list.size(); i++) list[i]->poll(table); +} + +InputGroupPool::InputGroupPool() : list(*this) { +} + +// + +InputUiGeneral inputUiGeneral; +InputUiPool inputUiPool; + +InputUiGeneral::InputUiGeneral() : InputGroup("General"), +loadCartridge(InputObject::Button, "Load cartridge", config.input.uiGeneral.loadCartridge), +pauseEmulation(InputObject::Button, "Pause emulation", config.input.uiGeneral.pauseEmulation), +resetSystem(InputObject::Button, "Reset system", config.input.uiGeneral.resetSystem), +powerCycleSystem(InputObject::Button, "Power cycle system", config.input.uiGeneral.powerCycleSystem), +lowerSpeed(InputObject::Button, "Decrease emulation speed", config.input.uiGeneral.lowerSpeed), +raiseSpeed(InputObject::Button, "Increase emulation speed", config.input.uiGeneral.raiseSpeed), +toggleCheatSystem(InputObject::Button, "Toggle cheat system on or off", config.input.uiGeneral.toggleCheatSystem), +toggleFullscreen(InputObject::Button, "Toggle fullscreen mode", config.input.uiGeneral.toggleFullscreen), +toggleMenu(InputObject::Button, "Toggle menubar", config.input.uiGeneral.toggleMenu), +toggleStatus(InputObject::Button, "Toggle statusbar", config.input.uiGeneral.toggleStatus), +exitEmulator(InputObject::Button, "Exit emulator", config.input.uiGeneral.exitEmulator) { + attach(loadCartridge); + attach(pauseEmulation); + attach(resetSystem); + attach(powerCycleSystem); + attach(lowerSpeed); + attach(raiseSpeed); + attach(toggleCheatSystem); + attach(toggleFullscreen); + attach(toggleMenu); + attach(toggleStatus); + attach(exitEmulator); +} + +InputUiPool::InputUiPool() { + attach(inputUiGeneral); +} diff --git a/src/ui_qt/input/userinterface.hpp b/src/ui_qt/input/userinterface.hpp new file mode 100644 index 00000000..328a9e2b --- /dev/null +++ b/src/ui_qt/input/userinterface.hpp @@ -0,0 +1,33 @@ +struct InputUiGeneral : public InputGroup { + InputObject loadCartridge; + InputObject pauseEmulation; + InputObject resetSystem; + InputObject powerCycleSystem; + InputObject lowerSpeed; + InputObject raiseSpeed; + InputObject toggleCheatSystem; + InputObject toggleFullscreen; + InputObject toggleMenu; + InputObject toggleStatus; + InputObject exitEmulator; + + InputUiGeneral(); +}; + +struct InputGroupPool : public array { + void attach(InputGroup &group); + void bind(); + void clear(); + void poll(const int16_t *table); + InputGroupPool(); + +private: + array &list; +}; + +struct InputUiPool : public InputGroupPool { + InputUiPool(); +}; + +extern InputUiGeneral inputUiGeneral; +extern InputUiPool inputUiPool; diff --git a/src/ui_qt/interface.cpp b/src/ui_qt/interface.cpp new file mode 100644 index 00000000..4a343eee --- /dev/null +++ b/src/ui_qt/interface.cpp @@ -0,0 +1,31 @@ +SNESInterface snesinterface; + +void SNESInterface::video_refresh(uint16_t *data, unsigned pitch, unsigned *line, unsigned width, unsigned height) { + uint32_t *output; + unsigned outpitch; + if(video.lock(output, outpitch) == true) { + unsigned outwidth, outheight; + libfilter::filter.render(output, outpitch, outwidth, outheight, data, pitch, line, width, height); + video.unlock(); + video.refresh(outwidth, outheight); + } +} + +void SNESInterface::audio_sample(uint16_t left, uint16_t right) { + if(config.audio.mute) left = right = 0; + audio.sample(left, right); +} + +void SNESInterface::input_poll() { + inputManager.poll(); +} + +int16_t SNESInterface::input_poll(unsigned deviceid, unsigned id) { + return inputManager.getStatus(deviceid, id); +} + +void SNESInterface::init() { +} + +void SNESInterface::term() { +} diff --git a/src/ui_qt/main.cpp b/src/ui_qt/main.cpp new file mode 100644 index 00000000..168ed900 --- /dev/null +++ b/src/ui_qt/main.cpp @@ -0,0 +1,158 @@ +#include "main.hpp" +#include "resource/resource.rcc" + +class utf8 : public nall::string { +public: + utf8& operator<<(const string &s) { string::operator<<(s); return *this; } + utf8& operator<<(const char *s) { string::operator<<(s); return *this; } + utf8& operator<<(int n) { string::operator<<(n); return *this; } + utf8& operator<<(double d) { string::operator<<(d); return *this; } + operator const QString() const { return QString::fromUtf8(*this); } +}; + +#include "platform.cpp" +#include "config.cpp" +#include "interface.cpp" +#include "ui.cpp" + +#include "input/input.cpp" +#include "utility/utility.cpp" + +const char defaultStylesheet[] = + "QLabel.title {\n" + " background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 rgba(255, 0, 0, 48), stop: 1 rgba(0, 0, 0, 32));\n" + " font-weight: bold;\n" + " margin-bottom: 5px;\n" + " padding: 3px;\n" + "}\n" + "\n" + "#backdrop {\n" + " background: #000000;\n" + "}\n" + "\n" + "#about-window {\n" + " background: qlineargradient(x1: 0, y1: 0, x2: 0.5, y2: 1, stop: 0 #555555, stop: 1 #aaaaaa);\n" + "}\n" + ""; + +void Application::initPaths(const char *basename) { + char temp[PATH_MAX]; + + if(realpath(basename, temp)) { + //remove program name + strtr(temp, "\\", "/"); + for(signed i = strlen(temp) - 1; i >= 0; i--) { + if(temp[i] == '/') { + temp[i] = 0; + break; + } + } + + if(strend(temp, "/") == false) strcat(temp, "/"); + snes.config.path.base = temp; + } else { + snes.config.path.base = ""; + } + + if(userpath(temp)) { + strtr(temp, "\\", "/"); + if(strend(temp, "/") == false) strcat(temp, "/"); + snes.config.path.user = temp; + } else { + snes.config.path.user = ""; + } + + char cwd[PATH_MAX]; + snes.config.path.current = getcwd(cwd); +} + +void Application::locateFile(string &filename, bool createDataDirectory) { + //first, check if file exists in executable directory (single-user mode) + string temp = string() << snes.config.path.base << filename; + + if(file::exists(temp) == false) { + //if not, use user data path (multi-user mode) + temp = snes.config.path.user; + temp << ".bsnes"; + if(createDataDirectory) mkdir(temp); //ensure directory exists + temp << "/" << filename; + } + + filename = temp; +} + +int Application::main(int argc, char **argv) { + app = new QApplication(argc, argv); + #if !defined(_WIN32) + //Windows port uses 256x256 icon from resource file + app->setWindowIcon(QIcon(":/bsnes.png")); + #endif + + initargs(argc, argv); //ensure argv[]s are in UTF-8 format + initPaths(argv[0]); + locateFile(configFilename = "bsnes.cfg", true); + locateFile(styleSheetFilename = "style.qss", false); + + string customStylesheet; + if(customStylesheet.readfile(styleSheetFilename) == true) { + app->setStyleSheet((const char*)customStylesheet); + } else { + app->setStyleSheet(defaultStylesheet); + } + + config.load(configFilename); + init(); + snes.init(); + + if(argc == 2) { + //if valid file was specified on the command-line, attempt to load it now + utility.loadCartridge(argv[1]); + } + + while(terminate == false) { + processEvents(); + utility.updateSystemState(); + inputManager.refresh(); + + if(config.input.focusPolicy == Configuration::Input::FocusPolicyPauseEmulation) { + bool inactive = (winMain->window->isActiveWindow() == false); + if(!autopause && inactive) { + autopause = true; + audio.clear(); + } else if(autopause && !inactive) { + autopause = false; + } + } else { + autopause = false; + } + + if(cartridge.loaded() && !pause && !autopause) { + snes.runtoframe(); + } else { + usleep(20 * 1000); + } + } + + config.save(configFilename); + return 0; +} + +void Application::processEvents() { + app->processEvents(); +} + +Application::Application() { + terminate = false; + power = false; + pause = false; + autopause = false; +} + +Application::~Application() { + //deleting (QApplication)app will segfault the application upon exit + //delete app; +} + +int main(int argc, char **argv) { + return application.main(argc, argv); +} diff --git a/src/ui_qt/main.hpp b/src/ui_qt/main.hpp new file mode 100644 index 00000000..84052d63 --- /dev/null +++ b/src/ui_qt/main.hpp @@ -0,0 +1,54 @@ +#define UNICODE +#define QT_NO_DEBUG +#define QT_CORE_LIB +#define QT_GUI_LIB +#define QT_THREAD_SUPPORT + +#include +#include + +#include <../base.hpp> +#include <../cart/cart.hpp> + +#include +#include +#include +using namespace nall; + +#include +using namespace ruby; + +#include + +#include "input/input.hpp" +#include "utility/utility.hpp" + +class Application { +public: + QApplication *app; + + bool terminate; //set to true to terminate main() loop and exit emulator + bool power; + bool pause; + bool autopause; + + string configFilename; + string styleSheetFilename; + + int main(int argc, char **argv); + void processEvents(); + void locateFile(string &filename, bool createDataDirectory = false); + void initPaths(const char *basename); + void init(); + + Application(); + ~Application(); +} application; + +struct Style { + enum { + WindowMargin = 5, + WidgetSpacing = 5, + SeparatorSpacing = 5, + }; +}; diff --git a/src/ui_qt/platform.cpp b/src/ui_qt/platform.cpp new file mode 100644 index 00000000..05a967bb --- /dev/null +++ b/src/ui_qt/platform.cpp @@ -0,0 +1,59 @@ +//platform-specific wrappers + +#if defined(_WIN32) + //Windows 32-bit and 64-bit + #define WIN32_LEAN_AND_MEAN + #include + #include + + char* realpath(const char *filename, char *resolvedname) { + wchar_t fn[_MAX_PATH] = L""; + _wfullpath(fn, nall::utf16_t(filename), _MAX_PATH); + strcpy(resolvedname, nall::utf8_t(fn)); + return resolvedname; + } + + char* userpath(char *path) { + wchar_t fp[_MAX_PATH] = L""; + SHGetFolderPathW(0, CSIDL_APPDATA | CSIDL_FLAG_CREATE, 0, 0, fp); + strcpy(path, nall::utf8_t(fp)); + return path; + } + + char* getcwd(char *path) { + wchar_t fp[_MAX_PATH] = L""; + _wgetcwd(fp, _MAX_PATH); + strcpy(path, nall::utf8_t(fp)); + return path; + } + + int mkdir(const char *path) { + return _wmkdir(nall::utf16_t(path)); + } + + void initargs(int &argc, char **&argv) { + wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &argc); + argv = new char*[argc]; + for(unsigned i = 0; i < argc; i++) { + argv[i] = new char[_MAX_PATH]; + strcpy(argv[i], nall::utf8_t(wargv[i])); + } + } +#else + //POSIX-compatible (Linux, BSD, etc.) + char* userpath(char *path) { + *path = 0; + struct passwd *userinfo = getpwuid(getuid()); + if(userinfo) strcpy(path, userinfo->pw_dir); + return path; + } + + char *getcwd(char *path) { + return getcwd(path, PATH_MAX); + } + + #define mkdir(path) (mkdir)(path, 0755) + + void initargs(int &argc, char **&argv) { + } +#endif diff --git a/src/ui_qt/resource/resource.qrc b/src/ui_qt/resource/resource.qrc new file mode 100644 index 00000000..edc7020b --- /dev/null +++ b/src/ui_qt/resource/resource.qrc @@ -0,0 +1,10 @@ + + + + ../../data/bsnes.png + ../../data/logo.png + ../../data/joypad.png + ../../data/documentation.html + ../../data/license.html + + diff --git a/src/ui_qt/resource/resource.rc b/src/ui_qt/resource/resource.rc new file mode 100644 index 00000000..63dfef44 --- /dev/null +++ b/src/ui_qt/resource/resource.rc @@ -0,0 +1,2 @@ +1 24 "data/bsnes.Manifest" +IDI_ICON1 ICON DISCARDABLE "data/bsnes.ico" diff --git a/src/ui_qt/settings/advanced.cpp b/src/ui_qt/settings/advanced.cpp new file mode 100644 index 00000000..9f6161f6 --- /dev/null +++ b/src/ui_qt/settings/advanced.cpp @@ -0,0 +1,183 @@ +void AdvancedSettingsWindow::setup() { + panel = new QWidget; + + layout = new QVBoxLayout; + layout->setMargin(0); + layout->setSpacing(0); + + title = new QLabel("Advanced Configuration Settings"); + title->setProperty("class", "title"); + layout->addWidget(title); + + driverLayout = new QGridLayout; + driverLayout->setVerticalSpacing(0); + driverLayout->setHorizontalSpacing(Style::WidgetSpacing); { + videoLabel = new QLabel("Video driver:"); + driverLayout->addWidget(videoLabel, 0, 0); + + audioLabel = new QLabel("Audio driver:"); + driverLayout->addWidget(audioLabel, 0, 1); + + inputLabel = new QLabel("Input driver:"); + driverLayout->addWidget(inputLabel, 0, 2); + + videoDriver = new QComboBox; + driverLayout->addWidget(videoDriver, 1, 0); + + audioDriver = new QComboBox; + driverLayout->addWidget(audioDriver, 1, 1); + + inputDriver = new QComboBox; + driverLayout->addWidget(inputDriver, 1, 2); + + driverInfo = new QLabel("Note: driver changes require restart to take effect."); + driverLayout->addWidget(driverInfo, 2, 0, 1, 3); + } + layout->addLayout(driverLayout); + layout->addSpacing(Style::WidgetSpacing); + + regionTitle = new QLabel("Hardware region:"); + layout->addWidget(regionTitle); + + regionLayout = new QHBoxLayout; + regionLayout->setSpacing(Style::WidgetSpacing); { + regionGroup = new QButtonGroup(panel); + + regionAuto = new QRadioButton("Auto-detect"); + regionAuto->setToolTip("Automatically select hardware region on cartridge load"); + regionGroup->addButton(regionAuto); + regionLayout->addWidget(regionAuto); + + regionNTSC = new QRadioButton("NTSC"); + regionNTSC->setToolTip("Force NTSC region (Japan, Korea, US)"); + regionGroup->addButton(regionNTSC); + regionLayout->addWidget(regionNTSC); + + regionPAL = new QRadioButton("PAL"); + regionPAL->setToolTip("Force PAL region (Europe, ...)"); + regionGroup->addButton(regionPAL); + regionLayout->addWidget(regionPAL); + } + layout->addLayout(regionLayout); + layout->addSpacing(Style::WidgetSpacing); + + portTitle = new QLabel("Expansion port device:"); + layout->addWidget(portTitle); + + portLayout = new QHBoxLayout; + portLayout->setSpacing(Style::WidgetSpacing); { + portGroup = new QButtonGroup(panel); + + portSatellaview = new QRadioButton("Satellaview"); + portGroup->addButton(portSatellaview); + portLayout->addWidget(portSatellaview); + + portNone = new QRadioButton("None"); + portGroup->addButton(portNone); + portLayout->addWidget(portNone); + + portSpacer = new QWidget; + portLayout->addWidget(portSpacer); + } + layout->addLayout(portLayout); + layout->addSpacing(Style::WidgetSpacing); + + focusTitle = new QLabel("When main window does not have focus:"); + layout->addWidget(focusTitle); + + focusLayout = new QHBoxLayout; + focusLayout->setSpacing(Style::WidgetSpacing); { + focusButtonGroup = new QButtonGroup(panel); + + focusPause = new QRadioButton("Pause emulation"); + focusPause->setToolTip("Ideal for prolonged multi-tasking"); + focusButtonGroup->addButton(focusPause); + focusLayout->addWidget(focusPause); + + focusIgnore = new QRadioButton("Ignore input"); + focusIgnore->setToolTip("Ideal for light multi-tasking when using keyboard"); + focusButtonGroup->addButton(focusIgnore); + focusLayout->addWidget(focusIgnore); + + focusAllow = new QRadioButton("Allow input"); + focusAllow->setToolTip("Ideal for light multi-tasking when using joypad(s)"); + focusButtonGroup->addButton(focusAllow); + focusLayout->addWidget(focusAllow); + } + layout->addLayout(focusLayout); + + spacer = new QWidget; + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + layout->addWidget(spacer); + + panel->setLayout(layout); + initializeUi(); + + connect(videoDriver, SIGNAL(currentIndexChanged(int)), this, SLOT(videoDriverChange(int))); + connect(audioDriver, SIGNAL(currentIndexChanged(int)), this, SLOT(audioDriverChange(int))); + connect(inputDriver, SIGNAL(currentIndexChanged(int)), this, SLOT(inputDriverChange(int))); + connect(regionAuto, SIGNAL(pressed()), this, SLOT(setRegionAuto())); + connect(regionNTSC, SIGNAL(pressed()), this, SLOT(setRegionNTSC())); + connect(regionPAL, SIGNAL(pressed()), this, SLOT(setRegionPAL())); + connect(portSatellaview, SIGNAL(pressed()), this, SLOT(setPortSatellaview())); + connect(portNone, SIGNAL(pressed()), this, SLOT(setPortNone())); + connect(focusPause, SIGNAL(pressed()), this, SLOT(pauseWithoutFocus())); + connect(focusIgnore, SIGNAL(pressed()), this, SLOT(ignoreInputWithoutFocus())); + connect(focusAllow, SIGNAL(pressed()), this, SLOT(allowInputWithoutFocus())); +} + +void AdvancedSettingsWindow::initializeUi() { + lstring part; + + part.split(";", video.driver_list()); + for(unsigned i = 0; i < part.size(); i++) { + videoDriver->addItem(utf8() << part[i]); + if(part[i] == config.system.video) videoDriver->setCurrentIndex(i); + } + + part.split(";", audio.driver_list()); + for(unsigned i = 0; i < part.size(); i++) { + audioDriver->addItem(utf8() << part[i]); + if(part[i] == config.system.audio) audioDriver->setCurrentIndex(i); + } + + part.split(";", input.driver_list()); + for(unsigned i = 0; i < part.size(); i++) { + inputDriver->addItem(utf8() << part[i]); + if(part[i] == config.system.input) inputDriver->setCurrentIndex(i); + } + + regionAuto->setChecked(snes.config.region == SNES::Autodetect); + regionNTSC->setChecked(snes.config.region == SNES::NTSC); + regionPAL->setChecked (snes.config.region == SNES::PAL); + + portSatellaview->setChecked(snes.config.expansion_port == SNES::ExpansionBSX); + portNone->setChecked (snes.config.expansion_port == SNES::ExpansionNone); + + focusPause->setChecked (config.input.focusPolicy == Configuration::Input::FocusPolicyPauseEmulation); + focusIgnore->setChecked(config.input.focusPolicy == Configuration::Input::FocusPolicyIgnoreInput); + focusAllow->setChecked (config.input.focusPolicy == Configuration::Input::FocusPolicyAllowInput); +} + +void AdvancedSettingsWindow::videoDriverChange(int index) { + if(index >= 0) config.system.video = videoDriver->itemText(index).toUtf8().data(); +} + +void AdvancedSettingsWindow::audioDriverChange(int index) { + if(index >= 0) config.system.audio = audioDriver->itemText(index).toUtf8().data(); +} + +void AdvancedSettingsWindow::inputDriverChange(int index) { + if(index >= 0) config.system.input = inputDriver->itemText(index).toUtf8().data(); +} + +void AdvancedSettingsWindow::setRegionAuto() { snes.config.region = SNES::Autodetect; } +void AdvancedSettingsWindow::setRegionNTSC() { snes.config.region = SNES::NTSC; } +void AdvancedSettingsWindow::setRegionPAL() { snes.config.region = SNES::PAL; } + +void AdvancedSettingsWindow::setPortSatellaview() { snes.config.expansion_port = SNES::ExpansionBSX; } +void AdvancedSettingsWindow::setPortNone() { snes.config.expansion_port = SNES::ExpansionNone; } + +void AdvancedSettingsWindow::pauseWithoutFocus() { config.input.focusPolicy = Configuration::Input::FocusPolicyPauseEmulation; } +void AdvancedSettingsWindow::ignoreInputWithoutFocus() { config.input.focusPolicy = Configuration::Input::FocusPolicyIgnoreInput; } +void AdvancedSettingsWindow::allowInputWithoutFocus() { config.input.focusPolicy = Configuration::Input::FocusPolicyAllowInput; } diff --git a/src/ui_qt/settings/advanced.hpp b/src/ui_qt/settings/advanced.hpp new file mode 100644 index 00000000..e2cd0948 --- /dev/null +++ b/src/ui_qt/settings/advanced.hpp @@ -0,0 +1,54 @@ +class AdvancedSettingsWindow : public QObject { + Q_OBJECT + +public: + QWidget *panel; + QVBoxLayout *layout; + QLabel *title; + QGridLayout *driverLayout; + QLabel *videoLabel; + QLabel *audioLabel; + QLabel *inputLabel; + QComboBox *videoDriver; + QComboBox *audioDriver; + QComboBox *inputDriver; + QLabel *driverInfo; + + QLabel *regionTitle; + QHBoxLayout *regionLayout; + QButtonGroup *regionGroup; + QRadioButton *regionAuto; + QRadioButton *regionNTSC; + QRadioButton *regionPAL; + + QLabel *portTitle; + QHBoxLayout *portLayout; + QButtonGroup *portGroup; + QRadioButton *portSatellaview; + QRadioButton *portNone; + QWidget *portSpacer; + + QLabel *focusTitle; + QHBoxLayout *focusLayout; + QButtonGroup *focusButtonGroup; + QRadioButton *focusPause; + QRadioButton *focusIgnore; + QRadioButton *focusAllow; + QWidget *spacer; + + void setup(); + void initializeUi(); + +public slots: + void videoDriverChange(int index); + void audioDriverChange(int index); + void inputDriverChange(int index); + void setRegionAuto(); + void setRegionNTSC(); + void setRegionPAL(); + void setPortSatellaview(); + void setPortNone(); + void pauseWithoutFocus(); + void ignoreInputWithoutFocus(); + void allowInputWithoutFocus(); +} *winAdvancedSettings; diff --git a/src/ui_qt/settings/audio.cpp b/src/ui_qt/settings/audio.cpp new file mode 100644 index 00000000..e029ffc0 --- /dev/null +++ b/src/ui_qt/settings/audio.cpp @@ -0,0 +1,129 @@ +void AudioSettingsWindow::setup() { + panel = new QWidget; + + layout = new QVBoxLayout; + layout->setMargin(0); + layout->setSpacing(0); + + title = new QLabel("Audio Settings"); + title->setProperty("class", "title"); + layout->addWidget(title); + + boxes = new QHBoxLayout; { + frequencyLabel = new QLabel("Frequency:"); + frequencyLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + boxes->addWidget(frequencyLabel); + + frequency = new QComboBox; + frequency->addItem("32000hz"); + frequency->addItem("44100hz"); + frequency->addItem("48000hz"); + frequency->addItem("96000hz"); + boxes->addWidget(frequency); + + latencyLabel = new QLabel("Latency:"); + latencyLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + boxes->addWidget(latencyLabel); + + latency = new QComboBox; + latency->addItem("20ms"); + latency->addItem("40ms"); + latency->addItem("60ms"); + latency->addItem("80ms"); + latency->addItem("100ms"); + latency->addItem("120ms"); + boxes->addWidget(latency); + } + boxes->setSpacing(Style::WidgetSpacing); + layout->addLayout(boxes); + layout->addSpacing(Style::WidgetSpacing); + + sliders = new QGridLayout; { + volumeLabel = new QLabel("Volume: 100%"); + volumeLabel->setToolTip("Warning: any volume other than 100% will result in a slight audio quality loss"); + sliders->addWidget(volumeLabel, 0, 0); + + volume = new QSlider(Qt::Horizontal); + volume->setMinimum(0); + volume->setMaximum(200); + sliders->addWidget(volume, 0, 1); + + frequencySkewLabel = new QLabel("Input frequency: 32000hz"); + frequencySkewLabel->setToolTip( + "Adjusts audio resampling rate.\n" + "When both video sync and audio sync are enabled, use this setting to fine-tune the output.\n" + "Lower the input frequency to clean audio output, eliminating crackling / popping.\n" + "Raise the input frequency to smooth video output, eliminating duplicated frames." + ); + sliders->addWidget(frequencySkewLabel, 1, 0); + + frequencySkew = new QSlider(Qt::Horizontal); + frequencySkew->setMinimum(31800); + frequencySkew->setMaximum(32200); + sliders->addWidget(frequencySkew); + } + sliders->setSpacing(Style::WidgetSpacing); + layout->addLayout(sliders); + + spacer = new QWidget; + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + layout->addWidget(spacer); + + panel->setLayout(layout); + connect(frequency, SIGNAL(currentIndexChanged(int)), this, SLOT(frequencyChange(int))); + connect(latency, SIGNAL(currentIndexChanged(int)), this, SLOT(latencyChange(int))); + connect(volume, SIGNAL(valueChanged(int)), this, SLOT(volumeAdjust(int))); + connect(frequencySkew, SIGNAL(valueChanged(int)), this, SLOT(frequencySkewAdjust(int))); + + syncUi(); +} + +void AudioSettingsWindow::syncUi() { + int n; + + n = config.audio.outputFrequency; + if(n <= 32000) frequency->setCurrentIndex(0); + else if(n <= 44100) frequency->setCurrentIndex(1); + else if(n <= 48000) frequency->setCurrentIndex(2); + else if(n <= 96000) frequency->setCurrentIndex(3); + else frequency->setCurrentIndex(0); + + n = config.audio.latency; + latency->setCurrentIndex((n - 20) / 20); + + n = config.audio.volume; + volumeLabel->setText(utf8() << "Volume: " << n << "%"); + volume->setSliderPosition(n); + + n = config.audio.inputFrequency; + frequencySkewLabel->setText(utf8() << "Input frequency: " << n << "hz"); + frequencySkew->setSliderPosition(n); +} + +void AudioSettingsWindow::frequencyChange(int value) { + switch(value) { default: + case 0: config.audio.outputFrequency = 32000; break; + case 1: config.audio.outputFrequency = 44100; break; + case 2: config.audio.outputFrequency = 48000; break; + case 3: config.audio.outputFrequency = 96000; break; + } + audio.set(Audio::Frequency, config.audio.outputFrequency); +} + +void AudioSettingsWindow::latencyChange(int value) { + value = max(0, min(5, value)); + config.audio.latency = 20 + value * 20; + audio.set(Audio::Latency, config.audio.latency); +} + +void AudioSettingsWindow::volumeAdjust(int value) { + config.audio.volume = value; + audio.set(Audio::Volume, config.audio.volume); + syncUi(); +} + +void AudioSettingsWindow::frequencySkewAdjust(int value) { + config.audio.inputFrequency = value; + utility.updateEmulationSpeed(); + syncUi(); +} diff --git a/src/ui_qt/settings/audio.hpp b/src/ui_qt/settings/audio.hpp new file mode 100644 index 00000000..f0667b41 --- /dev/null +++ b/src/ui_qt/settings/audio.hpp @@ -0,0 +1,28 @@ +class AudioSettingsWindow : public QObject { + Q_OBJECT + +public: + QWidget *panel; + QVBoxLayout *layout; + QLabel *title; + QHBoxLayout *boxes; + QLabel *frequencyLabel; + QComboBox *frequency; + QLabel *latencyLabel; + QComboBox *latency; + QGridLayout *sliders; + QLabel *volumeLabel; + QSlider *volume; + QLabel *frequencySkewLabel; + QSlider *frequencySkew; + QWidget *spacer; + + void setup(); + void syncUi(); + +public slots: + void frequencyChange(int value); + void latencyChange(int value); + void volumeAdjust(int value); + void frequencySkewAdjust(int value); +} *winAudioSettings; diff --git a/src/ui_qt/settings/cheateditor.cpp b/src/ui_qt/settings/cheateditor.cpp new file mode 100644 index 00000000..c9a93f5c --- /dev/null +++ b/src/ui_qt/settings/cheateditor.cpp @@ -0,0 +1,156 @@ +void CheatEditorWindow::setup() { + panel = new QWidget; + + layout = new QVBoxLayout; + layout->setMargin(0); + layout->setSpacing(0); + + title = new QLabel("Cheat Code Editor"); + title->setProperty("class", "title"); + layout->addWidget(title); + + list = new QTreeWidget; + list->setColumnCount(4); + list->setHeaderLabels(QStringList() << "Hidden" << "" << "Code" << "Description"); + list->sortByColumn(0, Qt::AscendingOrder); //set initial sorting, preserve user setting after reloadList() + list->hideColumn(0); //used for default sorting + hides child expansion box + layout->addWidget(list); + layout->addSpacing(Style::WidgetSpacing); + + controls = new QHBoxLayout; { + addCode = new QPushButton("Add Code ..."); + controls->addWidget(addCode); + + editCode = new QPushButton("Edit Code ..."); + controls->addWidget(editCode); + + deleteCode = new QPushButton("Delete Code"); + controls->addWidget(deleteCode); + } + controls->setSpacing(Style::WidgetSpacing); + layout->addLayout(controls); + + panel->setLayout(layout); + connect(list, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(itemChanged(QTreeWidgetItem*))); + connect(list, SIGNAL(itemActivated(QTreeWidgetItem*, int)), this, SLOT(editSelectedCode())); + connect(list, SIGNAL(itemSelectionChanged()), this, SLOT(listChanged())); + connect(addCode, SIGNAL(released()), this, SLOT(addNewCode())); + connect(editCode, SIGNAL(released()), this, SLOT(editSelectedCode())); + connect(deleteCode, SIGNAL(released()), this, SLOT(deleteSelectedCode())); + + reloadList(); +} + +void CheatEditorWindow::syncUi() { + addCode->setEnabled(cartridge.loaded()); + QList itemList = list->selectedItems(); + editCode->setEnabled(cartridge.loaded() && itemList.count() == 1); + deleteCode->setEnabled(cartridge.loaded() && itemList.count() == 1); +} + +//called when loading a new game, or after adding / deleting a code: +//synchronizes list with cheat class list +void CheatEditorWindow::reloadList() { + disconnect(list, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(itemChanged(QTreeWidgetItem*))); + + list->clear(); + list->setSortingEnabled(false); + listItem.reset(); + + if(cartridge.loaded()) { + for(unsigned i = 0; i < cheat.count(); i++) { + Cheat::cheat_t code; + cheat.get(i, code); + + //only want to show one code / description line in list + lstring lcode, ldesc; + lcode.split("+", code.code); + ldesc.split("\n", code.desc); + if(lcode.size() > 1) lcode[0] << "+" << (int)(lcode.size() - 1); + if(ldesc.size() > 1) ldesc[0] << " ..."; + + QTreeWidgetItem *item = new QTreeWidgetItem(list); + item->setText(0, utf8() << (int)(1000000 + i)); + item->setCheckState(1, code.enabled ? Qt::Checked : Qt::Unchecked); + item->setText(2, utf8() << lcode[0]); + item->setText(3, utf8() << ldesc[0]); + listItem.add(item); + } + } + + list->setSortingEnabled(true); + list->resizeColumnToContents(1); //shrink checkbox column width + list->resizeColumnToContents(2); //shrink code column width + connect(list, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(itemChanged(QTreeWidgetItem*))); + syncUi(); +} + +//called when modifying an existing code: +//list items are all still valid, only need to update item codes + descriptions +void CheatEditorWindow::updateList() { + disconnect(list, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(itemChanged(QTreeWidgetItem*))); + + for(unsigned i = 0; i < listItem.size(); i++) { + Cheat::cheat_t code; + cheat.get(i, code); + + //only want to show one code / description line in list + lstring lcode, ldesc; + lcode.split("+", code.code); + ldesc.split("\n", code.desc); + if(lcode.size() > 1) lcode[0] << "+" << (int)(lcode.size() - 1); + if(ldesc.size() > 1) ldesc[0] << " ..."; + + QTreeWidgetItem *item = listItem[i]; + item->setCheckState(1, code.enabled ? Qt::Checked : Qt::Unchecked); + item->setText(2, utf8() << lcode[0]); + item->setText(3, utf8() << ldesc[0]); + } + + list->resizeColumnToContents(1); //shrink checkbox column width + list->resizeColumnToContents(2); //shrink code column width + connect(list, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(itemChanged(QTreeWidgetItem*))); + syncUi(); +} + +//called when item enabled checkbox was clicked (eg cheat code enable state was toggled on or off) +void CheatEditorWindow::itemChanged(QTreeWidgetItem *item) { + signed i = listItem.find(item); + if(i >= 0) item->checkState(1) == Qt::Checked ? cheat.enable(i) : cheat.disable(i); +} + +void CheatEditorWindow::listChanged() { + syncUi(); +} + +void CheatEditorWindow::addNewCode() { + if(cartridge.loaded()) winCodeEditor->addCode(); +} + +void CheatEditorWindow::editSelectedCode() { + if(cartridge.loaded()) { + QTreeWidgetItem *item = list->currentItem(); + if(item && item->isSelected()) { + signed i = listItem.find(item); + if(i >= 0) winCodeEditor->editCode(i); + } + } +} + +void CheatEditorWindow::deleteSelectedCode() { + if(cartridge.loaded()) { + QTreeWidgetItem *item = list->currentItem(); + if(item && item->isSelected()) { + signed i = listItem.find(item); + if(i >= 0) { + //if code editor is modifying an existing code, its index will be invalidated by delete; + //therefore, the editor window must be closed first + if(winCodeEditor->activeCode >= 0) winCodeEditor->dismiss(); + + //remove code, and resync listItem with cheat list + cheat.remove(i); + reloadList(); + } + } + } +} diff --git a/src/ui_qt/settings/cheateditor.hpp b/src/ui_qt/settings/cheateditor.hpp new file mode 100644 index 00000000..053f50c0 --- /dev/null +++ b/src/ui_qt/settings/cheateditor.hpp @@ -0,0 +1,28 @@ +class CheatEditorWindow : public QObject { + Q_OBJECT + +public: + QWidget *panel; + QVBoxLayout *layout; + QLabel *title; + QTreeWidget *list; + QHBoxLayout *controls; + QPushButton *addCode; + QPushButton *editCode; + QPushButton *deleteCode; + + void setup(); + void syncUi(); + void reloadList(); + void updateList(); + +public slots: + void itemChanged(QTreeWidgetItem *item); + void listChanged(); + void addNewCode(); + void editSelectedCode(); + void deleteSelectedCode(); + +private: + array listItem; +} *winCheatEditor; diff --git a/src/ui_qt/settings/input.cpp b/src/ui_qt/settings/input.cpp new file mode 100644 index 00000000..5eb2e721 --- /dev/null +++ b/src/ui_qt/settings/input.cpp @@ -0,0 +1,158 @@ +void InputSettingsWindow::setup() { + panel = new QWidget; + + layout = new QVBoxLayout; + layout->setMargin(0); + layout->setSpacing(0); + + title = new QLabel("Input Configuration Editor"); + title->setProperty("class", "title"); + layout->addWidget(title); + + selection = new QHBoxLayout; { + port = new QComboBox; + port->addItem("Controller Port 1"); + port->addItem("Controller Port 2"); + port->addItem("User Interface"); + selection->addWidget(port); + + device = new QComboBox; + selection->addWidget(device); + } + selection->setSpacing(Style::WidgetSpacing); + layout->addLayout(selection); + layout->addSpacing(Style::WidgetSpacing); + + list = new QTreeWidget; + list->setColumnCount(3); + list->setHeaderLabels(QStringList() << "Hidden" << "Name" << "Assignment"); + list->hideColumn(0); //used for default sorting + hides child expansion box + layout->addWidget(list); + layout->addSpacing(Style::WidgetSpacing); + + controls = new QHBoxLayout; { + assign = new QPushButton("Assign Key ..."); + controls->addWidget(assign); + + unassign = new QPushButton("Unassign Key"); + controls->addWidget(unassign); + } + controls->setSpacing(Style::WidgetSpacing); + layout->addLayout(controls); + + panel->setLayout(layout); + connect(port, SIGNAL(currentIndexChanged(int)), this, SLOT(portChanged())); + connect(device, SIGNAL(currentIndexChanged(int)), this, SLOT(reloadList())); + connect(list, SIGNAL(itemActivated(QTreeWidgetItem*, int)), this, SLOT(assignKey())); + connect(list, SIGNAL(itemSelectionChanged()), this, SLOT(listChanged())); + connect(assign, SIGNAL(released()), this, SLOT(assignKey())); + connect(unassign, SIGNAL(released()), this, SLOT(unassignKey())); + + portChanged(); +} + +void InputSettingsWindow::syncUi() { + QList itemList = list->selectedItems(); + assign->setEnabled(itemList.count() == 1); + unassign->setEnabled(itemList.count() == 1); +} + +//when port combobox item is changed, device list needs to be repopulated +void InputSettingsWindow::portChanged() { + disconnect(device, SIGNAL(currentIndexChanged(int)), this, SLOT(reloadList())); + + device->clear(); + deviceItem.reset(); + + int index = port->currentIndex(); + if(index < 2) { + //this is a controller port + for(unsigned i = 0; i < inputPool.size(); i++) { + //only add devices for selected port + if(inputPool[i]->port == index) { + device->addItem(inputPool[i]->name); + deviceItem.add(inputPool[i]); + } + } + } else { + //user interface controls + for(unsigned i = 0; i < inputUiPool.size(); i++) { + device->addItem(inputUiPool[i]->name); + deviceItem.add(inputUiPool[i]); + } + } + + reloadList(); + connect(device, SIGNAL(currentIndexChanged(int)), this, SLOT(reloadList())); +} + +//when device combobox item is changed, object list needs to be repopulated +void InputSettingsWindow::reloadList() { + list->clear(); + list->setSortingEnabled(false); + listItem.reset(); + + int index = device->currentIndex(); + if(index < deviceItem.size()) { + InputGroup &group = *deviceItem[index]; + for(unsigned i = 0; i < group.size(); i++) { + QTreeWidgetItem *item = new QTreeWidgetItem(list); + item->setText(0, utf8() << (int)(1000000 + i)); + item->setText(1, group[i]->name); + item->setText(2, (const char*)group[i]->id); + listItem.add(item); + } + } + + list->setSortingEnabled(true); + list->sortByColumn(0, Qt::AscendingOrder); //set default sorting on list change, overriding user setting + list->resizeColumnToContents(1); //shrink name column + syncUi(); +} + +void InputSettingsWindow::listChanged() { + syncUi(); +} + +//InputCaptureWindow calls this after a successful key assignment change: +//need to update list of values to show new key assignment value. +void InputSettingsWindow::updateList() { + int index = device->currentIndex(); + if(index < deviceItem.size()) { + InputGroup &group = *deviceItem[index]; + + for(unsigned i = 0; i < listItem.size(); i++) { + listItem[i]->setText(2, (const char*)group[i]->id); + } + } +} + +void InputSettingsWindow::assignKey() { + int index = device->currentIndex(); + if(index < deviceItem.size()) { + InputGroup &group = *deviceItem[index]; + + QTreeWidgetItem *item = list->currentItem(); + if(item && item->isSelected()) { + signed i = listItem.find(item); + if(i >= 0) winInputCapture->activate(group[i]); + } + } +} + +void InputSettingsWindow::unassignKey() { + int index = device->currentIndex(); + if(index < deviceItem.size()) { + InputGroup &group = *deviceItem[index]; + + QTreeWidgetItem *item = list->currentItem(); + if(item && item->isSelected()) { + signed i = listItem.find(item); + if(i >= 0) { + group[i]->id = "none"; + inputManager.bind(); //update key bindings to reflect object ID change above + item->setText(2, (const char*)group[i]->id); + } + } + } +} diff --git a/src/ui_qt/settings/input.hpp b/src/ui_qt/settings/input.hpp new file mode 100644 index 00000000..810dbcd6 --- /dev/null +++ b/src/ui_qt/settings/input.hpp @@ -0,0 +1,30 @@ +class InputSettingsWindow : public QObject { + Q_OBJECT + +public: + QWidget *panel; + QVBoxLayout *layout; + QLabel *title; + QHBoxLayout *selection; + QComboBox *port; + QComboBox *device; + QTreeWidget *list; + QHBoxLayout *controls; + QPushButton *assign; + QPushButton *unassign; + + void setup(); + void syncUi(); + +public slots: + void portChanged(); + void reloadList(); + void listChanged(); + void updateList(); + void assignKey(); + void unassignKey(); + +private: + array deviceItem; + array listItem; +} *winInputSettings; diff --git a/src/ui_qt/settings/paths.cpp b/src/ui_qt/settings/paths.cpp new file mode 100644 index 00000000..345bc786 --- /dev/null +++ b/src/ui_qt/settings/paths.cpp @@ -0,0 +1,200 @@ +void PathSettingsWindow::setup() { + panel = new QWidget; + + layout = new QVBoxLayout; + layout->setMargin(0); + layout->setSpacing(0); + + title = new QLabel("Default File Paths"); + title->setProperty("class", "title"); + layout->addWidget(title); + + gameLabel = new QLabel("Games:"); + layout->addWidget(gameLabel); + + games = new QHBoxLayout; { + games->setMargin(0); + + gamePath = new QLineEdit; + gamePath->setReadOnly(true); + games->addWidget(gamePath); + + gameSelect = new QPushButton("Select ..."); + games->addWidget(gameSelect); + + gameDefault = new QPushButton("Default"); + games->addWidget(gameDefault); + } + games->setSpacing(Style::WidgetSpacing); + layout->addLayout(games); + layout->addSpacing(Style::WidgetSpacing); + + saveLabel = new QLabel("Save RAM:"); + layout->addWidget(saveLabel); + + saves = new QHBoxLayout; { + saves->setMargin(0); + + savePath = new QLineEdit; + savePath->setReadOnly(true); + saves->addWidget(savePath); + + saveSelect = new QPushButton("Select ..."); + saves->addWidget(saveSelect); + + saveDefault = new QPushButton("Default"); + saves->addWidget(saveDefault); + } + saves->setSpacing(Style::WidgetSpacing); + layout->addLayout(saves); + layout->addSpacing(Style::WidgetSpacing); + + patchLabel = new QLabel("UPS patches:"); + layout->addWidget(patchLabel); + + patches = new QHBoxLayout; { + patches->setMargin(0); + + patchPath = new QLineEdit; + patchPath->setReadOnly(true); + patches->addWidget(patchPath); + + patchSelect = new QPushButton("Select ..."); + patches->addWidget(patchSelect); + + patchDefault = new QPushButton("Default"); + patches->addWidget(patchDefault); + } + patches->setSpacing(Style::WidgetSpacing); + layout->addLayout(patches); + layout->addSpacing(Style::WidgetSpacing); + + cheatLabel = new QLabel("Cheat codes:"); + layout->addWidget(cheatLabel); + + cheats = new QHBoxLayout; { + cheats->setMargin(0); + + cheatPath = new QLineEdit; + cheatPath->setReadOnly(true); + cheats->addWidget(cheatPath); + + cheatSelect = new QPushButton("Select ..."); + cheats->addWidget(cheatSelect); + + cheatDefault = new QPushButton("Default"); + cheats->addWidget(cheatDefault); + } + cheats->setSpacing(Style::WidgetSpacing); + layout->addLayout(cheats); + layout->addSpacing(Style::WidgetSpacing); + + dataLabel = new QLabel("Export data:"); + layout->addWidget(dataLabel); + + data = new QHBoxLayout; + data->setMargin(0); + data->setSpacing(Style::WidgetSpacing); { + dataPath = new QLineEdit; + dataPath->setReadOnly(true); + data->addWidget(dataPath); + + dataSelect = new QPushButton("Select ..."); + data->addWidget(dataSelect); + + dataDefault = new QPushButton("Default"); + data->addWidget(dataDefault); + } + layout->addLayout(data); + + spacer = new QWidget; + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + layout->addWidget(spacer); + + syncUi(); + + panel->setLayout(layout); + connect(gameSelect, SIGNAL(released()), this, SLOT(selectGamePath())); + connect(gameDefault, SIGNAL(released()), this, SLOT(defaultGamePath())); + connect(saveSelect, SIGNAL(released()), this, SLOT(selectSavePath())); + connect(saveDefault, SIGNAL(released()), this, SLOT(defaultSavePath())); + connect(patchSelect, SIGNAL(released()), this, SLOT(selectPatchPath())); + connect(patchDefault, SIGNAL(released()), this, SLOT(defaultPatchPath())); + connect(cheatSelect, SIGNAL(released()), this, SLOT(selectCheatPath())); + connect(cheatDefault, SIGNAL(released()), this, SLOT(defaultCheatPath())); + connect(dataSelect, SIGNAL(released()), this, SLOT(selectDataPath())); + connect(dataDefault, SIGNAL(released()), this, SLOT(defaultDataPath())); +} + +void PathSettingsWindow::syncUi() { + gamePath->setText (snes.config.path.rom == "" ? "" : (const char*)snes.config.path.rom); + savePath->setText (snes.config.path.save == "" ? "" : (const char*)snes.config.path.save); + patchPath->setText(snes.config.path.patch == "" ? "" : (const char*)snes.config.path.patch); + cheatPath->setText(snes.config.path.cheat == "" ? "" : (const char*)snes.config.path.cheat); + dataPath->setText (snes.config.path.data == "" ? "" : (const char*)snes.config.path.data); +} + +void PathSettingsWindow::selectGamePath() { + string path = utility.selectFolder("Default Game Path"); + if(path.length() > 0) { + snes.config.path.rom = path; + syncUi(); + } +} + +void PathSettingsWindow::defaultGamePath() { + snes.config.path.rom = ""; + syncUi(); +} + +void PathSettingsWindow::selectSavePath() { + string path = utility.selectFolder("Default Save RAM Path"); + if(path.length() > 0) { + snes.config.path.save = path; + syncUi(); + } +} + +void PathSettingsWindow::defaultSavePath() { + snes.config.path.save = ""; + syncUi(); +} + +void PathSettingsWindow::selectPatchPath() { + string path = utility.selectFolder("Default UPS Patch Path"); + if(path.length() > 0) { + snes.config.path.patch = path; + syncUi(); + } +} + +void PathSettingsWindow::defaultPatchPath() { + snes.config.path.patch = ""; + syncUi(); +} + +void PathSettingsWindow::selectCheatPath() { + string path = utility.selectFolder("Default Cheat File Path"); + if(path.length() > 0) { + snes.config.path.cheat = path; + syncUi(); + } +} + +void PathSettingsWindow::defaultCheatPath() { + snes.config.path.cheat = ""; + syncUi(); +} + +void PathSettingsWindow::selectDataPath() { + string path = utility.selectFolder("Default Export Data Path"); + if(path.length() > 0) { + snes.config.path.data = path; + syncUi(); + } +} + +void PathSettingsWindow::defaultDataPath() { + snes.config.path.data = ""; + syncUi(); +} diff --git a/src/ui_qt/settings/paths.hpp b/src/ui_qt/settings/paths.hpp new file mode 100644 index 00000000..a0ca502f --- /dev/null +++ b/src/ui_qt/settings/paths.hpp @@ -0,0 +1,49 @@ +class PathSettingsWindow : public QObject { + Q_OBJECT + +public: + QWidget *panel; + QVBoxLayout *layout; + QLabel *title; + QLabel *gameLabel; + QHBoxLayout *games; + QLineEdit *gamePath; + QPushButton *gameSelect; + QPushButton *gameDefault; + QLabel *saveLabel; + QHBoxLayout *saves; + QLineEdit *savePath; + QPushButton *saveSelect; + QPushButton *saveDefault; + QLabel *patchLabel; + QHBoxLayout *patches; + QLineEdit *patchPath; + QPushButton *patchSelect; + QPushButton *patchDefault; + QLabel *cheatLabel; + QHBoxLayout *cheats; + QLineEdit *cheatPath; + QPushButton *cheatSelect; + QPushButton *cheatDefault; + QLabel *dataLabel; + QHBoxLayout *data; + QLineEdit *dataPath; + QPushButton *dataSelect; + QPushButton *dataDefault; + QWidget *spacer; + + void setup(); + void syncUi(); + +public slots: + void selectGamePath(); + void defaultGamePath(); + void selectSavePath(); + void defaultSavePath(); + void selectPatchPath(); + void defaultPatchPath(); + void selectCheatPath(); + void defaultCheatPath(); + void selectDataPath(); + void defaultDataPath(); +} *winPathSettings; diff --git a/src/ui_qt/settings/settings.cpp b/src/ui_qt/settings/settings.cpp new file mode 100644 index 00000000..921aa6a3 --- /dev/null +++ b/src/ui_qt/settings/settings.cpp @@ -0,0 +1,98 @@ +#include "video.cpp" +#include "audio.cpp" +#include "input.cpp" +#include "paths.cpp" +#include "cheateditor.cpp" +#include "advanced.cpp" + +#include "utility/inputcapture.cpp" +#include "utility/codeeditor.cpp" + +void SettingsWindow::setup() { + window = new QWidget; + window->setObjectName("settings-window"); + window->setWindowTitle("Configuration Settings"); + + list = new QListWidget; + video = new QListWidgetItem("Video"); + audio = new QListWidgetItem("Audio"); + input = new QListWidgetItem("Input"); + paths = new QListWidgetItem("Paths"); + cheatcodes = new QListWidgetItem("Cheat Codes"); + advanced = new QListWidgetItem("Advanced"); + list->addItem(video); + list->addItem(audio); + list->addItem(input); + list->addItem(paths); + list->addItem(cheatcodes); + list->addItem(advanced); + list->setCurrentItem(input); //select most frequently used panel by default + list->setFixedWidth(135); + list->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + + panel = new QWidget; + panel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + layout = new QHBoxLayout; + layout->setMargin(Style::WindowMargin); + layout->setSpacing(Style::WidgetSpacing); + layout->addWidget(list); + layout->addWidget(panel); + window->setLayout(layout); + + winVideoSettings = new VideoSettingsWindow; + winAudioSettings = new AudioSettingsWindow; + winInputSettings = new InputSettingsWindow; + winPathSettings = new PathSettingsWindow; + winCheatEditor = new CheatEditorWindow; + winAdvancedSettings = new AdvancedSettingsWindow; + winInputCapture = new InputCaptureWindow; + winCodeEditor = new CodeEditorWindow; + + winVideoSettings->setup(); + winAudioSettings->setup(); + winInputSettings->setup(); + winPathSettings->setup(); + winCheatEditor->setup(); + winAdvancedSettings->setup(); + winInputCapture->setup(); + winCodeEditor->setup(); + + panelLayout = new QStackedLayout(panel); + panelLayout->addWidget(winVideoSettings->panel); + panelLayout->addWidget(winAudioSettings->panel); + panelLayout->addWidget(winInputSettings->panel); + panelLayout->addWidget(winPathSettings->panel); + panelLayout->addWidget(winCheatEditor->panel); + panelLayout->addWidget(winAdvancedSettings->panel); + panel->setLayout(panelLayout); + + connect(list, SIGNAL(currentRowChanged(int)), this, SLOT(listChanged())); + + listChanged(); + window->resize(600, 360); +} + +void SettingsWindow::show() { + window->show(); + + static bool firstShow = true; + if(firstShow == true) { + firstShow = false; + utility.centerWindow(window); + } + + application.processEvents(); + window->activateWindow(); + window->raise(); +} + +void SettingsWindow::listChanged() { + QListWidgetItem *item = list->currentItem(); + if(item == video) panelLayout->setCurrentWidget(winVideoSettings->panel); + if(item == audio) panelLayout->setCurrentWidget(winAudioSettings->panel); + if(item == input) panelLayout->setCurrentWidget(winInputSettings->panel); + if(item == paths) panelLayout->setCurrentWidget(winPathSettings->panel); + if(item == cheatcodes) panelLayout->setCurrentWidget(winCheatEditor->panel); + if(item == advanced) panelLayout->setCurrentWidget(winAdvancedSettings->panel); +} diff --git a/src/ui_qt/settings/settings.hpp b/src/ui_qt/settings/settings.hpp new file mode 100644 index 00000000..efa5ce24 --- /dev/null +++ b/src/ui_qt/settings/settings.hpp @@ -0,0 +1,32 @@ +#include "video.moc" +#include "audio.moc" +#include "input.moc" +#include "paths.moc" +#include "cheateditor.moc" +#include "advanced.moc" + +#include "utility/inputcapture.moc" +#include "utility/codeeditor.moc" + +class SettingsWindow : public QObject { + Q_OBJECT + +public: + QWidget *window; + QHBoxLayout *layout; + QListWidget *list; + QListWidgetItem *video; + QListWidgetItem *audio; + QListWidgetItem *input; + QListWidgetItem *paths; + QListWidgetItem *cheatcodes; + QListWidgetItem *advanced; + QWidget *panel; + QStackedLayout *panelLayout; + + void setup(); + void show(); + +public slots: + void listChanged(); +} *winSettings; diff --git a/src/ui_qt/settings/utility/codeeditor.cpp b/src/ui_qt/settings/utility/codeeditor.cpp new file mode 100644 index 00000000..f5d654cb --- /dev/null +++ b/src/ui_qt/settings/utility/codeeditor.cpp @@ -0,0 +1,190 @@ +void CodeEditorWindow::setup() { + window = new QWidget; + window->setObjectName("code-editor-window"); + + layout = new QVBoxLayout; + layout->setMargin(Style::WindowMargin); + layout->setSpacing(0); + + descLabel = new QLabel("Description:"); + layout->addWidget(descLabel); + + description = new QTextEdit; + layout->addWidget(description); + layout->addSpacing(Style::WidgetSpacing); + + codeLabel = new QLabel("Cheat code(s):"); + layout->addWidget(codeLabel); + + codeLayout = new QHBoxLayout; { + codeLayout->setMargin(0); + + codeList = new QListWidget; + codeLayout->addWidget(codeList); + codeLayout->addSpacing(Style::WidgetSpacing); + + controls = new QVBoxLayout; { + controls->setMargin(0); + + codeValue = new QLineEdit; + controls->addWidget(codeValue); + + codeAdd = new QPushButton("Add Code"); + controls->addWidget(codeAdd); + + codeDelete = new QPushButton("Delete Code"); + controls->addWidget(codeDelete); + + codeDeleteAll = new QPushButton("Delete All"); + controls->addWidget(codeDeleteAll); + + spacer = new QWidget; + spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + controls->addWidget(spacer); + } + codeLayout->addLayout(controls); + } + layout->addLayout(codeLayout); + layout->addSpacing(Style::WidgetSpacing); + + enabled = new QCheckBox("Enable this cheat code"); + layout->addWidget(enabled); + + finishControls = new QHBoxLayout; { + okButton = new QPushButton("Ok"); + finishControls->addWidget(okButton); + + cancelButton = new QPushButton("Cancel"); + finishControls->addWidget(cancelButton); + } + finishControls->setSpacing(Style::WidgetSpacing); + layout->addLayout(finishControls); + + window->setLayout(layout); + window->resize(400, 375); + + connect(codeList, SIGNAL(itemSelectionChanged()), this, SLOT(listChanged())); + connect(codeValue, SIGNAL(textChanged(const QString&)), this, SLOT(codeChanged())); + connect(codeAdd, SIGNAL(released()), this, SLOT(addCodeToList())); + connect(codeDelete, SIGNAL(released()), this, SLOT(deleteCodeFromList())); + connect(codeDeleteAll, SIGNAL(released()), this, SLOT(deleteAllCodesFromList())); + connect(okButton, SIGNAL(released()), this, SLOT(accept())); + connect(cancelButton, SIGNAL(released()), this, SLOT(dismiss())); +} + +void CodeEditorWindow::syncUi() { + //only activate add button when code is valid + string code = codeValue->text().toUtf8().data(); + Cheat::cheat_t temp; + bool valid = cheat.decode(code, temp); + codeAdd->setEnabled(valid); + + //only activate delete button when a code is selected + QListWidgetItem *item = codeList->currentItem(); + codeDelete->setEnabled(item && item->isSelected()); + + //only activate delete all / ok buttons when there are one or more codes entered + codeDeleteAll->setEnabled(codeList->count() > 0); + okButton->setEnabled(codeList->count() > 0); +} + +void CodeEditorWindow::listChanged() { syncUi(); } +void CodeEditorWindow::codeChanged() { syncUi(); } + +void CodeEditorWindow::addCodeToList() { + string code = codeValue->text().toUtf8().data(); + Cheat::cheat_t temp; + if(cheat.decode(code, temp) == true) codeList->addItem(utf8() << code); + syncUi(); +} + +void CodeEditorWindow::deleteCodeFromList() { + int index = codeList->currentRow(); + if(index >= 0) { + QListWidgetItem *item = codeList->takeItem(index); + delete item; + } + syncUi(); +} + +void CodeEditorWindow::deleteAllCodesFromList() { + codeList->clear(); + syncUi(); +} + +void CodeEditorWindow::accept() { + string desc = description->toPlainText().toUtf8().data(); + string code; + for(unsigned i = 0; i < codeList->count(); i++) { + code << (codeList->item(i)->text().toUtf8().data()); + if(i != codeList->count() - 1) code << "+"; + } + + if(activeCode == -1) { + //adding a new code + cheat.add(enabled->isChecked(), code, desc); + winCheatEditor->reloadList(); + } else if(codeList->count() > 0) { + //editing an existing code + cheat.edit(activeCode, enabled->isChecked(), code, desc); + winCheatEditor->updateList(); + } else { + //deleting an existing code + cheat.remove(activeCode); + winCheatEditor->reloadList(); + } + + dismiss(); +} + +void CodeEditorWindow::dismiss() { + activeCode = -1; + window->hide(); +} + +void CodeEditorWindow::addCode() { + activeCode = -1; + description->setPlainText(""); + codeList->clear(); + codeValue->setText(""); + enabled->setCheckState(Qt::Unchecked); + showWindow("Add new cheat code"); +} + +void CodeEditorWindow::editCode(unsigned code) { + activeCode = code; + codeList->clear(); + codeValue->setText(""); + + Cheat::cheat_t item; + cheat.get(activeCode, item); + + description->setPlainText(utf8() << item.desc); + + lstring part; + part.split("+", item.code); + + for(unsigned i = 0; i < item.count; i++) codeList->addItem(utf8() << part[i]); + enabled->setCheckState(item.enabled ? Qt::Checked : Qt::Unchecked); + showWindow("Edit existing cheat code"); +} + +void CodeEditorWindow::showWindow(const char *title) { + syncUi(); + window->setWindowTitle(title); + window->show(); + + static bool firstShow = true; + if(firstShow == true) { + firstShow = false; + utility.centerWindow(window); + } + + application.processEvents(); + window->activateWindow(); + window->raise(); +} + +CodeEditorWindow::CodeEditorWindow() { + activeCode = -1; +} diff --git a/src/ui_qt/settings/utility/codeeditor.hpp b/src/ui_qt/settings/utility/codeeditor.hpp new file mode 100644 index 00000000..dc194666 --- /dev/null +++ b/src/ui_qt/settings/utility/codeeditor.hpp @@ -0,0 +1,43 @@ +class CodeEditorWindow : public QObject { + Q_OBJECT + +public: + QWidget *window; + QVBoxLayout *layout; + QLabel *descLabel; + QTextEdit *description; + QLabel *codeLabel; + QHBoxLayout *codeLayout; + QListWidget *codeList; + QVBoxLayout *controls; + QLineEdit *codeValue; + QPushButton *codeAdd; + QPushButton *codeDelete; + QPushButton *codeDeleteAll; + QWidget *spacer; + QCheckBox *enabled; + QHBoxLayout *finishControls; + QPushButton *okButton; + QPushButton *cancelButton; + + void setup(); + void syncUi(); + void addCode(); + void editCode(unsigned code); + CodeEditorWindow(); + +public slots: + void listChanged(); + void codeChanged(); + void addCodeToList(); + void deleteCodeFromList(); + void deleteAllCodesFromList(); + void accept(); + void dismiss(); + +private: + signed activeCode; + void showWindow(const char *title); + + friend class CheatEditorWindow; +} *winCodeEditor; diff --git a/src/ui_qt/settings/utility/inputcapture.cpp b/src/ui_qt/settings/utility/inputcapture.cpp new file mode 100644 index 00000000..92810067 --- /dev/null +++ b/src/ui_qt/settings/utility/inputcapture.cpp @@ -0,0 +1,180 @@ +void InputCaptureWindow::setup() { + window = new Window; + window->setObjectName("input-capture-window"); + window->setWindowTitle("Input Capture"); + + layout = new QVBoxLayout; + layout->setMargin(Style::WindowMargin); + layout->setSpacing(0); + + title = new QLabel; + layout->addWidget(title); + + axisGroup = new QGroupBox("Mouse axes"); { + axisLayout = new QHBoxLayout; + axisLayout->setSpacing(Style::WidgetSpacing); { + mouseAxisX = new QPushButton("X-axis"); + axisLayout->addWidget(mouseAxisX); + + mouseAxisY = new QPushButton("Y-axis"); + axisLayout->addWidget(mouseAxisY); + } + axisGroup->setLayout(axisLayout); + } + layout->addWidget(axisGroup); + + buttonGroup = new QGroupBox("Mouse buttons"); { + buttonLayout = new QGridLayout; + buttonLayout->setSpacing(Style::WidgetSpacing); { + mouseButton[0] = new QPushButton("Left"); + buttonLayout->addWidget(mouseButton[0], 0, 0); + + mouseButton[1] = new QPushButton("Middle"); + buttonLayout->addWidget(mouseButton[1], 0, 1); + + mouseButton[2] = new QPushButton("Right"); + buttonLayout->addWidget(mouseButton[2], 0, 2); + + mouseButton[3] = new QPushButton("Extra 1"); + buttonLayout->addWidget(mouseButton[3], 1, 0); + + mouseButton[4] = new QPushButton("Extra 2"); + buttonLayout->addWidget(mouseButton[4], 1, 1); + + mouseButton[5] = new QPushButton("Extra 3"); + buttonLayout->addWidget(mouseButton[5], 1, 2); + } + buttonGroup->setLayout(buttonLayout); + } + layout->addWidget(buttonGroup); + + imageSpacer = new QWidget; + imageSpacer->setFixedSize(Style::WidgetSpacing, Style::WidgetSpacing); + layout->addWidget(imageSpacer); + + imageWidget = new InputImage; + layout->addWidget(imageWidget, 0, Qt::AlignHCenter); + + spacer = new QWidget; + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + layout->addWidget(spacer); + + window->setLayout(layout); + connect(mouseAxisX, SIGNAL(released()), this, SLOT(assignMouseX())); + connect(mouseAxisY, SIGNAL(released()), this, SLOT(assignMouseY())); + connect(mouseButton[0], SIGNAL(released()), this, SLOT(assignMouse0())); + connect(mouseButton[1], SIGNAL(released()), this, SLOT(assignMouse1())); + connect(mouseButton[2], SIGNAL(released()), this, SLOT(assignMouse2())); + connect(mouseButton[3], SIGNAL(released()), this, SLOT(assignMouse3())); + connect(mouseButton[4], SIGNAL(released()), this, SLOT(assignMouse4())); + connect(mouseButton[5], SIGNAL(released()), this, SLOT(assignMouse5())); +} + +void InputCaptureWindow::activate(InputObject *object) { + window->hide(); + + utf8 info; + info << "ID: "; + if(object->parent) { + InputDevice *device = dynamic_cast(object->parent); + if(device) info << "Controller port " << (int)(device->port + 1) << " :: "; + else info << "User interface :: "; + info << object->parent->name << " :: "; + } + info << object->name << "
"; + + activeObject = object; + if(activeObject->type == InputObject::Button) { + axisGroup->hide(); + buttonGroup->show(); + + info << "Press any keyboard key / joypad button, or click the desired mouse button below to set assignment."; + } else /*(activeObject->type == InputObject::Axis)*/ { + axisGroup->show(); + buttonGroup->hide(); + + info << "Move any joypad axis, or click the desired mouse axis below to set assignment."; + } + + if(dynamic_cast(activeObject->parent)) { + imageSpacer->show(); + imageWidget->setFixedSize(600, 300); + imageWidget->show(); + } else { + imageSpacer->hide(); + imageWidget->hide(); + } + + title->setText(info); + window->resize(0, 0); //shrink window as much as possible (visible widgets will forcefully increase size) + window->show(); + window->resize(0, 0); + + static bool firstShow = true; + if(firstShow == true) { + firstShow = false; + utility.centerWindow(window); + } + + application.processEvents(); + window->activateWindow(); + window->raise(); +} + +void InputCaptureWindow::inputEvent(uint16_t code, bool forceAssign) { + if(!activeObject) return; + + //input polling is global, need to block mouse actions that may be UI interactions. + //custom controls on window allow mouse assignment instead. + if(forceAssign == false) { + InputCode::type_t type = InputCode::type(code); + + if(activeObject->type == InputObject::Button) { + if(type == InputCode::MouseAxis + || type == InputCode::MouseButton + || type == InputCode::JoypadAxis + ) return; + } else /*(activeObject->type == InputObject::Axis)*/ { + if(type == InputCode::KeyboardButton + || type == InputCode::MouseAxis + || type == InputCode::MouseButton + || type == InputCode::JoypadButton + ) return; + } + } + + if(window->isActiveWindow()) { + activeObject->id = nall::input_find(code); + winInputSettings->updateList(); + inputManager.bind(); + activeObject = 0; + window->hide(); + } +} + +void InputCaptureWindow::assignMouseX() { inputEvent(mouse::x, true); } +void InputCaptureWindow::assignMouseY() { inputEvent(mouse::y, true); } +void InputCaptureWindow::assignMouse0() { inputEvent(mouse::button + 0, true); } +void InputCaptureWindow::assignMouse1() { inputEvent(mouse::button + 1, true); } +void InputCaptureWindow::assignMouse2() { inputEvent(mouse::button + 2, true); } +void InputCaptureWindow::assignMouse3() { inputEvent(mouse::button + 3, true); } +void InputCaptureWindow::assignMouse4() { inputEvent(mouse::button + 4, true); } +void InputCaptureWindow::assignMouse5() { inputEvent(mouse::button + 5, true); } + +InputCaptureWindow::InputCaptureWindow() { + activeObject = 0; +} + +void InputCaptureWindow::Window::closeEvent(QCloseEvent*) { + //window closed by user, cancel key assignment + winInputCapture->activeObject = 0; +} + +void InputCaptureWindow::InputImage::paintEvent(QPaintEvent*) { + //currently, there is only an image available for the joypad. + //in the future, this routine should determine which type of + //image to draw via activeObject->parent's derived class type. + QPainter painter(this); + QPixmap pixmap(":/joypad.png"); + painter.drawPixmap(0, 0, pixmap); +} diff --git a/src/ui_qt/settings/utility/inputcapture.hpp b/src/ui_qt/settings/utility/inputcapture.hpp new file mode 100644 index 00000000..3ae607a3 --- /dev/null +++ b/src/ui_qt/settings/utility/inputcapture.hpp @@ -0,0 +1,39 @@ +class InputCaptureWindow : public QObject { + Q_OBJECT + +public: + InputObject *activeObject; + + struct Window : public QWidget { + void closeEvent(QCloseEvent*); + } *window; + QVBoxLayout *layout; + QLabel *title; + QGroupBox *axisGroup; + QHBoxLayout *axisLayout; + QPushButton *mouseAxisX; + QPushButton *mouseAxisY; + QGroupBox *buttonGroup; + QGridLayout *buttonLayout; + QPushButton *mouseButton[6]; + QWidget *imageSpacer; + struct InputImage : public QWidget { + void paintEvent(QPaintEvent*); + } *imageWidget; + QWidget *spacer; + + void setup(); + void activate(InputObject *object); + void inputEvent(uint16_t code, bool forceAssign = false); + InputCaptureWindow(); + +public slots: + void assignMouseX(); + void assignMouseY(); + void assignMouse0(); + void assignMouse1(); + void assignMouse2(); + void assignMouse3(); + void assignMouse4(); + void assignMouse5(); +} *winInputCapture; diff --git a/src/ui_qt/settings/video.cpp b/src/ui_qt/settings/video.cpp new file mode 100644 index 00000000..cb2c1492 --- /dev/null +++ b/src/ui_qt/settings/video.cpp @@ -0,0 +1,119 @@ +void VideoSettingsWindow::setup() { + panel = new QWidget; + + layout = new QVBoxLayout; + layout->setMargin(0); + layout->setSpacing(0); + + title = new QLabel("Video Settings"); + title->setProperty("class", "title"); + layout->addWidget(title); + + sliders = new QGridLayout; { + lcontrast = new QLabel("Contrast adjust: +100%"); + sliders->addWidget(lcontrast, 0, 0); + + contrast = new QSlider(Qt::Horizontal); + contrast->setMinimum(-95); + contrast->setMaximum(+95); + sliders->addWidget(contrast, 0, 1); + + lbrightness = new QLabel("Brightness adjust: +100%"); + sliders->addWidget(lbrightness, 1, 0); + + brightness = new QSlider(Qt::Horizontal); + brightness->setMinimum(-95); + brightness->setMaximum(+95); + sliders->addWidget(brightness, 1, 1); + + lgamma = new QLabel("Gamma adjust: +100%"); + sliders->addWidget(lgamma, 2, 0); + + gamma = new QSlider(Qt::Horizontal); + gamma->setMinimum(-95); + gamma->setMaximum(+95); + sliders->addWidget(gamma, 2, 1); + } + sliders->setSpacing(Style::WidgetSpacing); + layout->addLayout(sliders); + layout->addSpacing(Style::WidgetSpacing); + + options = new QHBoxLayout; { + options->setMargin(0); + + enableGammaRamp = new QCheckBox("Simulate NTSC TV gamma ramp"); + enableGammaRamp->setToolTip("Lower monitor gamma to more accurately match a CRT television"); + options->addWidget(enableGammaRamp); + + enableNtscMergeFields = new QCheckBox("Merge scan fields for NTSC filter"); + enableNtscMergeFields->setToolTip( + "NTSC filter requires 60hz w/video sync to simulate alternating field effect.\n" + "If this is not the case, this option should be enabled to prevent excessive video shimmering." + ); + options->addWidget(enableNtscMergeFields); + } + options->setSpacing(Style::WidgetSpacing); + layout->addLayout(options); + + spacer = new QWidget; + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + layout->addWidget(spacer); + + panel->setLayout(layout); + connect(contrast, SIGNAL(valueChanged(int)), this, SLOT(contrastAdjust(int))); + connect(brightness, SIGNAL(valueChanged(int)), this, SLOT(brightnessAdjust(int))); + connect(gamma, SIGNAL(valueChanged(int)), this, SLOT(gammaAdjust(int))); + connect(enableGammaRamp, SIGNAL(stateChanged(int)), this, SLOT(gammaRampToggle(int))); + connect(enableNtscMergeFields, SIGNAL(stateChanged(int)), this, SLOT(ntscFieldsToggle(int))); + + syncUi(); +} + +void VideoSettingsWindow::syncUi() { + int n; + + n = config.video.contrastAdjust; + lcontrast->setText(utf8() << "Contrast adjust: " << (n > 0 ? "+" : "") << n << "%"); + contrast->setSliderPosition(n); + + n = config.video.brightnessAdjust; + lbrightness->setText(utf8() << "Brightness adjust: " << (n > 0 ? "+" : "") << n << "%"); + brightness->setSliderPosition(n); + + n = config.video.gammaAdjust; + lgamma->setText(utf8() << "Gamma adjust: " << (n > 0 ? "+" : "") << n << "%"); + gamma->setSliderPosition(n); + + enableGammaRamp->setChecked(config.video.enableGammaRamp); + enableNtscMergeFields->setChecked(config.video.enableNtscMergeFields); +} + +void VideoSettingsWindow::gammaRampToggle(int state) { + config.video.enableGammaRamp = (state == Qt::Checked); + syncUi(); + utility.updateColorFilter(); +} + +void VideoSettingsWindow::ntscFieldsToggle(int state) { + config.video.enableNtscMergeFields = (state == Qt::Checked); + syncUi(); + utility.updateColorFilter(); +} + +void VideoSettingsWindow::contrastAdjust(int value) { + config.video.contrastAdjust = value; + syncUi(); + utility.updateColorFilter(); +} + +void VideoSettingsWindow::brightnessAdjust(int value) { + config.video.brightnessAdjust = value; + syncUi(); + utility.updateColorFilter(); +} + +void VideoSettingsWindow::gammaAdjust(int value) { + config.video.gammaAdjust = value; + syncUi(); + utility.updateColorFilter(); +} diff --git a/src/ui_qt/settings/video.hpp b/src/ui_qt/settings/video.hpp new file mode 100644 index 00000000..7c5f3cee --- /dev/null +++ b/src/ui_qt/settings/video.hpp @@ -0,0 +1,29 @@ +class VideoSettingsWindow : public QObject { + Q_OBJECT + +public: + QWidget *panel; + QVBoxLayout *layout; + QLabel *title; + QGridLayout *sliders; + QLabel *lcontrast; + QSlider *contrast; + QLabel *lbrightness; + QSlider *brightness; + QLabel *lgamma; + QSlider *gamma; + QHBoxLayout *options; + QCheckBox *enableGammaRamp; + QCheckBox *enableNtscMergeFields; + QWidget *spacer; + + void setup(); + void syncUi(); + +public slots: + void gammaRampToggle(int); + void ntscFieldsToggle(int); + void contrastAdjust(int); + void brightnessAdjust(int); + void gammaAdjust(int); +} *winVideoSettings; diff --git a/src/ui_qt/ui.cpp b/src/ui_qt/ui.cpp new file mode 100644 index 00000000..927d7072 --- /dev/null +++ b/src/ui_qt/ui.cpp @@ -0,0 +1,120 @@ +#include "base/main.moc" +#include "base/loader.moc" +#include "base/htmlviewer.moc" +#include "base/about.moc" +#include "settings/settings.moc" + +#include "base/main.cpp" +#include "base/loader.cpp" +#include "base/htmlviewer.cpp" +#include "base/about.cpp" +#include "settings/settings.cpp" + +void Application::init() { + if(config.system.crashedOnLastRun == true) { + //emulator crashed on last run, disable all drivers + QMessageBox::warning(0, "bsnes Crash Notification", utf8() << + "

Warning:
bsnes crashed while attempting to initialize device " + "drivers the last time it was run.

" + "

To prevent this from occurring again, all drivers have been disabled. Please " + "go to Settings->Configuration->Advanced and choose new driver settings, and then " + "restart the emulator for the changes to take effect. Video, audio and input " + "will not work until you do this!

" + "

Settings that caused failure on last run:
" + << "Video driver: " << config.system.video << "
" + << "Audio driver: " << config.system.audio << "
" + << "Input driver: " << config.system.input << "

" + ); + + config.system.video = "None"; + config.system.audio = "None"; + config.system.input = "None"; + } + + if(config.system.video == "") config.system.video = video.default_driver(); + if(config.system.audio == "") config.system.audio = audio.default_driver(); + if(config.system.input == "") config.system.input = input.default_driver(); + + winMain = new MainWindow; + winMain->setup(); + + winLoader = new LoaderWindow; + winLoader->setup(); + + winHtmlViewer = new HtmlViewerWindow; + winHtmlViewer->setup(); + + winAbout = new AboutWindow; + winAbout->setup(); + + //window must be onscreen and visible before initializing video interface + winMain->window->show(); + utility.updateFullscreenState(); + application.processEvents(); + + winSettings = new SettingsWindow; + winSettings->setup(); + + //if emulator crashes while initializing drivers, next run will disable them all. + //this will allow user to choose different driver settings. + config.system.crashedOnLastRun = true; + config.save(configFilename); + + video.driver(config.system.video); + video.set(Video::Handle, (uintptr_t)winMain->canvas->winId()); + if(video.init() == false) { + QMessageBox::warning(0, "bsnes", utf8() << + "

Warning: " << config.system.video << " video driver failed to initialize. " + "Video driver has been disabled.

" + "

Please go to Settings->Configuration->Advanced and choose a different driver, and " + "then restart the emulator for the changes to take effect.

" + ); + video.driver("None"); + video.init(); + } + + audio.driver(config.system.audio); + audio.set(Audio::Handle, (uintptr_t)winMain->window->winId()); + audio.set(Audio::Frequency, config.audio.outputFrequency); + audio.set(Audio::Latency, config.audio.latency); + audio.set(Audio::Volume, config.audio.volume); + if(audio.init() == false) { + QMessageBox::warning(0, "bsnes", utf8() << + "

Warning: " << config.system.audio << " audio driver failed to initialize. " + "Audio driver has been disabled.

" + "

Please go to Settings->Configuration->Advanced and choose a different driver, and " + "then restart the emulator for the changes to take effect.

" + ); + audio.driver("None"); + audio.init(); + } + + input.driver(config.system.input); + input.set(Input::Handle, (uintptr_t)winMain->window->winId()); + input.set(Input::AnalogAxisResistance, config.input.analogAxisResistance); + if(input.init() == false) { + QMessageBox::warning(0, "bsnes", utf8() << + "

Warning: " << config.system.input << " input driver failed to initialize. " + "Input driver has been disabled.

" + "

Please go to Settings->Configuration->Advanced and choose a different driver, and " + "then restart the emulator for the changes to take effect.

" + ); + input.driver("None"); + input.init(); + } + + //didn't crash, note this in the config file now in case a different kind of crash occurs later + config.system.crashedOnLastRun = false; + config.save(configFilename); + + inputManager.bind(); + inputManager.onInput = bind(&Utility::inputEvent, &utility); + + utility.updateAvSync(); + utility.updateVideoMode(); + utility.updateColorFilter(); + utility.updateHardwareFilter(); + utility.updateSoftwareFilter(); + utility.updateEmulationSpeed(); + utility.updateControllers(); +} diff --git a/src/ui_qt/utility/cartridge.cpp b/src/ui_qt/utility/cartridge.cpp new file mode 100644 index 00000000..294fcd47 --- /dev/null +++ b/src/ui_qt/utility/cartridge.cpp @@ -0,0 +1,163 @@ +string Utility::selectCartridge() { + audio.clear(); + QString filename = QFileDialog::getOpenFileName(0, + "Load Cartridge", + utf8() << (snes.config.path.rom != "" ? snes.config.path.rom : snes.config.path.current), + "SNES images (*.smc *.sfc *.swc *.fig *.bs *.st" + #if defined(GZIP_SUPPORT) + " *.zip *.gz" + #endif + #if defined(JMA_SUPPORT) + " *.jma" + #endif + ");;" + "All files (*)" + ); + return string() << filename.toUtf8().constData(); +} + +string Utility::selectFolder(const char *title) { + audio.clear(); + QString pathname = QFileDialog::getExistingDirectory(0, + title, utf8() << snes.config.path.current, + QFileDialog::ShowDirsOnly); + return string() << pathname.toUtf8().constData(); +} + +void Utility::loadCartridge(const char *filename) { + switch(cartridge.detect_image_type(filename)) { + case Cartridge::TypeNormal: loadCartridgeNormal(filename); break; + case Cartridge::TypeBsxSlotted: winLoader->loadBsxSlottedCartridge(filename, ""); break; + case Cartridge::TypeBsxBios: winLoader->loadBsxCartridge(filename, ""); break; + case Cartridge::TypeBsx: winLoader->loadBsxCartridge(snes.config.path.bsx, filename); break; + case Cartridge::TypeSufamiTurboBios: winLoader->loadSufamiTurboCartridge(filename, "", ""); break; + case Cartridge::TypeSufamiTurbo: winLoader->loadSufamiTurboCartridge(snes.config.path.st, filename, ""); break; + } +} + +bool Utility::loadCartridgeNormal(const char *base) { + if(!*base) return false; + unloadCartridge(); + cartridge.load_normal(base); + modifySystemState(LoadCartridge); + return true; +} + +bool Utility::loadCartridgeBsxSlotted(const char *base, const char *slot) { + if(!*base) return false; + unloadCartridge(); + cartridge.load_bsx_slotted(base, slot); + modifySystemState(LoadCartridge); + return true; +} + +bool Utility::loadCartridgeBsx(const char *base, const char *slot) { + if(!*base) return false; + unloadCartridge(); + cartridge.load_bsx(base, slot); + modifySystemState(LoadCartridge); + return true; +} + +bool Utility::loadCartridgeSufamiTurbo(const char *base, const char *slotA, const char *slotB) { + if(!*base) return false; + unloadCartridge(); + cartridge.load_sufami_turbo(base, slotA, slotB); + modifySystemState(LoadCartridge); + return true; +} + +void Utility::unloadCartridge() { + if(cartridge.loaded()) { + cartridge.unload(); + modifySystemState(UnloadCartridge); + } +} + +void Utility::modifySystemState(system_state_t state) { + video.clear(); + audio.clear(); + + switch(state) { + case LoadCartridge: { + //must call cartridge.load_cart_...() before calling modifySystemState(LoadCartridge) + if(cartridge.loaded() == false) break; + + application.power = true; + application.pause = false; + snes.power(); + + //warn if unsupported hardware detected + string chip; + if(cartridge.has_superfx()) chip = "SuperFX"; + else if(cartridge.has_sa1()) chip = "SA-1"; + else if(cartridge.has_st011()) chip = "ST011"; + else if(cartridge.has_st018()) chip = "ST018"; + else if(cartridge.has_dsp3()) chip = "DSP-3"; + if(chip != "") { + QMessageBox::warning(winMain->window, "Warning", utf8() + << "

Warning:
Unsupported " << chip << " chip detected. " + << "It is unlikely that this title will work properly.

"); + } + + showMessage(utf8() + << "Loaded " << cartridge.name() + << (cartridge.patched() ? ", and applied UPS patch." : ".")); + winMain->window->setWindowTitle(utf8() << BSNES_TITLE << " - " << cartridge.name()); + } break; + + case UnloadCartridge: { + if(cartridge.loaded() == false) break; //no cart to unload? + cartridge.unload(); + + application.power = false; + application.pause = true; + + showMessage(utf8() << "Unloaded " << cartridge.name() << "."); + winMain->window->setWindowTitle(utf8() << BSNES_TITLE); + } break; + + case PowerOn: { + if(cartridge.loaded() == false || application.power == true) break; + + application.power = true; + application.pause = false; + snes.power(); + + showMessage("Power on."); + } break; + + case PowerOff: { + if(cartridge.loaded() == false || application.power == false) break; + + application.power = false; + application.pause = true; + + showMessage("Power off."); + } break; + + case PowerCycle: { + if(cartridge.loaded() == false) break; + + application.power = true; + application.pause = false; + snes.power(); + + showMessage("System power was cycled."); + } break; + + case Reset: { + if(cartridge.loaded() == false || application.power == false) break; + + application.pause = false; + snes.reset(); + + showMessage("System was reset."); + } break; + } + + winMain->syncUi(); + winCodeEditor->dismiss(); + winCheatEditor->reloadList(); + winCheatEditor->syncUi(); +} diff --git a/src/ui_qt/utility/utility.cpp b/src/ui_qt/utility/utility.cpp new file mode 100644 index 00000000..dba8cc8a --- /dev/null +++ b/src/ui_qt/utility/utility.cpp @@ -0,0 +1,177 @@ +#include "cartridge.cpp" +#include "window.cpp" + +void Utility::inputEvent(uint16_t code) { + //if input capture assignment window is currently active, forward key-press event + if(winInputCapture->activeObject) winInputCapture->inputEvent(code); + + if(code == keyboard::escape && input.acquired()) { + input.unacquire(); + return; //do not trigger other UI actions that may be bound to escape key + } + + if(winMain->window->isActiveWindow()) { + bool resizeWindow = false; + + if(code == inputUiGeneral.loadCartridge.code) { + string filename = selectCartridge(); + if(filename.length() > 0) loadCartridge(filename); + } + + if(code == inputUiGeneral.pauseEmulation.code) { + application.pause = !application.pause; + } + + if(code == inputUiGeneral.resetSystem.code) { + modifySystemState(Reset); + } + + if(code == inputUiGeneral.powerCycleSystem.code) { + modifySystemState(PowerCycle); + } + + if(code == inputUiGeneral.lowerSpeed.code) { + config.system.speed--; + updateEmulationSpeed(); + winMain->syncUi(); + } + + if(code == inputUiGeneral.raiseSpeed.code) { + config.system.speed++; + updateEmulationSpeed(); + winMain->syncUi(); + } + + if(code == inputUiGeneral.toggleCheatSystem.code) { + if(cheat.enabled() == false) { + cheat.enable(); + showMessage("Cheat system enabled."); + } else { + cheat.disable(); + showMessage("Cheat system disabled."); + } + } + + if(code == inputUiGeneral.toggleFullscreen.code) { + config.video.isFullscreen = !config.video.isFullscreen; + updateFullscreenState(); + winMain->syncUi(); + } + + if(code == inputUiGeneral.toggleMenu.code) { + winMain->window->menuBar()->setVisible(!winMain->window->menuBar()->isVisibleTo(winMain->window)); + resizeWindow = true; + } + + if(code == inputUiGeneral.toggleStatus.code) { + winMain->window->statusBar()->setVisible(!winMain->window->statusBar()->isVisibleTo(winMain->window)); + resizeWindow = true; + } + + //prevent calling twice when toggleMenu == toggleStatus + if(resizeWindow == true) { + resizeMainWindow(); + } + + if(code == inputUiGeneral.exitEmulator.code) { + application.terminate = true; + } + } +} + +//display message in main window statusbar area for three seconds +void Utility::showMessage(const char *message) { + winMain->window->statusBar()->showMessage(utf8() << message, 3000); +} + +//updates system state text at bottom-right of main window statusbar +void Utility::updateSystemState() { + string text; + + if(cartridge.loaded() == false) { + text = "No cartridge loaded"; + } else if(application.power == false) { + text = "Power off"; + } else if(application.pause == true || application.autopause == true) { + text = "Paused"; + } else if(ppu.status.frames_updated == true) { + ppu.status.frames_updated = false; + text << (int)ppu.status.frames_executed; + text << " fps"; + } else { + //nothing to update + return; + } + + winMain->systemState->setText(utf8() << text); +} + +void Utility::acquireMouse() { + if(cartridge.loaded()) { + if(snes.config.controller_port1 == SNES::Input::DeviceMouse + || snes.config.controller_port2 == SNES::Input::DeviceMouse + || snes.config.controller_port2 == SNES::Input::DeviceSuperScope + || snes.config.controller_port2 == SNES::Input::DeviceJustifier + || snes.config.controller_port2 == SNES::Input::DeviceJustifiers + ) input.acquire(); + } +} + +void Utility::unacquireMouse() { + input.unacquire(); +} + +void Utility::updateAvSync() { + video.set(Video::Synchronize, config.video.synchronize); + audio.set(Audio::Synchronize, config.audio.synchronize); +} + +void Utility::updateVideoMode() { + if(config.video.context->region == 0) { + snes.video.set_mode(SNES::Video::ModeNTSC); + } else { + snes.video.set_mode(SNES::Video::ModePAL); + } +} + +void Utility::updateColorFilter() { + libfilter::colortable.set_format(libfilter::Colortable::RGB888); + libfilter::colortable.set_contrast(config.video.contrastAdjust); + libfilter::colortable.set_brightness(config.video.brightnessAdjust); + libfilter::colortable.set_gamma(100 + config.video.gammaAdjust); + libfilter::colortable.enable_gamma_ramp(config.video.enableGammaRamp); + libfilter::colortable.update(); +} + +void Utility::updateHardwareFilter() { + video.set(Video::Filter, config.video.context->hwFilter); +} + +void Utility::updateSoftwareFilter() { + libfilter::FilterInterface::FilterType type; + switch(config.video.context->swFilter) { default: + case 0: type = libfilter::FilterInterface::Direct; break; + case 1: type = libfilter::FilterInterface::Scanline; break; + case 2: type = libfilter::FilterInterface::Scale2x; break; + case 3: type = libfilter::FilterInterface::HQ2x; break; + case 4: type = libfilter::FilterInterface::NTSC; break; + } + libfilter::filter.set(type); +} + +void Utility::updateEmulationSpeed() { + config.system.speed = max(0, min(4, (signed)config.system.speed)); + + double scale[] = { 0.50, 0.75, 1.00, 1.50, 2.00 }; + unsigned outfreq = config.audio.outputFrequency; + unsigned infreq = config.audio.inputFrequency * scale[config.system.speed] + 0.5; + + audio.set(Audio::Resample, outfreq != infreq); //only resample when necessary + audio.set(Audio::ResampleOutputFrequency, outfreq); + audio.set(Audio::ResampleInputFrequency, infreq); +} + +void Utility::updateControllers() { + snes.input.port_set_device(0, snes.config.controller_port1); + snes.input.port_set_device(1, snes.config.controller_port2); +} diff --git a/src/ui_qt/utility/utility.hpp b/src/ui_qt/utility/utility.hpp new file mode 100644 index 00000000..691ecb67 --- /dev/null +++ b/src/ui_qt/utility/utility.hpp @@ -0,0 +1,36 @@ +class Utility { +public: + //utility.cpp + void inputEvent(uint16_t code); + void showMessage(const char *message); + void updateSystemState(); + void acquireMouse(); + void unacquireMouse(); + + void updateAvSync(); + void updateVideoMode(); + void updateColorFilter(); + void updateHardwareFilter(); + void updateSoftwareFilter(); + void updateEmulationSpeed(); + void updateControllers(); + + //cartridge.cpp + string selectCartridge(); + string selectFolder(const char *title); + void loadCartridge(const char*); + bool loadCartridgeNormal(const char*); + bool loadCartridgeBsxSlotted(const char*, const char*); + bool loadCartridgeBsx(const char*, const char*); + bool loadCartridgeSufamiTurbo(const char*, const char *, const char*); + void unloadCartridge(); + + enum system_state_t { LoadCartridge, UnloadCartridge, PowerOn, PowerOff, PowerCycle, Reset }; + void modifySystemState(system_state_t state); + + //window.cpp + void centerWindow(QWidget *window); + void updateFullscreenState(); + void constrainSize(unsigned &x, unsigned &y, unsigned max); + void resizeMainWindow(); +} utility; diff --git a/src/ui_qt/utility/window.cpp b/src/ui_qt/utility/window.cpp new file mode 100644 index 00000000..b11450c7 --- /dev/null +++ b/src/ui_qt/utility/window.cpp @@ -0,0 +1,127 @@ +//center specified top-level window onscreen: +//accounts for taskbar, dock, window frame, etc. +void Utility::centerWindow(QWidget *window) { + QRect deskRect = QApplication::desktop()->availableGeometry(); + unsigned deskWidth = (deskRect.right() - deskRect.left() + 1); + unsigned deskHeight = (deskRect.bottom() - deskRect.top() + 1); + + if(window->isVisible()) { + QRect windowRect = window->frameGeometry(); + unsigned windowWidth = (windowRect.right() - windowRect.left() + 1); + unsigned windowHeight = (windowRect.bottom() - windowRect.top() + 1); + + unsigned x = (deskWidth >= windowWidth ) ? (deskWidth - windowWidth ) / 2 : 0; + unsigned y = (deskHeight >= windowHeight) ? (deskHeight - windowHeight) / 2 : 0; + window->move(deskRect.left() + x, deskRect.top() + y); + } else { + unsigned x = (deskWidth >= window->size().width() ) ? (deskWidth - window->size().width() ) / 2 : 0; + unsigned y = (deskHeight >= window->size().height()) ? (deskHeight - window->size().height()) / 2 : 0; + window->setGeometry( + deskRect.left() + x, deskRect.top() + y, + window->size().width(), window->size().height() + ); + } +} + +void Utility::updateFullscreenState() { + video.clear(); + + if(config.video.isFullscreen == false) { + config.video.context = &config.video.windowed; + winMain->window->showNormal(); + } else { + config.video.context = &config.video.fullscreen; + winMain->window->showFullScreen(); + } + + //Xlib requires time to propogate fullscreen state change message to window manager; + //if window is resized before this occurs, canvas may not resize correctly + application.processEvents(); + usleep(50000); + + //refresh options that are unique to each video context + resizeMainWindow(); + updateVideoMode(); + updateHardwareFilter(); + updateSoftwareFilter(); + winMain->syncUi(); +} + +//if max exceeds x: x is set to max, and y is scaled down to keep proportion to x +void Utility::constrainSize(unsigned &x, unsigned &y, unsigned max) { + if(x > max) { + double scalar = (double)max / (double)x; + y = (unsigned)((double)y * (double)scalar); + x = max; + } +} + +void Utility::resizeMainWindow() { + //Xlib requires time to propogate window messages to window manager; + //repeat resize a few times to ensure window ends up correctly sized and centered + for(unsigned i = 0; i < 10; i++) { + unsigned multiplier = config.video.context->multiplier; + unsigned width = 256 * config.video.context->multiplier; + unsigned height = (config.video.context->region == 0 ? 224 : 239) * config.video.context->multiplier; + + if(config.video.context->correctAspectRatio) { + if(config.video.context->region == 0) { + width = (double)width * config.video.ntscAspectRatio + 0.5; //NTSC adjust + } else { + width = (double)width * config.video.palAspectRatio + 0.5; //PAL adjust + } + } + + QDesktopWidget *desktop = QApplication::desktop(); + application.processEvents(); + + if(config.video.isFullscreen == false) { + //get effective desktop work area region (ignore Windows taskbar, OS X doc, etc.) + QRect deskRect = desktop->availableGeometry(); + unsigned deskWidth = (deskRect.right() - deskRect.left() + 1); + unsigned deskHeight = (deskRect.bottom() - deskRect.top() + 1); + + //calculate frame geometry (window border + menubar + statusbar) + unsigned frameWidth, frameHeight; + if(winMain->window->isVisible()) { + QRect frameRect = winMain->window->frameGeometry(); + frameWidth = (frameRect.right() - frameRect.left() + 1) - winMain->canvasContainer->size().width(); + frameHeight = (frameRect.bottom() - frameRect.top() + 1) - winMain->canvasContainer->size().height(); + } else { + //frameGeometry() is inaccurate when window is not visible + //(especially before it is shown for the first time) + frameWidth = 10; //use reasonable defaults + frameHeight = 80; //for frame size + } + + //ensure window size will not be larger than viewable desktop area + constrainSize(height, width, deskHeight - frameHeight); + constrainSize(width, height, deskWidth - frameWidth ); + + //resize window such that it is as small as possible to hold canvas + //of size (width, height); and center resultant window onscreen + winMain->canvas->setFixedSize(width, height); + winMain->window->move( + deskRect.left() + ((deskWidth - (frameWidth + width )) / 2), + deskRect.top () + ((deskHeight - (frameHeight + height)) / 2) + ); + winMain->window->resize(width, height); + } else { + //center canvas onscreen, ensure it is not larger than viewable area + winMain->canvas->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + constrainSize(height, width, winMain->canvasContainer->size().height()); + constrainSize(width, height, winMain->canvasContainer->size().width()); + winMain->canvas->setMaximumSize(width, height); + } + + application.processEvents(); + usleep(2000); + } + + //work around for Qt/Xlib bug: + //if window resize occurs with cursor over it, Qt shows Qt::Size*DiagCursor; + //so force it to show Qt::ArrowCursor, as expected + winMain->window->setCursor(Qt::ArrowCursor); + winMain->canvasContainer->setCursor(Qt::ArrowCursor); + winMain->canvas->setCursor(Qt::ArrowCursor); +}