tjs2dec/
loader.rs

1use anyhow::{Context, Result, anyhow, bail};
2
3use crate::model::{ConstPools, Tjs2File, Tjs2Object, Variant};
4
5const FILE_TAG_LE: u32 =
6    ('T' as u32) | (('J' as u32) << 8) | (('S' as u32) << 16) | (('2' as u32) << 24);
7const VER_TAG_LE: u32 = ('1' as u32) | (('0' as u32) << 8) | (('0' as u32) << 16) | (0u32 << 24);
8
9const OBJ_TAG_LE: u32 =
10    ('O' as u32) | (('B' as u32) << 8) | (('J' as u32) << 16) | (('S' as u32) << 24);
11const DATA_TAG_LE: u32 =
12    ('D' as u32) | (('A' as u32) << 8) | (('T' as u32) << 16) | (('A' as u32) << 24);
13
14// Variant types (krkrz tTJSByteCodeLoader)
15const TYPE_VOID: i16 = 0;
16const TYPE_OBJECT: i16 = 1;
17const TYPE_INTER_OBJECT: i16 = 2;
18const TYPE_STRING: i16 = 3;
19const TYPE_OCTET: i16 = 4;
20const TYPE_REAL: i16 = 5;
21const TYPE_BYTE: i16 = 6;
22const TYPE_SHORT: i16 = 7;
23const TYPE_INTEGER: i16 = 8;
24const TYPE_LONG: i16 = 9;
25const TYPE_INTER_GENERATOR: i16 = 10;
26const TYPE_UNKNOWN: i16 = -1;
27
28pub fn load_tjs2_bytecode(buf: &[u8]) -> Result<Tjs2File> {
29    if buf.len() < 12 {
30        bail!("bytecode too small: {} bytes", buf.len());
31    }
32    let mut r = Reader::new(buf);
33
34    // File header: "TJS2100\0" (8 bytes) + filesize (u32)
35    let file_tag = r.read_u32_le().context("read file tag")?;
36    let ver_tag = r.read_u32_le().context("read version tag")?;
37    if file_tag != FILE_TAG_LE {
38        bail!(
39            "fourcc mismatch: expect {:?}, got {:?}",
40            b"TJS2",
41            u32_to_4cc(file_tag)
42        );
43    }
44    if ver_tag != VER_TAG_LE {
45        bail!(
46            "version mismatch: expect {:?}, got {:?}",
47            b"100\0",
48            u32_to_4cc(ver_tag)
49        );
50    }
51    let file_size = r.read_u32_le().context("read file size")? as usize;
52    if file_size != buf.len() {
53        bail!(
54            "file size mismatch: header={}, actual={}",
55            file_size,
56            buf.len()
57        );
58    }
59
60    // DATA chunk: tag + chunk_size (includes tag+size) + payload
61    let data_tag = r.read_u32_le().context("read DATA tag")?;
62    if data_tag != DATA_TAG_LE {
63        bail!(
64            "fourcc mismatch: expect {:?}, got {:?}",
65            b"DATA",
66            u32_to_4cc(data_tag)
67        );
68    }
69    let data_chunk_size = r.read_u32_le().context("read DATA chunk size")? as usize;
70    if data_chunk_size < 8 {
71        bail!("DATA chunk size too small: {}", data_chunk_size);
72    }
73    let data_payload_size = data_chunk_size - 8;
74    let data_start = r.pos();
75    let data_end = data_start
76        .checked_add(data_payload_size)
77        .ok_or_else(|| anyhow!("overflow computing DATA end"))?;
78    if data_end > buf.len() {
79        bail!(
80            "DATA payload out of range: end={}, file={}",
81            data_end,
82            buf.len()
83        );
84    }
85    let pools = read_data_area(&buf[data_start..data_end]).context("parse DATA area")?;
86    r.set_pos(data_end);
87
88    // OBJS chunk: tag + chunk_size (includes tag+size) + payload
89    let objs_tag = r.read_u32_le().context("read OBJS tag")?;
90    if objs_tag != OBJ_TAG_LE {
91        bail!(
92            "fourcc mismatch: expect {:?}, got {:?}",
93            b"OBJS",
94            u32_to_4cc(objs_tag)
95        );
96    }
97    let objs_chunk_size = r.read_u32_le().context("read OBJS chunk size")? as usize;
98    if objs_chunk_size < 8 {
99        bail!("OBJS chunk size too small: {}", objs_chunk_size);
100    }
101    let objs_payload_size = objs_chunk_size - 8;
102    let objs_start = r.pos();
103    let objs_end = objs_start
104        .checked_add(objs_payload_size)
105        .ok_or_else(|| anyhow!("overflow computing OBJS end"))?;
106    if objs_end > buf.len() {
107        bail!(
108            "OBJS payload out of range: end={}, file={}",
109            objs_end,
110            buf.len()
111        );
112    }
113
114    let (toplevel, objects) =
115        read_objects(&buf[objs_start..objs_end], &pools).context("parse OBJS area")?;
116    r.set_pos(objs_end);
117
118    // No extra trailing bytes are expected in krkrz's exporter.
119    if r.pos() != buf.len() {
120        bail!(
121            "unexpected trailing bytes: pos={}, file={}",
122            r.pos(),
123            buf.len()
124        );
125    }
126
127    Ok(Tjs2File {
128        toplevel,
129        const_pools: pools,
130        objects,
131    })
132}
133
134fn read_data_area(payload: &[u8]) -> Result<ConstPools> {
135    let mut r = Reader::new(payload);
136    let mut pools = ConstPools::default();
137
138    // byte
139    let count = r.read_u32_le().context("DATA.bytes.count")? as usize;
140    if count > 0 {
141        let b = r.read_bytes(count).context("DATA.bytes.data")?;
142        pools.bytes = b.iter().map(|x| *x as i8).collect();
143        r.align4().context("DATA.bytes.align4")?;
144    }
145
146    // short
147    let count = r.read_u32_le().context("DATA.shorts.count")? as usize;
148    if count > 0 {
149        pools.shorts.reserve(count);
150        for _ in 0..count {
151            pools
152                .shorts
153                .push(r.read_i16_le().context("DATA.shorts.elem")?);
154        }
155        if (count & 1) == 1 {
156            // alignment
157            let _ = r.read_u16_le().context("DATA.shorts.pad")?;
158        }
159    }
160
161    // int
162    let count = r.read_u32_le().context("DATA.ints.count")? as usize;
163    if count > 0 {
164        pools.ints.reserve(count);
165        for _ in 0..count {
166            pools.ints.push(r.read_i32_le().context("DATA.ints.elem")?);
167        }
168    }
169
170    // long (i64)
171    let count = r.read_u32_le().context("DATA.longs.count")? as usize;
172    if count > 0 {
173        pools.longs.reserve(count);
174        for _ in 0..count {
175            pools
176                .longs
177                .push(r.read_i64_le().context("DATA.longs.elem")?);
178        }
179    }
180
181    // double
182    let count = r.read_u32_le().context("DATA.doubles.count")? as usize;
183    if count > 0 {
184        pools.doubles.reserve(count);
185        for _ in 0..count {
186            let bits = r.read_u64_le().context("DATA.doubles.bits")?;
187            pools.doubles.push(f64::from_bits(bits));
188        }
189    }
190
191    // string (UTF-16LE)
192    let count = r.read_u32_le().context("DATA.strings.count")? as usize;
193    pools.strings.reserve(count);
194    for _ in 0..count {
195        let len = r.read_u32_le().context("DATA.strings.len")? as usize;
196        let mut units = Vec::with_capacity(len);
197        for _ in 0..len {
198            units.push(r.read_u16_le().context("DATA.strings.unit")?);
199        }
200        if (len & 1) == 1 {
201            let _ = r.read_u16_le().context("DATA.strings.pad")?;
202        }
203        pools.strings.push(String::from_utf16_lossy(&units));
204    }
205
206    // octet buffers
207    let count = r.read_u32_le().context("DATA.octets.count")? as usize;
208    pools.octets.reserve(count);
209    for _ in 0..count {
210        let cap = r.read_u32_le().context("DATA.octets.len")? as usize;
211        let data = r.read_bytes(cap).context("DATA.octets.data")?.to_vec();
212        pools.octets.push(data);
213        r.align4().context("DATA.octets.align4")?;
214    }
215
216    if r.pos() != payload.len() {
217        bail!(
218            "DATA: payload not fully consumed: pos={}, payload={}",
219            r.pos(),
220            payload.len()
221        );
222    }
223
224    Ok(pools)
225}
226
227fn read_objects(payload: &[u8], pools: &ConstPools) -> Result<(i32, Vec<Tjs2Object>)> {
228    let mut r = Reader::new(payload);
229
230    let toplevel = r.read_i32_le().context("OBJS.toplevel")?;
231    let objcount = r.read_i32_le().context("OBJS.objcount")?;
232    if objcount < 0 {
233        bail!("OBJS.objcount is negative: {}", objcount);
234    }
235    let objcount = objcount as usize;
236
237    let mut objects: Vec<Tjs2Object> = Vec::with_capacity(objcount);
238
239    // We keep raw property pairs here; we do not execute propSet logic.
240    for o in 0..objcount {
241        let tag = r.read_u32_le().context("OBJS.obj.tag")?;
242        if tag != FILE_TAG_LE {
243            bail!(
244                "object fourcc mismatch: expect {:?}, got {:?}",
245                b"TJS2",
246                u32_to_4cc(tag)
247            );
248        }
249        let _obj_payload_size = r.read_u32_le().context("OBJS.obj.size")? as usize;
250
251        let parent = r.read_i32_le().context("obj.parent")?;
252        let name_idx = r.read_i32_le().context("obj.name_idx")?;
253        let context_type = r.read_i32_le().context("obj.context_type")?;
254        let max_variable_count = r.read_i32_le().context("obj.max_variable_count")?;
255        let variable_reserve_count = r.read_i32_le().context("obj.variable_reserve_count")?;
256        let max_frame_count = r.read_i32_le().context("obj.max_frame_count")?;
257        let func_decl_arg_count = r.read_i32_le().context("obj.func_decl_arg_count")?;
258        let func_decl_unnamed_arg_array_base = r
259            .read_i32_le()
260            .context("obj.func_decl_unnamed_arg_array_base")?;
261        let func_decl_collapse_base = r.read_i32_le().context("obj.func_decl_collapse_base")?;
262        let prop_setter = r.read_i32_le().context("obj.prop_setter")?;
263        let prop_getter = r.read_i32_le().context("obj.prop_getter")?;
264        let super_class_getter = r.read_i32_le().context("obj.super_class_getter")?;
265
266        // srcpos
267        let srcpos_count = r.read_i32_le().context("obj.srcpos.count")?;
268        if srcpos_count < 0 {
269            bail!("obj.srcpos.count is negative: {}", srcpos_count);
270        }
271        let srcpos_count = srcpos_count as usize;
272        // We do not use srcpos mapping for now; just skip it.
273        for _ in 0..srcpos_count {
274            let _ = r.read_i32_le().context("obj.srcpos.codepos")?;
275        }
276        for _ in 0..srcpos_count {
277            let _ = r.read_i32_le().context("obj.srcpos.srcpos")?;
278        }
279
280        // code area
281        let code_count = r.read_i32_le().context("obj.code.count")?;
282        if code_count < 0 {
283            bail!("obj.code.count is negative: {}", code_count);
284        }
285        let code_count = code_count as usize;
286        let mut code: Vec<i32> = Vec::with_capacity(code_count);
287        for _ in 0..code_count {
288            code.push(r.read_i16_le().context("obj.code.word")? as i32);
289        }
290        // align to 4 bytes if odd
291        if (code_count & 1) == 1 {
292            let _ = r.read_u16_le().context("obj.code.pad")?;
293        }
294
295        // data area (vdata)
296        let data_count = r.read_i32_le().context("obj.data.count")?;
297        if data_count < 0 {
298            bail!("obj.data.count is negative: {}", data_count);
299        }
300        let data_count = data_count as usize;
301        let mut data: Vec<Variant> = Vec::with_capacity(data_count);
302        for _ in 0..data_count {
303            let ty = r.read_i16_le().context("obj.data.type")?;
304            let idx = r.read_i16_le().context("obj.data.index")? as i32;
305            let v = match ty {
306                TYPE_VOID => Variant::Void,
307                TYPE_OBJECT => Variant::NullObject,
308                TYPE_INTER_OBJECT => Variant::InterObject(idx),
309                TYPE_INTER_GENERATOR => Variant::InterGenerator(idx),
310                TYPE_STRING => Variant::String(idx),
311                TYPE_OCTET => Variant::Octet(idx),
312                TYPE_REAL => Variant::Real(idx),
313                TYPE_BYTE => Variant::Byte(idx),
314                TYPE_SHORT => Variant::Short(idx),
315                TYPE_INTEGER => Variant::Integer(idx),
316                TYPE_LONG => Variant::Long(idx),
317                TYPE_UNKNOWN | _ => Variant::Unknown,
318            };
319            data.push(v);
320        }
321
322        // super class getter pointer
323        let scg_count = r.read_i32_le().context("obj.scgetterps.count")?;
324        if scg_count < 0 {
325            bail!("obj.scgetterps.count is negative: {}", scg_count);
326        }
327        let scg_count = scg_count as usize;
328        let mut scgetterps: Vec<i32> = Vec::with_capacity(scg_count);
329        for _ in 0..scg_count {
330            scgetterps.push(r.read_i32_le().context("obj.scgetterps.elem")?);
331        }
332
333        // properties
334        let prop_count = r.read_i32_le().context("obj.properties.count")?;
335        if prop_count < 0 {
336            bail!("obj.properties.count is negative: {}", prop_count);
337        }
338        let prop_count = prop_count as usize;
339        let mut properties = Vec::new();
340        if prop_count > 0 {
341            properties.reserve(prop_count);
342            for _ in 0..prop_count {
343                let pname = r.read_i32_le().context("obj.properties.name")?;
344                let pobj = r.read_i32_le().context("obj.properties.obj")?;
345                properties.push((pname, pobj));
346            }
347        }
348
349        let name = if name_idx >= 0 {
350            pools.strings.get(name_idx as usize).cloned()
351        } else {
352            None
353        };
354
355        objects.push(Tjs2Object {
356            index: o,
357            parent,
358            name_string_index: name_idx,
359            name,
360            context_type,
361            max_variable_count,
362            variable_reserve_count,
363            max_frame_count,
364            func_decl_arg_count,
365            func_decl_unnamed_arg_array_base,
366            func_decl_collapse_base,
367            prop_setter,
368            prop_getter,
369            super_class_getter,
370            code,
371            data,
372            scgetterps,
373            properties,
374        });
375    }
376
377    if r.pos() != payload.len() {
378        bail!(
379            "OBJS: payload not fully consumed: pos={}, payload={}",
380            r.pos(),
381            payload.len()
382        );
383    }
384
385    Ok((toplevel, objects))
386}
387
388fn u32_to_4cc(x: u32) -> [u8; 4] {
389    [
390        (x & 0xff) as u8,
391        ((x >> 8) & 0xff) as u8,
392        ((x >> 16) & 0xff) as u8,
393        ((x >> 24) & 0xff) as u8,
394    ]
395}
396
397struct Reader<'a> {
398    buf: &'a [u8],
399    pos: usize,
400}
401
402impl<'a> Reader<'a> {
403    fn new(buf: &'a [u8]) -> Self {
404        Self { buf, pos: 0 }
405    }
406    fn pos(&self) -> usize {
407        self.pos
408    }
409    fn set_pos(&mut self, pos: usize) {
410        self.pos = pos;
411    }
412
413    fn read_bytes(&mut self, n: usize) -> Result<&'a [u8]> {
414        let end = self.pos.checked_add(n).ok_or_else(|| anyhow!("overflow"))?;
415        if end > self.buf.len() {
416            bail!("failed to fill whole buffer");
417        }
418        let out = &self.buf[self.pos..end];
419        self.pos = end;
420        Ok(out)
421    }
422
423    fn read_u16_le(&mut self) -> Result<u16> {
424        let b = self.read_bytes(2)?;
425        Ok(u16::from_le_bytes([b[0], b[1]]))
426    }
427    fn read_i16_le(&mut self) -> Result<i16> {
428        Ok(self.read_u16_le()? as i16)
429    }
430
431    fn read_u32_le(&mut self) -> Result<u32> {
432        let b = self.read_bytes(4)?;
433        Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
434    }
435    fn read_i32_le(&mut self) -> Result<i32> {
436        Ok(self.read_u32_le()? as i32)
437    }
438
439    fn read_u64_le(&mut self) -> Result<u64> {
440        let b = self.read_bytes(8)?;
441        Ok(u64::from_le_bytes([
442            b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
443        ]))
444    }
445    fn read_i64_le(&mut self) -> Result<i64> {
446        Ok(self.read_u64_le()? as i64)
447    }
448
449    fn align4(&mut self) -> Result<()> {
450        let rem = self.pos & 3;
451        if rem != 0 {
452            let pad = 4 - rem;
453            let _ = self.read_bytes(pad)?;
454        }
455        Ok(())
456    }
457}