Fix button extraction for delta-encoded usercmds - #348
Open
WangChuDi wants to merge 2 commits into
Open
Conversation
|
Are we getting this baked any time soon? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
This PR ports the missing
codegen_delta_encoderbehavior identified while investigating #343 and keeps the existingparse_ticksusercmd path as the public interface. It covers the command-number baseline ring, baseline validation, wire type 7 resets, nested message merges, and indexed repeated delta operations required by post-July demos.The decoder behavior missing from #343
#343 could decode the ordinary protobuf shape of
delta_data, but it did not model the client-side command state closely enough. The game does not apply every delta to one unverified last command per player: it selects a baseline from a 150-slot command-number ring, verifies the stored command number, and refuses a missing or mismatched baseline without mutating the previous state.The delta stream is also not an ordinary protobuf replacement message. Valve's generated encoder uses wire type 7 to clear protobuf presence and restore generated defaults, recursively merges nested messages, and uses indexed operations for repeated
input_historyandsubtick_movesentries. Treating these operations as ordinary field replacement can produce a syntactically valid command with fields inherited from the wrong baseline or repeated entries lost.Changes in this PR
parse_tickspath, including buttons, mouse movement, weapon selection, input history, subtick moves, execution notes, prediction fields, and attack-history indexes.parse_ticks(["buttons"])routing fix so aggregate button requests activate the stateful decoder and remain on the ordered parser path.No
parseusercmdsAPI is added. The DLL comparison hooks and counters are test/diagnostic support only; production parsing does not loadclient.dllor require HLAE.Public usercmd properties
Relative to the current
fork/mainbaseline, this PR additionally exposes the following usercmd properties through the existing property mapping:usercmd_command_number,usercmd_server_tick_executed,usercmd_legacy_command_number,usercmd_base_client_tick,usercmd_prediction_offset_ticks_x256,usercmd_up_move,usercmd_random_seed,usercmd_move_crc,usercmd_cmd_flags,usercmd_execution_notes,usercmd_attack1_start_history_index,usercmd_attack2_start_history_index,usercmd_is_predicting_body_shot_fx,usercmd_is_predicting_head_shot_fx,usercmd_is_predicting_kill_ragdolls,usercmd_transport_client_tick,usercmd_pawn_entity_handle,usercmd_player_slot,usercmd_subtick_move_analog_forward_delta,usercmd_subtick_move_analog_left_delta,usercmd_subtick_move_button,usercmd_subtick_move_when,usercmd_subtick_move_pitch_delta, andusercmd_subtick_move_yaw_delta.The baseline already exposed
usercmd_viewangle_x/y/z,usercmd_forward_move,usercmd_left_move,usercmd_impulse,usercmd_mouse_dx/dy,usercmd_buttonstate_1/2/3,usercmd_weapon_select,usercmd_left_hand_desired,usercmd_consumed_server_angle_changes,usercmd_input_history, andusercmd_subtick_moves; this PR fixes their reconstruction when the transport uses delta encoding.buttonsremains the aggregate convenience property, whileusercmd_buttonstate_1/2/3expose the raw button masks.All of these public property names can be requested directly through the existing
parse_ticks([...])API; noparseusercmdsAPI is required. For example:parser.parse_ticks(["usercmd_command_number", "usercmd_viewangle_x", "usercmd_mouse_dx", "usercmd_weapon_select", "usercmd_input_history", "usercmd_subtick_moves"]).usercmd_input_historyandusercmd_subtick_movesare repeated nested structures, so their output contains structured lists and child values.usercmd_move_crcis exposed as a hexadecimal string. Command number, server tick, player slot, pawn handle, and transport client tick are envelope metadata projected alongside the reconstructedCSGOUserCmdPB, whileusercmd_execution_notesis diagnostic information rather than a protobuf payload field.The internal decoder reconstructs the complete known usercmd payload and the DLL differential test reports zero field differences, but
parse_ticksintentionally exposes only the mapped properties listed above; arbitrary future protobuf fields are not automatically exported.Game DLL reference
The reference implementation was observed from an offline
-insecureCS2 process using the public CS2 usercmd DLL oracle. IDA was used to identify the pre-decode transport path, the post-decode point after ring/cache updates, and the generatedCSGOUserCmdPBserializer. The probe records the game's result; it does not replace the game decoder or run inside demoparser.Rejected DLL transports are recorded as rejected because the game never produces a complete post-decode payload for them. The parser therefore rejects the same transport rather than inventing a command from an unavailable baseline.
Validation demo and baseline
The validation demo is the demo attached to the issue #340 comment, stored locally as
issue340_pr343_sample.dem. The comparison baseline is LaihoE/demoparserorigin/mainat commit266a831f08b0264dd722b017a5c05d765206a7ed.Decoder result comparison
The table below uses the same demo for the DLL oracle and the current parser. The DLL column counts every transport observation, the
origin/maincolumn is the captured baseline at266a831, and the current parser column shows raw/aligned transport counts where publicparse_ticksoutput intentionally deduplicates by(player_slot, command_number).origin/main57414d857414d8vs DLL)datarecordsdelta_datarecords1,300,726is successful delta application count, not a DLL transport count.(player_slot, command_number)keys.cannot_movecannot_movecomes from usercmd execution notes; the DLL counts transport records, while the parser metric is recorded only for unique public commands.deaddeadis derived from demo entitylife_statethrough the pawn handle; it is not a field in the DLL usercmd payload.The current parser statistics were generated after the ring and decoder changes: raw
full_data=2,221, rawdelta_data=1,321,093,delta_applied=1,300,726,baseline_missing=20,340, andbaseline_mismatch=0. Public totals are lower because repeated checkpoint records and repeated command keys are not emitted as new public command rows.The full streaming differential test compared all 1,302,947 decoded payloads and 20,357 rejected records against the game oracle. It compared scalar fields, nested messages, repeated input-history entries, repeated subtick entries, command metadata, and ring/cache command numbers and reported zero protobuf field differences.
Values marked with
*are not game truth:origin/mainused one unchecked baseline per player and did not model command-number mismatch behavior. The differences in aggregate public counts are event-versus-public-key scope differences, not a failure of the aligned field comparison.Tests
cargo test --manifest-path src/parser/Cargo.toml: 346 passed, 0 failed, 2 ignored.matches_full_instrumented_dll_usercmd_stream_field_for_fieldwith the complete binary oracle: 1 passed, 0 failed; 1,302,947 decoded payloads and 20,357 rejected records aligned.git diff --check: passed.Completion and limitations
For the tested client.dll build and issue demo, the standalone decoder now matches the game's post-decode usercmd payload field for field, including the command-number baseline behavior and rejection boundaries. Future CS2 client updates may change generated fields, defaults, delta schemas, ring behavior, or hook signatures and require a new oracle capture and review.
GPT assistance disclosure
As with #343, GPT models were used to assist with the reverse-engineering analysis and demoparser code changes. The implementation was checked against IDA observations, the real issue demo, the
origin/mainbaseline, and the injected client.dll oracle.