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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ dist/
.vscode/*
!.vscode/extensions.json
.claude/
nul
nul

reference/
56 changes: 54 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion crates/arm7di-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1316,7 +1316,8 @@ impl<'a> Arm7Di<'a> {

fn exec_branch(&mut self, opcode: u32) -> u32 {
let link = opcode & (1 << 24) != 0;
let offset = opcode & 0x00FF_FFFF;
// Sign-extend the 24-bit offset: shift left 10 to move bit 23 to bit 31,
// then arithmetic shift right 8 to sign-extend and multiply by 4
let offset = ((opcode << 10) as i32) >> 8;
let next = self.ctx.regs[R15_ARM_NEXT].get();
if link {
Expand Down
3 changes: 2 additions & 1 deletion crates/dreamcast/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ refsw2-cpp = ["dep:refsw2-cpp"]
refsw2-native = ["refsw2-cpp"]

[dependencies]
bitfield = "0.14"
bitfield = "0.19.3"
once_cell = "1.19"
goblin = "0.10.3"
sh4-core = { path = "../sh4-core" }
arm7di-core = { path = "../arm7di-core" }
refsw2-rust = { path = "../refsw2-rust", optional = true }
Expand Down
147 changes: 132 additions & 15 deletions crates/dreamcast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use std::path::Path;
use std::ptr::{self, NonNull};
use std::sync::atomic::{AtomicPtr, Ordering};
use std::sync::Mutex;
use goblin::elf::Elf;

mod area0;
pub use area0::AREA0_HANDLERS;
Expand Down Expand Up @@ -230,24 +231,10 @@ fn load_file_into_slice<P: AsRef<Path>>(path: P, buf: &mut [u8]) -> io::Result<(
// pub static HELLO_BIN: &[u8] = include_bytes!("../../../data/hello.elf.bin");
// pub static ARM7W_BIN: &[u8] = include_bytes!("../../../data/arm7wrestler.bin");

pub fn init_dreamcast(dc_: *mut Dreamcast, bios_rom: &[u8], bios_flash: &[u8]) {
let dc: &mut Dreamcast;
unsafe {
dc = &mut *dc_;
}

DREAMCAST_PTR.store(dc as *mut Dreamcast, Ordering::SeqCst);

fn reset_dreamcast_to_defaults(dc: &mut Dreamcast) {
// Zero entire struct (like memset). In Rust, usually you'd implement Default.
*dc = Dreamcast::default();

// Copy BIOS ROM and Flash from provided slices
assert_eq!(bios_rom.len(), BIOS_ROM_SIZE as usize, "BIOS ROM must be exactly 2MB");
assert_eq!(bios_flash.len(), BIOS_FLASH_SIZE as usize, "BIOS Flash must be exactly 128KB");

dc.bios_rom[..].copy_from_slice(bios_rom);
dc.bios_flash[..].copy_from_slice(bios_flash);

sh4_init_ctx(&mut dc.ctx);

refsw2::refsw2_init();
Expand Down Expand Up @@ -409,6 +396,25 @@ pub fn init_dreamcast(dc_: *mut Dreamcast, bios_rom: &[u8], bios_flash: &[u8]) {
dc.ctx.sr_t = 1;

dc.ctx.fpscr.0 = 0x00040001;
}

pub fn init_dreamcast(dc_: *mut Dreamcast, bios_rom: &[u8], bios_flash: &[u8]) {
let dc: &mut Dreamcast;
unsafe {
dc = &mut *dc_;
}

DREAMCAST_PTR.store(dc as *mut Dreamcast, Ordering::SeqCst);

reset_dreamcast_to_defaults(dc);


// Copy BIOS ROM and Flash from provided slices
assert_eq!(bios_rom.len(), BIOS_ROM_SIZE as usize, "BIOS ROM must be exactly 2MB");
assert_eq!(bios_flash.len(), BIOS_FLASH_SIZE as usize, "BIOS Flash must be exactly 128KB");

dc.bios_rom[..].copy_from_slice(bios_rom);
dc.bios_flash[..].copy_from_slice(bios_flash);

// ROTO test program at 0x8C010000
// dc.ctx.pc0 = 0x8C01_0000;
Expand Down Expand Up @@ -670,3 +676,114 @@ pub fn get_arm_register(dc: *mut Dreamcast, register_name: &str) -> Option<u32>
}
}
}

pub fn init_dreamcast_with_elf(dc: *mut Dreamcast, elf_bytes: &[u8]) -> Result<(), String> {
// Parse the ELF file
let elf = Elf::parse(elf_bytes)
.map_err(|e| format!("Failed to parse ELF file: {}", e))?;

unsafe {
let dc_ref = &mut *dc;

reset_dreamcast_to_defaults(dc_ref);

// Iterate through program headers and load PT_LOAD segments
for ph in &elf.program_headers {
// Only load PT_LOAD segments
if ph.p_type != goblin::elf::program_header::PT_LOAD {
continue;
}

let vaddr = ph.p_vaddr as u32;
let memsz = ph.p_memsz as usize;
let filesz = ph.p_filesz as usize;
let offset = ph.p_offset as usize;

println!("Loading ELF segment: vaddr=0x{:08X}, memsz=0x{:X}, filesz=0x{:X}",
vaddr, memsz, filesz);

// Determine which memory region this address belongs to
let (dest_ptr, mask) = match vaddr {
// System RAM (mirrored across different regions)
0x0C00_0000..=0x0FFF_FFFF |
0x8C00_0000..=0x8FFF_FFFF |
0xAC00_0000..=0xAFFF_FFFF => {
(dc_ref.sys_ram.as_mut_ptr(), SYSRAM_MASK)
}
// Video RAM
0x0400_0000..=0x04FF_FFFF |
0xA400_0000..=0xA4FF_FFFF |
0x0500_0000..=0x05FF_FFFF |
0xA500_0000..=0xA5FF_FFFF |
0x0600_0000..=0x06FF_FFFF |
0xA600_0000..=0xA6FF_FFFF |
0x0700_0000..=0x07FF_FFFF |
0xA700_0000..=0xA7FF_FFFF => {
(dc_ref.video_ram.as_mut_ptr(), VIDEORAM_MASK)
}
// Audio RAM
0x0080_0000..=0x009F_FFFF |
0x8080_0000..=0x809F_FFFF |
0xA080_0000..=0xA09F_FFFF => {
(dc_ref.audio_ram.as_mut_ptr(), AUDIORAM_MASK)
}
// On-chip RAM
0x7C00_0000..=0x7FFF_FFFF => {
(dc_ref.oc_ram.as_mut_ptr(), OCRAM_MASK)
}
_ => {
return Err(format!("ELF segment virtual address 0x{:08X} is not in a valid memory region", vaddr));
}
};

// Calculate the offset into the destination memory
let dest_offset = (vaddr & mask) as usize;

// Check if the segment fits in the memory region
if dest_offset + memsz > (mask as usize + 1) {
return Err(format!("ELF segment at 0x{:08X} with size 0x{:X} exceeds memory bounds",
vaddr, memsz));
}

// Copy the file data
if filesz > 0 {
// Validate that we have enough data in the ELF file
if offset + filesz > elf_bytes.len() {
return Err(format!("ELF segment data at offset 0x{:X} with size 0x{:X} exceeds file bounds",
offset, filesz));
}

let src_slice = &elf_bytes[offset..offset + filesz];

ptr::copy_nonoverlapping(
src_slice.as_ptr(),
dest_ptr.add(dest_offset),
filesz
);
}

// Zero out remaining bytes (BSS sections)
if memsz > filesz {
let bss_size = memsz - filesz;
ptr::write_bytes(
dest_ptr.add(dest_offset + filesz),
0,
bss_size
);
}
}

DREAMCAST_PTR.store(dc, Ordering::SeqCst);


// Set PC to entry point if valid
if elf.entry > 0 {
let entry = elf.entry as u32;
println!("Setting entry point to 0x{:08X}", entry);
dc_ref.ctx.pc0 = entry;
dc_ref.ctx.pc1 = entry + 2;
dc_ref.ctx.pc2 = entry + 4;
}
Ok(())
}
}
16 changes: 16 additions & 0 deletions crates/nulldc-minimal/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ fn load_bios_files() -> (Vec<u8>, Vec<u8>) {
}

fn main() {
let args: Vec<String> = std::env::args().collect();

let (bios_rom, bios_flash) = load_bios_files();

// Create window
Expand All @@ -45,6 +47,20 @@ fn main() {
let dc = Box::into_raw(Box::new(dreamcast::Dreamcast::default()));
dreamcast::init_dreamcast(dc, &bios_rom, &bios_flash);

// Load ELF if provided as command line argument
if args.len() > 1 {
let elf_path = &args[1];
println!("Loading ELF file: {}", elf_path);

let elf_data = fs::read(elf_path)
.unwrap_or_else(|e| panic!("Failed to load ELF file from {}: {}", elf_path, e));

dreamcast::init_dreamcast_with_elf(dc, &elf_data)
.unwrap_or_else(|e| panic!("Failed to load ELF: {}", e));

println!("ELF file loaded successfully");
}

// Framebuffer for minifb (ARGB format)
let mut buffer: Vec<u32> = vec![0; WIDTH * HEIGHT];

Expand Down
2 changes: 1 addition & 1 deletion crates/refsw2-rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ version = "0.1.0"
edition = "2024"

[dependencies]
bitfield = "0.14"
bitfield = "0.19.3"

[build-dependencies]
2 changes: 1 addition & 1 deletion crates/sh4-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024"

[dependencies]
bitfield = "0.14"
bitfield = "0.19.3"
paste = "1.0"
seq-macro = "0.3"

Expand Down
4 changes: 2 additions & 2 deletions crates/sh4-core/src/sh4p4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1734,14 +1734,14 @@ pub fn p4_read<T: crate::sh4mem::MemoryData>(_ctx: *mut u8, addr: u32) -> T {
0xFF => {
let handler = area7_router(addr);
if handler.size as usize != std::mem::size_of::<T>() {
panic!(
println!(
"p4_read::<u{}> {:x} size mismatch, handler size = {}",
std::mem::size_of::<T>(),
addr,
handler.size
);
}
let raw_value = (handler.read)(handler.ctx, addr);
let raw_value = (handler.read)(handler.ctx, addr & !(handler.size as u32 -1));
T::from_u32(raw_value)
}

Expand Down
Loading
Loading