某ゲームのセーブデータの内容を読みやすく表示するプログラム

使い方

ROMのファイルシステムにあるcnvdat.binをカレントディレクトリにおいておく。 セーブデータをscrambled.binという名前でカレントディレクトリにおいておく。

実行すると次のような出力を得る。

use std::fs::File;
use std::io::{self, Read};
use std::collections::HashMap;
use std::cell::RefCell;
use std::rc::Rc;
use std::collections::HashSet;

struct Command {
    typename: String,
    name: String,
    num_bits_in_file: usize,
    num_bits_in_game: usize,
    number: usize,
    unknown1: usize,
    unknown2: bool
}

type Struct = Vec<Member>;

type StructMap = HashMap<String, Rc<RefCell<Struct>>>;

#[derive(Debug)]
struct Member {
    name : String,
    number: usize,
    tp : MemberType,
}

#[derive(Debug)]
enum MemberType {
    Int(usize),
    Struct(String)
}

fn unscramble(binary : &mut Vec<u8>) {
    let mut random_bytes = [0; 3 * 4];
    let mut random = [0; 3];
    let mut random_pool: [u16; 256] = [0; 256];
    //let size = binary.len() - 12;
    let size = 32640 - 12;
    let a = size / 16;
    let b = if a == 0 {
        0xB5BDCF5A_u32 >> 0x10
    } else {
        (a as u32) * (0xB5BDCF5A_u32 >> 0x10) >> 0x10
    } as usize;
    let index = b + 0x20;

    let mut binary_index = index;
    let mut seed = 0xB5BDCF5A_u32;
    for i in 0..12 {
    seed = 0x5D588B65_u32.wrapping_mul(seed).wrapping_add(0x269EC3_u32);
    let value = if a == 0 {
        seed >> 16
    } else {
        (seed >> 16) * (a as u32) >> 0x10
    };
    binary_index += (value as usize) + 2;
    random_bytes[i] = binary[binary_index];
    binary[binary_index] = binary[size + i];
    }
    for i in 0..3 {
        random[i] = u32::from_le_bytes(random_bytes[i * 4..i * 4 + 4].try_into().unwrap());
    }
    for i in 0..256 {
    random[0] = 0x5D588B65_u32.wrapping_mul(random[0]).wrapping_add(0x269EC3);
    random_pool[i] = (random[0] >> 0x10) as u16;
    }
    for i in 0..size {
        let rem =  (i + (size * (random_pool[i % 256] as usize)) / 0x10000) % size;
        if ((i < 4) || (19 < i)) && ((rem < 4) || (19 < rem)) {
            binary.swap(i, rem);
        }
    }
    for i in 0..size {
        random[1] = 0x5D588B65_u32.wrapping_mul(random[1]).wrapping_add(0x269EC3_u32);
        if i < 4 || 19 < i {
            binary[i] ^= (random[1] >> 24) as u8;
        }
    }
    for i in 0..size {
        random[2] = 0x5D588B65_u32.wrapping_mul(random[2]).wrapping_add(0x269EC3_u32);
        if i < 4 || 19 < i {
            let shift = ((random[2] >> 0x10) << 3) >> 0x10;
            binary[i] = (((binary[i] as u16) << 8 | (binary[i] as u16)) >> shift) as u8;
        }
    }
}

fn load_binary_file(path : &str) -> io::Result<Vec<u8>> {
    let mut file = File::open(path)?;
    let mut binary = Vec::new();
    file.read_to_end(&mut binary)?;
    return Ok(binary);
}

fn read_u8(binary : & Vec<u8>, index : usize) -> u8 {
    binary[index]
}

fn read_u16(binary : & Vec<u8>, index : usize) -> u16 {
    u16::from_le_bytes(binary[index..index + 2].try_into().unwrap())
}

fn read_cstr(binary : & Vec<u8>, index : usize) -> String {
    let maxlength = 256;
    let mut vec = vec![];
    for i in index..index+maxlength {
        let byte = binary[i];
        if byte == 0 { break; }
        vec.push(byte);
    }
    return String::from_utf8(vec).unwrap();
}

