Merge pull request #118 from 13xforever/vnext

Corrupted dump detection
This commit is contained in:
Ilya 2018-11-05 22:53:22 +05:00 committed by GitHub
commit f5468a41ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 427 additions and 75 deletions

1
.gitignore vendored
View File

@ -264,3 +264,4 @@ launchSettings.json
*.db
*.db-journal
logs/
*.ird

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -116,7 +116,7 @@ namespace CompatBot.EventHandlers.LogParsing
@"RSX: (?<driver_manuf>.*?)\r?\n[^\n]*?" +
@"RSX: Supported texel buffer size", DefaultOptions),
["GL RENDERER:"] = new Regex(@"GL RENDERER: (?<driver_manuf_new>.*?)\r?$", DefaultOptions),
["GL VERSION:"] = new Regex(@"GL VERSION: (?<opengl_version>(\d|\.)+)(\d|\.|\s|\w|-)*(?<driver_version_new>(\d+\.)*\d+)?\r?$", DefaultOptions),
["GL VERSION:"] = new Regex(@"GL VERSION: (?<opengl_version>(\d|\.)+)(\d|\.|\s|\w|-)*( (?<driver_version_new>(\d+\.)*\d+))?\r?$", DefaultOptions),
["GLSL VERSION:"] = new Regex(@"GLSL VERSION: (?<glsl_version>(\d|\.)+).*?\r?$", DefaultOptions),
["texel buffer size reported:"] = new Regex(@"RSX: Supported texel buffer size reported: (?<texel_buffer_size_new>\d*?) bytes", DefaultOptions),
["Physical device intialized"] = new Regex(@"Physical device intialized\. GPU=(?<vulkan_gpu>.+), driver=(?<vulkan_driver_version_raw>-?\d+)\r?$", DefaultOptions),
@ -129,6 +129,8 @@ namespace CompatBot.EventHandlers.LogParsing
["XAudio2Thread"] = new Regex(@"XAudio2Thread\s*: (?<xaudio_init_error>.+failed\s*\((?<xaudio_error_code>0x.+)\).*)\r?$", DefaultOptions),
["PPU executable hash:"] = new Regex(@"PPU executable hash: PPU-(?<ppu_hash>\w+) \(<-\s*(?<ppu_hash_patch>(?!0)\d+)\).*?\r?$", DefaultOptions),
["Loaded SPU image:"] = new Regex(@"Loaded SPU image: SPU-(?<spu_hash>\w+) \(<-\s*(?<spu_hash_patch>(?!0)\d+)\).*?\r?$", DefaultOptions),
["'sys_fs_open' failed"] = new Regex(@"'sys_fs_open' failed .+\xE2\x80\x9C/dev_bdvd/(?<broken_filename>.+)\xE2\x80\x9D.*?\r?$", DefaultOptions),
["'sys_fs_opendir' failed"] = new Regex(@"'sys_fs_opendir' failed .+\xE2\x80\x9C/dev_bdvd/(?<broken_directory>.+)\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)

View File

@ -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<byte> bufferPool = ArrayPool<byte>.Create(1024, 16);
public async Task<bool> CanHandleAsync(DiscordAttachment attachment)
public Task<bool> 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)

View File

@ -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

View File

@ -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)})"
);
}

View File

@ -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<bool> 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<string>(0);
var missingFiles = items["broken_filename"]?.Split(Environment.NewLine).Distinct().ToList() ?? new List<string>(0);
HashSet<string> knownFiles;
try
{
var irdFiles = await getIrdTask.ConfigureAwait(false);
knownFiles = new HashSet<string>(
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<string>(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)

View File

@ -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<List<Ird>> DownloadAsync(string productCode, string localCachePath, CancellationToken cancellationToken)
{
var result = new List<Ird>();
try
{
// first we search local cache and try to load whatever data we can
var localCacheItems = new List<string>();
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<string>();
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))

View File

@ -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<byte[]> RegionMd5Checksums; // 16 each
public int FileCount;
public List<IrdFile> Files;
public int Unknown; // always 0?
public byte[] Pic; // 115, v9 only?
public byte[] Data1; // 16
public byte[] Data2; // 16
// Pic for <v9
public int Uid;
public uint Crc32;
}
public class IrdFile
{
public long Offset;
public byte[] Md5Checksum;
}
}

View File

@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using DiscUtils.Iso9660;
using Force.Crc32;
namespace IrdLibraryClient.IrdFormat
{
public static class IrdParser
{
public static Ird Parse(byte[] content)
{
if (content == null)
throw new ArgumentNullException(nameof(content));
if (content.Length < 200)
throw new ArgumentException("Data is too small to be a valid IRD structure", nameof(content));
if (BitConverter.ToInt32(content, 0) != Ird.Magic)
using (var compressedStream = new MemoryStream(content, false))
using (var gzip = new GZipStream(compressedStream, CompressionMode.Decompress))
using (var decompressedStream = new MemoryStream())
{
gzip.CopyTo(decompressedStream);
content = decompressedStream.ToArray();
}
if (BitConverter.ToInt32(content, 0) != Ird.Magic)
throw new FormatException("Not a valid IRD file");
var result = new Ird();
using (var stream = new MemoryStream(content, false))
using (var reader = new BinaryReader(stream, Encoding.UTF8))
{
reader.ReadInt32(); // magic
result.Version = reader.ReadByte();
result.ProductCode = Encoding.ASCII.GetString(reader.ReadBytes(9));
result.TitleLength = reader.ReadByte();
result.Title = Encoding.UTF8.GetString(reader.ReadBytes(result.TitleLength));
result.UpdateVersion = Encoding.ASCII.GetString(reader.ReadBytes(4)).Trim();
result.GameVersion = Encoding.ASCII.GetString(reader.ReadBytes(5)).Trim();
result.AppVersion = Encoding.ASCII.GetString(reader.ReadBytes(5)).Trim();
if (result.Version == 7)
result.Id = reader.ReadInt32();
result.HeaderLength = reader.ReadInt32();
result.Header = reader.ReadBytes(result.HeaderLength);
result.FooterLength = reader.ReadInt32();
result.Footer = reader.ReadBytes(result.FooterLength);
result.RegionCount = reader.ReadByte();
result.RegionMd5Checksums = new List<byte[]>(result.RegionCount);
for (var i = 0; i < result.RegionCount; i++)
result.RegionMd5Checksums.Add(reader.ReadBytes(16));
result.FileCount = reader.ReadInt32();
result.Files = new List<IrdFile>(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<string> 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();
}
}
}
}

View File

@ -2,8 +2,14 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Crc32.NET" Version="1.2.0" />
<PackageReference Include="DiscUtils.OpticalDisk" Version="0.13.0-alpha" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CompatApiClient\CompatApiClient.csproj" />
</ItemGroup>

42
Tests/IrdTests.cs Normal file
View File

@ -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));
}
}
}
}

View File

@ -0,0 +1 @@


25
Tests/Tests.csproj Normal file
View File

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.11.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\IrdLibraryClient\IrdLibraryClient.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Test Files\*.ird">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -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