Compare commits
557 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c2ae4dc5a | |||
| 0a884e85df | |||
| 47098f60dd | |||
| ca4a20f524 | |||
| 3b145909e8 | |||
| 6eb3367f1b | |||
| 03f83fe917 | |||
| c4771f91e0 | |||
| bdd12352e1 | |||
| 224bd2df1d | |||
| 412f928781 | |||
| 779ef9fe77 | |||
| 048d1d662f | |||
| cf3a27cecf | |||
| b8c3556df6 | |||
| 9e337295ca | |||
| 8cb57811a6 | |||
| 82c02461f8 | |||
| 26253b5ed7 | |||
| f520aeab82 | |||
| edb6960d4f | |||
| a488f0d91a | |||
| 73d895a64a | |||
| 805bcba249 | |||
| 66abd14c3b | |||
| d856499f59 | |||
| bc3dd4d39a | |||
| 5a91d09f9e | |||
| b09f8bf891 | |||
| 8ee10e40ad | |||
| f8a7153fdd | |||
| a592d0eb84 | |||
| 28aa8ea280 | |||
| cb87f79710 | |||
| be117214c3 | |||
| bb056a77f4 | |||
| d4a90155f9 | |||
| e24a9a8126 | |||
| 83fab4c638 | |||
| 8196b52289 | |||
| 8dadbbbf58 | |||
| 093b39b17d | |||
| 7100750e2d | |||
| 40e250386e | |||
| d2e47509b2 | |||
| 451d66beee | |||
| 20bad48894 | |||
| b40ec6940f | |||
| d12f16fe29 | |||
| d061a856d2 | |||
| 7725aa2692 | |||
| baa14de3f3 | |||
| c14b461612 | |||
| 595089187d | |||
| c4b86eb4b1 | |||
| 4240a99ad5 | |||
| 9d7db93c70 | |||
| 9b71d3aa0b | |||
| e8898c52fc | |||
| 4b5c8d03a9 | |||
| 2706f8f6a5 | |||
| e4391b3089 | |||
| 4b54ea279d | |||
| 9f6194d64f | |||
| 8bb3179534 | |||
| 35dbd957cc | |||
| 125f79a0e6 | |||
| f865ccfacd | |||
| 914798e717 | |||
| 406ebba93d | |||
| d8ec09d475 | |||
| a1da699580 | |||
| 1226052035 | |||
| 2917e662df | |||
| 601713e102 | |||
| 6ed53e4e9d | |||
| 650fd270e1 | |||
| 54d82713ac | |||
| 11cb242ed2 | |||
| e932239a63 | |||
| 5207bd4b27 | |||
| d378ae187a | |||
| e72ddf73f0 | |||
| 4bba36f0c3 | |||
| 20b5cf3e4d | |||
| 973f4bb28d | |||
| de0db4b46d | |||
| 11d3e52779 | |||
| cef70faf1e | |||
| 9b0b3e67a4 | |||
| ebd22415cd | |||
| 5177aaab53 | |||
| ad5dc8311a | |||
| cc538f942f | |||
| 9528473f11 | |||
| 8b770cfd18 | |||
| 46cb6738a2 | |||
| 9b8040873d | |||
| bff39ab04d | |||
| 86ce5795fa | |||
| a834a4f9a4 | |||
| 1a4de8e911 | |||
| 0f607e61aa | |||
| 6220a566eb | |||
| 5da6a6c3ee | |||
| 567dd3a79d | |||
| 4a0a80b045 | |||
| 74b087e77e | |||
| faadefe96e | |||
| 72df783d8c | |||
| e7dfd02e17 | |||
| c6703244b3 | |||
| 8291a93337 | |||
| 3e177ea4be | |||
| 6de1ef3548 | |||
| 73a3e6a22a | |||
| fcceca5976 | |||
| acf5e08b97 | |||
| 768fcdaf7f | |||
| 1c6e23b7f2 | |||
| b4ae7db3dd | |||
| e750b6eed1 | |||
| f8b777248a | |||
| 51e9b3aa55 | |||
| efbeaeab88 | |||
| f24a5e7f05 | |||
| 55170fcca3 | |||
| b952c45e0b | |||
| b13f37baf8 | |||
| db40ec5034 | |||
| cedb2d5c22 | |||
| 22d57677cb | |||
| f29f497405 | |||
| 0aab9f71e1 | |||
| 7f84eb2aff | |||
| b460d55714 | |||
| ceaf56c563 | |||
| 5937922558 | |||
| e9e71a6e02 | |||
| 9775f103e0 | |||
| bf01898c6f | |||
| ce3b12bba9 | |||
| 56ff2b260c | |||
| 367c22877b | |||
| befbacc5b3 | |||
| e1a6663bb5 | |||
| c5185e60c6 | |||
| 1aa879d759 | |||
| bfad71f09f | |||
| 431b787998 | |||
| d48e3dc106 | |||
| 8be1ac7740 | |||
| 56d0b1b2e2 | |||
| 7f63bf1fde | |||
| 05be960358 | |||
| 38a5079acf | |||
| 144e31c7fc | |||
| e7a21171f4 | |||
| 413da0178e | |||
| d8c6669da1 | |||
| c6fcfddf8c | |||
| 0aa5c3e63a | |||
| 6f8433138d | |||
| ea5bbfb218 | |||
| 10af598223 | |||
| c08511adc5 | |||
| f116617ac3 | |||
| 1141095b65 | |||
| 162a6df56d | |||
| 72f47e5aff | |||
| 6d40f5a12d | |||
| 64e6d75db1 | |||
| 20761faf8c | |||
| 79eb34d83d | |||
| c4057c2f21 | |||
| 581cc0b9aa | |||
| 6f6e21c718 | |||
| 181774a1de | |||
| 002fbbe8a4 | |||
| ab630cc8cb | |||
| 06f9c56a79 | |||
| c3f4b11519 | |||
| 921a0d5362 | |||
| 24fc5b9638 | |||
| 1559856483 | |||
| 1deb91fc1e | |||
| 726a58f854 | |||
| fe0297690f | |||
| 847df61526 | |||
| 6de8128f5b | |||
| 6c09176d12 | |||
| 2050098735 | |||
| d3ecc37da6 | |||
| a0237e9f7d | |||
| d6d608b674 | |||
| f254bec968 | |||
| bd4aff2b2c | |||
| 0bf70a77ea | |||
| e23e7f33c1 | |||
| 9d0516545e | |||
| e3023a902f | |||
| 431c7c2a8d | |||
| cf99539f85 | |||
| c2493b829e | |||
| 2f25de55d7 | |||
| 116ffe9845 | |||
| f3271cba63 | |||
| 9037680cda | |||
| fc41e26a6d | |||
| d1f28bb7a8 | |||
| 9db831fdef | |||
| 004b603e96 | |||
| 8661e2c9b1 | |||
| c712f2a288 | |||
| 366162e6fe | |||
| 2b93fb8aa1 | |||
| a191701610 | |||
| 0605967c28 | |||
| d6e6a16814 | |||
| e374825d5e | |||
| f6d6746207 | |||
| 100238f198 | |||
| daee53a626 | |||
| 018d8ca3e3 | |||
| 24d26d864e | |||
| 7b8c549e8e | |||
| f6c7e3d3c0 | |||
| 374b01dccf | |||
| 569b26a067 | |||
| 380d196ae7 | |||
| 4fa1a8ad39 | |||
| 8acf3b9de8 | |||
| b3297f82b2 | |||
| 2127705e7b | |||
| cccb079d63 | |||
| c52b680753 | |||
| 8799650245 | |||
| 90be2130c1 | |||
| 96af33c434 | |||
| 0a42152bcb | |||
| db335e24d2 | |||
| 4857b86897 | |||
| 26022fff72 | |||
| 658dede9e9 | |||
| 6ad1a31e34 | |||
| 4476d476de | |||
| 8037fb426e | |||
| f0381a2cd1 | |||
| c8e70e6125 | |||
| e9664b1087 | |||
| 3f72700da0 | |||
| 9fcd2835eb | |||
| c6767c94a6 | |||
| f1968e3a58 | |||
| 23fdfff1ac | |||
| fc512d9517 | |||
| 3c317460fd | |||
| b1fcaa5cf4 | |||
| 98c9cf142d | |||
| 1eb1503981 | |||
| 5aa9a65e7b | |||
| 71f1d98132 | |||
| 0037c0f344 | |||
| 752e804101 | |||
| 73354246f3 | |||
| a2d23c2f41 | |||
| 6e652c7dbd | |||
| 20749cf036 | |||
| d7f656cd6a | |||
| 8558334391 | |||
| b6de872d78 | |||
| 712dc10c65 | |||
| cd2b5b4e92 | |||
| 98b5d6506e | |||
| f2e8bc6356 | |||
| 328e1730a0 | |||
| 8010a6211c | |||
| 41b1fd05be | |||
| c5da2a795f | |||
| 7361a9bd5c | |||
| d4e8933352 | |||
| fff376db5e | |||
| d7f1c8415a | |||
| ac09be4202 | |||
| 022190384a | |||
| e4dd5c2dee | |||
| 3e95059ec7 | |||
| abc39e74ef | |||
| 7109d28223 | |||
| 1d1e3829e4 | |||
| a46e4f505b | |||
| 03fbe658f2 | |||
| b96c9e4cdc | |||
| cea405ea30 | |||
| c13d354201 | |||
| ff43f59f74 | |||
| eb97b96daf | |||
| 9bf43a16b4 | |||
| b67247f935 | |||
| f10c591047 | |||
| 82f1965f26 | |||
| a5ddea353a | |||
| 6c977dbe5e | |||
| 5dcfd72b18 | |||
| e578d6083d | |||
| e99a236070 | |||
| e37633ecbd | |||
| d42e145b1f | |||
| 8ab31b0c28 | |||
| 6d43191c56 | |||
| 75457c5f7a | |||
| 6d85358f2b | |||
| ef4f93d6f0 | |||
| e4b7e291dd | |||
| 16732d3d3c | |||
| 5857da0404 | |||
| 6c6d0f32a7 | |||
| 7b400a0edc | |||
| 8f6c6f1b94 | |||
| 7fd43886de | |||
| 572807def0 | |||
| 10a1e3a556 | |||
| c12dc460a7 | |||
| 72456b48e5 | |||
| 6ecb81fda1 | |||
| 31b4e7944b | |||
| dc1535ea04 | |||
| 7a59714417 | |||
| ead561933b | |||
| 1d331e73fb | |||
| 25aaa985f1 | |||
| 11830b0222 | |||
| 3183a5eb0b | |||
| 5d8ac37a0f | |||
| baa6a6c635 | |||
| f389be0773 | |||
| 30eee885e4 | |||
| 3da98c58c5 | |||
| 6e9b8ae126 | |||
| ada6936b9a | |||
| 0bb3d2cd5d | |||
| f213b0b9a9 | |||
| 69029f3a02 | |||
| 0c2b4b2aac | |||
| c7680f10a0 | |||
| a28ed6766b | |||
| 6e6b1d463d | |||
| 662c0060a1 | |||
| 79e4cb1807 | |||
| f3e819158a | |||
| e83bcbb4ce | |||
| eb5d255fd7 | |||
| 776d127a84 | |||
| af5f292459 | |||
| c25a56df50 | |||
| 9d9f965b41 | |||
| 68197d235b | |||
| 1c91abbf2d | |||
| 9348594eb0 | |||
| e97f4e7f16 | |||
| cea55914d9 | |||
| 77a22f08b3 | |||
| cf0f325ced | |||
| 21f1a274b4 | |||
| def8932922 | |||
| 5b6f9b76a6 | |||
| 5a838464a4 | |||
| 383b568012 | |||
| 27bb66df4c | |||
| 33f74e5062 | |||
| 68f90e1cd7 | |||
| cc5f636912 | |||
| e3a9849ad7 | |||
| 7ed6867691 | |||
| 11a8e96564 | |||
| 7111cc9761 | |||
| b4832b941f | |||
| 82b1888f82 | |||
| f35869190b | |||
| 64eda1a1e3 | |||
| 8026bf13d6 | |||
| 83103130e7 | |||
| 87717c4c7e | |||
| 6355885a56 | |||
| 5d180dfb65 | |||
| fd6965ff3b | |||
| 2fcf202a5c | |||
| 93630c4156 | |||
| a66e8184e4 | |||
| e4f05af38d | |||
| 8a09cb711f | |||
| a58eed7dc9 | |||
| 09ba7e4d13 | |||
| 7316e41c7e | |||
| 1aa00de1b2 | |||
| f71ec9d54a | |||
| bf2734e59c | |||
| 7ee26961f1 | |||
| 5d692318fe | |||
| f2966230f9 | |||
| 48575f9c52 | |||
| dbab58f4a7 | |||
| f6b0b59bbc | |||
| ca61695dd1 | |||
| a182fbb1f9 | |||
| da9b2247f8 | |||
| 1bcadb3a8b | |||
| 433f08c7e0 | |||
| b07dde8768 | |||
| cba7bc7477 | |||
| ea6f203e33 | |||
| e48ad9bdff | |||
| 11b1137412 | |||
| 5528f233c0 | |||
| 9d18673257 | |||
| 2a3fa03b35 | |||
| ec2f47e5d3 | |||
| 139d66eb82 | |||
| 6e10bee216 | |||
| dff9fc5381 | |||
| 84dcc6c154 | |||
| 8aa23e1665 | |||
| f2b75a2dcc | |||
| 5bac846bda | |||
| e93c275795 | |||
| 67a8bb5e4c | |||
| 2828accee1 | |||
| 16a9754bda | |||
| fbe95767f5 | |||
| 0989be1e0f | |||
| e73816541e | |||
| f6b2342e0e | |||
| 7ab57d592b | |||
| a2cda03c8d | |||
| a07237aa64 | |||
| fdc17f4ae5 | |||
| 4802f3b71a | |||
| a4c505c72d | |||
| 30cb3f8339 | |||
| a5f5867ebb | |||
| ee8e34e17c | |||
| da1e686c1a | |||
| 5a3609d501 | |||
| 7b51ca82eb | |||
| ee77e75855 | |||
| ff093a9f7f | |||
| f7cb0db348 | |||
| eb9f37f1f5 | |||
| 37b902eb31 | |||
| 00964f06f0 | |||
| 319d7c8553 | |||
| 82b4adb4ad | |||
| 8fbc63a677 | |||
| 48ee50dea3 | |||
| 34a09925b4 | |||
| 168347b46e | |||
| 95a3d3d5ec | |||
| f6349f13da | |||
| fb2cf7fcb9 | |||
| 9980302d37 | |||
| 4692e2bcb9 | |||
| 04390301c9 | |||
| bc415821d6 | |||
| a068363787 | |||
| 97839fae40 | |||
| b6c805a27d | |||
| b7509a2425 | |||
| 6ecedde329 | |||
| 09dfd69283 | |||
| d262567084 | |||
| c363e78fbb | |||
| 792a3a6476 | |||
| 5bdb54a911 | |||
| b01c2d1885 | |||
| bb2da243b3 | |||
| 938a99caa9 | |||
| f7bfcfccba | |||
| 656e390171 | |||
| 17990dfebc | |||
| 671a961789 | |||
| aac68a4a1b | |||
| dc5a78a75e | |||
| 91a5483452 | |||
| baee7ffb82 | |||
| c2780c78b3 | |||
| ba006ddd53 | |||
| e04a151d49 | |||
| 8566eee906 | |||
| a1c2ac21be | |||
| b7d194e12d | |||
| df9ad377d3 | |||
| 8f20abe813 | |||
| 3aca3c1664 | |||
| 500b4bb505 | |||
| b01bbaa82f | |||
| 2010b82c8e | |||
| 74c65750a3 | |||
| 2d09425ca1 | |||
| d0965c03e1 | |||
| 6ee55ce4ca | |||
| 63b2ed69e4 | |||
| 0faf33fcfa | |||
| b67268ff62 | |||
| bda50f8d9d | |||
| ea012b4587 | |||
| a3e67d7ad9 | |||
| 8084a8cb80 | |||
| 2289971987 | |||
| 6c1f8d0ab0 | |||
| 7f6de5119f | |||
| 1c168c4bcd | |||
| 99511b7cc6 | |||
| 421d533479 | |||
| ab5ef57106 | |||
| 37b41c0f66 | |||
| 4c50203ad3 | |||
| 271e4be68c | |||
| a9afd6bb5c | |||
| cd2b3dd143 | |||
| ab31b44aac | |||
| 58eaad1742 | |||
| a8e7c3c136 | |||
| f4099cfcce | |||
| 5b3e2a25de | |||
| 55ab1cb60b | |||
| 778a0a4015 | |||
| 96d2a72850 | |||
| 4c7361f963 | |||
| 80fc5f8ea2 | |||
| f0eb5764c8 | |||
| 480af8d36e | |||
| ad4eddb408 | |||
| 166b7daa9d | |||
| 920a0323c0 | |||
| 14e45be300 | |||
| fba6a4a6d4 | |||
| 1efdeafc31 | |||
| a7c2c26d78 | |||
| 7d63a694cc | |||
| 5c98a08fe4 | |||
| 39ffad3af3 | |||
| ca2a3d423b | |||
| fe5346235e | |||
| f84444a12e | |||
| 026e4c0e74 | |||
| 363ed0953a | |||
| cb9309d7e4 | |||
| 1b1313003c | |||
| 7219a4445c | |||
| 687badc142 | |||
| 3e2189463f | |||
| ed8b4e8e02 | |||
| 611121568e | |||
| 27922fbf9c | |||
| 71b7baf613 | |||
| f4714a7813 | |||
| 4730b7f293 |
@@ -1,5 +1,23 @@
|
||||
[target.'cfg(all(target_arch = "aarch64", target_os = "linux"))']
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
|
||||
|
||||
[target.'cfg(any(target_os = "windows", target_os = "macos"))']
|
||||
linker = "rust-lld"
|
||||
|
||||
[target.'cfg(target_os = "windows")']
|
||||
rustflags = ["-C", "link-arg=/STACK:8388608"]
|
||||
|
||||
[target.'cfg(target_arch = "x86_64")']
|
||||
rustflags = ["-C", "target-cpu=x86-64-v3"]
|
||||
|
||||
[target.'cfg(target_arch = "aarch64")']
|
||||
[target.'cfg(all(target_arch = "aarch64", target_os = "macos"))']
|
||||
rustflags = ["-C", "target-cpu=apple-m1"]
|
||||
|
||||
[target.'cfg(all(target_arch = "aarch64", not(target_os = "macos")))']
|
||||
rustflags = ["-C", "target-cpu=cortex-a76"]
|
||||
|
||||
[env]
|
||||
CC = "clang"
|
||||
CXX = "clang++"
|
||||
AR = "llvm-ar"
|
||||
SDKROOT = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk"
|
||||
|
||||
@@ -3,8 +3,13 @@ updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: "cargo"
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "rust-toolchain"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
interval: "weekly"
|
||||
|
||||
# - package-ecosystem: "cargo"
|
||||
# directory: "/"
|
||||
# schedule:
|
||||
# interval: "daily"
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
name: Rust
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
branches: [ "main" ]
|
||||
tags:
|
||||
- "v*"
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
branches: [ "main" ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RA_HARDCORE: ${{ github.ref_type == 'tag' && 'true' || 'false' }}
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
@@ -18,79 +23,201 @@ jobs:
|
||||
include:
|
||||
- os: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
arch: linux-x86_64
|
||||
llvm: X64
|
||||
- os: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-gnu
|
||||
arch: linux-aarch64
|
||||
llvm: ARM64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
rustflags: ""
|
||||
cache: ${{ github.ref_type != 'tag' }}
|
||||
- name: Set LLVM_VERSION
|
||||
run: echo "LLVM_VERSION=$(rustc --version --verbose | grep "LLVM version" | cut
|
||||
-d':' -f2 | xargs)" >> $GITHUB_ENV
|
||||
- name: Install SDL Dependencies
|
||||
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y dist-upgrade
|
||||
sudo apt-get -y install build-essential git make \
|
||||
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
|
||||
libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
|
||||
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \
|
||||
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
|
||||
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
|
||||
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
|
||||
# https://wiki.libsdl.org/SDL3/README-linux#build-dependencies
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1
|
||||
with:
|
||||
packages: build-essential git make pkg-config cmake ninja-build
|
||||
gnome-desktop-testing libasound2-dev libpulse-dev libaudio-dev
|
||||
libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev
|
||||
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev
|
||||
libxtst-dev libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev
|
||||
libgles2-mesa-dev libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev
|
||||
libudev-dev libthai-dev libpipewire-0.3-dev libwayland-dev
|
||||
libdecor-0-dev liburing-dev libfreetype-dev libfontconfig-dev
|
||||
version: ${{ matrix.target }}-1.0
|
||||
- name: Install LLVM and Clang
|
||||
uses: KyleMayes/install-llvm-action@v2
|
||||
with:
|
||||
version: ${{ env.LLVM_VERSION }}
|
||||
force-url: https://github.com/llvm/llvm-project/releases/download/llvmorg-${{
|
||||
env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-Linux-${{
|
||||
matrix.llvm }}.tar.xz
|
||||
- name: Build
|
||||
run: |
|
||||
cargo build --release -v --target=${{ matrix.target }}
|
||||
mkdir output
|
||||
cp target/${{ matrix.target }}/release/gopher64 output/gopher64-${{ matrix.arch }}
|
||||
env:
|
||||
NETPLAY_ID: ${{ secrets.NETPLAY_ID }}
|
||||
- name: Upload file
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: gopher64-${{ matrix.target }}
|
||||
path: target/${{ matrix.target }}/release/gopher64
|
||||
name: gopher64-${{ matrix.arch }}
|
||||
path: output/gopher64-${{ matrix.arch }}
|
||||
archive: false
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-2025
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [x86_64-pc-windows-msvc]
|
||||
include:
|
||||
- os: windows-2025
|
||||
target: x86_64-pc-windows-msvc
|
||||
arch: windows-x86_64
|
||||
llvm: win64
|
||||
- os: windows-11-arm
|
||||
target: aarch64-pc-windows-msvc
|
||||
arch: windows-aarch64
|
||||
llvm: woa64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
rustflags: ""
|
||||
cache: ${{ github.ref_type != 'tag' }}
|
||||
- name: Set LLVM_VERSION
|
||||
run: |
|
||||
$llvm_version = rustc --version --verbose | Select-String "LLVM version" | ForEach-Object { $_.Line.Split(":")[1].Trim() }
|
||||
echo "LLVM_VERSION=$llvm_version" >> $env:GITHUB_ENV
|
||||
- name: Install LLVM and Clang
|
||||
uses: KyleMayes/install-llvm-action@v2
|
||||
with:
|
||||
version: ${{ env.LLVM_VERSION }}
|
||||
force-url: https://github.com/llvm/llvm-project/releases/download/llvmorg-${{
|
||||
env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-${{ matrix.llvm
|
||||
}}.exe
|
||||
- name: Build
|
||||
run: |
|
||||
cargo build --release -v --target=${{ matrix.target }}
|
||||
- name: Upload file
|
||||
uses: actions/upload-artifact@v4
|
||||
mkdir output
|
||||
cp target/${{ matrix.target }}/release/gopher64.exe output/gopher64-${{ matrix.arch }}.exe
|
||||
env:
|
||||
CC: clang-cl
|
||||
CXX: clang-cl
|
||||
NETPLAY_ID: ${{ secrets.NETPLAY_ID }}
|
||||
- name: Upload unsigned artifact
|
||||
id: upload-unsigned-artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: gopher64-${{ matrix.target }}
|
||||
path: target/${{ matrix.target }}/release/gopher64.exe
|
||||
name: gopher64-${{ matrix.arch }}
|
||||
path: output/gopher64-${{ matrix.arch }}.exe
|
||||
archive: ${{ github.ref_type == 'tag' && true || false }}
|
||||
- name: SignPath
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: signpath/github-action-submit-signing-request@v2
|
||||
with:
|
||||
api-token: "${{ secrets.SIGNPATH_API_TOKEN }}"
|
||||
organization-id: "${{ secrets.SIGNPATH_ORG_ID }}"
|
||||
project-slug: "gopher64"
|
||||
artifact-configuration-slug: "gopher64-${{ matrix.arch }}"
|
||||
signing-policy-slug: "release-signing"
|
||||
github-artifact-id: "${{ steps.upload-unsigned-artifact.outputs.artifact-id }}"
|
||||
wait-for-completion: true
|
||||
wait-for-completion-timeout-in-seconds: 3600
|
||||
output-artifact-directory: output-signed
|
||||
- name: Upload signed artifact
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: gopher64-${{ matrix.arch }}
|
||||
path: output-signed/gopher64-${{ matrix.arch }}.exe
|
||||
archive: false
|
||||
overwrite: true
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-14
|
||||
runs-on: macos-15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [x86_64-apple-darwin, aarch64-apple-darwin]
|
||||
include:
|
||||
- target: aarch64-apple-darwin
|
||||
arch: macos-aarch64
|
||||
llvm: ARM64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
rustflags: ""
|
||||
cache: ${{ github.ref_type != 'tag' }}
|
||||
- name: Set LLVM_VERSION
|
||||
run: echo "LLVM_VERSION=$(rustc --version --verbose | grep "LLVM version" | cut
|
||||
-d':' -f2 | xargs)" >> $GITHUB_ENV
|
||||
- name: Install LLVM and Clang
|
||||
uses: KyleMayes/install-llvm-action@v2
|
||||
with:
|
||||
version: ${{ env.LLVM_VERSION }}
|
||||
force-url: https://github.com/llvm/llvm-project/releases/download/llvmorg-${{
|
||||
env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-macOS-${{
|
||||
matrix.llvm }}.tar.xz
|
||||
- name: Build
|
||||
run: |
|
||||
cargo build --release -v --target=${{ matrix.target }}
|
||||
- name: Upload file
|
||||
uses: actions/upload-artifact@v4
|
||||
cargo install cargo-bundle
|
||||
brew install molten-vk
|
||||
cargo build --release --no-default-features -v --target=${{ matrix.target }}
|
||||
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.7"
|
||||
- name: Sign Bundle Dev
|
||||
if: ${{ github.ref_type != 'tag' }}
|
||||
run: |
|
||||
codesign -f --entitlements data/macos/entitlements_dev.plist --deep --timestamp --options runtime -s - target/${{ matrix.target }}/release/bundle/osx/Gopher64.app
|
||||
- name: Import Codesign Certs
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: apple-actions/import-codesign-certs@v6
|
||||
with:
|
||||
name: gopher64-${{ matrix.target }}
|
||||
path: target/${{ matrix.target }}/release/gopher64
|
||||
p12-file-base64: ${{ secrets.APPLE_CERTIFICATES_FILE_BASE64 }}
|
||||
p12-password: ${{ secrets.APPLE_CERTIFICATES_PASSWORD }}
|
||||
- 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
|
||||
zip -r gopher64-${{ matrix.arch }}.zip target/${{ matrix.target }}/release/bundle/osx/Gopher64.app
|
||||
xcrun notarytool submit --wait --apple-id "${{ secrets.APPLE_ID }}" --team-id "${{ secrets.APPLE_DEVELOPER_ID }}" --password "${{ secrets.APPLE_APP_PASSWORD }}" gopher64-${{ matrix.arch }}.zip
|
||||
xcrun stapler staple target/${{ matrix.target }}/release/bundle/osx/Gopher64.app
|
||||
- name: Upload file
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: gopher64-${{ matrix.arch }}
|
||||
path: target/${{ matrix.target }}/release/bundle/osx
|
||||
|
||||
@@ -2,9 +2,12 @@ name: Rust-lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
branches: [ "main" ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -13,25 +16,37 @@ jobs:
|
||||
lint-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
components: clippy, rustfmt
|
||||
- name: Set LLVM_VERSION
|
||||
run: echo "LLVM_VERSION=$(rustc --version --verbose | grep "LLVM version" | cut
|
||||
-d':' -f2 | xargs)" >> $GITHUB_ENV
|
||||
- name: Install SDL Dependencies
|
||||
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install build-essential git make \
|
||||
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
|
||||
libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
|
||||
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \
|
||||
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
|
||||
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
|
||||
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
|
||||
# https://wiki.libsdl.org/SDL3/README-linux#build-dependencies
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1
|
||||
with:
|
||||
packages: build-essential git make pkg-config cmake ninja-build
|
||||
gnome-desktop-testing libasound2-dev libpulse-dev libaudio-dev
|
||||
libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev
|
||||
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev
|
||||
libxtst-dev libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev
|
||||
libgles2-mesa-dev libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev
|
||||
libudev-dev libthai-dev libpipewire-0.3-dev libwayland-dev
|
||||
libdecor-0-dev liburing-dev libfreetype-dev libfontconfig-dev
|
||||
version: lint-1.0
|
||||
- name: Install LLVM and Clang
|
||||
uses: KyleMayes/install-llvm-action@v2
|
||||
with:
|
||||
version: ${{ env.LLVM_VERSION }}
|
||||
force-url: https://github.com/llvm/llvm-project/releases/download/llvmorg-${{
|
||||
env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-Linux-X64.tar.xz
|
||||
- name: Rustfmt Check
|
||||
uses: actions-rust-lang/rustfmt@v1
|
||||
- name: Lint
|
||||
run: |
|
||||
cargo clippy -- -Dwarnings
|
||||
cargo clippy --no-default-features -- -Dwarnings
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
name: Security audit
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
jobs:
|
||||
audit:
|
||||
permissions:
|
||||
issues: write
|
||||
checks: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: rustsec/audit-check@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -3,17 +3,13 @@
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
|
||||
.vscode/settings.json
|
||||
# Added by cargo
|
||||
|
||||
/target
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
[submodule "parallel-rdp/parallel-rdp-standalone"]
|
||||
path = parallel-rdp/parallel-rdp-standalone
|
||||
url = https://github.com/Themaister/parallel-rdp-standalone.git
|
||||
branch = master
|
||||
[submodule "src/compat/sse2neon"]
|
||||
path = src/compat/sse2neon
|
||||
url = https://github.com/DLTcollab/sse2neon.git
|
||||
branch = master
|
||||
[submodule "retroachievements/rcheevos"]
|
||||
path = retroachievements/rcheevos
|
||||
url = https://github.com/RetroAchievements/rcheevos.git
|
||||
|
||||
@@ -1,43 +1,63 @@
|
||||
[package]
|
||||
name = "gopher64"
|
||||
version = "0.1.23"
|
||||
edition = "2021"
|
||||
rust-version = "1.82"
|
||||
version = "1.1.18"
|
||||
edition = "2024"
|
||||
rust-version = "1.95.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[features]
|
||||
default = ["gui"]
|
||||
gui = ["dep:rfd", "dep:slint", "dep:slint-build"]
|
||||
|
||||
[dependencies]
|
||||
dirs = "6.0"
|
||||
zip = "2.1"
|
||||
governor = "0.8"
|
||||
sevenz-rust = "0.6"
|
||||
zip = "8.0"
|
||||
sevenz-rust2 = "0.20"
|
||||
chrono = "0.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
eframe = "0.30"
|
||||
sha2 = "0.10"
|
||||
rusttype = "0.9"
|
||||
sdl3-sys = { version = "0.4", features = ["build-from-source-static"] }
|
||||
rfd = "0.15"
|
||||
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 }
|
||||
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"] }
|
||||
rfd = {version = "0.17", optional = true }
|
||||
tokio = {version = "1.46", features = ["rt-multi-thread", "macros", "fs", "process"] }
|
||||
spin_sleep = "1.3"
|
||||
encoding_rs = "0.8"
|
||||
mimalloc = "0.1"
|
||||
regex = "1.11"
|
||||
tungstenite = "0.26"
|
||||
socket2 = "0.5"
|
||||
tokio-tungstenite = "0.29"
|
||||
futures = "0.3"
|
||||
socket2 = { version = "0.6", features = ["all"] }
|
||||
rand = "0.10"
|
||||
fatfs = "0.3"
|
||||
semver = "1.0"
|
||||
postcard = { version = "1.1", features = ["use-std"] }
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
|
||||
[dependencies.reqwest]
|
||||
version = "0.12"
|
||||
default-features = false
|
||||
features = ["http2","rustls-tls","charset","json","blocking"]
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
reqwest = { version = "0.13", features = ["json", "query"] }
|
||||
|
||||
[build-dependencies]
|
||||
winres = "0.1"
|
||||
bindgen = "0.71"
|
||||
winresource = "0.1"
|
||||
bindgen = "0.72"
|
||||
slint-build = { version = "1.12", optional = true }
|
||||
cc = { version = "1.2", features = ["parallel"] }
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
lto = "thin"
|
||||
strip = true
|
||||
|
||||
[profile.release-with-debug]
|
||||
inherits = "release"
|
||||
debug = true
|
||||
strip = false
|
||||
panic = "unwind"
|
||||
|
||||
[package.metadata.bundle]
|
||||
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_minimum_system_version = "15.7"
|
||||
|
||||
@@ -1,76 +1,71 @@
|
||||
# gopher64
|
||||
(gopher64 also has a GUI that can be launched by just running the executable)
|
||||
Gopher64 is a cross-platform N64 emulator. Some notable features:
|
||||
* Netplay
|
||||
* Homebrew support
|
||||
* Upscaling
|
||||
* CRT shader
|
||||
* Emulate CPU overclocking
|
||||
* Cheats
|
||||
* Savestates
|
||||
* RetroAchievements
|
||||
|
||||
```
|
||||
Usage: gopher64 [OPTIONS] [GAME]
|
||||
## download
|
||||
|
||||
Arguments:
|
||||
[GAME]
|
||||
Windows: https://github.com/gopher64/gopher64/releases/latest/download/gopher64-windows-x86_64.exe
|
||||
|
||||
MacOS: https://github.com/gopher64/gopher64/releases/latest/download/gopher64-macos-aarch64.zip
|
||||
|
||||
Linux: https://flathub.org/apps/io.github.gopher64.gopher64
|
||||
|
||||
## wiki
|
||||
|
||||
https://github.com/gopher64/gopher64/wiki
|
||||
|
||||
Options:
|
||||
-f, --fullscreen
|
||||
|
||||
-c, --configure-input-profile <PROFILE_NAME>
|
||||
Create a new input profile (keyboard/gamepad mappings).
|
||||
-u, --use-dinput
|
||||
Use DirectInput when configuring a new input profile.
|
||||
-b, --bind-input-profile <PROFILE_NAME>
|
||||
Must also specify --port. Used to bind a previously created profile to a port
|
||||
-l, --list-controllers
|
||||
Lists connected controllers which can be used in --assign-controller
|
||||
-a, --assign-controller <CONTROLLER_NUMBER>
|
||||
Must also specify --port. Used to assign a controller listed in --list-controllers to a port
|
||||
-p, --port <PORT>
|
||||
Valid values: 1-4. To be used alongside --bind-input-profile and --assign-controller
|
||||
-z, --clear-input-bindings
|
||||
Clear all input profile bindings and controller assignments
|
||||
-h, --help
|
||||
Print help
|
||||
-V, --version
|
||||
Print version
|
||||
```
|
||||
## discord
|
||||
|
||||
https://discord.gg/9RGXq8W8JQ
|
||||
[Discord invite link](https://discord.gg/9RGXq8W8JQ)
|
||||
|
||||
## controls
|
||||
|
||||
Keys are mapped according to mupen64plus defaults: https://mupen64plus.org/wiki/index.php/KeyboardSetup#2._Default_Key_Mappings_for_SDL-Input_Plugin. Xbox-style controllers also have a default mapping applied.
|
||||
|
||||
You can create you own mappings by running `./gopher64 --configure-input-profile my_profile`. You then bind that profile to a port: `./gopher64 --bind-input-profile my_profile --port 1`
|
||||
|
||||
In order to use a controller (for example, an Xbox controller), run `./gopher64 --list-controllers` to get a list of attached controllers, and then assign it by doing `./gopher64 --assign-controller <controller_number> --port 1`
|
||||
Keys are mapped according to [these defaults](https://github.com/gopher64/gopher64/wiki/Default-Keyboard-Setup). Xbox-style controllers also have a [default mapping applied](https://github.com/gopher64/gopher64/wiki/Default-Gamepad-Setup).
|
||||
|
||||
## netplay
|
||||
|
||||
Gopher64 supports netplay (online play with others). It has a few public netplay servers. If you are interested in running a public netplay server, please let me know (open an issue or discussion, or contact me on Discord). You can also run the server (https://github.com/simple64/simple64-netplay-server) yourself on a LAN.
|
||||
Gopher64 supports netplay (online play with others) via cloud hosted servers. You can also run the [server](https://github.com/gopher64/gopher64-netplay-server) yourself on a LAN.
|
||||
|
||||
## portable mode
|
||||
|
||||
If you would like to keep all the game data in the same folder as executable, you just need to create a file called "portable.txt" in the same directory as the executable.
|
||||
If you would like to keep all the game data in the same folder as the executable, you just need to create a file called "portable.txt" in the same directory as the executable.
|
||||
|
||||
## goals
|
||||
## flatpak
|
||||
|
||||
1. Performance. I want to be able to use this emulator on my laptop.
|
||||
2. Easy to use.
|
||||
3. Easy to work on. Dynamic recompilers perform well, but they are very hard to read and understand. This emulator will only have interpreters for the CPU and RSP. Additionally, it is completely written in Rust (besides Parallel-RDP), a modern programming language with a growing user base. I've tried to avoid the use of macros, which can reduce some repetitiveness in the code, but also reduce readability.
|
||||
If you want to run the flatpak from the command line, you need to add the `--filesystem=host:ro` option, for example:
|
||||
|
||||
```
|
||||
flatpak run --filesystem=host:ro io.github.gopher64.gopher64 /path/to/rom.z64
|
||||
```
|
||||
|
||||
## building and usage
|
||||
|
||||
1. Install rust: https://www.rust-lang.org/tools/install
|
||||
2. `git clone --recursive https://github.com/gopher64/gopher64.git`
|
||||
3. `cd gopher64`
|
||||
4. `cargo build --release`
|
||||
5. `./target/release/gopher64 /path/to/rom.z64`
|
||||
1. Linux only: [install the SDL3 dependencies](https://wiki.libsdl.org/SDL3/README-linux#build-dependencies)
|
||||
2. [Install rust](https://www.rust-lang.org/tools/install)
|
||||
3. `git clone --recursive https://github.com/gopher64/gopher64.git`
|
||||
4. `cd gopher64`
|
||||
5. `cargo build --release`
|
||||
6. `./target/release/gopher64 /path/to/rom.z64`
|
||||
|
||||
## contributing
|
||||
|
||||
I am very open to contributions! Please reach out to me via a GitHub issue, or via discord (loganmc10) before doing substantial work on a PR.
|
||||
I am very open to contributions! Please contact me via a GitHub issue or Discord (loganmc10) before doing substantial work on a PR.
|
||||
|
||||
## license
|
||||
|
||||
Gopher64 is licensed under the GPLv3 license. Many portions of gopher64 have been adapted from mupen64plus and/or ares. The license for mupen64plus can be found here: https://github.com/mupen64plus/mupen64plus-core/blob/master/LICENSES. The license for ares can be found here: https://github.com/ares-emulator/ares/blob/master/LICENSE.
|
||||
Gopher64 is licensed under the GPLv3 license. Many portions of gopher64 have been adapted from mupen64plus and/or ares. The license for mupen64plus can be found [here](https://github.com/mupen64plus/mupen64plus-core/blob/master/LICENSES). The license for ares can be found [here](https://github.com/ares-emulator/ares/blob/master/LICENSE).
|
||||
|
||||
## where to download
|
||||
## privacy and code signing policy
|
||||
|
||||
https://github.com/gopher64/gopher64/releases
|
||||
Free code signing for the Windows release is provided by [SignPath.io](https://about.signpath.io), certificate by [SignPath Foundation](https://signpath.org).
|
||||
|
||||
During online netplay sessions, the server logs your IP address and basic session information (game title and session name) for operational purposes. No additional personal data is collected or stored.
|
||||
|
||||
If you enable the RetroAchievements feature, some data is sent to their systems. Please see their terms [here](https://retroachievements.org/terms).
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
fn main() {
|
||||
println!("cargo::rerun-if-changed=parallel-rdp");
|
||||
println!("cargo::rerun-if-changed=retroachievements");
|
||||
println!("cargo::rerun-if-changed=src/compat");
|
||||
|
||||
#[cfg(feature = "gui")]
|
||||
{
|
||||
let slint_config = slint_build::CompilerConfiguration::new();
|
||||
slint_build::compile_with_config("src/ui/gui/appwindow.slint", slint_config).unwrap();
|
||||
}
|
||||
|
||||
let mut simd_build = cc::Build::new();
|
||||
let mut build = cc::Build::new();
|
||||
build
|
||||
let mut volk_build = cc::Build::new();
|
||||
volk_build
|
||||
.std("c17")
|
||||
.include("parallel-rdp/parallel-rdp-standalone/vulkan-headers/include")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/volk/volk.c");
|
||||
let mut rdp_build = cc::Build::new();
|
||||
rdp_build
|
||||
.cpp(true)
|
||||
.warnings(false)
|
||||
.std("c++17")
|
||||
.std("c++20")
|
||||
.flag("-Wno-unused-parameter")
|
||||
.flag("-Wno-missing-field-initializers")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/parallel-rdp/command_ring.cpp")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/parallel-rdp/rdp_device.cpp")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/parallel-rdp/rdp_dump_write.cpp")
|
||||
@@ -44,7 +57,6 @@ fn main() {
|
||||
.file("parallel-rdp/parallel-rdp-standalone/util/timeline_trace_file.cpp")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/util/environment.cpp")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/util/thread_name.cpp")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/volk/volk.c")
|
||||
.file("parallel-rdp/interface.cpp")
|
||||
.file("parallel-rdp/wsi_platform.cpp")
|
||||
.include("parallel-rdp/parallel-rdp-standalone/parallel-rdp")
|
||||
@@ -53,52 +65,152 @@ fn main() {
|
||||
.include("parallel-rdp/parallel-rdp-standalone/vulkan-headers/include")
|
||||
.include("parallel-rdp/parallel-rdp-standalone/util")
|
||||
.include(
|
||||
std::path::PathBuf::from(std::env::var("DEP_SDL3_OUT_DIR").to_owned().unwrap())
|
||||
std::path::PathBuf::from(std::env::var("DEP_SDL3_OUT_DIR").unwrap()).join("include"),
|
||||
)
|
||||
.include(
|
||||
std::path::PathBuf::from(std::env::var("DEP_SDL3_TTF_OUT_DIR").unwrap())
|
||||
.join("include"),
|
||||
);
|
||||
|
||||
let mut retroachievements_build = cc::Build::new();
|
||||
retroachievements_build
|
||||
.flag("-Wno-unused-parameter")
|
||||
.include("retroachievements/rcheevos/include")
|
||||
.flag("-DRC_CLIENT_SUPPORTS_HASH")
|
||||
.file("retroachievements/rcheevos/src/rc_client.c")
|
||||
.file("retroachievements/rcheevos/src/rc_compat.c")
|
||||
.file("retroachievements/rcheevos/src/rc_util.c")
|
||||
.file("retroachievements/rcheevos/src/rcheevos/alloc.c")
|
||||
.file("retroachievements/rcheevos/src/rcheevos/condition.c")
|
||||
.file("retroachievements/rcheevos/src/rcheevos/condset.c")
|
||||
.file("retroachievements/rcheevos/src/rcheevos/consoleinfo.c")
|
||||
.file("retroachievements/rcheevos/src/rcheevos/format.c")
|
||||
.file("retroachievements/rcheevos/src/rcheevos/lboard.c")
|
||||
.file("retroachievements/rcheevos/src/rcheevos/memref.c")
|
||||
.file("retroachievements/rcheevos/src/rcheevos/operand.c")
|
||||
.file("retroachievements/rcheevos/src/rcheevos/richpresence.c")
|
||||
.file("retroachievements/rcheevos/src/rcheevos/runtime.c")
|
||||
.file("retroachievements/rcheevos/src/rcheevos/runtime_progress.c")
|
||||
.file("retroachievements/rcheevos/src/rcheevos/trigger.c")
|
||||
.file("retroachievements/rcheevos/src/rcheevos/value.c")
|
||||
.file("retroachievements/rcheevos/src/rapi/rc_api_common.c")
|
||||
.file("retroachievements/rcheevos/src/rapi/rc_api_info.c")
|
||||
.file("retroachievements/rcheevos/src/rapi/rc_api_runtime.c")
|
||||
.file("retroachievements/rcheevos/src/rapi/rc_api_user.c")
|
||||
.file("retroachievements/rcheevos/src/rhash/aes.c")
|
||||
.file("retroachievements/rcheevos/src/rhash/cdreader.c")
|
||||
.file("retroachievements/rcheevos/src/rhash/md5.c")
|
||||
.file("retroachievements/rcheevos/src/rhash/hash.c")
|
||||
.file("retroachievements/rcheevos/src/rhash/hash_disc.c")
|
||||
.file("retroachievements/rcheevos/src/rhash/hash_encrypted.c")
|
||||
.file("retroachievements/rcheevos/src/rhash/hash_rom.c")
|
||||
.file("retroachievements/rcheevos/src/rhash/hash_zip.c")
|
||||
.file("retroachievements/retroachievements.c");
|
||||
|
||||
let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||
if os == "windows" {
|
||||
if arch == "x86_64" {
|
||||
build.flag("/arch:AVX2");
|
||||
} else if arch == "aarch64" {
|
||||
panic!("unsupported platform")
|
||||
} else {
|
||||
panic!("unknown arch")
|
||||
}
|
||||
build.flag("-DVK_USE_PLATFORM_WIN32_KHR");
|
||||
let opt_flag = if arch == "x86_64" {
|
||||
"-march=x86-64-v3"
|
||||
} else if arch == "aarch64" && os == "macos" {
|
||||
"-march=armv8.4-a"
|
||||
} else if arch == "aarch64" && os != "macos" {
|
||||
"-march=armv8.2-a"
|
||||
} else {
|
||||
panic!("unknown arch")
|
||||
};
|
||||
|
||||
winres::WindowsResource::new()
|
||||
.set_icon("data/icon.ico")
|
||||
volk_build.flag(opt_flag);
|
||||
rdp_build.flag(opt_flag);
|
||||
simd_build.flag(opt_flag);
|
||||
retroachievements_build.flag(opt_flag);
|
||||
|
||||
if os == "windows" {
|
||||
volk_build.flag("-DVK_USE_PLATFORM_WIN32_KHR");
|
||||
rdp_build.flag("-DVK_USE_PLATFORM_WIN32_KHR");
|
||||
|
||||
winresource::WindowsResource::new()
|
||||
.set_icon("data/icon/icon.ico")
|
||||
.compile()
|
||||
.unwrap();
|
||||
} else if os == "linux" || os == "macos" {
|
||||
if arch == "x86_64" {
|
||||
build.flag("-march=x86-64-v3");
|
||||
} else if arch == "aarch64" {
|
||||
build.flag("-march=armv8.2-a");
|
||||
simd_build.flag("-march=armv8.2-a");
|
||||
simd_build.flag("-DSSE2NEON_SUPPRESS_WARNINGS");
|
||||
simd_build.file("src/compat/aarch64.c");
|
||||
} else {
|
||||
panic!("unknown arch")
|
||||
}
|
||||
} else {
|
||||
panic!("unknown OS")
|
||||
} 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()
|
||||
.unwrap();
|
||||
|
||||
let runtime_dir = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
build.compile("parallel-rdp");
|
||||
volk_build.flag("-flto=thin");
|
||||
rdp_build.flag("-flto=thin");
|
||||
simd_build.flag("-flto=thin");
|
||||
retroachievements_build.flag("-flto=thin");
|
||||
|
||||
volk_build.compile("volk");
|
||||
rdp_build.compile("parallel-rdp");
|
||||
retroachievements_build.compile("retroachievements");
|
||||
|
||||
let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||
|
||||
let mut retroachievements_builder = bindgen::Builder::default()
|
||||
.header("retroachievements/retroachievements.h")
|
||||
.allowlist_function("ra_init_client")
|
||||
.allowlist_function("ra_welcome")
|
||||
.allowlist_function("ra_shutdown_client")
|
||||
.allowlist_function("ra_get_hardcore")
|
||||
.allowlist_function("ra_load_game")
|
||||
.allowlist_function("ra_unload_game")
|
||||
.allowlist_function("ra_set_dmem")
|
||||
.allowlist_function("ra_do_frame")
|
||||
.allowlist_function("ra_do_idle")
|
||||
.allowlist_function("ra_http_callback")
|
||||
.allowlist_function("ra_login_user")
|
||||
.allowlist_function("ra_login_token_user")
|
||||
.allowlist_function("ra_state_size")
|
||||
.allowlist_function("ra_save_state")
|
||||
.allowlist_function("ra_load_state");
|
||||
|
||||
if cfg!(feature = "gui") {
|
||||
retroachievements_builder = retroachievements_builder
|
||||
.allowlist_function("ra_logout_user")
|
||||
.allowlist_function("ra_get_username")
|
||||
.allowlist_function("ra_get_token");
|
||||
}
|
||||
|
||||
let retroachievements_bindings = retroachievements_builder
|
||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
|
||||
.generate()
|
||||
.expect("Unable to generate bindings");
|
||||
|
||||
retroachievements_bindings
|
||||
.write_to_file(out_path.join("retroachievements_bindings.rs"))
|
||||
.expect("Couldn't write bindings!");
|
||||
|
||||
let parallel_bindings = bindgen::Builder::default()
|
||||
.header("parallel-rdp/interface.hpp")
|
||||
.allowlist_function("rdp_init")
|
||||
.allowlist_function("rdp_close")
|
||||
.allowlist_function("rdp_set_vi_register")
|
||||
.allowlist_function("rdp_update_screen")
|
||||
.allowlist_function("rdp_render_frame")
|
||||
.allowlist_function("rdp_process_commands")
|
||||
.allowlist_function("rdp_onscreen_message")
|
||||
.allowlist_function("rdp_check_callback")
|
||||
.allowlist_function("rdp_new_processor")
|
||||
.allowlist_function("rdp_check_framebuffers")
|
||||
.allowlist_function("rdp_state_size")
|
||||
.allowlist_function("rdp_save_state")
|
||||
.allowlist_function("rdp_load_state")
|
||||
.allowlist_function("rdp_set_fps")
|
||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
|
||||
.generate()
|
||||
.expect("Unable to generate bindings");
|
||||
@@ -109,7 +221,7 @@ fn main() {
|
||||
|
||||
if arch == "aarch64" {
|
||||
let simd_bindings = bindgen::Builder::default()
|
||||
.header("src/compat/sse2neon/v1.8.0/sse2neon.h")
|
||||
.header("src/compat/sse2neon/sse2neon.h")
|
||||
.allowlist_function("_mm_setzero_si128")
|
||||
.allowlist_function("_mm_set_epi8")
|
||||
.allowlist_function("_mm_movemask_epi8")
|
||||
@@ -143,6 +255,7 @@ fn main() {
|
||||
.allowlist_function("_mm_cmplt_epi16")
|
||||
.allowlist_function("_mm_subs_epu16")
|
||||
.allowlist_function("_mm_set1_epi32")
|
||||
.allowlist_function("_mm_set1_epi16")
|
||||
.blocklist_type("__m128i")
|
||||
.blocklist_type("int64x2_t")
|
||||
.wrap_static_fns(true)
|
||||
@@ -154,8 +267,45 @@ fn main() {
|
||||
.write_to_file(out_path.join("simd_bindings.rs"))
|
||||
.expect("Couldn't write bindings!");
|
||||
|
||||
simd_build.file(std::env::temp_dir().join("bindgen").join("extern.c"));
|
||||
simd_build.include(".");
|
||||
simd_build.compile("simd");
|
||||
simd_build
|
||||
.std("c17")
|
||||
.flag("-D_POSIX_C_SOURCE=200112L")
|
||||
.flag("-DSSE2NEON_SUPPRESS_WARNINGS")
|
||||
.file("src/compat/aarch64.c")
|
||||
.file(std::env::temp_dir().join("bindgen").join("extern.c"))
|
||||
.include(".")
|
||||
.compile("simd");
|
||||
}
|
||||
|
||||
let mut git_output = std::process::Command::new("git");
|
||||
git_output.args(["describe", "--dirty"]);
|
||||
|
||||
let git_describe = if let Ok(git_output) = git_output.output()
|
||||
&& git_output.status.success()
|
||||
{
|
||||
String::from_utf8(git_output.stdout).unwrap()
|
||||
} else if let Ok(git_output) = git_output.args(["--always"]).output()
|
||||
&& git_output.status.success()
|
||||
{
|
||||
format!(
|
||||
"v{}-{}",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
String::from_utf8(git_output.stdout).unwrap()
|
||||
)
|
||||
} else {
|
||||
panic!("Failed to get git describe");
|
||||
};
|
||||
println!("cargo:rustc-env=GIT_DESCRIBE={git_describe}");
|
||||
|
||||
println!("cargo:rerun-if-env-changed=NETPLAY_ID");
|
||||
let netplay_id = std::env::var("NETPLAY_ID").unwrap_or("gopher64".to_string());
|
||||
println!("cargo:rustc-env=NETPLAY_ID={netplay_id}");
|
||||
|
||||
println!("cargo:rerun-if-env-changed=RA_HARDCORE");
|
||||
println!("cargo:rustc-check-cfg=cfg(ra_hardcore_enabled)");
|
||||
if let Ok(ra_hardcore) = std::env::var("RA_HARDCORE")
|
||||
&& ra_hardcore.to_lowercase() == "true"
|
||||
{
|
||||
println!("cargo:rustc-cfg=ra_hardcore_enabled");
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 97 KiB |
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
magick gopher64.png -resize 512x512 gopher64_512x512.png
|
||||
magick gopher64.png -resize 256x256 gopher64_256x256.png
|
||||
magick gopher64.png -resize 128x128 gopher64_128x128.png
|
||||
magick gopher64.png -resize 256x256 icon.ico
|
||||
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 243 KiB |
|
After Width: | Height: | Size: 64 KiB |
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.inherit</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.usb</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.bluetooth</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,135 @@
|
||||
#version 450
|
||||
|
||||
/*
|
||||
CRT Shader by EasyMode
|
||||
License: GPL
|
||||
*/
|
||||
|
||||
layout(push_constant) uniform Push
|
||||
{
|
||||
vec4 SourceSize;
|
||||
vec4 OutputSize;
|
||||
} params;
|
||||
|
||||
#define SHARPNESS_IMAGE 1.0
|
||||
#define SHARPNESS_EDGES 3.0
|
||||
#define GLOW_WIDTH 0.5
|
||||
#define GLOW_HEIGHT 0.5
|
||||
#define GLOW_HALATION 0.1
|
||||
#define GLOW_DIFFUSION 0.05
|
||||
#define MASK_COLORS 2.0
|
||||
#define MASK_STRENGTH 0.3
|
||||
#define MASK_SIZE 1.0
|
||||
#define SCANLINE_SIZE_MIN 0.5
|
||||
#define SCANLINE_SIZE_MAX 1.5
|
||||
#define SCANLINE_SHAPE 2.5
|
||||
#define SCANLINE_OFFSET 1.0
|
||||
#define GAMMA_INPUT 2.4
|
||||
#define GAMMA_OUTPUT 2.4
|
||||
#define BRIGHTNESS 1.5
|
||||
|
||||
layout(std140, set = 0, binding = 0) uniform UBO
|
||||
{
|
||||
mat4 MVP;
|
||||
} global;
|
||||
|
||||
#define FIX(c) max(abs(c), 1e-5)
|
||||
#define PI 3.141592653589
|
||||
#define TEX2D(c) pow(texture(tex, c).rgb, vec3(GAMMA_INPUT))
|
||||
#define saturate(c) clamp(c, 0.0, 1.0)
|
||||
|
||||
mat3x3 get_color_matrix(sampler2D tex, vec2 co, vec2 dx)
|
||||
{
|
||||
return mat3x3(TEX2D(co - dx), TEX2D(co), TEX2D(co + dx));
|
||||
}
|
||||
|
||||
vec3 blur(mat3 m, float dist, float rad)
|
||||
{
|
||||
vec3 x = vec3(dist - 1.0, dist, dist + 1.0) / rad;
|
||||
vec3 w = exp2(x * x * -1.0);
|
||||
|
||||
return (m[0] * w.x + m[1] * w.y + m[2] * w.z) / (w.x + w.y + w.z);
|
||||
}
|
||||
|
||||
vec3 filter_gaussian(sampler2D tex, vec2 co, vec2 tex_size)
|
||||
{
|
||||
vec2 dx = vec2(1.0 / tex_size.x, 0.0);
|
||||
vec2 dy = vec2(0.0, 1.0 / tex_size.y);
|
||||
vec2 pix_co = co * tex_size;
|
||||
vec2 tex_co = (floor(pix_co) + 0.5) / tex_size;
|
||||
vec2 dist = (fract(pix_co) - 0.5) * -1.0;
|
||||
|
||||
mat3x3 line0 = get_color_matrix(tex, tex_co - dy, dx);
|
||||
mat3x3 line1 = get_color_matrix(tex, tex_co, dx);
|
||||
mat3x3 line2 = get_color_matrix(tex, tex_co + dy, dx);
|
||||
mat3x3 column = mat3x3(blur(line0, dist.x, GLOW_WIDTH),
|
||||
blur(line1, dist.x, GLOW_WIDTH),
|
||||
blur(line2, dist.x, GLOW_WIDTH));
|
||||
|
||||
return blur(column, dist.y, GLOW_HEIGHT);
|
||||
}
|
||||
|
||||
vec3 filter_lanczos(sampler2D tex, vec2 co, vec2 tex_size, float sharp)
|
||||
{
|
||||
tex_size.x *= sharp;
|
||||
|
||||
vec2 dx = vec2(1.0 / tex_size.x, 0.0);
|
||||
vec2 pix_co = co * tex_size - vec2(0.5, 0.0);
|
||||
vec2 tex_co = (floor(pix_co) + vec2(0.5, 0.001)) / tex_size;
|
||||
vec2 dist = fract(pix_co);
|
||||
vec4 coef = PI * vec4(dist.x + 1.0, dist.x, dist.x - 1.0, dist.x - 2.0);
|
||||
|
||||
coef = FIX(coef);
|
||||
coef = 2.0 * sin(coef) * sin(coef / 2.0) / (coef * coef);
|
||||
coef /= dot(coef, vec4(1.0));
|
||||
|
||||
vec4 col1 = vec4(TEX2D(tex_co), 1.0);
|
||||
vec4 col2 = vec4(TEX2D(tex_co + dx), 1.0);
|
||||
|
||||
return (mat4x4(col1, col1, col2, col2) * coef).rgb;
|
||||
}
|
||||
|
||||
vec3 get_scanline_weight(float x, vec3 col)
|
||||
{
|
||||
vec3 beam = mix(vec3(SCANLINE_SIZE_MIN), vec3(SCANLINE_SIZE_MAX), pow(col, vec3(1.0 / SCANLINE_SHAPE)));
|
||||
vec3 x_mul = 2.0 / beam;
|
||||
vec3 x_offset = x_mul * 0.5;
|
||||
|
||||
return smoothstep(0.0, 1.0, 1.0 - abs(x * x_mul - x_offset)) * x_offset;
|
||||
}
|
||||
|
||||
vec3 get_mask_weight(float x)
|
||||
{
|
||||
float i = mod(floor(x * params.OutputSize.x * params.SourceSize.x / (params.SourceSize.x * MASK_SIZE)), MASK_COLORS);
|
||||
|
||||
if (i == 0.0) return mix(vec3(1.0, 0.0, 1.0), vec3(1.0, 0.0, 0.0), MASK_COLORS - 2.0);
|
||||
else if (i == 1.0) return vec3(0.0, 1.0, 0.0);
|
||||
else return vec3(0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
#pragma stage fragment
|
||||
layout(location = 0) in vec2 vTexCoord;
|
||||
layout(location = 0) out vec4 FragColor;
|
||||
layout(set = 0, binding = 0) uniform sampler2D Source;
|
||||
|
||||
void main()
|
||||
{
|
||||
float scale = floor(params.OutputSize.y * params.SourceSize.w);
|
||||
float offset = 1.0 / scale * 0.5;
|
||||
|
||||
if (bool(mod(scale, 2.0))) offset = 0.0;
|
||||
|
||||
vec2 co = (vTexCoord * params.SourceSize.xy - vec2(0.0, offset * SCANLINE_OFFSET)) * params.SourceSize.zw;
|
||||
vec3 col_glow = filter_gaussian(Source, co, params.SourceSize.xy);
|
||||
vec3 col_soft = filter_lanczos(Source, co, params.SourceSize.xy, SHARPNESS_IMAGE);
|
||||
vec3 col_sharp = filter_lanczos(Source, co, params.SourceSize.xy, SHARPNESS_EDGES);
|
||||
vec3 col = sqrt(col_sharp * col_soft);
|
||||
|
||||
col *= get_scanline_weight(fract(co.y * params.SourceSize.y), col_soft);
|
||||
col_glow = saturate(col_glow - col);
|
||||
col += col_glow * col_glow * GLOW_HALATION;
|
||||
col = mix(col, col * get_mask_weight(vTexCoord.x) * MASK_COLORS, MASK_STRENGTH);
|
||||
col += col_glow * GLOW_DIFFUSION;
|
||||
col = pow(col * BRIGHTNESS, vec3(1.0 / GAMMA_OUTPUT));
|
||||
FragColor = vec4(col, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,453 @@
|
||||
{0x07230203,0x00010300,0x000d000b,0x000004fd,
|
||||
0x00000000,0x00020011,0x00000001,0x0006000b,
|
||||
0x00000001,0x4c534c47,0x6474732e,0x3035342e,
|
||||
0x00000000,0x0003000e,0x00000000,0x00000001,
|
||||
0x0007000f,0x00000004,0x00000004,0x6e69616d,
|
||||
0x00000000,0x000001ba,0x0000021a,0x00030010,
|
||||
0x00000004,0x00000007,0x00030047,0x00000185,
|
||||
0x00000002,0x00050048,0x00000185,0x00000000,
|
||||
0x00000023,0x00000000,0x00050048,0x00000185,
|
||||
0x00000001,0x00000023,0x00000010,0x00040047,
|
||||
0x000001ba,0x0000001e,0x00000000,0x00040047,
|
||||
0x000001ca,0x00000021,0x00000000,0x00040047,
|
||||
0x000001ca,0x00000022,0x00000000,0x00040047,
|
||||
0x0000021a,0x0000001e,0x00000000,0x00020013,
|
||||
0x00000002,0x00030021,0x00000003,0x00000002,
|
||||
0x00030016,0x00000006,0x00000020,0x00090019,
|
||||
0x00000007,0x00000006,0x00000001,0x00000000,
|
||||
0x00000000,0x00000000,0x00000001,0x00000000,
|
||||
0x0003001b,0x00000008,0x00000007,0x00040020,
|
||||
0x00000009,0x00000000,0x00000008,0x00040017,
|
||||
0x0000000a,0x00000006,0x00000002,0x00040017,
|
||||
0x0000000c,0x00000006,0x00000003,0x00040017,
|
||||
0x00000037,0x00000006,0x00000004,0x0004002b,
|
||||
0x00000006,0x0000003a,0x4019999a,0x0006002c,
|
||||
0x0000000c,0x0000003b,0x0000003a,0x0000003a,
|
||||
0x0000003a,0x0004002b,0x00000006,0x00000049,
|
||||
0x3f800000,0x0004002b,0x00000006,0x0000004a,
|
||||
0x00000000,0x0004002b,0x00000006,0x00000068,
|
||||
0xbf800000,0x00040015,0x0000006b,0x00000020,
|
||||
0x00000001,0x0004002b,0x0000006b,0x0000006c,
|
||||
0x00000000,0x00040015,0x0000006f,0x00000020,
|
||||
0x00000000,0x0004002b,0x0000006f,0x00000070,
|
||||
0x00000000,0x0004002b,0x0000006b,0x00000074,
|
||||
0x00000001,0x0004002b,0x0000006f,0x00000077,
|
||||
0x00000001,0x0004002b,0x00000006,0x000000a1,
|
||||
0x3f000000,0x0005002c,0x0000000a,0x000000fc,
|
||||
0x000000a1,0x0000004a,0x0004002b,0x00000006,
|
||||
0x00000101,0x3a83126f,0x0005002c,0x0000000a,
|
||||
0x00000102,0x000000a1,0x00000101,0x0004002b,
|
||||
0x00000006,0x0000010b,0x40490fdb,0x0004002b,
|
||||
0x00000006,0x00000116,0x40000000,0x0004002b,
|
||||
0x00000006,0x0000011c,0x3727c5ac,0x0007002c,
|
||||
0x00000037,0x0000012c,0x00000049,0x00000049,
|
||||
0x00000049,0x00000049,0x00040018,0x00000149,
|
||||
0x00000037,0x00000004,0x0006002c,0x0000000c,
|
||||
0x00000165,0x000000a1,0x000000a1,0x000000a1,
|
||||
0x0004002b,0x00000006,0x00000166,0x3fc00000,
|
||||
0x0006002c,0x0000000c,0x00000167,0x00000166,
|
||||
0x00000166,0x00000166,0x0004002b,0x00000006,
|
||||
0x00000169,0x3ecccccd,0x0006002c,0x0000000c,
|
||||
0x0000016a,0x00000169,0x00000169,0x00000169,
|
||||
0x0004001e,0x00000185,0x00000037,0x00000037,
|
||||
0x00040020,0x00000186,0x00000009,0x00000185,
|
||||
0x0004003b,0x00000186,0x00000187,0x00000009,
|
||||
0x00040020,0x00000188,0x00000009,0x00000006,
|
||||
0x00020014,0x00000196,0x0006002c,0x0000000c,
|
||||
0x0000019a,0x00000049,0x0000004a,0x00000049,
|
||||
0x0006002c,0x0000000c,0x000001a1,0x0000004a,
|
||||
0x00000049,0x0000004a,0x0006002c,0x0000000c,
|
||||
0x000001a4,0x0000004a,0x0000004a,0x00000049,
|
||||
0x0004002b,0x0000006f,0x000001aa,0x00000003,
|
||||
0x00040020,0x000001b9,0x00000001,0x0000000a,
|
||||
0x0004003b,0x000001b9,0x000001ba,0x00000001,
|
||||
0x00040020,0x000001bc,0x00000009,0x00000037,
|
||||
0x0004003b,0x00000009,0x000001ca,0x00000000,
|
||||
0x0004002b,0x00000006,0x000001dc,0x40400000,
|
||||
0x0004002b,0x00000006,0x000001ff,0x3dcccccd,
|
||||
0x00040020,0x00000206,0x00000001,0x00000006,
|
||||
0x0004002b,0x00000006,0x0000020c,0x3e99999a,
|
||||
0x0004002b,0x00000006,0x00000210,0x3d4ccccd,
|
||||
0x0004002b,0x00000006,0x00000216,0x3ed55555,
|
||||
0x0006002c,0x0000000c,0x00000217,0x00000216,
|
||||
0x00000216,0x00000216,0x00040020,0x00000219,
|
||||
0x00000003,0x00000037,0x0004003b,0x00000219,
|
||||
0x0000021a,0x00000003,0x0005002c,0x0000000a,
|
||||
0x000004f3,0x000000a1,0x000000a1,0x0007002c,
|
||||
0x00000037,0x000004f4,0x0000011c,0x0000011c,
|
||||
0x0000011c,0x0000011c,0x0006002c,0x0000000c,
|
||||
0x000004f6,0x00000116,0x00000116,0x00000116,
|
||||
0x0006002c,0x0000000c,0x000004f7,0x00000049,
|
||||
0x00000049,0x00000049,0x0006002c,0x0000000c,
|
||||
0x000004f8,0x0000004a,0x0000004a,0x0000004a,
|
||||
0x0006002c,0x0000000c,0x000004f9,0x0000020c,
|
||||
0x0000020c,0x0000020c,0x0007002c,0x00000037,
|
||||
0x000004fa,0x000000a1,0x000000a1,0x000000a1,
|
||||
0x000000a1,0x0004002b,0x00000006,0x000004fb,
|
||||
0x3eaaaaab,0x00050036,0x00000002,0x00000004,
|
||||
0x00000000,0x00000003,0x000200f8,0x00000005,
|
||||
0x00060041,0x00000188,0x000001a8,0x00000187,
|
||||
0x00000074,0x00000077,0x0004003d,0x00000006,
|
||||
0x000001a9,0x000001a8,0x00060041,0x00000188,
|
||||
0x000001ab,0x00000187,0x0000006c,0x000001aa,
|
||||
0x0004003d,0x00000006,0x000001ac,0x000001ab,
|
||||
0x00050085,0x00000006,0x000001ad,0x000001a9,
|
||||
0x000001ac,0x0006000c,0x00000006,0x000001ae,
|
||||
0x00000001,0x00000008,0x000001ad,0x00050088,
|
||||
0x00000006,0x000001b2,0x000000a1,0x000001ae,
|
||||
0x0005008d,0x00000006,0x000001b4,0x000001ae,
|
||||
0x00000116,0x000500b7,0x00000196,0x000001b5,
|
||||
0x000001b4,0x0000004a,0x000600a9,0x00000006,
|
||||
0x000004fc,0x000001b5,0x0000004a,0x000001b2,
|
||||
0x0004003d,0x0000000a,0x000001bb,0x000001ba,
|
||||
0x00050041,0x000001bc,0x000001bd,0x00000187,
|
||||
0x0000006c,0x0004003d,0x00000037,0x000001be,
|
||||
0x000001bd,0x0007004f,0x0000000a,0x000001bf,
|
||||
0x000001be,0x000001be,0x00000000,0x00000001,
|
||||
0x00050085,0x0000000a,0x000001c0,0x000001bb,
|
||||
0x000001bf,0x00050050,0x0000000a,0x000001c3,
|
||||
0x0000004a,0x000004fc,0x00050083,0x0000000a,
|
||||
0x000001c4,0x000001c0,0x000001c3,0x0007004f,
|
||||
0x0000000a,0x000001c7,0x000001be,0x000001be,
|
||||
0x00000002,0x00000003,0x00050085,0x0000000a,
|
||||
0x000001c8,0x000001c4,0x000001c7,0x00050051,
|
||||
0x00000006,0x00000249,0x000001be,0x00000000,
|
||||
0x00050088,0x00000006,0x0000024a,0x00000049,
|
||||
0x00000249,0x00050050,0x0000000a,0x0000024b,
|
||||
0x0000024a,0x0000004a,0x00050051,0x00000006,
|
||||
0x0000024d,0x000001be,0x00000001,0x00050088,
|
||||
0x00000006,0x0000024e,0x00000049,0x0000024d,
|
||||
0x00050050,0x0000000a,0x0000024f,0x0000004a,
|
||||
0x0000024e,0x00050085,0x0000000a,0x00000252,
|
||||
0x000001c8,0x000001bf,0x0006000c,0x0000000a,
|
||||
0x00000254,0x00000001,0x00000008,0x00000252,
|
||||
0x00050081,0x0000000a,0x00000256,0x00000254,
|
||||
0x000004f3,0x00050088,0x0000000a,0x00000258,
|
||||
0x00000256,0x000001bf,0x0006000c,0x0000000a,
|
||||
0x0000025a,0x00000001,0x0000000a,0x00000252,
|
||||
0x00050083,0x0000000a,0x0000025c,0x0000025a,
|
||||
0x000004f3,0x0005008e,0x0000000a,0x0000025d,
|
||||
0x0000025c,0x00000068,0x00050083,0x0000000a,
|
||||
0x00000260,0x00000258,0x0000024f,0x0004003d,
|
||||
0x00000008,0x0000028a,0x000001ca,0x00050083,
|
||||
0x0000000a,0x0000028d,0x00000260,0x0000024b,
|
||||
0x00050057,0x00000037,0x0000028e,0x0000028a,
|
||||
0x0000028d,0x0008004f,0x0000000c,0x0000028f,
|
||||
0x0000028e,0x0000028e,0x00000000,0x00000001,
|
||||
0x00000002,0x0007000c,0x0000000c,0x00000290,
|
||||
0x00000001,0x0000001a,0x0000028f,0x0000003b,
|
||||
0x00050057,0x00000037,0x00000293,0x0000028a,
|
||||
0x00000260,0x0008004f,0x0000000c,0x00000294,
|
||||
0x00000293,0x00000293,0x00000000,0x00000001,
|
||||
0x00000002,0x0007000c,0x0000000c,0x00000295,
|
||||
0x00000001,0x0000001a,0x00000294,0x0000003b,
|
||||
0x00050081,0x0000000a,0x00000299,0x00000260,
|
||||
0x0000024b,0x00050057,0x00000037,0x0000029a,
|
||||
0x0000028a,0x00000299,0x0008004f,0x0000000c,
|
||||
0x0000029b,0x0000029a,0x0000029a,0x00000000,
|
||||
0x00000001,0x00000002,0x0007000c,0x0000000c,
|
||||
0x0000029c,0x00000001,0x0000001a,0x0000029b,
|
||||
0x0000003b,0x00050083,0x0000000a,0x000002af,
|
||||
0x00000258,0x0000024b,0x00050057,0x00000037,
|
||||
0x000002b0,0x0000028a,0x000002af,0x0008004f,
|
||||
0x0000000c,0x000002b1,0x000002b0,0x000002b0,
|
||||
0x00000000,0x00000001,0x00000002,0x0007000c,
|
||||
0x0000000c,0x000002b2,0x00000001,0x0000001a,
|
||||
0x000002b1,0x0000003b,0x00050057,0x00000037,
|
||||
0x000002b5,0x0000028a,0x00000258,0x0008004f,
|
||||
0x0000000c,0x000002b6,0x000002b5,0x000002b5,
|
||||
0x00000000,0x00000001,0x00000002,0x0007000c,
|
||||
0x0000000c,0x000002b7,0x00000001,0x0000001a,
|
||||
0x000002b6,0x0000003b,0x00050081,0x0000000a,
|
||||
0x000002bb,0x00000258,0x0000024b,0x00050057,
|
||||
0x00000037,0x000002bc,0x0000028a,0x000002bb,
|
||||
0x0008004f,0x0000000c,0x000002bd,0x000002bc,
|
||||
0x000002bc,0x00000000,0x00000001,0x00000002,
|
||||
0x0007000c,0x0000000c,0x000002be,0x00000001,
|
||||
0x0000001a,0x000002bd,0x0000003b,0x00050081,
|
||||
0x0000000a,0x00000268,0x00000258,0x0000024f,
|
||||
0x00050083,0x0000000a,0x000002d1,0x00000268,
|
||||
0x0000024b,0x00050057,0x00000037,0x000002d2,
|
||||
0x0000028a,0x000002d1,0x0008004f,0x0000000c,
|
||||
0x000002d3,0x000002d2,0x000002d2,0x00000000,
|
||||
0x00000001,0x00000002,0x0007000c,0x0000000c,
|
||||
0x000002d4,0x00000001,0x0000001a,0x000002d3,
|
||||
0x0000003b,0x00050057,0x00000037,0x000002d7,
|
||||
0x0000028a,0x00000268,0x0008004f,0x0000000c,
|
||||
0x000002d8,0x000002d7,0x000002d7,0x00000000,
|
||||
0x00000001,0x00000002,0x0007000c,0x0000000c,
|
||||
0x000002d9,0x00000001,0x0000001a,0x000002d8,
|
||||
0x0000003b,0x00050081,0x0000000a,0x000002dd,
|
||||
0x00000268,0x0000024b,0x00050057,0x00000037,
|
||||
0x000002de,0x0000028a,0x000002dd,0x0008004f,
|
||||
0x0000000c,0x000002df,0x000002de,0x000002de,
|
||||
0x00000000,0x00000001,0x00000002,0x0007000c,
|
||||
0x0000000c,0x000002e0,0x00000001,0x0000001a,
|
||||
0x000002df,0x0000003b,0x00050051,0x00000006,
|
||||
0x0000026d,0x0000025d,0x00000000,0x00050083,
|
||||
0x00000006,0x000002f3,0x0000026d,0x00000049,
|
||||
0x00050081,0x00000006,0x000002f6,0x0000026d,
|
||||
0x00000049,0x00060050,0x0000000c,0x000002f7,
|
||||
0x000002f3,0x0000026d,0x000002f6,0x00050085,
|
||||
0x0000000c,0x000002fa,0x000002f7,0x000004f6,
|
||||
0x00050085,0x0000000c,0x000002fd,0x000002fa,
|
||||
0x000002fa,0x0005008e,0x0000000c,0x000002fe,
|
||||
0x000002fd,0x00000068,0x0006000c,0x0000000c,
|
||||
0x000002ff,0x00000001,0x0000001d,0x000002fe,
|
||||
0x00050051,0x00000006,0x00000303,0x000002ff,
|
||||
0x00000000,0x0005008e,0x0000000c,0x00000304,
|
||||
0x00000290,0x00000303,0x00050051,0x00000006,
|
||||
0x00000308,0x000002ff,0x00000001,0x0005008e,
|
||||
0x0000000c,0x00000309,0x00000295,0x00000308,
|
||||
0x00050081,0x0000000c,0x0000030a,0x00000304,
|
||||
0x00000309,0x00050051,0x00000006,0x0000030e,
|
||||
0x000002ff,0x00000002,0x0005008e,0x0000000c,
|
||||
0x0000030f,0x0000029c,0x0000030e,0x00050081,
|
||||
0x0000000c,0x00000310,0x0000030a,0x0000030f,
|
||||
0x00050081,0x00000006,0x00000315,0x00000303,
|
||||
0x00000308,0x00050081,0x00000006,0x00000318,
|
||||
0x00000315,0x0000030e,0x00060050,0x0000000c,
|
||||
0x00000319,0x00000318,0x00000318,0x00000318,
|
||||
0x00050088,0x0000000c,0x0000031a,0x00000310,
|
||||
0x00000319,0x0005008e,0x0000000c,0x00000331,
|
||||
0x000002b2,0x00000303,0x0005008e,0x0000000c,
|
||||
0x00000336,0x000002b7,0x00000308,0x00050081,
|
||||
0x0000000c,0x00000337,0x00000331,0x00000336,
|
||||
0x0005008e,0x0000000c,0x0000033c,0x000002be,
|
||||
0x0000030e,0x00050081,0x0000000c,0x0000033d,
|
||||
0x00000337,0x0000033c,0x00050088,0x0000000c,
|
||||
0x00000347,0x0000033d,0x00000319,0x0005008e,
|
||||
0x0000000c,0x0000035e,0x000002d4,0x00000303,
|
||||
0x0005008e,0x0000000c,0x00000363,0x000002d9,
|
||||
0x00000308,0x00050081,0x0000000c,0x00000364,
|
||||
0x0000035e,0x00000363,0x0005008e,0x0000000c,
|
||||
0x00000369,0x000002e0,0x0000030e,0x00050081,
|
||||
0x0000000c,0x0000036a,0x00000364,0x00000369,
|
||||
0x00050088,0x0000000c,0x00000374,0x0000036a,
|
||||
0x00000319,0x00050051,0x00000006,0x00000286,
|
||||
0x0000025d,0x00000001,0x00050083,0x00000006,
|
||||
0x0000037a,0x00000286,0x00000049,0x00050081,
|
||||
0x00000006,0x0000037d,0x00000286,0x00000049,
|
||||
0x00060050,0x0000000c,0x0000037e,0x0000037a,
|
||||
0x00000286,0x0000037d,0x00050085,0x0000000c,
|
||||
0x00000381,0x0000037e,0x000004f6,0x00050085,
|
||||
0x0000000c,0x00000384,0x00000381,0x00000381,
|
||||
0x0005008e,0x0000000c,0x00000385,0x00000384,
|
||||
0x00000068,0x0006000c,0x0000000c,0x00000386,
|
||||
0x00000001,0x0000001d,0x00000385,0x00050051,
|
||||
0x00000006,0x0000038a,0x00000386,0x00000000,
|
||||
0x0005008e,0x0000000c,0x0000038b,0x0000031a,
|
||||
0x0000038a,0x00050051,0x00000006,0x0000038f,
|
||||
0x00000386,0x00000001,0x0005008e,0x0000000c,
|
||||
0x00000390,0x00000347,0x0000038f,0x00050081,
|
||||
0x0000000c,0x00000391,0x0000038b,0x00000390,
|
||||
0x00050051,0x00000006,0x00000395,0x00000386,
|
||||
0x00000002,0x0005008e,0x0000000c,0x00000396,
|
||||
0x00000374,0x00000395,0x00050081,0x0000000c,
|
||||
0x00000397,0x00000391,0x00000396,0x00050081,
|
||||
0x00000006,0x0000039c,0x0000038a,0x0000038f,
|
||||
0x00050081,0x00000006,0x0000039f,0x0000039c,
|
||||
0x00000395,0x00060050,0x0000000c,0x000003a0,
|
||||
0x0000039f,0x0000039f,0x0000039f,0x00050088,
|
||||
0x0000000c,0x000003a1,0x00000397,0x000003a0,
|
||||
0x00060052,0x0000000a,0x000004e1,0x00000249,
|
||||
0x000001bf,0x00000000,0x00050085,0x0000000a,
|
||||
0x000003b6,0x000001c8,0x000004e1,0x00050083,
|
||||
0x0000000a,0x000003b7,0x000003b6,0x000000fc,
|
||||
0x0006000c,0x0000000a,0x000003b9,0x00000001,
|
||||
0x00000008,0x000003b7,0x00050081,0x0000000a,
|
||||
0x000003ba,0x000003b9,0x00000102,0x00050088,
|
||||
0x0000000a,0x000003bc,0x000003ba,0x000004e1,
|
||||
0x0006000c,0x0000000a,0x000003be,0x00000001,
|
||||
0x0000000a,0x000003b7,0x00050051,0x00000006,
|
||||
0x000003c0,0x000003be,0x00000000,0x00050081,
|
||||
0x00000006,0x000003c1,0x000003c0,0x00000049,
|
||||
0x00050083,0x00000006,0x000003c6,0x000003c0,
|
||||
0x00000049,0x00050083,0x00000006,0x000003c9,
|
||||
0x000003c0,0x00000116,0x00070050,0x00000037,
|
||||
0x000003ca,0x000003c1,0x000003c0,0x000003c6,
|
||||
0x000003c9,0x0005008e,0x00000037,0x000003cb,
|
||||
0x000003ca,0x0000010b,0x0006000c,0x00000037,
|
||||
0x000003cd,0x00000001,0x00000004,0x000003cb,
|
||||
0x0007000c,0x00000037,0x000003cf,0x00000001,
|
||||
0x00000028,0x000003cd,0x000004f4,0x0006000c,
|
||||
0x00000037,0x000003d1,0x00000001,0x0000000d,
|
||||
0x000003cf,0x0005008e,0x00000037,0x000003d2,
|
||||
0x000003d1,0x00000116,0x00050085,0x00000037,
|
||||
0x000003d5,0x000003cf,0x000004fa,0x0006000c,
|
||||
0x00000037,0x000003d6,0x00000001,0x0000000d,
|
||||
0x000003d5,0x00050085,0x00000037,0x000003d7,
|
||||
0x000003d2,0x000003d6,0x00050085,0x00000037,
|
||||
0x000003da,0x000003cf,0x000003cf,0x00050088,
|
||||
0x00000037,0x000003db,0x000003d7,0x000003da,
|
||||
0x00050094,0x00000006,0x000003dd,0x000003db,
|
||||
0x0000012c,0x00070050,0x00000037,0x000003df,
|
||||
0x000003dd,0x000003dd,0x000003dd,0x000003dd,
|
||||
0x00050088,0x00000037,0x000003e0,0x000003db,
|
||||
0x000003df,0x00050057,0x00000037,0x000003e3,
|
||||
0x0000028a,0x000003bc,0x0008004f,0x0000000c,
|
||||
0x000003e4,0x000003e3,0x000003e3,0x00000000,
|
||||
0x00000001,0x00000002,0x0007000c,0x0000000c,
|
||||
0x000003e5,0x00000001,0x0000001a,0x000003e4,
|
||||
0x0000003b,0x00050051,0x00000006,0x000003e6,
|
||||
0x000003e5,0x00000000,0x00050051,0x00000006,
|
||||
0x000003e7,0x000003e5,0x00000001,0x00050051,
|
||||
0x00000006,0x000003e8,0x000003e5,0x00000002,
|
||||
0x00050081,0x0000000a,0x000003ed,0x000003bc,
|
||||
0x0000024b,0x00050057,0x00000037,0x000003ee,
|
||||
0x0000028a,0x000003ed,0x0008004f,0x0000000c,
|
||||
0x000003ef,0x000003ee,0x000003ee,0x00000000,
|
||||
0x00000001,0x00000002,0x0007000c,0x0000000c,
|
||||
0x000003f0,0x00000001,0x0000001a,0x000003ef,
|
||||
0x0000003b,0x00050051,0x00000006,0x000003f1,
|
||||
0x000003f0,0x00000000,0x00050051,0x00000006,
|
||||
0x000003f2,0x000003f0,0x00000001,0x00050051,
|
||||
0x00000006,0x000003f3,0x000003f0,0x00000002,
|
||||
0x00070050,0x00000037,0x00000407,0x000003e6,
|
||||
0x000003e7,0x000003e8,0x00000049,0x00070050,
|
||||
0x00000037,0x00000409,0x000003f1,0x000003f2,
|
||||
0x000003f3,0x00000049,0x00070050,0x00000149,
|
||||
0x0000040b,0x00000407,0x00000407,0x00000409,
|
||||
0x00000409,0x00050091,0x00000037,0x0000040d,
|
||||
0x0000040b,0x000003e0,0x0008004f,0x0000000c,
|
||||
0x0000040e,0x0000040d,0x0000040d,0x00000000,
|
||||
0x00000001,0x00000002,0x00050085,0x00000006,
|
||||
0x0000041b,0x00000249,0x000001dc,0x00060052,
|
||||
0x0000000a,0x000004e9,0x0000041b,0x000001bf,
|
||||
0x00000000,0x00050088,0x00000006,0x0000041f,
|
||||
0x000004fb,0x00000249,0x00050050,0x0000000a,
|
||||
0x00000420,0x0000041f,0x0000004a,0x00050085,
|
||||
0x0000000a,0x00000423,0x000001c8,0x000004e9,
|
||||
0x00050083,0x0000000a,0x00000424,0x00000423,
|
||||
0x000000fc,0x0006000c,0x0000000a,0x00000426,
|
||||
0x00000001,0x00000008,0x00000424,0x00050081,
|
||||
0x0000000a,0x00000427,0x00000426,0x00000102,
|
||||
0x00050088,0x0000000a,0x00000429,0x00000427,
|
||||
0x000004e9,0x0006000c,0x0000000a,0x0000042b,
|
||||
0x00000001,0x0000000a,0x00000424,0x00050051,
|
||||
0x00000006,0x0000042d,0x0000042b,0x00000000,
|
||||
0x00050081,0x00000006,0x0000042e,0x0000042d,
|
||||
0x00000049,0x00050083,0x00000006,0x00000433,
|
||||
0x0000042d,0x00000049,0x00050083,0x00000006,
|
||||
0x00000436,0x0000042d,0x00000116,0x00070050,
|
||||
0x00000037,0x00000437,0x0000042e,0x0000042d,
|
||||
0x00000433,0x00000436,0x0005008e,0x00000037,
|
||||
0x00000438,0x00000437,0x0000010b,0x0006000c,
|
||||
0x00000037,0x0000043a,0x00000001,0x00000004,
|
||||
0x00000438,0x0007000c,0x00000037,0x0000043c,
|
||||
0x00000001,0x00000028,0x0000043a,0x000004f4,
|
||||
0x0006000c,0x00000037,0x0000043e,0x00000001,
|
||||
0x0000000d,0x0000043c,0x0005008e,0x00000037,
|
||||
0x0000043f,0x0000043e,0x00000116,0x00050085,
|
||||
0x00000037,0x00000442,0x0000043c,0x000004fa,
|
||||
0x0006000c,0x00000037,0x00000443,0x00000001,
|
||||
0x0000000d,0x00000442,0x00050085,0x00000037,
|
||||
0x00000444,0x0000043f,0x00000443,0x00050085,
|
||||
0x00000037,0x00000447,0x0000043c,0x0000043c,
|
||||
0x00050088,0x00000037,0x00000448,0x00000444,
|
||||
0x00000447,0x00050094,0x00000006,0x0000044a,
|
||||
0x00000448,0x0000012c,0x00070050,0x00000037,
|
||||
0x0000044c,0x0000044a,0x0000044a,0x0000044a,
|
||||
0x0000044a,0x00050088,0x00000037,0x0000044d,
|
||||
0x00000448,0x0000044c,0x00050057,0x00000037,
|
||||
0x00000450,0x0000028a,0x00000429,0x0008004f,
|
||||
0x0000000c,0x00000451,0x00000450,0x00000450,
|
||||
0x00000000,0x00000001,0x00000002,0x0007000c,
|
||||
0x0000000c,0x00000452,0x00000001,0x0000001a,
|
||||
0x00000451,0x0000003b,0x00050051,0x00000006,
|
||||
0x00000453,0x00000452,0x00000000,0x00050051,
|
||||
0x00000006,0x00000454,0x00000452,0x00000001,
|
||||
0x00050051,0x00000006,0x00000455,0x00000452,
|
||||
0x00000002,0x00050081,0x0000000a,0x0000045a,
|
||||
0x00000429,0x00000420,0x00050057,0x00000037,
|
||||
0x0000045b,0x0000028a,0x0000045a,0x0008004f,
|
||||
0x0000000c,0x0000045c,0x0000045b,0x0000045b,
|
||||
0x00000000,0x00000001,0x00000002,0x0007000c,
|
||||
0x0000000c,0x0000045d,0x00000001,0x0000001a,
|
||||
0x0000045c,0x0000003b,0x00050051,0x00000006,
|
||||
0x0000045e,0x0000045d,0x00000000,0x00050051,
|
||||
0x00000006,0x0000045f,0x0000045d,0x00000001,
|
||||
0x00050051,0x00000006,0x00000460,0x0000045d,
|
||||
0x00000002,0x00070050,0x00000037,0x00000474,
|
||||
0x00000453,0x00000454,0x00000455,0x00000049,
|
||||
0x00070050,0x00000037,0x00000476,0x0000045e,
|
||||
0x0000045f,0x00000460,0x00000049,0x00070050,
|
||||
0x00000149,0x00000478,0x00000474,0x00000474,
|
||||
0x00000476,0x00000476,0x00050091,0x00000037,
|
||||
0x0000047a,0x00000478,0x0000044d,0x0008004f,
|
||||
0x0000000c,0x0000047b,0x0000047a,0x0000047a,
|
||||
0x00000000,0x00000001,0x00000002,0x00050085,
|
||||
0x0000000c,0x000001e8,0x0000047b,0x0000040e,
|
||||
0x0006000c,0x0000000c,0x000001e9,0x00000001,
|
||||
0x0000001f,0x000001e8,0x00050051,0x00000006,
|
||||
0x000001eb,0x000001c8,0x00000001,0x00060041,
|
||||
0x00000188,0x000001ec,0x00000187,0x0000006c,
|
||||
0x00000077,0x0004003d,0x00000006,0x000001ed,
|
||||
0x000001ec,0x00050085,0x00000006,0x000001ee,
|
||||
0x000001eb,0x000001ed,0x0006000c,0x00000006,
|
||||
0x000001ef,0x00000001,0x0000000a,0x000001ee,
|
||||
0x0007000c,0x0000000c,0x00000482,0x00000001,
|
||||
0x0000001a,0x0000040e,0x0000016a,0x0008000c,
|
||||
0x0000000c,0x00000483,0x00000001,0x0000002e,
|
||||
0x00000165,0x00000167,0x00000482,0x00050088,
|
||||
0x0000000c,0x00000486,0x000004f6,0x00000483,
|
||||
0x0005008e,0x0000000c,0x00000488,0x00000486,
|
||||
0x000000a1,0x0005008e,0x0000000c,0x0000048b,
|
||||
0x00000486,0x000001ef,0x00050083,0x0000000c,
|
||||
0x0000048d,0x0000048b,0x00000488,0x0006000c,
|
||||
0x0000000c,0x0000048e,0x00000001,0x00000004,
|
||||
0x0000048d,0x00050083,0x0000000c,0x00000490,
|
||||
0x000004f7,0x0000048e,0x0008000c,0x0000000c,
|
||||
0x00000493,0x00000001,0x00000031,0x000004f8,
|
||||
0x000004f7,0x00000490,0x00050085,0x0000000c,
|
||||
0x00000495,0x00000493,0x00000488,0x00050085,
|
||||
0x0000000c,0x000001f5,0x000001e9,0x00000495,
|
||||
0x00050083,0x0000000c,0x000001f8,0x000003a1,
|
||||
0x000001f5,0x0008000c,0x0000000c,0x000001fb,
|
||||
0x00000001,0x0000002b,0x000001f8,0x000004f8,
|
||||
0x000004f7,0x00050085,0x0000000c,0x000001fe,
|
||||
0x000001fb,0x000001fb,0x0005008e,0x0000000c,
|
||||
0x00000200,0x000001fe,0x000001ff,0x00050081,
|
||||
0x0000000c,0x00000202,0x000001f5,0x00000200,
|
||||
0x00050041,0x00000206,0x00000207,0x000001ba,
|
||||
0x00000070,0x0004003d,0x00000006,0x00000208,
|
||||
0x00000207,0x000300f7,0x000004b3,0x00000000,
|
||||
0x000300fb,0x00000070,0x0000049b,0x000200f8,
|
||||
0x0000049b,0x00060041,0x00000188,0x0000049d,
|
||||
0x00000187,0x00000074,0x00000070,0x0004003d,
|
||||
0x00000006,0x0000049e,0x0000049d,0x00050085,
|
||||
0x00000006,0x0000049f,0x00000208,0x0000049e,
|
||||
0x0006000c,0x00000006,0x000004a7,0x00000001,
|
||||
0x00000008,0x0000049f,0x0005008d,0x00000006,
|
||||
0x000004a8,0x000004a7,0x00000116,0x000500b4,
|
||||
0x00000196,0x000004aa,0x000004a8,0x0000004a,
|
||||
0x000300f7,0x000004b2,0x00000000,0x000400fa,
|
||||
0x000004aa,0x000004ab,0x000004ac,0x000200f8,
|
||||
0x000004ac,0x000500b4,0x00000196,0x000004ae,
|
||||
0x000004a8,0x00000049,0x000300f7,0x000004b1,
|
||||
0x00000000,0x000400fa,0x000004ae,0x000004af,
|
||||
0x000004b0,0x000200f8,0x000004b0,0x000200f9,
|
||||
0x000004b3,0x000200f8,0x000004af,0x000200f9,
|
||||
0x000004b3,0x000200f8,0x000004b1,0x000100ff,
|
||||
0x000200f8,0x000004ab,0x000200f9,0x000004b3,
|
||||
0x000200f8,0x000004b2,0x000100ff,0x000200f8,
|
||||
0x000004b3,0x000900f5,0x0000000c,0x000004f1,
|
||||
0x0000019a,0x000004ab,0x000001a1,0x000004af,
|
||||
0x000001a4,0x000004b0,0x00050085,0x0000000c,
|
||||
0x0000020a,0x00000202,0x000004f1,0x0005008e,
|
||||
0x0000000c,0x0000020b,0x0000020a,0x00000116,
|
||||
0x0008000c,0x0000000c,0x0000020e,0x00000001,
|
||||
0x0000002e,0x00000202,0x0000020b,0x000004f9,
|
||||
0x0005008e,0x0000000c,0x00000211,0x000001fb,
|
||||
0x00000210,0x00050081,0x0000000c,0x00000213,
|
||||
0x0000020e,0x00000211,0x0005008e,0x0000000c,
|
||||
0x00000215,0x00000213,0x00000166,0x0007000c,
|
||||
0x0000000c,0x00000218,0x00000001,0x0000001a,
|
||||
0x00000215,0x00000217,0x00050051,0x00000006,
|
||||
0x0000021c,0x00000218,0x00000000,0x00050051,
|
||||
0x00000006,0x0000021d,0x00000218,0x00000001,
|
||||
0x00050051,0x00000006,0x0000021e,0x00000218,
|
||||
0x00000002,0x00070050,0x00000037,0x0000021f,
|
||||
0x0000021c,0x0000021d,0x0000021e,0x00000049,
|
||||
0x0003003e,0x0000021a,0x0000021f,0x000100fd,
|
||||
0x00010038}
|
||||
@@ -0,0 +1,10 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) out vec4 FragColor;
|
||||
layout(set = 0, binding = 0) uniform sampler2D uTexture;
|
||||
layout(location = 0) in vec2 vUV;
|
||||
|
||||
void main()
|
||||
{
|
||||
FragColor = textureLod(uTexture, vUV, 0.0);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) out vec2 vUV;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (gl_VertexIndex == 0)
|
||||
{
|
||||
gl_Position = vec4(-1.0, -1.0, 0.0, 1.0);
|
||||
}
|
||||
else if (gl_VertexIndex == 1)
|
||||
{
|
||||
gl_Position = vec4(-1.0, +3.0, 0.0, 1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
gl_Position = vec4(+3.0, -1.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
vUV = gl_Position.xy * 0.5 + 0.5;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{0x07230203,0x00010300,0x000d000b,0x00000015,
|
||||
0x00000000,0x00020011,0x00000001,0x0006000b,
|
||||
0x00000001,0x4c534c47,0x6474732e,0x3035342e,
|
||||
0x00000000,0x0003000e,0x00000000,0x00000001,
|
||||
0x0007000f,0x00000004,0x00000004,0x6e69616d,
|
||||
0x00000000,0x00000009,0x00000011,0x00030010,
|
||||
0x00000004,0x00000007,0x00040047,0x00000009,
|
||||
0x0000001e,0x00000000,0x00040047,0x0000000d,
|
||||
0x00000021,0x00000000,0x00040047,0x0000000d,
|
||||
0x00000022,0x00000000,0x00040047,0x00000011,
|
||||
0x0000001e,0x00000000,0x00020013,0x00000002,
|
||||
0x00030021,0x00000003,0x00000002,0x00030016,
|
||||
0x00000006,0x00000020,0x00040017,0x00000007,
|
||||
0x00000006,0x00000004,0x00040020,0x00000008,
|
||||
0x00000003,0x00000007,0x0004003b,0x00000008,
|
||||
0x00000009,0x00000003,0x00090019,0x0000000a,
|
||||
0x00000006,0x00000001,0x00000000,0x00000000,
|
||||
0x00000000,0x00000001,0x00000000,0x0003001b,
|
||||
0x0000000b,0x0000000a,0x00040020,0x0000000c,
|
||||
0x00000000,0x0000000b,0x0004003b,0x0000000c,
|
||||
0x0000000d,0x00000000,0x00040017,0x0000000f,
|
||||
0x00000006,0x00000002,0x00040020,0x00000010,
|
||||
0x00000001,0x0000000f,0x0004003b,0x00000010,
|
||||
0x00000011,0x00000001,0x0004002b,0x00000006,
|
||||
0x00000013,0x00000000,0x00050036,0x00000002,
|
||||
0x00000004,0x00000000,0x00000003,0x000200f8,
|
||||
0x00000005,0x0004003d,0x0000000b,0x0000000e,
|
||||
0x0000000d,0x0004003d,0x0000000f,0x00000012,
|
||||
0x00000011,0x00070058,0x00000007,0x00000014,
|
||||
0x0000000e,0x00000012,0x00000002,0x00000013,
|
||||
0x0003003e,0x00000009,0x00000014,0x000100fd,
|
||||
0x00010038}
|
||||
@@ -0,0 +1,72 @@
|
||||
{0x07230203,0x00010300,0x000d000b,0x00000034,
|
||||
0x00000000,0x00020011,0x00000001,0x0006000b,
|
||||
0x00000001,0x4c534c47,0x6474732e,0x3035342e,
|
||||
0x00000000,0x0003000e,0x00000000,0x00000001,
|
||||
0x0008000f,0x00000000,0x00000004,0x6e69616d,
|
||||
0x00000000,0x00000008,0x00000016,0x0000002b,
|
||||
0x00040047,0x00000008,0x0000000b,0x0000002a,
|
||||
0x00030047,0x00000014,0x00000002,0x00050048,
|
||||
0x00000014,0x00000000,0x0000000b,0x00000000,
|
||||
0x00050048,0x00000014,0x00000001,0x0000000b,
|
||||
0x00000001,0x00050048,0x00000014,0x00000002,
|
||||
0x0000000b,0x00000003,0x00050048,0x00000014,
|
||||
0x00000003,0x0000000b,0x00000004,0x00040047,
|
||||
0x0000002b,0x0000001e,0x00000000,0x00020013,
|
||||
0x00000002,0x00030021,0x00000003,0x00000002,
|
||||
0x00040015,0x00000006,0x00000020,0x00000001,
|
||||
0x00040020,0x00000007,0x00000001,0x00000006,
|
||||
0x0004003b,0x00000007,0x00000008,0x00000001,
|
||||
0x0004002b,0x00000006,0x0000000a,0x00000000,
|
||||
0x00020014,0x0000000b,0x00030016,0x0000000f,
|
||||
0x00000020,0x00040017,0x00000010,0x0000000f,
|
||||
0x00000004,0x00040015,0x00000011,0x00000020,
|
||||
0x00000000,0x0004002b,0x00000011,0x00000012,
|
||||
0x00000001,0x0004001c,0x00000013,0x0000000f,
|
||||
0x00000012,0x0006001e,0x00000014,0x00000010,
|
||||
0x0000000f,0x00000013,0x00000013,0x00040020,
|
||||
0x00000015,0x00000003,0x00000014,0x0004003b,
|
||||
0x00000015,0x00000016,0x00000003,0x0004002b,
|
||||
0x0000000f,0x00000017,0xbf800000,0x0004002b,
|
||||
0x0000000f,0x00000018,0x00000000,0x0004002b,
|
||||
0x0000000f,0x00000019,0x3f800000,0x0007002c,
|
||||
0x00000010,0x0000001a,0x00000017,0x00000017,
|
||||
0x00000018,0x00000019,0x00040020,0x0000001b,
|
||||
0x00000003,0x00000010,0x0004002b,0x00000006,
|
||||
0x0000001f,0x00000001,0x0004002b,0x0000000f,
|
||||
0x00000023,0x40400000,0x0007002c,0x00000010,
|
||||
0x00000024,0x00000017,0x00000023,0x00000018,
|
||||
0x00000019,0x0007002c,0x00000010,0x00000027,
|
||||
0x00000023,0x00000017,0x00000018,0x00000019,
|
||||
0x00040017,0x00000029,0x0000000f,0x00000002,
|
||||
0x00040020,0x0000002a,0x00000003,0x00000029,
|
||||
0x0004003b,0x0000002a,0x0000002b,0x00000003,
|
||||
0x0004002b,0x0000000f,0x0000002f,0x3f000000,
|
||||
0x0005002c,0x00000029,0x00000033,0x0000002f,
|
||||
0x0000002f,0x00050036,0x00000002,0x00000004,
|
||||
0x00000000,0x00000003,0x000200f8,0x00000005,
|
||||
0x0004003d,0x00000006,0x00000009,0x00000008,
|
||||
0x000500aa,0x0000000b,0x0000000c,0x00000009,
|
||||
0x0000000a,0x000300f7,0x0000000e,0x00000000,
|
||||
0x000400fa,0x0000000c,0x0000000d,0x0000001d,
|
||||
0x000200f8,0x0000000d,0x00050041,0x0000001b,
|
||||
0x0000001c,0x00000016,0x0000000a,0x0003003e,
|
||||
0x0000001c,0x0000001a,0x000200f9,0x0000000e,
|
||||
0x000200f8,0x0000001d,0x000500aa,0x0000000b,
|
||||
0x00000020,0x00000009,0x0000001f,0x000300f7,
|
||||
0x00000022,0x00000000,0x000400fa,0x00000020,
|
||||
0x00000021,0x00000026,0x000200f8,0x00000021,
|
||||
0x00050041,0x0000001b,0x00000025,0x00000016,
|
||||
0x0000000a,0x0003003e,0x00000025,0x00000024,
|
||||
0x000200f9,0x00000022,0x000200f8,0x00000026,
|
||||
0x00050041,0x0000001b,0x00000028,0x00000016,
|
||||
0x0000000a,0x0003003e,0x00000028,0x00000027,
|
||||
0x000200f9,0x00000022,0x000200f8,0x00000022,
|
||||
0x000200f9,0x0000000e,0x000200f8,0x0000000e,
|
||||
0x00050041,0x0000001b,0x0000002c,0x00000016,
|
||||
0x0000000a,0x0004003d,0x00000010,0x0000002d,
|
||||
0x0000002c,0x0007004f,0x00000029,0x0000002e,
|
||||
0x0000002d,0x0000002d,0x00000000,0x00000001,
|
||||
0x0005008e,0x00000029,0x00000030,0x0000002e,
|
||||
0x0000002f,0x00050081,0x00000029,0x00000032,
|
||||
0x00000030,0x00000033,0x0003003e,0x0000002b,
|
||||
0x00000032,0x000100fd,0x00010038}
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
glslc -O crt-aperture.frag -o crt_frag.spv --target-env=vulkan1.1 -mfmt=c
|
||||
glslc -O main.frag -o main_frag.spv --target-env=vulkan1.1 -mfmt=c
|
||||
glslc -O main.vert -o main_vert.spv --target-env=vulkan1.1 -mfmt=c
|
||||
@@ -2,32 +2,71 @@
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <cstdint>
|
||||
#include <stddef.h>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t *RDRAM;
|
||||
uint8_t *DMEM;
|
||||
uint32_t RDRAM_SIZE;
|
||||
uint32_t *DPC_CURRENT_REG;
|
||||
uint32_t *DPC_START_REG;
|
||||
uint32_t *DPC_END_REG;
|
||||
uint32_t *DPC_STATUS_REG;
|
||||
uint32_t *VI_H_START_REG;
|
||||
uint32_t *VI_V_START_REG;
|
||||
uint32_t *VI_X_SCALE_REG;
|
||||
uint32_t *VI_Y_SCALE_REG;
|
||||
uint32_t *VI_WIDTH_REG;
|
||||
} GFX_INFO;
|
||||
typedef struct {
|
||||
uint8_t *RDRAM;
|
||||
uint8_t *DMEM;
|
||||
uint32_t RDRAM_SIZE;
|
||||
uint32_t *DPC_CURRENT_REG;
|
||||
uint32_t *DPC_START_REG;
|
||||
uint32_t *DPC_END_REG;
|
||||
uint32_t *DPC_STATUS_REG;
|
||||
bool PAL;
|
||||
bool widescreen;
|
||||
bool fullscreen;
|
||||
bool vsync;
|
||||
bool integer_scaling;
|
||||
uint32_t upscale;
|
||||
bool crt;
|
||||
} GFX_INFO;
|
||||
|
||||
void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_scaling, bool _fullscreen);
|
||||
void rdp_close();
|
||||
void rdp_set_vi_register(uint32_t reg, uint32_t value);
|
||||
bool rdp_update_screen();
|
||||
uint64_t rdp_process_commands();
|
||||
typedef struct {
|
||||
bool emu_running;
|
||||
bool save_state;
|
||||
bool load_state;
|
||||
bool reset_game;
|
||||
bool enable_speedlimiter;
|
||||
bool lower_volume;
|
||||
bool raise_volume;
|
||||
bool paused;
|
||||
bool frame_advance;
|
||||
uint32_t save_state_slot;
|
||||
} CALL_BACK;
|
||||
|
||||
typedef enum {
|
||||
MESSAGE_VERY_SHORT = 500,
|
||||
MESSAGE_SHORT = 3000,
|
||||
MESSAGE_LONG = 6000,
|
||||
} MESSAGE_LENGTH;
|
||||
|
||||
void rdp_init(void *_window, GFX_INFO _gfx_info, const void *font,
|
||||
size_t font_size, uint32_t save_state_slot);
|
||||
void rdp_close();
|
||||
void rdp_set_vi_register(uint32_t reg, uint32_t value);
|
||||
void rdp_update_screen();
|
||||
void rdp_render_frame();
|
||||
CALL_BACK rdp_check_callback();
|
||||
uint64_t rdp_process_commands();
|
||||
void rdp_onscreen_message(const char *message, MESSAGE_LENGTH milliseconds);
|
||||
void rdp_new_processor(GFX_INFO _gfx_info);
|
||||
void rdp_check_framebuffers(uint32_t address, uint32_t length);
|
||||
size_t rdp_state_size();
|
||||
void rdp_save_state(uint8_t *state);
|
||||
void rdp_load_state(const uint8_t *state);
|
||||
void rdp_set_fps(uint32_t fps, uint32_t vis);
|
||||
|
||||
void achievement_challenge_indicator_add(const char *achievement_title);
|
||||
void achievement_challenge_indicator_remove(const char *achievement_title);
|
||||
void achievement_progress_add(const char *achievement_title,
|
||||
const char *progress);
|
||||
void achievement_progress_remove();
|
||||
void leaderboard_tracker_add(uint32_t id, const char *title,
|
||||
const char *display);
|
||||
void leaderboard_tracker_remove(uint32_t id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
static const uint32_t vertex_spirv[] =
|
||||
{0x07230203, 0x00010000, 0x000d000a, 0x00000034,
|
||||
{0x07230203, 0x00010300, 0x000d000b, 0x00000034,
|
||||
0x00000000, 0x00020011, 0x00000001, 0x0006000b,
|
||||
0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e,
|
||||
0x00000000, 0x0003000e, 0x00000000, 0x00000001,
|
||||
0x0008000f, 0x00000000, 0x00000004, 0x6e69616d,
|
||||
0x00000000, 0x00000008, 0x00000016, 0x0000002b,
|
||||
0x00040047, 0x00000008, 0x0000000b, 0x0000002a,
|
||||
0x00050048, 0x00000014, 0x00000000, 0x0000000b,
|
||||
0x00000000, 0x00050048, 0x00000014, 0x00000001,
|
||||
0x0000000b, 0x00000001, 0x00050048, 0x00000014,
|
||||
0x00000002, 0x0000000b, 0x00000003, 0x00050048,
|
||||
0x00000014, 0x00000003, 0x0000000b, 0x00000004,
|
||||
0x00030047, 0x00000014, 0x00000002, 0x00040047,
|
||||
0x00030047, 0x00000014, 0x00000002, 0x00050048,
|
||||
0x00000014, 0x00000000, 0x0000000b, 0x00000000,
|
||||
0x00050048, 0x00000014, 0x00000001, 0x0000000b,
|
||||
0x00000001, 0x00050048, 0x00000014, 0x00000002,
|
||||
0x0000000b, 0x00000003, 0x00050048, 0x00000014,
|
||||
0x00000003, 0x0000000b, 0x00000004, 0x00040047,
|
||||
0x0000002b, 0x0000001e, 0x00000000, 0x00020013,
|
||||
0x00000002, 0x00030021, 0x00000003, 0x00000002,
|
||||
0x00040015, 0x00000006, 0x00000020, 0x00000001,
|
||||
@@ -74,8 +74,8 @@ static const uint32_t vertex_spirv[] =
|
||||
0x00000030, 0x00000033, 0x0003003e, 0x0000002b,
|
||||
0x00000032, 0x000100fd, 0x00010038};
|
||||
|
||||
static const uint32_t fragment_spirv[] =
|
||||
{0x07230203, 0x00010000, 0x000d000a, 0x00000015,
|
||||
static const uint32_t plain_fragment_spirv[] =
|
||||
{0x07230203, 0x00010300, 0x000d000b, 0x00000015,
|
||||
0x00000000, 0x00020011, 0x00000001, 0x0006000b,
|
||||
0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e,
|
||||
0x00000000, 0x0003000e, 0x00000000, 0x00000001,
|
||||
@@ -83,8 +83,8 @@ static const uint32_t fragment_spirv[] =
|
||||
0x00000000, 0x00000009, 0x00000011, 0x00030010,
|
||||
0x00000004, 0x00000007, 0x00040047, 0x00000009,
|
||||
0x0000001e, 0x00000000, 0x00040047, 0x0000000d,
|
||||
0x00000022, 0x00000000, 0x00040047, 0x0000000d,
|
||||
0x00000021, 0x00000000, 0x00040047, 0x00000011,
|
||||
0x00000021, 0x00000000, 0x00040047, 0x0000000d,
|
||||
0x00000022, 0x00000000, 0x00040047, 0x00000011,
|
||||
0x0000001e, 0x00000000, 0x00020013, 0x00000002,
|
||||
0x00030021, 0x00000003, 0x00000002, 0x00030016,
|
||||
0x00000006, 0x00000020, 0x00040017, 0x00000007,
|
||||
|
||||
@@ -0,0 +1,456 @@
|
||||
#pragma once
|
||||
|
||||
static const uint32_t crt_fragment_spirv[] =
|
||||
{0x07230203, 0x00010300, 0x000d000b, 0x000004fd,
|
||||
0x00000000, 0x00020011, 0x00000001, 0x0006000b,
|
||||
0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e,
|
||||
0x00000000, 0x0003000e, 0x00000000, 0x00000001,
|
||||
0x0007000f, 0x00000004, 0x00000004, 0x6e69616d,
|
||||
0x00000000, 0x000001ba, 0x0000021a, 0x00030010,
|
||||
0x00000004, 0x00000007, 0x00030047, 0x00000185,
|
||||
0x00000002, 0x00050048, 0x00000185, 0x00000000,
|
||||
0x00000023, 0x00000000, 0x00050048, 0x00000185,
|
||||
0x00000001, 0x00000023, 0x00000010, 0x00040047,
|
||||
0x000001ba, 0x0000001e, 0x00000000, 0x00040047,
|
||||
0x000001ca, 0x00000021, 0x00000000, 0x00040047,
|
||||
0x000001ca, 0x00000022, 0x00000000, 0x00040047,
|
||||
0x0000021a, 0x0000001e, 0x00000000, 0x00020013,
|
||||
0x00000002, 0x00030021, 0x00000003, 0x00000002,
|
||||
0x00030016, 0x00000006, 0x00000020, 0x00090019,
|
||||
0x00000007, 0x00000006, 0x00000001, 0x00000000,
|
||||
0x00000000, 0x00000000, 0x00000001, 0x00000000,
|
||||
0x0003001b, 0x00000008, 0x00000007, 0x00040020,
|
||||
0x00000009, 0x00000000, 0x00000008, 0x00040017,
|
||||
0x0000000a, 0x00000006, 0x00000002, 0x00040017,
|
||||
0x0000000c, 0x00000006, 0x00000003, 0x00040017,
|
||||
0x00000037, 0x00000006, 0x00000004, 0x0004002b,
|
||||
0x00000006, 0x0000003a, 0x4019999a, 0x0006002c,
|
||||
0x0000000c, 0x0000003b, 0x0000003a, 0x0000003a,
|
||||
0x0000003a, 0x0004002b, 0x00000006, 0x00000049,
|
||||
0x3f800000, 0x0004002b, 0x00000006, 0x0000004a,
|
||||
0x00000000, 0x0004002b, 0x00000006, 0x00000068,
|
||||
0xbf800000, 0x00040015, 0x0000006b, 0x00000020,
|
||||
0x00000001, 0x0004002b, 0x0000006b, 0x0000006c,
|
||||
0x00000000, 0x00040015, 0x0000006f, 0x00000020,
|
||||
0x00000000, 0x0004002b, 0x0000006f, 0x00000070,
|
||||
0x00000000, 0x0004002b, 0x0000006b, 0x00000074,
|
||||
0x00000001, 0x0004002b, 0x0000006f, 0x00000077,
|
||||
0x00000001, 0x0004002b, 0x00000006, 0x000000a1,
|
||||
0x3f000000, 0x0005002c, 0x0000000a, 0x000000fc,
|
||||
0x000000a1, 0x0000004a, 0x0004002b, 0x00000006,
|
||||
0x00000101, 0x3a83126f, 0x0005002c, 0x0000000a,
|
||||
0x00000102, 0x000000a1, 0x00000101, 0x0004002b,
|
||||
0x00000006, 0x0000010b, 0x40490fdb, 0x0004002b,
|
||||
0x00000006, 0x00000116, 0x40000000, 0x0004002b,
|
||||
0x00000006, 0x0000011c, 0x3727c5ac, 0x0007002c,
|
||||
0x00000037, 0x0000012c, 0x00000049, 0x00000049,
|
||||
0x00000049, 0x00000049, 0x00040018, 0x00000149,
|
||||
0x00000037, 0x00000004, 0x0006002c, 0x0000000c,
|
||||
0x00000165, 0x000000a1, 0x000000a1, 0x000000a1,
|
||||
0x0004002b, 0x00000006, 0x00000166, 0x3fc00000,
|
||||
0x0006002c, 0x0000000c, 0x00000167, 0x00000166,
|
||||
0x00000166, 0x00000166, 0x0004002b, 0x00000006,
|
||||
0x00000169, 0x3ecccccd, 0x0006002c, 0x0000000c,
|
||||
0x0000016a, 0x00000169, 0x00000169, 0x00000169,
|
||||
0x0004001e, 0x00000185, 0x00000037, 0x00000037,
|
||||
0x00040020, 0x00000186, 0x00000009, 0x00000185,
|
||||
0x0004003b, 0x00000186, 0x00000187, 0x00000009,
|
||||
0x00040020, 0x00000188, 0x00000009, 0x00000006,
|
||||
0x00020014, 0x00000196, 0x0006002c, 0x0000000c,
|
||||
0x0000019a, 0x00000049, 0x0000004a, 0x00000049,
|
||||
0x0006002c, 0x0000000c, 0x000001a1, 0x0000004a,
|
||||
0x00000049, 0x0000004a, 0x0006002c, 0x0000000c,
|
||||
0x000001a4, 0x0000004a, 0x0000004a, 0x00000049,
|
||||
0x0004002b, 0x0000006f, 0x000001aa, 0x00000003,
|
||||
0x00040020, 0x000001b9, 0x00000001, 0x0000000a,
|
||||
0x0004003b, 0x000001b9, 0x000001ba, 0x00000001,
|
||||
0x00040020, 0x000001bc, 0x00000009, 0x00000037,
|
||||
0x0004003b, 0x00000009, 0x000001ca, 0x00000000,
|
||||
0x0004002b, 0x00000006, 0x000001dc, 0x40400000,
|
||||
0x0004002b, 0x00000006, 0x000001ff, 0x3dcccccd,
|
||||
0x00040020, 0x00000206, 0x00000001, 0x00000006,
|
||||
0x0004002b, 0x00000006, 0x0000020c, 0x3e99999a,
|
||||
0x0004002b, 0x00000006, 0x00000210, 0x3d4ccccd,
|
||||
0x0004002b, 0x00000006, 0x00000216, 0x3ed55555,
|
||||
0x0006002c, 0x0000000c, 0x00000217, 0x00000216,
|
||||
0x00000216, 0x00000216, 0x00040020, 0x00000219,
|
||||
0x00000003, 0x00000037, 0x0004003b, 0x00000219,
|
||||
0x0000021a, 0x00000003, 0x0005002c, 0x0000000a,
|
||||
0x000004f3, 0x000000a1, 0x000000a1, 0x0007002c,
|
||||
0x00000037, 0x000004f4, 0x0000011c, 0x0000011c,
|
||||
0x0000011c, 0x0000011c, 0x0006002c, 0x0000000c,
|
||||
0x000004f6, 0x00000116, 0x00000116, 0x00000116,
|
||||
0x0006002c, 0x0000000c, 0x000004f7, 0x00000049,
|
||||
0x00000049, 0x00000049, 0x0006002c, 0x0000000c,
|
||||
0x000004f8, 0x0000004a, 0x0000004a, 0x0000004a,
|
||||
0x0006002c, 0x0000000c, 0x000004f9, 0x0000020c,
|
||||
0x0000020c, 0x0000020c, 0x0007002c, 0x00000037,
|
||||
0x000004fa, 0x000000a1, 0x000000a1, 0x000000a1,
|
||||
0x000000a1, 0x0004002b, 0x00000006, 0x000004fb,
|
||||
0x3eaaaaab, 0x00050036, 0x00000002, 0x00000004,
|
||||
0x00000000, 0x00000003, 0x000200f8, 0x00000005,
|
||||
0x00060041, 0x00000188, 0x000001a8, 0x00000187,
|
||||
0x00000074, 0x00000077, 0x0004003d, 0x00000006,
|
||||
0x000001a9, 0x000001a8, 0x00060041, 0x00000188,
|
||||
0x000001ab, 0x00000187, 0x0000006c, 0x000001aa,
|
||||
0x0004003d, 0x00000006, 0x000001ac, 0x000001ab,
|
||||
0x00050085, 0x00000006, 0x000001ad, 0x000001a9,
|
||||
0x000001ac, 0x0006000c, 0x00000006, 0x000001ae,
|
||||
0x00000001, 0x00000008, 0x000001ad, 0x00050088,
|
||||
0x00000006, 0x000001b2, 0x000000a1, 0x000001ae,
|
||||
0x0005008d, 0x00000006, 0x000001b4, 0x000001ae,
|
||||
0x00000116, 0x000500b7, 0x00000196, 0x000001b5,
|
||||
0x000001b4, 0x0000004a, 0x000600a9, 0x00000006,
|
||||
0x000004fc, 0x000001b5, 0x0000004a, 0x000001b2,
|
||||
0x0004003d, 0x0000000a, 0x000001bb, 0x000001ba,
|
||||
0x00050041, 0x000001bc, 0x000001bd, 0x00000187,
|
||||
0x0000006c, 0x0004003d, 0x00000037, 0x000001be,
|
||||
0x000001bd, 0x0007004f, 0x0000000a, 0x000001bf,
|
||||
0x000001be, 0x000001be, 0x00000000, 0x00000001,
|
||||
0x00050085, 0x0000000a, 0x000001c0, 0x000001bb,
|
||||
0x000001bf, 0x00050050, 0x0000000a, 0x000001c3,
|
||||
0x0000004a, 0x000004fc, 0x00050083, 0x0000000a,
|
||||
0x000001c4, 0x000001c0, 0x000001c3, 0x0007004f,
|
||||
0x0000000a, 0x000001c7, 0x000001be, 0x000001be,
|
||||
0x00000002, 0x00000003, 0x00050085, 0x0000000a,
|
||||
0x000001c8, 0x000001c4, 0x000001c7, 0x00050051,
|
||||
0x00000006, 0x00000249, 0x000001be, 0x00000000,
|
||||
0x00050088, 0x00000006, 0x0000024a, 0x00000049,
|
||||
0x00000249, 0x00050050, 0x0000000a, 0x0000024b,
|
||||
0x0000024a, 0x0000004a, 0x00050051, 0x00000006,
|
||||
0x0000024d, 0x000001be, 0x00000001, 0x00050088,
|
||||
0x00000006, 0x0000024e, 0x00000049, 0x0000024d,
|
||||
0x00050050, 0x0000000a, 0x0000024f, 0x0000004a,
|
||||
0x0000024e, 0x00050085, 0x0000000a, 0x00000252,
|
||||
0x000001c8, 0x000001bf, 0x0006000c, 0x0000000a,
|
||||
0x00000254, 0x00000001, 0x00000008, 0x00000252,
|
||||
0x00050081, 0x0000000a, 0x00000256, 0x00000254,
|
||||
0x000004f3, 0x00050088, 0x0000000a, 0x00000258,
|
||||
0x00000256, 0x000001bf, 0x0006000c, 0x0000000a,
|
||||
0x0000025a, 0x00000001, 0x0000000a, 0x00000252,
|
||||
0x00050083, 0x0000000a, 0x0000025c, 0x0000025a,
|
||||
0x000004f3, 0x0005008e, 0x0000000a, 0x0000025d,
|
||||
0x0000025c, 0x00000068, 0x00050083, 0x0000000a,
|
||||
0x00000260, 0x00000258, 0x0000024f, 0x0004003d,
|
||||
0x00000008, 0x0000028a, 0x000001ca, 0x00050083,
|
||||
0x0000000a, 0x0000028d, 0x00000260, 0x0000024b,
|
||||
0x00050057, 0x00000037, 0x0000028e, 0x0000028a,
|
||||
0x0000028d, 0x0008004f, 0x0000000c, 0x0000028f,
|
||||
0x0000028e, 0x0000028e, 0x00000000, 0x00000001,
|
||||
0x00000002, 0x0007000c, 0x0000000c, 0x00000290,
|
||||
0x00000001, 0x0000001a, 0x0000028f, 0x0000003b,
|
||||
0x00050057, 0x00000037, 0x00000293, 0x0000028a,
|
||||
0x00000260, 0x0008004f, 0x0000000c, 0x00000294,
|
||||
0x00000293, 0x00000293, 0x00000000, 0x00000001,
|
||||
0x00000002, 0x0007000c, 0x0000000c, 0x00000295,
|
||||
0x00000001, 0x0000001a, 0x00000294, 0x0000003b,
|
||||
0x00050081, 0x0000000a, 0x00000299, 0x00000260,
|
||||
0x0000024b, 0x00050057, 0x00000037, 0x0000029a,
|
||||
0x0000028a, 0x00000299, 0x0008004f, 0x0000000c,
|
||||
0x0000029b, 0x0000029a, 0x0000029a, 0x00000000,
|
||||
0x00000001, 0x00000002, 0x0007000c, 0x0000000c,
|
||||
0x0000029c, 0x00000001, 0x0000001a, 0x0000029b,
|
||||
0x0000003b, 0x00050083, 0x0000000a, 0x000002af,
|
||||
0x00000258, 0x0000024b, 0x00050057, 0x00000037,
|
||||
0x000002b0, 0x0000028a, 0x000002af, 0x0008004f,
|
||||
0x0000000c, 0x000002b1, 0x000002b0, 0x000002b0,
|
||||
0x00000000, 0x00000001, 0x00000002, 0x0007000c,
|
||||
0x0000000c, 0x000002b2, 0x00000001, 0x0000001a,
|
||||
0x000002b1, 0x0000003b, 0x00050057, 0x00000037,
|
||||
0x000002b5, 0x0000028a, 0x00000258, 0x0008004f,
|
||||
0x0000000c, 0x000002b6, 0x000002b5, 0x000002b5,
|
||||
0x00000000, 0x00000001, 0x00000002, 0x0007000c,
|
||||
0x0000000c, 0x000002b7, 0x00000001, 0x0000001a,
|
||||
0x000002b6, 0x0000003b, 0x00050081, 0x0000000a,
|
||||
0x000002bb, 0x00000258, 0x0000024b, 0x00050057,
|
||||
0x00000037, 0x000002bc, 0x0000028a, 0x000002bb,
|
||||
0x0008004f, 0x0000000c, 0x000002bd, 0x000002bc,
|
||||
0x000002bc, 0x00000000, 0x00000001, 0x00000002,
|
||||
0x0007000c, 0x0000000c, 0x000002be, 0x00000001,
|
||||
0x0000001a, 0x000002bd, 0x0000003b, 0x00050081,
|
||||
0x0000000a, 0x00000268, 0x00000258, 0x0000024f,
|
||||
0x00050083, 0x0000000a, 0x000002d1, 0x00000268,
|
||||
0x0000024b, 0x00050057, 0x00000037, 0x000002d2,
|
||||
0x0000028a, 0x000002d1, 0x0008004f, 0x0000000c,
|
||||
0x000002d3, 0x000002d2, 0x000002d2, 0x00000000,
|
||||
0x00000001, 0x00000002, 0x0007000c, 0x0000000c,
|
||||
0x000002d4, 0x00000001, 0x0000001a, 0x000002d3,
|
||||
0x0000003b, 0x00050057, 0x00000037, 0x000002d7,
|
||||
0x0000028a, 0x00000268, 0x0008004f, 0x0000000c,
|
||||
0x000002d8, 0x000002d7, 0x000002d7, 0x00000000,
|
||||
0x00000001, 0x00000002, 0x0007000c, 0x0000000c,
|
||||
0x000002d9, 0x00000001, 0x0000001a, 0x000002d8,
|
||||
0x0000003b, 0x00050081, 0x0000000a, 0x000002dd,
|
||||
0x00000268, 0x0000024b, 0x00050057, 0x00000037,
|
||||
0x000002de, 0x0000028a, 0x000002dd, 0x0008004f,
|
||||
0x0000000c, 0x000002df, 0x000002de, 0x000002de,
|
||||
0x00000000, 0x00000001, 0x00000002, 0x0007000c,
|
||||
0x0000000c, 0x000002e0, 0x00000001, 0x0000001a,
|
||||
0x000002df, 0x0000003b, 0x00050051, 0x00000006,
|
||||
0x0000026d, 0x0000025d, 0x00000000, 0x00050083,
|
||||
0x00000006, 0x000002f3, 0x0000026d, 0x00000049,
|
||||
0x00050081, 0x00000006, 0x000002f6, 0x0000026d,
|
||||
0x00000049, 0x00060050, 0x0000000c, 0x000002f7,
|
||||
0x000002f3, 0x0000026d, 0x000002f6, 0x00050085,
|
||||
0x0000000c, 0x000002fa, 0x000002f7, 0x000004f6,
|
||||
0x00050085, 0x0000000c, 0x000002fd, 0x000002fa,
|
||||
0x000002fa, 0x0005008e, 0x0000000c, 0x000002fe,
|
||||
0x000002fd, 0x00000068, 0x0006000c, 0x0000000c,
|
||||
0x000002ff, 0x00000001, 0x0000001d, 0x000002fe,
|
||||
0x00050051, 0x00000006, 0x00000303, 0x000002ff,
|
||||
0x00000000, 0x0005008e, 0x0000000c, 0x00000304,
|
||||
0x00000290, 0x00000303, 0x00050051, 0x00000006,
|
||||
0x00000308, 0x000002ff, 0x00000001, 0x0005008e,
|
||||
0x0000000c, 0x00000309, 0x00000295, 0x00000308,
|
||||
0x00050081, 0x0000000c, 0x0000030a, 0x00000304,
|
||||
0x00000309, 0x00050051, 0x00000006, 0x0000030e,
|
||||
0x000002ff, 0x00000002, 0x0005008e, 0x0000000c,
|
||||
0x0000030f, 0x0000029c, 0x0000030e, 0x00050081,
|
||||
0x0000000c, 0x00000310, 0x0000030a, 0x0000030f,
|
||||
0x00050081, 0x00000006, 0x00000315, 0x00000303,
|
||||
0x00000308, 0x00050081, 0x00000006, 0x00000318,
|
||||
0x00000315, 0x0000030e, 0x00060050, 0x0000000c,
|
||||
0x00000319, 0x00000318, 0x00000318, 0x00000318,
|
||||
0x00050088, 0x0000000c, 0x0000031a, 0x00000310,
|
||||
0x00000319, 0x0005008e, 0x0000000c, 0x00000331,
|
||||
0x000002b2, 0x00000303, 0x0005008e, 0x0000000c,
|
||||
0x00000336, 0x000002b7, 0x00000308, 0x00050081,
|
||||
0x0000000c, 0x00000337, 0x00000331, 0x00000336,
|
||||
0x0005008e, 0x0000000c, 0x0000033c, 0x000002be,
|
||||
0x0000030e, 0x00050081, 0x0000000c, 0x0000033d,
|
||||
0x00000337, 0x0000033c, 0x00050088, 0x0000000c,
|
||||
0x00000347, 0x0000033d, 0x00000319, 0x0005008e,
|
||||
0x0000000c, 0x0000035e, 0x000002d4, 0x00000303,
|
||||
0x0005008e, 0x0000000c, 0x00000363, 0x000002d9,
|
||||
0x00000308, 0x00050081, 0x0000000c, 0x00000364,
|
||||
0x0000035e, 0x00000363, 0x0005008e, 0x0000000c,
|
||||
0x00000369, 0x000002e0, 0x0000030e, 0x00050081,
|
||||
0x0000000c, 0x0000036a, 0x00000364, 0x00000369,
|
||||
0x00050088, 0x0000000c, 0x00000374, 0x0000036a,
|
||||
0x00000319, 0x00050051, 0x00000006, 0x00000286,
|
||||
0x0000025d, 0x00000001, 0x00050083, 0x00000006,
|
||||
0x0000037a, 0x00000286, 0x00000049, 0x00050081,
|
||||
0x00000006, 0x0000037d, 0x00000286, 0x00000049,
|
||||
0x00060050, 0x0000000c, 0x0000037e, 0x0000037a,
|
||||
0x00000286, 0x0000037d, 0x00050085, 0x0000000c,
|
||||
0x00000381, 0x0000037e, 0x000004f6, 0x00050085,
|
||||
0x0000000c, 0x00000384, 0x00000381, 0x00000381,
|
||||
0x0005008e, 0x0000000c, 0x00000385, 0x00000384,
|
||||
0x00000068, 0x0006000c, 0x0000000c, 0x00000386,
|
||||
0x00000001, 0x0000001d, 0x00000385, 0x00050051,
|
||||
0x00000006, 0x0000038a, 0x00000386, 0x00000000,
|
||||
0x0005008e, 0x0000000c, 0x0000038b, 0x0000031a,
|
||||
0x0000038a, 0x00050051, 0x00000006, 0x0000038f,
|
||||
0x00000386, 0x00000001, 0x0005008e, 0x0000000c,
|
||||
0x00000390, 0x00000347, 0x0000038f, 0x00050081,
|
||||
0x0000000c, 0x00000391, 0x0000038b, 0x00000390,
|
||||
0x00050051, 0x00000006, 0x00000395, 0x00000386,
|
||||
0x00000002, 0x0005008e, 0x0000000c, 0x00000396,
|
||||
0x00000374, 0x00000395, 0x00050081, 0x0000000c,
|
||||
0x00000397, 0x00000391, 0x00000396, 0x00050081,
|
||||
0x00000006, 0x0000039c, 0x0000038a, 0x0000038f,
|
||||
0x00050081, 0x00000006, 0x0000039f, 0x0000039c,
|
||||
0x00000395, 0x00060050, 0x0000000c, 0x000003a0,
|
||||
0x0000039f, 0x0000039f, 0x0000039f, 0x00050088,
|
||||
0x0000000c, 0x000003a1, 0x00000397, 0x000003a0,
|
||||
0x00060052, 0x0000000a, 0x000004e1, 0x00000249,
|
||||
0x000001bf, 0x00000000, 0x00050085, 0x0000000a,
|
||||
0x000003b6, 0x000001c8, 0x000004e1, 0x00050083,
|
||||
0x0000000a, 0x000003b7, 0x000003b6, 0x000000fc,
|
||||
0x0006000c, 0x0000000a, 0x000003b9, 0x00000001,
|
||||
0x00000008, 0x000003b7, 0x00050081, 0x0000000a,
|
||||
0x000003ba, 0x000003b9, 0x00000102, 0x00050088,
|
||||
0x0000000a, 0x000003bc, 0x000003ba, 0x000004e1,
|
||||
0x0006000c, 0x0000000a, 0x000003be, 0x00000001,
|
||||
0x0000000a, 0x000003b7, 0x00050051, 0x00000006,
|
||||
0x000003c0, 0x000003be, 0x00000000, 0x00050081,
|
||||
0x00000006, 0x000003c1, 0x000003c0, 0x00000049,
|
||||
0x00050083, 0x00000006, 0x000003c6, 0x000003c0,
|
||||
0x00000049, 0x00050083, 0x00000006, 0x000003c9,
|
||||
0x000003c0, 0x00000116, 0x00070050, 0x00000037,
|
||||
0x000003ca, 0x000003c1, 0x000003c0, 0x000003c6,
|
||||
0x000003c9, 0x0005008e, 0x00000037, 0x000003cb,
|
||||
0x000003ca, 0x0000010b, 0x0006000c, 0x00000037,
|
||||
0x000003cd, 0x00000001, 0x00000004, 0x000003cb,
|
||||
0x0007000c, 0x00000037, 0x000003cf, 0x00000001,
|
||||
0x00000028, 0x000003cd, 0x000004f4, 0x0006000c,
|
||||
0x00000037, 0x000003d1, 0x00000001, 0x0000000d,
|
||||
0x000003cf, 0x0005008e, 0x00000037, 0x000003d2,
|
||||
0x000003d1, 0x00000116, 0x00050085, 0x00000037,
|
||||
0x000003d5, 0x000003cf, 0x000004fa, 0x0006000c,
|
||||
0x00000037, 0x000003d6, 0x00000001, 0x0000000d,
|
||||
0x000003d5, 0x00050085, 0x00000037, 0x000003d7,
|
||||
0x000003d2, 0x000003d6, 0x00050085, 0x00000037,
|
||||
0x000003da, 0x000003cf, 0x000003cf, 0x00050088,
|
||||
0x00000037, 0x000003db, 0x000003d7, 0x000003da,
|
||||
0x00050094, 0x00000006, 0x000003dd, 0x000003db,
|
||||
0x0000012c, 0x00070050, 0x00000037, 0x000003df,
|
||||
0x000003dd, 0x000003dd, 0x000003dd, 0x000003dd,
|
||||
0x00050088, 0x00000037, 0x000003e0, 0x000003db,
|
||||
0x000003df, 0x00050057, 0x00000037, 0x000003e3,
|
||||
0x0000028a, 0x000003bc, 0x0008004f, 0x0000000c,
|
||||
0x000003e4, 0x000003e3, 0x000003e3, 0x00000000,
|
||||
0x00000001, 0x00000002, 0x0007000c, 0x0000000c,
|
||||
0x000003e5, 0x00000001, 0x0000001a, 0x000003e4,
|
||||
0x0000003b, 0x00050051, 0x00000006, 0x000003e6,
|
||||
0x000003e5, 0x00000000, 0x00050051, 0x00000006,
|
||||
0x000003e7, 0x000003e5, 0x00000001, 0x00050051,
|
||||
0x00000006, 0x000003e8, 0x000003e5, 0x00000002,
|
||||
0x00050081, 0x0000000a, 0x000003ed, 0x000003bc,
|
||||
0x0000024b, 0x00050057, 0x00000037, 0x000003ee,
|
||||
0x0000028a, 0x000003ed, 0x0008004f, 0x0000000c,
|
||||
0x000003ef, 0x000003ee, 0x000003ee, 0x00000000,
|
||||
0x00000001, 0x00000002, 0x0007000c, 0x0000000c,
|
||||
0x000003f0, 0x00000001, 0x0000001a, 0x000003ef,
|
||||
0x0000003b, 0x00050051, 0x00000006, 0x000003f1,
|
||||
0x000003f0, 0x00000000, 0x00050051, 0x00000006,
|
||||
0x000003f2, 0x000003f0, 0x00000001, 0x00050051,
|
||||
0x00000006, 0x000003f3, 0x000003f0, 0x00000002,
|
||||
0x00070050, 0x00000037, 0x00000407, 0x000003e6,
|
||||
0x000003e7, 0x000003e8, 0x00000049, 0x00070050,
|
||||
0x00000037, 0x00000409, 0x000003f1, 0x000003f2,
|
||||
0x000003f3, 0x00000049, 0x00070050, 0x00000149,
|
||||
0x0000040b, 0x00000407, 0x00000407, 0x00000409,
|
||||
0x00000409, 0x00050091, 0x00000037, 0x0000040d,
|
||||
0x0000040b, 0x000003e0, 0x0008004f, 0x0000000c,
|
||||
0x0000040e, 0x0000040d, 0x0000040d, 0x00000000,
|
||||
0x00000001, 0x00000002, 0x00050085, 0x00000006,
|
||||
0x0000041b, 0x00000249, 0x000001dc, 0x00060052,
|
||||
0x0000000a, 0x000004e9, 0x0000041b, 0x000001bf,
|
||||
0x00000000, 0x00050088, 0x00000006, 0x0000041f,
|
||||
0x000004fb, 0x00000249, 0x00050050, 0x0000000a,
|
||||
0x00000420, 0x0000041f, 0x0000004a, 0x00050085,
|
||||
0x0000000a, 0x00000423, 0x000001c8, 0x000004e9,
|
||||
0x00050083, 0x0000000a, 0x00000424, 0x00000423,
|
||||
0x000000fc, 0x0006000c, 0x0000000a, 0x00000426,
|
||||
0x00000001, 0x00000008, 0x00000424, 0x00050081,
|
||||
0x0000000a, 0x00000427, 0x00000426, 0x00000102,
|
||||
0x00050088, 0x0000000a, 0x00000429, 0x00000427,
|
||||
0x000004e9, 0x0006000c, 0x0000000a, 0x0000042b,
|
||||
0x00000001, 0x0000000a, 0x00000424, 0x00050051,
|
||||
0x00000006, 0x0000042d, 0x0000042b, 0x00000000,
|
||||
0x00050081, 0x00000006, 0x0000042e, 0x0000042d,
|
||||
0x00000049, 0x00050083, 0x00000006, 0x00000433,
|
||||
0x0000042d, 0x00000049, 0x00050083, 0x00000006,
|
||||
0x00000436, 0x0000042d, 0x00000116, 0x00070050,
|
||||
0x00000037, 0x00000437, 0x0000042e, 0x0000042d,
|
||||
0x00000433, 0x00000436, 0x0005008e, 0x00000037,
|
||||
0x00000438, 0x00000437, 0x0000010b, 0x0006000c,
|
||||
0x00000037, 0x0000043a, 0x00000001, 0x00000004,
|
||||
0x00000438, 0x0007000c, 0x00000037, 0x0000043c,
|
||||
0x00000001, 0x00000028, 0x0000043a, 0x000004f4,
|
||||
0x0006000c, 0x00000037, 0x0000043e, 0x00000001,
|
||||
0x0000000d, 0x0000043c, 0x0005008e, 0x00000037,
|
||||
0x0000043f, 0x0000043e, 0x00000116, 0x00050085,
|
||||
0x00000037, 0x00000442, 0x0000043c, 0x000004fa,
|
||||
0x0006000c, 0x00000037, 0x00000443, 0x00000001,
|
||||
0x0000000d, 0x00000442, 0x00050085, 0x00000037,
|
||||
0x00000444, 0x0000043f, 0x00000443, 0x00050085,
|
||||
0x00000037, 0x00000447, 0x0000043c, 0x0000043c,
|
||||
0x00050088, 0x00000037, 0x00000448, 0x00000444,
|
||||
0x00000447, 0x00050094, 0x00000006, 0x0000044a,
|
||||
0x00000448, 0x0000012c, 0x00070050, 0x00000037,
|
||||
0x0000044c, 0x0000044a, 0x0000044a, 0x0000044a,
|
||||
0x0000044a, 0x00050088, 0x00000037, 0x0000044d,
|
||||
0x00000448, 0x0000044c, 0x00050057, 0x00000037,
|
||||
0x00000450, 0x0000028a, 0x00000429, 0x0008004f,
|
||||
0x0000000c, 0x00000451, 0x00000450, 0x00000450,
|
||||
0x00000000, 0x00000001, 0x00000002, 0x0007000c,
|
||||
0x0000000c, 0x00000452, 0x00000001, 0x0000001a,
|
||||
0x00000451, 0x0000003b, 0x00050051, 0x00000006,
|
||||
0x00000453, 0x00000452, 0x00000000, 0x00050051,
|
||||
0x00000006, 0x00000454, 0x00000452, 0x00000001,
|
||||
0x00050051, 0x00000006, 0x00000455, 0x00000452,
|
||||
0x00000002, 0x00050081, 0x0000000a, 0x0000045a,
|
||||
0x00000429, 0x00000420, 0x00050057, 0x00000037,
|
||||
0x0000045b, 0x0000028a, 0x0000045a, 0x0008004f,
|
||||
0x0000000c, 0x0000045c, 0x0000045b, 0x0000045b,
|
||||
0x00000000, 0x00000001, 0x00000002, 0x0007000c,
|
||||
0x0000000c, 0x0000045d, 0x00000001, 0x0000001a,
|
||||
0x0000045c, 0x0000003b, 0x00050051, 0x00000006,
|
||||
0x0000045e, 0x0000045d, 0x00000000, 0x00050051,
|
||||
0x00000006, 0x0000045f, 0x0000045d, 0x00000001,
|
||||
0x00050051, 0x00000006, 0x00000460, 0x0000045d,
|
||||
0x00000002, 0x00070050, 0x00000037, 0x00000474,
|
||||
0x00000453, 0x00000454, 0x00000455, 0x00000049,
|
||||
0x00070050, 0x00000037, 0x00000476, 0x0000045e,
|
||||
0x0000045f, 0x00000460, 0x00000049, 0x00070050,
|
||||
0x00000149, 0x00000478, 0x00000474, 0x00000474,
|
||||
0x00000476, 0x00000476, 0x00050091, 0x00000037,
|
||||
0x0000047a, 0x00000478, 0x0000044d, 0x0008004f,
|
||||
0x0000000c, 0x0000047b, 0x0000047a, 0x0000047a,
|
||||
0x00000000, 0x00000001, 0x00000002, 0x00050085,
|
||||
0x0000000c, 0x000001e8, 0x0000047b, 0x0000040e,
|
||||
0x0006000c, 0x0000000c, 0x000001e9, 0x00000001,
|
||||
0x0000001f, 0x000001e8, 0x00050051, 0x00000006,
|
||||
0x000001eb, 0x000001c8, 0x00000001, 0x00060041,
|
||||
0x00000188, 0x000001ec, 0x00000187, 0x0000006c,
|
||||
0x00000077, 0x0004003d, 0x00000006, 0x000001ed,
|
||||
0x000001ec, 0x00050085, 0x00000006, 0x000001ee,
|
||||
0x000001eb, 0x000001ed, 0x0006000c, 0x00000006,
|
||||
0x000001ef, 0x00000001, 0x0000000a, 0x000001ee,
|
||||
0x0007000c, 0x0000000c, 0x00000482, 0x00000001,
|
||||
0x0000001a, 0x0000040e, 0x0000016a, 0x0008000c,
|
||||
0x0000000c, 0x00000483, 0x00000001, 0x0000002e,
|
||||
0x00000165, 0x00000167, 0x00000482, 0x00050088,
|
||||
0x0000000c, 0x00000486, 0x000004f6, 0x00000483,
|
||||
0x0005008e, 0x0000000c, 0x00000488, 0x00000486,
|
||||
0x000000a1, 0x0005008e, 0x0000000c, 0x0000048b,
|
||||
0x00000486, 0x000001ef, 0x00050083, 0x0000000c,
|
||||
0x0000048d, 0x0000048b, 0x00000488, 0x0006000c,
|
||||
0x0000000c, 0x0000048e, 0x00000001, 0x00000004,
|
||||
0x0000048d, 0x00050083, 0x0000000c, 0x00000490,
|
||||
0x000004f7, 0x0000048e, 0x0008000c, 0x0000000c,
|
||||
0x00000493, 0x00000001, 0x00000031, 0x000004f8,
|
||||
0x000004f7, 0x00000490, 0x00050085, 0x0000000c,
|
||||
0x00000495, 0x00000493, 0x00000488, 0x00050085,
|
||||
0x0000000c, 0x000001f5, 0x000001e9, 0x00000495,
|
||||
0x00050083, 0x0000000c, 0x000001f8, 0x000003a1,
|
||||
0x000001f5, 0x0008000c, 0x0000000c, 0x000001fb,
|
||||
0x00000001, 0x0000002b, 0x000001f8, 0x000004f8,
|
||||
0x000004f7, 0x00050085, 0x0000000c, 0x000001fe,
|
||||
0x000001fb, 0x000001fb, 0x0005008e, 0x0000000c,
|
||||
0x00000200, 0x000001fe, 0x000001ff, 0x00050081,
|
||||
0x0000000c, 0x00000202, 0x000001f5, 0x00000200,
|
||||
0x00050041, 0x00000206, 0x00000207, 0x000001ba,
|
||||
0x00000070, 0x0004003d, 0x00000006, 0x00000208,
|
||||
0x00000207, 0x000300f7, 0x000004b3, 0x00000000,
|
||||
0x000300fb, 0x00000070, 0x0000049b, 0x000200f8,
|
||||
0x0000049b, 0x00060041, 0x00000188, 0x0000049d,
|
||||
0x00000187, 0x00000074, 0x00000070, 0x0004003d,
|
||||
0x00000006, 0x0000049e, 0x0000049d, 0x00050085,
|
||||
0x00000006, 0x0000049f, 0x00000208, 0x0000049e,
|
||||
0x0006000c, 0x00000006, 0x000004a7, 0x00000001,
|
||||
0x00000008, 0x0000049f, 0x0005008d, 0x00000006,
|
||||
0x000004a8, 0x000004a7, 0x00000116, 0x000500b4,
|
||||
0x00000196, 0x000004aa, 0x000004a8, 0x0000004a,
|
||||
0x000300f7, 0x000004b2, 0x00000000, 0x000400fa,
|
||||
0x000004aa, 0x000004ab, 0x000004ac, 0x000200f8,
|
||||
0x000004ac, 0x000500b4, 0x00000196, 0x000004ae,
|
||||
0x000004a8, 0x00000049, 0x000300f7, 0x000004b1,
|
||||
0x00000000, 0x000400fa, 0x000004ae, 0x000004af,
|
||||
0x000004b0, 0x000200f8, 0x000004b0, 0x000200f9,
|
||||
0x000004b3, 0x000200f8, 0x000004af, 0x000200f9,
|
||||
0x000004b3, 0x000200f8, 0x000004b1, 0x000100ff,
|
||||
0x000200f8, 0x000004ab, 0x000200f9, 0x000004b3,
|
||||
0x000200f8, 0x000004b2, 0x000100ff, 0x000200f8,
|
||||
0x000004b3, 0x000900f5, 0x0000000c, 0x000004f1,
|
||||
0x0000019a, 0x000004ab, 0x000001a1, 0x000004af,
|
||||
0x000001a4, 0x000004b0, 0x00050085, 0x0000000c,
|
||||
0x0000020a, 0x00000202, 0x000004f1, 0x0005008e,
|
||||
0x0000000c, 0x0000020b, 0x0000020a, 0x00000116,
|
||||
0x0008000c, 0x0000000c, 0x0000020e, 0x00000001,
|
||||
0x0000002e, 0x00000202, 0x0000020b, 0x000004f9,
|
||||
0x0005008e, 0x0000000c, 0x00000211, 0x000001fb,
|
||||
0x00000210, 0x00050081, 0x0000000c, 0x00000213,
|
||||
0x0000020e, 0x00000211, 0x0005008e, 0x0000000c,
|
||||
0x00000215, 0x00000213, 0x00000166, 0x0007000c,
|
||||
0x0000000c, 0x00000218, 0x00000001, 0x0000001a,
|
||||
0x00000215, 0x00000217, 0x00050051, 0x00000006,
|
||||
0x0000021c, 0x00000218, 0x00000000, 0x00050051,
|
||||
0x00000006, 0x0000021d, 0x00000218, 0x00000001,
|
||||
0x00050051, 0x00000006, 0x0000021e, 0x00000218,
|
||||
0x00000002, 0x00070050, 0x00000037, 0x0000021f,
|
||||
0x0000021c, 0x0000021d, 0x0000021e, 0x00000049,
|
||||
0x0003003e, 0x0000021a, 0x0000021f, 0x000100fd,
|
||||
0x00010038};
|
||||
@@ -1,75 +1,55 @@
|
||||
#include "wsi_platform.hpp"
|
||||
#include <SDL3/SDL_vulkan.h>
|
||||
|
||||
VkSurfaceKHR SDL_WSIPlatform::create_surface(VkInstance instance, VkPhysicalDevice gpu)
|
||||
{
|
||||
VkSurfaceKHR surface = nullptr;
|
||||
bool result = SDL_Vulkan_CreateSurface(window, instance, NULL, &surface);
|
||||
if (result != true)
|
||||
{
|
||||
printf("Error creating surface\n");
|
||||
}
|
||||
return surface;
|
||||
VkSurfaceKHR SDL_WSIPlatform::create_surface(VkInstance instance,
|
||||
VkPhysicalDevice gpu) {
|
||||
VkSurfaceKHR surface = nullptr;
|
||||
bool result = SDL_Vulkan_CreateSurface(window, instance, NULL, &surface);
|
||||
if (result != true) {
|
||||
printf("Error creating surface\n");
|
||||
}
|
||||
return surface;
|
||||
}
|
||||
|
||||
void SDL_WSIPlatform::destroy_surface(VkInstance instance, VkSurfaceKHR surface)
|
||||
{
|
||||
SDL_Vulkan_DestroySurface(instance, surface, NULL);
|
||||
void SDL_WSIPlatform::destroy_surface(VkInstance instance,
|
||||
VkSurfaceKHR surface) {
|
||||
SDL_Vulkan_DestroySurface(instance, surface, NULL);
|
||||
}
|
||||
|
||||
std::vector<const char *> SDL_WSIPlatform::get_instance_extensions()
|
||||
{
|
||||
std::vector<const char *> SDL_WSIPlatform::get_instance_extensions() {
|
||||
|
||||
unsigned int extensionCount = 0;
|
||||
char const *const *extensions = SDL_Vulkan_GetInstanceExtensions(&extensionCount);
|
||||
if (extensions == NULL)
|
||||
{
|
||||
printf("Error getting instance extensions\n");
|
||||
}
|
||||
unsigned int extensionCount = 0;
|
||||
char const *const *extensions =
|
||||
SDL_Vulkan_GetInstanceExtensions(&extensionCount);
|
||||
if (extensions == NULL) {
|
||||
printf("Error getting instance extensions\n");
|
||||
}
|
||||
|
||||
std::vector<const char *> extensionNames;
|
||||
for (int i = 0; i < extensionCount; ++i)
|
||||
{
|
||||
extensionNames.push_back(extensions[i]);
|
||||
}
|
||||
return extensionNames;
|
||||
std::vector<const char *> extensionNames;
|
||||
for (unsigned int i = 0; i < extensionCount; ++i) {
|
||||
extensionNames.push_back(extensions[i]);
|
||||
}
|
||||
return extensionNames;
|
||||
}
|
||||
|
||||
uint32_t SDL_WSIPlatform::get_surface_width()
|
||||
{
|
||||
int w, h;
|
||||
SDL_GetWindowSize(window, &w, &h);
|
||||
return w;
|
||||
uint32_t SDL_WSIPlatform::get_surface_width() {
|
||||
int w, h;
|
||||
SDL_GetWindowSize(window, &w, &h);
|
||||
return w;
|
||||
}
|
||||
|
||||
uint32_t SDL_WSIPlatform::get_surface_height()
|
||||
{
|
||||
int w, h;
|
||||
SDL_GetWindowSize(window, &w, &h);
|
||||
return h;
|
||||
uint32_t SDL_WSIPlatform::get_surface_height() {
|
||||
int w, h;
|
||||
SDL_GetWindowSize(window, &w, &h);
|
||||
return h;
|
||||
}
|
||||
|
||||
bool SDL_WSIPlatform::alive(Vulkan::WSI &wsi)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool SDL_WSIPlatform::alive(Vulkan::WSI &wsi) { return true; }
|
||||
|
||||
void SDL_WSIPlatform::poll_input()
|
||||
{
|
||||
SDL_PumpEvents();
|
||||
}
|
||||
void SDL_WSIPlatform::poll_input() {}
|
||||
|
||||
void SDL_WSIPlatform::poll_input_async(Granite::InputTrackerHandler *handler)
|
||||
{
|
||||
SDL_PumpEvents();
|
||||
}
|
||||
void SDL_WSIPlatform::poll_input_async(Granite::InputTrackerHandler *handler) {}
|
||||
|
||||
void SDL_WSIPlatform::set_window(SDL_Window *_window)
|
||||
{
|
||||
window = _window;
|
||||
}
|
||||
void SDL_WSIPlatform::set_window(SDL_Window *_window) { window = _window; }
|
||||
|
||||
void SDL_WSIPlatform::do_resize()
|
||||
{
|
||||
resize = true;
|
||||
}
|
||||
void SDL_WSIPlatform::do_resize() { resize = true; }
|
||||
|
||||
@@ -3,20 +3,20 @@
|
||||
#include "wsi.hpp"
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
class SDL_WSIPlatform : public Vulkan::WSIPlatform
|
||||
{
|
||||
class SDL_WSIPlatform : public Vulkan::WSIPlatform {
|
||||
public:
|
||||
VkSurfaceKHR create_surface(VkInstance instance, VkPhysicalDevice gpu) override;
|
||||
void destroy_surface(VkInstance instance, VkSurfaceKHR surface) override;
|
||||
std::vector<const char *> get_instance_extensions() override;
|
||||
uint32_t get_surface_width() override;
|
||||
uint32_t get_surface_height() override;
|
||||
bool alive(Vulkan::WSI &wsi) override;
|
||||
void poll_input() override;
|
||||
void poll_input_async(Granite::InputTrackerHandler *handler) override;
|
||||
void set_window(SDL_Window *_window);
|
||||
void do_resize();
|
||||
VkSurfaceKHR create_surface(VkInstance instance,
|
||||
VkPhysicalDevice gpu) override;
|
||||
void destroy_surface(VkInstance instance, VkSurfaceKHR surface) override;
|
||||
std::vector<const char *> get_instance_extensions() override;
|
||||
uint32_t get_surface_width() override;
|
||||
uint32_t get_surface_height() override;
|
||||
bool alive(Vulkan::WSI &wsi) override;
|
||||
void poll_input() override;
|
||||
void poll_input_async(Granite::InputTrackerHandler *handler) override;
|
||||
void set_window(SDL_Window *_window);
|
||||
void do_resize();
|
||||
|
||||
private:
|
||||
SDL_Window *window;
|
||||
SDL_Window *window;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,399 @@
|
||||
#include "retroachievements.h"
|
||||
#include "../parallel-rdp/interface.hpp"
|
||||
#include <rc_client.h>
|
||||
#include <rc_consoles.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
void rust_server_call(const char *url, const char *post_data,
|
||||
const char *content_type,
|
||||
rc_client_server_callback_t callback,
|
||||
void *callback_data);
|
||||
void store_retroachievements_credentials(const char *username,
|
||||
const char *token, void *userdata);
|
||||
|
||||
void notify_load_game(void *userdata);
|
||||
|
||||
static rc_client_t *g_client = NULL;
|
||||
static const uint8_t *g_dmem = NULL;
|
||||
static size_t g_dmem_size = 0;
|
||||
static bool g_challenge = false;
|
||||
static bool g_leaderboard = false;
|
||||
static rc_client_leaderboard_list_t *g_leaderboard_list = NULL;
|
||||
|
||||
static uint32_t read_memory(uint32_t address, uint8_t *buffer,
|
||||
uint32_t num_bytes, rc_client_t *client) {
|
||||
if (address + num_bytes > g_dmem_size)
|
||||
memset(buffer, 0, num_bytes);
|
||||
else
|
||||
memcpy(buffer, &g_dmem[address], num_bytes);
|
||||
return num_bytes;
|
||||
}
|
||||
|
||||
static void server_call(const rc_api_request_t *request,
|
||||
rc_client_server_callback_t callback,
|
||||
void *callback_data, rc_client_t *client) {
|
||||
rust_server_call(request->url, request->post_data, request->content_type,
|
||||
callback, callback_data);
|
||||
}
|
||||
|
||||
void ra_http_callback(const char *content, size_t content_size, int status_code,
|
||||
void *callback, void *callback_data) {
|
||||
// Prepare a data object to pass the HTTP response to the callback
|
||||
rc_api_server_response_t server_response;
|
||||
memset(&server_response, 0, sizeof(server_response));
|
||||
server_response.body = content;
|
||||
server_response.body_length = content_size;
|
||||
server_response.http_status_code = status_code;
|
||||
|
||||
// handle non-http errors (socket timeout, no internet available, etc)
|
||||
if (status_code == 0) {
|
||||
// Let rc_client know the error was not catastrophic and could be retried.
|
||||
// It may decide to retry or just immediately pass the error to the
|
||||
// callback. To prevent possible retries, use
|
||||
// RC_API_SERVER_RESPONSE_CLIENT_ERROR.
|
||||
server_response.http_status_code =
|
||||
RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR;
|
||||
}
|
||||
|
||||
((rc_client_server_callback_t)callback)(&server_response, callback_data);
|
||||
}
|
||||
|
||||
static void log_message(const char *message, const rc_client_t *client) {
|
||||
printf("RetroAchievements: %s\n", message);
|
||||
}
|
||||
|
||||
static void login_callback(int result, const char *error_message,
|
||||
rc_client_t *client, void *userdata) {
|
||||
// If not successful, just report the error and bail.
|
||||
if (result != RC_OK) {
|
||||
if (client && !rc_client_get_userdata(client))
|
||||
rc_client_set_userdata(client, strdup(error_message));
|
||||
store_retroachievements_credentials(NULL, NULL, userdata);
|
||||
return;
|
||||
}
|
||||
|
||||
// Login was successful. Capture the token for future logins so we don't have
|
||||
// to store the password anywhere.
|
||||
const rc_client_user_t *user = rc_client_get_user_info(client);
|
||||
store_retroachievements_credentials(user->username, user->token, userdata);
|
||||
}
|
||||
|
||||
const char *ra_get_username() {
|
||||
const rc_client_user_t *user = rc_client_get_user_info(g_client);
|
||||
return user ? user->username : NULL;
|
||||
}
|
||||
const char *ra_get_token() {
|
||||
const rc_client_user_t *user = rc_client_get_user_info(g_client);
|
||||
return user ? user->token : NULL;
|
||||
}
|
||||
|
||||
void ra_logout_user() { rc_client_logout(g_client); }
|
||||
|
||||
void ra_login_user(const char *username, const char *password, void *userdata) {
|
||||
// This will generate an HTTP payload and call the server_call chain above.
|
||||
// Eventually, login_callback will be called to let us know if the login was
|
||||
// successful.
|
||||
rc_client_begin_login_with_password(g_client, username, password,
|
||||
login_callback, userdata);
|
||||
}
|
||||
|
||||
void ra_login_token_user(const char *username, const char *token,
|
||||
void *userdata) {
|
||||
// This is exactly the same functionality as
|
||||
// rc_client_begin_login_with_password, but uses the token captured from the
|
||||
// first login instead of a password. Note that it uses the same callback.
|
||||
rc_client_begin_login_with_token(g_client, username, token, login_callback,
|
||||
userdata);
|
||||
}
|
||||
|
||||
static void load_game_callback(int result, const char *error_message,
|
||||
rc_client_t *client, void *userdata) {
|
||||
if (result != RC_OK) {
|
||||
if (client && !rc_client_get_userdata(client))
|
||||
rc_client_set_userdata(client, strdup(error_message));
|
||||
rc_client_set_hardcore_enabled(client, false);
|
||||
notify_load_game(userdata);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rc_client_is_processing_required(client)) {
|
||||
rc_client_set_hardcore_enabled(client, false);
|
||||
}
|
||||
|
||||
g_leaderboard_list = rc_client_create_leaderboard_list(
|
||||
client, RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE);
|
||||
|
||||
notify_load_game(userdata);
|
||||
}
|
||||
|
||||
void ra_welcome() {
|
||||
if (!g_client)
|
||||
return;
|
||||
|
||||
char buffer[512];
|
||||
|
||||
char *error_message = rc_client_get_userdata(g_client);
|
||||
if (error_message) {
|
||||
snprintf(buffer, sizeof(buffer), "RA Error: %s", error_message);
|
||||
rdp_onscreen_message(buffer, MESSAGE_LONG);
|
||||
free(error_message);
|
||||
rc_client_set_userdata(g_client, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
const rc_client_game_t *game = rc_client_get_game_info(g_client);
|
||||
|
||||
rc_client_user_game_summary_t summary;
|
||||
rc_client_get_user_game_summary(g_client, &summary);
|
||||
|
||||
int message_length = snprintf(
|
||||
buffer, sizeof(buffer), "%s\nMode: %s\n", game->title,
|
||||
rc_client_get_hardcore_enabled(g_client) ? "Hardcore" : "Softcore");
|
||||
|
||||
if (summary.num_core_achievements != 0) {
|
||||
snprintf(buffer + message_length, sizeof(buffer) - message_length,
|
||||
"%u/%u achievements unlocked", summary.num_unlocked_achievements,
|
||||
summary.num_core_achievements);
|
||||
} else {
|
||||
snprintf(buffer + message_length, sizeof(buffer) - message_length,
|
||||
"Game has no achievements");
|
||||
}
|
||||
rdp_onscreen_message(buffer, MESSAGE_LONG);
|
||||
}
|
||||
|
||||
void ra_load_game(const uint8_t *rom, size_t rom_size, void *userdata) {
|
||||
rc_client_begin_identify_and_load_game(g_client, RC_CONSOLE_NINTENDO_64, NULL,
|
||||
rom, rom_size, load_game_callback,
|
||||
userdata);
|
||||
}
|
||||
|
||||
void ra_unload_game() {
|
||||
if (g_leaderboard_list) {
|
||||
rc_client_destroy_leaderboard_list(g_leaderboard_list);
|
||||
g_leaderboard_list = NULL;
|
||||
}
|
||||
rc_client_unload_game(g_client);
|
||||
}
|
||||
|
||||
void ra_set_dmem(const uint8_t *dmem, size_t dmem_size) {
|
||||
g_dmem = dmem;
|
||||
g_dmem_size = dmem_size;
|
||||
}
|
||||
|
||||
static void leaderboard_submitted(const rc_client_leaderboard_t *leaderboard) {
|
||||
char buffer[512];
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "Leaderboard submitted: %s - %s",
|
||||
leaderboard->title, leaderboard->tracker_value);
|
||||
rdp_onscreen_message(buffer, MESSAGE_SHORT);
|
||||
}
|
||||
|
||||
static void achievement_triggered(const rc_client_achievement_t *achievement) {
|
||||
char buffer[512];
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "Unlocked: %s", achievement->title);
|
||||
rdp_onscreen_message(buffer, MESSAGE_SHORT);
|
||||
}
|
||||
|
||||
static void game_completed(rc_client_t *client) {
|
||||
char buffer[512];
|
||||
const rc_client_game_t *game = rc_client_get_game_info(client);
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "%s: %s",
|
||||
rc_client_get_hardcore_enabled(client) ? "Mastered" : "Completed",
|
||||
game->title);
|
||||
rdp_onscreen_message(buffer, MESSAGE_SHORT);
|
||||
}
|
||||
|
||||
static void subset_completed(const rc_client_subset_t *subset,
|
||||
rc_client_t *client) {
|
||||
char buffer[512];
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "Subset %s: %s",
|
||||
rc_client_get_hardcore_enabled(client) ? "mastered" : "completed",
|
||||
subset->title);
|
||||
rdp_onscreen_message(buffer, MESSAGE_SHORT);
|
||||
}
|
||||
|
||||
static void server_error(const rc_client_server_error_t *server_error) {
|
||||
char buffer[512];
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "RA server error: %s",
|
||||
server_error->error_message);
|
||||
rdp_onscreen_message(buffer, MESSAGE_SHORT);
|
||||
}
|
||||
|
||||
static const char *get_leaderboard_title(const char *display) {
|
||||
if (g_leaderboard_list == NULL)
|
||||
return NULL;
|
||||
|
||||
for (uint32_t i = 0; i < g_leaderboard_list->num_buckets; i++) {
|
||||
for (uint32_t j = 0; j < g_leaderboard_list->buckets[i].num_leaderboards;
|
||||
j++) {
|
||||
// this looks like a mistake, but it is intentional.
|
||||
// Leaderboard trackers don't contain the title,
|
||||
// but we take advantage of the fact that tracker_value and display point
|
||||
// to the same char* to find a matching leaderboard.
|
||||
if (g_leaderboard_list->buckets[i].leaderboards[j]->tracker_value ==
|
||||
display) {
|
||||
return g_leaderboard_list->buckets[i].leaderboards[j]->title;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void event_handler(const rc_client_event_t *event, rc_client_t *client) {
|
||||
switch (event->type) {
|
||||
case RC_CLIENT_EVENT_ACHIEVEMENT_TRIGGERED:
|
||||
achievement_triggered(event->achievement);
|
||||
break;
|
||||
case RC_CLIENT_EVENT_LEADERBOARD_STARTED:
|
||||
break;
|
||||
case RC_CLIENT_EVENT_LEADERBOARD_FAILED:
|
||||
break;
|
||||
case RC_CLIENT_EVENT_LEADERBOARD_SUBMITTED:
|
||||
if (g_leaderboard)
|
||||
leaderboard_submitted(event->leaderboard);
|
||||
break;
|
||||
case RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW:
|
||||
if (g_challenge)
|
||||
achievement_challenge_indicator_add(event->achievement->title);
|
||||
break;
|
||||
case RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE:
|
||||
if (g_challenge)
|
||||
achievement_challenge_indicator_remove(event->achievement->title);
|
||||
break;
|
||||
case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_SHOW:
|
||||
achievement_progress_add(event->achievement->title,
|
||||
event->achievement->measured_progress);
|
||||
break;
|
||||
case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_HIDE:
|
||||
achievement_progress_remove();
|
||||
break;
|
||||
case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_UPDATE:
|
||||
achievement_progress_add(event->achievement->title,
|
||||
event->achievement->measured_progress);
|
||||
break;
|
||||
case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_SHOW:
|
||||
if (g_leaderboard)
|
||||
leaderboard_tracker_add(
|
||||
event->leaderboard_tracker->id,
|
||||
get_leaderboard_title(event->leaderboard_tracker->display),
|
||||
event->leaderboard_tracker->display);
|
||||
break;
|
||||
case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_HIDE:
|
||||
if (g_leaderboard)
|
||||
leaderboard_tracker_remove(event->leaderboard_tracker->id);
|
||||
break;
|
||||
case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE:
|
||||
if (g_leaderboard)
|
||||
leaderboard_tracker_add(
|
||||
event->leaderboard_tracker->id,
|
||||
get_leaderboard_title(event->leaderboard_tracker->display),
|
||||
event->leaderboard_tracker->display);
|
||||
break;
|
||||
case RC_CLIENT_EVENT_LEADERBOARD_SCOREBOARD:
|
||||
break;
|
||||
case RC_CLIENT_EVENT_GAME_COMPLETED:
|
||||
game_completed(client);
|
||||
break;
|
||||
case RC_CLIENT_EVENT_SUBSET_COMPLETED:
|
||||
subset_completed(event->subset, client);
|
||||
break;
|
||||
case RC_CLIENT_EVENT_SERVER_ERROR:
|
||||
server_error(event->server_error);
|
||||
break;
|
||||
default:
|
||||
printf("RetroAchievements: Unhandled event %d\n", event->type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ra_init_client(bool hardcore, bool challenge, bool leaderboard) {
|
||||
// Create the client instance (using a global variable simplifies this
|
||||
// example)
|
||||
g_client = rc_client_create(read_memory, server_call);
|
||||
g_dmem = NULL;
|
||||
g_dmem_size = 0;
|
||||
|
||||
// Provide a logging function to simplify debugging
|
||||
rc_client_enable_logging(g_client, RC_CLIENT_LOG_LEVEL_WARN, log_message);
|
||||
|
||||
rc_client_set_event_handler(g_client, event_handler);
|
||||
|
||||
rc_client_set_hardcore_enabled(g_client, hardcore);
|
||||
|
||||
g_challenge = challenge;
|
||||
g_leaderboard = leaderboard;
|
||||
}
|
||||
|
||||
bool ra_get_hardcore() { return rc_client_get_hardcore_enabled(g_client); }
|
||||
|
||||
void ra_shutdown_client() {
|
||||
if (g_client) {
|
||||
// Release resources associated to the client instance
|
||||
rc_client_destroy(g_client);
|
||||
g_client = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void ra_do_frame() { rc_client_do_frame(g_client); }
|
||||
|
||||
void ra_do_idle() { rc_client_idle(g_client); }
|
||||
|
||||
size_t ra_state_size() { return rc_client_progress_size(g_client); }
|
||||
|
||||
void ra_save_state(uint8_t *state, size_t state_size) {
|
||||
if (!rc_client_is_game_loaded(g_client))
|
||||
return;
|
||||
|
||||
if (rc_client_serialize_progress_sized(g_client, state, state_size) !=
|
||||
RC_OK) {
|
||||
printf("RetroAchievements: Failed to serialize progress\n");
|
||||
}
|
||||
}
|
||||
|
||||
void ra_load_state(const uint8_t *state, size_t state_size) {
|
||||
if (!rc_client_is_game_loaded(g_client))
|
||||
return;
|
||||
|
||||
if (rc_client_deserialize_progress_sized(g_client, state, state_size) !=
|
||||
RC_OK) {
|
||||
printf("RetroAchievements: Failed to deserialize progress\n");
|
||||
}
|
||||
}
|
||||
|
||||
void ra_display_inprogress_achievements(void *userdata) {
|
||||
if (!rc_client_is_game_loaded(g_client))
|
||||
return;
|
||||
|
||||
rc_client_achievement_list_t *list = rc_client_create_achievement_list(
|
||||
g_client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE,
|
||||
RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE);
|
||||
|
||||
char buffer[1024] = {0};
|
||||
size_t buffer_length = 0;
|
||||
|
||||
for (uint32_t i = 0; i < list->num_buckets; i++) {
|
||||
if (list->buckets[i].bucket_type == RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED) {
|
||||
for (uint32_t j = 0; j < list->buckets[i].num_achievements; j++) {
|
||||
const rc_client_achievement_t *achievement =
|
||||
list->buckets[i].achievements[j];
|
||||
if (achievement->measured_percent > 0.0f) {
|
||||
if (buffer_length < sizeof(buffer) - 1) {
|
||||
buffer_length += snprintf(
|
||||
buffer + buffer_length, sizeof(buffer) - buffer_length,
|
||||
"%s: %s\n", achievement->title, achievement->measured_progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (buffer_length > 0) {
|
||||
rdp_onscreen_message(buffer, MESSAGE_SHORT);
|
||||
}
|
||||
rc_client_destroy_achievement_list(list);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
void ra_init_client(bool hardcore, bool challenge, bool leaderboard);
|
||||
void ra_shutdown_client();
|
||||
void ra_welcome();
|
||||
bool ra_get_hardcore();
|
||||
void ra_load_game(const uint8_t *rom, size_t rom_size, void *userdata);
|
||||
void ra_unload_game();
|
||||
void ra_set_dmem(const uint8_t *dmem, size_t dmem_size);
|
||||
void ra_do_frame();
|
||||
void ra_do_idle();
|
||||
void ra_http_callback(const char *content, size_t content_size, int status_code,
|
||||
void *callback, void *callback_data);
|
||||
void ra_logout_user();
|
||||
void ra_login_user(const char *username, const char *password, void *userdata);
|
||||
void ra_login_token_user(const char *username, const char *token,
|
||||
void *userdata);
|
||||
const char *ra_get_username();
|
||||
const char *ra_get_token();
|
||||
size_t ra_state_size();
|
||||
void ra_save_state(uint8_t *state, size_t state_size);
|
||||
void ra_load_state(const uint8_t *state, size_t state_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
void ra_display_inprogress_achievements(void *userdata);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "1.95.0"
|
||||
@@ -0,0 +1,197 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Copy, Clone)]
|
||||
pub struct DecodedCheat {
|
||||
code_type: u8,
|
||||
address: u32,
|
||||
data: u16,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Cheats {
|
||||
pub cheats: Vec<Vec<DecodedCheat>>,
|
||||
pub boot: bool,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Clone)]
|
||||
pub struct CheatData {
|
||||
pub note: String,
|
||||
pub data: Vec<String>,
|
||||
pub options: Option<std::collections::BTreeMap<String, String>>,
|
||||
}
|
||||
|
||||
pub type Cheat = std::collections::BTreeMap<String, std::collections::BTreeMap<String, CheatData>>;
|
||||
|
||||
pub fn init(
|
||||
device: &mut device::Device,
|
||||
cheat_settings: std::collections::HashMap<String, Option<String>>,
|
||||
) {
|
||||
let cheats = serde_json::from_slice::<Cheat>(include_bytes!("../data/cheats.json"))
|
||||
.unwrap()
|
||||
.get(&ui::storage::get_game_crc(&device.cart.rom))
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
let re = regex::Regex::new(r"\?+").unwrap();
|
||||
for cheat_setting in cheat_settings.iter() {
|
||||
if let Some(cheat_data) = cheats.get(cheat_setting.0) {
|
||||
let mut option_value = None;
|
||||
if let Some(option) = cheat_setting.1 {
|
||||
if let Some(found_option_value) = cheat_data
|
||||
.options
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.get(option)
|
||||
{
|
||||
option_value = Some(found_option_value.to_string());
|
||||
} else {
|
||||
panic!("Cheat option: {option} not found");
|
||||
}
|
||||
}
|
||||
|
||||
println!(
|
||||
"Cheat: {} Option: {}",
|
||||
cheat_setting.0,
|
||||
cheat_setting.1.clone().unwrap_or("none".to_string())
|
||||
);
|
||||
let mut decoded_cheat: Vec<DecodedCheat> = vec![];
|
||||
for code in cheat_data.data.iter() {
|
||||
let mut result = code.clone();
|
||||
if let Some(option_value) = option_value.as_ref() {
|
||||
result = re.replace_all(code, option_value).into_owned();
|
||||
}
|
||||
let mut split = result.split_whitespace();
|
||||
let first_part = u32::from_str_radix(split.next().unwrap(), 16).unwrap();
|
||||
decoded_cheat.push(DecodedCheat {
|
||||
code_type: (first_part >> 24) as u8,
|
||||
address: first_part & 0x00FFFFFF,
|
||||
data: u16::from_str_radix(split.next().unwrap(), 16).unwrap(),
|
||||
});
|
||||
}
|
||||
device.cheats.cheats.push(decoded_cheat);
|
||||
} else {
|
||||
println!("Could not find cheat: {}", cheat_setting.0);
|
||||
}
|
||||
}
|
||||
|
||||
if !device.cheats.cheats.is_empty() {
|
||||
device.cheats.enabled = true;
|
||||
ui::video::onscreen_message("Cheats enabled", ui::video::MESSAGE_LENGTH_MESSAGE_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_byte(device: &mut device::Device, cheat_line: &DecodedCheat) {
|
||||
*device
|
||||
.rdram
|
||||
.mem
|
||||
.get_mut(cheat_line.address as usize ^ device.byte_swap)
|
||||
.unwrap_or(&mut 0) = cheat_line.data as u8;
|
||||
}
|
||||
|
||||
fn write_half(device: &mut device::Device, cheat_line: &DecodedCheat) {
|
||||
*device
|
||||
.rdram
|
||||
.mem
|
||||
.get_mut(cheat_line.address as usize ^ device.byte_swap)
|
||||
.unwrap_or(&mut 0) = (cheat_line.data >> 8) as u8;
|
||||
*device
|
||||
.rdram
|
||||
.mem
|
||||
.get_mut((cheat_line.address + 1) as usize ^ device.byte_swap)
|
||||
.unwrap_or(&mut 0) = cheat_line.data as u8;
|
||||
}
|
||||
|
||||
fn equal_byte(device: &mut device::Device, cheat_line: &DecodedCheat) -> bool {
|
||||
let current_value = *device
|
||||
.rdram
|
||||
.mem
|
||||
.get(cheat_line.address as usize ^ device.byte_swap)
|
||||
.unwrap_or(&0);
|
||||
current_value == (cheat_line.data as u8)
|
||||
}
|
||||
|
||||
fn equal_half(device: &mut device::Device, cheat_line: &DecodedCheat) -> bool {
|
||||
let current_value1 = *device
|
||||
.rdram
|
||||
.mem
|
||||
.get(cheat_line.address as usize ^ device.byte_swap)
|
||||
.unwrap_or(&0);
|
||||
let current_value2 = *device
|
||||
.rdram
|
||||
.mem
|
||||
.get((cheat_line.address + 1) as usize ^ device.byte_swap)
|
||||
.unwrap_or(&0);
|
||||
current_value1 == (cheat_line.data >> 8) as u8 && current_value2 == cheat_line.data as u8
|
||||
}
|
||||
|
||||
pub fn execute_cheats(device: &mut device::Device, cheats: Vec<Vec<DecodedCheat>>) {
|
||||
for cheat in cheats.iter() {
|
||||
let mut valid = true;
|
||||
let mut cheat_iter = cheat.iter().peekable();
|
||||
while let Some(cheat_line) = cheat_iter.next() {
|
||||
match cheat_line.code_type {
|
||||
0x50 => {
|
||||
if valid {
|
||||
let mut expanded_cheat: Vec<DecodedCheat> = vec![];
|
||||
let compressed_cheat = *cheat_iter.peek().unwrap();
|
||||
let count = (cheat_line.address & 0xFF00) >> 8;
|
||||
let offset = cheat_line.address & 0xFF;
|
||||
for i in 0..count {
|
||||
let line = DecodedCheat {
|
||||
code_type: compressed_cheat.code_type,
|
||||
address: compressed_cheat.address + (i * offset),
|
||||
data: compressed_cheat.data
|
||||
+ (i as i16 * cheat_line.data as i16) as u16,
|
||||
};
|
||||
expanded_cheat.push(line);
|
||||
}
|
||||
execute_cheats(device, [expanded_cheat].to_vec());
|
||||
}
|
||||
valid = false; // skip the next line
|
||||
}
|
||||
0x80 | 0xA0 => {
|
||||
if valid {
|
||||
write_byte(device, cheat_line);
|
||||
}
|
||||
valid = true;
|
||||
}
|
||||
0x81 | 0xA1 => {
|
||||
if valid {
|
||||
write_half(device, cheat_line);
|
||||
}
|
||||
valid = true;
|
||||
}
|
||||
0xF0 => {
|
||||
if device.cheats.boot && valid {
|
||||
write_byte(device, cheat_line);
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
0xF1 => {
|
||||
if device.cheats.boot && valid {
|
||||
write_half(device, cheat_line);
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
0xD0 => {
|
||||
valid = equal_byte(device, cheat_line);
|
||||
}
|
||||
0xD1 => {
|
||||
valid = equal_half(device, cheat_line);
|
||||
}
|
||||
0xD2 => {
|
||||
valid = !equal_byte(device, cheat_line);
|
||||
}
|
||||
0xD3 => {
|
||||
valid = !equal_half(device, cheat_line);
|
||||
}
|
||||
0x88 | 0x89 | 0xD8 | 0xD9 | 0xDA | 0xDB => { /* GS button not emulated */ }
|
||||
_ => panic!("Unknown cheat code type: {:X}", cheat_line.code_type),
|
||||
}
|
||||
}
|
||||
}
|
||||
device.cheats.boot = false;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "src/compat/sse2neon/v1.8.0/sse2neon.h"
|
||||
#include "src/compat/sse2neon/sse2neon.h"
|
||||
|
||||
// Static wrappers
|
||||
|
||||
|
||||
@@ -10,16 +10,14 @@ include!(concat!(env!("OUT_DIR"), "/simd_bindings.rs"));
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use std::arch::x86_64::*;
|
||||
|
||||
use crate::netplay;
|
||||
use crate::ui;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::io::Read;
|
||||
use rand::{Rng, SeedableRng};
|
||||
|
||||
use crate::{cheats, netplay, retroachievements, ui};
|
||||
use std::{collections::HashMap, fs, io::Read};
|
||||
|
||||
pub mod ai;
|
||||
pub mod cache;
|
||||
pub mod cart;
|
||||
pub mod cart_rom;
|
||||
pub mod controller;
|
||||
pub mod cop0;
|
||||
pub mod cop1;
|
||||
@@ -42,49 +40,84 @@ pub mod rsp_interface;
|
||||
pub mod rsp_su_instructions;
|
||||
pub mod rsp_vu_instructions;
|
||||
pub mod si;
|
||||
pub mod sram;
|
||||
pub mod tlb;
|
||||
pub mod unmapped;
|
||||
pub mod vi;
|
||||
|
||||
pub fn run_game(rom_contents: Vec<u8>, device: &mut Device, fullscreen: bool) {
|
||||
cart_rom::init(device, rom_contents); // cart needs to come before rdram
|
||||
pub fn run_game(device: &mut Device, rom_contents: &[u8], game_settings: ui::GameSettings) {
|
||||
device.cpu.overclock = game_settings.overclock;
|
||||
if game_settings.disable_expansion_pak {
|
||||
device.rdram.size = 0x400000;
|
||||
}
|
||||
if let Some(slot) = game_settings.load_savestate_slot {
|
||||
device.ui.storage.save_state_slot = slot;
|
||||
device.load_state = true;
|
||||
}
|
||||
|
||||
// rdram pointer is shared with parallel-rdp
|
||||
init_rng(device);
|
||||
|
||||
cart::rom::init(device, rom_contents); // cart needs to come before rdram
|
||||
|
||||
// rdram pointer is shared with parallel-rdp and retroachievements
|
||||
rdram::init(device);
|
||||
|
||||
ui::audio::init(&mut device.ui, 33600);
|
||||
ui::video::init(device, fullscreen);
|
||||
ui::video::init(device);
|
||||
ui::audio::init(&mut device.ui, device.ai.freq);
|
||||
ui::input::init(&mut device.ui);
|
||||
|
||||
// must be after video init
|
||||
retroachievements::welcome();
|
||||
|
||||
mi::init(device);
|
||||
pif::init(device);
|
||||
if device.ui.config.input.emulate_vru {
|
||||
if device.ui.config.input.emulate_vru && device.netplay.is_none() {
|
||||
controller::vru::init(device);
|
||||
}
|
||||
memory::init(device);
|
||||
cache::init(device);
|
||||
rsp_interface::init(device);
|
||||
rdp::init(device);
|
||||
vi::init(device);
|
||||
cpu::init(device);
|
||||
|
||||
ui::storage::init(&mut device.ui);
|
||||
ui::storage::init(&mut device.ui, &device.cart.rom);
|
||||
ui::storage::load_saves(&mut device.ui, &mut device.netplay);
|
||||
cart_rom::load_rom_save(device);
|
||||
|
||||
if !retroachievements::get_hardcore() {
|
||||
cheats::init(device, game_settings.cheats);
|
||||
}
|
||||
|
||||
cpu::run(device);
|
||||
|
||||
retroachievements::unload_game();
|
||||
|
||||
ui::storage::write_saves(device);
|
||||
ui::input::close(&mut device.ui);
|
||||
ui::video::close(&device.ui);
|
||||
ui::audio::close(&mut device.ui);
|
||||
ui::storage::write_saves(&device.ui, &device.netplay);
|
||||
ui::video::close(&device.ui);
|
||||
}
|
||||
|
||||
fn swap_rom(contents: Vec<u8>) -> Vec<u8> {
|
||||
fn set_rng() -> rand::rngs::Xoshiro256PlusPlus {
|
||||
rand::rngs::Xoshiro256PlusPlus::try_from_rng(&mut rand::rngs::SysRng).unwrap()
|
||||
}
|
||||
|
||||
fn init_rng(device: &mut Device) {
|
||||
let mut rng_seed = set_rng().next_u64();
|
||||
if let Some(netplay) = &mut device.netplay {
|
||||
if netplay.player_number == 0 {
|
||||
netplay::send_rng(netplay, rng_seed);
|
||||
} else {
|
||||
rng_seed = netplay::receive_rng(netplay);
|
||||
}
|
||||
}
|
||||
device.rng = rand::rngs::Xoshiro256PlusPlus::seed_from_u64(rng_seed);
|
||||
}
|
||||
|
||||
fn swap_rom(contents: Vec<u8>) -> Option<Vec<u8>> {
|
||||
let test = u32::from_be_bytes(contents[0..4].try_into().unwrap());
|
||||
if test == 0x80371240 {
|
||||
// z64
|
||||
contents
|
||||
Some(contents)
|
||||
} else if test == 0x37804012 {
|
||||
// v64
|
||||
let mut data: Vec<u8> = vec![0; contents.len()];
|
||||
@@ -92,7 +125,7 @@ fn swap_rom(contents: Vec<u8>) -> Vec<u8> {
|
||||
let temp = u16::from_ne_bytes(contents[i..i + 2].try_into().unwrap());
|
||||
data[i..i + 2].copy_from_slice(&temp.to_be_bytes());
|
||||
}
|
||||
return data;
|
||||
Some(data)
|
||||
} else if test == 0x40123780 {
|
||||
// n64
|
||||
let mut data: Vec<u8> = vec![0; contents.len()];
|
||||
@@ -100,16 +133,19 @@ fn swap_rom(contents: Vec<u8>) -> Vec<u8> {
|
||||
let temp = u32::from_ne_bytes(contents[i..i + 4].try_into().unwrap());
|
||||
data[i..i + 4].copy_from_slice(&temp.to_be_bytes());
|
||||
}
|
||||
return data;
|
||||
Some(data)
|
||||
} else {
|
||||
let data: Vec<u8> = vec![];
|
||||
data
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
|
||||
pub fn get_rom_contents(file_path: &std::path::Path) -> Option<Vec<u8>> {
|
||||
let mut contents = vec![];
|
||||
if file_path.extension().unwrap().eq_ignore_ascii_case("zip") {
|
||||
if file_path
|
||||
.extension()
|
||||
.unwrap_or_default()
|
||||
.eq_ignore_ascii_case("zip")
|
||||
{
|
||||
let zip_file = fs::File::open(file_path).unwrap();
|
||||
let mut archive = zip::ZipArchive::new(zip_file).unwrap();
|
||||
for i in 0..archive.len() {
|
||||
@@ -126,14 +162,18 @@ pub fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if file_path.extension().unwrap().eq_ignore_ascii_case("7z") {
|
||||
} else if file_path
|
||||
.extension()
|
||||
.unwrap_or_default()
|
||||
.eq_ignore_ascii_case("7z")
|
||||
{
|
||||
let mut archive =
|
||||
sevenz_rust::SevenZReader::open(file_path, sevenz_rust::Password::empty()).unwrap();
|
||||
sevenz_rust2::ArchiveReader::open(file_path, sevenz_rust2::Password::empty()).unwrap();
|
||||
|
||||
let mut found = false;
|
||||
archive
|
||||
.for_each_entries(
|
||||
&mut |entry: &sevenz_rust::SevenZArchiveEntry, reader: &mut dyn std::io::Read| {
|
||||
&mut |entry: &sevenz_rust2::ArchiveEntry, reader: &mut dyn std::io::Read| {
|
||||
let name = entry.name().to_ascii_lowercase();
|
||||
if !found
|
||||
&& (name.ends_with("z64") || name.ends_with("n64") || name.ends_with("v64"))
|
||||
@@ -151,31 +191,48 @@ pub fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
|
||||
)
|
||||
.expect("ok");
|
||||
} else {
|
||||
contents = fs::read(file_path).expect("Should have been able to read the file");
|
||||
contents = fs::read(file_path)
|
||||
.unwrap_or_else(|_| panic!("Could not read ROM file: {}", file_path.display()));
|
||||
}
|
||||
|
||||
swap_rom(contents)
|
||||
if contents.is_empty() {
|
||||
None
|
||||
} else {
|
||||
swap_rom(contents)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Device {
|
||||
#[serde(skip)]
|
||||
pub netplay: Option<netplay::Netplay>,
|
||||
#[serde(skip, default = "ui::Ui::default")]
|
||||
pub ui: ui::Ui,
|
||||
byte_swap: usize,
|
||||
pub byte_swap: usize,
|
||||
pub save_state: bool,
|
||||
pub load_state: bool,
|
||||
pub cpu: cpu::Cpu,
|
||||
pif: pif::Pif,
|
||||
cart: cart_rom::Cart,
|
||||
memory: memory::Memory,
|
||||
pub pif: pif::Pif,
|
||||
pub cart: cart::Cart,
|
||||
pub memory: memory::Memory,
|
||||
pub rsp: rsp_interface::Rsp,
|
||||
pub rdp: rdp::Rdp,
|
||||
pub rdram: rdram::Rdram,
|
||||
mi: mi::Mi,
|
||||
pi: pi::Pi,
|
||||
pub mi: mi::Mi,
|
||||
pub pi: pi::Pi,
|
||||
pub vi: vi::Vi,
|
||||
ai: ai::Ai,
|
||||
si: si::Si,
|
||||
ri: ri::Ri,
|
||||
flashram: sram::Flashram,
|
||||
pub ai: ai::Ai,
|
||||
pub si: si::Si,
|
||||
pub ri: ri::Ri,
|
||||
#[serde(skip, default = "set_rng")]
|
||||
pub rng: rand::rngs::Xoshiro256PlusPlus,
|
||||
pub vru: controller::vru::Vru,
|
||||
pub transferpaks: [controller::transferpak::TransferPak; 4],
|
||||
pub cheats: cheats::Cheats,
|
||||
}
|
||||
|
||||
pub fn zero_m128i() -> __m128i {
|
||||
unsafe { _mm_setzero_si128() }
|
||||
}
|
||||
|
||||
impl Device {
|
||||
@@ -191,11 +248,14 @@ impl Device {
|
||||
netplay: None,
|
||||
ui: ui::Ui::new(),
|
||||
byte_swap,
|
||||
save_state: false,
|
||||
load_state: false,
|
||||
cpu: cpu::Cpu {
|
||||
cop0: cop0::Cop0 {
|
||||
regs: [0; cop0::COP0_REGS_COUNT as usize],
|
||||
reg_write_masks: [0; cop0::COP0_REGS_COUNT as usize],
|
||||
reg_latch: 0,
|
||||
is_event: false,
|
||||
instrs: [cop0::reserved; 32],
|
||||
instrs2: [cop0::reserved; 32],
|
||||
tlb_lut_w: vec![
|
||||
@@ -261,6 +321,7 @@ impl Device {
|
||||
pc: 0xBFC00000,
|
||||
pc_phys: 0,
|
||||
llbit: false,
|
||||
overclock: false,
|
||||
lo: 0,
|
||||
hi: 0,
|
||||
running: false,
|
||||
@@ -270,10 +331,9 @@ impl Device {
|
||||
events: [events::Event {
|
||||
enabled: false,
|
||||
count: u64::MAX,
|
||||
handler: events::dummy_event,
|
||||
}; events::EventType::Count as usize],
|
||||
}; events::EVENT_TYPE_COUNT],
|
||||
next_event_count: u64::MAX,
|
||||
next_event: 0,
|
||||
next_event: events::EVENT_TYPE_NONE,
|
||||
},
|
||||
pif: pif::Pif {
|
||||
rom: [0; 1984],
|
||||
@@ -288,18 +348,33 @@ impl Device {
|
||||
change_pak: controller::PakType::None,
|
||||
}; 5],
|
||||
},
|
||||
cart: cart_rom::Cart {
|
||||
cart: cart::Cart {
|
||||
rom: Vec::new(),
|
||||
is_viewer_buffer: [0; 0xFFFF],
|
||||
is_viewer_buffer: vec![0; 0xFFFF],
|
||||
pal: false,
|
||||
latch: 0,
|
||||
cic_seed: 0,
|
||||
cic_type: cart_rom::CicType::CicNus6102,
|
||||
rdram_size_offset: 0,
|
||||
rtc_timestamp: 0,
|
||||
rtc: cart::AfRtc { control: 0x0200 },
|
||||
sc64: cart::sc64::Sc64 {
|
||||
regs: [0; cart::sc64::SC64_REGS_COUNT as usize],
|
||||
regs_locked: true,
|
||||
cfg: [0; cart::sc64::SC64_CFG_COUNT as usize],
|
||||
sector: 0,
|
||||
buffer: vec![0; 8192],
|
||||
writeback_sector: vec![0; 256],
|
||||
usb_buffer: vec![],
|
||||
},
|
||||
flashram: cart::sram::Flashram {
|
||||
status: 0,
|
||||
erase_page: 0,
|
||||
page_buf: [0xff; 128],
|
||||
silicon_id: [cart::sram::FLASHRAM_TYPE_ID, cart::sram::MX29L1100_ID],
|
||||
mode: cart::sram::FlashramMode::ReadArray,
|
||||
},
|
||||
},
|
||||
memory: memory::Memory {
|
||||
fast_read: [unmapped::read_mem; 0x2000],
|
||||
fast_read: [unmapped::read_mem_fast; 0x2000],
|
||||
memory_map_read: [unmapped::read_mem; 0x2000],
|
||||
memory_map_write: [unmapped::write_mem; 0x2000],
|
||||
icache: [cache::ICache {
|
||||
@@ -336,6 +411,7 @@ impl Device {
|
||||
pc: 0,
|
||||
},
|
||||
broken: false,
|
||||
running: false,
|
||||
halted: false,
|
||||
sync_point: false,
|
||||
cycle_counter: 0,
|
||||
@@ -367,6 +443,8 @@ impl Device {
|
||||
regs: [0; rsp_interface::SP_REGS_COUNT as usize],
|
||||
regs2: [0; rsp_interface::SP_REGS2_COUNT as usize],
|
||||
mem: [0; 0x2000],
|
||||
last_status_value: 0,
|
||||
run_after_dma: false,
|
||||
fifo: [rsp_interface::RspDma {
|
||||
dir: rsp_interface::DmaDir::None,
|
||||
length: 0,
|
||||
@@ -378,6 +456,7 @@ impl Device {
|
||||
regs_dpc: [0; rdp::DPC_REGS_COUNT as usize],
|
||||
regs_dps: [0; rdp::DPS_REGS_COUNT as usize],
|
||||
wait_frozen: false,
|
||||
last_status_value: 0,
|
||||
},
|
||||
mi: mi::Mi {
|
||||
regs: [0; mi::MI_REGS_COUNT as usize],
|
||||
@@ -388,6 +467,7 @@ impl Device {
|
||||
ai: ai::Ai {
|
||||
regs: [0; ai::AI_REGS_COUNT as usize],
|
||||
last_read: 0,
|
||||
freq: 33600,
|
||||
delayed_carry: false,
|
||||
fifo: [ai::AiDma {
|
||||
address: 0,
|
||||
@@ -409,15 +489,13 @@ impl Device {
|
||||
field: 0,
|
||||
delay: 0,
|
||||
count_per_scanline: 0,
|
||||
limiter: None,
|
||||
enable_speed_limiter: true,
|
||||
next_pace_deadline: None,
|
||||
vi_counter: 0,
|
||||
},
|
||||
flashram: sram::Flashram {
|
||||
status: 0,
|
||||
erase_page: 0,
|
||||
page_buf: [0xff; 128],
|
||||
silicon_id: [sram::FLASHRAM_TYPE_ID, sram::MX29L1100_ID],
|
||||
mode: sram::FlashramMode::ReadArray,
|
||||
min_wait_time: std::time::Duration::from_secs(1),
|
||||
frame_time: 0.0,
|
||||
limit_freq: 2,
|
||||
limit_freq_check: std::time::Instant::now(),
|
||||
},
|
||||
vru: controller::vru::Vru {
|
||||
status: 0,
|
||||
@@ -428,9 +506,18 @@ impl Device {
|
||||
words: Vec::new(),
|
||||
talking: false,
|
||||
word_mappings: HashMap::new(),
|
||||
window_notifier: None,
|
||||
word_receiver: None,
|
||||
gui_ctx: None,
|
||||
},
|
||||
rng: set_rng(),
|
||||
transferpaks: [
|
||||
controller::transferpak::TransferPak::default(),
|
||||
controller::transferpak::TransferPak::default(),
|
||||
controller::transferpak::TransferPak::default(),
|
||||
controller::transferpak::TransferPak::default(),
|
||||
],
|
||||
cheats: cheats::Cheats {
|
||||
cheats: vec![],
|
||||
boot: true,
|
||||
enabled: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
use crate::{device, ui};
|
||||
|
||||
const AI_DRAM_ADDR_REG: u32 = 0;
|
||||
const AI_LEN_REG: u32 = 1;
|
||||
@@ -12,26 +11,28 @@ pub const AI_REGS_COUNT: u32 = 6;
|
||||
const AI_STATUS_BUSY: u32 = 0x40000000;
|
||||
const AI_STATUS_FULL: u32 = 0x80000000;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Ai {
|
||||
pub regs: [u32; AI_REGS_COUNT as usize],
|
||||
pub fifo: [AiDma; 2],
|
||||
pub last_read: u64,
|
||||
pub delayed_carry: bool,
|
||||
pub freq: u64,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct AiDma {
|
||||
pub address: u64,
|
||||
pub length: u64,
|
||||
pub duration: u64,
|
||||
}
|
||||
|
||||
fn get_remaining_dma_length(device: &mut device::Device) -> u64 {
|
||||
fn get_remaining_dma_length(device: &device::Device) -> u64 {
|
||||
if device.ai.fifo[0].duration == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let next_ai_event = device::events::get_event(device, device::events::EventType::AI);
|
||||
let next_ai_event = device::events::get_event(device, device::events::EVENT_TYPE_AI);
|
||||
if next_ai_event.is_none() {
|
||||
return 0;
|
||||
}
|
||||
@@ -44,11 +45,10 @@ fn get_remaining_dma_length(device: &mut device::Device) -> u64 {
|
||||
}
|
||||
|
||||
fn get_dma_duration(device: &device::Device) -> u64 {
|
||||
let samples_per_sec = device.vi.clock / (1 + device.ai.regs[AI_DACRATE_REG as usize]) as u64;
|
||||
let bytes_per_sample = 4; /* XXX: assume 16bit stereo - should depends on bitrate instead */
|
||||
let length = (device.ai.regs[AI_LEN_REG as usize] & !7) as u64;
|
||||
|
||||
length * (device.cpu.clock_rate / (bytes_per_sample * samples_per_sec))
|
||||
(length * device.cpu.clock_rate) / (bytes_per_sample * device.ai.freq)
|
||||
}
|
||||
|
||||
fn do_dma(device: &mut device::Device) {
|
||||
@@ -64,11 +64,10 @@ fn do_dma(device: &mut device::Device) {
|
||||
/* schedule end of dma event */
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::AI,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + device.ai.fifo[0].duration,
|
||||
dma_event,
|
||||
device::events::EVENT_TYPE_AI,
|
||||
device.ai.fifo[0].duration,
|
||||
);
|
||||
device::mi::schedule_rcp_interrupt(device, device::mi::MI_INTR_AI);
|
||||
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_AI);
|
||||
}
|
||||
|
||||
fn fifo_push(device: &mut device::Device) {
|
||||
@@ -111,7 +110,7 @@ pub fn read_regs(
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
let return_value = match reg as u32 {
|
||||
AI_LEN_REG => {
|
||||
let value = get_remaining_dma_length(device);
|
||||
if value < device.ai.last_read {
|
||||
@@ -128,7 +127,9 @@ pub fn read_regs(
|
||||
value as u32
|
||||
}
|
||||
_ => device.ai.regs[reg as usize],
|
||||
}
|
||||
};
|
||||
device::cop0::add_cycles(device, 20);
|
||||
return_value
|
||||
}
|
||||
|
||||
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
@@ -143,9 +144,9 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
AI_STATUS_REG => device::mi::clear_rcp_interrupt(device, device::mi::MI_INTR_AI),
|
||||
AI_DACRATE_REG => {
|
||||
if device.ai.regs[reg as usize] != value & mask {
|
||||
let frequency = device.vi.clock / (1 + (value & mask)) as u64;
|
||||
ui::audio::close(&mut device.ui);
|
||||
ui::audio::init(&mut device.ui, frequency)
|
||||
device.ai.freq = device.vi.clock / (1 + (value & mask)) as u64;
|
||||
ui::audio::close_game_audio(&mut device.ui);
|
||||
ui::audio::init_game_audio(&mut device.ui, device.ai.freq)
|
||||
}
|
||||
device::memory::masked_write_32(&mut device.ai.regs[reg as usize], value, mask)
|
||||
}
|
||||
@@ -153,7 +154,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
}
|
||||
}
|
||||
|
||||
fn dma_event(device: &mut device::Device) {
|
||||
pub fn dma_event(device: &mut device::Device) {
|
||||
if device.ai.last_read != 0 {
|
||||
let diff = device.ai.fifo[0].length - device.ai.last_read;
|
||||
ui::audio::play_audio(
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
use crate::device;
|
||||
use crate::{device, savestates};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ICache {
|
||||
pub valid: bool,
|
||||
pub tag: u32,
|
||||
pub index: u16,
|
||||
pub words: [u32; 8],
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub instruction: [fn(&mut device::Device, u32); 8],
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DCache {
|
||||
pub valid: bool,
|
||||
pub dirty: bool,
|
||||
@@ -29,54 +30,14 @@ pub fn icache_writeback(device: &mut device::Device, line_index: usize) {
|
||||
let cache_address = ((device.memory.icache[line_index].tag
|
||||
| (device.memory.icache[line_index].index) as u32)
|
||||
& 0x1ffffffc) as u64;
|
||||
device.memory.memory_map_write[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address,
|
||||
device.memory.icache[line_index].words[0],
|
||||
0xFFFFFFFF,
|
||||
);
|
||||
device.memory.memory_map_write[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | 0x4,
|
||||
device.memory.icache[line_index].words[1],
|
||||
0xFFFFFFFF,
|
||||
);
|
||||
device.memory.memory_map_write[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | 0x8,
|
||||
device.memory.icache[line_index].words[2],
|
||||
0xFFFFFFFF,
|
||||
);
|
||||
device.memory.memory_map_write[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | 0xC,
|
||||
device.memory.icache[line_index].words[3],
|
||||
0xFFFFFFFF,
|
||||
);
|
||||
device.memory.memory_map_write[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | 0x10,
|
||||
device.memory.icache[line_index].words[4],
|
||||
0xFFFFFFFF,
|
||||
);
|
||||
device.memory.memory_map_write[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | 0x14,
|
||||
device.memory.icache[line_index].words[5],
|
||||
0xFFFFFFFF,
|
||||
);
|
||||
device.memory.memory_map_write[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | 0x18,
|
||||
device.memory.icache[line_index].words[6],
|
||||
0xFFFFFFFF,
|
||||
);
|
||||
device.memory.memory_map_write[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | 0x1C,
|
||||
device.memory.icache[line_index].words[7],
|
||||
0xFFFFFFFF,
|
||||
);
|
||||
for i in 0..8 {
|
||||
device.memory.memory_map_write[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | (i * 4),
|
||||
device.memory.icache[line_index].words[i as usize],
|
||||
0xFFFFFFFF,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icache_fill(device: &mut device::Device, line_index: usize, phys_address: u64) {
|
||||
@@ -87,71 +48,17 @@ pub fn icache_fill(device: &mut device::Device, line_index: usize, phys_address:
|
||||
let cache_address = ((device.memory.icache[line_index].tag
|
||||
| (device.memory.icache[line_index].index) as u32)
|
||||
& 0x1ffffffc) as u64;
|
||||
device.memory.icache[line_index].words[0] = device.memory.memory_map_read
|
||||
[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address,
|
||||
device::memory::AccessSize::Icache,
|
||||
);
|
||||
device.memory.icache[line_index].words[1] = device.memory.memory_map_read
|
||||
[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | 0x4,
|
||||
device::memory::AccessSize::Icache,
|
||||
);
|
||||
device.memory.icache[line_index].words[2] = device.memory.memory_map_read
|
||||
[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | 0x8,
|
||||
device::memory::AccessSize::Icache,
|
||||
);
|
||||
device.memory.icache[line_index].words[3] = device.memory.memory_map_read
|
||||
[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | 0xC,
|
||||
device::memory::AccessSize::Icache,
|
||||
);
|
||||
device.memory.icache[line_index].words[4] = device.memory.memory_map_read
|
||||
[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | 0x10,
|
||||
device::memory::AccessSize::Icache,
|
||||
);
|
||||
device.memory.icache[line_index].words[5] = device.memory.memory_map_read
|
||||
[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | 0x14,
|
||||
device::memory::AccessSize::Icache,
|
||||
);
|
||||
device.memory.icache[line_index].words[6] = device.memory.memory_map_read
|
||||
[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | 0x18,
|
||||
device::memory::AccessSize::Icache,
|
||||
);
|
||||
device.memory.icache[line_index].words[7] = device.memory.memory_map_read
|
||||
[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | 0x1C,
|
||||
device::memory::AccessSize::Icache,
|
||||
);
|
||||
for i in 0..8 {
|
||||
device.memory.icache[line_index].words[i as usize] = device.memory.memory_map_read
|
||||
[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | (i * 4),
|
||||
device::memory::AccessSize::Icache,
|
||||
);
|
||||
|
||||
device.memory.icache[line_index].instruction[0] =
|
||||
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[0]);
|
||||
device.memory.icache[line_index].instruction[1] =
|
||||
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[1]);
|
||||
device.memory.icache[line_index].instruction[2] =
|
||||
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[2]);
|
||||
device.memory.icache[line_index].instruction[3] =
|
||||
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[3]);
|
||||
device.memory.icache[line_index].instruction[4] =
|
||||
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[4]);
|
||||
device.memory.icache[line_index].instruction[5] =
|
||||
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[5]);
|
||||
device.memory.icache[line_index].instruction[6] =
|
||||
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[6]);
|
||||
device.memory.icache[line_index].instruction[7] =
|
||||
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[7]);
|
||||
device.memory.icache[line_index].instruction[i as usize] =
|
||||
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[i as usize]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icache_fetch(device: &mut device::Device, phys_address: u64) {
|
||||
@@ -179,30 +86,15 @@ pub fn dcache_writeback(device: &mut device::Device, line_index: usize) {
|
||||
let cache_address = ((device.memory.dcache[line_index].tag
|
||||
| (device.memory.dcache[line_index].index) as u32)
|
||||
& 0x1ffffffc) as u64;
|
||||
device.memory.memory_map_write[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address,
|
||||
device.memory.dcache[line_index].words[0],
|
||||
0xFFFFFFFF,
|
||||
);
|
||||
device.memory.memory_map_write[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | 0x4,
|
||||
device.memory.dcache[line_index].words[1],
|
||||
0xFFFFFFFF,
|
||||
);
|
||||
device.memory.memory_map_write[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | 0x8,
|
||||
device.memory.dcache[line_index].words[2],
|
||||
0xFFFFFFFF,
|
||||
);
|
||||
device.memory.memory_map_write[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | 0xC,
|
||||
device.memory.dcache[line_index].words[3],
|
||||
0xFFFFFFFF,
|
||||
);
|
||||
|
||||
for i in 0..4 {
|
||||
device.memory.memory_map_write[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | (i * 4),
|
||||
device.memory.dcache[line_index].words[i as usize],
|
||||
0xFFFFFFFF,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn dcache_fill(device: &mut device::Device, line_index: usize, phys_address: u64) {
|
||||
@@ -215,30 +107,15 @@ fn dcache_fill(device: &mut device::Device, line_index: usize, phys_address: u64
|
||||
let cache_address = ((device.memory.dcache[line_index].tag
|
||||
| (device.memory.dcache[line_index].index) as u32)
|
||||
& 0x1ffffffc) as u64;
|
||||
device.memory.dcache[line_index].words[0] = device.memory.memory_map_read
|
||||
[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address,
|
||||
device::memory::AccessSize::Dcache,
|
||||
);
|
||||
device.memory.dcache[line_index].words[1] = device.memory.memory_map_read
|
||||
[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | 0x4,
|
||||
device::memory::AccessSize::Dcache,
|
||||
);
|
||||
device.memory.dcache[line_index].words[2] = device.memory.memory_map_read
|
||||
[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | 0x8,
|
||||
device::memory::AccessSize::Dcache,
|
||||
);
|
||||
device.memory.dcache[line_index].words[3] = device.memory.memory_map_read
|
||||
[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | 0xC,
|
||||
device::memory::AccessSize::Dcache,
|
||||
);
|
||||
|
||||
for i in 0..4 {
|
||||
device.memory.dcache[line_index].words[i as usize] = device.memory.memory_map_read
|
||||
[(cache_address >> 16) as usize](
|
||||
device,
|
||||
cache_address | (i * 4),
|
||||
device::memory::AccessSize::Dcache,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dcache_read(device: &mut device::Device, phys_address: u64) -> u32 {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
use chrono::Datelike;
|
||||
use chrono::Timelike;
|
||||
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
use crate::{device, ui};
|
||||
|
||||
pub mod rom;
|
||||
pub mod sc64;
|
||||
pub mod sram;
|
||||
|
||||
const JCMD_STATUS: u8 = 0x00;
|
||||
const JCMD_EEPROM_READ: u8 = 0x04;
|
||||
@@ -18,6 +21,7 @@ const JDT_EEPROM_16K: u16 = 0xc000; /* 16k EEPROM */
|
||||
const EEPROM_BLOCK_SIZE: usize = 8;
|
||||
pub const EEPROM_MAX_SIZE: usize = 0x800;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct AfRtc {
|
||||
pub control: u16,
|
||||
}
|
||||
@@ -27,6 +31,20 @@ fn byte2bcd(mut n: u32) -> u8 {
|
||||
(((n / 10) << 4) | (n % 10)) as u8
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Cart {
|
||||
#[serde(skip)]
|
||||
pub rom: Vec<u8>,
|
||||
pub is_viewer_buffer: Vec<u8>,
|
||||
pub pal: bool,
|
||||
pub latch: u32,
|
||||
pub cic_seed: u8,
|
||||
pub rtc: AfRtc,
|
||||
pub sc64: sc64::Sc64,
|
||||
pub flashram: sram::Flashram,
|
||||
pub rtc_timestamp: i64,
|
||||
}
|
||||
|
||||
pub fn process(device: &mut device::Device, channel: usize) {
|
||||
let cmd = device.pif.ram[device.pif.channels[channel].tx_buf.unwrap()];
|
||||
|
||||
@@ -35,12 +53,14 @@ pub fn process(device: &mut device::Device, channel: usize) {
|
||||
let eeprom_type;
|
||||
if device
|
||||
.ui
|
||||
.storage
|
||||
.save_type
|
||||
.contains(&ui::storage::SaveTypes::Eeprom16k)
|
||||
{
|
||||
eeprom_type = JDT_EEPROM_16K;
|
||||
} else if device
|
||||
.ui
|
||||
.storage
|
||||
.save_type
|
||||
.contains(&ui::storage::SaveTypes::Eeprom4k)
|
||||
{
|
||||
@@ -95,9 +115,15 @@ pub fn process(device: &mut device::Device, channel: usize) {
|
||||
}
|
||||
}
|
||||
|
||||
fn format_eeprom(device: &mut device::Device) {
|
||||
if device.ui.saves.eeprom.0.len() < EEPROM_MAX_SIZE {
|
||||
device.ui.saves.eeprom.0.resize(EEPROM_MAX_SIZE, 0xFF)
|
||||
pub fn format_eeprom(device: &mut device::Device) {
|
||||
if device.ui.storage.saves.eeprom.data.len() < EEPROM_MAX_SIZE {
|
||||
device
|
||||
.ui
|
||||
.storage
|
||||
.saves
|
||||
.eeprom
|
||||
.data
|
||||
.resize(EEPROM_MAX_SIZE, 0xFF)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,8 +132,9 @@ fn eeprom_read_block(device: &mut device::Device, block: usize, offset: usize) {
|
||||
|
||||
format_eeprom(device);
|
||||
|
||||
device.pif.ram[offset..offset + EEPROM_BLOCK_SIZE]
|
||||
.copy_from_slice(&device.ui.saves.eeprom.0[address..address + EEPROM_BLOCK_SIZE]);
|
||||
device.pif.ram[offset..offset + EEPROM_BLOCK_SIZE].copy_from_slice(
|
||||
&device.ui.storage.saves.eeprom.data[address..address + EEPROM_BLOCK_SIZE],
|
||||
);
|
||||
}
|
||||
|
||||
fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
|
||||
@@ -115,16 +142,18 @@ fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usize,
|
||||
|
||||
format_eeprom(device);
|
||||
|
||||
device.ui.saves.eeprom.0[address..address + EEPROM_BLOCK_SIZE]
|
||||
device.ui.storage.saves.eeprom.data[address..address + EEPROM_BLOCK_SIZE]
|
||||
.copy_from_slice(&device.pif.ram[offset..offset + EEPROM_BLOCK_SIZE]);
|
||||
|
||||
device.pif.ram[status] = 0x00;
|
||||
|
||||
device.ui.saves.eeprom.1 = true
|
||||
ui::storage::schedule_save(device, ui::storage::SaveTypes::Eeprom4k);
|
||||
}
|
||||
|
||||
fn time2data(device: &mut device::Device, offset: usize) {
|
||||
let now: chrono::DateTime<chrono::Local> = chrono::Local::now();
|
||||
let timestamp =
|
||||
device.cart.rtc_timestamp + (device.vi.frame_time * device.vi.vi_counter as f64) as i64;
|
||||
let now = chrono::DateTime::from_timestamp(timestamp, 0).unwrap();
|
||||
|
||||
device.pif.ram[offset] = byte2bcd(now.second());
|
||||
device.pif.ram[offset + 1] = byte2bcd(now.minute());
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
use crate::device;
|
||||
use crate::netplay;
|
||||
use crate::ui;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
pub const CART_MASK: usize = 0xFFFFFFF;
|
||||
|
||||
fn read_cart_word(device: &device::Device, address: usize) -> u32 {
|
||||
let mut data: [u8; 4] = [0; 4];
|
||||
for i in 0..4 {
|
||||
if let Some(value) = device
|
||||
.ui
|
||||
.storage
|
||||
.saves
|
||||
.romsave
|
||||
.data
|
||||
.get(&(address as u32 + i))
|
||||
{
|
||||
data[i as usize] = *value;
|
||||
} else {
|
||||
data[i as usize] = *device.cart.rom.get(address + i as usize).unwrap_or(&0);
|
||||
}
|
||||
}
|
||||
u32::from_be_bytes(data)
|
||||
}
|
||||
|
||||
pub fn read_mem_fast(
|
||||
device: &device::Device,
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
let masked_address = address as usize & CART_MASK;
|
||||
read_cart_word(device, masked_address)
|
||||
}
|
||||
|
||||
pub fn read_mem(
|
||||
device: &mut device::Device,
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
let cycles = device::pi::calculate_cycles(device, 1, 4);
|
||||
device::cop0::add_cycles(device, cycles);
|
||||
|
||||
// well known cart ROM oddity, if a read is perfomed while PI_STATUS_IO_BUSY is set, the latched value is returned rather than the data at the specified address
|
||||
if device.pi.regs[device::pi::PI_STATUS_REG as usize] & device::pi::PI_STATUS_IO_BUSY != 0 {
|
||||
device.cart.latch
|
||||
} else {
|
||||
let masked_address = address as usize & CART_MASK;
|
||||
read_cart_word(device, masked_address)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
if device.cart.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] != 0 {
|
||||
let masked_address = address as usize & CART_MASK;
|
||||
let mut data = read_cart_word(device, masked_address);
|
||||
device::memory::masked_write_32(&mut data, value, mask);
|
||||
for (i, item) in data.to_be_bytes().iter().enumerate() {
|
||||
device
|
||||
.ui
|
||||
.storage
|
||||
.saves
|
||||
.romsave
|
||||
.data
|
||||
.insert((masked_address + i) as u32, *item);
|
||||
}
|
||||
ui::storage::schedule_save(device, ui::storage::SaveTypes::Romsave);
|
||||
}
|
||||
|
||||
device.cart.latch = value & mask;
|
||||
|
||||
device.pi.regs[device::pi::PI_STATUS_REG as usize] |= device::pi::PI_STATUS_IO_BUSY;
|
||||
|
||||
let cycles = device::pi::calculate_cycles(device, 1, 4);
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_PI, cycles);
|
||||
}
|
||||
|
||||
pub fn dma_read(
|
||||
device: &mut device::Device,
|
||||
mut cart_addr: u32,
|
||||
mut dram_addr: u32,
|
||||
length: u32,
|
||||
) -> u64 {
|
||||
if device.cart.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] != 0 {
|
||||
dram_addr &= device::rdram::RDRAM_MASK as u32;
|
||||
cart_addr &= CART_MASK as u32;
|
||||
|
||||
for i in 0..length {
|
||||
device.ui.storage.saves.romsave.data.insert(
|
||||
cart_addr + i,
|
||||
*device
|
||||
.rdram
|
||||
.mem
|
||||
.get((dram_addr + i) as usize ^ device.byte_swap)
|
||||
.unwrap_or(&0),
|
||||
);
|
||||
}
|
||||
ui::storage::schedule_save(device, ui::storage::SaveTypes::Romsave);
|
||||
}
|
||||
|
||||
device::pi::calculate_cycles(device, 1, length)
|
||||
}
|
||||
|
||||
// cart is big endian, rdram is native endian
|
||||
pub fn dma_write(
|
||||
device: &mut device::Device,
|
||||
mut cart_addr: u32,
|
||||
mut dram_addr: u32,
|
||||
length: u32,
|
||||
) -> u64 {
|
||||
dram_addr &= device::rdram::RDRAM_MASK as u32;
|
||||
cart_addr &= CART_MASK as u32;
|
||||
let mut i = dram_addr;
|
||||
let mut j = cart_addr;
|
||||
while i < dram_addr + length {
|
||||
if let Some(value) = device.ui.storage.saves.romsave.data.get(&j) {
|
||||
*device
|
||||
.rdram
|
||||
.mem
|
||||
.get_mut(i as usize ^ device.byte_swap)
|
||||
.unwrap_or(&mut 0) = *value;
|
||||
} else {
|
||||
*device
|
||||
.rdram
|
||||
.mem
|
||||
.get_mut(i as usize ^ device.byte_swap)
|
||||
.unwrap_or(&mut 0) = *device.cart.rom.get(j as usize).unwrap_or(&0);
|
||||
}
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
|
||||
device::pi::calculate_cycles(device, 1, length)
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device, rom_file: &[u8]) {
|
||||
let now: chrono::DateTime<chrono::Local> = chrono::Local::now();
|
||||
device.cart.rtc_timestamp = now.naive_local().and_utc().timestamp();
|
||||
if let Some(netplay) = &mut device.netplay {
|
||||
if netplay.player_number == 0 {
|
||||
netplay::send_rtc(netplay, device.cart.rtc_timestamp);
|
||||
} else {
|
||||
device.cart.rtc_timestamp = netplay::receive_rtc(netplay);
|
||||
}
|
||||
}
|
||||
|
||||
device.cart.sc64.cfg[device::cart::sc64::SC64_BOOTLOADER_SWITCH as usize] = 1;
|
||||
|
||||
device.cart.rom = rom_file.to_vec();
|
||||
device.cart.pal = is_system_pal(device.cart.rom[0x3E]);
|
||||
set_cic(device);
|
||||
|
||||
device.ui.game_hash = calculate_hash(&device.cart.rom);
|
||||
|
||||
device.ui.game_id = String::from_utf8(device.cart.rom[0x3B..0x3E].to_vec()).unwrap();
|
||||
if device.ui.game_id.contains('\0') {
|
||||
device.ui.game_id = String::from("UNK");
|
||||
}
|
||||
}
|
||||
|
||||
fn is_system_pal(country: u8) -> bool {
|
||||
let pal_codes: [u8; 8] = [b'D', b'F', b'I', b'P', b'S', b'U', b'X', b'Y'];
|
||||
for i in pal_codes {
|
||||
if country == i {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn set_cic(device: &mut device::Device) {
|
||||
let hash = calculate_hash(&device.cart.rom[0x40..0x1000]);
|
||||
match hash.as_str() {
|
||||
"B99F06C4802C2377E31E388435955EF3E99C618A6D55D24699D828EB1075F1EB" => {
|
||||
device.cart.cic_seed = 0x3F; // CicNus6101
|
||||
}
|
||||
"61E88238552C356C23D19409FE5570EE6910419586BC6FC740F638F761ADC46E" => {
|
||||
device.cart.cic_seed = 0x3F; // CicNus6102
|
||||
}
|
||||
"BF3620D30817007091EBE9BDDD1B88C23B8A0052170B3309CDE5B6B4238E45E7" => {
|
||||
device.cart.cic_seed = 0x78; // CicNus6103
|
||||
}
|
||||
"04B7BC6717A9F0EB724CF927E74AD3876C381CBB280D841736FC5E55580B756B" => {
|
||||
device.cart.cic_seed = 0x91; // CicNus6105
|
||||
}
|
||||
"36ADC40148AF56F0D78CD505EB6A90117D1FD6F11C6309E52ED36BC4C6BA340E" => {
|
||||
device.cart.cic_seed = 0x85; // CicNus6106
|
||||
}
|
||||
"53C0088FB777870D0AF32F0251E964030E2E8B72E830C26042FD191169508C05" => {
|
||||
device.cart.cic_seed = 0xdd; // CicNus5167
|
||||
device.cart.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] = 1;
|
||||
}
|
||||
_ => {
|
||||
device.cart.cic_seed = 0x3F; // CicNus6102
|
||||
//println!("unknown IPL3 {}", hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_hash(data: &[u8]) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(data);
|
||||
hasher
|
||||
.finalize()
|
||||
.iter()
|
||||
.map(|b| format!("{:02X}", b))
|
||||
.collect()
|
||||
}
|
||||
@@ -0,0 +1,482 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
|
||||
pub const SDCARD_SIZE: usize = 0x4000000;
|
||||
|
||||
const SC64_SCR_REG: u32 = 0;
|
||||
const SC64_DATA0_REG: u32 = 1;
|
||||
const SC64_DATA1_REG: u32 = 2;
|
||||
const SC64_IDENTIFIER_REG: u32 = 3;
|
||||
const SC64_KEY_REG: u32 = 4;
|
||||
//const SC64_IRQ_REG: u32 = 5;
|
||||
//const SC64_AUX_REG: u32 = 6;
|
||||
pub const SC64_REGS_COUNT: u32 = 7;
|
||||
|
||||
pub const SC64_BOOTLOADER_SWITCH: u32 = 0;
|
||||
pub const SC64_ROM_WRITE_ENABLE: u32 = 1;
|
||||
pub const SC64_SAVE_TYPE: u32 = 6;
|
||||
pub const SC64_CFG_COUNT: u32 = 15;
|
||||
|
||||
const SC64_BUFFER_MASK: usize = 0x1FFF;
|
||||
const SC64_EEPROM_MASK: usize = 0xFFF;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Sc64 {
|
||||
pub buffer: Vec<u8>,
|
||||
pub regs: [u32; SC64_REGS_COUNT as usize],
|
||||
pub regs_locked: bool,
|
||||
pub cfg: [u32; SC64_CFG_COUNT as usize],
|
||||
pub sector: u32,
|
||||
pub writeback_sector: Vec<u32>,
|
||||
pub usb_buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
fn format_sdcard(device: &mut device::Device) {
|
||||
if device.ui.storage.saves.sdcard.data.is_empty() {
|
||||
device.ui.storage.saves.sdcard.data.resize(SDCARD_SIZE, 0);
|
||||
let buf = std::io::Cursor::new(&mut device.ui.storage.saves.sdcard.data);
|
||||
fatfs::format_volume(buf, fatfs::FormatVolumeOptions::new()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_regs(
|
||||
device: &mut device::Device,
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
if device.cart.sc64.regs_locked {
|
||||
return 0;
|
||||
}
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
SC64_SCR_REG | SC64_DATA0_REG | SC64_DATA1_REG => device.cart.sc64.regs[reg as usize],
|
||||
SC64_IDENTIFIER_REG => 0x53437632,
|
||||
_ => panic!("unknown read reg {reg} address {address:#x}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
SC64_KEY_REG => {
|
||||
device::memory::masked_write_32(&mut device.cart.sc64.regs[reg as usize], value, mask);
|
||||
if device.cart.sc64.regs[SC64_KEY_REG as usize] == 0x4F434B5F {
|
||||
device.cart.sc64.regs_locked = false;
|
||||
} else if device.cart.sc64.regs[SC64_KEY_REG as usize] == 0xFFFFFFFF {
|
||||
device.cart.sc64.regs_locked = true;
|
||||
}
|
||||
}
|
||||
SC64_DATA0_REG | SC64_DATA1_REG => {
|
||||
if !device.cart.sc64.regs_locked {
|
||||
device::memory::masked_write_32(
|
||||
&mut device.cart.sc64.regs[reg as usize],
|
||||
value,
|
||||
mask,
|
||||
);
|
||||
}
|
||||
}
|
||||
SC64_SCR_REG => {
|
||||
if !device.cart.sc64.regs_locked {
|
||||
match char::from_u32(value & mask).unwrap() {
|
||||
'V' => {
|
||||
// get version
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] = (2 << 16) | 20;
|
||||
device.cart.sc64.regs[SC64_DATA1_REG as usize] = 2;
|
||||
}
|
||||
'c' => {
|
||||
// get config
|
||||
device.cart.sc64.regs[SC64_DATA1_REG as usize] = device.cart.sc64.cfg
|
||||
[device.cart.sc64.regs[SC64_DATA0_REG as usize] as usize]
|
||||
}
|
||||
'C' => {
|
||||
// set config
|
||||
if device.cart.sc64.regs[SC64_DATA0_REG as usize] == SC64_SAVE_TYPE {
|
||||
// if save type is being written, we are probably booting a game using the flash cart menu
|
||||
// we shouldn't write saves to disk in this case (they are written to the SD card)
|
||||
device.ui.storage.saves.write_to_disk = false;
|
||||
device.ui.storage.save_type =
|
||||
match device.cart.sc64.regs[SC64_DATA1_REG as usize] {
|
||||
0 => {
|
||||
vec![]
|
||||
}
|
||||
1 => {
|
||||
vec![ui::storage::SaveTypes::Eeprom4k]
|
||||
}
|
||||
2 => {
|
||||
vec![ui::storage::SaveTypes::Eeprom16k]
|
||||
}
|
||||
3 => {
|
||||
vec![ui::storage::SaveTypes::Sram]
|
||||
}
|
||||
4 => {
|
||||
vec![ui::storage::SaveTypes::Flash]
|
||||
}
|
||||
_ => {
|
||||
panic!(
|
||||
"unknown sc64 save type: {}",
|
||||
device.cart.sc64.regs[SC64_DATA1_REG as usize]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
std::mem::swap(
|
||||
&mut device.cart.sc64.cfg
|
||||
[device.cart.sc64.regs[SC64_DATA0_REG as usize] as usize],
|
||||
&mut device.cart.sc64.regs[SC64_DATA1_REG as usize],
|
||||
);
|
||||
}
|
||||
'i' => {
|
||||
// sd card operation
|
||||
match device.cart.sc64.regs[SC64_DATA1_REG as usize] {
|
||||
0 => { //Init SD card
|
||||
}
|
||||
1 => { //Deinit SD card
|
||||
}
|
||||
_ => {
|
||||
panic!(
|
||||
"unknown sc64 sd card operation: {}",
|
||||
device.cart.sc64.regs[SC64_DATA1_REG as usize]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
'I' => {
|
||||
// set sd sector
|
||||
device.cart.sc64.sector = device.cart.sc64.regs[SC64_DATA0_REG as usize];
|
||||
}
|
||||
's' => {
|
||||
format_sdcard(device);
|
||||
// read sd card
|
||||
let address =
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF;
|
||||
let offset = (device.cart.sc64.sector * 512) as usize;
|
||||
let length =
|
||||
(device.cart.sc64.regs[SC64_DATA1_REG as usize] * 512) as usize;
|
||||
let mut i = 0;
|
||||
|
||||
while i < length {
|
||||
let data = u32::from_be_bytes(
|
||||
device
|
||||
.ui
|
||||
.storage
|
||||
.saves
|
||||
.sdcard
|
||||
.data
|
||||
.get((offset + i)..(offset + i + 4))
|
||||
.unwrap_or(&[0; 4])
|
||||
.try_into()
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
|
||||
device::memory::data_write(
|
||||
device,
|
||||
address + i as u64,
|
||||
data,
|
||||
0xFFFFFFFF,
|
||||
false,
|
||||
);
|
||||
i += 4;
|
||||
}
|
||||
}
|
||||
'S' => {
|
||||
format_sdcard(device);
|
||||
// write sd card
|
||||
let address =
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF;
|
||||
let offset = (device.cart.sc64.sector * 512) as usize;
|
||||
let length =
|
||||
(device.cart.sc64.regs[SC64_DATA1_REG as usize] * 512) as usize;
|
||||
let mut i = 0;
|
||||
|
||||
while i < length {
|
||||
let data = device::memory::data_read(
|
||||
device,
|
||||
address + i as u64,
|
||||
device::memory::AccessSize::Word,
|
||||
false,
|
||||
)
|
||||
.to_be_bytes();
|
||||
device
|
||||
.ui
|
||||
.storage
|
||||
.saves
|
||||
.sdcard
|
||||
.data
|
||||
.get_mut((offset + i)..(offset + i + 4))
|
||||
.unwrap_or(&mut [0; 4])
|
||||
.copy_from_slice(&data);
|
||||
i += 4;
|
||||
}
|
||||
ui::storage::schedule_save(device, ui::storage::SaveTypes::Sdcard);
|
||||
}
|
||||
'U' => {
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0;
|
||||
}
|
||||
'u' => {
|
||||
// used to notify the game that there is data to read
|
||||
if let Some(cart_rx) = device.ui.usb.cart_rx.as_mut() {
|
||||
match cart_rx.try_recv() {
|
||||
Ok(data) => {
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] =
|
||||
data.data_type & 0xFF; // read_status/type
|
||||
device.cart.sc64.regs[SC64_DATA1_REG as usize] =
|
||||
data.data_size & 0xFFFFFF; // length
|
||||
device.cart.sc64.usb_buffer = data.data; // store the data to be read
|
||||
}
|
||||
Err(err) => {
|
||||
match err {
|
||||
tokio::sync::broadcast::error::TryRecvError::Lagged(_) => {
|
||||
panic!("cart_rx lagged: {err}");
|
||||
}
|
||||
_ => {
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0; // read_status/type
|
||||
device.cart.sc64.regs[SC64_DATA1_REG as usize] = 0; // length
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0; // read_status/type
|
||||
device.cart.sc64.regs[SC64_DATA1_REG as usize] = 0; // length
|
||||
}
|
||||
}
|
||||
'M' => {
|
||||
// Send data from from flashcart to USB
|
||||
let address =
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF;
|
||||
let length =
|
||||
device.cart.sc64.regs[SC64_DATA1_REG as usize] as usize & 0xFFFFFF;
|
||||
|
||||
if let Some(usb_tx) = device.ui.usb.usb_tx.as_mut() {
|
||||
let mut usb_buffer = vec![0; length];
|
||||
|
||||
let mut i = 0;
|
||||
|
||||
if address < device.rdram.size as u64 {
|
||||
while i < length {
|
||||
*usb_buffer.get_mut(i).unwrap_or(&mut 0) = *device
|
||||
.rdram
|
||||
.mem
|
||||
.get((address as usize + i) ^ device.byte_swap)
|
||||
.unwrap_or(&0);
|
||||
i += 1;
|
||||
}
|
||||
} else if address >= device::memory::MM_CART_ROM as u64
|
||||
&& address < device::memory::MM_PIF_MEM as u64
|
||||
{
|
||||
while i < length {
|
||||
*usb_buffer.get_mut(i).unwrap_or(&mut 0) = *device
|
||||
.ui
|
||||
.storage
|
||||
.saves
|
||||
.romsave
|
||||
.data
|
||||
.get(
|
||||
&(((address as usize + i)
|
||||
& device::cart::rom::CART_MASK)
|
||||
as u32),
|
||||
)
|
||||
.unwrap_or(
|
||||
device
|
||||
.cart
|
||||
.rom
|
||||
.get(
|
||||
(address as usize + i)
|
||||
& device::cart::rom::CART_MASK,
|
||||
)
|
||||
.unwrap_or(&0),
|
||||
);
|
||||
i += 1;
|
||||
}
|
||||
} else {
|
||||
panic!("Unknown address {address:#x} for SC64 M command");
|
||||
}
|
||||
|
||||
ui::usb::send_to_usb(
|
||||
usb_tx,
|
||||
ui::usb::UsbData {
|
||||
data: usb_buffer,
|
||||
data_type: device.cart.sc64.regs[SC64_DATA1_REG as usize] >> 24,
|
||||
data_size: device.cart.sc64.regs[SC64_DATA1_REG as usize]
|
||||
& 0xFFFFFF,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
'm' => {
|
||||
// Receive data from USB to flashcart
|
||||
let address =
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF;
|
||||
let length = device.cart.sc64.regs[SC64_DATA1_REG as usize] as usize;
|
||||
|
||||
let mut i = 0;
|
||||
|
||||
if address < device.rdram.size as u64 {
|
||||
while i < length {
|
||||
*device
|
||||
.rdram
|
||||
.mem
|
||||
.get_mut((address as usize + i) ^ device.byte_swap)
|
||||
.unwrap_or(&mut 0) =
|
||||
*device.cart.sc64.usb_buffer.get(i).unwrap_or(&0);
|
||||
i += 1;
|
||||
}
|
||||
} else if address >= device::memory::MM_CART_ROM as u64
|
||||
&& address < device::memory::MM_PIF_MEM as u64
|
||||
{
|
||||
while i < length {
|
||||
device.ui.storage.saves.romsave.data.insert(
|
||||
((address as usize + i) & device::cart::rom::CART_MASK) as u32,
|
||||
*device.cart.sc64.usb_buffer.get(i).unwrap_or(&0),
|
||||
);
|
||||
i += 1;
|
||||
}
|
||||
} else {
|
||||
panic!("Unknown address {address:#x} for SC64 m command");
|
||||
}
|
||||
}
|
||||
'w' => {
|
||||
// SD card writeback pending
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0;
|
||||
}
|
||||
'W' => {
|
||||
let writeback_sectors_address =
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64;
|
||||
for i in 0..256 {
|
||||
device.cart.sc64.writeback_sector[i] = device::memory::data_read(
|
||||
device,
|
||||
writeback_sectors_address + (i * 4) as u64,
|
||||
device::memory::AccessSize::Word,
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
panic!(
|
||||
"unknown sc64 command: {}",
|
||||
char::from_u32(value & mask).unwrap()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => panic!(
|
||||
"unknown write reg {} address {:#x} value {}",
|
||||
reg,
|
||||
address,
|
||||
char::from_u32(value & mask).unwrap()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_mem(
|
||||
device: &mut device::Device,
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
if address & 0x2000 != 0 {
|
||||
device::cart::format_eeprom(device);
|
||||
let masked_address = address as usize & SC64_EEPROM_MASK;
|
||||
u32::from_be_bytes(
|
||||
device.ui.storage.saves.eeprom.data[masked_address..masked_address + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
let masked_address = address as usize & SC64_BUFFER_MASK;
|
||||
u32::from_be_bytes(
|
||||
device.cart.sc64.buffer[masked_address..masked_address + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
if address & 0x2000 != 0 {
|
||||
device::cart::format_eeprom(device);
|
||||
let masked_address = address as usize & SC64_EEPROM_MASK;
|
||||
let mut data = u32::from_be_bytes(
|
||||
device.ui.storage.saves.eeprom.data[masked_address..masked_address + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
device::memory::masked_write_32(&mut data, value, mask);
|
||||
device.ui.storage.saves.eeprom.data[masked_address..masked_address + 4]
|
||||
.copy_from_slice(&data.to_be_bytes());
|
||||
ui::storage::schedule_save(device, ui::storage::SaveTypes::Eeprom4k);
|
||||
} else {
|
||||
let masked_address = address as usize & SC64_BUFFER_MASK;
|
||||
let mut data = u32::from_be_bytes(
|
||||
device.cart.sc64.buffer[masked_address..masked_address + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
device::memory::masked_write_32(&mut data, value, mask);
|
||||
device.cart.sc64.buffer[masked_address..masked_address + 4]
|
||||
.copy_from_slice(&data.to_be_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dma_read(
|
||||
device: &mut device::Device,
|
||||
mut cart_addr: u32,
|
||||
mut dram_addr: u32,
|
||||
length: u32,
|
||||
) -> u64 {
|
||||
dram_addr &= device::rdram::RDRAM_MASK as u32;
|
||||
let buffer = if cart_addr & 0x2000 != 0 {
|
||||
device::cart::format_eeprom(device);
|
||||
cart_addr &= SC64_EEPROM_MASK as u32;
|
||||
ui::storage::schedule_save(device, ui::storage::SaveTypes::Eeprom4k);
|
||||
&mut device.ui.storage.saves.eeprom.data
|
||||
} else {
|
||||
cart_addr &= SC64_BUFFER_MASK as u32;
|
||||
&mut device.cart.sc64.buffer
|
||||
};
|
||||
let mut i = dram_addr;
|
||||
let mut j = cart_addr;
|
||||
|
||||
while i < dram_addr + length {
|
||||
*buffer.get_mut(j as usize).unwrap_or(&mut 0) = *device
|
||||
.rdram
|
||||
.mem
|
||||
.get(i as usize ^ device.byte_swap)
|
||||
.unwrap_or(&0);
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
|
||||
device::pi::calculate_cycles(device, 1, length)
|
||||
}
|
||||
|
||||
pub fn dma_write(
|
||||
device: &mut device::Device,
|
||||
mut cart_addr: u32,
|
||||
mut dram_addr: u32,
|
||||
length: u32,
|
||||
) -> u64 {
|
||||
dram_addr &= device::rdram::RDRAM_MASK as u32;
|
||||
let buffer = if cart_addr & 0x2000 != 0 {
|
||||
device::cart::format_eeprom(device);
|
||||
cart_addr &= SC64_EEPROM_MASK as u32;
|
||||
&device.ui.storage.saves.eeprom.data
|
||||
} else {
|
||||
cart_addr &= SC64_BUFFER_MASK as u32;
|
||||
&device.cart.sc64.buffer
|
||||
};
|
||||
let mut i = dram_addr;
|
||||
let mut j = cart_addr;
|
||||
|
||||
while i < dram_addr + length {
|
||||
*device
|
||||
.rdram
|
||||
.mem
|
||||
.get_mut(i as usize ^ device.byte_swap)
|
||||
.unwrap_or(&mut 0) = *buffer.get(j as usize).unwrap_or(&0);
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
device::pi::calculate_cycles(device, 1, length)
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
use crate::{device, ui};
|
||||
|
||||
const SRAM_MASK: usize = 0xFFFF;
|
||||
pub const SRAM_SIZE: usize = 0x8000;
|
||||
@@ -9,7 +8,7 @@ pub const MX29L1100_ID: u32 = 0x00c2001e;
|
||||
const MX29L0000_ID: u32 = 0x00c20000;
|
||||
const MX29L0001_ID: u32 = 0x00c20001;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum FlashramMode {
|
||||
ReadArray,
|
||||
ReadSiliconId,
|
||||
@@ -19,23 +18,31 @@ pub enum FlashramMode {
|
||||
PageProgram,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Flashram {
|
||||
pub status: u32,
|
||||
pub mode: FlashramMode,
|
||||
pub erase_page: u16,
|
||||
#[serde(with = "serde_big_array::BigArray")]
|
||||
pub page_buf: [u8; 128],
|
||||
pub silicon_id: [u32; 2],
|
||||
}
|
||||
|
||||
fn format_sram(device: &mut device::Device) {
|
||||
if device.ui.saves.sram.0.len() < SRAM_SIZE {
|
||||
device.ui.saves.sram.0.resize(SRAM_SIZE, 0xFF)
|
||||
if device.ui.storage.saves.sram.data.len() < SRAM_SIZE {
|
||||
device.ui.storage.saves.sram.data.resize(SRAM_SIZE, 0xFF)
|
||||
}
|
||||
}
|
||||
|
||||
fn format_flash(device: &mut device::Device) {
|
||||
if device.ui.saves.flash.0.len() < FLASHRAM_SIZE {
|
||||
device.ui.saves.flash.0.resize(FLASHRAM_SIZE, 0xFF)
|
||||
if device.ui.storage.saves.flash.data.len() < FLASHRAM_SIZE {
|
||||
device
|
||||
.ui
|
||||
.storage
|
||||
.saves
|
||||
.flash
|
||||
.data
|
||||
.resize(FLASHRAM_SIZE, 0xFF)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,20 +52,21 @@ fn read_mem_sram(device: &mut device::Device, address: u64) -> u32 {
|
||||
format_sram(device);
|
||||
|
||||
u32::from_be_bytes(
|
||||
device.ui.saves.sram.0[masked_address..masked_address + 4]
|
||||
device.ui.storage.saves.sram.data[masked_address..masked_address + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
fn read_mem_flash(device: &device::Device, address: u64) -> u32 {
|
||||
if (address & 0x1ffff) == 0x00000 && device.flashram.mode == FlashramMode::Status {
|
||||
if (address & 0x1ffff) == 0x00000 && device.cart.flashram.mode == FlashramMode::Status {
|
||||
/* read Status register */
|
||||
device.flashram.status
|
||||
} else if (address & 0x1ffff) == 0x0000 && device.flashram.mode == FlashramMode::ReadArray {
|
||||
device.cart.flashram.status
|
||||
} else if (address & 0x1ffff) == 0x0000 && device.cart.flashram.mode == FlashramMode::ReadArray
|
||||
{
|
||||
/* flashram MMIO read are not supported except for the "dummy" read @0x0000 done before DMA.
|
||||
* returns a "dummy" value. */
|
||||
return 0;
|
||||
0
|
||||
} else {
|
||||
/* other accesses are not implemented */
|
||||
panic!("unknown flashram read")
|
||||
@@ -73,7 +81,12 @@ pub fn read_mem(
|
||||
let cycles = device::pi::calculate_cycles(device, 2, 4);
|
||||
device::cop0::add_cycles(device, cycles);
|
||||
|
||||
if device.ui.save_type.contains(&ui::storage::SaveTypes::Sram) {
|
||||
if device
|
||||
.ui
|
||||
.storage
|
||||
.save_type
|
||||
.contains(&ui::storage::SaveTypes::Sram)
|
||||
{
|
||||
read_mem_sram(device, address)
|
||||
} else {
|
||||
read_mem_flash(device, address)
|
||||
@@ -86,20 +99,21 @@ fn write_mem_sram(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
format_sram(device);
|
||||
|
||||
let mut data = u32::from_be_bytes(
|
||||
device.ui.saves.sram.0[masked_address..masked_address + 4]
|
||||
device.ui.storage.saves.sram.data[masked_address..masked_address + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
device::memory::masked_write_32(&mut data, value, mask);
|
||||
device.ui.saves.sram.0[masked_address..masked_address + 4].copy_from_slice(&data.to_be_bytes());
|
||||
device.ui.storage.saves.sram.data[masked_address..masked_address + 4]
|
||||
.copy_from_slice(&data.to_be_bytes());
|
||||
|
||||
device.ui.saves.sram.1 = true
|
||||
ui::storage::schedule_save(device, ui::storage::SaveTypes::Sram);
|
||||
}
|
||||
|
||||
fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
if (address & 0x1ffff) == 0x00000 && device.flashram.mode == FlashramMode::Status {
|
||||
if (address & 0x1ffff) == 0x00000 && device.cart.flashram.mode == FlashramMode::Status {
|
||||
/* clear/set Status register */
|
||||
device.flashram.status = (value & mask) & 0xff;
|
||||
device.cart.flashram.status = (value & mask) & 0xff;
|
||||
} else if (address & 0x1ffff) == 0x10000 {
|
||||
/* set command */
|
||||
format_flash(device);
|
||||
@@ -111,7 +125,12 @@ fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask:
|
||||
}
|
||||
|
||||
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
if device.ui.save_type.contains(&ui::storage::SaveTypes::Sram) {
|
||||
if device
|
||||
.ui
|
||||
.storage
|
||||
.save_type
|
||||
.contains(&ui::storage::SaveTypes::Sram)
|
||||
{
|
||||
write_mem_sram(device, address, value, mask)
|
||||
} else {
|
||||
write_mem_flash(device, address, value, mask)
|
||||
@@ -120,12 +139,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
|
||||
device.pi.regs[device::pi::PI_STATUS_REG as usize] |= device::pi::PI_STATUS_IO_BUSY;
|
||||
|
||||
let cycles = device::pi::calculate_cycles(device, 2, 4);
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::PI,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + cycles,
|
||||
device::pi::dma_event,
|
||||
);
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_PI, cycles);
|
||||
}
|
||||
|
||||
fn dma_read_sram(device: &mut device::Device, mut cart_addr: u32, mut dram_addr: u32, length: u32) {
|
||||
@@ -136,13 +150,24 @@ fn dma_read_sram(device: &mut device::Device, mut cart_addr: u32, mut dram_addr:
|
||||
|
||||
format_sram(device);
|
||||
|
||||
while i < dram_addr + length && i < device.rdram.size {
|
||||
device.ui.saves.sram.0[j as usize] = device.rdram.mem[i as usize ^ device.byte_swap];
|
||||
while i < dram_addr + length {
|
||||
*device
|
||||
.ui
|
||||
.storage
|
||||
.saves
|
||||
.sram
|
||||
.data
|
||||
.get_mut(j as usize)
|
||||
.unwrap_or(&mut 0) = *device
|
||||
.rdram
|
||||
.mem
|
||||
.get(i as usize ^ device.byte_swap)
|
||||
.unwrap_or(&0);
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
|
||||
device.ui.saves.sram.1 = true
|
||||
ui::storage::schedule_save(device, ui::storage::SaveTypes::Sram);
|
||||
}
|
||||
|
||||
fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u32, length: u32) {
|
||||
@@ -150,12 +175,15 @@ fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u32, l
|
||||
|
||||
if (cart_addr & 0x1ffff) == 0x00000
|
||||
&& length == 128
|
||||
&& device.flashram.mode == FlashramMode::PageProgram
|
||||
&& device.cart.flashram.mode == FlashramMode::PageProgram
|
||||
{
|
||||
/* load page buf using DMA */
|
||||
for i in 0..length {
|
||||
device.flashram.page_buf[i as usize] =
|
||||
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap];
|
||||
device.cart.flashram.page_buf[i as usize] = *device
|
||||
.rdram
|
||||
.mem
|
||||
.get((dram_addr + i) as usize ^ device.byte_swap)
|
||||
.unwrap_or(&0);
|
||||
}
|
||||
} else {
|
||||
/* other accesses are not implemented */
|
||||
@@ -165,7 +193,12 @@ fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u32, l
|
||||
|
||||
// cart is big endian, rdram is native endian
|
||||
pub fn dma_read(device: &mut device::Device, cart_addr: u32, dram_addr: u32, length: u32) -> u64 {
|
||||
if device.ui.save_type.contains(&ui::storage::SaveTypes::Sram) {
|
||||
if device
|
||||
.ui
|
||||
.storage
|
||||
.save_type
|
||||
.contains(&ui::storage::SaveTypes::Sram)
|
||||
{
|
||||
dma_read_sram(device, cart_addr, dram_addr, length)
|
||||
} else {
|
||||
dma_read_flash(device, cart_addr, dram_addr, length)
|
||||
@@ -186,8 +219,19 @@ fn dma_write_sram(
|
||||
|
||||
format_sram(device);
|
||||
|
||||
while i < dram_addr + length && i < device.rdram.size {
|
||||
device.rdram.mem[i as usize ^ device.byte_swap] = device.ui.saves.sram.0[j as usize];
|
||||
while i < dram_addr + length {
|
||||
*device
|
||||
.rdram
|
||||
.mem
|
||||
.get_mut(i as usize ^ device.byte_swap)
|
||||
.unwrap_or(&mut 0) = *device
|
||||
.ui
|
||||
.storage
|
||||
.saves
|
||||
.sram
|
||||
.data
|
||||
.get(j as usize)
|
||||
.unwrap_or(&0);
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
@@ -203,20 +247,30 @@ fn dma_write_flash(
|
||||
|
||||
if (cart_addr & 0x1ffff) == 0x00000
|
||||
&& length == 8
|
||||
&& device.flashram.mode == FlashramMode::ReadSiliconId
|
||||
&& device.cart.flashram.mode == FlashramMode::ReadSiliconId
|
||||
{
|
||||
/* read Silicon ID using DMA */
|
||||
device.rdram.mem[dram_addr as usize..dram_addr as usize + 4]
|
||||
.copy_from_slice(&device.flashram.silicon_id[0].to_ne_bytes());
|
||||
device
|
||||
.rdram
|
||||
.mem
|
||||
.get_mut(dram_addr as usize..dram_addr as usize + 4)
|
||||
.unwrap_or(&mut [0; 4])
|
||||
.copy_from_slice(&device.cart.flashram.silicon_id[0].to_ne_bytes());
|
||||
dram_addr += 4;
|
||||
device.rdram.mem[dram_addr as usize..dram_addr as usize + 4]
|
||||
.copy_from_slice(&device.flashram.silicon_id[1].to_ne_bytes());
|
||||
} else if (cart_addr & 0x1ffff) < 0x10000 && device.flashram.mode == FlashramMode::ReadArray {
|
||||
device
|
||||
.rdram
|
||||
.mem
|
||||
.get_mut(dram_addr as usize..dram_addr as usize + 4)
|
||||
.unwrap_or(&mut [0; 4])
|
||||
.copy_from_slice(&device.cart.flashram.silicon_id[1].to_ne_bytes());
|
||||
} else if (cart_addr & 0x1ffff) < 0x10000
|
||||
&& device.cart.flashram.mode == FlashramMode::ReadArray
|
||||
{
|
||||
format_flash(device);
|
||||
/* adjust flashram address before starting DMA. */
|
||||
if device.flashram.silicon_id[1] == MX29L1100_ID
|
||||
|| device.flashram.silicon_id[1] == MX29L0000_ID
|
||||
|| device.flashram.silicon_id[1] == MX29L0001_ID
|
||||
if device.cart.flashram.silicon_id[1] == MX29L1100_ID
|
||||
|| device.cart.flashram.silicon_id[1] == MX29L0000_ID
|
||||
|| device.cart.flashram.silicon_id[1] == MX29L0001_ID
|
||||
{
|
||||
/* "old" flash needs special address adjusting */
|
||||
cart_addr = (cart_addr & 0xffff) * 2;
|
||||
@@ -227,8 +281,11 @@ fn dma_write_flash(
|
||||
|
||||
/* do actual DMA */
|
||||
for i in 0..length {
|
||||
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap] =
|
||||
device.ui.saves.flash.0[(cart_addr + i) as usize];
|
||||
*device
|
||||
.rdram
|
||||
.mem
|
||||
.get_mut((dram_addr + i) as usize ^ device.byte_swap)
|
||||
.unwrap_or(&mut 0) = device.ui.storage.saves.flash.data[(cart_addr + i) as usize];
|
||||
}
|
||||
} else {
|
||||
/* other accesses are not implemented */
|
||||
@@ -238,7 +295,12 @@ fn dma_write_flash(
|
||||
|
||||
// cart is big endian, rdram is native endian
|
||||
pub fn dma_write(device: &mut device::Device, cart_addr: u32, dram_addr: u32, length: u32) -> u64 {
|
||||
if device.ui.save_type.contains(&ui::storage::SaveTypes::Sram) {
|
||||
if device
|
||||
.ui
|
||||
.storage
|
||||
.save_type
|
||||
.contains(&ui::storage::SaveTypes::Sram)
|
||||
{
|
||||
dma_write_sram(device, cart_addr, dram_addr, length)
|
||||
} else {
|
||||
dma_write_flash(device, cart_addr, dram_addr, length)
|
||||
@@ -250,77 +312,77 @@ fn flashram_command(device: &mut device::Device, command: u32) {
|
||||
match command & 0xff000000 {
|
||||
0x3c000000 => {
|
||||
/* set chip erase mode */
|
||||
device.flashram.mode = FlashramMode::ChipErase;
|
||||
device.cart.flashram.mode = FlashramMode::ChipErase;
|
||||
}
|
||||
|
||||
0x4b000000 => {
|
||||
/* set sector erase mode, set erase sector */
|
||||
device.flashram.mode = FlashramMode::SectorErase;
|
||||
device.flashram.erase_page = command as u16;
|
||||
device.cart.flashram.mode = FlashramMode::SectorErase;
|
||||
device.cart.flashram.erase_page = command as u16;
|
||||
}
|
||||
|
||||
0x78000000 => {
|
||||
/* set erase busy flag */
|
||||
device.flashram.status |= 0x02;
|
||||
device.cart.flashram.status |= 0x02;
|
||||
|
||||
/* do chip/sector erase */
|
||||
if device.flashram.mode == FlashramMode::SectorErase {
|
||||
let offset: usize = (device.flashram.erase_page & 0xff80) as usize * 128;
|
||||
if device.cart.flashram.mode == FlashramMode::SectorErase {
|
||||
let offset: usize = (device.cart.flashram.erase_page & 0xff80) as usize * 128;
|
||||
for i in 0..128 * 128 {
|
||||
device.ui.saves.flash.0[offset + i] = 0xFF;
|
||||
device.ui.storage.saves.flash.data[offset + i] = 0xFF;
|
||||
}
|
||||
device.ui.saves.flash.1 = true
|
||||
} else if device.flashram.mode == FlashramMode::ChipErase {
|
||||
ui::storage::schedule_save(device, ui::storage::SaveTypes::Flash);
|
||||
} else if device.cart.flashram.mode == FlashramMode::ChipErase {
|
||||
for i in 0..FLASHRAM_SIZE {
|
||||
device.ui.saves.flash.0[i] = 0xFF;
|
||||
device.ui.storage.saves.flash.data[i] = 0xFF;
|
||||
}
|
||||
device.ui.saves.flash.1 = true
|
||||
ui::storage::schedule_save(device, ui::storage::SaveTypes::Flash);
|
||||
} else {
|
||||
panic!("Unexpected flash erase command")
|
||||
}
|
||||
|
||||
/* clear erase busy flag, set erase success flag, transition to status mode */
|
||||
device.flashram.status &= !0x02;
|
||||
device.flashram.status |= 0x08;
|
||||
device.flashram.mode = FlashramMode::Status;
|
||||
device.cart.flashram.status &= !0x02;
|
||||
device.cart.flashram.status |= 0x08;
|
||||
device.cart.flashram.mode = FlashramMode::Status;
|
||||
}
|
||||
|
||||
0xa5000000 => {
|
||||
/* set program busy flag */
|
||||
device.flashram.status |= 0x01;
|
||||
device.cart.flashram.status |= 0x01;
|
||||
|
||||
/* program selected page */
|
||||
let offset: usize = (command & 0xffff) as usize * 128;
|
||||
for i in 0..128 {
|
||||
device.ui.saves.flash.0[offset + i] = device.flashram.page_buf[i];
|
||||
device.ui.storage.saves.flash.data[offset + i] = device.cart.flashram.page_buf[i];
|
||||
}
|
||||
device.ui.saves.flash.1 = true;
|
||||
ui::storage::schedule_save(device, ui::storage::SaveTypes::Flash);
|
||||
|
||||
/* clear program busy flag, set program success flag, transition to status mode */
|
||||
device.flashram.status &= !0x01;
|
||||
device.flashram.status |= 0x04;
|
||||
device.flashram.mode = FlashramMode::Status;
|
||||
device.cart.flashram.status &= !0x01;
|
||||
device.cart.flashram.status |= 0x04;
|
||||
device.cart.flashram.mode = FlashramMode::Status;
|
||||
}
|
||||
|
||||
0xb4000000 => {
|
||||
/* set page program mode */
|
||||
device.flashram.mode = FlashramMode::PageProgram;
|
||||
device.cart.flashram.mode = FlashramMode::PageProgram;
|
||||
}
|
||||
|
||||
0xd2000000 => {
|
||||
/* set status mode */
|
||||
device.flashram.mode = FlashramMode::Status;
|
||||
device.cart.flashram.mode = FlashramMode::Status;
|
||||
}
|
||||
|
||||
0xe1000000 => {
|
||||
/* set silicon_id mode */
|
||||
device.flashram.mode = FlashramMode::ReadSiliconId;
|
||||
device.flashram.status |= 0x01; /* Needed for Pokemon Puzzle League */
|
||||
device.cart.flashram.mode = FlashramMode::ReadSiliconId;
|
||||
device.cart.flashram.status |= 0x01; /* Needed for Pokemon Puzzle League */
|
||||
}
|
||||
|
||||
0xf0000000 => {
|
||||
/* set read mode */
|
||||
device.flashram.mode = FlashramMode::ReadArray;
|
||||
device.cart.flashram.mode = FlashramMode::ReadArray;
|
||||
}
|
||||
|
||||
_ => {
|
||||
@@ -1,200 +0,0 @@
|
||||
use crate::device;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
pub enum CicType {
|
||||
CicNus6101,
|
||||
CicNus6102,
|
||||
CicNus6103,
|
||||
CicNus6105,
|
||||
CicNus6106,
|
||||
CicNus5167,
|
||||
}
|
||||
|
||||
const CART_MASK: usize = 0xFFFFFFF;
|
||||
pub struct Cart {
|
||||
pub rom: Vec<u8>,
|
||||
pub is_viewer_buffer: [u8; 0xFFFF],
|
||||
pub pal: bool,
|
||||
pub latch: u32,
|
||||
pub cic_type: CicType,
|
||||
pub cic_seed: u8,
|
||||
pub rdram_size_offset: usize,
|
||||
pub rtc: device::cart::AfRtc,
|
||||
}
|
||||
|
||||
pub fn read_mem_fast(
|
||||
device: &mut device::Device,
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
let masked_address = address as usize & CART_MASK;
|
||||
u32::from_be_bytes(
|
||||
device.cart.rom[masked_address..masked_address + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn read_mem(
|
||||
device: &mut device::Device,
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
let cycles = device::pi::calculate_cycles(device, 1, 4);
|
||||
device::cop0::add_cycles(device, cycles);
|
||||
|
||||
// well known cart ROM oddity, if a read is perfomed while PI_STATUS_IO_BUSY is set, the latched value is returned rather than the data at the specified address
|
||||
if device.pi.regs[device::pi::PI_STATUS_REG as usize] & device::pi::PI_STATUS_IO_BUSY != 0 {
|
||||
device.cart.latch
|
||||
} else {
|
||||
let masked_address = address as usize & CART_MASK;
|
||||
u32::from_be_bytes(
|
||||
device.cart.rom[masked_address..masked_address + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_mem(device: &mut device::Device, _address: u64, value: u32, mask: u32) {
|
||||
device.cart.latch = value & mask;
|
||||
|
||||
device.pi.regs[device::pi::PI_STATUS_REG as usize] |= device::pi::PI_STATUS_IO_BUSY;
|
||||
|
||||
let cycles = device::pi::calculate_cycles(device, 1, 4);
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::PI,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + cycles,
|
||||
device::pi::dma_event,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn dma_read(
|
||||
device: &mut device::Device,
|
||||
mut cart_addr: u32,
|
||||
mut dram_addr: u32,
|
||||
length: u32,
|
||||
) -> u64 {
|
||||
dram_addr &= device::rdram::RDRAM_MASK as u32;
|
||||
cart_addr &= CART_MASK as u32;
|
||||
|
||||
for i in 0..length {
|
||||
if cart_addr + i < device.cart.rom.len() as u32 {
|
||||
device.cart.rom[(cart_addr + i) as usize] =
|
||||
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap];
|
||||
|
||||
device.ui.saves.romsave.0.insert(
|
||||
cart_addr + i,
|
||||
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
device.ui.saves.romsave.1 = true;
|
||||
|
||||
device::pi::calculate_cycles(device, 1, length)
|
||||
}
|
||||
|
||||
// cart is big endian, rdram is native endian
|
||||
pub fn dma_write(
|
||||
device: &mut device::Device,
|
||||
mut cart_addr: u32,
|
||||
mut dram_addr: u32,
|
||||
length: u32,
|
||||
) -> u64 {
|
||||
dram_addr &= device::rdram::RDRAM_MASK as u32;
|
||||
cart_addr &= CART_MASK as u32;
|
||||
let mut i = dram_addr;
|
||||
let mut j = cart_addr;
|
||||
while i < dram_addr + length && j < device.cart.rom.len() as u32 {
|
||||
device.rdram.mem[i as usize ^ device.byte_swap] = device.cart.rom[j as usize];
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
while i < dram_addr + length {
|
||||
// DMAs that extend past the end of the ROM return 0's for the portion that extends past the ROM length
|
||||
device.rdram.mem[i as usize ^ device.byte_swap] = 0;
|
||||
i += 1;
|
||||
}
|
||||
device::pi::calculate_cycles(device, 1, length)
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device, rom_file: Vec<u8>) {
|
||||
device.cart.rom = rom_file;
|
||||
set_system_region(device, device.cart.rom[0x3E]);
|
||||
set_cic(device);
|
||||
|
||||
device.ui.game_hash = calculate_hash(&device.cart.rom);
|
||||
|
||||
device.ui.game_id = String::from_utf8(device.cart.rom[0x3B..0x3E].to_vec()).unwrap();
|
||||
if device.ui.game_id.contains('\0') {
|
||||
device.ui.game_id = String::from("UNK");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_rom_save(device: &mut device::Device) {
|
||||
if device.ui.saves.romsave.0.is_empty() {
|
||||
return;
|
||||
}
|
||||
for (key, value) in device.ui.saves.romsave.0.iter() {
|
||||
device.cart.rom[*key as usize] = *value
|
||||
}
|
||||
}
|
||||
|
||||
fn set_system_region(device: &mut device::Device, country: u8) {
|
||||
let pal_codes: [u8; 8] = [b'D', b'F', b'I', b'P', b'S', b'U', b'X', b'Y'];
|
||||
for i in pal_codes {
|
||||
if country == i {
|
||||
device.cart.pal = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_cic(device: &mut device::Device) {
|
||||
let hash = calculate_hash(&device.cart.rom[0x40..0x1000]);
|
||||
match hash.as_str() {
|
||||
"B99F06C4802C2377E31E388435955EF3E99C618A6D55D24699D828EB1075F1EB" => {
|
||||
device.cart.cic_type = CicType::CicNus6101;
|
||||
device.cart.cic_seed = 0x3F;
|
||||
device.cart.rdram_size_offset = 0x318;
|
||||
}
|
||||
"61E88238552C356C23D19409FE5570EE6910419586BC6FC740F638F761ADC46E" => {
|
||||
device.cart.cic_type = CicType::CicNus6102;
|
||||
device.cart.cic_seed = 0x3F;
|
||||
device.cart.rdram_size_offset = 0x318;
|
||||
}
|
||||
"BF3620D30817007091EBE9BDDD1B88C23B8A0052170B3309CDE5B6B4238E45E7" => {
|
||||
device.cart.cic_type = CicType::CicNus6103;
|
||||
device.cart.cic_seed = 0x78;
|
||||
device.cart.rdram_size_offset = 0x318;
|
||||
}
|
||||
"04B7BC6717A9F0EB724CF927E74AD3876C381CBB280D841736FC5E55580B756B" => {
|
||||
device.cart.cic_type = CicType::CicNus6105;
|
||||
device.cart.cic_seed = 0x91;
|
||||
device.cart.rdram_size_offset = 0x3F0;
|
||||
}
|
||||
"36ADC40148AF56F0D78CD505EB6A90117D1FD6F11C6309E52ED36BC4C6BA340E" => {
|
||||
device.cart.cic_type = CicType::CicNus6106;
|
||||
device.cart.cic_seed = 0x85;
|
||||
device.cart.rdram_size_offset = 0x318;
|
||||
}
|
||||
"53C0088FB777870D0AF32F0251E964030E2E8B72E830C26042FD191169508C05" => {
|
||||
device.cart.cic_type = CicType::CicNus5167;
|
||||
device.cart.cic_seed = 0xdd;
|
||||
device.cart.rdram_size_offset = 0x318;
|
||||
}
|
||||
_ => {
|
||||
device.cart.cic_type = CicType::CicNus6102;
|
||||
device.cart.cic_seed = 0x3F;
|
||||
device.cart.rdram_size_offset = 0x318;
|
||||
println!("unknown IPL3 {}", hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_hash(data: &[u8]) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(data);
|
||||
format!("{:X}", hasher.finalize())
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::device;
|
||||
use crate::netplay;
|
||||
use crate::ui;
|
||||
use crate::{device, netplay, savestates, ui};
|
||||
|
||||
pub mod gbcart;
|
||||
pub mod mempak;
|
||||
pub mod rumble;
|
||||
pub mod transferpak;
|
||||
pub mod vru;
|
||||
|
||||
pub const JCMD_STATUS: u8 = 0x00;
|
||||
@@ -21,16 +21,19 @@ const CONT_STATUS_PAK_PRESENT: u8 = 1;
|
||||
const CONT_STATUS_PAK_NOT_PRESENT: u8 = 2;
|
||||
const CONT_FLAVOR: u16 = JDT_JOY_ABS_COUNTERS | JDT_JOY_PORT;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
#[derive(Copy, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum PakType {
|
||||
None = 0,
|
||||
MemPak = 1,
|
||||
RumblePak = 2,
|
||||
TransferPak = 3,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct PakHandler {
|
||||
#[serde(skip, default = "savestates::default_pak_handler")]
|
||||
pub read: fn(&mut device::Device, usize, u16, usize, usize),
|
||||
#[serde(skip, default = "savestates::default_pak_handler")]
|
||||
pub write: fn(&mut device::Device, usize, u16, usize, usize),
|
||||
pub pak_type: PakType,
|
||||
}
|
||||
@@ -53,31 +56,27 @@ pub fn process(device: &mut device::Device, channel: usize) {
|
||||
}
|
||||
JCMD_CONTROLLER_READ => {
|
||||
let offset = device.pif.channels[channel].rx_buf.unwrap();
|
||||
let input = if device.netplay.is_none() {
|
||||
ui::input::get(&mut device.ui, channel)
|
||||
} else {
|
||||
if device.netplay.as_ref().unwrap().player_number as usize == channel {
|
||||
let local_input = ui::input::get(&mut device.ui, 0);
|
||||
netplay::send_input(device.netplay.as_ref().unwrap(), local_input);
|
||||
let input = if let Some(netplay) = &mut device.netplay {
|
||||
if netplay.player_number as usize == channel {
|
||||
let local_input = ui::input::get(&device.ui, 0);
|
||||
netplay::send_input(netplay, local_input);
|
||||
}
|
||||
|
||||
netplay::get_input(device, channel)
|
||||
} else {
|
||||
ui::input::get(&device.ui, channel)
|
||||
};
|
||||
|
||||
device.pif.ram[offset..offset + 4].copy_from_slice(&input.0.to_ne_bytes());
|
||||
if input.1 {
|
||||
device.pif.ram[offset..offset + 4].copy_from_slice(&input.data.to_ne_bytes());
|
||||
if input.pak_change_pressed {
|
||||
// pak change button pressed
|
||||
if device::events::get_event(device, device::events::EventType::PakSwitch).is_none()
|
||||
{
|
||||
if device::events::get_event(device, device::events::EVENT_TYPE_PAK).is_none() {
|
||||
device.pif.channels[channel].change_pak =
|
||||
device.pif.channels[channel].pak_handler.unwrap().pak_type;
|
||||
device.pif.channels[channel].pak_handler = None;
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::PakSwitch,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize]
|
||||
+ (device.cpu.clock_rate), // 1 second
|
||||
pak_switch_event,
|
||||
device::events::EVENT_TYPE_PAK,
|
||||
device.cpu.clock_rate, // 1 second
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -96,7 +95,7 @@ pub fn process(device: &mut device::Device, channel: usize) {
|
||||
device.pif.channels[channel].rx_buf.unwrap(),
|
||||
channel,
|
||||
),
|
||||
_ => println!("unknown controller command {}", cmd),
|
||||
_ => println!("unknown controller command {cmd}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,8 +110,8 @@ fn pak_read_block(
|
||||
((device.pif.ram[addr_acrc] as u16) << 8) | (device.pif.ram[addr_acrc + 1] & 0xe0) as u16;
|
||||
let handler = device.pif.channels[channel].pak_handler;
|
||||
|
||||
if handler.is_some() {
|
||||
(handler.unwrap().read)(device, channel, address, data, PAK_CHUNK_SIZE);
|
||||
if let Some(handler) = handler {
|
||||
(handler.read)(device, channel, address, data, PAK_CHUNK_SIZE);
|
||||
device.pif.ram[dcrc] = data_crc(device, data, PAK_CHUNK_SIZE)
|
||||
} else {
|
||||
device.pif.ram[dcrc] = !data_crc(device, data, PAK_CHUNK_SIZE)
|
||||
@@ -130,8 +129,8 @@ fn pak_write_block(
|
||||
((device.pif.ram[addr_acrc] as u16) << 8) | (device.pif.ram[addr_acrc + 1] & 0xe0) as u16;
|
||||
let handler = device.pif.channels[channel].pak_handler;
|
||||
|
||||
if handler.is_some() {
|
||||
(handler.unwrap().write)(device, channel, address, data, PAK_CHUNK_SIZE);
|
||||
if let Some(handler) = handler {
|
||||
(handler.write)(device, channel, address, data, PAK_CHUNK_SIZE);
|
||||
device.pif.ram[dcrc] = data_crc(device, data, PAK_CHUNK_SIZE)
|
||||
} else {
|
||||
device.pif.ram[dcrc] = !data_crc(device, data, PAK_CHUNK_SIZE)
|
||||
@@ -158,31 +157,63 @@ fn data_crc(device: &device::Device, data_offset: usize, size: usize) -> u8 {
|
||||
crc
|
||||
}
|
||||
|
||||
fn pak_switch_event(device: &mut device::Device) {
|
||||
pub fn pak_switch_event(device: &mut device::Device) {
|
||||
for (i, channel) in device.pif.channels.iter_mut().enumerate() {
|
||||
if channel.change_pak != PakType::None {
|
||||
if channel.change_pak == PakType::RumblePak {
|
||||
if device.netplay.is_none() {
|
||||
device::ui::input::set_rumble(&mut device.ui, i, 0);
|
||||
} else if device.netplay.as_ref().unwrap().player_number as usize == i {
|
||||
device::ui::input::set_rumble(&mut device.ui, 0, 0);
|
||||
//stop rumble if it is on
|
||||
if let Some(netplay) = &device.netplay {
|
||||
if netplay.player_number as usize == i {
|
||||
device::ui::input::set_rumble(&device.ui, 0, 0);
|
||||
}
|
||||
} else {
|
||||
device::ui::input::set_rumble(&device.ui, i, 0);
|
||||
}
|
||||
|
||||
let handler = device::controller::PakHandler {
|
||||
let new_pak_type = match channel.change_pak {
|
||||
PakType::MemPak => PakType::RumblePak,
|
||||
PakType::RumblePak => {
|
||||
if device.transferpaks[i].cart.rom.is_empty() {
|
||||
PakType::MemPak
|
||||
} else {
|
||||
PakType::TransferPak
|
||||
}
|
||||
}
|
||||
PakType::TransferPak => PakType::MemPak,
|
||||
_ => {
|
||||
panic!("Invalid pak type");
|
||||
}
|
||||
};
|
||||
|
||||
if new_pak_type == PakType::MemPak {
|
||||
channel.pak_handler = Some(device::controller::PakHandler {
|
||||
read: device::controller::mempak::read,
|
||||
write: device::controller::mempak::write,
|
||||
pak_type: device::controller::PakType::MemPak,
|
||||
};
|
||||
channel.pak_handler = Some(handler);
|
||||
ui::audio::play_pak_switch(&mut device.ui, PakType::MemPak);
|
||||
} else if channel.change_pak == PakType::MemPak {
|
||||
let handler = device::controller::PakHandler {
|
||||
pak_type: new_pak_type,
|
||||
});
|
||||
ui::video::onscreen_message(
|
||||
"MemPak selected",
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_SHORT,
|
||||
);
|
||||
} else if new_pak_type == PakType::RumblePak {
|
||||
channel.pak_handler = Some(device::controller::PakHandler {
|
||||
read: device::controller::rumble::read,
|
||||
write: device::controller::rumble::write,
|
||||
pak_type: device::controller::PakType::RumblePak,
|
||||
};
|
||||
channel.pak_handler = Some(handler);
|
||||
ui::audio::play_pak_switch(&mut device.ui, PakType::RumblePak);
|
||||
pak_type: new_pak_type,
|
||||
});
|
||||
ui::video::onscreen_message(
|
||||
"RumblePak selected",
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_SHORT,
|
||||
);
|
||||
} else if new_pak_type == PakType::TransferPak {
|
||||
channel.pak_handler = Some(device::controller::PakHandler {
|
||||
read: device::controller::transferpak::read,
|
||||
write: device::controller::transferpak::write,
|
||||
pak_type: new_pak_type,
|
||||
});
|
||||
ui::video::onscreen_message(
|
||||
"TransferPak selected",
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_SHORT,
|
||||
);
|
||||
}
|
||||
channel.change_pak = PakType::None;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
use crate::device;
|
||||
|
||||
#[derive(Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct GbCart {
|
||||
pub enabled: bool,
|
||||
#[serde(skip)]
|
||||
pub rom: Vec<u8>,
|
||||
pub ram: Vec<u8>,
|
||||
pub cart_type: device::controller::gbcart::CartType,
|
||||
pub ram_enabled: bool,
|
||||
pub mbc1_mode: bool,
|
||||
pub ram_bank: u16,
|
||||
pub rom_bank: u32,
|
||||
pub set_latch: bool,
|
||||
pub latch_second: u8,
|
||||
pub latch_minute: u8,
|
||||
pub latch_hour: u8,
|
||||
pub latch_day: u16,
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum CartType {
|
||||
#[default]
|
||||
None,
|
||||
MBC1RamBatt,
|
||||
MBC3RamBatt,
|
||||
MBC3RamBattRtc,
|
||||
MBC5RamBatt,
|
||||
}
|
||||
|
||||
fn write_mbc1(
|
||||
pif_ram: &[u8],
|
||||
cart: &mut device::controller::gbcart::GbCart,
|
||||
address: u16,
|
||||
data: usize,
|
||||
size: usize,
|
||||
) {
|
||||
let value = pif_ram[data + size - 1];
|
||||
if address < 0x2000 {
|
||||
cart.ram_enabled = value & 0xf == 0xa;
|
||||
} else if address < 0x4000 {
|
||||
let bank = value & 0x1f;
|
||||
cart.rom_bank = bank as u32;
|
||||
if cart.rom_bank == 0 {
|
||||
cart.rom_bank = 1;
|
||||
}
|
||||
} else if address < 0x6000 {
|
||||
cart.ram_bank = (value & 0x3) as u16;
|
||||
} else if address < 0x8000 {
|
||||
cart.mbc1_mode = (value & 0x1) != 0;
|
||||
} else if (0xa000..0xc000).contains(&address) {
|
||||
if !cart.ram_enabled {
|
||||
return;
|
||||
}
|
||||
if cart.mbc1_mode {
|
||||
let banked_address = address - 0xA000 + (cart.ram_bank * 0x2000);
|
||||
cart.ram[banked_address as usize..banked_address as usize + size]
|
||||
.copy_from_slice(&pif_ram[data..data + size]);
|
||||
} else {
|
||||
let banked_address = address - 0xA000;
|
||||
cart.ram[banked_address as usize..banked_address as usize + size]
|
||||
.copy_from_slice(&pif_ram[data..data + size]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_mbc1(
|
||||
pif_ram: &mut [u8],
|
||||
cart: &device::controller::gbcart::GbCart,
|
||||
address: u16,
|
||||
data: usize,
|
||||
size: usize,
|
||||
) {
|
||||
if address < 0x4000 {
|
||||
let banked_address = address & 0x3FFF;
|
||||
pif_ram[data..data + size]
|
||||
.copy_from_slice(&cart.rom[banked_address as usize..banked_address as usize + size]);
|
||||
} else if address < 0x8000 {
|
||||
let banked_address = address as u32 - 0x4000 + (cart.rom_bank * 0x4000);
|
||||
pif_ram[data..data + size]
|
||||
.copy_from_slice(&cart.rom[banked_address as usize..banked_address as usize + size]);
|
||||
} else if (0xa000..0xc000).contains(&address) {
|
||||
if !cart.ram_enabled {
|
||||
for i in 0..size {
|
||||
pif_ram[data + i] = 0xff;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if cart.ram_bank > 3 {
|
||||
for i in 0..size {
|
||||
pif_ram[data + i] = 0;
|
||||
}
|
||||
} else {
|
||||
let banked_address = address - 0xA000 + (cart.ram_bank * 0x2000);
|
||||
pif_ram[data..data + size].copy_from_slice(
|
||||
&cart.ram[banked_address as usize..banked_address as usize + size],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_mbc3(
|
||||
pif_ram: &[u8],
|
||||
cart: &mut device::controller::gbcart::GbCart,
|
||||
address: u16,
|
||||
data: usize,
|
||||
size: usize,
|
||||
) {
|
||||
let value = pif_ram[data + size - 1];
|
||||
if address < 0x2000 {
|
||||
cart.ram_enabled = value & 0xf == 0xa;
|
||||
} else if address < 0x4000 {
|
||||
let bank = value & if cart.rom.len() > 2097152 { 0xff } else { 0x7f };
|
||||
cart.rom_bank = bank as u32;
|
||||
if cart.rom_bank == 0 {
|
||||
cart.rom_bank = 1;
|
||||
}
|
||||
} else if address < 0x6000 {
|
||||
cart.ram_bank = (value & 0xf) as u16;
|
||||
} else if address < 0x8000 {
|
||||
if !cart.set_latch && value != 0 {
|
||||
// not implemented
|
||||
}
|
||||
cart.set_latch = value != 0;
|
||||
} else if (0xa000..0xc000).contains(&address) {
|
||||
if !cart.ram_enabled {
|
||||
return;
|
||||
}
|
||||
if cart.ram_bank < 0x8 {
|
||||
let banked_address = address - 0xA000 + (cart.ram_bank * 0x2000);
|
||||
cart.ram[banked_address as usize..banked_address as usize + size]
|
||||
.copy_from_slice(&pif_ram[data..data + size]);
|
||||
} else {
|
||||
match cart.ram_bank {
|
||||
0x8 => {
|
||||
cart.latch_second = value;
|
||||
}
|
||||
0x9 => {
|
||||
cart.latch_minute = value;
|
||||
}
|
||||
0xA => {
|
||||
cart.latch_hour = value;
|
||||
}
|
||||
0xB => {
|
||||
cart.latch_day &= 0xFF00;
|
||||
cart.latch_day |= value as u16;
|
||||
}
|
||||
0xC => {
|
||||
cart.latch_day &= 0x00FF;
|
||||
cart.latch_day |= (value as u16) << 8;
|
||||
}
|
||||
_ => {
|
||||
panic!("Unsupported ram bank {:x}", cart.ram_bank);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("Unsupported write address {address:x}");
|
||||
}
|
||||
}
|
||||
|
||||
fn read_mbc3(
|
||||
pif_ram: &mut [u8],
|
||||
cart: &device::controller::gbcart::GbCart,
|
||||
address: u16,
|
||||
data: usize,
|
||||
size: usize,
|
||||
) {
|
||||
if address < 0x4000 {
|
||||
let banked_address = address & 0x3FFF;
|
||||
pif_ram[data..data + size]
|
||||
.copy_from_slice(&cart.rom[banked_address as usize..banked_address as usize + size]);
|
||||
} else if address < 0x8000 {
|
||||
let banked_address = address as u32 - 0x4000 + (cart.rom_bank * 0x4000);
|
||||
pif_ram[data..data + size]
|
||||
.copy_from_slice(&cart.rom[banked_address as usize..banked_address as usize + size]);
|
||||
} else if (0xa000..0xc000).contains(&address) {
|
||||
if !cart.ram_enabled {
|
||||
for i in 0..size {
|
||||
pif_ram[data + i] = 0xff;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if cart.ram_bank < 0x8 {
|
||||
let banked_address = address - 0xA000 + (cart.ram_bank * 0x2000);
|
||||
pif_ram[data..data + size].copy_from_slice(
|
||||
cart.ram
|
||||
.get(banked_address as usize..banked_address as usize + size)
|
||||
.unwrap_or(&vec![0; size]),
|
||||
);
|
||||
} else {
|
||||
let latch = match cart.ram_bank {
|
||||
0x8 => cart.latch_second,
|
||||
0x9 => cart.latch_minute,
|
||||
0xA => cart.latch_hour,
|
||||
0xB => cart.latch_day as u8,
|
||||
0xC => (cart.latch_day >> 8) as u8,
|
||||
_ => {
|
||||
panic!("Unsupported ram bank {:x}", cart.ram_bank);
|
||||
}
|
||||
};
|
||||
for i in 0..size {
|
||||
pif_ram[data + i] = latch;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("Unsupported read address {address:x}");
|
||||
}
|
||||
}
|
||||
|
||||
fn write_mbc5(
|
||||
pif_ram: &[u8],
|
||||
cart: &mut device::controller::gbcart::GbCart,
|
||||
address: u16,
|
||||
data: usize,
|
||||
size: usize,
|
||||
) {
|
||||
let value = pif_ram[data + size - 1];
|
||||
if address < 0x2000 {
|
||||
cart.ram_enabled = value & 0xf == 0xa;
|
||||
} else if address < 0x3000 {
|
||||
cart.rom_bank &= 0xff00;
|
||||
cart.rom_bank |= value as u32;
|
||||
} else if address < 0x4000 {
|
||||
cart.rom_bank &= 0x00ff;
|
||||
cart.rom_bank |= ((value & 0x1) as u32) << 8;
|
||||
} else if address < 0x6000 {
|
||||
cart.ram_bank = (value & 0xf) as u16;
|
||||
} else if address < 0xa000 {
|
||||
println!("Unknown MBC5 write address {address:x}");
|
||||
} else if (0xa000..0xc000).contains(&address) {
|
||||
if !cart.ram_enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
let banked_address = address - 0xA000 + (cart.ram_bank << 13);
|
||||
cart.ram[banked_address as usize..banked_address as usize + size]
|
||||
.copy_from_slice(&pif_ram[data..data + size]);
|
||||
} else {
|
||||
panic!("Unsupported write address {address:x}");
|
||||
}
|
||||
}
|
||||
|
||||
fn read_mbc5(
|
||||
pif_ram: &mut [u8],
|
||||
cart: &device::controller::gbcart::GbCart,
|
||||
address: u16,
|
||||
data: usize,
|
||||
size: usize,
|
||||
) {
|
||||
if address < 0x4000 {
|
||||
let banked_address = address & 0x3FFF;
|
||||
pif_ram[data..data + size]
|
||||
.copy_from_slice(&cart.rom[banked_address as usize..banked_address as usize + size]);
|
||||
} else if address < 0x8000 {
|
||||
let banked_address = address as u32 - 0x4000 + (cart.rom_bank << 14);
|
||||
pif_ram[data..data + size]
|
||||
.copy_from_slice(&cart.rom[banked_address as usize..banked_address as usize + size]);
|
||||
} else if (0xa000..0xc000).contains(&address) {
|
||||
if !cart.ram_enabled {
|
||||
for i in 0..size {
|
||||
pif_ram[data + i] = 0xff;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let banked_address = address - 0xA000 + (cart.ram_bank << 13);
|
||||
pif_ram[data..data + size]
|
||||
.copy_from_slice(&cart.ram[banked_address as usize..banked_address as usize + size]);
|
||||
} else {
|
||||
panic!("Unsupported read address {address:x}");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(
|
||||
pif_ram: &mut [u8],
|
||||
cart: &device::controller::gbcart::GbCart,
|
||||
address: u16,
|
||||
data: usize,
|
||||
size: usize,
|
||||
) {
|
||||
if !cart.enabled {
|
||||
for i in 0..size {
|
||||
pif_ram[data + i] = 0x00;
|
||||
}
|
||||
return;
|
||||
}
|
||||
match cart.cart_type {
|
||||
CartType::MBC1RamBatt => read_mbc1(pif_ram, cart, address, data, size),
|
||||
CartType::MBC3RamBatt => read_mbc3(pif_ram, cart, address, data, size),
|
||||
CartType::MBC3RamBattRtc => read_mbc3(pif_ram, cart, address, data, size),
|
||||
CartType::MBC5RamBatt => read_mbc5(pif_ram, cart, address, data, size),
|
||||
_ => panic!("Unsupported cart type"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(
|
||||
pif_ram: &[u8],
|
||||
cart: &mut device::controller::gbcart::GbCart,
|
||||
address: u16,
|
||||
data: usize,
|
||||
size: usize,
|
||||
) {
|
||||
if !cart.enabled {
|
||||
return;
|
||||
}
|
||||
match cart.cart_type {
|
||||
CartType::MBC1RamBatt => write_mbc1(pif_ram, cart, address, data, size),
|
||||
CartType::MBC3RamBatt => write_mbc3(pif_ram, cart, address, data, size),
|
||||
CartType::MBC3RamBattRtc => write_mbc3(pif_ram, cart, address, data, size),
|
||||
CartType::MBC5RamBatt => write_mbc5(pif_ram, cart, address, data, size),
|
||||
_ => panic!("Unsupported cart type"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cart_type(data: u8) -> CartType {
|
||||
match data {
|
||||
0x03 => CartType::MBC1RamBatt,
|
||||
0x10 => CartType::MBC3RamBattRtc,
|
||||
0x13 => CartType::MBC3RamBatt,
|
||||
0x1b => CartType::MBC5RamBatt,
|
||||
_ => panic!("Unsupported cart type {data:x}"),
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,18 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
|
||||
pub const MEMPAK_SIZE: usize = 0x8000;
|
||||
const MPK_PAGE_SIZE: usize = 256;
|
||||
|
||||
fn format_mempak(device: &mut device::Device) {
|
||||
if device.ui.saves.mempak.0.len() < MEMPAK_SIZE * 4 {
|
||||
device.ui.saves.mempak.0.resize(MEMPAK_SIZE * 4, 0);
|
||||
if device.ui.storage.saves.mempak.data.len() < MEMPAK_SIZE * 4 {
|
||||
device
|
||||
.ui
|
||||
.storage
|
||||
.saves
|
||||
.mempak
|
||||
.data
|
||||
.resize(MEMPAK_SIZE * 4, 0);
|
||||
|
||||
let page_0: [u8; MPK_PAGE_SIZE] = [
|
||||
/* Label area */
|
||||
@@ -38,28 +45,31 @@ fn format_mempak(device: &mut device::Device) {
|
||||
let offset = i * MEMPAK_SIZE;
|
||||
|
||||
/* Fill Page 0 with pre-initialized content */
|
||||
device.ui.saves.mempak.0[offset..offset + MPK_PAGE_SIZE].copy_from_slice(&page_0);
|
||||
device.ui.storage.saves.mempak.data[offset..offset + MPK_PAGE_SIZE]
|
||||
.copy_from_slice(&page_0);
|
||||
|
||||
/* Fill INODE page 1 and update it's checkum */
|
||||
let start_page = 5;
|
||||
for i in MPK_PAGE_SIZE..2 * start_page {
|
||||
device.ui.saves.mempak.0[offset + i] = 0;
|
||||
device.ui.storage.saves.mempak.data[offset + i] = 0;
|
||||
}
|
||||
for i in (MPK_PAGE_SIZE + 2 * start_page..2 * MPK_PAGE_SIZE).step_by(2) {
|
||||
device.ui.saves.mempak.0[offset + i] = 0x00;
|
||||
device.ui.saves.mempak.0[offset + i + 1] = 0x03;
|
||||
device.ui.storage.saves.mempak.data[offset + i] = 0x00;
|
||||
device.ui.storage.saves.mempak.data[offset + i + 1] = 0x03;
|
||||
}
|
||||
device.ui.saves.mempak.0[offset + (MPK_PAGE_SIZE + 1)] = 0x71;
|
||||
device.ui.storage.saves.mempak.data[offset + (MPK_PAGE_SIZE + 1)] = 0x71;
|
||||
|
||||
/* Page 2 is identical to page 1 */
|
||||
let page1 = offset + MPK_PAGE_SIZE;
|
||||
let page2 = offset + 2 * MPK_PAGE_SIZE;
|
||||
let page1data = device.ui.saves.mempak.0[page1..page1 + MPK_PAGE_SIZE].to_vec();
|
||||
device.ui.saves.mempak.0[page2..page2 + MPK_PAGE_SIZE].copy_from_slice(&page1data);
|
||||
let page1data =
|
||||
device.ui.storage.saves.mempak.data[page1..page1 + MPK_PAGE_SIZE].to_vec();
|
||||
device.ui.storage.saves.mempak.data[page2..page2 + MPK_PAGE_SIZE]
|
||||
.copy_from_slice(&page1data);
|
||||
|
||||
/* Remaining pages DIR+DATA (3...) are initialized with 0x00 */
|
||||
for i in 3 * MPK_PAGE_SIZE..MEMPAK_SIZE - 3 * MPK_PAGE_SIZE {
|
||||
device.ui.saves.mempak.0[offset + i] = 0;
|
||||
device.ui.storage.saves.mempak.data[offset + i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,7 +81,7 @@ pub fn read(device: &mut device::Device, channel: usize, address: u16, data: usi
|
||||
|
||||
let offset = (channel * MEMPAK_SIZE) + address as usize;
|
||||
device.pif.ram[data..data + size]
|
||||
.copy_from_slice(&device.ui.saves.mempak.0[offset..offset + size])
|
||||
.copy_from_slice(&device.ui.storage.saves.mempak.data[offset..offset + size])
|
||||
} else {
|
||||
for i in 0..size {
|
||||
device.pif.ram[data + i] = 0;
|
||||
@@ -84,9 +94,9 @@ pub fn write(device: &mut device::Device, channel: usize, address: u16, data: us
|
||||
format_mempak(device);
|
||||
|
||||
let offset = (channel * MEMPAK_SIZE) + address as usize;
|
||||
device.ui.saves.mempak.0[offset..offset + size]
|
||||
device.ui.storage.saves.mempak.data[offset..offset + size]
|
||||
.copy_from_slice(&device.pif.ram[data..data + size]);
|
||||
|
||||
device.ui.saves.mempak.1 = true
|
||||
ui::storage::schedule_save(device, ui::storage::SaveTypes::Mempak);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,13 @@ pub fn read(device: &mut device::Device, _channel: usize, address: u16, data: us
|
||||
pub fn write(device: &mut device::Device, channel: usize, address: u16, data: usize, size: usize) {
|
||||
if address == 0xc000 {
|
||||
let rumble = device.pif.ram[data + size - 1];
|
||||
if device.netplay.is_none() {
|
||||
device::ui::input::set_rumble(&mut device.ui, channel, rumble);
|
||||
} else if device.netplay.as_ref().unwrap().player_number as usize == channel {
|
||||
device::ui::input::set_rumble(&mut device.ui, 0, rumble);
|
||||
|
||||
if let Some(netplay) = &device.netplay {
|
||||
if netplay.player_number as usize == channel {
|
||||
device::ui::input::set_rumble(&device.ui, 0, rumble);
|
||||
}
|
||||
} else {
|
||||
device::ui::input::set_rumble(&device.ui, channel, rumble);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
use crate::device;
|
||||
|
||||
#[derive(Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct TransferPak {
|
||||
pub enabled: bool,
|
||||
pub reset_state: u8,
|
||||
pub bank: u16,
|
||||
pub cart: device::controller::gbcart::GbCart,
|
||||
}
|
||||
|
||||
pub fn read(device: &mut device::Device, channel: usize, address: u16, data: usize, size: usize) {
|
||||
let pak = &mut device.transferpaks[channel];
|
||||
|
||||
match address >> 12 {
|
||||
0x8 => {
|
||||
let value = if pak.enabled { 0x84 } else { 0x00 };
|
||||
for i in 0..size {
|
||||
device.pif.ram[data + i] = value;
|
||||
}
|
||||
return;
|
||||
}
|
||||
_ => {
|
||||
if !pak.enabled {
|
||||
for i in 0..size {
|
||||
device.pif.ram[data + i] = 0x00;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match address >> 12 {
|
||||
0xB => {
|
||||
let mut value = 0;
|
||||
if pak.cart.enabled {
|
||||
value |= 1 << 0;
|
||||
}
|
||||
value |= (pak.reset_state & 3) << 2;
|
||||
if pak.enabled {
|
||||
value |= 1 << 7;
|
||||
}
|
||||
|
||||
if pak.cart.enabled && pak.reset_state == 3 {
|
||||
pak.reset_state = 2
|
||||
} else if !pak.cart.enabled && pak.reset_state == 2 {
|
||||
pak.reset_state = 1
|
||||
} else if !pak.cart.enabled && pak.reset_state == 1 {
|
||||
pak.reset_state = 0
|
||||
}
|
||||
for i in 0..size {
|
||||
device.pif.ram[data + i] = value;
|
||||
}
|
||||
}
|
||||
0xC..=0xF => {
|
||||
device::controller::gbcart::read(
|
||||
&mut device.pif.ram,
|
||||
&pak.cart,
|
||||
0x4000 * pak.bank + (address & 0x7fff) - 0x4000,
|
||||
data,
|
||||
size,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
println!("unknown transfer pak read {:x}", address >> 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(device: &mut device::Device, channel: usize, address: u16, data: usize, size: usize) {
|
||||
let pak = &mut device.transferpaks[channel];
|
||||
|
||||
let value = device.pif.ram[data + size - 1];
|
||||
match address >> 12 {
|
||||
0x8 => {
|
||||
match value {
|
||||
0xFE => {
|
||||
pak.enabled = false;
|
||||
}
|
||||
0x84 => {
|
||||
if !pak.enabled {
|
||||
pak.bank = 3;
|
||||
pak.cart.enabled = false;
|
||||
pak.reset_state = 0;
|
||||
}
|
||||
pak.enabled = true;
|
||||
}
|
||||
_ => println!("unknown transfer pak value"),
|
||||
}
|
||||
return;
|
||||
}
|
||||
_ => {
|
||||
if !pak.enabled {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match address >> 12 {
|
||||
0xA => {
|
||||
pak.bank = value as u16;
|
||||
if pak.bank > 3 {
|
||||
pak.bank = 0;
|
||||
}
|
||||
}
|
||||
0xB => {
|
||||
if value & 1 != 0 {
|
||||
if !pak.cart.enabled {
|
||||
pak.reset_state = 3;
|
||||
pak.cart.rom_bank = 1;
|
||||
pak.cart.ram_bank = 0;
|
||||
pak.cart.ram_enabled = false;
|
||||
pak.cart.cart_type =
|
||||
device::controller::gbcart::get_cart_type(pak.cart.rom[0x147]);
|
||||
}
|
||||
pak.cart.enabled = true;
|
||||
} else {
|
||||
pak.cart.enabled = false;
|
||||
}
|
||||
}
|
||||
0xC..=0xF => {
|
||||
device::controller::gbcart::write(
|
||||
&device.pif.ram,
|
||||
&mut pak.cart,
|
||||
0x4000 * pak.bank + (address & 0x7fff) - 0x4000,
|
||||
data,
|
||||
size,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
println!("unknown transfer pak write {:x}", address >> 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::{device, ui};
|
||||
use eframe::egui;
|
||||
use crate::device;
|
||||
#[cfg(feature = "gui")]
|
||||
use crate::ui;
|
||||
use std::collections::HashMap;
|
||||
|
||||
const JCMD_VRU_READ: u8 = 0x09;
|
||||
@@ -17,18 +18,17 @@ const VOICE_STATUS_BUSY: u8 = 0x05;
|
||||
const JDT_VRU: u16 = 0x0100; /* VRU */
|
||||
const CONT_FLAVOR: u16 = JDT_VRU;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Vru {
|
||||
pub status: u8,
|
||||
pub voice_state: u8,
|
||||
pub load_offset: u8,
|
||||
pub voice_init: u8,
|
||||
#[serde(with = "serde_big_array::BigArray")]
|
||||
pub word_buffer: [u16; 40],
|
||||
pub words: Vec<String>,
|
||||
pub talking: bool,
|
||||
pub word_mappings: HashMap<String, String>,
|
||||
pub window_notifier: Option<std::sync::mpsc::Sender<Vec<String>>>,
|
||||
pub word_receiver: Option<std::sync::mpsc::Receiver<String>>,
|
||||
pub gui_ctx: Option<egui::Context>,
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
@@ -120,7 +120,7 @@ pub fn process(device: &mut device::Device, channel: usize) {
|
||||
|
||||
let (res, _enc, errors) = encoding_rs::SHIFT_JIS.decode(&data);
|
||||
if errors {
|
||||
panic!("Failed to decode Japanese word {:X?}", data);
|
||||
panic!("Failed to decode Japanese word {data:X?}");
|
||||
} else {
|
||||
device.vru.words.push(res.to_string());
|
||||
}
|
||||
@@ -138,7 +138,7 @@ pub fn process(device: &mut device::Device, channel: usize) {
|
||||
if let Some(result) = word {
|
||||
device.vru.words.push(result.clone());
|
||||
} else {
|
||||
panic!("Unknown VRU word {}", data);
|
||||
panic!("Unknown VRU word {data}");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -160,14 +160,12 @@ pub fn process(device: &mut device::Device, channel: usize) {
|
||||
device.vru.voice_init = 2;
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::Vru,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize]
|
||||
+ (device.cpu.clock_rate * 2), // 2 seconds
|
||||
vru_talking_event,
|
||||
device::events::EVENT_TYPE_VRU,
|
||||
device.cpu.clock_rate * 2, // 2 seconds
|
||||
)
|
||||
} else if device.pif.ram[device.pif.channels[channel].rx_buf.unwrap()] == 0xEF {
|
||||
device.vru.talking = false;
|
||||
device::events::remove_event(device, device::events::EventType::Vru);
|
||||
device::events::remove_event(device, device::events::EVENT_TYPE_VRU);
|
||||
} else if device.pif.ram[device.pif.channels[channel].tx_buf.unwrap() + 3] == 0x2 {
|
||||
device.vru.voice_init = 0;
|
||||
device.vru.words.clear();
|
||||
@@ -178,21 +176,15 @@ pub fn process(device: &mut device::Device, channel: usize) {
|
||||
let offset = device.pif.channels[channel].tx_buf.unwrap() + 1;
|
||||
if u16::from_ne_bytes(device.pif.ram[offset..offset + 2].try_into().unwrap()) == 0 {
|
||||
device.vru.talking = false;
|
||||
device::events::remove_event(device, device::events::EventType::Vru);
|
||||
device::events::remove_event(device, device::events::EVENT_TYPE_VRU);
|
||||
}
|
||||
device.pif.ram[device.pif.channels[channel].rx_buf.unwrap()] = 0;
|
||||
}
|
||||
JCMD_VRU_READ => {
|
||||
let index = if device.vru.window_notifier.is_some() {
|
||||
ui::vru::prompt_for_match(
|
||||
&device.vru.words,
|
||||
device.vru.window_notifier.as_ref().unwrap(),
|
||||
device.vru.word_receiver.as_ref().unwrap(),
|
||||
device.vru.gui_ctx.as_ref().unwrap(),
|
||||
)
|
||||
} else {
|
||||
0x7FFF
|
||||
};
|
||||
#[cfg(feature = "gui")]
|
||||
let index = ui::vru::prompt_for_match(&device.vru.words, device.vi.frame_time);
|
||||
#[cfg(not(feature = "gui"))]
|
||||
let index = 0x7FFF;
|
||||
let num_results = if index == 0x7FFF { 0 } else { 1 };
|
||||
let data: HashMap<usize, u16> = HashMap::from([
|
||||
(0, 0x8000),
|
||||
@@ -246,11 +238,11 @@ pub fn process(device: &mut device::Device, channel: usize) {
|
||||
}
|
||||
device.vru.status = 0; /* status is always set to 0 after a write */
|
||||
}
|
||||
_ => panic!("unknown VRU command {}", cmd),
|
||||
_ => panic!("unknown VRU command {cmd}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn vru_talking_event(device: &mut device::Device) {
|
||||
pub fn vru_talking_event(device: &mut device::Device) {
|
||||
device.vru.talking = false
|
||||
}
|
||||
|
||||
@@ -2723,5 +2715,17 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("82B582E382C182CF82C282AF82A282A982A2"),
|
||||
String::from("restricted-depart"),
|
||||
),
|
||||
(
|
||||
String::from("009903720366006903C903FF0045033C"),
|
||||
String::from("objection"),
|
||||
),
|
||||
(
|
||||
String::from("041100CF02EE0381004503B7"),
|
||||
String::from("hold-it"),
|
||||
),
|
||||
(
|
||||
String::from("03BA019203C9036C0087000303C6"),
|
||||
String::from("take-that"),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::device;
|
||||
use crate::{device, savestates};
|
||||
|
||||
pub const COP0_INDEX_REG: u32 = 0;
|
||||
const COP0_RANDOM_REG: u32 = 1;
|
||||
@@ -30,13 +30,15 @@ pub const COP0_XCONTEXT_REG: u32 = 20;
|
||||
//const COP0_CACHEERR_REG: u32 = 27;
|
||||
pub const COP0_TAGLO_REG: u32 = 28;
|
||||
//const COP0_TAGHI_REG: u32 = 29;
|
||||
const COP0_ERROREPC_REG: u32 = 30;
|
||||
pub const COP0_ERROREPC_REG: u32 = 30;
|
||||
//const COP0_UNUSED_31: u32 = 31;
|
||||
pub const COP0_REGS_COUNT: u32 = 32;
|
||||
|
||||
pub const COP0_STATUS_IE: u64 = 1 << 0;
|
||||
pub const COP0_STATUS_EXL: u64 = 1 << 1;
|
||||
pub const COP0_STATUS_ERL: u64 = 1 << 2;
|
||||
pub const COP0_STATUS_SR: u64 = 1 << 20;
|
||||
pub const COP0_STATUS_TS: u64 = 1 << 21;
|
||||
pub const COP0_STATUS_BEV: u64 = 1 << 22;
|
||||
pub const COP0_STATUS_FR: u64 = 1 << 26;
|
||||
pub const COP0_STATUS_CU1: u64 = 1 << 29;
|
||||
@@ -55,7 +57,8 @@ pub const COP0_CAUSE_EXCCODE_CPU: u64 = 11 << 2;
|
||||
pub const COP0_CAUSE_EXCCODE_TR: u64 = 13 << 2;
|
||||
pub const COP0_CAUSE_EXCCODE_FPE: u64 = 15 << 2;
|
||||
pub const COP0_CAUSE_IP2: u64 = 1 << 10;
|
||||
const COP0_CAUSE_IP7: u64 = 1 << 15;
|
||||
pub const COP0_CAUSE_IP4: u64 = 1 << 12;
|
||||
pub const COP0_CAUSE_IP7: u64 = 1 << 15;
|
||||
pub const COP0_CAUSE_BD: u64 = 1 << 31;
|
||||
|
||||
pub const COP0_CAUSE_CE1: u64 = 1 << 28;
|
||||
@@ -88,15 +91,19 @@ const COP0_XCONTEXT_REG_MASK: u64 =
|
||||
const COP0_PARITYERR_REG_MASK: u64 = 0b00000000000000000000000011111111;
|
||||
const COP0_TAGLO_REG_MASK: u64 = 0b00001111111111111111111111000000;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Cop0 {
|
||||
pub reg_latch: u64,
|
||||
pub regs: [u64; COP0_REGS_COUNT as usize],
|
||||
pub reg_write_masks: [u64; COP0_REGS_COUNT as usize],
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub instrs: [fn(&mut device::Device, u32); 32],
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub instrs2: [fn(&mut device::Device, u32); 32],
|
||||
pub tlb_lut_r: Vec<device::tlb::TlbLut>,
|
||||
pub tlb_lut_w: Vec<device::tlb::TlbLut>,
|
||||
pub tlb_entries: [device::tlb::TlbEntry; 32],
|
||||
pub is_event: bool,
|
||||
}
|
||||
|
||||
fn mfc0(device: &mut device::Device, opcode: u32) {
|
||||
@@ -174,6 +181,8 @@ pub fn reserved(device: &mut device::Device, _opcode: u32) {
|
||||
device::exceptions::reserved_exception(device, 0);
|
||||
}
|
||||
|
||||
pub fn emux_xdetect(_device: &mut device::Device, _opcode: u32) {}
|
||||
|
||||
fn get_control_registers(device: &device::Device, index: u32) -> u64 {
|
||||
match index {
|
||||
COP0_COUNT_REG => device.cpu.cop0.regs[index as usize] >> 1,
|
||||
@@ -209,16 +218,15 @@ fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64)
|
||||
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::Compare,
|
||||
device.cpu.cop0.regs[COP0_COUNT_REG as usize] + ((compare_event_diff as u64) << 1),
|
||||
compare_event,
|
||||
device::events::EVENT_TYPE_COMPARE,
|
||||
(compare_event_diff as u64) << 1,
|
||||
);
|
||||
device.cpu.cop0.regs[COP0_CAUSE_REG as usize] &= !COP0_CAUSE_IP7;
|
||||
}
|
||||
COP0_STATUS_REG => {
|
||||
if data & COP0_STATUS_FR != device.cpu.cop0.regs[index as usize] & COP0_STATUS_FR {
|
||||
device::cop1::set_fgr_registers(device, data)
|
||||
}
|
||||
COP0_STATUS_REG
|
||||
if data & COP0_STATUS_FR != device.cpu.cop0.regs[index as usize] & COP0_STATUS_FR =>
|
||||
{
|
||||
device::cop1::set_fgr_registers(device, data)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -230,15 +238,15 @@ fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64)
|
||||
device::exceptions::check_pending_interrupts(device);
|
||||
}
|
||||
|
||||
fn compare_event(device: &mut device::Device) {
|
||||
device.cpu.cop0.regs[COP0_CAUSE_REG as usize] &= !COP0_CAUSE_EXCCODE_MASK;
|
||||
device.cpu.cop0.regs[COP0_CAUSE_REG as usize] |= COP0_CAUSE_IP7;
|
||||
pub fn compare_event(device: &mut device::Device) {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP7;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
|
||||
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
|
||||
|
||||
device::events::create_event(
|
||||
device::events::create_event_at(
|
||||
device,
|
||||
device::events::EventType::Compare,
|
||||
device::events::EVENT_TYPE_COMPARE,
|
||||
device.cpu.next_event_count + (u32::MAX as u64),
|
||||
compare_event,
|
||||
);
|
||||
device::exceptions::check_pending_interrupts(device);
|
||||
}
|
||||
@@ -257,6 +265,78 @@ pub fn add_cycles(device: &mut device::Device, cycles: u64) {
|
||||
device.cpu.cop0.regs[COP0_COUNT_REG as usize] += cycles // COUNT_REG is shifted right 1 bit when read by MFC0
|
||||
}
|
||||
|
||||
pub fn map_instructions(device: &mut device::Device) {
|
||||
device.cpu.cop0.instrs = [
|
||||
device::cop0::mfc0, // 0
|
||||
device::cop0::dmfc0, // 1
|
||||
device::cop0::reserved, // 2
|
||||
device::cop0::reserved, // 3
|
||||
device::cop0::mtc0, // 4
|
||||
device::cop0::dmtc0, // 5
|
||||
device::cop0::reserved, // 6
|
||||
device::cop0::reserved, // 7
|
||||
device::cop0::reserved, // 8
|
||||
device::cop0::reserved, // 9
|
||||
device::cop0::reserved, // 10
|
||||
device::cop0::reserved, // 11
|
||||
device::cop0::reserved, // 12
|
||||
device::cop0::reserved, // 13
|
||||
device::cop0::reserved, // 14
|
||||
device::cop0::reserved, // 15
|
||||
device::cop0::execute_cp0, // 16
|
||||
device::cop0::reserved, // 17
|
||||
device::cop0::reserved, // 18
|
||||
device::cop0::reserved, // 19
|
||||
device::cop0::emux_xdetect, // 20
|
||||
device::cop0::reserved, // 21
|
||||
device::cop0::reserved, // 22
|
||||
device::cop0::reserved, // 23
|
||||
device::cop0::reserved, // 24
|
||||
device::cop0::reserved, // 25
|
||||
device::cop0::reserved, // 26
|
||||
device::cop0::reserved, // 27
|
||||
device::cop0::reserved, // 28
|
||||
device::cop0::reserved, // 29
|
||||
device::cop0::reserved, // 30
|
||||
device::cop0::reserved, // 31
|
||||
];
|
||||
|
||||
device.cpu.cop0.instrs2 = [
|
||||
device::cop0::reserved, // 0
|
||||
device::cop0::tlbr, // 1
|
||||
device::cop0::tlbwi, // 2
|
||||
device::cop0::reserved, // 3
|
||||
device::cop0::reserved, // 4
|
||||
device::cop0::reserved, // 5
|
||||
device::cop0::tlbwr, // 6
|
||||
device::cop0::reserved, // 7
|
||||
device::cop0::tlbp, // 8
|
||||
device::cop0::reserved, // 9
|
||||
device::cop0::reserved, // 10
|
||||
device::cop0::reserved, // 11
|
||||
device::cop0::reserved, // 12
|
||||
device::cop0::reserved, // 13
|
||||
device::cop0::reserved, // 14
|
||||
device::cop0::reserved, // 15
|
||||
device::cop0::reserved, // 16
|
||||
device::cop0::reserved, // 17
|
||||
device::cop0::reserved, // 18
|
||||
device::cop0::reserved, // 19
|
||||
device::cop0::reserved, // 20
|
||||
device::cop0::reserved, // 21
|
||||
device::cop0::reserved, // 22
|
||||
device::cop0::reserved, // 23
|
||||
device::cop0::eret, // 24
|
||||
device::cop0::reserved, // 25
|
||||
device::cop0::reserved, // 26
|
||||
device::cop0::reserved, // 27
|
||||
device::cop0::reserved, // 28
|
||||
device::cop0::reserved, // 29
|
||||
device::cop0::reserved, // 30
|
||||
device::cop0::reserved, // 31
|
||||
];
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
device.cpu.cop0.reg_write_masks = [
|
||||
COP0_INDEX_REG_MASK,
|
||||
@@ -293,75 +373,8 @@ pub fn init(device: &mut device::Device) {
|
||||
0, // 31
|
||||
];
|
||||
|
||||
device.cpu.cop0.instrs = [
|
||||
device::cop0::mfc0, // 0
|
||||
device::cop0::dmfc0, // 1
|
||||
device::cop0::reserved, // 2
|
||||
device::cop0::reserved, // 3
|
||||
device::cop0::mtc0, // 4
|
||||
device::cop0::dmtc0, // 5
|
||||
device::cop0::reserved, // 6
|
||||
device::cop0::reserved, // 7
|
||||
device::cop0::reserved, // 8
|
||||
device::cop0::reserved, // 9
|
||||
device::cop0::reserved, // 10
|
||||
device::cop0::reserved, // 11
|
||||
device::cop0::reserved, // 12
|
||||
device::cop0::reserved, // 13
|
||||
device::cop0::reserved, // 14
|
||||
device::cop0::reserved, // 15
|
||||
device::cop0::execute_cp0, // 16
|
||||
device::cop0::reserved, // 17
|
||||
device::cop0::reserved, // 18
|
||||
device::cop0::reserved, // 19
|
||||
device::cop0::reserved, // 20
|
||||
device::cop0::reserved, // 21
|
||||
device::cop0::reserved, // 22
|
||||
device::cop0::reserved, // 23
|
||||
device::cop0::reserved, // 24
|
||||
device::cop0::reserved, // 25
|
||||
device::cop0::reserved, // 26
|
||||
device::cop0::reserved, // 27
|
||||
device::cop0::reserved, // 28
|
||||
device::cop0::reserved, // 29
|
||||
device::cop0::reserved, // 30
|
||||
device::cop0::reserved, // 31
|
||||
];
|
||||
map_instructions(device);
|
||||
|
||||
device.cpu.cop0.instrs2 = [
|
||||
device::cop0::reserved, // 0
|
||||
device::cop0::tlbr, // 1
|
||||
device::cop0::tlbwi, // 2
|
||||
device::cop0::reserved, // 3
|
||||
device::cop0::reserved, // 4
|
||||
device::cop0::reserved, // 5
|
||||
device::cop0::tlbwr, // 6
|
||||
device::cop0::reserved, // 7
|
||||
device::cop0::tlbp, // 8
|
||||
device::cop0::reserved, // 9
|
||||
device::cop0::reserved, // 10
|
||||
device::cop0::reserved, // 11
|
||||
device::cop0::reserved, // 12
|
||||
device::cop0::reserved, // 13
|
||||
device::cop0::reserved, // 14
|
||||
device::cop0::reserved, // 15
|
||||
device::cop0::reserved, // 16
|
||||
device::cop0::reserved, // 17
|
||||
device::cop0::reserved, // 18
|
||||
device::cop0::reserved, // 19
|
||||
device::cop0::reserved, // 20
|
||||
device::cop0::reserved, // 21
|
||||
device::cop0::reserved, // 22
|
||||
device::cop0::reserved, // 23
|
||||
device::cop0::eret, // 24
|
||||
device::cop0::reserved, // 25
|
||||
device::cop0::reserved, // 26
|
||||
device::cop0::reserved, // 27
|
||||
device::cop0::reserved, // 28
|
||||
device::cop0::reserved, // 29
|
||||
device::cop0::reserved, // 30
|
||||
device::cop0::reserved, // 31
|
||||
];
|
||||
// taken from VR4300 manual
|
||||
device.cpu.cop0.regs[COP0_RANDOM_REG as usize] = 0b00000000000000000000000000011111;
|
||||
device.cpu.cop0.regs[COP0_CONFIG_REG as usize] = 0b01110000000001101110010001100000;
|
||||
@@ -372,10 +385,5 @@ pub fn init(device: &mut device::Device) {
|
||||
device.cpu.cop0.regs[COP0_BADVADDR_REG as usize] = 0xFFFFFFFF;
|
||||
device.cpu.cop0.regs[COP0_CONTEXT_REG as usize] = 0x7FFFF0;
|
||||
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::Compare,
|
||||
u32::MAX as u64,
|
||||
compare_event,
|
||||
)
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_COMPARE, u32::MAX as u64)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::device;
|
||||
use crate::{device, savestates};
|
||||
|
||||
//const FCR31_FLAG_INEXACT_BIT: u32 = 1 << 2;
|
||||
//const FCR31_FLAG_UNDERFLOW_BIT: u32 = 1 << 3;
|
||||
@@ -24,6 +24,7 @@ const FCR31_CAUSE_MASK: u32 = 0b00000000000000111111000000000000;
|
||||
const FCR31_ENABLE_MASK: u32 = 0b00000000000000000000111110000000;
|
||||
const FCR31_WRITE_MASK: u32 = 0b00000001100000111111111111111111;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Cop1 {
|
||||
pub fcr0: u32,
|
||||
pub fcr31: u32,
|
||||
@@ -31,11 +32,17 @@ pub struct Cop1 {
|
||||
//pub flush_mode: u32,
|
||||
pub fgr32: [[u8; 4]; 32],
|
||||
pub fgr64: [[u8; 8]; 32],
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub instrs: [fn(&mut device::Device, u32); 32],
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub b_instrs: [fn(&mut device::Device, u32); 4],
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub s_instrs: [fn(&mut device::Device, u32); 64],
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub d_instrs: [fn(&mut device::Device, u32); 64],
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub w_instrs: [fn(&mut device::Device, u32); 64],
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub l_instrs: [fn(&mut device::Device, u32); 64],
|
||||
}
|
||||
|
||||
@@ -448,10 +455,7 @@ pub fn get_fpr_double(device: &device::Device, index: usize) -> f64 {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
set_fgr_registers(device, 0);
|
||||
device.cpu.cop1.fcr0 = 0b101000000000;
|
||||
|
||||
pub fn map_instructions(device: &mut device::Device) {
|
||||
device.cpu.cop1.b_instrs = [
|
||||
device::fpu_instructions::bc1f, // 0
|
||||
device::fpu_instructions::bc1t, // 1
|
||||
@@ -763,6 +767,13 @@ pub fn init(device: &mut device::Device) {
|
||||
]
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
set_fgr_registers(device, 0);
|
||||
device.cpu.cop1.fcr0 = 0b101000000000;
|
||||
|
||||
map_instructions(device);
|
||||
}
|
||||
|
||||
pub fn set_fgr_registers(device: &mut device::Device, status_reg: u64) {
|
||||
// this method doesn't account for undocumented behaviour (accessing odd numbered registers in half mode)
|
||||
if (status_reg & device::cop0::COP0_STATUS_FR) == 0 {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::device;
|
||||
use crate::{device, savestates};
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Cop2 {
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub instrs: [fn(&mut device::Device, u32); 32],
|
||||
pub reg_latch: u64,
|
||||
}
|
||||
@@ -76,7 +78,7 @@ fn unusable(device: &mut device::Device, _opcode: u32) {
|
||||
device::exceptions::cop_unusable_exception(device, device::cop0::COP0_CAUSE_CE2)
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
pub fn map_instructions(device: &mut device::Device) {
|
||||
device.cpu.cop2.instrs = [
|
||||
device::cop2::mfc2, // 0
|
||||
device::cop2::dmfc2, // 1
|
||||
@@ -112,3 +114,7 @@ pub fn init(device: &mut device::Device) {
|
||||
device::cop2::reserved, // 31
|
||||
]
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
map_instructions(device);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::device;
|
||||
use crate::{device, savestates, ui};
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum State {
|
||||
Step,
|
||||
Take,
|
||||
@@ -11,10 +11,13 @@ pub enum State {
|
||||
Exception,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct BranchState {
|
||||
pub state: State,
|
||||
pub pc: u64,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Cpu {
|
||||
pub cop0: device::cop0::Cop0,
|
||||
pub cop1: device::cop1::Cop1,
|
||||
@@ -27,11 +30,15 @@ pub struct Cpu {
|
||||
pub hi: u64,
|
||||
pub running: bool,
|
||||
pub llbit: bool,
|
||||
pub overclock: bool,
|
||||
pub clock_rate: u64,
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub instrs: [fn(&mut device::Device, u32); 64],
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub special_instrs: [fn(&mut device::Device, u32); 64],
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub regimm_instrs: [fn(&mut device::Device, u32); 32],
|
||||
pub events: [device::events::Event; device::events::EventType::Count as usize],
|
||||
pub events: [device::events::Event; device::events::EVENT_TYPE_COUNT],
|
||||
pub next_event_count: u64,
|
||||
pub next_event: usize,
|
||||
}
|
||||
@@ -47,9 +54,7 @@ pub fn decode_opcode(device: &device::Device, opcode: u32) -> fn(&mut device::De
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
device.cpu.clock_rate = 93750000;
|
||||
|
||||
pub fn map_instructions(device: &mut device::Device) {
|
||||
device.cpu.instrs = [
|
||||
device::cop0::reserved, // SPECIAL
|
||||
device::cop0::reserved, // REGIMM
|
||||
@@ -216,6 +221,21 @@ pub fn init(device: &mut device::Device) {
|
||||
device::cop0::reserved, // 30
|
||||
device::cop0::reserved, // 31
|
||||
];
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
device.cpu.clock_rate = if !device.cpu.overclock {
|
||||
93750000
|
||||
} else {
|
||||
ui::video::onscreen_message(
|
||||
"Overclocking enabled, setting clock rate to 125 MHz",
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_SHORT,
|
||||
);
|
||||
125000000
|
||||
};
|
||||
|
||||
map_instructions(device);
|
||||
|
||||
device::cop0::init(device);
|
||||
device::cop1::init(device);
|
||||
device::cop2::init(device);
|
||||
|
||||
@@ -1,59 +1,82 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
|
||||
#[derive(PartialEq, Copy, Clone)]
|
||||
pub enum EventType {
|
||||
AI,
|
||||
SI,
|
||||
VI,
|
||||
PI,
|
||||
DP,
|
||||
SP,
|
||||
InterruptCheck,
|
||||
SPDma,
|
||||
Compare,
|
||||
Vru,
|
||||
PakSwitch,
|
||||
Count,
|
||||
}
|
||||
pub const EVENT_TYPE_NONE: usize = 0;
|
||||
pub const EVENT_TYPE_AI: usize = 1;
|
||||
pub const EVENT_TYPE_SI: usize = 2;
|
||||
pub const EVENT_TYPE_VI: usize = 3;
|
||||
pub const EVENT_TYPE_PI: usize = 4;
|
||||
pub const EVENT_TYPE_DP: usize = 5;
|
||||
pub const EVENT_TYPE_SP: usize = 6;
|
||||
pub const EVENT_TYPE_INT: usize = 7;
|
||||
pub const EVENT_TYPE_SPDMA: usize = 8;
|
||||
pub const EVENT_TYPE_COMPARE: usize = 9;
|
||||
pub const EVENT_TYPE_VRU: usize = 10;
|
||||
pub const EVENT_TYPE_PAK: usize = 11;
|
||||
pub const EVENT_TYPE_SAVE: usize = 12;
|
||||
pub const EVENT_TYPE_NMI: usize = 13;
|
||||
pub const EVENT_TYPE_COUNT: usize = 14;
|
||||
|
||||
#[derive(PartialEq, Copy, Clone)]
|
||||
#[derive(PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Event {
|
||||
pub enabled: bool,
|
||||
pub count: u64,
|
||||
pub handler: fn(&mut device::Device),
|
||||
}
|
||||
|
||||
pub fn create_event(
|
||||
device: &mut device::Device,
|
||||
name: EventType,
|
||||
when: u64,
|
||||
handler: fn(&mut device::Device),
|
||||
) {
|
||||
device.cpu.events[name as usize] = Event {
|
||||
pub fn create_event(device: &mut device::Device, name: usize, when: u64) {
|
||||
device.cpu.events[name] = Event {
|
||||
enabled: true,
|
||||
count: when,
|
||||
handler,
|
||||
count: device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + when,
|
||||
};
|
||||
set_next_event(device);
|
||||
}
|
||||
|
||||
pub fn get_event(device: &mut device::Device, name: EventType) -> Option<&mut Event> {
|
||||
if device.cpu.events[name as usize].enabled {
|
||||
return Some(&mut device.cpu.events[name as usize]);
|
||||
pub fn create_event_at(device: &mut device::Device, name: usize, when: u64) {
|
||||
device.cpu.events[name] = Event {
|
||||
enabled: true,
|
||||
count: when,
|
||||
};
|
||||
set_next_event(device);
|
||||
}
|
||||
|
||||
pub fn get_event(device: &device::Device, name: usize) -> Option<&Event> {
|
||||
if device.cpu.events[name].enabled {
|
||||
return Some(&device.cpu.events[name]);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn remove_event(device: &mut device::Device, name: EventType) {
|
||||
device.cpu.events[name as usize].enabled = false;
|
||||
pub fn remove_event(device: &mut device::Device, name: usize) {
|
||||
device.cpu.events[name].enabled = false;
|
||||
}
|
||||
|
||||
fn get_event_handler(name: usize) -> fn(&mut device::Device) {
|
||||
match name {
|
||||
EVENT_TYPE_AI => device::ai::dma_event,
|
||||
EVENT_TYPE_SI => device::si::dma_event,
|
||||
EVENT_TYPE_VI => device::vi::vertical_interrupt_event,
|
||||
EVENT_TYPE_PI => device::pi::dma_event,
|
||||
EVENT_TYPE_DP => device::rdp::rdp_interrupt_event,
|
||||
EVENT_TYPE_SP => device::rsp_interface::rsp_event,
|
||||
EVENT_TYPE_INT => device::exceptions::interrupt_exception,
|
||||
EVENT_TYPE_SPDMA => device::rsp_interface::fifo_pop,
|
||||
EVENT_TYPE_COMPARE => device::cop0::compare_event,
|
||||
EVENT_TYPE_VRU => device::controller::vru::vru_talking_event,
|
||||
EVENT_TYPE_PAK => device::controller::pak_switch_event,
|
||||
EVENT_TYPE_SAVE => ui::storage::save_event,
|
||||
EVENT_TYPE_NMI => device::exceptions::reset_event,
|
||||
_ => dummy_event,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trigger_event(device: &mut device::Device) {
|
||||
let next_event_name = device.cpu.next_event;
|
||||
device.cpu.events[next_event_name].enabled = false;
|
||||
|
||||
let handler = device.cpu.events[next_event_name].handler;
|
||||
let handler = get_event_handler(next_event_name);
|
||||
device.cpu.cop0.is_event = true;
|
||||
handler(device);
|
||||
device.cpu.cop0.is_event = false;
|
||||
set_next_event(device);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
|
||||
pub fn check_pending_interrupts(device: &mut device::Device) {
|
||||
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
|
||||
& device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize]
|
||||
& device::cop0::COP0_CAUSE_IP_MASK)
|
||||
== 0
|
||||
{
|
||||
// interrupt disabled, or no pending interrupts
|
||||
return;
|
||||
}
|
||||
|
||||
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
|
||||
& (device::cop0::COP0_STATUS_IE
|
||||
| device::cop0::COP0_STATUS_EXL
|
||||
@@ -20,6 +12,32 @@ pub fn check_pending_interrupts(device: &mut device::Device) {
|
||||
return;
|
||||
}
|
||||
|
||||
if device.mi.regs[device::mi::MI_INTR_REG as usize]
|
||||
& device.mi.regs[device::mi::MI_INTR_MASK_REG as usize]
|
||||
!= 0
|
||||
{
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
|
||||
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
|
||||
}
|
||||
|
||||
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
|
||||
& device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize]
|
||||
& device::cop0::COP0_CAUSE_IP_MASK)
|
||||
== 0
|
||||
{
|
||||
// interrupt disabled, or no pending interrupts
|
||||
return;
|
||||
}
|
||||
|
||||
if device.cpu.cop0.is_event {
|
||||
interrupt_exception(device);
|
||||
} else {
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_INT, 0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interrupt_exception(device: &mut device::Device) {
|
||||
exception_general(device, 0x180);
|
||||
}
|
||||
|
||||
@@ -133,6 +151,24 @@ pub fn tlb_miss_exception(
|
||||
exception_general(device, vector_offset)
|
||||
}
|
||||
|
||||
pub fn reset_event(device: &mut device::Device) {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &= !device::cop0::COP0_CAUSE_IP4;
|
||||
|
||||
device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] |= device::cop0::COP0_STATUS_ERL
|
||||
| device::cop0::COP0_STATUS_SR
|
||||
| device::cop0::COP0_STATUS_BEV;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] &= !device::cop0::COP0_STATUS_TS;
|
||||
|
||||
device.cpu.cop0.regs[device::cop0::COP0_ERROREPC_REG as usize] = device.cpu.pc;
|
||||
device.cpu.pc = 0xBFC00000;
|
||||
device.cpu.branch_state.state = device::cpu::State::Step;
|
||||
device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] = 0;
|
||||
|
||||
device::pif::reset_pif(device, true);
|
||||
|
||||
ui::video::onscreen_message("Game reset", ui::video::MESSAGE_LENGTH_MESSAGE_SHORT);
|
||||
}
|
||||
|
||||
fn exception_general(device: &mut device::Device, vector_offset: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_EXL
|
||||
== 0
|
||||
|
||||
@@ -21,9 +21,10 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
|
||||
let length = (value & mask) as u64;
|
||||
let data = std::str::from_utf8(
|
||||
&device.cart.is_viewer_buffer[0x20_usize..(0x20 + length) as usize],
|
||||
)
|
||||
.unwrap();
|
||||
print!("{}", data);
|
||||
);
|
||||
if let Ok(data) = data {
|
||||
print!("{}", data);
|
||||
}
|
||||
} else {
|
||||
let mut data = u32::from_be_bytes(
|
||||
device.cart.is_viewer_buffer[masked_address..masked_address + 4]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::device;
|
||||
use crate::{device, savestates};
|
||||
|
||||
const MM_RDRAM_DRAM: usize = 0x00000000;
|
||||
const MM_RDRAM_REGS: usize = 0x03f00000;
|
||||
@@ -16,9 +16,11 @@ const MM_SI_REGS: usize = 0x04800000;
|
||||
//const MM_DOM2_ADDR1: usize = 0x05000000;
|
||||
pub const MM_DOM2_ADDR2: usize = 0x08000000;
|
||||
pub const MM_CART_ROM: usize = 0x10000000;
|
||||
const MM_PIF_MEM: usize = 0x1fc00000;
|
||||
pub const MM_PIF_MEM: usize = 0x1fc00000;
|
||||
//const MM_DOM1_ADDR3: usize = 0x1fd00000;
|
||||
const MM_IS_VIEWER: usize = 0x13ff0000;
|
||||
pub const MM_SC64_BUFFER: usize = 0x1ffe0000;
|
||||
const MM_SC64_REGS: usize = 0x1fff0000;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum AccessType {
|
||||
@@ -26,20 +28,27 @@ pub enum AccessType {
|
||||
Read,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub enum AccessSize {
|
||||
// None = 0,
|
||||
#[default]
|
||||
None = 0,
|
||||
Word = 4,
|
||||
Dword = 8,
|
||||
Dcache = 16,
|
||||
Icache = 32,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Memory {
|
||||
pub fast_read: [fn(&mut device::Device, u64, AccessSize) -> u32; 0x2000], // fast_read is used for lookups that try to detect idle loops
|
||||
#[serde(skip, default = "savestates::default_memory_read_fast")]
|
||||
pub fast_read: [fn(&device::Device, u64, AccessSize) -> u32; 0x2000], // fast_read is used for lookups that try to detect idle loops
|
||||
#[serde(skip, default = "savestates::default_memory_read")]
|
||||
pub memory_map_read: [fn(&mut device::Device, u64, AccessSize) -> u32; 0x2000],
|
||||
#[serde(skip, default = "savestates::default_memory_write")]
|
||||
pub memory_map_write: [fn(&mut device::Device, u64, u32, u32); 0x2000],
|
||||
#[serde(with = "serde_big_array::BigArray")]
|
||||
pub icache: [device::cache::ICache; 512],
|
||||
#[serde(with = "serde_big_array::BigArray")]
|
||||
pub dcache: [device::cache::DCache; 512],
|
||||
}
|
||||
|
||||
@@ -53,11 +62,16 @@ pub fn masked_write_64(dst: &mut u64, value: u64, mask: u64) {
|
||||
|
||||
pub fn translate_address(
|
||||
device: &mut device::Device,
|
||||
address: u64,
|
||||
mut address: u64,
|
||||
access_type: AccessType,
|
||||
) -> (u64, bool, bool) {
|
||||
if (address & 0xc0000000) != 0x80000000 {
|
||||
return device::tlb::get_physical_address(device, address, access_type);
|
||||
if (address & 0xFFFFFFFF00000000) == 0x9000000000000000 {
|
||||
// this is a hack to support 64-bit addresses used by some homebrew
|
||||
address |= 0xA0000000;
|
||||
} else {
|
||||
return device::tlb::get_physical_address(device, address, access_type);
|
||||
}
|
||||
}
|
||||
(address & 0x1FFFFFFF, address & 0x20000000 == 0, false)
|
||||
}
|
||||
@@ -142,19 +156,27 @@ pub fn init(device: &mut device::Device) {
|
||||
device.memory.memory_map_read[i] = device::si::read_regs;
|
||||
device.memory.memory_map_write[i] = device::si::write_regs;
|
||||
} else if (MM_DOM2_ADDR2 >> 16..=(MM_DOM2_ADDR2 + 0x1FFFF) >> 16).contains(&i) {
|
||||
device.memory.memory_map_read[i] = device::sram::read_mem;
|
||||
device.memory.memory_map_write[i] = device::sram::write_mem;
|
||||
} else if i >= MM_CART_ROM >> 16 && i <= (MM_CART_ROM + device.cart.rom.len() - 1) >> 16 {
|
||||
device.memory.fast_read[i] = device::cart_rom::read_mem_fast;
|
||||
device.memory.memory_map_read[i] = device::cart_rom::read_mem;
|
||||
device.memory.memory_map_write[i] = device::cart_rom::write_mem;
|
||||
} else if i >= MM_IS_VIEWER >> 16 && i <= (MM_IS_VIEWER + 0xFFFF) >> 16 {
|
||||
device.memory.memory_map_read[i] = device::cart::sram::read_mem;
|
||||
device.memory.memory_map_write[i] = device::cart::sram::write_mem;
|
||||
} else if device.cart.rom.len() <= MM_IS_VIEWER - MM_CART_ROM
|
||||
&& i >= MM_IS_VIEWER >> 16
|
||||
&& i <= (MM_IS_VIEWER + 0xFFFF) >> 16
|
||||
{
|
||||
device.memory.memory_map_read[i] = device::is_viewer::read_mem;
|
||||
device.memory.memory_map_write[i] = device::is_viewer::write_mem;
|
||||
} else if (MM_CART_ROM >> 16..=(MM_CART_ROM + 0x0FBFFFFF) >> 16).contains(&i) {
|
||||
device.memory.fast_read[i] = device::cart::rom::read_mem_fast;
|
||||
device.memory.memory_map_read[i] = device::cart::rom::read_mem;
|
||||
device.memory.memory_map_write[i] = device::cart::rom::write_mem;
|
||||
} else if i >= MM_PIF_MEM >> 16 && i <= (MM_PIF_MEM + 0xFFFF) >> 16 {
|
||||
device.memory.memory_map_read[i] = device::pif::read_mem;
|
||||
device.memory.memory_map_write[i] = device::pif::write_mem;
|
||||
} else if i >= MM_SC64_BUFFER >> 16 && i <= (MM_SC64_BUFFER + 0xFFFF) >> 16 {
|
||||
device.memory.memory_map_read[i] = device::cart::sc64::read_mem;
|
||||
device.memory.memory_map_write[i] = device::cart::sc64::write_mem;
|
||||
} else if i >= MM_SC64_REGS >> 16 && i <= (MM_SC64_REGS + 0xFFFF) >> 16 {
|
||||
device.memory.memory_map_read[i] = device::cart::sc64::read_regs;
|
||||
device.memory.memory_map_write[i] = device::cart::sc64::write_regs;
|
||||
}
|
||||
}
|
||||
device::cache::init(device)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::device;
|
||||
|
||||
const MI_INIT_MODE_REG: u32 = 0;
|
||||
pub const MI_INIT_MODE_REG: u32 = 0;
|
||||
const MI_VERSION_REG: u32 = 1;
|
||||
const MI_INTR_REG: u32 = 2;
|
||||
const MI_INTR_MASK_REG: u32 = 3;
|
||||
pub const MI_INTR_REG: u32 = 2;
|
||||
pub const MI_INTR_MASK_REG: u32 = 3;
|
||||
pub const MI_REGS_COUNT: u32 = 4;
|
||||
|
||||
/* read */
|
||||
@@ -29,7 +29,7 @@ const MI_CLR_DP: u32 = 1 << 10;
|
||||
const MI_SET_DP: u32 = 1 << 11;
|
||||
|
||||
/* mode read */
|
||||
const MI_INIT_MODE: u32 = 1 << 7;
|
||||
pub const MI_INIT_MODE: u32 = 1 << 7;
|
||||
const MI_EBUS_MODE: u32 = 1 << 8;
|
||||
const MI_RDRAM_MODE: u32 = 1 << 9;
|
||||
|
||||
@@ -42,8 +42,9 @@ const MI_CLR_DP_INTR: u32 = 1 << 11;
|
||||
const MI_CLR_RDRAM: u32 = 1 << 12;
|
||||
const MI_SET_RDRAM: u32 = 1 << 13;
|
||||
|
||||
const MI_INIT_LENGTH_MASK: u32 = 0b1111111;
|
||||
pub const MI_INIT_LENGTH_MASK: u32 = 0b1111111;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Mi {
|
||||
pub regs: [u32; MI_REGS_COUNT as usize],
|
||||
}
|
||||
@@ -53,6 +54,7 @@ pub fn read_regs(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
device.mi.regs[((address & 0xFFFF) >> 2) as usize]
|
||||
}
|
||||
|
||||
@@ -64,11 +66,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
_ => device::memory::masked_write_32(&mut device.mi.regs[reg as usize], value, mask),
|
||||
}
|
||||
|
||||
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
|
||||
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
|
||||
} else {
|
||||
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] == 0 {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
|
||||
!device::cop0::COP0_CAUSE_IP2;
|
||||
}
|
||||
@@ -80,9 +78,15 @@ fn update_init_mode(device: &mut device::Device, w: u32) {
|
||||
device.mi.regs[MI_INIT_MODE_REG as usize] |= w & MI_INIT_LENGTH_MASK;
|
||||
|
||||
if w & MI_CLR_INIT != 0 {
|
||||
for i in 0..(0x3F00000 >> 16) {
|
||||
device.memory.memory_map_write[i] = device::rdram::write_mem;
|
||||
}
|
||||
device.mi.regs[MI_INIT_MODE_REG as usize] &= !MI_INIT_MODE
|
||||
}
|
||||
if w & MI_SET_INIT != 0 {
|
||||
for i in 0..(0x3F00000 >> 16) {
|
||||
device.memory.memory_map_write[i] = device::rdram::write_mem_repeat;
|
||||
}
|
||||
device.mi.regs[MI_INIT_MODE_REG as usize] |= MI_INIT_MODE
|
||||
}
|
||||
if w & MI_CLR_EBUS != 0 {
|
||||
@@ -149,37 +153,13 @@ pub fn clear_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
|
||||
!device::cop0::COP0_CAUSE_IP2;
|
||||
}
|
||||
device::exceptions::check_pending_interrupts(device)
|
||||
}
|
||||
|
||||
pub fn set_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
|
||||
device.mi.regs[MI_INTR_REG as usize] |= interrupt;
|
||||
|
||||
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
|
||||
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
|
||||
}
|
||||
device::exceptions::check_pending_interrupts(device)
|
||||
}
|
||||
|
||||
pub fn schedule_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
|
||||
device.mi.regs[MI_INTR_REG as usize] |= interrupt;
|
||||
|
||||
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
|
||||
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
|
||||
}
|
||||
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::InterruptCheck,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize],
|
||||
device::exceptions::check_pending_interrupts,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
device.mi.regs[MI_VERSION_REG as usize] = 0x02020102
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
use rand::Rng;
|
||||
|
||||
const PI_DRAM_ADDR_REG: u32 = 0;
|
||||
const PI_CART_ADDR_REG: u32 = 1;
|
||||
@@ -26,6 +28,7 @@ const PI_STATUS_INTERRUPT: u32 = 1 << 3;
|
||||
const PI_STATUS_RESET: u32 = 1 << 0;
|
||||
const PI_STATUS_CLR_INTR: u32 = 1 << 1;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Pi {
|
||||
pub regs: [u32; PI_REGS_COUNT as usize],
|
||||
}
|
||||
@@ -40,6 +43,7 @@ pub fn read_regs(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
PI_WR_LEN_REG | PI_RD_LEN_REG => 0x7F,
|
||||
@@ -63,12 +67,7 @@ fn dma_read(device: &mut device::Device) {
|
||||
|
||||
let cycles = (handler.read)(device, cart_addr, dram_addr, length);
|
||||
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::PI,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + cycles,
|
||||
dma_event,
|
||||
);
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_PI, cycles);
|
||||
|
||||
/* Update PI_DRAM_ADDR_REG and PI_CART_ADDR_REG */
|
||||
device.pi.regs[PI_DRAM_ADDR_REG as usize] =
|
||||
@@ -94,14 +93,12 @@ fn dma_write(device: &mut device::Device) {
|
||||
length -= dram_addr & 0x7;
|
||||
}
|
||||
|
||||
// check RDP before PI writes to RDRAM
|
||||
ui::video::check_framebuffers(dram_addr, length);
|
||||
|
||||
let cycles = (handler.write)(device, cart_addr, dram_addr, length);
|
||||
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::PI,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + cycles,
|
||||
dma_event,
|
||||
);
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_PI, cycles);
|
||||
|
||||
/* Update PI_DRAM_ADDR_REG and PI_CART_ADDR_REG */
|
||||
device.pi.regs[PI_DRAM_ADDR_REG as usize] =
|
||||
@@ -114,17 +111,27 @@ fn dma_write(device: &mut device::Device) {
|
||||
|
||||
fn get_handler(address: u32) -> PiHandler {
|
||||
let mut handler = PiHandler {
|
||||
read: device::cart_rom::dma_read,
|
||||
write: device::cart_rom::dma_write,
|
||||
read: device::cart::rom::dma_read,
|
||||
write: device::cart::rom::dma_write,
|
||||
};
|
||||
if address >= device::memory::MM_CART_ROM as u32 {
|
||||
handler.read = device::cart_rom::dma_read;
|
||||
handler.write = device::cart_rom::dma_write;
|
||||
} else if address >= device::memory::MM_DOM2_ADDR2 as u32 {
|
||||
handler.read = device::sram::dma_read;
|
||||
handler.write = device::sram::dma_write;
|
||||
if address >= device::memory::MM_SC64_BUFFER as u32
|
||||
&& address < (device::memory::MM_SC64_BUFFER + 0x2000) as u32
|
||||
{
|
||||
handler.read = device::cart::sc64::dma_read;
|
||||
handler.write = device::cart::sc64::dma_write;
|
||||
} else if address >= device::memory::MM_CART_ROM as u32
|
||||
&& address < device::memory::MM_PIF_MEM as u32
|
||||
{
|
||||
handler.read = device::cart::rom::dma_read;
|
||||
handler.write = device::cart::rom::dma_write;
|
||||
} else if address >= device::memory::MM_DOM2_ADDR2 as u32
|
||||
&& address < device::memory::MM_CART_ROM as u32
|
||||
{
|
||||
handler.read = device::cart::sram::dma_read;
|
||||
handler.write = device::cart::sram::dma_write;
|
||||
} else {
|
||||
panic!("unknown pi handler")
|
||||
handler.read = unknown_dma_read;
|
||||
handler.write = unknown_dma_write;
|
||||
}
|
||||
handler
|
||||
}
|
||||
@@ -149,15 +156,24 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
device.pi.regs[PI_STATUS_REG as usize] = 0
|
||||
}
|
||||
}
|
||||
PI_BSD_DOM1_LAT_REG | PI_BSD_DOM1_PWD_REG | PI_BSD_DOM1_PGS_REG | PI_BSD_DOM1_RLS_REG
|
||||
| PI_BSD_DOM2_LAT_REG | PI_BSD_DOM2_PWD_REG | PI_BSD_DOM2_PGS_REG | PI_BSD_DOM2_RLS_REG => {
|
||||
PI_BSD_DOM1_LAT_REG | PI_BSD_DOM1_PWD_REG | PI_BSD_DOM2_LAT_REG | PI_BSD_DOM2_PWD_REG => {
|
||||
device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value & 0xFF, mask)
|
||||
}
|
||||
PI_BSD_DOM1_PGS_REG | PI_BSD_DOM2_PGS_REG => {
|
||||
device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value & 0xF, mask)
|
||||
}
|
||||
PI_BSD_DOM1_RLS_REG | PI_BSD_DOM2_RLS_REG => {
|
||||
device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value & 0x3, mask)
|
||||
}
|
||||
_ => device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value, mask),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_cycles(device: &device::Device, domain: i32, length: u32) -> u64 {
|
||||
fn randomize_interrupt_time(rng: &mut rand::rngs::Xoshiro256PlusPlus) -> u64 {
|
||||
rng.next_u64() % 0x10
|
||||
}
|
||||
|
||||
pub fn calculate_cycles(device: &mut device::Device, domain: i32, length: u32) -> u64 {
|
||||
let mut cycles: f64 = 0.0;
|
||||
let (page_size, latency, pulse_width, release, pages);
|
||||
let page_size_base: f64 = 2.0;
|
||||
@@ -180,6 +196,7 @@ pub fn calculate_cycles(device: &device::Device, domain: i32, length: u32) -> u6
|
||||
cycles += (14.0 + latency) * pages;
|
||||
cycles += (pulse_width + release) * (length as f64 / 2.0);
|
||||
cycles += 5.0 * pages;
|
||||
cycles += randomize_interrupt_time(&mut device.rng) as f64;
|
||||
(cycles * 1.5) as u64 // Converting RCP clock speed to CPU clock speed
|
||||
}
|
||||
|
||||
@@ -189,3 +206,30 @@ pub fn dma_event(device: &mut device::Device) {
|
||||
|
||||
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_PI)
|
||||
}
|
||||
|
||||
fn unknown_dma_read(
|
||||
device: &mut device::Device,
|
||||
mut _cart_addr: u32,
|
||||
mut _dram_addr: u32,
|
||||
length: u32,
|
||||
) -> u64 {
|
||||
device::pi::calculate_cycles(device, 1, length)
|
||||
}
|
||||
|
||||
fn unknown_dma_write(
|
||||
device: &mut device::Device,
|
||||
mut _cart_addr: u32,
|
||||
mut dram_addr: u32,
|
||||
length: u32,
|
||||
) -> u64 {
|
||||
dram_addr &= device::rdram::RDRAM_MASK as u32;
|
||||
|
||||
for i in 0..length {
|
||||
*device
|
||||
.rdram
|
||||
.mem
|
||||
.get_mut((dram_addr + i) as usize ^ device.byte_swap)
|
||||
.unwrap_or(&mut 0) = 0;
|
||||
}
|
||||
device::pi::calculate_cycles(device, 1, length)
|
||||
}
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
mod rom;
|
||||
use crate::device;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Pif {
|
||||
#[serde(with = "serde_big_array::BigArray")]
|
||||
pub rom: [u8; 1984],
|
||||
#[serde(with = "serde_big_array::BigArray")]
|
||||
pub ram: [u8; 64],
|
||||
pub channels: [PifChannel; 5],
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct PifChannel {
|
||||
pub tx: Option<usize>,
|
||||
pub tx_buf: Option<usize>,
|
||||
pub rx: Option<usize>,
|
||||
pub rx_buf: Option<usize>,
|
||||
#[serde(skip)]
|
||||
pub process: Option<fn(&mut device::Device, usize)>,
|
||||
pub pak_handler: Option<device::controller::PakHandler>,
|
||||
pub change_pak: device::controller::PakType,
|
||||
@@ -51,7 +55,7 @@ pub fn read_mem(
|
||||
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
let mut masked_address = address as usize & PIF_MASK;
|
||||
if masked_address < PIF_RAM_OFFSET {
|
||||
panic! {"write to pif rom"}
|
||||
panic!("write to pif rom")
|
||||
}
|
||||
masked_address -= PIF_RAM_OFFSET;
|
||||
let mut data = u32::from_be_bytes(
|
||||
@@ -63,12 +67,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
|
||||
device.pif.ram[masked_address..masked_address + 4].copy_from_slice(&data.to_be_bytes());
|
||||
|
||||
device.si.dma_dir = device::si::DmaDir::Write;
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::SI,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + 3200,
|
||||
device::si::dma_event,
|
||||
); //based on https://github.com/rasky/n64-systembench
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_SI, 3200); //based on https://github.com/rasky/n64-systembench
|
||||
device.si.regs[device::si::SI_STATUS_REG as usize] |=
|
||||
device::si::SI_STATUS_DMA_BUSY | device::si::SI_STATUS_IO_BUSY
|
||||
}
|
||||
@@ -217,37 +216,82 @@ pub fn process_ram(device: &mut device::Device) {
|
||||
device.pif.ram[0x3F] &= !clrmask
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
pub fn connect_pif_channels(device: &mut device::Device) {
|
||||
for i in 0..4 {
|
||||
if let Some(netplay) = &device.netplay {
|
||||
if netplay.player_data[i].reg_id != 0 {
|
||||
device.pif.channels[i].process = Some(device::controller::process);
|
||||
}
|
||||
} else if device.ui.config.input.controller_enabled[i] {
|
||||
device.pif.channels[i].process = Some(device::controller::process);
|
||||
}
|
||||
}
|
||||
if device.ui.config.input.emulate_vru && device.netplay.is_none() {
|
||||
device.pif.channels[3].process = Some(device::controller::vru::process);
|
||||
}
|
||||
device.pif.channels[4].process = Some(device::cart::process)
|
||||
}
|
||||
|
||||
fn get_default_handler(device: &device::Device) -> device::controller::PakHandler {
|
||||
if device.ui.game_id == "NCT" {
|
||||
// Chameleon Twist does not support the mempak
|
||||
device::controller::PakHandler {
|
||||
read: device::controller::rumble::read,
|
||||
write: device::controller::rumble::write,
|
||||
pak_type: device::controller::PakType::RumblePak,
|
||||
}
|
||||
} else {
|
||||
device::controller::PakHandler {
|
||||
read: device::controller::mempak::read,
|
||||
write: device::controller::mempak::write,
|
||||
pak_type: device::controller::PakType::MemPak,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_pif(device: &mut device::Device, is_nmi_reset: bool) {
|
||||
if device.cart.pal {
|
||||
device.pif.rom = rom::PAL_PIF_ROM;
|
||||
} else {
|
||||
device.pif.rom = rom::NTSC_PIF_ROM;
|
||||
}
|
||||
device.pif.ram[0x26] = device.cart.cic_seed;
|
||||
device.pif.ram[0x27] = device.cart.cic_seed;
|
||||
|
||||
let mempak_handler = device::controller::PakHandler {
|
||||
read: device::controller::mempak::read,
|
||||
write: device::controller::mempak::write,
|
||||
pak_type: device::controller::PakType::MemPak,
|
||||
let reset_type = u32::from(is_nmi_reset);
|
||||
let rom_type = 0u32;
|
||||
let s7 = 0u32;
|
||||
let cic = u32::from(device.cart.cic_seed);
|
||||
let word = (rom_type << 19) | (s7 << 18) | (reset_type << 17) | (cic << 8) | 0x3f;
|
||||
device.pif.ram[0x24..0x28].copy_from_slice(&word.to_be_bytes());
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
reset_pif(device, false);
|
||||
|
||||
let default_handler = get_default_handler(device);
|
||||
let tpak_handler = device::controller::PakHandler {
|
||||
read: device::controller::transferpak::read,
|
||||
write: device::controller::transferpak::write,
|
||||
pak_type: device::controller::PakType::TransferPak,
|
||||
};
|
||||
|
||||
connect_pif_channels(device);
|
||||
|
||||
for i in 0..4 {
|
||||
if device.netplay.is_none() {
|
||||
if device.ui.config.input.controller_enabled[i] {
|
||||
device.pif.channels[i].pak_handler = Some(mempak_handler);
|
||||
device.pif.channels[i].process = Some(device::controller::process);
|
||||
if let Some(netplay) = &device.netplay {
|
||||
if netplay.player_data[i].reg_id != 0 {
|
||||
device.pif.channels[i].pak_handler = Some(default_handler);
|
||||
}
|
||||
} else if device.ui.config.input.controller_enabled[i] {
|
||||
if device.transferpaks[i].cart.rom.is_empty() {
|
||||
device.pif.channels[i].pak_handler = Some(default_handler);
|
||||
} else {
|
||||
device.pif.channels[i].pak_handler = Some(tpak_handler);
|
||||
}
|
||||
} else if device.netplay.as_ref().unwrap().player_data[i].reg_id != 0 {
|
||||
device.pif.channels[i].pak_handler = Some(mempak_handler);
|
||||
device.pif.channels[i].process = Some(device::controller::process);
|
||||
}
|
||||
}
|
||||
if device.ui.config.input.emulate_vru {
|
||||
if device.ui.config.input.emulate_vru && device.netplay.is_none() {
|
||||
device.pif.channels[3].pak_handler = None;
|
||||
device.pif.channels[3].process = Some(device::controller::vru::process);
|
||||
}
|
||||
device.pif.channels[4].process = Some(device::cart::process)
|
||||
}
|
||||
|
||||
fn process_cic_challenge(device: &mut device::Device) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
use crate::{device, savestates, ui};
|
||||
|
||||
pub const DPC_START_REG: u32 = 0;
|
||||
pub const DPC_END_REG: u32 = 1;
|
||||
@@ -41,10 +40,12 @@ const DPC_CLR_PIPE_CTR: u32 = 1 << 7;
|
||||
const DPC_CLR_CMD_CTR: u32 = 1 << 8;
|
||||
const DPC_CLR_CLOCK_CTR: u32 = 1 << 9;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Rdp {
|
||||
pub regs_dpc: [u32; DPC_REGS_COUNT as usize],
|
||||
pub regs_dps: [u32; DPS_REGS_COUNT as usize],
|
||||
pub wait_frozen: bool,
|
||||
pub last_status_value: u32,
|
||||
}
|
||||
|
||||
pub fn read_regs_dpc(
|
||||
@@ -52,7 +53,29 @@ pub fn read_regs_dpc(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device.rdp.regs_dpc[((address & 0xFFFF) >> 2) as usize]
|
||||
if !device.rsp.cpu.running {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
}
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
DPC_STATUS_REG => {
|
||||
let value =
|
||||
device.rdp.regs_dpc[reg as usize] & (DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY);
|
||||
if value == device.rdp.last_status_value && value != 0 {
|
||||
device.rsp.cpu.sync_point = true;
|
||||
}
|
||||
device.rdp.last_status_value = value;
|
||||
device.rdp.regs_dpc[reg as usize]
|
||||
}
|
||||
DPC_CLOCK_REG => 0xFFFFFF, // needed for JFG
|
||||
DPC_CURRENT_REG => {
|
||||
if device.rdp.wait_frozen {
|
||||
device.rsp.cpu.sync_point = true;
|
||||
}
|
||||
device.rdp.regs_dpc[reg as usize]
|
||||
}
|
||||
_ => device.rdp.regs_dpc[reg as usize],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_regs_dpc(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
@@ -93,14 +116,21 @@ pub fn write_regs_dpc(device: &mut device::Device, address: u64, value: u32, mas
|
||||
|
||||
fn run_rdp(device: &mut device::Device) {
|
||||
let timer = ui::video::process_rdp_list();
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |=
|
||||
DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY;
|
||||
device.rdp.regs_dpc[DPC_PIPEBUSY_REG as usize] = 0xFFFFFF;
|
||||
|
||||
if timer != 0 {
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::DP,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + timer,
|
||||
rdp_interrupt_event,
|
||||
)
|
||||
if device.save_state {
|
||||
// Right after full sync, good time for save state
|
||||
device.save_state = false;
|
||||
savestates::create_savestate(device);
|
||||
} else if device.load_state {
|
||||
// Right after full sync, good time for save state
|
||||
device.load_state = false;
|
||||
savestates::load_savestate(device);
|
||||
}
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_DP, timer)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +139,7 @@ pub fn read_regs_dps(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
device.rdp.regs_dps[((address & 0xFFFF) >> 2) as usize]
|
||||
}
|
||||
|
||||
@@ -171,8 +202,12 @@ fn update_dpc_status(device: &mut device::Device, w: u32) {
|
||||
pub fn init(device: &mut device::Device) {
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |=
|
||||
DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CBUF_READY;
|
||||
device.rdp.regs_dpc[DPC_PIPEBUSY_REG as usize] = 0xFFFFFF;
|
||||
}
|
||||
|
||||
fn rdp_interrupt_event(device: &mut device::Device) {
|
||||
pub fn rdp_interrupt_event(device: &mut device::Device) {
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &=
|
||||
!(DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY);
|
||||
|
||||
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_DP)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use crate::device;
|
||||
use std::alloc::{alloc_zeroed, Layout};
|
||||
use crate::retroachievements;
|
||||
use crate::ui;
|
||||
use std::alloc::{Layout, alloc_zeroed};
|
||||
|
||||
//const RDRAM_CONFIG_REG: u32 = 0;
|
||||
//const RDRAM_DEVICE_ID_REG: u32 = 1;
|
||||
@@ -15,6 +17,7 @@ pub const RDRAM_REGS_COUNT: u32 = 10;
|
||||
|
||||
pub const RDRAM_MASK: usize = 0xFFFFFF;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Rdram {
|
||||
pub mem: Vec<u8>,
|
||||
pub size: u32,
|
||||
@@ -22,15 +25,19 @@ pub struct Rdram {
|
||||
}
|
||||
|
||||
pub fn read_mem_fast(
|
||||
device: &mut device::Device,
|
||||
device: &device::Device,
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
let masked_address = address as usize & RDRAM_MASK;
|
||||
u32::from_ne_bytes(
|
||||
device.rdram.mem[masked_address..masked_address + 4]
|
||||
device
|
||||
.rdram
|
||||
.mem
|
||||
.get(masked_address..masked_address + 4)
|
||||
.unwrap_or(&[0; 4])
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -44,27 +51,71 @@ pub fn read_mem(
|
||||
rdram_calculate_cycles(access_size as u64) / (access_size as u64 / 4),
|
||||
);
|
||||
let masked_address = address as usize & RDRAM_MASK;
|
||||
if address < device.rdram.size as u64 {
|
||||
u32::from_ne_bytes(
|
||||
device.rdram.mem[masked_address..masked_address + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
ui::video::check_framebuffers(masked_address as u32, 4);
|
||||
|
||||
u32::from_ne_bytes(
|
||||
device
|
||||
.rdram
|
||||
.mem
|
||||
.get(masked_address..masked_address + 4)
|
||||
.unwrap_or(&[0; 4])
|
||||
.try_into()
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
if address < device.rdram.size as u64 {
|
||||
let mut data = u32::from_ne_bytes(
|
||||
device.rdram.mem[address as usize..(address + 4) as usize]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
ui::video::check_framebuffers(address as u32, 4);
|
||||
|
||||
let mut data = u32::from_ne_bytes(
|
||||
device
|
||||
.rdram
|
||||
.mem
|
||||
.get(address as usize..(address + 4) as usize)
|
||||
.unwrap_or(&[0; 4])
|
||||
.try_into()
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
device::memory::masked_write_32(&mut data, value, mask);
|
||||
device
|
||||
.rdram
|
||||
.mem
|
||||
.get_mut(address as usize..(address + 4) as usize)
|
||||
.unwrap_or(&mut [0; 4])
|
||||
.copy_from_slice(&data.to_ne_bytes());
|
||||
}
|
||||
|
||||
pub fn write_mem_repeat(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
if mask != 0xFFFFFFFF {
|
||||
panic!("RDRAM write_mem_repeat called with mask {:#x}", mask);
|
||||
}
|
||||
|
||||
let repeat_length = (device.mi.regs[device::mi::MI_INIT_MODE_REG as usize]
|
||||
& device::mi::MI_INIT_LENGTH_MASK)
|
||||
+ 1;
|
||||
|
||||
if !repeat_length.is_multiple_of(4) {
|
||||
panic!(
|
||||
"RDRAM write_mem_repeat called with non-word-aligned length {}",
|
||||
repeat_length
|
||||
);
|
||||
device::memory::masked_write_32(&mut data, value, mask);
|
||||
device.rdram.mem[address as usize..(address + 4) as usize]
|
||||
.copy_from_slice(&data.to_ne_bytes());
|
||||
}
|
||||
|
||||
ui::video::check_framebuffers(address as u32, repeat_length);
|
||||
|
||||
for i in (0..repeat_length as u64).step_by(4) {
|
||||
device
|
||||
.rdram
|
||||
.mem
|
||||
.get_mut((address + i) as usize..(address + i) as usize + 4)
|
||||
.unwrap_or(&mut [0; 4])
|
||||
.copy_from_slice(&value.to_ne_bytes());
|
||||
}
|
||||
|
||||
device.mi.regs[device::mi::MI_INIT_MODE_REG as usize] &= !device::mi::MI_INIT_MODE;
|
||||
for i in 0..(0x3F00000 >> 16) {
|
||||
device.memory.memory_map_write[i] = device::rdram::write_mem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,10 +124,12 @@ pub fn read_regs(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
let chip_id = (address >> 13) & 3;
|
||||
let reg = (address & 0x3FF) >> 2;
|
||||
match reg as u32 {
|
||||
RDRAM_MODE_REG => device.pi.regs[reg as usize] ^ 0xc0c0c0c0,
|
||||
0x80 => 0x00000000, //Row, needed for libdragon
|
||||
_ => device.rdram.regs[chip_id as usize][reg as usize],
|
||||
}
|
||||
}
|
||||
@@ -99,14 +152,25 @@ pub fn init(device: &mut device::Device) {
|
||||
device.rdram.mem =
|
||||
unsafe { Vec::from_raw_parts(ptr, device.rdram.size as usize, device.rdram.size as usize) };
|
||||
|
||||
retroachievements::set_dmem(device.rdram.mem.as_ptr(), device.rdram.size as usize);
|
||||
|
||||
// hack, skip RDRAM initialization
|
||||
device.rdram.mem[device.cart.rdram_size_offset..device.cart.rdram_size_offset + 4]
|
||||
device
|
||||
.rdram
|
||||
.mem
|
||||
.get_mut(0x318..0x318 + 4)
|
||||
.unwrap_or(&mut [0; 4])
|
||||
.copy_from_slice(&device.rdram.size.to_ne_bytes());
|
||||
// hack, skip RDRAM initialization
|
||||
device
|
||||
.rdram
|
||||
.mem
|
||||
.get_mut(0x3f0..0x3f0 + 4)
|
||||
.unwrap_or(&mut [0; 4])
|
||||
.copy_from_slice(&device.rdram.size.to_ne_bytes());
|
||||
|
||||
device.ri.regs[device::ri::RI_MODE_REG as usize] = 0x0e;
|
||||
device.ri.regs[device::ri::RI_CONFIG_REG as usize] = 0x40;
|
||||
device.ri.regs[device::ri::RI_SELECT_REG as usize] = 0x14;
|
||||
device.ri.regs[device::ri::RI_REFRESH_REG as usize] = 0x00063634;
|
||||
}
|
||||
|
||||
pub fn rdram_calculate_cycles(length: u64) -> u64 {
|
||||
|
||||
@@ -10,6 +10,7 @@ pub const RI_REFRESH_REG: u32 = 4;
|
||||
//const RI_WERROR_REG: u32 = 7;
|
||||
pub const RI_REGS_COUNT: u32 = 8;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Ri {
|
||||
pub regs: [u32; RI_REGS_COUNT as usize],
|
||||
pub ram_init: bool,
|
||||
@@ -20,17 +21,25 @@ pub fn read_regs(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
if ((address & 0xFFFF) >> 2) as u32 == RI_SELECT_REG && !device.ri.ram_init {
|
||||
device::cop0::add_cycles(device, device.cpu.clock_rate / 2); // hack, simulate RDRAM initialization
|
||||
device.ri.ram_init = true;
|
||||
device::cop0::add_cycles(device, 20);
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
RI_SELECT_REG => {
|
||||
if !device.ri.ram_init {
|
||||
device::cop0::add_cycles(device, device.cpu.clock_rate / 2); // hack, simulate RDRAM initialization
|
||||
device.ri.ram_init = true;
|
||||
}
|
||||
0x14 // hack, skip RDRAM initialization
|
||||
}
|
||||
RI_REFRESH_REG => 0x00063634, // hack, skip RDRAM initialization
|
||||
_ => device.ri.regs[reg as usize],
|
||||
}
|
||||
device.ri.regs[((address & 0xFFFF) >> 2) as usize]
|
||||
}
|
||||
|
||||
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
device::memory::masked_write_32(
|
||||
&mut device.ri.regs[((address & 0xFFFF) >> 2) as usize],
|
||||
value,
|
||||
mask,
|
||||
);
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
if reg as u32 == RI_SELECT_REG {
|
||||
device.ri.ram_init = false;
|
||||
}
|
||||
device::memory::masked_write_32(&mut device.ri.regs[reg as usize], value, mask);
|
||||
}
|
||||
|
||||
@@ -2,60 +2,115 @@
|
||||
use device::__m128i;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
include!(concat!(env!("OUT_DIR"), "/simd_bindings.rs"));
|
||||
use crate::device;
|
||||
use crate::{device, savestates};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use std::arch::x86_64::*;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct BranchState {
|
||||
pub state: device::cpu::State,
|
||||
pub pc: u32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Instructions {
|
||||
#[serde(skip, default = "savestates::default_instruction")]
|
||||
pub func: fn(&mut device::Device, u32),
|
||||
pub opcode: u32,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Copy, Clone)]
|
||||
#[derive(PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum InstructionType {
|
||||
Su,
|
||||
Vu,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Cpu {
|
||||
#[serde(with = "serde_big_array::BigArray")]
|
||||
pub instructions: [Instructions; 0x1000 / 4],
|
||||
pub last_instruction_type: InstructionType,
|
||||
pub instruction_type: InstructionType,
|
||||
pub pipeline_full: bool,
|
||||
pub branch_state: BranchState,
|
||||
pub broken: bool,
|
||||
pub running: bool,
|
||||
pub halted: bool,
|
||||
pub sync_point: bool,
|
||||
pub cycle_counter: u64,
|
||||
#[serde(
|
||||
serialize_with = "savestates::serialize_m128i_array",
|
||||
deserialize_with = "savestates::deserialize_m128i_array"
|
||||
)]
|
||||
pub shuffle: [__m128i; 16],
|
||||
pub gpr: [u32; 32],
|
||||
#[serde(
|
||||
serialize_with = "savestates::serialize_m128i_array",
|
||||
deserialize_with = "savestates::deserialize_m128i_array"
|
||||
)]
|
||||
pub vpr: [__m128i; 32],
|
||||
#[serde(with = "serde_big_array::BigArray")]
|
||||
pub reciprocals: [u16; 512],
|
||||
#[serde(with = "serde_big_array::BigArray")]
|
||||
pub inverse_square_roots: [u16; 512],
|
||||
#[serde(
|
||||
serialize_with = "savestates::serialize_m128i",
|
||||
deserialize_with = "savestates::deserialize_m128i"
|
||||
)]
|
||||
pub vcol: __m128i,
|
||||
#[serde(
|
||||
serialize_with = "savestates::serialize_m128i",
|
||||
deserialize_with = "savestates::deserialize_m128i"
|
||||
)]
|
||||
pub vcoh: __m128i,
|
||||
#[serde(
|
||||
serialize_with = "savestates::serialize_m128i",
|
||||
deserialize_with = "savestates::deserialize_m128i"
|
||||
)]
|
||||
pub vccl: __m128i,
|
||||
#[serde(
|
||||
serialize_with = "savestates::serialize_m128i",
|
||||
deserialize_with = "savestates::deserialize_m128i"
|
||||
)]
|
||||
pub vcch: __m128i,
|
||||
#[serde(
|
||||
serialize_with = "savestates::serialize_m128i",
|
||||
deserialize_with = "savestates::deserialize_m128i"
|
||||
)]
|
||||
pub vce: __m128i,
|
||||
#[serde(
|
||||
serialize_with = "savestates::serialize_m128i",
|
||||
deserialize_with = "savestates::deserialize_m128i"
|
||||
)]
|
||||
pub accl: __m128i,
|
||||
#[serde(
|
||||
serialize_with = "savestates::serialize_m128i",
|
||||
deserialize_with = "savestates::deserialize_m128i"
|
||||
)]
|
||||
pub accm: __m128i,
|
||||
#[serde(
|
||||
serialize_with = "savestates::serialize_m128i",
|
||||
deserialize_with = "savestates::deserialize_m128i"
|
||||
)]
|
||||
pub acch: __m128i,
|
||||
pub divdp: bool,
|
||||
pub divin: i16,
|
||||
pub divout: i16,
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub special_instrs: [fn(&mut device::Device, u32); 64],
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub regimm_instrs: [fn(&mut device::Device, u32); 32],
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub cop0_instrs: [fn(&mut device::Device, u32); 32],
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub cop2_instrs: [fn(&mut device::Device, u32); 32],
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub lwc2_instrs: [fn(&mut device::Device, u32); 32],
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub swc2_instrs: [fn(&mut device::Device, u32); 32],
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub instrs: [fn(&mut device::Device, u32); 64],
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub vec_instrs: [fn(&mut device::Device, u32); 64],
|
||||
}
|
||||
|
||||
@@ -71,6 +126,7 @@ pub fn in_delay_slot_taken(device: &device::Device) -> bool {
|
||||
pub fn run(device: &mut device::Device) -> u64 {
|
||||
device.rsp.cpu.broken = false;
|
||||
device.rsp.cpu.cycle_counter = 0;
|
||||
device.rsp.cpu.running = true;
|
||||
while !device.rsp.cpu.sync_point {
|
||||
device.rsp.cpu.instruction_type = InstructionType::Su;
|
||||
device.rsp.cpu.gpr[0] = 0; // gpr 0 is read only
|
||||
@@ -132,6 +188,7 @@ pub fn run(device: &mut device::Device) -> u64 {
|
||||
}
|
||||
}
|
||||
}
|
||||
device.rsp.cpu.running = false;
|
||||
(device.rsp.cpu.cycle_counter as f64 * 1.5) as u64 // converting RCP clock to CPU clock
|
||||
}
|
||||
|
||||
@@ -165,62 +222,7 @@ pub fn decode_opcode(device: &device::Device, opcode: u32) -> fn(&mut device::De
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
device.rsp.cpu.reciprocals[0] = u16::MAX;
|
||||
let mut index = 1;
|
||||
while index < 512 {
|
||||
let a = (index + 512) as u64;
|
||||
let b = (1_u64 << 34) / a;
|
||||
device.rsp.cpu.reciprocals[index] = ((b + 1) >> 8) as u16;
|
||||
index += 1;
|
||||
}
|
||||
|
||||
index = 0;
|
||||
while index < 512 {
|
||||
let mut shift = 0;
|
||||
if index % 2 == 1 {
|
||||
shift = 1
|
||||
}
|
||||
let a = ((index + 512) >> shift) as u64;
|
||||
let mut b = (1 << 17) as u64;
|
||||
//find the largest b where b < 1.0 / sqrt(a)
|
||||
while a * (b + 1) * (b + 1) < (1_u64 << 44) {
|
||||
b += 1;
|
||||
}
|
||||
device.rsp.cpu.inverse_square_roots[index] = (b >> 1) as u16;
|
||||
index += 1;
|
||||
}
|
||||
|
||||
device.rsp.cpu.shuffle = unsafe {
|
||||
[
|
||||
//vector
|
||||
_mm_set_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), //01234567
|
||||
_mm_set_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), //01234567
|
||||
//scalar quarter
|
||||
_mm_set_epi8(15, 14, 15, 14, 11, 10, 11, 10, 7, 6, 7, 6, 3, 2, 3, 2), //00224466
|
||||
_mm_set_epi8(13, 12, 13, 12, 9, 8, 9, 8, 5, 4, 5, 4, 1, 0, 1, 0), //11335577
|
||||
//scalar half
|
||||
_mm_set_epi8(15, 14, 15, 14, 15, 14, 15, 14, 7, 6, 7, 6, 7, 6, 7, 6), //00004444
|
||||
_mm_set_epi8(13, 12, 13, 12, 13, 12, 13, 12, 5, 4, 5, 4, 5, 4, 5, 4), //11115555
|
||||
_mm_set_epi8(11, 10, 11, 10, 11, 10, 11, 10, 3, 2, 3, 2, 3, 2, 3, 2), //22226666
|
||||
_mm_set_epi8(9, 8, 9, 8, 9, 8, 9, 8, 1, 0, 1, 0, 1, 0, 1, 0), //33337777
|
||||
//scalar whole
|
||||
_mm_set_epi8(
|
||||
15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14,
|
||||
), //00000000
|
||||
_mm_set_epi8(
|
||||
13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12,
|
||||
), //11111111
|
||||
_mm_set_epi8(
|
||||
11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10,
|
||||
), //22222222
|
||||
_mm_set_epi8(9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8), //33333333
|
||||
_mm_set_epi8(7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6), //44444444
|
||||
_mm_set_epi8(5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4), //55555555
|
||||
_mm_set_epi8(3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2), //66666666
|
||||
_mm_set_epi8(1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0), //77777777
|
||||
]
|
||||
};
|
||||
pub fn map_instructions(device: &mut device::Device) {
|
||||
device.rsp.cpu.instrs = [
|
||||
device::rsp_su_instructions::reserved, // SPECIAL
|
||||
device::rsp_su_instructions::reserved, // REGIMM
|
||||
@@ -289,70 +291,70 @@ pub fn init(device: &mut device::Device) {
|
||||
];
|
||||
|
||||
device.rsp.cpu.special_instrs = [
|
||||
device::rsp_su_instructions::sll, // 0
|
||||
device::rsp_su_instructions::reserved, // 1
|
||||
device::rsp_su_instructions::srl, // 2
|
||||
device::rsp_su_instructions::sra, // 3
|
||||
device::rsp_su_instructions::sllv, // 4
|
||||
device::rsp_su_instructions::reserved, // 5
|
||||
device::rsp_su_instructions::srlv, // 6
|
||||
device::rsp_su_instructions::srav, // 7
|
||||
device::rsp_su_instructions::jr, // 8
|
||||
device::rsp_su_instructions::jalr, // 9
|
||||
device::rsp_su_instructions::reserved, // 10
|
||||
device::rsp_su_instructions::reserved, // 11
|
||||
device::rsp_su_instructions::reserved, // 12
|
||||
device::rsp_su_instructions::break_, // 13
|
||||
device::rsp_su_instructions::reserved, // 14
|
||||
device::rsp_su_instructions::reserved, // 15
|
||||
device::rsp_su_instructions::reserved, // 16
|
||||
device::rsp_su_instructions::reserved, // 17
|
||||
device::rsp_su_instructions::reserved, // 18
|
||||
device::rsp_su_instructions::reserved, // 19
|
||||
device::rsp_su_instructions::reserved, // 20
|
||||
device::rsp_su_instructions::reserved, // 21
|
||||
device::rsp_su_instructions::reserved, // 22
|
||||
device::rsp_su_instructions::reserved, // 23
|
||||
device::rsp_su_instructions::reserved, // 24
|
||||
device::rsp_su_instructions::reserved, // 25
|
||||
device::rsp_su_instructions::reserved, // 26
|
||||
device::rsp_su_instructions::reserved, // 27
|
||||
device::rsp_su_instructions::reserved, // 28
|
||||
device::rsp_su_instructions::reserved, // 29
|
||||
device::rsp_su_instructions::reserved, // 30
|
||||
device::rsp_su_instructions::reserved, // 31
|
||||
device::rsp_su_instructions::add, // 32
|
||||
device::rsp_su_instructions::addu, // 33
|
||||
device::rsp_su_instructions::sub, // 34
|
||||
device::rsp_su_instructions::subu, // 35
|
||||
device::rsp_su_instructions::and, // 36
|
||||
device::rsp_su_instructions::or, // 37
|
||||
device::rsp_su_instructions::xor, // 38
|
||||
device::rsp_su_instructions::nor, // 39
|
||||
device::rsp_su_instructions::reserved, // 40
|
||||
device::rsp_su_instructions::reserved, // 41
|
||||
device::rsp_su_instructions::slt, // 42
|
||||
device::rsp_su_instructions::sltu, // 43
|
||||
device::rsp_su_instructions::reserved, // 44
|
||||
device::rsp_su_instructions::reserved, // 45
|
||||
device::rsp_su_instructions::reserved, // 46
|
||||
device::rsp_su_instructions::reserved, // 47
|
||||
device::rsp_su_instructions::reserved, // 48
|
||||
device::rsp_su_instructions::reserved, // 49
|
||||
device::rsp_su_instructions::reserved, // 50
|
||||
device::rsp_su_instructions::reserved, // 51
|
||||
device::rsp_su_instructions::reserved, // 52
|
||||
device::rsp_su_instructions::reserved, // 53
|
||||
device::rsp_su_instructions::reserved, // 54
|
||||
device::rsp_su_instructions::reserved, // 55
|
||||
device::rsp_su_instructions::reserved, // 56
|
||||
device::rsp_su_instructions::reserved, // 57
|
||||
device::rsp_su_instructions::reserved, // 58
|
||||
device::rsp_su_instructions::reserved, // 59
|
||||
device::rsp_su_instructions::reserved, // 60
|
||||
device::rsp_su_instructions::reserved, // 61
|
||||
device::rsp_su_instructions::reserved, // 62
|
||||
device::rsp_su_instructions::reserved, // 63
|
||||
device::rsp_su_instructions::sll, // 0
|
||||
device::rsp_su_instructions::special_reserved, // 1
|
||||
device::rsp_su_instructions::srl, // 2
|
||||
device::rsp_su_instructions::sra, // 3
|
||||
device::rsp_su_instructions::sllv, // 4
|
||||
device::rsp_su_instructions::special_reserved, // 5
|
||||
device::rsp_su_instructions::srlv, // 6
|
||||
device::rsp_su_instructions::srav, // 7
|
||||
device::rsp_su_instructions::jr, // 8
|
||||
device::rsp_su_instructions::jalr, // 9
|
||||
device::rsp_su_instructions::special_reserved, // 10
|
||||
device::rsp_su_instructions::special_reserved, // 11
|
||||
device::rsp_su_instructions::special_reserved, // 12
|
||||
device::rsp_su_instructions::break_, // 13
|
||||
device::rsp_su_instructions::special_reserved, // 14
|
||||
device::rsp_su_instructions::special_reserved, // 15
|
||||
device::rsp_su_instructions::special_reserved, // 16
|
||||
device::rsp_su_instructions::special_reserved, // 17
|
||||
device::rsp_su_instructions::special_reserved, // 18
|
||||
device::rsp_su_instructions::special_reserved, // 19
|
||||
device::rsp_su_instructions::special_reserved, // 20
|
||||
device::rsp_su_instructions::special_reserved, // 21
|
||||
device::rsp_su_instructions::special_reserved, // 22
|
||||
device::rsp_su_instructions::special_reserved, // 23
|
||||
device::rsp_su_instructions::special_reserved, // 24
|
||||
device::rsp_su_instructions::special_reserved, // 25
|
||||
device::rsp_su_instructions::special_reserved, // 26
|
||||
device::rsp_su_instructions::special_reserved, // 27
|
||||
device::rsp_su_instructions::special_reserved, // 28
|
||||
device::rsp_su_instructions::special_reserved, // 29
|
||||
device::rsp_su_instructions::special_reserved, // 30
|
||||
device::rsp_su_instructions::special_reserved, // 31
|
||||
device::rsp_su_instructions::add, // 32
|
||||
device::rsp_su_instructions::addu, // 33
|
||||
device::rsp_su_instructions::sub, // 34
|
||||
device::rsp_su_instructions::subu, // 35
|
||||
device::rsp_su_instructions::and, // 36
|
||||
device::rsp_su_instructions::or, // 37
|
||||
device::rsp_su_instructions::xor, // 38
|
||||
device::rsp_su_instructions::nor, // 39
|
||||
device::rsp_su_instructions::special_reserved, // 40
|
||||
device::rsp_su_instructions::special_reserved, // 41
|
||||
device::rsp_su_instructions::slt, // 42
|
||||
device::rsp_su_instructions::sltu, // 43
|
||||
device::rsp_su_instructions::special_reserved, // 44
|
||||
device::rsp_su_instructions::special_reserved, // 45
|
||||
device::rsp_su_instructions::special_reserved, // 46
|
||||
device::rsp_su_instructions::special_reserved, // 47
|
||||
device::rsp_su_instructions::special_reserved, // 48
|
||||
device::rsp_su_instructions::special_reserved, // 49
|
||||
device::rsp_su_instructions::special_reserved, // 50
|
||||
device::rsp_su_instructions::special_reserved, // 51
|
||||
device::rsp_su_instructions::special_reserved, // 52
|
||||
device::rsp_su_instructions::special_reserved, // 53
|
||||
device::rsp_su_instructions::special_reserved, // 54
|
||||
device::rsp_su_instructions::special_reserved, // 55
|
||||
device::rsp_su_instructions::special_reserved, // 56
|
||||
device::rsp_su_instructions::special_reserved, // 57
|
||||
device::rsp_su_instructions::special_reserved, // 58
|
||||
device::rsp_su_instructions::special_reserved, // 59
|
||||
device::rsp_su_instructions::special_reserved, // 60
|
||||
device::rsp_su_instructions::special_reserved, // 61
|
||||
device::rsp_su_instructions::special_reserved, // 62
|
||||
device::rsp_su_instructions::special_reserved, // 63
|
||||
];
|
||||
|
||||
device.rsp.cpu.regimm_instrs = [
|
||||
@@ -597,3 +599,63 @@ pub fn init(device: &mut device::Device) {
|
||||
device::rsp_vu_instructions::vnop, // 63
|
||||
]
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
device.rsp.cpu.reciprocals[0] = u16::MAX;
|
||||
let mut index = 1;
|
||||
while index < 512 {
|
||||
let a = (index + 512) as u64;
|
||||
let b = (1_u64 << 34) / a;
|
||||
device.rsp.cpu.reciprocals[index] = ((b + 1) >> 8) as u16;
|
||||
index += 1;
|
||||
}
|
||||
|
||||
index = 0;
|
||||
while index < 512 {
|
||||
let mut shift = 0;
|
||||
if index % 2 == 1 {
|
||||
shift = 1
|
||||
}
|
||||
let a = ((index + 512) >> shift) as u64;
|
||||
let mut b = (1 << 17) as u64;
|
||||
//find the largest b where b < 1.0 / sqrt(a)
|
||||
while a * (b + 1) * (b + 1) < (1_u64 << 44) {
|
||||
b += 1;
|
||||
}
|
||||
device.rsp.cpu.inverse_square_roots[index] = (b >> 1) as u16;
|
||||
index += 1;
|
||||
}
|
||||
|
||||
device.rsp.cpu.shuffle = unsafe {
|
||||
[
|
||||
//vector
|
||||
_mm_set_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), //01234567
|
||||
_mm_set_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), //01234567
|
||||
//scalar quarter
|
||||
_mm_set_epi8(15, 14, 15, 14, 11, 10, 11, 10, 7, 6, 7, 6, 3, 2, 3, 2), //00224466
|
||||
_mm_set_epi8(13, 12, 13, 12, 9, 8, 9, 8, 5, 4, 5, 4, 1, 0, 1, 0), //11335577
|
||||
//scalar half
|
||||
_mm_set_epi8(15, 14, 15, 14, 15, 14, 15, 14, 7, 6, 7, 6, 7, 6, 7, 6), //00004444
|
||||
_mm_set_epi8(13, 12, 13, 12, 13, 12, 13, 12, 5, 4, 5, 4, 5, 4, 5, 4), //11115555
|
||||
_mm_set_epi8(11, 10, 11, 10, 11, 10, 11, 10, 3, 2, 3, 2, 3, 2, 3, 2), //22226666
|
||||
_mm_set_epi8(9, 8, 9, 8, 9, 8, 9, 8, 1, 0, 1, 0, 1, 0, 1, 0), //33337777
|
||||
//scalar whole
|
||||
_mm_set_epi8(
|
||||
15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14,
|
||||
), //00000000
|
||||
_mm_set_epi8(
|
||||
13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12,
|
||||
), //11111111
|
||||
_mm_set_epi8(
|
||||
11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10,
|
||||
), //22222222
|
||||
_mm_set_epi8(9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8), //33333333
|
||||
_mm_set_epi8(7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6), //44444444
|
||||
_mm_set_epi8(5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4), //55555555
|
||||
_mm_set_epi8(3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2), //66666666
|
||||
_mm_set_epi8(1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0), //77777777
|
||||
]
|
||||
};
|
||||
|
||||
map_instructions(device);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
|
||||
const SP_MEM_ADDR_REG: u32 = 0;
|
||||
const SP_DRAM_ADDR_REG: u32 = 1;
|
||||
@@ -60,14 +61,14 @@ const SP_SET_SIG7: u32 = 1 << 24;
|
||||
|
||||
const RSP_MEM_MASK: usize = 0x1FFF;
|
||||
|
||||
#[derive(PartialEq, Copy, Clone)]
|
||||
#[derive(PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum DmaDir {
|
||||
None,
|
||||
Write,
|
||||
Read,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct RspDma {
|
||||
pub dir: DmaDir,
|
||||
pub length: u32,
|
||||
@@ -75,16 +76,20 @@ pub struct RspDma {
|
||||
pub dramaddr: u32,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Rsp {
|
||||
pub cpu: device::rsp_cpu::Cpu,
|
||||
pub regs: [u32; SP_REGS_COUNT as usize],
|
||||
pub regs2: [u32; SP_REGS2_COUNT as usize],
|
||||
#[serde(with = "serde_big_array::BigArray")]
|
||||
pub mem: [u8; 0x2000],
|
||||
pub fifo: [RspDma; 2],
|
||||
pub last_status_value: u32,
|
||||
pub run_after_dma: bool,
|
||||
}
|
||||
|
||||
pub fn read_mem_fast(
|
||||
device: &mut device::Device,
|
||||
device: &device::Device,
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
@@ -99,8 +104,10 @@ pub fn read_mem_fast(
|
||||
pub fn read_mem(
|
||||
device: &mut device::Device,
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device::cop0::add_cycles(device, access_size as u64 / 4);
|
||||
|
||||
let masked_address = address as usize & RSP_MEM_MASK;
|
||||
u32::from_be_bytes(
|
||||
device.rsp.mem[masked_address..masked_address + 4]
|
||||
@@ -140,6 +147,7 @@ fn do_dma(device: &mut device::Device, dma: RspDma) {
|
||||
let mut dram_addr = dma.dramaddr & 0xfffff8;
|
||||
let offset = dma.memaddr & 0x1000;
|
||||
|
||||
ui::video::check_framebuffers(dram_addr, count * (length + skip) - skip);
|
||||
if dma.dir == DmaDir::Read {
|
||||
let mut j = 0;
|
||||
while j < count {
|
||||
@@ -151,7 +159,11 @@ fn do_dma(device: &mut device::Device, dma: RspDma) {
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
device.rdram.mem[dram_addr as usize..dram_addr as usize + 4]
|
||||
device
|
||||
.rdram
|
||||
.mem
|
||||
.get_mut(dram_addr as usize..dram_addr as usize + 4)
|
||||
.unwrap_or(&mut [0; 4])
|
||||
.copy_from_slice(&data.to_ne_bytes());
|
||||
mem_addr += 4;
|
||||
dram_addr += 4;
|
||||
@@ -165,14 +177,16 @@ fn do_dma(device: &mut device::Device, dma: RspDma) {
|
||||
while j < count {
|
||||
let mut i = 0;
|
||||
while i < length {
|
||||
let mut data = 0;
|
||||
if dram_addr < device.rdram.size {
|
||||
data = u32::from_ne_bytes(
|
||||
device.rdram.mem[dram_addr as usize..dram_addr as usize + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
let data = u32::from_ne_bytes(
|
||||
device
|
||||
.rdram
|
||||
.mem
|
||||
.get(dram_addr as usize..dram_addr as usize + 4)
|
||||
.unwrap_or(&[0; 4])
|
||||
.try_into()
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
|
||||
if offset != 0 {
|
||||
// imem being updated
|
||||
device.rsp.cpu.instructions[((mem_addr & 0xFFF) / 4) as usize].func =
|
||||
@@ -197,11 +211,8 @@ fn do_dma(device: &mut device::Device, dma: RspDma) {
|
||||
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::SPDma,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize]
|
||||
+ device::rdram::rdram_calculate_cycles((count * length) as u64)
|
||||
+ 9,
|
||||
device::rsp_interface::fifo_pop,
|
||||
device::events::EVENT_TYPE_SPDMA,
|
||||
device::rdram::rdram_calculate_cycles((count * length) as u64) + 9,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -210,6 +221,8 @@ fn fifo_push(device: &mut device::Device, dir: DmaDir) {
|
||||
panic!("RSP DMA already full")
|
||||
}
|
||||
|
||||
device.rsp.cpu.sync_point = true;
|
||||
|
||||
if device.rsp.regs[SP_DMA_BUSY_REG as usize] != 0 {
|
||||
device.rsp.fifo[1].dir = dir;
|
||||
if dir == DmaDir::Read {
|
||||
@@ -237,7 +250,7 @@ fn fifo_push(device: &mut device::Device, dir: DmaDir) {
|
||||
}
|
||||
}
|
||||
|
||||
fn fifo_pop(device: &mut device::Device) {
|
||||
pub fn fifo_pop(device: &mut device::Device) {
|
||||
if device.rsp.regs[SP_DMA_FULL_REG as usize] != 0 {
|
||||
device.rsp.fifo[0].dir = device.rsp.fifo[1].dir;
|
||||
device.rsp.fifo[0].length = device.rsp.fifo[1].length;
|
||||
@@ -249,7 +262,11 @@ fn fifo_pop(device: &mut device::Device) {
|
||||
do_dma(device, device.rsp.fifo[0])
|
||||
} else {
|
||||
device.rsp.regs[SP_DMA_BUSY_REG as usize] = 0;
|
||||
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_DMA_BUSY
|
||||
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_DMA_BUSY;
|
||||
if device.rsp.run_after_dma {
|
||||
device.rsp.run_after_dma = false;
|
||||
do_task(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,10 +275,33 @@ pub fn read_regs(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
if !device.rsp.cpu.running {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
}
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
SP_STATUS_REG => {
|
||||
let value = device.rsp.regs[reg as usize]
|
||||
& (SP_STATUS_SIG0
|
||||
| SP_STATUS_SIG1
|
||||
| SP_STATUS_SIG2
|
||||
| SP_STATUS_SIG3
|
||||
| SP_STATUS_SIG4
|
||||
| SP_STATUS_SIG5
|
||||
| SP_STATUS_SIG6
|
||||
| SP_STATUS_SIG7);
|
||||
if value == device.rsp.last_status_value && value != 0 {
|
||||
device.rsp.cpu.sync_point = true;
|
||||
}
|
||||
|
||||
device.rsp.last_status_value = value;
|
||||
device.rsp.regs[reg as usize]
|
||||
}
|
||||
SP_SEMAPHORE_REG => {
|
||||
let value = device.rsp.regs[reg as usize];
|
||||
if value == 1 {
|
||||
device.rsp.cpu.sync_point = true;
|
||||
}
|
||||
device.rsp.regs[reg as usize] = 1;
|
||||
value
|
||||
}
|
||||
@@ -284,9 +324,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
SP_SEMAPHORE_REG => {
|
||||
device::memory::masked_write_32(&mut device.rsp.regs[reg as usize], 0, mask)
|
||||
}
|
||||
_ => {
|
||||
device::memory::masked_write_32(&mut device.rsp.regs[reg as usize], value, mask);
|
||||
}
|
||||
_ => device::memory::masked_write_32(&mut device.rsp.regs[reg as usize], value, mask),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,6 +333,7 @@ pub fn read_regs2(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
device.rsp.regs2[((address & 0xFFFF) >> 2) as usize]
|
||||
}
|
||||
|
||||
@@ -320,7 +359,7 @@ fn update_sp_status(device: &mut device::Device, w: u32) {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_HALT
|
||||
}
|
||||
if w & SP_SET_HALT != 0 && w & SP_CLR_HALT == 0 {
|
||||
device::events::remove_event(device, device::events::EventType::SP);
|
||||
device::events::remove_event(device, device::events::EVENT_TYPE_SP);
|
||||
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_HALT
|
||||
}
|
||||
|
||||
@@ -335,7 +374,7 @@ fn update_sp_status(device: &mut device::Device, w: u32) {
|
||||
}
|
||||
/* set SP interrupt */
|
||||
if (w & SP_SET_INTR) != 0 && (w & SP_CLR_INTR) == 0 {
|
||||
device::mi::schedule_rcp_interrupt(device, device::mi::MI_INTR_SP);
|
||||
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_SP);
|
||||
}
|
||||
|
||||
/* clear / set single step */
|
||||
@@ -421,23 +460,24 @@ fn update_sp_status(device: &mut device::Device, w: u32) {
|
||||
if device.rsp.regs[SP_STATUS_REG as usize] & SP_STATUS_HALT == 0 && was_halted {
|
||||
device.rsp.cpu.broken = false;
|
||||
device.rsp.cpu.halted = false;
|
||||
device.rsp.cpu.sync_point = false;
|
||||
do_task(device);
|
||||
}
|
||||
}
|
||||
|
||||
fn do_task(device: &mut device::Device) {
|
||||
let timer = device::rsp_cpu::run(device);
|
||||
device.rsp.cpu.sync_point = false;
|
||||
device.rsp.last_status_value = 0;
|
||||
device.rdp.last_status_value = 0;
|
||||
if device.rsp.regs[SP_DMA_BUSY_REG as usize] == 1 {
|
||||
device.rsp.run_after_dma = true
|
||||
} else {
|
||||
let timer = device::rsp_cpu::run(device);
|
||||
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::SP,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + timer,
|
||||
rsp_event,
|
||||
)
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_SP, timer)
|
||||
}
|
||||
}
|
||||
|
||||
fn rsp_event(device: &mut device::Device) {
|
||||
pub fn rsp_event(device: &mut device::Device) {
|
||||
if device.rsp.cpu.broken {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_HALT | SP_STATUS_BROKE;
|
||||
|
||||
@@ -450,7 +490,6 @@ fn rsp_event(device: &mut device::Device) {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_HALT;
|
||||
return;
|
||||
}
|
||||
device.rsp.cpu.sync_point = false;
|
||||
do_task(device)
|
||||
}
|
||||
|
||||
|
||||
@@ -304,26 +304,44 @@ pub fn lh(device: &mut device::Device, opcode: u32) {
|
||||
let address =
|
||||
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
|
||||
|
||||
let mut w = [0; 2];
|
||||
w[0] = device.rsp.mem[address as usize & 0xFFF];
|
||||
w[1] = device.rsp.mem[(address as usize + 1) & 0xFFF];
|
||||
// Optimize for aligned access when possible
|
||||
let addr_masked = address as usize & 0xFFF;
|
||||
let value = if addr_masked <= 0xFFE {
|
||||
u16::from_be_bytes(
|
||||
device.rsp.mem[addr_masked..addr_masked + 2]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
let w0 = device.rsp.mem[addr_masked];
|
||||
let w1 = device.rsp.mem[(addr_masked + 1) & 0xFFF];
|
||||
((w0 as u16) << 8) | w1 as u16
|
||||
};
|
||||
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] =
|
||||
(((w[0] as u16) << 8) | w[1] as u16) as i16 as i32 as u32
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] = value as i16 as i32 as u32;
|
||||
}
|
||||
|
||||
pub fn lw(device: &mut device::Device, opcode: u32) {
|
||||
let address =
|
||||
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
|
||||
|
||||
let mut w = [0; 4];
|
||||
w[0] = device.rsp.mem[address as usize & 0xFFF];
|
||||
w[1] = device.rsp.mem[(address as usize + 1) & 0xFFF];
|
||||
w[2] = device.rsp.mem[(address as usize + 2) & 0xFFF];
|
||||
w[3] = device.rsp.mem[(address as usize + 3) & 0xFFF];
|
||||
// Optimize for aligned access when possible
|
||||
let addr_masked = address as usize & 0xFFF;
|
||||
let value = if addr_masked <= 0xFFC {
|
||||
u32::from_be_bytes(
|
||||
device.rsp.mem[addr_masked..addr_masked + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
let mut value = 0;
|
||||
for i in 0..4 {
|
||||
value |= (device.rsp.mem[(addr_masked + i) & 0xFFF] as u32) << (8 * (3 - i));
|
||||
}
|
||||
value
|
||||
};
|
||||
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] =
|
||||
((w[0] as u32) << 24) | ((w[1] as u32) << 16) | ((w[2] as u32) << 8) | (w[3] as u32)
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] = value;
|
||||
}
|
||||
|
||||
pub fn lbu(device: &mut device::Device, opcode: u32) {
|
||||
@@ -337,25 +355,44 @@ pub fn lhu(device: &mut device::Device, opcode: u32) {
|
||||
let address =
|
||||
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
|
||||
|
||||
let mut w = [0; 2];
|
||||
w[0] = device.rsp.mem[address as usize & 0xFFF];
|
||||
w[1] = device.rsp.mem[(address as usize + 1) & 0xFFF];
|
||||
// Optimize for aligned access when possible
|
||||
let addr_masked = address as usize & 0xFFF;
|
||||
let value = if addr_masked <= 0xFFE {
|
||||
u16::from_be_bytes(
|
||||
device.rsp.mem[addr_masked..addr_masked + 2]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
let w0 = device.rsp.mem[addr_masked];
|
||||
let w1 = device.rsp.mem[(addr_masked + 1) & 0xFFF];
|
||||
((w0 as u16) << 8) | w1 as u16
|
||||
};
|
||||
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] = (((w[0] as u16) << 8) | w[1] as u16) as u32
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] = value as u32;
|
||||
}
|
||||
|
||||
pub fn lwu(device: &mut device::Device, opcode: u32) {
|
||||
let address =
|
||||
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
|
||||
|
||||
let mut w = [0; 4];
|
||||
w[0] = device.rsp.mem[address as usize & 0xFFF];
|
||||
w[1] = device.rsp.mem[(address as usize + 1) & 0xFFF];
|
||||
w[2] = device.rsp.mem[(address as usize + 2) & 0xFFF];
|
||||
w[3] = device.rsp.mem[(address as usize + 3) & 0xFFF];
|
||||
// Optimize for aligned access when possible
|
||||
let addr_masked = address as usize & 0xFFF;
|
||||
let value = if addr_masked <= 0xFFC {
|
||||
u32::from_be_bytes(
|
||||
device.rsp.mem[addr_masked..addr_masked + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
let mut value = 0;
|
||||
for i in 0..4 {
|
||||
value |= (device.rsp.mem[(addr_masked + i) & 0xFFF] as u32) << (8 * (3 - i));
|
||||
}
|
||||
value
|
||||
};
|
||||
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] =
|
||||
((w[0] as u32) << 24) | ((w[1] as u32) << 16) | ((w[2] as u32) << 8) | (w[3] as u32)
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] = value;
|
||||
}
|
||||
|
||||
pub fn sb(device: &mut device::Device, opcode: u32) {
|
||||
@@ -369,23 +406,31 @@ pub fn sh(device: &mut device::Device, opcode: u32) {
|
||||
let address =
|
||||
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
|
||||
|
||||
device.rsp.mem[address as usize & 0xFFF] = (device.rsp.cpu.gpr[rt(opcode) as usize] >> 8) as u8;
|
||||
device.rsp.mem[(address as usize + 1) & 0xFFF] =
|
||||
(device.rsp.cpu.gpr[rt(opcode) as usize]) as u8;
|
||||
let addr_masked = address as usize & 0xFFF;
|
||||
let value = device.rsp.cpu.gpr[rt(opcode) as usize] as u16;
|
||||
// Optimize for aligned access when possible
|
||||
if addr_masked <= 0xFFE {
|
||||
device.rsp.mem[addr_masked..addr_masked + 2].copy_from_slice(&value.to_be_bytes());
|
||||
} else {
|
||||
device.rsp.mem[addr_masked] = (value >> 8) as u8;
|
||||
device.rsp.mem[(addr_masked + 1) & 0xFFF] = value as u8;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sw(device: &mut device::Device, opcode: u32) {
|
||||
let address =
|
||||
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
|
||||
|
||||
device.rsp.mem[address as usize & 0xFFF] =
|
||||
(device.rsp.cpu.gpr[rt(opcode) as usize] >> 24) as u8;
|
||||
device.rsp.mem[(address as usize + 1) & 0xFFF] =
|
||||
(device.rsp.cpu.gpr[rt(opcode) as usize] >> 16) as u8;
|
||||
device.rsp.mem[(address as usize + 2) & 0xFFF] =
|
||||
(device.rsp.cpu.gpr[rt(opcode) as usize] >> 8) as u8;
|
||||
device.rsp.mem[(address as usize + 3) & 0xFFF] =
|
||||
(device.rsp.cpu.gpr[rt(opcode) as usize]) as u8;
|
||||
let addr_masked = address as usize & 0xFFF;
|
||||
let value = device.rsp.cpu.gpr[rt(opcode) as usize];
|
||||
// Optimize for aligned access when possible
|
||||
if addr_masked <= 0xFFC {
|
||||
device.rsp.mem[addr_masked..addr_masked + 4].copy_from_slice(&value.to_be_bytes());
|
||||
} else {
|
||||
for i in 0..4 {
|
||||
device.rsp.mem[(addr_masked + i) & 0xFFF] = (value >> ((3 - i) * 8)) as u8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sll(device: &mut device::Device, opcode: u32) {
|
||||
@@ -552,6 +597,7 @@ pub fn bgezal(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
pub fn mfc0(device: &mut device::Device, opcode: u32) {
|
||||
device.rsp.cpu.cycle_counter += 2;
|
||||
if rd(opcode) < device::rsp_interface::SP_REGS_COUNT {
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] = device::rsp_interface::read_regs(
|
||||
device,
|
||||
@@ -565,8 +611,6 @@ pub fn mfc0(device: &mut device::Device, opcode: u32) {
|
||||
device::memory::AccessSize::Word,
|
||||
)
|
||||
}
|
||||
device.rsp.cpu.cycle_counter += 4; // needed for DK64
|
||||
device.rsp.cpu.sync_point = true;
|
||||
}
|
||||
|
||||
pub fn mtc0(device: &mut device::Device, opcode: u32) {
|
||||
@@ -592,7 +636,6 @@ pub fn mtc0(device: &mut device::Device, opcode: u32) {
|
||||
!device::rsp_interface::SP_STATUS_HALT; // set halt when event happens
|
||||
device.rsp.cpu.halted = true // the RSP can halt itself by setting SP_SET_HALT
|
||||
}
|
||||
device.rsp.cpu.sync_point = true;
|
||||
}
|
||||
|
||||
pub fn mfc2(device: &mut device::Device, opcode: u32) {
|
||||
@@ -608,19 +651,19 @@ pub fn mfc2(device: &mut device::Device, opcode: u32) {
|
||||
pub fn cfc2(device: &mut device::Device, opcode: u32) {
|
||||
let hi;
|
||||
let lo;
|
||||
let mut zero = unsafe { _mm_setzero_si128() };
|
||||
let zero = unsafe { _mm_setzero_si128() };
|
||||
match rd(opcode) & 3 {
|
||||
0x00 => {
|
||||
hi = &mut device.rsp.cpu.vcoh;
|
||||
lo = &mut device.rsp.cpu.vcol;
|
||||
hi = &device.rsp.cpu.vcoh;
|
||||
lo = &device.rsp.cpu.vcol;
|
||||
}
|
||||
0x01 => {
|
||||
hi = &mut device.rsp.cpu.vcch;
|
||||
lo = &mut device.rsp.cpu.vccl;
|
||||
hi = &device.rsp.cpu.vcch;
|
||||
lo = &device.rsp.cpu.vccl;
|
||||
}
|
||||
0x02 | 0x03 => {
|
||||
hi = &mut zero;
|
||||
lo = &mut device.rsp.cpu.vce;
|
||||
hi = &zero;
|
||||
lo = &device.rsp.cpu.vce;
|
||||
}
|
||||
_ => {
|
||||
panic!("unknown cfc2")
|
||||
@@ -717,7 +760,7 @@ pub fn lsv(device: &mut device::Device, opcode: u32) {
|
||||
|
||||
let mut element = velement(opcode);
|
||||
|
||||
if element % 2 == 0 {
|
||||
if element.is_multiple_of(2) {
|
||||
modify_vpr16(
|
||||
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
|
||||
element / 2,
|
||||
@@ -744,11 +787,22 @@ pub fn llv(device: &mut device::Device, opcode: u32) {
|
||||
|
||||
let mut element = velement(opcode);
|
||||
|
||||
if element % 4 == 0 {
|
||||
let mut value = 0;
|
||||
for i in 0..4 {
|
||||
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u32) << (8 * (3 - i));
|
||||
}
|
||||
if element.is_multiple_of(4) {
|
||||
// Optimize for aligned access when possible
|
||||
let addr_masked = address as usize & 0xFFF;
|
||||
let value = if addr_masked <= 0xFFC {
|
||||
u32::from_be_bytes(
|
||||
device.rsp.mem[addr_masked..addr_masked + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
let mut value = 0;
|
||||
for i in 0..4 {
|
||||
value |= (device.rsp.mem[(addr_masked + i) & 0xFFF] as u32) << (8 * (3 - i));
|
||||
}
|
||||
value
|
||||
};
|
||||
modify_vpr32(
|
||||
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
|
||||
element / 4,
|
||||
@@ -774,11 +828,22 @@ pub fn ldv(device: &mut device::Device, opcode: u32) {
|
||||
|
||||
let mut element = velement(opcode);
|
||||
|
||||
if element % 8 == 0 {
|
||||
let mut value = 0;
|
||||
for i in 0..8 {
|
||||
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u64) << (8 * (7 - i));
|
||||
}
|
||||
if element.is_multiple_of(8) {
|
||||
// Optimize for aligned access when possible
|
||||
let addr_masked = address as usize & 0xFFF;
|
||||
let value = if addr_masked <= 0xFF8 {
|
||||
u64::from_be_bytes(
|
||||
device.rsp.mem[addr_masked..addr_masked + 8]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
let mut value = 0;
|
||||
for i in 0..8 {
|
||||
value |= (device.rsp.mem[(addr_masked + i) & 0xFFF] as u64) << (8 * (7 - i));
|
||||
}
|
||||
value
|
||||
};
|
||||
modify_vpr64(
|
||||
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
|
||||
element / 8,
|
||||
@@ -804,11 +869,22 @@ pub fn lqv(device: &mut device::Device, opcode: u32) {
|
||||
|
||||
let mut element = velement(opcode);
|
||||
|
||||
if element == 0 && address % 16 == 0 {
|
||||
let mut value = 0;
|
||||
for i in 0..16 {
|
||||
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u128) << (8 * (15 - i));
|
||||
}
|
||||
if element == 0 && address.is_multiple_of(16) {
|
||||
// Optimize for aligned 16-byte access
|
||||
let addr_masked = address as usize & 0xFFF;
|
||||
let value = if addr_masked <= 0xFF0 {
|
||||
u128::from_be_bytes(
|
||||
device.rsp.mem[addr_masked..addr_masked + 16]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
let mut value = 0;
|
||||
for i in 0..16 {
|
||||
value |= (device.rsp.mem[(addr_masked + i) & 0xFFF] as u128) << (8 * (15 - i));
|
||||
}
|
||||
value
|
||||
};
|
||||
modify_vpr128(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value);
|
||||
} else {
|
||||
let end = std::cmp::min(16 + element - ((address & 15) as u8), 16);
|
||||
@@ -831,10 +907,21 @@ pub fn lrv(device: &mut device::Device, opcode: u32) {
|
||||
let mut element = 16u8.wrapping_sub(((address & 15) as u8).wrapping_sub(velement(opcode)));
|
||||
address &= !15;
|
||||
if element == 0 {
|
||||
let mut value = 0;
|
||||
for i in 0..16 {
|
||||
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u128) << (8 * (15 - i));
|
||||
}
|
||||
// Optimize for aligned 16-byte access
|
||||
let addr_masked = address as usize & 0xFFF;
|
||||
let value = if addr_masked <= 0xFF0 {
|
||||
u128::from_be_bytes(
|
||||
device.rsp.mem[addr_masked..addr_masked + 16]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
let mut value = 0;
|
||||
for i in 0..16 {
|
||||
value |= (device.rsp.mem[(addr_masked + i) & 0xFFF] as u128) << (8 * (15 - i));
|
||||
}
|
||||
value
|
||||
};
|
||||
modify_vpr128(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value);
|
||||
} else {
|
||||
while element < 16 {
|
||||
@@ -983,7 +1070,7 @@ pub fn ssv(device: &mut device::Device, opcode: u32) {
|
||||
|
||||
let mut element = velement(opcode);
|
||||
|
||||
if element % 2 == 0 {
|
||||
if element.is_multiple_of(2) {
|
||||
let start = (address & 0xFFF) as usize;
|
||||
device.rsp.mem[start..start + 2].copy_from_slice(
|
||||
&get_vpr16(device.rsp.cpu.vpr[rt(opcode) as usize], element / 2).to_be_bytes(),
|
||||
@@ -1005,7 +1092,7 @@ pub fn slv(device: &mut device::Device, opcode: u32) {
|
||||
|
||||
let mut element = velement(opcode);
|
||||
|
||||
if element % 4 == 0 {
|
||||
if element.is_multiple_of(4) {
|
||||
let start = (address & 0xFFF) as usize;
|
||||
device.rsp.mem[start..start + 4].copy_from_slice(
|
||||
&get_vpr32(device.rsp.cpu.vpr[rt(opcode) as usize], element / 4).to_be_bytes(),
|
||||
@@ -1027,7 +1114,7 @@ pub fn sdv(device: &mut device::Device, opcode: u32) {
|
||||
|
||||
let mut element = velement(opcode);
|
||||
|
||||
if element % 8 == 0 {
|
||||
if element.is_multiple_of(8) {
|
||||
let start = (address & 0xFFF) as usize;
|
||||
device.rsp.mem[start..start + 8].copy_from_slice(
|
||||
&get_vpr64(device.rsp.cpu.vpr[rt(opcode) as usize], element / 8).to_be_bytes(),
|
||||
@@ -1049,7 +1136,7 @@ pub fn sqv(device: &mut device::Device, opcode: u32) {
|
||||
|
||||
let mut element = velement(opcode);
|
||||
|
||||
if element == 0 && address % 16 == 0 {
|
||||
if element == 0 && address.is_multiple_of(16) {
|
||||
let start = (address & 0xFFF) as usize;
|
||||
device.rsp.mem[start..start + 16]
|
||||
.copy_from_slice(&get_vpr128(device.rsp.cpu.vpr[rt(opcode) as usize]).to_be_bytes());
|
||||
@@ -1207,6 +1294,9 @@ pub fn stv(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reserved(_device: &mut device::Device, _opcode: u32) {
|
||||
panic!("rsp su reserved")
|
||||
pub fn special_reserved(device: &mut device::Device, opcode: u32) {
|
||||
device.rsp.cpu.gpr[rd(opcode) as usize] =
|
||||
device.rsp.cpu.gpr[rs(opcode) as usize] >> (device.rsp.cpu.gpr[rs(opcode) as usize] & 31)
|
||||
}
|
||||
|
||||
pub fn reserved(_device: &mut device::Device, _opcode: u32) {}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
use rand::Rng;
|
||||
|
||||
const SI_DRAM_ADDR_REG: u32 = 0;
|
||||
const SI_PIF_ADDR_RD64B_REG: u32 = 1;
|
||||
@@ -14,13 +16,14 @@ pub const SI_STATUS_IO_BUSY: u32 = 1 << 1;
|
||||
//const SI_STATUS_DMA_ERROR: u32 = 1 << 3;
|
||||
const SI_STATUS_INTERRUPT: u32 = 1 << 12;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum DmaDir {
|
||||
None,
|
||||
Write,
|
||||
Read,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Si {
|
||||
pub regs: [u32; SI_REGS_COUNT as usize],
|
||||
pub dma_dir: DmaDir,
|
||||
@@ -31,9 +34,14 @@ pub fn read_regs(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
device.si.regs[((address & 0xFFFF) >> 2) as usize]
|
||||
}
|
||||
|
||||
fn randomize_interrupt_time(rng: &mut rand::rngs::Xoshiro256PlusPlus) -> u64 {
|
||||
rng.next_u64() % 0x100
|
||||
}
|
||||
|
||||
fn dma_read(device: &mut device::Device) {
|
||||
device.si.dma_dir = DmaDir::Read;
|
||||
|
||||
@@ -41,12 +49,9 @@ fn dma_read(device: &mut device::Device) {
|
||||
|
||||
device.si.regs[SI_STATUS_REG as usize] |= SI_STATUS_DMA_BUSY;
|
||||
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::SI,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + duration,
|
||||
dma_event,
|
||||
)
|
||||
let length = duration + randomize_interrupt_time(&mut device.rng);
|
||||
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_SI, length)
|
||||
}
|
||||
|
||||
fn dma_write(device: &mut device::Device) {
|
||||
@@ -56,12 +61,9 @@ fn dma_write(device: &mut device::Device) {
|
||||
|
||||
device.si.regs[SI_STATUS_REG as usize] |= SI_STATUS_DMA_BUSY;
|
||||
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::SI,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + 6000, //based on https://github.com/rasky/n64-systembench
|
||||
dma_event,
|
||||
)
|
||||
let length = 6000 + randomize_interrupt_time(&mut device.rng); //based on https://github.com/rasky/n64-systembench
|
||||
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_SI, length)
|
||||
}
|
||||
|
||||
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
@@ -84,18 +86,29 @@ fn copy_pif_rdram(device: &mut device::Device) {
|
||||
let mut i = 0;
|
||||
while i < device::pif::PIF_RAM_SIZE {
|
||||
let data = u32::from_ne_bytes(
|
||||
device.rdram.mem[dram_addr + i..dram_addr + i + 4]
|
||||
device
|
||||
.rdram
|
||||
.mem
|
||||
.get(dram_addr + i..dram_addr + i + 4)
|
||||
.unwrap_or(&[0; 4])
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
device.pif.ram[i..i + 4].copy_from_slice(&data.to_be_bytes());
|
||||
i += 4;
|
||||
}
|
||||
} else if device.si.dma_dir == DmaDir::Read {
|
||||
// check RDP before SI writes to RDRAM
|
||||
ui::video::check_framebuffers(dram_addr as u32, device::pif::PIF_RAM_SIZE as u32);
|
||||
let mut i = 0;
|
||||
while i < device::pif::PIF_RAM_SIZE {
|
||||
let data = u32::from_be_bytes(device.pif.ram[i..i + 4].try_into().unwrap());
|
||||
device.rdram.mem[dram_addr + i..dram_addr + i + 4].copy_from_slice(&data.to_ne_bytes());
|
||||
device
|
||||
.rdram
|
||||
.mem
|
||||
.get_mut(dram_addr + i..dram_addr + i + 4)
|
||||
.unwrap_or(&mut [0; 4])
|
||||
.copy_from_slice(&data.to_ne_bytes());
|
||||
i += 4;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use crate::device;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct TlbLut {
|
||||
pub address: u64,
|
||||
pub cached: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct TlbEntry {
|
||||
pub mask: u64,
|
||||
pub vpn2: u64,
|
||||
@@ -146,7 +146,7 @@ pub fn probe(device: &mut device::Device) {
|
||||
}
|
||||
|
||||
fn tlb_unmap(device: &mut device::Device, index: u64) {
|
||||
let e = &mut device.cpu.cop0.tlb_entries[index as usize];
|
||||
let e = &device.cpu.cop0.tlb_entries[index as usize];
|
||||
|
||||
if e.v_even != 0 {
|
||||
let mut i = e.start_even;
|
||||
@@ -184,7 +184,7 @@ fn tlb_unmap(device: &mut device::Device, index: u64) {
|
||||
}
|
||||
|
||||
fn tlb_map(device: &mut device::Device, index: u64) {
|
||||
let e = &mut device.cpu.cop0.tlb_entries[index as usize];
|
||||
let e = &device.cpu.cop0.tlb_entries[index as usize];
|
||||
|
||||
if e.v_even != 0
|
||||
&& e.start_even < e.end_even
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::device;
|
||||
|
||||
pub fn read_mem(
|
||||
_device: &mut device::Device,
|
||||
pub fn read_mem_fast(
|
||||
_device: &device::Device,
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
@@ -9,4 +9,12 @@ pub fn read_mem(
|
||||
value | (value << 16)
|
||||
}
|
||||
|
||||
pub fn read_mem(
|
||||
device: &mut device::Device,
|
||||
address: u64,
|
||||
access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
read_mem_fast(device, address, access_size)
|
||||
}
|
||||
|
||||
pub fn write_mem(_device: &mut device::Device, _address: u64, _value: u32, _mask: u32) {}
|
||||
|
||||
@@ -1,74 +1,70 @@
|
||||
use crate::device;
|
||||
use crate::netplay;
|
||||
use crate::ui;
|
||||
use governor::clock::Clock;
|
||||
use crate::{cheats, device, netplay, retroachievements, ui};
|
||||
|
||||
const VI_STATUS_REG: u32 = 0;
|
||||
//const VI_ORIGIN_REG: u32 = 1;
|
||||
pub const VI_WIDTH_REG: u32 = 2;
|
||||
const VI_ORIGIN_REG: u32 = 1;
|
||||
//const VI_WIDTH_REG: u32 = 2;
|
||||
//const VI_V_INTR_REG: u32 = 3;
|
||||
const VI_CURRENT_REG: u32 = 4;
|
||||
//const VI_BURST_REG: u32 = 5;
|
||||
const VI_V_SYNC_REG: u32 = 6;
|
||||
const VI_H_SYNC_REG: u32 = 7;
|
||||
//const VI_LEAP_REG: u32 = 8;
|
||||
pub const VI_H_START_REG: u32 = 9;
|
||||
pub const VI_V_START_REG: u32 = 10;
|
||||
//const VI_H_START_REG: u32 = 9;
|
||||
//const VI_V_START_REG: u32 = 10;
|
||||
//const VI_V_BURST_REG: u32 = 11;
|
||||
pub const VI_X_SCALE_REG: u32 = 12;
|
||||
pub const VI_Y_SCALE_REG: u32 = 13;
|
||||
//const VI_X_SCALE_REG: u32 = 12;
|
||||
//const VI_Y_SCALE_REG: u32 = 13;
|
||||
pub const VI_REGS_COUNT: u32 = 14;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Vi {
|
||||
pub regs: [u32; VI_REGS_COUNT as usize],
|
||||
pub clock: u64,
|
||||
pub delay: u64,
|
||||
pub field: u32,
|
||||
pub limiter: Option<governor::DefaultDirectRateLimiter>,
|
||||
#[serde(skip)]
|
||||
pub next_pace_deadline: Option<std::time::Instant>,
|
||||
pub count_per_scanline: u64,
|
||||
pub enable_speed_limiter: bool,
|
||||
pub vi_counter: u64,
|
||||
pub min_wait_time: std::time::Duration,
|
||||
pub frame_time: f64,
|
||||
pub limit_freq: u64,
|
||||
#[serde(skip)]
|
||||
#[serde(default = "std::time::Instant::now")]
|
||||
pub limit_freq_check: std::time::Instant,
|
||||
}
|
||||
|
||||
//static mut FRAME_COUNTER: u64 = 0;
|
||||
const MAX_LIMIT_FREQ: u64 = 3;
|
||||
|
||||
const LIMIT_BUFFER: u64 = 3;
|
||||
|
||||
fn set_expected_refresh_rate(device: &mut device::Device) {
|
||||
pub fn set_expected_refresh_rate(device: &mut device::Device) {
|
||||
let expected_refresh_rate = device.vi.clock as f64
|
||||
/ (device.vi.regs[VI_V_SYNC_REG as usize] + 1) as f64
|
||||
/ ((device.vi.regs[VI_H_SYNC_REG as usize] & 0xFFF) + 1) as f64
|
||||
* 2.0;
|
||||
device.vi.frame_time = 1.0 / expected_refresh_rate;
|
||||
device.vi.delay = (device.cpu.clock_rate as f64 / expected_refresh_rate) as u64;
|
||||
device.vi.count_per_scanline =
|
||||
device.vi.delay / (device.vi.regs[VI_V_SYNC_REG as usize] + 1) as u64;
|
||||
|
||||
let quota = governor::Quota::with_period(std::time::Duration::from_secs_f64(
|
||||
(1.0 / expected_refresh_rate) * LIMIT_BUFFER as f64,
|
||||
))
|
||||
.unwrap();
|
||||
device.vi.limiter = Some(governor::RateLimiter::direct(quota))
|
||||
reset_pace_deadline(device);
|
||||
}
|
||||
|
||||
fn reset_pace_deadline(device: &mut device::Device) {
|
||||
device.vi.next_pace_deadline = None;
|
||||
}
|
||||
|
||||
fn set_vertical_interrupt(device: &mut device::Device) {
|
||||
if device::events::get_event(device, device::events::EventType::VI).is_none() {
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::VI,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + device.vi.delay,
|
||||
vertical_interrupt_event,
|
||||
)
|
||||
if device::events::get_event(device, device::events::EVENT_TYPE_VI).is_none() {
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_VI, device.vi.delay)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_current_line(device: &mut device::Device) {
|
||||
let delay = device.vi.delay;
|
||||
let next_vi = device::events::get_event(device, device::events::EventType::VI);
|
||||
if next_vi.is_some() {
|
||||
device.vi.regs[VI_CURRENT_REG as usize] = ((delay
|
||||
- (next_vi.unwrap().count
|
||||
- device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize]))
|
||||
/ device.vi.count_per_scanline)
|
||||
as u32;
|
||||
if let Some(next_vi) = device::events::get_event(device, device::events::EVENT_TYPE_VI) {
|
||||
device.vi.regs[VI_CURRENT_REG as usize] = ((device.vi.delay.saturating_sub(
|
||||
next_vi.count - device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize],
|
||||
)) / device.vi.count_per_scanline) as u32;
|
||||
|
||||
// wrap around VI_CURRENT_REG if needed
|
||||
if device.vi.regs[VI_CURRENT_REG as usize] >= device.vi.regs[VI_V_SYNC_REG as usize] {
|
||||
@@ -77,7 +73,8 @@ fn set_current_line(device: &mut device::Device) {
|
||||
}
|
||||
/* update current field */
|
||||
device.vi.regs[VI_CURRENT_REG as usize] =
|
||||
(device.vi.regs[VI_CURRENT_REG as usize] & !1) | device.vi.field
|
||||
(device.vi.regs[VI_CURRENT_REG as usize] & !1) | device.vi.field;
|
||||
ui::video::set_register(VI_CURRENT_REG, device.vi.regs[VI_CURRENT_REG as usize])
|
||||
}
|
||||
|
||||
pub fn read_regs(
|
||||
@@ -89,6 +86,7 @@ pub fn read_regs(
|
||||
if reg as u32 == VI_CURRENT_REG {
|
||||
set_current_line(device)
|
||||
}
|
||||
device::cop0::add_cycles(device, 20);
|
||||
device.vi.regs[reg as usize]
|
||||
}
|
||||
|
||||
@@ -109,6 +107,13 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
set_expected_refresh_rate(device);
|
||||
}
|
||||
}
|
||||
VI_ORIGIN_REG => {
|
||||
let current_origin = device.vi.regs[reg as usize];
|
||||
device::memory::masked_write_32(&mut device.vi.regs[reg as usize], value, mask);
|
||||
if current_origin != device.vi.regs[reg as usize] {
|
||||
let _ = device.ui.video.fps_tx.try_send(true);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
device::memory::masked_write_32(&mut device.vi.regs[reg as usize], value, mask);
|
||||
}
|
||||
@@ -116,36 +121,55 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
ui::video::set_register(reg as u32, device.vi.regs[reg as usize])
|
||||
}
|
||||
|
||||
fn vertical_interrupt_event(device: &mut device::Device) {
|
||||
device.cpu.running = ui::video::update_screen();
|
||||
pub fn vertical_interrupt_event(device: &mut device::Device) {
|
||||
if device.cheats.enabled {
|
||||
cheats::execute_cheats(device, device.cheats.cheats.clone());
|
||||
}
|
||||
|
||||
/*
|
||||
unsafe {
|
||||
FRAME_COUNTER += 1;
|
||||
ui::video::render_frame();
|
||||
let _ = device.ui.video.vis_tx.try_send(true);
|
||||
|
||||
retroachievements::do_frame();
|
||||
|
||||
let (mut speed_limiter_toggled, paused) = ui::video::check_callback(device);
|
||||
|
||||
if let Some(netplay) = &mut device.netplay {
|
||||
netplay::send_sync_check(netplay, device.cpu.cop0.regs.as_ref());
|
||||
if device.vi.enable_speed_limiter == netplay.fast_forward {
|
||||
speed_limiter_toggled = true;
|
||||
device.vi.enable_speed_limiter = !netplay.fast_forward;
|
||||
}
|
||||
*/
|
||||
|
||||
let mut enable_speed_limiter = true;
|
||||
if device.netplay.is_some() {
|
||||
netplay::send_sync_check(device);
|
||||
enable_speed_limiter = !device.netplay.as_ref().unwrap().fast_forward;
|
||||
}
|
||||
|
||||
if device.vi.vi_counter.is_multiple_of(device.vi.limit_freq) && device.vi.enable_speed_limiter {
|
||||
speed_limiter(device, speed_limiter_toggled);
|
||||
}
|
||||
|
||||
ui::video::update_screen();
|
||||
device.vi.vi_counter += 1;
|
||||
if device.vi.vi_counter % LIMIT_BUFFER == 0 && enable_speed_limiter {
|
||||
speed_limiter(device);
|
||||
|
||||
if device.netplay.is_none() && paused {
|
||||
if retroachievements::get_hardcore() {
|
||||
ui::video::onscreen_message(
|
||||
"Cannot pause in RA hardcore mode",
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_SHORT,
|
||||
);
|
||||
} else {
|
||||
ui::video::pause_loop(device.vi.frame_time);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe { sdl3_sys::events::SDL_PumpEvents() };
|
||||
|
||||
/* toggle vi field if in interlaced mode */
|
||||
device.vi.field ^= (device.vi.regs[VI_STATUS_REG as usize] >> 6) & 0x1;
|
||||
|
||||
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_VI);
|
||||
|
||||
device::events::create_event(
|
||||
device::events::create_event_at(
|
||||
device,
|
||||
device::events::EventType::VI,
|
||||
device::events::EVENT_TYPE_VI,
|
||||
device.cpu.next_event_count + device.vi.delay,
|
||||
vertical_interrupt_event,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -155,27 +179,58 @@ pub fn init(device: &mut device::Device) {
|
||||
} else {
|
||||
device.vi.clock = 48681812
|
||||
}
|
||||
/*
|
||||
std::thread::spawn(move || {
|
||||
let mut last_count = 0;
|
||||
loop {
|
||||
unsafe {
|
||||
println!("{:?}", FRAME_COUNTER - last_count);
|
||||
last_count = FRAME_COUNTER;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
fn speed_limiter(device: &device::Device) {
|
||||
let result = device.vi.limiter.as_ref().unwrap().check();
|
||||
if result.is_err() {
|
||||
let outcome = result.unwrap_err();
|
||||
let dur = outcome.wait_time_from(governor::clock::DefaultClock::default().now());
|
||||
spin_sleep::sleep(dur);
|
||||
fn speed_limiter(device: &mut device::Device, mut speed_limiter_toggled: bool) {
|
||||
let interval =
|
||||
std::time::Duration::from_secs_f64(device.vi.frame_time * device.vi.limit_freq as f64);
|
||||
|
||||
let _ = device.vi.limiter.as_ref().unwrap().check();
|
||||
let now = std::time::Instant::now();
|
||||
match device.vi.next_pace_deadline {
|
||||
None => {
|
||||
device.vi.next_pace_deadline = Some(now + interval);
|
||||
speed_limiter_toggled = true;
|
||||
}
|
||||
Some(deadline) => {
|
||||
if now < deadline {
|
||||
let dur = deadline - now;
|
||||
spin_sleep::sleep(dur);
|
||||
if dur < device.vi.min_wait_time {
|
||||
device.vi.min_wait_time = dur;
|
||||
}
|
||||
} else {
|
||||
//println!("did not sleep");
|
||||
device.vi.min_wait_time = std::time::Duration::from_secs(0);
|
||||
}
|
||||
let mut next = deadline + interval;
|
||||
let t = std::time::Instant::now();
|
||||
while next <= t {
|
||||
next += interval;
|
||||
}
|
||||
device.vi.next_pace_deadline = Some(next);
|
||||
}
|
||||
}
|
||||
|
||||
if std::time::Instant::now().duration_since(device.vi.limit_freq_check)
|
||||
> std::time::Duration::from_secs(1)
|
||||
{
|
||||
if !speed_limiter_toggled {
|
||||
if device.vi.min_wait_time == std::time::Duration::from_secs(0)
|
||||
&& device.vi.limit_freq < MAX_LIMIT_FREQ
|
||||
{
|
||||
device.vi.limit_freq += 1;
|
||||
reset_pace_deadline(device);
|
||||
} else if device.vi.min_wait_time
|
||||
> std::time::Duration::from_secs_f64(device.vi.frame_time)
|
||||
&& device.vi.limit_freq > 1
|
||||
{
|
||||
device.vi.limit_freq -= 1;
|
||||
reset_pace_deadline(device);
|
||||
}
|
||||
}
|
||||
|
||||
//println!("limit freq: {}", device.vi.limit_freq);
|
||||
device.vi.min_wait_time = std::time::Duration::from_secs(1);
|
||||
device.vi.limit_freq_check = std::time::Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,185 +3,298 @@
|
||||
#[global_allocator]
|
||||
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
|
||||
mod cheats;
|
||||
mod device;
|
||||
mod netplay;
|
||||
mod retroachievements;
|
||||
mod savestates;
|
||||
mod ui;
|
||||
use clap::Parser;
|
||||
use ui::gui;
|
||||
use std::io::Error;
|
||||
|
||||
/// N64 emulator
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[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<String>,
|
||||
#[arg(short, long)]
|
||||
fullscreen: bool,
|
||||
#[arg(long)]
|
||||
overclock: Option<bool>,
|
||||
#[arg(long)]
|
||||
disable_expansion_pak: Option<bool>,
|
||||
#[arg(long, value_name = "CHEATS_FILE", hide = true)]
|
||||
cheats: Option<String>,
|
||||
#[arg(long, value_name = "NETPLAY_PEER_ADDR", hide = true)]
|
||||
netplay_peer_addr: Option<String>,
|
||||
#[arg(long, value_name = "NETPLAY_PLAYER_NUMBER", hide = true)]
|
||||
netplay_player_number: Option<u8>,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
value_name = "PROFILE_NAME",
|
||||
help = "Create a new input profile (keyboard/gamepad mappings)."
|
||||
help = "Create a new input profile (keyboard/gamepad mappings)"
|
||||
)]
|
||||
configure_input_profile: Option<String>,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Use DirectInput when configuring a new input profile."
|
||||
)]
|
||||
#[arg(long, help = "Use DirectInput when configuring a new input profile")]
|
||||
use_dinput: bool,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
value_name = "DEADZONE_PERCENTAGE",
|
||||
help = "Used along with --configure-input-profile to set the deadzone for analog sticks"
|
||||
)]
|
||||
deadzone: Option<i32>,
|
||||
#[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<String>,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Lists connected controllers which can be used in --assign-controller"
|
||||
)]
|
||||
list_controllers: bool,
|
||||
#[arg(
|
||||
short,
|
||||
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<i32>,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
value_name = "PORT",
|
||||
help = "Valid values: 1-4. To be used alongside --bind-input-profile and --assign-controller"
|
||||
)]
|
||||
port: Option<usize>,
|
||||
#[arg(
|
||||
short = 'z',
|
||||
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<u32>,
|
||||
#[arg(
|
||||
long = "ra-username",
|
||||
value_name = "USERNAME",
|
||||
help = "Username for RetroAchievements"
|
||||
)]
|
||||
ra_username: Option<String>,
|
||||
#[arg(
|
||||
long = "ra-token",
|
||||
value_name = "TOKEN",
|
||||
help = "Token for RetroAchievements",
|
||||
hide = true
|
||||
)]
|
||||
ra_token: Option<String>,
|
||||
#[arg(
|
||||
long = "ra-password",
|
||||
value_name = "PASSWORD",
|
||||
help = "Password for RetroAchievements"
|
||||
)]
|
||||
ra_password: Option<String>,
|
||||
#[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,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
#[tokio::main(worker_threads = 4)]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
let dirs = ui::get_dirs();
|
||||
|
||||
let mut result = std::fs::create_dir_all(dirs.config_dir);
|
||||
if result.is_err() {
|
||||
panic!("could not create config dir: {}", result.err().unwrap())
|
||||
}
|
||||
result = std::fs::create_dir_all(dirs.cache_dir.clone());
|
||||
if result.is_err() {
|
||||
panic!("could not create cache dir: {}", result.err().unwrap())
|
||||
}
|
||||
result = std::fs::create_dir_all(dirs.data_dir.join("saves"));
|
||||
if result.is_err() {
|
||||
panic!("could not create data dir: {}", result.err().unwrap())
|
||||
}
|
||||
let running_file = dirs.cache_dir.join("game_running");
|
||||
if running_file.exists() {
|
||||
result = std::fs::remove_file(running_file);
|
||||
if result.is_err() {
|
||||
panic!("could not remove running file: {}", result.err().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
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"))?;
|
||||
let args = Args::parse();
|
||||
let args_as_strings: Vec<String> = std::env::args().collect();
|
||||
let args_count = args_as_strings.len();
|
||||
if args_count > 1 {
|
||||
let mut device = device::Device::new();
|
||||
|
||||
if args.clear_input_bindings {
|
||||
ui::input::clear_bindings(&mut device.ui);
|
||||
return;
|
||||
}
|
||||
if args.port.is_some() {
|
||||
let port = args.port.unwrap();
|
||||
if !(1..=4).contains(&port) {
|
||||
println!("Port must be betwen 1 and 4");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if args.list_controllers {
|
||||
let controllers = gui::get_controller_names(&device.ui);
|
||||
for (i, controller) in controllers.iter().enumerate() {
|
||||
println!("Controller {}: {}", i, controller);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if args.assign_controller.is_some() {
|
||||
if args.port.is_none() {
|
||||
println!("Must specify port number");
|
||||
return;
|
||||
}
|
||||
ui::input::assign_controller(
|
||||
&mut device.ui,
|
||||
args.assign_controller.unwrap(),
|
||||
args.port.unwrap(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if args.bind_input_profile.is_some() {
|
||||
if args.port.is_none() {
|
||||
println!("Must specify port number");
|
||||
return;
|
||||
}
|
||||
ui::input::bind_input_profile(
|
||||
&mut device.ui,
|
||||
args.bind_input_profile.unwrap(),
|
||||
args.port.unwrap(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if args.configure_input_profile.is_some() {
|
||||
ui::input::configure_input_profile(
|
||||
&mut device.ui,
|
||||
args.configure_input_profile.unwrap(),
|
||||
args.use_dinput,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if args.game.is_some() {
|
||||
let file_path = std::path::Path::new(args.game.as_ref().unwrap());
|
||||
let rom_contents = device::get_rom_contents(file_path);
|
||||
if rom_contents.is_empty() {
|
||||
println!("Could not read rom file");
|
||||
return;
|
||||
}
|
||||
device::run_game(rom_contents, &mut device, args.fullscreen);
|
||||
}
|
||||
} else {
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: eframe::egui::ViewportBuilder::default()
|
||||
.with_inner_size([640.0, 480.0])
|
||||
.with_icon(
|
||||
eframe::icon_data::from_png_bytes(include_bytes!("../data/gopher64.png"))
|
||||
.unwrap(),
|
||||
),
|
||||
..Default::default()
|
||||
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()
|
||||
)));
|
||||
};
|
||||
|
||||
let controllers_paths;
|
||||
let controller_names;
|
||||
if let Some(slot) = args.load_state
|
||||
&& slot > 9
|
||||
{
|
||||
let game_ui = ui::Ui::new();
|
||||
controllers_paths = gui::get_controller_paths(&game_ui);
|
||||
controller_names = gui::get_controller_names(&game_ui);
|
||||
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;
|
||||
|
||||
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()
|
||||
{
|
||||
device.transferpaks[i].cart.rom =
|
||||
std::fs::read(&device.ui.config.input.gb_rom_path[i]).unwrap();
|
||||
device.transferpaks[i].cart.ram =
|
||||
std::fs::read(&device.ui.config.input.gb_ram_path[i]).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
if device.ui.config.emulation.usb {
|
||||
(shutdown_tx, 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::<bool>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
retroachievements::load_game(&rom_contents, rom_contents.len()).await;
|
||||
device::run_game(
|
||||
&mut device,
|
||||
&rom_contents,
|
||||
ui::GameSettings {
|
||||
overclock,
|
||||
disable_expansion_pak,
|
||||
cheats,
|
||||
load_savestate_slot: args.load_state,
|
||||
},
|
||||
);
|
||||
retroachievements::shutdown_client();
|
||||
|
||||
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()
|
||||
{
|
||||
tokio::fs::write(
|
||||
&device.ui.config.input.gb_ram_path[i],
|
||||
&device.transferpaks[i].cart.ram,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(shutdown_tx) = &shutdown_tx {
|
||||
ui::usb::close(shutdown_tx);
|
||||
}
|
||||
} else if std::env::args().count() > 1 {
|
||||
let mut ui = ui::Ui::new();
|
||||
|
||||
if args.clear_input_bindings {
|
||||
ui::input::clear_bindings(&mut ui);
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(port) = args.port
|
||||
&& !(1..=4).contains(&port)
|
||||
{
|
||||
return Err(Error::other("Port must be between 1 and 4"));
|
||||
}
|
||||
if args.list_controllers {
|
||||
let controllers = ui::input::get_controller_names(&ui);
|
||||
for (i, controller) in controllers.iter().enumerate() {
|
||||
println!("Controller {i}: {controller}");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(profile) = args.configure_input_profile {
|
||||
ui::input::configure_input_profile(
|
||||
&mut ui,
|
||||
profile,
|
||||
args.use_dinput,
|
||||
args.deadzone.unwrap_or(ui::input::DEADZONE_DEFAULT),
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(assign_controller) = args.assign_controller {
|
||||
let Some(port) = args.port else {
|
||||
return Err(Error::other("Must specify port number"));
|
||||
};
|
||||
ui::input::assign_controller(&mut ui, assign_controller, port);
|
||||
}
|
||||
if let Some(profile) = args.bind_input_profile {
|
||||
let Some(port) = args.port else {
|
||||
return Err(Error::other("Must specify port number"));
|
||||
};
|
||||
ui::input::bind_input_profile(&mut ui, profile, port);
|
||||
}
|
||||
} else {
|
||||
#[cfg(feature = "gui")]
|
||||
{
|
||||
retroachievements::init_client(false, false, false);
|
||||
ui::gui::app_window();
|
||||
retroachievements::shutdown_client();
|
||||
}
|
||||
eframe::run_native(
|
||||
"gopher64",
|
||||
options,
|
||||
Box::new(|cc| {
|
||||
Ok(Box::new(ui::gui::GopherEguiApp::new(
|
||||
cc,
|
||||
controllers_paths,
|
||||
controller_names,
|
||||
)))
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
use eframe::egui;
|
||||
use crate::{device, ui};
|
||||
use std::io::{Read, Write};
|
||||
//UDP packet formats
|
||||
const UDP_SEND_KEY_INFO: u8 = 0;
|
||||
@@ -11,12 +9,17 @@ const UDP_SYNC_DATA: u8 = 4;
|
||||
|
||||
//TCP packet formats
|
||||
const TCP_SEND_SAVE: u8 = 1;
|
||||
const TCP_RECEIVE_SAVE: u8 = 2;
|
||||
//const TCP_RECEIVE_SAVE: u8 = 2;
|
||||
//const TCP_SEND_SETTINGS: u8 = 3;
|
||||
//const TCP_RECEIVE_SETTINGS: u8 = 4;
|
||||
const TCP_REGISTER_PLAYER: u8 = 5;
|
||||
const TCP_GET_REGISTRATION: u8 = 6;
|
||||
const TCP_DISCONNECT_NOTICE: u8 = 7;
|
||||
const TCP_RECEIVE_SAVE_WITH_SIZE: u8 = 8;
|
||||
const TCP_SEND_RTC: u8 = 64;
|
||||
const TCP_RECEIVE_RTC: u8 = 128;
|
||||
const TCP_SEND_RNG: u8 = 65;
|
||||
const TCP_RECEIVE_RNG: u8 = 129;
|
||||
|
||||
const CS4: u32 = 32;
|
||||
|
||||
@@ -29,8 +32,6 @@ pub struct Netplay {
|
||||
status: u8,
|
||||
buffer_target: u8,
|
||||
pub fast_forward: bool,
|
||||
pub error_notifier: std::sync::mpsc::Sender<String>,
|
||||
pub gui_ctx: egui::Context,
|
||||
}
|
||||
|
||||
pub struct PlayerData {
|
||||
@@ -45,10 +46,36 @@ struct InputEvent {
|
||||
plugin: u8,
|
||||
}
|
||||
|
||||
fn send_error(netplay: &mut Netplay, error: String) {
|
||||
netplay.error_notifier.send(error).unwrap();
|
||||
pub fn send_rtc(netplay: &mut Netplay, rtc: i64) {
|
||||
let mut request: Vec<u8> = [TCP_SEND_RTC].to_vec();
|
||||
let size: u32 = 8;
|
||||
request.extend_from_slice(&size.to_be_bytes());
|
||||
request.extend_from_slice(&rtc.to_be_bytes());
|
||||
netplay.tcp_stream.write_all(&request).unwrap();
|
||||
}
|
||||
|
||||
netplay.gui_ctx.request_repaint(); // this is so the window pops up right away
|
||||
pub fn receive_rtc(netplay: &mut Netplay) -> i64 {
|
||||
let request: Vec<u8> = [TCP_RECEIVE_RTC].to_vec();
|
||||
netplay.tcp_stream.write_all(&request).unwrap();
|
||||
let mut rtc: [u8; 8] = [0; 8];
|
||||
netplay.tcp_stream.read_exact(&mut rtc).unwrap();
|
||||
i64::from_be_bytes(rtc)
|
||||
}
|
||||
|
||||
pub fn send_rng(netplay: &mut Netplay, seed: u64) {
|
||||
let mut request: Vec<u8> = [TCP_SEND_RNG].to_vec();
|
||||
let size: u32 = 8;
|
||||
request.extend_from_slice(&size.to_be_bytes());
|
||||
request.extend_from_slice(&seed.to_be_bytes());
|
||||
netplay.tcp_stream.write_all(&request).unwrap();
|
||||
}
|
||||
|
||||
pub fn receive_rng(netplay: &mut Netplay) -> u64 {
|
||||
let request: Vec<u8> = [TCP_RECEIVE_RNG].to_vec();
|
||||
netplay.tcp_stream.write_all(&request).unwrap();
|
||||
let mut seed: [u8; 8] = [0; 8];
|
||||
netplay.tcp_stream.read_exact(&mut seed).unwrap();
|
||||
u64::from_be_bytes(seed)
|
||||
}
|
||||
|
||||
pub fn send_save(netplay: &mut Netplay, save_type: &str, save_data: &[u8], size: usize) {
|
||||
@@ -57,31 +84,32 @@ pub fn send_save(netplay: &mut Netplay, save_type: &str, save_data: &[u8], size:
|
||||
request.push(0); // null terminate string
|
||||
request.extend_from_slice(&(size as u32).to_be_bytes());
|
||||
|
||||
let mut send_data = save_data.to_owned();
|
||||
send_data.resize(size, 0); // pad with zeros if needed
|
||||
request.extend(send_data);
|
||||
if size > 0 {
|
||||
request.extend_from_slice(save_data);
|
||||
}
|
||||
netplay.tcp_stream.write_all(&request).unwrap();
|
||||
}
|
||||
|
||||
pub fn receive_save(netplay: &mut Netplay, save_type: &str, save_data: &mut Vec<u8>, size: usize) {
|
||||
let mut request: Vec<u8> = [TCP_RECEIVE_SAVE].to_vec();
|
||||
pub fn receive_save(netplay: &mut Netplay, save_type: &str, save_data: &mut Vec<u8>) {
|
||||
let mut request: Vec<u8> = [TCP_RECEIVE_SAVE_WITH_SIZE].to_vec();
|
||||
request.extend_from_slice(save_type.as_bytes());
|
||||
request.push(0); // null terminate string
|
||||
netplay.tcp_stream.write_all(&request).unwrap();
|
||||
|
||||
let mut response: Vec<u8> = vec![0; size];
|
||||
let mut size: [u8; 4] = [0; 4];
|
||||
netplay.tcp_stream.read_exact(&mut size).unwrap();
|
||||
let mut response: Vec<u8> = vec![0; u32::from_be_bytes(size) as usize];
|
||||
netplay.tcp_stream.read_exact(&mut response).unwrap();
|
||||
*save_data = response;
|
||||
}
|
||||
|
||||
pub fn send_sync_check(device: &mut device::Device) {
|
||||
let netplay = device.netplay.as_mut().unwrap();
|
||||
if netplay.vi_counter % 600 == 0 {
|
||||
pub fn send_sync_check(netplay: &mut Netplay, regs: &[u64]) {
|
||||
if netplay.vi_counter.is_multiple_of(600) {
|
||||
let mut request: Vec<u8> = [UDP_SYNC_DATA].to_vec();
|
||||
request.extend_from_slice(&(netplay.vi_counter).to_be_bytes());
|
||||
|
||||
for i in 0..device::cop0::COP0_REGS_COUNT as usize {
|
||||
request.extend_from_slice(&(device.cpu.cop0.regs[i] as u32).to_be_bytes());
|
||||
for item in regs.iter() {
|
||||
request.extend_from_slice(&(*item as u32).to_be_bytes());
|
||||
}
|
||||
|
||||
netplay.udp_socket.send(&request).unwrap();
|
||||
@@ -89,18 +117,18 @@ pub fn send_sync_check(device: &mut device::Device) {
|
||||
netplay.vi_counter = netplay.vi_counter.wrapping_add(1);
|
||||
}
|
||||
|
||||
pub fn send_input(netplay: &Netplay, input: (u32, bool)) {
|
||||
pub fn send_input(netplay: &Netplay, input: ui::input::InputData) {
|
||||
let mut request: Vec<u8> = [UDP_SEND_KEY_INFO].to_vec();
|
||||
request.push(netplay.player_number);
|
||||
request.extend_from_slice(
|
||||
&(netplay.player_data[netplay.player_number as usize].count).to_be_bytes(),
|
||||
);
|
||||
request.extend_from_slice(&(input.0).to_be_bytes());
|
||||
request.push(input.1 as u8);
|
||||
request.extend_from_slice(&(input.data).to_be_bytes());
|
||||
request.push(input.pak_change_pressed as u8);
|
||||
netplay.udp_socket.send(&request).unwrap();
|
||||
}
|
||||
|
||||
pub fn get_input(device: &mut device::Device, channel: usize) -> (u32, bool) {
|
||||
pub fn get_input(device: &mut device::Device, channel: usize) -> ui::input::InputData {
|
||||
let netplay = device.netplay.as_mut().unwrap();
|
||||
let mut input = None;
|
||||
|
||||
@@ -118,9 +146,9 @@ pub fn get_input(device: &mut device::Device, channel: usize) -> (u32, bool) {
|
||||
.remove(&netplay.player_data[channel].count);
|
||||
|
||||
if std::time::Instant::now() > timeout {
|
||||
send_error(
|
||||
netplay,
|
||||
"Timed out waiting for input. Lost connection to server".to_string(),
|
||||
ui::video::onscreen_message(
|
||||
"Lost connection to netplay server",
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_SHORT,
|
||||
);
|
||||
input = Some(InputEvent {
|
||||
input: 0,
|
||||
@@ -134,10 +162,10 @@ pub fn get_input(device: &mut device::Device, channel: usize) -> (u32, bool) {
|
||||
&& netplay.player_data[channel].input_events.len() as u8 > netplay.buffer_target;
|
||||
|
||||
netplay.player_data[channel].count = netplay.player_data[channel].count.wrapping_add(1);
|
||||
(
|
||||
input.as_ref().unwrap().input,
|
||||
input.as_ref().unwrap().plugin != 0,
|
||||
)
|
||||
ui::input::InputData {
|
||||
data: input.as_ref().unwrap().input,
|
||||
pak_change_pressed: input.as_ref().unwrap().plugin != 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn request_input(netplay: &Netplay, channel: usize) {
|
||||
@@ -166,15 +194,18 @@ fn process_incoming(netplay: &mut Netplay) {
|
||||
}
|
||||
if current_status != netplay.status {
|
||||
if ((current_status & 0x1) ^ (netplay.status & 0x1)) != 0 {
|
||||
send_error(
|
||||
netplay,
|
||||
format!("Players have desynced at VI {}", netplay.vi_counter),
|
||||
ui::video::onscreen_message(
|
||||
"Netplay desync detected",
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_LONG,
|
||||
);
|
||||
}
|
||||
for dis in 1..5 {
|
||||
if ((current_status & (0x1 << dis)) ^ (netplay.status & (0x1 << dis))) != 0
|
||||
{
|
||||
send_error(netplay, format!("Player {} has disconnected", dis));
|
||||
ui::video::onscreen_message(
|
||||
&format!("Player {dis} disconnected"),
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_SHORT,
|
||||
);
|
||||
}
|
||||
}
|
||||
netplay.status = current_status;
|
||||
@@ -206,26 +237,19 @@ fn process_incoming(netplay: &mut Netplay) {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
panic! {"unknown UDP packet"}
|
||||
panic!("unknown UDP packet")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(
|
||||
mut peer_addr: std::net::SocketAddr,
|
||||
session: ui::gui::gui_netplay::NetplayRoom,
|
||||
player_number: u8,
|
||||
error_notifier: std::sync::mpsc::Sender<String>,
|
||||
gui_ctx: egui::Context,
|
||||
) -> Netplay {
|
||||
peer_addr.set_port(session.port.unwrap() as u16);
|
||||
pub fn init(peer_addr: std::net::SocketAddr, player_number: u8) -> Netplay {
|
||||
let udp_socket;
|
||||
if peer_addr.is_ipv4() {
|
||||
udp_socket = std::net::UdpSocket::bind((std::net::Ipv4Addr::UNSPECIFIED, 0))
|
||||
.expect("couldn't bind to address");
|
||||
socket2::SockRef::from(&udp_socket)
|
||||
.set_tos(CS4 << 2)
|
||||
.set_tos_v4(CS4 << 2)
|
||||
.unwrap();
|
||||
} else {
|
||||
udp_socket = std::net::UdpSocket::bind((std::net::Ipv6Addr::UNSPECIFIED, 0))
|
||||
@@ -281,8 +305,6 @@ pub fn init(
|
||||
status: 0,
|
||||
buffer_target,
|
||||
fast_forward: false,
|
||||
error_notifier,
|
||||
gui_ctx,
|
||||
player_data: [
|
||||
PlayerData {
|
||||
lag: 0,
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
include!(concat!(env!("OUT_DIR"), "/retroachievements_bindings.rs"));
|
||||
|
||||
use crate::ui;
|
||||
|
||||
static WEB_CLIENT: std::sync::LazyLock<reqwest::Client> = std::sync::LazyLock::new(|| {
|
||||
reqwest::Client::builder()
|
||||
.user_agent(format!(
|
||||
"{}/{}",
|
||||
env!("CARGO_PKG_NAME"),
|
||||
env!("GIT_DESCRIBE")
|
||||
))
|
||||
.build()
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct RAConfig {
|
||||
pub username: String,
|
||||
pub token: String,
|
||||
pub enabled: bool,
|
||||
pub hardcore: bool,
|
||||
pub challenge: bool,
|
||||
pub leaderboard: bool,
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn notify_load_game(ctx: *mut std::ffi::c_void) {
|
||||
let tx = unsafe { Box::from_raw(ctx as *mut tokio::sync::oneshot::Sender<bool>) };
|
||||
tx.send(true).unwrap();
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn store_retroachievements_credentials(
|
||||
c_username: *const std::ffi::c_char,
|
||||
c_token: *const std::ffi::c_char,
|
||||
ctx: *mut std::ffi::c_void,
|
||||
) {
|
||||
let tx = unsafe { Box::from_raw(ctx as *mut tokio::sync::oneshot::Sender<bool>) };
|
||||
|
||||
if c_username.is_null() || c_token.is_null() {
|
||||
tx.send(false).unwrap();
|
||||
return;
|
||||
}
|
||||
|
||||
let file_path = ui::get_dirs().config_dir.join("retroachievements.json");
|
||||
let raconfig = if let Ok(ra_config) = std::fs::read(&file_path)
|
||||
&& let Ok(result) = serde_json::from_slice::<RAConfig>(ra_config.as_ref())
|
||||
{
|
||||
RAConfig {
|
||||
username: unsafe { std::ffi::CStr::from_ptr(c_username).to_str().unwrap() }.to_string(),
|
||||
token: unsafe { std::ffi::CStr::from_ptr(c_token).to_str().unwrap() }.to_string(),
|
||||
enabled: result.enabled,
|
||||
hardcore: result.hardcore,
|
||||
challenge: result.challenge,
|
||||
leaderboard: result.leaderboard,
|
||||
}
|
||||
} else {
|
||||
RAConfig {
|
||||
username: unsafe { std::ffi::CStr::from_ptr(c_username).to_str().unwrap() }.to_string(),
|
||||
token: unsafe { std::ffi::CStr::from_ptr(c_token).to_str().unwrap() }.to_string(),
|
||||
enabled: false,
|
||||
hardcore: false,
|
||||
challenge: false,
|
||||
leaderboard: false,
|
||||
}
|
||||
};
|
||||
let f = std::fs::File::create(&file_path).unwrap();
|
||||
serde_json::to_writer_pretty(f, &raconfig).unwrap();
|
||||
|
||||
tx.send(true).unwrap();
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn rust_server_call(
|
||||
c_url: *const std::ffi::c_char,
|
||||
c_post_data: *const std::ffi::c_char,
|
||||
c_content_type: *const std::ffi::c_char,
|
||||
c_callback: *mut std::ffi::c_void,
|
||||
c_callback_data: *mut std::ffi::c_void,
|
||||
) {
|
||||
let url = unsafe { std::ffi::CStr::from_ptr(c_url).to_str().unwrap() }.to_string();
|
||||
|
||||
let task = if !c_post_data.is_null() {
|
||||
let post_data =
|
||||
unsafe { std::ffi::CStr::from_ptr(c_post_data).to_str().unwrap() }.to_string();
|
||||
let content_type =
|
||||
unsafe { std::ffi::CStr::from_ptr(c_content_type).to_str().unwrap() }.to_string();
|
||||
WEB_CLIENT
|
||||
.post(url)
|
||||
.body(post_data)
|
||||
.header(reqwest::header::CONTENT_TYPE, content_type)
|
||||
.send()
|
||||
} else {
|
||||
WEB_CLIENT.get(url).send()
|
||||
};
|
||||
let callback = c_callback.addr();
|
||||
let callback_data = c_callback_data.addr();
|
||||
tokio::spawn(async move {
|
||||
let response = task.await;
|
||||
match response {
|
||||
Ok(response) => {
|
||||
let status = response.status().as_u16() as i32;
|
||||
match response.text().await {
|
||||
Ok(text) => {
|
||||
let c_text = std::ffi::CString::new(text).unwrap();
|
||||
unsafe {
|
||||
ra_http_callback(
|
||||
c_text.as_ptr(),
|
||||
c_text.count_bytes(),
|
||||
status,
|
||||
callback as *mut std::ffi::c_void,
|
||||
callback_data as *mut std::ffi::c_void,
|
||||
)
|
||||
};
|
||||
}
|
||||
Err(error) => {
|
||||
let c_error = std::ffi::CString::new(error.to_string()).unwrap();
|
||||
unsafe {
|
||||
ra_http_callback(
|
||||
c_error.as_ptr(),
|
||||
c_error.count_bytes(),
|
||||
status,
|
||||
callback as *mut std::ffi::c_void,
|
||||
callback_data as *mut std::ffi::c_void,
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
let c_error = std::ffi::CString::new(error.to_string()).unwrap();
|
||||
unsafe {
|
||||
ra_http_callback(
|
||||
c_error.as_ptr(),
|
||||
c_error.count_bytes(),
|
||||
0,
|
||||
callback as *mut std::ffi::c_void,
|
||||
callback_data as *mut std::ffi::c_void,
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn load_game(rom: &[u8], rom_size: usize) {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel::<bool>();
|
||||
unsafe {
|
||||
let tx_ptr = Box::into_raw(Box::new(tx)) as *mut std::ffi::c_void;
|
||||
ra_load_game(rom.as_ptr(), rom_size, tx_ptr);
|
||||
};
|
||||
rx.await.unwrap();
|
||||
}
|
||||
|
||||
pub fn unload_game() {
|
||||
unsafe { ra_unload_game() };
|
||||
}
|
||||
|
||||
pub fn welcome() {
|
||||
unsafe { ra_welcome() };
|
||||
}
|
||||
|
||||
pub fn set_dmem(dmem: *const u8, dmem_size: usize) {
|
||||
unsafe { ra_set_dmem(dmem, dmem_size) };
|
||||
}
|
||||
|
||||
pub fn do_frame() {
|
||||
unsafe { ra_do_frame() };
|
||||
}
|
||||
|
||||
pub fn do_idle() {
|
||||
unsafe { ra_do_idle() };
|
||||
}
|
||||
|
||||
pub fn init_client(hardcore: bool, challenge: bool, leaderboard: bool) {
|
||||
unsafe { ra_init_client(hardcore, challenge, leaderboard) };
|
||||
}
|
||||
|
||||
pub fn shutdown_client() {
|
||||
unsafe { ra_shutdown_client() };
|
||||
}
|
||||
|
||||
pub fn get_hardcore() -> bool {
|
||||
unsafe { ra_get_hardcore() }
|
||||
}
|
||||
|
||||
pub fn login_user(username: String, password: String, tx: tokio::sync::oneshot::Sender<bool>) {
|
||||
unsafe {
|
||||
let tx_ptr = Box::into_raw(Box::new(tx)) as *mut std::ffi::c_void;
|
||||
let c_username = std::ffi::CString::new(username).unwrap();
|
||||
let c_password = std::ffi::CString::new(password).unwrap();
|
||||
ra_login_user(c_username.as_ptr(), c_password.as_ptr(), tx_ptr)
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "gui")]
|
||||
pub fn logout_user() {
|
||||
unsafe { ra_logout_user() };
|
||||
}
|
||||
|
||||
pub fn login_token_user(username: String, token: String, tx: tokio::sync::oneshot::Sender<bool>) {
|
||||
unsafe {
|
||||
let tx_ptr = Box::into_raw(Box::new(tx)) as *mut std::ffi::c_void;
|
||||
let c_username = std::ffi::CString::new(username).unwrap();
|
||||
let c_token = std::ffi::CString::new(token).unwrap();
|
||||
ra_login_token_user(c_username.as_ptr(), c_token.as_ptr(), tx_ptr)
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "gui")]
|
||||
pub fn get_username() -> Option<String> {
|
||||
let c_username = unsafe { ra_get_username() };
|
||||
if c_username.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
unsafe { std::ffi::CStr::from_ptr(c_username) }
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "gui")]
|
||||
pub fn get_token() -> Option<String> {
|
||||
let c_token = unsafe { ra_get_token() };
|
||||
if c_token.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
unsafe { std::ffi::CStr::from_ptr(c_token) }
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn state_size() -> usize {
|
||||
unsafe { ra_state_size() }
|
||||
}
|
||||
|
||||
pub fn save_state(state: *mut u8, state_size: usize) {
|
||||
unsafe { ra_save_state(state, state_size) };
|
||||
}
|
||||
|
||||
pub fn load_state(state: *const u8, state_size: usize) {
|
||||
unsafe { ra_load_state(state, state_size) };
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
use crate::{cheats, device, retroachievements, ui};
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use device::__m128i;
|
||||
use serde::de::{Deserialize, Deserializer, SeqAccess, Visitor};
|
||||
use serde::ser::{Serialize, SerializeSeq, Serializer};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use std::arch::x86_64::*;
|
||||
|
||||
struct M128iArrayVisitor<const N: usize>;
|
||||
|
||||
impl<'de, const N: usize> Visitor<'de> for M128iArrayVisitor<N> {
|
||||
type Value = [__m128i; N];
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str(&format!("an array of {N} 128-bit integers"))
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
let mut arr: [__m128i; N] = [device::zero_m128i(); N];
|
||||
for (index, item) in arr.iter_mut().enumerate() {
|
||||
match seq.next_element::<u128>()? {
|
||||
Some(value) => *item = unsafe { std::mem::transmute::<u128, __m128i>(value) },
|
||||
None => return Err(serde::de::Error::invalid_length(index, &self)),
|
||||
}
|
||||
}
|
||||
Ok(arr)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize_m128i_array<'de, D, const N: usize>(
|
||||
deserializer: D,
|
||||
) -> Result<[__m128i; N], D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_seq(M128iArrayVisitor)
|
||||
}
|
||||
|
||||
pub fn serialize_m128i<S>(data: &__m128i, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let bytes: u128 = unsafe { std::mem::transmute(*data) };
|
||||
bytes.serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize_m128i<'de, D>(deserializer: D) -> Result<__m128i, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let bytes = u128::deserialize(deserializer)?;
|
||||
Ok(unsafe { std::mem::transmute::<u128, __m128i>(bytes) })
|
||||
}
|
||||
|
||||
pub fn serialize_m128i_array<S>(value: &[__m128i], serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut seq = serializer.serialize_seq(Some(value.len()))?;
|
||||
|
||||
for item in value {
|
||||
let bytes: u128 = unsafe { std::mem::transmute(*item) };
|
||||
seq.serialize_element(&bytes)?;
|
||||
}
|
||||
|
||||
seq.end()
|
||||
}
|
||||
|
||||
pub fn create_savestate(device: &device::Device) {
|
||||
let mut rdp_state: Vec<u8> = vec![0; ui::video::state_size()];
|
||||
ui::video::save_state(rdp_state.as_mut_ptr());
|
||||
|
||||
let mut ra_state: Vec<u8> = vec![0; retroachievements::state_size()];
|
||||
retroachievements::save_state(ra_state.as_mut_ptr(), ra_state.len());
|
||||
|
||||
let data: &[(&[u8], &str)] = &[
|
||||
(&postcard::to_stdvec(device).unwrap(), "device"),
|
||||
(
|
||||
&postcard::to_stdvec(&device.ui.storage.saves).unwrap(),
|
||||
"saves",
|
||||
),
|
||||
(&rdp_state, "rdp_state"),
|
||||
(&ra_state, "ra_state"),
|
||||
];
|
||||
let save_path = device.ui.storage.paths.savestate_file_path.clone();
|
||||
let compressed_file = ui::storage::compress_file(data);
|
||||
tokio::spawn(async move {
|
||||
tokio::fs::write(save_path, compressed_file).await.unwrap();
|
||||
});
|
||||
ui::video::onscreen_message(
|
||||
&format!(
|
||||
"Savestate created in slot {}",
|
||||
device.ui.storage.save_state_slot
|
||||
),
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_SHORT,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn load_savestate(device: &mut device::Device) {
|
||||
if retroachievements::get_hardcore() {
|
||||
ui::video::onscreen_message(
|
||||
"Cannot load savestate in RA hardcore mode",
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_SHORT,
|
||||
);
|
||||
return;
|
||||
}
|
||||
let savestate = std::fs::read(&device.ui.storage.paths.savestate_file_path);
|
||||
if let Ok(savestate) = &savestate {
|
||||
let device_bytes = ui::storage::decompress_file(savestate, "device");
|
||||
let save_bytes = ui::storage::decompress_file(savestate, "saves");
|
||||
let rdp_state = ui::storage::decompress_file(savestate, "rdp_state");
|
||||
let ra_state = ui::storage::decompress_file(savestate, "ra_state");
|
||||
if let Ok(state) = postcard::from_bytes::<device::Device>(&device_bytes) {
|
||||
device.ui.storage.saves = postcard::from_bytes(&save_bytes).unwrap();
|
||||
|
||||
device.cpu = state.cpu;
|
||||
device.pif = state.pif;
|
||||
|
||||
let rom = device.cart.rom.clone();
|
||||
device.cart = state.cart;
|
||||
device.cart.rom = rom;
|
||||
|
||||
device.memory = state.memory;
|
||||
device.rsp = state.rsp;
|
||||
device.rdp = state.rdp;
|
||||
device.rdram = state.rdram;
|
||||
device.mi = state.mi;
|
||||
device.pi = state.pi;
|
||||
device.vi = state.vi;
|
||||
device.ai = state.ai;
|
||||
device.si = state.si;
|
||||
device.ri = state.ri;
|
||||
device.vru = state.vru;
|
||||
device.cheats = state.cheats;
|
||||
|
||||
let mut tpak_rom = [vec![], vec![], vec![], vec![]];
|
||||
for (i, item) in tpak_rom.iter_mut().enumerate() {
|
||||
*item = device.transferpaks[i].cart.rom.clone();
|
||||
}
|
||||
device.transferpaks = state.transferpaks;
|
||||
for (i, item) in tpak_rom.iter().enumerate() {
|
||||
device.transferpaks[i].cart.rom = item.clone();
|
||||
}
|
||||
|
||||
device::memory::init(device);
|
||||
device::vi::set_expected_refresh_rate(device);
|
||||
device::cpu::map_instructions(device);
|
||||
device::cop0::map_instructions(device);
|
||||
device::cop1::map_instructions(device);
|
||||
device::cop2::map_instructions(device);
|
||||
device::rsp_cpu::map_instructions(device);
|
||||
|
||||
let mut mem_addr = 0x1000;
|
||||
while mem_addr < 0x2000 {
|
||||
let data =
|
||||
u32::from_be_bytes(device.rsp.mem[mem_addr..mem_addr + 4].try_into().unwrap());
|
||||
device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].func =
|
||||
device::rsp_cpu::decode_opcode(device, data);
|
||||
device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].opcode = data;
|
||||
mem_addr += 4;
|
||||
}
|
||||
|
||||
for line_index in 0..512 {
|
||||
for i in 0..8 {
|
||||
device.memory.icache[line_index].instruction[i] = device::cpu::decode_opcode(
|
||||
device,
|
||||
device.memory.icache[line_index].words[i],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
device::pif::connect_pif_channels(device);
|
||||
for i in 0..4 {
|
||||
if let Some(handler) = device.pif.channels[i].pak_handler {
|
||||
if handler.pak_type == device::controller::PakType::RumblePak {
|
||||
let rumblepak_handler = device::controller::PakHandler {
|
||||
read: device::controller::rumble::read,
|
||||
write: device::controller::rumble::write,
|
||||
pak_type: device::controller::PakType::RumblePak,
|
||||
};
|
||||
device.pif.channels[i].pak_handler = Some(rumblepak_handler);
|
||||
} else if handler.pak_type == device::controller::PakType::MemPak {
|
||||
let mempak_handler = device::controller::PakHandler {
|
||||
read: device::controller::mempak::read,
|
||||
write: device::controller::mempak::write,
|
||||
pak_type: device::controller::PakType::MemPak,
|
||||
};
|
||||
device.pif.channels[i].pak_handler = Some(mempak_handler);
|
||||
} else if handler.pak_type == device::controller::PakType::TransferPak {
|
||||
let tpak_handler = device::controller::PakHandler {
|
||||
read: device::controller::transferpak::read,
|
||||
write: device::controller::transferpak::write,
|
||||
pak_type: device::controller::PakType::TransferPak,
|
||||
};
|
||||
device.pif.channels[i].pak_handler = Some(tpak_handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ui::audio::close_game_audio(&mut device.ui);
|
||||
ui::audio::init_game_audio(&mut device.ui, device.ai.freq);
|
||||
ui::video::load_state(device, rdp_state.as_ptr());
|
||||
|
||||
if !ra_state.is_empty() {
|
||||
retroachievements::load_state(ra_state.as_ptr(), ra_state.len());
|
||||
} else {
|
||||
retroachievements::load_state(std::ptr::null(), 0);
|
||||
}
|
||||
|
||||
if device.cheats.enabled {
|
||||
cheats::execute_cheats(device, device.cheats.cheats.clone());
|
||||
}
|
||||
|
||||
ui::video::onscreen_message(
|
||||
&format!(
|
||||
"Savestate loaded from slot {}",
|
||||
device.ui.storage.save_state_slot
|
||||
),
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_SHORT,
|
||||
);
|
||||
} else {
|
||||
ui::video::onscreen_message(
|
||||
&format!(
|
||||
"Failed to load savestate from slot {}",
|
||||
device.ui.storage.save_state_slot
|
||||
),
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_SHORT,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
ui::video::onscreen_message(
|
||||
&format!(
|
||||
"Could not find savestate in slot {}",
|
||||
device.ui.storage.save_state_slot
|
||||
),
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_SHORT,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_pak_handler() -> fn(&mut device::Device, usize, u16, usize, usize) {
|
||||
device::controller::mempak::read
|
||||
}
|
||||
|
||||
pub fn default_instruction() -> fn(&mut device::Device, u32) {
|
||||
device::cop0::reserved
|
||||
}
|
||||
|
||||
pub fn default_instructions<const N: usize>() -> [fn(&mut device::Device, u32); N]
|
||||
where
|
||||
[fn(&mut device::Device, u32); N]: Sized,
|
||||
{
|
||||
[device::cop0::reserved; N]
|
||||
}
|
||||
|
||||
pub fn default_memory_read_fast()
|
||||
-> [fn(&device::Device, u64, device::memory::AccessSize) -> u32; 0x2000] {
|
||||
[device::unmapped::read_mem_fast; 0x2000]
|
||||
}
|
||||
|
||||
pub fn default_memory_read()
|
||||
-> [fn(&mut device::Device, u64, device::memory::AccessSize) -> u32; 0x2000] {
|
||||
[device::unmapped::read_mem; 0x2000]
|
||||
}
|
||||
|
||||
pub fn default_memory_write() -> [fn(&mut device::Device, u64, u32, u32); 0x2000] {
|
||||
[device::unmapped::write_mem; 0x2000]
|
||||
}
|
||||
@@ -1,54 +1,109 @@
|
||||
pub mod audio;
|
||||
#[cfg(feature = "gui")]
|
||||
pub mod cheats;
|
||||
pub mod config;
|
||||
#[cfg(feature = "gui")]
|
||||
pub mod gui;
|
||||
pub mod input;
|
||||
#[cfg(feature = "gui")]
|
||||
pub mod netplay;
|
||||
#[cfg(feature = "gui")]
|
||||
pub mod retroachievements;
|
||||
pub mod storage;
|
||||
pub mod usb;
|
||||
pub mod video;
|
||||
#[cfg(feature = "gui")]
|
||||
pub mod vru;
|
||||
|
||||
pub struct Dirs {
|
||||
pub config_dir: std::path::PathBuf,
|
||||
pub cache_dir: std::path::PathBuf,
|
||||
pub data_dir: std::path::PathBuf,
|
||||
pub cache_dir: std::path::PathBuf,
|
||||
}
|
||||
|
||||
pub struct Audio {
|
||||
pub audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
|
||||
pub gain: f32,
|
||||
}
|
||||
|
||||
pub struct Input {
|
||||
pub joysticks: Vec<sdl3_sys::joystick::SDL_JoystickID>,
|
||||
pub keyboard_state: *const bool,
|
||||
pub controllers: [input::Controllers; 4],
|
||||
}
|
||||
|
||||
pub struct Storage {
|
||||
pub save_type: Vec<storage::SaveTypes>,
|
||||
pub paths: storage::Paths,
|
||||
pub saves: storage::Saves,
|
||||
pub save_state_slot: u32,
|
||||
}
|
||||
|
||||
pub struct Video {
|
||||
pub window: *mut sdl3_sys::video::SDL_Window,
|
||||
pub fullscreen: bool,
|
||||
pub fps_tx: tokio::sync::mpsc::Sender<bool>,
|
||||
pub fps_rx: Option<tokio::sync::mpsc::Receiver<bool>>,
|
||||
pub vis_tx: tokio::sync::mpsc::Sender<bool>,
|
||||
pub vis_rx: Option<tokio::sync::mpsc::Receiver<bool>>,
|
||||
}
|
||||
|
||||
pub struct Usb {
|
||||
pub usb_tx: Option<tokio::sync::broadcast::Sender<usb::UsbData>>,
|
||||
pub cart_rx: Option<tokio::sync::broadcast::Receiver<usb::UsbData>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GameSettings {
|
||||
pub overclock: bool,
|
||||
pub disable_expansion_pak: bool,
|
||||
pub cheats: std::collections::HashMap<String, Option<String>>,
|
||||
pub load_savestate_slot: Option<u32>,
|
||||
}
|
||||
|
||||
pub struct Ui {
|
||||
pub dirs: Dirs,
|
||||
pub keyboard_state: *const bool,
|
||||
pub controllers: [input::Controllers; 4],
|
||||
pub config: config::Config,
|
||||
pub save_type: Vec<storage::SaveTypes>,
|
||||
pub game_id: String,
|
||||
pub game_hash: String,
|
||||
pub paths: storage::Paths,
|
||||
pub saves: storage::Saves,
|
||||
pub pak_audio: audio::PakAudio,
|
||||
pub window: *mut sdl3_sys::video::SDL_Window,
|
||||
pub audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
|
||||
pub pak_audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
|
||||
pub audio_freq: f64,
|
||||
pub audio_device: u32,
|
||||
pub num_joysticks: i32,
|
||||
pub joysticks: *mut sdl3_sys::joystick::SDL_JoystickID,
|
||||
pub with_sdl: bool,
|
||||
pub audio: Audio,
|
||||
pub input: Input,
|
||||
pub storage: Storage,
|
||||
pub video: Video,
|
||||
pub usb: Usb,
|
||||
}
|
||||
|
||||
impl Drop for Ui {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
sdl3_sys::stdinc::SDL_free(self.joysticks as *mut std::ffi::c_void);
|
||||
sdl3_sys::init::SDL_Quit();
|
||||
if self.with_sdl {
|
||||
unsafe {
|
||||
sdl3_ttf_sys::ttf::TTF_Quit();
|
||||
sdl3_sys::init::SDL_Quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sdl_init(flag: sdl3_sys::init::SDL_InitFlags) {
|
||||
unsafe {
|
||||
let init = sdl3_sys::init::SDL_WasInit(0);
|
||||
if init & flag == 0 && !sdl3_sys::init::SDL_InitSubSystem(flag) {
|
||||
if sdl3_sys::init::SDL_WasInit(flag) == 0 && !sdl3_sys::init::SDL_InitSubSystem(flag) {
|
||||
let err = std::ffi::CStr::from_ptr(sdl3_sys::error::SDL_GetError())
|
||||
.to_str()
|
||||
.unwrap();
|
||||
panic!("Could not initialize SDL subsystem: {}, {}", flag, err);
|
||||
panic!(
|
||||
"Could not initialize SDL subsystem: {}, {}",
|
||||
u32::from(flag),
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ttf_init() {
|
||||
unsafe {
|
||||
if !sdl3_ttf_sys::ttf::TTF_Init() {
|
||||
panic!("Could not initialize TTF");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,27 +112,122 @@ pub fn get_dirs() -> Dirs {
|
||||
let exe_path = std::env::current_exe().unwrap();
|
||||
let portable_dir = exe_path.parent();
|
||||
let portable = portable_dir.unwrap().join("portable.txt").exists();
|
||||
let config_dir;
|
||||
let cache_dir;
|
||||
let data_dir;
|
||||
if portable {
|
||||
config_dir = portable_dir.unwrap().join("portable_data").join("config");
|
||||
cache_dir = portable_dir.unwrap().join("portable_data").join("cache");
|
||||
data_dir = portable_dir.unwrap().join("portable_data").join("data");
|
||||
Dirs {
|
||||
config_dir: portable_dir.unwrap().join("portable_data").join("config"),
|
||||
data_dir: portable_dir.unwrap().join("portable_data").join("data"),
|
||||
cache_dir: portable_dir.unwrap().join("portable_data").join("cache"),
|
||||
}
|
||||
} else {
|
||||
config_dir = dirs::config_dir().unwrap().join("gopher64");
|
||||
cache_dir = dirs::cache_dir().unwrap().join("gopher64");
|
||||
data_dir = dirs::data_dir().unwrap().join("gopher64");
|
||||
};
|
||||
|
||||
Dirs {
|
||||
config_dir,
|
||||
cache_dir,
|
||||
data_dir,
|
||||
Dirs {
|
||||
config_dir: dirs::config_dir().unwrap().join("gopher64"),
|
||||
data_dir: dirs::data_dir().unwrap().join("gopher64"),
|
||||
cache_dir: dirs::cache_dir().unwrap().join("gopher64"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
fn construct_ui(joysticks: Vec<sdl3_sys::everything::SDL_JoystickID>, with_sdl: bool) -> Ui {
|
||||
let dirs = get_dirs();
|
||||
|
||||
let (fps_tx, fps_rx) = tokio::sync::mpsc::channel(1000);
|
||||
let (vis_tx, vis_rx) = tokio::sync::mpsc::channel(1000);
|
||||
Ui {
|
||||
input: Input {
|
||||
controllers: [
|
||||
input::Controllers {
|
||||
game_controller: std::ptr::null_mut(),
|
||||
joystick: std::ptr::null_mut(),
|
||||
rumble: false,
|
||||
},
|
||||
input::Controllers {
|
||||
game_controller: std::ptr::null_mut(),
|
||||
joystick: std::ptr::null_mut(),
|
||||
rumble: false,
|
||||
},
|
||||
input::Controllers {
|
||||
game_controller: std::ptr::null_mut(),
|
||||
joystick: std::ptr::null_mut(),
|
||||
rumble: false,
|
||||
},
|
||||
input::Controllers {
|
||||
game_controller: std::ptr::null_mut(),
|
||||
joystick: std::ptr::null_mut(),
|
||||
rumble: false,
|
||||
},
|
||||
],
|
||||
keyboard_state: std::ptr::null_mut(),
|
||||
joysticks,
|
||||
},
|
||||
storage: Storage {
|
||||
save_state_slot: 0,
|
||||
save_type: vec![],
|
||||
paths: storage::Paths {
|
||||
eep_file_path: std::path::PathBuf::new(),
|
||||
fla_file_path: std::path::PathBuf::new(),
|
||||
sra_file_path: std::path::PathBuf::new(),
|
||||
pak_file_path: std::path::PathBuf::new(),
|
||||
sdcard_file_path: std::path::PathBuf::new(),
|
||||
romsave_file_path: std::path::PathBuf::new(),
|
||||
savestate_file_path: std::path::PathBuf::new(),
|
||||
},
|
||||
saves: storage::Saves {
|
||||
write_to_disk: true,
|
||||
eeprom: storage::Save {
|
||||
data: Vec::new(),
|
||||
write_pending: false,
|
||||
},
|
||||
sram: storage::Save {
|
||||
data: Vec::new(),
|
||||
write_pending: false,
|
||||
},
|
||||
flash: storage::Save {
|
||||
data: Vec::new(),
|
||||
write_pending: false,
|
||||
},
|
||||
mempak: storage::Save {
|
||||
data: Vec::new(),
|
||||
write_pending: false,
|
||||
},
|
||||
sdcard: storage::Save {
|
||||
data: Vec::new(),
|
||||
write_pending: false,
|
||||
},
|
||||
romsave: storage::RomSave {
|
||||
data: std::collections::HashMap::new(),
|
||||
write_pending: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
config: config::Config::new(),
|
||||
game_id: String::new(),
|
||||
game_hash: String::new(),
|
||||
audio: Audio {
|
||||
audio_stream: std::ptr::null_mut(),
|
||||
gain: 1.0,
|
||||
},
|
||||
video: Video {
|
||||
window: std::ptr::null_mut(),
|
||||
fullscreen: false,
|
||||
fps_tx,
|
||||
fps_rx: Some(fps_rx),
|
||||
vis_tx,
|
||||
vis_rx: Some(vis_rx),
|
||||
},
|
||||
usb: Usb {
|
||||
usb_tx: None,
|
||||
cart_rx: None,
|
||||
},
|
||||
dirs,
|
||||
with_sdl,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default() -> Ui {
|
||||
Self::construct_ui(vec![], false)
|
||||
}
|
||||
|
||||
pub fn new() -> Ui {
|
||||
sdl_init(sdl3_sys::init::SDL_INIT_GAMEPAD);
|
||||
let mut num_joysticks = 0;
|
||||
@@ -85,63 +235,11 @@ impl Ui {
|
||||
if joysticks.is_null() {
|
||||
panic!("Could not get joystick list");
|
||||
}
|
||||
|
||||
let dirs = get_dirs();
|
||||
|
||||
Ui {
|
||||
controllers: [
|
||||
input::Controllers {
|
||||
game_controller: std::ptr::null_mut(),
|
||||
joystick: std::ptr::null_mut(),
|
||||
rumble: false,
|
||||
},
|
||||
input::Controllers {
|
||||
game_controller: std::ptr::null_mut(),
|
||||
joystick: std::ptr::null_mut(),
|
||||
rumble: false,
|
||||
},
|
||||
input::Controllers {
|
||||
game_controller: std::ptr::null_mut(),
|
||||
joystick: std::ptr::null_mut(),
|
||||
rumble: false,
|
||||
},
|
||||
input::Controllers {
|
||||
game_controller: std::ptr::null_mut(),
|
||||
joystick: std::ptr::null_mut(),
|
||||
rumble: false,
|
||||
},
|
||||
],
|
||||
keyboard_state: std::ptr::null_mut(),
|
||||
config: config::Config::new(),
|
||||
save_type: vec![],
|
||||
game_id: String::new(),
|
||||
game_hash: String::new(),
|
||||
paths: storage::Paths {
|
||||
eep_file_path: std::path::PathBuf::new(),
|
||||
fla_file_path: std::path::PathBuf::new(),
|
||||
sra_file_path: std::path::PathBuf::new(),
|
||||
pak_file_path: std::path::PathBuf::new(),
|
||||
romsave_file_path: std::path::PathBuf::new(),
|
||||
},
|
||||
saves: storage::Saves {
|
||||
eeprom: (Vec::new(), false),
|
||||
sram: (Vec::new(), false),
|
||||
flash: (Vec::new(), false),
|
||||
mempak: (Vec::new(), false),
|
||||
romsave: (std::collections::HashMap::new(), false),
|
||||
},
|
||||
pak_audio: audio::PakAudio {
|
||||
mempak: include_bytes!("../data/mempak.wav").to_vec(),
|
||||
rumblepak: include_bytes!("../data/rumblepak.wav").to_vec(),
|
||||
},
|
||||
window: std::ptr::null_mut(),
|
||||
audio_stream: std::ptr::null_mut(),
|
||||
pak_audio_stream: std::ptr::null_mut(),
|
||||
audio_freq: 0.0,
|
||||
audio_device: 0,
|
||||
num_joysticks,
|
||||
joysticks,
|
||||
dirs,
|
||||
let mut joystick_vec = vec![];
|
||||
for i in 0..num_joysticks {
|
||||
joystick_vec.push(unsafe { *joysticks.add(i as usize) });
|
||||
}
|
||||
unsafe { sdl3_sys::stdinc::SDL_free(joysticks as *mut std::ffi::c_void) }
|
||||
Self::construct_ui(joystick_vec, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,99 +1,101 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
|
||||
pub struct PakAudio {
|
||||
pub mempak: Vec<u8>,
|
||||
pub rumblepak: Vec<u8>,
|
||||
}
|
||||
|
||||
pub fn init(ui: &mut ui::Ui, frequency: u64) {
|
||||
ui::sdl_init(sdl3_sys::init::SDL_INIT_AUDIO);
|
||||
init_game_audio(ui, frequency);
|
||||
}
|
||||
|
||||
let audio_spec = sdl3_sys::audio::SDL_AudioSpec {
|
||||
pub fn init_game_audio(ui: &mut ui::Ui, frequency: u64) {
|
||||
let game_audio_spec = sdl3_sys::audio::SDL_AudioSpec {
|
||||
format: sdl3_sys::audio::SDL_AUDIO_S16LE,
|
||||
freq: frequency as i32,
|
||||
channels: 2,
|
||||
};
|
||||
ui.audio_device = unsafe {
|
||||
sdl3_sys::audio::SDL_OpenAudioDevice(
|
||||
|
||||
ui.audio.audio_stream = unsafe {
|
||||
sdl3_sys::audio::SDL_OpenAudioDeviceStream(
|
||||
sdl3_sys::audio::SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
|
||||
&audio_spec,
|
||||
&game_audio_spec,
|
||||
None,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
if ui.audio_device == 0 {
|
||||
panic!("Could not open audio device");
|
||||
if ui.audio.audio_stream.is_null() {
|
||||
panic!("Could not create audio stream");
|
||||
}
|
||||
|
||||
let mut dst = Default::default();
|
||||
if !unsafe {
|
||||
sdl3_sys::audio::SDL_GetAudioDeviceFormat(ui.audio_device, &mut dst, std::ptr::null_mut())
|
||||
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.audio_stream, ui.audio.gain)
|
||||
&& sdl3_sys::audio::SDL_ResumeAudioStreamDevice(ui.audio.audio_stream)
|
||||
} {
|
||||
panic!("Could not get audio device format");
|
||||
panic!("Could not resume audio stream");
|
||||
}
|
||||
|
||||
ui.audio_stream = unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&audio_spec, &dst) };
|
||||
if ui.audio_stream.is_null() {
|
||||
return;
|
||||
}
|
||||
if !unsafe { sdl3_sys::audio::SDL_BindAudioStream(ui.audio_device, ui.audio_stream) } {
|
||||
panic!("Could not bind audio stream");
|
||||
}
|
||||
|
||||
let wav_audio_spec = sdl3_sys::audio::SDL_AudioSpec {
|
||||
format: sdl3_sys::audio::SDL_AUDIO_S16LE,
|
||||
freq: 24000,
|
||||
channels: 1,
|
||||
};
|
||||
|
||||
ui.pak_audio_stream = unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&wav_audio_spec, &dst) };
|
||||
if !unsafe { sdl3_sys::audio::SDL_BindAudioStream(ui.audio_device, ui.pak_audio_stream) } {
|
||||
panic!("Could not bind audio stream");
|
||||
}
|
||||
|
||||
ui.audio_freq = audio_spec.freq as f64;
|
||||
}
|
||||
|
||||
pub fn close(ui: &mut ui::Ui) {
|
||||
close_game_audio(ui);
|
||||
}
|
||||
|
||||
pub fn close_game_audio(ui: &mut ui::Ui) {
|
||||
unsafe {
|
||||
if !ui.audio_stream.is_null() {
|
||||
sdl3_sys::audio::SDL_DestroyAudioStream(ui.audio_stream);
|
||||
ui.audio_stream = std::ptr::null_mut();
|
||||
if !ui.audio.audio_stream.is_null() {
|
||||
sdl3_sys::audio::SDL_DestroyAudioStream(ui.audio.audio_stream);
|
||||
ui.audio.audio_stream = std::ptr::null_mut();
|
||||
}
|
||||
if !ui.pak_audio_stream.is_null() {
|
||||
sdl3_sys::audio::SDL_DestroyAudioStream(ui.pak_audio_stream);
|
||||
ui.pak_audio_stream = std::ptr::null_mut();
|
||||
}
|
||||
sdl3_sys::audio::SDL_CloseAudioDevice(ui.audio_device);
|
||||
ui.audio_device = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_pak_switch(ui: &mut ui::Ui, pak: device::controller::PakType) {
|
||||
if ui.pak_audio_stream.is_null() {
|
||||
pub fn lower_audio_volume(ui: &mut ui::Ui) {
|
||||
unsafe {
|
||||
ui.audio.gain = sdl3_sys::audio::SDL_GetAudioStreamGain(ui.audio.audio_stream) - 0.05;
|
||||
if ui.audio.gain < 0.0 {
|
||||
ui.audio.gain = 0.0;
|
||||
}
|
||||
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.audio_stream, ui.audio.gain);
|
||||
}
|
||||
ui::video::onscreen_message(
|
||||
&format!("Audio volume: {:.0}%", ui.audio.gain * 100.0),
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_VERY_SHORT,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn raise_audio_volume(ui: &mut ui::Ui) {
|
||||
unsafe {
|
||||
ui.audio.gain = sdl3_sys::audio::SDL_GetAudioStreamGain(ui.audio.audio_stream) + 0.05;
|
||||
if ui.audio.gain > 2.0 {
|
||||
ui.audio.gain = 2.0;
|
||||
}
|
||||
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.audio_stream, ui.audio.gain);
|
||||
}
|
||||
ui::video::onscreen_message(
|
||||
&format!("Audio volume: {:.0}%", ui.audio.gain * 100.0),
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_VERY_SHORT,
|
||||
);
|
||||
}
|
||||
|
||||
fn adjust_audio_frequency(device: &device::Device, frequency: f32) {
|
||||
if !device.vi.enable_speed_limiter {
|
||||
return;
|
||||
}
|
||||
|
||||
let sound;
|
||||
if pak == device::controller::PakType::RumblePak {
|
||||
sound = &ui.pak_audio.rumblepak;
|
||||
} else if pak == device::controller::PakType::MemPak {
|
||||
sound = &ui.pak_audio.mempak;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if !unsafe {
|
||||
sdl3_sys::audio::SDL_PutAudioStreamData(
|
||||
ui.pak_audio_stream,
|
||||
sound.as_ptr() as *const std::ffi::c_void,
|
||||
sound.len() as i32,
|
||||
)
|
||||
} {
|
||||
panic!("Could not play audio");
|
||||
unsafe {
|
||||
let current_ratio =
|
||||
sdl3_sys::everything::SDL_GetAudioStreamFrequencyRatio(device.ui.audio.audio_stream);
|
||||
sdl3_sys::everything::SDL_SetAudioStreamFrequencyRatio(
|
||||
device.ui.audio.audio_stream,
|
||||
(current_ratio + frequency).clamp(0.995, 1.005),
|
||||
);
|
||||
/*
|
||||
println!(
|
||||
"Adjusted audio frequency ratio to {}",
|
||||
sdl3_sys::everything::SDL_GetAudioStreamFrequencyRatio(device.ui.audio.audio_stream)
|
||||
);
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) {
|
||||
if device.ui.audio_stream.is_null() {
|
||||
pub fn play_audio(device: &device::Device, dram_addr: usize, length: u64) {
|
||||
if device.ui.audio.audio_stream.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -101,42 +103,57 @@ pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) {
|
||||
let mut i = 0;
|
||||
while i < length as usize / 2 {
|
||||
// Left channel
|
||||
primary_buffer[i] = device.rdram.mem[dram_addr + (i * 2) + 2] as i16
|
||||
| ((device.rdram.mem[dram_addr + (i * 2) + 3] as i16) << 8);
|
||||
primary_buffer[i] = *device.rdram.mem.get(dram_addr + (i * 2) + 2).unwrap_or(&0) as i16
|
||||
| ((*device.rdram.mem.get(dram_addr + (i * 2) + 3).unwrap_or(&0) as i16) << 8);
|
||||
|
||||
// Right channel
|
||||
primary_buffer[i + 1] = device.rdram.mem[dram_addr + (i * 2)] as i16
|
||||
| ((device.rdram.mem[dram_addr + (i * 2) + 1] as i16) << 8);
|
||||
primary_buffer[i + 1] = *device.rdram.mem.get(dram_addr + (i * 2)).unwrap_or(&0) as i16
|
||||
| ((*device.rdram.mem.get(dram_addr + (i * 2) + 1).unwrap_or(&0) as i16) << 8);
|
||||
i += 2;
|
||||
}
|
||||
|
||||
let audio_queued =
|
||||
unsafe { sdl3_sys::audio::SDL_GetAudioStreamQueued(device.ui.audio_stream) } as f64;
|
||||
let acceptable_latency = (device.ui.audio_freq * 0.2) * 4.0;
|
||||
let min_latency = (device.ui.audio_freq * 0.02) * 4.0;
|
||||
unsafe { sdl3_sys::audio::SDL_GetAudioStreamQueued(device.ui.audio.audio_stream) } as f64;
|
||||
let samples_per_frame = device.ai.freq as f64 * device.vi.frame_time * 4.0;
|
||||
let max_latency = samples_per_frame * 8.0;
|
||||
|
||||
if audio_queued < min_latency {
|
||||
let silence_buffer: Vec<u8> = vec![0; (min_latency - audio_queued) as usize & !3];
|
||||
if audio_queued < samples_per_frame {
|
||||
let silence_buffer: Vec<u8> = vec![0; samples_per_frame as usize & !3];
|
||||
if !unsafe {
|
||||
sdl3_sys::audio::SDL_PutAudioStreamData(
|
||||
device.ui.audio_stream,
|
||||
device.ui.audio.audio_stream,
|
||||
silence_buffer.as_ptr() as *const std::ffi::c_void,
|
||||
silence_buffer.len() as i32,
|
||||
)
|
||||
} {
|
||||
panic!("Could not play audio");
|
||||
}
|
||||
/*
|
||||
println!(
|
||||
"Audio underrun: queued {} samples, expected at least {} samples",
|
||||
audio_queued, samples_per_frame
|
||||
);
|
||||
*/
|
||||
adjust_audio_frequency(device, -0.0005);
|
||||
}
|
||||
|
||||
if audio_queued < acceptable_latency
|
||||
&& !unsafe {
|
||||
if audio_queued < max_latency {
|
||||
if !unsafe {
|
||||
sdl3_sys::audio::SDL_PutAudioStreamData(
|
||||
device.ui.audio_stream,
|
||||
device.ui.audio.audio_stream,
|
||||
primary_buffer.as_ptr() as *const std::ffi::c_void,
|
||||
primary_buffer.len() as i32 * 2,
|
||||
)
|
||||
} {
|
||||
panic!("Could not play audio");
|
||||
}
|
||||
{
|
||||
panic!("Could not play audio");
|
||||
} else {
|
||||
/*
|
||||
println!(
|
||||
"Audio overrun: queued {} samples, expected at most {} samples",
|
||||
audio_queued, max_latency
|
||||
);
|
||||
*/
|
||||
adjust_audio_frequency(device, 0.0005);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
use crate::cheats;
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
use crate::ui::gui::AppWindow;
|
||||
use crate::ui::gui::ErrorDialog;
|
||||
use slint::ComponentHandle;
|
||||
|
||||
pub fn cheats_window(app: &AppWindow) {
|
||||
let weak = app.as_weak();
|
||||
app.on_cheats_select_rom_clicked(move |rom_dir| {
|
||||
let select_rom = if !rom_dir.is_empty()
|
||||
&& let Ok(exists) = std::fs::exists(&rom_dir)
|
||||
&& exists
|
||||
{
|
||||
rfd::AsyncFileDialog::new().set_directory(rom_dir)
|
||||
} else {
|
||||
rfd::AsyncFileDialog::new()
|
||||
}
|
||||
.set_title("Select ROM")
|
||||
.add_filter("ROM files", &ui::gui::N64_EXTENSIONS)
|
||||
.pick_file();
|
||||
let weak = weak.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Some(file) = select_rom.await
|
||||
&& let Some(rom_contents) = device::get_rom_contents(file.path())
|
||||
{
|
||||
let game_crc = ui::storage::get_game_crc(&rom_contents);
|
||||
let game_name = ui::storage::get_game_name(&rom_contents);
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
handle.set_cheat_game_name(game_name.into());
|
||||
})
|
||||
.unwrap();
|
||||
let cheats: cheats::Cheat =
|
||||
serde_json::from_slice(include_bytes!("../../data/cheats.json")).unwrap();
|
||||
if let Some(cheat) = cheats.get(&game_crc) {
|
||||
let cheat = cheat.clone();
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
let cheat_settings = ui::config::Cheats::new();
|
||||
let game_cheats = cheat_settings.cheats.get(&game_crc).cloned();
|
||||
handle.set_cheat_game_crc(game_crc.into());
|
||||
let cheats_vec = slint::VecModel::default();
|
||||
for item in cheat.iter() {
|
||||
let mut cheat_enabled = false;
|
||||
if let Some(game_cheat) = game_cheats.as_ref()
|
||||
&& game_cheat.contains_key(item.0)
|
||||
{
|
||||
cheat_enabled = true;
|
||||
}
|
||||
let options_vec = slint::VecModel::default();
|
||||
if let Some(options) = item.1.options.as_ref() {
|
||||
for option in options.iter() {
|
||||
let mut option_enabled = false;
|
||||
if let Some(game_cheat) = game_cheats.as_ref()
|
||||
&& game_cheat.contains_key(item.0)
|
||||
&& let Some(opt) = game_cheat.get(item.0)
|
||||
&& let Some(opt) = opt
|
||||
&& opt == option.0
|
||||
{
|
||||
option_enabled = true;
|
||||
}
|
||||
options_vec.push((option_enabled, option.0.into()));
|
||||
}
|
||||
}
|
||||
let options = slint::ModelRc::from(std::rc::Rc::new(options_vec));
|
||||
cheats_vec.push((
|
||||
item.0.clone().into(),
|
||||
cheat_enabled,
|
||||
item.1.clone().note.into(),
|
||||
options,
|
||||
));
|
||||
}
|
||||
handle.set_cheats(slint::ModelRc::from(std::rc::Rc::new(cheats_vec)));
|
||||
})
|
||||
.unwrap();
|
||||
} else {
|
||||
clear_cheats(&weak, false);
|
||||
}
|
||||
} else {
|
||||
clear_cheats(&weak, true);
|
||||
weak.upgrade_in_event_loop(move |_handle| {
|
||||
let message_dialog = ErrorDialog::new().unwrap();
|
||||
let weak_dialog = message_dialog.as_weak();
|
||||
message_dialog.on_close_clicked(move || {
|
||||
weak_dialog.unwrap().window().hide().unwrap();
|
||||
});
|
||||
message_dialog.set_text("Could not read ROM".into());
|
||||
message_dialog.show().unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let weak = app.as_weak();
|
||||
app.on_cheats_clear_clicked(move || {
|
||||
let mut cheats = ui::config::Cheats::new();
|
||||
cheats.cheats.clear();
|
||||
clear_cheats(&weak, true);
|
||||
});
|
||||
|
||||
app.on_cheat_toggled(move |game_crc, cheat_name, option, enabled| {
|
||||
let mut cheats = ui::config::Cheats::new();
|
||||
let mut game_cheats = cheats
|
||||
.cheats
|
||||
.remove(&game_crc.to_string())
|
||||
.unwrap_or_default();
|
||||
if enabled {
|
||||
let cheat_option = if option.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(option.into())
|
||||
};
|
||||
game_cheats.insert(cheat_name.into(), cheat_option);
|
||||
} else {
|
||||
game_cheats.remove(&cheat_name.to_string());
|
||||
}
|
||||
cheats.cheats.insert(game_crc.into(), game_cheats);
|
||||
});
|
||||
}
|
||||
|
||||
fn clear_cheats(weak: &slint::Weak<AppWindow>, clear_name: bool) {
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
handle.set_cheats(slint::ModelRc::from(std::rc::Rc::new(
|
||||
slint::VecModel::default(),
|
||||
)));
|
||||
if clear_name {
|
||||
handle.set_cheat_game_name(String::new().into());
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
@@ -1,34 +1,109 @@
|
||||
use crate::ui;
|
||||
|
||||
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct InputKeyButton {
|
||||
pub enabled: bool,
|
||||
pub id: i32,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct InputControllerAxis {
|
||||
pub enabled: bool,
|
||||
pub id: i32,
|
||||
pub axis: i16,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct InputJoystickHat {
|
||||
pub enabled: bool,
|
||||
pub id: i32,
|
||||
pub direction: u8,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct InputProfile {
|
||||
pub keys: [(bool, i32); ui::input::PROFILE_SIZE],
|
||||
pub controller_buttons: [(bool, i32); ui::input::PROFILE_SIZE],
|
||||
pub controller_axis: [(bool, i32, i16); ui::input::PROFILE_SIZE],
|
||||
pub joystick_buttons: [(bool, i32); ui::input::PROFILE_SIZE],
|
||||
pub joystick_hat: [(bool, i32, u8); ui::input::PROFILE_SIZE],
|
||||
pub joystick_axis: [(bool, i32, i16); ui::input::PROFILE_SIZE],
|
||||
pub keys: [InputKeyButton; ui::input::PROFILE_SIZE],
|
||||
pub controller_buttons: [InputKeyButton; ui::input::PROFILE_SIZE],
|
||||
pub controller_axis: [InputControllerAxis; ui::input::PROFILE_SIZE],
|
||||
pub joystick_buttons: [InputKeyButton; ui::input::PROFILE_SIZE],
|
||||
pub joystick_hat: [InputJoystickHat; ui::input::PROFILE_SIZE],
|
||||
pub joystick_axis: [InputControllerAxis; ui::input::PROFILE_SIZE],
|
||||
pub dinput: bool,
|
||||
pub deadzone: i32,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Input {
|
||||
pub input_profiles: std::collections::HashMap<String, InputProfile>,
|
||||
pub input_profiles: std::collections::BTreeMap<String, InputProfile>,
|
||||
pub input_profile_binding: [String; 4],
|
||||
pub controller_assignment: [Option<String>; 4],
|
||||
pub controller_enabled: [bool; 4],
|
||||
pub transfer_pak: [bool; 4],
|
||||
pub gb_rom_path: [String; 4],
|
||||
pub gb_ram_path: [String; 4],
|
||||
pub emulate_vru: bool,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Video {
|
||||
pub upscale: bool,
|
||||
pub upscale: u32,
|
||||
pub integer_scaling: bool,
|
||||
pub fullscreen: bool,
|
||||
pub widescreen: bool,
|
||||
pub vsync: bool,
|
||||
pub crt: bool,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Emulation {
|
||||
pub overclock: bool,
|
||||
pub disable_expansion_pak: bool,
|
||||
pub usb: bool,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Cheats {
|
||||
pub cheats:
|
||||
std::collections::HashMap<String, std::collections::HashMap<String, Option<String>>>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Config {
|
||||
pub input: Input,
|
||||
pub video: Video,
|
||||
pub emulation: Emulation,
|
||||
pub rom_dir: std::path::PathBuf,
|
||||
pub recent_roms: Vec<String>,
|
||||
}
|
||||
|
||||
impl Drop for Cheats {
|
||||
fn drop(&mut self) {
|
||||
write_cheats(self);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_cheats(cheats: &Cheats) {
|
||||
let dirs = ui::get_dirs();
|
||||
let file_path = dirs.config_dir.join("cheats.json");
|
||||
let f = std::fs::File::create(file_path).unwrap();
|
||||
serde_json::to_writer_pretty(f, &cheats).unwrap();
|
||||
}
|
||||
|
||||
impl Cheats {
|
||||
pub fn new() -> Cheats {
|
||||
let dirs = ui::get_dirs();
|
||||
let file_path = dirs.config_dir.join("cheats.json");
|
||||
let cheats_file = std::fs::read(file_path);
|
||||
if let Ok(cheats_file) = cheats_file {
|
||||
let result = serde_json::from_slice(cheats_file.as_ref());
|
||||
if let Ok(result) = result {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Cheats {
|
||||
cheats: std::collections::HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Config {
|
||||
@@ -49,13 +124,13 @@ impl Config {
|
||||
let dirs = ui::get_dirs();
|
||||
let file_path = dirs.config_dir.join("config.json");
|
||||
let config_file = std::fs::read(file_path);
|
||||
if config_file.is_ok() {
|
||||
let result = serde_json::from_slice(config_file.unwrap().as_ref());
|
||||
if let Ok(config_file) = config_file {
|
||||
let result = serde_json::from_slice(config_file.as_ref());
|
||||
if let Ok(result) = result {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
let mut input_profiles = std::collections::HashMap::new();
|
||||
let mut input_profiles = std::collections::BTreeMap::new();
|
||||
input_profiles.insert("default".to_string(), ui::input::get_default_profile());
|
||||
Config {
|
||||
input: Input {
|
||||
@@ -68,13 +143,26 @@ impl Config {
|
||||
controller_assignment: [None, None, None, None],
|
||||
input_profiles,
|
||||
controller_enabled: [true, false, false, false],
|
||||
transfer_pak: [false, false, false, false],
|
||||
gb_rom_path: [String::new(), String::new(), String::new(), String::new()],
|
||||
gb_ram_path: [String::new(), String::new(), String::new(), String::new()],
|
||||
emulate_vru: false,
|
||||
},
|
||||
video: Video {
|
||||
upscale: false,
|
||||
upscale: 1,
|
||||
integer_scaling: false,
|
||||
fullscreen: false,
|
||||
widescreen: false,
|
||||
vsync: true,
|
||||
crt: false,
|
||||
},
|
||||
emulation: Emulation {
|
||||
overclock: false,
|
||||
disable_expansion_pak: false,
|
||||
usb: false,
|
||||
},
|
||||
rom_dir: std::path::PathBuf::new(),
|
||||
recent_roms: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Button, HorizontalBox, VerticalBox } from "std-widgets.slint";
|
||||
import { Page } from "page.slint";
|
||||
|
||||
export global AboutData {
|
||||
in property <string> version;
|
||||
in property <bool> has_update;
|
||||
}
|
||||
|
||||
export component NewVersionButton {
|
||||
callback newversion_button_clicked;
|
||||
HorizontalBox {
|
||||
alignment: center;
|
||||
Button {
|
||||
text: @tr("New version available! Click here to download");
|
||||
primary: true;
|
||||
clicked => {
|
||||
newversion_button_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component About inherits Page {
|
||||
callback wiki_button_clicked;
|
||||
callback discord_button_clicked;
|
||||
callback newversion_button_clicked;
|
||||
callback patreon_button_clicked;
|
||||
callback github_sponsors_button_clicked;
|
||||
callback source_code_button_clicked;
|
||||
title: @tr("About");
|
||||
|
||||
VerticalBox {
|
||||
alignment: start;
|
||||
HorizontalBox {
|
||||
alignment: center;
|
||||
Button {
|
||||
text: @tr("Wiki");
|
||||
clicked => {
|
||||
wiki_button_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalBox {
|
||||
alignment: center;
|
||||
Button {
|
||||
text: @tr("Discord Server");
|
||||
clicked => {
|
||||
discord_button_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalBox {
|
||||
alignment: center;
|
||||
Button {
|
||||
text: @tr("Source Code");
|
||||
clicked => {
|
||||
source_code_button_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalBox {
|
||||
alignment: center;
|
||||
Button {
|
||||
text: @tr("Donate (via Patreon)");
|
||||
clicked => {
|
||||
patreon_button_clicked();
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: @tr("Donate (via GitHub)");
|
||||
clicked => {
|
||||
github_sponsors_button_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalBox {
|
||||
alignment: center;
|
||||
Text {
|
||||
text: AboutData.version;
|
||||
}
|
||||
}
|
||||
|
||||
if AboutData.has_update:NewVersionButton {
|
||||
newversion_button_clicked => {
|
||||
newversion_button_clicked();
|
||||
}
|
||||
}
|
||||
Rectangle { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { StandardButton } from "std-widgets.slint";
|
||||
import { LocalGame, ControllerConfig, Netplay, Cheats, RetroAchievements, Settings, About } from "pages.slint";
|
||||
import { SideBar } from "side_bar.slint";
|
||||
import { SettingsData } from "settings_page.slint";
|
||||
import { RAData } from "retroachievements_page.slint";
|
||||
import { AboutData } from "about_page.slint";
|
||||
import { ControllerData } from "controller_page.slint";
|
||||
import { State } from "localgame_page.slint";
|
||||
import { CheatsData } from "cheat_page.slint";
|
||||
export { InputProfileDialog } from "input_profile.slint";
|
||||
export { VruDialog } from "vru_dialog.slint";
|
||||
export { NetplayCreate } from "netplay_create.slint";
|
||||
export { NetplayJoin } from "netplay_join.slint";
|
||||
export { NetplayWait } from "netplay_wait.slint";
|
||||
export { CustomNetplayServer } from "netplay_page.slint";
|
||||
export { DispatcherDialog } from "netplay_create.slint";
|
||||
|
||||
export component AppWindow inherits Window {
|
||||
callback wiki_button_clicked;
|
||||
callback discord_button_clicked;
|
||||
callback newversion_button_clicked;
|
||||
callback recent_rom_button_clicked(string);
|
||||
callback patreon_button_clicked;
|
||||
callback github_sponsors_button_clicked;
|
||||
callback source_code_button_clicked;
|
||||
callback open_rom_button_clicked;
|
||||
callback saves_folder_button_clicked;
|
||||
callback input_profile_button_clicked;
|
||||
callback transferpak_toggled(int,bool);
|
||||
callback create_session_button_clicked;
|
||||
callback join_session_button_clicked;
|
||||
callback netplay_discord_button_clicked;
|
||||
callback netplay_feedback_button_clicked;
|
||||
callback cheats_select_rom_clicked(string);
|
||||
callback cheats_clear_clicked;
|
||||
callback cheat_toggled(string, string, string, bool);
|
||||
callback ra_button_clicked(string);
|
||||
callback ra_games_clicked;
|
||||
callback ra_show_profile_clicked;
|
||||
callback ra_toggled(bool,bool,bool,bool);
|
||||
in property version <=> AboutData.version;
|
||||
in property has_update <=> AboutData.has_update;
|
||||
in-out property ra_username <=> RAData.ra_username;
|
||||
in-out property ra_password <=> RAData.ra_password;
|
||||
in-out property ra_logging_in <=> RAData.ra_logging_in;
|
||||
in-out property ra_show_profile <=> RAData.ra_show_profile;
|
||||
in-out property ra_current_user_message <=> RAData.current_user_message;
|
||||
in-out property ra_enabled <=> RAData.enabled;
|
||||
in-out property ra_hardcore <=> RAData.hardcore;
|
||||
in-out property ra_challenge <=> RAData.challenge;
|
||||
in-out property ra_leaderboard <=> RAData.leaderboard;
|
||||
in-out property ra_softcore_only <=> RAData.ra_softcore_only;
|
||||
in-out property integer_scaling <=> SettingsData.integer_scaling;
|
||||
in-out property fullscreen <=> SettingsData.fullscreen;
|
||||
in-out property widescreen <=> SettingsData.widescreen;
|
||||
in-out property vsync <=> SettingsData.vsync;
|
||||
in-out property apply_crt_shader <=> SettingsData.apply_crt_shader;
|
||||
in-out property overclock_n64_cpu <=> SettingsData.overclock_n64_cpu;
|
||||
in-out property disable_expansion_pak <=> SettingsData.disable_expansion_pak;
|
||||
in-out property resolution <=> SettingsData.resolution;
|
||||
in-out property emulate_usb <=> SettingsData.emulate_usb;
|
||||
in-out property rom_dir <=> SettingsData.rom_dir;
|
||||
in-out property emulate_vru <=> ControllerData.emulate_vru;
|
||||
in-out property controller_enabled <=> ControllerData.controller_enabled;
|
||||
in-out property controller_changed <=> ControllerData.controller_changed;
|
||||
in-out property transferpak <=> ControllerData.transferpak;
|
||||
in-out property gb_rom_paths <=> ControllerData.gb_rom_paths;
|
||||
in-out property gb_ram_paths <=> ControllerData.gb_ram_paths;
|
||||
in-out property selected_profile_binding <=> ControllerData.selected_profile_binding;
|
||||
in-out property input_profiles <=> ControllerData.input_profiles;
|
||||
in-out property controller_names <=> ControllerData.controller_names;
|
||||
in-out property selected_controller <=> ControllerData.selected_controller;
|
||||
in-out property game_running <=> State.game_running;
|
||||
in-out property recent_roms <=> State.recent_roms;
|
||||
in-out property blank_profiles <=> ControllerData.blank_profiles;
|
||||
in-out property cheat_game_name <=> CheatsData.cheat_game_name;
|
||||
in-out property cheat_game_crc <=> CheatsData.cheat_game_crc;
|
||||
in-out property cheats <=> CheatsData.cheats;
|
||||
preferred-width: 700px;
|
||||
preferred-height: 500px;
|
||||
title: @tr("Gopher64");
|
||||
icon: @image-url("../../../data/icon/gopher64.png");
|
||||
|
||||
HorizontalLayout {
|
||||
side-bar := SideBar {
|
||||
title: @tr("Gopher64");
|
||||
model: [
|
||||
@tr("Menu" => "Local Game"),
|
||||
@tr("Menu" => "Controller Config"),
|
||||
@tr("Menu" => "Netplay"),
|
||||
@tr("Menu" => "Cheats"),
|
||||
@tr("Menu" => "RetroAchievements"),
|
||||
@tr("Menu" => "Settings"),
|
||||
@tr("Menu" => "About")
|
||||
];
|
||||
}
|
||||
|
||||
if(side-bar.current-item == 0): LocalGame {
|
||||
open_rom_button_clicked => {
|
||||
open_rom_button_clicked();
|
||||
}
|
||||
saves_folder_button_clicked => {
|
||||
saves_folder_button_clicked();
|
||||
}
|
||||
newversion_button_clicked => {
|
||||
newversion_button_clicked();
|
||||
}
|
||||
recent_rom_button_clicked(rom) => {
|
||||
recent_rom_button_clicked(rom);
|
||||
}
|
||||
}
|
||||
if(side-bar.current-item == 1): ControllerConfig {
|
||||
input_profile_button_clicked => {
|
||||
input_profile_button_clicked();
|
||||
}
|
||||
transferpak_toggled(player,enabled) => {
|
||||
transferpak_toggled(player,enabled);
|
||||
}
|
||||
}
|
||||
if(side-bar.current-item == 2): Netplay {
|
||||
create_session_button_clicked => {
|
||||
create_session_button_clicked();
|
||||
}
|
||||
join_session_button_clicked => {
|
||||
join_session_button_clicked();
|
||||
}
|
||||
netplay_discord_button_clicked => {
|
||||
netplay_discord_button_clicked();
|
||||
}
|
||||
netplay_feedback_button_clicked => {
|
||||
netplay_feedback_button_clicked();
|
||||
}
|
||||
}
|
||||
if(side-bar.current-item == 3): Cheats {
|
||||
cheats_select_rom_clicked(rom_dir) => {
|
||||
cheats_select_rom_clicked(rom_dir);
|
||||
}
|
||||
cheats_clear_clicked => {
|
||||
cheats_clear_clicked();
|
||||
}
|
||||
cheat_toggled(game_crc, cheat_name, option, enabled) => {
|
||||
cheat_toggled(game_crc,cheat_name,option,enabled);
|
||||
}
|
||||
}
|
||||
if(side-bar.current-item == 4): RetroAchievements {
|
||||
ra_button_clicked(password) => {
|
||||
ra_button_clicked(password);
|
||||
}
|
||||
ra_toggled(enabled,hardcore,challenge,leaderboard) => {
|
||||
ra_toggled(enabled,hardcore,challenge,leaderboard);
|
||||
}
|
||||
ra_games_clicked => {
|
||||
ra_games_clicked();
|
||||
}
|
||||
ra_show_profile_clicked => {
|
||||
ra_show_profile_clicked();
|
||||
}
|
||||
}
|
||||
if(side-bar.current-item == 5): Settings { }
|
||||
if(side-bar.current-item == 6): About {
|
||||
wiki_button_clicked => {
|
||||
wiki_button_clicked();
|
||||
}
|
||||
discord_button_clicked => {
|
||||
discord_button_clicked();
|
||||
}
|
||||
newversion_button_clicked => {
|
||||
newversion_button_clicked();
|
||||
}
|
||||
patreon_button_clicked => {
|
||||
patreon_button_clicked();
|
||||
}
|
||||
github_sponsors_button_clicked => {
|
||||
github_sponsors_button_clicked();
|
||||
}
|
||||
source_code_button_clicked => {
|
||||
source_code_button_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component ErrorDialog inherits Dialog {
|
||||
in-out property <string> text;
|
||||
title: @tr("Error");
|
||||
Text {
|
||||
text: text;
|
||||
}
|
||||
|
||||
StandardButton {
|
||||
kind: close;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { ListView, HorizontalBox, VerticalBox, Button, GroupBox, Switch, CheckBox } from "std-widgets.slint";
|
||||
import { Page } from "page.slint";
|
||||
import { SettingsData } from "settings_page.slint";
|
||||
|
||||
export global CheatsData {
|
||||
in-out property <string> cheat_game_name;
|
||||
in-out property <string> cheat_game_crc;
|
||||
in-out property <[{cheat_name:string, notes:string, enabled:bool, options:[{option:string, enabled:bool}]}]> cheats;
|
||||
}
|
||||
|
||||
export component Cheats inherits Page {
|
||||
title: @tr("Cheats");
|
||||
callback cheats_select_rom_clicked(string);
|
||||
callback cheats_clear_clicked;
|
||||
callback cheat_toggled(string, string, string, bool);
|
||||
|
||||
VerticalBox {
|
||||
alignment: start;
|
||||
HorizontalBox {
|
||||
alignment: start;
|
||||
if CheatsData.cheat_game_name == "": Button {
|
||||
text: @tr("Select ROM");
|
||||
clicked => {
|
||||
cheats_select_rom_clicked(SettingsData.rom_dir);
|
||||
}
|
||||
}
|
||||
if CheatsData.cheat_game_name != "": Button {
|
||||
text: CheatsData.cheat_game_name;
|
||||
clicked => {
|
||||
cheats_select_rom_clicked(SettingsData.rom_dir);
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
Button {
|
||||
text: @tr("Clear Cheats For All ROMs");
|
||||
clicked => {
|
||||
cheats_clear_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { }
|
||||
|
||||
if CheatsData.cheats.length > 0:ListView {
|
||||
preferred-height: 500px;
|
||||
preferred-width: 520px;
|
||||
for data in CheatsData.cheats: VerticalBox {
|
||||
Text {
|
||||
text: data.cheat_name;
|
||||
font-size: 16px;
|
||||
wrap: word-wrap;
|
||||
}
|
||||
|
||||
if data.notes != "":Text {
|
||||
text: data.notes;
|
||||
wrap: word-wrap;
|
||||
}
|
||||
|
||||
if data.options.length == 0: Switch {
|
||||
checked: data.enabled;
|
||||
toggled => {
|
||||
data.enabled = self.checked;
|
||||
cheat_toggled(CheatsData.cheat_game_crc,data.cheat_name,"",self.checked);
|
||||
}
|
||||
}
|
||||
for item in data.options: CheckBox {
|
||||
text: item.option;
|
||||
enabled: !data.enabled || (data.enabled && item.enabled);
|
||||
checked: item.enabled;
|
||||
toggled => {
|
||||
item.enabled = self.checked;
|
||||
data.enabled = self.checked;
|
||||
cheat_toggled(CheatsData.cheat_game_crc,data.cheat_name,item.option,self.checked);
|
||||
}
|
||||
}
|
||||
Rectangle { }
|
||||
}
|
||||
}
|
||||
if CheatsData.cheats.length == 0 && CheatsData.cheat_game_name != "": Text {
|
||||
text: @tr("No Cheats Available");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Switch, ComboBox, Button, HorizontalBox, VerticalBox, GroupBox } from "std-widgets.slint";
|
||||
import { Page } from "page.slint";
|
||||
import { State } from "localgame_page.slint";
|
||||
|
||||
export global ControllerData {
|
||||
in-out property <bool> emulate_vru;
|
||||
in-out property <[bool]> controller_enabled;
|
||||
in-out property <[bool]> transferpak;
|
||||
in-out property <[string]> gb_rom_paths;
|
||||
in-out property <[string]> gb_ram_paths;
|
||||
in-out property <[int]> selected_profile_binding: [-1, -1, -1, -1];
|
||||
in-out property <[string]> input_profiles;
|
||||
in-out property <[string]> controller_names;
|
||||
in-out property <[int]> selected_controller;
|
||||
in-out property <[bool]> controller_changed;
|
||||
in-out property <bool> blank_profiles;
|
||||
}
|
||||
|
||||
export component ControllerConfig inherits Page {
|
||||
callback input_profile_button_clicked;
|
||||
callback transferpak_toggled(int,bool);
|
||||
title: @tr("Controller Configuration");
|
||||
|
||||
VerticalBox {
|
||||
alignment: start;
|
||||
HorizontalBox {
|
||||
alignment: start;
|
||||
Button {
|
||||
text: @tr("Configure Input Profile");
|
||||
enabled: !State.game_running;
|
||||
clicked => {
|
||||
input_profile_button_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { }
|
||||
|
||||
HorizontalBox {
|
||||
GroupBox {
|
||||
title: @tr("Port");
|
||||
VerticalLayout {
|
||||
alignment: space-between;
|
||||
for player in [0, 1, 2, 3]: Text {
|
||||
text: player + 1;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GroupBox {
|
||||
title: @tr("Enabled");
|
||||
VerticalLayout {
|
||||
alignment: space-between;
|
||||
for player in [0, 1, 2, 3]: Switch {
|
||||
checked: ControllerData.controller_enabled[player];
|
||||
toggled => {
|
||||
ControllerData.controller_enabled[player] = self.checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GroupBox {
|
||||
title: @tr("Emulate VRU");
|
||||
VerticalLayout {
|
||||
alignment: space-between;
|
||||
for valid in [false, false, false, true]: Switch {
|
||||
enabled: valid;
|
||||
checked: valid && ControllerData.emulate_vru;
|
||||
toggled => {
|
||||
ControllerData.emulate_vru = self.checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GroupBox {
|
||||
title: @tr("Transfer Pak");
|
||||
VerticalLayout {
|
||||
alignment: space-between;
|
||||
for player in [0, 1, 2, 3]: Switch {
|
||||
checked: ControllerData.transferpak[player];
|
||||
toggled => {
|
||||
ControllerData.transferpak[player] = self.checked;
|
||||
transferpak_toggled(player,self.checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !ControllerData.blank_profiles: GroupBox {
|
||||
title: @tr("Profile");
|
||||
horizontal-stretch: 0.25;
|
||||
VerticalLayout {
|
||||
alignment: space-between;
|
||||
for player in [0, 1, 2, 3]: ComboBox {
|
||||
model: ControllerData.input_profiles;
|
||||
min-width: self.preferred-width > 150px ? self.preferred-width : 150px;
|
||||
current-index: ControllerData.selected_profile_binding[player];
|
||||
selected => {
|
||||
ControllerData.selected_profile_binding[player] = self.current-index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GroupBox {
|
||||
title: @tr("Controller");
|
||||
horizontal-stretch: 0.75;
|
||||
VerticalLayout {
|
||||
alignment: space-between;
|
||||
for player in [0, 1, 2, 3]: ComboBox {
|
||||
model: ControllerData.controller_names;
|
||||
min-width: self.preferred-width > 300px ? self.preferred-width : 300px;
|
||||
current-index: ControllerData.selected_controller[player];
|
||||
selected => {
|
||||
ControllerData.selected_controller[player] = self.current-index;
|
||||
ControllerData.controller_changed[player] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,879 +0,0 @@
|
||||
use crate::device;
|
||||
use crate::ui::gui;
|
||||
use crate::ui::gui::GopherEguiApp;
|
||||
use eframe::egui;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
const NETPLAY_VERSION: i32 = 17;
|
||||
const EMU_NAME: &str = "gopher64";
|
||||
|
||||
type GameInfo = (String, String, String, Vec<u8>);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GuiNetplay {
|
||||
pub create: bool,
|
||||
pub join: bool,
|
||||
pub wait: bool,
|
||||
pub session_name: String,
|
||||
pub password: String,
|
||||
pub player_name: String,
|
||||
pub error: String,
|
||||
pub create_rom_label: String,
|
||||
pub join_rom_label: String,
|
||||
pub send_chat: bool,
|
||||
pub have_sessions: Option<(String, String)>,
|
||||
pub begin_game: bool,
|
||||
pub chat_log: String,
|
||||
pub chat_message: String,
|
||||
pub selected_session: Option<NetplayRoom>,
|
||||
pub pending_begin: bool,
|
||||
pub peer_addr: Option<std::net::SocketAddr>,
|
||||
pub motd: String,
|
||||
pub sessions: Vec<NetplayRoom>,
|
||||
pub rom_contents: Vec<u8>,
|
||||
pub player_number: u8,
|
||||
pub player_names: [String; 4],
|
||||
pub server: (String, String),
|
||||
pub socket_waiting: bool,
|
||||
pub game_info: GameInfo,
|
||||
pub servers: std::collections::HashMap<String, String>,
|
||||
pub waiting_session: Option<NetplayRoom>,
|
||||
pub socket:
|
||||
Option<tungstenite::WebSocket<tungstenite::stream::MaybeTlsStream<std::net::TcpStream>>>,
|
||||
pub server_receiver:
|
||||
Option<std::sync::mpsc::Receiver<std::collections::HashMap<String, String>>>,
|
||||
pub game_info_receiver: Option<std::sync::mpsc::Receiver<GameInfo>>,
|
||||
pub broadcast_socket: Option<std::net::UdpSocket>,
|
||||
pub broadcast_timer: Option<std::time::Instant>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, PartialEq, Default, Clone)]
|
||||
pub struct NetplayRoom {
|
||||
room_name: Option<String>,
|
||||
password: Option<String>,
|
||||
protected: Option<bool>,
|
||||
#[serde(rename = "MD5")]
|
||||
md5: Option<String>,
|
||||
game_name: Option<String>,
|
||||
pub port: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct NetplayMessage {
|
||||
#[serde(rename = "type")]
|
||||
message_type: String,
|
||||
player_name: Option<String>,
|
||||
client_sha: Option<String>,
|
||||
netplay_version: Option<i32>,
|
||||
emulator: Option<String>,
|
||||
room: Option<NetplayRoom>,
|
||||
accept: Option<i32>,
|
||||
message: Option<String>,
|
||||
auth: Option<String>,
|
||||
player_names: Option<[String; 4]>,
|
||||
#[serde(rename = "authTime")]
|
||||
auth_time: Option<String>,
|
||||
rooms: Option<Vec<NetplayRoom>>,
|
||||
}
|
||||
|
||||
fn get_servers(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
if app.netplay.servers.is_empty() {
|
||||
if app.netplay.broadcast_socket.is_none() {
|
||||
app.netplay.broadcast_socket = Some(
|
||||
std::net::UdpSocket::bind((std::net::Ipv4Addr::UNSPECIFIED, 0))
|
||||
.expect("couldn't bind to address"),
|
||||
);
|
||||
let socket = app.netplay.broadcast_socket.as_ref().unwrap();
|
||||
socket
|
||||
.set_broadcast(true)
|
||||
.expect("set_broadcast call failed");
|
||||
socket
|
||||
.set_nonblocking(true)
|
||||
.expect("could not set up socket");
|
||||
let data: [u8; 1] = [1];
|
||||
socket
|
||||
.send_to(&data, (std::net::Ipv4Addr::BROADCAST, 45000))
|
||||
.expect("couldn't send data");
|
||||
app.netplay.broadcast_timer =
|
||||
Some(std::time::Instant::now() + std::time::Duration::from_secs(5));
|
||||
ctx.request_repaint();
|
||||
}
|
||||
if app.netplay.server_receiver.is_none() {
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
app.netplay.server_receiver = Some(rx);
|
||||
let gui_ctx = ctx.clone();
|
||||
std::thread::spawn(move || {
|
||||
if let Ok(response) =
|
||||
reqwest::blocking::get("https://m64p.s3.amazonaws.com/servers.json")
|
||||
.unwrap()
|
||||
.json::<std::collections::HashMap<String, String>>()
|
||||
{
|
||||
tx.send(response).unwrap();
|
||||
gui_ctx.request_repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if app.netplay.broadcast_timer.is_some()
|
||||
&& std::time::Instant::now() > app.netplay.broadcast_timer.unwrap()
|
||||
{
|
||||
app.netplay.broadcast_timer = None;
|
||||
}
|
||||
if app.netplay.broadcast_socket.is_some() && app.netplay.broadcast_timer.is_some() {
|
||||
let mut buffer = [0; 1024];
|
||||
let result = app
|
||||
.netplay
|
||||
.broadcast_socket
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.recv_from(&mut buffer);
|
||||
if result.is_ok() {
|
||||
let (amt, _src) = result.unwrap();
|
||||
let data: std::collections::HashMap<String, String> =
|
||||
serde_json::from_slice(&buffer[..amt]).unwrap();
|
||||
for server in data.iter() {
|
||||
let (server_name, server_ip) = server;
|
||||
app.netplay
|
||||
.servers
|
||||
.insert(server_name.to_string(), server_ip.to_string());
|
||||
app.netplay.server = (server.0.clone(), server.1.clone());
|
||||
}
|
||||
app.netplay.broadcast_socket = None;
|
||||
}
|
||||
ctx.request_repaint();
|
||||
}
|
||||
if app.netplay.server_receiver.is_some() {
|
||||
let result = app.netplay.server_receiver.as_ref().unwrap().try_recv();
|
||||
if result.is_ok() {
|
||||
app.netplay.servers.extend(result.unwrap());
|
||||
app.netplay.server_receiver = None;
|
||||
if app.netplay.server.0.is_empty() {
|
||||
let first_server = app.netplay.servers.iter().next().unwrap();
|
||||
app.netplay.server = (first_server.0.clone(), first_server.1.clone());
|
||||
}
|
||||
}
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
egui::Window::new("Create Netplay Session").show(ctx, |ui| {
|
||||
egui::Grid::new("netplay_create_grid").show(ui, |ui| {
|
||||
let profile_name_label = ui.label("Session Name:");
|
||||
let mut size = ui.spacing().interact_size;
|
||||
size.x = 200.0;
|
||||
ui.add_sized(size, |ui: &mut egui::Ui| {
|
||||
ui.text_edit_singleline(&mut app.netplay.session_name)
|
||||
.labelled_by(profile_name_label.id)
|
||||
});
|
||||
|
||||
ui.end_row();
|
||||
|
||||
let password_label = ui.label("Password (Optional):");
|
||||
|
||||
ui.text_edit_singleline(&mut app.netplay.password)
|
||||
.labelled_by(password_label.id);
|
||||
|
||||
ui.end_row();
|
||||
|
||||
ui.label("ROM");
|
||||
if app.netplay.create_rom_label.is_empty() {
|
||||
app.netplay.create_rom_label = "Open ROM".to_string();
|
||||
}
|
||||
if ui.button(&app.netplay.create_rom_label).clicked() {
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
app.netplay.game_info_receiver = Some(rx);
|
||||
let gui_ctx = ctx.clone();
|
||||
app.netplay.create_rom_label = "Inspecting ROM".to_string();
|
||||
std::thread::spawn(move || {
|
||||
let file = rfd::FileDialog::new().pick_file();
|
||||
|
||||
if let Some(file) = file {
|
||||
parse_rom_file(file, tx);
|
||||
} else {
|
||||
tx.send((
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
"Open ROM".to_string(),
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
gui_ctx.request_repaint();
|
||||
});
|
||||
}
|
||||
|
||||
ui.end_row();
|
||||
|
||||
let player_name_label = ui.label("Player Name:");
|
||||
|
||||
ui.text_edit_singleline(&mut app.netplay.player_name)
|
||||
.labelled_by(player_name_label.id);
|
||||
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Server:");
|
||||
|
||||
get_servers(app, ctx);
|
||||
|
||||
if app.netplay.game_info_receiver.is_some() {
|
||||
let result = app.netplay.game_info_receiver.as_ref().unwrap().try_recv();
|
||||
if result.is_ok() {
|
||||
app.netplay.game_info_receiver = None;
|
||||
let data = result.unwrap();
|
||||
if !data.0.is_empty() {
|
||||
app.netplay.game_info = data;
|
||||
app.netplay.rom_contents = app.netplay.game_info.3.clone();
|
||||
app.netplay.create_rom_label = app.netplay.game_info.2.clone();
|
||||
} else {
|
||||
app.netplay.create_rom_label = data.2;
|
||||
}
|
||||
}
|
||||
ctx.request_repaint();
|
||||
}
|
||||
|
||||
egui::ComboBox::from_id_salt("server-combobox")
|
||||
.selected_text(app.netplay.server.0.to_string())
|
||||
.show_ui(ui, |ui| {
|
||||
for server in app.netplay.servers.iter() {
|
||||
ui.selectable_value(
|
||||
&mut app.netplay.server,
|
||||
(server.0.clone(), server.1.clone()),
|
||||
server.0,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
ui.end_row();
|
||||
|
||||
if app.netplay.socket_waiting {
|
||||
let data = app.netplay.socket.as_mut().unwrap().read();
|
||||
if data.is_ok() {
|
||||
let message: NetplayMessage =
|
||||
serde_json::from_slice(&data.unwrap().into_data()).unwrap();
|
||||
if message.accept.unwrap() == 0 {
|
||||
if message.message_type == "reply_create_room" {
|
||||
app.netplay.create = false;
|
||||
app.netplay.wait = true;
|
||||
app.netplay.waiting_session = Some(message.room.unwrap());
|
||||
}
|
||||
} else {
|
||||
app.netplay.error = message.message.unwrap();
|
||||
}
|
||||
app.netplay.socket_waiting = false;
|
||||
}
|
||||
ctx.request_repaint();
|
||||
}
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Create Session").clicked() {
|
||||
if app.netplay.player_name.is_empty() {
|
||||
app.netplay.error = "Player Name cannot be empty".to_string();
|
||||
} else if app.netplay.session_name.is_empty() {
|
||||
app.netplay.error = "Session Name cannot be empty".to_string();
|
||||
} else if app.netplay.game_info.0.is_empty() {
|
||||
app.netplay.error = "ROM not loaded".to_string();
|
||||
} else {
|
||||
let now_utc = chrono::Utc::now().timestamp_millis().to_string();
|
||||
let hasher = Sha256::new().chain_update(&now_utc).chain_update(EMU_NAME);
|
||||
let mut game_name = app.netplay.game_info.1.to_string();
|
||||
if game_name.is_empty() {
|
||||
// If the ROM doesn't report a name, use the filename
|
||||
game_name = app.netplay.create_rom_label.clone();
|
||||
}
|
||||
let netplay_message = NetplayMessage {
|
||||
message_type: "request_create_room".to_string(),
|
||||
player_name: Some(app.netplay.player_name.clone()),
|
||||
client_sha: Some(env!("CARGO_PKG_VERSION").to_string()),
|
||||
netplay_version: Some(NETPLAY_VERSION),
|
||||
emulator: Some(EMU_NAME.to_string()),
|
||||
accept: None,
|
||||
message: None,
|
||||
rooms: None,
|
||||
auth_time: Some(now_utc),
|
||||
player_names: None,
|
||||
auth: Some(format!("{:x}", hasher.finalize())),
|
||||
room: Some(NetplayRoom {
|
||||
room_name: Some(app.netplay.session_name.clone()),
|
||||
password: Some(app.netplay.password.clone()),
|
||||
game_name: Some(game_name),
|
||||
md5: Some(app.netplay.game_info.0.clone()),
|
||||
protected: None,
|
||||
port: None,
|
||||
}),
|
||||
};
|
||||
let (mut socket, _response) =
|
||||
tungstenite::connect(&app.netplay.server.1).expect("Can't connect");
|
||||
socket
|
||||
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
|
||||
serde_json::to_vec(&netplay_message).unwrap(),
|
||||
)))
|
||||
.unwrap();
|
||||
match socket.get_mut() {
|
||||
tungstenite::stream::MaybeTlsStream::Plain(stream) => {
|
||||
app.netplay.peer_addr = Some(stream.peer_addr().unwrap());
|
||||
stream.set_nonblocking(true)
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
.expect("could not set socket to non-blocking");
|
||||
app.netplay.socket_waiting = true;
|
||||
app.netplay.socket = Some(socket);
|
||||
}
|
||||
}
|
||||
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if ui.button("Close").clicked() {
|
||||
if let Some(socket) = app.netplay.socket.as_mut() {
|
||||
socket.close(None).unwrap();
|
||||
loop {
|
||||
match socket.read() {
|
||||
Err(tungstenite::Error::ConnectionClosed) => break,
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
}
|
||||
app.netplay = Default::default();
|
||||
};
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn get_sessions(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
if app.netplay.have_sessions.is_some()
|
||||
&& app.netplay.server != *app.netplay.have_sessions.as_ref().unwrap()
|
||||
{
|
||||
// User has changed the server
|
||||
app.netplay.have_sessions = None;
|
||||
app.netplay.socket = None;
|
||||
}
|
||||
if app.netplay.socket.is_none() {
|
||||
let (mut sock, _response) =
|
||||
tungstenite::connect(&app.netplay.server.1).expect("Can't connect");
|
||||
match sock.get_mut() {
|
||||
tungstenite::stream::MaybeTlsStream::Plain(stream) => {
|
||||
app.netplay.peer_addr = Some(stream.peer_addr().unwrap());
|
||||
stream.set_nonblocking(true)
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
.expect("could not set socket to non-blocking");
|
||||
app.netplay.socket = Some(sock);
|
||||
}
|
||||
let socket = app.netplay.socket.as_mut().unwrap();
|
||||
if app.netplay.have_sessions.is_none() {
|
||||
let now_utc = chrono::Utc::now().timestamp_millis().to_string();
|
||||
let hasher = Sha256::new().chain_update(&now_utc).chain_update(EMU_NAME);
|
||||
let request_rooms = NetplayMessage {
|
||||
message_type: "request_get_rooms".to_string(),
|
||||
player_name: None,
|
||||
client_sha: None,
|
||||
netplay_version: Some(NETPLAY_VERSION),
|
||||
player_names: None,
|
||||
emulator: Some(EMU_NAME.to_string()),
|
||||
accept: None,
|
||||
rooms: None,
|
||||
message: None,
|
||||
auth_time: Some(now_utc),
|
||||
auth: Some(format!("{:x}", hasher.finalize())),
|
||||
room: None,
|
||||
};
|
||||
|
||||
socket
|
||||
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
|
||||
serde_json::to_vec(&request_rooms).unwrap(),
|
||||
)))
|
||||
.unwrap();
|
||||
app.netplay.have_sessions = Some(app.netplay.server.clone());
|
||||
app.netplay.socket_waiting = true;
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_rom_file(file: std::path::PathBuf, tx: std::sync::mpsc::Sender<GameInfo>) {
|
||||
let rom_contents = device::get_rom_contents(file.as_path());
|
||||
if !rom_contents.is_empty() {
|
||||
let hash = device::cart_rom::calculate_hash(&rom_contents);
|
||||
let game_name = std::str::from_utf8(&rom_contents[0x20..0x20 + 0x14])
|
||||
.unwrap()
|
||||
.trim()
|
||||
.replace('\0', "")
|
||||
.to_string();
|
||||
tx.send((
|
||||
hash,
|
||||
game_name,
|
||||
file.file_name().unwrap().to_string_lossy().to_string(),
|
||||
rom_contents,
|
||||
))
|
||||
.unwrap();
|
||||
} else {
|
||||
tx.send((
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
"Invalid ROM".to_string(),
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
if app.netplay.socket_waiting {
|
||||
let socket = app.netplay.socket.as_mut().unwrap();
|
||||
let data = socket.read();
|
||||
if data.is_ok() {
|
||||
let message: NetplayMessage =
|
||||
serde_json::from_slice(&data.unwrap().into_data()).unwrap();
|
||||
if message.accept.unwrap() == 0 {
|
||||
if message.message_type == "reply_get_rooms" {
|
||||
if message.rooms.is_some() {
|
||||
app.netplay.sessions = message.rooms.unwrap();
|
||||
}
|
||||
} else if message.message_type == "reply_join_room" {
|
||||
app.netplay.join = false;
|
||||
app.netplay.wait = true;
|
||||
app.netplay.waiting_session = Some(message.room.unwrap());
|
||||
}
|
||||
app.netplay.socket_waiting = false;
|
||||
} else {
|
||||
app.netplay.error = message.message.unwrap();
|
||||
app.netplay.join_rom_label = "Join Session (Open ROM)".to_string();
|
||||
}
|
||||
}
|
||||
ctx.request_repaint();
|
||||
}
|
||||
if app.netplay.game_info_receiver.is_some() {
|
||||
let result = app.netplay.game_info_receiver.as_ref().unwrap().try_recv();
|
||||
if result.is_ok() {
|
||||
app.netplay.game_info_receiver = None;
|
||||
let data = result.unwrap();
|
||||
if !data.0.is_empty() {
|
||||
app.netplay.game_info = data;
|
||||
app.netplay.rom_contents = app.netplay.game_info.3.clone();
|
||||
|
||||
let netplay_message = NetplayMessage {
|
||||
message_type: "request_join_room".to_string(),
|
||||
player_name: Some(app.netplay.player_name.clone()),
|
||||
client_sha: Some(env!("CARGO_PKG_VERSION").to_string()),
|
||||
netplay_version: None,
|
||||
emulator: None,
|
||||
accept: None,
|
||||
message: None,
|
||||
rooms: None,
|
||||
auth_time: None,
|
||||
player_names: None,
|
||||
auth: None,
|
||||
room: Some(NetplayRoom {
|
||||
room_name: None,
|
||||
password: Some(app.netplay.password.clone()),
|
||||
game_name: None,
|
||||
md5: Some(app.netplay.game_info.0.clone()),
|
||||
protected: None,
|
||||
port: app.netplay.selected_session.as_ref().unwrap().port,
|
||||
}),
|
||||
};
|
||||
let socket = app.netplay.socket.as_mut().unwrap();
|
||||
socket
|
||||
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
|
||||
serde_json::to_vec(&netplay_message).unwrap(),
|
||||
)))
|
||||
.unwrap();
|
||||
|
||||
app.netplay.socket_waiting = true;
|
||||
} else {
|
||||
app.netplay.error = data.2;
|
||||
app.netplay.join_rom_label = "Join Session (Open ROM)".to_string();
|
||||
}
|
||||
}
|
||||
ctx.request_repaint();
|
||||
}
|
||||
egui::Window::new("Join Netplay Session").show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
let mut size = ui.spacing().interact_size;
|
||||
size.x = 100.0;
|
||||
ui.add_sized(
|
||||
size,
|
||||
egui::TextEdit::singleline(&mut app.netplay.player_name).hint_text("Player name"),
|
||||
);
|
||||
|
||||
get_servers(app, ctx);
|
||||
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if ui.button("Refresh").clicked() {
|
||||
app.netplay.socket = None;
|
||||
app.netplay.have_sessions = None;
|
||||
app.netplay.selected_session = None;
|
||||
ctx.request_repaint();
|
||||
}
|
||||
egui::ComboBox::from_id_salt("server-combobox")
|
||||
.selected_text(app.netplay.server.0.to_string())
|
||||
.show_ui(ui, |ui| {
|
||||
for server in app.netplay.servers.iter() {
|
||||
ui.selectable_value(
|
||||
&mut app.netplay.server,
|
||||
(server.0.clone(), server.1.clone()),
|
||||
server.0,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
if !app.netplay.server.0.is_empty() {
|
||||
get_sessions(app, ctx);
|
||||
}
|
||||
ui.add_space(16.0);
|
||||
if app.netplay.sessions.is_empty() {
|
||||
ui.label("No sessions available");
|
||||
} else {
|
||||
egui::Grid::new("netplay_join_grid").show(ui, |ui| {
|
||||
ui.label(egui::RichText::new("Session Name (click to select)").underline());
|
||||
ui.label(egui::RichText::new("Game Name").underline());
|
||||
ui.label(egui::RichText::new("Password Protected").underline());
|
||||
ui.end_row();
|
||||
|
||||
for room in app.netplay.sessions.iter() {
|
||||
ui.selectable_value(
|
||||
&mut app.netplay.selected_session,
|
||||
Some(room.clone()),
|
||||
room.room_name.as_ref().unwrap(),
|
||||
);
|
||||
ui.label(room.game_name.as_ref().unwrap());
|
||||
ui.label(room.protected.unwrap_or(false).to_string());
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
}
|
||||
ui.add_space(16.0);
|
||||
ui.horizontal(|ui| {
|
||||
let mut size = ui.spacing().interact_size;
|
||||
size.x = 130.0;
|
||||
ui.add_sized(
|
||||
size,
|
||||
egui::TextEdit::singleline(&mut app.netplay.password)
|
||||
.hint_text("Password (if required)"),
|
||||
);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if ui.button("Close").clicked() {
|
||||
if let Some(socket) = app.netplay.socket.as_mut() {
|
||||
socket.close(None).unwrap();
|
||||
loop {
|
||||
match socket.read() {
|
||||
Err(tungstenite::Error::ConnectionClosed) => break,
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
}
|
||||
app.netplay = Default::default();
|
||||
};
|
||||
if app.netplay.join_rom_label.is_empty() {
|
||||
app.netplay.join_rom_label = "Join Session (Open ROM)".to_string();
|
||||
}
|
||||
if ui.button(&app.netplay.join_rom_label).clicked() {
|
||||
if app.netplay.player_name.is_empty() {
|
||||
app.netplay.error = "Player Name cannot be empty".to_string();
|
||||
} else if app.netplay.selected_session.is_none() {
|
||||
app.netplay.error = "No session selected".to_string();
|
||||
} else if app
|
||||
.netplay
|
||||
.selected_session
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.protected
|
||||
.unwrap()
|
||||
&& app.netplay.password.is_empty()
|
||||
{
|
||||
app.netplay.error = "Session requires a password".to_string();
|
||||
} else {
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
app.netplay.game_info_receiver = Some(rx);
|
||||
let gui_ctx = ctx.clone();
|
||||
app.netplay.join_rom_label = "Inspecting ROM".to_string();
|
||||
std::thread::spawn(move || {
|
||||
let file = rfd::FileDialog::new().pick_file();
|
||||
|
||||
if let Some(file) = file {
|
||||
parse_rom_file(file, tx);
|
||||
} else {
|
||||
tx.send((
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
"No ROM selected".to_string(),
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
gui_ctx.request_repaint();
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn netplay_wait(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
let motd_message = NetplayMessage {
|
||||
message_type: "request_motd".to_string(),
|
||||
player_name: None,
|
||||
client_sha: None,
|
||||
netplay_version: None,
|
||||
emulator: None,
|
||||
accept: None,
|
||||
rooms: None,
|
||||
player_names: None,
|
||||
message: None,
|
||||
auth_time: None,
|
||||
auth: None,
|
||||
room: None,
|
||||
};
|
||||
let request_players = NetplayMessage {
|
||||
message_type: "request_players".to_string(),
|
||||
player_name: None,
|
||||
client_sha: None,
|
||||
netplay_version: None,
|
||||
player_names: None,
|
||||
emulator: None,
|
||||
rooms: None,
|
||||
accept: None,
|
||||
message: None,
|
||||
auth_time: None,
|
||||
auth: None,
|
||||
room: Some(NetplayRoom {
|
||||
room_name: None,
|
||||
password: None,
|
||||
game_name: None,
|
||||
md5: None,
|
||||
protected: None,
|
||||
port: app.netplay.waiting_session.as_ref().unwrap().port,
|
||||
}),
|
||||
};
|
||||
|
||||
if !app.netplay.socket_waiting {
|
||||
let socket = app.netplay.socket.as_mut().unwrap();
|
||||
socket
|
||||
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
|
||||
serde_json::to_vec(&motd_message).unwrap(),
|
||||
)))
|
||||
.unwrap();
|
||||
socket
|
||||
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
|
||||
serde_json::to_vec(&request_players).unwrap(),
|
||||
)))
|
||||
.unwrap();
|
||||
app.netplay.socket_waiting = true;
|
||||
}
|
||||
|
||||
if app.netplay.begin_game {
|
||||
let begin_game = NetplayMessage {
|
||||
message_type: "request_begin_game".to_string(),
|
||||
player_name: None,
|
||||
client_sha: None,
|
||||
netplay_version: None,
|
||||
player_names: None,
|
||||
rooms: None,
|
||||
emulator: None,
|
||||
accept: None,
|
||||
message: None,
|
||||
auth_time: None,
|
||||
auth: None,
|
||||
room: Some(NetplayRoom {
|
||||
room_name: None,
|
||||
password: None,
|
||||
game_name: None,
|
||||
md5: None,
|
||||
protected: None,
|
||||
port: app.netplay.waiting_session.as_ref().unwrap().port,
|
||||
}),
|
||||
};
|
||||
let socket = app.netplay.socket.as_mut().unwrap();
|
||||
socket
|
||||
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
|
||||
serde_json::to_vec(&begin_game).unwrap(),
|
||||
)))
|
||||
.unwrap();
|
||||
app.netplay.begin_game = false;
|
||||
}
|
||||
|
||||
if app.netplay.send_chat {
|
||||
let send_chat = NetplayMessage {
|
||||
message_type: "request_chat_message".to_string(),
|
||||
player_name: Some(app.netplay.player_name.clone()),
|
||||
client_sha: None,
|
||||
netplay_version: None,
|
||||
player_names: None,
|
||||
rooms: None,
|
||||
emulator: None,
|
||||
accept: None,
|
||||
message: Some(app.netplay.chat_message.clone()),
|
||||
auth_time: None,
|
||||
auth: None,
|
||||
room: Some(NetplayRoom {
|
||||
room_name: None,
|
||||
password: None,
|
||||
game_name: None,
|
||||
md5: None,
|
||||
protected: None,
|
||||
port: app.netplay.waiting_session.as_ref().unwrap().port,
|
||||
}),
|
||||
};
|
||||
app.netplay.chat_message.clear();
|
||||
let socket = app.netplay.socket.as_mut().unwrap();
|
||||
socket
|
||||
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
|
||||
serde_json::to_vec(&send_chat).unwrap(),
|
||||
)))
|
||||
.unwrap();
|
||||
app.netplay.send_chat = false;
|
||||
}
|
||||
|
||||
if app.netplay.socket_waiting {
|
||||
let socket = app.netplay.socket.as_mut().unwrap();
|
||||
let data = socket.read();
|
||||
if data.is_ok() {
|
||||
let message: NetplayMessage =
|
||||
serde_json::from_slice(&data.unwrap().into_data()).unwrap();
|
||||
if message.accept.unwrap() == 0 {
|
||||
if message.message_type == "reply_motd" {
|
||||
let re = regex::Regex::new(r"<[^>]*>").unwrap();
|
||||
app.netplay.motd = re
|
||||
.replace_all(message.message.unwrap().as_str(), "")
|
||||
.into_owned();
|
||||
} else if message.message_type == "reply_players" {
|
||||
app.netplay.player_names = message.player_names.unwrap();
|
||||
} else if message.message_type == "reply_chat_message" {
|
||||
app.netplay.chat_log.push_str(&message.message.unwrap());
|
||||
app.netplay.chat_log.push('\n');
|
||||
} else if message.message_type == "reply_begin_game" {
|
||||
let mut player = 0;
|
||||
for (i, name) in app.netplay.player_names.iter().enumerate() {
|
||||
if *name == app.netplay.player_name {
|
||||
player = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
app.netplay.player_number = player as u8;
|
||||
|
||||
if message.accept.unwrap() == 0 {
|
||||
socket.close(None).unwrap();
|
||||
loop {
|
||||
match socket.read() {
|
||||
Err(tungstenite::Error::ConnectionClosed) => break,
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
|
||||
gui::open_rom(app, ctx);
|
||||
app.netplay = Default::default();
|
||||
return;
|
||||
} else {
|
||||
app.netplay.error = message.message.unwrap();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
app.netplay.error = message.message.unwrap();
|
||||
}
|
||||
}
|
||||
ctx.request_repaint();
|
||||
}
|
||||
|
||||
egui::Window::new("Pending Netplay Session").show(ctx, |ui| {
|
||||
egui::Grid::new("netplay_wait_grid_1").show(ui, |ui| {
|
||||
ui.label("Session Name:");
|
||||
let room_name = app
|
||||
.netplay
|
||||
.waiting_session
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.room_name
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let game_name = app
|
||||
.netplay
|
||||
.waiting_session
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.game_name
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
ui.label(room_name);
|
||||
ui.end_row();
|
||||
ui.label("Game Name:");
|
||||
ui.label(game_name);
|
||||
ui.end_row();
|
||||
for i in 0..4 {
|
||||
ui.label(format!("Player {}:", i + 1));
|
||||
ui.label(app.netplay.player_names[i].clone());
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
egui::ScrollArea::vertical()
|
||||
.max_height(100.0)
|
||||
.stick_to_bottom(true)
|
||||
.show(ui, |ui| {
|
||||
ui.add(
|
||||
egui::TextEdit::multiline(&mut app.netplay.chat_log)
|
||||
.interactive(false)
|
||||
.desired_width(ui.available_width()),
|
||||
);
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let mut size = ui.spacing().interact_size;
|
||||
size.x = 200.0;
|
||||
|
||||
ui.add_sized(
|
||||
size,
|
||||
egui::TextEdit::singleline(&mut app.netplay.chat_message)
|
||||
.hint_text("Enter chat message here"),
|
||||
);
|
||||
|
||||
if ui
|
||||
.add_enabled(!app.netplay.send_chat, egui::Button::new("Send Message"))
|
||||
.clicked()
|
||||
&& !app.netplay.chat_message.is_empty()
|
||||
{
|
||||
app.netplay.send_chat = true;
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(16.0);
|
||||
ui.label(app.netplay.motd.clone());
|
||||
ui.add_space(16.0);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let button_enabled = app.netplay.player_name == app.netplay.player_names[0]
|
||||
&& !app.netplay.pending_begin;
|
||||
if ui
|
||||
.add_enabled(button_enabled, egui::Button::new("Start Session"))
|
||||
.clicked()
|
||||
{
|
||||
app.netplay.begin_game = true;
|
||||
app.netplay.pending_begin = true;
|
||||
}
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if ui.button("Close").clicked() {
|
||||
if let Some(socket) = app.netplay.socket.as_mut() {
|
||||
socket.close(None).unwrap();
|
||||
loop {
|
||||
match socket.read() {
|
||||
Err(tungstenite::Error::ConnectionClosed) => break,
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
}
|
||||
app.netplay = Default::default();
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn netplay_error(app: &mut GopherEguiApp, ctx: &egui::Context, error: String) {
|
||||
egui::Window::new("Netplay Error").show(ctx, |ui| {
|
||||
ui.label(error);
|
||||
if ui.button("Close").clicked() {
|
||||
app.netplay.error = "".to_string();
|
||||
};
|
||||
});
|
||||
}
|
||||