feat: File* save/load actions, hashlib/uuid, dict spread and small renpy helpers in the player
Games with their own save screens work: FileAction/FileSave/FileLoad/FileDelete/FilePage*, FileLoadable,
FileTime, FileSaveName, FileJson and friends use browser slots (a slot has no screenshot). Added
renpy.has_image/has_screen/list_files/get_mouse_pos/log/jump_out_of_context/random.seed,
renpy.translation.translate_string, type/id/vars/Flatten/LiveComposite/Composite, bare defaultdict/
deepcopy, md5/sha256 (checked against test vectors), uuid4, time.monotonic, re.compile().fullmatch,
math.isfinite and `{**a, "k": v}`. The converter whitelists them and accepts the hashlib/uuid imports.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5c1e1aee01
commit
16ba20816c
8 files changed
+366
-20
No files matched your search
@@ -98,7 +98,7 @@ class DateTime {
|
||||
isoformat() { return `${this.year}-${pad(this.month)}-${pad(this.day)}T${pad(this.hour)}:${pad(this.minute)}:${pad(this.second)}`; }
|
||||
}
|
||||
|
||||
function strftime(format: string, d: Date): string {
|
||||
export function strftime(format: string, d: Date): string {
|
||||
const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
||||
const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
||||
return format.replace(/%([a-zA-Z%])/g, (_m, c: string) => {
|
||||
@@ -176,6 +176,83 @@ Object.assign(DateTime.prototype, {
|
||||
},
|
||||
});
|
||||
|
||||
function md5(bytes: Uint8Array): string {
|
||||
const K = Array.from({ length: 64 }, (_, i) => Math.floor(Math.abs(Math.sin(i + 1)) * 2 ** 32) >>> 0);
|
||||
const S = [7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21];
|
||||
let a0 = 0x67452301, b0 = 0xefcdab89, c0 = 0x98badcfe, d0 = 0x10325476;
|
||||
const padded = new Uint8Array(((bytes.length + 8) >> 6 << 6) + 64);
|
||||
padded.set(bytes);
|
||||
padded[bytes.length] = 0x80;
|
||||
const view = new DataView(padded.buffer);
|
||||
view.setUint32(padded.length - 8, (bytes.length * 8) >>> 0, true);
|
||||
view.setUint32(padded.length - 4, Math.floor(bytes.length * 8 / 2 ** 32), true);
|
||||
for (let off = 0; off < padded.length; off += 64) {
|
||||
const m = Array.from({ length: 16 }, (_, i) => view.getUint32(off + i * 4, true));
|
||||
let a = a0, b = b0, c = c0, d = d0;
|
||||
for (let i = 0; i < 64; i++) {
|
||||
let f: number, g: number;
|
||||
if (i < 16) { f = (b & c) | (~b & d); g = i; }
|
||||
else if (i < 32) { f = (d & b) | (~d & c); g = (5 * i + 1) % 16; }
|
||||
else if (i < 48) { f = b ^ c ^ d; g = (3 * i + 5) % 16; }
|
||||
else { f = c ^ (b | ~d); g = (7 * i) % 16; }
|
||||
f = (f + a + K[i] + m[g]) >>> 0;
|
||||
a = d; d = c; c = b;
|
||||
const s = S[(i >> 4) * 4 + (i % 4)];
|
||||
b = (b + ((f << s) | (f >>> (32 - s)))) >>> 0;
|
||||
}
|
||||
a0 = (a0 + a) >>> 0; b0 = (b0 + b) >>> 0; c0 = (c0 + c) >>> 0; d0 = (d0 + d) >>> 0;
|
||||
}
|
||||
return [a0, b0, c0, d0].map((v) => [0, 8, 16, 24].map((sh) => ((v >>> sh) & 255).toString(16).padStart(2, "0")).join("")).join("");
|
||||
}
|
||||
|
||||
function sha256(bytes: Uint8Array): string {
|
||||
const K = Array.from({ length: 64 }, (_, i) => {
|
||||
let n = 2, count = 0;
|
||||
for (; count <= i; n++) if (Array.from({ length: n - 2 }, (_, d) => d + 2).every((d) => n % d)) count++;
|
||||
return Math.floor((Math.cbrt(n - 1) % 1) * 2 ** 32) >>> 0;
|
||||
});
|
||||
const h = [2, 3, 5, 7, 11, 13, 17, 19].map((p) => Math.floor((Math.sqrt(p) % 1) * 2 ** 32) >>> 0);
|
||||
const padded = new Uint8Array(((bytes.length + 8) >> 6 << 6) + 64);
|
||||
padded.set(bytes);
|
||||
padded[bytes.length] = 0x80;
|
||||
const view = new DataView(padded.buffer);
|
||||
view.setUint32(padded.length - 8, Math.floor(bytes.length * 8 / 2 ** 32));
|
||||
view.setUint32(padded.length - 4, (bytes.length * 8) >>> 0);
|
||||
const rotr = (x: number, n: number) => (x >>> n) | (x << (32 - n));
|
||||
for (let off = 0; off < padded.length; off += 64) {
|
||||
const w = Array.from({ length: 64 }, (_, i) => (i < 16 ? view.getUint32(off + i * 4) : 0));
|
||||
for (let i = 16; i < 64; i++) {
|
||||
const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ (w[i - 15] >>> 3);
|
||||
const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ (w[i - 2] >>> 10);
|
||||
w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0;
|
||||
}
|
||||
let [a, b, c, d, e, f, g, hh] = h;
|
||||
for (let i = 0; i < 64; i++) {
|
||||
const t1 = (hh + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + ((e & f) ^ (~e & g)) + K[i] + w[i]) >>> 0;
|
||||
const t2 = ((rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + ((a & b) ^ (a & c) ^ (b & c))) >>> 0;
|
||||
hh = g; g = f; f = e; e = (d + t1) >>> 0; d = c; c = b; b = a; a = (t1 + t2) >>> 0;
|
||||
}
|
||||
[a, b, c, d, e, f, g, hh].forEach((v, i) => { h[i] = (h[i] + v) >>> 0; });
|
||||
}
|
||||
return h.map((v) => v.toString(16).padStart(8, "0")).join("");
|
||||
}
|
||||
|
||||
/** `hashlib.md5(data)`: the data may come later through `update`. */
|
||||
class Hash {
|
||||
private parts: Uint8Array[] = [];
|
||||
constructor(private digest: (bytes: Uint8Array) => string, data?: any) { if (data !== undefined) this.update(data); }
|
||||
update(data: any) {
|
||||
this.parts.push(typeof data === "string" ? new TextEncoder().encode(data) : Uint8Array.from(data ?? []));
|
||||
return null;
|
||||
}
|
||||
hexdigest() {
|
||||
const all = new Uint8Array(this.parts.reduce((n, p) => n + p.length, 0));
|
||||
let at = 0;
|
||||
for (const p of this.parts) { all.set(p, at); at += p.length; }
|
||||
return this.digest(all);
|
||||
}
|
||||
}
|
||||
|
||||
/** Namespaces added to the sandbox's builtins. */
|
||||
export function pythonModules(): Record<string, any> {
|
||||
const re = {
|
||||
@@ -186,6 +263,7 @@ export function pythonModules(): Record<string, any> {
|
||||
match: (s: string) => re.match(pattern, s, flags),
|
||||
sub: (r: string, s: string) => re.sub(pattern, r, s, 0, flags),
|
||||
findall: (s: string) => re.findall(pattern, s, flags),
|
||||
fullmatch: (s: string) => re.fullmatch(pattern, s, flags),
|
||||
split: (s: string) => re.split(pattern, s, 0, flags),
|
||||
}),
|
||||
search: (pattern: string, s: string, flags = 0) => {
|
||||
@@ -196,6 +274,10 @@ export function pythonModules(): Record<string, any> {
|
||||
const m = compile(`^(?:${pattern})`, flags).exec(String(s));
|
||||
return m ? new ReMatch(m) : null;
|
||||
},
|
||||
fullmatch: (pattern: string, s: string, flags = 0) => {
|
||||
const m = compile(`^(?:${pattern})$`, flags).exec(String(s));
|
||||
return m ? new ReMatch(m) : null;
|
||||
},
|
||||
findall: (pattern: string, s: string, flags = 0) =>
|
||||
[...String(s).matchAll(compile(pattern, flags, true))].map((m) => (m.length > 2 ? m.slice(1) : m[1] ?? m[0])),
|
||||
sub: (pattern: string, replacement: any, s: string, count = 0, flags = 0) => {
|
||||
@@ -220,6 +302,7 @@ export function pythonModules(): Record<string, any> {
|
||||
copy: { copy: (v: any) => (Array.isArray(v) ? [...v] : v instanceof PyDict ? new PyDict(v.map.entries()) : v), deepcopy: (v: any) => deepcopy(v) },
|
||||
time: {
|
||||
time: () => Date.now() / 1000,
|
||||
monotonic: () => performance.now() / 1000,
|
||||
sleep: () => null,
|
||||
strftime: (format: string, t?: any) => strftime(format, t instanceof DateTime ? t.d : new Date()),
|
||||
},
|
||||
@@ -229,5 +312,18 @@ export function pythonModules(): Record<string, any> {
|
||||
OrderedDict: () => new PyDict(),
|
||||
},
|
||||
functools: functionModules,
|
||||
hashlib: {
|
||||
md5: (data?: any) => new Hash(md5, data),
|
||||
sha256: (data?: any) => new Hash(sha256, data),
|
||||
},
|
||||
uuid: {
|
||||
uuid4: () => ({ hex: crypto.randomUUID().replaceAll("-", ""), toString: () => crypto.randomUUID() }),
|
||||
},
|
||||
// `from collections import defaultdict` and friends leave the bare names
|
||||
defaultdict: (factory?: any) => new PyDefaultDict(typeof factory === "function" ? () => factory() : null),
|
||||
OrderedDict: () => new PyDict(),
|
||||
deepcopy: (v: any) => deepcopy(v),
|
||||
partial,
|
||||
timedelta,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user