Skip to content

Commit 776654e

Browse files
committed
add xxh64 fastpath
1 parent f4cdd09 commit 776654e

File tree

5 files changed

+99
-25
lines changed

5 files changed

+99
-25
lines changed

frontend/src/fs.tsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
toWeb as streamToWeb,
77
/* @ts-expect-error */
88
} from "streamx-webstream";
9-
import { h64 as XXH64 } from "xxhashjs";
9+
import XXH64Worker from "./xxh64.worker?worker";
1010

1111
import iconFolder from "@ktibow/iconset-material-symbols/folder";
1212
import iconDraft from "@ktibow/iconset-material-symbols/draft";
@@ -41,16 +41,25 @@ export const TAR_TYPES = [
4141
} as FilePickerAcceptType,
4242
];
4343

44-
export async function calculateCelesteHash(): Promise<string> {
45-
const celesteFile = await rootFolder.getFileHandle("CustomCeleste.dll");
46-
const celeste = await celesteFile.getFile().then((r) => r.arrayBuffer());
44+
export async function calculateXXH64(path: string): Promise<string> {
45+
let split = path.split("/")
46+
let dir = await recursiveGetDirectory(rootFolder, split.slice(0, -1));
47+
let file = await dir.getFileHandle(split.at(-1)!);
48+
let buf = await file.getFile().then((r) => r.arrayBuffer());
4749

48-
const hash = XXH64();
49-
hash.init(0);
50-
hash.update(celeste);
51-
const out = hash.digest();
50+
let worker = new XXH64Worker({ name: `xxh64-${path}` });
51+
let promise = new Promise<string>((res, rej) => {
52+
worker.onmessage = ({ data }) => res(data.digest);
53+
worker.onerror = (x) => rej(new Error(`failed to hash: ${x}`));
54+
});
55+
worker.postMessage({ buf, }, [buf]);
56+
let res = await promise;
57+
worker.terminate();
5258

53-
return out.toString(16).toUpperCase().padStart(16, "0");
59+
return res;
60+
}
61+
export async function calculateCelesteHash(): Promise<string> {
62+
return await calculateXXH64("CustomCeleste.dll")
5463
}
5564

5665
export async function replaceHashes(hash: string) {

frontend/src/game/dotnet.ts

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { DotnetHostBuilder, MonoConfig } from "./dotnetdefs";
2-
import { recursiveGetDirectory, rootFolder } from "../fs";
2+
import { calculateXXH64, recursiveGetDirectory, rootFolder } from "../fs";
33
import { SteamJS } from "../achievements";
44
import { JsSplash } from "./loading";
55
import { epoxyFetch, EpxTcpWs, EpxWs, getWispUrl } from "../epoxy";
@@ -67,13 +67,13 @@ function hookfmod() {
6767
for (let context of contexts) {
6868
try {
6969
await context.resume();
70-
} catch {}
70+
} catch { }
7171
}
7272
} else {
7373
for (let context of contexts) {
7474
try {
7575
await context.suspend();
76-
} catch {}
76+
} catch { }
7777
}
7878
}
7979
});
@@ -89,7 +89,7 @@ useChange([gameState.playing, gameState.initting], () => {
8989
// @ts-expect-error
9090
navigator.keyboard.unlock();
9191
}
92-
} catch (err) {}
92+
} catch (err) { }
9393
});
9494

9595
let nativefetch = window.fetch;
@@ -133,15 +133,15 @@ function encryptRSA(data: Uint8Array, n: bigint, e: bigint): Uint8Array {
133133

134134
return BigInt(
135135
"0x" +
136-
[
137-
"00",
138-
"02",
139-
...padding.map((byte) => byte.toString(16).padStart(2, "0")),
140-
"00",
141-
...Array.from(messageBytes).map((byte: any) =>
142-
byte.toString(16).padStart(2, "0")
143-
),
144-
].join("")
136+
[
137+
"00",
138+
"02",
139+
...padding.map((byte) => byte.toString(16).padStart(2, "0")),
140+
"00",
141+
...Array.from(messageBytes).map((byte: any) =>
142+
byte.toString(16).padStart(2, "0")
143+
),
144+
].join("")
145145
);
146146
};
147147
const paddedMessage = pkcs1v15Pad(data, n);
@@ -273,7 +273,7 @@ export async function preInit() {
273273
let h = await file.getFile();
274274
console.log("got file cached", last);
275275
return new Response(h.stream());
276-
} catch {}
276+
} catch { }
277277
dl.download = "cross origin lol";
278278
dl.href = args[0];
279279
dl.click();
@@ -286,7 +286,7 @@ export async function preInit() {
286286
let h = await file.getFile();
287287
console.log("got file", last);
288288
return new Response(h.stream());
289-
} catch {}
289+
} catch { }
290290
await new Promise((r) => setTimeout(r, 100));
291291
}
292292
}
@@ -314,6 +314,22 @@ export async function preInit() {
314314
let encrypted = encryptRSA(data, modulus, exponent);
315315
return new Uint8Array(encrypted);
316316
},
317+
XXHash64_Fast: async (path: string) => {
318+
if (!path.startsWith("/libsdl/")) throw new Error("can't hash things not in opfs");
319+
path = path.slice("/libsdl/".length);
320+
321+
let start = performance.now();
322+
let hex = await calculateXXH64(path);
323+
let bytes = new Uint8Array(8);
324+
for (let i = 0; i < 8; i++) {
325+
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
326+
}
327+
let end = performance.now();
328+
329+
console.debug("xxh64_fast:", path, (end - start).toFixed(1));
330+
331+
return { ret: bytes };
332+
}
317333
});
318334

