SplatTool-public/Program.cs

253 lines
11 KiB
C#
Raw Permalink Normal View History

2026-04-23 17:08:47 +02:00
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<string, string> 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;
2026-04-23 17:08:47 +02:00
return Directory.EnumerateFiles(runtimeDir, "wayland-*").Any();
2026-04-23 17:08:47 +02:00
}
[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("<html></html>")
.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");
2026-04-23 17:08:47 +02:00
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<Dictionary<string, string>>(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<ModMenuRequest>(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("<html></html>")
.WaitForClose();
return;
}
}
}
}