@gmvjs/xray-v1 0.0.0-stage → 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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +71 -2
  3. package/dist/db.d.ts +12 -0
  4. package/dist/db.js +134 -0
  5. package/dist/db.js.map +1 -0
  6. package/dist/details.d.ts +42 -0
  7. package/dist/details.js +210 -0
  8. package/dist/details.js.map +1 -0
  9. package/dist/export.d.ts +38 -0
  10. package/dist/export.js +241 -0
  11. package/dist/export.js.map +1 -0
  12. package/dist/games/cop.d.ts +7 -0
  13. package/dist/games/cop.js +11 -0
  14. package/dist/games/cop.js.map +1 -0
  15. package/dist/games/cs.d.ts +7 -0
  16. package/dist/games/cs.js +11 -0
  17. package/dist/games/cs.js.map +1 -0
  18. package/dist/games/sdk.d.ts +7 -0
  19. package/dist/games/sdk.js +10 -0
  20. package/dist/games/sdk.js.map +1 -0
  21. package/dist/games/soc.d.ts +9 -0
  22. package/dist/games/soc.js +13 -0
  23. package/dist/games/soc.js.map +1 -0
  24. package/dist/i18n.d.ts +2 -0
  25. package/dist/i18n.js +48 -0
  26. package/dist/i18n.js.map +1 -0
  27. package/dist/index.d.ts +5 -0
  28. package/dist/index.js +131 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/level.d.ts +39 -0
  31. package/dist/level.js +234 -0
  32. package/dist/level.js.map +1 -0
  33. package/dist/load.d.ts +5 -0
  34. package/dist/load.js +126 -0
  35. package/dist/load.js.map +1 -0
  36. package/dist/lzhuf.d.ts +2 -0
  37. package/dist/lzhuf.js +136 -0
  38. package/dist/lzhuf.js.map +1 -0
  39. package/dist/motion.d.ts +28 -0
  40. package/dist/motion.js +20 -0
  41. package/dist/motion.js.map +1 -0
  42. package/dist/object.d.ts +138 -0
  43. package/dist/object.js +361 -0
  44. package/dist/object.js.map +1 -0
  45. package/dist/ogf.d.ts +93 -0
  46. package/dist/ogf.js +418 -0
  47. package/dist/ogf.js.map +1 -0
  48. package/dist/scene.d.ts +28 -0
  49. package/dist/scene.js +141 -0
  50. package/dist/scene.js.map +1 -0
  51. package/dist/skeleton.d.ts +34 -0
  52. package/dist/skeleton.js +252 -0
  53. package/dist/skeleton.js.map +1 -0
  54. package/dist/smotion.d.ts +44 -0
  55. package/dist/smotion.js +283 -0
  56. package/dist/smotion.js.map +1 -0
  57. package/dist/soc-key.d.ts +18 -0
  58. package/dist/soc-key.js +125 -0
  59. package/dist/soc-key.js.map +1 -0
  60. package/dist/thm.d.ts +26 -0
  61. package/dist/thm.js +69 -0
  62. package/dist/thm.js.map +1 -0
  63. package/dist/write-object.d.ts +31 -0
  64. package/dist/write-object.js +228 -0
  65. package/dist/write-object.js.map +1 -0
  66. package/dist/write-omf.d.ts +47 -0
  67. package/dist/write-scene.d.ts +26 -0
  68. package/dist/write-scene.js +114 -0
  69. package/dist/write-scene.js.map +1 -0
  70. package/package.json +70 -4