319335
(self as any).wasm = {

frontend/src/xxh64.worker.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { h64 as XXH64 } from "xxhashjs";
2+
3+
self.onmessage = ({ data: { buf } }) => {
4+
let hash = XXH64();
5+
hash.init(0);
6+
hash.update(buf);
7+
let out = hash.digest();
8+
9+
self.postMessage({ digest: out.toString(16).toUpperCase().padStart(16, "0") })
10+
};

loader/Celeste.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ private static void Main()
2020
[DllImport("Emscripten")]
2121
public extern static void wasm_func_viil(Int32 x, Int32 y, Int64 l);
2222

23+
[JSImport("XXHash64_Fast", "interop.js")]
24+
public static partial Task<JSObject> XXHash64_Fast(string file);
25+
2326
internal static void CallPinvokeFixers()
2427
{
2528
wasm_func_viil(0, 0, 0);
@@ -113,6 +116,19 @@ internal static Task Init(bool tailcalls)
113116
{
114117
var ParseArgs = Everest.GetMethod("ParseArgs", BindingFlags.Static | BindingFlags.NonPublic);
115118
ParseArgs.Invoke(null, [new string[] { }]);
119+
120+
var XXH64_Fast = Everest.GetField("CelesteWasm_XXHash64Fast", BindingFlags.Static | BindingFlags.Public);
121+
if (XXH64_Fast != null) {
122+
Func<string, byte[]> XXH64 = (path) => {
123+
try {
124+
var ret = XXHash64_Fast(path).Result;
125+
return ret.GetPropertyAsByteArray("ret");
126+
} catch {
127+
return null;
128+
}
129+
};
130+
XXH64_Fast.SetValue(null, XXH64);
131+
}
116132
}
117133

118134
Console.WriteLine($"CelesteWasm on {RuntimeInformation.FrameworkDescription}");

patcher/EverestMods.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using MonoMod;
44
using MonoMod.Cil;
55
using System;
6+
using System.Linq;
7+
using System.IO;
68
using System.Collections.Generic;
79

810
namespace Celeste.Mod
@@ -18,13 +20,34 @@ private static void InitMMFlags(MonoModder modder)
1820

1921
modder.DependencyDirs.Add("/bin/");
2022
modder.Mods.Add(ModuleDefinition.ReadModule("/bin/Celeste.Wasm.mm.dll"));
21-
modder.RemovePatchReferences = false;
23+
modder.RemovePatchReferences = false;
2224
}
2325

2426
[MonoModIgnore]
2527
[PatchRelinkModuleMapPath]
2628
public extern static Dictionary<string, ModuleDefinition> get_SharedRelinkModuleMap();
2729
}
30+
31+
[MonoModIgnore]
32+
public extern static byte[] ComputeHash(Stream inputStream);
33+
34+
public static Func<string, byte[]> CelesteWasm_XXHash64Fast;
35+
36+
// forward to js
37+
public static byte[] GetChecksum(string path)
38+
{
39+
if (CelesteWasm_XXHash64Fast != null)
40+
{
41+
var res = CelesteWasm_XXHash64Fast(path);
42+
if (res != null)
43+
return res;
44+
}
45+
46+
using (FileStream fs = File.OpenRead(path))
47+
{
48+
return ComputeHash(fs);
49+
}
50+
}
2851
}
2952

3053
public sealed class patch_EverestModuleAssemblyContext

0 commit comments

Comments
 (0)