From a9bff19b5b5fcfb0b97ebaa6c278068b6856982b Mon Sep 17 00:00:00 2001 From: byuu Date: Sun, 26 Oct 2008 19:59:04 +0000 Subject: [PATCH] Update to bsnes v037 release. This release adds support for the SNES mouse, Super Scope and Justifier peripherals. It also simplifies cartridge loading and refines the user interface. Lastly, GZ and ZIP archives can now contain non-ANSI characters (Chinese, Japanese, Russian, ...) This support existed in the last release for all uncompressed files. Together, this means only JMA support on Windows lacks support for loading non-ANSI filenames. This is due to the library itself (really, it's more Windows' fault), and licensing issues prevent me from patching libjma as I did with zlib (bsnes is not GPL compatible.) I'm planning to work with Nach to fix this in a future release. About the cartridge loading changes ... the emulator now determines what kind of cartridge is being loaded (eg normal, BS-X BIOS, Sufami Turbo cart, etc) by looking inside the file itself. If it detects a cart type that requires more than one ROM image to load, it will present you with the appropriate specialized load menu automatically. Aside from being more intuitive, this method also allows loading of BS-X and Sufami Turbo games from the command-line or via file association. Changelog: - added mouse support to DirectInput and SDL input drivers - up to 96 buttons per controller; 8 buttons per mouse (5 per mouse on Linux) can be mapped now - added SNES mouse support (does not support speed setting yet) - added Super Scope support - added Justifier support (supports both Justifiers) - input management system almost completely rewritten to support new controllers - "Load Special" menu removed, all cart loading merged to "Load Cartridge ..." option - replaced "Power Cycle" and "Unload Cartridge" with "Power" -> "On" / "Off" - when video exceeds screen size and is scaled down, aspect ratio is now maintained [Ver Greeneyes] - zlib modified to support non-ANSI characters - cheat code count was limited to 1,024 codes before; it now supports unlimited codes per game - added sort by description setting for cheat code list - polished listbox control interaction (disable buttons when nothing selected, etc) - cleaned up OBC-1 chip emulation (code is functionally identical to v036) - added option to toggle fullscreen mode to settings menu - added advanced mode options to toggle base unit (none, Satellaview) and system region (Auto-detect, NTSC, PAL) --- readme.txt | 14 +- src/base.h | 10 +- src/cart/cart.cpp | 105 ++-- src/cart/cart.h | 102 +++- src/cart/cart_bsc.cpp | 59 +- src/cart/cart_bsx.cpp | 46 +- src/cart/cart_header.cpp | 139 +++-- src/cart/cart_load.cpp | 50 ++ src/cart/cart_normal.cpp | 55 +- src/cart/cart_st.cpp | 94 +--- src/cheat/cheat.cpp | 247 +++++---- src/cheat/cheat.h | 80 +-- src/chip/obc1/obc1.cpp | 85 ++- src/chip/obc1/obc1.h | 8 +- src/chip/srtc/srtc.cpp | 4 +- src/chip/srtc/srtc.h | 4 +- src/config/config.cpp | 61 ++- src/config/config.h | 2 + src/cpu/scpu/timing/timing.cpp | 491 ++++++++--------- src/cpu/scpu/timing/timing.h | 56 +- src/lib/bbase.h | 85 +-- src/lib/hiro/gtk/button.cpp | 1 + src/lib/hiro/gtk/{button.h => button.hpp} | 0 src/lib/hiro/gtk/canvas.cpp | 17 + src/lib/hiro/gtk/{canvas.h => canvas.hpp} | 0 src/lib/hiro/gtk/{checkbox.h => checkbox.hpp} | 0 src/lib/hiro/gtk/{combobox.h => combobox.hpp} | 0 src/lib/hiro/gtk/editbox.cpp | 8 + src/lib/hiro/gtk/{editbox.h => editbox.hpp} | 0 .../gtk/{formcontrol.h => formcontrol.hpp} | 0 src/lib/hiro/gtk/{frame.h => frame.hpp} | 0 src/lib/hiro/gtk/hiro.cpp | 13 +- src/lib/hiro/gtk/{hiro.h => hiro.hpp} | 46 +- src/lib/hiro/gtk/{label.h => label.hpp} | 0 src/lib/hiro/gtk/{listbox.h => listbox.hpp} | 0 .../{menucheckitem.h => menucheckitem.hpp} | 0 .../gtk/{menucontrol.h => menucontrol.hpp} | 0 .../hiro/gtk/{menugroup.h => menugroup.hpp} | 0 src/lib/hiro/gtk/{menuitem.h => menuitem.hpp} | 0 .../{menuradioitem.h => menuradioitem.hpp} | 0 .../{menuseparator.h => menuseparator.hpp} | 0 src/lib/hiro/gtk/port.cpp | 17 + .../gtk/{progressbar.h => progressbar.hpp} | 0 src/lib/hiro/gtk/{radiobox.h => radiobox.hpp} | 0 src/lib/hiro/gtk/{slider.h => slider.hpp} | 0 src/lib/hiro/gtk/{widget.h => widget.hpp} | 0 src/lib/hiro/gtk/window.cpp | 12 +- src/lib/hiro/gtk/{window.h => window.hpp} | 0 src/lib/hiro/hiro.cpp | 2 +- src/lib/hiro/{hiro.h => hiro.hpp} | 16 +- src/lib/hiro/win/{button.h => button.hpp} | 0 src/lib/hiro/win/{canvas.h => canvas.hpp} | 0 src/lib/hiro/win/{checkbox.h => checkbox.hpp} | 0 src/lib/hiro/win/{combobox.h => combobox.hpp} | 0 src/lib/hiro/win/{editbox.h => editbox.hpp} | 0 .../win/{formcontrol.h => formcontrol.hpp} | 0 src/lib/hiro/win/{frame.h => frame.hpp} | 0 src/lib/hiro/win/hiro.cpp | 85 ++- src/lib/hiro/win/{hiro.h => hiro.hpp} | 54 +- src/lib/hiro/win/{label.h => label.hpp} | 0 src/lib/hiro/win/listbox.cpp | 1 + src/lib/hiro/win/{listbox.h => listbox.hpp} | 1 + src/lib/hiro/win/menucheckitem.cpp | 17 + .../{menucheckitem.h => menucheckitem.hpp} | 4 + src/lib/hiro/win/menucontrol.cpp | 11 +- .../win/{menucontrol.h => menucontrol.hpp} | 6 +- src/lib/hiro/win/menugroup.cpp | 17 + .../hiro/win/{menugroup.h => menugroup.hpp} | 4 + src/lib/hiro/win/menuitem.cpp | 17 + src/lib/hiro/win/{menuitem.h => menuitem.hpp} | 4 + src/lib/hiro/win/menuradioitem.cpp | 17 + .../{menuradioitem.h => menuradioitem.hpp} | 4 + src/lib/hiro/win/menuseparator.cpp | 17 + .../{menuseparator.h => menuseparator.hpp} | 4 + src/lib/hiro/win/port.cpp | 40 ++ .../win/{progressbar.h => progressbar.hpp} | 0 src/lib/hiro/win/{radiobox.h => radiobox.hpp} | 0 src/lib/hiro/win/{slider.h => slider.hpp} | 0 src/lib/hiro/win/{widget.h => widget.hpp} | 0 src/lib/hiro/win/{window.h => window.hpp} | 0 src/lib/libfilter/libfilter.cpp | 2 +- .../libfilter/{libfilter.h => libfilter.hpp} | 0 src/lib/nall/algorithm.hpp | 6 + src/lib/nall/array.hpp | 4 +- src/lib/nall/endian.hpp | 38 ++ src/lib/nall/input.hpp | 106 ++-- src/lib/nall/sort.hpp | 90 +-- src/lib/nall/string/math.cpp | 46 +- src/lib/nall/utility.hpp | 6 + src/lib/nall/vector.hpp | 66 ++- src/lib/ruby/{audio.h => audio.hpp} | 0 src/lib/ruby/audio/alsa.cpp | 4 +- src/lib/ruby/audio/{alsa.h => alsa.hpp} | 0 src/lib/ruby/audio/ao.cpp | 4 +- src/lib/ruby/audio/{ao.h => ao.hpp} | 0 src/lib/ruby/audio/directsound.cpp | 4 +- .../audio/{directsound.h => directsound.hpp} | 0 src/lib/ruby/audio/openal.cpp | 4 +- src/lib/ruby/audio/{openal.h => openal.hpp} | 0 src/lib/ruby/audio/oss.cpp | 4 +- src/lib/ruby/audio/{oss.h => oss.hpp} | 0 src/lib/ruby/{input.h => input.hpp} | 9 +- src/lib/ruby/input/directinput.cpp | 474 ++++++++-------- .../input/{directinput.h => directinput.hpp} | 7 +- src/lib/ruby/input/sdl.cpp | 517 ++++++++++-------- src/lib/ruby/input/{sdl.h => sdl.hpp} | 7 +- src/lib/ruby/input/x.cpp | 234 ++++---- src/lib/ruby/input/{x.h => x.hpp} | 5 +- src/lib/ruby/ruby.cpp | 10 +- src/lib/ruby/{ruby.h => ruby.hpp} | 17 +- src/lib/ruby/{video.h => video.hpp} | 0 src/lib/ruby/video/direct3d.cpp | 4 +- .../ruby/video/{direct3d.h => direct3d.hpp} | 0 src/lib/ruby/video/directdraw.cpp | 4 +- .../video/{directdraw.h => directdraw.hpp} | 0 src/lib/ruby/video/gdi.cpp | 4 +- src/lib/ruby/video/{gdi.h => gdi.hpp} | 0 src/lib/ruby/video/glx.cpp | 4 +- src/lib/ruby/video/{glx.h => glx.hpp} | 0 src/lib/ruby/video/sdl.cpp | 6 +- src/lib/ruby/video/{sdl.h => sdl.hpp} | 0 src/lib/ruby/video/wgl.cpp | 4 +- src/lib/ruby/video/{wgl.h => wgl.hpp} | 0 src/lib/ruby/video/xv.cpp | 4 +- src/lib/ruby/video/{xv.h => xv.hpp} | 0 src/lib/sync.bat | 11 + src/lib/sync.sh | 1 + src/memory/smemory/smemory.cpp | 2 - src/ppu/bppu/bppu.cpp | 6 +- src/reader/filereader.cpp | 89 +-- src/reader/gzreader.cpp | 156 +++--- src/reader/gzreader.h | 3 +- src/reader/jmareader.cpp | 66 ++- src/reader/jmareader.h | 12 +- src/reader/zipreader.cpp | 14 +- src/reader/zipreader.h | 7 +- src/reader/zlib/ioapi.c | 18 +- src/snes/audio/audio.h | 18 +- src/snes/input/input.cpp | 321 ++++++++++- src/snes/input/input.h | 147 +++-- src/snes/interface/interface.h | 5 +- src/snes/snes.cpp | 307 ++++++----- src/snes/snes.h | 10 +- src/snes/video/video.cpp | 137 +++-- src/snes/video/video.h | 31 +- src/ui/base/main.cpp | 240 ++++---- src/ui/base/main.h | 38 +- src/ui/config.cpp | 47 +- src/ui/event.cpp | 430 ++++++++------- src/ui/event.h | 21 +- src/ui/inputdevices.cpp | 379 +++++++++++++ src/ui/inputmanager.cpp | 235 +++----- src/ui/inputui.cpp | 35 ++ src/ui/interface.cpp | 7 +- src/ui/loader/bsxloader.cpp | 4 +- src/ui/loader/stloader.cpp | 6 +- src/ui/main.cpp | 56 +- src/ui/main.h | 3 +- src/ui/settings/advanced.cpp | 7 + src/ui/settings/audiosettings.cpp | 8 + src/ui/settings/audiosettings.h | 2 + src/ui/settings/cheateditor.cpp | 96 +++- src/ui/settings/cheateditor.h | 19 +- src/ui/settings/driverselect.cpp | 8 +- src/ui/settings/driverselect.h | 1 + src/ui/settings/inputconfig.cpp | 264 +++++---- src/ui/settings/inputconfig.h | 32 +- src/ui/settings/inputconfig.txt | 360 ++++++++++++ src/ui/settings/pathsettings.cpp | 8 +- src/ui/settings/videosettings.cpp | 26 +- src/ui/status.cpp | 4 +- src/ui/ui.cpp | 8 +- 172 files changed, 4696 insertions(+), 2935 deletions(-) create mode 100644 src/cart/cart_load.cpp rename src/lib/hiro/gtk/{button.h => button.hpp} (100%) rename src/lib/hiro/gtk/{canvas.h => canvas.hpp} (100%) rename src/lib/hiro/gtk/{checkbox.h => checkbox.hpp} (100%) rename src/lib/hiro/gtk/{combobox.h => combobox.hpp} (100%) rename src/lib/hiro/gtk/{editbox.h => editbox.hpp} (100%) rename src/lib/hiro/gtk/{formcontrol.h => formcontrol.hpp} (100%) rename src/lib/hiro/gtk/{frame.h => frame.hpp} (100%) rename src/lib/hiro/gtk/{hiro.h => hiro.hpp} (62%) rename src/lib/hiro/gtk/{label.h => label.hpp} (100%) rename src/lib/hiro/gtk/{listbox.h => listbox.hpp} (100%) rename src/lib/hiro/gtk/{menucheckitem.h => menucheckitem.hpp} (100%) rename src/lib/hiro/gtk/{menucontrol.h => menucontrol.hpp} (100%) rename src/lib/hiro/gtk/{menugroup.h => menugroup.hpp} (100%) rename src/lib/hiro/gtk/{menuitem.h => menuitem.hpp} (100%) rename src/lib/hiro/gtk/{menuradioitem.h => menuradioitem.hpp} (100%) rename src/lib/hiro/gtk/{menuseparator.h => menuseparator.hpp} (100%) create mode 100644 src/lib/hiro/gtk/port.cpp rename src/lib/hiro/gtk/{progressbar.h => progressbar.hpp} (100%) rename src/lib/hiro/gtk/{radiobox.h => radiobox.hpp} (100%) rename src/lib/hiro/gtk/{slider.h => slider.hpp} (100%) rename src/lib/hiro/gtk/{widget.h => widget.hpp} (100%) rename src/lib/hiro/gtk/{window.h => window.hpp} (100%) rename src/lib/hiro/{hiro.h => hiro.hpp} (97%) rename src/lib/hiro/win/{button.h => button.hpp} (100%) rename src/lib/hiro/win/{canvas.h => canvas.hpp} (100%) rename src/lib/hiro/win/{checkbox.h => checkbox.hpp} (100%) rename src/lib/hiro/win/{combobox.h => combobox.hpp} (100%) rename src/lib/hiro/win/{editbox.h => editbox.hpp} (100%) rename src/lib/hiro/win/{formcontrol.h => formcontrol.hpp} (100%) rename src/lib/hiro/win/{frame.h => frame.hpp} (100%) rename src/lib/hiro/win/{hiro.h => hiro.hpp} (62%) rename src/lib/hiro/win/{label.h => label.hpp} (100%) rename src/lib/hiro/win/{listbox.h => listbox.hpp} (96%) rename src/lib/hiro/win/{menucheckitem.h => menucheckitem.hpp} (77%) rename src/lib/hiro/win/{menucontrol.h => menucontrol.hpp} (66%) rename src/lib/hiro/win/{menugroup.h => menugroup.hpp} (75%) rename src/lib/hiro/win/{menuitem.h => menuitem.hpp} (67%) rename src/lib/hiro/win/{menuradioitem.h => menuradioitem.hpp} (81%) rename src/lib/hiro/win/{menuseparator.h => menuseparator.hpp} (66%) create mode 100644 src/lib/hiro/win/port.cpp rename src/lib/hiro/win/{progressbar.h => progressbar.hpp} (100%) rename src/lib/hiro/win/{radiobox.h => radiobox.hpp} (100%) rename src/lib/hiro/win/{slider.h => slider.hpp} (100%) rename src/lib/hiro/win/{widget.h => widget.hpp} (100%) rename src/lib/hiro/win/{window.h => window.hpp} (100%) rename src/lib/libfilter/{libfilter.h => libfilter.hpp} (100%) create mode 100644 src/lib/nall/endian.hpp rename src/lib/ruby/{audio.h => audio.hpp} (100%) rename src/lib/ruby/audio/{alsa.h => alsa.hpp} (100%) rename src/lib/ruby/audio/{ao.h => ao.hpp} (100%) rename src/lib/ruby/audio/{directsound.h => directsound.hpp} (100%) rename src/lib/ruby/audio/{openal.h => openal.hpp} (100%) rename src/lib/ruby/audio/{oss.h => oss.hpp} (100%) rename src/lib/ruby/{input.h => input.hpp} (62%) rename src/lib/ruby/input/{directinput.h => directinput.hpp} (67%) rename src/lib/ruby/input/{sdl.h => sdl.hpp} (72%) rename src/lib/ruby/input/{x.h => x.hpp} (72%) rename src/lib/ruby/{ruby.h => ruby.hpp} (91%) rename src/lib/ruby/{video.h => video.hpp} (100%) rename src/lib/ruby/video/{direct3d.h => direct3d.hpp} (100%) rename src/lib/ruby/video/{directdraw.h => directdraw.hpp} (100%) rename src/lib/ruby/video/{gdi.h => gdi.hpp} (100%) rename src/lib/ruby/video/{glx.h => glx.hpp} (100%) rename src/lib/ruby/video/{sdl.h => sdl.hpp} (100%) rename src/lib/ruby/video/{wgl.h => wgl.hpp} (100%) rename src/lib/ruby/video/{xv.h => xv.hpp} (100%) create mode 100644 src/lib/sync.bat create mode 100644 src/ui/inputdevices.cpp create mode 100644 src/ui/inputui.cpp create mode 100644 src/ui/settings/inputconfig.txt 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() {