feat: enma parsing

This commit is contained in:
2024-04-17 18:21:11 +02:00
commit 0f6bfc272c
22 changed files with 5030 additions and 0 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
/.direnv

4374
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

3
Cargo.toml Normal file
View File

@@ -0,0 +1,3 @@
[workspace]
members = ["bevy_nucompat", "nucompat", "enmacompat", "wangan_sunrise"]
resolver = "2"

9
bevy_nucompat/Cargo.toml Normal file
View File

@@ -0,0 +1,9 @@
[package]
name = "bevy_nucompat"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bevy = "0.13"

4
bevy_nucompat/src/lib.rs Normal file
View File

@@ -0,0 +1,4 @@
use bevy::asset::AssetLoader;
#[derive(Asset)]
struct NutAsset {}

10
enmacompat/Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "enmacompat"
version = "0.1.0"
edition = "2021"
[dependencies]
binrw = "0.13"
clap = { version = "4.5.4", features = ["derive"] }
serde = { version = "1.0.198", features = ["derive"] }
serde_json = "1.0.116"

118
enmacompat/src/area.rs Normal file
View File

@@ -0,0 +1,118 @@
use crate::{section::EnmaSection, windows_pe::WindowsPEFile};
use binrw::{BinRead, NullString};
use serde::Serialize;
pub fn find_area_array_location(file: &mut WindowsPEFile) -> Result<Option<u64>, std::io::Error> {
for area_name_addr in file.find_memory_address_of(b"OsDojima")? {
println!("Possible Area Name: {:#x}", area_name_addr);
for area_addr in file.find_references(area_name_addr)? {
let area_addr = area_addr - 8;
if !file.is_exactly(area_addr, &1_u64.to_le_bytes())? {
continue;
}
println!("Possible Area: {:#x}", area_addr);
for area_array_addr in file.find_references(area_addr)? {
let area_array_addr = area_array_addr - 8;
if !file
.is_exactly(area_array_addr, &0_u64.to_le_bytes())
.unwrap_or(false)
{
continue;
}
println!("Possible Area Array: {:#x}", area_array_addr);
if (1..10).all(|i| {
let reference = area_array_addr + i * 8;
file.read_addr::<u64>(reference)
.map(|addr| {
println!("Checking Array Member {} ({:#x})", i, addr);
file.is_exactly(addr, &(i as u64).to_le_bytes())
.unwrap_or(false)
})
.unwrap_or(false)
}) {
return Ok(Some(area_array_addr));
}
}
}
}
Ok(None)
}
pub fn read_area(file: &WindowsPEFile, address: u64) -> Result<EnmaArea, std::io::Error> {
let area = file.read_addr::<EnmaAreaRaw>(address)?;
Ok(EnmaArea {
id: area.id,
name: file.read_addr::<NullString>(area.name_addr)?.to_string(),
stage_id: area.stage_id,
related_area_addr: area.related_area_addr,
unknown: area.unk2,
sections: (0..area.section_count)
.map(|i| file.read_addr::<EnmaSection>(area.sections_addr + i as u64 * 0x24))
.collect::<Result<_, _>>()?,
})
}
#[derive(Debug, Serialize)]
pub struct EnmaArea {
pub id: u64,
pub name: String,
pub stage_id: u64,
pub related_area_addr: [u64; 4],
pub unknown: [f32; 4],
pub sections: Vec<EnmaSection>,
}
#[derive(BinRead, Debug)]
#[br(little)]
pub struct EnmaAreaRaw {
pub id: u64,
pub name_addr: u64,
pub stage_id: u64,
pub related_area_addr: [u64; 4],
pub unk2: [f32; 4],
pub sections_addr: u64,
pub section_count: i32,
pub unk1: [f32; 3],
pub bank_left_addr: u64,
#[br(pad_before = 8)]
pub bank_right_addr: u64,
#[br(pad_before = 8)]
pub zebra_left_addr: u64,
#[br(pad_before = 8)]
pub zebra_right_addr: u64,
#[br(pad_before = 8)]
pub gaps_addr: u64,
#[br(pad_before = 8)]
pub non_guard_left_addr: u64,
#[br(pad_before = 8)]
pub non_guard_right_addr: u64,
#[br(pad_before = 8)]
pub speed_addr: u64,
#[br(pad_before = 8)]
pub lane_addr: u64,
#[br(pad_before = 8)]
pub other_addr: u64,
#[br(pad_before = 8)]
pub non_lane_change_addr: u64,
#[br(pad_before = 8)]
pub signs_addr: u64,
#[br(pad_before = 8)]
pub notices_addr: u64,
#[br(pad_before = 8)]
pub watches_addr: u64,
#[br(pad_before = 8)]
pub on_comers_addr: u64,
#[br(pad_before = 8)]
pub pillers_addr: u64,
}