fn make_structs(cnvdat : &Vec<u8>) -> StructMap {
    let max_commands = read_u16(&cnvdat, 8) as usize;
    let max_types = read_u16(&cnvdat, 10) as usize;
    let mut strings = vec![];
    let mut commands = vec![];
    for i in 0..max_types {
        let offset = read_u16(&cnvdat, 16 + max_commands * 8 + i * 2) as usize;
        let str = read_cstr(&cnvdat, 16 + max_commands * 8 + max_types * 2 + offset);
        strings.push(str);
    }
    for i in 0..max_commands {
        let value_0 = read_u16(&cnvdat, 16 + i * 8 + 0);
        let value_1 = read_u16(&cnvdat, 16 + i * 8 + 2);
        let value_2 = read_u16(&cnvdat, 16 + i * 8 + 4);
        let value_3 = read_u8(&cnvdat, 16 + i * 8 + 6) & 0x7f;
        let bit = read_u8(&cnvdat, 16 + i * 8 + 6) & 0x80 != 0;
        let value_4 = read_u8(&cnvdat, 16 + i * 8 + 7);
        let str1 = strings[(value_0 & 0x1fff) as usize].clone();
        let str2 = strings[value_1 as usize].clone();
        let command = Command {
            typename: str1,
            name: str2,
            num_bits_in_file: value_4 as usize,
            num_bits_in_game: value_3 as usize,
            number: value_2 as usize,
            unknown1: (value_0 >> 13) as usize,
            unknown2: bit,
        };
        commands.push(command);
    }
    let mut map = StructMap::new();
    let mut stack: Vec<Rc<RefCell<Vec<Member>>>> = vec![];
    let mut counter_unnamed = 0;
    for command in &commands {
        if command.number == 0 {
            stack.pop();
        } else if command.typename == "@" {
            let member = Member { name: command.name.clone(), number: command.number, tp: MemberType::Int(command.num_bits_in_file) };
            if command.unknown1 & 1 == 0 {
                stack.last().unwrap().try_borrow_mut().unwrap().push(member);
            }
        } else if command.num_bits_in_game == 0 {
            if command.typename == "*" {
                let struct_name = format!("unnamed{}", counter_unnamed);
                counter_unnamed += 1;
                let member = Member { name: command.name.clone(), number: command.number, tp: MemberType::Struct(struct_name.clone()) };
                stack.last().unwrap().try_borrow_mut().unwrap().push(member);
                let strct = Rc::new(RefCell::new(vec![]));
                map.insert(struct_name.clone(), strct.clone());
                stack.push(strct.clone());
            } else if command.typename == "" {
                stack.pop();
            } else {
                let member = Member { name: command.name.clone(), number: command.number, tp: MemberType::Struct(command.typename.clone()) };
                stack.last().unwrap().try_borrow_mut().unwrap().push(member);
            }
        } else {
            if stack.len() > 0 {
                stack.pop();
            }
            let strct = Rc::new(RefCell::new(vec![]));
            map.insert(command.typename.clone(), strct.clone());
            stack.push(strct.clone());
        }
    }
    //dbg!(&map);
    return map;
}

fn show_paths(bin: &Vec<u8>, map: &StructMap, path: &str, offset: usize, name: &str) -> usize {
    let str_members : HashSet<String> = HashSet::from([
        "magicnumber".to_string(),
        "username".to_string(),
        "teamname".to_string(),
        "userName".to_string(),
        "mapname".to_string(),
        "mapfile".to_string(),
        "caption".to_string(),
        "string".to_string(),
        "mapinfoFile".to_string(),
        "effectname".to_string(),
        "name".to_string(),
        "nameWC".to_string(),
        "message".to_string(),
    ]);
    let members = map.get(name).unwrap().borrow();
    let mut off = 0;
    for member in members.iter() {
        match &member.tp {
            &MemberType::Int(num_bits) => {
                let values = read_values(bin, offset + off, num_bits, member.number);
                if str_members.contains(&member.name) {
                    let bytes: Vec<u8> = values.into_iter().map(|v| v as u8).take_while(|&v| v != 0).collect::<Vec<u8>>();
                    let (str, _, _) = encoding_rs::SHIFT_JIS.decode(&bytes);
                    println!("{}.{} = \"{}\"", path, member.name, str);
                } else {
                    print!("{}.{} = [", path, member.name);
                    for i in 0..values.len() {
                        if i > 0 {
                            print!(", ");
                        }
                        print!("{}", values[i]);
                    }
                    println!("]");
                }
                off += member.number * num_bits;
            }
            MemberType::Struct(nm) => {
                if member.number == 1 {
                    off += show_paths(&bin, &map, &format!("{}.{}", path, member.name), offset + off, &nm);
                } else {
                    for i in 0..member.number {
                        off += show_paths(&bin, &map, &format!("{}.{}[{}]", path, member.name, i), offset + off, &nm);
                    }
                }
            }
        }
    }
    return off;
}

fn read_values(bin : &Vec<u8>, index: usize, num_bits: usize, number: usize) -> Vec<usize> {
    let mut values = vec![];
    for i in 0..number {
        let value = read_bits(bin, index + i * num_bits, num_bits);
        values.push(value);
    }
    return values;
}

fn read_bits(bin : &Vec<u8>, index: usize, num_bits: usize) -> usize {
    let mut res = 0;
    for i in 0..num_bits {
        res |= read_bit(bin, index + i) << i;
    }
    res
}

fn read_bit(bin : &Vec<u8>, index: usize) -> usize {
    ((bin[index / 8] >> (index % 8)) & 1) as usize
}

fn show_all(binary: &Vec<u8>, cnvdat: &Vec<u8>) {
    let map = make_structs(&cnvdat);
    show_paths(&binary, &map, "", 0, "st_all");
}

fn main() -> io::Result<()> {
    let cnvdat = load_binary_file("cnvdat.bin")?;
    let mut binary = load_binary_file("scrambled.bin")?;
    unscramble(&mut binary);
    show_all(&binary, &cnvdat);
    Ok(())
}