From 62edc065ad5734d5961e3d52d8f50e590011c67f Mon Sep 17 00:00:00 2001 From: Douglas Teles Date: Tue, 7 Jul 2026 18:31:41 -0300 Subject: [PATCH] inputs: single source of truth for the built-in gamepad configs Every platform pad needed three hand-maintained files (es_input.cfg, the RetroArch autoconfig and gamecontrollerdb.txt) and nothing checked they agreed; the Soysauce pad shipped without the SDL entry, so ports saw no controller. Add projects/ArchR/inputs: one JSON definition per pad plus gen-input-configs, which verifies the three artifacts against the defs (wired as a fail-fast gate on the docker-% Makefile targets), regenerates them on demand, imports legacy entries, and computes SDL GUIDs the same way the runtime does, including the name-embedded form SDL uses when vendor*product is zero. The 17 platform pads were imported from the current artifacts and the migration already caught two real inconsistencies. The GO-Super gamecontrollerdb line carried a numeric-form GUID, but the pad reports vendor 0 so SDL derives the name-embedded GUID at runtime (the form the device-captured es_input block always had); the line is now fixed to 1900bb3e474f2d537570657220476100 and its RetroArch cfg canonicalized. And retrogame_joypad legitimately ships different mappings per bus (guide:b10 only on 0x19), now modeled explicitly as a per-GUID map. Verification is semantic for es_input blocks and RetroArch cfgs (the legacy files mix tabs and spaces) and byte-exact for gamecontrollerdb lines, with GUIDs always recomputed from the identity fields. Co-Authored-By: Claude Fable 5 --- Makefile | 5 + projects/ArchR/inputs/README.md | 56 +++ .../gen-input-configscpython-314.pyc | Bin 0 -> 20330 bytes .../ArchR/inputs/defs/GO-Super_Gamepad.json | 204 ++++++++++ .../ArchR/inputs/defs/GO-Ultra_Gamepad.json | 202 ++++++++++ .../inputs/defs/GameForce_ACE_Gamepad.json | 191 +++++++++ projects/ArchR/inputs/defs/H700_Gamepad.json | 209 ++++++++++ .../ArchR/inputs/defs/RGB20S_Gamepad.json | 199 ++++++++++ projects/ArchR/inputs/defs/XU10_Gamepad.json | 203 ++++++++++ .../ArchR/inputs/defs/XU_Mini_M_Gamepad.json | 199 ++++++++++ .../ArchR/inputs/defs/odroidgo2_joypad.json | 157 ++++++++ .../inputs/defs/odroidgo2_v11_joypad.json | 171 ++++++++ .../ArchR/inputs/defs/odroidgo3_joypad.json | 199 ++++++++++ projects/ArchR/inputs/defs/r33s_joypad.json | 128 ++++++ projects/ArchR/inputs/defs/r36s_Gamepad.json | 199 ++++++++++ .../ArchR/inputs/defs/retrogame_joypad.json | 224 +++++++++++ .../inputs/defs/retrogame_joypad_s1_f2.json | 195 ++++++++++ projects/ArchR/inputs/defs/rg552_joypad.json | 199 ++++++++++ projects/ArchR/inputs/defs/rg_arc_joypad.json | 148 +++++++ projects/ArchR/inputs/defs/zed_joystick.json | 203 ++++++++++ projects/ArchR/inputs/gen-input-configs | 365 ++++++++++++++++++ .../config/gamecontrollerdb.txt | 2 +- .../gamepads/GO-Super Gamepad.cfg | 71 ++-- 23 files changed, 3686 insertions(+), 43 deletions(-) create mode 100644 projects/ArchR/inputs/README.md create mode 100644 projects/ArchR/inputs/__pycache__/gen-input-configscpython-314.pyc create mode 100644 projects/ArchR/inputs/defs/GO-Super_Gamepad.json create mode 100644 projects/ArchR/inputs/defs/GO-Ultra_Gamepad.json create mode 100644 projects/ArchR/inputs/defs/GameForce_ACE_Gamepad.json create mode 100644 projects/ArchR/inputs/defs/H700_Gamepad.json create mode 100644 projects/ArchR/inputs/defs/RGB20S_Gamepad.json create mode 100644 projects/ArchR/inputs/defs/XU10_Gamepad.json create mode 100644 projects/ArchR/inputs/defs/XU_Mini_M_Gamepad.json create mode 100644 projects/ArchR/inputs/defs/odroidgo2_joypad.json create mode 100644 projects/ArchR/inputs/defs/odroidgo2_v11_joypad.json create mode 100644 projects/ArchR/inputs/defs/odroidgo3_joypad.json create mode 100644 projects/ArchR/inputs/defs/r33s_joypad.json create mode 100644 projects/ArchR/inputs/defs/r36s_Gamepad.json create mode 100644 projects/ArchR/inputs/defs/retrogame_joypad.json create mode 100644 projects/ArchR/inputs/defs/retrogame_joypad_s1_f2.json create mode 100644 projects/ArchR/inputs/defs/rg552_joypad.json create mode 100644 projects/ArchR/inputs/defs/rg_arc_joypad.json create mode 100644 projects/ArchR/inputs/defs/zed_joystick.json create mode 100755 projects/ArchR/inputs/gen-input-configs diff --git a/Makefile b/Makefile index 529ab94a00..babb957295 100644 --- a/Makefile +++ b/Makefile @@ -118,7 +118,12 @@ docker-image-pull: # Runs host-side on purpose: the website repo is not mounted into the build # container. The sync script is itself non-fatal, and the guard keeps it off # non-build docker targets (docker-shell, docker-update, ...). +# Before any docker build, fail fast if the three shipped input-config +# artifacts drifted from the per-pad definitions (see projects/ArchR/inputs). +# Verification only: the build never rewrites the artifacts by itself. docker-%: + @./projects/ArchR/inputs/gen-input-configs verify >/dev/null || \ + { ./projects/ArchR/inputs/gen-input-configs verify; exit 1; } ./scripts/get_env > .env BUILD_DIR="$(DOCKER_WORK_DIR)" $(DOCKER_CMD) run $(PODMAN_ARGS) $(INTERACTIVE) --init --env-file .env --rm --user $(UID):$(GID) $(GLOBAL_SETTINGS) $(LOCAL_SSH_KEYS_FILE) $(EMULATIONSTATION_SRC) -v "$(PWD)":"$(DOCKER_WORK_DIR)" -v /tmp:/tmp -w "$(DOCKER_WORK_DIR)" $(DOCKER_EXTRA_OPTS) $(DOCKER_IMAGE) $(COMMAND) @if [ "$*" = "RK3326" ]; then ./scripts/repo/sync-site-bases; fi diff --git a/projects/ArchR/inputs/README.md b/projects/ArchR/inputs/README.md new file mode 100644 index 0000000000..adb6cde091 --- /dev/null +++ b/projects/ArchR/inputs/README.md @@ -0,0 +1,56 @@ +# Built-in gamepad definitions (single source of truth) + +Every built-in platform pad must exist in THREE shipped artifacts: + +1. `packages/ui/emulationstation/config/common/es_input.cfg` (EmulationStation) +2. `packages/emulators/libretro/retroarch/retroarch-joypads/gamepads/.cfg` (RetroArch) +3. `packages/apps/gamecontrollerdb/config/gamecontrollerdb.txt` (SDL games and ports) + +Historically these were maintained by hand and nothing checked they agreed: +the Soysauce pad shipped without the SDL entry, so ports saw no controller. +The JSON files in `defs/` are now the source of truth and `gen-input-configs` +keeps the three artifacts honest. + +## Daily use + +`make docker-*` refuses to build if the artifacts drifted from `defs/`: + + ./gen-input-configs verify # what CI/build runs + ./gen-input-configs write # regenerate artifacts from defs + +Only external controllers (USB/Bluetooth pads inherited from upstream +databases) stay unmanaged; the managed set is exactly the defs directory. + +## Adding a new pad (example: a future R36T Max) + +1. On the device, read the identity: + + cat /proc/bus/input/devices # Name=, Vendor=, Product=, Version= + +2. Compute the GUID the runtime will use (bus 0x19 for platform drivers): + + ./gen-input-configs guid "r36tmax_Gamepad" --vendor 1 --product 0x1199 --version 0x0100 + + Vendor or product zero switches SDL to the name-embedded GUID form + automatically, exactly like the runtime does (that mismatch was the + Soysauce bug). + +3. Copy the closest def (`defs/r36s_Gamepad.json`), adjust name, identity, + buses, the SDL mapping string, the ES inputs and the RetroArch keys. + +4. Materialize and check: + + ./gen-input-configs write "r36tmax_Gamepad" + ./gen-input-configs verify + +5. Commit the def together with the regenerated artifacts. + +## Fidelity rules (deliberate) + +* gamecontrollerdb line: byte-exact; the GUID is always recomputed from the + identity fields (use `guid_override` only for entries that predate the + tool and cannot be reproduced; none of the platform pads needs it). +* RetroArch cfg and es_input block: semantically exact (key/value sets); + comments, blank lines and ordering are not preserved by `write`. +* `sdl_mapping` may be a per-GUID dict when buses legitimately differ + (retrogame_joypad ships guide:b10 only on the 0x19 bus line). diff --git a/projects/ArchR/inputs/__pycache__/gen-input-configscpython-314.pyc b/projects/ArchR/inputs/__pycache__/gen-input-configscpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac2fe63f9a9f8e6bb2f7eae066dc88f4777ea70d GIT binary patch literal 20330 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.cfg (RetroArch udev autoconfig) +# 3. gamecontrollerdb.txt (SDL games and ports) +# and nothing checked they agreed. The Soysauce pad shipped with only +# two of the three, so SDL ports saw no controller at all. +# +# This tool keeps one JSON definition per pad in defs/ and can: +# verify compare defs against the three shipped artifacts (CI check) +# write regenerate the managed sections of the artifacts from defs +# import bootstrap a def from the current artifacts (migration aid) +# guid print the SDL GUIDs a given identity produces +# +# SDL GUID layout (SDL2 >= 2.26, matches what the devices report at +# runtime): bus u16 LE + crc16-ARC(name) u16 LE, then +# vendor and product both non-zero: +# vendor LE, 0000, product LE, 0000, version LE, 0000 +# otherwise (vendor*product == 0): +# first 11 bytes of the name, zero padded +# The second form matters: the Soysauce pad reports vendor 0, so its +# real GUID embeds the name ("GO-Super Ga"), not the product id. +# +# Round-trip fidelity (documented, deliberate): +# gamecontrollerdb line : byte-exact (GUID recomputed from identity, +# mapping stored verbatim) +# retroarch cfg : semantic (key = value set; comments, blank +# lines and ordering are not modeled) +# es_input block : semantic (device attrs + set of +# tuples; the legacy file mixes tabs/spaces) + +import json +import os +import re +import sys +import argparse + +HERE = os.path.dirname(os.path.abspath(__file__)) +ROOT = os.path.abspath(os.path.join(HERE, '..', '..', '..')) +DEFS = os.path.join(HERE, 'defs') + +ES_INPUT = os.path.join( + ROOT, 'projects/ArchR/packages/ui/emulationstation/config/common/es_input.cfg') +RA_DIR = os.path.join( + ROOT, 'projects/ArchR/packages/emulators/libretro/retroarch/retroarch-joypads/gamepads') +GCDB = os.path.join( + ROOT, 'projects/ArchR/packages/apps/gamecontrollerdb/config/gamecontrollerdb.txt') + + +# --------------------------------------------------------------------------- +# GUID machinery +# --------------------------------------------------------------------------- + +def crc16_arc(data: bytes) -> int: + crc = 0 + for b in data: + crc ^= b + for _ in range(8): + crc = (crc >> 1) ^ 0xA001 if crc & 1 else crc >> 1 + return crc + + +def _u16le(v): + return bytes((v & 0xff, (v >> 8) & 0xff)) + + +def sdl_guid(name, bus, vendor, product, version): + head = _u16le(bus) + _u16le(crc16_arc(name.encode())) + if vendor and product: + body = (_u16le(vendor) + b'\x00\x00' + _u16le(product) + b'\x00\x00' + + _u16le(version) + b'\x00\x00') + else: + body = name.encode()[:11].ljust(12, b'\x00') + return (head + body).hex() + + +def guids_for(pad): + return [sdl_guid(pad['name'], bus, pad['vendor'], pad['product'], + pad['version']) for bus in pad['buses']] + + +# --------------------------------------------------------------------------- +# Artifact parsing +# --------------------------------------------------------------------------- + +def parse_retroarch_cfg(text): + out = {} + for line in text.splitlines(): + m = re.match(r'^\s*([A-Za-z0-9_]+)\s*=\s*"(.*)"\s*$', line) + if m: + out[m.group(1)] = m.group(2) + return out + + +_ES_BLOCK = re.compile( + r']*deviceGUID="(?P[0-9a-fA-F]{32})"[^>]*>' + r'(?P.*?)', re.S) +_ES_ATTR = re.compile(r'(\w+)="([^"]*)"') + + +def es_blocks(text): + """guid -> (deviceName, set of (name,type,id,value), full block span).""" + blocks = {} + for m in _ES_BLOCK.finditer(text): + head = text[m.start():text.index('>', m.start()) + 1] + attrs = dict(_ES_ATTR.findall(head)) + inputs = set() + for im in re.finditer(r']*)/>', m.group('body')): + a = dict(_ES_ATTR.findall(im.group(1))) + inputs.add((a.get('name'), a.get('type'), a.get('id'), a.get('value'))) + blocks[m.group('guid').lower()] = (attrs.get('deviceName'), inputs, + (m.start(), m.end())) + return blocks + + +def gcdb_lines(text): + """name -> {guid: full line}""" + out = {} + for line in text.splitlines(): + if not line or line.startswith('#'): + continue + parts = line.split(',', 2) + if len(parts) < 3: + continue + out.setdefault(parts[1], {})[parts[0].lower()] = line + return out + + +# --------------------------------------------------------------------------- +# Generation from a definition +# --------------------------------------------------------------------------- + +def gen_gcdb_line(pad, guid): + # sdl_mapping is a plain string, or a {guid: mapping} dict when the + # shipped lines genuinely differ per bus (retrogame_joypad's 0x19 + # line carries guide:b10, its 0x03 line does not). + m = pad['sdl_mapping'] + if isinstance(m, dict): + m = m[guid] + return f"{guid},{pad['name']},{m}" + + +def gen_retroarch_text(pad): + ra = pad['retroarch'] + ident = ['input_driver', 'input_device', 'input_device_display_name', + 'input_vendor_id', 'input_product_id'] + lines = [f'{k} = "{ra[k]}"' for k in ident if k in ra] + lines += [f'{k} = "{ra[k]}"' for k in sorted(ra) if k not in ident] + return '\n'.join(lines) + '\n' + + +def gen_es_block(pad, guid, indent=' '): + rows = sorted(pad['es_inputs'], key=lambda r: r['name']) + out = [f'{indent}'] + for r in rows: + out.append(f'{indent} ') + out.append(f'{indent}') + return '\n'.join(out) + + +# --------------------------------------------------------------------------- +# Subcommands +# --------------------------------------------------------------------------- + +def load_defs(names=None): + pads = [] + for f in sorted(os.listdir(DEFS)): + if not f.endswith('.json'): + continue + pad = json.load(open(os.path.join(DEFS, f))) + if names and pad['name'] not in names: + continue + pads.append(pad) + return pads + + +def cmd_verify(args): + pads = load_defs(args.names) + gcdb = gcdb_lines(open(GCDB).read()) + es = es_blocks(open(ES_INPUT).read()) + failed = False + + def bad(msg): + nonlocal failed + failed = True + print(f'DRIFT {msg}') + + for pad in pads: + name = pad['name'] + guids = guids_for(pad) + if pad.get('guid_override'): + guids = pad['guid_override'] + + # 1. gamecontrollerdb: byte-exact line per GUID + have = gcdb.get(name, {}) + for g in guids: + want = gen_gcdb_line(pad, g) + if g not in have: + bad(f'{name}: gamecontrollerdb missing GUID {g}') + elif have[g] != want: + bad(f'{name}: gamecontrollerdb line differs for {g}\n' + f' have: {have[g]}\n want: {want}') + for g in have: + if g not in guids: + bad(f'{name}: gamecontrollerdb has stale GUID {g}') + + # 2. retroarch cfg: semantic key=value equality + ra_path = os.path.join(RA_DIR, f'{name}.cfg') + if not os.path.exists(ra_path): + bad(f'{name}: retroarch cfg missing ({ra_path})') + else: + have_ra = parse_retroarch_cfg(open(ra_path).read()) + want_ra = pad['retroarch'] + for k in sorted(set(have_ra) | set(want_ra)): + if have_ra.get(k) != want_ra.get(k): + bad(f'{name}: retroarch {k}: ' + f'have {have_ra.get(k)!r} want {want_ra.get(k)!r}') + + # 3. es_input: semantic block per GUID + want_es = {(r['name'], r['type'], str(r['id']), str(r['value'])) + for r in pad['es_inputs']} + for g in guids: + if g not in es: + bad(f'{name}: es_input block missing for GUID {g}') + continue + dev, have_es, _ = es[g] + if dev != name: + bad(f'{name}: es_input deviceName is {dev!r} for {g}') + if have_es != want_es: + bad(f'{name}: es_input inputs differ for {g}: ' + f'only-in-file {sorted(have_es - want_es)} ' + f'only-in-def {sorted(want_es - have_es)}') + + if not failed: + print(f'OK {name} ({len(guids)} guid)') + return 1 if failed else 0 + + +def cmd_write(args): + pads = load_defs(args.names) + + gcdb_text = open(GCDB).read() + name_lines = gcdb_lines(gcdb_text) + lines = gcdb_text.splitlines() + for pad in pads: + guids = pad.get('guid_override') or guids_for(pad) + wanted = {g: gen_gcdb_line(pad, g) for g in guids} + old = set(name_lines.get(pad['name'], {}).values()) + lines = [l for l in lines if l not in old or l in wanted.values()] + present = set(lines) + for g, l in wanted.items(): + if l not in present: + lines.append(l) + open(GCDB, 'w').write('\n'.join(lines).rstrip('\n') + '\n') + + for pad in pads: + ra_path = os.path.join(RA_DIR, f"{pad['name']}.cfg") + open(ra_path, 'w').write(gen_retroarch_text(pad)) + + es_text = open(ES_INPUT).read() + for pad in pads: + guids = pad.get('guid_override') or guids_for(pad) + for g in guids: + blocks = es_blocks(es_text) + new_block = gen_es_block(pad, g) + if g in blocks: + s, e = blocks[g][2] + # keep the original leading indentation of the block + ls = es_text.rfind('\n', 0, s) + 1 + indent = es_text[ls:s] + new_block = gen_es_block(pad, g, indent=indent or ' ') + es_text = es_text[:ls] + new_block + es_text[e:] + else: + end = es_text.rindex('') + es_text = es_text[:end] + new_block + '\n' + es_text[end:] + open(ES_INPUT, 'w').write(es_text) + print(f'wrote {len(pads)} pad(s)') + return 0 + + +def cmd_import(args): + gcdb = gcdb_lines(open(GCDB).read()) + es = es_blocks(open(ES_INPUT).read()) + for name in args.names: + entry = gcdb.get(name) + if not entry: + print(f'skip {name}: not in gamecontrollerdb') + continue + guids = sorted(entry) + # identity from the first numeric-form GUID, else name-form + vendor = product = version = 0 + buses = [] + for g in guids: + raw = bytes.fromhex(g) + buses.append(raw[0] | raw[1] << 8) + body = raw[4:] + if body[:11] == name.encode()[:11].ljust(11, b'\x00')[:11]: + continue + vendor = raw[4] | raw[5] << 8 + product = raw[8] | raw[9] << 8 + version = raw[12] | raw[13] << 8 + pad = { + 'name': name, + 'vendor': vendor, 'product': product, 'version': version, + 'buses': sorted(set(buses)), + 'sdl_mapping': (entry[guids[0]].split(',', 2)[2] + if len({l.split(',', 2)[2] for l in entry.values()}) == 1 + else {g: entry[g].split(',', 2)[2] for g in guids}), + } + # cross-check: do the recomputed GUIDs reproduce the file? + recomputed = set(guids_for(pad)) + if recomputed != set(guids): + pad['guid_override'] = guids + print(f'note {name}: GUIDs not reproducible from identity, ' + f'stored verbatim ({guids} vs {sorted(recomputed)})') + ra_path = os.path.join(RA_DIR, f'{name}.cfg') + pad['retroarch'] = (parse_retroarch_cfg(open(ra_path).read()) + if os.path.exists(ra_path) else {}) + rows = [] + for g in guids: + if g in es: + rows = [{'name': n, 'type': t, 'id': i, 'value': v} + for (n, t, i, v) in sorted(es[g][1])] + break + pad['es_inputs'] = rows + out = os.path.join(DEFS, re.sub(r'[^A-Za-z0-9._-]', '_', name) + '.json') + json.dump(pad, open(out, 'w'), indent=2, ensure_ascii=False) + open(out, 'a').write('\n') + print(f'imported {name} -> {os.path.relpath(out, HERE)} ' + f'({len(guids)} guid, {len(rows)} es inputs, ' + f'{len(pad["retroarch"])} ra keys)') + return 0 + + +def cmd_guid(args): + print(sdl_guid(args.name, args.bus, args.vendor, args.product, args.version)) + return 0 + + +def main(): + p = argparse.ArgumentParser(description=__doc__) + sub = p.add_subparsers(dest='cmd', required=True) + for c in ('verify', 'write', 'import'): + sp = sub.add_parser(c) + sp.add_argument('names', nargs='*' if c != 'import' else '+') + g = sub.add_parser('guid') + g.add_argument('name') + g.add_argument('--bus', type=lambda x: int(x, 0), default=0x19) + g.add_argument('--vendor', type=lambda x: int(x, 0), default=0) + g.add_argument('--product', type=lambda x: int(x, 0), default=0) + g.add_argument('--version', type=lambda x: int(x, 0), default=0) + args = p.parse_args() + return {'verify': cmd_verify, 'write': cmd_write, + 'import': cmd_import, 'guid': cmd_guid}[args.cmd](args) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/projects/ArchR/packages/apps/gamecontrollerdb/config/gamecontrollerdb.txt b/projects/ArchR/packages/apps/gamecontrollerdb/config/gamecontrollerdb.txt index 2949ace9f9..bb20560668 100644 --- a/projects/ArchR/packages/apps/gamecontrollerdb/config/gamecontrollerdb.txt +++ b/projects/ArchR/packages/apps/gamecontrollerdb/config/gamecontrollerdb.txt @@ -17,7 +17,6 @@ 1900c3bbb0c300000002000010000000,XU Mini M Gamepad,platform:Linux,x:b2,a:b1,b:b0,y:b3,back:b8,guide:b16,start:b9,dpleft:b14,dpdown:b13,dpright:b15,dpup:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3, 0300f353202000000130000001000000,Retroid Pocket Gamepad,platform:Linux,x:b2,a:b1,b:b0,y:b3,back:b6,guide:b8,start:b7,dpleft:b13,dpdown:b12,dpright:b14,dpup:b11,leftshoulder:b4,lefttrigger:a6,rightshoulder:b5,righttrigger:a7,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,misc1:b15, 19003982010000008811000088010000,r36s_Gamepad,platform:Linux,a:b1,b:b0,dpdown:b14,dpleft:b15,+lefty:+a1,-leftx:-a0,+leftx:+a0,-lefty:-a1,leftshoulder:b4,leftstick:b11,lefttrigger:b6,dpright:b16,+righty:+a3,-rightx:-a2,+rightx:+a2,-righty:-a3,rightshoulder:b5,rightstick:b12,righttrigger:b7,back:b8,start:b9,dpup:b13,x:b2,y:b3, -1900bb3e000000000011000000010000,GO-Super Gamepad,platform:Linux,a:b1,b:b0,dpdown:b14,dpleft:b15,+lefty:+a1,-leftx:-a0,+leftx:+a0,-lefty:-a1,leftshoulder:b4,leftstick:b11,lefttrigger:b6,dpright:b16,+righty:+a3,-rightx:-a2,+rightx:+a2,-righty:-a3,rightshoulder:b5,rightstick:b12,righttrigger:b7,back:b8,start:b9,dpup:b13,x:b2,y:b3, 1900f6a24b480000df14000000010000,H700 Gamepad,platform:Linux,x:b3,a:b1,b:b0,y:b2,back:b8,guide:b10,start:b9,dpleft:b15,dpdown:b14,dpright:b16,dpup:b13,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b11,rightstick:b12,leftx:a0,lefty:a1,rightx:a2,righty:a3, 0300b605202000000130000001000000,AYN Odin2 Gamepad,platform:Linux,x:b2,a:b1,b:b0,y:b3,back:b6,guide:b8,start:b7,dpleft:b13,dpdown:b12,dpright:b14,dpup:b11,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, 1900365541594e204f64696e20476100,AYN Odin Gamepad,platform:Linux,x:b2,a:b1,b:b0,y:b3,back:b8,guide:b10,start:b9,dpleft:b15,dpdown:b14,dpright:b16,dpup:b13,leftshoulder:b4,lefttrigger:a5,rightshoulder:b5,righttrigger:a4,leftstick:b11,rightstick:b12,leftx:a0,lefty:a1,rightx:a2,righty:a3, @@ -25,3 +24,4 @@ #InputPlumber Controller 0300f5a35e040000120b000001000000,InputPlumber GameController,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,misc1:b15,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, +1900bb3e474f2d537570657220476100,GO-Super Gamepad,platform:Linux,a:b1,b:b0,dpdown:b14,dpleft:b15,+lefty:+a1,-leftx:-a0,+leftx:+a0,-lefty:-a1,leftshoulder:b4,leftstick:b11,lefttrigger:b6,dpright:b16,+righty:+a3,-rightx:-a2,+rightx:+a2,-righty:-a3,rightshoulder:b5,rightstick:b12,righttrigger:b7,back:b8,start:b9,dpup:b13,x:b2,y:b3, diff --git a/projects/ArchR/packages/emulators/libretro/retroarch/retroarch-joypads/gamepads/GO-Super Gamepad.cfg b/projects/ArchR/packages/emulators/libretro/retroarch/retroarch-joypads/gamepads/GO-Super Gamepad.cfg index cd9a67fbfb..5acc23699a 100644 --- a/projects/ArchR/packages/emulators/libretro/retroarch/retroarch-joypads/gamepads/GO-Super Gamepad.cfg +++ b/projects/ArchR/packages/emulators/libretro/retroarch/retroarch-joypads/gamepads/GO-Super Gamepad.cfg @@ -1,53 +1,40 @@ -input_device = "GO-Super Gamepad" input_driver = "udev" +input_device = "GO-Super Gamepad" input_vendor_id = "0000" input_product_id = "1100" - -input_b_btn = "0" input_a_btn = "1" -input_x_btn = "2" -input_y_btn = "3" -input_l_btn = "4" -input_r_btn = "5" -input_l2_btn = "6" -input_r2_btn = "7" - -input_up_btn = "13" +input_b_btn = "0" input_down_btn = "14" -input_left_btn = "15" -input_right_btn = "16" - -input_select_btn = "8" -input_start_btn = "9" - -input_l3_btn = "11" -input_r3_btn = "12" - -input_l_x_plus_axis = "+0" -input_l_x_minus_axis = "-0" -input_l_y_plus_axis = "+1" -input_l_y_minus_axis = "-1" - -input_r_x_plus_axis = "+2" -input_r_x_minus_axis = "-2" -input_r_y_plus_axis = "+3" -input_r_y_minus_axis = "-3" - - -# Hotkeys input_enable_hotkey_btn = "8" input_exit_emulator_btn = "9" - -input_screenshot_btn = "0" -input_pause_toggle_btn = "1" -input_menu_toggle_btn = "2" input_fps_toggle_btn = "3" - -input_state_slot_increase_btn = "13" -input_state_slot_decrease_btn = "14" - +input_l2_btn = "6" +input_l3_btn = "11" +input_l_btn = "4" +input_l_x_minus_axis = "-0" +input_l_x_plus_axis = "+0" +input_l_y_minus_axis = "-1" +input_l_y_plus_axis = "+1" +input_left_btn = "15" input_load_state_btn = "4" +input_menu_toggle_btn = "2" +input_pause_toggle_btn = "1" +input_r2_btn = "7" +input_r3_btn = "12" +input_r_btn = "5" +input_r_x_minus_axis = "-2" +input_r_x_plus_axis = "+2" +input_r_y_minus_axis = "-3" +input_r_y_plus_axis = "+3" +input_rewind_btn = "6" +input_right_btn = "16" input_save_state_btn = "5" - -input_rewind_btn = "6" +input_screenshot_btn = "0" +input_select_btn = "8" +input_start_btn = "9" +input_state_slot_decrease_btn = "14" +input_state_slot_increase_btn = "13" input_toggle_fast_forward_btn = "7" +input_up_btn = "13" +input_x_btn = "2" +input_y_btn = "3"