4
enmacompat/src/lib.rs Normal file
View File

@@ -0,0 +1,4 @@
pub mod area;
pub mod section;
mod util;
pub mod windows_pe;

40
enmacompat/src/main.rs Normal file
View File

@@ -0,0 +1,40 @@
use clap::{command, Parser};
use enmacompat::{
area::{find_area_array_location, read_area, EnmaAreaRaw},
windows_pe::WindowsPEFile,
};
use std::fs::File;
#[derive(Parser, Debug)]
#[command(version, about)]
struct Args {
#[clap(short, long)]
path: std::path::PathBuf,
}
fn main() {
let args = Args::parse();
let file = File::open(&args.path).expect("Failed to open file");
let mut windows_pe_file = WindowsPEFile::from(file);
println!("\x1b[34m\x1b[1mSearching for Area Array Location\x1b[0m");
let location = Some(0x1417cc980); //find_area_array_location(&mut windows_pe_file).unwrap();
if let Some(location) = location {
println!(
"\x1b[32m\x1b[1mArea Array Location\x1b[0m: \x1b[4m{:#x}\x1b[0m (Memory), \x1b[4m{:#x}\x1b[0m (File)",
location,
windows_pe_file.get_file_address(location).unwrap()
);
for i in 1..2 {
let address = windows_pe_file.read_addr::<u64>(location + i * 8).unwrap();
let area = read_area(&windows_pe_file, address).unwrap();
let file = File::create(format!("{}.json", area.name)).unwrap();
let mut writer = std::io::BufWriter::new(file);
serde_json::to_writer_pretty(&mut writer, &area).unwrap();
}
} else {
println!("\x1b[31mArea array location could not found\x1b[0m");
}
}

16
enmacompat/src/section.rs Normal file
View File

@@ -0,0 +1,16 @@
use binrw::BinRead;
use serde::Serialize;
#[derive(Debug, BinRead, Serialize)]
pub struct EnmaSection {
pub distance: f32,
pub x: f32,
pub y: f32,
pub angle1: f32,
pub angle2: f32,
pub left: f32,
pub right: f32,
pub height: f32,
pub unk1: f32,
pub unk2: f32,
}

44
enmacompat/src/util.rs Normal file
View File

@@ -0,0 +1,44 @@
use std::io::{Read, Seek, SeekFrom};
pub fn find_in_data<T: Read + Seek>(
data: &mut T,
target: &[u8],
) -> Result<Vec<usize>, std::io::Error> {
let pos = data.stream_position()?;
let mut result = Vec::new();
let mut possible = vec![false; target.len()];
let mut possible_i = 0;
for (i, byte) in data.bytes().enumerate() {
let byte = byte?;
if possible[possible_i] {
result.push(i - target.len());
}
possible[possible_i] = byte == target[possible_i];
for j in 0..possible.len() {
let target_index = (possible_i + j) % target.len();
possible[j] = possible[j] && byte == target[target_index];
}
possible_i = (possible_i + 1) % target.len();
}
data.seek(SeekFrom::Start(pos))?;
Ok(result)
}
pub fn is_exactly<T: Read + Seek>(
data: &mut T,
location: usize,
target: &[u8],
) -> Result<bool, std::io::Error> {
let pos = data.stream_position()?;
data.seek(SeekFrom::Start(location as u64))?;
let mut buf = [0; 8];
let result = data.read_exact(&mut buf);
data.seek(SeekFrom::Start(pos))?;
Ok(result.is_ok() && buf == target)
}

