Skip to content

Instantly share code, notes, and snippets.

@CryZe
Last active August 5, 2019 00:37
Show Gist options
  • Select an option

  • Save CryZe/a2ac80ee5f2bcb2f3a8c0beb9da78aee to your computer and use it in GitHub Desktop.

Select an option

Save CryZe/a2ac80ee5f2bcb2f3a8c0beb9da78aee to your computer and use it in GitHub Desktop.
Wind Waker - LiveSplit One Rom Hack
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
}
#![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);
}
#![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