From 4e897d3031abe79be0a2aa065d4f86d4d8fc3883 Mon Sep 17 00:00:00 2001 From: Timerix22 Date: Sun, 13 Mar 2022 17:13:47 +0300 Subject: [PATCH] added minecraft launcher files --- .gitignore | 365 ++++++++++++ minecraft-launcher-client/App.config | 6 + minecraft-launcher-client/InternalServer.cs | 71 +++ minecraft-launcher-client/Launcher.cs | 529 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 35 ++ minecraft-launcher-client/gui/exit.gui | 29 + minecraft-launcher-client/gui/login.gui | 29 + minecraft-launcher-client/gui/settings.gui | 29 + .../gui/test_label.colormap | 0 .../gui/test_label.textmap | 3 + minecraft-launcher-client/gui/window.dtsod | 16 + .../launcher-client.csproj | 79 +++ minecraft-launcher-client/launcher.dtsod | 4 + minecraft-launcher-client/logo-D.ico | Bin 0 -> 38078 bytes minecraft-launcher-server/App.config | 6 + .../Properties/AssemblyInfo.cs | 35 ++ minecraft-launcher-server/Server.cs | 218 ++++++++ .../launcher-server.csproj | 59 ++ .../launcher-server.dtsod | 2 + minecraft-launcher.sln | 51 ++ 20 files changed, 1566 insertions(+) create mode 100644 .gitignore create mode 100644 minecraft-launcher-client/App.config create mode 100644 minecraft-launcher-client/InternalServer.cs create mode 100644 minecraft-launcher-client/Launcher.cs create mode 100644 minecraft-launcher-client/Properties/AssemblyInfo.cs create mode 100644 minecraft-launcher-client/gui/exit.gui create mode 100644 minecraft-launcher-client/gui/login.gui create mode 100644 minecraft-launcher-client/gui/settings.gui create mode 100644 minecraft-launcher-client/gui/test_label.colormap create mode 100644 minecraft-launcher-client/gui/test_label.textmap create mode 100644 minecraft-launcher-client/gui/window.dtsod create mode 100644 minecraft-launcher-client/launcher-client.csproj create mode 100644 minecraft-launcher-client/launcher.dtsod create mode 100644 minecraft-launcher-client/logo-D.ico create mode 100644 minecraft-launcher-server/App.config create mode 100644 minecraft-launcher-server/Properties/AssemblyInfo.cs create mode 100644 minecraft-launcher-server/Server.cs create mode 100644 minecraft-launcher-server/launcher-server.csproj create mode 100644 minecraft-launcher-server/launcher-server.dtsod create mode 100644 minecraft-launcher.sln diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c65ebad --- /dev/null +++ b/.gitignore @@ -0,0 +1,365 @@ +## 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 + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +.vshistory/ +# 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 +nunit-*.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/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# 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 +*.editorconfig + +# 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 + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# 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 +# NuGet Symbol Packages +*.snupkg +# 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 +*.appxbundle +*.appxupload + +# 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 +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# 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/ + +# 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/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd diff --git a/minecraft-launcher-client/App.config b/minecraft-launcher-client/App.config new file mode 100644 index 0000000..3916e0e --- /dev/null +++ b/minecraft-launcher-client/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/minecraft-launcher-client/InternalServer.cs b/minecraft-launcher-client/InternalServer.cs new file mode 100644 index 0000000..ec8ba8b --- /dev/null +++ b/minecraft-launcher-client/InternalServer.cs @@ -0,0 +1,71 @@ +using DTLib; +using DTLib.Network; +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Linq; + +namespace launcher_client +{ + class InternalServer + { + Socket internalServerSocket; + Thread internalServerThread; + public InternalServer() + { + internalServerSocket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + internalServerThread = new(() => Start()); + internalServerThread.Start(); + } + + void Start() + { + internalServerSocket.Bind(new IPEndPoint(IPAddress.Loopback, 38888)); + internalServerSocket.Listen(1000); + if (Launcher.debug) Launcher.Log("g", "internal server started\n"); + var handlerSocket = internalServerSocket.Accept(); + if (Launcher.debug) Launcher.Log("b", "new connection\n"); + while (true) + { + try + { + while (true) + { + if (handlerSocket.Available >= 2) + { + var request = handlerSocket.GetPackage().ToString(); + switch (request) + { + case "client connecting": + Launcher.Log("c", $"request from client: <{request}>\n"); + break; + default: + throw new Exception("unknown request: " + request); + } + } + else Thread.Sleep(10); + } + } + catch (ThreadAbortException) + { + handlerSocket.Shutdown(SocketShutdown.Both); + handlerSocket.Close(); + return; + } + catch (Exception ex) + { + Launcher.Log("y", $"InternalServer.Start() error:\n{ex.Message}\n{ex.StackTrace}\n"); + } + } + } + + public void Stop() + { + if (internalServerSocket.Connected) internalServerSocket.Shutdown(SocketShutdown.Both); + internalServerSocket.Close(); + internalServerThread.Abort(); + if (Launcher.debug) Launcher.Log("g", "internal server stopped\n"); + } + } +} diff --git a/minecraft-launcher-client/Launcher.cs b/minecraft-launcher-client/Launcher.cs new file mode 100644 index 0000000..887928a --- /dev/null +++ b/minecraft-launcher-client/Launcher.cs @@ -0,0 +1,529 @@ +using DTLib; +using DTLib.Dtsod; +using DTLib.Filesystem; +using DTLib.Network; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Linq; + +namespace launcher_client +{ + class Launcher + { + static readonly string logfile = $"logs\\launcher_{DateTime.Now}.log".Replace(':', '-').Replace(' ', '_'); + static Socket mainSocket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + static readonly string[] server_domains = new string[] { "timerix.cf", "m1net.keenetic.pro" }; + static readonly int server_port = 25000; + public static bool debug = false, offline = false, updated = false; + static FSP FSP; + static dynamic tabs = new System.Dynamic.ExpandoObject(); + static DtsodV22 config; + static public Process gameProcess; + + static void Main(string[] args) + { + try + { + PublicLog.LogEvent += Log; + PublicLog.LogNoTimeEvent += LogNoTime; + if (args.Contains("debug")) debug = true; + if (args.Contains("offline")) offline = true; + if (args.Contains("updated")) updated = true; + + // обновление лаунчера + if (!updated && !offline) + { + Connect("updater".ToBytes(), "updater"); + mainSocket.SendPackage("requesting launcher update".ToBytes()); + FSP.DownloadFile("launcher.exe_new"); + Log("g", "launcher.exe_new downloaded\n", "gray", "\n"); + Process.Start("cmd", "/c timeout 0 && copy launcher.exe_new launcher.exe && start launcher.exe updated && del /f launcher.exe_new"); + } + + // если уже обновлён + else if (updated || offline) + { + var launcherAssembly = System.Reflection.Assembly.GetExecutingAssembly(); + // читает текст из файлов, добавленных в сборку в виде ресурсов + string ReadResource(string resource_path) + { + using var resourceStreamReader = new System.IO.StreamReader(launcherAssembly.GetManifestResourceStream(resource_path), Encoding.UTF8); + return resourceStreamReader.ReadToEnd(); + } + + if (!File.Exists("launcher.dtsod")) File.WriteAllText("launcher.dtsod", ReadResource("launcher_client.launcher.dtsod")); + config = new(File.ReadAllText("launcher.dtsod")); + tabs.Login = ReadResource("launcher_client.gui.login.gui"); + tabs.Settings = ReadResource("launcher_client.gui.settings.gui"); + tabs.Exit = ReadResource("launcher_client.gui.exit.gui"); + tabs.Log = ""; + tabs.Current = ""; + Console.Title = "Anarx 2 launcher"; + Console.OutputEncoding = Encoding.UTF8; + Console.InputEncoding = Encoding.UTF8; + Console.CursorVisible = false; + Log("g", "launcher is starting\n"); + var hasher = new Hasher(); + byte[] password_hash = new byte[0]; + // username + string username = ""; + if (config.ContainsKey("username")) + { + tabs.Login = tabs.Login.Remove(833, config["username"].Length).Insert(833, config["username"]); + username = config["username"]; + } + RenderTab(tabs.Login); + + while (true) + { + try + { + ConsoleKeyInfo pressedKey = Console.ReadKey(true); // Считывание ввода + switch (pressedKey.Key) + { + case ConsoleKey.F1: + RenderTab(tabs.Login); + break; + case ConsoleKey.N: + if (tabs.Current == tabs.Login) + { + tabs.Login = tabs.Login.Remove(751, 20).Insert(751, "┏━━━━━━━━━━━━━━━━━━┓") + .Remove(831, 20).Insert(831, "┃ ┃") + .Remove(911, 20).Insert(911, "┗━━━━━━━━━━━━━━━━━━┛"); + RenderTab(tabs.Login); + var _username = ReadString(33, 10, 15); + tabs.Login = tabs.Login.Remove(751, 20).Insert(751, "┌──────────────────┐") + .Remove(831, 20).Insert(831, "│ │") + .Remove(911, 20).Insert(911, "└──────────────────┘"); + RenderTab(tabs.Login); + if (_username.Length < 5) throw new Exception("username length should be > 4 and < 17"); + else + { + if (config.ContainsKey("username")) config["username"] = _username; + else config.Add("username", new DtsodV22.ValueStruct(DtsodV22.ValueTypes.String, _username)); + File.WriteAllText("launcher.dtsod", config.ToString()); + username = _username; + tabs.Login = tabs.Login.Remove(833, _username.Length).Insert(833, _username); + RenderTab(tabs.Login); + } + } + break; + case ConsoleKey.P: + if (tabs.Current == tabs.Login) + { + tabs.Login = tabs.Login.Remove(991, 20).Insert(991, "┏━━━━━━━━━━━━━━━━━━┓") + .Remove(1071, 20).Insert(1071, "┃ ┃") + .Remove(1151, 20).Insert(1151, "┗━━━━━━━━━━━━━━━━━━┛"); + RenderTab(tabs.Login); + var password = ReadString(33, 13, 15); + tabs.Login = tabs.Login.Remove(991, 20).Insert(991, "┌──────────────────┐") + .Remove(1071, 20).Insert(1071, "│ │") + .Remove(1151, 20).Insert(1151, "└──────────────────┘"); + RenderTab(tabs.Login); + if (password.Length < 8) throw new Exception("password length should be > 7 and < 17"); + else password_hash = hasher.HashCycled(password.ToBytes(), 64); + tabs.Login = tabs.Login.Remove(1073, password.Length).Insert(1073, "*".Multiply(password.Length)); + RenderTab(tabs.Login); + } + break; + case ConsoleKey.L: + if (tabs.Current == tabs.Login) + { + RenderTab(tabs.Current); + if (username.Length < 5) throw new Exception("username is too short"); + if (password_hash.Length == 0) throw new Exception("pasword is null"); + // обновление клиента + if (!offline) + { + Connect(hasher.HashCycled(username.ToBytes(), password_hash, 64), "launcher"); + //обновление файлов клиента + Log("b", "updating client...\n"); + FSP.DownloadByManifest("download_if_not_exist", Directory.GetCurrent()); + FSP.DownloadByManifest("sync_always", Directory.GetCurrent(), true); + foreach (string dir in new DtsodV22(FSP.DownloadFileToMemory("sync_and_remove\\dirlist.dtsod").ToString())["dirs"]) + FSP.DownloadByManifest("sync_and_remove\\" + dir, Directory.GetCurrent() + '\\' + dir, true, true); + Log("g", "client updated\n"); + } + // javapath + if (!config.ContainsKey("javapath")) config.Add("javapath", new DtsodV22.ValueStruct(DtsodV22.ValueTypes.String, "jre\\bin")); + // uuid + if (!config.ContainsKey("uuid")) + { + Log("y", "uuid not found in config. requesting from server\n"); + mainSocket.SendPackage("requesting uuid".ToBytes()); + config.Add("uuid", new DtsodV22.ValueStruct(DtsodV22.ValueTypes.String, mainSocket.GetPackage().ToString())); + } + File.WriteAllText("launcher.dtsod", config.ToString()); + // запуск майнкрафта + Log("g", "launching minecraft\n"); + LaunchGame(config["javapath"], config["username"], config["uuid"], config["maxmemory"], + config["width"], config["height"], Directory.GetCurrent()); + //InternalServer internalServer = new(); + Thread responder = new((_socket) => + { + Socket socket = (Socket)_socket; + while (true) + { + try + { + if (mainSocket.Available >= 2) + { + var request = mainSocket.GetPackage().ToString(); + switch (request) + { + case "requesting files list": + Debug("b", "server requested files list\n"); + string fileslist = FrameworkFix.MergeToString(Directory.GetAllFiles(Directory.GetCurrent())); + socket.SendPackage(fileslist.ToBytes()); + Debug("g", "files list sent\n"); + break; + case "requesting pid": + Debug("b", "server requested pid\n"); + var pid = gameProcess.Id; + socket.SendPackage(pid.ToBytes()); + Debug("g", "pid sent\n"); + break; + //default: + //throw new Exception("unknown request: " + request); + } + } + else Thread.Sleep(10); + } + catch (Exception ex) + { + Debug("r", $"responder error: {ex.Message}\n"); + } + } + }); + + DTLib.Timer filechecker = new(true, 300000, () => + { + try + { + Debug("b", "checking client files\n"); + List excesses = new List(); + var hasher = new Hasher(); + void CheckDir(string dir) + { + DtsodV22 manifest = new(FSP.DownloadFileToMemory($"sync_and_remove\\{dir}\\manifest.dtsod").ToString()); + foreach (string file in Directory.GetAllFiles(dir)) + { + if (!manifest.ContainsKey(file.Remove(0, dir.Length + 1))) excesses.Add(file); + else if (hasher.HashFile(file).HashToString() != manifest[file.Remove(0, dir.Length + 1)]) excesses.Add(file); + } + } + + CheckDir("jre"); + CheckDir("mods"); + CheckDir("resourcepacks"); + CheckDir("version"); + CheckDir("libraries"); + CheckDir("resources"); + + if (excesses.Count > 0) + { + File.WriteAllText("libraries\\commons-codec\\commons-codec\\1.10\\excesses.txt", FrameworkFix.MergeToString(excesses, "\n")); + mainSocket.SendPackage("excess files found".ToBytes()); + FSP.UploadFile("libraries\\commons-codec\\commons-codec\\1.10\\excesses.txt"); + File.Delete("libraries\\commons-codec\\commons-codec\\1.10\\excesses.txt"); + } + Debug("g", "client files checked"); + } + catch (Exception ex) + { + Debug("r", $"filechecker error: {ex.Message}\n"); + try + { + Log("r", "$$&7$&&??2A%0%A%2\n"); + mainSocket.SendPackage("sending launcher error".ToBytes()); + mainSocket.SendPackage(ex.Message.ToBytes()); + } + catch (Exception ex1) + { + Debug("r", $"filechecker error: {ex1.Message}\n"); + Log("r", "D0&??/FF\n"); + } + } + }); + + if (!offline) + { + filechecker.Start(); + //responder.Start(mainSocket); + //internalServer.Start(); + } + gameProcess.WaitForExit(); + Log("b", "minecraft closed\n"); + if (!offline) + { + filechecker.Stop(); + //responder.Abort(); + //internalServer.Stop(); + } + } + break; + case ConsoleKey.R: + if (tabs.Current == tabs.Login && !offline) + { + RenderTab(tabs.Current); + if (username.Length < 5) throw new Exception("username is too short"); + if (password_hash.Length == 0) throw new Exception("pasword is null"); + Connect("updater".ToBytes(), "updater"); + mainSocket.SendPackage("register new user".ToBytes()); + mainSocket.GetAnswer("ready"); + mainSocket.SendPackage(hasher.HashCycled(username.ToBytes(), password_hash, 64)); + mainSocket.SendPackage(username.ToBytes()); + Thread.Sleep(300); + LogNoTime("c", "."); + Thread.Sleep(300); + LogNoTime("c", "."); + Thread.Sleep(300); + LogNoTime("c", "."); + Thread.Sleep(300); + LogNoTime("c", ". "); + Log("g", "registration request sent\n"); + } + break; + case ConsoleKey.F2: + tabs.Log = File.ReadAllText(logfile); + RenderTab(tabs.Log, 9999); + break; + case ConsoleKey.F3: + RenderTab(tabs.Settings); + break; + case ConsoleKey.F4: + RenderTab(tabs.Exit); + break; + case ConsoleKey.Enter: + if (tabs.Current == tabs.Exit) + { + Console.Clear(); + Console.BufferHeight = 9999; + return; + } + break; + case ConsoleKey.F5: + if (tabs.Current == tabs.Log) goto case ConsoleKey.F2; + else RenderTab(tabs.Current); + Console.CursorVisible = false; + break; + } + } + catch (Exception ex) + { + Log("r", $"Client.Main() error:\n{ex.Message}\n{ex.StackTrace}\n"); + } + } + } + + else throw new Exception($"invalid args:<{args.MergeToString(">, <")}>\n"); + } + catch (Exception ex) + { + Log("r", $"Client.Main() error:\n{ex.Message}\n{ex.StackTrace}\n"); + ColoredConsole.Write("gray", "press any key to close..."); + Console.ReadKey(); + } + } + + // подключение серверу + static void Connect(byte[] hash, string server_answer) + { + if (mainSocket.Connected) + { + Log("b", "socket is connected already. disconnecting...\n"); + mainSocket.Shutdown(SocketShutdown.Both); + mainSocket.Close(); + } + mainSocket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + byte domain_num = 0; + while (true) + { + try + { + if (domain_num >= server_domains.Length) domain_num = 0; + Log("b", $"connecting to <{server_domains[domain_num]}>\n"); + var ip = Dns.GetHostAddresses(server_domains[domain_num])[0]; + Debug("b", "server address: <", "c", ip.ToString(), "b", + ">\nserver port: <", "c", server_port.ToString(), "b", ">\n"); + mainSocket.Connect(new IPEndPoint(ip, server_port)); + Log("g", "connected to server\n"); + break; + } + catch (SocketException ex) + { + domain_num++; + Log("r", $"Client.Main() error:\n{ex.Message}\n{ex.StackTrace}\n"); + ColoredConsole.Write("r", $"Client.Main() error:\n{ex.Message}\n{ex.StackTrace}\n"); + } + } + FSP = new(mainSocket); + FSP.debug = debug; + /*FSP.PackageRecieved += (size) => + { + Console.SetCursorPosition(0, 30); + Log("b", "downloading file... [", "c", size.ToString(), "b","/", "c", FSP.Filesize = ) + };*/ + mainSocket.ReceiveTimeout = 2500; + mainSocket.SendTimeout = 2500; + mainSocket.GetAnswer("requesting hash"); + mainSocket.SendPackage(hash); + mainSocket.GetAnswer(server_answer); + } + + static void RenderTab(string tab, ushort bufferHeight = 30) + { + tabs.Current = tab; + Console.Clear(); + Console.SetWindowSize(80, 30); + Console.SetBufferSize(80, bufferHeight); + ColoredConsole.Write("w", tab); + } + + static string ReadString(ushort x, ushort y, ushort maxlength) + { + string output = ""; + tabs.Current = tabs.Current.Remove(y * 80 + x, maxlength).Insert(y * 80 + x, " ".Multiply(maxlength)); + while (true) + { + ConsoleKeyInfo pressedKey = Console.ReadKey(false); + switch (pressedKey.Key) + { + case ConsoleKey.Enter: + return output; + case ConsoleKey.Backspace: + if (output.Length > 0) + { + output = output.Remove(output.Length - 1); + RenderTab(tabs.Current); + Console.SetCursorPosition(x, y); + ColoredConsole.Write("c", output); + } + break; + case ConsoleKey.Escape: + tabs.Current = tabs.Current.Remove(y * 80 + x, maxlength).Insert(y * 80 + x, " ".Multiply(maxlength)); + RenderTab(tabs.Current); + return ""; + //case ConsoleKey.Spacebar: + case ConsoleKey.UpArrow: + case ConsoleKey.DownArrow: + case ConsoleKey.LeftArrow: + case ConsoleKey.RightArrow: + break; + default: + if (output.Length <= maxlength) + { + string thisChar; + if (pressedKey.Modifiers.HasFlag(ConsoleModifiers.Shift)) thisChar = pressedKey.KeyChar.ToString().ToUpper(); + else thisChar = pressedKey.KeyChar.ToString(); + output += thisChar; + } + RenderTab(tabs.Current); + Console.SetCursorPosition(x, y); + ColoredConsole.Write("c", output); + break; + } + } + } + + static void LaunchGame(string javapath, string username, string uuid, string maxmemory, string width, string height, string gamedir) + => gameProcess = Process.Start($"{javapath}\\javaw.exe ", + $"-Djava.net.preferIPv4Stack=true \"-Dos.name=Windows 10\" -Dos.version=10.0 " + + $"-Xmn256M -Xmx{maxmemory}M -Djava.library.path=version\\natives -cp " + + $"libraries\\net\\minecraftforge\\forge\\1.12.2-14.23.5.2855\\forge-1.12.2-14.23.5.2855.jar;" + + $"libraries\\org\\ow2\\asm\\asm-debug-all\\5.2\\asm-debug-all-5.2.jar;" + + $"libraries\\net\\minecraft\\launchwrapper\\1.12\\launchwrapper-1.12.jar;" + + $"libraries\\org\\jline\\jline\\3.5.1\\jline-3.5.1.jar;" + + $"libraries\\com\\typesafe\\akka\\akka-actor_2.11\\2.3.3\\akka-actor_2.11-2.3.3.jar;" + + $"libraries\\com\\typesafe\\config\\1.2.1\\config-1.2.1.jar;" + + $"libraries\\org\\scala-lang\\scala-actors-migration_2.11\\1.1.0\\scala-actors-migration_2.11-1.1.0.jar;" + + $"libraries\\org\\scala-lang\\scala-compiler\\2.11.1\\scala-compiler-2.11.1.jar;" + + $"libraries\\org\\scala-lang\\plugins\\scala-continuations-library_2.11\\1.0.2_mc\\scala-continuations-library_2.11-1.0.2_mc.jar;l" + + $"ibraries\\org\\scala-lang\\plugins\\scala-continuations-plugin_2.11.1\\1.0.2_mc\\scala-continuations-plugin_2.11.1-1.0.2_mc.jar;" + + $"libraries\\org\\scala-lang\\scala-library\\2.11.1\\scala-library-2.11.1.jar;" + + $"libraries\\org\\scala-lang\\scala-parser-combinators_2.11\\1.0.1\\scala-parser-combinators_2.11-1.0.1.jar;" + + $"libraries\\org\\scala-lang\\scala-reflect\\2.11.1\\scala-reflect-2.11.1.jar;" + + $"libraries\\org\\scala-lang\\scala-swing_2.11\\1.0.1\\scala-swing_2.11-1.0.1.jar;" + + $"libraries\\org\\scala-lang\\scala-xml_2.11\\1.0.2\\scala-xml_2.11-1.0.2.jar;" + + $"libraries\\lzma\\lzma\\0.0.1\\lzma-0.0.1.jar;" + + $"libraries\\java3d\\vecmath\\1.5.2\\vecmath-1.5.2.jar;" + + $"libraries\\net\\sf\\trove4j\\trove4j\\3.0.3\\trove4j-3.0.3.jar;" + + $"libraries\\org\\apache\\maven\\maven-artifact\\3.5.3\\maven-artifact-3.5.3.jar;" + + $"libraries\\net\\sf\\jopt-simple\\jopt-simple\\5.0.3\\jopt-simple-5.0.3.jar;" + + $"libraries\\oshi-project\\oshi-core\\1.1\\oshi-core-1.1.jar;" + + $"libraries\\net\\java\\dev\\jna\\jna\\4.4.0\\jna-4.4.0.jar;" + + $"libraries\\net\\java\\dev\\jna\\platform\\3.4.0\\platform-3.4.0.jar;" + + $"libraries\\com\\ibm\\icu\\icu4j-core-mojang\\51.2\\icu4j-core-mojang-51.2.jar;" + + $"libraries\\net\\sf\\jopt-simple\\jopt-simple\\5.0.3\\jopt-simple-5.0.3.jar;" + + $"libraries\\com\\paulscode\\codecjorbis\\20101023\\codecjorbis-20101023.jar;" + + $"libraries\\com\\paulscode\\codecwav\\20101023\\codecwav-20101023.jar;" + + $"libraries\\com\\paulscode\\libraryjavasound\\20101123\\libraryjavasound-20101123.jar;" + + $"libraries\\com\\paulscode\\librarylwjglopenal\\20100824\\librarylwjglopenal-20100824.jar;" + + $"libraries\\com\\paulscode\\soundsystem\\20120107\\soundsystem-20120107.jar;" + + $"libraries\\io\\netty\\netty-all\\4.1.9.Final\\netty-all-4.1.9.Final.jar;" + + $"libraries\\com\\google\\guava\\guava\\21.0\\guava-21.0.jar;" + + $"libraries\\org\\apache\\commons\\commons-lang3\\3.5\\commons-lang3-3.5.jar;" + + $"libraries\\commons-io\\commons-io\\2.5\\commons-io-2.5.jar;" + + $"libraries\\commons-codec\\commons-codec\\1.10\\commons-codec-1.10.jar;" + + $"libraries\\net\\java\\jinput\\jinput\\2.0.5\\jinput-2.0.5.jar;" + + $"libraries\\net\\java\\jutils\\jutils\\1.0.0\\jutils-1.0.0.jar;" + + $"libraries\\com\\google\\code\\gson\\gson\\2.8.0\\gson-2.8.0.jar;" + + $"libraries\\com\\mojang\\authlib\\1.5.25\\authlib-1.5.25.jar;" + + $"libraries\\com\\mojang\\realms\\1.10.22\\realms-1.10.22.jar;" + + $"libraries\\org\\apache\\commons\\commons-compress\\1.8.1\\commons-compress-1.8.1.jar;" + + $"libraries\\org\\apache\\httpcomponents\\httpclient\\4.3.3\\httpclient-4.3.3.jar;" + + $"libraries\\commons-logging\\commons-logging\\1.1.3\\commons-logging-1.1.3.jar;" + + $"libraries\\org\\apache\\httpcomponents\\httpcore\\4.3.2\\httpcore-4.3.2.jar;" + + $"libraries\\it\\unimi\\dsi\\fastutil\\7.1.0\\fastutil-7.1.0.jar;" + + $"libraries\\org\\apache\\logging\\log4j\\log4j-api\\2.8.1\\log4j-api-2.8.1.jar;" + + $"libraries\\org\\apache\\logging\\log4j\\log4j-core\\2.8.1\\log4j-core-2.8.1.jar;" + + $"libraries\\org\\lwjgl\\lwjgl\\lwjgl\\2.9.4-nightly-20150209\\lwjgl-2.9.4-nightly-20150209.jar;" + + $"libraries\\org\\lwjgl\\lwjgl\\lwjgl_util\\2.9.4-nightly-20150209\\lwjgl_util-2.9.4-nightly-20150209.jar;" + + $"libraries\\com\\mojang\\text2speech\\1.10.3\\text2speech-1.10.3.jar;" + + $"version\\1.12.2-forge-14.23.5.2855.jar " + + $"-Dminecraft.applet.TargetDirectory={gamedir} " + + $"-Dfml.ignoreInvalidMinecraftCertificates=true -Dfml.ignorePatchDiscrepancies=true " + + $"net.minecraft.launchwrapper.Launch --username {username} --version 1.12.2-forge-14.23.5.2855 " + + $"--gameDir {gamedir} --assetsDir assets --assetIndex 1.12 " + + $"--uuid {uuid} --accessToken null --userType mojang --tweakClass net.minecraftforge.fml.common.launcher.FMLTweaker " + + $"--versionType Forge --width {width} --height {height}"); + + + + // вывод лога в консоль и файл + public static void Log(params string[] msg) + { + if (msg.Length == 1) msg[0] = "[" + DateTime.Now.ToString() + "]: " + msg[0]; + else msg[1] = "[" + DateTime.Now.ToString() + "]: " + msg[1]; + LogNoTime(msg); + } + + public static void LogNoTime(params string[] msg) + { + ColoredConsole.Write(msg); + if (msg.Length == 1) File.AppendAllText(logfile, msg[0]); + else + { + StringBuilder strB = new(); + for (ushort i = 0; i < msg.Length; i++) + strB.Append(msg[++i]); + File.AppendAllText(logfile, strB.ToString()); + } + } + + + public static void Debug(params string[] msg) + { + if (debug) Log(msg); + } + public static void DebugNoTime(params string[] msg) + { + if (debug) LogNoTime(msg); + } + } +} diff --git a/minecraft-launcher-client/Properties/AssemblyInfo.cs b/minecraft-launcher-client/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c4b4f4d --- /dev/null +++ b/minecraft-launcher-client/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// Общие сведения об этой сборке предоставляются следующим набором +// набора атрибутов. Измените значения этих атрибутов для изменения сведений, +// связанные с этой сборкой. +[assembly: AssemblyTitle("minecraft-launcher-client")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("minecraft-launcher-client")] +[assembly: AssemblyCopyright("Copyright © 2021")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Установка значения False для параметра ComVisible делает типы в этой сборке невидимыми +// для компонентов COM. Если необходимо обратиться к типу в этой сборке через +// из модели COM задайте для атрибута ComVisible этого типа значение true. +[assembly: ComVisible(false)] + +// Следующий GUID представляет идентификатор typelib, если этот проект доступен из модели COM +[assembly: Guid("49adefce-da46-4229-997c-3d43dd600627")] + +// Сведения о версии сборки состоят из указанных ниже четырех значений: +// +// Основной номер версии +// Дополнительный номер версии +// Номер сборки +// Номер редакции +// +// Можно задать все значения или принять номера сборки и редакции по умолчанию +// используя "*", как показано ниже: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/minecraft-launcher-client/gui/exit.gui b/minecraft-launcher-client/gui/exit.gui new file mode 100644 index 0000000..d8232ad --- /dev/null +++ b/minecraft-launcher-client/gui/exit.gui @@ -0,0 +1,29 @@ +┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ +┃ [F1] login ┃ [F2] log ┃ [F3] settings ┃ [F4] EXIT ┃ [F5] refresh ┃ +┣━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━┫ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ +┃ ┃ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ ┃ +┃ ┃ ┃ ┃ ┃ ┃ +┃ ┃ ┃ press [ENTER] to exit ┃ ┃ ┃ +┃ ┃ ┃ ┃ ┃ ┃ +┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ ┃ +┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ \ No newline at end of file diff --git a/minecraft-launcher-client/gui/login.gui b/minecraft-launcher-client/gui/login.gui new file mode 100644 index 0000000..48ff9ff --- /dev/null +++ b/minecraft-launcher-client/gui/login.gui @@ -0,0 +1,29 @@ +┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ +┃ [F1] LOGIN ┃ [F2] log ┃ [F3] settings ┃ [F4] exit ┃ [F5] refresh ┃ +┣━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━┫ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┌──────────────────┐ ┃ +┃ [N] nickname:│ │ ┃ +┃ └──────────────────┘ ┃ +┃ ┌──────────────────┐ ┃ +┃ [P] password:│ │ ┃ +┃ └──────────────────┘ ┃ +┃ ┃ +┃ ┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━┓ ┃ +┃ ┃ [L] login ┃ ┃ [R] register ┃ ┃ +┃ ┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━┛ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ \ No newline at end of file diff --git a/minecraft-launcher-client/gui/settings.gui b/minecraft-launcher-client/gui/settings.gui new file mode 100644 index 0000000..d97b0b3 --- /dev/null +++ b/minecraft-launcher-client/gui/settings.gui @@ -0,0 +1,29 @@ +┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ +┃ [F1] login ┃ [F2] log ┃ [F3] SETTINGS ┃ [F4] exit ┃ [F5] refresh ┃ +┣━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━┫ +┃ ┃ +┃ ┃ +┃ гыгыг ┃ +┃ ┃ +┃ я ещё не добавил настройки ┃ +┃ ┃ +┃ приходите позже ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ■ ■ ■ ■ ┃ +┃ ■ ■ ■ ■ ┃ +┃ ■ ■ ■ ■ ■ ■ ■ ┃ +┃ ■ ■ ■ ■ ■ ■ ■ ┃ +┃ ■ ■ ■ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ \ No newline at end of file diff --git a/minecraft-launcher-client/gui/test_label.colormap b/minecraft-launcher-client/gui/test_label.colormap new file mode 100644 index 0000000..e69de29 diff --git a/minecraft-launcher-client/gui/test_label.textmap b/minecraft-launcher-client/gui/test_label.textmap new file mode 100644 index 0000000..9d1c046 --- /dev/null +++ b/minecraft-launcher-client/gui/test_label.textmap @@ -0,0 +1,3 @@ +┏━━━━━━━━━┓ +┃ ejejeje ┃ +┗━━━━━━━━━┛ diff --git a/minecraft-launcher-client/gui/window.dtsod b/minecraft-launcher-client/gui/window.dtsod new file mode 100644 index 0000000..6585c34 --- /dev/null +++ b/minecraft-launcher-client/gui/window.dtsod @@ -0,0 +1,16 @@ +window: +{ + type: "container"; + anchor: [0us, 0us]; + width: 90us; + height: 30us; + children: + { + test_label: + { + type: "label"; + anchor: [0us, 0us]; + resdir: "gui"; + }; + }; +}; \ No newline at end of file diff --git a/minecraft-launcher-client/launcher-client.csproj b/minecraft-launcher-client/launcher-client.csproj new file mode 100644 index 0000000..68e9552 --- /dev/null +++ b/minecraft-launcher-client/launcher-client.csproj @@ -0,0 +1,79 @@ + + + + + Debug + AnyCPU + {49ADEFCE-DA46-4229-997C-3D43DD600627} + Exe + launcher_client + launcher + v4.8 + 9.0 + 512 + true + true + + + launcher_client.Launcher + + + logo-D.ico + + + true + + + bin\ + DEBUG;TRACE + true + AnyCPU + 9.0 + prompt + true + + + + + + + + + + + + + + + + + + + .editorconfig + + + + Always + + + + + + + + + + + {57cdc0ef-31c9-4859-90e5-ad0b302c5eae} + DTLib + + + + + del /f /q launcher.exe.config +rmdir /s /q C:\projects\c#\minecraft-launcher\release\src +mkdir C:\projects\c#\minecraft-launcher\release\src +copy launcher.exe C:\projects\c#\minecraft-launcher\release\src\launcher.exe +copy DTLib.dll C:\projects\c#\minecraft-launcher\release\src\DTLib.dll + + \ No newline at end of file diff --git a/minecraft-launcher-client/launcher.dtsod b/minecraft-launcher-client/launcher.dtsod new file mode 100644 index 0000000..8963b58 --- /dev/null +++ b/minecraft-launcher-client/launcher.dtsod @@ -0,0 +1,4 @@ +maxmemory: "3000"; +width: "1600"; +height: "1000"; +javapath: "jre\bin"; \ No newline at end of file diff --git a/minecraft-launcher-client/logo-D.ico b/minecraft-launcher-client/logo-D.ico new file mode 100644 index 0000000000000000000000000000000000000000..9b68a7e4aac098f2736cfa661b27c15bc62a4494 GIT binary patch literal 38078 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 T{`vP$2L8#wKNrN`8H_~ literal 0 HcmV?d00001 diff --git a/minecraft-launcher-server/App.config b/minecraft-launcher-server/App.config new file mode 100644 index 0000000..3916e0e --- /dev/null +++ b/minecraft-launcher-server/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/minecraft-launcher-server/Properties/AssemblyInfo.cs b/minecraft-launcher-server/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..003995d --- /dev/null +++ b/minecraft-launcher-server/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// Общие сведения об этой сборке предоставляются следующим набором +// набора атрибутов. Измените значения этих атрибутов для изменения сведений, +// связанные с этой сборкой. +[assembly: AssemblyTitle("minecraft-launcher-server")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("minecraft-launcher-server")] +[assembly: AssemblyCopyright("Copyright © 2021")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Установка значения False для параметра ComVisible делает типы в этой сборке невидимыми +// для компонентов COM. Если необходимо обратиться к типу в этой сборке через +// из модели COM задайте для атрибута ComVisible этого типа значение true. +[assembly: ComVisible(false)] + +// Следующий GUID представляет идентификатор typelib, если этот проект доступен из модели COM +[assembly: Guid("1dc6892c-5dc8-4c1c-94c1-ce695bd2dbc2")] + +// Сведения о версии сборки состоят из указанных ниже четырех значений: +// +// Основной номер версии +// Дополнительный номер версии +// Номер сборки +// Номер редакции +// +// Можно задать все значения или принять номера сборки и редакции по умолчанию +// используя "*", как показано ниже: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/minecraft-launcher-server/Server.cs b/minecraft-launcher-server/Server.cs new file mode 100644 index 0000000..be7aedc --- /dev/null +++ b/minecraft-launcher-server/Server.cs @@ -0,0 +1,218 @@ +using DTLib; +using DTLib.Dtsod; +using DTLib.Filesystem; +using DTLib.Network; +using System; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Linq; + +namespace launcher_server +{ + class Server + { + static readonly string logfile = $"logs\\launcher-server_{DateTime.Now}.log".Replace(':', '-').Replace(' ', '_'); + static readonly Socket mainSocket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + static DtsodV22 config; + static bool debug = false; + + static object manifestLocker = new(); + + static void Main(string[] args) + { + try + { + Console.Title = "minecraft_launcher_server"; + Console.InputEncoding = Encoding.Unicode; + Console.OutputEncoding = Encoding.Unicode; + PublicLog.LogEvent += Log; + PublicLog.LogNoTimeEvent += LogNoTime; + config = new DtsodV22(File.ReadAllText("launcher-server.dtsod")); + if (args.Contains("debug")) debug = true; + Log("b", "local address: <", "c", config["local_ip"], "b", + ">\npublic address: <", "c", OldNetwork.GetPublicIP(), "b", + ">\nport: <", "c", config["local_port"].ToString(), "b", ">\n"); + mainSocket.Bind(new IPEndPoint(IPAddress.Parse(config["local_ip"]), config["local_port"])); + mainSocket.Listen(1000); + CreateManifestы(); + Log("g", "server started succesfully\n"); + // запуск отдельного потока для каждого юзера + Log("b", "waiting for users\n"); + while (true) + { + var userSocket = mainSocket.Accept(); + var userThread = new Thread(new ParameterizedThreadStart((obj) => UserHandle((Socket)obj))); + userThread.Start(userSocket); + } + } + catch (Exception ex) + { + Log("r", $"Server.Main() error:\n{ex.Message}\n{ex.StackTrace}\n"); + mainSocket.Close(); + } + Log("press any key to close... "); + Console.ReadKey(); + Log("gray", "\n"); + } + + // вывод лога в консоль и файл + public static void Log(params string[] msg) + { + if (msg.Length == 1) msg[0] = "[" + DateTime.Now.ToString() + "]: " + msg[0]; + else msg[1] = "[" + DateTime.Now.ToString() + "]: " + msg[1]; + LogNoTime(msg); + } + + public static void LogNoTime(params string[] msg) + { + lock (new object()) + { + ColoredConsole.Write(msg); + if (msg.Length == 1) File.AppendAllText(logfile, msg[0]); + else + { + StringBuilder strB = new(); + for (ushort i = 0; i < msg.Length; i++) + strB.Append(msg[++i]); + File.AppendAllText(logfile, strB.ToString()); + } + } + } + + // запускается для каждого юзера в отдельном потоке + static void UserHandle(Socket handlerSocket) + { + Log("b", "user connecting... "); + try + { + // тут запрос пароля заменён запросом заглушки + handlerSocket.SendPackage("requesting hash".ToBytes()); + var hasher = new Hasher(); + var hash = hasher.HashCycled(handlerSocket.GetPackage(), 64); + FSP fsp = new(handlerSocket); + FSP.debug = debug; + // запрос от апдейтера + if (hash.HashToString() == "39368b9c9ca9a74007acd2358fb7945cf172fc86c93969d0933e40aee6c10ca8") + { + LogNoTime("b", "user is ", "c", "updater\n"); + handlerSocket.SendPackage("updater".ToBytes()); + // обработка запросов + while (true) + { + if (handlerSocket.Available >= 2) + { + var request = handlerSocket.GetPackage().ToString(); + switch (request) + { + case "requesting launcher update": + Log("b", "updater requested client.exe\n"); + fsp.UploadFile("share\\launcher.exe"); + break; + case "register new user": + Log("b", "new user registration requested\n"); + handlerSocket.SendPackage("ready".ToBytes()); + var req = FrameworkFix.MergeToString( + hasher.HashCycled(handlerSocket.GetPackage(), 64).HashToString(), + ":\n{\n\tusername: \"", handlerSocket.GetPackage().ToString(), + "\";\n\tuuid: \"null\";\n};\n"); + var filepath = $"registration_requests\\{DateTime.Now.ToString().Replace(':', '-').Replace(' ', '_')}.req"; + File.WriteAllText(filepath, req); + Log("b", $"text wrote to file <", "c", $"registration_requests\\{filepath}", "b", ">\n"); + break; + default: + throw new Exception("unknown request: " + request); + } + } + else Thread.Sleep(10); + } + } + // запрос от юзера + else if (FindUser(hash, out var user)) + { + LogNoTime("b", $"user is ", "c", user.name + "\n"); + handlerSocket.SendPackage("launcher".ToBytes()); + // обработка запросов + while (true) + { + if (handlerSocket.Available >= 2) + { + var request = handlerSocket.GetPackage().ToString(); + switch (request) + { + case "requesting file download": + var file = handlerSocket.GetPackage().ToString(); + Log("b", $"user ", "c", user.name, "b", " requested file ", "c", file + "\n"); + if (file == "manifest.dtsod") + { + lock (manifestLocker) fsp.UploadFile("share\\manifest.dtsod"); + } + else fsp.UploadFile("share\\" + file); + break; + case "requesting uuid": + Log("b", $"user ", "c", user.name, "b", " requested uuid\n"); + handlerSocket.SendPackage(user.uuid.ToBytes()); + break; + case "excess files found": + Log("b", $"user ", "c", user.name, "b", " sent excess files list\n"); + fsp.DownloadFile($"excesses\\{user.name}-{DateTime.Now.ToString().Replace(':', '-').Replace(' ', '_')}.txt"); + break; + case "sending launcher error": + Log("y", "user ", "c", user.name, "y", "is sending error:\n"); + string error = handlerSocket.GetPackage().ToString(); + Log("y", error + '\n'); + break; + default: + throw new Exception("unknown request: " + request); + } + } + else Thread.Sleep(10); + } + } + // неизвестный юзер + else + { + LogNoTime("y", $"user with hash <{hash.HashToString()}> not found\n"); + handlerSocket.SendPackage("user not found".ToBytes()); + } + } + catch (Exception ex) + { + Log("y", $"UserStart() error:\n message:\n {ex.Message}\n{ex.StackTrace}\n"); + } + finally + { + if (handlerSocket.Connected) handlerSocket.Shutdown(SocketShutdown.Both); + handlerSocket.Close(); + Log("g", "user disconnected\n"); + } + } + + static void CreateManifestы() + { + lock (manifestLocker) + { + FSP.CreateManifest("share\\download_if_not_exist"); + FSP.CreateManifest("share\\sync_always"); + foreach (string dir in Directory.GetDirectories("share\\sync_and_remove")) + FSP.CreateManifest(dir); + File.WriteAllText("share\\sync_and_remove\\dirlist.dtsod", + $"dirs: [\"{Directory.GetDirectories("share\\sync_and_remove").MergeToString("\",\"").Replace("share\\sync_and_remove\\", "")}\"];\n"); + }; + } + + static bool FindUser(byte[] hash, out (string name, string uuid) user) + { + DtsodV22 usersdb = new(File.ReadAllText("users.dtsod")); + user = new(); + if (usersdb.ContainsKey(hash.HashToString())) + { + user.name = usersdb[hash.HashToString()]["username"]; + user.uuid = usersdb[hash.HashToString()]["uuid"]; + return true; + } + else return false; + } + } +} diff --git a/minecraft-launcher-server/launcher-server.csproj b/minecraft-launcher-server/launcher-server.csproj new file mode 100644 index 0000000..e6daf15 --- /dev/null +++ b/minecraft-launcher-server/launcher-server.csproj @@ -0,0 +1,59 @@ + + + + + Debug + AnyCPU + {1DC6892C-5DC8-4C1C-94C1-CE695BD2DBC2} + Exe + launcher_server + minecraft-launcher-server + v4.8 + 9.0 + 512 + true + true + + + AnyCPU + none + true + bin\ + DEBUG;TRACE + prompt + 4 + + + launcher_server.Server + + + + + + + + + + + + + + + + + + + Always + + + + + {57cdc0ef-31c9-4859-90e5-ad0b302c5eae} + DTLib + + + + + del /f /q minecraft-launcher-server.exe.config + + \ No newline at end of file diff --git a/minecraft-launcher-server/launcher-server.dtsod b/minecraft-launcher-server/launcher-server.dtsod new file mode 100644 index 0000000..61ed54f --- /dev/null +++ b/minecraft-launcher-server/launcher-server.dtsod @@ -0,0 +1,2 @@ +local_ip: ""; +local_port: 25000; \ No newline at end of file diff --git a/minecraft-launcher.sln b/minecraft-launcher.sln new file mode 100644 index 0000000..323f38d --- /dev/null +++ b/minecraft-launcher.sln @@ -0,0 +1,51 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32104.313 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "launcher-client", "minecraft-launcher-client\launcher-client.csproj", "{49ADEFCE-DA46-4229-997C-3D43DD600627}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "launcher-server", "minecraft-launcher-server\launcher-server.csproj", "{1DC6892C-5DC8-4C1C-94C1-CE695BD2DBC2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DTLib", "..\DTLib\DTLib\DTLib.csproj", "{57CDC0EF-31C9-4859-90E5-AD0B302C5EAE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Build|Any CPU = Build|Any CPU + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + Release-net48|Any CPU = Release-net48|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {49ADEFCE-DA46-4229-997C-3D43DD600627}.Build|Any CPU.ActiveCfg = Build|Any CPU + {49ADEFCE-DA46-4229-997C-3D43DD600627}.Build|Any CPU.Build.0 = Build|Any CPU + {49ADEFCE-DA46-4229-997C-3D43DD600627}.Debug|Any CPU.ActiveCfg = Build|Any CPU + {49ADEFCE-DA46-4229-997C-3D43DD600627}.Debug|Any CPU.Build.0 = Build|Any CPU + {49ADEFCE-DA46-4229-997C-3D43DD600627}.Release|Any CPU.ActiveCfg = Build|Any CPU + {49ADEFCE-DA46-4229-997C-3D43DD600627}.Release|Any CPU.Build.0 = Build|Any CPU + {49ADEFCE-DA46-4229-997C-3D43DD600627}.Release-net48|Any CPU.ActiveCfg = Build|Any CPU + {49ADEFCE-DA46-4229-997C-3D43DD600627}.Release-net48|Any CPU.Build.0 = Build|Any CPU + {1DC6892C-5DC8-4C1C-94C1-CE695BD2DBC2}.Build|Any CPU.ActiveCfg = Build|Any CPU + {1DC6892C-5DC8-4C1C-94C1-CE695BD2DBC2}.Build|Any CPU.Build.0 = Build|Any CPU + {1DC6892C-5DC8-4C1C-94C1-CE695BD2DBC2}.Debug|Any CPU.ActiveCfg = Build|Any CPU + {1DC6892C-5DC8-4C1C-94C1-CE695BD2DBC2}.Debug|Any CPU.Build.0 = Build|Any CPU + {1DC6892C-5DC8-4C1C-94C1-CE695BD2DBC2}.Release|Any CPU.ActiveCfg = Build|Any CPU + {1DC6892C-5DC8-4C1C-94C1-CE695BD2DBC2}.Release|Any CPU.Build.0 = Build|Any CPU + {1DC6892C-5DC8-4C1C-94C1-CE695BD2DBC2}.Release-net48|Any CPU.ActiveCfg = Build|Any CPU + {1DC6892C-5DC8-4C1C-94C1-CE695BD2DBC2}.Release-net48|Any CPU.Build.0 = Build|Any CPU + {57CDC0EF-31C9-4859-90E5-AD0B302C5EAE}.Build|Any CPU.ActiveCfg = Debug|Any CPU + {57CDC0EF-31C9-4859-90E5-AD0B302C5EAE}.Build|Any CPU.Build.0 = Debug|Any CPU + {57CDC0EF-31C9-4859-90E5-AD0B302C5EAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {57CDC0EF-31C9-4859-90E5-AD0B302C5EAE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {57CDC0EF-31C9-4859-90E5-AD0B302C5EAE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {57CDC0EF-31C9-4859-90E5-AD0B302C5EAE}.Release|Any CPU.Build.0 = Release|Any CPU + {57CDC0EF-31C9-4859-90E5-AD0B302C5EAE}.Release-net48|Any CPU.ActiveCfg = Release-net48|Any CPU + {57CDC0EF-31C9-4859-90E5-AD0B302C5EAE}.Release-net48|Any CPU.Build.0 = Release-net48|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5D358070-7ABE-4BD6-9A87-6A5BE8CB6BC9} + EndGlobalSection +EndGlobal