diff --git a/.run/Package Unity.run.xml b/.run/Package Unity.run.xml new file mode 100644 index 0000000..c339d98 --- /dev/null +++ b/.run/Package Unity.run.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.run/Wacs.Core Publish Wacs.Core.dll.run.xml b/.run/Wacs.Core Publish Wacs.Core.dll.run.xml new file mode 100644 index 0000000..9ab2962 --- /dev/null +++ b/.run/Wacs.Core Publish Wacs.Core.dll.run.xml @@ -0,0 +1,17 @@ + + + + \ No newline at end of file diff --git a/.run/Wacs.Core Publish Wacs.WASIp1.dll.run.xml b/.run/Wacs.Core Publish Wacs.WASIp1.dll.run.xml new file mode 100644 index 0000000..b864a2d --- /dev/null +++ b/.run/Wacs.Core Publish Wacs.WASIp1.dll.run.xml @@ -0,0 +1,17 @@ + + + + \ No newline at end of file diff --git a/.run/sync_version.sh b/.run/sync_version.sh new file mode 100755 index 0000000..2c72ef2 --- /dev/null +++ b/.run/sync_version.sh @@ -0,0 +1,102 @@ +#!/bin/bash + +# -------------------------------------------- +# Script to Sync .NET Assembly Version to package.json +# -------------------------------------------- + +# Exit immediately if a command exits with a non-zero status +set -e + +# Function to display usage +usage() { + echo "Usage: $0 [path/to/project.csproj] [path/to/package.json]" + echo "If no arguments are provided, it searches for the first .csproj file and assumes package.json is in the current directory." + exit 1 +} + +# Check for help flag +if [[ "$1" == "--help" || "$1" == "-h" ]]; then + usage +fi + +# Assign arguments or set defaults +CSPROJ_FILE="${1:-}" +PACKAGE_JSON_FILE="${2:-package.json}" + +# Function to find the first .csproj file if not provided +find_csproj() { + local csproj + csproj=$(find . -maxdepth 1 -name "*.csproj" | head -n 1) + echo "$csproj" +} + +# Determine the .csproj file +if [[ -z "$CSPROJ_FILE" ]]; then + CSPROJ_FILE=$(find_csproj) + if [[ -z "$CSPROJ_FILE" ]]; then + echo "Error: No .csproj file found in the current directory." + exit 1 + fi +fi + +# Check if the .csproj file exists +if [[ ! -f "$CSPROJ_FILE" ]]; then + echo "Error: .csproj file '$CSPROJ_FILE' does not exist." + exit 1 +fi + +# Check if package.json exists +if [[ ! -f "$PACKAGE_JSON_FILE" ]]; then + echo "Error: package.json file '$PACKAGE_JSON_FILE' does not exist." + exit 1 +fi + +# Extract the version from the .csproj file using sed +# This sed command captures the content between and +VERSION=$(sed -n 's:.*\(.*\).*:\1:p' "$CSPROJ_FILE") + +# If VERSION is empty, try to extract from AssemblyInfo.cs +if [[ -z "$VERSION" ]]; then + # Assuming AssemblyInfo.cs is located in Properties directory + ASSEMBLY_INFO=$(find . -path "*/Properties/AssemblyInfo.cs" | head -n 1) + if [[ -n "$ASSEMBLY_INFO" ]]; then + VERSION=$(sed -n 's/.*AssemblyVersion("\([^"]*\)").*/\1/p' "$ASSEMBLY_INFO") + fi +fi + +# If VERSION is still empty, exit with error +if [[ -z "$VERSION" ]]; then + echo "Error: Could not find the version in $CSPROJ_FILE or AssemblyInfo.cs." + exit 1 +fi + +echo "Detected .NET project version: $VERSION" + +# Function to update package.json using jq +update_package_json_jq() { + local ver="$1" + local pkg="$2" + jq --arg ver "$ver" '.version = $ver' "$pkg" > "${pkg}.tmp" && mv "${pkg}.tmp" "$pkg" +} + +# Function to update package.json using sed +update_package_json_sed() { + local ver="$1" + local pkg="$2" + # This regex matches the "version": "x.y.z" pattern and replaces it + sed -i.bak -E 's/("version"\s*:\s*")[^"]+(")/\1'"$ver"'\2/' "$pkg" +} + +# Update package.json +if command -v jq > /dev/null 2>&1; then + echo "Updating package.json using jq..." + update_package_json_jq "$VERSION" "$PACKAGE_JSON_FILE" + echo "Successfully updated 'version' in $PACKAGE_JSON_FILE to $VERSION." +else + echo "jq is not installed. Falling back to sed." + update_package_json_sed "$VERSION" "$PACKAGE_JSON_FILE" + echo "Successfully updated 'version' in $PACKAGE_JSON_FILE to $VERSION." + echo "A backup of the original package.json is saved as package.json.bak." +fi + +exit 0 \ No newline at end of file diff --git a/global.json b/global.json new file mode 100644 index 0000000..87aef9f --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "6.0.0", + "rollForward": "latestMajor", + "allowPrerelease": false + } +} \ No newline at end of file diff --git a/unity/CHANGELOG.md b/unity/CHANGELOG.md new file mode 100644 index 0000000..64310b6 --- /dev/null +++ b/unity/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes to the unity package will be documented in this file. + +## [0.1.4] +### Added +- Initial project setup for Unity. + +### Changed +- Updated project structure to allow installation as a Unity package from git. diff --git a/unity/Documentation~/manual.md b/unity/Documentation~/manual.md new file mode 100644 index 0000000..8c80acf --- /dev/null +++ b/unity/Documentation~/manual.md @@ -0,0 +1,7 @@ +## Documentation... + +I build and maintain WACS as a solo dev. + +I hope someday I'll have time for some great documentation. + +Until then, you can browse the [github repo](https://github.com/kelnishi/WACS/discussions) and connect with me there. \ No newline at end of file diff --git a/unity/Editor/AssetImporters/WasmImporter.cs b/unity/Editor/AssetImporters/WasmImporter.cs new file mode 100644 index 0000000..0f8de10 --- /dev/null +++ b/unity/Editor/AssetImporters/WasmImporter.cs @@ -0,0 +1,22 @@ +using UnityEngine; +using UnityEditor; +using UnityEditor.AssetImporters; +using System.IO; + +[ScriptedImporter(1, "wasm")] +public class WasmImporter : ScriptedImporter +{ + public override void OnImportAsset(AssetImportContext ctx) + { + // Load the binary data from the .kelvin file + byte[] data = File.ReadAllBytes(ctx.assetPath); + + // Create an instance of KelvinAsset and assign the data + WasmAsset wasmAsset = ScriptableObject.CreateInstance(); + wasmAsset.data = data; + + // Add the asset to the import context + ctx.AddObjectToAsset("Wasm Asset", wasmAsset); + ctx.SetMainObject(wasmAsset); + } +} \ No newline at end of file diff --git a/unity/README.md b/unity/README.md new file mode 100644 index 0000000..23447a3 --- /dev/null +++ b/unity/README.md @@ -0,0 +1,55 @@ +# WACS - WebAssembly Interpreter + +**WACS** is a pure C# WebAssembly Interpreter designed for .NET environments, including Unity's IL2CPP. + +## Features + +- **Execute WebAssembly**: Load and run .wasm files or data streams. +- **Interpreter-Only**: No JIT compilation, works in AOT modes like IL2CPP on iOS. +- **Magic Interop**: Reflection based interop allows for easy function binding with little boilerplate code. +- **Open Source**: Library is fully open source, free to inspect, change, and improve. + +## Unity Installation + + - Window>Package Manager + - Click + Add package from git URL... + - Enter the repo URL: ```https://github.com/kelnishi/WACS``` + - Click Add + +## Usage + +To use Wacs, you'll need to instantiate a runtime and a module. + +```csharp +using Wacs.Core; +using Wacs.Core.Runtime; +using Wacs.Core.Runtime.Types; + +public class ExampleClass : MonoBehaviour +{ + [SerializeField] private WasmAsset wasmAsset; + public string moduleName = "_"; + + void Start() + { + var stream = new MemoryStream(wasmAsset.data); + var module = BinaryModuleParser.ParseWasm(stream); + + _runtime = new WasmRuntime(); + _output = new(); + _runtime.BindHostFunction>(("env", "sayc"), c => + { + _output.Append(c); + }); + + _moduleInstance = _runtime.InstantiateModule(module, new RuntimeOptions { SkipModuleValidation = true}); + _runtime.RegisterModule(moduleName, _moduleInstance); + + var fa = runtime.GetExportedFunction((moduleName, "main")); + var main = runtime.CreateInvoker>(fa); + + //Execute the module + int result = main(); + } +} +``` diff --git a/unity/Runtime/Plugins/FluentValidation.dll b/unity/Runtime/Plugins/FluentValidation.dll new file mode 100755 index 0000000..1c73e5c Binary files /dev/null and b/unity/Runtime/Plugins/FluentValidation.dll differ diff --git a/unity/Runtime/Plugins/Microsoft.Extensions.ObjectPool.dll b/unity/Runtime/Plugins/Microsoft.Extensions.ObjectPool.dll new file mode 100755 index 0000000..2ecee33 Binary files /dev/null and b/unity/Runtime/Plugins/Microsoft.Extensions.ObjectPool.dll differ diff --git a/unity/Runtime/Plugins/Wacs.Core.deps.json b/unity/Runtime/Plugins/Wacs.Core.deps.json new file mode 100644 index 0000000..b6ff191 --- /dev/null +++ b/unity/Runtime/Plugins/Wacs.Core.deps.json @@ -0,0 +1,58 @@ +{ + "runtimeTarget": { + "name": ".NETStandard,Version=v2.1/", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETStandard,Version=v2.1": {}, + ".NETStandard,Version=v2.1/": { + "Wacs.Core/0.1.4": { + "dependencies": { + "FluentValidation": "11.10.0", + "Microsoft.Extensions.ObjectPool": "9.0.0" + }, + "runtime": { + "Wacs.Core.dll": {} + } + }, + "FluentValidation/11.10.0": { + "runtime": { + "lib/netstandard2.1/FluentValidation.dll": { + "assemblyVersion": "11.0.0.0", + "fileVersion": "11.10.0.0" + } + } + }, + "Microsoft.Extensions.ObjectPool/9.0.0": { + "runtime": { + "lib/netstandard2.0/Microsoft.Extensions.ObjectPool.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.24.52903" + } + } + } + } + }, + "libraries": { + "Wacs.Core/0.1.4": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "FluentValidation/11.10.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-qsJGSJDdZ8qiG+lVJ70PZfJHcEdq8UQZ/tZDXoj78/iHKG6lVKtMJsD11zyyv/IPc7rwqGqnFoFLTNzpo3IPYg==", + "path": "fluentvalidation/11.10.0", + "hashPath": "fluentvalidation.11.10.0.nupkg.sha512" + }, + "Microsoft.Extensions.ObjectPool/9.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-UbsU/gYe4nv1DeqMXIVzDfNNek7Sk2kKuAOXL/Y+sLcAR0HwFUqzg1EPiU88jeHNe0g81aPvvHbvHarQr3r9IA==", + "path": "microsoft.extensions.objectpool/9.0.0", + "hashPath": "microsoft.extensions.objectpool.9.0.0.nupkg.sha512" + } + } +} \ No newline at end of file diff --git a/unity/Runtime/Plugins/Wacs.Core.dll b/unity/Runtime/Plugins/Wacs.Core.dll new file mode 100644 index 0000000..20511c5 Binary files /dev/null and b/unity/Runtime/Plugins/Wacs.Core.dll differ diff --git a/unity/Runtime/Plugins/Wacs.Core.pdb b/unity/Runtime/Plugins/Wacs.Core.pdb new file mode 100644 index 0000000..2f4de56 Binary files /dev/null and b/unity/Runtime/Plugins/Wacs.Core.pdb differ diff --git a/unity/Runtime/Plugins/Wacs.WASIp1.deps.json b/unity/Runtime/Plugins/Wacs.WASIp1.deps.json new file mode 100644 index 0000000..041d873 --- /dev/null +++ b/unity/Runtime/Plugins/Wacs.WASIp1.deps.json @@ -0,0 +1,85 @@ +{ + "runtimeTarget": { + "name": ".NETStandard,Version=v2.1/", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETStandard,Version=v2.1": {}, + ".NETStandard,Version=v2.1/": { + "Wacs.WASIp1/0.9.1": { + "dependencies": { + "WACS": "0.1.4", + "Wacs.Core": "0.1.4.0" + }, + "runtime": { + "Wacs.WASIp1.dll": {} + } + }, + "FluentValidation/11.10.0": { + "runtime": { + "lib/netstandard2.1/FluentValidation.dll": { + "assemblyVersion": "11.0.0.0", + "fileVersion": "11.10.0.0" + } + } + }, + "Microsoft.Extensions.ObjectPool/9.0.0": { + "runtime": { + "lib/netstandard2.0/Microsoft.Extensions.ObjectPool.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.24.52903" + } + } + }, + "WACS/0.1.4": { + "dependencies": { + "FluentValidation": "11.10.0", + "Microsoft.Extensions.ObjectPool": "9.0.0" + }, + "runtime": { + "Wacs.Core.dll": {} + } + }, + "Wacs.Core/0.1.4.0": { + "runtime": { + "Wacs.Core.dll": { + "assemblyVersion": "0.1.4.0", + "fileVersion": "0.1.4.0" + } + } + } + } + }, + "libraries": { + "Wacs.WASIp1/0.9.1": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "FluentValidation/11.10.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-qsJGSJDdZ8qiG+lVJ70PZfJHcEdq8UQZ/tZDXoj78/iHKG6lVKtMJsD11zyyv/IPc7rwqGqnFoFLTNzpo3IPYg==", + "path": "fluentvalidation/11.10.0", + "hashPath": "fluentvalidation.11.10.0.nupkg.sha512" + }, + "Microsoft.Extensions.ObjectPool/9.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-UbsU/gYe4nv1DeqMXIVzDfNNek7Sk2kKuAOXL/Y+sLcAR0HwFUqzg1EPiU88jeHNe0g81aPvvHbvHarQr3r9IA==", + "path": "microsoft.extensions.objectpool/9.0.0", + "hashPath": "microsoft.extensions.objectpool.9.0.0.nupkg.sha512" + }, + "WACS/0.1.4": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "Wacs.Core/0.1.4.0": { + "type": "reference", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/unity/Runtime/Plugins/Wacs.WASIp1.dll b/unity/Runtime/Plugins/Wacs.WASIp1.dll new file mode 100644 index 0000000..5337727 Binary files /dev/null and b/unity/Runtime/Plugins/Wacs.WASIp1.dll differ diff --git a/unity/Runtime/Plugins/Wacs.WASIp1.pdb b/unity/Runtime/Plugins/Wacs.WASIp1.pdb new file mode 100644 index 0000000..eb908eb Binary files /dev/null and b/unity/Runtime/Plugins/Wacs.WASIp1.pdb differ diff --git a/unity/Runtime/WasmAsset.cs b/unity/Runtime/WasmAsset.cs new file mode 100644 index 0000000..bfa5a4f --- /dev/null +++ b/unity/Runtime/WasmAsset.cs @@ -0,0 +1,8 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +public class WasmAsset : ScriptableObject +{ + public byte[] data; +} diff --git a/unity/Samples~/WasmRunner/Scripts/WasmRunner.cs b/unity/Samples~/WasmRunner/Scripts/WasmRunner.cs new file mode 100644 index 0000000..9b2449d --- /dev/null +++ b/unity/Samples~/WasmRunner/Scripts/WasmRunner.cs @@ -0,0 +1,98 @@ +using System; +using System.IO; +using System.Text; +using UnityEngine; +using Wacs.Core; +using Wacs.Core.Runtime; +using Wacs.Core.Runtime.Types; +using Wacs.Core.WASIp1; + +public class WasmRunner : MonoBehaviour +{ + [SerializeField] private WasmAsset wasmAsset; + + public string moduleName = "_"; + + private WasmRuntime _runtime; + + private ModuleInstance _moduleInstance; + + private StringBuilder _output; + + private TextMesh text; + + private void OnEnable() + { + text = GetComponent(); + var stream = new MemoryStream(wasmAsset.data); + var module = BinaryModuleParser.ParseWasm(stream); + + _runtime = new WasmRuntime(); + _output = new(); + _runtime.BindHostFunction>(("env", "sayc"), c => + { + _output.Append(c); + }); + + _moduleInstance = _runtime.InstantiateModule(module, new RuntimeOptions { SkipModuleValidation = true}); + _runtime.RegisterModule(moduleName, _moduleInstance); + } + + // Start is called before the first frame update + void Start() + { + var callOptions = new InvokerOptions { + LogGas = false, + LogProgressEvery = 0, + LogInstructionExecution = InstructionLogging.None, + CalculateLineNumbers = false, + CollectStats = false, + }; + + //Wasm/WASI entry points + if (_moduleInstance.StartFunc != null) + { + var caller = _runtime.CreateInvoker(_moduleInstance.StartFunc, callOptions); + try + { + caller(); + } + catch (TrapException exc) + { + Debug.LogError(exc); + } + catch (SignalException exc) + { + ErrNo sig = (ErrNo)exc.Signal; + Debug.LogError($"WasmRunner: Module exited with signal {exc}"); + } + } + else if (_runtime.TryGetExportedFunction((moduleName, "main"), out var mainAddr)) + { + var caller = _runtime.CreateInvoker>(mainAddr, callOptions); + try + { + int result = caller(); + if (text) + text.text = _output.ToString(); + + Debug.Log($"WasmRunner: Module returned result:{result}"); + } + catch (TrapException exc) + { + Debug.LogError(exc); + } + catch (SignalException exc) + { + ErrNo sig = (ErrNo)exc.Signal; + Debug.LogError($"WasmRunner: Module exited with signal {exc}"); + } + } + } + + // Update is called once per frame + void Update() + { + + } +} \ No newline at end of file diff --git a/unity/Samples~/WasmRunner/WASM/HelloWorld.wasm b/unity/Samples~/WasmRunner/WASM/HelloWorld.wasm new file mode 100644 index 0000000..9080e49 Binary files /dev/null and b/unity/Samples~/WasmRunner/WASM/HelloWorld.wasm differ diff --git a/unity/package.json b/unity/package.json new file mode 100644 index 0000000..b9de279 --- /dev/null +++ b/unity/package.json @@ -0,0 +1,29 @@ +{ + "name": "com.kelnishi.wacs", + "version": "0.1.4", + "displayName": "WACS - WebAssembly Interpreter", + "description": "This package includes the WACS WebAssembly Interpreter runtime and Unity asset types for handling .wasm files.", + "documentationUrl": "https://github.com/kelnishi/WACS/tree/main/unity/Documentation~/manual.md", + "changelogUrl": "https://github.com/kelnishi/WACS/tree/main/unity/CHANGELOG.md", + "licensesUrl": "https://github.com/kelnishi/WACS/tree/main/LICENSE", + "unity": "2021.3", + "dependencies": {}, + "keywords": [ + "wasm", + "WACS", + "WebAssebly", + "wasi" + ], + "author": { + "name": "Kelvin Nishikawa", + "email": "kelnishiplays@gmail.com", + "url": "https://github.com/kelnishi/WACS" + }, + "samples": [ + { + "displayName": "WasmRunner Example", + "description": "Contains a MonoBehavior showing how to load a wasm file and execute it.", + "path": "Samples~/WasmRunner" + } + ] +}