Added more warnings in log reader (#17)

* Added warnings:

- File permissions fix
- File not found fix
- Missing services fix
- Graphics Backend threading warning
- Software memory manager warning severity upgraded

* Small fixes:
- Use file size to track duplicate files
- Use logging module for exception warning

* Fix log analysis not showing on no game boot

* Formatting for clarity on mobile, minor rewording

* Add graphics backend to visible output

* Suggest Vulkan for AMD and Intel GPU

* Reword pr-testing message

* Remove anisotropic filtering warning

* Deduplicate mods information

* Nit: spelling error in comment

* Shorten log time message

* Increase bytes read to handle large amounts of DLC

* Add Texture Recompression to output

* Fix shadowed variables

* Recompression suggestion for Vulkan memory error

* Improve regex to deal with MB/MiB RAM measurement

* Rename Expand DRAM warning to match rewording

* Update LDN regex to detect new versions
This commit is contained in:
Mark 2022-11-26 09:34:00 +00:00 committed by TSR Berry
parent ade0917985
commit 367cc8fe9d
No known key found for this signature in database
GPG key ID: 52353C0A4CCA15E2

View file

@ -2,10 +2,11 @@ import logging
import re import re
import aiohttp import aiohttp
import config
from discord import Colour, Embed from discord import Colour, Embed
from discord.ext.commands import Cog from discord.ext.commands import Cog
import config
logging.basicConfig( logging.basicConfig(
format="%(asctime)s (%(levelname)s) %(message)s (Line %(lineno)d)", format="%(asctime)s (%(levelname)s) %(message)s (Line %(lineno)d)",
level=logging.INFO, level=logging.INFO,
@ -22,7 +23,7 @@ class LogFileReader(Cog):
async def download_file(self, log_url): async def download_file(self, log_url):
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
# Grabs first and last few bytes of log file to prevent abuse from large files # Grabs first and last few bytes of log file to prevent abuse from large files
headers = {"Range": "bytes=0-35000, -6000"} headers = {"Range": "bytes=0-60000, -6000"}
async with session.get(log_url, headers=headers) as response: async with session.get(log_url, headers=headers) as response:
return await response.text("UTF-8") return await response.text("UTF-8")
@ -47,9 +48,11 @@ class LogFileReader(Cog):
}, },
"settings": { "settings": {
"audio_backend": "Unknown", "audio_backend": "Unknown",
"backend_threading": "Unknown",
"docked": "Unknown", "docked": "Unknown",
"expand_ram": "Unknown", "expand_ram": "Unknown",
"fs_integrity": "Unknown", "fs_integrity": "Unknown",
"graphics_backend": "Unknown",
"ignore_missing_services": "Unknown", "ignore_missing_services": "Unknown",
"memory_manager": "Unknown", "memory_manager": "Unknown",
"pptc": "Unknown", "pptc": "Unknown",
@ -58,6 +61,7 @@ class LogFileReader(Cog):
"resolution_scale": "Unknown", "resolution_scale": "Unknown",
"anisotropic_filtering": "Unknown", "anisotropic_filtering": "Unknown",
"aspect_ratio": "Unknown", "aspect_ratio": "Unknown",
"texture_recompression": "Unknown",
}, },
} }
attached_log = message.attachments[0] attached_log = message.attachments[0]
@ -109,7 +113,6 @@ class LogFileReader(Cog):
continue continue
def get_ryujinx_info(log_file=log_file): def get_ryujinx_info(log_file=log_file):
# try:
for setting in self.embed["emu_info"]: for setting in self.embed["emu_info"]:
try: try:
if setting == "ryu_version": if setting == "ryu_version":
@ -154,17 +157,19 @@ class LogFileReader(Cog):
( (
f"**Audio Backend:** `{self.embed['settings']['audio_backend']}`", f"**Audio Backend:** `{self.embed['settings']['audio_backend']}`",
f"**Console Mode:** `{self.embed['settings']['docked']}`", f"**Console Mode:** `{self.embed['settings']['docked']}`",
f"**PPTC cache:** `{self.embed['settings']['pptc']}`", f"**PPTC Cache:** `{self.embed['settings']['pptc']}`",
f"**Shader cache:** `{self.embed['settings']['shader_cache']}`", f"**Shader Cache:** `{self.embed['settings']['shader_cache']}`",
f"**V-Sync:** `{self.embed['settings']['vsync']}`", f"**V-Sync:** `{self.embed['settings']['vsync']}`",
) )
) )
graphics_settings_info = "\n".join( graphics_settings_info = "\n".join(
( (
f"**Graphics Backend:** `{self.embed['settings']['graphics_backend']}`",
f"**Resolution:** `{self.embed['settings']['resolution_scale']}`", f"**Resolution:** `{self.embed['settings']['resolution_scale']}`",
f"**Anisotropic Filtering:** `{self.embed['settings']['anisotropic_filtering']}`", f"**Anisotropic Filtering:** `{self.embed['settings']['anisotropic_filtering']}`",
f"**Aspect Ratio:** `{self.embed['settings']['aspect_ratio']}`", f"**Aspect Ratio:** `{self.embed['settings']['aspect_ratio']}`",
f"**Texture Recompression:** `{self.embed['settings']['texture_recompression']}`",
) )
) )
@ -199,11 +204,11 @@ class LogFileReader(Cog):
log_embed.add_field( log_embed.add_field(
name="Empty Log", name="Empty Log",
value=f"""The log file appears to be empty. To get a proper log, follow these steps: value=f"""The log file appears to be empty. To get a proper log, follow these steps:
1) In Logging settings, ensure `Enable Logging to File` is checked. 1) In Logging settings, ensure `Enable Logging to File` is checked.
2) Ensure the following default logs are enabled: `Info`, `Warning`, `Error`, `Guest` and `Stub`. 2) Ensure the following default logs are enabled: `Info`, `Warning`, `Error`, `Guest` and `Stub`.
3) Start a game up. 3) Start a game up.
4) Play until your issue occurs. 4) Play until your issue occurs.
5) Upload the latest log file.""", 5) Upload the latest log file which is larger than 2KB.""",
inline=False, inline=False,
) )
if ( if (
@ -218,11 +223,11 @@ class LogFileReader(Cog):
log_embed.add_field( log_embed.add_field(
name="No Game Boot Detected", name="No Game Boot Detected",
value=f"""No game boot has been detected in log file. To get a proper log, follow these steps: value=f"""No game boot has been detected in log file. To get a proper log, follow these steps:
1) In Logging settings, ensure `Enable Logging to File` is checked. 1) In Logging settings, ensure `Enable Logging to File` is checked.
2) Ensure the following default logs are enabled: `Info`, `Warning`, `Error`, `Guest` and `Stub`. 2) Ensure the following default logs are enabled: `Info`, `Warning`, `Error`, `Guest` and `Stub`.
3) Start a game up. 3) Start a game up.
4) Play until your issue occurs. 4) Play until your issue occurs.
5) Upload the latest log file.""", 5) Upload the latest log file which is larger than 2KB.""",
inline=False, inline=False,
) )
else: else:
@ -235,15 +240,15 @@ class LogFileReader(Cog):
name="Mods", value=self.embed["game_info"]["mods"], inline=False name="Mods", value=self.embed["game_info"]["mods"], inline=False
) )
try: try:
notes_value = "\n".join(game_notes) notes_value = "\n".join(game_notes)
except TypeError: except TypeError:
notes_value = "Nothing to note" notes_value = "Nothing to note"
log_embed.add_field( log_embed.add_field(
name="Notes", name="Notes",
value=notes_value, value=notes_value,
inline=False, inline=False,
) )
return log_embed return log_embed
@ -296,6 +301,7 @@ class LogFileReader(Cog):
if name in [ if name in [
"pptc", "pptc",
"shader_cache", "shader_cache",
"texture_recompression",
"vsync", "vsync",
]: ]:
setting[ setting[
@ -307,14 +313,17 @@ class LogFileReader(Cog):
"anisotropic_filtering": "MaxAnisotropy", "anisotropic_filtering": "MaxAnisotropy",
"aspect_ratio": "AspectRatio", "aspect_ratio": "AspectRatio",
"audio_backend": "AudioBackend", "audio_backend": "AudioBackend",
"backend_threading": "BackendThreading",
"docked": "EnableDockedMode", "docked": "EnableDockedMode",
"expand_ram": "ExpandRam", "expand_ram": "ExpandRam",
"fs_integrity": "EnableFsIntegrityChecks", "fs_integrity": "EnableFsIntegrityChecks",
"graphics_backend": "GraphicsBackend",
"ignore_missing_services": "IgnoreMissingServices", "ignore_missing_services": "IgnoreMissingServices",
"memory_manager": "MemoryManagerMode", "memory_manager": "MemoryManagerMode",
"pptc": "EnablePtc", "pptc": "EnablePtc",
"resolution_scale": "ResScale", "resolution_scale": "ResScale",
"shader_cache": "EnableShaderCache", "shader_cache": "EnableShaderCache",
"texture_recompression": "EnableTextureRecompression",
"vsync": "EnableVsync", "vsync": "EnableVsync",
} }
try: try:
@ -322,7 +331,7 @@ class LogFileReader(Cog):
setting_name, setting_map[setting_name], log_file=log_file setting_name, setting_map[setting_name], log_file=log_file
) )
except (AttributeError, IndexError) as error: except (AttributeError, IndexError) as error:
print( logging.info(
f"Settings exception: {setting_name}: {type(error).__name__}" f"Settings exception: {setting_name}: {type(error).__name__}"
) )
continue continue
@ -350,7 +359,7 @@ class LogFileReader(Cog):
return False return False
shader_cache_collision = error_search(["Cache collision found"]) shader_cache_collision = error_search(["Cache collision found"])
dump_hash_warning = error_search( dump_hash_error = error_search(
[ [
"ResultFsInvalidIvfcHash", "ResultFsInvalidIvfcHash",
"ResultFsNonRealDataVerificationFailed", "ResultFsNonRealDataVerificationFailed",
@ -368,6 +377,12 @@ class LogFileReader(Cog):
["ResultFsPermissionDenied"] ["ResultFsPermissionDenied"]
) )
file_not_found_error = error_search(["ResultFsTargetNotFound"]) file_not_found_error = error_search(["ResultFsTargetNotFound"])
missing_services_error = error_search(
["ServiceNotImplementedException"]
)
vulkan_out_of_memory_error = error_search(
["ErrorOutOfDeviceMemory"]
)
last_errors = "\n".join( last_errors = "\n".join(
errors[-1][:2] if "|E|" in errors[-1][0] else "" errors[-1][:2] if "|E|" in errors[-1][0] else ""
@ -377,23 +392,27 @@ class LogFileReader(Cog):
return ( return (
last_errors, last_errors,
shader_cache_collision, shader_cache_collision,
dump_hash_warning, dump_hash_error,
shader_cache_corruption, shader_cache_corruption,
update_keys_error, update_keys_error,
file_permissions_error, file_permissions_error,
file_not_found_error, file_not_found_error,
missing_services_error,
vulkan_out_of_memory_error,
) )
# Finds the lastest error denoted by |E| in the log and its first line # Finds the latest error denoted by |E| in the log and its first line
# Also warns of common issues # Also warns of common issues
( (
last_error_snippet, last_error_snippet,
shader_cache_warn, shader_cache_warn,
dump_hash_warning, dump_hash_warning,
shader_cache_corruption_warn, shader_cache_corruption_warn,
update_keys_error, update_keys_warn,
file_permissions_error, file_permissions_warn,
file_not_found_error, file_not_found_warn,
missing_services_warn,
vulkan_out_of_memory_warn,
) = analyse_error_message() ) = analyse_error_message()
if last_error_snippet: if last_error_snippet:
self.embed["game_info"]["errors"] = f"```{last_error_snippet}```" self.embed["game_info"]["errors"] = f"```{last_error_snippet}```"
@ -427,24 +446,38 @@ class LogFileReader(Cog):
dump_hash_warning = f"⚠️ Dump error detected. Investigate possible bad game/firmware dump issues" dump_hash_warning = f"⚠️ Dump error detected. Investigate possible bad game/firmware dump issues"
self.embed["game_info"]["notes"].append(dump_hash_warning) self.embed["game_info"]["notes"].append(dump_hash_warning)
if update_keys_error: if update_keys_warn:
update_keys_error = ( update_keys_warn = (
f"⚠️ Keys or firmware out of date, consider updating them" f"⚠️ Keys or firmware out of date, consider updating them"
) )
self.embed["game_info"]["notes"].append(update_keys_error) self.embed["game_info"]["notes"].append(update_keys_warn)
if file_permissions_error: if file_permissions_warn:
file_permissions_error = f"⚠️ File permission error. Consider deleting save directory and allowing Ryujinx to make a new one" file_permissions_warn = f"⚠️ File permission error. Consider deleting save directory and allowing Ryujinx to make a new one"
self.embed["game_info"]["notes"].append(file_permissions_error) self.embed["game_info"]["notes"].append(file_permissions_warn)
if file_not_found_error: if file_not_found_warn:
file_not_found_error = f"⚠️ Save not found error. Consider starting game without a save file or using a new save file" file_not_found_warn = f"⚠️ Save not found error. Consider starting game without a save file or using a new save file"
self.embed["game_info"]["notes"].append(file_not_found_error) self.embed["game_info"]["notes"].append(file_not_found_warn)
if (
missing_services_warn
and self.embed["settings"]["ignore_missing_services"] == "False"
):
missing_services_warn = f"⚠️ Consider enabling `Ignore Missing Services` in Ryujinx settings"
self.embed["game_info"]["notes"].append(missing_services_warn)
if (
vulkan_out_of_memory_warn
and self.embed["settings"]["texture_recompression"] == "Disabled"
):
vulkan_out_of_memory_warn = f"⚠️ Consider enabling `Texture Recompression` in Ryujinx settings"
self.embed["game_info"]["notes"].append(vulkan_out_of_memory_warn)
timestamp_regex = re.compile(r"\d{2}:\d{2}:\d{2}\.\d{3}") timestamp_regex = re.compile(r"\d{2}:\d{2}:\d{2}\.\d{3}")
latest_timestamp = re.findall(timestamp_regex, log_file)[-1] latest_timestamp = re.findall(timestamp_regex, log_file)[-1]
if latest_timestamp: if latest_timestamp:
timestamp_message = f" Time elapsed in log: `{latest_timestamp}`" timestamp_message = f" Time elapsed: `{latest_timestamp}`"
self.embed["game_info"]["notes"].append(timestamp_message) self.embed["game_info"]["notes"].append(timestamp_message)
def mods_information(log_file=log_file): def mods_information(log_file=log_file):
@ -458,6 +491,8 @@ class LogFileReader(Cog):
f" {i['mod']} ({'ExeFS' if i['status'] == '[E]' else 'RomFS'})" f" {i['mod']} ({'ExeFS' if i['status'] == '[E]' else 'RomFS'})"
for i in mods for i in mods
] ]
# Remove duplicated mods from output
mods_status = list(dict.fromkeys(mods_status))
return mods_status return mods_status
game_mods = mods_information() game_mods = mods_information()
@ -484,7 +519,9 @@ class LogFileReader(Cog):
self.embed["game_info"]["notes"].append(input_string) self.embed["game_info"]["notes"].append(input_string)
try: try:
ram_available_regex = re.compile(r"Available\s(\d+)(?=\sMB)") ram_available_regex = re.compile(
r"Application\sPrint:\sRAM:(?:.*Available\s)(\d+)"
)
ram_available = re.search(ram_available_regex, log_file)[1] ram_available = re.search(ram_available_regex, log_file)[1]
if int(ram_available) < 8000: if int(ram_available) < 8000:
ram_warning = ( ram_warning = (
@ -498,13 +535,17 @@ class LogFileReader(Cog):
mac_os_warning = "**❌ macOS is currently unsupported**" mac_os_warning = "**❌ macOS is currently unsupported**"
self.embed["game_info"]["notes"].append(mac_os_warning) self.embed["game_info"]["notes"].append(mac_os_warning)
if "Intel" in self.embed["hardware_info"]["gpu"]: if (
if ( "Windows" in self.embed["hardware_info"]["os"]
"Darwin" in self.embed["hardware_info"]["os"] and self.embed["settings"]["graphics_backend"] != "Vulkan"
or "Windows" in self.embed["hardware_info"]["os"] ):
): if "Intel" in self.embed["hardware_info"]["gpu"]:
intel_gpu_warning = "**⚠️ Intel iGPUs are known to have driver issues, consider using a discrete GPU**" intel_gpu_warning = "**⚠️ Intel iGPU users should consider using Vulkan graphics backend**"
self.embed["game_info"]["notes"].append(intel_gpu_warning) self.embed["game_info"]["notes"].append(intel_gpu_warning)
if "AMD" in self.embed["hardware_info"]["gpu"]:
amd_gpu_warning = "**⚠️ AMD GPU users should consider using Vulkan graphics backend**"
self.embed["game_info"]["notes"].append(amd_gpu_warning)
try: try:
default_logs = ["Info", "Warning", "Error", "Guest", "Stub"] default_logs = ["Info", "Warning", "Error", "Guest", "Stub"]
user_logs = ( user_logs = (
@ -532,15 +573,6 @@ class LogFileReader(Cog):
firmware_warning = f"**❌ Nintendo Switch firmware not found**" firmware_warning = f"**❌ Nintendo Switch firmware not found**"
self.embed["game_info"]["notes"].append(firmware_warning) self.embed["game_info"]["notes"].append(firmware_warning)
if self.embed["settings"]["anisotropic_filtering"] not in [
"Auto",
"Unknown",
]:
anisotropic_filtering_warning = "⚠️ Anisotropic filtering not set to `Auto` can cause graphical issues"
self.embed["game_info"]["notes"].append(
anisotropic_filtering_warning
)
if self.embed["settings"]["audio_backend"] == "Dummy": if self.embed["settings"]["audio_backend"] == "Dummy":
dummy_warning = ( dummy_warning = (
f"⚠️ Dummy audio backend, consider changing to SDL2 or OpenAL" f"⚠️ Dummy audio backend, consider changing to SDL2 or OpenAL"
@ -556,11 +588,11 @@ class LogFileReader(Cog):
self.embed["game_info"]["notes"].append(shader_warning) self.embed["game_info"]["notes"].append(shader_warning)
if self.embed["settings"]["expand_ram"] == "True": if self.embed["settings"]["expand_ram"] == "True":
expand_ram_warning = f"⚠️ `Expand DRAM size to 6GB` should only be enabled for 4K mods" expand_ram_warning = f"⚠️ `Use alternative memory layout` should only be enabled for 4K mods"
self.embed["game_info"]["notes"].append(expand_ram_warning) self.embed["game_info"]["notes"].append(expand_ram_warning)
if self.embed["settings"]["memory_manager"] == "SoftwarePageTable": if self.embed["settings"]["memory_manager"] == "SoftwarePageTable":
software_memory_manager_warning = "⚠️ `Software` setting in Memory Manager Mode will give slower performance than the default setting of `Host unchecked`" software_memory_manager_warning = "🔴 **`Software` setting in Memory Manager Mode will give slower performance than the default setting of `Host unchecked`**"
self.embed["game_info"]["notes"].append( self.embed["game_info"]["notes"].append(
software_memory_manager_warning software_memory_manager_warning
) )
@ -579,10 +611,16 @@ class LogFileReader(Cog):
fs_integrity_warning = f"⚠️ Disabling file integrity checks may cause corrupted dumps to not be detected" fs_integrity_warning = f"⚠️ Disabling file integrity checks may cause corrupted dumps to not be detected"
self.embed["game_info"]["notes"].append(fs_integrity_warning) self.embed["game_info"]["notes"].append(fs_integrity_warning)
if self.embed["settings"]["backend_threading"] == "Off":
backend_threading_warning = (
f"🔴 **Graphics Backend Multithreading should be set to `Auto`**"
)
self.embed["game_info"]["notes"].append(backend_threading_warning)
mainline_version = re.compile(r"^\d\.\d\.\d+$") mainline_version = re.compile(r"^\d\.\d\.\d+$")
old_mainline_version = re.compile(r"^\d\.\d\.(\d){4}$") old_mainline_version = re.compile(r"^\d\.\d\.(\d){4}$")
pr_version = re.compile(r"^\d\.\d\.\d\+([a-f]|\d){7}$") pr_version = re.compile(r"^\d\.\d\.\d\+([a-f]|\d){7}$")
ldn_version = re.compile(r"^\d\.\d\.\d\-ldn\d\.\d$") ldn_version = re.compile(r"^\d\.\d\.\d\-ldn\d+\.\d+(?:\.\d+|$)")
if ( if (
message.channel.id == config.bot_log_allowed_channels["support"] message.channel.id == config.bot_log_allowed_channels["support"]
@ -592,7 +630,7 @@ class LogFileReader(Cog):
== config.bot_log_allowed_channels["linux-master-race"] == config.bot_log_allowed_channels["linux-master-race"]
): ):
if re.match(pr_version, self.embed["emu_info"]["ryu_version"]): if re.match(pr_version, self.embed["emu_info"]["ryu_version"]):
pr_version_warning = f"**⚠️ PR build logs should be posted in <#{config.bot_log_allowed_channels['pr-testing']}>**" pr_version_warning = f"**⚠️ PR build logs should be posted in <#{config.bot_log_allowed_channels['pr-testing']}> if reporting bugs or tests**"
self.embed["game_info"]["notes"].append(pr_version_warning) self.embed["game_info"]["notes"].append(pr_version_warning)
if re.match( if re.match(
@ -652,6 +690,7 @@ class LogFileReader(Cog):
author_id = message.author.id author_id = message.author.id
author_mention = message.author.mention author_mention = message.author.mention
filename = message.attachments[0].filename filename = message.attachments[0].filename
filesize = message.attachments[0].size
# Any message over 2000 chars is uploaded as message.txt, so this is accounted for # Any message over 2000 chars is uploaded as message.txt, so this is accounted for
ryujinx_log_file_regex = re.compile(r"^Ryujinx_.*\.log|message\.txt$") ryujinx_log_file_regex = re.compile(r"^Ryujinx_.*\.log|message\.txt$")
log_file = re.compile(r"^.*\.log|.*\.txt$") log_file = re.compile(r"^.*\.log|.*\.txt$")
@ -676,6 +715,7 @@ class LogFileReader(Cog):
self.uploaded_log_info.append( self.uploaded_log_info.append(
{ {
"filename": filename, "filename": filename,
"file_size": filesize,
"link": log_file_link, "link": log_file_link,
"author": author_id, "author": author_id,
} }
@ -705,6 +745,7 @@ class LogFileReader(Cog):
elem elem
for elem in self.uploaded_log_info for elem in self.uploaded_log_info
if elem["filename"] == filename if elem["filename"] == filename
and elem["file_size"] == filesize
and elem["author"] == author_id and elem["author"] == author_id
), ),
None, None,