zx-kit 0.37.1 → 0.38.1
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.
- package/README.md +2 -1
- package/dist/hiscore.d.ts +86 -0
- package/dist/hiscore.d.ts.map +1 -0
- package/dist/hiscore.js +109 -0
- package/dist/hiscore.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/save.d.ts +24 -2
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +44 -2
- package/dist/save.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -189,7 +189,8 @@ Zero runtime dependencies, `sideEffects: false`, fully tree-shakeable.
|
|
|
189
189
|
| `ay` | AY-3-8912: 3-channel tone, LFSR noise, 16 envelopes, per-channel stereo pan + volume | [audio](docs/audio.md#ayts--ay-3-8912-melodik-audio) |
|
|
190
190
|
| `music` | AY music by note name (`A5`, `C#4`) + looping | [audio](docs/audio.md#musicts--note-name-ay-music) |
|
|
191
191
|
| `collision` | AABB / rect-vs-tile / pixel-precise mask overlap | [collision](docs/collision.md) |
|
|
192
|
-
| `save` | Typed save/load: versioning, migration, slots, throttle | [save](docs/save.md) |
|
|
192
|
+
| `save` | Typed save/load: versioning, migration, slots, throttle, optional tamper signature | [save](docs/save.md) |
|
|
193
|
+
| `hiscore` | High-score table over the save envelope: top-N insert, per-game `Extra` fields, tamper deterrence | [save](docs/save.md#hiscorets--high-score-table) |
|
|
193
194
|
| `input` | Keyboard + gamepad movement, key-repeat, action flags, built-in `+`/`-` volume keys | [api](docs/api.md#inputts--keyboard--gamepad-input) |
|
|
194
195
|
| `sprite` | Free-roaming sprites: position, velocity, gravity, flip | [api](docs/api.md#spritets--free-roaming-sprites) |
|
|
195
196
|
| `animation` | Frame timer, position tween, blinker | [api](docs/api.md#animationts--frame-timer--tween) |
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type SaveProfile, type SaveResult } from './save.js';
|
|
2
|
+
/** A single table row: the two fields every game shares, plus the game's own. */
|
|
3
|
+
export type HighScoreEntry<Extra = object> = {
|
|
4
|
+
name: string;
|
|
5
|
+
score: number;
|
|
6
|
+
} & Extra;
|
|
7
|
+
/**
|
|
8
|
+
* Configuration for a game's high-score table. The entry's game-specific
|
|
9
|
+
* shape is the `Extra` type parameter of {@link createHighScores} — all
|
|
10
|
+
* JSON-safe; use the default for plain name+score tables.
|
|
11
|
+
*/
|
|
12
|
+
export interface HighScoreConfig {
|
|
13
|
+
/** Game key — storage namespace, conventionally the same as the save profile's. */
|
|
14
|
+
key: string;
|
|
15
|
+
/** Rows kept, best first. Default 5. */
|
|
16
|
+
maxEntries?: number;
|
|
17
|
+
/** Longest accepted name. Default 10. Longer names are rejected, not cut. */
|
|
18
|
+
maxNameLength?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Optional integrity secret — same scheme and same caveat as
|
|
21
|
+
* `SaveProfileConfig.secret` (deterrence, not security). A tampered table
|
|
22
|
+
* loads as empty rather than failing the game.
|
|
23
|
+
*/
|
|
24
|
+
secret?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Validates the game-specific fields of one entry when loading. Rows that
|
|
27
|
+
* fail are dropped silently (legacy tolerance: make optional fields optional
|
|
28
|
+
* here). Omit for plain name+score tables.
|
|
29
|
+
*/
|
|
30
|
+
validateExtra?: (entry: Record<string, unknown>) => boolean;
|
|
31
|
+
}
|
|
32
|
+
/** Returned by {@link createHighScores}; pass to all table operations. */
|
|
33
|
+
export interface HighScores<Extra = object> {
|
|
34
|
+
readonly config: HighScoreConfig & {
|
|
35
|
+
maxEntries: number;
|
|
36
|
+
maxNameLength: number;
|
|
37
|
+
};
|
|
38
|
+
/** Internal save profile — the table is one slot inside the game's namespace. */
|
|
39
|
+
readonly profile: SaveProfile<HighScoreEntry<Extra>[]>;
|
|
40
|
+
/** In-memory buffer the profile serializes from / deserializes into. Internal. */
|
|
41
|
+
buffer: HighScoreEntry<Extra>[];
|
|
42
|
+
}
|
|
43
|
+
/** Result of {@link insertScore}: where the entry landed, if it did. */
|
|
44
|
+
export interface InsertResult<Extra = object> {
|
|
45
|
+
/** True iff the entry made the table. */
|
|
46
|
+
placed: boolean;
|
|
47
|
+
/** 1-based rank when placed, null otherwise. */
|
|
48
|
+
rank: number | null;
|
|
49
|
+
/** The table after the insert attempt (what the game should render). */
|
|
50
|
+
scores: HighScoreEntry<Extra>[];
|
|
51
|
+
/** Persistence outcome of the write (only meaningful when placed). */
|
|
52
|
+
saved: SaveResult | null;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Registers a high-score table. Call once at startup and reuse the handle.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* interface MinefieldExtra { level: number; date?: string }
|
|
59
|
+
* const hs = createHighScores<MinefieldExtra>({
|
|
60
|
+
* key: 'minefield',
|
|
61
|
+
* validateExtra: (e) => typeof e.level === 'number' &&
|
|
62
|
+
* (e.date === undefined || typeof e.date === 'string'),
|
|
63
|
+
* })
|
|
64
|
+
* if (isHighScore(hs, run.score)) insertScore(hs, { name, score: run.score, level, date })
|
|
65
|
+
*/
|
|
66
|
+
export declare function createHighScores<Extra = object>(config: HighScoreConfig): HighScores<Extra>;
|
|
67
|
+
/**
|
|
68
|
+
* Loads the table, best score first. Invalid rows are dropped; a missing,
|
|
69
|
+
* corrupt or tampered table is simply empty — a broken leaderboard must never
|
|
70
|
+
* break the game.
|
|
71
|
+
*/
|
|
72
|
+
export declare function loadHighScores<Extra>(table: HighScores<Extra>): HighScoreEntry<Extra>[];
|
|
73
|
+
/**
|
|
74
|
+
* True iff `score` would make the table — the cheap pre-check before asking
|
|
75
|
+
* the player for a name. Zero and negative scores never qualify.
|
|
76
|
+
*/
|
|
77
|
+
export declare function isHighScore<Extra>(table: HighScores<Extra>, score: number): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Inserts an entry (top-N, best first; on a tie the earlier entry keeps the
|
|
80
|
+
* higher rank) and persists the table. Entries that fail validation or fall
|
|
81
|
+
* off the end are not written.
|
|
82
|
+
*/
|
|
83
|
+
export declare function insertScore<Extra>(table: HighScores<Extra>, entry: HighScoreEntry<Extra>): InsertResult<Extra>;
|
|
84
|
+
/** Wipes the table. Returns true if there was one. */
|
|
85
|
+
export declare function clearHighScores<Extra>(table: HighScores<Extra>): boolean;
|
|
86
|
+
//# sourceMappingURL=hiscore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hiscore.d.ts","sourceRoot":"","sources":["../src/hiscore.ts"],"names":[],"mappings":"AAWA,OAAO,EAKL,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,WAAW,CAAA;AAElB,iFAAiF;AACjF,MAAM,MAAM,cAAc,CAAC,KAAK,GAAG,MAAM,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,KAAK,CAAA;AAEpF;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,mFAAmF;IACnF,GAAG,EAAE,MAAM,CAAA;IACX,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAA;CAC5D;AAED,0EAA0E;AAC1E,MAAM,WAAW,UAAU,CAAC,KAAK,GAAG,MAAM;IACxC,QAAQ,CAAC,MAAM,EAAE,eAAe,GAAG;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAA;IAChF,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IACtD,kFAAkF;IAClF,MAAM,EAAE,cAAc,CAAC,KAAK,CAAC,EAAE,CAAA;CAChC;AAED,wEAAwE;AACxE,MAAM,WAAW,YAAY,CAAC,KAAK,GAAG,MAAM;IAC1C,yCAAyC;IACzC,MAAM,EAAE,OAAO,CAAA;IACf,gDAAgD;IAChD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,wEAAwE;IACxE,MAAM,EAAE,cAAc,CAAC,KAAK,CAAC,EAAE,CAAA;IAC/B,sEAAsE;IACtE,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;CACzB;AAKD;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,GAAG,MAAM,EAC7C,MAAM,EAAE,eAAe,GACtB,UAAU,CAAC,KAAK,CAAC,CAiBnB;AAkBD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,GAAG,cAAc,CAAC,KAAK,CAAC,EAAE,CAOvF;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAKnF;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAC/B,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,EACxB,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAC3B,YAAY,CAAC,KAAK,CAAC,CAoBrB;AAED,sDAAsD;AACtD,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,GAAG,OAAO,CAGxE"}
|
package/dist/hiscore.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// ── Hiscore: a typed, versioned high-score table over the save envelope ─────
|
|
2
|
+
//
|
|
3
|
+
// The kit owns the DATA: validation, top-N insertion, persistence (through the
|
|
4
|
+
// same envelope + optional integrity signature as `save.ts`). The GAME owns the
|
|
5
|
+
// POLICY and the look: what extra fields an entry carries (a daily date, a
|
|
6
|
+
// level, a vehicle…), how the table is rendered, and how names are entered.
|
|
7
|
+
//
|
|
8
|
+
// Every entry has `name` + `score`; anything else is the game's `Extra` shape,
|
|
9
|
+
// validated by the game's own `validateExtra` guard so a hand-edited table
|
|
10
|
+
// can't smuggle malformed rows back in.
|
|
11
|
+
import { createSaveProfile, readSave, writeSave, deleteSave, } from './save.js';
|
|
12
|
+
const SLOT = 'hiscore';
|
|
13
|
+
const TABLE_VERSION = 1;
|
|
14
|
+
/**
|
|
15
|
+
* Registers a high-score table. Call once at startup and reuse the handle.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* interface MinefieldExtra { level: number; date?: string }
|
|
19
|
+
* const hs = createHighScores<MinefieldExtra>({
|
|
20
|
+
* key: 'minefield',
|
|
21
|
+
* validateExtra: (e) => typeof e.level === 'number' &&
|
|
22
|
+
* (e.date === undefined || typeof e.date === 'string'),
|
|
23
|
+
* })
|
|
24
|
+
* if (isHighScore(hs, run.score)) insertScore(hs, { name, score: run.score, level, date })
|
|
25
|
+
*/
|
|
26
|
+
export function createHighScores(config) {
|
|
27
|
+
const full = { maxEntries: 5, maxNameLength: 10, ...config };
|
|
28
|
+
const table = {
|
|
29
|
+
config: full,
|
|
30
|
+
buffer: [],
|
|
31
|
+
profile: createSaveProfile({
|
|
32
|
+
key: config.key,
|
|
33
|
+
version: TABLE_VERSION,
|
|
34
|
+
secret: config.secret,
|
|
35
|
+
serialize: () => table.buffer,
|
|
36
|
+
deserialize: (data) => {
|
|
37
|
+
// A hand-edited envelope can carry any JSON here — only an array counts.
|
|
38
|
+
table.buffer = Array.isArray(data) ? data : [];
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
};
|
|
42
|
+
return table;
|
|
43
|
+
}
|
|
44
|
+
function isValidEntry(table, value) {
|
|
45
|
+
if (typeof value !== 'object' || value === null)
|
|
46
|
+
return false;
|
|
47
|
+
const e = value;
|
|
48
|
+
return (typeof e.name === 'string' &&
|
|
49
|
+
e.name.trim().length > 0 &&
|
|
50
|
+
e.name.length <= table.config.maxNameLength &&
|
|
51
|
+
typeof e.score === 'number' &&
|
|
52
|
+
Number.isFinite(e.score) &&
|
|
53
|
+
(table.config.validateExtra === undefined || table.config.validateExtra(e)));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Loads the table, best score first. Invalid rows are dropped; a missing,
|
|
57
|
+
* corrupt or tampered table is simply empty — a broken leaderboard must never
|
|
58
|
+
* break the game.
|
|
59
|
+
*/
|
|
60
|
+
export function loadHighScores(table) {
|
|
61
|
+
table.buffer = [];
|
|
62
|
+
readSave(table.profile, SLOT); // failure reasons all collapse to "empty table"
|
|
63
|
+
return table.buffer
|
|
64
|
+
.filter((e) => isValidEntry(table, e))
|
|
65
|
+
.sort((a, b) => b.score - a.score)
|
|
66
|
+
.slice(0, table.config.maxEntries);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* True iff `score` would make the table — the cheap pre-check before asking
|
|
70
|
+
* the player for a name. Zero and negative scores never qualify.
|
|
71
|
+
*/
|
|
72
|
+
export function isHighScore(table, score) {
|
|
73
|
+
if (!Number.isFinite(score) || score <= 0)
|
|
74
|
+
return false;
|
|
75
|
+
const scores = loadHighScores(table);
|
|
76
|
+
if (scores.length < table.config.maxEntries)
|
|
77
|
+
return true;
|
|
78
|
+
return score > scores[scores.length - 1].score;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Inserts an entry (top-N, best first; on a tie the earlier entry keeps the
|
|
82
|
+
* higher rank) and persists the table. Entries that fail validation or fall
|
|
83
|
+
* off the end are not written.
|
|
84
|
+
*/
|
|
85
|
+
export function insertScore(table, entry) {
|
|
86
|
+
const current = loadHighScores(table);
|
|
87
|
+
if (!isValidEntry(table, entry)) {
|
|
88
|
+
return { placed: false, rank: null, scores: current, saved: null };
|
|
89
|
+
}
|
|
90
|
+
// Stable insert after the last entry with score >= entry.score = tie keeps rank.
|
|
91
|
+
let at = current.length;
|
|
92
|
+
while (at > 0 && current[at - 1].score < entry.score)
|
|
93
|
+
at--;
|
|
94
|
+
current.splice(at, 0, entry);
|
|
95
|
+
const next = current.slice(0, table.config.maxEntries);
|
|
96
|
+
const rank = at < table.config.maxEntries ? at + 1 : null;
|
|
97
|
+
if (rank === null) {
|
|
98
|
+
return { placed: false, rank: null, scores: next, saved: null };
|
|
99
|
+
}
|
|
100
|
+
table.buffer = next;
|
|
101
|
+
const saved = writeSave(table.profile, SLOT);
|
|
102
|
+
return { placed: true, rank, scores: next, saved };
|
|
103
|
+
}
|
|
104
|
+
/** Wipes the table. Returns true if there was one. */
|
|
105
|
+
export function clearHighScores(table) {
|
|
106
|
+
table.buffer = [];
|
|
107
|
+
return deleteSave(table.profile, SLOT);
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=hiscore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hiscore.js","sourceRoot":"","sources":["../src/hiscore.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,cAAc,mBAAmB,CAAA;AACjC,cAAc,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,cAAc,mBAAmB,CAAA;AACjC,cAAc,YAAY,CAAA"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,cAAc,mBAAmB,CAAA;AACjC,cAAc,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,cAAc,mBAAmB,CAAA;AACjC,cAAc,YAAY,CAAA"}
|
package/dist/save.d.ts
CHANGED
|
@@ -21,6 +21,18 @@ export interface SaveProfileConfig<T> {
|
|
|
21
21
|
* version is encountered, the load fails with `version_unsupported`.
|
|
22
22
|
*/
|
|
23
23
|
migrate?: (data: unknown, fromVersion: number) => T;
|
|
24
|
+
/**
|
|
25
|
+
* Optional integrity secret. When set, every write stores a signature of
|
|
26
|
+
* the payload (`sig` in the envelope) and every read verifies it — a
|
|
27
|
+
* mismatch or missing signature fails with `tampered`, the same way a bad
|
|
28
|
+
* version fails. Adopting a secret on an existing profile therefore needs
|
|
29
|
+
* a version bump (old sig-less saves would read as tampered).
|
|
30
|
+
*
|
|
31
|
+
* This is **deterrence, not security**: the secret ships inside the game's
|
|
32
|
+
* JS, so a determined cheater can always re-sign. The goal is only that
|
|
33
|
+
* editing localStorage by hand stops being free.
|
|
34
|
+
*/
|
|
35
|
+
secret?: string;
|
|
24
36
|
}
|
|
25
37
|
/**
|
|
26
38
|
* Returned by {@link createSaveProfile}. Carries config plus in-memory
|
|
@@ -64,9 +76,18 @@ export type LoadResult = {
|
|
|
64
76
|
slot: string;
|
|
65
77
|
} | {
|
|
66
78
|
ok: false;
|
|
67
|
-
reason: 'not_found' | 'corrupt' | 'version_unsupported' | 'parse_error' | 'disabled';
|
|
79
|
+
reason: 'not_found' | 'corrupt' | 'version_unsupported' | 'parse_error' | 'disabled' | 'tampered';
|
|
68
80
|
error?: Error;
|
|
69
81
|
};
|
|
82
|
+
/**
|
|
83
|
+
* The envelope signature: version + timestamp + the data's JSON + the secret.
|
|
84
|
+
* `dataJson` must be `JSON.stringify(data)` — at read time re-stringifying the
|
|
85
|
+
* parsed data reproduces the written string byte-for-byte (stringify preserves
|
|
86
|
+
* parse order), so write and read agree without storing the raw payload twice.
|
|
87
|
+
*
|
|
88
|
+
* Internal; exported for the hiscore module so both sign the same way.
|
|
89
|
+
*/
|
|
90
|
+
export declare function _envelopeSig(version: number, timestamp: number, dataJson: string, secret: string): string;
|
|
70
91
|
/**
|
|
71
92
|
* Registers a save profile for a game. Call once at startup and reuse the
|
|
72
93
|
* returned handle for all save/load operations.
|
|
@@ -103,7 +124,8 @@ export declare function writeSaveThrottled<T>(profile: SaveProfile<T>, slot: str
|
|
|
103
124
|
export declare function readSave<T>(profile: SaveProfile<T>, slot?: string): LoadResult;
|
|
104
125
|
/**
|
|
105
126
|
* Reads whichever slot has the newest timestamp. Returns `not_found` if
|
|
106
|
-
* no slots exist for this profile's key
|
|
127
|
+
* no slots exist for this profile's key, `disabled` when storage itself
|
|
128
|
+
* is unavailable (so the two cases stay distinguishable).
|
|
107
129
|
*/
|
|
108
130
|
export declare function readSaveLatest<T>(profile: SaveProfile<T>): LoadResult;
|
|
109
131
|
/** True iff the slot exists in storage. Does not validate envelope shape. */
|
package/dist/save.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"save.d.ts","sourceRoot":"","sources":["../src/save.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,wEAAwE;IACxE,GAAG,EAAE,MAAM,CAAA;IACX,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAA;IACf,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC,CAAA;IAClB,gEAAgE;IAChE,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAA;IAC9B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"save.d.ts","sourceRoot":"","sources":["../src/save.ts"],"names":[],"mappings":"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"}
|
package/dist/save.js
CHANGED
|
@@ -18,6 +18,30 @@ function getStorage() {
|
|
|
18
18
|
return null;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* FNV-1a 32-bit over a string, hex-encoded. Synchronous on purpose — the save
|
|
23
|
+
* path is sync, and for a deterrent-grade signature (the secret ships in the
|
|
24
|
+
* bundle anyway) a cryptographic hash buys nothing over FNV.
|
|
25
|
+
*/
|
|
26
|
+
function fnv1a(text) {
|
|
27
|
+
let hash = 0x811c9dc5;
|
|
28
|
+
for (let i = 0; i < text.length; i++) {
|
|
29
|
+
hash ^= text.charCodeAt(i);
|
|
30
|
+
hash = Math.imul(hash, 0x01000193);
|
|
31
|
+
}
|
|
32
|
+
return (hash >>> 0).toString(16).padStart(8, '0');
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The envelope signature: version + timestamp + the data's JSON + the secret.
|
|
36
|
+
* `dataJson` must be `JSON.stringify(data)` — at read time re-stringifying the
|
|
37
|
+
* parsed data reproduces the written string byte-for-byte (stringify preserves
|
|
38
|
+
* parse order), so write and read agree without storing the raw payload twice.
|
|
39
|
+
*
|
|
40
|
+
* Internal; exported for the hiscore module so both sign the same way.
|
|
41
|
+
*/
|
|
42
|
+
export function _envelopeSig(version, timestamp, dataJson, secret) {
|
|
43
|
+
return fnv1a(`${version}|${timestamp}|${dataJson}|${secret}`);
|
|
44
|
+
}
|
|
21
45
|
function isValidEnvelope(value) {
|
|
22
46
|
if (typeof value !== 'object' || value === null)
|
|
23
47
|
return false;
|
|
@@ -73,6 +97,9 @@ export function writeSave(profile, slot = DEFAULT_SLOT) {
|
|
|
73
97
|
};
|
|
74
98
|
let serialized;
|
|
75
99
|
try {
|
|
100
|
+
if (profile.config.secret !== undefined) {
|
|
101
|
+
envelope.sig = _envelopeSig(envelope.version, envelope.timestamp, JSON.stringify(payload), profile.config.secret);
|
|
102
|
+
}
|
|
76
103
|
serialized = JSON.stringify(envelope);
|
|
77
104
|
}
|
|
78
105
|
catch (error) {
|
|
@@ -113,7 +140,13 @@ export function readSave(profile, slot = DEFAULT_SLOT) {
|
|
|
113
140
|
const storage = getStorage();
|
|
114
141
|
if (!storage)
|
|
115
142
|
return { ok: false, reason: 'disabled' };
|
|
116
|
-
|
|
143
|
+
let raw;
|
|
144
|
+
try {
|
|
145
|
+
raw = storage.getItem(storageKey(profile.key, slot));
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
return { ok: false, reason: 'disabled', error: error };
|
|
149
|
+
}
|
|
117
150
|
if (raw === null)
|
|
118
151
|
return { ok: false, reason: 'not_found' };
|
|
119
152
|
let parsed;
|
|
@@ -126,6 +159,12 @@ export function readSave(profile, slot = DEFAULT_SLOT) {
|
|
|
126
159
|
if (!isValidEnvelope(parsed)) {
|
|
127
160
|
return { ok: false, reason: 'corrupt' };
|
|
128
161
|
}
|
|
162
|
+
if (profile.config.secret !== undefined) {
|
|
163
|
+
const expected = _envelopeSig(parsed.version, parsed.timestamp, JSON.stringify(parsed.data), profile.config.secret);
|
|
164
|
+
if (parsed.sig !== expected) {
|
|
165
|
+
return { ok: false, reason: 'tampered' };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
129
168
|
let data = parsed.data;
|
|
130
169
|
if (parsed.version !== profile.version) {
|
|
131
170
|
if (parsed.version > profile.version) {
|
|
@@ -151,9 +190,12 @@ export function readSave(profile, slot = DEFAULT_SLOT) {
|
|
|
151
190
|
}
|
|
152
191
|
/**
|
|
153
192
|
* Reads whichever slot has the newest timestamp. Returns `not_found` if
|
|
154
|
-
* no slots exist for this profile's key
|
|
193
|
+
* no slots exist for this profile's key, `disabled` when storage itself
|
|
194
|
+
* is unavailable (so the two cases stay distinguishable).
|
|
155
195
|
*/
|
|
156
196
|
export function readSaveLatest(profile) {
|
|
197
|
+
if (!getStorage())
|
|
198
|
+
return { ok: false, reason: 'disabled' };
|
|
157
199
|
const slots = listSaves(profile);
|
|
158
200
|
if (slots.length === 0)
|
|
159
201
|
return { ok: false, reason: 'not_found' };
|
package/dist/save.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"save.js","sourceRoot":"","sources":["../src/save.ts"],"names":[],"mappings":"AAAA,+EAA+E;
|
|
1
|
+
{"version":3,"file":"save.js","sourceRoot":"","sources":["../src/save.ts"],"names":[],"mappings":"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"}
|