From c8f485cd93c117444babcd5c6956caa8b5c5c5bf Mon Sep 17 00:00:00 2001 From: Logan McNaughton <848146+loganmc10@users.noreply.github.com> Date: Fri, 15 May 2026 13:18:48 +0200 Subject: [PATCH] Build clib (#893) * Build clib * more * more * more * linker * more * more * more * more * more * more --- .github/workflows/build.yml | 8 - Cargo.lock | 610 +++++++++++++++++++++++++++++++----- Cargo.toml | 9 +- build.rs | 6 - src/lib.rs | 317 +++++++++++++++++++ src/main.rs | 314 +------------------ 6 files changed, 863 insertions(+), 401 deletions(-) create mode 100644 src/lib.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b766775d..cc7359a0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -211,12 +211,6 @@ jobs: mv target/${{ matrix.target }}/release/gopher64 target/${{ matrix.target }}/release/gopher64-cli cargo bundle --release --target=${{ matrix.target }} --format osx cp target/${{ matrix.target }}/release/gopher64-cli target/${{ matrix.target }}/release/bundle/osx/Gopher64.app/Contents/MacOS/gopher64-cli - install_name_tool -change /opt/homebrew/opt/freetype/lib/libfreetype.6.dylib \ - @executable_path/../Frameworks/libfreetype.6.dylib target/${{ matrix.target }}/release/bundle/osx/Gopher64.app/Contents/MacOS/gopher64 - install_name_tool -change /opt/homebrew/opt/freetype/lib/libfreetype.6.dylib \ - @executable_path/../Frameworks/libfreetype.6.dylib target/${{ matrix.target }}/release/bundle/osx/Gopher64.app/Contents/MacOS/gopher64-cli - install_name_tool -change /opt/homebrew/opt/libpng/lib/libpng16.16.dylib \ - @executable_path/../Frameworks/libpng16.16.dylib target/${{ matrix.target }}/release/bundle/osx/Gopher64.app/Contents/Frameworks/libfreetype.6.dylib env: NETPLAY_ID: ${{ secrets.NETPLAY_ID }} MACOSX_DEPLOYMENT_TARGET: "15.0" @@ -233,8 +227,6 @@ jobs: - name: Sign Bundle Release if: ${{ github.ref_type == 'tag' }} run: | - codesign -f --timestamp --options runtime -s "${{ secrets.APPLE_DEVELOPER_ID }}" target/${{ matrix.target }}/release/bundle/osx/Gopher64.app/Contents/Frameworks/libpng16.16.dylib - codesign -f --timestamp --options runtime -s "${{ secrets.APPLE_DEVELOPER_ID }}" target/${{ matrix.target }}/release/bundle/osx/Gopher64.app/Contents/Frameworks/libfreetype.6.dylib codesign -f --timestamp --options runtime -s "${{ secrets.APPLE_DEVELOPER_ID }}" target/${{ matrix.target }}/release/bundle/osx/Gopher64.app/Contents/Frameworks/libMoltenVK.dylib codesign -f --entitlements data/macos/entitlements_child.plist --timestamp --options runtime -s "${{ secrets.APPLE_DEVELOPER_ID }}" target/${{ matrix.target }}/release/bundle/osx/Gopher64.app/Contents/MacOS/gopher64-cli codesign -f --entitlements data/macos/entitlements_prod.plist --timestamp --options runtime -s "${{ secrets.APPLE_DEVELOPER_ID }}" target/${{ matrix.target }}/release/bundle/osx/Gopher64.app diff --git a/Cargo.lock b/Cargo.lock index 4c8c6bae..70f20c39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -315,6 +315,15 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + [[package]] name = "async-broadcast" version = "0.7.2" @@ -552,18 +561,18 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375082f007bd67184fb9c0374614b29f9aaa604ec301635f72338bb65386a53d" +checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38" dependencies = [ "arrayvec", ] [[package]] name = "aws-lc-rs" -version = "1.16.3" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" dependencies = [ "aws-lc-sys", "zeroize", @@ -571,9 +580,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.40.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" dependencies = [ "cc", "cmake", @@ -617,6 +626,21 @@ dependencies = [ "syn", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bit_field" version = "0.10.3" @@ -647,6 +671,12 @@ dependencies = [ "no_std_io2", ] +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + [[package]] name = "block-buffer" version = "0.10.4" @@ -843,9 +873,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.61" +version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ "find-msvc-tools", "jobserver", @@ -1010,6 +1040,17 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "codespan-reporting" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -1131,7 +1172,7 @@ checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", - "core-graphics-types", + "core-graphics-types 0.1.3", "foreign-types", "libc", ] @@ -1147,6 +1188,17 @@ dependencies = [ "libc", ] +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.11.1", + "core-foundation 0.10.1", + "libc", +] + [[package]] name = "core_maths" version = "0.1.1" @@ -1442,6 +1494,15 @@ dependencies = [ "libloading", ] +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "downcast-rs" version = "1.2.1" @@ -1680,6 +1741,27 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "femtovg" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727c5dd9d601f3f4ea133b8a6ba37d37ca67ed59781675dc1c9c9f1701e0d197" +dependencies = [ + "bitflags 2.11.1", + "bytemuck", + "fnv", + "glow 0.17.0", + "imgref", + "itertools 0.14.0", + "log", + "rgb", + "slotmap", + "swash", + "wasm-bindgen", + "web-sys", + "wgpu", +] + [[package]] name = "field-offset" version = "0.3.6" @@ -1692,13 +1774,12 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.27" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" dependencies = [ "cfg-if", "libc", - "libredox", ] [[package]] @@ -1928,28 +2009,6 @@ dependencies = [ "slab", ] -[[package]] -name = "gbm" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce852e998d3ca5e4a97014fb31c940dc5ef344ec7d364984525fd11e8a547e6a" -dependencies = [ - "bitflags 2.11.1", - "drm", - "drm-fourcc", - "gbm-sys", - "libc", -] - -[[package]] -name = "gbm-sys" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c13a5f2acc785d8fb6bf6b7ab6bfb0ef5dad4f4d97e8e70bb8e470722312f76f" -dependencies = [ - "libc", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -2049,6 +2108,18 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "glow" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "glow" version = "0.17.0" @@ -2165,6 +2236,40 @@ dependencies = [ "zip", ] +[[package]] +name = "gpu-allocator" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" +dependencies = [ + "ash", + "hashbrown 0.16.1", + "log", + "presser", + "thiserror 2.0.18", + "windows 0.62.2", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.11.1", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.11.1", +] + [[package]] name = "grid" version = "1.0.1" @@ -2198,6 +2303,7 @@ checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "num-traits", "zerocopy", ] @@ -2251,9 +2357,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heapless" @@ -2293,6 +2399,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + [[package]] name = "hmac" version = "0.13.0" @@ -2349,9 +2461,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hybrid-array" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" dependencies = [ "typenum", ] @@ -2426,15 +2538,11 @@ dependencies = [ "calloop 0.14.4", "cfg_aliases", "drm", - "gbm", - "glutin", "i-slint-common", "i-slint-core", - "i-slint-renderer-skia", + "i-slint-renderer-femtovg", "input", - "memmap2", "nix", - "raw-window-handle", "xkbcommon", ] @@ -2450,6 +2558,7 @@ dependencies = [ "i-slint-common", "i-slint-core", "i-slint-core-macros", + "i-slint-renderer-femtovg", ] [[package]] @@ -2471,6 +2580,7 @@ dependencies = [ "i-slint-common", "i-slint-core", "i-slint-core-macros", + "i-slint-renderer-femtovg", "i-slint-renderer-skia", "lyon_path", "muda", @@ -2586,6 +2696,7 @@ dependencies = [ "wasm-bindgen", "web-sys", "web-time", + "wgpu", ] [[package]] @@ -2599,24 +2710,50 @@ dependencies = [ "syn", ] +[[package]] +name = "i-slint-renderer-femtovg" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b28a8f0a7a777e670fbe2af6cbcec4935c37335bc0dab100f73bdc197dfa3a" +dependencies = [ + "cfg-if", + "const-field-offset", + "derive_more", + "femtovg", + "glow 0.17.0", + "i-slint-common", + "i-slint-core", + "i-slint-core-macros", + "imgref", + "lyon_path", + "pin-weak", + "rgb", + "wasm-bindgen", + "web-sys", + "wgpu", +] + [[package]] name = "i-slint-renderer-skia" version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4030ff9eeb1b99fdf9b47472f3a7a582f3bcb57207c9bc07505dc6998abdcbc" dependencies = [ + "ash", "bytemuck", "cfg-if", "cfg_aliases", "clru", "const-field-offset", "derive_more", - "glow", + "foreign-types", + "glow 0.17.0", "glutin", "i-slint-common", "i-slint-core", "i-slint-core-macros", "lyon_path", + "metal 0.31.0", "objc2 0.6.4", "objc2-app-kit 0.3.2", "objc2-core-foundation", @@ -2630,9 +2767,12 @@ dependencies = [ "scoped-tls-hkt", "skia-safe", "softbuffer", + "spin_on", "unicode-segmentation", "vtable", + "wgpu", "windows 0.62.2", + "windows-core 0.62.2", "write-fonts", ] @@ -2887,7 +3027,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.0", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -3098,6 +3238,17 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + [[package]] name = "khronos_api" version = "3.1.0" @@ -3106,12 +3257,13 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kurbo" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7564e90fe3c0d5771e1f0bc95322b21baaeaa0d9213fa6a0b61c99f8b17b3bfb" +checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" dependencies = [ "arrayvec", "euclid", + "polycool", "smallvec", ] @@ -3135,9 +3287,9 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" [[package]] name = "libbz2-rs-sys" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a6a8c165077efc8f3a971534c50ea6a1a18b329ef4a66e897a7e3a1494565f" +checksum = "f8fc329e1457d97a9d58a4e2ca49e3be572431a7e096008efc2e3a3c19d428f4" [[package]] name = "libc" @@ -3247,6 +3399,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.14" @@ -3327,6 +3485,15 @@ dependencies = [ "sha2 0.10.9", ] +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + [[package]] name = "maybe-rayon" version = "0.1.1" @@ -3361,6 +3528,36 @@ dependencies = [ "autocfg", ] +[[package]] +name = "metal" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" +dependencies = [ + "bitflags 2.11.1", + "block", + "core-graphics-types 0.1.3", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "metal" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15" +dependencies = [ + "bitflags 2.11.1", + "block", + "core-graphics-types 0.2.0", + "foreign-types", + "log", + "objc", + "paste", +] + [[package]] name = "mimalloc" version = "0.1.50" @@ -3432,6 +3629,32 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "naga" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "618f667225063219ddfc61251087db8a9aec3c3f0950c916b614e403486f1135" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.11.1", + "cfg-if", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown 0.16.1", + "hexf-parse", + "indexmap", + "libm", + "log", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "spirv", + "thiserror 2.0.18", + "unicode-ident", +] + [[package]] name = "natord" version = "1.0.9" @@ -3599,6 +3822,15 @@ dependencies = [ "syn", ] +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + [[package]] name = "objc-sys" version = "0.3.5" @@ -3992,9 +4224,9 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "open" -version = "5.3.4" +version = "5.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3bab717c29a857abf75fcef718d441ec7cb2725f937343c734740a985d37fd" +checksum = "2fbaa89d2ddc8473c78a3adf69eea8cffa28c483b8e02a971ef31527cd0fc92c" dependencies = [ "is-wsl", "libc", @@ -4023,6 +4255,15 @@ dependencies = [ "libredox", ] +[[package]] +name = "ordered-float" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -4048,6 +4289,29 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link 0.2.1", +] + [[package]] name = "parlance" version = "0.1.0" @@ -4123,18 +4387,18 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.1.12" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf0d9e68100b3a7989b4901972f265cd542e560a3a8a724e1e20322f4d06ce9" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.12" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a990e22f43e84855daf260dded30524ef4a9021cc7541c26540500a50b624389" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", @@ -4228,6 +4492,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" +[[package]] +name = "polycool" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6" +dependencies = [ + "arrayvec", +] + [[package]] name = "portable-atomic" version = "1.13.1" @@ -4237,6 +4510,15 @@ dependencies = [ "critical-section", ] +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + [[package]] name = "postcard" version = "1.1.3" @@ -4282,6 +4564,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + [[package]] name = "prettyplease" version = "0.2.37" @@ -4371,9 +4659,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" -version = "0.39.3" +version = "0.39.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721da970c312655cde9b4ffe0547f20a8494866a4af5ff51f18b7c633d0c870b" +checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" dependencies = [ "memchr", "serde", @@ -4502,6 +4790,12 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "range-alloc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" + [[package]] name = "rav1e" version = "0.8.1" @@ -4668,6 +4962,12 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + [[package]] name = "reqwest" version = "0.13.3" @@ -5346,6 +5646,7 @@ dependencies = [ "i-slint-common", "i-slint-core", "i-slint-core-macros", + "i-slint-renderer-femtovg", "i-slint-renderer-software", "num-traits", "once_cell", @@ -5516,7 +5817,6 @@ checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" dependencies = [ "as-raw-xcb-connection", "bytemuck", - "drm", "fastrand", "js-sys", "memmap2", @@ -5567,6 +5867,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.11.1", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -5742,6 +6051,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "text-size" version = "1.1.1" @@ -5914,9 +6232,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.2" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -6001,7 +6319,7 @@ dependencies = [ "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -6032,7 +6350,7 @@ dependencies = [ "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -6041,7 +6359,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -6717,6 +7035,156 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" +[[package]] +name = "wgpu" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cb534d5ffd109c7d1135f34cdae29e60eab94855a625dcfe1705f8bc7ad79f" +dependencies = [ + "arrayvec", + "bitflags 2.11.1", + "bytemuck", + "cfg-if", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "js-sys", + "log", + "naga", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "28.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d23f4642f53f666adcfd2d3218ab174d1e6681101aef18696b90cbe64d1c10f9" +dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", + "bitflags 2.11.1", + "bytemuck", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.18", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core-deps-apple" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87b7b696b918f337c486bf93142454080a32a37832ba8a31e4f48221890047da" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b251c331f84feac147de3c4aa3aa45112622a95dd7ee1b74384fa0458dbd79" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ca976e72b2c9964eb243e281f6ce7f14a514e409920920dcda12ae40febaae" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-hal" +version = "28.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d6cb474beb218824dcc9e1ce679d973f719262789bfb27407da560cac20eeb" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.11.1", + "block", + "bytemuck", + "cfg-if", + "cfg_aliases", + "core-graphics-types 0.2.0", + "glow 0.16.0", + "glutin_wgl_sys", + "gpu-allocator", + "gpu-descriptor", + "hashbrown 0.16.1", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "metal 0.33.0", + "naga", + "ndk-sys", + "objc", + "once_cell", + "ordered-float", + "parking_lot", + "portable-atomic", + "portable-atomic-util", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "smallvec", + "thiserror 2.0.18", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "windows 0.62.2", + "windows-core 0.62.2", +] + +[[package]] +name = "wgpu-types" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e18308757e594ed2cd27dddbb16a139c42a683819d32a2e0b1b0167552f5840c" +dependencies = [ + "bitflags 2.11.1", + "bytemuck", + "js-sys", + "log", + "web-sys", +] + [[package]] name = "winapi-util" version = "0.1.11" @@ -7227,9 +7695,9 @@ checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" [[package]] name = "winnow" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] @@ -7532,7 +8000,7 @@ dependencies = [ "uds_windows", "uuid 1.23.1", "windows-sys 0.61.2", - "winnow 1.0.2", + "winnow 1.0.3", "zbus_macros", "zbus_names", "zvariant", @@ -7584,7 +8052,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d" dependencies = [ "serde", - "winnow 1.0.2", + "winnow 1.0.3", "zvariant", ] @@ -7628,9 +8096,9 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] @@ -7800,7 +8268,7 @@ dependencies = [ "endi", "enumflags2", "serde", - "winnow 1.0.2", + "winnow 1.0.3", "zvariant_derive", "zvariant_utils", ] @@ -7828,5 +8296,5 @@ dependencies = [ "quote", "serde", "syn", - "winnow 1.0.2", + "winnow 1.0.3", ] diff --git a/Cargo.toml b/Cargo.toml index cd799b0e..9a320885 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,9 @@ rust-version = "1.95.0" default = ["gui"] gui = ["dep:rfd", "dep:slint", "dep:slint-build"] +[lib] +crate-type = ["cdylib", "rlib"] + [dependencies] dirs = "6.0" zip = "8.0" @@ -16,11 +19,11 @@ chrono = "0.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde-big-array = "0.5" -slint = { version = "1.16", default-features = false, features = ["compat-1-2", "std", "unstable-winit-030", "backend-winit", "renderer-skia", "accessibility"], optional = true } +slint = { version = "1.16", default-features = false, features = ["compat-1-2", "std", "unstable-winit-030", "backend-winit", "renderer-femtovg-wgpu", "accessibility"], optional = true } open = "5.3" sha2 = "0.11" sdl3-sys = { version = "0.6", features = ["build-from-source-static"] } -sdl3-ttf-sys = { version = "0.6", features = ["build-from-source-static", "no-sdlttf-harfbuzz", "no-sdlttf-plutosvg"] } +sdl3-ttf-sys = { version = "0.6", features = ["build-static-vendored", "no-sdlttf-harfbuzz", "no-sdlttf-plutosvg"] } rfd = {version = "0.17", optional = true } tokio = {version = "1.46", features = ["rt-multi-thread", "macros", "fs", "process"] } spin_sleep = "1.3" @@ -60,5 +63,5 @@ name = "Gopher64" identifier = "io.github.gopher64.gopher64" category = "public.app-category.games" icon = ["data/icon/gopher64_128x128.png","data/icon/gopher64_256x256.png","data/icon/gopher64_512x512.png"] -osx_frameworks = ["/opt/homebrew/opt/molten-vk/lib/libMoltenVK.dylib","/opt/homebrew/opt/freetype/lib/libfreetype.6.dylib","/opt/homebrew/opt/libpng/lib/libpng16.16.dylib"] +osx_frameworks = ["/opt/homebrew/opt/molten-vk/lib/libMoltenVK.dylib"] osx_minimum_system_version = "15.0" diff --git a/build.rs b/build.rs index 5f53053e..b1876fdb 100644 --- a/build.rs +++ b/build.rs @@ -135,9 +135,6 @@ fn main() { .compile() .unwrap(); } else if os == "macos" { - println!("cargo:rustc-link-search=native=/opt/homebrew/opt/freetype/lib"); - println!("cargo:rustc-link-lib=dylib=freetype"); - let output = std::process::Command::new("clang") .args(["--print-runtime-dir"]) .output() @@ -148,9 +145,6 @@ fn main() { println!("cargo:rustc-link-search=native={}", runtime_dir); println!("cargo:rustc-link-lib=static=clang_rt.osx"); } - if cfg!(not(feature = "gui")) && os == "linux" { - println!("cargo:rustc-link-lib=dylib=freetype"); - } volk_build.flag("-flto=thin"); rdp_build.flag("-flto=thin"); diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..e824cc07 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,317 @@ +#![deny(warnings)] + +mod cheats; +mod device; +mod netplay; +mod retroachievements; +mod savestates; +mod ui; +use clap::Parser; +use std::io::Error; + +/// N64 emulator +#[derive(Parser, Debug)] +#[command(author, version=env!("GIT_DESCRIBE"), about, long_about = None, arg_required_else_help = if cfg!(feature = "gui") { false } else { true })] +struct Args { + game: Option, + #[arg(short, long)] + fullscreen: bool, + #[arg(long)] + overclock: Option, + #[arg(long)] + disable_expansion_pak: Option, + #[arg(long, value_name = "CHEATS_FILE", hide = true)] + cheats: Option, + #[arg(long, value_name = "NETPLAY_PEER_ADDR", hide = true)] + netplay_peer_addr: Option, + #[arg(long, value_name = "NETPLAY_PLAYER_NUMBER", hide = true)] + netplay_player_number: Option, + #[arg( + long, + value_name = "PROFILE_NAME", + help = "Create a new input profile (keyboard/gamepad mappings)" + )] + configure_input_profile: Option, + #[arg(long, help = "Use DirectInput when configuring a new input profile")] + use_dinput: bool, + #[arg( + long, + value_name = "DEADZONE_PERCENTAGE", + help = "Used along with --configure-input-profile to set the deadzone for analog sticks" + )] + deadzone: Option, + #[arg( + long, + value_name = "PROFILE_NAME", + help = "Must also specify --port. Used to bind a previously created profile to a port" + )] + bind_input_profile: Option, + #[arg( + long, + help = "Lists connected controllers which can be used in --assign-controller" + )] + list_controllers: bool, + #[arg( + long, + value_name = "CONTROLLER_NUMBER", + help = "Must also specify --port. Used to assign a controller listed in --list-controllers to a port" + )] + assign_controller: Option, + #[arg( + long, + value_name = "PORT", + help = "Valid values: 1-4. To be used alongside --bind-input-profile and --assign-controller" + )] + port: Option, + #[arg( + long, + help = "Clear all input profile bindings and controller assignments" + )] + clear_input_bindings: bool, + #[arg( + long, + value_name = "SLOT", + help = "Load savestate from slot 0-9 when starting the game" + )] + load_state: Option, + #[arg( + long = "ra-username", + value_name = "USERNAME", + help = "Username for RetroAchievements" + )] + ra_username: Option, + #[arg( + long = "ra-token", + value_name = "TOKEN", + help = "Token for RetroAchievements", + hide = true + )] + ra_token: Option, + #[arg( + long = "ra-password", + value_name = "PASSWORD", + help = "Password for RetroAchievements" + )] + ra_password: Option, + #[arg( + long = "ra-hardcore", + help = "Enable Hardcore mode for RetroAchievements" + )] + ra_hardcore: bool, + #[arg( + long = "ra-challenge", + help = "Enable Challenge Indicators for RetroAchievements" + )] + ra_challenge: bool, + #[arg( + long = "ra-leaderboard", + help = "Enable Leaderboard Trackers for RetroAchievements" + )] + ra_leaderboard: bool, + #[arg(long = "discord-rich-presence", help = "Enable Discord Rich Presence")] + discord_rich_presence: bool, +} + +pub async fn run() -> std::io::Result<()> { + let dirs = ui::get_dirs(); + + std::fs::create_dir_all(dirs.config_dir)?; + std::fs::create_dir_all(dirs.cache_dir)?; + std::fs::create_dir_all(dirs.data_dir.join("saves"))?; + std::fs::create_dir_all(dirs.data_dir.join("states"))?; + + ui::sdl_hints(); + + let args = Args::parse(); + if let Some(game) = args.game { + let file_path = std::path::Path::new(&game); + let Some(rom_contents) = device::get_rom_contents(file_path) else { + return Err(Error::other(format!( + "Could not read ROM file: {}", + file_path.display() + ))); + }; + + if let Some(slot) = args.load_state + && slot > 9 + { + return Err(Error::other("Savestate slot must be between 0 and 9")); + } + + let mut device = device::Device::new(); + + device.ui.config.recent_roms.retain(|x| *x != game); + device.ui.config.recent_roms.insert(0, game); + device.ui.config.recent_roms.truncate(5); + + if args.fullscreen { + device.ui.video.fullscreen = true; + } else { + device.ui.video.fullscreen = device.ui.config.video.fullscreen; + } + let overclock = args + .overclock + .unwrap_or(device.ui.config.emulation.overclock); + let disable_expansion_pak = args + .disable_expansion_pak + .unwrap_or(device.ui.config.emulation.disable_expansion_pak); + let cheats = if let Some(cheats_file) = args.cheats + && let Ok(data) = std::fs::read(cheats_file) + && let Ok(cheats) = serde_json::from_slice(&data) + { + cheats + } else { + let game_crc = ui::storage::get_game_crc(&rom_contents); + ui::config::Cheats::new() + .cheats + .get(&game_crc) + .cloned() + .unwrap_or_default() + }; + + let mut shutdown_tx = None; + let mut usb_handle = None; + + if let Some(peer_addr) = args.netplay_peer_addr + && let Some(player_number) = args.netplay_player_number + { + device.netplay = Some(netplay::init(peer_addr.parse().unwrap(), player_number)); + } else { + for i in 0..4 { + if device.ui.config.input.transfer_pak[i] + && !device.ui.config.input.gb_rom_path[i].is_empty() + && !device.ui.config.input.gb_ram_path[i].is_empty() + && let Ok(rom) = std::fs::read(&device.ui.config.input.gb_rom_path[i]) + && let Ok(ram) = std::fs::read(&device.ui.config.input.gb_ram_path[i]) + { + device::controller::gbcart::init(&mut device.transferpaks[i].cart, &rom, &ram); + } + } + + if device.ui.config.emulation.usb { + (shutdown_tx, usb_handle, device.ui.usb) = ui::usb::init(); + } + + if let Some(username) = args.ra_username { + retroachievements::init_client( + if cfg!(ra_hardcore_enabled) { + args.ra_hardcore + } else { + false + }, + args.ra_challenge, + args.ra_leaderboard, + ); + + let (tx, rx) = tokio::sync::oneshot::channel::(); + if let Some(password) = args.ra_password { + retroachievements::login_user(username, password, tx); + } else if let Some(token) = args.ra_token { + retroachievements::login_token_user(username, token, tx); + } else { + tx.send(false).unwrap(); + } + + rx.await.unwrap(); + } + } + + let (discord_watch_tx, discord_handle) = retroachievements::load_game( + &rom_contents, + rom_contents.len(), + args.discord_rich_presence, + ) + .await; + + device::run_game( + &mut device, + &rom_contents, + ui::GameSettings { + overclock, + disable_expansion_pak, + cheats, + load_savestate_slot: args.load_state, + }, + ); + + retroachievements::shutdown_client(discord_watch_tx, discord_handle).await; + + if device.netplay.is_some() { + netplay::close(&mut device); + } else { + for i in 0..4 { + if device.ui.config.input.transfer_pak[i] + && !device.ui.config.input.gb_ram_path[i].is_empty() + && !device.transferpaks[i].cart.ram.is_empty() + { + device::controller::gbcart::save( + &mut device.transferpaks[i].cart, + device.vi.elapsed_time as i64, + &device.ui.config.input.gb_ram_path[i], + ); + } + } + } + if let Some(shutdown_tx) = &shutdown_tx { + ui::usb::close(shutdown_tx, usb_handle).await; + } + } else if std::env::args().count() > 1 { + let mut config = ui::config::Config::new(); + + if let Some(profile) = args.configure_input_profile { + ui::input::configure_input_profile( + &mut config, + profile, + args.use_dinput, + args.deadzone.unwrap_or(ui::input::DEADZONE_DEFAULT), + ); + + ui::sdl_close(); + return Ok(()); + } else { + if args.clear_input_bindings { + ui::input::clear_bindings(&mut config); + return Ok(()); + } + if let Some(port) = args.port + && !(1..=4).contains(&port) + { + return Err(Error::other("Port must be between 1 and 4")); + } + + ui::sdl_init(sdl3_sys::init::SDL_INIT_GAMEPAD); + + if args.list_controllers { + let controllers = ui::input::get_controller_names(); + for (i, controller) in controllers.iter().enumerate() { + println!("Controller {i}: {controller}"); + } + } else { + if let Some(assign_controller) = args.assign_controller { + let Some(port) = args.port else { + ui::sdl_close(); + return Err(Error::other("Must specify port number")); + }; + ui::input::assign_controller(&mut config, assign_controller - 1, port); + } + if let Some(profile) = args.bind_input_profile { + let Some(port) = args.port else { + ui::sdl_close(); + return Err(Error::other("Must specify port number")); + }; + ui::input::bind_input_profile(&mut config, profile, port); + } + } + } + } else { + #[cfg(feature = "gui")] + { + retroachievements::init_client(false, false, false); + ui::gui::app_window(); + retroachievements::shutdown_client(None, None).await; + } + } + + ui::sdl_close(); + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 9c7ca5bb..e275fc0a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,319 +3,7 @@ #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; -mod cheats; -mod device; -mod netplay; -mod retroachievements; -mod savestates; -mod ui; -use clap::Parser; -use std::io::Error; - -/// N64 emulator -#[derive(Parser, Debug)] -#[command(author, version=env!("GIT_DESCRIBE"), about, long_about = None, arg_required_else_help = if cfg!(feature = "gui") { false } else { true })] -struct Args { - game: Option, - #[arg(short, long)] - fullscreen: bool, - #[arg(long)] - overclock: Option, - #[arg(long)] - disable_expansion_pak: Option, - #[arg(long, value_name = "CHEATS_FILE", hide = true)] - cheats: Option, - #[arg(long, value_name = "NETPLAY_PEER_ADDR", hide = true)] - netplay_peer_addr: Option, - #[arg(long, value_name = "NETPLAY_PLAYER_NUMBER", hide = true)] - netplay_player_number: Option, - #[arg( - long, - value_name = "PROFILE_NAME", - help = "Create a new input profile (keyboard/gamepad mappings)" - )] - configure_input_profile: Option, - #[arg(long, help = "Use DirectInput when configuring a new input profile")] - use_dinput: bool, - #[arg( - long, - value_name = "DEADZONE_PERCENTAGE", - help = "Used along with --configure-input-profile to set the deadzone for analog sticks" - )] - deadzone: Option, - #[arg( - long, - value_name = "PROFILE_NAME", - help = "Must also specify --port. Used to bind a previously created profile to a port" - )] - bind_input_profile: Option, - #[arg( - long, - help = "Lists connected controllers which can be used in --assign-controller" - )] - list_controllers: bool, - #[arg( - long, - value_name = "CONTROLLER_NUMBER", - help = "Must also specify --port. Used to assign a controller listed in --list-controllers to a port" - )] - assign_controller: Option, - #[arg( - long, - value_name = "PORT", - help = "Valid values: 1-4. To be used alongside --bind-input-profile and --assign-controller" - )] - port: Option, - #[arg( - long, - help = "Clear all input profile bindings and controller assignments" - )] - clear_input_bindings: bool, - #[arg( - long, - value_name = "SLOT", - help = "Load savestate from slot 0-9 when starting the game" - )] - load_state: Option, - #[arg( - long = "ra-username", - value_name = "USERNAME", - help = "Username for RetroAchievements" - )] - ra_username: Option, - #[arg( - long = "ra-token", - value_name = "TOKEN", - help = "Token for RetroAchievements", - hide = true - )] - ra_token: Option, - #[arg( - long = "ra-password", - value_name = "PASSWORD", - help = "Password for RetroAchievements" - )] - ra_password: Option, - #[arg( - long = "ra-hardcore", - help = "Enable Hardcore mode for RetroAchievements" - )] - ra_hardcore: bool, - #[arg( - long = "ra-challenge", - help = "Enable Challenge Indicators for RetroAchievements" - )] - ra_challenge: bool, - #[arg( - long = "ra-leaderboard", - help = "Enable Leaderboard Trackers for RetroAchievements" - )] - ra_leaderboard: bool, - #[arg(long = "discord-rich-presence", help = "Enable Discord Rich Presence")] - discord_rich_presence: bool, -} - #[tokio::main(worker_threads = 4)] async fn main() -> std::io::Result<()> { - let dirs = ui::get_dirs(); - - std::fs::create_dir_all(dirs.config_dir)?; - std::fs::create_dir_all(dirs.cache_dir)?; - std::fs::create_dir_all(dirs.data_dir.join("saves"))?; - std::fs::create_dir_all(dirs.data_dir.join("states"))?; - - ui::sdl_hints(); - - let args = Args::parse(); - if let Some(game) = args.game { - let file_path = std::path::Path::new(&game); - let Some(rom_contents) = device::get_rom_contents(file_path) else { - return Err(Error::other(format!( - "Could not read ROM file: {}", - file_path.display() - ))); - }; - - if let Some(slot) = args.load_state - && slot > 9 - { - return Err(Error::other("Savestate slot must be between 0 and 9")); - } - - let mut device = device::Device::new(); - - device.ui.config.recent_roms.retain(|x| *x != game); - device.ui.config.recent_roms.insert(0, game); - device.ui.config.recent_roms.truncate(5); - - if args.fullscreen { - device.ui.video.fullscreen = true; - } else { - device.ui.video.fullscreen = device.ui.config.video.fullscreen; - } - let overclock = args - .overclock - .unwrap_or(device.ui.config.emulation.overclock); - let disable_expansion_pak = args - .disable_expansion_pak - .unwrap_or(device.ui.config.emulation.disable_expansion_pak); - let cheats = if let Some(cheats_file) = args.cheats - && let Ok(data) = std::fs::read(cheats_file) - && let Ok(cheats) = serde_json::from_slice(&data) - { - cheats - } else { - let game_crc = ui::storage::get_game_crc(&rom_contents); - ui::config::Cheats::new() - .cheats - .get(&game_crc) - .cloned() - .unwrap_or_default() - }; - - let mut shutdown_tx = None; - let mut usb_handle = None; - - if let Some(peer_addr) = args.netplay_peer_addr - && let Some(player_number) = args.netplay_player_number - { - device.netplay = Some(netplay::init(peer_addr.parse().unwrap(), player_number)); - } else { - for i in 0..4 { - if device.ui.config.input.transfer_pak[i] - && !device.ui.config.input.gb_rom_path[i].is_empty() - && !device.ui.config.input.gb_ram_path[i].is_empty() - && let Ok(rom) = std::fs::read(&device.ui.config.input.gb_rom_path[i]) - && let Ok(ram) = std::fs::read(&device.ui.config.input.gb_ram_path[i]) - { - device::controller::gbcart::init(&mut device.transferpaks[i].cart, &rom, &ram); - } - } - - if device.ui.config.emulation.usb { - (shutdown_tx, usb_handle, device.ui.usb) = ui::usb::init(); - } - - if let Some(username) = args.ra_username { - retroachievements::init_client( - if cfg!(ra_hardcore_enabled) { - args.ra_hardcore - } else { - false - }, - args.ra_challenge, - args.ra_leaderboard, - ); - - let (tx, rx) = tokio::sync::oneshot::channel::(); - if let Some(password) = args.ra_password { - retroachievements::login_user(username, password, tx); - } else if let Some(token) = args.ra_token { - retroachievements::login_token_user(username, token, tx); - } else { - tx.send(false).unwrap(); - } - - rx.await.unwrap(); - } - } - - let (discord_watch_tx, discord_handle) = retroachievements::load_game( - &rom_contents, - rom_contents.len(), - args.discord_rich_presence, - ) - .await; - - device::run_game( - &mut device, - &rom_contents, - ui::GameSettings { - overclock, - disable_expansion_pak, - cheats, - load_savestate_slot: args.load_state, - }, - ); - - retroachievements::shutdown_client(discord_watch_tx, discord_handle).await; - - if device.netplay.is_some() { - netplay::close(&mut device); - } else { - for i in 0..4 { - if device.ui.config.input.transfer_pak[i] - && !device.ui.config.input.gb_ram_path[i].is_empty() - && !device.transferpaks[i].cart.ram.is_empty() - { - device::controller::gbcart::save( - &mut device.transferpaks[i].cart, - device.vi.elapsed_time as i64, - &device.ui.config.input.gb_ram_path[i], - ); - } - } - } - if let Some(shutdown_tx) = &shutdown_tx { - ui::usb::close(shutdown_tx, usb_handle).await; - } - } else if std::env::args().count() > 1 { - let mut config = ui::config::Config::new(); - - if let Some(profile) = args.configure_input_profile { - ui::input::configure_input_profile( - &mut config, - profile, - args.use_dinput, - args.deadzone.unwrap_or(ui::input::DEADZONE_DEFAULT), - ); - - ui::sdl_close(); - return Ok(()); - } else { - if args.clear_input_bindings { - ui::input::clear_bindings(&mut config); - return Ok(()); - } - if let Some(port) = args.port - && !(1..=4).contains(&port) - { - return Err(Error::other("Port must be between 1 and 4")); - } - - ui::sdl_init(sdl3_sys::init::SDL_INIT_GAMEPAD); - - if args.list_controllers { - let controllers = ui::input::get_controller_names(); - for (i, controller) in controllers.iter().enumerate() { - println!("Controller {i}: {controller}"); - } - } else { - if let Some(assign_controller) = args.assign_controller { - let Some(port) = args.port else { - ui::sdl_close(); - return Err(Error::other("Must specify port number")); - }; - ui::input::assign_controller(&mut config, assign_controller - 1, port); - } - if let Some(profile) = args.bind_input_profile { - let Some(port) = args.port else { - ui::sdl_close(); - return Err(Error::other("Must specify port number")); - }; - ui::input::bind_input_profile(&mut config, profile, port); - } - } - } - } else { - #[cfg(feature = "gui")] - { - retroachievements::init_client(false, false, false); - ui::gui::app_window(); - retroachievements::shutdown_client(None, None).await; - } - } - - ui::sdl_close(); - Ok(()) + gopher64::run().await }