Files
archived-discord-bot/CompatBot/Utils/ResultFormatters/LogParserResultFormatter.cs
2026-01-29 11:26:30 +05:00

1294 lines
70 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Collections.Specialized;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using CompatApiClient;
using CompatApiClient.POCOs;
using CompatApiClient.Utils;
using CompatBot.Database.Providers;
using CompatBot.EventHandlers;
using CompatBot.EventHandlers.LogParsing.POCOs;
using CompatBot.EventHandlers.LogParsing.SourceHandlers;
using IrdLibraryClient;
namespace CompatBot.Utils.ResultFormatters;
internal static partial class LogParserResult
{
private static readonly Client CompatClient = new();
private static readonly IrdClient IrdClient = new();
private static readonly PsnClient.Client PsnClient = new();
private const RegexOptions DefaultSingleLine = RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase | RegexOptions.Singleline;
// RPCS3 v0.0.3-3-3499d08 Alpha | HEAD
// RPCS3 v0.0.4-6422-95c6ac699 Alpha | HEAD
// RPCS3 v0.0.5-7104-a19113025 Alpha | HEAD
// RPCS3 v0.0.5-42b4ce13a Alpha | minor
// RPCS3 v0.0.18-local_build Alpha | local_build
[GeneratedRegex(@"RPCS3 v(?<version_string>(?<version>(\d|\.)+)(-(?<build>(\d|\.)+))?-(?<commit>([0-9a-z_]+|unknown)( \S+)??))( (?<stage>\w+))?( \| (?<branch>[^|\r\n]+))?( \| Firmware version: (?<fw_version_installed>[^|\r\n]+))?( \| (?<unknown>.*))?\r?$", DefaultSingleLine)]
private static partial Regex BuildInfoInLog();
[GeneratedRegex(@"(\d{1,2}(th|rd|nd|st) Gen)?(?<cpu_model>[^|@]+?)\s*(((CPU\s*)?@\s*(?<cpu_speed>.+)\s*GHz\s*)|((APU with|((with|w/) )?Radeon|R\d, \d+ Compute) [^|]+)|((\w+[\- ]Core )?Processor))?\s* \| (?<thread_count>\d+) Threads \| (?<memory_amount>[0-9\.\,]+) GiB RAM( \| TSC: (?<tsc>\S+))?( \| (?<cpu_extensions>.*?))?\r?$", DefaultSingleLine)]
private static partial Regex CpuInfoInLog();
[GeneratedRegex(@"(?<version>\d+\.\d+\.\d+)", DefaultSingleLine)]
private static partial Regex LinuxKernelVersion();
[GeneratedRegex(@"(?<hash>\w+(-\d+)?)( \(<-\s*(?<patch_count>\d+)\))?", DefaultSingleLine)]
private static partial Regex ProgramHashPatch();
// rpcs3-v0.0.5-7105-064d0619_win64.7z
// rpcs3-v0.0.5-7105-064d0619_linux64.AppImage
[GeneratedRegex(@"rpcs3-v(?<version>(\d|\.)+)(-(?<build>\d+))?-(?<commit>[0-9a-f]+)_", DefaultSingleLine)]
private static partial Regex BuildInfoInUpdate();
[GeneratedRegex(@"'(?<device_name>.+)' running on driver (?<version>.+)\r?$", DefaultSingleLine)]
private static partial Regex VulkanDeviceInfo();
[GeneratedRegex(@"Intel\s?(®|\(R\))? (?<gpu_model>((?<gpu_family>(\w|®| )+) Graphics)( (?<gpu_model_number>P?\d+))?)(\s+\(|$)", DefaultSingleLine)]
private static partial Regex IntelGpuModel();
[GeneratedRegex(@"[A-Z]:/(?<program_files>Program Files( \(x86\))?/)?(?<desktop>([^/]+/)+Desktop/)?(?<rpcs3_folder>[^/]+/)*GuiConfigs/", DefaultSingleLine)]
private static partial Regex InstallPath();
private static readonly char[] NewLineChars = ['\r', '\n'];
private static readonly Version MinimumOpenGLVersion = new(4, 3);
private static readonly Version MinimumFirmwareVersion = new(4, 80);
private static readonly Version NvidiaFullscreenBugMinVersion = new(400, 0);
private static readonly Version NvidiaFullscreenBugMaxVersion = new(499, 99);
private static readonly Version NvidiaTextureMemoryBugMinVersion = new(526, 0);
private static readonly Version NvidiaTextureMemoryBugMaxVersion = new(526, 99);
private static readonly Version NvidiaRecommendedWindowsDriverVersion = new(512, 16);
private static readonly Version NvidiaRecommendedLinuxDriverVersion = new(515, 57);
private static readonly Version AmdRecommendedWindowsDriverVersion = new(24, 2, 1);
private static readonly Version IntelRecommendedWindowsDriverVersion = new(0, 101, 1660);
private static readonly Version NvidiaFullscreenBugFixed = new(0, 0, 6, 8204);
private static readonly Version TsxFaFixedVersion = new(0, 0, 12, 10995);
private static readonly Version RdnaMsaaFixedVersion = new(0, 0, 13, 11300);
private static readonly Version IntelThreadSchedulerBuildVersion = new(0, 0, 15, 12008);
private static readonly Version PsnDiscFixBuildVersion = new(0, 0, 18, 12783);
private static readonly Version CubebBuildVersion = new(0, 0, 19, 13050);
private static readonly Version FixedTlouRcbBuild = new(0, 0, 21, 13432); // the best I got was "it was fixed more than a year ago", so it's just a random build from a year ago
private static readonly Version FixedSimpsonsBuild = new(0, 0, 29, 15470);
private static readonly Version FixedSpuGetllarOptimizationBuild = new(0, 0, 36, 17938);
private static readonly Version BrokenMsvcOptimizationBuild = new(0, 0, 37, 18023);
private static readonly Version UnBrokenMsvcOptimizationBuild = new(0, 0, 37, 18147);
private static readonly Dictionary<string, string> RsxPresentModeMap = new()
{
["0"] = "VK_PRESENT_MODE_IMMEDIATE_KHR", // no vsync
["1"] = "VK_PRESENT_MODE_MAILBOX_KHR", // fast sync
["2"] = "VK_PRESENT_MODE_FIFO_KHR", // vsync
["3"] = "VK_PRESENT_MODE_FIFO_RELAXED_KHR", // adaptive vsync
};
private static readonly HashSet<string> KnownSyncFolders = new(StringComparer.InvariantCultureIgnoreCase)
{
"OneDrive",
"MEGASync",
"RslSync",
"BTSync",
"Google Drive",
"Google Backup",
"Dropbox",
};
private static readonly Dictionary<string, string> KnownDiscOnPsnIds = new(StringComparer.InvariantCultureIgnoreCase)
{
// Demon's Souls
{"BLES00932", "NPEB01202"},
{"BLUS30443", "NPUB30910"},
{"BCJS30022", "NPJA00102"},
//{"BCJS70013", "NPJA00102"},
// White Knight Chronicles II
{"BCJS30042", "NPJA00104"},
// Aquanaut's Holiday: Hidden Memories
{"BCJS30023", "NPJA00103"},
};
private static readonly string[] Known1080pIds =
[
"NPEB00258", "NPUB30162", "NPJB00068", // scott pilgrim
];
private static readonly string[] KnownDisableVertexCacheIds =
[
"BLES01219", "BLUS30616", // penguins blowhole
"NPEB00754", "NPUB30605", // real steel
"BLES01379", "BLUS30813", // marvel comic combat
"BLES00895", "BLUS30525", "BLAS50277", // marvel infinity gauntlet
"BLES01446", "BLUS30565", "BLJM60391", // sh downpour
"BLES02070", "BLUS31460", "BLJM61220", // kh2.5
"NPEB00258", "NPUB30162", "NPJB00068", // scott pilgrim
"NPEB00303", "NPUB30242", "NPHB00229", // crazy taxi
"NPEB00630", "NPUB30493", "NPJB00161", "NPHB00383", // daytona usa
"BLES00712", "BLUS30398", "BLJS10039", "BLJS50009", // way samurai 3
"NPEB00304", "NPUB30249", "NPJB00083", "NPHB00228", // sonic adventure
"BLES00024", "BLUS30032", "NPEB00788", "NPUB30636", "NPHB00481", // splinter cell double agent
"BLES01342", "BLUS30666", "BLES01343", "BLKS20333", "BLES01747", "BLES01748", "BLUS31062", "NPEB00890", "NPUB30700", // saints row 3
];
private static readonly HashSet<string> KnownNoRelaxedXFloatIds = [];
private static readonly HashSet<string> KnownNoApproximateXFloatIds =
[
"BLES02247", "BLUS31604", "BLJM61346", "NPEB02436", "NPUB31848", "NPJB00769", // p5
"BLES00932", "BLUS30443", // DeS
"BLUS30464", "BLES00760", "BLJM60296", "NPEB90226", "NPUB90375", "NPHB00200", // Skate 3
];
private static readonly HashSet<string> KnownFpsUnlockPatchIds =
[
"BLES00932", "BLUS30443", // DeS
"BLUS30481", "BLES00826", "BLJM60223", // Nier
"BLUS31197", "NPUB31251", "NPEB01407", "BLJM61043", "BCAS20311", // DoD3
"BLUS31405", // jojo asb
"BLJS10318", // jojo eoh
"BLES00148", "BLES00149", "BLES00154", "BLES00155", "BLES00156", "BLUS30072", "BLJS10013", "BLKS20048", "NPEB00740", "NPUB30588", // Call of Duty 4: Modern Warfare
"BLES01329", "BLES01330", "BLUS30778", "BLJM60413", "BLAS50546", "BLES01885", "BLES01886", "BLUS31202", "BLJM61086", "BLAS50624", // The Elder Scrolls V: Skyrim
"BLJM61211", "BLJM55085", "BLAS50763", "NPEB02076", "NPUB31552", "NPJB00653", // Resident Evil HD
"BLJM61272", "NPEB02226", "NPUB31689", "NPJB00726", // Resident Evil 0
"BLES01773", "BLUS31051", "NPEB90478", // Resident Evil: Revelations
"BLES00875", "BLUS30490", "BLJM60180", "BLUD80019", // 3D Dot Game Heroes
"BLES01265", "NPEB00625", // Alice: Madness Returns
"NPEB01088", // Alien Rage
"NPEB01224", // Aliens: Colonial Marines
"NPEB00316", // All Zombies Must Die!
"BLES00704", // Alpha Protocol
"BLES01227", "BLUS30721", "BLJM60409", // Asura's Wrath
"BLES00827", "BLUS30515", "NPEB01156", "NPEB90170", // Batman: Arkham Asylum
];
private static readonly HashSet<string> KnownWriteColorBuffersIds =
[
"BLUS30235", "BLES00453", // AC/DC Live: Rock Band Track Pack
"BLUS30399", "BCJS30021", "BCAS20050", // Afrika
"BLUS30607", "BLES0126", "NPUB30545", "BLJM60359", // Alice: Madness Returns
"BLUS30049", "NPUB90035", // All-Pro Football 2K8
"BLES00937", "BLUS30555", "NPEB90257", "NPUB90448", // Apache: Air Assault
"BLUS30027", "BLES00039", "BLJM60012", "NPEB90009", "NPUB90008", // Armored Core 4
"BLES00370", "BLUS30187", "BLJM60066", "NPHB00033", "NPJB90067", // Armored Core: For Answer
"BLES00168", "BLUS30057", // Army of TWO
"BLES01763", "BLUS31069", "NPEB01216", "NPEB01217", "NPUB30987", "BLES01767", "BLJM60578", "NPJB00332", "NPEB90470, NPUB90862", // Army of TWO: The Devil's Cartel
"BLES01882", "BLES01883", "BLES01884", "BLUS31193", "NPEB01396", "NPUB31246", "BLJM61056", "BLUS31483", "BLES02085", // Assassin's Creed IV: Black Flag
"BLUS31152", "BLES01793", "NPUB31115", "NPEB01262", "BLJM60486", "BLJM61051", // Atelier Ayesha: The Alchemist of Dusk
"BLUS30941", "BLES01593", "BLJM60348", "BLJM55041", "NPJB60142", // Atelier Meruru: The Apprentice of Arland
"BLES00253", "NPHB00035", "NPUB30126", "BCAS20043", "BLJM60077", "NPHB00268", "NPJB90105", // Battle Fantasia
"BLES01275", "BLUS30762", "NPEB00723", "NPJB00202", "NPUB30742", "BLAS50380", "BLJM60384", "NPHB00491", "NPUB90600", "BLET70016", // Battlefield 3
"BLES01832", "BLUS31162", "NPEB01303", "NPUB31148", "BLJM61039", "BLAS50588", "NPJB00377", "NPHB00546", "BLET70034", "NPUB90959", "NPJB90637", // Battlefield 4
"BLES00259", "BLUS30118", "BLJM60071", "BLES00261", "BLUS30121", "NPEB90073", "NPUB90070", "NPJB90112", // Battlefield: Bad Company
"BLES00773", "BLES00779", "BLUS30517", "BLUS30458", "NPEB00724", "NPUB30583", "BLJM60197", "NPEB90212", "NPUB90347", "NPHB00186", "NPUB90316", "BLET70004", "NPHB00156", // Battlefield: Bad Company 2
"BLES02039", "BLUS31440", "BLJM61203", "NPUB31511", "BLAS50725", "NPEB02038", "NPJB00641", "NPHB00673", "BLET70061", "NPUB91010", "NPJB90712", // Battlefield: Hardline
"BLES00286", "BLUS30154", "NPEB90097", "NPUB90151", // Beijing 2008
"NPEB00435", "NPUB30394", // Beyond Good & Evil HD
"BCAS25017", "BCES01121", "BCES01122", "BCES01123", "BCUS98298", "BCUS99134", "BCJS37009", "NPEA00513", "NPUA81087", "NPHA80260", "BCES01888", "NPUA81088", "NPJA00097", "NPEA90127", "NPJA90259", "NPUA72074", // Beyond: Two Souls
"BLES01397", "BLUS30831", "NPEB01119", "NPEB90417", // Birds of Steel
"BLES00759", "BLUS30295", "BLJM60244", "NPEB90250", "NPUB90428", // Blur
"NPUB30505", "NPEB00563", // Castlevania: Harmony of Despair
"NPUB30722", // Closure
"BLES00673", "BLUS30313", "NPEB90176", "NPUB90294", // Colin McRae: DiRT 2
"BLUS30782", "BLES01402", "BLES01396", "BLJM60993", "BLAS50397", "BLES01765", "BLJM60517", // Dark Souls
"NPEB00409", // Deadstorm Pirates
"BLUS30024", "BLES00042", "BLAS50012", "BLJM60029", "NPUB90004", // Def Jam: Icon
"BLES00932", "BLUS30443", "BCJS70013", "BCJS30022", // Demon's Souls
"BLES01857", "BLUS31181", "BCJS35001", "NPEB02021", "NPUB31202", "NPEB02097", "NPUB31545", "NPJA90277", "NPEB90553", "NPJA90286", // Destiny
"BLES01287", "NPEB00848", "NPUB30680", "BLUS30724", "BLES01548", "BLUS30975", // DiRT 3
"NPJA00037", "NPJA90090", // Dress
"BLES01147", "BLUS30615", "NPEB00828", "NPUB30655", "BLES01297", "BLJS10121", "BLAS50341", "NPHB00484", "NPEB90334", "NPUB90573", // Duke Nukem Forever
"BLES00075", "BLUS30042", // Fantastic Four: Rise of the Silver Surfer
"BLUS30178", "BLES00325", "BLES00324", "NPEB00599", "NPUB30523", "BLJM60108", "NPJB00397", "NPHB00423", // Far Cry 2
"BLES01137", "BLES01138", "BLUS30687", "BLJM60532", "NPEB01096", "NPUB30825", "BLES01995", "BLUS31393", "NPJB00559", "BLET70025", // Far Cry 3
"NPEB01322", "NPUB31142", // Far Cry 3: Blood Dragon
"BLES02011", "BLES02012", "BLUS31420", "NPEB01982", "BLJM61179", "NPEB02272", "NPUB31470", "NPJB00603", // Far Cry 4
"BLUS30504", "BLES01062", "BLJM60196", "BLJM60303", "NPEB90285", "NPUB90456", // Fist of the North Star: Ken's Rage
"BCES00005", "BCUS98142", "NPEA90003", "BCJS30005", "BCAS20009", // Formula One Championship Edition
"NPUB30418", "NPEB00466", // From Dust
"BLES01724", "BLUS31040", "NPUB30874", "NPEB01112", "NPEB90442", "NPUB90815", // Fuse
"NPEB01300", "NPUB31140", // God Mode
"BCAS25003", "BCES00510", "BCES00516", "BCES00799", "BCJS37001", "BCUS98111", "BCKS15003", "NPUA70080", // God of War 3 / Demo
"BCES01741", "BCES01742", "BCUS98232", "NPEA00445", "NPUA80918", "NPHA80258", "BCJS37008", "BCAS25016", "NPJA00094", "BCKS15012", "NPEA90123", "NPUA70265", "NPUA70269", "NPEA90115", "NPUA70216", "BCET70050", // God of War: Ascension
"BLES00229", "BLUS30127", "NPEB00882, NPUB30702, BLES00258", "BLJM60093", "BLES01128", "BLUS30682", // Grand Theft Auto IV
"BLES01807", "BLUS31156", "NPEB01283", "NPUB31154", "BLJM61019", "BLJM61182", "BLJM61304", "NPJB00516", // Grand Theft Auto V
"BLES00887", "NPEB00907", "NPUB30704", "BLUS30524", "BLJM60235", // Grand Theft Auto: Episodes from Liberty City
"BLJS10286", "BLAS50770", // Gundam Breaker 2
"BCES00797", "BCES00802", "BCUS98164", "BCAS20107", "BCES00458", "BCES01293", "BCJS30040", "NPEA90076", "NPUA70112", "NPHA80118", "NPEA90053", "NPUA70088", "NPJA90129", "NPHA80086", // Heavy Rain
"NPEB01341", "NPUB31200", // Hotline Miami
"NPEB02007", "NPUB31481", // Hotline Miami 2: Wrong Number
"BLUS30924", "NPUA80227", // Jeopardy!
"BLUS30084", "BLES00143", "BLJM60058", "BLJM60127", "NPUB90053", // Juiced 2: Hot Import Nights
"BLUS30215", // Karaoke Revolution Presents: American Idol Encore 2
"BCAS20066", "BCES00081", "BCUS98116", "NPUA98116", "NPUA70034", "NPJA90092", "NPEA90034", "NPUA70034", // Killzone 2 / demo
"BCES01007", "BCUS98234", "BCAS25008", "BCJS30066", "NPUA70167", "NPEA00321", "NPJA00071", "NPEA90084", "NPUA70133", "NPJA90176", "NPHA80140", "NPEA90085", "NPJA90178", "NPUA70134", "NPHA80141", "BCET01007", "NPEA90086", "BCET70024", "NPUA70118", "NPUA70138", // Killzone 3 / Demo / Beta / MP
"BLES01251", "BLUS30710", "NPEB01055", "BLJS10191", "NPUB90707", // Kingdoms of Amalur: Reckoning
"BCES00141", "BCUS98148", "NPEA00241", "NPUA80472", "BCAS20058", "BCJS30018", "BCUS98199", "BCJS70009", "BCKS10059", "BCES00611", "BCUS98208", "BCAS20078", "NPEA00147", "NPUA70045", "NPJA90097", "NPHA80067", "BCUS70030", "BCET70002", "BCET70011", // LittleBigPlanet
"BCES01422", "BCUS98254", "NPUA80848", "NPEA00421", "NPHA80239", "NPJA90244", "NPEA90117", "NPUA70249", // LittleBigPlanet Karting / demo
"BLES01525", "BLUS30917", "BLJS10125", "NPJB00273", "BLJS10168", // Lollipop Chainsaw
"BLES00710", "BLUS30434", "BLJM60177", "BLAS50173", "NPEB90189", "NPUB90321", "NPHB00138", "NPEB90181", "NPUB90300", // Lost Planet 2
"BLJS10184", // Macross 30 Ginga o Tsunagu Utagoe
"BLES00285", "BLUS30146", "BLJM60088", "BLUS30170", "NPUB90138", // Madden NFL 09
"BLUS31178", "NPUB31183", "BLES01850", "BLAS50622", "NPEB90493", "NPUB90953", // Madden NFL 25
"BLES00546", "BLUS30294", // Marvel: Ultimate Alliance 2
"BLES00867", "BLUS30518", // Megamind
"BLES00246", "BLUS30109", "BLJM67001", "NPEB02182", "NPUB31633", "BLAS55002", "BLAS55004", "BLKS25002", "NPJB00698", "NPEB90116", "NPUB90176", "NPJB90149", "NPHB00065", "NPHB00067", // Metal Gear Solid 4 / Demo
"BLES02102", "BLUS31491", "BLJM61247", "NPEB02140", "NPUB31594", "BLAS50815", "NPJB00673", "NPHB00731", // Metal Gear Solid V / Demo
"BLES00362", "BLUS30190", "BLJS10046", "BLJM60368", "BLES00652", "BLUS30442", "NPEB00546", "NPUB30471", "NPJB00503", "NPHB00411", // Midnight Club: Los Angeles
"BCUS98167", "BCJS30041", "BCES00701", "NPUA80535", "NPEA00291", "NPUA70096", "NPEA90062", "NPUA70074", // Modnation Racers / demo / beta
"BCES00006", "BCUS98137", "NPEA00333", "NPUA80499", // Motorstorm
"NPEA00333", "NPUA80678", "NPJA00077", "NPHA80190", // MotorStorm RC
"BCES00484", "BCUS98242", "NPEA00315", "NPUA80661", // Motorstorm Apocalypse
"BCES00129", "BCUS98155", "NPEA90090", "NPUA70140", "NPEA90033", // Motorstorm Pacific Rift / Demo
"BLES02032", "BLUS31455", "NPUB31530", // MX vs. ATV Supercross
"BLAS50266", "BLES00949", "BLUS30566", "NPEB00587", "NPUB30521", "BLES00950", "NPEB90293", "NPUB90488", // Need for Speed Hot Pursuit / Demo
"BLES01659", "BLES01660", "BLUS31010", "NPEB01042", "NPUB30789", "BLJM60519", "BLAS50482", "NPJB00228", "NPHB00494", "NPEB90472", "NPUB90927", // Need for Speed Most Wanted
"BLUS30391", "BLES00681", "BLES00682", "BLAS50137", "NPUB90325", "NPHB00153", "NPEB90194", // Need for Speed Shift
"BLUS30481", "BLES00826", "BLJM60223", // Nier
"BLJM60467", "NPEB00900", "NPUB30720", "NPJB00195", "NPHB00495", "BLAS50523", // Okami HD
"BLJS10221", // Onechanbara Z: Kagura with NoNoNo!
"BLES01090", // PDC World Championship Darts: Pro Tour
"BLUS30852", "NPEA00271", "NPUA30059", // Plants vs. Zombies
"BLES00680", "BLUS30418", "NPEB00833", "NPUB30638", "BLJM60265", "BLKS20202", "NPHB00465", "BLES01294", "BLUS30758", "BLJM60403", "BLAS50382", "BLKS20315", "NPJB00504", // Red Dead Redemption
"BLES00485", "BLUS30270", "BLES00816", "BLUS30491", "NPEB00687", "NPUB30564", "BLJM60199", "NPEB90124", "NPJB90152", "NPHB00070", // Resident Evil 5
"BLES01465", "BLJM60405", "BLUS30855", "NPEB01150", "NPUB30984", "BLES01683", "NPJB00319", "NPEB90464", "NPUB90772", "NPUB90864", "NPEB90426", "NPJB90541", "NPHB00514", // Resident Evil 6
"BLES02133", "NPUB31720", "BLJM61294", // Ride
"NPEB02103", "NPUB31577", // Risk
"NPEB02269", // Risk Urban Assault
"BLES00385", "BLUS30147", // Rock Band 2
"BLES00986", "BLUS30463", "NPUB90505", "NPEB90291", // Rock Band 3
"BLUS30327", "BLUS30623", "BLUS30351", "BLUS30352", // Rock Band Track Packs
"BLES00777", // Rugby League Live
"BLES01472", "NPEB01197", "NPEB01846", // Rugby League Live 2
"BLES01889", "BLES01954", "BLUS31205", "NPEB01404", "NPUB31257", "BLJS10246", "NPJB00551", "BLES02019", "BLUS31416", "NPEB90502", "NPUB90965", // Saints Row IV
"BLES02095", "NPEB02121", "NPUB31604", "BLUS31496", // Saints Row: Gat out of Hell
"BLES01342", "BLUS30666", "NPEB00888", "NPEB00890", "NPUB30700", "BLES01343", "BLKS20333", "BLES01747", "BLES01748", "BLUS31062", "NPEB90361", "NPUB90632", // Saints Row: The Third
"BLUS30580", "BLES01066", "NPEB00618", "NPUB30539", // Shift 2 Unleashed
"BLES00124", "BLES00125", "BLUS30059", "BLJM60070", "NPUB90057", // Skate
"BLUS30253", "BLES00461", "NPEB90131", "NPUB90196", "NPHB00080", // Skate 2
"BLUS30464", "BLES00760", "BLJM60296", "NPEB90226", "NPUB90375", "NPHB00200", // Skate 3
"BLES01981", "BLUS31401", "NPEB01905", "BLES02145", "BLUS31532", // Sniper Elite 3
"BLES01290", "BLUS30798", "NPEB01009", "BLJM60503", "NPUB31291", "BLES01812", "NPEB90404", "NPUB90749", // Sniper Elite V2
"BLES01646", "BLUS30839", "NPEB01232", "BLJM61145", "NPUB31090", "NPJB00535", "NPEB90471", "NPUB90928", // Sonic & All-Stars Racing Transformed / demo
"BLES00750", "BLUS30342", "NPEB90229", "NPUB90275", // Sonic & SEGA All-Stars Racing
"BLES00296", "BLUS30160", "BLJS10026", "NPEB90099", "NPJB90130", // SoulCalibur IV / Demo
"BLUS30736", "BLES01250", "NPEB01363", "NPUB31195", // SoulCalibur V
"BLES00055", "BLES00056", "BLUS30031", "BLUS30030", // Spider-Man 3
"BLES01702", "BLJS10187", "BLUS31002", "NPEB01140", "NPJB00236", "NPUB30899", // Tekken Tag Tournament 2
"BLUS30527", "BLES00884", // Test Drive Unlimited 2 - Needs RCB Also
"BLES01987", "BLUS30964", "BLJS10160", // The Witch and the Hundred Knight
"BLES01329", "BLES01330", "BLUS30778", "BLJM60413", "BLAS50546", "BLES01885", "BLES01886", "BLUS31202", "BLJM61086", "BLAS50624", // The Elder Scrolls V: Skyrim
"BCES00057", "BLUS30093", // Time Crisis 4
"BCES01070", "BLUS30528", "BLJS10091", "NPEA90078", "NPUB90464", // Time Crisis: Razing Storm
"BLES00326", "BLUS30180", "NPEB01170", "NPUB30913", "BLES00487", "NPEB90118", "NPUB90171", // Tom Clancy's EndWar
"BLUS30186", "BLES00330", "NPUB31101", "NPUB90167", // Tom Clancy's H.A.W.X
"BLES01766", "BLES01878", "BLUS31025", "BLJM61057", "NPEB01379", "NPUB31248", "BLES01879", "BLAS50612", "NPJB00410", "NPHB00604", "BLET70047", // Tom Clancy's Splinter Cell: Blacklist
"BCAS20087", "BCJS30034", // Toro! Let's Party!
"NPHA80058", "NPUA80247", // Trash Panic
"BLES01231", "BLUS30738", // UFC Undisputed 3
"BCES00065", "BCUS98103", "BCJS30015", "BCAS20024", "NPEA00363", "NPUA80697", "NPHA80193", "NPEA90018", "NPUA98103", "NPJA90063", // Uncharted: Drake's Fortune / Demo / Beta
"BCES00509", "BCES00727", "BCES00757", "BCUS98123", "BCJS30035", "BCAS20097", "NPEA00364", "NPEA00365", "NPUA80698", "NPHA80194", "BCKS10086", "NPEA00369", "NPEA90055", "NPUA70092", "NPJA90127", "BCET70015", "NPUA70051", "NPUA70049", // Uncharted 2: Among Thieves
"BCES01175", "BCES01176", "BCUS98233", "BCJS37004", "BCAS25009", "BCES01670", "BCES01692", "BCUS99086", "BCAS25014", "NPEA00422", "NPUA80858", "NPUA70183", "BCET70034", "BCET70043", "NPUA70153", "NPUA70180", "NPHA80158", // Uncharted 3: Drake's Deception
"NPUA80137", "BLUS30923", // Wheel of Fortune
"BCAS20100", "BCES00664", "NPEA00057", "NPJA00031", "NPUA80105", "NPHA80039", // WipEout HD / Fury
"BLES01909", "NPEB01789", "NPUB31297", "BLES01910", "BLUS31220", "BLJM61201", // Wolfenstein: The New Order
"BLES01721", "BLUS31168", "NPEB01072", "NPUB31153", "BLJM60575", "NPEB90467", // WRC 3: FIA World Rally Championship
"BLES01874", "BLUS31509", "NPEB01381", "NPUB31452", "NPJB00624", "BLJM61195", "NPEB90523", // WRC 4: FIA World Rally Championship
"BLES01937", "NPEB01815", "BLUS31277", // WWE 2K14
"BLES00694", "BLUS30378", // Karaoke Revolution
];
private static readonly HashSet<string> KnownResScaleThresholdIds =
[
"BCAS20270", "BCES01584", "BCES01585", "BCJS37010", "BCUS98174", // The Last of Us
"NPEA00435", "NPEA90122", "NPHA80243", "NPHA80279", "NPJA00096", "NPJA00129", "NPUA70257", "NPUA80960", "NPUA81175",
];
private static readonly HashSet<string> KnownMotionControlsIds =
[
"BCES00797", "BCES00802", "BCUS98164", "BCJS30040", "NPEA90053", "NPEA90076", "NPUA70088", "NPUA70112", // heavy rain
"BCAS25017", "BCES01121", "BCES01122", "BCES01123", "BCUS98298", "NPEA00513", "NPUA81087", "NPEA90127", "NPJA90259", "NPUA72074", "NPJA00097", // beyond two souls
"NPEA00094", "NPEA00250", "NPJA00039", "NPUA80083", // flower
"NPEA00036", "NPUA80069", "NPJA00004", // locoroco
"BCES01284", "BCUS98247", "BCUS99142", "NPEA00429", "NPUA80875", "NPEA90120", "NPUA70250", // sly cooper 4
"BCAS20112", "BCAS20189", "BCKS10112", "BLES01101", "BLJS10072", "BLJS10114", "BLJS50026", "BLUS30652", "NPEB90321", // no more heroes
"BCUS98116", "BCES00081", "BCAS20066", "BCJS30032", // killzone 2
];
private static readonly HashSet<string> KnownGamesThatRequireInterpreter =
[
"NPEB02126", "NPUB31590", // don't starve
"BLES00023", "BLUS30006", "BLJS10007", // Blazing Angels: Squadrons of WWII
"NPEB00220", "NPUB30254", "NPHB00297", // Landit Bandit
"BLES00189", "BLES00190", "BLUS30105", // Soldier of Fortune: Payback
"BLES02185", "NPEB02302", "NPUB31776", // Rugby League Live 3
"NPEB01386", "NPJB00562", "NPUB31244", "NPUB31569", // Assassin's Creed lib
"BCUS98144", "BCES00112", "NPEA90021", "NPUA80112", // NBA08
"BLUS31152", "BLES01793", "BLJM60486", "NPUB31115", "NPEB01262", // Atelier Ayesha: The Alchemist of Dusk
"BLES00103", "BLUS30052", "BLJM60072", "NPEB90043", "NPUB90063", // Blazing Angels 2: Secret Missions of WWII
"BLES00249", "BLES00250", "BLES00263", "BLUS30137", "NPEB90066", "NPUB90051", // robert ludlum remove after #14845 is fixed
"BLES01796", "BLUS31381", "BLJM61068", "NPEB01332", "NPUB31231", "NPJB00415", // farming simulator
"BLUS31465", "BLES02061", "BLES02062", "BLJM61208", "NPEB02064", "NPUB31547", // Assassin's Creed Rogue
"BLES01667", "BLES01668", "BLES01669", "BLES01968", "BLUS30991", "BLJM61174", "BLJM60516", "NPEB01099", "NPUB30826", // Assassin's Creed 3
"BLES01882", "BLES01883", "BLES01884", "BLUS31193", "BLJM61056", "BLES02085", "BLUS31483", "NPEB01396", "NPUB31246", // Assassin's Creed 4
"BLES01466", "BLES01467", "BLUS30905", "BLUS31145", "BLKS20325", "NPEB00880", "NPUB30707", "BLET70017", "NPUB90674", // Assassin's Creed rev
"BLES00909", "BLES00910", "BLES00911", "BLUS30537", "BLJM60250", "BLKS20231", "NPEB00600", "NPUB30522", "BLET70013", "NPUB90483", // Assassin's Creed bro
];
private static readonly HashSet<string> KnownGamesThatRequireAccurateXfloat =
[
"BLES00229", "BLES00258", "BLES00887", "BLES01128", // gta4 + efls
"BLJM55011", "BLJM60235", "BLJM60459", "BLJM60525", "BLJM61180", "BLKS20073", "BLKS20198", // gta4 + efls
"BLUS30127", "BLUS30149", "BLUS30524", "BLUS30682", // gta4 + efls
"NPEB00882", "NPUB30702", "NPUB30704", "NPEB00511", // gta4 + efls
"BLES01867", "BLUS31184", "BLJS10218", "NPEB01369", "NPUB31219", "BLES01999", "NPEB01955", "NPUB50339", // metro LL
];
private static readonly HashSet<string> KnownGamesThatWorkWithRelaxedZcull =
[
"BLAS50296", "BLES00680", "BLES01179", "BLES01294", "BLUS30418", "BLUS30711", "BLUS30758", //rdr
"BLJM60314", "BLJM60403", "BLJM61181", "BLKS20315",
"NPEB00833", "NPHB00465", "NPHB00466", "NPUB30638", "NPUB30639",
"NPUB50139", // max payne 3 / rdr bundle???
"BLAS55005", "BLES00246", "BLJM57001", "BLJM67001", "BLKS25001", "BLUS30109", "BLUS30148", //mgs4
"NPEB00027", "NPEB02182", "NPEB90116", "NPJB00698", "NPJB90149", "NPUB31633",
"NPHB00065", "NPHB00067",
"BCAS20100", "BCES00664", "NPEA00057", "NPJA00031", "NPUA80105", // wipeout hd
"BCES01584", "BCES01585", "BCUS98174", "BCJS37010", "BCAS20270", "NPEA00435", "NPUA80960", "NPJA00096", "NPHA80243", "NPEA00521", "NPUA81175", "NPJA00129", "NPHA80279", "NPEA90122", "NPUA70257", "NPHA80246", // tlou
"BCES01175", "BCES01176", "BCUS98233", "BCJS37004", "BCAS25009", "BCES01670", "BCES01692", "BCUS99086", "BCAS25014", "NPEA00422", "NPUA80858", "NPUA70183", "BCET70034", "BCET70043", "NPUA70153", "NPUA70180", "NPHA80158", // uc3
"BLJM61211", "BLJM55085", "BLAS50763", "NPEB02076", "NPUB31552", "NPJB00653", // Resident Evil HD
"BLJM61272", "NPEB02226", "NPUB31689", "NPJB00726", // Resident Evil 0
"BLES01773", "BLJM60518", "BLUS31051", "NPEB01187", "NPUB30991", "NPJB00310", "NPEB90478", "NPUB90938", "NPJB90584", "NPHB00545", // Resident Evil: Revelations
"BLJM61249", "BLAS50796", "NPJB00684", "NPHB00720", "NPJB90764", "NPHB00719", // Yakuza 0
"BLJM61149", "NPJB00532", "NPHB00654", "NPJB90690", // Yakuza Ishin!
"BLJM61313", "NPJB00772", // Yakuza Kiwami
"BLES00148", "BLES00149", "BLES00154", "BLES00155", "BLES00156", "BLUS30072", "BLJS10013", "BLKS20048", "NPEB00740", "NPUB30588", // Call of Duty 4: Modern Warfare
"BLES01356", "BLUS30720", "BLJM60379", "BLES01794", "BLUS31155", "BLJM61012", "NPEB01268", "NPUB31117", "NPJB00335", "NPEB90366", "NPEB90439", "NPJB90524", "NPJB90581", "NPUB90813", // Dragon's Dogma
"BLUS30089", "BLES00199", "BLES00158", "BLJM60050", "BLKS20049", "NPUB30451", "NPEB00393", // Assassin's Creed
"BLES01898", "BLUS31194", "BLJM61014", "NPUB31245", "NPEB01428", // Armored Core: Verdict Day
"BCES01007", "BCUS98234", "BCAS25008", "BCJS30066", "NPUA70167", "NPEA00321", "NPJA00071", "NPEA90084", "NPUA70133", "NPJA90176", "NPHA80140", "NPEA90085", "NPJA90178", "NPUA70134", "NPHA80141", "BCET01007", "NPEA90086", "BCET70024", "NPUA70118", "NPUA70138", // Killzone 3
"BLES00362", "BLUS30190", "BLJS10046", "BLJM60368", "BLES00652", "BLUS30442", "NPEB00546", "NPUB30471", "NPJB00503", "NPHB00411", // Midnight Club: Los Angeles
"BCUS98167", "BCJS30041", "BCES00701", "NPUA80535", "NPEA00291", "NPUA70096", "NPEA90062", "NPUA70074", // Modnation Racers
"BCES00484", "BCUS98242", "NPEA00315", "NPUA80661", // Motorstorm Apocalypse
];
private static readonly HashSet<string> KnownBogusLicenses = new(StringComparer.InvariantCultureIgnoreCase)
{
"UP0700-NPUB30932_00-NNKDLFULLGAMEPTB.rap",
"EP0700-NPEB01158_00-NNKDLFULLGAMEPTB.rap",
};
private static readonly HashSet<string> KnownCustomLicenses = new(StringComparer.InvariantCultureIgnoreCase)
{
"EP4062-NPEB02436_00-PPERSONA5X000000.rap",
"UP2611-NPUB31848_00-PPERSONA5X000000.rap",
};
// v1.5 https://wiki.rpcs3.net/index.php?title=Help:Game_Patches#Disable_SPU_MLAA
private static readonly HashSet<string> KnownMlaaSpuHashes = new(StringComparer.InvariantCultureIgnoreCase)
{
"1549476fe258150ff9f902229ffaed69a932a9c1",
"191fe1c92c8360992b3240348e70ea37d50812d4",
"2239af4827b17317522bd6323c646b45b34ebf14",
"45f98378f0837fc6821f63576f65d47d10f9bbcb",
"5177cbc4bf45c8a0a6968c2a722da3a9e6cfb28b",
"530c255936b07b25467a58e24ceff5fd4e2960b7",
"702d0205a89d445d15dc0f96548546c4e2e7a59f",
"794795c449beef176d076816284849d266f55f99",
"7b5ea49122ec7f023d4a72452dc7a9208d9d6dbf",
"7cd211ff1cbd33163eb0711440dccbb3c1dbcf6c",
"82b3399c8e6533ba991eedb0e139bf20c7783bac",
"9001b44fd7278b5a6fa5385939fe928a0e549394",
"931132fd48a40bce0bec28e21f760b1fc6ca4364",
"969cf3e9db75f52a6b41074ccbff74106b709854",
"976d2128f08c362731413b75c934101b76c3d73b",
"a129a01a270246c85df18eee0e959ef4263b6510",
"ac189d7f87091160a94e69803ac0cff0a8bb7813",
"df5b1c3353cc36bb2f0fb59197d849bb99c3fecd",
"e3780fe1dc8953f849ac844ec9688ff4da3ca3ae",
};
private static readonly TimeSpan OldBuild = TimeSpan.FromDays(30);
private static readonly TimeSpan VeryOldBuild = TimeSpan.FromDays(60);
//private static readonly TimeSpan VeryVeryOldBuild = TimeSpan.FromDays(90);
private static readonly TimeSpan AncientBuild = TimeSpan.FromDays(180);
private static readonly TimeSpan PrehistoricBuild = TimeSpan.FromDays(365);
private static readonly char[] PrioritySeparator = [' '];
private static readonly string[] EmojiPriority = new[]{ "😱", "💢", "‼️", "❗", "❌", "⁉️", "⚠️", "❓", "❔", "✅", "" }
.Select(e => e.TrimEnd('\ufe0f'))
.ToArray();
private const string EnabledMark = "[x]";
private const string DisabledMark = "[\u00a0]";
public static async Task<DiscordEmbedBuilder> AsEmbedAsync(this LogParseState state, DiscordClient client, DiscordMessage message, ISource source)
{
DiscordEmbedBuilder builder;
state.CompletedCollection ??= state.WipCollection;
state.CompleteMultiValueCollection ??= state.WipMultiValueCollection;
var collection = state.CompletedCollection;
if (collection.Count > 0)
{
var ldrGameSerial = collection["ldr_game_serial"] ?? collection["ldr_path_serial"];
if (collection["serial"] is string serial
&& KnownDiscOnPsnIds.TryGetValue(serial, out var psnSerial)
&& !string.IsNullOrEmpty(ldrGameSerial)
&& ldrGameSerial.StartsWith("NP", StringComparison.OrdinalIgnoreCase)
&& ldrGameSerial.Equals(psnSerial, StringComparison.OrdinalIgnoreCase))
{
collection["disc_to_psn_serial"] = serial;
collection["serial"] = psnSerial;
collection["game_category"] = "HG";
}
serial = collection["serial"] ?? "";
var titleUpdateInfoTask = TitleUpdateInfoProvider.GetAsync(serial, Config.Cts.Token);
var titleMetaTask = PsnClient.GetTitleMetaAsync(serial, Config.Cts.Token);
var gameInfo = await client.LookupGameInfoWithEmbedAsync(serial, collection["game_title"], true, category: collection["game_category"]).ConfigureAwait(false);
try
{
var titleUpdateInfo = await titleUpdateInfoTask.ConfigureAwait(false);
if (titleUpdateInfo?.Tag?.Packages?.LastOrDefault()?.Version is string tuVersion)
collection["game_update_version"] = tuVersion;
}
catch {}
try
{
var resInfo = await titleMetaTask.ConfigureAwait(false);
if (resInfo?.Resolution is string resList)
collection["game_supported_resolution_list"] = resList;
}
catch {}
builder = new(gameInfo.embedBuilder) {Thumbnail = null}; // or this will fuck up all formatting
TitleInfo? compatData = null;
if (gameInfo.compatResult?.Results?.TryGetValue(serial, out compatData) is true
&& compatData?.Status is string titleStatus)
collection["game_status"] = titleStatus;
collection["embed_title"] = builder.Title ?? "";
if (state.Error == LogParseState.ErrorCode.PiracyDetected)
{
var msg = "__You are being denied further support until you legally dump the game__.\n" +
"Please note that the RPCS3 community and its developers do not support piracy.\n" +
"Most of the issues with pirated dumps occur due to them having been tampered with in some way " +
"and therefore act unpredictably on RPCS3.\n" +
"If you need help obtaining legal dumps, please read [the quickstart guide](https://rpcs3.net/quickstart).";
builder.WithColor(Config.Colors.LogAlert)
.WithTitle("Pirated content detected")
.WithDescription(msg);
}
else
{
CleanupValues(state);
BuildInfoSection(builder, collection);
var hwUpdateTask = HwInfoProvider.AddOrUpdateSystemAsync(client, message, collection, Config.Cts.Token);
var colA = BuildCpuSection(collection);
var colB = BuildGpuSection(collection);
BuildSettingsSections(builder, collection, colA, colB);
BuildLibsSection(builder, collection);
await BuildNotesSectionAsync(builder, state, client).ConfigureAwait(false);
await hwUpdateTask.ConfigureAwait(false);
}
}
else
{
builder = new()
{
Description = "Log analysis failed. Please try again.",
Color = Config.Colors.LogResultFailed,
};
if (state.TotalBytes < 2048 || state.ReadBytes < 2048)
builder.Description = "Log analysis failed, most likely cause is an empty log. Please try again.";
}
return await builder.AddAuthorAsync(client, message, source, state).ConfigureAwait(false);
}
private static void CleanupValues(LogParseState state)
{
var items = state.CompletedCollection!;
var multiItems = state.CompleteMultiValueCollection!;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
var itemKeys = items.AllKeys;
foreach (var key in itemKeys)
items[key] = items[key]?.TrimEnd('”');
var colKeyList = multiItems.Keys;
foreach (var key in colKeyList)
multiItems[key] = new(multiItems[key].Select(i => i.TrimEnd('”')));
}
if (items["strict_rendering_mode"] == "true")
items["resolution_scale"] = "Strict Mode";
if (items["spu_threads"] == "0")
items["spu_threads"] = "Auto";
var threadSched = (items["thread_scheduler"] ?? items["spu_secondary_cores"]) switch
{
"false" => "OS",
"Operating System" => "OS",
"true" => "RPCS3",
"RPCS3 Scheduler" => "RPCS3",
"RPCS3 Alternative Scheduler" => "RPCS3 Alt",
string s => s,
null => null,
};
if (!string.IsNullOrEmpty(threadSched))
items["thread_scheduler"] = threadSched;
if (items["fw_version_installed"] is { Length: >1 } fwVer
&& fwVer.Split('.') is [_, {Length: 1}])
{
// fix x.y0 reported as x.y for some reason in logs; x.0y is properly logged, so there's no confusion
items["fw_version_installed"] += "0";
}
static string? StripOpenGlMaker(string? gpuName)
{
if (gpuName is null)
return null;
if (gpuName.EndsWith("(intel)", StringComparison.OrdinalIgnoreCase)
|| gpuName.EndsWith("(nvidia)", StringComparison.OrdinalIgnoreCase)
|| gpuName.EndsWith(" corporation)", StringComparison.OrdinalIgnoreCase)
|| gpuName.EndsWith("(amd)", StringComparison.OrdinalIgnoreCase)
|| gpuName.EndsWith(" inc.)", StringComparison.OrdinalIgnoreCase) // ati
|| gpuName.EndsWith("(apple)", StringComparison.OrdinalIgnoreCase)
|| gpuName.EndsWith("(x.org)", StringComparison.OrdinalIgnoreCase) // linux
)
{
var idx = gpuName.LastIndexOf('(');
if (idx > 0)
gpuName = gpuName[..idx].TrimEnd();
}
if (gpuName.EndsWith("/PCIe/SSE2"))
gpuName = gpuName[..^10];
return gpuName;
}
if (items["vulkan_initialized_device"] != null)
items["gpu_info"] = items["vulkan_initialized_device"];
else if (items["driver_manuf_new"] != null)
items["gpu_info"] = items["driver_manuf_new"];
else if (items["vulkan_gpu"] != "\"\"")
items["gpu_info"] = items["vulkan_gpu"];
else if (items["d3d_gpu"] != "\"\"")
items["gpu_info"] = items["d3d_gpu"];
else if (items["driver_manuf"] != null)
items["gpu_info"] = items["driver_manuf"];
if (!string.IsNullOrEmpty(items["gpu_info"]))
{
items["gpu_info"] = items["gpu_info"]?.StripMarks();
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"]);
}
items["gpu_info"] = StripOpenGlMaker(items["gpu_info"]);
if (items["driver_version_info"] != null)
{
items["gpu_name"] = items["gpu_info"];
items["gpu_info"] += $" ({items["driver_version_info"]})";
}
if (multiItems["vulkan_compatible_device_name"] is { Count: > 0 } vulkanDevices)
{
var devices = vulkanDevices
.Distinct()
.Select(n => new { name = n.StripMarks(), driverVersion = GetVulkanDriverVersion(n, multiItems["vulkan_found_device"]) })
.Reverse()
.ToList();
if (string.IsNullOrEmpty(items["gpu_info"]) && devices.Count > 0)
{
var discreteGpu = devices.FirstOrDefault(d => IsNvidia(d.name))
?? devices.FirstOrDefault(d => IsAmd(d.name))
?? devices.First();
items["gpu_name"] = StripOpenGlMaker(discreteGpu.name);
items["discrete_gpu_info"] = $"{discreteGpu.name} ({discreteGpu.driverVersion})";
items["driver_version_info"] = discreteGpu.driverVersion;
}
items["gpu_available_info"] = string.Join(Environment.NewLine, devices.Select(d => $"{d.name} ({d.driverVersion})"));
}
if (items["af_override"] is string af)
{
if (af == "0")
items["af_override"] = "Auto";
else if (af == "1")
items["af_override"] = "Disabled";
}
if (items["zcull"] == "true")
items["zcull_status"] = "Disabled";
else if (items["relaxed_zcull"] == "true")
items["zcull_status"] = "Relaxed";
else
items["zcull_status"] = "Full";
if (items["lib_loader"] is string libLoader)
{
var liblv2 = libLoader.Contains("liblv2", StringComparison.InvariantCultureIgnoreCase);
var auto = libLoader.Contains("auto", StringComparison.InvariantCultureIgnoreCase);
var manual = libLoader.Contains("manual", StringComparison.InvariantCultureIgnoreCase);
var strict = libLoader.Contains("strict", StringComparison.InvariantCultureIgnoreCase);
if (auto && manual)
items["lib_loader"] = "Auto & manual select";
else if (liblv2 && manual)
items["lib_loader"] = "Liblv2.sprx & manual";
else if (liblv2 && strict)
items["lib_loader"] = "Liblv2.sprx & strict";
else if (auto)
items["lib_loader"] = "Auto";
else if (manual)
items["lib_loader"] = "Manual selection";
else
items["lib_loader"] = "Liblv2.sprx only";
}
items["frame_limit_combined"] = (items["frame_limit"], items["vsync"]) switch
{
("Off", "true") => "VSync",
("Off", _) => "Off",
( _, "true") => items["frame_limit"] + "+VSync",
_ => items["frame_limit"],
};
if (items["shader_mode"] is string sm)
{
var async = sm.Contains("async", StringComparison.InvariantCultureIgnoreCase);
var recompiler = sm.Contains("recompiler", StringComparison.InvariantCultureIgnoreCase);
var interpreter = sm.Contains("interpreter", StringComparison.InvariantCultureIgnoreCase);
items["shader_mode"] = (async, recompiler, interpreter) switch
{
( true, true, false) => "Async",
( true, _, true) => "Async+Interpreter",
(false, true, false) => "Recompiler only",
(false, _, true) => "Interpreter only",
_ => items["shader_mode"],
};
}
else if (items["disable_async_shaders"] is DisabledMark or "false")
items["shader_mode"] = "Async";
else if (items["disable_async_shaders"] is EnabledMark or "true")
items["shader_mode"] = "Recompiler only";
if (items["rsx_fifo_mode"] is string rsxfm && rsxfm.StartsWith('"') && rsxfm.EndsWith('"'))
items["rsx_fifo_mode"] = rsxfm[1..^1];
if (items["cpu_preempt_count"] is "0")
items["cpu_preempt_count"] = "Disabled";
if (items["xfloat_mode"] is null) // accurate, approximate, relaxed, inaccurate
{
if (items["relaxed_xfloat"] is null)
{
items["xfloat_mode"] = (items["accurate_xfloat"], items["approximate_xfloat"]) switch
{
("true", _) => "Accurate",
(_, "true") => "Approximate",
(_, _) v => $"[{(v.Item1 == "true" ? "a" : "-")}{(v.Item2 == "true" ? "x" : "-")}]",
};
}
else
{
items["xfloat_mode"] = (items["accurate_xfloat"], items["approximate_xfloat"], items["relaxed_xfloat"]) switch
{
("true", "false", "true") => "Accurate",
("false", "true", "true") => "Approximate",
("false", "false", "true") => "Relaxed",
("false", "false", "false") => "Inaccurate",
(_, _, _) v => $"[{(v.Item1 == "true" ? "a" : "-")}{(v.Item2 == "true" ? "x" : "-")}{(v.Item3 == "true" ? "r" : "-")}]",
};
}
}
static string? reformatDecoder(string? dec)
{
if (string.IsNullOrEmpty(dec))
return dec;
var match = Regex.Match(dec, @"(?<name>[^(]+)(\((?<type>.+)\))?", RegexOptions.ExplicitCapture | RegexOptions.Singleline);
var n = match.Groups["name"].Value.TrimEnd();
var t = match.Groups["type"].Value;
return string.IsNullOrEmpty(t) ? n : $"{n}/{t}";
}
items["ppu_decoder"] = reformatDecoder(items["ppu_decoder"]);
items["spu_decoder"] = reformatDecoder(items["spu_decoder"]);
if (items["win_path"] != null)
items["os_type"] = "Windows";
else if (items["lin_path"] != null)
items["os_type"] = "Linux";
if (items["os_type"] == "Windows" && GetWindowsVersion(items["driver_version_new"] ?? items["driver_version"]) is string winVersion)
items["os_windows_version"] = winVersion;
if (items["library_list"] is string libs)
{
var libList = libs.Split('\n')
.Select(l => l.Trim(' ', '\t', '-', '\r', '[', ']'))
.Where(s => !string.IsNullOrEmpty(s))
.Select(l => l.Split(':'))
.Select(p => (name: p[0], mode: p.Length > 1 ? p[1] : ""))
.ToList();
var newFormat = libs.Contains(".sprx:");
if (newFormat)
{
items["library_list"] = "None";
var lleList = new List<string>(libList.Count);
var hleList = new List<string>(libList.Count);
foreach (var (name, mode) in libList)
{
if (mode == "lle")
lleList.Add(name);
else if (mode == "hle")
hleList.Add(name);
else
Config.Log.Warn($"Unknown library override mode '{mode}' in {libs}");
}
items["library_list_lle"] = lleList.Count > 0 ? string.Join(", ", lleList) : "None";
items["library_list_hle"] = hleList.Count > 0 ? string.Join(", ", hleList) : "None";
}
else
items["library_list"] = libList.Count > 0 ? string.Join(", ", libList.Select(i => i.name)) : "None";
}
else
items["library_list"] = "None";
if (items["app_version"] is string appVer && !appVer.Equals("Unknown", StringComparison.InvariantCultureIgnoreCase))
items["game_version"] = appVer;
else if (items["disc_app_version"] is string discAppVer && !discAppVer.Equals("Unknown", StringComparison.InvariantCultureIgnoreCase))
items["game_version"] = discAppVer;
else if (items["disc_package_version"] is string discPkgVer && !discPkgVer.Equals("Unknown", StringComparison.InvariantCultureIgnoreCase))
items["game_version"] = discPkgVer;
if (items["game_version"] is string gameVer && gameVer.StartsWith("0"))
items["game_version"] = gameVer[1..];
if (items["game_update_version"] is string gameUpVer && gameUpVer.StartsWith("0"))
items["game_update_version"] = gameUpVer[1..];
items["custom_config"] = items["custom_config"] is { Length: > 0 } ? EnabledMark : DisabledMark;
items["booting_savestate"] = items["booting_savestate"] is {Length: >0} ? EnabledMark : DisabledMark;
if (multiItems["fatal_error"] is UniqueList<string> {Count: > 0} fatalErrors)
multiItems["fatal_error"] = new(fatalErrors.Select(str => str.Contains("'tex00'") ? str.Split('\n', 2)[0] : str), fatalErrors.Comparer);
multiItems["broken_filename"].AddRange(multiItems["broken_filename_or_dir"]);
multiItems["broken_directory"].AddRange(multiItems["broken_filename_or_dir"]);
foreach (var key in items.AllKeys)
{
var value = items[key];
if ("true".Equals(value, StringComparison.CurrentCultureIgnoreCase))
value = EnabledMark;
else if ("false".Equals(value, StringComparison.CurrentCultureIgnoreCase))
value = DisabledMark;
items[key] = value?.Sanitize(false);
}
}
private static void PageSection(DiscordEmbedBuilder builder, string notesContent, string sectionName)
{
if (!string.IsNullOrEmpty(notesContent))
{
var fields = notesContent.Split(Environment.NewLine).BreakInFieldContent(100).ToList();
if (fields.Count > 1)
for (var idx = 0; idx < fields.Count; idx++)
builder.AddField($"{sectionName} #{idx + 1} of {fields.Count}", fields[idx].content);
else
builder.AddField(sectionName, fields[0].content);
}
}
private static async ValueTask<(UpdateInfo? updateInfo, bool isTooOld)> CheckForUpdateAsync(NameValueCollection items)
{
if (string.IsNullOrEmpty(items["build_and_specs"]))
return default;
var currentBuildCommit = items["build_commit"];
if (string.IsNullOrEmpty(currentBuildCommit))
currentBuildCommit = null;
var updateInfo = await CompatClient.GetUpdateAsync(Config.Cts.Token, currentBuildCommit).ConfigureAwait(false);
if (updateInfo.ReturnCode != StatusCode.UpdatesAvailable && currentBuildCommit is not null)
updateInfo = await CompatClient.GetUpdateAsync(Config.Cts.Token).ConfigureAwait(false);
var link = updateInfo.X64?.LatestBuild.Windows?.Download
?? updateInfo.X64?.LatestBuild.Linux?.Download
?? updateInfo.X64?.LatestBuild.Mac?.Download
?? updateInfo.Arm?.LatestBuild.Windows?.Download
?? updateInfo.Arm?.LatestBuild.Linux?.Download
?? updateInfo.Arm?.LatestBuild.Mac?.Download;
if (updateInfo.ReturnCode is not StatusCode.UpdatesAvailable || link is null)
return default;
var latestBuildInfo = BuildInfoInUpdate().Match(link.ToLowerInvariant());
if (latestBuildInfo.Success && VersionIsTooOld(items, latestBuildInfo, updateInfo))
return (updateInfo, true);
return (updateInfo, false);
}
private static bool VersionIsTooOld(NameValueCollection items, Match update, UpdateInfo updateInfo)
{
if (updateInfo.GetUpdateDelta() is TimeSpan updateTimeDelta
&& updateTimeDelta < Config.BuildTimeDifferenceForOutdatedBuildsInDays)
return false;
if (Version.TryParse(items["build_version"], out var logVersion)
&& Version.TryParse(update.Groups["version"].Value, out var updateVersion))
{
if (logVersion < updateVersion)
return true;
if (int.TryParse(items["build_number"], out var logBuild)
&& int.TryParse(update.Groups["build"].Value, out var updateBuild))
{
if (logBuild + Config.BuildNumberDifferenceForOutdatedBuilds < updateBuild)
return true;
}
return false;
}
return !SameCommits(items["build_commit"], update.Groups["commit"].Value);
}
private static bool SameCommits(string? commitA, string? commitB)
{
if (string.IsNullOrEmpty(commitA) && string.IsNullOrEmpty(commitB))
return true;
if (string.IsNullOrEmpty(commitA) || string.IsNullOrEmpty(commitB))
return false;
var len = Math.Min(commitA.Length, commitB.Length);
return commitA[..len] == commitB[..len];
}
private static string? GetOpenglDriverVersion(string? gpuInfo, string? version)
{
if (string.IsNullOrEmpty(version))
return null;
gpuInfo ??= "";
if (gpuInfo.Contains("Radeon", StringComparison.InvariantCultureIgnoreCase)
|| gpuInfo.Contains("AMD", StringComparison.InvariantCultureIgnoreCase)
|| gpuInfo.Contains("ATI ", StringComparison.InvariantCultureIgnoreCase))
return AmdDriverVersionProvider.GetFromOpenglAsync(version).ConfigureAwait(false).GetAwaiter().GetResult();
return version;
}
private static string? GetVulkanDriverVersion(string? gpu, UniqueList<string> foundDevices)
{
if (string.IsNullOrEmpty(gpu) || foundDevices.Length is 0)
return null;
var info = (from line in foundDevices
let m = VulkanDeviceInfo().Match(line)
where m.Success
select m
).FirstOrDefault(m => m.Groups["device_name"].Value == gpu);
var result = info?.Groups["version"].Value;
if (string.IsNullOrEmpty(result))
return null;
if (gpu.Contains("Radeon", StringComparison.InvariantCultureIgnoreCase) ||
gpu.Contains("AMD", StringComparison.InvariantCultureIgnoreCase) ||
gpu.Contains("ATI ", StringComparison.InvariantCultureIgnoreCase))
{
if (gpu.Contains("RADV", StringComparison.InvariantCultureIgnoreCase))
return result;
return AmdDriverVersionProvider.GetFromVulkanAsync(result).ConfigureAwait(false).GetAwaiter().GetResult();
}
if (gpu.Contains("Intel", StringComparison.InvariantCultureIgnoreCase)
&& Version.TryParse(result, out var ver)
&& ver is { Minor: > 400, Build: >= 0 })
{
var build = ((ver.Minor & 0b_11) << 12) | ver.Build;
var minor = ver.Minor >> 2;
return new Version(ver.Major, minor, build).ToString();
}
if (Version.TryParse(result, out var nvVer))
{
result = $"{nvVer.Major}.{nvVer.Minor:00}";
if (nvVer.Build > 0)
result += $".{nvVer.Build}";
}
else
{
if (result.EndsWith(".0.0"))
result = result[..^4];
if (result.Length > 3 && result[^2] == '.')
result = result[..^1] + "0" + result[^1];
}
return result;
}
private static string? GetVulkanDriverVersionRaw(string? gpuInfo, string? version)
{
if (string.IsNullOrEmpty(version))
return null;
gpuInfo ??= "";
var ver = int.Parse(version);
if (IsAmd(gpuInfo))
{
var major = (ver >> 22) & 0x3ff;
var minor = (ver >> 12) & 0x3ff;
var patch = ver & 0xfff;
var result = $"{major}.{minor}.{patch}";
if (gpuInfo.Contains("RADV", StringComparison.InvariantCultureIgnoreCase))
return result;
return AmdDriverVersionProvider.GetFromVulkanAsync(result).ConfigureAwait(false).GetAwaiter().GetResult();
}
else
{
var major = (ver >> 22) & 0x3ff;
var minor = (ver >> 14) & 0xff;
var patch = ver & 0x3fff;
if (major == 0 && gpuInfo.Contains("Intel", StringComparison.InvariantCultureIgnoreCase))
return $"{minor}.{patch}";
if (IsNvidia(gpuInfo))
{
if (patch == 0)
return $"{major}.{minor}";
return $"{major}.{minor:00}.{(patch >> 6) & 0xff}.{patch & 0x3f}";
}
return $"{major}.{minor}.{patch}";
}
}
private static string? GetWindowsVersion(string? driverVersionString)
{
// see https://learn.microsoft.com/en-us/windows-hardware/drivers/display/wddm-2-1-features#driver-versioning
if (string.IsNullOrEmpty(driverVersionString) || !Version.TryParse(driverVersionString, out var driverVer))
return null;
return driverVer.Major switch
{
6 => "XP", //XDDM
7 => "Vista",
8 => "7",
9 => "8",
10 => "8.1",
>= 20 and < 40 => driverVer.Major switch
{
// see https://en.wikipedia.org/wiki/Windows_Display_Driver_Model#WDDM_2.0
// and https://learn.microsoft.com/en-us/windows-hardware/drivers/display/windows-vista-display-driver-model-design-guide
20 => "10",
21 => "10 1607",
22 => "10 1703",
23 => "10 1709",
24 => "10 1803",
25 => "10 1809",
26 => "10 1903",
27 => "10 2004",
28 => "10 20H1 Preview",
29 => "10 21H1",
30 => "11 21H2",
31 => "11 22H2",
32 => "11 24H2",
_ => null,
},
_ => null,
};
}
private static string? GetWindowsVersion(Version windowsVersion) =>
windowsVersion.Major switch
{
5 => windowsVersion.Minor switch
{
0 => "2000",
1 => "XP",
2 => "XP x64",
_ => null,
},
6 => windowsVersion.Minor switch
{
0 => "Vista",
1 => "7",
2 => "8",
3 => "8.1",
_ => null
},
10 => windowsVersion.Build switch
{
< 10240 => "10 TH1 Build " + windowsVersion.Build,
10240 => "10 1507",
< 10586 => "10 TH2 Build " + windowsVersion.Build,
10586 => "10 1511",
< 14393 => "10 RS1 Build " + windowsVersion.Build,
14393 => "10 1607",
< 15063 => "10 RS2 Build " + windowsVersion.Build,
15063 => "10 1703",
< 16299 => "10 RS3 Build " + windowsVersion.Build,
16299 => "10 1709",
< 17134 => "10 RS4 Build " + windowsVersion.Build,
17134 => "10 1803",
< 17763 => "10 RS5 Build " + windowsVersion.Build,
17763 => "10 1809",
< 18362 => "10 19H1 Build " + windowsVersion.Build,
18362 => "10 1903",
18363 => "10 1909",
< 19041 => "10 20H1 Build " + windowsVersion.Build,
19041 => "10 2004",
19042 => "10 20H2",
19043 => "10 21H1",
19044 => "10 21H2",
19045 => "10 22H2", // deprecated
< 21390 => "10 Dev Build " + windowsVersion.Build,
21390 => "10 21H2 Insider",
< 22000 => "11 Internal Build " + windowsVersion.Build,
22000 => "11 21H2",
< 22621 => "11 22H2 Insider Build " + windowsVersion.Build,
22621 => "11 22H2", // deprecated
22631 => "11 23H2", // deprecated
< 23000 => "11 Beta Build " + windowsVersion.Build, // 22k series
< 24000 => "11 Dev Build " + windowsVersion.Build, // 23k series
< 25000 => "11 ??? Build " + windowsVersion.Build,
< 26052 => "11 Canary Build " + windowsVersion.Build, // 25k series
26100 => "11 24H2",
< 26120 => "11 Dev/Canary Build " + windowsVersion.Build, // dev/canary merge branch before 24H2
26120 => "11 24H2 Beta Build " + windowsVersion.Build,
26200 => "11 25H2",
26220 => "11 25H2 Beta Build " + windowsVersion.Build,
26300 => "11 25H2 Dev Build " + windowsVersion.Build,
<28000 => "11 Canary Build " + windowsVersion.Build,
<29000 => "11 26H1 Canary Build " + windowsVersion.Build,
_ => "11 ??? Build " + windowsVersion.Build,
},
_ => null,
};
private static string? GetLinuxVersion(string? osType, string? release, string version)
{
if (string.IsNullOrEmpty(release))
return null;
var kernelVersion = release;
if (LinuxKernelVersion().Match(release) is {Success: true} m)
kernelVersion = m.Groups["version"].Value;
if (version.Contains("Ubuntu", StringComparison.OrdinalIgnoreCase))
return "Ubuntu " + kernelVersion;
if (version.Contains("Debian", StringComparison.OrdinalIgnoreCase))
return "Debian " + kernelVersion;
if (release.Contains("-MANJARO", StringComparison.OrdinalIgnoreCase))
return "Manjaro " + kernelVersion;
if (release.Contains("-ARCH", StringComparison.OrdinalIgnoreCase))
return "Arch " + kernelVersion;
if (release.Contains("-gentoo", StringComparison.OrdinalIgnoreCase))
return "Gentoo " + kernelVersion;
if (release.Contains("-valve", StringComparison.OrdinalIgnoreCase))
return "SteamOS " + kernelVersion;
if (release.Contains("-artix", StringComparison.OrdinalIgnoreCase))
return "Artix " + kernelVersion;
if (release.Contains("-clear", StringComparison.OrdinalIgnoreCase))
return "Clear Linux " + kernelVersion;
if (release.Contains("-Unraid", StringComparison.OrdinalIgnoreCase))
return "Unraid " + kernelVersion;
if (release.Contains("-cachyos", StringComparison.OrdinalIgnoreCase))
return "CachyOS " + kernelVersion;
if (release.Contains("-chos1", StringComparison.OrdinalIgnoreCase)
|| release.Contains("-chimeraos", StringComparison.OrdinalIgnoreCase))
return "ChimeraOS " + kernelVersion;
if (release.Contains(".fc"))
{
var ver = release.Split('.').FirstOrDefault(p => p.StartsWith("fc"))?[2..];
return "Fedora " + ver;
}
return $"{osType} {kernelVersion}";
}
private static string GetMacOsVersion(Version macVer)
=> macVer.Major switch
{
10 => macVer.Minor switch
{
0 => "Mac OS X Cheetah",
1 => "Mac OS X Puma",
2 => "Mac OS X Jaguar",
3 => "Mac OS X Panther",
4 => "Mac OS X Tiger",
5 => "Mac OS X Leopard",
6 => "Mac OS X Snow Leopard",
7 => "OS X Lion",
8 => "OS X Mountain Lion",
9 => "OS X Mavericks",
10 => "OS X Yosemite",
11 => "OS X El Capitan",
12 => "macOS Sierra",
13 => "macOS High Sierra",
14 => "macOS Mojave",
15 => "macOS Catalina",
_ => "Unknown Apple OS",
},
11 => "macOS Big Sur",
12 => "macOS Monterey",
13 => "macOS Ventura",
14 => "macOS Sonoma",
15 => "macOS Sequoia",
26 => "macOS Tahoe",
_ => "Unknown Apple OS",
};
internal static bool IsAmd(string gpuInfo)
=> gpuInfo.Contains("Radeon", StringComparison.OrdinalIgnoreCase)
|| gpuInfo.Contains("AMD", StringComparison.OrdinalIgnoreCase)
|| gpuInfo.Contains("ATI ", StringComparison.OrdinalIgnoreCase);
internal static bool IsNvidia(string gpuInfo)
=> gpuInfo.Contains("GeForce", StringComparison.OrdinalIgnoreCase)
|| gpuInfo.Contains("nVidia", StringComparison.OrdinalIgnoreCase)
|| gpuInfo.Contains("Quadro", StringComparison.OrdinalIgnoreCase)
|| gpuInfo.Contains("GTX", StringComparison.OrdinalIgnoreCase)
|| gpuInfo.Contains("RTX", StringComparison.OrdinalIgnoreCase);
internal static bool IsNouveau(string gpuInfo)
=> gpuInfo.Contains("NVK ");
internal static bool IsIntel(string gpuInfo)
=> gpuInfo.Contains("Intel", StringComparison.OrdinalIgnoreCase);
private static string GetTimeFormat(long microseconds)
=> microseconds switch
{
<1000 => $"{microseconds} µs",
<1_000_000 => $"{microseconds / 1000.0:0.##} ms",
_ => $"{microseconds / 1_000_000.0:0.##} s"
};
private static List<string> SortLines(List<string> notes, DiscordEmoji? piracyEmoji = null)
{
if (notes.Count < 2)
return notes;
var priorityList = new List<string>(EmojiPriority);
if (piracyEmoji is not null)
priorityList.Insert(0, piracyEmoji.ToString());
return notes
.Select(s =>
{
var prioritySymbol = s.Split(PrioritySeparator, 2)[0].TrimEnd('\ufe0f');
var priority = priorityList.IndexOf(prioritySymbol);
return new
{
priority = priority == -1 ? 69 : priority,
line = s
};
})
.OrderBy(i => i.priority)
.Select(i => i.line)
.ToList();
}
private static Dictionary<string, int> GetPatches(UniqueList<string> patchList, in bool onlyApplied)
{
if (!patchList.Any())
return new();
var result = new Dictionary<string, int>(patchList.Count);
foreach (var patch in patchList)
{
var match = ProgramHashPatch().Match(patch);
if (match.Success)
{
_ = int.TryParse(match.Groups["patch_count"].Value, out var pCount);
if (!onlyApplied || pCount > 0)
result[match.Groups["hash"].Value] = pCount;
}
}
return result;
}
internal static async ValueTask<DiscordEmbedBuilder> AddAuthorAsync(this DiscordEmbedBuilder builder, DiscordClient client, DiscordMessage? message, ISource source, LogParseState? state = null)
{
if (message == null || state?.Error == LogParseState.ErrorCode.PiracyDetected)
return builder;
var author = message.Author;
var member = await client.GetMemberAsync(message.Channel?.Guild, author).ConfigureAwait(false);
string msg;
if (member == null)
msg = $"Log from {author.Username.Sanitize()} | {author.Id}\n";
else
msg = $"Log from {member.DisplayName.Sanitize()} | {member.Id}\n";
msg += " | " + source.SourceType;
if (state?.ReadBytes > 0 && source.LogFileSize is >0 and <2L*1024*1024*1024 && state.ReadBytes <= source.LogFileSize)
msg += $" | Parsed {state.ReadBytes * 100.0 / source.LogFileSize:0.##}%";
else if (source.SourceFilePosition > 0 && source.SourceFileSize > 0 && source.SourceFilePosition <= source.SourceFileSize)
msg += $" | Read {source.SourceFilePosition * 100.0 / source.SourceFileSize:0.##}%";
else if (state?.ReadBytes > 0)
msg += $" | Parsed {state.ReadBytes} byte{(state.ReadBytes == 1 ? "" : "s")}";
else if (source.LogFileSize > 0)
msg += $" | {source.LogFileSize} byte{(source.LogFileSize == 1 ? "" : "s")}";
#if DEBUG
if (state?.ParsingTime.TotalMilliseconds > 0)
msg += $" | {state.ParsingTime.TotalSeconds:0.###}s";
msg += " | Test Bot Instance";
#endif
builder.WithFooter(msg);
return builder;
}
private static (int numerator, int denominator) Reduce(int numerator, int denominator)
{
var gcd = Gcd(numerator, denominator);
return (numerator / gcd, denominator / gcd);
}
private static int Gcd(int a, int b)
{
while (a != b)
{
if (a % b == 0)
return b;
if (b % a == 0)
return a;
if (a > b)
a -= b;
if (b > a)
b -= a;
}
return a;
}
private static List<(string fatalError, int count, double similarity)> GroupSimilar(UniqueList<string> fatalErrors)
{
var result = new List<(string fatalError, int count, double similarity)>(fatalErrors.Count);
if (fatalErrors.Count == 0)
return result;
result.Add((fatalErrors[0], 1, 1.0));
if (fatalErrors.Count < 2)
return result;
foreach (var error in fatalErrors[1..])
{
var idx = -1;
var similarity = 0.0;
for (var i = 0; i < result.Count; i++)
{
similarity = result[i].fatalError.GetFuzzyCoefficientCached(error);
if (similarity > 0.75) // spu worker X gives .9763 confidence, ppu thread XXXX gives about .9525
{
idx = i;
break;
}
}
if (idx == -1)
result.Add((error, 1, 1.0));
else
{
var (e, c, s) = result[idx];
result[idx] = (e, c + 1, Math.Min(s, similarity));
}
}
return result;
}
private static bool TryGetRpcs3Version(NameValueCollection items, out Version? version)
{
version = null;
return items["build_branch"] is "HEAD" or "master" && Version.TryParse(items["build_full_version"], out version);
}
}