2024-05-08 12:42:40 +02:00
|
|
|
use std::mem::size_of;
|
2024-05-08 13:32:13 +02:00
|
|
|
use std::net::UdpSocket;
|
2024-05-08 12:42:40 +02:00
|
|
|
|
|
|
|
use clap::Parser;
|
|
|
|
use num_derive::FromPrimitive;
|
|
|
|
|
|
|
|
#[derive(Parser, Debug)]
|
|
|
|
struct Cli {
|
|
|
|
#[arg(long = "bind", default_value = "0.0.0.0:2342")]
|
|
|
|
bind: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, FromPrimitive)]
|
|
|
|
enum DisplayCommand {
|
|
|
|
CmdClear = 0x0002,
|
|
|
|
CmdCp437data = 0x0003,
|
|
|
|
CmdCharBrightness = 0x0005,
|
|
|
|
CmdBrightness = 0x0007,
|
|
|
|
CmdHardReset = 0x000b,
|
|
|
|
CmdFadeOut = 0x000d,
|
|
|
|
CmdBitmapLegacy = 0x0010,
|
|
|
|
CmdBitmapLinear = 0x0012,
|
|
|
|
CmdBitmapLinearWin = 0x0013,
|
|
|
|
CmdBitmapLinearAnd = 0x0014,
|
|
|
|
CmdBitmapLinearOr = 0x0015,
|
|
|
|
CmdBitmapLinearXor = 0x0016,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[repr(u16)]
|
|
|
|
enum DisplaySubcommand {
|
|
|
|
SubCmdBitmapNormal = 0x0,
|
|
|
|
SubCmdBitmapCompressZ = 0x677a,
|
|
|
|
SubCmdBitmapCompressBz = 0x627a,
|
|
|
|
SubCmdBitmapCompressLz = 0x6c7a,
|
|
|
|
SubCmdBitmapCompressZs = 0x7a73,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[repr(C)]
|
|
|
|
#[derive(Debug)]
|
|
|
|
struct HdrWindow {
|
|
|
|
command: DisplayCommand,
|
|
|
|
x: u16,
|
|
|
|
y: u16,
|
|
|
|
w: u16,
|
|
|
|
h: u16,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[repr(C)]
|
|
|
|
struct HdrBitmap {
|
|
|
|
command: DisplayCommand,
|
|
|
|
offset: u16,
|
|
|
|
length: u16,
|
|
|
|
subcommand: u16,
|
|
|
|
reserved: u16,
|
|
|
|
}
|
|
|
|
|
2024-05-08 20:17:39 +02:00
|
|
|
const TILE_SIZE: u16 = 8;
|
|
|
|
const TILE_WIDTH: u16 = 65;
|
|
|
|
const TILE_HEIGHT: u16 = 20;
|
|
|
|
const TILE_COUNT: u16 = TILE_WIDTH * TILE_HEIGHT;
|
|
|
|
const PIXEL_WIDTH: u16 = TILE_WIDTH * TILE_SIZE;
|
|
|
|
const PIXEL_HEIGHT: u16 = TILE_HEIGHT * TILE_SIZE;
|
|
|
|
const PIXEL_COUNT: u16 = PIXEL_WIDTH * TILE_HEIGHT;
|
|
|
|
|
2024-05-08 12:42:40 +02:00
|
|
|
fn main() -> std::io::Result<()> {
|
|
|
|
assert_eq!(size_of::<HdrWindow>(), 10, "invalid struct size");
|
|
|
|
|
|
|
|
let cli = Cli::parse();
|
2024-05-08 13:21:31 +02:00
|
|
|
println!("running with args: {:?}", &cli);
|
|
|
|
|
|
|
|
loop {
|
|
|
|
// to emulate a hard reset, the actual main method gets called until it crashes
|
|
|
|
main2(&cli).unwrap();
|
|
|
|
}
|
|
|
|
}
|
2024-05-08 12:42:40 +02:00
|
|
|
|
2024-05-08 13:21:31 +02:00
|
|
|
fn main2(cli: &Cli) -> std::io::Result<()> {
|
|
|
|
println!("display booting up");
|
|
|
|
|
2024-05-08 20:17:39 +02:00
|
|
|
let screen = [0u8; TILE_COUNT as usize];
|
|
|
|
|
2024-05-08 13:21:31 +02:00
|
|
|
let socket = UdpSocket::bind(&cli.bind)?;
|
2024-05-08 12:42:40 +02:00
|
|
|
let mut buf = [0; 8985];
|
|
|
|
|
|
|
|
loop {
|
|
|
|
let (amount, source) = socket.recv_from(&mut buf)?;
|
|
|
|
let received = &mut buf[..amount];
|
|
|
|
|
2024-05-08 13:32:13 +02:00
|
|
|
let header = read_hdr_window(&received[..10]);
|
2024-05-08 20:17:39 +02:00
|
|
|
if let Err(err) = header {
|
|
|
|
println!("could not read header: {}", err);
|
2024-05-08 12:42:40 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-05-08 20:17:39 +02:00
|
|
|
let header = header.unwrap();
|
2024-05-08 12:42:40 +02:00
|
|
|
let payload = &received[10..];
|
2024-05-08 20:17:39 +02:00
|
|
|
|
2024-05-08 12:42:40 +02:00
|
|
|
println!(
|
|
|
|
"received from {:?}: {:?} (and {} bytes of payload)",
|
|
|
|
source,
|
|
|
|
header,
|
|
|
|
payload.len()
|
|
|
|
);
|
|
|
|
|
2024-05-08 13:21:31 +02:00
|
|
|
match header.command {
|
|
|
|
DisplayCommand::CmdClear => {
|
|
|
|
println!("(imagine an empty screen now)")
|
|
|
|
}
|
|
|
|
DisplayCommand::CmdHardReset => {
|
|
|
|
println!("display shutting down");
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
DisplayCommand::CmdBitmapLinearWin => {
|
|
|
|
print_bitmap_linear_win(&header, payload);
|
|
|
|
}
|
|
|
|
DisplayCommand::CmdCp437data => {
|
|
|
|
print_cp437_data(&header, payload);
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
println!(
|
|
|
|
"command {:?} sent by {:?} not implemented yet",
|
|
|
|
header.command, source
|
|
|
|
);
|
|
|
|
}
|
2024-05-08 12:42:40 +02:00
|
|
|
}
|
2024-05-08 13:21:31 +02:00
|
|
|
}
|
|
|
|
}
|
2024-05-08 12:42:40 +02:00
|
|
|
|
2024-05-08 13:32:13 +02:00
|
|
|
fn read_hdr_window(buffer: &[u8]) -> Result<HdrWindow, String> {
|
|
|
|
if buffer.len() < size_of::<HdrWindow>() {
|
|
|
|
return Err("received a packet that is too small".into());
|
|
|
|
}
|
|
|
|
|
|
|
|
let command_u16 = u16::from_be(unsafe { std::ptr::read(buffer[0..=1].as_ptr() as *const u16) });
|
|
|
|
let maybe_command = num::FromPrimitive::from_u16(command_u16);
|
|
|
|
if maybe_command.is_none() {
|
|
|
|
return Err(format!("received invalid command {}", command_u16));
|
|
|
|
}
|
|
|
|
|
|
|
|
return Ok(HdrWindow {
|
|
|
|
command: maybe_command.unwrap(),
|
|
|
|
x: u16::from_be(unsafe { std::ptr::read(buffer[2..=3].as_ptr() as *const u16) }),
|
|
|
|
y: u16::from_be(unsafe { std::ptr::read(buffer[4..=5].as_ptr() as *const u16) }),
|
|
|
|
w: u16::from_be(unsafe { std::ptr::read(buffer[6..=7].as_ptr() as *const u16) }),
|
|
|
|
h: u16::from_be(unsafe { std::ptr::read(buffer[8..=9].as_ptr() as *const u16) }),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-05-08 13:21:31 +02:00
|
|
|
fn check_payload_size(buf: &[u8], expected: usize) -> bool {
|
|
|
|
let actual = buf.len();
|
|
|
|
if actual == expected {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
println!(
|
|
|
|
"expected a payload length of {} but got {}",
|
|
|
|
expected, actual
|
|
|
|
);
|
|
|
|
return false;
|
|
|
|
}
|
2024-05-08 12:42:40 +02:00
|
|
|
|
2024-05-08 13:21:31 +02:00
|
|
|
fn print_bitmap_linear_win(header: &HdrWindow, payload: &[u8]) {
|
|
|
|
if !check_payload_size(payload, (header.w * header.h) as usize) {
|
|
|
|
return;
|
|
|
|
}
|
2024-05-08 12:42:40 +02:00
|
|
|
|
2024-05-08 20:17:39 +02:00
|
|
|
println!(
|
|
|
|
"top left is offset {} tiles in x-direction and {} pixels in y-direction",
|
|
|
|
header.x, header.y
|
|
|
|
);
|
2024-05-08 13:21:31 +02:00
|
|
|
for y in 0..header.h {
|
|
|
|
for byte_x in 0..header.w {
|
|
|
|
let byte_index = (y * header.w + byte_x) as usize;
|
|
|
|
let byte = payload[byte_index];
|
|
|
|
|
2024-05-08 20:17:39 +02:00
|
|
|
for e in (1u8..7u8).rev() {
|
|
|
|
let bitmask = 1 << e;
|
|
|
|
let char = if byte & bitmask != 0 { '█' } else { ' ' };
|
2024-05-08 13:21:31 +02:00
|
|
|
print!("{}", char);
|
2024-05-08 12:42:40 +02:00
|
|
|
}
|
2024-05-08 13:21:31 +02:00
|
|
|
}
|
2024-05-08 12:42:40 +02:00
|
|
|
|
2024-05-08 13:21:31 +02:00
|
|
|
println!();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-08 13:32:13 +02:00
|
|
|
// TODO: actually convert from CP437
|
2024-05-08 13:21:31 +02:00
|
|
|
fn print_cp437_data(header: &HdrWindow, payload: &[u8]) {
|
|
|
|
if !check_payload_size(payload, (header.w * header.h) as usize) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-05-08 20:17:39 +02:00
|
|
|
println!("top left is offset by ({} | {}) tiles", header.x, header.y);
|
2024-05-08 13:21:31 +02:00
|
|
|
for y in 0..header.h {
|
2024-05-08 20:17:39 +02:00
|
|
|
for x in 0..header.w {
|
|
|
|
let byte_index = (y * header.w + x) as usize;
|
2024-05-08 13:21:31 +02:00
|
|
|
print!("{}", payload[byte_index] as char)
|
2024-05-08 12:42:40 +02:00
|
|
|
}
|
2024-05-08 13:21:31 +02:00
|
|
|
|
|
|
|
println!();
|
2024-05-08 12:42:40 +02:00
|
|
|
}
|
2024-05-08 10:50:54 +02:00
|
|
|
}
|