Game Updater
A small, auditable updater for a game or app that you host yourself.
It does not run anything at install time. It runs only when you call the
CLI or the update() function, and it refuses to install anything that is not
covered by a valid Ed25519 signature and a matching SHA-256 hash.
What it does
- Checks the VM compatibility policy when enabled (default:
off). Withvm_policy="block", a detected VM stops the call before anything is downloaded, installed, or run. - Downloads
manifest.jsonover HTTPS. - Verifies the manifest signature against the public key embedded in this package.
- Compares the remote version with the local
version.json. - Downloads each listed file to a staging folder.
- Verifies size and SHA-256 for every file.
- Moves the verified files into the game directory.
- Writes
version.jsonwith the version, sequence, notes, and file list. - Optionally runs a patch executable — only if it is listed in the signed
manifest, hash-verified, and
--runwas passed.
What it does not do
- No
setup.py/ PEP 517 hooks that execute anything. - No
.pthfiles, nositecustomize.py, no persistence. - No obfuscation. The source is readable.
- No unsigned downloads. Every byte is checked against the signed manifest.
- No silent execution.
--runis explicit. - No stealth VM detection. The policy is explicit, documented, and printed by the CLI.
- No anti-debug, timing checks, process scanning, or security-tool interference.
- No network requests for VM detection. It only reads local OS metadata.
Pinned hello demo (Windows, opt-in)
game-updater --hello downloads a specific demo batch file, verifies its
pinned SHA-256, writes it as %TEMP%\game-updater-hello\hello.bat, and runs
it. The batch file writes hello.txt next to itself and opens Notepad. Pass
--dir to use a different folder:
game-updater --hello
game-updater --hello --dir "C:\Games\MyGame"
Python has the same default:
import game_updater
game_updater.run_hello() # %TEMP%\game-updater-hello
game_updater.run_hello(r"C:\Games\MyGame") # custom folder
| Item | Value |
|---|---|
| URL | https://hello-phi-amber.vercel.app/hello.bat |
| Size | 120 bytes |
| SHA-256 | 41e425416ddb186861598dffc847c2b7d585a9af7e94e130e2354362b61259da |
This is an explicit demo path, not part of the signed-manifest update flow. It
does not run at install time, it does not run on import, and it does not run
unless --hello (or run_hello() in Python) is called. The file is re-hashed
immediately before execution. It is Windows-only because it is a .bat file.
VM compatibility policy
The check is a configurable compatibility policy, not a stealth feature. It
runs at the start of update() and before the CLI reads local state.
| Policy | Behavior |
|---|---|
off (default) |
Skip VM detection entirely. |
allow |
Detect and report, but never block. Ordinary physical PCs and VM users both work. |
block |
If a VM is detected, raise VMBlockedError before creating the game directory, fetching the manifest, installing files, or running a patch. |
When detection runs, the CLI prints the result before doing any work. A
block refusal prints the documented error instead; off prints nothing.
vm check: physical machine (no known VM indicators found)
If a VM is detected and the policy is block:
error: VM use is disabled by policy (vm_policy='block'), but this machine
appears to be a vmware virtual machine (confidence: high). Indicators:
DMI system manufacturer is VMware; DMI system product is VMware. No update
was downloaded, installed, or run. If this is a physical machine, this is a
false positive; use vm_policy='allow' or vm_policy='off' to proceed. VM
detection is best-effort and can be wrong.
CLI exit code is 3 for a VM policy block, 2 for an update/manifest error,
and 0 on success.
Indicators used
Detection is conservative and uses only documented, read-only DMI/SMBIOS values exposed by the OS:
| Environment | Definitive indicators | Supporting only |
|---|---|---|
| Hyper-V | DMI system product Virtual Machine; DMI system version Hyper-V |
Hyper-V guest integration registry key |
| QEMU / KVM | DMI system/board manufacturer QEMU; DMI product Standard PC (Q35 ...) or Standard PC (i440FX ...) |
BIOS vendor SeaBIOS |
| VMware | DMI manufacturer, product, version, board, or BIOS containing VMware |
— |
| VirtualBox | DMI manufacturer/board/BIOS innotek; product/version VirtualBox |
— |
Unsupported platforms (including macOS) return UNKNOWN; they are not
guessed at and are allowed under block.
Rules:
- A VM result requires at least one definitive vendor-specific string.
- Supporting-only evidence (for example, SeaBIOS alone, or the Hyper-V guest
key alone) returns
UNKNOWN, notVM. - Two different definitive vendors return
UNKNOWNwithconflicting=True. - A missing or unreadable DMI source returns
UNKNOWN. blockrefuses only onVM.PHYSICALandUNKNOWNare allowed, which keeps ordinary PCs working and avoids blocking on ambiguous evidence.
What is intentionally not used
- The CPUID hypervisor-present bit. Physical Windows machines with Hyper-V, VBS, or Memory Integrity enabled can set it, causing false positives.
- Timing measurements, process scanning, debugger checks, or anti-analysis tricks.
- Network requests or external services.
- Any attempt to disable or interfere with security tools.
Limitations
VM detection is imperfect. DMI strings can be spoofed, hidden, or changed by
the hypervisor, and some physical firmware exposes unusual values. A
PHYSICAL result means "no known VM indicator was found", not proof. A VM
result can be a false positive; vm_policy="allow" or vm_policy="off" is
the documented escape hatch.
This check is not a security boundary and does not make the library impossible to crack or bypass. The real protections are the signed manifest, SHA-256 verification, HTTPS, and keeping the private signing key offline. Do not use VM detection as a substitute for those controls.
Install
The PyPI distribution is gu-7f3a2c; the import package is game_updater and
the CLI command is game-updater.
pip install gu-7f3a2c
From a local checkout:
pip install .
This package is for publishers who control both the manifest host and the signing key. Only point it at manifests you signed.
Generate a signing key
python tools/keygen.py --private game_private.pem
Put the printed public key into src/game_updater/public_key.txt.
Keep game_private.pem offline. Never commit it.
Build and sign a manifest
Create manifest.json without a signature:
{
"version": "1.4.0",
"sequence": 3,
"notes": "Season 3 patch",
"files": [
{
"path": "game/data.pak",
"url": "https://cdn.example.com/game/data-1.4.0.pak",
"sha256": "…",
"size": 12345678
},
{
"path": "game/patch.exe",
"url": "https://cdn.example.com/game/patch-1.4.0.exe",
"sha256": "…",
"size": 456789,
"executable": true
}
],
"run": "game/patch.exe"
}
sequence is a signed, non-negative integer. Increase it for every release.
The updater stores it in version.json and refuses a manifest whose sequence
is lower than the installed one, or equal while claiming a different version.
That prevents replay of an older signed manifest.
Sign it:
python tools/sign_manifest.py --key game_private.pem --manifest manifest.json --out manifest.signed.json
Upload manifest.signed.json (and the files) to your HTTPS host.
Update a game folder
game-updater --manifest https://cdn.example.com/game/manifest.signed.json --dir "C:\Games\MyGame"
Other modes:
game-updater --manifest ... --dir ... --check # show versions only
game-updater --manifest ... --dir ... --apply # install (asks first)
game-updater --manifest ... --dir ... --apply --yes
game-updater --manifest ... --dir ... --apply --run
game-updater --manifest ... --dir ... --apply --vm-policy block
--vm-policy accepts off (default), allow, or block.
Python API
from game_updater import update
result = update(
manifest_url="https://cdn.example.com/game/manifest.signed.json",
game_dir=r"C:\Games\MyGame",
apply=True,
run_patch=False,
)
print(result)
print(result.vm.summary() if result.vm else "vm check skipped")
To refuse updates on a detected VM:
from game_updater import VMBlockedError, update
try:
result = update(
manifest_url="https://cdn.example.com/game/manifest.signed.json",
game_dir=r"C:\Games\MyGame",
apply=True,
vm_policy="block",
)
except VMBlockedError as error:
print(error) # clear, documented explanation
print(error.detection.kind.value) # e.g. "vmware"
Inspect detection without updating:
from game_updater import detect_vm, collect_dmi
print(collect_dmi())
print(detect_vm().summary())
print(detect_vm().indicators)
Layout produced in the game folder
MyGame/
version.json # version, sequence, notes, file list
game/
data.pak
patch.exe
version.json is written last, after every file is verified.
Threat model
| Attack | Mitigation |
|---|---|
| Compromised CDN / HTTP host | Manifest signature check |
| Modified file in transit | SHA-256 per file |
| Manifest replay of an older signed version | Signed monotonic sequence; lower or unchanged sequences for a different version are rejected |
Path traversal (../) |
safe_join rejects absolute paths and .. |
| Unsigned patch execution | run target must be in files, verified, and --run passed |
| Malicious local manifest URL | HTTPS only (localhost allowed only with --allow-insecure) |
| VM use against policy | vm_policy="block" stops before any work; best-effort detection, not a security boundary |
Honest limitation
If someone steals your private signing key, they can sign a malicious update. Treat the key like a code-signing key: offline, backed up, never in the repo.
VM detection is a compatibility policy, not DRM. DMI values can be spoofed or withheld, so the check can be wrong in both directions. It does not make the library uncrackable; the signed manifest and SHA-256 checks are the actual integrity controls.
Metadata
Release files for gu-7f3a2c 0.4.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| gu_7f3a2c-0.4.2.tar.gz | 25.3 kB | Without provenance | Details for gu_7f3a2c-0.4.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| gu_7f3a2c-0.4.2-py3-none-any.whl | Python 3 | none | any | Details for gu_7f3a2c-0.4.2-py3-none-any.whl |
Total release size: 47.6 kB
Release files / gu_7f3a2c-0.4.2.tar.gz
| Download URL | gu_7f3a2c-0.4.2.tar.gz |
|---|---|
| Size | 25.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f352d020080b54a9c3d874e42f7fa2fd963ad47f18a4d3894e720b80ebeff28b
|
|
BLAKE2b-256 checksum How to use checksums |
b4caaa2c5683be192c043aed0d1c98ea8cd7466c17f158cd6fa392bd5e985fdc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.11
|
Release files / gu_7f3a2c-0.4.2-py3-none-any.whl
| Download URL | gu_7f3a2c-0.4.2-py3-none-any.whl |
|---|---|
| Size | 22.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cffe234f87f319d62564dc830bd3f8d9bbc2deb18a3d94fa0ef05c3748a62e57
|
|
BLAKE2b-256 checksum How to use checksums |
176845813b6ee03f84e9ad882d94ae335ba4b382519d0eaaa5f9d873c1126db8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.11
|
Security for gu-7f3a2c 0.4.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.