commit 5c87063c487ea29933f28311384489816bf5b303 Author: Aubrey Taylor Date: Tue Feb 25 00:43:18 2025 -0600 Initial changes (mostly working) diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..7520df8 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +RUSTC_BOOTSTRAP = "1" diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..8392d15 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7f97f47 --- /dev/null +++ b/.gitignore @@ -0,0 +1,126 @@ +# User-specific stuff +/.idea + +*.iml +*.ipr +*.iws + +# IntelliJ +/out +# mpeltonen/sbt-idea plugin +/.idea_modules + +# JIRA plugin +atlassian-ide-plugin.xml + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +/.gradle +/build + +# Ignore Gradle GUI config +gradle-app.setting + +# Cache of project +.gradletasknamecache + +**/build/ + +# Common working directory +/run +/runs + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# direnv +/.direnv + +# rust +/rust/target +/target diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..9e85bcc --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,2 @@ +Copyright (c) 2025 +All rights reserved. diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..262875d --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,115 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("jvm") version "2.1.10" + id("fabric-loom") version "1.9-SNAPSHOT" + id("maven-publish") +} + +version = project.property("mod_version") as String +group = project.property("maven_group") as String + +base { + archivesName.set(project.property("archives_base_name") as String) +} + +val targetJavaVersion = 21 +java { + toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) + // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task + // if it is present. + // If you remove this line, sources will not be generated. + withSourcesJar() +} + +loom { + mods { + register("golemcomputers") { + sourceSet("main") + } + } +} + +repositories { + // Add repositories to retrieve artifacts from in here. + // You should only use this when depending on other mods because + // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. + // See https://docs.gradle.org/current/userguide/declaring_repositories.html + // for more information about repositories. +} + +configurations { + create("rustlib") { + isCanBeConsumed = false + isCanBeResolved = true + } +} + + +dependencies { + // To change the versions see the gradle.properties file + minecraft("com.mojang:minecraft:${project.property("minecraft_version")}") + mappings("net.fabricmc:yarn:${project.property("yarn_mappings")}:v2") + modImplementation("net.fabricmc:fabric-loader:${project.property("loader_version")}") + modImplementation("net.fabricmc:fabric-language-kotlin:${project.property("kotlin_loader_version")}") + + modImplementation("net.fabricmc.fabric-api:fabric-api:${project.property("fabric_version")}") + add("rustlib", project(":rust")) + implementation("com.github.jhg023:Pbbl:1.0.2") +} + +tasks.processResources { + inputs.property("version", project.version) + inputs.property("minecraft_version", project.property("minecraft_version")) + inputs.property("loader_version", project.property("loader_version")) + filteringCharset = "UTF-8" + + from(configurations.getByName("rustlib")) + + filesMatching("fabric.mod.json") { + expand( + "version" to project.version, + "minecraft_version" to project.property("minecraft_version"), + "loader_version" to project.property("loader_version"), + "kotlin_loader_version" to project.property("kotlin_loader_version") + ) + } +} + +tasks.withType().configureEach { + // ensure that the encoding is set to UTF-8, no matter what the system default is + // this fixes some edge cases with special characters not displaying correctly + // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html + // If Javadoc is generated, this must be specified in that task too. + options.encoding = "UTF-8" + options.release.set(targetJavaVersion) +} + +tasks.withType().configureEach { + compilerOptions.jvmTarget.set(JvmTarget.fromTarget(targetJavaVersion.toString())) +} + +tasks.jar { + from("LICENSE") { + rename { "${it}_${project.base.archivesName}" } + } +} + +// configure the maven publication +publishing { + publications { + create("mavenJava") { + artifactId = project.property("archives_base_name") as String + from(components["java"]) + } + } + + // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. + repositories { + // Add repositories to publish to here. + // Notice: This block does NOT have the same function as the block in the top level. + // The repositories here will be used for publishing your artifact, not for + // retrieving dependencies. + } +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..977ad7a --- /dev/null +++ b/flake.lock @@ -0,0 +1,113 @@ +{ + "nodes": { + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1740119667, + "narHash": "sha256-8LsFuU6ORym0DUIqp0Kga95w5T+9UnN3aHaephEakvw=", + "owner": "nix-community", + "repo": "fenix", + "rev": "019d950c784141c8f3ba6ad5d8a53f72fee0953f", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1738453229, + "narHash": "sha256-7H9XgNiGLKN1G1CgRh0vUL4AheZSYzPm+zmZ7vxbJdo=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "32ea77a06711b758da0ad9bd6a844c5740a87abd", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1739866667, + "narHash": "sha256-EO1ygNKZlsAC9avfcwHkKGMsmipUk1Uc0TbrEZpkn64=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "73cf49b8ad837ade2de76f87eb53fc85ed5d4680", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1738452942, + "narHash": "sha256-vJzFZGaCpnmo7I6i416HaBLpC+hvcURh/BQwROcGIp8=", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz" + } + }, + "root": { + "inputs": { + "fenix": "fenix", + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs", + "systems": "systems" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1740077634, + "narHash": "sha256-KlYdDhon/hy91NutuBeN8e3qTKf3FXgsudWsjnHud68=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "88fbdcd510e79ef3bcd81d6d9d4f07bdce84be8c", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..64a4d5f --- /dev/null +++ b/flake.nix @@ -0,0 +1,72 @@ +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + flake-parts.url = "github:hercules-ci/flake-parts"; + fenix = { + url = "github:nix-community/fenix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + systems.url = "github:nix-systems/default"; + }; + + outputs = inputs: + inputs.flake-parts.lib.mkFlake {inherit inputs;} { + systems = import inputs.systems; + perSystem = { + config, + self', + pkgs, + lib, + system, + ... + }: let + fenix = inputs.fenix.packages.${system}; + libs = with pkgs; [ + (fenix.fromToolchainFile { + file = ./rust-toolchain.toml; + sha256 = "sha256-AJ6LX/Q/Er9kS15bn9iflkUwcgYqRQxiOIL2ToVAXaU="; + }) + fenix.rust-analyzer + wabt + maven + cmake + ninja + pkg-config + + #needed for minecraft to function properly + libGL + glfw-wayland-minecraft + libpulseaudio + openal + flite + ]; + mkDevshell = openIdea: + pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + jetbrains.jdk + ]; + + buildInputs = libs; + LD_LIBRARY_PATH = lib.makeLibraryPath libs; + + env = { + JAVA_HOME = "${pkgs.jetbrains.jdk}/lib/openjdk/"; + }; + + shellHook = + if openIdea + then '' + idea-community ./ + exit + '' + else ""; + }; + in { + formatter = pkgs.alejandra; + devShells = { + default = mkDevshell false; + idea = mkDevshell true; + }; + }; + }; +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..c4c3c2a --- /dev/null +++ b/gradle.properties @@ -0,0 +1,16 @@ +# Done to increase the memory available to gradle. +org.gradle.jvmargs=-Xmx1G +# Fabric Properties +# check these on https://modmuss50.me/fabric.html +minecraft_version=1.21.4 +yarn_mappings=1.21.4+build.8 +loader_version=0.16.10 +kotlin_loader_version=1.13.1+kotlin.2.1.10 +# Mod Properties +mod_version=1.0-SNAPSHOT +maven_group=ca.sanae +archives_base_name=GolemComputers +# Dependencies +# check this on https://modmuss50.me/fabric.html +fabric_version=0.117.0+1.21.4 +chicory_version=1.0.0 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ab6f03b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..ac293e1 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.85" +profile = "complete" diff --git a/rust/Cargo.lock b/rust/Cargo.lock new file mode 100644 index 0000000..864f869 --- /dev/null +++ b/rust/Cargo.lock @@ -0,0 +1,842 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", + "tracing", +] + +[[package]] +name = "async-signal" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitfield-struct" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2be5a46ba01b60005ae2c51a36a29cfe134bcacae2dd5cedcd4615fbaad1494b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +dependencies = [ + "serde", +] + +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bytes" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" + +[[package]] +name = "cc" +version = "1.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" +dependencies = [ + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "disarm64" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ccd32ba4e20b8b4a0db9c11d3a34a38ef03d729aa1d9ea4c9cd5dc6b29b5946" +dependencies = [ + "bitfield-struct", + "disarm64_defn", +] + +[[package]] +name = "disarm64_defn" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a968ec75d668b2e37898ba8b84d522e7261abde8721d0da8d877c2f51d911b2" +dependencies = [ + "bitflags", + "serde", + "strum", +] + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "golem-computers" +version = "0.1.0" +dependencies = [ + "bitfield-struct", + "disarm64", + "jni", + "num-derive", + "num-traits", + "oneshot", + "pollster", + "smol", + "static_assertions", + "unicorn-engine", + "zerocopy", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "libc" +version = "0.2.170" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "log" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "oneshot" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce411919553d3f9fa53a0880544cda985a112117a0444d5ff1e870a893d6ea" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "polling" +version = "3.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.218" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.218" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smol" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" +dependencies = [ + "async-channel", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-net", + "async-process", + "blocking", + "futures-lite", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" + +[[package]] +name = "unicode-ident" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" + +[[package]] +name = "unicorn-engine" +version = "2.1.1" +source = "git+https://github.com/Sanae6/unicorn/?branch=cpreg#31789c88932fc9a025d009e1f668b15d36fb28f9" +dependencies = [ + "bitflags", + "cc", + "cmake", + "libc", + "pkg-config", + "zerocopy", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde3bb8c68a8f3f1ed4ac9221aad6b10cece3e60a8e2ea54a6a2dec806d0084c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eea57037071898bf96a6da35fd626f4f27e9cee3ead2a6c703cf09d472b2e700" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 0000000..89627a2 --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "golem-computers" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +bitfield-struct = "0.10.1" +disarm64 = "0.1.24" +jni = "0.21.1" +num-derive = "0.4.2" +num-traits = "0.2.19" +oneshot = "0.1.10" +pollster = "0.4.0" +smol = "2.0.2" +static_assertions = "1.1.0" +unicorn-engine = { version = "2.1.1", default-features = false, features = ["arch_aarch64"], git = "https://github.com/Sanae6/unicorn/", branch = "cpreg" } +zerocopy = { version = "0.8.20", features = ["derive"] } + +[profile.release] +panic = "abort" +debug-assertions = true diff --git a/rust/build.gradle.kts b/rust/build.gradle.kts new file mode 100644 index 0000000..b501b7e --- /dev/null +++ b/rust/build.gradle.kts @@ -0,0 +1,20 @@ +val task = tasks.register("default") { + commandLine("cargo", "build", "--release") + val library = System.mapLibraryName("golem_computers") + val libraryPath = "$workingDir/target/release/$library" + outputs.file(libraryPath) + inputs.dir("$workingDir/src") + + doLast { + if (!file(libraryPath).exists()) { + throw GradleException("the library wasn't properly build! expected $libraryPath") + } + } +} + +configurations { + create("default") { + + } +} +artifacts.add("default", task) \ No newline at end of file diff --git a/rust/src/component/mmio.rs b/rust/src/component/mmio.rs new file mode 100644 index 0000000..1762c67 --- /dev/null +++ b/rust/src/component/mmio.rs @@ -0,0 +1,251 @@ +use std::{ + mem::offset_of, + ops::{Deref, DerefMut, Range}, + pin::Pin, + sync::{atomic::Ordering, Arc}, +}; + +use bitfield_struct::bitfield; +use jni::JavaVM; +use unicorn_engine::Unicorn; +use zerocopy::{ + big_endian::U16 as U16BE, + little_endian::{U16, U32, U64}, + FromBytes, Immutable, IntoBytes, KnownLayout, +}; + +use crate::{ + align_up, component::{Component, ComponentInterface}, core::CpuContext, memory_map::RAM_START, overlapping, range_of_field, unsync_cell::UnsyncCell, unsync_read +}; + +use super::ComponentDma; + +pub struct ComponentIoResult { + component_index: usize, + direction: ComponentIoDirection, +} + +pub enum ComponentIoDirection { + ToComponent, + ToGuest, +} + +impl Component { + pub fn perform_write( + &self, + range: Range, + writer: impl FnOnce(&UnsyncCell), + ram: Arc>, + java_vm: JavaVM, + ) -> Option>>> { + let Self::Populated { + read_transfer_active, + write_transfer_active, + info, + interface, + own_index, + java_component, + .. + } = self + else { + return None; + }; + + if overlapping(&range, &range_of_field!(ComponentInterface, info)) { + return None; + } + let read_range = range_of_field!(ComponentInterface, read_dma); + if overlapping(&range, &read_range) { + if read_transfer_active.load(Ordering::Acquire) || read_range != range { + return None; + } + + writer(interface); + + let dma: ComponentDma = unsync_read!(interface, ComponentInterface, read_dma); + if dma.dma_enabled() + && read_transfer_active.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire).is_ok() + { + if !overlapping( + &(dma.address..dma.address + dma.size()), + &(RAM_START..RAM_START + ram.len() as u64), + ) { + read_transfer_active.store(false, Ordering::Release); + return None; + } + let bytes_per_chunk: u32 = info.bytes_per_tick / 5; + let java_component = + let index = return Some(Box::pin(async move { + let java_component = java_component; + ComponentIoResult { component_index: 0, direction: todo!() } + })); + } + + return None; + } + // let write_range = range_of_field!(ComponentInterface, write_dma); + // if write_transfer_active && overlapping(range.clone(), write_range) { + // return; + // } + + return None; + } +} + +#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)] +#[repr(C)] +struct ComponentMmioHeader { + component_count: U32, + /// version bump when component access method (offset table) changes or [ComponentInfo] is non-additively changed + version: U16, + control: U16BE, +} + +#[bitfield(u16)] +#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)] +pub struct ComponentMmioControl { + interrupt_added: bool, + interrupt_removed: bool, + #[bits(2)] + _reserved: U16, + /// read-write, indicates the core to fire interrupts on. indicating an invalid core means no core will receive the interrupt + #[bits(4)] + interrupt_firing_core: u8, + #[bits(8)] + _reserved: U16, +} + +pub fn calculate_components_start(component_count: usize) -> usize { + OFFSETS_START + align_up(component_count as u64 * 4 + 16, 0x1000) as usize +} + +fn read_bytes_to_u64(bytes: &[u8]) -> u64 { + match bytes.len() { + 1 => bytes[0] as u64, + 2 => U16::read_from_bytes(bytes).unwrap().get() as u64, + 4 => U32::read_from_bytes(bytes).unwrap().get() as u64, + 8 => U64::read_from_bytes(bytes).unwrap().get() as u64, + 16 => todo!("128 bit reads are currently neither implemented here nor supported by unicorn hooks"), + _ => panic!("expected byte slice of primitive size"), + } +} + +fn write_bytes_from_u64(bytes: &mut [u8], value: u64) { + match bytes.len() { + 1 => bytes[0] = value.try_into().unwrap(), + 2 => U16::new(value.try_into().unwrap()).write_to(bytes).unwrap(), + 4 => U32::new(value.try_into().unwrap()).write_to(bytes).unwrap(), + 8 => U64::new(value).write_to(bytes).unwrap(), + 16 => todo!("128 bit writes are currently neither implemented here nor supported by unicorn hooks"), + _ => panic!("expected byte slice of primitive size"), + } +} + +const OFFSETS_START: usize = 0x200; +const INTERFACE_INTERVAL: usize = 0x800; + +pub fn component_mmio_read(vcpu: &mut Unicorn, offset: u64, size: usize) -> u64 { + let offset = offset as usize; + let component_count = vcpu.get_data().components.len(); + if offset < OFFSETS_START { + // mmio header + println!("reading from mmio start!"); + let header = ComponentMmioHeader { + component_count: U32::new(component_count as u32), + version: U16::new(0), + control: U16BE::new(vcpu.get_data().component_control.read().into_bits()), + }; + if offset > size_of::() { + return 0; + } + let mut region = [0; size_of::() + size_of::()]; + header.write_to_prefix(&mut region).unwrap(); + let range = offset as usize..(offset + size) as usize; + return read_bytes_to_u64(®ion[range]); + } + + if offset < vcpu.get_data().component_data_start { + // offsets + let range = offset as usize..(offset + size) as usize; + let components_range = OFFSETS_START..(size_of::() * component_count); + + let range = range.start - components_range.start as usize..range.end - components_range.start as usize; + let first_offset_index = range.start >> 2; + let offset_buffer: [U32; 3] = std::array::from_fn(|index| { + (first_offset_index + index < component_count as usize) + .then_some(U32::new( + ((first_offset_index + index) * INTERFACE_INTERVAL as usize) as u32, + )) + .unwrap_or_default() + }); + + return read_bytes_to_u64(&offset_buffer.as_bytes()[range]); + } + + // info region + + let offset = offset - vcpu.get_data().component_data_start; + let index = offset / INTERFACE_INTERVAL as usize; + let offset = offset - (index * INTERFACE_INTERVAL as usize); + + let Some(Component::Populated { interface, .. }) = vcpu.get_data().components.get(index) else { + return 0; + }; + + match size { + 1 => interface.read_into::(offset).into(), + 2 => interface.read_into::(offset).into(), + 4 => interface.read_into::(offset).into(), + 8 => interface.read_into::(offset).into(), + _ => unreachable!(), + } +} + +pub fn component_mmio_write(vcpu: &mut Unicorn, offset: u64, size: usize, value: u64) { + let offset = offset as usize; + let range = offset..offset + size; + + if offset < OFFSETS_START { + if overlapping( + &range, + &(offset_of!(ComponentMmioHeader, control)..size_of::()), + ) { + // control included + let mut buffer = [0; size_of::() + size_of::()]; + write_bytes_from_u64(&mut buffer[range], value); + let header = ComponentMmioHeader::ref_from_prefix(&buffer).unwrap().0; + + vcpu.get_data().component_control.write(ComponentMmioControl::from_bits(header.control.get())); + } + + return; + } + + if offset < vcpu.get_data().component_data_start { + return; + } + + let offset = offset - vcpu.get_data().component_data_start; + let index = offset / INTERFACE_INTERVAL as usize; + let offset = offset - (index * INTERFACE_INTERVAL as usize); + + let Some(component) = vcpu.get_data().components.get(index) else { + return; + }; + + if offset > size_of::() { + return; + } + + component.perform_write( + range, + |interface| match size { + 1 => interface.write_from(offset, &(value as u8)), + 2 => interface.write_from(offset, &U16::new(value as u16)), + 4 => interface.write_from(offset, &U32::new(value as u32)), + 8 => interface.write_from(offset, &U64::new(value as u64)), + _ => unreachable!(), + }, + vcpu.get_data().ram, + ); +} diff --git a/rust/src/component/mod.rs b/rust/src/component/mod.rs new file mode 100644 index 0000000..9fd9c24 --- /dev/null +++ b/rust/src/component/mod.rs @@ -0,0 +1,120 @@ +pub mod mmio; + +use std::{ + cell::RefCell, + mem::offset_of, + ops::{Deref, DerefMut, Range, RangeBounds}, + pin::Pin, + rc::Rc, + sync::{ + atomic::{AtomicBool, Ordering}, + mpsc, Arc, LazyLock, Mutex, + }, +}; + +use bitfield_struct::bitfield; +use jni::objects::GlobalRef; +use unicorn_engine::Unicorn; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +use crate::{ + core::CpuContext, memory_map::RAM_START, overlapping, range_of_field, subtract_range, unsync_cell::UnsyncCell, + unsync_read, +}; + +#[derive(Default)] +pub enum Component { + #[default] + Empty, + Populated { + own_index: usize, + java_component: GlobalRef, + read_transfer_active: AtomicBool, + write_transfer_active: AtomicBool, + info: ComponentInfo, + interface: UnsyncCell, + }, +} + +#[derive(Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)] +#[repr(C)] +pub struct ComponentInterface { + // read only + pub info: ComponentInfo, + + // read-write + control: ComponentControl, + _restart_device: u8, + _reserved: [u8; 6], + read_dma: ComponentDma, + write_dma: ComponentDma, +} + +#[bitfield(u8)] +#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)] +pub struct ComponentControl { + interrupt_when_added: bool, + interrupt_when_removed: bool, + #[bits(2)] + _reserved: u8, + + #[bits(4)] + interrupt_firing_core: u8, +} + +#[derive(Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)] +#[repr(C)] +pub struct ComponentDma { + pub upper_word: ComponentDmaUpper, + pub address: u64, +} + +impl Deref for ComponentDma { + type Target = ComponentDmaUpper; + + fn deref(&self) -> &Self::Target { + &self.upper_word + } +} + +impl DerefMut for ComponentDma { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.upper_word + } +} + +#[bitfield(u64)] +#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)] +pub struct ComponentDmaUpper { + dma_enabled: bool, + #[bits(3)] + _reserved: u8, + /// read-write, indicates the core to fire interrupts on. indicating an invalid core means no core will receive the interrupt + #[bits(4)] + interrupt_firing_core: u8, + + #[bits(56)] + size: u64, +} + +#[derive(Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)] +#[repr(C)] +pub struct ComponentInfo { + /// a null terminated string, the type name of the component + pub type_name: [u8; 256], + /// a null terminated string, the protocol identifier for how to talk to the component + pub protocol: [u8; 256], + pub bytes_per_tick: u32, + pub version: u32, +} + +impl Default for ComponentInfo { + fn default() -> Self { + Self { + type_name: [0; 256], + protocol: [0; 256], + bytes_per_tick: 0, + version: 0, + } + } +} diff --git a/rust/src/computer.rs b/rust/src/computer.rs new file mode 100644 index 0000000..b0dcfb4 --- /dev/null +++ b/rust/src/computer.rs @@ -0,0 +1,202 @@ +use std::sync::{Arc, OnceLock, RwLock}; + +use jni::{ + objects::{JByteBuffer, JIntArray, JObject, JString, JValue}, + signature::{Primitive, ReturnType}, + strings::JNIString, + sys::{jint, jlong}, + JNIEnv, +}; + +use crate::{ + assert_positive, + component::{mmio::ComponentMmioControl, Component, ComponentInfo, ComponentInterface}, + core::CoreHandle, + unsync_cell::UnsyncCell, +}; + +pub struct Computer { + pub components: Arc<[RwLock]>, + pub component_control: Arc>, + pub shared_ram: Arc>, + pub shared_rom: Arc>, + + pub cores: OnceLock>, +} + +impl Computer { + #[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_new")] + extern "C" fn new<'local>( + mut env: JNIEnv<'local>, + java_computer: JObject<'local>, + component_capacity: i32, + memory_size: i32, + core_speeds: JIntArray<'local>, + ) { + let (component_capacity, memory_size): (usize, usize) = ( + assert_positive!(env, component_capacity), + assert_positive!(env, memory_size), + ); + + let core_count = env.get_array_length(&core_speeds).unwrap() as usize; + if core_count > 16 { + env.throw("no more than 16 cores are allowed").unwrap(); + return; + } + println!("got to computer construction!"); + let computer = Box::new(Self { + components: { + let mut components = Vec::with_capacity(component_capacity); + components.extend(std::iter::from_fn(|| Default::default()).take(component_capacity)); + Arc::from(components.into_boxed_slice()) + }, + component_control: Arc::new(UnsyncCell::new(ComponentMmioControl::new())), + shared_ram: Arc::from(UnsyncCell::new_zeroed_box(memory_size as usize)), + shared_rom: Arc::from(UnsyncCell::new_zeroed_box(0x1000000 as usize)), + // late initalized, see below + cores: OnceLock::new(), + }); + + println!("core construction"); + let mut speeds = [0; 16]; + env.get_int_array_region(core_speeds, 0, &mut speeds[..core_count]).unwrap(); + println!("core speeds acquired {:?}", &speeds[..core_count]); + let Ok(cores) = speeds + .into_iter() + .take(core_count) + .enumerate() + .map(|(core_index, speed)| { + Ok(CoreHandle::new( + &computer, + core_index, + assert_positive!(env, speed, Err(())), + env.get_java_vm().unwrap(), + )) + }) + .collect::, _>>() + else { + return; + }; + + let _ = computer.cores.set(cores.into_boxed_slice()); + + println!("storing computer info in field!"); + + env + .set_field( + &java_computer, + "address", + "J", + JValue::Long(Box::into_raw(computer) as jlong), + ) + .unwrap(); + } + + fn from_jni<'local>(env: &mut JNIEnv<'local>, computer: JObject<'local>) -> *mut Self { + env + .get_field_unchecked( + &computer, + (env.get_object_class(&computer).unwrap(), "address", "J"), + ReturnType::Primitive(Primitive::Long), + ) + .unwrap() + .j() + .unwrap() as *mut _ + } + + #[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_insertComponentNative")] + unsafe extern "C" fn insert_component<'local>( + mut env: JNIEnv<'local>, + computer: JObject<'local>, + component_index: jint, + component_object: JObject<'local>, + type_name: JString<'local>, + protocol: JString<'local>, + bytes_per_tick: u32, + version: u32, + ) { + // Safety: the responsibility of asserting the pointer is valid is on the java caller + let computer = unsafe { &*Self::from_jni(&mut env, computer) }; + + let component_index: usize = assert_positive!(env, component_index); + let mut make_info_string = |string: JString<'local>| { + let mut bytes = [0u8; 256]; + let jstring = env.get_string(&string).unwrap(); + + let length = bytes.len().min(jstring.to_bytes().len()); + bytes[..length].copy_from_slice(&jstring.to_bytes()[..length]); + + bytes + }; + let type_name = make_info_string(type_name); + let protocol = make_info_string(protocol); + let info = ComponentInfo { + type_name, + protocol, + bytes_per_tick, + version, + }; + let java_component = env.new_global_ref(component_object).unwrap(); + *computer.components[component_index].write().unwrap() = Component::Populated { + own_index: component_index, + java_component, + read_transfer_active: false.into(), + write_transfer_active: false.into(), + info, + interface: ComponentInterface::default().into(), + }; + } + + #[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_updateRomTest")] + unsafe extern "C" fn update_rom_test<'local>( + mut env: JNIEnv<'local>, + computer: JObject<'local>, + bytes: JByteBuffer<'local>, + ) { + // Safety: the responsibility of asserting the pointer is valid is on the java caller + let computer = unsafe { &*Self::from_jni(&mut env, computer) }; + + computer.shared_rom.update_from_jni(&mut env, &bytes).expect("failed to update rom"); + } + + #[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_updateRomFile")] + unsafe extern "C" fn update_rom_file<'local>(env: JNIEnv<'local>, computer: JObject<'local>) { + drop((env, computer)); + todo!() + } + + #[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_free")] + unsafe extern "C" fn free<'local>(mut env: JNIEnv<'local>, computer: JObject<'local>) { + let computer = Self::from_jni(&mut env, computer); + // Safety: the responsibility of asserting the pointer is valid is on the java caller + drop(unsafe { Box::from_raw(computer) }) + } + + #[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_wakeCoreAt")] + unsafe extern "C" fn wake_core_at<'local>( + mut env: JNIEnv<'local>, + computer: JObject<'local>, + core: jint, + address: jlong, + ) { + let (core, address): (usize, u64) = (assert_positive!(env, core), assert_positive!(env, address)); + // Safety: the responsibility of asserting the pointer is valid is on the java caller + let computer = unsafe { &*Self::from_jni(&mut env, computer) }; + + let Some(core) = computer.cores.get().unwrap().get(core) else { + env.throw("core index was out of bounds").unwrap(); + return; + }; + core.wake_at(address); + } + + #[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_tick")] + unsafe extern "C" fn tick<'local>(mut env: JNIEnv<'local>, computer: JObject<'local>) { + // Safety: the responsibility of asserting the pointer is valid is on the java caller + let computer = unsafe { &*Self::from_jni(&mut env, computer) }; + let cores = &computer.cores.get().unwrap(); + for core in cores.iter() { + core.tick(); + } + } +} diff --git a/rust/src/core.rs b/rust/src/core.rs new file mode 100644 index 0000000..d75ab58 --- /dev/null +++ b/rust/src/core.rs @@ -0,0 +1,235 @@ +use std::{ + cell::{OnceCell, RefCell}, + pin::Pin, + rc::Rc, + sync::{mpsc, Arc, RwLock}, +}; + +use disarm64::decoder; +use jni::{JNIEnv, JavaVM}; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; +use unicorn_engine::{Arch, ArmCpRegister, HookType, Mode, Permission, Query, RegisterARM64, Unicorn}; +use zerocopy::IntoBytes; + +use crate::{component::{mmio::{calculate_components_start, component_mmio_read, component_mmio_write, ComponentMmioControl}, Component, ComponentIoResult}, computer::Computer, unsync_cell::UnsyncCell}; + +pub struct CoreHandle { + message_sender: mpsc::Sender, +} + +/* + + + +todo REWRITEEEEEEE RAAAAH + + + + */ + +pub struct CpuContext<'a> { + component_data_start: usize, + components: Arc<[RwLock]>, + /// see comment on [Computer] + component_control: Arc>, + message_sender: mpsc::Sender, + component_access_futures: Vec>>>, + ram: Arc>, + stopped: bool, + _env: JNIEnv<'a>, +} + +enum CpuMessage { + Tick, + Wake(Option), + Sleep, +} + +impl CoreHandle { + pub fn new(computer: &Computer, core_index: usize, instructions_per_tick: u32, java_vm: JavaVM) -> Self { + let (components, component_control) = (computer.components.clone(), computer.component_control.clone()); + let (started_up_sender, started_up_receiver) = oneshot::channel(); + + let (message_sender, message_receiver) = mpsc::channel(); + let memories = (computer.shared_ram.clone(), computer.shared_rom.clone()); + + std::thread::spawn({ + let message_sender = message_sender.clone(); + + move || { + let env = java_vm.attach_current_thread_permanently().unwrap(); + + let context = CpuContext { + component_data_start: calculate_components_start(components.len()), + components, + component_control, + message_sender, + component_access_futures: Vec::new(), + ram: memories.0.clone(), + _env: env, + stopped: true, + }; + thread( + core_index, + instructions_per_tick, + memories, + context, + started_up_sender, + message_receiver, + ) + } + }); + + let _ = started_up_receiver.recv().expect("failed to start up core"); + + CoreHandle { message_sender } + } + + #[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_RustNative_coreFree")] + unsafe extern "C" fn free<'local>(_env: JNIEnv<'local>, core: *const Self) { + // Safety: the responsibility of verifying the pointer is valid is on the java caller + drop(unsafe { Arc::from_raw(core) }) + } + + pub fn tick(&self) { + self.message_sender.send(CpuMessage::Tick).expect("cpu thread panicked :("); + } + + pub fn wake_at(&self, start: u64) { + self.message_sender.send(CpuMessage::Wake(Some(start))).unwrap(); + } +} + +#[derive(Debug, FromPrimitive)] +enum Exception { + UndefinedInstruction = 1, + SupervisorCall = 2, + PrefetchAbort = 3, + DataAbort = 4, + Interrupt = 5, + FastInterrupt = 6, + Breakpoint = 7, + ExceptionExit = 8, + KernelTrap = 9, + HypervisorCall = 11, + HypervisorTrap = 12, + SecureMonitorCall = 13, + VirtualInterrupt = 14, + VirtualFastInterrupt = 15, + Semihost = 16, + Nocp = 17, + Invstate = 18, + Stkof = 19, + Lazyfp = 20, + Lserr = 21, + Unaligned = 22, +} + +fn thread( + core_index: usize, + instructions_per_tick: u32, + (shared_ram, shared_rom): (Arc>, Arc>), + context: CpuContext, + started_up_sender: oneshot::Sender<()>, + message_receiver: mpsc::Receiver, +) -> Result<(), ()> { + let mut vcpu = + Unicorn::new_with_data(Arch::ARM64, Mode::LITTLE_ENDIAN, context).expect("failed to spawn unicorn engine"); + + const MPIDR_EL1: ArmCpRegister = ArmCpRegister::from_manual(0b11, 0b000, 0b0000, 0b0000, 0b101); + vcpu.reg_write_cp(CP_REG, MPIDR_EL1, core_index as u64).unwrap(); + + vcpu + .mmio_map( + 0x2000_0000, + 0x1000, + Some(component_mmio_read), + Some(component_mmio_write), + ) + .expect("failed to map mmio for core"); + shared_rom + .map(&mut vcpu, 0x4000_0000, None, Permission::READ | Permission::EXEC) + .expect("failed to map memory for rom"); + shared_ram.map(&mut vcpu, 0x8000_0000, None, Permission::ALL).expect("failed to map memory for rom"); + use RegisterARM64::*; + + vcpu + .add_intr_hook(|vcpu, excp| { + println!("interrupt: {excp}"); + let exception = Exception::from_u32(excp).unwrap(); + + let pc = vcpu.pc_read().unwrap(); + println!("exception: {exception:?}"); + let mut opcode = 0u32; + if let Ok(_) = vcpu.mem_read(pc, opcode.as_mut_bytes()) { + if let Some(opcode) = decoder::decode(opcode) { + let mut inst = String::new(); + disarm64::format_insn::format_insn_pc(pc, &mut inst, &opcode).unwrap(); + println!("pc {pc:08X} {inst}"); + } else { + println!("pc {pc:08X} - op parse failed {opcode:08X}"); + } + } else { + println!("pc {pc:08X} - can't read"); + } + println!("x0 {:08X}", vcpu.reg_read(X0).unwrap()); + println!("x1 {:08X}", vcpu.reg_read(X1).unwrap()); + + let syndrome = vcpu.query(Query::SYNDROME).unwrap(); + println!("0x{syndrome:08X}"); + vcpu.get_data_mut().stopped = true; + vcpu.emu_stop().unwrap(); + }) + .unwrap(); + + vcpu + .add_mem_hook( + HookType::MEM_INVALID, + u64::MIN, + u64::MAX, + |vcpu, mem_type, address, _, value| { + eprintln!("memory exception: {mem_type:?} at {address:X} (value: {value:X})"); + + vcpu.emu_stop().unwrap(); + true + }, + ) + .unwrap(); + + started_up_sender.send(()).expect("panicked when trying to core"); + let try_fetch_message = |stopped| { + if stopped { + message_receiver.recv().map(Some).map_err(|_| ()) + } else { + Ok(message_receiver.try_recv().ok()) + } + }; + + let mut instructions_left = 0; + loop { + while let Some(message) = try_fetch_message(vcpu.get_data().stopped || instructions_left == 0)? { + match message { + CpuMessage::Tick => { + if vcpu.get_data().stopped { + continue; + } + } + CpuMessage::Wake(address) => { + if let Some(address) = address { + vcpu.set_pc(address).unwrap(); + println!("waking at {address}"); + } + vcpu.get_data_mut().stopped = false; + + let pc = vcpu.pc_read().unwrap(); + println!("executing {pc:X}"); + vcpu.emu_start(pc, 0, 0, instructions_per_tick as usize).expect("error while executing cpu"); + } + CpuMessage::Sleep => vcpu.get_data_mut().stopped = true, + } + } + vcpu.emu_start(vcpu.pc_read().unwrap(), 0, 0, instructions_left.min(1000)).expect("error while executing cpu"); + instructions_left = instructions_left.saturating_sub(1000); + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 0000000..def964d --- /dev/null +++ b/rust/src/lib.rs @@ -0,0 +1,86 @@ +#![deny(unsafe_op_in_unsafe_fn)] +#![feature(sync_unsafe_cell)] +#![feature(generic_const_exprs)] + +use std::ops::Range; + +use jni::{ + sys::{jint, JNI_VERSION_1_8}, + JNIEnv, +}; + +pub mod component; +mod computer; +mod core; +pub mod unsync_cell; +mod memory_map; + +#[macro_export] +macro_rules! assert_positive { + ($env: expr, $value: expr) => {{ + let value = $value; + if value < 0 { + ($env) + .throw(format!( + "input {} from java cannot be negative! got {value}", + stringify!($value) + )) + .unwrap(); + return; + } + value as _ + }}; + ($env: expr, $value: expr, $err: expr) => {{ + let value = $value; + if value < 0 { + ($env) + .throw(format!( + "input {} from java cannot be negative! got {value}", + stringify!($value) + )) + .unwrap(); + return $err; + } + value as _ + }}; +} + +#[macro_export] +macro_rules! range_of_field { + ($type: ty, $field: ident) => {{ + const RANGE: std::ops::Range = { + let size = { + let v: std::mem::MaybeUninit<$type> = std::mem::MaybeUninit::uninit(); + size_of_val(unsafe { &v.assume_init_ref().$field }) + }; + + let offset = std::mem::offset_of!($type, $field); + + offset as _..(offset + size) as _ + }; + + let range: std::ops::Range<_> = RANGE.start as _..RANGE.end as _; + range + }}; +} + +fn overlapping(a: &Range, b: &Range) -> bool { + a.contains(&b.start) && a.contains(&b.end) +} + +fn subtract_range(a: &Range, b: &Range) -> Range { + a.start - b.start..a.end - b.end +} + +pub fn align_up(value: u64, alignment: u64) -> u64 { + (value + alignment - 1) & !(alignment - 1) +} + +struct UnicornWrap { + +} + +#[unsafe(no_mangle)] +pub extern "system" fn JNI_OnLoad<'local>(_: JNIEnv<'local>, _: usize) -> jint { + JNI_VERSION_1_8 +} diff --git a/rust/src/memory_map.rs b/rust/src/memory_map.rs new file mode 100644 index 0000000..db4b51c --- /dev/null +++ b/rust/src/memory_map.rs @@ -0,0 +1,3 @@ +pub const COMPONENTS_START: u64 = 0x2000_0000; +pub const ROM_START: u64 = 0x4000_0000; +pub const RAM_START: u64 = 0x8000_0000; diff --git a/rust/src/unsync_cell.rs b/rust/src/unsync_cell.rs new file mode 100644 index 0000000..9050b55 --- /dev/null +++ b/rust/src/unsync_cell.rs @@ -0,0 +1,137 @@ +use std::cell::SyncUnsafeCell; + +use jni::{objects::JByteBuffer, JNIEnv}; +use unicorn_engine::{uc_error, Permission, Unicorn}; +use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout}; + +/// A simple replacement for [std::cell::Cell] in Sync contexts which explicitly does not try to synchronize the contents. +/// This is used for contexts where unsynchronized access is explicitly fine. +/// ## Dropping: +/// This type does not drop the value stored inside it if you write a new value to it. Generally the intention of the type is to store primitives and slices, +/// so this shouldn't be a problem for anyone, but **be warned**. +#[derive(Default, FromBytes, IntoBytes, KnownLayout)] +#[repr(transparent)] +pub struct UnsyncCell(SyncUnsafeCell); + +impl UnsyncCell { + pub fn as_mut_ptr(&self) -> *mut T { + self.0.get() + } +} + +impl UnsyncCell { + pub fn new(value: T) -> Self { + Self(SyncUnsafeCell::new(value)) + } + + pub fn write(&self, value: T) { + unsafe { self.as_mut_ptr().write(value) }; + } + + pub fn read(&self) -> T { + unsafe { self.as_mut_ptr().read() } + } +} + +impl UnsyncCell { + // N must be the same as the size of U + pub fn read_into(&self, offset: usize) -> U + where + [u8; size_of::()]:, + { + let mut buffer = [0; size_of::()]; + let end = (offset + size_of::()).min(size_of::()); + assert!(end - offset <= size_of::()); + // Safety: both pointers are valid for 0..(end - offset) + unsafe { (self.as_mut_ptr() as *const u8).copy_to(buffer.as_mut_ptr(), end - offset) }; + U::read_from_bytes(&buffer).expect("N was not the same as the size of U") + } +} + +impl UnsyncCell { + pub fn write_from(&self, offset: usize, value: &U) { + let end = (offset + size_of::()).min(size_of::()); + assert!(end - offset <= size_of::()); + unsafe { (self.as_mut_ptr() as *mut u8).copy_from(value.as_bytes().as_ptr(), end - offset) }; + } +} + +#[macro_export] +macro_rules! unsync_read { + ($value: expr, $T: ty, $field: ident) => { + $crate::unsync_cell::UnsyncCell::<$T>::read_into(&($value), std::mem::offset_of!($T, $field)) + }; +} + +impl UnsyncCell<[T]> { + pub fn new_zeroed_box(size: usize) -> Box { + let mut memory = Box::new_uninit_slice(size); + memory.zero(); + + // Safety: UnsafeCell transparently wraps the slice, meaning the two types are semantically identical + unsafe { std::mem::transmute::, Box>(memory.assume_init()) } + } + + fn as_mut_ptr(&self) -> *mut [T] { + self.0.get() + } + + pub fn len(&self) -> usize { + self.as_mut_ptr().len() + } + + pub fn update_from_slice(&self, bytes: &[u8]) -> Result<(), usize> { + if self.len() < bytes.len() { + return Err(bytes.len() - self.len()); + } + + // Safety: our slice is valid for bytes.len(), both are u8 pointers and therefore aligned + unsafe { + (self.as_mut_ptr() as *mut u8).copy_from(bytes.as_ptr(), bytes.len()); + }; + + Ok(()) + } + + pub fn update_from_jni(&self, env: &mut JNIEnv, byte_buffer: &JByteBuffer) -> Result<(), usize> { + let size = env.get_direct_buffer_capacity(&byte_buffer).expect("failed to get byte buffer size"); + if self.len() < size { + return Err(size - self.len()); + } + let address = env.get_direct_buffer_address(&byte_buffer).expect("failed to get byte buffer address"); + + // Safety: our slice is valid for bytes.len(), jni gives us a valid pointer for bytes.len(), both are u8 pointers and therefore aligned + unsafe { + (self.as_mut_ptr() as *mut u8).copy_from(address, size); + }; + + Ok(()) + } + + pub fn map( + &self, + vcpu: &mut Unicorn, + address: u64, + size: Option, + permissions: Permission, + ) -> Result<(), uc_error> { + let size = size.unwrap_or(self.len()); + if size > self.len() { + return Err(uc_error::ARG); + } + println!("mapping {permissions:?} at {address:08X} for {size:08X}"); + unsafe { vcpu.mem_map_ptr(address, size, permissions, self.as_mut_ptr() as *mut _) } + } +} + +impl Clone for UnsyncCell { + fn clone(&self) -> Self { + Self(SyncUnsafeCell::new(unsafe { self.0.get().read() })) + } +} + +impl From for UnsyncCell { + fn from(value: T) -> Self { + Self(value.into()) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..61d9d66 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + maven("https://maven.fabricmc.net/") { + name = "Fabric" + } + gradlePluginPortal() + mavenCentral() + } +} +include("rust") diff --git a/src/main/kotlin/ca/sanae/golemcomputers/GolemComputers.kt b/src/main/kotlin/ca/sanae/golemcomputers/GolemComputers.kt new file mode 100644 index 0000000..1342ea9 --- /dev/null +++ b/src/main/kotlin/ca/sanae/golemcomputers/GolemComputers.kt @@ -0,0 +1,61 @@ +package ca.sanae.golemcomputers + +import ca.sanae.golemcomputers.blocks.GolemBlock +import ca.sanae.golemcomputers.blocks.GolemBlockEntity +import ca.sanae.golemcomputers.computer.RustNative +import com.mojang.serialization.Codec +import net.fabricmc.api.ModInitializer +import net.fabricmc.fabric.api.`object`.builder.v1.block.entity.FabricBlockEntityTypeBuilder +import net.minecraft.block.Block +import net.minecraft.block.entity.BlockEntity +import net.minecraft.block.entity.BlockEntityType +import net.minecraft.component.ComponentType +import net.minecraft.network.codec.PacketCodecs +import net.minecraft.registry.Registries +import net.minecraft.registry.Registry +import net.minecraft.registry.RegistryKey +import net.minecraft.registry.RegistryKeys +import net.minecraft.text.Text +import net.minecraft.text.TextCodecs +import net.minecraft.util.Identifier + + +class GolemComputers : ModInitializer { + companion object { + const val MOD_ID = "golem_computers" + + fun id(path: String): Identifier { + return Identifier.of(MOD_ID, path) + } + + val GOLEM_BLOCK_KEY: RegistryKey = RegistryKey.of(RegistryKeys.BLOCK, id("golem")) + val GOLEM_BLOCK = register(GOLEM_BLOCK_KEY, GolemBlock()) + val GOLEM_BLOCK_ENTITY = register(GOLEM_BLOCK_KEY, ::GolemBlockEntity, GOLEM_BLOCK) + + private fun register(key: RegistryKey, block: T): T { + return Registry.register(Registries.BLOCK, key, block) + } + + private fun register(key: RegistryKey>, componentType: ComponentType): ComponentType { + return Registry.register(Registries.DATA_COMPONENT_TYPE, key, componentType) + } + + private fun register( + key: RegistryKey, + entityFactory: FabricBlockEntityTypeBuilder.Factory, + vararg blocks: Block + ): BlockEntityType { + return Registry.register( + Registries.BLOCK_ENTITY_TYPE, + key.value, + FabricBlockEntityTypeBuilder.create(entityFactory, *blocks).build() + ) + } + } + + override fun onInitialize() { + if (!RustNative.isInitialized()) { + error("Rust binary failed to load!") + } + } +} diff --git a/src/main/kotlin/ca/sanae/golemcomputers/blocks/GolemBlock.kt b/src/main/kotlin/ca/sanae/golemcomputers/blocks/GolemBlock.kt new file mode 100644 index 0000000..d3cdabd --- /dev/null +++ b/src/main/kotlin/ca/sanae/golemcomputers/blocks/GolemBlock.kt @@ -0,0 +1,59 @@ +package ca.sanae.golemcomputers.blocks + +import ca.sanae.golemcomputers.GolemComputers +import com.mojang.serialization.MapCodec +import net.minecraft.block.BlockState +import net.minecraft.block.BlockWithEntity +import net.minecraft.block.entity.BlockEntity +import net.minecraft.block.entity.BlockEntityTicker +import net.minecraft.block.entity.BlockEntityType +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.sound.BlockSoundGroup +import net.minecraft.text.Text +import net.minecraft.util.ActionResult +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.math.BlockPos +import net.minecraft.world.World + +class GolemBlock : + BlockWithEntity( + Settings.create().registryKey(GolemComputers.GOLEM_BLOCK_KEY).sounds(BlockSoundGroup.METAL).strength(100.0f) + ) { + override fun getCodec(): MapCodec { + return createCodec(::GolemBlock::call) + } + + override fun createBlockEntity(pos: BlockPos, state: BlockState): BlockEntity { + return GolemBlockEntity(pos, state) + } + + override fun getTicker( + world: World?, + state: BlockState?, + type: BlockEntityType? + ): BlockEntityTicker? { + return validateTicker( + type, + GolemComputers.GOLEM_BLOCK_ENTITY + ) { _, _, _, golemBlockEntity -> golemBlockEntity.tick() } + } + + override fun onUse( + state: BlockState, + world: World, + pos: BlockPos, + player: PlayerEntity, + hit: BlockHitResult + ): ActionResult { + val blockEntity = world.getBlockEntity(pos) + if (blockEntity !is GolemBlockEntity) + return super.onUse(state, world, pos, player, hit) + if (world.isClient()) { + return ActionResult.SUCCESS + } + blockEntity.incrementClicks(); + player.sendMessage(Text.literal("You've clicked the block for the ${blockEntity.getClicks()}th time."), true); + + return ActionResult.SUCCESS_SERVER + } +} \ No newline at end of file diff --git a/src/main/kotlin/ca/sanae/golemcomputers/blocks/GolemBlockEntity.kt b/src/main/kotlin/ca/sanae/golemcomputers/blocks/GolemBlockEntity.kt new file mode 100644 index 0000000..be6f6d1 --- /dev/null +++ b/src/main/kotlin/ca/sanae/golemcomputers/blocks/GolemBlockEntity.kt @@ -0,0 +1,44 @@ +package ca.sanae.golemcomputers.blocks + +import ca.sanae.golemcomputers.GolemComputers +import ca.sanae.golemcomputers.computer.Computer +import net.fabricmc.api.EnvType +import net.fabricmc.api.Environment +import net.minecraft.block.BlockState +import net.minecraft.block.entity.BlockEntity +import net.minecraft.nbt.NbtCompound +import net.minecraft.registry.RegistryWrapper +import net.minecraft.util.math.BlockPos +import net.minecraft.world.World + +class GolemBlockEntity(pos: BlockPos?, state: BlockState?) : + BlockEntity(GolemComputers.GOLEM_BLOCK_ENTITY, pos, state) { + private var computer: Computer? = null + + fun getClicks(): Int { + return 0 + } + + fun incrementClicks() { + markDirty() + } + + override fun writeNbt(nbt: NbtCompound, registries: RegistryWrapper.WrapperLookup?) { + nbt.putInt("clicks", 0) + super.writeNbt(nbt, registries) + } + + override fun readNbt(nbt: NbtCompound, registries: RegistryWrapper.WrapperLookup?) { + nbt.getInt("clicks") + super.readNbt(nbt, registries) + } + + override fun setWorld(world: World?) { + computer = if (!world!!.isClient) Computer(world) else null + super.setWorld(world) + } + + fun tick() { + computer?.tick() + } +} \ No newline at end of file diff --git a/src/main/kotlin/ca/sanae/golemcomputers/client/GolemComputersClient.kt b/src/main/kotlin/ca/sanae/golemcomputers/client/GolemComputersClient.kt new file mode 100644 index 0000000..283c52a --- /dev/null +++ b/src/main/kotlin/ca/sanae/golemcomputers/client/GolemComputersClient.kt @@ -0,0 +1,8 @@ +package ca.sanae.golemcomputers.client + +import net.fabricmc.api.ClientModInitializer + +class GolemComputersClient : ClientModInitializer { + override fun onInitializeClient() { + } +} diff --git a/src/main/kotlin/ca/sanae/golemcomputers/computer/Computer.kt b/src/main/kotlin/ca/sanae/golemcomputers/computer/Computer.kt new file mode 100644 index 0000000..6586220 --- /dev/null +++ b/src/main/kotlin/ca/sanae/golemcomputers/computer/Computer.kt @@ -0,0 +1,62 @@ +package ca.sanae.golemcomputers.computer + +import ca.sanae.golemcomputers.computer.RustNative.Companion.cleaner +import ca.sanae.golemcomputers.computer.components.ChatComponent +import ca.sanae.golemcomputers.computer.components.Component +import net.minecraft.world.World +import java.nio.ByteBuffer +import java.nio.channels.FileChannel +import java.nio.file.Files +import kotlin.io.path.Path + +class Computer(val world: World) { + @Suppress("unused") + private val address: Long = 0 + + init { + new(1, 0x1000, intArrayOf(0x10000)) + cleaner.register(this) { + free() + } + val path = Path("/home/aubrey/Projects/golem-software/assembly.bin") + val channel = FileChannel.open(path); + val buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()) + val wakeAddress = buffer.getUnsignedInt(); + + updateRomTest(buffer.rewind()) + println("waking at 0x${wakeAddress.toString(16)}") + insertComponent(0, ChatComponent(this)) + wakeCoreAt(0, wakeAddress) + } + + fun ByteBuffer.getUnsignedInt(): Long { + return Integer.reverseBytes(this.getInt()).toLong() and 0xFFFFFFFFL + } + + fun insertComponent(coreIndex: Int, component: Component) { + insertComponentNative( + coreIndex, + component, + component.info.typeName.toString(), + component.info.protocol.toString(), + component.info.bytesPerTick, + component.info.version + ) + } + + private external fun new(componentCapacity: Int, memorySize: Int, coreIps: IntArray): Long + private external fun free() + private external fun updateRomTest(data: ByteBuffer) + private external fun wakeCoreAt(coreIndex: Int, address: Long) + private external fun insertComponentNative( + index: Int, + component: Component, + typeName: String, + protocol: String, + bytesPerTick: UInt, + version: UInt + ) + + external fun tick(); + +} \ No newline at end of file diff --git a/src/main/kotlin/ca/sanae/golemcomputers/computer/IMachine.kt b/src/main/kotlin/ca/sanae/golemcomputers/computer/IMachine.kt new file mode 100644 index 0000000..d3f5327 --- /dev/null +++ b/src/main/kotlin/ca/sanae/golemcomputers/computer/IMachine.kt @@ -0,0 +1,5 @@ +package ca.sanae.golemcomputers.computer + +interface IMachine { + fun getComponents() {} +} \ No newline at end of file diff --git a/src/main/kotlin/ca/sanae/golemcomputers/computer/RustNative.kt b/src/main/kotlin/ca/sanae/golemcomputers/computer/RustNative.kt new file mode 100644 index 0000000..37836f7 --- /dev/null +++ b/src/main/kotlin/ca/sanae/golemcomputers/computer/RustNative.kt @@ -0,0 +1,32 @@ +package ca.sanae.golemcomputers.computer + +import java.io.File +import java.io.FileOutputStream +import java.lang.ref.Cleaner +import java.nio.channels.Channels + + +class RustNative private constructor() { + companion object { + val cleaner: Cleaner = Cleaner.create() + private val instance: RustNative = RustNative(); + + init { + val nativeLibraryName = System.mapLibraryName("golem_computers") + val tempFile: File = File.createTempFile("extracted_", nativeLibraryName) + + val classLoader = RustNative::class.java.getClassLoader() + + Channels.newChannel(classLoader.getResourceAsStream(nativeLibraryName)!!).use { src -> + FileOutputStream(tempFile).channel.use { dst -> + dst.transferFrom(src, 0, Long.MAX_VALUE) + } + } + System.load(tempFile.absolutePath) + } + + fun isInitialized(): Boolean { + return true + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/ca/sanae/golemcomputers/computer/components/ChatComponent.kt b/src/main/kotlin/ca/sanae/golemcomputers/computer/components/ChatComponent.kt new file mode 100644 index 0000000..ff0445e --- /dev/null +++ b/src/main/kotlin/ca/sanae/golemcomputers/computer/components/ChatComponent.kt @@ -0,0 +1,29 @@ +package ca.sanae.golemcomputers.computer.components + +import ca.sanae.golemcomputers.GolemComputers +import ca.sanae.golemcomputers.computer.Computer +import kotlinx.io.bytestring.decodeToString +import kotlinx.io.bytestring.getByteString +import net.minecraft.text.Text +import java.nio.ByteBuffer + +class ChatComponent(private val computer: Computer) : + Component(GolemComputers.id("chat"), GolemComputers.id("string_stream"), 10000, 0u) { + var chatBuffer: String = "" + override fun gotByteChunk(buffer: ByteBuffer) { + chatBuffer += buffer.getByteString().decodeToString() + while (true) { + val index = chatBuffer.indexOf('\u0000') + if (index == -1) break + val (message, new) = chatBuffer.splitAtIndex(index) + chatBuffer = new + computer.world.server!!.sendMessage(Text.of(message)) + } + } + + override fun restarted() { + chatBuffer = "" + } + + fun String.splitAtIndex(index: Int) = take(index) to substring(index) +} \ No newline at end of file diff --git a/src/main/kotlin/ca/sanae/golemcomputers/computer/components/Component.kt b/src/main/kotlin/ca/sanae/golemcomputers/computer/components/Component.kt new file mode 100644 index 0000000..0a49acd --- /dev/null +++ b/src/main/kotlin/ca/sanae/golemcomputers/computer/components/Component.kt @@ -0,0 +1,31 @@ +package ca.sanae.golemcomputers.computer.components + +import com.github.pbbl.heap.ByteBufferPool +import com.jogamp.common.util.LFRingbuffer +import com.jogamp.common.util.Ringbuffer +import net.minecraft.util.Identifier +import java.nio.ByteBuffer + +abstract class Component(val info: ComponentInfo) { + @Suppress("unused") + private val address: Long = 0 + private val pool = ByteBufferPool() + abstract fun gotByteChunk(buffer: ByteBuffer) + private external fun writeBytesNative(buffer: ByteBuffer): Boolean + fun writeBytes(lambda: ((size: Int) -> ByteBuffer) -> ByteBuffer) { + synchronized(pool) { + val buffer = lambda(pool::take) + val overflowed = writeBytesNative(buffer) + pool.give(buffer) + if (overflowed) { + overflowShutdown() + } + } + } + + // called when the component runs out of runway to store unread data + open fun overflowShutdown() {} + abstract fun restarted() + + data class ComponentInfo(val typeName: Identifier, val protocol: Identifier, val bytesPerTick: UInt, val version: UInt) +} \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..bca4b94 --- /dev/null +++ b/src/main/resources/fabric.mod.json @@ -0,0 +1,29 @@ +{ + "schemaVersion": 1, + "id": "golem_computers", + "version": "${version}", + "name": "GolemComputers", + "description": "", + "authors": [], + "contact": {}, + "license": "All-Rights-Reserved", + "icon": "assets/golem_computers/icon.png", + "environment": "*", + "entrypoints": { + "client": [ + "ca.sanae.golemcomputers.client.GolemComputersClient" + ], + "main": [ + "ca.sanae.golemcomputers.GolemComputers" + ] + }, + "mixins": [ + "golem_computers.mixins.json" + ], + "depends": { + "fabricloader": ">=${loader_version}", + "fabric-language-kotlin": ">=${kotlin_loader_version}", + "fabric": "*", + "minecraft": "${minecraft_version}" + } +} diff --git a/src/main/resources/golem_computers.mixins.json b/src/main/resources/golem_computers.mixins.json new file mode 100644 index 0000000..97e50ce --- /dev/null +++ b/src/main/resources/golem_computers.mixins.json @@ -0,0 +1,11 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "ca.sanae.golemcomputers.mixin", + "compatibilityLevel": "JAVA_21", + "mixins": [ + ], + "injectors": { + "defaultRequire": 1 + } +}