1use crate::lzss;
28use crate::util::read_i32_le;
29use anyhow::{anyhow, bail, Result};
30use encoding_rs::SHIFT_JIS;
31use std::fs;
32use std::path::Path;
33
34const MAP_WIDTH: usize = 16;
35const TILE_WIDTH: usize = 5;
36const TILE_HEIGHT: usize = 5;
37
38const TILE: [u8; TILE_WIDTH * TILE_HEIGHT] = [
40 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 255, 255, 255, 0, 255, 0, 0, 255, 0, 0, 0, 0, 0, 255,
41 255,
42];
43
44const XORCODE: [u32; 3] = [0x753A4098, 0x4A673CCC, 0xFE6215AF];
45
46#[derive(Debug, Clone, Copy)]
47pub struct DbsRowHeader {
48 pub call_no: i32,
49}
50
51#[derive(Debug, Clone, Copy)]
52pub struct DbsColumnHeader {
53 pub call_no: i32,
54 pub data_type: i32,
55}
56
57#[derive(Debug, Clone, Copy)]
58struct DbsHeader {
59 data_size: i32,
60 row_cnt: i32,
61 column_cnt: i32,
62 row_header_offset: i32,
63 column_header_offset: i32,
64 data_offset: i32,
65 str_offset: i32,
66}
67
68#[derive(Debug, Clone)]
69pub struct DbsDatabase {
70 db_type: i32,
71 expanded: Vec<u8>,
72 header: DbsHeader,
73 rows: Vec<DbsRowHeader>,
74 cols: Vec<DbsColumnHeader>,
75 data: Vec<u32>,
76 str_base: usize,
77}
78
79impl DbsDatabase {
80 pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
82 let bytes = fs::read(path)?;
83 Self::from_bytes(&bytes)
84 }
85
86 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
88 if bytes.len() < 4 {
89 bail!("DBS: file too short");
90 }
91 let db_type = i32::from_le_bytes(bytes[0..4].try_into().unwrap());
92 let payload = &bytes[4..];
93 let expanded = tnm_database_expand(payload)?;
94 Self::parse_expanded(db_type, expanded)
95 }
96
97 pub fn db_type(&self) -> i32 {
98 self.db_type
99 }
100
101 pub fn row_count(&self) -> usize {
102 self.rows.len()
103 }
104
105 pub fn column_count(&self) -> usize {
106 self.cols.len()
107 }
108
109 pub fn rows(&self) -> &[DbsRowHeader] {
110 &self.rows
111 }
112
113 pub fn columns(&self) -> &[DbsColumnHeader] {
114 &self.cols
115 }
116
117 pub fn get_data_int(&self, item_call_no: i32, column_call_no: i32) -> Result<Option<i32>> {
119 let item_no = self.get_item_no(item_call_no);
120 let col_no = self.get_column_no(column_call_no);
121 if item_no < 0 || col_no < 0 {
122 return Ok(None);
123 }
124 let col_no = col_no as usize;
125 if self.cols[col_no].data_type as u8 != b'V' {
126 bail!("DBS: column_call_no={column_call_no} is not numeric");
127 }
128 let idx = (item_no as usize)
129 .checked_mul(self.cols.len())
130 .and_then(|v| v.checked_add(col_no))
131 .ok_or_else(|| anyhow!("DBS: data index overflow"))?;
132 let v = self
133 .data
134 .get(idx)
135 .ok_or_else(|| anyhow!("DBS: data index out of range"))?;
136 Ok(Some(*v as i32))
137 }
138
139 pub fn get_data_str(&self, item_call_no: i32, column_call_no: i32) -> Result<Option<String>> {
141 let item_no = self.get_item_no(item_call_no);
142 let col_no = self.get_column_no(column_call_no);
143 if item_no < 0 || col_no < 0 {
144 return Ok(None);
145 }
146 let col_no = col_no as usize;
147 if self.cols[col_no].data_type as u8 != b'S' {
148 bail!("DBS: column_call_no={column_call_no} is not string");
149 }
150 let idx = (item_no as usize)
151 .checked_mul(self.cols.len())
152 .and_then(|v| v.checked_add(col_no))
153 .ok_or_else(|| anyhow!("DBS: data index overflow"))?;
154 let off = self
155 .data
156 .get(idx)
157 .ok_or_else(|| anyhow!("DBS: data index out of range"))?;
158 Ok(Some(self.get_str(*off as usize)?))
159 }
160
161 pub fn check_column_no(&self, column_call_no: i32) -> i32 {
163 let col_no = self.get_column_no(column_call_no);
164 if col_no < 0 {
165 return 0;
166 }
167 match self.cols[col_no as usize].data_type as u8 {
168 b'V' => 1,
169 b'S' => 2,
170 _ => 0,
171 }
172 }
173
174 pub fn check_item_no(&self, item_call_no: i32) -> i32 {
176 if self.get_item_no(item_call_no) >= 0 {
177 1
178 } else {
179 0
180 }
181 }
182
183 pub fn find_num(&self, column_call_no: i32, num: i32) -> Result<i32> {
185 let col_no = self.get_column_no(column_call_no);
186 if col_no < 0 {
187 return Ok(-1);
188 }
189 let col_no = col_no as usize;
190 if self.cols[col_no].data_type as u8 != b'V' {
191 bail!("DBS: column_call_no={column_call_no} is not numeric");
192 }
193 for row in 0..self.rows.len() {
194 let idx = row * self.cols.len() + col_no;
195 if (self.data[idx] as i32) == num {
196 return Ok(self.rows[row].call_no);
197 }
198 }
199 Ok(-1)
200 }
201
202 pub fn find_str(&self, column_call_no: i32, s: &str) -> Result<i32> {
204 let col_no = self.get_column_no(column_call_no);
205 if col_no < 0 {
206 return Ok(-1);
207 }
208 let col_no = col_no as usize;
209 if self.cols[col_no].data_type as u8 != b'S' {
210 bail!("DBS: column_call_no={column_call_no} is not string");
211 }
212 let needle = s.to_ascii_lowercase();
213 for row in 0..self.rows.len() {
214 let idx = row * self.cols.len() + col_no;
215 let off = self.data[idx] as usize;
216 let got = self.get_str(off)?;
217 if got.to_ascii_lowercase() == needle {
218 return Ok(self.rows[row].call_no);
219 }
220 }
221 Ok(-1)
222 }
223
224 pub fn find_str_real(&self, column_call_no: i32, s: &str) -> Result<i32> {
226 let col_no = self.get_column_no(column_call_no);
227 if col_no < 0 {
228 return Ok(-1);
229 }
230 let col_no = col_no as usize;
231 if self.cols[col_no].data_type as u8 != b'S' {
232 bail!("DBS: column_call_no={column_call_no} is not string");
233 }
234 for row in 0..self.rows.len() {
235 let idx = row * self.cols.len() + col_no;
236 let off = self.data[idx] as usize;
237 let got = self.get_str(off)?;
238 if got == s {
239 return Ok(self.rows[row].call_no);
240 }
241 }
242 Ok(-1)
243 }
244
245 fn parse_expanded(db_type: i32, expanded: Vec<u8>) -> Result<Self> {
246 let mut off = 0usize;
247 let data_size = read_i32_le(&expanded, &mut off)?;
248 let row_cnt = read_i32_le(&expanded, &mut off)?;
249 let column_cnt = read_i32_le(&expanded, &mut off)?;
250 let row_header_offset = read_i32_le(&expanded, &mut off)?;
251 let column_header_offset = read_i32_le(&expanded, &mut off)?;
252 let data_offset = read_i32_le(&expanded, &mut off)?;
253 let str_offset = read_i32_le(&expanded, &mut off)?;
254
255 if data_size <= 0 {
256 bail!("DBS: invalid data_size={data_size}");
257 }
258 if data_size as usize > expanded.len() {
259 bail!(
260 "DBS: header data_size out of range (data_size={}, buf_len={})",
261 data_size,
262 expanded.len()
263 );
264 }
265 if row_cnt < 0 || column_cnt < 0 {
266 bail!("DBS: negative counts (row_cnt={row_cnt}, column_cnt={column_cnt})");
267 }
268
269 let row_cnt_u = row_cnt as usize;
270 let col_cnt_u = column_cnt as usize;
271
272 let row_header_off = row_header_offset as usize;
273 let col_header_off = column_header_offset as usize;
274 let data_off = data_offset as usize;
275 let str_off = str_offset as usize;
276
277 if row_header_off > expanded.len()
278 || col_header_off > expanded.len()
279 || data_off > expanded.len()
280 || str_off > expanded.len()
281 {
282 bail!("DBS: one or more offsets out of range");
283 }
284
285 let row_headers_bytes = row_cnt_u
286 .checked_mul(4)
287 .ok_or_else(|| anyhow!("DBS: row headers size overflow"))?;
288 let col_headers_bytes = col_cnt_u
289 .checked_mul(8)
290 .ok_or_else(|| anyhow!("DBS: col headers size overflow"))?;
291 let data_bytes = row_cnt_u
292 .checked_mul(col_cnt_u)
293 .and_then(|v| v.checked_mul(4))
294 .ok_or_else(|| anyhow!("DBS: data table size overflow"))?;
295
296 if row_header_off + row_headers_bytes > expanded.len() {
297 bail!("DBS: row header table truncated");
298 }
299 if col_header_off + col_headers_bytes > expanded.len() {
300 bail!("DBS: column header table truncated");
301 }
302 if data_off + data_bytes > expanded.len() {
303 bail!("DBS: data table truncated");
304 }
305 if str_off > expanded.len() {
306 bail!("DBS: string table offset out of range");
307 }
308
309 let mut rows = Vec::with_capacity(row_cnt_u);
310 let mut roff = row_header_off;
311 for _ in 0..row_cnt_u {
312 let call_no = i32::from_le_bytes(expanded[roff..roff + 4].try_into().unwrap());
313 rows.push(DbsRowHeader { call_no });
314 roff += 4;
315 }
316
317 let mut cols = Vec::with_capacity(col_cnt_u);
318 let mut coff = col_header_off;
319 for _ in 0..col_cnt_u {
320 let call_no = i32::from_le_bytes(expanded[coff..coff + 4].try_into().unwrap());
321 let data_type = i32::from_le_bytes(expanded[coff + 4..coff + 8].try_into().unwrap());
322 cols.push(DbsColumnHeader { call_no, data_type });
323 coff += 8;
324 }
325
326 let mut data = Vec::with_capacity(row_cnt_u * col_cnt_u);
327 let mut doff = data_off;
328 for _ in 0..(row_cnt_u * col_cnt_u) {
329 let v = u32::from_le_bytes(expanded[doff..doff + 4].try_into().unwrap());
330 data.push(v);
331 doff += 4;
332 }
333
334 Ok(Self {
335 db_type,
336 expanded,
337 header: DbsHeader {
338 data_size,
339 row_cnt,
340 column_cnt,
341 row_header_offset,
342 column_header_offset,
343 data_offset,
344 str_offset,
345 },
346 rows,
347 cols,
348 data,
349 str_base: str_off,
350 })
351 }
352
353 fn get_item_no(&self, item_call_no: i32) -> i32 {
354 for (i, r) in self.rows.iter().enumerate() {
355 if r.call_no == item_call_no {
356 return i as i32;
357 }
358 }
359 -1
360 }
361
362 fn get_column_no(&self, column_call_no: i32) -> i32 {
363 for (i, c) in self.cols.iter().enumerate() {
364 if c.call_no == column_call_no {
365 return i as i32;
366 }
367 }
368 -1
369 }
370
371 fn get_str(&self, str_offset: usize) -> Result<String> {
372 let base = self
373 .str_base
374 .checked_add(str_offset)
375 .ok_or_else(|| anyhow!("DBS: string offset overflow"))?;
376 if base >= self.expanded.len() {
377 bail!("DBS: string offset out of range");
378 }
379
380 if self.db_type == 0 {
381 let end = self.expanded[base..]
383 .iter()
384 .position(|&b| b == 0)
385 .map(|p| base + p)
386 .unwrap_or(self.expanded.len());
387 let bytes = &self.expanded[base..end];
388 let (cow, _, _) = SHIFT_JIS.decode(bytes);
389 Ok(cow.into_owned())
390 } else {
391 let mut cur = base;
393 let mut u16s: Vec<u16> = Vec::new();
394 loop {
395 if cur + 2 > self.expanded.len() {
396 break;
397 }
398 let w = u16::from_le_bytes([self.expanded[cur], self.expanded[cur + 1]]);
399 cur += 2;
400 if w == 0 {
401 break;
402 }
403 u16s.push(w);
404 }
405 Ok(String::from_utf16_lossy(&u16s))
406 }
407 }
408}
409
410fn tnm_database_expand(src_payload: &[u8]) -> Result<Vec<u8>> {
411 if src_payload.is_empty() {
412 bail!("DBS: empty payload");
413 }
414
415 let mut payload = src_payload.to_vec();
417 xor_u32_in_place(&mut payload, XORCODE[2]);
418
419 let unpack_data = lzss::lzss_unpack(&payload)?;
421 let unpack_size = unpack_data.len();
422 if unpack_size == 0 {
423 bail!("DBS: unpack_size=0");
424 }
425 if unpack_size % (MAP_WIDTH * 4) != 0 {
426 bail!(
427 "DBS: unpack_size not aligned to map width (unpack_size={}, map_stride={})",
428 unpack_size,
429 MAP_WIDTH * 4
430 );
431 }
432
433 let yl = unpack_size / (MAP_WIDTH * 4);
434
435 let mut temp_a = vec![0u8; unpack_size];
437 let mut temp_b = vec![0u8; unpack_size];
438 mask_copy_u32(
439 &mut temp_a,
440 &unpack_data,
441 MAP_WIDTH,
442 yl,
443 &TILE,
444 TILE_WIDTH,
445 TILE_HEIGHT,
446 0,
447 128,
448 )?;
449 mask_copy_u32(
450 &mut temp_b,
451 &unpack_data,
452 MAP_WIDTH,
453 yl,
454 &TILE,
455 TILE_WIDTH,
456 TILE_HEIGHT,
457 1,
458 128,
459 )?;
460
461 xor_u32_in_place(&mut temp_a, XORCODE[0]);
463 xor_u32_in_place(&mut temp_b, XORCODE[1]);
464
465 let mut dst = vec![0u8; unpack_size];
467 mask_copy_u32(
468 &mut dst,
469 &temp_a,
470 MAP_WIDTH,
471 yl,
472 &TILE,
473 TILE_WIDTH,
474 TILE_HEIGHT,
475 0,
476 128,
477 )?;
478 mask_copy_u32(
479 &mut dst,
480 &temp_b,
481 MAP_WIDTH,
482 yl,
483 &TILE,
484 TILE_WIDTH,
485 TILE_HEIGHT,
486 1,
487 128,
488 )?;
489
490 Ok(dst)
491}
492
493#[inline]
494fn xor_u32_in_place(buf: &mut [u8], xor_code: u32) {
495 let n = buf.len() / 4;
496 for i in 0..n {
497 let off = i * 4;
498 let v = u32::from_le_bytes(buf[off..off + 4].try_into().unwrap()) ^ xor_code;
499 buf[off..off + 4].copy_from_slice(&v.to_le_bytes());
500 }
501}
502
503fn mask_copy_u32(
507 dst: &mut [u8],
508 src: &[u8],
509 xl: usize,
510 yl: usize,
511 mask: &[u8],
512 m_xl: usize,
513 m_yl: usize,
514 reverse: i32,
515 limit: u8,
516) -> Result<()> {
517 if dst.len() != src.len() {
518 bail!("DBS: mask_copy size mismatch");
519 }
520 let expect_len = xl
521 .checked_mul(yl)
522 .and_then(|v| v.checked_mul(4))
523 .ok_or_else(|| anyhow!("DBS: mask_copy size overflow"))?;
524 if expect_len != dst.len() {
525 bail!(
526 "DBS: mask_copy unexpected buffer length (expect={}, got={})",
527 expect_len,
528 dst.len()
529 );
530 }
531 if mask.len() != m_xl * m_yl {
532 bail!("DBS: mask tile size mismatch");
533 }
534
535 for y in 0..yl {
536 for x in 0..xl {
537 let mx = x % m_xl;
538 let my = y % m_yl;
539 let mv = mask[my * m_xl + mx];
540 let cond = if reverse == 0 {
544 mv >= limit
545 } else {
546 mv < limit
547 };
548 if !cond {
549 continue;
550 }
551 let p = (y * xl + x) * 4;
552 dst[p..p + 4].copy_from_slice(&src[p..p + 4]);
553 }
554 }
555
556 Ok(())
557}