diff options
Diffstat (limited to 'Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/AmongUsModifier.cs')
-rw-r--r-- | Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/AmongUsModifier.cs | 247 |
1 files changed, 247 insertions, 0 deletions
diff --git a/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/AmongUsModifier.cs b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/AmongUsModifier.cs new file mode 100644 index 0000000..95f5524 --- /dev/null +++ b/Impostor-dev/src/Impostor.Patcher/Impostor.Patcher.Shared/AmongUsModifier.cs @@ -0,0 +1,247 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Gameloop.Vdf; +using Gameloop.Vdf.Linq; +using Impostor.Patcher.Shared.Events; +using Impostor.Patcher.Shared.Innersloth; +using ErrorEventArgs = Impostor.Patcher.Shared.Events.ErrorEventArgs; + +namespace Impostor.Patcher.Shared +{ + public class AmongUsModifier + { + private const uint AppId = 945360; + public const string DefaultRegionName = "Impostor"; + public const ushort DefaultPort = 22023; + + private readonly string _amongUsDir; + private readonly string _regionFile; + + public string RegionName { get; set; } = DefaultRegionName; + + public AmongUsModifier() + { + var appData = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "..", "LocalLow"); + + if (!Directory.Exists(appData)) + { + appData = FindProtonAppData(); + } + + if (appData == null) + return; + + _amongUsDir = Path.Combine(appData, "Innersloth", "Among Us"); + _regionFile = Path.Combine(_amongUsDir, "regionInfo.dat"); + } + + private string FindProtonAppData() + { + string steamApps; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + steamApps = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".steam", "steam", "steamapps"); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + steamApps = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support", "Steam", "steamapps"); + } + else + { + return null; + } + + if (!Directory.Exists(steamApps)) + return null; + + var libraries = new List<string> + { + steamApps + }; + + var vdf = Path.Combine(steamApps, "libraryfolders.vdf"); + if (File.Exists(vdf)) + { + var libraryFolders = VdfConvert.Deserialize(File.ReadAllText(vdf)); + + foreach (var libraryFolder in libraryFolders.Value.Children<VProperty>()) + { + if (!int.TryParse(libraryFolder.Key, out _)) + continue; + + libraries.Add(Path.Combine(libraryFolder.Value.Value<string>(), "steamapps")); + } + } + + foreach (var library in libraries) + { + var path = Path.Combine(library, "compatdata", AppId.ToString(), "pfx", "drive_c", "users", "steamuser", "AppData", "LocalLow"); + if (Directory.Exists(path)) + { + return path; + } + } + + return null; + } + + public async Task<bool> SaveIpAsync(string input) + { + // Filter out whitespace. + input = input.Trim(); + + // Split port from ip. + // Only IPv4 is supported so just do it simple. + var ip = string.Empty; + var port = DefaultPort; + + var parts = input.Split(':'); + if (parts.Length >= 1) + { + ip = parts[0]; + } + + if (parts.Length >= 2) + { + ushort.TryParse(parts[1], out port); + } + + // Check if a valid IP address was entered. + if (!IPAddress.TryParse(ip, out var ipAddress)) + { + // Attempt to resolve DNS. + try + { + var hostAddresses = await Dns.GetHostAddressesAsync(ip); + if (hostAddresses.Length == 0) + { + OnError("Invalid IP Address entered"); + return false; + } + + // Use first IPv4 result. + ipAddress = hostAddresses.First(x => x.AddressFamily == AddressFamily.InterNetwork); + } + catch (SocketException) + { + OnError("Failed to resolve hostname."); + return false; + } + } + + // Only IPv4. + if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6) + { + OnError("Invalid IP Address entered, only IPv4 is allowed."); + return false; + } + + return WriteIp(ipAddress, port); + } + + /// <summary> + /// Writes an IP Address to the Among Us region file. + /// </summary> + /// <param name="ipAddress">The IPv4 address to write.</param> + /// <param name="port"></param> + private bool WriteIp(IPAddress ipAddress, ushort port) + { + if (ipAddress == null || + ipAddress.AddressFamily != AddressFamily.InterNetwork) + { + throw new ArgumentException(nameof(ipAddress)); + } + + if (!Directory.Exists(_amongUsDir)) + { + OnError("Among Us directory was not found, is it installed? Try running it once."); + return false; + } + + using (var file = File.Open(_regionFile, FileMode.Create, FileAccess.Write)) + using (var writer = new BinaryWriter(file)) + { + var ip = ipAddress.ToString(); + var region = new RegionInfo(RegionName, ip, new[] + { + new ServerInfo($"{RegionName}-Master-1", ip, port) + }); + + region.Serialize(writer); + + OnSaved(ip, port); + return true; + } + } + + /// <summary> + /// Loads the existing region info from the Among Us. + /// </summary> + public bool TryLoadRegionInfo(out RegionInfo regionInfo) + { + regionInfo = null; + + if (!File.Exists(_regionFile)) + { + return false; + } + + using (var file = File.Open(_regionFile, FileMode.Open, FileAccess.Read)) + using (var reader = new BinaryReader(file)) + { + try + { + regionInfo = RegionInfo.Deserialize(reader); + return true; + } + catch (Exception exception) + { + OnError("Couldn't parse region info\n" + exception); + return false; + } + } + } + + /// <summary> + /// Loads the existing IP Address from the Among Us region file + /// if it was set by Impostor before. + /// </summary> + public bool TryLoadIp(out string ipAddress) + { + ipAddress = null; + + if (!TryLoadRegionInfo(out var regionInfo)) + { + return false; + } + + if ((regionInfo.Name == RegionName || regionInfo.Name == DefaultRegionName) && regionInfo.Servers.Count >= 1) + { + ipAddress = regionInfo.Servers.ElementAt(0).Ip; + return true; + } + + return false; + } + + private void OnError(string message) + { + Error?.Invoke(this, new ErrorEventArgs(message)); + } + + private void OnSaved(string ipAddress, ushort port) + { + Saved?.Invoke(this, new SavedEventArgs(ipAddress, port)); + } + + public event EventHandler<ErrorEventArgs> Error; + public event EventHandler<SavedEventArgs> Saved; + } +}
\ No newline at end of file |