@gmvjs/xray-v1 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +77 -2
- package/dist/db.d.ts +12 -0
- package/dist/db.js +134 -0
- package/dist/db.js.map +1 -0
- package/dist/details.d.ts +42 -0
- package/dist/details.js +210 -0
- package/dist/details.js.map +1 -0
- package/dist/export.d.ts +38 -0
- package/dist/export.js +241 -0
- package/dist/export.js.map +1 -0
- package/dist/games/cop.d.ts +7 -0
- package/dist/games/cop.js +11 -0
- package/dist/games/cop.js.map +1 -0
- package/dist/games/cs.d.ts +7 -0
- package/dist/games/cs.js +11 -0
- package/dist/games/cs.js.map +1 -0
- package/dist/games/sdk.d.ts +7 -0
- package/dist/games/sdk.js +10 -0
- package/dist/games/sdk.js.map +1 -0
- package/dist/games/soc.d.ts +9 -0
- package/dist/games/soc.js +13 -0
- package/dist/games/soc.js.map +1 -0
- package/dist/i18n.d.ts +2 -0
- package/dist/i18n.js +48 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +131 -0
- package/dist/index.js.map +1 -0
- package/dist/level.d.ts +39 -0
- package/dist/level.js +234 -0
- package/dist/level.js.map +1 -0
- package/dist/load.d.ts +5 -0
- package/dist/load.js +126 -0
- package/dist/load.js.map +1 -0
- package/dist/lzhuf.d.ts +2 -0
- package/dist/lzhuf.js +136 -0
- package/dist/lzhuf.js.map +1 -0
- package/dist/motion.d.ts +28 -0
- package/dist/motion.js +20 -0
- package/dist/motion.js.map +1 -0
- package/dist/object.d.ts +138 -0
- package/dist/object.js +361 -0
- package/dist/object.js.map +1 -0
- package/dist/ogf.d.ts +93 -0
- package/dist/ogf.js +418 -0
- package/dist/ogf.js.map +1 -0
- package/dist/scene.d.ts +28 -0
- package/dist/scene.js +141 -0
- package/dist/scene.js.map +1 -0
- package/dist/skeleton.d.ts +34 -0
- package/dist/skeleton.js +252 -0
- package/dist/skeleton.js.map +1 -0
- package/dist/smotion.d.ts +44 -0
- package/dist/smotion.js +283 -0
- package/dist/smotion.js.map +1 -0
- package/dist/soc-key.d.ts +18 -0
- package/dist/soc-key.js +125 -0
- package/dist/soc-key.js.map +1 -0
- package/dist/thm.d.ts +26 -0
- package/dist/thm.js +69 -0
- package/dist/thm.js.map +1 -0
- package/dist/write-object.d.ts +31 -0
- package/dist/write-object.js +228 -0
- package/dist/write-object.js.map +1 -0
- package/dist/write-omf.d.ts +47 -0
- package/dist/write-scene.d.ts +26 -0
- package/dist/write-scene.js +114 -0
- package/dist/write-scene.js.map +1 -0
- package/package.json +70 -4
package/dist/smotion.js
ADDED
|
@@ -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 animations (ActorEditor): .skl (chunk 0x1200 with one animation), .skls (u32 count + animations in sequence),\n// the same inside .object (chunk 0x0916). Animation (CSMotion, versions 6–7): name, u32 first and last frame, f32 FPS,\n// u16 version, u8 flags (1 = an effect on one bone), u16 bone/part (0xFFFF = the whole body), f32 speed, accrue, falloff, power,\n// u16 bone count; bone: name, u8 flags, 6 envelopes (position X, Y, Z; rotations H, P, B). Envelope (as in LightWave):\n// u8×2 pre/post behavior, u16 keys; key: f32 value, f32 time (s), u8 shape (0 TCB, 1 Hermite, 2 Bezier, 3 linear,\n// 4 stepped, 5 Bezier-2), except stepped, 7 u16 numbers in [−32, 32]: tension, continuity, bias, parameters ×4.\n// Since version 7, marks (a name up to \"\\r\\n\", u32 count, f32×2 intervals). Bone pose: Fmatrix::setXYZi(P, H, B) + position.\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(); // accrue, falloff, power\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/** Animation list: u32 count + animations (.skls, .object chunk 0x0916). */\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: chunk 0x1200 with one animation. */\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// ---- envelopes (evalEnvelope from the LightWave SDK, as in 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/** Bezier-2: a curve over time, the parameter is found by bisection. */\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/** Quaternion from a rotation matrix (rows, as in the X-Ray skeleton's quatLocal). */\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 animation → X-Ray tracks per model bone (by name): envelopes are evaluated at every frame, the rotation (setXYZi)\n * is converted to a quaternion. local(rot, pos, out) is the bone matrix from angles, as for this skeleton's bind pose.\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; } // no sign jumps between frames\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 // body part or effect bone, as for OGF animations\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
|
+
/** Inverse substitution table (alphabet_back) for a seed and permutation count. */
|
|
7
|
+
export declare function socAlphabetBack(tableSeed: number, iterations: number): Uint8Array;
|
|
8
|
+
export declare function socDecode(key: SocKey, src: Uint8Array, back?: Uint8Array<ArrayBufferLike>): Uint8Array;
|
|
9
|
+
/** The inverse of socDecode (for tests: synthetic encrypted archives). */
|
|
10
|
+
export declare function socEncode(key: SocKey, src: Uint8Array): Uint8Array;
|
|
11
|
+
/** 32-bit values (step 4) from the PE file's non-executable sections: seed candidates. */
|
|
12
|
+
export declare function peDataWords(exe: Uint8Array): Uint32Array;
|
|
13
|
+
/**
|
|
14
|
+
* Find the key in the game executable. probe is an encrypted file table (chunk 1) of any archive, check verifies
|
|
15
|
+
* the unpacked table. Search: for every table seed candidate and permutation count the expected 4th stream byte is known
|
|
16
|
+
* (the high byte of the table size = 0); stream seed candidates are filtered by it and then checked in full.
|
|
17
|
+
*/
|
|
18
|
+
export declare function findSocKey(exe: Uint8Array, probe: Uint8Array, check: (table: Uint8Array) => boolean): SocKey | null;
|
package/dist/soc-key.js
ADDED
|
@@ -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":["// The file tables of Shadow of Chernobyl archives (gamedata.db*) are encrypted with the engine's simple cipher (xrCore trivial_encryptor):\n// byte substitution via a 256-entry table + XOR with the random32 generator stream (seed = 0x08088405·seed + 1, the value is the high\n// bits of the product with the range). The substitution table is built by permutations from its own seed, the stream from another.\n// The keys (two seeds) are not built into the viewer: they are searched for in the user's game executable (XR_3DA.exe):\n// the 32-bit values of its data sections are tried, and the right key is recognized because the archive's file table, after\n// decryption, unpacks with LZHUF and parses without errors. The permutation count comes from a small set of powers of two.\nimport { lzhufDecompress } from './lzhuf';\n\nexport interface SocKey { tableSeed: number; encryptSeed: number; iterations: number }\n\n/** The engine's random32 generator. */\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/** Inverse substitution table (alphabet_back) for a seed and permutation count. */\nexport function socAlphabetBack(tableSeed: number, iterations: number): Uint8Array {\n // random(256) = the high byte of the new state (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/** The inverse of socDecode (for tests: synthetic encrypted archives). */\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-bit values (step 4) from the PE file's non-executable sections: seed candidates. */\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 // writable data first (.data holds the global objects), then constants (.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 // data: initialized (0x40), not code (0x20000000 = executable); resources and relocations are skipped\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 * Key check: cheap first, the first bytes (a plausible size, the first table record: record length and the start of the name),\n * then in full (check = parsing the unpacked table and the record bounds).\n */\nfunction tryKey(key: SocKey, back: Uint8Array, probe: Uint8Array, check: (table: Uint8Array) => boolean): boolean {\n // size (the first 4 bytes) without allocating memory\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 // the first record length from the first 16 unpacked bytes\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 // the first table records: u16 length (16..520), a name of printable characters; the second record also has a plausible length\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 * Find the key in the game executable. probe is an encrypted file table (chunk 1) of any archive, check verifies\n * the unpacked table. Search: for every table seed candidate and permutation count the expected 4th stream byte is known\n * (the high byte of the table size = 0); stream seed candidates are filtered by it and then checked in full.\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 // the first 4 stream bytes for every stream seed candidate: indexed by the 4th\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
|
+
/** Compression formats the SDK re-saves the texture with: DXT1, DXT1 with alpha, DXT3, DXT5, uncompressed. */
|
|
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
|
package/dist/thm.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thm.js","sources":["../src/thm.ts"],"sourcesContent":["// X-Ray texture parameters (.thm next to .dds in gamedata/textures): needed by the SDK and the xrLC compiler.\n// Chunks: 0x810 version, 0x813 type (image), 0x812 format/flags/size, 0x814 kind (regular, bump, terrain),\n// 0x815 detail texture, 0x816 material, 0x817 bump, 0x818 external normal map, 0x819 fade delay.\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/** Compression formats the SDK re-saves the texture with: DXT1, DXT1 with alpha, DXT3, DXT5, uncompressed. */\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
|
+
/** Texture without extension relative to gamedata/textures, for example "wall\\wall_stucco_05". */
|
|
4
|
+
texture: string;
|
|
5
|
+
/** Engine shader (eshader), "default" by default. */
|
|
6
|
+
eshader?: string;
|
|
7
|
+
/** Compiler shader (cshader), "default" by default. */
|
|
8
|
+
cshader?: string;
|
|
9
|
+
/** Game material, "default" by default. */
|
|
10
|
+
gamemtl?: string;
|
|
11
|
+
twoSided?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface ObjectMeshIn {
|
|
14
|
+
name: string;
|
|
15
|
+
pos: ArrayLike<number>;
|
|
16
|
+
/** Vertex normals: hard edges are found from their discontinuities. Without them, hard edges by angle. */
|
|
17
|
+
nor?: ArrayLike<number> | null;
|
|
18
|
+
uv: ArrayLike<number>;
|
|
19
|
+
indices: ArrayLike<number>;
|
|
20
|
+
/** Surface number for every triangle, or one for the whole mesh. */
|
|
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
|
+
/** Write a .object. Returns the file bytes. */
|
|
31
|
+
export declare function writeObject(o: ObjectIn): Uint8Array;
|