@@ -0,0 +1,283 @@
1
+ import { XRAY_FX_PART } from "./motion.js";
2
+ class R {
3
+ constructor(u, p, end) {
4
+ this.u = u;
5
+ this.p = p;
6
+ this.end = end;
7
+ this.dv = new DataView(u.buffer, u.byteOffset, u.byteLength);
8
+ }
9
+ u;
10
+ p;
11
+ end;
12
+ dv;
13
+ need(n) {
14
+ if (this.p + n > this.end) throw new Error("Анимация SDK обрезана");
15
+ }
16
+ u8() {
17
+ this.need(1);
18
+ return this.u[this.p++];
19
+ }
20
+ u16() {
21
+ this.need(2);
22
+ const v = this.dv.getUint16(this.p, true);
23
+ this.p += 2;
24
+ return v;
25
+ }
26
+ u32() {
27
+ this.need(4);
28
+ const v = this.dv.getUint32(this.p, true);
29
+ this.p += 4;
30
+ return v;
31
+ }
32
+ f32() {
33
+ this.need(4);
34
+ const v = this.dv.getFloat32(this.p, true);
35
+ this.p += 4;
36
+ return v;
37
+ }
38
+ q16() {
39
+ return -32 + this.u16() * 64 / 65535;
40
+ }
41
+ sz() {
42
+ const e = this.u.indexOf(0, this.p);
43
+ if (e < 0 || e >= this.end) throw new Error("Анимация SDK: строка не закрыта");
44
+ const s = new TextDecoder("latin1").decode(this.u.subarray(this.p, e));
45
+ this.p = e + 1;
46
+ return s;
47
+ }
48
+ line() {
49
+ while (this.p < this.end && this.u[this.p] !== 10) this.p++;
50
+ this.p++;
51
+ }
52
+ }
53
+ function readEnvelope(r) {
54
+ const behavior = [r.u8(), r.u8()], n = r.u16(), keys = [];
55
+ for (let i = 0; i < n; i++) {
56
+ const value = r.f32(), time = r.f32(), shape = r.u8();
57
+ const k = { value, time, shape, tension: 0, continuity: 0, bias: 0, param: [0, 0, 0, 0] };
58
+ if (shape !== 4) {
59
+ k.tension = r.q16();
60
+ k.continuity = r.q16();
61
+ k.bias = r.q16();
62
+ k.param = [r.q16(), r.q16(), r.q16(), r.q16()];
63
+ }
64
+ keys.push(k);
65
+ }
66
+ return { behavior, keys };
67
+ }
68
+ function readSMotion(r) {
69
+ const name = r.sz(), frameStart = r.u32(), frameEnd = r.u32(), fps = r.f32(), version = r.u16();
70
+ if (version < 6) throw new Error(`Анимация SDK версии ${version} не поддерживается (нужна 6–7)`);
71
+ const flags = r.u8(), boneOrPart = r.u16(), speed = r.f32();
72
+ r.f32();
73
+ r.f32();
74
+ r.f32();
75
+ const nb = r.u16(), bones = [];
76
+ for (let b = 0; b < nb; b++) {
77
+ const bn = r.sz(), bf = r.u8(), envs = [];
78
+ for (let c = 0; c < 6; c++) envs.push(readEnvelope(r));
79
+ bones.push({ name: bn, flags: bf, envs });
80
+ }
81
+ if (version >= 7) {
82
+ const nm = r.u32();
83
+ for (let i = 0; i < nm; i++) {
84
+ r.line();
85
+ const k = r.u32();
86
+ r.p += k * 8;
87
+ }
88
+ }
89
+ return { name, frameStart, frameEnd, fps, version, flags, boneOrPart, speed, bones };
90
+ }
91
+ function parseSMotions(u) {
92
+ const r = new R(u, 0, u.length), n = r.u32(), out = [];
93
+ for (let i = 0; i < n; i++) out.push(readSMotion(r));
94
+ return out;
95
+ }
96
+ function parseSkl(u) {
97
+ const dv = new DataView(u.buffer, u.byteOffset, u.byteLength);
98
+ if (u.length < 8 || dv.getUint32(0, true) !== 4608) throw new Error("Не .skl (нет чанка 0x1200)");
99
+ return readSMotion(new R(u, 8, Math.min(u.length, 8 + dv.getUint32(4, true))));
100
+ }
101
+ function outgoing(keys, i) {
102
+ const k0 = keys[i], k1 = keys[i + 1], prev = keys[i - 1];
103
+ switch (k0.shape) {
104
+ case 0: {
105
+ const a = (1 - k0.tension) * (1 + k0.continuity) * (1 + k0.bias), b = (1 - k0.tension) * (1 - k0.continuity) * (1 - k0.bias), d = k1.value - k0.value;
106
+ return prev ? (k1.time - k0.time) / (k1.time - prev.time) * (a * (k0.value - prev.value) + b * d) : b * d;
107
+ }
108
+ case 3: {
109
+ const d = k1.value - k0.value;
110
+ return prev ? (k1.time - k0.time) / (k1.time - prev.time) * (k0.value - prev.value + d) : d;
111
+ }
112
+ case 1:
113
+ case 2:
114
+ return prev ? k0.param[1] * ((k1.time - k0.time) / (k1.time - prev.time)) : k0.param[1];
115
+ case 5: {
116
+ const o = k0.param[3] * (k1.time - k0.time);
117
+ return Math.abs(k0.param[2]) > 1e-5 ? o / k0.param[2] : o * 1e5;
118
+ }
119
+ default:
120
+ return 0;
121
+ }
122
+ }
123
+ function incoming(keys, i) {
124
+ const k0 = keys[i - 1], k1 = keys[i], next = keys[i + 1];
125
+ switch (k1.shape) {
126
+ case 0: {
127
+ const a = (1 - k1.tension) * (1 - k1.continuity) * (1 + k1.bias), b = (1 - k1.tension) * (1 + k1.continuity) * (1 - k1.bias), d = k1.value - k0.value;
128
+ return next ? (k1.time - k0.time) / (next.time - k0.time) * (b * (next.value - k1.value) + a * d) : a * d;
129
+ }
130
+ case 3: {
131
+ const d = k1.value - k0.value;
132
+ return next ? (k1.time - k0.time) / (next.time - k0.time) * (next.value - k1.value + d) : d;
133
+ }
134
+ case 1:
135
+ case 2:
136
+ return next ? k1.param[0] * ((k1.time - k0.time) / (next.time - k0.time)) : k1.param[0];
137
+ case 5: {
138
+ const o = k1.param[1] * (k1.time - k0.time);
139
+ return Math.abs(k1.param[0]) > 1e-5 ? o / k1.param[0] : o * 1e5;
140
+ }
141
+ default:
142
+ return 0;
143
+ }
144
+ }
145
+ function bez2(k0, k1, time) {
146
+ const x0 = k0.time, x1 = k0.time + (k0.shape === 5 ? k0.param[2] : (k1.time - k0.time) / 3), x3 = k1.time, x2 = k1.time + (k1.shape === 5 ? k1.param[0] : -(k1.time - k0.time) / 3);
147
+ const y0 = k0.value, y1 = k0.value + (k0.shape === 5 ? k0.param[3] : outgoing([k0, k1], 0) / 3), y3 = k1.value, y2 = k1.value + (k1.shape === 5 ? k1.param[1] : -incoming([k0, k1], 1) / 3);
148
+ const cub = (a, b, c, d, t2) => {
149
+ const s = 1 - t2;
150
+ return s * s * s * a + 3 * s * s * t2 * b + 3 * s * t2 * t2 * c + t2 * t2 * t2 * d;
151
+ };
152
+ let lo = 0, hi = 1, t = 0.5;
153
+ for (let i = 0; i < 30; i++) {
154
+ t = (lo + hi) / 2;
155
+ if (cub(x0, x1, x2, x3, t) < time) lo = t;
156
+ else hi = t;
157
+ }
158
+ return cub(y0, y1, y2, y3, t);
159
+ }
160
+ function evalEnvelope(env, time) {
161
+ const K = env.keys;
162
+ if (!K.length) return 0;
163
+ if (K.length === 1) return K[0].value;
164
+ const s = K[0], e = K[K.length - 1];
165
+ if (time < s.time) {
166
+ if (env.behavior[0] === 0) return 0;
167
+ if (env.behavior[0] === 2 && e.time > s.time) time = s.time + ((time - s.time) % (e.time - s.time) + (e.time - s.time)) % (e.time - s.time);
168
+ else return s.value;
169
+ } else if (time > e.time) {
170
+ if (env.behavior[1] === 0) return 0;
171
+ if (env.behavior[1] === 2 && e.time > s.time) time = s.time + (time - s.time) % (e.time - s.time);
172
+ else return e.value;
173
+ }
174
+ let k = 0;
175
+ while (k + 2 < K.length && time > K[k + 1].time) k++;
176
+ const k0 = K[k], k1 = K[k + 1];
177
+ if (time <= k0.time) return k0.value;
178
+ if (time >= k1.time) return k1.value;
179
+ const t = (time - k0.time) / (k1.time - k0.time);
180
+ switch (k1.shape) {
181
+ case 0:
182
+ case 1:
183
+ case 2: {
184
+ const out = outgoing(K, k), inn = incoming(K, k + 1), t2 = t * t, t3 = t2 * t;
185
+ const h2 = 3 * t2 - 2 * t3, h1 = 1 - h2, h4 = t3 - t2, h3 = h4 - t2 + t;
186
+ return h1 * k0.value + h2 * k1.value + h3 * out + h4 * inn;
187
+ }
188
+ case 5:
189
+ return bez2(k0, k1, time);
190
+ case 3:
191
+ return k0.value + t * (k1.value - k0.value);
192
+ case 4:
193
+ return k0.value;
194
+ default:
195
+ return 0;
196
+ }
197
+ }
198
+ function quatOfRows(o) {
199
+ const tr = o[0] + o[5] + o[10];
200
+ let x, y, z, w;
201
+ if (tr > 0) {
202
+ const s = Math.sqrt(tr + 1) * 2;
203
+ w = s / 4;
204
+ x = (o[9] - o[6]) / s;
205
+ y = (o[2] - o[8]) / s;
206
+ z = (o[4] - o[1]) / s;
207
+ } else if (o[0] > o[5] && o[0] > o[10]) {
208
+ const s = Math.sqrt(1 + o[0] - o[5] - o[10]) * 2;
209
+ x = s / 4;
210
+ w = (o[9] - o[6]) / s;
211
+ y = (o[1] + o[4]) / s;
212
+ z = (o[2] + o[8]) / s;
213
+ } else if (o[5] > o[10]) {
214
+ const s = Math.sqrt(1 + o[5] - o[0] - o[10]) * 2;
215
+ y = s / 4;
216
+ w = (o[2] - o[8]) / s;
217
+ x = (o[1] + o[4]) / s;
218
+ z = (o[6] + o[9]) / s;
219
+ } else {
220
+ const s = Math.sqrt(1 + o[10] - o[0] - o[5]) * 2;
221
+ z = s / 4;
222
+ w = (o[4] - o[1]) / s;
223
+ x = (o[2] + o[8]) / s;
224
+ y = (o[6] + o[9]) / s;
225
+ }
226
+ return [x, y, z, w];
227
+ }
228
+ function smotionToXray(sm, boneNames, local, parts = []) {
229
+ const frames = Math.max(1, sm.frameEnd - sm.frameStart), fps = sm.fps > 0 ? sm.fps : 30;
230
+ const idx = new Map(boneNames.map((n, i) => [n.toLowerCase(), i]));
231
+ const tracks = [], M = new Float32Array(16);
232
+ for (const b of sm.bones) {
233
+ const bi = idx.get(b.name.toLowerCase());
234
+ if (bi === void 0) continue;
235
+ const rot = new Int16Array(frames * 4), trans = new Float32Array(frames * 3);
236
+ let px = 0, py = 0, pz = 0, pw = 1;
237
+ for (let f = 0; f < frames; f++) {
238
+ const t = (sm.frameStart + f) / fps, E = b.envs;
239
+ const T = [evalEnvelope(E[0], t), evalEnvelope(E[1], t), evalEnvelope(E[2], t)];
240
+ local([evalEnvelope(E[4], t), evalEnvelope(E[3], t), evalEnvelope(E[5], t)], T, M);
241
+ let [x, y, z, w] = quatOfRows(M);
242
+ if (x * px + y * py + z * pz + w * pw < 0) {
243
+ x = -x;
244
+ y = -y;
245
+ z = -z;
246
+ w = -w;
247
+ }
248
+ [px, py, pz, pw] = [x, y, z, w];
249
+ rot[f * 4] = Math.round(x * 32767);
250
+ rot[f * 4 + 1] = Math.round(y * 32767);
251
+ rot[f * 4 + 2] = Math.round(z * 32767);
252
+ rot[f * 4 + 3] = Math.round(w * 32767);
253
+ trans.set(T, f * 3);
254
+ }
255
+ tracks[bi] = { rot, trans };
256
+ }
257
+ const m = { kind: "xray", name: sm.name, frames, duration: frames / fps, tracks };
258
+ if (sm.flags & 1) {
259
+ const t = tracks[sm.boneOrPart];
260
+ if (t) {
261
+ m.tracks = [];
262
+ m.tracks[sm.boneOrPart] = t;
263
+ m.part = XRAY_FX_PART;
264
+ }
265
+ } else if (sm.boneOrPart !== 65535 && parts.length > 1 && parts[sm.boneOrPart]) {
266
+ const p = parts[sm.boneOrPart], keep = [];
267
+ for (const n of p.bones) {
268
+ const i = idx.get(n.toLowerCase());
269
+ if (i !== void 0 && tracks[i]) keep[i] = tracks[i];
270
+ }
271
+ m.tracks = keep;
272
+ m.part = p.name;
273
+ }
274
+ return m;
275
+ }
276
+ export {
277
+ evalEnvelope,
278
+ parseSMotions,
279
+ parseSkl,
280
+ quatOfRows,
281
+ smotionToXray
282
+ };
283
+ //# sourceMappingURL=smotion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smotion.js","sources":["../src/smotion.ts"],"sourcesContent":["// Анимации X-Ray SDK (ActorEditor): .skl (чанк 0x1200 с одной анимацией), .skls (u32 число + анимации подряд),\n// они же внутри .object (чанк 0x0916). Анимация (CSMotion, версии 6–7): имя, u32 первый и последний кадр, f32 FPS,\n// u16 версия, u8 флаги (1 — эффект на одну кость), u16 кость/часть (0xFFFF — всё тело), f32 скорость, наплыв, спад, сила,\n// u16 число костей; кость: имя, u8 флаги, 6 огибающих (позиция X, Y, Z; повороты H, P, B). Огибающая (как в LightWave):\n// u8×2 поведение до/после, u16 ключей; ключ: f32 значение, f32 время (с), u8 форма (0 TCB, 1 Эрмит, 2 Безье, 3 линия,\n// 4 ступень, 5 Безье-2), кроме ступени — 7 чисел u16 в [−32, 32]: натяжение, непрерывность, смещение, параметры ×4.\n// С версии 7 — метки (имя до «\\r\\n», u32 число, интервалы f32×2). Поза кости: Fmatrix::setXYZi(P, H, B) + позиция.\nimport { XRAY_FX_PART } from './motion';\nimport type { XrayMotion, XrayTrack } from './motion';\n\nexport interface EnvKey { value: number; time: number; shape: number; tension: number; continuity: number; bias: number; param: number[] }\nexport interface Envelope { behavior: [number, number]; keys: EnvKey[] }\nexport interface SMotion {\n name: string; frameStart: number; frameEnd: number; fps: number; version: number;\n flags: number; boneOrPart: number; speed: number;\n bones: Array<{ name: string; flags: number; envs: Envelope[] }>;\n}\n\nclass R {\n readonly dv: DataView;\n constructor(readonly u: Uint8Array, public p: number, readonly end: number) { this.dv = new DataView(u.buffer, u.byteOffset, u.byteLength); }\n need(n: number) { if (this.p + n > this.end) throw new Error('Анимация SDK обрезана'); }\n u8() { this.need(1); return this.u[this.p++]!; }\n u16() { this.need(2); const v = this.dv.getUint16(this.p, true); this.p += 2; return v; }\n u32() { this.need(4); const v = this.dv.getUint32(this.p, true); this.p += 4; return v; }\n f32() { this.need(4); const v = this.dv.getFloat32(this.p, true); this.p += 4; return v; }\n q16() { return -32 + (this.u16() * 64) / 65535; }\n sz() { const e = this.u.indexOf(0, this.p); if (e < 0 || e >= this.end) throw new Error('Анимация SDK: строка не закрыта'); const s = new TextDecoder('latin1').decode(this.u.subarray(this.p, e)); this.p = e + 1; return s; }\n line() { while (this.p < this.end && this.u[this.p] !== 10) this.p++; this.p++; }\n}\n\nfunction readEnvelope(r: R): Envelope {\n const behavior: [number, number] = [r.u8(), r.u8()], n = r.u16(), keys: EnvKey[] = [];\n for (let i = 0; i < n; i++) {\n const value = r.f32(), time = r.f32(), shape = r.u8();\n const k: EnvKey = { value, time, shape, tension: 0, continuity: 0, bias: 0, param: [0, 0, 0, 0] };\n if (shape !== 4) { k.tension = r.q16(); k.continuity = r.q16(); k.bias = r.q16(); k.param = [r.q16(), r.q16(), r.q16(), r.q16()]; }\n keys.push(k);\n }\n return { behavior, keys };\n}\n\nfunction readSMotion(r: R): SMotion {\n const name = r.sz(), frameStart = r.u32(), frameEnd = r.u32(), fps = r.f32(), version = r.u16();\n if (version < 6) throw new Error(`Анимация SDK версии ${version} не поддерживается (нужна 6–7)`);\n const flags = r.u8(), boneOrPart = r.u16(), speed = r.f32();\n r.f32(); r.f32(); r.f32(); // наплыв, спад, сила\n const nb = r.u16(), bones: SMotion['bones'] = [];\n for (let b = 0; b < nb; b++) {\n const bn = r.sz(), bf = r.u8(), envs: Envelope[] = [];\n for (let c = 0; c < 6; c++) envs.push(readEnvelope(r));\n bones.push({ name: bn, flags: bf, envs });\n }\n if (version >= 7) { const nm = r.u32(); for (let i = 0; i < nm; i++) { r.line(); const k = r.u32(); r.p += k * 8; } }\n return { name, frameStart, frameEnd, fps, version, flags, boneOrPart, speed, bones };\n}\n\n/** Список анимаций: u32 число + анимации (.skls, чанк 0x0916 .object). */\nexport function parseSMotions(u: Uint8Array): SMotion[] {\n const r = new R(u, 0, u.length), n = r.u32(), out: SMotion[] = [];\n for (let i = 0; i < n; i++) out.push(readSMotion(r));\n return out;\n}\n\n/** .skl: чанк 0x1200 с одной анимацией. */\nexport function parseSkl(u: Uint8Array): SMotion {\n const dv = new DataView(u.buffer, u.byteOffset, u.byteLength);\n if (u.length < 8 || dv.getUint32(0, true) !== 0x1200) throw new Error('Не .skl (нет чанка 0x1200)');\n return readSMotion(new R(u, 8, Math.min(u.length, 8 + dv.getUint32(4, true))));\n}\n\n// ---- огибающие (evalEnvelope из LightWave SDK, как в xrCore/envelope.cpp) ----\nfunction outgoing(keys: EnvKey[], i: number): number {\n const k0 = keys[i]!, k1 = keys[i + 1]!, prev = keys[i - 1];\n switch (k0.shape) {\n case 0: {\n const a = (1 - k0.tension) * (1 + k0.continuity) * (1 + k0.bias), b = (1 - k0.tension) * (1 - k0.continuity) * (1 - k0.bias), d = k1.value - k0.value;\n return prev ? ((k1.time - k0.time) / (k1.time - prev.time)) * (a * (k0.value - prev.value) + b * d) : b * d;\n }\n case 3: { const d = k1.value - k0.value; return prev ? ((k1.time - k0.time) / (k1.time - prev.time)) * (k0.value - prev.value + d) : d; }\n case 1: case 2: return prev ? k0.param[1]! * ((k1.time - k0.time) / (k1.time - prev.time)) : k0.param[1]!;\n case 5: { const o = k0.param[3]! * (k1.time - k0.time); return Math.abs(k0.param[2]!) > 1e-5 ? o / k0.param[2]! : o * 1e5; }\n default: return 0;\n }\n}\nfunction incoming(keys: EnvKey[], i: number): number {\n const k0 = keys[i - 1]!, k1 = keys[i]!, next = keys[i + 1];\n switch (k1.shape) {\n case 0: {\n const a = (1 - k1.tension) * (1 - k1.continuity) * (1 + k1.bias), b = (1 - k1.tension) * (1 + k1.continuity) * (1 - k1.bias), d = k1.value - k0.value;\n return next ? ((k1.time - k0.time) / (next.time - k0.time)) * (b * (next.value - k1.value) + a * d) : a * d;\n }\n case 3: { const d = k1.value - k0.value; return next ? ((k1.time - k0.time) / (next.time - k0.time)) * (next.value - k1.value + d) : d; }\n case 1: case 2: return next ? k1.param[0]! * ((k1.time - k0.time) / (next.time - k0.time)) : k1.param[0]!;\n case 5: { const o = k1.param[1]! * (k1.time - k0.time); return Math.abs(k1.param[0]!) > 1e-5 ? o / k1.param[0]! : o * 1e5; }\n default: return 0;\n }\n}\n/** Безье-2: кривая по времени — ищем параметр делением отрезка пополам. */\nfunction bez2(k0: EnvKey, k1: EnvKey, time: number): number {\n const x0 = k0.time, x1 = k0.time + (k0.shape === 5 ? k0.param[2]! : (k1.time - k0.time) / 3), x3 = k1.time, x2 = k1.time + (k1.shape === 5 ? k1.param[0]! : -(k1.time - k0.time) / 3);\n const y0 = k0.value, y1 = k0.value + (k0.shape === 5 ? k0.param[3]! : outgoing([k0, k1], 0) / 3), y3 = k1.value, y2 = k1.value + (k1.shape === 5 ? k1.param[1]! : -incoming([k0, k1], 1) / 3);\n const cub = (a: number, b: number, c: number, d: number, t: number) => { const s = 1 - t; return s * s * s * a + 3 * s * s * t * b + 3 * s * t * t * c + t * t * t * d; };\n let lo = 0, hi = 1, t = 0.5;\n for (let i = 0; i < 30; i++) { t = (lo + hi) / 2; if (cub(x0, x1, x2, x3, t) < time) lo = t; else hi = t; }\n return cub(y0, y1, y2, y3, t);\n}\n\nexport function evalEnvelope(env: Envelope, time: number): number {\n const K = env.keys;\n if (!K.length) return 0;\n if (K.length === 1) return K[0]!.value;\n const s = K[0]!, e = K[K.length - 1]!;\n if (time < s.time) {\n if (env.behavior[0] === 0) return 0;\n if (env.behavior[0] === 2 && e.time > s.time) time = s.time + ((((time - s.time) % (e.time - s.time)) + (e.time - s.time)) % (e.time - s.time));\n else return s.value;\n } else if (time > e.time) {\n if (env.behavior[1] === 0) return 0;\n if (env.behavior[1] === 2 && e.time > s.time) time = s.time + ((time - s.time) % (e.time - s.time));\n else return e.value;\n }\n let k = 0;\n while (k + 2 < K.length && time > K[k + 1]!.time) k++;\n const k0 = K[k]!, k1 = K[k + 1]!;\n if (time <= k0.time) return k0.value;\n if (time >= k1.time) return k1.value;\n const t = (time - k0.time) / (k1.time - k0.time);\n switch (k1.shape) {\n case 0: case 1: case 2: {\n const out = outgoing(K, k), inn = incoming(K, k + 1), t2 = t * t, t3 = t2 * t;\n const h2 = 3 * t2 - 2 * t3, h1 = 1 - h2, h4 = t3 - t2, h3 = h4 - t2 + t;\n return h1 * k0.value + h2 * k1.value + h3 * out + h4 * inn;\n }\n case 5: return bez2(k0, k1, time);\n case 3: return k0.value + t * (k1.value - k0.value);\n case 4: return k0.value;\n default: return 0;\n }\n}\n\n/** Кватернион из матрицы поворота (строки, как в quatLocal скелета X-Ray). */\nexport function quatOfRows(o: ArrayLike<number>): [number, number, number, number] {\n const tr = o[0]! + o[5]! + o[10]!;\n let x: number, y: number, z: number, w: number;\n if (tr > 0) { const s = Math.sqrt(tr + 1) * 2; w = s / 4; x = (o[9]! - o[6]!) / s; y = (o[2]! - o[8]!) / s; z = (o[4]! - o[1]!) / s; }\n else if (o[0]! > o[5]! && o[0]! > o[10]!) { const s = Math.sqrt(1 + o[0]! - o[5]! - o[10]!) * 2; x = s / 4; w = (o[9]! - o[6]!) / s; y = (o[1]! + o[4]!) / s; z = (o[2]! + o[8]!) / s; }\n else if (o[5]! > o[10]!) { const s = Math.sqrt(1 + o[5]! - o[0]! - o[10]!) * 2; y = s / 4; w = (o[2]! - o[8]!) / s; x = (o[1]! + o[4]!) / s; z = (o[6]! + o[9]!) / s; }\n else { const s = Math.sqrt(1 + o[10]! - o[0]! - o[5]!) * 2; z = s / 4; w = (o[4]! - o[1]!) / s; x = (o[2]! + o[8]!) / s; y = (o[6]! + o[9]!) / s; }\n return [x, y, z, w];\n}\n\n/**\n * Анимация SDK → треки X-Ray по костям модели (по именам): огибающие считаются на каждом кадре, поворот (setXYZi)\n * переводится в кватернион. local(rot, pos, out) — матрица кости по углам, как у bind-позы этого скелета.\n */\nexport function smotionToXray(sm: SMotion, boneNames: readonly string[], local: (rot: number[], pos: number[], o: Float32Array) => void, parts: Array<{ name: string; bones: string[] }> = []): XrayMotion {\n const frames = Math.max(1, sm.frameEnd - sm.frameStart), fps = sm.fps > 0 ? sm.fps : 30;\n const idx = new Map(boneNames.map((n, i) => [n.toLowerCase(), i]));\n const tracks: XrayTrack[] = [], M = new Float32Array(16);\n for (const b of sm.bones) {\n const bi = idx.get(b.name.toLowerCase());\n if (bi === undefined) continue;\n const rot = new Int16Array(frames * 4), trans = new Float32Array(frames * 3);\n let px = 0, py = 0, pz = 0, pw = 1;\n for (let f = 0; f < frames; f++) {\n const t = (sm.frameStart + f) / fps, E = b.envs;\n const T = [evalEnvelope(E[0]!, t), evalEnvelope(E[1]!, t), evalEnvelope(E[2]!, t)];\n local([evalEnvelope(E[4]!, t), evalEnvelope(E[3]!, t), evalEnvelope(E[5]!, t)], T, M);\n let [x, y, z, w] = quatOfRows(M);\n if (x * px + y * py + z * pz + w * pw < 0) { x = -x; y = -y; z = -z; w = -w; } // без скачков знака между кадрами\n [px, py, pz, pw] = [x, y, z, w];\n rot[f * 4] = Math.round(x * 32767); rot[f * 4 + 1] = Math.round(y * 32767); rot[f * 4 + 2] = Math.round(z * 32767); rot[f * 4 + 3] = Math.round(w * 32767);\n trans.set(T, f * 3);\n }\n tracks[bi] = { rot, trans };\n }\n const m: XrayMotion = { kind: 'xray', name: sm.name, frames, duration: frames / fps, tracks };\n // часть тела или кость-эффект — как у анимаций OGF\n if (sm.flags & 1) {\n const t = tracks[sm.boneOrPart];\n if (t) { m.tracks = []; m.tracks[sm.boneOrPart] = t; m.part = XRAY_FX_PART; }\n } else if (sm.boneOrPart !== 0xffff && parts.length > 1 && parts[sm.boneOrPart]) {\n const p = parts[sm.boneOrPart]!, keep: XrayTrack[] = [];\n for (const n of p.bones) { const i = idx.get(n.toLowerCase()); if (i !== undefined && tracks[i]) keep[i] = tracks[i]!; }\n m.tracks = keep; m.part = p.name;\n }\n return m;\n}\n"],"names":["t"],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ export interface SocKey {
2
+ tableSeed: number;
3
+ encryptSeed: number;
4
+ iterations: number;
5
+ }
6
+ /** Обратная таблица подстановки (alphabet_back) для зерна и числа перестановок. */
7
+ export declare function socAlphabetBack(tableSeed: number, iterations: number): Uint8Array;
8
+ export declare function socDecode(key: SocKey, src: Uint8Array, back?: Uint8Array<ArrayBufferLike>): Uint8Array;
9
+ /** Обратное к socDecode (для тестов: синтетические зашифрованные архивы). */
10
+ export declare function socEncode(key: SocKey, src: Uint8Array): Uint8Array;
11
+ /** 32-битные значения (с шагом 4) из неисполняемых разделов PE-файла: кандидаты в зёрна. */
12
+ export declare function peDataWords(exe: Uint8Array): Uint32Array;
13
+ /**
14
+ * Найти ключ в исполняемом файле игры. probe — зашифрованная таблица файлов (чанк 1) любого архива, check — проверка
15
+ * распакованной таблицы. Поиск: для каждого кандидата в зерно таблицы и числа перестановок известно, каким должен быть
16
+ * 4-й байт потока (старший байт размера таблицы = 0), — кандидаты в зерно потока отбираются по нему и проверяются целиком.
17
+ */
18
+ export declare function findSocKey(exe: Uint8Array, probe: Uint8Array, check: (table: Uint8Array) => boolean): SocKey | null;
@@ -0,0 +1,125 @@
1
+ import { lzhufDecompress } from "./lzhuf.js";
2
+ function random32(seed) {
3
+ let s = seed >>> 0;
4
+ return (range) => {
5
+ s = Math.imul(134775813, s) + 1 >>> 0;
6
+ return range === 256 ? s >>> 24 : Math.floor(s * range / 4294967296);
7
+ };
8
+ }
9
+ function socAlphabetBack(tableSeed, iterations) {
10
+ const a = new Uint8Array(256);
11
+ let s = tableSeed >>> 0;
12
+ for (let i = 0; i < 256; i++) a[i] = i;
13
+ for (let i = 0; i < iterations; i++) {
14
+ s = Math.imul(134775813, s) + 1 >>> 0;
15
+ const j = s >>> 24;
16
+ let k;
17
+ do {
18
+ s = Math.imul(134775813, s) + 1 >>> 0;
19
+ k = s >>> 24;
20
+ } while (j === k);
21
+ const t = a[j];
22
+ a[j] = a[k];
23
+ a[k] = t;
24
+ }
25
+ const b = new Uint8Array(256);
26
+ for (let i = 0; i < 256; i++) b[a[i]] = i;
27
+ return b;
28
+ }
29
+ function socDecode(key, src, back = socAlphabetBack(key.tableSeed, key.iterations)) {
30
+ const out = new Uint8Array(src.length), r = random32(key.encryptSeed);
31
+ for (let i = 0; i < src.length; i++) out[i] = back[src[i] ^ r(256) & 255];
32
+ return out;
33
+ }
34
+ function socEncode(key, src) {
35
+ const back = socAlphabetBack(key.tableSeed, key.iterations), a = new Uint8Array(256), r = random32(key.encryptSeed), out = new Uint8Array(src.length);
36
+ for (let i = 0; i < 256; i++) a[back[i]] = i;
37
+ for (let i = 0; i < src.length; i++) out[i] = a[src[i]] ^ r(256) & 255;
38
+ return out;
39
+ }
40
+ function peDataWords(exe) {
41
+ const dv = new DataView(exe.buffer, exe.byteOffset, exe.byteLength), out = /* @__PURE__ */ new Set();
42
+ if (exe.length < 64 || dv.getUint16(0, true) !== 23117) return new Uint32Array(0);
43
+ const pe = dv.getUint32(60, true);
44
+ if (pe + 24 > exe.length || dv.getUint32(pe, true) !== 17744) return new Uint32Array(0);
45
+ const ns = dv.getUint16(pe + 6, true), opt = dv.getUint16(pe + 20, true);
46
+ const order = Array.from({ length: ns }, (_, k) => k).sort((a, b) => {
47
+ const w = (k) => pe + 24 + opt + k * 40 + 40 <= exe.length && dv.getUint32(pe + 24 + opt + k * 40 + 36, true) & 2147483648 ? 0 : 1;
48
+ return w(a) - w(b);
49
+ });
50
+ for (const k of order) {
51
+ const o = pe + 24 + opt + k * 40;
52
+ if (o + 40 > exe.length) break;
53
+ const name = String.fromCharCode(...exe.subarray(o, o + 8)).replace(/\0+$/, ""), raw = dv.getUint32(o + 16, true), ptr = dv.getUint32(o + 20, true), ch = dv.getUint32(o + 36, true);
54
+ if (ch & 536870912 || !(ch & 64) || name === ".rsrc" || name === ".reloc") continue;
55
+ for (let p = ptr; p + 4 <= Math.min(exe.length, ptr + raw); p += 4) {
56
+ const v = dv.getUint32(p, true);
57
+ if (v > 65535) out.add(v);
58
+ }
59
+ }
60
+ return Uint32Array.from(out);
61
+ }
62
+ const ITERATIONS = [2048, 1024, 4096, 512];
63
+ function tryKey(key, back, probe, check) {
64
+ let st = key.encryptSeed >>> 0, size = 0;
65
+ for (let i = 0; i < 4; i++) {
66
+ st = Math.imul(134775813, st) + 1 >>> 0;
67
+ size += back[probe[i] ^ st >>> 24] * 2 ** (8 * i);
68
+ }
69
+ if (size < 16 || size > 32 * probe.length + 1024) return false;
70
+ try {
71
+ const pre = lzhufDecompress(socDecode(key, probe.subarray(0, Math.min(probe.length, 24)), back).subarray(4), Math.min(size, 16)), bs0 = pre[0] | pre[1] << 8;
72
+ if (bs0 < 16 || bs0 > 520 || pre.length > 14 && (pre[14] < 32 || pre[14] > 126)) return false;
73
+ const head = socDecode(key, probe.subarray(0, Math.min(probe.length, 512)), back);
74
+ const first = lzhufDecompress(head.subarray(4), Math.min(size, 600)), dv = new DataView(first.buffer);
75
+ let q = 0;
76
+ for (let rec = 0; rec < 2 && q + 2 <= first.length; rec++) {
77
+ const bs = dv.getUint16(q, true);
78
+ if (bs < 16 || bs > 520) return false;
79
+ for (let k = q + 14; k < Math.min(first.length, q + 2 + bs - 4); k++) if (first[k] < 32 || first[k] > 126) return false;
80
+ q += 2 + bs;
81
+ }
82
+ return check(lzhufDecompress(socDecode(key, probe, back)));
83
+ } catch {
84
+ return false;
85
+ }
86
+ }
87
+ function findSocKey(exe, probe, check) {
88
+ if (probe.length < 8) return null;
89
+ const words = peDataWords(exe);
90
+ const byK3 = /* @__PURE__ */ new Map();
91
+ for (const w of words) {
92
+ const r = random32(w);
93
+ r(256);
94
+ r(256);
95
+ r(256);
96
+ const k3 = r(256) & 255;
97
+ let l = byK3.get(k3);
98
+ if (!l) byK3.set(k3, l = []);
99
+ l.push(w);
100
+ }
101
+ for (const iterations of ITERATIONS) {
102
+ for (const tableSeed of words) {
103
+ const back = socAlphabetBack(tableSeed, iterations);
104
+ let a0 = 0;
105
+ for (let i = 0; i < 256; i++) if (back[i] === 0) {
106
+ a0 = i;
107
+ break;
108
+ }
109
+ for (const encryptSeed of byK3.get(probe[3] ^ a0) ?? []) {
110
+ if (encryptSeed === tableSeed) continue;
111
+ const key = { tableSeed, encryptSeed, iterations };
112
+ if (tryKey(key, back, probe, check)) return key;
113
+ }
114
+ }
115
+ }
116
+ return null;
117
+ }
118
+ export {
119
+ findSocKey,
120
+ peDataWords,
121
+ socAlphabetBack,
122
+ socDecode,
123
+ socEncode
124
+ };
125
+ //# sourceMappingURL=soc-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"soc-key.js","sources":["../src/soc-key.ts"],"sourcesContent":["// Таблицы файлов архивов «Тени Чернобыля» (gamedata.db*) зашифрованы простым шифром движка (trivial_encryptor xrCore):\n// подстановка байта по таблице 256 + XOR с потоком генератора random32 (seed = 0x08088405·seed + 1, значение — старшие\n// биты произведения на диапазон). Таблица подстановки строится перестановками от своего зерна, поток — от другого.\n// Ключи (два зерна) в просмотрщик не встроены: они ищутся в исполняемом файле игры пользователя (XR_3DA.exe) —\n// перебираются 32-битные значения его разделов данных, а верный ключ узнаётся по тому, что таблица файлов архива после\n// расшифровки распаковывается LZHUF и разбирается без ошибок. Число перестановок — из небольшого набора степеней двойки.\nimport { lzhufDecompress } from './lzhuf';\n\nexport interface SocKey { tableSeed: number; encryptSeed: number; iterations: number }\n\n/** Генератор random32 движка. */\nfunction random32(seed: number): (range: number) => number {\n let s = seed >>> 0;\n return (range) => { s = (Math.imul(0x08088405, s) + 1) >>> 0; return range === 256 ? s >>> 24 : Math.floor((s * range) / 4294967296); };\n}\n\n/** Обратная таблица подстановки (alphabet_back) для зерна и числа перестановок. */\nexport function socAlphabetBack(tableSeed: number, iterations: number): Uint8Array {\n // random(256) = старший байт нового состояния (u64(seed)·256 >> 32)\n const a = new Uint8Array(256);\n let s = tableSeed >>> 0;\n for (let i = 0; i < 256; i++) a[i] = i;\n for (let i = 0; i < iterations; i++) {\n s = (Math.imul(0x08088405, s) + 1) >>> 0;\n const j = s >>> 24;\n let k: number;\n do { s = (Math.imul(0x08088405, s) + 1) >>> 0; k = s >>> 24; } while (j === k);\n const t = a[j]!; a[j] = a[k]!; a[k] = t;\n }\n const b = new Uint8Array(256);\n for (let i = 0; i < 256; i++) b[a[i]!] = i;\n return b;\n}\n\nexport function socDecode(key: SocKey, src: Uint8Array, back = socAlphabetBack(key.tableSeed, key.iterations)): Uint8Array {\n const out = new Uint8Array(src.length), r = random32(key.encryptSeed);\n for (let i = 0; i < src.length; i++) out[i] = back[src[i]! ^ (r(256) & 255)]!;\n return out;\n}\n\n/** Обратное к socDecode (для тестов: синтетические зашифрованные архивы). */\nexport function socEncode(key: SocKey, src: Uint8Array): Uint8Array {\n const back = socAlphabetBack(key.tableSeed, key.iterations), a = new Uint8Array(256), r = random32(key.encryptSeed), out = new Uint8Array(src.length);\n for (let i = 0; i < 256; i++) a[back[i]!] = i;\n for (let i = 0; i < src.length; i++) out[i] = a[src[i]!]! ^ (r(256) & 255);\n return out;\n}\n\n/** 32-битные значения (с шагом 4) из неисполняемых разделов PE-файла: кандидаты в зёрна. */\nexport function peDataWords(exe: Uint8Array): Uint32Array {\n const dv = new DataView(exe.buffer, exe.byteOffset, exe.byteLength), out = new Set<number>();\n if (exe.length < 0x40 || dv.getUint16(0, true) !== 0x5a4d) return new Uint32Array(0);\n const pe = dv.getUint32(0x3c, true);\n if (pe + 24 > exe.length || dv.getUint32(pe, true) !== 0x4550) return new Uint32Array(0);\n const ns = dv.getUint16(pe + 6, true), opt = dv.getUint16(pe + 20, true);\n // сначала изменяемые данные (.data — там глобальные объекты), затем константы (.rdata)\n const order = Array.from({ length: ns }, (_, k) => k).sort((a, b) => {\n const w = (k: number) => (pe + 24 + opt + k * 40 + 40 <= exe.length && dv.getUint32(pe + 24 + opt + k * 40 + 36, true) & 0x80000000 ? 0 : 1);\n return w(a) - w(b);\n });\n for (const k of order) {\n const o = pe + 24 + opt + k * 40;\n if (o + 40 > exe.length) break;\n const name = String.fromCharCode(...exe.subarray(o, o + 8)).replace(/\\0+$/, ''), raw = dv.getUint32(o + 16, true), ptr = dv.getUint32(o + 20, true), ch = dv.getUint32(o + 36, true);\n // данные: инициализированные (0x40), не код (0x20000000 — исполняемый); ресурсы и релокации пропускаются\n if (ch & 0x20000000 || !(ch & 0x40) || name === '.rsrc' || name === '.reloc') continue;\n for (let p = ptr; p + 4 <= Math.min(exe.length, ptr + raw); p += 4) { const v = dv.getUint32(p, true); if (v > 0xffff) out.add(v); }\n }\n return Uint32Array.from(out);\n}\n\nconst ITERATIONS = [2048, 1024, 4096, 512];\n\n/**\n * Проверка ключа: сначала дёшево — первые байты (размер правдоподобен, первая запись таблицы: длина записи и начало имени),\n * затем целиком (check — разбор распакованной таблицы и границы записей).\n */\nfunction tryKey(key: SocKey, back: Uint8Array, probe: Uint8Array, check: (table: Uint8Array) => boolean): boolean {\n // размер (первые 4 байта) — без выделения памяти\n let st = key.encryptSeed >>> 0, size = 0;\n for (let i = 0; i < 4; i++) { st = (Math.imul(0x08088405, st) + 1) >>> 0; size += back[probe[i]! ^ (st >>> 24)]! * 2 ** (8 * i); }\n if (size < 16 || size > 32 * probe.length + 1024) return false;\n try {\n // длина первой записи — по первым 16 распакованным байтам\n const pre = lzhufDecompress(socDecode(key, probe.subarray(0, Math.min(probe.length, 24)), back).subarray(4), Math.min(size, 16)), bs0 = pre[0]! | (pre[1]! << 8);\n if (bs0 < 16 || bs0 > 520 || (pre.length > 14 && (pre[14]! < 0x20 || pre[14]! > 0x7e))) return false;\n const head = socDecode(key, probe.subarray(0, Math.min(probe.length, 512)), back);\n // первые записи таблицы: u16 длина (16..520), имя из печатных символов; вторая запись — тоже правдоподобной длины\n const first = lzhufDecompress(head.subarray(4), Math.min(size, 600)), dv = new DataView(first.buffer);\n let q = 0;\n for (let rec = 0; rec < 2 && q + 2 <= first.length; rec++) {\n const bs = dv.getUint16(q, true);\n if (bs < 16 || bs > 520) return false;\n for (let k = q + 14; k < Math.min(first.length, q + 2 + bs - 4); k++) if (first[k]! < 0x20 || first[k]! > 0x7e) return false;\n q += 2 + bs;\n }\n return check(lzhufDecompress(socDecode(key, probe, back)));\n } catch { return false; }\n}\n\n/**\n * Найти ключ в исполняемом файле игры. probe — зашифрованная таблица файлов (чанк 1) любого архива, check — проверка\n * распакованной таблицы. Поиск: для каждого кандидата в зерно таблицы и числа перестановок известно, каким должен быть\n * 4-й байт потока (старший байт размера таблицы = 0), — кандидаты в зерно потока отбираются по нему и проверяются целиком.\n */\nexport function findSocKey(exe: Uint8Array, probe: Uint8Array, check: (table: Uint8Array) => boolean): SocKey | null {\n if (probe.length < 8) return null;\n const words = peDataWords(exe);\n // первые 4 байта потока для каждого кандидата в зерно потока: по 4-му — индекс\n const byK3 = new Map<number, number[]>();\n for (const w of words) {\n const r = random32(w);\n r(256); r(256); r(256);\n const k3 = r(256) & 255;\n let l = byK3.get(k3);\n if (!l) byK3.set(k3, (l = []));\n l.push(w);\n }\n for (const iterations of ITERATIONS) {\n for (const tableSeed of words) {\n const back = socAlphabetBack(tableSeed, iterations);\n // back[c3 ^ k3] = 0 → k3 = c3 ^ alphabet[0]\n let a0 = 0;\n for (let i = 0; i < 256; i++) if (back[i] === 0) { a0 = i; break; }\n for (const encryptSeed of byK3.get(probe[3]! ^ a0) ?? []) {\n if (encryptSeed === tableSeed) continue;\n const key = { tableSeed, encryptSeed, iterations };\n if (tryKey(key, back, probe, check)) return key;\n }\n }\n }\n return null;\n}\n"],"names":[],"mappings":"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"}
package/dist/thm.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ export declare const THM_FLAG: {
2
+ readonly MIPMAPS: 1;
3
+ readonly DITHER: number;
4
+ readonly DIFFUSE_DETAIL: number;
5
+ readonly IMPLICIT_LIGHTED: number;
6
+ readonly HAS_ALPHA: number;
7
+ readonly BUMP_DETAIL: number;
8
+ };
9
+ /** Форматы сжатия, которыми SDK пересохраняет текстуру: DXT1, DXT1 с альфой, DXT3, DXT5, без сжатия. */
10
+ export declare const THM_FMT: {
11
+ readonly DXT1: 0;
12
+ readonly ADXT1: 1;
13
+ readonly DXT3: 2;
14
+ readonly DXT5: 3;
15
+ readonly RGBA: 6;
16
+ };
17
+ export interface ThmIn {
18
+ width: number;
19
+ height: number;
20
+ alpha?: boolean;
21
+ format?: number;
22
+ detail?: string;
23
+ detailScale?: number;
24
+ bump?: string;
25
+ }
26
+ export declare function writeThm(t: ThmIn): Uint8Array;
package/dist/thm.js ADDED
@@ -0,0 +1,69 @@
1
+ const THM_FLAG = { MIPMAPS: 1, DITHER: 1 << 8, DIFFUSE_DETAIL: 1 << 23, HAS_ALPHA: 1 << 25 };
2
+ const THM_FMT = { DXT1: 0, DXT5: 3 };
3
+ function writeThm(t) {
4
+ const parts = [];
5
+ const u8 = (v) => parts.push(v & 255);
6
+ const u16 = (v) => {
7
+ u8(v);
8
+ u8(v >> 8);
9
+ };
10
+ const u32 = (v) => {
11
+ u16(v & 65535);
12
+ u16(v >>> 16 & 65535);
13
+ };
14
+ const f32 = (v) => {
15
+ const b = new Uint8Array(new Float32Array([v]).buffer);
16
+ for (const x of b) u8(x);
17
+ };
18
+ const sz = (s) => {
19
+ for (let i = 0; i < s.length; i++) u8(s.charCodeAt(i));
20
+ u8(0);
21
+ };
22
+ const chunk = (id, body) => {
23
+ u32(id);
24
+ const at = parts.length;
25
+ u32(0);
26
+ body();
27
+ const n = parts.length - at - 4;
28
+ parts[at] = n & 255;
29
+ parts[at + 1] = n >> 8 & 255;
30
+ parts[at + 2] = n >> 16 & 255;
31
+ parts[at + 3] = n >>> 24 & 255;
32
+ };
33
+ const flags = THM_FLAG.MIPMAPS | (t.alpha ? THM_FLAG.HAS_ALPHA | THM_FLAG.DITHER : 0) | (t.detail ? THM_FLAG.DIFFUSE_DETAIL : 0);
34
+ chunk(2064, () => u16(18));
35
+ chunk(2067, () => u32(1));
36
+ chunk(2066, () => {
37
+ u32(t.format ?? (t.alpha ? THM_FMT.DXT5 : THM_FMT.DXT1));
38
+ u32(flags);
39
+ u32(0);
40
+ u32(0);
41
+ u32(0);
42
+ u32(0);
43
+ u32(t.width);
44
+ u32(t.height);
45
+ });
46
+ chunk(2068, () => u32(0));
47
+ chunk(2069, () => {
48
+ sz(t.detail ?? "");
49
+ f32(t.detailScale ?? 1);
50
+ });
51
+ chunk(2070, () => {
52
+ u32(1);
53
+ f32(0);
54
+ });
55
+ chunk(2071, () => {
56
+ f32(0.05);
57
+ u32(t.bump ? 2 : 1);
58
+ sz(t.bump ?? "");
59
+ });
60
+ chunk(2072, () => sz(""));
61
+ chunk(2073, () => u8(0));
62
+ return Uint8Array.from(parts);
63
+ }
64
+ export {
65
+ THM_FLAG,
66
+ THM_FMT,
67
+ writeThm
68
+ };
69
+ //# sourceMappingURL=thm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thm.js","sources":["../src/thm.ts"],"sourcesContent":["// Параметры текстуры X-Ray (.thm рядом с .dds в gamedata/textures): нужны SDK и компилятору xrLC.\n// Чанки: 0x810 версия, 0x813 тип (картинка), 0x812 формат/флаги/размер, 0x814 вид (обычная, бамп, ландшафт),\n// 0x815 детальная текстура, 0x816 материал, 0x817 бамп, 0x818 внешняя карта нормалей, 0x819 задержка затухания.\nexport const THM_FLAG = { MIPMAPS: 1, DITHER: 1 << 8, DIFFUSE_DETAIL: 1 << 23, IMPLICIT_LIGHTED: 1 << 24, HAS_ALPHA: 1 << 25, BUMP_DETAIL: 1 << 26 } as const;\n/** Форматы сжатия, которыми SDK пересохраняет текстуру: DXT1, DXT1 с альфой, DXT3, DXT5, без сжатия. */\nexport const THM_FMT = { DXT1: 0, ADXT1: 1, DXT3: 2, DXT5: 3, RGBA: 6 } as const;\n\nexport interface ThmIn { width: number; height: number; alpha?: boolean; format?: number; detail?: string; detailScale?: number; bump?: string }\n\nexport function writeThm(t: ThmIn): Uint8Array {\n const parts: number[] = [];\n const u8 = (v: number) => parts.push(v & 0xff);\n const u16 = (v: number) => { u8(v); u8(v >> 8); };\n const u32 = (v: number) => { u16(v & 0xffff); u16((v >>> 16) & 0xffff); };\n const f32 = (v: number) => { const b = new Uint8Array(new Float32Array([v]).buffer); for (const x of b) u8(x); };\n const sz = (s: string) => { for (let i = 0; i < s.length; i++) u8(s.charCodeAt(i)); u8(0); };\n const chunk = (id: number, body: () => void) => { u32(id); const at = parts.length; u32(0); body(); const n = parts.length - at - 4; parts[at] = n & 0xff; parts[at + 1] = (n >> 8) & 0xff; parts[at + 2] = (n >> 16) & 0xff; parts[at + 3] = (n >>> 24) & 0xff; };\n const flags = THM_FLAG.MIPMAPS | (t.alpha ? THM_FLAG.HAS_ALPHA | THM_FLAG.DITHER : 0) | (t.detail ? THM_FLAG.DIFFUSE_DETAIL : 0);\n chunk(0x810, () => u16(0x12));\n chunk(0x813, () => u32(1));\n chunk(0x812, () => { u32(t.format ?? (t.alpha ? THM_FMT.DXT5 : THM_FMT.DXT1)); u32(flags); u32(0); u32(0); u32(0); u32(0); u32(t.width); u32(t.height); });\n chunk(0x814, () => u32(0));\n chunk(0x815, () => { sz(t.detail ?? ''); f32(t.detailScale ?? 1); });\n chunk(0x816, () => { u32(1); f32(0); });\n chunk(0x817, () => { f32(0.05); u32(t.bump ? 2 : 1); sz(t.bump ?? ''); });\n chunk(0x818, () => sz(''));\n chunk(0x819, () => u8(0));\n return Uint8Array.from(parts);\n}\n"],"names":[],"mappings":"AAGO,MAAM,WAAW,EAAE,SAAS,GAAG,QAAQ,KAAK,GAAG,gBAAgB,KAAK,IAA+B,WAAW,KAAK,GAAyB;AAE5I,MAAM,UAAU,EAAE,MAAM,GAAsB,MAAM,EAAW;AAI/D,SAAS,SAAS,GAAsB;AAC7C,QAAM,QAAkB,CAAA;AACxB,QAAM,KAAK,CAAC,MAAc,MAAM,KAAK,IAAI,GAAI;AAC7C,QAAM,MAAM,CAAC,MAAc;AAAE,OAAG,CAAC;AAAG,OAAG,KAAK,CAAC;AAAA,EAAG;AAChD,QAAM,MAAM,CAAC,MAAc;AAAE,QAAI,IAAI,KAAM;AAAG,QAAK,MAAM,KAAM,KAAM;AAAA,EAAG;AACxE,QAAM,MAAM,CAAC,MAAc;AAAE,UAAM,IAAI,IAAI,WAAW,IAAI,aAAa,CAAC,CAAC,CAAC,EAAE,MAAM;AAAG,eAAW,KAAK,EAAG,IAAG,CAAC;AAAA,EAAG;AAC/G,QAAM,KAAK,CAAC,MAAc;AAAE,aAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,IAAK,IAAG,EAAE,WAAW,CAAC,CAAC;AAAG,OAAG,CAAC;AAAA,EAAG;AAC3F,QAAM,QAAQ,CAAC,IAAY,SAAqB;AAAE,QAAI,EAAE;AAAG,UAAM,KAAK,MAAM;AAAQ,QAAI,CAAC;AAAG,SAAA;AAAQ,UAAM,IAAI,MAAM,SAAS,KAAK;AAAG,UAAM,EAAE,IAAI,IAAI;AAAM,UAAM,KAAK,CAAC,IAAK,KAAK,IAAK;AAAM,UAAM,KAAK,CAAC,IAAK,KAAK,KAAM;AAAM,UAAM,KAAK,CAAC,IAAK,MAAM,KAAM;AAAA,EAAM;AACjQ,QAAM,QAAQ,SAAS,WAAW,EAAE,QAAQ,SAAS,YAAY,SAAS,SAAS,MAAM,EAAE,SAAS,SAAS,iBAAiB;AAC9H,QAAM,MAAO,MAAM,IAAI,EAAI,CAAC;AAC5B,QAAM,MAAO,MAAM,IAAI,CAAC,CAAC;AACzB,QAAM,MAAO,MAAM;AAAE,QAAI,EAAE,WAAW,EAAE,QAAQ,QAAQ,OAAO,QAAQ,KAAK;AAAG,QAAI,KAAK;AAAG,QAAI,CAAC;AAAG,QAAI,CAAC;AAAG,QAAI,CAAC;AAAG,QAAI,CAAC;AAAG,QAAI,EAAE,KAAK;AAAG,QAAI,EAAE,MAAM;AAAA,EAAG,CAAC;AACzJ,QAAM,MAAO,MAAM,IAAI,CAAC,CAAC;AACzB,QAAM,MAAO,MAAM;AAAE,OAAG,EAAE,UAAU,EAAE;AAAG,QAAI,EAAE,eAAe,CAAC;AAAA,EAAG,CAAC;AACnE,QAAM,MAAO,MAAM;AAAE,QAAI,CAAC;AAAG,QAAI,CAAC;AAAA,EAAG,CAAC;AACtC,QAAM,MAAO,MAAM;AAAE,QAAI,IAAI;AAAG,QAAI,EAAE,OAAO,IAAI,CAAC;AAAG,OAAG,EAAE,QAAQ,EAAE;AAAA,EAAG,CAAC;AACxE,QAAM,MAAO,MAAM,GAAG,EAAE,CAAC;AACzB,QAAM,MAAO,MAAM,GAAG,CAAC,CAAC;AACxB,SAAO,WAAW,KAAK,KAAK;AAC9B;"}
@@ -0,0 +1,31 @@
1
+ export interface ObjectSurfaceIn {
2
+ name: string;
3
+ /** Текстура без расширения относительно gamedata/textures, например «wall\\wall_stucco_05». */
4
+ texture: string;
5
+ /** Шейдер движка (eshader), по умолчанию «default». */
6
+ eshader?: string;
7
+ /** Шейдер компилятора (cshader), по умолчанию «default». */
8
+ cshader?: string;
9
+ /** Игровой материал, по умолчанию «default». */
10
+ gamemtl?: string;
11
+ twoSided?: boolean;
12
+ }
13
+ export interface ObjectMeshIn {
14
+ name: string;
15
+ pos: ArrayLike<number>;
16
+ /** Нормали вершин: по их разрывам находятся жёсткие рёбра. Нет — жёсткие рёбра по углу. */
17
+ nor?: ArrayLike<number> | null;
18
+ uv: ArrayLike<number>;
19
+ indices: ArrayLike<number>;
20
+ /** Номер поверхности для каждого треугольника или одна на весь меш. */
21
+ surface: number | ArrayLike<number>;
22
+ }
23
+ export interface ObjectIn {
24
+ meshes: ObjectMeshIn[];
25
+ surfaces: ObjectSurfaceIn[];
26
+ owner?: string;
27
+ dynamic?: boolean;
28
+ hardAngleDeg?: number;
29
+ }
30
+ /** Записать .object. Возвращает байты файла. */
31
+ export declare function writeObject(o: ObjectIn): Uint8Array;