mirror of
https://github.com/RPCS3/discord-bot.git
synced 2024-12-14 14:28:49 +00:00
Merge pull request #525 from 13xforever/vnext
Rewrite log parser to properly support multi-value items
This commit is contained in:
commit
1f9a18b0d2
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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" />
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
80
CompatBot/Utils/NameUniqueObjectCollection.cs
Normal file
80
CompatBot/Utils/NameUniqueObjectCollection.cs
Normal 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;
|
||||
}
|
||||
}
|
@ -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"))
|
||||
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() ??
|
||||
|
@ -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,15 +139,11 @@ 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)
|
||||
{
|
||||
var rsxCaveats = notSupported.Split(Environment.NewLine).Distinct().ToArray();
|
||||
if (rsxCaveats.Contains("alpha-to-one for multisampling"))
|
||||
if (multiItems["rsx_not_supported"].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");
|
||||
}
|
||||
}
|
||||
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");
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
99
CompatBot/Utils/UniqueList.cs
Normal file
99
CompatBot/Utils/UniqueList.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user