re: the pad word is REMAPPED -- my own bit table was mislabelled throughout

input-pad-read-path.md says of the word the C_PAD_DECODER reads: "There is
no shift and no remap on the way in -- the bit positions are XINPUT's own."
That is wrong. sub_8220D500 rebuilds the word out of XINPUT_GAMEPAD before
anything else sees it, into the game's own numbering.

  bits  0-3   A B X Y
  bits  4-7   left stick  UP DOWN LEFT RIGHT   (+/-20000 of 32767)
  bits  8-11  right stick UP DOWN LEFT RIGHT
  bits 12-15  D-pad       UP DOWN LEFT RIGHT
  bits 16-17  START, BACK
  bits 18-19  LB, RB
  bits 20-21  LT, RT  -- digital, threshold >220 of 255
  bits 22-23  L3, R3

Extracted mechanically from the image, no row typed by hand. The control is
the shape of the result: the 24 assignments land on bits 0..23, each used
exactly once, none repeated. A misdecode does not produce a bijection over a
contiguous range, and coincidence does not put the stick and D-pad
directions in the same order in two aligned nibbles.

So every mask in that page's tables names the wrong button. The 0xE000 x18
site, read there as "B | X | Y", is "D-pad DOWN | LEFT | RIGHT" -- eighteen
sites testing a menu cursor, which is what 18 sites should be.

And its headline negative is REFUTED: "LB and RB are not menu inputs" is
false. They are bound at config fields this+0x70 and this+0x84, LT/RT at
+0x74/+0x80. The negative was searched for 0x0100/0x0200 -- LB and RB in
XINPUT's numbering -- in a word where they live at 0x40000/0x80000. Right
function, right buttons, wrong bit positions, so it could only come back
empty. A negative is only as good as the numbering it was searched in.

Also decodes the ring record: +12 HELD, +16 PRESSED, +20 RELEASED, +28/+32
raw trigger bytes. Edge and level are one struct four bytes apart, which
displaces that page's guess that press-vs-hold was split between GetState
and the XamInputGetKeystrokeEx queue.

The superset claim in that page survives and is untouched: sub_82457038
really does compare every XINPUT_GAMEPAD field, and it really is
XINPUT-layout. This page depends on it.

Not decoded: which output bit means which ACTION, and per-screen sets. 5 of
18 output-bit sites did not resolve to a pad guard, so the output map is a
lower bound -- in particular "START is not tested" is NOT claimed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
This commit is contained in:
sylph-decoder
2026-09-01 18:33:42 +00:00
parent 0eb5637329
commit 1e388d0176
7 changed files with 377 additions and 1 deletions

View File

@@ -0,0 +1,52 @@
# C_PAD_DECODER: what sets each bit of the OUTPUT word at this+0x24C.
# Guards are in the RING word's numbering (see ringmap.txt), NOT XINPUT's.
# 'cfg +0xNN' = a REMAPPABLE binding written by the ctor sub_8220B610
# 'literal' = a mask hard-coded in the update sub_8220B8C0
# '(internal)'= guarded by decoder state, not by a pad bit, in this window
site out bit guard means
8220BBB0 0x000004 (internal) -
8220BE78 0x002000 cfg +0x90 R3
8220BEBC 0x000002 cfg +0x84 RB
8220BF00 0x000001 cfg +0x80 RT>220
8220BFB8 0x000800 cfg +0x74 LT>220
8220C0B0 0x000800 cfg +0x70 LB
8220C118 0x000020 (internal) -
8220C13C 0x000040 (internal) -
8220C2F0 0x000010 cfg +0xA0 B
8220C338 0x000100 cfg +0x7C X
8220C37C 0x200000 cfg +0xA4 DPAD DOWN
8220C404 0x010000 cfg +0x98 BACK
8220C458 0x040000 literal LS LEFT
8220C474 0x080000 literal LS RIGHT
8220C490 0x000200 literal LS UP
8220C4AC 0x000400 literal LS DOWN
8220C4EC 0x100000 (internal) -
8220CB48 0x000008 (internal) -
# 13 of 18 output bits resolve to a pad guard.
# every config field the ctor sets to a ring-word button set:
# +0x4C = 0x00000100 RS UP
# +0x64 = 0x00000001 A
# +0x70 = 0x00040000 LB
# +0x74 = 0x00100000 LT>220
# +0x7C = 0x00000004 X
# +0x80 = 0x00200000 RT>220
# +0x84 = 0x00080000 RB
# +0x8C = 0x00400000 L3
# +0x90 = 0x00800000 R3
# +0x94 = 0x00000001 A
# +0x98 = 0x00020000 BACK
# +0x9C = 0x00000008 Y
# +0xA0 = 0x00000002 B
# +0xA4 = 0x00002000 DPAD DOWN
# +0xAC = 0x00000014 X | LS UP
# +0xB4 = 0x0000000A B | Y
# +0xB8 = 0x0000005A B | Y | LS UP | LS LEFT
# +0xBC = 0x0000000A B | Y
# +0xC0 = 0x0000005A B | Y | LS UP | LS LEFT
# +0xC4 = 0x0000000A B | Y
# +0xC8 = 0x00000008 Y
# +0xD4 = 0x00000002 B
# +0xD8 = 0x00000078 Y | LS UP | LS DOWN | LS LEFT

