Merge pull request #525 from 13xforever/vnext

Rewrite log parser to properly support multi-value items
This commit is contained in:
Ilya 2020-03-02 17:06:19 +05:00 committed by GitHub
commit 1f9a18b0d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 291 additions and 106 deletions

View File

@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.2" />
</ItemGroup>
<ItemGroup>

View File

@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.2" />
</ItemGroup>
<ItemGroup>

View File

@ -14,7 +14,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.2" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="1.3.2" />
</ItemGroup>

View File

@ -24,28 +24,28 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="DSharpPlus" Version="4.0.0-nightly-00657" />
<PackageReference Include="DSharpPlus.CommandsNext" Version="4.0.0-nightly-00657" />
<PackageReference Include="DSharpPlus.Interactivity" Version="4.0.0-nightly-00657" />
<PackageReference Include="DSharpPlus" Version="4.0.0-nightly-00662" />
<PackageReference Include="DSharpPlus.CommandsNext" Version="4.0.0-nightly-00662" />
<PackageReference Include="DSharpPlus.Interactivity" Version="4.0.0-nightly-00662" />
<PackageReference Include="DuoVia.FuzzyStrings" Version="2.0.1" />
<PackageReference Include="Google.Apis.Drive.v3" Version="1.43.0.1841" />
<PackageReference Include="Google.Apis.Drive.v3" Version="1.44.0.1863" />
<PackageReference Include="ksemenenko.ColorThief" Version="1.1.1.4" />
<PackageReference Include="MathParser.org-mXparser" Version="4.4.2" />
<PackageReference Include="MegaApiClient" Version="1.7.1" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.1">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.2" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="1.3.2" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.9.10" />
<PackageReference Include="Nerdbank.Streams" Version="2.4.50" />
<PackageReference Include="Nerdbank.Streams" Version="2.4.60" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NLog" Version="4.6.8" />
<PackageReference Include="NLog.Extensions.Logging" Version="1.6.1" />

View File

