From 7febed2d49cd53a0915b2c3275a6d6cc65d3d927 Mon Sep 17 00:00:00 2001
From: Scighost <scighost@outlook.com>
Date: Tue, 25 Oct 2022 23:50:35 +0800
Subject: [PATCH 01/15] Support set audio and video encode format when merge
 into mkv

---
 src/Mergers/FFMPEG.cs |  9 ++++++
 src/Mergers/Merger.cs |  2 ++
 src/Program.cs        | 66 +++++++++++++++++++++++++++++--------------
 3 files changed, 56 insertions(+), 21 deletions(-)

diff --git a/src/Mergers/FFMPEG.cs b/src/Mergers/FFMPEG.cs
index 5e7b067..5b037e0 100644
--- a/src/Mergers/FFMPEG.cs
+++ b/src/Mergers/FFMPEG.cs
@@ -107,5 +107,14 @@ public void Merge()
             Process process = Process.Start(_ffmpeg, _command);
             process.WaitForExit();
         }
+
+        public void Merge(string audioFormat, string videoFormat)
+        {
+            _command += string.Join(" ", _inputOptions) + string.Join(" ", _mapOptions) + string.Join(" ", _metadataOptions);
+            _command += $" -c:a \"{(string.IsNullOrWhiteSpace(audioFormat) ? "copy" : audioFormat)}\" -c:v \"{(string.IsNullOrWhiteSpace(videoFormat) ? "copy" : videoFormat)}\" \"{_output}\"";
+            //Console.WriteLine(_ffmpeg + _command);
+            Process process = Process.Start(_ffmpeg, _command);
+            process.WaitForExit();
+        }
     }
 }
diff --git a/src/Mergers/Merger.cs b/src/Mergers/Merger.cs
index 494fa2f..a9cc8b6 100644
--- a/src/Mergers/Merger.cs
+++ b/src/Mergers/Merger.cs
@@ -13,5 +13,7 @@ internal interface Merger
 
         void Merge();
 
+        void Merge(string audioFormat, string videoFormat) { }
+
     }
 }
diff --git a/src/Program.cs b/src/Program.cs
index b827144..2490816 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -21,7 +21,7 @@ internal sealed class Program
         [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Retrieves the configuration file")]
         private static Task<int> Main(string[] args)
         {
-            
+
             // Loading config file
             // TODO: A LA MANO ?
             IConfiguration config = new ConfigurationBuilder()
@@ -86,9 +86,18 @@ private static Task<int> Main(string[] args)
             mkvEngineOption.SetDefaultValue("internal");
             mkvEngineOption.AddAlias("-e");
 
+            var audioFormatOption = new Option<string>(
+                name: "--audio-format",
+                description: "Audio encode format in MKV file, the original is PCM.");
+            audioFormatOption.AddAlias("-af");
+
+            var videoFormatOption = new Option<string>(
+                name: "--video-format",
+                description: "Video encode format in MKV file, the original is VP9.");
+            videoFormatOption.AddAlias("-vf");
 
             var rootCommand = new RootCommand("A command line program playing with the cutscenes files (USM) from Genshin Impact.");
-            
+
             rootCommand.AddGlobalOption(outputFolderOption);
             rootCommand.AddGlobalOption(noCleanupOption);
 
@@ -99,15 +108,19 @@ private static Task<int> Main(string[] args)
                 key2Option,
                 subsOption,
                 mergeOption,
-                mkvEngineOption
+                mkvEngineOption,
+                audioFormatOption,
+                videoFormatOption,
             };
 
             var batchDemuxCommand = new Command("batchDemux", "Tries to demux all .usm files in the specified folder")
-            {   
+            {
                 usmFolderArg,
                 subsOption,
                 mergeOption,
-                mkvEngineOption
+                mkvEngineOption,
+                audioFormatOption,
+                videoFormatOption,
             };
 
             //var hcaDecrypt = new Command();
@@ -123,37 +136,37 @@ private static Task<int> Main(string[] args)
 
 
             // Command Handlers
-            demuxUsmCommand.SetHandler(async (FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup) => 
+            demuxUsmCommand.SetHandler(async (FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup, string audioFormat, string videoFormat) =>
                 {
-                    await DemuxUsmCommand(file, key1, key2, output, engine, merge, subs, noCleanup);
-                }, demuxFileOption, key1Option, key2Option, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption);
+                    await DemuxUsmCommand(file, key1, key2, output, engine, merge, subs, noCleanup, audioFormat, videoFormat);
+                }, demuxFileOption, key1Option, key2Option, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption, audioFormatOption, videoFormatOption);
 
 
-            batchDemuxCommand.SetHandler(async (DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup) =>
+            batchDemuxCommand.SetHandler(async (DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup, string audioFormat, string videoFormat) =>
             {
                 var timer = System.Diagnostics.Stopwatch.StartNew();
-                await BatchDemuxCommand(inputDir, outputDir, engine, merge, subs, noCleanup);
+                await BatchDemuxCommand(inputDir, outputDir, engine, merge, subs, noCleanup, audioFormat, videoFormat);
                 timer.Stop();
                 Console.WriteLine($"{timer.ElapsedMilliseconds}ms elapsed");
             },
-                usmFolderArg, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption);
+                usmFolderArg, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption, audioFormatOption, videoFormatOption);
 
             convertHcaCommand.SetHandler(async (FileSystemInfo input, DirectoryInfo? output, bool noCleanup) =>
             {
                 await ConvertHcaCommand(input, output /*, noCleanup*/);
-            }, 
+            },
                 hcaInputArg, outputFolderOption, noCleanupOption);
 
             return Task.FromResult(rootCommand.InvokeAsync(args).Result);
         }
 
-        private static async Task DemuxUsmCommand(FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup)
+        private static async Task DemuxUsmCommand(FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup, string audioFormat, string videoFormat)
         {
             if (file == null) throw new ArgumentNullException("No file provided.");
             if (!file.Exists) throw new ArgumentException("File {0} does not exist.", file.Name);
             if (!file.Name.EndsWith(".usm"))
                 throw new ArgumentException($"File {file.Name} provided isn't a .usm file.");
-            if (key1!=null && key2!= null && (key1.Length != 8 || key2.Length != 8)) throw new ArgumentException("Keys are invalid.");
+            if (key1 != null && key2 != null && (key1.Length != 8 || key2.Length != 8)) throw new ArgumentException("Keys are invalid.");
             string outputArg = output == null
                 ? file.Directory.FullName
                 : ((output.Exists) ? output.FullName : throw new ArgumentException("Output directory is invalid."));
@@ -163,12 +176,12 @@ private static async Task DemuxUsmCommand(FileInfo file, string key1, string key
             Demuxer.Demux(file.FullName, key1Arg, key2Arg, outputArg);
             if (merge)
             {
-                MergeFiles(outputArg, Path.GetFileNameWithoutExtension(file.FullName), engine, subs);
+                MergeFiles(outputArg, Path.GetFileNameWithoutExtension(file.FullName), engine, subs, audioFormat, videoFormat);
                 if (!noCleanup) CleanFiles(outputArg, Path.GetFileNameWithoutExtension(file.FullName));
             }
         }
 
-        private static async Task BatchDemuxCommand(DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup)
+        private static async Task BatchDemuxCommand(DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup, string audioFormat, string videoFormat)
         {
             if (inputDir is not { Exists: true }) throw new DirectoryNotFoundException("Input directory is invalid.");
             string outputArg = (outputDir == null)
@@ -180,7 +193,7 @@ private static async Task BatchDemuxCommand(DirectoryInfo inputDir, DirectoryInf
                 Demuxer.Demux(f, Array.Empty<byte>(), Array.Empty<byte>(), outputArg);
                 if (!merge) continue;
 
-                MergeFiles(outputArg, Path.GetFileNameWithoutExtension(f), engine, subs);
+                MergeFiles(outputArg, Path.GetFileNameWithoutExtension(f), engine, subs, audioFormat, videoFormat);
                 if (!noCleanup) CleanFiles(outputArg, Path.GetFileNameWithoutExtension(f));
             }
         }
@@ -213,9 +226,13 @@ private static async Task ConvertHcaCommand(FileSystemInfo input, DirectoryInfo?
             }
         }
 
