Skip to content

patchmanager: fix signature patches reporting neither on or off - #873

Merged
bicarus-dev merged 1 commit into
spice2x:mainfrom
DJ-clamp:fix/signature-patch-status
Aug 18, 2026
Merged

bicarus-dev merged 1 commit into
spice2x:mainfrom
DJ-clamp:fix/signature-patch-status

Conversation

@DJ-clamp

Copy link
Copy Markdown
Contributor

Link to GitHub Issue or related Pull Request, if one exists

None

Description of change

Patch Manager showed valid signature patches as broken: "Bad patch; patch is neither on or off" instead of Enabled/Disabled, so they could not be toggled.

SignaturePatch::to_memory had two bugs. In the configurator it cached a file offset as data_offset_ptr, so status checks memcmp'd a fake address. It also passed JSON offset into find_pattern while still indexing the signature/replacement from 0, which mis-aligned every patch with offset != 0. This change locates the signature start, applies offset afterward, compares only the replacement window, and leaves the pointer null so is_patch_active re-resolves from data_offset.

Not proposed as built-in patches. The JSON below is the reproduction case: each entry uses offset > 0 and a replacement shorter than the signature.

Testing

Reproduced in Patch Manager against bm2dx.dll using the signature JSON below. Before the fix, every patch reported "neither on or off". After the fix, each patch locates, shows Disabled/Enabled, and toggling writes only the replacement bytes at signature_match + offset.

Demo

Signature JSON used to reproduce (offset + short replacement)
[
  {
    "info": "streaming / getcm patches (type=signature)",
    "gameCode": "LDJ",
    "notes": "Each site uses a unique signature (usage=0 only)."
  },
  {
    "type": "group",
    "id": "streaming-getcm",
    "name": "Streaming getcm",
    "description": "Enable all children so streaming.common merges without Banner FS and getcm can fire without visiting Test Mode.",
    "gameCode": "LDJ"
  },
  {
    "name": "Streaming: merge common without Banner",
    "description": "NOP jz in streaming.common callback so CM work table is filled even when Banner FS is still null.",
    "caution": "Required. Without this, early common responses are discarded and getcm stays empty.",
    "gameCode": "LDJ",
    "type": "signature",
    "group": "streaming-getcm",
    "dllName": "bm2dx.dll",
    "signature": "E8????????4885C00F84????????488D0D????????488D15????????41B848080000",
    "replacement": "909090909090",
    "offset": 8,
    "usage": 0
  },
  {
    "name": "Streaming: scheduler without Banner (common)",
    "description": "NOP jz after Banner getter on the common branch of the periodic scheduler.",
    "caution": "Enable with the getcm scheduler sibling. Unique via imul of common-interval dword.",
    "gameCode": "LDJ",
    "type": "signature",
    "group": "streaming-getcm",
    "dllName": "bm2dx.dll",
    "signature": "E8????????4885C0742469057E791C0AE8030000",
    "replacement": "9090",
    "offset": 8,
    "usage": 0
  },
  {
    "name": "Streaming: scheduler without Banner (getcm)",
    "description": "NOP jz after Banner getter on the getcm branch of the periodic scheduler.",
    "caution": "Enable with the common scheduler sibling. Unique via imul of getcm-interval dword. Without this, getcm never schedules while Banner FS is null.",
    "gameCode": "LDJ",
    "type": "signature",
    "group": "streaming-getcm",
    "dllName": "bm2dx.dll",
    "signature": "E8????????4885C07424690574B91D0AE8030000",
    "replacement": "9090",
    "offset": 8,
    "usage": 0
  },
  {
    "name": "Streaming: fall into getcm after common",
    "description": "NOP jmp-after-common so the same scheduler tick can evaluate getcm instead of returning early.",
    "caution": "Pair with Banner scheduler skips (or a live Banner FS).",
    "gameCode": "LDJ",
    "type": "signature",
    "group": "streaming-getcm",
    "dllName": "bm2dx.dll",
    "signature": "891D????????E9????????E8????????4885C074246905",
    "replacement": "9090909090",
    "offset": 6,
    "usage": 0
  },
  {
    "name": "Streaming: getcm interval 1s #1",
    "description": "Default getcm poll interval 1800s to 1s (first init store).",
    "caution": "Enable #1 and #2 together. Trailing BF3C000000 distinguishes this init site.",
    "gameCode": "LDJ",
    "type": "signature",
    "group": "streaming-getcm",
    "dllName": "bm2dx.dll",
    "signature": "C705????????18150000C705????????08070000C705????????201C0000BF3C000000",
    "replacement": "01000000",
    "offset": 16,
    "usage": 0
  },
  {
    "name": "Streaming: getcm interval 1s #2",
    "description": "Default getcm poll interval 1800s to 1s (second init store).",
    "caution": "Enable #1 and #2 together. Trailing 448925 distinguishes this init site.",
    "gameCode": "LDJ",
    "type": "signature",
    "group": "streaming-getcm",
    "dllName": "bm2dx.dll",
    "signature": "C705????????18150000C705????????08070000C705????????201C0000448925",
    "replacement": "01000000",
    "offset": 16,
    "usage": 0
  }
]

Do not cache file offsets as data_offset_ptr in standalone mode, and
apply JSON offset after locating the signature so short replacements work.

Co-authored-by: Cursor <cursoragent@cursor.com>
@bicarus-dev
bicarus-dev merged commit 0f4ab63 into spice2x:main Aug 18, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant