11 Commits

Author SHA1 Message Date
2a15eb1122 [doc] Missing images 2024-02-25 10:18:15 +05:30
537bcad73b [bump] Bump version 2024-02-25 10:13:12 +05:30
101c513838 [doc] Reword 2024-02-25 10:11:53 +05:30
eb86e33117 [doc] Reorder 2024-02-25 10:09:30 +05:30
f250eee2ea [doc] README.md 2024-02-25 10:08:35 +05:30
6b6b2211a8 [img] Keyboard tests 2024-02-25 10:07:53 +05:30
6fefd600e1 [cln] Lint errors 2024-02-25 10:01:47 +05:30
aff5103636 [ref] Cleanup 2024-02-21 19:26:33 +05:30
b166e9c004 [lic] Create LICENSE 2024-02-21 18:53:29 +05:30
4b15253580 [ref] Cleanup 2024-02-21 18:52:38 +05:30
20c90af201 [ref] Cleanup 2024-02-21 18:52:26 +05:30
16 changed files with 121 additions and 62 deletions

2
Cargo.lock generated
View File

@@ -58,7 +58,7 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "byte-pusher-emu" name = "byte-pusher-emu"
version = "0.1.0" version = "0.2.0"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"clap", "clap",

View File

@@ -1,7 +1,10 @@
[package] [package]
name = "byte-pusher-emu" name = "byte-pusher-emu"
version = "0.1.0" version = "0.2.0"
edition = "2021" edition = "2021"
license = "BSD-3"
authors = ["chrisvrose"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -14,3 +17,4 @@ sdl2 = "0.36.0"
[profile.release] [profile.release]
strip = true strip = true
lto = true

28
LICENSE Normal file
View File

@@ -0,0 +1,28 @@
BSD 3-Clause License
Copyright (c) 2024, Atreya Bain
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,27 +1,32 @@
# BytePusher Emulator # BytePusher Emulator
This is a play at emulating a BytePusher machine developed by [Javamannen](https://esolangs.org/wiki/User:Javamannen). `byte-pusher-emu` is an emulator for the BytePusher virtual machine developed by [Javamannen](https://esolangs.org/wiki/User:Javamannen).
Binary builds for linux x86_64 is available in GitHub Action runs.
Binary builds for linux x86_64 is available in the Releases section.
![Audio test](assets/audio_test.png)
<details>
<summary>More screenshots</summary>
#### Palette Test
![Screen test](assets/screen_test.png) ![Screen test](assets/screen_test.png)
Screenshot program author: [Javamannen](https://esolangs.org/wiki/User:Javamannen)
## Status #### Keyboard test
![Keyboard test](assets/kb_test.png)
- [X] Memory </details>
- [X] Color
- [X] CPU The roms showcased here have been created by [Javamannen](https://esolangs.org/wiki/User:Javamannen).
- [X] Inner loop - 65536 instructions
- [X] Outer loop - 60fps display loop Note: This is a project that was used to learn Rust and emulation basics. However, it should work good as a reference for running ROMs.
- [X] Display adapter
- [X] SDL2 adapter
- [X] Keyboard
- [X] Audio
- [X] Load a ROM
## Usage instructions ## Usage instructions
For help on how to use, please refer `byte-pusher-emu` is rather minimalistic, and needs to be launched from CLI.
For help on how to use, run the following:
```sh ```sh
./byte-pusher-emu --help ./byte-pusher-emu --help
``` ```
@@ -37,3 +42,13 @@ Loads a rom from the file specified. If `-f` is not mentioned, the VM will run w
## More information about the BytePusher VM ## More information about the BytePusher VM
https://esolangs.org/wiki/BytePusher https://esolangs.org/wiki/BytePusher
## Development Status
- [X] Memory
- [X] Color
- [X] CPU
- [X] Display adapter - SDL2
- [X] Keyboard - SDL2
- [X] Audio - SDL2
- [X] Load a ROM

BIN
assets/audio_test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
assets/kb_test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@@ -1,8 +1,10 @@
use clap::Parser; use clap::Parser;
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command(version, about, long_about = "Byte Pusher Emulator")] #[command(version, about, author)]
pub struct BytePusherArgs { pub struct BytePusherArgs {
#[arg(short,long)] #[arg(short, long, help = "ROM file to load")]
pub file_name:Option<String> pub file_name: Option<String>,
#[arg(short, long, help = "Scale at which to draw", default_value_t = 2.0)]
pub draw_scale: f32,
} }

View File

@@ -8,7 +8,10 @@ use crate::misc::error::EmulatorError;
use crate::misc::result::EmulatorResult; use crate::misc::result::EmulatorResult;
pub const AUDIO_BUFFER_SIZE: usize = 256; pub const AUDIO_BUFFER_SIZE: usize = 256;
const I8_SIGN_BYTE: u8 = 0x80;
const AUDIO_REG_START: usize = 6;
const AUDIO_REG_LEN: usize = 2;
const AUDIO_REG_END: usize = AUDIO_REG_START + AUDIO_REG_LEN -1;
pub struct AudioProcessor<'a> { pub struct AudioProcessor<'a> {
ram: &'a RamMemory, ram: &'a RamMemory,
@@ -31,6 +34,7 @@ impl<'a> AudioProcessor<'a> {
} }
} }
impl<'a> AudioProcessor<'a> { impl<'a> AudioProcessor<'a> {
pub fn queue(&mut self) -> EmulatorResult<()> { pub fn queue(&mut self) -> EmulatorResult<()> {
let audio_base_reg = (self.get_audio_base_reg() as u32) << 8; let audio_base_reg = (self.get_audio_base_reg() as u32) << 8;
@@ -39,22 +43,22 @@ impl<'a> AudioProcessor<'a> {
// The CPU frame timing is just a little less than 60 fps to prevent audio stutter. // The CPU frame timing is just a little less than 60 fps to prevent audio stutter.
// We will then wait for audio to drain to adjust frame timing // We will then wait for audio to drain to adjust frame timing
if self.audio_queue.size() == 0 { if self.audio_queue.size() == 0 {
log::trace!("Detected Queue empty!"); log::info!("Detected Queue empty! Audio stutter may occur");
} }
while self.audio_queue.size() > 32 { while self.audio_queue.size() > (AUDIO_BUFFER_SIZE / 2) as u32 {
::std::thread::sleep(Duration::from_micros(1)) std::thread::sleep(Duration::from_micros(1))
} }
self.ram.try_copy_block(audio_base_reg, fb)?; self.ram.try_copy_block(audio_base_reg, fb)?;
//convert to u8 audio format (Bytepusher stores it as "i8") //convert to u8 audio format (BytePusher stores it as "i8")
fb.iter_mut().for_each(|item|{ fb.iter_mut().for_each(|item|{
*item^= 0x80; *item ^= I8_SIGN_BYTE;
}); });
self.audio_queue.queue_audio(fb).map_err(|s| { EmulatorError::OtherError(Some(AUDIO), s) }) self.audio_queue.queue_audio(fb).map_err(|s| { EmulatorError::OtherError(Some(AUDIO), s) })
} }
fn get_audio_base_reg(&self) -> u16 { fn get_audio_base_reg(&self) -> u16 {
let data = self.ram.get_data_ref(); let data = self.ram.get_data_ref();
let audio_base_reg = data.get(6..8).unwrap(); let audio_base_reg = data.get(AUDIO_REG_START..=AUDIO_REG_END).unwrap();
read_big_endian_u16(audio_base_reg.try_into().unwrap()) read_big_endian_u16(audio_base_reg.try_into().unwrap())
} }
} }

View File

@@ -40,7 +40,6 @@ impl<'a> Cpu<'a> {
log::trace!("Finished internal loop"); log::trace!("Finished internal loop");
self.graphics_processor.draw()?; self.graphics_processor.draw()?;
// TODO send audio
Ok(()) Ok(())
} }

View File

@@ -5,7 +5,8 @@ use crate::misc::error::DeviceType::GRAPHICS;
use crate::misc::error::EmulatorError; use crate::misc::error::EmulatorError;
use crate::misc::result::EmulatorResult; use crate::misc::result::EmulatorResult;
pub const DEVICE_FRAMEBUFFER_SIZE: usize = 256 * 256; pub const DEVICE_RESOLUTION: usize = 256;
pub const DEVICE_FRAMEBUFFER_SIZE: usize = DEVICE_RESOLUTION * DEVICE_RESOLUTION;
#[derive(Debug)] #[derive(Debug)]
pub struct GraphicsProcessor<'a> { pub struct GraphicsProcessor<'a> {

View File

@@ -19,12 +19,12 @@ impl<'a> Keyboard<'a>{
pub fn key_down(&mut self,x:u8){ pub fn key_down(&mut self,x:u8){
self.bitflags |= 1<<x; self.bitflags |= 1<<x;
log::debug!("Key Down, keyboard state {}",self.bitflags); log::trace!("Key Down - state {}",self.bitflags);
self.dirty = true self.dirty = true
} }
pub fn key_up(&mut self,x:u8){ pub fn key_up(&mut self,x:u8){
self.bitflags &= !((1<<x) as u16); self.bitflags &= !((1<<x) as u16);
log::debug!("Key Up, keyboard state {}",self.bitflags); log::debug!("Key Up - state {}",self.bitflags);
self.dirty = true self.dirty = true
} }
pub fn flush_keyboard(&mut self)->EmulatorResult<()>{ pub fn flush_keyboard(&mut self)->EmulatorResult<()>{

View File

@@ -53,6 +53,7 @@ impl RamMemory {
pub fn get_data_ref(&self) -> Ref<Box<[u8; MEM_LENGTH]>> { pub fn get_data_ref(&self) -> Ref<Box<[u8; MEM_LENGTH]>> {
self.data.borrow() self.data.borrow()
} }
} }
impl Memory for RamMemory { impl Memory for RamMemory {

View File

@@ -21,7 +21,7 @@ impl<'a> Debug for SDLGraphicsAdapter<'a> {
impl<'a> SDLGraphicsAdapter<'a> { impl<'a> SDLGraphicsAdapter<'a> {
pub fn new(graphics_processor: &'a GraphicsProcessor) -> SDLGraphicsAdapter<'a> { pub fn new(graphics_processor: &'a GraphicsProcessor) -> SDLGraphicsAdapter<'a> {
let color_fb_vec = vec![0u8; DEVICE_FRAMEBUFFER_SIZE * 3].into_boxed_slice().try_into().expect("???"); let color_fb_vec = vec![0u8; DEVICE_FRAMEBUFFER_SIZE * 3].into_boxed_slice().try_into().expect("Failed conversion");
SDLGraphicsAdapter { SDLGraphicsAdapter {
color_fb: color_fb_vec, color_fb: color_fb_vec,
graphics_processor, graphics_processor,
@@ -37,8 +37,8 @@ impl<'a> SDLGraphicsAdapter<'a> {
let mut texture = texture_creator.create_texture(PixelFormatEnum::RGB24, TextureAccess::Streaming, 256, 256).expect("Failed to make texture"); let mut texture = texture_creator.create_texture(PixelFormatEnum::RGB24, TextureAccess::Streaming, 256, 256).expect("Failed to make texture");
texture.with_lock(None, |f, _i| { texture.with_lock(None, |f, _i| {
f.copy_from_slice(self.color_fb.as_ref()) f.copy_from_slice(self.color_fb.as_ref())
}).expect("TODO: panic message"); })?;
canvas.copy(&texture, None, None).expect("Failed to write texture"); canvas.copy(&texture, None, None)?;
Ok(()) Ok(())
} }

View File

@@ -13,7 +13,7 @@ use simple_logger::SimpleLogger;
use crate::args::BytePusherArgs; use crate::args::BytePusherArgs;
use crate::emu::audio::AudioProcessor; use crate::emu::audio::AudioProcessor;
use crate::emu::cpu::Cpu; use crate::emu::cpu::Cpu;
use crate::emu::graphics::GraphicsProcessor; use crate::emu::graphics::{DEVICE_RESOLUTION, GraphicsProcessor};
use crate::emu::keyboard::Keyboard; use crate::emu::keyboard::Keyboard;
use crate::emu::memory::{MEM_LENGTH, RamMemory}; use crate::emu::memory::{MEM_LENGTH, RamMemory};
use crate::graphics::graphics_adapter::SDLGraphicsAdapter; use crate::graphics::graphics_adapter::SDLGraphicsAdapter;
@@ -26,14 +26,12 @@ mod misc;
mod graphics; mod graphics;
fn main() -> EmulatorResult<()> { fn main() -> EmulatorResult<()> {
let BytePusherArgs { file_name } = BytePusherArgs::parse(); let BytePusherArgs { file_name ,draw_scale} = BytePusherArgs::parse();
SimpleLogger::new().with_level(LevelFilter::Info).env().init().unwrap(); SimpleLogger::new().with_level(LevelFilter::Info).env().init()?;
let (file_bytes, ..) = try_load_rom(&file_name)?;
let (file_bytes, x) = try_load_rom(&file_name)?; let (mut canvas, mut event_pump, audio_queue) = initiate_sdl(draw_scale);
assert!(x < MEM_LENGTH);
let (mut canvas, mut event_pump, audio_queue) = initiate_sdl();
let ram = RamMemory::try_from(file_bytes.as_slice())?; let ram = RamMemory::try_from(file_bytes.as_slice())?;
@@ -44,9 +42,9 @@ fn main() -> EmulatorResult<()> {
let mut sdl2_graphics_adapter = SDLGraphicsAdapter::new(&graphics_processor); let mut sdl2_graphics_adapter = SDLGraphicsAdapter::new(&graphics_processor);
'running: loop {
canvas.set_draw_color(Color::BLACK); canvas.set_draw_color(Color::BLACK);
canvas.clear(); canvas.clear();
'running: loop {
for event in event_pump.poll_iter() { for event in event_pump.poll_iter() {
match event { match event {
Event::Quit { .. } | Event::Quit { .. } |
@@ -71,15 +69,14 @@ fn main() -> EmulatorResult<()> {
// The rest of the game loop goes here... // The rest of the game loop goes here...
cpu.cycle()?; cpu.cycle()?;
// draw graphics
sdl2_graphics_adapter.draw(&mut canvas)?;
// TODO render audio
audio_processor.queue()?;
sdl2_graphics_adapter.draw(&mut canvas)?;
audio_processor.queue()?;
canvas.present(); canvas.present();
// 60fps - small offset to consider for cpu cycle time // 60fps - small offset to consider for cpu cycle time
::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60 - 2000_000)); std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60 - 2000_000));
} }
Ok(()) Ok(())
@@ -121,10 +118,8 @@ fn try_load_rom(file_name_option: &Option<String>) -> EmulatorResult<(Vec<u8>, u
Ok((file_bytes, bytes_read)) Ok((file_bytes, bytes_read))
} }
fn initiate_sdl() -> (WindowCanvas, EventPump, AudioQueue<u8>) { fn initiate_sdl(draw_scale:f32) -> (WindowCanvas, EventPump, AudioQueue<u8>) {
const BASE_RESOLUTION: u32 = 256; let window_resolution: u32 = (DEVICE_RESOLUTION as f32 * draw_scale) as u32;
const DRAW_FACTOR: u32 = 4;
const WINDOW_RESOLUTION: u32 = BASE_RESOLUTION * DRAW_FACTOR;
let sdl_context = sdl2::init().unwrap(); let sdl_context = sdl2::init().unwrap();
let video_subsystem = sdl_context.video().unwrap(); let video_subsystem = sdl_context.video().unwrap();
let audio_subsystem = sdl_context.audio().unwrap(); let audio_subsystem = sdl_context.audio().unwrap();
@@ -137,18 +132,16 @@ fn initiate_sdl() -> (WindowCanvas, EventPump, AudioQueue<u8>) {
audio_queue.resume(); audio_queue.resume();
let window = video_subsystem.window("byte-pusher-emu", WINDOW_RESOLUTION, WINDOW_RESOLUTION) let window = video_subsystem.window("byte-pusher-emu", window_resolution, window_resolution)
.position_centered() .position_centered()
.build() .build()
.unwrap(); .unwrap();
let mut canvas = window.into_canvas().build().unwrap(); let mut canvas = window.into_canvas().build().unwrap();
canvas.set_draw_color(Color::RGB(0x10, 0x10, 0x10)); canvas.set_scale(draw_scale, draw_scale).expect("Setting scale");
canvas.set_integer_scale(true).expect("Setting int scale");
canvas.set_scale(DRAW_FACTOR as f32, DRAW_FACTOR as f32).expect("Setting scale");
canvas.clear();
canvas.set_blend_mode(BlendMode::None); canvas.set_blend_mode(BlendMode::None);
canvas.clear();
canvas.present(); canvas.present();
let event_pump = sdl_context.event_pump().unwrap(); let event_pump = sdl_context.event_pump().unwrap();
(canvas, event_pump, audio_queue) (canvas, event_pump, audio_queue)

View File

@@ -1,6 +1,7 @@
use std::array::TryFromSliceError; use std::array::TryFromSliceError;
use std::fmt::Debug; use std::fmt::Debug;
use std::io::Error; use std::io::Error;
use log::SetLoggerError;
use crate::misc::error::EmulatorError::EmulatorIOError; use crate::misc::error::EmulatorError::EmulatorIOError;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
@@ -24,12 +25,23 @@ impl From<TryFromSliceError> for EmulatorError {
} }
} }
impl From<std::io::Error> for EmulatorError { impl From<Error> for EmulatorError {
fn from(value: Error) -> Self { fn from(value: Error) -> Self {
EmulatorIOError(value) EmulatorIOError(value)
} }
} }
impl From<SetLoggerError> for EmulatorError{
fn from(value: SetLoggerError) -> Self {
EmulatorError::OtherError(None,format!("Logger allocation failed! Error: {}",value))
}
}
impl From<String> for EmulatorError{
fn from(value: String) -> Self {
EmulatorError::OtherError(None,value)
}
}