-        private static void MergeFiles(string outputPath, string basename, string engine, bool subs)
+        private static void MergeFiles(string outputPath, string basename, string engine, bool subs, string audioFormat, string videoFormat)
         {
             Merger merger;
+            if (!string.IsNullOrWhiteSpace(audioFormat) || !string.IsNullOrWhiteSpace(videoFormat))
+            {
+                engine = "ffmpeg";
+            }
             switch (engine)
             {
                 case "internal":
@@ -233,14 +250,14 @@ private static void MergeFiles(string outputPath, string basename, string engine
                 case "ffmpeg":
                     Console.WriteLine("Merging using ffmpeg.");
                     merger = File.Exists(
-                        Path.GetFileNameWithoutExtension(settings.FfmpegPath) == "ffmpeg" ? settings.FfmpegPath : "") 
+                        Path.GetFileNameWithoutExtension(settings.FfmpegPath) == "ffmpeg" ? settings.FfmpegPath : "")
                         ? new FFMPEG(Path.Combine(outputPath, basename + ".mkv"), settings.FfmpegPath) : new FFMPEG(Path.Combine(outputPath, basename + ".mkv"));
                     merger.AddVideoTrack(Path.Combine(outputPath, basename + ".ivf"));
                     break;
                 default:
                     throw new ArgumentException("Not implemented");
             }
-            
+
             foreach (string f in Directory.EnumerateFiles(outputPath, $"{basename}_*.wav"))
             {
                 if (!int.TryParse(Path.GetFileNameWithoutExtension(f)[^1..], out int language)) // Extracting language number from filename
@@ -301,7 +318,14 @@ private static void MergeFiles(string outputPath, string basename, string engine
                 else Console.WriteLine($"No subtitles found for cutscene {basename}");
             }
             // Merging the file
-            merger.Merge();
+            if (!string.IsNullOrWhiteSpace(audioFormat) || !string.IsNullOrWhiteSpace(videoFormat))
+            {
+                merger.Merge(audioFormat, videoFormat);
+            }
+            else
+            {
+                merger.Merge();
+            }
         }
 
         private static void CleanFiles(string outputPath, string basename)

From deccf3a189f94345f8e2c1ba54008e1fddd8dd84 Mon Sep 17 00:00:00 2001
From: Scighost <scighost@outlook.com>
Date: Wed, 26 Oct 2022 21:29:32 +0800
Subject: [PATCH 02/15] Improve endian convert method

---
 src/Utils/Utils.cs | 93 +++++-----------------------------------------
 1 file changed, 9 insertions(+), 84 deletions(-)

diff --git a/src/Utils/Utils.cs b/src/Utils/Utils.cs
index 1df5823..91235b5 100644
--- a/src/Utils/Utils.cs
+++ b/src/Utils/Utils.cs
@@ -1,114 +1,38 @@
 
+using System.Net;
+
 namespace GICutscenes.Utils
 {
     internal class Tools
     {
         public static ushort Bswap(ushort v)
         {
-            if (!BitConverter.IsLittleEndian) return v;
-            ushort r = (ushort)(v & 0xFF);
-            r <<= 8;
-            v >>= 8;
-            r |= (ushort)(v & 0xFF);
-            return r;
+            return (ushort)IPAddress.HostToNetworkOrder((short)v);
         }
 
         public static short Bswap(short v)
         {
-            if (!BitConverter.IsLittleEndian) return v;
-            short r = (short)(v & 0xFF);
-            r <<= 8;
-            v >>= 8;
-            r |= (short)(v & 0xFF);
-            return r;
+            return IPAddress.HostToNetworkOrder(v);
         }
 
         public static int Bswap(int v)
         {
-            if (!BitConverter.IsLittleEndian) return v;
-            int r = v & 0xFF;
-            r <<= 8;
-            v >>= 8;
-            r |= v & 0xFF;
-            r <<= 8;
-            v >>= 8;
-            r |= v & 0xFF;
-            r <<= 8;
-            v >>= 8;
-            r |= v & 0xFF;
-            return r;
+            return IPAddress.HostToNetworkOrder(v);
         }
 
         public static uint Bswap(uint v)
         {
-            if (!BitConverter.IsLittleEndian) return v;
-            uint r = v & 0xFF;
-            r <<= 8;
-            v >>= 8;
-            r |= v & 0xFF;
-            r <<= 8;
-            v >>= 8;
-            r |= v & 0xFF;
-            r <<= 8;
-            v >>= 8;
-            r |= v & 0xFF;
-            return r;
+            return (uint)IPAddress.HostToNetworkOrder((int)v);
         }
 
         public static long Bswap(long v)
         {
-            if (!BitConverter.IsLittleEndian) return v;
-            long r = v & 0xFF;
-            r <<= 8;
-            v >>= 8;
-            r |= v & 0xFF;
-            r <<= 8;
-            v >>= 8;
-            r |= v & 0xFF;
-            r <<= 8;
-            v >>= 8;
-            r |= v & 0xFF;
-            r <<= 8;
-            v >>= 8;
-            r |= v & 0xFF;
-            r <<= 8;
-            v >>= 8;
-            r |= v & 0xFF;
-            r <<= 8;
-            v >>= 8;
-            r |= v & 0xFF;
-            r <<= 8;
-            v >>= 8;
-            r |= v & 0xFF;
-            return r;
+            return IPAddress.HostToNetworkOrder(v);
         }
 
         public static ulong Bswap(ulong v)
         {
-            if (!BitConverter.IsLittleEndian) return v;
-            ulong r = v & 0xFF;
-            r <<= 8;
-            v >>= 8;
-            r |= v & 0xFF;
-            r <<= 8;
-            v >>= 8;
-            r |= v & 0xFF;
-            r <<= 8;
-            v >>= 8;
-            r |= v & 0xFF;
-            r <<= 8;
-            v >>= 8;
-            r |= v & 0xFF;
-            r <<= 8;
-            v >>= 8;
-            r |= v & 0xFF;
-            r <<= 8;
-            v >>= 8;
-            r |= v & 0xFF;
-            r <<= 8;
-            v >>= 8;
-            r |= v & 0xFF;
-            return r;
+            return (ulong)IPAddress.HostToNetworkOrder((long)v);
         }
 
         public static float Bswap(float v)
@@ -117,6 +41,7 @@ public static float Bswap(float v)
             uint i = Bswap(BitConverter.SingleToUInt32Bits(v));
             return BitConverter.UInt32BitsToSingle(i);
         }
+
         public static uint Ceil2(uint a, uint b) { return (uint)(b > 0 ? a / b + (a % b != 0 ? 1 : 0) : 0); }
     }
 }

From 98102817da2303b20f77f3c4a8c213103ce49798 Mon Sep 17 00:00:00 2001
From: Scighost <scighost@outlook.com>
Date: Wed, 26 Oct 2022 22:08:39 +0800
Subject: [PATCH 03/15] Use absolute path

---
 src/Demuxer.cs |  7 ++++---
 src/Program.cs | 18 +++++++++---------
 2 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/src/Demuxer.cs b/src/Demuxer.cs
index e6751ca..b5d7913 100644
--- a/src/Demuxer.cs
+++ b/src/Demuxer.cs
@@ -45,9 +45,10 @@ private static ulong EncryptionKeyInFilename(string filename)
 
         private static (ulong, bool) EncryptionKeyInBLK(string videoFilename)
         {
-            if (!File.Exists("versions.json")) throw new FileNotFoundException("File versions.json couldn't be found in the folder of the tool.");
+            var versionsFilePath = Path.Combine(AppContext.BaseDirectory, "versions.json");
+            if (!File.Exists(versionsFilePath)) throw new FileNotFoundException("File versions.json couldn't be found in the folder of the tool.");
             videoFilename = Path.GetFileNameWithoutExtension(videoFilename);
-            string jsonString = File.ReadAllText("versions.json");
+            string jsonString = File.ReadAllText(versionsFilePath);
             VersionList? versions = JsonSerializer.Deserialize<VersionList>(jsonString, VersionJson.Default.VersionList);
             if (versions?.list == null) throw new JsonException("Json content from versions.json is invalid or couldn't be parsed...");
             Version? v = Array.Find(versions.list, x => (x.videos != null && x.videos.Contains(videoFilename)) || (x.videoGroups != null && Array.Exists(x.videoGroups, y => y.videos != null && y.videos.Contains(videoFilename))));
@@ -56,7 +57,7 @@ private static (ulong, bool) EncryptionKeyInBLK(string videoFilename)
             if (v.videoGroups != null)
             {
                 key = Array.Find(v.videoGroups, y => y.videos != null && y.videos.Contains(videoFilename))?.key ?? throw new KeyNotFoundException("Unable to find the second key in versions.json for " + videoFilename);
-            } 
+            }
             return (key, v.encAudio ?? false);
         }
 
