using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; namespace TinfoilVibeServer.Utilities; /// /// Streams a multipart RAR as a single, seekable stream. /// Supports: /// • myGame.rar (single volume) /// • myGame.r00 / r01 … (RAR 4.x style) /// • myGame.part01.rar … (WinRAR “part” style) /// public static class MultiPartRarHelper { public static string GetBaseNameForRarVolume(string fileName) { string baseName = string.Empty; // Multipart – remove the ".rNN" or ".partNN" suffix var m = Regex.Match(fileName, @"^(?.+?)(\.r\d\d|\.part\d\d)\.rar$", RegexOptions.IgnoreCase); if (!m.Success) { if (fileName.EndsWith(".rar", StringComparison.OrdinalIgnoreCase)) { // Single‑volume archive – just drop the suffix baseName = fileName.Substring(0, fileName.Length - 4); } } else { baseName = m.Groups["base"].Value; } return baseName; } /// /// Returns the list of files that belong to the same multipart set. /// public static List DiscoverVolumes(string dir, string baseName) { // Pattern: .(rar | rNN | partNN.rar) string pattern = $@"^{Regex.Escape(baseName)}(\.rar|\.r\d\d|\.part\d\d\.rar)$"; return Directory.GetFiles(dir) .Where(f => Regex.IsMatch(Path.GetFileName(f), pattern, RegexOptions.IgnoreCase)) .OrderBy(f => GetPartNumber(Path.GetFileName(f), baseName)) .ToList(); } /// /// Gives each file a numeric key that guarantees the correct order. /// 0 → .rar (first volume) /// 1 → .r00 or .part01 /// 2 → .r01 or .part02 /// … and so on. /// private static int GetPartNumber(string fileName, string baseName) { // 1️⃣ ".rar" → 0 if (fileName.Equals($"{baseName}.rar", StringComparison.OrdinalIgnoreCase)) return 0; // 2️⃣ ".rNN" or ".partNN.rar" var m = Regex.Match(fileName, $@"\.r(?\d\d)$|\.part(?\d\d)\.rar$", RegexOptions.IgnoreCase); if (m.Success) { int partNum = int.Parse(m.Groups["num"].Value); return partNum + 1; // so r00 → 1, r01 → 2; part01 → 1, part02 → 2 } // 3️⃣ unknown pattern – treat as first part return 0; } }