Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0195cbf5ac | ||
|
|
ccb96a8cf1 |
No files matched your search
@@ -324,9 +324,12 @@ async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> {
|
||||
/// external game-directory root for the one-click StarLight install flow.
|
||||
#[tauri::command]
|
||||
fn get_launcher_root_dir() -> api::Result<String> {
|
||||
let dir = theseus::default_game_dir_root().ok_or_else(|| {
|
||||
let exe = std::env::current_exe().map_err(|error| {
|
||||
theseus::Error::from(theseus::ErrorKind::FSError(error.to_string()))
|
||||
})?;
|
||||
let dir = exe.parent().ok_or_else(|| {
|
||||
theseus::Error::from(theseus::ErrorKind::FSError(
|
||||
"Could not determine a default game directory root".to_string(),
|
||||
"Launcher executable has no parent directory".to_string(),
|
||||
))
|
||||
})?;
|
||||
Ok(dir.to_string_lossy().into_owned())
|
||||
|
||||
@@ -109,6 +109,7 @@ pub struct CrashAnalysisMod {
|
||||
struct SourceCandidate {
|
||||
path: PathBuf,
|
||||
filename: String,
|
||||
upload_name: String,
|
||||
source_type: &'static str,
|
||||
modified: SystemTime,
|
||||
}
|
||||
@@ -116,10 +117,25 @@ struct SourceCandidate {
|
||||
#[derive(Debug)]
|
||||
struct SourceText {
|
||||
filename: String,
|
||||
upload_name: String,
|
||||
source_type: &'static str,
|
||||
modified: SystemTime,
|
||||
content: String,
|
||||
}
|
||||
pub(super) struct CollectedCrashLog {
|
||||
pub name: String,
|
||||
pub content: CensoredString,
|
||||
}
|
||||
|
||||
pub(super) struct CollectedCrashLogs {
|
||||
pub combined_log: CensoredString,
|
||||
pub sources: Vec<CollectedCrashLog>,
|
||||
}
|
||||
|
||||
struct CrashContext {
|
||||
sources: Vec<SourceText>,
|
||||
credentials: Vec<Credentials>,
|
||||
}
|
||||
|
||||
struct Rule {
|
||||
id: &'static str,
|
||||
@@ -1185,20 +1201,25 @@ async fn collect_candidates(
|
||||
) -> crate::Result<Vec<SourceCandidate>> {
|
||||
let mut candidates = Vec::new();
|
||||
let mut locations = vec![
|
||||
(directories.game_logs_dir(instance_root), "minecraft_log"),
|
||||
(
|
||||
directories.game_logs_dir(instance_root),
|
||||
"minecraft_log",
|
||||
None,
|
||||
),
|
||||
(
|
||||
directories.game_crash_reports_dir(instance_root),
|
||||
"crash_report",
|
||||
Some("crash-reports"),
|
||||
),
|
||||
(instance_root.to_path_buf(), "instance_log"),
|
||||
(instance_root.to_path_buf(), "instance_log", None),
|
||||
];
|
||||
if let Some(dot_minecraft) = isolated_minecraft_root(instance_root) {
|
||||
// PCL-CE also searches the shared `.minecraft` root for JVM crash
|
||||
// logs when a version-isolated instance is active.
|
||||
locations.push((dot_minecraft, "minecraft_root_log"));
|
||||
locations.push((dot_minecraft, "minecraft_root_log", None));
|
||||
}
|
||||
|
||||
for (directory, default_type) in locations {
|
||||
for (directory, default_type, upload_prefix) in locations {
|
||||
if !tokio::fs::try_exists(&directory).await? {
|
||||
continue;
|
||||
}
|
||||
@@ -1248,6 +1269,9 @@ async fn collect_candidates(
|
||||
};
|
||||
candidates.push(SourceCandidate {
|
||||
path: entry.path(),
|
||||
upload_name: upload_prefix
|
||||
.map(|prefix| format!("{prefix}/{filename}"))
|
||||
.unwrap_or_else(|| filename.clone()),
|
||||
filename,
|
||||
source_type,
|
||||
modified: metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH),
|
||||
@@ -1257,6 +1281,53 @@ async fn collect_candidates(
|
||||
Ok(candidates)
|
||||
}
|
||||
|
||||
async fn collect_crash_context(
|
||||
instance_id: &str,
|
||||
) -> crate::Result<CrashContext> {
|
||||
let state = State::get().await?;
|
||||
let (instance_path, game_dir_override) =
|
||||
resolve_instance_path(instance_id, &state).await?;
|
||||
let instance_root = state
|
||||
.directories
|
||||
.resolve_game_dir(&instance_path, game_dir_override.as_deref());
|
||||
let candidates =
|
||||
collect_candidates(&instance_root, &state.directories).await?;
|
||||
let selected = select_run_candidates(candidates);
|
||||
let sources = read_sources(selected).await;
|
||||
let credentials = Credentials::get_all(&state.pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|credential| credential.1)
|
||||
.collect::<Vec<_>>();
|
||||
Ok(CrashContext {
|
||||
sources,
|
||||
credentials,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) async fn collect_crash_logs(
|
||||
instance_id: &str,
|
||||
) -> crate::Result<CollectedCrashLogs> {
|
||||
let CrashContext {
|
||||
sources,
|
||||
credentials,
|
||||
..
|
||||
} = collect_crash_context(instance_id).await?;
|
||||
let combined_log =
|
||||
CensoredString::censor(build_combined_log(&sources), &credentials);
|
||||
let sources = sources
|
||||
.into_iter()
|
||||
.map(|source| CollectedCrashLog {
|
||||
name: source.upload_name,
|
||||
content: CensoredString::censor(source.content, &credentials),
|
||||
})
|
||||
.collect();
|
||||
Ok(CollectedCrashLogs {
|
||||
combined_log,
|
||||
sources,
|
||||
})
|
||||
}
|
||||
|
||||
fn isolated_minecraft_root(instance_root: &Path) -> Option<PathBuf> {
|
||||
let versions_dir = instance_root.parent()?;
|
||||
if versions_dir
|
||||
@@ -1320,6 +1391,7 @@ async fn read_sources(candidates: Vec<SourceCandidate>) -> Vec<SourceText> {
|
||||
};
|
||||
sources.push(SourceText {
|
||||
filename: candidate.filename,
|
||||
upload_name: candidate.upload_name,
|
||||
source_type: candidate.source_type,
|
||||
modified: candidate.modified,
|
||||
content: String::from_utf8_lossy(bytes).into_owned(),
|
||||
@@ -1779,6 +1851,7 @@ mod tests {
|
||||
fn source(filename: &str, content: &str) -> SourceText {
|
||||
SourceText {
|
||||
filename: filename.to_string(),
|
||||
upload_name: filename.to_string(),
|
||||
source_type: "minecraft_log",
|
||||
modified: SystemTime::now(),
|
||||
content: content.to_string(),
|
||||
@@ -1802,6 +1875,7 @@ mod tests {
|
||||
let candidates = vec![SourceCandidate {
|
||||
path: PathBuf::from("launcher_log.txt"),
|
||||
filename: "launcher_log.txt".to_string(),
|
||||
upload_name: "launcher_log.txt".to_string(),
|
||||
source_type: "minecraft_log",
|
||||
modified: SystemTime::now() - Duration::from_secs(181),
|
||||
}];
|
||||
|
||||
@@ -4,8 +4,7 @@ use futures::StreamExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::CensoredString;
|
||||
use super::crash_analysis::analyze_crash;
|
||||
use super::crash_analysis::collect_crash_logs;
|
||||
use crate::State;
|
||||
use crate::emit_logshare_ai_event;
|
||||
|
||||
@@ -200,32 +199,6 @@ fn source_identifier() -> String {
|
||||
format!("{LOGSHARE_SOURCE_PREFIX}/{}", env!("CARGO_PKG_VERSION"))
|
||||
}
|
||||
|
||||
struct CollectedCrashLog {
|
||||
name: String,
|
||||
content: CensoredString,
|
||||
}
|
||||
|
||||
struct CollectedCrashLogs {
|
||||
combined_log: CensoredString,
|
||||
sources: Vec<CollectedCrashLog>,
|
||||
}
|
||||
|
||||
async fn collect_crash_logs(
|
||||
instance_id: &str,
|
||||
) -> crate::Result<CollectedCrashLogs> {
|
||||
let analysis = analyze_crash(instance_id).await?;
|
||||
Ok(CollectedCrashLogs {
|
||||
combined_log: analysis.combined_log,
|
||||
sources: analysis
|
||||
.sources
|
||||
.into_iter()
|
||||
.map(|source| CollectedCrashLog {
|
||||
name: source.filename,
|
||||
content: source.content,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
async fn prepare_upload(instance_id: &str) -> crate::Result<UploadBody> {
|
||||
let settings = get_log_share_settings().await?;
|
||||
let logs = collect_crash_logs(instance_id).await?;
|
||||
|
||||
@@ -39,7 +39,7 @@ pub use state::{DirectoryInfo, State};
|
||||
pub use storage::*;
|
||||
pub use util::fetch::build_proxied_client;
|
||||
pub use util::file_lock::{LockingProcess, get_locking_processes};
|
||||
pub use util::platform::{default_game_dir_root, is_process_elevated};
|
||||
pub use util::platform::is_process_elevated;
|
||||
pub use util::proxy::{ProxyConfig, ProxyMode, ProxyTestResult};
|
||||
pub use util::symlink::SymlinkCapability;
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//! Platform-related code
|
||||
use daedalus::minecraft::{Os, OsRule};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// (e.g. "Run as administrator" on Windows).
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -144,31 +143,3 @@ pub fn classpath_separator(java_arch: &str) -> &'static str {
|
||||
Os::Windows | Os::WindowsArm64 => ";",
|
||||
}
|
||||
}
|
||||
|
||||
/// Root directory used as the default for one-click StarLight instance
|
||||
/// installs. Portable builds live next to their executable, which is a fine
|
||||
/// place for game data. System installs (e.g. a Linux `.deb` under `/usr/bin`)
|
||||
/// do not, so fall back to the user's home directory there.
|
||||
pub fn default_game_dir_root() -> Option<PathBuf> {
|
||||
let exe_dir = std::env::current_exe().ok()?.parent()?.to_path_buf();
|
||||
if cfg!(target_os = "linux") && is_system_install_dir(&exe_dir) {
|
||||
return dirs::home_dir();
|
||||
}
|
||||
Some(exe_dir)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn is_system_install_dir(path: &std::path::Path) -> bool {
|
||||
const SYSTEM_PREFIXES: &[&str] = &[
|
||||
"/usr/", "/opt/", "/snap/", "/bin/", "/sbin/", "/lib/", "/lib64/",
|
||||
];
|
||||
let display = path.to_string_lossy();
|
||||
SYSTEM_PREFIXES
|
||||
.iter()
|
||||
.any(|prefix| display.starts_with(prefix))
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fn is_system_install_dir(_path: &std::path::Path) -> bool {
|
||||
false
|
||||
}
|
||||
Reference in new issue
Block a user