diff --git a/.gitignore b/.gitignore index e3dbfb21..4570f08f 100644 --- a/.gitignore +++ b/.gitignore @@ -264,3 +264,4 @@ launchSettings.json *.db *.db-journal logs/ +*.ird diff --git a/CompatBot/Commands/CompatList.cs b/CompatBot/Commands/CompatList.cs index 9b90d9b8..f5675c62 100644 --- a/CompatBot/Commands/CompatList.cs +++ b/CompatBot/Commands/CompatList.cs @@ -33,7 +33,7 @@ namespace CompatBot.Commands } [Command("compat"), Aliases("c")] - [Description("Searches the compatibility database, USE: !compat searchterm")] + [Description("Searches the compatibility database, USE: !compat search term")] public async Task Compat(CommandContext ctx, [RemainingText, Description("Game title to look up")] string title) { try @@ -205,7 +205,7 @@ Example usage: result.AppendFormat(returnCode.info, compatResult.SearchTerm); yield return result.ToString(); result.Clear(); - var footer = $"Retrieved from: *<{request.Build(false).ToString().Replace(' ', '+')}>* in {compatResult.RequestDuration.TotalMilliseconds:0} milliseconds!"; + //var footer = $"Retrieved from: *<{request.Build(false).ToString().Replace(' ', '+')}>* in {compatResult.RequestDuration.TotalMilliseconds:0} milliseconds!"; if (returnCode.displayResults) { @@ -217,10 +217,10 @@ Example usage: } result.Append("```"); yield return result.ToString(); - yield return footer; + //yield return footer; } - else if (returnCode.displayFooter) - yield return footer; + //else if (returnCode.displayFooter) + // yield return footer; } } } diff --git a/CompatBot/Config.cs b/CompatBot/Config.cs index ebf163cd..dd702b7c 100644 --- a/CompatBot/Config.cs +++ b/CompatBot/Config.cs @@ -26,9 +26,11 @@ namespace CompatBot public static readonly int AttachmentSizeLimit = 8 * 1024 * 1024; public static readonly int LogSizeLimit = 64 * 1024 * 1024; public static readonly int MinimumBufferSize = 512; + public static readonly int MaxBuildNumberDifferenceForOutdatedBuilds = 10; public static readonly string Token; public static readonly string LogPath = "../../../logs/bot.log"; // paths are relative to the assembly, so this will put it in the project's root + public static readonly string IrdCachePath = "./ird/"; internal static readonly ILogger Log; diff --git a/CompatBot/EventHandlers/LogParsing/LogParser.LogSections.cs b/CompatBot/EventHandlers/LogParsing/LogParser.LogSections.cs index f2255ee0..2fba3515 100644 --- a/CompatBot/EventHandlers/LogParsing/LogParser.LogSections.cs +++ b/CompatBot/EventHandlers/LogParsing/LogParser.LogSections.cs @@ -116,7 +116,7 @@ namespace CompatBot.EventHandlers.LogParsing @"RSX: (?.*?)\r?\n[^\n]*?" + @"RSX: Supported texel buffer size", DefaultOptions), ["GL RENDERER:"] = new Regex(@"GL RENDERER: (?.*?)\r?$", DefaultOptions), - ["GL VERSION:"] = new Regex(@"GL VERSION: (?(\d|\.)+)(\d|\.|\s|\w|-)*(?(\d+\.)*\d+)?\r?$", DefaultOptions), + ["GL VERSION:"] = new Regex(@"GL VERSION: (?(\d|\.)+)(\d|\.|\s|\w|-)*( (?(\d+\.)*\d+))?\r?$", DefaultOptions), ["GLSL VERSION:"] = new Regex(@"GLSL VERSION: (?(\d|\.)+).*?\r?$", DefaultOptions), ["texel buffer size reported:"] = new Regex(@"RSX: Supported texel buffer size reported: (?\d*?) bytes", DefaultOptions), ["Physical device intialized"] = new Regex(@"Physical device intialized\. GPU=(?.+), driver=(?-?\d+)\r?$", DefaultOptions), @@ -129,6 +129,8 @@ namespace CompatBot.EventHandlers.LogParsing ["XAudio2Thread"] = new Regex(@"XAudio2Thread\s*: (?.+failed\s*\((?0x.+)\).*)\r?$", DefaultOptions), ["PPU executable hash:"] = new Regex(@"PPU executable hash: PPU-(?\w+) \(<-\s*(?(?!0)\d+)\).*?\r?$", DefaultOptions), ["Loaded SPU image:"] = new Regex(@"Loaded SPU image: SPU-(?\w+) \(<-\s*(?(?!0)\d+)\).*?\r?$", DefaultOptions), + ["'sys_fs_open' failed"] = new Regex(@"'sys_fs_open' failed .+\xE2\x80\x9C/dev_bdvd/(?.+)\xE2\x80\x9D.*?\r?$", DefaultOptions), + ["'sys_fs_opendir' failed"] = new Regex(@"'sys_fs_opendir' failed .+\xE2\x80\x9C/dev_bdvd/(?.+)\xE2\x80\x9D.*?\r?$", DefaultOptions), }, OnSectionEnd = MarkAsCompleteAndReset, EndTrigger = "All threads stopped...", @@ -143,6 +145,8 @@ namespace CompatBot.EventHandlers.LogParsing "ppu_hash_patch", "spu_hash", "spu_hash_patch", + "broken_filename", + "broken_directory", }; private static async Task PiracyCheckAsync(string line, LogParseState state) diff --git a/CompatBot/EventHandlers/LogParsing/SourceHandlers/SevenZipHandler.cs b/CompatBot/EventHandlers/LogParsing/SourceHandlers/SevenZipHandler.cs index e7e521e1..20370491 100644 --- a/CompatBot/EventHandlers/LogParsing/SourceHandlers/SevenZipHandler.cs +++ b/CompatBot/EventHandlers/LogParsing/SourceHandlers/SevenZipHandler.cs @@ -2,10 +2,8 @@ using System.Buffers; using System.IO; using System.IO.Pipelines; -using System.Linq; using System.Net.Http; using System.Threading.Tasks; -using CompatBot.Utils; using DSharpPlus.Entities; using SharpCompress.Archives.SevenZip; @@ -15,40 +13,12 @@ namespace CompatBot.EventHandlers.LogParsing.SourceHandlers { private static readonly ArrayPool bufferPool = ArrayPool.Create(1024, 16); - public async Task CanHandleAsync(DiscordAttachment attachment) + public Task CanHandleAsync(DiscordAttachment attachment) { if (!attachment.FileName.EndsWith(".7z", StringComparison.InvariantCultureIgnoreCase)) - return false; + return Task.FromResult(false); - return true; -/* - try - { - using (var client = HttpClientFactory.Create()) - using (var stream = await client.GetStreamAsync(attachment.Url).ConfigureAwait(false)) - { - var buf = bufferPool.Rent(4096); - bool result; - try - { - var read = await stream.ReadBytesAsync(buf).ConfigureAwait(false); - using (var memStream = new MemoryStream(read)) - using (var zipArchive = SevenZipArchive.Open(memStream)) - result = zipArchive.Entries.Any(e => !e.IsDirectory && e.Key.EndsWith(".log", StringComparison.InvariantCultureIgnoreCase)); - } - finally - { - bufferPool.Return(buf); - } - return result; - } - } - catch (Exception e) - { - Config.Log.Error(e, "Error sniffing the 7z content"); - return false; - } -*/ + return Task.FromResult(true); } public async Task FillPipeAsync(DiscordAttachment attachment, PipeWriter writer) diff --git a/CompatBot/Program.cs b/CompatBot/Program.cs index ae97a109..401b69e1 100644 --- a/CompatBot/Program.cs +++ b/CompatBot/Program.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Threading; using System.Threading.Tasks; using CompatBot.Commands; @@ -81,6 +82,16 @@ namespace CompatBot var gameTdbScrapingTask = GameTdbScraper.RunAsync(Config.Cts.Token); await amdDriverRefreshTask.ConfigureAwait(false); + try + { + if (!Directory.Exists(Config.IrdCachePath)) + Directory.CreateDirectory(Config.IrdCachePath); + } + catch (Exception e) + { + Config.Log.Warn(e, $"Failed to create new folder {Config.IrdCachePath}: {e.Message}"); + } + var config = new DiscordConfiguration { Token = Config.Token, @@ -88,7 +99,6 @@ namespace CompatBot //UseInternalLogHandler = true, //LogLevel = LogLevel.Debug, }; - using (var client = new DiscordClient(config)) { var commands = client.UseCommandsNext(new CommandsNextConfiguration diff --git a/CompatBot/Utils/ResultFormatters/IrdSearchResultFormattercs.cs b/CompatBot/Utils/ResultFormatters/IrdSearchResultFormattercs.cs index 39b33ee3..5b16f36a 100644 --- a/CompatBot/Utils/ResultFormatters/IrdSearchResultFormattercs.cs +++ b/CompatBot/Utils/ResultFormatters/IrdSearchResultFormattercs.cs @@ -29,7 +29,7 @@ namespace CompatBot.Utils.ResultFormatters else if (parts.Length == 1) parts = new[] {null, item.Filename}; result.AddField( - $"[{parts?[0]}] {item.Title?.Sanitize().Trim(EmbedPager.MaxFieldTitleLength)}", + $"[{parts?[0]} v{item.GameVersion}] {item.Title?.Sanitize().Trim(EmbedPager.MaxFieldTitleLength)}", $"⏬ [`{parts[1]?.Sanitize().Trim(200)}`]({IrdClient.GetDownloadLink(item.Filename)}) ℹ [Info]({IrdClient.GetInfoLink(item.Filename)})" ); } diff --git a/CompatBot/Utils/ResultFormatters/LogParserResultFormatter.cs b/CompatBot/Utils/ResultFormatters/LogParserResultFormatter.cs index 4844839f..631a05f4 100644 --- a/CompatBot/Utils/ResultFormatters/LogParserResultFormatter.cs +++ b/CompatBot/Utils/ResultFormatters/LogParserResultFormatter.cs @@ -9,17 +9,21 @@ using System.Threading.Tasks; using CompatApiClient; using CompatApiClient.POCOs; using CompatApiClient.Utils; +using CompatBot.Commands; using CompatBot.Database.Providers; using CompatBot.EventHandlers; using CompatBot.EventHandlers.LogParsing.POCOs; using DSharpPlus; using DSharpPlus.Entities; +using IrdLibraryClient; +using IrdLibraryClient.IrdFormat; namespace CompatBot.Utils.ResultFormatters { internal static class LogParserResult { private static readonly Client compatClient = new Client(); + private static readonly IrdClient irdClient = new IrdClient(); // RPCS3 v0.0.3-3-3499d08 Alpha | HEAD // RPCS3 v0.0.4-6422-95c6ac699 Alpha | HEAD @@ -289,10 +293,8 @@ namespace CompatBot.Utils.ResultFormatters PageSection(builder, notesContent, "Important Settings to Review"); } - private static async Task BuildNotesSectionAsync(DiscordEmbedBuilder builder, LogParseState state, NameValueCollection items) + private static void BuildMissingLicensesSection(DiscordEmbedBuilder builder, NameValueCollection items) { - BuildWeirdSettingsSection(builder, items); - var notes = new StringBuilder(); if (items["rap_file"] is string rap) { var limitTo = 5; @@ -308,12 +310,65 @@ namespace CompatBot.Utils.ResultFormatters content = string.Join(Environment.NewLine, licenseNames); builder.AddField("Missing Licenses", content); } + } + + private static async Task BuildMissingFilesSection(DiscordEmbedBuilder builder, NameValueCollection items) + { + if (!(items["serial"] is string productCode)) + return false; + + if (!productCode.StartsWith("B") && !productCode.StartsWith("M")) + return false; + + if (string.IsNullOrEmpty(items["broken_directory"]) && string.IsNullOrEmpty(items["broken_filename"])) + return false; + + var getIrdTask = irdClient.DownloadAsync(productCode, Config.IrdCachePath, Config.Cts.Token); + var missingDirs = items["broken_directory"]?.Split(Environment.NewLine).Distinct().ToList() ?? new List(0); + var missingFiles = items["broken_filename"]?.Split(Environment.NewLine).Distinct().ToList() ?? new List(0); + HashSet knownFiles; + try + { + var irdFiles = await getIrdTask.ConfigureAwait(false); + knownFiles = new HashSet( + from ird in irdFiles + from name in ird.GetFilenames() + select name, + StringComparer.InvariantCultureIgnoreCase + ); + } + catch (Exception e) + { + Config.Log.Warn(e, "Failed to get IRD files for " + productCode); + return false; + } + var broken = missingFiles.Any(knownFiles.Contains); + if (broken) + return true; + + var knownDirs = new HashSet(knownFiles.Select(f => Path.GetDirectoryName(f).Replace('\\', '/')), StringComparer.InvariantCultureIgnoreCase); + return missingDirs.Any(knownDirs.Contains); + } + + private static async Task BuildNotesSectionAsync(DiscordEmbedBuilder builder, LogParseState state, NameValueCollection items) + { + BuildWeirdSettingsSection(builder, items); + BuildMissingLicensesSection(builder, items); + var brokenDump = await BuildMissingFilesSection(builder, items).ConfigureAwait(false); + var notes = new StringBuilder(); if (items["fatal_error"] is string fatalError) { builder.AddField("Fatal Error", $"```{fatalError.Trim(1022)}```"); if (fatalError.Contains("psf.cpp")) notes.AppendLine("Game save data might be corrupted"); } + if (items["failed_to_decrypt"] is string _) + notes.AppendLine("Failed to decrypt game content, license file might be corrupted"); + if (items["failed_to_boot"] is string _) + notes.AppendLine("Failed to boot the game, the dump might be encrypted or corrupted"); + if (brokenDump) + notes.AppendLine("Some game files are missing or corrupted, please check and redump if needed."); + Version oglVersion = null; if (items["opengl_version"] is string oglVersionString) Version.TryParse(oglVersionString, out oglVersion); @@ -327,35 +382,9 @@ namespace CompatBot.Utils.ResultFormatters { if (oglVersion < MinimumOpenGLVersion) notes.AppendLine($"GPU only supports OpenGL {oglVersion.Major}.{oglVersion.Minor}, which is below the minimum requirement of {MinimumOpenGLVersion}"); -/* - else if (oglVersion < RecommendedOpenGLVersion) - notes.AppendLine($"GPU only supports OpenGL {oglVersion.Major}.{oglVersion.Minor}, which is below the recommended requirement of {RecommendedOpenGLVersion}"); -*/ } -/* - var patchCount = 0; - if (items["ppu_hash_patch"] is string ppuPatch) - patchCount += ppuPatch.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries).Select(s => - { - int.TryParse(s, out var result); - return result; - }).Sum(); - if (items["spu_hash_patch"] is string spuPatch) - patchCount += spuPatch.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries).Select(s => - { - int.TryParse(s, out var result); - return result; - }).Sum(); - if (patchCount > 0) - notes.AppendLine($"{patchCount} game patch{(patchCount == 1 ? " was" : "es were")} applied"); -*/ if (!string.IsNullOrEmpty(items["ppu_hash_patch"]) || !string.IsNullOrEmpty(items["spu_hash_patch"])) notes.AppendLine("Game-specific patches were applied"); - - if (items["failed_to_decrypt"] is string _) - notes.AppendLine("Failed to decrypt game content, license file might be corrupted"); - if (items["failed_to_boot"] is string _) - notes.AppendLine("Failed to boot the game, the dump might be encrypted or corrupted"); if (string.IsNullOrEmpty(items["ppu_decoder"]) || string.IsNullOrEmpty(items["renderer"])) notes.AppendLine("The log is empty, you need to run the game before uploading the log"); if (!string.IsNullOrEmpty(items["hdd_game_path"]) && !(items["serial"]?.StartsWith("NP", StringComparison.InvariantCultureIgnoreCase) ?? false)) @@ -408,10 +437,27 @@ namespace CompatBot.Utils.ResultFormatters return null; var latestBuildInfo = BuildInfoInUpdate.Match(link.ToLowerInvariant()); - if (!latestBuildInfo.Success || SameCommits(buildInfo.Groups["commit"].Value, latestBuildInfo.Groups["commit"].Value)) - return null; + if (latestBuildInfo.Success && VersionIsTooOld(buildInfo, latestBuildInfo)) + return updateInfo; - return updateInfo; + return null; + } + + private static bool VersionIsTooOld(Match log, Match update) + { + if (Version.TryParse(log.Groups["version"].Value, out var logVersion) && Version.TryParse(update.Groups["version"].Value, out var updateVersion)) + { + if (logVersion < updateVersion) + return true; + + if (int.TryParse(log.Groups["build"].Value, out var logBuild) && int.TryParse(update.Groups["build"].Value, out var updateBuild)) + { + if (logBuild + Config.MaxBuildNumberDifferenceForOutdatedBuilds < updateBuild) + return true; + } + return false; + } + return !SameCommits(log.Groups["commit"].Value, update.Groups["commit"].Value); } private static bool SameCommits(string commitA, string commitB) diff --git a/IrdLibraryClient/IrdClient.cs b/IrdLibraryClient/IrdClient.cs index 8b409eb5..33c6b52f 100644 --- a/IrdLibraryClient/IrdClient.cs +++ b/IrdLibraryClient/IrdClient.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net; using System.Net.Http; @@ -11,6 +12,7 @@ using System.Threading.Tasks; using CompatApiClient; using CompatApiClient.Compression; using CompatApiClient.Utils; +using IrdLibraryClient.IrdFormat; using IrdLibraryClient.POCOs; using Newtonsoft.Json; using JsonContractResolver = CompatApiClient.JsonContractResolver; @@ -70,7 +72,7 @@ namespace IrdLibraryClient ["start"] = "0", ["length"] = "10", - ["search[value]"] = query, + ["search[value]"] = query.Trim(100), ["_"] = DateTime.UtcNow.Ticks.ToString(), }); @@ -101,6 +103,105 @@ namespace IrdLibraryClient } } + public async Task> DownloadAsync(string productCode, string localCachePath, CancellationToken cancellationToken) + { + var result = new List(); + try + { + // first we search local cache and try to load whatever data we can + var localCacheItems = new List(); + try + { + var tmpCacheItemList = Directory.GetFiles(localCachePath, productCode + "*.ird", SearchOption.TopDirectoryOnly).Select(Path.GetFileName).ToList(); + foreach (var item in tmpCacheItemList) + { + try + { + result.Add(IrdParser.Parse(File.ReadAllBytes(Path.Combine(localCachePath, item)))); + localCacheItems.Add(item); + } + catch (Exception ex) + { + ApiConfig.Log.Warn(ex, "Error reading local IRD file: " + ex.Message); + } + } + } + catch (Exception e) + { + ApiConfig.Log.Warn(e, "Error accessing local IRD cache: " + e.Message); + } + ApiConfig.Log.Debug($"Found {localCacheItems.Count} cached items for {productCode}"); + SearchResult searchResult = null; + + // then try to do IRD Library search + try + { + searchResult = await SearchAsync(productCode, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) + { + ApiConfig.Log.Error(e); + } + var tmpFilesToGet = searchResult?.Data.Select(i => i.Filename).Except(localCacheItems, StringComparer.InvariantCultureIgnoreCase).ToList(); + if ((tmpFilesToGet?.Count ?? 0) == 0) + return result; + + // as IRD Library could return more data than we found, try to check for all the items locally + var filesToDownload = new List(); + foreach (var item in tmpFilesToGet) + { + try + { + var localItemPath = Path.Combine(localCachePath, item); + if (File.Exists(localItemPath)) + { + result.Add(IrdParser.Parse(File.ReadAllBytes(localItemPath))); + localCacheItems.Add(item); + } + else + filesToDownload.Add(item); + } + catch (Exception ex) + { + ApiConfig.Log.Warn(ex, "Error reading local IRD file: " + ex.Message); + filesToDownload.Add(item); + } + } + ApiConfig.Log.Debug($"Found {tmpFilesToGet.Count} total matches for {productCode}, {result.Count} already cached"); + if (filesToDownload.Count == 0) + return result; + + // download the remaining .ird files + foreach (var item in filesToDownload) + { + try + { + var resultBytes = await client.GetByteArrayAsync(GetDownloadLink(item)).ConfigureAwait(false); + result.Add(IrdParser.Parse(resultBytes)); + try + { + File.WriteAllBytes(Path.Combine(localCachePath, item), resultBytes); + } + catch (Exception ex) + { + ApiConfig.Log.Warn(ex, $"Failed to write {item} to local cache: {ex.Message}"); + } + } + catch (Exception e) + { + ApiConfig.Log.Warn(e, $"Failed to download {item}: {e.Message}"); + } + } + ApiConfig.Log.Debug($"Returning {result.Count} .ird files for {productCode}"); + return result; + } + catch (Exception e) + { + ApiConfig.Log.Error(e); + return result; + } + } + private static string GetIrdFilename(string html) { if (string.IsNullOrEmpty(html)) diff --git a/IrdLibraryClient/IrdFormat/Ird.cs b/IrdLibraryClient/IrdFormat/Ird.cs new file mode 100644 index 00000000..9096eac7 --- /dev/null +++ b/IrdLibraryClient/IrdFormat/Ird.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace IrdLibraryClient.IrdFormat +{ + public class Ird + { + public static int Magic = BitConverter.ToInt32(Encoding.ASCII.GetBytes("3IRD"), 0); + public byte Version; + public string ProductCode; // 9 + public byte TitleLength; + public string Title; + public string UpdateVersion; // 4 + public string GameVersion; // 5 + public string AppVersion; // 5 + public int Id; // v7 only? + public int HeaderLength; + public byte[] Header; // gz + public int FooterLength; + public byte[] Footer; // gz + public byte RegionCount; + public List RegionMd5Checksums; // 16 each + public int FileCount; + public List Files; + public int Unknown; // always 0? + public byte[] Pic; // 115, v9 only? + public byte[] Data1; // 16 + public byte[] Data2; // 16 + // Pic for (result.RegionCount); + for (var i = 0; i < result.RegionCount; i++) + result.RegionMd5Checksums.Add(reader.ReadBytes(16)); + result.FileCount = reader.ReadInt32(); + result.Files = new List(result.FileCount); + for (var i = 0; i < result.FileCount; i++) + { + var file = new IrdFile(); + file.Offset = reader.ReadInt64(); + file.Md5Checksum = reader.ReadBytes(16); + result.Files.Add(file); + } + result.Unknown = reader.ReadInt32(); + if (result.Version == 9) + result.Pic = reader.ReadBytes(115); + result.Data1 = reader.ReadBytes(16); + result.Data2 = reader.ReadBytes(16); + if (result.Version < 9) + result.Pic = reader.ReadBytes(115); + result.Uid = reader.ReadInt32(); + var dataLength = reader.BaseStream.Position; + result.Crc32 = reader.ReadUInt32(); + + var crc32 = Crc32Algorithm.Compute(content, 0, (int)dataLength); + if (result.Crc32 != crc32) + throw new InvalidDataException($"Corrupted IRD data, expected {result.Crc32:x8}, but was {crc32:x8}"); + } + return result; + } + } + + public static class IsoHeaderParser + { + public static List GetFilenames(this Ird ird) + { + using (var decompressedStream = new MemoryStream()) + { + using (var compressedStream = new MemoryStream(ird.Header, false)) + using (var gzip = new GZipStream(compressedStream, CompressionMode.Decompress)) + gzip.CopyTo(decompressedStream); + + decompressedStream.Seek(0, SeekOrigin.Begin); + var reader = new CDReader(decompressedStream, true, true); + return reader.GetFiles(reader.Root.FullName, "*.*", SearchOption.AllDirectories).Distinct().Select(n => n.TrimStart('\\').Replace('\\', '/')).ToList(); + } + } + } +} diff --git a/IrdLibraryClient/IrdLibraryClient.csproj b/IrdLibraryClient/IrdLibraryClient.csproj index 31687bc0..823f0079 100644 --- a/IrdLibraryClient/IrdLibraryClient.csproj +++ b/IrdLibraryClient/IrdLibraryClient.csproj @@ -2,8 +2,14 @@ netstandard2.0 + latest + + + + + diff --git a/Tests/IrdTests.cs b/Tests/IrdTests.cs new file mode 100644 index 00000000..9220ef92 --- /dev/null +++ b/Tests/IrdTests.cs @@ -0,0 +1,42 @@ +using System.IO; +using IrdLibraryClient.IrdFormat; +using NUnit.Framework; + +namespace Tests +{ + [TestFixture] + public class IrdTests + { + [Test] + public void ParsingTest() + { + var baseDir = TestContext.CurrentContext.TestDirectory; + var testFiles = Directory.GetFiles(baseDir, "*.ird", SearchOption.AllDirectories); + Assert.That(testFiles.Length, Is.GreaterThan(0)); + + foreach (var file in testFiles) + { + var bytes = File.ReadAllBytes(file); + Assert.That(() => IrdParser.Parse(bytes), Throws.Nothing, "Failed to parse " + Path.GetFileName(file)); + } + } + + [Test] + public void HeaderParsingTest() + { + var baseDir = TestContext.CurrentContext.TestDirectory; + var testFiles = Directory.GetFiles(baseDir, "*.ird", SearchOption.AllDirectories); + Assert.That(testFiles.Length, Is.GreaterThan(0)); + + foreach (var file in testFiles) + { + var bytes = File.ReadAllBytes(file); + var ird = IrdParser.Parse(bytes); + Assert.That(ird.FileCount, Is.GreaterThan(0)); + + var fileList = ird.GetFilenames(); + Assert.That(fileList.Count, Is.EqualTo(ird.FileCount)); + } + } + } +} diff --git a/Tests/Test Files/Put IRD files here.txt b/Tests/Test Files/Put IRD files here.txt new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/Tests/Test Files/Put IRD files here.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj new file mode 100644 index 00000000..9a435948 --- /dev/null +++ b/Tests/Tests.csproj @@ -0,0 +1,25 @@ + + + + netcoreapp2.1 + + false + + + + + + + + + + + + + + + Always + + + + diff --git a/discord-bot-net.sln b/discord-bot-net.sln index 4601362b..1e8c473f 100644 --- a/discord-bot-net.sln +++ b/discord-bot-net.sln @@ -11,7 +11,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PsnClient", "PsnClient\PsnC EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HomoglyphConverter", "HomoglyphConverter\HomoglyphConverter.csproj", "{A1E6566C-F506-43C8-B06E-9A472AEBF447}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IrdLibraryClient", "IrdLibraryClient\IrdLibraryClient.csproj", "{AA2A333B-CD30-41A5-A680-CC9BCB2D726B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IrdLibraryClient", "IrdLibraryClient\IrdLibraryClient.csproj", "{AA2A333B-CD30-41A5-A680-CC9BCB2D726B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{815D60C3-F84B-4AE2-B4E4-48004515FCFD}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -39,6 +41,9 @@ Global {AA2A333B-CD30-41A5-A680-CC9BCB2D726B}.Debug|Any CPU.Build.0 = Debug|Any CPU {AA2A333B-CD30-41A5-A680-CC9BCB2D726B}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA2A333B-CD30-41A5-A680-CC9BCB2D726B}.Release|Any CPU.Build.0 = Release|Any CPU + {815D60C3-F84B-4AE2-B4E4-48004515FCFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {815D60C3-F84B-4AE2-B4E4-48004515FCFD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {815D60C3-F84B-4AE2-B4E4-48004515FCFD}.Release|Any CPU.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE