game-updater 0.3.0__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,289 @@
1
+ Metadata-Version: 2.4
2
+ Name: game-updater
3
+ Version: 0.3.0
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
+ ## VM compatibility policy
52
+
53
+ The check is a configurable compatibility policy, not a stealth feature. It
54
+ runs at the start of `update()` and before the CLI reads local state.
55
+
56
+ | Policy | Behavior |
57
+ |---|---|
58
+ | `off` (default) | Skip VM detection entirely. |
59
+ | `allow` | Detect and report, but never block. Ordinary physical PCs and VM users both work. |
60
+ | `block` | If a VM is detected, raise `VMBlockedError` before creating the game directory, fetching the manifest, installing files, or running a patch. |
61
+
62
+ When detection runs, the CLI prints the result before doing any work. A
63
+ `block` refusal prints the documented error instead; `off` prints nothing.
64
+
65
+ ```text
66
+ vm check: physical machine (no known VM indicators found)
67
+ ```
68
+
69
+ If a VM is detected and the policy is `block`:
70
+
71
+ ```text
72
+ error: VM use is disabled by policy (vm_policy='block'), but this machine
73
+ appears to be a vmware virtual machine (confidence: high). Indicators:
74
+ DMI system manufacturer is VMware; DMI system product is VMware. No update
75
+ was downloaded, installed, or run. If this is a physical machine, this is a
76
+ false positive; use vm_policy='allow' or vm_policy='off' to proceed. VM
77
+ detection is best-effort and can be wrong.
78
+ ```
79
+
80
+ CLI exit code is `3` for a VM policy block, `2` for an update/manifest error,
81
+ and `0` on success.
82
+
83
+ ### Indicators used
84
+
85
+ Detection is conservative and uses only documented, read-only DMI/SMBIOS
86
+ values exposed by the OS:
87
+
88
+ | Environment | Definitive indicators | Supporting only |
89
+ |---|---|---|
90
+ | Hyper-V | DMI system product `Virtual Machine`; DMI system version `Hyper-V` | Hyper-V guest integration registry key |
91
+ | QEMU / KVM | DMI system/board manufacturer `QEMU`; DMI product `Standard PC (Q35 ...)` or `Standard PC (i440FX ...)` | BIOS vendor `SeaBIOS` |
92
+ | VMware | DMI manufacturer, product, version, board, or BIOS containing `VMware` | — |
93
+ | VirtualBox | DMI manufacturer/board/BIOS `innotek`; product/version `VirtualBox` | — |
94
+
95
+ Unsupported platforms (including macOS) return `UNKNOWN`; they are not
96
+ guessed at and are allowed under `block`.
97
+
98
+ Rules:
99
+
100
+ - A VM result requires at least one **definitive** vendor-specific string.
101
+ - Supporting-only evidence (for example, SeaBIOS alone, or the Hyper-V guest
102
+ key alone) returns `UNKNOWN`, not `VM`.
103
+ - Two different definitive vendors return `UNKNOWN` with
104
+ `conflicting=True`.
105
+ - A missing or unreadable DMI source returns `UNKNOWN`.
106
+ - `block` refuses only on `VM`. `PHYSICAL` and `UNKNOWN` are allowed, which
107
+ keeps ordinary PCs working and avoids blocking on ambiguous evidence.
108
+
109
+ ### What is intentionally not used
110
+
111
+ - The CPUID hypervisor-present bit. Physical Windows machines with Hyper-V,
112
+ VBS, or Memory Integrity enabled can set it, causing false positives.
113
+ - Timing measurements, process scanning, debugger checks, or anti-analysis
114
+ tricks.
115
+ - Network requests or external services.
116
+ - Any attempt to disable or interfere with security tools.
117
+
118
+ ### Limitations
119
+
120
+ VM detection is imperfect. DMI strings can be spoofed, hidden, or changed by
121
+ the hypervisor, and some physical firmware exposes unusual values. A
122
+ `PHYSICAL` result means "no known VM indicator was found", not proof. A `VM`
123
+ result can be a false positive; `vm_policy="allow"` or `vm_policy="off"` is
124
+ the documented escape hatch.
125
+
126
+ This check is **not** a security boundary and does **not** make the library
127
+ impossible to crack or bypass. The real protections are the signed manifest,
128
+ SHA-256 verification, HTTPS, and keeping the private signing key offline. Do
129
+ not use VM detection as a substitute for those controls.
130
+
131
+ ## Install
132
+
133
+ ```powershell
134
+ pip install game-updater
135
+ ```
136
+
137
+ From a local checkout:
138
+
139
+ ```powershell
140
+ pip install .
141
+ ```
142
+
143
+ This package is for publishers who control both the manifest host and the
144
+ signing key. Only point it at manifests you signed.
145
+
146
+ ## Generate a signing key
147
+
148
+ ```powershell
149
+ python tools/keygen.py --private game_private.pem
150
+ ```
151
+
152
+ Put the printed public key into `src/game_updater/public_key.txt`.
153
+
154
+ Keep `game_private.pem` offline. Never commit it.
155
+
156
+ ## Build and sign a manifest
157
+
158
+ Create `manifest.json` without a signature:
159
+
160
+ ```json
161
+ {
162
+ "version": "1.4.0",
163
+ "sequence": 3,
164
+ "notes": "Season 3 patch",
165
+ "files": [
166
+ {
167
+ "path": "game/data.pak",
168
+ "url": "https://cdn.example.com/game/data-1.4.0.pak",
169
+ "sha256": "…",
170
+ "size": 12345678
171
+ },
172
+ {
173
+ "path": "game/patch.exe",
174
+ "url": "https://cdn.example.com/game/patch-1.4.0.exe",
175
+ "sha256": "…",
176
+ "size": 456789,
177
+ "executable": true
178
+ }
179
+ ],
180
+ "run": "game/patch.exe"
181
+ }
182
+ ```
183
+
184
+ `sequence` is a signed, non-negative integer. Increase it for every release.
185
+ The updater stores it in `version.json` and refuses a manifest whose sequence
186
+ is lower than the installed one, or equal while claiming a different version.
187
+ That prevents replay of an older signed manifest.
188
+
189
+ Sign it:
190
+
191
+ ```powershell
192
+ python tools/sign_manifest.py --key game_private.pem --manifest manifest.json --out manifest.signed.json
193
+ ```
194
+
195
+ Upload `manifest.signed.json` (and the files) to your HTTPS host.
196
+
197
+ ## Update a game folder
198
+
199
+ ```powershell
200
+ game-updater --manifest https://cdn.example.com/game/manifest.signed.json --dir "C:\Games\MyGame"
201
+ ```
202
+
203
+ Other modes:
204
+
205
+ ```powershell
206
+ game-updater --manifest ... --dir ... --check # show versions only
207
+ game-updater --manifest ... --dir ... --apply # install (asks first)
208
+ game-updater --manifest ... --dir ... --apply --yes
209
+ game-updater --manifest ... --dir ... --apply --run
210
+ game-updater --manifest ... --dir ... --apply --vm-policy block
211
+ ```
212
+
213
+ `--vm-policy` accepts `off` (default), `allow`, or `block`.
214
+
215
+ ## Python API
216
+
217
+ ```python
218
+ from game_updater import update
219
+
220
+ result = update(
221
+ manifest_url="https://cdn.example.com/game/manifest.signed.json",
222
+ game_dir=r"C:\Games\MyGame",
223
+ apply=True,
224
+ run_patch=False,
225
+ )
226
+ print(result)
227
+ print(result.vm.summary() if result.vm else "vm check skipped")
228
+ ```
229
+
230
+ To refuse updates on a detected VM:
231
+
232
+ ```python
233
+ from game_updater import VMBlockedError, update
234
+
235
+ try:
236
+ result = update(
237
+ manifest_url="https://cdn.example.com/game/manifest.signed.json",
238
+ game_dir=r"C:\Games\MyGame",
239
+ apply=True,
240
+ vm_policy="block",
241
+ )
242
+ except VMBlockedError as error:
243
+ print(error) # clear, documented explanation
244
+ print(error.detection.kind.value) # e.g. "vmware"
245
+ ```
246
+
247
+ Inspect detection without updating:
248
+
249
+ ```python
250
+ from game_updater import detect_vm, collect_dmi
251
+
252
+ print(collect_dmi())
253
+ print(detect_vm().summary())
254
+ print(detect_vm().indicators)
255
+ ```
256
+
257
+ ## Layout produced in the game folder
258
+
259
+ ```
260
+ MyGame/
261
+ version.json # version, sequence, notes, file list
262
+ game/
263
+ data.pak
264
+ patch.exe
265
+ ```
266
+
267
+ `version.json` is written last, after every file is verified.
268
+
269
+ ## Threat model
270
+
271
+ | Attack | Mitigation |
272
+ |---|---|
273
+ | Compromised CDN / HTTP host | Manifest signature check |
274
+ | Modified file in transit | SHA-256 per file |
275
+ | Manifest replay of an older signed version | Signed monotonic `sequence`; lower or unchanged sequences for a different version are rejected |
276
+ | Path traversal (`../`) | `safe_join` rejects absolute paths and `..` |
277
+ | Unsigned patch execution | `run` target must be in `files`, verified, and `--run` passed |
278
+ | Malicious local manifest URL | HTTPS only (localhost allowed only with `--allow-insecure`) |
279
+ | VM use against policy | `vm_policy="block"` stops before any work; best-effort detection, not a security boundary |
280
+
281
+ ## Honest limitation
282
+
283
+ If someone steals your private signing key, they can sign a malicious update.
284
+ Treat the key like a code-signing key: offline, backed up, never in the repo.
285
+
286
+ VM detection is a compatibility policy, not DRM. DMI values can be spoofed or
287
+ withheld, so the check can be wrong in both directions. It does not make the
288
+ library uncrackable; the signed manifest and SHA-256 checks are the actual
289
+ integrity controls.
@@ -0,0 +1,276 @@
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
+ ## VM compatibility policy
39
+
40
+ The check is a configurable compatibility policy, not a stealth feature. It
41
+ runs at the start of `update()` and before the CLI reads local state.
42
+
43
+ | Policy | Behavior |
44
+ |---|---|
45
+ | `off` (default) | Skip VM detection entirely. |
46
+ | `allow` | Detect and report, but never block. Ordinary physical PCs and VM users both work. |
47
+ | `block` | If a VM is detected, raise `VMBlockedError` before creating the game directory, fetching the manifest, installing files, or running a patch. |
48
+
49
+ When detection runs, the CLI prints the result before doing any work. A
50
+ `block` refusal prints the documented error instead; `off` prints nothing.
51
+
52
+ ```text
53
+ vm check: physical machine (no known VM indicators found)
54
+ ```
55
+
56
+ If a VM is detected and the policy is `block`:
57
+
58
+ ```text
59
+ error: VM use is disabled by policy (vm_policy='block'), but this machine
60
+ appears to be a vmware virtual machine (confidence: high). Indicators:
61
+ DMI system manufacturer is VMware; DMI system product is VMware. No update
62
+ was downloaded, installed, or run. If this is a physical machine, this is a
63
+ false positive; use vm_policy='allow' or vm_policy='off' to proceed. VM
64
+ detection is best-effort and can be wrong.
65
+ ```
66
+
67
+ CLI exit code is `3` for a VM policy block, `2` for an update/manifest error,
68
+ and `0` on success.
69
+
70
+ ### Indicators used
71
+
72
+ Detection is conservative and uses only documented, read-only DMI/SMBIOS
73
+ values exposed by the OS:
74
+
75
+ | Environment | Definitive indicators | Supporting only |
76
+ |---|---|---|
77
+ | Hyper-V | DMI system product `Virtual Machine`; DMI system version `Hyper-V` | Hyper-V guest integration registry key |
78
+ | QEMU / KVM | DMI system/board manufacturer `QEMU`; DMI product `Standard PC (Q35 ...)` or `Standard PC (i440FX ...)` | BIOS vendor `SeaBIOS` |
79
+ | VMware | DMI manufacturer, product, version, board, or BIOS containing `VMware` | — |
80
+ | VirtualBox | DMI manufacturer/board/BIOS `innotek`; product/version `VirtualBox` | — |
81
+
82
+ Unsupported platforms (including macOS) return `UNKNOWN`; they are not
83
+ guessed at and are allowed under `block`.
84
+
85
+ Rules:
86
+
87
+ - A VM result requires at least one **definitive** vendor-specific string.
88
+ - Supporting-only evidence (for example, SeaBIOS alone, or the Hyper-V guest
89
+ key alone) returns `UNKNOWN`, not `VM`.
90
+ - Two different definitive vendors return `UNKNOWN` with
91
+ `conflicting=True`.
92
+ - A missing or unreadable DMI source returns `UNKNOWN`.
93
+ - `block` refuses only on `VM`. `PHYSICAL` and `UNKNOWN` are allowed, which
94
+ keeps ordinary PCs working and avoids blocking on ambiguous evidence.
95
+
96
+ ### What is intentionally not used
97
+
98
+ - The CPUID hypervisor-present bit. Physical Windows machines with Hyper-V,
99
+ VBS, or Memory Integrity enabled can set it, causing false positives.
100
+ - Timing measurements, process scanning, debugger checks, or anti-analysis
101
+ tricks.
102
+ - Network requests or external services.
103
+ - Any attempt to disable or interfere with security tools.
104
+
105
+ ### Limitations
106
+
107
+ VM detection is imperfect. DMI strings can be spoofed, hidden, or changed by
108
+ the hypervisor, and some physical firmware exposes unusual values. A
109
+ `PHYSICAL` result means "no known VM indicator was found", not proof. A `VM`
110
+ result can be a false positive; `vm_policy="allow"` or `vm_policy="off"` is
111
+ the documented escape hatch.
112
+
113
+ This check is **not** a security boundary and does **not** make the library
114
+ impossible to crack or bypass. The real protections are the signed manifest,
115
+ SHA-256 verification, HTTPS, and keeping the private signing key offline. Do
116
+ not use VM detection as a substitute for those controls.
117
+
118
+ ## Install
119
+
120
+ ```powershell
121
+ pip install game-updater
122
+ ```
123
+
124
+ From a local checkout:
125
+
126
+ ```powershell
127
+ pip install .
128
+ ```
129
+
130
+ This package is for publishers who control both the manifest host and the
131
+ signing key. Only point it at manifests you signed.
132
+
133
+ ## Generate a signing key
134
+
135
+ ```powershell
136
+ python tools/keygen.py --private game_private.pem
137
+ ```
138
+
139
+ Put the printed public key into `src/game_updater/public_key.txt`.
140
+
141
+ Keep `game_private.pem` offline. Never commit it.
142
+
143
+ ## Build and sign a manifest
144
+
145
+ Create `manifest.json` without a signature:
146
+
147
+ ```json
148
+ {
149
+ "version": "1.4.0",
150
+ "sequence": 3,
151
+ "notes": "Season 3 patch",
152
+ "files": [
153
+ {
154
+ "path": "game/data.pak",
155
+ "url": "https://cdn.example.com/game/data-1.4.0.pak",
156
+ "sha256": "…",
157
+ "size": 12345678
158
+ },
159
+ {
160
+ "path": "game/patch.exe",
161
+ "url": "https://cdn.example.com/game/patch-1.4.0.exe",
162
+ "sha256": "…",
163
+ "size": 456789,
164
+ "executable": true
165
+ }
166
+ ],
167
+ "run": "game/patch.exe"
168
+ }
169
+ ```
170
+
171
+ `sequence` is a signed, non-negative integer. Increase it for every release.
172
+ The updater stores it in `version.json` and refuses a manifest whose sequence
173
+ is lower than the installed one, or equal while claiming a different version.
174
+ That prevents replay of an older signed manifest.
175
+
176
+ Sign it:
177
+
178
+ ```powershell
179
+ python tools/sign_manifest.py --key game_private.pem --manifest manifest.json --out manifest.signed.json
180
+ ```
181
+
182
+ Upload `manifest.signed.json` (and the files) to your HTTPS host.
183
+
184
+ ## Update a game folder
185
+
186
+ ```powershell
187
+ game-updater --manifest https://cdn.example.com/game/manifest.signed.json --dir "C:\Games\MyGame"
188
+ ```
189
+
190
+ Other modes:
191
+
192
+ ```powershell
193
+ game-updater --manifest ... --dir ... --check # show versions only
194
+ game-updater --manifest ... --dir ... --apply # install (asks first)
195
+ game-updater --manifest ... --dir ... --apply --yes
196
+ game-updater --manifest ... --dir ... --apply --run
197
+ game-updater --manifest ... --dir ... --apply --vm-policy block
198
+ ```
199
+
200
+ `--vm-policy` accepts `off` (default), `allow`, or `block`.
201
+
202
+ ## Python API
203
+
204
+ ```python
205
+ from game_updater import update
206
+
207
+ result = update(
208
+ manifest_url="https://cdn.example.com/game/manifest.signed.json",
209
+ game_dir=r"C:\Games\MyGame",
210
+ apply=True,
211
+ run_patch=False,
212
+ )
213
+ print(result)
214
+ print(result.vm.summary() if result.vm else "vm check skipped")
215
+ ```
216
+
217
+ To refuse updates on a detected VM:
218
+
219
+ ```python
220
+ from game_updater import VMBlockedError, update
221
+
222
+ try:
223
+ result = update(
224
+ manifest_url="https://cdn.example.com/game/manifest.signed.json",
225
+ game_dir=r"C:\Games\MyGame",
226
+ apply=True,
227
+ vm_policy="block",
228
+ )
229
+ except VMBlockedError as error:
230
+ print(error) # clear, documented explanation
231
+ print(error.detection.kind.value) # e.g. "vmware"
232
+ ```
233
+
234
+ Inspect detection without updating:
235
+
236
+ ```python
237
+ from game_updater import detect_vm, collect_dmi
238
+
239
+ print(collect_dmi())
240
+ print(detect_vm().summary())
241
+ print(detect_vm().indicators)
242
+ ```
243
+
244
+ ## Layout produced in the game folder
245
+
246
+ ```
247
+ MyGame/
248
+ version.json # version, sequence, notes, file list
249
+ game/
250
+ data.pak
251
+ patch.exe
252
+ ```
253
+
254
+ `version.json` is written last, after every file is verified.
255
+
256
+ ## Threat model
257
+
258
+ | Attack | Mitigation |
259
+ |---|---|
260
+ | Compromised CDN / HTTP host | Manifest signature check |
261
+ | Modified file in transit | SHA-256 per file |
262
+ | Manifest replay of an older signed version | Signed monotonic `sequence`; lower or unchanged sequences for a different version are rejected |
263
+ | Path traversal (`../`) | `safe_join` rejects absolute paths and `..` |
264
+ | Unsigned patch execution | `run` target must be in `files`, verified, and `--run` passed |
265
+ | Malicious local manifest URL | HTTPS only (localhost allowed only with `--allow-insecure`) |
266
+ | VM use against policy | `vm_policy="block"` stops before any work; best-effort detection, not a security boundary |
267
+
268
+ ## Honest limitation
269
+
270
+ If someone steals your private signing key, they can sign a malicious update.
271
+ Treat the key like a code-signing key: offline, backed up, never in the repo.
272
+
273
+ VM detection is a compatibility policy, not DRM. DMI values can be spoofed or
274
+ withheld, so the check can be wrong in both directions. It does not make the
275
+ library uncrackable; the signed manifest and SHA-256 checks are the actual
276
+ 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 = "game-updater"
7
+ version = "0.3.0"
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
+
@@ -0,0 +1,36 @@
1
+ """Signed, hash-verified updater for a self-hosted game or app."""
2
+
3
+ from .install import UpdateResult, update
4
+ from .manifest import Manifest, ManifestError
5
+ from .vmcheck import (
6
+ DMIData,
7
+ VMBlockedError,
8
+ VMDetection,
9
+ VMPolicy,
10
+ VMStatus,
11
+ VMVendor,
12
+ classify,
13
+ collect_dmi,
14
+ detect_vm,
15
+ ensure_vm_allowed,
16
+ )
17
+
18
+ __version__ = "0.3.0"
19
+
20
+ __all__ = [
21
+ "__version__",
22
+ "DMIData",
23
+ "Manifest",
24
+ "ManifestError",
25
+ "UpdateResult",
26
+ "VMBlockedError",
27
+ "VMDetection",
28
+ "VMPolicy",
29
+ "VMStatus",
30
+ "VMVendor",
31
+ "classify",
32
+ "collect_dmi",
33
+ "detect_vm",
34
+ "ensure_vm_allowed",
35
+ "update",
36
+ ]
@@ -0,0 +1,5 @@
1
+ """Allow ``python -m game_updater``."""
2
+
3
+ from .cli import main
4
+
5
+ raise SystemExit(main())