Skip to content

Commit 8bf85fd

Browse files
committed
Initial commit
0 parents  commit 8bf85fd

7 files changed

Lines changed: 362 additions & 0 deletions

File tree

.cargo/config.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[build]
2+
target = "wasm32-unknown-unknown"
3+
rustflags = [
4+
"-C", "target-feature=+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,+simd128,+relaxed-simd",
5+
]

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
debug/
2+
target/
3+
**/*.rs.bk
4+
*.pdb

Cargo.lock

Lines changed: 159 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
[package]
2+
name = "sar-auto-splitter"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies.asr]
7+
git = "https://github.com/LiveSplit/asr"
8+
rev = "5eb46194eccb3ae81791eb9bfba70e85029abed3"
9+
features = ["signature", "flags"]
10+
11+
[dependencies.asr-derive]
12+
git = "https://github.com/LiveSplit/asr"
13+
rev = "5eb46194eccb3ae81791eb9bfba70e85029abed3"
14+
features = []
15+
16+
[lib]
17+
crate-type = ["cdylib"]
18+
19+
[profile.release]
20+
lto = true
21+
panic = "abort"
22+
codegen-units = 1
23+
24+
[profile.release.build-override]
25+
opt-level = 0
26+
27+
[features]
28+
nightly = []

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024, NeKz
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# SAR Auto Splitter
2+
3+
LiveSplit One compatible auto splitter for [SourceAutoRecord](https://sar.portal2.sr).
4+
5+
## Setup
6+
7+
```sh
8+
rustup target add wasm32-unknown-unknown
9+
```
10+
11+
## Building
12+
13+
```sh
14+
cargo build --release
15+
```
16+
17+
## Installation
18+
19+
Wasm file is at: `target/wasm32-unknown-unknown/release/sar_auto_splitter.wasm`
20+
21+
## Debugging
22+
23+
Get [asr-debugger](https://github.com/LiveSplit/asr-debugger).

src/lib.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#![no_std]
2+
3+
use asr::{
4+
future::{next_tick, retry},
5+
signature::Signature,
6+
time::Duration,
7+
timer::{self},
8+
watcher::Watcher,
9+
Process,
10+
};
11+
12+
const SAR_TIMER_SIGNATURE: Signature<42> = Signature::new("53 41 52 5F 54 49 4D 45 52 5F 53 54 41 52 54 00 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 53 41 52 5F 54 49 4D 45 52 5F 45 4E 44 00");
13+
const SAR_TIMER_OFFSET: u64 = 16;
14+
15+
const _TIMER_ACTION_NONE: i32 = 0;
16+
const TIMER_ACTION_START: i32 = 1;
17+
const TIMER_ACTION_RESTART: i32 = 2;
18+
const TIMER_ACTION_SPLIT: i32 = 3;
19+
const TIMER_ACTION_END: i32 = 4;
20+
const TIMER_ACTION_RESET: i32 = 5;
21+
22+
asr::panic_handler!();
23+
24+
#[cfg(not(feature = "nightly"))]
25+
asr::async_main!(stable);
26+
#[cfg(feature = "nightly")]
27+
asr::async_main!(nightly);
28+
29+
async fn main() {
30+
loop {
31+
asr::set_tick_rate(1.0);
32+
33+
timer::pause_game_time();
34+
35+
let process =
36+
retry(|| Process::attach("portal2.exe").or_else(|| Process::attach("portal2_linux")))
37+
.await;
38+
39+
process
40+
.until_closes(async {
41+
let mut interface_address: Option<asr::Address>;
42+
43+
loop {
44+
if process.get_module_address("sar.dll").is_ok()
45+
|| process.get_module_address("sar.so").is_ok()
46+
{
47+
interface_address = process
48+
.memory_ranges()
49+
.filter(|m| {
50+
m.flags()
51+
.unwrap_or_default()
52+
.contains(asr::MemoryRangeFlags::WRITE)
53+
})
54+
.find_map(|m| {
55+
m.range().map_or(None, |range| {
56+
SAR_TIMER_SIGNATURE.scan_process_range(&process, range)
57+
})
58+
})
59+
.and_then(|x| Some(x.add(SAR_TIMER_OFFSET)));
60+
61+
if interface_address.is_some() {
62+
break;
63+
}
64+
}
65+
66+
next_tick().await;
67+
}
68+
69+
let total_address = interface_address.unwrap();
70+
let ipt_address = total_address.clone().add(4);
71+
let action_address = total_address.clone().add(8);
72+
73+
asr::print_limited::<40>(&format_args!(
74+
"Found SAR + pubInterface at 0x{:X}",
75+
total_address.value()
76+
));
77+
78+
let mut total = Watcher::<i32>::new();
79+
let mut ipt = Watcher::<f32>::new();
80+
let mut action = Watcher::<i32>::new();
81+
82+
asr::set_tick_rate(120.0);
83+
84+
loop {
85+
let Some(total) = total.update(process.read(total_address).ok()) else {
86+
next_tick().await;
87+
continue;
88+
};
89+
let Some(ipt) = ipt.update(process.read(ipt_address).ok()) else {
90+
next_tick().await;
91+
continue;
92+
};
93+
let Some(action) = action.update(process.read(action_address).ok()) else {
94+
next_tick().await;
95+
continue;
96+
};
97+
98+
timer::set_game_time(Duration::saturating_seconds_f32(
99+
total.current as f32 * ipt.current,
100+
));
101+
102+
if action.changed() {
103+
match action.current {
104+
TIMER_ACTION_START | TIMER_ACTION_RESTART => {
105+
timer::start();
106+
}
107+
TIMER_ACTION_SPLIT | TIMER_ACTION_END => {
108+
timer::split();
109+
}
110+
TIMER_ACTION_RESET => {
111+
timer::reset();
112+
}
113+
_ => (),
114+
}
115+
}
116+
117+
next_tick().await;
118+
}
119+
})
120+
.await;
121+
}
122+
}

0 commit comments

Comments
 (0)