1use std::fs::File;
2use std::io::{Read, Write};
3use std::path::Path;
4
5use byteorder::{BigEndian, ByteOrder, LittleEndian};
6
7use crate::entry::Entry;
8use crate::error::{JMapError, Result};
9use crate::field::{Field, FieldType, FieldValue};
10use crate::hash::HashTable;
11use crate::jmap::JMapInfo;
12
13#[derive(Debug, Clone)]
15pub struct IoOptions {
16 pub big_endian: bool,
18 pub encoding: Encoding,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum Encoding {
25 ShiftJis,
27 Utf8,
29}
30
31impl Default for IoOptions {
32 fn default() -> Self {
33 Self {
34 big_endian: true,
35 encoding: Encoding::ShiftJis,
36 }
37 }
38}
39
40impl IoOptions {
41 pub fn super_mario_galaxy() -> Self {
43 Self {
44 big_endian: true,
45 encoding: Encoding::ShiftJis,
46 }
47 }
48}
49
50pub fn from_buffer<H: HashTable>(
63 hash_table: H,
64 data: &[u8],
65 options: &IoOptions,
66) -> Result<JMapInfo<H>> {
67 let mut jmap = JMapInfo::new(hash_table);
68
69 if data.len() < 0x10 {
71 return Err(JMapError::BufferTooSmall {
72 expected: 0x10,
73 got: data.len(),
74 });
75 }
76
77 let (num_entries, num_fields, off_data, entry_size) = if options.big_endian {
79 (
80 BigEndian::read_u32(&data[0x00..0x04]),
81 BigEndian::read_u32(&data[0x04..0x08]),
82 BigEndian::read_u32(&data[0x08..0x0C]),
83 BigEndian::read_u32(&data[0x0C..0x10]),
84 )
85 } else {
86 (
87 LittleEndian::read_u32(&data[0x00..0x04]),
88 LittleEndian::read_u32(&data[0x04..0x08]),
89 LittleEndian::read_u32(&data[0x08..0x0C]),
90 LittleEndian::read_u32(&data[0x0C..0x10]),
91 )
92 };
93
94 jmap.entry_size = entry_size;
95
96 let off_strings = off_data as usize + (num_entries as usize * entry_size as usize);
100
101 let mut off = 0x10_usize;
103 for _ in 0..num_fields {
104 let field = read_field(data, off, options.big_endian)?;
105 jmap.fields_map_mut().insert(field.hash, field);
106 off += 0x0C;
107 }
108
109 off = off_data as usize;
111 for _ in 0..num_entries {
112 let entry = read_entry(data, off, off_strings, &jmap, options)?;
113 jmap.entries_vec_mut().push(entry);
114 off += entry_size as usize;
115 }
116
117 Ok(jmap)
118}
119
120pub fn from_file<H: HashTable, P: AsRef<Path>>(
134 hash_table: H,
135 path: P,
136 options: &IoOptions,
137) -> Result<JMapInfo<H>> {
138 let mut file = File::open(path)?;
139 let mut data = Vec::new();
140 file.read_to_end(&mut data)?;
141 from_buffer(hash_table, &data, options)
142}
143
144pub fn to_buffer<H: HashTable>(jmap: &JMapInfo<H>, options: &IoOptions) -> Result<Vec<u8>> {
159 let num_entries = jmap.len() as u32;
160 let num_fields = jmap.num_fields() as u32;
161 let off_data = 0x10 + num_fields * 0x0C; let mut fields_with_offsets: Vec<(u32, Field)> = jmap
165 .fields()
166 .map(|f| (f.hash, f.clone()))
167 .collect();
168
169 fields_with_offsets.sort_by_key(|(_, f)| f.field_type.order());
171
172 let mut current_offset: u16 = 0;
173 for (_, field) in &mut fields_with_offsets {
174 field.offset = current_offset;
175 current_offset += field.field_type.size() as u16;
176 }
177
178 let entry_size = ((current_offset as u32 + 3) & !3) as u32;
180
181 let mut buffer = vec![0u8; (off_data + num_entries * entry_size) as usize];
183
184 if options.big_endian {
186 BigEndian::write_u32(&mut buffer[0x00..0x04], num_entries);
187 BigEndian::write_u32(&mut buffer[0x04..0x08], num_fields);
188 BigEndian::write_u32(&mut buffer[0x08..0x0C], off_data);
189 BigEndian::write_u32(&mut buffer[0x0C..0x10], entry_size);
190 } else {
191 LittleEndian::write_u32(&mut buffer[0x00..0x04], num_entries);
192 LittleEndian::write_u32(&mut buffer[0x04..0x08], num_fields);
193 LittleEndian::write_u32(&mut buffer[0x08..0x0C], off_data);
194 LittleEndian::write_u32(&mut buffer[0x0C..0x10], entry_size);
195 }
196
197 let field_offsets: std::collections::HashMap<u32, &Field> = fields_with_offsets
199 .iter()
200 .map(|(hash, field)| (*hash, field))
201 .collect();
202
203 let mut off = 0x10_usize;
205 for (hash, field) in &fields_with_offsets {
206 write_field(&mut buffer, off, *hash, field, options.big_endian);
207 off += 12;
208 }
209
210 let mut string_table: Vec<u8> = Vec::new();
212 let mut string_offsets: std::collections::HashMap<String, u32> = std::collections::HashMap::new();
213
214 off = off_data as usize;
216 for entry in jmap.entries() {
217 write_entry(
218 &mut buffer,
219 off,
220 entry,
221 &field_offsets,
222 &mut string_table,
223 &mut string_offsets,
224 options,
225 )?;
226 off += entry_size as usize;
227 }
228
229 buffer.extend_from_slice(&string_table);
231
232 let len = buffer.len();
234 let aligned_len = (len + 31) & !31;
235 buffer.resize(aligned_len, 0x40);
236
237 Ok(buffer)
238}
239
240pub fn to_file<H: HashTable, P: AsRef<Path>>(
254 jmap: &JMapInfo<H>,
255 path: P,
256 options: &IoOptions,
257) -> Result<()> {
258 let buffer = to_buffer(jmap, options)?;
259 let mut file = File::create(path)?;
260 file.write_all(&buffer)?;
261 file.flush()?;
262 Ok(())
263}
264
265fn read_field(data: &[u8], offset: usize, big_endian: bool) -> Result<Field> {
280 let (hash, mask, field_offset, shift, raw_type) = if big_endian {
281 (
282 BigEndian::read_u32(&data[offset..offset + 0x04]),
283 BigEndian::read_u32(&data[offset + 0x04..offset + 0x08]),
284 BigEndian::read_u16(&data[offset + 0x08..offset + 0x0A]),
285 data[offset + 0x0A],
286 data[offset + 0x0B],
287 )
288 } else {
289 (
290 LittleEndian::read_u32(&data[offset..offset + 0x04]),
291 LittleEndian::read_u32(&data[offset + 0x04..offset + 0x08]),
292 LittleEndian::read_u16(&data[offset + 0x08..offset + 0x0A]),
293 data[offset + 0x0A],
294 data[offset + 0x0B],
295 )
296 };
297
298 let field_type = FieldType::from_raw(raw_type)
299 .ok_or(JMapError::InvalidFieldType(raw_type))?;
300
301 Ok(Field {
302 hash,
303 field_type,
304 mask,
305 shift,
306 offset: field_offset,
307 default: FieldValue::default_for(field_type),
308 })
309}
310
311fn write_field(buffer: &mut [u8], offset: usize, hash: u32, field: &Field, big_endian: bool) {
320 if big_endian {
321 BigEndian::write_u32(&mut buffer[offset..offset + 0x04], hash);
322 BigEndian::write_u32(&mut buffer[offset + 0x04..offset + 0x08], field.mask);
323 BigEndian::write_u16(&mut buffer[offset + 0x08..offset + 0x0A], field.offset);
324 } else {
325 LittleEndian::write_u32(&mut buffer[offset..offset + 0x04], hash);
326 LittleEndian::write_u32(&mut buffer[offset + 0x04..offset + 0x08], field.mask);
327 LittleEndian::write_u16(&mut buffer[offset + 0x08..offset + 0x0A], field.offset);
328 }
329 buffer[offset + 0x0A] = field.shift;
330 buffer[offset + 0x0B] = field.field_type as u8;
331}
332
333fn read_entry<H: HashTable>(
345 data: &[u8],
346 entry_offset: usize,
347 string_table_offset: usize,
348 jmap: &JMapInfo<H>,
349 options: &IoOptions,
350) -> Result<Entry> {
351 let mut entry = Entry::with_capacity(jmap.num_fields());
352
353 for field in jmap.fields() {
354 let val_offset = entry_offset + field.offset as usize;
355 let value = read_field_value(data, val_offset, string_table_offset, field, options)?;
356 entry.set_by_hash(field.hash, value);
357 }
358
359 Ok(entry)
360}
361
362fn read_field_value(
376 data: &[u8],
377 offset: usize,
378 string_table_offset: usize,
379 field: &Field,
380 options: &IoOptions,
381) -> Result<FieldValue> {
382 let value = match field.field_type {
383 FieldType::Long | FieldType::UnsignedLong => {
384 let raw = if options.big_endian {
385 BigEndian::read_u32(&data[offset..offset + 4])
386 } else {
387 LittleEndian::read_u32(&data[offset..offset + 4])
388 };
389 let masked = (raw & field.mask) >> field.shift;
390 let signed = if masked & 0x80000000 != 0 {
392 masked as i32
393 } else {
394 masked as i32
395 };
396 FieldValue::Int(signed)
397 }
398
399 FieldType::Float => {
400 let val = if options.big_endian {
401 BigEndian::read_f32(&data[offset..offset + 4])
402 } else {
403 LittleEndian::read_f32(&data[offset..offset + 4])
404 };
405 FieldValue::Float(val)
406 }
407
408 FieldType::Short => {
409 let raw = if options.big_endian {
410 BigEndian::read_u16(&data[offset..offset + 2])
411 } else {
412 LittleEndian::read_u16(&data[offset..offset + 2])
413 };
414 let masked = ((raw as u32) & field.mask) >> field.shift;
415 let signed = if masked & 0x8000 != 0 {
416 (masked | 0xFFFF0000) as i32
417 } else {
418 masked as i32
419 };
420 FieldValue::Int(signed)
421 }
422
423 FieldType::Char => {
424 let raw = data[offset];
425 let masked = ((raw as u32) & field.mask) >> field.shift;
426 let signed = if masked & 0x80 != 0 {
427 (masked | 0xFFFFFF00) as i32
428 } else {
429 masked as i32
430 };
431 FieldValue::Int(signed)
432 }
433
434 FieldType::String => {
435 let end = data[offset..offset + 32]
437 .iter()
438 .position(|&b| b == 0)
439 .unwrap_or(32);
440 let bytes = &data[offset..offset + end];
441 let s = decode_string(bytes, options.encoding)?;
442 FieldValue::String(s)
443 }
444
445 FieldType::StringOffset => {
446 let str_offset = if options.big_endian {
447 BigEndian::read_u32(&data[offset..offset + 4])
448 } else {
449 LittleEndian::read_u32(&data[offset..offset + 4])
450 };
451 let str_start = string_table_offset + str_offset as usize;
452 let end = data[str_start..]
453 .iter()
454 .position(|&b| b == 0)
455 .unwrap_or(0);
456 let bytes = &data[str_start..str_start + end];
457 let s = decode_string(bytes, options.encoding)?;
458 FieldValue::String(s)
459 }
460 };
461
462 Ok(value)
463}
464
465fn write_entry(
479 buffer: &mut [u8],
480 entry_offset: usize,
481 entry: &Entry,
482 field_offsets: &std::collections::HashMap<u32, &Field>,
483 string_table: &mut Vec<u8>,
484 string_offsets: &mut std::collections::HashMap<String, u32>,
485 options: &IoOptions,
486) -> Result<()> {
487 for (hash, value) in entry.iter() {
488 if let Some(field) = field_offsets.get(hash) {
489 let val_offset = entry_offset + field.offset as usize;
490 write_field_value(
491 buffer,
492 val_offset,
493 value,
494 field,
495 string_table,
496 string_offsets,
497 options,
498 )?;
499 }
500 }
501 Ok(())
502}
503
504fn write_field_value(
518 buffer: &mut [u8],
519 offset: usize,
520 value: &FieldValue,
521 field: &Field,
522 string_table: &mut Vec<u8>,
523 string_offsets: &mut std::collections::HashMap<String, u32>,
524 options: &IoOptions,
525) -> Result<()> {
526 match (field.field_type, value) {
527 (FieldType::Long | FieldType::UnsignedLong, FieldValue::Int(v)) => {
528 let existing = if options.big_endian {
529 BigEndian::read_u32(&buffer[offset..offset + 4])
530 } else {
531 LittleEndian::read_u32(&buffer[offset..offset + 4])
532 };
533 let masked = (existing & !field.mask) | (((*v as u32) << field.shift) & field.mask);
534 if options.big_endian {
535 BigEndian::write_u32(&mut buffer[offset..offset + 4], masked);
536 } else {
537 LittleEndian::write_u32(&mut buffer[offset..offset + 4], masked);
538 }
539 }
540
541 (FieldType::Float, FieldValue::Float(v)) => {
542 if options.big_endian {
543 BigEndian::write_f32(&mut buffer[offset..offset + 4], *v);
544 } else {
545 LittleEndian::write_f32(&mut buffer[offset..offset + 4], *v);
546 }
547 }
548
549 (FieldType::Short, FieldValue::Int(v)) => {
550 let existing = if options.big_endian {
551 BigEndian::read_u16(&buffer[offset..offset + 2])
552 } else {
553 LittleEndian::read_u16(&buffer[offset..offset + 2])
554 };
555 let masked = ((existing as u32 & !field.mask) | (((*v as u32) << field.shift) & field.mask)) as u16;
556 if options.big_endian {
557 BigEndian::write_u16(&mut buffer[offset..offset + 2], masked);
558 } else {
559 LittleEndian::write_u16(&mut buffer[offset..offset + 2], masked);
560 }
561 }
562
563 (FieldType::Char, FieldValue::Int(v)) => {
564 let existing = buffer[offset] as u32;
565 let masked = ((existing & !field.mask) | (((*v as u32) << field.shift) & field.mask)) as u8;
566 buffer[offset] = masked;
567 }
568
569 (FieldType::String, FieldValue::String(s)) => {
570 let bytes = encode_string(s, options.encoding)?;
571 let len = bytes.len().min(32);
572 buffer[offset..offset + len].copy_from_slice(&bytes[..len]);
573 }
574
575 (FieldType::StringOffset, FieldValue::String(s)) => {
576 let str_offset = if let Some(&existing_offset) = string_offsets.get(s) {
577 existing_offset
578 } else {
579 let offset = string_table.len() as u32;
580 let bytes = encode_string(s, options.encoding)?;
581 string_table.extend_from_slice(&bytes);
582 string_table.push(0); string_offsets.insert(s.clone(), offset);
584 offset
585 };
586
587 if options.big_endian {
588 BigEndian::write_u32(&mut buffer[offset..offset + 4], str_offset);
589 } else {
590 LittleEndian::write_u32(&mut buffer[offset..offset + 4], str_offset);
591 }
592 }
593
594 _ => {
595 return Err(JMapError::TypeMismatch {
596 expected: field.field_type.csv_name(),
597 got: value.type_name(),
598 });
599 }
600 }
601
602 Ok(())
603}
604
605fn decode_string(bytes: &[u8], encoding: Encoding) -> Result<String> {
617 match encoding {
618 Encoding::Utf8 => String::from_utf8(bytes.to_vec())
619 .map_err(|e| JMapError::EncodingError(e.to_string())),
620 Encoding::ShiftJis => {
621 let (decoded, _, had_errors) = encoding_rs::SHIFT_JIS.decode(bytes);
622 if had_errors {
623 }
625 Ok(decoded.into_owned())
626 }
627 }
628}
629
630fn encode_string(s: &str, encoding: Encoding) -> Result<Vec<u8>> {
639 match encoding {
640 Encoding::Utf8 => Ok(s.as_bytes().to_vec()),
641 Encoding::ShiftJis => {
642 let (encoded, _, _) = encoding_rs::SHIFT_JIS.encode(s);
643 Ok(encoded.into_owned())
644 }
645 }
646}