1use crate::lzss;
28use crate::util::read_i32_le;
29use anyhow::{Result, anyhow, bail};
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] = [0x7190C70E, 0x499BF135, 0x89F4622D];
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}
558
559#[cfg(test)]
560mod tests {
561 use super::*;
562
563 #[test]
564 fn loads_cg_composition_lookup_from_compressed_database() {
565 let mut expanded = [0u8; 128];
568 let words = [
569 128i32,
571 1,
572 2,
573 28,
574 32,
575 48,
576 56,
577 7,
579 0,
580 b'S' as i32,
581 1,
582 b'S' as i32,
583 0,
584 10,
585 ];
586 for (dst, word) in expanded.as_chunks_mut::<4>().0.iter_mut().zip(words) {
587 dst.copy_from_slice(&word.to_le_bytes());
588 }
589 for (dst, word) in expanded[56..]
590 .as_chunks_mut::<2>()
591 .0
592 .iter_mut()
593 .zip("cg_a\0cg_a__base|cg_a\0".encode_utf16())
594 {
595 dst.copy_from_slice(&word.to_le_bytes());
596 }
597
598 for (i, word) in expanded.as_chunks_mut::<4>().0.iter_mut().enumerate() {
601 let tile =
602 TILE[(i / MAP_WIDTH % TILE_HEIGHT) * TILE_WIDTH + i % MAP_WIDTH % TILE_WIDTH];
603 let key = XORCODE[usize::from(tile < 128)];
604 let value = u32::from_le_bytes(*word) ^ key;
605 word.copy_from_slice(&value.to_le_bytes());
606 }
607 let mut archive = vec![0u8; 8];
608 for chunk in expanded.chunks(8) {
609 archive.push(0xff);
610 archive.extend_from_slice(chunk);
611 }
612 let arc_size = archive.len() as u32;
613 archive[..4].copy_from_slice(&arc_size.to_le_bytes());
614 archive[4..8].copy_from_slice(&(expanded.len() as u32).to_le_bytes());
615 xor_u32_in_place(&mut archive, XORCODE[2]);
616 let mut file = 1i32.to_le_bytes().to_vec();
617 file.extend_from_slice(&archive);
618
619 let db = DbsDatabase::from_bytes(&file).unwrap();
620 let row = db.find_str(0, "CG_A").unwrap();
621 assert_eq!(row, 7);
622 assert_eq!(
623 db.get_data_str(row, 1).unwrap().as_deref(),
624 Some("cg_a__base|cg_a")
625 );
626 assert_eq!(db.find_str(0, "missing").unwrap(), -1);
627 }
628}