diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 144e9819..c421eb97 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -8,7 +8,7 @@ on: env: PROJECT: "MinecraftClient" - target-version: "net8.0" +/us target-version: "net10.0" compile-flags: "--self-contained=true -c Release -p:UseAppHost=true -p:IncludeNativeLibrariesForSelfExtract=true -p:EnableCompressionInSingleFile=true -p:DebugType=Embedded" jobs: @@ -45,6 +45,11 @@ jobs: run: | echo project-path=${{ github.workspace }}/${{ env.PROJECT }} >> $GITHUB_ENV echo file-ext=${{ (startsWith(matrix.target, 'win') && '.exe') || ' ' }} >> $GITHUB_ENV + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.x - name: Setup Environment Variables run: | diff --git a/MinecraftClient/ChatBots/DiscordBridge.cs b/MinecraftClient/ChatBots/DiscordBridge.cs index b46ad770..6f3ab5c4 100644 --- a/MinecraftClient/ChatBots/DiscordBridge.cs +++ b/MinecraftClient/ChatBots/DiscordBridge.cs @@ -284,7 +284,7 @@ namespace MinecraftClient.ChatBots if (text != null) messageBuilder.WithContent(text); - messageBuilder.WithFiles(new Dictionary() { { $"attachment://{filePath}", fs } }); + messageBuilder.AddFiles(new Dictionary() { { filePath, fs } }); discordBotClient!.SendMessageAsync(discordChannel, messageBuilder).Wait(Config.Message_Send_Timeout * 1000); } @@ -301,7 +301,7 @@ namespace MinecraftClient.ChatBots if (!CanSendMessages()) return; - SendMessage(new DiscordMessageBuilder().WithFile(fileStream)); + SendMessage(new DiscordMessageBuilder().AddFile(fileStream)); } private bool CanSendMessages() diff --git a/MinecraftClient/ChatBots/Map.cs b/MinecraftClient/ChatBots/Map.cs index c0c3aa8e..6a9f6bc4 100644 --- a/MinecraftClient/ChatBots/Map.cs +++ b/MinecraftClient/ChatBots/Map.cs @@ -259,7 +259,8 @@ namespace MinecraftClient.ChatBots { using (var image = new MagickImage(fileName)) { - var size = new MagickGeometry(Config.Resize_To, Config.Resize_To); + uint resizeTo = (uint)Math.Max(Config.Resize_To, 1); + var size = new MagickGeometry(resizeTo, resizeTo); size.IgnoreAspectRatio = true; image.Resize(size); diff --git a/MinecraftClient/ChatBots/TelegramBridge.cs b/MinecraftClient/ChatBots/TelegramBridge.cs index f30b7107..1140756a 100644 --- a/MinecraftClient/ChatBots/TelegramBridge.cs +++ b/MinecraftClient/ChatBots/TelegramBridge.cs @@ -12,7 +12,6 @@ using Telegram.Bot.Exceptions; using Telegram.Bot.Polling; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; -using Telegram.Bot.Types.InputFiles; using Tomlet.Attributes; using File = System.IO.File; @@ -205,7 +204,7 @@ namespace MinecraftClient.ChatBots try { - botClient!.SendTextMessageAsync(Config.ChannelId.Trim(), message, ParseMode.Markdown).Wait(Config.Message_Send_Timeout); + botClient!.SendMessage(Config.ChannelId.Trim(), message, parseMode: ParseMode.Markdown).Wait(Config.Message_Send_Timeout); } catch (Exception e) { @@ -224,9 +223,9 @@ namespace MinecraftClient.ChatBots string fileName = filePath[(filePath.IndexOf(Path.DirectorySeparatorChar) + 1)..]; Stream stream = File.OpenRead(filePath); - botClient!.SendDocumentAsync( + botClient!.SendDocument( Config.ChannelId.Trim(), - document: new InputOnlineFile(content: stream, fileName), + document: InputFile.FromStream(stream, fileName), caption: text, parseMode: ParseMode.Markdown).Wait(Config.Message_Send_Timeout * 1000); } @@ -260,14 +259,14 @@ namespace MinecraftClient.ChatBots cancellationToken = new CancellationTokenSource(); botClient.StartReceiving( - updateHandler: HandleUpdateAsync, - pollingErrorHandler: HandlePollingErrorAsync, - receiverOptions: new ReceiverOptions + HandleUpdateAsync, + HandlePollingErrorAsync, + new ReceiverOptions { // receive all update types AllowedUpdates = Array.Empty() }, - cancellationToken: cancellationToken.Token + cancellationToken.Token ); IsConnected = true; @@ -313,9 +312,9 @@ namespace MinecraftClient.ChatBots if (text.ToLower().Contains(".chatid")) { - await botClient.SendTextMessageAsync(chatId: chatId, - replyToMessageId: message.MessageId, + await botClient.SendMessage(chatId: chatId, text: $"Chat ID: {chatId}", + replyParameters: message.MessageId, cancellationToken: _cancellationToken, parseMode: ParseMode.Markdown); return; @@ -324,10 +323,10 @@ namespace MinecraftClient.ChatBots if (Config.Authorized_Chat_Ids.Length > 0 && !Config.Authorized_Chat_Ids.Contains(chatId)) { LogDebugToConsole($"Unauthorized message '{messageText}' received in a chat with with an ID: {chatId} !"); - await botClient.SendTextMessageAsync( + await botClient.SendMessage( chatId: chatId, - replyToMessageId: message.MessageId, text: Translations.bot_TelegramBridge_unauthorized, + replyParameters: message.MessageId, cancellationToken: _cancellationToken, parseMode: ParseMode.Markdown); return; @@ -347,10 +346,10 @@ namespace MinecraftClient.ChatBots if (command.ToLower().Contains("quit") || command.ToLower().Contains("exit")) { - await botClient.SendTextMessageAsync( + await botClient.SendMessage( chatId: chatId, - replyToMessageId: message.MessageId, text: $"{Translations.bot_TelegramBridge_quit_disabled}", + replyParameters: message.MessageId, cancellationToken: _cancellationToken, parseMode: ParseMode.Markdown); return;; @@ -359,11 +358,10 @@ namespace MinecraftClient.ChatBots CmdResult result = new(); PerformInternalCommand(command, ref result); - await botClient.SendTextMessageAsync( + await botClient.SendMessage( chatId: chatId, - replyToMessageId: - message.MessageId, text: $"{Translations.bot_TelegramBridge_command_executed}:\n\n{result}", + replyParameters: message.MessageId, cancellationToken: _cancellationToken, parseMode: ParseMode.Markdown); } diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index bd71f18a..68e605b1 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0 Exe publish\ false @@ -28,25 +28,22 @@ - - - - + + + - - - - - - - - - + + + + + + + + NU1701 - - + diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index ae518b78..d09a8e8a 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -70,7 +70,7 @@ namespace MinecraftClient options.Dsn = SentryDSN; options.AutoSessionTracking = true; options.IsGlobalModeEnabled = true; - options.EnableTracing = true; + options.TracesSampleRate = 1.0; options.SendDefaultPii = false; }); diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index ab00dfc6..bdc1a8eb 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -350,7 +350,7 @@ namespace MinecraftClient.Protocol.Handlers { break; } - catch (Ionic.Zlib.ZlibException) + catch (System.IO.InvalidDataException) { break; } diff --git a/MinecraftClient/Protocol/Handlers/ZlibUtils.cs b/MinecraftClient/Protocol/Handlers/ZlibUtils.cs index 62f8bf85..bff4131d 100644 --- a/MinecraftClient/Protocol/Handlers/ZlibUtils.cs +++ b/MinecraftClient/Protocol/Handlers/ZlibUtils.cs @@ -1,12 +1,10 @@ -using Ionic.Zlib; +using System.IO; +using System.IO.Compression; namespace MinecraftClient.Protocol.Handlers { /// /// Quick Zlib compression handling for network packet compression. - /// Note: Underlying compression handling is taken from the DotNetZip Library. - /// This library is open source and provided under the Microsoft Public License. - /// More info about DotNetZip at dotnetzip.codeplex.com. /// public static class ZlibUtils { @@ -17,16 +15,13 @@ namespace MinecraftClient.Protocol.Handlers /// Compressed data as a byte array public static byte[] Compress(byte[] to_compress) { - byte[] data; - using (System.IO.MemoryStream memstream = new()) + using MemoryStream memstream = new(); + using (ZLibStream stream = new(memstream, CompressionMode.Compress, leaveOpen: true)) { - using (ZlibStream stream = new(memstream, CompressionMode.Compress)) - { - stream.Write(to_compress, 0, to_compress.Length); - } - data = memstream.ToArray(); + stream.Write(to_compress, 0, to_compress.Length); } - return data; + + return memstream.ToArray(); } /// @@ -37,10 +32,20 @@ namespace MinecraftClient.Protocol.Handlers /// Decompressed data as a byte array public static byte[] Decompress(byte[] to_decompress, int size_uncompressed) { - ZlibStream stream = new(new System.IO.MemoryStream(to_decompress, false), CompressionMode.Decompress); + using MemoryStream compressedStream = new(to_decompress, writable: false); + using ZLibStream stream = new(compressedStream, CompressionMode.Decompress); + byte[] packetData_decompressed = new byte[size_uncompressed]; - stream.Read(packetData_decompressed, 0, size_uncompressed); - stream.Close(); + int totalRead = 0; + while (totalRead < size_uncompressed) + { + int read = stream.Read(packetData_decompressed, totalRead, size_uncompressed - totalRead); + if (read <= 0) + break; + + totalRead += read; + } + return packetData_decompressed; } @@ -51,12 +56,14 @@ namespace MinecraftClient.Protocol.Handlers /// Decompressed data as byte array public static byte[] Decompress(byte[] to_decompress) { - ZlibStream stream = new(new System.IO.MemoryStream(to_decompress, false), CompressionMode.Decompress); + using MemoryStream compressedStream = new(to_decompress, writable: false); + using ZLibStream stream = new(compressedStream, CompressionMode.Decompress); byte[] buffer = new byte[16 * 1024]; - using System.IO.MemoryStream decompressedBuffer = new(); + using MemoryStream decompressedBuffer = new(); int read; while ((read = stream.Read(buffer, 0, buffer.Length)) > 0) decompressedBuffer.Write(buffer, 0, read); + return decompressedBuffer.ToArray(); } } diff --git a/MinecraftClient/Protocol/ReplayHandler.cs b/MinecraftClient/Protocol/ReplayHandler.cs index be567caf..697e23c3 100644 --- a/MinecraftClient/Protocol/ReplayHandler.cs +++ b/MinecraftClient/Protocol/ReplayHandler.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.IO; +using System.IO.Compression; using System.Linq; -using Ionic.Zip; using MinecraftClient.Mapping; using MinecraftClient.Protocol.Handlers; using MinecraftClient.Protocol.Handlers.PacketPalettes; @@ -138,12 +138,18 @@ namespace MinecraftClient.Protocol using (Stream recordingFile = new FileStream(Path.Combine(temporaryCache, recordingTmpFileName), FileMode.Open)) { using Stream metaDataFile = new FileStream(Path.Combine(temporaryCache, MetaData.MetaDataFileName), FileMode.Open); - using ZipOutputStream zs = new(Path.Combine(ReplayFileDirectory, replayFileName)); - zs.PutNextEntry(recordingTmpFileName); - recordingFile.CopyTo(zs); - zs.PutNextEntry(MetaData.MetaDataFileName); - metaDataFile.CopyTo(zs); - zs.Close(); + using FileStream replayArchiveFile = new(Path.Combine(ReplayFileDirectory, replayFileName), FileMode.Create, FileAccess.Write); + using ZipArchive replayArchive = new(replayArchiveFile, ZipArchiveMode.Create); + + ZipArchiveEntry recordingEntry = replayArchive.CreateEntry(recordingTmpFileName); + using (Stream recordingEntryStream = recordingEntry.Open()) + { + recordingFile.CopyTo(recordingEntryStream); + } + + ZipArchiveEntry metadataEntry = replayArchive.CreateEntry(MetaData.MetaDataFileName); + using Stream metadataEntryStream = metadataEntry.Open(); + metaDataFile.CopyTo(metadataEntryStream); } File.Delete(Path.Combine(temporaryCache, recordingTmpFileName)); @@ -167,18 +173,29 @@ namespace MinecraftClient.Protocol using (Stream metaDataFile = new FileStream(Path.Combine(temporaryCache, MetaData.MetaDataFileName), FileMode.Open)) { - using ZipOutputStream zs = new(replayFileName); - zs.PutNextEntry(recordingTmpFileName); - // .CopyTo() method start from stream current position - // We need to reset position in order to get full content - var lastPosition = recordStream!.BaseStream.Position; - recordStream.BaseStream.Position = 0; - recordStream.BaseStream.CopyTo(zs); - recordStream.BaseStream.Position = lastPosition; + using FileStream replayArchiveFile = new(replayFileName, FileMode.Create, FileAccess.Write); + using ZipArchive replayArchive = new(replayArchiveFile, ZipArchiveMode.Create); - zs.PutNextEntry(MetaData.MetaDataFileName); - metaDataFile.CopyTo(zs); - zs.Close(); + ZipArchiveEntry recordingEntry = replayArchive.CreateEntry(recordingTmpFileName); + using (Stream recordingEntryStream = recordingEntry.Open()) + { + // .CopyTo() method start from stream current position + // We need to reset position in order to get full content + long lastPosition = recordStream!.BaseStream.Position; + try + { + recordStream.BaseStream.Position = 0; + recordStream.BaseStream.CopyTo(recordingEntryStream); + } + finally + { + recordStream.BaseStream.Position = lastPosition; + } + } + + ZipArchiveEntry metadataEntry = replayArchive.CreateEntry(MetaData.MetaDataFileName); + using Stream metadataEntryStream = metadataEntry.Open(); + metaDataFile.CopyTo(metadataEntryStream); } WriteDebugLog("Backup replay file created."); diff --git a/MinecraftClient/Scripting/DynamicRun/Builder/Compiler.cs b/MinecraftClient/Scripting/DynamicRun/Builder/Compiler.cs index 1e99f226..81417178 100644 --- a/MinecraftClient/Scripting/DynamicRun/Builder/Compiler.cs +++ b/MinecraftClient/Scripting/DynamicRun/Builder/Compiler.cs @@ -7,7 +7,6 @@ https://github.com/laurentkempe/DynamicRun/blob/master/LICENSE using System; using System.Collections.Generic; using System.IO; -using System.IO.MemoryMappedFiles; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; @@ -117,10 +116,11 @@ namespace MinecraftClient.Scripting.DynamicRun.Builder File.Copy(executablePath, tempFile); // Access the contents of the executable. - ExecutableReader e = new(); - var viewAccessor = MemoryMappedFile.CreateFromFile(tempFile, FileMode.Open).CreateViewAccessor(); - var manifest = e.ReadManifest(viewAccessor); - var files = manifest.Files; + using ExecutableReader executableReader = new(tempFile); + if (!executableReader.IsSingleFile) + throw new InvalidOperationException("[Script Error] The executable is not a single-file bundle."); + + var files = executableReader.Bundle.Files; Stream? assemblyStream; @@ -133,8 +133,8 @@ namespace MinecraftClient.Scripting.DynamicRun.Builder if (string.IsNullOrEmpty(loadedAssembly.Location)) { // Check if we can access the file from the executable. var reference = files.FirstOrDefault(x => - x.RelativePath.Remove(x.RelativePath.Length - 4) == refs.Name); - var refCount = files.Count(x => x.RelativePath.Remove(x.RelativePath.Length - 4) == refs.Name); + Path.GetFileNameWithoutExtension(x.RelativePath) == refs.Name); + var refCount = files.Count(x => Path.GetFileNameWithoutExtension(x.RelativePath) == refs.Name); if (refCount > 1) { // Safety net for the case where the assembly is referenced multiple times. // Should not happen normally, but we can make exceptions when it does happen. @@ -147,17 +147,13 @@ namespace MinecraftClient.Scripting.DynamicRun.Builder "[Script Error] The executable does not contain a referenced assembly. Assembly name: " + refs.Name); } - assemblyStream = GetStreamForFileEntry(viewAccessor, reference); + assemblyStream = reference.AsStream(); references.Add(MetadataReference.CreateFromStream(assemblyStream!)); continue; } references.Add(MetadataReference.CreateFromFile(loadedAssembly.Location)); } - - // Cleanup. - viewAccessor.Flush(); - viewAccessor.Dispose(); } else { @@ -176,14 +172,6 @@ namespace MinecraftClient.Scripting.DynamicRun.Builder assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default)); } - private static Stream? GetStreamForFileEntry(MemoryMappedViewAccessor viewAccessor, FileEntry file) - { - if (typeof(BundleExtractor).GetMethod("GetStreamForFileEntry", BindingFlags.NonPublic | BindingFlags.Static)!.Invoke(null, new object[] { viewAccessor, file }) is not Stream stream) - throw new InvalidOperationException("[Script Error] The executable does not contain the assembly. Assembly name: " + file.RelativePath); - - return stream; - } - internal struct CompileResult { internal byte[]? Assembly;