View File

@@ -0,0 +1,164 @@
use std::{fs::File, io::Seek};
use binrw::{io::BufReader, BinRead, BinReaderExt, Endian};
use crate::util::{find_in_data, is_exactly};
#[derive(BinRead)]
#[br(little, magic = b"MZ")]
pub struct WindowsPEHeader {
pub last_page_size: u16,
pub pages_in_file: u16,
pub relocations: u16,
pub header_size: u16,
pub min_memory: u16,
pub max_memory: u16,
pub initial_ss: u16,
pub initial_sp: u16,
pub checksum: u16,
pub initial_ip: u16,
pub initial_cs: u16,
pub relocations_offset: u16,
pub overlay_number: u16,
pub reserved: [u16; 4],
pub oem_id: u16,
pub oem_info: u16,
pub reserved2: [u16; 10],
pub pe_offset: u32,
#[br(pad_before = pe_offset - 0x40)]
pub image_nt_headers: WindowsPEImageNTHeaders,
}
#[derive(BinRead)]
#[br(little, magic = b"PE\0\0")]
pub struct WindowsPEImageNTHeaders {
pub machine: u16,
pub number_of_sections: u16,
pub time_date_stamp: u32,
pub pointer_to_symbol_table: u32,
pub number_of_symbols: u32,
pub size_of_optional_header: u16,
pub characteristics: u16,
#[br(pad_size_to = size_of_optional_header)]
pub optional_header: WindowsPEOptionalHeader,
#[br(count = number_of_sections)]
pub sections: Vec<WindowsPEImageSectionHeader>,
}
#[derive(BinRead)]
#[br(little, magic = b"\x0b\x02")]
pub struct WindowsPEOptionalHeader {
pub major_linker_version: u8,
pub minor_linker_version: u8,
pub size_of_code: u32,
pub size_of_initialized_data: u32,
pub size_of_uninitialized_data: u32,
pub address_of_entry_point: u32,
pub base_of_code: u32,
pub image_base: u64,
}
#[derive(BinRead)]
#[br(little)]
pub struct WindowsPEImageSectionHeader {
pub name: [u8; 8],
pub virtual_size: u32,
pub virtual_address: u32,
pub size_of_raw_data: u32,
pub pointer_to_raw_data: u32,
pub pointer_to_relocations: u32,
pub pointer_to_linenumbers: u32,
pub number_of_relocations: u16,
pub number_of_linenumbers: u16,
pub characteristics: u32,
}
pub struct WindowsPEFile {
pub file: File,
pub header: WindowsPEHeader,
}
impl WindowsPEFile {
pub fn get_file_address(&self, memory_address: u64) -> Option<u64> {
let image_base = self.header.image_nt_headers.optional_header.image_base;
let section = self
.header
.image_nt_headers
.sections
.iter()
.find(|section| {
let virtual_address = section.virtual_address as u64 + image_base;
memory_address >= virtual_address
&& memory_address < virtual_address + section.virtual_size as u64
})?;
let offset = memory_address - section.virtual_address as u64 - image_base as u64
+ section.pointer_to_raw_data as u64;
Some(offset)
}
pub fn get_memory_address(&self, file_address: u64) -> Option<u64> {
let image_base = self.header.image_nt_headers.optional_header.image_base;
let section = self
.header
.image_nt_headers
.sections
.iter()
.find(|section| {
file_address >= section.pointer_to_raw_data as u64
&& file_address
< section.pointer_to_raw_data as u64 + section.size_of_raw_data as u64
})?;
let offset = file_address - section.pointer_to_raw_data as u64
+ image_base as u64
+ section.virtual_address as u64;
Some(offset)
}
pub fn find_memory_address_of(&self, target: &[u8]) -> Result<Vec<u64>, std::io::Error> {
let mut reader = BufReader::new(&self.file);
reader.seek(std::io::SeekFrom::Start(0))?;
let file_addr = find_in_data(&mut reader, target)?;
Ok(file_addr
.iter()
.map(|file_addr| self.get_memory_address(*file_addr as u64).unwrap())
.collect())
}
pub fn find_references(&self, address: u64) -> Result<Vec<u64>, std::io::Error> {
self.find_memory_address_of(&address.to_le_bytes())
}
pub fn is_exactly(&self, address: u64, target: &[u8]) -> Result<bool, std::io::Error> {
let mut reader = BufReader::new(&self.file);
self.get_file_address(address)
.map(|file_addr| is_exactly(&mut reader, file_addr as usize, target))
.unwrap_or(Ok(false))
}
pub fn read_addr<'a, T>(&self, address: u64) -> Result<T, std::io::Error>
where
T: BinRead,
T::Args<'a>: Default,
{
let mut reader = BufReader::new(&self.file);
if let Some(file_addr) = self.get_file_address(address) {
reader.seek(std::io::SeekFrom::Start(file_addr as u64))?;
reader
.read_type::<T>(Endian::Little)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
} else {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Address not found",
))
}
}
}
impl From<std::fs::File> for WindowsPEFile {
fn from(file: std::fs::File) -> Self {
let mut reader = binrw::io::BufReader::new(&file);
let header = reader.read_ne().unwrap();
Self { file, header }
}
}

