Skip to content

Commit

Permalink
v0.4.0 (#5)
Browse files Browse the repository at this point in the history
- Added logging to help troubleshoot crashes and inventory issues
- Added a failsafe that will clean up box inventory issues when loading a save and when closing the box
- Fixed an issue where moving right from a two-slot item in the bottom row would not advance to the next row of the box
- Fixed an issue where moving down from a one-slot item to a two-slot item could result in half of the item being selected
- Removed .cargo/config.toml as this was really only relevant for my personal setup
  • Loading branch information
descawed authored Oct 4, 2023
1 parent 96167ac commit d06cf99
Show file tree
Hide file tree
Showing 10 changed files with 973 additions and 293 deletions.
2 changes: 0 additions & 2 deletions .cargo/config.toml

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target
/Cargo.lock
.cargo/
8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "re0box"
version = "0.3.1"
version = "0.4.0"
authors = ["descawed <[email protected]>"]
edition = "2021"
description = "An item box mod for Resident Evil 0"
Expand All @@ -20,9 +20,11 @@ panic = "abort"

[dependencies]
anyhow = "1.0"
binrw = "0.11"
binrw = "0.12"
configparser = "3.0"
windows = { version = "0.51", features = [ "Win32_Foundation", "Win32_System_Memory", "Win32_System_SystemServices", "Win32_System_Threading" ] }
log = "0.4"
simplelog = "0.12"
windows = { version = "0.51", features = [ "Win32_Foundation", "Win32_System_Diagnostics_Debug", "Win32_System_Memory", "Win32_System_ProcessStatus", "Win32_System_Kernel", "Win32_System_SystemServices", "Win32_System_Threading" ] }

[build-dependencies]
winresource = "0.1"
29 changes: 22 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,35 @@ file (see the Configuration section below).
The mod comes with a configuration file called re0box.ini with a couple options. You'll find it in your Resident Evil 0
folder and you can open it with any text editor. Note that changes to this file won't take effect while the game is
running; you'll need to restart the game for it to pick up any changes.

**Enable**

This section controls whether mod features are enabled.

- Mod: this controls whether the mod is enabled. When Mod=1 (which is the default), the mod is enabled. If you change it
to Mod=0, the mod is disabled.
- Leave: this controls whether you're allowed to drop items (the "Leave" option in the inventory). The default is
Leave=0, meaning you're not allowed to drop items, because I think that having both the item box and the ability to
drop items is OP. But if you want both, you can change it to Leave=1, and then you'll be able to drop items and still
access the item box.

**Log**

This section controls logging behavior.

- Level: this controls how much information is logged. The options are off, error, warn, info, debug, and trace, where
each option logs progressively more information. High log levels (such as trace) may impact performance but are
useful for troubleshooting issues like crashes. The default is info.
- Path: path to the log file, relative to the game folder. The default is re0box.log. If you don't want it to go in the
game folder, you can also use an absolute path for a different location, such as C:\Users\Bob\Documents\re0box.log.

## Uninstall
Delete scripts\re0box.asi from the Resident Evil 0 folder. None of the other mod files will have any effect once that's
gone, but if you want to purge everything, this is the full list of files added by the mod:
- dinput8.dll (note that other mods may need this file. if you have anything in your scripts folder besides re0box.asi,
you should leave this one alone.)
- re0box.ini
- re0box.log
- re0box_readme.txt
- nativePC\arc\message\msg_chS_box.arc
- nativePC\arc\message\msg_chT_box.arc
Expand All @@ -58,12 +74,11 @@ gone, but if you want to purge everything, this is the full list of files added
- scripts\re0box.asi

## Build
This mod is written in Rust. The default target is i686-windows-pc-gnu because RE0 is a 32-bit game and I'm
cross-compiling from Linux. I imagine the MSVC toolchain would also work but I haven't tested it. As long as Rust and
the appropriate toolchain are installed, you should just be able to do a `cargo build`. The mod is currently distributed
as an ASI plugin using [Ultimate-ASI-Loader](https://github.com/ThirteenAG/Ultimate-ASI-Loader) as the loader. This
helps ensure compatibility with other DLL-based mods. Just rename re0box.dll to re0box.asi and put it in the scripts
folder.
This mod is written in Rust. RE0 is a 32-bit game, so you'll need a 32-bit target installed. Either i686-pc-windows-gnu
or i686-pc-windows-msvc will work. As long as Rust and an appropriate target are installed, you should just be able to
do `cargo build --target=<target>`. The mod is currently distributed as an ASI plugin using
[Ultimate-ASI-Loader](https://github.com/ThirteenAG/Ultimate-ASI-Loader) as the loader. This helps ensure compatibility
with other DLL-based mods. Just rename re0box.dll to re0box.asi and put it in the scripts folder.

Aside from the DLL itself, we also have to edit the game's message files so typewriters prompt to use the box. These are
found in nativePC\arc\message. There's one file for each language the game supports, named in the format
Expand All @@ -85,4 +100,4 @@ This mod was made by descawed. I used a number of existing tools in the making o
- ThirteenAG for [Ultimate ASI Loader](https://github.com/ThirteenAG/Ultimate-ASI-Loader)
- FluffyQuack for [ARCtool](https://residentevilmodding.boards.net/thread/481/)
- onepiecefreak3 for [GMDConverter](https://github.com/onepiecefreak3/GMDConverter)
- ernestin1245 for the Spanish translation
- ErnestJugend for the Spanish translation
6 changes: 6 additions & 0 deletions re0box.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@
Mod=1
; whether you're allowed to use the "Leave" option to drop items. ignored if the mod is disabled.
Leave=0

[Log]
; level of information to log. default is info. options are off, error, warn, info, debug, trace.
Level=info
; path to log file. if the path is not absolute, it will be relative to the game directory.
Path=re0box.log
221 changes: 221 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
use std::ffi::c_void;
use std::fs::File;
use std::ops::BitAnd;
use std::panic;
use std::path::PathBuf;
use std::{cmp, mem};

use anyhow::Result;
use simplelog::{Config, LevelFilter, WriteLogger};
use windows::core::PWSTR;
use windows::Win32::Foundation::{HMODULE, MAX_PATH};
use windows::Win32::System::Diagnostics::Debug::{
AddVectoredExceptionHandler, RemoveVectoredExceptionHandler, CONTEXT_CONTROL_X86,
CONTEXT_DEBUG_REGISTERS_X86, CONTEXT_FLOATING_POINT_X86, CONTEXT_INTEGER_X86,
CONTEXT_SEGMENTS_X86, EXCEPTION_POINTERS,
};
use windows::Win32::System::Kernel::ExceptionContinueSearch;
use windows::Win32::System::Memory::{
VirtualQuery, MEMORY_BASIC_INFORMATION, MEM_COMMIT, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE,
PAGE_PROTECTION_FLAGS, PAGE_READONLY, PAGE_READWRITE,
};
use windows::Win32::System::ProcessStatus::{
EnumProcessModules, GetModuleBaseNameW, GetModuleInformation, MODULEINFO,
};
use windows::Win32::System::Threading::GetCurrentProcess;

const STACK_DUMP_WORDS_PER_LINE: usize = 4;
const STACK_DUMP_LINES: usize = 6;
const READABLE_PROTECT: [PAGE_PROTECTION_FLAGS; 4] = [
PAGE_EXECUTE_READ,
PAGE_EXECUTE_READWRITE,
PAGE_READWRITE,
PAGE_READONLY,
];
const MAX_MODULES: usize = 1000;

unsafe extern "system" fn exception_handler(exc_info: *mut EXCEPTION_POINTERS) -> i32 {
if let Some(exc_info) = exc_info.as_ref() {
// exception details
let mut record_ptr = exc_info.ExceptionRecord;
while let Some(record) = record_ptr.as_ref() {
log::error!(
"Unhandled exception {:08X} at {:08X}. Parameters: {:?}",
record.ExceptionCode.0,
record.ExceptionAddress as usize,
&record.ExceptionInformation[..record.NumberParameters as usize]
);
record_ptr = record.ExceptionRecord;
}

// registers
let mut sp = None;
if let Some(context) = exc_info.ContextRecord.as_ref() {
if context.ContextFlags.bitand(CONTEXT_INTEGER_X86) == CONTEXT_INTEGER_X86 {
log::error!("\tedi = {:08X}\tesi = {:08X}", context.Edi, context.Esi);
log::error!("\tebx = {:08X}\tedx = {:08X}", context.Ebx, context.Edx);
log::error!("\tecx = {:08X}\teax = {:08X}", context.Ecx, context.Eax);
}

if context.ContextFlags.bitand(CONTEXT_CONTROL_X86) == CONTEXT_CONTROL_X86 {
log::error!("\tebp = {:08X}\teip = {:08X}", context.Ebp, context.Eip);
log::error!(
"\tesp = {:08X}\teflags = {:08X}",
context.Esp,
context.EFlags
);
log::error!("\tcs = {:04X}\tss = {:04X}", context.SegCs, context.SegSs);
sp = Some(context.Esp as usize);
}

if context.ContextFlags.bitand(CONTEXT_SEGMENTS_X86) == CONTEXT_SEGMENTS_X86 {
log::error!("\tgs = {:04X}\tfs = {:04X}", context.SegGs, context.SegFs);
log::error!("\tes = {:04X}\tds = {:04X}", context.SegEs, context.SegDs);
}

if context.ContextFlags.bitand(CONTEXT_FLOATING_POINT_X86) == CONTEXT_FLOATING_POINT_X86
{
log::error!("\tfloat: {:?}", context.FloatSave);
}

if context.ContextFlags.bitand(CONTEXT_DEBUG_REGISTERS_X86)
== CONTEXT_DEBUG_REGISTERS_X86
{
log::error!("\tdr0 = {:08X}\tdr1 = {:08X}", context.Dr0, context.Dr1);
log::error!("\tdr2 = {:08X}\tdr3 = {:08X}", context.Dr2, context.Dr3);
log::error!("\tdr6 = {:08X}\tdr7 = {:08X}", context.Dr6, context.Dr7);
}
}

// stack dump if it's valid
if let Some(mut ptr) = sp {
let mut info = MEMORY_BASIC_INFORMATION::default();
let info_size = mem::size_of::<MEMORY_BASIC_INFORMATION>();
let mut region_end = ptr;
log::error!("Stack dump:");
for _ in 0..STACK_DUMP_LINES {
let mut words = [0usize; STACK_DUMP_WORDS_PER_LINE];
let mut exit = false;
let line_addr = ptr;
for word in &mut words {
let mut word_buf = [0u8; mem::size_of::<usize>()];
let bytes_to_copy = cmp::min(region_end - ptr, word_buf.len());
if bytes_to_copy > 0 {
(ptr as *const u8)
.copy_to_nonoverlapping(word_buf.as_mut_ptr(), bytes_to_copy);
}
ptr += bytes_to_copy;
if bytes_to_copy < word_buf.len() {
// we reached the end of the region; need to query the next region
let bytes_written =
VirtualQuery(Some(ptr as *const c_void), &mut info, info_size);
if bytes_written < info_size {
log::error!("{:08X}: VirtualQuery for stack info failed", ptr as usize);
exit = true;
break;
} else if info.State != MEM_COMMIT
|| !READABLE_PROTECT
.iter()
.any(|p| info.Protect.bitand(*p) == *p)
{
log::error!("{:08X}: memory is not readable", ptr as usize);
exit = true;
break;
}

region_end = info.AllocationBase as usize + info.RegionSize;
let remaining_bytes = word_buf.len() - bytes_to_copy;
(ptr as *const u8).copy_to_nonoverlapping(
word_buf[bytes_to_copy..].as_mut_ptr(),
remaining_bytes,
);
ptr += remaining_bytes;
}

*word = usize::from_le_bytes(word_buf);
}

if exit {
break;
}

let mut line = format!("\t{:08X}: ", line_addr);
for word in words {
line = format!("{} {:08X}", line, word);
}
log::error!("{}", line);
}
} else {
log::error!("Stack dump: stack pointer was not present");
}

// module list
let mut modules = [HMODULE::default(); MAX_MODULES];
let mut size_needed = 0;
if !EnumProcessModules(
GetCurrentProcess(),
modules.as_mut_ptr(),
mem::size_of::<[HMODULE; MAX_MODULES]>() as u32,
&mut size_needed,
)
.is_ok()
{
log::error!("Modules: could not enumerate modules");
} else {
log::error!("Modules:");
let num_modules = size_needed as usize / mem::size_of::<HMODULE>();
for module in modules.into_iter().take(num_modules) {
let mut name_buf = [0u16; MAX_PATH as usize];
let chars_copied = GetModuleBaseNameW(GetCurrentProcess(), module, &mut name_buf);
let module_name = if chars_copied == 0 || chars_copied >= name_buf.len() as u32 {
String::from("<unknown>")
} else {
PWSTR::from_raw(name_buf.as_mut_ptr())
.to_string()
.unwrap_or_else(|_| String::from("<invalid>"))
};

let mut mod_info = MODULEINFO::default();
let address_range = match GetModuleInformation(
GetCurrentProcess(),
module,
&mut mod_info,
mem::size_of::<MODULEINFO>() as u32,
) {
Ok(_) => format!(
"{:08X}-{:08X}",
mod_info.lpBaseOfDll as usize,
mod_info.lpBaseOfDll as usize + mod_info.SizeOfImage as usize
),
Err(e) => format!("error: {:?}", e),
};

log::error!("\t{}\t{}", module_name, address_range);
}
}
}

ExceptionContinueSearch.0
}

pub fn open_log(log_level: LevelFilter, log_path: PathBuf) -> Result<()> {
let log_file = File::create(log_path)?;
WriteLogger::init(log_level, Config::default(), log_file)?;
panic::set_hook(Box::new(|info| {
let msg = info.payload().downcast_ref::<&str>().unwrap_or(&"unknown");
let (file, line) = info
.location()
.map_or(("unknown", 0), |l| (l.file(), l.line()));
log::error!("Panic in {} on line {}: {}", file, line, msg);
}));
unsafe {
AddVectoredExceptionHandler(0, Some(exception_handler));
}
Ok(())
}

pub fn close_log() {
unsafe {
RemoveVectoredExceptionHandler(exception_handler as *const c_void);
}
}
1 change: 1 addition & 0 deletions src/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub const SCROLL_UP_CHECK: usize = 0x005E386A;
pub const SCROLL_DOWN_CHECK: usize = 0x005E3935;
pub const SCROLL_LEFT_CHECK: usize = 0x005E39F1;
pub const SCROLL_RIGHT_CHECK: usize = 0x005E3AFD;
pub const SCROLL_RIGHT_TWO_CHECK: usize = 0x005E3B5A;
pub const GET_PARTNER_CHARACTER: usize = 0x0066DEC0;
pub const SUB_522A20: usize = 0x00522A20;
pub const PTR_DCDF3C: usize = 0x00DCDF3C;
Expand Down
Loading

0 comments on commit d06cf99

Please sign in to comment.