View File

@@ -0,0 +1,32 @@
# C_PAD_RINGBUF output record -- the tail of sub_8220D500, read from
# /image/sylpheed.pe. r3 = the ringbuf; r9 = the XINPUT_GAMEPAD source.
# This is where a menu gets edge-vs-level, and it is one struct, not two paths.
8220D7C0 7D0A582E
8220D7C4 9103000C +12 <- cur : HELD (level)
8220D7C8 7D0A582E
8220D7CC 80E3000C
8220D7D0 7D083278 r8 = cur XOR prev : CHANGED
8220D7D4 91030010 +16 <- changed : (overwritten below)
8220D7D8 5508003E
8220D7DC 7CCA582E
8220D7E0 7D063078 r6 = changed ANDC cur : RELEASED this frame
8220D7E4 90C30014 +20 <- released : FALLING EDGE
8220D7E8 7D6A582E
8220D7EC 90E30018 +24 <- cur : HELD (second copy)
8220D7F0 7D0B5838 r11 = changed AND cur : PRESSED this frame
8220D7F4 91630010 +16 <- pressed : RISING EDGE (final value)
8220D7F8 8969002A
8220D7FC 9163001C +28 <- bLeftTrigger : RAW ANALOG byte
8220D800 8969002B
8220D804 91630020 +32 <- bRightTrigger : RAW ANALOG byte
8220D808 A169002C
8220D80C 7D6B0734
# So the ring record is:
# +12 buttons HELD (level) -- a menu that repeats on hold reads this
# +16 buttons PRESSED (rising) -- a menu that fires once per press reads this
# +20 buttons RELEASED (falling)
# +24 buttons HELD (copy)
# +28 bLeftTrigger raw 0..255, analog value preserved alongside the bit
# +32 bRightTrigger raw 0..255

View File

@@ -0,0 +1,34 @@
# C_PAD_RINGBUF button word -- the REMAP from XINPUT_GAMEPAD into the game's
# own bit numbering. Built by sub_8220D500 and stored to ringbuf+12.
# Every row read straight out of /image/sylpheed.pe (VA - 0x82000000); the
# 'raw' column is the instruction word in the image at that address.
site raw XINPUT source condition ring bit bit#
8220D55C 39000001 wButtons A 0x00000001 0
8220D578 61080002 wButtons B 0x00000002 1
8220D594 61080004 wButtons X 0x00000004 2
8220D5B0 61080008 wButtons Y 0x00000008 3
8220D5CC 65080001 wButtons START 0x00010000 16
8220D5E8 65080002 wButtons BACK 0x00020000 17
8220D604 61081000 wButtons DPAD_UP 0x00001000 12
8220D620 61082000 wButtons DPAD_DOWN 0x00002000 13
8220D63C 61084000 wButtons DPAD_LEFT 0x00004000 14
8220D658 61088000 wButtons DPAD_RIGHT 0x00008000 15
8220D674 65080004 wButtons LB (left shoulder) 0x00040000 18
8220D690 65080008 wButtons RB (right shoulder) 0x00080000 19
8220D6AC 65080040 wButtons L3 (left thumb click) 0x00400000 22
8220D6C8 65080080 wButtons R3 (right thumb click) 0x00800000 23
8220D6E0 65080010 bLeftTrigger bLeftTrigger > 0xDC (220) 0x00100000 20
8220D6F8 65080020 bRightTrigger bRightTrigger > 0xDC (220) 0x00200000 21
8220D714 61080040 sThumbLX sThumbLX < -20000 0x00000040 6
8220D728 61080080 sThumbLX sThumbLX > +20000 0x00000080 7
8220D744 61080020 sThumbLY sThumbLY < -20000 0x00000020 5
8220D758 61080010 sThumbLY sThumbLY > +20000 0x00000010 4
8220D774 61080400 sThumbRX sThumbRX < -20000 0x00000400 10
8220D788 61080800 sThumbRX sThumbRX > +20000 0x00000800 11
8220D7A4 61080200 sThumbRY sThumbRY < -20000 0x00000200 9
8220D7B8 61080100 sThumbRY sThumbRY > +20000 0x00000100 8
# 24 mappings, covering every field of XINPUT_GAMEPAD.
# ring bits used: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23
# contiguous 0..23 with none repeated: True