Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .clippy.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# Clippy configuration for GCRecomp
avoid-breaking-exported-api = false
msrv = "1.70.0"
msrv = "1.80.0"
too-many-arguments-threshold = 10
type-complexity-threshold = 300
single-char-lifetime-names = false
verbose-bit-mask-threshold = 4

6 changes: 5 additions & 1 deletion game/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ name = "game"
path = "src/main.rs"

[dependencies]
gcrecomp-core = { path = "../gcrecomp-core" }
gcrecomp-runtime = { path = "../gcrecomp-runtime" }
gcrecomp-ui = { path = "../gcrecomp-ui" }
gcrecomp-lua = { path = "../gcrecomp-lua" }
log = { workspace = true }

env_logger = "0.11"
anyhow = { workspace = true }
winit = { workspace = true }
wgpu = { workspace = true }
187 changes: 171 additions & 16 deletions game/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,177 @@
// Game entry point
fn main() {
println!("Game entry point - recompiled code will be integrated here");

// Initialize Lua scripting engine
match gcrecomp_lua::engine::LuaEngine::new() {
Ok(engine) => {
println!("Lua scripting engine initialized");

// Load game initialization scripts
let init_script = std::path::Path::new("lua/game/init.lua");
if init_script.exists() {
if let Err(e) = engine.execute_file(init_script) {
eprintln!("Failed to load game scripts: {}", e);
// Game entry point — full game runtime
use anyhow::Result;
use gcrecomp_core::runtime::context::CpuContext;
use gcrecomp_core::runtime::memory::MemoryManager;
use gcrecomp_core::runtime::sdk::os::OsState;
use log::info;
use std::sync::Arc;
use winit::application::ApplicationHandler;
use winit::event::{KeyEvent, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::keyboard::{Key, NamedKey};
use winit::window::Window;

struct GameApp {
window: Option<Arc<Window>>,
runtime: Option<gcrecomp_runtime::runtime::Runtime>,
_memory: MemoryManager,
_os_state: OsState,
_ctx: CpuContext,
menu_visible: bool,
}

impl GameApp {
fn new() -> Self {
let mut memory = MemoryManager::new();
let mut os_state = OsState::new();
let mut ctx = CpuContext::new();

// Run SDK init sequence
gcrecomp_core::runtime::sdk::os::os_init(&mut os_state, &mut memory);

// Setup initial CPU context
ctx.set_register(1, 0x817F_FF00); // r1 = stack pointer (top of MEM1)
ctx.set_register(13, 0x8040_0000); // Typical SDA base
ctx.set_register(2, 0x8040_0000); // Typical SDA2 base

info!("SDK initialized, CPU context ready");

Self {
window: None,
runtime: None,
_memory: memory,
_os_state: os_state,
_ctx: ctx,
menu_visible: false,
}
}
}

impl ApplicationHandler for GameApp {
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
if self.window.is_some() {
return;
}

let attrs = Window::default_attributes()
.with_title("GCRecomp")
.with_inner_size(winit::dpi::LogicalSize::new(1280, 720));

let window = Arc::new(
event_loop
.create_window(attrs)
.expect("Failed to create window"),
);

let mut runtime =
gcrecomp_runtime::runtime::Runtime::new().expect("Failed to init runtime");
runtime
.initialize_graphics(window.clone())
.expect("Failed to init graphics");
if let Err(e) = runtime.initialize_audio() {
log::warn!(
"Audio initialization failed (continuing without audio): {}",
e
);
}
info!("Runtime initialized: graphics, input, audio, video");

self.window = Some(window);
self.runtime = Some(runtime);
}

fn window_event(
&mut self,
event_loop: &winit::event_loop::ActiveEventLoop,
_window_id: winit::window::WindowId,
event: WindowEvent,
) {
let runtime = match self.runtime.as_mut() {
Some(r) => r,
None => return,
};

match event {
WindowEvent::CloseRequested => {
info!("Window close requested");
event_loop.exit();
}
WindowEvent::Resized(size) => {
if let Some(renderer) = runtime.renderer_mut() {
renderer.resize(size.width, size.height);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing zero-size guard causes wgpu panic on minimize

High Severity

The WindowEvent::Resized handler passes size.width and size.height directly to renderer.resize(), which sets them on the wgpu SurfaceConfiguration and calls surface.configure(). wgpu requires non-zero dimensions — when the window is minimized (common on Windows), winit delivers a Resized event with (0, 0), causing a panic inside wgpu. A guard like if size.width > 0 && size.height > 0 is needed before calling resize.

Fix in Cursor Fix in Web

}
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: Key::Named(NamedKey::Escape),
state: winit::event::ElementState::Pressed,
..
},
..
} => {
self.menu_visible = !self.menu_visible;
info!("Menu toggle: {}", self.menu_visible);
}
WindowEvent::RedrawRequested => {
if let Some(renderer) = runtime.renderer_mut() {
match renderer.begin_frame() {
Ok(frame) => {
renderer.end_frame(frame);
}
Err(e) => {
log::warn!("Frame error: {}", e);
}
}
}
}
_ => {}
}
}

fn about_to_wait(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {
if let Some(runtime) = self.runtime.as_mut() {
if let Err(e) = runtime.update() {
log::warn!("Runtime update error: {}", e);
}
}
if let Some(window) = &self.window {
window.request_redraw();
}
}
}

fn main() -> Result<()> {
// 1. Init logging
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
info!("GCRecomp game runtime starting");

// 2. Init Lua engine and load UI screens
let lua_engine = gcrecomp_lua::engine::LuaEngine::new()?;
info!("Lua scripting engine initialized");

// Load UI screen definitions
let ui_init = std::path::Path::new("lua/ui/init.lua");
if ui_init.exists() {
if let Err(e) = lua_engine.execute_file(ui_init) {
log::warn!("Failed to load UI screens: {}", e);
}
Err(e) => {
eprintln!("Failed to initialize Lua engine: {}", e);
}

// Load game initialization scripts
let game_init = std::path::Path::new("lua/game/init.lua");
if game_init.exists() {
if let Err(e) = lua_engine.execute_file(game_init) {
log::warn!("Failed to load game scripts: {}", e);
}
}

// 3. Create event loop and run
let event_loop = EventLoop::new()?;
event_loop.set_control_flow(ControlFlow::Poll);

let mut app = GameApp::new();
event_loop.run_app(&mut app)?;

Ok(())
}
101 changes: 46 additions & 55 deletions gcrecomp-cli/src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,87 +1,84 @@
// CLI command handlers
use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
use gcrecomp_core::recompiler::{
parser::DolFile,
ghidra::{GhidraAnalysis, GhidraBackend},
codegen::CodeGenerator,
ghidra::{GhidraAnalysis, GhidraBackend},
parser::DolFile,
};
use std::fs;
use std::path::{Path, PathBuf};

pub fn analyze_dol(dol_file: &Path, use_reoxide: bool) -> Result<()> {
println!("Reading DOL file: {}", dol_file.display());

let data = fs::read(dol_file)
.with_context(|| format!("Failed to read DOL file: {}", dol_file.display()))?;

let dol = DolFile::parse(&data, dol_file.to_str().unwrap_or("unknown.dol"))
.context("Failed to parse DOL file")?;

println!("DOL file parsed successfully");
println!(" Text sections: {}", dol.text_sections.len());
println!(" Data sections: {}", dol.data_sections.len());
println!(" Entry point: 0x{:08X}", dol.entry_point);
println!(" BSS address: 0x{:08X}, size: 0x{:08X}", dol.bss_address, dol.bss_size);

println!(
" BSS address: 0x{:08X}, size: 0x{:08X}",
dol.bss_address, dol.bss_size
);

println!("\nRunning Ghidra analysis...");
let backend = if use_reoxide {
GhidraBackend::ReOxide
} else {
GhidraBackend::HeadlessCli
};

let analysis = GhidraAnalysis::analyze(
dol_file.to_str().context("Invalid DOL file path")?,
backend,
)?;


let analysis =
GhidraAnalysis::analyze(dol_file.to_str().context("Invalid DOL file path")?, backend)?;

println!("Analysis complete");
println!(" Functions found: {}", analysis.functions.len());
println!(" Symbols found: {}", analysis.symbols.len());

for func in &analysis.functions {
println!(" Function: {} @ 0x{:08X} (size: {})",
func.name, func.address, func.size);
println!(
" Function: {} @ 0x{:08X} (size: {})",
func.name, func.address, func.size
);
}

Ok(())
}

pub fn recompile_dol(
dol_file: &Path,
output_dir: Option<&Path>,
use_reoxide: bool,
) -> Result<()> {
pub fn recompile_dol(dol_file: &Path, output_dir: Option<&Path>, use_reoxide: bool) -> Result<()> {
println!("Recompiling DOL file: {}", dol_file.display());

// Analyze the DOL file
let backend = if use_reoxide {
GhidraBackend::ReOxide
} else {
GhidraBackend::HeadlessCli
};

let analysis = GhidraAnalysis::analyze(
dol_file.to_str().context("Invalid DOL file path")?,
backend,
)?;


let analysis =
GhidraAnalysis::analyze(dol_file.to_str().context("Invalid DOL file path")?, backend)?;

// Determine output directory
let output_dir = output_dir
.map(|p| p.to_path_buf())
.unwrap_or_else(|| PathBuf::from("game/src"));

fs::create_dir_all(&output_dir)
.context("Failed to create output directory")?;


fs::create_dir_all(&output_dir).context("Failed to create output directory")?;

// Generate Rust code for each function
let mut codegen = CodeGenerator::new();
let _codegen = CodeGenerator::new();
let mut rust_code = String::new();

rust_code.push_str("// Auto-generated recompiled Rust code\n");
rust_code.push_str("// This file is generated by gcrecomp and should not be edited manually\n\n");
rust_code
.push_str("// This file is generated by gcrecomp and should not be edited manually\n\n");
rust_code.push_str("use gcrecomp_runtime::{CpuContext, MemoryManager};\n\n");

for func_info in &analysis.functions {
// For now, generate a placeholder function
// In a real implementation, we would decode instructions and generate proper code
Expand All @@ -93,32 +90,27 @@ pub fn recompile_dol(
rust_code.push_str(" 0\n");
rust_code.push_str("}\n\n");
}

// Write generated code
let output_file = output_dir.join("recompiled.rs");
fs::write(&output_file, rust_code)
.context("Failed to write generated Rust code")?;

fs::write(&output_file, rust_code).context("Failed to write generated Rust code")?;

println!("Generated Rust code written to: {}", output_file.display());

Ok(())
}

pub fn build_dol(
dol_file: &Path,
output_dir: Option<&Path>,
use_reoxide: bool,
) -> Result<()> {
pub fn build_dol(dol_file: &Path, output_dir: Option<&Path>, use_reoxide: bool) -> Result<()> {
println!("Building recompiled game from: {}", dol_file.display());

// Step 1: Analyze
println!("Step 1/3: Analyzing DOL file...");
analyze_dol(dol_file, use_reoxide)?;

// Step 2: Recompile
println!("\nStep 2/3: Recompiling to Rust...");
recompile_dol(dol_file, output_dir, use_reoxide)?;

// Step 3: Build
println!("\nStep 3/3: Building Rust project...");
let output = std::process::Command::new("cargo")
Expand All @@ -128,14 +120,13 @@ pub fn build_dol(
.arg("game/Cargo.toml")
.output()
.context("Failed to run cargo build")?;

if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("Cargo build failed: {}", stderr);
}

println!("Build complete! Executable should be in game/target/release/");

Ok(())
}

Loading
Loading