diff --git a/emulator/Cargo.toml b/emulator/Cargo.toml index f4cf832..fb37322 100644 --- a/emulator/Cargo.toml +++ b/emulator/Cargo.toml @@ -7,7 +7,7 @@ default-run = "emulator" [lib] name = "dsa_rs" path = "src/lib.rs" -type = "cdylib" +crate-type = ["cdylib", "rlib"] [[bin]] name = "emulator" @@ -25,17 +25,16 @@ toml = { version = "0.8.23", optional = true } serde = { version = "1.0.219", features = ["derive"], optional = true } egui_file = "0.22.1" +[features] +default = ["config"] +discord-rpc = ["dep:discord-presence"] +config = ["dep:toml", "dep:serde"] + # Add support for Android for the fun of it. [target.'cfg(target_os = "android")'.dependencies] winit = { version = "0.30.11", features = ["android-native-activity"] } # jni = "0.21.1" - [target.'cfg(target_os = "android")'.dependencies.eframe] version = "0.31.1" features = ["android-native-activity"] - -[features] -default = ["config"] -discord-rpc = ["dep:discord-presence"] -config = ["dep:toml", "dep:serde"] diff --git a/emulator/src/lib.rs b/emulator/src/lib.rs index 5374434..19558c4 100644 --- a/emulator/src/lib.rs +++ b/emulator/src/lib.rs @@ -13,3 +13,117 @@ )] pub mod emulator; + +use std::{ + sync::{ + Arc, + mpsc::{Receiver, Sender}, + }, + thread, +}; + +#[cfg(target_os = "android")] +use winit::platform::android::{EventLoopBuilderExtAndroid, activity::AndroidApp}; + +use crate::emulator::{ + misc::rpc::RpcClient, + system::{ + emulator::run_emulator, + memory::MainStore, + model::{Command, State}, + processor::Processor, + }, + ui::{ + control_unit::ControlPanel, display::Display, editor::Editor, + interface::EmulatorUI, memory_inspector::MemoryInspector, + stack_inspector::StackInspector, + }, +}; + +#[cfg(target_os = "android")] +#[unsafe(no_mangle)] +pub fn android_main(app: AndroidApp) -> Result<(), Box> { + use crate::emulator::{config::Config, misc::rpc::get_rpc_client_or_none}; + use std::path::Path; + + // Initialize channels and read in configuration. + let (cmd_sender, cmd_receiver) = std::sync::mpsc::channel(); + let (state_sender, state_reciever) = std::sync::mpsc::channel(); + let config = Config::load(Path::new(".dsa.emulator.toml"))?; + + // Setup RPC if enabled. + let (rpc_sender, rpc_reciever) = std::sync::mpsc::channel(); + + let rpc_client = + get_rpc_client_or_none(&config, rpc_sender, rpc_reciever)?.map(Arc::new); + + setup_emulator(cmd_receiver, state_sender, rpc_client); + + let ui = setup_ui(cmd_sender, state_reciever); + + // Run UI. + #[allow(unused_variables)] + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default().with_inner_size([800.0, 600.0]), + event_loop_builder: Some(Box::new(move |builder| { + #[cfg(target_os = "android")] + builder.with_android_app(app); + })), + ..Default::default() + }; + + eframe::run_native( + "DSA Simulator (Damn Simple Architecture 🔥)", + options, + Box::new(move |cc| { + cc.egui_ctx.set_visuals(egui::Visuals::default()); + Ok(Box::new(ui)) + }), + )?; + + Ok(()) +} + +pub fn setup_emulator( + cmd_receiver: Receiver, + state_sender: Sender, + rpc_client: Option>, +) { + let main_store = MainStore::new(); + let processor = Processor::new(Box::new(main_store), vec![]); + + thread::spawn(move || { + run_emulator(&cmd_receiver, &state_sender, processor, rpc_client.as_ref()); + }); +} + +/// Creates the [`EmulatorUI`]. +#[must_use] +pub fn setup_ui( + cmd_sender: Sender, + state_reciever: Receiver, +) -> EmulatorUI { + let mut ui = EmulatorUI::new(cmd_sender.clone(), state_reciever); + + // Create UI modules. + let control_unit = ControlPanel::new(cmd_sender.clone()); + + ui.add_component(Box::new(control_unit)); + + let mem_inspector = MemoryInspector::new(cmd_sender.clone()); + ui.add_component(Box::new(mem_inspector)); + + let stack_inspector = StackInspector::new(); + ui.add_component(Box::new(stack_inspector)); + + let editor = Editor::new(cmd_sender); + ui.add_component(Box::new(editor)); + + let display = Display::new(); + ui.add_component(Box::new(display)); + + let history = emulator::ui::history::History::new(); + ui.add_component(Box::new(history)); + + ui +} diff --git a/emulator/src/main.rs b/emulator/src/main.rs index e5cff51..b074df9 100644 --- a/emulator/src/main.rs +++ b/emulator/src/main.rs @@ -1,26 +1,7 @@ +use std::path::Path; use std::sync::Arc; -use std::{ - path::Path, - sync::mpsc::{Receiver, Sender}, - thread, -}; -use dsa_rs::emulator::misc::rpc::{RpcClient, get_rpc_client_or_none}; - -use dsa_rs::emulator::{ - config::Config, - system::{ - emulator::run_emulator, - memory::MainStore, - model::{Command, State}, - processor::Processor, - }, - ui::{ - control_unit::ControlPanel, display::Display, editor::Editor, - interface::EmulatorUI, memory_inspector::MemoryInspector, - stack_inspector::StackInspector, - }, -}; +use dsa_rs::emulator::{config::Config, misc::rpc::get_rpc_client_or_none}; fn main() -> Result<(), Box> { // Initialize channels and read in configuration. @@ -34,9 +15,9 @@ fn main() -> Result<(), Box> { let rpc_client = get_rpc_client_or_none(&config, rpc_sender, rpc_reciever)?.map(Arc::new); - setup_emulator(cmd_receiver, state_sender, rpc_client); + dsa_rs::setup_emulator(cmd_receiver, state_sender, rpc_client); - let ui = setup_ui(cmd_sender, state_reciever); + let ui = dsa_rs::setup_ui(cmd_sender, state_reciever); // Run UI. #[allow(unused_variables)] @@ -56,87 +37,3 @@ fn main() -> Result<(), Box> { Ok(()) } - -fn setup_emulator( - cmd_receiver: Receiver, - state_sender: Sender, - rpc_client: Option>, -) { - let main_store = MainStore::new(); - let processor = Processor::new(Box::new(main_store), vec![]); - - thread::spawn(move || { - run_emulator(&cmd_receiver, &state_sender, processor, rpc_client.as_ref()); - }); -} - -/// Creates the [`EmulatorUI`]. -fn setup_ui(cmd_sender: Sender, state_reciever: Receiver) -> EmulatorUI { - let mut ui = EmulatorUI::new(cmd_sender.clone(), state_reciever); - - // Create UI modules. - let control_unit = ControlPanel::new(cmd_sender.clone()); - - ui.add_component(Box::new(control_unit)); - - let mem_inspector = MemoryInspector::new(cmd_sender.clone()); - ui.add_component(Box::new(mem_inspector)); - - let stack_inspector = StackInspector::new(); - ui.add_component(Box::new(stack_inspector)); - - let editor = Editor::new(cmd_sender.clone()); - ui.add_component(Box::new(editor)); - - let display = Display::new(); - ui.add_component(Box::new(display)); - - let history = dsa_rs::emulator::ui::history::History::new(); - ui.add_component(Box::new(history)); - - ui -} - -#[cfg(target_os = "android")] -use winit::platform::android::{EventLoopBuilderExtAndroid, activity::AndroidApp}; - -#[cfg(target_os = "android")] -#[unsafe(no_mangle)] -fn android_main(app: AndroidApp) -> Result<(), Box> { - // Initialize channels and read in configuration. - let (cmd_sender, cmd_receiver) = std::sync::mpsc::channel(); - let (state_sender, state_reciever) = std::sync::mpsc::channel(); - let config = Config::load(Path::new(".dsa.emulator.toml"))?; - - // Setup RPC if enabled. - let (rpc_sender, rpc_reciever) = std::sync::mpsc::channel(); - - let rpc_client = - get_rpc_client_or_none(&config, rpc_sender, rpc_reciever)?.map(Arc::new); - - setup_emulator(cmd_receiver, state_sender, rpc_client); - - let ui = setup_ui(cmd_sender, state_reciever); - - // Run UI. - #[allow(unused_variables)] - let options = eframe::NativeOptions { - viewport: egui::ViewportBuilder::default().with_inner_size([800.0, 600.0]), - event_loop_builder: Some(Box::new(move |builder| { - #[cfg(target_os = "android")] - builder.with_android_app(app); - })), - ..Default::default() - }; - - eframe::run_native( - "DSA Simulator (Damn Simple Architecture 🔥)", - options, - Box::new(move |cc| { - cc.egui_ctx.set_visuals(egui::Visuals::default()); - Ok(Box::new(ui)) - }), - )?; - - Ok(()) -} diff --git a/emulator/AndroidManifest.xml b/resources/emulator/AndroidManifest.xml similarity index 51% rename from emulator/AndroidManifest.xml rename to resources/emulator/AndroidManifest.xml index 35c7f94..21292bf 100644 --- a/emulator/AndroidManifest.xml +++ b/resources/emulator/AndroidManifest.xml @@ -1,17 +1,28 @@ + + package="com.dsa.emulator" + android:versionCode="1" + android:versionName="1.0"> + + + + android:icon="@mipmap/ic_launcher" + android:hasCode="false" + android:hardwareAccelerated="true"> + android:exported="true" + android:configChanges="orientation|keyboardHidden|screenSize" + android:screenOrientation="unspecified" + android:launchMode="singleInstance"> @@ -21,4 +32,4 @@ - \ No newline at end of file + diff --git a/resources/emulator/AppIcon.png b/resources/emulator/AppIcon.png new file mode 100644 index 0000000..4f58b16 Binary files /dev/null and b/resources/emulator/AppIcon.png differ diff --git a/resources/emulator/build_android.ps1 b/resources/emulator/build_android.ps1 new file mode 100644 index 0000000..1e11e65 --- /dev/null +++ b/resources/emulator/build_android.ps1 @@ -0,0 +1,74 @@ +#!/usr/bin/env pwsh + +$env:ANDROID_HOME = "C:\Users\jacob\AppData\Local\Android\Sdk" +$TOOL_PREFIX = "$env:ANDROID_HOME\build-tools\35.0.1" + +# Only really works on Windows, for aarch64. + +# Create directories +New-Item -ItemType Directory -Force -Path "..\target\apk_build\lib\arm64-v8a" +New-Item -ItemType Directory -Force -Path "..\target\apk_build\res\values" +New-Item -ItemType Directory -Force -Path "..\target\apk_build\res\mipmap-hdpi" +New-Item -ItemType Directory -Force -Path "..\target\apk_build\res\mipmap-mdpi" +New-Item -ItemType Directory -Force -Path "..\target\apk_build\res\mipmap-xhdpi" +New-Item -ItemType Directory -Force -Path "..\target\apk_build\res\mipmap-xxhdpi" + +# Copy the shared library +Copy-Item "..\target\aarch64-linux-android\release\libdsa_rs.so" "..\target\apk_build\lib\arm64-v8a\" + +# Copy the manifest +Copy-Item "..\resources\emulator\AndroidManifest.xml" "..\target\apk_build\AndroidManifest.xml" + +# Copy the icons +Copy-Item "..\resources\emulator\AppIcon.png" "..\target\apk_build\res\mipmap-hdpi\ic_launcher.png" +Copy-Item "..\resources\emulator\AppIcon.png" "..\target\apk_build\res\mipmap-mdpi\ic_launcher.png" +Copy-Item "..\resources\emulator\AppIcon.png" "..\target\apk_build\res\mipmap-xhdpi\ic_launcher.png" +Copy-Item "..\resources\emulator\AppIcon.png" "..\target\apk_build\res\mipmap-xxhdpi\ic_launcher.png" + +# Create strings.xml +@" + + + DSA Emulator + +"@ | Out-File -FilePath "..\target\apk_build\res\values\strings.xml" -Encoding utf8 + +# Change to build directory +Push-Location "..\target\apk_build" + +try { + # Compile resources + & "$TOOL_PREFIX\aapt2.exe" compile --dir res -o compiled_resources.zip + + # Link resources + & "$TOOL_PREFIX\aapt2.exe" link -o unaligned.apk ` + -I "$env:ANDROID_HOME\platforms\android-35\android.jar" ` + --manifest AndroidManifest.xml ` + compiled_resources.zip + + # Add native libraries to APK + & "C:\Program Files\7-Zip\7z.exe" a -tzip unaligned.apk lib\* + + # Align APK + & "$TOOL_PREFIX\zipalign.exe" -v 4 unaligned.apk aligned.apk + + # Generate debug keystore if it doesn't exist + if (-not (Test-Path "debug.keystore")) { + & keytool -genkey -v -keystore debug.keystore -alias androiddebugkey -keyalg RSA -keysize 2048 -validity 10000 -storepass android -keypass android -dname "CN=Android Debug,O=Android,C=US" + } + + # Sign APK + & "$TOOL_PREFIX\apksigner.bat" sign --ks debug.keystore --ks-key-alias androiddebugkey --ks-pass pass:android --key-pass pass:android --out dsa_emulator.apk aligned.apk + + # Copy final APK + Copy-Item "dsa_emulator.apk" "..\dsa_emulator.apk" + + Write-Host "APK created successfully at: ..\target\dsa_emulator.apk" -ForegroundColor Green +} +catch { + Write-Error "Build failed: $_" +} +finally { + # Return to original directory + Pop-Location +} diff --git a/resources/emulator/build_android.sh b/resources/emulator/build_android.sh new file mode 100644 index 0000000..fba7943 --- /dev/null +++ b/resources/emulator/build_android.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +export ANDROID_HOME="/mnt/c/Users/jacob/AppData/Local/Android/Sdk" +export TOOL_PREFIX="$ANDROID_HOME/build-tools/35.0.1" + +# Only really works on Linux, for aarch64. + +mkdir -p ../target/apk_build/lib/arm64-v8a +mkdir -p ../target/apk_build/res/values +mkdir -p ../target/apk_build/res/mipmap-hdpi +mkdir -p ../target/apk_build/res/mipmap-mdpi +mkdir -p ../target/apk_build/res/mipmap-xhdpi +mkdir -p ../target/apk_build/res/mipmap-xxhdpi + +# Copy the shared library. +cp ../target/aarch64-linux-android/release/libdsa_rs.so ../target/apk_build/lib/arm64-v8a/ + +# Copy the manifest. +cp AndroidManifest.xml ../target/apk_build/AndroidManifest.xml + +cat << EOF > ../target/apk_build/res/values/strings.xml + + + DSA Emulator + +EOF + +pushd ../target/apk_build + +$TOOL_PREFIX/aapt2 compile --dir res -o compiled_resources.zip +$TOOL_PREFIX/aapt2 link -o unaligned.apk \ + -I "$ANDROID_HOME/platforms/android-35/android.jar" \ + --manifest AndroidManifest.xml \ + compiled_resources.zip + +zip -r unaligned.apk lib/ + +$TOOL_PREFIX/zipalign -v 4 unaligned.apk aligned.apk + +keytool -genkey -v -keystore debug.keystore -alias androiddebugkey -keyalg RSA -keysize 2048 -validity 10000 -storepass android -keypass android -dname "CN=Android Debug,O=Android,C=US" + +$TOOL_PREFIX/apksigner sign --ks debug.keystore --ks-key-alias androiddebugkey --ks-pass pass:android --key-pass pass:android --out dsa_emulator.apk aligned.apk + +cp dsa_emulator.apk ../dsa_emulator.apk + +popd