gamebridge: Twitch Control #2
|
@ -71,3 +71,7 @@ sm64config.txt
|
||||||
!/sound/**/*custom*/**/*.aiff
|
!/sound/**/*custom*/**/*.aiff
|
||||||
!/assets/**/*custom*.bin
|
!/assets/**/*custom*.bin
|
||||||
!/assets/**/*custom*/**/*.bin
|
!/assets/**/*custom*/**/*.bin
|
||||||
|
|
||||||
|
/target
|
||||||
|
vblank
|
||||||
|
input
|
||||||
|
|
|
@ -0,0 +1,183 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "0.7.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9a60d744a80c30fcb657dfe2c1b22bcb3e814c1a1e3674f32bf5820b570fbff"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atty"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
|
||||||
|
dependencies = [
|
||||||
|
"atty",
|
||||||
|
"humantime",
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
"termcolor",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gamebridge"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"log",
|
||||||
|
"pretty_env_logger",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "61565ff7aaace3525556587bd2dc31d4a07071957be715e63ce7b1eccf51a8f4"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "humantime"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
|
||||||
|
dependencies = [
|
||||||
|
"quick-error",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pretty_env_logger"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d"
|
||||||
|
dependencies = [
|
||||||
|
"env_logger",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-error"
|
||||||
|
version = "1.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
"thread_local",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
|
||||||
|
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-util"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|
@ -0,0 +1,2 @@
|
||||||
|
[workspace]
|
||||||
|
members = [ "./gamebridge" ]
|
|
@ -38,10 +38,13 @@ write mode when it starts. The bridge will open this fifo in read mode.
|
||||||
The second one will be called =input= and will be opened by the game in read
|
The second one will be called =input= and will be opened by the game in read
|
||||||
mode. The bridge will open this fifo in write mode.
|
mode. The bridge will open this fifo in write mode.
|
||||||
|
|
||||||
On every frame, the game will write the text ="OK\n"= to the vblank fifo. This
|
On every frame, the game *MUST* write the text ="OK\n"= to the vblank fifo. This
|
||||||
will signal the bridge that it should write four bytes of data to the input
|
will signal the bridge that it *MUST* write four bytes of data to the input
|
||||||
fifo, conforming to the [[http://tasvideos.org/EmulatorResources/Mupen/M64.html#ControllerData][Controller Data]] specification of the Mupen64 format.
|
fifo, conforming to the [[http://tasvideos.org/EmulatorResources/Mupen/M64.html#ControllerData][Controller Data]] specification of the Mupen64 format.
|
||||||
This data will be interpreted by the game as actions for Mario to take.
|
This data will be interpreted by the game as actions for Mario to take.
|
||||||
|
|
||||||
The bridge *MUST* block on waiting for the vblank fifo to be written to by the
|
The bridge *MUST* block on waiting for the vblank fifo to be written to by the
|
||||||
game and the game *MUST* block on the input fifo being written to by the bridge.
|
game and the game *MUST* block on the input fifo being written to by the bridge.
|
||||||
|
|
||||||
|
When the game is exiting, the game *SHOULD* write ="BYE"= to the vblank fifo.
|
||||||
|
When the bridge recieves a ="BYE"= message, it *MUST* exit.
|
||||||
|
|
|
@ -7,3 +7,6 @@ edition = "2018"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0"
|
||||||
|
log = "0.4"
|
||||||
|
pretty_env_logger = "0.4"
|
||||||
|
|
|
@ -1,3 +1,29 @@
|
||||||
fn main() {
|
use log::{debug, info, warn};
|
||||||
println!("Hello, world!");
|
use std::{
|
||||||
|
fs::{File, OpenOptions},
|
||||||
|
io::{Read, Write},
|
||||||
|
str::from_utf8,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() -> anyhow::Result<()> {
|
||||||
|
pretty_env_logger::try_init()?;
|
||||||
|
let controller_data = [0; 4];
|
||||||
|
|
||||||
|
let mut vblank = File::open("vblank")?;
|
||||||
|
let mut input = OpenOptions::new().write(true).open("input")?;
|
||||||
|
|
||||||
|
info!("[gamebridge::rust] ready");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut data = [0; 3];
|
||||||
|
info!("waiting for vblank");
|
||||||
|
vblank.read(&mut data)?;
|
||||||
|
let str = from_utf8(&data)?;
|
||||||
|
debug!("got data: {}", str);
|
||||||
|
if str == "BYE" {
|
||||||
|
warn!("asked to exit by the game");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
input.write(&controller_data)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,12 @@ in pkgs.mkShell {
|
||||||
# build tools
|
# build tools
|
||||||
gnumake
|
gnumake
|
||||||
clang_10
|
clang_10
|
||||||
|
gdb
|
||||||
|
|
||||||
# gamebridge
|
# gamebridge
|
||||||
rustc
|
rustc
|
||||||
cargo
|
cargo
|
||||||
rls
|
rls
|
||||||
|
rustfmt
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,12 +17,21 @@ static FILE *input;
|
||||||
|
|
||||||
#define vblank_fname "vblank"
|
#define vblank_fname "vblank"
|
||||||
#define input_fname "input"
|
#define input_fname "input"
|
||||||
|
#define ok "OK\n"
|
||||||
|
#define bye "BYE"
|
||||||
|
|
||||||
|
static void gamebridge_close(void) {
|
||||||
|
fwrite(bye, 1, strlen(bye), vblank);
|
||||||
|
}
|
||||||
|
|
||||||
static void gamebridge_init(void) {
|
static void gamebridge_init(void) {
|
||||||
if (!configGameBridge) {
|
if (!configGameBridge) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printf("[gamebridge] starting...\n");
|
||||||
|
fflush(stdout);
|
||||||
|
|
||||||
unlink(vblank_fname);
|
unlink(vblank_fname);
|
||||||
unlink(input_fname);
|
unlink(input_fname);
|
||||||
|
|
||||||
|
@ -40,13 +49,18 @@ static void gamebridge_init(void) {
|
||||||
assert(result < 0);
|
assert(result < 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
vblank = fopen(vblank_fname, "w");
|
vblank = fopen(vblank_fname, "w+");
|
||||||
input = fopen(input_fname, "rb");
|
input = fopen(input_fname, "rb+");
|
||||||
assert(vblank);
|
assert(vblank);
|
||||||
assert(input);
|
assert(input);
|
||||||
|
|
||||||
setvbuf(vblank, NULL, _IONBF, 0);
|
setvbuf(vblank, NULL, _IONBF, 0);
|
||||||
setvbuf(input, NULL, _IONBF, 0);
|
setvbuf(input, NULL, _IONBF, 0);
|
||||||
|
|
||||||
|
printf("[gamebridge] starting rust daemon\n");
|
||||||
|
fflush(stdout);
|
||||||
|
system("./target/debug/gamebridge &");
|
||||||
|
atexit(gamebridge_close);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gamebridge_read(OSContPad *pad) {
|
static void gamebridge_read(OSContPad *pad) {
|
||||||
|
@ -54,13 +68,15 @@ static void gamebridge_read(OSContPad *pad) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
char* ok = "OK\n";
|
printf("[gamebridge] waiting for input\n");
|
||||||
fwrite(ok, 1, sizeof(ok), vblank);
|
fwrite(ok, 1, strlen(ok), vblank);
|
||||||
uint8_t bytes[4] = {0};
|
uint8_t bytes[4] = {0};
|
||||||
fread(bytes, 1, 4, input);
|
fread(bytes, 1, 4, input);
|
||||||
pad->button = (bytes[0] << 8) | bytes[1];
|
pad->button = (bytes[0] << 8) | bytes[1];
|
||||||
pad->stick_x = bytes[2];
|
pad->stick_x = bytes[2];
|
||||||
pad->stick_y = bytes[3];
|
pad->stick_y = bytes[3];
|
||||||
|
printf("[gamebridge] %02x%02x %02x%02x\n", bytes[0], bytes[1], bytes[2], bytes[3]);
|
||||||
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ControllerAPI controller_gamebridge = {
|
struct ControllerAPI controller_gamebridge = {
|
||||||
|
|
Loading…
Reference in New Issue