From a4629e1f6462c86390cfa58c5c0eb414143992b6 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Tue, 6 Jun 2017 23:44:40 +1000 Subject: [PATCH] Update to v102r21 release. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit byuu says: Changelog: - GBA: fixed WININ2 reads, BG3PB writes [Jonas Quinn] - R65816: added support for yielding/resuming from WAI/STP¹ - SFC: removed status.dmaCounter functionality (also fixes possible TAS desync issue) - tomoko: added support for combinatorial inputs [hex\_usr\]² - nall: fixed missing return value from Arithmetic::operator-- [Hendricks266] Now would be the time to start looking for major regressions with the new GBA PPU renderer, I suppose ... ¹: this doesn't matter for the master thread (SNES CPU), but is important for slave threads (SNES SA1). If you try to save a state and the SA1 is inside of a WAI instruction, it will get stuck there forever. This was causing attempts to create a save state in Super Bomberman - Panic Bomber W to deadlock the emulator and crash it. This is now finally fixed. Note that I still need to implement similar functionality into the Mega Drive 68K and Z80 cores. They still have the possibility of deadlocking. The SNES implementation was more a dry-run test for this new functionality. This possible crashing bug in the Mega Drive core is the major blocking bug for a new official release. ²: many, many thanks to hex\_usr for coming up with a really nice design. I mostly implemented it the exact same way, but with a few tiny differences that don't really matter (display " and ", " or " instead of " & ", " | " in the input settings windows; append → bind; assignmentName changed to displayName.) The actual functionality is identical to the old higan v094 and earlier builds. Emulated digital inputs let you combine multiple possible keys to trigger the buttons. This is OR logic, so you can map to eg keyboard.up OR gamepad.up for instance. Emulated analog inputs always sum together. Emulated rumble outputs will cause all mapped devices to rumble, which is probably not at all useful but whatever. Hotkeys use AND logic, so you have to press every key mapped to trigger them. Useful for eg Ctrl+F to trigger fullscreen. Obviously, there are cases where OR logic would be nice for hotkeys, too. Eg if you want both F11 and your gamepad's guide button to trigger the fullscreen toggle. Unfortunately, this isn't supported, and likely won't ever be in tomoko. Something I might consider is a throw switch in the configuration file to swap between AND or OR logic for hotkeys, but I'm not going to allow construction of mappings like "(Keyboard.Ctrl and Keyboard.F) or Gamepad.Guide", as that's just too complicated to code, and too complicated to make a nice GUI to set up the mappings for. --- higan/emulator/emulator.hpp | 2 +- higan/gba/ppu/io.cpp | 4 +- higan/processor/r65816/instructions-misc.cpp | 9 +- higan/processor/r65816/r65816.hpp | 5 +- higan/processor/r65816/registers.hpp | 1 + higan/processor/r65816/serialization.cpp | 1 + higan/sfc/coprocessor/sa1/sa1.cpp | 8 + higan/sfc/coprocessor/sa1/sa1.hpp | 1 + higan/sfc/cpu/cpu.cpp | 6 +- higan/sfc/cpu/cpu.hpp | 2 +- higan/sfc/cpu/serialization.cpp | 1 - higan/sfc/cpu/timing.cpp | 13 +- higan/sfc/ppu/counter/counter-inline.hpp | 20 +-- higan/target-tomoko/input/input.cpp | 169 ++++++++++++------- higan/target-tomoko/input/input.hpp | 21 ++- higan/target-tomoko/settings/hotkeys.cpp | 5 +- higan/target-tomoko/settings/input.cpp | 5 +- nall/arithmetic/natural.hpp | 2 +- 18 files changed, 166 insertions(+), 109 deletions(-) diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 7e71519f..1c01c7d7 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -12,7 +12,7 @@ using namespace nall; namespace Emulator { static const string Name = "higan"; - static const string Version = "102.20"; + static const string Version = "102.21"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "http://byuu.org/"; diff --git a/higan/gba/ppu/io.cpp b/higan/gba/ppu/io.cpp index e3173e9f..b3ace9ee 100644 --- a/higan/gba/ppu/io.cpp +++ b/higan/gba/ppu/io.cpp @@ -68,7 +68,7 @@ auto PPU::readIO(uint32 addr) -> uint8 { case 0x0400'004a: return window3.io.active[BG0] << 0 | window3.io.active[BG1] << 1 | window3.io.active[BG2] << 2 | window3.io.active[BG3] << 3 | window3.io.active[OBJ] << 4 | window3.io.active[SFX] << 5; //WININ2 - case 0x0400'004b: return window2.io.active[BG0] << 0 | window2.io.active[BG1] << 1 | window2.io.active[BG2] << 3 | window2.io.active[BG3] << 3 | window2.io.active[OBJ] << 4 | window2.io.active[SFX] << 5; + case 0x0400'004b: return window2.io.active[BG0] << 0 | window2.io.active[BG1] << 1 | window2.io.active[BG2] << 2 | window2.io.active[BG3] << 3 | window2.io.active[OBJ] << 4 | window2.io.active[SFX] << 5; //MOSAIC (write-only) @@ -266,7 +266,7 @@ auto PPU::writeIO(uint32 addr, uint8 data) -> void { //BG3PB case 0x0400'0032: bg3.io.pb.byte(0) = data; return; - case 0x0400'0033: bg3.io.pb.byte(0) = data; return; + case 0x0400'0033: bg3.io.pb.byte(1) = data; return; //BG3PC case 0x0400'0034: bg3.io.pc.byte(0) = data; return; diff --git a/higan/processor/r65816/instructions-misc.cpp b/higan/processor/r65816/instructions-misc.cpp index 58b383f4..ab45999b 100644 --- a/higan/processor/r65816/instructions-misc.cpp +++ b/higan/processor/r65816/instructions-misc.cpp @@ -53,15 +53,16 @@ L r.pc.h = readLong(vector + 1); r.pc.b = 0x00; } -auto R65816::op_stp() { - while(r.wai = true) { +auto R65816::op_stp() -> void { + r.stp = true; + while(r.stp && !synchronizing()) { L idle(); } } -auto R65816::op_wai() { +auto R65816::op_wai() -> void { r.wai = true; - while(r.wai) { + while(r.wai && !synchronizing()) { L idle(); } idle(); diff --git a/higan/processor/r65816/r65816.hpp b/higan/processor/r65816/r65816.hpp index c1827a69..d3eb2868 100644 --- a/higan/processor/r65816/r65816.hpp +++ b/higan/processor/r65816/r65816.hpp @@ -19,6 +19,7 @@ struct R65816 { virtual auto lastCycle() -> void = 0; virtual auto interruptPending() const -> bool = 0; virtual auto interrupt() -> void; + virtual auto synchronizing() const -> bool = 0; virtual auto readDisassembler(uint24 addr) -> uint8 { return 0; } @@ -174,8 +175,8 @@ struct R65816 { auto op_move_b(int adjust); auto op_move_w(int adjust); auto op_interrupt(uint16); - auto op_stp(); - auto op_wai(); + auto op_stp() -> void; + auto op_wai() -> void; auto op_xce(); auto op_set_flag(uint bit); auto op_clear_flag(uint bit); diff --git a/higan/processor/r65816/registers.hpp b/higan/processor/r65816/registers.hpp index 2d9baff8..369e0101 100644 --- a/higan/processor/r65816/registers.hpp +++ b/higan/processor/r65816/registers.hpp @@ -75,6 +75,7 @@ struct Registers { bool irq = false; //IRQ pin (0 = low, 1 = trigger) bool wai = false; //raised during wai, cleared after interrupt triggered + bool stp = false; //raised during stp, never cleared uint8 mdr = 0; //memory data register uint16 vector = 0; //interrupt vector address }; diff --git a/higan/processor/r65816/serialization.cpp b/higan/processor/r65816/serialization.cpp index d3f20a92..fd2d1730 100644 --- a/higan/processor/r65816/serialization.cpp +++ b/higan/processor/r65816/serialization.cpp @@ -14,6 +14,7 @@ auto R65816::serialize(serializer& s) -> void { s.integer(r.e); s.integer(r.irq); s.integer(r.wai); + s.integer(r.stp); s.integer(r.mdr); s.integer(r.vector); diff --git a/higan/sfc/coprocessor/sa1/sa1.cpp b/higan/sfc/coprocessor/sa1/sa1.cpp index 0593862f..44599c07 100644 --- a/higan/sfc/coprocessor/sa1/sa1.cpp +++ b/higan/sfc/coprocessor/sa1/sa1.cpp @@ -14,6 +14,9 @@ auto SA1::Enter() -> void { } auto SA1::main() -> void { + if(r.wai) return op_wai(); + if(r.stp) return op_stp(); + if(mmio.sa1_rdyb || mmio.sa1_resb) { //SA-1 co-processor is asleep tick(); @@ -75,6 +78,10 @@ auto SA1::interruptPending() const -> bool { return status.interruptPending; } +auto SA1::synchronizing() const -> bool { + return scheduler.synchronizing(); +} + auto SA1::tick() -> void { step(2); if(++status.counter == 0) synchronize(cpu); @@ -142,6 +149,7 @@ auto SA1::power() -> void { r.e = 1; r.mdr = 0x00; r.wai = false; + r.stp = false; r.vector = 0x0000; status.counter = 0; diff --git a/higan/sfc/coprocessor/sa1/sa1.hpp b/higan/sfc/coprocessor/sa1/sa1.hpp index a056b9be..45683641 100644 --- a/higan/sfc/coprocessor/sa1/sa1.hpp +++ b/higan/sfc/coprocessor/sa1/sa1.hpp @@ -8,6 +8,7 @@ struct SA1 : Processor::R65816, Thread { alwaysinline auto triggerIRQ() -> void; alwaysinline auto lastCycle() -> void override; alwaysinline auto interruptPending() const -> bool override; + auto synchronizing() const -> bool override; auto init() -> void; auto load() -> void; diff --git a/higan/sfc/cpu/cpu.cpp b/higan/sfc/cpu/cpu.cpp index 60bdd169..4a592702 100644 --- a/higan/sfc/cpu/cpu.cpp +++ b/higan/sfc/cpu/cpu.cpp @@ -13,6 +13,7 @@ CPU cpu; auto CPU::interruptPending() const -> bool { return status.interruptPending; } auto CPU::pio() const -> uint8 { return io.pio; } auto CPU::joylatch() const -> bool { return io.joypadStrobeLatch; } +auto CPU::synchronizing() const -> bool { return scheduler.synchronizing(); } CPU::CPU() { PPUcounter::scanline = {&CPU::scanline, this}; @@ -23,6 +24,9 @@ auto CPU::Enter() -> void { } auto CPU::main() -> void { + if(r.wai) return op_wai(); + if(r.stp) return op_stp(); + if(status.interruptPending) { status.interruptPending = false; if(status.nmiPending) { @@ -93,6 +97,7 @@ auto CPU::power() -> void { r.e = 1; r.mdr = 0x00; r.wai = false; + r.stp = false; r.vector = 0xfffc; //reset vector address //DMA @@ -209,7 +214,6 @@ auto CPU::power() -> void { status.interruptPending = true; status.dmaActive = false; - status.dmaCounter = 0; status.dmaClocks = 0; status.dmaPending = false; status.hdmaPending = false; diff --git a/higan/sfc/cpu/cpu.hpp b/higan/sfc/cpu/cpu.hpp index 49859488..4ed0424e 100644 --- a/higan/sfc/cpu/cpu.hpp +++ b/higan/sfc/cpu/cpu.hpp @@ -2,6 +2,7 @@ struct CPU : Processor::R65816, Thread, PPUcounter { auto interruptPending() const -> bool override; auto pio() const -> uint8; auto joylatch() const -> bool; + auto synchronizing() const -> bool override; CPU(); @@ -122,7 +123,6 @@ private: //DMA bool dmaActive; - uint dmaCounter; uint dmaClocks; bool dmaPending; bool hdmaPending; diff --git a/higan/sfc/cpu/serialization.cpp b/higan/sfc/cpu/serialization.cpp index 27e84ea4..c3814482 100644 --- a/higan/sfc/cpu/serialization.cpp +++ b/higan/sfc/cpu/serialization.cpp @@ -40,7 +40,6 @@ auto CPU::serialize(serializer& s) -> void { s.integer(status.resetPending); s.integer(status.dmaActive); - s.integer(status.dmaCounter); s.integer(status.dmaClocks); s.integer(status.dmaPending); s.integer(status.hdmaPending); diff --git a/higan/sfc/cpu/timing.cpp b/higan/sfc/cpu/timing.cpp index 6449d8a2..2676802c 100644 --- a/higan/sfc/cpu/timing.cpp +++ b/higan/sfc/cpu/timing.cpp @@ -1,19 +1,13 @@ -auto CPU::dmaCounter() const -> uint { - return clockCounter & 7; -//return (status.dmaCounter + hcounter()) & 7; -} - -auto CPU::joypadCounter() const -> uint { - return clockCounter & 255; -} +auto CPU::dmaCounter() const -> uint { return clockCounter & 7; } +auto CPU::joypadCounter() const -> uint { return clockCounter & 255; } auto CPU::step(uint clocks) -> void { status.irqLock = false; uint ticks = clocks >> 1; while(ticks--) { + clockCounter += 2; tick(); if(hcounter() & 2) pollInterrupts(); - clockCounter += 2; if(joypadCounter() == 0) joypadEdge(); } @@ -37,7 +31,6 @@ auto CPU::step(uint clocks) -> void { //called by ppu.tick() when Hcounter=0 auto CPU::scanline() -> void { - status.dmaCounter = (status.dmaCounter + status.lineClocks) & 7; status.lineClocks = lineclocks(); //forcefully sync S-CPU to other processors, in case chips are not communicating diff --git a/higan/sfc/ppu/counter/counter-inline.hpp b/higan/sfc/ppu/counter/counter-inline.hpp index 711bf89b..bf43cca1 100644 --- a/higan/sfc/ppu/counter/counter-inline.hpp +++ b/higan/sfc/ppu/counter/counter-inline.hpp @@ -2,7 +2,7 @@ //keeps track of previous counter positions in history table auto PPUcounter::tick() -> void { status.hcounter += 2; //increment by smallest unit of time - if(status.hcounter >= 1360 && status.hcounter == lineclocks()) { + if(status.hcounter == lineclocks()) { status.hcounter = 0; vcounterTick(); } @@ -27,12 +27,12 @@ auto PPUcounter::tick(uint clocks) -> void { auto PPUcounter::vcounterTick() -> void { if(++status.vcounter == 128) status.interlace = ppu.interlace(); - if((system.region() == System::Region::NTSC && status.interlace == false && status.vcounter == 262) - || (system.region() == System::Region::NTSC && status.interlace == true && status.vcounter == 263) - || (system.region() == System::Region::NTSC && status.interlace == true && status.vcounter == 262 && status.field == 1) - || (system.region() == System::Region::PAL && status.interlace == false && status.vcounter == 312) - || (system.region() == System::Region::PAL && status.interlace == true && status.vcounter == 313) - || (system.region() == System::Region::PAL && status.interlace == true && status.vcounter == 312 && status.field == 1) + if((system.region() == System::Region::NTSC && status.interlace == 0 && status.vcounter == 262) + || (system.region() == System::Region::NTSC && status.interlace == 1 && status.vcounter == 263) + || (system.region() == System::Region::NTSC && status.interlace == 1 && status.vcounter == 262 && status.field == 1) + || (system.region() == System::Region::PAL && status.interlace == 0 && status.vcounter == 312) + || (system.region() == System::Region::PAL && status.interlace == 1 && status.vcounter == 313) + || (system.region() == System::Region::PAL && status.interlace == 1 && status.vcounter == 312 && status.field == 1) ) { status.vcounter = 0; status.field = !status.field; @@ -58,7 +58,7 @@ auto PPUcounter::hcounter(uint offset) const -> uint16 { return history.hcounter //dot 327 range = {1310, 1312, 1314} auto PPUcounter::hdot() const -> uint16 { - if(system.region() == System::Region::NTSC && status.interlace == false && vcounter() == 240 && field() == 1) { + if(system.region() == System::Region::NTSC && status.interlace == 0 && vcounter() == 240 && field() == 1) { return (hcounter() >> 2); } else { return (hcounter() - ((hcounter() > 1292) << 1) - ((hcounter() > 1310) << 1)) >> 2; @@ -66,12 +66,12 @@ auto PPUcounter::hdot() const -> uint16 { } auto PPUcounter::lineclocks() const -> uint16 { - if(system.region() == System::Region::NTSC && status.interlace == false && vcounter() == 240 && field() == 1) return 1360; + if(system.region() == System::Region::NTSC && status.interlace == 0 && vcounter() == 240 && field() == 1) return 1360; return 1364; } auto PPUcounter::reset() -> void { - status.interlace = false; + status.interlace = 0; status.field = 0; status.vcounter = 0; status.hcounter = 0; diff --git a/higan/target-tomoko/input/input.cpp b/higan/target-tomoko/input/input.cpp index b8fcb68b..eb0a339a 100644 --- a/higan/target-tomoko/input/input.cpp +++ b/higan/target-tomoko/input/input.cpp @@ -2,30 +2,56 @@ #include "hotkeys.cpp" unique_pointer inputManager; +//build mappings list from assignment string auto InputMapping::bind() -> void { - auto token = assignment.split("/"); - if(token.size() < 3) return unbind(); - uint64 id = token[0].natural(); - uint group = token[1].natural(); - uint input = token[2].natural(); - string qualifier = token(3, "None"); + mappings.reset(); - for(auto& device : inputManager->devices) { - if(id != device->id()) continue; + auto list = assignment.split(logic() == Logic::AND ? "&" : "|"); + for(auto& item : list) { + auto token = item.split("/"); + if(token.size() < 3) continue; //skip invalid mappings - this->device = device; - this->group = group; - this->input = input; - this->qualifier = Qualifier::None; - if(qualifier == "Lo") this->qualifier = Qualifier::Lo; - if(qualifier == "Hi") this->qualifier = Qualifier::Hi; - if(qualifier == "Rumble") this->qualifier = Qualifier::Rumble; - break; + uint64 id = token[0].natural(); + uint group = token[1].natural(); + uint input = token[2].natural(); + string qualifier = token(3, "None"); + + Mapping mapping; + for(auto& device : inputManager->devices) { + if(id != device->id()) continue; + + mapping.device = device; + mapping.group = group; + mapping.input = input; + mapping.qualifier = Qualifier::None; + if(qualifier == "Lo") mapping.qualifier = Qualifier::Lo; + if(qualifier == "Hi") mapping.qualifier = Qualifier::Hi; + if(qualifier == "Rumble") mapping.qualifier = Qualifier::Rumble; + break; + } + + if(!mapping.device) continue; + mappings.append(mapping); } settings[path].setValue(assignment); } +//append new mapping to mappings list +auto InputMapping::bind(string mapping) -> void { + auto list = assignment.split(logic() == Logic::AND ? "&" : "|"); + if(list.find(mapping)) return; //already in the mappings list + if(!assignment || assignment == "None") { + //create new mapping + assignment = mapping; + } else { + //add additional mapping + assignment.append(logic() == Logic::AND ? "&" : "|"); + assignment.append(mapping); + } + bind(); +} + auto InputMapping::bind(shared_pointer device, uint group, uint input, int16 oldValue, int16 newValue) -> bool { if(device->isNull() || (device->isKeyboard() && device->group(group).input(input).name() == "Escape")) { return unbind(), true; @@ -38,8 +64,7 @@ auto InputMapping::bind(shared_pointer device, uint group, uint inp || (device->isMouse() && group == HID::Mouse::GroupID::Button) || (device->isJoypad() && group == HID::Joypad::GroupID::Button)) { if(newValue) { - this->assignment = encoding; - return bind(), true; + return bind(encoding), true; } } @@ -47,13 +72,11 @@ auto InputMapping::bind(shared_pointer device, uint group, uint inp || (device->isJoypad() && group == HID::Joypad::GroupID::Hat) || (device->isJoypad() && group == HID::Joypad::GroupID::Trigger)) { if(newValue < -16384 && group != HID::Joypad::GroupID::Trigger) { //triggers are always hi - this->assignment = {encoding, "/Lo"}; - return bind(), true; + return bind({encoding, "/Lo"}), true; } if(newValue > +16384) { - this->assignment = {encoding, "/Hi"}; - return bind(), true; + return bind({encoding, "/Hi"}), true; } } } @@ -63,8 +86,7 @@ auto InputMapping::bind(shared_pointer device, uint group, uint inp || (device->isJoypad() && group == HID::Joypad::GroupID::Axis) || (device->isJoypad() && group == HID::Joypad::GroupID::Hat)) { if(newValue < -16384 || newValue > +16384) { - this->assignment = encoding; - return bind(), true; + return bind(encoding), true; } } } @@ -72,8 +94,7 @@ auto InputMapping::bind(shared_pointer device, uint group, uint inp if(isRumble()) { if(device->isJoypad() && group == HID::Joypad::GroupID::Button) { if(newValue) { - this->assignment = {encoding, "/Rumble"}; - return bind(), true; + return bind({encoding, "/Rumble"}), true; } } } @@ -82,62 +103,86 @@ auto InputMapping::bind(shared_pointer device, uint group, uint inp } auto InputMapping::poll() -> int16 { - if(!device) return 0; - auto value = device->group(group).input(input).value(); + if(!mappings) return 0; if(isDigital()) { - if(device->isKeyboard() && group == HID::Keyboard::GroupID::Button) return value != 0; - if(device->isMouse() && group == HID::Mouse::GroupID::Button) return value != 0; - if(device->isJoypad() && group == HID::Joypad::GroupID::Button) return value != 0; - if((device->isJoypad() && group == HID::Joypad::GroupID::Axis) - || (device->isJoypad() && group == HID::Joypad::GroupID::Hat) - || (device->isJoypad() && group == HID::Joypad::GroupID::Trigger)) { - if(qualifier == Qualifier::Lo) return value < -16384; - if(qualifier == Qualifier::Hi) return value > +16384; + bool result = logic() == Logic::AND ? 1 : 0; + + for(auto& mapping : mappings) { + auto value = mapping.device->group(mapping.group).input(mapping.input).value(); + bool output = logic() == Logic::AND ? 0 : 1; + + if(mapping.device->isKeyboard() && mapping.group == HID::Keyboard::GroupID::Button) output = value != 0; + if(mapping.device->isMouse() && mapping.group == HID::Mouse::GroupID::Button) output = value != 0; + if(mapping.device->isJoypad() && mapping.group == HID::Joypad::GroupID::Button) output = value != 0; + if((mapping.device->isJoypad() && mapping.group == HID::Joypad::GroupID::Axis) + || (mapping.device->isJoypad() && mapping.group == HID::Joypad::GroupID::Hat) + || (mapping.device->isJoypad() && mapping.group == HID::Joypad::GroupID::Trigger)) { + if(mapping.qualifier == Qualifier::Lo) output = value < -16384; + if(mapping.qualifier == Qualifier::Hi) output = value > +16384; + } + + if(logic() == Logic::AND) result &= output; + if(logic() == Logic::OR ) result |= output; } + + return result; } if(isAnalog()) { - if(device->isMouse() && group == HID::Mouse::GroupID::Axis) return value; - if(device->isJoypad() && group == HID::Joypad::GroupID::Axis) return value >> 8; - if(device->isJoypad() && group == HID::Joypad::GroupID::Hat) return value < 0 ? -1 : value > 0 ? +1 : 0; + int16 result = 0; + + for(auto& mapping : mappings) { + auto value = mapping.device->group(mapping.group).input(mapping.input).value(); + + //logic does not apply to analog inputs ... always combinatorial + if(mapping.device->isMouse() && mapping.group == HID::Mouse::GroupID::Axis) result += value; + if(mapping.device->isJoypad() && mapping.group == HID::Joypad::GroupID::Axis) result += value >> 8; + if(mapping.device->isJoypad() && mapping.group == HID::Joypad::GroupID::Hat) result += value < 0 ? -1 : value > 0 ? +1 : 0; + } + + return result; } return 0; } auto InputMapping::rumble(bool enable) -> void { - if(!device) return; - ::input->rumble(device->id(), enable); + if(!mappings) return; + for(auto& mapping : mappings) { + ::input->rumble(mapping.device->id(), enable); + } } auto InputMapping::unbind() -> void { + mappings.reset(); assignment = "None"; - device = nullptr; - group = 0; - input = 0; - qualifier = Qualifier::None; settings[path].setValue(assignment); } -auto InputMapping::assignmentName() -> string { - if(!device) return "None"; - string path; - path.append(device->name()); - if(device->name() != "Keyboard") { - //keyboards only have one group; no need to append group name - path.append(".", device->group(group).name()); - } - path.append(".", device->group(group).input(input).name()); - if(qualifier == Qualifier::Lo) path.append(".Lo"); - if(qualifier == Qualifier::Hi) path.append(".Hi"); - if(qualifier == Qualifier::Rumble) path.append(".Rumble"); - return path; -} +//create a human-readable string from mappings list for display in the user interface +auto InputMapping::displayName() -> string { + if(!mappings) return "None"; -auto InputMapping::deviceName() -> string { - if(!device) return ""; - return hex(device->id()); + string path; + for(auto& mapping : mappings) { + path.append(mapping.device->name()); + if(mapping.device->name() != "Keyboard" && mapping.device->name() != "Mouse") { + //show device IDs to distinguish between multiple joypads + path.append("(", hex(mapping.device->id()), ")"); + } + if(mapping.device->name() != "Keyboard") { + //keyboards only have one group; no need to append group name + path.append(".", mapping.device->group(mapping.group).name()); + } + path.append(".", mapping.device->group(mapping.group).input(mapping.input).name()); + if(mapping.qualifier == Qualifier::Lo) path.append(".Lo"); + if(mapping.qualifier == Qualifier::Hi) path.append(".Hi"); + if(mapping.qualifier == Qualifier::Rumble) path.append(".Rumble"); + path.append(logic() == Logic::AND ? " and " : " or "); + } + + return path.trimRight(logic() == Logic::AND ? " and " : " or ", 1L); } // diff --git a/higan/target-tomoko/input/input.hpp b/higan/target-tomoko/input/input.hpp index da44abd4..4a61d258 100644 --- a/higan/target-tomoko/input/input.hpp +++ b/higan/target-tomoko/input/input.hpp @@ -1,5 +1,6 @@ struct InputMapping { auto bind() -> void; + auto bind(string mapping) -> void; auto bind(shared_pointer device, uint group, uint input, int16 oldValue, int16 newValue) -> bool; auto poll() -> int16; auto rumble(bool enable) -> void; @@ -9,20 +10,28 @@ struct InputMapping { auto isAnalog() const -> bool { return type == 1; } auto isRumble() const -> bool { return type == 2; } - auto assignmentName() -> string; - auto deviceName() -> string; + auto displayName() -> string; string path; //configuration file key path string name; //input name (human readable) uint type = 0; string assignment = "None"; - shared_pointer device; - uint group = 0; - uint input = 0; - enum class Qualifier : uint { None, Lo, Hi, Rumble } qualifier = Qualifier::None; + + enum class Logic : uint { AND, OR }; + enum class Qualifier : uint { None, Lo, Hi, Rumble }; + virtual auto logic() const -> Logic { return Logic::OR; } + struct Mapping { + shared_pointer device; + uint group = 0; + uint input = 0; + Qualifier qualifier = Qualifier::None; + }; + vector mappings; }; struct InputHotkey : InputMapping { + auto logic() const -> Logic override { return Logic::AND; } + function void> press; function void> release; diff --git a/higan/target-tomoko/settings/hotkeys.cpp b/higan/target-tomoko/settings/hotkeys.cpp index fc585a29..ef29f247 100644 --- a/higan/target-tomoko/settings/hotkeys.cpp +++ b/higan/target-tomoko/settings/hotkeys.cpp @@ -29,13 +29,11 @@ auto HotkeySettings::reloadMappings() -> void { mappingList.append(TableViewHeader().setVisible() .append(TableViewColumn().setText("Name")) .append(TableViewColumn().setText("Mapping").setExpandable()) - .append(TableViewColumn().setText("Device").setAlignment(1.0).setForegroundColor({0, 128, 0})) ); for(auto& hotkey : inputManager->hotkeys) { mappingList.append(TableViewItem() .append(TableViewCell().setText(hotkey->name)) .append(TableViewCell()) - .append(TableViewCell()) ); } mappingList.resizeColumns(); @@ -44,8 +42,7 @@ auto HotkeySettings::reloadMappings() -> void { auto HotkeySettings::refreshMappings() -> void { uint position = 0; for(auto& hotkey : inputManager->hotkeys) { - mappingList.item(position).cell(1).setText(hotkey->assignmentName()); - mappingList.item(position).cell(2).setText(hotkey->deviceName()); + mappingList.item(position).cell(1).setText(hotkey->displayName()); position++; } mappingList.resizeColumns(); diff --git a/higan/target-tomoko/settings/input.cpp b/higan/target-tomoko/settings/input.cpp index 038de2c1..b32b87b3 100644 --- a/higan/target-tomoko/settings/input.cpp +++ b/higan/target-tomoko/settings/input.cpp @@ -95,13 +95,11 @@ auto InputSettings::reloadMappings() -> void { mappingList.append(TableViewHeader().setVisible() .append(TableViewColumn().setText("Name")) .append(TableViewColumn().setText("Mapping").setExpandable()) - .append(TableViewColumn().setText("Device").setAlignment(1.0).setForegroundColor({0, 128, 0})) ); for(auto& mapping : activeDevice().mappings) { mappingList.append(TableViewItem() .append(TableViewCell().setText(mapping.name)) .append(TableViewCell()) - .append(TableViewCell()) ); } refreshMappings(); @@ -110,8 +108,7 @@ auto InputSettings::reloadMappings() -> void { auto InputSettings::refreshMappings() -> void { uint position = 0; for(auto& mapping : activeDevice().mappings) { - mappingList.item(position).cell(1).setText(mapping.assignmentName()); - mappingList.item(position).cell(2).setText(mapping.deviceName()); + mappingList.item(position).cell(1).setText(mapping.displayName()); position++; } mappingList.resizeColumns(); diff --git a/nall/arithmetic/natural.hpp b/nall/arithmetic/natural.hpp index a49527f4..98797653 100644 --- a/nall/arithmetic/natural.hpp +++ b/nall/arithmetic/natural.hpp @@ -24,7 +24,7 @@ struct Pair { auto operator!() const -> bool { return !(hi || lo); } auto operator++() -> Pair& { lo++; hi += lo == 0; return *this; } - auto operator--() -> Pair& { hi -= lo == 0; lo--; } + auto operator--() -> Pair& { hi -= lo == 0; lo--; return *this; } auto operator++(int) -> Pair { Pair r = *this; lo++; hi += lo == 0; return r; } auto operator--(int) -> Pair { Pair r = *this; hi -= lo == 0; lo--; return r; }