130
flake.lock generated Normal file
View File

@@ -0,0 +1,130 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1712883908,
"narHash": "sha256-icE1IJE9fHcbDfJ0+qWoDdcBXUoZCcIJxME4lMHwvSM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a0c9e3aee1000ac2bfb0e5b98c94c946a5d180a9",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1706487304,
"narHash": "sha256-LE8lVX28MV2jWJsidW13D2qrHU/RUUONendL2Q/WlJg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "90f456026d284c22b3e3497be980b2e47d0b28ac",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1712973993,
"narHash": "sha256-ZJxC6t2K0UAPW+lV+bJ+pAtwbn29eqZQzXLTG54oL+I=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "a497535d074432133b683dda3a1faa8c8ab587ad",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

45
flake.nix Normal file
View File

@@ -0,0 +1,45 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
rust-overlay.url = "github:oxalica/rust-overlay";
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
{
self,
nixpkgs,
flake-utils,
rust-overlay,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
overlays = [ (import rust-overlay) ];
pkgs = import nixpkgs { inherit system overlays; };
rust-bin = pkgs.rust-bin.stable.latest.default.override {
extensions = [
"rust-src"
"rust-std"
"clippy"
"rust-analyzer"
];
};
in
{
devShell = pkgs.mkShell rec {
nativeBuildInputs = with pkgs; [
rust-bin
pkg-config
];
buildInputs = with pkgs; [
udev
alsa-lib
vulkan-loader
libxkbcommon
wayland
];
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;
};
}
);
}

7
nucompat/Cargo.toml Normal file
View File

@@ -0,0 +1,7 @@
[package]
name = "nucompat"
version = "0.1.0"
edition = "2021"
[dependencies]
binrw = "0.13"

0
nucompat/src/lib.rs Normal file
View File

1
nucompat/src/main.rs Normal file
View File

@@ -0,0 +1 @@
fn main() {}

View File

@@ -0,0 +1,7 @@
[package]
name = "wangan_sunrise"
version = "0.1.0"
edition = "2021"
[dependencies]
bevy = { version = "0.13", features = ["wayland"] }

View File

@@ -0,0 +1,43 @@
use std::{env, fs::File, path::Path};
use bevy::{
asset::io::{AssetReader, AssetReaderError, Reader},
utils::BoxedFuture,
};
struct GameAssetReader(Box<dyn AssetReader>);
fn get_game_dir() -> String {
env::var("GAME_DIR").unwrap_or(".".to_string())
}
impl AssetReader for GameAssetReader {
fn read<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
let full_path = path.join(get_game_dir());
self.0.read(&full_path)
}
fn read_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
todo!()
}
fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<bevy::asset::io::PathStream>, AssetReaderError>> {
todo!()
}
fn is_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<bool, AssetReaderError>> {
todo!()
}
}

View File

@@ -0,0 +1 @@
pub mod game_asset_reader;

View File

@@ -0,0 +1,7 @@
mod assets;
use bevy::{app::App, asset::io::AssetReader, DefaultPlugins};
fn main() {
App::new().add_plugins(DefaultPlugins).run();
}