Compare commits
4 Commits
40ea7eae82
...
0eab06620a
Author | SHA1 | Date | |
---|---|---|---|
0eab06620a | |||
b77f096863 | |||
ae2c331d84 | |||
ade0b99b12 |
131
src/backplane.rs
131
src/backplane.rs
@ -15,20 +15,30 @@ use crate::{
|
||||
};
|
||||
|
||||
pub trait DMAHandler: Debug {
|
||||
fn handle<'a>(&mut self, backplane: &'a Backplane, card_accessor: DMACardAccessorBuilder<'a>);
|
||||
fn handle<'a>(&mut self, backplane: &'a Backplane, card_accessor: CardAccessorBuilder<'a>);
|
||||
}
|
||||
|
||||
pub trait MMUHandler: Debug {
|
||||
fn translate_address<'a>(
|
||||
&mut self,
|
||||
backplane: &'a Backplane,
|
||||
card_accessor: CardAccessorBuilder<'a>,
|
||||
address: u32,
|
||||
write: bool,
|
||||
) -> NullableResult<u32, BusError>;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct DMACardAccessorBuilder<'a> {
|
||||
pub struct CardAccessorBuilder<'a> {
|
||||
backplane: &'a Backplane,
|
||||
card_no: usize,
|
||||
}
|
||||
|
||||
impl<'a> DMACardAccessorBuilder<'a> {
|
||||
impl<'a> CardAccessorBuilder<'a> {
|
||||
#[allow(dead_code)]
|
||||
pub fn build<T: Card>(self) -> DMACardAccessor<'a, T> {
|
||||
DMACardAccessor {
|
||||
pub fn build<T: Card>(self) -> CardAccessor<'a, T> {
|
||||
CardAccessor {
|
||||
backplane: self.backplane,
|
||||
card_no: self.card_no,
|
||||
card_type: PhantomData,
|
||||
@ -38,13 +48,13 @@ impl<'a> DMACardAccessorBuilder<'a> {
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct DMACardAccessor<'a, T> {
|
||||
pub struct CardAccessor<'a, T> {
|
||||
backplane: &'a Backplane,
|
||||
card_no: usize,
|
||||
card_type: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Card> DMACardAccessor<'_, T> {
|
||||
impl<T: Card> CardAccessor<'_, T> {
|
||||
#[allow(dead_code)]
|
||||
pub fn get(&self) -> MappedMutexGuard<T> {
|
||||
MutexGuard::map(self.backplane.cards.lock(), |cards| {
|
||||
@ -57,6 +67,7 @@ impl<T: Card> DMACardAccessor<'_, T> {
|
||||
pub struct Backplane {
|
||||
cards: Mutex<Vec<Box<dyn Card>>>,
|
||||
dma_handlers: Mutex<Vec<(usize, Box<dyn DMAHandler>)>>,
|
||||
mmu: Mutex<Option<(usize, Box<dyn MMUHandler>)>>,
|
||||
}
|
||||
|
||||
impl Display for Backplane {
|
||||
@ -81,17 +92,26 @@ impl Backplane {
|
||||
}
|
||||
let mut cards = Vec::new();
|
||||
let mut dma_handlers = Vec::new();
|
||||
let mut mmu = None;
|
||||
for item in card_configs.into_iter().map(|cfg| cfg.into_card()) {
|
||||
let (card, dma_handler) = item?;
|
||||
cards.push(card);
|
||||
let (mut card, dma_handler) = item?;
|
||||
if let Some(dma_handler) = dma_handler {
|
||||
dma_handlers.push((cards.len() - 1, dma_handler));
|
||||
dma_handlers.push((cards.len(), dma_handler));
|
||||
}
|
||||
if let Some(mmu_ret) = card.try_get_mmu() {
|
||||
if mmu.is_some() {
|
||||
panic!("Can't have two MMU cards!");
|
||||
} else {
|
||||
mmu = Some((cards.len(), mmu_ret));
|
||||
}
|
||||
}
|
||||
cards.push(card);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
cards: Mutex::new(cards),
|
||||
dma_handlers: Mutex::new(dma_handlers),
|
||||
mmu: Mutex::new(mmu),
|
||||
})
|
||||
}
|
||||
|
||||
@ -115,6 +135,8 @@ impl Backplane {
|
||||
|card| card.read_word(address),
|
||||
|card| card.read_word_io(address as u8),
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
)?;
|
||||
if !self.dma_handlers.is_locked() {
|
||||
self.handle_dma()
|
||||
@ -128,6 +150,8 @@ impl Backplane {
|
||||
|card| card.read_byte(address),
|
||||
|card| card.read_byte_io(address as u8),
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
)?;
|
||||
if !self.dma_handlers.is_locked() {
|
||||
self.handle_dma()
|
||||
@ -141,6 +165,8 @@ impl Backplane {
|
||||
|card| card.write_word(address, data),
|
||||
|card| card.write_word_io(address as u8, data),
|
||||
(),
|
||||
false,
|
||||
true,
|
||||
)?;
|
||||
if !self.dma_handlers.is_locked() {
|
||||
self.handle_dma()
|
||||
@ -154,6 +180,68 @@ impl Backplane {
|
||||
|card| card.write_byte(address, data),
|
||||
|card| card.write_byte_io(address as u8, data),
|
||||
(),
|
||||
false,
|
||||
true,
|
||||
)?;
|
||||
if !self.dma_handlers.is_locked() {
|
||||
self.handle_dma()
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_word_phys(&self, address: u32) -> Result<u16, BusError> {
|
||||
let data = self.mem_helper(
|
||||
address,
|
||||
|card| card.read_word(address),
|
||||
|card| card.read_word_io(address as u8),
|
||||
0,
|
||||
true,
|
||||
false,
|
||||
)?;
|
||||
if !self.dma_handlers.is_locked() {
|
||||
self.handle_dma()
|
||||
}
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub fn read_byte_phys(&self, address: u32) -> Result<u8, BusError> {
|
||||
let data = self.mem_helper(
|
||||
address,
|
||||
|card| card.read_byte(address),
|
||||
|card| card.read_byte_io(address as u8),
|
||||
0,
|
||||
true,
|
||||
false,
|
||||
)?;
|
||||
if !self.dma_handlers.is_locked() {
|
||||
self.handle_dma()
|
||||
}
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub fn write_word_phys(&self, address: u32, data: u16) -> Result<(), BusError> {
|
||||
self.mem_helper(
|
||||
address,
|
||||
|card| card.write_word(address, data),
|
||||
|card| card.write_word_io(address as u8, data),
|
||||
(),
|
||||
true,
|
||||
true,
|
||||
)?;
|
||||
if !self.dma_handlers.is_locked() {
|
||||
self.handle_dma()
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_byte_phys(&self, address: u32, data: u8) -> Result<(), BusError> {
|
||||
self.mem_helper(
|
||||
address,
|
||||
|card| card.write_byte(address, data),
|
||||
|card| card.write_byte_io(address as u8, data),
|
||||
(),
|
||||
true,
|
||||
true,
|
||||
)?;
|
||||
if !self.dma_handlers.is_locked() {
|
||||
self.handle_dma()
|
||||
@ -167,12 +255,33 @@ impl Backplane {
|
||||
mut mem_func: M,
|
||||
mut io_func: I,
|
||||
io_default: T,
|
||||
bypass_mmu: bool,
|
||||
write: bool,
|
||||
) -> Result<T, BusError>
|
||||
where
|
||||
M: FnMut(&mut Box<dyn Card>) -> NullableResult<T, BusError>,
|
||||
I: FnMut(&mut Box<dyn Card>) -> NullableResult<T, BusError>,
|
||||
T: Copy,
|
||||
{
|
||||
let address = if bypass_mmu {
|
||||
address
|
||||
} else if let Some((card_no, ref mut mmu)) = *self.mmu.lock() {
|
||||
match mmu.translate_address(
|
||||
self,
|
||||
CardAccessorBuilder {
|
||||
backplane: self,
|
||||
card_no,
|
||||
},
|
||||
address,
|
||||
write,
|
||||
) {
|
||||
NullableResult::Ok(address) => address,
|
||||
NullableResult::Err(e) => return Err(e),
|
||||
NullableResult::Null => return Err(BusError),
|
||||
}
|
||||
} else {
|
||||
address
|
||||
};
|
||||
match address {
|
||||
(0..=0x00fe_ffff) | (0x0100_0000..=0xffff_ffff) => self
|
||||
.cards
|
||||
@ -195,7 +304,7 @@ impl Backplane {
|
||||
for handler in self.dma_handlers.lock().iter_mut() {
|
||||
handler.1.handle(
|
||||
self,
|
||||
DMACardAccessorBuilder {
|
||||
CardAccessorBuilder {
|
||||
backplane: self,
|
||||
card_no: handler.0,
|
||||
},
|
||||
|
10
src/card.rs
10
src/card.rs
@ -1,6 +1,9 @@
|
||||
#![allow(clippy::transmute_ptr_to_ref)]
|
||||
|
||||
use crate::{backplane::DMAHandler, m68k::BusError};
|
||||
use crate::{
|
||||
backplane::{DMAHandler, MMUHandler},
|
||||
m68k::BusError,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use mopa::mopafy;
|
||||
use nullable_result::NullableResult;
|
||||
@ -116,7 +119,12 @@ pub trait Card: Debug + Display + mopa::Any {
|
||||
fn cmd(&mut self, _cmd: &[&str]) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset(&mut self) {}
|
||||
|
||||
fn try_get_mmu(&mut self) -> Option<Box<dyn MMUHandler>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
mopafy!(Card);
|
||||
|
10
src/disas.rs
10
src/disas.rs
@ -62,14 +62,14 @@ pub fn disasm<T>(
|
||||
#[derive(Debug)]
|
||||
pub enum DisassemblyError<T> {
|
||||
InvalidInstruction,
|
||||
ReadError(u32, T),
|
||||
ReadError(T),
|
||||
}
|
||||
|
||||
impl<T: Display> Display for DisassemblyError<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::InvalidInstruction => f.write_str("Invalid instruction"),
|
||||
Self::ReadError(addr, e) => f.write_fmt(format_args!("Read error at {} ({})", addr, e)),
|
||||
Self::ReadError(e) => f.write_fmt(format_args!("{}", e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -926,11 +926,9 @@ impl<T> Disasm<'_, T> {
|
||||
}
|
||||
|
||||
fn read_prog_word(&mut self) -> Result<u16, DisassemblyError<T>> {
|
||||
let word = ((self.byte_read)(self.pc)
|
||||
.map_err(|e| DisassemblyError::ReadError(self.pc, e))? as u16)
|
||||
let word = ((self.byte_read)(self.pc).map_err(|e| DisassemblyError::ReadError(e))? as u16)
|
||||
<< 8
|
||||
| ((self.byte_read)(self.pc + 1)
|
||||
.map_err(|e| DisassemblyError::ReadError(self.pc + 1, e))? as u16);
|
||||
| ((self.byte_read)(self.pc + 1).map_err(|e| DisassemblyError::ReadError(e))? as u16);
|
||||
self.pc += 2;
|
||||
Ok(word)
|
||||
}
|
||||
|
540
src/m68k.rs
540
src/m68k.rs
File diff suppressed because it is too large
Load Diff
53
src/main.rs
53
src/main.rs
@ -6,6 +6,7 @@ mod disas;
|
||||
mod instruction;
|
||||
mod location;
|
||||
mod m68k;
|
||||
mod mmu;
|
||||
mod peek;
|
||||
mod ram;
|
||||
mod rom;
|
||||
@ -17,7 +18,7 @@ mod term;
|
||||
use crate::{
|
||||
backplane::Backplane,
|
||||
location::Location,
|
||||
m68k::{BusError, M68K},
|
||||
m68k::{DetailedBusError, M68K},
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use clap::Parser;
|
||||
@ -45,6 +46,7 @@ struct EmuState {
|
||||
cpu: M68K,
|
||||
symbol_tables: SymbolTables,
|
||||
address_breakpoints: IndexSet<u32>,
|
||||
last_peek_format: (peek::Format, peek::Size),
|
||||
}
|
||||
|
||||
/// 68K Backplane Computer Emulator
|
||||
@ -74,11 +76,18 @@ fn main() -> Result<(), anyhow::Error> {
|
||||
cpu: M68K::new(backplane),
|
||||
symbol_tables,
|
||||
address_breakpoints: IndexSet::new(),
|
||||
last_peek_format: (peek::Format::Hex, peek::Size::Byte),
|
||||
};
|
||||
if args.run {
|
||||
let mut out = String::new();
|
||||
while !state.cpu.stopped {
|
||||
state.cpu.step();
|
||||
match state.cpu.step() {
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
println!("{}", e);
|
||||
state.cpu.stopped = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
out += &format!("{}\n", state.cpu);
|
||||
let pc = state.cpu.pc();
|
||||
@ -234,23 +243,30 @@ fn main() -> Result<(), anyhow::Error> {
|
||||
.arg(
|
||||
Arg::new("fmt")
|
||||
.short('f')
|
||||
.required(true)
|
||||
.takes_value(true)
|
||||
.help("The format to print the values in (<fmt><size>)"),
|
||||
)
|
||||
.arg(Arg::new("addr").required(true))
|
||||
.about("Peek a memory address"),
|
||||
|args, state| {
|
||||
let fmt_str = args.get_one::<String>("fmt").unwrap();
|
||||
let (fmt, size) = if let Some(fmt_str) = args.get_one::<String>("fmt") {
|
||||
if fmt_str.len() != 2 {
|
||||
return Err(anyhow!("Peek format length must be 2"));
|
||||
}
|
||||
let fmt = peek::Format::try_from(fmt_str.chars().next().unwrap())?;
|
||||
let size = peek::Size::try_from(fmt_str.chars().nth(1).unwrap())?;
|
||||
state.last_peek_format = (fmt, size);
|
||||
(fmt, size)
|
||||
} else {
|
||||
state.last_peek_format
|
||||
};
|
||||
let count = parse::<u32>(args.get_one::<String>("count").map_or("1", String::as_str))?;
|
||||
let addr = state
|
||||
.symbol_tables
|
||||
.parse_location_address(args.get_one::<String>("addr").unwrap())?;
|
||||
if (size != peek::Size::Byte) & ((addr & 0x1) != 0) {
|
||||
return Err(anyhow!("Cannot peek words from a non-aligned address"));
|
||||
}
|
||||
let mut data = Vec::new();
|
||||
let bus = state.cpu.bus_mut();
|
||||
for i in 0..count {
|
||||
@ -276,6 +292,33 @@ fn main() -> Result<(), anyhow::Error> {
|
||||
))
|
||||
},
|
||||
)
|
||||
.with_command(
|
||||
Command::new("poke")
|
||||
.arg(Arg::new("address").help("The address to write to").required(true))
|
||||
.arg(Arg::new("data").help("The data to write").required(true))
|
||||
.arg(
|
||||
Arg::new("word")
|
||||
.long("word")
|
||||
.short('w')
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Write a word instead of a byte"),
|
||||
)
|
||||
.about("Write to a memory address"),
|
||||
|args, state| {
|
||||
let address = parse::<u32>(args.get_one::<String>("address").unwrap())?;
|
||||
if args.get_flag("word") {
|
||||
if (address & 0x1) != 0 {
|
||||
return Err(anyhow!("Cannot poke a word to a non-aligned address"));
|
||||
}
|
||||
let data = parse::<u16>(args.get_one::<String>("data").unwrap())?;
|
||||
state.cpu.bus_mut().write_word(address, data)?;
|
||||
} else {
|
||||
let data = parse::<u8>(args.get_one::<String>("data").unwrap())?;
|
||||
state.cpu.bus_mut().write_byte(address, data as u8)?;
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
)
|
||||
.with_command(
|
||||
Command::new("disas")
|
||||
.arg(Arg::new("addr").help("Address to start disassembly at. Defaults to current PC"))
|
||||
@ -457,7 +500,7 @@ fn disas_fmt(
|
||||
cpu: &mut M68K,
|
||||
addr: u32,
|
||||
symbol_tables: &SymbolTables,
|
||||
) -> (String, Result<u32, DisassemblyError<BusError>>) {
|
||||
) -> (String, Result<u32, DisassemblyError<DetailedBusError>>) {
|
||||
let addr_fmt = if let Some((table, symbol, offset)) = symbol_tables.address_to_symbol(addr) {
|
||||
format!("{}:{} + {} (0x{:x})", table, symbol, offset, addr)
|
||||
} else {
|
||||
|
125
src/mmu.rs
Normal file
125
src/mmu.rs
Normal file
@ -0,0 +1,125 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use nullable_result::NullableResult;
|
||||
|
||||
use crate::{
|
||||
backplane::{Backplane, CardAccessorBuilder, DMAHandler, MMUHandler},
|
||||
card::{u16_get_be_byte, Card},
|
||||
m68k::BusError,
|
||||
register,
|
||||
};
|
||||
|
||||
const ID: u16 = 5;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct PagingEntry {
|
||||
frame: u32,
|
||||
present: bool,
|
||||
writable: bool,
|
||||
#[allow(unused)]
|
||||
user: bool,
|
||||
}
|
||||
|
||||
impl From<u32> for PagingEntry {
|
||||
fn from(entry: u32) -> Self {
|
||||
Self {
|
||||
frame: entry >> 12,
|
||||
present: entry & 0x1 == 1,
|
||||
writable: entry & 0x2 == 2,
|
||||
user: entry & 0x4 == 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MmuCard {
|
||||
enabled: bool,
|
||||
cache: [Option<PagingEntry>; 4096],
|
||||
map_frames: [Option<u32>; 4],
|
||||
}
|
||||
|
||||
impl Card for MmuCard {
|
||||
fn new(_data: toml::Value) -> anyhow::Result<(Self, Option<Box<dyn DMAHandler>>)> {
|
||||
Ok((
|
||||
Self {
|
||||
enabled: false,
|
||||
cache: [None; 4096],
|
||||
map_frames: [None; 4],
|
||||
},
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
fn try_get_mmu(&mut self) -> Option<Box<dyn MMUHandler>> {
|
||||
Some(Box::new(Mmu))
|
||||
}
|
||||
|
||||
fn read_byte_io(&mut self, address: u8) -> NullableResult<u8, BusError> {
|
||||
match address {
|
||||
(0xFE..=0xFF) => NullableResult::Ok(u16_get_be_byte(ID, address - 0xFE)),
|
||||
_ => NullableResult::Null,
|
||||
}
|
||||
}
|
||||
|
||||
fn cmd(&mut self, cmd: &[&str]) -> anyhow::Result<()> {
|
||||
if cmd[0] == "enable" && cmd.len() >= 2 {
|
||||
self.enabled = cmd[1].parse()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MmuCard {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_fmt(format_args!(
|
||||
"MMU card, {}",
|
||||
if self.enabled { "enabled" } else { "disabled" },
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mmu;
|
||||
|
||||
impl MMUHandler for Mmu {
|
||||
fn translate_address<'a>(
|
||||
&mut self,
|
||||
backplane: &'a Backplane,
|
||||
card_accessor: CardAccessorBuilder<'a>,
|
||||
address: u32,
|
||||
write: bool,
|
||||
) -> NullableResult<u32, BusError> {
|
||||
let card_accessor = card_accessor.build::<MmuCard>();
|
||||
if card_accessor.get().enabled {
|
||||
let page = address >> 12;
|
||||
let offset = address & 0xFFF;
|
||||
let entry = if let Some(entry) = card_accessor.get().cache[page as usize] {
|
||||
entry
|
||||
} else {
|
||||
let map_frame = card_accessor.get().map_frames[(page >> 10) as usize]?;
|
||||
let entry_address = (map_frame << 12) | ((page & 0x3FF) << 2);
|
||||
let entry_hi = backplane.read_word_phys(entry_address)?;
|
||||
let entry_lo = backplane.read_word_phys(entry_address + 2)?;
|
||||
let entry = PagingEntry::from((entry_hi as u32) << 16 | entry_lo as u32);
|
||||
card_accessor.get().cache[page as usize] = Some(entry);
|
||||
entry
|
||||
};
|
||||
if entry.present {
|
||||
if write && !entry.writable {
|
||||
return NullableResult::Err(BusError);
|
||||
}
|
||||
NullableResult::Ok((entry.frame << 12) | offset)
|
||||
} else {
|
||||
NullableResult::Null
|
||||
}
|
||||
} else {
|
||||
NullableResult::Ok(address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
register!(MmuCard, "mmu");
|
@ -1,6 +1,6 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Format {
|
||||
Octal,
|
||||
Hex,
|
||||
@ -47,7 +47,7 @@ impl TryFrom<char> for Format {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Size {
|
||||
Byte,
|
||||
Word,
|
||||
|
@ -26,8 +26,6 @@ pub struct Ram {
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl Ram {}
|
||||
|
||||
impl Card for Ram {
|
||||
fn new(data: Value) -> anyhow::Result<(Self, Option<Box<dyn DMAHandler>>)> {
|
||||
let size = data.try_into::<Config>()?.size;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
backplane::{Backplane, DMACardAccessorBuilder, DMAHandler},
|
||||
backplane::{Backplane, CardAccessorBuilder, DMAHandler},
|
||||
card::{u16_get_be_byte, u32_get_be_byte, u32_set_be_byte, Card},
|
||||
m68k::BusError,
|
||||
register,
|
||||
@ -176,7 +176,7 @@ register!(Storage, "storage");
|
||||
struct Dma;
|
||||
|
||||
impl DMAHandler for Dma {
|
||||
fn handle<'a>(&mut self, backplane: &'a Backplane, card_accessor: DMACardAccessorBuilder<'a>) {
|
||||
fn handle<'a>(&mut self, backplane: &'a Backplane, card_accessor: CardAccessorBuilder<'a>) {
|
||||
let card_accessor = card_accessor.build::<Storage>();
|
||||
if card_accessor.get().transfer {
|
||||
let mut address = card_accessor.get().start_addresss;
|
||||
|
Loading…
Reference in New Issue
Block a user