Skip to content
Open
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
5 changes: 4 additions & 1 deletion src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,8 @@ use external_fetch::{fetch_github_releases, fetch_pg_news};
use gst_manager::{gst_check_status, gst_download, gst_launch};
use unified_search::unified_search;
use settings::{
get_server_list, get_settings_file_path, load_settings, save_settings, SettingsManager,
get_default_game_data_path_command, get_default_player_log_path_command, get_server_list,
get_settings_file_path, load_settings, save_settings, SettingsManager,
};
use setup_commands::{
complete_setup, delete_character, get_user_characters, import_latest_inventory_for_character,
Expand Down Expand Up @@ -617,6 +618,8 @@ pub fn run() {
load_settings,
save_settings,
get_settings_file_path,
get_default_player_log_path_command,
get_default_game_data_path_command,
get_server_list,
// Setup / Onboarding
validate_game_data_path,
Expand Down
65 changes: 59 additions & 6 deletions src-tauri/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ pub struct AppSettings {
/// Auto-watch Player.log on startup (legacy)
pub auto_watch_on_startup: bool,

/// Root game data directory (%AppData%\LocalLow\Elder Game\Project Gorgon\)
/// Root game data directory.
/// Windows: %AppData%\..\LocalLow\Elder Game\Project Gorgon\
/// macOS: ~/Library/Application Support/unity.Elder Game.Project Gorgon/
pub game_data_path: String,

/// Automatically purge old data
Expand Down Expand Up @@ -261,7 +263,7 @@ fn default_report_watch_interval() -> u32 {
impl Default for AppSettings {
fn default() -> Self {
Self {
log_file_path: String::new(),
log_file_path: get_default_player_log_path(),
auto_watch_on_startup: false,
game_data_path: get_default_game_data_path(),
auto_purge_enabled: false,
Expand Down Expand Up @@ -374,10 +376,17 @@ impl SettingsManager {
self.settings.read().unwrap().game_data_path.clone()
}

/// Get Player.log path (constructed from game data path)
/// Get Player.log path.
///
/// Uses the explicit `log_file_path` setting when non-empty (macOS).
/// Falls back to `game_data_path / "Player.log"` for backward compat (Windows).
pub fn get_player_log_path(&self) -> Option<PathBuf> {
let settings = self.settings.read().unwrap();

if !settings.log_file_path.is_empty() {
return Some(PathBuf::from(&settings.log_file_path));
}

if settings.game_data_path.is_empty() {
return None;
}
Expand All @@ -397,9 +406,31 @@ impl SettingsManager {
}
}

/// Get default game data path (Windows-specific)
fn get_default_game_data_path() -> String {
// %APPDATA%\..\LocalLow\Elder Game\Project Gorgon\
/// Get the default Player.log path, or empty string if not determinable.
/// On macOS Player.log lives under ~/Library/Logs/, separate from game data.
/// On Windows returns empty so callers fall back to game_data_path/Player.log.
pub fn get_default_player_log_path() -> String {
if cfg!(target_os = "macos") {
if let Ok(home) = std::env::var("HOME") {
return PathBuf::from(home)
.join("Library")
.join("Logs")
.join("Elder Game")
.join("Project Gorgon")
.join("Player.log")
.to_str()
.unwrap_or_default()
.to_string();
}
}
String::new()
}

/// Get default game data path (platform-aware).
///
/// Windows: %APPDATA%\..\LocalLow\Elder Game\Project Gorgon\
/// macOS: ~/Library/Application Support/unity.Elder Game.Project Gorgon/
pub fn get_default_game_data_path() -> String {
if cfg!(target_os = "windows") {
if let Ok(local_appdata_low) = std::env::var("APPDATA") {
// APPDATA points to Roaming, we need LocalLow
Expand All @@ -413,6 +444,16 @@ fn get_default_game_data_path() -> String {
return path;
}
}
} else if cfg!(target_os = "macos") {
if let Ok(home) = std::env::var("HOME") {
return PathBuf::from(home)
.join("Library")
.join("Application Support")
.join("unity.Elder Game.Project Gorgon")
.to_str()
.unwrap_or_default()
.to_string();
}
}

// Fallback for other OSes or if we can't determine the path
Expand Down Expand Up @@ -446,6 +487,18 @@ pub fn get_server_list() -> Vec<String> {
PG_SERVERS.iter().map(|s| s.to_string()).collect()
}

/// Return the platform-appropriate default Player.log path.
#[tauri::command]
pub fn get_default_player_log_path_command() -> String {
get_default_player_log_path()
}

/// Return the platform-appropriate default game data path.
#[tauri::command]
pub fn get_default_game_data_path_command() -> String {
get_default_game_data_path()
}

/// Get the settings file path (for user reference)
#[tauri::command]
pub fn get_settings_file_path(
Expand Down
10 changes: 8 additions & 2 deletions src-tauri/src/setup_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use tauri::State;

use crate::cdn_commands::GameDataState;
use crate::db::DbPool;
use crate::settings::SettingsManager;
use crate::settings::{get_default_player_log_path, SettingsManager};

/// Timestamped log line for startup diagnostics.
macro_rules! startup_log {
Expand Down Expand Up @@ -65,9 +65,15 @@ struct ReportHeader {
#[tauri::command]
pub fn validate_game_data_path(path: String) -> GameDataPathValidation {
let base = Path::new(&path);

// Player.log may live alongside game data (Windows) or at the default
// macOS path (~/Library/Logs/Elder Game/Project Gorgon/Player.log).
let player_log_found = base.join("Player.log").exists()
|| Path::new(&get_default_player_log_path()).exists();

GameDataPathValidation {
path_exists: base.exists() && base.is_dir(),
player_log_found: base.join("Player.log").exists(),
player_log_found,
chat_logs_found: base.join("ChatLogs").is_dir(),
reports_found: base.join("Reports").is_dir(),
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/Help/HelpSetupTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
<div class="flex flex-col gap-2.5">
<SetupStep number="1" title="Set your Game Data path">
Go to <button class="text-accent-gold bg-transparent border-none cursor-pointer underline p-0 font-inherit text-inherit" @click="$emit('navigate', 'settings')">Settings &rarr; General</button>
and set the path to your Project: Gorgon installation folder.
This is the folder containing your <code class="text-accent-gold bg-surface-dark px-1.5 py-0.5 rounded-sm text-xs">Player.log</code> file. This should have been auto-detected during the setup, but in case that failed you can manually set it here.
and set the path to your Project: Gorgon data folder.
This is the folder containing <code class="text-accent-gold bg-surface-dark px-1.5 py-0.5 rounded-sm text-xs">ChatLogs</code> and <code class="text-accent-gold bg-surface-dark px-1.5 py-0.5 rounded-sm text-xs">Reports</code>. On Windows <code class="text-accent-gold bg-surface-dark px-1.5 py-0.5 rounded-sm text-xs">Player.log</code> lives here too; on macOS it is auto-detected from <code class="text-accent-gold bg-surface-dark px-1.5 py-0.5 rounded-sm text-xs">~/Library/Logs/</code>.
</SetupStep>
<SetupStep number="2" title="Enable Chat Logging in-game">
In Project Gorgon, open Settings &rarr; GUI and enable
Expand Down
109 changes: 99 additions & 10 deletions src/components/Settings/GeneralSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,49 @@
v-model="localGameDataPath"
@blur="handleGameDataPathInput"
@keyup.enter="handleGameDataPathInput"
placeholder="Path to Elder Game\Project Gorgon folder..."
placeholder="Path to Project Gorgon data folder..."
class="input flex-1" />
<button @click="browseGameDataFolder" class="btn btn-secondary whitespace-nowrap">
Browse
</button>
</div>
<p class="mt-2 text-text-muted text-xs leading-relaxed">
Default location: %APPDATA%\..\LocalLow\Elder Game\Project Gorgon\
<br />
This folder contains Player.log, ChatLogs, and Reports subfolders.
Auto-detection works on Windows.
Contains ChatLogs, Reports, and other game data.<br />
Windows: <code>%APPDATA%\..\LocalLow\Elder Game\Project Gorgon\</code><br />
macOS: <code>~/Library/Application Support/unity.Elder Game.Project Gorgon/</code>
</p>
</div>

<div>
<button @click="useDefaultPlayerLog" class="btn btn-secondary">
<div class="mb-4">
<label for="player-log-path" class="block text-text-secondary mb-2 text-sm">Player.log File</label>
<div class="flex gap-2">
<input
id="player-log-path"
v-model="localPlayerLogPath"
@blur="handlePlayerLogPathInput"
@keyup.enter="handlePlayerLogPathInput"
placeholder="Path to Player.log..."
class="input flex-1" />
<button @click="browsePlayerLogFile" class="btn btn-secondary whitespace-nowrap">
Browse
</button>
</div>
<p class="mt-2 text-text-muted text-xs leading-relaxed">
On macOS this is separate from the data folder:<br />
<code>~/Library/Logs/Elder Game/Project Gorgon/Player.log</code>
</p>
</div>

<div class="flex gap-2 mt-2">
<button @click="useDefaultGameDataPath" class="btn btn-secondary text-xs">
Use Default Game Data Path
</button>
<button @click="useDefaultPlayerLog" class="btn btn-secondary text-xs">
Use Default Player.log Location
</button>
<button @click="useDefaultsBoth" class="btn btn-secondary text-xs">
Reset Both to Defaults
</button>
</div>
</div>

Expand Down Expand Up @@ -149,11 +174,13 @@

<script setup lang="ts">
import { ref, computed, watch } from "vue";
import { invoke } from "@tauri-apps/api/core";
import { open } from "@tauri-apps/plugin-dialog";
import { useSettingsStore } from "../../stores/settingsStore";

const settingsStore = useSettingsStore();
const localGameDataPath = ref(settingsStore.settings.gameDataPath);
const localPlayerLogPath = ref(settingsStore.settings.logFilePath);
const autoTailChat = ref(settingsStore.settings.autoTailChat);
const autoTailPlayerLog = ref(settingsStore.settings.autoTailPlayerLog);
const excludeMaxEnchanted = ref(settingsStore.settings.excludeMaxEnchantedRecipes);
Expand Down Expand Up @@ -194,6 +221,13 @@ watch(
}
);

watch(
() => settingsStore.settings.logFilePath,
(newPath) => {
localPlayerLogPath.value = newPath;
}
);

watch(
() => settingsStore.settings.autoTailChat,
(val) => { autoTailChat.value = val; }
Expand Down Expand Up @@ -223,6 +257,7 @@ async function browseGameDataFolder() {
const selected = await open({
directory: true,
multiple: false,
defaultPath: settingsStore.settings.gameDataPath || undefined,
});
if (selected) {
localGameDataPath.value = selected;
Expand All @@ -234,9 +269,63 @@ function handleGameDataPathInput() {
settingsStore.updateGameDataPath(localGameDataPath.value);
}

function useDefaultPlayerLog() {
const playerLogPath = settingsStore.getPlayerLogPath();
settingsStore.updateLogFilePath(playerLogPath);
async function useDefaultGameDataPath() {
try {
const defaultPath = await invoke<string>("get_default_game_data_path_command");
if (defaultPath) {
localGameDataPath.value = defaultPath;
settingsStore.updateGameDataPath(defaultPath);
}
} catch (e) {
console.error("Failed to get default game data path:", e);
}
}

async function useDefaultPlayerLog() {
try {
const defaultPath = await invoke<string>("get_default_player_log_path_command");
if (defaultPath) {
localPlayerLogPath.value = defaultPath;
settingsStore.updateLogFilePath(defaultPath);
}
} catch (e) {
console.error("Failed to get default Player.log path:", e);
}
}

async function useDefaultsBoth() {
try {
const [dataPath, logPath] = await Promise.all([
invoke<string>("get_default_game_data_path_command"),
invoke<string>("get_default_player_log_path_command"),
]);
if (dataPath) {
localGameDataPath.value = dataPath;
settingsStore.updateGameDataPath(dataPath);
}
if (logPath) {
localPlayerLogPath.value = logPath;
settingsStore.updateLogFilePath(logPath);
}
} catch (e) {
console.error("Failed to reset both paths to defaults:", e);
}
}

function handlePlayerLogPathInput() {
settingsStore.updateLogFilePath(localPlayerLogPath.value);
}

async function browsePlayerLogFile() {
const selected = await open({
multiple: false,
defaultPath: settingsStore.settings.logFilePath || settingsStore.settings.gameDataPath || undefined,
filters: [{ name: "Player.log", extensions: ["log"] }],
});
if (selected) {
localPlayerLogPath.value = selected;
settingsStore.updateLogFilePath(selected);
}
}

function handleAutoTailChatToggle() {
Expand Down
9 changes: 5 additions & 4 deletions src/components/Startup/SetupPathStep.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@
<div class="card p-6">
<h2 class="text-lg text-text-primary mb-2">Locate Project Gorgon</h2>
<p class="text-text-muted text-sm mb-6">
Select the folder where Project Gorgon stores its data.
On Windows this is typically in AppData\LocalLow\Elder Game\Project Gorgon.
Select the folder where Project Gorgon stores its data (ChatLogs, Reports, Books).
On Windows: <code>AppData\LocalLow\Elder Game\Project Gorgon</code><br />
On macOS: <code>~/Library/Application Support/unity.Elder Game.Project Gorgon</code>
</p>

<div class="flex gap-2 mb-4">
<input
v-model="localPath"
@blur="onPathChange"
@keyup.enter="onPathChange"
placeholder="Path to Elder Game\Project Gorgon folder..."
placeholder="Path to Project Gorgon data folder..."
class="input flex-1" />
<button @click="browse" class="btn btn-secondary whitespace-nowrap">Browse</button>
</div>
Expand Down Expand Up @@ -62,7 +63,7 @@ async function onPathChange() {
}

async function browse() {
const selected = await open({ directory: true, multiple: false });
const selected = await open({ directory: true, multiple: false, defaultPath: localPath.value || undefined });
if (selected) {
localPath.value = selected;
await startupStore.validatePath(selected);
Expand Down
14 changes: 8 additions & 6 deletions src/stores/settingsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,10 +252,12 @@ export const useSettingsStore = defineStore("settings", () => {
}

async function updateGameDataPath(path: string) {
const oldPath = settings.value.gameDataPath;
settings.value.gameDataPath = path;
// Update log path if it was using the old game data path
if (settings.value.logFilePath.includes("Player.log")) {
settings.value.logFilePath = path + "\\Player.log";
// Update log path only if it was derived from the old game data path
// (Windows: same directory). On macOS the paths are independent.
if (oldPath && settings.value.logFilePath.startsWith(oldPath)) {
settings.value.logFilePath = path + settings.value.logFilePath.slice(oldPath.length);
}
await saveSettings(settings.value);
}
Expand All @@ -266,15 +268,15 @@ export const useSettingsStore = defineStore("settings", () => {
}

function getPlayerLogPath(): string {
return settings.value.gameDataPath + "\\Player.log";
return settings.value.gameDataPath + "/Player.log";
}

function getChatLogsPath(): string {
return settings.value.gameDataPath + "\\ChatLogs";
return settings.value.gameDataPath + "/ChatLogs";
}

function getReportsPath(): string {
return settings.value.gameDataPath + "\\Reports";
return settings.value.gameDataPath + "/Reports";
}

async function updateAutoPurgeEnabled(enabled: boolean) {
Expand Down