about summary refs log tree commit diff
diff options
context:
space:
mode:
authorgennyble <gen@nyble.dev>2025-03-05 05:48:04 -0600
committergennyble <gen@nyble.dev>2025-03-05 05:48:04 -0600
commit557766c22a17d42ac58bd39cda9889f1f6b7f5bc (patch)
tree78ba1a6a78f9c5b74f05c0c96cd4fb59dc260665
downloadnokia3310emu-557766c22a17d42ac58bd39cda9889f1f6b7f5bc.tar.gz
nokia3310emu-557766c22a17d42ac58bd39cda9889f1f6b7f5bc.zip
inititty comitty
-rwxr-xr-x.DS_Storebin0 -> 6148 bytes
-rwxr-xr-x.gitignore4
-rwxr-xr-x.rustfmt.toml1
-rwxr-xr-xCargo.lock325
-rwxr-xr-xCargo.toml12
-rwxr-xr-xMy 3310 NR1 v5.79.flsbin0 -> 2097152 bytes
-rw-r--r--README.md2
-rwxr-xr-xsrc/currentcontroller.rs207
-rwxr-xr-xsrc/main.rs199
-rwxr-xr-xsrc/mem.rs244
10 files changed, 994 insertions, 0 deletions
diff --git a/.DS_Store b/.DS_Store
new file mode 100755
index 0000000..5707b56
--- /dev/null
+++ b/.DS_Store
Binary files differdiff --git a/.gitignore b/.gitignore
new file mode 100755
index 0000000..7fa6f53
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/target
+*.log
+FuBu3310 v6.39 (PPM B).fls
+mapped_rom.dump
\ No newline at end of file
diff --git a/.rustfmt.toml b/.rustfmt.toml
new file mode 100755
index 0000000..218e203
--- /dev/null
+++ b/.rustfmt.toml
@@ -0,0 +1 @@
+hard_tabs = true
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100755
index 0000000..9e550ba
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,325 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "armv4t_emu"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "041e88e25b5fd858fc0091f44a8981aed83d4c29141820b46c9f56f94e42e816"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "colored"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
+dependencies = [
+ "lazy_static",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "deranged"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "libc"
+version = "0.2.170"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
+
+[[package]]
+name = "log"
+version = "0.4.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
+
+[[package]]
+name = "nokia3310emu"
+version = "0.1.0"
+dependencies = [
+ "armv4t_emu",
+ "log",
+ "simple_logger",
+ "time",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[package]]
+name = "num_threads"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.218"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.218"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "simple_logger"
+version = "4.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e7e46c8c90251d47d08b28b8a419ffb4aede0f87c2eea95e17d1d5bacbf3ef1"
+dependencies = [
+ "colored",
+ "log",
+ "time",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.99"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "time"
+version = "0.3.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
+dependencies = [
+ "deranged",
+ "itoa",
+ "libc",
+ "num-conv",
+ "num_threads",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+
+[[package]]
+name = "time-macros"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100755
index 0000000..d1a5e75
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "nokia3310emu"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+armv4t_emu = "0.1.0"
+log = "0.4.20"
+simple_logger = "4.3.3"
+time = "0.3.31"
diff --git a/My 3310 NR1 v5.79.fls b/My 3310 NR1 v5.79.fls
new file mode 100755
index 0000000..ccc7627
--- /dev/null
+++ b/My 3310 NR1 v5.79.fls
Binary files differdiff --git a/README.md b/README.md
new file mode 100644
index 0000000..de7ba18
--- /dev/null
+++ b/README.md
@@ -0,0 +1,2 @@
+"Vintage Firmware Modding Nokia DCT3 Phones"
+https://www.youtube.com/watch?v=_7tkjJ-F95U
\ No newline at end of file
diff --git a/src/currentcontroller.rs b/src/currentcontroller.rs
new file mode 100755
index 0000000..7a39770
--- /dev/null
+++ b/src/currentcontroller.rs
@@ -0,0 +1,207 @@
+use std::time::SystemTime;
+
+use time::OffsetDateTime;
+
+use crate::mem::{GensioState, Mem};
+
+pub struct CurrentController {
+	pub reg: Option<u8>,
+	pub control_reg: u8,
+	//TODO: make enum
+	pub charging_mode: u8,
+	pub tenbit_adc: u16,
+	pub head_hook_detection: u8,
+	//TODO: make enum
+	pub watchdog_setting: u8,
+	pub rtc_enable: u8,
+	//TODO: track time in struct
+	pub alarm_minute: u8,
+	pub alarm_hour: u8,
+	pub rtc_calibration: u8,
+	/// Bit 0 - RTC bat present
+	/// Bit 3/3 - CContINT 3-7
+	pub irq: u8,
+	/// True(1)/False(0)
+	pub irq_mask: u8,
+}
+
+impl CurrentController {
+	pub fn new() -> Self {
+		Self {
+			reg: None,
+			control_reg: 0,
+			charging_mode: 0,
+			tenbit_adc: 0,
+			head_hook_detection: 0,
+			watchdog_setting: 0,
+			rtc_enable: 0,
+			alarm_minute: 0,
+			alarm_hour: 12,
+			rtc_calibration: 0,
+			irq: 0,
+			irq_mask: 0,
+		}
+	}
+
+	pub fn event(&mut self, mem: &mut Mem) {
+		log::trace!("CCont value={:02X}", mem.inner[Mem::CCONT_WRITE]);
+		log::trace!(
+			"CCont write >> 3 value={:02X}",
+			mem.inner[Mem::CCONT_WRITE] >> 3
+		);
+		log::trace!(
+			"CCont read ^ 4 >> 3 value={:02X}",
+			(mem.inner[Mem::CCONT_WRITE] ^ 0x04) >> 3
+		);
+
+		let and_four = mem.inner[Mem::CCONT_WRITE] & 0x04;
+
+		match self.reg {
+			None => {
+				if and_four > 0 {
+					self.read(mem.inner[Mem::CCONT_WRITE], mem);
+				} else {
+					self.reg = Some(mem.inner[Mem::CCONT_WRITE])
+				}
+			}
+			Some(reg) => {
+				self.write(reg, mem);
+				self.reg = None;
+			}
+		}
+	}
+
+	pub fn read(&mut self, addr: u8, mem: &mut Mem) {
+		log::trace!("ccont::read()");
+		let reg = (addr ^ 0x04) >> 3;
+
+		match reg {
+			0x02 => {
+				log::trace!("CCont A/D 0-7 Read");
+				let adc = self.tenbit_adc.to_be_bytes();
+				mem.inner[Mem::CCONT_READ] = adc[0];
+			}
+			0x03 => {
+				log::trace!("CCont Headset/Hooked flag?");
+				mem.inner[Mem::CCONT_READ] = self.head_hook_detection;
+			}
+			0x04 => {
+				log::trace!("CCont A/D 8-9 Read & test bits");
+				let adc = self.tenbit_adc.to_be_bytes();
+				mem.inner[Mem::CCONT_READ] = ((adc[1] >> 6) & 3) | 0b101100_00;
+			}
+			0x09 => {
+				log::trace!("CCont RTC Second");
+				let time = OffsetDateTime::now_local().unwrap();
+				mem.inner[Mem::CCONT_READ] = time.second();
+			}
+			0x0A => {
+				log::trace!("CCont RTC Minute");
+				let time = OffsetDateTime::now_local().unwrap();
+				mem.inner[Mem::CCONT_READ] = time.minute();
+			}
+			0x0B => {
+				log::trace!("CCont RTC Hour");
+				let time = OffsetDateTime::now_local().unwrap();
+				mem.inner[Mem::CCONT_READ] = time.hour();
+			}
+			0x0C => {
+				log::trace!("CCont RTC Day");
+				let time = OffsetDateTime::now_local().unwrap();
+				mem.inner[Mem::CCONT_READ] = time.day();
+			}
+			0x0D => {
+				log::trace!("CCont RTC Alarm Minute");
+				mem.inner[Mem::CCONT_READ] = self.alarm_minute;
+			}
+			0x0E => {
+				log::trace!("CCont RTC Alarm Hour");
+				mem.inner[Mem::CCONT_READ] = self.alarm_hour;
+			}
+			0x0F => {
+				log::trace!("CCont RTC Calibration");
+				mem.inner[Mem::CCONT_READ] = self.rtc_calibration;
+			}
+			0x10 => {
+				log::trace!("CCont RTC Interrupt Controller");
+				mem.inner[Mem::CCONT_READ] = self.irq;
+			}
+			0x11 => {
+				log::trace!("CCont RTC IRQ Mask");
+				mem.inner[Mem::CCONT_READ] = self.irq_mask;
+			}
+			reg => {
+				log::trace!("CCont !!! {reg:02X} unknown read!");
+				panic!()
+			}
+		}
+
+		//mem.gensio_status_clear();
+		mem.set_gensio_status(GensioState::DataRead);
+		//mem.set_gensio_status(GensioState::TransactionReady);
+	}
+
+	pub fn write(&mut self, addr: u8, mem: &mut Mem) {
+		log::trace!("ccont::write()");
+		let reg = addr >> 3;
+		let val = mem.inner[Mem::CCONT_WRITE];
+
+		match reg {
+			0x00 => {
+				log::trace!("CCont control register");
+				self.control_reg = val;
+			}
+			0x01 => {
+				log::trace!("CCont PWM (charger)");
+				self.charging_mode = val;
+			}
+			0x03 => {
+				log::trace!("CCont A/D Read 0-7 (why call write?)");
+				log::info!("Not writing to AD due to confusion on how that works");
+			}
+			0x05 => {
+				log::trace!("CCont Headset/Hooked flag?");
+				self.head_hook_detection = val;
+			}
+			0x06 => {
+				log::trace!("CCont Set Watchdog");
+				self.watchdog_setting = val;
+			}
+			0x07 => {
+				log::trace!("CCont RTC Enable");
+				self.rtc_enable = val;
+			}
+			0x08 => {
+				log::trace!("CCont RTC Second");
+				log::info!("Ignoring RTC Second write due to current RTC implementation")
+			}
+			0x0C => {
+				log::trace!("CCont RTC Day");
+				log::info!("Ignoring RTC Day write due to current RTC implementation")
+			}
+			0x0D => {
+				log::trace!("CCont RTC Alarm Minute");
+				self.alarm_minute = val;
+			}
+			0x0E => {
+				log::trace!("CCont RTC Alarm Hour");
+				self.alarm_hour = val;
+			}
+			0x0F => {
+				log::trace!("CCont RTC Callibration");
+				self.rtc_calibration = val;
+			}
+			0x10 => {
+				log::trace!("CCont RTC Interrupt Controller");
+				self.irq = val;
+			}
+			0x11 => {
+				log::trace!("CCont RTC IRQ Mask");
+				self.irq_mask = val;
+			}
+			reg => {
+				log::trace!("CCont !!! {reg:02X} unknown register")
+			}
+		}
+	}
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100755
index 0000000..1279ceb
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,199 @@
+#![allow(clippy::new_without_default)]
+#![allow(clippy::unusual_byte_groupings)]
+use std::{
+	collections::HashMap,
+	sync::mpsc::TryRecvError,
+	time::{Duration, Instant},
+};
+
+use armv4t_emu::{reg, Cpu, Exception, Memory, Mode};
+
+use crate::mem::{Ctsi, Event, GenSIO, GensioState, Mem, Uif};
+
+mod currentcontroller;
+mod mem;
+
+use currentcontroller::CurrentController;
+
+fn main() {
+	simple_logger::SimpleLogger::new()
+		.with_level(log::LevelFilter::Info)
+		.with_module_level("nokia3310emu::currentcontroller", log::LevelFilter::Trace)
+		//.with_module_level("pc_all", log::LevelFilter::Trace)
+		.with_module_level("eec46_jmp", log::LevelFilter::Trace)
+		.with_module_level("armv4t_emu", log::LevelFilter::Info)
+		.env()
+		.with_colors(true)
+		.init()
+		.unwrap();
+
+	// Channel so the memory controller can tell us when memory mapped
+	// IO is activated
+	let (mut tx, mut rx) = std::sync::mpsc::sync_channel::<Event>(10);
+
+	// Setup Memory
+	let mut memory_array = vec![0; 0x800000];
+	let file = std::fs::read("My 3310 NR1 v5.79.fls").unwrap();
+	//let file = std::fs::read("FuBu3310 v6.39 (PPM B).fls").unwrap();
+
+	memory_array[0x00200000..0x003FFFFF + 1].copy_from_slice(&file);
+
+	let mut mem = Mem {
+		inner: &mut memory_array,
+		tx,
+	};
+
+	// Current Controller (CCont)
+	let mut ccont = CurrentController::new();
+
+	// Setup CPU
+	let mut cpu = Cpu::new();
+	// We don't have a bootloader. Hope everything is okay and jump straight
+	// to where the bootloader does
+	cpu.reg_set(Mode::User, reg::PC, 0x200040);
+
+	let mut hit_eec46 = false;
+	// Main loop
+	let mut last_pc = 0x0;
+	let mut print_all_pc = false;
+	let mut jump_counter = JumpCounter::default();
+
+	let known_jumps = vec![
+		0x002ef9e6, // Delay_r0_loop
+	];
+
+	let start = Instant::now();
+	let run_duration = Duration::from_millis(5000);
+
+	'cpustep: while cpu.step(&mut mem) {
+		// Log jumps to make debugging easier. We can look at these
+		// addresses in Ghidra
+		let pc = cpu.reg_get(Mode::User, reg::PC);
+		if print_all_pc {
+			log::trace!(target: "pc_all", "[{}] {pc:08x}", pc as isize - last_pc as isize);
+		}
+
+		if (pc as isize - last_pc as isize).abs() > 4 {
+			log::trace!("jump detect - {last_pc:08X}  =>  {pc:08X}",);
+			let count = jump_counter.jump(last_pc, pc);
+
+			if count >= 100 && count % 100 == 0 && !known_jumps.contains(&pc) {
+				log::trace!(target: "eec46_jmp", "[{count:<6}] {last_pc:08x}  =>  {pc:08x}");
+			}
+		}
+
+		if pc == 0x002eebec && last_pc != 0x002eebec {
+			log::info!("Entered eebec loop!");
+		}
+
+		if pc == 0x002eec44 {
+			//let r5 = cpu.reg_get(Mode::User, 5);
+			// We set R5 here because the next two instructions are as follows:
+			// [MainLoop?] eec46: cmp r5, 0x1
+			//             eec48: bne [MainLoop?]
+			// So if we do not set r5 to 0x1, we'll loop back to eec46 forever
+			log::warn!("hit eec44... setting register 5!");
+			cpu.reg_set(Mode::User, 5, 0x1);
+		}
+
+		if pc == 0x002eec46 && !hit_eec46 {
+			hit_eec46 = true;
+			print_all_pc = true;
+			log::info!("hit eec46!");
+		} else if pc != 0x002eec48 && pc != 0x002eec46 && hit_eec46 {
+			hit_eec46 = false;
+			print_all_pc = true;
+			log::warn!("broke from eec46! (pc = {pc:x})");
+		}
+		last_pc = pc;
+
+		if pc == 0x002eec52 {
+			log::info!("hit eec52!");
+		}
+
+		match rx.try_recv() {
+			Err(TryRecvError::Empty) => (),
+			Err(TryRecvError::Disconnected) => panic!("event sender disconnected"),
+			Ok(event) => match event {
+				Event::GenSIO(GenSIO::StartTransaction) => {
+					let start_byte = mem.inner[Mem::GENSIO_START_TRANSACTION];
+					if start_byte == 0x25 {
+						mem.gensio_status_clear();
+						mem.set_gensio_status(GensioState::DataWrite);
+						mem.set_gensio_status(GensioState::TransactionReady);
+					}
+				}
+
+				Event::GenSIO(GenSIO::CContWrite) => ccont.event(&mut mem),
+
+				Event::GenSIO(GenSIO::LcdCtrl) => {
+					let dog = mem.inner[Mem::GENSIO_LCD_CTRL];
+					log::info!("GENSIO LCD Mystery {dog:02X} -- {dog:08b}");
+					//mem.inner[Mem::UIF_CTRL_IO_2] = 0b0010_0000;
+				}
+
+				Event::Ctsi(Ctsi::AsicWatchdog) => {
+					let dog = mem.inner[Mem::CTSI_ASIC_WATCHDOG];
+					log::info!("CTSI Clock ={dog}");
+				}
+
+				Event::Ctsi(Ctsi::WriteClockControl) => {
+					let clock = mem.inner[Mem::CTSI_CLOCK_CONTROL];
+					log::info!("CTSI Clock {clock:08b}");
+				}
+
+				Event::Uif(Uif::CtrlIo2) => {
+					let ctrl = mem.inner[Mem::UIF_CTRL_IO_2];
+					log::info!("CTSI Clock {ctrl:08b}");
+				}
+			},
+		}
+
+		if start.elapsed() >= run_duration {
+			log::error!("hit run_duration max runtime. breaking from cpustep loop");
+			break 'cpustep;
+		}
+
+		// Run slow
+		std::thread::sleep(Duration::from_micros(10));
+	}
+
+	println!("cpu halted");
+}
+
+#[derive(Debug, Default)]
+pub struct JumpCounter {
+	jmps: Vec<Jump>,
+}
+
+impl JumpCounter {
+	pub fn jump(&mut self, from: u32, to: u32) -> usize {
+		match self.jmps.iter_mut().find(|j| j.from == from && j.to == to) {
+			None => {
+				self.jmps.push(Jump::new(from, to));
+				1
+			}
+			Some(jmp) => {
+				jmp.count += 1;
+				jmp.count
+			}
+		}
+	}
+}
+
+#[derive(Debug)]
+pub struct Jump {
+	from: u32,
+	to: u32,
+	count: usize,
+}
+
+impl Jump {
+	pub fn new(from: u32, to: u32) -> Self {
+		Self { from, to, count: 1 }
+	}
+}
+
+pub struct Lcd {
+	pub data: [u8; 44 * 84],
+}
diff --git a/src/mem.rs b/src/mem.rs
new file mode 100755
index 0000000..34d16c6
--- /dev/null
+++ b/src/mem.rs
@@ -0,0 +1,244 @@
+use std::sync::mpsc::SyncSender;
+
+use armv4t_emu::Memory;
+
+pub struct Mem<'m> {
+	pub inner: &'m mut [u8],
+	pub tx: SyncSender<Event>,
+}
+
+impl<'m> Memory for Mem<'m> {
+	fn r8(&mut self, addr: u32) -> u8 {
+		self.special_addr(addr, false);
+
+		self.inner[addr as usize]
+	}
+
+	fn r16(&mut self, addr: u32) -> u16 {
+		self.special_addr(addr, false);
+
+		u16::from_be_bytes([self.inner[addr as usize], self.inner[addr as usize + 1]])
+	}
+
+	fn r32(&mut self, addr: u32) -> u32 {
+		self.special_addr(addr, false);
+
+		u32::from_be_bytes([
+			self.inner[addr as usize],
+			self.inner[addr as usize + 1],
+			self.inner[addr as usize + 2],
+			self.inner[addr as usize + 3],
+		])
+	}
+
+	fn w8(&mut self, addr: u32, val: u8) {
+		self.special_addr(addr, true);
+
+		self.inner[addr as usize] = val;
+	}
+
+	fn w16(&mut self, addr: u32, val: u16) {
+		self.special_addr(addr, true);
+
+		let bytes = val.to_be_bytes();
+		self.inner[addr as usize] = bytes[0];
+		self.inner[addr as usize + 1] = bytes[1];
+	}
+
+	fn w32(&mut self, addr: u32, val: u32) {
+		self.special_addr(addr, true);
+
+		let bytes = val.to_be_bytes();
+		self.inner[addr as usize] = bytes[0];
+		self.inner[addr as usize + 1] = bytes[1];
+		self.inner[addr as usize + 2] = bytes[2];
+		self.inner[addr as usize + 3] = bytes[3];
+	}
+}
+
+impl<'m> Mem<'m> {
+	const IO: u32 = 0x00020000;
+	const IO_END: u32 = 0x000200FF;
+
+	pub const CTSI_ASIC_WATCHDOG: usize = Self::IO as usize | 0x03;
+	pub const CTSI_IRQ_LINES: usize = Self::IO as usize | 0x09;
+	pub const CTSI_CLOCK_CONTROL: usize = Self::IO as usize | 0x0D;
+
+	pub const CCONT_WRITE: usize = Self::IO as usize | 0x2C;
+	pub const GENSIO_START_TRANSACTION: usize = Self::IO as usize | 0x2D;
+	pub const UIF_CTRL_IO_2: usize = Self::IO as usize | 0x32;
+	pub const CCONT_READ: usize = Self::IO as usize | 0x6C;
+	pub const GENSIO_STATUS: usize = Self::IO as usize | 0x6D;
+	pub const GENSIO_LCD_CTRL: usize = Self::IO as usize | 0x6E;
+
+	pub fn set_gensio_status(&mut self, state: GensioState) {
+		let byte = &mut self.inner[Self::GENSIO_STATUS];
+
+		match state {
+			GensioState::DataWrite => *byte |= 1,
+			GensioState::TransactionReady => *byte |= 2,
+			GensioState::DataRead => *byte |= 5,
+		}
+	}
+
+	pub fn gensio_status_clear(&mut self) {
+		self.inner[Self::GENSIO_STATUS] = 0x00;
+	}
+
+	fn special_addr(&mut self, addr: u32, write: bool) {
+		if (Self::IO..=Self::IO_END).contains(&addr) {
+			self.special_io(addr, write)
+		}
+	}
+
+	fn special_io(&mut self, addr: u32, write: bool) {
+		let io_addr = addr ^ Self::IO;
+
+		let meaning = match io_addr {
+			// 00-3F
+			0x00 => "ASIC Version",
+			0x01 => "MCU reset control register",
+			0x02 => "DSP reset control register",
+
+			0x03 => {
+				self.tx.send(Event::Ctsi(Ctsi::AsicWatchdog)).unwrap();
+
+				"CTSI ASIC Watchdog Write Register"
+			}
+			0x0A => "CTSI FIQ lines mask",
+			0x0B => "CTSI IRQ lines mask ",
+			0x0D => {
+				self.tx.send(Event::Ctsi(Ctsi::WriteClockControl)).unwrap();
+
+				"CTSI Clock Control Register"
+			}
+			0x20 => "McuGenIO Signal Lines",
+			0x28 => "Keyboard ROW signal lines",
+			0x2A => "Keyboard COL signal lines",
+			0x2C => {
+				self.tx.send(Event::GenSIO(GenSIO::CContWrite)).unwrap();
+
+				"CCont write"
+			}
+			0x2D => {
+				self.tx
+					.send(Event::GenSIO(GenSIO::StartTransaction))
+					.unwrap();
+
+				"GENSIO start transaction"
+			}
+			0x2E => "GENSIO LCD data write",
+			0x32 => {
+				self.tx.send(Event::Uif(Uif::CtrlIo2)).unwrap();
+
+				"UIF CTRL IO 2"
+			}
+			addr if addr >= 0x36 && addr <= 0x3F => "SIM something [0x36 to 0x3F]",
+
+			// 40-7F
+			0x6B => "Keyboard COL interrupt mask",
+			0x6C => "CCont read",
+			0x6D => {
+				self.gensio_status();
+				"GENSIO status"
+			}
+			0x6E => {
+				self.tx.send(Event::GenSIO(GenSIO::LcdCtrl)).unwrap();
+
+				"GENSIO LCD Ctrl"
+			}
+			0x6F => "GENSIO - 3/SELECT1",
+			0x70 => "CTRL I/O Direction 0,1",
+			0x71 => "CTRL I/O Direction 1,1",
+			0x72 => "CTRL I/O Direction 2,1",
+			0x73 => "CTRL I/O Direction 3,1",
+
+			//80-BF
+			0xA8 => "Keyboard ROW I/O direction",
+			0xAD => "GENSIO - 1/SELECT2",
+			0xAE => "GENSIO - 2/SELECT2",
+			0xAF => "GENSIO - 3/SELECT2",
+			0xB0 => "CTRL I/O Direction 0,2",
+			0xB1 => "CTRL I/O Direction 1,2",
+			0xB2 => "CTRL I/O Direction 2,2",
+			0xB3 => "CTRL I/O Direction 3,2",
+
+			//C0-FF
+			0xED => "GENSIO - 1/SELECT3",
+			0xEE => "GENSIO - 2/SELECT3",
+			0xEF => "GENSIO - 3/SELECT3",
+			0xF0 => "CTRL I/O Input 0",
+			0xF1 => "CTRL I/O Input 1",
+			0xF2 => "CTRL I/O Input 2",
+			0xF3 => "CTRL I/O Input 3",
+
+			_ => "unknown IO",
+		};
+
+		let read_write = if write { "w" } else { "r" };
+
+		log::info!("IO {read_write} [{io_addr:02X}] - {meaning}");
+	}
+
+	fn gensio_status(&mut self) {
+		let base = (Self::IO | 0x2D) as usize;
+		let ctrl_io_dir_1 = self.inner[base];
+		log::info!("CTRL Start Transaction -- {:08b}", ctrl_io_dir_1);
+
+		log::info!("GENSIO Status -- {:08b}", self.inner[Self::GENSIO_STATUS]);
+
+		self.print_ctrl_io_direction_1();
+		self.print_ctrl_io_direction_2();
+	}
+
+	fn print_ctrl_io_direction_1(&self) {
+		let base = (Self::IO | 0x70) as usize;
+		let ctrl_io_dir_1 = &self.inner[base..base + 4];
+		log::info!(
+			"CTRL I/O Direction (1) -- {:08b} {:08b} {:08b} {:08b}",
+			ctrl_io_dir_1[0],
+			ctrl_io_dir_1[1],
+			ctrl_io_dir_1[2],
+			ctrl_io_dir_1[3]
+		);
+	}
+
+	fn print_ctrl_io_direction_2(&self) {
+		let base = (Self::IO | 0xB0) as usize;
+		let ctrl_io_dir_1 = &self.inner[base..base + 4];
+		log::info!(
+			"CTRL I/O Direction (2) -- {:08b} {:08b} {:08b} {:08b}",
+			ctrl_io_dir_1[0],
+			ctrl_io_dir_1[1],
+			ctrl_io_dir_1[2],
+			ctrl_io_dir_1[3]
+		);
+	}
+}
+
+pub enum GensioState {
+	DataWrite,
+	TransactionReady,
+	DataRead,
+}
+
+pub enum Event {
+	GenSIO(GenSIO),
+	Ctsi(Ctsi),
+	Uif(Uif),
+}
+
+pub enum GenSIO {
+	StartTransaction,
+	CContWrite,
+	LcdCtrl,
+}
+
+pub enum Ctsi {
+	AsicWatchdog,
+	WriteClockControl,
+}
+
+pub enum Uif {
+	CtrlIo2,
+}