risupack 0.1.0
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/LICENSE +661 -0
- package/README.md +155 -0
- package/dist/build-charx.d.ts +2 -0
- package/dist/bundle.d.ts +2 -0
- package/dist/cli.js +73 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +2 -0
- package/dist/inspect-risusave.d.ts +8 -0
- package/dist/unpack-charx.d.ts +5 -0
- package/dist/unpack-risum-8Iw-UEO2.js +1055 -0
- package/dist/unpack-risum-8Iw-UEO2.js.map +1 -0
- package/dist/unpack-risum.d.ts +2 -0
- package/package.json +45 -0
- package/vendor/rpack/LICENSE +6 -0
- package/vendor/rpack/LICENSE_AGPL +661 -0
- package/vendor/rpack/LICENSE_MIT +21 -0
- package/vendor/rpack/README +5 -0
- package/vendor/rpack/rpack_map.bin +0 -0
|
@@ -0,0 +1,1055 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import zlib from "node:zlib";
|
|
7
|
+
//#region src/bundle.ts
|
|
8
|
+
var REQUIRE_PATTERN = /require\s*\(?["']([^"']+)["']\)?/g;
|
|
9
|
+
function formatBytes(bytes) {
|
|
10
|
+
if (bytes === 0) return "0 B";
|
|
11
|
+
const base = 1024;
|
|
12
|
+
const sizes = [
|
|
13
|
+
"B",
|
|
14
|
+
"KB",
|
|
15
|
+
"MB"
|
|
16
|
+
];
|
|
17
|
+
const index = Math.floor(Math.log(bytes) / Math.log(base));
|
|
18
|
+
return `${Number.parseFloat((bytes / Math.pow(base, index)).toFixed(2))} ${sizes[index]}`;
|
|
19
|
+
}
|
|
20
|
+
function resolveModulePath(moduleName) {
|
|
21
|
+
return `${moduleName.replace(/\./g, "/")}.lua`;
|
|
22
|
+
}
|
|
23
|
+
function minifyLua(content, hoistedComments) {
|
|
24
|
+
return content.replace(/(--\[(=*)\[[\s\S]*?\]\2\])|(--.*)|(\[(=*)\[[\s\S]*?\]\5\])|("([^"\\]|\\.)*")|('([^'\\]|\\.)*')/g, (match, longComment, _equals, shortComment) => {
|
|
25
|
+
if (!longComment && !shortComment) return match;
|
|
26
|
+
if (shortComment?.startsWith("--!") || longComment && /^--\[(=*)\[!/.test(match)) hoistedComments.push(match);
|
|
27
|
+
return " ";
|
|
28
|
+
}).split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
29
|
+
}
|
|
30
|
+
function processModule(moduleName, state) {
|
|
31
|
+
if (state.includedModules.has(moduleName)) return;
|
|
32
|
+
const rootDirectory = path.dirname(path.resolve(state.entryPath));
|
|
33
|
+
const modulePath = path.join(rootDirectory, resolveModulePath(moduleName));
|
|
34
|
+
if (!fs.existsSync(modulePath)) {
|
|
35
|
+
console.warn(`Warning: Module '${moduleName}' not found`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const rawContent = fs.readFileSync(modulePath, "utf8");
|
|
39
|
+
state.includedModules.add(moduleName);
|
|
40
|
+
state.totalInputBytes += fs.statSync(modulePath).size;
|
|
41
|
+
for (const match of rawContent.matchAll(new RegExp(REQUIRE_PATTERN.source, "g"))) {
|
|
42
|
+
const dependency = match[1];
|
|
43
|
+
if (dependency) processModule(dependency, state);
|
|
44
|
+
}
|
|
45
|
+
const minifiedContent = minifyLua(rawContent, state.hoistedComments);
|
|
46
|
+
state.preloads.push(`package.preload["${moduleName}"]=function(...)${minifiedContent} end`);
|
|
47
|
+
}
|
|
48
|
+
function bundleLua(entryPath, outputArgument) {
|
|
49
|
+
const resolvedEntryPath = path.resolve(entryPath);
|
|
50
|
+
if (!fs.existsSync(resolvedEntryPath)) throw new Error(`Entry file not found: ${entryPath}`);
|
|
51
|
+
let outputPath = path.resolve(outputArgument);
|
|
52
|
+
if (fs.existsSync(outputPath) && fs.statSync(outputPath).isDirectory()) outputPath = path.join(outputPath, path.basename(resolvedEntryPath));
|
|
53
|
+
if (path.extname(outputPath) !== ".lua") outputPath += ".lua";
|
|
54
|
+
const state = {
|
|
55
|
+
entryPath: resolvedEntryPath,
|
|
56
|
+
hoistedComments: [],
|
|
57
|
+
includedModules: /* @__PURE__ */ new Set(),
|
|
58
|
+
preloads: [],
|
|
59
|
+
totalInputBytes: fs.statSync(resolvedEntryPath).size
|
|
60
|
+
};
|
|
61
|
+
const mainContent = fs.readFileSync(resolvedEntryPath, "utf8");
|
|
62
|
+
for (const match of mainContent.matchAll(new RegExp(REQUIRE_PATTERN.source, "g"))) {
|
|
63
|
+
const moduleName = match[1];
|
|
64
|
+
if (moduleName) processModule(moduleName, state);
|
|
65
|
+
}
|
|
66
|
+
const mainMinified = minifyLua(mainContent, state.hoistedComments);
|
|
67
|
+
const output = `${state.hoistedComments.length > 0 ? `${state.hoistedComments.join("\n")}\n` : ""}${state.preloads.join("\n")}\n${mainMinified}`;
|
|
68
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
69
|
+
fs.writeFileSync(outputPath, output, "utf8");
|
|
70
|
+
const outputBytes = fs.statSync(outputPath).size;
|
|
71
|
+
const reduction = (state.totalInputBytes - outputBytes) / state.totalInputBytes * 100;
|
|
72
|
+
console.log(`Bundled ${path.relative(process.cwd(), resolvedEntryPath)} to ${path.relative(process.cwd(), outputPath)}`);
|
|
73
|
+
console.log(`${state.includedModules.size} modules, ${formatBytes(state.totalInputBytes)} to ${formatBytes(outputBytes)}, ${reduction.toFixed(2)}% reduction`);
|
|
74
|
+
return outputPath;
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/build-charx.ts
|
|
78
|
+
var DEFAULT_ICON = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M/wHwAEAQH/69v17QAAAABJRU5ErkJggg==", "base64");
|
|
79
|
+
var RPACK_MAP_PATH$2 = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "vendor", "rpack", "rpack_map.bin");
|
|
80
|
+
var CRC32_TABLE$1 = (() => {
|
|
81
|
+
const table = /* @__PURE__ */ new Uint32Array(256);
|
|
82
|
+
for (let index = 0; index < table.length; index += 1) {
|
|
83
|
+
let value = index;
|
|
84
|
+
for (let bit = 0; bit < 8; bit += 1) if ((value & 1) !== 0) value = 3988292384 ^ value >>> 1;
|
|
85
|
+
else value >>>= 1;
|
|
86
|
+
table[index] = value >>> 0;
|
|
87
|
+
}
|
|
88
|
+
return table;
|
|
89
|
+
})();
|
|
90
|
+
function assert$2(condition, message) {
|
|
91
|
+
if (!condition) throw new Error(message);
|
|
92
|
+
}
|
|
93
|
+
function calculateCRC32$1(data) {
|
|
94
|
+
let value = 4294967295;
|
|
95
|
+
for (const byte of data) value = CRC32_TABLE$1[(value ^ byte) & 255] ^ value >>> 8;
|
|
96
|
+
return (value ^ 4294967295) >>> 0;
|
|
97
|
+
}
|
|
98
|
+
function createUUID(seed) {
|
|
99
|
+
const bytes = crypto.createHash("sha256").update(seed).digest().subarray(0, 16);
|
|
100
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
101
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
102
|
+
const hex = bytes.toString("hex");
|
|
103
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
104
|
+
}
|
|
105
|
+
function detectImageType(data) {
|
|
106
|
+
if (data.subarray(0, 8).equals(Buffer.from("89504e470d0a1a0a", "hex"))) return "PNG";
|
|
107
|
+
if (data.subarray(0, 3).equals(Buffer.from("ffd8ff", "hex"))) return "JPEG";
|
|
108
|
+
if (data.subarray(0, 6).toString("ascii") === "GIF87a" || data.subarray(0, 6).toString("ascii") === "GIF89a") return "GIF";
|
|
109
|
+
if (data.subarray(0, 4).toString("ascii") === "RIFF" && data.subarray(8, 12).toString("ascii") === "WEBP") return "WEBP";
|
|
110
|
+
return "Unknown";
|
|
111
|
+
}
|
|
112
|
+
function getAssetCategory(extension) {
|
|
113
|
+
const audio = /* @__PURE__ */ new Set([
|
|
114
|
+
"flac",
|
|
115
|
+
"mp3",
|
|
116
|
+
"ogg",
|
|
117
|
+
"wav"
|
|
118
|
+
]);
|
|
119
|
+
const code = /* @__PURE__ */ new Set([
|
|
120
|
+
"js",
|
|
121
|
+
"lua",
|
|
122
|
+
"ts"
|
|
123
|
+
]);
|
|
124
|
+
const fonts = /* @__PURE__ */ new Set([
|
|
125
|
+
"otf",
|
|
126
|
+
"ttf",
|
|
127
|
+
"woff",
|
|
128
|
+
"woff2"
|
|
129
|
+
]);
|
|
130
|
+
const images = /* @__PURE__ */ new Set([
|
|
131
|
+
"avif",
|
|
132
|
+
"gif",
|
|
133
|
+
"jpeg",
|
|
134
|
+
"jpg",
|
|
135
|
+
"png",
|
|
136
|
+
"webp"
|
|
137
|
+
]);
|
|
138
|
+
const models = /* @__PURE__ */ new Set(["mmd", "obj"]);
|
|
139
|
+
const video = /* @__PURE__ */ new Set([
|
|
140
|
+
"avi",
|
|
141
|
+
"mkv",
|
|
142
|
+
"mov",
|
|
143
|
+
"mp4",
|
|
144
|
+
"webm"
|
|
145
|
+
]);
|
|
146
|
+
if (audio.has(extension)) return "audio";
|
|
147
|
+
if (code.has(extension)) return "code";
|
|
148
|
+
if (fonts.has(extension)) return "fonts";
|
|
149
|
+
if (images.has(extension)) return "image";
|
|
150
|
+
if (models.has(extension)) return "model";
|
|
151
|
+
if (extension === "onnx" || extension === "safetensors" || extension === "cpkt") return "ai";
|
|
152
|
+
if (video.has(extension)) return "video";
|
|
153
|
+
return "other";
|
|
154
|
+
}
|
|
155
|
+
function readSource(manifestDirectory, source, label) {
|
|
156
|
+
if (typeof source === "string") return fs.readFileSync(path.resolve(manifestDirectory, source), "utf8");
|
|
157
|
+
assert$2(source && typeof source === "object", `${label} must be a file path or source object`);
|
|
158
|
+
if (typeof source.content === "string") return source.content;
|
|
159
|
+
assert$2(typeof source.file === "string", `${label}.file must be a string`);
|
|
160
|
+
return fs.readFileSync(path.resolve(manifestDirectory, source.file), "utf8");
|
|
161
|
+
}
|
|
162
|
+
function sanitizeArchiveName(name) {
|
|
163
|
+
return Array.from(name, (character) => character.charCodeAt(0) < 32 ? "_" : character).join("").replace(/[<>:"/\\|?*]/g, "_").trim().slice(0, 100) || "asset";
|
|
164
|
+
}
|
|
165
|
+
function sortKeysDeep$1(value) {
|
|
166
|
+
if (Array.isArray(value)) return value.map(sortKeysDeep$1);
|
|
167
|
+
if (value && typeof value === "object" && !Buffer.isBuffer(value)) {
|
|
168
|
+
const sorted = {};
|
|
169
|
+
for (const key of Object.keys(value).sort()) if (value[key] !== void 0) sorted[key] = sortKeysDeep$1(value[key]);
|
|
170
|
+
return sorted;
|
|
171
|
+
}
|
|
172
|
+
return value;
|
|
173
|
+
}
|
|
174
|
+
function parseFrontmatter(content, fileName) {
|
|
175
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
176
|
+
assert$2(normalized.startsWith("---\n"), `Missing frontmatter in ${fileName}`);
|
|
177
|
+
const end = normalized.indexOf("\n---\n", 4);
|
|
178
|
+
assert$2(end !== -1, `Unclosed frontmatter in ${fileName}`);
|
|
179
|
+
const metadata = {};
|
|
180
|
+
for (const line of normalized.slice(4, end).split("\n")) {
|
|
181
|
+
if (line.trim() === "") continue;
|
|
182
|
+
const separator = line.indexOf(":");
|
|
183
|
+
assert$2(separator !== -1, `Invalid frontmatter line in ${fileName}: ${line}`);
|
|
184
|
+
const key = line.slice(0, separator).trim();
|
|
185
|
+
const rawValue = line.slice(separator + 1).trim();
|
|
186
|
+
assert$2(key !== "", `Empty frontmatter key in ${fileName}`);
|
|
187
|
+
if (rawValue === "true") metadata[key] = true;
|
|
188
|
+
else if (rawValue === "false") metadata[key] = false;
|
|
189
|
+
else metadata[key] = rawValue;
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
body: normalized.slice(end + 5),
|
|
193
|
+
metadata
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function parseRegexBody(content, fileName) {
|
|
197
|
+
const match = content.replace(/^\n+/, "").replace(/\n$/, "").match(/^IN:\s*\n([\s\S]*?)\nOUT:\s*(?:\n([\s\S]*))?$/);
|
|
198
|
+
assert$2(match, `Invalid regex body in ${fileName}`);
|
|
199
|
+
return {
|
|
200
|
+
in: match[1],
|
|
201
|
+
out: match[2] ?? ""
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function parseRegexDocument(content, fileName) {
|
|
205
|
+
const { body, metadata } = parseFrontmatter(content, fileName);
|
|
206
|
+
const parsed = parseRegexBody(body, fileName);
|
|
207
|
+
return {
|
|
208
|
+
ableFlag: metadata.ableFlag ?? true,
|
|
209
|
+
comment: metadata.comment ?? "",
|
|
210
|
+
flag: metadata.flag,
|
|
211
|
+
in: parsed.in,
|
|
212
|
+
out: parsed.out,
|
|
213
|
+
type: metadata.type ?? "editdisplay"
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function buildRegexScripts(manifestDirectory, regexGroups = []) {
|
|
217
|
+
const scripts = [];
|
|
218
|
+
for (const group of regexGroups) {
|
|
219
|
+
if (typeof group === "string") {
|
|
220
|
+
scripts.push(parseRegexDocument(readSource(manifestDirectory, group, `regex ${group}`), group));
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (group.in !== void 0 || group.out !== void 0) {
|
|
224
|
+
assert$2(typeof group.in === "string" && typeof group.out === "string", "Inline regex entries require in and out");
|
|
225
|
+
scripts.push({
|
|
226
|
+
ableFlag: group.ableFlag ?? true,
|
|
227
|
+
comment: group.comment ?? "",
|
|
228
|
+
flag: group.flag,
|
|
229
|
+
in: group.in,
|
|
230
|
+
out: group.out,
|
|
231
|
+
type: group.type ?? "editdisplay"
|
|
232
|
+
});
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
assert$2(typeof group.file === "string", "Regex entries require a file path, or inline in and out fields");
|
|
236
|
+
scripts.push(parseRegexDocument(readSource(manifestDirectory, group.file, `regex ${group.file}`), group.file));
|
|
237
|
+
}
|
|
238
|
+
return scripts;
|
|
239
|
+
}
|
|
240
|
+
function buildLorebook(manifest, manifestDirectory) {
|
|
241
|
+
const folderKeys = /* @__PURE__ */ new Map();
|
|
242
|
+
const folderNames = new Set(manifest.folders ?? []);
|
|
243
|
+
for (const entry of manifest.lorebook ?? []) if (entry.folder) folderNames.add(entry.folder);
|
|
244
|
+
for (const name of folderNames) folderKeys.set(name, `\uf000folder:${createUUID(`${manifest.namespace ?? manifest.name}:folder:${name}`)}`);
|
|
245
|
+
const temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "risums-charx-lorebook-"));
|
|
246
|
+
try {
|
|
247
|
+
const lorebook = (manifest.lorebook ?? []).map((entry, index) => {
|
|
248
|
+
let content;
|
|
249
|
+
if (entry.bundle) {
|
|
250
|
+
assert$2(typeof entry.file === "string", `Bundled lorebook ${entry.comment ?? index + 1} requires a file`);
|
|
251
|
+
content = bundleLua(path.resolve(manifestDirectory, entry.file), entry.bundleOutput ? path.resolve(manifestDirectory, entry.bundleOutput) : path.join(temporaryDirectory, `lorebook-${index}.lua`));
|
|
252
|
+
} else content = readSource(manifestDirectory, entry, `lorebook ${entry.comment ?? entry.file ?? ""}`);
|
|
253
|
+
return {
|
|
254
|
+
activationPercent: entry.activationPercent,
|
|
255
|
+
alwaysActive: entry.alwaysActive ?? false,
|
|
256
|
+
bookVersion: entry.bookVersion ?? 2,
|
|
257
|
+
comment: entry.comment ?? path.basename(entry.file ?? "Lorebook"),
|
|
258
|
+
content,
|
|
259
|
+
extentions: entry.extentions,
|
|
260
|
+
folder: entry.folder ? folderKeys.get(entry.folder) : void 0,
|
|
261
|
+
insertorder: entry.insertOrder ?? 100,
|
|
262
|
+
key: entry.key ?? "",
|
|
263
|
+
mode: entry.mode ?? "normal",
|
|
264
|
+
secondkey: entry.secondaryKey ?? "",
|
|
265
|
+
selective: entry.selective ?? false,
|
|
266
|
+
useRegex: entry.useRegex ?? false
|
|
267
|
+
};
|
|
268
|
+
});
|
|
269
|
+
for (const name of folderNames) lorebook.push({
|
|
270
|
+
alwaysActive: false,
|
|
271
|
+
bookVersion: 2,
|
|
272
|
+
comment: name,
|
|
273
|
+
content: "",
|
|
274
|
+
insertorder: 100,
|
|
275
|
+
key: folderKeys.get(name),
|
|
276
|
+
mode: "folder",
|
|
277
|
+
secondkey: "",
|
|
278
|
+
selective: false,
|
|
279
|
+
useRegex: false
|
|
280
|
+
});
|
|
281
|
+
return lorebook;
|
|
282
|
+
} finally {
|
|
283
|
+
fs.rmSync(temporaryDirectory, {
|
|
284
|
+
force: true,
|
|
285
|
+
recursive: true
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function readBundledLua(entryPath, outputPath) {
|
|
290
|
+
bundleLua(entryPath, outputPath);
|
|
291
|
+
return fs.readFileSync(outputPath, "utf8");
|
|
292
|
+
}
|
|
293
|
+
function buildTriggers(manifest, manifestDirectory) {
|
|
294
|
+
const temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "risums-charx-"));
|
|
295
|
+
try {
|
|
296
|
+
return (manifest.triggers ?? []).map((trigger, index) => {
|
|
297
|
+
let effect = trigger.effect;
|
|
298
|
+
if (trigger.lua) {
|
|
299
|
+
const entryPath = path.resolve(manifestDirectory, trigger.lua);
|
|
300
|
+
let code;
|
|
301
|
+
if (trigger.bundle ?? true) code = readBundledLua(entryPath, trigger.bundleOutput ? path.resolve(manifestDirectory, trigger.bundleOutput) : path.join(temporaryDirectory, `trigger-${index}.lua`));
|
|
302
|
+
else code = fs.readFileSync(entryPath, "utf8");
|
|
303
|
+
effect = [{
|
|
304
|
+
code,
|
|
305
|
+
type: "triggerlua"
|
|
306
|
+
}];
|
|
307
|
+
}
|
|
308
|
+
assert$2(Array.isArray(effect), `Trigger ${index + 1} requires lua or effect`);
|
|
309
|
+
return {
|
|
310
|
+
comment: trigger.comment ?? "",
|
|
311
|
+
conditions: trigger.conditions ?? [],
|
|
312
|
+
effect,
|
|
313
|
+
lowLevelAccess: trigger.lowLevelAccess,
|
|
314
|
+
type: trigger.type ?? "start"
|
|
315
|
+
};
|
|
316
|
+
});
|
|
317
|
+
} finally {
|
|
318
|
+
fs.rmSync(temporaryDirectory, {
|
|
319
|
+
force: true,
|
|
320
|
+
recursive: true
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function createRisuM(module, encodeMap) {
|
|
325
|
+
const payload = Buffer.from(JSON.stringify(sortKeysDeep$1({
|
|
326
|
+
module,
|
|
327
|
+
type: "risuModule"
|
|
328
|
+
}), null, 2), "utf8");
|
|
329
|
+
const encoded = Buffer.allocUnsafe(payload.length);
|
|
330
|
+
for (let index = 0; index < payload.length; index += 1) encoded[index] = encodeMap[payload[index]];
|
|
331
|
+
const header = Buffer.alloc(6);
|
|
332
|
+
header.writeUInt8(111, 0);
|
|
333
|
+
header.writeUInt8(0, 1);
|
|
334
|
+
header.writeUInt32LE(encoded.length, 2);
|
|
335
|
+
return Buffer.concat([
|
|
336
|
+
header,
|
|
337
|
+
encoded,
|
|
338
|
+
Buffer.from([0])
|
|
339
|
+
]);
|
|
340
|
+
}
|
|
341
|
+
function createAssetFiles(manifest, manifestDirectory) {
|
|
342
|
+
const cardAssets = [];
|
|
343
|
+
const files = [];
|
|
344
|
+
const takenNames = /* @__PURE__ */ new Set();
|
|
345
|
+
function addAsset({ data, extension, name, type }) {
|
|
346
|
+
const category = getAssetCategory(extension);
|
|
347
|
+
const baseDirectory = `assets/${type === "icon" ? "icon" : "other"}/${category}`;
|
|
348
|
+
const baseName = sanitizeArchiveName(name);
|
|
349
|
+
let archiveName = baseName;
|
|
350
|
+
let suffix = 0;
|
|
351
|
+
while (takenNames.has(archiveName)) {
|
|
352
|
+
suffix += 1;
|
|
353
|
+
archiveName = `${baseName}_${suffix}`;
|
|
354
|
+
}
|
|
355
|
+
const archivePath = `${baseDirectory}/${archiveName}.${extension}`;
|
|
356
|
+
takenNames.add(archiveName);
|
|
357
|
+
cardAssets.push({
|
|
358
|
+
ext: extension,
|
|
359
|
+
name,
|
|
360
|
+
type,
|
|
361
|
+
uri: `embeded://${archivePath}`
|
|
362
|
+
});
|
|
363
|
+
files.push({
|
|
364
|
+
data,
|
|
365
|
+
name: archivePath
|
|
366
|
+
});
|
|
367
|
+
files.push({
|
|
368
|
+
data: Buffer.from(JSON.stringify({ type: detectImageType(data) }, null, 2)),
|
|
369
|
+
name: `x_meta/${archiveName}.json`
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
if (manifest.icon) {
|
|
373
|
+
const iconPath = path.resolve(manifestDirectory, manifest.icon);
|
|
374
|
+
const extension = path.extname(iconPath).slice(1).toLowerCase() || "png";
|
|
375
|
+
addAsset({
|
|
376
|
+
data: fs.readFileSync(iconPath),
|
|
377
|
+
extension,
|
|
378
|
+
name: "main",
|
|
379
|
+
type: "icon"
|
|
380
|
+
});
|
|
381
|
+
} else addAsset({
|
|
382
|
+
data: DEFAULT_ICON,
|
|
383
|
+
extension: "png",
|
|
384
|
+
name: "main",
|
|
385
|
+
type: "icon"
|
|
386
|
+
});
|
|
387
|
+
for (const asset of manifest.assets ?? []) {
|
|
388
|
+
assert$2(typeof asset.file === "string", "Asset file must be a string");
|
|
389
|
+
const assetPath = path.resolve(manifestDirectory, asset.file);
|
|
390
|
+
const extension = (asset.extension ?? path.extname(assetPath).slice(1)).toLowerCase();
|
|
391
|
+
assert$2(extension !== "", `Cannot determine extension for ${asset.file}`);
|
|
392
|
+
assert$2(/^[a-z0-9]+$/.test(extension), `Invalid extension for ${asset.file}: ${extension}`);
|
|
393
|
+
addAsset({
|
|
394
|
+
data: fs.readFileSync(assetPath),
|
|
395
|
+
extension,
|
|
396
|
+
name: asset.name ?? path.basename(assetPath, path.extname(assetPath)),
|
|
397
|
+
type: "x-risu-asset"
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
return {
|
|
401
|
+
cardAssets,
|
|
402
|
+
files
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
function createCard(manifest, lorebook, cardAssets) {
|
|
406
|
+
return {
|
|
407
|
+
data: {
|
|
408
|
+
alternate_greetings: [],
|
|
409
|
+
assets: cardAssets,
|
|
410
|
+
character_book: {
|
|
411
|
+
entries: lorebook.map((entry) => ({
|
|
412
|
+
case_sensitive: entry.extentions?.risu_case_sensitive ?? false,
|
|
413
|
+
comment: entry.comment,
|
|
414
|
+
constant: entry.alwaysActive,
|
|
415
|
+
content: entry.content,
|
|
416
|
+
enabled: true,
|
|
417
|
+
extensions: {
|
|
418
|
+
...entry.extentions,
|
|
419
|
+
risu_activationPercent: entry.activationPercent,
|
|
420
|
+
risu_loreCache: entry.loreCache
|
|
421
|
+
},
|
|
422
|
+
folder: entry.folder,
|
|
423
|
+
insertion_order: entry.insertorder,
|
|
424
|
+
keys: entry.key.split(",").map((key) => key.trim()),
|
|
425
|
+
mode: entry.mode,
|
|
426
|
+
name: entry.comment,
|
|
427
|
+
secondary_keys: entry.selective ? entry.secondkey.split(",").map((key) => key.trim()) : void 0,
|
|
428
|
+
selective: entry.selective,
|
|
429
|
+
use_regex: entry.useRegex
|
|
430
|
+
})),
|
|
431
|
+
extensions: { risu_fullWordMatching: false }
|
|
432
|
+
},
|
|
433
|
+
character_version: manifest.version ?? "",
|
|
434
|
+
creation_date: 0,
|
|
435
|
+
creator: manifest.creator ?? "",
|
|
436
|
+
creator_notes: manifest.description ?? "",
|
|
437
|
+
description: "",
|
|
438
|
+
extensions: {
|
|
439
|
+
moduleNoneImage: manifest.icon ? void 0 : true,
|
|
440
|
+
risuai: {
|
|
441
|
+
additionalText: "",
|
|
442
|
+
backgroundHTML: manifest.CSS ? readSource(path.dirname(manifest.__path), manifest.CSS, "CSS") : "",
|
|
443
|
+
bias: [],
|
|
444
|
+
defaultVariables: "",
|
|
445
|
+
hideChatIcon: manifest.hideIcon ?? false,
|
|
446
|
+
inlayViewScreen: false,
|
|
447
|
+
largePortrait: false,
|
|
448
|
+
license: manifest.license ?? "",
|
|
449
|
+
lorePlus: false,
|
|
450
|
+
lowLevelAccess: manifest.lowLevelAccess ?? false,
|
|
451
|
+
moduleNamespace: manifest.namespace,
|
|
452
|
+
newGenData: void 0,
|
|
453
|
+
prebuiltAssetCommand: "",
|
|
454
|
+
prebuiltAssetExclude: [],
|
|
455
|
+
prebuiltAssetStyle: "",
|
|
456
|
+
sdData: [],
|
|
457
|
+
toggles: manifest.toggles ? readSource(path.dirname(manifest.__path), manifest.toggles, "toggles") : "",
|
|
458
|
+
utilityBot: false,
|
|
459
|
+
viewScreen: "none",
|
|
460
|
+
virtualscript: "",
|
|
461
|
+
vits: {}
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
first_mes: "",
|
|
465
|
+
group_only_greetings: [],
|
|
466
|
+
mes_example: "",
|
|
467
|
+
modification_date: process.env.SOURCE_DATE_EPOCH ? Number(process.env.SOURCE_DATE_EPOCH) : Math.floor(Date.now() / 1e3),
|
|
468
|
+
name: manifest.name,
|
|
469
|
+
nickname: "",
|
|
470
|
+
personality: "",
|
|
471
|
+
post_history_instructions: "",
|
|
472
|
+
scenario: "",
|
|
473
|
+
source: [],
|
|
474
|
+
system_prompt: "",
|
|
475
|
+
tags: manifest.tags ?? []
|
|
476
|
+
},
|
|
477
|
+
spec: "chara_card_v3",
|
|
478
|
+
spec_version: "3.0"
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
function getDOSDateTime(date) {
|
|
482
|
+
return {
|
|
483
|
+
date: Math.max(1980, date.getUTCFullYear()) - 1980 << 9 | date.getUTCMonth() + 1 << 5 | date.getUTCDate(),
|
|
484
|
+
time: date.getUTCHours() << 11 | date.getUTCMinutes() << 5 | Math.floor(date.getUTCSeconds() / 2)
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
function createZIP(files, timestamp) {
|
|
488
|
+
assert$2(files.length <= 65535, "ZIP contains too many files");
|
|
489
|
+
const centralRecords = [];
|
|
490
|
+
const localRecords = [];
|
|
491
|
+
let offset = 0;
|
|
492
|
+
const { date, time } = getDOSDateTime(timestamp);
|
|
493
|
+
for (const file of files) {
|
|
494
|
+
const data = Buffer.from(file.data);
|
|
495
|
+
const compressed = zlib.deflateRawSync(data, { level: 6 });
|
|
496
|
+
const fileName = Buffer.from(file.name.replace(/\\/g, "/"), "utf8");
|
|
497
|
+
const CRC32 = calculateCRC32$1(data);
|
|
498
|
+
assert$2(data.length <= 4294967295 && compressed.length <= 4294967295, `${file.name} exceeds ZIP32 limits`);
|
|
499
|
+
const localHeader = Buffer.alloc(30);
|
|
500
|
+
localHeader.writeUInt32LE(67324752, 0);
|
|
501
|
+
localHeader.writeUInt16LE(20, 4);
|
|
502
|
+
localHeader.writeUInt16LE(2048, 6);
|
|
503
|
+
localHeader.writeUInt16LE(8, 8);
|
|
504
|
+
localHeader.writeUInt16LE(time, 10);
|
|
505
|
+
localHeader.writeUInt16LE(date, 12);
|
|
506
|
+
localHeader.writeUInt32LE(CRC32, 14);
|
|
507
|
+
localHeader.writeUInt32LE(compressed.length, 18);
|
|
508
|
+
localHeader.writeUInt32LE(data.length, 22);
|
|
509
|
+
localHeader.writeUInt16LE(fileName.length, 26);
|
|
510
|
+
localHeader.writeUInt16LE(0, 28);
|
|
511
|
+
localRecords.push(localHeader, fileName, compressed);
|
|
512
|
+
const centralHeader = Buffer.alloc(46);
|
|
513
|
+
centralHeader.writeUInt32LE(33639248, 0);
|
|
514
|
+
centralHeader.writeUInt16LE(20, 4);
|
|
515
|
+
centralHeader.writeUInt16LE(20, 6);
|
|
516
|
+
centralHeader.writeUInt16LE(2048, 8);
|
|
517
|
+
centralHeader.writeUInt16LE(8, 10);
|
|
518
|
+
centralHeader.writeUInt16LE(time, 12);
|
|
519
|
+
centralHeader.writeUInt16LE(date, 14);
|
|
520
|
+
centralHeader.writeUInt32LE(CRC32, 16);
|
|
521
|
+
centralHeader.writeUInt32LE(compressed.length, 20);
|
|
522
|
+
centralHeader.writeUInt32LE(data.length, 24);
|
|
523
|
+
centralHeader.writeUInt16LE(fileName.length, 28);
|
|
524
|
+
centralHeader.writeUInt16LE(0, 30);
|
|
525
|
+
centralHeader.writeUInt16LE(0, 32);
|
|
526
|
+
centralHeader.writeUInt16LE(0, 34);
|
|
527
|
+
centralHeader.writeUInt16LE(0, 36);
|
|
528
|
+
centralHeader.writeUInt32LE(0, 38);
|
|
529
|
+
centralHeader.writeUInt32LE(offset, 42);
|
|
530
|
+
centralRecords.push(centralHeader, fileName);
|
|
531
|
+
offset += localHeader.length + fileName.length + compressed.length;
|
|
532
|
+
}
|
|
533
|
+
const centralDirectory = Buffer.concat(centralRecords);
|
|
534
|
+
const end = Buffer.alloc(22);
|
|
535
|
+
end.writeUInt32LE(101010256, 0);
|
|
536
|
+
end.writeUInt16LE(0, 4);
|
|
537
|
+
end.writeUInt16LE(0, 6);
|
|
538
|
+
end.writeUInt16LE(files.length, 8);
|
|
539
|
+
end.writeUInt16LE(files.length, 10);
|
|
540
|
+
end.writeUInt32LE(centralDirectory.length, 12);
|
|
541
|
+
end.writeUInt32LE(offset, 16);
|
|
542
|
+
end.writeUInt16LE(0, 20);
|
|
543
|
+
return Buffer.concat([
|
|
544
|
+
...localRecords,
|
|
545
|
+
centralDirectory,
|
|
546
|
+
end
|
|
547
|
+
]);
|
|
548
|
+
}
|
|
549
|
+
function buildCharX(manifestPath, outputArgument) {
|
|
550
|
+
const resolvedManifestPath = path.resolve(manifestPath);
|
|
551
|
+
const manifestDirectory = path.dirname(resolvedManifestPath);
|
|
552
|
+
const manifest = JSON.parse(fs.readFileSync(resolvedManifestPath, "utf8"));
|
|
553
|
+
manifest.__path = resolvedManifestPath;
|
|
554
|
+
assert$2(typeof manifest.name === "string" && manifest.name !== "", "Manifest name is required");
|
|
555
|
+
const lorebook = buildLorebook(manifest, manifestDirectory);
|
|
556
|
+
const regex = buildRegexScripts(manifestDirectory, manifest.regex);
|
|
557
|
+
const trigger = buildTriggers(manifest, manifestDirectory);
|
|
558
|
+
const { cardAssets, files: assetFiles } = createAssetFiles(manifest, manifestDirectory);
|
|
559
|
+
const card = createCard(manifest, lorebook, cardAssets);
|
|
560
|
+
const module = {
|
|
561
|
+
description: `Module for ${manifest.name}`,
|
|
562
|
+
id: createUUID(`${manifest.namespace ?? manifest.name}:module`),
|
|
563
|
+
lorebook,
|
|
564
|
+
name: `${manifest.name} Module`,
|
|
565
|
+
regex,
|
|
566
|
+
trigger
|
|
567
|
+
};
|
|
568
|
+
const map = fs.readFileSync(RPACK_MAP_PATH$2);
|
|
569
|
+
assert$2(map.length >= 256, `Invalid RPack map: ${RPACK_MAP_PATH$2}`);
|
|
570
|
+
const files = [
|
|
571
|
+
...assetFiles,
|
|
572
|
+
{
|
|
573
|
+
data: Buffer.from(JSON.stringify(sortKeysDeep$1(card), null, 2)),
|
|
574
|
+
name: "card.json"
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
data: createRisuM(sortKeysDeep$1(module), map.subarray(0, 256)),
|
|
578
|
+
name: "module.risum"
|
|
579
|
+
}
|
|
580
|
+
].sort((left, right) => left.name.localeCompare(right.name));
|
|
581
|
+
const outputPath = outputArgument ? path.resolve(outputArgument) : path.resolve(manifestDirectory, manifest.output ?? `../dist/${sanitizeArchiveName(manifest.name)}.charx`);
|
|
582
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
583
|
+
const timestamp = process.env.SOURCE_DATE_EPOCH ? /* @__PURE__ */ new Date(Number(process.env.SOURCE_DATE_EPOCH) * 1e3) : /* @__PURE__ */ new Date();
|
|
584
|
+
fs.writeFileSync(outputPath, createZIP(files, timestamp));
|
|
585
|
+
console.log(`Built ${path.relative(process.cwd(), outputPath)} (${files.length} files)`);
|
|
586
|
+
return outputPath;
|
|
587
|
+
}
|
|
588
|
+
//#endregion
|
|
589
|
+
//#region src/inspect-risusave.ts
|
|
590
|
+
var MAGIC = Buffer.from("RISUSAVE\0");
|
|
591
|
+
var MODULE_BLOCK_TYPE = 5;
|
|
592
|
+
function parseModuleBlock(database) {
|
|
593
|
+
if (!database.subarray(0, MAGIC.length).equals(MAGIC)) throw new Error("Invalid RISUSAVE header");
|
|
594
|
+
let offset = MAGIC.length;
|
|
595
|
+
while (offset < database.length) {
|
|
596
|
+
if (offset + 3 > database.length) throw new Error(`Truncated block header at byte ${offset}`);
|
|
597
|
+
const type = database[offset];
|
|
598
|
+
const compressed = database[offset + 1] === 1;
|
|
599
|
+
const nameLength = database[offset + 2];
|
|
600
|
+
offset += 3;
|
|
601
|
+
if (offset + nameLength + 4 > database.length) throw new Error(`Truncated block name at byte ${offset}`);
|
|
602
|
+
const name = database.subarray(offset, offset + nameLength).toString("utf8");
|
|
603
|
+
offset += nameLength;
|
|
604
|
+
const contentLength = database.readUInt32LE(offset);
|
|
605
|
+
offset += 4;
|
|
606
|
+
if (offset + contentLength > database.length) throw new Error(`Truncated ${name} block at byte ${offset}`);
|
|
607
|
+
let content = database.subarray(offset, offset + contentLength);
|
|
608
|
+
offset += contentLength;
|
|
609
|
+
if (type !== MODULE_BLOCK_TYPE) continue;
|
|
610
|
+
if (compressed) content = zlib.gunzipSync(content);
|
|
611
|
+
return JSON.parse(content.toString("utf8"));
|
|
612
|
+
}
|
|
613
|
+
throw new Error("RISUSAVE module block not found");
|
|
614
|
+
}
|
|
615
|
+
//#endregion
|
|
616
|
+
//#region src/unpack-charx.ts
|
|
617
|
+
var RPACK_MAP_PATH$1 = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "vendor", "rpack", "rpack_map.bin");
|
|
618
|
+
var CRC32_TABLE = (() => {
|
|
619
|
+
const table = /* @__PURE__ */ new Uint32Array(256);
|
|
620
|
+
for (let index = 0; index < table.length; index += 1) {
|
|
621
|
+
let value = index;
|
|
622
|
+
for (let bit = 0; bit < 8; bit += 1) if ((value & 1) !== 0) value = 3988292384 ^ value >>> 1;
|
|
623
|
+
else value >>>= 1;
|
|
624
|
+
table[index] = value >>> 0;
|
|
625
|
+
}
|
|
626
|
+
return table;
|
|
627
|
+
})();
|
|
628
|
+
function assert$1(condition, message) {
|
|
629
|
+
if (!condition) throw new Error(message);
|
|
630
|
+
}
|
|
631
|
+
function calculateCRC32(data) {
|
|
632
|
+
let value = 4294967295;
|
|
633
|
+
for (const byte of data) value = CRC32_TABLE[(value ^ byte) & 255] ^ value >>> 8;
|
|
634
|
+
return (value ^ 4294967295) >>> 0;
|
|
635
|
+
}
|
|
636
|
+
function decodeModule(data, map) {
|
|
637
|
+
assert$1(data.length >= 7, "module.risum is truncated");
|
|
638
|
+
assert$1(data.readUInt8(0) === 111, "Invalid module.risum magic number");
|
|
639
|
+
assert$1(data.readUInt8(1) === 0, `Unsupported module.risum version: ${data.readUInt8(1)}`);
|
|
640
|
+
const encodedLength = data.readUInt32LE(2);
|
|
641
|
+
assert$1(encodedLength <= data.length - 6, "module.risum payload is truncated");
|
|
642
|
+
assert$1(map.length >= 256, `Invalid RPack map: ${RPACK_MAP_PATH$1}`);
|
|
643
|
+
let decodeMap;
|
|
644
|
+
if (map.length >= 512) decodeMap = map.subarray(256, 512);
|
|
645
|
+
else {
|
|
646
|
+
decodeMap = Buffer.alloc(256);
|
|
647
|
+
const seen = /* @__PURE__ */ new Set();
|
|
648
|
+
for (let index = 0; index < 256; index += 1) {
|
|
649
|
+
const encoded = map[index];
|
|
650
|
+
assert$1(!seen.has(encoded), "RPack encode map is not a permutation");
|
|
651
|
+
seen.add(encoded);
|
|
652
|
+
decodeMap[encoded] = index;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
const encoded = data.subarray(6, 6 + encodedLength);
|
|
656
|
+
const decoded = Buffer.allocUnsafe(encoded.length);
|
|
657
|
+
for (let index = 0; index < encoded.length; index += 1) decoded[index] = decodeMap[encoded[index]];
|
|
658
|
+
const parsed = JSON.parse(decoded.toString("utf8"));
|
|
659
|
+
assert$1(parsed && parsed.type === "risuModule" && parsed.module, "module.risum does not contain a Risu module");
|
|
660
|
+
return parsed;
|
|
661
|
+
}
|
|
662
|
+
function findEndOfCentralDirectory(archive) {
|
|
663
|
+
const minimumOffset = Math.max(0, archive.length - 65535 - 22);
|
|
664
|
+
for (let offset = archive.length - 22; offset >= minimumOffset; offset -= 1) if (archive.readUInt32LE(offset) === 101010256) {
|
|
665
|
+
const commentLength = archive.readUInt16LE(offset + 20);
|
|
666
|
+
if (offset + 22 + commentLength === archive.length) return offset;
|
|
667
|
+
}
|
|
668
|
+
throw new Error("ZIP end record not found");
|
|
669
|
+
}
|
|
670
|
+
function normalizeEntryName(name) {
|
|
671
|
+
assert$1(name !== "", "ZIP entry has an empty name");
|
|
672
|
+
assert$1(!name.includes("\0"), `ZIP entry contains a null byte: ${JSON.stringify(name)}`);
|
|
673
|
+
const normalized = name.replace(/\\/g, "/");
|
|
674
|
+
assert$1(!normalized.startsWith("/"), `ZIP entry uses an absolute path: ${name}`);
|
|
675
|
+
assert$1(!/^[a-zA-Z]:/.test(normalized), `ZIP entry uses an absolute path: ${name}`);
|
|
676
|
+
const parts = normalized.split("/");
|
|
677
|
+
assert$1(!parts.includes(".."), `ZIP entry escapes the output directory: ${name}`);
|
|
678
|
+
return parts.filter((part) => part !== "" && part !== ".").join("/");
|
|
679
|
+
}
|
|
680
|
+
function sanitizeSourceName(name, fallback) {
|
|
681
|
+
return Array.from(name, (character) => character.charCodeAt(0) < 32 ? "_" : character).join("").replace(/[<>:"/\\|?*]/g, "_").replace(/[. ]+$/g, "").trim() || fallback;
|
|
682
|
+
}
|
|
683
|
+
function sortKeysDeep(value) {
|
|
684
|
+
if (Array.isArray(value)) return value.map(sortKeysDeep);
|
|
685
|
+
if (value && typeof value === "object" && !Buffer.isBuffer(value)) {
|
|
686
|
+
const sorted = {};
|
|
687
|
+
for (const key of Object.keys(value).sort()) if (value[key] !== void 0) sorted[key] = sortKeysDeep(value[key]);
|
|
688
|
+
return sorted;
|
|
689
|
+
}
|
|
690
|
+
return value;
|
|
691
|
+
}
|
|
692
|
+
function takeSourcePath(directory, label, extension, takenPaths) {
|
|
693
|
+
const baseName = sanitizeSourceName(label, "untitled");
|
|
694
|
+
const suffix = baseName.toLowerCase().endsWith(extension) ? "" : extension;
|
|
695
|
+
let candidate = `${directory}/${baseName}${suffix}`;
|
|
696
|
+
let collision = 1;
|
|
697
|
+
while (takenPaths.has(candidate)) {
|
|
698
|
+
collision += 1;
|
|
699
|
+
candidate = `${directory}/${baseName}_${collision}${suffix}`;
|
|
700
|
+
}
|
|
701
|
+
takenPaths.add(candidate);
|
|
702
|
+
return candidate;
|
|
703
|
+
}
|
|
704
|
+
function createRegexDocument(regex) {
|
|
705
|
+
const frontmatter = [`ableFlag: ${regex.ableFlag ?? true}`, `comment: ${regex.comment ?? ""}`];
|
|
706
|
+
if (regex.flag !== void 0) frontmatter.push(`flag: ${regex.flag}`);
|
|
707
|
+
frontmatter.push(`type: ${regex.type ?? "editdisplay"}`);
|
|
708
|
+
return `---\n${frontmatter.join("\n")}\n---\n\nIN:\n${regex.in ?? ""}\nOUT:\n${regex.out ?? ""}\n`;
|
|
709
|
+
}
|
|
710
|
+
function createExpandedModuleSources(card, decodedModule) {
|
|
711
|
+
const moduleData = decodedModule.module;
|
|
712
|
+
const files = /* @__PURE__ */ new Map();
|
|
713
|
+
const folderNames = /* @__PURE__ */ new Map();
|
|
714
|
+
const folders = [];
|
|
715
|
+
const takenPaths = /* @__PURE__ */ new Set();
|
|
716
|
+
for (const entry of moduleData.lorebook ?? []) {
|
|
717
|
+
if (entry.mode !== "folder") continue;
|
|
718
|
+
folderNames.set(entry.key, entry.comment);
|
|
719
|
+
folders.push(entry.comment);
|
|
720
|
+
}
|
|
721
|
+
const lorebook = [];
|
|
722
|
+
for (const entry of moduleData.lorebook ?? []) {
|
|
723
|
+
if (entry.mode === "folder") continue;
|
|
724
|
+
const file = takeSourcePath("lorebooks", entry.comment, ".md", takenPaths);
|
|
725
|
+
files.set(file, Buffer.from(entry.content ?? "", "utf8"));
|
|
726
|
+
lorebook.push({
|
|
727
|
+
activationPercent: entry.activationPercent,
|
|
728
|
+
alwaysActive: entry.alwaysActive ?? false,
|
|
729
|
+
bookVersion: entry.bookVersion ?? 2,
|
|
730
|
+
comment: entry.comment ?? "",
|
|
731
|
+
extentions: entry.extentions,
|
|
732
|
+
file,
|
|
733
|
+
folder: folderNames.get(entry.folder),
|
|
734
|
+
insertOrder: entry.insertorder ?? 100,
|
|
735
|
+
key: entry.key ?? "",
|
|
736
|
+
mode: entry.mode ?? "normal",
|
|
737
|
+
secondaryKey: entry.secondkey ?? "",
|
|
738
|
+
selective: entry.selective ?? false,
|
|
739
|
+
useRegex: entry.useRegex ?? false
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
const regex = [];
|
|
743
|
+
for (const entry of moduleData.regex ?? []) {
|
|
744
|
+
const file = takeSourcePath("regex", entry.comment, ".md", takenPaths);
|
|
745
|
+
files.set(file, Buffer.from(createRegexDocument(entry), "utf8"));
|
|
746
|
+
regex.push(file);
|
|
747
|
+
}
|
|
748
|
+
const triggers = [];
|
|
749
|
+
for (let index = 0; index < (moduleData.trigger ?? []).length; index += 1) {
|
|
750
|
+
const trigger = moduleData.trigger[index];
|
|
751
|
+
const luaEffect = trigger.effect?.length === 1 && trigger.effect[0].type === "triggerlua" ? trigger.effect[0] : void 0;
|
|
752
|
+
if (!luaEffect || typeof luaEffect.code !== "string") {
|
|
753
|
+
triggers.push(trigger);
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
const luaPath = takeSourcePath("triggers", trigger.comment || `trigger-${index + 1}`, ".lua", takenPaths);
|
|
757
|
+
files.set(luaPath, Buffer.from(luaEffect.code, "utf8"));
|
|
758
|
+
triggers.push({
|
|
759
|
+
bundle: false,
|
|
760
|
+
comment: trigger.comment ?? "",
|
|
761
|
+
conditions: trigger.conditions ?? [],
|
|
762
|
+
lowLevelAccess: trigger.lowLevelAccess,
|
|
763
|
+
lua: luaPath,
|
|
764
|
+
type: trigger.type ?? "start"
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
const risuai = card.data?.extensions?.risuai ?? {};
|
|
768
|
+
const manifest = {
|
|
769
|
+
card: "card.json",
|
|
770
|
+
creator: card.data?.creator,
|
|
771
|
+
description: card.data?.creator_notes ?? moduleData.description ?? "",
|
|
772
|
+
folders,
|
|
773
|
+
hideIcon: risuai.hideChatIcon ?? moduleData.hideIcon ?? false,
|
|
774
|
+
lorebook,
|
|
775
|
+
lowLevelAccess: risuai.lowLevelAccess ?? moduleData.lowLevelAccess ?? false,
|
|
776
|
+
name: card.data?.name ?? moduleData.name,
|
|
777
|
+
namespace: risuai.moduleNamespace ?? moduleData.namespace,
|
|
778
|
+
regex,
|
|
779
|
+
tags: card.data?.tags,
|
|
780
|
+
triggers,
|
|
781
|
+
version: card.data?.character_version ?? ""
|
|
782
|
+
};
|
|
783
|
+
const CSS = risuai.backgroundHTML ?? moduleData.backgroundEmbedding;
|
|
784
|
+
if (CSS) {
|
|
785
|
+
manifest.CSS = "style.html";
|
|
786
|
+
files.set("style.html", Buffer.from(CSS, "utf8"));
|
|
787
|
+
}
|
|
788
|
+
const toggles = risuai.toggles ?? moduleData.customModuleToggle;
|
|
789
|
+
if (toggles) {
|
|
790
|
+
manifest.toggles = "toggles.txt";
|
|
791
|
+
files.set("toggles.txt", Buffer.from(toggles, "utf8"));
|
|
792
|
+
}
|
|
793
|
+
const mainIcon = card.data?.assets?.find((asset) => asset.type === "icon" && asset.name === "main");
|
|
794
|
+
if (mainIcon?.uri?.startsWith("embeded://")) manifest.icon = mainIcon.uri.slice(10);
|
|
795
|
+
const assets = [];
|
|
796
|
+
for (const asset of card.data?.assets ?? []) {
|
|
797
|
+
if (asset === mainIcon || !asset.uri?.startsWith("embeded://")) continue;
|
|
798
|
+
assets.push({
|
|
799
|
+
extension: asset.ext,
|
|
800
|
+
file: asset.uri.slice(10),
|
|
801
|
+
name: asset.name
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
if (assets.length > 0) manifest.assets = assets;
|
|
805
|
+
files.set("charx.json", Buffer.from(`${JSON.stringify(sortKeysDeep(manifest), null, 2)}\n`, "utf8"));
|
|
806
|
+
return files;
|
|
807
|
+
}
|
|
808
|
+
function parseZIP(archive) {
|
|
809
|
+
const endOffset = findEndOfCentralDirectory(archive);
|
|
810
|
+
const diskNumber = archive.readUInt16LE(endOffset + 4);
|
|
811
|
+
const centralDisk = archive.readUInt16LE(endOffset + 6);
|
|
812
|
+
const diskEntries = archive.readUInt16LE(endOffset + 8);
|
|
813
|
+
const totalEntries = archive.readUInt16LE(endOffset + 10);
|
|
814
|
+
const centralSize = archive.readUInt32LE(endOffset + 12);
|
|
815
|
+
const centralOffset = archive.readUInt32LE(endOffset + 16);
|
|
816
|
+
assert$1(diskNumber === 0 && centralDisk === 0, "Multi-disk ZIP archives are not supported");
|
|
817
|
+
assert$1(diskEntries === totalEntries, "Inconsistent ZIP entry count");
|
|
818
|
+
assert$1(totalEntries !== 65535 && centralSize !== 4294967295 && centralOffset !== 4294967295, "ZIP64 is not supported");
|
|
819
|
+
const zipOffset = endOffset - centralSize - centralOffset;
|
|
820
|
+
assert$1(zipOffset >= 0, "Invalid ZIP central directory offset");
|
|
821
|
+
const entries = [];
|
|
822
|
+
const names = /* @__PURE__ */ new Set();
|
|
823
|
+
let offset = zipOffset + centralOffset;
|
|
824
|
+
for (let index = 0; index < totalEntries; index += 1) {
|
|
825
|
+
assert$1(offset + 46 <= archive.length, "ZIP central directory is truncated");
|
|
826
|
+
assert$1(archive.readUInt32LE(offset) === 33639248, `Invalid ZIP central record at byte ${offset}`);
|
|
827
|
+
const flags = archive.readUInt16LE(offset + 8);
|
|
828
|
+
const method = archive.readUInt16LE(offset + 10);
|
|
829
|
+
const expectedCRC32 = archive.readUInt32LE(offset + 16);
|
|
830
|
+
const compressedSize = archive.readUInt32LE(offset + 20);
|
|
831
|
+
const uncompressedSize = archive.readUInt32LE(offset + 24);
|
|
832
|
+
const nameLength = archive.readUInt16LE(offset + 28);
|
|
833
|
+
const extraLength = archive.readUInt16LE(offset + 30);
|
|
834
|
+
const commentLength = archive.readUInt16LE(offset + 32);
|
|
835
|
+
const diskStart = archive.readUInt16LE(offset + 34);
|
|
836
|
+
const localOffset = archive.readUInt32LE(offset + 42);
|
|
837
|
+
const recordLength = 46 + nameLength + extraLength + commentLength;
|
|
838
|
+
assert$1(offset + recordLength <= archive.length, "ZIP central record is truncated");
|
|
839
|
+
assert$1((flags & 1) === 0, "Encrypted ZIP entries are not supported");
|
|
840
|
+
assert$1(method === 0 || method === 8, `Unsupported ZIP compression method: ${method}`);
|
|
841
|
+
assert$1(diskStart === 0, "Multi-disk ZIP entries are not supported");
|
|
842
|
+
assert$1(compressedSize !== 4294967295 && uncompressedSize !== 4294967295 && localOffset !== 4294967295, "ZIP64 entries are not supported");
|
|
843
|
+
const decodedName = archive.subarray(offset + 46, offset + 46 + nameLength).toString("utf8");
|
|
844
|
+
const directory = decodedName.endsWith("/") || decodedName.endsWith("\\");
|
|
845
|
+
const name = normalizeEntryName(decodedName);
|
|
846
|
+
assert$1(name !== "", "ZIP entry resolves to an empty path");
|
|
847
|
+
assert$1(!names.has(name), `Duplicate ZIP entry: ${name}`);
|
|
848
|
+
names.add(name);
|
|
849
|
+
entries.push({
|
|
850
|
+
compressedSize,
|
|
851
|
+
directory,
|
|
852
|
+
expectedCRC32,
|
|
853
|
+
localOffset: zipOffset + localOffset,
|
|
854
|
+
method,
|
|
855
|
+
name,
|
|
856
|
+
uncompressedSize
|
|
857
|
+
});
|
|
858
|
+
offset += recordLength;
|
|
859
|
+
}
|
|
860
|
+
assert$1(offset === endOffset, "ZIP central directory size does not match its records");
|
|
861
|
+
return entries;
|
|
862
|
+
}
|
|
863
|
+
function readEntry(archive, entry) {
|
|
864
|
+
const offset = entry.localOffset;
|
|
865
|
+
assert$1(offset + 30 <= archive.length, `ZIP local record is truncated: ${entry.name}`);
|
|
866
|
+
assert$1(archive.readUInt32LE(offset) === 67324752, `Invalid ZIP local record: ${entry.name}`);
|
|
867
|
+
const nameLength = archive.readUInt16LE(offset + 26);
|
|
868
|
+
const extraLength = archive.readUInt16LE(offset + 28);
|
|
869
|
+
const dataOffset = offset + 30 + nameLength + extraLength;
|
|
870
|
+
const dataEnd = dataOffset + entry.compressedSize;
|
|
871
|
+
assert$1(dataEnd <= archive.length, `ZIP entry data is truncated: ${entry.name}`);
|
|
872
|
+
const compressed = archive.subarray(dataOffset, dataEnd);
|
|
873
|
+
const data = entry.method === 0 ? Buffer.from(compressed) : zlib.inflateRawSync(compressed);
|
|
874
|
+
assert$1(data.length === entry.uncompressedSize, `ZIP entry size mismatch: ${entry.name}`);
|
|
875
|
+
assert$1(calculateCRC32(data) === entry.expectedCRC32, `ZIP entry checksum mismatch: ${entry.name}`);
|
|
876
|
+
assert$1(!entry.directory || data.length === 0, `ZIP directory entry contains data: ${entry.name}`);
|
|
877
|
+
return data;
|
|
878
|
+
}
|
|
879
|
+
function assertEmptyOutputDirectory$1(outputDirectory) {
|
|
880
|
+
if (!fs.existsSync(outputDirectory)) return;
|
|
881
|
+
assert$1(fs.statSync(outputDirectory).isDirectory(), `Output path is not a directory: ${outputDirectory}`);
|
|
882
|
+
assert$1(fs.readdirSync(outputDirectory).length === 0, `Output directory is not empty: ${outputDirectory}`);
|
|
883
|
+
}
|
|
884
|
+
function unpackCharX(inputPath, outputPath) {
|
|
885
|
+
const resolvedInputPath = path.resolve(inputPath);
|
|
886
|
+
const resolvedOutputPath = path.resolve(outputPath);
|
|
887
|
+
const archive = fs.readFileSync(resolvedInputPath);
|
|
888
|
+
const entries = parseZIP(archive);
|
|
889
|
+
const extracted = /* @__PURE__ */ new Map();
|
|
890
|
+
for (const entry of entries) extracted.set(entry.name, {
|
|
891
|
+
data: readEntry(archive, entry),
|
|
892
|
+
directory: entry.directory
|
|
893
|
+
});
|
|
894
|
+
const cardEntry = extracted.get("card.json");
|
|
895
|
+
assert$1(cardEntry, "CharX archive does not contain card.json");
|
|
896
|
+
assert$1(!cardEntry.directory, "card.json is a directory");
|
|
897
|
+
const card = JSON.parse(cardEntry.data.toString("utf8"));
|
|
898
|
+
let decodedModule;
|
|
899
|
+
let decodedModuleName;
|
|
900
|
+
if (extracted.has("module.risum")) {
|
|
901
|
+
const moduleEntry = extracted.get("module.risum");
|
|
902
|
+
assert$1(moduleEntry, "CharX archive does not contain module.risum");
|
|
903
|
+
assert$1(!moduleEntry.directory, "module.risum is a directory");
|
|
904
|
+
const map = fs.readFileSync(RPACK_MAP_PATH$1);
|
|
905
|
+
decodedModule = decodeModule(moduleEntry.data, map);
|
|
906
|
+
decodedModuleName = extracted.has("module.json") ? "module.decoded.json" : "module.json";
|
|
907
|
+
}
|
|
908
|
+
const expandedSources = decodedModule ? createExpandedModuleSources(card, decodedModule) : /* @__PURE__ */ new Map();
|
|
909
|
+
for (const name of expandedSources.keys()) assert$1(!extracted.has(name), `Generated module source conflicts with an archive entry: ${name}`);
|
|
910
|
+
assertEmptyOutputDirectory$1(resolvedOutputPath);
|
|
911
|
+
fs.mkdirSync(resolvedOutputPath, { recursive: true });
|
|
912
|
+
for (const [name, entry] of extracted) {
|
|
913
|
+
if (name === "module.risum") continue;
|
|
914
|
+
const destination = path.join(resolvedOutputPath, ...name.split("/"));
|
|
915
|
+
if (entry.directory) {
|
|
916
|
+
fs.mkdirSync(destination, { recursive: true });
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
920
|
+
fs.writeFileSync(destination, entry.data);
|
|
921
|
+
}
|
|
922
|
+
if (decodedModule) fs.writeFileSync(path.join(resolvedOutputPath, decodedModuleName), `${JSON.stringify(decodedModule, null, 2)}\n`);
|
|
923
|
+
for (const [name, data] of expandedSources) {
|
|
924
|
+
const destination = path.join(resolvedOutputPath, ...name.split("/"));
|
|
925
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
926
|
+
fs.writeFileSync(destination, data);
|
|
927
|
+
}
|
|
928
|
+
const relativeOutput = path.relative(process.cwd(), resolvedOutputPath) || ".";
|
|
929
|
+
const moduleNote = decodedModule ? ` and decoded ${decodedModuleName}` : "";
|
|
930
|
+
console.log(`Unpacked ${entries.length - (decodedModule ? 1 : 0)} archive files to ${relativeOutput}${moduleNote}`);
|
|
931
|
+
return resolvedOutputPath;
|
|
932
|
+
}
|
|
933
|
+
//#endregion
|
|
934
|
+
//#region src/unpack-risum.ts
|
|
935
|
+
var RPACK_MAP_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "vendor", "rpack", "rpack_map.bin");
|
|
936
|
+
function assert(condition, message) {
|
|
937
|
+
if (!condition) throw new Error(message);
|
|
938
|
+
}
|
|
939
|
+
function assertEmptyOutputDirectory(outputDirectory) {
|
|
940
|
+
if (!fs.existsSync(outputDirectory)) return;
|
|
941
|
+
assert(fs.statSync(outputDirectory).isDirectory(), `Output path is not a directory: ${outputDirectory}`);
|
|
942
|
+
assert(fs.readdirSync(outputDirectory).length === 0, `Output directory is not empty: ${outputDirectory}`);
|
|
943
|
+
}
|
|
944
|
+
function decodeRPack(data, decodeMap) {
|
|
945
|
+
for (let index = 0; index < data.length; index += 1) data[index] = decodeMap[data[index]];
|
|
946
|
+
return data;
|
|
947
|
+
}
|
|
948
|
+
function readExactly(fileDescriptor, length, position, label) {
|
|
949
|
+
const data = Buffer.allocUnsafe(length);
|
|
950
|
+
let read = 0;
|
|
951
|
+
while (read < length) {
|
|
952
|
+
const count = fs.readSync(fileDescriptor, data, read, length - read, position + read);
|
|
953
|
+
assert(count > 0, `${label} is truncated at byte ${position + read}`);
|
|
954
|
+
read += count;
|
|
955
|
+
}
|
|
956
|
+
return data;
|
|
957
|
+
}
|
|
958
|
+
function sanitizeAssetName(name, fallback) {
|
|
959
|
+
return Array.from(name, (character) => character.charCodeAt(0) < 32 ? "_" : character).join("").replace(/[<>:"/\\|?*]/g, "_").replace(/[. ]+$/g, "").trim().slice(0, 180) || fallback;
|
|
960
|
+
}
|
|
961
|
+
function detectAssetExtension(data, fallback) {
|
|
962
|
+
if (data.subarray(0, 8).equals(Buffer.from("89504e470d0a1a0a", "hex"))) return "png";
|
|
963
|
+
if (data.subarray(0, 3).equals(Buffer.from("ffd8ff", "hex"))) return "jpg";
|
|
964
|
+
if (data.subarray(0, 6).toString("ascii") === "GIF87a" || data.subarray(0, 6).toString("ascii") === "GIF89a") return "gif";
|
|
965
|
+
if (data.subarray(0, 4).toString("ascii") === "RIFF" && data.subarray(8, 12).toString("ascii") === "WEBP") return "webp";
|
|
966
|
+
if (data.subarray(4, 12).toString("ascii").includes("ftypavif")) return "avif";
|
|
967
|
+
return /^[a-z0-9]+$/i.test(fallback ?? "") ? fallback.toLowerCase() : "bin";
|
|
968
|
+
}
|
|
969
|
+
function takeAssetPath(asset, data, index, takenPaths) {
|
|
970
|
+
const extension = detectAssetExtension(data, asset[2]);
|
|
971
|
+
const baseName = sanitizeAssetName(asset[0] ?? "", `asset_${index + 1}`);
|
|
972
|
+
let candidate = `assets/${baseName}.${extension}`;
|
|
973
|
+
let collision = 1;
|
|
974
|
+
while (takenPaths.has(candidate)) {
|
|
975
|
+
collision += 1;
|
|
976
|
+
candidate = `assets/${baseName}_${collision}.${extension}`;
|
|
977
|
+
}
|
|
978
|
+
takenPaths.add(candidate);
|
|
979
|
+
return candidate;
|
|
980
|
+
}
|
|
981
|
+
function writeSources(outputDirectory, decodedModule, assetSources) {
|
|
982
|
+
const sources = createExpandedModuleSources({ data: { name: decodedModule.module.name } }, decodedModule);
|
|
983
|
+
const manifestSource = sources.get("charx.json");
|
|
984
|
+
assert(manifestSource, "Expanded module sources do not contain charx.json");
|
|
985
|
+
const manifest = JSON.parse(manifestSource.toString("utf8"));
|
|
986
|
+
manifest.assets = assetSources;
|
|
987
|
+
sources.set("charx.json", Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, "utf8"));
|
|
988
|
+
sources.set("module.json", Buffer.from(`${JSON.stringify(decodedModule, null, 2)}\n`, "utf8"));
|
|
989
|
+
for (const [name, data] of sources) {
|
|
990
|
+
const destination = path.join(outputDirectory, ...name.split("/"));
|
|
991
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
992
|
+
fs.writeFileSync(destination, data);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
function unpackRisuM(inputPath, outputPath) {
|
|
996
|
+
const resolvedInputPath = path.resolve(inputPath);
|
|
997
|
+
const resolvedOutputPath = path.resolve(outputPath);
|
|
998
|
+
assertEmptyOutputDirectory(resolvedOutputPath);
|
|
999
|
+
const map = fs.readFileSync(RPACK_MAP_PATH);
|
|
1000
|
+
assert(map.length >= 512, `Invalid RPack map: ${RPACK_MAP_PATH}`);
|
|
1001
|
+
const decodeMap = map.subarray(256, 512);
|
|
1002
|
+
const fileDescriptor = fs.openSync(resolvedInputPath, "r");
|
|
1003
|
+
try {
|
|
1004
|
+
const fileSize = fs.fstatSync(fileDescriptor).size;
|
|
1005
|
+
const header = readExactly(fileDescriptor, 6, 0, "RISUM header");
|
|
1006
|
+
assert(header.readUInt8(0) === 111, "Invalid RISUM magic number");
|
|
1007
|
+
assert(header.readUInt8(1) === 0, `Unsupported RISUM version: ${header.readUInt8(1)}`);
|
|
1008
|
+
const payloadLength = header.readUInt32LE(2);
|
|
1009
|
+
const payload = readExactly(fileDescriptor, payloadLength, 6, "RISUM module payload");
|
|
1010
|
+
const decodedModule = JSON.parse(decodeRPack(payload, decodeMap).toString("utf8"));
|
|
1011
|
+
assert(decodedModule?.type === "risuModule" && decodedModule.module, "RISUM does not contain a Risu module");
|
|
1012
|
+
const assetMetadata = decodedModule.module.assets ?? [];
|
|
1013
|
+
const assetSources = [];
|
|
1014
|
+
const takenPaths = /* @__PURE__ */ new Set();
|
|
1015
|
+
let assetIndex = 0;
|
|
1016
|
+
let position = 6 + payloadLength;
|
|
1017
|
+
fs.mkdirSync(path.join(resolvedOutputPath, "assets"), { recursive: true });
|
|
1018
|
+
while (position < fileSize) {
|
|
1019
|
+
const marker = readExactly(fileDescriptor, 1, position, "RISUM asset marker").readUInt8(0);
|
|
1020
|
+
position += 1;
|
|
1021
|
+
if (marker === 0) break;
|
|
1022
|
+
assert(marker === 1, `Invalid RISUM asset marker at byte ${position - 1}: ${marker}`);
|
|
1023
|
+
assert(assetIndex < assetMetadata.length, "RISUM contains more asset blocks than asset metadata entries");
|
|
1024
|
+
const assetLength = readExactly(fileDescriptor, 4, position, "RISUM asset length").readUInt32LE(0);
|
|
1025
|
+
position += 4;
|
|
1026
|
+
const encodedAsset = readExactly(fileDescriptor, assetLength, position, `RISUM asset ${assetIndex + 1}`);
|
|
1027
|
+
position += assetLength;
|
|
1028
|
+
const metadata = assetMetadata[assetIndex];
|
|
1029
|
+
assert(metadata, `RISUM asset ${assetIndex + 1} has no metadata`);
|
|
1030
|
+
const decodedAsset = decodeRPack(encodedAsset, decodeMap);
|
|
1031
|
+
const sourcePath = takeAssetPath(metadata, decodedAsset, assetIndex, takenPaths);
|
|
1032
|
+
const destination = path.join(resolvedOutputPath, ...sourcePath.split("/"));
|
|
1033
|
+
fs.writeFileSync(destination, decodedAsset);
|
|
1034
|
+
assetSources.push({
|
|
1035
|
+
extension: path.extname(sourcePath).slice(1),
|
|
1036
|
+
file: sourcePath,
|
|
1037
|
+
name: metadata[0]
|
|
1038
|
+
});
|
|
1039
|
+
assetIndex += 1;
|
|
1040
|
+
if (assetIndex % 500 === 0) console.log(`Extracted ${assetIndex} / ${assetMetadata.length} assets`);
|
|
1041
|
+
}
|
|
1042
|
+
assert(assetIndex === assetMetadata.length, `RISUM contains ${assetIndex} asset blocks but declares ${assetMetadata.length}`);
|
|
1043
|
+
assert(position === fileSize, `RISUM contains ${fileSize - position} trailing bytes after the end marker`);
|
|
1044
|
+
writeSources(resolvedOutputPath, decodedModule, assetSources);
|
|
1045
|
+
const relativeOutput = path.relative(process.cwd(), resolvedOutputPath) || ".";
|
|
1046
|
+
console.log(`Unpacked ${assetIndex} assets and module sources to ${relativeOutput}`);
|
|
1047
|
+
return resolvedOutputPath;
|
|
1048
|
+
} finally {
|
|
1049
|
+
fs.closeSync(fileDescriptor);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
//#endregion
|
|
1053
|
+
export { unpackCharX as a, bundleLua as c, parseZIP as i, createExpandedModuleSources as n, parseModuleBlock as o, decodeModule as r, buildCharX as s, unpackRisuM as t };
|
|
1054
|
+
|
|
1055
|
+
//# sourceMappingURL=unpack-risum-8Iw-UEO2.js.map
|