Last active
August 5, 2019 00:37
-
-
Save CryZe/a2ac80ee5f2bcb2f3a8c0beb9da78aee to your computer and use it in GitHub Desktop.
Wind Waker - LiveSplit One Rom Hack
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use libtww::game::gamepad; | |
| use libtww::system; | |
| pub const DPAD_LEFT: Button = Button(0); | |
| pub const DPAD_RIGHT: Button = Button(1); | |
| pub const DPAD_DOWN: Button = Button(2); | |
| pub const DPAD_UP: Button = Button(3); | |
| pub const Z: Button = Button(4); | |
| pub const R: Button = Button(5); | |
| pub const L: Button = Button(6); | |
| pub const A: Button = Button(7); | |
| pub const B: Button = Button(8); | |
| pub const X: Button = Button(9); | |
| pub const Y: Button = Button(10); | |
| pub const START: Button = Button(11); | |
| const REPEAT_TIME: u32 = 3; | |
| const REPEAT_DELAY: u32 = 5; | |
| pub struct Button(usize); | |
| impl Button { | |
| pub fn is_pressed(&self) -> bool { | |
| let delta = system::get_frame_count() - unsafe { button_states[self.0].pressed_frame }; | |
| let just_clicked = delta == 0; | |
| let held_down_long_enough = delta > REPEAT_DELAY; | |
| let is_repeat_frame = held_down_long_enough && delta % REPEAT_TIME < REPEAT_TIME - 1; | |
| self.is_down() && (just_clicked || is_repeat_frame) | |
| } | |
| pub fn is_down(&self) -> bool { | |
| unsafe { button_states[self.0].is_down } | |
| } | |
| } | |
| static mut button_states: [ButtonState; 12] = [ | |
| ButtonState::new(gamepad::DPAD_LEFT), | |
| ButtonState::new(gamepad::DPAD_RIGHT), | |
| ButtonState::new(gamepad::DPAD_DOWN), | |
| ButtonState::new(gamepad::DPAD_UP), | |
| ButtonState::new(gamepad::Z), | |
| ButtonState::new(gamepad::R), | |
| ButtonState::new(gamepad::L), | |
| ButtonState::new(gamepad::A), | |
| ButtonState::new(gamepad::B), | |
| ButtonState::new(gamepad::X), | |
| ButtonState::new(gamepad::Y), | |
| ButtonState::new(gamepad::START), | |
| ]; | |
| struct ButtonState { | |
| button: u16, | |
| pressed_frame: u32, | |
| is_down: bool, | |
| } | |
| impl ButtonState { | |
| const fn new(button: u16) -> Self { | |
| ButtonState { | |
| button: button, | |
| pressed_frame: 0xFFFFFFFF, | |
| is_down: false, | |
| } | |
| } | |
| } | |
| static mut buttons_pressed: u16 = 0; | |
| static mut buttons_down: u16 = 0; | |
| static mut buttons_down_last_frame: u16 = 0; | |
| #[no_mangle] | |
| pub extern "C" fn read_controller() -> u32 { | |
| unsafe { | |
| buttons_down_last_frame = buttons_down; | |
| buttons_down = system::memory::read(0x803E0CF8); | |
| buttons_pressed = buttons_down & (0xFFFF ^ buttons_down_last_frame); | |
| for state in &mut button_states { | |
| state.is_down = state.button & buttons_down != 0; | |
| if state.button & buttons_pressed != 0 { | |
| state.pressed_frame = system::get_frame_count() + 1; | |
| } | |
| } | |
| } | |
| 0x80000000 | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #![feature(alloc_error_handler)] | |
| #![no_std] | |
| extern crate alloc; | |
| use alloc::borrow::ToOwned; | |
| use alloc::vec::Vec; | |
| use gcn::gx; | |
| use livesplit_core::{ | |
| component::Graph, | |
| layout::{Component, LayoutDirection}, | |
| rendering::{Backend, Mesh, Renderer, Rgba, Transform, Vertex}, | |
| Clock, Duration, Layout, Run, Segment, Timer, | |
| }; | |
| #[global_allocator] | |
| #[cfg(target_arch = "powerpc")] | |
| static A: libtww::Alloc = libtww::Alloc; | |
| pub mod runtime; | |
| mod controller; | |
| pub struct WindWakerClock; | |
| impl Clock for WindWakerClock { | |
| fn now(&self) -> Duration { | |
| let ticks = unsafe { gcn::os::get_time() }; | |
| let seconds = ticks as f64 / 40_500_000.0 + 946_684_800.0; | |
| let nanoseconds = seconds * 1_000_000_000.0; | |
| Duration::nanoseconds(nanoseconds as _) | |
| } | |
| } | |
| pub struct GCNRenderer; | |
| type GCNMesh = (Vec<Vertex>, Vec<u16>); | |
| impl Backend for GCNRenderer { | |
| type Mesh = GCNMesh; | |
| type Texture = (); | |
| fn create_mesh(&mut self, mesh: &Mesh) -> Self::Mesh { | |
| (mesh.vertices().to_owned(), mesh.indices().to_owned()) | |
| } | |
| fn render_mesh( | |
| &mut self, | |
| (vertices, indices): &Self::Mesh, | |
| transform: Transform, | |
| [tl, tr, br, bl]: [Rgba; 4], | |
| _: Option<&Self::Texture>, | |
| ) { | |
| unsafe { | |
| gx::begin(gx::TRIANGLES, gx::VTXFMT0, indices.len() as _); | |
| { | |
| for &index in indices { | |
| let Vertex { x, y, u, v } = vertices[index as usize]; | |
| let top = [ | |
| (1.0 - u) * tl[0] + u * tr[0], | |
| (1.0 - u) * tl[1] + u * tr[1], | |
| (1.0 - u) * tl[2] + u * tr[2], | |
| (1.0 - u) * tl[3] + u * tr[3], | |
| ]; | |
| let bottom = [ | |
| (1.0 - u) * bl[0] + u * br[0], | |
| (1.0 - u) * bl[1] + u * br[1], | |
| (1.0 - u) * bl[2] + u * br[2], | |
| (1.0 - u) * bl[3] + u * br[3], | |
| ]; | |
| let [r, g, b, a] = [ | |
| (1.0 - v) * top[0] + v * bottom[0], | |
| (1.0 - v) * top[1] + v * bottom[1], | |
| (1.0 - v) * top[2] + v * bottom[2], | |
| (1.0 - v) * top[3] + v * bottom[3], | |
| ]; | |
| let color = u32::from_be_bytes([ | |
| (r * 255.0) as u8, | |
| (g * 255.0) as u8, | |
| (b * 255.0) as u8, | |
| (a * 255.0) as u8, | |
| ]); | |
| let (x, y) = ( | |
| x * transform.m11 + y * transform.m21 + transform.m31, | |
| x * transform.m21 + y * transform.m22 + transform.m32, | |
| ); | |
| let (x, y) = (640.0 * x, 30.0 * y); | |
| gx::submit_f32s(&[x, y, 0.0]); | |
| gx::submit_u32(color); | |
| } | |
| } | |
| gx::end(); | |
| } | |
| } | |
| fn free_mesh(&mut self, mesh: Self::Mesh) {} | |
| fn create_texture(&mut self, width: u32, height: u32, data: &[u8]) -> Self::Texture {} | |
| fn free_texture(&mut self, texture: Self::Texture) {} | |
| fn resize(&mut self, _width: f32, _height: f32) {} | |
| } | |
| struct State { | |
| timer: Timer, | |
| renderer: Renderer<GCNMesh, ()>, | |
| layout: Layout, | |
| } | |
| static mut STATE: Option<State> = None; | |
| #[no_mangle] | |
| pub unsafe extern "C" fn draw() { | |
| let State { | |
| timer, | |
| renderer, | |
| layout, | |
| } = STATE.get_or_insert_with(|| { | |
| livesplit_core::register_clock(WindWakerClock); | |
| let mut run = Run::new(); | |
| run.set_game_name("Wind Waker"); | |
| run.set_category_name("Any%"); | |
| run.push_segment(Segment::new("Spoils Bag")); | |
| run.push_segment(Segment::new("FF1")); | |
| run.push_segment(Segment::new("Wind Waker")); | |
| run.push_segment(Segment::new("Dragon Roost Cavern")); | |
| run.push_segment(Segment::new("Forbidden Woods")); | |
| run.push_segment(Segment::new("Ganondorf")); | |
| let mut layout = Layout::default_layout(); | |
| match &mut layout.components[1] { | |
| Component::Splits(splits) => { | |
| splits.settings_mut().visual_split_count = 4; | |
| } | |
| _ => unreachable!(), | |
| } | |
| layout.general_settings_mut().direction = LayoutDirection::Horizontal; | |
| // layout.push(Graph::new()); | |
| let timer = Timer::new(run).unwrap(); | |
| let renderer = Renderer::new(); | |
| State { | |
| timer, | |
| renderer, | |
| layout, | |
| } | |
| }); | |
| if controller::DPAD_UP.is_pressed() { | |
| timer.undo_split(); | |
| } | |
| if controller::DPAD_LEFT.is_pressed() { | |
| timer.reset(true); | |
| } | |
| if controller::DPAD_RIGHT.is_pressed() { | |
| timer.split_or_start(); | |
| } | |
| if controller::DPAD_DOWN.is_pressed() { | |
| timer.skip_split(); | |
| } | |
| let state = layout.state(&timer); | |
| gx::clear_vtx_desc(); | |
| gx::set_vtx_desc(gx::VA_POS as u8, gx::DIRECT); | |
| gx::set_vtx_desc(gx::VA_CLR0 as u8, gx::DIRECT); | |
| gx::set_blend_mode( | |
| gx::BM_BLEND, | |
| gx::BL_SRCALPHA, | |
| gx::BL_INVSRCALPHA, | |
| gx::LO_SET, | |
| ); | |
| gx::load_pos_mtx_imm(&mut gx::Mtx::identity(), gx::PNMTX0); | |
| gx::set_vtx_attr_fmt(gx::VTXFMT0, gx::VA_POS, gx::POS_XYZ, gx::F32, 0); | |
| renderer.render(&mut GCNRenderer, (640.0, 30.0), &state); | |
| gx::set_vtx_attr_fmt(gx::VTXFMT0, gx::VA_POS, gx::POS_XYZ, gx::S16, 0); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #![cfg(target_arch = "powerpc")] | |
| #[panic_handler] | |
| pub fn panic(info: &::core::panic::PanicInfo) -> ! { | |
| // gcn::report!("{}", info); | |
| loop {} | |
| } | |
| #[alloc_error_handler] | |
| fn alloc_error(_: core::alloc::Layout) -> ! { | |
| panic!("Failed allocating") | |
| } | |
| #[no_mangle] | |
| pub extern "C" fn fmodf(x: f32, y: f32) -> f32 { | |
| libm::fmodf(x, y) | |
| } | |
| #[no_mangle] | |
| pub extern "C" fn fmod(x: f64, y: f64) -> f64 { | |
| libm::fmod(x, y) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment