Skip to content

Commit bab1392

Browse files
committed
better process finder
1 parent f1ac719 commit bab1392

File tree

10 files changed

+103
-85
lines changed

10 files changed

+103
-85
lines changed

.github/workflows/rust.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ jobs:
1515
- uses: actions/checkout@v4
1616
- uses: taiki-e/create-gh-release-action@v1
1717
with:
18+
changelog: CHANGELOG.md
1819
# (required) GitHub token for creating GitHub Releases.
1920
token: ${{ secrets.GITHUB_TOKEN }}
2021

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## v1.1.0
2+
3+
More robust way of finding either Everest or Vanilla Celeste. Whether or not you started linsplit while in the menu, it should now work when opening a save file.

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "linsplit"
3-
version = "1.0.2"
3+
version = "1.1.0"
44
edition = "2024"
55
authors = ["Paloys"]
66
description = "An autosplitter for the game Celeste, working on Linux!"

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ If/when the projet gets big enough, I might try to upload it to various package
1616

1717
To use it, just run it using `linsplit` if it's in your path, or with `./linsplit` wherever you put the executable if not.
1818
LinSplit will then start listening on a port for a LiveSplit One connection. Once it has connected to LiveSplit One, it'll start searching for either Celeste or Everest (the modded version of Celeste) in the memory.\
19-
The way LinSplit detects Vanilla Celeste in by finding a specific object in the memory of the program by parsing your save files for your file timers (if you're not using the game from Steam, you might want to change the location with the `-f` argument).\
20-
It is recommended you start LinSplit inside an already started game file, while inside the menu (the 3D map where you choose your chapter). If it fails to detect on the first time, interrupt linsplit with Ctrl-C and try again.
19+
The way LinSplit detects Vanilla Celeste in by finding a specific object in the memory of the program by parsing your save files for your file timers (if you're not using the game from Steam, you might want to change the location with the `-f` argument).
2120

2221
### Arguments
2322

src/linsplit_data.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ pub struct LinSplitData {
2323
}
2424

2525
impl LinSplitData {
26-
pub async fn new(file_path: &str, addr: &str, save_location: &str) -> Arc<Self> {
26+
pub async fn new(file_path: &str, addr: &str, save_location: String) -> Arc<Self> {
2727
let events = Arc::new(Mutex::new(VecDeque::new()));
2828
let event_notifications = Arc::new(Notify::new());
2929
let splits = SplitData::read_splits(file_path).unwrap();
3030
let socket =
3131
SplitterSocket::new(addr, Arc::clone(&events), Arc::clone(&event_notifications))
3232
.await
3333
.unwrap();
34-
let game_data = RwLock::new(GameData::new(save_location));
34+
let game_data = RwLock::new(GameData::new(save_location).await);
3535
// tokio::time::sleep(Duration::from_secs(3)).await;
3636
let data = Arc::new(LinSplitData {
3737
splits,

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ async fn main() {
5050
let data = LinSplitData::new(
5151
&args.splits,
5252
&format!("{}:{}", args.address, args.port),
53-
&args.save_location,
53+
args.save_location,
5454
)
5555
.await;
5656
data.main_loop().await;

src/memory_reader/everest_reader.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use procfs::process::{MMPermissions, Process};
66
use std::{
77
fs::File,
88
io::{Read, Seek, SeekFrom},
9-
thread::sleep,
109
time::Duration,
1110
};
1211

@@ -16,7 +15,7 @@ pub(super) struct EverestMemReader {
1615
}
1716

1817
impl EverestMemReader {
19-
pub fn new() -> Result<Option<Box<Self>>> {
18+
pub async fn new() -> Result<Option<Box<Self>>> {
2019
const CORE_AUTOSPLITTER_MAGIC: &[u8] = b"EVERESTAUTOSPLIT\xF0\xF1\xF2\xF3";
2120
const CORE_AUTOSPLITTER_INFO_MIN_VERSION: u8 = 3;
2221
loop {
@@ -57,7 +56,7 @@ impl EverestMemReader {
5756
}
5857
}
5958
}
60-
sleep(Duration::from_millis(200));
59+
tokio::time::sleep(Duration::from_millis(200)).await;
6160
}
6261
}
6362

src/memory_reader/game_data.rs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
use std::fmt::{self, Display};
2+
use std::time::Duration;
3+
4+
use tokio::select;
25

36
use crate::split_reader::split_reader::{Area, AreaMode};
47

@@ -24,18 +27,32 @@ pub struct GameData {
2427
}
2528

2629
impl GameData {
27-
pub fn new(save_location: &str) -> Self {
30+
pub async fn new(save_location: String) -> Self {
2831
println!("Waiting for Celeste...");
2932
let mem_reader: Box<dyn MemReader>;
3033
loop {
31-
if let Ok(Some(reader)) = VanillaMemReader::new(save_location) {
32-
println!("Found Vanilla Celeste.");
33-
mem_reader = reader;
34-
break;
35-
} else if let Ok(Some(reader)) = EverestMemReader::new() {
36-
println!("Found Everest.");
37-
mem_reader = reader;
38-
break;
34+
select! {
35+
res = VanillaMemReader::new(save_location.clone()) => {
36+
match res {
37+
Ok(Some(reader)) => {
38+
println!("Found Vanilla Celeste.");
39+
mem_reader = reader;
40+
break
41+
},
42+
_ => {},
43+
}
44+
}
45+
res = EverestMemReader::new() => {
46+
match res {
47+
Ok(Some(reader)) => {
48+
println!("Found Everest.");
49+
mem_reader = reader;
50+
break;
51+
},
52+
_ => {},
53+
}
54+
}
55+
_ = tokio::time::sleep(Duration::from_secs(5)) => {}
3956
}
4057
}
4158
Self {

src/memory_reader/vanilla_reader.rs

Lines changed: 65 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -17,83 +17,82 @@ pub(super) struct VanillaMemReader {
1717
}
1818

1919
impl VanillaMemReader {
20-
pub fn new(save_location: &str) -> Result<Option<Box<Self>>> {
21-
let mut times: Vec<[u8; 8]> = Vec::with_capacity(3);
22-
for i in 0..3 {
23-
let file_path = expand_tilde(save_location)?.join(format!("{}.celeste", i));
24-
if file_path.exists() {
25-
let t = fs::read_to_string(file_path)?;
26-
let u = Document::parse(t.as_str());
27-
if let Ok(doc) = u {
28-
for child in doc.get_node(NodeId::new(1)).unwrap().children() {
29-
if child.tag_name().name() == "Time"
30-
&& let Some(time_str) = child.text()
31-
&& let Ok(time) = time_str.parse::<u64>()
32-
{
33-
times.push(time.to_le_bytes());
34-
break;
20+
pub async fn new(save_location: String) -> Result<Option<Box<Self>>> {
21+
tokio::task::spawn_blocking(move || {
22+
let mut times: Vec<[u8; 8]> = Vec::with_capacity(3);
23+
for i in 0..3 {
24+
let file_path = expand_tilde(&save_location)?.join(format!("{}.celeste", i));
25+
if file_path.exists() {
26+
let t = fs::read_to_string(file_path)?;
27+
let u = Document::parse(t.as_str());
28+
if let Ok(doc) = u {
29+
for child in doc.get_node(NodeId::new(1)).unwrap().children() {
30+
if child.tag_name().name() == "Time"
31+
&& let Some(time_str) = child.text()
32+
&& let Ok(time) = time_str.parse::<u64>()
33+
{
34+
times.push(time.to_le_bytes());
35+
break;
36+
}
3537
}
3638
}
3739
}
3840
}
39-
}
40-
if times.is_empty() {
41-
println!(
42-
"Couldn't find any save files! Make sure the right path is provided with the -f argument"
43-
);
44-
return Ok(None);
45-
}
46-
let all_processes: Vec<Process> = procfs::process::all_processes()
47-
.expect("Can't read /proc")
48-
.filter_map(|p| match p {
49-
Ok(p) => {
50-
if p.stat().ok()?.comm.contains("Celeste.bin.x86") {
51-
return Some(p);
52-
}
53-
None
54-
} // happy path
55-
Err(_) => None,
56-
})
57-
.collect();
58-
for process in all_processes {
59-
if let Ok(mut memory) = process.mem()
60-
&& let Ok(maps) = process.maps()
61-
{
62-
for map in maps {
63-
if map.perms.contains(
64-
MMPermissions::READ | MMPermissions::WRITE | MMPermissions::PRIVATE,
65-
) && map.address.1 - map.address.0 >= 24
66-
{
67-
if map.pathname != MMapPath::Anonymous {
68-
continue;
41+
if times.is_empty() {
42+
return Ok(None);
43+
}
44+
let all_processes: Vec<Process> = procfs::process::all_processes()
45+
.expect("Can't read /proc")
46+
.filter_map(|p| match p {
47+
Ok(p) => {
48+
if p.stat().ok()?.comm.contains("Celeste.bin.x86") {
49+
return Some(p);
6950
}
70-
let size = (map.address.1 - map.address.0) as usize;
71-
let mut buf = vec![0u8; size];
72-
73-
memory.seek(SeekFrom::Start(map.address.0))?;
74-
if let Err(_) = memory.read_exact(&mut buf) {
75-
continue;
76-
};
77-
for time in &times {
78-
let needle: [u8; 8] = *time;
79-
for i in (0..=buf.len() - 24).step_by(8) {
80-
if &buf[i..i + 8] == needle
81-
&& buf[i - 16..i].iter().all(|&b| b == 0)
82-
{
83-
let position = map.address.0 + i as u64;
84-
return Ok(Some(Box::new(VanillaMemReader {
85-
memory,
86-
offset: position - 0x28,
87-
last_file_time: f64::INFINITY,
88-
})));
51+
None
52+
} // happy path
53+
Err(_) => None,
54+
})
55+
.collect();
56+
for process in all_processes {
57+
if let Ok(mut memory) = process.mem()
58+
&& let Ok(maps) = process.maps()
59+
{
60+
for map in maps {
61+
if map.perms.contains(
62+
MMPermissions::READ | MMPermissions::WRITE | MMPermissions::PRIVATE,
63+
) && map.address.1 - map.address.0 >= 24
64+
{
65+
if map.pathname != MMapPath::Anonymous {
66+
continue;
67+
}
68+
let size = (map.address.1 - map.address.0) as usize;
69+
let mut buf = vec![0u8; size];
70+
71+
memory.seek(SeekFrom::Start(map.address.0))?;
72+
if let Err(_) = memory.read_exact(&mut buf) {
73+
continue;
74+
};
75+
for time in &times {
76+
let needle: [u8; 8] = *time;
77+
for i in (0..=buf.len() - 24).step_by(8) {
78+
if &buf[i..i + 8] == needle
79+
&& buf[i - 16..i].iter().all(|&b| b == 0)
80+
{
81+
let position = map.address.0 + i as u64;
82+
return Ok(Some(Box::new(VanillaMemReader {
83+
memory,
84+
offset: position - 0x28,
85+
last_file_time: f64::INFINITY,
86+
})));
87+
}
8988
}
9089
}
9190
}
9291
}
9392
}
9493
}
95-
}
96-
Ok(None)
94+
Ok(None)
95+
}).await?
9796
}
9897

9998
fn read_bits<const COUNT: usize>(&mut self, offset: u64) -> Result<[u8; COUNT]> {

0 commit comments

Comments
 (0)