@ -193,18 +193,17 @@ namespace CompatBot.EventHandlers.LogParsing
["RSX: Your GPU does not support"] = new Regex(@"RSX: Your GPU does not support (?<rsx_not_supported>.+)\..+?\r?$", DefaultOptions),
["RSX: GPU/driver lacks support"] = new Regex(@"RSX: GPU/driver lacks support for (?<rsx_not_supported>.+)\..+?\r?$", DefaultOptions),
["RSX: Swapchain:"] = new Regex(@"RSX: Swapchain: present mode (?<rsx_swapchain_mode>\d+?) in use.+?\r?$", DefaultOptions),
["F "] = new Regex(@"F \d+:\d+:\d+\.\d+ ({(?<fatal_error_context>.+?)} )?(?<fatal_error>.*?(\:\W*\r?\n\(.*?)*)\r?$", DefaultOptions),
["} SYS:"] = new Regex(@"F \d+:\d+:\d+\.\d+ (({(?<fatal_error_context>.+)} )?SYS:\s*\r?\n)(?<fatal_error>.*?)(\r?\n)(\r?\n|\xC2\xB7)", DefaultSingleLineOptions),
["F "] = new Regex(@"F \d+:\d+:\d+\.\d+ (({(?<fatal_error_context>[^}]+)} )?\w+:\s*(class [^\r\n]+ thrown: )?\r?\n?)(?<fatal_error>.*?)(\r?\n)(\r?\n|\xC2\xB7)", DefaultSingleLineOptions),
["Failed to load RAP file:"] = new Regex(@"Failed to load RAP file: (?<rap_file>.*?\.rap).*\r?$", DefaultOptions),
["Rap file not found:"] = new Regex(@"Rap file not found: (?<rap_file>.*?)\r?$", DefaultOptions),
["Rap file not found:"] = new Regex(@"Rap file not found: (\xE2\x80\x9C)?(?<rap_file>.*?)(\xE2\x80\x9D)?\r?$", DefaultOptions),
["Pad handler expected but none initialized"] = new Regex(@"(?<native_ui_input>Pad handler expected but none initialized).*?\r?$", DefaultOptions),
["XAudio2Thread"] = new Regex(@"XAudio2Thread\s*: (?<xaudio_init_error>.+failed\s*\((?<xaudio_error_code>0x.+)\).*)\r?$", DefaultOptions),
["cellAudio Thread"] = new Regex(@"XAudio2Backend\s*: (?<xaudio_init_error>.+failed\s*\((?<xaudio_error_code>0x.+)\).*)\r?$", DefaultOptions),
["using a Null renderer instead"] = new Regex(@"Audio renderer (?<audio_backend_init_error>.+) could not be initialized\r?$", DefaultOptions),
["PPU executable hash:"] = new Regex(@"PPU executable hash: PPU-(?<ppu_hash>\w+) \(<-\s*(?<ppu_hash_patch>\d+)\).*?\r?$", DefaultOptions),
["OVL executable hash:"] = new Regex(@"OVL executable hash: OVL-(?<ovl_hash>\w+) \(<-\s*(?<ovl_hash_patch>\d+)\).*?\r?$", DefaultOptions),
["SPU executable hash:"] = new Regex(@"SPU executable hash: SPU-(?<spu_hash>\w+) \(<-\s*(?<spu_hash_patch>\d+)\).*?\r?$", DefaultOptions),
["Loaded SPU image:"] = new Regex(@"Loaded SPU image: SPU-(?<spu_hash>\w+) \(<-\s*(?<spu_hash_patch>\d+)\).*?\r?$", DefaultOptions),
["PPU executable hash:"] = new Regex(@"PPU executable hash: PPU-(?<ppu_patch>\w+ \(<-\s*\d+\)).*?\r?$", DefaultOptions),
["OVL executable hash:"] = new Regex(@"OVL executable hash: OVL-(?<ovl_patch>\w+ \(<-\s*\d+\)).*?\r?$", DefaultOptions),
["SPU executable hash:"] = new Regex(@"SPU executable hash: SPU-(?<spu_patch>\w+ \(<-\s*\d+\)).*?\r?$", DefaultOptions),
["Loaded SPU image:"] = new Regex(@"Loaded SPU image: SPU-(?<spu_patch>\w+ \(<-\s*\d+\)).*?\r?$", DefaultOptions),
["'sys_fs_open' failed"] = new Regex(@"'sys_fs_open' failed (?!with 0x8001002c).+\xE2\x80\x9C(/dev_bdvd/(?<broken_filename>.+)|/dev_hdd0/game/NP\w+/(?<broken_digital_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),
["EDAT: "] = new Regex(@"EDAT: Block at offset (?<edat_block_offset>0x[0-9a-f]+) has invalid hash!.*?\r?$", DefaultOptions),
@ -227,12 +226,9 @@ namespace CompatBot.EventHandlers.LogParsing
"rap_file",
"vulkan_found_device",
"vulkan_compatible_device_name",
"ppu_hash",
"ppu_hash_patch",
"ovl_hash",
"ovl_hash_patch",
"spu_hash",
"spu_hash_patch",
"ppu_patch",
"ovl_patch",
"spu_patch",
"broken_filename",
"broken_digital_filename",
"broken_directory",
@ -290,10 +286,15 @@ namespace CompatBot.EventHandlers.LogParsing
void Copy(params string[] keys)
{
foreach (var key in keys)
{
if (state.CompleteCollection?[key] is string value)
state.WipCollection[key] = value;
if (state.CompleteMultiValueCollection?[key] is UniqueList<string> collection)
state.WipMultiValueCollection[key] = collection;
}
}
state.WipCollection = new NameValueCollection();
state.WipMultiValueCollection = new NameUniqueObjectCollection<string>();
Copy(
"build_and_specs", "fw_version_installed",
"vulkan_gpu", "d3d_gpu",
@ -309,6 +310,7 @@ namespace CompatBot.EventHandlers.LogParsing
private static void MarkAsComplete(LogParseState state)
{
state.CompleteCollection = state.WipCollection;
state.CompleteMultiValueCollection = state.WipMultiValueCollection;
Config.Log.Trace("----- complete section");
}

View File

@ -67,7 +67,11 @@ namespace CompatBot.EventHandlers.LogParsing
state.Error = LogParseState.ErrorCode.SizeLimit;
}
else if (result.IsCompleted)
{
if (!buffer.End.Equals(currentSectionLines.Last.Value.End))
await OnNewLineAsync(buffer.Slice(0), result.Buffer, currentSectionLines, state).ConfigureAwait(false);
await FlushAllLinesAsync(result.Buffer, currentSectionLines, state).ConfigureAwait(false);
}
var sectionStart = currentSectionLines.Count == 0 ? buffer : currentSectionLines.First.Value;
totalReadBytes += result.Buffer.Slice(0, sectionStart.Start).Length;
reader.AdvanceTo(sectionStart.Start);
@ -112,7 +116,6 @@ namespace CompatBot.EventHandlers.LogParsing
if (currentProcessor.OnExtract != null)
await TaskScheduler.AddAsync(state, Task.Run(() => currentProcessor.OnExtract(firstSectionLine, section, state)));
sectionLines.RemoveFirst();
}
}
}

