diff --git a/readme.txt b/readme.txt index 9749a1a7..1af72f7d 100644 --- a/readme.txt +++ b/readme.txt @@ -1,5 +1,5 @@ bsnes -Version: 0.036 +Version: 0.037 Author: byuu ======== @@ -99,18 +99,10 @@ SETA RISC CPU used by Quick-move Shogi Match with Nidan Rank-holder Morita 2 Super Gameboy Cartridge passthrough used for playing Gameboy games -========================== -Unsupported Controller(s): -========================== - -Mouse -Super Scope -Justifier - ============= Contributors: ============= Andreas Naive, anomie, blargg, DMV27, FitzRoy, GIGO, Jonas Quinn, kode54, krom, -mudlord, Nach, neviksti, Overload, RedDwarf, Richard Bannister, tetsuo55, TRAC, -zones +Matthew Callis, mudlord, Nach, neviksti, Overload, RedDwarf, Richard Bannister, +tetsuo55, TRAC, zones diff --git a/src/base.h b/src/base.h index fa653c3c..b1fa2b85 100644 --- a/src/base.h +++ b/src/base.h @@ -1,4 +1,4 @@ -#define BSNES_VERSION "0.036" +#define BSNES_VERSION "0.037" #define BSNES_TITLE "bsnes v" BSNES_VERSION #define BUSCORE sBus @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,7 @@ #include #include #include +#include #include using namespace nall; @@ -42,9 +44,3 @@ void alert(const char*, ...); void dprintf(const char*, ...); #include "interface.h" - -//helper: disable access to FILE, when possible (GZIP / JMA require it) -//reason: Windows fopen() does not support UTF-8 filenames; use nall::file instead. -#if !defined(GZIP_SUPPORT) && !defined(JMA_SUPPORT) -#define FILE FILE_deprecated -#endif diff --git a/src/cart/cart.cpp b/src/cart/cart.cpp index d50cd4f4..2acf7496 100644 --- a/src/cart/cart.cpp +++ b/src/cart/cart.cpp @@ -3,7 +3,8 @@ #include #include - + +#include "cart_load.cpp" #include "cart_normal.cpp" #include "cart_bsx.cpp" #include "cart_bsc.cpp" @@ -21,12 +22,13 @@ namespace memory { Cartridge cartridge; +const char* Cartridge::name() { return info.filename; } +Cartridge::CartridgeMode Cartridge::mode() { return info.mode; } Cartridge::MemoryMapper Cartridge::mapper() { return info.mapper; } Cartridge::Region Cartridge::region() { return info.region; } - bool Cartridge::loaded() { return cart.loaded; } -void Cartridge::load_begin(CartridgeType cart_type) { +void Cartridge::load_begin(CartridgeMode mode) { cart.rom = cart.ram = cart.rtc = 0; bs.ram = 0; stA.rom = stA.ram = 0; @@ -37,38 +39,11 @@ void Cartridge::load_begin(CartridgeType cart_type) { stA.rom_size = stA.ram_size = 0; stB.rom_size = stB.ram_size = 0; - info.type = cart_type; + info.mode = mode; info.patched = false; - info.bsxbase = false; info.bsxcart = false; info.bsxflash = false; - info.st = false; - - info.superfx = false; - info.sa1 = false; - info.srtc = false; - info.sdd1 = false; - info.spc7110 = false; - info.spc7110rtc = false; - info.cx4 = false; - info.dsp1 = false; - info.dsp2 = false; - info.dsp3 = false; - info.dsp4 = false; - info.obc1 = false; - info.st010 = false; - info.st011 = false; - info.st018 = false; - - info.dsp1_mapper = DSP1Unmapped; - - info.header_index = 0xffc0; - info.mapper = LoROM; - info.region = NTSC; - - info.rom_size = 0; - info.ram_size = 0; } void Cartridge::load_end() { @@ -103,11 +78,11 @@ bool Cartridge::unload() { bus.unload_cart(); - switch(info.type) { - case CartridgeNormal: unload_cart_normal(); break; - case CartridgeBSX: unload_cart_bsx(); break; - case CartridgeBSC: unload_cart_bsc(); break; - case CartridgeSufamiTurbo: unload_cart_st(); break; + 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; } if(cart.rom) { delete[] cart.rom; cart.rom = 0; } @@ -119,9 +94,6 @@ bool Cartridge::unload() { if(stB.rom) { delete[] stB.rom; stB.rom = 0; } if(stB.ram) { delete[] stB.ram; stB.ram = 0; } - char fn[PATH_MAX]; - strcpy(fn, cart.fn); - modify_extension(fn, "cht"); if(cheat.count() > 0 || file::exists(get_cheat_filename(cart.fn, "cht"))) { cheat.save(cheatfn); cheat.clear(); @@ -138,3 +110,58 @@ Cartridge::Cartridge() { Cartridge::~Cartridge() { if(cart.loaded == true) unload(); } + +// + +void Cartridge::cartinfo_t::reset() { + type = TypeUnknown; + mapper = LoROM; + dsp1_mapper = DSP1Unmapped; + region = NTSC; + + rom_size = 0; + ram_size = 0; + + bsxslot = false; + superfx = false; + sa1 = false; + srtc = false; + sdd1 = false; + spc7110 = false; + spc7110rtc = false; + cx4 = false; + dsp1 = false; + dsp2 = false; + dsp3 = false; + dsp4 = false; + obc1 = false; + st010 = false; + st011 = false; + 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; +} diff --git a/src/cart/cart.h b/src/cart/cart.h index a3df4c73..9198d675 100644 --- a/src/cart/cart.h +++ b/src/cart/cart.h @@ -1,10 +1,20 @@ class Cartridge { public: + enum CartridgeMode { + ModeNormal, + ModeBSC, + ModeBSX, + ModeSufamiTurbo, + }; + enum CartridgeType { - CartridgeNormal, - CartridgeBSX, - CartridgeBSC, - CartridgeSufamiTurbo, + TypeNormal, + TypeBSC, + TypeBSXBIOS, + TypeBSX, + TypeSufamiTurboBIOS, + TypeSufamiTurbo, + TypeUnknown, }; enum HeaderField { @@ -32,9 +42,9 @@ public: ExLoROM, ExHiROM, SPC7110ROM, - BSXROM, BSCLoROM, BSCHiROM, + BSXROM, STROM, }; @@ -45,6 +55,11 @@ public: DSP1HiROM, }; + const char* name(); + CartridgeMode mode(); + MemoryMapper mapper(); + Region region(); + struct { bool loaded; char fn[PATH_MAX]; @@ -64,26 +79,20 @@ public: uint rom_size, ram_size; } stA, stB; - struct { + struct cartinfo_t { CartridgeType type; - - uint32 crc32; - char filename[PATH_MAX * 4]; - bool patched; - - Region region; MemoryMapper mapper; - uint rom_size; - uint ram_size; + DSP1MemoryMapper dsp1_mapper; + Region region; - bool bsxbase; - bool bsxcart; - bool bsxflash; - bool st; + unsigned rom_size; + unsigned ram_size; + + bool bsxslot; bool superfx; bool sa1; bool srtc; - bool sdd1; + bool sdd1; bool spc7110; bool spc7110rtc; bool cx4; @@ -96,17 +105,53 @@ public: bool st011; bool st018; - DSP1MemoryMapper dsp1_mapper; + void reset(); + }; - uint header_index; + struct info_t { + char filename[PATH_MAX * 4]; + bool patched; + + CartridgeMode mode; + MemoryMapper mapper; + DSP1MemoryMapper dsp1_mapper; + Region region; + + 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; - MemoryMapper mapper(); - Region region(); + 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_bsx(const char*, 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(); @@ -115,14 +160,13 @@ public: void unload_cart_st(); bool loaded(); - void load_begin(CartridgeType); + void load_begin(CartridgeMode); void load_end(); bool unload(); - unsigned score_header(unsigned); - void find_header(); - void read_header(); - void read_extended_header(); + 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); enum CompressionMode { CompressionNone, //always load without compression diff --git a/src/cart/cart_bsc.cpp b/src/cart/cart_bsc.cpp index 4b1f72e1..e1cb44c8 100644 --- a/src/cart/cart_bsc.cpp +++ b/src/cart/cart_bsc.cpp @@ -1,57 +1,36 @@ #ifdef CART_CPP void Cartridge::load_cart_bsc(const char *base, const char *slot) { - if(!base || !*base) return; - - strcpy(cart.fn, base); - strcpy(bs.fn, slot ? slot : ""); - load_begin(CartridgeBSC); - - uint8_t *data = 0; + uint8_t *data; unsigned size; - load_file(cart.fn, data, size, CompressionAuto); - cart.rom = data, cart.rom_size = size; + strcpy(cart.fn, base); + strcpy(bs.fn, slot); - if(load_file(get_patch_filename(cart.fn, "ups"), data, size, CompressionInspect) == true) { - apply_patch(data, size, cart.rom, cart.rom_size); - delete[] data; + 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(*bs.fn) { - if(load_file(bs.fn, data, size, CompressionAuto) == true) { - info.bsxflash = true; - bs.ram = data, bs.ram_size = size; - if(load_file(get_patch_filename(bs.fn, "ups"), data, size, CompressionInspect) == true) { - apply_patch(data, size, bs.ram, bs.ram_size); - delete[] data; - } - } - } - - find_header(); - read_header(); - - info.mapper = cartridge.info.header_index == 0x7fc0 ? BSCLoROM : BSCHiROM; - info.region = NTSC; - - if(info.ram_size > 0) { - cart.ram = new uint8_t[cart.ram_size = info.ram_size]; - memset(cart.ram, 0xff, cart.ram_size); - - if(load_file(get_save_filename(cart.fn, "srm"), data, size, CompressionNone) == true) { - memcpy(cart.ram, data, min(size, cart.ram_size)); - delete[] data; - } + 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, cart.fn); + strcpy(info.filename, base); get_base_filename(info.filename); - if(*bs.fn) { + if(*slot) { char filenameBS[PATH_MAX]; - strcpy(filenameBS, bs.fn); + strcpy(filenameBS, slot); get_base_filename(filenameBS); strcat(info.filename, " + "); strcat(info.filename, filenameBS); diff --git a/src/cart/cart_bsx.cpp b/src/cart/cart_bsx.cpp index ba59b945..7836907f 100644 --- a/src/cart/cart_bsx.cpp +++ b/src/cart/cart_bsx.cpp @@ -1,27 +1,20 @@ #ifdef CART_CPP void Cartridge::load_cart_bsx(const char *base, const char *slot) { - if(!base || !*base) return; - - strcpy(cart.fn, base); - strcpy(bs.fn, slot ? slot : ""); - - load_begin(CartridgeBSX); - info.bsxbase = true; - info.bsxcart = true; - info.mapper = BSXROM; - info.region = NTSC; - - uint8_t *data = 0; + uint8_t *data; unsigned size; - load_file(cart.fn, data, size, CompressionAuto); - cart.rom = data, cart.rom_size = size; - cart.ram = 0, cart.ram_size = 0; + strcpy(cart.fn, base); + strcpy(bs.fn, slot); - if(load_file(get_patch_filename(cart.fn, "ups"), data, size, CompressionInspect) == true) { - apply_patch(data, size, cart.rom, cart.rom_size); - delete[] data; - } + 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()); @@ -36,20 +29,15 @@ void Cartridge::load_cart_bsx(const char *base, const char *slot) { delete[] data; } - if(*bs.fn) { - if(load_file(bs.fn, data, size, CompressionAuto) == true) { - info.bsxflash = true; - bs.ram = data, bs.ram_size = size; - if(load_file(get_patch_filename(bs.fn, "ups"), data, size, CompressionInspect) == true) { - apply_patch(data, size, bs.ram, bs.ram_size); - delete[] data; - } - } + if(load_image(slot)) { + info.bsxflash = true; + bs.ram = image.data; + bs.ram_size = image.size; } load_end(); - strcpy(info.filename, !*bs.fn ? cart.fn : bs.fn); + strcpy(info.filename, !*slot ? base : slot); get_base_filename(info.filename); } diff --git a/src/cart/cart_header.cpp b/src/cart/cart_header.cpp index 2a6097bd..b213f704 100644 --- a/src/cart/cart_header.cpp +++ b/src/cart/cart_header.cpp @@ -1,30 +1,70 @@ #ifdef CART_CPP -void Cartridge::read_header() { - uint8 *rom = cart.rom; - uint index = info.header_index; - uint8 mapper = rom[index + MAPPER]; - uint8 rom_type = rom[index + ROM_TYPE]; - uint8 rom_size = rom[index + ROM_SIZE]; - uint8 company = rom[index + COMPANY]; - uint8 region = rom[index + REGION] & 0x7f; +void Cartridge::read_header(cartinfo_t &info, const uint8_t *data, unsigned size) { + info.reset(); + unsigned index = find_header(data, size); - //detect presence of BS-X flash cartridge connector (reads extended header information) - bool has_bsxflash = false; - if(rom[index - 14] == 'Z') { - if(rom[index - 11] == 'J') { - uint8 n13 = rom[index - 13]; - if((n13 >= 'A' && n13 <= 'Z') || (n13 >= '0' && n13 <= '9')) { - if(company == 0x33 || (rom[index - 10] == 0x00 && rom[index - 4] == 0x00)) { - has_bsxflash = true; + //detect BS-X flash carts + if(data[index + 0x13] == 0x00 || data[index + 0x13] == 0xff) { + if(data[index + 0x14] == 0x00) { + 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.mapper = BSXROM; + info.region = NTSC; //BS-X only released in Japan + return; } } } } - if(has_bsxflash == true) { - info.mapper = index == 0x7fc0 ? BSCLoROM : BSCHiROM; - } else if(index == 0x7fc0 && cart.rom_size >= 0x401000) { + //detect Sufami Turbo carts + if(!memcmp(data, "BANDAI SFC-ADX", 14)) { + if(!memcmp(data + 16, "SFC-ADX BACKUP", 14)) { + info.type = TypeSufamiTurboBIOS; + } else { + info.type = TypeSufamiTurbo; + } + info.mapper = STROM; + info.region = NTSC; //Sufami Turbo only released in Japan + return; + } + + //standard cart + uint8 mapper = data[index + MAPPER]; + uint8 rom_type = data[index + ROM_TYPE]; + uint8 rom_size = data[index + ROM_SIZE]; + uint8 company = data[index + COMPANY]; + uint8 region = data[index + REGION] & 0x7f; + + //detect presence of BS-X flash cartridge connector (reads extended header information) + if(data[index - 14] == 'Z') { + if(data[index - 11] == 'J') { + 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; + } + } + } + } + + if(info.bsxslot == true) { + if(!memcmp(data + index, "Satellaview BS-X ", 21)) { + //BS-X base cart + info.type = TypeBSXBIOS; + info.mapper = BSXROM; + } else { + info.type = TypeBSC; + info.mapper = (index == 0x7fc0 ? BSCLoROM : BSCHiROM); + } + return; + } + + info.type = TypeNormal; + + if(index == 0x7fc0 && size >= 0x401000) { info.mapper = ExLoROM; } else if(index == 0x7fc0 && mapper == 0x32) { info.mapper = ExLoROM; @@ -75,7 +115,7 @@ void Cartridge::read_header() { } if(info.dsp1 == true) { - if((mapper & 0x2f) == 0x20 && cart.rom_size <= 0x100000) { + if((mapper & 0x2f) == 0x20 && size <= 0x100000) { info.dsp1_mapper = DSP1LoROM1MB; } else if((mapper & 0x2f) == 0x20) { info.dsp1_mapper = DSP1LoROM2MB; @@ -112,8 +152,8 @@ void Cartridge::read_header() { info.st018 = true; } - if(rom[info.header_index + RAM_SIZE] & 7) { - info.ram_size = 1024 << (rom[info.header_index + RAM_SIZE] & 7); + if(data[index + RAM_SIZE] & 7) { + info.ram_size = 1024 << (data[index + RAM_SIZE] & 7); } else { info.ram_size = 0; } @@ -122,17 +162,31 @@ void Cartridge::read_header() { info.region = (region <= 1 || region >= 13) ? NTSC : PAL; } -unsigned Cartridge::score_header(unsigned addr) { - if(cart.rom_size < addr + 64) return 0; //image too small to contain header at this location? - uint8 *rom = cart.rom; +unsigned Cartridge::find_header(const uint8_t *data, unsigned size) { + unsigned score_lo = score_header(data, size, 0x007fc0); + unsigned score_hi = score_header(data, size, 0x00ffc0); + unsigned score_ex = score_header(data, size, 0x40ffc0); + if(score_ex) score_ex += 4; //favor ExHiROM on images > 32mbits + + if(score_lo >= score_hi && score_lo >= score_ex) { + return 0x007fc0; + } else if(score_hi >= score_ex) { + return 0x00ffc0; + } else { + return 0x40ffc0; + } +} + +unsigned Cartridge::score_header(const uint8_t *data, unsigned size, unsigned addr) { + if(size < addr + 64) return 0; //image too small to contain header at this location? int score = 0; - uint16 resetvector = rom[addr + RESETV] | (rom[addr + RESETV + 1] << 8); - uint16 checksum = rom[addr + CKSUM] | (rom[addr + CKSUM + 1] << 8); - uint16 ichecksum = rom[addr + ICKSUM] | (rom[addr + ICKSUM + 1] << 8); + uint16 resetvector = data[addr + RESETV] | (data[addr + RESETV + 1] << 8); + uint16 checksum = data[addr + CKSUM] | (data[addr + CKSUM + 1] << 8); + uint16 ichecksum = data[addr + ICKSUM] | (data[addr + ICKSUM + 1] << 8); - uint8 resetop = rom[(addr & ~0x7fff) | (resetvector & 0x7fff)]; //first opcode executed upon reset - uint8 mapper = rom[addr + MAPPER] & ~0x10; //mask off irrelevent FastROM-capable bit + uint8 resetop = data[(addr & ~0x7fff) | (resetvector & 0x7fff)]; //first opcode executed upon reset + uint8 mapper = data[addr + MAPPER] & ~0x10; //mask off irrelevent FastROM-capable bit //$00:[000-7fff] contains uninitialized RAM and MMIO. //reset vector must point to ROM at $00:[8000-ffff] to be considered valid. @@ -194,29 +248,14 @@ unsigned Cartridge::score_header(unsigned addr) { if(addr == 0x007fc0 && mapper == 0x22) score += 2; //0x22 is usually ExLoROM if(addr == 0x40ffc0 && mapper == 0x25) score += 2; //0x25 is usually ExHiROM - if(rom[addr + COMPANY] == 0x33) score += 2; //0x33 indicates extended header - if(rom[addr + ROM_TYPE] < 0x08) score++; - if(rom[addr + ROM_SIZE] < 0x10) score++; - if(rom[addr + RAM_SIZE] < 0x08) score++; - if(rom[addr + REGION] < 14) score++; + if(data[addr + COMPANY] == 0x33) score += 2; //0x33 indicates extended header + if(data[addr + ROM_TYPE] < 0x08) score++; + if(data[addr + ROM_SIZE] < 0x10) score++; + if(data[addr + RAM_SIZE] < 0x08) score++; + if(data[addr + REGION] < 14) score++; if(score < 0) score = 0; return score; } -void Cartridge::find_header() { - unsigned score_lo = score_header(0x007fc0); - unsigned score_hi = score_header(0x00ffc0); - unsigned score_ex = score_header(0x40ffc0); - if(score_ex) score_ex += 4; //favor ExHiROM on images > 32mbits - - if(score_lo >= score_hi && score_lo >= score_ex) { - info.header_index = 0x007fc0; - } else if(score_hi >= score_ex) { - info.header_index = 0x00ffc0; - } else { - info.header_index = 0x40ffc0; - } -} - #endif //ifdef CART_CPP diff --git a/src/cart/cart_load.cpp b/src/cart/cart_load.cpp new file mode 100644 index 00000000..75e4275b --- /dev/null +++ b/src/cart/cart_load.cpp @@ -0,0 +1,50 @@ +#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 //ifdef CART_CPP diff --git a/src/cart/cart_normal.cpp b/src/cart/cart_normal.cpp index d08a29c8..11073842 100644 --- a/src/cart/cart_normal.cpp +++ b/src/cart/cart_normal.cpp @@ -1,58 +1,29 @@ #ifdef CART_CPP -void Cartridge::load_cart_normal(const char *filename) { - if(!filename || !*filename) return; - - uint8_t *data = 0; +void Cartridge::load_cart_normal(const char *base) { + uint8_t *data; unsigned size; - if(load_file(filename, data, size, CompressionAuto) == false) return; - strcpy(cart.fn, filename); + strcpy(cart.fn, base); - load_begin(CartridgeNormal); + load_begin(ModeNormal); + if(load_image(base) == false) return; - //load ROM data, ignore 512-byte header if detected - if((size & 0x7fff) != 512) { - cart.rom = new uint8_t[cart.rom_size = size]; - memcpy(cart.rom, data, size); - } else { - cart.rom = new uint8_t[cart.rom_size = size - 512]; - memcpy(cart.rom, data + 512, size - 512); - } - delete[] data; + cartinfo_t cartinfo; + read_header(cartinfo, cart.rom = image.data, cart.rom_size = image.size); + info = cartinfo; - if(load_file(get_patch_filename(cart.fn, "ups"), data, size, CompressionInspect) == true) { - apply_patch(data, size, cart.rom, cart.rom_size); - delete[] data; - info.patched = true; + if(cartinfo.ram_size > 0) { + load_ram(get_save_filename(base, "srm"), cart.ram, cart.ram_size = cartinfo.ram_size, 0xff); } - info.crc32 = crc32_calculate(cart.rom, cart.rom_size); - - find_header(); - read_header(); - - if(info.ram_size > 0) { - cart.ram = new uint8_t[cart.ram_size = info.ram_size]; - memset(cart.ram, 0xff, cart.ram_size); - - if(load_file(get_save_filename(cart.fn, "srm"), data, size, CompressionNone) == true) { - memcpy(cart.ram, data, min(size, cart.ram_size)); - delete[] data; - } - } - - if(info.srtc || info.spc7110rtc) { - cart.rtc = new(zeromemory) uint8_t[cart.rtc_size = 20]; - if(load_file(get_save_filename(cart.fn, "rtc"), data, size, CompressionNone) == true) { - memcpy(cart.rtc, data, min(size, cart.rtc_size)); - delete[] data; - } + 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, cart.fn); + strcpy(info.filename, base); get_base_filename(info.filename); } diff --git a/src/cart/cart_st.cpp b/src/cart/cart_st.cpp index 049765f7..b32b5035 100644 --- a/src/cart/cart_st.cpp +++ b/src/cart/cart_st.cpp @@ -1,86 +1,52 @@ #ifdef CART_CPP void Cartridge::load_cart_st(const char *base, const char *slotA, const char *slotB) { - if(!base || !*base) return; - - strcpy(cart.fn, base); - strcpy(stA.fn, slotA ? slotA : ""); - strcpy(stB.fn, slotB ? slotB : ""); - - load_begin(CartridgeSufamiTurbo); - info.st = true; - info.mapper = STROM; - info.region = NTSC; - - uint8_t *data = 0; + uint8_t *data; unsigned size; - if(load_file(cart.fn, data, size, CompressionAuto) == true) { - cart.rom = new(zeromemory) uint8_t[cart.rom_size = 0x040000]; - memcpy(cart.rom, data, min(size, cart.rom_size)); - delete[] data; - if(load_file(get_patch_filename(cart.fn, "ups"), data, size, CompressionInspect) == true) { - apply_patch(data, size, cart.rom, cart.rom_size); - delete[] data; - } + 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(*stA.fn) { - if(load_file(stA.fn, data, size, CompressionAuto) == true) { - stA.rom = new(zeromemory) uint8_t[stA.rom_size = 0x100000]; - memcpy(stA.rom, data, min(size, stA.rom_size)); - delete[] data; - if(load_file(get_patch_filename(stA.fn, "ups"), data, size, CompressionInspect) == true) { - apply_patch(data, size, stA.rom, stA.rom_size); - delete[] data; - } + 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; - stA.ram = new uint8_t[stA.ram_size = 0x020000]; - memset(stA.ram, 0xff, stA.ram_size); - - if(load_file(get_save_filename(stA.fn, "srm"), data, size, CompressionNone) == true) { - memcpy(stA.ram, data, min(size, 0x020000U)); - delete[] data; - } - } - } - - if(*stB.fn) { - if(load_file(stB.fn, data, size, CompressionAuto) == true) { - stB.rom = new(zeromemory) uint8_t[stB.rom_size = 0x100000]; - memcpy(stB.rom, data, min(size, stB.rom_size)); - delete[] data; - if(load_file(get_patch_filename(stB.fn, "ups"), data, size, CompressionInspect) == true) { - apply_patch(data, size, stB.rom, stB.rom_size); - delete[] data; - } - - stB.ram = new uint8_t[stB.ram_size = 0x020000]; - memset(stB.ram, 0xff, stB.ram_size); - - if(load_file(get_save_filename(stB.fn, "srm"), data, size, CompressionNone) == true) { - memcpy(stB.ram, data, min(size, 0x020000U)); - delete[] data; - } - } + load_ram(get_save_filename(slotB, "srm"), stB.ram, stB.ram_size = 0x020000, 0xff); } load_end(); //set base filename - if(!*stA.fn && !*stB.fn) { + if(!*slotA && !*slotB) { strcpy(info.filename, cart.fn); get_base_filename(info.filename); - } else if(*stA.fn && !*stB.fn) { - strcpy(info.filename, stA.fn); + } else if(*slotA && !*slotB) { + strcpy(info.filename, slotA); get_base_filename(info.filename); - } else if(!*stA.fn && *stB.fn) { - strcpy(info.filename, stB.fn); + } else if(!*slotA && *slotB) { + strcpy(info.filename, slotB); get_base_filename(info.filename); } else { char filenameA[PATH_MAX], filenameB[PATH_MAX]; - strcpy(filenameA, stA.fn); + strcpy(filenameA, slotA); get_base_filename(filenameA); - strcpy(filenameB, stB.fn); + strcpy(filenameB, slotB); get_base_filename(filenameB); strcpy(info.filename, filenameA); strcat(info.filename, " + "); diff --git a/src/cheat/cheat.cpp b/src/cheat/cheat.cpp index 02ad02d1..9ee8ccac 100644 --- a/src/cheat/cheat.cpp +++ b/src/cheat/cheat.cpp @@ -1,30 +1,53 @@ #include "../base.h" -#include "../reader/filereader.h" Cheat cheat; +Cheat::cheat_t& Cheat::cheat_t::operator=(const Cheat::cheat_t& source) { + enabled = source.enabled; + addr = source.addr; + data = source.data; + code = source.code; + desc = source.desc; + return *this; +} + +//used to sort cheat code list by description +bool Cheat::cheat_t::operator<(const Cheat::cheat_t& source) { + return strcmp(desc, source.desc) < 0; +} + /***** * string <> binary code translation routines * decode() "7e1234:56" -> 0x7e123456 * encode() 0x7e123456 -> "7e1234:56" *****/ -bool Cheat::decode(char *str, uint32 &addr, uint8 &data, uint8 &type) { - string t, part; - strcpy(t, str); - strlower(t()); - if(strlen(t) == 8 || (strlen(t) == 9 && t()[6] == ':')) { +bool Cheat::decode(const char *str, unsigned &addr, uint8 &data, type_t &type) { + string t = str; + strlower(t); + + #define ischr(n) ((n >= '0' && n <= '9') || (n >= 'a' && n <= 'f')) + + if(strlen(t) == 8 || (strlen(t) == 9 && t[6] == ':')) { + //strip ':' + if(strlen(t) == 9 && t[6] == ':') t = string() << substr(t, 0, 6) << substr(t, 7); + //validate input + for(unsigned i = 0; i < 8; i++) if(!ischr(t[i])) return false; + type = ProActionReplay; - replace(t, ":", ""); - uint32 r = strhex((const char*)t); + unsigned r = strhex((const char*)t); addr = r >> 8; data = r & 0xff; return true; - } else if(strlen(t) == 9 && t()[4] == '-') { + } else if(strlen(t) == 9 && t[4] == '-') { + //strip '-' + t = string() << substr(t, 0, 4) << substr(t, 5); + //validate input + for(unsigned i = 0; i < 8; i++) if(!ischr(t[i])) return false; + type = GameGenie; - replace(t, "-", ""); strtr(t, "df4709156bc8a23e", "0123456789abcdef"); - uint32 r = strhex((const char*)t); + unsigned r = strhex((const char*)t); //8421 8421 8421 8421 8421 8421 //abcd efgh ijkl mnop qrst uvwx //ijkl qrst opab cduv wxef ghmn @@ -42,16 +65,20 @@ bool Cheat::decode(char *str, uint32 &addr, uint8 &data, uint8 &type) { (!!(r & 0x000080) << 1) | (!!(r & 0x000040) << 0); data = r >> 24; return true; + } else { + return false; } - return false; } -bool Cheat::encode(char *str, uint32 addr, uint8 data, uint8 type) { +bool Cheat::encode(string &str, unsigned addr, uint8 data, type_t type) { + char t[16]; + if(type == ProActionReplay) { - sprintf(str, "%0.6x:%0.2x", addr, data); + sprintf(t, "%0.6x:%0.2x", addr, data); + str = t; return true; } else if(type == GameGenie) { - uint32 r = addr; + unsigned r = addr; addr = (!!(r & 0x008000) << 23) | (!!(r & 0x004000) << 22) | (!!(r & 0x002000) << 21) | (!!(r & 0x001000) << 20) | (!!(r & 0x000080) << 19) | (!!(r & 0x000040) << 18) | @@ -64,11 +91,13 @@ bool Cheat::encode(char *str, uint32 addr, uint8 data, uint8 type) { (!!(r & 0x080000) << 5) | (!!(r & 0x040000) << 4) | (!!(r & 0x020000) << 3) | (!!(r & 0x010000) << 2) | (!!(r & 0x000800) << 1) | (!!(r & 0x000400) << 0); - sprintf(str, "%0.2x%0.2x-%0.4x", data, addr >> 16, addr & 0xffff); - strtr(str, "0123456789abcdef", "df4709156bc8a23e"); + sprintf(t, "%0.2x%0.2x-%0.4x", data, addr >> 16, addr & 0xffff); + strtr(t, "0123456789abcdef", "df4709156bc8a23e"); + str = t; return true; + } else { + return false; } - return false; } /***** @@ -78,21 +107,21 @@ bool Cheat::encode(char *str, uint32 addr, uint8 data, uint8 type) { * clear() disable specified address, mirror accordingly *****/ -uint Cheat::mirror_address(uint addr) { +unsigned Cheat::mirror_address(unsigned addr) const { if((addr & 0x40e000) != 0x0000) return addr; //8k WRAM mirror //$[00-3f|80-bf]:[0000-1fff] -> $7e:[0000-1fff] return (0x7e0000 + (addr & 0x1fff)); } -void Cheat::set(uint32 addr) { +void Cheat::set(unsigned addr) { addr = mirror_address(addr); mask[addr >> 3] |= 1 << (addr & 7); if((addr & 0xffe000) == 0x7e0000) { //mirror $7e:[0000-1fff] to $[00-3f|80-bf]:[0000-1fff] - uint mirror; - for(int x = 0; x <= 0x3f; x++) { + unsigned mirror; + for(unsigned x = 0; x <= 0x3f; x++) { mirror = ((0x00 + x) << 16) + (addr & 0x1fff); mask[mirror >> 3] |= 1 << (mirror & 7); mirror = ((0x80 + x) << 16) + (addr & 0x1fff); @@ -101,20 +130,20 @@ void Cheat::set(uint32 addr) { } } -void Cheat::clear(uint32 addr) { +void Cheat::clear(unsigned addr) { addr = mirror_address(addr); - //is there more than one cheat code using the same address - //(and likely a different override value) that is enabled? - //if so, do not clear code lookup table entry for this address. + //if there is more than one cheat code using the same address, + //(eg with a different override value) then do not clear code + //lookup table entry. uint8 r; - if(read(addr, r) == true)return; + if(read(addr, r) == true) return; mask[addr >> 3] &= ~(1 << (addr & 7)); if((addr & 0xffe000) == 0x7e0000) { //mirror $7e:[0000-1fff] to $[00-3f|80-bf]:[0000-1fff] - uint mirror; - for(int x = 0; x <= 0x3f; x++) { + unsigned mirror; + for(unsigned x = 0; x <= 0x3f; x++) { mirror = ((0x00 + x) << 16) + (addr & 0x1fff); mask[mirror >> 3] &= ~(1 << (mirror & 7)); mirror = ((0x80 + x) << 16) + (addr & 0x1fff); @@ -130,12 +159,12 @@ void Cheat::clear(uint32 addr) { * when true, cheat code substitution value is stored in data. *****/ -bool Cheat::read(uint32 addr, uint8 &data) { +bool Cheat::read(unsigned addr, uint8 &data) const { addr = mirror_address(addr); - for(int i = 0; i < cheat_count; i++) { + for(unsigned i = 0; i < code.size(); i++) { if(enabled(i) == false) continue; - if(addr == mirror_address(index[i].addr)) { - data = index[i].data; + if(addr == mirror_address(code[i].addr)) { + data = code[i].data; return true; } } @@ -150,96 +179,74 @@ bool Cheat::read(uint32 addr, uint8 &data) { *****/ void Cheat::update_cheat_status() { - for(unsigned i = 0; i < cheat_count; i++) { - if(index[i].enabled) { - cheat_enabled = true; + for(unsigned i = 0; i < code.size(); i++) { + if(code[i].enabled) { + cheat_system_enabled = true; return; } } - cheat_enabled = false; + cheat_system_enabled = false; } /***** * cheat list manipulation routines *****/ -bool Cheat::add(bool enable, char *code, char *desc) { - if(cheat_count >= CheatLimit) return false; +bool Cheat::add(bool enable, const char *code_, const char *desc_) { + unsigned addr; + uint8 data; + type_t type; + if(decode(code_, addr, data, type) == false) return false; - uint32 addr, len; - uint8 data, type; - if(decode(code, addr, data, type) == false) return false; - - index[cheat_count].enabled = enable; - index[cheat_count].addr = addr; - index[cheat_count].data = data; - len = strlen(code); - len = len > 16 ? 16 : len; - memcpy(index[cheat_count].code, code, len); - index[cheat_count].code[len] = 0; - len = strlen(desc); - len = len > 128 ? 128 : len; - memcpy(index[cheat_count].desc, desc, len); - index[cheat_count].desc[len] = 0; - cheat_count++; + unsigned n = code.size(); + code[n].enabled = enable; + code[n].addr = addr; + code[n].data = data; + code[n].code = code_; + code[n].desc = desc_; (enable) ? set(addr) : clear(addr); update_cheat_status(); return true; } -bool Cheat::edit(uint32 n, bool enable, char *code, char *desc) { - if(n >= cheat_count) return false; - - uint32 addr, len; - uint8 data, type; - if(decode(code, addr, data, type) == false) return false; +bool Cheat::edit(unsigned n, bool enable, const char *code_, const char *desc_) { + unsigned addr; + uint8 data; + type_t type; + if(decode(code_, addr, data, type) == false) return false; //disable current code and clear from code lookup table - index[n].enabled = false; - clear(index[n].addr); + code[n].enabled = false; + clear(code[n].addr); //update code and enable in code lookup table - index[n].enabled = enable; - index[n].addr = addr; - index[n].data = data; - len = strlen(code); - len = len > 16 ? 16 : len; - memcpy(index[n].code, code, len); - index[n].code[len] = 0; - len = strlen(desc); - len = len > 128 ? 128 : len; - memcpy(index[n].desc, desc, len); - index[n].desc[len] = 0; + code[n].enabled = enable; + code[n].addr = addr; + code[n].data = data; + code[n].code = code_; + code[n].desc = desc_; set(addr); update_cheat_status(); return true; } -bool Cheat::remove(uint32 n) { - if(n >= cheat_count) return false; +bool Cheat::remove(unsigned n) { + unsigned size = code.size(); + if(n >= size) return false; //also verifies size cannot be < 1 - for(unsigned i = n; i < cheat_count; i++) { - index[i].enabled = index[i + 1].enabled; - index[i].addr = index[i + 1].addr; - index[i].data = index[i + 1].data; - strcpy(index[i].desc, index[i + 1].desc); - } - - cheat_count--; + for(unsigned i = n; i < size - 1; i++) code[i] = code[i + 1]; + code.resize(size - 1); update_cheat_status(); return true; } -bool Cheat::get(uint32 n, bool &enable, uint32 &addr, uint8 &data, char *code, char *desc) { - if(n >= cheat_count) return false; - enable = index[n].enabled; - addr = index[n].addr; - data = index[n].data; - strcpy(code, index[n].code); - strcpy(desc, index[n].desc); +bool Cheat::get(unsigned n, cheat_t &cheat) const { + if(n >= code.size()) return false; + + cheat = code[n]; return true; } @@ -247,22 +254,23 @@ bool Cheat::get(uint32 n, bool &enable, uint32 &addr, uint8 &data, char *code, c * code status modifier routines *****/ -bool Cheat::enabled(uint32 n) { - if(n >= cheat_count) return false; - return index[n].enabled; +bool Cheat::enabled(unsigned n) const { + return (n < code.size()) ? code[n].enabled : false; } -void Cheat::enable(uint32 n) { - if(n >= cheat_count) return; - index[n].enabled = true; - set(index[n].addr); +void Cheat::enable(unsigned n) { + if(n >= code.size()) return; + + code[n].enabled = true; + set(code[n].addr); update_cheat_status(); } -void Cheat::disable(uint32 n) { - if(n >= cheat_count) return; - index[n].enabled = false; - clear(index[n].addr); +void Cheat::disable(unsigned n) { + if(n >= code.size()) return; + + code[n].enabled = false; + clear(code[n].addr); update_cheat_status(); } @@ -288,40 +296,39 @@ bool Cheat::load(const char *fn) { split(part, ",", line[i]); if(::count(part) != 3) continue; trim(part[2], "\""); - add(part[1] == "enabled", part[0](), part[2]()); + add(part[1] == "enabled", part[0], part[2]); } return true; } -bool Cheat::save(const char *fn) { +bool Cheat::save(const char *fn) const { file fp; if(!fp.open(fn, file::mode_write)) return false; - for(unsigned i = 0; i < cheat_count; i++) { + for(unsigned i = 0; i < code.size(); i++) { fp.print(string() - << index[i].code << " = " - << (index[i].enabled ? "enabled" : "disabled") << ", \"" - << index[i].desc << "\"\r\n"); + << code[i].code << " = " + << (code[i].enabled ? "enabled" : "disabled") << ", " + << "\"" << code[i].desc << "\"" + << "\r\n"); } fp.close(); return true; } -/***** - * initialization routines - *****/ +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_enabled = false; - cheat_count = 0; + cheat_system_enabled = false; memset(mask, 0, 0x200000); - for(unsigned i = 0; i <= CheatLimit; i++) { - index[i].enabled = false; - index[i].addr = 0x000000; - index[i].data = 0x00; - strcpy(index[i].code, ""); - strcpy(index[i].desc, ""); - } + code.reset(); } Cheat::Cheat() { diff --git a/src/cheat/cheat.h b/src/cheat/cheat.h index 74ead8ad..ac8a543d 100644 --- a/src/cheat/cheat.h +++ b/src/cheat/cheat.h @@ -1,51 +1,55 @@ class Cheat { -public: - enum { CheatLimit = 1024 }; - - enum Type { - ProActionReplay, - GameGenie, +public: + enum type_t { + ProActionReplay, + GameGenie, }; - struct CheatIndex { - bool enabled; - uint32 addr; - uint8 data; - char code[ 16 + 1]; - char desc[128 + 1]; - } index[CheatLimit + 1]; - - bool cheat_enabled; - uint32 cheat_count; - uint8 mask[0x200000]; + struct cheat_t { + bool enabled; + unsigned addr; + uint8 data; + string code; + string desc; - inline bool enabled() { return cheat_enabled; } - inline uint count() { return cheat_count; } - inline bool exists(uint32 addr) { return bool(mask[addr >> 3] & 1 << (addr & 7)); } + cheat_t& operator=(const cheat_t&); + bool operator<(const cheat_t&); + }; - bool decode(char *str, uint32 &addr, uint8 &data, uint8 &type); - bool encode(char *str, uint32 addr, uint8 data, uint8 type); + static bool decode(const char *str, unsigned &addr, uint8 &data, type_t &type); + static bool encode(string &str, unsigned addr, uint8 data, type_t type); - bool read(uint32 addr, uint8 &data); + inline bool enabled() const { return cheat_system_enabled; } + inline unsigned count() const { return code.size(); } + inline bool exists(unsigned addr) const { return bool(mask[addr >> 3] & 1 << (addr & 7)); } + + bool read(unsigned addr, uint8 &data) const; + + bool add(bool enable, const char *code, const char *desc); + bool edit(unsigned n, bool enable, const char *code, const char *desc); + bool get(unsigned n, cheat_t &cheat) const; + bool remove(unsigned n); + bool enabled(unsigned n) const; + void enable(unsigned n); + void disable(unsigned n); - void update_cheat_status(); - bool add(bool enable, char *code, char *desc); - bool edit(uint32 n, bool enable, char *code, char *desc); - bool get(uint32 n, bool &enable, uint32 &addr, uint8 &data, char *code, char *desc); - bool remove(uint32 n); - bool enabled(uint32 n); - void enable(uint32 n); - void disable(uint32 n); bool load(const char *fn); - bool save(const char *fn); + bool save(const char *fn) const; + + void sort(); void clear(); - Cheat(); - -private: - uint mirror_address(uint addr); - void set(uint32 addr); - void clear(uint32 addr); + Cheat(); + +private: + bool cheat_system_enabled; + uint8 mask[0x200000]; + vector code; + + void update_cheat_status(); + unsigned mirror_address(unsigned addr) const; + void set(unsigned addr); + void clear(unsigned addr); }; extern Cheat cheat; diff --git a/src/chip/obc1/obc1.cpp b/src/chip/obc1/obc1.cpp index edd08e7b..dd656aaa 100644 --- a/src/chip/obc1/obc1.cpp +++ b/src/chip/obc1/obc1.cpp @@ -8,80 +8,61 @@ void OBC1::power() { } void OBC1::reset() { - for(uint i = 0x0000; i <= 0x1fff; i++) ram_write(i, 0xff); + for(unsigned i = 0x0000; i <= 0x1fff; i++) ram_write(i, 0xff); status.baseptr = (ram_read(0x1ff5) & 1) ? 0x1800 : 0x1c00; status.address = (ram_read(0x1ff6) & 0x7f); status.shift = (ram_read(0x1ff6) & 3) << 1; } -uint8 OBC1::read(uint addr) { +uint8 OBC1::read(unsigned addr) { addr &= 0x1fff; if((addr & 0x1ff8) != 0x1ff0) return ram_read(addr); - switch(addr) { - case 0x1ff0: - return ram_read(status.baseptr + (status.address << 2) + 0); - case 0x1ff1: - return ram_read(status.baseptr + (status.address << 2) + 1); - case 0x1ff2: - return ram_read(status.baseptr + (status.address << 2) + 2); - case 0x1ff3: - return ram_read(status.baseptr + (status.address << 2) + 3); - case 0x1ff4: - return ram_read(status.baseptr + (status.address >> 2) + 0x200); - case 0x1ff5: - case 0x1ff6: - case 0x1ff7: - return ram_read(addr); + switch(addr) { default: //never used, avoids compiler warning + case 0x1ff0: return ram_read(status.baseptr + (status.address << 2) + 0); + case 0x1ff1: return ram_read(status.baseptr + (status.address << 2) + 1); + case 0x1ff2: return ram_read(status.baseptr + (status.address << 2) + 2); + case 0x1ff3: return ram_read(status.baseptr + (status.address << 2) + 3); + case 0x1ff4: return ram_read(status.baseptr + (status.address >> 2) + 0x200); + case 0x1ff5: case 0x1ff6: case 0x1ff7: return ram_read(addr); } - - return 0x00; //never used, avoids compiler warning } -void OBC1::write(uint addr, uint8 data) { +void OBC1::write(unsigned addr, uint8 data) { addr &= 0x1fff; if((addr & 0x1ff8) != 0x1ff0) return ram_write(addr, data); switch(addr) { - case 0x1ff0: - ram_write(status.baseptr + (status.address << 2) + 0, data); - break; - case 0x1ff1: - ram_write(status.baseptr + (status.address << 2) + 1, data); - break; - case 0x1ff2: - ram_write(status.baseptr + (status.address << 2) + 2, data); - break; - case 0x1ff3: - ram_write(status.baseptr + (status.address << 2) + 3, data); - break; - case 0x1ff4: { - uint8 temp; - temp = ram_read(status.baseptr + (status.address >> 2) + 0x200); - temp = (temp & ~(3 << status.shift)) | ((data & 3) << status.shift); - ram_write(status.baseptr + (status.address >> 2) + 0x200, temp); - } break; - case 0x1ff5: - status.baseptr = (data & 1) ? 0x1800 : 0x1c00; - ram_write(addr, data); - break; - case 0x1ff6: - status.address = (data & 0x7f); - status.shift = (data & 3) << 1; - ram_write(addr, data); - break; - case 0x1ff7: - ram_write(addr, data); - break; + case 0x1ff0: ram_write(status.baseptr + (status.address << 2) + 0, data); break; + case 0x1ff1: ram_write(status.baseptr + (status.address << 2) + 1, data); break; + case 0x1ff2: ram_write(status.baseptr + (status.address << 2) + 2, data); break; + case 0x1ff3: ram_write(status.baseptr + (status.address << 2) + 3, data); break; + case 0x1ff4: { + uint8 temp = ram_read(status.baseptr + (status.address >> 2) + 0x200); + temp = (temp & ~(3 << status.shift)) | ((data & 3) << status.shift); + ram_write(status.baseptr + (status.address >> 2) + 0x200, temp); + } break; + case 0x1ff5: { + status.baseptr = (data & 1) ? 0x1800 : 0x1c00; + ram_write(addr, data); + } break; + case 0x1ff6: { + status.address = (data & 0x7f); + status.shift = (data & 3) << 1; + ram_write(addr, data); + } break; + case 0x1ff7: { + ram_write(addr, data); + } break; } } -uint8 OBC1::ram_read(uint addr) { +uint8 OBC1::ram_read(unsigned addr) { return memory::cartram.read(addr & 0x1fff); } -void OBC1::ram_write(uint addr, uint8 data) { +void OBC1::ram_write(unsigned addr, uint8 data) { memory::cartram.write(addr & 0x1fff, data); } diff --git a/src/chip/obc1/obc1.h b/src/chip/obc1/obc1.h index 4caf682d..2fb97544 100644 --- a/src/chip/obc1/obc1.h +++ b/src/chip/obc1/obc1.h @@ -5,15 +5,15 @@ public: void power(); void reset(); - uint8 read(uint addr); - void write(uint addr, uint8 data); + uint8 read(unsigned addr); + void write(unsigned addr, uint8 data); OBC1(); ~OBC1(); private: - uint8 ram_read(uint addr); - void ram_write(uint addr, uint8 data); + uint8 ram_read(unsigned addr); + void ram_write(unsigned addr, uint8 data); struct { uint16 address; diff --git a/src/chip/srtc/srtc.cpp b/src/chip/srtc/srtc.cpp index e688da23..04b20057 100644 --- a/src/chip/srtc/srtc.cpp +++ b/src/chip/srtc/srtc.cpp @@ -139,7 +139,7 @@ unsigned SRTC::weekday(unsigned year, unsigned month, unsigned day) { return (sum + 1) % 7; //1900-01-01 was a Monday } -uint8 SRTC::mmio_read(uint addr) { +uint8 SRTC::mmio_read(unsigned addr) { addr &= 0xffff; if(addr == 0x2800) { @@ -160,7 +160,7 @@ uint8 SRTC::mmio_read(uint addr) { return cpu.regs.mdr; } -void SRTC::mmio_write(uint addr, uint8 data) { +void SRTC::mmio_write(unsigned addr, uint8 data) { addr &= 0xffff; if(addr == 0x2801) { diff --git a/src/chip/srtc/srtc.h b/src/chip/srtc/srtc.h index c0d9ab6f..e2c99945 100644 --- a/src/chip/srtc/srtc.h +++ b/src/chip/srtc/srtc.h @@ -8,8 +8,8 @@ public: void power(); void reset(); - uint8 mmio_read (uint addr); - void mmio_write(uint addr, uint8 data); + uint8 mmio_read (unsigned addr); + void mmio_write(unsigned addr, uint8 data); SRTC(); diff --git a/src/config/config.cpp b/src/config/config.cpp index 30e710ba..8fc3565a 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -3,22 +3,22 @@ namespace config { configuration& config() { static configuration config; return config; -} - -integral_setting File::autodetect_type(config(), "file.autodetect_type", - "Auto-detect file type by inspecting file header, rather than by file extension.\n" - "In other words, if a .zip file is renamed to .smc, it will still be correctly\n" - "identified as a .zip file. However, there is an infinitesimal (1:~500,000,000)\n" - "chance of a false detection when loading an uncompressed image file, if this\n" - "option is enabled.", - integral_setting::boolean, false); - -integral_setting File::bypass_patch_crc32(config(), "file.bypass_patch_crc32", - "UPS patches contain CRC32s to validate that a patch was applied successfully.\n" - "By default, if this validation fails, said patch will not be applied.\n" - "Setting this option to true will bypass the validation,\n" - "which may or may not result in a working image.\n" - "Enabling this option is strongly discouraged.", +} + +integral_setting File::autodetect_type(config(), "file.autodetect_type", + "Auto-detect file type by inspecting file header, rather than by file extension.\n" + "In other words, if a .zip file is renamed to .smc, it will still be correctly\n" + "identified as a .zip file. However, there is an infinitesimal (1:~500,000,000)\n" + "chance of a false detection when loading an uncompressed image file, if this\n" + "option is enabled.", + integral_setting::boolean, false); + +integral_setting File::bypass_patch_crc32(config(), "file.bypass_patch_crc32", + "UPS patches contain CRC32s to validate that a patch was applied successfully.\n" + "By default, if this validation fails, said patch will not be applied.\n" + "Setting this option to true will bypass the validation,\n" + "which may or may not result in a working image.\n" + "Enabling this option is strongly discouraged.", integral_setting::boolean, false); string file_updatepath(const char *req_file, const char *req_path) { @@ -48,13 +48,13 @@ string_setting Path::base("path.base", "Path that bsnes resides in", ""); string_setting Path::user("path.user", "Path to user folder", ""); string_setting Path::rom(config(), "path.rom", - "Default path to look for ROM files in (\"\" = use default directory)", ""); -string_setting Path::patch(config(), "path.patch", + "Default path to look for ROM files in (\"\" = use default directory)", ""); +string_setting Path::patch(config(), "path.patch", "Default path for all UPS patch files (\"\" = use current directory)", ""); -string_setting Path::save(config(), "path.save", - "Default path for all save RAM files (\"\" = use current directory)", ""); -string_setting Path::cheat(config(), "path.cheat", - "Default path for all cheat files (\"\" = use current directory)", ""); +string_setting Path::save(config(), "path.save", + "Default path for all save RAM files (\"\" = use current directory)", ""); +string_setting Path::cheat(config(), "path.cheat", + "Default path for all cheat files (\"\" = use current directory)", ""); string_setting Path::bsx(config(), "path.bsx", "", ""); string_setting Path::st(config(), "path.st", "", ""); @@ -62,6 +62,17 @@ integral_setting SNES::controller_port1(config(), "snes.controller_port1", "Controller attached to SNES port 1", integral_setting::decimal, ::SNES::Input::DeviceJoypad); integral_setting SNES::controller_port2(config(), "snes.controller_port2", "Controller attached to SNES port 2", integral_setting::decimal, ::SNES::Input::DeviceJoypad); +integral_setting SNES::expansion_port(config(), "snes.expansion_port", + "Device attached to SNES expansion port\n" + "0 = None\n" + "1 = Satellaview BS-X\n" + "", integral_setting::decimal, ::SNES::ExpansionBSX); +integral_setting SNES::region(config(), "snes.region", + "SNES regional model\n" + "0 = Auto-detect based on cartridge\n" + "1 = NTSC\n" + "2 = PAL\n" + "", integral_setting::decimal, ::SNES::Autodetect); integral_setting CPU::ntsc_clock_rate(config(), "cpu.ntsc_clock_rate", "NTSC S-CPU clock rate (in hz)", integral_setting::decimal, 21477272); @@ -82,9 +93,9 @@ integral_setting CPU::wram_init_value(config(), "cpu.wram_init_value", integral_setting::hex, 0x55); integral_setting SMP::ntsc_clock_rate(config(), "smp.ntsc_clock_rate", - "NTSC S-SMP clock rate (in hz)", integral_setting::decimal, 32040 * 768); + "NTSC S-SMP clock rate (in hz)", integral_setting::decimal, 32041 * 768); integral_setting SMP::pal_clock_rate(config(), "smp.pal_clock_rate", - "PAL S-SMP clock rate (in hz)", integral_setting::decimal, 32040 * 768); + "PAL S-SMP clock rate (in hz)", integral_setting::decimal, 32041 * 768); integral_setting PPU::Hack::render_scanline_position(config(), "ppu.hack.render_scanline_position", "Approximate HCLOCK position to render at for scanline-based renderers", @@ -124,4 +135,4 @@ integral_setting PPU::oam_pri1_enable("ppu.oam_pri1_enable", "Enable OAM Priorit integral_setting PPU::oam_pri2_enable("ppu.oam_pri2_enable", "Enable OAM Priority 2", integral_setting::boolean, true); integral_setting PPU::oam_pri3_enable("ppu.oam_pri3_enable", "Enable OAM Priority 3", integral_setting::boolean, true); -} //namespace config +} //namespace config diff --git a/src/config/config.h b/src/config/config.h index 52cc0b64..5f865fb8 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -17,6 +17,8 @@ extern struct Path { extern struct SNES { static integral_setting controller_port1; static integral_setting controller_port2; + static integral_setting expansion_port; + static integral_setting region; } snes; extern struct CPU { diff --git a/src/cpu/scpu/timing/timing.cpp b/src/cpu/scpu/timing/timing.cpp index 2a32a6db..45c44a96 100644 --- a/src/cpu/scpu/timing/timing.cpp +++ b/src/cpu/scpu/timing/timing.cpp @@ -1,255 +1,256 @@ #ifdef SCPU_CPP - -#include "irq.cpp" -#include "joypad.cpp" - -uint16 sCPU::vcounter() { return status.vcounter; } -uint16 sCPU::hcounter() { return status.hcounter; } -uint sCPU::dma_counter() { return (status.dma_counter + status.hcounter) & 7; } - -/***** - * One PPU dot = 4 CPU clocks - * - * PPU dots 323 and 327 are 6 CPU clocks long. - * This does not apply to NTSC non-interlace scanline 240 on odd fields. This is - * because the PPU skips one dot to alter the color burst phase of the video signal. - * - * Dot 323 range = { 1292, 1294, 1296 } - * Dot 327 range = { 1310, 1312, 1314 } - *****/ - -#define ntsc_color_burst_phase_shift_scanline() ( \ - snes.region() == SNES::NTSC && status.vcounter == 240 && \ - ppu.interlace() == false && ppu.field() == 1 \ -) - -uint16 sCPU::hdot() { - if(ntsc_color_burst_phase_shift_scanline() == true) return (status.hcounter >> 2); - return (status.hcounter - ((status.hcounter > 1292) << 1) - ((status.hcounter > 1310) << 1)) >> 2; -} - -void sCPU::add_clocks(uint clocks) { - if(status.dram_refreshed == false) { - if(status.hcounter + clocks >= status.dram_refresh_position) { - status.dram_refreshed = true; - clocks += 40; - } - } - - counter.sub(counter.irq_delay, clocks); - scheduler.addclocks_cpu(clocks); - - clocks >>= 1; + +#include "irq.cpp" +#include "joypad.cpp" + +unsigned sCPU::dma_counter() { + return (status.dma_counter + status.hcounter) & 7; +} + +/***** + * One PPU dot = 4 CPU clocks + * + * PPU dots 323 and 327 are 6 CPU clocks long. + * This does not apply to NTSC non-interlace scanline 240 on odd fields. This is + * because the PPU skips one dot to alter the color burst phase of the video signal. + * + * Dot 323 range = { 1292, 1294, 1296 } + * Dot 327 range = { 1310, 1312, 1314 } + *****/ + +#define ntsc_color_burst_phase_shift_scanline() ( \ + snes.region() == SNES::NTSC && status.vcounter == 240 && \ + ppu.interlace() == false && ppu.field() == 1 \ +) + +uint16 sCPU::hdot() { + if(ntsc_color_burst_phase_shift_scanline() == true) return (status.hcounter >> 2); + return (status.hcounter - ((status.hcounter > 1292) << 1) - ((status.hcounter > 1310) << 1)) >> 2; +} + +void sCPU::add_clocks(unsigned clocks) { + if(status.dram_refreshed == false) { + if(status.hcounter + clocks >= status.dram_refresh_position) { + status.dram_refreshed = true; + clocks += 40; + } + } + + counter.sub(counter.irq_delay, clocks); + scheduler.addclocks_cpu(clocks); + + clocks >>= 1; while(clocks--) { - history.enqueue(status.vcounter, status.hcounter); - status.hcounter += 2; - if(status.hcounter >= status.line_clocks) scanline(); - poll_interrupts(); - } -} - -void sCPU::scanline() { - status.hcounter = 0; - status.dma_counter = (status.dma_counter + status.line_clocks) & 7; - if(++status.vcounter >= status.field_lines) frame(); - status.line_clocks = (ntsc_color_burst_phase_shift_scanline() == false) ? 1364 : 1360; - - //dram refresh occurs once every scanline - status.dram_refreshed = false; - if(cpu_version == 2) status.dram_refresh_position = 530 + 8 - dma_counter(); - - //hdma triggers once every visible scanline - status.line_rendered = false; - status.hdma_triggered = (status.vcounter <= (ppu.overscan() == false ? 224 : 239)) ? false : true; - - ppu.scanline(); - snes.scanline(); - - update_interrupts(); - - if(status.auto_joypad_poll == true && status.vcounter == (ppu.overscan() == false ? 227 : 242)) { - snes.input.poll(); - run_auto_joypad_poll(); - } -} - + history.enqueue(status.vcounter, status.hcounter); + status.hcounter += 2; + if(status.hcounter >= status.line_clocks) scanline(); + poll_interrupts(); + snes.input.tick(); + } +} + +void sCPU::scanline() { + status.hcounter = 0; + status.dma_counter = (status.dma_counter + status.line_clocks) & 7; + if(++status.vcounter >= status.field_lines) frame(); + status.line_clocks = (ntsc_color_burst_phase_shift_scanline() == false) ? 1364 : 1360; + + //dram refresh occurs once every scanline + status.dram_refreshed = false; + if(cpu_version == 2) status.dram_refresh_position = 530 + 8 - dma_counter(); + + //hdma triggers once every visible scanline + status.line_rendered = false; + status.hdma_triggered = (status.vcounter <= (ppu.overscan() == false ? 224 : 239)) ? false : true; + + ppu.scanline(); + snes.scanline(); + + update_interrupts(); + + if(status.auto_joypad_poll == true && status.vcounter == (ppu.overscan() == false ? 227 : 242)) { + snes.input.poll(); + run_auto_joypad_poll(); + } +} + void sCPU::frame() { ppu.frame(); snes.frame(); - - status.vcounter = 0; - status.field_lines = (snes.region() == SNES::NTSC ? 525 : 625) >> 1; - //interlaced even fields have one extra scanline (263+262=525 NTSC, 313+312=625 PAL) - if(ppu.interlace() == true && ppu.field() == 0) status.field_lines++; - - status.hdmainit_triggered = false; - if(cpu_version == 1) { - status.hdmainit_trigger_position = 12 + 8 - dma_counter(); - } else { - status.hdmainit_trigger_position = 12 + dma_counter(); - } -} - -/***** - * precycle_edge() - * - * Used for H/DMA bus synchronization - *****/ -void sCPU::precycle_edge() { - if(status.dma_state == DMA_CPUsync) { - add_clocks(status.clock_count - (status.dma_clocks % status.clock_count)); - status.dma_state = DMA_Inactive; - } -} - -/***** - * cycle_edge() - * - * Used to test for H/DMA, which can trigger on the edge of every opcode cycle. - *****/ -void sCPU::cycle_edge() { - if(status.line_rendered == false) { - if(status.hcounter >= status.line_render_position) { - status.line_rendered = true; - ppu.render_scanline(); - } - } - - if(status.hdmainit_triggered == false) { - if(status.hcounter >= status.hdmainit_trigger_position || status.vcounter) { - status.hdmainit_triggered = true; - hdma_init_reset(); - if(hdma_enabled_channels()) { - status.hdma_pending = true; - status.hdma_mode = 0; - } - } - } - - if(status.hdma_triggered == false) { - if(status.hcounter >= 1104) { - status.hdma_triggered = true; - if(hdma_active_channels()) { - status.hdma_pending = true; - status.hdma_mode = 1; - } - } - } - - //H/DMA pending && DMA inactive? - //.. Run one full CPU cycle - //.. HDMA pending && HDMA enabled ? DMA sync + HDMA run - //.. DMA pending && DMA enabled ? DMA sync + DMA run - //.... HDMA during DMA && HDMA enabled ? DMA sync + HDMA run - //.. Run one bus CPU cycle + + status.vcounter = 0; + status.field_lines = (snes.region() == SNES::NTSC ? 525 : 625) >> 1; + //interlaced even fields have one extra scanline (263+262=525 NTSC, 313+312=625 PAL) + if(ppu.interlace() == true && ppu.field() == 0) status.field_lines++; + + status.hdmainit_triggered = false; + if(cpu_version == 1) { + status.hdmainit_trigger_position = 12 + 8 - dma_counter(); + } else { + status.hdmainit_trigger_position = 12 + dma_counter(); + } +} + +/***** + * precycle_edge() + * + * Used for H/DMA bus synchronization + *****/ +void sCPU::precycle_edge() { + if(status.dma_state == DMA_CPUsync) { + add_clocks(status.clock_count - (status.dma_clocks % status.clock_count)); + status.dma_state = DMA_Inactive; + } +} + +/***** + * cycle_edge() + * + * Used to test for H/DMA, which can trigger on the edge of every opcode cycle. + *****/ +void sCPU::cycle_edge() { + if(status.line_rendered == false) { + if(status.hcounter >= status.line_render_position) { + status.line_rendered = true; + ppu.render_scanline(); + } + } + + if(status.hdmainit_triggered == false) { + if(status.hcounter >= status.hdmainit_trigger_position || status.vcounter) { + status.hdmainit_triggered = true; + hdma_init_reset(); + if(hdma_enabled_channels()) { + status.hdma_pending = true; + status.hdma_mode = 0; + } + } + } + + if(status.hdma_triggered == false) { + if(status.hcounter >= 1104) { + status.hdma_triggered = true; + if(hdma_active_channels()) { + status.hdma_pending = true; + status.hdma_mode = 1; + } + } + } + + //H/DMA pending && DMA inactive? + //.. Run one full CPU cycle + //.. HDMA pending && HDMA enabled ? DMA sync + HDMA run + //.. DMA pending && DMA enabled ? DMA sync + DMA run + //.... HDMA during DMA && HDMA enabled ? DMA sync + HDMA run + //.. Run one bus CPU cycle //.. CPU sync - - if(status.dma_state == DMA_Run) { - if(status.hdma_pending) { - status.hdma_pending = false; - if(hdma_enabled_channels()) { - dma_add_clocks(8 - dma_counter()); //DMA sync - status.hdma_mode == 0 ? hdma_init() : hdma_run(); - if(!dma_enabled_channels()) status.dma_state = DMA_CPUsync; - } - } - - if(status.dma_pending) { - status.dma_pending = false; - if(dma_enabled_channels()) { - dma_add_clocks(8 - dma_counter()); //DMA sync - dma_run(); - status.dma_state = DMA_CPUsync; - } - } - } - - if(status.dma_state == DMA_Inactive) { - if(status.dma_pending || status.hdma_pending) { - status.dma_clocks = 0; - status.dma_state = DMA_Run; - } - } -} - -/***** - * last_cycle() - * - * Used to test for NMI/IRQ, which can trigger on the edge of every opcode. - * Test one cycle early to simulate two-stage pipeline of x816 CPU. - * - * status.irq_delay is used to simulate hardware delay before interrupts can - * trigger during certain events (immediately after DMA, writes to $4200, etc) - *****/ -void sCPU::last_cycle() { - if(counter.irq_delay) return; - - status.nmi_pending |= nmi_test(); - status.irq_pending |= irq_test(); - - event.irq = (status.nmi_pending || status.irq_pending); -} - -void sCPU::timing_power() { -} - -void sCPU::timing_reset() { - counter.nmi_hold = 0; - counter.irq_hold = 0; - - counter.nmi_fire = 0; - counter.irq_fire = 0; - counter.irq_delay = 0; - counter.hw_math = 0; - - status.clock_count = 0; - - status.vcounter = 0; - status.hcounter = 0; - - status.field_lines = (snes.region() == SNES::NTSC ? 525 : 625) >> 1; - status.line_clocks = 1364; - - status.line_rendered = false; - status.line_render_position = min(1112U, (unsigned)config::ppu.hack.render_scanline_position); - - status.dram_refreshed = false; - status.dram_refresh_position = (cpu_version == 1) ? 530 : 538; - - status.hdmainit_triggered = false; - status.hdmainit_trigger_position = 0; - - status.hdma_triggered = false; - - status.irq_delay = 0; - - status.nmi_valid = false; - status.nmi_line = false; - status.nmi_transition = false; - status.nmi_pending = false; - - status.irq_valid = false; - status.irq_line = false; - status.irq_transition = false; - status.irq_pending = false; - - update_interrupts(); - - status.dma_counter = 0; - status.dma_clocks = 0; - status.dma_pending = false; - status.hdma_pending = false; - status.hdma_mode = 0; + + if(status.dma_state == DMA_Run) { + if(status.hdma_pending) { + status.hdma_pending = false; + if(hdma_enabled_channels()) { + dma_add_clocks(8 - dma_counter()); //DMA sync + status.hdma_mode == 0 ? hdma_init() : hdma_run(); + if(!dma_enabled_channels()) status.dma_state = DMA_CPUsync; + } + } + + if(status.dma_pending) { + status.dma_pending = false; + if(dma_enabled_channels()) { + dma_add_clocks(8 - dma_counter()); //DMA sync + dma_run(); + status.dma_state = DMA_CPUsync; + } + } + } + + if(status.dma_state == DMA_Inactive) { + if(status.dma_pending || status.hdma_pending) { + status.dma_clocks = 0; + status.dma_state = DMA_Run; + } + } +} + +/***** + * last_cycle() + * + * Used to test for NMI/IRQ, which can trigger on the edge of every opcode. + * Test one cycle early to simulate two-stage pipeline of x816 CPU. + * + * status.irq_delay is used to simulate hardware delay before interrupts can + * trigger during certain events (immediately after DMA, writes to $4200, etc) + *****/ +void sCPU::last_cycle() { + if(counter.irq_delay) return; + + status.nmi_pending |= nmi_test(); + status.irq_pending |= irq_test(); + + event.irq = (status.nmi_pending || status.irq_pending); +} + +void sCPU::timing_power() { +} + +void sCPU::timing_reset() { + counter.nmi_hold = 0; + counter.irq_hold = 0; + + counter.nmi_fire = 0; + counter.irq_fire = 0; + counter.irq_delay = 0; + counter.hw_math = 0; + + status.clock_count = 0; + + status.vcounter = 0; + status.hcounter = 0; + + status.field_lines = (snes.region() == SNES::NTSC ? 525 : 625) >> 1; + status.line_clocks = 1364; + + status.line_rendered = false; + status.line_render_position = min(1112U, (unsigned)config::ppu.hack.render_scanline_position); + + status.dram_refreshed = false; + status.dram_refresh_position = (cpu_version == 1) ? 530 : 538; + + status.hdmainit_triggered = false; + status.hdmainit_trigger_position = 0; + + status.hdma_triggered = false; + + status.irq_delay = 0; + + status.nmi_valid = false; + status.nmi_line = false; + status.nmi_transition = false; + status.nmi_pending = false; + + status.irq_valid = false; + status.irq_line = false; + status.irq_transition = false; + status.irq_pending = false; + + update_interrupts(); + + status.dma_counter = 0; + status.dma_clocks = 0; + status.dma_pending = false; + status.hdma_pending = false; + status.hdma_mode = 0; status.dma_state = DMA_Inactive; - history.reset(); - - //initial latch values for $213c/$213d - //[x]0035 : [y]0000 (53.0 -> 212) [lda $2137] - //[x]0038 : [y]0000 (56.5 -> 226) [nop : lda $2137] - add_clocks(186); -} - -#undef ntsc_color_burst_phase_shift_scanline + history.reset(); + + //initial latch values for $213c/$213d + //[x]0035 : [y]0000 (53.0 -> 212) [lda $2137] + //[x]0038 : [y]0000 (56.5 -> 226) [nop : lda $2137] + add_clocks(186); +} + +#undef ntsc_color_burst_phase_shift_scanline #endif //ifdef SCPU_CPP diff --git a/src/cpu/scpu/timing/timing.h b/src/cpu/scpu/timing/timing.h index 12185d57..347c0288 100644 --- a/src/cpu/scpu/timing/timing.h +++ b/src/cpu/scpu/timing/timing.h @@ -1,9 +1,9 @@ - uint16 vcounter(); - uint16 hcounter(); + alwaysinline uint16 vcounter() { return status.vcounter; } + alwaysinline uint16 hcounter() { return status.hcounter; } uint16 hdot(); - uint dma_counter(); + unsigned dma_counter(); - void add_clocks(uint clocks); + void add_clocks(unsigned clocks); void scanline(); void frame(); @@ -14,30 +14,30 @@ void timing_power(); void timing_reset(); - - //timeshifting -- needed by NMI and IRQ timing - struct History { - struct Time { - uint16 vcounter; - uint16 hcounter; - } time[32]; - unsigned index; - alwaysinline void enqueue(uint16 vcounter, uint16 hcounter) { - Time &t = time[index++]; - index &= 31; - t.vcounter = vcounter; - t.hcounter = hcounter; - } - alwaysinline void query(unsigned offset, uint16 &vcounter, uint16 &hcounter) { - Time &t = time[(index - (offset >> 1)) & 31]; - vcounter = t.vcounter; - hcounter = t.hcounter; - } - void reset() { - index = 0; - for(unsigned i = 0; i < 32; i++) time[i].vcounter = time[i].hcounter = 0; - } - History() { reset(); } + + //timeshifting -- needed by NMI and IRQ timing + struct History { + struct Time { + uint16 vcounter; + uint16 hcounter; + } time[32]; + unsigned index; + alwaysinline void enqueue(uint16 vcounter, uint16 hcounter) { + Time &t = time[index++]; + index &= 31; + t.vcounter = vcounter; + t.hcounter = hcounter; + } + alwaysinline void query(unsigned offset, uint16 &vcounter, uint16 &hcounter) { + Time &t = time[(index - (offset >> 1)) & 31]; + vcounter = t.vcounter; + hcounter = t.hcounter; + } + void reset() { + index = 0; + for(unsigned i = 0; i < 32; i++) time[i].vcounter = time[i].hcounter = 0; + } + History() { reset(); } } history; //irq.cpp diff --git a/src/lib/bbase.h b/src/lib/bbase.h index 4e90f0cd..cbf64f4b 100644 --- a/src/lib/bbase.h +++ b/src/lib/bbase.h @@ -1,5 +1,5 @@ /* - bbase : version 0.15 ~byuu (2008-09-14) + bbase : version 0.17 ~byuu (2008-10-19) license: public domain */ @@ -15,7 +15,7 @@ typedef uint8_t uint8; typedef uint16_t uint16; typedef uint32_t uint32; typedef uint64_t uint64; -typedef unsigned int uint; +typedef unsigned uint; #include using std::min; @@ -76,85 +76,4 @@ using std::max; #define alwaysinline inline #endif -/***** - * OS localization - *****/ - -#if defined(_MSC_VER) || defined(__MINGW32__) -static char* realpath(const char *file_name, char *resolved_name) { - wchar_t filename[PATH_MAX] = L""; - _wfullpath(filename, utf16(file_name), PATH_MAX); - strcpy(resolved_name, utf8(filename)); - return resolved_name; -} - -static char* userpath(char *output) { - wchar_t path[PATH_MAX] = L"."; //failsafe - SHGetFolderPathW(0, CSIDL_APPDATA | CSIDL_FLAG_CREATE, 0, 0, path); - strcpy(output, utf8(path)); - return output; -} -#define mkdir(path) _wmkdir(utf16(path)) -#else -static char* userpath(char *output) { - strcpy(output, "."); //failsafe - struct passwd *userinfo = getpwuid(getuid()); - if(userinfo) { strcpy(output, userinfo->pw_dir); } - return output; -} -#define mkdir(path) (mkdir)(path, 0755); -#endif - -template inline T minmax(const T x) { - return (x < (T)min) ? (T)min : (x > (T)max) ? (T)max : x; -} - -/***** - * endian wrappers - *****/ - -#ifndef ARCH_MSB -//little-endian: uint8[] { 0x01, 0x02, 0x03, 0x04 } == 0x04030201 - #define order_lsb2(a,b) a,b - #define order_lsb3(a,b,c) a,b,c - #define order_lsb4(a,b,c,d) a,b,c,d - #define order_lsb5(a,b,c,d,e) a,b,c,d,e - #define order_lsb6(a,b,c,d,e,f) a,b,c,d,e,f - #define order_lsb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g - #define order_lsb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h - #define order_msb2(a,b) b,a - #define order_msb3(a,b,c) c,b,a - #define order_msb4(a,b,c,d) d,c,b,a - #define order_msb5(a,b,c,d,e) e,d,c,b,a - #define order_msb6(a,b,c,d,e,f) f,e,d,c,b,a - #define order_msb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a - #define order_msb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a -#else -//big-endian: uint8[] { 0x01, 0x02, 0x03, 0x04 } == 0x01020304 - #define order_lsb2(a,b) b,a - #define order_lsb3(a,b,c) c,b,a - #define order_lsb4(a,b,c,d) d,c,b,a - #define order_lsb5(a,b,c,d,e) e,d,c,b,a - #define order_lsb6(a,b,c,d,e,f) f,e,d,c,b,a - #define order_lsb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a - #define order_lsb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a - #define order_msb2(a,b) a,b - #define order_msb3(a,b,c) a,b,c - #define order_msb4(a,b,c,d) a,b,c,d - #define order_msb5(a,b,c,d,e) a,b,c,d,e - #define order_msb6(a,b,c,d,e,f) a,b,c,d,e,f - #define order_msb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g - #define order_msb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h -#endif - -/***** - * libc extensions - *****/ - -//pseudo-random number generator -static unsigned prng() { - static unsigned n = 0; - return n = (n >> 1) ^ (((n & 1) - 1) & 0xedb88320); -} - #endif //ifndef BBASE_H diff --git a/src/lib/hiro/gtk/button.cpp b/src/lib/hiro/gtk/button.cpp index b01bfa79..bc9c1718 100644 --- a/src/lib/hiro/gtk/button.cpp +++ b/src/lib/hiro/gtk/button.cpp @@ -13,6 +13,7 @@ void pButton::create(unsigned style, unsigned width, unsigned height, const char void pButton::set_text(const char *text) { if(!button) return; gtk_button_set_label(GTK_BUTTON(button), text ? text : ""); + set_default_font(button); } pButton::pButton(Button &self_) : pFormControl(self_), self(self_) { diff --git a/src/lib/hiro/gtk/button.h b/src/lib/hiro/gtk/button.hpp similarity index 100% rename from src/lib/hiro/gtk/button.h rename to src/lib/hiro/gtk/button.hpp diff --git a/src/lib/hiro/gtk/canvas.cpp b/src/lib/hiro/gtk/canvas.cpp index 31b3e63c..5e571bb7 100644 --- a/src/lib/hiro/gtk/canvas.cpp +++ b/src/lib/hiro/gtk/canvas.cpp @@ -14,6 +14,20 @@ void hiro_pcanvas_expose(pCanvas *p) { GDK_RGB_DITHER_NONE, (guchar*)p->rbuffer, p->bpitch); } +gboolean hiro_pcanvas_button_press(GtkWidget *widget, GdkEventButton *event, pCanvas *p) { + if(p->self.on_input && event->button < mouse::buttons) { + p->self.on_input(event_t(event_t::Input, (mouse::button + event->button) + (1 << 16), &p->self)); + } + return false; //do not propogate the event to other handlers +} + +gboolean hiro_pcanvas_button_release(GtkWidget *widget, GdkEventButton *event, pCanvas *p) { + if(p->self.on_input && event->button < mouse::buttons) { + p->self.on_input(event_t(event_t::Input, (mouse::button + event->button) + (0 << 16), &p->self)); + } + return false; //do not propogate the event to other handlers +} + void pCanvas::create(unsigned style, unsigned width, unsigned height) { canvas = gtk_drawing_area_new(); resize(width, height); @@ -21,8 +35,11 @@ void pCanvas::create(unsigned style, unsigned width, unsigned height) { color.pixel = color.red = color.green = color.blue = 0; gtk_widget_modify_bg(canvas, GTK_STATE_NORMAL, &color); gtk_widget_set_double_buffered(canvas, false); + gtk_widget_add_events(canvas, GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); gtk_widget_show(canvas); g_signal_connect_swapped(G_OBJECT(canvas), "expose_event", G_CALLBACK(hiro_pcanvas_expose), (gpointer)this); + g_signal_connect(G_OBJECT(canvas), "button_press_event", G_CALLBACK(hiro_pcanvas_button_press), (gpointer)this); + g_signal_connect(G_OBJECT(canvas), "button_release_event", G_CALLBACK(hiro_pcanvas_button_release), (gpointer)this); } void pCanvas::redraw() { diff --git a/src/lib/hiro/gtk/canvas.h b/src/lib/hiro/gtk/canvas.hpp similarity index 100% rename from src/lib/hiro/gtk/canvas.h rename to src/lib/hiro/gtk/canvas.hpp diff --git a/src/lib/hiro/gtk/checkbox.h b/src/lib/hiro/gtk/checkbox.hpp similarity index 100% rename from src/lib/hiro/gtk/checkbox.h rename to src/lib/hiro/gtk/checkbox.hpp diff --git a/src/lib/hiro/gtk/combobox.h b/src/lib/hiro/gtk/combobox.hpp similarity index 100% rename from src/lib/hiro/gtk/combobox.h rename to src/lib/hiro/gtk/combobox.hpp diff --git a/src/lib/hiro/gtk/editbox.cpp b/src/lib/hiro/gtk/editbox.cpp index 81780f66..18ff92a6 100644 --- a/src/lib/hiro/gtk/editbox.cpp +++ b/src/lib/hiro/gtk/editbox.cpp @@ -1,3 +1,9 @@ +void hiro_peditbox_change(pEditbox *p) { + if(p->self.on_change) { + p->self.on_change(event_t(event_t::Change, 0, &p->self)); + } +} + void pEditbox::create(unsigned style, unsigned width, unsigned height, const char *text) { multiline = bool(style & Editbox::Multiline); @@ -7,6 +13,7 @@ void pEditbox::create(unsigned style, unsigned width, unsigned height, const cha gtk_entry_set_text(GTK_ENTRY(editbox), text ? text : ""); gtk_widget_set_size_request(editbox, width, height); gtk_widget_show(editbox); + g_signal_connect_swapped(G_OBJECT(editbox), "changed", G_CALLBACK(hiro_peditbox_change), (gpointer)this); } else { GtkPolicyType hscroll = (style & Editbox::HorizontalScrollAlways) ? GTK_POLICY_ALWAYS : (style & Editbox::HorizontalScrollNever) ? GTK_POLICY_NEVER : @@ -25,6 +32,7 @@ void pEditbox::create(unsigned style, unsigned width, unsigned height, const cha gtk_text_buffer_set_text(buffer, text ? text : "", -1); gtk_widget_show(editbox); gtk_widget_show(scrollbox); + g_signal_connect_swapped(G_OBJECT(buffer), "changed", G_CALLBACK(hiro_peditbox_change), (gpointer)this); } set_default_font(editbox); diff --git a/src/lib/hiro/gtk/editbox.h b/src/lib/hiro/gtk/editbox.hpp similarity index 100% rename from src/lib/hiro/gtk/editbox.h rename to src/lib/hiro/gtk/editbox.hpp diff --git a/src/lib/hiro/gtk/formcontrol.h b/src/lib/hiro/gtk/formcontrol.hpp similarity index 100% rename from src/lib/hiro/gtk/formcontrol.h rename to src/lib/hiro/gtk/formcontrol.hpp diff --git a/src/lib/hiro/gtk/frame.h b/src/lib/hiro/gtk/frame.hpp similarity index 100% rename from src/lib/hiro/gtk/frame.h rename to src/lib/hiro/gtk/frame.hpp diff --git a/src/lib/hiro/gtk/hiro.cpp b/src/lib/hiro/gtk/hiro.cpp index 30e726d9..b711b996 100644 --- a/src/lib/hiro/gtk/hiro.cpp +++ b/src/lib/hiro/gtk/hiro.cpp @@ -1,4 +1,5 @@ -#include "hiro.h" +#include "hiro.hpp" +#include "port.cpp" #include using nall::min; @@ -40,16 +41,6 @@ static void set_default_font(GtkWidget *widget) { #include "slider.cpp" void pHiro::init() { - //simulate passing argc, argv to gtk_init() - int argc = 1; - char **argv; - argv = (char**)malloc(1 * sizeof(char*)); - argv[0] = (char*)malloc(64 * sizeof(char)); - strcpy(argv[0], "./hiro"); - gtk_init(&argc, &argv); - free(argv[0]); - free(argv); - is_composited = false; *default_path = 0; screen = gdk_screen_get_default(); diff --git a/src/lib/hiro/gtk/hiro.h b/src/lib/hiro/gtk/hiro.hpp similarity index 62% rename from src/lib/hiro/gtk/hiro.h rename to src/lib/hiro/gtk/hiro.hpp index 86b74491..0c14c0f9 100644 --- a/src/lib/hiro/gtk/hiro.h +++ b/src/lib/hiro/gtk/hiro.hpp @@ -1,6 +1,10 @@ #ifndef HIRO_GTK_H #define HIRO_GTK_H +#include +#include +#include + #include #include #include @@ -8,28 +12,30 @@ #include #include +extern int hiromain(int argc, const char *const argv[]); + namespace libhiro { -#include "widget.h" - #include "window.h" - #include "menucontrol.h" - #include "menugroup.h" - #include "menuitem.h" - #include "menucheckitem.h" - #include "menuradioitem.h" - #include "menuseparator.h" - #include "formcontrol.h" - #include "frame.h" - #include "canvas.h" - #include "label.h" - #include "button.h" - #include "checkbox.h" - #include "radiobox.h" - #include "editbox.h" - #include "listbox.h" - #include "combobox.h" - #include "progressbar.h" - #include "slider.h" +#include "widget.hpp" + #include "window.hpp" + #include "menucontrol.hpp" + #include "menugroup.hpp" + #include "menuitem.hpp" + #include "menucheckitem.hpp" + #include "menuradioitem.hpp" + #include "menuseparator.hpp" + #include "formcontrol.hpp" + #include "frame.hpp" + #include "canvas.hpp" + #include "label.hpp" + #include "button.hpp" + #include "checkbox.hpp" + #include "radiobox.hpp" + #include "editbox.hpp" + #include "listbox.hpp" + #include "combobox.hpp" + #include "progressbar.hpp" + #include "slider.hpp" class pHiro { public: diff --git a/src/lib/hiro/gtk/label.h b/src/lib/hiro/gtk/label.hpp similarity index 100% rename from src/lib/hiro/gtk/label.h rename to src/lib/hiro/gtk/label.hpp diff --git a/src/lib/hiro/gtk/listbox.h b/src/lib/hiro/gtk/listbox.hpp similarity index 100% rename from src/lib/hiro/gtk/listbox.h rename to src/lib/hiro/gtk/listbox.hpp diff --git a/src/lib/hiro/gtk/menucheckitem.h b/src/lib/hiro/gtk/menucheckitem.hpp similarity index 100% rename from src/lib/hiro/gtk/menucheckitem.h rename to src/lib/hiro/gtk/menucheckitem.hpp diff --git a/src/lib/hiro/gtk/menucontrol.h b/src/lib/hiro/gtk/menucontrol.hpp similarity index 100% rename from src/lib/hiro/gtk/menucontrol.h rename to src/lib/hiro/gtk/menucontrol.hpp diff --git a/src/lib/hiro/gtk/menugroup.h b/src/lib/hiro/gtk/menugroup.hpp similarity index 100% rename from src/lib/hiro/gtk/menugroup.h rename to src/lib/hiro/gtk/menugroup.hpp diff --git a/src/lib/hiro/gtk/menuitem.h b/src/lib/hiro/gtk/menuitem.hpp similarity index 100% rename from src/lib/hiro/gtk/menuitem.h rename to src/lib/hiro/gtk/menuitem.hpp diff --git a/src/lib/hiro/gtk/menuradioitem.h b/src/lib/hiro/gtk/menuradioitem.hpp similarity index 100% rename from src/lib/hiro/gtk/menuradioitem.h rename to src/lib/hiro/gtk/menuradioitem.hpp diff --git a/src/lib/hiro/gtk/menuseparator.h b/src/lib/hiro/gtk/menuseparator.hpp similarity index 100% rename from src/lib/hiro/gtk/menuseparator.h rename to src/lib/hiro/gtk/menuseparator.hpp diff --git a/src/lib/hiro/gtk/port.cpp b/src/lib/hiro/gtk/port.cpp new file mode 100644 index 00000000..6e3aaa0c --- /dev/null +++ b/src/lib/hiro/gtk/port.cpp @@ -0,0 +1,17 @@ +char* userpath(char *output) { + struct passwd *userinfo = getpwuid(getuid()); + if(userinfo) { strcpy(output, userinfo->pw_dir); } + return output; +} + +int mkdir(const char *path) { + return mkdir(path, 0755); +} + +int main(int argc, char *argv[]) { + gtk_init(&argc, &argv); + libhiro::hiro().init(); + int result = hiromain(argc, argv); + libhiro::hiro().term(); + return result; +} diff --git a/src/lib/hiro/gtk/progressbar.h b/src/lib/hiro/gtk/progressbar.hpp similarity index 100% rename from src/lib/hiro/gtk/progressbar.h rename to src/lib/hiro/gtk/progressbar.hpp diff --git a/src/lib/hiro/gtk/radiobox.h b/src/lib/hiro/gtk/radiobox.hpp similarity index 100% rename from src/lib/hiro/gtk/radiobox.h rename to src/lib/hiro/gtk/radiobox.hpp diff --git a/src/lib/hiro/gtk/slider.h b/src/lib/hiro/gtk/slider.hpp similarity index 100% rename from src/lib/hiro/gtk/slider.h rename to src/lib/hiro/gtk/slider.hpp diff --git a/src/lib/hiro/gtk/widget.h b/src/lib/hiro/gtk/widget.hpp similarity index 100% rename from src/lib/hiro/gtk/widget.h rename to src/lib/hiro/gtk/widget.hpp diff --git a/src/lib/hiro/gtk/window.cpp b/src/lib/hiro/gtk/window.cpp index b9fca1d5..e234027c 100644 --- a/src/lib/hiro/gtk/window.cpp +++ b/src/lib/hiro/gtk/window.cpp @@ -25,12 +25,20 @@ static gboolean hiro_pwindow_expose(pWindow *p) { } static gint hiro_pwindow_keydown(GtkWidget *w, GdkEventKey *key, pWindow *p) { - if(p && p->self.on_keydown) p->self.on_keydown(event_t(event_t::KeyDown, phiro().translate_key(key->keyval), &p->self)); + if(p && p->self.on_input) { + p->self.on_input(event_t(event_t::Input, + phiro().translate_key(key->keyval) + (1 << 16), + &p->self)); + } return FALSE; } static gint hiro_pwindow_keyup(GtkWidget *w, GdkEventKey *key, pWindow *p) { - if(p && p->self.on_keyup) p->self.on_keyup(event_t(event_t::KeyUp, phiro().translate_key(key->keyval), &p->self)); + if(p && p->self.on_input) { + p->self.on_input(event_t(event_t::Input, + phiro().translate_key(key->keyval) + (0 << 16), + &p->self)); + } return FALSE; } diff --git a/src/lib/hiro/gtk/window.h b/src/lib/hiro/gtk/window.hpp similarity index 100% rename from src/lib/hiro/gtk/window.h rename to src/lib/hiro/gtk/window.hpp diff --git a/src/lib/hiro/hiro.cpp b/src/lib/hiro/hiro.cpp index aac11a7f..252f4210 100644 --- a/src/lib/hiro/hiro.cpp +++ b/src/lib/hiro/hiro.cpp @@ -1,4 +1,4 @@ -#include "hiro.h" +#include "hiro.hpp" using namespace nall; #if defined(_WIN32) diff --git a/src/lib/hiro/hiro.h b/src/lib/hiro/hiro.hpp similarity index 97% rename from src/lib/hiro/hiro.h rename to src/lib/hiro/hiro.hpp index 3ab7a966..85135fae 100644 --- a/src/lib/hiro/hiro.h +++ b/src/lib/hiro/hiro.hpp @@ -1,6 +1,6 @@ /* hiro - version: 0.006 (2008-08-12) + version: 0.007b (2008-10-26) author: byuu license: public domain */ @@ -16,6 +16,10 @@ #include #include +extern char* realpath(const char*, char*); +extern char* userpath(char*); +int mkdir(const char*); + namespace libhiro { class pHiro; @@ -92,8 +96,7 @@ struct event_t { enum type_t { Close, Block, - KeyDown, - KeyUp, + Input, Change, Tick, Activate, @@ -225,8 +228,7 @@ public: nall::function on_close; nall::function on_block; - nall::function on_keydown; - nall::function on_keyup; + nall::function on_input; Window(); @@ -352,6 +354,8 @@ public: Canvas(); + nall::function on_input; + private: pFriends; pCanvas &p; @@ -435,6 +439,8 @@ public: unsigned get_text(char *text, unsigned length = -1U); void set_text(const char *text = ""); + nall::function on_change; + Editbox(); private: diff --git a/src/lib/hiro/win/button.h b/src/lib/hiro/win/button.hpp similarity index 100% rename from src/lib/hiro/win/button.h rename to src/lib/hiro/win/button.hpp diff --git a/src/lib/hiro/win/canvas.h b/src/lib/hiro/win/canvas.hpp similarity index 100% rename from src/lib/hiro/win/canvas.h rename to src/lib/hiro/win/canvas.hpp diff --git a/src/lib/hiro/win/checkbox.h b/src/lib/hiro/win/checkbox.hpp similarity index 100% rename from src/lib/hiro/win/checkbox.h rename to src/lib/hiro/win/checkbox.hpp diff --git a/src/lib/hiro/win/combobox.h b/src/lib/hiro/win/combobox.hpp similarity index 100% rename from src/lib/hiro/win/combobox.h rename to src/lib/hiro/win/combobox.hpp diff --git a/src/lib/hiro/win/editbox.h b/src/lib/hiro/win/editbox.hpp similarity index 100% rename from src/lib/hiro/win/editbox.h rename to src/lib/hiro/win/editbox.hpp diff --git a/src/lib/hiro/win/formcontrol.h b/src/lib/hiro/win/formcontrol.hpp similarity index 100% rename from src/lib/hiro/win/formcontrol.h rename to src/lib/hiro/win/formcontrol.hpp diff --git a/src/lib/hiro/win/frame.h b/src/lib/hiro/win/frame.hpp similarity index 100% rename from src/lib/hiro/win/frame.h rename to src/lib/hiro/win/frame.hpp diff --git a/src/lib/hiro/win/hiro.cpp b/src/lib/hiro/win/hiro.cpp index fb05ab5c..2a361409 100644 --- a/src/lib/hiro/win/hiro.cpp +++ b/src/lib/hiro/win/hiro.cpp @@ -1,12 +1,5 @@ -#include "hiro.h" - -#include -using nall::min; -using nall::max; - -#include -using nall::utf8; -using nall::utf16; +#include "hiro.hpp" +#include "port.cpp" namespace libhiro { @@ -66,10 +59,14 @@ bool pHiro::run() { MSG msg; if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { //TODO: IsDialogMessage() does not clear keyboard buffer, but is required for tab key to work ... - //if(!IsDialogMessage(GetParent(msg.hwnd) ? GetParent(msg.hwnd) : msg.hwnd, &msg)) { + #if defined(HIRO_WIN_TABSTOP) + if(!IsDialogMessage(GetParent(msg.hwnd) ? GetParent(msg.hwnd) : msg.hwnd, &msg)) { + #endif TranslateMessage(&msg); DispatchMessage(&msg); - //} + #if defined(HIRO_WIN_TABSTOP) + } + #endif } return pending(); } @@ -147,7 +144,7 @@ bool pHiro::file_open(Window *focus, char *filename, const char *path, const cha ofn.lpstrInitialDir = wdir; ofn.lpstrFile = wfilename; ofn.nMaxFile = MAX_PATH; - ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST; + ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; ofn.lpstrDefExt = L""; bool result = GetOpenFileName(&ofn); @@ -195,7 +192,7 @@ bool pHiro::file_save(Window *focus, char *filename, const char *path, const cha ofn.lpstrInitialDir = wdir; ofn.lpstrFile = wfilename; ofn.nMaxFile = MAX_PATH; - ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST; + ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; ofn.lpstrDefExt = L""; bool result = GetSaveFileName(&ofn); @@ -290,13 +287,29 @@ LRESULT pHiro::wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { case WM_KEYDOWN: { if(!p || p->self.type != Widget::WindowType) break; Window &w = ((pWindow*)p)->self; - if(w.on_keydown) w.on_keydown(event_t(event_t::KeyDown, translate_key(wparam), &w)); + if(w.on_input) w.on_input(event_t(event_t::Input, translate_key(wparam) + (1 << 16), &w)); } break; case WM_KEYUP: { if(!p || p->self.type != Widget::WindowType) break; Window &w = ((pWindow*)p)->self; - if(w.on_keyup) w.on_keyup(event_t(event_t::KeyUp, translate_key(wparam), &w)); + if(w.on_input) w.on_input(event_t(event_t::Input, translate_key(wparam) + (0 << 16), &w)); + } break; + + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: { + if(!p || p->self.type != Widget::CanvasType) break; + Canvas &canvas = ((pCanvas*)p)->self; + uintptr_t param = (msg == WM_LBUTTONDOWN ? mouse::button + 0 : mouse::button + 1) + (1 << 16); + if(canvas.on_input) canvas.on_input(event_t(event_t::Input, param, &canvas)); + } break; + + case WM_LBUTTONUP: + case WM_RBUTTONUP: { + if(!p || p->self.type != Widget::CanvasType) break; + Canvas &canvas = ((pCanvas*)p)->self; + uintptr_t param = (msg == WM_LBUTTONUP ? mouse::button + 0 : mouse::button + 1) + (0 << 16); + if(canvas.on_input) canvas.on_input(event_t(event_t::Input, param, &canvas)); } break; case WM_ERASEBKGND: { @@ -327,32 +340,45 @@ LRESULT pHiro::wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { MenuItem &w = (MenuItem&)*widget; if(w.on_tick) w.on_tick(event_t(event_t::Tick, 0, &w)); } break; + case Widget::MenuCheckItemType: { MenuCheckItem &w = (MenuCheckItem&)*widget; w.check(!w.checked()); //invert check state if(w.on_tick) w.on_tick(event_t(event_t::Tick, w.checked(), &w)); } break; + case Widget::MenuRadioItemType: { MenuRadioItem &w = (MenuRadioItem&)*widget; bool checked = w.checked(); w.check(); if(!checked && w.on_tick) w.on_tick(event_t(event_t::Tick, w.checked(), &w)); } break; + case Widget::ButtonType: { Button &w = (Button&)*widget; if(w.on_tick) w.on_tick(event_t(event_t::Tick, 0, &w)); } break; + case Widget::CheckboxType: { Checkbox &w = (Checkbox&)*widget; w.check(!w.checked()); //invert check state if(w.on_tick) w.on_tick(event_t(event_t::Tick, w.checked(), &w)); } break; + case Widget::RadioboxType: { Radiobox &w = (Radiobox&)*widget; bool checked = w.checked(); w.check(); if(!checked && w.on_tick) w.on_tick(event_t(event_t::Tick, w.checked(), &w)); } break; + + case Widget::EditboxType: { + Editbox &editbox = (Editbox&)*widget; + if(HIWORD(wparam) == EN_CHANGE) { + if(editbox.on_change) editbox.on_change(event_t(event_t::Change, 0, &editbox)); + } + } break; + case Widget::ComboboxType: { Combobox &combobox = (Combobox&)*widget; if(HIWORD(wparam) == CBN_SELCHANGE) { @@ -384,13 +410,28 @@ LRESULT pHiro::wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { switch(widget->type) { case Widget::ListboxType: { Listbox &listbox = (Listbox&)*widget; - if(((LPNMHDR)lparam)->code == LVN_ITEMCHANGED - && ((LPNMLISTVIEW)lparam)->uChanged & LVIF_STATE - && ListView_GetItemState(listbox.p.hwnd, ((LPNMLISTVIEW)lparam)->iItem, LVIS_FOCUSED) - && ListView_GetItemState(listbox.p.hwnd, ((LPNMLISTVIEW)lparam)->iItem, LVIS_SELECTED) - ) { - if(listbox.on_change) listbox.on_change(event_t(event_t::Change, listbox.get_selection(), &listbox)); - } else if(((LPNMHDR)lparam)->code == LVN_ITEMACTIVATE) { + LPNMHDR nmhdr = (LPNMHDR)lparam; + LPNMLISTVIEW nmlistview = (LPNMLISTVIEW)lparam; + + if(nmhdr->code == LVN_ITEMCHANGED && (nmlistview->uChanged & LVIF_STATE)) { + //LVN_ITEMCHANGED is sent whenever an item gains or loses either focus or selection; + //it does not send a special message to indicate that no items are focused or selected. + //it will send two messages when a different item gains selection -- the first to remove + //focus from the old item, the second to set selection to the new item. + //hiro sends only one message whenever an item changed (eg gained selection), + //including for deselection of all items. below code adapts win32 model to hiro model. + //(focused means an item has a dotted outline box around it, but is not highlighted.) + //(selected means an item is highlighted.) + if((nmlistview->uOldState & LVIS_FOCUSED) && !(nmlistview->uNewState & LVIS_FOCUSED)) { + listbox.p.lostfocus = true; + } else { + if((!(nmlistview->uOldState & LVIS_SELECTED) && (nmlistview->uNewState & LVIS_SELECTED)) + || (listbox.p.lostfocus == false && listbox.get_selection() == -1)) { + if(listbox.on_change) listbox.on_change(event_t(event_t::Change, listbox.get_selection(), &listbox)); + } + listbox.p.lostfocus = false; + } + } else if(nmhdr->code == LVN_ITEMACTIVATE) { if(listbox.on_activate) listbox.on_activate(event_t(event_t::Activate, listbox.get_selection(), &listbox)); } } break; diff --git a/src/lib/hiro/win/hiro.h b/src/lib/hiro/win/hiro.hpp similarity index 62% rename from src/lib/hiro/win/hiro.h rename to src/lib/hiro/win/hiro.hpp index 753f376c..16ab6d6c 100644 --- a/src/lib/hiro/win/hiro.h +++ b/src/lib/hiro/win/hiro.hpp @@ -5,39 +5,53 @@ #undef _WIN32_WINNT #undef _WIN32_IE #undef NOMINMAX +#undef _NO_OLDNAMES #define WINVER 0x0501 #define _WIN32_WINNT 0x0501 #define _WIN32_IE 0x0600 #define NOMINMAX +#define _NO_OLDNAMES #define UNICODE #include #include +#include +#include #include +#include +using nall::min; +using nall::max; + +#include +using nall::utf8; +using nall::utf16; + +extern int hiromain(int argc, const char *const argv[]); + namespace libhiro { -#include "widget.h" - #include "window.h" - #include "menucontrol.h" - #include "menugroup.h" - #include "menuitem.h" - #include "menucheckitem.h" - #include "menuradioitem.h" - #include "menuseparator.h" - #include "formcontrol.h" - #include "frame.h" - #include "canvas.h" - #include "label.h" - #include "button.h" - #include "checkbox.h" - #include "radiobox.h" - #include "editbox.h" - #include "listbox.h" - #include "combobox.h" - #include "progressbar.h" - #include "slider.h" +#include "widget.hpp" + #include "window.hpp" + #include "menucontrol.hpp" + #include "menugroup.hpp" + #include "menuitem.hpp" + #include "menucheckitem.hpp" + #include "menuradioitem.hpp" + #include "menuseparator.hpp" + #include "formcontrol.hpp" + #include "frame.hpp" + #include "canvas.hpp" + #include "label.hpp" + #include "button.hpp" + #include "checkbox.hpp" + #include "radiobox.hpp" + #include "editbox.hpp" + #include "listbox.hpp" + #include "combobox.hpp" + #include "progressbar.hpp" + #include "slider.hpp" class pHiro { public: diff --git a/src/lib/hiro/win/label.h b/src/lib/hiro/win/label.hpp similarity index 100% rename from src/lib/hiro/win/label.h rename to src/lib/hiro/win/label.hpp diff --git a/src/lib/hiro/win/listbox.cpp b/src/lib/hiro/win/listbox.cpp index 2934e045..e8f19851 100644 --- a/src/lib/hiro/win/listbox.cpp +++ b/src/lib/hiro/win/listbox.cpp @@ -94,4 +94,5 @@ void pListbox::reset() { pListbox::pListbox(Listbox &self_) : pFormControl(self_), self(self_) { column_count = 0; + lostfocus = false; //used for message parsing } diff --git a/src/lib/hiro/win/listbox.h b/src/lib/hiro/win/listbox.hpp similarity index 96% rename from src/lib/hiro/win/listbox.h rename to src/lib/hiro/win/listbox.hpp index 1aeaad50..bb00bd1b 100644 --- a/src/lib/hiro/win/listbox.h +++ b/src/lib/hiro/win/listbox.hpp @@ -14,4 +14,5 @@ public: /* internal */ unsigned column_count; + bool lostfocus; }; diff --git a/src/lib/hiro/win/menucheckitem.cpp b/src/lib/hiro/win/menucheckitem.cpp index 45bf6a63..bb816a72 100644 --- a/src/lib/hiro/win/menucheckitem.cpp +++ b/src/lib/hiro/win/menucheckitem.cpp @@ -19,5 +19,22 @@ bool pMenuCheckItem::checked() { return info.fState & MFS_CHECKED; } +void pMenuCheckItem::enable(bool state) { + EnableMenuItem(parent, instance, MF_BYCOMMAND | (state ? MF_ENABLED : MF_GRAYED)); +} + +void pMenuCheckItem::disable() { + enable(false); +} + +bool pMenuCheckItem::enabled() { + MENUITEMINFO info; + memset(&info, 0, sizeof info); + info.cbSize = sizeof info; + info.fMask = MIIM_STATE; + GetMenuItemInfo(parent, instance, false, &info); + return info.fState & MFS_ENABLED; +} + pMenuCheckItem::pMenuCheckItem(MenuCheckItem &self_) : pMenuControl(self_), self(self_) { } diff --git a/src/lib/hiro/win/menucheckitem.h b/src/lib/hiro/win/menucheckitem.hpp similarity index 77% rename from src/lib/hiro/win/menucheckitem.h rename to src/lib/hiro/win/menucheckitem.hpp index 9bcacade..bf8a944c 100644 --- a/src/lib/hiro/win/menucheckitem.h +++ b/src/lib/hiro/win/menucheckitem.hpp @@ -5,6 +5,10 @@ public: void uncheck(); bool checked(); + void enable(bool = true); + void disable(); + bool enabled(); + MenuCheckItem &self; pMenuCheckItem(MenuCheckItem&); }; diff --git a/src/lib/hiro/win/menucontrol.cpp b/src/lib/hiro/win/menucontrol.cpp index ffea43a6..55d3de55 100644 --- a/src/lib/hiro/win/menucontrol.cpp +++ b/src/lib/hiro/win/menucontrol.cpp @@ -1,18 +1,11 @@ -void pMenuControl::enable(bool state) { - EnableMenuItem(parent, instance, MF_BYCOMMAND | (state ? MF_ENABLED : MF_GRAYED)); +void pMenuControl::enable(bool) { } void pMenuControl::disable() { - enable(false); } bool pMenuControl::enabled() { -MENUITEMINFO info; - memset(&info, 0, sizeof info); - info.cbSize = sizeof info; - info.fMask = MIIM_STATE; - GetMenuItemInfo(parent, instance, false, &info); - return info.fState & MFS_ENABLED; + return true; } pMenuControl::pMenuControl(MenuControl &self_) : pWidget(self_), self(self_) { diff --git a/src/lib/hiro/win/menucontrol.h b/src/lib/hiro/win/menucontrol.hpp similarity index 66% rename from src/lib/hiro/win/menucontrol.h rename to src/lib/hiro/win/menucontrol.hpp index 806a57cb..bdf5e874 100644 --- a/src/lib/hiro/win/menucontrol.h +++ b/src/lib/hiro/win/menucontrol.hpp @@ -1,8 +1,8 @@ class pMenuControl : public pWidget { public: - void enable(bool = true); - void disable(); - bool enabled(); + virtual void enable(bool = true); + virtual void disable(); + virtual bool enabled(); MenuControl &self; pMenuControl(MenuControl&); diff --git a/src/lib/hiro/win/menugroup.cpp b/src/lib/hiro/win/menugroup.cpp index bf043d17..7e82ae9f 100644 --- a/src/lib/hiro/win/menugroup.cpp +++ b/src/lib/hiro/win/menugroup.cpp @@ -26,6 +26,23 @@ void pMenuGroup::attach(MenuControl &menucontrol) { menucontrol.p.parent = group; } +void pMenuGroup::enable(bool state) { + EnableMenuItem(parent, (unsigned)group, MF_BYCOMMAND | (state ? MF_ENABLED : MF_GRAYED)); +} + +void pMenuGroup::disable() { + enable(false); +} + +bool pMenuGroup::enabled() { + MENUITEMINFO info; + memset(&info, 0, sizeof info); + info.cbSize = sizeof info; + info.fMask = MIIM_STATE; + GetMenuItemInfo(parent, (unsigned)group, false, &info); + return info.fState & MFS_ENABLED; +} + pMenuGroup::pMenuGroup(MenuGroup &self_) : pMenuControl(self_), self(self_) { group = 0; } diff --git a/src/lib/hiro/win/menugroup.h b/src/lib/hiro/win/menugroup.hpp similarity index 75% rename from src/lib/hiro/win/menugroup.h rename to src/lib/hiro/win/menugroup.hpp index 783ed800..4a0cfd2a 100644 --- a/src/lib/hiro/win/menugroup.h +++ b/src/lib/hiro/win/menugroup.hpp @@ -4,6 +4,10 @@ public: void create(const char *text); void attach(MenuControl &menucontrol); + void enable(bool = true); + void disable(); + bool enabled(); + pMenuGroup(MenuGroup&); /* internal */ diff --git a/src/lib/hiro/win/menuitem.cpp b/src/lib/hiro/win/menuitem.cpp index 98ea66c2..9b90657c 100644 --- a/src/lib/hiro/win/menuitem.cpp +++ b/src/lib/hiro/win/menuitem.cpp @@ -2,5 +2,22 @@ void pMenuItem::create(const char *text_) { text = strdup(text_); } +void pMenuItem::enable(bool state) { + EnableMenuItem(parent, instance, MF_BYCOMMAND | (state ? MF_ENABLED : MF_GRAYED)); +} + +void pMenuItem::disable() { + enable(false); +} + +bool pMenuItem::enabled() { + MENUITEMINFO info; + memset(&info, 0, sizeof info); + info.cbSize = sizeof info; + info.fMask = MIIM_STATE; + GetMenuItemInfo(parent, instance, false, &info); + return info.fState & MFS_ENABLED; +} + pMenuItem::pMenuItem(MenuItem &self_) : pMenuControl(self_), self(self_) { } diff --git a/src/lib/hiro/win/menuitem.h b/src/lib/hiro/win/menuitem.hpp similarity index 67% rename from src/lib/hiro/win/menuitem.h rename to src/lib/hiro/win/menuitem.hpp index 715f8cc3..7166ffbe 100644 --- a/src/lib/hiro/win/menuitem.h +++ b/src/lib/hiro/win/menuitem.hpp @@ -2,6 +2,10 @@ class pMenuItem : public pMenuControl { public: void create(const char *text = ""); + void enable(bool = true); + void disable(); + bool enabled(); + MenuItem &self; pMenuItem(MenuItem&); }; diff --git a/src/lib/hiro/win/menuradioitem.cpp b/src/lib/hiro/win/menuradioitem.cpp index 582463b3..f379ad7a 100644 --- a/src/lib/hiro/win/menuradioitem.cpp +++ b/src/lib/hiro/win/menuradioitem.cpp @@ -19,6 +19,23 @@ bool pMenuRadioItem::checked() { return info.fState & MFS_CHECKED; } +void pMenuRadioItem::enable(bool state) { + EnableMenuItem(parent, instance, MF_BYCOMMAND | (state ? MF_ENABLED : MF_GRAYED)); +} + +void pMenuRadioItem::disable() { + enable(false); +} + +bool pMenuRadioItem::enabled() { + MENUITEMINFO info; + memset(&info, 0, sizeof info); + info.cbSize = sizeof info; + info.fMask = MIIM_STATE; + GetMenuItemInfo(parent, instance, false, &info); + return info.fState & MFS_ENABLED; +} + pMenuRadioItem::pMenuRadioItem(MenuRadioItem &self_) : pMenuControl(self_), self(self_) { create_checked = false; } diff --git a/src/lib/hiro/win/menuradioitem.h b/src/lib/hiro/win/menuradioitem.hpp similarity index 81% rename from src/lib/hiro/win/menuradioitem.h rename to src/lib/hiro/win/menuradioitem.hpp index 406663c6..58f5b6c5 100644 --- a/src/lib/hiro/win/menuradioitem.h +++ b/src/lib/hiro/win/menuradioitem.hpp @@ -4,6 +4,10 @@ public: void check(); bool checked(); + void enable(bool = true); + void disable(); + bool enabled(); + MenuRadioItem &self; pMenuRadioItem(MenuRadioItem&); diff --git a/src/lib/hiro/win/menuseparator.cpp b/src/lib/hiro/win/menuseparator.cpp index 4fa3e0c1..eecdb0c1 100644 --- a/src/lib/hiro/win/menuseparator.cpp +++ b/src/lib/hiro/win/menuseparator.cpp @@ -1,5 +1,22 @@ void pMenuSeparator::create() { } +void pMenuSeparator::enable(bool state) { + EnableMenuItem(parent, instance, MF_BYCOMMAND | (state ? MF_ENABLED : MF_GRAYED)); +} + +void pMenuSeparator::disable() { + enable(false); +} + +bool pMenuSeparator::enabled() { + MENUITEMINFO info; + memset(&info, 0, sizeof info); + info.cbSize = sizeof info; + info.fMask = MIIM_STATE; + GetMenuItemInfo(parent, instance, false, &info); + return info.fState & MFS_ENABLED; +} + pMenuSeparator::pMenuSeparator(MenuSeparator &self_) : pMenuControl(self_), self(self_) { } diff --git a/src/lib/hiro/win/menuseparator.h b/src/lib/hiro/win/menuseparator.hpp similarity index 66% rename from src/lib/hiro/win/menuseparator.h rename to src/lib/hiro/win/menuseparator.hpp index c270b58b..2dd883df 100644 --- a/src/lib/hiro/win/menuseparator.h +++ b/src/lib/hiro/win/menuseparator.hpp @@ -3,5 +3,9 @@ public: MenuSeparator &self; void create(); + void enable(bool = true); + void disable(); + bool enabled(); + pMenuSeparator(MenuSeparator&); }; diff --git a/src/lib/hiro/win/port.cpp b/src/lib/hiro/win/port.cpp new file mode 100644 index 00000000..deedf6a4 --- /dev/null +++ b/src/lib/hiro/win/port.cpp @@ -0,0 +1,40 @@ +char* realpath(const char *file_name, char *resolved_name) { + wchar_t filename[_MAX_PATH] = L""; + _wfullpath(filename, utf16(file_name), _MAX_PATH); + strcpy(resolved_name, utf8(filename)); + return resolved_name; +} + +char* userpath(char *output) { + wchar_t path[_MAX_PATH] = L""; + SHGetFolderPathW(0, CSIDL_APPDATA | CSIDL_FLAG_CREATE, 0, 0, path); + strcpy(output, utf8(path)); + return output; +} + +int mkdir(const char *path) { + return _wmkdir(utf16(path)); +} + +int __stdcall WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { + //argv[] is in 7-bit ANSI format; Unicode characters are converted to '?'s. + //this needs to be converted to UTF-8, eg for realpath(argv[0]) to work. + int argc; + wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &argc); + char **argv = new char*[argc]; + for(unsigned i = 0; i < argc; i++) { + argv[i] = new char[_MAX_PATH]; + strcpy(argv[i], utf8(wargv[i])); + } + + libhiro::hiro().init(); + int result = hiromain(argc, argv); + libhiro::hiro().term(); + + for(unsigned i = 0; i < argc; i++) { + delete[] argv[i]; + } + delete[] argv; + + return result; +} diff --git a/src/lib/hiro/win/progressbar.h b/src/lib/hiro/win/progressbar.hpp similarity index 100% rename from src/lib/hiro/win/progressbar.h rename to src/lib/hiro/win/progressbar.hpp diff --git a/src/lib/hiro/win/radiobox.h b/src/lib/hiro/win/radiobox.hpp similarity index 100% rename from src/lib/hiro/win/radiobox.h rename to src/lib/hiro/win/radiobox.hpp diff --git a/src/lib/hiro/win/slider.h b/src/lib/hiro/win/slider.hpp similarity index 100% rename from src/lib/hiro/win/slider.h rename to src/lib/hiro/win/slider.hpp diff --git a/src/lib/hiro/win/widget.h b/src/lib/hiro/win/widget.hpp similarity index 100% rename from src/lib/hiro/win/widget.h rename to src/lib/hiro/win/widget.hpp diff --git a/src/lib/hiro/win/window.h b/src/lib/hiro/win/window.hpp similarity index 100% rename from src/lib/hiro/win/window.h rename to src/lib/hiro/win/window.hpp diff --git a/src/lib/libfilter/libfilter.cpp b/src/lib/libfilter/libfilter.cpp index 0f984efa..c7af7aa6 100644 --- a/src/lib/libfilter/libfilter.cpp +++ b/src/lib/libfilter/libfilter.cpp @@ -1,4 +1,4 @@ -#include "libfilter.h" +#include "libfilter.hpp" using nall::min; using nall::max; diff --git a/src/lib/libfilter/libfilter.h b/src/lib/libfilter/libfilter.hpp similarity index 100% rename from src/lib/libfilter/libfilter.h rename to src/lib/libfilter/libfilter.hpp diff --git a/src/lib/nall/algorithm.hpp b/src/lib/nall/algorithm.hpp index f63d27be..bce2f70a 100644 --- a/src/lib/nall/algorithm.hpp +++ b/src/lib/nall/algorithm.hpp @@ -16,6 +16,12 @@ T max(const T& t, const U& u) { return t > u ? t : u; } +//pseudo-random number generator +inline unsigned prng() { + static unsigned n = 0; + return n = (n >> 1) ^ (((n & 1) - 1) & 0xedb88320); +} + } //namespace nall #endif //ifndef NALL_ALGORITHM_HPP diff --git a/src/lib/nall/array.hpp b/src/lib/nall/array.hpp index 84215c83..cd597e96 100644 --- a/src/lib/nall/array.hpp +++ b/src/lib/nall/array.hpp @@ -58,8 +58,8 @@ public: } void resize(unsigned size) { - reserve(findsize(size)); - buffersize = size; + if(size > poolsize) reserve(findsize(size)); + if(size > buffersize) buffersize = size; } array() { diff --git a/src/lib/nall/endian.hpp b/src/lib/nall/endian.hpp new file mode 100644 index 00000000..ccbd209c --- /dev/null +++ b/src/lib/nall/endian.hpp @@ -0,0 +1,38 @@ +#ifndef NALL_ENDIAN_HPP +#define NALL_ENDIAN_HPP + +#if !defined(ARCH_MSB) + //little-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x04030201 + #define order_lsb2(a,b) a,b + #define order_lsb3(a,b,c) a,b,c + #define order_lsb4(a,b,c,d) a,b,c,d + #define order_lsb5(a,b,c,d,e) a,b,c,d,e + #define order_lsb6(a,b,c,d,e,f) a,b,c,d,e,f + #define order_lsb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g + #define order_lsb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h + #define order_msb2(a,b) b,a + #define order_msb3(a,b,c) c,b,a + #define order_msb4(a,b,c,d) d,c,b,a + #define order_msb5(a,b,c,d,e) e,d,c,b,a + #define order_msb6(a,b,c,d,e,f) f,e,d,c,b,a + #define order_msb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a + #define order_msb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a +#else + //big-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x01020304 + #define order_lsb2(a,b) b,a + #define order_lsb3(a,b,c) c,b,a + #define order_lsb4(a,b,c,d) d,c,b,a + #define order_lsb5(a,b,c,d,e) e,d,c,b,a + #define order_lsb6(a,b,c,d,e,f) f,e,d,c,b,a + #define order_lsb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a + #define order_lsb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a + #define order_msb2(a,b) a,b + #define order_msb3(a,b,c) a,b,c + #define order_msb4(a,b,c,d) a,b,c,d + #define order_msb5(a,b,c,d,e) a,b,c,d,e + #define order_msb6(a,b,c,d,e,f) a,b,c,d,e,f + #define order_msb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g + #define order_msb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h +#endif + +#endif //ifndef NALL_ENDIAN_HPP diff --git a/src/lib/nall/input.hpp b/src/lib/nall/input.hpp index b4e660b3..b358c4b1 100644 --- a/src/lib/nall/input.hpp +++ b/src/lib/nall/input.hpp @@ -5,7 +5,6 @@ #include #include -#include #include namespace nall { @@ -31,41 +30,54 @@ struct keyboard { }; }; +struct mouse { + enum { buttons = 8 }; + + enum { + none = keyboard::limit, + x, y, z, + button, + limit = button + buttons, + }; +}; + template struct joypad { + enum { axes = 8 }; + enum { buttons = 96 }; + enum { none = joypad::limit, up, down, left, right, - button_00, button_01, button_02, button_03, - button_04, button_05, button_06, button_07, - button_08, button_09, button_10, button_11, - button_12, button_13, button_14, button_15, - limit, + axis, + button = axis + axes, + limit = button + buttons, }; }; template<> struct joypad<-1> { + enum { count = 16 }; + enum { axes = 8 }; + enum { buttons = 96 }; + enum { none, up, down, left, right, - button_00, button_01, button_02, button_03, - button_04, button_05, button_06, button_07, - button_08, button_09, button_10, button_11, - button_12, button_13, button_14, button_15, - length = button_15 - none + 1, //number of syms per joypad - limit = keyboard::limit, //start joypad syms immediately after keyboard syms + axis, + button = axis + axes, + length = button + buttons - none, //number of syms per joypad + limit = mouse::limit, }; - static uint16_t index(int joypad_number, int joypad_enum) { - if(joypad_number < 0 || joypad_number > 15) return keyboard::none; + static uint16_t index(unsigned joypad_number, unsigned joypad_enum) { + if(joypad_number >= count) return keyboard::none; return limit + joypad_number * length + joypad_enum; } }; -enum { input_limit = joypad<15>::limit }; +enum { input_limit = joypad::count - 1>::limit }; -static static_assert keyboard_length_assert; //error if keyboard syms spill into joypad syms - -static const char keyboard_table[][64] = { +static const char sym_table[][64] = { + //keyboard "none", "escape", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12", "print_screen", "scroll_lock", "pause", "tilde", @@ -81,24 +93,39 @@ static const char keyboard_table[][64] = { "up", "down", "left", "right", "tab", "return", "spacebar", "lctrl", "rctrl", "lalt", "ralt", "lshift", "rshift", "lsuper", "rsuper", "menu", - "limit", + "keyboard.limit", + + //mouse + "mouse.x", "mouse.y", "mouse.z", + "mouse.button00", "mouse.button01", "mouse.button02", "mouse.button03", + "mouse.button04", "mouse.button05", "mouse.button06", "mouse.button07", + "mouse.limit", }; static const char* input_find(uint16_t key) { - if(key < keyboard::limit) return keyboard_table[key]; + if(key < mouse::limit) return sym_table[key]; + static char buffer[64]; - for(uint16_t j = 0; j < 16; j++) { + for(unsigned j = 0; j < 16; j++) { if(key == joypad<>::index(j, joypad<>::up)) { sprintf(buffer, "joypad%0.2d.up", j); return buffer; } if(key == joypad<>::index(j, joypad<>::down)) { sprintf(buffer, "joypad%0.2d.down", j); return buffer; } if(key == joypad<>::index(j, joypad<>::left)) { sprintf(buffer, "joypad%0.2d.left", j); return buffer; } if(key == joypad<>::index(j, joypad<>::right)) { sprintf(buffer, "joypad%0.2d.right", j); return buffer; } - if(key >= joypad<>::index(j, joypad<>::button_00) - && key <= joypad<>::index(j, joypad<>::button_15)) { - sprintf(buffer, "joypad%0.2d.button_%0.2d", j, key - joypad<>::index(j, joypad<>::button_00)); + + if(key >= joypad<>::index(j, joypad<>::axis + 0) + && key < joypad<>::index(j, joypad<>::axis + joypad<>::axes)) { + sprintf(buffer, "joypad%0.2d.axis%0.2d", j, key - joypad<>::index(j, joypad<>::axis)); + return buffer; + } + + if(key >= joypad<>::index(j, joypad<>::button + 0) + && key < joypad<>::index(j, joypad<>::button + joypad<>::buttons)) { + sprintf(buffer, "joypad%0.2d.button%0.2d", j, key - joypad<>::index(j, joypad<>::button)); return buffer; } } - return keyboard_table[0]; //"none" + + return "none"; } static char* input_find(char *out, uint16_t key) { @@ -107,8 +134,8 @@ static char* input_find(char *out, uint16_t key) { } static uint16_t input_find(const char *key) { - for(uint16_t i = 0; i < keyboard::limit; i++) { - if(!strcmp(keyboard_table[i], key)) return i; + for(unsigned i = 0; i < mouse::limit; i++) { + if(!strcmp(sym_table[i], key)) return i; } if(memcmp(key, "joypad", 6)) return keyboard::none; @@ -116,17 +143,30 @@ static uint16_t input_find(const char *key) { if(!*key || !*(key + 1)) return keyboard::none; uint8_t j = (*key - '0') * 10 + (*(key + 1) - '0'); if(j > 15) return keyboard::none; + key += 2; if(!strcmp(key, ".up")) return joypad<>::index(j, joypad<>::up); if(!strcmp(key, ".down")) return joypad<>::index(j, joypad<>::down); if(!strcmp(key, ".left")) return joypad<>::index(j, joypad<>::left); if(!strcmp(key, ".right")) return joypad<>::index(j, joypad<>::right); - if(memcmp(key, ".button_", 8)) return keyboard::none; - key += 8; - if(!*key || !*(key + 1)) return keyboard::none; - uint8_t button = (*key - '0') * 10 + (*(key + 1) - '0'); - if(button > 15) return keyboard::none; - return joypad<>::index(j, joypad<>::button_00 + button); + + if(!memcmp(key, ".axis", 5)) { + key += 5; + if(!*key || !*(key + 1)) return keyboard::none; + uint8_t axis = (*key - '0') * 10 + (*(key + 1) - '0'); + if(axis >= joypad<>::axes) return keyboard::none; + return joypad<>::index(j, joypad<>::axis + axis); + } + + if(!memcmp(key, ".button", 7)) { + key += 7; + if(!*key || !*(key + 1)) return keyboard::none; + uint8_t button = (*key - '0') * 10 + (*(key + 1) - '0'); + if(button >= joypad<>::buttons) return keyboard::none; + return joypad<>::index(j, joypad<>::button + button); + } + + return keyboard::none; } } //namespace nall diff --git a/src/lib/nall/sort.hpp b/src/lib/nall/sort.hpp index 08106b76..23f88bbb 100644 --- a/src/lib/nall/sort.hpp +++ b/src/lib/nall/sort.hpp @@ -1,40 +1,62 @@ -#ifndef NALL_SORT_HPP +#ifndef NALL_SORT_HPP #define NALL_SORT_HPP +//class: merge sort +//average: O(n log n) +//worst: O(n log n) +//memory: O(n) +//stack: O(log n) +//stable?: yes + +//notes: +//there are two primary reasons for choosing merge sort +//over the (usually) faster quick sort*: +//1: it is a stable sort. +//2: it lacks O(n^2) worst-case overhead. +//(* which is also O(n log n) in the average case.) + namespace nall { -template inline void swap(T &x, T &y) { - T temp = x; - x = y; - y = temp; -} - -template -void sort(T list[], unsigned length) { - for(unsigned d = 0; d < length; d++) { - unsigned min = d; - for(unsigned s = d + 1; s < length; s++) { - if(list[s] < list[min]) { min = s; } - } - if(min != d) { - swap(list[d], list[min]); - } - } -} - -template -void sort(T list[], unsigned length, Comparator comparator) { - for(unsigned d = 0; d < length; d++) { - unsigned min = d; - for(unsigned s = d + 1; s < length; s++) { - if(comparator(list[s], list[min]) == true) { min = s; } - } - if(min != d) { - swap(list[d], list[min]); - } - } +template +void sort(T list[], unsigned length) { + if(length <= 1) return; //nothing to sort + + //use insertion sort to quickly sort smaller blocks + if(length < 64) { + for(unsigned i = 0; i < length; i++) { + unsigned min = i; + for(unsigned j = i + 1; j < length; j++) { + if(list[j] < list[min]) min = j; + } + if(min != i) swap(list[i], list[min]); + } + return; + } + + //split list in half and recursively sort both + unsigned middle = length / 2; + sort(list, middle); + sort(list + middle, length - middle); + + //left and right are sorted here; perform merge sort + T *buffer = new T[length]; + unsigned offset = 0; + unsigned left = 0; + unsigned right = middle; + while(left < middle && right < length) { + if(list[left] < list[right]) { + buffer[offset++] = list[left++]; + } else { + buffer[offset++] = list[right++]; + } + } + while(left < middle) buffer[offset++] = list[left++]; + while(right < length) buffer[offset++] = list[right++]; + + for(unsigned i = 0; i < length; i++) list[i] = buffer[i]; + delete[] buffer; } -} //namespace nall - -#endif //ifndef NALL_SORT_HPP +} //namespace nall + +#endif //ifndef NALL_SORT_HPP diff --git a/src/lib/nall/string/math.cpp b/src/lib/nall/string/math.cpp index 812ebd0d..bb0ac5b4 100644 --- a/src/lib/nall/string/math.cpp +++ b/src/lib/nall/string/math.cpp @@ -1,13 +1,13 @@ #ifdef NALL_STRING_CPP static int eval_integer(const char *&s) { - if(!*s) throw "nall::bad_eval_integer"; + if(!*s) throw "unrecognized_integer"; int value = 0, x = *s, y = *(s + 1); //hexadecimal if(x == '0' && (y == 'X' || y == 'x')) { s += 2; - for(;;) { + while(true) { if(*s >= '0' && *s <= '9') { value = value * 16 + (*s++ - '0'); continue; } if(*s >= 'A' && *s <= 'F') { value = value * 16 + (*s++ - 'A' + 10); continue; } if(*s >= 'a' && *s <= 'f') { value = value * 16 + (*s++ - 'a' + 10); continue; } @@ -18,7 +18,7 @@ static int eval_integer(const char *&s) { //binary if(x == '0' && (y == 'B' || y == 'b')) { s += 2; - for(;;) { + while(true) { if(*s == '0' || *s == '1') { value = value * 2 + (*s++ - '0'); continue; } return value; } @@ -27,7 +27,7 @@ static int eval_integer(const char *&s) { //octal (or decimal '0') if(x == '0') { s += 1; - for(;;) { + while(true) { if(*s >= '0' && *s <= '7') { value = value * 8 + (*s++ - '0'); continue; } return value; } @@ -35,35 +35,45 @@ static int eval_integer(const char *&s) { //decimal if(x >= '0' && x <= '9') { - for(;;) { + while(true) { if(*s >= '0' && *s <= '9') { value = value * 10 + (*s++ - '0'); continue; } return value; } } - throw "nall::bad_eval_integer"; + //char + if(x == '\'' && y != '\'') { + s += 1; + while(true) { + value = value * 256 + *s++; + if(*s == '\'') { s += 1; return value; } + if(!*s) throw "mismatched_char"; + } + } + + throw "unrecognized_integer"; } static int eval(const char *&s, int depth = 0) { - if(!*s) throw "nall::bad_eval"; while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) throw "unrecognized_token"; int value = 0, x = *s, y = *(s + 1); if(*s == '(') { value = eval(++s, 1); - if(*s++ != ')') throw "nall::bad_eval"; + if(*s++ != ')') throw "mismatched_group"; } - else if(x == '!') value = !eval(++s, 14); - else if(x == '~') value = ~eval(++s, 14); - else if(x == '+') value = +eval(++s, 14); - else if(x == '-') value = -eval(++s, 14); + else if(x == '!') value = !eval(++s, 13); + else if(x == '~') value = ~eval(++s, 13); + else if(x == '+') value = +eval(++s, 13); + else if(x == '-') value = -eval(++s, 13); - else if(x >= '0' && x <= '9') value = eval_integer(s); + else if((x >= '0' && x <= '9') || x == '\'') value = eval_integer(s); - else throw "nall::bad_eval"; + else throw "unrecognized_token"; - for(;;) { + while(true) { while(*s == ' ' || *s == '\t') s++; //trim whitespace if(!*s) break; x = *s, y = *(s + 1); @@ -109,18 +119,18 @@ static int eval(const char *&s, int depth = 0) { if(depth >= 3) break; if(x == '|' && y == '|') { value = eval(++++s, 3) || value; continue; } - if(depth >= 2) break; if(x == '?') { int lhs = eval(++s, 2); - if(*s != ':') throw "nall::bad_eval"; + if(*s != ':') throw "mismatched_ternary"; int rhs = eval(++s, 2); value = value ? lhs : rhs; continue; } + if(depth >= 2) break; if(depth > 0 && x == ')') break; - throw "nall::bad_eval"; + throw "unrecognized_token"; } return value; diff --git a/src/lib/nall/utility.hpp b/src/lib/nall/utility.hpp index 9a8caf40..6d5cfd7e 100644 --- a/src/lib/nall/utility.hpp +++ b/src/lib/nall/utility.hpp @@ -3,6 +3,12 @@ namespace nall { +template inline void swap(T &x, T &y) { + T temp = x; + x = y; + y = temp; +} + template struct base_from_member { T value; diff --git a/src/lib/nall/vector.hpp b/src/lib/nall/vector.hpp index dd408a4a..0149b669 100644 --- a/src/lib/nall/vector.hpp +++ b/src/lib/nall/vector.hpp @@ -89,7 +89,7 @@ public: } void reserve(unsigned size) { - if(size == poolsize)return; + if(size == poolsize) return; if(size < poolsize) { for(unsigned i = size; i < objectsize; i++) { pool[i].~T(); } @@ -100,9 +100,9 @@ public: poolsize = size; } - void resize(int size) { - if(size == objectsize)return; - if(size > poolsize)reserve(size); + void resize(unsigned size) { + if(size == objectsize) return; + if(size > poolsize) reserve(size); if(size < objectsize) { for(unsigned i = size; i < objectsize; i++) { pool[i].~T(); } @@ -122,14 +122,24 @@ public: ~linear_vector() { reset(); } inline operator T&() { - if(objectsize == 0)resize(1); - if(objectsize == 0)throw "vector[] out of bounds"; + if(objectsize == 0) resize(1); + if(objectsize == 0) throw "vector[] out of bounds"; return pool[0]; } - inline T &operator[](int index) { - if(index >= objectsize)resize(index + 1); - if(index >= objectsize)throw "vector[] out of bounds"; + inline operator const T&() const { + if(objectsize == 0) throw "vector[] out of bounds"; + return pool[0]; + } + + inline T& operator[](int index) { + if(index >= objectsize) resize(index + 1); + if(index >= objectsize) throw "vector[] out of bounds"; + return pool[index]; + } + + inline const T& operator[](int index) const { + if(index >= objectsize) throw "vector[] out of bounds"; return pool[index]; } }; @@ -144,7 +154,7 @@ public: unsigned capacity() const { return poolsize; } void reset() { - for(unsigned i = 0; i < objectsize; i++) { if(pool[i])delete pool[i]; } + for(unsigned i = 0; i < objectsize; i++) { if(pool[i]) delete pool[i]; } if(pool) { free(pool); @@ -156,10 +166,10 @@ public: } void reserve(unsigned size) { - if(size == poolsize)return; + if(size == poolsize) return; if(size < poolsize) { - for(unsigned i = size; i < objectsize; i++) { if(pool[i])delete pool[i]; } + for(unsigned i = size; i < objectsize; i++) { if(pool[i]) delete pool[i]; } objectsize = size; } @@ -170,12 +180,12 @@ public: poolsize = size; } - void resize(int size) { - if(size == objectsize)return; - if(size > poolsize)reserve(size); + void resize(unsigned size) { + if(size == objectsize) return; + if(size > poolsize) reserve(size); if(size < objectsize) { - for(unsigned i = size; i < objectsize; i++) { if(pool[i])delete pool[i]; } + for(unsigned i = size; i < objectsize; i++) { if(pool[i]) delete pool[i]; } } objectsize = size; @@ -190,16 +200,26 @@ public: ~ptr_vector() { reset(); } inline operator T&() { - if(objectsize == 0)resize(1); - if(objectsize == 0)throw "vector[] out of bounds"; - if(!pool[0])pool[0] = new T; + if(objectsize == 0) resize(1); + if(objectsize == 0) throw "vector[] out of bounds"; + if(!pool[0]) pool[0] = new T; return *pool[0]; } - inline T &operator[](int index) { - if(index >= objectsize)resize(index + 1); - if(index >= objectsize)throw "vector[] out of bounds"; - if(!pool[index])pool[index] = new T; + inline operator const T&() const { + if(objectsize == 0 || !pool[0]) throw "vector[] out of bounds"; + return *pool[0]; + } + + inline T& operator[](int index) { + if(index >= objectsize) resize(index + 1); + if(index >= objectsize) throw "vector[] out of bounds"; + if(!pool[index]) pool[index] = new T; + return *pool[index]; + } + + inline const T& operator[](int index) const { + if(index >= objectsize || !pool[index]) throw "vector[] out of bounds"; return *pool[index]; } }; diff --git a/src/lib/ruby/audio.h b/src/lib/ruby/audio.hpp similarity index 100% rename from src/lib/ruby/audio.h rename to src/lib/ruby/audio.hpp diff --git a/src/lib/ruby/audio/alsa.cpp b/src/lib/ruby/audio/alsa.cpp index a669a330..eb5e7327 100644 --- a/src/lib/ruby/audio/alsa.cpp +++ b/src/lib/ruby/audio/alsa.cpp @@ -1,10 +1,8 @@ #include -#include - namespace ruby { -#include "alsa.h" +#include "alsa.hpp" class pAudioALSA { public: diff --git a/src/lib/ruby/audio/alsa.h b/src/lib/ruby/audio/alsa.hpp similarity index 100% rename from src/lib/ruby/audio/alsa.h rename to src/lib/ruby/audio/alsa.hpp diff --git a/src/lib/ruby/audio/ao.cpp b/src/lib/ruby/audio/ao.cpp index e390f468..e943d57e 100644 --- a/src/lib/ruby/audio/ao.cpp +++ b/src/lib/ruby/audio/ao.cpp @@ -1,10 +1,8 @@ #include -#include - namespace ruby { -#include "ao.h" +#include "ao.hpp" class pAudioAO { public: diff --git a/src/lib/ruby/audio/ao.h b/src/lib/ruby/audio/ao.hpp similarity index 100% rename from src/lib/ruby/audio/ao.h rename to src/lib/ruby/audio/ao.hpp diff --git a/src/lib/ruby/audio/directsound.cpp b/src/lib/ruby/audio/directsound.cpp index c3a846c8..8a13ede1 100644 --- a/src/lib/ruby/audio/directsound.cpp +++ b/src/lib/ruby/audio/directsound.cpp @@ -1,11 +1,9 @@ #include #include -#include - namespace ruby { -#include "directsound.h" +#include "directsound.hpp" class pAudioDS { public: diff --git a/src/lib/ruby/audio/directsound.h b/src/lib/ruby/audio/directsound.hpp similarity index 100% rename from src/lib/ruby/audio/directsound.h rename to src/lib/ruby/audio/directsound.hpp diff --git a/src/lib/ruby/audio/openal.cpp b/src/lib/ruby/audio/openal.cpp index 6c39f526..69d811ea 100644 --- a/src/lib/ruby/audio/openal.cpp +++ b/src/lib/ruby/audio/openal.cpp @@ -1,11 +1,9 @@ #include #include -#include - namespace ruby { -#include "openal.h" +#include "openal.hpp" class pAudioOpenAL { public: diff --git a/src/lib/ruby/audio/openal.h b/src/lib/ruby/audio/openal.hpp similarity index 100% rename from src/lib/ruby/audio/openal.h rename to src/lib/ruby/audio/openal.hpp diff --git a/src/lib/ruby/audio/oss.cpp b/src/lib/ruby/audio/oss.cpp index 91a41cbe..03a2e1f2 100644 --- a/src/lib/ruby/audio/oss.cpp +++ b/src/lib/ruby/audio/oss.cpp @@ -18,11 +18,9 @@ #define SNDCTL_DSP_POLICY _IOW('P', 45, int) #endif -#include - namespace ruby { -#include "oss.h" +#include "oss.hpp" class pAudioOSS { public: diff --git a/src/lib/ruby/audio/oss.h b/src/lib/ruby/audio/oss.hpp similarity index 100% rename from src/lib/ruby/audio/oss.h rename to src/lib/ruby/audio/oss.hpp diff --git a/src/lib/ruby/input.h b/src/lib/ruby/input.hpp similarity index 62% rename from src/lib/ruby/input.h rename to src/lib/ruby/input.hpp index 244eedfc..841f1b39 100644 --- a/src/lib/ruby/input.h +++ b/src/lib/ruby/input.hpp @@ -3,6 +3,7 @@ public: enum Setting { Handle, KeyboardSupport, + MouseSupport, JoypadSupport, AnalogAxisResistance, }; @@ -11,11 +12,11 @@ public: virtual uintptr_t get(Setting) { return false; } virtual bool set(Setting, uintptr_t) { return false; } - virtual bool key_down(uint16_t key) { return false; } - virtual bool key_up (uint16_t key) { return !key_down(key); } + virtual bool acquire() { return false; } + virtual bool unacquire() { return false; } + virtual bool acquired() { return false; } - virtual void clear() {} - virtual void poll() {} + virtual bool poll(int16_t *table) { return false; } virtual bool init() { return true; } virtual void term() {} diff --git a/src/lib/ruby/input/directinput.cpp b/src/lib/ruby/input/directinput.cpp index 740cee3b..0d4b80ad 100644 --- a/src/lib/ruby/input/directinput.cpp +++ b/src/lib/ruby/input/directinput.cpp @@ -1,27 +1,23 @@ -#include #include - -#define DIRECTINPUT_VERSION 0x0800 -#define DIRECTINPUT_JOYMAX 16 #include -#include - namespace ruby { -#include "directinput.h" +#include "directinput.hpp" -BOOL CALLBACK DI_EnumJoypadsCallback(const DIDEVICEINSTANCE*, void*); +static BOOL CALLBACK DI_EnumJoypadsCallback(const DIDEVICEINSTANCE*, void*); using namespace nall; class pInputDI { public: InputDI &self; - uint8_t keystate[256 + DIRECTINPUT_JOYMAX * 256]; - LPDIRECTINPUT8 di; - LPDIRECTINPUTDEVICE8 di_key, di_joy[DIRECTINPUT_JOYMAX]; - unsigned di_joy_count; + LPDIRECTINPUT8 context; + LPDIRECTINPUTDEVICE8 keyboard; + LPDIRECTINPUTDEVICE8 mouse; + LPDIRECTINPUTDEVICE8 gamepad[joypad<>::count]; + unsigned gamepad_count; //number of physical gamepads present + bool mouseacquired; struct { HWND handle; @@ -31,6 +27,7 @@ public: bool cap(Input::Setting setting) { if(setting == Input::Handle) return true; if(setting == Input::KeyboardSupport) return true; + if(setting == Input::MouseSupport) return true; if(setting == Input::JoypadSupport) return true; if(setting == Input::AnalogAxisResistance) return true; return false; @@ -56,258 +53,302 @@ public: return false; } - void clear() { - memset(keystate, 0, sizeof keystate); - } + bool poll(int16_t *table) { + memset(table, 0, input_limit * sizeof(int16_t)); - void poll() { - clear(); - - DIJOYSTATE2 js; - if(di_key) { - if(FAILED(di_key->GetDeviceState(256, keystate))) { - di_key->Acquire(); - if(FAILED(di_key->GetDeviceState(256, keystate))) { - memset(keystate, 0, 256); + if(keyboard) { + uint8_t state[256]; + if(FAILED(keyboard->GetDeviceState(sizeof state, state))) { + keyboard->Acquire(); + if(FAILED(keyboard->GetDeviceState(sizeof state, state))) { + memset(state, 0, sizeof state); } } + + table[keyboard::escape] = (bool)(state[0x01] & 0x80); + + table[keyboard::f1 ] = (bool)(state[0x3b] & 0x80); + table[keyboard::f2 ] = (bool)(state[0x3c] & 0x80); + table[keyboard::f3 ] = (bool)(state[0x3d] & 0x80); + table[keyboard::f4 ] = (bool)(state[0x3e] & 0x80); + table[keyboard::f5 ] = (bool)(state[0x3f] & 0x80); + table[keyboard::f6 ] = (bool)(state[0x40] & 0x80); + table[keyboard::f7 ] = (bool)(state[0x41] & 0x80); + table[keyboard::f8 ] = (bool)(state[0x42] & 0x80); + table[keyboard::f9 ] = (bool)(state[0x43] & 0x80); + table[keyboard::f10] = (bool)(state[0x44] & 0x80); + table[keyboard::f11] = (bool)(state[0x57] & 0x80); + table[keyboard::f12] = (bool)(state[0x58] & 0x80); + + table[keyboard::print_screen] = (bool)(state[0xb7] & 0x80); + table[keyboard::scroll_lock ] = (bool)(state[0x46] & 0x80); + table[keyboard::pause ] = (bool)(state[0xc5] & 0x80); + + table[keyboard::tilde] = (bool)(state[0x29] & 0x80); + + table[keyboard::num_1] = (bool)(state[0x02] & 0x80); + table[keyboard::num_2] = (bool)(state[0x03] & 0x80); + table[keyboard::num_3] = (bool)(state[0x04] & 0x80); + table[keyboard::num_4] = (bool)(state[0x05] & 0x80); + table[keyboard::num_5] = (bool)(state[0x06] & 0x80); + table[keyboard::num_6] = (bool)(state[0x07] & 0x80); + table[keyboard::num_7] = (bool)(state[0x08] & 0x80); + table[keyboard::num_8] = (bool)(state[0x09] & 0x80); + table[keyboard::num_9] = (bool)(state[0x0a] & 0x80); + table[keyboard::num_0] = (bool)(state[0x0b] & 0x80); + + table[keyboard::dash ] = (bool)(state[0x0c] & 0x80); + table[keyboard::equal ] = (bool)(state[0x0d] & 0x80); + table[keyboard::backspace] = (bool)(state[0x0e] & 0x80); + + table[keyboard::insert ] = (bool)(state[0xd2] & 0x80); + table[keyboard::delete_ ] = (bool)(state[0xd3] & 0x80); + table[keyboard::home ] = (bool)(state[0xc7] & 0x80); + table[keyboard::end ] = (bool)(state[0xcf] & 0x80); + table[keyboard::page_up ] = (bool)(state[0xc9] & 0x80); + table[keyboard::page_down] = (bool)(state[0xd1] & 0x80); + + table[keyboard::a] = (bool)(state[0x1e] & 0x80); + table[keyboard::b] = (bool)(state[0x30] & 0x80); + table[keyboard::c] = (bool)(state[0x2e] & 0x80); + table[keyboard::d] = (bool)(state[0x20] & 0x80); + table[keyboard::e] = (bool)(state[0x12] & 0x80); + table[keyboard::f] = (bool)(state[0x21] & 0x80); + table[keyboard::g] = (bool)(state[0x22] & 0x80); + table[keyboard::h] = (bool)(state[0x23] & 0x80); + table[keyboard::i] = (bool)(state[0x17] & 0x80); + table[keyboard::j] = (bool)(state[0x24] & 0x80); + table[keyboard::k] = (bool)(state[0x25] & 0x80); + table[keyboard::l] = (bool)(state[0x26] & 0x80); + table[keyboard::m] = (bool)(state[0x32] & 0x80); + table[keyboard::n] = (bool)(state[0x31] & 0x80); + table[keyboard::o] = (bool)(state[0x18] & 0x80); + table[keyboard::p] = (bool)(state[0x19] & 0x80); + table[keyboard::q] = (bool)(state[0x10] & 0x80); + table[keyboard::r] = (bool)(state[0x13] & 0x80); + table[keyboard::s] = (bool)(state[0x1f] & 0x80); + table[keyboard::t] = (bool)(state[0x14] & 0x80); + table[keyboard::u] = (bool)(state[0x16] & 0x80); + table[keyboard::v] = (bool)(state[0x2f] & 0x80); + table[keyboard::w] = (bool)(state[0x11] & 0x80); + table[keyboard::x] = (bool)(state[0x2d] & 0x80); + table[keyboard::y] = (bool)(state[0x15] & 0x80); + table[keyboard::z] = (bool)(state[0x2c] & 0x80); + + table[keyboard::lbracket ] = (bool)(state[0x1a] & 0x80); + table[keyboard::rbracket ] = (bool)(state[0x1b] & 0x80); + table[keyboard::backslash ] = (bool)(state[0x2b] & 0x80); + table[keyboard::semicolon ] = (bool)(state[0x27] & 0x80); + table[keyboard::apostrophe] = (bool)(state[0x28] & 0x80); + table[keyboard::comma ] = (bool)(state[0x33] & 0x80); + table[keyboard::period ] = (bool)(state[0x34] & 0x80); + table[keyboard::slash ] = (bool)(state[0x35] & 0x80); + + table[keyboard::pad_0] = (bool)(state[0x4f] & 0x80); + table[keyboard::pad_1] = (bool)(state[0x50] & 0x80); + table[keyboard::pad_2] = (bool)(state[0x51] & 0x80); + table[keyboard::pad_3] = (bool)(state[0x4b] & 0x80); + table[keyboard::pad_4] = (bool)(state[0x4c] & 0x80); + table[keyboard::pad_5] = (bool)(state[0x4d] & 0x80); + table[keyboard::pad_6] = (bool)(state[0x47] & 0x80); + table[keyboard::pad_7] = (bool)(state[0x48] & 0x80); + table[keyboard::pad_8] = (bool)(state[0x49] & 0x80); + table[keyboard::pad_9] = (bool)(state[0x52] & 0x80); + table[keyboard::point] = (bool)(state[0x53] & 0x80); + + table[keyboard::add] = (bool)(state[0x4e] & 0x80); + table[keyboard::subtract] = (bool)(state[0x4a] & 0x80); + table[keyboard::multiply] = (bool)(state[0x37] & 0x80); + table[keyboard::divide] = (bool)(state[0xb5] & 0x80); + table[keyboard::enter] = (bool)(state[0x9c] & 0x80); + + table[keyboard::num_lock ] = (bool)(state[0x45] & 0x80); + table[keyboard::caps_lock] = (bool)(state[0x3a] & 0x80); + + table[keyboard::up ] = (bool)(state[0xc8] & 0x80); + table[keyboard::down ] = (bool)(state[0xd0] & 0x80); + table[keyboard::left ] = (bool)(state[0xcb] & 0x80); + table[keyboard::right] = (bool)(state[0xcd] & 0x80); + + table[keyboard::tab ] = (bool)(state[0x0f] & 0x80); + table[keyboard::return_ ] = (bool)(state[0x1c] & 0x80); + table[keyboard::spacebar] = (bool)(state[0x39] & 0x80); + + table[keyboard::lctrl ] = (bool)(state[0x1d] & 0x80); + table[keyboard::rctrl ] = (bool)(state[0x9d] & 0x80); + table[keyboard::lalt ] = (bool)(state[0x38] & 0x80); + table[keyboard::ralt ] = (bool)(state[0xb8] & 0x80); + table[keyboard::lshift] = (bool)(state[0x2a] & 0x80); + table[keyboard::rshift] = (bool)(state[0x36] & 0x80); + table[keyboard::lsuper] = (bool)(state[0xdb] & 0x80); + table[keyboard::rsuper] = (bool)(state[0xdc] & 0x80); + table[keyboard::menu ] = (bool)(state[0xdd] & 0x80); } - for(int i = 0; i < di_joy_count; i++) { - if(!di_joy[i]) continue; + if(mouse) { + DIMOUSESTATE2 state; + if(FAILED(mouse->GetDeviceState(sizeof(DIMOUSESTATE2), (void*)&state))) { + mouse->Acquire(); + if(FAILED(mouse->GetDeviceState(sizeof(DIMOUSESTATE2), (void*)&state))) { + memset(&state, 0, sizeof(DIMOUSESTATE2)); + } + } - memset(js.rgbButtons, 0, 128); + table[mouse::x] = state.lX; + table[mouse::y] = state.lY; + table[mouse::z] = state.lZ / WHEEL_DELTA; + for(unsigned n = 0; n < mouse::buttons; n++) { + table[mouse::button + n] = (bool)state.rgbButtons[n]; + } - if(FAILED(di_joy[i]->Poll())) { - di_joy[i]->Acquire(); - if(FAILED(di_joy[i]->Poll())) { + //on Windows, 0 = left, 1 = right, 2 = middle + //swap middle and right buttons for consistency with Linux + int16_t temp = table[mouse::button + 1]; + table[mouse::button + 1] = table[mouse::button + 2]; + table[mouse::button + 2] = temp; + } + + for(unsigned i = 0; i < gamepad_count; i++) { + if(!gamepad[i]) continue; + + DIJOYSTATE2 state; + if(FAILED(gamepad[i]->Poll())) { + gamepad[i]->Acquire(); + if(FAILED(gamepad[i]->Poll())) { continue; } } - di_joy[i]->GetDeviceState(sizeof(DIJOYSTATE2), &js); - uint16_t index = 256 + (i << 8); //joypad index - memcpy(keystate + index, js.rgbButtons, 128); + gamepad[i]->GetDeviceState(sizeof(DIJOYSTATE2), &state); + + unsigned index = joypad<>::index(i, joypad<>::none); - //map d-pad axes int resistance = settings.analog_axis_resistance; resistance = max(1, min(99, resistance)); - resistance = int(double(resistance) * 32768.0 / 100.0); + resistance = (int)((double)resistance * 32768.0 / 100.0); int resistance_lo = 0x7fff - resistance; int resistance_hi = 0x8000 + resistance; - keystate[index + 0x80] = (js.lY <= resistance_lo) ? 0x80 : 0x00; - keystate[index + 0x81] = (js.lY >= resistance_hi) ? 0x80 : 0x00; - keystate[index + 0x82] = (js.lX <= resistance_lo) ? 0x80 : 0x00; - keystate[index + 0x83] = (js.lX >= resistance_hi) ? 0x80 : 0x00; - //map analog POV hat (directional pad) as well - unsigned pov = js.rgdwPOV[0]; - keystate[index + 0x80] |= (pov == 0 || pov == 31500 || pov == 4500) ? 0x80 : 0x00; - keystate[index + 0x81] |= (pov == 18000 || pov == 13500 || pov == 22500) ? 0x80 : 0x00; - keystate[index + 0x82] |= (pov == 27000 || pov == 22500 || pov == 31500) ? 0x80 : 0x00; - keystate[index + 0x83] |= (pov == 9000 || pov == 4500 || pov == 13500) ? 0x80 : 0x00; + table[index + joypad<>::up ] = (state.lY <= resistance_lo); + table[index + joypad<>::down ] = (state.lY >= resistance_hi); + table[index + joypad<>::left ] = (state.lX <= resistance_lo); + table[index + joypad<>::right] = (state.lX >= resistance_hi); + + unsigned pov = state.rgdwPOV[0]; + table[index + joypad<>::up ] |= (pov == 0 || pov == 31500 || pov == 4500); + table[index + joypad<>::down ] |= (pov == 18000 || pov == 13500 || pov == 22500); + table[index + joypad<>::left ] |= (pov == 27000 || pov == 22500 || pov == 31500); + table[index + joypad<>::right] |= (pov == 9000 || pov == 4500 || pov == 13500); + + table[index + joypad<>::axis + 0] = (int16_t)(state.lX - 32768); + table[index + joypad<>::axis + 1] = (int16_t)(state.lY - 32768); + + for(unsigned n = 0; n < joypad<>::buttons; n++) { + table[index + joypad<>::button + n] = (bool)state.rgbButtons[n]; + } } + + return true; } bool enum_joypads(const DIDEVICEINSTANCE *instance) { - if(FAILED(di->CreateDevice(instance->guidInstance, &di_joy[di_joy_count], 0))) { - return DIENUM_CONTINUE; //continue and try next joypad + if(FAILED(context->CreateDevice(instance->guidInstance, &gamepad[gamepad_count], 0))) { + return DIENUM_CONTINUE; //continue and try next gamepad } - di_joy[di_joy_count]->SetDataFormat(&c_dfDIJoystick2); - di_joy[di_joy_count]->SetCooperativeLevel(settings.handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND); + gamepad[gamepad_count]->SetDataFormat(&c_dfDIJoystick2); + gamepad[gamepad_count]->SetCooperativeLevel(settings.handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND); - if(++di_joy_count >= DIRECTINPUT_JOYMAX) return DIENUM_STOP; + if(++gamepad_count >= joypad<>::count) return DIENUM_STOP; return DIENUM_CONTINUE; } bool init() { - di_key = 0; - for(int i = 0; i < DIRECTINPUT_JOYMAX; i++) di_joy[i] = 0; - di = 0; - di_joy_count = 0; + context = 0; + keyboard = 0; + mouse = 0; + for(unsigned i = 0; i < joypad<>::count; i++) gamepad[i] = 0; + gamepad_count = 0; + mouseacquired = false; - DirectInput8Create(GetModuleHandle(0), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&di, 0); - di->CreateDevice(GUID_SysKeyboard, &di_key, 0); + DirectInput8Create(GetModuleHandle(0), 0x0800, IID_IDirectInput8, (void**)&context, 0); - di_key->SetDataFormat(&c_dfDIKeyboard); - di_key->SetCooperativeLevel(settings.handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND); - di_key->Acquire(); + context->CreateDevice(GUID_SysKeyboard, &keyboard, 0); + keyboard->SetDataFormat(&c_dfDIKeyboard); + keyboard->SetCooperativeLevel(settings.handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND); + keyboard->Acquire(); - di->EnumDevices(DI8DEVCLASS_GAMECTRL, DI_EnumJoypadsCallback, (void*)this, DIEDFL_ATTACHEDONLY); + context->CreateDevice(GUID_SysMouse, &mouse, 0); + mouse->SetDataFormat(&c_dfDIMouse2); + HRESULT hr = mouse->SetCooperativeLevel(settings.handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND); + mouse->Acquire(); + + context->EnumDevices(DI8DEVCLASS_GAMECTRL, DI_EnumJoypadsCallback, (void*)this, DIEDFL_ATTACHEDONLY); return true; } void term() { - if(di_key) { - di_key->Unacquire(); - di_key->Release(); - di_key = 0; + if(keyboard) { + keyboard->Unacquire(); + keyboard->Release(); + keyboard = 0; } - for(int i = 0; i < DIRECTINPUT_JOYMAX; i++) { - if(di_joy[i]) { - di_joy[i]->Unacquire(); - di_joy[i]->Release(); - di_joy[i] = 0; + if(mouse) { + mouse->Unacquire(); + mouse->Release(); + mouse = 0; + } + + for(unsigned i = 0; i < joypad<>::count; i++) { + if(gamepad[i]) { + gamepad[i]->Unacquire(); + gamepad[i]->Release(); + gamepad[i] = 0; } } + gamepad_count = 0; - if(di) { - di->Release(); - di = 0; + if(context) { + context->Release(); + context = 0; } - - di_joy_count = 0; } - bool key_down(uint16_t key) { - assert(key < sizeof keystate); - return key ? keystate[translate(key)] & 0x80 : false; + bool acquire() { + if(!mouse) return false; + if(acquired() == false) { + mouse->Unacquire(); + mouse->SetCooperativeLevel(settings.handle, DISCL_EXCLUSIVE | DISCL_FOREGROUND); + mouse->Acquire(); + mouseacquired = true; + } + return true; } - //translate keymap code to DirectInput code, to lookup key status in DI status table - uint16_t translate(uint16_t key) { - switch(key) { - case keyboard::escape: return 0x01; - - case keyboard::f1: return 0x3b; - case keyboard::f2: return 0x3c; - case keyboard::f3: return 0x3d; - case keyboard::f4: return 0x3e; - case keyboard::f5: return 0x3f; - case keyboard::f6: return 0x40; - case keyboard::f7: return 0x41; - case keyboard::f8: return 0x42; - case keyboard::f9: return 0x43; - case keyboard::f10: return 0x44; - case keyboard::f11: return 0x57; - case keyboard::f12: return 0x58; - - case keyboard::print_screen: return 0xb7; - case keyboard::scroll_lock: return 0x46; - case keyboard::pause: return 0xc5; - - case keyboard::tilde: return 0x29; - - case keyboard::num_1: return 0x02; - case keyboard::num_2: return 0x03; - case keyboard::num_3: return 0x04; - case keyboard::num_4: return 0x05; - case keyboard::num_5: return 0x06; - case keyboard::num_6: return 0x07; - case keyboard::num_7: return 0x08; - case keyboard::num_8: return 0x09; - case keyboard::num_9: return 0x0a; - case keyboard::num_0: return 0x0b; - - case keyboard::dash: return 0x0c; - case keyboard::equal: return 0x0d; - case keyboard::backspace: return 0x0e; - - case keyboard::insert: return 0xd2; - case keyboard::delete_: return 0xd3; - case keyboard::home: return 0xc7; - case keyboard::end: return 0xcf; - case keyboard::page_up: return 0xc9; - case keyboard::page_down: return 0xd1; - - case keyboard::a: return 0x1e; - case keyboard::b: return 0x30; - case keyboard::c: return 0x2e; - case keyboard::d: return 0x20; - case keyboard::e: return 0x12; - case keyboard::f: return 0x21; - case keyboard::g: return 0x22; - case keyboard::h: return 0x23; - case keyboard::i: return 0x17; - case keyboard::j: return 0x24; - case keyboard::k: return 0x25; - case keyboard::l: return 0x26; - case keyboard::m: return 0x32; - case keyboard::n: return 0x31; - case keyboard::o: return 0x18; - case keyboard::p: return 0x19; - case keyboard::q: return 0x10; - case keyboard::r: return 0x13; - case keyboard::s: return 0x1f; - case keyboard::t: return 0x14; - case keyboard::u: return 0x16; - case keyboard::v: return 0x2f; - case keyboard::w: return 0x11; - case keyboard::x: return 0x2d; - case keyboard::y: return 0x15; - case keyboard::z: return 0x2c; - - case keyboard::lbracket: return 0x1a; - case keyboard::rbracket: return 0x1b; - case keyboard::backslash: return 0x2b; - case keyboard::semicolon: return 0x27; - case keyboard::apostrophe: return 0x28; - case keyboard::comma: return 0x33; - case keyboard::period: return 0x34; - case keyboard::slash: return 0x35; - - case keyboard::pad_1: return 0x4f; - case keyboard::pad_2: return 0x50; - case keyboard::pad_3: return 0x51; - case keyboard::pad_4: return 0x4b; - case keyboard::pad_5: return 0x4c; - case keyboard::pad_6: return 0x4d; - case keyboard::pad_7: return 0x47; - case keyboard::pad_8: return 0x48; - case keyboard::pad_9: return 0x49; - case keyboard::pad_0: return 0x52; - case keyboard::point: return 0x53; - - case keyboard::add: return 0x4e; - case keyboard::subtract: return 0x4a; - case keyboard::multiply: return 0x37; - case keyboard::divide: return 0xb5; - case keyboard::enter: return 0x9c; - - case keyboard::num_lock : return 0x45; - case keyboard::caps_lock: return 0x3a; - - case keyboard::up: return 0xc8; - case keyboard::down: return 0xd0; - case keyboard::left: return 0xcb; - case keyboard::right: return 0xcd; - - case keyboard::tab: return 0x0f; - case keyboard::return_: return 0x1c; - case keyboard::spacebar: return 0x39; - - case keyboard::lctrl : return 0x1d; - case keyboard::rctrl : return 0x9d; - case keyboard::lalt : return 0x38; - case keyboard::ralt : return 0xb8; - case keyboard::lshift: return 0x2a; - case keyboard::rshift: return 0x36; - case keyboard::lsuper: return 0xdb; - case keyboard::rsuper: return 0xdc; - case keyboard::menu: return 0xdd; + bool unacquire() { + if(!mouse) return false; + if(acquired() == true) { + mouse->Unacquire(); + mouse->SetCooperativeLevel(settings.handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND); + mouse->Acquire(); + mouseacquired = false; } + return true; + } - for(uint16_t j = 0; j < 16; j++) { - uint16_t index = 256 + (j << 8); - if(key == joypad<>::index(j, joypad<>::up)) return index + 0x80; - if(key == joypad<>::index(j, joypad<>::down)) return index + 0x81; - if(key == joypad<>::index(j, joypad<>::left)) return index + 0x82; - if(key == joypad<>::index(j, joypad<>::right)) return index + 0x83; - for(uint16_t b = 0; b < 16; b++) { - if(key == joypad<>::index(j, joypad<>::button_00 + b)) return index + b; - } - } - - return 0x00; + bool acquired() { + return mouseacquired; } pInputDI(InputDI &self_) : self(self_) { - di = 0; - di_key = 0; - for(int i = 0; i < DIRECTINPUT_JOYMAX; i++) di_joy[i] = 0; + context = 0; + keyboard = 0; + mouse = 0; + for(unsigned i = 0; i < joypad<>::count; i++) gamepad[i] = 0; + gamepad_count = 0; + mouseacquired = false; settings.handle = 0; settings.analog_axis_resistance = 75; @@ -323,9 +364,10 @@ BOOL CALLBACK DI_EnumJoypadsCallback(const DIDEVICEINSTANCE *instance, void *p) bool InputDI::cap(Setting setting) { return p.cap(setting); } uintptr_t InputDI::get(Setting setting) { return p.get(setting); } bool InputDI::set(Setting setting, uintptr_t param) { return p.set(setting, param); } -bool InputDI::key_down(uint16_t key) { return p.key_down(key); } -void InputDI::clear() { p.clear(); } -void InputDI::poll() { p.poll(); } +bool InputDI::acquire() { return p.acquire(); } +bool InputDI::unacquire() { return p.unacquire(); } +bool InputDI::acquired() { return p.acquired(); } +bool InputDI::poll(int16_t *table) { return p.poll(table); } bool InputDI::init() { return p.init(); } void InputDI::term() { p.term(); } InputDI::InputDI() : p(*new pInputDI(*this)) {} diff --git a/src/lib/ruby/input/directinput.h b/src/lib/ruby/input/directinput.hpp similarity index 67% rename from src/lib/ruby/input/directinput.h rename to src/lib/ruby/input/directinput.hpp index f568fa43..17c97569 100644 --- a/src/lib/ruby/input/directinput.h +++ b/src/lib/ruby/input/directinput.hpp @@ -6,10 +6,11 @@ public: uintptr_t get(Setting); bool set(Setting, uintptr_t); - bool key_down(uint16_t key); + bool acquire(); + bool unacquire(); + bool acquired(); - void clear(); - void poll(); + bool poll(int16_t *table); bool init(); void term(); diff --git a/src/lib/ruby/input/sdl.cpp b/src/lib/ruby/input/sdl.cpp index 7b6456cc..36952697 100644 --- a/src/lib/ruby/input/sdl.cpp +++ b/src/lib/ruby/input/sdl.cpp @@ -1,76 +1,67 @@ -/***** - * SDL input driver - * - * Design notes: - * SDL contains the ability to poll both the keyboard and joypads, - * however it cannot capture keyboard input from windows that it - * did not create. Joypad input is captured globally, regardless - * of what created the active window. - * Letting SDL create the window to capture keyboard input is not - * practical. Users of an input library should ideally be able to - * create a window however they like -- with GTK+, Qt, etc. - * Therefore, this driver basically uses SDL for joypad support - * only, and uses the native platform's keyboard input handling - * routines. Clearly, this is not as portable, but there is little - * choice in the matter. - * In the worst case scenario, an unsupported platform, this - * driver will still work, but will not support keyboard input. - *****/ +//================ +//SDL input driver +//================ +//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. #include - -#if !defined(_WIN32) #include #include #include #include #include -#endif - -#include namespace ruby { -#include "sdl.h" +#include "sdl.hpp" using namespace nall; class pInputSDL { public: InputSDL &self; - SDL_Joystick *joy[16]; - SDL_Event event; - enum { - joy_button_00, joy_button_01, joy_button_02, joy_button_03, - joy_button_04, joy_button_05, joy_button_06, joy_button_07, - joy_button_08, joy_button_09, joy_button_10, joy_button_11, - joy_button_12, joy_button_13, joy_button_14, joy_button_15, - joy_up, joy_down, joy_left, joy_right, - joy_limit, - }; - bool joystate[16][joy_limit]; - - #if !defined(_WIN32) - char keystate[32]; Display *display; - #endif + Window rootwindow; + unsigned screenwidth, screenheight; + unsigned relativex, relativey; + Cursor InvisibleCursor; + SDL_Joystick *gamepad[joypad<>::count]; + bool mouseacquired; struct { + //mouse device settings + int accel_numerator; + int accel_denominator; + int threshold; + } device; + + struct { + uintptr_t handle; unsigned analog_axis_resistance; } settings; bool cap(Input::Setting setting) { + if(setting == Input::Handle) return true; if(setting == Input::KeyboardSupport) return true; + if(setting == Input::MouseSupport) return true; if(setting == Input::JoypadSupport) return true; if(setting == Input::AnalogAxisResistance) return true; return false; } uintptr_t get(Input::Setting setting) { + if(setting == Input::Handle) return settings.handle; if(setting == Input::AnalogAxisResistance) return settings.analog_axis_resistance; return false; } bool set(Input::Setting setting, uintptr_t param) { + if(setting == Input::Handle) { + settings.handle = param; + return true; + } + if(setting == Input::AnalogAxisResistance) { settings.analog_axis_resistance = param; return true; @@ -79,225 +70,298 @@ public: return false; } - bool key_down(uint16_t key) { - #if !defined(_WIN32) - #define map(i) (keystate[i >> 3] & (1 << (i & 7))) + bool acquire() { + if(acquired()) return true; - switch(key) { - case keyboard::escape: return map(0x09); + if(XGrabPointer(display, settings.handle, True, 0, GrabModeAsync, GrabModeAsync, + rootwindow, InvisibleCursor, CurrentTime) == GrabSuccess) { + //backup existing cursor acceleration settings + XGetPointerControl(display, &device.accel_numerator, &device.accel_denominator, &device.threshold); - case keyboard::f1: return map(0x43); - case keyboard::f2: return map(0x44); - case keyboard::f3: return map(0x45); - case keyboard::f4: return map(0x46); - case keyboard::f5: return map(0x47); - case keyboard::f6: return map(0x48); - case keyboard::f7: return map(0x49); - case keyboard::f8: return map(0x4a); - case keyboard::f9: return map(0x4b); - case keyboard::f10: return map(0x4c); - case keyboard::f11: return map(0x5f); - case keyboard::f12: return map(0x60); + //disable cursor acceleration + XChangePointerControl(display, True, False, 1, 1, 0); - case keyboard::print_screen: return map(0x6f); - case keyboard::scroll_lock: return map(0x4e); - case keyboard::pause: return map(0x6e); + //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); - case keyboard::tilde: return map(0x31); - - case keyboard::num_1: return map(0x0a); - case keyboard::num_2: return map(0x0b); - case keyboard::num_3: return map(0x0c); - case keyboard::num_4: return map(0x0d); - case keyboard::num_5: return map(0x0e); - case keyboard::num_6: return map(0x0f); - case keyboard::num_7: return map(0x10); - case keyboard::num_8: return map(0x11); - case keyboard::num_9: return map(0x12); - case keyboard::num_0: return map(0x13); - - case keyboard::dash: return map(0x14); - case keyboard::equal: return map(0x15); - case keyboard::backspace: return map(0x16); - - case keyboard::insert: return map(0x6a); - case keyboard::delete_: return map(0x6b); - case keyboard::home: return map(0x61); - case keyboard::end: return map(0x67); - case keyboard::page_up: return map(0x63); - case keyboard::page_down: return map(0x69); - - case keyboard::a: return map(0x26); - case keyboard::b: return map(0x38); - case keyboard::c: return map(0x36); - case keyboard::d: return map(0x28); - case keyboard::e: return map(0x1a); - case keyboard::f: return map(0x29); - case keyboard::g: return map(0x2a); - case keyboard::h: return map(0x2b); - case keyboard::i: return map(0x1f); - case keyboard::j: return map(0x2c); - case keyboard::k: return map(0x2d); - case keyboard::l: return map(0x2e); - case keyboard::m: return map(0x3a); - case keyboard::n: return map(0x39); - case keyboard::o: return map(0x20); - case keyboard::p: return map(0x21); - case keyboard::q: return map(0x18); - case keyboard::r: return map(0x1b); - case keyboard::s: return map(0x27); - case keyboard::t: return map(0x1c); - case keyboard::u: return map(0x1e); - case keyboard::v: return map(0x37); - case keyboard::w: return map(0x19); - case keyboard::x: return map(0x35); - case keyboard::y: return map(0x1d); - case keyboard::z: return map(0x34); - - case keyboard::lbracket: return map(0x22); - case keyboard::rbracket: return map(0x23); - case keyboard::backslash: return map(0x33); - case keyboard::semicolon: return map(0x2f); - case keyboard::apostrophe: return map(0x30); - case keyboard::comma: return map(0x3b); - case keyboard::period: return map(0x3c); - case keyboard::slash: return map(0x3d); - - case keyboard::pad_1: return map(0x57); - case keyboard::pad_2: return map(0x58); - case keyboard::pad_3: return map(0x59); - case keyboard::pad_4: return map(0x53); - case keyboard::pad_5: return map(0x54); - case keyboard::pad_6: return map(0x55); - case keyboard::pad_7: return map(0x4f); - case keyboard::pad_8: return map(0x50); - case keyboard::pad_9: return map(0x51); - - case keyboard::add: return map(0x56); - case keyboard::subtract: return map(0x52); - case keyboard::multiply: return map(0x3f); - case keyboard::divide: return map(0x70); - case keyboard::enter: return map(0x6c); - - case keyboard::num_lock: return map(0x4d); - case keyboard::caps_lock: return map(0x42); - - case keyboard::up: return map(0x62); - case keyboard::down: return map(0x68); - case keyboard::left: return map(0x64); - case keyboard::right: return map(0x66); - - case keyboard::tab: return map(0x17); - case keyboard::return_: return map(0x24); - case keyboard::spacebar: return map(0x41); - - case keyboard::lctrl: return map(0x25); - case keyboard::rctrl: return map(0x6d); - case keyboard::lalt: return map(0x40); - case keyboard::ralt: return map(0x71); - case keyboard::lshift: return map(0x32); - case keyboard::rshift: return map(0x3e); - case keyboard::lsuper: return map(0x73); - case keyboard::rsuper: return map(0x74); - case keyboard::menu: return map(0x75); - } - - #undef map - #endif - - for(int i = 0; i < 16; i++) { - if(key == joypad<>::index(i, joypad<>::up)) return joystate[i][joy_up]; - if(key == joypad<>::index(i, joypad<>::down)) return joystate[i][joy_down]; - if(key == joypad<>::index(i, joypad<>::left)) return joystate[i][joy_left]; - if(key == joypad<>::index(i, joypad<>::right)) return joystate[i][joy_right]; - for(int b = 0; b < 16; b++) { - if(key == joypad<>::index(i, joypad<>::button_00 + b)) return joystate[i][joy_button_00 + b]; - } - } - - return false; - } - - void clear() { - #if !defined(_WIN32) - memset(keystate, 0, sizeof keystate); - #endif - - for(int i = 0; i < 16; i++) { - for(int b = 0; b < joy_limit; b++) { - joystate[i][b] = false; - } + return mouseacquired = true; + } else { + return mouseacquired = false; } } - void poll() { + 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); + return mouseacquired = false; + } + } + + bool acquired() { + return mouseacquired; + } + + bool poll(int16_t *table) { + memset(table, 0, input_limit * sizeof(int16_t)); + + //======== + //Keyboard + //======== + + #define key(n) (bool)(state[n >> 3] & (1 << (n & 7))) + { + char state[32]; + XQueryKeymap(display, state); + + table[keyboard::escape] = key(0x09); + + table[keyboard::f1 ] = key(0x43); + table[keyboard::f2 ] = key(0x44); + table[keyboard::f3 ] = key(0x45); + table[keyboard::f4 ] = key(0x46); + table[keyboard::f5 ] = key(0x47); + table[keyboard::f6 ] = key(0x48); + table[keyboard::f7 ] = key(0x49); + table[keyboard::f8 ] = key(0x4a); + table[keyboard::f9 ] = key(0x4b); + table[keyboard::f10] = key(0x4c); + table[keyboard::f11] = key(0x5f); + table[keyboard::f12] = key(0x60); + + table[keyboard::print_screen] = key(0x6f); + table[keyboard::scroll_lock ] = key(0x4e); + table[keyboard::pause ] = key(0x6e); + + table[keyboard::tilde] = key(0x31); + + table[keyboard::num_0] = key(0x0a); + table[keyboard::num_1] = key(0x0b); + table[keyboard::num_2] = key(0x0c); + table[keyboard::num_3] = key(0x0d); + table[keyboard::num_4] = key(0x0e); + table[keyboard::num_5] = key(0x0f); + table[keyboard::num_6] = key(0x10); + table[keyboard::num_7] = key(0x11); + table[keyboard::num_8] = key(0x12); + table[keyboard::num_9] = key(0x13); + + table[keyboard::dash ] = key(0x14); + table[keyboard::equal ] = key(0x15); + table[keyboard::backspace] = key(0x16); + + table[keyboard::insert ] = key(0x6a); + table[keyboard::delete_ ] = key(0x6b); + table[keyboard::home ] = key(0x61); + table[keyboard::end ] = key(0x67); + table[keyboard::page_up ] = key(0x63); + table[keyboard::page_down] = key(0x69); + + table[keyboard::a] = key(0x26); + table[keyboard::b] = key(0x38); + table[keyboard::c] = key(0x36); + table[keyboard::d] = key(0x28); + table[keyboard::e] = key(0x1a); + table[keyboard::f] = key(0x29); + table[keyboard::g] = key(0x2a); + table[keyboard::h] = key(0x2b); + table[keyboard::i] = key(0x1f); + table[keyboard::j] = key(0x2c); + table[keyboard::k] = key(0x2d); + table[keyboard::l] = key(0x2e); + table[keyboard::m] = key(0x3a); + table[keyboard::n] = key(0x39); + table[keyboard::o] = key(0x20); + table[keyboard::p] = key(0x21); + table[keyboard::q] = key(0x18); + table[keyboard::r] = key(0x1b); + table[keyboard::s] = key(0x27); + table[keyboard::t] = key(0x1c); + table[keyboard::u] = key(0x1e); + table[keyboard::v] = key(0x37); + table[keyboard::w] = key(0x19); + table[keyboard::x] = key(0x35); + table[keyboard::y] = key(0x1d); + table[keyboard::z] = key(0x34); + + table[keyboard::lbracket ] = key(0x22); + table[keyboard::rbracket ] = key(0x23); + table[keyboard::backslash ] = key(0x33); + table[keyboard::semicolon ] = key(0x2f); + table[keyboard::apostrophe] = key(0x30); + table[keyboard::comma ] = key(0x3b); + table[keyboard::period ] = key(0x3c); + table[keyboard::slash ] = key(0x3d); + + table[keyboard::pad_0] = key(0x5a); + table[keyboard::pad_1] = key(0x57); + table[keyboard::pad_2] = key(0x58); + table[keyboard::pad_3] = key(0x59); + table[keyboard::pad_4] = key(0x53); + table[keyboard::pad_5] = key(0x54); + table[keyboard::pad_6] = key(0x55); + table[keyboard::pad_7] = key(0x4f); + table[keyboard::pad_8] = key(0x50); + table[keyboard::pad_9] = key(0x51); + + table[keyboard::add ] = key(0x56); + table[keyboard::subtract] = key(0x52); + table[keyboard::multiply] = key(0x3f); + table[keyboard::divide ] = key(0x70); + table[keyboard::enter ] = key(0x6c); + + table[keyboard::num_lock ] = key(0x4d); + table[keyboard::caps_lock] = key(0x42); + + table[keyboard::up ] = key(0x62); + table[keyboard::down ] = key(0x68); + table[keyboard::left ] = key(0x64); + table[keyboard::right] = key(0x66); + + table[keyboard::tab ] = key(0x17); + table[keyboard::return_ ] = key(0x24); + table[keyboard::spacebar] = key(0x41); + + table[keyboard::lctrl ] = key(0x25); + table[keyboard::rctrl ] = key(0x6d); + table[keyboard::lalt ] = key(0x40); + table[keyboard::ralt ] = key(0x71); + table[keyboard::lshift] = key(0x32); + table[keyboard::rshift] = key(0x3e); + table[keyboard::lsuper] = key(0x73); + table[keyboard::rsuper] = key(0x74); + table[keyboard::menu ] = key(0x75); + } + #undef key + + //===== + //Mouse + //===== + + Window root_return, child_return; + 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, + &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); + + //absolute -> relative conversion + table[mouse::x] = (int16_t)(root_x_return - screenwidth / 2); + table[mouse::y] = (int16_t)(root_y_return - 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); + } + } else { + table[mouse::x] = (int16_t)(root_x_return - relativex); + table[mouse::y] = (int16_t)(root_y_return - relativey); + + relativex = root_x_return; + relativey = root_y_return; + } + + //manual device polling is limited to only five buttons ... + table[mouse::button + 0] = (bool)(mask_return & Button1Mask); + table[mouse::button + 1] = (bool)(mask_return & Button2Mask); + table[mouse::button + 2] = (bool)(mask_return & Button3Mask); + table[mouse::button + 3] = (bool)(mask_return & Button4Mask); + table[mouse::button + 4] = (bool)(mask_return & Button5Mask); + + //========= + //Joypad(s) + //========= + SDL_JoystickUpdate(); + for(unsigned i = 0; i < joypad<>::count; i++) { + if(!gamepad[i]) continue; - #if !defined(_WIN32) - XQueryKeymap(display, keystate); - #endif - - for(int i = 0; i < 16; i++) { - if(!joy[i]) continue; - - joystate[i][joy_up] = false; - joystate[i][joy_down] = false; - joystate[i][joy_left] = false; - joystate[i][joy_right] = false; + unsigned index = joypad<>::index(i, joypad<>::none); + table[index + joypad<>::up ] = false; + table[index + joypad<>::down ] = false; + table[index + joypad<>::left ] = false; + table[index + joypad<>::right] = false; int resistance = settings.analog_axis_resistance; resistance = max(1, min(99, resistance)); - resistance = int(double(resistance) * 32768.0 / 100.0); + resistance = (int)((double)resistance * 32768.0 / 100.0); - //only poll X,Y axes for D-pad, left analog and right analog. - //note 1: right analog is swapped on some controllers, this cannot be helped. - //note 2: some controllers report more axes than physically exist. - //these tend to return fixed values (eg 32767), which makes state changes - //impossible to detect. hence why polling *all* axes is unsafe. - int axes = SDL_JoystickNumAxes(joy[i]); - for(int a = 0; a < min(axes, 6); a++) { - int value = SDL_JoystickGetAxis(joy[i], a); - if((a & 1) == 0) { //X axis - joystate[i][joy_left] |= value < -resistance; - joystate[i][joy_right] |= value > +resistance; - } else { //Y axis - joystate[i][joy_up] |= value < -resistance; - joystate[i][joy_down] |= value > +resistance; + unsigned axes = min((unsigned)joypad<>::axes, SDL_JoystickNumAxes(gamepad[i])); + for(unsigned axis = 0; axis < axes; axis++) { + int16_t value = (int16_t)SDL_JoystickGetAxis(gamepad[i], axis); + table[index + joypad<>::axis + axis] = value; + if(axis == 0) { //X-axis + table[index + joypad<>::left ] |= value < -resistance; + table[index + joypad<>::right] |= value > +resistance; + } else if(axis == 1) { //Y-axis + table[index + joypad<>::up ] |= value < -resistance; + table[index + joypad<>::down ] |= value > +resistance; } } - for(int b = 0; b < 16; b++) { - joystate[i][b] = SDL_JoystickGetButton(joy[i], b); + for(unsigned button = 0; button < joypad<>::buttons; button++) { + table[index + joypad<>::button + button] = SDL_JoystickGetButton(gamepad[i], button); } } + + return true; } bool init() { SDL_InitSubSystem(SDL_INIT_JOYSTICK); SDL_JoystickEventState(SDL_IGNORE); - for(int i = 0; i < SDL_NumJoysticks(); i++) { - joy[i] = SDL_JoystickOpen(i); - } - - #if !defined(_WIN32) display = XOpenDisplay(0); - #endif + rootwindow = DefaultRootWindow(display); + XWindowAttributes attributes; + XGetWindowAttributes(display, rootwindow, &attributes); + screenwidth = attributes.width; + screenheight = attributes.height; + + //Xlib: "because XShowCursor(false) would be too easy." + //create a fully transparent cursor named InvisibleCursor, + //for use while acquire() / XGrabPointer() is active. + 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); + + mouseacquired = false; + relativex = 0; + relativey = 0; + + for(unsigned i = 0; i < joypad<>::count && i < SDL_NumJoysticks(); i++) { + gamepad[i] = SDL_JoystickOpen(i); + } return true; } void term() { - for(int i = 0; i < 16; i++) { - if(joy[i]) SDL_JoystickClose(joy[i]); + unacquire(); + XFreeCursor(display, InvisibleCursor); + + for(unsigned i = 0; i < joypad<>::count; i++) { + if(gamepad[i]) SDL_JoystickClose(gamepad[i]); + gamepad[i] = 0; } + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); } pInputSDL(InputSDL &self_) : self(self_) { - for(int i = 0; i < 16; i++) joy[i] = 0; - clear(); - + for(unsigned i = 0; i < joypad<>::count; i++) gamepad[i] = 0; settings.analog_axis_resistance = 75; } }; @@ -306,9 +370,10 @@ public: bool InputSDL::cap(Setting setting) { return p.cap(setting); } uintptr_t InputSDL::get(Setting setting) { return p.get(setting); } bool InputSDL::set(Setting setting, uintptr_t param) { return p.set(setting, param); } -bool InputSDL::key_down(uint16_t key) { return p.key_down(key); } -void InputSDL::clear() { p.clear(); } -void InputSDL::poll() { p.poll(); } +bool InputSDL::acquire() { return p.acquire(); } +bool InputSDL::unacquire() { return p.unacquire(); } +bool InputSDL::acquired() { return p.acquired(); } +bool InputSDL::poll(int16_t *table) { return p.poll(table); } bool InputSDL::init() { return p.init(); } void InputSDL::term() { p.term(); } InputSDL::InputSDL() : p(*new pInputSDL(*this)) {} diff --git a/src/lib/ruby/input/sdl.h b/src/lib/ruby/input/sdl.hpp similarity index 72% rename from src/lib/ruby/input/sdl.h rename to src/lib/ruby/input/sdl.hpp index 9cf41045..dfee634b 100644 --- a/src/lib/ruby/input/sdl.h +++ b/src/lib/ruby/input/sdl.hpp @@ -6,10 +6,11 @@ public: uintptr_t get(Setting); bool set(Setting, uintptr_t); - bool key_down(uint16_t key); + bool acquire(); + bool unacquire(); + bool acquired(); - void clear(); - void poll(); + bool poll(int16_t *table); bool init(); void term(); diff --git a/src/lib/ruby/input/x.cpp b/src/lib/ruby/input/x.cpp index 8d7b1a88..5cc47d62 100644 --- a/src/lib/ruby/input/x.cpp +++ b/src/lib/ruby/input/x.cpp @@ -4,17 +4,14 @@ #include #include -#include - namespace ruby { -#include "x.h" +#include "x.hpp" class pInputX { public: InputX &self; Display *display; - char keymap[32]; bool cap(Input::Setting setting) { if(setting == Input::KeyboardSupport) return true; @@ -29,138 +26,133 @@ public: return false; } - bool key_down(uint16_t key) { - using namespace nall; - #define map(i) (keymap[i >> 3] & (1 << (i & 7))) - switch(key) { - case keyboard::escape: return map(0x09); + bool poll(int16_t *table) { + memset(table, 0, input_limit * sizeof(int16_t)); - case keyboard::f1: return map(0x43); - case keyboard::f2: return map(0x44); - case keyboard::f3: return map(0x45); - case keyboard::f4: return map(0x46); - case keyboard::f5: return map(0x47); - case keyboard::f6: return map(0x48); - case keyboard::f7: return map(0x49); - case keyboard::f8: return map(0x4a); - case keyboard::f9: return map(0x4b); - case keyboard::f10: return map(0x4c); - case keyboard::f11: return map(0x5f); - case keyboard::f12: return map(0x60); + #define key(n) (bool)(state[n >> 3] & (1 << (n & 7))) + char state[32]; + XQueryKeymap(display, state); - case keyboard::print_screen: return map(0x6f); - case keyboard::scroll_lock: return map(0x4e); - case keyboard::pause: return map(0x6e); + table[keyboard::escape] = key(0x09); - case keyboard::tilde: return map(0x31); + table[keyboard::f1 ] = key(0x43); + table[keyboard::f2 ] = key(0x44); + table[keyboard::f3 ] = key(0x45); + table[keyboard::f4 ] = key(0x46); + table[keyboard::f5 ] = key(0x47); + table[keyboard::f6 ] = key(0x48); + table[keyboard::f7 ] = key(0x49); + table[keyboard::f8 ] = key(0x4a); + table[keyboard::f9 ] = key(0x4b); + table[keyboard::f10] = key(0x4c); + table[keyboard::f11] = key(0x5f); + table[keyboard::f12] = key(0x60); - case keyboard::num_1: return map(0x0a); - case keyboard::num_2: return map(0x0b); - case keyboard::num_3: return map(0x0c); - case keyboard::num_4: return map(0x0d); - case keyboard::num_5: return map(0x0e); - case keyboard::num_6: return map(0x0f); - case keyboard::num_7: return map(0x10); - case keyboard::num_8: return map(0x11); - case keyboard::num_9: return map(0x12); - case keyboard::num_0: return map(0x13); + table[keyboard::print_screen] = key(0x6f); + table[keyboard::scroll_lock ] = key(0x4e); + table[keyboard::pause ] = key(0x6e); - case keyboard::dash: return map(0x14); - case keyboard::equal: return map(0x15); - case keyboard::backspace: return map(0x16); + table[keyboard::tilde] = key(0x31); - case keyboard::insert: return map(0x6a); - case keyboard::delete_: return map(0x6b); - case keyboard::home: return map(0x61); - case keyboard::end: return map(0x67); - case keyboard::page_up: return map(0x63); - case keyboard::page_down: return map(0x69); + table[keyboard::num_0] = key(0x0a); + table[keyboard::num_1] = key(0x0b); + table[keyboard::num_2] = key(0x0c); + table[keyboard::num_3] = key(0x0d); + table[keyboard::num_4] = key(0x0e); + table[keyboard::num_5] = key(0x0f); + table[keyboard::num_6] = key(0x10); + table[keyboard::num_7] = key(0x11); + table[keyboard::num_8] = key(0x12); + table[keyboard::num_9] = key(0x13); - case keyboard::a: return map(0x26); - case keyboard::b: return map(0x38); - case keyboard::c: return map(0x36); - case keyboard::d: return map(0x28); - case keyboard::e: return map(0x1a); - case keyboard::f: return map(0x29); - case keyboard::g: return map(0x2a); - case keyboard::h: return map(0x2b); - case keyboard::i: return map(0x1f); - case keyboard::j: return map(0x2c); - case keyboard::k: return map(0x2d); - case keyboard::l: return map(0x2e); - case keyboard::m: return map(0x3a); - case keyboard::n: return map(0x39); - case keyboard::o: return map(0x20); - case keyboard::p: return map(0x21); - case keyboard::q: return map(0x18); - case keyboard::r: return map(0x1b); - case keyboard::s: return map(0x27); - case keyboard::t: return map(0x1c); - case keyboard::u: return map(0x1e); - case keyboard::v: return map(0x37); - case keyboard::w: return map(0x19); - case keyboard::x: return map(0x35); - case keyboard::y: return map(0x1d); - case keyboard::z: return map(0x34); + table[keyboard::dash ] = key(0x14); + table[keyboard::equal ] = key(0x15); + table[keyboard::backspace] = key(0x16); - case keyboard::lbracket: return map(0x22); - case keyboard::rbracket: return map(0x23); - case keyboard::backslash: return map(0x33); - case keyboard::semicolon: return map(0x2f); - case keyboard::apostrophe: return map(0x30); - case keyboard::comma: return map(0x3b); - case keyboard::period: return map(0x3c); - case keyboard::slash: return map(0x3d); + table[keyboard::insert ] = key(0x6a); + table[keyboard::delete_ ] = key(0x6b); + table[keyboard::home ] = key(0x61); + table[keyboard::end ] = key(0x67); + table[keyboard::page_up ] = key(0x63); + table[keyboard::page_down] = key(0x69); - case keyboard::pad_1: return map(0x57); - case keyboard::pad_2: return map(0x58); - case keyboard::pad_3: return map(0x59); - case keyboard::pad_4: return map(0x53); - case keyboard::pad_5: return map(0x54); - case keyboard::pad_6: return map(0x55); - case keyboard::pad_7: return map(0x4f); - case keyboard::pad_8: return map(0x50); - case keyboard::pad_9: return map(0x51); + table[keyboard::a] = key(0x26); + table[keyboard::b] = key(0x38); + table[keyboard::c] = key(0x36); + table[keyboard::d] = key(0x28); + table[keyboard::e] = key(0x1a); + table[keyboard::f] = key(0x29); + table[keyboard::g] = key(0x2a); + table[keyboard::h] = key(0x2b); + table[keyboard::i] = key(0x1f); + table[keyboard::j] = key(0x2c); + table[keyboard::k] = key(0x2d); + table[keyboard::l] = key(0x2e); + table[keyboard::m] = key(0x3a); + table[keyboard::n] = key(0x39); + table[keyboard::o] = key(0x20); + table[keyboard::p] = key(0x21); + table[keyboard::q] = key(0x18); + table[keyboard::r] = key(0x1b); + table[keyboard::s] = key(0x27); + table[keyboard::t] = key(0x1c); + table[keyboard::u] = key(0x1e); + table[keyboard::v] = key(0x37); + table[keyboard::w] = key(0x19); + table[keyboard::x] = key(0x35); + table[keyboard::y] = key(0x1d); + table[keyboard::z] = key(0x34); - case keyboard::add: return map(0x56); - case keyboard::subtract: return map(0x52); - case keyboard::multiply: return map(0x3f); - case keyboard::divide: return map(0x70); - case keyboard::enter: return map(0x6c); + table[keyboard::lbracket ] = key(0x22); + table[keyboard::rbracket ] = key(0x23); + table[keyboard::backslash ] = key(0x33); + table[keyboard::semicolon ] = key(0x2f); + table[keyboard::apostrophe] = key(0x30); + table[keyboard::comma ] = key(0x3b); + table[keyboard::period ] = key(0x3c); + table[keyboard::slash ] = key(0x3d); - case keyboard::num_lock: return map(0x4d); - case keyboard::caps_lock: return map(0x42); + table[keyboard::pad_0] = key(0x5a); + table[keyboard::pad_1] = key(0x57); + table[keyboard::pad_2] = key(0x58); + table[keyboard::pad_3] = key(0x59); + table[keyboard::pad_4] = key(0x53); + table[keyboard::pad_5] = key(0x54); + table[keyboard::pad_6] = key(0x55); + table[keyboard::pad_7] = key(0x4f); + table[keyboard::pad_8] = key(0x50); + table[keyboard::pad_9] = key(0x51); - case keyboard::up: return map(0x62); - case keyboard::down: return map(0x68); - case keyboard::left: return map(0x64); - case keyboard::right: return map(0x66); + table[keyboard::add ] = key(0x56); + table[keyboard::subtract] = key(0x52); + table[keyboard::multiply] = key(0x3f); + table[keyboard::divide ] = key(0x70); + table[keyboard::enter ] = key(0x6c); - case keyboard::tab: return map(0x17); - case keyboard::return_: return map(0x24); - case keyboard::spacebar: return map(0x41); + table[keyboard::num_lock ] = key(0x4d); + table[keyboard::caps_lock] = key(0x42); - case keyboard::lctrl: return map(0x25); - case keyboard::rctrl: return map(0x6d); - case keyboard::lalt: return map(0x40); - case keyboard::ralt: return map(0x71); - case keyboard::lshift: return map(0x32); - case keyboard::rshift: return map(0x3e); - case keyboard::lsuper: return map(0x73); - case keyboard::rsuper: return map(0x74); - case keyboard::menu: return map(0x75); - } + table[keyboard::up ] = key(0x62); + table[keyboard::down ] = key(0x68); + table[keyboard::left ] = key(0x64); + table[keyboard::right] = key(0x66); - #undef map - return false; - } + table[keyboard::tab ] = key(0x17); + table[keyboard::return_ ] = key(0x24); + table[keyboard::spacebar] = key(0x41); - void clear() { - memset(keymap, 0, sizeof keymap); - } + table[keyboard::lctrl ] = key(0x25); + table[keyboard::rctrl ] = key(0x6d); + table[keyboard::lalt ] = key(0x40); + table[keyboard::ralt ] = key(0x71); + table[keyboard::lshift] = key(0x32); + table[keyboard::rshift] = key(0x3e); + table[keyboard::lsuper] = key(0x73); + table[keyboard::rsuper] = key(0x74); + table[keyboard::menu ] = key(0x75); + #undef key - void poll() { - XQueryKeymap(display, keymap); + return true; } bool init() { @@ -177,9 +169,7 @@ public: bool InputX::cap(Setting setting) { return p.cap(setting); } uintptr_t InputX::get(Setting setting) { return p.get(setting); } bool InputX::set(Setting setting, uintptr_t param) { return p.set(setting, param); } -bool InputX::key_down(uint16_t key) { return p.key_down(key); } -void InputX::clear() { p.clear(); } -void InputX::poll() { p.poll(); } +bool InputX::poll(int16_t *table) { return p.poll(table); } bool InputX::init() { return p.init(); } void InputX::term() { p.term(); } InputX::InputX() : p(*new pInputX(*this)) {} diff --git a/src/lib/ruby/input/x.h b/src/lib/ruby/input/x.hpp similarity index 72% rename from src/lib/ruby/input/x.h rename to src/lib/ruby/input/x.hpp index 92b7c948..72f9979b 100644 --- a/src/lib/ruby/input/x.h +++ b/src/lib/ruby/input/x.hpp @@ -6,10 +6,7 @@ public: uintptr_t get(Setting); bool set(Setting, uintptr_t); - bool key_down(uint16_t key); - - void clear(); - void poll(); + bool poll(int16_t *table); bool init(); void term(); diff --git a/src/lib/ruby/ruby.cpp b/src/lib/ruby/ruby.cpp index d8c27687..a8176837 100644 --- a/src/lib/ruby/ruby.cpp +++ b/src/lib/ruby/ruby.cpp @@ -1,4 +1,4 @@ -#include +#include #include namespace ruby { @@ -285,10 +285,10 @@ void InputInterface::term() { bool InputInterface::cap(Input::Setting setting) { return p ? p->cap(setting) : false; } uintptr_t InputInterface::get(Input::Setting setting) { return p ? p->get(setting) : false; } bool InputInterface::set(Input::Setting setting, uintptr_t param) { return p ? p->set(setting, param) : false; } -bool InputInterface::key_down(uint16_t key) { return p ? p->key_down(key) : false; } -bool InputInterface::key_up(uint16_t key) { return p ? p->key_up(key) : true; } -void InputInterface::clear() { if(p) p->clear(); } -void InputInterface::poll() { if(p) p->poll(); } +bool InputInterface::acquire() { return p ? p->acquire() : false; } +bool InputInterface::unacquire() { return p ? p->unacquire() : false; } +bool InputInterface::acquired() { return p ? p->acquired() : false; } +bool InputInterface::poll(int16_t *table) { return p ? p->poll(table) : false; } InputInterface::InputInterface() : p(0) {} InputInterface::~InputInterface() { term(); } diff --git a/src/lib/ruby/ruby.h b/src/lib/ruby/ruby.hpp similarity index 91% rename from src/lib/ruby/ruby.h rename to src/lib/ruby/ruby.hpp index 67a3d1d4..9aff63b2 100644 --- a/src/lib/ruby/ruby.h +++ b/src/lib/ruby/ruby.hpp @@ -14,13 +14,14 @@ #include using nall::min; using nall::max; +using nall::sclamp; using nall::zeromemory; namespace ruby { -#include -#include -#include +#include +#include +#include class VideoInterface { public: @@ -84,10 +85,12 @@ public: bool cap(Input::Setting setting); uintptr_t get(Input::Setting setting); bool set(Input::Setting setting, uintptr_t param); - bool key_down(uint16_t key); - bool key_up(uint16_t key); - void clear(); - void poll(); + + bool acquire(); + bool unacquire(); + bool acquired(); + + bool poll(int16_t *table); InputInterface(); ~InputInterface(); diff --git a/src/lib/ruby/video.h b/src/lib/ruby/video.hpp similarity index 100% rename from src/lib/ruby/video.h rename to src/lib/ruby/video.hpp diff --git a/src/lib/ruby/video/direct3d.cpp b/src/lib/ruby/video/direct3d.cpp index 39e5e13c..56a73850 100644 --- a/src/lib/ruby/video/direct3d.cpp +++ b/src/lib/ruby/video/direct3d.cpp @@ -3,11 +3,9 @@ #define D3DVERTEX (D3DFVF_XYZRHW | D3DFVF_TEX1) -#include - namespace ruby { -#include "direct3d.h" +#include "direct3d.hpp" class pVideoD3D { public: diff --git a/src/lib/ruby/video/direct3d.h b/src/lib/ruby/video/direct3d.hpp similarity index 100% rename from src/lib/ruby/video/direct3d.h rename to src/lib/ruby/video/direct3d.hpp diff --git a/src/lib/ruby/video/directdraw.cpp b/src/lib/ruby/video/directdraw.cpp index e3691aa6..0092e131 100644 --- a/src/lib/ruby/video/directdraw.cpp +++ b/src/lib/ruby/video/directdraw.cpp @@ -1,11 +1,9 @@ #include #include -#include - namespace ruby { -#include "directdraw.h" +#include "directdraw.hpp" class pVideoDD { public: diff --git a/src/lib/ruby/video/directdraw.h b/src/lib/ruby/video/directdraw.hpp similarity index 100% rename from src/lib/ruby/video/directdraw.h rename to src/lib/ruby/video/directdraw.hpp diff --git a/src/lib/ruby/video/gdi.cpp b/src/lib/ruby/video/gdi.cpp index 7a29c9c1..41bce7ba 100644 --- a/src/lib/ruby/video/gdi.cpp +++ b/src/lib/ruby/video/gdi.cpp @@ -1,11 +1,9 @@ #include #include -#include - namespace ruby { -#include "gdi.h" +#include "gdi.hpp" class pVideoGDI { public: diff --git a/src/lib/ruby/video/gdi.h b/src/lib/ruby/video/gdi.hpp similarity index 100% rename from src/lib/ruby/video/gdi.h rename to src/lib/ruby/video/gdi.hpp diff --git a/src/lib/ruby/video/glx.cpp b/src/lib/ruby/video/glx.cpp index 0ce12f40..909390cc 100644 --- a/src/lib/ruby/video/glx.cpp +++ b/src/lib/ruby/video/glx.cpp @@ -26,11 +26,9 @@ #include #include -#include - namespace ruby { -#include "glx.h" +#include "glx.hpp" //returns true once window is mapped (created and displayed onscreen) static Bool glx_wait_for_map_notify(Display *d, XEvent *e, char *arg) { diff --git a/src/lib/ruby/video/glx.h b/src/lib/ruby/video/glx.hpp similarity index 100% rename from src/lib/ruby/video/glx.h rename to src/lib/ruby/video/glx.hpp diff --git a/src/lib/ruby/video/sdl.cpp b/src/lib/ruby/video/sdl.cpp index a39b3718..3d80515a 100644 --- a/src/lib/ruby/video/sdl.cpp +++ b/src/lib/ruby/video/sdl.cpp @@ -8,11 +8,9 @@ #include #include -#include - namespace ruby { -#include "sdl.h" +#include "sdl.hpp" class pVideoSDL { public: @@ -100,9 +98,11 @@ public: bool init() { display = XOpenDisplay(0); + char env[512]; sprintf(env, "SDL_WINDOWID=%ld", settings.handle); putenv(env); + SDL_InitSubSystem(SDL_INIT_VIDEO); //screen depth must be 32, as 24bpp with a 32-bit X window visual produces no output. screen = SDL_SetVideoMode(2560, 1600, 32, SDL_HWSURFACE); diff --git a/src/lib/ruby/video/sdl.h b/src/lib/ruby/video/sdl.hpp similarity index 100% rename from src/lib/ruby/video/sdl.h rename to src/lib/ruby/video/sdl.hpp diff --git a/src/lib/ruby/video/wgl.cpp b/src/lib/ruby/video/wgl.cpp index c06b97aa..e074e376 100644 --- a/src/lib/ruby/video/wgl.cpp +++ b/src/lib/ruby/video/wgl.cpp @@ -9,11 +9,9 @@ #include #include -#include - namespace ruby { -#include "wgl.h" +#include "wgl.hpp" class pVideoWGL { public: diff --git a/src/lib/ruby/video/wgl.h b/src/lib/ruby/video/wgl.hpp similarity index 100% rename from src/lib/ruby/video/wgl.h rename to src/lib/ruby/video/wgl.hpp diff --git a/src/lib/ruby/video/xv.cpp b/src/lib/ruby/video/xv.cpp index fecb544e..2198ed51 100644 --- a/src/lib/ruby/video/xv.cpp +++ b/src/lib/ruby/video/xv.cpp @@ -9,11 +9,9 @@ extern "C" XvImage* XvShmCreateImage(Display*, XvPortID, int, char*, int, int, XShmSegmentInfo*); -#include - namespace ruby { -#include "xv.h" +#include "xv.hpp" class pVideoXv { public: diff --git a/src/lib/ruby/video/xv.h b/src/lib/ruby/video/xv.hpp similarity index 100% rename from src/lib/ruby/video/xv.h rename to src/lib/ruby/video/xv.hpp diff --git a/src/lib/sync.bat b/src/lib/sync.bat new file mode 100644 index 00000000..88362bdd --- /dev/null +++ b/src/lib/sync.bat @@ -0,0 +1,11 @@ +rmdir /Q /S nall +rmdir /Q /S hiro +rmdir /Q /S ruby + +mkdir nall +mkdir hiro +mkdir ruby + +xcopy /E ..\..\..\nall nall +xcopy /E ..\..\..\hiro hiro +xcopy /E ..\..\..\ruby ruby diff --git a/src/lib/sync.sh b/src/lib/sync.sh index f8ecd2a7..b191c01f 100644 --- a/src/lib/sync.sh +++ b/src/lib/sync.sh @@ -1,6 +1,7 @@ rm -r nall rm -r hiro rm -r ruby + cp -r ../../../nall ./nall cp -r ../../../hiro ./hiro cp -r ../../../ruby ./ruby diff --git a/src/memory/smemory/smemory.cpp b/src/memory/smemory/smemory.cpp index 6fbf95fd..696af789 100644 --- a/src/memory/smemory/smemory.cpp +++ b/src/memory/smemory/smemory.cpp @@ -30,8 +30,6 @@ void sBus::load_cart() { if(cartridge.info.obc1) map_obc1(); if(cartridge.info.st010) map_st010(); - snes.set_region(cartridge.region() == Cartridge::NTSC ? SNES::NTSC : SNES::PAL); - is_cart_loaded = true; } diff --git a/src/ppu/bppu/bppu.cpp b/src/ppu/bppu/bppu.cpp index 84a27911..6275d3b6 100644 --- a/src/ppu/bppu/bppu.cpp +++ b/src/ppu/bppu/bppu.cpp @@ -73,7 +73,11 @@ void bPPU::power() { for(unsigned i = 0; i < memory::oam.size(); i++) memory::oam[i] = 0x00; for(unsigned i = 0; i < memory::cgram.size(); i++) memory::cgram[i] = 0x00; - region = snes.region(); + if(snes.region() == SNES::NTSC) { + region = 0; + } else /* (snes.region == SNES::PAL) */ { + region = 1; + } //$2100 regs.display_disabled = 1; diff --git a/src/reader/filereader.cpp b/src/reader/filereader.cpp index 0b26529c..c3318d5e 100644 --- a/src/reader/filereader.cpp +++ b/src/reader/filereader.cpp @@ -1,49 +1,50 @@ #ifdef READER_CPP -#include "filereader.h" - -unsigned FileReader::size() { - return fp.size(); -} - -//This function will allocate memory even if open() fails. -//This is needed so that when SRAM files do not exist, the -//memory for the SRAM data will be allocated still. -//The memory is flushed to 0x00 when no file is opened. -uint8_t* FileReader::read(unsigned length) { - uint8_t *data = 0; +#include "filereader.h" - if(length == 0) { - //read the entire file into RAM - data = new(zeromemory) uint8_t[fp.size()]; - if(fp.open()) fp.read(data, fp.size()); - } else if(length > fp.size()) { - //read the entire file into RAM, pad the rest with 0x00s - data = new(zeromemory) uint8_t[length]; - if(fp.open()) fp.read(data, fp.size()); - } else { //filesize >= length - //read only what was requested - data = new(zeromemory) uint8_t[length]; - if(fp.open()) fp.read(data, length); - } - return data; -} - -bool FileReader::ready() { - return fp.open(); -} - -FileReader::FileReader(const char *fn) { - if(!fp.open(fn, file::mode_read)) return; - - //empty file? - if(fp.size() == 0) { - fp.close(); - } -} - -FileReader::~FileReader() { - if(fp.open()) fp.close(); -} +unsigned FileReader::size() { + return fp.size(); +} + +//This function will allocate memory even if open() fails. +//This is needed so that when SRAM files do not exist, the +//memory for the SRAM data will be allocated still. +//The memory is flushed to 0x00 when no file is opened. +uint8_t* FileReader::read(unsigned length) { + uint8_t *data = 0; + + if(length == 0) { + //read the entire file into RAM + data = new(zeromemory) uint8_t[fp.size()]; + if(fp.open()) fp.read(data, fp.size()); + } else if(length > fp.size()) { + //read the entire file into RAM, pad the rest with 0x00s + data = new(zeromemory) uint8_t[length]; + if(fp.open()) fp.read(data, fp.size()); + } else { //filesize >= length + //read only what was requested + data = new(zeromemory) uint8_t[length]; + if(fp.open()) fp.read(data, length); + } + + return data; +} + +bool FileReader::ready() { + return fp.open(); +} + +FileReader::FileReader(const char *fn) { + if(!fp.open(fn, file::mode_read)) return; + + if(fp.size() == 0) { + //empty file + fp.close(); + } +} + +FileReader::~FileReader() { + if(fp.open()) fp.close(); +} #endif //ifdef READER_CPP diff --git a/src/reader/gzreader.cpp b/src/reader/gzreader.cpp index 8b04b60f..ce53f071 100644 --- a/src/reader/gzreader.cpp +++ b/src/reader/gzreader.cpp @@ -1,83 +1,85 @@ #ifdef READER_CPP -#include "gzreader.h" - -unsigned GZReader::size() { - return filesize; -} - -//This function will allocate memory even if open() fails. -//This is needed so that when SRAM files do not exist, the -//memory for the SRAM data will be allocated still. -//The memory is flushed to 0x00 when no file is opened. -uint8_t* GZReader::read(unsigned length) { - uint8_t *data = 0; +#include "gzreader.h" - if(length == 0) { - //read the entire file into RAM - data = new(zeromemory) uint8_t[filesize]; - if(gp) gzread(gp, data, filesize); - } else if(length > filesize) { - //read the entire file into RAM, pad the rest with 0x00s - data = new(zeromemory) uint8_t[length]; - if(gp) gzread(gp, data, filesize); - } else { //filesize >= length - //read only what was requested - data = new(zeromemory) uint8_t[length]; - if(gp) gzread(gp, data, length); - } +unsigned GZReader::size() { + return filesize; +} - return data; -} - -bool GZReader::ready() { - return (gp != 0); -} - -GZReader::GZReader(const char *fn) { - gp = 0; - FILE *fp = fopen(fn, "rb"); - if(!fp) return; - - fseek(fp, 0, SEEK_END); - filesize = ftell(fp); - - if(filesize < 4) { - fclose(fp); - fp = 0; - return; - } - -uint32 gzsize; - fseek(fp, -4, SEEK_END); //jump to 4 bytes before end - gzsize = fgetc(fp); - gzsize |= fgetc(fp) << 8; - gzsize |= fgetc(fp) << 16; - gzsize |= fgetc(fp) << 24; - - fclose(fp); - fp = 0; - - gp = gzopen(fn, "rb"); - if(!gp) return; - - if(!gzdirect(gp)) { - filesize = gzsize; - } - - //empty file? - if(filesize == 0) { - gzclose(gp); - gp = 0; - return; - } -} - -GZReader::~GZReader() { - if(gp) { - gzclose(gp); - gp = 0; - } -} +//This function will allocate memory even if open() fails. +//This is needed so that when SRAM files do not exist, the +//memory for the SRAM data will be allocated still. +//The memory is flushed to 0x00 when no file is opened. +uint8_t* GZReader::read(unsigned length) { + uint8_t *data = 0; + + if(length == 0) { + //read the entire file into RAM + data = new(zeromemory) uint8_t[filesize]; + if(gp) gzread(gp, data, filesize); + } else if(length > filesize) { + //read the entire file into RAM, pad the rest with 0x00s + data = new(zeromemory) uint8_t[length]; + if(gp) gzread(gp, data, filesize); + } else { //filesize >= length + //read only what was requested + data = new(zeromemory) uint8_t[length]; + if(gp) gzread(gp, data, length); + } + + return data; +} + +bool GZReader::ready() { + return (gp != 0); +} + +GZReader::GZReader(const char *fn) : gp(0) { + #if !defined(_WIN32) + fp = fopen(fn, "rb"); + #else + fp = _wfopen(utf16(fn), L"rb"); + #endif + if(!fp) return; + + fseek(fp, 0, SEEK_END); + filesize = ftell(fp); + + if(filesize < 4) { + //too small to be a valid GZ archive + fclose(fp); + fp = 0; + return; + } + + fseek(fp, -4, SEEK_END); + unsigned gzsize; + gzsize = fgetc(fp); + gzsize |= fgetc(fp) << 8; + gzsize |= fgetc(fp) << 16; + gzsize |= fgetc(fp) << 24; + fseek(fp, 0, SEEK_SET); + + //zlib does not support UTF-8 filenames on Windows, + //thus _wfopen() wrapper above + fileno() wrapper here. + gp = gzdopen(fileno(fp), "rb"); + if(!gp) return; + + if(gzdirect(gp) == false) filesize = gzsize; + + if(filesize == 0) { + //archive is empty + gzclose(gp); + gp = 0; + return; + } +} + +GZReader::~GZReader() { + if(gp) { + gzclose(gp); + gp = 0; + } +} #endif //ifdef READER_CPP diff --git a/src/reader/gzreader.h b/src/reader/gzreader.h index a49682be..f009fe1d 100644 --- a/src/reader/gzreader.h +++ b/src/reader/gzreader.h @@ -2,8 +2,9 @@ class GZReader : public Reader { private: + FILE *fp; gzFile gp; - uint32 filesize; + unsigned filesize; public: unsigned size(); diff --git a/src/reader/jmareader.cpp b/src/reader/jmareader.cpp index 6d7ed3c5..69e3840b 100644 --- a/src/reader/jmareader.cpp +++ b/src/reader/jmareader.cpp @@ -1,40 +1,38 @@ #ifdef READER_CPP -#include "jmareader.h" -#include "jma/jma.h" - -unsigned JMAReader::size() { - return filesize; -} - -#define MAXROM 0x800000 - -uint8_t* JMAReader::read(unsigned length) { - uint8_t *data = 0; +#include "jmareader.h" +#include "jma/jma.h" + +unsigned JMAReader::size() { + return filesize; +} + +uint8_t* JMAReader::read(unsigned length) { + uint8_t *data = 0; if(!filesize) return 0; - - if(length <= filesize) { - //read the entire file into RAM - data = new(zeromemory) uint8_t[filesize]; - JMAFile.extract_file(cname, data); - } else if(length > filesize) { - //read the entire file into RAM, pad the rest with 0x00s - data = new(zeromemory) uint8_t[length]; - JMAFile.extract_file(cname, data); + + if(length <= filesize) { + //read the entire file into RAM + data = new(zeromemory) uint8_t[filesize]; + JMAFile.extract_file(cname, data); + } else if(length > filesize) { + //read the entire file into RAM, pad the rest with 0x00s + data = new(zeromemory) uint8_t[length]; + JMAFile.extract_file(cname, data); } - - return data; -} - -JMAReader::JMAReader(const char *fn) : JMAFile(fn), filesize(0) { - std::vector file_info = JMAFile.get_files_info(); - for(std::vector::iterator i = file_info.begin(); i != file_info.end(); i++) { - //Check for valid ROM based on size - if((i->size <= MAXROM + 512) && (i->size > filesize)) { - cname = i->name; - filesize = i->size; - } - } -} + + return data; +} + +JMAReader::JMAReader(const char *fn) : JMAFile(fn), filesize(0) { + std::vector file_info = JMAFile.get_files_info(); + for(std::vector::iterator i = file_info.begin(); i != file_info.end(); i++) { + //Check for valid ROM based on size + if((i->size <= 0x1000000 + 512) && (i->size > filesize)) { + cname = i->name; + filesize = i->size; + } + } +} #endif //ifdef READER_CPP diff --git a/src/reader/jmareader.h b/src/reader/jmareader.h index bb3d6450..c3d12754 100644 --- a/src/reader/jmareader.h +++ b/src/reader/jmareader.h @@ -5,10 +5,10 @@ public: unsigned size(); uint8_t* read(unsigned length = 0); - JMAReader(const char *fn); - -private: - JMA::jma_open JMAFile; - uint32 filesize; - std::string cname; + JMAReader(const char *fn); + +private: + JMA::jma_open JMAFile; + unsigned filesize; + std::string cname; }; diff --git a/src/reader/zipreader.cpp b/src/reader/zipreader.cpp index 12ae9f33..8975ed4a 100644 --- a/src/reader/zipreader.cpp +++ b/src/reader/zipreader.cpp @@ -6,18 +6,15 @@ unsigned ZipReader::size() { return filesize; } -#define MAXROM 0x800000 - uint8_t* ZipReader::read(unsigned length) { - uint8_t *data = 0; - if(!filesize) return 0; + uint8_t *data = 0; if(length <= filesize) { //read the entire file into RAM data = new(zeromemory) uint8_t[filesize]; unzReadCurrentFile(zipfile, data, filesize); - } else if(length > filesize) { + } else { /* length > filesize */ //read the entire file into RAM, pad the rest with 0x00s data = new(zeromemory) uint8_t[length]; unzReadCurrentFile(zipfile, data, filesize); @@ -31,15 +28,16 @@ bool ZipReader::ready() { } ZipReader::ZipReader(const char *fn) : filesize(0), zipready(false) { - unz_file_info cFileInfo; //Create variable to hold info for a compressed file + unz_file_info cFileInfo; //Create variable to hold info for a compressed file char cFileName[sizeof(cname)]; - if(zipfile = unzOpen(fn)) { //Open zip file + if(zipfile = unzOpen(fn)) { //Open zip file for(int cFile = unzGoToFirstFile(zipfile); cFile == UNZ_OK; cFile = unzGoToNextFile(zipfile)) { //Gets info on current file, and places it in cFileInfo unzGetCurrentFileInfo(zipfile, &cFileInfo, cFileName, sizeof(cname), 0, 0, 0, 0); - if((cFileInfo.uncompressed_size <= MAXROM+512) && (cFileInfo.uncompressed_size > filesize)) { + //verify uncompressed file is not bigger than max ROM size + if((cFileInfo.uncompressed_size <= 0x1000000 + 512) && (cFileInfo.uncompressed_size > filesize)) { strcpy(cname, cFileName); filesize = cFileInfo.uncompressed_size; } diff --git a/src/reader/zipreader.h b/src/reader/zipreader.h index 91cce200..94639ab9 100644 --- a/src/reader/zipreader.h +++ b/src/reader/zipreader.h @@ -1,7 +1,6 @@ #include "zlib/unzip.h" -//Could be up to 65536 -#define ZIP_MAX_FILE_NAME 4096 +#define ZIP_MAX_FILE_NAME PATH_MAX class ZipReader : public Reader { public: @@ -14,7 +13,7 @@ public: private: unzFile zipfile; - uint32 filesize; + unsigned filesize; bool zipready; - char cname[4096]; + char cname[PATH_MAX]; }; diff --git a/src/reader/zlib/ioapi.c b/src/reader/zlib/ioapi.c index f1bee23e..d2ddb606 100644 --- a/src/reader/zlib/ioapi.c +++ b/src/reader/zlib/ioapi.c @@ -10,6 +10,12 @@ #include #include +#if defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +//needed for MultiByteToWideChar() +#include +#endif + #include "zlib.h" #include "ioapi.h" @@ -81,8 +87,18 @@ voidpf ZCALLBACK fopen_file_func (opaque, filename, mode) if (mode & ZLIB_FILEFUNC_MODE_CREATE) mode_fopen = "wb"; - if ((filename!=NULL) && (mode_fopen != NULL)) + if ((filename!=NULL) && (mode_fopen != NULL)) { + //[2008-10-17 byuu] wrap Win32 fopen() to support UTF-8 filenames + #if !defined(_WIN32) file = fopen(filename, mode_fopen); + #else + wchar_t wfilename[_MAX_PATH + 1]; + wchar_t wmode_fopen[_MAX_PATH + 1]; + MultiByteToWideChar(CP_UTF8, 0, filename, -1, wfilename, _MAX_PATH); + MultiByteToWideChar(CP_UTF8, 0, mode_fopen, -1, wmode_fopen, _MAX_PATH); + file = _wfopen(wfilename, wmode_fopen); + #endif + } return file; } diff --git a/src/snes/audio/audio.h b/src/snes/audio/audio.h index fe51f3eb..6551023b 100644 --- a/src/snes/audio/audio.h +++ b/src/snes/audio/audio.h @@ -1,14 +1,16 @@ class Audio { public: - //if a filename is not specified, one will be generated - //automatically ("audio%0.3d.wav") - void log_enable(const char *fn = 0); - void log_disable(); - - void update(uint16 l_sample, uint16 r_sample); - void init(); - void term(); + //if a filename is not specified, one will be generated + //automatically ("audio%0.3d.wav") + void log_enable(const char *fn = 0); + void log_disable(); + + void update(uint16 l_sample, uint16 r_sample); + void init(); + void term(); private: FILE *pcmfp; + + friend class SNES; } audio; diff --git a/src/snes/input/input.cpp b/src/snes/input/input.cpp index d3edf7f4..4012c221 100644 --- a/src/snes/input/input.cpp +++ b/src/snes/input/input.cpp @@ -1,46 +1,47 @@ #ifdef SNES_CPP -uint8 SNES::Input::port_read(bool port) { - switch(port_device[port]) { - case DeviceJoypad: { - if(port_counter0[port] >= 16) return 1; +uint8 SNES::Input::port_read(bool portnumber) { + port_t &p = port[portnumber]; - unsigned deviceid = port == 0 ? DeviceIDJoypad1 : DeviceIDJoypad2; - return snesinterface.input_poll(deviceid, port_counter0[port]++); + switch(p.device) { + case DeviceJoypad: { + if(p.counter0 >= 16) return 1; + unsigned deviceid = (portnumber == 0 ? DeviceIDJoypad1 : DeviceIDJoypad2); + return snesinterface.input_poll(deviceid, p.counter0++); } //case DeviceJoypad case DeviceMultitap: { if(cpu.joylatch()) return 2; //when latch is high -- data2 = 1, data1 = 0 unsigned deviceidx, deviceid0, deviceid1; - if(port == 0) { + if(portnumber == 0) { if(cpu.pio() & 0x40) { - deviceidx = port_counter0[port]; + deviceidx = p.counter0; if(deviceidx >= 16) return 3; - port_counter0[port]++; + p.counter0++; deviceid0 = DeviceIDMultitap1A; deviceid1 = DeviceIDMultitap1B; } else { - deviceidx = port_counter1[port]; + deviceidx = p.counter1; if(deviceidx >= 16) return 3; - port_counter1[port]++; + p.counter1++; deviceid0 = DeviceIDMultitap1C; deviceid1 = DeviceIDMultitap1D; } } else { if(cpu.pio() & 0x80) { - deviceidx = port_counter0[port]; + deviceidx = p.counter0; if(deviceidx >= 16) return 3; - port_counter0[port]++; + p.counter0++; deviceid0 = DeviceIDMultitap2A; deviceid1 = DeviceIDMultitap2B; } else { - deviceidx = port_counter1[port]; + deviceidx = p.counter1; if(deviceidx >= 16) return 3; - port_counter1[port]++; + p.counter1++; deviceid0 = DeviceIDMultitap2C; deviceid1 = DeviceIDMultitap2D; @@ -50,29 +51,291 @@ uint8 SNES::Input::port_read(bool port) { return (snesinterface.input_poll(deviceid0, deviceidx) << 0) | (snesinterface.input_poll(deviceid1, deviceidx) << 1); } //case DeviceMultitap - } + + case DeviceMouse: { + if(p.counter0 >= 32) return 1; + unsigned deviceid = (portnumber == 0 ? DeviceIDMouse1 : DeviceIDMouse2); + + int position_x = snesinterface.input_poll(deviceid, MouseX); //-n = left, 0 = center, +n = right + int position_y = snesinterface.input_poll(deviceid, MouseY); //-n = up, 0 = center, +n = right + + bool direction_x = position_x < 0; //0 = right, 1 = left + bool direction_y = position_y < 0; //0 = down, 1 = up + + if(position_x < 0) position_x = -position_x; //abs(position_x) + if(position_y < 0) position_y = -position_y; //abs(position_x) + + position_x = min(127, position_x); //range = 0 - 127 + position_y = min(127, position_y); //range = 0 - 127 + + switch(p.counter0++) { default: + case 0: return 0; + case 1: return 0; + case 2: return 0; + case 3: return 0; + case 4: return 0; + case 5: return 0; + case 6: return 0; + case 7: return 0; + + case 8: return snesinterface.input_poll(deviceid, MouseRight); + case 9: return snesinterface.input_poll(deviceid, MouseLeft); + case 10: return 0; //speed (0 = slow, 1 = normal, 2 = fast, 3 = unused) + case 11: return 0; // || + + case 12: return 0; //signature + case 13: return 0; // || + case 14: return 0; // || + case 15: return 1; // || + + case 16: return (direction_y) & 1; + case 17: return (position_y >> 6) & 1; + case 18: return (position_y >> 5) & 1; + case 19: return (position_y >> 4) & 1; + case 20: return (position_y >> 3) & 1; + case 21: return (position_y >> 2) & 1; + case 22: return (position_y >> 1) & 1; + case 23: return (position_y >> 0) & 1; + + case 24: return (direction_x) & 1; + case 25: return (position_x >> 6) & 1; + case 26: return (position_x >> 5) & 1; + case 27: return (position_x >> 4) & 1; + case 28: return (position_x >> 3) & 1; + case 29: return (position_x >> 2) & 1; + case 30: return (position_x >> 1) & 1; + case 31: return (position_x >> 0) & 1; + } + } //case DeviceMouse + + case DeviceSuperScope: { + if(portnumber == 0) break; //Super Scope in port 1 not supported ... + if(p.counter0 >= 8) return 1; + + if(p.counter0 == 0) { + //turbo is a switch; toggle is edge sensitive + bool turbo = snesinterface.input_poll(DeviceIDSuperScope, SuperScopeTurbo); + if(turbo && !p.superscope.turbolock) { + p.superscope.turbo = !p.superscope.turbo; //toggle state + p.superscope.turbolock = true; + } else if(!turbo) { + p.superscope.turbolock = false; + } + + //trigger is a button + //if turbo is active, trigger is level sensitive; otherwise it is edge sensitive + p.superscope.trigger = false; + bool trigger = snesinterface.input_poll(DeviceIDSuperScope, SuperScopeTrigger); + if(trigger && (p.superscope.turbo || !p.superscope.triggerlock)) { + p.superscope.trigger = true; + p.superscope.triggerlock = true; + } else if(!trigger) { + p.superscope.triggerlock = false; + } + + //cursor is a button; it is always level sensitive + p.superscope.cursor = snesinterface.input_poll(DeviceIDSuperScope, SuperScopeCursor); + + //pause is a button; it is always edge sensitive + p.superscope.pause = false; + bool pause = snesinterface.input_poll(DeviceIDSuperScope, SuperScopePause); + if(pause && !p.superscope.pauselock) { + p.superscope.pause = true; + p.superscope.pauselock = true; + } else if(!pause) { + p.superscope.pauselock = false; + } + + p.superscope.offscreen = + p.superscope.x < 0 || p.superscope.x >= 256 + || p.superscope.y < 0 || p.superscope.y >= (ppu.overscan() ? 240 : 225); + } + + switch(p.counter0++) { + case 0: return p.superscope.trigger; + case 1: return p.superscope.cursor; + case 2: return p.superscope.turbo; + case 3: return p.superscope.pause; + case 4: return 0; + case 5: return 0; + case 6: return p.superscope.offscreen; + case 7: return 0; //noise (1 = yes) + } + } //case DeviceSuperScope + + case DeviceJustifier: + case DeviceJustifiers: { + if(portnumber == 0) break; //Justifier in port 1 not supported ... + if(p.counter0 >= 32) return 1; + + if(p.counter0 == 0) { + p.justifier.trigger1 = snesinterface.input_poll(DeviceIDJustifier1, JustifierTrigger); + p.justifier.start1 = snesinterface.input_poll(DeviceIDJustifier1, JustifierStart); + + if(p.device == DeviceJustifiers) { + p.justifier.trigger2 = snesinterface.input_poll(DeviceIDJustifier2, JustifierTrigger); + p.justifier.start2 = snesinterface.input_poll(DeviceIDJustifier2, JustifierStart); + } else { + p.justifier.x2 = -1; + p.justifier.y2 = -1; + + p.justifier.trigger2 = false; + p.justifier.start2 = false; + } + } + + switch(p.counter0++) { + case 0: return 0; + case 1: return 0; + case 2: return 0; + case 3: return 0; + case 4: return 0; + case 5: return 0; + case 6: return 0; + case 7: return 0; + case 8: return 0; + case 9: return 0; + case 10: return 0; + case 11: return 0; + + case 12: return 1; //signature + case 13: return 1; // || + case 14: return 1; // || + case 15: return 0; // || + + case 16: return 0; + case 17: return 1; + case 18: return 0; + case 19: return 1; + case 20: return 0; + case 21: return 1; + case 22: return 0; + case 23: return 1; + + case 24: return p.justifier.trigger1; + case 25: return p.justifier.trigger2; + case 26: return p.justifier.start1; + case 27: return p.justifier.start2; + case 28: return p.justifier.active; + + case 29: return 0; + case 30: return 0; + case 31: return 0; + } + } //case DeviceJustifier(s) + } //switch(p.device) //no device connected return 0; } -void SNES::Input::port_set_device(bool port, unsigned device) { - port_device[port] = device; - port_counter0[port] = 0; - port_counter1[port] = 0; +//scan all input; update cursor positions if needed +void SNES::Input::update() { + snesinterface.input_poll(); + port_t &p = port[1]; + + switch(p.device) { + case DeviceSuperScope: { + int x = snesinterface.input_poll(DeviceIDSuperScope, SuperScopeX); + int y = snesinterface.input_poll(DeviceIDSuperScope, SuperScopeY); + x += p.superscope.x; + y += p.superscope.y; + p.superscope.x = max(-16, min(256 + 16, x)); + p.superscope.y = max(-16, min(240 + 16, y)); + + latchx = p.superscope.x; + latchy = p.superscope.y; + } break; + + case DeviceJustifier: + case DeviceJustifiers: { + int x1 = snesinterface.input_poll(DeviceIDJustifier1, JustifierX); + int y1 = snesinterface.input_poll(DeviceIDJustifier1, JustifierY); + x1 += p.justifier.x1; + y1 += p.justifier.y1; + p.justifier.x1 = max(-16, min(256 + 16, x1)); + p.justifier.y1 = max(-16, min(240 + 16, y1)); + + int x2 = snesinterface.input_poll(DeviceIDJustifier2, JustifierX); + int y2 = snesinterface.input_poll(DeviceIDJustifier2, JustifierY); + x2 += p.justifier.x2; + y2 += p.justifier.y2; + p.justifier.x2 = max(-16, min(256 + 16, x2)); + p.justifier.y2 = max(-16, min(240 + 16, y2)); + + if(p.justifier.active == 0) { + latchx = p.justifier.x1; + latchy = p.justifier.y1; + } else { + latchx = (p.device == DeviceJustifiers ? p.justifier.x2 : -1); + latchy = (p.device == DeviceJustifiers ? p.justifier.y2 : -1); + } + } break; + } +} + +void SNES::Input::port_set_device(bool portnumber, unsigned device) { + port_t &p = port[portnumber]; + + p.device = device; + p.counter0 = 0; + p.counter1 = 0; + + //set iobit to true if device is capable of latching PPU counters + iobit = port[1].device == DeviceSuperScope + || port[1].device == DeviceJustifier + || port[1].device == DeviceJustifiers; + latchx = -1; + latchy = -1; + + if(device == DeviceSuperScope) { + p.superscope.x = 256 / 2; + p.superscope.y = 240 / 2; + + p.superscope.trigger = false; + p.superscope.cursor = false; + p.superscope.turbo = false; + p.superscope.pause = false; + p.superscope.offscreen = false; + + p.superscope.turbolock = false; + p.superscope.triggerlock = false; + p.superscope.pauselock = false; + } else if(device == DeviceJustifier) { + p.justifier.active = 0; + p.justifier.x1 = 256 / 2; + p.justifier.y1 = 240 / 2; + p.justifier.x2 = -1; + p.justifier.y2 = -1; + + p.justifier.trigger1 = false; + p.justifier.trigger2 = false; + p.justifier.start1 = false; + p.justifier.start2 = false; + } else if(device == DeviceJustifiers) { + p.justifier.active = 0; + p.justifier.x1 = 256 / 2 - 16; + p.justifier.y1 = 240 / 2; + p.justifier.x2 = 256 / 2 + 16; + p.justifier.y2 = 240 / 2; + + p.justifier.trigger1 = false; + p.justifier.trigger2 = false; + p.justifier.start1 = false; + p.justifier.start2 = false; + } } void SNES::Input::poll() { - snesinterface.input_poll(); - port_counter0[0] = 0; - port_counter1[0] = 0; - port_counter0[1] = 0; - port_counter1[1] = 0; + port[0].counter0 = 0; + port[0].counter1 = 0; + port[1].counter0 = 0; + port[1].counter1 = 0; + + port[1].justifier.active = !port[1].justifier.active; } void SNES::Input::init() { - port_set_device(0, config::snes.controller_port1); - port_set_device(1, config::snes.controller_port2); -} +} -#endif //ifdef SNES_CPP +#endif //ifdef SNES_CPP diff --git a/src/snes/input/input.h b/src/snes/input/input.h index 7c51c94c..b080dbc8 100644 --- a/src/snes/input/input.h +++ b/src/snes/input/input.h @@ -1,39 +1,114 @@ class Input { public: - enum Device { - DeviceNone, - DeviceJoypad, - DeviceMultitap, - }; - - enum { - DeviceIDNone, - DeviceIDJoypad1, - DeviceIDJoypad2, - DeviceIDMultitap1A, - DeviceIDMultitap1B, - DeviceIDMultitap1C, - DeviceIDMultitap1D, - DeviceIDMultitap2A, - DeviceIDMultitap2B, - DeviceIDMultitap2C, - DeviceIDMultitap2D, - }; - - enum { - JoypadB = 0, JoypadY = 1, - JoypadSelect = 2, JoypadStart = 3, - JoypadUp = 4, JoypadDown = 5, - JoypadLeft = 6, JoypadRight = 7, - JoypadA = 8, JoypadX = 9, - JoypadL = 10, JoypadR = 11, - }; - - uint8 port_read(bool port); - void port_set_device(bool port, unsigned device); - void init(); - void poll(); - -private: - unsigned port_device[2], port_counter0[2], port_counter1[2]; + enum Device { + DeviceNone, + DeviceJoypad, + DeviceMultitap, + DeviceMouse, + DeviceSuperScope, + DeviceJustifier, + DeviceJustifiers, + }; + + enum DeviceID { + DeviceIDNone, + DeviceIDJoypad1, + DeviceIDJoypad2, + DeviceIDMultitap1A, + DeviceIDMultitap1B, + DeviceIDMultitap1C, + DeviceIDMultitap1D, + DeviceIDMultitap2A, + DeviceIDMultitap2B, + DeviceIDMultitap2C, + DeviceIDMultitap2D, + DeviceIDMouse1, + DeviceIDMouse2, + DeviceIDSuperScope, + DeviceIDJustifier1, + DeviceIDJustifier2, + }; + + enum JoypadID { + JoypadB = 0, JoypadY = 1, + JoypadSelect = 2, JoypadStart = 3, + JoypadUp = 4, JoypadDown = 5, + JoypadLeft = 6, JoypadRight = 7, + JoypadA = 8, JoypadX = 9, + JoypadL = 10, JoypadR = 11, + }; + + enum MouseID { + MouseX = 0, MouseY = 1, + MouseLeft = 2, MouseRight = 3, + }; + + enum SuperScopeID { + SuperScopeX = 0, SuperScopeY = 1, + SuperScopeTrigger = 2, SuperScopeCursor = 3, + SuperScopeTurbo = 4, SuperScopePause = 5, + }; + + enum JustifierID { + JustifierX = 0, JustifierY = 1, + JustifierTrigger = 2, JustifierStart = 3, + }; + + uint8 port_read(bool port); + void port_set_device(bool port, unsigned device); + void init(); + void poll(); + void update(); + + //light guns (Super Scope, Justifier(s)) strobe IOBit whenever the CRT + //beam cannon is detected. this needs to be tested at the cycle level + //(hence inlining here for speed) to avoid 'dead space' during DRAM refresh. + //iobit is updated during port_set_device(), + //latchx, latchy are updated during update() (once per frame) + alwaysinline void tick() { + //only test if Super Scope or Justifier is connected + if(iobit) { + if(cpu.vcounter() == latchy //test Y cursor position + && cpu.hcounter() == latchx << 2 //test X cursor position (cycles == pixels << 2) + && latchy < (ppu.overscan() ? 240 : 225) //verify Y is not offscreen + && latchx < 256 //verify X is not offscreen + ) ppu.latch_counters(); + } + } + +private: + bool iobit; + uint16_t latchx, latchy; + + struct port_t { + unsigned device; + unsigned counter0; //read counters + unsigned counter1; + + struct superscope_t { + int x, y; + + bool trigger; + bool cursor; + bool turbo; + bool pause; + bool offscreen; + + bool turbolock; + bool triggerlock; + bool pauselock; + } superscope; + + struct justifier_t { + bool active; + + int x1, x2; + int y1, y2; + + bool trigger1, trigger2; + bool start1, start2; + } justifier; + } port[2]; + + friend class SNES; } input; diff --git a/src/snes/interface/interface.h b/src/snes/interface/interface.h index f1bd7ef6..8b846cbe 100644 --- a/src/snes/interface/interface.h +++ b/src/snes/interface/interface.h @@ -5,14 +5,15 @@ * (video, audio, input, ...) *****/ -class SNESInterface { public: +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(); - bool input_poll(unsigned deviceid, unsigned id); + int16_t input_poll(unsigned deviceid, unsigned id); void init(); void term(); diff --git a/src/snes/snes.cpp b/src/snes/snes.cpp index 9d2f82d5..5b22920a 100644 --- a/src/snes/snes.cpp +++ b/src/snes/snes.cpp @@ -1,143 +1,170 @@ #include "../base.h" #define SNES_CPP -SNES snes; -BSXBase bsxbase; -BSXCart bsxcart; -BSXFlash bsxflash; -SRTC srtc; -SDD1 sdd1; -SPC7110 spc7110; -Cx4 cx4; -DSP1 dsp1; -DSP2 dsp2; -DSP3 dsp3; -DSP4 dsp4; -OBC1 obc1; -ST010 st010; - -#include "scheduler/scheduler.cpp" -#include "tracer/tracer.cpp" - -#include "video/video.cpp" -#include "audio/audio.cpp" -#include "input/input.cpp" - -void SNES::run() {} - -void SNES::runtoframe() { - scheduler.enter(); -} - -void SNES::init() { - bsxbase.init(); - bsxcart.init(); - bsxflash.init(); - srtc.init(); - sdd1.init(); - spc7110.init(); - cx4.init(); - dsp1.init(); - dsp2.init(); - dsp3.init(); - dsp4.init(); - obc1.init(); - st010.init(); - - video.init(); - audio.init(); - input.init(); - snesinterface.init(); -} - -void SNES::term() { - audio.term(); - snesinterface.term(); -} - -void SNES::power() { - scheduler.init(); - - cpu.power(); - smp.power(); - dsp.power(); - ppu.power(); - bus.power(); - - if(cartridge.info.bsxbase) bsxbase.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(); - - for(uint16_t i = 0x2100; i <= 0x213f; i++) memory::mmio.map(i, ppu); - for(uint16_t i = 0x2140; i <= 0x217f; i++) memory::mmio.map(i, cpu); - for(uint16_t i = 0x2180; i <= 0x2183; i++) memory::mmio.map(i, cpu); - for(uint16_t i = 0x4016; i <= 0x4017; i++) memory::mmio.map(i, cpu); - for(uint16_t i = 0x4200; i <= 0x421f; i++) memory::mmio.map(i, cpu); - for(uint16_t i = 0x4300; i <= 0x437f; i++) memory::mmio.map(i, cpu); - - if(cartridge.info.bsxbase) bsxbase.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(); - - video.update(); -} - -void SNES::reset() { - scheduler.init(); - - cpu.reset(); - smp.reset(); - dsp.reset(); - ppu.reset(); - bus.reset(); - - if(cartridge.info.bsxbase) bsxbase.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(); - - video.update(); -} - -void SNES::scanline() { - video.scanline(); - - if(cpu.vcounter() == 241) { - video.update(); - scheduler.exit(); - } -} - -void SNES::frame() {} -void SNES::set_region(Region region) { snes_region = region; } -SNES::Region SNES::region() { return snes_region; } -SNES::SNES() {} +SNES snes; +BSXBase bsxbase; +BSXCart bsxcart; +BSXFlash bsxflash; +SRTC srtc; +SDD1 sdd1; +SPC7110 spc7110; +Cx4 cx4; +DSP1 dsp1; +DSP2 dsp2; +DSP3 dsp3; +DSP4 dsp4; +OBC1 obc1; +ST010 st010; + +#include "scheduler/scheduler.cpp" +#include "tracer/tracer.cpp" + +#include "video/video.cpp" +#include "audio/audio.cpp" +#include "input/input.cpp" + +void SNES::run() { +} + +void SNES::runtoframe() { + scheduler.enter(); +} + +void SNES::init() { + bsxbase.init(); + bsxcart.init(); + bsxflash.init(); + srtc.init(); + sdd1.init(); + spc7110.init(); + cx4.init(); + dsp1.init(); + dsp2.init(); + dsp3.init(); + dsp4.init(); + obc1.init(); + st010.init(); + + video.init(); + audio.init(); + input.init(); + snesinterface.init(); +} + +void SNES::term() { + audio.term(); + snesinterface.term(); +} + +void SNES::power() { + snes_region = max(0, min(2, config::snes.region)); + snes_expansion = max(0, min(1, config::snes.expansion_port)); + + if(snes_region == Autodetect) { + snes_region = cartridge.region() == Cartridge::NTSC ? NTSC : PAL; + } + + scheduler.init(); + + cpu.power(); + smp.power(); + dsp.power(); + ppu.power(); + bus.power(); + + if(expansion() == ExpansionBSX) { bsxbase.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(); + + for(unsigned i = 0x2100; i <= 0x213f; i++) memory::mmio.map(i, ppu); + for(unsigned i = 0x2140; i <= 0x217f; i++) memory::mmio.map(i, cpu); + for(unsigned i = 0x2180; i <= 0x2183; i++) memory::mmio.map(i, cpu); + for(unsigned i = 0x4016; i <= 0x4017; i++) memory::mmio.map(i, cpu); + 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(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(); + + input.port_set_device(0, config::snes.controller_port1); + input.port_set_device(1, config::snes.controller_port2); + input.update(); + video.update(); +} + +void SNES::reset() { + scheduler.init(); + + cpu.reset(); + smp.reset(); + dsp.reset(); + ppu.reset(); + bus.reset(); + + if(expansion() == ExpansionBSX) { bsxbase.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(); + + input.port_set_device(0, config::snes.controller_port1); + input.port_set_device(1, config::snes.controller_port2); + input.update(); + video.update(); +} + +void SNES::scanline() { + video.scanline(); + + if(cpu.vcounter() == 241) { + input.update(); + video.update(); + scheduler.exit(); + } +} + +void SNES::frame() { +} + +SNES::Region SNES::region() { + return (SNES::Region)snes_region; +} + +SNES::ExpansionPortDevice SNES::expansion() { + return (SNES::ExpansionPortDevice)snes_expansion; +} + +SNES::SNES() : snes_region(NTSC), snes_expansion(ExpansionNone) { +} diff --git a/src/snes/snes.h b/src/snes/snes.h index a52aaba6..5a73517f 100644 --- a/src/snes/snes.h +++ b/src/snes/snes.h @@ -7,6 +7,8 @@ class VideoFilter; class SNES { public: enum Region { NTSC = 0, PAL = 1 }; + enum RegionAutodetect { Autodetect = 2 }; + enum ExpansionPortDevice { ExpansionNone = 0, ExpansionBSX = 1 }; //system functions virtual void run(); @@ -20,9 +22,10 @@ public: virtual void frame(); virtual void scanline(); - //PAL/NTSC + //return *active* region / expansion port device information + //settings cached upon power-on Region region(); - void set_region(Region); + ExpansionPortDevice expansion(); #include "video/video.h" #include "audio/audio.h" @@ -32,7 +35,8 @@ public: virtual ~SNES() {} private: - Region snes_region; + unsigned snes_region; + unsigned snes_expansion; }; extern SNES snes; diff --git a/src/snes/video/video.cpp b/src/snes/video/video.cpp index 596fb648..ac2f6721 100644 --- a/src/snes/video/video.cpp +++ b/src/snes/video/video.cpp @@ -1,58 +1,103 @@ #ifdef SNES_CPP -void SNES::Video::set_mode(Mode mode_) { - mode = mode_; -} - -void SNES::Video::update() { +const uint8_t SNES::Video::cursor[15 * 15] = { + 0,0,0,0,0,0,1,1,1,0,0,0,0,0,0, + 0,0,0,0,1,1,2,2,2,1,1,0,0,0,0, + 0,0,0,1,2,2,1,2,1,2,2,1,0,0,0, + 0,0,1,2,1,1,0,1,0,1,1,2,1,0,0, + 0,1,2,1,0,0,0,1,0,0,0,1,2,1,0, + 0,1,2,1,0,0,1,2,1,0,0,1,2,1,0, + 1,2,1,0,0,1,1,2,1,1,0,0,1,2,1, + 1,2,2,1,1,2,2,2,2,2,1,1,2,2,1, + 1,2,1,0,0,1,1,2,1,1,0,0,1,2,1, + 0,1,2,1,0,0,1,2,1,0,0,1,2,1,0, + 0,1,2,1,0,0,0,1,0,0,0,1,2,1,0, + 0,0,1,2,1,1,0,1,0,1,1,2,1,0,0, + 0,0,0,1,2,2,1,2,1,2,2,1,0,0,0, + 0,0,0,0,1,1,2,2,2,1,1,0,0,0,0, + 0,0,0,0,0,0,1,1,1,0,0,0,0,0,0, +}; + +void SNES::Video::draw_cursor(uint16_t color, int x, int y) { + for(int cy = 0; cy < 15; cy++) { + int vy = y + cy - 7; + if(vy <= 0 || vy >= 240) continue; //do not draw offscreen + + bool hires = (pline_width[vy] == 512); + for(int cx = 0; cx < 15; cx++) { + int vx = x + cx - 7; + if(vx < 0 || vx >= 256) continue; //do not draw offscreen + uint8_t pixel = cursor[cy * 15 + cx]; + if(pixel == 0) continue; + uint16_t pixelcolor = (pixel == 1) ? 0 : color; + + if(hires == false) { + *((uint16_t*)ppu.output + vy * 1024 + 0 + vx) = pixelcolor; + *((uint16_t*)ppu.output + vy * 1024 + 512 + vx) = pixelcolor; + } else { + *((uint16_t*)ppu.output + vy * 1024 + 0 + vx * 2 + 0) = pixelcolor; + *((uint16_t*)ppu.output + vy * 1024 + 512 + vx * 2 + 0) = pixelcolor; + *((uint16_t*)ppu.output + vy * 1024 + 0 + vx * 2 + 1) = pixelcolor; + *((uint16_t*)ppu.output + vy * 1024 + 512 + vx * 2 + 1) = pixelcolor; + } + } + } +} + +void SNES::Video::update() { uint16_t *data = (uint16_t*)ppu.output; unsigned width, height; - switch(mode) { default: - case ModeNTSC: { - width = 256; - height = 224; - data += ((int)ppu.overscan() << 13) + 1024; - } break; - case ModePAL: { - width = 256; - height = 239; - data += 1024; - break; - } - } - - if(frame_hires) width <<= 1; + switch(snes.input.port[1].device) { + case SNES::Input::DeviceSuperScope: draw_cursor(0x001f, snes.input.port[1].superscope.x, snes.input.port[1].superscope.y); break; + case SNES::Input::DeviceJustifiers: draw_cursor(0x02e0, snes.input.port[1].justifier.x2, snes.input.port[1].justifier.y2); //fallthrough + case SNES::Input::DeviceJustifier: draw_cursor(0x001f, snes.input.port[1].justifier.x1, snes.input.port[1].justifier.y1); break; + } + + unsigned yoffset = 1; //scanline 0 is always black, skip this line for video output + if(mode == ModeNTSC && ppu.overscan()) yoffset += 8; //NTSC overscan centers x240 height image + + switch(mode) { default: + case ModeNTSC: { width = 256; height = 224; } break; + case ModePAL: { width = 256; height = 239; } break; + } + + if(frame_hires) width <<= 1; if(frame_interlace) height <<= 1; snesinterface.video_refresh( - data, - /* pitch = */ height <= 240 ? 2048 : 1024, - /* *line = */ height <= 240 ? (pline_width + 1) : (iline_width + 2), + data + yoffset * 1024, + /* pitch = */ height <= 240 ? 2048 : 1024, + /* line[] = */ height <= 240 ? (pline_width + yoffset) : (iline_width + yoffset * 2), width, height ); - - frame_hires = false; - frame_interlace = false; -} - -void SNES::Video::scanline() { - int y = cpu.vcounter(); - int o = (mode == ModeNTSC) ? ((int)ppu.overscan() << 3) : 0; - if(y <= (0 + o) || y >= (225 + o)) return; - y -= o; - - pline_width[y] = iline_width[y * 2 + (int)ppu.field()] = (ppu.hires() == false) ? 256 : 512; - frame_hires |= ppu.hires(); - frame_interlace |= ppu.interlace(); -} - -void SNES::Video::init() { - for(unsigned i = 0; i < 240; i++) pline_width[i] = 256; - for(unsigned i = 0; i < 480; i++) iline_width[i] = 256; - frame_hires = false; - frame_interlace = false; - set_mode(ModeNTSC); -} + + frame_hires = false; + frame_interlace = false; +} + +void SNES::Video::scanline() { + unsigned y = cpu.vcounter(); + if(y >= 240) return; + + unsigned width = (ppu.hires() == false ? 256 : 512); + pline_width[y] = width; + iline_width[y * 2 + (int)ppu.field()] = width; + + frame_hires |= ppu.hires(); + frame_interlace |= ppu.interlace(); +} + +void SNES::Video::set_mode(Mode mode_) { + mode = mode_; +} + +void SNES::Video::init() { + for(unsigned i = 0; i < 240; i++) pline_width[i] = 256; + for(unsigned i = 0; i < 480; i++) iline_width[i] = 256; + frame_hires = false; + frame_interlace = false; + set_mode(ModeNTSC); +} #endif //ifdef SNES_CPP diff --git a/src/snes/video/video.h b/src/snes/video/video.h index 5766bcf0..310b1d97 100644 --- a/src/snes/video/video.h +++ b/src/snes/video/video.h @@ -1,22 +1,25 @@ class Video { public: - enum Mode { - ModeNTSC, - ModePAL, + enum Mode { + ModeNTSC, + ModePAL, }; - void set_mode(Mode); + void set_mode(Mode); -private: - Mode mode; +private: + Mode mode; bool frame_hires; - bool frame_interlace; - - unsigned pline_width[240]; //progressive - unsigned iline_width[480]; //interlace - - void update(); - void scanline(); + bool frame_interlace; + + unsigned pline_width[240]; //progressive + unsigned iline_width[480]; //interlace + + void update(); + void scanline(); void init(); - friend class SNES; + static const uint8_t cursor[15 * 15]; + void draw_cursor(uint16_t color, int x, int y); + + friend class SNES; } video; diff --git a/src/ui/base/main.cpp b/src/ui/base/main.cpp index 6a6c35f4..8bdae5db 100644 --- a/src/ui/base/main.cpp +++ b/src/ui/base/main.cpp @@ -14,52 +14,35 @@ uintptr_t MainWindow::close(event_t) { uintptr_t MainWindow::event(event_t e) { if(e.type == event_t::Tick) { - if(e.widget == &menu_file_load) { - event::load_rom(); + if(e.widget == &menu_system_load) { + event::load_cart(); } - if(e.widget == &menu_file_load_bsx) { - window_bsxloader.mode = BSXLoaderWindow::ModeBSX; - window_bsxloader.set_text(translate["Load BS-X Cartridge"]); - window_bsxloader.tbase.set_text(config::path.bsx); - window_bsxloader.focus(); - } + if(e.widget == &menu_system_power_on) { event::modify_system_state(event::PowerOn); } + if(e.widget == &menu_system_power_off) { event::modify_system_state(event::PowerOff); } + if(e.widget == &menu_system_reset) { event::modify_system_state(event::Reset); } - if(e.widget == &menu_file_load_bsc) { - window_bsxloader.mode = BSXLoaderWindow::ModeBSC; - window_bsxloader.set_text(translate["Load BS-X Slotted Cartridge"]); - window_bsxloader.tbase.set_text(""); - window_bsxloader.focus(); - } + if(e.widget == &menu_system_controller_port1_none) { event::update_controller_port1(SNES::Input::DeviceNone); } + if(e.widget == &menu_system_controller_port1_joypad) { event::update_controller_port1(SNES::Input::DeviceJoypad); } + if(e.widget == &menu_system_controller_port1_multitap) { event::update_controller_port1(SNES::Input::DeviceMultitap); } + if(e.widget == &menu_system_controller_port1_mouse) { event::update_controller_port1(SNES::Input::DeviceMouse); } - if(e.widget == &menu_file_load_st) { - window_stloader.tbase.set_text(config::path.st); - window_stloader.focus(); - } + if(e.widget == &menu_system_controller_port2_none) { event::update_controller_port2(SNES::Input::DeviceNone); } + if(e.widget == &menu_system_controller_port2_joypad) { event::update_controller_port2(SNES::Input::DeviceJoypad); } + if(e.widget == &menu_system_controller_port2_multitap) { event::update_controller_port2(SNES::Input::DeviceMultitap); } + if(e.widget == &menu_system_controller_port2_mouse) { event::update_controller_port2(SNES::Input::DeviceMouse); } + if(e.widget == &menu_system_controller_port2_superscope) { event::update_controller_port2(SNES::Input::DeviceSuperScope); } + if(e.widget == &menu_system_controller_port2_justifier) { event::update_controller_port2(SNES::Input::DeviceJustifier); } + if(e.widget == &menu_system_controller_port2_justifiers) { event::update_controller_port2(SNES::Input::DeviceJustifiers); } - if(e.widget == &menu_file_unload) { - event::unload_rom(); - } + if(e.widget == &menu_system_expansion_port_none) { config::snes.expansion_port = SNES::ExpansionNone; } + if(e.widget == &menu_system_expansion_port_bsx) { config::snes.expansion_port = SNES::ExpansionBSX; } - if(e.widget == &menu_file_reset) { - event::reset(); - } + if(e.widget == &menu_system_region_auto) { config::snes.region = SNES::Autodetect; } + if(e.widget == &menu_system_region_ntsc) { config::snes.region = SNES::NTSC; } + if(e.widget == &menu_system_region_pal) { config::snes.region = SNES::PAL; } - if(e.widget == &menu_file_power) { - event::power(); - } - - if(e.widget == &menu_system_controller_port1_none) { event::update_controller_port1(0); } - if(e.widget == &menu_system_controller_port1_joypad) { event::update_controller_port1(1); } - if(e.widget == &menu_system_controller_port1_multitap) { event::update_controller_port1(2); } - - if(e.widget == &menu_system_controller_port2_none) { event::update_controller_port2(0); } - if(e.widget == &menu_system_controller_port2_joypad) { event::update_controller_port2(1); } - if(e.widget == &menu_system_controller_port2_multitap) { event::update_controller_port2(2); } - - if(e.widget == &menu_file_exit) { - event(event_t(event_t::Close)); - } + if(e.widget == &menu_system_exit) { event::quit(); } if(e.widget == &menu_settings_videomode_1x) { event::update_multiplier(1); } if(e.widget == &menu_settings_videomode_2x) { event::update_multiplier(2); } @@ -71,6 +54,8 @@ uintptr_t MainWindow::event(event_t e) { event::update_aspect_correction(menu_settings_videomode_aspect_correction.checked()); } + if(e.widget == &menu_settings_videomode_fullscreen) { event::toggle_fullscreen(); } + if(e.widget == &menu_settings_videomode_ntsc) { event::update_region(0); } if(e.widget == &menu_settings_videomode_pal) { event::update_region(1); } @@ -105,13 +90,8 @@ uintptr_t MainWindow::event(event_t e) { if(e.widget == &menu_settings_emuspeed_fastest) { event::update_emulation_speed(4); } if(e.widget == &menu_settings_emuspeed_videosync) { - if(config::video.mode == 0) { - config::video.windowed.synchronize = menu_settings_emuspeed_videosync.checked(); - video.set(Video::Synchronize, config::video.windowed.synchronize); - } else { - config::video.fullscreen.synchronize = menu_settings_emuspeed_videosync.checked(); - video.set(Video::Synchronize, config::video.fullscreen.synchronize); - } + config::video.synchronize = menu_settings_emuspeed_videosync.checked(); + video.set(Video::Synchronize, config::video.synchronize); } if(e.widget == &menu_settings_emuspeed_audiosync) { @@ -133,6 +113,17 @@ uintptr_t MainWindow::event(event_t e) { return true; } +uintptr_t MainWindow::input(event_t e) { + uint16_t code = e.param; + int16_t state = e.param >> 16; + + if(state == 0 && code >= mouse::button && code < mouse::button + mouse::buttons) { + event::acquire(); + } + + return true; +} + uintptr_t MainWindow::block(event_t) { audio.clear(); return true; @@ -144,41 +135,74 @@ void MainWindow::setup() { set_icon(48, 48, (uint32_t*)resource::icon48); MenuRadioItemGroup group; - attach(menu_file.create(translate["System"])); - menu_file.attach(menu_file_load.create(string() << translate["Load Cartridge"] << " ...")); - menu_file.attach(menu_file_load_special.create(translate["Load Special"])); - menu_file_load_special.attach(menu_file_load_bsx.create(string() << translate["Load BS-X Cartridge"] << " ...")); - menu_file_load_special.attach(menu_file_load_bsc.create(string() << translate["Load BS-X Slotted Cartridge"] << " ...")); - menu_file_load_special.attach(menu_file_load_st.create(string() << translate["Load Sufami Turbo Cartridge"] << " ...")); - menu_file.attach(menu_file_unload.create(translate["Unload Cartridge"])); + attach(menu_system.create(translate["{{menu}}System"])); + menu_system.attach(menu_system_load.create(string() << translate["{{system}}Load Cartridge"] << " ...")); - menu_file.attach(menu_file_sep1.create()); - menu_file.attach(menu_file_reset.create(translate["Reset"])); - menu_file_power.create(translate["Power Cycle"]); - if(config::advanced.enable) menu_file.attach(menu_file_power); + menu_system.attach(menu_system_sep1.create()); + menu_system.attach(menu_system_power.create(translate["{{system}}Power"])); + group.add(&menu_system_power_on); + group.add(&menu_system_power_off); + menu_system_power.attach(menu_system_power_on.create(group, translate["{{power}}On"])); + menu_system_power.attach(menu_system_power_off.create(group, translate["{{power}}Off"])); + group.reset(); + menu_system.attach(menu_system_reset.create(translate["{{system}}Reset"])); - menu_file.attach(menu_file_sep2.create()); - menu_file.attach(menu_system_controller_port1.create(translate["Controller Port 1"])); + menu_system.attach(menu_system_sep2.create()); + menu_system.attach(menu_system_controller_port1.create(translate["{{system}}Controller Port 1"])); group.add(&menu_system_controller_port1_none); group.add(&menu_system_controller_port1_joypad); group.add(&menu_system_controller_port1_multitap); - menu_system_controller_port1.attach(menu_system_controller_port1_none.create (group, translate["None"])); - menu_system_controller_port1.attach(menu_system_controller_port1_joypad.create (group, translate["Joypad"])); - menu_system_controller_port1.attach(menu_system_controller_port1_multitap.create(group, translate["Multitap"])); + group.add(&menu_system_controller_port1_mouse); + menu_system_controller_port1.attach(menu_system_controller_port1_none.create (group, translate["{{controllerport}}None"])); + menu_system_controller_port1.attach(menu_system_controller_port1_joypad.create (group, translate["{{controllerport}}Joypad"])); + menu_system_controller_port1.attach(menu_system_controller_port1_multitap.create(group, translate["{{controllerport}}Multitap"])); + menu_system_controller_port1.attach(menu_system_controller_port1_mouse.create (group, translate["{{controllerport}}Mouse"])); group.reset(); - menu_file.attach(menu_system_controller_port2.create(translate["Controller Port 2"])); + menu_system.attach(menu_system_controller_port2.create(translate["{{system}}Controller Port 2"])); group.add(&menu_system_controller_port2_none); group.add(&menu_system_controller_port2_joypad); group.add(&menu_system_controller_port2_multitap); - menu_system_controller_port2.attach(menu_system_controller_port2_none.create (group, translate["None"])); - menu_system_controller_port2.attach(menu_system_controller_port2_joypad.create (group, translate["Joypad"])); - menu_system_controller_port2.attach(menu_system_controller_port2_multitap.create(group, translate["Multitap"])); + group.add(&menu_system_controller_port2_mouse); + group.add(&menu_system_controller_port2_superscope); + group.add(&menu_system_controller_port2_justifier); + group.add(&menu_system_controller_port2_justifiers); + menu_system_controller_port2.attach(menu_system_controller_port2_none.create (group, translate["{{controllerport}}None"])); + menu_system_controller_port2.attach(menu_system_controller_port2_joypad.create (group, translate["{{controllerport}}Joypad"])); + menu_system_controller_port2.attach(menu_system_controller_port2_multitap.create (group, translate["{{controllerport}}Multitap"])); + menu_system_controller_port2.attach(menu_system_controller_port2_mouse.create (group, translate["{{controllerport}}Mouse"])); + menu_system_controller_port2.attach(menu_system_controller_port2_superscope.create(group, translate["{{controllerport}}Super Scope"])); + menu_system_controller_port2.attach(menu_system_controller_port2_justifier.create (group, translate["{{controllerport}}Justifier"])); + menu_system_controller_port2.attach(menu_system_controller_port2_justifiers.create(group, translate["{{controllerport}}Two Justifiers"])); group.reset(); - menu_file.attach(menu_file_sep3.create()); - menu_file.attach(menu_file_exit.create(translate["Exit"])); + menu_system_sep3.create(); + menu_system_expansion_port.create(translate["{{system}}Expansion Port"]); + group.add(&menu_system_expansion_port_none); + group.add(&menu_system_expansion_port_bsx); + menu_system_expansion_port.attach(menu_system_expansion_port_none.create(group, translate["{{expansionport}}None"])); + menu_system_expansion_port.attach(menu_system_expansion_port_bsx.create (group, translate["{{expansionport}}Satellaview"])); + group.reset(); + menu_system_region.create(translate["{{system}}Region"]); + group.add(&menu_system_region_auto); + group.add(&menu_system_region_ntsc); + group.add(&menu_system_region_pal); + menu_system_region.attach(menu_system_region_auto.create(group, translate["{{region}}Auto-detect"])); + menu_system_region.attach(menu_system_region_ntsc.create(group, translate["{{region}}NTSC"])); + menu_system_region.attach(menu_system_region_pal.create (group, translate["{{region}}PAL"])); + group.reset(); - attach(menu_settings.create(translate["Settings"])); + if(config::advanced.enable == true) { + menu_system.attach(menu_system_sep3); + menu_system.attach(menu_system_expansion_port); + menu_system.attach(menu_system_region); + } + + menu_system_sep4.create(); + menu_system_exit.create(translate["{{system}}Exit"]); + //menu_system.attach(menu_system_sep4); + //menu_system.attach(menu_system_exit); + + attach(menu_settings.create(translate["{{menu}}Settings"])); menu_settings.attach(menu_settings_videomode.create(translate["Video Mode"])); group.add(&menu_settings_videomode_1x); group.add(&menu_settings_videomode_2x); @@ -193,6 +217,7 @@ void MainWindow::setup() { group.reset(); menu_settings_videomode.attach(menu_settings_videomode_sep1.create()); menu_settings_videomode.attach(menu_settings_videomode_aspect_correction.create(translate["Correct Aspect Ratio"])); + menu_settings_videomode.attach(menu_settings_videomode_fullscreen.create(translate["Fullscreen"])); menu_settings_videomode.attach(menu_settings_videomode_sep2.create()); group.add(&menu_settings_videomode_ntsc); group.add(&menu_settings_videomode_pal); @@ -289,38 +314,47 @@ void MainWindow::setup() { } menu_settings.attach(menu_settings_config.create(string() << translate["Configuration"] << " ...")); - attach(menu_misc.create(translate["Misc"])); - menu_misc.attach(menu_misc_logaudio.create(translate["Log Audio Data"])); + attach(menu_misc.create(translate["{{menu}}Misc"])); + menu_misc.attach(menu_misc_logaudio.create(translate["{{misc}}Log Audio Data"])); menu_misc.attach(menu_misc_sep1.create()); - menu_misc.attach(menu_misc_about.create(string() << translate["About"] << " ...")); + menu_misc.attach(menu_misc_about.create(string() << translate["{{misc}}About"] << " ...")); - menu_file_unload.disable(); - menu_file_reset.disable(); - menu_file_power.disable(); + menu_system_power.disable(); + menu_system_reset.disable(); view.create(0, 256, 224); + view.on_input = bind(&MainWindow::input, this); attach(view, 0, 0); on_close = bind(&MainWindow::close, this); on_block = bind(&MainWindow::block, this); - menu_file_exit.on_tick = bind(&MainWindow::close, this); - - menu_file_load.on_tick = - menu_file_load_bsx.on_tick = - menu_file_load_bsc.on_tick = - menu_file_load_st.on_tick = - menu_file_unload.on_tick = - menu_file_reset.on_tick = - menu_file_power.on_tick = + menu_system_load.on_tick = + menu_system_power_on.on_tick = + menu_system_power_off.on_tick = + menu_system_reset.on_tick = menu_system_controller_port1_none.on_tick = menu_system_controller_port1_joypad.on_tick = menu_system_controller_port1_multitap.on_tick = + menu_system_controller_port1_mouse.on_tick = menu_system_controller_port2_none.on_tick = menu_system_controller_port2_joypad.on_tick = menu_system_controller_port2_multitap.on_tick = + menu_system_controller_port2_mouse.on_tick = + menu_system_controller_port2_superscope.on_tick = + menu_system_controller_port2_justifier.on_tick = + menu_system_controller_port2_justifiers.on_tick = + + menu_system_expansion_port_none.on_tick = + menu_system_expansion_port_bsx.on_tick = + + menu_system_region_auto.on_tick = + menu_system_region_ntsc.on_tick = + menu_system_region_pal.on_tick = + + menu_system_exit.on_tick = menu_settings_videomode_1x.on_tick = menu_settings_videomode_2x.on_tick = @@ -328,6 +362,7 @@ void MainWindow::setup() { menu_settings_videomode_4x.on_tick = menu_settings_videomode_5x.on_tick = menu_settings_videomode_aspect_correction.on_tick = + menu_settings_videomode_fullscreen.on_tick = menu_settings_videomode_ntsc.on_tick = menu_settings_videomode_pal.on_tick = @@ -372,15 +407,31 @@ void MainWindow::sync() { event::load_video_settings(); switch(config::snes.controller_port1) { default: - case SNES::Input::DeviceNone: menu_system_controller_port1_none.check(); break; - case SNES::Input::DeviceJoypad: menu_system_controller_port1_joypad.check(); break; - case SNES::Input::DeviceMultitap: menu_system_controller_port1_multitap.check(); break; + case SNES::Input::DeviceNone: menu_system_controller_port1_none.check(); break; + case SNES::Input::DeviceJoypad: menu_system_controller_port1_joypad.check(); break; + case SNES::Input::DeviceMultitap: menu_system_controller_port1_multitap.check(); break; + case SNES::Input::DeviceMouse: menu_system_controller_port1_mouse.check(); break; } - switch(config::snes.controller_port2) { - case SNES::Input::DeviceNone: menu_system_controller_port2_none.check(); break; - case SNES::Input::DeviceJoypad: menu_system_controller_port2_joypad.check(); break; - case SNES::Input::DeviceMultitap: menu_system_controller_port2_multitap.check(); break; + switch(config::snes.controller_port2) { default: + case SNES::Input::DeviceNone: menu_system_controller_port2_none.check(); break; + case SNES::Input::DeviceJoypad: menu_system_controller_port2_joypad.check(); break; + case SNES::Input::DeviceMultitap: menu_system_controller_port2_multitap.check(); break; + case SNES::Input::DeviceMouse: menu_system_controller_port2_mouse.check(); break; + case SNES::Input::DeviceSuperScope: menu_system_controller_port2_superscope.check(); break; + case SNES::Input::DeviceJustifier: menu_system_controller_port2_justifier.check(); break; + case SNES::Input::DeviceJustifiers: menu_system_controller_port2_justifiers.check(); break; + } + + switch(config::snes.expansion_port) { default: + case SNES::ExpansionNone: menu_system_expansion_port_none.check(); break; + case SNES::ExpansionBSX: menu_system_expansion_port_bsx.check(); break; + } + + switch(config::snes.region) { default: + case SNES::Autodetect: menu_system_region_auto.check(); break; + case SNES::NTSC: menu_system_region_ntsc.check(); break; + case SNES::PAL: menu_system_region_pal.check(); break; } switch(event::video_settings.multiplier) { default: @@ -392,6 +443,7 @@ void MainWindow::sync() { } menu_settings_videomode_aspect_correction.check(event::video_settings.aspect_correction); + menu_settings_videomode_fullscreen.check(event::video_settings.mode == 1); switch(event::video_settings.region) { default: case 0: menu_settings_videomode_ntsc.check(); break; @@ -434,10 +486,6 @@ void MainWindow::sync() { case 4: menu_settings_emuspeed_fastest.check(); break; } - if(config::video.mode == 0) { - menu_settings_emuspeed_videosync.check(config::video.windowed.synchronize); - } else { - menu_settings_emuspeed_videosync.check(config::video.fullscreen.synchronize); - } + menu_settings_emuspeed_videosync.check(config::video.synchronize); menu_settings_emuspeed_audiosync.check(config::audio.synchronize); } diff --git a/src/ui/base/main.h b/src/ui/base/main.h index 3a6b10a4..12f27f90 100644 --- a/src/ui/base/main.h +++ b/src/ui/base/main.h @@ -1,26 +1,36 @@ class MainWindow : public Window { public: - MenuGroup menu_file; - MenuItem menu_file_load; - MenuGroup menu_file_load_special; - MenuItem menu_file_load_bsx; - MenuItem menu_file_load_bsc; - MenuItem menu_file_load_st; - MenuItem menu_file_unload; - MenuSeparator menu_file_sep1; - MenuItem menu_file_reset; - MenuItem menu_file_power; - MenuSeparator menu_file_sep2; + MenuGroup menu_system; + MenuItem menu_system_load; + MenuSeparator menu_system_sep1; + MenuGroup menu_system_power; + MenuRadioItem menu_system_power_on; + MenuRadioItem menu_system_power_off; + MenuItem menu_system_reset; + MenuSeparator menu_system_sep2; MenuGroup menu_system_controller_port1; MenuRadioItem menu_system_controller_port1_none; MenuRadioItem menu_system_controller_port1_joypad; MenuRadioItem menu_system_controller_port1_multitap; + MenuRadioItem menu_system_controller_port1_mouse; MenuGroup menu_system_controller_port2; MenuRadioItem menu_system_controller_port2_none; MenuRadioItem menu_system_controller_port2_joypad; MenuRadioItem menu_system_controller_port2_multitap; - MenuSeparator menu_file_sep3; - MenuItem menu_file_exit; + MenuRadioItem menu_system_controller_port2_mouse; + MenuRadioItem menu_system_controller_port2_superscope; + MenuRadioItem menu_system_controller_port2_justifier; + MenuRadioItem menu_system_controller_port2_justifiers; + MenuSeparator menu_system_sep3; + MenuGroup menu_system_expansion_port; + MenuRadioItem menu_system_expansion_port_none; + MenuRadioItem menu_system_expansion_port_bsx; + MenuGroup menu_system_region; + MenuRadioItem menu_system_region_auto; + MenuRadioItem menu_system_region_ntsc; + MenuRadioItem menu_system_region_pal; + MenuSeparator menu_system_sep4; + MenuItem menu_system_exit; MenuGroup menu_settings; MenuGroup menu_settings_videomode; @@ -31,6 +41,7 @@ public: MenuRadioItem menu_settings_videomode_5x; MenuSeparator menu_settings_videomode_sep1; MenuCheckItem menu_settings_videomode_aspect_correction; + MenuCheckItem menu_settings_videomode_fullscreen; MenuSeparator menu_settings_videomode_sep2; MenuRadioItem menu_settings_videomode_ntsc; MenuRadioItem menu_settings_videomode_pal; @@ -81,5 +92,6 @@ public: uintptr_t close(event_t); uintptr_t event(event_t); + uintptr_t input(event_t); uintptr_t block(event_t); } window_main; diff --git a/src/ui/config.cpp b/src/ui/config.cpp index 22e9cdb7..58e9d9f0 100644 --- a/src/ui/config.cpp +++ b/src/ui/config.cpp @@ -47,22 +47,24 @@ integral_setting System::gamma(config(), "system.colorfilter.gamma", struct Video { static integral_setting mode; + static integral_setting synchronize; + struct Windowed { - static integral_setting synchronize, aspect_correction; + static integral_setting aspect_correction; static integral_setting region, multiplier, hardware_filter, software_filter; } windowed; struct Fullscreen { - static integral_setting synchronize, aspect_correction; + static integral_setting aspect_correction; static integral_setting region, multiplier, hardware_filter, software_filter; } fullscreen; static integral_setting aspect_ntsc_x, aspect_ntsc_y, aspect_pal_x, aspect_pal_y; static integral_setting frameskip; } video; -//0 = windowed, 1 = fullscreen, 2 = exclusive +//0 = windowed, 1 = fullscreen, 2 = exclusive (not implemented yet) integral_setting Video::mode("video.mode", "Active video mode", integral_setting::decimal, 0); +integral_setting Video::synchronize(config(), "video.synchronize", "Synchronize to video refresh rate", integral_setting::boolean, false); -integral_setting Video::Windowed::synchronize(config(), "video.windowed.synchronize", "Synchronize to video refresh rate", integral_setting::boolean, false); integral_setting Video::Windowed::aspect_correction(config(), "video.windowed.aspect_correction", "Correct video aspect ratio\n" "Defaults assume display pixels are perfectly square\n" @@ -88,7 +90,6 @@ integral_setting Video::Windowed::software_filter(config(), "video.windowed.soft "4 = NTSC\n", integral_setting::decimal, 0); -integral_setting Video::Fullscreen::synchronize (config(), "video.fullscreen.synchronize", "", integral_setting::boolean, false); integral_setting Video::Fullscreen::aspect_correction(config(), "video.fullscreen.aspect_correction", "", integral_setting::boolean, true); integral_setting Video::Fullscreen::region (config(), "video.fullscreen.region", "", integral_setting::decimal, 0); integral_setting Video::Fullscreen::multiplier (config(), "video.fullscreen.multiplier", "", integral_setting::decimal, 2); @@ -132,6 +133,13 @@ struct Input { struct Multitap2C { static string_setting up, down, left, right, a, b, x, y, l, r, select, start; } multitap2c; struct Multitap2D { static string_setting up, down, left, right, a, b, x, y, l, r, select, start; } multitap2d; + struct Mouse1 { static string_setting x, y, l, r; } mouse1; + struct Mouse2 { static string_setting x, y, l, r; } mouse2; + + struct SuperScope { static string_setting x, y, trigger, turbo, cursor, pause; } superscope; + struct Justifier1 { static string_setting x, y, trigger, start; } justifier1; + struct Justifier2 { static string_setting x, y, trigger, start; } justifier2; + struct GUI { static string_setting load; static string_setting pause; @@ -222,6 +230,33 @@ DeclMultitap(Multitap2D, "2d") #undef DeclMultitap +string_setting Input::Mouse1::x(config(), "input.mouse1.x", "", "mouse.x"); +string_setting Input::Mouse1::y(config(), "input.mouse1.y", "", "mouse.y"); +string_setting Input::Mouse1::l(config(), "input.mouse1.l", "", "mouse.button00"); +string_setting Input::Mouse1::r(config(), "input.mouse1.r", "", "mouse.button02"); + +string_setting Input::Mouse2::x(config(), "input.mouse2.x", "", "mouse.x"); +string_setting Input::Mouse2::y(config(), "input.mouse2.y", "", "mouse.y"); +string_setting Input::Mouse2::l(config(), "input.mouse2.l", "", "mouse.button00"); +string_setting Input::Mouse2::r(config(), "input.mouse2.r", "", "mouse.button02"); + +string_setting Input::SuperScope::x (config(), "input.superscope.x", "", "mouse.x"); +string_setting Input::SuperScope::y (config(), "input.superscope.y", "", "mouse.y"); +string_setting Input::SuperScope::trigger(config(), "input.superscope.trigger", "", "mouse.button00"); +string_setting Input::SuperScope::cursor (config(), "input.superscope.cursor", "", "mouse.button02"); +string_setting Input::SuperScope::turbo (config(), "input.superscope.turbo", "", "t"); +string_setting Input::SuperScope::pause (config(), "input.superscope.pause", "", "p"); + +string_setting Input::Justifier1::x (config(), "input.justifier1.x", "", "mouse.x"); +string_setting Input::Justifier1::y (config(), "input.justifier1.y", "", "mouse.y"); +string_setting Input::Justifier1::trigger(config(), "input.justifier1.trigger", "", "mouse.button00"); +string_setting Input::Justifier1::start (config(), "input.justifier1.start", "", "mouse.button02"); + +string_setting Input::Justifier2::x (config(), "input.justifier2.x", "", "none"); +string_setting Input::Justifier2::y (config(), "input.justifier2.y", "", "none"); +string_setting Input::Justifier2::trigger(config(), "input.justifier2.trigger", "", "none"); +string_setting Input::Justifier2::start (config(), "input.justifier2.start", "", "none"); + string_setting Input::GUI::load (config(), "input.gui.load", "", "none"); string_setting Input::GUI::pause (config(), "input.gui.pause", "", "f12"); string_setting Input::GUI::reset (config(), "input.gui.reset", "", "none"); @@ -236,9 +271,11 @@ string_setting Input::GUI::toggle_menubar (config(), "input.gui.toggle_menuba string_setting Input::GUI::toggle_statusbar (config(), "input.gui.toggle_statusbar", "", "escape"); struct Misc { + static integral_setting cheat_autosort; static integral_setting opacity; } misc; +integral_setting Misc::cheat_autosort(config(), "misc.cheat_autosort", "Keep cheat code list sorted by description", integral_setting::boolean, false); integral_setting Misc::opacity(config(), "misc.opacity", "Opacity of user interface windows", integral_setting::decimal, 100); struct Advanced { diff --git a/src/ui/event.cpp b/src/ui/event.cpp index 5ab186b1..f98ec571 100644 --- a/src/ui/event.cpp +++ b/src/ui/event.cpp @@ -1,60 +1,63 @@ namespace event { -void keydown(uint16_t key) { +void input_event(uint16_t code) { + if(code == keyboard::escape && input.acquired()) { + unacquire(); + return; + } + if(window_main.focused()) { - if(key == input_manager.gui.load) load_rom(); - if(key == input_manager.gui.pause) { + if(code == inputuigeneral.load.code) load_cart(); + if(code == inputuigeneral.pause.code) { app.pause = !app.pause; //toggle pause state if(app.pause) { audio.clear(); if(cartridge.loaded()) status.update(); } } - if(key == input_manager.gui.reset) reset(); - if(key == input_manager.gui.power) power(); - if(key == input_manager.gui.quit) quit(); - if(key == input_manager.gui.speed_decrease) { + if(code == inputuigeneral.reset.code) modify_system_state(Reset); + if(code == inputuigeneral.power.code) modify_system_state(PowerCycle); + if(code == inputuigeneral.quit.code) quit(); + if(code == inputuigeneral.speed_decrease.code) { update_emulation_speed(config::system.emulation_speed - 1); } - if(key == input_manager.gui.speed_increase) { + if(code == inputuigeneral.speed_increase.code) { update_emulation_speed(config::system.emulation_speed + 1); } - if(key == input_manager.gui.frameskip_decrease) { + if(code == inputuigeneral.frameskip_decrease.code) { update_frameskip(config::video.frameskip - 1); } - if(key == input_manager.gui.frameskip_increase) { + if(code == inputuigeneral.frameskip_increase.code) { update_frameskip(config::video.frameskip + 1); } - if(key == input_manager.gui.toggle_fullscreen) toggle_fullscreen(); - if(key == input_manager.gui.toggle_menubar) toggle_menubar(); - if(key == input_manager.gui.toggle_statusbar) toggle_statusbar(); - } - - if(window_input_capture.focused()) { - if(window_input_capture.waiting == true) { - if(window_input_capture.locked == false) { - window_input_capture.assign(key); - } - } + if(code == inputuigeneral.toggle_fullscreen.code) toggle_fullscreen(); + if(code == inputuigeneral.toggle_menubar.code) toggle_menubar(); + if(code == inputuigeneral.toggle_statusbar.code) toggle_statusbar(); } } -void keyup(uint16_t key) { - if(window_input_capture.focused()) { - if(window_input_capture.waiting == true) { - if(window_input_capture.locked == true) { - window_input_capture.locked = input.key_down(keyboard::return_) || input.key_down(keyboard::spacebar); - } - } +void acquire() { + if(cartridge.loaded() == true) { + if(config::snes.controller_port1 == SNES::Input::DeviceMouse + || config::snes.controller_port2 == SNES::Input::DeviceMouse + || config::snes.controller_port2 == SNES::Input::DeviceSuperScope + || config::snes.controller_port2 == SNES::Input::DeviceJustifier + || config::snes.controller_port2 == SNES::Input::DeviceJustifiers + ) input.acquire(); } } +void unacquire() { + input.unacquire(); +} + void load_video_settings() { video_settings.mode = config::video.mode; + video_settings.synchronize = config::video.synchronize; + switch(video_settings.mode) { default: case 0: { //windowed video_settings.aspect_correction = config::video.windowed.aspect_correction; - video_settings.synchronize = config::video.windowed.synchronize; video_settings.region = config::video.windowed.region; video_settings.multiplier = config::video.windowed.multiplier; video_settings.hardware_filter = config::video.windowed.hardware_filter; @@ -63,7 +66,6 @@ void load_video_settings() { case 1: { //fullscreen video_settings.aspect_correction = config::video.fullscreen.aspect_correction; - video_settings.synchronize = config::video.fullscreen.synchronize; video_settings.region = config::video.fullscreen.region; video_settings.multiplier = config::video.fullscreen.multiplier; video_settings.hardware_filter = config::video.fullscreen.hardware_filter; @@ -131,18 +133,110 @@ void update_emulation_speed(int speed) { window_main.sync(); } -void update_controller_port1(int device) { - unsigned current_device = config::snes.controller_port1; - unsigned new_device; +void modify_system_state(system_state_t state) { + video.clear(); + audio.clear(); - switch(device) { default: - case 0: new_device = SNES::Input::DeviceNone; break; - case 1: new_device = SNES::Input::DeviceJoypad; break; - case 2: new_device = SNES::Input::DeviceMultitap; break; + switch(state) { + case LoadCart: { + if(cartridge.loaded() == false) break; + + app.power = true; + app.pause = false; + snes.power(); + + status.flush(); + string t = translate["Loaded $."]; + replace(t, "$", cartridge.info.filename); + status.enqueue(t); + if(cartridge.info.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); } + + message = translate["Warning: partially supported $ chip detected."]; + if(cartridge.info.dsp3) { replace(message, "$", "DSP-3"); status.enqueue(message); } + } break; + + case UnloadCart: { + if(cartridge.loaded() == true) break; + cartridge.unload(); + + app.power = false; + app.pause = true; + + status.flush(); + string t = translate["Unloaded $."]; + replace(t, "$", cartridge.info.filename); + status.enqueue(t); + } break; + + case PowerOn: { + if(cartridge.loaded() == false || app.power == true) break; + + app.power = true; + app.pause = false; + snes.power(); + + status.flush(); + status.enqueue(translate["Power on."]); + } break; + + case PowerOff: { + if(cartridge.loaded() == false || app.power == false) break; + + app.power = false; + app.pause = true; + + status.flush(); + status.enqueue(translate["Power off."]); + } break; + + case PowerCycle: { + if(cartridge.loaded() == false) break; + + app.power = true; + app.pause = false; + snes.power(); + + status.flush(); + status.enqueue(translate["System power was cycled."]); + } break; + + case Reset: { + if(cartridge.loaded() == false || app.power == false) break; + + app.pause = false; + snes.reset(); + + status.flush(); + status.enqueue(translate["System was reset."]); + } break; } - if(new_device != current_device) { - snes.input.port_set_device(0, config::snes.controller_port1 = new_device); + window_main.menu_system_power.enable(cartridge.loaded()); + window_main.menu_system_reset.enable(cartridge.loaded() && app.power); + window_main.menu_system_expansion_port_none.enable(cartridge.loaded() && !app.power); + window_main.menu_system_expansion_port_bsx.enable(cartridge.loaded() && !app.power); + window_main.menu_system_region_auto.enable(cartridge.loaded() && !app.power); + window_main.menu_system_region_ntsc.enable(cartridge.loaded() && !app.power); + window_main.menu_system_region_pal.enable(cartridge.loaded() && !app.power); + app.power + ? window_main.menu_system_power_on.check() + : window_main.menu_system_power_off.check(); + window_cheat_editor.refresh(); +} + +void update_controller_port1(int device) { + unsigned current_device = config::snes.controller_port1; + + if(device != current_device) { + snes.input.port_set_device(0, config::snes.controller_port1 = device); } window_main.sync(); @@ -150,16 +244,9 @@ void update_controller_port1(int device) { void update_controller_port2(int device) { unsigned current_device = config::snes.controller_port2; - unsigned new_device; - switch(device) { default: - case 0: new_device = SNES::Input::DeviceNone; break; - case 1: new_device = SNES::Input::DeviceJoypad; break; - case 2: new_device = SNES::Input::DeviceMultitap; break; - } - - if(new_device != current_device) { - snes.input.port_set_device(1, config::snes.controller_port2 = new_device); + if(device != current_device) { + snes.input.port_set_device(1, config::snes.controller_port2 = device); } window_main.sync(); @@ -168,47 +255,52 @@ void update_controller_port2(int device) { void update_video_settings() { load_video_settings(); - uint width = 256; - uint height = video_settings.region == 0 ? 224 : 239; - uint multiplier = minmax<1, 5>(video_settings.multiplier); + SNES::Video::Mode mode = (video_settings.region == 0 ? SNES::Video::ModeNTSC : SNES::Video::ModePAL); + unsigned width = 256; + unsigned height = (mode == SNES::Video::ModeNTSC ? 224 : 239); + unsigned multiplier = max(1, min(5, video_settings.multiplier)); + width *= multiplier; height *= multiplier; if(video_settings.aspect_correction == true) { - if(video_settings.region == 0) { - //NTSC - width = uint( double(width) * double(config::video.aspect_ntsc_x) / double(config::video.aspect_ntsc_y) ); + double scalar; + if(mode == SNES::Video::ModeNTSC) { + scalar = (double)config::video.aspect_ntsc_x / (double)config::video.aspect_ntsc_y; } else { - //PAL - width = uint( double(width) * double(config::video.aspect_pal_x) / double(config::video.aspect_pal_y) ); + scalar = (double)config::video.aspect_pal_x / (double)config::video.aspect_pal_y; } + width = (unsigned)((double)width * (double)scalar); } - switch(video_settings.mode) { default: - case 0: { //windowed - window_main.unfullscreen(); - //try and clamp windows larger than the screen to the screen size. - //note that since APIs such as X11 lack the ability to get the full window size - //(with border, etc), this can never be a perfect fit to the screen. - int w = min(width, hiro().screen_width()); - int h = min(height, hiro().screen_height()); - window_main.resize(w, h); - window_main.move(window_main.view, 0, 0); - window_main.view.resize(w, h); - } break; - - case 1: { //fullscreen - window_main.fullscreen(); - int view_width = window_main.get_width(); - int view_height = window_main.get_height(); - window_main.move(window_main.view, - //center view inside window, do not exceed window size - width < view_width ? (view_width - width) / 2 : 0, - height < view_height ? (view_height - height) / 2 : 0 - ); - window_main.view.resize(min(width, view_width), min(height, view_height)); - } break; + if(video_settings.mode == 0) { + window_main.unfullscreen(); + window_main.resize(width, height); + } else { + window_main.fullscreen(); } + //get maximum possible size for visible area + unsigned viewwidth = window_main.get_width(); + unsigned viewheight = window_main.get_height(); + + //if requested size exceeds visible area, + //constrain proportions and preserve aspect ratio + if(height > viewheight) { + double scalar = (double)viewheight / (double)height; + width = (unsigned)((double)width * (double)scalar); + height = viewheight; + } + + if(width > viewwidth) { + double scalar = (double)viewwidth / (double)width; + width = viewwidth; + height = (unsigned)((double)height * (double)scalar); + } + + //center video output within visible area + window_main.move(window_main.view, (viewwidth - width) / 2, (viewheight - height) / 2); + window_main.view.resize(width, height); + libfilter::FilterInterface::FilterType filter; switch(video_settings.software_filter) { default: case 0: filter = libfilter::FilterInterface::Direct; break; @@ -219,20 +311,10 @@ void update_video_settings() { } libfilter::filter.set(filter); - - SNES::Video::Mode mode; - switch(video_settings.region) { default: - case 0: mode = SNES::Video::ModeNTSC; break; - case 1: mode = SNES::Video::ModePAL; break; - } - snes.video.set_mode(mode); - video.set(Video::Synchronize, video_settings.synchronize); video.set(Video::Filter, video_settings.hardware_filter); - - //update main window video mode checkbox settings - window_main.sync(); + window_main.sync(); //update main window video mode checkbox settings } void update_opacity() { @@ -269,7 +351,7 @@ void toggle_statusbar() { // -bool load_rom(char *fn) { +bool load_cart(char *fn) { audio.clear(); lstring dir; @@ -302,133 +384,107 @@ bool load_rom(char *fn) { ); } -//File -> Load ROM action -void load_rom() { +//used by File -> Load ROM and "Load Cartridge" hotkey +void load_cart() { char fn[PATH_MAX]; - if(load_rom(fn) == false) return; - load_cart_normal(fn); + if(load_cart(fn) == false) return; + load_image(fn); } -void load_cart_normal(const char *filename) { - if(!filename || !*filename) return; +void load_image(const char *filename) { + Cartridge::cartinfo_t cartinfo; + if(!cartridge.inspect_image(cartinfo, filename)) return; - unload_rom(); - cartridge.load_cart_normal(filename); - if(cartridge.loaded() == false) return; + switch(cartinfo.type) { + case Cartridge::TypeNormal: { + load_cart_normal(filename); + } break; - app.pause = false; - snes.power(); - window_main.menu_file_unload.enable(); - window_main.menu_file_reset.enable(); - window_main.menu_file_power.enable(); - window_cheat_editor.refresh(); + case Cartridge::TypeBSC: { + window_bsxloader.mode = BSXLoaderWindow::ModeBSC; + window_bsxloader.set_text(translate["Load BS-X Slotted Cartridge"]); + window_bsxloader.tbase.set_text(filename); + window_bsxloader.tslot.set_text(""); + window_bsxloader.load.focus(); + window_bsxloader.focus(); + } break; - status.flush(); - string t = translate["Loaded $."]; - replace(t, "$", cartridge.info.filename); - status.enqueue(t); - if(cartridge.info.patched) status.enqueue(translate["UPS patch applied."]); + case Cartridge::TypeBSXBIOS: { + window_bsxloader.mode = BSXLoaderWindow::ModeBSX; + window_bsxloader.set_text(translate["Load BS-X Cartridge"]); + window_bsxloader.tbase.set_text(filename); + window_bsxloader.tslot.set_text(""); + window_bsxloader.load.focus(); + window_bsxloader.focus(); + } break; - //warn if unsupported hardware detected - string 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); } + case Cartridge::TypeBSX: { + window_bsxloader.mode = BSXLoaderWindow::ModeBSX; + window_bsxloader.set_text(translate["Load BS-X Cartridge"]); + window_bsxloader.tbase.set_text(config::path.bsx); + window_bsxloader.tslot.set_text(filename); + window_bsxloader.load.focus(); + window_bsxloader.focus(); + } break; + + case Cartridge::TypeSufamiTurboBIOS: { + window_stloader.tbase.set_text(filename); + window_stloader.tslotA.set_text(""); + window_stloader.tslotB.set_text(""); + window_stloader.load.focus(); + window_stloader.focus(); + } break; + + case Cartridge::TypeSufamiTurbo: { + window_stloader.tbase.set_text(config::path.st); + window_stloader.tslotA.set_text(filename); + window_stloader.tslotB.set_text(""); + window_stloader.load.focus(); + window_stloader.focus(); + } break; + } } -void load_cart_bsx(const char *base, const char *slot) { +void load_cart_normal(const char *base) { if(!base || !*base) return; - unload_rom(); - cartridge.load_cart_bsx(base, slot); + unload_cart(); + cartridge.load_cart_normal(base); if(cartridge.loaded() == false) return; - - app.pause = false; - snes.power(); - window_main.menu_file_unload.enable(); - window_main.menu_file_reset.enable(); - window_main.menu_file_power.enable(); - window_cheat_editor.refresh(); - - status.flush(); - string t = translate["Loaded $."]; - replace(t, "$", cartridge.info.filename); - status.enqueue(t); + modify_system_state(LoadCart); } void load_cart_bsc(const char *base, const char *slot) { if(!base || !*base) return; - unload_rom(); + unload_cart(); cartridge.load_cart_bsc(base, slot); if(cartridge.loaded() == false) return; + modify_system_state(LoadCart); +} - app.pause = false; - snes.power(); - window_main.menu_file_unload.enable(); - window_main.menu_file_reset.enable(); - window_main.menu_file_power.enable(); - window_cheat_editor.refresh(); +void load_cart_bsx(const char *base, const char *slot) { + if(!base || !*base) return; - status.flush(); - string t = translate["Loaded $."]; - replace(t, "$", cartridge.info.filename); - status.enqueue(t); + unload_cart(); + cartridge.load_cart_bsx(base, slot); + if(cartridge.loaded() == false) return; + modify_system_state(LoadCart); } void load_cart_st(const char *base, const char *slotA, const char *slotB) { if(!base || !*base) return; - unload_rom(); + unload_cart(); cartridge.load_cart_st(base, slotA, slotB); if(cartridge.loaded() == false) return; - - app.pause = false; - snes.power(); - window_main.menu_file_unload.enable(); - window_main.menu_file_reset.enable(); - window_main.menu_file_power.enable(); - window_cheat_editor.refresh(); - - status.flush(); - string t = translate["Loaded $."]; - replace(t, "$", cartridge.info.filename); - status.enqueue(t); + modify_system_state(LoadCart); } -void unload_rom() { +void unload_cart() { if(cartridge.loaded() == false) return; - cartridge.unload(); - video.clear(); - audio.clear(); - - window_main.menu_file_unload.disable(); - window_main.menu_file_reset.disable(); - window_main.menu_file_power.disable(); - window_cheat_editor.refresh(); - - status.flush(); - string t = translate["Unloaded $."]; - replace(t, "$", cartridge.info.filename); - status.enqueue(t); -} - -void reset() { - if(cartridge.loaded() == true) { - snes.reset(); - status.flush(); - status.enqueue(translate["System was reset."]); - } -} - -void power() { - if(cartridge.loaded() == true) { - snes.power(); - status.flush(); - status.enqueue(translate["System power was cycled."]); - } + modify_system_state(UnloadCart); } void quit() { diff --git a/src/ui/event.h b/src/ui/event.h index c37e3b57..d02301f9 100644 --- a/src/ui/event.h +++ b/src/ui/event.h @@ -1,7 +1,9 @@ namespace event { -void keydown(uint16_t); -void keyup(uint16_t); +void input_event(uint16_t); + +void acquire(); +void unacquire(); struct VideoSettings { uint mode; @@ -23,24 +25,27 @@ void update_software_filter(uint); void update_frameskip(int); void update_emulation_speed(int); +enum system_state_t { LoadCart, UnloadCart, PowerOn, PowerOff, PowerCycle, Reset }; +void modify_system_state(system_state_t); + void update_controller_port1(int); void update_controller_port2(int); void update_video_settings(); void update_opacity(); + void toggle_fullscreen(); void toggle_menubar(); void toggle_statusbar(); -bool load_rom(char*); -void load_rom(); +bool load_cart(char*); +void load_cart(); +void load_image(const char*); void load_cart_normal(const char*); -void load_cart_bsx(const char*, 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_rom(); -void reset(); -void power(); +void unload_cart(); void quit(); diff --git a/src/ui/inputdevices.cpp b/src/ui/inputdevices.cpp new file mode 100644 index 00000000..3156e7e3 --- /dev/null +++ b/src/ui/inputdevices.cpp @@ -0,0 +1,379 @@ +struct InputCode { + enum Type { + KeyboardButton, + MouseAxis, + MouseButton, + JoypadAxis, + JoypadButton, + Unknown, + }; + + static Type 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; + } + + static bool is_button(uint16_t code) { + Type id = type(code); + return (id == KeyboardButton || id == MouseButton || id == JoypadButton); + } + + static bool is_axis(uint16_t code) { + Type id = type(code); + return (id == MouseAxis || id == JoypadAxis); + } +}; + +struct InputObject { + enum Type { Button, Axis } type; + const char *name; + string_setting &setting; + uint16_t code; + int16_t state; + + void bind() { code = input_find(setting); } + InputObject(Type t, const char *n, string_setting &s) : type(t), name(n), setting(s) {} +}; + +struct InputGroup { + const char *name; + vector list; + + void attach(InputObject &object) { + list[list.size()] = &object; + } + + void bind() { + for(unsigned i = 0; i < list.size(); i++) list[i]->bind(); + } + + void clear() { + for(unsigned i = 0; i < list.size(); i++) { + list[i]->state = 0; + } + } + + void poll(int16_t *table) { + for(unsigned i = 0; i < list.size(); i++) { + if(InputCode::type(list[i]->code) == InputCode::MouseAxis && !input.acquired()) { + //mouse must be acquired (locked to window) to move axes + list[i]->state = 0; + } else if(InputCode::type(list[i]->code) == 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]; + } + } + } + + virtual int16_t state(unsigned index) { + if(index < list.size()) return list[index]->state; + return 0; + } + + InputGroup(const char *n) : name(n) {} +}; + +struct InputDevice : InputGroup { + SNES::Input::DeviceID id; + enum Port { Port1, Port2 }; + const bool port; + + InputDevice(SNES::Input::DeviceID i, bool p, const char *n) : InputGroup(n), id(i), port(p) {} +}; + +struct Joypad : InputDevice { + InputObject up, down, left, right; + InputObject a, b, x, y; + InputObject l, r, select, start; + + int16_t state(unsigned index) { + 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( + SNES::Input::DeviceID id, bool port, const char *name, + string_setting &up_t, string_setting &down_t, string_setting &left_t, string_setting &right_t, + string_setting &a_t, string_setting &b_t, string_setting &x_t, string_setting &y_t, + string_setting &l_t, string_setting &r_t, string_setting &select_t, string_setting &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); + } +}; + +struct Mouse : InputDevice { + InputObject x, y; + InputObject left, right; + + int16_t state(unsigned index) { + 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( + SNES::Input::DeviceID id, bool port, const char *name, + string_setting &x_t, string_setting &y_t, + string_setting &left_t, string_setting &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); + } +}; + +struct SuperScope : InputDevice { + InputObject x, y; + InputObject trigger, cursor, turbo, pause; + + int16_t state(unsigned index) { + 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( + SNES::Input::DeviceID id, bool port, const char *name, + string_setting &x_t, string_setting &y_t, + string_setting &trigger_t, string_setting &cursor_t, string_setting &turbo_t, string_setting &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); + } +}; + +struct Justifier : InputDevice { + InputObject x, y; + InputObject trigger, start; + + int16_t state(unsigned index) { + 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( + SNES::Input::DeviceID id, bool port, const char *name, + string_setting &x_t, string_setting &y_t, + string_setting &trigger_t, string_setting &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); + } +}; + +struct InputDevicePool { + vector list; + + void attach(InputDevice &device) { + list[list.size()] = &device; + } + + void bind() { + for(unsigned i = 0; i < list.size(); i++) list[i]->bind(); + } + + void clear() { + for(unsigned i = 0; i < list.size(); i++) list[i]->clear(); + } + + void poll(int16_t *table) { + for(unsigned i = 0; i < list.size(); i++) list[i]->poll(table); + } + + InputDevice *find(SNES::Input::DeviceID id) { + for(unsigned i = 0; i < list.size(); i++) { + if(list[i]->id == id) return list[i]; + } + return 0; + } + + InputDevicePool(); +} inputpool; + +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.l, config::input.mouse1.r +); + +Mouse mouse2( +SNES::Input::DeviceIDMouse2, InputDevice::Port2, "Mouse", +config::input.mouse2.x, config::input.mouse2.y, config::input.mouse2.l, config::input.mouse2.r +); + +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 +); + +InputDevicePool::InputDevicePool() { + 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/inputmanager.cpp b/src/ui/inputmanager.cpp index 33808582..dea69433 100644 --- a/src/ui/inputmanager.cpp +++ b/src/ui/inputmanager.cpp @@ -1,169 +1,106 @@ -class InputManager { -public: - // 0 = Joypad 1 - // 1 = Joypad 2 - //2-5 = Multitap 1 - //6-9 = Multitap 2 - struct Joypad { - struct Button { - uint16_t value; - bool state; - } up, down, left, right, a, b, x, y, l, r, select, start; - } joypad[10]; +#include "inputdevices.cpp" +#include "inputui.cpp" - struct GUI { - uint16_t load; - uint16_t pause; - uint16_t reset; - uint16_t power; - uint16_t quit; - uint16_t speed_decrease; - uint16_t speed_increase; - uint16_t frameskip_decrease; - uint16_t frameskip_increase; - uint16_t toggle_fullscreen; - uint16_t toggle_menubar; - uint16_t toggle_statusbar; - } gui; - - void bind(); - void poll(); - bool get_status(unsigned deviceid, unsigned id); +class InputManager { +public: + void bind(); + void poll(); + void clear(); + void flush(); + + int16_t state(uint16_t code); + int16_t get_status(unsigned deviceid, unsigned id); void refresh(); - function on_keydown; - function on_keyup; + function on_input; InputManager(); ~InputManager(); private: - uint8_t *state; + bool active_state; + int16_t state_table[2][input_limit]; } input_manager; //refresh input state for PC keyboard and joypads -//callbacks can be bound to on_keydown and on_keyup -//callbacks will be invoked whenever input status changes +//callback can be bound to on_input //this should be called roughly every ~20-50ms -//however, this need not be called if no callbacks are attached +//however, this need not be called if no callback is attached void InputManager::refresh() { - input.poll(); - for(uint i = 0; i < input_limit; i++) { - bool prev_state = state[i]; - state[i] = input.key_down(i); - if(!prev_state && state[i] && on_keydown) on_keydown(i); - if( prev_state && !state[i] && on_keyup ) on_keyup(i); - } -} - -void InputManager::bind() { - #define map(i, n) \ - joypad[i].up.value = input_find(config::input.n.up); \ - joypad[i].down.value = input_find(config::input.n.down); \ - joypad[i].left.value = input_find(config::input.n.left); \ - joypad[i].right.value = input_find(config::input.n.right); \ - joypad[i].a.value = input_find(config::input.n.a); \ - joypad[i].b.value = input_find(config::input.n.b); \ - joypad[i].x.value = input_find(config::input.n.x); \ - joypad[i].y.value = input_find(config::input.n.y); \ - joypad[i].l.value = input_find(config::input.n.l); \ - joypad[i].r.value = input_find(config::input.n.r); \ - joypad[i].select.value = input_find(config::input.n.select); \ - joypad[i].start.value = input_find(config::input.n.start); - - map(0, joypad1) - map(1, joypad2) - map(2, multitap1a) - map(3, multitap1b) - map(4, multitap1c) - map(5, multitap1d) - map(6, multitap2a) - map(7, multitap2b) - map(8, multitap2c) - map(9, multitap2d) - - #undef map - - for(unsigned i = 0; i < 10; i++) { - joypad[i].up.state = joypad[i].down.state = joypad[i].left.state = joypad[i].right.state = - joypad[i].a.state = joypad[i].b.state = joypad[i].x.state = joypad[i].y.state = - joypad[i].l.state = joypad[i].r.state = joypad[i].select.state = joypad[i].start.state = - false; - } + bool last = active_state; + active_state = !active_state; + bool next = active_state; - gui.load = input_find(config::input.gui.load); - gui.pause = input_find(config::input.gui.pause); - gui.reset = input_find(config::input.gui.reset); - gui.power = input_find(config::input.gui.power); - gui.quit = input_find(config::input.gui.quit); - gui.speed_decrease = input_find(config::input.gui.speed_decrease); - gui.speed_increase = input_find(config::input.gui.speed_increase); - gui.frameskip_decrease = input_find(config::input.gui.frameskip_decrease); - gui.frameskip_increase = input_find(config::input.gui.frameskip_increase); - gui.toggle_fullscreen = input_find(config::input.gui.toggle_fullscreen); - gui.toggle_menubar = input_find(config::input.gui.toggle_menubar); - gui.toggle_statusbar = input_find(config::input.gui.toggle_statusbar); -} - -void InputManager::poll() { - for(unsigned i = 0; i < 10; i++) { - joypad[i].up.state = input.key_down(joypad[i].up.value); - joypad[i].down.state = input.key_down(joypad[i].down.value); - joypad[i].left.state = input.key_down(joypad[i].left.value); - joypad[i].right.state = input.key_down(joypad[i].right.value); - joypad[i].a.state = input.key_down(joypad[i].a.value); - joypad[i].b.state = input.key_down(joypad[i].b.value); - joypad[i].x.state = input.key_down(joypad[i].x.value); - joypad[i].y.state = input.key_down(joypad[i].y.value); - joypad[i].l.state = input.key_down(joypad[i].l.value); - joypad[i].r.state = input.key_down(joypad[i].r.value); - joypad[i].select.state = input.key_down(joypad[i].select.value); - joypad[i].start.state = input.key_down(joypad[i].start.value); - } -} - -bool InputManager::get_status(unsigned deviceid, unsigned id) { - //======= - //Joypads - //======= - int index = -1; - switch(deviceid) { - case SNES::Input::DeviceIDJoypad1: index = 0; break; - case SNES::Input::DeviceIDJoypad2: index = 1; break; - case SNES::Input::DeviceIDMultitap1A: index = 2; break; - case SNES::Input::DeviceIDMultitap1B: index = 3; break; - case SNES::Input::DeviceIDMultitap1C: index = 4; break; - case SNES::Input::DeviceIDMultitap1D: index = 5; break; - case SNES::Input::DeviceIDMultitap2A: index = 6; break; - case SNES::Input::DeviceIDMultitap2B: index = 7; break; - case SNES::Input::DeviceIDMultitap2C: index = 8; break; - case SNES::Input::DeviceIDMultitap2D: index = 9; break; - } - - if(index >= 0) { - switch(id) { - case SNES::Input::JoypadUp: return joypad[index].up.state; - case SNES::Input::JoypadDown: return joypad[index].down.state; - case SNES::Input::JoypadLeft: return joypad[index].left.state; - case SNES::Input::JoypadRight: return joypad[index].right.state; - case SNES::Input::JoypadA: return joypad[index].a.state; - case SNES::Input::JoypadB: return joypad[index].b.state; - case SNES::Input::JoypadX: return joypad[index].x.state; - case SNES::Input::JoypadY: return joypad[index].y.state; - case SNES::Input::JoypadL: return joypad[index].l.state; - case SNES::Input::JoypadR: return joypad[index].r.state; - case SNES::Input::JoypadSelect: return joypad[index].select.state; - case SNES::Input::JoypadStart: return joypad[index].start.state; - } - } - - return false; -} + input.poll(state_table[next]); + for(unsigned i = 0; i < input_limit; i++) { + //call on_input() whenever button is pressed down; ignores axes + if(!state_table[last][i] + && state_table[next][i] + && InputCode::is_button(i) + && on_input + ) on_input(i); + + //detect any change of state for UI input capture window + if(state_table[last][i] != state_table[next][i] + && window_input_capture.focused() + && window_input_capture.waiting + && !window_input_capture.locked + ) { + //buttons only map upon pressing down; axes map on movement in any direction + if(InputCode::is_button(i) == false || state_table[next][i]) { + window_input_capture.assign(i); + } + } + } + + //input capture locks to avoid immediate assignment of enter / spacebar / etc; + //in other words, inputs that can be used to trigger the input capture window. + //this will release the lock when none of said buttons are active. + if(window_input_capture.focused() + && window_input_capture.waiting + && window_input_capture.locked + ) { + window_input_capture.locked + = input_manager.state(keyboard::return_) + || input_manager.state(keyboard::spacebar) + || input_manager.state(mouse::button + 0); + } +} + +void InputManager::bind() { + inputpool.bind(); + inputuigeneral.bind(); +} + +void InputManager::poll() { + inputpool.poll(state_table[active_state]); +} + +void InputManager::clear() { + inputpool.clear(); +} + +void InputManager::flush() { + for(unsigned i = 0; i < input_limit; i++) { + state_table[0][i] = 0; + state_table[1][i] = 0; + } +} + +int16_t InputManager::state(uint16_t code) { + return state_table[active_state][code]; +} + +int16_t InputManager::get_status(unsigned deviceid, unsigned id) { + InputDevice *device = inputpool.find((SNES::Input::DeviceID)deviceid); + if(device) return device->state(id); + return 0; +} InputManager::InputManager() { - state = new(zeromemory) uint8_t[input_limit]; + active_state = 0; + flush(); } InputManager::~InputManager() { - delete[] state; } diff --git a/src/ui/inputui.cpp b/src/ui/inputui.cpp new file mode 100644 index 00000000..a4ebad52 --- /dev/null +++ b/src/ui/inputui.cpp @@ -0,0 +1,35 @@ +struct InputUIGeneral : InputGroup { + InputObject load; + InputObject pause; + InputObject reset; + InputObject power; + InputObject quit; + InputObject speed_decrease; + InputObject speed_increase; + InputObject frameskip_decrease; + InputObject frameskip_increase; + InputObject toggle_fullscreen; + InputObject toggle_menubar; + InputObject toggle_statusbar; + + InputUIGeneral() + : InputGroup("General"), + load (InputObject::Button, "Load cartridge", config::input.gui.load), + pause (InputObject::Button, "Pause emulation", config::input.gui.pause), + reset (InputObject::Button, "Reset system", config::input.gui.reset), + power (InputObject::Button, "Power cycle system", config::input.gui.power), + quit (InputObject::Button, "Exit emulator", config::input.gui.quit), + speed_decrease (InputObject::Button, "Decrease emulation speed", config::input.gui.speed_decrease), + speed_increase (InputObject::Button, "Increase emulation speed", config::input.gui.speed_increase), + frameskip_decrease(InputObject::Button, "Decrease frameskip rate", config::input.gui.frameskip_decrease), + frameskip_increase(InputObject::Button, "Increase frameskip rate", config::input.gui.frameskip_increase), + toggle_fullscreen (InputObject::Button, "Toggle fullscreen mode", config::input.gui.toggle_fullscreen), + toggle_menubar (InputObject::Button, "Toggle menubar", config::input.gui.toggle_menubar), + toggle_statusbar (InputObject::Button, "Toggle statusbar", config::input.gui.toggle_statusbar) + { + attach(load); attach(pause); attach(reset); attach(power); attach(quit); + attach(speed_decrease); attach(speed_increase); + attach(frameskip_decrease); attach(frameskip_increase); + attach(toggle_fullscreen); attach(toggle_menubar); attach(toggle_statusbar); + } +} inputuigeneral; diff --git a/src/ui/interface.cpp b/src/ui/interface.cpp index de82c16f..3329f654 100644 --- a/src/ui/interface.cpp +++ b/src/ui/interface.cpp @@ -52,14 +52,13 @@ void SNESInterface::audio_sample(uint16 l_sample, uint16 r_sample) { void SNESInterface::input_poll() { if(input_ready && input_ready() == false) { - input.clear(); + input_manager.clear(); } else { - input.poll(); + input_manager.poll(); } - input_manager.poll(); } -bool SNESInterface::input_poll(unsigned deviceid, unsigned id) { +int16_t SNESInterface::input_poll(unsigned deviceid, unsigned id) { return input_manager.get_status(deviceid, id); } diff --git a/src/ui/loader/bsxloader.cpp b/src/ui/loader/bsxloader.cpp index 2a4a17ea..f04fadc5 100644 --- a/src/ui/loader/bsxloader.cpp +++ b/src/ui/loader/bsxloader.cpp @@ -5,7 +5,7 @@ uintptr_t BSXLoaderWindow::close(event_t) { uintptr_t BSXLoaderWindow::bbase_tick(event_t) { char fn[PATH_MAX]; - if(event::load_rom(fn) == true) tbase.set_text(fn); + if(event::load_cart(fn) == true) tbase.set_text(fn); return true; } @@ -16,7 +16,7 @@ uintptr_t BSXLoaderWindow::cbase_tick(event_t) { uintptr_t BSXLoaderWindow::bslot_tick(event_t) { char fn[PATH_MAX]; - if(event::load_rom(fn) == true) tslot.set_text(fn); + if(event::load_cart(fn) == true) tslot.set_text(fn); return true; } diff --git a/src/ui/loader/stloader.cpp b/src/ui/loader/stloader.cpp index 8a352373..a31349ed 100644 --- a/src/ui/loader/stloader.cpp +++ b/src/ui/loader/stloader.cpp @@ -5,7 +5,7 @@ uintptr_t STLoaderWindow::close(event_t) { uintptr_t STLoaderWindow::bbase_tick(event_t) { char fn[PATH_MAX]; - if(event::load_rom(fn) == true) tbase.set_text(fn); + if(event::load_cart(fn) == true) tbase.set_text(fn); return true; } @@ -16,7 +16,7 @@ uintptr_t STLoaderWindow::cbase_tick(event_t) { uintptr_t STLoaderWindow::bslotA_tick(event_t) { char fn[PATH_MAX]; - if(event::load_rom(fn) == true) tslotA.set_text(fn); + if(event::load_cart(fn) == true) tslotA.set_text(fn); return true; } @@ -27,7 +27,7 @@ uintptr_t STLoaderWindow::cslotA_tick(event_t) { uintptr_t STLoaderWindow::bslotB_tick(event_t) { char fn[PATH_MAX]; - if(event::load_rom(fn) == true) tslotB.set_text(fn); + if(event::load_cart(fn) == true) tslotB.set_text(fn); return true; } diff --git a/src/ui/main.cpp b/src/ui/main.cpp index e59f2036..32cff12f 100644 --- a/src/ui/main.cpp +++ b/src/ui/main.cpp @@ -12,19 +12,16 @@ void term_snes(); * hardware abstraction layer *****/ -#include +#include using namespace ruby; -#include - -#include "inputmanager.cpp" -#include "interface.cpp" +#include /***** * platform abstraction layer *****/ -#include +#include using namespace libhiro; /***** @@ -35,6 +32,9 @@ using namespace libhiro; #include "status.h" #include "event.h" +#include "inputmanager.cpp" +#include "interface.cpp" + #include "ui.cpp" #include "status.cpp" #include "event.cpp" @@ -128,30 +128,13 @@ void run() { } if(cartridge.loaded() == false || app.pause == true || app.autopause == true) { - //prevent bsnes from consuming 100% CPU resources when idle - usleep(20 * 1000); + usleep(20 * 1000); //prevent bsnes from consuming 100% CPU resources when idle } else { snes.runtoframe(); } } -#if defined(PLATFORM_WIN) -int __stdcall WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { - //On Windows, argv[] is in 7-bit ANSI format, UTF-8 chars are converted to '?'s. - //Need argv[] to be in UTF-8 format to properly determine realpath() and default image filepaths. - //To do this, parse command line in UTF-16, and then convert to UTF-8. - int argc; - wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &argc); - char **argv = new char*[argc]; - for(unsigned i = 0; i < argc; i++) { - argv[i] = new(zeromemory) char[PATH_MAX]; - strcpy(argv[i], utf8(wargv[i])); - } -#else -int main(int argc, char *argv[]) { -#endif /* - -int main(int argc, char *argv[]) { */ +int hiromain(int argc, const char *const argv[]) { get_paths(argv[0]); set_config_filenames(); @@ -163,29 +146,16 @@ int main(int argc, char *argv[]) { */ } translate.import(config::locale_cfg); - hiro().init(); ui_init(); - if(app.term == true) goto app_term; - snes.init(); - - if(argc >= 2 && file::exists(argv[1])) { - cartridge.load_cart_normal(argv[1]); - if(cartridge.loaded()) { - snes.power(); - window_main.menu_file_unload.enable(); - window_main.menu_file_reset.enable(); - window_main.menu_file_power.enable(); - } + if(app.term == false) { + snes.init(); + if(argc >= 2) event::load_image(argv[1]); + while(app.term == false) run(); + event::unload_cart(); } - while(app.term == false) run(); - - event::unload_rom(); - - app_term: config::config().save(config::bsnes_cfg); snes.term(); ui_term(); - hiro().term(); return 0; } diff --git a/src/ui/main.h b/src/ui/main.h index a2ae7714..4400acd6 100644 --- a/src/ui/main.h +++ b/src/ui/main.h @@ -1,7 +1,8 @@ struct Application { bool term; + bool power; bool pause; bool autopause; - Application() : term(false), pause(false), autopause(false) {} + Application() : term(false), power(false), pause(false), autopause(false) {} } app; diff --git a/src/ui/settings/advanced.cpp b/src/ui/settings/advanced.cpp index 6cfc480a..e742378c 100644 --- a/src/ui/settings/advanced.cpp +++ b/src/ui/settings/advanced.cpp @@ -55,6 +55,8 @@ void AdvancedWindow::load() { string name = config::config().list[i]->name; //blacklist (omit/hide options that can be configured through the standard UI) + if(name == "snes.expansion_port") continue; + if(name == "snes.region") continue; if(strbegin(name, "system.")) continue; if(strbegin(name, "path.")) continue; if(strbegin(name, "snes.controller_port")) continue; @@ -62,11 +64,16 @@ void AdvancedWindow::load() { if(name == "system.emulation_speed") continue; if(strbegin(name, "video.windowed.")) continue; if(strbegin(name, "video.fullscreen.")) continue; + if(name == "video.synchronize") continue; if(strbegin(name, "audio.")) continue; if(name == "input.capture_mode") continue; if(strbegin(name, "input.joypad")) continue; if(strbegin(name, "input.multitap")) continue; + if(strbegin(name, "input.mouse")) continue; + if(strbegin(name, "input.superscope")) continue; + if(strbegin(name, "input.justifier")) continue; if(strbegin(name, "input.gui")) continue; + if(name == "misc.cheat_autosort") continue; string value_, default_; config::config().list[i]->get(value_); diff --git a/src/ui/settings/audiosettings.cpp b/src/ui/settings/audiosettings.cpp index d95c20e5..1a66bd2e 100644 --- a/src/ui/settings/audiosettings.cpp +++ b/src/ui/settings/audiosettings.cpp @@ -146,6 +146,12 @@ void AudioSettingsWindow::setup() { einput.create(0, 130, 25); binput.create(0, 100, 25, translate["{{audio}}Set"]); + note.create(0, 475, 54, string() + << translate["{{audio}}Frequency adjust is used to improve video sync timing."] << "\n" + << translate["{{audio}}Lower value to clean audio output."] << "\n" + << translate["{{audio}}Raise value to smooth video output."] + ); + unsigned y = 0; if(config::advanced.enable == false) { attach(lvolume, 0, y); y += 18; @@ -188,6 +194,8 @@ void AudioSettingsWindow::setup() { } } + attach(note, 0, y); + svolume.on_change = bind(&AudioSettingsWindow::volume_change, this); slatency.on_change = bind(&AudioSettingsWindow::latency_change, this); soutput.on_change = bind(&AudioSettingsWindow::output_change, this); diff --git a/src/ui/settings/audiosettings.h b/src/ui/settings/audiosettings.h index 66292ce0..740a1664 100644 --- a/src/ui/settings/audiosettings.h +++ b/src/ui/settings/audiosettings.h @@ -20,6 +20,8 @@ public: Editbox einput; Button binput; + Label note; + uintptr_t volume_change(event_t); uintptr_t latency_change(event_t); uintptr_t output_change(event_t); diff --git a/src/ui/settings/cheateditor.cpp b/src/ui/settings/cheateditor.cpp index d23489f8..af7f8dd9 100644 --- a/src/ui/settings/cheateditor.cpp +++ b/src/ui/settings/cheateditor.cpp @@ -1,8 +1,9 @@ void CheatEditorWindow::setup() { create(0, 475, 355); - list.create(Listbox::Header | Listbox::VerticalScrollAlways, 475, 295, + list.create(Listbox::Header | Listbox::VerticalScrollAlways, 475, 272, string() << translate["Status"] << "\t" << translate["Code"] << "\t" << translate["Description"]); + autosort.create (0, 475, 18, translate["Keep cheat code list sorted by description"]); add_code.create (0, 155, 25, translate["Add Code"]); toggle_code.create(0, 155, 25, translate["Toggle Status"]); delete_code.create(0, 155, 25, translate["Delete Code"]); @@ -10,16 +11,22 @@ void CheatEditorWindow::setup() { desc.create(0, 315, 25, translate[""]); unsigned y = 0; - attach(list, 0, y); y += 295 + 5; + attach(list, 0, y); y += 272 + 5; + attach(autosort, 0, y); y += 18 + 5; attach(add_code, 0, y); attach(toggle_code, 160, y); - attach(delete_code, 320, y); y += 25 + 5; + attach(delete_code, 320, y); y += 25 + 5; attach(code, 0, y); - attach(desc, 160, y); y += 25 + 5; + attach(desc, 160, y); y += 25 + 5; - list.on_activate = bind(&CheatEditorWindow::toggle_event, this); - add_code.on_tick = bind(&CheatEditorWindow::add_tick, this); - toggle_code.on_tick = bind(&CheatEditorWindow::toggle_event, this); + 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); + code.on_change = bind(&CheatEditorWindow::code_input, this); + add_code.on_tick = bind(&CheatEditorWindow::add_tick, this); + toggle_code.on_tick = bind(&CheatEditorWindow::toggle_code_state, this); delete_code.on_tick = bind(&CheatEditorWindow::delete_tick, this); refresh(); @@ -27,52 +34,83 @@ void CheatEditorWindow::setup() { void CheatEditorWindow::refresh() { list.reset(); + if(config::misc.cheat_autosort == true) cheat.sort(); for(unsigned i = 0; i < cheat.count(); i++) { - bool enabled; - uint32 addr; - uint8 data; - char s_code[256], s_desc[256]; - cheat.get(i, enabled, addr, data, s_code, s_desc); + Cheat::cheat_t cheatcode; + cheat.get(i, cheatcode); list.add_item(string() - << (enabled ? translate["Enabled"] : translate["Disabled"]) << "\t" - << s_code << "\t" - << s_desc); + << (cheatcode.enabled ? translate["Enabled"] : translate["Disabled"]) << "\t" + << cheatcode.code << "\t" + << cheatcode.desc); } list.autosize_columns(); //enable controls only if cartridge is loaded bool loaded = cartridge.loaded(); + if(loaded == false) { + code.set_text(translate[""]); + desc.set_text(translate[""]); + } code.enable(loaded); desc.enable(loaded); - add_code.enable(loaded); - toggle_code.enable(loaded); - delete_code.enable(loaded); + + add_code.enable(loaded && is_code_valid()); + toggle_code.disable(); //no list item will be selected; + delete_code.disable(); //so there's nothing to toggle / delete. } -uintptr_t CheatEditorWindow::toggle_event(event_t) { +bool CheatEditorWindow::is_code_valid() { + //input + char s_code[16]; + code.get_text(s_code, sizeof s_code); + //output + unsigned addr; + uint8_t data; + Cheat::type_t type; + return cheat.decode(s_code, addr, data, type); +} + +//enable "Add Code" button only when cheat code is valid +uintptr_t CheatEditorWindow::code_input(event_t) { + add_code.enable(is_code_valid()); + return true; +} + +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()) { cheat.enabled(index) ? cheat.disable(index) : cheat.enable(index); - bool enabled; - uint32 addr; - uint8 data; - char s_code[256], s_desc[256]; - cheat.get(index, enabled, addr, data, s_code, s_desc); + Cheat::cheat_t cheatcode; + cheat.get(index, cheatcode); list.set_item(index, string() - << (enabled ? translate["Enabled"] : translate["Disabled"]) << "\t" - << s_code << "\t" - << s_desc); + << (cheatcode.enabled ? translate["Enabled"] : translate["Disabled"]) << "\t" + << cheatcode.code << "\t" + << cheatcode.desc); } return true; } +//enables "Toggle Code" / "Delete Code" buttons when a code is selected +uintptr_t CheatEditorWindow::list_change(event_t) { + int index = list.get_selection(); + toggle_code.enable(index >= 0); + delete_code.enable(index >= 0); + return true; +} + uintptr_t CheatEditorWindow::add_tick(event_t) { - char s_code[256], s_desc[256]; + char s_code[1024], s_desc[1024]; code.get_text(s_code, sizeof s_code); desc.get_text(s_desc, sizeof s_desc); - cheat.add(false, s_code, s_desc); //param 0 = false, meaning: new codes disabled by default + cheat.add(false, s_code, s_desc); //param 0 = false, meaning new codes are disabled by default refresh(); return true; } diff --git a/src/ui/settings/cheateditor.h b/src/ui/settings/cheateditor.h index 656d5997..7d2c92ec 100644 --- a/src/ui/settings/cheateditor.h +++ b/src/ui/settings/cheateditor.h @@ -1,16 +1,21 @@ class CheatEditorWindow : public Window { public: - Listbox list; - Button add_code; - Button toggle_code; - Button delete_code; - Editbox code; - Editbox desc; + Listbox list; + Checkbox autosort; + Button add_code; + Button toggle_code; + Button delete_code; + Editbox code; + Editbox desc; void setup(); void refresh(); + bool is_code_valid(); - uintptr_t toggle_event(event_t); + uintptr_t toggle_code_state(event_t); + uintptr_t list_change(event_t); + uintptr_t code_input(event_t); + uintptr_t autosort_tick(event_t); uintptr_t add_tick(event_t); uintptr_t delete_tick(event_t); } window_cheat_editor; diff --git a/src/ui/settings/driverselect.cpp b/src/ui/settings/driverselect.cpp index e05ccba6..ff967264 100644 --- a/src/ui/settings/driverselect.cpp +++ b/src/ui/settings/driverselect.cpp @@ -77,6 +77,8 @@ void DriverSelectWindow::setup() { input_keyboard.create(0, 155, 18, translate["Keyboard support"]); input_keyboard.disable(); + input_mouse.create(0, 155, 18, translate["Mouse support"]); + input_mouse.disable(); input_joypad.create(0, 155, 18, translate["Joypad support"]); input_joypad.disable(); @@ -105,9 +107,10 @@ void DriverSelectWindow::setup() { input_caps.set_text(t); input_keyboard.check(input.cap(Input::KeyboardSupport)); + input_mouse.check(input.cap(Input::MouseSupport)); input_joypad.check(input.cap(Input::JoypadSupport)); - unsigned y = 5; + unsigned y = 0; if(crashed == true) { attach(crash_message, 0, y); y += 36 + 5; @@ -133,7 +136,8 @@ void DriverSelectWindow::setup() { attach(input_caps, 0, y); y += 18; attach(input_keyboard, 0, y); - attach(input_joypad, 160, y); y += 18 + 5; + attach(input_mouse, 160, y); + attach(input_joypad, 320, y); y += 18 + 5; attach(restart_message, 0, y); y += 36 + 5; } diff --git a/src/ui/settings/driverselect.h b/src/ui/settings/driverselect.h index f127d2de..a16d2105 100644 --- a/src/ui/settings/driverselect.h +++ b/src/ui/settings/driverselect.h @@ -20,6 +20,7 @@ public: Label input_caps; Checkbox input_keyboard; + Checkbox input_mouse; Checkbox input_joypad; Label restart_message; diff --git a/src/ui/settings/inputconfig.cpp b/src/ui/settings/inputconfig.cpp index a0bf0141..a17394cb 100644 --- a/src/ui/settings/inputconfig.cpp +++ b/src/ui/settings/inputconfig.cpp @@ -3,14 +3,14 @@ void InputConfigWindow::setup() { create(0, 475, 355); - capture_mode.create(0, 475, 18, translate["When emulation window does not have focus:"]); + capture_mode.create(0, 475, 18, translate["{{input}}When emulation window does not have focus:"]); RadioboxGroup group; group.add(&capture_always); group.add(&capture_focus); group.add(&capture_pause); - capture_always.create(group, 0, 155, 18, translate["Allow input"]); - capture_focus.create (group, 0, 155, 18, translate["Ignore input"]); - capture_pause.create (group, 0, 155, 18, translate["Pause emulation"]); + capture_always.create(group, 0, 155, 18, translate["{{input}}Allow input"]); + capture_focus.create (group, 0, 155, 18, translate["{{input}}Ignore input"]); + capture_pause.create (group, 0, 155, 18, translate["{{input}}Pause emulation"]); config_type.create(0, 235, 25); config_type.add_item(translate["{{input}}Controller Port 1"]); @@ -21,10 +21,11 @@ void InputConfigWindow::setup() { config_subtype.create(0, 235, 25); refresh_subtype(); - list.create(Listbox::Header | Listbox::VerticalScrollAlways, 475, 254, string() << translate["{{input}}Name"] << "\t" << translate["{{input}}Value"]); - setkey.create(0, 235, 25, translate["Assign Key"]); + list.create(Listbox::Header | Listbox::VerticalScrollAlways, 475, 254, + string() << translate["{{input}}Name"] << "\t" << translate["{{input}}Value"]); + setkey.create(0, 235, 25, string() << translate["{{input}}Assign Key"] << " ..."); setkey.disable(); - clrkey.create(0, 235, 25, translate["Unassign Key"]); + clrkey.create(0, 235, 25, translate["{{input}}Unassign Key"]); clrkey.disable(); unsigned y = 0; @@ -56,71 +57,91 @@ void InputConfigWindow::setup() { window_input_capture.setup(); } -InputConfigWindow::InputType InputConfigWindow::get_input_type(unsigned &length) { - unsigned type = config_type.get_selection(); - unsigned subtype = config_subtype.get_selection(); +InputGroup* InputConfigWindow::get_group() { + unsigned port = config_type.get_selection(); + unsigned index = config_subtype.get_selection(); - switch(type) { - case 0: { - switch(subtype) { - case 0: length = 12; return Port1_Joypad; - case 1: length = 12; return Port1_Multitap1; - case 2: length = 12; return Port1_Multitap2; - case 3: length = 12; return Port1_Multitap3; - case 4: length = 12; return Port1_Multitap4; + if(port == 0 || port == 1) { + //SNES controller + InputDevice *device = 0; + for(unsigned i = 0; i < inputpool.list.size(); i++) { + if(inputpool.list[i]->port == port) { + if(index-- == 0) { + device = inputpool.list[i]; + break; + } } + } + return device; + } else { + //user interface + return &inputuigeneral; + } +} + +bool InputConfigWindow::assign(uint16_t code) { + InputGroup *group = get_group(); + if(!group) return false; + + int pos = list.get_selection(); + if(pos < 0 || pos >= group->list.size()) return false; + + //make sure boolean buttons map to boolean input; axes to mouse / joypad axes + switch(group->list[pos]->type) { + case InputObject::Button: { + if(InputCode::is_button(code) == false) return false; } break; - case 1: { - switch(subtype) { - case 0: length = 12; return Port2_Joypad; - case 1: length = 12; return Port2_Multitap1; - case 2: length = 12; return Port2_Multitap2; - case 3: length = 12; return Port2_Multitap3; - case 4: length = 12; return Port2_Multitap4; - } - } break; - - case 2: { - switch(subtype) { - case 0: length = 12; return UI_General; - } + case InputObject::Axis: { + if(InputCode::is_axis(code) == false) return false; + int16_t state = input_manager.state(code); + //add a bit of resistance to prevent infinitesimally small movements from triggering assignment + if(InputCode::type(code) == InputCode::MouseAxis && abs(state) < 8) return false; + //joypad axis range = -32768 to +32767 + //some joypads have pressure-sensitive buttons that read +32767 when depressed ... + //therefore, range test between 25% and 75% pressure before triggering assignment + if(InputCode::type(code) == InputCode::JoypadAxis && (abs(state) < 8192 || abs(state) > 24576)) return false; } break; } - return TypeUnknown; + group->list[pos]->setting = input_find(code); + input_manager.bind(); + return true; } void InputConfigWindow::refresh_subtype() { config_subtype.reset(); + unsigned port = config_type.get_selection(); - switch(config_type.get_selection()) { - case 0: - case 1: { - config_subtype.add_item(translate["Joypad"]); - config_subtype.add_item(translate["Multitap Port 1"]); - config_subtype.add_item(translate["Multitap Port 2"]); - config_subtype.add_item(translate["Multitap Port 3"]); - config_subtype.add_item(translate["Multitap Port 4"]); - } break; - - case 2: { - config_subtype.add_item(translate["General"]); - } break; + if(port == 0 || port == 1) { + //SNES controller + for(unsigned device = 0; device < inputpool.list.size(); device++) { + if(inputpool.list[device]->port == port) { + config_subtype.add_item(translate[inputpool.list[device]->name]); + } + } + } else { + //user interface + config_subtype.add_item(translate[inputuigeneral.name]); } config_subtype.set_selection(0); } void InputConfigWindow::refresh_list() { + setkey.disable(); + clrkey.disable(); list.reset(); - unsigned length; - get_input_type(length); - for(unsigned i = 0; i < length; i++) { - string name; - acquire(i, name); - list.add_item(string() << name << "\t" << input_find(get_value(i))); + InputGroup *group = get_group(); + if(!group) return; + + for(unsigned i = 0; i < group->list.size(); i++) { + list.add_item(string() + << translate[group->list[i]->name] + << "\t" + << group->list[i]->setting); } + list.autosize_columns(); } @@ -150,41 +171,71 @@ uintptr_t InputConfigWindow::list_change(event_t) { } uintptr_t InputConfigWindow::set_tick(event_t) { + InputGroup *group = get_group(); + if(!group) return true; + int pos = list.get_selection(); - if(pos < 0) return true; - window_input_capture.index = pos; - string message = translate["Press a key to assign to $ ..."]; - string name; - acquire(pos, name); - replace(message, "$", name); + if(pos < 0 || pos >= group->list.size()) return true; + + string message; + if(group->list[pos]->type == InputObject::Button) { + message = translate["Press a key or button to assign to $ ..."]; + } else { + message = translate["Move mouse or analog joypad axis to assign to $ ..."]; + } + + replace(message, "$", group->list[pos]->name); window_input_capture.label.set_text(message); - window_input_capture.canvas.show(config_type.get_selection() < 2); //only show joypad graphic if setting joypad button + + bool show_controller_graphic = false; + InputDevice *device = dynamic_cast(group); + if(device) { + SNES::Input::DeviceID id = device->id; + if(id == SNES::Input::DeviceIDJoypad1 || id == SNES::Input::DeviceIDJoypad2 + || id == SNES::Input::DeviceIDMultitap1A || id == SNES::Input::DeviceIDMultitap2A + || id == SNES::Input::DeviceIDMultitap1B || id == SNES::Input::DeviceIDMultitap2B + || id == SNES::Input::DeviceIDMultitap1C || id == SNES::Input::DeviceIDMultitap2C + || id == SNES::Input::DeviceIDMultitap1D || id == SNES::Input::DeviceIDMultitap2D + ) { + show_controller_graphic = true; + } + } + + window_input_capture.index = pos; + window_input_capture.canvas.show(show_controller_graphic); window_input_capture.show(); return true; } uintptr_t InputConfigWindow::clr_tick(event_t) { - int pos = list.get_selection(); - if(pos < 0) return true; - set_value(pos, keyboard::none); - refresh_list(); + if(list.get_selection() >= 0) { + assign(keyboard::none); + refresh_list(); + } return true; } /* InputCaptureWindow */ -void InputCaptureWindow::assign(uint16_t key) { - waiting = false; - hide(); - window_input_config.set_value(index, key); - window_input_config.refresh_list(); - input.clear(); +void InputCaptureWindow::assign(uint16_t code) { + if(window_input_config.assign(code)) { + waiting = false; + hide(); + window_input_config.refresh_list(); + input_manager.flush(); + } } void InputCaptureWindow::show() { - input.poll(); + input_manager.refresh(); waiting = true; - locked = input.key_down(keyboard::return_) || input.key_down(keyboard::spacebar); + //certain keys (eg spacebar) can activate the key assignment window, + //which would then auto-bind those keys. detect these keys, and set + //a lock so that these keys will not be registered unless they + //are released and then re-pressed. + locked = input_manager.state(keyboard::return_) + || input_manager.state(keyboard::spacebar) + || input_manager.state(mouse::button + 0); Window::focus(); } @@ -208,74 +259,3 @@ InputCaptureWindow::InputCaptureWindow() { locked = false; index = 0; } - -/* Misc */ - -string_setting& InputConfigWindow::acquire(unsigned index, string &name) { - #define map(n, lname) \ - case n: { \ - switch(index) { \ - case 0: name = translate["Up"]; return config::input.lname.up; \ - case 1: name = translate["Down"]; return config::input.lname.down; \ - case 2: name = translate["Left"]; return config::input.lname.left; \ - case 3: name = translate["Right"]; return config::input.lname.right; \ - case 4: name = translate["A"]; return config::input.lname.a; \ - case 5: name = translate["B"]; return config::input.lname.b; \ - case 6: name = translate["X"]; return config::input.lname.x; \ - case 7: name = translate["Y"]; return config::input.lname.y; \ - case 8: name = translate["L"]; return config::input.lname.l; \ - case 9: name = translate["R"]; return config::input.lname.r; \ - case 10: name = translate["Select"]; return config::input.lname.select; \ - case 11: name = translate["Start"]; return config::input.lname.start; \ - } \ - } break; - - unsigned length; - switch(get_input_type(length)) { default: - map(Port1_Joypad, joypad1) - map(Port1_Multitap1, multitap1a) - map(Port1_Multitap2, multitap1b) - map(Port1_Multitap3, multitap1c) - map(Port1_Multitap4, multitap1d) - - map(Port2_Joypad, joypad2) - map(Port2_Multitap1, multitap2a) - map(Port2_Multitap2, multitap2b) - map(Port2_Multitap3, multitap2c) - map(Port2_Multitap4, multitap2d) - - case UI_General: { - switch(index) { - case 0: name = translate["Load Cartridge"]; return config::input.gui.load; - case 1: name = translate["Pause Emulation"]; return config::input.gui.pause; - case 2: name = translate["Reset System"]; return config::input.gui.reset; - case 3: name = translate["Power Cycle System"]; return config::input.gui.power; - case 4: name = translate["Exit Emulator"]; return config::input.gui.quit; - case 5: name = translate["Emulation Speed Decrease"]; return config::input.gui.speed_decrease; - case 6: name = translate["Emulation Speed Increase"]; return config::input.gui.speed_increase; - case 7: name = translate["Frameskip Decrease"]; return config::input.gui.frameskip_decrease; - case 8: name = translate["Frameskip Increase"]; return config::input.gui.frameskip_increase; - case 9: name = translate["Toggle Fullscreen"]; return config::input.gui.toggle_fullscreen; - case 10: name = translate["Toggle Menubar"]; return config::input.gui.toggle_menubar; - case 11: name = translate["Toggle Statusbar"]; return config::input.gui.toggle_statusbar; - } - } break; - } - - #undef map - - name = ""; - static string_setting notfound("", "", ""); - return notfound; -} - -uint InputConfigWindow::get_value(uint index) { - string name; - return input_find(acquire(index, name)); -} - -void InputConfigWindow::set_value(uint index, uint16 value) { - string name; - acquire(index, name) = input_find(value); - input_manager.bind(); -} diff --git a/src/ui/settings/inputconfig.h b/src/ui/settings/inputconfig.h index 37e56332..e9a1e892 100644 --- a/src/ui/settings/inputconfig.h +++ b/src/ui/settings/inputconfig.h @@ -1,3 +1,5 @@ +class InputGroup; + class InputConfigWindow : public Window { public: Label capture_mode; @@ -11,39 +13,19 @@ public: Button clrkey; void setup(); + + InputGroup* get_group(); + bool assign(uint16_t code); + void refresh_subtype(); void refresh_list(); - enum InputType { - TypeUnknown, - - Port1_Joypad, - Port1_Multitap1, - Port1_Multitap2, - Port1_Multitap3, - Port1_Multitap4, - - Port2_Joypad, - Port2_Multitap1, - Port2_Multitap2, - Port2_Multitap3, - Port2_Multitap4, - - UI_General, - }; - - InputType get_input_type(unsigned &length); - uintptr_t capture_change(event_t); uintptr_t type_change(event_t); uintptr_t subtype_change(event_t); uintptr_t list_change(event_t); uintptr_t set_tick(event_t); uintptr_t clr_tick(event_t); - - string_setting& acquire(unsigned index, string &name); - uint get_value(uint index); - void set_value(uint index, uint16 value); } window_input_config; class InputCaptureWindow : public Window { @@ -55,7 +37,7 @@ public: bool locked; uint index; - void assign(uint16_t key); + void assign(uint16_t code); void show(); void setup(); diff --git a/src/ui/settings/inputconfig.txt b/src/ui/settings/inputconfig.txt new file mode 100644 index 00000000..275c3e96 --- /dev/null +++ b/src/ui/settings/inputconfig.txt @@ -0,0 +1,360 @@ +/* InputConfigWindow */ + +void InputConfigWindow::setup() { + create(0, 475, 355); + + capture_mode.create(0, 475, 18, translate["When emulation window does not have focus:"]); + RadioboxGroup group; + group.add(&capture_always); + group.add(&capture_focus); + group.add(&capture_pause); + capture_always.create(group, 0, 155, 18, translate["Allow input"]); + capture_focus.create (group, 0, 155, 18, translate["Ignore input"]); + capture_pause.create (group, 0, 155, 18, translate["Pause emulation"]); + + config_type.create(0, 235, 25); + config_type.add_item(translate["{{input}}Controller Port 1"]); + config_type.add_item(translate["{{input}}Controller Port 2"]); + config_type.add_item(translate["{{input}}User Interface"]); + config_type.set_selection(0); + + config_subtype.create(0, 235, 25); + refresh_subtype(); + + list.create(Listbox::Header | Listbox::VerticalScrollAlways, 475, 254, string() << translate["{{input}}Name"] << "\t" << translate["{{input}}Value"]); + setkey.create(0, 235, 25, string() << translate["Assign Key"] << " ..."); + setkey.disable(); + clrkey.create(0, 235, 25, translate["Unassign Key"]); + clrkey.disable(); + + unsigned y = 0; + attach(capture_mode, 0, y); y += 18; + attach(capture_always, 0, y); + attach(capture_focus, 160, y); + attach(capture_pause, 320, y); y += 18 + 5; + attach(config_type, 0, y); + attach(config_subtype, 240, y); y += 25 + 5; + attach(list, 0, y); y += 254 + 5; + attach(setkey, 0, y); + attach(clrkey, 240, y); y += 25 + 5; + + capture_always.on_tick = bind(&InputConfigWindow::capture_change, this); + capture_focus.on_tick = bind(&InputConfigWindow::capture_change, this); + capture_pause.on_tick = bind(&InputConfigWindow::capture_change, this); + config_type.on_change = bind(&InputConfigWindow::type_change, this); + config_subtype.on_change = bind(&InputConfigWindow::subtype_change, this); + list.on_change = bind(&InputConfigWindow::list_change, this); + list.on_activate = bind(&InputConfigWindow::set_tick, this); + setkey.on_tick = bind(&InputConfigWindow::set_tick, this); + clrkey.on_tick = bind(&InputConfigWindow::clr_tick, this); + + if(config::input.capture_mode == 1) capture_focus.check(); + else if(config::input.capture_mode == 2) capture_pause.check(); + else config::input.capture_mode = 0; //capture_always + + refresh_list(); + window_input_capture.setup(); +} + +InputConfigWindow::InputType InputConfigWindow::get_input_type(unsigned &length) { + unsigned type = config_type.get_selection(); + unsigned subtype = config_subtype.get_selection(); + + switch(type) { + case 0: { + switch(subtype) { default: + case 0: length = 12; return Port1_Joypad; + case 1: length = 12; return Port1_Multitap1; + case 2: length = 12; return Port1_Multitap2; + case 3: length = 12; return Port1_Multitap3; + case 4: length = 12; return Port1_Multitap4; + case 5: length = 4; return Port1_Mouse; + } + } break; + + case 1: { + switch(subtype) { default: + case 0: length = 12; return Port2_Joypad; + case 1: length = 12; return Port2_Multitap1; + case 2: length = 12; return Port2_Multitap2; + case 3: length = 12; return Port2_Multitap3; + case 4: length = 12; return Port2_Multitap4; + case 5: length = 4; return Port2_Mouse; + case 6: length = 6; return Port2_SuperScope; + case 7: length = 4; return Port2_Justifier1; + case 8: length = 4; return Port2_Justifier2; + } + } break; + + case 2: { + switch(subtype) { default: + case 0: length = 12; return UI_General; + } + } break; + } + + return TypeUnknown; +} + +void InputConfigWindow::refresh_subtype() { + config_subtype.reset(); + + switch(config_type.get_selection()) { + case 0: { + config_subtype.add_item(translate["Joypad"]); + config_subtype.add_item(translate["Multitap Port 1"]); + config_subtype.add_item(translate["Multitap Port 2"]); + config_subtype.add_item(translate["Multitap Port 3"]); + config_subtype.add_item(translate["Multitap Port 4"]); + config_subtype.add_item(translate["Mouse"]); + } break; + + case 1: { + config_subtype.add_item(translate["Joypad"]); + config_subtype.add_item(translate["Multitap Port 1"]); + config_subtype.add_item(translate["Multitap Port 2"]); + config_subtype.add_item(translate["Multitap Port 3"]); + config_subtype.add_item(translate["Multitap Port 4"]); + config_subtype.add_item(translate["Mouse"]); + config_subtype.add_item(translate["Super Scope"]); + config_subtype.add_item(translate["Justifier 1"]); + config_subtype.add_item(translate["Justifier 2"]); + } break; + + case 2: { + config_subtype.add_item(translate["General"]); + } break; + } + + config_subtype.set_selection(0); +} + +void InputConfigWindow::refresh_list() { + list.reset(); + unsigned length; + get_input_type(length); + for(unsigned i = 0; i < length; i++) { + string name; + acquire(i, name); + list.add_item(string() << name << "\t" << input_find(get_value(i))); + } + list.autosize_columns(); +} + +uintptr_t InputConfigWindow::capture_change(event_t e) { + if(e.widget == &capture_always) config::input.capture_mode = 0; + if(e.widget == &capture_focus) config::input.capture_mode = 1; + if(e.widget == &capture_pause) config::input.capture_mode = 2; + return true; +} + +uintptr_t InputConfigWindow::type_change(event_t) { + refresh_subtype(); + refresh_list(); + return true; +} + +uintptr_t InputConfigWindow::subtype_change(event_t) { + refresh_list(); + return true; +} + +uintptr_t InputConfigWindow::list_change(event_t) { + int pos = list.get_selection(); + setkey.enable(pos >= 0); + clrkey.enable(pos >= 0); + return true; +} + +uintptr_t InputConfigWindow::set_tick(event_t) { + int pos = list.get_selection(); + if(pos < 0) return true; + window_input_capture.index = pos; + string message = translate["Press a key to assign to $ ..."]; + string name; + acquire(pos, name); + replace(message, "$", name); + window_input_capture.label.set_text(message); + + unsigned length; + InputType type = get_input_type(length); + bool show_controller_graphic = + type == Port1_Joypad || type == Port1_Multitap1 || type == Port1_Multitap2 + || type == Port1_Multitap3 || type == Port1_Multitap4 + || type == Port2_Joypad || type == Port2_Multitap1 || type == Port1_Multitap2 + || type == Port2_Multitap3 || type == Port2_Multitap4; + + window_input_capture.canvas.show(show_controller_graphic); + window_input_capture.show(); + return true; +} + +uintptr_t InputConfigWindow::clr_tick(event_t) { + int pos = list.get_selection(); + if(pos < 0) return true; + set_value(pos, keyboard::none); + refresh_list(); + return true; +} + +/* InputCaptureWindow */ + +void InputCaptureWindow::assign(uint16_t key) { + waiting = false; + hide(); + window_input_config.set_value(index, key); + window_input_config.refresh_list(); + input_manager.clear(); +} + +void InputCaptureWindow::show() { + input_manager.refresh(); + waiting = true; + //certain keys (eg spacebar) can activate the key assignment window, + //which would then auto-bind those keys. detect these keys, and set + //a lock so that these keys will not be registered unless they + //are released and then re-pressed. + locked = input_manager.state(keyboard::return_) + || input_manager.state(keyboard::spacebar) + || input_manager.state(mouse::button + 0); + Window::focus(); +} + +uintptr_t InputCaptureWindow::close(event_t) { + hide(); + return false; +} + +void InputCaptureWindow::setup() { + create(Window::AutoCenter, 382, 206, translate["bsnes Key Capture"]); + label.create(0, 340, 18); + canvas.create(0, 372, 178); + memcpy(canvas.buffer(), resource::controller, 372 * 178 * 4); + attach(label, 5, 5); + attach(canvas, 5, 23); + on_close = bind(&InputCaptureWindow::close, this); +} + +InputCaptureWindow::InputCaptureWindow() { + waiting = false; + locked = false; + index = 0; +} + +/* Misc */ + +string_setting& InputConfigWindow::acquire(unsigned index, string &name) { + #define map(n, lname) \ + case n: { \ + switch(index) { \ + case 0: name = translate["Up"]; return config::input.lname.up; \ + case 1: name = translate["Down"]; return config::input.lname.down; \ + case 2: name = translate["Left"]; return config::input.lname.left; \ + case 3: name = translate["Right"]; return config::input.lname.right; \ + case 4: name = translate["A"]; return config::input.lname.a; \ + case 5: name = translate["B"]; return config::input.lname.b; \ + case 6: name = translate["X"]; return config::input.lname.x; \ + case 7: name = translate["Y"]; return config::input.lname.y; \ + case 8: name = translate["L"]; return config::input.lname.l; \ + case 9: name = translate["R"]; return config::input.lname.r; \ + case 10: name = translate["Select"]; return config::input.lname.select; \ + case 11: name = translate["Start"]; return config::input.lname.start; \ + } \ + } break; + + unsigned length; + switch(get_input_type(length)) { default: + map(Port1_Joypad, joypad1) + map(Port1_Multitap1, multitap1a) + map(Port1_Multitap2, multitap1b) + map(Port1_Multitap3, multitap1c) + map(Port1_Multitap4, multitap1d) + + case Port1_Mouse: { + switch(index) { + case 0: name = translate["X-axis"]; return config::input.mouse1.x; + case 1: name = translate["Y-axis"]; return config::input.mouse1.y; + case 2: name = translate["Left button"]; return config::input.mouse1.l; + case 3: name = translate["Right button"]; return config::input.mouse1.r; + } + } break; + + map(Port2_Joypad, joypad2) + map(Port2_Multitap1, multitap2a) + map(Port2_Multitap2, multitap2b) + map(Port2_Multitap3, multitap2c) + map(Port2_Multitap4, multitap2d) + + case Port2_Mouse: { + switch(index) { + case 0: name = translate["X-axis"]; return config::input.mouse2.x; + case 1: name = translate["Y-axis"]; return config::input.mouse2.y; + case 2: name = translate["Left button"]; return config::input.mouse2.l; + case 3: name = translate["Right button"]; return config::input.mouse2.r; + } + } break; + + case Port2_SuperScope: { + switch(index) { + case 0: name = translate["{{superscope}}X-axis"]; return config::input.superscope.x; + case 1: name = translate["{{superscope}}Y-axis"]; return config::input.superscope.y; + case 2: name = translate["{{superscope}}Trigger"]; return config::input.superscope.trigger; + case 3: name = translate["{{superscope}}Cursor"]; return config::input.superscope.cursor; + case 4: name = translate["{{superscope}}Turbo"]; return config::input.superscope.turbo; + case 5: name = translate["{{superscope}}Pause"]; return config::input.superscope.pause; + } + } break; + + case Port2_Justifier1: { + switch(index) { + case 0: name = translate["{{justifier}}X-axis"]; return config::input.justifier1.x; + case 1: name = translate["{{justifier}}Y-axis"]; return config::input.justifier1.y; + case 2: name = translate["{{justifier}}Trigger"]; return config::input.justifier1.trigger; + case 3: name = translate["{{justifier}}Start"]; return config::input.justifier1.start; + } + } break; + + case Port2_Justifier2: { + switch(index) { + case 0: name = translate["{{justifier}}X-axis"]; return config::input.justifier2.x; + case 1: name = translate["{{justifier}}Y-axis"]; return config::input.justifier2.y; + case 2: name = translate["{{justifier}}Trigger"]; return config::input.justifier2.trigger; + case 3: name = translate["{{justifier}}Start"]; return config::input.justifier2.start; + } + } break; + + case UI_General: { + switch(index) { + case 0: name = translate["Load Cartridge"]; return config::input.gui.load; + case 1: name = translate["Pause Emulation"]; return config::input.gui.pause; + case 2: name = translate["Reset System"]; return config::input.gui.reset; + case 3: name = translate["Power Cycle System"]; return config::input.gui.power; + case 4: name = translate["Exit Emulator"]; return config::input.gui.quit; + case 5: name = translate["Emulation Speed Decrease"]; return config::input.gui.speed_decrease; + case 6: name = translate["Emulation Speed Increase"]; return config::input.gui.speed_increase; + case 7: name = translate["Frameskip Decrease"]; return config::input.gui.frameskip_decrease; + case 8: name = translate["Frameskip Increase"]; return config::input.gui.frameskip_increase; + case 9: name = translate["Toggle Fullscreen"]; return config::input.gui.toggle_fullscreen; + case 10: name = translate["Toggle Menubar"]; return config::input.gui.toggle_menubar; + case 11: name = translate["Toggle Statusbar"]; return config::input.gui.toggle_statusbar; + } + } break; + } + + #undef map + + name = ""; + static string_setting notfound("", "", ""); + return notfound; +} + +uint InputConfigWindow::get_value(uint index) { + string name; + return input_find(acquire(index, name)); +} + +void InputConfigWindow::set_value(uint index, uint16 value) { + string name; + acquire(index, name) = input_find(value); + input_manager.bind(); +} diff --git a/src/ui/settings/pathsettings.cpp b/src/ui/settings/pathsettings.cpp index d6ff7412..d31a3b49 100644 --- a/src/ui/settings/pathsettings.cpp +++ b/src/ui/settings/pathsettings.cpp @@ -79,22 +79,22 @@ void PathSettingsWindow::setup() { lrompath.create(0, 475, 18, translate["Default game ROM path:"]); rompath.create(Editbox::Readonly, 265, 25); - romselect.create(0, 100, 25, translate["{{path}}Select"]); + romselect.create(0, 100, 25, string() << translate["{{path}}Select"] << " ..."); romdefault.create(0, 100, 25, translate["{{path}}Default"]); lpatchpath.create(0, 475, 18, translate["Default UPS patch path:"]); patchpath.create(Editbox::Readonly, 265, 25); - patchselect.create(0, 100, 25, translate["{{path}}Select"]); + patchselect.create(0, 100, 25, string() << translate["{{path}}Select"] << " ..."); patchdefault.create(0, 100, 25, translate["{{path}}Default"]); lsavepath.create(0, 475, 18, translate["Default save RAM path:"]); savepath.create(Editbox::Readonly, 265, 25); - saveselect.create(0, 100, 25, translate["{{path}}Select"]); + saveselect.create(0, 100, 25, string() << translate["{{path}}Select"] << " ..."); savedefault.create(0, 100, 25, translate["{{path}}Default"]); lcheatpath.create(0, 475, 18, translate["Default cheat file path:"]); cheatpath.create(Editbox::Readonly, 265, 25); - cheatselect.create(0, 100, 25, translate["{{path}}Select"]); + cheatselect.create(0, 100, 25, string() << translate["{{path}}Select"] << " ..."); cheatdefault.create(0, 100, 25, translate["{{path}}Default"]); unsigned y = 0; diff --git a/src/ui/settings/videosettings.cpp b/src/ui/settings/videosettings.cpp index d3fb5d07..5e51efba 100644 --- a/src/ui/settings/videosettings.cpp +++ b/src/ui/settings/videosettings.cpp @@ -25,7 +25,7 @@ void VideoSettingsWindow::setup() { attach(gamma_ramp, 0, y); attach(sepia, 240, y); y += 18; attach(grayscale, 0, y); - attach(invert, 240, y); y += 18; + attach(invert, 240, y); y += 18 + 5; attach(preset_optimal, 0, y); attach(preset_standard, 240, y); y += 25; @@ -52,16 +52,16 @@ void VideoSettingsWindow::sync_ui() { gamma_ramp.check(config::system.gamma_ramp); sepia.check(config::system.sepia); grayscale.check(config::system.grayscale); - invert.check(config::system.invert); - - libfilter::colortable.set_contrast(config::system.contrast); - libfilter::colortable.set_brightness(config::system.brightness); - libfilter::colortable.set_gamma(config::system.gamma); - libfilter::colortable.enable_gamma_ramp(config::system.gamma_ramp); - libfilter::colortable.enable_sepia(config::system.sepia); - libfilter::colortable.enable_grayscale(config::system.grayscale); - libfilter::colortable.enable_invert(config::system.invert); - libfilter::colortable.update(); + invert.check(config::system.invert); + + libfilter::colortable.set_contrast(config::system.contrast); + libfilter::colortable.set_brightness(config::system.brightness); + libfilter::colortable.set_gamma(config::system.gamma); + libfilter::colortable.enable_gamma_ramp(config::system.gamma_ramp); + libfilter::colortable.enable_sepia(config::system.sepia); + libfilter::colortable.enable_grayscale(config::system.grayscale); + libfilter::colortable.enable_invert(config::system.invert); + libfilter::colortable.update(); ui_lock = false; } @@ -130,7 +130,7 @@ uintptr_t VideoSettingsWindow::optimal_tick(event_t) { config::system.sepia = false; config::system.grayscale = false; config::system.invert = false; - sync_ui(); + sync_ui(); return true; } @@ -142,7 +142,7 @@ uintptr_t VideoSettingsWindow::standard_tick(event_t) { config::system.sepia = false; config::system.grayscale = false; config::system.invert = false; - sync_ui(); + sync_ui(); return true; } diff --git a/src/ui/status.cpp b/src/ui/status.cpp index 0f86e6d7..c46c66b1 100644 --- a/src/ui/status.cpp +++ b/src/ui/status.cpp @@ -17,6 +17,8 @@ void Status::update() { } } else if(!cartridge.loaded()) { output = ""; + } else if(app.power == false) { + output = string() << cartridge.name() << " : " << translate["Power off."]; } else if(app.pause || app.autopause) { output = translate["Paused."]; } else if(ppu.status.frames_updated) { @@ -32,7 +34,7 @@ void Status::update() { case 5: max_framerate = 0; break; } - output = string() << cartridge.info.filename << " : " << (int)ppu.status.frames_executed; + output = string() << cartridge.name() << " : " << (int)ppu.status.frames_executed; if(max_framerate != 0) { output << " / "; output << (int)max_framerate; diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp index d4eefe31..ef273345 100644 --- a/src/ui/ui.cpp +++ b/src/ui/ui.cpp @@ -69,7 +69,7 @@ void ui_init() { config::config().save(config::bsnes_cfg); video.set(Video::Handle, window_main.view.handle()); - video.set(Video::Synchronize, config::video.windowed.synchronize); + video.set(Video::Synchronize, config::video.synchronize); audio.set(Audio::Handle, window_main.handle()); audio.set(Audio::Synchronize, config::audio.synchronize); audio.set(Audio::Volume, config::audio.volume); @@ -87,7 +87,6 @@ void ui_init() { video.clear(); audio.clear(); - input.clear(); //if code has reached this point, driver initialized successfully config::system.invoke_crash_handler = false; @@ -95,10 +94,9 @@ void ui_init() { event::update_video_settings(); //call second time to update video class settings - //UI setup complete, hook keyboard callbacks + //UI setup complete, hook input callbacks snesinterface.input_ready = bind(&MainWindow::input_ready, &window_main); - input_manager.on_keydown = bind(&event::keydown); - input_manager.on_keyup = bind(&event::keyup); + input_manager.on_input = bind(&event::input_event); } void ui_term() {