commit ec6778b2be732889eeddb848e6782241fe19897b Author: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Tue Jan 25 22:20:53 2022 +0100 Initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4d13c54 --- /dev/null +++ b/.gitignore @@ -0,0 +1,334 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ diff --git a/HyperbolicDowloader.sln b/HyperbolicDowloader.sln new file mode 100644 index 0000000..402e0f8 --- /dev/null +++ b/HyperbolicDowloader.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32112.339 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HyperbolicDowloader", "HyperbolicDowloader\HyperbolicDowloader.csproj", "{231393D8-DDBF-4F2F-90F3-BEB96903BE0B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {231393D8-DDBF-4F2F-90F3-BEB96903BE0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {231393D8-DDBF-4F2F-90F3-BEB96903BE0B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {231393D8-DDBF-4F2F-90F3-BEB96903BE0B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {231393D8-DDBF-4F2F-90F3-BEB96903BE0B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6B7C6636-9A71-4594-AEDF-74B1057EC7AF} + EndGlobalSection +EndGlobal diff --git a/HyperbolicDowloader/FileProcessing/FileCompressor.cs b/HyperbolicDowloader/FileProcessing/FileCompressor.cs new file mode 100644 index 0000000..0a4eae4 --- /dev/null +++ b/HyperbolicDowloader/FileProcessing/FileCompressor.cs @@ -0,0 +1,22 @@ +using System.IO.Compression; + +namespace HyperbolicDowloader.FileProcessing; + +internal class FileCompressor +{ + private static void CompressFile(string inputFilePath, string compressedFilePath) + { + using FileStream originalFileStream = File.Open(inputFilePath, FileMode.Open); + using FileStream compressedFileStream = File.Create(compressedFilePath); + using GZipStream? compressor = new GZipStream(compressedFileStream, CompressionMode.Compress); + originalFileStream.CopyTo(compressor); + } + + private static void DecompressFile(string compressedFilePath, string outputFilePath) + { + using FileStream compressedFileStream = File.Open(compressedFilePath, FileMode.Open); + using FileStream outputFileStream = File.Create(outputFilePath); + using GZipStream? decompressor = new GZipStream(compressedFileStream, CompressionMode.Decompress); + decompressor.CopyTo(outputFileStream); + } +} \ No newline at end of file diff --git a/HyperbolicDowloader/FileProcessing/FileValidator.cs b/HyperbolicDowloader/FileProcessing/FileValidator.cs new file mode 100644 index 0000000..a703cba --- /dev/null +++ b/HyperbolicDowloader/FileProcessing/FileValidator.cs @@ -0,0 +1,19 @@ +using System.Security.Cryptography; + +namespace HyperbolicDowloader.FileProcessing; + +internal class FileValidator +{ + public static async Task CalculateHashAsync(string filePath) + { + using SHA512 sha = SHA512.Create(); + using FileStream? stream = File.OpenRead(filePath); + byte[]? hash = await sha.ComputeHashAsync(stream); + return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); + } + + public static async Task ValidateHashAsync(string filePath, string hash) + { + return await CalculateHashAsync(filePath) == hash; + } +} \ No newline at end of file diff --git a/HyperbolicDowloader/HyperbolicDowloader.csproj b/HyperbolicDowloader/HyperbolicDowloader.csproj new file mode 100644 index 0000000..6246172 --- /dev/null +++ b/HyperbolicDowloader/HyperbolicDowloader.csproj @@ -0,0 +1,15 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + diff --git a/HyperbolicDowloader/Networking/BroadcastClient.cs b/HyperbolicDowloader/Networking/BroadcastClient.cs new file mode 100644 index 0000000..c707f5a --- /dev/null +++ b/HyperbolicDowloader/Networking/BroadcastClient.cs @@ -0,0 +1,75 @@ +using Stone_Red_Utilities.ConsoleExtentions; + +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Text; + +namespace HyperbolicDowloader.Networking; + +internal class BroadcastClient +{ + public event EventHandler? OnBroadcastRecived; + + public bool IsListening { get; private set; } = false; + + private UdpClient? udpListener; + + public void Send(int port, string message) + { + Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + + IPAddress? ip4Address = NetworkUtilities.GetIP4Adress(); + + if (ip4Address is null) + { + ConsoleExt.WriteLine("Could not find suitable network adapter!", ConsoleColor.Red); + return; + } + + UnicastIPAddressInformation? addressInformation = NetworkUtilities.GetUnicastIPAddressInformation(ip4Address); + + if (addressInformation is null) + { + ConsoleExt.WriteLine("Could not find suitable network adapter!", ConsoleColor.Red); + return; + } + + IPAddress broadcast = NetworkUtilities.GetBroadcastAddress(addressInformation); + + byte[] sendbuf = Encoding.ASCII.GetBytes(message); + IPEndPoint ep = new IPEndPoint(broadcast, port); + + s.SendTo(sendbuf, ep); + } + + public void StartListening(int port) + { + if (IsListening == true) + { + throw new InvalidOperationException("Already listening!"); + } + + IsListening = true; + + udpListener = new UdpClient(port); + IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, port); + + Task.Run(() => + { + while (IsListening) + { + byte[] bytes = udpListener.Receive(ref groupEP); + string message = Encoding.ASCII.GetString(bytes, 0, bytes.Length); + + OnBroadcastRecived?.Invoke(this, new BroadcastRecivedEventArgs(groupEP, message)); + } + }); + } + + public void StopListening() + { + udpListener?.Close(); + IsListening = false; + } +} \ No newline at end of file diff --git a/HyperbolicDowloader/Networking/BroadcastRecivedEventArgs.cs b/HyperbolicDowloader/Networking/BroadcastRecivedEventArgs.cs new file mode 100644 index 0000000..bca6b2d --- /dev/null +++ b/HyperbolicDowloader/Networking/BroadcastRecivedEventArgs.cs @@ -0,0 +1,15 @@ +using System.Net; + +namespace HyperbolicDowloader.Networking; + +internal class BroadcastRecivedEventArgs : EventArgs +{ + public BroadcastRecivedEventArgs(IPEndPoint iPEndPoint, string message) + { + IPEndPoint = iPEndPoint; + Message = message; + } + + public IPEndPoint IPEndPoint { get; } + public string Message { get; } +} \ No newline at end of file diff --git a/HyperbolicDowloader/Networking/DataContainer.cs b/HyperbolicDowloader/Networking/DataContainer.cs new file mode 100644 index 0000000..e2227a8 --- /dev/null +++ b/HyperbolicDowloader/Networking/DataContainer.cs @@ -0,0 +1,14 @@ +namespace HyperbolicDowloader +{ + internal class DataContainer + { + public string EventName { get; set; } + public string JsonData { get; set; } + + public DataContainer(string eventName, string jsonData) + { + JsonData = jsonData; + EventName = eventName; + } + } +} \ No newline at end of file diff --git a/HyperbolicDowloader/Networking/HostsManager.cs b/HyperbolicDowloader/Networking/HostsManager.cs new file mode 100644 index 0000000..8dcaae0 --- /dev/null +++ b/HyperbolicDowloader/Networking/HostsManager.cs @@ -0,0 +1,90 @@ +using HyperbolicDowloader.Networking; + +using Stone_Red_Utilities.ConsoleExtentions; + +using System.Net.Sockets; +using System.Text.Json; + +namespace HyperbolicDowloader; + +internal class HostsManager +{ + private readonly List hosts = new List(); + public int Count => hosts.Count; + + public void AddRange(IEnumerable hosts) + { + foreach (NetworkSocket host in hosts) + { + if (!this.hosts.Any(x => x.IPAddress == host.IPAddress && x.Port == host.Port)) + { + this.hosts.Add(host); + } + } + + SaveHosts(); + } + + public void Add(NetworkSocket host) + { + if (!hosts.Any(x => x.IPAddress == host.IPAddress && x.Port == host.Port)) + { + hosts.Add(host); + } + SaveHosts(); + } + + public void Remove(NetworkSocket host) + { + hosts.RemoveAll(x => x.IPAddress == host.IPAddress && x.Port == host.Port); + SaveHosts(); + } + + public void RemoveInactiveHosts() + { + List hostsToRemove = new List(); + foreach (NetworkSocket host in hosts) + { + ConsoleExt.Write($"{host.IPAddress}:{host.Port} > ???", ConsoleColor.DarkYellow); + using TcpClient tcpClient = new TcpClient(); + + try + { + tcpClient.ConnectAsync(host.IPAddress, host.Port).Wait(1000); + Console.CursorLeft = 0; + if (tcpClient.Connected) + { + ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Active", ConsoleColor.Green); + } + else + { + hostsToRemove.Add(host); + ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Inactive", ConsoleColor.Red); + } + } + catch + { + hostsToRemove.Add(host); + Console.CursorLeft = 0; + ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Inactive", ConsoleColor.Red); + } + } + + foreach (NetworkSocket host in hostsToRemove) + { + hosts.Remove(host); + } + + SaveHosts(); + } + + public List ToList() + { + return hosts.ToList(); + } + + private void SaveHosts() + { + File.WriteAllText(Program.HostsFilePath, JsonSerializer.Serialize(hosts)); + } +} \ No newline at end of file diff --git a/HyperbolicDowloader/Networking/MessageRecivedEventArgs.cs b/HyperbolicDowloader/Networking/MessageRecivedEventArgs.cs new file mode 100644 index 0000000..bb130ae --- /dev/null +++ b/HyperbolicDowloader/Networking/MessageRecivedEventArgs.cs @@ -0,0 +1,34 @@ +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Text.Json; + +namespace HyperbolicDowloader +{ + internal class MessageRecivedEventArgs : EventArgs + { + private readonly NetworkStream networkStream; + + public MessageRecivedEventArgs(NetworkStream networkStream, IPAddress ipAddress, T data) + { + this.networkStream = networkStream; + Data = data; + IpAddress = ipAddress; + } + + public T Data { get; set; } + + public IPAddress IpAddress { get; set; } + + public async Task SendResponseAsync(object response) + { + byte[] bytesToSend = Encoding.ASCII.GetBytes(JsonSerializer.Serialize(response)); + await networkStream.WriteAsync(bytesToSend); + } + + public void SendResponse(object response) + { + SendResponseAsync(response).GetAwaiter().GetResult(); + } + } +} \ No newline at end of file diff --git a/HyperbolicDowloader/Networking/NetworkClient.cs b/HyperbolicDowloader/Networking/NetworkClient.cs new file mode 100644 index 0000000..2615392 --- /dev/null +++ b/HyperbolicDowloader/Networking/NetworkClient.cs @@ -0,0 +1,155 @@ +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Text.Json; + +namespace HyperbolicDowloader +{ + internal class NetworkClient + { + public bool IsListening { get; private set; } = false; + + private TcpListener? tcpListener; + private readonly Dictionary events = new(); + + public NetworkClient() + { + } + + public static async Task SendAsync(IPAddress remoteIp, int remotePort, string eventName, object data) + { + if (remoteIp is null) + { + throw new ArgumentNullException(nameof(remoteIp)); + } + + TcpClient client = new TcpClient(); + + await client.ConnectAsync(remoteIp, remotePort); + + NetworkStream nwStream = client.GetStream(); + + string stringData = JsonSerializer.Serialize(new DataContainer(eventName, JsonSerializer.Serialize(data))); + byte[] bytesToSend = Encoding.ASCII.GetBytes(stringData); + + await nwStream.WriteAsync(bytesToSend); + + byte[] bytesToRead = new byte[client.ReceiveBufferSize]; + int bytesRead = await nwStream.ReadAsync(bytesToRead.AsMemory(0, client.ReceiveBufferSize)); + string response = Encoding.ASCII.GetString(bytesToRead, 0, bytesRead); + + client.Close(); + + if (string.IsNullOrWhiteSpace(response)) + { + return default; + } + else + { + return JsonSerializer.Deserialize(response); + } + } + + public static async Task SendAsync(IPAddress remoteIp, int remotePort, string eventName, object data) + { + if (remoteIp is null) + { + throw new ArgumentNullException(nameof(remoteIp)); + } + + TcpClient client = new TcpClient(); + + await client.ConnectAsync(remoteIp, remotePort); + + NetworkStream nwStream = client.GetStream(); + + string stringData = JsonSerializer.Serialize(new DataContainer(eventName, JsonSerializer.Serialize(data))); + byte[] bytesToSend = Encoding.ASCII.GetBytes(stringData); + + await nwStream.WriteAsync(bytesToSend); + + client.Close(); + } + + public static T? Send(IPAddress remoteIp, int remotePort, string eventName, object data) + { + return SendAsync(remoteIp, remotePort, eventName, data).GetAwaiter().GetResult(); + } + + public static void Send(IPAddress remoteIp, int remotePort, string eventName, object data) + { + SendAsync(remoteIp, remotePort, eventName, data).GetAwaiter().GetResult(); + } + + public void StartListening(int port) + { + if (IsListening == true) + { + throw new InvalidOperationException("Already listening!"); + } + + tcpListener = new TcpListener(IPAddress.Any, port); + + tcpListener.Start(); + IsListening = true; + + Task.Run(() => + { + while (IsListening) + { + try + { + using TcpClient client = tcpListener.AcceptTcpClient(); + NetworkStream nwStream = client.GetStream(); + byte[] buffer = new byte[client.ReceiveBufferSize]; + + int bytesRead = nwStream.Read(buffer, 0, client.ReceiveBufferSize); + + string dataReceived = Encoding.ASCII.GetString(buffer, 0, bytesRead); + + if (string.IsNullOrWhiteSpace(dataReceived)) + { + continue; + } + + DataContainer? dataContainer = JsonSerializer.Deserialize(dataReceived); + + if (dataContainer is not null && events.ContainsKey(dataContainer.EventName)) + { + (Type type, Delegate method) = events[dataContainer.EventName]; + + Type eventArgsType = typeof(MessageRecivedEventArgs<>).MakeGenericType(type); + + object? eventArgs = Activator.CreateInstance( + eventArgsType, + nwStream, + (client.Client.RemoteEndPoint as IPEndPoint)?.Address, + JsonSerializer.Deserialize(dataContainer.JsonData, type)); + + method?.DynamicInvoke(this, eventArgs); + } + } + catch (SocketException ex) + { + if (ex.SocketErrorCode != SocketError.Interrupted) + { + throw ex; + } + } + } + tcpListener.Stop(); + }); + } + + public void StopListening() + { + tcpListener?.Stop(); + IsListening = false; + } + + public void ListenTo(string eventName, EventHandler> eventHandler) + { + events.Add(eventName, (typeof(T), eventHandler)); + } + } +} \ No newline at end of file diff --git a/HyperbolicDowloader/Networking/NetworkSocket.cs b/HyperbolicDowloader/Networking/NetworkSocket.cs new file mode 100644 index 0000000..1f96f54 --- /dev/null +++ b/HyperbolicDowloader/Networking/NetworkSocket.cs @@ -0,0 +1,13 @@ +namespace HyperbolicDowloader.Networking; + +internal class NetworkSocket +{ + public NetworkSocket(string ipAddress, int port) + { + IPAddress = ipAddress; + Port = port; + } + + public string IPAddress { get; set; } + public int Port { get; set; } +} \ No newline at end of file diff --git a/HyperbolicDowloader/Networking/NetworkUtilities.cs b/HyperbolicDowloader/Networking/NetworkUtilities.cs new file mode 100644 index 0000000..7d6dab4 --- /dev/null +++ b/HyperbolicDowloader/Networking/NetworkUtilities.cs @@ -0,0 +1,49 @@ +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; + +namespace HyperbolicDowloader.Networking; + +internal class NetworkUtilities +{ + public static UnicastIPAddressInformation? GetUnicastIPAddressInformation(IPAddress address) + { + foreach (NetworkInterface adapter in NetworkInterface.GetAllNetworkInterfaces()) + { + foreach (UnicastIPAddressInformation unicastIPAddressInformation in adapter.GetIPProperties().UnicastAddresses) + { + if (unicastIPAddressInformation.Address.AddressFamily == AddressFamily.InterNetwork) + { + if (address.Equals(unicastIPAddressInformation.Address)) + { + return unicastIPAddressInformation; + } + } + } + } + return null; + } + + public static IPAddress? GetIP4Adress() + { + using Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, 0); + socket.Connect("8.8.8.8", 65530); + IPEndPoint? endPoint = socket.LocalEndPoint as IPEndPoint; + + return endPoint?.Address; + } + + public static IPAddress GetBroadcastAddress(UnicastIPAddressInformation unicastAddress) + { + return GetBroadcastAddress(unicastAddress.Address, unicastAddress.IPv4Mask); + } + + public static IPAddress GetBroadcastAddress(IPAddress address, IPAddress mask) + { + uint ipAddress = BitConverter.ToUInt32(address.GetAddressBytes(), 0); + uint ipMaskV4 = BitConverter.ToUInt32(mask.GetAddressBytes(), 0); + uint broadCastIpAddress = ipAddress | ~ipMaskV4; + + return new IPAddress(BitConverter.GetBytes(broadCastIpAddress)); + } +} \ No newline at end of file diff --git a/HyperbolicDowloader/Program.cs b/HyperbolicDowloader/Program.cs new file mode 100644 index 0000000..f427416 --- /dev/null +++ b/HyperbolicDowloader/Program.cs @@ -0,0 +1,230 @@ + +using HyperbolicDowloader.Networking; + +using Open.Nat; + +using Stone_Red_Utilities.ConsoleExtentions; + +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Text.Json; + +namespace HyperbolicDowloader; + +internal class Program +{ + private const int BroadcastPort = 2155; + + public const string HostsFilePath = "Hosts.json"; + + private static int publicPort; + private static readonly int privatePort = 3055; + private static NatDevice? device; + private static Mapping? portMapping; + private static readonly HostsManager hosts = new(); + private static readonly NetworkClient networkClient = new(); + private static readonly Random random = new Random(); + + private static async Task Main() + { + Console.CancelKeyPress += Console_CancelKeyPress; + + if (File.Exists(HostsFilePath)) + { + string hostsJson = await File.ReadAllTextAsync(HostsFilePath); + hosts.AddRange(JsonSerializer.Deserialize>(hostsJson) ?? new()); + } + + Console.WriteLine("Searching for a UPnP/NAT-PMP device..."); + _ = await OpenPorts(); + + ConsoleExt.WriteLine($"The private IP Address is: {NetworkUtilities.GetIP4Adress()} ", ConsoleColor.Green); + ConsoleExt.WriteLine($"The private port is: {privatePort}", ConsoleColor.Green); + + Console.WriteLine("Starting TCP listener..."); + + try + { + networkClient.ListenTo("GetHostsList", GetHostList); + networkClient.ListenTo>("DiscoverAnswer", DiscoverAnswer); + networkClient.StartListening(privatePort); + } + catch (SocketException ex) + { + Console.WriteLine($"An error occurred while starting the TCP listener! Error message: {ex.Message}"); + return; + } + + BroadcastClient broadcastClient = new BroadcastClient(); + + Console.WriteLine("Running local discovery routine..."); + broadcastClient.Send(BroadcastPort, privatePort.ToString()); + await Task.Delay(5000); + + if (hosts.Count > 0) + { + Console.WriteLine("Checking if hosts are active..."); + hosts.RemoveInactiveHosts(); + } + + if (hosts.Count == 0) + { + hosts.AddRange(await Setup.ConfigureHost()); + Console.WriteLine("Checking if hosts are active..."); + hosts.RemoveInactiveHosts(); + } + + Console.WriteLine($"{hosts.Count} active host(s)."); + Console.WriteLine("Starting broadcast listener..."); + broadcastClient.StartListening(BroadcastPort); + broadcastClient.OnBroadcastRecived += BroadcastClient_OnBroadcastRecived; + ConsoleExt.WriteLine("Done", ConsoleColor.Green); + await Task.Delay(-1); + } + + private static async void BroadcastClient_OnBroadcastRecived(object? sender, BroadcastRecivedEventArgs recivedEventArgs) + { + Debug.WriteLine($"Received broadcast \"{recivedEventArgs.Message}\" from {recivedEventArgs.IPEndPoint.Address}"); + List hostsToSend = hosts.ToList(); + + NetworkSocket? localSocket = GetLocalSocket(); + + if (localSocket is null) + { + return; + } + + hostsToSend.RemoveAll(x => x.IPAddress == recivedEventArgs.IPEndPoint.Address.ToString()); + hostsToSend.Add(localSocket); + + bool success = int.TryParse(recivedEventArgs.Message, out int remotePort); + + if (success) + { + hosts.Add(new NetworkSocket(recivedEventArgs.IPEndPoint.Address.ToString(), remotePort)); + try + { + await NetworkClient.SendAsync(recivedEventArgs.IPEndPoint.Address, remotePort, "DiscoverAnswer", hostsToSend); + } + catch (SocketException ex) + { + Debug.WriteLine(ex); + } + } + } + + private static void DiscoverAnswer(object? sender, MessageRecivedEventArgs> recivedEventArgs) + { + Console.WriteLine($"Received answer from {recivedEventArgs.IpAddress}. Returned {recivedEventArgs.Data.Count} host(s)."); + hosts.AddRange(recivedEventArgs.Data); + } + + private static async void GetHostList(object? sender, MessageRecivedEventArgs recivedEventArgs) + { + List hostsToSend = hosts.ToList(); + + NetworkSocket? localSocket = GetLocalSocket(); + + if (localSocket is null) + { + return; + } + + hostsToSend.RemoveAll(x => x.IPAddress == recivedEventArgs.IpAddress.ToString()); + hostsToSend.Add(localSocket); + + if (recivedEventArgs.Data.Port != 0) + { + hosts.Add(recivedEventArgs.Data); + } + + await recivedEventArgs.SendResponseAsync(hostsToSend); + } + + public static async Task OpenPorts() + { + try + { + publicPort = random.Next(1000, 6000); + + NatDiscoverer? discoverer = new NatDiscoverer(); + device = await discoverer.DiscoverDeviceAsync(); + + IPAddress? ip = await device.GetExternalIPAsync(); + ConsoleExt.WriteLine($"The public IP Address is: {ip} ", ConsoleColor.Green); + + portMapping = new Mapping(Protocol.Tcp, privatePort, publicPort, "HyperbolicDowloader"); + + await device.CreatePortMapAsync(portMapping); + + ConsoleExt.WriteLine($"The public port is: {publicPort}", ConsoleColor.Green); + return true; + } + catch (NatDeviceNotFoundException) + { + ConsoleExt.WriteLine($"Could not find a UPnP or NAT-PMP device!", ConsoleColor.Red); + return false; + } + catch (MappingException ex) + { + ConsoleExt.WriteLine($"An error occurred while mapping the private port ({privatePort}) to the public port ({publicPort})! Error message: {ex.Message}", ConsoleColor.Red); + return false; + } + } + + private static void ClosePorts() + { + Console.WriteLine("Closing ports..."); + if (device is not null) + { + try + { + IEnumerable? mappings = device.GetAllMappingsAsync().GetAwaiter().GetResult(); + foreach (Mapping? mapping in mappings) + { + if (mapping.Description.Contains("HyperbolicDowloader") && mapping.PrivateIP.ToString() == portMapping?.PrivateIP.ToString()) + { + device.DeletePortMapAsync(mapping).GetAwaiter().GetResult(); + } + } + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + } + + ConsoleExt.WriteLine("Ports closed!", ConsoleColor.DarkYellow); + Environment.Exit(0); + } + + private static void Console_CancelKeyPress(object? sender, ConsoleCancelEventArgs e) + { + ClosePorts(); + } + + public static NetworkSocket? GetLocalSocket() + { + int port = publicPort; + string? ipAddress = null; + + if (device is not null) + { + ipAddress = (device.GetExternalIPAsync()).GetAwaiter().GetResult()?.ToString(); + } + + if (ipAddress is null || ipAddress == "0.0.0.0") + { + ipAddress = NetworkUtilities.GetIP4Adress()?.ToString(); + port = privatePort; + } + + if (ipAddress is null) + { + return null; + } + + return new NetworkSocket(ipAddress, port); + } +} \ No newline at end of file diff --git a/HyperbolicDowloader/Setup.cs b/HyperbolicDowloader/Setup.cs new file mode 100644 index 0000000..406b474 --- /dev/null +++ b/HyperbolicDowloader/Setup.cs @@ -0,0 +1,58 @@ +using HyperbolicDowloader.Networking; + +using Stone_Red_Utilities.ConsoleExtentions; + +using System.Net; +using System.Net.Sockets; + +namespace HyperbolicDowloader; + +internal static class Setup +{ + public static async Task> ConfigureHost() + { + ConsoleExt.WriteLine("No active hosts found!", ConsoleColor.Red); + do + { + Console.WriteLine(); + Console.Write("Please enter an IP address manually: "); + string? ipAddressInput = Console.ReadLine(); + + Console.Write("Please enter an port number manually: "); + string? portInput = Console.ReadLine(); + + _ = int.TryParse(portInput, out int port); + if (port < 1000 || port >= 6000) + { + ConsoleExt.WriteLine("Invalid port number!", ConsoleColor.Red); + } + else if (IPAddress.TryParse(ipAddressInput, out IPAddress? ipAddress)) + { + try + { + Console.WriteLine("Waiting for response..."); + NetworkSocket? localSocket = Program.GetLocalSocket() ?? new NetworkSocket("0.0.0.0", 0); + List? recivedHosts = await NetworkClient.SendAsync>(ipAddress, port, "GetHostsList", localSocket); + + if (recivedHosts is not null) + { + ConsoleExt.WriteLine($"Success! Added {recivedHosts.Count} new host(s).", ConsoleColor.Green); + return recivedHosts; + } + else + { + ConsoleExt.WriteLine($"Invalid response!", ConsoleColor.Red); + } + } + catch (SocketException ex) + { + ConsoleExt.WriteLine($"Invalid host! Error message: {ex.Message}", ConsoleColor.Red); + } + } + else + { + ConsoleExt.WriteLine("Invalid IP address!", ConsoleColor.Red); + } + } while (true); + } +} \ No newline at end of file