View File

@ -92,12 +92,7 @@ namespace CompatBot.EventHandlers.LogParsing
lock (state)
{
if (MultiValueItems.Contains(group.Name))
{
var currentValue = state.WipCollection[group.Name];
if (!string.IsNullOrEmpty(currentValue))
currentValue += Environment.NewLine;
state.WipCollection[group.Name] = currentValue + strValue;
}
state.WipMultiValueCollection[group.Name].Add(strValue);
else
state.WipCollection[group.Name] = strValue;
if (CountValueItems.Contains(group.Name))

View File

@ -2,13 +2,16 @@
using System.Collections.Generic;
using System.Collections.Specialized;
using CompatBot.Database;
using CompatBot.Utils;
namespace CompatBot.EventHandlers.LogParsing.POCOs
{
public class LogParseState
{
public NameValueCollection CompleteCollection = null;
public NameUniqueObjectCollection<string> CompleteMultiValueCollection = null;
public NameValueCollection WipCollection = new NameValueCollection();
public NameUniqueObjectCollection<string> WipMultiValueCollection = new NameUniqueObjectCollection<string>();
public readonly Dictionary<string, int> ValueHitStats = new Dictionary<string, int>();
public readonly Dictionary<string, Dictionary<string, HashSet<string>>> Syscalls = new Dictionary<string, Dictionary<string, HashSet<string>>>();
public int Id = 0;

View File

@ -40,11 +40,12 @@ namespace CompatBot.EventHandlers
&& (e.Context.Message.Content?.EndsWith("?") ?? false)
&& e.Context.CommandsNext.RegisteredCommands.TryGetValue("8ball", out var cmd))
{
var prefixLen = e.Context.Prefix.Length; // workaround for resharper bug
var updatedContext = e.Context.CommandsNext.CreateContext(
e.Context.Message,
e.Context.Prefix,
cmd,
e.Context.Message.Content[(e.Context.Prefix.Length)..].Trim()
e.Context.Message.Content[prefixLen..].Trim()
);
try { await cmd.ExecuteAsync(updatedContext).ConfigureAwait(false); } catch { }
return;

View File

@ -0,0 +1,80 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace CompatBot.Utils
{
public class NameUniqueObjectCollection<TValue>: IDictionary<string, UniqueList<TValue>>
{
private readonly Dictionary<string, UniqueList<TValue>> dict;
private readonly IEqualityComparer<TValue> valueComparer;
public NameUniqueObjectCollection(IEqualityComparer<string> keyComparer = null, IEqualityComparer<TValue> valueComparer = null)
{
dict = new Dictionary<string, UniqueList<TValue>>(keyComparer);
this.valueComparer = valueComparer;
}
public IEnumerator<KeyValuePair<string, UniqueList<TValue>>> GetEnumerator() => dict.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)dict).GetEnumerator();
void ICollection<KeyValuePair<string, UniqueList<TValue>>>.Add(KeyValuePair<string, UniqueList<TValue>> item) => Add(item.Key, item.Value);
public void Add(string key, UniqueList<TValue> value)
{
value ??= new UniqueList<TValue>(valueComparer);
if (dict.TryGetValue(key, out var c))
c.AddRange(value);
else
dict.Add(key, value);
}
public void Add(string key, TValue value)
{
if (dict.TryGetValue(key, out var c))
c.Add(value);
else
{
dict[key] = c = new UniqueList<TValue>(valueComparer);
c.Add(value);
}
}
public void Clear() => dict.Clear();
bool ICollection<KeyValuePair<string, UniqueList<TValue>>>.Contains(KeyValuePair<string, UniqueList<TValue>> item) => ((IDictionary<string, UniqueList<TValue>>)dict).Contains(item);
void ICollection<KeyValuePair<string, UniqueList<TValue>>>.CopyTo(KeyValuePair<string, UniqueList<TValue>>[] array, int arrayIndex) => ((IDictionary<string, UniqueList<TValue>>)dict).CopyTo(array, arrayIndex);
bool ICollection<KeyValuePair<string, UniqueList<TValue>>>.Remove(KeyValuePair<string, UniqueList<TValue>> item) => ((IDictionary<string, UniqueList<TValue>>)dict).Remove(item);
public int Count => dict.Count;
public bool IsReadOnly => false;
public bool ContainsKey(string key) => dict.ContainsKey(key);
public bool Remove(string key) => dict.Remove(key);
public bool TryGetValue(string key, out UniqueList<TValue> value)
{
var result = dict.TryGetValue(key, out value);
if (!result)
dict[key] = value = new UniqueList<TValue>(valueComparer);
return result;
}
public UniqueList<TValue> this[string key]
{
get
{
TryGetValue(key, out var value);
return value;
}
set => dict[key] = (value ?? new UniqueList<TValue>(valueComparer));
}
public ICollection<string> Keys => dict.Keys;
public ICollection<UniqueList<TValue>> Values => dict.Values;
}
}

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Text;
@ -17,11 +16,13 @@ namespace CompatBot.Utils.ResultFormatters
{
internal static partial class LogParserResult
{
private static async Task BuildNotesSectionAsync(DiscordEmbedBuilder builder, LogParseState state, NameValueCollection items, DiscordClient discordClient)
private static async Task BuildNotesSectionAsync(DiscordEmbedBuilder builder, LogParseState state, DiscordClient discordClient)
{
var items = state.CompleteCollection;
var multiItems = state.CompleteMultiValueCollection;
var notes = new List<string>();
var (irdChecked, brokenDump, longestPath) = await HasBrokenFilesAsync(items).ConfigureAwait(false);
brokenDump |= !string.IsNullOrEmpty(items["edat_block_offset"]);
var (_, brokenDump, longestPath) = await HasBrokenFilesAsync(state).ConfigureAwait(false);
brokenDump |= multiItems["edat_block_offset"].Any();
var elfBootPath = items["elf_boot_path"] ?? "";
var isEboot = !string.IsNullOrEmpty(elfBootPath) && elfBootPath.EndsWith("EBOOT.BIN", StringComparison.InvariantCultureIgnoreCase);
var isElf = !string.IsNullOrEmpty(elfBootPath) && !elfBootPath.EndsWith("EBOOT.BIN", StringComparison.InvariantCultureIgnoreCase);
@ -74,12 +75,8 @@ namespace CompatBot.Utils.ResultFormatters
notes.Add("❌ Failed to decrypt game content, license file might be corrupted");
if (items["failed_to_boot"] != null)
notes.Add("❌ Failed to boot the game, the dump might be encrypted or corrupted");
if (items["failed_to_verify"] is string verifyFails)
{
var types = verifyFails.Split(Environment.NewLine).Distinct().ToList();
if (types.Contains("sce"))
notes.Add("❌ Failed to decrypt executables, PPU recompiler may crash or fail");
}
if (multiItems["failed_to_verify"].Contains("sce"))
notes.Add("❌ Failed to decrypt executables, PPU recompiler may crash or fail");
if (brokenDump)
notes.Add("❌ Some game files are missing or corrupted, please re-dump and validate.");
if (items["fw_version_installed"] is string fw && !string.IsNullOrEmpty(fw))
@ -350,9 +347,9 @@ namespace CompatBot.Utils.ResultFormatters
if (!string.IsNullOrEmpty(items["patch_error_file"]))
notes.Add($"⚠ Failed to load `patch.yml`, check syntax around line {items["patch_error_line"]} column {items["patch_error_column"]}");
var ppuPatches = GetPatches(items["ppu_hash"], items["ppu_hash_patch"], true);
var ovlPatches = GetPatches(items["ovl_hash"], items["ovl_hash_patch"], true);
var allSpuPatches = GetPatches(items["spu_hash"], items["spu_hash_patch"], false);
var ppuPatches = GetPatches(multiItems["ppu_patch"], true);
var ovlPatches = GetPatches(multiItems["ovl_patch"], true);
var allSpuPatches = GetPatches(multiItems["spu_patch"], false);
var spuPatches = new Dictionary<string, int>(allSpuPatches.Where(kvp => kvp.Value != 0));
if (ppuPatches.Any() || spuPatches.Any() || ovlPatches.Any())
{
@ -461,7 +458,7 @@ namespace CompatBot.Utils.ResultFormatters
}
if (items["failed_pad"] is string failedPad)
notes.Add($" Binding `{failedPad.Sanitize(replaceBackTicks: true)}` failed, check if device is connected.");
notes.Add($" Binding `{failedPad.Sanitize(replaceBackTicks: true)}` failed, check if device is connected.");
if (DesIds.Contains(serial))
notes.Add(" If you experience infinite load screen, clear game cache via `File` → `All games` → `Remove Disk Cache`");
@ -476,9 +473,10 @@ namespace CompatBot.Utils.ResultFormatters
msg += $" (update available: v{gameUpVer})";
notes.Add(msg);
}
if (items["ppu_hash"] is string ppuHashes
&& ppuHashes.Split(Environment.NewLine, 2, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault() is string firstPpuHash
&& !string.IsNullOrEmpty(firstPpuHash))
if (multiItems["ppu_patch"].FirstOrDefault() is string firstPpuPatch
&& ProgramHashPatch.Match(firstPpuPatch) is Match m
&& m.Success
&& m.Groups["hash"].Value is string firstPpuHash)
{
var exe = Path.GetFileName(items["elf_boot_path"] ?? "");
if (string.IsNullOrEmpty(exe) || exe.Equals("EBOOT.BIN", StringComparison.InvariantCultureIgnoreCase))
@ -491,8 +489,8 @@ namespace CompatBot.Utils.ResultFormatters
if (state.Error == LogParseState.ErrorCode.SizeLimit)
notes.Add(" The log was too large, so only the last processed run is shown");
BuildWeirdSettingsSection(builder, items, notes);
BuildMissingLicensesSection(builder, items, notes);
BuildWeirdSettingsSection(builder, state, notes);
BuildMissingLicensesSection(builder, serial, multiItems, notes);
var notesContent = new StringBuilder();
foreach (var line in SortLines(notes, pirateEmoji))
@ -500,12 +498,12 @@ namespace CompatBot.Utils.ResultFormatters
PageSection(builder, notesContent.ToString().Trim(), "Notes");
}
private static void BuildMissingLicensesSection(DiscordEmbedBuilder builder, NameValueCollection items, List<string> generalNotes)
private static void BuildMissingLicensesSection(DiscordEmbedBuilder builder, string serial, NameUniqueObjectCollection<string> items, List<string> generalNotes)
{
if (items["rap_file"] is string rap)
if (items["rap_file"] is UniqueList<string> raps && raps.Any())
{
var limitTo = 5;
var licenseNames = rap.Split(Environment.NewLine)
var licenseNames = raps
.Select(Path.GetFileName)
.Distinct()
.Except(KnownBogusLicenses)
@ -529,7 +527,7 @@ namespace CompatBot.Utils.ResultFormatters
builder.AddField("Missing Licenses", content);
var gameRegion = items["serial"] is string serial && serial.Length > 3 ? new[] {serial[2]} : Enumerable.Empty<char>();
var gameRegion = serial?.Length > 3 ? new[] {serial[2]} : Enumerable.Empty<char>();
var dlcRegions = licenseNames.Select(n => n[9]).Concat(gameRegion).Distinct().ToArray();
if (dlcRegions.Length > 1)
generalNotes.Add($"🤔 That is a very interesting DLC collection from {dlcRegions.Length} different regions");
@ -539,18 +537,21 @@ namespace CompatBot.Utils.ResultFormatters
}
}
private static async Task<(bool irdChecked, bool broken, int longestPath)> HasBrokenFilesAsync(NameValueCollection items)
private static async Task<(bool irdChecked, bool broken, int longestPath)> HasBrokenFilesAsync(LogParseState state)
{
var items = state.CompleteCollection;
var multiItems = state.CompleteMultiValueCollection;
var defaultLongestPath = "/PS3_GAME/USRDIR/".Length + (1+8+3)*2; // usually there's at least one more level for data files
if (!(items["serial"] is string productCode))
return (false, false, defaultLongestPath);
if (!productCode.StartsWith("B") && !productCode.StartsWith("M"))
{
if (P5Ids.Contains(productCode) && items["broken_digital_filename"] is string brokenDigitalFiles)
if (P5Ids.Contains(productCode)
&& multiItems["broken_digital_filename"] is UniqueList<string> brokenDigitalFiles
&& brokenDigitalFiles.Any())
{
var missingDigitalFiles = brokenDigitalFiles.Split(Environment.NewLine).Distinct().ToList();
if (missingDigitalFiles.Contains("USRDIR/ps3.cpk") || missingDigitalFiles.Contains("USRDIR/data.cpk"))
if (brokenDigitalFiles.Contains("USRDIR/ps3.cpk") || brokenDigitalFiles.Contains("USRDIR/data.cpk"))
return (false, true, defaultLongestPath);
}
return (false, false, defaultLongestPath);
@ -576,8 +577,8 @@ namespace CompatBot.Utils.ResultFormatters
return (false, false, defaultLongestPath);
var longestPath = knownFiles.Max(p => p.TrimEnd('.').Length);
if (string.IsNullOrEmpty(items["broken_directory"])
&& string.IsNullOrEmpty(items["broken_filename"]))
if (!multiItems["broken_directory"].Any()
&& !multiItems["broken_filename"].Any())
return (false, false, longestPath);
var missingDirs = items["broken_directory"]?.Split(Environment.NewLine).Distinct().ToList() ??

View File

@ -6,14 +6,17 @@ using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using CompatApiClient.Utils;
using CompatBot.EventHandlers.LogParsing.POCOs;
using DSharpPlus.Entities;
namespace CompatBot.Utils.ResultFormatters
{
internal static partial class LogParserResult
{
private static void BuildWeirdSettingsSection(DiscordEmbedBuilder builder, NameValueCollection items, List<string> generalNotes)
private static void BuildWeirdSettingsSection(DiscordEmbedBuilder builder, LogParseState state, List<string> generalNotes)
{
var items = state.CompleteCollection;
var multiItems = state.CompleteMultiValueCollection;
var notes = new List<string>();
var serial = items["serial"] ?? "";
int.TryParse(items["thread_count"], out var threadCount);
@ -136,18 +139,14 @@ namespace CompatBot.Utils.ResultFormatters
if (KnownDisableVertexCacheIds.Contains(serial) && !vertexCacheDisabled)
notes.Add("⚠ This game requires disabling `Vertex Cache` option");
if (items["rsx_not_supported"] is string notSupported)
if (multiItems["rsx_not_supported"].Contains("alpha-to-one for multisampling"))
{
var rsxCaveats = notSupported.Split(Environment.NewLine).Distinct().ToArray();
if (rsxCaveats.Contains("alpha-to-one for multisampling"))
{
if (items["msaa"] is string msaa && msaa != "Disabled")
generalNotes.Add(" The driver or GPU do not support all required features for proper MSAA implementation, which may result in minor visual artifacts");
}
if (items["msaa"] is string msaa && msaa != "Disabled")
generalNotes.Add(" The driver or GPU do not support all required features for proper MSAA implementation, which may result in minor visual artifacts");
}
var isWireframeBugPossible = items["gpu_info"] is string gpuInfo
&& Regex.IsMatch(gpuInfo, @"Radeon RX 5\d{3}", RegexOptions.IgnoreCase) // RX 590 is a thing 😔
&& !gpuInfo.Contains("RADV");
&& Regex.IsMatch(gpuInfo, @"Radeon RX 5\d{3}", RegexOptions.IgnoreCase) // RX 590 is a thing 😔
&& !gpuInfo.Contains("RADV");
if (items["msaa"] == "Disabled")
{
if (!isWireframeBugPossible)
@ -234,8 +233,9 @@ namespace CompatBot.Utils.ResultFormatters
notes.Add("⚠ `Resolution Scaling Threshold` below `16x16` may result in corrupted visuals and game crash");
}
}
var ppuPatches = GetPatches(items["ppu_hash"], items["ppu_hash_patch"], true);
var ppuHashes = GetHashes(items["ppu_hash"]);
var allPpuHashes = GetPatches(multiItems["ppu_patch"], false);
var ppuPatches = allPpuHashes.Where(kvp => kvp.Value > 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
var ppuHashes = new HashSet<string>(allPpuHashes.Keys, StringComparer.InvariantCultureIgnoreCase);
if (items["write_color_buffers"] == DisabledMark
&& !string.IsNullOrEmpty(serial)
&& KnownWriteColorBuffersIds.Contains(serial))
@ -292,7 +292,7 @@ namespace CompatBot.Utils.ResultFormatters
{
CheckP5Settings(serial, items, notes, ppuPatches);
CheckAsurasWrathSettings(serial, items, notes);
CheckJojoSettings(serial, items, notes, ppuPatches, ppuHashes, generalNotes);
CheckJojoSettings(serial, state, notes, ppuPatches, ppuHashes, generalNotes);
CheckSimpsonsSettings(serial, generalNotes);
CheckNierSettings(serial, items, notes, ppuPatches, ppuHashes, generalNotes);
CheckDod3Settings(serial, items, notes, ppuPatches, ppuHashes, generalNotes);
@ -512,8 +512,9 @@ namespace CompatBot.Utils.ResultFormatters
"18cf9a4e8196684ed9ee816f82649561fd1bf182",
};
private static void CheckJojoSettings(string serial, NameValueCollection items, List<string> notes, Dictionary<string, int> ppuPatches, HashSet<string> ppuHashes, List<string> generalNotes)
private static void CheckJojoSettings(string serial, LogParseState state, List<string> notes, Dictionary<string, int> ppuPatches, HashSet<string> ppuHashes, List<string> generalNotes)
{
var items = state.CompleteCollection;
if (AllStarBattleIds.Contains(serial) || serial == "BLJS10318" || serial == "NPJB00753")
{
if (items["audio_buffering"] == EnabledMark && items["audio_buffer_duration"] != "20")
@ -549,7 +550,7 @@ namespace CompatBot.Utils.ResultFormatters
if (serial == "BLUS31405"
&& items["compat_database_path"] is string compatDbPath
&& compatDbPath.Contains("JoJo ASB Emulator v.04")
&& !string.IsNullOrEmpty(items["rap_file"]))
&& state.CompleteMultiValueCollection["rap_file"].Any())
generalNotes.Add("🤔 Very interesting version of the game you got there");
}
}

View File

@ -38,6 +38,7 @@ namespace CompatBot.Utils.ResultFormatters
@"Operating system: (?<os_type>[^,]+), (Name: (?<posix_name>[^,]+), Release: (?<posix_release>[^,]+), Version: (?<posix_version>[^\r\n]+)|Major: (?<os_version_major>\d+), Minor: (?<os_version_minor>\d+), Build: (?<os_version_build>\d+), Service Pack: (?<os_service_pack>[^,]+), Compatibility mode: (?<os_compat_mode>[^,\r\n]+))\r?$",
DefaultSingleLine);
private static readonly Regex LinuxKernelVersion = new Regex(@"(?<version>\d+\.\d+\.\d+(-\d+)?)", DefaultSingleLine);
private static readonly Regex ProgramHashPatch = new Regex(@"(?<hash>\w+) \(<-\s*(?<patch_count>\d+)\)", DefaultSingleLine);
private static readonly char[] NewLineChars = {'\r', '\n'};
// rpcs3-v0.0.5-7105-064d0619_win64.7z
@ -204,7 +205,11 @@ namespace CompatBot.Utils.ResultFormatters
public static async Task<DiscordEmbedBuilder> AsEmbedAsync(this LogParseState state, DiscordClient client, DiscordMessage message, ISource source)
{
DiscordEmbedBuilder builder;
var collection = state.CompleteCollection ?? state.WipCollection;
state.CompleteCollection ??= state.WipCollection;
state.CompleteMultiValueCollection ??= state.WipMultiValueCollection;
var collection = state.CompleteCollection;
var multiValueCollection = state.CompleteMultiValueCollection;
if (collection?.Count > 0)
{
var ldrGameSerial = collection["ldr_game_serial"] ?? collection["ldr_path_serial"];
@ -242,13 +247,13 @@ namespace CompatBot.Utils.ResultFormatters
}
else
{
CleanupValues(collection);
CleanupValues(state);
BuildInfoSection(builder, collection);
var colA = BuildCpuSection(collection);
var colB = BuildGpuSection(collection);
BuildSettingsSections(builder, collection, colA, colB);
BuildLibsSection(builder, collection);
await BuildNotesSectionAsync(builder, state, collection, client).ConfigureAwait(false);
await BuildNotesSectionAsync(builder, state, client).ConfigureAwait(false);
}
}
else
@ -263,8 +268,10 @@ namespace CompatBot.Utils.ResultFormatters
return builder;
}
private static void CleanupValues(NameValueCollection items)
private static void CleanupValues(LogParseState state)
{
var items = state.CompleteCollection;
var multiItems = state.CompleteMultiValueCollection;
if (items["strict_rendering_mode"] == "true")
items["resolution_scale"] = "Strict Mode";
if (items["spu_threads"] == "0")
@ -284,19 +291,19 @@ namespace CompatBot.Utils.ResultFormatters
if (!string.IsNullOrEmpty(items["gpu_info"]))
{
items["gpu_info"] = items["gpu_info"].StripMarks();
items["driver_version_info"] = GetVulkanDriverVersion(items["vulkan_initialized_device"], items["vulkan_found_device"]) ??
GetVulkanDriverVersion(items["gpu_info"], items["vulkan_found_device"]) ??
items["driver_version_info"] = GetVulkanDriverVersion(items["vulkan_initialized_device"], multiItems["vulkan_found_device"]) ??
GetVulkanDriverVersion(items["gpu_info"], multiItems["vulkan_found_device"]) ??
GetOpenglDriverVersion(items["gpu_info"], items["driver_version_new"] ?? items["driver_version"]) ??
GetVulkanDriverVersionRaw(items["gpu_info"], items["vulkan_driver_version_raw"]);
}
if (items["driver_version_info"] != null)
items["gpu_info"] += $" ({items["driver_version_info"]})";
if (items["vulkan_compatible_device_name"] is string vulkanDevices)
if (multiItems["vulkan_compatible_device_name"] is UniqueList<string> vulkanDevices && vulkanDevices.Any())
{
var devices = vulkanDevices.Split(Environment.NewLine)
var devices = vulkanDevices
.Distinct()
.Select(n => new {name = n.StripMarks(), driverVersion = GetVulkanDriverVersion(n, items["vulkan_found_device"])})
.Select(n => new {name = n.StripMarks(), driverVersion = GetVulkanDriverVersion(n, multiItems["vulkan_found_device"])})
.Reverse()
.ToList();
if (string.IsNullOrEmpty(items["gpu_info"]) && devices.Count > 0)
@ -472,12 +479,12 @@ namespace CompatBot.Utils.ResultFormatters
return version;
}
private static string GetVulkanDriverVersion(string gpu, string foundDevices)
private static string GetVulkanDriverVersion(string gpu, UniqueList<string> foundDevices)
{
if (string.IsNullOrEmpty(gpu) || string.IsNullOrEmpty(foundDevices))
if (string.IsNullOrEmpty(gpu) || !foundDevices.Any())
return null;
var info = (from line in foundDevices.Split(Environment.NewLine)
var info = (from line in foundDevices
let m = VulkanDeviceInfo.Match(line)
where m.Success
select m
@ -698,25 +705,18 @@ namespace CompatBot.Utils.ResultFormatters
.ToList();
}
private static Dictionary<string, int> GetPatches(string hashList, string patchesList, in bool onlyApplied)
private static Dictionary<string, int> GetPatches(UniqueList<string> patchList, in bool onlyApplied)
{
if (string.IsNullOrEmpty(hashList) || string.IsNullOrEmpty(patchesList))
if (!patchList.Any())
return new Dictionary<string, int>(0);
var hashes = hashList.Split(Environment.NewLine);
var patches = patchesList.Split(Environment.NewLine);
if (hashes.Length != patches.Length)
var result = new Dictionary<string, int>(patchList.Count);
foreach (var patch in patchList)
{
Config.Log.Warn($"Hashes count: {hashes.Length}, Patches count: {patches.Length}");
return new Dictionary<string, int>(0);
}
var result = new Dictionary<string, int>();
for (var i = 0; i < hashes.Length; i++)
{
if (int.TryParse(patches[i], out var pCount))
var match = ProgramHashPatch.Match(patch);
if (match.Success && int.TryParse(match.Groups["patch_count"].Value, out var pCount))
if (!onlyApplied || pCount > 0)
result[hashes[i]] = pCount;
result[match.Groups["hash"].Value] = pCount;
}
return result;
}

View File

@ -0,0 +1,99 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace CompatBot.Utils
{
public class UniqueList<T>: IList<T>
{
private readonly List<T> list;
private readonly HashSet<T> set;
public UniqueList(IEqualityComparer<T> comparer = null)
{
list = new List<T>();
set = new HashSet<T>(comparer);
}
public UniqueList(int count, IEqualityComparer<T> comparer = null)
{
list = new List<T>(count);
set = new HashSet<T>(count, comparer);
}
public UniqueList(IEnumerable<T> collection, IEqualityComparer<T> comparer = null)
{
if (collection is ICollection c)
{
list = new List<T>(c.Count);
set = new HashSet<T>(c.Count, comparer);
}
else
{
list = new List<T>();
set = new HashSet<T>(comparer);
}
foreach (var item in collection)
if (set.Add(item))
list.Add(item);
}
public IEnumerator<T> GetEnumerator() => list.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)list).GetEnumerator();
public void Add(T item)
{
if (set.Add(item))
list.Add(item);
}
public void AddRange(IEnumerable<T> collection)
{
foreach (var item in collection)
Add(item);
}
public void Clear()
{
list.Clear();
set.Clear();
}
public bool Contains(T item) => set.Contains(item);
public void CopyTo(T[] array, int arrayIndex) => list.CopyTo(array, arrayIndex);
public bool Remove(T item)
{
list.Remove(item);
return set.Remove(item);
}
public int Count => list.Count;
public bool IsReadOnly => false;
public int IndexOf(T item) => list.IndexOf(item);
public void Insert(int index, T item)
{
if (set.Add(item))
list.Insert(index, item);
else if (IndexOf(item) != index)
throw new ArgumentException("Collection already contains item at different index", nameof(item));
}
public void RemoveAt(int index)
{
var item = this[index];
list.RemoveAt(index);
set.Remove(item);
}
public T this[int index] {
get => list[index];
set => throw new NotSupportedException();
}
}
}