├── .gitignore ├── elf2psexe ├── Cargo.lock ├── Cargo.toml ├── Makefile └── src │ ├── main.rs │ ├── elf.rs │ └── psexe.rs ├── apps ├── skeleton │ ├── Makefile │ └── main.rs ├── Makefile └── app_common.mk ├── vram_to_png ├── Cargo.toml ├── src │ └── main.rs └── Cargo.lock ├── Makefile ├── runtime ├── Makefile └── libpsx │ ├── lib.rs │ └── uart │ └── mod.rs ├── target.json ├── psx.ld ├── config.mk └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.rlib 2 | *.elf 3 | *.o 4 | *.a 5 | *.psexe 6 | elf2psexe/target 7 | -------------------------------------------------------------------------------- /elf2psexe/Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "elf2psexe" 3 | version = "0.0.1" 4 | 5 | -------------------------------------------------------------------------------- /apps/skeleton/Makefile: -------------------------------------------------------------------------------- 1 | NAME = skeleton 2 | 3 | include ../../config.mk 4 | include ../app_common.mk 5 | -------------------------------------------------------------------------------- /apps/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | $(MAKE_COMMAND) -C skeleton/ 3 | 4 | clean: 5 | $(MAKE_COMMAND) -C skeleton/ clean 6 | -------------------------------------------------------------------------------- /elf2psexe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "elf2psexe" 3 | version = "0.0.1" 4 | authors = ["Lionel Flandrin "] 5 | -------------------------------------------------------------------------------- /elf2psexe/Makefile: -------------------------------------------------------------------------------- 1 | # Dummy Makefile used to invoke cargo to build/update the binary if 2 | # needed 3 | 4 | all: 5 | cargo build --release 6 | 7 | clean: 8 | cargo clean 9 | -------------------------------------------------------------------------------- /vram_to_png/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vram_to_png" 3 | version = "0.1.0" 4 | authors = ["Lionel Flandrin "] 5 | 6 | [dependencies] 7 | image = "0.6.1" 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME = test 2 | 3 | include config.mk 4 | 5 | RUSTFLAGS = -O --target=target.json -C soft-float -C lto 6 | RUSTFLAGS += --extern psx=runtime/libpsx.rlib 7 | 8 | all: 9 | $(MAKE_COMMAND) -C elf2psexe/ 10 | $(MAKE_COMMAND) -C runtime/ 11 | $(MAKE_COMMAND) -C apps/ 12 | 13 | clean: 14 | $(MAKE_COMMAND) -C elf2psexe/ clean 15 | $(MAKE_COMMAND) -C runtime/ clean 16 | $(MAKE_COMMAND) -C apps/ clean 17 | 18 | 19 | .PHONY: all clean 20 | -------------------------------------------------------------------------------- /runtime/Makefile: -------------------------------------------------------------------------------- 1 | include ../config.mk 2 | 3 | LIBPSX_SRC := $(shell find libpsx -name '*.rs') 4 | 5 | all: libcore.rlib libpsx.rlib libbios.a 6 | 7 | libcore.rlib: 8 | $(RUSTC) $(RUSTFLAGS) -o $@ $(LIBCORE_SRC) 9 | 10 | libpsx.rlib: $(LIBPSX_SRC) 11 | $(RUSTC) $(RUSTFLAGS) --extern core=libcore.rlib -o $@ libpsx/lib.rs 12 | 13 | libbios.a: libbios.o 14 | $(AR) rcs $@ $< 15 | 16 | libbios.o: libbios.s 17 | $(AS) -O2 -o $@ $< 18 | 19 | clean: 20 | rm -f libcore.rlib libpsx.rlib 21 | -------------------------------------------------------------------------------- /target.json: -------------------------------------------------------------------------------- 1 | { 2 | "data-layout": "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:64:128-a:0:64-n32", 3 | "llvm-target": "mipsel-unknown-linux-gnu", 4 | "target-endian": "little", 5 | "target-pointer-width": "32", 6 | "target_os": "psx", 7 | "os": "psx", 8 | "arch": "mips", 9 | "cpu": "mips2", 10 | "no-compiler-rt": true, 11 | "eliminate-frame-pointer": false, 12 | "morestack": false, 13 | "dynamic-linking": false, 14 | "relocation-model": "static", 15 | "function-sections": true 16 | } 17 | -------------------------------------------------------------------------------- /psx.ld: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* RAM through the KSEG0 region: 2MB minus 64K reserved for the BIOS */ 4 | RAM (rwx) : ORIGIN = 0x80000000 + 0x10000, LENGTH = 0x200000 - 0x10000 5 | } 6 | 7 | ENTRY(main); 8 | 9 | SECTIONS 10 | { 11 | .text : 12 | { 13 | *(.text*) 14 | } > RAM 15 | 16 | /* The PSX doesn't have an MMU so we don't really care about 17 | * RO vs RW 18 | */ 19 | .data : 20 | { 21 | *(.data*) 22 | *(.rodata*) 23 | } > RAM 24 | 25 | /* This section must be stored in the "memfill" part of the 26 | * header to be initialized to 0 at bootup by the BIOS/loader. 27 | */ 28 | .bss : 29 | { 30 | *(.bss*) 31 | *(COMMON) 32 | } > RAM 33 | } 34 | -------------------------------------------------------------------------------- /runtime/libpsx/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(no_std,core,lang_items,asm)] 2 | #![no_std] 3 | #![crate_type = "rlib"] 4 | #![crate_name = "psx"] 5 | 6 | extern crate core; 7 | 8 | pub mod uart; 9 | 10 | #[no_mangle] 11 | pub extern fn memset(dst: *mut u8, b: i32, len: u32) -> *mut u8 { 12 | for i in 0..len { 13 | unsafe { 14 | *dst.offset(i as isize) = b as u8; 15 | } 16 | } 17 | 18 | dst 19 | } 20 | 21 | // Various lang items required by rustc 22 | #[lang = "stack_exhausted"] 23 | extern fn stack_exhausted() {} 24 | 25 | #[lang = "eh_personality"] 26 | extern fn eh_personality() {} 27 | 28 | #[lang = "panic_fmt"] 29 | fn panic_fmt() -> ! { loop {} } 30 | -------------------------------------------------------------------------------- /apps/app_common.mk: -------------------------------------------------------------------------------- 1 | ELF2PSEXE := $(SDK_ROOT)/elf2psexe/target/release/elf2psexe 2 | LDSCRIPT := $(SDK_ROOT)/psx.ld 3 | 4 | RUSTFLAGS += -L $(SDK_ROOT)/runtime 5 | 6 | RUSTLIBS := $(SDK_ROOT)/runtime/libpsx.rlib \ 7 | $(SDK_ROOT)/runtime/libcore.rlib \ 8 | $(SDK_ROOT)/runtime/libbios.a 9 | 10 | APP_SRC := $(shell find . -name '*.rs') 11 | 12 | $(NAME).psexe: $(NAME).elf 13 | $(ELF2PSEXE) $(REGION) $< $@ 14 | 15 | $(NAME).elf: $(LDSCRIPT) $(NAME).o 16 | $(LD) --gc-sections -o $@ -T $(LDSCRIPT) $(NAME).o $(RUSTLIBS) 17 | 18 | $(NAME).o: $(APP_SRC) $(RUSTLIBS) 19 | $(RUSTC) $(RUSTFLAGS) --emit obj -o $@ main.rs 20 | 21 | clean: 22 | rm -f $(NAME).o $(NAME).elf $(NAME).psexe 23 | -------------------------------------------------------------------------------- /runtime/libpsx/uart/mod.rs: -------------------------------------------------------------------------------- 1 | use core::prelude::*; 2 | 3 | use core::fmt; 4 | 5 | /// UART interface connected to the serial port, for debugging purposes 6 | pub struct Uart; 7 | 8 | impl Uart { 9 | pub fn new() -> Uart { 10 | Uart 11 | } 12 | 13 | /// Send a character using the BIOS `putc` function 14 | pub fn putchar(c: u8) { 15 | unsafe { 16 | bios_putchar(c) 17 | } 18 | } 19 | } 20 | 21 | /*impl fmt::Write for Uart { 22 | fn write_str(&mut self, s: &str) -> fmt::Result { 23 | for &b in s.as_bytes() { 24 | Uart::putchar(b); 25 | } 26 | 27 | Ok(()) 28 | } 29 | }*/ 30 | 31 | 32 | extern { 33 | fn bios_putchar(b: u8); 34 | } 35 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | RUSTC = rustc 2 | 3 | # Cross compilation toolchain used for linking 4 | CROSS = /opt/psx-tools/bin/mipsel-unknown-elf- 5 | LD = $(CROSS)ld 6 | AS = $(CROSS)as 7 | AR = $(CROSS)ar 8 | 9 | # Absolute path to this directory. It makes the rest of the Makefiles 10 | # simpler but it sucks a bit. We should be able to get this path 11 | # dynamically but I suck at Makefiles 12 | SDK_ROOT = $(HOME)/src/psx-sdk-rs 13 | 14 | # Path to libcore's lib.rs 15 | LIBCORE_SRC=$(HOME)/src/rust/src/libcore/lib.rs 16 | 17 | # Region for the resulting executable: NA, E or J 18 | REGION = E 19 | 20 | # Absolude path to target.json in this directory. We'll be able to 21 | # replace it with a relative path once 22 | # https://github.com/rust-lang/rust/issues/24666 is fixed 23 | TARGET_FILE = $(SDK_ROOT)/target.json 24 | 25 | # Common rust flags (both for runtime and apps) 26 | RUSTFLAGS = -O --target=$(TARGET_FILE) -C soft-float 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | psx-sdk-rs 2 | ========== 3 | 4 | Basic SDK to run custom Rust code on a Playstation. 5 | 6 | In order to use this you'll need a nighly rustc (since we use unstable 7 | features), the rust source (since we need to crosscompile libcore) and 8 | a linker targetting `mipsel-unknown-elf`. 9 | 10 | You'll also need a way to run custom "PS-EXE" executables on the 11 | console, I use an Xplorer-FX flashed with caetla 0.34 and the 12 | catflap4linux to control it. 13 | 14 | `psx.ld` contains the linker script to put the executable at the 15 | correct location in RAM (without overwriting the BIOS). 16 | 17 | `elf2psexe` is a tool that converts the `ELF` executables produced by 18 | `ld` into the standard `PS-EXE` format used on the console (and 19 | understood by many Playstation utilities). 20 | 21 | The applications are in `apps/`. The build system is a bit crappy 22 | since I can't use cargo unfortunately. Instead I use ad-hoc Makefiles 23 | and a global `config.mk` for the various settings. 24 | -------------------------------------------------------------------------------- /vram_to_png/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate image; 2 | 3 | use std::fs::File; 4 | use std::io::Read; 5 | use std::path::Path; 6 | 7 | use image::{RgbImage, Pixel}; 8 | 9 | fn main() { 10 | let argv: Vec<_> = std::env::args().collect(); 11 | 12 | if argv.len() < 3 { 13 | println!("Usage: {} ", argv[0]); 14 | return; 15 | } 16 | 17 | let mut raw_file = File::open(&argv[1]).unwrap(); 18 | 19 | let mut raw = Vec::new(); 20 | 21 | raw_file.read_to_end(&mut raw).unwrap(); 22 | 23 | let mut img = RgbImage::new(XRES, YRES); 24 | 25 | for (x, y, p) in img.enumerate_pixels_mut() { 26 | let offset = (y * XRES + x) * 2; 27 | 28 | let offset = offset as usize; 29 | 30 | let b1 = raw[offset]; 31 | let b2 = raw[offset + 1]; 32 | 33 | let pixel = (b1 as u16) | ((b2 as u16) << 8); 34 | 35 | let five_to_eight = |v: u16| { 36 | (((v & 0x1f) << 3) | ((v >> 2) & 0x7)) as u8 37 | }; 38 | 39 | let r = five_to_eight(pixel); 40 | let g = five_to_eight(pixel >> 5); 41 | let b = five_to_eight(pixel >> 10); 42 | 43 | p.channels_mut()[0] = r as u8; 44 | p.channels_mut()[1] = g as u8; 45 | p.channels_mut()[2] = b as u8; 46 | } 47 | 48 | img.save(Path::new(&argv[2])).unwrap(); 49 | } 50 | 51 | const XRES: u32 = 1024; 52 | const YRES: u32 = 512; 53 | -------------------------------------------------------------------------------- /elf2psexe/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | mod elf; 4 | mod psexe; 5 | 6 | pub struct Section { 7 | base: u32, 8 | contents: SectionType, 9 | } 10 | 11 | enum SectionType { 12 | /// The section's data is contained in the file 13 | ProgBits(Vec), 14 | /// BSS data that's set to 0 by the loader (not contained in the 15 | /// file). There can be only one contiguous Memfill resion in an 16 | /// EXE file. 17 | Memfill(u32), 18 | } 19 | 20 | #[derive(Clone, Copy)] 21 | pub enum Region { 22 | NorthAmerica, 23 | Europe, 24 | Japan, 25 | } 26 | 27 | impl Region { 28 | fn from_str(s: &str) -> Region { 29 | match s { 30 | "NA" => Region::NorthAmerica, 31 | "E" => Region::Europe, 32 | "J" => Region::Japan, 33 | _ => panic!("Invalid region {}", s) 34 | } 35 | } 36 | } 37 | 38 | fn main() { 39 | let args: Vec<_> = std::env::args().collect(); 40 | 41 | if args.len() < 4 { 42 | println!("usage: elf2psexe "); 43 | println!("Valid regions: NA, E or J"); 44 | panic!("Missing argument"); 45 | } 46 | 47 | let region = Region::from_str(&args[1]); 48 | let elfpath = &args[2]; 49 | let psexepath = &args[3]; 50 | 51 | let elf = elf::ElfReader::new(Path::new(elfpath)); 52 | 53 | let entry = elf.entry(); 54 | let sections = elf.into_sections(); 55 | 56 | let psexe = psexe::PsxWriter::new(Path::new(psexepath), region); 57 | 58 | psexe.dump(entry, sections); 59 | } 60 | -------------------------------------------------------------------------------- /apps/skeleton/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(no_std,core)] 2 | #![no_std] 3 | 4 | use core::intrinsics::{volatile_store, volatile_load}; 5 | use core::fmt::Write; 6 | use psx::uart::Uart; 7 | 8 | #[macro_use] 9 | extern crate core; 10 | extern crate psx; 11 | 12 | #[no_mangle] 13 | pub fn main() { 14 | //let mut uart = Uart::new(); 15 | 16 | // unsafe { bios_print_devices() }; 17 | // print_devices(); 18 | // unsafe { bios_putchar(b'$') }; 19 | // putchar(b'$'); 20 | // unsafe { bios_print_devices() }; 21 | 22 | // putchar(b'A'); 23 | // printf(b"test %d\n\0" as *const u8, 42); 24 | 25 | // unsafe { bios_puts(b"test %d\n\0" as *const u8); } 26 | 27 | // putchar(b'A'); 28 | // putchar(b'B'); 29 | // putchar(b'C'); 30 | 31 | // printf(b"Foo %d\n\0" as *const u8, 128); 32 | 33 | //let _ = writeln!(uart, "Hello world from rust!"); 34 | 35 | //for &b in b"\n\nHello world from rust!\n\n" { 36 | Uart::putchar(b'$'); 37 | Uart::putchar(b'$'); 38 | //Uart::putchar(b'$'); 39 | //putchar(b); 40 | //} 41 | // 42 | 43 | //uart.putchar(c); 44 | 45 | // Clear command FIFO 46 | gp1_command(0x01000000); 47 | 48 | // Top left at 0,0 49 | gp0_command(0xe3000000); 50 | // Bottom right: 256x256 51 | gp0_command(0xe4040100); 52 | // Offset at 0,0 53 | gp0_command(0xe5000000); 54 | 55 | // Shaded quad 56 | gp0_command(0x38000000); 57 | gp0_command(0x00000000); 58 | gp0_command(0x00ff0000); 59 | gp0_command(0x00000100); 60 | gp0_command(0x0000ff00); 61 | gp0_command(0x01000000); 62 | gp0_command(0x000000ff); 63 | gp0_command(0x01000100); 64 | 65 | //loop {} 66 | } 67 | 68 | /// Send command on GPU port 0 69 | fn gp0_command(cmd: u32) { 70 | let cmd_reg = 0x1f801810u32 as *mut u32; 71 | 72 | // Hack to avoid overflowing the command FIFO, I should check the 73 | // ready status flag. 74 | delay(100); 75 | 76 | unsafe { 77 | volatile_store(cmd_reg, cmd); 78 | } 79 | } 80 | 81 | /// Send command on GPU port 1 82 | fn gp1_command(cmd: u32) { 83 | let cmd_reg = 0x1f801814u32 as *mut u32; 84 | 85 | unsafe { 86 | let v = volatile_load(cmd_reg); 87 | 88 | volatile_store(cmd_reg, (v != cmd_reg as u32) as u32); 89 | } 90 | } 91 | 92 | fn delay(n: u32) { 93 | for _ in 0..n { 94 | unsafe { 95 | volatile_load(0 as *mut u32); 96 | } 97 | } 98 | } 99 | 100 | fn print_devices() { 101 | unsafe { 102 | bios_print_devices(); 103 | } 104 | } 105 | 106 | fn putchar(c: u8) { 107 | unsafe { 108 | bios_putchar(c); 109 | } 110 | } 111 | 112 | fn printf(c: *const u8, v: u32) { 113 | unsafe { bios_printf(c, v) }; 114 | } 115 | 116 | extern { 117 | fn bios_putchar(b: u8) -> u32; 118 | fn bios_puts(s: *const u8) -> u32; 119 | fn bios_toupper(b: u8) -> u8; 120 | fn bios_print_devices(); 121 | fn bios_printf(s: *const u8, v: u32); 122 | } 123 | -------------------------------------------------------------------------------- /vram_to_png/Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "vram_to_png" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "image 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 6 | ] 7 | 8 | [[package]] 9 | name = "advapi32-sys" 10 | version = "0.1.2" 11 | source = "registry+https://github.com/rust-lang/crates.io-index" 12 | dependencies = [ 13 | "winapi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", 14 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 15 | ] 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "0.3.3" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | 22 | [[package]] 23 | name = "byteorder" 24 | version = "0.4.2" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | 27 | [[package]] 28 | name = "color_quant" 29 | version = "1.0.0" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | 32 | [[package]] 33 | name = "enum_primitive" 34 | version = "0.1.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | dependencies = [ 37 | "num 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)", 38 | ] 39 | 40 | [[package]] 41 | name = "flate2" 42 | version = "0.2.13" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | dependencies = [ 45 | "libc 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 46 | "miniz-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 47 | ] 48 | 49 | [[package]] 50 | name = "gcc" 51 | version = "0.3.21" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | dependencies = [ 54 | "advapi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 55 | "winapi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", 56 | ] 57 | 58 | [[package]] 59 | name = "gif" 60 | version = "0.7.0" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | dependencies = [ 63 | "color_quant 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 64 | "lzw 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 65 | ] 66 | 67 | [[package]] 68 | name = "glob" 69 | version = "0.2.10" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | 72 | [[package]] 73 | name = "image" 74 | version = "0.6.1" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | dependencies = [ 77 | "byteorder 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 78 | "enum_primitive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 79 | "gif 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 80 | "glob 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", 81 | "num 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)", 82 | "png 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 83 | ] 84 | 85 | [[package]] 86 | name = "inflate" 87 | version = "0.1.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | 90 | [[package]] 91 | name = "libc" 92 | version = "0.2.6" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | 95 | [[package]] 96 | name = "lzw" 97 | version = "0.9.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | 100 | [[package]] 101 | name = "miniz-sys" 102 | version = "0.1.7" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | dependencies = [ 105 | "gcc 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", 106 | "libc 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 107 | ] 108 | 109 | [[package]] 110 | name = "num" 111 | version = "0.1.30" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | dependencies = [ 114 | "rand 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", 115 | "rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", 116 | ] 117 | 118 | [[package]] 119 | name = "png" 120 | version = "0.4.1" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | dependencies = [ 123 | "bitflags 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 124 | "flate2 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 125 | "inflate 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 126 | "libc 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 127 | "num 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)", 128 | ] 129 | 130 | [[package]] 131 | name = "rand" 132 | version = "0.3.13" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | dependencies = [ 135 | "advapi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 136 | "libc 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 137 | "winapi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", 138 | ] 139 | 140 | [[package]] 141 | name = "rustc-serialize" 142 | version = "0.3.16" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | 145 | [[package]] 146 | name = "winapi" 147 | version = "0.2.5" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | 150 | [[package]] 151 | name = "winapi-build" 152 | version = "0.1.1" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | 155 | -------------------------------------------------------------------------------- /elf2psexe/src/elf.rs: -------------------------------------------------------------------------------- 1 | use std::fs::{File, OpenOptions}; 2 | use std::io::{Read, Seek, SeekFrom}; 3 | use std::path::Path; 4 | 5 | use Section; 6 | use SectionType; 7 | 8 | pub struct ElfReader { 9 | elf: File, 10 | entry: u32, 11 | sections: Vec
, 12 | } 13 | 14 | impl ElfReader { 15 | pub fn new(path: &Path) -> ElfReader { 16 | let elf = 17 | match OpenOptions::new().read(true).open(path) { 18 | Ok(elf) => elf, 19 | Err(e) => panic!("Can't open {}: {}", path.display(), e), 20 | }; 21 | 22 | let mut reader = ElfReader { 23 | elf: elf, 24 | entry: 0, 25 | sections: Vec::new(), 26 | }; 27 | 28 | reader.parse(); 29 | 30 | reader 31 | } 32 | 33 | /// Parse ELF header and make sure it's a valid 32bit MIPS 34 | /// executable. Then parse all the sections. 35 | fn parse(&mut self) { 36 | // Read the ELF header. We're always expecting a 32bit executable 37 | // so the header should be 52bytes long 38 | let mut header = [0; 52]; 39 | self.read(&mut header); 40 | 41 | if &header[..4] != b"\x7fELF" { 42 | panic!("Invalid ELF file: bad magic"); 43 | } 44 | 45 | if header[4] != 1 { 46 | panic!("Invalid ELF file: not a 32bit object"); 47 | } 48 | 49 | if header[5] != 1 { 50 | panic!("Invalid ELF file: not a little endian object"); 51 | } 52 | 53 | if header[6] != 1 { 54 | panic!("Invalid ELF file: bad IDENT version"); 55 | } 56 | 57 | if halfword(&header[16..]) != 2 { 58 | panic!("Invalid ELF file: not an executable"); 59 | } 60 | 61 | if halfword(&header[18..]) != 8 { 62 | panic!("Invalid ELF file: not a MIPS executable"); 63 | } 64 | 65 | if word(&header[20..]) != 1 { 66 | panic!("Invalid ELF file: bad object version"); 67 | } 68 | 69 | self.entry = word(&header[24..]); 70 | 71 | let section_header_off = word(&header[32..]) as u64; 72 | let section_header_sz = halfword(&header[46..]) as u64; 73 | let section_count = halfword(&header[48..]) as u64; 74 | 75 | if section_header_sz < 40 { 76 | panic!("Invalid ELF file: bad section header size"); 77 | } 78 | 79 | for s in 0..section_count { 80 | let offset = section_header_off + section_header_sz * s; 81 | 82 | if let Some(s) = self.parse_section(offset) { 83 | self.sections.push(s); 84 | } 85 | } 86 | 87 | // Make sure we have at least one ProgBits section 88 | if self.sections.iter().find(|s| { 89 | match s.contents { 90 | SectionType::ProgBits(_) => true, 91 | _ => false, 92 | } 93 | }).is_none() { 94 | panic!("No progbits section found"); 95 | } 96 | } 97 | 98 | fn parse_section(&mut self, header_offset: u64) -> Option
{ 99 | self.seek(header_offset); 100 | 101 | // Read the section header 102 | let mut header = [0; 40]; 103 | self.read(&mut header); 104 | 105 | let section_type = word(&header[4..]); 106 | let section_flags = word(&header[8..]); 107 | let section_addr = word(&header[12..]); 108 | let section_offset = word(&header[16..]) as u64; 109 | let section_size = word(&header[20..]); 110 | let section_align = word(&header[32..]); 111 | 112 | if section_align != 0 && section_addr % section_align != 0 { 113 | // I think it's not possible (unless the ELF is completely 114 | // broken) but I'd rather make sure 115 | panic!("bad section alignment: addr {:08x} align {}", 116 | section_addr, section_align); 117 | } 118 | 119 | // We only keep sections with the ALLOC attribute flag. 120 | if section_flags & 2 != 0 { 121 | match section_type { 122 | // Progbits 123 | 1 => { 124 | // This section contains data stored in the elf 125 | // file. 126 | let mut data = vec![0; section_size as usize]; 127 | self.seek(section_offset); 128 | self.read(&mut data); 129 | 130 | Some(Section { 131 | base: section_addr, 132 | contents: SectionType::ProgBits(data), 133 | }) 134 | } 135 | // Nobits 136 | 8 => { 137 | // This is a "BSS" type section: not present in 138 | // the file but must be initialized to 0 by the 139 | // loader. 140 | Some(Section { 141 | base: section_addr, 142 | contents: SectionType::Memfill(section_size), 143 | }) 144 | } 145 | _ => None, 146 | } 147 | } else { 148 | None 149 | } 150 | } 151 | 152 | fn read(&mut self, buf: &mut [u8]) { 153 | match self.elf.read(buf) { 154 | Ok(n) => { 155 | if n != buf.len() { 156 | panic!("Unexpected end of file"); 157 | } 158 | } 159 | Err(e) => panic!("Read failed: {}", e), 160 | } 161 | } 162 | 163 | fn seek(&mut self, pos: u64) { 164 | match self.elf.seek(SeekFrom::Start(pos)) { 165 | Ok(n) => { 166 | if n != pos { 167 | panic!("Unexpected end of file"); 168 | } 169 | } 170 | Err(e) => panic!("Read failed: {}", e), 171 | } 172 | } 173 | 174 | pub fn entry(&self) -> u32 { 175 | self.entry 176 | } 177 | 178 | pub fn into_sections(self) -> Vec
{ 179 | self.sections 180 | } 181 | } 182 | 183 | /// Retreive a big endian 16bit integer 184 | fn halfword(buf: &[u8]) -> u16 { 185 | (buf[0] as u16) | ((buf[1] as u16) << 8) 186 | } 187 | 188 | /// Retreive a big endian 32bit integer 189 | fn word(buf: &[u8]) -> u32 { 190 | (halfword(buf) as u32) | ((halfword(&buf[2..]) as u32) << 16) 191 | } 192 | -------------------------------------------------------------------------------- /elf2psexe/src/psexe.rs: -------------------------------------------------------------------------------- 1 | use std::fs::{File, OpenOptions}; 2 | use std::io::Write; 3 | use std::path::Path; 4 | 5 | use Section; 6 | use SectionType; 7 | use Region; 8 | 9 | pub struct PsxWriter { 10 | psexe: File, 11 | region: Region, 12 | } 13 | 14 | impl PsxWriter { 15 | pub fn new(path: &Path, region: Region) -> PsxWriter { 16 | let psexe = 17 | match OpenOptions::new() 18 | .write(true).create(true).truncate(true).open(path) { 19 | Ok(psexe) => psexe, 20 | Err(e) => panic!("Can't open {}: {}", path.display(), e), 21 | }; 22 | 23 | PsxWriter { 24 | psexe: psexe, 25 | region: region, 26 | } 27 | } 28 | 29 | pub fn dump(mut self, entry: u32, mut sections: Vec
) { 30 | // Magic 31 | self.write(b"PS-X EXE"); 32 | 33 | // Padding 34 | self.write(&[0; 8]); 35 | 36 | // First PC address (entry point) 37 | println!("Entry PC: 0x{:08x}", entry); 38 | self.write32(entry); 39 | 40 | // Initial GP, we don't use that for now 41 | self.write32(0); 42 | 43 | // Sort the sections by base address since that's how we're 44 | // going to dump them 45 | sections.sort_by(|s1, s2| s1.base.cmp(&s2.base)); 46 | 47 | // Base address 48 | let base = sections[0].base; 49 | println!("Base address: 0x{:08x}", base); 50 | self.write32(base); 51 | 52 | // Object size (file size minus the 2048bytes header). Since 53 | // we've sorted the list by base address and sections 54 | // shouldn't overlap we just look for the lats progbit section 55 | // and see where it ends. Then we can just substract the base 56 | // address 57 | let end_addr = sections.iter().filter_map( 58 | |s| { 59 | match s.contents { 60 | // For progbit sections we compute the end address 61 | // and return that 62 | SectionType::ProgBits(ref p) => 63 | Some(s.base + p.len() as u32), 64 | // We ignore memfill sections since they take no 65 | // space in the file 66 | SectionType::Memfill(_) => None, 67 | } 68 | }) 69 | // We only care about the last section 70 | .last(); 71 | 72 | let end_addr = 73 | match end_addr { 74 | Some(e) => e, 75 | _ => panic!("No progbits section found!"), 76 | }; 77 | 78 | let object_size = end_addr - base; 79 | // Arbitrarily refuse object files greater than 1MB. The PSX 80 | // only has 2MB of RAM, most executables are a few hundred KBs 81 | // at most. 82 | if object_size > 1 * 1024 * 1024 { 83 | panic!("Object is too big"); 84 | } 85 | 86 | println!("Text+data size: {}B", object_size); 87 | self.write32(object_size); 88 | 89 | // I don't know what the two next words do but the Nocash spec 90 | // says that they're "usually 0" 91 | self.write(&[0; 8]); 92 | 93 | // Next we want to initialize the memfill 94 | let mut memfill = sections.iter().filter_map( 95 | |s| { 96 | match s.contents { 97 | SectionType::Memfill(len) => Some((s.base, len)), 98 | _ => None, 99 | } 100 | }); 101 | 102 | let (memfill_base, memfill_length) = 103 | match memfill.next() { 104 | Some(m) => m, 105 | // No memfill 106 | None => (0, 0), 107 | }; 108 | 109 | println!("Memfill base: 0x{:08x}", memfill_base); 110 | self.write32(memfill_base); 111 | println!("Memfill length: {}B", memfill_length); 112 | self.write32(memfill_length); 113 | 114 | // Make sure we don't have more than one memfill sections. 115 | // 116 | // XXX Technically we could handle more than one (either by 117 | // merging contiguous regions or putting zeroed sections 118 | // directly in the file like progbits) but I don't want to 119 | // bother with that for now 120 | if memfill.next().is_some() { 121 | panic!("Got more than one memfill sections!"); 122 | } 123 | 124 | // For now hardcode SP base and offset. 125 | let sp = 0x801ffff0; 126 | let sp_off = 0; 127 | 128 | println!("SP base: 0x{:08x}", sp); 129 | self.write32(sp); 130 | println!("SP offset: {}", sp_off); 131 | self.write32(sp_off); 132 | 133 | // Padding that is used by the BIOS to store R16, R28, R30, SP 134 | // and RA when it starts the execution of our program. 135 | self.write(&[0; 20]); 136 | 137 | // License marker. 138 | self.write(b"Sony Computer Entertainment Inc. for "); 139 | 140 | let region_str = 141 | match self.region { 142 | Region::NorthAmerica => "North America area", 143 | Region::Europe => "Europe area", 144 | Region::Japan => "Japan area", 145 | }; 146 | 147 | println!("Region: {}", region_str); 148 | self.write(region_str.as_bytes()); 149 | 150 | // *huge* pad before we reach the actual object. Not sure why 151 | // they did that... 152 | let pad = vec![0; 1935 - region_str.len()]; 153 | self.write(&pad); 154 | 155 | // Finally we can dump the progbits sections 156 | let progbits = sections.iter().filter_map( 157 | |s| { 158 | match &s.contents { 159 | &SectionType::ProgBits(ref data) => Some((s.base, data)), 160 | _ => None, 161 | } 162 | }); 163 | 164 | let mut offset = base; 165 | 166 | for (base, data) in progbits { 167 | // If there's a gap between the previous section and this 168 | // one we fill it with 0s 169 | let padlen = base - offset; 170 | let pad = vec![0; padlen as usize]; 171 | self.write(&pad); 172 | 173 | // And we can dump the data 174 | self.write(data); 175 | 176 | // Update the offset 177 | offset = base + data.len() as u32; 178 | } 179 | } 180 | 181 | fn write(&mut self, v: &[u8]) { 182 | match self.psexe.write(v) { 183 | Ok(n) => { 184 | if n != v.len() { 185 | panic!("Couldn't write {} bytes to file", v.len()); 186 | } 187 | } 188 | Err(e) => panic!("Write failed: {}", e), 189 | } 190 | } 191 | 192 | /// Write 32bit value in the file in little endian 193 | fn write32(&mut self, v: u32) { 194 | self.write(&[ v as u8, 195 | (v >> 8) as u8, 196 | (v >> 16) as u8, 197 | (v >> 24) as u8]); 198 | } 199 | } 200 | --------------------------------------------------------------------------------