s2cfgtojson 7.0.13 → 7.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,67 @@
1
+ export * from "./types.mjs";
2
+ export * from "./enums.mjs";
3
+ export type Internal = "__internal__" | "fork" | "removeNode" | "addNode" | "clone" | "entries" | "forEach" | "filter" | "map" | "toJson" | "toString";
4
+ export type InternalPlus = Internal | "fromString" | "fromJson";
5
+ export interface DefaultEntries {
6
+ bpatch?: boolean;
7
+ bskipref?: boolean;
8
+ isArray?: boolean;
9
+ isRoot?: boolean;
10
+ rawName?: string;
11
+ refkey?: string | number;
12
+ refurl?: string;
13
+ removenode?: boolean;
14
+ useAsterisk?: boolean;
15
+ }
16
+ export type GetStructType<In> = In extends Array<infer T> ? Struct & {
17
+ [K in `${number}`]: GetStructType<T>;
18
+ } : In extends Record<any, any> ? Struct & {
19
+ [key in keyof In]: key extends Internal ? Struct[key] : GetStructType<In[key]>;
20
+ } : In extends string ? In : In extends number ? number : In extends boolean ? boolean : In;
21
+ /**
22
+ * This file is part of the Stalker 2 Modding Tools project.
23
+ * This is a base class for all structs.
24
+ */
25
+ export declare class Struct implements Record<Internal, any> {
26
+ __internal__: Refs;
27
+ /**
28
+ * Creates a new struct instance.
29
+ */
30
+ constructor(parentOrRaw?: string | Struct | object);
31
+ fork(clone?: boolean): this;
32
+ removeNode(key: Exclude<keyof this, Symbol>): this;
33
+ addNode(value: any, key?: string | number): this;
34
+ clone(): this;
35
+ entries<K extends Exclude<keyof this, InternalPlus>, V extends (typeof this)[K]>(): [K, V][];
36
+ forEach<K extends Exclude<keyof this, InternalPlus>, V extends (typeof this)[K]>(callback: ([key, value]: [K, V], i: number, arr: [K, V][]) => void): void;
37
+ /**
38
+ * Filters the struct entries based on a callback function. Returns a copy.
39
+ * @param callback
40
+ */
41
+ filter<K extends Exclude<keyof this, InternalPlus>, V extends (typeof this)[K], S extends this>(callback: (value: [K, V], index: number, array: [K, V][]) => boolean): S;
42
+ /**
43
+ * Maps the struct entries based on a callback function. Returns a copy.
44
+ * @param callback
45
+ */
46
+ map<K extends Exclude<keyof this, InternalPlus>, V extends (typeof this)[K]>(callback: ([key, value]: [K, V], i: number, arr: [K, V][]) => V): this;
47
+ static fromJson<T>(obj: T, minified?: boolean): T extends object ? GetStructType<T> : T;
48
+ toJson<T extends object>(minify?: boolean): T;
49
+ toString(): string;
50
+ static fromString<IntendedType>(text: string): (IntendedType & Struct)[];
51
+ }
52
+ export declare class Refs implements DefaultEntries {
53
+ rawName?: string;
54
+ refurl?: string;
55
+ refkey?: string | number;
56
+ bskipref?: boolean;
57
+ bpatch?: boolean;
58
+ isArray?: boolean;
59
+ isRoot?: boolean;
60
+ useAsterisk?: boolean;
61
+ removenode?: boolean;
62
+ constructor(ref?: string | Refs);
63
+ toString(): string;
64
+ }
65
+ export declare function pad(text: string): string;
66
+ export declare function createDynamicClassInstance<T extends Struct = Struct>(rawName: string, index?: number): T;
67
+ export declare function parseKey(key: string, parent: Struct, index: number): string | number;
@@ -0,0 +1,481 @@
1
+ export * from "./types.mjs";
2
+ export * from "./enums.mjs";
3
+ const TAB = " ";
4
+ const WILDCARD = "_wildcard";
5
+ const KEYWORDS = [
6
+ "refurl", // a file path to override
7
+ "refkey", // SID to override
8
+ "bskipref", // ??? not sure
9
+ "bpatch", // allows patching only specific keys
10
+ ];
11
+ const REMOVE_NODE = "removenode";
12
+ const INTERNAL_PROPS = new Map([
13
+ ["__internal__", "_"],
14
+ ["fork", ""], // methods
15
+ ["removeNode", ""], // methods
16
+ ["addNode", ""], // methods
17
+ ["clone", ""], // methods
18
+ ["entries", ""], // methods
19
+ ["forEach", ""], // methods
20
+ ["filter", ""], // methods
21
+ ["map", ""], // methods
22
+ ["fromJson", ""], // methods
23
+ ["toJson", ""], // methods
24
+ ["toString", ""], // methods
25
+ ["fromString", ""], // methods
26
+ ]);
27
+ const INTERNAL_PROPS_INV = new Map(Array.from(INTERNAL_PROPS.entries()).map(([k, v]) => [v, k]));
28
+ const REF_INTERNAL_PROPS = new Map([
29
+ ["rawName", "w"],
30
+ ["refurl", "u"],
31
+ ["refkey", "k"],
32
+ ["bskipref", "s"],
33
+ ["bpatch", "p"],
34
+ ["isArray", "a"],
35
+ ["isRoot", "r"],
36
+ ["useAsterisk", "*"],
37
+ ]);
38
+ const REF_INTERNAL_PROPS_INV = new Map(Array.from(REF_INTERNAL_PROPS.entries()).map(([k, v]) => [v, k]));
39
+ /**
40
+ * This file is part of the Stalker 2 Modding Tools project.
41
+ * This is a base class for all structs.
42
+ */
43
+ export class Struct {
44
+ __internal__ = new Refs();
45
+ /**
46
+ * Creates a new struct instance.
47
+ */
48
+ constructor(parentOrRaw) {
49
+ if (parentOrRaw instanceof Struct) {
50
+ Object.assign(this, parentOrRaw.clone());
51
+ }
52
+ if (typeof parentOrRaw === "string") {
53
+ Object.assign(this, Struct.fromString(parentOrRaw)[0]);
54
+ }
55
+ if (typeof parentOrRaw === "object" && parentOrRaw !== null) {
56
+ Object.assign(this, Struct.fromJson(parentOrRaw));
57
+ }
58
+ }
59
+ fork(clone = false) {
60
+ const patch = clone
61
+ ? this.clone()
62
+ : createDynamicClassInstance(this.__internal__.rawName || this.constructor.name);
63
+ patch.__internal__.isRoot = this.__internal__.isRoot;
64
+ patch.__internal__.isArray = this.__internal__.isArray;
65
+ patch.__internal__.useAsterisk = this.__internal__.useAsterisk;
66
+ function markAsbPatch(s) {
67
+ s.__internal__.bpatch = true;
68
+ Object.values(s)
69
+ .filter((v) => v instanceof Struct)
70
+ .forEach(markAsbPatch);
71
+ }
72
+ markAsbPatch(patch);
73
+ return patch;
74
+ }
75
+ removeNode(key) {
76
+ if (this.__internal__.bpatch !== true) {
77
+ throw new Error("Cannot remove node from non-patch struct. Use fork() first.");
78
+ }
79
+ if (this[key] instanceof Struct) {
80
+ this[key].__internal__.removenode = true;
81
+ }
82
+ else {
83
+ console.warn(`Attempting to remove node on non-struct value. Old value: '${this.__internal__.rawName}.${key}' = ${this[key]}. Assigning 'empty' instead.`);
84
+ this[key] = "empty";
85
+ }
86
+ return this;
87
+ }
88
+ addNode(value, key) {
89
+ if (key === undefined) {
90
+ const nextIndex = Object.keys(this)
91
+ .map((k) => parseInt(k))
92
+ .filter((k) => !isNaN(k))
93
+ .sort((a, b) => a - b)
94
+ .pop();
95
+ this[nextIndex !== undefined ? nextIndex + 1 : 0] = value;
96
+ }
97
+ else {
98
+ if (value instanceof Struct) {
99
+ value.__internal__.rawName = String(key);
100
+ }
101
+ this[key] = value;
102
+ }
103
+ return this;
104
+ }
105
+ clone() {
106
+ const newInstance = new Struct();
107
+ newInstance.__internal__ = new Refs(this.__internal__);
108
+ this.forEach(([k, v]) => {
109
+ if (v instanceof Struct) {
110
+ newInstance[k] = v.clone();
111
+ }
112
+ else {
113
+ newInstance[k] = v;
114
+ }
115
+ });
116
+ return newInstance;
117
+ }
118
+ entries() {
119
+ return Object.entries(this).filter(([key]) => !INTERNAL_PROPS.has(key));
120
+ }
121
+ forEach(callback) {
122
+ this.entries().forEach(([key, value], i, arr) => callback([key, value], i, arr));
123
+ }
124
+ /**
125
+ * Filters the struct entries based on a callback function. Returns a copy.
126
+ * @param callback
127
+ */
128
+ filter(callback) {
129
+ const clone = this.clone();
130
+ clone.entries().forEach((entry, i, arr) => {
131
+ if (!callback(entry, i, arr)) {
132
+ delete clone[entry[0]];
133
+ }
134
+ });
135
+ return clone;
136
+ }
137
+ /**
138
+ * Maps the struct entries based on a callback function. Returns a copy.
139
+ * @param callback
140
+ */
141
+ map(callback) {
142
+ const clone = this.clone();
143
+ clone.entries().forEach(([key, value], i, arr) => {
144
+ clone[key] = callback([key, value], i, arr);
145
+ if (clone[key] === null || clone[key] === undefined) {
146
+ delete clone[key];
147
+ }
148
+ });
149
+ return clone;
150
+ }
151
+ static fromJson(obj, minified = false) {
152
+ if (typeof obj === "object" && !!obj) {
153
+ const instance = new Struct();
154
+ Object.entries(obj).forEach(([key, value]) => {
155
+ const nKey = fromMinifiedKey(key, minified);
156
+ if (nKey === "__internal__") {
157
+ instance[nKey] = new Refs(Object.fromEntries(Object.entries(value).map(([k, v]) => [
158
+ fromMinifiedKey(k, minified),
159
+ v,
160
+ ])));
161
+ }
162
+ else {
163
+ instance[nKey] = Struct.fromJson(value, minified);
164
+ }
165
+ });
166
+ return instance;
167
+ }
168
+ return obj;
169
+ }
170
+ toJson(minify = false) {
171
+ const obj = {};
172
+ Object.entries(this).forEach(([key, value]) => {
173
+ let nKey = maybeMinifyKey(key, minify);
174
+ if (value instanceof Struct) {
175
+ obj[nKey] = value.toJson(minify);
176
+ }
177
+ else if (value instanceof Refs) {
178
+ obj[nKey] = Object.fromEntries(Object.entries(value).map(([k, v]) => [maybeMinifyKey(k, minify), v]));
179
+ }
180
+ else {
181
+ obj[nKey] = value;
182
+ }
183
+ });
184
+ return obj;
185
+ }
186
+ toString() {
187
+ if (!(this.__internal__ instanceof Refs)) {
188
+ this.__internal__ = new Refs(this.__internal__);
189
+ }
190
+ let text = this.__internal__.isRoot
191
+ ? `${this.__internal__.rawName} : `
192
+ : "";
193
+ text += "struct.begin";
194
+ const refs = this.__internal__.toString();
195
+ if (refs) {
196
+ text += ` {${refs}}`;
197
+ }
198
+ text += "\n";
199
+ // Add all keys
200
+ text += this.entries()
201
+ .map(([key, value]) => {
202
+ const nameAlreadyRendered = value instanceof Struct && value.__internal__.isRoot;
203
+ const useAsterisk = this.__internal__.isArray && this.__internal__.useAsterisk;
204
+ let keyOrIndex = "";
205
+ let equalsOrColon = "";
206
+ let spaceOrNoSpace = "";
207
+ if (!nameAlreadyRendered) {
208
+ keyOrIndex = renderKeyName(key, useAsterisk) + " ";
209
+ equalsOrColon = value instanceof Struct ? ":" : "=";
210
+ spaceOrNoSpace = value === "" ? "" : " ";
211
+ }
212
+ const renderedValue = value instanceof Struct && value.__internal__.removenode
213
+ ? REMOVE_NODE
214
+ : value;
215
+ return pad(`${keyOrIndex}${equalsOrColon}${spaceOrNoSpace}${renderedValue}`);
216
+ })
217
+ .join("\n");
218
+ text += "\nstruct.end";
219
+ return text;
220
+ }
221
+ static fromString(text) {
222
+ return walk(text.trim().split("\n"));
223
+ }
224
+ }
225
+ export class Refs {
226
+ rawName;
227
+ refurl;
228
+ refkey;
229
+ bskipref;
230
+ bpatch;
231
+ isArray;
232
+ isRoot;
233
+ useAsterisk;
234
+ removenode;
235
+ constructor(ref) {
236
+ if (typeof ref === "string") {
237
+ ref
238
+ .split(";")
239
+ .map((ref) => ref.trim())
240
+ .filter(Boolean)
241
+ .reduce((acc, ref) => {
242
+ const [key, value] = ref.split("=");
243
+ if (KEYWORDS.includes(key.trim())) {
244
+ acc[key.trim()] = value ? value.trim() : true;
245
+ }
246
+ return acc;
247
+ }, this);
248
+ }
249
+ if (typeof ref === "object") {
250
+ Object.assign(this, ref);
251
+ }
252
+ }
253
+ toString() {
254
+ return KEYWORDS.map((k) => [k, this[k]])
255
+ .filter(([_, v]) => v !== "" && v !== undefined && v !== false)
256
+ .map(([k, v]) => {
257
+ if (v === true)
258
+ return k;
259
+ if (k === "refkey") {
260
+ return `${k}=${renderKeyName(`${v}`, this.useAsterisk)}`;
261
+ }
262
+ return `${k}=${v}`;
263
+ })
264
+ .join(";");
265
+ }
266
+ }
267
+ const structHeadRegex = new RegExp(`^\\s*((.*)\\s*:)?\\s*struct\\.begin\\s*({\\s*((${KEYWORDS.join("|")})\\s*(=.+)?)\\s*})?`);
268
+ function parseHead(line, index) {
269
+ const match = line.match(structHeadRegex);
270
+ if (!match) {
271
+ throw new Error(`Invalid struct head: ${line}`);
272
+ }
273
+ const dummy = createDynamicClassInstance(match[2]?.trim() || "", index);
274
+ if (match[3]) {
275
+ const parsed = new Refs(match[4]);
276
+ dummy.__internal__.refurl = parsed.refurl;
277
+ dummy.__internal__.refkey = parsed.refkey;
278
+ dummy.__internal__.bskipref = parsed.bskipref;
279
+ dummy.__internal__.bpatch = parsed.bpatch;
280
+ }
281
+ return dummy;
282
+ }
283
+ export function pad(text) {
284
+ return `${TAB}${text.replace(/\n+/g, `\n${TAB}`)}`;
285
+ }
286
+ function isNumber(ref) {
287
+ return Number.isInteger(parseInt(ref)) || typeof ref === "number";
288
+ }
289
+ export function createDynamicClassInstance(rawName, index) {
290
+ const parsedName = parseStructName(rawName) || `UnnamedStruct${index}`;
291
+ const name = makeSafeClassName(parsedName);
292
+ return new (new Function("parent", "Refs", `return class ${name} extends parent {
293
+ __internal__ = new Refs({ rawName: "${rawName.trim()}" });
294
+ }`)(Struct, Refs))();
295
+ }
296
+ function parseKeyValue(line, parent, index) {
297
+ const match = line.match(/^(.*?)(\s*:\s*|\s*=\s*)(.*)$/);
298
+ if (!match) {
299
+ throw new Error(`Invalid key-value pair: ${line}`);
300
+ }
301
+ const key = parseKey(match[1].trim(), parent, index);
302
+ parent[key] = parseValue(match[3].trim());
303
+ }
304
+ function walk(lines) {
305
+ const roots = [];
306
+ const stack = [];
307
+ let index = 0;
308
+ while (index < lines.length) {
309
+ const line = lines[index++].trim();
310
+ if (line.startsWith("#") || line.startsWith("//")) {
311
+ continue; // Skip comments
312
+ }
313
+ const current = stack[stack.length - 1];
314
+ if (line.includes("struct.begin")) {
315
+ const newStruct = parseHead(line, index);
316
+ if (current) {
317
+ const key = parseKey(renderStructName(newStruct.constructor.name), current, index);
318
+ current[key] = newStruct;
319
+ }
320
+ else {
321
+ newStruct.__internal__.isRoot = true;
322
+ roots.push(newStruct);
323
+ }
324
+ stack.push(newStruct);
325
+ }
326
+ else if (line.includes("struct.end")) {
327
+ stack.pop();
328
+ }
329
+ else if (line.includes("=") && current) {
330
+ parseKeyValue(line, current, index);
331
+ }
332
+ }
333
+ return roots;
334
+ }
335
+ export function parseKey(key, parent, index) {
336
+ let normKey = key;
337
+ if (key.startsWith("[") && key.endsWith("]")) {
338
+ parent.__internal__.isArray = true;
339
+ normKey = extractKeyFromBrackets(key);
340
+ if (normKey === "*") {
341
+ parent.__internal__.useAsterisk = true;
342
+ return Object.keys(parent).length - 1;
343
+ }
344
+ if (parent[normKey] !== undefined) {
345
+ return `${normKey}_dupe_${index}`;
346
+ }
347
+ return normKey;
348
+ }
349
+ if (parent[normKey] !== undefined) {
350
+ return `${normKey}_dupe_${index}`;
351
+ }
352
+ return normKey;
353
+ }
354
+ function parseValue(value) {
355
+ if (value === "true" || value === "false") {
356
+ return value === "true";
357
+ }
358
+ try {
359
+ // understand +- 0.1f / 1. / 0.f / .1 / .1f -> ((\d*)\.?(\d+)|(\d+)\.?(\d*))f?
360
+ const matches = value.match(/^(-?)(\d*)\.?(\d*)f?$/);
361
+ const minus = matches[1];
362
+ const first = matches[2];
363
+ const second = matches[3];
364
+ if (first || second) {
365
+ return parseFloat(`${minus ? "-" : ""}${first || 0}${second ? `.${second}` : ""}`);
366
+ }
367
+ }
368
+ catch (e) { }
369
+ return value;
370
+ }
371
+ function renderStructName(name) {
372
+ if (name === WILDCARD) {
373
+ return "[*]"; // Special case for wildcard structs
374
+ }
375
+ if (`${name}`.startsWith("_")) {
376
+ return renderStructName(name.slice(1)); // Special case for indexed structs
377
+ }
378
+ if (isNumber(name)) {
379
+ return `[${parseInt(name)}]`;
380
+ }
381
+ return name;
382
+ }
383
+ function renderKeyName(key, useAsterisk) {
384
+ if (`${key}`.startsWith("_")) {
385
+ return renderKeyName(key.slice(1), useAsterisk); // Special case for indexed structs
386
+ }
387
+ if (`${key}`.includes("*") || useAsterisk) {
388
+ return "[*]"; // Special case for wildcard structs
389
+ }
390
+ if (`${key}`.includes("_dupe_")) {
391
+ return renderKeyName(key.slice(0, key.indexOf("_dupe_")));
392
+ }
393
+ if (isNumber(key)) {
394
+ return `[${parseInt(key)}]`;
395
+ }
396
+ return key;
397
+ }
398
+ function extractKeyFromBrackets(key) {
399
+ if (/\[(.+)]/.test(key)) {
400
+ return key.match(/\[(.+)]/)[1];
401
+ }
402
+ return "";
403
+ }
404
+ function parseStructName(name) {
405
+ if (extractKeyFromBrackets(name) === "*") {
406
+ return WILDCARD; // Special case for wildcard structs
407
+ }
408
+ if (isNumber(extractKeyFromBrackets(name))) {
409
+ return `_${name.match(/\[(\d+)]/)[1]}`; // Special case for indexed structs
410
+ }
411
+ return name
412
+ .replace(/\W/g, "_")
413
+ .replace(/^\d+/, "_")
414
+ .replace(/_+/g, "_")
415
+ .replace(/^_+/, "");
416
+ }
417
+ const RESERVED_IDENTIFIERS = new Set([
418
+ "break",
419
+ "case",
420
+ "catch",
421
+ "class",
422
+ "const",
423
+ "continue",
424
+ "debugger",
425
+ "default",
426
+ "delete",
427
+ "do",
428
+ "else",
429
+ "export",
430
+ "extends",
431
+ "finally",
432
+ "for",
433
+ "function",
434
+ "if",
435
+ "import",
436
+ "in",
437
+ "instanceof",
438
+ "new",
439
+ "return",
440
+ "super",
441
+ "switch",
442
+ "this",
443
+ "throw",
444
+ "try",
445
+ "typeof",
446
+ "var",
447
+ "void",
448
+ "while",
449
+ "with",
450
+ "yield",
451
+ "enum",
452
+ "await",
453
+ "implements",
454
+ "package",
455
+ "protected",
456
+ "static",
457
+ "interface",
458
+ "private",
459
+ "public",
460
+ "let",
461
+ ]);
462
+ function makeSafeClassName(name) {
463
+ if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ||
464
+ RESERVED_IDENTIFIERS.has(name)) {
465
+ return `_${name}`;
466
+ }
467
+ return name;
468
+ }
469
+ function maybeMinifyKey(key, minify) {
470
+ return minify &&
471
+ (INTERNAL_PROPS.has(key) || REF_INTERNAL_PROPS.has(key))
472
+ ? INTERNAL_PROPS.get(key) || REF_INTERNAL_PROPS.get(key)
473
+ : key;
474
+ }
475
+ function fromMinifiedKey(key, minified) {
476
+ if (!minified)
477
+ return key;
478
+ return (INTERNAL_PROPS_INV.get(key) ||
479
+ REF_INTERNAL_PROPS_INV.get(key) ||
480
+ key);
481
+ }