Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
stringflow committed Oct 11, 2020
0 parents commit 08c3b6d
Show file tree
Hide file tree
Showing 17 changed files with 1,505 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Generated by Cargo
# will have compiled files and executables
/target/

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk

# rustfmt
.rustfmt.toml

# vs code
/.vscode/
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "gbc-input-display"
version = "1.0.0"
authors = ["stringflow"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
byteorder = "1"
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# GBC Input Display

A lightweight input display written in Rust that can be utilized for GameBoy and GameBoy Color games.
This was the first Rust code I wrote, so expect it to not be very nice.
238 changes: 238 additions & 0 deletions src/application.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
use crate::bmp::*;
use crate::configuration::*;
use crate::dpad::*;
use crate::gambatte::*;
use crate::gfx::*;
use crate::key::*;
use crate::platform::*;
use crate::winapi::*;

use std::thread;
use std::time::*;

pub const KEY_SIZE: i32 = 34;
pub const ARROW_SIZE: i32 = 14;
pub const CHARACTER_SIZE: i32 = 16;

pub const WIDTH: i32 = KEY_SIZE * 9;
pub const HEIGHT: i32 = KEY_SIZE * 8;
pub const SCALE: i32 = 1;
pub const TITLE: &str = "Input Display";

pub const UP: usize = 0;
pub const DOWN: usize = 1;
pub const LEFT: usize = 2;
pub const RIGHT: usize = 3;
pub const POWER: usize = 8;

pub struct Application {
pub platform: Platform,
pub keyset: SpriteSheet,
pub arrowset: SpriteSheet,
pub font: SpriteSheet,
pub palettes: Vec<(String, Palette)>,
pub palette: Palette,
pub palette_index: usize,
pub keys: Vec<Key>,
pub dpad: Vec<DpadKey>,
pub key_to_configure: i32,
pub text_buffer: String,
pub gambatte_sync: bool,
}

pub static mut APP_POINTER: *mut Application = std::ptr::null_mut();

pub fn start() {
let mut app = Application {
platform: Platform::new(WIDTH, HEIGHT, SCALE, TITLE).unwrap(),
keyset: SpriteSheet::new(bmp_load(include_bytes!("gfx/keys.bmp")).unwrap(), KEY_SIZE, KEY_SIZE),
arrowset: SpriteSheet::new(bmp_load(include_bytes!("gfx/arrows.bmp")).unwrap(), ARROW_SIZE, ARROW_SIZE),
font: SpriteSheet::new(bmp_load(include_bytes!("gfx/font.bmp")).unwrap(), CHARACTER_SIZE, CHARACTER_SIZE),
palettes: vec![
(String::from("Brown"), vec![[0, 0, 0], [228, 150, 133], [228, 150, 133], [248, 248, 248]]),
(String::from("Pastel Mix"), vec![[0, 0, 0], [228, 144, 163], [228, 144, 163], [242, 226, 187]]),
(String::from("Blue"), vec![[0, 0, 0], [225, 128, 150], [113, 182, 208], [248, 248, 248]]),
(String::from("Green"), vec![[0, 0, 0], [96, 186, 46], [96, 186, 46], [248, 248, 248]]),
(String::from("Red"), vec![[0, 0, 0], [131, 198, 86], [225, 128, 150], [248, 248, 248]]),
(String::from("Orange"), vec![[0, 0, 0], [232, 186, 77], [232, 186, 77], [248, 248, 248]]),
(String::from("Dark Blue"), vec![[0, 0, 0], [225, 128, 150], [141, 156, 191], [248, 248, 248]]),
(String::from("Dark Green"), vec![[0, 0, 0], [225, 128, 150], [131, 198, 86], [248, 248, 248]]),
(String::from("Dark Brown"), vec![[78, 38, 28], [228, 150, 133], [189, 146, 144], [241, 216, 206]]),
(String::from("Yellow"), vec![[0, 0, 0], [113, 182, 208], [232, 186, 77], [248, 248, 248]]),
(String::from("Monochrome"), vec![[0, 0, 0], [160, 160, 160], [160, 160, 160], [248, 248, 248]]),
(String::from("Inverted"), vec![[248, 248, 248], [24, 128, 104], [24, 128, 104], [0, 0, 0]]),
],
palette: Vec::new(),
palette_index: 0,
keys: vec![
Key { ipt: 0, name: String::from("UP"), reg_entry: String::from("GameUpKey1"), x: 2.0, y: 2.0, idx: 5 },
Key { ipt: 0, name: String::from("DOWN"), reg_entry: String::from("GameDownKey1"), x: 2.0, y: 4.0, idx: 6 },
Key { ipt: 0, name: String::from("LEFT"), reg_entry: String::from("GameLeftKey1"), x: 1.0, y: 3.0, idx: 7 },
Key { ipt: 0, name: String::from("RIGHT"), reg_entry: String::from("GameRightKey1"), x: 3.0, y: 3.0, idx: 8 },
Key { ipt: 0, name: String::from("SELECT"), reg_entry: String::from("GameSelectKey1"), x: 3.5, y: 6.0, idx: 2 },
Key { ipt: 0, name: String::from("START"), reg_entry: String::from("GameStartKey1"), x: 4.5, y: 6.0, idx: 3 },
Key { ipt: 0, name: String::from("B"), reg_entry: String::from("GameBKey1"), x: 5.5, y: 4.0, idx: 1 },
Key { ipt: 0, name: String::from("A"), reg_entry: String::from("GameAKey1"), x: 7.0, y: 3.0, idx: 0 },
Key { ipt: 0, name: String::from("POWER"), reg_entry: String::from("PlayHard resetKey1"), x: 7.0, y: 1.0 - (6.0 / KEY_SIZE as f32), idx: 4 },
],
dpad: vec![
// TODO: Is there a better way to construct overhangs?
DpadKey::new(UP, 0, -2, 2, KEY_SIZE - 2, 2, KEY_SIZE, KEY_SIZE - 4, 2, 10, 8),
DpadKey::new(DOWN, 0, 2, 2, 0, 2, -2, KEY_SIZE - 4, 2, 10, 6),
DpadKey::new(LEFT, -2, 0, KEY_SIZE - 2, 2, KEY_SIZE, 2, 2, KEY_SIZE - 4, 8, 7),
DpadKey::new(RIGHT, 2, 0, 0, 2, -2, 2, 2, KEY_SIZE - 4, 12, 7),
],
key_to_configure: -1,
text_buffer: String::from(""),
gambatte_sync: false,
};

app.platform.register_callback(WM_CLOSE, on_quit);
app.platform.register_callback(WM_RBUTTONUP, on_rightclick);
app.platform.register_hook(WH_KEYBOARD_LL, on_key_state);

unsafe { APP_POINTER = &mut app };

match load_configuration() {
Ok(_) => {}
Err(_) => change_palette(3),
}

app.platform.start_message_queue();
}

fn on_quit(_wparam: usize, _lparam: usize) {
let mut app = unsafe { &mut *APP_POINTER };
app.platform.running = false;
}

fn on_rightclick(_wparam: usize, _lparam: usize) {
let app = unsafe { &mut *APP_POINTER };
let mut item_counter = 0;

let mut palette_menu: Vec<MenuItem> = Vec::new();

for (i, pal) in app.palettes.iter().enumerate() {
palette_menu.push(if app.palette_index == i { MenuItem::Checked(pal.0.clone()) } else { MenuItem::Unchecked(pal.0.clone()) });
}

let res = app.platform.show_menu(
&[
MenuItem::Unchecked(String::from("Sync Gambatte Keybinds")),
MenuItem::Unchecked(String::from("Set Keybinds")),
MenuItem::Seperator,
MenuItem::SubMenu(String::from("Palettes"), app.platform.create_menu(palette_menu.as_slice(), &mut item_counter)),
],
&mut item_counter,
) as usize;
match res {
// TODO: These should not be constants, in case more palettes get added
13 => {
let result_text = match sync_gambatte_keybindings() {
Ok(_) => "SUCCESS",
Err(_) => "FAILURE",
};

thread::spawn(move || {
app.text_buffer = String::from(result_text);
draw_background();
app.text_buffer = String::from("");
thread::sleep(Duration::from_secs(3));
draw_background();
});
}
14 => {
configure_next_key();
}
// TODO: Uhhh, is this the correct way to accomplish this
x => {
if res > 0 && res <= palette_menu.len() {
change_palette(x as usize - 1)
}
}
};
save_configuration().ok();
}

pub fn draw_background() {
let app = unsafe { &mut *APP_POINTER };

macro_rules! coord {
($base:expr, $offs:expr) => {
($base * KEY_SIZE as f32) as i32 + $offs
};
}

macro_rules! draw {
($sheet:expr, $x:expr, $y:expr, $idx:expr) => {
app.platform.offscreen_buffer.draw_sprite(&app.palette, $sheet, coord!($x, 0), coord!($y, 0) as i32, $idx)
};
}

update_dpad();

app.platform.offscreen_buffer.clear(app.palette[3]);
for key in app.keys.iter() {
draw!(&app.keyset, key.x, key.y, key.idx);
}

draw!(&app.keyset, app.keys[UP].x, app.keys[UP].y + 1.0, 32);

for dpad in app.dpad.iter() {
let key = &app.keys[dpad.key_id];
draw!(
&app.arrowset,
key.x + (dpad.arrow.key_x_offset + dpad.arrow.current_x_shift) as f32 / KEY_SIZE as f32,
key.y + (dpad.arrow.key_y_offset + dpad.arrow.current_y_shift) as f32 / KEY_SIZE as f32,
dpad.arrow.idx
);

app.platform.offscreen_buffer.draw_subsprite(
&app.palette,
&app.keyset,
coord!(key.x, dpad.overhang.x_dest),
coord!(key.y, dpad.overhang.y_dest),
key.idx,
dpad.overhang.x_src,
dpad.overhang.y_src,
dpad.overhang.width,
dpad.overhang.height,
);
}

app.platform.offscreen_buffer.draw_text(&app.palette, &app.font, &app.text_buffer, (WIDTH - app.text_buffer.len() as i32 * CHARACTER_SIZE) / 2, coord!(7.0, (KEY_SIZE - CHARACTER_SIZE) / 2));

app.platform.update_window();
}

pub fn change_palette(index: usize) {
let app = unsafe { &mut *APP_POINTER };
app.palette = app.palettes[index].1.clone();
app.palette_index = index;
draw_background();
}

unsafe extern "system" fn on_key_state(code: i32, wparam: usize, lparam: usize) -> usize {
let app = &mut *APP_POINTER;
let key_code = std::ptr::read(lparam as *const u32);
let key_state = app.platform.key_state_from_wparam(wparam);

if app.key_to_configure == -1 {
let mut key = app.keys.iter_mut().find(|key| key.ipt == key_code);

if let None = key {
if (app.platform.is_key_down(VK_CONTROL) && key_code as u32 == 'R' as u32) || (app.platform.is_key_down('R' as u32) && key_code == VK_CONTROL) {
key = Some(&mut app.keys[POWER]);
}
}

if let Some(key) = key {
key.set_pressed(key_state == KeyState::Pressed);
draw_background();
}
} else if key_state == KeyState::Pressed {
configure_current_key(key_code);
}

return CallNextHookEx(0, code, wparam, lparam);
}
83 changes: 83 additions & 0 deletions src/bmp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
extern crate byteorder;

use byteorder::*;
use std::convert::*;
use std::io::{Cursor, Error, Read, Seek, SeekFrom};
use std::ops::Index;

pub struct Bitmap {
pub width: i32,
pub height: i32,
pub data: Vec<u8>,
}

#[derive(Debug)]
pub enum BMPError {
WrongSignature,
UnsupportedBitsPerPixel,
IOError,
}

struct BitmapHeader {
pub file_size: u32,
pub reserved: u32,
pub bitmap_offset: u32,
pub size: u32,
pub width: i32,
pub height: i32,
pub planes: u16,
pub bits_per_pixel: u16,
}

impl From<Error> for BMPError {
fn from(_error: Error) -> Self {
return BMPError::IOError;
}
}

impl Index<usize> for Bitmap {
type Output = u8;

fn index(&self, index: usize) -> &Self::Output {
return &self.data[index];
}
}

const BMP_SIGNATURE: u16 = 0x424d;
const BPP: u8 = 4;

pub fn bmp_load(data: &[u8]) -> Result<Bitmap, BMPError> {
let mut pointer = Cursor::new(data);

if pointer.read_u16::<BigEndian>()? != BMP_SIGNATURE {
return Err(BMPError::WrongSignature);
}

let header = BitmapHeader {
file_size: pointer.read_u32::<LittleEndian>()?,
reserved: pointer.read_u32::<LittleEndian>()?,
bitmap_offset: pointer.read_u32::<LittleEndian>()?,
size: pointer.read_u32::<LittleEndian>()?,
width: pointer.read_i32::<LittleEndian>()?,
height: pointer.read_i32::<LittleEndian>()?,
planes: pointer.read_u16::<LittleEndian>()?,
bits_per_pixel: pointer.read_u16::<LittleEndian>()?,
};

if header.bits_per_pixel != 32 {
return Err(BMPError::UnsupportedBitsPerPixel);
}

let mut px = [0; 4];
let mut data = vec![0; (header.width * header.height) as usize];

pointer.seek(SeekFrom::Start(header.bitmap_offset as u64))?;
for y in 0..header.height {
for x in 0..header.width {
pointer.read(&mut px)?;
data[(x + (header.height - y - 1) * header.width) as usize] = px[0] / (BPP << 4);
}
}

return Ok(Bitmap { width: header.width, height: header.height, data: data });
}
Loading

0 comments on commit 08c3b6d

Please sign in to comment.