diff --git a/.github/workflows/ci-mac.yml b/.github/workflows/ci-mac.yml index 576eb5e48..e58c6a023 100644 --- a/.github/workflows/ci-mac.yml +++ b/.github/workflows/ci-mac.yml @@ -15,7 +15,7 @@ on: type: string required: true env: - CMAKE_BUILD_PARALLEL_LEVEL: '3' + CMAKE_BUILD_PARALLEL_LEVEL: "3" jobs: build: name: ${{ inputs.name }} @@ -25,6 +25,10 @@ jobs: QT_URL_BASE: https://download.qt.io/online/qtsdkrepository/mac_x64/desktop/qt6_672/qt.qt6.672.clang_64/6.7.2-0-202406110330qtbase-MacOS-MacOS_13-Clang-MacOS-MacOS_13-X86_64-ARM64.7z QT_URL_SVG: https://download.qt.io/online/qtsdkrepository/mac_x64/desktop/qt6_672/qt.qt6.672.clang_64/6.7.2-0-202406110330qtsvg-MacOS-MacOS_13-Clang-MacOS-MacOS_13-X86_64-ARM64.7z steps: + - name: Set up Python 3.12 (the latest version that is able to build skia-bindings) + uses: actions/setup-python@v4 + with: + python-version: "3.12" - name: Checkout source uses: actions/checkout@v4 - name: Generate cache keys diff --git a/.vscode/launch.json b/.vscode/launch.json index ab6425d1e..82cd0891c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,7 +1,7 @@ { "configurations": [ { - "name": "GUI", + "name": "GUI (Qt)", "type": "lldb", "request": "launch", "args": [ @@ -22,6 +22,24 @@ "program": "${workspaceFolder}/build/gui/obliteration.app/Contents/MacOS/obliteration" } }, + { + "name": "GUI (Slint)", + "type": "lldb", + "request": "launch", + "cargo": { + "args": [ + "build", + "--manifest-path", + "${workspaceFolder}/gui/Cargo.toml", + "--features", + "slint" + ], + "filter": { + "kind": "bin" + } + }, + "cwd": "${workspaceFolder}" + }, { "name": "Kernel", "type": "lldb", @@ -31,10 +49,10 @@ "target create ${workspaceFolder}/build/obkrnl", "target modules load --file ${workspaceFolder}/build/obkrnl -s 0xffffffff82200000" ], - "processCreateCommands": [ - "gdb-remote 1234" - ] + "processCreateCommands": ["gdb-remote 1234"] } ], "version": "2.0.0" } + + diff --git a/CMakeLists.txt b/CMakeLists.txt index e5e75a1d6..5292f75a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,7 @@ else() endif() add_cargo(MANIFEST Cargo.toml) -add_crate(gui) +add_crate(gui LIBRARY ARGS --features "qt") add_crate(obkrnl TOOLCHAIN ${kernel_toolchain} VENDOR "unknown" diff --git a/cmake/cargo.cmake b/cmake/cargo.cmake index 685f09674..bb15baa4e 100644 --- a/cmake/cargo.cmake +++ b/cmake/cargo.cmake @@ -178,6 +178,9 @@ function(add_crate crate) if(${kind} STREQUAL "staticlib") add_library(${crate} STATIC IMPORTED) + + list(APPEND build_args "--lib") + if(WIN32) set(debug_artifact "${debug_outputs}/${crate}.lib") set(release_artifact "${release_outputs}/${crate}.lib") diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index 69e39da68..82b254ac8 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -66,6 +66,10 @@ target_link_libraries(obliteration PRIVATE Qt6::Svg Qt6::Widgets) target_link_libraries(obliteration PRIVATE Threads::Threads) target_link_libraries(obliteration PRIVATE gui) +if(WIN32 OR (UNIX AND NOT APPLE)) + target_link_libraries(obliteration PRIVATE Vulkan::Vulkan) +endif() + if(WIN32) target_link_libraries(obliteration PRIVATE bcrypt imm32 ntdll setupapi userenv version winhvplatform winmm ws2_32) elseif(APPLE) diff --git a/gui/Cargo.toml b/gui/Cargo.toml index 38e717eac..8c9bd4ff1 100644 --- a/gui/Cargo.toml +++ b/gui/Cargo.toml @@ -5,6 +5,20 @@ edition = "2021" [lib] crate-type = ["staticlib"] +required-features = ["qt"] + +[[bin]] +name = "obliteration" +path = "src/main.rs" +required-features = ["slint"] + +[features] +slint = [ + "dep:slint", + "dep:clap", + "dep:raw-window-handle", +] +qt = [] [dependencies] bitfield-struct = "0.8.0" @@ -26,9 +40,18 @@ aarch64 = { path = "../arch/aarch64" } [target.'cfg(target_arch = "x86_64")'.dependencies] x86-64 = { path = "../arch/x86-64" } +raw-window-handle = { version = "0.6", optional = true } +clap = { version = "4.5.20", features = ["derive"], optional = true } + +[dependencies.slint] +git = "https://github.com/slint-ui/slint" +rev = "875ca075fb5b2dfe4c3ab0a499d5759412fc1395" +features = ["compat-1-2", "std", "accessibility", "raw-window-handle-06", "backend-winit", "renderer-skia"] +default-features = false +optional = true [target.'cfg(not(target_os = "macos"))'.dependencies] -ash = "0.38.0" +ash = { version = "0.38.0", features = ["linked", "std"], default-features = false } [target.'cfg(windows)'.dependencies.windows-sys] version = "0.52.0" @@ -49,3 +72,4 @@ objc = "0.2.7" [build-dependencies] cbindgen = "0.26.0" +slint-build = { git = "https://github.com/slint-ui/slint", rev = "875ca075fb5b2dfe4c3ab0a499d5759412fc1395" } diff --git a/gui/build.rs b/gui/build.rs index 0e58b48e7..fd0846963 100644 --- a/gui/build.rs +++ b/gui/build.rs @@ -1,4 +1,5 @@ use cbindgen::{Builder, Config, Language, Style}; +use slint_build::CompilerConfiguration; use std::path::PathBuf; const LINUX_INCLUDE: &str = r#" @@ -8,6 +9,25 @@ const LINUX_INCLUDE: &str = r#" "#; fn main() { + if std::env::var("CARGO_FEATURE_SLINT").is_ok_and(|var| var == "1") { + build_bin(); + } + + if std::env::var("CARGO_FEATURE_QT").is_ok_and(|var| var == "1") { + build_lib(); + } +} + +fn build_bin() { + // Compile main + slint_build::compile_with_config( + "slint/main.slint", + CompilerConfiguration::new().with_style(String::from("fluent-dark")), + ) + .unwrap(); +} + +fn build_lib() { let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); let mut conf = Config::default(); let mut buf = String::new(); diff --git a/gui/main.cpp b/gui/main.cpp index 3cef4fd7f..2e861ebbe 100644 --- a/gui/main.cpp +++ b/gui/main.cpp @@ -166,7 +166,7 @@ int main(int argc, char *argv[]) QMessageBox::critical( nullptr, "Error", - "No any Vulkan device supports Vulkan 1.3."); + "No Vulkan device supports Vulkan 1.3."); return 1; } diff --git a/gui/slint/main.slint b/gui/slint/main.slint new file mode 100644 index 000000000..660cb55c2 --- /dev/null +++ b/gui/slint/main.slint @@ -0,0 +1,63 @@ +import { Game } from "structs.slint"; +import { Tabs } from "main/tabs.slint"; +import { Menu } from "main/menu.slint"; +import { Actions } from "main/actions.slint"; + +export component MainWindow inherits Window { + preferred-width: 1920px; + preferred-height: 1080px; + icon: @image-url("../resources/obliteration-icon.png"); + title: "Obliteration"; + + in property <[Game]> games: []; + in property <[string]> profiles: []; + in property <[string]> devices: []; + + pure callback start_game(int); + + pure callback clear_log(); + pure callback get_log_text() -> string; + + pure callback open_new_issue_link(); + + pure callback install_pkg(); + pure callback open_system_folder(); + pure callback quit(); + + VerticalLayout { + Menu { + background: root.background; + popup_width: root.width / 2; + popup_height: root.height / 2; + popup_x: root.width / 4; + popup_y: root.height / 4; + + quit => { quit(); } + open_new_issue_link => { open_new_issue_link(); } + install_pkg => { install_pkg(); } + open_system_folder => { open_system_folder(); } + } + Tabs { + devices: devices; + games: games; + select_game(index) => { } + } + Actions { + profiles: profiles; + start_game => { + start_game(0) + } + } + } +} + +export component Screen inherits Window {} + +export component ErrorDialog inherits Window { + in property message; + + Text { + padding: 10px; + text: message; + } +} diff --git a/gui/slint/main/actions.slint b/gui/slint/main/actions.slint new file mode 100644 index 000000000..dd2b57ca2 --- /dev/null +++ b/gui/slint/main/actions.slint @@ -0,0 +1,29 @@ +import { ComboBox, Button } from "std-widgets.slint"; + +export component Actions { + in property <[string]> profiles: []; + + pure callback start_game(); + + HorizontalLayout { + + alignment: stretch; + spacing: 5px; + + ComboBox { + model: profiles; + horizontal-stretch: 1; + } + Button { + text: "Start"; + horizontal-stretch: 0; + icon: @image-url("../../resources/play.svg"); + clicked => { start_game(); } + } + Button { + text: "Save"; + horizontal-stretch: 0; + icon: @image-url("../../resources/content-save.svg"); + } + } +} diff --git a/gui/slint/main/menu.slint b/gui/slint/main/menu.slint new file mode 100644 index 000000000..cfb3a342e --- /dev/null +++ b/gui/slint/main/menu.slint @@ -0,0 +1,178 @@ +import { Button, StandardButton, AboutSlint } from "std-widgets.slint"; +export component Menu { + in property background; + in property popup_width; + in property popup_height; + in property popup_x; + in property popup_y; + + pure callback quit(); + pure callback open_new_issue_link(); + pure callback open_system_folder(); + pure callback install_pkg(); + + install_pkg_dialog := PopupWindow { + width: popup_width; + height: popup_height; + x: popup_x; + y: popup_y; + + Dialog { + Text { + text: "This is a dialog box"; + } + StandardButton { kind: ok; } + StandardButton { kind: cancel; } + } + } + + about_dialog := PopupWindow { + width: popup_width; + height: popup_height; + x: popup_x; + y: popup_y; + + Rectangle { + Dialog { + Text { + text: "Obliteration is a free and open-source software for playing your PlayStation 4 titles on PC."; + } + StandardButton { kind: ok; } + } + } + } + + about_slint := PopupWindow { + width: popup_width; + height: popup_height; + x: popup_x; + y: popup_y; + + Rectangle { + Dialog { + AboutSlint {} + StandardButton { kind: ok; } + } + } + } + + logs := PopupWindow { + width: popup_width; + height: popup_height; + x: popup_x; + y: popup_y; + + Rectangle { + Text { + text: "Obliteration is a free and open-source software for playing your PlayStation 4 titles on PC."; + } + } + } + + HorizontalLayout { + alignment: start; + spacing: 5px; + padding-left: 5px; + + VerticalLayout { + TouchArea { + clicked => { files_popup.show(); } + + Rectangle { + Text { + text: "File"; + } + } + } + + files_popup := PopupWindow { + y: parent.y + parent.height; + + Rectangle { + background: background; + + VerticalLayout { + spacing: 0px; + + Button { + text: "Install PKG"; + clicked => { install_pkg(); } + } + Button { + text: "Open System Folder"; + clicked => { open_system_folder(); } + } + Button { + text: "Quit"; + clicked => { quit() } + } + } + } + } + } + VerticalLayout { + TouchArea { + clicked => { view_popup.show(); } + + Rectangle { + Text { + text: "View"; + } + } + } + + view_popup := PopupWindow { + y: parent.y + parent.height; + + Rectangle { + background: background; + + VerticalLayout { + spacing: 0px; + + Button { + text: "Logs"; + clicked => { logs.show(); } + } + } + } + } + } + VerticalLayout { + TouchArea { + clicked => { help_popup.show(); } + + Rectangle { + Text { + text: "Help"; + } + } + } + + help_popup := PopupWindow { + y: parent.y + parent.height; + + Rectangle { + background: background; + + VerticalLayout { + spacing: 0px; + + Button { + text: "Report an Issue"; + clicked => { open_new_issue_link(); } + } + Button { + text: "About Slint"; + clicked => { about_slint.show(); } + } + Button { + text: "About Obliteration"; + clicked => { about_dialog.show(); } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/gui/slint/main/tabs.slint b/gui/slint/main/tabs.slint new file mode 100644 index 000000000..06dcfb063 --- /dev/null +++ b/gui/slint/main/tabs.slint @@ -0,0 +1,43 @@ + +import { TabWidget } from "std-widgets.slint"; +import { DisplayTab } from "tabs/display.slint"; +import { CpuTab } from "tabs/cpu.slint"; +import { GamesTab } from "tabs/games.slint"; +import { Game } from "../structs.slint"; + +export component Tabs { + in property <[string]> devices: []; + in property <[Game]> games: []; + + pure callback select_game(int); + + TabWidget { + Tab { + title: "Display"; + //icon: @image-url("resources/darkmode/card-text-outline.svg"); + VerticalLayout { + alignment: start; + + DisplayTab { devices: devices; } + } + } + Tab { + title: "CPU"; + //icon: @image-url("resources/darkmode/card-text-outline.svg"); + VerticalLayout { + alignment: start; + + CpuTab {} + } + } + Tab { + title: "Games"; + //icon: @image-url("resources/darkmode/view-comfy.png"); + VerticalLayout { + alignment: start; + + GamesTab { games: games; } + } + } + } +} diff --git a/gui/slint/main/tabs/cpu.slint b/gui/slint/main/tabs/cpu.slint new file mode 100644 index 000000000..4e4c3acff --- /dev/null +++ b/gui/slint/main/tabs/cpu.slint @@ -0,0 +1,49 @@ +import { Button, Slider, LineEdit } from "std-widgets.slint"; + +export component CpuTab { + HorizontalLayout { + VerticalLayout { + padding: 10px; + vertical-stretch: 0; + alignment: start; + + Text { + text: "Count"; + } + Slider { + value: 8; + minimum: 1; + maximum: 16; + } + Text { + text: "Changing this value to other than 8 may crash the game."; + } + } + VerticalLayout { + padding: 10px; + vertical-stretch: 0; + alignment: start; + + Text { + text: "Debug"; + } + HorizontalLayout { + spacing: 5px; + Text { + text: "Listen address"; + vertical-alignment: center; + } + LineEdit { + text: "127.0.0.1"; + max-height: 30px; + } + Button { + text: "Start"; + } + } + Text { + text: "Specify a TCP address to listen for a debugger. The kernel will wait for a debugger to connect before start."; + } + } + } +} \ No newline at end of file diff --git a/gui/slint/main/tabs/display.slint b/gui/slint/main/tabs/display.slint new file mode 100644 index 000000000..ed7125456 --- /dev/null +++ b/gui/slint/main/tabs/display.slint @@ -0,0 +1,37 @@ +import { ComboBox } from "std-widgets.slint"; + +export component DisplayTab { + in property <[string]> devices: []; + + HorizontalLayout { + padding: 10px; + spacing: 10px; + VerticalLayout { + vertical-stretch: 0; + spacing: 10px; + alignment: start; + + Text { + text: "Device"; + } + ComboBox { + model: devices; + } + } + VerticalLayout { + VerticalLayout { + vertical-stretch: 0; + spacing: 10px; + alignment: start; + + Text { + text: "Resolution"; + } + ComboBox { + model: ["1280 × 720", "1920 × 1080", "3840 × 2160"]; + current-value: "1920 × 1080"; + } + } + } + } +} \ No newline at end of file diff --git a/gui/slint/main/tabs/games.slint b/gui/slint/main/tabs/games.slint new file mode 100644 index 000000000..187fc5620 --- /dev/null +++ b/gui/slint/main/tabs/games.slint @@ -0,0 +1,57 @@ +import { Game } from "../../structs.slint"; + +export component GamesTab { + in-out property <[Game]> games: []; + + VerticalLayout { + alignment: start; + + HorizontalLayout { + alignment: stretch; + height: 30px; + + Text { + text: "Name"; + horizontal-alignment: center; + } + Text { + text: "ID"; + width: 100px; + horizontal-alignment: center; + } + } + + for game[i] in games: HorizontalLayout { + alignment: stretch; + + HorizontalLayout { + alignment: stretch; + height: 30px; + + Rectangle { + width: 15px; + + Text { + text: i; + horizontal-alignment: center; + vertical-alignment: center; + } + } + Image { + source: game.icon; + width: 30px; + } + Text { + padding-left: 10px; + text: game.name; + vertical-alignment: center; + } + } + Text { + text: game.id; + vertical-alignment: center; + width: 80px; + } + } + } +} diff --git a/gui/slint/structs.slint b/gui/slint/structs.slint new file mode 100644 index 000000000..60c1fd968 --- /dev/null +++ b/gui/slint/structs.slint @@ -0,0 +1,6 @@ +export struct Game { + id: string, + name: string, + dir: string, + icon: image, +} diff --git a/gui/src/args.rs b/gui/src/args.rs new file mode 100644 index 000000000..09947ae56 --- /dev/null +++ b/gui/src/args.rs @@ -0,0 +1,25 @@ +use clap::Parser; +use std::net::SocketAddrV4; +use std::path::Path; + +#[derive(Debug, Parser)] +pub(crate) struct CliArgs { + #[arg(long, help = "Immediate launch the VMM in debug mode.")] + debug: Option, + + #[arg( + long, + help = "Use the kernel image at the specified path instead of the default one." + )] + kernel: Option>, +} + +impl CliArgs { + pub fn debug_addr(&self) -> Option { + self.debug + } + + pub fn kernel_path(&self) -> Option<&Path> { + self.kernel.as_deref() + } +} diff --git a/gui/src/debug/client.rs b/gui/src/debug/client.rs index 255c46646..52218e33d 100644 --- a/gui/src/debug/client.rs +++ b/gui/src/debug/client.rs @@ -2,11 +2,6 @@ use std::io::{Error, ErrorKind, Read, Write}; use std::net::TcpStream; -#[no_mangle] -pub unsafe extern "C" fn debug_client_free(d: *mut DebugClient) { - drop(Box::from_raw(d)); -} - /// Encapsulate a debugger connection. pub struct DebugClient { sock: TcpStream, @@ -75,13 +70,10 @@ impl gdbstub::conn::Connection for DebugClient { while !buf.is_empty() { let written = match Write::write(&mut self.sock, buf) { Ok(v) => v, - Err(e) => { - if matches!(e.kind(), ErrorKind::Interrupted | ErrorKind::WouldBlock) { - continue; - } else { - return Err(e); - } + Err(e) if matches!(e.kind(), ErrorKind::Interrupted | ErrorKind::WouldBlock) => { + continue; } + Err(e) => return Err(e), }; if written == 0 { diff --git a/gui/src/debug/ffi.rs b/gui/src/debug/ffi.rs new file mode 100644 index 000000000..b3b7c0c1d --- /dev/null +++ b/gui/src/debug/ffi.rs @@ -0,0 +1,66 @@ +use super::{DebugClient, DebugServer}; +use crate::error::RustError; +use std::ffi::{c_char, CStr}; +use std::ptr::null_mut; + +#[no_mangle] +pub unsafe extern "C" fn debug_server_start( + addr: *const c_char, + err: *mut *mut RustError, +) -> *mut DebugServer { + // Get address. + let addr = match CStr::from_ptr(addr).to_str() { + Ok(v) => v, + Err(_) => { + *err = RustError::new("the specified address is not UTF-8").into_c(); + return null_mut(); + } + }; + + // Start server. + match DebugServer::new(addr) { + Ok(server) => Box::into_raw(Box::new(server)), + Err(e) => { + *err = RustError::wrap(e).into_c(); + null_mut() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn debug_server_free(s: *mut DebugServer) { + drop(Box::from_raw(s)); +} + +#[no_mangle] +pub unsafe extern "C" fn debug_server_addr(s: *mut DebugServer) -> *const c_char { + (*s).addr.as_ptr() +} + +#[no_mangle] +pub unsafe extern "C" fn debug_server_socket(s: *mut DebugServer) -> isize { + #[cfg(unix)] + return std::os::fd::AsRawFd::as_raw_fd(&(*s).sock) as _; + + #[cfg(windows)] + return std::os::windows::io::AsRawSocket::as_raw_socket(&(*s).sock) as _; +} + +#[no_mangle] +pub unsafe extern "C" fn debug_server_accept( + s: *mut DebugServer, + err: *mut *mut RustError, +) -> *mut DebugClient { + match (*s).accept() { + Ok(client) => Box::into_raw(Box::new(client)), + Err(e) => { + *err = RustError::wrap(e).into_c(); + null_mut() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn debug_client_free(d: *mut DebugClient) { + drop(Box::from_raw(d)); +} diff --git a/gui/src/debug/mod.rs b/gui/src/debug/mod.rs index 0760e6e02..b1c19ca43 100644 --- a/gui/src/debug/mod.rs +++ b/gui/src/debug/mod.rs @@ -1,84 +1,45 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pub use self::client::*; -use crate::error::RustError; -use std::ffi::{c_char, CStr, CString}; -use std::net::TcpListener; -use std::ptr::null_mut; +use std::ffi::CString; +use std::net::{TcpListener, ToSocketAddrs}; +use thiserror::Error; mod client; +#[cfg(feature = "qt")] +mod ffi; -#[no_mangle] -pub unsafe extern "C" fn debug_server_start( - addr: *const c_char, - err: *mut *mut RustError, -) -> *mut DebugServer { - // Get address. - let addr = match CStr::from_ptr(addr).to_str() { - Ok(v) => v, - Err(_) => { - *err = RustError::new("the specified address is not UTF-8").into_c(); - return null_mut(); - } - }; - - // Start server. - let sock = match TcpListener::bind(addr) { - Ok(v) => v, - Err(e) => { - *err = RustError::with_source("couldn't bind to the specified address", e).into_c(); - return null_mut(); - } - }; - - // Get effective address to let the user know. - let addr = match sock.local_addr() { - Ok(v) => CString::new(v.to_string()).unwrap(), - Err(e) => { - *err = RustError::with_source("couldn't get server address", e).into_c(); - return null_mut(); - } - }; - - // Return server object. - Box::into_raw(Box::new(DebugServer { addr, sock })) -} - -#[no_mangle] -pub unsafe extern "C" fn debug_server_free(s: *mut DebugServer) { - drop(Box::from_raw(s)); -} - -#[no_mangle] -pub unsafe extern "C" fn debug_server_addr(s: *mut DebugServer) -> *const c_char { - (*s).addr.as_ptr() +/// TCP listener to accept a debugger connection. +pub struct DebugServer { + addr: CString, + sock: TcpListener, } -#[no_mangle] -pub unsafe extern "C" fn debug_server_socket(s: *mut DebugServer) -> isize { - #[cfg(unix)] - return std::os::fd::AsRawFd::as_raw_fd(&(*s).sock) as _; +impl DebugServer { + pub fn new(addr: impl ToSocketAddrs) -> Result { + let sock = TcpListener::bind(addr).map_err(StartDebugServerError::BindFailed)?; + let addr = sock + .local_addr() + .map_err(StartDebugServerError::GetAddrFailed)?; + + Ok(Self { + addr: CString::new(addr.to_string()).unwrap(), + sock, + }) + } - #[cfg(windows)] - return std::os::windows::io::AsRawSocket::as_raw_socket(&(*s).sock) as _; -} + pub fn accept(&self) -> std::io::Result { + let (sock, _) = self.sock.accept()?; -#[no_mangle] -pub unsafe extern "C" fn debug_server_accept( - s: *mut DebugServer, - err: *mut *mut RustError, -) -> *mut DebugClient { - match (*s).sock.accept() { - Ok((sock, _)) => Box::into_raw(Box::new(DebugClient::new(sock))), - Err(e) => { - *err = RustError::wrap(e).into_c(); - null_mut() - } + Ok(DebugClient::new(sock)) } } -/// TCP listener to accept a debugger connection. -pub struct DebugServer { - addr: CString, - sock: TcpListener, +#[derive(Debug, Error)] +pub enum StartDebugServerError { + #[error("couldn't bind to the specified address")] + BindFailed(#[source] std::io::Error), + + #[error("couldn't get server address")] + GetAddrFailed(#[source] std::io::Error), } diff --git a/gui/src/error/ffi.rs b/gui/src/error/ffi.rs new file mode 100644 index 000000000..e6679ad0a --- /dev/null +++ b/gui/src/error/ffi.rs @@ -0,0 +1,12 @@ +use super::RustError; +use std::ffi::c_char; + +#[no_mangle] +pub unsafe extern "C" fn error_free(e: *mut RustError) { + drop(Box::from_raw(e)); +} + +#[no_mangle] +pub unsafe extern "C" fn error_message(e: *const RustError) -> *const c_char { + (*e).0.as_ptr() +} diff --git a/gui/src/error/mod.rs b/gui/src/error/mod.rs index 380ef68b2..34c2c9b08 100644 --- a/gui/src/error/mod.rs +++ b/gui/src/error/mod.rs @@ -1,17 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use std::error::Error; -use std::ffi::{c_char, CString}; +use std::ffi::CString; use std::fmt::{Display, Write}; -#[no_mangle] -pub unsafe extern "C" fn error_free(e: *mut RustError) { - drop(Box::from_raw(e)); -} - -#[no_mangle] -pub unsafe extern "C" fn error_message(e: *const RustError) -> *const c_char { - (*e).0.as_ptr() -} +#[cfg(feature = "qt")] +mod ffi; /// Error object managed by Rust side. pub struct RustError(CString); diff --git a/gui/src/lib.rs b/gui/src/lib.rs index 830e98f25..8220cba50 100644 --- a/gui/src/lib.rs +++ b/gui/src/lib.rs @@ -11,6 +11,7 @@ mod string; mod system; mod vmm; +#[cfg(feature = "qt")] #[no_mangle] pub unsafe extern "C-unwind" fn set_panic_hook( cx: *mut c_void, diff --git a/gui/src/main.rs b/gui/src/main.rs new file mode 100644 index 000000000..1f3f65bd2 --- /dev/null +++ b/gui/src/main.rs @@ -0,0 +1,177 @@ +use args::CliArgs; +use clap::Parser; +use debug::DebugServer; +use slint::{ComponentHandle, ModelExt, ModelRc, SharedString, VecModel}; +use std::process::{ExitCode, Termination}; +use thiserror::Error; + +mod args; +mod debug; +mod error; +mod param; +mod pkg; +mod profile; +#[cfg(unix)] +mod rlim; +mod screen; +mod string; +mod system; +mod ui; +mod vmm; + +fn main() -> AppExit { + let res = run(); + + AppExit::from(res) +} + +fn run() -> Result<(), ApplicationError> { + #[cfg(unix)] + if let Err(e) = rlim::set_rlimit_nofile() { + ui::ErrorDialog::new() + .and_then(|error_dialog| { + error_dialog.set_message(SharedString::from(format!( + "Error setting rlimit: {}", + full_error_reason(e) + ))); + + error_dialog.run() + }) + .inspect_err(|e| eprintln!("Error displaying error dialog: {e}")) + .unwrap(); + } + + let args = CliArgs::try_parse().map_err(ApplicationError::ParseArgs)?; + + if let Some(debug_addr) = args.debug_addr() { + let debug_server = DebugServer::new(debug_addr) + .map_err(|e| ApplicationError::StartDebugServer(e, debug_addr))?; + + let debug_client = debug_server + .accept() + .map_err(ApplicationError::CreateDebugClient)?; + } + + let app = App::new()?; + + app.run()?; + + Ok(()) +} + +struct App { + main_window: ui::MainWindow, + + games: ModelRc, + profiles: ModelRc, +} + +impl App { + fn new() -> Result { + let main_window = ui::MainWindow::new().map_err(ApplicationError::CreateMainWindow)?; + + let games = ModelRc::new(VecModel::from(Vec::new())); + + main_window.set_games(games.clone()); + + let profiles = ModelRc::new( + VecModel::from(vec![profile::Profile::default()]) + .map(|p| SharedString::from(String::from(p.name().to_string_lossy()))), + ); + + main_window.set_profiles(profiles.clone()); + + main_window.on_start_game(|_index| { + let screen = ui::Screen::new().unwrap(); + + screen.show().unwrap(); + }); + + Ok(Self { + main_window, + games, + profiles, + }) + } + + fn run(&self) -> Result<(), ApplicationError> { + self.main_window + .run() + .map_err(ApplicationError::RunMainWindow) + } +} + +fn full_error_reason(e: T) -> String +where + T: std::error::Error, +{ + use std::fmt::Write; + + let mut msg = format!("{e}"); + let mut src = e.source(); + + while let Some(e) = src { + write!(&mut msg, " -> {e}").unwrap(); + src = e.source(); + } + + msg +} + +pub enum AppExit { + Ok, + Err(ApplicationError), +} + +impl Termination for AppExit { + fn report(self) -> ExitCode { + match self { + AppExit::Ok => ExitCode::SUCCESS, + AppExit::Err(e) => { + ui::ErrorDialog::new() + .and_then(|error_dialog| { + error_dialog.set_message(SharedString::from(format!( + "Error running application: {}", + full_error_reason(e) + ))); + + error_dialog.run() + }) + .inspect_err(|e| eprintln!("Error displaying error dialog: {e}")) + .unwrap(); + + ExitCode::FAILURE + } + } + } +} + +impl From> for AppExit { + fn from(v: Result<(), ApplicationError>) -> Self { + match v { + Ok(_) => AppExit::Ok, + Err(e) => AppExit::Err(e), + } + } +} + +#[derive(Debug, Error)] +pub enum ApplicationError { + #[error(transparent)] + ParseArgs(clap::Error), + + #[error("failed to start debug server on {1}")] + StartDebugServer( + #[source] debug::StartDebugServerError, + std::net::SocketAddrV4, + ), + + #[error("failed to accept debug client")] + CreateDebugClient(#[source] std::io::Error), + + #[error("failed to create main window")] + CreateMainWindow(#[source] slint::PlatformError), + + #[error("failed to run main window")] + RunMainWindow(#[source] slint::PlatformError), +} diff --git a/gui/src/param/ffi.rs b/gui/src/param/ffi.rs new file mode 100644 index 000000000..91f77a392 --- /dev/null +++ b/gui/src/param/ffi.rs @@ -0,0 +1,58 @@ +use crate::error::RustError; +use crate::string::strdup; +use param::Param; +use std::ffi::{c_char, CStr}; +use std::ptr::null_mut; + +#[no_mangle] +pub unsafe extern "C" fn param_open(file: *const c_char, error: *mut *mut RustError) -> *mut Param { + let path = CStr::from_ptr(file).to_str().unwrap(); + + match Param::open(path) { + Ok(param) => Box::into_raw(Box::new(param)), + Err(e) => { + *error = RustError::wrap(e).into_c(); + null_mut() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn param_close(p: *mut Param) { + drop(Box::from_raw(p)); +} + +#[no_mangle] +pub unsafe extern "C" fn param_app_ver_get(p: *const Param) -> *mut c_char { + (*p).app_ver().map(strdup).unwrap_or(null_mut()) +} + +#[no_mangle] +pub unsafe extern "C" fn param_category_get(p: *const Param) -> *mut c_char { + strdup((*p).category()) +} + +#[no_mangle] +pub unsafe extern "C" fn param_content_id_get(p: *const Param) -> *mut c_char { + strdup((*p).content_id()) +} + +#[no_mangle] +pub unsafe extern "C" fn param_short_content_id_get(p: *const Param) -> *mut c_char { + strdup((*p).shortcontent_id()) +} + +#[no_mangle] +pub unsafe extern "C" fn param_title_get(p: *const Param) -> *mut c_char { + (*p).title().map(strdup).unwrap_or(null_mut()) +} + +#[no_mangle] +pub unsafe extern "C" fn param_title_id_get(p: *const Param) -> *mut c_char { + strdup((*p).title_id()) +} + +#[no_mangle] +pub unsafe extern "C" fn param_version_get(p: *const Param) -> *mut c_char { + (*p).version().map(strdup).unwrap_or(null_mut()) +} diff --git a/gui/src/param/mod.rs b/gui/src/param/mod.rs index d02b38300..9b77bb3c5 100644 --- a/gui/src/param/mod.rs +++ b/gui/src/param/mod.rs @@ -1,69 +1,2 @@ -use crate::error::RustError; -use crate::string::strdup; -use param::Param; -use std::ffi::{c_char, CStr}; -use std::fs::File; -use std::ptr::null_mut; - -#[no_mangle] -pub unsafe extern "C" fn param_open(file: *const c_char, error: *mut *mut RustError) -> *mut Param { - // Open file. - let file = match File::open(CStr::from_ptr(file).to_str().unwrap()) { - Ok(v) => v, - Err(e) => { - *error = RustError::with_source("couldn't open the specified file", e).into_c(); - return null_mut(); - } - }; - - // Parse. - let param = match Param::read(file) { - Ok(v) => v, - Err(e) => { - *error = RustError::with_source("couldn't read the specified file", e).into_c(); - return null_mut(); - } - }; - - Box::into_raw(param.into()) -} - -#[no_mangle] -pub unsafe extern "C" fn param_close(p: *mut Param) { - drop(Box::from_raw(p)); -} - -#[no_mangle] -pub unsafe extern "C" fn param_app_ver_get(p: *const Param) -> *mut c_char { - (*p).app_ver().map(strdup).unwrap_or(null_mut()) -} - -#[no_mangle] -pub unsafe extern "C" fn param_category_get(p: *const Param) -> *mut c_char { - strdup((*p).category()) -} - -#[no_mangle] -pub unsafe extern "C" fn param_content_id_get(p: *const Param) -> *mut c_char { - strdup((*p).content_id()) -} - -#[no_mangle] -pub unsafe extern "C" fn param_short_content_id_get(p: *const Param) -> *mut c_char { - strdup((*p).shortcontent_id()) -} - -#[no_mangle] -pub unsafe extern "C" fn param_title_get(p: *const Param) -> *mut c_char { - (*p).title().map(strdup).unwrap_or(null_mut()) -} - -#[no_mangle] -pub unsafe extern "C" fn param_title_id_get(p: *const Param) -> *mut c_char { - strdup((*p).title_id()) -} - -#[no_mangle] -pub unsafe extern "C" fn param_version_get(p: *const Param) -> *mut c_char { - (*p).version().map(strdup).unwrap_or(null_mut()) -} +#[cfg(feature = "qt")] +mod ffi; diff --git a/gui/src/pkg/ffi.rs b/gui/src/pkg/ffi.rs new file mode 100644 index 000000000..3b14c226c --- /dev/null +++ b/gui/src/pkg/ffi.rs @@ -0,0 +1,58 @@ +use super::ExtractProgress; +use crate::error::RustError; +use param::Param; +use pkg::Pkg; +use std::ffi::{c_char, c_void, CStr}; +use std::path::Path; +use std::ptr::null_mut; + +#[no_mangle] +pub unsafe extern "C" fn pkg_open(file: *const c_char, error: *mut *mut RustError) -> *mut Pkg { + let path = CStr::from_ptr(file).to_str().unwrap(); + + match Pkg::open(path) { + Ok(pkg) => Box::into_raw(Box::new(pkg)), + Err(e) => { + *error = RustError::wrap(e).into_c(); + null_mut() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn pkg_close(pkg: *mut Pkg) { + drop(Box::from_raw(pkg)); +} + +#[no_mangle] +pub unsafe extern "C" fn pkg_get_param(pkg: *const Pkg, error: *mut *mut RustError) -> *mut Param { + match (*pkg).get_param() { + Ok(param) => Box::into_raw(Box::new(param)), + Err(e) => { + *error = RustError::wrap(e).into_c(); + null_mut() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn pkg_extract( + pkg: *const Pkg, + dir: *const c_char, + status: extern "C" fn(*const c_char, usize, u64, u64, *mut c_void), + ud: *mut c_void, +) -> *mut RustError { + let root: &Path = CStr::from_ptr(dir).to_str().unwrap().as_ref(); + let progress = ExtractProgress { + status, + ud, + root, + total: 0, + progress: 0, + }; + + match (*pkg).extract(root, progress) { + Ok(_) => null_mut(), + Err(e) => RustError::wrap(e).into_c(), + } +} diff --git a/gui/src/pkg/mod.rs b/gui/src/pkg/mod.rs index 811ea72d2..f7ae7cd3a 100644 --- a/gui/src/pkg/mod.rs +++ b/gui/src/pkg/mod.rs @@ -1,64 +1,11 @@ -use crate::error::RustError; use humansize::{SizeFormatter, DECIMAL}; -use param::Param; -use pkg::{Pkg, PkgProgress}; -use std::ffi::{c_char, c_void, CStr, CString}; +use pkg::PkgProgress; +use std::ffi::{c_char, c_void, CString}; use std::path::Path; -use std::ptr::{null, null_mut}; +use std::ptr::null; -#[no_mangle] -pub unsafe extern "C" fn pkg_open(file: *const c_char, error: *mut *mut RustError) -> *mut Pkg { - let path = CStr::from_ptr(file); - let pkg = match Pkg::open(path.to_str().unwrap()) { - Ok(v) => Box::new(v), - Err(e) => { - *error = RustError::wrap(e).into_c(); - return null_mut(); - } - }; - - Box::into_raw(pkg) -} - -#[no_mangle] -pub unsafe extern "C" fn pkg_close(pkg: *mut Pkg) { - drop(Box::from_raw(pkg)); -} - -#[no_mangle] -pub unsafe extern "C" fn pkg_get_param(pkg: *const Pkg, error: *mut *mut RustError) -> *mut Param { - let param = match (*pkg).get_param() { - Ok(v) => Box::new(v), - Err(e) => { - *error = RustError::wrap(e).into_c(); - return null_mut(); - } - }; - - Box::into_raw(param) -} - -#[no_mangle] -pub unsafe extern "C" fn pkg_extract( - pkg: *const Pkg, - dir: *const c_char, - status: extern "C" fn(*const c_char, usize, u64, u64, *mut c_void), - ud: *mut c_void, -) -> *mut RustError { - let root: &Path = CStr::from_ptr(dir).to_str().unwrap().as_ref(); - let progress = ExtractProgress { - status, - ud, - root, - total: 0, - progress: 0, - }; - - match (*pkg).extract(root, progress) { - Ok(_) => null_mut(), - Err(e) => RustError::wrap(e).into_c(), - } -} +#[cfg(feature = "qt")] +mod ffi; struct ExtractProgress<'a> { status: extern "C" fn(*const c_char, usize, u64, u64, *mut c_void), @@ -87,7 +34,7 @@ impl<'a> PkgProgress for ExtractProgress<'a> { let total = total.try_into().unwrap(); (self.status)( - b"Entries extraction completed\0".as_ptr().cast(), // https://github.com/mozilla/cbindgen/issues/927 + b"Entries extraction completed\0".as_ptr().cast(), 0, total, total, @@ -128,7 +75,7 @@ impl<'a> PkgProgress for ExtractProgress<'a> { fn pfs_completed(&mut self) { (self.status)( - b"PFS extraction completed\0".as_ptr().cast(), // https://github.com/mozilla/cbindgen/issues/927 + b"PFS extraction completed\0".as_ptr().cast(), 0, self.total, self.total, diff --git a/gui/src/profile/ffi.rs b/gui/src/profile/ffi.rs new file mode 100644 index 000000000..502dbfe84 --- /dev/null +++ b/gui/src/profile/ffi.rs @@ -0,0 +1,77 @@ +use super::{DisplayResolution, Profile}; +use crate::error::RustError; +use crate::string::strdup; +use std::ffi::{c_char, CStr}; +use std::path::Path; +use std::ptr::null_mut; + +#[no_mangle] +pub unsafe extern "C" fn profile_new(name: *const c_char) -> *mut Profile { + Box::into_raw(Box::new(Profile { + name: CStr::from_ptr(name).to_owned(), + ..Default::default() + })) +} + +#[no_mangle] +pub unsafe extern "C" fn profile_load( + path: *const c_char, + err: *mut *mut RustError, +) -> *mut Profile { + // Check if path UTF-8. + let root = match CStr::from_ptr(path).to_str() { + Ok(v) => Path::new(v), + Err(_) => { + *err = RustError::new("the specified path is not UTF-8").into_c(); + return null_mut(); + } + }; + + match Profile::load(root) { + Ok(v) => Box::into_raw(Box::new(v)), + Err(e) => { + *err = RustError::wrap(e).into_c(); + + null_mut() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn profile_free(p: *mut Profile) { + drop(Box::from_raw(p)); +} + +#[no_mangle] +pub unsafe extern "C" fn profile_id(p: *const Profile) -> *mut c_char { + strdup((*p).id.to_string()) +} + +#[no_mangle] +pub unsafe extern "C" fn profile_name(p: *const Profile) -> *const c_char { + (*p).name.as_ptr() +} + +#[no_mangle] +pub unsafe extern "C" fn profile_display_resolution(p: *const Profile) -> DisplayResolution { + (*p).display_resolution +} + +#[no_mangle] +pub unsafe extern "C" fn profile_set_display_resolution(p: *mut Profile, v: DisplayResolution) { + (*p).display_resolution = v; +} + +#[no_mangle] +pub unsafe extern "C" fn profile_save(p: *const Profile, path: *const c_char) -> *mut RustError { + // Check if path UTF-8. + let root = match CStr::from_ptr(path).to_str() { + Ok(v) => Path::new(v), + Err(_) => return RustError::new("the specified path is not UTF-8").into_c(), + }; + + match (*p).save(root) { + Ok(_) => null_mut(), + Err(e) => RustError::wrap(e).into_c(), + } +} diff --git a/gui/src/profile/mod.rs b/gui/src/profile/mod.rs index a555ef70b..fec95476f 100644 --- a/gui/src/profile/mod.rs +++ b/gui/src/profile/mod.rs @@ -1,130 +1,55 @@ -use crate::error::RustError; -use crate::string::strdup; use obconf::Config; use serde::{Deserialize, Serialize}; -use std::ffi::{c_char, CStr, CString}; +use std::ffi::{CStr, CString}; use std::fs::File; use std::num::NonZero; use std::path::Path; -use std::ptr::null_mut; use std::time::SystemTime; +use thiserror::Error; use uuid::Uuid; -#[no_mangle] -pub unsafe extern "C" fn profile_new(name: *const c_char) -> *mut Profile { - Box::into_raw(Box::new(Profile { - name: CStr::from_ptr(name).to_owned(), - ..Default::default() - })) -} - -#[no_mangle] -pub unsafe extern "C" fn profile_load( - path: *const c_char, - err: *mut *mut RustError, -) -> *mut Profile { - // Check if path UTF-8. - let root = match CStr::from_ptr(path).to_str() { - Ok(v) => Path::new(v), - Err(_) => { - *err = RustError::new("the specified path is not UTF-8").into_c(); - return null_mut(); - } - }; - - // Open profile.bin. - let path = root.join("profile.bin"); - let file = match File::open(&path) { - Ok(v) => v, - Err(e) => { - *err = RustError::with_source(format_args!("couldn't open {}", path.display()), e) - .into_c(); - return null_mut(); - } - }; - - // Load profile.bin. - let p = match ciborium::from_reader(file) { - Ok(v) => v, - Err(e) => { - *err = RustError::with_source(format_args!("couldn't load {}", path.display()), e) - .into_c(); - return null_mut(); - } - }; +#[cfg(feature = "qt")] +mod ffi; - Box::into_raw(Box::new(p)) +/// Contains settings to launch the kernel. +#[derive(Clone, Deserialize, Serialize)] +#[serde(default)] +pub struct Profile { + id: Uuid, + name: CString, + display_resolution: DisplayResolution, + kernel_config: Config, + created: SystemTime, } -#[no_mangle] -pub unsafe extern "C" fn profile_free(p: *mut Profile) { - drop(Box::from_raw(p)); -} +impl Profile { + pub fn load(path: impl AsRef) -> Result { + let path = path.as_ref().join("profile.bin"); -#[no_mangle] -pub unsafe extern "C" fn profile_id(p: *const Profile) -> *mut c_char { - strdup((*p).id.to_string()) -} + let file = File::open(&path).map_err(LoadError::Open)?; -#[no_mangle] -pub unsafe extern "C" fn profile_name(p: *const Profile) -> *const c_char { - (*p).name.as_ptr() -} + let profile = ciborium::from_reader(file).map_err(LoadError::Load)?; -#[no_mangle] -pub unsafe extern "C" fn profile_display_resolution(p: *const Profile) -> DisplayResolution { - (*p).display_resolution -} + Ok(profile) + } + pub fn save(&self, path: impl AsRef) -> Result<(), SaveError> { + let path = path.as_ref(); -#[no_mangle] -pub unsafe extern "C" fn profile_set_display_resolution(p: *mut Profile, v: DisplayResolution) { - (*p).display_resolution = v; -} + std::fs::create_dir_all(&path).map_err(SaveError::CreateDir)?; -#[no_mangle] -pub unsafe extern "C" fn profile_save(p: *const Profile, path: *const c_char) -> *mut RustError { - // Check if path UTF-8. - let root = match CStr::from_ptr(path).to_str() { - Ok(v) => Path::new(v), - Err(_) => return RustError::new("the specified path is not UTF-8").into_c(), - }; - - // Create a directory. - if let Err(e) = std::fs::create_dir_all(root) { - return RustError::with_source("couldn't create the specified path", e).into_c(); - } + let path = path.join("profile.bin"); - // Create profile.bin. - let path = root.join("profile.bin"); - let file = match File::create(&path) { - Ok(v) => v, - Err(e) => { - return RustError::with_source(format_args!("couldn't create {}", path.display()), e) - .into_c() - } - }; + let file = File::create(&path).map_err(SaveError::CreateFile)?; - // Write profile.bin. - if let Err(e) = ciborium::into_writer(&*p, file) { - return RustError::with_source(format_args!("couldn't write {}", path.display()), e) - .into_c(); - } + ciborium::into_writer(self, file).map_err(SaveError::WriteFile)?; - null_mut() -} + Ok(()) + } -/// Contains settings to launch the kernel. -#[derive(Deserialize, Serialize)] -#[serde(default)] -pub struct Profile { - id: Uuid, - name: CString, - display_resolution: DisplayResolution, - kernel_config: Config, - created: SystemTime, -} + pub fn name(&self) -> &CStr { + &self.name + } -impl Profile { pub fn kernel_config(&self) -> &Config { &self.kernel_config } @@ -155,3 +80,24 @@ pub enum DisplayResolution { /// 3840 × 2160. UltraHd, } + +#[derive(Debug, Error)] +pub enum LoadError { + #[error("couldn't open the profile file")] + Open(#[source] std::io::Error), + + #[error("couldn't load the profile file")] + Load(#[source] ciborium::de::Error), +} + +#[derive(Debug, Error)] +pub enum SaveError { + #[error("couldn't create the directory")] + CreateDir(#[source] std::io::Error), + + #[error("couldn't create the profile file")] + CreateFile(#[source] std::io::Error), + + #[error("couldn't write the profile file")] + WriteFile(#[source] ciborium::ser::Error), +} diff --git a/gui/src/rlim.rs b/gui/src/rlim.rs new file mode 100644 index 000000000..4cc3d04f3 --- /dev/null +++ b/gui/src/rlim.rs @@ -0,0 +1,34 @@ +use std::mem::MaybeUninit; +use thiserror::Error; + +pub(crate) fn set_rlimit_nofile() -> Result<(), RlimitError> { + let mut rlim = MaybeUninit::uninit(); + + let ret = unsafe { libc::getrlimit(libc::RLIMIT_NOFILE, rlim.as_mut_ptr()) }; + + match ret { + 0 => { + let mut rlim = unsafe { rlim.assume_init() }; + + if rlim.rlim_cur < rlim.rlim_max { + rlim.rlim_cur = rlim.rlim_max; + + if unsafe { libc::setrlimit(libc::RLIMIT_NOFILE, &rlim) } != 0 { + return Err(RlimitError::SetRlimitFailed(std::io::Error::last_os_error())); + } + } + + Ok(()) + } + _ => Err(RlimitError::GetRlimitFailed(std::io::Error::last_os_error())), + } +} + +#[derive(Debug, Error)] +pub(crate) enum RlimitError { + #[error("failed to get RLIMIT_NOFILE")] + GetRlimitFailed(#[source] std::io::Error), + + #[error("failed to set RLIMIT_NOFILE")] + SetRlimitFailed(#[source] std::io::Error), +} diff --git a/gui/src/screen/metal/mod.rs b/gui/src/screen/metal/mod.rs index 086e83fbd..a6a32ef96 100644 --- a/gui/src/screen/metal/mod.rs +++ b/gui/src/screen/metal/mod.rs @@ -22,7 +22,7 @@ pub struct Metal { } impl Metal { - pub fn new(screen: &VmmScreen) -> Result { + pub fn from_screen(screen: &VmmScreen) -> Result { // Get Metal device. let device = match Device::system_default() { Some(v) => v, diff --git a/gui/src/screen/mod.rs b/gui/src/screen/mod.rs index 5d3183d34..dfe697382 100644 --- a/gui/src/screen/mod.rs +++ b/gui/src/screen/mod.rs @@ -12,6 +12,12 @@ pub type Default = self::engine::Vulkan; #[cfg(target_os = "macos")] pub type Default = self::engine::Metal; +#[cfg(not(target_os = "macos"))] +pub type ScreenError = self::engine::VulkanError; + +#[cfg(target_os = "macos")] +pub type ScreenError = self::engine::MetalError; + /// Encapsulates a platform-specific surface for drawing a VM screen. pub trait Screen: 'static { type Buffer: ScreenBuffer; diff --git a/gui/src/screen/vulkan/ffi.rs b/gui/src/screen/vulkan/ffi.rs deleted file mode 100644 index 46bc1f1fd..000000000 --- a/gui/src/screen/vulkan/ffi.rs +++ /dev/null @@ -1,186 +0,0 @@ -use ash::vk::{ - AllocationCallbacks, Device, DeviceCreateInfo, ExtensionProperties, ExternalBufferProperties, - ExternalFenceProperties, ExternalSemaphoreProperties, Format, FormatProperties, - FormatProperties2, ImageCreateFlags, ImageFormatProperties, ImageFormatProperties2, - ImageTiling, ImageType, ImageUsageFlags, Instance, LayerProperties, PFN_vkVoidFunction, - PhysicalDevice, PhysicalDeviceExternalBufferInfo, PhysicalDeviceExternalFenceInfo, - PhysicalDeviceExternalSemaphoreInfo, PhysicalDeviceFeatures, PhysicalDeviceFeatures2, - PhysicalDeviceGroupProperties, PhysicalDeviceImageFormatInfo2, PhysicalDeviceMemoryProperties, - PhysicalDeviceMemoryProperties2, PhysicalDeviceProperties, PhysicalDeviceProperties2, - PhysicalDeviceSparseImageFormatInfo2, PhysicalDeviceToolProperties, QueueFamilyProperties, - QueueFamilyProperties2, Result, SampleCountFlags, SparseImageFormatProperties, - SparseImageFormatProperties2, -}; -use std::ffi::c_char; - -extern "system" { - #[link_name = "vmm_vk_create_device"] - pub fn create_device( - physical_device: PhysicalDevice, - p_create_info: *const DeviceCreateInfo<'_>, - p_allocator: *const AllocationCallbacks<'_>, - p_device: *mut Device, - ) -> Result; - - #[link_name = "vmm_vk_enumerate_device_extension_properties"] - pub fn enumerate_device_extension_properties( - physical_device: PhysicalDevice, - p_layer_name: *const c_char, - p_property_count: *mut u32, - p_properties: *mut ExtensionProperties, - ) -> Result; - - #[link_name = "vmm_vk_enumerate_device_layer_properties"] - pub fn enumerate_device_layer_properties( - physical_device: PhysicalDevice, - p_property_count: *mut u32, - p_properties: *mut LayerProperties, - ) -> Result; - - #[link_name = "vmm_vk_enumerate_physical_device_groups"] - pub fn enumerate_physical_device_groups( - instance: Instance, - p_physical_device_group_count: *mut u32, - p_physical_device_group_properties: *mut PhysicalDeviceGroupProperties<'_>, - ) -> Result; - - #[link_name = "vmm_vk_enumerate_physical_devices"] - pub fn enumerate_physical_devices( - instance: Instance, - p_physical_device_count: *mut u32, - p_physical_devices: *mut PhysicalDevice, - ) -> Result; - - #[link_name = "vmm_vk_get_device_proc_addr"] - pub fn get_device_proc_addr(device: Device, p_name: *const c_char) -> PFN_vkVoidFunction; - - #[link_name = "vmm_vk_get_physical_device_external_buffer_properties"] - pub fn get_physical_device_external_buffer_properties( - physical_device: PhysicalDevice, - p_external_buffer_info: *const PhysicalDeviceExternalBufferInfo<'_>, - p_external_buffer_properties: *mut ExternalBufferProperties<'_>, - ); - - #[link_name = "vmm_vk_get_physical_device_external_fence_properties"] - pub fn get_physical_device_external_fence_properties( - physical_device: PhysicalDevice, - p_external_fence_info: *const PhysicalDeviceExternalFenceInfo<'_>, - p_external_fence_properties: *mut ExternalFenceProperties<'_>, - ); - - #[link_name = "vmm_vk_get_physical_device_external_semaphore_properties"] - pub fn get_physical_device_external_semaphore_properties( - physical_device: PhysicalDevice, - p_external_semaphore_info: *const PhysicalDeviceExternalSemaphoreInfo<'_>, - p_external_semaphore_properties: *mut ExternalSemaphoreProperties<'_>, - ); - - #[link_name = "vmm_vk_get_physical_device_features"] - pub fn get_physical_device_features( - physical_device: PhysicalDevice, - p_features: *mut PhysicalDeviceFeatures, - ); - - #[link_name = "vmm_vk_get_physical_device_features2"] - pub fn get_physical_device_features2( - physical_device: PhysicalDevice, - p_features: *mut PhysicalDeviceFeatures2<'_>, - ); - - #[link_name = "vmm_vk_get_physical_device_format_properties"] - pub fn get_physical_device_format_properties( - physical_device: PhysicalDevice, - format: Format, - p_format_properties: *mut FormatProperties, - ); - - #[link_name = "vmm_vk_get_physical_device_format_properties2"] - pub fn get_physical_device_format_properties2( - physical_device: PhysicalDevice, - format: Format, - p_format_properties: *mut FormatProperties2<'_>, - ); - - #[link_name = "vmm_vk_get_physical_device_image_format_properties"] - pub fn get_physical_device_image_format_properties( - physical_device: PhysicalDevice, - format: Format, - ty: ImageType, - tiling: ImageTiling, - usage: ImageUsageFlags, - flags: ImageCreateFlags, - p_image_format_properties: *mut ImageFormatProperties, - ) -> Result; - - #[link_name = "vmm_vk_get_physical_device_image_format_properties2"] - pub fn get_physical_device_image_format_properties2( - physical_device: PhysicalDevice, - p_image_format_info: *const PhysicalDeviceImageFormatInfo2<'_>, - p_image_format_properties: *mut ImageFormatProperties2<'_>, - ) -> Result; - - #[link_name = "vmm_vk_get_physical_device_memory_properties"] - pub fn get_physical_device_memory_properties( - physical_device: PhysicalDevice, - p_memory_properties: *mut PhysicalDeviceMemoryProperties, - ); - - #[link_name = "vmm_vk_get_physical_device_memory_properties2"] - pub fn get_physical_device_memory_properties2( - physical_device: PhysicalDevice, - p_memory_properties: *mut PhysicalDeviceMemoryProperties2<'_>, - ); - - #[link_name = "vmm_vk_get_physical_device_properties"] - pub fn get_physical_device_properties( - physical_device: PhysicalDevice, - p_properties: *mut PhysicalDeviceProperties, - ); - - #[link_name = "vmm_vk_get_physical_device_properties2"] - pub fn get_physical_device_properties2( - physical_device: PhysicalDevice, - p_properties: *mut PhysicalDeviceProperties2<'_>, - ); - - #[link_name = "vmm_vk_get_physical_device_queue_family_properties"] - pub fn get_physical_device_queue_family_properties( - physical_device: PhysicalDevice, - p_queue_family_property_count: *mut u32, - p_queue_family_properties: *mut QueueFamilyProperties, - ); - - #[link_name = "vmm_vk_get_physical_device_queue_family_properties2"] - pub fn get_physical_device_queue_family_properties2( - physical_device: PhysicalDevice, - p_queue_family_property_count: *mut u32, - p_queue_family_properties: *mut QueueFamilyProperties2<'_>, - ); - - #[link_name = "vmm_vk_get_physical_device_sparse_image_format_properties"] - pub fn get_physical_device_sparse_image_format_properties( - physical_device: PhysicalDevice, - format: Format, - ty: ImageType, - samples: SampleCountFlags, - usage: ImageUsageFlags, - tiling: ImageTiling, - p_property_count: *mut u32, - p_properties: *mut SparseImageFormatProperties, - ); - - #[link_name = "vmm_vk_get_physical_device_sparse_image_format_properties2"] - pub fn get_physical_device_sparse_image_format_properties2( - physical_device: PhysicalDevice, - p_format_info: *const PhysicalDeviceSparseImageFormatInfo2<'_>, - p_property_count: *mut u32, - p_properties: *mut SparseImageFormatProperties2<'_>, - ); - - #[link_name = "vmm_vk_get_physical_device_tool_properties"] - pub fn get_physical_device_tool_properties( - physical_device: PhysicalDevice, - p_tool_count: *mut u32, - p_tool_properties: *mut PhysicalDeviceToolProperties<'_>, - ) -> Result; -} diff --git a/gui/src/screen/vulkan/mod.rs b/gui/src/screen/vulkan/mod.rs index 1f0739e19..e1e30cc17 100644 --- a/gui/src/screen/vulkan/mod.rs +++ b/gui/src/screen/vulkan/mod.rs @@ -1,28 +1,13 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use self::buffer::VulkanBuffer; -use self::ffi::{ - create_device, enumerate_device_extension_properties, enumerate_device_layer_properties, - enumerate_physical_device_groups, enumerate_physical_devices, get_device_proc_addr, - get_physical_device_external_buffer_properties, get_physical_device_external_fence_properties, - get_physical_device_external_semaphore_properties, get_physical_device_features, - get_physical_device_features2, get_physical_device_format_properties, - get_physical_device_format_properties2, get_physical_device_image_format_properties, - get_physical_device_image_format_properties2, get_physical_device_memory_properties, - get_physical_device_memory_properties2, get_physical_device_properties, - get_physical_device_properties2, get_physical_device_queue_family_properties, - get_physical_device_queue_family_properties2, - get_physical_device_sparse_image_format_properties, - get_physical_device_sparse_image_format_properties2, get_physical_device_tool_properties, -}; use super::{Screen, ScreenBuffer}; use crate::vmm::VmmScreen; use ash::vk::{DeviceCreateInfo, DeviceQueueCreateInfo, Handle, QueueFlags}; -use ash::{Device, Instance, InstanceFnV1_0, InstanceFnV1_1, InstanceFnV1_3}; +use ash::Device; use std::sync::Arc; use thiserror::Error; mod buffer; -mod ffi; /// Implementation of [`Screen`] using Vulkan. pub struct Vulkan { @@ -31,44 +16,15 @@ pub struct Vulkan { } impl Vulkan { - pub fn new(screen: &VmmScreen) -> Result { - // Wrap VkInstance. - let instance = screen.vk_instance.try_into().unwrap(); - let instance = ash::vk::Instance::from_raw(instance); - let instance = Instance::from_parts_1_3( - instance, - InstanceFnV1_0 { - destroy_instance: Self::destroy_instance, - enumerate_physical_devices, - get_physical_device_features, - get_physical_device_format_properties, - get_physical_device_image_format_properties, - get_physical_device_properties, - get_physical_device_queue_family_properties, - get_physical_device_memory_properties, - get_device_proc_addr, - create_device, - enumerate_device_extension_properties, - enumerate_device_layer_properties, - get_physical_device_sparse_image_format_properties, - }, - InstanceFnV1_1 { - enumerate_physical_device_groups, - get_physical_device_features2, - get_physical_device_properties2, - get_physical_device_format_properties2, - get_physical_device_image_format_properties2, - get_physical_device_queue_family_properties2, - get_physical_device_memory_properties2, - get_physical_device_sparse_image_format_properties2, - get_physical_device_external_buffer_properties, - get_physical_device_external_fence_properties, - get_physical_device_external_semaphore_properties, - }, - InstanceFnV1_3 { - get_physical_device_tool_properties, - }, - ); + pub fn from_screen(screen: &VmmScreen) -> Result { + let entry = ash::Entry::linked(); + + let instance = unsafe { + ash::Instance::load( + entry.static_fn(), + ash::vk::Instance::from_raw(screen.vk_instance.try_into().unwrap()), + ) + }; // Wrap VkPhysicalDevice. let physical = screen.vk_device.try_into().unwrap(); @@ -76,32 +32,28 @@ impl Vulkan { // Setup VkDeviceQueueCreateInfo. let queue = unsafe { instance.get_physical_device_queue_family_properties(physical) } - .iter() + .into_iter() .position(|p| p.queue_flags.contains(QueueFlags::GRAPHICS)) - .unwrap(); - let queues = [DeviceQueueCreateInfo::default() - .queue_family_index(queue.try_into().unwrap()) - .queue_priorities(&[1.0])]; + .ok_or(VulkanError::NoQueue)?; + + let queue = queue + .try_into() + .map_err(|_| VulkanError::QueueOutOfBounds(queue))?; + + let queues = DeviceQueueCreateInfo::default() + .queue_family_index(queue) + .queue_priorities(&[1.0]); // Create logical device. - let device = DeviceCreateInfo::default().queue_create_infos(&queues); - let device = match unsafe { instance.create_device(physical, &device, None) } { - Ok(v) => v, - Err(e) => return Err(VulkanError::CreateDeviceFailed(e)), - }; + let device = DeviceCreateInfo::default().queue_create_infos(std::slice::from_ref(&queues)); + let device = unsafe { instance.create_device(physical, &device, None) } + .map_err(VulkanError::CreateDeviceFailed)?; Ok(Self { buffer: Arc::new(VulkanBuffer::new()), device, }) } - - unsafe extern "system" fn destroy_instance( - _: ash::vk::Instance, - _: *const ash::vk::AllocationCallbacks<'_>, - ) { - unimplemented!() - } } impl Drop for Vulkan { @@ -127,6 +79,12 @@ impl Screen for Vulkan { /// Represents an error when [`Vulkan::new()`] fails. #[derive(Debug, Error)] pub enum VulkanError { + #[error("couldn't find suitable queue")] + NoQueue, + + #[error("queue index #{0} out of bounds")] + QueueOutOfBounds(usize), + #[error("couldn't create a logical device")] CreateDeviceFailed(#[source] ash::vk::Result), } diff --git a/gui/src/ui.rs b/gui/src/ui.rs new file mode 100644 index 000000000..046c74a92 --- /dev/null +++ b/gui/src/ui.rs @@ -0,0 +1,2 @@ +// This macro includes the generated Rust code from .slint files +slint::include_modules!(); diff --git a/gui/src/vmm/ffi.rs b/gui/src/vmm/ffi.rs new file mode 100644 index 000000000..ddabb5573 --- /dev/null +++ b/gui/src/vmm/ffi.rs @@ -0,0 +1,139 @@ +use super::{DebugResult, KernelStop, Vmm, VmmEvent, VmmScreen}; +use crate::debug::DebugClient; +use crate::error::RustError; +use crate::profile::Profile; +use crate::screen::Screen; +use gdbstub::common::Signal; +use gdbstub::stub::state_machine::GdbStubStateMachine; +use gdbstub::stub::MultiThreadStopReason; +use std::ffi::{c_char, c_void, CStr}; +use std::ptr::null_mut; +use std::sync::atomic::Ordering; + +#[no_mangle] +pub unsafe extern "C" fn vmm_start( + kernel: *const c_char, + screen: *const VmmScreen, + profile: *const Profile, + debugger: *mut DebugClient, + event: unsafe extern "C" fn(*const VmmEvent, *mut c_void), + cx: *mut c_void, + err: *mut *mut RustError, +) -> *mut Vmm { + // Consume the debugger now to prevent memory leak in case of error. + let debugger = if debugger.is_null() { + None + } else { + Some(*Box::from_raw(debugger)) + }; + + // Check if path UTF-8. + let path = match CStr::from_ptr(kernel).to_str() { + Ok(v) => v, + Err(_) => { + *err = RustError::new("path of the kernel is not UTF-8").into_c(); + return null_mut(); + } + }; + + let screen = unsafe { &*screen }; + let profile = unsafe { &*profile }; + + match Vmm::new(path, screen, profile, debugger, event, cx) { + Ok(vmm) => Box::into_raw(Box::new(vmm)), + Err(e) => { + *err = RustError::wrap(e).into_c(); + null_mut() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn vmm_free(vmm: *mut Vmm) { + drop(Box::from_raw(vmm)); +} + +#[no_mangle] +pub unsafe extern "C" fn vmm_draw(vmm: *mut Vmm) -> *mut RustError { + match (*vmm).screen.update() { + Ok(_) => null_mut(), + Err(e) => RustError::wrap(e).into_c(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn vmm_dispatch_debug(vmm: *mut Vmm, stop: *mut KernelStop) -> DebugResult { + // Consume stop reason now to prevent memory leak. + let vmm = &mut *vmm; + let mut stop = if stop.is_null() { + None + } else { + Some(Box::from_raw(stop).0) + }; + + loop { + // Check current state. + let r = match vmm.gdb.take().unwrap() { + GdbStubStateMachine::Idle(s) => match super::debug::dispatch_idle(&mut vmm.cpu, s) { + Ok(Ok(v)) => Ok(v), + Ok(Err(v)) => { + // No pending data from the debugger. + vmm.gdb = Some(v.into()); + return DebugResult::Ok; + } + Err(e) => Err(e), + }, + GdbStubStateMachine::Running(s) => { + match super::debug::dispatch_running(&mut vmm.cpu, s, stop.take()) { + Ok(Ok(v)) => Ok(v), + Ok(Err(v)) => { + // No pending data from the debugger. + vmm.gdb = Some(v.into()); + return DebugResult::Ok; + } + Err(e) => Err(e), + } + } + GdbStubStateMachine::CtrlCInterrupt(s) => { + vmm.cpu.lock(); + + s.interrupt_handled( + &mut vmm.cpu, + Some(MultiThreadStopReason::Signal(Signal::SIGINT)), + ) + .map_err(|e| RustError::with_source("couldn't handle CTRL+C from a debugger", e)) + } + GdbStubStateMachine::Disconnected(_) => return DebugResult::Disconnected, + }; + + match r { + Ok(v) => vmm.gdb = Some(v), + Err(e) => return DebugResult::Error { reason: e.into_c() }, + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn vmm_debug_socket(vmm: *mut Vmm) -> isize { + let s = match &mut (*vmm).gdb { + Some(v) => v, + None => return -1, + }; + + match s { + GdbStubStateMachine::Idle(s) => s.borrow_conn().socket() as _, + GdbStubStateMachine::Running(s) => s.borrow_conn().socket() as _, + GdbStubStateMachine::CtrlCInterrupt(s) => s.borrow_conn().socket() as _, + GdbStubStateMachine::Disconnected(s) => s.borrow_conn().socket() as _, + } +} + +#[no_mangle] +pub unsafe extern "C" fn vmm_shutdown(vmm: *mut Vmm) { + (*vmm).shutdown.store(true, Ordering::Relaxed); +} + +#[no_mangle] +pub unsafe extern "C" fn vmm_shutting_down(vmm: *mut Vmm) -> bool { + (*vmm).shutdown.load(Ordering::Relaxed) +} diff --git a/gui/src/vmm/hv/mod.rs b/gui/src/vmm/hv/mod.rs index e68811d4b..1cd1d8960 100644 --- a/gui/src/vmm/hv/mod.rs +++ b/gui/src/vmm/hv/mod.rs @@ -24,6 +24,15 @@ pub type Default = self::os::Hvf; #[cfg(target_os = "windows")] pub type Default = self::os::Whp; +#[cfg(target_os = "linux")] +pub type HypervisorError = self::os::KvmError; + +#[cfg(target_os = "macos")] +pub type HypervisorError = self::os::HvfError; + +#[cfg(target_os = "windows")] +pub type HypervisorError = self::os::WhpError; + /// Underlying hypervisor (e.g. KVM on Linux). pub trait Hypervisor: Send + Sync + 'static { type Mapper: RamMapper; diff --git a/gui/src/vmm/mod.rs b/gui/src/vmm/mod.rs index d78b8896d..d6442a946 100644 --- a/gui/src/vmm/mod.rs +++ b/gui/src/vmm/mod.rs @@ -10,16 +10,17 @@ use crate::debug::DebugClient; use crate::error::RustError; use crate::profile::Profile; use crate::screen::Screen; -use gdbstub::common::Signal; +use cpu::GdbError; use gdbstub::stub::state_machine::GdbStubStateMachine; -use gdbstub::stub::MultiThreadStopReason; +use gdbstub::stub::{GdbStubError, MultiThreadStopReason}; +use kernel::{KernelError, ProgramHeaderError}; use obconf::{BootEnv, ConsoleType, Vm}; use std::cmp::max; use std::error::Error; use std::ffi::{c_char, c_void, CStr}; use std::io::Read; use std::num::NonZero; -use std::ptr::null_mut; +use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use thiserror::Error; @@ -29,568 +30,326 @@ use thiserror::Error; mod arch; mod cpu; mod debug; +#[cfg(feature = "qt")] +mod ffi; mod hv; mod hw; mod kernel; mod ram; -#[no_mangle] -pub unsafe extern "C" fn vmm_start( - kernel: *const c_char, - screen: *const VmmScreen, - profile: *const Profile, - debugger: *mut DebugClient, - event: unsafe extern "C" fn(*const VmmEvent, *mut c_void), - cx: *mut c_void, - err: *mut *mut RustError, -) -> *mut Vmm { - // Consume the debugger now to prevent memory leak in case of error. - let debugger = match debugger.is_null() { - true => None, - false => Some(Box::from_raw(debugger)), - }; - - // Check if path UTF-8. - let path = match CStr::from_ptr(kernel).to_str() { - Ok(v) => v, - Err(_) => { - *err = RustError::new("path of the kernel is not UTF-8").into_c(); - return null_mut(); - } - }; - - // Open kernel image. - let mut file = match Kernel::open(path) { - Ok(v) => v, - Err(e) => { - *err = RustError::with_source(format_args!("couldn't open {path}"), e).into_c(); - return null_mut(); - } - }; - - // Get program header enumerator. - let hdrs = match file.program_headers() { - Ok(v) => v, - Err(e) => { - *err = RustError::with_source( - format_args!("couldn't start enumerating program headers of {path}"), - e, - ) - .into_c(); - - return null_mut(); - } - }; - - // Parse program headers. - let mut segments = Vec::new(); - let mut dynamic = None; - let mut note = None; - - for (index, item) in hdrs.enumerate() { - // Check if success. - let hdr = match item { - Ok(v) => v, - Err(e) => { - *err = RustError::with_source( - format_args!("couldn't read program header #{index} on {path}"), - e, - ) - .into_c(); - - return null_mut(); - } - }; +#[cfg(unix)] +fn get_page_size() -> Result, std::io::Error> { + let v = unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) }; - // Process the header. - match hdr.p_type { - PT_LOAD => { - if hdr.p_filesz > TryInto::::try_into(hdr.p_memsz).unwrap() { - *err = - RustError::new(format!("invalid p_filesz on on PT_LOAD {index}")).into_c(); - return null_mut(); - } + if v < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(v.try_into().ok().and_then(NonZero::new).unwrap()) + } +} - segments.push(hdr); - } - PT_DYNAMIC => { - if dynamic.is_some() { - *err = RustError::new("multiple PT_DYNAMIC is not supported").into_c(); - return null_mut(); - } +#[cfg(windows)] +fn get_page_size() -> Result, std::io::Error> { + use std::mem::zeroed; + use windows_sys::Win32::System::SystemInformation::GetSystemInfo; + let mut i = unsafe { zeroed() }; - dynamic = Some(hdr); - } - PT_NOTE => { - if note.is_some() { - *err = RustError::new("multiple PT_NOTE is not supported").into_c(); - return null_mut(); - } + unsafe { GetSystemInfo(&mut i) }; - note = Some(hdr); - } - PT_PHDR | PT_GNU_EH_FRAME | PT_GNU_STACK | PT_GNU_RELRO => {} - v => { - *err = RustError::new(format!("unknown p_type {v} on program header {index}")) - .into_c(); - return null_mut(); - } - } - } + Ok(i.dwPageSize.try_into().ok().and_then(NonZero::new).unwrap()) +} - segments.sort_unstable_by_key(|i| i.p_vaddr); +/// Manage a virtual machine that run the kernel. +pub struct Vmm { + cpu: CpuManager, // Drop first. + screen: crate::screen::Default, + gdb: Option< + GdbStubStateMachine< + 'static, + CpuManager, + DebugClient, + >, + >, + shutdown: Arc, +} - // Make sure the first PT_LOAD includes the ELF header. - match segments.first() { - Some(hdr) => { - if hdr.p_offset != 0 { - *err = RustError::new("the first PT_LOAD does not includes ELF header").into_c(); - return null_mut(); - } - } - None => { - *err = RustError::new("no any PT_LOAD on the kernel").into_c(); - return null_mut(); - } - } +impl Vmm { + pub fn new( + kernel_path: impl AsRef, + screen: &VmmScreen, + profile: &Profile, + debugger: Option, + event_handler: unsafe extern "C" fn(*const VmmEvent, *mut c_void), + cx: *mut c_void, + ) -> Result { + let path = kernel_path.as_ref(); + + // Open kernel image. + let mut kernel_img = + Kernel::open(path).map_err(|e| VmmError::OpenKernel(e, path.to_path_buf()))?; + + // Get program header enumerator. + let hdrs = kernel_img + .program_headers() + .map_err(|e| VmmError::EnumerateProgramHeaders(e, path.to_path_buf()))?; + + // Parse program headers. + let mut segments = Vec::new(); + let mut dynamic = None; + let mut note = None; + + for (index, item) in hdrs.enumerate() { + // Check if success. + let hdr = + item.map_err(|e| VmmError::ReadProgramHeader(e, index, path.to_path_buf()))?; + + // Process the header. + match hdr.p_type { + PT_LOAD => { + if hdr.p_filesz > u64::try_from(hdr.p_memsz).unwrap() { + return Err(VmmError::InvalidFilesz(index)); + } - // Check if PT_DYNAMIC exists. - let dynamic = match dynamic { - Some(v) => v, - None => { - *err = RustError::new("no PT_DYNAMIC segment on the kernel").into_c(); - return null_mut(); - } - }; - - // Check if PT_NOTE exists. - let note = match note { - Some(v) => v, - None => { - *err = RustError::new("no PT_NOTE segment on the kernel").into_c(); - return null_mut(); - } - }; - - // Seek to PT_NOTE. - let mut data = match file.segment_data(¬e) { - Ok(v) => v, - Err(e) => { - *err = RustError::with_source(format_args!("couldn't seek to PT_NOTE on {path}"), e) - .into_c(); - return null_mut(); - } - }; + segments.push(hdr); + } + PT_DYNAMIC => { + if dynamic.is_some() { + return Err(VmmError::MultipleDynamic); + } - // Parse PT_NOTE. - let mut vm_page_size = None; + dynamic = Some(hdr); + } + PT_NOTE => { + if note.is_some() { + return Err(VmmError::MultipleNote); + } - for i in 0.. { - // Check remaining data. - if data.limit() == 0 { - break; + note = Some(hdr); + } + PT_PHDR | PT_GNU_EH_FRAME | PT_GNU_STACK | PT_GNU_RELRO => {} + v => return Err(VmmError::UnknownProgramHeaderType(v, index)), + } } - // Read note header. - let mut buf = [0u8; 4 * 3]; + segments.sort_unstable_by_key(|i| i.p_vaddr); - if let Err(e) = data.read_exact(&mut buf) { - *err = RustError::with_source(format_args!("couldn't read kernel note #{i} header"), e) - .into_c(); - return null_mut(); - } + // Make sure the first PT_LOAD includes the ELF header. + let hdr = segments.first().ok_or(VmmError::NoLoadSegment)?; - // Parse note header. - let nlen: usize = u32::from_ne_bytes(buf[..4].try_into().unwrap()) - .try_into() - .unwrap(); - let dlen: usize = u32::from_ne_bytes(buf[4..8].try_into().unwrap()) - .try_into() - .unwrap(); - let ty = u32::from_ne_bytes(buf[8..].try_into().unwrap()); - - if nlen > 0xff { - *err = RustError::new(format!("name on kernel note #{i} is too large")).into_c(); - return null_mut(); + if hdr.p_offset != 0 { + return Err(VmmError::ElfHeaderNotInFirstLoadSegment); } - if dlen > 0xff { - *err = RustError::new(format!("description on kernel note #{i} is too large")).into_c(); - return null_mut(); - } + // Check if PT_DYNAMIC exists. + let dynamic = dynamic.ok_or(VmmError::NoDynamicSegment)?; - // Read note name + description. - let nalign = nlen.next_multiple_of(4); - let mut buf = vec![0u8; nalign + dlen]; + // Check if PT_NOTE exists. + let note = note.ok_or(VmmError::NoNoteSegment)?; - if let Err(e) = data.read_exact(&mut buf) { - *err = RustError::with_source(format_args!("couldn't read kernel note #{i} data"), e) - .into_c(); - return null_mut(); - } + // Seek to PT_NOTE. + let mut data: std::io::Take<&mut std::fs::File> = kernel_img + .segment_data(¬e) + .map_err(|e| VmmError::SeekToNote(e, path.to_path_buf()))?; + + // Parse PT_NOTE. + let mut vm_page_size = None; - // Check name. - let name = match CStr::from_bytes_until_nul(&buf) { - Ok(v) if v.to_bytes_with_nul().len() == nlen => v, - _ => { - *err = RustError::new(format!("kernel note #{i} has invalid name")).into_c(); - return null_mut(); + for i in 0u32.. { + // Check remaining data. + if data.limit() == 0 { + break; } - }; - if name.to_bytes() != b"obkrnl" { - continue; - } + // Read note header. + let mut buf = [0u8; 4 * 3]; - // Parse description. - match ty { - 0 => { - if vm_page_size.is_some() { - *err = RustError::new(format!("kernel note #{i} is duplicated")).into_c(); - return null_mut(); - } + data.read_exact(&mut buf) + .map_err(|e| VmmError::ReadKernelNote(e, i))?; - vm_page_size = buf[nalign..] - .try_into() - .map(usize::from_ne_bytes) - .ok() - .and_then(NonZero::new) - .filter(|v| v.is_power_of_two()); - - if vm_page_size.is_none() { - *err = - RustError::new(format!("invalid description on kernel note #{i}")).into_c(); - return null_mut(); - } - } - v => { - *err = RustError::new(format!("unknown type {v} on kernel note #{i}")).into_c(); - return null_mut(); - } - } - } + // Parse note header. + let nlen: usize = u32::from_ne_bytes(buf[..4].try_into().unwrap()) + .try_into() + .unwrap(); + let dlen: usize = u32::from_ne_bytes(buf[4..8].try_into().unwrap()) + .try_into() + .unwrap(); + let ty = u32::from_ne_bytes(buf[8..].try_into().unwrap()); - // Check if page size exists. - let vm_page_size = match vm_page_size { - Some(v) => v, - None => { - *err = RustError::new("no page size in kernel note").into_c(); - return null_mut(); - } - }; - - // Get page size on the host. - let host_page_size = match get_page_size() { - Ok(v) => v, - Err(e) => { - *err = RustError::with_source("couldn't get host page size", e).into_c(); - return null_mut(); - } - }; + if nlen > 0xff { + return Err(VmmError::NoteNameTooLarge(i)); + } - // Get kernel memory size. - let mut len = 0; + if dlen > 0xff { + return Err(VmmError::InvalidNoteDescription(i)); + } - for hdr in &segments { - if hdr.p_vaddr < len { - *err = RustError::new(format!( - "PT_LOAD at {:#x} is overlapped with the previous PT_LOAD", - hdr.p_vaddr - )) - .into_c(); + // Read note name + description. + let nalign = nlen.next_multiple_of(4); + let mut buf = vec![0u8; nalign + dlen]; - return null_mut(); - } + data.read_exact(&mut buf) + .map_err(|e| VmmError::ReadKernelNoteData(e, i))?; - len = match hdr.p_vaddr.checked_add(hdr.p_memsz) { - Some(v) => v, - None => { - *err = RustError::new(format!("invalid p_memsz on PT_LOAD at {:#x}", hdr.p_vaddr)) - .into_c(); - return null_mut(); - } - }; - } + // Check name. + let name = match CStr::from_bytes_until_nul(&buf) { + Ok(v) if v.to_bytes_with_nul().len() == nlen => v, + _ => return Err(VmmError::InvalidNoteName(i)), + }; - // Round kernel memory size. - let block_size = max(vm_page_size, host_page_size); - let len = match len { - 0 => { - *err = RustError::new("the kernel has PT_LOAD with zero length").into_c(); - return null_mut(); - } - v => match v.checked_next_multiple_of(block_size.get()) { - Some(v) => NonZero::new_unchecked(v), - None => { - *err = RustError::new("total size of PT_LOAD is too large").into_c(); - return null_mut(); + if name.to_bytes() != b"obkrnl" { + continue; } - }, - }; - - // Setup virtual devices. - let ram = NonZero::new(1024 * 1024 * 1024 * 8).unwrap(); - let event = VmmEventHandler { fp: event, cx }; - let devices = Arc::new(setup_devices(ram.get(), block_size, event)); - - // Setup hypervisor. - let mut hv = match self::hv::new(8, ram, block_size, debugger.is_some()) { - Ok(v) => v, - Err(e) => { - *err = RustError::with_source("couldn't setup a hypervisor", e).into_c(); - return null_mut(); - } - }; - - // Map the kernel. - let feats = hv.cpu_features().clone(); - let mut ram = RamBuilder::new(hv.ram_mut()); - let kern = match ram.alloc_kernel(len) { - Ok(v) => v, - Err(e) => { - *err = RustError::with_source("couldn't allocate RAM for the kernel", e).into_c(); - return null_mut(); - } - }; - - for hdr in &segments { - // Seek to segment data. - let mut data = match file.segment_data(hdr) { - Ok(v) => v, - Err(e) => { - *err = RustError::with_source( - format_args!("couldn't seek to offset {}", hdr.p_offset), - e, - ) - .into_c(); - - return null_mut(); - } - }; - // Read segment data. - let mut seg = &mut kern[hdr.p_vaddr..(hdr.p_vaddr + hdr.p_memsz)]; - - match std::io::copy(&mut data, &mut seg) { - Ok(v) => { - if v != hdr.p_filesz { - *err = RustError::new(format!("{path} is incomplete")).into_c(); - return null_mut(); + // Parse description. + match ty { + 0 => { + if vm_page_size.is_some() { + return Err(VmmError::DuplicateKernelNote(i)); + } + vm_page_size = buf[nalign..] + .try_into() + .map(usize::from_ne_bytes) + .ok() + .and_then(NonZero::new) + .filter(|v| v.is_power_of_two()); + + if vm_page_size.is_none() { + return Err(VmmError::InvalidNoteDescription(i)); + } } - } - Err(e) => { - *err = RustError::with_source( - format_args!("couldn't read kernet at offset {}", hdr.p_offset), - e, - ) - .into_c(); - - return null_mut(); + v => return Err(VmmError::UnknownKernelNoteType(v, i)), } } - } - // Allocate stack. - if let Err(e) = ram.alloc_stack(NonZero::new(1024 * 1024 * 2).unwrap()) { - *err = RustError::with_source("couldn't allocate RAM for stack", e).into_c(); - return null_mut(); - } + // Check if page size exists. + let vm_page_size = vm_page_size.ok_or(VmmError::NoPageSizeInKernelNote)?; - // Allocate arguments. - let env = BootEnv::Vm(Vm { - vmm: devices.vmm().addr(), - console: devices.console().addr(), - host_page_size, - }); + // Get page size on the host. + let host_page_size = get_page_size().map_err(VmmError::GetHostPageSize)?; - if let Err(e) = ram.alloc_args(env, (*profile).kernel_config().clone()) { - *err = RustError::with_source("couldn't allocate RAM for arguments", e).into_c(); - return null_mut(); - } + // Get kernel memory size. + let mut len = 0; - // Build RAM. - let map = match ram.build(&feats, vm_page_size, &devices, dynamic) { - Ok(v) => v, - Err(e) => { - *err = RustError::with_source("couldn't build RAM", e).into_c(); - return null_mut(); - } - }; - - // Setup screen. - let screen = match crate::screen::Default::new(&*screen) { - Ok(v) => v, - Err(e) => { - *err = RustError::with_source("couldn't setup a screen", e).into_c(); - return null_mut(); - } - }; - - // Setup CPU manager. - let shutdown = Arc::new(AtomicBool::new(false)); - let mut cpu = CpuManager::new( - Arc::new(hv), - screen.buffer().clone(), - devices, - event, - shutdown.clone(), - ); - - // Setup GDB stub. - let gdb = match debugger - .map(|client| { - gdbstub::stub::GdbStub::new(*client) - .run_state_machine(&mut cpu) - .map_err(|e| RustError::with_source("couldn't setup a GDB stub", e)) - }) - .transpose() - { - Ok(v) => v, - Err(e) => { - *err = e.into_c(); - return null_mut(); + for hdr in &segments { + if hdr.p_vaddr < len { + return Err(VmmError::OverlappedLoadSegment(hdr.p_vaddr)); + } + + len = hdr + .p_vaddr + .checked_add(hdr.p_memsz) + .ok_or(VmmError::InvalidPmemsz(hdr.p_vaddr))?; } - }; - // Spawn main CPU. - cpu.spawn(map.kern_vaddr + file.entry(), Some(map), gdb.is_some()); + // Round kernel memory size. + let block_size = max(vm_page_size, host_page_size); + let len = NonZero::new(len) + .ok_or(VmmError::ZeroLengthLoadSegment)? + .get() + .checked_next_multiple_of(block_size.get()) + .ok_or(VmmError::TotalSizeTooLarge)?; + + // Setup RAM. + let ram_size = NonZero::new(1024 * 1024 * 1024 * 8).unwrap(); + + // Setup virtual devices. + let event = VmmEventHandler { + event: event_handler, + cx, + }; + let devices = Arc::new(setup_devices(ram_size.get(), block_size, event)); - // Create VMM. - let vmm = Vmm { - cpu, - screen, - gdb, - shutdown, - }; + // Setup hypervisor. + let mut hv = unsafe { self::hv::new(8, ram_size, block_size, debugger.is_some()) } + .map_err(VmmError::SetupHypervisor)?; - Box::into_raw(vmm.into()) -} + // Map the kernel. + let feats = hv.cpu_features().clone(); + let mut ram = RamBuilder::new(hv.ram_mut()); -#[no_mangle] -pub unsafe extern "C" fn vmm_free(vmm: *mut Vmm) { - drop(Box::from_raw(vmm)); -} + let kern = ram + .alloc_kernel(NonZero::new(len).unwrap()) + .map_err(VmmError::AllocateRamForKernel)?; -#[no_mangle] -pub unsafe extern "C" fn vmm_draw(vmm: *mut Vmm) -> *mut RustError { - match (*vmm).screen.update() { - Ok(_) => null_mut(), - Err(e) => RustError::wrap(e).into_c(), - } -} + for hdr in &segments { + // Seek to segment data. + let mut data = kernel_img + .segment_data(hdr) + .map_err(VmmError::SeekToOffset)?; -#[no_mangle] -pub unsafe extern "C" fn vmm_dispatch_debug(vmm: *mut Vmm, stop: *mut KernelStop) -> DebugResult { - // Consume stop reason now to prevent memory leak. - let vmm = &mut *vmm; - let mut stop = match stop.is_null() { - true => None, - false => Some(Box::from_raw(stop).0), - }; - - loop { - // Check current state. - let r = match vmm.gdb.take().unwrap() { - GdbStubStateMachine::Idle(s) => match self::debug::dispatch_idle(&mut vmm.cpu, s) { - Ok(Ok(v)) => Ok(v), - Ok(Err(v)) => { - // No pending data from the debugger. - vmm.gdb = Some(v.into()); - return DebugResult::Ok; - } - Err(e) => Err(e), - }, - GdbStubStateMachine::Running(s) => { - match self::debug::dispatch_running(&mut vmm.cpu, s, stop.take()) { - Ok(Ok(v)) => Ok(v), - Ok(Err(v)) => { - // No pending data from the debugger. - vmm.gdb = Some(v.into()); - return DebugResult::Ok; + // Read segment data. + let mut seg = &mut kern[hdr.p_vaddr..(hdr.p_vaddr + hdr.p_memsz)]; + + match std::io::copy(&mut data, &mut seg) { + Ok(v) => { + if v != hdr.p_filesz { + return Err(VmmError::IncompleteKernel(path.to_path_buf())); } - Err(e) => Err(e), } + Err(e) => return Err(VmmError::ReadKernel(e, hdr.p_offset)), } - GdbStubStateMachine::CtrlCInterrupt(s) => { - vmm.cpu.lock(); - - s.interrupt_handled( - &mut vmm.cpu, - Some(MultiThreadStopReason::Signal(Signal::SIGINT)), - ) - .map_err(|e| RustError::with_source("couldn't handle CTRL+C from a debugger", e)) - } - GdbStubStateMachine::Disconnected(_) => return DebugResult::Disconnected, - }; - - match r { - Ok(v) => vmm.gdb = Some(v), - Err(e) => return DebugResult::Error { reason: e.into_c() }, } - } -} - -#[no_mangle] -pub unsafe extern "C" fn vmm_debug_socket(vmm: *mut Vmm) -> isize { - let s = match &mut (*vmm).gdb { - Some(v) => v, - None => return -1, - }; - - match s { - GdbStubStateMachine::Idle(s) => s.borrow_conn().socket() as _, - GdbStubStateMachine::Running(s) => s.borrow_conn().socket() as _, - GdbStubStateMachine::CtrlCInterrupt(s) => s.borrow_conn().socket() as _, - GdbStubStateMachine::Disconnected(s) => s.borrow_conn().socket() as _, - } -} -#[no_mangle] -pub unsafe extern "C" fn vmm_shutdown(vmm: *mut Vmm) { - (*vmm).shutdown.store(true, Ordering::Relaxed); -} - -#[no_mangle] -pub unsafe extern "C" fn vmm_shutting_down(vmm: *mut Vmm) -> bool { - (*vmm).shutdown.load(Ordering::Relaxed) -} - -#[cfg(unix)] -fn get_page_size() -> Result, std::io::Error> { - let v = unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) }; + // Allocate stack. + ram.alloc_stack(NonZero::new(1024 * 1024 * 2).unwrap()) + .map_err(VmmError::AllocateRamForStack)?; + + // Allocate arguments. + let env = BootEnv::Vm(Vm { + vmm: devices.vmm().addr(), + console: devices.console().addr(), + host_page_size, + }); + + ram.alloc_args(env, profile.kernel_config().clone()) + .map_err(VmmError::AllocateRamForArgs)?; + + // Build RAM. + let map = ram + .build(&feats, vm_page_size, &devices, dynamic) + .map_err(VmmError::BuildRam)?; + + // Setup screen. + let screen = crate::screen::Default::from_screen(screen).map_err(VmmError::SetupScreen)?; + + // Setup CPU manager. + let shutdown = Arc::new(AtomicBool::new(false)); + let mut cpu_manager = CpuManager::new( + Arc::new(hv), + screen.buffer().clone(), + devices, + event, + shutdown.clone(), + ); + + // Setup GDB stub. + let gdb = debugger + .map(|client| { + gdbstub::stub::GdbStub::new(client) + .run_state_machine(&mut cpu_manager) + .map_err(VmmError::SetupGdbStub) + }) + .transpose()?; + + // Spawn main CPU. + cpu_manager.spawn( + map.kern_vaddr + kernel_img.entry(), + Some(map), + gdb.is_some(), + ); + + // Create VMM. + let vmm = Vmm { + cpu: cpu_manager, + screen, + gdb, + shutdown, + }; - if v < 0 { - Err(std::io::Error::last_os_error()) - } else { - Ok(v.try_into().ok().and_then(NonZero::new).unwrap()) + Ok(vmm) } } -#[cfg(windows)] -fn get_page_size() -> Result, std::io::Error> { - use std::mem::zeroed; - use windows_sys::Win32::System::SystemInformation::GetSystemInfo; - let mut i = unsafe { zeroed() }; - - unsafe { GetSystemInfo(&mut i) }; - - Ok(i.dwPageSize.try_into().ok().and_then(NonZero::new).unwrap()) -} - -/// Manage a virtual machine that run the kernel. -pub struct Vmm { - cpu: CpuManager, // Drop first. - screen: crate::screen::Default, - gdb: Option< - GdbStubStateMachine< - 'static, - CpuManager, - DebugClient, - >, - >, - shutdown: Arc, -} - impl Drop for Vmm { fn drop(&mut self) { // Set shutdown flag before dropping the other fields so their background thread can stop @@ -615,13 +374,13 @@ pub struct VmmScreen { /// Encapsulates a function to handle VMM events. #[derive(Clone, Copy)] struct VmmEventHandler { - fp: unsafe extern "C" fn(*const VmmEvent, *mut c_void), + event: unsafe extern "C" fn(*const VmmEvent, *mut c_void), cx: *mut c_void, } impl VmmEventHandler { unsafe fn invoke(self, e: VmmEvent) { - (self.fp)(&e, self.cx) + (self.event)(&e, self.cx); } } @@ -684,6 +443,116 @@ pub enum DebugResult { Error { reason: *mut RustError }, } +#[derive(Debug, Error)] +pub enum VmmError { + #[error("couldn't open kernel path {1}")] + OpenKernel(#[source] KernelError, PathBuf), + + #[error("couldn't start enumerating program headers of {1}")] + EnumerateProgramHeaders(#[source] std::io::Error, PathBuf), + + #[error("couldn't read program header #{1} on {2}")] + ReadProgramHeader(#[source] ProgramHeaderError, usize, PathBuf), + + #[error("invalid p_filesz on on PT_LOAD {0}")] + InvalidFilesz(usize), + + #[error("multiple PT_DYNAMIC is not supported")] + MultipleDynamic, + + #[error("multiple PT_NOTE is not supported")] + MultipleNote, + + #[error("unknown p_type {0} on program header {1}")] + UnknownProgramHeaderType(u32, usize), + + #[error("the first PT_LOAD does not include ELF header")] + ElfHeaderNotInFirstLoadSegment, + + #[error("no PT_LOAD on the kernel")] + NoLoadSegment, + + #[error("no PT_DYNAMIC on the kernel")] + NoDynamicSegment, + + #[error("no PT_NOTE on the kernel")] + NoNoteSegment, + + #[error("couldn't seek to PT_NOTE on {1}")] + SeekToNote(#[source] std::io::Error, PathBuf), + + #[error("couldn't read kernel note #{1}")] + ReadKernelNote(#[source] std::io::Error, u32), + + #[error("name on kernel note #{0} is too large")] + NoteNameTooLarge(u32), + + #[error("invalid description on kernel note #{0}")] + InvalidNoteDescription(u32), + + #[error("couldn't read kernel note #{1} data")] + ReadKernelNoteData(#[source] std::io::Error, u32), + + #[error("kernel note #{0} has invalid name")] + InvalidNoteName(u32), + + #[error("kernel note #{0} is duplicated")] + DuplicateKernelNote(u32), + + #[error("unknown type {0} on kernel note #{1}")] + UnknownKernelNoteType(u32, u32), + + #[error("no page size in kernel note")] + NoPageSizeInKernelNote, + + #[error("couldn't get host page size")] + GetHostPageSize(#[source] std::io::Error), + + #[error("PT_LOAD at {0:#} is overlapped with the previous PT_LOAD")] + OverlappedLoadSegment(usize), + + #[error("invalid p_memsz on PT_LOAD at {0:#}")] + InvalidPmemsz(usize), + + #[error("the kernel has PT_LOAD with zero length")] + ZeroLengthLoadSegment, + + #[error("total size of PT_LOAD is too large")] + TotalSizeTooLarge, + + #[error("couldn't setup a hypervisor")] + SetupHypervisor(#[source] hv::HypervisorError), + + #[error("couldn't allocate RAM for the kernel")] + AllocateRamForKernel(#[source] hv::RamError), + + #[error("couldn't seek to offset")] + SeekToOffset(#[source] std::io::Error), + + #[error("{0} is incomplete")] + IncompleteKernel(PathBuf), + + #[error("couldn't read kernel at offset {1}")] + ReadKernel(#[source] std::io::Error, u64), + + #[error("couldn't allocate RAM for stack")] + AllocateRamForStack(#[source] hv::RamError), + + #[error("couldn't allocate RAM for arguments")] + AllocateRamForArgs(#[source] hv::RamError), + + #[error("couldn't build RAM")] + BuildRam(#[source] ram::RamBuilderError), + + #[error("couldn't setup a screen")] + SetupScreen(#[source] crate::screen::ScreenError), + + #[error("couldn't setup a GDB stub")] + SetupGdbStub( + #[source] GdbStubError::Error>, + ), +} + /// Represents an error when [`main_cpu()`] fails to reach event loop. #[derive(Debug, Error)] enum MainCpuError { diff --git a/gui/vulkan.cpp b/gui/vulkan.cpp index 2faaa6e77..6149658f4 100644 --- a/gui/vulkan.cpp +++ b/gui/vulkan.cpp @@ -3,251 +3,3 @@ #include QVulkanFunctions *vkFunctions; - -extern "C" VkResult vmm_vk_enumerate_physical_devices( - VkInstance instance, - uint32_t *p_physical_device_count, - VkPhysicalDevice *p_physical_devices) -{ - return vkFunctions->vkEnumeratePhysicalDevices( - instance, - p_physical_device_count, - p_physical_devices); -} - -extern "C" void vmm_vk_get_physical_device_features( - VkPhysicalDevice physical_device, - VkPhysicalDeviceFeatures *p_features) -{ - vkFunctions->vkGetPhysicalDeviceFeatures(physical_device, p_features); -} - -extern "C" void vmm_vk_get_physical_device_format_properties( - VkPhysicalDevice physical_device, - VkFormat format, - VkFormatProperties *p_format_properties) -{ - vkFunctions->vkGetPhysicalDeviceFormatProperties(physical_device, format, p_format_properties); -} - -extern "C" VkResult vmm_vk_get_physical_device_image_format_properties( - VkPhysicalDevice physical_device, - VkFormat format, - VkImageType ty, - VkImageTiling tiling, - VkImageUsageFlags usage, - VkImageCreateFlags flags, - VkImageFormatProperties *p_image_format_properties) -{ - return vkFunctions->vkGetPhysicalDeviceImageFormatProperties( - physical_device, - format, - ty, - tiling, - usage, - flags, - p_image_format_properties); -} - -extern "C" void vmm_vk_get_physical_device_properties( - VkPhysicalDevice physical_device, - VkPhysicalDeviceProperties *p_properties) -{ - vkFunctions->vkGetPhysicalDeviceProperties(physical_device, p_properties); -} - -extern "C" void vmm_vk_get_physical_device_queue_family_properties( - VkPhysicalDevice physical_device, - uint32_t *p_queue_family_property_count, - VkQueueFamilyProperties *p_queue_family_properties) -{ - vkFunctions->vkGetPhysicalDeviceQueueFamilyProperties( - physical_device, - p_queue_family_property_count, - p_queue_family_properties); -} - -extern "C" void vmm_vk_get_physical_device_memory_properties( - VkPhysicalDevice physical_device, - VkPhysicalDeviceMemoryProperties *p_memory_properties) -{ - vkFunctions->vkGetPhysicalDeviceMemoryProperties(physical_device, p_memory_properties); -} - -extern "C" PFN_vkVoidFunction vmm_vk_get_device_proc_addr(VkDevice device, const char *p_name) -{ - return vkFunctions->vkGetDeviceProcAddr(device, p_name); -} - -extern "C" VkResult vmm_vk_create_device( - VkPhysicalDevice physical_device, - const VkDeviceCreateInfo *p_create_info, - const VkAllocationCallbacks *p_allocator, - VkDevice *p_device) -{ - return vkFunctions->vkCreateDevice(physical_device, p_create_info, p_allocator, p_device); -} - -extern "C" VkResult vmm_vk_enumerate_device_extension_properties( - VkPhysicalDevice physical_device, - const char *p_layer_name, - uint32_t *p_property_count, - VkExtensionProperties *p_properties) -{ - return vkFunctions->vkEnumerateDeviceExtensionProperties( - physical_device, - p_layer_name, - p_property_count, - p_properties); -} - -extern "C" VkResult vmm_vk_enumerate_device_layer_properties( - VkPhysicalDevice physical_device, - uint32_t *p_property_count, - VkLayerProperties *p_properties) -{ - return vkFunctions->vkEnumerateDeviceLayerProperties( - physical_device, - p_property_count, - p_properties); -} - -extern "C" void vmm_vk_get_physical_device_sparse_image_format_properties( - VkPhysicalDevice physical_device, - VkFormat format, - VkImageType ty, - VkSampleCountFlagBits samples, - VkImageUsageFlags usage, - VkImageTiling tiling, - uint32_t *p_property_count, - VkSparseImageFormatProperties *p_properties) -{ - vkFunctions->vkGetPhysicalDeviceSparseImageFormatProperties( - physical_device, - format, - ty, - samples, - usage, - tiling, - p_property_count, - p_properties); -} - -extern "C" VkResult vmm_vk_enumerate_physical_device_groups( - VkInstance instance, - uint32_t *p_physical_device_group_count, - VkPhysicalDeviceGroupProperties *p_physical_device_group_properties) -{ - return vkFunctions->vkEnumeratePhysicalDeviceGroups( - instance, - p_physical_device_group_count, - p_physical_device_group_properties); -} - -extern "C" void vmm_vk_get_physical_device_features2( - VkPhysicalDevice physical_device, - VkPhysicalDeviceFeatures2 *p_features) -{ - vkFunctions->vkGetPhysicalDeviceFeatures2(physical_device, p_features); -} - -extern "C" void vmm_vk_get_physical_device_properties2( - VkPhysicalDevice physical_device, - VkPhysicalDeviceProperties2 *p_properties) -{ - vkFunctions->vkGetPhysicalDeviceProperties2(physical_device, p_properties); -} - -extern "C" void vmm_vk_get_physical_device_format_properties2( - VkPhysicalDevice physical_device, - VkFormat format, - VkFormatProperties2 *p_format_properties) -{ - vkFunctions->vkGetPhysicalDeviceFormatProperties2(physical_device, format, p_format_properties); -} - -extern "C" VkResult vmm_vk_get_physical_device_image_format_properties2( - VkPhysicalDevice physical_device, - const VkPhysicalDeviceImageFormatInfo2 *p_image_format_info, - VkImageFormatProperties2 *p_image_format_properties) -{ - return vkFunctions->vkGetPhysicalDeviceImageFormatProperties2( - physical_device, - p_image_format_info, - p_image_format_properties); -} - -extern "C" void vmm_vk_get_physical_device_queue_family_properties2( - VkPhysicalDevice physical_device, - uint32_t *p_queue_family_property_count, - VkQueueFamilyProperties2 *p_queue_family_properties) -{ - vkFunctions->vkGetPhysicalDeviceQueueFamilyProperties2( - physical_device, - p_queue_family_property_count, - p_queue_family_properties); -} - -extern "C" void vmm_vk_get_physical_device_memory_properties2( - VkPhysicalDevice physical_device, - VkPhysicalDeviceMemoryProperties2 *p_memory_properties) -{ - vkFunctions->vkGetPhysicalDeviceMemoryProperties2(physical_device, p_memory_properties); -} - -extern "C" void vmm_vk_get_physical_device_sparse_image_format_properties2( - VkPhysicalDevice physical_device, - const VkPhysicalDeviceSparseImageFormatInfo2 *p_format_info, - uint32_t *p_property_count, - VkSparseImageFormatProperties2 *p_properties) -{ - vkFunctions->vkGetPhysicalDeviceSparseImageFormatProperties2( - physical_device, - p_format_info, - p_property_count, - p_properties); -} - -extern "C" void vmm_vk_get_physical_device_external_buffer_properties( - VkPhysicalDevice physical_device, - const VkPhysicalDeviceExternalBufferInfo *p_external_buffer_info, - VkExternalBufferProperties *p_external_buffer_properties) -{ - vkFunctions->vkGetPhysicalDeviceExternalBufferProperties( - physical_device, - p_external_buffer_info, - p_external_buffer_properties); -} - -extern "C" void vmm_vk_get_physical_device_external_fence_properties( - VkPhysicalDevice physical_device, - const VkPhysicalDeviceExternalFenceInfo *p_external_fence_info, - VkExternalFenceProperties *p_external_fence_properties) -{ - vkFunctions->vkGetPhysicalDeviceExternalFenceProperties( - physical_device, - p_external_fence_info, - p_external_fence_properties); -} - -extern "C" void vmm_vk_get_physical_device_external_semaphore_properties( - VkPhysicalDevice physical_device, - const VkPhysicalDeviceExternalSemaphoreInfo *p_external_semaphore_info, - VkExternalSemaphoreProperties *p_external_semaphore_properties) -{ - vkFunctions->vkGetPhysicalDeviceExternalSemaphoreProperties( - physical_device, - p_external_semaphore_info, - p_external_semaphore_properties); -} - -extern "C" VkResult vmm_vk_get_physical_device_tool_properties( - VkPhysicalDevice physical_device, - uint32_t *p_tool_count, - VkPhysicalDeviceToolProperties *p_tool_properties) -{ - return vkFunctions->vkGetPhysicalDeviceToolProperties( - physical_device, - p_tool_count, - p_tool_properties); -} diff --git a/src/param/src/lib.rs b/src/param/src/lib.rs index 9568dfa60..ac2689953 100644 --- a/src/param/src/lib.rs +++ b/src/param/src/lib.rs @@ -1,5 +1,7 @@ use byteorder::{ByteOrder, BE, LE}; +use std::fs::File; use std::io::{ErrorKind, Read, Seek, SeekFrom}; +use std::path::Path; use thiserror::Error; /// A loaded param.sfo. @@ -16,6 +18,12 @@ pub struct Param { } impl Param { + pub fn open(path: impl AsRef) -> Result { + let file = File::open(path).map_err(OpenError::OpenFailed)?; + + Self::read(file).map_err(OpenError::ReadFailed) + } + pub fn read(mut raw: R) -> Result { // Seek to the beginning. if let Err(e) = raw.seek(SeekFrom::Start(0)) { @@ -238,6 +246,15 @@ impl Param { } } +#[derive(Debug, Error)] +pub enum OpenError { + #[error("couldn't open the file")] + OpenFailed(#[source] std::io::Error), + + #[error("couldn't read the file")] + ReadFailed(#[source] ReadError), +} + /// Errors for reading param.sfo. #[derive(Debug, Error)] pub enum ReadError {