某ゲームのセーブデータのスクランブルを解除するプログラム

このプログラムを使うとセーブデータのスクランブルを解除し、以下のようなファイルを得ることができます。

これに関連するTODOは以下の通り

  • スクランブルをかける方のプログラムも書く
  • チェックサムを直すプログラムも書く
  • セーブデータの各領域が何を意味しているかはROMの中のあるファイルに従えばわかるので、それを使って人間に見やすく表示するプログラムを書く

以下はRustで書かれたプログラムです。

カレントディレクトリのファイルscrambled.bin (32640バイトのもの)を読み込み、スクランブルを解除し、ファイルunscrambled.binに出力します。

use std::fs::File;
use std::io::{self, Read, Write};

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 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 main() -> io::Result<()> {
    let file_path: &str = "scrambled.bin";
    let mut file = File::open(file_path)?;
    let mut binary = Vec::new();
    file.read_to_end(&mut binary)?;
    unscramble(&mut binary);
    let file_path = "unscrambled.bin";
    let mut file = File::create(file_path)?;
    file.write(&binary)?;
    Ok(())
}