REFACTOR:
- created dsa_formats crate for DSA file formats such as DSE executables (.dsb, will become .dse once relocations/linking are introduced) - moved IO devices from emulator_ui to emulator_core (keeping only the UI trait impls in emulator_ui) - other refactors within emulator_ui to make the project structure a bit more sane
This commit is contained in:
Generated
+1
@@ -14,6 +14,7 @@
|
|||||||
<sourceFolder url="file://$MODULE_DIR$/dsx/dsx_client/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/dsx/dsx_client/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/dsx/dsx_common/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/dsx/dsx_common/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/dsx/dsx_server/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/dsx/dsx_server/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/dsa/dsa_formats/src" isTestSource="false" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.idea/caches" />
|
<excludeFolder url="file://$MODULE_DIR$/.idea/caches" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/doc/.obsidian" />
|
<excludeFolder url="file://$MODULE_DIR$/doc/.obsidian" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
|
|||||||
+17
-1
@@ -1,8 +1,24 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["dsa/common", "dsa/assembler", "dsa/emulator/emulator_ui", "dsa/linker", "dsa/compiler", "dsa/emulator/emulator_core", "dsx/dsx_common", "dsx/dsx_server", "dsx/dsx_client", "dsa/disassembler"]
|
members = ["dsa/common", "dsa/assembler", "dsa/emulator/emulator_ui", "dsa/linker", "dsa/compiler", "dsa/emulator/emulator_core", "dsx/dsx_common", "dsx/dsx_server", "dsx/dsx_client", "dsa/disassembler", "dsa/dsa_formats"]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
authors = ["zxq5", "zxq4"]
|
authors = ["zxq5", "zxq4"]
|
||||||
|
|
||||||
|
#[workspace.lints.clippy]
|
||||||
|
#all = "warn"
|
||||||
|
#pedantic = "warn"
|
||||||
|
#cast_possible_truncation = "warn"
|
||||||
|
#cast_sign_loss = "warn"
|
||||||
|
#cast_possible_wrap = "warn"
|
||||||
|
#as_conversions = "warn"
|
||||||
|
#unwrap_used = "warn"
|
||||||
|
#expect_used = "warn"
|
||||||
|
#indexing_slicing = "warn"
|
||||||
|
#large_enum_variant = "warn"
|
||||||
|
#mem_replace_with_default = "warn"
|
||||||
|
#mutex_atomic = "warn"
|
||||||
|
#todo = "warn"
|
||||||
|
#dbg_macro = "warn"
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ name = "assembler"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
binrw = "0.15.1"
|
|
||||||
clap = { version = "4.6.0", features = ["derive"] }
|
clap = { version = "4.6.0", features = ["derive"] }
|
||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
|
dsa_formats = { path = "../dsa_formats" }
|
||||||
|
|
||||||
num_cpus = "1.17.0"
|
num_cpus = "1.17.0"
|
||||||
strum = { version = "0.28.0", features = ["derive"] }
|
strum = { version = "0.28.0", features = ["derive"] }
|
||||||
|
|||||||
@@ -1,22 +1,16 @@
|
|||||||
#![allow(dead_code, unused)]
|
#![allow(dead_code, unused)]
|
||||||
|
|
||||||
|
use crate::assembler::resolver2::{SectionId, resolve_address, resolve_dependencies, resolve_symbols, split_sections};
|
||||||
|
use common::process::{ProcessAgent, TraceableProcess};
|
||||||
|
use dsa_formats::binary_dse::{DseExecutable, SymbolEntry};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
fmt, fs,
|
fmt, fs,
|
||||||
hash::{DefaultHasher, Hash, Hasher},
|
hash::{DefaultHasher, Hash, Hasher},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{
|
sync::
|
||||||
Arc, Mutex,
|
mpsc::{self},
|
||||||
mpsc::{self, Receiver, Sender},
|
|
||||||
},
|
|
||||||
thread,
|
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::io::Cursor;
|
|
||||||
use common::formats::binary_dse::{DseExecutable, SymbolEntry};
|
|
||||||
use common::prelude::Instruction;
|
|
||||||
use common::process::{ProcessAgent, TraceableProcess};
|
|
||||||
use crate::assembler::resolver2::{resolve_address, resolve_dependencies, resolve_symbols, split_sections, SectionId};
|
|
||||||
|
|
||||||
// Module declarations
|
// Module declarations
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
pub mod binary_dse;
|
|
||||||
pub mod binary_dso;
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
pub mod asm;
|
pub mod asm;
|
||||||
pub mod isa;
|
pub mod isa;
|
||||||
pub mod formats;
|
|
||||||
pub mod process;
|
pub mod process;
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ path = "src/lib.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.6.0", features = ["derive"] }
|
clap = { version = "4.6.0", features = ["derive"] }
|
||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
|
dsa_formats = { path = "../dsa_formats" }
|
||||||
@@ -1,350 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt;
|
|
||||||
use common::formats::binary_dse::DseExecutable;
|
|
||||||
use common::isa::instructions::Opcode;
|
|
||||||
use common::prelude::{Instruction, Register};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Disassembly {
|
|
||||||
pub symbols: Vec<SymbolLine>,
|
|
||||||
pub text_lines: Vec<String>,
|
|
||||||
pub data_lines: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct SymbolLine {
|
|
||||||
pub name: String,
|
|
||||||
pub address: u32,
|
|
||||||
pub section: SectionKind,
|
|
||||||
pub binding: BindingKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum SectionKind { Data, Text, Abs, Undef }
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum BindingKind { Local, Global, Weak }
|
|
||||||
|
|
||||||
impl SectionKind {
|
|
||||||
fn from_raw(v: u8) -> Self {
|
|
||||||
match v { 1 => Self::Data, 2 => Self::Text, 0 => Self::Abs, _ => Self::Undef }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl BindingKind {
|
|
||||||
fn from_raw(v: u8) -> Self {
|
|
||||||
match v { 1 => Self::Global, 2 => Self::Weak, _ => Self::Local }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disassemble(exe: &DseExecutable) -> Disassembly {
|
|
||||||
let symbols = build_symbol_lines(exe);
|
|
||||||
|
|
||||||
let mut text_labels: HashMap<u32, String> = HashMap::new();
|
|
||||||
let mut data_labels: HashMap<u32, String> = HashMap::new();
|
|
||||||
for sym in &symbols {
|
|
||||||
match sym.section {
|
|
||||||
SectionKind::Text => { text_labels.insert(sym.address, sym.name.clone()); }
|
|
||||||
SectionKind::Data => { data_labels.insert(sym.address, sym.name.clone()); }
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// symbols can refer to either section by full resolved address (per your
|
|
||||||
// v1 loader convention: data lives right after text), so a single combined
|
|
||||||
// map is actually what address-resolution needs — keep both, but merge
|
|
||||||
// for lookups against full 32-bit addresses.
|
|
||||||
let mut all_labels = text_labels.clone();
|
|
||||||
all_labels.extend(data_labels.clone());
|
|
||||||
|
|
||||||
let text_lines = build_text_lines(&exe.text, &all_labels);
|
|
||||||
let data_lines = build_data_lines(&exe.data, &data_labels);
|
|
||||||
|
|
||||||
Disassembly { symbols, text_lines, data_lines }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_symbol_lines(exe: &DseExecutable) -> Vec<SymbolLine> {
|
|
||||||
exe.symbols
|
|
||||||
.iter()
|
|
||||||
.map(|s| SymbolLine {
|
|
||||||
name: read_string(&exe.string_table, s.name_offset),
|
|
||||||
address: s.value,
|
|
||||||
section: SectionKind::from_raw(s.section),
|
|
||||||
binding: BindingKind::from_raw(s.binding),
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_string(strtab: &[u8], offset: u32) -> String {
|
|
||||||
let start = offset as usize;
|
|
||||||
let end = strtab[start..].iter().position(|&b| b == 0).map(|p| start + p).unwrap_or(strtab.len());
|
|
||||||
String::from_utf8_lossy(&strtab[start..end]).into_owned()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Address a jump-like instruction actually targets, if statically known.
|
|
||||||
/// Returns None when the jump is register-relative (addr reg != Zero) since
|
|
||||||
/// that depends on runtime state and can't be resolved here.
|
|
||||||
fn static_jump_target(addr_reg: Register, imm: u16) -> Option<u32> {
|
|
||||||
(addr_reg == Register::Zero).then_some(imm as u32)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_text_lines(words: &[u32], labels: &HashMap<u32, String>) -> Vec<String> {
|
|
||||||
let mut out = Vec::new();
|
|
||||||
let mut i = 0;
|
|
||||||
|
|
||||||
while i < words.len() {
|
|
||||||
let addr = 4 * i as u32;
|
|
||||||
if let Some(label) = labels.get(&addr) {
|
|
||||||
out.push(format!("{label}:"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let instr = Instruction(words[i]);
|
|
||||||
let opcode = Opcode::from_u8(instr.opcode());
|
|
||||||
|
|
||||||
// Collapse Lli+Lui address-load pairs back into `lwi` (or a full
|
|
||||||
// ldx/stx pseudo-op if a matching mem instruction follows), mirroring
|
|
||||||
// what expand_ldx/expand_stx/expand_lwi originally expanded from.
|
|
||||||
if let Some(Opcode::Lli) = opcode {
|
|
||||||
if let Some(collapsed) = try_collapse_addr_load(words, i, labels) {
|
|
||||||
out.push(format!(" {}", collapsed.text));
|
|
||||||
i += collapsed.consumed;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out.push(format!(" {}", render_instruction(instr, opcode, labels)));
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Collapsed {
|
|
||||||
text: String,
|
|
||||||
consumed: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_collapse_addr_load(
|
|
||||||
words: &[u32],
|
|
||||||
i: usize,
|
|
||||||
labels: &HashMap<u32, String>,
|
|
||||||
) -> Option<Collapsed> {
|
|
||||||
let lli = Instruction(words[i]);
|
|
||||||
let lui = Instruction(*words.get(i + 1)?);
|
|
||||||
|
|
||||||
if Opcode::from_u8(lui.opcode())? as u8 != Opcode::Lui as u8 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if lui.dest_checked() != lli.dest_checked() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let dest = lli.dest_checked();
|
|
||||||
let full_addr = (lli.imm16() as u32) | ((lui.imm16() as u32) << 16);
|
|
||||||
let addr_text = labels.get(&full_addr).cloned().unwrap_or_else(|| format!("{full_addr:#010x}"));
|
|
||||||
|
|
||||||
// Check whether a mem op immediately follows, referencing `dest` —
|
|
||||||
// that's the ldb/ldh/ldw/stb/sth/stw expansion pattern, not a bare lwi.
|
|
||||||
if let Some(&mem_word) = words.get(i + 2) {
|
|
||||||
let mem = Instruction(mem_word);
|
|
||||||
if let Some(mem_op) = Opcode::from_u8(mem.opcode()) {
|
|
||||||
let is_mem_op = matches!(
|
|
||||||
mem_op,
|
|
||||||
Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw
|
|
||||||
| Opcode::Stb | Opcode::Sth | Opcode::Stw
|
|
||||||
);
|
|
||||||
if is_mem_op && (mem.src1_checked() == dest || mem.dest_checked() == dest) {
|
|
||||||
let mnemonic = format!("{mem_op:?}").to_lowercase();
|
|
||||||
let other = if mem.src1_checked() == dest { mem.dest_checked() } else { mem.src1_checked() };
|
|
||||||
return Some(Collapsed {
|
|
||||||
text: format!("{mnemonic} {addr_text}, {other:?}, {:#06x}", mem.imm16()),
|
|
||||||
consumed: 3,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Collapsed {
|
|
||||||
text: format!("lwi {addr_text}, {dest:?}"),
|
|
||||||
consumed: 2,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_instruction(instr: Instruction, opcode: Option<Opcode>, labels: &HashMap<u32, String>) -> String {
|
|
||||||
let Some(op) = opcode else {
|
|
||||||
return format!("; INVALID OPCODE {:#04x} (word {:#010x})", instr.opcode(), instr.0);
|
|
||||||
};
|
|
||||||
|
|
||||||
let mnemonic = format!("{op:?}").to_lowercase();
|
|
||||||
|
|
||||||
match op {
|
|
||||||
Opcode::Nop | Opcode::Hlt | Opcode::Ret | Opcode::IRet => mnemonic,
|
|
||||||
|
|
||||||
Opcode::Push => format!("{mnemonic} {:?}", instr.src1_checked()),
|
|
||||||
Opcode::Pop => format!("{mnemonic} {:?}", instr.dest_checked()),
|
|
||||||
|
|
||||||
Opcode::Lli | Opcode::Lui => {
|
|
||||||
format!("{mnemonic} {:?}, {:#06x}", instr.dest_checked(), instr.imm16())
|
|
||||||
}
|
|
||||||
|
|
||||||
Opcode::Int => format!("{mnemonic} {:#06x}", instr.imm16()),
|
|
||||||
|
|
||||||
Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw
|
|
||||||
| Opcode::Stb | Opcode::Sth | Opcode::Stw | Opcode::Addi | Opcode::Subi => format!(
|
|
||||||
"{mnemonic} {:?}, {:?}, {:#06x}",
|
|
||||||
instr.src1_checked(), instr.dest_checked(), instr.imm16()
|
|
||||||
),
|
|
||||||
|
|
||||||
Opcode::Jez | Opcode::Jnz => {
|
|
||||||
let addr_reg = instr.dest_checked();
|
|
||||||
let target = static_jump_target(addr_reg, instr.imm16())
|
|
||||||
.and_then(|a| labels.get(&a).cloned());
|
|
||||||
match target {
|
|
||||||
Some(label) => format!("{mnemonic} {:?}, {label}", instr.src1_checked()),
|
|
||||||
None if addr_reg == Register::Zero => {
|
|
||||||
format!("{mnemonic} {:?}, {:#06x}", instr.src1_checked(), instr.imm16())
|
|
||||||
}
|
|
||||||
None => format!(
|
|
||||||
"{mnemonic} {:?}, {:#06x}, {addr_reg:?}",
|
|
||||||
instr.src1_checked(), instr.imm16()
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Opcode::Jmp | Opcode::Call | Opcode::Jic | Opcode::Jnc => {
|
|
||||||
let addr_reg = instr.dest_checked();
|
|
||||||
let target = static_jump_target(addr_reg, instr.imm16())
|
|
||||||
.and_then(|a| labels.get(&a).cloned());
|
|
||||||
match target {
|
|
||||||
Some(label) => format!("{mnemonic} {label}"),
|
|
||||||
None if addr_reg == Register::Zero => format!("{mnemonic} {:#06x}", instr.imm16()),
|
|
||||||
None => format!("{mnemonic} {:#06x}, {addr_reg:?}", instr.imm16()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Opcode::Mov | Opcode::Not => {
|
|
||||||
format!("{mnemonic} {:?}, {:?}", instr.src1_checked(), instr.dest_checked())
|
|
||||||
}
|
|
||||||
|
|
||||||
Opcode::CMov => format!(
|
|
||||||
"{mnemonic} {:?}, {:?}, {:?}",
|
|
||||||
instr.src1_checked(), instr.dest_checked(), instr.src2_checked()
|
|
||||||
),
|
|
||||||
|
|
||||||
Opcode::Add | Opcode::Sub | Opcode::And | Opcode::Nand | Opcode::Or | Opcode::Nor
|
|
||||||
| Opcode::Xor | Opcode::Xnor | Opcode::Ieq | Opcode::Ine | Opcode::Ilt | Opcode::Ile
|
|
||||||
| Opcode::Igt | Opcode::Ige => format!(
|
|
||||||
"{mnemonic} {:?}, {:?}, {:?}",
|
|
||||||
instr.src1_checked(), instr.src2_checked(), instr.dest_checked()
|
|
||||||
),
|
|
||||||
|
|
||||||
Opcode::Shl | Opcode::Shr => format!(
|
|
||||||
"{mnemonic} {:?}, {:?}, {:?}, {}",
|
|
||||||
instr.src1_checked(), instr.dest_checked(), instr.src2_checked(), instr.shamt()
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_data_lines(data: &[u8], data_labels: &HashMap<u32, String>) -> Vec<String> {
|
|
||||||
let mut out = Vec::new();
|
|
||||||
let mut i = 0;
|
|
||||||
|
|
||||||
while i < data.len() {
|
|
||||||
let addr = i as u32;
|
|
||||||
if let Some(label) = data_labels.get(&addr) {
|
|
||||||
out.push(format!("{label}:"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try string detection only when this byte could actually start one.
|
|
||||||
if data[i] != 0 {
|
|
||||||
if let Some(end) = data[i..].iter().position(|&b| b == 0) {
|
|
||||||
let slice = &data[i..i + end];
|
|
||||||
if slice.iter().all(|&b| b.is_ascii_graphic() || b == b' ') {
|
|
||||||
out.push(format!(" db \"{}\"", String::from_utf8_lossy(slice).escape_default()));
|
|
||||||
i += end + 1; // skip the null terminator too
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not a string start: consume only the run of zero bytes here,
|
|
||||||
// grouped into 4-byte words, stopping the instant a non-zero byte
|
|
||||||
// appears so we never swallow the start of the next string/value.
|
|
||||||
let run_end = data[i..]
|
|
||||||
.iter()
|
|
||||||
.position(|&b| b != 0)
|
|
||||||
.map(|p| i + p)
|
|
||||||
.unwrap_or(data.len());
|
|
||||||
|
|
||||||
if run_end == i {
|
|
||||||
// data[i] is non-zero but didn't form a printable/terminated
|
|
||||||
// string (e.g. binary constant) — dump one raw word.
|
|
||||||
let mut buf = [0u8; 4];
|
|
||||||
let take = (data.len() - i).min(4);
|
|
||||||
buf[..take].copy_from_slice(&data[i..i + take]);
|
|
||||||
out.push(format!(" dw {:#010x}", u32::from_le_bytes(buf)));
|
|
||||||
i += take;
|
|
||||||
} else {
|
|
||||||
let mut j = i;
|
|
||||||
while j < run_end {
|
|
||||||
let take = (run_end - j).min(4);
|
|
||||||
let mut buf = [0u8; 4];
|
|
||||||
buf[..take].copy_from_slice(&data[j..j + take]);
|
|
||||||
out.push(format!(" dw {:#010x}", u32::from_le_bytes(buf)));
|
|
||||||
j += take;
|
|
||||||
}
|
|
||||||
i = j;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for SectionKind {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Data => write!(f, "DATA"),
|
|
||||||
Self::Text => write!(f, "TEXT"),
|
|
||||||
Self::Abs => write!(f, "ABS"),
|
|
||||||
Self::Undef => write!(f, "UNDEF"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for BindingKind {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Local => write!(f, "local"),
|
|
||||||
Self::Global => write!(f, "global"),
|
|
||||||
Self::Weak => write!(f, "weak"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for SymbolLine {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"; {:<24} {:#010x} {:<5} {}",
|
|
||||||
self.name, self.address, self.section, self.binding
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Disassembly {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
writeln!(f, "; ==== symbols ====")?;
|
|
||||||
for sym in &self.symbols {
|
|
||||||
writeln!(f, "{sym}")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeln!(f, "\n; ==== data ====")?;
|
|
||||||
for line in &self.data_lines {
|
|
||||||
writeln!(f, "{line}")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeln!(f, "\n; ==== text ====")?;
|
|
||||||
for line in &self.text_lines {
|
|
||||||
writeln!(f, "{line}")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+348
-13
@@ -1,15 +1,350 @@
|
|||||||
mod internal;
|
use common::isa::instructions::Opcode;
|
||||||
|
use common::prelude::{Instruction, Register};
|
||||||
use std::fs;
|
use dsa_formats::binary_dse::DseExecutable;
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use common::formats::binary_dse::DseExecutable;
|
use std::fmt;
|
||||||
use common::isa::instructions::{Instruction, Opcode};
|
|
||||||
use common::isa::register::Register;
|
|
||||||
|
|
||||||
pub use internal::{
|
#[derive(Debug)]
|
||||||
disassemble, Disassembly, BindingKind, SectionKind, SymbolLine
|
pub struct Disassembly {
|
||||||
};
|
pub symbols: Vec<SymbolLine>,
|
||||||
|
pub text_lines: Vec<String>,
|
||||||
|
pub data_lines: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SymbolLine {
|
||||||
|
pub name: String,
|
||||||
|
pub address: u32,
|
||||||
|
pub section: SectionKind,
|
||||||
|
pub binding: BindingKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum SectionKind { Data, Text, Abs, Undef }
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum BindingKind { Local, Global, Weak }
|
||||||
|
|
||||||
|
impl SectionKind {
|
||||||
|
fn from_raw(v: u8) -> Self {
|
||||||
|
match v { 1 => Self::Data, 2 => Self::Text, 0 => Self::Abs, _ => Self::Undef }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl BindingKind {
|
||||||
|
fn from_raw(v: u8) -> Self {
|
||||||
|
match v { 1 => Self::Global, 2 => Self::Weak, _ => Self::Local }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disassemble(exe: &DseExecutable) -> Disassembly {
|
||||||
|
let symbols = build_symbol_lines(exe);
|
||||||
|
|
||||||
|
let mut text_labels: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut data_labels: HashMap<u32, String> = HashMap::new();
|
||||||
|
for sym in &symbols {
|
||||||
|
match sym.section {
|
||||||
|
SectionKind::Text => { text_labels.insert(sym.address, sym.name.clone()); }
|
||||||
|
SectionKind::Data => { data_labels.insert(sym.address, sym.name.clone()); }
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// symbols can refer to either section by full resolved address (per your
|
||||||
|
// v1 loader convention: data lives right after text), so a single combined
|
||||||
|
// map is actually what address-resolution needs — keep both, but merge
|
||||||
|
// for lookups against full 32-bit addresses.
|
||||||
|
let mut all_labels = text_labels.clone();
|
||||||
|
all_labels.extend(data_labels.clone());
|
||||||
|
|
||||||
|
let text_lines = build_text_lines(&exe.text, &all_labels);
|
||||||
|
let data_lines = build_data_lines(&exe.data, &data_labels);
|
||||||
|
|
||||||
|
Disassembly { symbols, text_lines, data_lines }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_symbol_lines(exe: &DseExecutable) -> Vec<SymbolLine> {
|
||||||
|
exe.symbols
|
||||||
|
.iter()
|
||||||
|
.map(|s| SymbolLine {
|
||||||
|
name: read_string(&exe.string_table, s.name_offset),
|
||||||
|
address: s.value,
|
||||||
|
section: SectionKind::from_raw(s.section),
|
||||||
|
binding: BindingKind::from_raw(s.binding),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_string(strtab: &[u8], offset: u32) -> String {
|
||||||
|
let start = offset as usize;
|
||||||
|
let end = strtab[start..].iter().position(|&b| b == 0).map(|p| start + p).unwrap_or(strtab.len());
|
||||||
|
String::from_utf8_lossy(&strtab[start..end]).into_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Address a jump-like instruction actually targets, if statically known.
|
||||||
|
/// Returns None when the jump is register-relative (addr reg != Zero) since
|
||||||
|
/// that depends on runtime state and can't be resolved here.
|
||||||
|
fn static_jump_target(addr_reg: Register, imm: u16) -> Option<u32> {
|
||||||
|
(addr_reg == Register::Zero).then_some(imm as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_text_lines(words: &[u32], labels: &HashMap<u32, String>) -> Vec<String> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
while i < words.len() {
|
||||||
|
let addr = 4 * i as u32;
|
||||||
|
if let Some(label) = labels.get(&addr) {
|
||||||
|
out.push(format!("{label}:"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let instr = Instruction(words[i]);
|
||||||
|
let opcode = Opcode::from_u8(instr.opcode());
|
||||||
|
|
||||||
|
// Collapse Lli+Lui address-load pairs back into `lwi` (or a full
|
||||||
|
// ldx/stx pseudo-op if a matching mem instruction follows), mirroring
|
||||||
|
// what expand_ldx/expand_stx/expand_lwi originally expanded from.
|
||||||
|
if let Some(Opcode::Lli) = opcode {
|
||||||
|
if let Some(collapsed) = try_collapse_addr_load(words, i, labels) {
|
||||||
|
out.push(format!(" {}", collapsed.text));
|
||||||
|
i += collapsed.consumed;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push(format!(" {}", render_instruction(instr, opcode, labels)));
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Collapsed {
|
||||||
|
text: String,
|
||||||
|
consumed: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_collapse_addr_load(
|
||||||
|
words: &[u32],
|
||||||
|
i: usize,
|
||||||
|
labels: &HashMap<u32, String>,
|
||||||
|
) -> Option<Collapsed> {
|
||||||
|
let lli = Instruction(words[i]);
|
||||||
|
let lui = Instruction(*words.get(i + 1)?);
|
||||||
|
|
||||||
|
if Opcode::from_u8(lui.opcode())? as u8 != Opcode::Lui as u8 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if lui.dest_checked() != lli.dest_checked() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dest = lli.dest_checked();
|
||||||
|
let full_addr = (lli.imm16() as u32) | ((lui.imm16() as u32) << 16);
|
||||||
|
let addr_text = labels.get(&full_addr).cloned().unwrap_or_else(|| format!("{full_addr:#010x}"));
|
||||||
|
|
||||||
|
// Check whether a mem op immediately follows, referencing `dest` —
|
||||||
|
// that's the ldb/ldh/ldw/stb/sth/stw expansion pattern, not a bare lwi.
|
||||||
|
if let Some(&mem_word) = words.get(i + 2) {
|
||||||
|
let mem = Instruction(mem_word);
|
||||||
|
if let Some(mem_op) = Opcode::from_u8(mem.opcode()) {
|
||||||
|
let is_mem_op = matches!(
|
||||||
|
mem_op,
|
||||||
|
Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw
|
||||||
|
| Opcode::Stb | Opcode::Sth | Opcode::Stw
|
||||||
|
);
|
||||||
|
if is_mem_op && (mem.src1_checked() == dest || mem.dest_checked() == dest) {
|
||||||
|
let mnemonic = format!("{mem_op:?}").to_lowercase();
|
||||||
|
let other = if mem.src1_checked() == dest { mem.dest_checked() } else { mem.src1_checked() };
|
||||||
|
return Some(Collapsed {
|
||||||
|
text: format!("{mnemonic} {addr_text}, {other:?}, {:#06x}", mem.imm16()),
|
||||||
|
consumed: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Collapsed {
|
||||||
|
text: format!("lwi {addr_text}, {dest:?}"),
|
||||||
|
consumed: 2,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_instruction(instr: Instruction, opcode: Option<Opcode>, labels: &HashMap<u32, String>) -> String {
|
||||||
|
let Some(op) = opcode else {
|
||||||
|
return format!("; INVALID OPCODE {:#04x} (word {:#010x})", instr.opcode(), instr.0);
|
||||||
|
};
|
||||||
|
|
||||||
|
let mnemonic = format!("{op:?}").to_lowercase();
|
||||||
|
|
||||||
|
match op {
|
||||||
|
Opcode::Nop | Opcode::Hlt | Opcode::Ret | Opcode::IRet => mnemonic,
|
||||||
|
|
||||||
|
Opcode::Push => format!("{mnemonic} {:?}", instr.src1_checked()),
|
||||||
|
Opcode::Pop => format!("{mnemonic} {:?}", instr.dest_checked()),
|
||||||
|
|
||||||
|
Opcode::Lli | Opcode::Lui => {
|
||||||
|
format!("{mnemonic} {:?}, {:#06x}", instr.dest_checked(), instr.imm16())
|
||||||
|
}
|
||||||
|
|
||||||
|
Opcode::Int => format!("{mnemonic} {:#06x}", instr.imm16()),
|
||||||
|
|
||||||
|
Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw
|
||||||
|
| Opcode::Stb | Opcode::Sth | Opcode::Stw | Opcode::Addi | Opcode::Subi => format!(
|
||||||
|
"{mnemonic} {:?}, {:?}, {:#06x}",
|
||||||
|
instr.src1_checked(), instr.dest_checked(), instr.imm16()
|
||||||
|
),
|
||||||
|
|
||||||
|
Opcode::Jez | Opcode::Jnz => {
|
||||||
|
let addr_reg = instr.dest_checked();
|
||||||
|
let target = static_jump_target(addr_reg, instr.imm16())
|
||||||
|
.and_then(|a| labels.get(&a).cloned());
|
||||||
|
match target {
|
||||||
|
Some(label) => format!("{mnemonic} {:?}, {label}", instr.src1_checked()),
|
||||||
|
None if addr_reg == Register::Zero => {
|
||||||
|
format!("{mnemonic} {:?}, {:#06x}", instr.src1_checked(), instr.imm16())
|
||||||
|
}
|
||||||
|
None => format!(
|
||||||
|
"{mnemonic} {:?}, {:#06x}, {addr_reg:?}",
|
||||||
|
instr.src1_checked(), instr.imm16()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Opcode::Jmp | Opcode::Call | Opcode::Jic | Opcode::Jnc => {
|
||||||
|
let addr_reg = instr.dest_checked();
|
||||||
|
let target = static_jump_target(addr_reg, instr.imm16())
|
||||||
|
.and_then(|a| labels.get(&a).cloned());
|
||||||
|
match target {
|
||||||
|
Some(label) => format!("{mnemonic} {label}"),
|
||||||
|
None if addr_reg == Register::Zero => format!("{mnemonic} {:#06x}", instr.imm16()),
|
||||||
|
None => format!("{mnemonic} {:#06x}, {addr_reg:?}", instr.imm16()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Opcode::Mov | Opcode::Not => {
|
||||||
|
format!("{mnemonic} {:?}, {:?}", instr.src1_checked(), instr.dest_checked())
|
||||||
|
}
|
||||||
|
|
||||||
|
Opcode::CMov => format!(
|
||||||
|
"{mnemonic} {:?}, {:?}, {:?}",
|
||||||
|
instr.src1_checked(), instr.dest_checked(), instr.src2_checked()
|
||||||
|
),
|
||||||
|
|
||||||
|
Opcode::Add | Opcode::Sub | Opcode::And | Opcode::Nand | Opcode::Or | Opcode::Nor
|
||||||
|
| Opcode::Xor | Opcode::Xnor | Opcode::Ieq | Opcode::Ine | Opcode::Ilt | Opcode::Ile
|
||||||
|
| Opcode::Igt | Opcode::Ige => format!(
|
||||||
|
"{mnemonic} {:?}, {:?}, {:?}",
|
||||||
|
instr.src1_checked(), instr.src2_checked(), instr.dest_checked()
|
||||||
|
),
|
||||||
|
|
||||||
|
Opcode::Shl | Opcode::Shr => format!(
|
||||||
|
"{mnemonic} {:?}, {:?}, {:?}, {}",
|
||||||
|
instr.src1_checked(), instr.dest_checked(), instr.src2_checked(), instr.shamt()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_data_lines(data: &[u8], data_labels: &HashMap<u32, String>) -> Vec<String> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
while i < data.len() {
|
||||||
|
let addr = i as u32;
|
||||||
|
if let Some(label) = data_labels.get(&addr) {
|
||||||
|
out.push(format!("{label}:"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try string detection only when this byte could actually start one.
|
||||||
|
if data[i] != 0 {
|
||||||
|
if let Some(end) = data[i..].iter().position(|&b| b == 0) {
|
||||||
|
let slice = &data[i..i + end];
|
||||||
|
if slice.iter().all(|&b| b.is_ascii_graphic() || b == b' ') {
|
||||||
|
out.push(format!(" db \"{}\"", String::from_utf8_lossy(slice).escape_default()));
|
||||||
|
i += end + 1; // skip the null terminator too
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a string start: consume only the run of zero bytes here,
|
||||||
|
// grouped into 4-byte words, stopping the instant a non-zero byte
|
||||||
|
// appears so we never swallow the start of the next string/value.
|
||||||
|
let run_end = data[i..]
|
||||||
|
.iter()
|
||||||
|
.position(|&b| b != 0)
|
||||||
|
.map(|p| i + p)
|
||||||
|
.unwrap_or(data.len());
|
||||||
|
|
||||||
|
if run_end == i {
|
||||||
|
// data[i] is non-zero but didn't form a printable/terminated
|
||||||
|
// string (e.g. binary constant) — dump one raw word.
|
||||||
|
let mut buf = [0u8; 4];
|
||||||
|
let take = (data.len() - i).min(4);
|
||||||
|
buf[..take].copy_from_slice(&data[i..i + take]);
|
||||||
|
out.push(format!(" dw {:#010x}", u32::from_le_bytes(buf)));
|
||||||
|
i += take;
|
||||||
|
} else {
|
||||||
|
let mut j = i;
|
||||||
|
while j < run_end {
|
||||||
|
let take = (run_end - j).min(4);
|
||||||
|
let mut buf = [0u8; 4];
|
||||||
|
buf[..take].copy_from_slice(&data[j..j + take]);
|
||||||
|
out.push(format!(" dw {:#010x}", u32::from_le_bytes(buf)));
|
||||||
|
j += take;
|
||||||
|
}
|
||||||
|
i = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for SectionKind {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Data => write!(f, "DATA"),
|
||||||
|
Self::Text => write!(f, "TEXT"),
|
||||||
|
Self::Abs => write!(f, "ABS"),
|
||||||
|
Self::Undef => write!(f, "UNDEF"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for BindingKind {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Local => write!(f, "local"),
|
||||||
|
Self::Global => write!(f, "global"),
|
||||||
|
Self::Weak => write!(f, "weak"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for SymbolLine {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"; {:<24} {:#010x} {:<5} {}",
|
||||||
|
self.name, self.address, self.section, self.binding
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Disassembly {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
writeln!(f, "; ==== symbols ====")?;
|
||||||
|
for sym in &self.symbols {
|
||||||
|
writeln!(f, "{sym}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeln!(f, "\n; ==== data ====")?;
|
||||||
|
for line in &self.data_lines {
|
||||||
|
writeln!(f, "{line}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeln!(f, "\n; ==== text ====")?;
|
||||||
|
for line in &self.text_lines {
|
||||||
|
writeln!(f, "{line}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
use std::fs::File;
|
use std::fs;
|
||||||
use std::io::Write;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use disassembler::disassemble;
|
use disassembler::disassemble;
|
||||||
use common::formats::binary_dse::DseExecutable;
|
use dsa_formats::binary_dse::DseExecutable;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{Cursor, Write};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[derive(Parser, Debug, Clone)]
|
#[derive(Parser, Debug, Clone)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
@@ -17,11 +18,15 @@ struct Args {
|
|||||||
fn main() {
|
fn main() {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
let Ok(exec) = DseExecutable::load(args.input_path) else {
|
let Ok(contents) = fs::read(args.input_path) else {
|
||||||
|
panic!("Failed to read the input file")
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(exec) = DseExecutable::read(&mut Cursor::new(contents)) else {
|
||||||
panic!("Failed to load the input file")
|
panic!("Failed to load the input file")
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = disassemble(&exec);
|
let result = disassemble(&exec);
|
||||||
File::create(args.output_path).unwrap()
|
let _bytes_written = File::create(args.output_path).expect("Failed to create the output file")
|
||||||
.write(result.to_string().as_bytes()).unwrap();
|
.write(result.to_string().as_bytes()).expect("Failed to write to output file");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "dsa_formats"
|
||||||
|
edition.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
binrw = "0.15.2"
|
||||||
|
common = { path = "../common" }
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
|
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use binrw::{binrw, BinRead, binwrite, NullString, BinReaderExt, BinWriterExt, BinWrite};
|
use binrw::{binrw, BinRead, BinReaderExt, BinWriterExt, BinWrite};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use crate::prelude::Instruction;
|
use common::isa::instructions::Instruction;
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[brw(magic = b"DSAX", little)]
|
#[brw(magic = b"DSAX", little)]
|
||||||
@@ -48,11 +47,6 @@ pub struct DseExecutable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DseExecutable {
|
impl DseExecutable {
|
||||||
pub fn load(path: PathBuf) -> binrw::BinResult<Self> {
|
|
||||||
let mut file = File::open(path).map_err(|e| binrw::Error::Io(e))?;
|
|
||||||
Self::read(&mut file)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read<R: Read + Seek>(reader: &mut R) -> binrw::BinResult<Self> {
|
pub fn read<R: Read + Seek>(reader: &mut R) -> binrw::BinResult<Self> {
|
||||||
let header = DseHeader::read(reader)?;
|
let header = DseHeader::read(reader)?;
|
||||||
|
|
||||||
@@ -76,13 +70,7 @@ impl DseExecutable {
|
|||||||
|
|
||||||
Ok(DseExecutable { header, symbols, string_table, data, text })
|
Ok(DseExecutable { header, symbols, string_table, data, text })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save(&self, path: PathBuf) -> binrw::BinResult<()> {
|
|
||||||
let mut file = File::create(path)
|
|
||||||
.map_err(|e| binrw::Error::Io(e))?;
|
|
||||||
self.write(&mut file)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write<W: Write + Seek>(&self, writer: &mut W) -> binrw::BinResult<()> {
|
pub fn write<W: Write + Seek>(&self, writer: &mut W) -> binrw::BinResult<()> {
|
||||||
self.header.write(writer)?;
|
self.header.write(writer)?;
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
pub mod binary_dse;
|
||||||
|
pub mod binary_dso;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn it_works() {
|
||||||
|
// let result = add(2, 2);
|
||||||
|
// assert_eq!(result, 4);
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
use crate::EmulatorHandle;
|
||||||
|
use crate::memory::PhysAddr;
|
||||||
|
|
||||||
|
pub struct Display {
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
addr: PhysAddr,
|
||||||
|
|
||||||
|
emulator: EmulatorHandle,
|
||||||
|
buffer: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display {
|
||||||
|
pub fn new(width: u32, height: u32, addr: PhysAddr, emulator: EmulatorHandle) -> Self {
|
||||||
|
Self {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
addr,
|
||||||
|
emulator,
|
||||||
|
buffer: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn width(&self) -> usize {
|
||||||
|
self.width as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn height(&self) -> usize {
|
||||||
|
self.height as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn changed(&mut self) -> bool {
|
||||||
|
let temp = self.read();
|
||||||
|
if temp != self.buffer {
|
||||||
|
self.buffer = temp;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&mut self) -> Vec<u8> {
|
||||||
|
self.emulator.mem_read(self.addr, self.width * self.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
use crate::EmulatorHandle;
|
||||||
|
use crate::memory::PhysAddr;
|
||||||
|
|
||||||
|
pub struct FrameBuffer {
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
addr: PhysAddr,
|
||||||
|
|
||||||
|
emulator: EmulatorHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameBuffer {
|
||||||
|
pub fn new(width: u32, height: u32, addr: PhysAddr, emulator: EmulatorHandle) -> Self {
|
||||||
|
let width = width as usize;
|
||||||
|
let height = height as usize;
|
||||||
|
Self {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
addr,
|
||||||
|
emulator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn width(&self) -> usize {
|
||||||
|
self.width
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn height(&self) -> usize {
|
||||||
|
self.height
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&self) -> Vec<u8> {
|
||||||
|
self.emulator.mem_read(self.addr, ( self.width * self.height * 4 ) as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod display;
|
||||||
|
pub mod framebuffer;
|
||||||
|
pub mod serial;
|
||||||
+6
-47
@@ -1,18 +1,8 @@
|
|||||||
use std::{
|
use crate::EmulatorHandle;
|
||||||
collections::VecDeque,
|
use crate::memory::PhysAddr;
|
||||||
sync::{
|
use crate::processor::Interrupt;
|
||||||
mpsc::{Receiver, Sender},
|
use std::collections::VecDeque;
|
||||||
},
|
use std::sync::mpsc::{Receiver, Sender};
|
||||||
};
|
|
||||||
use std::any::Any;
|
|
||||||
use egui::{Ui, WidgetText};
|
|
||||||
use egui_tiles::TileId;
|
|
||||||
use emulator_core::{memory::{
|
|
||||||
PhysAddr,
|
|
||||||
}, EmulatorHandle};
|
|
||||||
use emulator_core::processor::Interrupt;
|
|
||||||
use crate::ui::Action;
|
|
||||||
use crate::ui::panels::TilePane;
|
|
||||||
|
|
||||||
/// ## SERIAL LAYOUT: (from the perspective of code inside the ui)
|
/// ## SERIAL LAYOUT: (from the perspective of code inside the ui)
|
||||||
/// - [base+0x00]: ser_out valid flag - set by kernel after writing a byte to serial
|
/// - [base+0x00]: ser_out valid flag - set by kernel after writing a byte to serial
|
||||||
@@ -97,35 +87,4 @@ impl Serial {
|
|||||||
self.read_buff.push(byte);
|
self.read_buff.push(byte);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TilePane for Serial {
|
|
||||||
fn title(&self) -> WidgetText {
|
|
||||||
"Serial".into()
|
|
||||||
}
|
|
||||||
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
|
||||||
self.update();
|
|
||||||
|
|
||||||
// Text field for input and a send button
|
|
||||||
ui.text_edit_multiline(&mut self.write_buff);
|
|
||||||
if ui.button("Send").clicked() {
|
|
||||||
self.write_buff.push(0 as char);
|
|
||||||
for byte in self.write_buff.bytes() {
|
|
||||||
let _ = self.write_tx.send(byte);
|
|
||||||
}
|
|
||||||
self.write_buff.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display received data
|
|
||||||
ui.label(String::from_utf8(self.read_buff.clone()).unwrap());
|
|
||||||
if ui.button("Clear").clicked() {
|
|
||||||
self.read_buff.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,6 +15,7 @@ use crate::processor::{Interrupt, RunningState, SharedState};
|
|||||||
pub mod processor;
|
pub mod processor;
|
||||||
pub mod memory;
|
pub mod memory;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
pub mod devices;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum DsaError {
|
pub enum DsaError {
|
||||||
@@ -41,7 +42,7 @@ pub trait DsaImplementation: Send {
|
|||||||
/// IO mapping also
|
/// IO mapping also
|
||||||
fn apply_io_mapping(&mut self, map: IoMap);
|
fn apply_io_mapping(&mut self, map: IoMap);
|
||||||
|
|
||||||
/// List all memory devices
|
/// List all memory io_devices
|
||||||
/// returns a copy of the IOmap (cannot be a reference since across-threads)
|
/// returns a copy of the IOmap (cannot be a reference since across-threads)
|
||||||
fn get_io_map(&self) -> IoMap;
|
fn get_io_map(&self) -> IoMap;
|
||||||
|
|
||||||
@@ -64,7 +65,7 @@ pub struct EmulatorHandle {
|
|||||||
|
|
||||||
impl EmulatorHandle {
|
impl EmulatorHandle {
|
||||||
|
|
||||||
pub fn read_byte(&mut self, addr: PhysAddr) -> u8 {
|
pub fn read_byte(&self, addr: PhysAddr) -> u8 {
|
||||||
self.memory.read_byte(addr)
|
self.memory.read_byte(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +73,7 @@ impl EmulatorHandle {
|
|||||||
self.memory.write_byte(addr, val);
|
self.memory.write_byte(addr, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mem_read(&mut self, addr: PhysAddr, size: u32) -> Vec<u8> {
|
pub fn mem_read(&self, addr: PhysAddr, size: u32) -> Vec<u8> {
|
||||||
self.memory.read_bytes(addr, size as usize).to_vec()
|
self.memory.read_bytes(addr, size as usize).to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::memory::{FaultInfo, PhysAddr, VirtAddr, tlb::TLB};
|
use crate::memory::{FaultInfo, PhysAddr, VirtAddr, tlb::TLB};
|
||||||
|
|
||||||
pub struct MMU {
|
pub(crate) struct MMU {
|
||||||
buffer: TLB,
|
buffer: TLB,
|
||||||
enable_paging: bool,
|
enable_paging: bool,
|
||||||
|
|
||||||
|
|||||||
@@ -2,32 +2,6 @@ use std::{
|
|||||||
alloc::{Layout, alloc_zeroed, dealloc},
|
alloc::{Layout, alloc_zeroed, dealloc},
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "mainstore-arraymap")]
|
|
||||||
pub mod arraymap;
|
|
||||||
#[cfg(feature = "mainstore-arraymap")]
|
|
||||||
pub use arraymap::ArrayStore;
|
|
||||||
|
|
||||||
#[cfg(feature = "mainstore-hashmap")]
|
|
||||||
pub mod hashmap;
|
|
||||||
#[cfg(feature = "mainstore-hashmap")]
|
|
||||||
pub use hashmap::HashStore;
|
|
||||||
|
|
||||||
#[cfg(feature = "mainstore-stackarray")]
|
|
||||||
pub mod stackarray;
|
|
||||||
#[cfg(feature = "mainstore-stackarray")]
|
|
||||||
pub use stackarray::StackArrayStore;
|
|
||||||
|
|
||||||
#[cfg(feature = "mainstore-bulkalloc")]
|
|
||||||
pub mod bulkalloc;
|
|
||||||
#[cfg(feature = "mainstore-bulkalloc")]
|
|
||||||
pub use bulkalloc::BulkAllocStore;
|
|
||||||
|
|
||||||
// #[cfg(feature = "mainstore-prealloc")]
|
|
||||||
// pub mod prealloc;
|
|
||||||
// #[cfg(feature = "mainstore-prealloc")]
|
|
||||||
// pub use prealloc::PreAllocStore;
|
|
||||||
|
|
||||||
use crate::memory::PhysAddr;
|
use crate::memory::PhysAddr;
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
@@ -91,12 +65,7 @@ impl TryFrom<Vec<u8>> for Page {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
pub(crate) struct MemoryBank {
|
||||||
const fn idx(addr: PhysAddr) -> (usize, usize) {
|
|
||||||
((addr >> 12) as usize, (addr & 0xFFF) as usize)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MemoryBank {
|
|
||||||
heap: *mut u8,
|
heap: *mut u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +75,7 @@ impl MemoryBank {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
heap: unsafe {
|
heap: unsafe {
|
||||||
// allocate the full 32 bit address range.
|
// allocate the full 32-bit address range.
|
||||||
alloc_zeroed(Layout::from_size_align(u32::MAX as usize, 4096).unwrap())
|
alloc_zeroed(Layout::from_size_align(u32::MAX as usize, 4096).unwrap())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "emulator_ui"
|
name = "emulator_ui"
|
||||||
version = "0.3.0"
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
|
|
||||||
@@ -12,36 +12,21 @@ path = "src/main.rs"
|
|||||||
name = "dsa"
|
name = "dsa"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
criterion = { version = "0.8.2", features = ["html_reports"] }
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arc-swap = "1.8.2"
|
|
||||||
clap = { version = "4.5.60", features = ["derive"] }
|
|
||||||
fxhash = "0.2.1"
|
|
||||||
ron = "0.12.0"
|
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
|
||||||
|
|
||||||
common = { path = "../../common" }
|
common = { path = "../../common" }
|
||||||
|
dsa_formats = { path = "../../dsa_formats" }
|
||||||
|
assembler = { path = "../../assembler" }
|
||||||
|
emulator_core = { version = "0.3.0", path = "../emulator_core" }
|
||||||
|
disassembler = { version = "0.3.0", path = "../../disassembler" }
|
||||||
|
|
||||||
|
clap = { version = "4.5.60", features = ["derive"] }
|
||||||
|
ron = "0.12.0"
|
||||||
egui = "0.35.0"
|
egui = "0.35.0"
|
||||||
eframe = "0.35.0"
|
eframe = "0.35.0"
|
||||||
egui_tiles = "0.16.0"
|
egui_tiles = "0.16.0"
|
||||||
egui_code_editor = "0.3.7"
|
egui_code_editor = "0.3.7"
|
||||||
egui_file = "0.27.0"
|
egui_file = "0.27.0"
|
||||||
dirs = "6.0.0"
|
|
||||||
assembler = { path = "../../assembler" }
|
|
||||||
egui_extras = "0.35.0"
|
egui_extras = "0.35.0"
|
||||||
emulator_core = { version = "0.3.0", path = "../emulator_core" }
|
|
||||||
disassembler = { version = "0.3.0", path = "../../disassembler" }
|
|
||||||
egui-file-dialog = "0.14.1"
|
egui-file-dialog = "0.14.1"
|
||||||
egui_commonmark = { version = "0.24.0", features = ["better_syntax_highlighting", "macros"] }
|
egui_commonmark = { version = "0.24.0", features = ["better_syntax_highlighting", "macros"] }
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["mainstore-prealloc"]
|
|
||||||
|
|
||||||
# Memory Bank Features
|
|
||||||
mainstore-bulkalloc = [] # Fastest for Writes
|
|
||||||
mainstore-prealloc = [] # Fastest for Reads
|
|
||||||
mainstore-stackarray = [] # Slightly outperforms ArrayMap but requires a large stack
|
|
||||||
mainstore-arraymap = [] # Simple implementation
|
|
||||||
mainstore-hashmap = [] # Old implementation, no raw pointers. Uses a hashmap
|
|
||||||
@@ -2,7 +2,7 @@ use std::{fs, path::PathBuf};
|
|||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use emulator_core::config::{IoMap, IoMapping, MemoryMap};
|
use emulator_core::config::{IoMap, MemoryMap};
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
#![feature(likely_unlikely)]
|
use crate::ui::DamnSimpleUI;
|
||||||
#![feature(inherent_associated_types)]
|
use eframe::NativeOptions;
|
||||||
#![feature(str_as_str)]
|
use emulator_core::EmulatorController;
|
||||||
|
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
pub mod args;
|
pub mod args;
|
||||||
|
pub mod state;
|
||||||
|
|
||||||
|
pub fn run_app(mut controller: EmulatorController) -> eframe::Result<()> {
|
||||||
|
// initialise emulator
|
||||||
|
controller.run().unwrap();
|
||||||
|
|
||||||
|
eframe::run_native(
|
||||||
|
"Damn Simple Architecture",
|
||||||
|
NativeOptions::default(),
|
||||||
|
Box::new(|cc| Ok(Box::new(DamnSimpleUI::new(cc, controller)))),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use dsa::args::DsaArgs;
|
use dsa::args::DsaArgs;
|
||||||
use dsa::ui::run_app;
|
use dsa::run_app;
|
||||||
use emulator_core::{DsaImplementation, EmulatorController, processor::Emulator};
|
use emulator_core::{DsaImplementation, EmulatorController, processor::Emulator};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -9,49 +9,7 @@ fn main() {
|
|||||||
|
|
||||||
let mut emulator = Emulator::new();
|
let mut emulator = Emulator::new();
|
||||||
emulator.apply_memory_map(args.get_memory_map());
|
emulator.apply_memory_map(args.get_memory_map());
|
||||||
|
|
||||||
let controller = EmulatorController::new(Box::new(emulator));
|
let controller = EmulatorController::new(Box::new(emulator));
|
||||||
|
|
||||||
//
|
run_app(controller).unwrap();
|
||||||
// if let Some(bin) = args.get_binary() {
|
}
|
||||||
// for (i, ch) in bin.chunks(4).enumerate() {
|
|
||||||
// let instruction = Instruction(u32::from_le_bytes(ch.try_into().unwrap()));
|
|
||||||
// let hex = ch
|
|
||||||
// .iter()
|
|
||||||
// .map(|b| format!("{:02X}", b))
|
|
||||||
// .collect::<Vec<_>>()
|
|
||||||
// .join(" ");
|
|
||||||
// let chars = ch
|
|
||||||
// .iter()
|
|
||||||
// .map(|b| {
|
|
||||||
// if b.is_ascii_graphic() {
|
|
||||||
// *b as char
|
|
||||||
// } else {
|
|
||||||
// '.'
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .collect::<String>();
|
|
||||||
// println!("{:04X}: {:<12} {:4} {:?}", i * 4, hex, chars, instruction);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// let program: Vec<Page> = Page::paginate(&bin).collect();
|
|
||||||
//
|
|
||||||
// for (i, page) in program.iter().enumerate() {
|
|
||||||
// emulator.memory_mut().write_page(i as u32 * 4096, &page);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
if args.headless() {
|
|
||||||
run_server(&args);
|
|
||||||
} else {
|
|
||||||
run_app(controller).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_server(args: &DsaArgs) {
|
|
||||||
if args.headless() {
|
|
||||||
println!("Running in headless mode");
|
|
||||||
} else {
|
|
||||||
println!("Running in GUI mode");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
use crate::ui::process_manager::ProcessSpawner;
|
||||||
|
use egui_tiles::TileId;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
|
||||||
|
/// the action enum forms the core of the application's state.
|
||||||
|
/// - Actions are used as the main way that Tiles inside the application pass state updates back to the parent UI,
|
||||||
|
/// specifically when those state updates involve other files. for example, pressing "build" in the editor would emit
|
||||||
|
/// a SpawnProcess action, running some task which on completion emits a LoadBinary action on the result
|
||||||
|
pub enum Action {
|
||||||
|
None,
|
||||||
|
RequestCloseTab(TileId),
|
||||||
|
CloseTab(TileId),
|
||||||
|
Quit,
|
||||||
|
OpenFile(PathBuf),
|
||||||
|
LoadBinary(DataSource),
|
||||||
|
SaveFile(PathBuf, TileId),
|
||||||
|
RequestSaveAs(TileId),
|
||||||
|
ShowError(String),
|
||||||
|
SpawnProcess(ProcessSpawner)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum DataSource {
|
||||||
|
File(PathBuf),
|
||||||
|
Data(Vec<u8>),
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
pub fn configure_appearance(ctx: &egui::Context) {
|
||||||
|
// configure appearance of UI elements
|
||||||
|
let mut visuals = egui::Visuals::dark();
|
||||||
|
|
||||||
|
visuals.window_fill = egui::Color32::from_rgb(20, 20, 20);
|
||||||
|
visuals.panel_fill = egui::Color32::from_rgb(20, 20, 20);
|
||||||
|
visuals.widgets.inactive.fg_stroke =
|
||||||
|
egui::Stroke::from((1.0, egui::Color32::from_rgb(255, 255, 255)));
|
||||||
|
visuals.widgets.inactive.bg_stroke =
|
||||||
|
egui::Stroke::from((1.0, egui::Color32::from_rgb(60, 60, 60)));
|
||||||
|
visuals.widgets.inactive.corner_radius = egui::CornerRadius::from(4);
|
||||||
|
visuals.widgets.inactive.bg_fill = egui::Color32::from_rgb(20, 20, 20);
|
||||||
|
visuals.widgets.inactive.weak_bg_fill = egui::Color32::from_rgb(20, 20, 20);
|
||||||
|
visuals.widgets.inactive.expansion = 1.0;
|
||||||
|
|
||||||
|
// tile resize handle
|
||||||
|
visuals.window_stroke = egui::Stroke::new(1.0, egui::Color32::from_rgb(45, 45, 45));
|
||||||
|
visuals.extreme_bg_color = egui::Color32::from_rgb(20, 20, 20);
|
||||||
|
|
||||||
|
ctx.set_visuals(visuals);
|
||||||
|
|
||||||
|
let mut fonts = egui::FontDefinitions::default();
|
||||||
|
fonts.font_data.insert(
|
||||||
|
"JetBrains Mono Nerd Font".to_string(),
|
||||||
|
std::sync::Arc::new(egui::FontData::from_static(include_bytes!(
|
||||||
|
"../../font/JetBrainsMonoNerdFontMono_Regular.ttf",
|
||||||
|
))),
|
||||||
|
);
|
||||||
|
|
||||||
|
fonts
|
||||||
|
.families
|
||||||
|
.entry(egui::FontFamily::Proportional)
|
||||||
|
.or_default()
|
||||||
|
.insert(0, "JetBrains Mono Nerd Font".to_string());
|
||||||
|
|
||||||
|
fonts
|
||||||
|
.families
|
||||||
|
.entry(egui::FontFamily::Monospace)
|
||||||
|
.or_default()
|
||||||
|
.insert(0, "JetBrains Mono Nerd Font".to_string());
|
||||||
|
|
||||||
|
ctx.set_fonts(fonts);
|
||||||
|
}
|
||||||
@@ -1,697 +0,0 @@
|
|||||||
use std::fmt::Write;
|
|
||||||
use std::fs::{self};
|
|
||||||
use std::io::Cursor;
|
|
||||||
use std::sync::atomic::Ordering;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::{
|
|
||||||
ffi::OsStr,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod syntax;
|
|
||||||
|
|
||||||
use super::Component;
|
|
||||||
use assembler::prelude::Assembler;
|
|
||||||
use common::formats::binary_dse::DseExecutable;
|
|
||||||
use common::prelude::Instruction;
|
|
||||||
use egui::{Align, Context, Key, Layout, Ui};
|
|
||||||
use egui_code_editor::{CodeEditor, ColorTheme, Completer, Syntax};
|
|
||||||
use egui_extras::{Column, TableBuilder};
|
|
||||||
use egui_file::FileDialog;
|
|
||||||
use emulator_core::{
|
|
||||||
memory::ram::{MemoryBank, Page},
|
|
||||||
processor::state::SharedState
|
|
||||||
};
|
|
||||||
use crate::ui::components::memory::MemoryViewer;
|
|
||||||
|
|
||||||
pub struct Editor {
|
|
||||||
state: Arc<SharedState>,
|
|
||||||
memory: MemoryBank,
|
|
||||||
|
|
||||||
// editor state
|
|
||||||
path: Option<PathBuf>,
|
|
||||||
unsaved: bool,
|
|
||||||
text: String,
|
|
||||||
buffer: String,
|
|
||||||
|
|
||||||
// editor widget
|
|
||||||
editor: CodeEditor,
|
|
||||||
completer: Completer,
|
|
||||||
syntax: Syntax,
|
|
||||||
|
|
||||||
// output / loading
|
|
||||||
output: Vec<u8>,
|
|
||||||
viewer: MemoryViewer,
|
|
||||||
load_offset: u32,
|
|
||||||
offset_str: String,
|
|
||||||
|
|
||||||
// file dialogs
|
|
||||||
open_file_dialog: Option<FileDialog>,
|
|
||||||
save_file_dialog: Option<FileDialog>,
|
|
||||||
|
|
||||||
// other
|
|
||||||
visible: bool,
|
|
||||||
show_output: bool,
|
|
||||||
error: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Editor {
|
|
||||||
fn title(&self) -> &'static str {
|
|
||||||
"Editor"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visible(&mut self) -> &mut bool {
|
|
||||||
&mut self.visible
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut Ui) {
|
|
||||||
if self.buffer != self.text {
|
|
||||||
self.unsaved = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.vertical(|ui| {
|
|
||||||
// Top bar
|
|
||||||
|
|
||||||
if ui.input(|i| i.key_pressed(Key::S) && i.modifiers.ctrl) {
|
|
||||||
self.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.render_toolbar(ui);
|
|
||||||
|
|
||||||
ui.add_space(4.0); // Add some spacing instead of just a separator
|
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
let remaining_height = f32::max(ui.available_height() - 100.0, 100.0);
|
|
||||||
|
|
||||||
ui.allocate_ui_with_layout(
|
|
||||||
egui::Vec2::new(ui.available_width(), remaining_height),
|
|
||||||
Layout::left_to_right(Align::Min),
|
|
||||||
|ui| {
|
|
||||||
if self.show_output {
|
|
||||||
egui::Panel::right("Editor Output").resizable(false).show_inside(ui, |ui| {
|
|
||||||
self.viewer.ui(ui, self.output.as_slice(), 0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
self.render_editor(ui);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
self.render_bottom_bar(ui);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Editor {
|
|
||||||
#[must_use]
|
|
||||||
pub fn new(state: Arc<SharedState>, memory: MemoryBank) -> Self {
|
|
||||||
Self {
|
|
||||||
memory,
|
|
||||||
state,
|
|
||||||
path: None,
|
|
||||||
text: String::new(),
|
|
||||||
buffer: String::new(),
|
|
||||||
completer: Completer::new_with_syntax(&Syntax::default()).with_user_words(),
|
|
||||||
syntax: Syntax::default(),
|
|
||||||
editor: CodeEditor::default()
|
|
||||||
.id_source("editor")
|
|
||||||
.with_fontsize(12.0)
|
|
||||||
.with_rows(0)
|
|
||||||
.with_theme(THEME)
|
|
||||||
.with_numlines(true),
|
|
||||||
output: Vec::new(),
|
|
||||||
viewer: MemoryViewer::new(),
|
|
||||||
unsaved: true,
|
|
||||||
visible: false,
|
|
||||||
load_offset: 0,
|
|
||||||
offset_str: String::new(),
|
|
||||||
error: None,
|
|
||||||
open_file_dialog: None,
|
|
||||||
save_file_dialog: None,
|
|
||||||
show_output: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn filename(&self) -> &str {
|
|
||||||
if let Some(path) = &self.path {
|
|
||||||
return path
|
|
||||||
.file_name()
|
|
||||||
.unwrap_or_else(|| OsStr::new("Unnamed!"))
|
|
||||||
.to_str()
|
|
||||||
.unwrap_or_else(|| unreachable!("File name should be valid UTF-8."));
|
|
||||||
}
|
|
||||||
"Unnamed!"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extension(&self) -> &str {
|
|
||||||
if let Some(path) = &self.path {
|
|
||||||
return path
|
|
||||||
.extension()
|
|
||||||
.unwrap_or_else(|| OsStr::new("Unknown!"))
|
|
||||||
.to_str()
|
|
||||||
.unwrap_or_else(|| unreachable!("File name should be valid UTF-8."));
|
|
||||||
}
|
|
||||||
"Unknown!"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn save(&mut self) {
|
|
||||||
if self.open_file_dialog.is_some() {
|
|
||||||
// TODO: Flash an error stating you can only have one menu open at once.
|
|
||||||
self.open_file_dialog = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(path) = &self.path {
|
|
||||||
// Save to existing path
|
|
||||||
self.buffer = self.text.clone();
|
|
||||||
|
|
||||||
let text = if path.extension().is_some_and(|ext| ext == "dsb") {
|
|
||||||
let mut res = Vec::new();
|
|
||||||
for line in self.text.lines() {
|
|
||||||
for line in line.split_whitespace() {
|
|
||||||
match u32::from_str_radix(line, 16) {
|
|
||||||
Ok(num) => res.push(num),
|
|
||||||
Err(e) => {
|
|
||||||
self.error = Some(format!("Failed to parse file: {e}"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res.into_iter()
|
|
||||||
.flat_map(u32::to_be_bytes)
|
|
||||||
.collect::<Vec<u8>>()
|
|
||||||
} else {
|
|
||||||
self.text.as_bytes().to_vec()
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(why) = std::fs::write(path, text) {
|
|
||||||
self.error = Some(format!("Failed to save file: {why}"));
|
|
||||||
} else {
|
|
||||||
self.unsaved = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Open the save dialog.
|
|
||||||
let work_dir = std::env::current_dir().unwrap_or_else(|_| {
|
|
||||||
dirs::home_dir()
|
|
||||||
.expect("Couldn't get your current working directory or your home directory.")
|
|
||||||
});
|
|
||||||
|
|
||||||
if self.save_file_dialog.is_none() {
|
|
||||||
let mut dialog = FileDialog::save_file().initial_path(work_dir);
|
|
||||||
dialog.open();
|
|
||||||
self.save_file_dialog = Some(dialog);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open(&mut self) {
|
|
||||||
let work_dir = std::env::current_dir().unwrap_or_else(|_| {
|
|
||||||
dirs::home_dir()
|
|
||||||
.expect("Couldn't get your current working directory or your home directory.")
|
|
||||||
});
|
|
||||||
|
|
||||||
if self.save_file_dialog.is_some() {
|
|
||||||
// TODO: Flash an error stating you can only have one menu open at once.
|
|
||||||
self.save_file_dialog = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.open_file_dialog.is_none() {
|
|
||||||
let mut dialog = FileDialog::open_file();
|
|
||||||
if let Some(p) = &self.path
|
|
||||||
&& let Some(path) = p.parent().map(Path::to_path_buf)
|
|
||||||
{
|
|
||||||
dialog.set_path(path);
|
|
||||||
} else {
|
|
||||||
dialog.set_path(work_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog.open();
|
|
||||||
self.open_file_dialog = Some(dialog);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.syntax = match self.extension() {
|
|
||||||
"dsa" => syntax::dsa(),
|
|
||||||
"dsc" => syntax::dsc(),
|
|
||||||
"rs" => Syntax::rust(),
|
|
||||||
_ => Syntax::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.completer = Completer::new_with_syntax(&self.syntax).with_user_words();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_file_dialogs(&mut self, ui: &Ui) {
|
|
||||||
// Handle open dialog
|
|
||||||
if let Some(dialog) = &mut self.open_file_dialog
|
|
||||||
&& dialog.show(ui).selected()
|
|
||||||
{
|
|
||||||
if let Some(file) = dialog.path() {
|
|
||||||
// check if the file is a binary file
|
|
||||||
if file.extension().is_some_and(|ext| ext == "dsb") {
|
|
||||||
match std::fs::read(file) {
|
|
||||||
Ok(content) => {
|
|
||||||
let mut res = String::new();
|
|
||||||
for (i, b) in content.iter().enumerate() {
|
|
||||||
_ = write!(res, "{b:02x}");
|
|
||||||
if i % 4 == 3 {
|
|
||||||
res.push('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.text = res.clone();
|
|
||||||
self.buffer = res;
|
|
||||||
self.path = Some(file.to_path_buf());
|
|
||||||
self.unsaved = false;
|
|
||||||
self.error = None;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
self.error = Some(format!("Failed to read file: {e}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
match std::fs::read_to_string(file) {
|
|
||||||
Ok(content) => {
|
|
||||||
self.text = content.clone();
|
|
||||||
self.buffer = content;
|
|
||||||
self.path = Some(file.to_path_buf());
|
|
||||||
self.unsaved = false;
|
|
||||||
self.error = None;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
self.error = Some(format!("Failed to read file: {e}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.open_file_dialog = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle save dialog
|
|
||||||
if let Some(dialog) = &mut self.save_file_dialog
|
|
||||||
&& dialog.show(ui).selected()
|
|
||||||
{
|
|
||||||
if let Some(file) = dialog.path() {
|
|
||||||
self.buffer = self.text.clone();
|
|
||||||
|
|
||||||
let content = if file.extension().is_some_and(|ext| ext == "dsb") {
|
|
||||||
let mut res = Vec::new();
|
|
||||||
for line in self.text.lines() {
|
|
||||||
for line in line.split_whitespace() {
|
|
||||||
match u32::from_str_radix(line, 16) {
|
|
||||||
Ok(num) => res.push(num),
|
|
||||||
Err(e) => {
|
|
||||||
self.error = Some(format!("Failed to parse file: {e}"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res.into_iter()
|
|
||||||
.flat_map(u32::to_be_bytes)
|
|
||||||
.collect::<Vec<u8>>()
|
|
||||||
} else {
|
|
||||||
self.text.clone().as_bytes().to_vec()
|
|
||||||
};
|
|
||||||
|
|
||||||
match std::fs::write(file, content) {
|
|
||||||
Ok(()) => {
|
|
||||||
self.path = Some(file.to_path_buf());
|
|
||||||
self.unsaved = false;
|
|
||||||
self.error = None;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
self.error = Some(format!("Failed to save file: {e}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.save_file_dialog = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_editor(&mut self, ui: &mut Ui) {
|
|
||||||
let available_width = ui.available_width();
|
|
||||||
let mut editor = self.editor.clone().desired_width(available_width);
|
|
||||||
editor.show_with_completer(ui, &mut self.text, &self.syntax, &mut self.completer);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_bottom_bar(&self, ui: &mut Ui) {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
// error display
|
|
||||||
ui.label(
|
|
||||||
egui::RichText::new(self.error.clone().unwrap_or_default())
|
|
||||||
.color(egui::Color32::RED),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build(&mut self) {
|
|
||||||
if let Some(path) = &self.path {
|
|
||||||
match path.extension().and_then(|ext| ext.to_str()) {
|
|
||||||
Some("dsa") => {
|
|
||||||
let mut assembler = Assembler::new(path);
|
|
||||||
assembler.start(());
|
|
||||||
|
|
||||||
// Or block until done
|
|
||||||
self.output = match assembler.output() {
|
|
||||||
Ok(instructions) => instructions,
|
|
||||||
Err(e) => {
|
|
||||||
self.error = Some(e.to_string());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// Some("dsc") => {
|
|
||||||
// let dsa_path = Path::new(path).with_extension("dsa");
|
|
||||||
// let mut compiler = Compiler::new(path);
|
|
||||||
|
|
||||||
// let is_lib = false;
|
|
||||||
// compiler.start(is_lib);
|
|
||||||
|
|
||||||
// if let Err(e) = compiler.write_result(&dsa_path) {
|
|
||||||
// self.error = Some(e.to_string());
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let mut assembler = Assembler::new(&dsa_path);
|
|
||||||
// assembler.start(());
|
|
||||||
|
|
||||||
// // Or block until done
|
|
||||||
// self.output = match assembler.output() {
|
|
||||||
// Ok(instructions) => instructions,
|
|
||||||
// Err(e) => {
|
|
||||||
// self.error = Some(format!("Assembler error: {e}"));
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
Some("dsb") => {
|
|
||||||
if let Ok(bytes) = fs::read(path) {
|
|
||||||
self.output = bytes;
|
|
||||||
} else {
|
|
||||||
self.error = Some("Failed to read file".to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
self.error = Some(format!("Invalid file type: {}", self.filename()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_toolbar(&mut self, ui: &mut Ui) {
|
|
||||||
self.handle_file_dialogs(ui);
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label(format!("File type: {}", self.extension()));
|
|
||||||
ui.label(format!("Filename: {}", self.filename()));
|
|
||||||
ui.label(format!("Unsaved: {}", self.unsaved));
|
|
||||||
|
|
||||||
// number of lines in the file
|
|
||||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
|
||||||
let line_count = self.text.lines().count();
|
|
||||||
ui.label(format!("Lines: {line_count}"));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.spacing_mut().button_padding = egui::vec2(8.0, 4.0);
|
|
||||||
ui.spacing_mut().item_spacing.x = 6.0;
|
|
||||||
|
|
||||||
// Opens a file
|
|
||||||
// ui.add_enabled(false, egui::Button::new("Open"));
|
|
||||||
if ui.button("Open").clicked() {
|
|
||||||
self.open();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Saves the current file
|
|
||||||
// ui.add_enabled(false, egui::Button::new("Save"));
|
|
||||||
if ui.button("Save").clicked() {
|
|
||||||
self.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
// // builds the current file
|
|
||||||
// ui.add_enabled(false, egui::Button::new("Build"));
|
|
||||||
if ui.button("Build").clicked() && !self.unsaved {
|
|
||||||
self.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
// // Loads the generated binary into the assembler at the provided offset
|
|
||||||
// ui.add_enabled(false, egui::Button::new("Load"));
|
|
||||||
if ui.button("Load").clicked() {
|
|
||||||
if self.error.is_some() {
|
|
||||||
self.error = Some("Can't load program at invalid offset!".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut reader = Cursor::new(self.output.clone());
|
|
||||||
let Ok(executable) = DseExecutable::read(&mut reader) else {
|
|
||||||
self.error = Some("Failed to read program!".to_string());
|
|
||||||
return
|
|
||||||
};
|
|
||||||
|
|
||||||
let image: Vec<Page> = Page::paginate(&executable.to_memory_image()).collect();
|
|
||||||
|
|
||||||
self.state.reseting.store(true, Ordering::Relaxed);
|
|
||||||
|
|
||||||
self.memory.clear();
|
|
||||||
for (i, page) in image.iter().enumerate() {
|
|
||||||
self.memory.write_page(i as u32 * 4096, &page);
|
|
||||||
}
|
|
||||||
self.state.reseting.store(false, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry widget to enter a load offset
|
|
||||||
if ui.text_edit_singleline(&mut self.offset_str).changed() {
|
|
||||||
if let Some(offset) = parse_address(&self.offset_str) {
|
|
||||||
self.load_offset = offset;
|
|
||||||
self.error = None;
|
|
||||||
} else {
|
|
||||||
self.error = Some("Invalid offset".to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.checkbox(&mut self.show_output, "Show Output");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_address(address: &str) -> Option<u32> {
|
|
||||||
address.strip_prefix("0x").map_or_else(
|
|
||||||
|| {
|
|
||||||
address.strip_prefix("0b").map_or_else(
|
|
||||||
|| {
|
|
||||||
address.strip_prefix("0o").map_or_else(
|
|
||||||
|| address.parse::<u32>().ok(),
|
|
||||||
|oct| u32::from_str_radix(oct, 8).ok(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|bin| u32::from_str_radix(bin, 2).ok(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|hex| u32::from_str_radix(hex, 16).ok(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const THEME: ColorTheme = ColorTheme {
|
|
||||||
name: "Theme",
|
|
||||||
dark: true,
|
|
||||||
bg: "1b1b1b",
|
|
||||||
cursor: "de5252", // fg4
|
|
||||||
selection: "28323B", // bg2
|
|
||||||
comments: "444444", // gray1
|
|
||||||
functions: "7CCCC7", // green1
|
|
||||||
keywords: "6C81E0", // red1
|
|
||||||
literals: "A3ABFF", // fg1
|
|
||||||
numerics: "8A46CF", // purple1
|
|
||||||
punctuation: "99C9C9", // orange1
|
|
||||||
strs: "618c84", // aqua1
|
|
||||||
types: "B8B9D4", // yellow1
|
|
||||||
special: "de5252", // blue1
|
|
||||||
};
|
|
||||||
//
|
|
||||||
// struct Output {
|
|
||||||
// pub data: Vec<u8>,
|
|
||||||
//
|
|
||||||
// show_bytes: bool,
|
|
||||||
// show_words: bool,
|
|
||||||
// show_ascii: bool,
|
|
||||||
// show_instruction: bool,
|
|
||||||
// show_decimal: bool,
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// impl Output {
|
|
||||||
// fn new() -> Self {
|
|
||||||
// Self {
|
|
||||||
// data: vec![],
|
|
||||||
// show_bytes: true,
|
|
||||||
// show_words: true,
|
|
||||||
// show_ascii: true,
|
|
||||||
// show_instruction: true,
|
|
||||||
// show_decimal: true,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// fn mut_data(&mut self) -> &mut Vec<u8> {
|
|
||||||
// &mut self.data
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// fn data(&self) -> &Vec<u8> {
|
|
||||||
// &self.data
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// fn ui(&mut self, ui: &mut Ui) {
|
|
||||||
// // Output area with synchronized scrolling
|
|
||||||
// egui::ScrollArea::vertical()
|
|
||||||
// .id_salt("output_scroll")
|
|
||||||
// .max_width(400.0)
|
|
||||||
// .show(ui, |ui| {
|
|
||||||
// if self.data.is_empty() {
|
|
||||||
// ui.label(
|
|
||||||
// egui::RichText::new("No output data")
|
|
||||||
// .font(egui::FontId::monospace(12.0))
|
|
||||||
// .color(egui::Color32::GRAY),
|
|
||||||
// );
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// ui.horizontal(|ui| {
|
|
||||||
// ui.toggle_value(&mut self.show_bytes, "Bytes");
|
|
||||||
// ui.toggle_value(&mut self.show_words, "Words");
|
|
||||||
// ui.toggle_value(&mut self.show_decimal, "Decimal");
|
|
||||||
// ui.toggle_value(&mut self.show_ascii, "Ascii");
|
|
||||||
// ui.toggle_value(&mut self.show_instruction, "Instructions");
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// ui.separator();
|
|
||||||
//
|
|
||||||
// let mut table = TableBuilder::new(ui)
|
|
||||||
// .resizable(false) // makes all columns resizable at once
|
|
||||||
// .column(Column::initial(55.0)); // addr
|
|
||||||
//
|
|
||||||
// if self.show_bytes {
|
|
||||||
// table = table.column(Column::initial(90.0));
|
|
||||||
// }
|
|
||||||
// if self.show_words {
|
|
||||||
// table = table.column(Column::initial(75.0));
|
|
||||||
// }
|
|
||||||
// if self.show_decimal {
|
|
||||||
// table = table.column(Column::initial(80.0));
|
|
||||||
// }
|
|
||||||
// if self.show_ascii {
|
|
||||||
// table = table.column(Column::initial(50.0));
|
|
||||||
// }
|
|
||||||
// if self.show_instruction {
|
|
||||||
// table = table.column(Column::initial(200.0).clip(true));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// table
|
|
||||||
// .header(20.0, |mut header| {
|
|
||||||
// header.col(|ui| {
|
|
||||||
// ui.label("Addr");
|
|
||||||
// });
|
|
||||||
// if self.show_bytes {
|
|
||||||
// header.col(|ui| {
|
|
||||||
// ui.label("Bytes");
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// if self.show_words {
|
|
||||||
// header.col(|ui| {
|
|
||||||
// ui.label("Word");
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// if self.show_decimal {
|
|
||||||
// header.col(|ui| {
|
|
||||||
// ui.label("Decimal");
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// if self.show_ascii {
|
|
||||||
// header.col(|ui| {
|
|
||||||
// ui.label("Ascii");
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// if self.show_instruction {
|
|
||||||
// header.col(|ui| {
|
|
||||||
// ui.label("Instruction");
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .body(|mut body| {
|
|
||||||
// // Process bytes in chunks of 4
|
|
||||||
// for (line_num, chunk) in self.data.chunks(4).enumerate() {
|
|
||||||
// body.row(18.0, |mut row| {
|
|
||||||
// let address = line_num * 4;
|
|
||||||
// let mut buf = [0u8; 4];
|
|
||||||
// buf[..chunk.len()].copy_from_slice(chunk);
|
|
||||||
// let value = u32::from_le_bytes(buf);
|
|
||||||
//
|
|
||||||
// // Address column
|
|
||||||
// row.col(|ui| {
|
|
||||||
// ui.label(
|
|
||||||
// egui::RichText::new(format!("0x{address:04X}"))
|
|
||||||
// .font(egui::FontId::monospace(12.0)),
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// if self.show_bytes {
|
|
||||||
// row.col(|ui| {
|
|
||||||
// // Individual bytes column
|
|
||||||
// ui.label(
|
|
||||||
// egui::RichText::new(
|
|
||||||
// chunk
|
|
||||||
// .iter()
|
|
||||||
// .map(|b| format!("{b:02X}"))
|
|
||||||
// .collect::<Vec<_>>()
|
|
||||||
// .join(" "),
|
|
||||||
// )
|
|
||||||
// .font(egui::FontId::monospace(12.0))
|
|
||||||
// .color(egui::Color32::from_rgb(200, 200, 255)),
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if self.show_words {
|
|
||||||
// row.col(|ui| {
|
|
||||||
// // Hex column
|
|
||||||
// ui.label(
|
|
||||||
// egui::RichText::new(format!("0x{value:08X}"))
|
|
||||||
// .font(egui::FontId::monospace(12.0))
|
|
||||||
// .color(egui::Color32::from_rgb(255, 100, 100)),
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if self.show_decimal {
|
|
||||||
// row.col(|ui| {
|
|
||||||
// ui.label(
|
|
||||||
// egui::RichText::new(format!("{value}"))
|
|
||||||
// .font(egui::FontId::monospace(12.0))
|
|
||||||
// .color(egui::Color32::from_rgb(100, 255, 100)),
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if self.show_ascii {
|
|
||||||
// let ascii = value.to_le_bytes();
|
|
||||||
// row.col(|ui| {
|
|
||||||
// ui.label(
|
|
||||||
// egui::RichText::new(String::from_utf8_lossy(&ascii))
|
|
||||||
// .font(egui::FontId::monospace(12.0))
|
|
||||||
// .color(egui::Color32::from_rgb(100, 255, 255)),
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if self.show_instruction {
|
|
||||||
// row.col(|ui| {
|
|
||||||
// ui.label(
|
|
||||||
// egui::RichText::new(format!(
|
|
||||||
// "{:?}",
|
|
||||||
// Instruction(value)
|
|
||||||
// ))
|
|
||||||
// .font(egui::FontId::monospace(12.0))
|
|
||||||
// .color(egui::Color32::from_rgb(255, 100, 255)),
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
pub mod controller;
|
|
||||||
pub mod display;
|
|
||||||
pub mod editor;
|
|
||||||
pub mod framebuffer;
|
|
||||||
pub mod memory;
|
|
||||||
pub mod registers;
|
|
||||||
pub mod serial;
|
|
||||||
pub mod disassembler;
|
|
||||||
|
|
||||||
pub trait Component {
|
|
||||||
/// The tab label shown in the tile header.
|
|
||||||
fn title(&self) -> &str;
|
|
||||||
|
|
||||||
fn visible(&mut self) -> &mut bool;
|
|
||||||
|
|
||||||
/// Draw the component's UI inside the provided `Ui`.
|
|
||||||
fn ui(&mut self, ui: &mut egui::Ui);
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
use crate::ui::ui_helpers::StatusIndicator;
|
|
||||||
use eframe::emath::Vec2;
|
|
||||||
use eframe::epaint::Color32;
|
use eframe::epaint::Color32;
|
||||||
use common::prelude::Register;
|
use common::prelude::Register;
|
||||||
use emulator_core::EmulatorHandle;
|
use emulator_core::EmulatorHandle;
|
||||||
@@ -8,14 +6,12 @@ use crate::ui::ui_helpers::PaddedWidget;
|
|||||||
|
|
||||||
pub struct Controller {
|
pub struct Controller {
|
||||||
emulator: EmulatorHandle,
|
emulator: EmulatorHandle,
|
||||||
visible: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Controller {
|
impl Controller {
|
||||||
pub fn new(state: EmulatorHandle) -> Self {
|
pub fn new(state: EmulatorHandle) -> Self {
|
||||||
Self {
|
Self {
|
||||||
emulator: state,
|
emulator: state,
|
||||||
visible: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,8 +88,5 @@ impl Controller {
|
|||||||
PaddedWidget::label(format!("Freq: {}MHz", mips)).ui(ui);
|
PaddedWidget::label(format!("Freq: {}MHz", mips)).ui(ui);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,78 +1,34 @@
|
|||||||
|
use crate::state::{Action, DataSource};
|
||||||
use crate::ui::controller::Controller;
|
use crate::ui::controller::Controller;
|
||||||
use crate::ui::dialog::error_dialog::ErrorDialog;
|
use crate::ui::dialogs::error_dialog::ErrorDialog;
|
||||||
use crate::ui::panels::about::AboutScreen;
|
use crate::ui::tiles::about::AboutScreen;
|
||||||
use crate::ui::panels::disassembler::BinaryViewer;
|
use crate::ui::tiles::disassembler::BinaryViewer;
|
||||||
use crate::ui::panels::display::Display;
|
use crate::ui::tiles::memory::MemoryInspector;
|
||||||
use crate::ui::panels::framebuffer::FrameBuffer;
|
use crate::ui::tiles::registers::Registers;
|
||||||
use crate::ui::panels::memory::MemoryInspector;
|
use crate::ui::tiles::{CentralTiles, TilePane};
|
||||||
use crate::ui::panels::registers::Registers;
|
use dialogs::confirm_dialog::ConfirmDialog;
|
||||||
use crate::ui::panels::serial::Serial;
|
use dialogs::file_dialog::DsaFileDialog;
|
||||||
use crate::ui::panels::{CentralTiles, TilePane};
|
use dsa_formats::binary_dse::DseExecutable;
|
||||||
use common::formats::binary_dse::DseExecutable;
|
|
||||||
use common::process::TraceableProcess;
|
|
||||||
use dialog::confirm_dialog::ConfirmDialog;
|
|
||||||
use dialog::file_dialog::DsaFileDialog;
|
|
||||||
use eframe::NativeOptions;
|
|
||||||
use egui::{CentralPanel, Panel, Ui};
|
use egui::{CentralPanel, Panel, Ui};
|
||||||
use egui_tiles::TileId;
|
use egui_tiles::TileId;
|
||||||
use emulator_core::EmulatorController;
|
use emulator_core::EmulatorController;
|
||||||
use panels::PanelFactory;
|
use emulator_core::devices::display::Display;
|
||||||
use panels::documentation::Documentation;
|
use emulator_core::devices::serial::Serial;
|
||||||
use panels::editor::Editor;
|
use tiles::PanelFactory;
|
||||||
use process_manager::{ProcessManager, ProcessSpawner};
|
use tiles::documentation::Documentation;
|
||||||
use std::fmt::Debug;
|
use tiles::editor::Editor;
|
||||||
|
use tiles::io_devices::framebuffer::FrameBufferUi;
|
||||||
|
use process_manager::ProcessManager;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
mod dialog;
|
mod dialogs;
|
||||||
mod panels;
|
mod tiles;
|
||||||
mod ui_helpers;
|
mod ui_helpers;
|
||||||
mod controller;
|
mod controller;
|
||||||
pub mod process_manager;
|
pub mod process_manager;
|
||||||
|
pub mod appearance;
|
||||||
pub fn run_app(mut controller: EmulatorController) -> eframe::Result<()> {
|
|
||||||
// initialise emulator
|
|
||||||
controller.run().unwrap();
|
|
||||||
|
|
||||||
eframe::run_native(
|
|
||||||
"Damn Simple Architecture",
|
|
||||||
NativeOptions::default(),
|
|
||||||
Box::new(|cc| Ok(Box::new(DamnSimpleUI::new(cc, controller)))),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
trait FileFormat: Debug + Clone {
|
|
||||||
fn extension(&self) -> &'static str;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileFormat for DseExecutable {
|
|
||||||
fn extension(&self) -> &'static str {
|
|
||||||
"dse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum DataSource {
|
|
||||||
File(PathBuf),
|
|
||||||
Data(Vec<u8>),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Action {
|
|
||||||
None,
|
|
||||||
RequestCloseTab(TileId),
|
|
||||||
CloseTab(TileId),
|
|
||||||
Quit,
|
|
||||||
OpenFile(PathBuf),
|
|
||||||
// OpenCachedFile(Box<dyn FileFormat>),
|
|
||||||
LoadBinary(DataSource),
|
|
||||||
SaveFile(PathBuf, TileId),
|
|
||||||
RequestSaveAs(TileId),
|
|
||||||
ShowError(String),
|
|
||||||
SpawnProcess(ProcessSpawner)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub struct DamnSimpleUI {
|
pub struct DamnSimpleUI {
|
||||||
emulator: EmulatorController,
|
emulator: EmulatorController,
|
||||||
@@ -91,7 +47,7 @@ pub struct DamnSimpleUI {
|
|||||||
|
|
||||||
|
|
||||||
impl eframe::App for DamnSimpleUI {
|
impl eframe::App for DamnSimpleUI {
|
||||||
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
fn ui(&mut self, ui: &mut Ui, frame: &mut eframe::Frame) {
|
||||||
ui.request_repaint_after(Duration::from_millis(16));
|
ui.request_repaint_after(Duration::from_millis(16));
|
||||||
|
|
||||||
if let Some(action) = self.confirm_dialog.show(ui) {
|
if let Some(action) = self.confirm_dialog.show(ui) {
|
||||||
@@ -152,7 +108,7 @@ impl DamnSimpleUI {
|
|||||||
cc: &eframe::CreationContext,
|
cc: &eframe::CreationContext,
|
||||||
mut emulator: EmulatorController,
|
mut emulator: EmulatorController,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
configure_appearance(&cc.egui_ctx);
|
appearance::configure_appearance(&cc.egui_ctx);
|
||||||
let handle = emulator.get_handle().clone();
|
let handle = emulator.get_handle().clone();
|
||||||
Self {
|
Self {
|
||||||
central_tiles: CentralTiles::new(),
|
central_tiles: CentralTiles::new(),
|
||||||
@@ -173,7 +129,7 @@ impl DamnSimpleUI {
|
|||||||
}),
|
}),
|
||||||
PanelFactory::register("Framebuffer", {
|
PanelFactory::register("Framebuffer", {
|
||||||
let handle = handle.clone();
|
let handle = handle.clone();
|
||||||
move || FrameBuffer::new(320, 240, 0x30000, handle.clone())
|
move || FrameBufferUi::new(320, 240, 0x30000, handle.clone())
|
||||||
}),
|
}),
|
||||||
PanelFactory::register("Serial", {
|
PanelFactory::register("Serial", {
|
||||||
let handle = handle.clone();
|
let handle = handle.clone();
|
||||||
@@ -323,47 +279,3 @@ impl DamnSimpleUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn configure_appearance(ctx: &egui::Context) {
|
|
||||||
// configure appearance of UI elements
|
|
||||||
let mut visuals = egui::Visuals::dark();
|
|
||||||
|
|
||||||
// --- your existing settings ---
|
|
||||||
visuals.window_fill = egui::Color32::from_rgb(20, 20, 20);
|
|
||||||
visuals.panel_fill = egui::Color32::from_rgb(20, 20, 20);
|
|
||||||
visuals.widgets.inactive.fg_stroke =
|
|
||||||
egui::Stroke::from((1.0, egui::Color32::from_rgb(255, 255, 255)));
|
|
||||||
visuals.widgets.inactive.bg_stroke =
|
|
||||||
egui::Stroke::from((1.0, egui::Color32::from_rgb(60, 60, 60)));
|
|
||||||
visuals.widgets.inactive.corner_radius = egui::CornerRadius::from(4);
|
|
||||||
visuals.widgets.inactive.bg_fill = egui::Color32::from_rgb(20, 20, 20);
|
|
||||||
visuals.widgets.inactive.weak_bg_fill = egui::Color32::from_rgb(20, 20, 20);
|
|
||||||
visuals.widgets.inactive.expansion = 1.0;
|
|
||||||
|
|
||||||
// tile resize handle
|
|
||||||
visuals.window_stroke = egui::Stroke::new(1.0, egui::Color32::from_rgb(45, 45, 45));
|
|
||||||
visuals.extreme_bg_color = egui::Color32::from_rgb(20, 20, 20);
|
|
||||||
|
|
||||||
ctx.set_visuals(visuals);
|
|
||||||
|
|
||||||
let mut fonts = egui::FontDefinitions::default();
|
|
||||||
fonts.font_data.insert(
|
|
||||||
"JetBrains Mono Nerd Font".to_string(),
|
|
||||||
std::sync::Arc::new(egui::FontData::from_static(include_bytes!(
|
|
||||||
"../../font/JetBrainsMonoNerdFontMono_Regular.ttf",
|
|
||||||
))),
|
|
||||||
);
|
|
||||||
|
|
||||||
fonts
|
|
||||||
.families
|
|
||||||
.entry(egui::FontFamily::Proportional)
|
|
||||||
.or_default()
|
|
||||||
.insert(0, "JetBrains Mono Nerd Font".to_string());
|
|
||||||
|
|
||||||
fonts
|
|
||||||
.families
|
|
||||||
.entry(egui::FontFamily::Monospace)
|
|
||||||
.or_default()
|
|
||||||
.insert(0, "JetBrains Mono Nerd Font".to_string());
|
|
||||||
|
|
||||||
ctx.set_fonts(fonts);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
use std::any::Any;
|
|
||||||
use egui::{Ui, Vec2, WidgetText};
|
|
||||||
use egui_tiles::TileId;
|
|
||||||
use emulator_core::{memory::PhysAddr, EmulatorHandle};
|
|
||||||
use crate::ui::Action;
|
|
||||||
use crate::ui::panels::TilePane;
|
|
||||||
|
|
||||||
pub struct FrameBuffer {
|
|
||||||
width: usize,
|
|
||||||
height: usize,
|
|
||||||
addr: PhysAddr,
|
|
||||||
emulator: EmulatorHandle,
|
|
||||||
pub buffer: Vec<u8>,
|
|
||||||
|
|
||||||
texture: Option<egui::TextureHandle>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FrameBuffer {
|
|
||||||
pub fn new(width: u32, height: u32, addr: PhysAddr, emulator: EmulatorHandle) -> Self {
|
|
||||||
let width = width as usize;
|
|
||||||
let height = height as usize;
|
|
||||||
Self {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
addr,
|
|
||||||
emulator,
|
|
||||||
buffer: vec![0; width * height * 4],
|
|
||||||
texture: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn width(&self) -> usize {
|
|
||||||
self.width
|
|
||||||
}
|
|
||||||
pub fn height(&self) -> usize {
|
|
||||||
self.height
|
|
||||||
}
|
|
||||||
|
|
||||||
fn internal_read(&mut self) -> Vec<u8> {
|
|
||||||
let byte_size = self.width * self.height * 4;
|
|
||||||
self.emulator.mem_read(self.addr, byte_size as u32)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn changed(&mut self) -> bool {
|
|
||||||
let temp = self.internal_read();
|
|
||||||
if temp != self.buffer {
|
|
||||||
self.buffer = temp;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read(&self) -> &[u8] {
|
|
||||||
&self.buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert buffer to RGBA bytes for use with egui/wgpu textures
|
|
||||||
pub fn as_rgba_bytes(&self) -> Vec<u8> {
|
|
||||||
self.buffer
|
|
||||||
.iter()
|
|
||||||
.flat_map(|&pixel| pixel.to_le_bytes())
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TilePane for FrameBuffer {
|
|
||||||
fn title(&self) -> WidgetText {
|
|
||||||
"Framebuffer".into()
|
|
||||||
}
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
|
||||||
let changed = self.changed();
|
|
||||||
|
|
||||||
// get or create the texture
|
|
||||||
let texture = self.texture.get_or_insert_with(|| {
|
|
||||||
ui.load_texture(
|
|
||||||
"framebuffer",
|
|
||||||
egui::ColorImage {
|
|
||||||
size: [self.width, self.height],
|
|
||||||
source_size: Vec2::from([self.width as f32, self.height as f32]),
|
|
||||||
pixels: vec![egui::Color32::BLACK; self.width * self.height],
|
|
||||||
},
|
|
||||||
egui::TextureOptions::NEAREST,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
if changed {
|
|
||||||
let pixels: Vec<egui::Color32> = self
|
|
||||||
.buffer
|
|
||||||
.as_chunks::<4>().0
|
|
||||||
.iter()
|
|
||||||
.map(|&[red, green, blue, alpha]| {
|
|
||||||
egui::Color32::from_rgba_premultiplied(red, green, blue, alpha)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
texture.set(
|
|
||||||
egui::ColorImage {
|
|
||||||
size: [self.width, self.height],
|
|
||||||
source_size: Vec2::from([self.width as f32, self.height as f32]),
|
|
||||||
pixels,
|
|
||||||
},
|
|
||||||
egui::TextureOptions::NEAREST,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// scale to fit available space while preserving aspect ratio
|
|
||||||
let available = ui.available_size();
|
|
||||||
let aspect = self.width as f32 / self.height as f32;
|
|
||||||
let size = if available.x / aspect <= available.y {
|
|
||||||
egui::vec2(available.x, available.x / aspect)
|
|
||||||
} else {
|
|
||||||
egui::vec2(available.y * aspect, available.y)
|
|
||||||
};
|
|
||||||
|
|
||||||
ui.image(egui::load::SizedTexture::new(texture.id(), size));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
use crate::ui::Action;
|
use crate::state::Action;
|
||||||
|
use crate::ui::ui_helpers::text::strip_ansi;
|
||||||
|
use crate::ui::ui_helpers::painted_tab;
|
||||||
use common::process::{ProcessAgent, TraceableProcess};
|
use common::process::{ProcessAgent, TraceableProcess};
|
||||||
use egui::Ui;
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::thread::JoinHandle;
|
use std::thread::JoinHandle;
|
||||||
use crate::ui::ui_helpers::{close_button, painted_tab};
|
|
||||||
use crate::ui::ui_helpers::text::strip_ansi;
|
|
||||||
|
|
||||||
pub type ProcessSpawner = Box<dyn FnOnce(&mut ProcessManager) + Send>;
|
pub type ProcessSpawner = Box<dyn FnOnce(&mut ProcessManager) + Send>;
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
use crate::ui::Action;
|
use crate::state::Action;
|
||||||
use crate::ui::panels::TilePane;
|
use crate::ui::tiles::TilePane;
|
||||||
use eframe::epaint::Direction;
|
use eframe::epaint::Direction;
|
||||||
use egui::{Ui, WidgetText};
|
use egui::{Ui, WidgetText};
|
||||||
use egui_tiles::TileId;
|
use egui_tiles::TileId;
|
||||||
+6
-7
@@ -2,8 +2,8 @@ use std::any::Any;
|
|||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::mpsc;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::sync::mpsc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use egui::{ScrollArea, Ui, WidgetText};
|
use egui::{ScrollArea, Ui, WidgetText};
|
||||||
@@ -11,12 +11,11 @@ use egui_extras::{Size, StripBuilder};
|
|||||||
use egui_file::FileDialog;
|
use egui_file::FileDialog;
|
||||||
use egui_tiles::TileId;
|
use egui_tiles::TileId;
|
||||||
|
|
||||||
use common::formats::binary_dse::DseExecutable;
|
use crate::state::{Action, DataSource};
|
||||||
use disassembler::{disassemble, Disassembly};
|
use crate::ui::tiles::TilePane;
|
||||||
|
use crate::ui::tiles::memory::MemoryViewer;
|
||||||
use crate::ui::panels::TilePane;
|
use disassembler::{Disassembly, disassemble};
|
||||||
use crate::ui::{Action, DataSource};
|
use dsa_formats::binary_dse::DseExecutable;
|
||||||
use crate::ui::panels::memory::MemoryViewer;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Copy)]
|
#[derive(PartialEq, Clone, Copy)]
|
||||||
enum ViewMode {
|
enum ViewMode {
|
||||||
+6
-6
@@ -1,11 +1,11 @@
|
|||||||
use std::default::Default;
|
use crate::state::Action;
|
||||||
use std::any::Any;
|
use crate::ui::tiles::TilePane;
|
||||||
use std::path::Path;
|
use egui::{Ui, WidgetText};
|
||||||
use egui::{Ui, Widget, WidgetText};
|
|
||||||
use egui_commonmark::{CommonMarkCache, CommonMarkViewer};
|
use egui_commonmark::{CommonMarkCache, CommonMarkViewer};
|
||||||
use egui_tiles::TileId;
|
use egui_tiles::TileId;
|
||||||
use crate::ui::Action;
|
use std::any::Any;
|
||||||
use crate::ui::panels::TilePane;
|
use std::default::Default;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
pub struct Documentation {
|
pub struct Documentation {
|
||||||
buffer: Option<String>,
|
buffer: Option<String>,
|
||||||
+18
-17
@@ -1,21 +1,18 @@
|
|||||||
use crate::ui::Action;
|
use crate::state::Action;
|
||||||
use crate::ui::panels::TilePane;
|
use crate::state::DataSource::Data;
|
||||||
use common::process::{ProcessAgent, TraceableProcess};
|
use crate::ui::tiles::TilePane;
|
||||||
|
use crate::ui::tiles::editor::syntax::SyntaxFrom;
|
||||||
|
use crate::ui::ui_helpers::PaddedWidget;
|
||||||
|
use assembler::prelude::Assembler;
|
||||||
|
use common::process::TraceableProcess;
|
||||||
|
use dsa_formats::binary_dse::DseExecutable;
|
||||||
use egui::{Color32, Key, Ui, Widget};
|
use egui::{Color32, Key, Ui, Widget};
|
||||||
use egui_code_editor::{CodeEditor, Completer, Syntax};
|
use egui_code_editor::{CodeEditor, Completer, Syntax};
|
||||||
use egui_tiles::TileId;
|
use egui_tiles::TileId;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Error;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
|
||||||
use assembler::prelude::Assembler;
|
|
||||||
use common::formats::binary_dse::DseExecutable;
|
|
||||||
use theme::THEME;
|
use theme::THEME;
|
||||||
use crate::ui::DataSource::Data;
|
|
||||||
use crate::ui::panels::editor::syntax::SyntaxFrom;
|
|
||||||
use crate::ui::ui_helpers::PaddedWidget;
|
|
||||||
|
|
||||||
mod syntax;
|
mod syntax;
|
||||||
mod theme;
|
mod theme;
|
||||||
@@ -66,7 +63,14 @@ impl Editor {
|
|||||||
&mut self.buffer
|
&mut self.buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_cmd(&self, filename: PathBuf, on_complete: impl FnOnce(DseExecutable) -> Action + Send + 'static) -> Action {
|
pub fn get_build_command(filename: PathBuf) -> impl TraceableProcess {
|
||||||
|
match filename.extension().unwrap().to_str().unwrap() {
|
||||||
|
"dsa" => Assembler::new(filename),
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_build_action(&self, filename: PathBuf, on_complete: impl FnOnce(DseExecutable) -> Action + Send + 'static) -> Action {
|
||||||
Action::SpawnProcess(Box::new(move |pm| {
|
Action::SpawnProcess(Box::new(move |pm| {
|
||||||
let builder = Assembler::new(filename);
|
let builder = Assembler::new(filename);
|
||||||
pm.spawn(
|
pm.spawn(
|
||||||
@@ -75,7 +79,6 @@ impl Editor {
|
|||||||
move |result| match result {
|
move |result| match result {
|
||||||
Ok(bin) => on_complete(bin),
|
Ok(bin) => on_complete(bin),
|
||||||
Err(e) => Action::ShowError(e.to_string()),
|
Err(e) => Action::ShowError(e.to_string()),
|
||||||
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}))
|
}))
|
||||||
@@ -101,7 +104,7 @@ impl TilePane for Editor {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
action = self.build_cmd(
|
action = self.create_build_action(
|
||||||
filename, |res| {
|
filename, |res| {
|
||||||
Action::LoadBinary(Data(res.to_memory_image()))
|
Action::LoadBinary(Data(res.to_memory_image()))
|
||||||
}
|
}
|
||||||
@@ -114,7 +117,7 @@ impl TilePane for Editor {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
action = self.build_cmd(
|
action = self.create_build_action(
|
||||||
filename.clone(), move |res| {
|
filename.clone(), move |res| {
|
||||||
let filename = filename.with_extension("dsb");
|
let filename = filename.with_extension("dsb");
|
||||||
let mut writer = File::create(&filename).unwrap();
|
let mut writer = File::create(&filename).unwrap();
|
||||||
@@ -124,8 +127,6 @@ impl TilePane for Editor {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
PaddedWidget::label(
|
PaddedWidget::label(
|
||||||
self.filename
|
self.filename
|
||||||
.as_ref()
|
.as_ref()
|
||||||
+4
-48
@@ -1,53 +1,9 @@
|
|||||||
use std::any::Any;
|
use crate::state::Action;
|
||||||
|
use crate::ui::tiles::TilePane;
|
||||||
use egui::{Color32, FontId, Ui, Vec2, WidgetText};
|
use egui::{Color32, FontId, Ui, Vec2, WidgetText};
|
||||||
use egui_tiles::TileId;
|
use egui_tiles::TileId;
|
||||||
use emulator_core::{memory::ram::{MemoryBank, Page}, memory::PhysAddr, EmulatorHandle};
|
use emulator_core::devices::display::Display;
|
||||||
use crate::ui::Action;
|
use std::any::Any;
|
||||||
use crate::ui::panels::TilePane;
|
|
||||||
|
|
||||||
pub struct Display {
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
addr: PhysAddr,
|
|
||||||
|
|
||||||
emulator: EmulatorHandle,
|
|
||||||
buffer: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display {
|
|
||||||
pub fn new(width: u32, height: u32, addr: PhysAddr, emulator: EmulatorHandle) -> Self {
|
|
||||||
Self {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
addr,
|
|
||||||
emulator,
|
|
||||||
buffer: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn width(&self) -> usize {
|
|
||||||
self.width as usize
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn height(&self) -> usize {
|
|
||||||
self.height as usize
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn changed(&mut self) -> bool {
|
|
||||||
let temp = self.read();
|
|
||||||
if temp != self.buffer {
|
|
||||||
self.buffer = temp;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read(&mut self) -> Vec<u8> {
|
|
||||||
self.emulator.mem_read(self.addr, self.width * self.height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl TilePane for Display {
|
impl TilePane for Display {
|
||||||
fn title(&self) -> WidgetText {
|
fn title(&self) -> WidgetText {
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
use crate::state::Action;
|
||||||
|
use crate::ui::tiles::TilePane;
|
||||||
|
use egui::{Ui, Vec2, WidgetText};
|
||||||
|
use egui_tiles::TileId;
|
||||||
|
use emulator_core::EmulatorHandle;
|
||||||
|
use emulator_core::devices::framebuffer::FrameBuffer;
|
||||||
|
use emulator_core::memory::PhysAddr;
|
||||||
|
use std::any::Any;
|
||||||
|
|
||||||
|
pub struct FrameBufferUi {
|
||||||
|
device: FrameBuffer,
|
||||||
|
texture: Option<egui::TextureHandle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameBufferUi {
|
||||||
|
pub fn new(width: u32, height: u32, addr: PhysAddr, emulator: EmulatorHandle) -> Self {
|
||||||
|
Self {
|
||||||
|
device: FrameBuffer::new(width, height, addr, emulator),
|
||||||
|
texture: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TilePane for FrameBufferUi {
|
||||||
|
fn title(&self) -> WidgetText {
|
||||||
|
"Framebuffer".into()
|
||||||
|
}
|
||||||
|
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
||||||
|
let frame = self.device.read();
|
||||||
|
let width = self.device.width();
|
||||||
|
let height = self.device.height();
|
||||||
|
|
||||||
|
// get or create the texture
|
||||||
|
let texture = self.texture.get_or_insert_with(|| {
|
||||||
|
ui.load_texture(
|
||||||
|
"framebuffer",
|
||||||
|
egui::ColorImage {
|
||||||
|
size: [width, height],
|
||||||
|
source_size: Vec2::from([width as f32, height as f32]),
|
||||||
|
pixels: vec![egui::Color32::BLACK; width * height],
|
||||||
|
},
|
||||||
|
egui::TextureOptions::NEAREST,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let pixels: Vec<egui::Color32> = frame
|
||||||
|
.as_chunks::<4>().0
|
||||||
|
.iter()
|
||||||
|
.map(|&[red, green, blue, _alpha]| {
|
||||||
|
egui::Color32::from_rgba_premultiplied(red, green, blue, 255)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
texture.set(
|
||||||
|
egui::ColorImage {
|
||||||
|
size: [width, height],
|
||||||
|
source_size: Vec2::from([width as f32, height as f32]),
|
||||||
|
pixels,
|
||||||
|
},
|
||||||
|
egui::TextureOptions::NEAREST,
|
||||||
|
);
|
||||||
|
|
||||||
|
// scale to fit available space while preserving aspect ratio
|
||||||
|
let available = ui.available_size();
|
||||||
|
let aspect = width as f32 / height as f32;
|
||||||
|
let size = if available.x / aspect <= available.y {
|
||||||
|
egui::vec2(available.x, available.x / aspect)
|
||||||
|
} else {
|
||||||
|
egui::vec2(available.y * aspect, available.y)
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.image(egui::load::SizedTexture::new(texture.id(), size));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod display;
|
||||||
|
pub mod framebuffer;
|
||||||
|
pub mod serial;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
use crate::state::Action;
|
||||||
|
use crate::ui::tiles::TilePane;
|
||||||
|
use egui::{Ui, WidgetText};
|
||||||
|
use egui_tiles::TileId;
|
||||||
|
use emulator_core::devices::serial::Serial;
|
||||||
|
use std::any::Any;
|
||||||
|
|
||||||
|
impl TilePane for Serial {
|
||||||
|
fn title(&self) -> WidgetText {
|
||||||
|
"Serial".into()
|
||||||
|
}
|
||||||
|
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
||||||
|
self.update();
|
||||||
|
|
||||||
|
// Text field for input and a send button
|
||||||
|
ui.text_edit_multiline(&mut self.write_buff);
|
||||||
|
if ui.button("Send").clicked() {
|
||||||
|
self.write_buff.push(0 as char);
|
||||||
|
for byte in self.write_buff.bytes() {
|
||||||
|
let _ = self.write_tx.send(byte);
|
||||||
|
}
|
||||||
|
self.write_buff.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display received data
|
||||||
|
ui.label(String::from_utf8(self.read_buff.clone()).unwrap());
|
||||||
|
if ui.button("Clear").clicked() {
|
||||||
|
self.read_buff.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-7
@@ -1,13 +1,12 @@
|
|||||||
use std::any::Any;
|
use crate::state::Action;
|
||||||
use std::num::ParseIntError;
|
use crate::ui::tiles::TilePane;
|
||||||
|
use common::prelude::Instruction;
|
||||||
use egui::{Ui, WidgetText};
|
use egui::{Ui, WidgetText};
|
||||||
use egui_extras::{Column, TableBuilder};
|
use egui_extras::{Column, TableBuilder};
|
||||||
use egui_tiles::{Tile, TileId};
|
use egui_tiles::TileId;
|
||||||
use common::prelude::Instruction;
|
|
||||||
use emulator_core::EmulatorHandle;
|
use emulator_core::EmulatorHandle;
|
||||||
use emulator_core::memory::ram::MemoryBank;
|
use std::any::Any;
|
||||||
use crate::ui::Action;
|
use std::num::ParseIntError;
|
||||||
use crate::ui::panels::TilePane;
|
|
||||||
|
|
||||||
pub struct MemoryInspector {
|
pub struct MemoryInspector {
|
||||||
emulator: EmulatorHandle,
|
emulator: EmulatorHandle,
|
||||||
+6
-8
@@ -2,19 +2,17 @@ pub mod documentation;
|
|||||||
pub mod editor;
|
pub mod editor;
|
||||||
pub mod about;
|
pub mod about;
|
||||||
pub mod disassembler;
|
pub mod disassembler;
|
||||||
pub mod display;
|
|
||||||
pub mod framebuffer;
|
|
||||||
pub mod memory;
|
pub mod memory;
|
||||||
pub mod registers;
|
pub mod registers;
|
||||||
pub mod serial;
|
pub mod io_devices;
|
||||||
|
|
||||||
use std::any::Any;
|
use crate::state::Action;
|
||||||
|
use crate::state::Action::RequestCloseTab;
|
||||||
|
use crate::ui::ui_helpers::painted_tab;
|
||||||
use eframe::epaint::{Color32, CornerRadius};
|
use eframe::epaint::{Color32, CornerRadius};
|
||||||
use egui::{Id, Response, Sense, Stroke, TextStyle, Ui, WidgetInfo, WidgetType};
|
use egui::{Id, Response, Stroke, Ui};
|
||||||
use egui_tiles::{Behavior, Container, SimplificationOptions, TabState, Tile, TileId, Tiles, Tree, UiResponse};
|
use egui_tiles::{Behavior, Container, SimplificationOptions, TabState, Tile, TileId, Tiles, Tree, UiResponse};
|
||||||
use crate::ui::Action;
|
use std::any::Any;
|
||||||
use crate::ui::Action::RequestCloseTab;
|
|
||||||
use crate::ui::ui_helpers::{close_button_at, PaddedWidget, painted_tab};
|
|
||||||
|
|
||||||
/// Anything that can live inside a tab in the central tiled area.
|
/// Anything that can live inside a tab in the central tiled area.
|
||||||
/// `as_any_mut` lets callers reach back into a specific pane's concrete
|
/// `as_any_mut` lets callers reach back into a specific pane's concrete
|
||||||
+5
-5
@@ -1,11 +1,11 @@
|
|||||||
use std::f32;
|
use crate::state::Action;
|
||||||
use std::any::Any;
|
use crate::ui::tiles::TilePane;
|
||||||
|
use common::prelude::Register;
|
||||||
use egui::{Ui, WidgetText};
|
use egui::{Ui, WidgetText};
|
||||||
use egui_tiles::TileId;
|
use egui_tiles::TileId;
|
||||||
use common::prelude::Register;
|
|
||||||
use emulator_core::EmulatorHandle;
|
use emulator_core::EmulatorHandle;
|
||||||
use crate::ui::Action;
|
use std::any::Any;
|
||||||
use crate::ui::panels::TilePane;
|
use std::f32;
|
||||||
|
|
||||||
pub struct Registers {
|
pub struct Registers {
|
||||||
state: EmulatorHandle,
|
state: EmulatorHandle,
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
include print: "./print.dsa"
|
||||||
|
|
||||||
|
|
||||||
dw DISPLAY: 0x30000
|
dw DISPLAY: 0x30000
|
||||||
dw stack: 0x10000
|
dw stack: 0x10000
|
||||||
|
|
||||||
@@ -16,17 +19,25 @@ dw COL_IDX: 0
|
|||||||
// rg3 = y direction (1 = down, 0 = up)
|
// rg3 = y direction (1 = down, 0 = up)
|
||||||
// rg5 = current colour
|
// rg5 = current colour
|
||||||
|
|
||||||
|
db startup: "Starting animation..."
|
||||||
|
|
||||||
_init:
|
_init:
|
||||||
ldw stack, bpr
|
ldw stack, bpr
|
||||||
mov bpr, spr
|
mov bpr, spr
|
||||||
|
|
||||||
start:
|
start:
|
||||||
|
lwi startup, rg0
|
||||||
|
push rg0
|
||||||
|
call print::print
|
||||||
|
pop zero
|
||||||
|
|
||||||
lli 0, rg0
|
lli 0, rg0
|
||||||
lli 0, rg1
|
lli 0, rg1
|
||||||
lli 1, rg2
|
lli 1, rg2
|
||||||
lli 1, rg3
|
lli 1, rg3
|
||||||
lli 0, rg8
|
lli 0, rg8
|
||||||
stw rg8, COL_IDX
|
stw rg8, COL_IDX
|
||||||
|
|
||||||
ldw COL0, rg5
|
ldw COL0, rg5
|
||||||
|
|
||||||
frame_loop:
|
frame_loop:
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user