Merge pull request #440 from outis1one/claude/wolf-pair-port-conflict-7nz8qg
wolf: add ./manage.sh cemu-sync-controllers — full auto-clone via SDL
This commit is contained in:
+172
-1
@@ -3554,7 +3554,7 @@ _manage_wolf_complete() {
|
||||
local commands="start stop restart logs status pin controllers update apps cores reorder
|
||||
add-web ge-proton games setup-swbf2 fix-ea-game wait-ea-app
|
||||
install-ea-app diagnose-ea fix-perms install-completion backup
|
||||
steam-add-nonsteam-game cemu-clone-controller"
|
||||
steam-add-nonsteam-game cemu-clone-controller cemu-sync-controllers"
|
||||
COMPREPLY=( $(compgen -W "$commands" -- "$cur") )
|
||||
}
|
||||
# Register for both 'manage.sh' and './manage.sh' invocation styles
|
||||
@@ -4123,6 +4123,156 @@ CLONEPY
|
||||
echo "Close Cemu COMPLETELY and relaunch for this to take effect — a hand-edited"
|
||||
echo "profile file isn't picked up by an already-running Cemu."
|
||||
;;
|
||||
cemu-sync-controllers)
|
||||
# Fully automates cemu-clone-controller above: queries SDL directly
|
||||
# (the exact same library Cemu itself links against) for the REAL
|
||||
# live GUID of every controller currently connected to the active
|
||||
# session's app container, then clones a proven-working mapping
|
||||
# onto each one's controllerN.xml — no Cemu GUI interaction at all,
|
||||
# not even the single-button-and-Save step cemu-clone-controller
|
||||
# still needs to learn a brand-new device's uuid.
|
||||
#
|
||||
# CONFIRMED LIVE: querying SDL_JoystickGetDeviceGUID() this way
|
||||
# inside a running ES-DE session reproduced the EXACT uuids Cemu
|
||||
# had already written by hand for two different real controllers
|
||||
# (Nintendo Switch Pro and Xbox One S) — byte-for-byte identical to
|
||||
# controllerProfiles/controller0.xml and controller1.xml. SDL's own
|
||||
# library is the authoritative source for this value — an earlier
|
||||
# attempt to reverse-engineer just the CRC16 portion of the GUID by
|
||||
# hand, tried against 7 different CRC16 variants, matched neither
|
||||
# device. This sidesteps that entirely by asking the same library
|
||||
# Cemu itself calls, instead of recomputing what it would return.
|
||||
#
|
||||
# Wolf's own controller numbering (0-indexed) matched SDL's
|
||||
# enumeration order for that session 1:1 in testing (index 0 was
|
||||
# the controller Wolf logged as "controller 0", index 1 was
|
||||
# "controller 1") — so the Nth joystick SDL reports maps directly
|
||||
# onto controllerN.xml, the same file-naming convention used
|
||||
# everywhere else in this script.
|
||||
GAME_DIR=$(grep '^GAME_STORAGE_DIR=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d= -f2-)
|
||||
if [ -z "$GAME_DIR" ]; then read -r -p " Game storage path: " GAME_DIR; fi
|
||||
if [ -z "$GAME_DIR" ]; then echo "No game storage path."; exit 1; fi
|
||||
_CSC_DIR="$GAME_DIR/retro-home/Cemu/controllerProfiles"
|
||||
if [ ! -d "$_CSC_DIR" ]; then
|
||||
echo "No controllerProfiles/ yet at $_CSC_DIR — set up at least one controller"
|
||||
echo "in Cemu's own Input Settings first (Controller 1), so there's a real,"
|
||||
echo "working profile for this to clone the mapping from."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Find whichever currently-running Wolf app container actually has
|
||||
# joysticks wired up — the active session's controllers could be
|
||||
# attached to ES-DE, Desktop, RetroArch, or Steam depending on what
|
||||
# you're connected to right now, so this isn't hard-wired to one app.
|
||||
_CSC_CONTAINER=""
|
||||
for _c in $(docker ps --format '{{.Names}}' | grep -i '^Wolf'); do
|
||||
if docker exec "$_c" test -e /dev/input/js0 2>/dev/null; then
|
||||
_CSC_CONTAINER="$_c"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -z "$_CSC_CONTAINER" ]; then
|
||||
echo "No running Wolf app container has any controller attached right now."
|
||||
echo "Connect via Moonlight with your controllers active, then re-run this."
|
||||
exit 1
|
||||
fi
|
||||
echo "Reading live controller GUIDs from $_CSC_CONTAINER..."
|
||||
|
||||
_CSC_PROBE=$(docker exec -u retro "$_CSC_CONTAINER" python3 - << 'PROBEPY'
|
||||
import ctypes, ctypes.util, sys
|
||||
|
||||
libname = ctypes.util.find_library("SDL2") or "libSDL2-2.0.so.0"
|
||||
try:
|
||||
sdl = ctypes.CDLL(libname)
|
||||
except OSError as e:
|
||||
print(f"ERROR: could not load libSDL2 ({libname}): {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
class SDL_JoystickGUID(ctypes.Structure):
|
||||
_fields_ = [("data", ctypes.c_uint8 * 16)]
|
||||
|
||||
sdl.SDL_Init.argtypes = [ctypes.c_uint32]
|
||||
sdl.SDL_Init.restype = ctypes.c_int
|
||||
sdl.SDL_NumJoysticks.restype = ctypes.c_int
|
||||
sdl.SDL_JoystickGetDeviceGUID.argtypes = [ctypes.c_int]
|
||||
sdl.SDL_JoystickGetDeviceGUID.restype = SDL_JoystickGUID
|
||||
sdl.SDL_JoystickGetGUIDString.argtypes = [SDL_JoystickGUID, ctypes.c_char_p, ctypes.c_int]
|
||||
sdl.SDL_JoystickNameForIndex.argtypes = [ctypes.c_int]
|
||||
sdl.SDL_JoystickNameForIndex.restype = ctypes.c_char_p
|
||||
|
||||
if sdl.SDL_Init(0x00000200) != 0:
|
||||
print("ERROR: SDL_Init(JOYSTICK) failed", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
n = sdl.SDL_NumJoysticks()
|
||||
for i in range(n):
|
||||
guid = sdl.SDL_JoystickGetDeviceGUID(i)
|
||||
buf = ctypes.create_string_buffer(64)
|
||||
sdl.SDL_JoystickGetGUIDString(guid, buf, 64)
|
||||
name = sdl.SDL_JoystickNameForIndex(i)
|
||||
name = name.decode() if name else f"Controller {i}"
|
||||
print(f"{i} 0_{buf.value.decode()} {name}")
|
||||
PROBEPY
|
||||
)
|
||||
if [ -z "$_CSC_PROBE" ]; then
|
||||
echo "SDL reported zero joysticks inside $_CSC_CONTAINER — nothing to sync."
|
||||
echo "Make sure controllers are actually connected and active in this session."
|
||||
exit 1
|
||||
fi
|
||||
echo "$_CSC_PROBE"
|
||||
echo ""
|
||||
|
||||
_CSC_ANY_SYNCED=0
|
||||
while read -r _idx _uuid _live_name; do
|
||||
[ -z "$_idx" ] && continue
|
||||
_target="$_CSC_DIR/controller${_idx}.xml"
|
||||
|
||||
# Already has this exact uuid — nothing to do, don't churn a backup.
|
||||
if [ -f "$_target" ] && grep -qF "<uuid>$_uuid</uuid>" "$_target"; then
|
||||
echo "controller${_idx}.xml already has this device's mapping — skipping."
|
||||
continue
|
||||
fi
|
||||
|
||||
# Pick any OTHER existing, non-empty controllerN.xml as the source.
|
||||
_src=""
|
||||
for _n in 0 1 2 3; do
|
||||
_cand="$_CSC_DIR/controller${_n}.xml"
|
||||
if [ -s "$_cand" ] && [ "$_n" != "$_idx" ]; then
|
||||
_src="$_cand"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -z "$_src" ]; then
|
||||
echo "controller${_idx}.xml: no other working profile to clone from yet — skipping."
|
||||
continue
|
||||
fi
|
||||
|
||||
[ -f "$_target" ] && cp "$_target" "$_target.bak.$(date +%s)"
|
||||
# Use SDL's own live device name if it reported one — more useful
|
||||
# in Cemu's own UI than inheriting whatever device the mapping
|
||||
# template happened to come from.
|
||||
_name="${_live_name:-$(grep -o '<display_name>[^<]*</display_name>' "$_src" | head -1 | sed -E 's#</?display_name>##g')}"
|
||||
|
||||
python3 - "$_src" "$_target" "$_uuid" "$_name" << 'SYNCPY'
|
||||
import re, sys
|
||||
src_path, out_path, uuid, display_name = sys.argv[1:5]
|
||||
with open(src_path) as f:
|
||||
template = f.read()
|
||||
new = re.sub(r'<uuid>[^<]*</uuid>', f'<uuid>{uuid}</uuid>', template, count=1)
|
||||
new = re.sub(r'<display_name>[^<]*</display_name>', f'<display_name>{display_name}</display_name>', new, count=1)
|
||||
with open(out_path, 'w') as f:
|
||||
f.write(new)
|
||||
SYNCPY
|
||||
chown 1000:1000 "$_target"
|
||||
echo "controller${_idx}.xml: synced (uuid $_uuid, cloned from $(basename "$_src"))."
|
||||
_CSC_ANY_SYNCED=1
|
||||
done <<< "$_CSC_PROBE"
|
||||
|
||||
echo ""
|
||||
if [ "$_CSC_ANY_SYNCED" = 1 ]; then
|
||||
echo "Close Cemu COMPLETELY and relaunch for the synced profiles to take effect."
|
||||
fi
|
||||
;;
|
||||
pin)
|
||||
# Wolf logs: "Insert pin at http://SOMEIP:47989/pin/#HEXHASH"
|
||||
# Extract just the hash fragment and build URLs for every interface
|
||||
@@ -4179,6 +4329,7 @@ CLONEPY
|
||||
echo " - Add an emulator (emulators/) as a non-Steam game, no GUI needed"
|
||||
echo " ./manage.sh cemu-clone-controller [slot 0-3] [uuid] [display name]"
|
||||
echo " - Clone a working Cemu controller mapping onto a new device slot"
|
||||
echo " ./manage.sh cemu-sync-controllers - Auto-detect connected controllers via SDL and clone mappings onto all of them"
|
||||
echo " ./manage.sh backup - How to set up backups"
|
||||
;;
|
||||
esac
|
||||
@@ -4740,6 +4891,7 @@ cd $WOLF_DIR
|
||||
./manage.sh install-completion # enable tab-completion for manage.sh
|
||||
./manage.sh steam-add-nonsteam-game # add an emulator as a non-Steam game, no GUI needed
|
||||
./manage.sh cemu-clone-controller # clone a working Cemu controller mapping onto a new device slot
|
||||
./manage.sh cemu-sync-controllers # auto-detect connected controllers via SDL, clone mappings onto all of them
|
||||
\`\`\`
|
||||
|
||||
### Tab completion
|
||||
@@ -5265,6 +5417,25 @@ uuid — no per-button rebinding, no fighting the cut-off Save button for
|
||||
every single controller. Backs up any existing \`controllerN.xml\` first,
|
||||
and needs Cemu closed and relaunched afterward like any other hand-edit.
|
||||
|
||||
**\`./manage.sh cemu-sync-controllers\` skips the uuid step too — genuinely
|
||||
zero Cemu GUI interaction for any controller.** Instead of getting each
|
||||
new device's uuid from Cemu itself, this asks **SDL directly** — the exact
|
||||
library Cemu links against — for the live GUID of every controller
|
||||
currently connected to your active session, then clones the mapping onto
|
||||
each one automatically. **Confirmed live:** querying SDL this way inside a
|
||||
real session reproduced the *exact* uuids Cemu had already written by hand
|
||||
for two different controllers, byte-for-byte. With your controller types
|
||||
already assigned via \`./manage.sh controllers\` and at least one slot
|
||||
mapped once (by hand, or via \`cemu-clone-controller\` above), just:
|
||||
\`\`\`bash
|
||||
cd $WOLF_DIR && ./manage.sh cemu-sync-controllers
|
||||
\`\`\`
|
||||
Run it any time after connecting with controllers active — already-synced
|
||||
slots are detected and skipped (no needless backups), and it works
|
||||
regardless of which app (ES-DE, Desktop, RetroArch, Steam) currently has
|
||||
the controllers attached, since it looks for whichever running Wolf
|
||||
container actually has joysticks wired up.
|
||||
|
||||
ROMs are mounted at \`/ROMs\` inside Desktop too, so Cemu's own File → Load
|
||||
can browse straight to \`/ROMs/wiiu/\` without needing ES-DE at all.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user