2021-09-28 23:09:38 +00:00
|
|
|
|
|
|
|
use std::fs;
|
2023-05-15 04:12:38 +00:00
|
|
|
use std::cmp;
|
2021-10-27 04:32:25 +00:00
|
|
|
use std::rc::Rc;
|
|
|
|
use std::cell::RefCell;
|
2023-03-06 04:19:49 +00:00
|
|
|
use std::fmt::Write;
|
2021-09-28 23:09:38 +00:00
|
|
|
|
2022-10-09 16:40:20 +00:00
|
|
|
use crate::info;
|
2023-05-22 06:14:26 +00:00
|
|
|
use crate::error::Error;
|
2023-04-24 01:49:40 +00:00
|
|
|
use crate::clock::ClockTime;
|
2023-06-08 03:44:14 +00:00
|
|
|
use crate::devices::{Address, Addressable, Transmutable, Device, read_beu16};
|
2021-09-28 23:09:38 +00:00
|
|
|
|
|
|
|
|
2023-06-11 06:19:10 +00:00
|
|
|
/// A contiguous block of `Addressable` memory, backed by a `Vec`
|
2021-09-30 06:21:11 +00:00
|
|
|
pub struct MemoryBlock {
|
2021-12-13 20:00:24 +00:00
|
|
|
read_only: bool,
|
|
|
|
contents: Vec<u8>,
|
2021-09-28 23:09:38 +00:00
|
|
|
}
|
|
|
|
|
2021-09-30 06:21:11 +00:00
|
|
|
impl MemoryBlock {
|
|
|
|
pub fn new(contents: Vec<u8>) -> MemoryBlock {
|
|
|
|
MemoryBlock {
|
2021-11-03 22:33:22 +00:00
|
|
|
read_only: false,
|
2021-09-30 06:21:11 +00:00
|
|
|
contents
|
2021-09-28 23:09:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-30 06:21:11 +00:00
|
|
|
pub fn load(filename: &str) -> Result<MemoryBlock, Error> {
|
2021-09-28 23:09:38 +00:00
|
|
|
match fs::read(filename) {
|
2021-09-30 06:21:11 +00:00
|
|
|
Ok(contents) => Ok(MemoryBlock::new(contents)),
|
2023-06-11 00:39:20 +00:00
|
|
|
Err(_) => Err(Error::new(format!("Error reading contents of {}", filename))),
|
2021-09-28 23:09:38 +00:00
|
|
|
}
|
|
|
|
}
|
2021-10-03 16:55:20 +00:00
|
|
|
|
2021-11-09 19:03:57 +00:00
|
|
|
pub fn load_at(&mut self, addr: Address, filename: &str) -> Result<(), Error> {
|
2021-10-03 16:55:20 +00:00
|
|
|
match fs::read(filename) {
|
|
|
|
Ok(contents) => {
|
2023-03-06 04:19:49 +00:00
|
|
|
self.contents[(addr as usize)..(addr as usize) + contents.len()].copy_from_slice(&contents);
|
2021-10-03 16:55:20 +00:00
|
|
|
Ok(())
|
|
|
|
},
|
2023-06-11 00:39:20 +00:00
|
|
|
Err(_) => Err(Error::new(format!("Error reading contents of {}", filename))),
|
2021-10-03 16:55:20 +00:00
|
|
|
}
|
|
|
|
}
|
2021-11-03 22:33:22 +00:00
|
|
|
|
|
|
|
pub fn read_only(&mut self) {
|
|
|
|
self.read_only = true;
|
|
|
|
}
|
2022-09-30 18:24:11 +00:00
|
|
|
|
|
|
|
pub fn resize(&mut self, new_size: usize) {
|
|
|
|
self.contents.resize(new_size, 0);
|
|
|
|
}
|
2021-09-28 23:09:38 +00:00
|
|
|
}
|
|
|
|
|
2021-09-30 06:21:11 +00:00
|
|
|
impl Addressable for MemoryBlock {
|
2023-06-08 02:56:00 +00:00
|
|
|
fn size(&self) -> usize {
|
2021-09-30 06:21:11 +00:00
|
|
|
self.contents.len()
|
|
|
|
}
|
|
|
|
|
2023-04-24 01:49:40 +00:00
|
|
|
fn read(&mut self, _clock: ClockTime, addr: Address, data: &mut [u8]) -> Result<(), Error> {
|
2023-03-06 04:19:49 +00:00
|
|
|
data.copy_from_slice(&self.contents[(addr as usize)..(addr as usize) + data.len()]);
|
2021-10-27 00:33:23 +00:00
|
|
|
Ok(())
|
2021-09-28 23:09:38 +00:00
|
|
|
}
|
|
|
|
|
2023-04-24 01:49:40 +00:00
|
|
|
fn write(&mut self, _clock: ClockTime, addr: Address, data: &[u8]) -> Result<(), Error> {
|
2021-11-03 22:33:22 +00:00
|
|
|
if self.read_only {
|
2023-06-11 00:39:20 +00:00
|
|
|
return Err(Error::breakpoint(format!("Attempt to write to read-only memory at {:x} with data {:?}", addr, data)));
|
2021-11-03 22:33:22 +00:00
|
|
|
}
|
|
|
|
|
2023-03-06 04:19:49 +00:00
|
|
|
self.contents[(addr as usize) .. (addr as usize) + data.len()].copy_from_slice(data);
|
2021-10-06 02:58:22 +00:00
|
|
|
Ok(())
|
2021-09-28 23:09:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-17 17:39:43 +00:00
|
|
|
impl Transmutable for MemoryBlock {
|
|
|
|
fn as_addressable(&mut self) -> Option<&mut dyn Addressable> {
|
|
|
|
Some(self)
|
2021-10-06 23:14:56 +00:00
|
|
|
}
|
|
|
|
}
|
2021-09-28 23:09:38 +00:00
|
|
|
|
|
|
|
|
2023-06-11 06:19:10 +00:00
|
|
|
/// An address adapter that repeats the address space of the subdevice over the given range
|
|
|
|
pub struct AddressRepeater {
|
2023-06-08 03:44:14 +00:00
|
|
|
subdevice: Device,
|
2023-06-11 06:19:10 +00:00
|
|
|
range: Address,
|
2021-11-13 19:39:20 +00:00
|
|
|
}
|
|
|
|
|
2023-06-11 06:19:10 +00:00
|
|
|
impl AddressRepeater {
|
|
|
|
pub fn new(subdevice: Device, range: Address) -> Self {
|
2021-11-13 19:39:20 +00:00
|
|
|
Self {
|
|
|
|
subdevice,
|
2023-06-11 06:19:10 +00:00
|
|
|
range,
|
2021-11-13 19:39:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-11 06:19:10 +00:00
|
|
|
impl Addressable for AddressRepeater {
|
2023-06-08 02:56:00 +00:00
|
|
|
fn size(&self) -> usize {
|
2023-06-11 06:19:10 +00:00
|
|
|
self.range as usize
|
2021-11-13 19:39:20 +00:00
|
|
|
}
|
|
|
|
|
2023-04-24 01:49:40 +00:00
|
|
|
fn read(&mut self, clock: ClockTime, addr: Address, data: &mut [u8]) -> Result<(), Error> {
|
2023-06-11 06:19:10 +00:00
|
|
|
let size = self.subdevice.borrow_mut().as_addressable().unwrap().size() as Address;
|
|
|
|
self.subdevice.borrow_mut().as_addressable().unwrap().read(clock, addr % size, data)
|
2021-11-13 19:39:20 +00:00
|
|
|
}
|
|
|
|
|
2023-04-24 01:49:40 +00:00
|
|
|
fn write(&mut self, clock: ClockTime, addr: Address, data: &[u8]) -> Result<(), Error> {
|
2023-06-11 06:19:10 +00:00
|
|
|
let size = self.subdevice.borrow_mut().as_addressable().unwrap().size() as Address;
|
|
|
|
self.subdevice.borrow_mut().as_addressable().unwrap().write(clock, addr % size, data)
|
2021-11-13 19:39:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-11 06:19:10 +00:00
|
|
|
impl Transmutable for AddressRepeater {
|
2022-10-02 04:01:56 +00:00
|
|
|
fn as_addressable(&mut self) -> Option<&mut dyn Addressable> {
|
|
|
|
Some(self)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-06-11 06:19:10 +00:00
|
|
|
/// An address adapter that uses a closure to translate the address before accessing the subdevice
|
|
|
|
pub struct AddressTranslator {
|
2023-06-08 03:44:14 +00:00
|
|
|
subdevice: Device,
|
2023-06-11 06:19:10 +00:00
|
|
|
size: usize,
|
|
|
|
func: Box<dyn Fn(Address) -> Address>,
|
2022-10-02 04:01:56 +00:00
|
|
|
}
|
|
|
|
|
2023-06-11 06:19:10 +00:00
|
|
|
impl AddressTranslator {
|
|
|
|
pub fn new<F>(subdevice: Device, size: usize, func: F) -> Self
|
|
|
|
where
|
|
|
|
F: Fn(Address) -> Address + 'static
|
|
|
|
{
|
2022-10-02 04:01:56 +00:00
|
|
|
Self {
|
|
|
|
subdevice,
|
2023-06-11 06:19:10 +00:00
|
|
|
size,
|
|
|
|
func: Box::new(func),
|
2022-10-02 04:01:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-11 06:19:10 +00:00
|
|
|
impl Addressable for AddressTranslator {
|
2023-06-08 02:56:00 +00:00
|
|
|
fn size(&self) -> usize {
|
2023-06-11 06:19:10 +00:00
|
|
|
self.size
|
2022-10-02 04:01:56 +00:00
|
|
|
}
|
|
|
|
|
2023-04-24 01:49:40 +00:00
|
|
|
fn read(&mut self, clock: ClockTime, addr: Address, data: &mut [u8]) -> Result<(), Error> {
|
2023-06-11 06:19:10 +00:00
|
|
|
self.subdevice.borrow_mut().as_addressable().unwrap().read(clock, (self.func)(addr), data)
|
2022-10-02 04:01:56 +00:00
|
|
|
}
|
|
|
|
|
2023-04-24 01:49:40 +00:00
|
|
|
fn write(&mut self, clock: ClockTime, addr: Address, data: &[u8]) -> Result<(), Error> {
|
2023-06-11 06:19:10 +00:00
|
|
|
self.subdevice.borrow_mut().as_addressable().unwrap().write(clock, (self.func)(addr), data)
|
2022-10-02 04:01:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-11 06:19:10 +00:00
|
|
|
impl Transmutable for AddressTranslator {
|
2021-11-13 19:39:20 +00:00
|
|
|
fn as_addressable(&mut self) -> Option<&mut dyn Addressable> {
|
|
|
|
Some(self)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-17 17:39:43 +00:00
|
|
|
|
2022-09-10 21:08:01 +00:00
|
|
|
#[derive(Clone)]
|
2021-10-06 23:14:56 +00:00
|
|
|
pub struct Block {
|
2021-09-30 06:21:11 +00:00
|
|
|
pub base: Address,
|
2023-06-08 02:56:00 +00:00
|
|
|
pub size: usize,
|
2023-06-08 03:44:14 +00:00
|
|
|
pub dev: Device,
|
2021-09-30 06:21:11 +00:00
|
|
|
}
|
|
|
|
|
2023-06-11 06:19:10 +00:00
|
|
|
/// A bus-like collection of `Addressable` `Device`s mapped to different address ranges
|
|
|
|
///
|
|
|
|
/// This is the fundamental means of connecting devices together to a CPU implementation.
|
2023-03-06 04:19:49 +00:00
|
|
|
#[derive(Clone, Default)]
|
2021-10-06 23:14:56 +00:00
|
|
|
pub struct Bus {
|
2021-12-13 20:00:24 +00:00
|
|
|
blocks: Vec<Block>,
|
2021-12-27 00:32:15 +00:00
|
|
|
ignore_unmapped: bool,
|
|
|
|
watchers: Vec<Address>,
|
|
|
|
watcher_modified: bool,
|
2021-09-30 06:21:11 +00:00
|
|
|
}
|
|
|
|
|
2021-10-06 23:14:56 +00:00
|
|
|
impl Bus {
|
2021-12-26 00:30:07 +00:00
|
|
|
pub fn set_ignore_unmapped(&mut self, ignore_unmapped: bool) {
|
|
|
|
self.ignore_unmapped = ignore_unmapped;
|
|
|
|
}
|
|
|
|
|
2021-12-13 20:00:24 +00:00
|
|
|
pub fn clear_all_bus_devices(&mut self) {
|
|
|
|
self.blocks.clear();
|
|
|
|
}
|
|
|
|
|
2023-06-08 03:44:14 +00:00
|
|
|
pub fn insert(&mut self, base: Address, dev: Device) {
|
2023-06-08 02:56:00 +00:00
|
|
|
let size = dev.borrow_mut().as_addressable().unwrap().size();
|
|
|
|
let block = Block { base, size, dev };
|
2021-11-23 19:45:11 +00:00
|
|
|
let i = self.blocks.iter().position(|cur| cur.base > block.base).unwrap_or(self.blocks.len());
|
2021-11-18 16:46:41 +00:00
|
|
|
self.blocks.insert(i, block);
|
2021-09-28 23:09:38 +00:00
|
|
|
}
|
|
|
|
|
2023-06-08 03:44:14 +00:00
|
|
|
pub fn get_device_at(&self, addr: Address, count: usize) -> Result<(Device, Address), Error> {
|
2021-10-06 23:14:56 +00:00
|
|
|
for block in &self.blocks {
|
2023-06-08 02:56:00 +00:00
|
|
|
if addr >= block.base && addr < (block.base + block.size as Address) {
|
2021-10-06 23:14:56 +00:00
|
|
|
let relative_addr = addr - block.base;
|
2023-06-08 02:56:00 +00:00
|
|
|
if relative_addr as usize + count <= block.size {
|
2021-10-07 16:41:01 +00:00
|
|
|
return Ok((block.dev.clone(), relative_addr));
|
2021-10-06 23:14:56 +00:00
|
|
|
} else {
|
2023-06-11 00:39:20 +00:00
|
|
|
return Err(Error::new(format!("Error reading address {:#010x}", addr)));
|
2021-10-06 23:14:56 +00:00
|
|
|
}
|
2021-09-28 23:09:38 +00:00
|
|
|
}
|
|
|
|
}
|
2023-06-11 00:39:20 +00:00
|
|
|
Err(Error::new(format!("No segment found at {:#010x}", addr)))
|
2021-09-28 23:09:38 +00:00
|
|
|
}
|
|
|
|
|
2023-04-24 01:49:40 +00:00
|
|
|
pub fn dump_memory(&mut self, clock: ClockTime, mut addr: Address, mut count: Address) {
|
2021-10-07 16:41:01 +00:00
|
|
|
while count > 0 {
|
|
|
|
let mut line = format!("{:#010x}: ", addr);
|
|
|
|
|
|
|
|
let to = if count < 16 { count / 2 } else { 8 };
|
|
|
|
for _ in 0..to {
|
2023-04-24 01:49:40 +00:00
|
|
|
let word = self.read_beu16(clock, addr);
|
2021-10-07 16:41:01 +00:00
|
|
|
if word.is_err() {
|
|
|
|
println!("{}", line);
|
|
|
|
return;
|
|
|
|
}
|
2023-03-06 04:19:49 +00:00
|
|
|
write!(line, "{:#06x} ", word.unwrap()).unwrap();
|
2021-10-07 16:41:01 +00:00
|
|
|
addr += 2;
|
|
|
|
count -= 2;
|
|
|
|
}
|
|
|
|
println!("{}", line);
|
|
|
|
}
|
|
|
|
}
|
2021-12-27 00:32:15 +00:00
|
|
|
|
|
|
|
pub fn add_watcher(&mut self, addr: Address) {
|
|
|
|
self.watchers.push(addr);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn remove_watcher(&mut self, addr: Address) {
|
|
|
|
self.watchers.push(addr);
|
|
|
|
if let Some(index) = self.watchers.iter().position(|a| *a == addr) {
|
|
|
|
self.watchers.remove(index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn check_and_reset_watcher_modified(&mut self) -> bool {
|
|
|
|
let result = self.watcher_modified;
|
|
|
|
self.watcher_modified = false;
|
|
|
|
result
|
|
|
|
}
|
2021-10-07 16:41:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Addressable for Bus {
|
2023-06-08 02:56:00 +00:00
|
|
|
fn size(&self) -> usize {
|
2021-10-06 23:14:56 +00:00
|
|
|
let block = &self.blocks[self.blocks.len() - 1];
|
2023-06-08 02:56:00 +00:00
|
|
|
(block.base as usize) + block.size
|
2021-10-07 16:41:01 +00:00
|
|
|
}
|
|
|
|
|
2023-04-24 01:49:40 +00:00
|
|
|
fn read(&mut self, clock: ClockTime, addr: Address, data: &mut [u8]) -> Result<(), Error> {
|
2021-12-26 00:30:07 +00:00
|
|
|
let (dev, relative_addr) = match self.get_device_at(addr, data.len()) {
|
|
|
|
Ok(result) => result,
|
|
|
|
Err(err) if self.ignore_unmapped => {
|
|
|
|
info!("{:?}", err);
|
|
|
|
return Ok(())
|
|
|
|
},
|
|
|
|
Err(err) => return Err(err),
|
|
|
|
};
|
2023-04-24 01:49:40 +00:00
|
|
|
let result = dev.borrow_mut().as_addressable().unwrap().read(clock, relative_addr, data);
|
2021-10-07 16:41:01 +00:00
|
|
|
result
|
|
|
|
}
|
|
|
|
|
2023-04-24 01:49:40 +00:00
|
|
|
fn write(&mut self, clock: ClockTime, addr: Address, data: &[u8]) -> Result<(), Error> {
|
2023-03-06 04:19:49 +00:00
|
|
|
if self.watchers.iter().any(|a| *a == addr) {
|
2021-12-27 00:32:15 +00:00
|
|
|
println!("watch: writing to address {:#06x} with {:?}", addr, data);
|
|
|
|
self.watcher_modified = true;
|
|
|
|
}
|
|
|
|
|
2021-12-26 00:30:07 +00:00
|
|
|
let (dev, relative_addr) = match self.get_device_at(addr, data.len()) {
|
|
|
|
Ok(result) => result,
|
|
|
|
Err(err) if self.ignore_unmapped => {
|
|
|
|
info!("{:?}", err);
|
|
|
|
return Ok(())
|
|
|
|
},
|
|
|
|
Err(err) => return Err(err),
|
|
|
|
};
|
2023-04-24 01:49:40 +00:00
|
|
|
let result = dev.borrow_mut().as_addressable().unwrap().write(clock, relative_addr, data);
|
2021-10-07 16:41:01 +00:00
|
|
|
result
|
2021-09-30 19:58:11 +00:00
|
|
|
}
|
2021-10-06 02:58:22 +00:00
|
|
|
}
|
2021-09-30 19:58:11 +00:00
|
|
|
|
2023-06-11 06:19:10 +00:00
|
|
|
/// An adapter for limiting the access requests of a device (eg. CPU) on a `Bus` to the address
|
|
|
|
/// and data widths of the device
|
2022-09-10 21:08:01 +00:00
|
|
|
#[derive(Clone)]
|
2021-10-27 04:32:25 +00:00
|
|
|
pub struct BusPort {
|
2021-12-13 20:00:24 +00:00
|
|
|
offset: Address,
|
|
|
|
address_mask: Address,
|
|
|
|
data_width: u8,
|
|
|
|
subdevice: Rc<RefCell<Bus>>,
|
2021-10-27 04:32:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl BusPort {
|
|
|
|
pub fn new(offset: Address, address_bits: u8, data_bits: u8, bus: Rc<RefCell<Bus>>) -> Self {
|
|
|
|
Self {
|
|
|
|
offset,
|
2023-05-15 04:12:38 +00:00
|
|
|
address_mask: (1 << address_bits) - 1,
|
2021-10-27 04:32:25 +00:00
|
|
|
data_width: data_bits / 8,
|
|
|
|
subdevice: bus,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-24 01:49:40 +00:00
|
|
|
pub fn dump_memory(&mut self, clock: ClockTime, addr: Address, count: Address) {
|
|
|
|
self.subdevice.borrow_mut().dump_memory(clock, self.offset + (addr & self.address_mask), count)
|
2021-10-27 04:32:25 +00:00
|
|
|
}
|
2021-12-15 00:06:34 +00:00
|
|
|
|
2023-05-22 06:14:26 +00:00
|
|
|
#[inline]
|
2022-09-11 21:51:58 +00:00
|
|
|
pub fn address_mask(&self) -> Address {
|
|
|
|
self.address_mask
|
|
|
|
}
|
|
|
|
|
2023-05-22 06:14:26 +00:00
|
|
|
#[inline]
|
2021-12-15 00:06:34 +00:00
|
|
|
pub fn data_width(&self) -> u8 {
|
|
|
|
self.data_width
|
|
|
|
}
|
2021-10-27 04:32:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Addressable for BusPort {
|
2023-06-08 02:56:00 +00:00
|
|
|
fn size(&self) -> usize {
|
|
|
|
self.subdevice.borrow().size()
|
2021-10-27 04:32:25 +00:00
|
|
|
}
|
|
|
|
|
2023-04-24 01:49:40 +00:00
|
|
|
fn read(&mut self, clock: ClockTime, addr: Address, data: &mut [u8]) -> Result<(), Error> {
|
2021-10-27 04:32:25 +00:00
|
|
|
let addr = self.offset + (addr & self.address_mask);
|
|
|
|
let mut subdevice = self.subdevice.borrow_mut();
|
|
|
|
for i in (0..data.len()).step_by(self.data_width as usize) {
|
2023-05-15 04:12:38 +00:00
|
|
|
let addr_index = (addr + i as Address) & self.address_mask;
|
|
|
|
let end = cmp::min(i + self.data_width as usize, data.len());
|
|
|
|
subdevice.read(clock, addr_index, &mut data[i..end])?;
|
2021-10-27 04:32:25 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-04-24 01:49:40 +00:00
|
|
|
fn write(&mut self, clock: ClockTime, addr: Address, data: &[u8]) -> Result<(), Error> {
|
2021-10-27 04:32:25 +00:00
|
|
|
let addr = self.offset + (addr & self.address_mask);
|
|
|
|
let mut subdevice = self.subdevice.borrow_mut();
|
|
|
|
for i in (0..data.len()).step_by(self.data_width as usize) {
|
2023-05-15 04:12:38 +00:00
|
|
|
let addr_index = (addr + i as Address) & self.address_mask;
|
2023-06-11 06:19:10 +00:00
|
|
|
let end = cmp::min(i + self.data_width as usize, data.len());
|
2023-05-15 04:12:38 +00:00
|
|
|
subdevice.write(clock, addr_index, &data[i..end])?;
|
2021-10-27 04:32:25 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-04 21:55:58 +00:00
|
|
|
pub fn dump_slice(data: &[u8], mut count: usize) {
|
|
|
|
let mut addr = 0;
|
|
|
|
while count > 0 {
|
|
|
|
let mut line = format!("{:#010x}: ", addr);
|
|
|
|
|
|
|
|
let to = if count < 16 { count / 2 } else { 8 };
|
|
|
|
for _ in 0..to {
|
|
|
|
let word = read_beu16(&data[addr..]);
|
2023-03-06 04:19:49 +00:00
|
|
|
write!(line, "{:#06x} ", word).unwrap();
|
2021-12-04 21:55:58 +00:00
|
|
|
addr += 2;
|
|
|
|
count -= 2;
|
|
|
|
}
|
|
|
|
println!("{}", line);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|