diff --git a/src/Program.cs b/src/Program.cs
index b827144..613066f 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -21,11 +21,11 @@ internal sealed class Program
         [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Retrieves the configuration file")]
         private static Task<int> Main(string[] args)
         {
-            
+
             // Loading config file
             // TODO: A LA MANO ?
             IConfiguration config = new ConfigurationBuilder()
-                .AddJsonFile("appsettings.json")
+                .AddJsonFile(Path.Combine(AppContext.BaseDirectory, "appsettings.json"))
                 .AddEnvironmentVariables()
                 .Build();
 
@@ -88,7 +88,7 @@ private static Task<int> Main(string[] args)
 
 
             var rootCommand = new RootCommand("A command line program playing with the cutscenes files (USM) from Genshin Impact.");
-            
+
             rootCommand.AddGlobalOption(outputFolderOption);
             rootCommand.AddGlobalOption(noCleanupOption);
 
@@ -103,7 +103,7 @@ private static Task<int> Main(string[] args)
             };
 
             var batchDemuxCommand = new Command("batchDemux", "Tries to demux all .usm files in the specified folder")
-            {   
+            {
                 usmFolderArg,
                 subsOption,
                 mergeOption,
@@ -123,7 +123,7 @@ private static Task<int> Main(string[] args)
 
 
             // Command Handlers
-            demuxUsmCommand.SetHandler(async (FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup) => 
+            demuxUsmCommand.SetHandler(async (FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup) =>
                 {
                     await DemuxUsmCommand(file, key1, key2, output, engine, merge, subs, noCleanup);
                 }, demuxFileOption, key1Option, key2Option, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption);
@@ -141,7 +141,7 @@ private static Task<int> Main(string[] args)
             convertHcaCommand.SetHandler(async (FileSystemInfo input, DirectoryInfo? output, bool noCleanup) =>
             {
                 await ConvertHcaCommand(input, output /*, noCleanup*/);
-            }, 
+            },
                 hcaInputArg, outputFolderOption, noCleanupOption);
 
             return Task.FromResult(rootCommand.InvokeAsync(args).Result);
@@ -153,7 +153,7 @@ private static async Task DemuxUsmCommand(FileInfo file, string key1, string key
             if (!file.Exists) throw new ArgumentException("File {0} does not exist.", file.Name);
             if (!file.Name.EndsWith(".usm"))
                 throw new ArgumentException($"File {file.Name} provided isn't a .usm file.");
-            if (key1!=null && key2!= null && (key1.Length != 8 || key2.Length != 8)) throw new ArgumentException("Keys are invalid.");
+            if (key1 != null && key2 != null && (key1.Length != 8 || key2.Length != 8)) throw new ArgumentException("Keys are invalid.");
             string outputArg = output == null
                 ? file.Directory.FullName
                 : ((output.Exists) ? output.FullName : throw new ArgumentException("Output directory is invalid."));
@@ -233,14 +233,14 @@ private static void MergeFiles(string outputPath, string basename, string engine
                 case "ffmpeg":
                     Console.WriteLine("Merging using ffmpeg.");
                     merger = File.Exists(
-                        Path.GetFileNameWithoutExtension(settings.FfmpegPath) == "ffmpeg" ? settings.FfmpegPath : "") 
+                        Path.GetFileNameWithoutExtension(settings.FfmpegPath) == "ffmpeg" ? settings.FfmpegPath : "")
                         ? new FFMPEG(Path.Combine(outputPath, basename + ".mkv"), settings.FfmpegPath) : new FFMPEG(Path.Combine(outputPath, basename + ".mkv"));
                     merger.AddVideoTrack(Path.Combine(outputPath, basename + ".ivf"));
                     break;
                 default:
                     throw new ArgumentException("Not implemented");
             }
-            
+
             foreach (string f in Directory.EnumerateFiles(outputPath, $"{basename}_*.wav"))
             {
                 if (!int.TryParse(Path.GetFileNameWithoutExtension(f)[^1..], out int language)) // Extracting language number from filename

From d3a30d0877adbf4c6423af857eaeaff5e099d76c Mon Sep 17 00:00:00 2001
From: Scighost <scighost@outlook.com>
Date: Wed, 26 Oct 2022 22:54:26 +0800
Subject: [PATCH 04/15] Add command to reset appsettings.json

---
 src/Program.cs | 52 ++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 42 insertions(+), 10 deletions(-)

diff --git a/src/Program.cs b/src/Program.cs
index 613066f..ba82e1e 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -4,6 +4,7 @@
 using GICutscenes.Mergers;
 using GICutscenes.Mergers.GIMKV;
 using System.Diagnostics.CodeAnalysis;
+using System.Text.Json;
 
 namespace GICutscenes
 {
@@ -16,21 +17,12 @@ internal sealed class Settings
     }
     internal sealed class Program
     {
-        public static Settings settings;
+        public static Settings? settings;
 
         [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Retrieves the configuration file")]
         private static Task<int> Main(string[] args)
         {
 
-            // Loading config file
-            // TODO: A LA MANO ?
-            IConfiguration config = new ConfigurationBuilder()
-                .AddJsonFile(Path.Combine(AppContext.BaseDirectory, "appsettings.json"))
-                .AddEnvironmentVariables()
-                .Build();
-
-            settings = config.GetRequiredSection(nameof(Settings)).Get<Settings>();
-
             // CLI Options
             var demuxFileOption = new Argument<FileInfo?>(
                 name: "Input file",
@@ -92,6 +84,8 @@ private static Task<int> Main(string[] args)
             rootCommand.AddGlobalOption(outputFolderOption);
             rootCommand.AddGlobalOption(noCleanupOption);
 
+            var resetCommand = new Command("reset", "Reset 'appsettings.json' file to default.");
+
             var demuxUsmCommand = new Command("demuxUsm", "Demuxes a specified .usm file to a specified folder")
             {
                 demuxFileOption,
@@ -117,20 +111,33 @@ private static Task<int> Main(string[] args)
                 hcaInputArg
             };
 
+            rootCommand.AddCommand(resetCommand);
             rootCommand.AddCommand(demuxUsmCommand);
             rootCommand.AddCommand(batchDemuxCommand);
             rootCommand.AddCommand(convertHcaCommand);
 
+            resetCommand.SetHandler(() =>
+            {
+                var obj = new { Settings = new Settings { FfmpegPath = "", MkvMergePath = "", SubsFolder = "" } };
+                var json = JsonSerializer.Serialize(obj, new JsonSerializerOptions { WriteIndented = true });
+                File.WriteAllText(Path.Combine(AppContext.BaseDirectory, "appsettings.json"), json);
+                Console.ForegroundColor = ConsoleColor.Green;
+                Console.WriteLine("'appsettings.json' has reset to default.");
+                Console.ResetColor();
+            });
+
 
             // Command Handlers
             demuxUsmCommand.SetHandler(async (FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup) =>
                 {
+                ReadSetting();
                     await DemuxUsmCommand(file, key1, key2, output, engine, merge, subs, noCleanup);
                 }, demuxFileOption, key1Option, key2Option, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption);
 
 
             batchDemuxCommand.SetHandler(async (DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup) =>
             {
+                ReadSetting();
                 var timer = System.Diagnostics.Stopwatch.StartNew();
                 await BatchDemuxCommand(inputDir, outputDir, engine, merge, subs, noCleanup);
                 timer.Stop();
@@ -147,6 +154,31 @@ private static Task<int> Main(string[] args)
             return Task.FromResult(rootCommand.InvokeAsync(args).Result);
         }
 
+
+        [RequiresUnreferencedCode("Calls Microsoft.Extensions.Configuration.ConfigurationBinder.Get<T>()")]
+        private static void ReadSetting()
+        {
+            if (settings is null)
+            {
+                // Loading config file
+                // TODO: A LA MANO ?
+                IConfiguration config = new ConfigurationBuilder()
+                    .AddJsonFile(Path.Combine(AppContext.BaseDirectory, "appsettings.json"))
+                    .AddEnvironmentVariables()
+                    .Build();
+
+                settings = config?.GetSection(nameof(Settings)).Get<Settings>();
+                if (settings is null)
+                {
+                    Console.ForegroundColor = ConsoleColor.DarkYellow;
+                    Console.WriteLine("File 'appsettings.json' has error section, use command 'reset' to reset it to default.");
+                    Console.ResetColor();
+                    Environment.Exit(1);
+                }
+            }
+        }
+
+
         private static async Task DemuxUsmCommand(FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup)
         {
             if (file == null) throw new ArgumentNullException("No file provided.");

From 7a77d34fa089e9812351aae2fb8f66b0a173a084 Mon Sep 17 00:00:00 2001
From: Scighost <scighost@outlook.com>
Date: Wed, 26 Oct 2022 22:58:44 +0800
Subject: [PATCH 05/15] null reference warning

---
 src/Program.cs | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/src/Program.cs b/src/Program.cs
index ba82e1e..a7a0705 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -129,10 +129,10 @@ private static Task<int> Main(string[] args)
 
             // Command Handlers
             demuxUsmCommand.SetHandler(async (FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup) =>
-                {
+            {
                 ReadSetting();
-                    await DemuxUsmCommand(file, key1, key2, output, engine, merge, subs, noCleanup);
-                }, demuxFileOption, key1Option, key2Option, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption);
+                await DemuxUsmCommand(file, key1, key2, output, engine, merge, subs, noCleanup);
+            }, demuxFileOption, key1Option, key2Option, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption);
 
 
             batchDemuxCommand.SetHandler(async (DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup) =>
@@ -181,13 +181,13 @@ private static void ReadSetting()
 
         private static async Task DemuxUsmCommand(FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup)
         {
-            if (file == null) throw new ArgumentNullException("No file provided.");
+            if (file == null) throw new ArgumentNullException(nameof(file), "No file provided.");
             if (!file.Exists) throw new ArgumentException("File {0} does not exist.", file.Name);
             if (!file.Name.EndsWith(".usm"))
                 throw new ArgumentException($"File {file.Name} provided isn't a .usm file.");
             if (key1 != null && key2 != null && (key1.Length != 8 || key2.Length != 8)) throw new ArgumentException("Keys are invalid.");
             string outputArg = output == null
-                ? file.Directory.FullName
+                ? file.Directory!.FullName
                 : ((output.Exists) ? output.FullName : throw new ArgumentException("Output directory is invalid."));
             Console.WriteLine($"Output folder : {outputArg}");
             byte[] key1Arg = Convert.FromHexString(key1 ?? "");
@@ -258,15 +258,15 @@ private static void MergeFiles(string outputPath, string basename, string engine
                 case "mkvmerge":
                     Console.WriteLine("Merging using mkvmerge.");
                     merger = File.Exists(
-                            Path.GetFileNameWithoutExtension(settings.MkvMergePath) == "mkvmerge" ? settings.MkvMergePath : "")
-                        ? new Mkvmerge(Path.Combine(outputPath, basename + ".mkv"), settings.MkvMergePath) : new Mkvmerge(Path.Combine(outputPath, basename + ".mkv"));
+                        Path.GetFileNameWithoutExtension(settings?.MkvMergePath)?.ToLower() == "mkvmerge" ? settings?.MkvMergePath : "")
+                        ? new Mkvmerge(Path.Combine(outputPath, basename + ".mkv"), settings!.MkvMergePath!) : new Mkvmerge(Path.Combine(outputPath, basename + ".mkv"));
                     merger.AddVideoTrack(Path.Combine(outputPath, basename + ".ivf"));
                     break;
                 case "ffmpeg":
                     Console.WriteLine("Merging using ffmpeg.");
                     merger = File.Exists(
-                        Path.GetFileNameWithoutExtension(settings.FfmpegPath) == "ffmpeg" ? settings.FfmpegPath : "")
-                        ? new FFMPEG(Path.Combine(outputPath, basename + ".mkv"), settings.FfmpegPath) : new FFMPEG(Path.Combine(outputPath, basename + ".mkv"));
+                        Path.GetFileNameWithoutExtension(settings?.FfmpegPath)?.ToLower() == "ffmpeg" ? settings?.FfmpegPath : "")
+                        ? new FFMPEG(Path.Combine(outputPath, basename + ".mkv"), settings!.FfmpegPath!) : new FFMPEG(Path.Combine(outputPath, basename + ".mkv"));
                     merger.AddVideoTrack(Path.Combine(outputPath, basename + ".ivf"));
                     break;
                 default:
@@ -282,7 +282,7 @@ private static void MergeFiles(string outputPath, string basename, string engine
 
             if (subs)
             {
-                string subsFolder = settings.SubsFolder ?? throw new ArgumentException("Configuration value is not set for the key SubsFolder.");
+                string subsFolder = settings?.SubsFolder ?? throw new ArgumentException("Configuration value is not set for the key SubsFolder.");
                 subsFolder = Path.GetFullPath(subsFolder);
                 if (!Directory.Exists(subsFolder))
                     throw new ArgumentException(

From 734f3978f806db21236b6f638f7f933e20e04b2c Mon Sep 17 00:00:00 2001
From: Scighost <scighost@outlook.com>
Date: Wed, 26 Oct 2022 23:12:10 +0800
Subject: [PATCH 06/15] Change command order

---
 src/Program.cs | 32 +++++++++++++++-----------------
 1 file changed, 15 insertions(+), 17 deletions(-)

diff --git a/src/Program.cs b/src/Program.cs
index a7a0705..c646092 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -84,8 +84,6 @@ private static Task<int> Main(string[] args)
             rootCommand.AddGlobalOption(outputFolderOption);
             rootCommand.AddGlobalOption(noCleanupOption);
 
-            var resetCommand = new Command("reset", "Reset 'appsettings.json' file to default.");
-
             var demuxUsmCommand = new Command("demuxUsm", "Demuxes a specified .usm file to a specified folder")
             {
                 demuxFileOption,
@@ -111,20 +109,12 @@ private static Task<int> Main(string[] args)
                 hcaInputArg
             };
 
-            rootCommand.AddCommand(resetCommand);
+            var resetCommand = new Command("reset", "Reset 'appsettings.json' file to default.");
+
             rootCommand.AddCommand(demuxUsmCommand);
             rootCommand.AddCommand(batchDemuxCommand);
             rootCommand.AddCommand(convertHcaCommand);
-
-            resetCommand.SetHandler(() =>
-            {
-                var obj = new { Settings = new Settings { FfmpegPath = "", MkvMergePath = "", SubsFolder = "" } };
-                var json = JsonSerializer.Serialize(obj, new JsonSerializerOptions { WriteIndented = true });
-                File.WriteAllText(Path.Combine(AppContext.BaseDirectory, "appsettings.json"), json);
-                Console.ForegroundColor = ConsoleColor.Green;
-                Console.WriteLine("'appsettings.json' has reset to default.");
-                Console.ResetColor();
-            });
+            rootCommand.AddCommand(resetCommand);
 
 
             // Command Handlers
@@ -142,14 +132,22 @@ private static Task<int> Main(string[] args)
                 await BatchDemuxCommand(inputDir, outputDir, engine, merge, subs, noCleanup);
                 timer.Stop();
                 Console.WriteLine($"{timer.ElapsedMilliseconds}ms elapsed");
-            },
-                usmFolderArg, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption);
+            }, usmFolderArg, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption);
 
             convertHcaCommand.SetHandler(async (FileSystemInfo input, DirectoryInfo? output, bool noCleanup) =>
             {
                 await ConvertHcaCommand(input, output /*, noCleanup*/);
-            },
-                hcaInputArg, outputFolderOption, noCleanupOption);
+            }, hcaInputArg, outputFolderOption, noCleanupOption);
+
+            resetCommand.SetHandler(() =>
+            {
+                var obj = new { Settings = new Settings { FfmpegPath = "", MkvMergePath = "", SubsFolder = "" } };
+                var json = JsonSerializer.Serialize(obj, new JsonSerializerOptions { WriteIndented = true });
+                File.WriteAllText(Path.Combine(AppContext.BaseDirectory, "appsettings.json"), json);
+                Console.ForegroundColor = ConsoleColor.Green;
+                Console.WriteLine("'appsettings.json' has reset to default.");
+                Console.ResetColor();
+            });
 
             return Task.FromResult(rootCommand.InvokeAsync(args).Result);
         }

From de8c090e8ac3114d15582eef297d5225de2251c4 Mon Sep 17 00:00:00 2001
From: Scighost <scighost@outlook.com>
Date: Thu, 27 Oct 2022 09:08:36 +0800
Subject: [PATCH 07/15] Handle exception

---
 src/Program.cs | 30 +++++++++++++++++++++++++-----
 1 file changed, 25 insertions(+), 5 deletions(-)

diff --git a/src/Program.cs b/src/Program.cs
index c646092..29f327a 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -1,8 +1,10 @@
-using Microsoft.Extensions.Configuration;
-using System.CommandLine;
-using GICutscenes.FileTypes;
+using GICutscenes.FileTypes;
 using GICutscenes.Mergers;
 using GICutscenes.Mergers.GIMKV;
+using Microsoft.Extensions.Configuration;
+using System.CommandLine;
+using System.CommandLine.Builder;
+using System.CommandLine.Parsing;
 using System.Diagnostics.CodeAnalysis;
 using System.Text.Json;
 
@@ -20,7 +22,7 @@ internal sealed class Program
         public static Settings? settings;
 
         [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Retrieves the configuration file")]
-        private static Task<int> Main(string[] args)
+        private static int Main(string[] args)
         {
 
             // CLI Options
@@ -78,11 +80,17 @@ private static Task<int> Main(string[] args)
             mkvEngineOption.SetDefaultValue("internal");
             mkvEngineOption.AddAlias("-e");
 
+            var stackTraceOption = new Option<bool>(
+                name: "--stack-trace",
+                description: "Show stack trace when throw exception.");
+            stackTraceOption.AddAlias("-st");
+
 
             var rootCommand = new RootCommand("A command line program playing with the cutscenes files (USM) from Genshin Impact.");
 
             rootCommand.AddGlobalOption(outputFolderOption);
             rootCommand.AddGlobalOption(noCleanupOption);
+            rootCommand.AddGlobalOption(stackTraceOption);
 
             var demuxUsmCommand = new Command("demuxUsm", "Demuxes a specified .usm file to a specified folder")
             {
@@ -149,7 +157,19 @@ private static Task<int> Main(string[] args)
                 Console.ResetColor();
             });
 
-            return Task.FromResult(rootCommand.InvokeAsync(args).Result);
+            return new CommandLineBuilder(rootCommand).UseDefaults().UseExceptionHandler((ex, context) =>
+            {
+                Console.ForegroundColor = ConsoleColor.Red;
+                if (context.ParseResult.GetValueForOption(stackTraceOption))
+                {
+                    Console.Error.WriteLine(ex);
+                }
+                else
+                {
+                    Console.Error.WriteLine($"{ex.GetType()}: {ex.Message}");
+                }
+                Console.ResetColor();
+            }).Build().Invoke(args);
         }
 
 

From 4ece977c711d97c1a047b6bd805cbe71994f1ead Mon Sep 17 00:00:00 2001
From: Scighost <scighost@outlook.com>
Date: Thu, 27 Oct 2022 09:21:34 +0800
Subject: [PATCH 08/15] Change async Task to void

---
 src/Program.cs | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/Program.cs b/src/Program.cs
index 29f327a..8c4a17c 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -126,25 +126,25 @@ private static int Main(string[] args)
 
 
             // Command Handlers
-            demuxUsmCommand.SetHandler(async (FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup) =>
+            demuxUsmCommand.SetHandler((FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup) =>
             {
                 ReadSetting();
-                await DemuxUsmCommand(file, key1, key2, output, engine, merge, subs, noCleanup);
+                DemuxUsmCommand(file, key1, key2, output, engine, merge, subs, noCleanup);
             }, demuxFileOption, key1Option, key2Option, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption);
 
 
-            batchDemuxCommand.SetHandler(async (DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup) =>
+            batchDemuxCommand.SetHandler((DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup) =>
             {
                 ReadSetting();
                 var timer = System.Diagnostics.Stopwatch.StartNew();
-                await BatchDemuxCommand(inputDir, outputDir, engine, merge, subs, noCleanup);
+                BatchDemuxCommand(inputDir, outputDir, engine, merge, subs, noCleanup);
                 timer.Stop();
                 Console.WriteLine($"{timer.ElapsedMilliseconds}ms elapsed");
             }, usmFolderArg, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption);
 
-            convertHcaCommand.SetHandler(async (FileSystemInfo input, DirectoryInfo? output, bool noCleanup) =>
+            convertHcaCommand.SetHandler((FileSystemInfo input, DirectoryInfo? output, bool noCleanup) =>
             {
-                await ConvertHcaCommand(input, output /*, noCleanup*/);
+                ConvertHcaCommand(input, output /*, noCleanup*/);
             }, hcaInputArg, outputFolderOption, noCleanupOption);
 
             resetCommand.SetHandler(() =>
@@ -197,7 +197,7 @@ private static void ReadSetting()
         }
 
 
-        private static async Task DemuxUsmCommand(FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup)
+        private static void DemuxUsmCommand(FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup)
         {
             if (file == null) throw new ArgumentNullException(nameof(file), "No file provided.");
             if (!file.Exists) throw new ArgumentException("File {0} does not exist.", file.Name);
@@ -218,7 +218,7 @@ private static async Task DemuxUsmCommand(FileInfo file, string key1, string key
             }
         }
 
-        private static async Task BatchDemuxCommand(DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup)
+        private static void BatchDemuxCommand(DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup)
         {
             if (inputDir is not { Exists: true }) throw new DirectoryNotFoundException("Input directory is invalid.");
             string outputArg = (outputDir == null)
@@ -235,7 +235,7 @@ private static async Task BatchDemuxCommand(DirectoryInfo inputDir, DirectoryInf
             }
         }
 
-        private static async Task ConvertHcaCommand(FileSystemInfo input, DirectoryInfo? output)
+        private static void ConvertHcaCommand(FileSystemInfo input, DirectoryInfo? output)
         {
             if (!input.Exists) throw new ArgumentException("No file or directory given.");
             string outputArg = (output == null)

From ce8e10e1bc06ce14cd04ec58e91facac92fb914f Mon Sep 17 00:00:00 2001
From: Scighost <scighost@outlook.com>
Date: Thu, 27 Oct 2022 10:00:17 +0800
Subject: [PATCH 09/15] Add update command

---
 src/Program.cs             |  92 +++++++++++++++++++++++++++++++---
 src/Utils/GithubRelease.cs | 100 +++++++++++++++++++++++++++++++++++++
 2 files changed, 184 insertions(+), 8 deletions(-)
 create mode 100644 src/Utils/GithubRelease.cs

diff --git a/src/Program.cs b/src/Program.cs
index 8c4a17c..f7422f9 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -1,11 +1,14 @@
 using GICutscenes.FileTypes;
 using GICutscenes.Mergers;
 using GICutscenes.Mergers.GIMKV;
+using GICutscenes.Utils;
 using Microsoft.Extensions.Configuration;
 using System.CommandLine;
 using System.CommandLine.Builder;
 using System.CommandLine.Parsing;
+using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
+using System.Net;
 using System.Text.Json;
 
 namespace GICutscenes
@@ -80,17 +83,22 @@ private static int Main(string[] args)
             mkvEngineOption.SetDefaultValue("internal");
             mkvEngineOption.AddAlias("-e");
 
+            var notOpenBrowserOption = new Option<bool>(
+                name: "-n",
+                description: "Do not open browser if there's new version.");
+
             var stackTraceOption = new Option<bool>(
                 name: "--stack-trace",
                 description: "Show stack trace when throw exception.");
             stackTraceOption.AddAlias("-st");
 
+            var proxyOption = new Option<string>(
+                name: "--proxy",
+                description: "Specifies a proxy server for the request.");
+            proxyOption.AddAlias("-p");
 
-            var rootCommand = new RootCommand("A command line program playing with the cutscenes files (USM) from Genshin Impact.");
 
-            rootCommand.AddGlobalOption(outputFolderOption);
-            rootCommand.AddGlobalOption(noCleanupOption);
-            rootCommand.AddGlobalOption(stackTraceOption);
+            var rootCommand = new RootCommand("A command line program playing with the cutscenes files (USM) from Genshin Impact.");
 
             var demuxUsmCommand = new Command("demuxUsm", "Demuxes a specified .usm file to a specified folder")
             {
@@ -99,7 +107,9 @@ private static int Main(string[] args)
                 key2Option,
                 subsOption,
                 mergeOption,
-                mkvEngineOption
+                mkvEngineOption,
+                outputFolderOption,
+                noCleanupOption,
             };
 
             var batchDemuxCommand = new Command("batchDemux", "Tries to demux all .usm files in the specified folder")
@@ -107,14 +117,23 @@ private static int Main(string[] args)
                 usmFolderArg,
                 subsOption,
                 mergeOption,
-                mkvEngineOption
+                mkvEngineOption,
+                outputFolderOption,
+                noCleanupOption,
             };
 
             //var hcaDecrypt = new Command();
 
             var convertHcaCommand = new Command("convertHca", "Converts input .hca files into .wav files")
             {
-                hcaInputArg
+                hcaInputArg,
+                outputFolderOption,
+            };
+
+            var updateCommand = new Command("update", "Update for usm secret key and GICutscenes.")
+            {
+                notOpenBrowserOption,
+                proxyOption,
             };
 
             var resetCommand = new Command("reset", "Reset 'appsettings.json' file to default.");
@@ -122,6 +141,7 @@ private static int Main(string[] args)
             rootCommand.AddCommand(demuxUsmCommand);
             rootCommand.AddCommand(batchDemuxCommand);
             rootCommand.AddCommand(convertHcaCommand);
+            rootCommand.AddCommand(updateCommand);
             rootCommand.AddCommand(resetCommand);
 
 
@@ -136,7 +156,7 @@ private static int Main(string[] args)
             batchDemuxCommand.SetHandler((DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup) =>
             {
                 ReadSetting();
-                var timer = System.Diagnostics.Stopwatch.StartNew();
+                var timer = Stopwatch.StartNew();
                 BatchDemuxCommand(inputDir, outputDir, engine, merge, subs, noCleanup);
                 timer.Stop();
                 Console.WriteLine($"{timer.ElapsedMilliseconds}ms elapsed");
@@ -147,6 +167,11 @@ private static int Main(string[] args)
                 ConvertHcaCommand(input, output /*, noCleanup*/);
             }, hcaInputArg, outputFolderOption, noCleanupOption);
 
+            updateCommand.SetHandler(async (bool notOpenBrowser, string proxy) =>
+            {
+                await UpdateAsync(notOpenBrowser, proxy);
+            }, notOpenBrowserOption, proxyOption);
+
             resetCommand.SetHandler(() =>
             {
                 var obj = new { Settings = new Settings { FfmpegPath = "", MkvMergePath = "", SubsFolder = "" } };
@@ -363,5 +388,56 @@ private static void CleanFiles(string outputPath, string basename)
             foreach (string f in Directory.EnumerateFiles(outputPath, $"{basename}_*.hca")) File.Delete(f);
             foreach (string f in Directory.EnumerateFiles(outputPath, $"{basename}_*.wav")) File.Delete(f);
         }
+
+        [RequiresUnreferencedCode("Calls System.Text.Json.JsonSerializer.Deserialize<TValue>(String, JsonSerializerOptions)")]
+        private static async Task UpdateAsync(bool notOpenBroswer, string? proxy)
+        {
+            var webProxy = new WebProxy(proxy);
+            var client = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.All, Proxy = webProxy });
+            client.DefaultRequestHeaders.Add("User-Agent", "GICutscenes");
+
+            Console.ForegroundColor = ConsoleColor.DarkGray;
+            Console.WriteLine("Update 'versions.json'...");
+            Console.ResetColor();
+            var versionsString = await client.GetStringAsync("https://raw.githubusercontent.com/ToaHartor/GI-cutscenes/main/versions.json");
+            await File.WriteAllTextAsync(Path.Combine(AppContext.BaseDirectory, "versions.json"), versionsString);
+            Console.WriteLine("'versions.json' has updated to the latest version.");
+
+            Console.ForegroundColor = ConsoleColor.DarkGray;
+            Console.WriteLine("Check update for GICutscenes...");
+            Console.ResetColor();
+            var releaseString = await client.GetStringAsync("https://api.github.com/repos/ToaHartor/GI-cutscenes/releases/latest");
+            var release = JsonSerializer.Deserialize<GithubRelease>(releaseString!);
+            var currentVersion = typeof(Program).Assembly.GetName().Version;
+            if (System.Version.TryParse(release?.TagName?[1..], out var latestVersion))
+            {
+                if (latestVersion > currentVersion)
+                {
+
+                    Console.ForegroundColor = ConsoleColor.DarkYellow;
+                    Console.WriteLine($"Latest version is '{release?.TagName}', GICutscenes needs to update.");
+                    Console.ResetColor();
+                    if (!notOpenBroswer)
+                    {
+                        if (!string.IsNullOrWhiteSpace(release?.HtmlUrl))
+                        {
+                            // What happens on macOS or Linux?
+                            Process.Start(new ProcessStartInfo(release.HtmlUrl) { UseShellExecute = true });
+                        }
+                    }
+                }
+                else
+                {
+                    Console.WriteLine("GICutscenes is already the latest version.");
+                }
+            }
+            else
+            {
+                Console.ForegroundColor = ConsoleColor.DarkYellow;
+                Console.WriteLine($"Cannot compare version, current version is '{currentVersion}', latest version is '{release?.TagName}'.");
+                Console.ResetColor();
+            }
+        }
+
     }
 }
diff --git a/src/Utils/GithubRelease.cs b/src/Utils/GithubRelease.cs
new file mode 100644
index 0000000..5d5b960
--- /dev/null
+++ b/src/Utils/GithubRelease.cs
@@ -0,0 +1,100 @@
+using System.Text.Json.Serialization;
+
+namespace GICutscenes.Utils;
+
+internal class GithubRelease
+{
+
+    [JsonPropertyName("url")]
+    public string Url { get; set; }
+
+    [JsonPropertyName("assets_url")]
+    public string AssetsUrl { get; set; }
+
+    [JsonPropertyName("upload_url")]
+    public string UploadUrl { get; set; }
+
+    [JsonPropertyName("html_url")]
+    public string HtmlUrl { get; set; }
+
+    [JsonPropertyName("id")]
+    public int Id { get; set; }
+
+    [JsonPropertyName("node_id")]
+    public string NodeId { get; set; }
+
+    [JsonPropertyName("tag_name")]
+    public string TagName { get; set; }
+
+    [JsonPropertyName("target_commitish")]
+    public string TargetCommitish { get; set; }
+
+    [JsonPropertyName("name")]
+    public string Name { get; set; }
+
+    [JsonPropertyName("draft")]
+    public bool Draft { get; set; }
+
+    [JsonPropertyName("prerelease")]
+    public bool Prerelease { get; set; }
+
+    [JsonPropertyName("created_at")]
+    public DateTime CreatedAt { get; set; }
+
+    [JsonPropertyName("published_at")]
+    public DateTime PublishedAt { get; set; }
+
+    [JsonPropertyName("assets")]
+    public List<GithubAsset> Assets { get; set; }
+
+    [JsonPropertyName("tarball_url")]
+    public string TarballUrl { get; set; }
+
+    [JsonPropertyName("zipball_url")]
+    public string ZipballUrl { get; set; }
+
+    [JsonPropertyName("body")]
+    public string Body { get; set; }
+
+}
+
+
+public class GithubAsset
+{
+    [JsonPropertyName("url")]
+    public string Url { get; set; }
+
+    [JsonPropertyName("id")]
+    public int Id { get; set; }
+
+    [JsonPropertyName("node_id")]
+    public string NodeId { get; set; }
+
+    [JsonPropertyName("name")]
+    public string Name { get; set; }
+
+    [JsonPropertyName("label")]
+    public string Label { get; set; }
+
+    [JsonPropertyName("content_type")]
+    public string ContentType { get; set; }
+
+    [JsonPropertyName("state")]
+    public string State { get; set; }
+
+    [JsonPropertyName("size")]
+    public int Size { get; set; }
+
+    [JsonPropertyName("download_count")]
+    public int DownloadCount { get; set; }
+
+    [JsonPropertyName("created_at")]
+    public DateTime CreatedAt { get; set; }
+
+    [JsonPropertyName("updated_at")]
+    public DateTime UpdatedAt { get; set; }
+
+    [JsonPropertyName("browser_download_url")]
+    public string BrowserDownloadUrl { get; set; }
+}
+

From f73b38bc960c89dead9411c86946e88c769ca9a8 Mon Sep 17 00:00:00 2001
From: Scighost <scighost@outlook.com>
Date: Thu, 27 Oct 2022 15:21:59 +0800
Subject: [PATCH 10/15] Change options order

---
 src/Program.cs | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/src/Program.cs b/src/Program.cs
index f7422f9..3dcacd6 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -84,18 +84,20 @@ private static int Main(string[] args)
             mkvEngineOption.AddAlias("-e");
 
             var notOpenBrowserOption = new Option<bool>(
-                name: "-n",
+                name: "--no-browser",
                 description: "Do not open browser if there's new version.");
+            notOpenBrowserOption.AddAlias("-nb");
+
+            var proxyOption = new Option<string>(
+               name: "--proxy",
+               description: "Specifies a proxy server for the request.");
+            proxyOption.AddAlias("-p");
 
             var stackTraceOption = new Option<bool>(
                 name: "--stack-trace",
                 description: "Show stack trace when throw exception.");
             stackTraceOption.AddAlias("-st");
 
-            var proxyOption = new Option<string>(
-                name: "--proxy",
-                description: "Specifies a proxy server for the request.");
-            proxyOption.AddAlias("-p");
 
 
             var rootCommand = new RootCommand("A command line program playing with the cutscenes files (USM) from Genshin Impact.");
@@ -416,6 +418,7 @@ private static async Task UpdateAsync(bool notOpenBroswer, string? proxy)
 
                     Console.ForegroundColor = ConsoleColor.DarkYellow;
                     Console.WriteLine($"Latest version is '{release?.TagName}', GICutscenes needs to update.");
+                    Console.WriteLine($"Release page: {release?.HtmlUrl}");
                     Console.ResetColor();
                     if (!notOpenBroswer)
                     {

From c055daafdef1e9416b94f6c754a593551595181f Mon Sep 17 00:00:00 2001
From: Scighost <scighost@outlook.com>
Date: Thu, 27 Oct 2022 15:25:00 +0800
Subject: [PATCH 11/15] Subtitles style

---
 src/FileTypes/ASS.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/FileTypes/ASS.cs b/src/FileTypes/ASS.cs
index 31bcbea..8752668 100644
--- a/src/FileTypes/ASS.cs
+++ b/src/FileTypes/ASS.cs
@@ -80,7 +80,7 @@ public string ConvertToAss() {
 
 [V4+ Styles]
 Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
-Style: Default,{_fontname},18,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,20,1
+Style: Default,{_fontname},12,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,14,1
 
 [Events]
 Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text" + Environment.NewLine;

From 70c2a4d1a0a57edc90ad0e93ae25ae7f48086bb6 Mon Sep 17 00:00:00 2001
From: Scighost <scighost@outlook.com>
Date: Thu, 27 Oct 2022 15:58:07 +0800
Subject: [PATCH 12/15] Create output folder if not exitsts

---
 src/Program.cs | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/src/Program.cs b/src/Program.cs
index 3dcacd6..bdaaa45 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -43,7 +43,7 @@ private static int Main(string[] args)
 
             var outputFolderOption = new Option<DirectoryInfo?>(
                 name: "--output",
-                description: "Output folder."
+                description: "Output folder, default is './output'."
                 );
             outputFolderOption.AddAlias("-o");
 
@@ -151,6 +151,8 @@ private static int Main(string[] args)
             demuxUsmCommand.SetHandler((FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup) =>
             {
                 ReadSetting();
+                output ??= new DirectoryInfo("./output");
+                output.Create();
                 DemuxUsmCommand(file, key1, key2, output, engine, merge, subs, noCleanup);
             }, demuxFileOption, key1Option, key2Option, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption);
 
@@ -158,16 +160,20 @@ private static int Main(string[] args)
             batchDemuxCommand.SetHandler((DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup) =>
             {
                 ReadSetting();
+                outputDir ??= new DirectoryInfo("./output");
+                outputDir.Create();
                 var timer = Stopwatch.StartNew();
                 BatchDemuxCommand(inputDir, outputDir, engine, merge, subs, noCleanup);
                 timer.Stop();
                 Console.WriteLine($"{timer.ElapsedMilliseconds}ms elapsed");
             }, usmFolderArg, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption);
 
-            convertHcaCommand.SetHandler((FileSystemInfo input, DirectoryInfo? output, bool noCleanup) =>
+            convertHcaCommand.SetHandler((FileSystemInfo input, DirectoryInfo? output) =>
             {
-                ConvertHcaCommand(input, output /*, noCleanup*/);
-            }, hcaInputArg, outputFolderOption, noCleanupOption);
+                output ??= new DirectoryInfo("./output");
+                output.Create();
+                ConvertHcaCommand(input, output);
+            }, hcaInputArg, outputFolderOption);
 
             updateCommand.SetHandler(async (bool notOpenBrowser, string proxy) =>
             {

From dbca55fb18337c180ea343d287dd915a4bb9dfb3 Mon Sep 17 00:00:00 2001
From: Scighost <scighost@outlook.com>
Date: Sat, 29 Oct 2022 23:09:03 +0800
Subject: [PATCH 13/15] jsDelivr

---
 src/Program.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Program.cs b/src/Program.cs
index bdaaa45..22c52e4 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -407,7 +407,7 @@ private static async Task UpdateAsync(bool notOpenBroswer, string? proxy)
             Console.ForegroundColor = ConsoleColor.DarkGray;
             Console.WriteLine("Update 'versions.json'...");
             Console.ResetColor();
-            var versionsString = await client.GetStringAsync("https://raw.githubusercontent.com/ToaHartor/GI-cutscenes/main/versions.json");
+            var versionsString = await client.GetStringAsync("https://cdn.jsdelivr.net/gh/ToaHartor/GI-cutscenes@main/versions.json");
             await File.WriteAllTextAsync(Path.Combine(AppContext.BaseDirectory, "versions.json"), versionsString);
             Console.WriteLine("'versions.json' has updated to the latest version.");
 

From 46e9f773ea54fe403519266c924227e56d42f6be Mon Sep 17 00:00:00 2001
From: Scighost <scighost@outlook.com>
Date: Mon, 31 Oct 2022 19:03:33 +0800
Subject: [PATCH 14/15] Custom subtitle style

---
 appsettings.json       |   3 +-
 src/FileTypes/ASS.cs   | 123 ++++++++++++++++++++++++-----------------
 src/GICutscenes.csproj |  87 +++++++++++++++--------------
 src/Program.cs         |  25 +++++++--
 4 files changed, 138 insertions(+), 100 deletions(-)

diff --git a/appsettings.json b/appsettings.json
index 52388d7..9f028f5 100644
--- a/appsettings.json
+++ b/appsettings.json
@@ -2,6 +2,7 @@
   "Settings": {
     "MkvMergePath": "",
     "FfmpegPath": "",
-    "SubsFolder": "./GenshinData/Subtitle"
+    "SubsFolder": "./GenshinData/Subtitle",
+    "SubtiteStyle": "Style: Default,{fontname},12,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,14,1"
   }
 }
\ No newline at end of file
diff --git a/src/FileTypes/ASS.cs b/src/FileTypes/ASS.cs
index 8752668..80bbbf0 100644
--- a/src/FileTypes/ASS.cs
+++ b/src/FileTypes/ASS.cs
@@ -1,10 +1,11 @@
-using System.Text.RegularExpressions;
+using System.Text;
+using System.Text.RegularExpressions;
 
 namespace GICutscenes.FileTypes
 {
     internal class ASS
     {
-        public static readonly string[] SubsExtensions = {".ass", ".srt", ".txt"};
+        public static readonly string[] SubsExtensions = { ".ass", ".srt", ".txt" };
         private readonly string _srt;
         private readonly string _fontname;
         private readonly List<string> _dialogLines;
@@ -67,34 +68,52 @@ public void ParseSrt()
             }
         }
 
-        public string ConvertToAss() {
-
-            string filename = Path.ChangeExtension(_srt, ".ass");
-            string header =
-                @$"[Script Info]
-; This is an Advanced Sub Station Alpha v4+ script.
-ScriptType: v4.00+
-Collisions: Normal
-ScaledBorderAndShadow: yes
-PlayDepth: 0
-
-[V4+ Styles]
-Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
-Style: Default,{_fontname},12,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,14,1
+        public string ConvertToAss(string outputPath)
+        {
+            var sb = new StringBuilder();
+            sb.AppendLine("""
+                [Script Info]
+                ; This is an Advanced Sub Station Alpha v4+ script.
+                ScriptType: v4.00+
+                Collisions: Normal
+                ScaledBorderAndShadow: yes
+                PlayDepth: 0
+                
+                [V4+ Styles]
+                Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
+                """);
+            if (string.IsNullOrWhiteSpace(Program.settings?.SubtiteStyle))
+            {
+                sb.AppendLine($"Style: Default,{_fontname},12,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,14,1");
+            }
+            else
+            {
+                sb.AppendLine(Program.settings?.SubtiteStyle.Replace("{fontname}", _fontname));
+            }
+            sb.AppendLine("""
+                
+                [Events]
+                Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text
+                """);
 
-[Events]
-Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text" + Environment.NewLine;
-            File.WriteAllText(filename, header);
-            string content = string.Join(Environment.NewLine, _dialogLines);
-            // Correcting styles
-            content = Regex.Replace(content, @"<([ubi])>", @"{\${1}1}");
-            content = Regex.Replace(content, @"</([ubi])>", @"{\${1}0}");
-            content = Regex.Replace(content, @"<font\s+color=""?#(\w{2})(\w{2})(\w{2})""?>", @"{\c&H$3$2$1&}");
-            content = Regex.Replace(content, @"</font>", "");
+            foreach (var dialogLine in _dialogLines)
+            {
+                if (!string.IsNullOrWhiteSpace(dialogLine))
+                {
+                    string content = dialogLine;
+                    content = Regex.Replace(content, @"<([ubi])>", @"{\${1}1}");
+                    content = Regex.Replace(content, @"</([ubi])>", @"{\${1}0}");
+                    content = Regex.Replace(content, @"<font\s+color=""?#(\w{2})(\w{2})(\w{2})""?>", @"{\c&H$3$2$1&}");
+                    content = Regex.Replace(content, @"</font>", "");
+                    sb.AppendLine(content);
+                }
+            }
 
-            File.AppendAllText(filename, content);
+            var filename = Path.Combine(outputPath, "Subs", Path.GetFileNameWithoutExtension(_srt) + ".ass");
+            Directory.CreateDirectory(Path.Combine(outputPath, "Subs"));
+            File.WriteAllText(filename, sb.ToString());
             Console.WriteLine($"{_srt} converted to ASS");
-            File.Delete(_srt); // Can be commented if you don't want to delete the original
+            //File.Delete(_srt); // Can be commented if you don't want to delete the original
             return filename;
         }
 
@@ -122,30 +141,30 @@ public string ConvertToAss() {
             return null;
         }
 
-        public static void ConvertAllSrt(string subsFolder)
-        {
-            string? file = null;
-            //file = "ID/Cs_Inazuma_EQ4002207_ShikishogunRecalling_Boy_ID.txt";
-            if (file == null)
-            {
-                foreach (string langDir in Directory.EnumerateDirectories(subsFolder))
-                {
-                    foreach (string srtFile in Directory.GetFiles(langDir, "*.txt"))
-                    {
-                        ASS newAss = new(srtFile, Path.GetDirectoryName(langDir) ?? "unk");
-                        newAss.ParseSrt();
-                        newAss.ConvertToAss();
-                    }
-                }
-            }
-            else
-            {
-                ASS newAss = new(Path.Combine(subsFolder, file), Path.GetDirectoryName(file) ?? "unk");
-                newAss.ParseSrt();
-                newAss.ConvertToAss();
-            }
-
-            Environment.Exit(0);
-        }
+        //public static void ConvertAllSrt(string subsFolder)
+        //{
+        //    string? file = null;
+        //    //file = "ID/Cs_Inazuma_EQ4002207_ShikishogunRecalling_Boy_ID.txt";
+        //    if (file == null)
+        //    {
+        //        foreach (string langDir in Directory.EnumerateDirectories(subsFolder))
+        //        {
+        //            foreach (string srtFile in Directory.GetFiles(langDir, "*.txt"))
+        //            {
+        //                ASS newAss = new(srtFile, Path.GetDirectoryName(langDir) ?? "unk");
+        //                newAss.ParseSrt();
+        //                newAss.ConvertToAss();
+        //            }
+        //        }
+        //    }
+        //    else
+        //    {
+        //        ASS newAss = new(Path.Combine(subsFolder, file), Path.GetDirectoryName(file) ?? "unk");
+        //        newAss.ParseSrt();
+        //        newAss.ConvertToAss();
+        //    }
+
+        //    Environment.Exit(0);
+        //}
     }
 }
diff --git a/src/GICutscenes.csproj b/src/GICutscenes.csproj
index 9843993..1180d44 100644
--- a/src/GICutscenes.csproj
+++ b/src/GICutscenes.csproj
@@ -1,46 +1,47 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
-  <PropertyGroup>
-    <Version>0.4.1</Version>
-	<OutputType>Exe</OutputType>
-    <TargetFramework>net6.0</TargetFramework>
-	  <!--<RuntimeIdentifier>win-x64</RuntimeIdentifier>-->
-	<PublishTrimmed>true</PublishTrimmed>
-	<Authors>ToaHartor</Authors>
-    <RootNamespace>GICutscenes</RootNamespace>
-	<Description>A command line program playing with the cutscenes files (USM) from Genshin Impact.</Description>
-	<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
-	<RepositoryUrl>https://github.com/ToaHartor/GI-cutscenes</RepositoryUrl>
+	<PropertyGroup>
+		<Version>0.4.1</Version>
+		<OutputType>Exe</OutputType>
+		<TargetFramework>net6.0</TargetFramework>
+		<!--<RuntimeIdentifier>win-x64</RuntimeIdentifier>-->
+		<PublishTrimmed>true</PublishTrimmed>
+		<Authors>ToaHartor</Authors>
+		<RootNamespace>GICutscenes</RootNamespace>
+		<Description>A command line program playing with the cutscenes files (USM) from Genshin Impact.</Description>
+		<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
+		<RepositoryUrl>https://github.com/ToaHartor/GI-cutscenes</RepositoryUrl>
 
-	<Nullable>enable</Nullable>
-	<ImplicitUsings>enable</ImplicitUsings>
-	<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
-	<DebugType>embedded</DebugType>
-	<PackageReadmeFile>README.md</PackageReadmeFile>
-	<RepositoryType>git</RepositoryType>
-	<AnalysisLevel>latest-all</AnalysisLevel>
-	<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
-  </PropertyGroup>
+		<Nullable>enable</Nullable>
+		<LangVersion>preview</LangVersion>
+		<ImplicitUsings>enable</ImplicitUsings>
+		<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
+		<DebugType>embedded</DebugType>
+		<PackageReadmeFile>README.md</PackageReadmeFile>
+		<RepositoryType>git</RepositoryType>
+		<AnalysisLevel>latest-all</AnalysisLevel>
+		<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
+	</PropertyGroup>
 
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
-    <Optimize>False</Optimize>
-    <DebugType>full</DebugType>
-  </PropertyGroup>
+	<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+		<Optimize>False</Optimize>
+		<DebugType>full</DebugType>
+	</PropertyGroup>
 
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
-    <DebugType>full</DebugType>
-	<PublishSingleFile>true</PublishSingleFile>
-	<TieredCompilation>true</TieredCompilation>
-	<PublishReadyToRun>true</PublishReadyToRun>
-	<!--<RunAOTCompilation>true</RunAOTCompilation>-->
-  </PropertyGroup>
-	
-  <ItemGroup>
-    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.0-preview.5.22301.12" />
-    <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0-preview.5.22301.12" />
-    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0-preview.5.22301.12" />
-    <PackageReference Include="System.CommandLine" Version="2.0.0-beta3.22114.1" />
-  </ItemGroup>
+	<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+		<DebugType>full</DebugType>
+		<PublishSingleFile>true</PublishSingleFile>
+		<TieredCompilation>true</TieredCompilation>
+		<PublishReadyToRun>true</PublishReadyToRun>
+		<!--<RunAOTCompilation>true</RunAOTCompilation>-->
+	</PropertyGroup>
+
+	<ItemGroup>
+		<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.0-preview.5.22301.12" />
+		<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0-preview.5.22301.12" />
+		<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0-preview.5.22301.12" />
+		<PackageReference Include="System.CommandLine" Version="2.0.0-beta3.22114.1" />
+	</ItemGroup>
 
 	<ItemGroup>
 		<Content Include="../appsettings.json">
@@ -55,10 +56,10 @@
 	</ItemGroup>
 
 	<ItemGroup>
-	  <None Include="..\.editorconfig" Link=".editorconfig" />
-	  <None Include="..\README.md">
-	    <Pack>True</Pack>
-	    <PackagePath>\</PackagePath>
-	  </None>
+		<None Include="..\.editorconfig" Link=".editorconfig" />
+		<None Include="..\README.md">
+			<Pack>True</Pack>
+			<PackagePath>\</PackagePath>
+		</None>
 	</ItemGroup>
 </Project>
diff --git a/src/Program.cs b/src/Program.cs
index 22c52e4..a2e8408 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -18,6 +18,7 @@ internal sealed class Settings
         public string? MkvMergePath { get; set; }
         public string? SubsFolder { get; set; }
         public string? FfmpegPath { get; set; }
+        public string? SubtiteStyle { get; set; }
 
     }
     internal sealed class Program
@@ -182,9 +183,17 @@ private static int Main(string[] args)
 
             resetCommand.SetHandler(() =>
             {
-                var obj = new { Settings = new Settings { FfmpegPath = "", MkvMergePath = "", SubsFolder = "" } };
-                var json = JsonSerializer.Serialize(obj, new JsonSerializerOptions { WriteIndented = true });
-                File.WriteAllText(Path.Combine(AppContext.BaseDirectory, "appsettings.json"), json);
+                const string str = """
+                {
+                  "Settings": {
+                    "MkvMergePath": "",
+                    "FfmpegPath": "",
+                    "SubsFolder": "",
+                    "SubtiteStyle": "Style: Default,{fontname},12,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,14,1"
+                  }
+                }
+                """;
+                File.WriteAllText(Path.Combine(AppContext.BaseDirectory, "appsettings.json"), str);
                 Console.ForegroundColor = ConsoleColor.Green;
                 Console.WriteLine("'appsettings.json' has reset to default.");
                 Console.ResetColor();
@@ -249,6 +258,10 @@ private static void DemuxUsmCommand(FileInfo file, string key1, string key2, Dir
                 MergeFiles(outputArg, Path.GetFileNameWithoutExtension(file.FullName), engine, subs);
                 if (!noCleanup) CleanFiles(outputArg, Path.GetFileNameWithoutExtension(file.FullName));
             }
+            if (!noCleanup && Directory.Exists(Path.Combine(outputArg, "Subs")))
+            {
+                Directory.Delete(Path.Combine(outputArg, "Subs"), true);
+            }
         }
 
         private static void BatchDemuxCommand(DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup)
@@ -266,6 +279,10 @@ private static void BatchDemuxCommand(DirectoryInfo inputDir, DirectoryInfo? out
                 MergeFiles(outputArg, Path.GetFileNameWithoutExtension(f), engine, subs);
                 if (!noCleanup) CleanFiles(outputArg, Path.GetFileNameWithoutExtension(f));
             }
+            if (!noCleanup && Directory.Exists(Path.Combine(outputArg, "Subs")))
+            {
+                Directory.Delete(Path.Combine(outputArg, "Subs"), true);
+            }
         }
 
         private static void ConvertHcaCommand(FileSystemInfo input, DirectoryInfo? output)
@@ -367,7 +384,7 @@ private static void MergeFiles(string outputPath, string basename, string engine
                                 if (!sub.IsAss())
                                 {
                                     sub.ParseSrt();
-                                    subFile = sub.ConvertToAss();
+                                    subFile = sub.ConvertToAss(outputPath);
                                 }
 
                                 merger.AddSubtitlesTrack(subFile, lang);

From 90192945fef2ec0f16bbd910fecd207c12041634 Mon Sep 17 00:00:00 2001
From: Scighost <scighost@outlook.com>
Date: Mon, 31 Oct 2022 19:30:34 +0800
Subject: [PATCH 15/15] Fix spelling error

---
 appsettings.json     | 2 +-
 src/FileTypes/ASS.cs | 4 ++--
 src/Program.cs       | 4 ++--
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/appsettings.json b/appsettings.json
index 9f028f5..74ff011 100644
--- a/appsettings.json
+++ b/appsettings.json
@@ -3,6 +3,6 @@
     "MkvMergePath": "",
     "FfmpegPath": "",
     "SubsFolder": "./GenshinData/Subtitle",
-    "SubtiteStyle": "Style: Default,{fontname},12,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,14,1"
+    "SubsStyle": "Style: Default,{fontname},12,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,14,1"
   }
 }
\ No newline at end of file
diff --git a/src/FileTypes/ASS.cs b/src/FileTypes/ASS.cs
index 80bbbf0..bf532dc 100644
--- a/src/FileTypes/ASS.cs
+++ b/src/FileTypes/ASS.cs
@@ -82,13 +82,13 @@ [Script Info]
                 [V4+ Styles]
                 Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
                 """);
-            if (string.IsNullOrWhiteSpace(Program.settings?.SubtiteStyle))
+            if (string.IsNullOrWhiteSpace(Program.settings?.SubsStyle))
             {
                 sb.AppendLine($"Style: Default,{_fontname},12,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,14,1");
             }
             else
             {
-                sb.AppendLine(Program.settings?.SubtiteStyle.Replace("{fontname}", _fontname));
+                sb.AppendLine(Program.settings?.SubsStyle.Replace("{fontname}", _fontname));
             }
             sb.AppendLine("""
                 
diff --git a/src/Program.cs b/src/Program.cs
index 18cc964..0ca8744 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -18,7 +18,7 @@ internal sealed class Settings
         public string? MkvMergePath { get; set; }
         public string? SubsFolder { get; set; }
         public string? FfmpegPath { get; set; }
-        public string? SubtiteStyle { get; set; }
+        public string? SubsStyle { get; set; }
 
     }
     internal sealed class Program
@@ -203,7 +203,7 @@ private static int Main(string[] args)
                     "MkvMergePath": "",
                     "FfmpegPath": "",
                     "SubsFolder": "",
-                    "SubtiteStyle": "Style: Default,{fontname},12,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,14,1"
+                    "SubsStyle": "Style: Default,{fontname},12,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,14,1"
                   }
                 }
                 """;