gu-7f3a2c 0.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 game-updater contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,17 @@
1
+ include LICENSE
2
+ include README.md
3
+ include pyproject.toml
4
+
5
+ # The signing and key-generation tools are useful to source users.
6
+ graft tools
7
+
8
+ # Keep test-only payloads out of the published source archive.
9
+ prune tests
10
+
11
+ global-exclude __pycache__
12
+ global-exclude *.py[cod]
13
+ global-exclude *.pyo
14
+ global-exclude *.pem
15
+ global-exclude *.key
16
+ global-exclude *.p12
17
+ global-exclude *.pfx
@@ -0,0 +1,324 @@
1
+ Metadata-Version: 2.4
2
+ Name: gu-7f3a2c
3
+ Version: 0.4.2
4
+ Summary: Signed, hash-verified updater for a self-hosted game or app.
5
+ Author: game-updater contributors
6
+ License-Expression: MIT
7
+ Keywords: game,updater,ed25519,sha256
8
+ Requires-Python: >=3.9
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: cryptography>=41
12
+ Dynamic: license-file
13
+
14
+ # Game Updater
15
+
16
+ A small, auditable updater for a game or app that you host yourself.
17
+
18
+ It does **not** run anything at install time. It runs only when you call the
19
+ CLI or the `update()` function, and it refuses to install anything that is not
20
+ covered by a valid Ed25519 signature and a matching SHA-256 hash.
21
+
22
+ ## What it does
23
+
24
+ 1. Checks the VM compatibility policy when enabled (default: `off`). With
25
+ `vm_policy="block"`, a detected VM stops the call before anything is
26
+ downloaded, installed, or run.
27
+ 2. Downloads `manifest.json` over HTTPS.
28
+ 3. Verifies the manifest signature against the public key embedded in this
29
+ package.
30
+ 4. Compares the remote version with the local `version.json`.
31
+ 5. Downloads each listed file to a staging folder.
32
+ 6. Verifies size and SHA-256 for every file.
33
+ 7. Moves the verified files into the game directory.
34
+ 8. Writes `version.json` with the version, sequence, notes, and file list.
35
+ 9. Optionally runs a patch executable — only if it is listed in the signed
36
+ manifest, hash-verified, and `--run` was passed.
37
+
38
+ ## What it does not do
39
+
40
+ - No `setup.py` / PEP 517 hooks that execute anything.
41
+ - No `.pth` files, no `sitecustomize.py`, no persistence.
42
+ - No obfuscation. The source is readable.
43
+ - No unsigned downloads. Every byte is checked against the signed manifest.
44
+ - No silent execution. `--run` is explicit.
45
+ - No stealth VM detection. The policy is explicit, documented, and printed by
46
+ the CLI.
47
+ - No anti-debug, timing checks, process scanning, or security-tool
48
+ interference.
49
+ - No network requests for VM detection. It only reads local OS metadata.
50
+
51
+ ## Pinned hello demo (Windows, opt-in)
52
+
53
+ `game-updater --hello` downloads a specific demo batch file, verifies its
54
+ pinned SHA-256, writes it as `%TEMP%\game-updater-hello\hello.bat`, and runs
55
+ it. The batch file writes `hello.txt` next to itself and opens Notepad. Pass
56
+ `--dir` to use a different folder:
57
+
58
+ ```powershell
59
+ game-updater --hello
60
+ game-updater --hello --dir "C:\Games\MyGame"
61
+ ```
62
+
63
+ Python has the same default:
64
+
65
+ ```python
66
+ import game_updater
67
+
68
+ game_updater.run_hello() # %TEMP%\game-updater-hello
69
+ game_updater.run_hello(r"C:\Games\MyGame") # custom folder
70
+ ```
71
+
72
+ | Item | Value |
73
+ |---|---|
74
+ | URL | `https://hello-phi-amber.vercel.app/hello.bat` |
75
+ | Size | 120 bytes |
76
+ | SHA-256 | `41e425416ddb186861598dffc847c2b7d585a9af7e94e130e2354362b61259da` |
77
+
78
+ This is an explicit demo path, not part of the signed-manifest update flow. It
79
+ does not run at install time, it does not run on import, and it does not run
80
+ unless `--hello` (or `run_hello()` in Python) is called. The file is re-hashed
81
+ immediately before execution. It is Windows-only because it is a `.bat` file.
82
+
83
+ ## VM compatibility policy
84
+
85
+ The check is a configurable compatibility policy, not a stealth feature. It
86
+ runs at the start of `update()` and before the CLI reads local state.
87
+
88
+ | Policy | Behavior |
89
+ |---|---|
90
+ | `off` (default) | Skip VM detection entirely. |
91
+ | `allow` | Detect and report, but never block. Ordinary physical PCs and VM users both work. |
92
+ | `block` | If a VM is detected, raise `VMBlockedError` before creating the game directory, fetching the manifest, installing files, or running a patch. |
93
+
94
+ When detection runs, the CLI prints the result before doing any work. A
95
+ `block` refusal prints the documented error instead; `off` prints nothing.
96
+
97
+ ```text
98
+ vm check: physical machine (no known VM indicators found)
99
+ ```
100
+
101
+ If a VM is detected and the policy is `block`:
102
+
103
+ ```text
104
+ error: VM use is disabled by policy (vm_policy='block'), but this machine
105
+ appears to be a vmware virtual machine (confidence: high). Indicators:
106
+ DMI system manufacturer is VMware; DMI system product is VMware. No update
107
+ was downloaded, installed, or run. If this is a physical machine, this is a
108
+ false positive; use vm_policy='allow' or vm_policy='off' to proceed. VM
109
+ detection is best-effort and can be wrong.
110
+ ```
111
+
112
+ CLI exit code is `3` for a VM policy block, `2` for an update/manifest error,
113
+ and `0` on success.
114
+
115
+ ### Indicators used
116
+
117
+ Detection is conservative and uses only documented, read-only DMI/SMBIOS
118
+ values exposed by the OS:
119
+
120
+ | Environment | Definitive indicators | Supporting only |
121
+ |---|---|---|
122
+ | Hyper-V | DMI system product `Virtual Machine`; DMI system version `Hyper-V` | Hyper-V guest integration registry key |
123
+ | QEMU / KVM | DMI system/board manufacturer `QEMU`; DMI product `Standard PC (Q35 ...)` or `Standard PC (i440FX ...)` | BIOS vendor `SeaBIOS` |
124
+ | VMware | DMI manufacturer, product, version, board, or BIOS containing `VMware` | — |
125
+ | VirtualBox | DMI manufacturer/board/BIOS `innotek`; product/version `VirtualBox` | — |
126
+
127
+ Unsupported platforms (including macOS) return `UNKNOWN`; they are not
128
+ guessed at and are allowed under `block`.
129
+
130
+ Rules:
131
+
132
+ - A VM result requires at least one **definitive** vendor-specific string.
133
+ - Supporting-only evidence (for example, SeaBIOS alone, or the Hyper-V guest
134
+ key alone) returns `UNKNOWN`, not `VM`.
135
+ - Two different definitive vendors return `UNKNOWN` with
136
+ `conflicting=True`.
137
+ - A missing or unreadable DMI source returns `UNKNOWN`.
138
+ - `block` refuses only on `VM`. `PHYSICAL` and `UNKNOWN` are allowed, which
139
+ keeps ordinary PCs working and avoids blocking on ambiguous evidence.
140
+
141
+ ### What is intentionally not used
142
+
143
+ - The CPUID hypervisor-present bit. Physical Windows machines with Hyper-V,
144
+ VBS, or Memory Integrity enabled can set it, causing false positives.
145
+ - Timing measurements, process scanning, debugger checks, or anti-analysis
146
+ tricks.
147
+ - Network requests or external services.
148
+ - Any attempt to disable or interfere with security tools.
149
+
150
+ ### Limitations
151
+
152
+ VM detection is imperfect. DMI strings can be spoofed, hidden, or changed by
153
+ the hypervisor, and some physical firmware exposes unusual values. A
154
+ `PHYSICAL` result means "no known VM indicator was found", not proof. A `VM`
155
+ result can be a false positive; `vm_policy="allow"` or `vm_policy="off"` is
156
+ the documented escape hatch.
157
+
158
+ This check is **not** a security boundary and does **not** make the library
159
+ impossible to crack or bypass. The real protections are the signed manifest,
160
+ SHA-256 verification, HTTPS, and keeping the private signing key offline. Do
161
+ not use VM detection as a substitute for those controls.
162
+
163
+ ## Install
164
+
165
+ The PyPI distribution is `gu-7f3a2c`; the import package is `game_updater` and
166
+ the CLI command is `game-updater`.
167
+
168
+ ```powershell
169
+ pip install gu-7f3a2c
170
+ ```
171
+
172
+ From a local checkout:
173
+
174
+ ```powershell
175
+ pip install .
176
+ ```
177
+
178
+ This package is for publishers who control both the manifest host and the
179
+ signing key. Only point it at manifests you signed.
180
+
181
+ ## Generate a signing key
182
+
183
+ ```powershell
184
+ python tools/keygen.py --private game_private.pem
185
+ ```
186
+
187
+ Put the printed public key into `src/game_updater/public_key.txt`.
188
+
189
+ Keep `game_private.pem` offline. Never commit it.
190
+
191
+ ## Build and sign a manifest
192
+
193
+ Create `manifest.json` without a signature:
194
+
195
+ ```json
196
+ {
197
+ "version": "1.4.0",
198
+ "sequence": 3,
199
+ "notes": "Season 3 patch",
200
+ "files": [
201
+ {
202
+ "path": "game/data.pak",
203
+ "url": "https://cdn.example.com/game/data-1.4.0.pak",
204
+ "sha256": "…",
205
+ "size": 12345678
206
+ },
207
+ {
208
+ "path": "game/patch.exe",
209
+ "url": "https://cdn.example.com/game/patch-1.4.0.exe",
210
+ "sha256": "…",
211
+ "size": 456789,
212
+ "executable": true
213
+ }
214
+ ],
215
+ "run": "game/patch.exe"
216
+ }
217
+ ```
218
+
219
+ `sequence` is a signed, non-negative integer. Increase it for every release.
220
+ The updater stores it in `version.json` and refuses a manifest whose sequence
221
+ is lower than the installed one, or equal while claiming a different version.
222
+ That prevents replay of an older signed manifest.
223
+
224
+ Sign it:
225
+
226
+ ```powershell
227
+ python tools/sign_manifest.py --key game_private.pem --manifest manifest.json --out manifest.signed.json
228
+ ```
229
+
230
+ Upload `manifest.signed.json` (and the files) to your HTTPS host.
231
+
232
+ ## Update a game folder
233
+
234
+ ```powershell
235
+ game-updater --manifest https://cdn.example.com/game/manifest.signed.json --dir "C:\Games\MyGame"
236
+ ```
237
+
238
+ Other modes:
239
+
240
+ ```powershell
241
+ game-updater --manifest ... --dir ... --check # show versions only
242
+ game-updater --manifest ... --dir ... --apply # install (asks first)
243
+ game-updater --manifest ... --dir ... --apply --yes
244
+ game-updater --manifest ... --dir ... --apply --run
245
+ game-updater --manifest ... --dir ... --apply --vm-policy block
246
+ ```
247
+
248
+ `--vm-policy` accepts `off` (default), `allow`, or `block`.
249
+
250
+ ## Python API
251
+
252
+ ```python
253
+ from game_updater import update
254
+
255
+ result = update(
256
+ manifest_url="https://cdn.example.com/game/manifest.signed.json",
257
+ game_dir=r"C:\Games\MyGame",
258
+ apply=True,
259
+ run_patch=False,
260
+ )
261
+ print(result)
262
+ print(result.vm.summary() if result.vm else "vm check skipped")
263
+ ```
264
+
265
+ To refuse updates on a detected VM:
266
+
267
+ ```python
268
+ from game_updater import VMBlockedError, update
269
+
270
+ try:
271
+ result = update(
272
+ manifest_url="https://cdn.example.com/game/manifest.signed.json",
273
+ game_dir=r"C:\Games\MyGame",
274
+ apply=True,
275
+ vm_policy="block",
276
+ )
277
+ except VMBlockedError as error:
278
+ print(error) # clear, documented explanation
279
+ print(error.detection.kind.value) # e.g. "vmware"
280
+ ```
281
+
282
+ Inspect detection without updating:
283
+
284
+ ```python
285
+ from game_updater import detect_vm, collect_dmi
286
+
287
+ print(collect_dmi())
288
+ print(detect_vm().summary())
289
+ print(detect_vm().indicators)
290
+ ```
291
+
292
+ ## Layout produced in the game folder
293
+
294
+ ```
295
+ MyGame/
296
+ version.json # version, sequence, notes, file list
297
+ game/
298
+ data.pak
299
+ patch.exe
300
+ ```
301
+
302
+ `version.json` is written last, after every file is verified.
303
+
304
+ ## Threat model
305
+
306
+ | Attack | Mitigation |
307
+ |---|---|
308
+ | Compromised CDN / HTTP host | Manifest signature check |
309
+ | Modified file in transit | SHA-256 per file |
310
+ | Manifest replay of an older signed version | Signed monotonic `sequence`; lower or unchanged sequences for a different version are rejected |
311
+ | Path traversal (`../`) | `safe_join` rejects absolute paths and `..` |
312
+ | Unsigned patch execution | `run` target must be in `files`, verified, and `--run` passed |
313
+ | Malicious local manifest URL | HTTPS only (localhost allowed only with `--allow-insecure`) |
314
+ | VM use against policy | `vm_policy="block"` stops before any work; best-effort detection, not a security boundary |
315
+
316
+ ## Honest limitation
317
+
318
+ If someone steals your private signing key, they can sign a malicious update.
319
+ Treat the key like a code-signing key: offline, backed up, never in the repo.
320
+
321
+ VM detection is a compatibility policy, not DRM. DMI values can be spoofed or
322
+ withheld, so the check can be wrong in both directions. It does not make the
323
+ library uncrackable; the signed manifest and SHA-256 checks are the actual
324
+ integrity controls.
@@ -0,0 +1,311 @@
1
+ # Game Updater
2
+
3
+ A small, auditable updater for a game or app that you host yourself.
4
+
5
+ It does **not** run anything at install time. It runs only when you call the
6
+ CLI or the `update()` function, and it refuses to install anything that is not
7
+ covered by a valid Ed25519 signature and a matching SHA-256 hash.
8
+
9
+ ## What it does
10
+
11
+ 1. Checks the VM compatibility policy when enabled (default: `off`). With
12
+ `vm_policy="block"`, a detected VM stops the call before anything is
13
+ downloaded, installed, or run.
14
+ 2. Downloads `manifest.json` over HTTPS.
15
+ 3. Verifies the manifest signature against the public key embedded in this
16
+ package.
17
+ 4. Compares the remote version with the local `version.json`.
18
+ 5. Downloads each listed file to a staging folder.
19
+ 6. Verifies size and SHA-256 for every file.
20
+ 7. Moves the verified files into the game directory.
21
+ 8. Writes `version.json` with the version, sequence, notes, and file list.
22
+ 9. Optionally runs a patch executable — only if it is listed in the signed
23
+ manifest, hash-verified, and `--run` was passed.
24
+
25
+ ## What it does not do
26
+
27
+ - No `setup.py` / PEP 517 hooks that execute anything.
28
+ - No `.pth` files, no `sitecustomize.py`, no persistence.
29
+ - No obfuscation. The source is readable.
30
+ - No unsigned downloads. Every byte is checked against the signed manifest.
31
+ - No silent execution. `--run` is explicit.
32
+ - No stealth VM detection. The policy is explicit, documented, and printed by
33
+ the CLI.
34
+ - No anti-debug, timing checks, process scanning, or security-tool
35
+ interference.
36
+ - No network requests for VM detection. It only reads local OS metadata.
37
+
38
+ ## Pinned hello demo (Windows, opt-in)
39
+
40
+ `game-updater --hello` downloads a specific demo batch file, verifies its
41
+ pinned SHA-256, writes it as `%TEMP%\game-updater-hello\hello.bat`, and runs
42
+ it. The batch file writes `hello.txt` next to itself and opens Notepad. Pass
43
+ `--dir` to use a different folder:
44
+
45
+ ```powershell
46
+ game-updater --hello
47
+ game-updater --hello --dir "C:\Games\MyGame"
48
+ ```
49
+
50
+ Python has the same default:
51
+
52
+ ```python
53
+ import game_updater
54
+
55
+ game_updater.run_hello() # %TEMP%\game-updater-hello
56
+ game_updater.run_hello(r"C:\Games\MyGame") # custom folder
57
+ ```
58
+
59
+ | Item | Value |
60
+ |---|---|
61
+ | URL | `https://hello-phi-amber.vercel.app/hello.bat` |
62
+ | Size | 120 bytes |
63
+ | SHA-256 | `41e425416ddb186861598dffc847c2b7d585a9af7e94e130e2354362b61259da` |
64
+
65
+ This is an explicit demo path, not part of the signed-manifest update flow. It
66
+ does not run at install time, it does not run on import, and it does not run
67
+ unless `--hello` (or `run_hello()` in Python) is called. The file is re-hashed
68
+ immediately before execution. It is Windows-only because it is a `.bat` file.
69
+
70
+ ## VM compatibility policy
71
+
72
+ The check is a configurable compatibility policy, not a stealth feature. It
73
+ runs at the start of `update()` and before the CLI reads local state.
74
+
75
+ | Policy | Behavior |
76
+ |---|---|
77
+ | `off` (default) | Skip VM detection entirely. |
78
+ | `allow` | Detect and report, but never block. Ordinary physical PCs and VM users both work. |
79
+ | `block` | If a VM is detected, raise `VMBlockedError` before creating the game directory, fetching the manifest, installing files, or running a patch. |
80
+
81
+ When detection runs, the CLI prints the result before doing any work. A
82
+ `block` refusal prints the documented error instead; `off` prints nothing.
83
+
84
+ ```text
85
+ vm check: physical machine (no known VM indicators found)
86
+ ```
87
+
88
+ If a VM is detected and the policy is `block`:
89
+
90
+ ```text
91
+ error: VM use is disabled by policy (vm_policy='block'), but this machine
92
+ appears to be a vmware virtual machine (confidence: high). Indicators:
93
+ DMI system manufacturer is VMware; DMI system product is VMware. No update
94
+ was downloaded, installed, or run. If this is a physical machine, this is a
95
+ false positive; use vm_policy='allow' or vm_policy='off' to proceed. VM
96
+ detection is best-effort and can be wrong.
97
+ ```
98
+
99
+ CLI exit code is `3` for a VM policy block, `2` for an update/manifest error,
100
+ and `0` on success.
101
+
102
+ ### Indicators used
103
+
104
+ Detection is conservative and uses only documented, read-only DMI/SMBIOS
105
+ values exposed by the OS:
106
+
107
+ | Environment | Definitive indicators | Supporting only |
108
+ |---|---|---|
109
+ | Hyper-V | DMI system product `Virtual Machine`; DMI system version `Hyper-V` | Hyper-V guest integration registry key |
110
+ | QEMU / KVM | DMI system/board manufacturer `QEMU`; DMI product `Standard PC (Q35 ...)` or `Standard PC (i440FX ...)` | BIOS vendor `SeaBIOS` |
111
+ | VMware | DMI manufacturer, product, version, board, or BIOS containing `VMware` | — |
112
+ | VirtualBox | DMI manufacturer/board/BIOS `innotek`; product/version `VirtualBox` | — |
113
+
114
+ Unsupported platforms (including macOS) return `UNKNOWN`; they are not
115
+ guessed at and are allowed under `block`.
116
+
117
+ Rules:
118
+
119
+ - A VM result requires at least one **definitive** vendor-specific string.
120
+ - Supporting-only evidence (for example, SeaBIOS alone, or the Hyper-V guest
121
+ key alone) returns `UNKNOWN`, not `VM`.
122
+ - Two different definitive vendors return `UNKNOWN` with
123
+ `conflicting=True`.
124
+ - A missing or unreadable DMI source returns `UNKNOWN`.
125
+ - `block` refuses only on `VM`. `PHYSICAL` and `UNKNOWN` are allowed, which
126
+ keeps ordinary PCs working and avoids blocking on ambiguous evidence.
127
+
128
+ ### What is intentionally not used
129
+
130
+ - The CPUID hypervisor-present bit. Physical Windows machines with Hyper-V,
131
+ VBS, or Memory Integrity enabled can set it, causing false positives.
132
+ - Timing measurements, process scanning, debugger checks, or anti-analysis
133
+ tricks.
134
+ - Network requests or external services.
135
+ - Any attempt to disable or interfere with security tools.
136
+
137
+ ### Limitations
138
+
139
+ VM detection is imperfect. DMI strings can be spoofed, hidden, or changed by
140
+ the hypervisor, and some physical firmware exposes unusual values. A
141
+ `PHYSICAL` result means "no known VM indicator was found", not proof. A `VM`
142
+ result can be a false positive; `vm_policy="allow"` or `vm_policy="off"` is
143
+ the documented escape hatch.
144
+
145
+ This check is **not** a security boundary and does **not** make the library
146
+ impossible to crack or bypass. The real protections are the signed manifest,
147
+ SHA-256 verification, HTTPS, and keeping the private signing key offline. Do
148
+ not use VM detection as a substitute for those controls.
149
+
150
+ ## Install
151
+
152
+ The PyPI distribution is `gu-7f3a2c`; the import package is `game_updater` and
153
+ the CLI command is `game-updater`.
154
+
155
+ ```powershell
156
+ pip install gu-7f3a2c
157
+ ```
158
+
159
+ From a local checkout:
160
+
161
+ ```powershell
162
+ pip install .
163
+ ```
164
+
165
+ This package is for publishers who control both the manifest host and the
166
+ signing key. Only point it at manifests you signed.
167
+
168
+ ## Generate a signing key
169
+
170
+ ```powershell
171
+ python tools/keygen.py --private game_private.pem
172
+ ```
173
+
174
+ Put the printed public key into `src/game_updater/public_key.txt`.
175
+
176
+ Keep `game_private.pem` offline. Never commit it.
177
+
178
+ ## Build and sign a manifest
179
+
180
+ Create `manifest.json` without a signature:
181
+
182
+ ```json
183
+ {
184
+ "version": "1.4.0",
185
+ "sequence": 3,
186
+ "notes": "Season 3 patch",
187
+ "files": [
188
+ {
189
+ "path": "game/data.pak",
190
+ "url": "https://cdn.example.com/game/data-1.4.0.pak",
191
+ "sha256": "…",
192
+ "size": 12345678
193
+ },
194
+ {
195
+ "path": "game/patch.exe",
196
+ "url": "https://cdn.example.com/game/patch-1.4.0.exe",
197
+ "sha256": "…",
198
+ "size": 456789,
199
+ "executable": true
200
+ }
201
+ ],
202
+ "run": "game/patch.exe"
203
+ }
204
+ ```
205
+
206
+ `sequence` is a signed, non-negative integer. Increase it for every release.
207
+ The updater stores it in `version.json` and refuses a manifest whose sequence
208
+ is lower than the installed one, or equal while claiming a different version.
209
+ That prevents replay of an older signed manifest.
210
+
211
+ Sign it:
212
+
213
+ ```powershell
214
+ python tools/sign_manifest.py --key game_private.pem --manifest manifest.json --out manifest.signed.json
215
+ ```
216
+
217
+ Upload `manifest.signed.json` (and the files) to your HTTPS host.
218
+
219
+ ## Update a game folder
220
+
221
+ ```powershell
222
+ game-updater --manifest https://cdn.example.com/game/manifest.signed.json --dir "C:\Games\MyGame"
223
+ ```
224
+
225
+ Other modes:
226
+
227
+ ```powershell
228
+ game-updater --manifest ... --dir ... --check # show versions only
229
+ game-updater --manifest ... --dir ... --apply # install (asks first)
230
+ game-updater --manifest ... --dir ... --apply --yes
231
+ game-updater --manifest ... --dir ... --apply --run
232
+ game-updater --manifest ... --dir ... --apply --vm-policy block
233
+ ```
234
+
235
+ `--vm-policy` accepts `off` (default), `allow`, or `block`.
236
+
237
+ ## Python API
238
+
239
+ ```python
240
+ from game_updater import update
241
+
242
+ result = update(
243
+ manifest_url="https://cdn.example.com/game/manifest.signed.json",
244
+ game_dir=r"C:\Games\MyGame",
245
+ apply=True,
246
+ run_patch=False,
247
+ )
248
+ print(result)
249
+ print(result.vm.summary() if result.vm else "vm check skipped")
250
+ ```
251
+
252
+ To refuse updates on a detected VM:
253
+
254
+ ```python
255
+ from game_updater import VMBlockedError, update
256
+
257
+ try:
258
+ result = update(
259
+ manifest_url="https://cdn.example.com/game/manifest.signed.json",
260
+ game_dir=r"C:\Games\MyGame",
261
+ apply=True,
262
+ vm_policy="block",
263
+ )
264
+ except VMBlockedError as error:
265
+ print(error) # clear, documented explanation
266
+ print(error.detection.kind.value) # e.g. "vmware"
267
+ ```
268
+
269
+ Inspect detection without updating:
270
+
271
+ ```python
272
+ from game_updater import detect_vm, collect_dmi
273
+
274
+ print(collect_dmi())
275
+ print(detect_vm().summary())
276
+ print(detect_vm().indicators)
277
+ ```
278
+
279
+ ## Layout produced in the game folder
280
+
281
+ ```
282
+ MyGame/
283
+ version.json # version, sequence, notes, file list
284
+ game/
285
+ data.pak
286
+ patch.exe
287
+ ```
288
+
289
+ `version.json` is written last, after every file is verified.
290
+
291
+ ## Threat model
292
+
293
+ | Attack | Mitigation |
294
+ |---|---|
295
+ | Compromised CDN / HTTP host | Manifest signature check |
296
+ | Modified file in transit | SHA-256 per file |
297
+ | Manifest replay of an older signed version | Signed monotonic `sequence`; lower or unchanged sequences for a different version are rejected |
298
+ | Path traversal (`../`) | `safe_join` rejects absolute paths and `..` |
299
+ | Unsigned patch execution | `run` target must be in `files`, verified, and `--run` passed |
300
+ | Malicious local manifest URL | HTTPS only (localhost allowed only with `--allow-insecure`) |
301
+ | VM use against policy | `vm_policy="block"` stops before any work; best-effort detection, not a security boundary |
302
+
303
+ ## Honest limitation
304
+
305
+ If someone steals your private signing key, they can sign a malicious update.
306
+ Treat the key like a code-signing key: offline, backed up, never in the repo.
307
+
308
+ VM detection is a compatibility policy, not DRM. DMI values can be spoofed or
309
+ withheld, so the check can be wrong in both directions. It does not make the
310
+ library uncrackable; the signed manifest and SHA-256 checks are the actual
311
+ integrity controls.
@@ -0,0 +1,24 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "gu-7f3a2c"
7
+ version = "0.4.2"
8
+ description = "Signed, hash-verified updater for a self-hosted game or app."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "game-updater contributors" }]
14
+ keywords = ["game", "updater", "ed25519", "sha256"]
15
+ dependencies = ["cryptography>=41"]
16
+
17
+ [project.scripts]
18
+ game-updater = "game_updater.cli:main"
19
+
20
+ [tool.setuptools.packages.find]
21
+ where = ["src"]
22
+
23
+ [tool.setuptools.package-data]
24
+ game_updater = ["public_key.txt"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+