Compare commits
535 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e1ad3aac06 | |||
| e8a7abffe3 | |||
| f7ad6a4b0d | |||
| 1bfb50de8a | |||
| 3bc7c96191 | |||
| 032bf1acf3 | |||
| 8bc765c1a3 | |||
| da061240af | |||
| 407a1b967c | |||
| ad756cd6bd | |||
| 6e11bc18d6 | |||
| c8eee9f727 | |||
| 01a53ce1c0 | |||
| 0eddf31f92 | |||
| e257ba717b | |||
| 7a797165fd | |||
| a659413090 | |||
| 08e7ec71d9 | |||
| c97f14980b | |||
| 5d090f496b | |||
| d6b46037b4 | |||
| 5b5acfdd25 | |||
| 926338ba62 | |||
| cc420013f9 | |||
| ba0a21feca | |||
| 70b9d20c64 | |||
| dee2b85daa | |||
| 119e9044a8 | |||
| e6a260bd6a | |||
| a23ede433b | |||
| 346b0a139d | |||
| f5cda2901f | |||
| 2dda5c8706 | |||
| d6aa2ddce3 | |||
| 6f9801a185 | |||
| c82edae338 | |||
| 57327c1f73 | |||
| e3a22bf7d0 | |||
| 3718d914fd | |||
| 97a8b63c9c | |||
| d6d0b66078 | |||
| b173598320 | |||
| f211eede3b | |||
| b123ed9fa3 | |||
| 2b0d8e5423 | |||
| 49b8024fbe | |||
| be987e1434 | |||
| 27e2b89fcf | |||
| c261a746a3 | |||
| c290e60af1 | |||
| 19142a30c6 | |||
| 2ca317d742 | |||
| 45963aeea5 | |||
| 4929f2cc51 | |||
| 90e51f5f09 | |||
| 9b7aeccd48 | |||
| 10d55bd25c | |||
| 16224cd5a6 | |||
| d62d4c852a | |||
| 6d6349c79d | |||
| 9d2eec1732 | |||
| d6da1656b8 | |||
| ae7bac65d5 | |||
| 99c4d6dff8 | |||
| 74600b4b25 | |||
| 6cae2ed3ac | |||
| 611b56186d | |||
| dfa673e6e9 | |||
| be74e3358a | |||
| 4cc85d595f | |||
| b2d7a12033 | |||
| 65cffbba0c | |||
| 5f8e3b9760 | |||
| 397a15349c | |||
| 235a4af0a9 | |||
| b171d48f87 | |||
| bfeba94e20 | |||
| aa0bde0999 | |||
| 72e4a6b94a | |||
| 88f00f5638 | |||
| db21800d9f | |||
| 4a63f469bf | |||
| 810852997a | |||
| 02d53beeac | |||
| d589d8bac2 | |||
| 0deb1d11fc | |||
| 6cc6153abb | |||
| af163dac62 | |||
| 8c4be03ab7 | |||
| fce57f8ee2 | |||
| d48423da77 | |||
| f691578698 | |||
| 42a4a46dd9 | |||
| e1d715bc38 | |||
| 4285290452 | |||
| d79834eb50 | |||
| 1244706b6e | |||
| a1a7699516 | |||
| 577741907f | |||
| ba35935d2e | |||
| a7df8d837f | |||
| 820c577f5f | |||
| 8b3a1a40f2 | |||
| 21238c8e41 | |||
| 73f145a35c | |||
| 3184305c41 | |||
| 06dc40fd3e | |||
| c7ac333cb7 | |||
| 1d6e415422 | |||
| c89c5d1cc3 | |||
| 19dc966abd | |||
| 077c1f439c | |||
| 9140138843 | |||
| 618e3a32f2 | |||
| 681f8670f5 | |||
| 0a44124c20 | |||
| eb64a9ad43 | |||
| 5a5ff40efc | |||
| cd6726d451 | |||
| cbc973a559 | |||
| d968983ceb | |||
| 5175d50aa8 | |||
| e25818b451 | |||
| 4453e66bcf | |||
| 19432f33ba | |||
| 5c7170268e | |||
| 79d2c023e6 | |||
| 864175fae6 | |||
| 7fc2412c9c | |||
| 3fdbe9a4f7 | |||
| 399b5d65e5 | |||
| 24e5a3fac4 | |||
| 31420b6777 | |||
| 7379484932 | |||
| d36ce10ab3 | |||
| 57807f09c8 | |||
| 0745d75c2f | |||
| a78f47ecfe | |||
| 1df705c4f9 | |||
| f7ed2c5273 | |||
| 8bd4c936f8 | |||
| 34e55d9cb5 | |||
| 8a8596a66b | |||
| 40ab4ceb2b | |||
| a8dbbba6e3 | |||
| 5b0cc0130e | |||
| bb32a0a8ba | |||
| e23894c2e6 | |||
| e0efa80f44 | |||
| cb734f8df1 | |||
| 3197d415e0 | |||
| 9dfc42c723 | |||
| 14b973ec5f | |||
| 81a3cea9c7 | |||
| dc6138076f | |||
| a9f7285c0f | |||
| 7106a461b5 | |||
| 69e3ebcc26 | |||
| f80f0f5f02 | |||
| a447bc8104 | |||
| 32f54f157b | |||
| 9c9f1a3000 | |||
| 9d7b13d4c9 | |||
| 60cbf59ddf | |||
| 6f00e630e0 | |||
| 7599f71c17 | |||
| 411cb0efa7 | |||
| 00ec554783 | |||
| d25d3a460a | |||
| 8eec90c35c | |||
| a596e01ef2 | |||
| 43ebc37f87 | |||
| 6223f10fae | |||
| c16e927288 | |||
| 6ef33204e8 | |||
| 3f9d62248a | |||
| 18fbd8e56c | |||
| e5c74d05aa | |||
| 82c22dc18e | |||
| 66a74b5693 | |||
| 367b8bdb5c | |||
| b865c88d99 | |||
| 53b39010a0 | |||
| 7ad8bed7df | |||
| 01ea4c422a | |||
| 7eb090a622 | |||
| 48956dc016 | |||
| 2eb8a0dc1d | |||
| c53ffebece | |||
| 06d0aa53bc | |||
| 28b1424357 | |||
| e852439b6a | |||
| 5850c514f2 | |||
| 11a49fd687 | |||
| be9ba1366e | |||
| dbe67f1034 | |||
| 7eecfa3275 | |||
| e1b4039a52 | |||
| 9f2cfb5a9f | |||
| 41e66825d0 | |||
| a78393ca91 | |||
| 8dde32cb21 | |||
| 1538513fb7 | |||
| 30c8aef24e | |||
| 8daa65d93e | |||
| df5fcae69b | |||
| e3b63908de | |||
| 8c53de9cc7 | |||
| 78cb758a26 | |||
| 8fea9ef212 | |||
| 38fdc24a49 | |||
| 069c64ab08 | |||
| 2005fb1421 | |||
| 04b15fee2f | |||
| eda3dda613 | |||
| 03be98b571 | |||
| 93bf103f03 | |||
| c8f485cd93 | |||
| 3a301c517d | |||
| d17c577ccd | |||
| 8c331c25eb | |||
| 8341ef5731 | |||
| 40a29d927f | |||
| 9846c2243c | |||
| c1dc4fe589 | |||
| 06052b729b | |||
| d7c912b53e | |||
| b792ebbd45 | |||
| e7071047e3 | |||
| 6496288c4e | |||
| 1ab8987f2d | |||
| 034b39bec5 | |||
| 5368ae3d37 | |||
| f963742009 | |||
| a8ae9dbc6a | |||
| b55a031971 | |||
| 27ffedbdee | |||
| 493f08afaf | |||
| 0659da24fa | |||
| 5596dad111 | |||
| a5b7561c51 | |||
| fe23e4bad9 | |||
| 5b3b8265e5 | |||
| a7cbf866e8 | |||
| 7fae21d7ac | |||
| 55bf63210a | |||
| 2b72c24dbe | |||
| 455365a71d | |||
| 1f6b575440 | |||
| 86304018dd | |||
| c44ab9602c | |||
| 13be114823 | |||
| f793eb06be | |||
| 61532f7b45 | |||
| a69a74bf37 | |||
| 6d4d5468f9 | |||
| 7015d44da7 | |||
| 172867527b | |||
| 323e8cf1f0 | |||
| 172d10d7cb | |||
| 945d456f9c | |||
| a4f635265b | |||
| f45f951af8 | |||
| 883852a828 | |||
| 90052257bb | |||
| 1184d3d77a | |||
| 12280bb35b | |||
| 17f3f2dc63 | |||
| 3d6ca17a9a | |||
| 01863a70a8 | |||
| 801cc1bc74 | |||
| 9ed8d835c0 | |||
| b754891d45 | |||
| 9cc6d48493 | |||
| b865538d6e | |||
| abacaddf8a | |||
| b86c5daa2c | |||
| df8d7d9ff0 | |||
| 8c7d3d4893 | |||
| b88f041bd8 | |||
| e860b96bd8 | |||
| b2ef544513 | |||
| ca00ee1a42 | |||
| 511d8461c2 | |||
| fe42efadfa | |||
| c1e73320b1 | |||
| 47dd6d61b0 | |||
| 212557610b | |||
| 5e6a7731f4 | |||
| 65c63dfc5d | |||
| ae4e7cb01f | |||
| a233c72dad | |||
| b267b83248 | |||
| 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 |
@@ -1,16 +1,27 @@
|
||||
[target.'cfg(target_os = "linux")']
|
||||
[target.'cfg(all(target_arch = "aarch64", target_os = "linux"))']
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
|
||||
|
||||
[target.'cfg(target_os = "windows")']
|
||||
[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",
|
||||
"-C", "link-arg=-headerpad_max_install_names"
|
||||
]
|
||||
|
||||
[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"
|
||||
CMAKE_POLICY_VERSION_MINIMUM = "3.5" # for freetype
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Bugs only! No feature requests
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
type: Bug
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Device (please complete the following information):**
|
||||
- OS: [e.g. Windows 11]
|
||||
- CPU: [e.g. Intel i7-1365U]
|
||||
- GPU: [e.g. Nvidia 5060 Ti]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
@@ -8,6 +8,7 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
# - package-ecosystem: "cargo"
|
||||
# directory: "/"
|
||||
# schedule:
|
||||
|
||||
@@ -9,9 +9,14 @@ on:
|
||||
- "v*"
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
merge_group:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RA_HARDCORE: ${{ github.ref_type == 'tag' && 'true' || 'false' }}
|
||||
NETPLAY_ID: ${{ github.ref_type == 'tag' && secrets.NETPLAY_ID || 'dev' }}
|
||||
BUILD_PROFILE: ${{ github.ref_type == 'tag' && 'release' || 'dev' }}
|
||||
BUILD_DIR: ${{ github.ref_type == 'tag' && 'release' || 'debug' }}
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
@@ -29,42 +34,51 @@ jobs:
|
||||
arch: linux-aarch64
|
||||
llvm: ARM64
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v7
|
||||
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
|
||||
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
|
||||
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 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 fcitx-libs-dev \
|
||||
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
|
||||
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 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
|
||||
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 }}
|
||||
cargo build --profile ${{ env.BUILD_PROFILE }} -v --target=${{ matrix.target }}
|
||||
mkdir output
|
||||
cp target/${{ matrix.target }}/release/gopher64 output/gopher64-${{ matrix.arch }}
|
||||
cp target/${{ matrix.target }}/${{ env.BUILD_DIR }}/gopher64 output/gopher64-${{ matrix.arch }}
|
||||
- name: Upload file
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: gopher64-${{ matrix.arch }}
|
||||
path: output/gopher64-${{ matrix.arch }}
|
||||
archive: false
|
||||
|
||||
build-windows:
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -76,18 +90,20 @@ jobs:
|
||||
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
|
||||
- os: windows-11-arm
|
||||
target: aarch64-pc-windows-msvc
|
||||
arch: windows-aarch64
|
||||
llvm: woa64
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v7
|
||||
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() }
|
||||
@@ -96,21 +112,28 @@ jobs:
|
||||
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
|
||||
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 }}
|
||||
cargo build --profile ${{ env.BUILD_PROFILE }} -v --target=${{ matrix.target }}
|
||||
mkdir output
|
||||
cp target/${{ matrix.target }}/release/gopher64.exe output/gopher64-${{ matrix.arch }}.exe
|
||||
cp target/${{ matrix.target }}/${{ env.BUILD_DIR }}/gopher64.exe output/gopher64-${{ matrix.arch }}.exe
|
||||
env:
|
||||
CC: clang-cl
|
||||
CXX: clang-cl
|
||||
- name: Upload unsigned artifact
|
||||
id: upload-unsigned-artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
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@v1.3
|
||||
uses: signpath/github-action-submit-signing-request@v2
|
||||
with:
|
||||
api-token: "${{ secrets.SIGNPATH_API_TOKEN }}"
|
||||
organization-id: "${{ secrets.SIGNPATH_ORG_ID }}"
|
||||
@@ -118,7 +141,17 @@ jobs:
|
||||
artifact-configuration-slug: "gopher64-${{ matrix.arch }}"
|
||||
signing-policy-slug: "release-signing"
|
||||
github-artifact-id: "${{ steps.upload-unsigned-artifact.outputs.artifact-id }}"
|
||||
wait-for-completion: false
|
||||
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-15
|
||||
@@ -126,27 +159,143 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: x86_64-apple-darwin
|
||||
arch: mac-x86_64
|
||||
- target: aarch64-apple-darwin
|
||||
arch: mac-aarch64
|
||||
arch: macos-aarch64
|
||||
llvm: ARM64
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v7
|
||||
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 }}
|
||||
mkdir output
|
||||
cp target/${{ matrix.target }}/release/gopher64 output/gopher64-${{ matrix.arch }}
|
||||
cargo install cargo-bundle
|
||||
brew install molten-vk
|
||||
cargo build --profile ${{ env.BUILD_PROFILE }} --no-default-features -v --target=${{ matrix.target }}
|
||||
mv target/${{ matrix.target }}/${{ env.BUILD_DIR }}/gopher64 target/${{ matrix.target }}/${{ env.BUILD_DIR }}/gopher64-cli
|
||||
cargo bundle --profile ${{ env.BUILD_PROFILE }} --target=${{ matrix.target }} --format osx
|
||||
cp target/${{ matrix.target }}/${{ env.BUILD_DIR }}/gopher64-cli target/${{ matrix.target }}/${{ env.BUILD_DIR }}/bundle/osx/Gopher64.app/Contents/MacOS/gopher64-cli
|
||||
env:
|
||||
AR: ar
|
||||
MACOSX_DEPLOYMENT_TARGET: "15.0"
|
||||
- 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 }}/${{ env.BUILD_DIR }}/bundle/osx/Gopher64.app
|
||||
- name: Import Codesign Certs
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: apple-actions/import-codesign-certs@v7
|
||||
with:
|
||||
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 }}/${{ env.BUILD_DIR }}/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 }}/${{ env.BUILD_DIR }}/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 }}/${{ env.BUILD_DIR }}/bundle/osx/Gopher64.app
|
||||
zip -r gopher64-${{ matrix.arch }}.zip target/${{ matrix.target }}/${{ env.BUILD_DIR }}/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 }}/${{ env.BUILD_DIR }}/bundle/osx/Gopher64.app
|
||||
- name: Upload file
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: gopher64-${{ matrix.arch }}
|
||||
path: output/gopher64-${{ matrix.arch }}
|
||||
path: target/${{ matrix.target }}/${{ env.BUILD_DIR }}/bundle/osx
|
||||
|
||||
build-android:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-24.04
|
||||
platform: 33
|
||||
ndk: 29.0.14206865
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
target: x86_64-linux-android,aarch64-linux-android
|
||||
rustflags: ""
|
||||
cache: ${{ github.ref_type != 'tag' }}
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v4
|
||||
with:
|
||||
packages: "platforms;android-${{ matrix.platform }} ndk;${{ matrix.ndk }}"
|
||||
- name: Decode keystore and create jks
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
working-directory: android-project
|
||||
run: |
|
||||
echo "${{ secrets.ANDROID_KEYSTORE }}" | base64 --decode > app/keystore.jks
|
||||
|
||||
cat <<EOF > keystore.properties
|
||||
storePassword=${{ secrets.ANDROID_STORE_PASSWORD }}
|
||||
keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
keyAlias=gopher64
|
||||
storeFile=keystore.jks
|
||||
EOF
|
||||
- name: Build
|
||||
working-directory: android-project
|
||||
run: |
|
||||
cargo install cargo-ndk
|
||||
./gradlew assemble${{ env.BUILD_PROFILE == 'release' && 'Release' || 'Debug' }}
|
||||
mkdir output
|
||||
cp app/build/outputs/apk/${{ env.BUILD_DIR }}/app-${{ env.BUILD_DIR }}.apk output/gopher64-android.apk
|
||||
- name: Upload file
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: gopher64-android
|
||||
path: android-project/output/gopher64-android.apk
|
||||
archive: false
|
||||
|
||||
collect-artifacts:
|
||||
name: Collect release artifacts
|
||||
needs: [build-linux, build-windows, build-macos, build-android]
|
||||
if: github.ref_type == 'tag'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Download all workflow artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
skip-decompress: true
|
||||
merge-multiple: true
|
||||
path: release-artifacts
|
||||
- name: Add files to release
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
draft: true
|
||||
files: release-artifacts/*
|
||||
- name: Setup butler
|
||||
uses: remarkablegames/setup-butler@v3
|
||||
- name: Upload to itch.io
|
||||
run: |
|
||||
butler push release-artifacts/gopher64-linux-x86_64 loganmc10/gopher64:linux-x86_64 --userversion ${GITHUB_REF_NAME#v}
|
||||
butler push release-artifacts/gopher64-linux-aarch64 loganmc10/gopher64:linux-aarch64 --userversion ${GITHUB_REF_NAME#v}
|
||||
butler push release-artifacts/gopher64-windows-x86_64.exe loganmc10/gopher64:windows-x86_64 --userversion ${GITHUB_REF_NAME#v}
|
||||
butler push release-artifacts/gopher64-windows-aarch64.exe loganmc10/gopher64:windows-aarch64 --userversion ${GITHUB_REF_NAME#v}
|
||||
butler push release-artifacts/gopher64-macos-aarch64.zip loganmc10/gopher64:macos-aarch64 --userversion ${GITHUB_REF_NAME#v}
|
||||
butler push release-artifacts/gopher64-android.apk loganmc10/gopher64:android --userversion ${GITHUB_REF_NAME#v}
|
||||
env:
|
||||
BUTLER_API_KEY: ${{ secrets.BUTLER_API_KEY }}
|
||||
|
||||
@@ -5,6 +5,7 @@ on:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
merge_group:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -16,33 +17,46 @@ jobs:
|
||||
lint-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
rustflags: ""
|
||||
components: clippy, rustfmt
|
||||
- name: Set LLVM_VERSION
|
||||
run: echo "LLVM_VERSION=$(rustc --version --verbose | grep "LLVM version" | cut -d':' -f2 | xargs)" >> $GITHUB_ENV
|
||||
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
|
||||
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 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 fcitx-libs-dev \
|
||||
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
|
||||
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 libfontconfig-dev gettext
|
||||
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
|
||||
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 install slint-tr-extractor
|
||||
cargo clippy -- -Dwarnings
|
||||
cargo clippy --no-default-features -- -Dwarnings
|
||||
find -name \*.slint | sort | xargs slint-tr-extractor -o /tmp/translations.pot
|
||||
msgcmp --use-untranslated /tmp/translations.pot data/translations/gopher64.pot
|
||||
msgcmp --use-untranslated data/translations/gopher64.pot /tmp/translations.pot
|
||||
find data/translations -name "*.po" | xargs -I {} msgcmp {} data/translations/gopher64.pot
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
checks: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: rustsec/audit-check@v2
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
[submodule "parallel-rdp/parallel-rdp-standalone"]
|
||||
path = parallel-rdp/parallel-rdp-standalone
|
||||
url = https://github.com/gopher64/parallel-rdp-standalone.git
|
||||
branch = gopher64
|
||||
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,47 +1,77 @@
|
||||
[package]
|
||||
name = "gopher64"
|
||||
version = "1.1.4"
|
||||
version = "1.1.32"
|
||||
edition = "2024"
|
||||
rust-version = "1.89"
|
||||
rust-version = "1.96.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:open", "dep:slint-build"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
dirs = "6.0"
|
||||
zip = "4.3"
|
||||
governor = "0.10"
|
||||
sevenz-rust2 = { version = "0.17", default-features = false }
|
||||
zip = "8.0"
|
||||
sevenz-rust2 = "0.21"
|
||||
chrono = "0.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde-big-array = "0.5"
|
||||
slint = { version = "1.12", default-features = false, features = ["compat-1-2", "std", "backend-winit", "renderer-femtovg"] }
|
||||
open = "5.3"
|
||||
sha2 = "0.10"
|
||||
sdl3-sys = { version = "0.5", features = ["build-from-source-static"] }
|
||||
sdl3-ttf-sys = { version = "0.2", features = ["build-static-vendored", "no-sdlttf-harfbuzz", "no-sdlttf-plutosvg"] }
|
||||
rfd = { version = "0.15", default-features = false, features = ["xdg-portal", "tokio"] }
|
||||
tokio = {version = "1.46", features = ["rt-multi-thread", "macros"] }
|
||||
sha2 = "0.11"
|
||||
tokio = {version = "1.46", features = ["rt-multi-thread", "fs", "process"] }
|
||||
spin_sleep = "1.3"
|
||||
encoding_rs = "0.8"
|
||||
mimalloc = "0.1"
|
||||
regex = "1.11"
|
||||
tokio-tungstenite = "0.27"
|
||||
tokio-tungstenite = { version = "0.29", features = ["native-tls-vendored"] }
|
||||
matchbox_socket = "0.14"
|
||||
ggrs = { version = "0.13", features = ["sync-send"] }
|
||||
rustc-hash = "2.1"
|
||||
futures = "0.3"
|
||||
socket2 = { version = "0.6", features = ["all"] }
|
||||
rand_chacha = "0.9"
|
||||
rand = { version = "0.10", features = ["serde"] }
|
||||
fatfs = "0.3"
|
||||
semver = "1.0"
|
||||
postcard = { version = "1.1", features = ["use-std"] }
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
reqwest = { version = "0.12", default-features = false, features = ["http2","rustls-tls","charset","json"] }
|
||||
reqwest = { version = "0.13", default-features = false, features = ["native-tls-vendored", "charset", "http2", "system-proxy", "json", "query"] }
|
||||
discord-rich-presence = "1.1"
|
||||
quick-xml = "0.40"
|
||||
|
||||
[target.'cfg(not(target_os = "android"))'.dependencies]
|
||||
slint = { version = "1.16", default-features = false, features = ["compat-1-2", "std", "unstable-winit-030", "backend-winit", "renderer-femtovg-wgpu", "accessibility"], optional = true }
|
||||
rfd = {version = "0.17", optional = true }
|
||||
dirs = "6.0"
|
||||
open = { version = "5.3", optional = true }
|
||||
sdl3-sys = { version = "0.6", features = ["build-from-source-static"] }
|
||||
sdl3-ttf-sys = { version = "0.6", features = ["build-static-vendored", "no-sdlttf-harfbuzz", "no-sdlttf-plutosvg"] }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
slint = { version = "1.16", default-features = false, features = ["compat-1-2", "std", "backend-android-activity-06", "accessibility"], optional = true }
|
||||
jni = "0.22"
|
||||
sdl3-sys = { version = "0.6", features = ["build-from-source"] }
|
||||
sdl3-ttf-sys = { version = "0.6", features = ["build-from-source", "no-sdlttf-harfbuzz", "no-sdlttf-plutosvg"] }
|
||||
|
||||
[build-dependencies]
|
||||
winresource = "0.1"
|
||||
bindgen = "0.72"
|
||||
slint-build = "1.12"
|
||||
sdl3-src = "3.4"
|
||||
slint-build = { version = "1.12", optional = true }
|
||||
cc = { version = "1.2", features = ["parallel"] }
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = "thin"
|
||||
strip = true
|
||||
|
||||
[profile.release-with-debug]
|
||||
inherits = "release"
|
||||
debug = true
|
||||
strip = false
|
||||
|
||||
[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"]
|
||||
osx_minimum_system_version = "15.0"
|
||||
|
||||
@@ -1,9 +1,31 @@
|
||||
# gopher64
|
||||
Gopher64 is a cross-platform N64 emulator. Some notable features:
|
||||
* Netplay
|
||||
* Homebrew support
|
||||
* Upscaling
|
||||
* CRT shader
|
||||
* Emulate CPU overclocking
|
||||
* Cheats
|
||||
* Savestates
|
||||
* RetroAchievements
|
||||
|
||||
## download
|
||||
|
||||
Windows: https://github.com/gopher64/gopher64/releases/latest/download/gopher64-windows-x86_64.exe
|
||||
<a href="https://loganmc10.itch.io/gopher64"><img src="https://static.itch.io/images/badge.svg" width="200" ></a>
|
||||
|
||||
Linux: https://flathub.org/apps/io.github.gopher64.gopher64
|
||||
Windows:
|
||||
* Standalone executable: [gopher64-windows-x86_64.exe](https://github.com/gopher64/gopher64/releases/latest/download/gopher64-windows-x86_64.exe)
|
||||
|
||||
MacOS:
|
||||
* App Bundle: [gopher64-macos-aarch64.zip](https://github.com/gopher64/gopher64/releases/latest/download/gopher64-macos-aarch64.zip)
|
||||
* Homebrew: `brew install --cask gopher64`
|
||||
|
||||
Linux:
|
||||
* Standalone executable: [gopher64-linux-x86_64](https://github.com/gopher64/gopher64/releases/latest/download/gopher64-linux-x86_64)
|
||||
* Flatpak: `flatpak install flathub io.github.gopher64.gopher64`
|
||||
|
||||
Android:
|
||||
* APK: [gopher64-android.apk](https://github.com/gopher64/gopher64/releases/latest/download/gopher64-android.apk)
|
||||
|
||||
## wiki
|
||||
|
||||
@@ -11,7 +33,7 @@ https://github.com/gopher64/gopher64/wiki
|
||||
|
||||
## discord
|
||||
|
||||
https://discord.gg/9RGXq8W8JQ
|
||||
[Discord invite link](https://discord.gg/9RGXq8W8JQ)
|
||||
|
||||
## controls
|
||||
|
||||
@@ -19,7 +41,9 @@ Keys are mapped according to [these defaults](https://github.com/gopher64/gopher
|
||||
|
||||
## netplay
|
||||
|
||||
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.
|
||||
Gopher64 supports P2P netplay (online play with others).
|
||||
|
||||
Please read the [guide](https://github.com/gopher64/gopher64/wiki/Netplay-Guide) before trying out netplay.
|
||||
|
||||
## portable mode
|
||||
|
||||
@@ -35,8 +59,8 @@ flatpak run --filesystem=host:ro io.github.gopher64.gopher64 /path/to/rom.z64
|
||||
|
||||
## building and usage
|
||||
|
||||
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
|
||||
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`
|
||||
@@ -44,14 +68,18 @@ flatpak run --filesystem=host:ro io.github.gopher64.gopher64 /path/to/rom.z64
|
||||
|
||||
## contributing
|
||||
|
||||
I am very open to contributions! Please contact me via a GitHub issue or Discord (loganmc10) before doing substantial work on a PR.
|
||||
PRs that are vibe coded and/or co-authored by AI agents (Plan/Agent mode) are not permitted. If you are going to submit a PR, it should be scoped to a single feature/improvement.
|
||||
|
||||
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).
|
||||
|
||||
## privacy and code signing policy
|
||||
|
||||
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).
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
jniLibs/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
keystore.properties
|
||||
|
||||
# Log/OS Files
|
||||
*.log
|
||||
|
||||
# Android Studio generated files and folders
|
||||
captures/
|
||||
.externalNativeBuild/
|
||||
.cxx/
|
||||
*.aab
|
||||
*.apk
|
||||
output-metadata.json
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/
|
||||
misc.xml
|
||||
deploymentTargetDropDown.xml
|
||||
render.experimental.xml
|
||||
|
||||
# Keystore files
|
||||
*.jks
|
||||
*.keystore
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
google-services.json
|
||||
|
||||
# Android Profiling
|
||||
*.hprof
|
||||
|
||||
app/src/main/java/org/libsdl/app/
|
||||
@@ -0,0 +1,4 @@
|
||||
set(ANDROID_PLATFORM $ENV{ANDROID_PLATFORM})
|
||||
set(ANDROID_ABI $ENV{ANDROID_ABI})
|
||||
set(ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES ON)
|
||||
include("$ENV{ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake")
|
||||
@@ -0,0 +1,161 @@
|
||||
import groovy.json.JsonSlurper
|
||||
import java.util.Properties
|
||||
import java.io.FileInputStream
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
}
|
||||
|
||||
val keystorePropertiesFile = rootProject.file("keystore.properties")
|
||||
val keystoreProperties = Properties()
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.github.gopher64.gopher64"
|
||||
compileSdk {
|
||||
version = release(36) {
|
||||
minorApiLevel = 1
|
||||
}
|
||||
}
|
||||
|
||||
ndkVersion = "29.0.14206865"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "io.github.gopher64.gopher64"
|
||||
minSdk = 33
|
||||
targetSdk = 36
|
||||
versionCode = semverToVersionCode(cargoPackageVersion())
|
||||
versionName = cargoPackageVersion()
|
||||
ndk {
|
||||
abiFilters.addAll(listOf("arm64-v8a", "x86_64"))
|
||||
}
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
create("release") {
|
||||
keyAlias = keystoreProperties["keyAlias"] as String
|
||||
keyPassword = keystoreProperties["keyPassword"] as String
|
||||
storeFile = file(keystoreProperties["storeFile"] as String)
|
||||
storePassword = keystoreProperties["storePassword"] as String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
signingConfig = signingConfigs["release"]
|
||||
}
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
packaging {
|
||||
jniLibs {
|
||||
excludes.add("lib/**/libsevenz_rust2*.so")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun cargoPackageVersion(packageName: String = "gopher64"): String {
|
||||
val repoRoot = rootDir.parentFile
|
||||
val jsonText = providers.exec {
|
||||
workingDir(repoRoot)
|
||||
commandLine("cargo", "metadata", "--format-version", "1", "--no-deps")
|
||||
}.standardOutput.asText.get()
|
||||
|
||||
val parsedJson = JsonSlurper().parseText(jsonText) as Map<String, Any>
|
||||
val packages = parsedJson["packages"] as List<Map<String, Any>>
|
||||
return packages.first { it["name"] == packageName }["version"] as String
|
||||
}
|
||||
|
||||
fun semverToVersionCode(version: String): Int {
|
||||
val parts = version.substringBefore('-').split(".")
|
||||
val (major, minor, patch) = Triple(parts[0].toInt(), parts[1].toInt(), parts[2].toInt())
|
||||
|
||||
return major * 1_000_000 + minor * 1_000 + patch
|
||||
}
|
||||
|
||||
val ndkBuild = tasks.register<Exec>("ndkBuild") {
|
||||
val isRelease = gradle.startParameter.taskNames.any { it.endsWith("Release", ignoreCase = true) }
|
||||
workingDir = rootDir.parentFile
|
||||
val toolchainPath = "$rootDir/android.toolchain.cmake"
|
||||
environment("CMAKE_TOOLCHAIN_FILE", toolchainPath)
|
||||
|
||||
var minSdk = android.defaultConfig.minSdk
|
||||
var ndkDir = androidComponents.sdkComponents.ndkDirectory.get().asFile.absolutePath
|
||||
environment("ANDROID_NDK_HOME", "$ndkDir")
|
||||
environment("ANDROID_NDK_ROOT", "$ndkDir")
|
||||
environment("LIBCLANG_PATH", "$ndkDir/toolchains/llvm/prebuilt/linux-x86_64/musl/lib")
|
||||
|
||||
val jniType = if (isRelease) "release" else "debug"
|
||||
val jniLibsFolder = "$rootDir/app/src/$jniType/jniLibs"
|
||||
|
||||
commandLine(
|
||||
"cargo", "ndk",
|
||||
"--link-libcxx-shared",
|
||||
"-P", "$minSdk",
|
||||
"-t", "arm64-v8a",
|
||||
"-t", "x86_64",
|
||||
"-o", jniLibsFolder,
|
||||
"build", "--lib",
|
||||
"--profile", if (isRelease) "release" else "dev",
|
||||
)
|
||||
}
|
||||
|
||||
val sdlLibsArm64 = tasks.register<Copy>("sdlLibsArm64") {
|
||||
val isRelease = gradle.startParameter.taskNames.any { it.endsWith("Release", ignoreCase = true) }
|
||||
val jniType = if (isRelease) "release" else "debug"
|
||||
val jniLibsFolder = "$rootDir/app/src/$jniType/jniLibs/arm64-v8a"
|
||||
|
||||
from("$rootDir/../target/aarch64-linux-android/$jniType")
|
||||
into(jniLibsFolder)
|
||||
include("libSDL*")
|
||||
}
|
||||
|
||||
val sdlLibsX64 = tasks.register<Copy>("sdlLibsX64") {
|
||||
val isRelease = gradle.startParameter.taskNames.any { it.endsWith("Release", ignoreCase = true) }
|
||||
val jniType = if (isRelease) "release" else "debug"
|
||||
val jniLibsFolder = "$rootDir/app/src/$jniType/jniLibs/x86_64"
|
||||
|
||||
from("$rootDir/../target/x86_64-linux-android/$jniType")
|
||||
into(jniLibsFolder)
|
||||
include("libSDL*")
|
||||
}
|
||||
|
||||
tasks.named("preBuild") {
|
||||
dependsOn(sdlLibsArm64)
|
||||
dependsOn(sdlLibsX64)
|
||||
}
|
||||
|
||||
tasks.named("sdlLibsArm64") {
|
||||
dependsOn(ndkBuild)
|
||||
}
|
||||
|
||||
tasks.named("sdlLibsX64") {
|
||||
dependsOn(ndkBuild)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.material)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLActivity {
|
||||
java.lang.String nativeGetHint(java.lang.String); # Java-side doesn't use this, so it gets minified, but C-side still tries to register it
|
||||
java.lang.String clipboardGetText();
|
||||
boolean clipboardHasText();
|
||||
void clipboardSetText(java.lang.String);
|
||||
int createCustomCursor(int[], int, int, int, int);
|
||||
void destroyCustomCursor(int);
|
||||
android.app.Activity getContext();
|
||||
boolean getManifestEnvironmentVariables();
|
||||
android.view.Surface getNativeSurface();
|
||||
void initTouch();
|
||||
boolean isAndroidTV();
|
||||
boolean isChromebook();
|
||||
boolean isDeXMode();
|
||||
boolean isTablet();
|
||||
void manualBackButton();
|
||||
int messageboxShowMessageBox(int, java.lang.String, java.lang.String, int[], int[], java.lang.String[], int[]);
|
||||
void minimizeWindow();
|
||||
boolean openURL(java.lang.String);
|
||||
void requestPermission(java.lang.String, int);
|
||||
boolean showToast(java.lang.String, int, int, int, int);
|
||||
boolean sendMessage(int, int);
|
||||
boolean setActivityTitle(java.lang.String);
|
||||
boolean setCustomCursor(int);
|
||||
void setOrientation(int, int, boolean, java.lang.String);
|
||||
boolean setRelativeMouseEnabled(boolean);
|
||||
boolean setSystemCursor(int);
|
||||
void setWindowStyle(boolean);
|
||||
boolean shouldMinimizeOnFocusLoss();
|
||||
boolean showTextInput(int, int, int, int, int);
|
||||
boolean supportsRelativeMouse();
|
||||
int openFileDescriptor(java.lang.String, java.lang.String);
|
||||
boolean showFileDialog(java.lang.String[], boolean, boolean, int);
|
||||
java.lang.String getPreferredLocales();
|
||||
java.lang.String formatLocale(java.util.Locale);
|
||||
}
|
||||
|
||||
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.HIDDeviceManager {
|
||||
void closeDevice(int);
|
||||
boolean initialize(boolean, boolean);
|
||||
boolean openDevice(int);
|
||||
boolean readReport(int, byte[], boolean);
|
||||
int writeReport(int, byte[], boolean);
|
||||
}
|
||||
|
||||
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLAudioManager {
|
||||
void registerAudioDeviceCallback();
|
||||
void unregisterAudioDeviceCallback();
|
||||
void audioSetThreadPriority(boolean, int);
|
||||
}
|
||||
|
||||
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLControllerManager {
|
||||
void joystickSetSensorsEnabled(int, boolean);
|
||||
void pollInputDevices();
|
||||
void joystickSetLED(int, int, int, int);
|
||||
void pollHapticDevices();
|
||||
void hapticRun(int, float, int);
|
||||
void hapticRumble(int, float, float, int);
|
||||
void hapticStop(int);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-feature android:name="android.hardware.vulkan.version" android:required="true"
|
||||
android:version="0x00401000" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.bluetooth"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.gamepad"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.usb.host"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="Gopher64"
|
||||
android:appCategory="game"
|
||||
android:hardwareAccelerated="true">
|
||||
<activity android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
||||
android:name=".SlintActivity" android:exported="true">
|
||||
<meta-data android:name="android.app.lib_name" android:value="gopher64" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".N64Activity"
|
||||
android:launchMode="singleTop"
|
||||
android:configChanges="layoutDirection|locale|grammaticalGender|fontScale|fontWeightAdjustment|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
|
||||
android:preferMinimalPostProcessing="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,48 @@
|
||||
package io.github.gopher64.gopher64
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.PowerManager
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import org.libsdl.app.SDLActivity
|
||||
|
||||
class N64Activity : SDLActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val powerManager = getContext().getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
|
||||
if (powerManager.isSustainedPerformanceModeSupported) {
|
||||
Log.v("SDL", "Enabling sustained performance mode")
|
||||
window.setSustainedPerformanceMode(true)
|
||||
} else {
|
||||
Log.v("SDL", "Sustained performance mode not supported")
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLibraries(): Array<String> = arrayOf(
|
||||
"SDL3",
|
||||
"SDL3_ttf",
|
||||
"gopher64"
|
||||
)
|
||||
|
||||
override fun getMainFunction(): String = "gopher64_sdl_main"
|
||||
|
||||
override fun getArguments(): Array<String> {
|
||||
val intent = intent ?: return super.getArguments()
|
||||
val args = intent.getStringArrayExtra("args") ?: return super.getArguments()
|
||||
|
||||
val dataIntent = Intent()
|
||||
val file_path = intent.getStringExtra("file_path")
|
||||
if (file_path != null) {
|
||||
dataIntent.putExtra("file_path", file_path)
|
||||
}
|
||||
val cheats_path = intent.getStringExtra("cheats_path")
|
||||
if (cheats_path != null) {
|
||||
dataIntent.putExtra("cheats_path", cheats_path)
|
||||
}
|
||||
setResult(RESULT_OK, dataIntent)
|
||||
return args
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package io.github.gopher64.gopher64
|
||||
|
||||
import android.app.NativeActivity
|
||||
import android.content.Intent
|
||||
|
||||
class SlintActivity : NativeActivity() {
|
||||
companion object {
|
||||
init {
|
||||
System.loadLibrary("gopher64")
|
||||
}
|
||||
}
|
||||
private external fun nativeOnActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
||||
nativeOnActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 47 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#419567</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,4 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. For more details, visit
|
||||
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
|
||||
# org.gradle.parallel=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
@@ -0,0 +1,12 @@
|
||||
#This file is generated by updateDaemonJvm
|
||||
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect
|
||||
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect
|
||||
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect
|
||||
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect
|
||||
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/73bcfb608d1fde9fb62e462f834a3299/redirect
|
||||
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/846ee0d876d26a26f37aa1ce8de73224/redirect
|
||||
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect
|
||||
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect
|
||||
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/9482ddec596298c84656d31d16652665/redirect
|
||||
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/39701d92e1756bb2f141eb67cd4c660e/redirect
|
||||
toolchainVersion=21
|
||||
@@ -0,0 +1,20 @@
|
||||
[versions]
|
||||
agp = "9.2.1"
|
||||
coreKtx = "1.18.0"
|
||||
junit = "4.13.2"
|
||||
junitVersion = "1.3.0"
|
||||
espressoCore = "3.7.0"
|
||||
appcompat = "1.7.1"
|
||||
material = "1.14.0"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#Thu May 21 08:40:22 CEST 2026
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=2ab2958f2a1e51120c326cad6f385153bb11ee93b3c216c5fccebfdfbb7ec6cb
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -0,0 +1,251 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
@@ -0,0 +1,94 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -0,0 +1,26 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google {
|
||||
content {
|
||||
includeGroupByRegex("com\\.android.*")
|
||||
includeGroupByRegex("com\\.google.*")
|
||||
includeGroupByRegex("androidx.*")
|
||||
}
|
||||
}
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
plugins {
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "Gopher64"
|
||||
include(":app")
|
||||
@@ -1,9 +1,16 @@
|
||||
fn main() {
|
||||
println!("cargo::rerun-if-changed=parallel-rdp");
|
||||
println!("cargo::rerun-if-changed=retroachievements");
|
||||
println!("cargo::rerun-if-changed=src/compat");
|
||||
println!("cargo::rerun-if-changed=data/translations");
|
||||
println!("cargo::rerun-if-changed=data/ui");
|
||||
|
||||
let slint_config = slint_build::CompilerConfiguration::new().with_style("cosmic".into());
|
||||
slint_build::compile_with_config("src/ui/gui/appwindow.slint", slint_config).unwrap();
|
||||
#[cfg(feature = "gui")]
|
||||
{
|
||||
let slint_config = slint_build::CompilerConfiguration::new()
|
||||
.with_bundled_translations("data/translations");
|
||||
slint_build::compile_with_config("src/ui/gui/appwindow.slint", slint_config).unwrap();
|
||||
}
|
||||
|
||||
let mut simd_build = cc::Build::new();
|
||||
let mut volk_build = cc::Build::new();
|
||||
@@ -14,7 +21,7 @@ fn main() {
|
||||
let mut rdp_build = cc::Build::new();
|
||||
rdp_build
|
||||
.cpp(true)
|
||||
.std("c++23")
|
||||
.std("c++20")
|
||||
.flag("-Wno-unused-parameter")
|
||||
.flag("-Wno-missing-field-initializers")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/parallel-rdp/command_ring.cpp")
|
||||
@@ -36,7 +43,6 @@ fn main() {
|
||||
.file("parallel-rdp/parallel-rdp-standalone/vulkan/image.cpp")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/vulkan/indirect_layout.cpp")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/vulkan/memory_allocator.cpp")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/vulkan/pipeline_cache.cpp")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/vulkan/pipeline_event.cpp")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/vulkan/query_pool.cpp")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/vulkan/render_pass.cpp")
|
||||
@@ -62,20 +68,55 @@ 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())
|
||||
.join("include"),
|
||||
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").to_owned().unwrap())
|
||||
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();
|
||||
let profile = std::env::var("PROFILE").unwrap();
|
||||
let opt_flag = if arch == "x86_64" {
|
||||
"-march=x86-64-v3"
|
||||
} else if arch == "aarch64" {
|
||||
} else if arch == "aarch64" && os == "macos" {
|
||||
"-march=armv8.4-a"
|
||||
} else if arch == "aarch64" && os != "macos" {
|
||||
"-march=armv8.2-a"
|
||||
} else {
|
||||
panic!("unknown arch")
|
||||
@@ -84,6 +125,7 @@ fn main() {
|
||||
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");
|
||||
@@ -93,31 +135,80 @@ fn main() {
|
||||
.set_icon("data/icon/icon.ico")
|
||||
.compile()
|
||||
.unwrap();
|
||||
} else if os == "macos" {
|
||||
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 profile == "release" {
|
||||
volk_build.flag("-flto=thin");
|
||||
rdp_build.flag("-flto=thin");
|
||||
simd_build.flag("-flto=thin");
|
||||
}
|
||||
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_rdram")
|
||||
.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")
|
||||
.allowlist_function("ra_get_rich_presence");
|
||||
|
||||
if cfg!(feature = "gui") {
|
||||
retroachievements_builder = retroachievements_builder
|
||||
.allowlist_function("ra_logout_user")
|
||||
.allowlist_function("ra_get_username");
|
||||
}
|
||||
|
||||
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_idle")
|
||||
.allowlist_function("rdp_set_fps")
|
||||
.allowlist_function("get_joystick_event")
|
||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
|
||||
.generate()
|
||||
.expect("Unable to generate bindings");
|
||||
@@ -162,6 +253,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)
|
||||
@@ -183,13 +275,60 @@ fn main() {
|
||||
.compile("simd");
|
||||
}
|
||||
|
||||
let git_output = std::process::Command::new("git")
|
||||
.args(["rev-parse", "HEAD"])
|
||||
.output()
|
||||
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");
|
||||
}
|
||||
if os == "android" {
|
||||
copy_dir_all(
|
||||
std::path::Path::new(sdl3_src::SOURCE_DIR)
|
||||
.join("android-project/app/src/main/java/org/libsdl/app"),
|
||||
"android-project/app/src/main/java/org/libsdl/app",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let git_hash = String::from_utf8(git_output.stdout).unwrap();
|
||||
println!("cargo:rustc-env=GIT_HASH={git_hash}");
|
||||
|
||||
println!("cargo:rustc-env=N64_STACK_SIZE={}", 8 * 1024 * 1024);
|
||||
}
|
||||
}
|
||||
|
||||
fn copy_dir_all(
|
||||
src: impl AsRef<std::path::Path>,
|
||||
dst: impl AsRef<std::path::Path>,
|
||||
) -> std::io::Result<()> {
|
||||
std::fs::create_dir_all(&dst)?;
|
||||
for entry in std::fs::read_dir(src)? {
|
||||
let entry = entry?;
|
||||
let ty = entry.file_type()?;
|
||||
if ty.is_dir() {
|
||||
copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
|
||||
} else {
|
||||
std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -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,26 @@
|
||||
# Adding translations to Gopher64
|
||||
1. Copy and modify the `gopher64.pot` file in this directory with your translations.
|
||||
2. Rename it to `gopher64.po` and put it here:
|
||||
`data/translations/<lang>/LC_MESSAGES/gopher64.po`
|
||||
|
||||
See [here](https://docs.slint.dev/latest/docs/slint/guide/development/translations/#translating-strings) for more details on creating a .po file. I recommend using a program like [poedit](https://poedit.com) to create the .po file from the template.
|
||||
|
||||
---
|
||||
|
||||
For example, in the .pot file you will see:
|
||||
```
|
||||
#: src/ui/gui/about_page.slint:17
|
||||
msgctxt "NewVersionButton"
|
||||
msgid "New version available! Click here to download"
|
||||
msgstr ""
|
||||
```
|
||||
|
||||
A translation will look something like this:
|
||||
```
|
||||
#: src/ui/gui/about_page.slint:17
|
||||
msgctxt "NewVersionButton"
|
||||
msgid "New version available! Click here to download"
|
||||
msgstr "Nova versão disponível! Clique aqui para baixar"
|
||||
```
|
||||
|
||||
The only line you modify is the `msgstr`. The `msgctxt` and `msgid` need to remain unchanged.
|
||||
@@ -0,0 +1,620 @@
|
||||
# gopher64 - German Translation
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: gopher64\n"
|
||||
"POT-Creation-Date: 2026-06-30 09:43+0000\n"
|
||||
"PO-Revision-Date: 2026-05-11 12:00+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"Language: de\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: src/ui/gui/about_page.slint:17
|
||||
msgctxt "NewVersionButton"
|
||||
msgid "New version available! Click here to download"
|
||||
msgstr "Neue Version verfügbar! Zum Herunterladen hier klicken"
|
||||
|
||||
#: src/ui/gui/about_page.slint:33
|
||||
msgctxt "About"
|
||||
msgid "About"
|
||||
msgstr "Über"
|
||||
|
||||
#: src/ui/gui/about_page.slint:40
|
||||
msgctxt "About"
|
||||
msgid "Wiki"
|
||||
msgstr "Wiki"
|
||||
|
||||
#: src/ui/gui/about_page.slint:50
|
||||
msgctxt "About"
|
||||
msgid "Discord Server"
|
||||
msgstr "Discord-Server"
|
||||
|
||||
#: src/ui/gui/about_page.slint:60
|
||||
msgctxt "About"
|
||||
msgid "Source Code"
|
||||
msgstr "Quellcode"
|
||||
|
||||
#: src/ui/gui/about_page.slint:70
|
||||
msgctxt "About"
|
||||
msgid "Donate (via Patreon)"
|
||||
msgstr "Spenden (über Patreon)"
|
||||
|
||||
#: src/ui/gui/about_page.slint:77
|
||||
msgctxt "About"
|
||||
msgid "Donate (via GitHub)"
|
||||
msgstr "Spenden (über GitHub)"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:164
|
||||
msgctxt "Menu"
|
||||
msgid "Local Game"
|
||||
msgstr "Lokales Spiel"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:168
|
||||
msgctxt "Menu"
|
||||
msgid "Controller Config"
|
||||
msgstr "Controller-Konfiguration"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:172
|
||||
msgctxt "Menu"
|
||||
msgid "Netplay"
|
||||
msgstr "Netplay"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:176
|
||||
msgctxt "Menu"
|
||||
msgid "Cheats"
|
||||
msgstr "Cheats"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:180
|
||||
msgctxt "Menu"
|
||||
msgid "RetroAchievements"
|
||||
msgstr "RetroAchievements"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:184
|
||||
msgctxt "Menu"
|
||||
msgid "Settings"
|
||||
msgstr "Einstellungen"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:188
|
||||
msgctxt "Menu"
|
||||
msgid "About"
|
||||
msgstr "Über"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:15
|
||||
msgctxt "Cheats"
|
||||
msgid "Cheats"
|
||||
msgstr "Cheats"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:25
|
||||
msgctxt "Cheats"
|
||||
msgid "Select ROM"
|
||||
msgstr "ROM auswählen"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:41
|
||||
msgctxt "Cheats"
|
||||
msgid "Clear Cheats For All ROMs"
|
||||
msgstr "Cheats für alle ROMs löschen"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:83
|
||||
msgctxt "Cheats"
|
||||
msgid "No Cheats Available"
|
||||
msgstr "Keine Cheats verfügbar"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:80
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Enabled"
|
||||
msgstr "Aktiviert"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:93
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Transfer Pak"
|
||||
msgstr "Transfer Pak"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:107
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Profile"
|
||||
msgstr "Profil"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:157
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Configuration"
|
||||
msgstr "Controller-Konfiguration"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:171
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Configure Input Profile"
|
||||
msgstr "Eingabeprofil konfigurieren"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:180
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Settings"
|
||||
msgstr "Controller-Einstellungen"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:190
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Assignment"
|
||||
msgstr "Controller-Zuweisung"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:12
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Create/Modify Input Profile"
|
||||
msgstr "Eingabeprofil erstellen/bearbeiten"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:25
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Enter profile name to create or modify"
|
||||
msgstr "Profilname zum Erstellen oder Bearbeiten eingeben"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:38
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Deadzone Percentage"
|
||||
msgstr "Deadzone Größe in Prozent"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:61
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Use DirectInput"
|
||||
msgstr "DirectInput verwenden"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:74
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Start Profile Creation Wizard"
|
||||
msgstr "Assistent zur Profilerstellung starten"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:82
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Cancel"
|
||||
msgstr "Abbrechen"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:19
|
||||
msgctxt "LocalGame"
|
||||
msgid "Local Game"
|
||||
msgstr "Lokales Spiel"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:26
|
||||
msgctxt "LocalGame"
|
||||
msgid "Open ROM"
|
||||
msgstr "ROM öffnen"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:37
|
||||
msgctxt "LocalGame"
|
||||
msgid "Open Saves Folder"
|
||||
msgstr "Speicherordner öffnen"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:54
|
||||
msgctxt "LocalGame"
|
||||
msgid "Recent ROMs"
|
||||
msgstr "Zuletzt verwendete ROMs"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:7
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Netplay: Create Session"
|
||||
msgstr "Netplay: Sitzung erstellen"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:22
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Session Name:"
|
||||
msgstr "Sitzungsname:"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:26
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter session name"
|
||||
msgstr "Sitzungsname eingeben"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:36
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Password (Optional):"
|
||||
msgstr "Passwort (optional):"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:40
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter session password"
|
||||
msgstr "Sitzungspasswort eingeben"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:51
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Player Name:"
|
||||
msgstr "Spielername:"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:55
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter player name"
|
||||
msgstr "Spielername eingeben"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:66
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Select ROM"
|
||||
msgstr "ROM auswählen"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:82
|
||||
msgctxt "NetplayCreate"
|
||||
msgid ""
|
||||
"This game has cheats enabled. Cheats will be synchronized with other players."
|
||||
msgstr ""
|
||||
"Für dieses Spiel sind Cheats aktiviert. Cheats werden mit anderen Spielern "
|
||||
"synchronisiert."
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:91
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Create Session"
|
||||
msgstr "Sitzung erstellen"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:107
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Cancel"
|
||||
msgstr "Abbrechen"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:7
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Netplay: Join Session"
|
||||
msgstr "Netplay: Sitzung beitreten"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:18
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Player Name (required):"
|
||||
msgstr "Spielername (erforderlich):"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:22
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Enter player name"
|
||||
msgstr "Spielername eingeben"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:31
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Password (if required)"
|
||||
msgstr "Passwort (falls erforderlich)"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:42
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Select ROM"
|
||||
msgstr "ROM auswählen"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:57
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Refreshing session list, please wait..."
|
||||
msgstr "Sitzungsliste wird aktualisiert, bitte warten …"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:62
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Click on a session below to select it"
|
||||
msgstr "Auf eine Sitzung klicken um diese auszuwählen"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:67
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "No sessions available. Please refresh to check again."
|
||||
msgstr "Keine Sitzungen verfügbar. Bitte aktualisieren, um erneut zu prüfen."
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:73
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Session Name"
|
||||
msgstr "Sitzungsname"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:74
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Game Name"
|
||||
msgstr "Spielname"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:75
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Password"
|
||||
msgstr "Passwort"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:76
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Cheats"
|
||||
msgstr "Cheats"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:90
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Refresh Session List"
|
||||
msgstr "Sitzungsliste aktualisieren"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:105
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Join Session"
|
||||
msgstr "Sitzung beitreten"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:117
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Cancel"
|
||||
msgstr "Abbrechen"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:31
|
||||
msgctxt "Netplay"
|
||||
msgid "Netplay"
|
||||
msgstr "Netplay"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:39
|
||||
msgctxt "Netplay"
|
||||
msgid "Create Session"
|
||||
msgstr "Sitzung erstellen"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:50
|
||||
msgctxt "Netplay"
|
||||
msgid "Join Session"
|
||||
msgstr "Sitzung beitreten"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:64
|
||||
msgctxt "Netplay"
|
||||
msgid "Netplay Discord Channel"
|
||||
msgstr "Netplay-Discord-Kanal"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:71
|
||||
msgctxt "Netplay"
|
||||
msgid "Provide Feedback"
|
||||
msgstr "Feedback geben"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:17
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Netplay: Waiting Room"
|
||||
msgstr "Netplay: Warteraum"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:29
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Session Name:"
|
||||
msgstr "Sitzungsname:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:35
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Game Name:"
|
||||
msgstr "Spielname:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:45
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 1:"
|
||||
msgstr "Spieler 1:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:49
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 2:"
|
||||
msgstr "Spieler 2:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:55
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 3:"
|
||||
msgstr "Spieler 3:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:59
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 4:"
|
||||
msgstr "Spieler 4:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:73
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Enter chat message"
|
||||
msgstr "Chatnachricht eingeben"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:84
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Send Message"
|
||||
msgstr "Nachricht senden"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:94
|
||||
msgctxt "NetplayWait"
|
||||
msgid ""
|
||||
"Setting the input delay too low will cause the game to lag.\n"
|
||||
"You can lower the input delay in-game by pressing Alt+[ and raise it with "
|
||||
"Alt+]."
|
||||
msgstr ""
|
||||
"Eine zu niedrige Eingabeverzögerung führt zu Ruckeln im Spiel.\n"
|
||||
"Sie können die Eingabeverzögerung im Spiel mit Alt+[ verringern und mit "
|
||||
"Alt+] erhöhen."
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:101
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Local Input Delay:"
|
||||
msgstr "Lokale Eingabeverzögerung:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:118
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Recommended: "
|
||||
msgstr "Empfohlen: "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Start Game"
|
||||
msgstr "Spiel starten"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Waiting for P2P mesh "
|
||||
msgstr "Warten auf P2P-Mesh "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:135
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Cancel"
|
||||
msgstr "Abbrechen"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:19
|
||||
msgctxt "RAData"
|
||||
msgid "Not currently logged in"
|
||||
msgstr "Derzeit nicht angemeldet"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:27
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "RetroAchievements"
|
||||
msgstr "RetroAchievements"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:36
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Enabled"
|
||||
msgstr "Aktiviert"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:51
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Hardcore mode"
|
||||
msgstr "Hardcore-Modus"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:60
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Challenge Indicators"
|
||||
msgstr "Challenge Tipps"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:69
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Leaderboard Trackers"
|
||||
msgstr "Bestenlisten-Tracker"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:78
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Discord Rich Presence"
|
||||
msgstr "Rich Presence für Discord"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:96
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "View Profile"
|
||||
msgstr "Profil anzeigen"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:106
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Username"
|
||||
msgstr "Benutzername"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:116
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Password"
|
||||
msgstr "Passwort"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:125
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Login"
|
||||
msgstr "Anmelden"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:141
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "View Supported Games"
|
||||
msgstr "Unterstützte Spiele anzeigen"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:25
|
||||
msgctxt "Settings"
|
||||
msgid "Settings"
|
||||
msgstr "Einstellungen"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:32
|
||||
msgctxt "Settings"
|
||||
msgid "Game"
|
||||
msgstr "Spiel"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:38
|
||||
msgctxt "Settings"
|
||||
msgid "Resolution:"
|
||||
msgstr "Auflösung:"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:51
|
||||
msgctxt "Settings"
|
||||
msgid "SSAA downscaling"
|
||||
msgstr "SSAA-Herunterskalierung"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:59
|
||||
msgctxt "Settings"
|
||||
msgid "Integer Scaling"
|
||||
msgstr "Pixelgenaue Skalierung"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:67
|
||||
msgctxt "Settings"
|
||||
msgid "Fullscreen"
|
||||
msgstr "Vollbild"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:75
|
||||
msgctxt "Settings"
|
||||
msgid "Widescreen (stretch)"
|
||||
msgstr "16:9 (gestreckt)"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:83
|
||||
msgctxt "Settings"
|
||||
msgid "VSync"
|
||||
msgstr "VSync"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:91
|
||||
msgctxt "Settings"
|
||||
msgid "Apply CRT shader"
|
||||
msgstr "CRT-Shader anwenden"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:103
|
||||
msgctxt "Settings"
|
||||
msgid "Overclock N64 CPU (may cause bugs)"
|
||||
msgstr "N64-CPU übertakten (kann Fehler verursachen)"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:111
|
||||
msgctxt "Settings"
|
||||
msgid "Disable Expansion Pak"
|
||||
msgstr "Expansion Pak deaktivieren"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:119
|
||||
msgctxt "Settings"
|
||||
msgid "Enable Rewind"
|
||||
msgstr "Rewind aktivieren"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:127
|
||||
msgctxt "Settings"
|
||||
msgid "Emulate UNFLoader"
|
||||
msgstr "UNFLoader emulieren"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:135
|
||||
msgctxt "Settings"
|
||||
msgid "Emulate VRU"
|
||||
msgstr "VRU emulieren"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:144
|
||||
msgctxt "Settings"
|
||||
msgid "UI"
|
||||
msgstr "Benutzeroberfläche"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:150
|
||||
msgctxt "Settings"
|
||||
msgid "Theme:"
|
||||
msgstr "Thema:"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "System"
|
||||
msgstr "System"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "Dark"
|
||||
msgstr "Dunkel"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "Light"
|
||||
msgstr "Hell"
|
||||
|
||||
#: src/ui/gui/vru_dialog.slint:7
|
||||
msgctxt "VruDialog"
|
||||
msgid "What Would You Like To Say?"
|
||||
msgstr "Was möchten Sie sagen?"
|
||||
|
||||
#~ msgctxt "NetplayCreate"
|
||||
#~ msgid "Server:"
|
||||
#~ msgstr "Server:"
|
||||
|
||||
#~ msgctxt "CustomServerDialog"
|
||||
#~ msgid "Enter a custom server URL"
|
||||
#~ msgstr "Eigene Server-URL eingeben"
|
||||
|
||||
#~ msgctxt "NetplayJoin"
|
||||
#~ msgid "Custom Server URL (optional):"
|
||||
#~ msgstr "Eigene Server-URL (optional):"
|
||||
|
||||
#~ msgctxt "NetplayJoin"
|
||||
#~ msgid "Server"
|
||||
#~ msgstr "Server"
|
||||
|
||||
#~ msgctxt "NetplayWait"
|
||||
#~ msgid "Ping:"
|
||||
#~ msgstr "Ping:"
|
||||
|
||||
#~ msgctxt "NetplayCreate"
|
||||
#~ msgid "Creating server, please wait...This may take about 30 seconds."
|
||||
#~ msgstr ""
|
||||
#~ "Server wird erstellt, bitte warten … Dies kann etwa 30 Sekunden dauern."
|
||||
|
||||
#~ msgctxt "ControllerConfig"
|
||||
#~ msgid "Port"
|
||||
#~ msgstr "Port"
|
||||
|
||||
#~ msgctxt "ControllerConfig"
|
||||
#~ msgid "Controller"
|
||||
#~ msgstr "Controller"
|
||||
@@ -0,0 +1,620 @@
|
||||
# gopher64 - Spanish Translation
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: gopher64\n"
|
||||
"POT-Creation-Date: 2026-06-30 09:43+0000\n"
|
||||
"PO-Revision-Date: 2026-05-11 12:00+0000\n"
|
||||
"Last-Translator: Gemini\n"
|
||||
"Language-Team: Spanish (Spain)\n"
|
||||
"Language: es_ES\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: src/ui/gui/about_page.slint:17
|
||||
msgctxt "NewVersionButton"
|
||||
msgid "New version available! Click here to download"
|
||||
msgstr "¡Nueva versión disponible! Pulsa aquí para descargar"
|
||||
|
||||
#: src/ui/gui/about_page.slint:33
|
||||
msgctxt "About"
|
||||
msgid "About"
|
||||
msgstr "Acerca de"
|
||||
|
||||
#: src/ui/gui/about_page.slint:40
|
||||
msgctxt "About"
|
||||
msgid "Wiki"
|
||||
msgstr "Wiki"
|
||||
|
||||
#: src/ui/gui/about_page.slint:50
|
||||
msgctxt "About"
|
||||
msgid "Discord Server"
|
||||
msgstr "Servidor de Discord"
|
||||
|
||||
#: src/ui/gui/about_page.slint:60
|
||||
msgctxt "About"
|
||||
msgid "Source Code"
|
||||
msgstr "Código fuente"
|
||||
|
||||
#: src/ui/gui/about_page.slint:70
|
||||
msgctxt "About"
|
||||
msgid "Donate (via Patreon)"
|
||||
msgstr "Donar (a través de Patreon)"
|
||||
|
||||
#: src/ui/gui/about_page.slint:77
|
||||
msgctxt "About"
|
||||
msgid "Donate (via GitHub)"
|
||||
msgstr "Donar (a través de GitHub)"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:164
|
||||
msgctxt "Menu"
|
||||
msgid "Local Game"
|
||||
msgstr "Juego local"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:168
|
||||
msgctxt "Menu"
|
||||
msgid "Controller Config"
|
||||
msgstr "Config. del mando"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:172
|
||||
msgctxt "Menu"
|
||||
msgid "Netplay"
|
||||
msgstr "Netplay"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:176
|
||||
msgctxt "Menu"
|
||||
msgid "Cheats"
|
||||
msgstr "Trucos"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:180
|
||||
msgctxt "Menu"
|
||||
msgid "RetroAchievements"
|
||||
msgstr "RetroAchievements"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:184
|
||||
msgctxt "Menu"
|
||||
msgid "Settings"
|
||||
msgstr "Ajustes"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:188
|
||||
msgctxt "Menu"
|
||||
msgid "About"
|
||||
msgstr "Acerca de"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:15
|
||||
msgctxt "Cheats"
|
||||
msgid "Cheats"
|
||||
msgstr "Trucos"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:25
|
||||
msgctxt "Cheats"
|
||||
msgid "Select ROM"
|
||||
msgstr "Seleccionar ROM"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:41
|
||||
msgctxt "Cheats"
|
||||
msgid "Clear Cheats For All ROMs"
|
||||
msgstr "Borrar trucos de todas las ROMs"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:83
|
||||
msgctxt "Cheats"
|
||||
msgid "No Cheats Available"
|
||||
msgstr "No hay trucos disponibles"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:80
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Enabled"
|
||||
msgstr "Activado"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:93
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Transfer Pak"
|
||||
msgstr "Transfer Pak"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:107
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Profile"
|
||||
msgstr "Perfil"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:157
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Configuration"
|
||||
msgstr "Configuración del mando"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:171
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Configure Input Profile"
|
||||
msgstr "Configurar perfil de entrada"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:180
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Settings"
|
||||
msgstr "Ajustes del mando"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:190
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Assignment"
|
||||
msgstr "Asignación del mando"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:12
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Create/Modify Input Profile"
|
||||
msgstr "Crear/Modificar perfil de entrada"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:25
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Enter profile name to create or modify"
|
||||
msgstr "Introduce el nombre del perfil para crear o modificar"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:38
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Deadzone Percentage"
|
||||
msgstr "Porcentaje de zona muerta"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:61
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Use DirectInput"
|
||||
msgstr "Usar DirectInput"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:74
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Start Profile Creation Wizard"
|
||||
msgstr "Iniciar asistente de creación de perfil"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:82
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:19
|
||||
msgctxt "LocalGame"
|
||||
msgid "Local Game"
|
||||
msgstr "Juego local"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:26
|
||||
msgctxt "LocalGame"
|
||||
msgid "Open ROM"
|
||||
msgstr "Abrir ROM"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:37
|
||||
msgctxt "LocalGame"
|
||||
msgid "Open Saves Folder"
|
||||
msgstr "Abrir carpeta de guardados"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:54
|
||||
msgctxt "LocalGame"
|
||||
msgid "Recent ROMs"
|
||||
msgstr "ROMs recientes"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:7
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Netplay: Create Session"
|
||||
msgstr "Netplay: Crear sesión"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:22
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Session Name:"
|
||||
msgstr "Nombre de la sesión:"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:26
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter session name"
|
||||
msgstr "Introduce el nombre de la sesión"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:36
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Password (Optional):"
|
||||
msgstr "Contraseña (Opcional):"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:40
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter session password"
|
||||
msgstr "Introduce la contraseña"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:51
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Player Name:"
|
||||
msgstr "Nombre del jugador:"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:55
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter player name"
|
||||
msgstr "Introduce tu nombre"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:66
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Select ROM"
|
||||
msgstr "Seleccionar ROM"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:82
|
||||
msgctxt "NetplayCreate"
|
||||
msgid ""
|
||||
"This game has cheats enabled. Cheats will be synchronized with other players."
|
||||
msgstr ""
|
||||
"Este juego tiene trucos activados. Los trucos se sincronizarán con los otros "
|
||||
"jugadores."
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:91
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Create Session"
|
||||
msgstr "Crear sesión"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:107
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:7
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Netplay: Join Session"
|
||||
msgstr "Netplay: Unirse a la sesión"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:18
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Player Name (required):"
|
||||
msgstr "Nombre de jugador (requerido):"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:22
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Enter player name"
|
||||
msgstr "Introduce tu nombre"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:31
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Password (if required)"
|
||||
msgstr "Contraseña (si es necesaria)"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:42
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Select ROM"
|
||||
msgstr "Seleccionar ROM"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:57
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Refreshing session list, please wait..."
|
||||
msgstr "Actualizando lista de sesiones, espera..."
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:62
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Click on a session below to select it"
|
||||
msgstr "Pulsa una sesión para seleccionarla"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:67
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "No sessions available. Please refresh to check again."
|
||||
msgstr "No hay sesiones disponibles. Actualiza para comprobar de nuevo."
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:73
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Session Name"
|
||||
msgstr "Nombre de la sesión"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:74
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Game Name"
|
||||
msgstr "Nombre del juego"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:75
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Password"
|
||||
msgstr "Contraseña"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:76
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Cheats"
|
||||
msgstr "Trucos"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:90
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Refresh Session List"
|
||||
msgstr "Actualizar lista"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:105
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Join Session"
|
||||
msgstr "Unirse a la sesión"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:117
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:31
|
||||
msgctxt "Netplay"
|
||||
msgid "Netplay"
|
||||
msgstr "Netplay"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:39
|
||||
msgctxt "Netplay"
|
||||
msgid "Create Session"
|
||||
msgstr "Crear sesión"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:50
|
||||
msgctxt "Netplay"
|
||||
msgid "Join Session"
|
||||
msgstr "Unirse a la sesión"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:64
|
||||
msgctxt "Netplay"
|
||||
msgid "Netplay Discord Channel"
|
||||
msgstr "Canal de Discord de Netplay"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:71
|
||||
msgctxt "Netplay"
|
||||
msgid "Provide Feedback"
|
||||
msgstr "Enviar comentarios"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:17
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Netplay: Waiting Room"
|
||||
msgstr "Netplay: Sala de espera"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:29
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Session Name:"
|
||||
msgstr "Nombre de la sesión:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:35
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Game Name:"
|
||||
msgstr "Nombre del juego:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:45
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 1:"
|
||||
msgstr "Jugador 1:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:49
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 2:"
|
||||
msgstr "Jugador 2:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:55
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 3:"
|
||||
msgstr "Jugador 3:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:59
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 4:"
|
||||
msgstr "Jugador 4:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:73
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Enter chat message"
|
||||
msgstr "Escribe un mensaje en el chat"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:84
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Send Message"
|
||||
msgstr "Enviar mensaje"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:94
|
||||
msgctxt "NetplayWait"
|
||||
msgid ""
|
||||
"Setting the input delay too low will cause the game to lag.\n"
|
||||
"You can lower the input delay in-game by pressing Alt+[ and raise it with "
|
||||
"Alt+]."
|
||||
msgstr ""
|
||||
"Configurar un retardo de entrada demasiado bajo hará que el juego se "
|
||||
"ralentice.\n"
|
||||
"Puedes reducir el retardo de entrada en el juego pulsando Alt+[ y aumentarlo "
|
||||
"con Alt+]."
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:101
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Local Input Delay:"
|
||||
msgstr "Retardo de entrada local:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:118
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Recommended: "
|
||||
msgstr "Recomendado: "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Start Game"
|
||||
msgstr "Iniciar juego"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Waiting for P2P mesh "
|
||||
msgstr "Esperando malla P2P "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:135
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:19
|
||||
msgctxt "RAData"
|
||||
msgid "Not currently logged in"
|
||||
msgstr "Sesión no iniciada"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:27
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "RetroAchievements"
|
||||
msgstr "RetroAchievements"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:36
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Enabled"
|
||||
msgstr "Activado"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:51
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Hardcore mode"
|
||||
msgstr "Modo Hardcore"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:60
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Challenge Indicators"
|
||||
msgstr "Indicadores de desafío"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:69
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Leaderboard Trackers"
|
||||
msgstr "Indicadores de tabla de clasificación"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:78
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Discord Rich Presence"
|
||||
msgstr "Rich Presence en Discord"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:96
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "View Profile"
|
||||
msgstr "Ver perfil"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:106
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Username"
|
||||
msgstr "Usuario"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:116
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Password"
|
||||
msgstr "Contraseña"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:125
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Login"
|
||||
msgstr "Entrar"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:141
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "View Supported Games"
|
||||
msgstr "Ver juegos compatibles"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:25
|
||||
msgctxt "Settings"
|
||||
msgid "Settings"
|
||||
msgstr "Ajustes"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:32
|
||||
msgctxt "Settings"
|
||||
msgid "Game"
|
||||
msgstr "Juego"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:38
|
||||
msgctxt "Settings"
|
||||
msgid "Resolution:"
|
||||
msgstr "Resolución:"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:51
|
||||
msgctxt "Settings"
|
||||
msgid "SSAA downscaling"
|
||||
msgstr "Reducción de escala SSAA"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:59
|
||||
msgctxt "Settings"
|
||||
msgid "Integer Scaling"
|
||||
msgstr "Escalado por enteros"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:67
|
||||
msgctxt "Settings"
|
||||
msgid "Fullscreen"
|
||||
msgstr "Pantalla completa"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:75
|
||||
msgctxt "Settings"
|
||||
msgid "Widescreen (stretch)"
|
||||
msgstr "Pantalla ancha (estirado)"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:83
|
||||
msgctxt "Settings"
|
||||
msgid "VSync"
|
||||
msgstr "Sincronización vertical (VSync)"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:91
|
||||
msgctxt "Settings"
|
||||
msgid "Apply CRT shader"
|
||||
msgstr "Aplicar sombreador CRT"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:103
|
||||
msgctxt "Settings"
|
||||
msgid "Overclock N64 CPU (may cause bugs)"
|
||||
msgstr "Overclock CPU N64 (puede causar errores)"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:111
|
||||
msgctxt "Settings"
|
||||
msgid "Disable Expansion Pak"
|
||||
msgstr "Desactivar Expansion Pak"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:119
|
||||
msgctxt "Settings"
|
||||
msgid "Enable Rewind"
|
||||
msgstr "Activar rebobinado"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:127
|
||||
msgctxt "Settings"
|
||||
msgid "Emulate UNFLoader"
|
||||
msgstr "Emular UNFLoader"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:135
|
||||
msgctxt "Settings"
|
||||
msgid "Emulate VRU"
|
||||
msgstr "Emular VRU"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:144
|
||||
msgctxt "Settings"
|
||||
msgid "UI"
|
||||
msgstr "Interfaz"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:150
|
||||
msgctxt "Settings"
|
||||
msgid "Theme:"
|
||||
msgstr "Tema:"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "System"
|
||||
msgstr "Sistema"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "Dark"
|
||||
msgstr "Oscuro"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "Light"
|
||||
msgstr "Claro"
|
||||
|
||||
#: src/ui/gui/vru_dialog.slint:7
|
||||
msgctxt "VruDialog"
|
||||
msgid "What Would You Like To Say?"
|
||||
msgstr "¿Qué te gustaría decir?"
|
||||
|
||||
#~ msgctxt "NetplayCreate"
|
||||
#~ msgid "Server:"
|
||||
#~ msgstr "Servidor:"
|
||||
|
||||
#~ msgctxt "CustomServerDialog"
|
||||
#~ msgid "Enter a custom server URL"
|
||||
#~ msgstr "Introduce la URL de servidor personalizado"
|
||||
|
||||
#~ msgctxt "NetplayJoin"
|
||||
#~ msgid "Custom Server URL (optional):"
|
||||
#~ msgstr "URL de servidor personalizado (opcional):"
|
||||
|
||||
#~ msgctxt "NetplayJoin"
|
||||
#~ msgid "Server"
|
||||
#~ msgstr "Servidor"
|
||||
|
||||
#~ msgctxt "NetplayWait"
|
||||
#~ msgid "Ping:"
|
||||
#~ msgstr "Latencia (Ping):"
|
||||
|
||||
#~ msgctxt "NetplayCreate"
|
||||
#~ msgid "Creating server, please wait...This may take about 30 seconds."
|
||||
#~ msgstr "Creando servidor, espera... Puede tardar unos 30 segundos."
|
||||
|
||||
#~ msgctxt "ControllerConfig"
|
||||
#~ msgid "Port"
|
||||
#~ msgstr "Puerto"
|
||||
|
||||
#~ msgctxt "ControllerConfig"
|
||||
#~ msgid "Controller"
|
||||
#~ msgstr "Mando"
|
||||
@@ -0,0 +1,622 @@
|
||||
# gopher64 - French (France) Translation
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: gopher64\n"
|
||||
"POT-Creation-Date: 2026-06-30 09:43+0000\n"
|
||||
"PO-Revision-Date: 2026-05-11 12:00+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: French (France)\n"
|
||||
"Language: fr_FR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: src/ui/gui/about_page.slint:17
|
||||
msgctxt "NewVersionButton"
|
||||
msgid "New version available! Click here to download"
|
||||
msgstr "Nouvelle version disponible ! Cliquez ici pour télécharger"
|
||||
|
||||
#: src/ui/gui/about_page.slint:33
|
||||
msgctxt "About"
|
||||
msgid "About"
|
||||
msgstr "À propos"
|
||||
|
||||
#: src/ui/gui/about_page.slint:40
|
||||
msgctxt "About"
|
||||
msgid "Wiki"
|
||||
msgstr "Wiki"
|
||||
|
||||
#: src/ui/gui/about_page.slint:50
|
||||
msgctxt "About"
|
||||
msgid "Discord Server"
|
||||
msgstr "Serveur Discord"
|
||||
|
||||
#: src/ui/gui/about_page.slint:60
|
||||
msgctxt "About"
|
||||
msgid "Source Code"
|
||||
msgstr "Code source"
|
||||
|
||||
#: src/ui/gui/about_page.slint:70
|
||||
msgctxt "About"
|
||||
msgid "Donate (via Patreon)"
|
||||
msgstr "Faire un don (via Patreon)"
|
||||
|
||||
#: src/ui/gui/about_page.slint:77
|
||||
msgctxt "About"
|
||||
msgid "Donate (via GitHub)"
|
||||
msgstr "Faire un don (via GitHub)"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:164
|
||||
msgctxt "Menu"
|
||||
msgid "Local Game"
|
||||
msgstr "Jeu local"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:168
|
||||
msgctxt "Menu"
|
||||
msgid "Controller Config"
|
||||
msgstr "Configuration manette"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:172
|
||||
msgctxt "Menu"
|
||||
msgid "Netplay"
|
||||
msgstr "Netplay"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:176
|
||||
msgctxt "Menu"
|
||||
msgid "Cheats"
|
||||
msgstr "Triche"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:180
|
||||
msgctxt "Menu"
|
||||
msgid "RetroAchievements"
|
||||
msgstr "RetroAchievements"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:184
|
||||
msgctxt "Menu"
|
||||
msgid "Settings"
|
||||
msgstr "Paramètres"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:188
|
||||
msgctxt "Menu"
|
||||
msgid "About"
|
||||
msgstr "À propos"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:15
|
||||
msgctxt "Cheats"
|
||||
msgid "Cheats"
|
||||
msgstr "Triche"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:25
|
||||
msgctxt "Cheats"
|
||||
msgid "Select ROM"
|
||||
msgstr "Sélectionner une ROM"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:41
|
||||
msgctxt "Cheats"
|
||||
msgid "Clear Cheats For All ROMs"
|
||||
msgstr "Effacer la triche pour toutes les ROMs"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:83
|
||||
msgctxt "Cheats"
|
||||
msgid "No Cheats Available"
|
||||
msgstr "Aucune triche disponible"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:80
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Enabled"
|
||||
msgstr "Activé"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:93
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Transfer Pak"
|
||||
msgstr "Transfer Pak"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:107
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Profile"
|
||||
msgstr "Profil"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:157
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Configuration"
|
||||
msgstr "Configuration de la manette"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:171
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Configure Input Profile"
|
||||
msgstr "Configurer le profil d'entrée"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:180
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Settings"
|
||||
msgstr "Paramètres de la manette"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:190
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Assignment"
|
||||
msgstr "Attribution de la manette"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:12
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Create/Modify Input Profile"
|
||||
msgstr "Créer / modifier le profil d'entrée"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:25
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Enter profile name to create or modify"
|
||||
msgstr "Saisir le nom du profil à créer ou à modifier"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:38
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Deadzone Percentage"
|
||||
msgstr "Pourcentage de zone morte"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:61
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Use DirectInput"
|
||||
msgstr "Utiliser DirectInput"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:74
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Start Profile Creation Wizard"
|
||||
msgstr "Lancer l'assistant de création de profil"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:82
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Cancel"
|
||||
msgstr "Annuler"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:19
|
||||
msgctxt "LocalGame"
|
||||
msgid "Local Game"
|
||||
msgstr "Jeu local"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:26
|
||||
msgctxt "LocalGame"
|
||||
msgid "Open ROM"
|
||||
msgstr "Ouvrir une ROM"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:37
|
||||
msgctxt "LocalGame"
|
||||
msgid "Open Saves Folder"
|
||||
msgstr "Ouvrir le dossier des sauvegardes"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:54
|
||||
msgctxt "LocalGame"
|
||||
msgid "Recent ROMs"
|
||||
msgstr "ROMs récentes"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:7
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Netplay: Create Session"
|
||||
msgstr "Netplay : créer une session"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:22
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Session Name:"
|
||||
msgstr "Nom de la session :"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:26
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter session name"
|
||||
msgstr "Saisir le nom de la session"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:36
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Password (Optional):"
|
||||
msgstr "Mot de passe (facultatif) :"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:40
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter session password"
|
||||
msgstr "Saisir le mot de passe de la session"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:51
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Player Name:"
|
||||
msgstr "Nom du joueur :"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:55
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter player name"
|
||||
msgstr "Saisir le nom du joueur"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:66
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Select ROM"
|
||||
msgstr "Sélectionner une ROM"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:82
|
||||
msgctxt "NetplayCreate"
|
||||
msgid ""
|
||||
"This game has cheats enabled. Cheats will be synchronized with other players."
|
||||
msgstr ""
|
||||
"La triche est activée pour ce jeu. Elle sera synchronisée avec les autres "
|
||||
"joueurs."
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:91
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Create Session"
|
||||
msgstr "Créer la session"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:107
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Cancel"
|
||||
msgstr "Annuler"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:7
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Netplay: Join Session"
|
||||
msgstr "Netplay : rejoindre une session"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:18
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Player Name (required):"
|
||||
msgstr "Nom du joueur (obligatoire) :"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:22
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Enter player name"
|
||||
msgstr "Saisir le nom du joueur"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:31
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Password (if required)"
|
||||
msgstr "Mot de passe (si requis)"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:42
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Select ROM"
|
||||
msgstr "Sélectionner une ROM"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:57
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Refreshing session list, please wait..."
|
||||
msgstr "Actualisation de la liste des sessions, veuillez patienter…"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:62
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Click on a session below to select it"
|
||||
msgstr "Cliquez sur une session ci-dessous pour la sélectionner"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:67
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "No sessions available. Please refresh to check again."
|
||||
msgstr ""
|
||||
"Aucune session disponible. Veuillez actualiser pour vérifier à nouveau."
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:73
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Session Name"
|
||||
msgstr "Nom de la session"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:74
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Game Name"
|
||||
msgstr "Nom du jeu"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:75
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Password"
|
||||
msgstr "Mot de passe"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:76
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Cheats"
|
||||
msgstr "Triche"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:90
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Refresh Session List"
|
||||
msgstr "Actualiser la liste"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:105
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Join Session"
|
||||
msgstr "Rejoindre la session"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:117
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Cancel"
|
||||
msgstr "Annuler"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:31
|
||||
msgctxt "Netplay"
|
||||
msgid "Netplay"
|
||||
msgstr "Netplay"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:39
|
||||
msgctxt "Netplay"
|
||||
msgid "Create Session"
|
||||
msgstr "Créer une session"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:50
|
||||
msgctxt "Netplay"
|
||||
msgid "Join Session"
|
||||
msgstr "Rejoindre une session"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:64
|
||||
msgctxt "Netplay"
|
||||
msgid "Netplay Discord Channel"
|
||||
msgstr "Salon Discord Netplay"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:71
|
||||
msgctxt "Netplay"
|
||||
msgid "Provide Feedback"
|
||||
msgstr "Donner votre avis"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:17
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Netplay: Waiting Room"
|
||||
msgstr "Netplay : salle d'attente"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:29
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Session Name:"
|
||||
msgstr "Nom de la session :"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:35
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Game Name:"
|
||||
msgstr "Nom du jeu :"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:45
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 1:"
|
||||
msgstr "Joueur 1 :"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:49
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 2:"
|
||||
msgstr "Joueur 2 :"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:55
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 3:"
|
||||
msgstr "Joueur 3 :"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:59
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 4:"
|
||||
msgstr "Joueur 4 :"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:73
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Enter chat message"
|
||||
msgstr "Saisir un message"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:84
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Send Message"
|
||||
msgstr "Envoyer"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:94
|
||||
msgctxt "NetplayWait"
|
||||
msgid ""
|
||||
"Setting the input delay too low will cause the game to lag.\n"
|
||||
"You can lower the input delay in-game by pressing Alt+[ and raise it with "
|
||||
"Alt+]."
|
||||
msgstr ""
|
||||
"Régler le délai d'entrée trop bas provoquera des ralentissements.\n"
|
||||
"Vous pouvez diminuer le délai d'entrée en jeu avec Alt+[ et l'augmenter avec "
|
||||
"Alt+]."
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:101
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Local Input Delay:"
|
||||
msgstr "Délai d'entrée local :"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:118
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Recommended: "
|
||||
msgstr "Recommandé : "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Start Game"
|
||||
msgstr "Lancer la partie"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Waiting for P2P mesh "
|
||||
msgstr "En attente du maillage P2P "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:135
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Cancel"
|
||||
msgstr "Annuler"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:19
|
||||
msgctxt "RAData"
|
||||
msgid "Not currently logged in"
|
||||
msgstr "Non connecté"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:27
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "RetroAchievements"
|
||||
msgstr "RetroAchievements"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:36
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Enabled"
|
||||
msgstr "Activé"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:51
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Hardcore mode"
|
||||
msgstr "Mode hardcore"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:60
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Challenge Indicators"
|
||||
msgstr "Indicateurs de défi"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:69
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Leaderboard Trackers"
|
||||
msgstr "Suivi des classements"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:78
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Discord Rich Presence"
|
||||
msgstr "Rich Presence Discord"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:96
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "View Profile"
|
||||
msgstr "Voir le profil"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:106
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Username"
|
||||
msgstr "Nom d'utilisateur"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:116
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Password"
|
||||
msgstr "Mot de passe"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:125
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Login"
|
||||
msgstr "Connexion"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:141
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "View Supported Games"
|
||||
msgstr "Voir les jeux pris en charge"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:25
|
||||
msgctxt "Settings"
|
||||
msgid "Settings"
|
||||
msgstr "Paramètres"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:32
|
||||
msgctxt "Settings"
|
||||
msgid "Game"
|
||||
msgstr "Jeu"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:38
|
||||
msgctxt "Settings"
|
||||
msgid "Resolution:"
|
||||
msgstr "Résolution :"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:51
|
||||
msgctxt "Settings"
|
||||
msgid "SSAA downscaling"
|
||||
msgstr "Réduction SSAA"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:59
|
||||
msgctxt "Settings"
|
||||
msgid "Integer Scaling"
|
||||
msgstr "Mise à l'échelle par entiers"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:67
|
||||
msgctxt "Settings"
|
||||
msgid "Fullscreen"
|
||||
msgstr "Plein écran"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:75
|
||||
msgctxt "Settings"
|
||||
msgid "Widescreen (stretch)"
|
||||
msgstr "Écran large (étiré)"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:83
|
||||
msgctxt "Settings"
|
||||
msgid "VSync"
|
||||
msgstr "Synchro verticale"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:91
|
||||
msgctxt "Settings"
|
||||
msgid "Apply CRT shader"
|
||||
msgstr "Appliquer le filtre CRT"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:103
|
||||
msgctxt "Settings"
|
||||
msgid "Overclock N64 CPU (may cause bugs)"
|
||||
msgstr "Surcharger le CPU N64 (risque de bugs)"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:111
|
||||
msgctxt "Settings"
|
||||
msgid "Disable Expansion Pak"
|
||||
msgstr "Désactiver l'Expansion Pak"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:119
|
||||
msgctxt "Settings"
|
||||
msgid "Enable Rewind"
|
||||
msgstr "Activer le rembobinage"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:127
|
||||
msgctxt "Settings"
|
||||
msgid "Emulate UNFLoader"
|
||||
msgstr "Émuler UNFLoader"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:135
|
||||
msgctxt "Settings"
|
||||
msgid "Emulate VRU"
|
||||
msgstr "Émuler le VRU"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:144
|
||||
msgctxt "Settings"
|
||||
msgid "UI"
|
||||
msgstr "Interface"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:150
|
||||
msgctxt "Settings"
|
||||
msgid "Theme:"
|
||||
msgstr "Thème :"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "System"
|
||||
msgstr "Système"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "Dark"
|
||||
msgstr "Sombre"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "Light"
|
||||
msgstr "Clair"
|
||||
|
||||
#: src/ui/gui/vru_dialog.slint:7
|
||||
msgctxt "VruDialog"
|
||||
msgid "What Would You Like To Say?"
|
||||
msgstr "Que souhaitez-vous dire ?"
|
||||
|
||||
#~ msgctxt "NetplayCreate"
|
||||
#~ msgid "Server:"
|
||||
#~ msgstr "Serveur :"
|
||||
|
||||
#~ msgctxt "CustomServerDialog"
|
||||
#~ msgid "Enter a custom server URL"
|
||||
#~ msgstr "Saisir l'URL personnalisée du serveur"
|
||||
|
||||
#~ msgctxt "NetplayJoin"
|
||||
#~ msgid "Custom Server URL (optional):"
|
||||
#~ msgstr "URL personnalisée du serveur (facultatif) :"
|
||||
|
||||
#~ msgctxt "NetplayJoin"
|
||||
#~ msgid "Server"
|
||||
#~ msgstr "Serveur"
|
||||
|
||||
#~ msgctxt "NetplayWait"
|
||||
#~ msgid "Ping:"
|
||||
#~ msgstr "Ping :"
|
||||
|
||||
#~ msgctxt "NetplayCreate"
|
||||
#~ msgid "Creating server, please wait...This may take about 30 seconds."
|
||||
#~ msgstr ""
|
||||
#~ "Création du serveur, veuillez patienter… Cela peut prendre environ 30 "
|
||||
#~ "secondes."
|
||||
|
||||
#~ msgctxt "ControllerConfig"
|
||||
#~ msgid "Port"
|
||||
#~ msgstr "Port"
|
||||
|
||||
#~ msgctxt "ControllerConfig"
|
||||
#~ msgid "Controller"
|
||||
#~ msgstr "Manette"
|
||||
@@ -0,0 +1,581 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2026-06-30 09:43+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: src/ui/gui/about_page.slint:17
|
||||
msgctxt "NewVersionButton"
|
||||
msgid "New version available! Click here to download"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/about_page.slint:33
|
||||
msgctxt "About"
|
||||
msgid "About"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/about_page.slint:40
|
||||
msgctxt "About"
|
||||
msgid "Wiki"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/about_page.slint:50
|
||||
msgctxt "About"
|
||||
msgid "Discord Server"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/about_page.slint:60
|
||||
msgctxt "About"
|
||||
msgid "Source Code"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/about_page.slint:70
|
||||
msgctxt "About"
|
||||
msgid "Donate (via Patreon)"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/about_page.slint:77
|
||||
msgctxt "About"
|
||||
msgid "Donate (via GitHub)"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/appwindow.slint:164
|
||||
msgctxt "Menu"
|
||||
msgid "Local Game"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/appwindow.slint:168
|
||||
msgctxt "Menu"
|
||||
msgid "Controller Config"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/appwindow.slint:172
|
||||
msgctxt "Menu"
|
||||
msgid "Netplay"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/appwindow.slint:176
|
||||
msgctxt "Menu"
|
||||
msgid "Cheats"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/appwindow.slint:180
|
||||
msgctxt "Menu"
|
||||
msgid "RetroAchievements"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/appwindow.slint:184
|
||||
msgctxt "Menu"
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/appwindow.slint:188
|
||||
msgctxt "Menu"
|
||||
msgid "About"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:15
|
||||
msgctxt "Cheats"
|
||||
msgid "Cheats"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:25
|
||||
msgctxt "Cheats"
|
||||
msgid "Select ROM"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:41
|
||||
msgctxt "Cheats"
|
||||
msgid "Clear Cheats For All ROMs"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:83
|
||||
msgctxt "Cheats"
|
||||
msgid "No Cheats Available"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/controller_page.slint:80
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Enabled"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/controller_page.slint:93
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Transfer Pak"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/controller_page.slint:107
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Profile"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/controller_page.slint:157
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Configuration"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/controller_page.slint:171
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Configure Input Profile"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/controller_page.slint:180
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/controller_page.slint:190
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Assignment"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/input_profile.slint:12
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Create/Modify Input Profile"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/input_profile.slint:25
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Enter profile name to create or modify"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/input_profile.slint:38
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Deadzone Percentage"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/input_profile.slint:61
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Use DirectInput"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/input_profile.slint:74
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Start Profile Creation Wizard"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/input_profile.slint:82
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:19
|
||||
msgctxt "LocalGame"
|
||||
msgid "Local Game"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:26
|
||||
msgctxt "LocalGame"
|
||||
msgid "Open ROM"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:37
|
||||
msgctxt "LocalGame"
|
||||
msgid "Open Saves Folder"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:54
|
||||
msgctxt "LocalGame"
|
||||
msgid "Recent ROMs"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:7
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Netplay: Create Session"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:22
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Session Name:"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:26
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter session name"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:36
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Password (Optional):"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:40
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter session password"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:51
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Player Name:"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:55
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter player name"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:66
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Select ROM"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:82
|
||||
msgctxt "NetplayCreate"
|
||||
msgid ""
|
||||
"This game has cheats enabled. Cheats will be synchronized with other players."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:91
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Create Session"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:107
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:7
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Netplay: Join Session"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:18
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Player Name (required):"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:22
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Enter player name"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:31
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Password (if required)"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:42
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Select ROM"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:57
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Refreshing session list, please wait..."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:62
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Click on a session below to select it"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:67
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "No sessions available. Please refresh to check again."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:73
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Session Name"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:74
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Game Name"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:75
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:76
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Cheats"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:90
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Refresh Session List"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:105
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Join Session"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:117
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:31
|
||||
msgctxt "Netplay"
|
||||
msgid "Netplay"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:39
|
||||
msgctxt "Netplay"
|
||||
msgid "Create Session"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:50
|
||||
msgctxt "Netplay"
|
||||
msgid "Join Session"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:64
|
||||
msgctxt "Netplay"
|
||||
msgid "Netplay Discord Channel"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:71
|
||||
msgctxt "Netplay"
|
||||
msgid "Provide Feedback"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:17
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Netplay: Waiting Room"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:29
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Session Name:"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:35
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Game Name:"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:45
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 1:"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:49
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 2:"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:55
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 3:"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:59
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 4:"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:73
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Enter chat message"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:84
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Send Message"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:94
|
||||
msgctxt "NetplayWait"
|
||||
msgid ""
|
||||
"Setting the input delay too low will cause the game to lag.\nYou can lower "
|
||||
"the input delay in-game by pressing Alt+[ and raise it with Alt+]."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:101
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Local Input Delay:"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:118
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Recommended: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Start Game"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Waiting for P2P mesh "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:135
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:19
|
||||
msgctxt "RAData"
|
||||
msgid "Not currently logged in"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:27
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "RetroAchievements"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:36
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Enabled"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:51
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Hardcore mode"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:60
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Challenge Indicators"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:69
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Leaderboard Trackers"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:78
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Discord Rich Presence"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:96
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "View Profile"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:106
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:116
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:125
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Login"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:141
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "View Supported Games"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/settings_page.slint:25
|
||||
msgctxt "Settings"
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/settings_page.slint:32
|
||||
msgctxt "Settings"
|
||||
msgid "Game"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/settings_page.slint:38
|
||||
msgctxt "Settings"
|
||||
msgid "Resolution:"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/settings_page.slint:51
|
||||
msgctxt "Settings"
|
||||
msgid "SSAA downscaling"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/settings_page.slint:59
|
||||
msgctxt "Settings"
|
||||
msgid "Integer Scaling"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/settings_page.slint:67
|
||||
msgctxt "Settings"
|
||||
msgid "Fullscreen"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/settings_page.slint:75
|
||||
msgctxt "Settings"
|
||||
msgid "Widescreen (stretch)"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/settings_page.slint:83
|
||||
msgctxt "Settings"
|
||||
msgid "VSync"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/settings_page.slint:91
|
||||
msgctxt "Settings"
|
||||
msgid "Apply CRT shader"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/settings_page.slint:103
|
||||
msgctxt "Settings"
|
||||
msgid "Overclock N64 CPU (may cause bugs)"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/settings_page.slint:111
|
||||
msgctxt "Settings"
|
||||
msgid "Disable Expansion Pak"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/settings_page.slint:119
|
||||
msgctxt "Settings"
|
||||
msgid "Enable Rewind"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/settings_page.slint:127
|
||||
msgctxt "Settings"
|
||||
msgid "Emulate UNFLoader"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/settings_page.slint:135
|
||||
msgctxt "Settings"
|
||||
msgid "Emulate VRU"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/settings_page.slint:144
|
||||
msgctxt "Settings"
|
||||
msgid "UI"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/settings_page.slint:150
|
||||
msgctxt "Settings"
|
||||
msgid "Theme:"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "System"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "Dark"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "Light"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/gui/vru_dialog.slint:7
|
||||
msgctxt "VruDialog"
|
||||
msgid "What Would You Like To Say?"
|
||||
msgstr ""
|
||||
@@ -0,0 +1,618 @@
|
||||
# gopher64 - Japanese Translation
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: gopher64\n"
|
||||
"POT-Creation-Date: 2026-06-30 09:43+0000\n"
|
||||
"PO-Revision-Date: 2026-05-11 12:00+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Japanese\n"
|
||||
"Language: ja\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: src/ui/gui/about_page.slint:17
|
||||
msgctxt "NewVersionButton"
|
||||
msgid "New version available! Click here to download"
|
||||
msgstr "新しいバージョンがあります。クリックしてダウンロード"
|
||||
|
||||
#: src/ui/gui/about_page.slint:33
|
||||
msgctxt "About"
|
||||
msgid "About"
|
||||
msgstr "バージョン情報"
|
||||
|
||||
#: src/ui/gui/about_page.slint:40
|
||||
msgctxt "About"
|
||||
msgid "Wiki"
|
||||
msgstr "Wiki"
|
||||
|
||||
#: src/ui/gui/about_page.slint:50
|
||||
msgctxt "About"
|
||||
msgid "Discord Server"
|
||||
msgstr "Discordサーバー"
|
||||
|
||||
#: src/ui/gui/about_page.slint:60
|
||||
msgctxt "About"
|
||||
msgid "Source Code"
|
||||
msgstr "ソースコード"
|
||||
|
||||
#: src/ui/gui/about_page.slint:70
|
||||
msgctxt "About"
|
||||
msgid "Donate (via Patreon)"
|
||||
msgstr "Patreonで寄付"
|
||||
|
||||
#: src/ui/gui/about_page.slint:77
|
||||
msgctxt "About"
|
||||
msgid "Donate (via GitHub)"
|
||||
msgstr "GitHubで寄付"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:164
|
||||
msgctxt "Menu"
|
||||
msgid "Local Game"
|
||||
msgstr "ローカルゲーム"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:168
|
||||
msgctxt "Menu"
|
||||
msgid "Controller Config"
|
||||
msgstr "コントローラー設定"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:172
|
||||
msgctxt "Menu"
|
||||
msgid "Netplay"
|
||||
msgstr "ネットプレイ"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:176
|
||||
msgctxt "Menu"
|
||||
msgid "Cheats"
|
||||
msgstr "チート"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:180
|
||||
msgctxt "Menu"
|
||||
msgid "RetroAchievements"
|
||||
msgstr "RetroAchievements"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:184
|
||||
msgctxt "Menu"
|
||||
msgid "Settings"
|
||||
msgstr "設定"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:188
|
||||
msgctxt "Menu"
|
||||
msgid "About"
|
||||
msgstr "バージョン情報"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:15
|
||||
msgctxt "Cheats"
|
||||
msgid "Cheats"
|
||||
msgstr "チート"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:25
|
||||
msgctxt "Cheats"
|
||||
msgid "Select ROM"
|
||||
msgstr "ROMを選択"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:41
|
||||
msgctxt "Cheats"
|
||||
msgid "Clear Cheats For All ROMs"
|
||||
msgstr "すべてのROMのチートをクリア"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:83
|
||||
msgctxt "Cheats"
|
||||
msgid "No Cheats Available"
|
||||
msgstr "利用できるチートはありません"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:80
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Enabled"
|
||||
msgstr "有効"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:93
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Transfer Pak"
|
||||
msgstr "Transfer Pak"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:107
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Profile"
|
||||
msgstr "プロファイル"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:157
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Configuration"
|
||||
msgstr "コントローラー設定"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:171
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Configure Input Profile"
|
||||
msgstr "入力プロファイルを設定"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:180
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Settings"
|
||||
msgstr "コントローラー詳細設定"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:190
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Assignment"
|
||||
msgstr "コントローラー割り当て"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:12
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Create/Modify Input Profile"
|
||||
msgstr "入力プロファイルの作成/変更"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:25
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Enter profile name to create or modify"
|
||||
msgstr "作成または変更するプロファイル名を入力"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:38
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Deadzone Percentage"
|
||||
msgstr "デッドゾーン(%)"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:61
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Use DirectInput"
|
||||
msgstr "DirectInputを使用"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:74
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Start Profile Creation Wizard"
|
||||
msgstr "プロファイル作成ウィザードを開始"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:82
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Cancel"
|
||||
msgstr "キャンセル"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:19
|
||||
msgctxt "LocalGame"
|
||||
msgid "Local Game"
|
||||
msgstr "ローカルゲーム"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:26
|
||||
msgctxt "LocalGame"
|
||||
msgid "Open ROM"
|
||||
msgstr "ROMを開く"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:37
|
||||
msgctxt "LocalGame"
|
||||
msgid "Open Saves Folder"
|
||||
msgstr "セーブフォルダーを開く"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:54
|
||||
msgctxt "LocalGame"
|
||||
msgid "Recent ROMs"
|
||||
msgstr "最近使ったROM"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:7
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Netplay: Create Session"
|
||||
msgstr "ネットプレイ:セッション作成"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:22
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Session Name:"
|
||||
msgstr "セッション名:"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:26
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter session name"
|
||||
msgstr "セッション名を入力"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:36
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Password (Optional):"
|
||||
msgstr "パスワード(任意):"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:40
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter session password"
|
||||
msgstr "セッションのパスワードを入力"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:51
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Player Name:"
|
||||
msgstr "プレイヤー名:"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:55
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter player name"
|
||||
msgstr "プレイヤー名を入力"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:66
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Select ROM"
|
||||
msgstr "ROMを選択"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:82
|
||||
msgctxt "NetplayCreate"
|
||||
msgid ""
|
||||
"This game has cheats enabled. Cheats will be synchronized with other players."
|
||||
msgstr "このゲームではチートが有効です。チートは他のプレイヤーと同期されます。"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:91
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Create Session"
|
||||
msgstr "セッションを作成"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:107
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Cancel"
|
||||
msgstr "キャンセル"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:7
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Netplay: Join Session"
|
||||
msgstr "ネットプレイ:セッションに参加"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:18
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Player Name (required):"
|
||||
msgstr "プレイヤー名(必須):"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:22
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Enter player name"
|
||||
msgstr "プレイヤー名を入力"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:31
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Password (if required)"
|
||||
msgstr "パスワード(必要な場合)"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:42
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Select ROM"
|
||||
msgstr "ROMを選択"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:57
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Refreshing session list, please wait..."
|
||||
msgstr "セッション一覧を更新しています。しばらくお待ちください…"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:62
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Click on a session below to select it"
|
||||
msgstr "下の一覧からセッションをクリックして選択"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:67
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "No sessions available. Please refresh to check again."
|
||||
msgstr "利用できるセッションがありません。更新して再度確認してください。"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:73
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Session Name"
|
||||
msgstr "セッション名"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:74
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Game Name"
|
||||
msgstr "ゲーム名"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:75
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Password"
|
||||
msgstr "パスワード"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:76
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Cheats"
|
||||
msgstr "チート"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:90
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Refresh Session List"
|
||||
msgstr "セッション一覧を更新"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:105
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Join Session"
|
||||
msgstr "セッションに参加"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:117
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Cancel"
|
||||
msgstr "キャンセル"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:31
|
||||
msgctxt "Netplay"
|
||||
msgid "Netplay"
|
||||
msgstr "ネットプレイ"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:39
|
||||
msgctxt "Netplay"
|
||||
msgid "Create Session"
|
||||
msgstr "セッションを作成"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:50
|
||||
msgctxt "Netplay"
|
||||
msgid "Join Session"
|
||||
msgstr "セッションに参加"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:64
|
||||
msgctxt "Netplay"
|
||||
msgid "Netplay Discord Channel"
|
||||
msgstr "ネットプレイ用Discordチャンネル"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:71
|
||||
msgctxt "Netplay"
|
||||
msgid "Provide Feedback"
|
||||
msgstr "フィードバックを送る"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:17
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Netplay: Waiting Room"
|
||||
msgstr "ネットプレイ:待機室"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:29
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Session Name:"
|
||||
msgstr "セッション名:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:35
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Game Name:"
|
||||
msgstr "ゲーム名:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:45
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 1:"
|
||||
msgstr "プレイヤー1:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:49
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 2:"
|
||||
msgstr "プレイヤー2:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:55
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 3:"
|
||||
msgstr "プレイヤー3:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:59
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 4:"
|
||||
msgstr "プレイヤー4:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:73
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Enter chat message"
|
||||
msgstr "チャットメッセージを入力"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:84
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Send Message"
|
||||
msgstr "送信"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:94
|
||||
msgctxt "NetplayWait"
|
||||
msgid ""
|
||||
"Setting the input delay too low will cause the game to lag.\n"
|
||||
"You can lower the input delay in-game by pressing Alt+[ and raise it with "
|
||||
"Alt+]."
|
||||
msgstr ""
|
||||
"入力遅延を低く設定しすぎるとゲームが遅延します。\n"
|
||||
"Alt+[ でゲーム内の入力遅延を下げ、Alt+] で上げることができます。"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:101
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Local Input Delay:"
|
||||
msgstr "ローカル入力遅延:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:118
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Recommended: "
|
||||
msgstr "推奨: "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Start Game"
|
||||
msgstr "ゲーム開始"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Waiting for P2P mesh "
|
||||
msgstr "P2Pメッシュ接続待機中 "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:135
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Cancel"
|
||||
msgstr "キャンセル"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:19
|
||||
msgctxt "RAData"
|
||||
msgid "Not currently logged in"
|
||||
msgstr "ログインしていません"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:27
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "RetroAchievements"
|
||||
msgstr "RetroAchievements"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:36
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Enabled"
|
||||
msgstr "有効"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:51
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Hardcore mode"
|
||||
msgstr "ハードコアモード"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:60
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Challenge Indicators"
|
||||
msgstr "チャレンジ表示"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:69
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Leaderboard Trackers"
|
||||
msgstr "リーダーボード追跡"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:78
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Discord Rich Presence"
|
||||
msgstr "Discord のリッチプレゼンス"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:96
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "View Profile"
|
||||
msgstr "プロフィールを表示"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:106
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Username"
|
||||
msgstr "ユーザー名"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:116
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Password"
|
||||
msgstr "パスワード"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:125
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Login"
|
||||
msgstr "ログイン"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:141
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "View Supported Games"
|
||||
msgstr "対応ゲームを表示"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:25
|
||||
msgctxt "Settings"
|
||||
msgid "Settings"
|
||||
msgstr "設定"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:32
|
||||
msgctxt "Settings"
|
||||
msgid "Game"
|
||||
msgstr "ゲーム"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:38
|
||||
msgctxt "Settings"
|
||||
msgid "Resolution:"
|
||||
msgstr "解像度:"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:51
|
||||
msgctxt "Settings"
|
||||
msgid "SSAA downscaling"
|
||||
msgstr "SSAAダウンスケール"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:59
|
||||
msgctxt "Settings"
|
||||
msgid "Integer Scaling"
|
||||
msgstr "整数倍スケーリング"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:67
|
||||
msgctxt "Settings"
|
||||
msgid "Fullscreen"
|
||||
msgstr "全画面表示"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:75
|
||||
msgctxt "Settings"
|
||||
msgid "Widescreen (stretch)"
|
||||
msgstr "ワイド画面(伸ばす)"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:83
|
||||
msgctxt "Settings"
|
||||
msgid "VSync"
|
||||
msgstr "垂直同期(VSync)"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:91
|
||||
msgctxt "Settings"
|
||||
msgid "Apply CRT shader"
|
||||
msgstr "CRTシェーダーを適用"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:103
|
||||
msgctxt "Settings"
|
||||
msgid "Overclock N64 CPU (may cause bugs)"
|
||||
msgstr "N64のCPUをオーバークロック(不具合の原因になることがあります)"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:111
|
||||
msgctxt "Settings"
|
||||
msgid "Disable Expansion Pak"
|
||||
msgstr "拡張パックを無効にする"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:119
|
||||
msgctxt "Settings"
|
||||
msgid "Enable Rewind"
|
||||
msgstr "巻き戻しを有効にする"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:127
|
||||
msgctxt "Settings"
|
||||
msgid "Emulate UNFLoader"
|
||||
msgstr "UNFLoaderをエミュレート"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:135
|
||||
msgctxt "Settings"
|
||||
msgid "Emulate VRU"
|
||||
msgstr "VRUをエミュレート"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:144
|
||||
msgctxt "Settings"
|
||||
msgid "UI"
|
||||
msgstr "UI"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:150
|
||||
msgctxt "Settings"
|
||||
msgid "Theme:"
|
||||
msgstr "テーマ:"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "System"
|
||||
msgstr "システム"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "Dark"
|
||||
msgstr "ダーク"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "Light"
|
||||
msgstr "ライト"
|
||||
|
||||
#: src/ui/gui/vru_dialog.slint:7
|
||||
msgctxt "VruDialog"
|
||||
msgid "What Would You Like To Say?"
|
||||
msgstr "何と言いますか?"
|
||||
|
||||
#~ msgctxt "NetplayCreate"
|
||||
#~ msgid "Server:"
|
||||
#~ msgstr "サーバー:"
|
||||
|
||||
#~ msgctxt "CustomServerDialog"
|
||||
#~ msgid "Enter a custom server URL"
|
||||
#~ msgstr "カスタムサーバーURLを入力"
|
||||
|
||||
#~ msgctxt "NetplayJoin"
|
||||
#~ msgid "Custom Server URL (optional):"
|
||||
#~ msgstr "カスタムサーバーURL(任意):"
|
||||
|
||||
#~ msgctxt "NetplayJoin"
|
||||
#~ msgid "Server"
|
||||
#~ msgstr "サーバー"
|
||||
|
||||
#~ msgctxt "NetplayWait"
|
||||
#~ msgid "Ping:"
|
||||
#~ msgstr "Ping:"
|
||||
|
||||
#~ msgctxt "NetplayCreate"
|
||||
#~ msgid "Creating server, please wait...This may take about 30 seconds."
|
||||
#~ msgstr ""
|
||||
#~ "サーバーを作成しています。しばらくお待ちください… 最大約30秒かかることがあ"
|
||||
#~ "ります。"
|
||||
|
||||
#~ msgctxt "ControllerConfig"
|
||||
#~ msgid "Port"
|
||||
#~ msgstr "ポート"
|
||||
|
||||
#~ msgctxt "ControllerConfig"
|
||||
#~ msgid "Controller"
|
||||
#~ msgstr "コントローラー"
|
||||
@@ -0,0 +1,620 @@
|
||||
# gopher64 - Dutch (Netherlands) Translation
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: gopher64\n"
|
||||
"POT-Creation-Date: 2026-06-30 09:43+0000\n"
|
||||
"PO-Revision-Date: 2026-05-11 12:00+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Dutch (Netherlands)\n"
|
||||
"Language: nl_NL\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: src/ui/gui/about_page.slint:17
|
||||
msgctxt "NewVersionButton"
|
||||
msgid "New version available! Click here to download"
|
||||
msgstr "Nieuwe versie beschikbaar! Klik hier om te downloaden"
|
||||
|
||||
#: src/ui/gui/about_page.slint:33
|
||||
msgctxt "About"
|
||||
msgid "About"
|
||||
msgstr "Over"
|
||||
|
||||
#: src/ui/gui/about_page.slint:40
|
||||
msgctxt "About"
|
||||
msgid "Wiki"
|
||||
msgstr "Wiki"
|
||||
|
||||
#: src/ui/gui/about_page.slint:50
|
||||
msgctxt "About"
|
||||
msgid "Discord Server"
|
||||
msgstr "Discord-server"
|
||||
|
||||
#: src/ui/gui/about_page.slint:60
|
||||
msgctxt "About"
|
||||
msgid "Source Code"
|
||||
msgstr "Broncode"
|
||||
|
||||
#: src/ui/gui/about_page.slint:70
|
||||
msgctxt "About"
|
||||
msgid "Donate (via Patreon)"
|
||||
msgstr "Doneren (via Patreon)"
|
||||
|
||||
#: src/ui/gui/about_page.slint:77
|
||||
msgctxt "About"
|
||||
msgid "Donate (via GitHub)"
|
||||
msgstr "Doneren (via GitHub)"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:164
|
||||
msgctxt "Menu"
|
||||
msgid "Local Game"
|
||||
msgstr "Lokaal spel"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:168
|
||||
msgctxt "Menu"
|
||||
msgid "Controller Config"
|
||||
msgstr "Controllerconfiguratie"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:172
|
||||
msgctxt "Menu"
|
||||
msgid "Netplay"
|
||||
msgstr "Netplay"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:176
|
||||
msgctxt "Menu"
|
||||
msgid "Cheats"
|
||||
msgstr "Cheats"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:180
|
||||
msgctxt "Menu"
|
||||
msgid "RetroAchievements"
|
||||
msgstr "RetroAchievements"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:184
|
||||
msgctxt "Menu"
|
||||
msgid "Settings"
|
||||
msgstr "Instellingen"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:188
|
||||
msgctxt "Menu"
|
||||
msgid "About"
|
||||
msgstr "Over"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:15
|
||||
msgctxt "Cheats"
|
||||
msgid "Cheats"
|
||||
msgstr "Cheats"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:25
|
||||
msgctxt "Cheats"
|
||||
msgid "Select ROM"
|
||||
msgstr "ROM selecteren"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:41
|
||||
msgctxt "Cheats"
|
||||
msgid "Clear Cheats For All ROMs"
|
||||
msgstr "Cheats wissen voor alle ROM's"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:83
|
||||
msgctxt "Cheats"
|
||||
msgid "No Cheats Available"
|
||||
msgstr "Geen cheats beschikbaar"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:80
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Enabled"
|
||||
msgstr "Ingeschakeld"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:93
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Transfer Pak"
|
||||
msgstr "Transfer Pak"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:107
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Profile"
|
||||
msgstr "Profiel"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:157
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Configuration"
|
||||
msgstr "Controllerconfiguratie"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:171
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Configure Input Profile"
|
||||
msgstr "Invoerprofiel configureren"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:180
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Settings"
|
||||
msgstr "Controllerinstellingen"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:190
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Assignment"
|
||||
msgstr "Controllertoewijzing"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:12
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Create/Modify Input Profile"
|
||||
msgstr "Invoerprofiel maken/wijzigen"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:25
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Enter profile name to create or modify"
|
||||
msgstr "Profielnaam invoeren om aan te maken of te wijzigen"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:38
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Deadzone Percentage"
|
||||
msgstr "Dode-zonepercentage"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:61
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Use DirectInput"
|
||||
msgstr "DirectInput gebruiken"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:74
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Start Profile Creation Wizard"
|
||||
msgstr "Wizard voor profiel maken starten"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:82
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Cancel"
|
||||
msgstr "Annuleren"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:19
|
||||
msgctxt "LocalGame"
|
||||
msgid "Local Game"
|
||||
msgstr "Lokaal spel"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:26
|
||||
msgctxt "LocalGame"
|
||||
msgid "Open ROM"
|
||||
msgstr "ROM openen"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:37
|
||||
msgctxt "LocalGame"
|
||||
msgid "Open Saves Folder"
|
||||
msgstr "Map met opslagen openen"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:54
|
||||
msgctxt "LocalGame"
|
||||
msgid "Recent ROMs"
|
||||
msgstr "Recent gebruikte ROM's"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:7
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Netplay: Create Session"
|
||||
msgstr "Netplay: sessie aanmaken"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:22
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Session Name:"
|
||||
msgstr "Sessienaam:"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:26
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter session name"
|
||||
msgstr "Sessienaam invoeren"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:36
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Password (Optional):"
|
||||
msgstr "Wachtwoord (optioneel):"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:40
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter session password"
|
||||
msgstr "Sessiewachtwoord invoeren"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:51
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Player Name:"
|
||||
msgstr "Spelernaam:"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:55
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter player name"
|
||||
msgstr "Spelernaam invoeren"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:66
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Select ROM"
|
||||
msgstr "ROM selecteren"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:82
|
||||
msgctxt "NetplayCreate"
|
||||
msgid ""
|
||||
"This game has cheats enabled. Cheats will be synchronized with other players."
|
||||
msgstr ""
|
||||
"Voor dit spel zijn cheats ingeschakeld. Cheats worden gesynchroniseerd met "
|
||||
"andere spelers."
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:91
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Create Session"
|
||||
msgstr "Sessie aanmaken"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:107
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Cancel"
|
||||
msgstr "Annuleren"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:7
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Netplay: Join Session"
|
||||
msgstr "Netplay: deelnemen aan sessie"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:18
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Player Name (required):"
|
||||
msgstr "Spelernaam (verplicht):"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:22
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Enter player name"
|
||||
msgstr "Spelernaam invoeren"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:31
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Password (if required)"
|
||||
msgstr "Wachtwoord (indien vereist)"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:42
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Select ROM"
|
||||
msgstr "ROM selecteren"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:57
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Refreshing session list, please wait..."
|
||||
msgstr "Sessielijst wordt vernieuwd, even geduld…"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:62
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Click on a session below to select it"
|
||||
msgstr "Klik hieronder op een sessie om die te selecteren"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:67
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "No sessions available. Please refresh to check again."
|
||||
msgstr "Geen sessies beschikbaar. Vernieuw om opnieuw te controleren."
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:73
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Session Name"
|
||||
msgstr "Sessienaam"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:74
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Game Name"
|
||||
msgstr "Spelnaam"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:75
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Password"
|
||||
msgstr "Wachtwoord"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:76
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Cheats"
|
||||
msgstr "Cheats"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:90
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Refresh Session List"
|
||||
msgstr "Sessielijst vernieuwen"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:105
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Join Session"
|
||||
msgstr "Deelnemen aan sessie"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:117
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Cancel"
|
||||
msgstr "Annuleren"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:31
|
||||
msgctxt "Netplay"
|
||||
msgid "Netplay"
|
||||
msgstr "Netplay"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:39
|
||||
msgctxt "Netplay"
|
||||
msgid "Create Session"
|
||||
msgstr "Sessie aanmaken"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:50
|
||||
msgctxt "Netplay"
|
||||
msgid "Join Session"
|
||||
msgstr "Deelnemen aan sessie"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:64
|
||||
msgctxt "Netplay"
|
||||
msgid "Netplay Discord Channel"
|
||||
msgstr "Netplay-Discordkanaal"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:71
|
||||
msgctxt "Netplay"
|
||||
msgid "Provide Feedback"
|
||||
msgstr "Feedback geven"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:17
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Netplay: Waiting Room"
|
||||
msgstr "Netplay: wachtruimte"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:29
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Session Name:"
|
||||
msgstr "Sessienaam:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:35
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Game Name:"
|
||||
msgstr "Spelnaam:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:45
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 1:"
|
||||
msgstr "Speler 1:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:49
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 2:"
|
||||
msgstr "Speler 2:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:55
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 3:"
|
||||
msgstr "Speler 3:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:59
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 4:"
|
||||
msgstr "Speler 4:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:73
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Enter chat message"
|
||||
msgstr "Chatbericht invoeren"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:84
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Send Message"
|
||||
msgstr "Bericht verzenden"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:94
|
||||
msgctxt "NetplayWait"
|
||||
msgid ""
|
||||
"Setting the input delay too low will cause the game to lag.\n"
|
||||
"You can lower the input delay in-game by pressing Alt+[ and raise it with "
|
||||
"Alt+]."
|
||||
msgstr ""
|
||||
"Een te lage invoervertraging zorgt ervoor dat het spel gaat haperen.\n"
|
||||
"Je kunt de invoervertraging in het spel verlagen met Alt+[ en verhogen met "
|
||||
"Alt+]."
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:101
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Local Input Delay:"
|
||||
msgstr "Lokale invoervertraging:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:118
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Recommended: "
|
||||
msgstr "Aanbevolen: "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Start Game"
|
||||
msgstr "Spel starten"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Waiting for P2P mesh "
|
||||
msgstr "Wachten op P2P-mesh "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:135
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Cancel"
|
||||
msgstr "Annuleren"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:19
|
||||
msgctxt "RAData"
|
||||
msgid "Not currently logged in"
|
||||
msgstr "Momenteel niet ingelogd"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:27
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "RetroAchievements"
|
||||
msgstr "RetroAchievements"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:36
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Enabled"
|
||||
msgstr "Ingeschakeld"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:51
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Hardcore mode"
|
||||
msgstr "Hardcore-modus"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:60
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Challenge Indicators"
|
||||
msgstr "Challenge-indicatoren"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:69
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Leaderboard Trackers"
|
||||
msgstr "Leaderboard-trackers"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:78
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Discord Rich Presence"
|
||||
msgstr "Discord Rich Presence"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:96
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "View Profile"
|
||||
msgstr "Profiel bekijken"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:106
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Username"
|
||||
msgstr "Gebruikersnaam"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:116
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Password"
|
||||
msgstr "Wachtwoord"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:125
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Login"
|
||||
msgstr "Inloggen"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:141
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "View Supported Games"
|
||||
msgstr "Ondersteunde spellen bekijken"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:25
|
||||
msgctxt "Settings"
|
||||
msgid "Settings"
|
||||
msgstr "Instellingen"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:32
|
||||
msgctxt "Settings"
|
||||
msgid "Game"
|
||||
msgstr "Spel"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:38
|
||||
msgctxt "Settings"
|
||||
msgid "Resolution:"
|
||||
msgstr "Resolutie:"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:51
|
||||
msgctxt "Settings"
|
||||
msgid "SSAA downscaling"
|
||||
msgstr "SSAA-downscaling"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:59
|
||||
msgctxt "Settings"
|
||||
msgid "Integer Scaling"
|
||||
msgstr "Schalen in hele getallen"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:67
|
||||
msgctxt "Settings"
|
||||
msgid "Fullscreen"
|
||||
msgstr "Volledig scherm"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:75
|
||||
msgctxt "Settings"
|
||||
msgid "Widescreen (stretch)"
|
||||
msgstr "Breedbeeld (uitgerekt)"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:83
|
||||
msgctxt "Settings"
|
||||
msgid "VSync"
|
||||
msgstr "VSync"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:91
|
||||
msgctxt "Settings"
|
||||
msgid "Apply CRT shader"
|
||||
msgstr "CRT-shader toepassen"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:103
|
||||
msgctxt "Settings"
|
||||
msgid "Overclock N64 CPU (may cause bugs)"
|
||||
msgstr "N64-CPU overklokken (kan bugs veroorzaken)"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:111
|
||||
msgctxt "Settings"
|
||||
msgid "Disable Expansion Pak"
|
||||
msgstr "Expansion Pak uitschakelen"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:119
|
||||
msgctxt "Settings"
|
||||
msgid "Enable Rewind"
|
||||
msgstr "Terugspoelen inschakelen"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:127
|
||||
msgctxt "Settings"
|
||||
msgid "Emulate UNFLoader"
|
||||
msgstr "UNFLoader emuleren"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:135
|
||||
msgctxt "Settings"
|
||||
msgid "Emulate VRU"
|
||||
msgstr "VRU emuleren"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:144
|
||||
msgctxt "Settings"
|
||||
msgid "UI"
|
||||
msgstr "Interface"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:150
|
||||
msgctxt "Settings"
|
||||
msgid "Theme:"
|
||||
msgstr "Thema:"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "System"
|
||||
msgstr "Systeem"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "Dark"
|
||||
msgstr "Donker"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "Light"
|
||||
msgstr "Licht"
|
||||
|
||||
#: src/ui/gui/vru_dialog.slint:7
|
||||
msgctxt "VruDialog"
|
||||
msgid "What Would You Like To Say?"
|
||||
msgstr "Wat wil je zeggen?"
|
||||
|
||||
#~ msgctxt "NetplayCreate"
|
||||
#~ msgid "Server:"
|
||||
#~ msgstr "Server:"
|
||||
|
||||
#~ msgctxt "CustomServerDialog"
|
||||
#~ msgid "Enter a custom server URL"
|
||||
#~ msgstr "Aangepaste server-URL invoeren"
|
||||
|
||||
#~ msgctxt "NetplayJoin"
|
||||
#~ msgid "Custom Server URL (optional):"
|
||||
#~ msgstr "Aangepaste server-URL (optioneel):"
|
||||
|
||||
#~ msgctxt "NetplayJoin"
|
||||
#~ msgid "Server"
|
||||
#~ msgstr "Server"
|
||||
|
||||
#~ msgctxt "NetplayWait"
|
||||
#~ msgid "Ping:"
|
||||
#~ msgstr "Ping:"
|
||||
|
||||
#~ msgctxt "NetplayCreate"
|
||||
#~ msgid "Creating server, please wait...This may take about 30 seconds."
|
||||
#~ msgstr ""
|
||||
#~ "Server wordt aangemaakt, even geduld… Dit kan ongeveer 30 seconden duren."
|
||||
|
||||
#~ msgctxt "ControllerConfig"
|
||||
#~ msgid "Port"
|
||||
#~ msgstr "Poort"
|
||||
|
||||
#~ msgctxt "ControllerConfig"
|
||||
#~ msgid "Controller"
|
||||
#~ msgstr "Controller"
|
||||
@@ -0,0 +1,621 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2026-06-30 09:43+0000\n"
|
||||
"PO-Revision-Date: 2026-05-11 18:11-3000\n"
|
||||
"Last-Translator: EDUARDO CARVALHO <eduardotcarvalho@gmail.com>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: Brazilian Portuguese\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: src/ui/gui/about_page.slint:17
|
||||
msgctxt "NewVersionButton"
|
||||
msgid "New version available! Click here to download"
|
||||
msgstr "Nova versão disponível! Clique aqui para baixar"
|
||||
|
||||
#: src/ui/gui/about_page.slint:33
|
||||
msgctxt "About"
|
||||
msgid "About"
|
||||
msgstr "Sobre"
|
||||
|
||||
#: src/ui/gui/about_page.slint:40
|
||||
msgctxt "About"
|
||||
msgid "Wiki"
|
||||
msgstr "Wiki"
|
||||
|
||||
#: src/ui/gui/about_page.slint:50
|
||||
msgctxt "About"
|
||||
msgid "Discord Server"
|
||||
msgstr "Servidor do Discord"
|
||||
|
||||
#: src/ui/gui/about_page.slint:60
|
||||
msgctxt "About"
|
||||
msgid "Source Code"
|
||||
msgstr "Código Fonte"
|
||||
|
||||
#: src/ui/gui/about_page.slint:70
|
||||
msgctxt "About"
|
||||
msgid "Donate (via Patreon)"
|
||||
msgstr "Doações (via Patreon)"
|
||||
|
||||
#: src/ui/gui/about_page.slint:77
|
||||
msgctxt "About"
|
||||
msgid "Donate (via GitHub)"
|
||||
msgstr "Doações (via GitHub)"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:164
|
||||
msgctxt "Menu"
|
||||
msgid "Local Game"
|
||||
msgstr "Jogo Local"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:168
|
||||
msgctxt "Menu"
|
||||
msgid "Controller Config"
|
||||
msgstr "Configurar Controle"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:172
|
||||
msgctxt "Menu"
|
||||
msgid "Netplay"
|
||||
msgstr "Netplay"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:176
|
||||
msgctxt "Menu"
|
||||
msgid "Cheats"
|
||||
msgstr "Trapaças"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:180
|
||||
msgctxt "Menu"
|
||||
msgid "RetroAchievements"
|
||||
msgstr "RetroAchievements"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:184
|
||||
msgctxt "Menu"
|
||||
msgid "Settings"
|
||||
msgstr "Configurações"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:188
|
||||
msgctxt "Menu"
|
||||
msgid "About"
|
||||
msgstr "Sobre"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:15
|
||||
msgctxt "Cheats"
|
||||
msgid "Cheats"
|
||||
msgstr "Trapaças"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:25
|
||||
msgctxt "Cheats"
|
||||
msgid "Select ROM"
|
||||
msgstr "Selecionar ROM"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:41
|
||||
msgctxt "Cheats"
|
||||
msgid "Clear Cheats For All ROMs"
|
||||
msgstr "Limpar Trapaças para todas as ROMs"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:83
|
||||
msgctxt "Cheats"
|
||||
msgid "No Cheats Available"
|
||||
msgstr "Sem trapaças disponíveis"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:80
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Enabled"
|
||||
msgstr "Habilitado"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:93
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Transfer Pak"
|
||||
msgstr "Transfer Pak"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:107
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Profile"
|
||||
msgstr "Perfil"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:157
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Configuration"
|
||||
msgstr "Configurar Controles"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:171
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Configure Input Profile"
|
||||
msgstr "Configurar Perfil de Controle"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:180
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Settings"
|
||||
msgstr "Configurações do controle"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:190
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Assignment"
|
||||
msgstr "Atribuição do controle"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:12
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Create/Modify Input Profile"
|
||||
msgstr "Criar/Modificar Perfil de Comandos"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:25
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Enter profile name to create or modify"
|
||||
msgstr "Insira o nome de perfil para criar ou modificar"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:38
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Deadzone Percentage"
|
||||
msgstr "Porcentagem de Zona-morta"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:61
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Use DirectInput"
|
||||
msgstr "Usar DirectInput"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:74
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Start Profile Creation Wizard"
|
||||
msgstr "Iniciar Assistente de Criação de Perfil"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:82
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:19
|
||||
msgctxt "LocalGame"
|
||||
msgid "Local Game"
|
||||
msgstr "Jogo Local"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:26
|
||||
msgctxt "LocalGame"
|
||||
msgid "Open ROM"
|
||||
msgstr "Abrir ROM"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:37
|
||||
msgctxt "LocalGame"
|
||||
msgid "Open Saves Folder"
|
||||
msgstr "Abrir Pasta de Saves"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:54
|
||||
msgctxt "LocalGame"
|
||||
msgid "Recent ROMs"
|
||||
msgstr "ROMs Recentes"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:7
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Netplay: Create Session"
|
||||
msgstr "Netplay: Criar Sessão"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:22
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Session Name:"
|
||||
msgstr "Nome da Sessão:"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:26
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter session name"
|
||||
msgstr "Insira nome da sessão"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:36
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Password (Optional):"
|
||||
msgstr "Senha: (Opcional):"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:40
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter session password"
|
||||
msgstr "Insira a senha da sessão"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:51
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Player Name:"
|
||||
msgstr "Nome de Jogador:"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:55
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter player name"
|
||||
msgstr "Insira nome de jogador"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:66
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Select ROM"
|
||||
msgstr "Selecionar ROM"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:82
|
||||
msgctxt "NetplayCreate"
|
||||
msgid ""
|
||||
"This game has cheats enabled. Cheats will be synchronized with other players."
|
||||
msgstr ""
|
||||
"Este jogo tem trapaças habilitadas. As trapaças serão sincronizadas com "
|
||||
"outros jogadores."
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:91
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Create Session"
|
||||
msgstr "Criar Sessão"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:107
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:7
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Netplay: Join Session"
|
||||
msgstr "Netplay: Entrar na sessão"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:18
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Player Name (required):"
|
||||
msgstr "Nome de Jogador (obrigatório):"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:22
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Enter player name"
|
||||
msgstr "Insira o nome de jogador"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:31
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Password (if required)"
|
||||
msgstr "Senha (caso obrigatória)"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:42
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Select ROM"
|
||||
msgstr "Selecionar ROM"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:57
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Refreshing session list, please wait..."
|
||||
msgstr "Recarregando lista de sessões, por favor aguarde..."
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:62
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Click on a session below to select it"
|
||||
msgstr "Clique em uma sessão abaixo para selecionar"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:67
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "No sessions available. Please refresh to check again."
|
||||
msgstr ""
|
||||
"Sem sessões disponíveis. Por favor recarregue para verificar novamente."
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:73
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Session Name"
|
||||
msgstr "Nome da Sessão"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:74
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Game Name"
|
||||
msgstr "Nome do Jogo"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:75
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Password"
|
||||
msgstr "Senha"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:76
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Cheats"
|
||||
msgstr "Trapaças"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:90
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Refresh Session List"
|
||||
msgstr "Recarregar Lista de Sessões"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:105
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Join Session"
|
||||
msgstr "Entrar na Sessão"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:117
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:31
|
||||
msgctxt "Netplay"
|
||||
msgid "Netplay"
|
||||
msgstr "Netplay"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:39
|
||||
msgctxt "Netplay"
|
||||
msgid "Create Session"
|
||||
msgstr "Criar Sessão"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:50
|
||||
msgctxt "Netplay"
|
||||
msgid "Join Session"
|
||||
msgstr "Entrar na Sessão"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:64
|
||||
msgctxt "Netplay"
|
||||
msgid "Netplay Discord Channel"
|
||||
msgstr "Canal de Netplay do Discord"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:71
|
||||
msgctxt "Netplay"
|
||||
msgid "Provide Feedback"
|
||||
msgstr "Fornecer Feedback"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:17
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Netplay: Waiting Room"
|
||||
msgstr "Netplay: Aguardando Sala"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:29
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Session Name:"
|
||||
msgstr "Nome da Sessão:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:35
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Game Name:"
|
||||
msgstr "Nome do Jogo:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:45
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 1:"
|
||||
msgstr "Jogador 1:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:49
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 2:"
|
||||
msgstr "Jogador 2:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:55
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 3:"
|
||||
msgstr "Jogador 3:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:59
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 4:"
|
||||
msgstr "Jogador 4:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:73
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Enter chat message"
|
||||
msgstr "Escrever mensagem no chat"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:84
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Send Message"
|
||||
msgstr "Enviar Mensagem"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:94
|
||||
msgctxt "NetplayWait"
|
||||
msgid ""
|
||||
"Setting the input delay too low will cause the game to lag.\n"
|
||||
"You can lower the input delay in-game by pressing Alt+[ and raise it with "
|
||||
"Alt+]."
|
||||
msgstr ""
|
||||
"Definir o atraso de entrada muito baixo causará lag no jogo.\n"
|
||||
"Você pode diminuir o atraso de entrada no jogo pressionando Alt+[ e aumentá-"
|
||||
"lo com Alt+]."
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:101
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Local Input Delay:"
|
||||
msgstr "Atraso de entrada local:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:118
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Recommended: "
|
||||
msgstr "Recomendado: "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Start Game"
|
||||
msgstr "Iniciar jogo"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Waiting for P2P mesh "
|
||||
msgstr "Aguardando malha P2P "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:135
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:19
|
||||
msgctxt "RAData"
|
||||
msgid "Not currently logged in"
|
||||
msgstr "Não está logado."
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:27
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "RetroAchievements"
|
||||
msgstr "RetroAchievements"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:36
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Enabled"
|
||||
msgstr "Habilitado"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:51
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Hardcore mode"
|
||||
msgstr "Modo Hardcore"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:60
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Challenge Indicators"
|
||||
msgstr "Indicadores de Desafio"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:69
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Leaderboard Trackers"
|
||||
msgstr "Rastreadores de Tabela"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:78
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Discord Rich Presence"
|
||||
msgstr "Rich Presence no Discord"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:96
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "View Profile"
|
||||
msgstr "Ver Perfil"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:106
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Username"
|
||||
msgstr "Usuário"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:116
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Password"
|
||||
msgstr "Senha"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:125
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Login"
|
||||
msgstr "Login"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:141
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "View Supported Games"
|
||||
msgstr "Ver Jogos com Suporte"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:25
|
||||
msgctxt "Settings"
|
||||
msgid "Settings"
|
||||
msgstr "Configurações"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:32
|
||||
msgctxt "Settings"
|
||||
msgid "Game"
|
||||
msgstr "Jogo"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:38
|
||||
msgctxt "Settings"
|
||||
msgid "Resolution:"
|
||||
msgstr "Resolução:"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:51
|
||||
msgctxt "Settings"
|
||||
msgid "SSAA downscaling"
|
||||
msgstr "Redução de SSAA"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:59
|
||||
msgctxt "Settings"
|
||||
msgid "Integer Scaling"
|
||||
msgstr "Proporção Íntegra"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:67
|
||||
msgctxt "Settings"
|
||||
msgid "Fullscreen"
|
||||
msgstr "Tela Cheia"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:75
|
||||
msgctxt "Settings"
|
||||
msgid "Widescreen (stretch)"
|
||||
msgstr "Widescreen (esticar)"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:83
|
||||
msgctxt "Settings"
|
||||
msgid "VSync"
|
||||
msgstr "VSync"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:91
|
||||
msgctxt "Settings"
|
||||
msgid "Apply CRT shader"
|
||||
msgstr "Aplicar filtro de CRT"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:103
|
||||
msgctxt "Settings"
|
||||
msgid "Overclock N64 CPU (may cause bugs)"
|
||||
msgstr "Overclock da CPU do N64 (pode causar bugs)"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:111
|
||||
msgctxt "Settings"
|
||||
msgid "Disable Expansion Pak"
|
||||
msgstr "Desabilitar Expansion Pak"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:119
|
||||
msgctxt "Settings"
|
||||
msgid "Enable Rewind"
|
||||
msgstr "Habilitar rebobinamento"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:127
|
||||
msgctxt "Settings"
|
||||
msgid "Emulate UNFLoader"
|
||||
msgstr "Emular UNFLoader"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:135
|
||||
msgctxt "Settings"
|
||||
msgid "Emulate VRU"
|
||||
msgstr "Emular VRU"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:144
|
||||
msgctxt "Settings"
|
||||
msgid "UI"
|
||||
msgstr "Interface"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:150
|
||||
msgctxt "Settings"
|
||||
msgid "Theme:"
|
||||
msgstr "Tema:"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "System"
|
||||
msgstr "Sistema"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "Dark"
|
||||
msgstr "Escuro"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "Light"
|
||||
msgstr "Claro"
|
||||
|
||||
#: src/ui/gui/vru_dialog.slint:7
|
||||
msgctxt "VruDialog"
|
||||
msgid "What Would You Like To Say?"
|
||||
msgstr "O que você quer falar?"
|
||||
|
||||
#~ msgctxt "NetplayCreate"
|
||||
#~ msgid "Server:"
|
||||
#~ msgstr "Servidor:"
|
||||
|
||||
#~ msgctxt "CustomServerDialog"
|
||||
#~ msgid "Enter a custom server URL"
|
||||
#~ msgstr "Insira a URL personalizada do servidor"
|
||||
|
||||
#~ msgctxt "NetplayJoin"
|
||||
#~ msgid "Custom Server URL (optional):"
|
||||
#~ msgstr "URL Personalizada de Servidor (opcional):"
|
||||
|
||||
#~ msgctxt "NetplayJoin"
|
||||
#~ msgid "Server"
|
||||
#~ msgstr "Servidor"
|
||||
|
||||
#~ msgctxt "NetplayWait"
|
||||
#~ msgid "Ping:"
|
||||
#~ msgstr "Ping:"
|
||||
|
||||
#~ msgctxt "NetplayCreate"
|
||||
#~ msgid "Creating server, please wait...This may take about 30 seconds."
|
||||
#~ msgstr ""
|
||||
#~ "Criando servidor, por favor aguarde...Isto poderá levar até 30 segundos."
|
||||
|
||||
#~ msgctxt "ControllerConfig"
|
||||
#~ msgid "Port"
|
||||
#~ msgstr "Entrada"
|
||||
|
||||
#~ msgctxt "ControllerConfig"
|
||||
#~ msgid "Controller"
|
||||
#~ msgstr "Controle"
|
||||
@@ -0,0 +1,621 @@
|
||||
# gopher64 - Russian Translation
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: gopher64\n"
|
||||
"POT-Creation-Date: 2026-06-30 09:43+0000\n"
|
||||
"PO-Revision-Date: 2026-05-11 12:00+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Russian\n"
|
||||
"Language: ru\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
|
||||
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
|
||||
#: src/ui/gui/about_page.slint:17
|
||||
msgctxt "NewVersionButton"
|
||||
msgid "New version available! Click here to download"
|
||||
msgstr "Доступна новая версия! Нажмите здесь для загрузки"
|
||||
|
||||
#: src/ui/gui/about_page.slint:33
|
||||
msgctxt "About"
|
||||
msgid "About"
|
||||
msgstr "О программе"
|
||||
|
||||
#: src/ui/gui/about_page.slint:40
|
||||
msgctxt "About"
|
||||
msgid "Wiki"
|
||||
msgstr "Вики"
|
||||
|
||||
#: src/ui/gui/about_page.slint:50
|
||||
msgctxt "About"
|
||||
msgid "Discord Server"
|
||||
msgstr "Сервер Discord"
|
||||
|
||||
#: src/ui/gui/about_page.slint:60
|
||||
msgctxt "About"
|
||||
msgid "Source Code"
|
||||
msgstr "Исходный код"
|
||||
|
||||
#: src/ui/gui/about_page.slint:70
|
||||
msgctxt "About"
|
||||
msgid "Donate (via Patreon)"
|
||||
msgstr "Пожертвовать (через Patreon)"
|
||||
|
||||
#: src/ui/gui/about_page.slint:77
|
||||
msgctxt "About"
|
||||
msgid "Donate (via GitHub)"
|
||||
msgstr "Пожертвовать (через GitHub)"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:164
|
||||
msgctxt "Menu"
|
||||
msgid "Local Game"
|
||||
msgstr "Локальная игра"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:168
|
||||
msgctxt "Menu"
|
||||
msgid "Controller Config"
|
||||
msgstr "Настройка контроллера"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:172
|
||||
msgctxt "Menu"
|
||||
msgid "Netplay"
|
||||
msgstr "Сетевая игра"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:176
|
||||
msgctxt "Menu"
|
||||
msgid "Cheats"
|
||||
msgstr "Читы"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:180
|
||||
msgctxt "Menu"
|
||||
msgid "RetroAchievements"
|
||||
msgstr "RetroAchievements"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:184
|
||||
msgctxt "Menu"
|
||||
msgid "Settings"
|
||||
msgstr "Настройки"
|
||||
|
||||
#: src/ui/gui/appwindow.slint:188
|
||||
msgctxt "Menu"
|
||||
msgid "About"
|
||||
msgstr "О программе"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:15
|
||||
msgctxt "Cheats"
|
||||
msgid "Cheats"
|
||||
msgstr "Читы"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:25
|
||||
msgctxt "Cheats"
|
||||
msgid "Select ROM"
|
||||
msgstr "Выбрать ROM"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:41
|
||||
msgctxt "Cheats"
|
||||
msgid "Clear Cheats For All ROMs"
|
||||
msgstr "Очистить читы для всех ROMов"
|
||||
|
||||
#: src/ui/gui/cheat_page.slint:83
|
||||
msgctxt "Cheats"
|
||||
msgid "No Cheats Available"
|
||||
msgstr "Читы недоступны"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:80
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Enabled"
|
||||
msgstr "Включено"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:93
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Transfer Pak"
|
||||
msgstr "Transfer Pak"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:107
|
||||
msgctxt "ControllerSettingsGrid"
|
||||
msgid "Profile"
|
||||
msgstr "Профиль"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:157
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Configuration"
|
||||
msgstr "Настройка контроллера"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:171
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Configure Input Profile"
|
||||
msgstr "Настроить профиль ввода"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:180
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Settings"
|
||||
msgstr "Настройки контроллера"
|
||||
|
||||
#: src/ui/gui/controller_page.slint:190
|
||||
msgctxt "ControllerConfig"
|
||||
msgid "Controller Assignment"
|
||||
msgstr "Назначение контроллера"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:12
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Create/Modify Input Profile"
|
||||
msgstr "Создать/изменить профиль ввода"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:25
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Enter profile name to create or modify"
|
||||
msgstr "Введите имя профиля для создания или изменения"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:38
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Deadzone Percentage"
|
||||
msgstr "Процент мёртвой зоны"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:61
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Use DirectInput"
|
||||
msgstr "Использовать DirectInput"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:74
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Start Profile Creation Wizard"
|
||||
msgstr "Запустить мастер создания профиля"
|
||||
|
||||
#: src/ui/gui/input_profile.slint:82
|
||||
msgctxt "InputProfileDialog"
|
||||
msgid "Cancel"
|
||||
msgstr "Отмена"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:19
|
||||
msgctxt "LocalGame"
|
||||
msgid "Local Game"
|
||||
msgstr "Локальная игра"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:26
|
||||
msgctxt "LocalGame"
|
||||
msgid "Open ROM"
|
||||
msgstr "Открыть ROM"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:37
|
||||
msgctxt "LocalGame"
|
||||
msgid "Open Saves Folder"
|
||||
msgstr "Открыть папку сохранений"
|
||||
|
||||
#: src/ui/gui/localgame_page.slint:54
|
||||
msgctxt "LocalGame"
|
||||
msgid "Recent ROMs"
|
||||
msgstr "Недавние ROMы"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:7
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Netplay: Create Session"
|
||||
msgstr "Сетевая игра: Создать сессию"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:22
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Session Name:"
|
||||
msgstr "Имя сессии:"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:26
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter session name"
|
||||
msgstr "Введите имя сессии"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:36
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Password (Optional):"
|
||||
msgstr "Пароль (необязательно):"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:40
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter session password"
|
||||
msgstr "Введите пароль сессии"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:51
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Player Name:"
|
||||
msgstr "Имя игрока:"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:55
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Enter player name"
|
||||
msgstr "Введите имя игрока"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:66
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Select ROM"
|
||||
msgstr "Выбрать ROM"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:82
|
||||
msgctxt "NetplayCreate"
|
||||
msgid ""
|
||||
"This game has cheats enabled. Cheats will be synchronized with other players."
|
||||
msgstr ""
|
||||
"Для этой игры включены читы. Читы будут синхронизированы с другими игроками."
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:91
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Create Session"
|
||||
msgstr "Создать сессию"
|
||||
|
||||
#: src/ui/gui/netplay_create.slint:107
|
||||
msgctxt "NetplayCreate"
|
||||
msgid "Cancel"
|
||||
msgstr "Отмена"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:7
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Netplay: Join Session"
|
||||
msgstr "Сетевая игра: Присоединиться к сессии"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:18
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Player Name (required):"
|
||||
msgstr "Имя игрока (обязательно):"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:22
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Enter player name"
|
||||
msgstr "Введите имя игрока"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:31
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Password (if required)"
|
||||
msgstr "Пароль (если требуется)"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:42
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Select ROM"
|
||||
msgstr "Выбрать ROM"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:57
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Refreshing session list, please wait..."
|
||||
msgstr "Обновление списка сессий, пожалуйста, подождите..."
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:62
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Click on a session below to select it"
|
||||
msgstr "Нажмите на сессию ниже, чтобы выбрать её"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:67
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "No sessions available. Please refresh to check again."
|
||||
msgstr "Нет доступных сессий. Пожалуйста, обновите, чтобы проверить снова."
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:73
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Session Name"
|
||||
msgstr "Имя сессии"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:74
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Game Name"
|
||||
msgstr "Название игры"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:75
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Password"
|
||||
msgstr "Пароль"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:76
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Cheats"
|
||||
msgstr "Читы"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:90
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Refresh Session List"
|
||||
msgstr "Обновить список сессий"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:105
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Join Session"
|
||||
msgstr "Присоединиться к сессии"
|
||||
|
||||
#: src/ui/gui/netplay_join.slint:117
|
||||
msgctxt "NetplayJoin"
|
||||
msgid "Cancel"
|
||||
msgstr "Отмена"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:31
|
||||
msgctxt "Netplay"
|
||||
msgid "Netplay"
|
||||
msgstr "Сетевая игра"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:39
|
||||
msgctxt "Netplay"
|
||||
msgid "Create Session"
|
||||
msgstr "Создать сессию"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:50
|
||||
msgctxt "Netplay"
|
||||
msgid "Join Session"
|
||||
msgstr "Присоединиться к сессии"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:64
|
||||
msgctxt "Netplay"
|
||||
msgid "Netplay Discord Channel"
|
||||
msgstr "Канал Discord для сетевой игры"
|
||||
|
||||
#: src/ui/gui/netplay_page.slint:71
|
||||
msgctxt "Netplay"
|
||||
msgid "Provide Feedback"
|
||||
msgstr "Оставить отзыв"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:17
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Netplay: Waiting Room"
|
||||
msgstr "Сетевая игра: Комната ожидания"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:29
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Session Name:"
|
||||
msgstr "Имя сессии:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:35
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Game Name:"
|
||||
msgstr "Название игры:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:45
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 1:"
|
||||
msgstr "Игрок 1:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:49
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 2:"
|
||||
msgstr "Игрок 2:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:55
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 3:"
|
||||
msgstr "Игрок 3:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:59
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Player 4:"
|
||||
msgstr "Игрок 4:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:73
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Enter chat message"
|
||||
msgstr "Введите сообщение чата"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:84
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Send Message"
|
||||
msgstr "Отправить сообщение"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:94
|
||||
msgctxt "NetplayWait"
|
||||
msgid ""
|
||||
"Setting the input delay too low will cause the game to lag.\n"
|
||||
"You can lower the input delay in-game by pressing Alt+[ and raise it with "
|
||||
"Alt+]."
|
||||
msgstr ""
|
||||
"Слишком низкая задержка ввода вызовет лаги в игре.\n"
|
||||
"Вы можете уменьшить задержку ввода в игре, нажав Alt+[, и увеличить её с "
|
||||
"помощью Alt+]."
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:101
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Local Input Delay:"
|
||||
msgstr "Локальная задержка ввода:"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:118
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Recommended: "
|
||||
msgstr "Рекомендуется: "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Start Game"
|
||||
msgstr "Начать игру"
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:127
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Waiting for P2P mesh "
|
||||
msgstr "Ожидание P2P-сети "
|
||||
|
||||
#: src/ui/gui/netplay_wait.slint:135
|
||||
msgctxt "NetplayWait"
|
||||
msgid "Cancel"
|
||||
msgstr "Отмена"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:19
|
||||
msgctxt "RAData"
|
||||
msgid "Not currently logged in"
|
||||
msgstr "В данный момент не выполнен вход"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:27
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "RetroAchievements"
|
||||
msgstr "RetroAchievements"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:36
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Enabled"
|
||||
msgstr "Включено"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:51
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Hardcore mode"
|
||||
msgstr "Хардкорный режим"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:60
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Challenge Indicators"
|
||||
msgstr "Индикаторы испытаний"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:69
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Leaderboard Trackers"
|
||||
msgstr "Трекеры таблицы лидеров"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:78
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Discord Rich Presence"
|
||||
msgstr "Расширенный статус Discord"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:96
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "View Profile"
|
||||
msgstr "Просмотреть профиль"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:106
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Username"
|
||||
msgstr "Имя пользователя"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:116
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Password"
|
||||
msgstr "Пароль"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:125
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "Login"
|
||||
msgstr "Войти"
|
||||
|
||||
#: src/ui/gui/retroachievements_page.slint:141
|
||||
msgctxt "RetroAchievements"
|
||||
msgid "View Supported Games"
|
||||
msgstr "Просмотреть поддерживаемые игры"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:25
|
||||
msgctxt "Settings"
|
||||
msgid "Settings"
|
||||
msgstr "Настройки"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:32
|
||||
msgctxt "Settings"
|
||||
msgid "Game"
|
||||
msgstr "Игра"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:38
|
||||
msgctxt "Settings"
|
||||
msgid "Resolution:"
|
||||
msgstr "Разрешение:"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:51
|
||||
msgctxt "Settings"
|
||||
msgid "SSAA downscaling"
|
||||
msgstr "SSAA даунскейлинг"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:59
|
||||
msgctxt "Settings"
|
||||
msgid "Integer Scaling"
|
||||
msgstr "Целочисленное масштабирование"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:67
|
||||
msgctxt "Settings"
|
||||
msgid "Fullscreen"
|
||||
msgstr "Полноэкранный режим"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:75
|
||||
msgctxt "Settings"
|
||||
msgid "Widescreen (stretch)"
|
||||
msgstr "Широкоэкранный режим (растянуто)"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:83
|
||||
msgctxt "Settings"
|
||||
msgid "VSync"
|
||||
msgstr "Вертикальная синхронизация"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:91
|
||||
msgctxt "Settings"
|
||||
msgid "Apply CRT shader"
|
||||
msgstr "Применить шейдер ЭЛТ"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:103
|
||||
msgctxt "Settings"
|
||||
msgid "Overclock N64 CPU (may cause bugs)"
|
||||
msgstr "Разгон процессора N64 (может вызвать ошибки)"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:111
|
||||
msgctxt "Settings"
|
||||
msgid "Disable Expansion Pak"
|
||||
msgstr "Отключить Expansion Pak"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:119
|
||||
msgctxt "Settings"
|
||||
msgid "Enable Rewind"
|
||||
msgstr "Включить перемотку назад"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:127
|
||||
msgctxt "Settings"
|
||||
msgid "Emulate UNFLoader"
|
||||
msgstr "Эмулировать UNFLoader"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:135
|
||||
msgctxt "Settings"
|
||||
msgid "Emulate VRU"
|
||||
msgstr "Эмулировать VRU"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:144
|
||||
msgctxt "Settings"
|
||||
msgid "UI"
|
||||
msgstr "Интерфейс"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:150
|
||||
msgctxt "Settings"
|
||||
msgid "Theme:"
|
||||
msgstr "Тема:"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "System"
|
||||
msgstr "Система"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "Dark"
|
||||
msgstr "Тёмная"
|
||||
|
||||
#: src/ui/gui/settings_page.slint:154
|
||||
msgctxt "Settings"
|
||||
msgid "Light"
|
||||
msgstr "Светлая"
|
||||
|
||||
#: src/ui/gui/vru_dialog.slint:7
|
||||
msgctxt "VruDialog"
|
||||
msgid "What Would You Like To Say?"
|
||||
msgstr "Что вы хотите сказать?"
|
||||
|
||||
#~ msgctxt "NetplayCreate"
|
||||
#~ msgid "Server:"
|
||||
#~ msgstr "Сервер:"
|
||||
|
||||
#~ msgctxt "CustomServerDialog"
|
||||
#~ msgid "Enter a custom server URL"
|
||||
#~ msgstr "Введите URL пользовательского сервера"
|
||||
|
||||
#~ msgctxt "NetplayJoin"
|
||||
#~ msgid "Custom Server URL (optional):"
|
||||
#~ msgstr "URL пользовательского сервера (необязательно):"
|
||||
|
||||
#~ msgctxt "NetplayJoin"
|
||||
#~ msgid "Server"
|
||||
#~ msgstr "Сервер"
|
||||
|
||||
#~ msgctxt "NetplayWait"
|
||||
#~ msgid "Ping:"
|
||||
#~ msgstr "Пинг:"
|
||||
|
||||
#~ msgctxt "NetplayCreate"
|
||||
#~ msgid "Creating server, please wait...This may take about 30 seconds."
|
||||
#~ msgstr ""
|
||||
#~ "Создание сервера, пожалуйста, подождите... Это может занять около 30 "
|
||||
#~ "секунд."
|
||||
|
||||
#~ msgctxt "ControllerConfig"
|
||||
#~ msgid "Port"
|
||||
#~ msgstr "Порт"
|
||||
|
||||
#~ msgctxt "ControllerConfig"
|
||||
#~ msgid "Controller"
|
||||
#~ msgstr "Контроллер"
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M440-280h80v-240h-80v240Zm68.5-331.5Q520-623 520-640t-11.5-28.5Q497-680 480-680t-28.5 11.5Q440-657 440-640t11.5 28.5Q463-600 480-600t28.5-11.5ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
|
||||
|
After Width: | Height: | Size: 553 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M280-120v-80h160v-124q-49-11-87.5-41.5T296-442q-75-9-125.5-65.5T120-640v-40q0-33 23.5-56.5T200-760h80v-80h400v80h80q33 0 56.5 23.5T840-680v40q0 76-50.5 132.5T664-442q-18 46-56.5 76.5T520-324v124h160v80H280Zm0-408v-152h-80v40q0 38 22 68.5t58 43.5Zm285 93q35-35 35-85v-240H360v240q0 50 35 85t85 35q50 0 85-35Zm115-93q36-13 58-43.5t22-68.5v-40h-80v152Zm-200-52Z"/></svg>
|
||||
|
After Width: | Height: | Size: 483 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M480-160v-80h320v80H480ZM220-320l-56-56 183-184-183-184 56-56 240 240-240 240Z"/></svg>
|
||||
|
After Width: | Height: | Size: 203 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M189-160q-60 0-102.5-43T42-307q0-9 1-18t3-18l84-336q14-54 57-87.5t98-33.5h390q55 0 98 33.5t57 87.5l84 336q2 9 3.5 18.5T919-306q0 61-43.5 103.5T771-160q-42 0-78-22t-54-60l-28-58q-5-10-15-15t-21-5H385q-11 0-21 5t-15 15l-28 58q-18 38-54 60t-78 22Zm3-80q19 0 34.5-10t23.5-27l28-57q15-31 44-48.5t63-17.5h190q34 0 63 18t45 48l28 57q8 17 23.5 27t34.5 10q28 0 48-18.5t21-46.5q0 1-2-19l-84-335q-7-27-28-44t-49-17H285q-28 0-49.5 17T208-659l-84 335q-2 6-2 18 0 28 20.5 47t49.5 19Zm376.5-291.5Q580-543 580-560t-11.5-28.5Q557-600 540-600t-28.5 11.5Q500-577 500-560t11.5 28.5Q523-520 540-520t28.5-11.5Zm80-80Q660-623 660-640t-11.5-28.5Q637-680 620-680t-28.5 11.5Q580-657 580-640t11.5 28.5Q603-600 620-600t28.5-11.5Zm0 160Q660-463 660-480t-11.5-28.5Q637-520 620-520t-28.5 11.5Q580-497 580-480t11.5 28.5Q603-440 620-440t28.5-11.5Zm80-80Q740-543 740-560t-11.5-28.5Q717-600 700-600t-28.5 11.5Q660-577 660-560t11.5 28.5Q683-520 700-520t28.5-11.5Zm-367 63Q370-477 370-490v-40h40q13 0 21.5-8.5T440-560q0-13-8.5-21.5T410-590h-40v-40q0-13-8.5-21.5T340-660q-13 0-21.5 8.5T310-630v40h-40q-13 0-21.5 8.5T240-560q0 13 8.5 21.5T270-530h40v40q0 13 8.5 21.5T340-460q13 0 21.5-8.5ZM480-480Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M240-200h120v-240h240v240h120v-360L480-740 240-560v360Zm-80 80v-480l320-240 320 240v480H520v-240h-80v240H160Zm320-350Z"/></svg>
|
||||
|
After Width: | Height: | Size: 243 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M260-160q-91 0-155.5-63T40-377q0-78 47-139t123-78q25-92 100-149t170-57q117 0 198.5 81.5T760-520q69 8 114.5 59.5T920-340q0 75-52.5 127.5T740-160H260Zm0-80h480q42 0 71-29t29-71q0-42-29-71t-71-29h-60v-80q0-83-58.5-141.5T480-720q-83 0-141.5 58.5T280-520h-20q-58 0-99 41t-41 99q0 58 41 99t99 41Zm220-240Z"/></svg>
|
||||
|
After Width: | Height: | Size: 424 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="m370-80-16-128q-13-5-24.5-12T307-235l-119 50L78-375l103-78q-1-7-1-13.5v-27q0-6.5 1-13.5L78-585l110-190 119 50q11-8 23-15t24-12l16-128h220l16 128q13 5 24.5 12t22.5 15l119-50 110 190-103 78q1 7 1 13.5v27q0 6.5-2 13.5l103 78-110 190-118-50q-11 8-23 15t-24 12L590-80H370Zm70-80h79l14-106q31-8 57.5-23.5T639-327l99 41 39-68-86-65q5-14 7-29.5t2-31.5q0-16-2-31.5t-7-29.5l86-65-39-68-99 42q-22-23-48.5-38.5T533-694l-13-106h-79l-14 106q-31 8-57.5 23.5T321-633l-99-41-39 68 86 64q-5 15-7 30t-2 32q0 16 2 31t7 30l-86 65 39 68 99-42q22 23 48.5 38.5T427-266l13 106Zm42-180q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Zm-2-140Z"/></svg>
|
||||
|
After Width: | Height: | Size: 771 B |
@@ -4,51 +4,80 @@
|
||||
#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;
|
||||
bool PAL;
|
||||
bool widescreen;
|
||||
bool fullscreen;
|
||||
bool integer_scaling;
|
||||
uint32_t upscale;
|
||||
bool crt;
|
||||
} 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 ssaa;
|
||||
bool crt;
|
||||
} GFX_INFO;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
bool emu_running;
|
||||
bool save_state;
|
||||
bool load_state;
|
||||
bool enable_speedlimiter;
|
||||
bool lower_volume;
|
||||
bool raise_volume;
|
||||
bool paused;
|
||||
bool frame_advance;
|
||||
uint32_t save_state_slot;
|
||||
} CALL_BACK;
|
||||
typedef struct {
|
||||
bool emu_running;
|
||||
bool save_state;
|
||||
bool load_state;
|
||||
bool load_rewind;
|
||||
bool reset_game;
|
||||
bool enable_speedlimiter;
|
||||
bool lower_volume;
|
||||
bool raise_volume;
|
||||
bool decrease_input_delay;
|
||||
bool increase_input_delay;
|
||||
bool paused;
|
||||
bool frame_advance;
|
||||
uint32_t save_state_slot;
|
||||
} CALL_BACK;
|
||||
|
||||
void rdp_init(void *_window, GFX_INFO _gfx_info, const void *font, size_t font_size);
|
||||
void rdp_close();
|
||||
void rdp_set_vi_register(uint32_t reg, uint32_t value);
|
||||
void rdp_update_screen();
|
||||
CALL_BACK rdp_check_callback();
|
||||
uint64_t rdp_process_commands();
|
||||
void rdp_onscreen_message(const char *_message);
|
||||
void rdp_new_processor(GFX_INFO _gfx_info);
|
||||
size_t rdp_state_size();
|
||||
void rdp_save_state(uint8_t *state);
|
||||
void rdp_load_state(const uint8_t *state);
|
||||
typedef struct {
|
||||
uint32_t joystick_id;
|
||||
bool connected;
|
||||
} JoystickEvent;
|
||||
|
||||
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_idle();
|
||||
void rdp_onscreen_message(const char *message, MESSAGE_LENGTH milliseconds);
|
||||
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(GFX_INFO _gfx_info, const uint8_t *state);
|
||||
void rdp_set_fps(uint32_t fps, uint32_t vis);
|
||||
|
||||
JoystickEvent get_joystick_event();
|
||||
|
||||
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,73 +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 (unsigned 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()
|
||||
{
|
||||
}
|
||||
void SDL_WSIPlatform::poll_input() { SDL_PumpEvents(); }
|
||||
|
||||
void SDL_WSIPlatform::poll_input_async(Granite::InputTrackerHandler *handler)
|
||||
{
|
||||
}
|
||||
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,421 @@
|
||||
#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_rdram = NULL;
|
||||
static size_t g_rdram_size = 0;
|
||||
static bool g_challenge = false;
|
||||
static bool g_leaderboard = false;
|
||||
static rc_client_leaderboard_list_t *g_leaderboard_list = NULL;
|
||||
|
||||
static char g_rich_presence_buffer[512];
|
||||
|
||||
static uint32_t read_memory(uint32_t address, uint8_t *buffer,
|
||||
uint32_t num_bytes, rc_client_t *client) {
|
||||
if (address + num_bytes > g_rdram_size)
|
||||
memset(buffer, 0, num_bytes);
|
||||
else
|
||||
memcpy(buffer, &g_rdram[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;
|
||||
}
|
||||
|
||||
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(const char **game_title, const char **game_image_url) {
|
||||
if (!g_client) {
|
||||
*game_title = NULL;
|
||||
*game_image_url = NULL;
|
||||
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);
|
||||
|
||||
*game_title = NULL;
|
||||
*game_image_url = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
const rc_client_game_t *game = rc_client_get_game_info(g_client);
|
||||
if (!game) {
|
||||
*game_title = NULL;
|
||||
*game_image_url = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
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" : "Casual");
|
||||
|
||||
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);
|
||||
|
||||
*game_title = game->title;
|
||||
*game_image_url = game->badge_url;
|
||||
}
|
||||
|
||||
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_rdram(const uint8_t *rdram, size_t rdram_size) {
|
||||
g_rdram = rdram;
|
||||
g_rdram_size = rdram_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)
|
||||
if (!g_client)
|
||||
g_client = rc_client_create(read_memory, server_call);
|
||||
g_rdram = NULL;
|
||||
g_rdram_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); }
|
||||
|
||||
const char *ra_get_rich_presence() {
|
||||
size_t size = rc_client_get_rich_presence_message(
|
||||
g_client, g_rich_presence_buffer, sizeof(g_rich_presence_buffer));
|
||||
if (size == 0) {
|
||||
return NULL;
|
||||
}
|
||||
return g_rich_presence_buffer;
|
||||
}
|
||||
|
||||
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,33 @@
|
||||
#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(const char **game_title, const char **game_image_url);
|
||||
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_rdram(const uint8_t *rdram, size_t rdram_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();
|
||||
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);
|
||||
|
||||
const char *ra_get_rich_presence();
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
void ra_display_inprogress_achievements(void *userdata);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "1.89.0"
|
||||
channel = "1.96.1"
|
||||
|
||||
@@ -8,23 +8,31 @@ pub struct DecodedCheat {
|
||||
data: u16,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, 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>>,
|
||||
cheat_settings: rustc_hash::FxHashMap<String, Option<String>>,
|
||||
) {
|
||||
let cheats =
|
||||
serde_json::from_slice::<ui::cheats::Cheats>(include_bytes!("../data/cheats.json"))
|
||||
.unwrap()
|
||||
.get(&ui::storage::get_game_crc(&device.cart.rom))
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
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() {
|
||||
@@ -40,7 +48,8 @@ pub fn init(
|
||||
{
|
||||
option_value = Some(found_option_value.to_string());
|
||||
} else {
|
||||
panic!("Cheat option: {option} not found");
|
||||
eprintln!("Cheat option: {option} not found");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,21 +66,27 @@ pub fn init(
|
||||
}
|
||||
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(),
|
||||
});
|
||||
if let Ok(data) = u16::from_str_radix(split.next().unwrap(), 16) {
|
||||
decoded_cheat.push(DecodedCheat {
|
||||
code_type: (first_part >> 24) as u8,
|
||||
address: first_part & 0x00FFFFFF,
|
||||
data,
|
||||
})
|
||||
} else {
|
||||
eprintln!("Could not parse data for: {}", cheat_setting.0);
|
||||
};
|
||||
}
|
||||
if !decoded_cheat.is_empty() {
|
||||
device.cheats.cheats.push(decoded_cheat);
|
||||
}
|
||||
device.cheats.cheats.push(decoded_cheat);
|
||||
} else {
|
||||
println!("Could not find cheat: {}", cheat_setting.0);
|
||||
eprintln!("Could not find cheat: {}", cheat_setting.0);
|
||||
}
|
||||
}
|
||||
|
||||
if !device.cheats.cheats.is_empty() {
|
||||
device.cheats.enabled = true;
|
||||
ui::video::onscreen_message(&device.ui, "Cheats enabled");
|
||||
ui::video::onscreen_message("Cheats enabled", ui::video::MESSAGE_LENGTH_MESSAGE_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,10 +10,13 @@ include!(concat!(env!("OUT_DIR"), "/simd_bindings.rs"));
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use std::arch::x86_64::*;
|
||||
|
||||
use rand_chacha::rand_core::{RngCore, SeedableRng};
|
||||
const N64_EXTENSIONS_UNCOMPRESSED: [&str; 8] =
|
||||
["n64", "v64", "z64", "bin", "N64", "V64", "Z64", "BIN"];
|
||||
|
||||
use crate::{cheats, netplay, ui};
|
||||
use std::{collections::HashMap, fs, io::Read};
|
||||
use rand::{Rng, SeedableRng};
|
||||
|
||||
use crate::{cheats, netplay, retroachievements, savestates, ui};
|
||||
use std::io::Read;
|
||||
|
||||
pub mod ai;
|
||||
pub mod cache;
|
||||
@@ -44,24 +47,55 @@ pub mod tlb;
|
||||
pub mod unmapped;
|
||||
pub mod vi;
|
||||
|
||||
pub fn run_game(device: &mut Device, rom_contents: Vec<u8>, game_settings: ui::gui::GameSettings) {
|
||||
device.ui.video.fullscreen = game_settings.fullscreen;
|
||||
pub fn run_game(
|
||||
device: &mut Device,
|
||||
rom_contents: &[u8],
|
||||
game_settings: ui::GameSettings,
|
||||
ra_config: retroachievements::RAConfig,
|
||||
netplay_config: Option<netplay::NetplayConfig>,
|
||||
) {
|
||||
device.cpu.overclock = game_settings.overclock;
|
||||
if game_settings.disable_expansion_pak {
|
||||
device.rdram.size = 0x400000;
|
||||
}
|
||||
|
||||
init_rng(device);
|
||||
if let Some(slot) = game_settings.load_savestate_slot {
|
||||
device.ui.storage.save_state_slot = slot;
|
||||
device.savestate.load_state = true;
|
||||
}
|
||||
|
||||
cart::rom::init(device, rom_contents); // cart needs to come before rdram
|
||||
|
||||
// rdram pointer is shared with parallel-rdp
|
||||
// rdram pointer is shared with parallel-rdp and retroachievements
|
||||
rdram::init(device);
|
||||
|
||||
ui::video::init(device);
|
||||
ui::audio::init(&mut device.ui, device.ai.freq);
|
||||
ui::video::init(device, netplay_config.is_some());
|
||||
ui::audio::init(device);
|
||||
ui::input::init(&mut device.ui);
|
||||
|
||||
// must be after video init
|
||||
if let Some(netplay_config) = &netplay_config {
|
||||
device.netplay = netplay::init(
|
||||
device,
|
||||
netplay_config,
|
||||
cart::rom::is_system_pal(rom_contents),
|
||||
);
|
||||
if device.netplay.is_none() {
|
||||
ui::input::close(&mut device.ui);
|
||||
ui::audio::close(&mut device.ui);
|
||||
ui::video::close(&device.ui);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
init_rng_rtc(device);
|
||||
|
||||
// must be after video init
|
||||
let (discord_watch_tx, discord_handle) = if ra_config.enabled && device.netplay.is_none() {
|
||||
retroachievements::load_game(rom_contents, rom_contents.len(), ra_config.rich_presence)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
mi::init(device);
|
||||
pif::init(device);
|
||||
if device.ui.config.input.emulate_vru && device.netplay.is_none() {
|
||||
@@ -76,32 +110,53 @@ pub fn run_game(device: &mut Device, rom_contents: Vec<u8>, game_settings: ui::g
|
||||
|
||||
ui::storage::init(&mut device.ui, &device.cart.rom);
|
||||
ui::storage::load_saves(&mut device.ui, &mut device.netplay);
|
||||
ui::storage::format_saves(device);
|
||||
|
||||
cheats::init(device, game_settings.cheats);
|
||||
if !retroachievements::get_hardcore() {
|
||||
cheats::init(device, game_settings.cheats);
|
||||
}
|
||||
|
||||
cpu::run(device);
|
||||
|
||||
retroachievements::unload_game(discord_watch_tx, discord_handle);
|
||||
|
||||
ui::storage::write_saves(device);
|
||||
|
||||
if let Some(netplay) = &mut device.netplay {
|
||||
netplay::close(netplay);
|
||||
}
|
||||
|
||||
ui::input::close(&mut device.ui);
|
||||
ui::audio::close(&mut device.ui);
|
||||
ui::video::close(&device.ui);
|
||||
}
|
||||
|
||||
fn set_rng() -> rand_chacha::ChaCha8Rng {
|
||||
rand_chacha::ChaCha8Rng::from_os_rng()
|
||||
fn set_rng() -> rand::rngs::Xoshiro256PlusPlus {
|
||||
rand::rngs::Xoshiro256PlusPlus::try_from_rng(&mut rand::rngs::SysRng).unwrap()
|
||||
}
|
||||
|
||||
fn init_rng(device: &mut Device) {
|
||||
fn init_rng_rtc(device: &mut Device) {
|
||||
let mut rng_seed = set_rng().next_u64();
|
||||
if device.netplay.is_some() {
|
||||
let netplay = device.netplay.as_mut().unwrap();
|
||||
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_chacha::ChaCha8Rng::seed_from_u64(rng_seed);
|
||||
device.rng = rand::rngs::Xoshiro256PlusPlus::seed_from_u64(rng_seed);
|
||||
|
||||
device.cart.rtc_timestamp = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as i64;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn swap_rom(contents: Vec<u8>) -> Option<Vec<u8>> {
|
||||
@@ -130,36 +185,47 @@ fn swap_rom(contents: Vec<u8>) -> Option<Vec<u8>> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_rom_contents(file_path: &std::path::Path) -> Option<Vec<u8>> {
|
||||
pub fn get_rom_contents(file_path: &std::path::PathBuf) -> Option<Vec<u8>> {
|
||||
let mut contents = vec![];
|
||||
if file_path.extension().unwrap().eq_ignore_ascii_case("zip") {
|
||||
let zip_file = fs::File::open(file_path).unwrap();
|
||||
if file_path
|
||||
.extension()
|
||||
.unwrap_or_default()
|
||||
.eq_ignore_ascii_case("zip")
|
||||
{
|
||||
#[cfg(target_os = "android")]
|
||||
let zip_file = ui::android::get_file_from_uri(file_path).unwrap();
|
||||
#[cfg(not(target_os = "android"))]
|
||||
let zip_file = std::fs::File::open(file_path).unwrap();
|
||||
let mut archive = zip::ZipArchive::new(zip_file).unwrap();
|
||||
for i in 0..archive.len() {
|
||||
let mut file = archive.by_index(i).unwrap();
|
||||
let extension = file
|
||||
.enclosed_name()
|
||||
.unwrap()
|
||||
.extension()
|
||||
.unwrap()
|
||||
.to_ascii_lowercase();
|
||||
if extension == "z64" || extension == "n64" || extension == "v64" {
|
||||
if let Some(extension) = file.enclosed_name().unwrap().extension()
|
||||
&& N64_EXTENSIONS_UNCOMPRESSED.contains(&extension.to_str().unwrap())
|
||||
{
|
||||
file.read_to_end(&mut contents)
|
||||
.expect("could not read zip file");
|
||||
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")
|
||||
{
|
||||
#[cfg(target_os = "android")]
|
||||
let sevenz_file = ui::android::get_file_from_uri(file_path).unwrap();
|
||||
#[cfg(not(target_os = "android"))]
|
||||
let sevenz_file = std::fs::File::open(file_path).unwrap();
|
||||
let mut archive =
|
||||
sevenz_rust2::ArchiveReader::open(file_path, sevenz_rust2::Password::empty()).unwrap();
|
||||
sevenz_rust2::ArchiveReader::new(sevenz_file, sevenz_rust2::Password::empty()).unwrap();
|
||||
|
||||
let mut found = false;
|
||||
archive
|
||||
.for_each_entries(
|
||||
&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"))
|
||||
&& let Some(extension) = std::path::PathBuf::from(entry.name()).extension()
|
||||
&& N64_EXTENSIONS_UNCOMPRESSED.contains(&extension.to_str().unwrap())
|
||||
{
|
||||
reader
|
||||
.read_to_end(&mut contents)
|
||||
@@ -174,7 +240,11 @@ pub fn get_rom_contents(file_path: &std::path::Path) -> Option<Vec<u8>> {
|
||||
)
|
||||
.expect("ok");
|
||||
} else {
|
||||
contents = fs::read(file_path).expect("Should have been able to read the file");
|
||||
#[cfg(target_os = "android")]
|
||||
let mut file = ui::android::get_file_from_uri(file_path).unwrap();
|
||||
#[cfg(not(target_os = "android"))]
|
||||
let mut file = std::fs::File::open(file_path).unwrap();
|
||||
file.read_to_end(&mut contents).unwrap();
|
||||
}
|
||||
|
||||
if contents.is_empty() {
|
||||
@@ -184,15 +254,28 @@ pub fn get_rom_contents(file_path: &std::path::Path) -> Option<Vec<u8>> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct SpeedLimiter {
|
||||
pub limit_freq: u64,
|
||||
#[serde(skip)]
|
||||
#[serde(default = "std::time::Instant::now")]
|
||||
pub limit_freq_check: std::time::Instant,
|
||||
pub frame_counter: u64,
|
||||
pub enabled: bool,
|
||||
pub min_wait_time: std::time::Duration,
|
||||
#[serde(skip)]
|
||||
pub next_pace_deadline: Option<std::time::Instant>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Device {
|
||||
#[serde(skip)]
|
||||
pub netplay: Option<netplay::Netplay>,
|
||||
#[serde(skip, default = "ui::Ui::default")]
|
||||
#[serde(skip)]
|
||||
pub ui: ui::Ui,
|
||||
pub speed_limiter: SpeedLimiter,
|
||||
pub byte_swap: usize,
|
||||
pub save_state: bool,
|
||||
pub load_state: bool,
|
||||
pub savestate: savestates::Savestate,
|
||||
pub cpu: cpu::Cpu,
|
||||
pub pif: pif::Pif,
|
||||
pub cart: cart::Cart,
|
||||
@@ -206,10 +289,8 @@ pub struct Device {
|
||||
pub ai: ai::Ai,
|
||||
pub si: si::Si,
|
||||
pub ri: ri::Ri,
|
||||
#[serde(skip, default = "set_rng")]
|
||||
pub rng: rand_chacha::ChaCha8Rng,
|
||||
pub rng: rand::rngs::Xoshiro256PlusPlus,
|
||||
pub vru: controller::vru::Vru,
|
||||
pub vru_window: controller::vru::VruWindow,
|
||||
pub transferpaks: [controller::transferpak::TransferPak; 4],
|
||||
pub cheats: cheats::Cheats,
|
||||
}
|
||||
@@ -219,7 +300,27 @@ pub fn zero_m128i() -> __m128i {
|
||||
}
|
||||
|
||||
impl Device {
|
||||
pub fn new() -> Device {
|
||||
pub fn clone_state(&mut self, device: &Device) {
|
||||
self.rng.clone_from(&device.rng);
|
||||
self.cpu.clone_from(&device.cpu);
|
||||
self.pif.clone_from(&device.pif);
|
||||
self.cart.clone_from(&device.cart);
|
||||
self.memory.clone_from(&device.memory);
|
||||
self.rsp.clone_from(&device.rsp);
|
||||
self.rdp.clone_from(&device.rdp);
|
||||
self.rdram.clone_from(&device.rdram);
|
||||
self.mi.clone_from(&device.mi);
|
||||
self.pi.clone_from(&device.pi);
|
||||
self.vi.clone_from(&device.vi);
|
||||
self.ai.clone_from(&device.ai);
|
||||
self.si.clone_from(&device.si);
|
||||
self.ri.clone_from(&device.ri);
|
||||
self.vru.clone_from(&device.vru);
|
||||
self.transferpaks.clone_from(&device.transferpaks);
|
||||
self.cheats.clone_from(&device.cheats);
|
||||
}
|
||||
|
||||
pub fn new(with_ui: bool) -> Box<Device> {
|
||||
let mut byte_swap: usize = 0;
|
||||
let test: [u8; 4] = [1, 2, 3, 4];
|
||||
// if the host computer is little endian, that means the RDRAM will be stored as little endian
|
||||
@@ -227,35 +328,41 @@ impl Device {
|
||||
if u32::from_le_bytes(test) == u32::from_ne_bytes(test) {
|
||||
byte_swap = 3;
|
||||
}
|
||||
Device {
|
||||
Box::new(Device {
|
||||
netplay: None,
|
||||
ui: ui::Ui::new(),
|
||||
ui: if with_ui {
|
||||
ui::Ui::new()
|
||||
} else {
|
||||
ui::Ui::default()
|
||||
},
|
||||
speed_limiter: SpeedLimiter {
|
||||
limit_freq: 2,
|
||||
limit_freq_check: std::time::Instant::now(),
|
||||
frame_counter: 0,
|
||||
enabled: true,
|
||||
min_wait_time: std::time::Duration::from_secs(1),
|
||||
next_pace_deadline: None,
|
||||
},
|
||||
byte_swap,
|
||||
save_state: false,
|
||||
load_state: false,
|
||||
savestate: savestates::Savestate {
|
||||
save_state: false,
|
||||
load_state: false,
|
||||
load_rewind: false,
|
||||
last_rewind_saved: 0.0,
|
||||
rewind_pool: std::sync::Arc::new(tokio::sync::Mutex::new(
|
||||
std::collections::BTreeMap::new(),
|
||||
)),
|
||||
},
|
||||
cpu: cpu::Cpu {
|
||||
cop0: cop0::Cop0 {
|
||||
regs: [0; cop0::COP0_REGS_COUNT as usize],
|
||||
reg_write_masks: [0; cop0::COP0_REGS_COUNT as usize],
|
||||
regs: [0; cop0::COP0_REGS_COUNT],
|
||||
reg_write_masks: [0; cop0::COP0_REGS_COUNT],
|
||||
reg_latch: 0,
|
||||
is_event: false,
|
||||
pending_compare_interrupt: false,
|
||||
instrs: [cop0::reserved; 32],
|
||||
instrs2: [cop0::reserved; 32],
|
||||
tlb_lut_w: vec![
|
||||
tlb::TlbLut {
|
||||
address: 0,
|
||||
cached: false,
|
||||
};
|
||||
0x100000
|
||||
],
|
||||
tlb_lut_r: vec![
|
||||
tlb::TlbLut {
|
||||
address: 0,
|
||||
cached: false,
|
||||
};
|
||||
0x100000
|
||||
],
|
||||
tlb_lut_w: rustc_hash::FxHashMap::default(),
|
||||
tlb_lut_r: rustc_hash::FxHashMap::default(),
|
||||
tlb_entries: [tlb::TlbEntry {
|
||||
mask: 0,
|
||||
vpn2: 0,
|
||||
@@ -341,9 +448,9 @@ impl Device {
|
||||
rtc_timestamp: 0,
|
||||
rtc: cart::AfRtc { control: 0x0200 },
|
||||
sc64: cart::sc64::Sc64 {
|
||||
regs: [0; cart::sc64::SC64_REGS_COUNT as usize],
|
||||
regs: [0; cart::sc64::SC64_REGS_COUNT],
|
||||
regs_locked: true,
|
||||
cfg: [0; cart::sc64::SC64_CFG_COUNT as usize],
|
||||
cfg: [0; cart::sc64::SC64_CFG_COUNT],
|
||||
sector: 0,
|
||||
buffer: vec![0; 8192],
|
||||
writeback_sector: vec![0; 256],
|
||||
@@ -379,7 +486,7 @@ impl Device {
|
||||
rdram: rdram::Rdram {
|
||||
mem: vec![],
|
||||
size: 0x800000,
|
||||
regs: [[0; rdram::RDRAM_REGS_COUNT as usize]; 4],
|
||||
regs: [[0; rdram::RDRAM_REGS_COUNT]; 4],
|
||||
},
|
||||
rsp: rsp_interface::Rsp {
|
||||
cpu: rsp_cpu::Cpu {
|
||||
@@ -424,8 +531,8 @@ impl Device {
|
||||
instrs: [rsp_su_instructions::reserved; 64],
|
||||
vec_instrs: [rsp_vu_instructions::reserved; 64],
|
||||
},
|
||||
regs: [0; rsp_interface::SP_REGS_COUNT as usize],
|
||||
regs2: [0; rsp_interface::SP_REGS2_COUNT as usize],
|
||||
regs: [0; rsp_interface::SP_REGS_COUNT],
|
||||
regs2: [0; rsp_interface::SP_REGS2_COUNT],
|
||||
mem: [0; 0x2000],
|
||||
last_status_value: 0,
|
||||
run_after_dma: false,
|
||||
@@ -437,19 +544,19 @@ impl Device {
|
||||
}; 2],
|
||||
},
|
||||
rdp: rdp::Rdp {
|
||||
regs_dpc: [0; rdp::DPC_REGS_COUNT as usize],
|
||||
regs_dps: [0; rdp::DPS_REGS_COUNT as usize],
|
||||
regs_dpc: [0; rdp::DPC_REGS_COUNT],
|
||||
regs_dps: [0; rdp::DPS_REGS_COUNT],
|
||||
wait_frozen: false,
|
||||
last_status_value: 0,
|
||||
},
|
||||
mi: mi::Mi {
|
||||
regs: [0; mi::MI_REGS_COUNT as usize],
|
||||
regs: [0; mi::MI_REGS_COUNT],
|
||||
},
|
||||
pi: pi::Pi {
|
||||
regs: [0; pi::PI_REGS_COUNT as usize],
|
||||
regs: [0; pi::PI_REGS_COUNT],
|
||||
},
|
||||
ai: ai::Ai {
|
||||
regs: [0; ai::AI_REGS_COUNT as usize],
|
||||
regs: [0; ai::AI_REGS_COUNT],
|
||||
last_read: 0,
|
||||
freq: 33600,
|
||||
delayed_carry: false,
|
||||
@@ -460,32 +567,21 @@ impl Device {
|
||||
}; 2],
|
||||
},
|
||||
si: si::Si {
|
||||
regs: [0; si::SI_REGS_COUNT as usize],
|
||||
regs: [0; si::SI_REGS_COUNT],
|
||||
dma_dir: si::DmaDir::None,
|
||||
},
|
||||
ri: ri::Ri {
|
||||
regs: [0; ri::RI_REGS_COUNT as usize],
|
||||
regs: [0; ri::RI_REGS_COUNT],
|
||||
ram_init: false,
|
||||
},
|
||||
vi: vi::Vi {
|
||||
regs: [0; vi::VI_REGS_COUNT as usize],
|
||||
regs: [0; vi::VI_REGS_COUNT],
|
||||
clock: 0,
|
||||
field: 0,
|
||||
delay: 0,
|
||||
count_per_scanline: 0,
|
||||
enable_speed_limiter: true,
|
||||
limiter: None,
|
||||
vi_counter: 0,
|
||||
last_origin: 0,
|
||||
internal_frame_counter: 0,
|
||||
min_wait_time: std::time::Duration::from_secs(1),
|
||||
frame_time: 0.0,
|
||||
limit_freq: 2,
|
||||
limit_freq_check: std::time::Instant::now(),
|
||||
},
|
||||
vru_window: controller::vru::VruWindow {
|
||||
window_notifier: None,
|
||||
word_receiver: None,
|
||||
elapsed_time: 0.0,
|
||||
},
|
||||
vru: controller::vru::Vru {
|
||||
status: 0,
|
||||
@@ -495,7 +591,7 @@ impl Device {
|
||||
word_buffer: [0; 40],
|
||||
words: Vec::new(),
|
||||
talking: false,
|
||||
word_mappings: HashMap::new(),
|
||||
word_mappings: rustc_hash::FxHashMap::default(),
|
||||
},
|
||||
rng: set_rng(),
|
||||
transferpaks: [
|
||||
@@ -509,6 +605,6 @@ impl Device {
|
||||
boot: true,
|
||||
enabled: false,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
use crate::{device, ui};
|
||||
|
||||
const AI_DRAM_ADDR_REG: u32 = 0;
|
||||
const AI_LEN_REG: u32 = 1;
|
||||
// const AI_CONTROL_REG: u32 = 2;
|
||||
const AI_STATUS_REG: u32 = 3;
|
||||
const AI_DACRATE_REG: u32 = 4;
|
||||
// const AI_BITRATE_REG: u32 = 5;
|
||||
pub const AI_REGS_COUNT: u32 = 6;
|
||||
const AI_DRAM_ADDR_REG: usize = 0;
|
||||
const AI_LEN_REG: usize = 1;
|
||||
// const AI_CONTROL_REG: usize = 2;
|
||||
pub const AI_STATUS_REG: usize = 3;
|
||||
const AI_DACRATE_REG: usize = 4;
|
||||
// const AI_BITRATE_REG: usize = 5;
|
||||
pub const AI_REGS_COUNT: usize = 6;
|
||||
|
||||
const AI_STATUS_BUSY: u32 = 0x40000000;
|
||||
pub const AI_STATUS_BUSY: u32 = 0x40000000;
|
||||
const AI_STATUS_FULL: u32 = 0x80000000;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Ai {
|
||||
pub regs: [u32; AI_REGS_COUNT as usize],
|
||||
pub regs: [u32; AI_REGS_COUNT],
|
||||
pub fifo: [AiDma; 2],
|
||||
pub last_read: u64,
|
||||
pub delayed_carry: bool,
|
||||
@@ -38,18 +38,17 @@ fn get_remaining_dma_length(device: &device::Device) -> u64 {
|
||||
}
|
||||
|
||||
let remaining_dma_duration =
|
||||
next_ai_event.unwrap().count - device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize];
|
||||
next_ai_event.unwrap().count - device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG];
|
||||
|
||||
let dma_length = remaining_dma_duration * device.ai.fifo[0].length / device.ai.fifo[0].duration;
|
||||
dma_length & !7
|
||||
}
|
||||
|
||||
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;
|
||||
let length = (device.ai.regs[AI_LEN_REG] & !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) {
|
||||
@@ -74,34 +73,36 @@ fn do_dma(device: &mut device::Device) {
|
||||
fn fifo_push(device: &mut device::Device) {
|
||||
let duration = get_dma_duration(device);
|
||||
|
||||
if (device.ai.regs[AI_STATUS_REG as usize] & AI_STATUS_BUSY) != 0 {
|
||||
if (device.ai.regs[AI_STATUS_REG] & AI_STATUS_BUSY) != 0 {
|
||||
device.ai.fifo[1].address =
|
||||
device.ai.regs[AI_DRAM_ADDR_REG as usize] as u64 & device::rdram::RDRAM_MASK as u64;
|
||||
device.ai.fifo[1].length = (device.ai.regs[AI_LEN_REG as usize] & !7) as u64;
|
||||
device.ai.regs[AI_DRAM_ADDR_REG] as u64 & device::rdram::RDRAM_MASK as u64;
|
||||
device.ai.fifo[1].length = (device.ai.regs[AI_LEN_REG] & !7) as u64;
|
||||
device.ai.fifo[1].duration = duration;
|
||||
device.ai.regs[AI_STATUS_REG as usize] |= AI_STATUS_FULL;
|
||||
device.ai.regs[AI_STATUS_REG] |= AI_STATUS_FULL;
|
||||
} else {
|
||||
device.ai.fifo[0].address =
|
||||
device.ai.regs[AI_DRAM_ADDR_REG as usize] as u64 & device::rdram::RDRAM_MASK as u64;
|
||||
device.ai.fifo[0].length = (device.ai.regs[AI_LEN_REG as usize] & !7) as u64;
|
||||
device.ai.regs[AI_DRAM_ADDR_REG] as u64 & device::rdram::RDRAM_MASK as u64;
|
||||
device.ai.fifo[0].length = (device.ai.regs[AI_LEN_REG] & !7) as u64;
|
||||
device.ai.fifo[0].duration = duration;
|
||||
device.ai.regs[AI_STATUS_REG as usize] |= AI_STATUS_BUSY;
|
||||
device.ai.regs[AI_STATUS_REG] |= AI_STATUS_BUSY;
|
||||
|
||||
do_dma(device);
|
||||
ui::audio::resume_game_audio(&mut device.ui);
|
||||
}
|
||||
}
|
||||
|
||||
fn fifo_pop(device: &mut device::Device) {
|
||||
if device.ai.regs[AI_STATUS_REG as usize] & AI_STATUS_FULL != 0 {
|
||||
if device.ai.regs[AI_STATUS_REG] & AI_STATUS_FULL != 0 {
|
||||
device.ai.fifo[0].address = device.ai.fifo[1].address;
|
||||
device.ai.fifo[0].length = device.ai.fifo[1].length;
|
||||
device.ai.fifo[0].duration = device.ai.fifo[1].duration;
|
||||
device.ai.regs[AI_STATUS_REG as usize] &= !AI_STATUS_FULL;
|
||||
device.ai.regs[AI_STATUS_REG] &= !AI_STATUS_FULL;
|
||||
|
||||
do_dma(device);
|
||||
} else {
|
||||
device.ai.regs[AI_STATUS_REG as usize] &= !AI_STATUS_BUSY;
|
||||
device.ai.regs[AI_STATUS_REG] &= !AI_STATUS_BUSY;
|
||||
device.ai.delayed_carry = false;
|
||||
ui::audio::pause_game_audio(&mut device.ui);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +112,7 @@ pub fn read_regs(
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
let return_value = match reg as u32 {
|
||||
let return_value = match reg as usize {
|
||||
AI_LEN_REG => {
|
||||
let value = get_remaining_dma_length(device);
|
||||
if value < device.ai.last_read {
|
||||
@@ -135,7 +136,7 @@ pub fn read_regs(
|
||||
|
||||
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
match reg as usize {
|
||||
AI_LEN_REG => {
|
||||
device::memory::masked_write_32(&mut device.ai.regs[reg as usize], value, mask);
|
||||
if device.ai.regs[reg as usize] != 0 {
|
||||
@@ -146,8 +147,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
AI_DACRATE_REG => {
|
||||
if device.ai.regs[reg as usize] != value & mask {
|
||||
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)
|
||||
ui::audio::update_freq(device);
|
||||
}
|
||||
device::memory::masked_write_32(&mut device.ai.regs[reg as usize], value, mask)
|
||||
}
|
||||
|
||||
@@ -21,7 +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)]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct AfRtc {
|
||||
pub control: u16,
|
||||
}
|
||||
@@ -31,7 +31,7 @@ fn byte2bcd(mut n: u32) -> u8 {
|
||||
(((n / 10) << 4) | (n % 10)) as u8
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Cart {
|
||||
#[serde(skip)]
|
||||
pub rom: Vec<u8>,
|
||||
@@ -130,8 +130,6 @@ pub fn format_eeprom(device: &mut device::Device) {
|
||||
fn eeprom_read_block(device: &mut device::Device, block: usize, offset: usize) {
|
||||
let address = device.pif.ram[block] as usize * EEPROM_BLOCK_SIZE;
|
||||
|
||||
format_eeprom(device);
|
||||
|
||||
device.pif.ram[offset..offset + EEPROM_BLOCK_SIZE].copy_from_slice(
|
||||
&device.ui.storage.saves.eeprom.data[address..address + EEPROM_BLOCK_SIZE],
|
||||
);
|
||||
@@ -140,20 +138,19 @@ fn eeprom_read_block(device: &mut device::Device, block: usize, offset: usize) {
|
||||
fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
|
||||
let address = device.pif.ram[block] as usize * EEPROM_BLOCK_SIZE;
|
||||
|
||||
format_eeprom(device);
|
||||
|
||||
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.storage.saves.eeprom.written = true
|
||||
ui::storage::schedule_save(device, ui::storage::SaveTypes::Eeprom4k);
|
||||
}
|
||||
|
||||
fn time2data(device: &mut device::Device, offset: usize) {
|
||||
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();
|
||||
let timestamp = device.cart.rtc_timestamp + device.vi.elapsed_time as i64;
|
||||
let now = chrono::DateTime::from_timestamp(timestamp, 0)
|
||||
.unwrap()
|
||||
.with_timezone(&chrono::Local);
|
||||
|
||||
device.pif.ram[offset] = byte2bcd(now.second());
|
||||
device.pif.ram[offset + 1] = byte2bcd(now.minute());
|
||||
|
||||
@@ -1,26 +1,18 @@
|
||||
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)
|
||||
let romsave = &device.ui.storage.saves.romsave.data;
|
||||
let rom = &device.cart.rom;
|
||||
u32::from_be_bytes(std::array::from_fn(|i| {
|
||||
romsave
|
||||
.get(&((address + i) as u32))
|
||||
.copied()
|
||||
.unwrap_or_else(|| *rom.get(address + i).unwrap_or(&0))
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn read_mem_fast(
|
||||
@@ -40,8 +32,8 @@ pub fn read_mem(
|
||||
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 {
|
||||
// well known cart ROM oddity, if a read is performed 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] & device::pi::PI_STATUS_IO_BUSY != 0 {
|
||||
device.cart.latch
|
||||
} else {
|
||||
let masked_address = address as usize & CART_MASK;
|
||||
@@ -63,12 +55,12 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
|
||||
.data
|
||||
.insert((masked_address + i) as u32, *item);
|
||||
}
|
||||
device.ui.storage.saves.romsave.written = true;
|
||||
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;
|
||||
device.pi.regs[device::pi::PI_STATUS_REG] |= 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);
|
||||
@@ -94,7 +86,7 @@ pub fn dma_read(
|
||||
.unwrap_or(&0),
|
||||
);
|
||||
}
|
||||
device.ui.storage.saves.romsave.written = true;
|
||||
ui::storage::schedule_save(device, ui::storage::SaveTypes::Romsave);
|
||||
}
|
||||
|
||||
device::pi::calculate_cycles(device, 1, length)
|
||||
@@ -112,19 +104,15 @@ pub fn dma_write(
|
||||
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;
|
||||
*device
|
||||
.rdram
|
||||
.mem
|
||||
.get_mut(i as usize ^ device.byte_swap)
|
||||
.unwrap_or(&mut 0) = if let Some(value) = device.ui.storage.saves.romsave.data.get(&j) {
|
||||
*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);
|
||||
}
|
||||
*device.cart.rom.get(j as usize).unwrap_or(&0)
|
||||
};
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
@@ -132,22 +120,11 @@ pub fn dma_write(
|
||||
device::pi::calculate_cycles(device, 1, length)
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device, rom_file: Vec<u8>) {
|
||||
let now: chrono::DateTime<chrono::Local> = chrono::Local::now();
|
||||
device.cart.rtc_timestamp = now.naive_local().and_utc().timestamp();
|
||||
if device.netplay.is_some() {
|
||||
let netplay = device.netplay.as_mut().unwrap();
|
||||
if netplay.player_number == 0 {
|
||||
netplay::send_rtc(netplay, device.cart.rtc_timestamp);
|
||||
} else {
|
||||
device.cart.rtc_timestamp = netplay::receive_rtc(netplay);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device, rom_file: &[u8]) {
|
||||
device.cart.sc64.cfg[device::cart::sc64::SC64_BOOTLOADER_SWITCH as usize] = 1;
|
||||
|
||||
device.cart.rom = rom_file;
|
||||
device.cart.pal = is_system_pal(device.cart.rom[0x3E]);
|
||||
device.cart.rom = rom_file.to_vec();
|
||||
device.cart.pal = is_system_pal(&device.cart.rom);
|
||||
set_cic(device);
|
||||
|
||||
device.ui.game_hash = calculate_hash(&device.cart.rom);
|
||||
@@ -158,10 +135,10 @@ pub fn init(device: &mut device::Device, rom_file: Vec<u8>) {
|
||||
}
|
||||
}
|
||||
|
||||
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'];
|
||||
pub fn is_system_pal(rom_contents: &[u8]) -> bool {
|
||||
let pal_codes: [u8; 8] = *b"DFIPSUXY";
|
||||
for i in pal_codes {
|
||||
if country == i {
|
||||
if rom_contents[0x3E] == i {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -200,5 +177,9 @@ fn set_cic(device: &mut device::Device) {
|
||||
pub fn calculate_hash(data: &[u8]) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(data);
|
||||
format!("{:X}", hasher.finalize())
|
||||
hasher
|
||||
.finalize()
|
||||
.iter()
|
||||
.map(|b| format!("{:02X}", b))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -3,29 +3,29 @@ 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;
|
||||
const SC64_SCR_REG: usize = 0;
|
||||
const SC64_DATA0_REG: usize = 1;
|
||||
const SC64_DATA1_REG: usize = 2;
|
||||
const SC64_IDENTIFIER_REG: usize = 3;
|
||||
const SC64_KEY_REG: usize = 4;
|
||||
//const SC64_IRQ_REG: usize = 5;
|
||||
//const SC64_AUX_REG: usize = 6;
|
||||
pub const SC64_REGS_COUNT: usize = 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;
|
||||
pub const SC64_CFG_COUNT: usize = 15;
|
||||
|
||||
const SC64_BUFFER_MASK: usize = 0x1FFF;
|
||||
const SC64_EEPROM_MASK: usize = 0xFFF;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Sc64 {
|
||||
pub buffer: Vec<u8>,
|
||||
pub regs: [u32; SC64_REGS_COUNT as usize],
|
||||
pub regs: [u32; SC64_REGS_COUNT],
|
||||
pub regs_locked: bool,
|
||||
pub cfg: [u32; SC64_CFG_COUNT as usize],
|
||||
pub cfg: [u32; SC64_CFG_COUNT],
|
||||
pub sector: u32,
|
||||
pub writeback_sector: Vec<u32>,
|
||||
pub usb_buffer: Vec<u8>,
|
||||
@@ -49,7 +49,7 @@ pub fn read_regs(
|
||||
return 0;
|
||||
}
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
match reg as usize {
|
||||
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}"),
|
||||
@@ -58,12 +58,12 @@ pub fn read_regs(
|
||||
|
||||
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
match reg as usize {
|
||||
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 {
|
||||
if device.cart.sc64.regs[SC64_KEY_REG] == 0x4F434B5F {
|
||||
device.cart.sc64.regs_locked = false;
|
||||
} else if device.cart.sc64.regs[SC64_KEY_REG as usize] == 0xFFFFFFFF {
|
||||
} else if device.cart.sc64.regs[SC64_KEY_REG] == 0xFFFFFFFF {
|
||||
device.cart.sc64.regs_locked = true;
|
||||
}
|
||||
}
|
||||
@@ -81,22 +81,22 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
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;
|
||||
device.cart.sc64.regs[SC64_DATA0_REG] = (2 << 16) | 20;
|
||||
device.cart.sc64.regs[SC64_DATA1_REG] = 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]
|
||||
device.cart.sc64.regs[SC64_DATA1_REG] =
|
||||
device.cart.sc64.cfg[device.cart.sc64.regs[SC64_DATA0_REG] as usize]
|
||||
}
|
||||
'C' => {
|
||||
// set config
|
||||
if device.cart.sc64.regs[SC64_DATA0_REG as usize] == SC64_SAVE_TYPE {
|
||||
if device.cart.sc64.regs[SC64_DATA0_REG] == 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] {
|
||||
match device.cart.sc64.regs[SC64_DATA1_REG] {
|
||||
0 => {
|
||||
vec![]
|
||||
}
|
||||
@@ -115,20 +115,20 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
_ => {
|
||||
panic!(
|
||||
"unknown sc64 save type: {}",
|
||||
device.cart.sc64.regs[SC64_DATA1_REG as usize]
|
||||
device.cart.sc64.regs[SC64_DATA1_REG]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
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],
|
||||
[device.cart.sc64.regs[SC64_DATA0_REG] as usize],
|
||||
&mut device.cart.sc64.regs[SC64_DATA1_REG],
|
||||
);
|
||||
}
|
||||
'i' => {
|
||||
// sd card operation
|
||||
match device.cart.sc64.regs[SC64_DATA1_REG as usize] {
|
||||
match device.cart.sc64.regs[SC64_DATA1_REG] {
|
||||
0 => { //Init SD card
|
||||
}
|
||||
1 => { //Deinit SD card
|
||||
@@ -136,23 +136,21 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
_ => {
|
||||
panic!(
|
||||
"unknown sc64 sd card operation: {}",
|
||||
device.cart.sc64.regs[SC64_DATA1_REG as usize]
|
||||
device.cart.sc64.regs[SC64_DATA1_REG]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
'I' => {
|
||||
// set sd sector
|
||||
device.cart.sc64.sector = device.cart.sc64.regs[SC64_DATA0_REG as usize];
|
||||
device.cart.sc64.sector = device.cart.sc64.regs[SC64_DATA0_REG];
|
||||
}
|
||||
's' => {
|
||||
format_sdcard(device);
|
||||
// read sd card
|
||||
let address =
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF;
|
||||
let address = device.cart.sc64.regs[SC64_DATA0_REG] 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 length = (device.cart.sc64.regs[SC64_DATA1_REG] * 512) as usize;
|
||||
let mut i = 0;
|
||||
|
||||
while i < length {
|
||||
@@ -182,11 +180,9 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
'S' => {
|
||||
format_sdcard(device);
|
||||
// write sd card
|
||||
let address =
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF;
|
||||
let address = device.cart.sc64.regs[SC64_DATA0_REG] 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 length = (device.cart.sc64.regs[SC64_DATA1_REG] * 512) as usize;
|
||||
let mut i = 0;
|
||||
|
||||
while i < length {
|
||||
@@ -208,45 +204,35 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
.copy_from_slice(&data);
|
||||
i += 4;
|
||||
}
|
||||
device.ui.storage.saves.sdcard.written = true;
|
||||
ui::storage::schedule_save(device, ui::storage::SaveTypes::Sdcard);
|
||||
}
|
||||
'U' => {
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0;
|
||||
device.cart.sc64.regs[SC64_DATA0_REG] = 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] =
|
||||
device.cart.sc64.regs[SC64_DATA0_REG] = data.data_type & 0xFF; // read_status/type
|
||||
device.cart.sc64.regs[SC64_DATA1_REG] =
|
||||
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
|
||||
}
|
||||
}
|
||||
Err(_err) => {
|
||||
device.cart.sc64.regs[SC64_DATA0_REG] = 0; // read_status/type
|
||||
device.cart.sc64.regs[SC64_DATA1_REG] = 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
|
||||
device.cart.sc64.regs[SC64_DATA0_REG] = 0; // read_status/type
|
||||
device.cart.sc64.regs[SC64_DATA1_REG] = 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;
|
||||
let address = device.cart.sc64.regs[SC64_DATA0_REG] as u64 & 0x1FFFFFFF;
|
||||
let length = device.cart.sc64.regs[SC64_DATA1_REG] as usize & 0xFFFFFF;
|
||||
|
||||
if let Some(usb_tx) = device.ui.usb.usb_tx.as_mut() {
|
||||
let mut usb_buffer = vec![0; length];
|
||||
@@ -297,18 +283,16 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
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,
|
||||
data_type: device.cart.sc64.regs[SC64_DATA1_REG] >> 24,
|
||||
data_size: device.cart.sc64.regs[SC64_DATA1_REG] & 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 address = device.cart.sc64.regs[SC64_DATA0_REG] as u64 & 0x1FFFFFFF;
|
||||
let length = device.cart.sc64.regs[SC64_DATA1_REG] as usize;
|
||||
|
||||
let mut i = 0;
|
||||
|
||||
@@ -338,11 +322,11 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
}
|
||||
'w' => {
|
||||
// SD card writeback pending
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0;
|
||||
device.cart.sc64.regs[SC64_DATA0_REG] = 0;
|
||||
}
|
||||
'W' => {
|
||||
let writeback_sectors_address =
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64;
|
||||
device.cart.sc64.regs[SC64_DATA0_REG] as u64;
|
||||
for i in 0..256 {
|
||||
device.cart.sc64.writeback_sector[i] = device::memory::data_read(
|
||||
device,
|
||||
@@ -376,7 +360,6 @@ pub fn read_mem(
|
||||
_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]
|
||||
@@ -395,7 +378,6 @@ pub fn read_mem(
|
||||
|
||||
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]
|
||||
@@ -405,7 +387,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
|
||||
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());
|
||||
device.ui.storage.saves.eeprom.written = true
|
||||
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(
|
||||
@@ -427,9 +409,8 @@ pub fn dma_read(
|
||||
) -> 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.written = true;
|
||||
ui::storage::schedule_save(device, ui::storage::SaveTypes::Eeprom4k);
|
||||
&mut device.ui.storage.saves.eeprom.data
|
||||
} else {
|
||||
cart_addr &= SC64_BUFFER_MASK as u32;
|
||||
@@ -459,7 +440,6 @@ pub fn dma_write(
|
||||
) -> 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 {
|
||||
|
||||
@@ -8,7 +8,7 @@ pub const MX29L1100_ID: u32 = 0x00c2001e;
|
||||
const MX29L0000_ID: u32 = 0x00c20000;
|
||||
const MX29L0001_ID: u32 = 0x00c20001;
|
||||
|
||||
#[derive(PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum FlashramMode {
|
||||
ReadArray,
|
||||
ReadSiliconId,
|
||||
@@ -18,7 +18,7 @@ pub enum FlashramMode {
|
||||
PageProgram,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Flashram {
|
||||
pub status: u32,
|
||||
pub mode: FlashramMode,
|
||||
@@ -28,13 +28,13 @@ pub struct Flashram {
|
||||
pub silicon_id: [u32; 2],
|
||||
}
|
||||
|
||||
fn format_sram(device: &mut device::Device) {
|
||||
pub fn format_sram(device: &mut device::Device) {
|
||||
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) {
|
||||
pub fn format_flash(device: &mut device::Device) {
|
||||
if device.ui.storage.saves.flash.data.len() < FLASHRAM_SIZE {
|
||||
device
|
||||
.ui
|
||||
@@ -49,8 +49,6 @@ fn format_flash(device: &mut device::Device) {
|
||||
fn read_mem_sram(device: &mut device::Device, address: u64) -> u32 {
|
||||
let masked_address = address as usize & SRAM_MASK;
|
||||
|
||||
format_sram(device);
|
||||
|
||||
u32::from_be_bytes(
|
||||
device.ui.storage.saves.sram.data[masked_address..masked_address + 4]
|
||||
.try_into()
|
||||
@@ -96,8 +94,6 @@ pub fn read_mem(
|
||||
fn write_mem_sram(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
let masked_address = address as usize & SRAM_MASK;
|
||||
|
||||
format_sram(device);
|
||||
|
||||
let mut data = u32::from_be_bytes(
|
||||
device.ui.storage.saves.sram.data[masked_address..masked_address + 4]
|
||||
.try_into()
|
||||
@@ -107,7 +103,7 @@ fn write_mem_sram(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
device.ui.storage.saves.sram.data[masked_address..masked_address + 4]
|
||||
.copy_from_slice(&data.to_be_bytes());
|
||||
|
||||
device.ui.storage.saves.sram.written = true
|
||||
ui::storage::schedule_save(device, ui::storage::SaveTypes::Sram);
|
||||
}
|
||||
|
||||
fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
@@ -116,7 +112,6 @@ fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask:
|
||||
device.cart.flashram.status = (value & mask) & 0xff;
|
||||
} else if (address & 0x1ffff) == 0x10000 {
|
||||
/* set command */
|
||||
format_flash(device);
|
||||
flashram_command(device, value & mask);
|
||||
} else {
|
||||
/* other accesses are not implemented */
|
||||
@@ -136,7 +131,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
|
||||
write_mem_flash(device, address, value, mask)
|
||||
}
|
||||
|
||||
device.pi.regs[device::pi::PI_STATUS_REG as usize] |= device::pi::PI_STATUS_IO_BUSY;
|
||||
device.pi.regs[device::pi::PI_STATUS_REG] |= device::pi::PI_STATUS_IO_BUSY;
|
||||
|
||||
let cycles = device::pi::calculate_cycles(device, 2, 4);
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_PI, cycles);
|
||||
@@ -148,8 +143,6 @@ fn dma_read_sram(device: &mut device::Device, mut cart_addr: u32, mut dram_addr:
|
||||
let mut i = dram_addr;
|
||||
let mut j = cart_addr;
|
||||
|
||||
format_sram(device);
|
||||
|
||||
while i < dram_addr + length {
|
||||
*device
|
||||
.ui
|
||||
@@ -167,12 +160,10 @@ fn dma_read_sram(device: &mut device::Device, mut cart_addr: u32, mut dram_addr:
|
||||
j += 1;
|
||||
}
|
||||
|
||||
device.ui.storage.saves.sram.written = 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) {
|
||||
format_flash(device);
|
||||
|
||||
if (cart_addr & 0x1ffff) == 0x00000
|
||||
&& length == 128
|
||||
&& device.cart.flashram.mode == FlashramMode::PageProgram
|
||||
@@ -217,8 +208,6 @@ fn dma_write_sram(
|
||||
let mut i = dram_addr;
|
||||
let mut j = cart_addr;
|
||||
|
||||
format_sram(device);
|
||||
|
||||
while i < dram_addr + length {
|
||||
*device
|
||||
.rdram
|
||||
@@ -266,7 +255,6 @@ fn dma_write_flash(
|
||||
} else if (cart_addr & 0x1ffff) < 0x10000
|
||||
&& device.cart.flashram.mode == FlashramMode::ReadArray
|
||||
{
|
||||
format_flash(device);
|
||||
/* adjust flashram address before starting DMA. */
|
||||
if device.cart.flashram.silicon_id[1] == MX29L1100_ID
|
||||
|| device.cart.flashram.silicon_id[1] == MX29L0000_ID
|
||||
@@ -331,12 +319,12 @@ fn flashram_command(device: &mut device::Device, command: u32) {
|
||||
for i in 0..128 * 128 {
|
||||
device.ui.storage.saves.flash.data[offset + i] = 0xFF;
|
||||
}
|
||||
device.ui.storage.saves.flash.written = true
|
||||
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.storage.saves.flash.data[i] = 0xFF;
|
||||
}
|
||||
device.ui.storage.saves.flash.written = true
|
||||
ui::storage::schedule_save(device, ui::storage::SaveTypes::Flash);
|
||||
} else {
|
||||
panic!("Unexpected flash erase command")
|
||||
}
|
||||
@@ -356,7 +344,7 @@ fn flashram_command(device: &mut device::Device, command: u32) {
|
||||
for i in 0..128 {
|
||||
device.ui.storage.saves.flash.data[offset + i] = device.cart.flashram.page_buf[i];
|
||||
}
|
||||
device.ui.storage.saves.flash.written = true;
|
||||
ui::storage::schedule_save(device, ui::storage::SaveTypes::Flash);
|
||||
|
||||
/* clear program busy flag, set program success flag, transition to status mode */
|
||||
device.cart.flashram.status &= !0x01;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{device, netplay, savestates, ui};
|
||||
use crate::{device, savestates, ui};
|
||||
|
||||
pub mod gbcart;
|
||||
pub mod mempak;
|
||||
@@ -56,15 +56,10 @@ 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(&device.ui, channel)
|
||||
let input = if let Some(netplay) = &mut device.netplay {
|
||||
netplay.inputs[channel].0
|
||||
} else {
|
||||
if device.netplay.as_ref().unwrap().player_number as usize == channel {
|
||||
let local_input = ui::input::get(&device.ui, 0);
|
||||
netplay::send_input(device.netplay.as_ref().unwrap(), local_input);
|
||||
}
|
||||
|
||||
netplay::get_input(device, channel)
|
||||
ui::input::get(&mut device.ui, channel)
|
||||
};
|
||||
|
||||
device.pif.ram[offset..offset + 4].copy_from_slice(&input.data.to_ne_bytes());
|
||||
@@ -77,7 +72,7 @@ pub fn process(device: &mut device::Device, channel: usize) {
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EVENT_TYPE_PAK,
|
||||
device.cpu.clock_rate, // 1 second
|
||||
device.cpu.clock_rate / 2, // 500ms
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -96,7 +91,7 @@ pub fn process(device: &mut device::Device, channel: usize) {
|
||||
device.pif.channels[channel].rx_buf.unwrap(),
|
||||
channel,
|
||||
),
|
||||
_ => println!("unknown controller command {cmd}"),
|
||||
_ => eprintln!("unknown controller command {cmd}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,10 +157,12 @@ 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 {
|
||||
//stop rumble if it is on
|
||||
if device.netplay.is_none() {
|
||||
if let Some(netplay) = &device.netplay {
|
||||
if netplay.player_number == i {
|
||||
device::ui::input::set_rumble(&device.ui, 0, 0);
|
||||
}
|
||||
} else {
|
||||
device::ui::input::set_rumble(&device.ui, i, 0);
|
||||
} else if device.netplay.as_ref().unwrap().player_number as usize == i {
|
||||
device::ui::input::set_rumble(&device.ui, 0, 0);
|
||||
}
|
||||
|
||||
let new_pak_type = match channel.change_pak {
|
||||
@@ -189,21 +186,30 @@ pub fn pak_switch_event(device: &mut device::Device) {
|
||||
write: device::controller::mempak::write,
|
||||
pak_type: new_pak_type,
|
||||
});
|
||||
ui::video::onscreen_message(&device.ui, "MemPak selected");
|
||||
ui::video::onscreen_message(
|
||||
&format!("P{} MemPak selected", i + 1),
|
||||
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: new_pak_type,
|
||||
});
|
||||
ui::video::onscreen_message(&device.ui, "RumblePak selected");
|
||||
ui::video::onscreen_message(
|
||||
&format!("P{} RumblePak selected", i + 1),
|
||||
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(&device.ui, "TransferPak selected");
|
||||
ui::video::onscreen_message(
|
||||
&format!("P{} TransferPak selected", i + 1),
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_SHORT,
|
||||
);
|
||||
}
|
||||
channel.change_pak = PakType::None;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
use crate::device;
|
||||
|
||||
#[derive(Default, serde::Serialize, serde::Deserialize)]
|
||||
use std::io::Write;
|
||||
const MBC3_RTC_SECONDS: usize = 0;
|
||||
const MBC3_RTC_MINUTES: usize = 1;
|
||||
const MBC3_RTC_HOURS: usize = 2;
|
||||
const MBC3_RTC_DAYS_L: usize = 3;
|
||||
const MBC3_RTC_DAYS_H: usize = 4;
|
||||
const MBC3_RTC_REGS_COUNT: usize = 5;
|
||||
|
||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct GbCart {
|
||||
pub enabled: bool,
|
||||
#[serde(skip)]
|
||||
@@ -11,14 +19,12 @@ pub struct GbCart {
|
||||
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,
|
||||
pub rtc_regs: [u8; MBC3_RTC_REGS_COUNT],
|
||||
pub rtc_regs_latch: [u8; MBC3_RTC_REGS_COUNT],
|
||||
pub last_elapsed_time: i64,
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Default, Copy, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum CartType {
|
||||
#[default]
|
||||
None,
|
||||
@@ -28,6 +34,135 @@ pub enum CartType {
|
||||
MBC5RamBatt,
|
||||
}
|
||||
|
||||
pub fn init(gb_cart: &mut device::controller::gbcart::GbCart, rom: &[u8], ram: &[u8]) {
|
||||
gb_cart.rom = rom.to_vec();
|
||||
let offset = match ram.len() % 8192 {
|
||||
44 => {
|
||||
gb_cart.ram = ram[..ram.len() - 44].to_vec();
|
||||
ram.len() - 44
|
||||
}
|
||||
48 => {
|
||||
gb_cart.ram = ram[..ram.len() - 48].to_vec();
|
||||
ram.len() - 48
|
||||
}
|
||||
_ => {
|
||||
gb_cart.ram = ram.to_vec();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
gb_cart.rtc_regs[MBC3_RTC_SECONDS] =
|
||||
u32::from_le_bytes(ram[offset..offset + 4].try_into().unwrap()) as u8;
|
||||
gb_cart.rtc_regs[MBC3_RTC_MINUTES] =
|
||||
u32::from_le_bytes(ram[offset + 4..offset + 8].try_into().unwrap()) as u8;
|
||||
gb_cart.rtc_regs[MBC3_RTC_HOURS] =
|
||||
u32::from_le_bytes(ram[offset + 8..offset + 12].try_into().unwrap()) as u8;
|
||||
gb_cart.rtc_regs[MBC3_RTC_DAYS_L] =
|
||||
u32::from_le_bytes(ram[offset + 12..offset + 16].try_into().unwrap()) as u8;
|
||||
gb_cart.rtc_regs[MBC3_RTC_DAYS_H] =
|
||||
u32::from_le_bytes(ram[offset + 16..offset + 20].try_into().unwrap()) as u8;
|
||||
|
||||
gb_cart.rtc_regs_latch[MBC3_RTC_SECONDS] =
|
||||
u32::from_le_bytes(ram[offset + 20..offset + 24].try_into().unwrap()) as u8;
|
||||
gb_cart.rtc_regs_latch[MBC3_RTC_MINUTES] =
|
||||
u32::from_le_bytes(ram[offset + 24..offset + 28].try_into().unwrap()) as u8;
|
||||
gb_cart.rtc_regs_latch[MBC3_RTC_HOURS] =
|
||||
u32::from_le_bytes(ram[offset + 28..offset + 32].try_into().unwrap()) as u8;
|
||||
gb_cart.rtc_regs_latch[MBC3_RTC_DAYS_L] =
|
||||
u32::from_le_bytes(ram[offset + 32..offset + 36].try_into().unwrap()) as u8;
|
||||
gb_cart.rtc_regs_latch[MBC3_RTC_DAYS_H] =
|
||||
u32::from_le_bytes(ram[offset + 36..offset + 40].try_into().unwrap()) as u8;
|
||||
}
|
||||
|
||||
pub fn save(
|
||||
gb_cart: &mut device::controller::gbcart::GbCart,
|
||||
elapsed_time: i64,
|
||||
ram_path: &std::string::String,
|
||||
) {
|
||||
if cfg!(target_os = "android") {
|
||||
return;
|
||||
}
|
||||
update_rtc_regs(gb_cart, elapsed_time);
|
||||
|
||||
if let Ok(file) = std::fs::File::create(ram_path) {
|
||||
let mut f = std::io::BufWriter::new(file);
|
||||
f.write_all(&gb_cart.ram).unwrap();
|
||||
|
||||
if gb_cart.cart_type == CartType::MBC3RamBattRtc {
|
||||
f.write_all(&(gb_cart.rtc_regs[MBC3_RTC_SECONDS] as u32).to_le_bytes())
|
||||
.unwrap();
|
||||
f.write_all(&(gb_cart.rtc_regs[MBC3_RTC_MINUTES] as u32).to_le_bytes())
|
||||
.unwrap();
|
||||
f.write_all(&(gb_cart.rtc_regs[MBC3_RTC_HOURS] as u32).to_le_bytes())
|
||||
.unwrap();
|
||||
f.write_all(&(gb_cart.rtc_regs[MBC3_RTC_DAYS_L] as u32).to_le_bytes())
|
||||
.unwrap();
|
||||
f.write_all(&(gb_cart.rtc_regs[MBC3_RTC_DAYS_H] as u32).to_le_bytes())
|
||||
.unwrap();
|
||||
|
||||
f.write_all(&(gb_cart.rtc_regs_latch[MBC3_RTC_SECONDS] as u32).to_le_bytes())
|
||||
.unwrap();
|
||||
f.write_all(&(gb_cart.rtc_regs_latch[MBC3_RTC_MINUTES] as u32).to_le_bytes())
|
||||
.unwrap();
|
||||
f.write_all(&(gb_cart.rtc_regs_latch[MBC3_RTC_HOURS] as u32).to_le_bytes())
|
||||
.unwrap();
|
||||
f.write_all(&(gb_cart.rtc_regs_latch[MBC3_RTC_DAYS_L] as u32).to_le_bytes())
|
||||
.unwrap();
|
||||
f.write_all(&(gb_cart.rtc_regs_latch[MBC3_RTC_DAYS_H] as u32).to_le_bytes())
|
||||
.unwrap();
|
||||
|
||||
let max = ((i32::MAX as i64) << 32) | (i32::MAX as i64);
|
||||
f.write_all(&max.to_le_bytes()).unwrap();
|
||||
|
||||
f.flush().unwrap();
|
||||
}
|
||||
} else {
|
||||
eprintln!("Error saving TransferPak RAM to {ram_path}");
|
||||
}
|
||||
}
|
||||
|
||||
fn update_rtc_regs(cart: &mut device::controller::gbcart::GbCart, elapsed_time: i64) {
|
||||
let mut diff = elapsed_time - cart.last_elapsed_time;
|
||||
cart.last_elapsed_time = elapsed_time;
|
||||
|
||||
if diff > 0 {
|
||||
cart.rtc_regs[MBC3_RTC_SECONDS] += (diff % 60) as u8;
|
||||
if cart.rtc_regs[MBC3_RTC_SECONDS] >= 60 {
|
||||
cart.rtc_regs[MBC3_RTC_SECONDS] -= 60;
|
||||
cart.rtc_regs[MBC3_RTC_MINUTES] += 1;
|
||||
}
|
||||
diff /= 60;
|
||||
|
||||
cart.rtc_regs[MBC3_RTC_MINUTES] += (diff % 60) as u8;
|
||||
if cart.rtc_regs[MBC3_RTC_MINUTES] >= 60 {
|
||||
cart.rtc_regs[MBC3_RTC_MINUTES] -= 60;
|
||||
cart.rtc_regs[MBC3_RTC_HOURS] += 1;
|
||||
}
|
||||
diff /= 60;
|
||||
|
||||
cart.rtc_regs[MBC3_RTC_HOURS] += (diff % 24) as u8;
|
||||
if cart.rtc_regs[MBC3_RTC_HOURS] >= 24 {
|
||||
cart.rtc_regs[MBC3_RTC_HOURS] -= 24;
|
||||
cart.rtc_regs[MBC3_RTC_DAYS_L] += 1;
|
||||
}
|
||||
diff /= 24;
|
||||
|
||||
/* update days counter */
|
||||
let days = ((((cart.rtc_regs[MBC3_RTC_DAYS_H] & 0x01) as i64) << 8)
|
||||
| cart.rtc_regs[MBC3_RTC_DAYS_L] as i64)
|
||||
+ diff;
|
||||
|
||||
cart.rtc_regs[MBC3_RTC_DAYS_L] = (days & 0xff) as u8;
|
||||
cart.rtc_regs[MBC3_RTC_DAYS_H] &= !0x01;
|
||||
cart.rtc_regs[MBC3_RTC_DAYS_H] |= ((days >> 8) & 1) as u8;
|
||||
|
||||
/* set carry bit if days overflow */
|
||||
if days > 511 {
|
||||
cart.rtc_regs[MBC3_RTC_DAYS_H] |= 0x80;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_mbc1(
|
||||
pif_ram: &[u8],
|
||||
cart: &mut device::controller::gbcart::GbCart,
|
||||
@@ -105,12 +240,13 @@ fn write_mbc3(
|
||||
address: u16,
|
||||
data: usize,
|
||||
size: usize,
|
||||
elapsed_time: i64,
|
||||
) {
|
||||
let value = pif_ram[data + size - 1];
|
||||
if address < 0x2000 {
|
||||
cart.ram_enabled = value & 0xf == 0xa;
|
||||
} else if address < 0x4000 {
|
||||
let bank = value & 0x7f;
|
||||
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;
|
||||
@@ -118,10 +254,10 @@ fn write_mbc3(
|
||||
} else if address < 0x6000 {
|
||||
cart.ram_bank = (value & 0xf) as u16;
|
||||
} else if address < 0x8000 {
|
||||
if !cart.set_latch && value != 0 {
|
||||
// not implemented
|
||||
if cart.cart_type == CartType::MBC3RamBattRtc && (value & 0x1) != 0 {
|
||||
update_rtc_regs(cart, elapsed_time);
|
||||
cart.rtc_regs_latch = cart.rtc_regs;
|
||||
}
|
||||
cart.set_latch = value != 0;
|
||||
} else if (0xa000..0xc000).contains(&address) {
|
||||
if !cart.ram_enabled {
|
||||
return;
|
||||
@@ -130,29 +266,8 @@ fn write_mbc3(
|
||||
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 if cart.cart_type == CartType::MBC3RamBattRtc {
|
||||
cart.rtc_regs[cart.ram_bank as usize - 0x8] = value;
|
||||
}
|
||||
} else {
|
||||
panic!("Unsupported write address {address:x}");
|
||||
@@ -161,7 +276,7 @@ fn write_mbc3(
|
||||
|
||||
fn read_mbc3(
|
||||
pif_ram: &mut [u8],
|
||||
cart: &device::controller::gbcart::GbCart,
|
||||
cart: &mut device::controller::gbcart::GbCart,
|
||||
address: u16,
|
||||
data: usize,
|
||||
size: usize,
|
||||
@@ -182,29 +297,15 @@ fn read_mbc3(
|
||||
return;
|
||||
}
|
||||
if cart.ram_bank < 0x8 {
|
||||
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],
|
||||
);
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
};
|
||||
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 if cart.cart_type == CartType::MBC3RamBattRtc {
|
||||
for i in 0..size {
|
||||
pif_ram[data + i] = latch;
|
||||
pif_ram[data + i] = cart.rtc_regs_latch[cart.ram_bank as usize - 0x8];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -231,7 +332,7 @@ fn write_mbc5(
|
||||
} else if address < 0x6000 {
|
||||
cart.ram_bank = (value & 0xf) as u16;
|
||||
} else if address < 0xa000 {
|
||||
println!("Unknown MBC5 write address {address:x}");
|
||||
eprintln!("Unknown MBC5 write address {address:x}");
|
||||
} else if (0xa000..0xc000).contains(&address) {
|
||||
if !cart.ram_enabled {
|
||||
return;
|
||||
@@ -278,7 +379,7 @@ fn read_mbc5(
|
||||
|
||||
pub fn read(
|
||||
pif_ram: &mut [u8],
|
||||
cart: &device::controller::gbcart::GbCart,
|
||||
cart: &mut device::controller::gbcart::GbCart,
|
||||
address: u16,
|
||||
data: usize,
|
||||
size: usize,
|
||||
@@ -304,14 +405,15 @@ pub fn write(
|
||||
address: u16,
|
||||
data: usize,
|
||||
size: usize,
|
||||
elapsed_time: i64,
|
||||
) {
|
||||
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::MBC3RamBatt => write_mbc3(pif_ram, cart, address, data, size, elapsed_time),
|
||||
CartType::MBC3RamBattRtc => write_mbc3(pif_ram, cart, address, data, size, elapsed_time),
|
||||
CartType::MBC5RamBatt => write_mbc5(pif_ram, cart, address, data, size),
|
||||
_ => panic!("Unsupported cart type"),
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
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) {
|
||||
pub fn format_mempak(device: &mut device::Device) {
|
||||
if device.ui.storage.saves.mempak.data.len() < MEMPAK_SIZE * 4 {
|
||||
device
|
||||
.ui
|
||||
@@ -42,6 +43,11 @@ fn format_mempak(device: &mut device::Device) {
|
||||
];
|
||||
for i in 0..4 {
|
||||
let offset = i * MEMPAK_SIZE;
|
||||
if device.ui.storage.saves.mempak.data[offset..offset + MEMPAK_SIZE] != [0; MEMPAK_SIZE]
|
||||
{
|
||||
// don't overwrite existing data
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Fill Page 0 with pre-initialized content */
|
||||
device.ui.storage.saves.mempak.data[offset..offset + MPK_PAGE_SIZE]
|
||||
@@ -76,8 +82,6 @@ fn format_mempak(device: &mut device::Device) {
|
||||
|
||||
pub fn read(device: &mut device::Device, channel: usize, address: u16, data: usize, size: usize) {
|
||||
if (address as usize) < MEMPAK_SIZE {
|
||||
format_mempak(device);
|
||||
|
||||
let offset = (channel * MEMPAK_SIZE) + address as usize;
|
||||
device.pif.ram[data..data + size]
|
||||
.copy_from_slice(&device.ui.storage.saves.mempak.data[offset..offset + size])
|
||||
@@ -90,12 +94,10 @@ pub fn read(device: &mut device::Device, channel: usize, address: u16, data: usi
|
||||
|
||||
pub fn write(device: &mut device::Device, channel: usize, address: u16, data: usize, size: usize) {
|
||||
if (address as usize) < MEMPAK_SIZE {
|
||||
format_mempak(device);
|
||||
|
||||
let offset = (channel * MEMPAK_SIZE) + address as usize;
|
||||
device.ui.storage.saves.mempak.data[offset..offset + size]
|
||||
.copy_from_slice(&device.pif.ram[data..data + size]);
|
||||
|
||||
device.ui.storage.saves.mempak.written = 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() {
|
||||
|
||||
if let Some(netplay) = &device.netplay {
|
||||
if netplay.player_number == channel {
|
||||
device::ui::input::set_rumble(&device.ui, 0, rumble);
|
||||
}
|
||||
} else {
|
||||
device::ui::input::set_rumble(&device.ui, channel, rumble);
|
||||
} else if device.netplay.as_ref().unwrap().player_number as usize == channel {
|
||||
device::ui::input::set_rumble(&device.ui, 0, rumble);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::device;
|
||||
|
||||
#[derive(Default, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct TransferPak {
|
||||
pub enabled: bool,
|
||||
pub reset_state: u8,
|
||||
@@ -54,14 +54,14 @@ pub fn read(device: &mut device::Device, channel: usize, address: u16, data: usi
|
||||
0xC..=0xF => {
|
||||
device::controller::gbcart::read(
|
||||
&mut device.pif.ram,
|
||||
&pak.cart,
|
||||
&mut pak.cart,
|
||||
0x4000 * pak.bank + (address & 0x7fff) - 0x4000,
|
||||
data,
|
||||
size,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
println!("unknown transfer pak read {:x}", address >> 12);
|
||||
eprintln!("unknown transfer pak read {:x}", address >> 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ pub fn write(device: &mut device::Device, channel: usize, address: u16, data: us
|
||||
}
|
||||
pak.enabled = true;
|
||||
}
|
||||
_ => println!("unknown transfer pak value"),
|
||||
_ => eprintln!("unknown transfer pak value"),
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -124,10 +124,11 @@ pub fn write(device: &mut device::Device, channel: usize, address: u16, data: us
|
||||
0x4000 * pak.bank + (address & 0x7fff) - 0x4000,
|
||||
data,
|
||||
size,
|
||||
device.vi.elapsed_time as i64,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
println!("unknown transfer pak write {:x}", address >> 12);
|
||||
eprintln!("unknown transfer pak write {:x}", address >> 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::{device, ui};
|
||||
use std::collections::HashMap;
|
||||
use crate::device;
|
||||
#[cfg(all(feature = "gui", not(target_os = "android")))]
|
||||
use crate::ui;
|
||||
|
||||
const JCMD_VRU_READ: u8 = 0x09;
|
||||
const JCMD_VRU_WRITE: u8 = 0x0A;
|
||||
@@ -16,7 +17,7 @@ const VOICE_STATUS_BUSY: u8 = 0x05;
|
||||
const JDT_VRU: u16 = 0x0100; /* VRU */
|
||||
const CONT_FLAVOR: u16 = JDT_VRU;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Vru {
|
||||
pub status: u8,
|
||||
pub voice_state: u8,
|
||||
@@ -26,15 +27,7 @@ pub struct Vru {
|
||||
pub word_buffer: [u16; 40],
|
||||
pub words: Vec<String>,
|
||||
pub talking: bool,
|
||||
pub word_mappings: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct VruWindow {
|
||||
#[serde(skip)]
|
||||
pub window_notifier: Option<tokio::sync::mpsc::Sender<Option<Vec<String>>>>,
|
||||
#[serde(skip)]
|
||||
pub word_receiver: Option<tokio::sync::mpsc::Receiver<String>>,
|
||||
pub word_mappings: rustc_hash::FxHashMap<String, String>,
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
@@ -187,17 +180,12 @@ pub fn process(device: &mut device::Device, channel: usize) {
|
||||
device.pif.ram[device.pif.channels[channel].rx_buf.unwrap()] = 0;
|
||||
}
|
||||
JCMD_VRU_READ => {
|
||||
let index = if device.vru_window.window_notifier.is_some() {
|
||||
ui::vru::prompt_for_match(
|
||||
&device.vru.words,
|
||||
device.vru_window.window_notifier.as_ref().unwrap(),
|
||||
device.vru_window.word_receiver.as_mut().unwrap(),
|
||||
)
|
||||
} else {
|
||||
0x7FFF
|
||||
};
|
||||
#[cfg(all(feature = "gui", not(target_os = "android")))]
|
||||
let index = ui::vru::prompt_for_match(&device.vru.words, device.vi.frame_time);
|
||||
#[cfg(any(not(feature = "gui"), target_os = "android"))]
|
||||
let index = 0x7FFF;
|
||||
let num_results = if index == 0x7FFF { 0 } else { 1 };
|
||||
let data: HashMap<usize, u16> = HashMap::from([
|
||||
let data: rustc_hash::FxHashMap<usize, u16> = rustc_hash::FxHashMap::from_iter([
|
||||
(0, 0x8000),
|
||||
(2, 0x0F00),
|
||||
(4, 0), // error flags
|
||||
@@ -258,7 +246,7 @@ pub fn vru_talking_event(device: &mut device::Device) {
|
||||
}
|
||||
|
||||
fn create_word_mappings(device: &mut device::Device) {
|
||||
device.vru.word_mappings = HashMap::from([
|
||||
device.vru.word_mappings = rustc_hash::FxHashMap::from_iter([
|
||||
(
|
||||
String::from("03A50024000303CF00A80003036000EA"),
|
||||
String::from("pikachu"),
|
||||
|
||||
@@ -1,42 +1,44 @@
|
||||
use crate::{device, savestates};
|
||||
|
||||
pub const COP0_INDEX_REG: u32 = 0;
|
||||
const COP0_RANDOM_REG: u32 = 1;
|
||||
pub const COP0_ENTRYLO0_REG: u32 = 2;
|
||||
pub const COP0_ENTRYLO1_REG: u32 = 3;
|
||||
pub const COP0_CONTEXT_REG: u32 = 4;
|
||||
pub const COP0_PAGEMASK_REG: u32 = 5;
|
||||
const COP0_WIRED_REG: u32 = 6;
|
||||
//const COP0_UNUSED_7: u32 = 7;
|
||||
pub const COP0_BADVADDR_REG: u32 = 8;
|
||||
pub const COP0_COUNT_REG: u32 = 9;
|
||||
pub const COP0_ENTRYHI_REG: u32 = 10;
|
||||
const COP0_COMPARE_REG: u32 = 11;
|
||||
pub const COP0_STATUS_REG: u32 = 12;
|
||||
pub const COP0_CAUSE_REG: u32 = 13;
|
||||
pub const COP0_EPC_REG: u32 = 14;
|
||||
const COP0_PREVID_REG: u32 = 15;
|
||||
const COP0_CONFIG_REG: u32 = 16;
|
||||
pub const COP0_LLADDR_REG: u32 = 17;
|
||||
//const COP0_WATCHLO_REG: u32 = 18;
|
||||
//const COP0_WATCHHI_REG: u32 = 19;
|
||||
pub const COP0_XCONTEXT_REG: u32 = 20;
|
||||
//const COP0_UNUSED_21: u32 = 21;
|
||||
//const COP0_UNUSED_22: u32 = 22;
|
||||
//const COP0_UNUSED_23: u32 = 23;
|
||||
//const COP0_UNUSED_24: u32 = 24;
|
||||
//const COP0_UNUSED_25: u32 = 25;
|
||||
//const COP0_PARITYERR_REG: u32 = 26;
|
||||
//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;
|
||||
//const COP0_UNUSED_31: u32 = 31;
|
||||
pub const COP0_REGS_COUNT: u32 = 32;
|
||||
pub const COP0_INDEX_REG: usize = 0;
|
||||
const COP0_RANDOM_REG: usize = 1;
|
||||
pub const COP0_ENTRYLO0_REG: usize = 2;
|
||||
pub const COP0_ENTRYLO1_REG: usize = 3;
|
||||
pub const COP0_CONTEXT_REG: usize = 4;
|
||||
pub const COP0_PAGEMASK_REG: usize = 5;
|
||||
const COP0_WIRED_REG: usize = 6;
|
||||
//const COP0_UNUSED_7: usize = 7;
|
||||
pub const COP0_BADVADDR_REG: usize = 8;
|
||||
pub const COP0_COUNT_REG: usize = 9;
|
||||
pub const COP0_ENTRYHI_REG: usize = 10;
|
||||
const COP0_COMPARE_REG: usize = 11;
|
||||
pub const COP0_STATUS_REG: usize = 12;
|
||||
pub const COP0_CAUSE_REG: usize = 13;
|
||||
pub const COP0_EPC_REG: usize = 14;
|
||||
const COP0_PREVID_REG: usize = 15;
|
||||
const COP0_CONFIG_REG: usize = 16;
|
||||
pub const COP0_LLADDR_REG: usize = 17;
|
||||
//const COP0_WATCHLO_REG: usize = 18;
|
||||
//const COP0_WATCHHI_REG: usize = 19;
|
||||
pub const COP0_XCONTEXT_REG: usize = 20;
|
||||
//const COP0_UNUSED_21: usize = 21;
|
||||
//const COP0_UNUSED_22: usize = 22;
|
||||
//const COP0_UNUSED_23: usize = 23;
|
||||
//const COP0_UNUSED_24: usize = 24;
|
||||
//const COP0_UNUSED_25: usize = 25;
|
||||
//const COP0_PARITYERR_REG: usize = 26;
|
||||
//const COP0_CACHEERR_REG: usize = 27;
|
||||
pub const COP0_TAGLO_REG: usize = 28;
|
||||
//const COP0_TAGHI_REG: usize = 29;
|
||||
pub const COP0_ERROREPC_REG: usize = 30;
|
||||
//const COP0_UNUSED_31: usize = 31;
|
||||
pub const COP0_REGS_COUNT: usize = 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,6 +57,7 @@ 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;
|
||||
pub const COP0_CAUSE_IP4: u64 = 1 << 12;
|
||||
pub const COP0_CAUSE_IP7: u64 = 1 << 15;
|
||||
pub const COP0_CAUSE_BD: u64 = 1 << 31;
|
||||
|
||||
@@ -62,7 +65,7 @@ pub const COP0_CAUSE_CE1: u64 = 1 << 28;
|
||||
pub const COP0_CAUSE_CE2: u64 = 1 << 29;
|
||||
|
||||
pub const COP0_CAUSE_IP_MASK: u64 = 0b00000000000000001111111100000000;
|
||||
//pub const COP0_CAUSE_EXCCODE_MASK: u64 = 0x1F << 2;
|
||||
pub const COP0_CAUSE_EXCCODE_MASK: u64 = 0x1F << 2;
|
||||
pub const COP0_CONTEXT_BADVPN2_MASK: u64 = 0b00000000011111111111111111110000;
|
||||
pub const COP0_XCONTEXT_BADVPN2_MASK: u64 = 0b01111111111111111111111111110000;
|
||||
pub const COP0_XCONTEXT_REGION_MASK: u64 = 0b110000000000000000000000000000000;
|
||||
@@ -88,20 +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)]
|
||||
#[derive(Clone, 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],
|
||||
pub regs: [u64; COP0_REGS_COUNT],
|
||||
pub reg_write_masks: [u64; COP0_REGS_COUNT],
|
||||
#[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_lut_r: rustc_hash::FxHashMap<u64, device::tlb::TlbLut>,
|
||||
pub tlb_lut_w: rustc_hash::FxHashMap<u64, device::tlb::TlbLut>,
|
||||
pub tlb_entries: [device::tlb::TlbEntry; 32],
|
||||
pub is_event: bool,
|
||||
pub pending_compare_interrupt: bool,
|
||||
}
|
||||
|
||||
fn mfc0(device: &mut device::Device, opcode: u32) {
|
||||
@@ -134,11 +136,11 @@ fn dmtc0(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
fn tlbr(device: &mut device::Device, _opcode: u32) {
|
||||
device::tlb::read(device, device.cpu.cop0.regs[COP0_INDEX_REG as usize])
|
||||
device::tlb::read(device, device.cpu.cop0.regs[COP0_INDEX_REG])
|
||||
}
|
||||
|
||||
fn tlbwi(device: &mut device::Device, _opcode: u32) {
|
||||
device::tlb::write(device, device.cpu.cop0.regs[COP0_INDEX_REG as usize])
|
||||
device::tlb::write(device, device.cpu.cop0.regs[COP0_INDEX_REG])
|
||||
}
|
||||
|
||||
fn tlbwr(device: &mut device::Device, _opcode: u32) {
|
||||
@@ -151,12 +153,12 @@ fn tlbp(device: &mut device::Device, _opcode: u32) {
|
||||
}
|
||||
|
||||
fn eret(device: &mut device::Device, _opcode: u32) {
|
||||
if device.cpu.cop0.regs[COP0_STATUS_REG as usize] & COP0_STATUS_ERL != 0 {
|
||||
device.cpu.pc = device.cpu.cop0.regs[COP0_ERROREPC_REG as usize];
|
||||
device.cpu.cop0.regs[COP0_STATUS_REG as usize] &= !COP0_STATUS_ERL
|
||||
if device.cpu.cop0.regs[COP0_STATUS_REG] & COP0_STATUS_ERL != 0 {
|
||||
device.cpu.pc = device.cpu.cop0.regs[COP0_ERROREPC_REG];
|
||||
device.cpu.cop0.regs[COP0_STATUS_REG] &= !COP0_STATUS_ERL
|
||||
} else {
|
||||
device.cpu.pc = device.cpu.cop0.regs[COP0_EPC_REG as usize];
|
||||
device.cpu.cop0.regs[COP0_STATUS_REG as usize] &= !COP0_STATUS_EXL
|
||||
device.cpu.pc = device.cpu.cop0.regs[COP0_EPC_REG];
|
||||
device.cpu.cop0.regs[COP0_STATUS_REG] &= !COP0_STATUS_EXL
|
||||
}
|
||||
device.cpu.branch_state.state = device::cpu::State::Exception;
|
||||
device.cpu.llbit = false;
|
||||
@@ -179,8 +181,10 @@ 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 {
|
||||
match index as usize {
|
||||
COP0_COUNT_REG => device.cpu.cop0.regs[index as usize] >> 1,
|
||||
COP0_RANDOM_REG => set_random_register(device),
|
||||
7 | 21 | 22 | 23 | 24 | 25 | 31 => device.cpu.cop0.reg_latch,
|
||||
@@ -190,22 +194,18 @@ fn get_control_registers(device: &device::Device, index: u32) -> u64 {
|
||||
|
||||
fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64) {
|
||||
device.cpu.cop0.reg_latch = data;
|
||||
match index {
|
||||
match index as usize {
|
||||
COP0_COUNT_REG => {
|
||||
data &= 0xFFFFFFFF;
|
||||
data <<= 1;
|
||||
device::events::translate_events(
|
||||
device,
|
||||
device.cpu.cop0.regs[COP0_COUNT_REG as usize],
|
||||
data,
|
||||
);
|
||||
device.cpu.cop0.regs[COP0_COUNT_REG as usize] = data;
|
||||
device::events::translate_events(device, device.cpu.cop0.regs[COP0_COUNT_REG], data);
|
||||
device.cpu.cop0.regs[COP0_COUNT_REG] = data;
|
||||
return;
|
||||
}
|
||||
COP0_WIRED_REG => device.cpu.cop0.regs[COP0_RANDOM_REG as usize] = 31,
|
||||
COP0_WIRED_REG => device.cpu.cop0.regs[COP0_RANDOM_REG] = 31,
|
||||
COP0_COMPARE_REG => {
|
||||
data &= 0xFFFFFFFF;
|
||||
let current_count = (device.cpu.cop0.regs[COP0_COUNT_REG as usize] >> 1) & 0xFFFFFFFF;
|
||||
let current_count = (device.cpu.cop0.regs[COP0_COUNT_REG] >> 1) & 0xFFFFFFFF;
|
||||
let mut compare_event_diff = (data as u32).wrapping_sub(current_count as u32);
|
||||
|
||||
if compare_event_diff == 0 {
|
||||
@@ -217,13 +217,12 @@ fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64)
|
||||
device::events::EVENT_TYPE_COMPARE,
|
||||
(compare_event_diff as u64) << 1,
|
||||
);
|
||||
device.cpu.cop0.regs[COP0_CAUSE_REG as usize] &= !COP0_CAUSE_IP7;
|
||||
device.cpu.cop0.pending_compare_interrupt = false;
|
||||
device.cpu.cop0.regs[COP0_CAUSE_REG] &= !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)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -236,7 +235,9 @@ fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64)
|
||||
}
|
||||
|
||||
pub fn compare_event(device: &mut device::Device) {
|
||||
device.cpu.cop0.pending_compare_interrupt = true;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] |= device::cop0::COP0_CAUSE_IP7;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] &= !device::cop0::COP0_CAUSE_EXCCODE_MASK;
|
||||
|
||||
device::events::create_event_at(
|
||||
device,
|
||||
device::events::EVENT_TYPE_COMPARE,
|
||||
@@ -246,53 +247,53 @@ pub fn compare_event(device: &mut device::Device) {
|
||||
}
|
||||
|
||||
fn set_random_register(device: &device::Device) -> u64 {
|
||||
if device.cpu.cop0.regs[COP0_WIRED_REG as usize] > 31 {
|
||||
(u64::MAX - device.cpu.cop0.regs[COP0_COUNT_REG as usize]) & 0x3F
|
||||
if device.cpu.cop0.regs[COP0_WIRED_REG] > 31 {
|
||||
(u64::MAX - device.cpu.cop0.regs[COP0_COUNT_REG]) & 0x3F
|
||||
} else {
|
||||
(u64::MAX - device.cpu.cop0.regs[COP0_COUNT_REG as usize])
|
||||
% (32 - device.cpu.cop0.regs[COP0_WIRED_REG as usize])
|
||||
+ device.cpu.cop0.regs[COP0_WIRED_REG as usize]
|
||||
(u64::MAX - device.cpu.cop0.regs[COP0_COUNT_REG])
|
||||
% (32 - device.cpu.cop0.regs[COP0_WIRED_REG])
|
||||
+ device.cpu.cop0.regs[COP0_WIRED_REG]
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
device.cpu.cop0.regs[COP0_COUNT_REG] += 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::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
|
||||
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 = [
|
||||
@@ -370,14 +371,14 @@ pub fn init(device: &mut device::Device) {
|
||||
map_instructions(device);
|
||||
|
||||
// taken from VR4300 manual
|
||||
device.cpu.cop0.regs[COP0_RANDOM_REG as usize] = 0b00000000000000000000000000011111;
|
||||
device.cpu.cop0.regs[COP0_CONFIG_REG as usize] = 0b01110000000001101110010001100000;
|
||||
device.cpu.cop0.regs[COP0_STATUS_REG as usize] = 0b00000000010000000000000000000100;
|
||||
device.cpu.cop0.regs[COP0_PREVID_REG as usize] = 0b00000000000000000000101100100010;
|
||||
device.cpu.cop0.regs[COP0_EPC_REG as usize] = 0b11111111111111111111111111111111;
|
||||
device.cpu.cop0.regs[COP0_ERROREPC_REG as usize] = 0b11111111111111111111111111111111;
|
||||
device.cpu.cop0.regs[COP0_BADVADDR_REG as usize] = 0xFFFFFFFF;
|
||||
device.cpu.cop0.regs[COP0_CONTEXT_REG as usize] = 0x7FFFF0;
|
||||
device.cpu.cop0.regs[COP0_RANDOM_REG] = 0b00000000000000000000000000011111;
|
||||
device.cpu.cop0.regs[COP0_CONFIG_REG] = 0b01110000000001101110010001100000;
|
||||
device.cpu.cop0.regs[COP0_STATUS_REG] = 0b00000000010000000000000000000100;
|
||||
device.cpu.cop0.regs[COP0_PREVID_REG] = 0b00000000000000000000101100100010;
|
||||
device.cpu.cop0.regs[COP0_EPC_REG] = 0b11111111111111111111111111111111;
|
||||
device.cpu.cop0.regs[COP0_ERROREPC_REG] = 0b11111111111111111111111111111111;
|
||||
device.cpu.cop0.regs[COP0_BADVADDR_REG] = 0xFFFFFFFF;
|
||||
device.cpu.cop0.regs[COP0_CONTEXT_REG] = 0x7FFFF0;
|
||||
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_COMPARE, u32::MAX as u64)
|
||||
}
|
||||
|
||||
@@ -24,7 +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)]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Cop1 {
|
||||
pub fcr0: u32,
|
||||
pub fcr31: u32,
|
||||
@@ -47,9 +47,7 @@ pub struct Cop1 {
|
||||
}
|
||||
|
||||
pub fn lwc1(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
|
||||
@@ -79,9 +77,7 @@ pub fn lwc1(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
pub fn ldc1(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
|
||||
@@ -118,9 +114,7 @@ pub fn ldc1(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
pub fn swc1(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
|
||||
@@ -145,9 +139,7 @@ pub fn swc1(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
pub fn sdc1(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
|
||||
@@ -181,9 +173,7 @@ pub fn sdc1(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
fn mfc1(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
|
||||
@@ -193,9 +183,7 @@ fn mfc1(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
fn dmfc1(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
|
||||
@@ -205,9 +193,7 @@ fn dmfc1(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
fn cfc1(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
|
||||
@@ -217,9 +203,7 @@ fn cfc1(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
fn dcfc1(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
|
||||
@@ -229,9 +213,7 @@ fn dcfc1(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
fn mtc1(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
|
||||
@@ -247,9 +229,7 @@ fn mtc1(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
fn dmtc1(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
|
||||
@@ -260,9 +240,7 @@ fn dmtc1(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
fn ctc1(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
|
||||
@@ -274,9 +252,7 @@ fn ctc1(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
fn dctc1(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
|
||||
@@ -286,9 +262,7 @@ fn dctc1(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
fn execute_cop1_b(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
|
||||
@@ -296,9 +270,7 @@ fn execute_cop1_b(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
fn execute_cop1_s(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
|
||||
@@ -306,9 +278,7 @@ fn execute_cop1_s(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
fn execute_cop1_d(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
|
||||
@@ -316,9 +286,7 @@ fn execute_cop1_d(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
fn execute_cop1_l(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
|
||||
@@ -326,9 +294,7 @@ fn execute_cop1_l(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
fn execute_cop1_w(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
|
||||
@@ -340,9 +306,7 @@ fn unusable(device: &mut device::Device, _opcode: u32) {
|
||||
}
|
||||
|
||||
pub fn reserved(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
|
||||
@@ -406,9 +370,7 @@ fn set_control_registers_fpu(device: &mut device::Device, index: u32, data: u32)
|
||||
|
||||
pub fn set_fpr_single(device: &mut device::Device, index: usize, value: f32, clear_high: bool) {
|
||||
let bytes = value.to_ne_bytes();
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_FR
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_FR == 0 {
|
||||
device.cpu.cop1.fgr32[index] = bytes;
|
||||
} else {
|
||||
let bytes_lo = bytes;
|
||||
@@ -422,9 +384,7 @@ pub fn set_fpr_single(device: &mut device::Device, index: usize, value: f32, cle
|
||||
}
|
||||
|
||||
pub fn get_fpr_single(device: &device::Device, index: usize) -> f32 {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_FR
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_FR == 0 {
|
||||
f32::from_ne_bytes(device.cpu.cop1.fgr32[index])
|
||||
} else {
|
||||
f32::from_ne_bytes(device.cpu.cop1.fgr64[index][0..4].try_into().unwrap())
|
||||
@@ -433,9 +393,7 @@ pub fn get_fpr_single(device: &device::Device, index: usize) -> f32 {
|
||||
|
||||
pub fn set_fpr_double(device: &mut device::Device, index: usize, value: f64) {
|
||||
let bytes = value.to_ne_bytes();
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_FR
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_FR == 0 {
|
||||
device.cpu.cop1.fgr32[index & !1] = bytes[0..4].try_into().unwrap();
|
||||
device.cpu.cop1.fgr32[(index & !1) + 1] = bytes[4..8].try_into().unwrap();
|
||||
} else {
|
||||
@@ -444,9 +402,7 @@ pub fn set_fpr_double(device: &mut device::Device, index: usize, value: f64) {
|
||||
}
|
||||
|
||||
pub fn get_fpr_double(device: &device::Device, index: usize) -> f64 {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_FR
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_FR == 0 {
|
||||
let bytes_lo = device.cpu.cop1.fgr32[index & !1];
|
||||
let bytes_hi = device.cpu.cop1.fgr32[(index & !1) + 1];
|
||||
f64::from_ne_bytes([bytes_lo, bytes_hi].concat().try_into().unwrap())
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{device, savestates};
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Cop2 {
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub instrs: [fn(&mut device::Device, u32); 32],
|
||||
@@ -8,9 +8,7 @@ pub struct Cop2 {
|
||||
}
|
||||
|
||||
fn mfc2(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU2 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize] =
|
||||
@@ -18,18 +16,14 @@ fn mfc2(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
fn dmfc2(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU2 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize] = device.cpu.cop2.reg_latch
|
||||
}
|
||||
|
||||
fn cfc2(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU2 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize] =
|
||||
@@ -37,27 +31,21 @@ fn cfc2(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
fn mtc2(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU2 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
device.cpu.cop2.reg_latch = device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize]
|
||||
}
|
||||
|
||||
fn dmtc2(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU2 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
device.cpu.cop2.reg_latch = device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize]
|
||||
}
|
||||
|
||||
fn ctc2(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU2 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
|
||||
@@ -65,9 +53,7 @@ fn ctc2(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
pub fn reserved(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU2 == 0 {
|
||||
return unusable(device, opcode);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{device, savestates, ui};
|
||||
|
||||
#[derive(PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum State {
|
||||
Step,
|
||||
Take,
|
||||
@@ -11,13 +11,13 @@ pub enum State {
|
||||
Exception,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct BranchState {
|
||||
pub state: State,
|
||||
pub pc: u64,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Cpu {
|
||||
pub cop0: device::cop0::Cop0,
|
||||
pub cop1: device::cop1::Cop1,
|
||||
@@ -228,8 +228,8 @@ pub fn init(device: &mut device::Device) {
|
||||
93750000
|
||||
} else {
|
||||
ui::video::onscreen_message(
|
||||
&device.ui,
|
||||
"Overclocking enabled, setting clock rate to 125 MHz",
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_SHORT,
|
||||
);
|
||||
125000000
|
||||
};
|
||||
@@ -300,8 +300,7 @@ pub fn run(device: &mut device::Device) {
|
||||
State::Exception => device.cpu.branch_state.state = State::Step,
|
||||
}
|
||||
device::cop0::add_cycles(device, 1);
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] > device.cpu.next_event_count
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG] > device.cpu.next_event_count {
|
||||
device::events::trigger_event(device)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ pub fn check_relative_idle_loop(device: &mut device::Device, opcode: u32) {
|
||||
device::memory::AccessSize::Word,
|
||||
) == 0
|
||||
{
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] = device.cpu.next_event_count
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG] = device.cpu.next_event_count
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ fn check_absolute_idle_loop(device: &mut device::Device, target: u64) {
|
||||
device::memory::AccessSize::Word,
|
||||
) == 0
|
||||
{
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] = device.cpu.next_event_count
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG] = device.cpu.next_event_count
|
||||
}
|
||||
}
|
||||
|
||||
@@ -647,17 +647,17 @@ pub fn cache(device: &mut device::Device, opcode: u32) {
|
||||
valid = 1
|
||||
}
|
||||
device::memory::masked_write_64(
|
||||
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize],
|
||||
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG],
|
||||
valid << 7,
|
||||
0x80,
|
||||
);
|
||||
device::memory::masked_write_64(
|
||||
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize],
|
||||
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG],
|
||||
0,
|
||||
0x40,
|
||||
);
|
||||
device::memory::masked_write_64(
|
||||
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize],
|
||||
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG],
|
||||
(device.memory.icache[icache_line].tag >> 4) as u64,
|
||||
0xFFFFF00,
|
||||
)
|
||||
@@ -665,10 +665,9 @@ pub fn cache(device: &mut device::Device, opcode: u32) {
|
||||
0x08 => {
|
||||
//icache store tag
|
||||
device.memory.icache[icache_line].valid =
|
||||
(device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize] & 0x80) >> 7 != 0;
|
||||
(device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG] & 0x80) >> 7 != 0;
|
||||
device.memory.icache[icache_line].tag =
|
||||
((device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize] & 0xFFFFF00) << 4)
|
||||
as u32
|
||||
((device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG] & 0xFFFFF00) << 4) as u32
|
||||
}
|
||||
0x10 => {
|
||||
//icache hit invalidate
|
||||
@@ -704,17 +703,17 @@ pub fn cache(device: &mut device::Device, opcode: u32) {
|
||||
dirty = 1
|
||||
}
|
||||
device::memory::masked_write_64(
|
||||
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize],
|
||||
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG],
|
||||
valid << 7,
|
||||
0x80,
|
||||
);
|
||||
device::memory::masked_write_64(
|
||||
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize],
|
||||
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG],
|
||||
dirty << 6,
|
||||
0x40,
|
||||
);
|
||||
device::memory::masked_write_64(
|
||||
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize],
|
||||
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG],
|
||||
(device.memory.dcache[dcache_line].tag >> 4) as u64,
|
||||
0xFFFFF00,
|
||||
)
|
||||
@@ -722,12 +721,11 @@ pub fn cache(device: &mut device::Device, opcode: u32) {
|
||||
0x09 => {
|
||||
//dcache index store tag
|
||||
device.memory.dcache[dcache_line].valid =
|
||||
(device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize] & 0x80) >> 7 != 0;
|
||||
(device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG] & 0x80) >> 7 != 0;
|
||||
device.memory.dcache[dcache_line].dirty =
|
||||
(device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize] & 0x40) >> 6 != 0;
|
||||
(device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG] & 0x40) >> 6 != 0;
|
||||
device.memory.dcache[dcache_line].tag =
|
||||
((device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize] & 0xFFFFF00) << 4)
|
||||
as u32
|
||||
((device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG] & 0xFFFFF00) << 4) as u32
|
||||
}
|
||||
0x0D => {
|
||||
//dcache create dirty exclusive
|
||||
@@ -765,7 +763,7 @@ pub fn cache(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
panic!("unknown cache code {:#01x}", rt(opcode))
|
||||
panic!("unknown cache code {:x}", rt(opcode))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -787,7 +785,7 @@ pub fn ll(device: &mut device::Device, opcode: u32) {
|
||||
cached,
|
||||
) as i32);
|
||||
device.cpu.llbit = true;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_LLADDR_REG as usize] = phys_address >> 4
|
||||
device.cpu.cop0.regs[device::cop0::COP0_LLADDR_REG] = phys_address >> 4
|
||||
}
|
||||
|
||||
pub fn lld(device: &mut device::Device, opcode: u32) {
|
||||
@@ -816,7 +814,7 @@ pub fn lld(device: &mut device::Device, opcode: u32) {
|
||||
device.cpu.gpr[rt(opcode) as usize] = ((w[0] as u64) << 32) | (w[1]) as u64;
|
||||
|
||||
device.cpu.llbit = true;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_LLADDR_REG as usize] = phys_address >> 4
|
||||
device.cpu.cop0.regs[device::cop0::COP0_LLADDR_REG] = phys_address >> 4
|
||||
}
|
||||
|
||||
pub fn ld(device: &mut device::Device, opcode: u32) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
|
||||
pub const EVENT_TYPE_NONE: usize = 0;
|
||||
pub const EVENT_TYPE_AI: usize = 1;
|
||||
@@ -12,7 +13,10 @@ 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_COUNT: usize = 12;
|
||||
pub const EVENT_TYPE_SAVE: usize = 12;
|
||||
pub const EVENT_TYPE_NMI: usize = 13;
|
||||
pub const EVENT_TYPE_UPDATE_SCREEN: usize = 14;
|
||||
pub const EVENT_TYPE_COUNT: usize = 15;
|
||||
|
||||
#[derive(PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Event {
|
||||
@@ -23,7 +27,7 @@ pub struct Event {
|
||||
pub fn create_event(device: &mut device::Device, name: usize, when: u64) {
|
||||
device.cpu.events[name] = Event {
|
||||
enabled: true,
|
||||
count: device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + when,
|
||||
count: device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG] + when,
|
||||
};
|
||||
set_next_event(device);
|
||||
}
|
||||
@@ -60,6 +64,9 @@ fn get_event_handler(name: usize) -> fn(&mut device::Device) {
|
||||
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,
|
||||
EVENT_TYPE_UPDATE_SCREEN => device::vi::update_screen,
|
||||
_ => dummy_event,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
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]
|
||||
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG]
|
||||
& (device::cop0::COP0_STATUS_IE
|
||||
| device::cop0::COP0_STATUS_EXL
|
||||
| device::cop0::COP0_STATUS_ERL))
|
||||
@@ -11,18 +12,14 @@ 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;
|
||||
} else if device.cpu.cop0.pending_compare_interrupt {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] = device::cop0::COP0_CAUSE_IP7;
|
||||
device.cpu.cop0.pending_compare_interrupt = false;
|
||||
if device.mi.regs[device::mi::MI_INTR_REG] & device.mi.regs[device::mi::MI_INTR_MASK_REG] != 0 {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] |= device::cop0::COP0_CAUSE_IP2;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] &=
|
||||
!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]
|
||||
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG]
|
||||
& device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG]
|
||||
& device::cop0::COP0_CAUSE_IP_MASK)
|
||||
== 0
|
||||
{
|
||||
@@ -42,43 +39,37 @@ pub fn interrupt_exception(device: &mut device::Device) {
|
||||
}
|
||||
|
||||
pub fn floating_point_exception(device: &mut device::Device) {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] =
|
||||
device::cop0::COP0_CAUSE_EXCCODE_FPE;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] = device::cop0::COP0_CAUSE_EXCCODE_FPE;
|
||||
|
||||
exception_general(device, 0x180)
|
||||
}
|
||||
|
||||
pub fn trap_exception(device: &mut device::Device) {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] =
|
||||
device::cop0::COP0_CAUSE_EXCCODE_TR;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] = device::cop0::COP0_CAUSE_EXCCODE_TR;
|
||||
|
||||
exception_general(device, 0x180)
|
||||
}
|
||||
|
||||
pub fn syscall_exception(device: &mut device::Device) {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] =
|
||||
device::cop0::COP0_CAUSE_EXCCODE_SYS;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] = device::cop0::COP0_CAUSE_EXCCODE_SYS;
|
||||
|
||||
exception_general(device, 0x180)
|
||||
}
|
||||
|
||||
pub fn break_exception(device: &mut device::Device) {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] =
|
||||
device::cop0::COP0_CAUSE_EXCCODE_BP;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] = device::cop0::COP0_CAUSE_EXCCODE_BP;
|
||||
|
||||
exception_general(device, 0x180)
|
||||
}
|
||||
|
||||
pub fn reserved_exception(device: &mut device::Device, cop: u64) {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] =
|
||||
device::cop0::COP0_CAUSE_EXCCODE_RI | cop;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] = device::cop0::COP0_CAUSE_EXCCODE_RI | cop;
|
||||
|
||||
exception_general(device, 0x180)
|
||||
}
|
||||
|
||||
pub fn cop_unusable_exception(device: &mut device::Device, cop: u64) {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] =
|
||||
device::cop0::COP0_CAUSE_EXCCODE_CPU | cop;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] = device::cop0::COP0_CAUSE_EXCCODE_CPU | cop;
|
||||
|
||||
exception_general(device, 0x180)
|
||||
}
|
||||
@@ -89,31 +80,29 @@ pub fn tlb_miss_exception(
|
||||
access_type: device::memory::AccessType,
|
||||
) {
|
||||
if access_type == device::memory::AccessType::Read {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] =
|
||||
device::cop0::COP0_CAUSE_EXCCODE_TLBL
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] = device::cop0::COP0_CAUSE_EXCCODE_TLBL
|
||||
} else {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] =
|
||||
device::cop0::COP0_CAUSE_EXCCODE_TLBS
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] = device::cop0::COP0_CAUSE_EXCCODE_TLBS
|
||||
}
|
||||
|
||||
device.cpu.cop0.regs[device::cop0::COP0_BADVADDR_REG as usize] = address;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_BADVADDR_REG] = address;
|
||||
device::memory::masked_write_64(
|
||||
&mut device.cpu.cop0.regs[device::cop0::COP0_CONTEXT_REG as usize],
|
||||
&mut device.cpu.cop0.regs[device::cop0::COP0_CONTEXT_REG],
|
||||
address >> 9,
|
||||
device::cop0::COP0_CONTEXT_BADVPN2_MASK,
|
||||
);
|
||||
device::memory::masked_write_64(
|
||||
&mut device.cpu.cop0.regs[device::cop0::COP0_XCONTEXT_REG as usize],
|
||||
&mut device.cpu.cop0.regs[device::cop0::COP0_XCONTEXT_REG],
|
||||
address >> 9,
|
||||
device::cop0::COP0_XCONTEXT_BADVPN2_MASK,
|
||||
);
|
||||
device::memory::masked_write_64(
|
||||
&mut device.cpu.cop0.regs[device::cop0::COP0_XCONTEXT_REG as usize],
|
||||
&mut device.cpu.cop0.regs[device::cop0::COP0_XCONTEXT_REG],
|
||||
address >> 31,
|
||||
device::cop0::COP0_XCONTEXT_REGION_MASK,
|
||||
);
|
||||
device::memory::masked_write_64(
|
||||
&mut device.cpu.cop0.regs[device::cop0::COP0_ENTRYHI_REG as usize],
|
||||
&mut device.cpu.cop0.regs[device::cop0::COP0_ENTRYHI_REG],
|
||||
address,
|
||||
0xFFFFE000,
|
||||
);
|
||||
@@ -124,7 +113,7 @@ pub fn tlb_miss_exception(
|
||||
if address & !3 >= i.start_even && address & !3 <= i.end_even {
|
||||
valid = i.v_even != 0;
|
||||
if valid && access_type == device::memory::AccessType::Write && i.d_even == 0 {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] =
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] =
|
||||
device::cop0::COP0_CAUSE_EXCCODE_MOD;
|
||||
valid = false;
|
||||
}
|
||||
@@ -133,7 +122,7 @@ pub fn tlb_miss_exception(
|
||||
if address & !3 >= i.start_odd && address & !3 <= i.end_odd {
|
||||
valid = i.v_odd != 0;
|
||||
if valid && access_type == device::memory::AccessType::Write && i.d_odd == 0 {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] =
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] =
|
||||
device::cop0::COP0_CAUSE_EXCCODE_MOD;
|
||||
valid = false;
|
||||
}
|
||||
@@ -141,8 +130,7 @@ pub fn tlb_miss_exception(
|
||||
}
|
||||
}
|
||||
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_EXL
|
||||
== 0
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_EXL == 0
|
||||
&& valid
|
||||
{
|
||||
vector_offset = 0;
|
||||
@@ -151,26 +139,38 @@ 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] &= !device::cop0::COP0_CAUSE_IP4;
|
||||
|
||||
device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] |= device::cop0::COP0_STATUS_ERL
|
||||
| device::cop0::COP0_STATUS_SR
|
||||
| device::cop0::COP0_STATUS_BEV;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] &= !device::cop0::COP0_STATUS_TS;
|
||||
|
||||
device.cpu.cop0.regs[device::cop0::COP0_ERROREPC_REG] = 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] = 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
|
||||
{
|
||||
device.cpu.cop0.regs[device::cop0::COP0_EPC_REG as usize] = device.cpu.pc;
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_EXL == 0 {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_EPC_REG] = device.cpu.pc;
|
||||
if device::cpu::in_delay_slot(device) {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |=
|
||||
device::cop0::COP0_CAUSE_BD;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_EPC_REG as usize] -= 4;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] |= device::cop0::COP0_CAUSE_BD;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_EPC_REG] -= 4;
|
||||
} else {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
|
||||
!device::cop0::COP0_CAUSE_BD;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] &= !device::cop0::COP0_CAUSE_BD;
|
||||
}
|
||||
}
|
||||
|
||||
device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] |= device::cop0::COP0_STATUS_EXL;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] |= device::cop0::COP0_STATUS_EXL;
|
||||
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_BEV
|
||||
== 0
|
||||
{
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_BEV == 0 {
|
||||
device.cpu.pc = device::cpu_instructions::se32((0x80000000 + vector_offset) as i32);
|
||||
} else {
|
||||
device.cpu.pc = device::cpu_instructions::se32((0xBFC00200 + vector_offset) as i32);
|
||||
|
||||
@@ -22,8 +22,8 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
|
||||
let data = std::str::from_utf8(
|
||||
&device.cart.is_viewer_buffer[0x20_usize..(0x20 + length) as usize],
|
||||
);
|
||||
if data.is_ok() {
|
||||
print!("{}", data.unwrap());
|
||||
if let Ok(data) = data {
|
||||
print!("{}", data);
|
||||
}
|
||||
} else {
|
||||
let mut data = u32::from_be_bytes(
|
||||
|
||||
@@ -38,7 +38,7 @@ pub enum AccessSize {
|
||||
Icache = 32,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Memory {
|
||||
#[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
|
||||
@@ -62,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)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::device;
|
||||
|
||||
const MI_INIT_MODE_REG: u32 = 0;
|
||||
const MI_VERSION_REG: u32 = 1;
|
||||
pub const MI_INTR_REG: u32 = 2;
|
||||
pub const MI_INTR_MASK_REG: u32 = 3;
|
||||
pub const MI_REGS_COUNT: u32 = 4;
|
||||
pub const MI_INIT_MODE_REG: usize = 0;
|
||||
const MI_VERSION_REG: usize = 1;
|
||||
pub const MI_INTR_REG: usize = 2;
|
||||
pub const MI_INTR_MASK_REG: usize = 3;
|
||||
pub const MI_REGS_COUNT: usize = 4;
|
||||
|
||||
/* read */
|
||||
pub const MI_INTR_SP: u32 = 1 << 0;
|
||||
@@ -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,11 +42,11 @@ 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)]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Mi {
|
||||
pub regs: [u32; MI_REGS_COUNT as usize],
|
||||
pub regs: [u32; MI_REGS_COUNT],
|
||||
}
|
||||
|
||||
pub fn read_regs(
|
||||
@@ -60,40 +60,45 @@ pub fn read_regs(
|
||||
|
||||
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
match reg as usize {
|
||||
MI_INIT_MODE_REG => update_init_mode(device, value),
|
||||
MI_INTR_MASK_REG => update_intr_mask(device, value),
|
||||
_ => 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_IP2;
|
||||
if device.mi.regs[MI_INTR_REG] & device.mi.regs[MI_INTR_MASK_REG] == 0 {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] &= !device::cop0::COP0_CAUSE_IP2;
|
||||
}
|
||||
device::exceptions::check_pending_interrupts(device)
|
||||
}
|
||||
|
||||
fn update_init_mode(device: &mut device::Device, w: u32) {
|
||||
device.mi.regs[MI_INIT_MODE_REG as usize] &= !MI_INIT_LENGTH_MASK;
|
||||
device.mi.regs[MI_INIT_MODE_REG as usize] |= w & MI_INIT_LENGTH_MASK;
|
||||
device.mi.regs[MI_INIT_MODE_REG] &= !MI_INIT_LENGTH_MASK;
|
||||
device.mi.regs[MI_INIT_MODE_REG] |= w & MI_INIT_LENGTH_MASK;
|
||||
|
||||
if w & MI_CLR_INIT != 0 {
|
||||
device.mi.regs[MI_INIT_MODE_REG as usize] &= !MI_INIT_MODE
|
||||
for i in 0..(0x3F00000 >> 16) {
|
||||
device.memory.memory_map_write[i] = device::rdram::write_mem;
|
||||
}
|
||||
device.mi.regs[MI_INIT_MODE_REG] &= !MI_INIT_MODE
|
||||
}
|
||||
if w & MI_SET_INIT != 0 {
|
||||
device.mi.regs[MI_INIT_MODE_REG as usize] |= MI_INIT_MODE
|
||||
for i in 0..(0x3F00000 >> 16) {
|
||||
device.memory.memory_map_write[i] = device::rdram::write_mem_repeat;
|
||||
}
|
||||
device.mi.regs[MI_INIT_MODE_REG] |= MI_INIT_MODE
|
||||
}
|
||||
if w & MI_CLR_EBUS != 0 {
|
||||
device.mi.regs[MI_INIT_MODE_REG as usize] &= !MI_EBUS_MODE
|
||||
device.mi.regs[MI_INIT_MODE_REG] &= !MI_EBUS_MODE
|
||||
}
|
||||
if w & MI_SET_EBUS != 0 {
|
||||
device.mi.regs[MI_INIT_MODE_REG as usize] |= MI_EBUS_MODE
|
||||
device.mi.regs[MI_INIT_MODE_REG] |= MI_EBUS_MODE
|
||||
}
|
||||
if w & MI_CLR_RDRAM != 0 {
|
||||
device.mi.regs[MI_INIT_MODE_REG as usize] &= !MI_RDRAM_MODE
|
||||
device.mi.regs[MI_INIT_MODE_REG] &= !MI_RDRAM_MODE
|
||||
}
|
||||
if w & MI_SET_RDRAM != 0 {
|
||||
device.mi.regs[MI_INIT_MODE_REG as usize] |= MI_RDRAM_MODE
|
||||
device.mi.regs[MI_INIT_MODE_REG] |= MI_RDRAM_MODE
|
||||
}
|
||||
|
||||
if w & MI_CLR_DP_INTR != 0 {
|
||||
@@ -103,57 +108,56 @@ fn update_init_mode(device: &mut device::Device, w: u32) {
|
||||
|
||||
fn update_intr_mask(device: &mut device::Device, w: u32) {
|
||||
if w & MI_CLR_SP != 0 {
|
||||
device.mi.regs[MI_INTR_MASK_REG as usize] &= !MI_INTR_SP
|
||||
device.mi.regs[MI_INTR_MASK_REG] &= !MI_INTR_SP
|
||||
}
|
||||
if w & MI_SET_SP != 0 {
|
||||
device.mi.regs[MI_INTR_MASK_REG as usize] |= MI_INTR_SP
|
||||
device.mi.regs[MI_INTR_MASK_REG] |= MI_INTR_SP
|
||||
}
|
||||
if w & MI_CLR_SI != 0 {
|
||||
device.mi.regs[MI_INTR_MASK_REG as usize] &= !MI_INTR_SI
|
||||
device.mi.regs[MI_INTR_MASK_REG] &= !MI_INTR_SI
|
||||
}
|
||||
if w & MI_SET_SI != 0 {
|
||||
device.mi.regs[MI_INTR_MASK_REG as usize] |= MI_INTR_SI
|
||||
device.mi.regs[MI_INTR_MASK_REG] |= MI_INTR_SI
|
||||
}
|
||||
if w & MI_CLR_AI != 0 {
|
||||
device.mi.regs[MI_INTR_MASK_REG as usize] &= !MI_INTR_AI
|
||||
device.mi.regs[MI_INTR_MASK_REG] &= !MI_INTR_AI
|
||||
}
|
||||
if w & MI_SET_AI != 0 {
|
||||
device.mi.regs[MI_INTR_MASK_REG as usize] |= MI_INTR_AI
|
||||
device.mi.regs[MI_INTR_MASK_REG] |= MI_INTR_AI
|
||||
}
|
||||
if w & MI_CLR_VI != 0 {
|
||||
device.mi.regs[MI_INTR_MASK_REG as usize] &= !MI_INTR_VI
|
||||
device.mi.regs[MI_INTR_MASK_REG] &= !MI_INTR_VI
|
||||
}
|
||||
if w & MI_SET_VI != 0 {
|
||||
device.mi.regs[MI_INTR_MASK_REG as usize] |= MI_INTR_VI
|
||||
device.mi.regs[MI_INTR_MASK_REG] |= MI_INTR_VI
|
||||
}
|
||||
if w & MI_CLR_PI != 0 {
|
||||
device.mi.regs[MI_INTR_MASK_REG as usize] &= !MI_INTR_PI
|
||||
device.mi.regs[MI_INTR_MASK_REG] &= !MI_INTR_PI
|
||||
}
|
||||
if w & MI_SET_PI != 0 {
|
||||
device.mi.regs[MI_INTR_MASK_REG as usize] |= MI_INTR_PI
|
||||
device.mi.regs[MI_INTR_MASK_REG] |= MI_INTR_PI
|
||||
}
|
||||
if w & MI_CLR_DP != 0 {
|
||||
device.mi.regs[MI_INTR_MASK_REG as usize] &= !MI_INTR_DP
|
||||
device.mi.regs[MI_INTR_MASK_REG] &= !MI_INTR_DP
|
||||
}
|
||||
if w & MI_SET_DP != 0 {
|
||||
device.mi.regs[MI_INTR_MASK_REG as usize] |= MI_INTR_DP
|
||||
device.mi.regs[MI_INTR_MASK_REG] |= MI_INTR_DP
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
|
||||
device.mi.regs[MI_INTR_REG as usize] &= !interrupt;
|
||||
device.mi.regs[MI_INTR_REG] &= !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_IP2;
|
||||
if device.mi.regs[MI_INTR_REG] & device.mi.regs[MI_INTR_MASK_REG] == 0 {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] &= !device::cop0::COP0_CAUSE_IP2;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
|
||||
device.mi.regs[MI_INTR_REG as usize] |= interrupt;
|
||||
device.mi.regs[MI_INTR_REG] |= interrupt;
|
||||
device::exceptions::check_pending_interrupts(device)
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
device.mi.regs[MI_VERSION_REG as usize] = 0x02020102
|
||||
device.mi.regs[MI_VERSION_REG] = 0x02020102
|
||||
}
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
use crate::device;
|
||||
use rand_chacha::rand_core::RngCore;
|
||||
use crate::ui;
|
||||
use rand::Rng;
|
||||
|
||||
const PI_DRAM_ADDR_REG: u32 = 0;
|
||||
const PI_CART_ADDR_REG: u32 = 1;
|
||||
const PI_RD_LEN_REG: u32 = 2;
|
||||
const PI_WR_LEN_REG: u32 = 3;
|
||||
pub const PI_STATUS_REG: u32 = 4;
|
||||
const PI_BSD_DOM1_LAT_REG: u32 = 5;
|
||||
const PI_BSD_DOM1_PWD_REG: u32 = 6;
|
||||
const PI_BSD_DOM1_PGS_REG: u32 = 7;
|
||||
const PI_BSD_DOM1_RLS_REG: u32 = 8;
|
||||
const PI_BSD_DOM2_LAT_REG: u32 = 9;
|
||||
const PI_BSD_DOM2_PWD_REG: u32 = 10;
|
||||
const PI_BSD_DOM2_PGS_REG: u32 = 11;
|
||||
const PI_BSD_DOM2_RLS_REG: u32 = 12;
|
||||
//const UNKNOWN_REG: u32 = 13; //LibDragon
|
||||
pub const PI_REGS_COUNT: u32 = 14;
|
||||
const PI_DRAM_ADDR_REG: usize = 0;
|
||||
const PI_CART_ADDR_REG: usize = 1;
|
||||
const PI_RD_LEN_REG: usize = 2;
|
||||
const PI_WR_LEN_REG: usize = 3;
|
||||
pub const PI_STATUS_REG: usize = 4;
|
||||
const PI_BSD_DOM1_LAT_REG: usize = 5;
|
||||
const PI_BSD_DOM1_PWD_REG: usize = 6;
|
||||
const PI_BSD_DOM1_PGS_REG: usize = 7;
|
||||
const PI_BSD_DOM1_RLS_REG: usize = 8;
|
||||
const PI_BSD_DOM2_LAT_REG: usize = 9;
|
||||
const PI_BSD_DOM2_PWD_REG: usize = 10;
|
||||
const PI_BSD_DOM2_PGS_REG: usize = 11;
|
||||
const PI_BSD_DOM2_RLS_REG: usize = 12;
|
||||
//const UNKNOWN_REG: usize = 13; //LibDragon
|
||||
pub const PI_REGS_COUNT: usize = 14;
|
||||
|
||||
/* PI_STATUS - read */
|
||||
const PI_STATUS_DMA_BUSY: u32 = 1 << 0;
|
||||
@@ -27,9 +28,9 @@ 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)]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Pi {
|
||||
pub regs: [u32; PI_REGS_COUNT as usize],
|
||||
pub regs: [u32; PI_REGS_COUNT],
|
||||
}
|
||||
|
||||
struct PiHandler {
|
||||
@@ -44,7 +45,7 @@ pub fn read_regs(
|
||||
) -> u32 {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
match reg as usize {
|
||||
PI_WR_LEN_REG | PI_RD_LEN_REG => 0x7F,
|
||||
PI_CART_ADDR_REG => device.pi.regs[reg as usize] & 0xFFFFFFFE,
|
||||
PI_DRAM_ADDR_REG => device.pi.regs[reg as usize] & 0xFFFFFE,
|
||||
@@ -53,11 +54,11 @@ pub fn read_regs(
|
||||
}
|
||||
|
||||
fn dma_read(device: &mut device::Device) {
|
||||
let handler = get_handler(device.pi.regs[PI_CART_ADDR_REG as usize]);
|
||||
let handler = get_handler(device.pi.regs[PI_CART_ADDR_REG]);
|
||||
|
||||
let cart_addr = device.pi.regs[PI_CART_ADDR_REG as usize] & !1;
|
||||
let dram_addr = device.pi.regs[PI_DRAM_ADDR_REG as usize] & 0xFFFFFE;
|
||||
let mut length = (device.pi.regs[PI_RD_LEN_REG as usize] & 0xFFFFFF) + 1;
|
||||
let cart_addr = device.pi.regs[PI_CART_ADDR_REG] & !1;
|
||||
let dram_addr = device.pi.regs[PI_DRAM_ADDR_REG] & 0xFFFFFE;
|
||||
let mut length = (device.pi.regs[PI_RD_LEN_REG] & 0xFFFFFF) + 1;
|
||||
|
||||
/* PI seems to treat the first 128 bytes differently, see https://n64brew.dev/wiki/Peripheral_Interface#Unaligned_DMA_transfer */
|
||||
if length >= 0x7f && (length & 1) != 0 {
|
||||
@@ -69,20 +70,18 @@ fn dma_read(device: &mut device::Device) {
|
||||
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] =
|
||||
(device.pi.regs[PI_DRAM_ADDR_REG as usize] + length + 7) & !7;
|
||||
device.pi.regs[PI_CART_ADDR_REG as usize] =
|
||||
(device.pi.regs[PI_CART_ADDR_REG as usize] + length + 1) & !1;
|
||||
device.pi.regs[PI_DRAM_ADDR_REG] = (device.pi.regs[PI_DRAM_ADDR_REG] + length + 7) & !7;
|
||||
device.pi.regs[PI_CART_ADDR_REG] = (device.pi.regs[PI_CART_ADDR_REG] + length + 1) & !1;
|
||||
|
||||
device.pi.regs[PI_STATUS_REG as usize] |= PI_STATUS_DMA_BUSY
|
||||
device.pi.regs[PI_STATUS_REG] |= PI_STATUS_DMA_BUSY
|
||||
}
|
||||
|
||||
fn dma_write(device: &mut device::Device) {
|
||||
let handler = get_handler(device.pi.regs[PI_CART_ADDR_REG as usize]);
|
||||
let handler = get_handler(device.pi.regs[PI_CART_ADDR_REG]);
|
||||
|
||||
let cart_addr = device.pi.regs[PI_CART_ADDR_REG as usize] & !1;
|
||||
let dram_addr = device.pi.regs[PI_DRAM_ADDR_REG as usize] & 0xFFFFFE;
|
||||
let mut length = (device.pi.regs[PI_WR_LEN_REG as usize] & 0xFFFFFF) + 1;
|
||||
let cart_addr = device.pi.regs[PI_CART_ADDR_REG] & !1;
|
||||
let dram_addr = device.pi.regs[PI_DRAM_ADDR_REG] & 0xFFFFFE;
|
||||
let mut length = (device.pi.regs[PI_WR_LEN_REG] & 0xFFFFFF) + 1;
|
||||
|
||||
/* PI seems to treat the first 128 bytes differently, see https://n64brew.dev/wiki/Peripheral_Interface#Unaligned_DMA_transfer */
|
||||
if length >= 0x7f && (length & 1) != 0 {
|
||||
@@ -92,17 +91,18 @@ 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::EVENT_TYPE_PI, cycles);
|
||||
|
||||
/* Update PI_DRAM_ADDR_REG and PI_CART_ADDR_REG */
|
||||
device.pi.regs[PI_DRAM_ADDR_REG as usize] =
|
||||
(device.pi.regs[PI_DRAM_ADDR_REG as usize] + length + 7) & !7;
|
||||
device.pi.regs[PI_CART_ADDR_REG as usize] =
|
||||
(device.pi.regs[PI_CART_ADDR_REG as usize] + length + 1) & !1;
|
||||
device.pi.regs[PI_DRAM_ADDR_REG] = (device.pi.regs[PI_DRAM_ADDR_REG] + length + 7) & !7;
|
||||
device.pi.regs[PI_CART_ADDR_REG] = (device.pi.regs[PI_CART_ADDR_REG] + length + 1) & !1;
|
||||
|
||||
device.pi.regs[PI_STATUS_REG as usize] |= PI_STATUS_DMA_BUSY
|
||||
device.pi.regs[PI_STATUS_REG] |= PI_STATUS_DMA_BUSY
|
||||
}
|
||||
|
||||
fn get_handler(address: u32) -> PiHandler {
|
||||
@@ -134,7 +134,7 @@ fn get_handler(address: u32) -> PiHandler {
|
||||
|
||||
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
match reg as usize {
|
||||
PI_RD_LEN_REG => {
|
||||
device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value, mask);
|
||||
device::pi::dma_read(device)
|
||||
@@ -149,7 +149,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
device::mi::clear_rcp_interrupt(device, device::mi::MI_INTR_PI);
|
||||
}
|
||||
if value & mask & PI_STATUS_RESET != 0 {
|
||||
device.pi.regs[PI_STATUS_REG as usize] = 0
|
||||
device.pi.regs[PI_STATUS_REG] = 0
|
||||
}
|
||||
}
|
||||
PI_BSD_DOM1_LAT_REG | PI_BSD_DOM1_PWD_REG | PI_BSD_DOM2_LAT_REG | PI_BSD_DOM2_PWD_REG => {
|
||||
@@ -165,7 +165,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
}
|
||||
}
|
||||
|
||||
fn randomize_interrupt_time(rng: &mut rand_chacha::ChaCha8Rng) -> u64 {
|
||||
fn randomize_interrupt_time(rng: &mut rand::rngs::Xoshiro256PlusPlus) -> u64 {
|
||||
rng.next_u64() % 0x10
|
||||
}
|
||||
|
||||
@@ -175,15 +175,15 @@ pub fn calculate_cycles(device: &mut device::Device, domain: i32, length: u32) -
|
||||
let page_size_base: f64 = 2.0;
|
||||
|
||||
if domain == 1 {
|
||||
latency = (device.pi.regs[PI_BSD_DOM1_LAT_REG as usize] + 1) as f64;
|
||||
pulse_width = (device.pi.regs[PI_BSD_DOM1_PWD_REG as usize] + 1) as f64;
|
||||
release = (device.pi.regs[PI_BSD_DOM1_RLS_REG as usize] + 1) as f64;
|
||||
page_size = page_size_base.powf((device.pi.regs[PI_BSD_DOM1_PGS_REG as usize] + 2) as f64);
|
||||
latency = (device.pi.regs[PI_BSD_DOM1_LAT_REG] + 1) as f64;
|
||||
pulse_width = (device.pi.regs[PI_BSD_DOM1_PWD_REG] + 1) as f64;
|
||||
release = (device.pi.regs[PI_BSD_DOM1_RLS_REG] + 1) as f64;
|
||||
page_size = page_size_base.powf((device.pi.regs[PI_BSD_DOM1_PGS_REG] + 2) as f64);
|
||||
} else if domain == 2 {
|
||||
latency = (device.pi.regs[PI_BSD_DOM2_LAT_REG as usize] + 1) as f64;
|
||||
pulse_width = (device.pi.regs[PI_BSD_DOM2_PWD_REG as usize] + 1) as f64;
|
||||
release = (device.pi.regs[PI_BSD_DOM2_RLS_REG as usize] + 1) as f64;
|
||||
page_size = page_size_base.powf((device.pi.regs[PI_BSD_DOM2_PGS_REG as usize] + 2) as f64);
|
||||
latency = (device.pi.regs[PI_BSD_DOM2_LAT_REG] + 1) as f64;
|
||||
pulse_width = (device.pi.regs[PI_BSD_DOM2_PWD_REG] + 1) as f64;
|
||||
release = (device.pi.regs[PI_BSD_DOM2_RLS_REG] + 1) as f64;
|
||||
page_size = page_size_base.powf((device.pi.regs[PI_BSD_DOM2_PGS_REG] + 2) as f64);
|
||||
} else {
|
||||
panic!("unknown pi dma")
|
||||
}
|
||||
@@ -197,8 +197,8 @@ pub fn calculate_cycles(device: &mut device::Device, domain: i32, length: u32) -
|
||||
}
|
||||
|
||||
pub fn dma_event(device: &mut device::Device) {
|
||||
device.pi.regs[PI_STATUS_REG as usize] &= !(PI_STATUS_DMA_BUSY | PI_STATUS_IO_BUSY);
|
||||
device.pi.regs[PI_STATUS_REG as usize] |= PI_STATUS_INTERRUPT;
|
||||
device.pi.regs[PI_STATUS_REG] &= !(PI_STATUS_DMA_BUSY | PI_STATUS_IO_BUSY);
|
||||
device.pi.regs[PI_STATUS_REG] |= PI_STATUS_INTERRUPT;
|
||||
|
||||
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_PI)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
mod rom;
|
||||
use crate::device;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Pif {
|
||||
#[serde(with = "serde_big_array::BigArray")]
|
||||
pub rom: [u8; 1984],
|
||||
@@ -68,7 +68,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
|
||||
|
||||
device.si.dma_dir = device::si::DmaDir::Write;
|
||||
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.regs[device::si::SI_STATUS_REG] |=
|
||||
device::si::SI_STATUS_DMA_BUSY | device::si::SI_STATUS_IO_BUSY
|
||||
}
|
||||
|
||||
@@ -218,11 +218,11 @@ pub fn process_ram(device: &mut device::Device) {
|
||||
|
||||
pub fn connect_pif_channels(device: &mut device::Device) {
|
||||
for i in 0..4 {
|
||||
if device.netplay.is_none() {
|
||||
if device.ui.config.input.controller_enabled[i] {
|
||||
if let Some(netplay) = &device.netplay {
|
||||
if netplay.connected[i] {
|
||||
device.pif.channels[i].process = Some(device::controller::process);
|
||||
}
|
||||
} else if device.netplay.as_ref().unwrap().player_data[i].reg_id != 0 {
|
||||
} else if device.ui.config.input.controller_enabled[i] {
|
||||
device.pif.channels[i].process = Some(device::controller::process);
|
||||
}
|
||||
}
|
||||
@@ -249,14 +249,23 @@ fn get_default_handler(device: &device::Device) -> device::controller::PakHandle
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
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 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 {
|
||||
@@ -268,16 +277,16 @@ pub fn init(device: &mut device::Device) {
|
||||
connect_pif_channels(device);
|
||||
|
||||
for i in 0..4 {
|
||||
if device.netplay.is_none() {
|
||||
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);
|
||||
}
|
||||
if let Some(netplay) = &device.netplay {
|
||||
if netplay.connected[i] {
|
||||
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(default_handler);
|
||||
}
|
||||
}
|
||||
if device.ui.config.input.emulate_vru && device.netplay.is_none() {
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
use crate::{device, savestates, ui};
|
||||
use crate::{device, ui};
|
||||
|
||||
pub const DPC_START_REG: u32 = 0;
|
||||
pub const DPC_END_REG: u32 = 1;
|
||||
pub const DPC_CURRENT_REG: u32 = 2;
|
||||
pub const DPC_STATUS_REG: u32 = 3;
|
||||
const DPC_CLOCK_REG: u32 = 4;
|
||||
const DPC_BUFBUSY_REG: u32 = 5;
|
||||
const DPC_PIPEBUSY_REG: u32 = 6;
|
||||
const DPC_TMEM_REG: u32 = 7;
|
||||
pub const DPC_REGS_COUNT: u32 = 8;
|
||||
pub const DPC_START_REG: usize = 0;
|
||||
pub const DPC_END_REG: usize = 1;
|
||||
pub const DPC_CURRENT_REG: usize = 2;
|
||||
pub const DPC_STATUS_REG: usize = 3;
|
||||
const DPC_CLOCK_REG: usize = 4;
|
||||
const DPC_BUFBUSY_REG: usize = 5;
|
||||
const DPC_PIPEBUSY_REG: usize = 6;
|
||||
const DPC_TMEM_REG: usize = 7;
|
||||
pub const DPC_REGS_COUNT: usize = 8;
|
||||
|
||||
//const DPS_TBIST_REG: u32 = 0;
|
||||
//const DPS_TEST_MODE_REG: u32 = 1;
|
||||
//const DPS_BUFTEST_ADDR_REG: u32 = 2;
|
||||
//const DPS_BUFTEST_DATA_REG: u32 = 3;
|
||||
pub const DPS_REGS_COUNT: u32 = 4;
|
||||
//const DPS_TBIST_REG: usize = 0;
|
||||
//const DPS_TEST_MODE_REG: usize = 1;
|
||||
//const DPS_BUFTEST_ADDR_REG: usize = 2;
|
||||
//const DPS_BUFTEST_DATA_REG: usize = 3;
|
||||
pub const DPS_REGS_COUNT: usize = 4;
|
||||
|
||||
/* DPC status - read */
|
||||
const DPC_STATUS_XBUS_DMEM_DMA: u32 = 1 << 0;
|
||||
@@ -40,10 +40,10 @@ 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)]
|
||||
#[derive(Clone, 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 regs_dpc: [u32; DPC_REGS_COUNT],
|
||||
pub regs_dps: [u32; DPS_REGS_COUNT],
|
||||
pub wait_frozen: bool,
|
||||
pub last_status_value: u32,
|
||||
}
|
||||
@@ -57,7 +57,7 @@ pub fn read_regs_dpc(
|
||||
device::cop0::add_cycles(device, 20);
|
||||
}
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
match reg as usize {
|
||||
DPC_STATUS_REG => {
|
||||
let value =
|
||||
device.rdp.regs_dpc[reg as usize] & (DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY);
|
||||
@@ -80,18 +80,18 @@ pub fn read_regs_dpc(
|
||||
|
||||
pub fn write_regs_dpc(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
match reg as usize {
|
||||
DPC_CURRENT_REG | DPC_CLOCK_REG | DPC_BUFBUSY_REG | DPC_PIPEBUSY_REG | DPC_TMEM_REG => {}
|
||||
DPC_STATUS_REG => update_dpc_status(device, value & mask),
|
||||
DPC_START_REG => {
|
||||
if (device.rdp.regs_dpc[DPC_STATUS_REG as usize] & DPC_STATUS_START_VALID) == 0 {
|
||||
if (device.rdp.regs_dpc[DPC_STATUS_REG] & DPC_STATUS_START_VALID) == 0 {
|
||||
device::memory::masked_write_32(
|
||||
&mut device.rdp.regs_dpc[reg as usize],
|
||||
value & 0xFFFFF8,
|
||||
mask,
|
||||
)
|
||||
}
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |= DPC_STATUS_START_VALID
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG] |= DPC_STATUS_START_VALID
|
||||
}
|
||||
DPC_END_REG => {
|
||||
device::memory::masked_write_32(
|
||||
@@ -99,12 +99,11 @@ pub fn write_regs_dpc(device: &mut device::Device, address: u64, value: u32, mas
|
||||
value & 0xFFFFF8,
|
||||
mask,
|
||||
);
|
||||
if (device.rdp.regs_dpc[DPC_STATUS_REG as usize] & DPC_STATUS_START_VALID) != 0 {
|
||||
device.rdp.regs_dpc[DPC_CURRENT_REG as usize] =
|
||||
device.rdp.regs_dpc[DPC_START_REG as usize];
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &= !DPC_STATUS_START_VALID
|
||||
if (device.rdp.regs_dpc[DPC_STATUS_REG] & DPC_STATUS_START_VALID) != 0 {
|
||||
device.rdp.regs_dpc[DPC_CURRENT_REG] = device.rdp.regs_dpc[DPC_START_REG];
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG] &= !DPC_STATUS_START_VALID
|
||||
}
|
||||
if device.rdp.regs_dpc[DPC_STATUS_REG as usize] & DPC_STATUS_FREEZE == 0 {
|
||||
if device.rdp.regs_dpc[DPC_STATUS_REG] & DPC_STATUS_FREEZE == 0 {
|
||||
run_rdp(device)
|
||||
} else {
|
||||
device.rdp.wait_frozen = true;
|
||||
@@ -116,20 +115,11 @@ 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] |=
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG] |=
|
||||
DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY;
|
||||
device.rdp.regs_dpc[DPC_PIPEBUSY_REG as usize] = 0xFFFFFF;
|
||||
device.rdp.regs_dpc[DPC_PIPEBUSY_REG] = 0xFFFFFF;
|
||||
|
||||
if timer != 0 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -154,59 +144,59 @@ pub fn write_regs_dps(device: &mut device::Device, address: u64, value: u32, mas
|
||||
fn update_dpc_status(device: &mut device::Device, w: u32) {
|
||||
/* clear / set xbus_dmem_dma */
|
||||
if w & DPC_CLR_XBUS_DMEM_DMA != 0 {
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &= !DPC_STATUS_XBUS_DMEM_DMA
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG] &= !DPC_STATUS_XBUS_DMEM_DMA
|
||||
}
|
||||
if w & DPC_SET_XBUS_DMEM_DMA != 0 {
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |= DPC_STATUS_XBUS_DMEM_DMA
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG] |= DPC_STATUS_XBUS_DMEM_DMA
|
||||
}
|
||||
|
||||
/* clear / set freeze */
|
||||
if w & DPC_CLR_FREEZE != 0 {
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &= !DPC_STATUS_FREEZE;
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG] &= !DPC_STATUS_FREEZE;
|
||||
if device.rdp.wait_frozen {
|
||||
run_rdp(device);
|
||||
device.rdp.wait_frozen = false;
|
||||
}
|
||||
}
|
||||
if w & DPC_SET_FREEZE != 0 {
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |= DPC_STATUS_FREEZE;
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG] |= DPC_STATUS_FREEZE;
|
||||
}
|
||||
|
||||
/* clear / set flush */
|
||||
if w & DPC_CLR_FLUSH != 0 {
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &= !DPC_STATUS_FLUSH
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG] &= !DPC_STATUS_FLUSH
|
||||
}
|
||||
if w & DPC_SET_FLUSH != 0 {
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |= DPC_STATUS_FLUSH
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG] |= DPC_STATUS_FLUSH
|
||||
}
|
||||
|
||||
if w & DPC_CLR_TMEM_CTR != 0 {
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &= !DPC_STATUS_TMEM_BUSY;
|
||||
device.rdp.regs_dpc[DPC_TMEM_REG as usize] = 0
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG] &= !DPC_STATUS_TMEM_BUSY;
|
||||
device.rdp.regs_dpc[DPC_TMEM_REG] = 0
|
||||
}
|
||||
if w & DPC_CLR_PIPE_CTR != 0 {
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &= !DPC_STATUS_PIPE_BUSY;
|
||||
device.rdp.regs_dpc[DPC_PIPEBUSY_REG as usize] = 0
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG] &= !DPC_STATUS_PIPE_BUSY;
|
||||
device.rdp.regs_dpc[DPC_PIPEBUSY_REG] = 0
|
||||
}
|
||||
if w & DPC_CLR_CMD_CTR != 0 {
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &= !DPC_STATUS_CMD_BUSY;
|
||||
device.rdp.regs_dpc[DPC_BUFBUSY_REG as usize] = 0
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG] &= !DPC_STATUS_CMD_BUSY;
|
||||
device.rdp.regs_dpc[DPC_BUFBUSY_REG] = 0
|
||||
}
|
||||
|
||||
/* clear clock counter */
|
||||
if w & DPC_CLR_CLOCK_CTR != 0 {
|
||||
device.rdp.regs_dpc[DPC_CLOCK_REG as usize] = 0
|
||||
device.rdp.regs_dpc[DPC_CLOCK_REG] = 0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |=
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG] |=
|
||||
DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CBUF_READY;
|
||||
device.rdp.regs_dpc[DPC_PIPEBUSY_REG as usize] = 0xFFFFFF;
|
||||
device.rdp.regs_dpc[DPC_PIPEBUSY_REG] = 0xFFFFFF;
|
||||
}
|
||||
|
||||
pub fn rdp_interrupt_event(device: &mut device::Device) {
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &=
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG] &=
|
||||
!(DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY);
|
||||
|
||||
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_DP)
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
use crate::device;
|
||||
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;
|
||||
//const RDRAM_DELAY_REG: u32 = 2;
|
||||
const RDRAM_MODE_REG: u32 = 3;
|
||||
//const RDRAM_REF_INTERVAL_REG: u32 = 4;
|
||||
//const RDRAM_REF_ROW_REG: u32 = 5;
|
||||
//const RDRAM_RAS_INTERVAL_REG: u32 = 6;
|
||||
//const RDRAM_MIN_INTERVAL_REG: u32 = 7;
|
||||
//const RDRAM_ADDR_SELECT_REG: u32 = 8;
|
||||
//const RDRAM_DEVICE_MANUF_REG: u32 = 9;
|
||||
pub const RDRAM_REGS_COUNT: u32 = 10;
|
||||
//const RDRAM_CONFIG_REG: usize = 0;
|
||||
//const RDRAM_DEVICE_ID_REG: usize = 1;
|
||||
//const RDRAM_DELAY_REG: usize = 2;
|
||||
const RDRAM_MODE_REG: usize = 3;
|
||||
//const RDRAM_REF_INTERVAL_REG: usize = 4;
|
||||
//const RDRAM_REF_ROW_REG: usize = 5;
|
||||
//const RDRAM_RAS_INTERVAL_REG: usize = 6;
|
||||
//const RDRAM_MIN_INTERVAL_REG: usize = 7;
|
||||
//const RDRAM_ADDR_SELECT_REG: usize = 8;
|
||||
//const RDRAM_DEVICE_MANUF_REG: usize = 9;
|
||||
pub const RDRAM_REGS_COUNT: usize = 10;
|
||||
|
||||
pub const RDRAM_MASK: usize = 0xFFFFFF;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Rdram {
|
||||
pub mem: Vec<u8>,
|
||||
pub size: u32,
|
||||
pub regs: [[u32; RDRAM_REGS_COUNT as usize]; 4],
|
||||
pub regs: [[u32; RDRAM_REGS_COUNT]; 4],
|
||||
}
|
||||
|
||||
pub fn read_mem_fast(
|
||||
@@ -50,6 +52,8 @@ pub fn read_mem(
|
||||
);
|
||||
let masked_address = address as usize & RDRAM_MASK;
|
||||
|
||||
ui::video::check_framebuffers(masked_address as u32, 4);
|
||||
|
||||
u32::from_ne_bytes(
|
||||
device
|
||||
.rdram
|
||||
@@ -62,6 +66,8 @@ pub fn read_mem(
|
||||
}
|
||||
|
||||
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
ui::video::check_framebuffers(address as u32, 4);
|
||||
|
||||
let mut data = u32::from_ne_bytes(
|
||||
device
|
||||
.rdram
|
||||
@@ -80,6 +86,38 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
|
||||
.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] & 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
|
||||
);
|
||||
}
|
||||
|
||||
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] &= !device::mi::MI_INIT_MODE;
|
||||
for i in 0..(0x3F00000 >> 16) {
|
||||
device.memory.memory_map_write[i] = device::rdram::write_mem;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_regs(
|
||||
device: &mut device::Device,
|
||||
address: u64,
|
||||
@@ -88,7 +126,7 @@ pub fn read_regs(
|
||||
device::cop0::add_cycles(device, 20);
|
||||
let chip_id = (address >> 13) & 3;
|
||||
let reg = (address & 0x3FF) >> 2;
|
||||
match reg as u32 {
|
||||
match reg as usize {
|
||||
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],
|
||||
@@ -113,6 +151,8 @@ 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_rdram(device.rdram.mem.as_ptr(), device.rdram.size as usize);
|
||||
|
||||
// hack, skip RDRAM initialization
|
||||
device
|
||||
.rdram
|
||||
@@ -128,8 +168,8 @@ pub fn init(device: &mut device::Device) {
|
||||
.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_MODE_REG] = 0x0e;
|
||||
device.ri.regs[device::ri::RI_CONFIG_REG] = 0x40;
|
||||
}
|
||||
|
||||
pub fn rdram_calculate_cycles(length: u64) -> u64 {
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
use crate::device;
|
||||
|
||||
pub const RI_MODE_REG: u32 = 0;
|
||||
pub const RI_CONFIG_REG: u32 = 1;
|
||||
//const RI_CURRENT_LOAD_REG: u32 = 2;
|
||||
pub const RI_SELECT_REG: u32 = 3;
|
||||
pub const RI_REFRESH_REG: u32 = 4;
|
||||
//const RI_LATENCY_REG: u32 = 5;
|
||||
//const RI_ERROR_REG: u32 = 6;
|
||||
//const RI_WERROR_REG: u32 = 7;
|
||||
pub const RI_REGS_COUNT: u32 = 8;
|
||||
pub const RI_MODE_REG: usize = 0;
|
||||
pub const RI_CONFIG_REG: usize = 1;
|
||||
//const RI_CURRENT_LOAD_REG: usize = 2;
|
||||
pub const RI_SELECT_REG: usize = 3;
|
||||
pub const RI_REFRESH_REG: usize = 4;
|
||||
//const RI_LATENCY_REG: usize = 5;
|
||||
//const RI_ERROR_REG: usize = 6;
|
||||
//const RI_WERROR_REG: usize = 7;
|
||||
pub const RI_REGS_COUNT: usize = 8;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Ri {
|
||||
pub regs: [u32; RI_REGS_COUNT as usize],
|
||||
pub regs: [u32; RI_REGS_COUNT],
|
||||
pub ram_init: bool,
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ pub fn read_regs(
|
||||
) -> u32 {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
match reg as usize {
|
||||
RI_SELECT_REG => {
|
||||
if !device.ri.ram_init {
|
||||
device::cop0::add_cycles(device, device.cpu.clock_rate / 2); // hack, simulate RDRAM initialization
|
||||
@@ -38,7 +38,7 @@ pub fn read_regs(
|
||||
|
||||
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
if reg as u32 == RI_SELECT_REG {
|
||||
if reg as usize == RI_SELECT_REG {
|
||||
device.ri.ram_init = false;
|
||||
}
|
||||
device::memory::masked_write_32(&mut device.ri.regs[reg as usize], value, mask);
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::{device, savestates};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use std::arch::x86_64::*;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct BranchState {
|
||||
pub state: device::cpu::State,
|
||||
pub pc: u32,
|
||||
@@ -25,7 +25,7 @@ pub enum InstructionType {
|
||||
Vu,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Cpu {
|
||||
#[serde(with = "serde_big_array::BigArray")]
|
||||
pub instructions: [Instructions; 0x1000 / 4],
|
||||
@@ -132,48 +132,47 @@ pub fn run(device: &mut device::Device) -> u64 {
|
||||
device.rsp.cpu.gpr[0] = 0; // gpr 0 is read only
|
||||
|
||||
let instruction = device.rsp.cpu.instructions
|
||||
[(device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] / 4) as usize];
|
||||
[(device.rsp.regs2[device::rsp_interface::SP_PC_REG] / 4) as usize];
|
||||
(instruction.func)(device, instruction.opcode);
|
||||
|
||||
match device.rsp.cpu.branch_state.state {
|
||||
device::cpu::State::Step => {
|
||||
device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] += 4;
|
||||
device.rsp.regs2[device::rsp_interface::SP_PC_REG] += 4;
|
||||
if device.rsp.cpu.broken {
|
||||
break;
|
||||
}
|
||||
}
|
||||
device::cpu::State::Take => {
|
||||
device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] += 4;
|
||||
device.rsp.regs2[device::rsp_interface::SP_PC_REG] += 4;
|
||||
device.rsp.cpu.branch_state.state = device::cpu::State::DelaySlotTaken
|
||||
}
|
||||
device::cpu::State::NotTaken => {
|
||||
device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] += 4;
|
||||
device.rsp.regs2[device::rsp_interface::SP_PC_REG] += 4;
|
||||
device.rsp.cpu.branch_state.state = device::cpu::State::DelaySlotNotTaken
|
||||
}
|
||||
device::cpu::State::DelaySlotTaken => {
|
||||
device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] =
|
||||
device.rsp.cpu.branch_state.pc;
|
||||
device.rsp.regs2[device::rsp_interface::SP_PC_REG] = device.rsp.cpu.branch_state.pc;
|
||||
device.rsp.cpu.branch_state.state = device::cpu::State::Step;
|
||||
if device.rsp.cpu.broken {
|
||||
break;
|
||||
}
|
||||
}
|
||||
device::cpu::State::DelaySlotNotTaken => {
|
||||
device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] += 4;
|
||||
device.rsp.regs2[device::rsp_interface::SP_PC_REG] += 4;
|
||||
device.rsp.cpu.branch_state.state = device::cpu::State::Step;
|
||||
if device.rsp.cpu.broken {
|
||||
break;
|
||||
}
|
||||
}
|
||||
device::cpu::State::Discard => {
|
||||
device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] += 8;
|
||||
device.rsp.regs2[device::rsp_interface::SP_PC_REG] += 8;
|
||||
device.rsp.cpu.branch_state.state = device::cpu::State::Step
|
||||
}
|
||||
device::cpu::State::Exception => {
|
||||
device.rsp.cpu.branch_state.state = device::cpu::State::Step
|
||||
}
|
||||
}
|
||||
device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] &= 0xFFC;
|
||||
device.rsp.regs2[device::rsp_interface::SP_PC_REG] &= 0xFFC;
|
||||
|
||||
if device.rsp.cpu.instruction_type == device.rsp.cpu.last_instruction_type {
|
||||
device.rsp.cpu.cycle_counter += 1;
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
|
||||
const SP_MEM_ADDR_REG: u32 = 0;
|
||||
const SP_DRAM_ADDR_REG: u32 = 1;
|
||||
const SP_RD_LEN_REG: u32 = 2;
|
||||
const SP_WR_LEN_REG: u32 = 3;
|
||||
pub const SP_STATUS_REG: u32 = 4;
|
||||
const SP_DMA_FULL_REG: u32 = 5;
|
||||
const SP_DMA_BUSY_REG: u32 = 6;
|
||||
const SP_SEMAPHORE_REG: u32 = 7;
|
||||
pub const SP_REGS_COUNT: u32 = 8;
|
||||
const SP_MEM_ADDR_REG: usize = 0;
|
||||
const SP_DRAM_ADDR_REG: usize = 1;
|
||||
const SP_RD_LEN_REG: usize = 2;
|
||||
const SP_WR_LEN_REG: usize = 3;
|
||||
pub const SP_STATUS_REG: usize = 4;
|
||||
const SP_DMA_FULL_REG: usize = 5;
|
||||
const SP_DMA_BUSY_REG: usize = 6;
|
||||
const SP_SEMAPHORE_REG: usize = 7;
|
||||
pub const SP_REGS_COUNT: usize = 8;
|
||||
|
||||
pub const SP_PC_REG: u32 = 0;
|
||||
//const SP_IBIST_REG: u32 = 1;
|
||||
pub const SP_REGS2_COUNT: u32 = 2;
|
||||
pub const SP_PC_REG: usize = 0;
|
||||
//const SP_IBIST_REG: usize = 1;
|
||||
pub const SP_REGS2_COUNT: usize = 2;
|
||||
|
||||
/* SP_STATUS - read */
|
||||
pub const SP_STATUS_HALT: u32 = 1 << 0;
|
||||
@@ -75,11 +76,11 @@ pub struct RspDma {
|
||||
pub dramaddr: u32,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, 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],
|
||||
pub regs: [u32; SP_REGS_COUNT],
|
||||
pub regs2: [u32; SP_REGS2_COUNT],
|
||||
#[serde(with = "serde_big_array::BigArray")]
|
||||
pub mem: [u8; 0x2000],
|
||||
pub fifo: [RspDma; 2],
|
||||
@@ -146,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 {
|
||||
@@ -202,10 +204,10 @@ fn do_dma(device: &mut device::Device, dma: RspDma) {
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
device.rsp.regs[SP_MEM_ADDR_REG as usize] = (mem_addr & 0xfff) + (dma.memaddr & 0x1000);
|
||||
device.rsp.regs[SP_DRAM_ADDR_REG as usize] = dram_addr;
|
||||
device.rsp.regs[SP_RD_LEN_REG as usize] = 0xff8;
|
||||
device.rsp.regs[SP_WR_LEN_REG as usize] = 0xff8;
|
||||
device.rsp.regs[SP_MEM_ADDR_REG] = (mem_addr & 0xfff) + (dma.memaddr & 0x1000);
|
||||
device.rsp.regs[SP_DRAM_ADDR_REG] = dram_addr;
|
||||
device.rsp.regs[SP_RD_LEN_REG] = 0xff8;
|
||||
device.rsp.regs[SP_WR_LEN_REG] = 0xff8;
|
||||
|
||||
device::events::create_event(
|
||||
device,
|
||||
@@ -215,50 +217,52 @@ fn do_dma(device: &mut device::Device, dma: RspDma) {
|
||||
}
|
||||
|
||||
fn fifo_push(device: &mut device::Device, dir: DmaDir) {
|
||||
if device.rsp.regs[SP_DMA_FULL_REG as usize] != 0 {
|
||||
if device.rsp.regs[SP_DMA_FULL_REG] != 0 {
|
||||
panic!("RSP DMA already full")
|
||||
}
|
||||
|
||||
if device.rsp.regs[SP_DMA_BUSY_REG as usize] != 0 {
|
||||
device.rsp.cpu.sync_point = true;
|
||||
|
||||
if device.rsp.regs[SP_DMA_BUSY_REG] != 0 {
|
||||
device.rsp.fifo[1].dir = dir;
|
||||
if dir == DmaDir::Read {
|
||||
device.rsp.fifo[1].length = device.rsp.regs[SP_WR_LEN_REG as usize]
|
||||
device.rsp.fifo[1].length = device.rsp.regs[SP_WR_LEN_REG]
|
||||
} else {
|
||||
device.rsp.fifo[1].length = device.rsp.regs[SP_RD_LEN_REG as usize]
|
||||
device.rsp.fifo[1].length = device.rsp.regs[SP_RD_LEN_REG]
|
||||
}
|
||||
device.rsp.fifo[1].memaddr = device.rsp.regs[SP_MEM_ADDR_REG as usize];
|
||||
device.rsp.fifo[1].dramaddr = device.rsp.regs[SP_DRAM_ADDR_REG as usize];
|
||||
device.rsp.regs[SP_DMA_FULL_REG as usize] = 1;
|
||||
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_DMA_FULL
|
||||
device.rsp.fifo[1].memaddr = device.rsp.regs[SP_MEM_ADDR_REG];
|
||||
device.rsp.fifo[1].dramaddr = device.rsp.regs[SP_DRAM_ADDR_REG];
|
||||
device.rsp.regs[SP_DMA_FULL_REG] = 1;
|
||||
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_DMA_FULL
|
||||
} else {
|
||||
device.rsp.fifo[0].dir = dir;
|
||||
if dir == DmaDir::Read {
|
||||
device.rsp.fifo[0].length = device.rsp.regs[SP_WR_LEN_REG as usize]
|
||||
device.rsp.fifo[0].length = device.rsp.regs[SP_WR_LEN_REG]
|
||||
} else {
|
||||
device.rsp.fifo[0].length = device.rsp.regs[SP_RD_LEN_REG as usize]
|
||||
device.rsp.fifo[0].length = device.rsp.regs[SP_RD_LEN_REG]
|
||||
}
|
||||
device.rsp.fifo[0].memaddr = device.rsp.regs[SP_MEM_ADDR_REG as usize];
|
||||
device.rsp.fifo[0].dramaddr = device.rsp.regs[SP_DRAM_ADDR_REG as usize];
|
||||
device.rsp.regs[SP_DMA_BUSY_REG as usize] = 1;
|
||||
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_DMA_BUSY;
|
||||
device.rsp.fifo[0].memaddr = device.rsp.regs[SP_MEM_ADDR_REG];
|
||||
device.rsp.fifo[0].dramaddr = device.rsp.regs[SP_DRAM_ADDR_REG];
|
||||
device.rsp.regs[SP_DMA_BUSY_REG] = 1;
|
||||
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_DMA_BUSY;
|
||||
|
||||
do_dma(device, device.rsp.fifo[0])
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fifo_pop(device: &mut device::Device) {
|
||||
if device.rsp.regs[SP_DMA_FULL_REG as usize] != 0 {
|
||||
if device.rsp.regs[SP_DMA_FULL_REG] != 0 {
|
||||
device.rsp.fifo[0].dir = device.rsp.fifo[1].dir;
|
||||
device.rsp.fifo[0].length = device.rsp.fifo[1].length;
|
||||
device.rsp.fifo[0].memaddr = device.rsp.fifo[1].memaddr;
|
||||
device.rsp.fifo[0].dramaddr = device.rsp.fifo[1].dramaddr;
|
||||
device.rsp.regs[SP_DMA_FULL_REG as usize] = 0;
|
||||
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_DMA_FULL;
|
||||
device.rsp.regs[SP_DMA_FULL_REG] = 0;
|
||||
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_DMA_FULL;
|
||||
|
||||
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_DMA_BUSY_REG] = 0;
|
||||
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_DMA_BUSY;
|
||||
if device.rsp.run_after_dma {
|
||||
device.rsp.run_after_dma = false;
|
||||
do_task(device);
|
||||
@@ -275,22 +279,21 @@ pub fn read_regs(
|
||||
device::cop0::add_cycles(device, 20);
|
||||
}
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
SP_DMA_BUSY_REG | SP_DMA_FULL_REG => {
|
||||
if device.rsp.regs[reg as usize] != 0 {
|
||||
device.rsp.cpu.sync_point = true;
|
||||
}
|
||||
device.rsp.regs[reg as usize]
|
||||
}
|
||||
match reg as usize {
|
||||
SP_STATUS_REG => {
|
||||
let value = device.rsp.regs[reg as usize]
|
||||
& !(SP_STATUS_HALT | SP_STATUS_BROKE | SP_STATUS_INTR_BREAK);
|
||||
& (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;
|
||||
}
|
||||
if value & (SP_STATUS_DMA_BUSY | SP_STATUS_DMA_FULL) != 0 {
|
||||
device.rsp.cpu.sync_point = true;
|
||||
}
|
||||
|
||||
device.rsp.last_status_value = value;
|
||||
device.rsp.regs[reg as usize]
|
||||
}
|
||||
@@ -308,7 +311,7 @@ pub fn read_regs(
|
||||
|
||||
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
match reg as usize {
|
||||
SP_STATUS_REG => update_sp_status(device, value),
|
||||
SP_RD_LEN_REG => {
|
||||
device::memory::masked_write_32(&mut device.rsp.regs[reg as usize], value, mask);
|
||||
@@ -336,7 +339,7 @@ pub fn read_regs2(
|
||||
|
||||
pub fn write_regs2(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
match reg as usize {
|
||||
SP_PC_REG => {
|
||||
device::memory::masked_write_32(
|
||||
&mut device.rsp.regs2[reg as usize],
|
||||
@@ -349,20 +352,20 @@ pub fn write_regs2(device: &mut device::Device, address: u64, value: u32, mask:
|
||||
}
|
||||
|
||||
fn update_sp_status(device: &mut device::Device, w: u32) {
|
||||
let was_halted = device.rsp.regs[SP_STATUS_REG as usize] & SP_STATUS_HALT != 0;
|
||||
let was_halted = device.rsp.regs[SP_STATUS_REG] & SP_STATUS_HALT != 0;
|
||||
|
||||
/* clear / set halt */
|
||||
if w & SP_CLR_HALT != 0 && w & SP_SET_HALT == 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_HALT
|
||||
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_HALT
|
||||
}
|
||||
if w & SP_SET_HALT != 0 && w & SP_CLR_HALT == 0 {
|
||||
device::events::remove_event(device, device::events::EVENT_TYPE_SP);
|
||||
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_HALT
|
||||
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_HALT
|
||||
}
|
||||
|
||||
/* clear broke */
|
||||
if w & SP_CLR_BROKE != 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_BROKE
|
||||
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_BROKE
|
||||
}
|
||||
|
||||
/* clear SP interrupt */
|
||||
@@ -376,85 +379,85 @@ fn update_sp_status(device: &mut device::Device, w: u32) {
|
||||
|
||||
/* clear / set single step */
|
||||
if w & SP_CLR_SSTEP != 0 && w & SP_SET_SSTEP == 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_SSTEP
|
||||
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SSTEP
|
||||
}
|
||||
if w & SP_SET_SSTEP != 0 && w & SP_CLR_SSTEP == 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_SSTEP
|
||||
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SSTEP
|
||||
}
|
||||
|
||||
/* clear / set interrupt on break */
|
||||
if w & SP_CLR_INTR_BREAK != 0 && w & SP_SET_INTR_BREAK == 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_INTR_BREAK
|
||||
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_INTR_BREAK
|
||||
}
|
||||
if w & SP_SET_INTR_BREAK != 0 && w & SP_CLR_INTR_BREAK == 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_INTR_BREAK
|
||||
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_INTR_BREAK
|
||||
}
|
||||
|
||||
/* clear / set signal 0 */
|
||||
if w & SP_CLR_SIG0 != 0 && w & SP_SET_SIG0 == 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_SIG0
|
||||
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG0
|
||||
}
|
||||
if w & SP_SET_SIG0 != 0 && w & SP_CLR_SIG0 == 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_SIG0
|
||||
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG0
|
||||
}
|
||||
|
||||
/* clear / set signal 1 */
|
||||
if w & SP_CLR_SIG1 != 0 && w & SP_SET_SIG1 == 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_SIG1
|
||||
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG1
|
||||
}
|
||||
if w & SP_SET_SIG1 != 0 && w & SP_CLR_SIG1 == 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_SIG1
|
||||
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG1
|
||||
}
|
||||
|
||||
/* clear / set signal 2 */
|
||||
if w & SP_CLR_SIG2 != 0 && w & SP_SET_SIG2 == 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_SIG2
|
||||
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG2
|
||||
}
|
||||
if w & SP_SET_SIG2 != 0 && w & SP_CLR_SIG2 == 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_SIG2
|
||||
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG2
|
||||
}
|
||||
|
||||
/* clear / set signal 3 */
|
||||
if w & SP_CLR_SIG3 != 0 && w & SP_SET_SIG3 == 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_SIG3
|
||||
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG3
|
||||
}
|
||||
if w & SP_SET_SIG3 != 0 && w & SP_CLR_SIG3 == 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_SIG3
|
||||
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG3
|
||||
}
|
||||
|
||||
/* clear / set signal 4 */
|
||||
if w & SP_CLR_SIG4 != 0 && w & SP_SET_SIG4 == 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_SIG4
|
||||
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG4
|
||||
}
|
||||
if w & SP_SET_SIG4 != 0 && w & SP_CLR_SIG4 == 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_SIG4
|
||||
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG4
|
||||
}
|
||||
|
||||
/* clear / set signal 5 */
|
||||
if w & SP_CLR_SIG5 != 0 && w & SP_SET_SIG5 == 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_SIG5
|
||||
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG5
|
||||
}
|
||||
if w & SP_SET_SIG5 != 0 && w & SP_CLR_SIG5 == 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_SIG5
|
||||
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG5
|
||||
}
|
||||
|
||||
/* clear / set signal 6 */
|
||||
if w & SP_CLR_SIG6 != 0 && w & SP_SET_SIG6 == 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_SIG6
|
||||
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG6
|
||||
}
|
||||
if w & SP_SET_SIG6 != 0 && w & SP_CLR_SIG6 == 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_SIG6
|
||||
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG6
|
||||
}
|
||||
|
||||
/* clear / set signal 7 */
|
||||
if w & SP_CLR_SIG7 != 0 && w & SP_SET_SIG7 == 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_SIG7
|
||||
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG7
|
||||
}
|
||||
if w & SP_SET_SIG7 != 0 && w & SP_CLR_SIG7 == 0 {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_SIG7
|
||||
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG7
|
||||
}
|
||||
|
||||
if device.rsp.regs[SP_STATUS_REG as usize] & SP_STATUS_HALT == 0 && was_halted {
|
||||
if device.rsp.regs[SP_STATUS_REG] & SP_STATUS_HALT == 0 && was_halted {
|
||||
device.rsp.cpu.broken = false;
|
||||
device.rsp.cpu.halted = false;
|
||||
do_task(device);
|
||||
@@ -464,7 +467,8 @@ fn update_sp_status(device: &mut device::Device, w: u32) {
|
||||
fn do_task(device: &mut device::Device) {
|
||||
device.rsp.cpu.sync_point = false;
|
||||
device.rsp.last_status_value = 0;
|
||||
if device.rsp.regs[SP_DMA_BUSY_REG as usize] == 1 {
|
||||
device.rdp.last_status_value = 0;
|
||||
if device.rsp.regs[SP_DMA_BUSY_REG] == 1 {
|
||||
device.rsp.run_after_dma = true
|
||||
} else {
|
||||
let timer = device::rsp_cpu::run(device);
|
||||
@@ -475,21 +479,21 @@ fn do_task(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;
|
||||
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_HALT | SP_STATUS_BROKE;
|
||||
|
||||
if device.rsp.regs[SP_STATUS_REG as usize] & SP_STATUS_INTR_BREAK != 0 {
|
||||
if device.rsp.regs[SP_STATUS_REG] & SP_STATUS_INTR_BREAK != 0 {
|
||||
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_SP)
|
||||
}
|
||||
return;
|
||||
}
|
||||
if device.rsp.cpu.halted {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_HALT;
|
||||
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_HALT;
|
||||
return;
|
||||
}
|
||||
do_task(device)
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] = 1;
|
||||
device.rsp.regs[SP_STATUS_REG] = 1;
|
||||
device::rsp_cpu::init(device);
|
||||
}
|
||||
|
||||
@@ -183,23 +183,22 @@ pub fn j(device: &mut device::Device, opcode: u32) {
|
||||
return;
|
||||
}
|
||||
device.rsp.cpu.branch_state.state = device::cpu::State::Take;
|
||||
device.rsp.cpu.branch_state.pc =
|
||||
(device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] + 4) & 0xF0000000
|
||||
| ((opcode & 0x3FFFFFF) << 2)
|
||||
device.rsp.cpu.branch_state.pc = (device.rsp.regs2[device::rsp_interface::SP_PC_REG] + 4)
|
||||
& 0xF0000000
|
||||
| ((opcode & 0x3FFFFFF) << 2)
|
||||
}
|
||||
|
||||
pub fn jal(device: &mut device::Device, opcode: u32) {
|
||||
if device::rsp_cpu::in_delay_slot_taken(device) {
|
||||
device.rsp.cpu.gpr[31] = (device.rsp.cpu.branch_state.pc + 4) & 0xFFF
|
||||
} else {
|
||||
device.rsp.cpu.gpr[31] =
|
||||
(device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] + 8) & 0xFFF
|
||||
device.rsp.cpu.gpr[31] = (device.rsp.regs2[device::rsp_interface::SP_PC_REG] + 8) & 0xFFF
|
||||
}
|
||||
if !device::rsp_cpu::in_delay_slot_taken(device) {
|
||||
device.rsp.cpu.branch_state.state = device::cpu::State::Take;
|
||||
device.rsp.cpu.branch_state.pc =
|
||||
(device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] + 4) & 0xF0000000
|
||||
| ((opcode & 0x3FFFFFF) << 2)
|
||||
device.rsp.cpu.branch_state.pc = (device.rsp.regs2[device::rsp_interface::SP_PC_REG] + 4)
|
||||
& 0xF0000000
|
||||
| ((opcode & 0x3FFFFFF) << 2)
|
||||
} else if !device::rsp_cpu::in_delay_slot(device) {
|
||||
device.rsp.cpu.branch_state.state = device::cpu::State::NotTaken;
|
||||
}
|
||||
@@ -208,8 +207,7 @@ pub fn jal(device: &mut device::Device, opcode: u32) {
|
||||
pub fn beq(device: &mut device::Device, opcode: u32) {
|
||||
if device.rsp.cpu.gpr[rs(opcode) as usize] == device.rsp.cpu.gpr[rt(opcode) as usize] {
|
||||
device.rsp.cpu.branch_state.state = device::cpu::State::Take;
|
||||
device.rsp.cpu.branch_state.pc = device.rsp.regs2
|
||||
[device::rsp_interface::SP_PC_REG as usize]
|
||||
device.rsp.cpu.branch_state.pc = device.rsp.regs2[device::rsp_interface::SP_PC_REG]
|
||||
.wrapping_add(se16(imm(opcode) as i16) << 2)
|
||||
+ 4;
|
||||
} else {
|
||||
@@ -220,8 +218,7 @@ pub fn beq(device: &mut device::Device, opcode: u32) {
|
||||
pub fn bne(device: &mut device::Device, opcode: u32) {
|
||||
if device.rsp.cpu.gpr[rs(opcode) as usize] != device.rsp.cpu.gpr[rt(opcode) as usize] {
|
||||
device.rsp.cpu.branch_state.state = device::cpu::State::Take;
|
||||
device.rsp.cpu.branch_state.pc = device.rsp.regs2
|
||||
[device::rsp_interface::SP_PC_REG as usize]
|
||||
device.rsp.cpu.branch_state.pc = device.rsp.regs2[device::rsp_interface::SP_PC_REG]
|
||||
.wrapping_add(se16(imm(opcode) as i16) << 2)
|
||||
+ 4;
|
||||
} else {
|
||||
@@ -232,8 +229,7 @@ pub fn bne(device: &mut device::Device, opcode: u32) {
|
||||
pub fn blez(device: &mut device::Device, opcode: u32) {
|
||||
if device.rsp.cpu.gpr[rs(opcode) as usize] as i32 <= 0 {
|
||||
device.rsp.cpu.branch_state.state = device::cpu::State::Take;
|
||||
device.rsp.cpu.branch_state.pc = device.rsp.regs2
|
||||
[device::rsp_interface::SP_PC_REG as usize]
|
||||
device.rsp.cpu.branch_state.pc = device.rsp.regs2[device::rsp_interface::SP_PC_REG]
|
||||
.wrapping_add(se16(imm(opcode) as i16) << 2)
|
||||
+ 4;
|
||||
} else {
|
||||
@@ -244,8 +240,7 @@ pub fn blez(device: &mut device::Device, opcode: u32) {
|
||||
pub fn bgtz(device: &mut device::Device, opcode: u32) {
|
||||
if device.rsp.cpu.gpr[rs(opcode) as usize] as i32 > 0 {
|
||||
device.rsp.cpu.branch_state.state = device::cpu::State::Take;
|
||||
device.rsp.cpu.branch_state.pc = device.rsp.regs2
|
||||
[device::rsp_interface::SP_PC_REG as usize]
|
||||
device.rsp.cpu.branch_state.pc = device.rsp.regs2[device::rsp_interface::SP_PC_REG]
|
||||
.wrapping_add(se16(imm(opcode) as i16) << 2)
|
||||
+ 4;
|
||||
} else {
|
||||
@@ -304,25 +299,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];
|
||||
for (i, item) in w.iter_mut().enumerate() {
|
||||
*item = device.rsp.mem[(address as usize + i) & 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) {
|
||||
@@ -336,24 +350,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];
|
||||
for (i, item) in w.iter_mut().enumerate() {
|
||||
*item = device.rsp.mem[(address as usize + i) & 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) {
|
||||
@@ -367,18 +401,30 @@ 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);
|
||||
|
||||
for i in 0..4 {
|
||||
device.rsp.mem[(address as usize + i) & 0xFFF] =
|
||||
(device.rsp.cpu.gpr[rt(opcode) as usize] >> ((3 - i) * 8)) 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,7 +480,7 @@ pub fn jalr(device: &mut device::Device, opcode: u32) {
|
||||
device.rsp.cpu.gpr[rd(opcode) as usize] = (device.rsp.cpu.branch_state.pc + 4) & 0xFFF
|
||||
} else {
|
||||
device.rsp.cpu.gpr[rd(opcode) as usize] =
|
||||
(device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] + 8) & 0xFFF
|
||||
(device.rsp.regs2[device::rsp_interface::SP_PC_REG] + 8) & 0xFFF
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,8 +542,7 @@ pub fn sltu(device: &mut device::Device, opcode: u32) {
|
||||
pub fn bltz(device: &mut device::Device, opcode: u32) {
|
||||
if (device.rsp.cpu.gpr[rs(opcode) as usize] as i32) < 0 {
|
||||
device.rsp.cpu.branch_state.state = device::cpu::State::Take;
|
||||
device.rsp.cpu.branch_state.pc = device.rsp.regs2
|
||||
[device::rsp_interface::SP_PC_REG as usize]
|
||||
device.rsp.cpu.branch_state.pc = device.rsp.regs2[device::rsp_interface::SP_PC_REG]
|
||||
.wrapping_add(se16(imm(opcode) as i16) << 2)
|
||||
+ 4;
|
||||
} else {
|
||||
@@ -508,8 +553,7 @@ pub fn bltz(device: &mut device::Device, opcode: u32) {
|
||||
pub fn bgez(device: &mut device::Device, opcode: u32) {
|
||||
if device.rsp.cpu.gpr[rs(opcode) as usize] as i32 >= 0 {
|
||||
device.rsp.cpu.branch_state.state = device::cpu::State::Take;
|
||||
device.rsp.cpu.branch_state.pc = device.rsp.regs2
|
||||
[device::rsp_interface::SP_PC_REG as usize]
|
||||
device.rsp.cpu.branch_state.pc = device.rsp.regs2[device::rsp_interface::SP_PC_REG]
|
||||
.wrapping_add(se16(imm(opcode) as i16) << 2)
|
||||
+ 4;
|
||||
} else {
|
||||
@@ -520,34 +564,30 @@ pub fn bgez(device: &mut device::Device, opcode: u32) {
|
||||
pub fn bltzal(device: &mut device::Device, opcode: u32) {
|
||||
if (device.rsp.cpu.gpr[rs(opcode) as usize] as i32) < 0 {
|
||||
device.rsp.cpu.branch_state.state = device::cpu::State::Take;
|
||||
device.rsp.cpu.branch_state.pc = device.rsp.regs2
|
||||
[device::rsp_interface::SP_PC_REG as usize]
|
||||
device.rsp.cpu.branch_state.pc = device.rsp.regs2[device::rsp_interface::SP_PC_REG]
|
||||
.wrapping_add(se16(imm(opcode) as i16) << 2)
|
||||
+ 4;
|
||||
} else {
|
||||
device.rsp.cpu.branch_state.state = device::cpu::State::NotTaken;
|
||||
}
|
||||
device.rsp.cpu.gpr[31] =
|
||||
(device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] + 8) & 0xFFF
|
||||
device.rsp.cpu.gpr[31] = (device.rsp.regs2[device::rsp_interface::SP_PC_REG] + 8) & 0xFFF
|
||||
}
|
||||
|
||||
pub fn bgezal(device: &mut device::Device, opcode: u32) {
|
||||
if (device.rsp.cpu.gpr[rs(opcode) as usize] as i32) >= 0 {
|
||||
device.rsp.cpu.branch_state.state = device::cpu::State::Take;
|
||||
device.rsp.cpu.branch_state.pc = device.rsp.regs2
|
||||
[device::rsp_interface::SP_PC_REG as usize]
|
||||
device.rsp.cpu.branch_state.pc = device.rsp.regs2[device::rsp_interface::SP_PC_REG]
|
||||
.wrapping_add(se16(imm(opcode) as i16) << 2)
|
||||
+ 4;
|
||||
} else {
|
||||
device.rsp.cpu.branch_state.state = device::cpu::State::NotTaken;
|
||||
}
|
||||
device.rsp.cpu.gpr[31] =
|
||||
(device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] + 8) & 0xFFF
|
||||
device.rsp.cpu.gpr[31] = (device.rsp.regs2[device::rsp_interface::SP_PC_REG] + 8) & 0xFFF
|
||||
}
|
||||
|
||||
pub fn mfc0(device: &mut device::Device, opcode: u32) {
|
||||
device.rsp.cpu.cycle_counter += 2;
|
||||
if rd(opcode) < device::rsp_interface::SP_REGS_COUNT {
|
||||
if rd(opcode) < device::rsp_interface::SP_REGS_COUNT as u32 {
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] = device::rsp_interface::read_regs(
|
||||
device,
|
||||
(rd(opcode) << 2) as u64,
|
||||
@@ -556,14 +596,14 @@ pub fn mfc0(device: &mut device::Device, opcode: u32) {
|
||||
} else {
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] = device::rdp::read_regs_dpc(
|
||||
device,
|
||||
((rd(opcode) - device::rsp_interface::SP_REGS_COUNT) << 2) as u64,
|
||||
((rd(opcode) - device::rsp_interface::SP_REGS_COUNT as u32) << 2) as u64,
|
||||
device::memory::AccessSize::Word,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mtc0(device: &mut device::Device, opcode: u32) {
|
||||
if rd(opcode) < device::rsp_interface::SP_REGS_COUNT {
|
||||
if rd(opcode) < device::rsp_interface::SP_REGS_COUNT as u32 {
|
||||
device::rsp_interface::write_regs(
|
||||
device,
|
||||
(rd(opcode) << 2) as u64,
|
||||
@@ -573,15 +613,15 @@ pub fn mtc0(device: &mut device::Device, opcode: u32) {
|
||||
} else {
|
||||
device::rdp::write_regs_dpc(
|
||||
device,
|
||||
((rd(opcode) - device::rsp_interface::SP_REGS_COUNT) << 2) as u64,
|
||||
((rd(opcode) - device::rsp_interface::SP_REGS_COUNT as u32) << 2) as u64,
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize],
|
||||
0xFFFFFFFF,
|
||||
)
|
||||
}
|
||||
if rd(opcode) == device::rsp_interface::SP_STATUS_REG
|
||||
if rd(opcode) as usize == device::rsp_interface::SP_STATUS_REG
|
||||
&& device.rsp.cpu.gpr[rt(opcode) as usize] & device::rsp_interface::SP_SET_HALT != 0
|
||||
{
|
||||
device.rsp.regs[device::rsp_interface::SP_STATUS_REG as usize] &=
|
||||
device.rsp.regs[device::rsp_interface::SP_STATUS_REG] &=
|
||||
!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
|
||||
}
|
||||
@@ -737,10 +777,21 @@ pub fn llv(device: &mut device::Device, opcode: u32) {
|
||||
let mut element = velement(opcode);
|
||||
|
||||
if element.is_multiple_of(4) {
|
||||
let mut value = 0;
|
||||
for i in 0..4 {
|
||||
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u32) << (8 * (3 - i));
|
||||
}
|
||||
// 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,
|
||||
@@ -767,10 +818,21 @@ pub fn ldv(device: &mut device::Device, opcode: u32) {
|
||||
let mut element = velement(opcode);
|
||||
|
||||
if element.is_multiple_of(8) {
|
||||
let mut value = 0;
|
||||
for i in 0..8 {
|
||||
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u64) << (8 * (7 - i));
|
||||
}
|
||||
// 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,
|
||||
@@ -797,10 +859,21 @@ pub fn lqv(device: &mut device::Device, opcode: u32) {
|
||||
let mut element = velement(opcode);
|
||||
|
||||
if element == 0 && address.is_multiple_of(16) {
|
||||
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 {
|
||||
let end = std::cmp::min(16 + element - ((address & 15) as u8), 16);
|
||||
@@ -823,10 +896,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 {
|
||||
|
||||