Initial commit
This commit is contained in:
commit
6f829c5252
23 changed files with 2216 additions and 0 deletions
11
.editorconfig
Normal file
11
.editorconfig
Normal file
|
@ -0,0 +1,11 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
# specifically for YAML
|
||||
[{yml, yaml}]
|
||||
indent_style = space
|
18
.gitignore
vendored
Normal file
18
.gitignore
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
vgcore*
|
||||
perf*
|
||||
|
||||
trash/
|
||||
cores/
|
||||
roms/
|
||||
libretro.so
|
||||
core
|
||||
|
||||
# CLion
|
||||
.idea/
|
||||
cmake-build-debug/
|
||||
|
||||
# Rust
|
||||
target/
|
||||
|
||||
# misc test data
|
||||
testdata/
|
1
.rustfmt.toml
Normal file
1
.rustfmt.toml
Normal file
|
@ -0,0 +1 @@
|
|||
hard_tabs = true
|
684
Cargo.lock
generated
Normal file
684
Cargo.lock
generated
Normal file
|
@ -0,0 +1,684 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.69.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn",
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc"
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.155"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libretro-sys"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "207b060b02cecbcee6df3d0f5ed38691d5c4df1379dd1acd5c49c9b25d20b439"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libvnc-sys"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8399305e393bfa13b9974237b72a5190c41b0c65c4355df1ce7ac381bf75f775"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
"cmake",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
dependencies = [
|
||||
"overload",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||
|
||||
[[package]]
|
||||
name = "retro_frontend"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"libloading",
|
||||
"libretro-sys",
|
||||
"once_cell",
|
||||
"rgb565",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "retrovnc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"libvnc-sys",
|
||||
"retro_frontend",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rgb565"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d43e85498d0bb728f77a88b4313eaf4ed21673f3f8a05c36e835cf6c9c0d066"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
|
||||
dependencies = [
|
||||
"either",
|
||||
"home",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
5
Cargo.toml
Normal file
5
Cargo.toml
Normal file
|
@ -0,0 +1,5 @@
|
|||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"crates/*"
|
||||
]
|
7
LICENSE
Normal file
7
LICENSE
Normal file
|
@ -0,0 +1,7 @@
|
|||
Copyright 2024 Lily Tsuru (modeco80)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
README.md
Normal file
21
README.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
# retrovnc
|
||||
|
||||
a headless Libretro frontend that exports a VNC server.
|
||||
|
||||
# Dependencies
|
||||
|
||||
- A C++ toolchain
|
||||
- A Rust toolchain.
|
||||
- Maybe libvncserver (i'm not sure, it seems like the package can build it).
|
||||
|
||||
# Building
|
||||
|
||||
`$ cargo b --release`
|
||||
|
||||
# Usage
|
||||
|
||||
`$ retrovnc --core <CORE> --rom <ROM>`
|
||||
|
||||
For disc-based titles it is probably a good idea to pass the cuesheet file. I will implement stuff later to make this less annoying.
|
||||
|
||||
|
18
crates/retro_frontend/Cargo.toml
Normal file
18
crates/retro_frontend/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "retro_frontend"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
# Could be useful, and does build seperately, so...
|
||||
# publish = false
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.155"
|
||||
libloading = "0.8.3"
|
||||
libretro-sys = "0.1.1"
|
||||
once_cell = "1.19.0"
|
||||
rgb565 = "0.1.3"
|
||||
thiserror = "1.0.61"
|
||||
tracing = "0.1.40"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0.99"
|
12
crates/retro_frontend/build.rs
Normal file
12
crates/retro_frontend/build.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use cc;
|
||||
|
||||
fn main() {
|
||||
let mut build = cc::Build::new();
|
||||
|
||||
build
|
||||
.emit_rerun_if_env_changed(true)
|
||||
.cpp(true)
|
||||
.std("c++20")
|
||||
.file("src/libretro_log_helper.cpp")
|
||||
.compile("retro_log_helper");
|
||||
}
|
34
crates/retro_frontend/src/core.rs
Normal file
34
crates/retro_frontend/src/core.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
use std::path::Path;
|
||||
|
||||
use crate::frontend;
|
||||
use crate::result::Result;
|
||||
|
||||
/// A "RAII" wrapper over a core, useful for making cleanup a bit less ardous.
|
||||
pub struct Core();
|
||||
|
||||
impl Core {
|
||||
/// Same as [frontend::load_core], but returns a struct which will keep the core
|
||||
/// alive until it is dropped.
|
||||
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||
frontend::load_core(path.as_ref())?;
|
||||
Ok(Self {})
|
||||
}
|
||||
|
||||
/// Same as [frontend::load_game].
|
||||
pub fn load_game<P: AsRef<Path>>(&mut self, rom_path: P) -> Result<()> {
|
||||
frontend::load_game(rom_path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Same as [frontend::unload_game].
|
||||
pub fn unload_game(&mut self) -> Result<()> {
|
||||
frontend::unload_game()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Core {
|
||||
fn drop(&mut self) {
|
||||
let _ = frontend::unload_core();
|
||||
}
|
||||
}
|
98
crates/retro_frontend/src/frontend.rs
Normal file
98
crates/retro_frontend/src/frontend.rs
Normal file
|
@ -0,0 +1,98 @@
|
|||
//! The primary frontend API.
|
||||
//! This is a singleton API, not by choice, but due to Libretro's design.
|
||||
//!
|
||||
//! # Safety
|
||||
//! Don't even think about using this across multiple threads. If you want to run multiple frontends,
|
||||
//! it's easier to just host this crate in a runner process and fork off those runners.
|
||||
use crate::frontend_impl::FRONTEND_IMPL;
|
||||
use crate::joypad::Joypad;
|
||||
use crate::libretro_sys_new::*;
|
||||
use crate::result::Result;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Sets the callback used to update video.
|
||||
pub fn set_video_update_callback(cb: impl FnMut(&[u32]) + 'static) {
|
||||
unsafe {
|
||||
FRONTEND_IMPL.set_video_update_callback(cb);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the callback for video resize.
|
||||
pub fn set_video_resize_callback(cb: impl FnMut(u32, u32) + 'static) {
|
||||
unsafe {
|
||||
FRONTEND_IMPL.set_video_resize_callback(cb);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the callback for audio samples.
|
||||
pub fn set_audio_sample_callback(cb: impl FnMut(&[i16], usize) + 'static) {
|
||||
unsafe {
|
||||
FRONTEND_IMPL.set_audio_sample_callback(cb);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the callback for input polling.
|
||||
pub fn set_input_poll_callback(cb: impl FnMut() + 'static) {
|
||||
unsafe {
|
||||
FRONTEND_IMPL.set_input_poll_callback(cb);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the given port's input device. This takes a implementation of the [crate::joypad::Joypad]
|
||||
/// trait, which will provide the information needed for libretro to work and all that.
|
||||
pub fn set_input_port_device(port: u32, device: Rc<RefCell<dyn Joypad>>) {
|
||||
unsafe {
|
||||
FRONTEND_IMPL.set_input_port_device(port, device);
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads a core from the given path into the global frontend state.
|
||||
///
|
||||
/// ```rust
|
||||
/// use retro_frontend::frontend;
|
||||
/// frontend::load_core("./cores/gbasp.so");
|
||||
/// ```
|
||||
pub fn load_core<P: AsRef<std::path::Path>>(path: P) -> Result<()> {
|
||||
unsafe { FRONTEND_IMPL.load_core(path) }
|
||||
}
|
||||
|
||||
/// Unloads the core currently running in the global frontend state.
|
||||
///
|
||||
/// ```rust
|
||||
/// use retro_frontend::frontend;
|
||||
/// frontend::unload_core();
|
||||
/// ```
|
||||
pub fn unload_core() -> Result<()> {
|
||||
unsafe { FRONTEND_IMPL.unload_core() }
|
||||
}
|
||||
|
||||
/// Loads a ROM into the given core. This function requires that [load_core] has been called and has succeeded first.
|
||||
///
|
||||
/// ```rust
|
||||
/// use retro_frontend::frontend;
|
||||
/// frontend::load_game("./roms/sma2.gba");
|
||||
/// ```
|
||||
pub fn load_game<P: AsRef<std::path::Path>>(path: P) -> Result<()> {
|
||||
unsafe { FRONTEND_IMPL.load_game(path) }
|
||||
}
|
||||
|
||||
/// Unloads a ROM from the given core.
|
||||
pub fn unload_game() -> Result<()> {
|
||||
unsafe { FRONTEND_IMPL.unload_game() }
|
||||
}
|
||||
|
||||
/// Gets the core's current AV information.
|
||||
pub fn get_av_info() -> Result<SystemAvInfo> {
|
||||
unsafe { FRONTEND_IMPL.get_av_info() }
|
||||
}
|
||||
|
||||
/// Gets the current framebuffer width and height as a tuple.
|
||||
pub fn get_size() -> (u32, u32) {
|
||||
unsafe { FRONTEND_IMPL.get_size() }
|
||||
}
|
||||
|
||||
/// Runs the currently loaded core for one video frame.
|
||||
pub fn run_frame() {
|
||||
unsafe { FRONTEND_IMPL.run() }
|
||||
}
|
344
crates/retro_frontend/src/frontend_impl.rs
Normal file
344
crates/retro_frontend/src/frontend_impl.rs
Normal file
|
@ -0,0 +1,344 @@
|
|||
use crate::joypad::Joypad;
|
||||
use crate::libretro_callbacks;
|
||||
use crate::result::{Error, Result};
|
||||
use ffi::CString;
|
||||
use libloading::Library;
|
||||
use libretro_sys::*;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
use std::{fs, mem::MaybeUninit};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use std::ffi;
|
||||
|
||||
use tracing::{error, info};
|
||||
|
||||
// FIXME(lily): Rust 2024 will make a good chunk of this code illegal.
|
||||
// It might be wise to just bind some "simpler" C++ code and make it safe with lifetimes here,
|
||||
// or something. It's a bit of a pickle.
|
||||
|
||||
/// The frontend implementation.
|
||||
///
|
||||
/// # Safety
|
||||
/// Note that Libretro itself is not thread safe, so we do not try and pretend
|
||||
/// that we are thread safe either.
|
||||
pub(crate) static mut FRONTEND_IMPL: Lazy<FrontendStateImpl> =
|
||||
Lazy::new(|| FrontendStateImpl::new());
|
||||
|
||||
pub(crate) type VideoUpdateCallback = dyn FnMut(&[u32]);
|
||||
pub(crate) type VideoResizeCallback = dyn FnMut(u32, u32);
|
||||
|
||||
// TODO(lily): This should probably return the amount of consumed frames,
|
||||
// as in some cases that *might* differ?
|
||||
pub(crate) type AudioSampleCallback = dyn FnMut(&[i16], usize);
|
||||
|
||||
pub(crate) type InputPollCallback = dyn FnMut();
|
||||
|
||||
pub(crate) struct FrontendStateImpl {
|
||||
/// The current core's libretro functions.
|
||||
pub(crate) core_api: Option<CoreAPI>,
|
||||
|
||||
/// The current core library.
|
||||
pub(crate) core_library: Option<Box<Library>>,
|
||||
|
||||
pub(crate) game_loaded: bool,
|
||||
|
||||
pub(crate) av_info: Option<SystemAvInfo>,
|
||||
|
||||
/// Core requested pixel format.
|
||||
pub(crate) pixel_format: PixelFormat,
|
||||
|
||||
// Converted pixel buffer. We store it here so we don't keep allocating over and over.
|
||||
pub(crate) converted_pixel_buffer: Vec<u32>,
|
||||
|
||||
pub(crate) fb_width: u32,
|
||||
pub(crate) fb_height: u32,
|
||||
pub(crate) fb_pitch: u32,
|
||||
|
||||
pub(crate) system_directory: CString,
|
||||
pub(crate) save_directory: CString,
|
||||
|
||||
pub(crate) joypads: HashMap<u32 /* port */, Rc<RefCell<dyn Joypad>>>,
|
||||
|
||||
// Callbacks that consumers can set
|
||||
pub(crate) video_update_callback: Option<Box<VideoUpdateCallback>>,
|
||||
pub(crate) video_resize_callback: Option<Box<VideoResizeCallback>>,
|
||||
pub(crate) audio_sample_callback: Option<Box<AudioSampleCallback>>,
|
||||
pub(crate) input_poll_callback: Option<Box<InputPollCallback>>,
|
||||
}
|
||||
|
||||
impl FrontendStateImpl {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
core_api: None,
|
||||
core_library: None,
|
||||
|
||||
game_loaded: false,
|
||||
|
||||
av_info: None,
|
||||
|
||||
pixel_format: PixelFormat::RGB565,
|
||||
converted_pixel_buffer: Vec::new(),
|
||||
|
||||
fb_width: 0,
|
||||
fb_height: 0,
|
||||
fb_pitch: 0,
|
||||
|
||||
// TODO: We should let callers set these!!
|
||||
system_directory: CString::new("system").unwrap(),
|
||||
save_directory: CString::new("save").unwrap(),
|
||||
|
||||
joypads: HashMap::new(),
|
||||
|
||||
video_update_callback: None,
|
||||
video_resize_callback: None,
|
||||
audio_sample_callback: None,
|
||||
input_poll_callback: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn core_loaded(&self) -> bool {
|
||||
// Ideally this logic could be simplified but just to make sure..
|
||||
self.core_library.is_some() && self.core_api.is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn set_video_update_callback(&mut self, cb: impl FnMut(&[u32]) + 'static) {
|
||||
self.video_update_callback = Some(Box::new(cb));
|
||||
}
|
||||
|
||||
pub(crate) fn set_video_resize_callback(&mut self, cb: impl FnMut(u32, u32) + 'static) {
|
||||
self.video_resize_callback = Some(Box::new(cb));
|
||||
}
|
||||
|
||||
pub(crate) fn set_audio_sample_callback(&mut self, cb: impl FnMut(&[i16], usize) + 'static) {
|
||||
self.audio_sample_callback = Some(Box::new(cb));
|
||||
}
|
||||
|
||||
pub(crate) fn set_input_poll_callback(&mut self, cb: impl FnMut() + 'static) {
|
||||
self.input_poll_callback = Some(Box::new(cb));
|
||||
}
|
||||
|
||||
pub(crate) fn set_input_port_device(&mut self, port: u32, device: Rc<RefCell<dyn Joypad>>) {
|
||||
if self.core_loaded() {
|
||||
let core_api = self.core_api.as_mut().unwrap();
|
||||
|
||||
unsafe {
|
||||
(core_api.retro_set_controller_port_device)(port, device.borrow().device_type());
|
||||
}
|
||||
|
||||
self.joypads.insert(port, device);
|
||||
}
|
||||
}
|
||||
|
||||
// clear_input_port_device?
|
||||
|
||||
pub(crate) fn load_core<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
|
||||
if self.core_loaded() {
|
||||
return Err(Error::CoreAlreadyLoaded);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let lib = Box::new(Library::new(path.as_ref())?);
|
||||
|
||||
// bleh; CoreAPI doesn't implement Default so I can't do this in a "good" way
|
||||
let mut api_uninitialized: MaybeUninit<CoreAPI> = MaybeUninit::zeroed();
|
||||
let api_ptr = api_uninitialized.as_mut_ptr();
|
||||
|
||||
// helper for DRY reasons
|
||||
macro_rules! load_symbol {
|
||||
($name:ident) => {
|
||||
(*api_ptr).$name = *(lib.get(stringify!($name).as_bytes())?);
|
||||
};
|
||||
}
|
||||
|
||||
load_symbol!(retro_set_environment);
|
||||
load_symbol!(retro_set_video_refresh);
|
||||
load_symbol!(retro_set_audio_sample);
|
||||
load_symbol!(retro_set_audio_sample_batch);
|
||||
load_symbol!(retro_set_input_poll);
|
||||
load_symbol!(retro_set_input_state);
|
||||
load_symbol!(retro_init);
|
||||
load_symbol!(retro_deinit);
|
||||
load_symbol!(retro_api_version);
|
||||
load_symbol!(retro_get_system_info);
|
||||
load_symbol!(retro_get_system_av_info);
|
||||
load_symbol!(retro_set_controller_port_device);
|
||||
load_symbol!(retro_reset);
|
||||
load_symbol!(retro_run);
|
||||
load_symbol!(retro_serialize_size);
|
||||
load_symbol!(retro_serialize);
|
||||
load_symbol!(retro_unserialize);
|
||||
load_symbol!(retro_cheat_reset);
|
||||
load_symbol!(retro_cheat_set);
|
||||
load_symbol!(retro_load_game);
|
||||
load_symbol!(retro_load_game_special);
|
||||
load_symbol!(retro_unload_game);
|
||||
load_symbol!(retro_get_region);
|
||||
load_symbol!(retro_get_memory_data);
|
||||
load_symbol!(retro_get_memory_size);
|
||||
|
||||
// If we get here, then we have initalized all the core API without failing.
|
||||
// We can now get an initalized CoreAPI.
|
||||
let core_api = api_uninitialized.assume_init();
|
||||
|
||||
// Let's sanity check the libretro API version against bindings to make sure we can actually use this core.
|
||||
// If we can't then fail the load.
|
||||
let api_version = (core_api.retro_api_version)();
|
||||
if api_version != libretro_sys::API_VERSION {
|
||||
error!(
|
||||
"Core {} has invalid API version {api_version}; refusing to continue loading",
|
||||
path.as_ref().display()
|
||||
);
|
||||
return Err(Error::InvalidLibRetroAPI {
|
||||
expected: libretro_sys::API_VERSION,
|
||||
got: api_version,
|
||||
});
|
||||
}
|
||||
|
||||
// Set required libretro callbacks before calling libretro_init.
|
||||
// Some cores expect some callbacks to be set before libretro_init is called,
|
||||
// some cores don't. For maximum compatibility, pamper the cores which do.
|
||||
(core_api.retro_set_environment)(libretro_callbacks::environment_callback);
|
||||
|
||||
// Initalize the libretro core. We do this first because
|
||||
// there are a Few cores which initalize resources that later
|
||||
// are poked by the later callback setting that could break if we don't.
|
||||
(core_api.retro_init)();
|
||||
|
||||
// Set more libretro callbacks now that we have initalized the core.
|
||||
(core_api.retro_set_video_refresh)(libretro_callbacks::video_refresh_callback);
|
||||
(core_api.retro_set_input_poll)(libretro_callbacks::input_poll_callback);
|
||||
(core_api.retro_set_input_state)(libretro_callbacks::input_state_callback);
|
||||
(core_api.retro_set_audio_sample_batch)(
|
||||
libretro_callbacks::audio_sample_batch_callback,
|
||||
);
|
||||
|
||||
info!("Core {} loaded", path.as_ref().display());
|
||||
|
||||
// Get AV info
|
||||
// Like core API, we have to MaybeUninit again.
|
||||
let mut av_info: MaybeUninit<SystemAvInfo> = MaybeUninit::uninit();
|
||||
(core_api.retro_get_system_av_info)(av_info.as_mut_ptr());
|
||||
|
||||
self.av_info = Some(av_info.assume_init());
|
||||
|
||||
self.core_library = Some(lib);
|
||||
self.core_api = Some(core_api);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn unload_core(&mut self) -> Result<()> {
|
||||
if !self.core_loaded() {
|
||||
return Err(Error::CoreNotLoaded);
|
||||
}
|
||||
|
||||
if self.game_loaded {
|
||||
self.unload_game()?;
|
||||
}
|
||||
|
||||
// First deinitalize the libretro core before unloading the library.
|
||||
if let Some(core_api) = &self.core_api {
|
||||
unsafe {
|
||||
(core_api.retro_deinit)();
|
||||
}
|
||||
}
|
||||
|
||||
// Unload the library. We don't worry about error handling right now, but
|
||||
// we could.
|
||||
let lib = self.core_library.take().unwrap();
|
||||
lib.close()?;
|
||||
|
||||
self.core_api = None;
|
||||
self.core_library = None;
|
||||
|
||||
// FIXME: Do other various cleanup (when we need to do said cleanup)
|
||||
self.av_info = None;
|
||||
|
||||
self.fb_width = 0;
|
||||
self.fb_height = 0;
|
||||
self.fb_pitch = 0;
|
||||
|
||||
// disconnect all currently connected joypads
|
||||
self.joypads.clear();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn load_game<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
|
||||
if !self.core_loaded() {
|
||||
return Err(Error::CoreNotLoaded);
|
||||
}
|
||||
|
||||
// For now I'm only implementing the gameinfo garbage that
|
||||
// makes you read the whole file in. Later on I'll look into VFS
|
||||
// support; but for now, it seems more cores will probably
|
||||
// play ball with this.. which sucks :(
|
||||
|
||||
// I'm aware this is nasty but bleh
|
||||
let slice = path.as_ref().as_os_str().as_bytes();
|
||||
let path_string = CString::new(slice).expect("shouldn't fail");
|
||||
let contents = fs::read(path)?;
|
||||
|
||||
let gameinfo = GameInfo {
|
||||
path: path_string.as_ptr(),
|
||||
data: contents.as_ptr() as *const ffi::c_void,
|
||||
size: contents.len(),
|
||||
meta: std::ptr::null(),
|
||||
};
|
||||
|
||||
let core_api = self.core_api.as_ref().unwrap();
|
||||
|
||||
unsafe {
|
||||
if !(core_api.retro_load_game)(&gameinfo) {
|
||||
return Err(Error::RomLoadFailed);
|
||||
}
|
||||
|
||||
self.game_loaded = true;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn unload_game(&mut self) -> Result<()> {
|
||||
if !self.core_loaded() {
|
||||
return Err(Error::CoreNotLoaded);
|
||||
}
|
||||
|
||||
let core_api = self.core_api.as_ref().unwrap();
|
||||
|
||||
if self.game_loaded {
|
||||
unsafe {
|
||||
(core_api.retro_unload_game)();
|
||||
}
|
||||
|
||||
self.game_loaded = false;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn get_av_info(&mut self) -> Result<SystemAvInfo> {
|
||||
if !self.core_loaded() {
|
||||
return Err(Error::CoreNotLoaded);
|
||||
}
|
||||
|
||||
Ok(self.av_info.as_ref().unwrap().clone())
|
||||
}
|
||||
|
||||
pub(crate) fn get_size(&mut self) -> (u32, u32) {
|
||||
(self.fb_width, self.fb_height)
|
||||
}
|
||||
|
||||
pub(crate) fn run(&mut self) {
|
||||
let core_api = self.core_api.as_ref().unwrap();
|
||||
|
||||
unsafe {
|
||||
(core_api.retro_run)();
|
||||
}
|
||||
}
|
||||
}
|
67
crates/retro_frontend/src/joypad.rs
Normal file
67
crates/retro_frontend/src/joypad.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
//! libretro pad abstraction
|
||||
|
||||
use crate::libretro_sys_new;
|
||||
|
||||
pub trait Joypad {
|
||||
// TODO: is_pressed(id: u32)?
|
||||
|
||||
fn device_type(&self) -> u32;
|
||||
|
||||
fn get_button(&self, id: u32) -> i16;
|
||||
|
||||
fn reset(&mut self);
|
||||
|
||||
fn press_button(&mut self, id: u32, pressure: Option<i16>);
|
||||
}
|
||||
|
||||
// TODO: Split this into a new module, and make this a dir based one
|
||||
// (mod.rs/retropad.rs/analogpad.rs) once we have AnalogPad support.
|
||||
|
||||
/// Implementation of the [Joypad] trait for the Libretro
|
||||
/// RetroPad; which is essentially a standard PS1 controller,
|
||||
/// with a couple more buttons inherited from the Dual Analog/DualShock.
|
||||
pub struct RetroPad {
|
||||
buttons: [i16; 16],
|
||||
}
|
||||
|
||||
impl RetroPad {
|
||||
pub fn new() -> Self {
|
||||
Self { buttons: [0; 16] }
|
||||
}
|
||||
}
|
||||
|
||||
impl Joypad for RetroPad {
|
||||
fn device_type(&self) -> u32 {
|
||||
libretro_sys_new::DEVICE_JOYPAD
|
||||
}
|
||||
|
||||
fn get_button(&self, id: u32) -> i16 {
|
||||
if id > 16 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
self.buttons[id as usize]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
for button in &mut self.buttons {
|
||||
*button = 0i16;
|
||||
}
|
||||
}
|
||||
|
||||
fn press_button(&mut self, id: u32, pressure: Option<i16>) {
|
||||
if id > 16 {
|
||||
return;
|
||||
}
|
||||
|
||||
match pressure {
|
||||
Some(pressure_value) => {
|
||||
self.buttons[id as usize] = pressure_value;
|
||||
}
|
||||
None => {
|
||||
// ? or 0x7fff ? Unsure
|
||||
self.buttons[id as usize] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
crates/retro_frontend/src/lib.rs
Normal file
17
crates/retro_frontend/src/lib.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
//! A libretro frontend as a reusable library crate.
|
||||
|
||||
mod frontend_impl;
|
||||
mod libretro_callbacks;
|
||||
mod libretro_log;
|
||||
|
||||
pub mod libretro_sys_new;
|
||||
|
||||
pub mod core;
|
||||
|
||||
pub mod joypad;
|
||||
|
||||
//#[macro_use]
|
||||
pub mod util;
|
||||
|
||||
pub mod frontend;
|
||||
pub mod result;
|
272
crates/retro_frontend/src/libretro_callbacks.rs
Normal file
272
crates/retro_frontend/src/libretro_callbacks.rs
Normal file
|
@ -0,0 +1,272 @@
|
|||
use crate::libretro_sys_new::*;
|
||||
use crate::{frontend_impl::*, libretro_log, util};
|
||||
|
||||
use rgb565::Rgb565;
|
||||
|
||||
use std::ffi;
|
||||
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
pub(crate) unsafe extern "C" fn environment_callback(
|
||||
environment_command: u32,
|
||||
data: *mut ffi::c_void,
|
||||
) -> bool {
|
||||
match environment_command {
|
||||
ENVIRONMENT_GET_LOG_INTERFACE => {
|
||||
*(data as *mut LogCallback) = libretro_log::LOG_INTERFACE.clone();
|
||||
return true;
|
||||
}
|
||||
|
||||
ENVIRONMENT_SET_PERFORMANCE_LEVEL => {
|
||||
let level = *(data as *const ffi::c_uint);
|
||||
debug!("Core is performance level {level}");
|
||||
return true;
|
||||
}
|
||||
|
||||
ENVIRONMENT_SET_CONTROLLER_INFO => {
|
||||
let ptr = data as *const ControllerInfo;
|
||||
|
||||
let slice = util::terminated_array(ptr, |item| {
|
||||
return item.num_types == 0 && item.types.is_null();
|
||||
});
|
||||
|
||||
for desc in slice {
|
||||
debug!("{:?}", desc);
|
||||
|
||||
for i in 0..desc.num_types as usize {
|
||||
let p = desc.types.add(i).as_ref().unwrap();
|
||||
debug!(
|
||||
"type {i} = {:?} (name is {})",
|
||||
p,
|
||||
std::ffi::CStr::from_ptr(p.desc).to_str().unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ENVIRONMENT_SET_INPUT_DESCRIPTORS => {
|
||||
let ptr = data as *const InputDescriptor;
|
||||
|
||||
let slice = util::terminated_array(ptr, |item| {
|
||||
return item.description.is_null();
|
||||
});
|
||||
|
||||
debug!("{} input descriptor entries", slice.len());
|
||||
|
||||
for desc in slice {
|
||||
debug!("Descriptor {:?}", desc);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ENVIRONMENT_GET_CAN_DUPE => {
|
||||
*(data as *mut bool) = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
ENVIRONMENT_GET_SYSTEM_DIRECTORY => {
|
||||
*(data as *mut *const ffi::c_char) = FRONTEND_IMPL.system_directory.as_ptr();
|
||||
return true;
|
||||
}
|
||||
|
||||
ENVIRONMENT_GET_SAVE_DIRECTORY => {
|
||||
*(data as *mut *const ffi::c_char) = FRONTEND_IMPL.save_directory.as_ptr();
|
||||
return true;
|
||||
}
|
||||
|
||||
ENVIRONMENT_SET_PIXEL_FORMAT => {
|
||||
let _pixel_format = *(data as *const ffi::c_uint);
|
||||
let pixel_format = PixelFormat::from_uint(_pixel_format).unwrap();
|
||||
FRONTEND_IMPL.pixel_format = pixel_format;
|
||||
return true;
|
||||
}
|
||||
|
||||
ENVIRONMENT_SET_GEOMETRY => {
|
||||
if data.is_null() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let geometry = (data as *const GameGeometry).as_ref().unwrap();
|
||||
|
||||
FRONTEND_IMPL.fb_width = geometry.base_width;
|
||||
FRONTEND_IMPL.fb_height = geometry.base_height;
|
||||
|
||||
if let Some(resize_callback) = &mut FRONTEND_IMPL.video_resize_callback {
|
||||
resize_callback(geometry.base_width, geometry.base_height);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ENVIRONMENT_GET_VARIABLE => {
|
||||
// Make sure the core actually is giving us a pointer to a *Variable
|
||||
// so we can (if we have it!) fill it in.
|
||||
if data.is_null() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let var = (data as *mut Variable).as_mut().unwrap();
|
||||
|
||||
match ffi::CStr::from_ptr(var.key).to_str() {
|
||||
Ok(_key) => {
|
||||
debug!("Core wants to get variable \"{_key}\"",);
|
||||
return false;
|
||||
}
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Core gave an invalid key for ENVIRONMENT_GET_VARIABLE: {:?}",
|
||||
err
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ENVIRONMENT_GET_VARIABLE_UPDATE => {
|
||||
// We currently pressent no changed variables to the core.
|
||||
// TODO: this will change
|
||||
*(data as *mut bool) = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Fully implement, we'll need to implement above more fully.
|
||||
// Ideas:
|
||||
// - FrontendStateImpl can have a HashMap<CString, CString> which will then
|
||||
// be where we can store stuff. Also the consumer application could in theory
|
||||
// use that to save/restore (by injecting keys from another source)
|
||||
ENVIRONMENT_SET_VARIABLES => {
|
||||
let ptr = data as *const Variable;
|
||||
|
||||
let _slice = util::terminated_array(ptr, |item| item.key.is_null());
|
||||
|
||||
/*
|
||||
|
||||
for var in slice {
|
||||
let key = std::ffi::CStr::from_ptr(var.key).to_str().unwrap();
|
||||
let value = std::ffi::CStr::from_ptr(var.value).to_str().unwrap();
|
||||
}*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_ => {
|
||||
debug!("Environment callback called with currently unhandled command: {environment_command}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe extern "C" fn video_refresh_callback(
|
||||
pixels: *const ffi::c_void,
|
||||
width: ffi::c_uint,
|
||||
height: ffi::c_uint,
|
||||
pitch: usize,
|
||||
) {
|
||||
// I guess this must be how duplicated frames are signaled.
|
||||
// one word: Bleh
|
||||
if pixels.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
//info!("Video refresh called, {width}, {height}, {pitch}");
|
||||
|
||||
// bleh
|
||||
FRONTEND_IMPL.fb_width = width;
|
||||
FRONTEND_IMPL.fb_height = height;
|
||||
FRONTEND_IMPL.fb_pitch =
|
||||
pitch as u32 / util::bytes_per_pixel_from_libretro(FRONTEND_IMPL.pixel_format);
|
||||
|
||||
let pitch = FRONTEND_IMPL.fb_pitch as usize;
|
||||
|
||||
match FRONTEND_IMPL.pixel_format {
|
||||
PixelFormat::RGB565 => {
|
||||
let pixel_data_slice = std::slice::from_raw_parts(
|
||||
pixels as *const u16,
|
||||
(pitch * height as usize) as usize,
|
||||
);
|
||||
|
||||
// Resize the pixel buffer if we need to
|
||||
if (pitch * height as usize) as usize != FRONTEND_IMPL.converted_pixel_buffer.len() {
|
||||
info!("Resizing RGB565 -> RGBA buffer");
|
||||
FRONTEND_IMPL
|
||||
.converted_pixel_buffer
|
||||
.resize((pitch * height as usize) as usize, 0);
|
||||
|
||||
// some cores are stupid
|
||||
if let Some(resize_callback) = &mut FRONTEND_IMPL.video_resize_callback {
|
||||
resize_callback(pitch as u32, height);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make this convert from weird pitches to native resolution where possible.
|
||||
for x in 0..pitch as usize {
|
||||
for y in 0..height as usize {
|
||||
let rgb = Rgb565::from_rgb565(pixel_data_slice[y * pitch as usize + x]);
|
||||
let comp = rgb.to_rgb888_components();
|
||||
|
||||
// Finally save the pixel data in the result array as an XRGB8888 value
|
||||
FRONTEND_IMPL.converted_pixel_buffer[y * pitch as usize + x] =
|
||||
((comp[0] as u32) << 16) | ((comp[1] as u32) << 8) | (comp[2] as u32);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(update_callback) = &mut FRONTEND_IMPL.video_update_callback {
|
||||
update_callback(&FRONTEND_IMPL.converted_pixel_buffer[..]);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let pixel_data_slice = std::slice::from_raw_parts(
|
||||
pixels as *const u32,
|
||||
(pitch * height as usize) as usize,
|
||||
);
|
||||
|
||||
if let Some(update_callback) = &mut FRONTEND_IMPL.video_update_callback {
|
||||
update_callback(&pixel_data_slice);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe extern "C" fn input_poll_callback() {
|
||||
if let Some(poll) = &mut FRONTEND_IMPL.input_poll_callback {
|
||||
poll() |