Skip to main content

terraria_world/
reader.rs

1use chrono::DateTime;
2
3pub struct ByteReader<'a> {
4    data: &'a [u8],
5    offset: usize,
6}
7
8impl<'a> ByteReader<'a> {
9    pub fn new(data: &'a [u8]) -> Self {
10        ByteReader { data, offset: 0 }
11    }
12
13    pub fn u8(&mut self) -> u8 {
14        if self.offset >= self.data.len() {
15            panic!(
16                "Attempted to read u8 at offset {} but data length is {}",
17                self.offset,
18                self.data.len()
19            );
20        }
21        let val = self.data[self.offset];
22        self.offset += 1;
23        val
24    }
25
26    pub fn u16(&mut self) -> u16 {
27        if self.offset + 2 > self.data.len() {
28            panic!(
29                "Attempted to read u16 at offset {} but data length is {}",
30                self.offset,
31                self.data.len()
32            );
33        }
34        let val = u16::from_le_bytes(self.data[self.offset..self.offset + 2].try_into().unwrap());
35        self.offset += 2;
36        val
37    }
38
39    pub fn u32(&mut self) -> u32 {
40        if self.offset + 4 > self.data.len() {
41            panic!(
42                "Attempted to read u32 at offset {} but data length is {}",
43                self.offset,
44                self.data.len()
45            );
46        }
47        let val = u32::from_le_bytes(self.data[self.offset..self.offset + 4].try_into().unwrap());
48        self.offset += 4;
49        val
50    }
51
52    pub fn u64(&mut self) -> u64 {
53        if self.offset + 8 > self.data.len() {
54            panic!(
55                "Attempted to read u64 at offset {} but data length is {}",
56                self.offset,
57                self.data.len()
58            );
59        }
60        let val = u64::from_le_bytes(self.data[self.offset..self.offset + 8].try_into().unwrap());
61        self.offset += 8;
62        val
63    }
64
65    pub fn i8(&mut self) -> i8 {
66        if self.offset >= self.data.len() {
67            panic!(
68                "Attempted to read i8 at offset {} but data length is {}",
69                self.offset,
70                self.data.len()
71            );
72        }
73        let val = self.data[self.offset] as i8;
74        self.offset += 1;
75        val
76    }
77
78    pub fn i16(&mut self) -> i16 {
79        if self.offset + 2 > self.data.len() {
80            panic!(
81                "Attempted to read i16 at offset {} but data length is {}",
82                self.offset,
83                self.data.len()
84            );
85        }
86        let val = i16::from_le_bytes(self.data[self.offset..self.offset + 2].try_into().unwrap());
87        self.offset += 2;
88        val
89    }
90
91    pub fn i32(&mut self) -> i32 {
92        if self.offset + 4 > self.data.len() {
93            panic!(
94                "Attempted to read i32 at offset {} but data length is {}",
95                self.offset,
96                self.data.len()
97            );
98        }
99        let val = i32::from_le_bytes(self.data[self.offset..self.offset + 4].try_into().unwrap());
100        self.offset += 4;
101        val
102    }
103
104    pub fn i64(&mut self) -> i64 {
105        if self.offset + 8 > self.data.len() {
106            panic!(
107                "Attempted to read i64 at offset {} but data length is {}",
108                self.offset,
109                self.data.len()
110            );
111        }
112        let val = i64::from_le_bytes(self.data[self.offset..self.offset + 8].try_into().unwrap());
113        self.offset += 8;
114        val
115    }
116
117    pub fn bool(&mut self) -> bool {
118        let byte = self.u8();
119        // In Terraria world files, any non-zero value is considered true
120        byte != 0
121    }
122
123    pub fn bits(&mut self) -> Vec<bool> {
124        let byte = self.u8(); // read one byte
125        (0..8).map(|i| (byte & (1 << i)) != 0).collect()
126    }
127
128    pub fn bytes(&mut self, count: usize) -> &'a [u8] {
129        if self.offset + count > self.data.len() {
130            panic!(
131                "Attempted to read {} bytes at offset {} but data length is {}",
132                count,
133                self.offset,
134                self.data.len()
135            );
136        }
137        let slice = &self.data[self.offset..self.offset + count];
138        self.offset += count;
139        slice
140    }
141
142    /// Returns a slice of bytes from the current offset without advancing the offset.
143    pub fn peek_bytes(&self, count: usize) -> &'a [u8] {
144        if self.offset + count > self.data.len() {
145            panic!(
146                "Attempted to peek {} bytes at offset {} but data length is {}",
147                count,
148                self.offset,
149                self.data.len()
150            );
151        }
152        &self.data[self.offset..self.offset + count]
153    }
154
155    pub fn read_until(&mut self, address: usize) -> Vec<u8> {
156        let end = std::cmp::min(address, self.data.len());
157        if self.offset >= end {
158            return Vec::new(); // Already past the target address
159        }
160        let slice = &self.data[self.offset..end];
161        self.offset = end; // update offset to the end of the slice
162        slice.to_vec()
163    }
164
165    pub fn offset(&self) -> usize {
166        self.offset
167    }
168
169    pub fn seek(&mut self, offset: usize) {
170        self.offset = offset;
171    }
172
173    pub fn uleb128(&mut self) -> u64 {
174        let mut value = 0u64;
175        let mut shift = 0;
176        loop {
177            let byte = self.u8();
178            value |= ((byte & 0x7F) as u64) << shift;
179            if (byte & 0x80) == 0 {
180                break;
181            }
182            shift += 7;
183        }
184        value
185    }
186
187    pub fn string(&mut self, size: Option<usize>) -> String {
188        let size = size.unwrap_or_else(|| self.uleb128() as usize);
189        let bytes = self.bytes(size);
190        bytes.iter().map(|&b| b as char).collect() // assuming latin1
191    }
192
193    pub fn uuid(&mut self) -> String {
194        let bytes = self.bytes(16);
195        format!(
196            "{:02x}{:02x}{:02x}{:02x}-\
197             {:02x}{:02x}-\
198             {:02x}{:02x}-\
199             {:02x}{:02x}-\
200             {:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
201            bytes[0],
202            bytes[1],
203            bytes[2],
204            bytes[3],
205            bytes[4],
206            bytes[5],
207            bytes[6],
208            bytes[7],
209            bytes[8],
210            bytes[9],
211            bytes[10],
212            bytes[11],
213            bytes[12],
214            bytes[13],
215            bytes[14],
216            bytes[15],
217        )
218    }
219
220    pub fn datetime(&mut self) -> String {
221        let raw = self.u64(); // already reads 8 bytes little-endian
222
223        let _kind: u64 = (raw >> 62) & 0b11;
224        let ticks: u64 = raw & 0x3FFF_FFFF_FFFF_FFFF; // mask top 2 bits
225
226        // println!("Kind: {}", match kind {
227        //     0 => "Unspecified",
228        //     1 => "Utc",
229        //     2 => "Local",
230        //     _ => "⚠️ Reserved/Invalid",
231        // });
232
233        // .NET ticks start at 0001-01-01
234        let unix_offset: u64 = 621355968000000000;
235        if ticks < unix_offset {
236            return "⚠️ Before UNIX epoch".to_string();
237        }
238
239        let unix_ticks: u64 = ticks - unix_offset;
240        let secs: u64 = unix_ticks / 10_000_000;
241        let nsecs: u64 = (unix_ticks % 10_000_000) * 100;
242
243        match DateTime::from_timestamp(secs as i64, nsecs as u32) {
244            Some(dt) => {
245                // Use format with 7 decimal places to preserve .NET tick precision
246                // .NET ticks are 100ns intervals, so 7 decimal places gives us the full precision
247                dt.format("%Y-%m-%d %H:%M:%S%.f").to_string()
248            }
249            _ => "⚠️ Invalid datetime".to_string(),
250        }
251    }
252
253    pub fn f32(&mut self) -> f32 {
254        if self.offset + 4 > self.data.len() {
255            panic!(
256                "Attempted to read f32 at offset {} but data length is {}",
257                self.offset,
258                self.data.len()
259            );
260        }
261        let bytes = self.bytes(4);
262        f32::from_le_bytes(bytes.try_into().unwrap())
263    }
264
265    pub fn f64(&mut self) -> f64 {
266        if self.offset + 8 > self.data.len() {
267            panic!(
268                "Attempted to read f64 at offset {} but data length is {}",
269                self.offset,
270                self.data.len()
271            );
272        }
273        let bytes = self.bytes(8);
274        f64::from_le_bytes(bytes.try_into().unwrap())
275    }
276
277    pub fn slice_bytes(&self, start: usize, end: usize) -> Vec<u8> {
278        if start > end || end > self.data.len() {
279            panic!(
280                "Invalid slice range: {}..{} (data len {})",
281                start,
282                end,
283                self.data.len()
284            );
285        }
286        self.data[start..end].to_vec()
287    }
288}