Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
- Before: every time the launcher started, it put Link Play back on Same network, so players who only play over the internet had to pick Internet again each time
- Now: Internet through the public relay or a friend's relay stays chosen. A direct address, a relay on this PC (off until you turn it on again) and old room codes are still cleared at start
- Thanks to Sakamoto on Discord for the report
- **Set up says why it failed when the game folder has an accent in its path** (#630)
- Before: with the game in a folder such as `OneDrive\Área de Trabalho`, a failed Set up showed only "The game build failed (exit code: 1)" and setup.log had no reason, because the reason named that folder in a form the launcher could not read
- Now: the game build writes its messages as UTF-8 and the launcher reads every line of a tool's output, so the real error reaches the screen and setup.log
- Still not: this shows the error, it does not fix it. A game folder inside OneDrive can still fail while OneDrive syncs the build files; a folder outside OneDrive (such as `C:\Games`) avoids that

### Gameplay
- **Camera aim keeps the block you aimed at while a move plays** (#535)
Expand Down
6 changes: 6 additions & 0 deletions game-build/release_b/pkp_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@
import time
from concurrent.futures import ThreadPoolExecutor, FIRST_COMPLETED, wait

# The launcher reads this script's stdout and stderr as UTF-8, and lift() reads its --lift-one children's the same way. Under -I
# Python writes a pipe in the ANSI code page (cp1252 on a Portuguese Windows), so an @error naming a folder such as
# "OneDrive\Área de Trabalho" reached the launcher as bytes it could not read, and Set up failed with no reason (#630).
for _stream in (sys.stdout, sys.stderr):
if hasattr(_stream, 'reconfigure'):
_stream.reconfigure(encoding='utf-8', errors='replace')
HERE = os.path.dirname(os.path.abspath(__file__))
RECOMP = os.path.join(HERE, 'recomp')
SDK = os.path.join(HERE, 'sdk')
Expand Down
2 changes: 2 additions & 0 deletions launcher/src-tauri/examples/first_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
//! it fetches the Linux build tools first (host.rs). Prints each phase's time.

// host.rs reaches the launcher's own modules, so an example carries the same set the binary declares (main.rs), by path.
#[path = "../src/child_lines.rs"]
mod child_lines;
#[allow(dead_code, clippy::all)]
#[path = "../src/config.rs"]
mod config;
Expand Down
2 changes: 2 additions & 0 deletions launcher/src-tauri/examples/shader_setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
//! cargo run --example shader_setup -- --native <harness dll> <corpus> <out> [--gentle]

// host.rs reaches the launcher's own modules, so an example carries the same set the binary declares (main.rs), by path.
#[path = "../src/child_lines.rs"]
mod child_lines;
#[allow(dead_code, clippy::all)]
#[path = "../src/config.rs"]
mod config;
Expand Down
40 changes: 40 additions & 0 deletions launcher/src-tauri/src/child_lines.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//! A child process's output (the game build, the shader tools, the DLSS download) read line by line.
//!
//! `BufRead::lines()` ends at the first line that is not UTF-8, and `map_while(Result::ok)` then drops the rest of the stream.
//! A Windows tool writes in the ANSI code page, so a line naming a folder such as "OneDrive\Área de Trabalho" ended the
//! reader: the game build's `@error` line never arrived and Set up failed with no reason in setup.log (#630). Here such a line
//! is read lossily and the stream goes on.

use std::io::{BufRead, BufReader, Read};

/// The lines `r` gives until it ends or fails, without their line ending ("\n" or "\r\n"). Bytes that are not UTF-8 read as
/// U+FFFD.
pub fn lines<R: Read>(r: R) -> impl Iterator<Item = String> {
let mut r = BufReader::new(r);
std::iter::from_fn(move || {
let mut buf = Vec::new();
match r.read_until(b'\n', &mut buf) {
Ok(0) | Err(_) => None,
Ok(_) => {
if buf.last() == Some(&b'\n') {
buf.pop();
if buf.last() == Some(&b'\r') {
buf.pop();
}
}
Some(String::from_utf8_lossy(&buf).into_owned())
}
}
})
}

#[cfg(test)]
mod tests {
#[test]
fn a_line_that_is_not_utf8_does_not_end_the_stream() {
// "@error C:\...\Área\r\n" as cp1252 writes it (0xC1), then a line after it
let out: &[u8] = b"@progress lift 0 4 x\r\n@error C:\\\xC1rea\r\nlast";
let got: Vec<String> = super::lines(out).collect();
assert_eq!(got, ["@progress lift 0 4 x", "@error C:\\\u{FFFD}rea", "last"]);
}
}
5 changes: 2 additions & 3 deletions launcher/src-tauri/src/dlss.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
//! downloads the file from NVIDIA's own GitHub repository at a fixed tag and checks its sha256, into `<game>/upscalers/streamline`.
use serde::Serialize;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
use std::process::Stdio;

Expand Down Expand Up @@ -76,9 +75,9 @@ pub fn fetch(game_dir: &Path, tools_dir: &Path, mut report: impl FnMut(Progress)
}
let mut child = cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).spawn().map_err(|e| format!("could not start the download ({}): {}", py.display(), e))?;
let err = child.stderr.take().unwrap();
let err_tail = std::thread::spawn(move || BufReader::new(err).lines().map_while(Result::ok).last().unwrap_or_default());
let err_tail = std::thread::spawn(move || crate::child_lines::lines(err).last().unwrap_or_default());
let mut error: Option<String> = None;
for l in BufReader::new(child.stdout.take().unwrap()).lines().map_while(Result::ok) {
for l in crate::child_lines::lines(child.stdout.take().unwrap()) {
if let Some(rest) = l.strip_prefix("@progress ") {
let mut it = rest.splitn(4, ' ');
let _phase = it.next();
Expand Down
5 changes: 2 additions & 3 deletions launcher/src-tauri/src/gamebuild.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
//! Its crate-local imports are host.rs (with download.rs) and runtime_api.rs, so `examples/first_run.rs` can build the files alone.

use std::fs;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicBool, Ordering};
Expand Down Expand Up @@ -293,11 +292,11 @@ pub fn run(job: &Job, tools: &Tools, cancel: &AtomicBool, mut report: impl FnMut
let out = child.stdout.take().unwrap();
let err = child.stderr.take().unwrap();
let (tx, rx) = std::sync::mpsc::channel::<String>();
std::thread::spawn(move || BufReader::new(out).lines().map_while(Result::ok).for_each(|l| drop(tx.send(l))));
std::thread::spawn(move || crate::child_lines::lines(out).for_each(|l| drop(tx.send(l))));
let elog = job.log.clone();
let err_tail = std::thread::spawn(move || {
let mut tail: Vec<String> = Vec::new();
for l in BufReader::new(err).lines().map_while(Result::ok) {
for l in crate::child_lines::lines(err) {
if let Some(f) = &elog {
f(&format!("stderr: {}", l));
}
Expand Down
6 changes: 3 additions & 3 deletions launcher/src-tauri/src/harness.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Running the shader translator (tools/shaders/harness, `spirv_harness.exe`) for `shaders`: a run, a worker that runs chunk after
//! chunk in one process, and why a failed run failed.
use std::fs;
use std::io::{BufRead, BufReader, Write};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Child, ChildStdin, Command, Stdio};
use std::sync::atomic::{AtomicBool, Ordering};
Expand Down Expand Up @@ -164,12 +164,12 @@ impl Worker {
.map_err(|e| format!("could not start {}: {}", name, e))?;
let out = child.stdout.take().unwrap();
let (tx, lines) = mpsc::channel::<String>();
std::thread::spawn(move || BufReader::new(out).lines().map_while(Result::ok).for_each(|l| drop(tx.send(l))));
std::thread::spawn(move || crate::child_lines::lines(out).for_each(|l| drop(tx.send(l))));
let err = child.stderr.take().unwrap();
let (etx, stderr) = mpsc::channel::<String>();
std::thread::spawn(move || {
let mut tail: Vec<String> = Vec::new();
for l in BufReader::new(err).lines().map_while(Result::ok) {
for l in crate::child_lines::lines(err) {
tail.push(l);
if tail.len() > 12 {
tail.remove(0);
Expand Down
1 change: 1 addition & 0 deletions launcher/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
mod backup_cmds;
mod branding;
mod branding_cmds;
mod child_lines;
mod cli;
mod config;
mod dialogs;
Expand Down
6 changes: 3 additions & 3 deletions launcher/src-tauri/src/shaders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::fs;
use std::io::{BufRead, BufReader, Write};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
Expand Down Expand Up @@ -455,13 +455,13 @@ pub(crate) fn run_tool(mut cmd: Command, cancel: &AtomicBool, idle: bool, log: O
let (tx, rx) = mpsc::channel::<String>();
let out = child.stdout.take().unwrap();
let tx2 = tx.clone();
std::thread::spawn(move || BufReader::new(out).lines().map_while(Result::ok).for_each(|l| drop(tx2.send(l))));
std::thread::spawn(move || crate::child_lines::lines(out).for_each(|l| drop(tx2.send(l))));
let err = child.stderr.take().unwrap();
let (etx, erx) = mpsc::channel::<String>();
let elog = log.cloned();
std::thread::spawn(move || {
let mut tail: Vec<String> = Vec::new();
for l in BufReader::new(err).lines().map_while(Result::ok) {
for l in crate::child_lines::lines(err) {
if let Some(f) = &elog {
f(&format!("stderr: {}", l));
}
Expand Down
Loading