Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
2a15eb1122 | |||
537bcad73b | |||
101c513838 | |||
eb86e33117 | |||
f250eee2ea | |||
6b6b2211a8 | |||
6fefd600e1 | |||
aff5103636 | |||
b166e9c004 | |||
4b15253580 | |||
20c90af201 |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -58,7 +58,7 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "byte-pusher-emu"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"clap",
|
||||
|
@@ -1,7 +1,10 @@
|
||||
[package]
|
||||
name = "byte-pusher-emu"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
license = "BSD-3"
|
||||
authors = ["chrisvrose"]
|
||||
|
||||
|
||||
# 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]
|
||||
strip = true
|
||||
lto = true
|
||||
|
28
LICENSE
Normal file
28
LICENSE
Normal 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.
|
45
README.md
45
README.md
@@ -1,27 +1,32 @@
|
||||
# BytePusher Emulator
|
||||
|
||||
This is a play at emulating a BytePusher machine developed by [Javamannen](https://esolangs.org/wiki/User:Javamannen).
|
||||
Binary builds for linux x86_64 is available in GitHub Action runs.
|
||||
`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 the Releases section.
|
||||
|
||||
|
||||

|
||||
|
||||
<details>
|
||||
<summary>More screenshots</summary>
|
||||
|
||||
#### Palette Test
|
||||

|
||||
Screenshot program author: [Javamannen](https://esolangs.org/wiki/User:Javamannen)
|
||||
|
||||
## Status
|
||||
#### Keyboard test
|
||||

|
||||
|
||||
- [X] Memory
|
||||
- [X] Color
|
||||
- [X] CPU
|
||||
- [X] Inner loop - 65536 instructions
|
||||
- [X] Outer loop - 60fps display loop
|
||||
- [X] Display adapter
|
||||
- [X] SDL2 adapter
|
||||
- [X] Keyboard
|
||||
- [X] Audio
|
||||
- [X] Load a ROM
|
||||
</details>
|
||||
|
||||
The roms showcased here have been created by [Javamannen](https://esolangs.org/wiki/User:Javamannen).
|
||||
|
||||
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.
|
||||
|
||||
## 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
|
||||
./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
|
||||
|
||||
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
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
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 |
12
src/args.rs
12
src/args.rs
@@ -1,8 +1,10 @@
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Debug,Parser)]
|
||||
#[command(version, about, long_about = "Byte Pusher Emulator")]
|
||||
pub struct BytePusherArgs{
|
||||
#[arg(short,long)]
|
||||
pub file_name:Option<String>
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(version, about, author)]
|
||||
pub struct BytePusherArgs {
|
||||
#[arg(short, long, help = "ROM file to load")]
|
||||
pub file_name: Option<String>,
|
||||
#[arg(short, long, help = "Scale at which to draw", default_value_t = 2.0)]
|
||||
pub draw_scale: f32,
|
||||
}
|
@@ -8,7 +8,10 @@ use crate::misc::error::EmulatorError;
|
||||
use crate::misc::result::EmulatorResult;
|
||||
|
||||
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> {
|
||||
ram: &'a RamMemory,
|
||||
@@ -31,6 +34,7 @@ impl<'a> AudioProcessor<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<'a> AudioProcessor<'a> {
|
||||
pub fn queue(&mut self) -> EmulatorResult<()> {
|
||||
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.
|
||||
// We will then wait for audio to drain to adjust frame timing
|
||||
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 {
|
||||
::std::thread::sleep(Duration::from_micros(1))
|
||||
while self.audio_queue.size() > (AUDIO_BUFFER_SIZE / 2) as u32 {
|
||||
std::thread::sleep(Duration::from_micros(1))
|
||||
}
|
||||
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|{
|
||||
*item^= 0x80;
|
||||
*item ^= I8_SIGN_BYTE;
|
||||
});
|
||||
self.audio_queue.queue_audio(fb).map_err(|s| { EmulatorError::OtherError(Some(AUDIO), s) })
|
||||
}
|
||||
fn get_audio_base_reg(&self) -> u16 {
|
||||
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())
|
||||
}
|
||||
}
|
@@ -40,7 +40,6 @@ impl<'a> Cpu<'a> {
|
||||
|
||||
log::trace!("Finished internal loop");
|
||||
self.graphics_processor.draw()?;
|
||||
// TODO send audio
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@@ -5,7 +5,8 @@ use crate::misc::error::DeviceType::GRAPHICS;
|
||||
use crate::misc::error::EmulatorError;
|
||||
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)]
|
||||
pub struct GraphicsProcessor<'a> {
|
||||
|
@@ -19,12 +19,12 @@ impl<'a> Keyboard<'a>{
|
||||
|
||||
pub fn key_down(&mut self,x:u8){
|
||||
self.bitflags |= 1<<x;
|
||||
log::debug!("Key Down, keyboard state {}",self.bitflags);
|
||||
log::trace!("Key Down - state {}",self.bitflags);
|
||||
self.dirty = true
|
||||
}
|
||||
pub fn key_up(&mut self,x:u8){
|
||||
self.bitflags &= !((1<<x) as u16);
|
||||
log::debug!("Key Up, keyboard state {}",self.bitflags);
|
||||
log::debug!("Key Up - state {}",self.bitflags);
|
||||
self.dirty = true
|
||||
}
|
||||
pub fn flush_keyboard(&mut self)->EmulatorResult<()>{
|
||||
|
@@ -53,6 +53,7 @@ impl RamMemory {
|
||||
pub fn get_data_ref(&self) -> Ref<Box<[u8; MEM_LENGTH]>> {
|
||||
self.data.borrow()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Memory for RamMemory {
|
||||
|
@@ -21,7 +21,7 @@ impl<'a> Debug for SDLGraphicsAdapter<'a> {
|
||||
|
||||
impl<'a> 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 {
|
||||
color_fb: color_fb_vec,
|
||||
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");
|
||||
texture.with_lock(None, |f, _i| {
|
||||
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(())
|
||||
}
|
||||
|
37
src/main.rs
37
src/main.rs
@@ -13,7 +13,7 @@ use simple_logger::SimpleLogger;
|
||||
use crate::args::BytePusherArgs;
|
||||
use crate::emu::audio::AudioProcessor;
|
||||
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::memory::{MEM_LENGTH, RamMemory};
|
||||
use crate::graphics::graphics_adapter::SDLGraphicsAdapter;
|
||||
@@ -26,14 +26,12 @@ mod misc;
|
||||
mod graphics;
|
||||
|
||||
fn main() -> EmulatorResult<()> {
|
||||
let BytePusherArgs { file_name } = BytePusherArgs::parse();
|
||||
SimpleLogger::new().with_level(LevelFilter::Info).env().init().unwrap();
|
||||
let BytePusherArgs { file_name ,draw_scale} = BytePusherArgs::parse();
|
||||
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)?;
|
||||
assert!(x < MEM_LENGTH);
|
||||
|
||||
let (mut canvas, mut event_pump, audio_queue) = initiate_sdl();
|
||||
let (mut canvas, mut event_pump, audio_queue) = initiate_sdl(draw_scale);
|
||||
|
||||
|
||||
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);
|
||||
|
||||
'running: loop {
|
||||
canvas.set_draw_color(Color::BLACK);
|
||||
canvas.clear();
|
||||
'running: loop {
|
||||
for event in event_pump.poll_iter() {
|
||||
match event {
|
||||
Event::Quit { .. } |
|
||||
@@ -71,15 +69,14 @@ fn main() -> EmulatorResult<()> {
|
||||
|
||||
// The rest of the game loop goes here...
|
||||
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();
|
||||
|
||||
|
||||
// 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(())
|
||||
@@ -121,10 +118,8 @@ fn try_load_rom(file_name_option: &Option<String>) -> EmulatorResult<(Vec<u8>, u
|
||||
Ok((file_bytes, bytes_read))
|
||||
}
|
||||
|
||||
fn initiate_sdl() -> (WindowCanvas, EventPump, AudioQueue<u8>) {
|
||||
const BASE_RESOLUTION: u32 = 256;
|
||||
const DRAW_FACTOR: u32 = 4;
|
||||
const WINDOW_RESOLUTION: u32 = BASE_RESOLUTION * DRAW_FACTOR;
|
||||
fn initiate_sdl(draw_scale:f32) -> (WindowCanvas, EventPump, AudioQueue<u8>) {
|
||||
let window_resolution: u32 = (DEVICE_RESOLUTION as f32 * draw_scale) as u32;
|
||||
let sdl_context = sdl2::init().unwrap();
|
||||
let video_subsystem = sdl_context.video().unwrap();
|
||||
let audio_subsystem = sdl_context.audio().unwrap();
|
||||
@@ -137,18 +132,16 @@ fn initiate_sdl() -> (WindowCanvas, EventPump, AudioQueue<u8>) {
|
||||
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()
|
||||
.build()
|
||||
.unwrap();
|
||||
let mut canvas = window.into_canvas().build().unwrap();
|
||||
|
||||
canvas.set_draw_color(Color::RGB(0x10, 0x10, 0x10));
|
||||
canvas.set_integer_scale(true).expect("Setting int scale");
|
||||
canvas.set_scale(draw_scale, draw_scale).expect("Setting scale");
|
||||
|
||||
canvas.set_scale(DRAW_FACTOR as f32, DRAW_FACTOR as f32).expect("Setting scale");
|
||||
canvas.clear();
|
||||
canvas.set_blend_mode(BlendMode::None);
|
||||
canvas.clear();
|
||||
canvas.present();
|
||||
let event_pump = sdl_context.event_pump().unwrap();
|
||||
(canvas, event_pump, audio_queue)
|
||||
|
@@ -1,6 +1,7 @@
|
||||
use std::array::TryFromSliceError;
|
||||
use std::fmt::Debug;
|
||||
use std::io::Error;
|
||||
use log::SetLoggerError;
|
||||
use crate::misc::error::EmulatorError::EmulatorIOError;
|
||||
|
||||
#[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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user