using ProcessWrapper; using Func; using System.Diagnostics; using System.Runtime.InteropServices; using System.IO; using Photino.NET; using System.Text.Json; namespace ModMenu { public class Program { public static int pid = 0; public static int OsType = 0; public static IProcessWrapper OsProcessWrapper = null; public static ulong cbase = 0; public static ulong cbaseCorrected = 0; public static Dictionary config = null; public static byte[] readBytes(uint address, uint length) { return OsProcessWrapper.ReadProcessMemory(pid, (ulong)(cbase + 0xE000000) + address - 0x10000000, length); } public static uint readUInt32(uint address) { return BitConverter.ToUInt32(OsProcessWrapper.ReadProcessMemory(pid, (ulong)(cbase + 0xE000000) + address - 0x10000000, 4).Reverse().ToArray(), 0); } public static float readFloat(uint address) { var b = OsProcessWrapper.ReadProcessMemory(pid, (ulong)(cbase + 0xE000000) + address - 0x10000000, 4); Array.Reverse(b, 0, b.Length); return BitConverter.ToSingle(b, 0); } public static void writeBytes(uint address, byte[] bytes) { OsProcessWrapper.WriteProcessMemory(pid, (ulong)(cbase + 0xE000000) + address - 0x10000000, bytes); } public static void writeUInt32(uint address, uint value) { byte[] b = BitConverter.GetBytes(value); Array.Reverse(b, 0, b.Length); OsProcessWrapper.WriteProcessMemory(pid, (ulong)(cbase + 0xE000000) + address - 0x10000000, b); } public static void writeInt16(uint address, Int16 value) { byte[] b = BitConverter.GetBytes(value); Array.Reverse(b, 0, b.Length); OsProcessWrapper.WriteProcessMemory(pid, (ulong)(cbase + 0xE000000) + address - 0x10000000, b); } public static void writeFloat(uint address, float value) { byte[] b = BitConverter.GetBytes(value); Array.Reverse(b, 0, b.Length); OsProcessWrapper.WriteProcessMemory(pid, (ulong)(cbase + 0xE000000) + address - 0x10000000, b); } public static string FindExecutable(string name) { // Not required for Mac/Windows if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return null; if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return null; var pathEnv = Environment.GetEnvironmentVariable("PATH"); if (string.IsNullOrEmpty(pathEnv)) return null; var paths = pathEnv.Split(':'); foreach (var path in paths) { var fullPath = Path.Combine(path, name); if (File.Exists(fullPath)) { return fullPath; } } return null; } public static bool IsWayland() { string runtimeDir = Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR"); if (string.IsNullOrEmpty(runtimeDir) || !Directory.Exists(runtimeDir)) return false; return Directory.EnumerateFiles(runtimeDir, "wayland-*").Any(); } [STAThread] public static void Main(string[] args) { Console.OutputEncoding = System.Text.Encoding.UTF8; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { OsType = 1; OsProcessWrapper = new LinuxProcessWrapper(); if (OsProcessWrapper.getuid() != 0) { var pkexecPath = FindExecutable("pkexec"); if (!string.IsNullOrEmpty(pkexecPath)) { string exePath = Process.GetCurrentProcess().MainModule.FileName; string display; string runtimeDir; string command = null; Console.WriteLine($"Wayland is: {IsWayland()}"); if (IsWayland()) { display = Environment.GetEnvironmentVariable("WAYLAND_DISPLAY"); runtimeDir = Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR"); command = $"--disable-internal-agent env XDG_RUNTIME_DIR={runtimeDir} WAYLAND_DISPLAY={display} \"{exePath}\""; } else { display = Environment.GetEnvironmentVariable("DISPLAY"); command = $"--disable-internal-agent env DISPLAY={display} \"{exePath}\""; } if (string.IsNullOrEmpty(command)) { Console.WriteLine("Please run as root..."); System.Environment.Exit(1); } var startInfo = new ProcessStartInfo { FileName = pkexecPath, Arguments = command, UseShellExecute = false }; try { using (Process elevatedProcess = Process.Start(startInfo)) { elevatedProcess?.WaitForExit(); Environment.Exit(elevatedProcess?.ExitCode ?? 0); }; } catch (Exception ex) { Console.WriteLine($"Elevation failed: {ex.Message}"); Environment.Exit(1); } } else { Console.WriteLine("Please run as root..."); System.Environment.Exit(1); } } } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { OsType = 2; OsProcessWrapper = new MacProcessWrapper(); if (OsProcessWrapper.getuid() != 0) { string basePath = AppContext.BaseDirectory; string rootHelperPath = Path.Combine(basePath, "root_helper"); if (Path.Exists(rootHelperPath)) { var startInfo = new ProcessStartInfo { FileName = rootHelperPath, UseShellExecute = false }; try { Process.Start(startInfo); Environment.Exit(0); } catch (Exception ex) { Console.WriteLine($"Elevation failed: {ex.Message}"); Environment.Exit(1); } } else { Console.WriteLine("Please run as root..."); System.Environment.Exit(1); } } } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { OsType = 3; OsProcessWrapper = new WindowsProcessWrapper(); } else { Console.WriteLine("Unsupported Operating System exiting..."); return; } // Root access needed past here for Linux/Mac string[] targetNames = { "cemu", "xapfish", ".cemu-wrapped", "cemu_release" }; Process targetProcess = Process.GetProcesses() .FirstOrDefault(p => targetNames.Any(name => p.ProcessName.Equals(name, StringComparison.OrdinalIgnoreCase))); if (targetProcess == null) { new PhotinoWindow() .SetSize(0, 0) .SetMinimized(true) .SetNotificationRegistrationId("8FDF1B15-3408-47A6-8EF5-2B0676B76277") .SetNotificationsEnabled(false) .RegisterWindowCreatedHandler((sender, e) => { var window = sender as PhotinoWindow; window.ShowMessage("Error", "Could not find Cemu process.", PhotinoDialogButtons.Ok, PhotinoDialogIcon.Error); window.Close(); System.Environment.Exit(0); }) .LoadRawString("") .WaitForClose(); return; } pid = targetProcess.Id; cbase = OsProcessWrapper.FindCemuBase(pid, 1308622848); Console.WriteLine($"Found target process: {targetProcess.ProcessName} (PID: {pid})"); if (cbase != 0) { Console.WriteLine("SplatTool v3.2.1 by CrafterPika"); Console.WriteLine("Special Thanks: Winterberry, Javi.ig and Tombuntu, KittenTM."); cbaseCorrected = (Program.cbase + 0xE000000) - 0x10000000; string indexPath; string iconPath; string configPath; if (OsType == 2) { indexPath = Path.Combine(AppContext.BaseDirectory, "..", "Resources", "modmenu", "index.html"); iconPath = Path.Combine(AppContext.BaseDirectory, "..", "Resources", "icon.ico"); configPath = Path.Combine(AppContext.BaseDirectory, "..", "Resources", "config.json"); } else { indexPath = Path.Combine(AppContext.BaseDirectory, "modmenu", "index.html"); iconPath = Path.Combine(AppContext.BaseDirectory, "icon.ico"); configPath = Path.Combine(AppContext.BaseDirectory, "config.json"); } string configJson = File.ReadAllText(configPath); config = JsonSerializer.Deserialize>(configJson); var window = new PhotinoWindow() .SetTitle("SplatTool Mod Menu") .SetNotificationRegistrationId("8FDF1B15-3408-47A6-8EF5-2B0676B76277") .SetNotificationsEnabled(false) .SetUseOsDefaultSize(false) .SetSize(900, 700) .Center() .RegisterWebMessageReceivedHandler((object sender, string message) => { var w = sender as PhotinoWindow; var data = JsonSerializer.Deserialize(message); if (data != null) { string result = httpMenu.RunCode(data); w.SendWebMessage(result); } }); if (File.Exists(iconPath)) { window.SetIconFile(iconPath); } window.Load(indexPath).WaitForClose(); } else { new PhotinoWindow() .SetSize(0, 0) .SetNotificationRegistrationId("8FDF1B15-3408-47A6-8EF5-2B0676B76277") .SetNotificationsEnabled(false) .SetMinimized(true) .RegisterWindowCreatedHandler((sender, e) => { var window = sender as PhotinoWindow; window.ShowMessage("Error", "Cemu base not found! Is Splatoon running?!", PhotinoDialogButtons.Ok, PhotinoDialogIcon.Error); window.Close(); System.Environment.Exit(0); }) .LoadRawString("") .WaitForClose(); return; } } } }