xiv-strat-board 0.1.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 +166 -0
- package/dist/index.cjs +1153 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +171 -0
- package/dist/index.d.ts +171 -0
- package/dist/index.js +1144 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1144 @@
|
|
|
1
|
+
import pako from 'pako';
|
|
2
|
+
|
|
3
|
+
// src/constants.ts
|
|
4
|
+
var CIPHER_TABLE = new Uint8Array([
|
|
5
|
+
0,
|
|
6
|
+
0,
|
|
7
|
+
0,
|
|
8
|
+
0,
|
|
9
|
+
0,
|
|
10
|
+
0,
|
|
11
|
+
0,
|
|
12
|
+
0,
|
|
13
|
+
0,
|
|
14
|
+
0,
|
|
15
|
+
0,
|
|
16
|
+
0,
|
|
17
|
+
0,
|
|
18
|
+
0,
|
|
19
|
+
0,
|
|
20
|
+
0,
|
|
21
|
+
0,
|
|
22
|
+
0,
|
|
23
|
+
0,
|
|
24
|
+
0,
|
|
25
|
+
0,
|
|
26
|
+
0,
|
|
27
|
+
0,
|
|
28
|
+
0,
|
|
29
|
+
0,
|
|
30
|
+
0,
|
|
31
|
+
0,
|
|
32
|
+
0,
|
|
33
|
+
0,
|
|
34
|
+
0,
|
|
35
|
+
0,
|
|
36
|
+
0,
|
|
37
|
+
0,
|
|
38
|
+
0,
|
|
39
|
+
0,
|
|
40
|
+
0,
|
|
41
|
+
0,
|
|
42
|
+
0,
|
|
43
|
+
0,
|
|
44
|
+
0,
|
|
45
|
+
0,
|
|
46
|
+
0,
|
|
47
|
+
0,
|
|
48
|
+
78,
|
|
49
|
+
0,
|
|
50
|
+
80,
|
|
51
|
+
0,
|
|
52
|
+
0,
|
|
53
|
+
120,
|
|
54
|
+
103,
|
|
55
|
+
48,
|
|
56
|
+
75,
|
|
57
|
+
56,
|
|
58
|
+
83,
|
|
59
|
+
74,
|
|
60
|
+
50,
|
|
61
|
+
115,
|
|
62
|
+
90,
|
|
63
|
+
0,
|
|
64
|
+
0,
|
|
65
|
+
0,
|
|
66
|
+
0,
|
|
67
|
+
0,
|
|
68
|
+
0,
|
|
69
|
+
0,
|
|
70
|
+
68,
|
|
71
|
+
70,
|
|
72
|
+
116,
|
|
73
|
+
84,
|
|
74
|
+
54,
|
|
75
|
+
69,
|
|
76
|
+
97,
|
|
77
|
+
86,
|
|
78
|
+
99,
|
|
79
|
+
112,
|
|
80
|
+
76,
|
|
81
|
+
77,
|
|
82
|
+
109,
|
|
83
|
+
101,
|
|
84
|
+
106,
|
|
85
|
+
57,
|
|
86
|
+
88,
|
|
87
|
+
66,
|
|
88
|
+
52,
|
|
89
|
+
82,
|
|
90
|
+
89,
|
|
91
|
+
55,
|
|
92
|
+
95,
|
|
93
|
+
110,
|
|
94
|
+
79,
|
|
95
|
+
98,
|
|
96
|
+
0,
|
|
97
|
+
0,
|
|
98
|
+
0,
|
|
99
|
+
0,
|
|
100
|
+
0,
|
|
101
|
+
0,
|
|
102
|
+
105,
|
|
103
|
+
45,
|
|
104
|
+
118,
|
|
105
|
+
72,
|
|
106
|
+
67,
|
|
107
|
+
65,
|
|
108
|
+
114,
|
|
109
|
+
87,
|
|
110
|
+
111,
|
|
111
|
+
100,
|
|
112
|
+
73,
|
|
113
|
+
113,
|
|
114
|
+
104,
|
|
115
|
+
85,
|
|
116
|
+
108,
|
|
117
|
+
107,
|
|
118
|
+
51,
|
|
119
|
+
102,
|
|
120
|
+
121,
|
|
121
|
+
53,
|
|
122
|
+
71,
|
|
123
|
+
119,
|
|
124
|
+
49,
|
|
125
|
+
117,
|
|
126
|
+
122,
|
|
127
|
+
81,
|
|
128
|
+
0,
|
|
129
|
+
0,
|
|
130
|
+
0,
|
|
131
|
+
0,
|
|
132
|
+
0
|
|
133
|
+
]);
|
|
134
|
+
var KEY_REVERSE = {};
|
|
135
|
+
for (let i = 0; i < CIPHER_TABLE.length; i++) {
|
|
136
|
+
if (CIPHER_TABLE[i] !== 0) {
|
|
137
|
+
KEY_REVERSE[CIPHER_TABLE[i]] = i;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
var INVERSE_MAPPING = {
|
|
141
|
+
98: 45,
|
|
142
|
+
50: 48,
|
|
143
|
+
119: 49,
|
|
144
|
+
55: 50,
|
|
145
|
+
113: 51,
|
|
146
|
+
83: 52,
|
|
147
|
+
116: 53,
|
|
148
|
+
69: 54,
|
|
149
|
+
86: 55,
|
|
150
|
+
52: 56,
|
|
151
|
+
80: 57,
|
|
152
|
+
102: 65,
|
|
153
|
+
82: 66,
|
|
154
|
+
101: 67,
|
|
155
|
+
65: 68,
|
|
156
|
+
70: 69,
|
|
157
|
+
66: 70,
|
|
158
|
+
117: 71,
|
|
159
|
+
100: 72,
|
|
160
|
+
107: 73,
|
|
161
|
+
54: 74,
|
|
162
|
+
51: 75,
|
|
163
|
+
75: 76,
|
|
164
|
+
76: 77,
|
|
165
|
+
43: 78,
|
|
166
|
+
89: 79,
|
|
167
|
+
45: 80,
|
|
168
|
+
122: 81,
|
|
169
|
+
84: 82,
|
|
170
|
+
53: 83,
|
|
171
|
+
68: 84,
|
|
172
|
+
110: 85,
|
|
173
|
+
72: 86,
|
|
174
|
+
104: 87,
|
|
175
|
+
81: 88,
|
|
176
|
+
85: 89,
|
|
177
|
+
57: 90,
|
|
178
|
+
87: 95,
|
|
179
|
+
71: 97,
|
|
180
|
+
90: 98,
|
|
181
|
+
73: 99,
|
|
182
|
+
106: 100,
|
|
183
|
+
78: 101,
|
|
184
|
+
114: 102,
|
|
185
|
+
49: 103,
|
|
186
|
+
109: 104,
|
|
187
|
+
97: 105,
|
|
188
|
+
79: 106,
|
|
189
|
+
112: 107,
|
|
190
|
+
111: 108,
|
|
191
|
+
77: 109,
|
|
192
|
+
88: 110,
|
|
193
|
+
105: 111,
|
|
194
|
+
74: 112,
|
|
195
|
+
108: 113,
|
|
196
|
+
103: 114,
|
|
197
|
+
56: 115,
|
|
198
|
+
67: 116,
|
|
199
|
+
120: 117,
|
|
200
|
+
99: 118,
|
|
201
|
+
118: 119,
|
|
202
|
+
48: 120,
|
|
203
|
+
115: 121,
|
|
204
|
+
121: 122
|
|
205
|
+
};
|
|
206
|
+
var FORWARD_MAPPING = {};
|
|
207
|
+
for (const [k, v] of Object.entries(INVERSE_MAPPING)) {
|
|
208
|
+
FORWARD_MAPPING[v] = Number(k);
|
|
209
|
+
}
|
|
210
|
+
var ICON_TYPE_IDS = {
|
|
211
|
+
// Field backgrounds (type 1)
|
|
212
|
+
checkered_circle: 4,
|
|
213
|
+
checkered_square: 8,
|
|
214
|
+
grey_circle: 124,
|
|
215
|
+
grey_square: 125,
|
|
216
|
+
// AoE/Mechanics (type 6)
|
|
217
|
+
circle_aoe: 9,
|
|
218
|
+
fan_aoe: 10,
|
|
219
|
+
line_aoe: 11,
|
|
220
|
+
line: 12,
|
|
221
|
+
gaze: 13,
|
|
222
|
+
stack: 14,
|
|
223
|
+
line_stack: 15,
|
|
224
|
+
proximity: 16,
|
|
225
|
+
donut: 17,
|
|
226
|
+
stack_multi: 106,
|
|
227
|
+
proximity_player: 107,
|
|
228
|
+
tankbuster: 108,
|
|
229
|
+
radial_knockback: 109,
|
|
230
|
+
linear_knockback: 110,
|
|
231
|
+
tower: 111,
|
|
232
|
+
targeting: 112,
|
|
233
|
+
moving_circle_aoe: 126,
|
|
234
|
+
"1person_aoe": 127,
|
|
235
|
+
"2person_aoe": 128,
|
|
236
|
+
"3person_aoe": 129,
|
|
237
|
+
"4person_aoe": 130,
|
|
238
|
+
// Base classes (type 2)
|
|
239
|
+
gladiator: 18,
|
|
240
|
+
pugilist: 19,
|
|
241
|
+
marauder: 20,
|
|
242
|
+
lancer: 21,
|
|
243
|
+
archer: 22,
|
|
244
|
+
conjurer: 23,
|
|
245
|
+
thaumaturge: 24,
|
|
246
|
+
arcanist: 25,
|
|
247
|
+
rogue: 26,
|
|
248
|
+
// Jobs (type 2)
|
|
249
|
+
paladin: 27,
|
|
250
|
+
monk: 28,
|
|
251
|
+
warrior: 29,
|
|
252
|
+
dragoon: 30,
|
|
253
|
+
bard: 31,
|
|
254
|
+
white_mage: 32,
|
|
255
|
+
black_mage: 33,
|
|
256
|
+
summoner: 34,
|
|
257
|
+
scholar: 35,
|
|
258
|
+
ninja: 36,
|
|
259
|
+
machinist: 37,
|
|
260
|
+
dark_knight: 38,
|
|
261
|
+
astrologian: 39,
|
|
262
|
+
samurai: 40,
|
|
263
|
+
red_mage: 41,
|
|
264
|
+
blue_mage: 42,
|
|
265
|
+
gunbreaker: 43,
|
|
266
|
+
dancer: 44,
|
|
267
|
+
reaper: 45,
|
|
268
|
+
sage: 46,
|
|
269
|
+
viper: 101,
|
|
270
|
+
pictomancer: 102,
|
|
271
|
+
// Role markers (type 2)
|
|
272
|
+
tank: 47,
|
|
273
|
+
tank_1: 48,
|
|
274
|
+
tank_2: 49,
|
|
275
|
+
healer: 50,
|
|
276
|
+
healer_1: 51,
|
|
277
|
+
healer_2: 52,
|
|
278
|
+
dps: 53,
|
|
279
|
+
dps_1: 54,
|
|
280
|
+
dps_2: 55,
|
|
281
|
+
dps_3: 56,
|
|
282
|
+
dps_4: 57,
|
|
283
|
+
melee_dps: 118,
|
|
284
|
+
ranged_dps: 119,
|
|
285
|
+
physical_ranged_dps: 120,
|
|
286
|
+
magical_ranged_dps: 121,
|
|
287
|
+
pure_healer: 122,
|
|
288
|
+
barrier_healer: 123,
|
|
289
|
+
// Enemies (type 3)
|
|
290
|
+
small_enemy: 60,
|
|
291
|
+
medium_enemy: 62,
|
|
292
|
+
large_enemy: 64,
|
|
293
|
+
// Target markers (type 3)
|
|
294
|
+
attack_1: 65,
|
|
295
|
+
attack_2: 66,
|
|
296
|
+
attack_3: 67,
|
|
297
|
+
attack_4: 68,
|
|
298
|
+
attack_5: 69,
|
|
299
|
+
attack_6: 115,
|
|
300
|
+
attack_7: 116,
|
|
301
|
+
attack_8: 117,
|
|
302
|
+
bind_1: 70,
|
|
303
|
+
bind_2: 71,
|
|
304
|
+
bind_3: 72,
|
|
305
|
+
ignore_1: 73,
|
|
306
|
+
ignore_2: 74,
|
|
307
|
+
// Chain markers (type 3)
|
|
308
|
+
square_marker: 75,
|
|
309
|
+
circle_marker: 76,
|
|
310
|
+
plus_marker: 77,
|
|
311
|
+
triangle_marker: 78,
|
|
312
|
+
// Waymarks (type 3)
|
|
313
|
+
waymark_a: 79,
|
|
314
|
+
waymark_b: 80,
|
|
315
|
+
waymark_c: 81,
|
|
316
|
+
waymark_d: 82,
|
|
317
|
+
waymark_1: 83,
|
|
318
|
+
waymark_2: 84,
|
|
319
|
+
waymark_3: 85,
|
|
320
|
+
waymark_4: 86,
|
|
321
|
+
// Shapes (type 4)
|
|
322
|
+
shape_circle: 87,
|
|
323
|
+
shape_x: 88,
|
|
324
|
+
shape_triangle: 89,
|
|
325
|
+
shape_square: 90,
|
|
326
|
+
up_arrow: 94,
|
|
327
|
+
text: 100,
|
|
328
|
+
rotate: 103,
|
|
329
|
+
highlighted_circle: 135,
|
|
330
|
+
highlighted_x: 136,
|
|
331
|
+
highlighted_square: 137,
|
|
332
|
+
highlighted_triangle: 138,
|
|
333
|
+
rotate_clockwise: 139,
|
|
334
|
+
rotate_counterclockwise: 140,
|
|
335
|
+
// Effects (type 3)
|
|
336
|
+
enhancement: 113,
|
|
337
|
+
enfeeblement: 114,
|
|
338
|
+
// Lock-on markers (type 3)
|
|
339
|
+
lockon_red: 131,
|
|
340
|
+
lockon_blue: 132,
|
|
341
|
+
lockon_purple: 133,
|
|
342
|
+
lockon_green: 134,
|
|
343
|
+
// Groups (type 5)
|
|
344
|
+
group: 105
|
|
345
|
+
};
|
|
346
|
+
var ICON_TYPES = {};
|
|
347
|
+
for (const [name, id] of Object.entries(ICON_TYPE_IDS)) {
|
|
348
|
+
ICON_TYPES[id] = name;
|
|
349
|
+
}
|
|
350
|
+
var BOARD_BACKGROUND_TYPES = {
|
|
351
|
+
1: "none",
|
|
352
|
+
2: "checkered",
|
|
353
|
+
3: "checkered_circle",
|
|
354
|
+
4: "checkered_square",
|
|
355
|
+
5: "grey",
|
|
356
|
+
6: "grey_circle",
|
|
357
|
+
7: "grey_square"
|
|
358
|
+
};
|
|
359
|
+
var BOARD_BACKGROUND_IDS = {
|
|
360
|
+
none: 1,
|
|
361
|
+
checkered: 2,
|
|
362
|
+
checkered_circle: 3,
|
|
363
|
+
checkered_square: 4,
|
|
364
|
+
grey: 5,
|
|
365
|
+
grey_circle: 6,
|
|
366
|
+
grey_square: 7
|
|
367
|
+
};
|
|
368
|
+
var OBJECT_BACKGROUND_TYPES = {
|
|
369
|
+
0: "none",
|
|
370
|
+
1: "checkered",
|
|
371
|
+
2: "checkered_circle",
|
|
372
|
+
3: "checkered_square",
|
|
373
|
+
4: "grey",
|
|
374
|
+
5: "grey_circle",
|
|
375
|
+
6: "grey_square"
|
|
376
|
+
};
|
|
377
|
+
var OBJECT_BACKGROUND_IDS = {
|
|
378
|
+
none: 0,
|
|
379
|
+
checkered: 1,
|
|
380
|
+
checkered_circle: 2,
|
|
381
|
+
checkered_square: 3,
|
|
382
|
+
grey: 4,
|
|
383
|
+
grey_circle: 5,
|
|
384
|
+
grey_square: 6
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
// src/validation.ts
|
|
388
|
+
var BOUNDS = {
|
|
389
|
+
/** Minimum X coordinate (allows slight overflow for edge objects) */
|
|
390
|
+
minX: -100,
|
|
391
|
+
/** Maximum X coordinate */
|
|
392
|
+
maxX: 612,
|
|
393
|
+
/** Minimum Y coordinate */
|
|
394
|
+
minY: -100,
|
|
395
|
+
/** Maximum Y coordinate */
|
|
396
|
+
maxY: 484,
|
|
397
|
+
/** Minimum size percentage */
|
|
398
|
+
minSize: 1,
|
|
399
|
+
/** Maximum size percentage */
|
|
400
|
+
maxSize: 255,
|
|
401
|
+
/** Minimum arc angle */
|
|
402
|
+
minArc: 0,
|
|
403
|
+
/** Maximum arc angle */
|
|
404
|
+
maxArc: 360,
|
|
405
|
+
/** Minimum donut radius */
|
|
406
|
+
minDonut: 0,
|
|
407
|
+
/** Maximum donut radius */
|
|
408
|
+
maxDonut: 255,
|
|
409
|
+
/** Maximum number of objects per board */
|
|
410
|
+
maxObjects: 50,
|
|
411
|
+
/** Maximum name length (bytes) */
|
|
412
|
+
maxNameLength: 7
|
|
413
|
+
};
|
|
414
|
+
var VALID_CIPHER_CHARS = new Set(
|
|
415
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-_".split("")
|
|
416
|
+
);
|
|
417
|
+
function validateShareCode(code) {
|
|
418
|
+
if (typeof code !== "string") {
|
|
419
|
+
throw new Error("Share code must be a string");
|
|
420
|
+
}
|
|
421
|
+
if (code.length < 10) {
|
|
422
|
+
throw new Error("Share code is too short");
|
|
423
|
+
}
|
|
424
|
+
if (code.length > 1e4) {
|
|
425
|
+
throw new Error("Share code is too long");
|
|
426
|
+
}
|
|
427
|
+
if (!code.startsWith("[stgy:a")) {
|
|
428
|
+
throw new Error('Share code must start with "[stgy:a"');
|
|
429
|
+
}
|
|
430
|
+
if (!code.endsWith("]")) {
|
|
431
|
+
throw new Error('Share code must end with "]"');
|
|
432
|
+
}
|
|
433
|
+
const body = code.slice(7, -1);
|
|
434
|
+
if (body.length < 2) {
|
|
435
|
+
throw new Error("Share code body is too short");
|
|
436
|
+
}
|
|
437
|
+
for (const char of body) {
|
|
438
|
+
if (!VALID_CIPHER_CHARS.has(char)) {
|
|
439
|
+
throw new Error(`Invalid character in share code: "${char}"`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
function clamp(value, min, max) {
|
|
444
|
+
return Math.max(min, Math.min(max, value));
|
|
445
|
+
}
|
|
446
|
+
function sanitizeName(name) {
|
|
447
|
+
if (!name || typeof name !== "string") {
|
|
448
|
+
return "board";
|
|
449
|
+
}
|
|
450
|
+
const sanitized = name.replace(/[^\x20-\x7E]/g, "").replace(/[<>:"/\\|?*]/g, "").trim().slice(0, BOUNDS.maxNameLength);
|
|
451
|
+
return sanitized || "board";
|
|
452
|
+
}
|
|
453
|
+
function sanitizeCoordinate(value, isX) {
|
|
454
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
455
|
+
return isX ? 256 : 192;
|
|
456
|
+
}
|
|
457
|
+
const min = isX ? BOUNDS.minX : BOUNDS.minY;
|
|
458
|
+
const max = isX ? BOUNDS.maxX : BOUNDS.maxY;
|
|
459
|
+
return clamp(Math.round(value * 10) / 10, min, max);
|
|
460
|
+
}
|
|
461
|
+
function sanitizeSize(value) {
|
|
462
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
463
|
+
return 100;
|
|
464
|
+
}
|
|
465
|
+
return clamp(Math.round(value), BOUNDS.minSize, BOUNDS.maxSize);
|
|
466
|
+
}
|
|
467
|
+
function sanitizeColorComponent(value) {
|
|
468
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
469
|
+
return 255;
|
|
470
|
+
}
|
|
471
|
+
return clamp(Math.round(value), 0, 255);
|
|
472
|
+
}
|
|
473
|
+
function parseHexColor(color) {
|
|
474
|
+
if (!color || typeof color !== "string") {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
const hex = color.replace(/^#/, "");
|
|
478
|
+
if (!/^[0-9A-Fa-f]{6}$/.test(hex)) {
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
483
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
484
|
+
b: parseInt(hex.slice(4, 6), 16)
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
function validateBackground(value) {
|
|
488
|
+
if (typeof value === "number") {
|
|
489
|
+
if (value >= 0 && value <= 6) {
|
|
490
|
+
return value;
|
|
491
|
+
}
|
|
492
|
+
return 0;
|
|
493
|
+
}
|
|
494
|
+
if (typeof value === "string") {
|
|
495
|
+
const id = OBJECT_BACKGROUND_IDS[value.toLowerCase()];
|
|
496
|
+
return id ?? 0;
|
|
497
|
+
}
|
|
498
|
+
return 0;
|
|
499
|
+
}
|
|
500
|
+
function validateBoardBackground(value) {
|
|
501
|
+
if (typeof value === "string") {
|
|
502
|
+
const id = BOARD_BACKGROUND_IDS[value.toLowerCase()];
|
|
503
|
+
return id ?? 1;
|
|
504
|
+
}
|
|
505
|
+
if (typeof value === "number" && value >= 1 && value <= 7) {
|
|
506
|
+
return value;
|
|
507
|
+
}
|
|
508
|
+
return 1;
|
|
509
|
+
}
|
|
510
|
+
function validateIconType(typeName, typeId) {
|
|
511
|
+
if (typeof typeId === "number" && Number.isFinite(typeId) && typeId > 0 && typeId < 256) {
|
|
512
|
+
return typeId;
|
|
513
|
+
}
|
|
514
|
+
const id = ICON_TYPE_IDS[typeName];
|
|
515
|
+
if (id !== void 0) {
|
|
516
|
+
return id;
|
|
517
|
+
}
|
|
518
|
+
const unknownMatch = typeName.match(/^unknown_(\d+)$/);
|
|
519
|
+
if (unknownMatch) {
|
|
520
|
+
const parsedId = parseInt(unknownMatch[1], 10);
|
|
521
|
+
if (parsedId > 0 && parsedId < 256) {
|
|
522
|
+
return parsedId;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
throw new Error(`Unknown icon type: "${typeName}"`);
|
|
526
|
+
}
|
|
527
|
+
function sanitizeObject(obj, index) {
|
|
528
|
+
if (!obj || typeof obj !== "object") {
|
|
529
|
+
throw new Error(`Object at index ${index} is not a valid object`);
|
|
530
|
+
}
|
|
531
|
+
if (!obj.type && !obj.typeId) {
|
|
532
|
+
throw new Error(`Object at index ${index} must have a type or typeId`);
|
|
533
|
+
}
|
|
534
|
+
const typeId = validateIconType(obj.type || "", obj.typeId);
|
|
535
|
+
let colorR = obj.colorR ?? 255;
|
|
536
|
+
let colorG = obj.colorG ?? 255;
|
|
537
|
+
let colorB = obj.colorB ?? 255;
|
|
538
|
+
if (obj.color) {
|
|
539
|
+
const parsed = parseHexColor(obj.color);
|
|
540
|
+
if (parsed) {
|
|
541
|
+
colorR = parsed.r;
|
|
542
|
+
colorG = parsed.g;
|
|
543
|
+
colorB = parsed.b;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
typeId,
|
|
548
|
+
x: sanitizeCoordinate(obj.x, true),
|
|
549
|
+
y: sanitizeCoordinate(obj.y, false),
|
|
550
|
+
size: sanitizeSize(obj.size),
|
|
551
|
+
background: validateBackground(obj.background),
|
|
552
|
+
colorR: sanitizeColorComponent(colorR),
|
|
553
|
+
colorG: sanitizeColorComponent(colorG),
|
|
554
|
+
colorB: sanitizeColorComponent(colorB),
|
|
555
|
+
transparency: sanitizeColorComponent(obj.transparency ?? 0),
|
|
556
|
+
arcAngle: clamp(Math.round(obj.arcAngle ?? 0), BOUNDS.minArc, BOUNDS.maxArc),
|
|
557
|
+
donutRadius: clamp(Math.round(obj.donutRadius ?? 0), BOUNDS.minDonut, BOUNDS.maxDonut),
|
|
558
|
+
hidden: Boolean(obj.hidden),
|
|
559
|
+
locked: Boolean(obj.locked)
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
function sanitizeBoard(board) {
|
|
563
|
+
if (!board || typeof board !== "object") {
|
|
564
|
+
throw new Error("Board must be an object");
|
|
565
|
+
}
|
|
566
|
+
if (!Array.isArray(board.objects)) {
|
|
567
|
+
throw new Error("Board must have an objects array");
|
|
568
|
+
}
|
|
569
|
+
if (board.objects.length > BOUNDS.maxObjects) {
|
|
570
|
+
throw new Error(`Board has too many objects (max ${BOUNDS.maxObjects})`);
|
|
571
|
+
}
|
|
572
|
+
return {
|
|
573
|
+
name: sanitizeName(board.name),
|
|
574
|
+
boardBackground: validateBoardBackground(board.boardBackground),
|
|
575
|
+
objects: board.objects.map((obj, i) => sanitizeObject(obj, i))
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// src/binary.ts
|
|
580
|
+
var BinaryReader = class {
|
|
581
|
+
view;
|
|
582
|
+
pos;
|
|
583
|
+
constructor(data) {
|
|
584
|
+
this.view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
585
|
+
this.pos = 0;
|
|
586
|
+
}
|
|
587
|
+
get position() {
|
|
588
|
+
return this.pos;
|
|
589
|
+
}
|
|
590
|
+
get length() {
|
|
591
|
+
return this.view.byteLength;
|
|
592
|
+
}
|
|
593
|
+
get remaining() {
|
|
594
|
+
return this.length - this.pos;
|
|
595
|
+
}
|
|
596
|
+
seek(pos) {
|
|
597
|
+
if (pos < 0 || pos > this.length) {
|
|
598
|
+
throw new Error(`Seek position ${pos} out of bounds [0, ${this.length}]`);
|
|
599
|
+
}
|
|
600
|
+
this.pos = pos;
|
|
601
|
+
}
|
|
602
|
+
readUint8() {
|
|
603
|
+
if (this.pos + 1 > this.length) {
|
|
604
|
+
throw new Error("Unexpected end of data reading uint8");
|
|
605
|
+
}
|
|
606
|
+
const val = this.view.getUint8(this.pos);
|
|
607
|
+
this.pos += 1;
|
|
608
|
+
return val;
|
|
609
|
+
}
|
|
610
|
+
readInt16() {
|
|
611
|
+
if (this.pos + 2 > this.length) {
|
|
612
|
+
throw new Error("Unexpected end of data reading int16");
|
|
613
|
+
}
|
|
614
|
+
const val = this.view.getInt16(this.pos, true);
|
|
615
|
+
this.pos += 2;
|
|
616
|
+
return val;
|
|
617
|
+
}
|
|
618
|
+
readUint16() {
|
|
619
|
+
if (this.pos + 2 > this.length) {
|
|
620
|
+
throw new Error("Unexpected end of data reading uint16");
|
|
621
|
+
}
|
|
622
|
+
const val = this.view.getUint16(this.pos, true);
|
|
623
|
+
this.pos += 2;
|
|
624
|
+
return val;
|
|
625
|
+
}
|
|
626
|
+
readUint32() {
|
|
627
|
+
if (this.pos + 4 > this.length) {
|
|
628
|
+
throw new Error("Unexpected end of data reading uint32");
|
|
629
|
+
}
|
|
630
|
+
const val = this.view.getUint32(this.pos, true);
|
|
631
|
+
this.pos += 4;
|
|
632
|
+
return val;
|
|
633
|
+
}
|
|
634
|
+
readBytes(count) {
|
|
635
|
+
if (this.pos + count > this.length) {
|
|
636
|
+
throw new Error(`Unexpected end of data reading ${count} bytes`);
|
|
637
|
+
}
|
|
638
|
+
const data = new Uint8Array(this.view.buffer, this.view.byteOffset + this.pos, count);
|
|
639
|
+
this.pos += count;
|
|
640
|
+
return data;
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
function parseBinary(data) {
|
|
644
|
+
if (data.length < 28) {
|
|
645
|
+
throw new Error("Binary data is too short for header");
|
|
646
|
+
}
|
|
647
|
+
const reader = new BinaryReader(data);
|
|
648
|
+
const version = reader.readUint32();
|
|
649
|
+
reader.seek(24);
|
|
650
|
+
reader.readUint16();
|
|
651
|
+
const nameLength = reader.readUint16();
|
|
652
|
+
if (data.length < 28 + nameLength) {
|
|
653
|
+
throw new Error("Binary data is too short for name field");
|
|
654
|
+
}
|
|
655
|
+
const nameBytes = reader.readBytes(nameLength);
|
|
656
|
+
const name = new TextDecoder("utf-8", { fatal: false }).decode(nameBytes).replace(/\0+$/, "").trim();
|
|
657
|
+
const icons = [];
|
|
658
|
+
const textContents = [];
|
|
659
|
+
while (reader.remaining >= 4) {
|
|
660
|
+
const marker = reader.readUint16();
|
|
661
|
+
if (marker === 2) {
|
|
662
|
+
const iconId = reader.readUint16();
|
|
663
|
+
icons.push(iconId);
|
|
664
|
+
} else if (marker === 3) {
|
|
665
|
+
const val = reader.readUint16();
|
|
666
|
+
if (val > 1) {
|
|
667
|
+
if (reader.remaining < val) break;
|
|
668
|
+
const textBytes = reader.readBytes(val);
|
|
669
|
+
const textContent = new TextDecoder("utf-8", { fatal: false }).decode(textBytes).replace(/\0+$/, "");
|
|
670
|
+
textContents.push(textContent);
|
|
671
|
+
} else {
|
|
672
|
+
reader.seek(reader.position - 4);
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
} else {
|
|
676
|
+
reader.seek(reader.position - 2);
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
const n = icons.length;
|
|
681
|
+
const positions = [];
|
|
682
|
+
const backgrounds = [];
|
|
683
|
+
const sizes = [];
|
|
684
|
+
const colors = [];
|
|
685
|
+
const tag10 = [];
|
|
686
|
+
const tag11 = [];
|
|
687
|
+
const tag12 = [];
|
|
688
|
+
const objectFlags = [];
|
|
689
|
+
let boardBackground = 1;
|
|
690
|
+
while (reader.remaining >= 2) {
|
|
691
|
+
const tag = reader.readUint16();
|
|
692
|
+
if (tag === 2) {
|
|
693
|
+
if (reader.remaining < 2) break;
|
|
694
|
+
const iconId = reader.readUint16();
|
|
695
|
+
icons.push(iconId);
|
|
696
|
+
} else if (tag === 4) {
|
|
697
|
+
if (reader.remaining < 4) break;
|
|
698
|
+
reader.readUint16();
|
|
699
|
+
const countVal = reader.readUint16();
|
|
700
|
+
if (countVal > 1) {
|
|
701
|
+
if (reader.remaining < countVal * 2) break;
|
|
702
|
+
for (let i = 0; i < countVal; i++) {
|
|
703
|
+
objectFlags.push(reader.readUint16());
|
|
704
|
+
}
|
|
705
|
+
} else {
|
|
706
|
+
if (reader.remaining < 2) break;
|
|
707
|
+
objectFlags.push(reader.readUint16());
|
|
708
|
+
}
|
|
709
|
+
} else if (tag === 5) {
|
|
710
|
+
if (reader.remaining < 4) break;
|
|
711
|
+
reader.readUint16();
|
|
712
|
+
const count = reader.readUint16();
|
|
713
|
+
for (let i = 0; i < count && reader.remaining >= 4; i++) {
|
|
714
|
+
const x = reader.readInt16() / 10;
|
|
715
|
+
const y = reader.readInt16() / 10;
|
|
716
|
+
positions.push({ x, y });
|
|
717
|
+
}
|
|
718
|
+
} else if (tag === 6) {
|
|
719
|
+
if (reader.remaining < 4) break;
|
|
720
|
+
reader.readUint16();
|
|
721
|
+
const count = reader.readUint16();
|
|
722
|
+
for (let i = 0; i < count && reader.remaining >= 2; i++) {
|
|
723
|
+
backgrounds.push(reader.readInt16());
|
|
724
|
+
}
|
|
725
|
+
} else if (tag === 7) {
|
|
726
|
+
if (reader.remaining < 4) break;
|
|
727
|
+
reader.readUint16();
|
|
728
|
+
const count = reader.readUint16();
|
|
729
|
+
for (let i = 0; i < count && reader.remaining >= 1; i++) {
|
|
730
|
+
sizes.push(reader.readUint8());
|
|
731
|
+
}
|
|
732
|
+
if (count % 2 === 1 && reader.remaining >= 1) {
|
|
733
|
+
reader.readUint8();
|
|
734
|
+
}
|
|
735
|
+
} else if (tag === 8) {
|
|
736
|
+
if (reader.remaining < 4) break;
|
|
737
|
+
reader.readUint16();
|
|
738
|
+
const count = reader.readUint16();
|
|
739
|
+
for (let i = 0; i < count && reader.remaining >= 4; i++) {
|
|
740
|
+
const r = reader.readUint8();
|
|
741
|
+
const g = reader.readUint8();
|
|
742
|
+
const b = reader.readUint8();
|
|
743
|
+
const a = reader.readUint8();
|
|
744
|
+
colors.push({ r, g, b, a });
|
|
745
|
+
}
|
|
746
|
+
} else if (tag === 10) {
|
|
747
|
+
if (reader.remaining < 4) break;
|
|
748
|
+
reader.readUint16();
|
|
749
|
+
const count = reader.readUint16();
|
|
750
|
+
for (let i = 0; i < count && reader.remaining >= 2; i++) {
|
|
751
|
+
tag10.push(reader.readUint16());
|
|
752
|
+
}
|
|
753
|
+
} else if (tag === 11) {
|
|
754
|
+
if (reader.remaining < 4) break;
|
|
755
|
+
reader.readUint16();
|
|
756
|
+
const count = reader.readUint16();
|
|
757
|
+
for (let i = 0; i < count && reader.remaining >= 2; i++) {
|
|
758
|
+
tag11.push(reader.readUint16());
|
|
759
|
+
}
|
|
760
|
+
} else if (tag === 12) {
|
|
761
|
+
if (reader.remaining < 4) break;
|
|
762
|
+
reader.readUint16();
|
|
763
|
+
const count = reader.readUint16();
|
|
764
|
+
for (let i = 0; i < count && reader.remaining >= 2; i++) {
|
|
765
|
+
tag12.push(reader.readUint16());
|
|
766
|
+
}
|
|
767
|
+
} else if (tag === 3) {
|
|
768
|
+
if (reader.remaining < 2) break;
|
|
769
|
+
const val = reader.readUint16();
|
|
770
|
+
if (val === 1) {
|
|
771
|
+
if (reader.remaining < 4) break;
|
|
772
|
+
reader.readUint16();
|
|
773
|
+
boardBackground = reader.readUint16();
|
|
774
|
+
break;
|
|
775
|
+
} else {
|
|
776
|
+
if (reader.remaining < val) break;
|
|
777
|
+
const textBytes = reader.readBytes(val);
|
|
778
|
+
const textContent = new TextDecoder("utf-8", { fatal: false }).decode(textBytes).replace(/\0+$/, "");
|
|
779
|
+
textContents.push(textContent);
|
|
780
|
+
}
|
|
781
|
+
} else {
|
|
782
|
+
break;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
const objects = [];
|
|
786
|
+
for (let i = 0; i < n; i++) {
|
|
787
|
+
const iconId = icons[i];
|
|
788
|
+
const typeName = ICON_TYPES[iconId] ?? `unknown_${iconId}`;
|
|
789
|
+
const obj = {
|
|
790
|
+
type: typeName,
|
|
791
|
+
typeId: iconId,
|
|
792
|
+
x: positions[i]?.x ?? 0,
|
|
793
|
+
y: positions[i]?.y ?? 0
|
|
794
|
+
};
|
|
795
|
+
obj.size = sizes[i] && sizes[i] > 0 ? sizes[i] : 100;
|
|
796
|
+
const rotatableTypes = /* @__PURE__ */ new Set([10, 11, 12, 15, 16, 17, 18, 19, 110]);
|
|
797
|
+
if (rotatableTypes.has(iconId)) {
|
|
798
|
+
if (backgrounds[i] !== void 0 && backgrounds[i] !== 0) {
|
|
799
|
+
obj.angle = backgrounds[i];
|
|
800
|
+
}
|
|
801
|
+
} else if (backgrounds[i] !== void 0 && backgrounds[i] > 0) {
|
|
802
|
+
const bgName = OBJECT_BACKGROUND_TYPES[backgrounds[i]];
|
|
803
|
+
obj.background = bgName ?? backgrounds[i];
|
|
804
|
+
}
|
|
805
|
+
const colorableTypes = /* @__PURE__ */ new Set([11, 12, 100]);
|
|
806
|
+
if (colors[i] && colorableTypes.has(iconId)) {
|
|
807
|
+
const { r, g, b, a } = colors[i];
|
|
808
|
+
const hex = `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
809
|
+
obj.color = hex;
|
|
810
|
+
if (a > 0) {
|
|
811
|
+
obj.transparency = a;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
if (iconId === 11) {
|
|
815
|
+
if (tag10[i] && tag10[i] > 0) {
|
|
816
|
+
obj.width = tag10[i];
|
|
817
|
+
}
|
|
818
|
+
if (tag11[i] && tag11[i] > 0) {
|
|
819
|
+
obj.height = tag11[i];
|
|
820
|
+
}
|
|
821
|
+
} else if (iconId === 10) {
|
|
822
|
+
if (tag10[i] && tag10[i] > 0) {
|
|
823
|
+
obj.arcAngle = tag10[i];
|
|
824
|
+
}
|
|
825
|
+
} else if (iconId === 12) {
|
|
826
|
+
if (tag10[i] && tag10[i] > 0) {
|
|
827
|
+
obj.endX = tag10[i] / 10;
|
|
828
|
+
}
|
|
829
|
+
if (tag11[i] && tag11[i] > 0) {
|
|
830
|
+
obj.endY = tag11[i] / 10;
|
|
831
|
+
}
|
|
832
|
+
if (tag12[i] && tag12[i] > 0) {
|
|
833
|
+
obj.height = tag12[i];
|
|
834
|
+
}
|
|
835
|
+
} else if (iconId === 15) {
|
|
836
|
+
if (tag11[i] && tag11[i] > 0) {
|
|
837
|
+
obj.displayCount = tag11[i];
|
|
838
|
+
}
|
|
839
|
+
} else if (iconId === 110) {
|
|
840
|
+
if (tag10[i] && tag10[i] > 0) {
|
|
841
|
+
obj.horizontalCount = tag10[i];
|
|
842
|
+
}
|
|
843
|
+
if (tag11[i] && tag11[i] > 0) {
|
|
844
|
+
obj.verticalCount = tag11[i];
|
|
845
|
+
}
|
|
846
|
+
} else if (iconId === 17) {
|
|
847
|
+
if (tag10[i] && tag10[i] > 0) {
|
|
848
|
+
obj.arcAngle = tag10[i];
|
|
849
|
+
}
|
|
850
|
+
if (tag11[i] && tag11[i] > 0) {
|
|
851
|
+
obj.donutRadius = tag11[i];
|
|
852
|
+
}
|
|
853
|
+
} else {
|
|
854
|
+
if (tag10[i] && tag10[i] > 0) {
|
|
855
|
+
obj.arcAngle = tag10[i];
|
|
856
|
+
}
|
|
857
|
+
if (tag11[i] && tag11[i] > 0) {
|
|
858
|
+
obj.donutRadius = tag11[i];
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
if (iconId === 100 && textContents.length > 0) {
|
|
862
|
+
obj.text = textContents.shift();
|
|
863
|
+
}
|
|
864
|
+
const objFlags = objectFlags[i] ?? 1;
|
|
865
|
+
if ((objFlags & 1) === 0) {
|
|
866
|
+
obj.hidden = true;
|
|
867
|
+
}
|
|
868
|
+
if (objFlags & 2) {
|
|
869
|
+
obj.horizontalFlip = true;
|
|
870
|
+
}
|
|
871
|
+
if (objFlags & 4) {
|
|
872
|
+
obj.verticalFlip = true;
|
|
873
|
+
}
|
|
874
|
+
if (objFlags & 8) {
|
|
875
|
+
obj.locked = true;
|
|
876
|
+
}
|
|
877
|
+
objects.push(obj);
|
|
878
|
+
}
|
|
879
|
+
return {
|
|
880
|
+
version,
|
|
881
|
+
name: name || void 0,
|
|
882
|
+
boardBackground: BOARD_BACKGROUND_TYPES[boardBackground] ?? "none",
|
|
883
|
+
objects
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
function buildBinary(data) {
|
|
887
|
+
const { name, boardBackground, objects } = data;
|
|
888
|
+
const n = objects.length;
|
|
889
|
+
const tag4Size = n <= 1 ? 8 : 6 + n * 2;
|
|
890
|
+
const tag7Size = 6 + n + (n % 2 === 1 ? 1 : 0);
|
|
891
|
+
const bufferSize = n === 0 ? 36 + 8 : 36 + n * 4 + tag4Size + (6 + n * 4) + (6 + n * 2) + tag7Size + (6 + n * 4) + (6 + n * 2) + (6 + n * 2) + (6 + n * 2) + 8;
|
|
892
|
+
const buffer = new ArrayBuffer(bufferSize);
|
|
893
|
+
const view = new DataView(buffer);
|
|
894
|
+
let pos = 0;
|
|
895
|
+
const writeUint8 = (val) => {
|
|
896
|
+
view.setUint8(pos, val);
|
|
897
|
+
pos += 1;
|
|
898
|
+
};
|
|
899
|
+
const writeUint16 = (val) => {
|
|
900
|
+
view.setUint16(pos, val, true);
|
|
901
|
+
pos += 2;
|
|
902
|
+
};
|
|
903
|
+
const writeInt16 = (val) => {
|
|
904
|
+
view.setInt16(pos, val, true);
|
|
905
|
+
pos += 2;
|
|
906
|
+
};
|
|
907
|
+
const writeUint32 = (val) => {
|
|
908
|
+
view.setUint32(pos, val, true);
|
|
909
|
+
pos += 4;
|
|
910
|
+
};
|
|
911
|
+
writeUint32(2);
|
|
912
|
+
writeUint32(0);
|
|
913
|
+
for (let i = 0; i < 10; i++) writeUint8(0);
|
|
914
|
+
writeUint32(0);
|
|
915
|
+
writeUint16(0);
|
|
916
|
+
writeUint16(1);
|
|
917
|
+
writeUint16(8);
|
|
918
|
+
const nameBytes = new TextEncoder().encode(name.slice(0, 7));
|
|
919
|
+
for (let i = 0; i < 8; i++) {
|
|
920
|
+
writeUint8(nameBytes[i] ?? 0);
|
|
921
|
+
}
|
|
922
|
+
for (const obj of objects) {
|
|
923
|
+
writeUint16(2);
|
|
924
|
+
writeUint16(obj.typeId);
|
|
925
|
+
}
|
|
926
|
+
if (n === 0) ; else if (n === 1) {
|
|
927
|
+
writeUint16(4);
|
|
928
|
+
writeUint16(1);
|
|
929
|
+
const obj = objects[0];
|
|
930
|
+
let flagsVal = 1;
|
|
931
|
+
if (obj.hidden) {
|
|
932
|
+
flagsVal = 0;
|
|
933
|
+
} else if (obj.locked) {
|
|
934
|
+
flagsVal = 9;
|
|
935
|
+
}
|
|
936
|
+
writeUint16(1);
|
|
937
|
+
writeUint16(flagsVal);
|
|
938
|
+
} else {
|
|
939
|
+
writeUint16(4);
|
|
940
|
+
writeUint16(1);
|
|
941
|
+
writeUint16(n);
|
|
942
|
+
for (let i = 0; i < n; i++) {
|
|
943
|
+
writeUint16(1);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
if (n > 0) {
|
|
947
|
+
writeUint16(5);
|
|
948
|
+
writeUint16(3);
|
|
949
|
+
writeUint16(n);
|
|
950
|
+
for (const obj of objects) {
|
|
951
|
+
writeInt16(Math.round(obj.x * 10));
|
|
952
|
+
writeInt16(Math.round(obj.y * 10));
|
|
953
|
+
}
|
|
954
|
+
writeUint16(6);
|
|
955
|
+
writeUint16(1);
|
|
956
|
+
writeUint16(n);
|
|
957
|
+
for (const obj of objects) {
|
|
958
|
+
writeUint16(obj.background);
|
|
959
|
+
}
|
|
960
|
+
writeUint16(7);
|
|
961
|
+
writeUint16(0);
|
|
962
|
+
writeUint16(n);
|
|
963
|
+
for (const obj of objects) {
|
|
964
|
+
writeUint8(obj.size & 255);
|
|
965
|
+
}
|
|
966
|
+
if (n % 2 === 1) {
|
|
967
|
+
writeUint8(0);
|
|
968
|
+
}
|
|
969
|
+
writeUint16(8);
|
|
970
|
+
writeUint16(2);
|
|
971
|
+
writeUint16(n);
|
|
972
|
+
for (const obj of objects) {
|
|
973
|
+
writeUint8(obj.colorR);
|
|
974
|
+
writeUint8(obj.colorG);
|
|
975
|
+
writeUint8(obj.colorB);
|
|
976
|
+
writeUint8(obj.transparency);
|
|
977
|
+
}
|
|
978
|
+
writeUint16(10);
|
|
979
|
+
writeUint16(1);
|
|
980
|
+
writeUint16(n);
|
|
981
|
+
for (const obj of objects) {
|
|
982
|
+
writeUint16(obj.arcAngle);
|
|
983
|
+
}
|
|
984
|
+
writeUint16(11);
|
|
985
|
+
writeUint16(1);
|
|
986
|
+
writeUint16(n);
|
|
987
|
+
for (const obj of objects) {
|
|
988
|
+
writeUint16(obj.donutRadius);
|
|
989
|
+
}
|
|
990
|
+
writeUint16(12);
|
|
991
|
+
writeUint16(1);
|
|
992
|
+
writeUint16(n);
|
|
993
|
+
for (let i = 0; i < n; i++) {
|
|
994
|
+
writeUint16(0);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
writeUint16(3);
|
|
998
|
+
writeUint16(1);
|
|
999
|
+
writeUint16(1);
|
|
1000
|
+
writeUint16(boardBackground);
|
|
1001
|
+
const result = new Uint8Array(buffer, 0, pos);
|
|
1002
|
+
const headerView = new DataView(result.buffer, result.byteOffset, result.byteLength);
|
|
1003
|
+
const payloadSize = pos - 28;
|
|
1004
|
+
headerView.setUint32(18, payloadSize, true);
|
|
1005
|
+
const field04 = pos - 16;
|
|
1006
|
+
headerView.setUint32(4, field04, true);
|
|
1007
|
+
return result;
|
|
1008
|
+
}
|
|
1009
|
+
function charToBase64Value(c) {
|
|
1010
|
+
const o = c.charCodeAt(0);
|
|
1011
|
+
if (o >= 65 && o <= 90) return o - 65;
|
|
1012
|
+
if (o >= 97 && o <= 122) return o - 71;
|
|
1013
|
+
if (o >= 48 && o <= 57) return o + 4;
|
|
1014
|
+
if (c === "-") return 62;
|
|
1015
|
+
if (c === "_") return 63;
|
|
1016
|
+
return 0;
|
|
1017
|
+
}
|
|
1018
|
+
function base64ValueToChar(val) {
|
|
1019
|
+
val = val & 63;
|
|
1020
|
+
if (val < 26) return String.fromCharCode(val + 65);
|
|
1021
|
+
if (val < 52) return String.fromCharCode(val + 71);
|
|
1022
|
+
if (val < 62) return String.fromCharCode(val - 4);
|
|
1023
|
+
if (val === 62) return "-";
|
|
1024
|
+
if (val === 63) return "_";
|
|
1025
|
+
return "A";
|
|
1026
|
+
}
|
|
1027
|
+
function decodeCipher(stgyString) {
|
|
1028
|
+
const data = stgyString.slice(7, -1);
|
|
1029
|
+
if (data.length < 2) {
|
|
1030
|
+
throw new Error("Share code body is too short");
|
|
1031
|
+
}
|
|
1032
|
+
const keyChar = data[0];
|
|
1033
|
+
const keyCharCode = keyChar.charCodeAt(0);
|
|
1034
|
+
if (keyCharCode >= CIPHER_TABLE.length) {
|
|
1035
|
+
throw new Error("Invalid key character in share code");
|
|
1036
|
+
}
|
|
1037
|
+
const keyMapped = CIPHER_TABLE[keyCharCode];
|
|
1038
|
+
if (keyMapped === 0) {
|
|
1039
|
+
throw new Error("Invalid key character in share code");
|
|
1040
|
+
}
|
|
1041
|
+
const keyMappedChar = String.fromCharCode(keyMapped);
|
|
1042
|
+
const key = charToBase64Value(keyMappedChar);
|
|
1043
|
+
const decoded = [];
|
|
1044
|
+
for (let i = 0; i < data.length - 1; i++) {
|
|
1045
|
+
const c = data[i + 1];
|
|
1046
|
+
const charCode = c.charCodeAt(0);
|
|
1047
|
+
const standardCharCode = INVERSE_MAPPING[charCode] ?? 65;
|
|
1048
|
+
const standardChar = String.fromCharCode(standardCharCode);
|
|
1049
|
+
const val = charToBase64Value(standardChar);
|
|
1050
|
+
const decodedVal = val - i - key & 63;
|
|
1051
|
+
decoded.push(base64ValueToChar(decodedVal));
|
|
1052
|
+
}
|
|
1053
|
+
let b64String = decoded.join("").replace(/-/g, "+").replace(/_/g, "/");
|
|
1054
|
+
const padLength = (4 - b64String.length % 4) % 4;
|
|
1055
|
+
b64String += "=".repeat(padLength);
|
|
1056
|
+
const binaryString = atob(b64String);
|
|
1057
|
+
const binaryData = new Uint8Array(binaryString.length);
|
|
1058
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
1059
|
+
binaryData[i] = binaryString.charCodeAt(i);
|
|
1060
|
+
}
|
|
1061
|
+
if (binaryData.length < 7) {
|
|
1062
|
+
throw new Error("Binary data is too short");
|
|
1063
|
+
}
|
|
1064
|
+
try {
|
|
1065
|
+
const compressed = binaryData.slice(6);
|
|
1066
|
+
return pako.inflate(compressed);
|
|
1067
|
+
} catch (e) {
|
|
1068
|
+
throw new Error(`Failed to decompress data: ${e instanceof Error ? e.message : "unknown error"}`);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
function encodeCipher(binaryData, key) {
|
|
1072
|
+
const compressed = pako.deflate(binaryData, { level: 6 });
|
|
1073
|
+
const length = binaryData.length;
|
|
1074
|
+
const lengthBytes = new Uint8Array(2);
|
|
1075
|
+
lengthBytes[0] = length & 255;
|
|
1076
|
+
lengthBytes[1] = length >> 8 & 255;
|
|
1077
|
+
const checksumInput = new Uint8Array(2 + compressed.length);
|
|
1078
|
+
checksumInput.set(lengthBytes, 0);
|
|
1079
|
+
checksumInput.set(compressed, 2);
|
|
1080
|
+
const checksum = crc32(checksumInput);
|
|
1081
|
+
const fullData = new Uint8Array(6 + compressed.length);
|
|
1082
|
+
fullData[0] = checksum & 255;
|
|
1083
|
+
fullData[1] = checksum >> 8 & 255;
|
|
1084
|
+
fullData[2] = checksum >> 16 & 255;
|
|
1085
|
+
fullData[3] = checksum >> 24 & 255;
|
|
1086
|
+
fullData[4] = lengthBytes[0];
|
|
1087
|
+
fullData[5] = lengthBytes[1];
|
|
1088
|
+
fullData.set(compressed, 6);
|
|
1089
|
+
let b64 = "";
|
|
1090
|
+
for (let i = 0; i < fullData.length; i++) {
|
|
1091
|
+
b64 += String.fromCharCode(fullData[i]);
|
|
1092
|
+
}
|
|
1093
|
+
b64 = btoa(b64);
|
|
1094
|
+
b64 = b64.replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
1095
|
+
if (key === void 0 || key < 0 || key > 63) {
|
|
1096
|
+
key = Math.floor(Math.random() * 64);
|
|
1097
|
+
}
|
|
1098
|
+
const keyCharStandard = base64ValueToChar(key);
|
|
1099
|
+
const keyCharCode = keyCharStandard.charCodeAt(0);
|
|
1100
|
+
const keySource = KEY_REVERSE[keyCharCode] ?? 86;
|
|
1101
|
+
const encoded = [];
|
|
1102
|
+
for (let i = 0; i < b64.length; i++) {
|
|
1103
|
+
const c = b64[i];
|
|
1104
|
+
const val = charToBase64Value(c);
|
|
1105
|
+
const encodedVal = val + i + key & 63;
|
|
1106
|
+
const standardChar = base64ValueToChar(encodedVal);
|
|
1107
|
+
const standardCharCode = standardChar.charCodeAt(0);
|
|
1108
|
+
const substChar = FORWARD_MAPPING[standardCharCode] ?? 65;
|
|
1109
|
+
encoded.push(String.fromCharCode(substChar));
|
|
1110
|
+
}
|
|
1111
|
+
return `[stgy:a${String.fromCharCode(keySource)}${encoded.join("")}]`;
|
|
1112
|
+
}
|
|
1113
|
+
function crc32(data) {
|
|
1114
|
+
let crc = 4294967295;
|
|
1115
|
+
for (let i = 0; i < data.length; i++) {
|
|
1116
|
+
crc ^= data[i];
|
|
1117
|
+
for (let j = 0; j < 8; j++) {
|
|
1118
|
+
if (crc & 1) {
|
|
1119
|
+
crc = crc >>> 1 ^ 3988292384;
|
|
1120
|
+
} else {
|
|
1121
|
+
crc = crc >>> 1;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
return (crc ^ 4294967295) >>> 0;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// src/encoder.ts
|
|
1129
|
+
function encode(board, options) {
|
|
1130
|
+
const sanitized = sanitizeBoard(board);
|
|
1131
|
+
const binary = buildBinary(sanitized);
|
|
1132
|
+
return encodeCipher(binary, options?.key);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// src/decoder.ts
|
|
1136
|
+
function decode(shareCode) {
|
|
1137
|
+
validateShareCode(shareCode);
|
|
1138
|
+
const binary = decodeCipher(shareCode);
|
|
1139
|
+
return parseBinary(binary);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
export { ICON_TYPES, ICON_TYPE_IDS, decode, encode };
|
|
1143
|
+
//# sourceMappingURL=index.js.map
|
|
1144
|
+
//# sourceMappingURL=index.js.map
|