Skip to content

Instantly share code, notes, and snippets.

@kvnxiao
Last active June 22, 2026 07:28
Show Gist options
  • Select an option

  • Save kvnxiao/32ee4fcbd2364befeddee7c2a4bff5a0 to your computer and use it in GitHub Desktop.

Select an option

Save kvnxiao/32ee4fcbd2364befeddee7c2a4bff5a0 to your computer and use it in GitHub Desktop.
Hatsukoi 1/1 NoCD patch applier / reverter
#!/usr/bin/env python3
"""Hatsukoi 1/1 NoCD patch - applier / reverter.
Forces the engine's disc-presence check (FUN_005f4500) to always report
"disc present" by overwriting its entry prologue with `mov al,1 ; ret`.
Keep THIS file (a few KB) instead of the 8.5 MB patched exe. After a reinstall,
run it against the fresh original SiglusEngine.exe to re-apply.
Usage:
python apply_nocd_patch.py [apply|revert|check] [path-to-SiglusEngine.exe]
- no path -> looks for SiglusEngine.exe next to this script, then in the
parent folder (i.e. the game dir, if this file lives in _nocd_backup).
- if action is omitted, it defaults to "apply".
"""
import os, sys, hashlib
OFFSET = 0x1F3900 # .text file offset; VMA 0x5F4500
ORIG = bytes([0x55, 0x8B, 0xEC]) # push ebp ; mov ebp,esp
PATCHED = bytes([0xB0, 0x01, 0xC3]) # mov al,1 ; ret
# 13-byte build signature that follows the 3 patched bytes (unchanged by the
# patch). Used to confirm we're looking at the known build before writing.
ANCHOR_TAIL = bytes([0x6A, 0xFF, 0x68, 0x2C, 0x6C, 0x97, 0x00,
0x64, 0xA1, 0x00, 0x00, 0x00, 0x00])
# This is the English patched executable from https://tsurezurescans.wordpress.com/2018/09/27/hatsukoi-1-1-full-english-patch-main-game-append-scenarios/
ORIG_SHA256 = "2e48aa40b5f0db720c1f520effd4b39e364ecf16a66ed0611a991d49a513e8d3"
def find_exe():
here = os.path.dirname(os.path.abspath(__file__))
parent = os.path.dirname(here)
cands = []
# If this script lives in _nocd_backup\, the real game exe is one level up;
# prefer it over the pristine backup copy that sits next to this script.
if os.path.basename(here).lower() == "_nocd_backup":
cands.append(os.path.join(parent, "SiglusEngine.exe"))
cands.append(os.path.join(here, "SiglusEngine.exe"))
cands.append(os.path.join(parent, "SiglusEngine.exe"))
for cand in cands:
if os.path.isfile(cand):
return cand
return None
def state(data):
window = data[OFFSET:OFFSET + 16]
if len(window) < 16 or window[3:16] != ANCHOR_TAIL:
return "unknown" # not the known build (updated/different exe)
head = window[0:3]
if head == PATCHED:
return "patched"
if head == ORIG:
return "original"
return "unexpected"
def main():
args = [a for a in sys.argv[1:]]
action = "apply"
path = None
for a in args:
if a in ("apply", "revert", "check"):
action = a
else:
path = a
if path is None:
path = find_exe()
if not path or not os.path.isfile(path):
sys.exit("ERROR: SiglusEngine.exe not found. Pass its path as an argument.")
data = bytearray(open(path, "rb").read())
st = state(data)
sha = hashlib.sha256(data).hexdigest()
print("target : %s" % path)
print("state : %s (sha256 %s%s)" % (
st, sha[:16], " == known-original" if sha == ORIG_SHA256 else ""))
if st == "unknown":
sys.exit("ABORT: bytes at 0x%X don't match the known build. This is likely a\n"
" different/updated SiglusEngine.exe. Not touching it to avoid\n"
" corruption. (Expected tail %s)" % (OFFSET, ANCHOR_TAIL.hex(" ")))
if st == "unexpected":
sys.exit("ABORT: unexpected bytes at the patch site; refusing to write.")
if action == "check":
print("OK (no changes made).")
return
if action == "apply":
if st == "patched":
print("Already patched. Nothing to do.")
return
data[OFFSET:OFFSET + 3] = PATCHED
open(path, "wb").write(data)
print("PATCHED: wrote %s at 0x%X (NoCD active)." % (PATCHED.hex(" "), OFFSET))
return
if action == "revert":
if st == "original":
print("Already original. Nothing to do.")
return
data[OFFSET:OFFSET + 3] = ORIG
open(path, "wb").write(data)
print("REVERTED: wrote %s at 0x%X (original disc check restored)." % (ORIG.hex(" "), OFFSET))
return
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment