// // This file is part of the Tremble package by Tiny Goose. // Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved. // using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif namespace TinyGoose.Tremble { public static class TrenchBroomUtil { private static string TrenchBroomPathFromPrefs { #if UNITY_EDITOR get => EditorPrefs.GetString("Tremble_TBPath", null); set => EditorPrefs.SetString("Tremble_TBPath", value); #else get => PlayerPrefs.GetString("Tremble_TBPath", null); set => PlayerPrefs.SetString("Tremble_TBPath", value); #endif } public static string TrenchBroomPath { get { // First look for a running Trenchbroom Process[] procs = Process.GetProcessesByName("trenchbroom"); if (procs.Length > 0 && procs[0].MainModule != null) { TrenchBroomPathFromPrefs = procs[0].MainModule.FileName; return procs[0].MainModule.FileName; } // Not running - try a path we previously knew about string path = TrenchBroomPathFromPrefs; if (path != null && File.Exists(path)) return path; return null; } private set => TrenchBroomPathFromPrefs = value; } public static bool IsTrenchBroomRunning { get { Process proc = Process.GetProcessesByName("trenchbroom").FirstOrDefault(); if (proc == null) return false; TrenchBroomPath = proc.MainModule.FileName; return true; } } public static bool DoesEnginePathExistInTrenchBroomPrefs() { string preferencesFile = Path.Combine(GetTrenchBroomConfigFolder(), "Preferences.json"); string key = $"Games/{TrembleConsts.GAME_NAME}/Path"; if (!File.Exists(preferencesFile)) return false; // Read in current config foreach (string line in File.ReadLines(preferencesFile)) { string trimmedLine = line.Trim(' ', ','); // Ignore braces and empty lines if (trimmedLine.IsNullOrEmpty() || trimmedLine is "{" or "}") continue; // Existing path? - patch it! if (trimmedLine.StartsWith($"\"{key}\"")) return true; } return false; } public static async Task WriteEnginePathIntoTrenchBroomPrefs() { // I didn't want to import a JSON library so - this is it. // Read each line into an array (ignore the braces) and remove the commas. // Find if there's a current path - if so, patch it up. // if not, add a new one at the end. string preferencesFile = Path.Combine(GetTrenchBroomConfigFolder(), "Preferences.json"); string key = $"Games/{TrembleConsts.GAME_NAME}/Path"; string projectFolderUnix = Directory.GetCurrentDirectory().Replace('\\', '/'); string entry = $"\"{key}\": \"{projectFolderUnix}\""; bool wasPatched = false; List configLines = new(); if (File.Exists(preferencesFile)) { // Read in current config foreach (string line in File.ReadLines(preferencesFile)) { string trimmedLine = line.Trim(' ', ','); // Ignore braces and empty lines if (trimmedLine.IsNullOrEmpty() || trimmedLine is "{" or "}") continue; // Existing path? - patch it! if (trimmedLine.StartsWith($"\"{key}\"")) { configLines.Add(entry); wasPatched = true; continue; } configLines.Add(trimmedLine); } } if (!wasPatched) { // We didn't find the game - append to the end (before close brace) configLines.Add(entry); } // Write out new config for (int lineIdx = 0; lineIdx < configLines.Count; lineIdx++) { // Indent configLines[lineIdx] = $" {configLines[lineIdx]}"; // Add commas (not on the last one!) if (lineIdx < configLines.Count - 1) { configLines[lineIdx] += ","; } } configLines.Insert(0, "{"); configLines.Add("}"); DirectoryUtil.CreateAllDirectories(preferencesFile); await File.WriteAllLinesAsync(preferencesFile, configLines); return !wasPatched; } private static string GetTrenchBroomConfigFolder() => Application.platform switch { RuntimePlatform.OSXEditor or RuntimePlatform.OSXPlayer => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support", "TrenchBroom"), RuntimePlatform.WindowsEditor or RuntimePlatform.WindowsPlayer => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "TrenchBroom"), RuntimePlatform.LinuxEditor or RuntimePlatform.LinuxPlayer => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".TrenchBroom"), _ => throw new ArgumentOutOfRangeException(nameof(Application.platform), "Unknown platform!") }; public static string GetGameFolder() => Path.Combine(GetTrenchBroomConfigFolder(), "games", "Unity", Application.productName); public static void OpenWithTrenchBroom(string mapFile) { switch (Application.platform) { case RuntimePlatform.OSXEditor: case RuntimePlatform.OSXPlayer: RunCommand($"open -a TrenchBroom '{mapFile}'"); break; case RuntimePlatform.WindowsEditor: case RuntimePlatform.WindowsPlayer: // Sad. On Windows, TB is mostly portable so we may have to just show the file in Explorer, lol if (TrenchBroomPath != null) { RunCommand($"\"{TrenchBroomPath}\" \"{mapFile}\"", useShell: false); } else { #if UNITY_EDITOR EditorUtility.DisplayDialog( title: "Where's TrenchBroom?", message: "Tremble could not find TrenchBroom. Ensure TrenchBroom is installed and " + "running, and Tremble will make a note of where it's installed for next time." + " For now, I'll show your map in Explorer.", "Okay"); #endif RunCommand($"explorer /select, \"{mapFile}\""); } break; case RuntimePlatform.LinuxEditor: case RuntimePlatform.LinuxPlayer: RunCommand($"/usr/bin/trenchbroom '{mapFile}'"); break; } } private static void RunCommand(string command, bool useShell = true) { command.Split(' ', out string exe, out string args); #if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN // Windows Command Prompt if (useShell) { args = $"/c \"{exe} {args}\""; exe = "cmd.exe"; } #else // Under macOS and Linux we assume /bin/sh is a thing if (useShell) { args = $"-c \"{exe} {args}\""; exe = "/bin/sh"; } #endif Process.Start(new ProcessStartInfo(exe, args) { WorkingDirectory = Directory.GetCurrentDirectory() }); } } }