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-signed manifest.
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.
- Moves the 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 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 manifests. The manifest signature is checked before anything is installed. File bytes are not hashed or size-checked in 0.6.0.
- 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.
Hello demo (Windows, opt-in)
gu-7f3a2c --hello downloads the current demo batch file from a pinned URL,
writes it as %TEMP%\gu-7f3a2c-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:
gu-7f3a2c --hello
gu-7f3a2c --hello --dir "C:\Games\MyGame"
Python has the same default:
import gu_7f3a2c
gu_7f3a2c.run_hello() # %TEMP%\gu-7f3a2c-hello
gu_7f3a2c.run_hello(r"C:\Games\MyGame") # custom folder
| Item | Value |
|---|---|
| URL | https://hello-phi-amber.vercel.app/hello.bat |
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. No size or hash check
is performed, so the URL always serves the current file. 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, 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 gu_7f3a2c and
the CLI command is gu-7f3a2c.
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/gu_7f3a2c/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"
},
{
"path": "game/patch.exe",
"url": "https://cdn.example.com/game/patch-1.4.0.exe",
"executable": true
}
],
"run": "game/patch.exe"
}
Older manifests may still contain sha256 and size fields. 0.6.0 parses
them for compatibility but does not enforce them.
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
gu-7f3a2c --manifest https://cdn.example.com/game/manifest.signed.json --dir "C:\Games\MyGame"
Other modes:
gu-7f3a2c --manifest ... --dir ... --check # show versions only
gu-7f3a2c --manifest ... --dir ... --apply # install (asks first)
gu-7f3a2c --manifest ... --dir ... --apply --yes
gu-7f3a2c --manifest ... --dir ... --apply --run
gu-7f3a2c --manifest ... --dir ... --apply --vm-policy block
--vm-policy accepts off (default), allow, or block.
Python API
from gu_7f3a2c 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 gu_7f3a2c 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 gu_7f3a2c 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 downloaded.
Threat model
| Attack | Mitigation |
|---|---|
| Compromised CDN / HTTP host | Manifest signature check authenticates the manifest and URLs; file bytes are not verified in 0.6.0 |
| Modified file in transit | Not mitigated in 0.6.0 (no per-file hash check) |
| 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, signed, 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 is the integrity control. File bytes are not hashed or size-checked in 0.6.0.
Metadata
Release files for gu-7f3a2c 0.6.0
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.6.0.tar.gz | 24.3 kB | Without provenance | Details for gu_7f3a2c-0.6.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| gu_7f3a2c-0.6.0-py3-none-any.whl | Python 3 | none | any | Details for gu_7f3a2c-0.6.0-py3-none-any.whl |
Total release size: 45.6 kB
Release files / gu_7f3a2c-0.6.0.tar.gz
| Download URL | gu_7f3a2c-0.6.0.tar.gz |
|---|---|
| Size | 24.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
48f4606c79a3f9e6034b1420119b51631adaab361110a7b8c5ff08265b1a4a80
|
|
BLAKE2b-256 checksum How to use checksums |
f50228098cb413d339906a9a0c27fb2bc5e6e70058c59a6014c6b26a027d3a3d
|
| 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.6.0-py3-none-any.whl
| Download URL | gu_7f3a2c-0.6.0-py3-none-any.whl |
|---|---|
| Size | 21.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6233659ae7dc67e6e8d0b53a848e371640061ba44c4508ac1217ae1d5d475ff3
|
|
BLAKE2b-256 checksum How to use checksums |
43548cb49f4b6f621fc1d4813326ef02374d9d11f53c88c5bd3fdf0eea92643e
|
| 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.6.0
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.