Initial commit

This commit is contained in:
Lily Tsuru 2024-08-02 03:20:29 -04:00
commit 6f829c5252
23 changed files with 2216 additions and 0 deletions

11
.editorconfig Normal file
View 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
View 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
View file

@ -0,0 +1 @@
hard_tabs = true

684
Cargo.lock generated Normal file
View 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
View file

@ -0,0 +1,5 @@
[workspace]
resolver = "2"
members = [
"crates/*"
]

7
LICENSE Normal file
View 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
View 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.

View 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"

View 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");
}

View 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();
}
}

View 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() }
}

View 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)();
}
}
}

View 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;
}
}
}
}

View 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;

View 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()