From 47d1baae275e646030ba7158b035a7d09f74fa32 Mon Sep 17 00:00:00 2001 From: autumn <34-paradoxical_autumn@users.noreply.gitlab.1024cats.social> Date: Mon, 16 Feb 2026 22:46:21 +0000 Subject: [PATCH] Initial port --- .gitignore | 359 ++++++++++++++++++++++ MinecraftClient/Client.cs | 70 +++++ MinecraftClient/MessageEncoder.cs | 65 ++++ MinecraftClient/MinecraftClient.csproj | 8 + MinecraftClient/Shell.cs | 69 +++++ mc-manager.sln | 28 ++ mc-manager/Commands/DefaultCommand.cs | 129 ++++++++ mc-manager/Commands/RemoteServerSystem.cs | 104 +++++++ mc-manager/Program.cs | 24 ++ mc-manager/Strings.cs | 23 ++ mc-manager/Utils/Utils.cs | 64 ++++ mc-manager/mc-manager.csproj | 22 ++ 12 files changed, 965 insertions(+) create mode 100755 .gitignore create mode 100644 MinecraftClient/Client.cs create mode 100644 MinecraftClient/MessageEncoder.cs create mode 100644 MinecraftClient/MinecraftClient.csproj create mode 100644 MinecraftClient/Shell.cs create mode 100644 mc-manager.sln create mode 100644 mc-manager/Commands/DefaultCommand.cs create mode 100644 mc-manager/Commands/RemoteServerSystem.cs create mode 100644 mc-manager/Program.cs create mode 100644 mc-manager/Strings.cs create mode 100644 mc-manager/Utils/Utils.cs create mode 100644 mc-manager/mc-manager.csproj diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..c0b0941 --- /dev/null +++ b/.gitignore @@ -0,0 +1,359 @@ +## 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 + +# prdx +version.cs +.env +pub/ +global.json +cfg/ + +# 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/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# 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 +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/ + +# 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 + +# 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 +# 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/ + +.idea diff --git a/MinecraftClient/Client.cs b/MinecraftClient/Client.cs new file mode 100644 index 0000000..f5ace60 --- /dev/null +++ b/MinecraftClient/Client.cs @@ -0,0 +1,70 @@ +using System; +using System.Net.Sockets; +using System.Threading; + +namespace MinecraftClient +{ + public class MinecraftClient : IDisposable + { + private const int MaxMessageSize = 4110; // 4096 + 14 bytes of header data. + + private TcpClient client; + private NetworkStream conn; + private int lastID = 0; + + public MinecraftClient(string host, int port) + { + client = new TcpClient(host, port); + conn = client.GetStream(); + } + + public void Dispose() + { + this.Close(); + } + + public void Close() + { + conn.Close(); + client.Close(); + } + + public bool Authenticate(string password) + { + Message resp; + return sendMessage(new Message( + password.Length + Encoder.HeaderLength, + Interlocked.Increment(ref lastID), + MessageType.Authenticate, + password + ), out resp); + } + + public bool SendCommand(string command, out Message resp) + { + return sendMessage(new Message( + command.Length + Encoder.HeaderLength, + Interlocked.Increment(ref lastID), + MessageType.Command, + command + ), out resp); + } + + private bool sendMessage(Message req, out Message resp) + { + // Send the message. + byte[] encoded = Encoder.EncodeMessage(req); + conn.Write(encoded, 0, encoded.Length); + + // Receive the response. + byte[] respBytes = new byte[MaxMessageSize]; + int bytesRead = conn.Read(respBytes, 0, respBytes.Length); + Array.Resize(ref respBytes, bytesRead); + + // Decode the response and check for errors before returning. + resp = Encoder.DecodeMessage(respBytes); + if (req.ID != resp.ID) { return false; }; + return true; + } + } +} diff --git a/MinecraftClient/MessageEncoder.cs b/MinecraftClient/MessageEncoder.cs new file mode 100644 index 0000000..7cf3a39 --- /dev/null +++ b/MinecraftClient/MessageEncoder.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MinecraftClient +{ + public enum MessageType : int + { + Response, + _, + Command, + Authenticate + } + + public readonly struct Message + { + public readonly int Length; + public readonly int ID; + public readonly MessageType Type; + public readonly String Body; + + public Message(int length, int id, MessageType type, String body) + { + Length = length; + ID = id; + Type = type; + Body = body; + } + } + + public class Encoder + { + public const int HeaderLength = 10; // Does not include 4-byte message length. + + public static byte[] EncodeMessage(Message msg) + { + List bytes = new List(); + + bytes.AddRange(BitConverter.GetBytes(msg.Length)); + bytes.AddRange(BitConverter.GetBytes(msg.ID)); + bytes.AddRange(BitConverter.GetBytes((int)msg.Type)); + bytes.AddRange(Encoding.ASCII.GetBytes(msg.Body)); + bytes.AddRange(new byte[] { 0, 0 }); + + return bytes.ToArray(); + } + + public static Message DecodeMessage(byte[] bytes) + { + int len = BitConverter.ToInt32(bytes, 0); + int id = BitConverter.ToInt32(bytes, 4); + int type = BitConverter.ToInt32(bytes, 8); + + int bodyLen = bytes.Length - (HeaderLength + 4); + if (bodyLen > 0) + { + byte[] bodyBytes = new byte[bodyLen]; + Array.Copy(bytes, 12, bodyBytes, 0, bodyLen); + Array.Resize(ref bodyBytes, bodyLen); + return new Message(len, id, (MessageType)type, Encoding.ASCII.GetString(bodyBytes)); + } + else { return new Message(len, id, (MessageType)type, ""); } + } + } +} diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj new file mode 100644 index 0000000..92e46dd --- /dev/null +++ b/MinecraftClient/MinecraftClient.csproj @@ -0,0 +1,8 @@ + + + + Exe + net9.0 + + + diff --git a/MinecraftClient/Shell.cs b/MinecraftClient/Shell.cs new file mode 100644 index 0000000..bdb831f --- /dev/null +++ b/MinecraftClient/Shell.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; + +namespace MinecraftClient +{ + class Shell + { + private const string DefaultHost = "127.0.0.1"; + private const int DefaultPort = 25575; + private const string DefaultPassword = "minecraft"; + + static void Main(string[] args) + { + string host = DefaultHost; + int port = DefaultPort; + string password = DefaultPassword; + + // Parse arguments. + for (int i = 0; i < args.Length; i++) + { + switch (args[i]) + { + case "--host": + host = args[i + 1]; + break; + case "--port": + port = int.Parse(args[i + 1]); + break; + case "--password": + password = args[i + 1]; + break; + default: + break; + } + } + + // Connect and authenticate. + using (MinecraftClient client = new MinecraftClient(host, port)) + { + if (!client.Authenticate(password)) + { + Console.WriteLine("authentication failure"); + return; + } + + // Start RCON shell. + List quitCommands = new List { "exit", "quit" }; + Console.WriteLine("Starting RCON shell. Use 'exit', 'quit', or Ctrl-C to exit."); + while (true) + { + Console.Write("> "); + String command = Console.ReadLine(); + if (quitCommands.Contains(command)) { break; } + + Message resp; + if (client.SendCommand(command, out resp)) + { + Console.WriteLine(resp.Body); + } + else + { + Console.WriteLine("Error sending command."); + break; + } + } + } + } + } +} diff --git a/mc-manager.sln b/mc-manager.sln new file mode 100644 index 0000000..ed51602 --- /dev/null +++ b/mc-manager.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mc-manager", "mc-manager\mc-manager.csproj", "{60481ECC-2C73-4C64-97C3-596F632B85BC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinecraftClient", "MinecraftClient\MinecraftClient.csproj", "{43480D23-E89C-4DE2-B60C-699993F79413}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {60481ECC-2C73-4C64-97C3-596F632B85BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60481ECC-2C73-4C64-97C3-596F632B85BC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60481ECC-2C73-4C64-97C3-596F632B85BC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60481ECC-2C73-4C64-97C3-596F632B85BC}.Release|Any CPU.Build.0 = Release|Any CPU + {43480D23-E89C-4DE2-B60C-699993F79413}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {43480D23-E89C-4DE2-B60C-699993F79413}.Debug|Any CPU.Build.0 = Debug|Any CPU + {43480D23-E89C-4DE2-B60C-699993F79413}.Release|Any CPU.ActiveCfg = Release|Any CPU + {43480D23-E89C-4DE2-B60C-699993F79413}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/mc-manager/Commands/DefaultCommand.cs b/mc-manager/Commands/DefaultCommand.cs new file mode 100644 index 0000000..3ae64ba --- /dev/null +++ b/mc-manager/Commands/DefaultCommand.cs @@ -0,0 +1,129 @@ +using System.ComponentModel; +using Spectre.Console; +using Spectre.Console.Cli; + +namespace mc_manager.Commands; + +public class DefaultCommand : Command +{ + public class Settings : CommandSettings + { + [Description("Prints version information")] + [CommandOption("-v|--version")] + public bool PrintVersion { get; set; } + } + + public override int Execute(CommandContext context, Settings settings, CancellationToken cancellationToken) + { + if (settings.PrintVersion) + { + AnsiConsole.WriteLine(Utils.Utils.GetVersion()); + } + + if (!AnsiConsole.Profile.Capabilities.Interactive) + { + AnsiConsole.MarkupLine("[red]Error: Not interactive console[/]"); + return 1; + } + + int responseCode = -1; + + while (true) + { + AnsiConsole.Clear(); + + Markups.DisplayHeader(); + + string choice = Markups.PromptForCommand(); + + try + { + switch (choice) + { + case Strings.MenuOptions.ConnectLocally: + throw new NotImplementedException(); + case Strings.MenuOptions.ConnectRemote: + RemoteServerSystem.Settings set = RemoteServerSystem.PromptForSettings(); + + responseCode = new RemoteServerSystem().Execute(context, set, cancellationToken); + break; + case "quit": + return 0; + default: + AnsiConsole.WriteLine("You shouldn't be here."); + break; + } + + } catch (Exception ex) { + AnsiConsole.MarkupLine("[red]\n---[[CRITICAL ERROR]]---\n:(\nAn unhandled exception occurred. Please report it to the developers![/]"); + AnsiConsole.WriteException(ex, new ExceptionSettings + { + Format = ExceptionFormats.ShortenEverything | ExceptionFormats.ShowLinks, + Style = new ExceptionStyle + { + Exception = new Style().Foreground(Color.Grey), + Message = new Style().Foreground(Color.White), + NonEmphasized = new Style().Foreground(Color.Cornsilk1), + Parenthesis = new Style().Foreground(Color.Cornsilk1), + Method = new Style().Foreground(Color.Red), + ParameterName = new Style().Foreground(Color.Cornsilk1), + ParameterType = new Style().Foreground(Color.Red), + Path = new Style().Foreground(Color.Red), + LineNumber = new Style().Foreground(Color.Cornsilk1), + } + }); + } + + if (responseCode == 0 || !AnsiConsole.Confirm("Return to main menu?")) + { + return 0; + } + } + } + + private static class Markups + { + public static void DisplayHeader() + { + AnsiConsole.WriteLine(); + var table = new Table().HideHeaders().NoBorder(); + + table.Title($"[royalblue1]mc manager[/] [grey]{Utils.Utils.GetVersion()}[/]"); + table.AddColumn("col1", c => c.NoWrap().RightAligned().PadRight(3)); + table.AddColumn("col2", c => c.PadRight(0)); + table.AddEmptyRow(); + + table.AddRow( + new Markup($"[royalblue1]{Strings.MenuOptions.ConnectLocally}[/]"), + new Markup($"{Strings.MenuDescs.ConnectLocally}") + ); + + table.AddRow( + new Markup($"[royalblue1]{Strings.MenuOptions.ConnectRemote}[/]"), + new Markup($"{Strings.MenuDescs.ConnectRemote}") + ); + + AnsiConsole.Write(table); + AnsiConsole.WriteLine(); + } + + public static string PromptForCommand() + { + var prompt = new SelectionPrompt() + .Title("What are you connecting to?") + .HighlightStyle(new Style(foreground: Color.SeaGreen1, decoration: Decoration.Bold)) + .AddChoices(new[] + { + Strings.MenuOptions.ConnectLocally, + Strings.MenuOptions.ConnectRemote, + "quit" + }); + + prompt.HighlightStyle = new Style(Color.Black, Color.SeaGreen1); + prompt.WrapAround = true; + prompt.DisableSearch(); + + return AnsiConsole.Prompt(prompt); + } + } +} \ No newline at end of file diff --git a/mc-manager/Commands/RemoteServerSystem.cs b/mc-manager/Commands/RemoteServerSystem.cs new file mode 100644 index 0000000..4f13d74 --- /dev/null +++ b/mc-manager/Commands/RemoteServerSystem.cs @@ -0,0 +1,104 @@ +using Spectre.Console; +using Spectre.Console.Cli; + +namespace mc_manager.Commands; + +public class RemoteServerSystem : Command +{ + public static Settings PromptForSettings() + { + string preSavedIP = Utils.Utils.GetConfig("remoteServer.IP", "127.0.0.1"); + int preSavedPort = Utils.Utils.GetConfig("remoteServer.Port", 25575); + + string ip = AnsiConsole.Prompt(new TextPrompt(Strings.Prompts.InputIP).DefaultValue(preSavedIP) + .Validate(input => + { + if (String.IsNullOrWhiteSpace(input)) + { + return false; + } + + string[] splitValues = input.Split('.'); + if (splitValues.Length != 4) + { + return false; + } + + return splitValues.All(r => byte.TryParse(r, out _)); + }, "[red]Invalid input[/]")); + int port = AnsiConsole.Prompt(new TextPrompt(Strings.Prompts.InputPort).DefaultValue(preSavedPort)); + string password = AnsiConsole.Prompt(new TextPrompt(Strings.Prompts.InputPassword).Secret()); + + Settings newSettings = new Settings + { + Ip = ip, + Port = port, + Password = password + }; + + Utils.Utils.WriteConfig("remoteServer.IP", newSettings.Ip); + Utils.Utils.WriteConfig("remoteServer.Port", newSettings.Port); + + return newSettings; + } + + public class Settings : CommandSettings + { + [CommandArgument(0, "")] + public required string Ip { get; init; } + + [CommandArgument(1, "")] + public required int Port { get; init; } + + [CommandArgument(2, "")] + public required string Password { get; init; } + } + + public override int Execute(CommandContext context, Settings settings, CancellationToken cancellationToken) + { + // AnsiConsole.Clear(); + + using MinecraftClient.MinecraftClient client = new MinecraftClient.MinecraftClient(settings.Ip, settings.Port); + AnsiConsole.Status() + .Spinner(Spinner.Known.BouncingBar) + .Start($"Connecting to: {settings.Ip}:{settings.Port}...", ctx => + { + ctx.Status = "Authenticating..."; + // ReSharper disable once AccessToDisposedClosure + if (!client.Authenticate(settings.Password)) + { + throw new UnauthorizedAccessException("Authentication failed."); + } + }); + + if (!client.SendCommand("list", out var resp)) + { + AnsiConsole.MarkupLine("[red]Command error.[/]"); + } + + AnsiConsole.WriteLine($"{resp.Body}"); + + AnsiConsole.MarkupLine("Enter QUIT or EXIT to exit."); + List quitCmds = ["quit", "exit"]; + + while (true) + { + string request = AnsiConsole.Prompt(new TextPrompt("MCM > ")); + if (quitCmds.Contains(request)) + { + break; + } + + if (request == "stop" && !AnsiConsole.Confirm("[red][invert]WARNING: You are about to stop the server! Continue? (NO UNDO!!)[/][/]", false)) + { + continue; + } + + AnsiConsole.MarkupLine(client.SendCommand(request, out resp) + ? $"[royalblue1]{resp.Body.EscapeMarkup()}[/]" + : "[red]Error sending command.[/]"); + } + + return 0; + } +} \ No newline at end of file diff --git a/mc-manager/Program.cs b/mc-manager/Program.cs new file mode 100644 index 0000000..8e41e77 --- /dev/null +++ b/mc-manager/Program.cs @@ -0,0 +1,24 @@ +using mc_manager.Commands; +using Spectre.Console.Cli; + +namespace mc_manager; + +public static class Program +{ + public static int Main(string[] args) + { + Console.OutputEncoding = System.Text.Encoding.UTF8; + Console.InputEncoding = System.Text.Encoding.UTF8; + + var app = new CommandApp(); + app.Configure(config => + { + config.SetApplicationName("mc manager"); + config.SetApplicationVersion(Utils.Utils.GetVersion()); + + config.AddCommand("remote"); + }); + + return app.Run(args); + } +} \ No newline at end of file diff --git a/mc-manager/Strings.cs b/mc-manager/Strings.cs new file mode 100644 index 0000000..1bf57a9 --- /dev/null +++ b/mc-manager/Strings.cs @@ -0,0 +1,23 @@ +namespace mc_manager; + +public static class Strings +{ + public static class MenuOptions + { + public const string ConnectLocally = "Local Server"; + public const string ConnectRemote = "Remote Server"; + } + + public static class MenuDescs + { + public const string ConnectLocally = "Connect to a local server & tail its log."; + public const string ConnectRemote = "Connect to a remote server (log tailing unsupported)"; + } + + public static class Prompts + { + public const string InputIP = "Enter IP Address: "; + public const string InputPort = "Enter Port "; + public const string InputPassword = "Enter Password: "; + } +} \ No newline at end of file diff --git a/mc-manager/Utils/Utils.cs b/mc-manager/Utils/Utils.cs new file mode 100644 index 0000000..5a71d13 --- /dev/null +++ b/mc-manager/Utils/Utils.cs @@ -0,0 +1,64 @@ +using System.Reflection; +using System.Text.Json; + +namespace mc_manager.Utils; + +public static class Utils +{ + private const string ConfigFilePath = "./cfg"; + private const string ConfigFileName = "config.json"; + public static string FullConfigFilePath => $"{ConfigFilePath}/{ConfigFileName}"; + + private static Dictionary _applicationSettings = _LoadFromFile(); + private static JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions { WriteIndented = true }; + + public static string GetVersion() + { + var version = Assembly.GetEntryAssembly()?.GetName().Version; + var versionString = version != null ? $"{version.Major}.{version.Minor}.{version.Build}" : "Unknown Version"; + + return versionString; + } + + public static void WriteConfig(string key, object value) + { + if (!Directory.Exists(ConfigFilePath)) + { + Directory.CreateDirectory(ConfigFilePath); + } + + _applicationSettings[key] = value; + + string jsonString = JsonSerializer.Serialize(_applicationSettings, _jsonSerializerOptions); + File.WriteAllText(FullConfigFilePath, jsonString); + } + + public static T GetConfig(string key, T defaultValue = default!) + { + if (!_applicationSettings.TryGetValue(key, out var value)) + return defaultValue; + + if (value is JsonElement element) + { + return JsonSerializer.Deserialize(element.GetRawText())!; + } + + return (T)Convert.ChangeType(value, typeof(T)); + } + + private static Dictionary _LoadFromFile() + { + if (!File.Exists(FullConfigFilePath)) return new Dictionary(); + + try + { + string json = File.ReadAllText(FullConfigFilePath); + return JsonSerializer.Deserialize>(json) + ?? new Dictionary(); + } + catch + { + return new Dictionary(); + } + } +} \ No newline at end of file diff --git a/mc-manager/mc-manager.csproj b/mc-manager/mc-manager.csproj new file mode 100644 index 0000000..06adf4b --- /dev/null +++ b/mc-manager/mc-manager.csproj @@ -0,0 +1,22 @@ + + + + Exe + net10.0 + mc_manager + enable + enable + + $([System.DateTime]::UtcNow.ToString("yyyy.MMdd.HHmm")) + + + + + + + + + + + +