{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "np93's blog",
  "description": "just a collection of thoughts~",
  "home_page_url": "https://chronovore.dev/posts",
  "feed_url": "https://chronovore.dev/posts/feed.json",
  "authors": [
    {
      "name": "Neptuwunium"
    }
  ],
  "generator": "pumpkin",
  "language": "en",
  "items": [
    {
      "title": "some notes about gaming via wine",
      "url": "https://chronovore.dev/posts/2025-06-24-0539P-wine-gaming-notes.html",
      "id": "tag:chronovore.dev,posts:2025-06-24-0539P-wine-gaming-notes",
      "summary": "it has become quite a bit, huh?",
      "date_published": "2024-06-21T23:06:00+00:00",
      "date_modified": "2025-09-16T18:05:00+00:00",
      "content_text": "I recently migrated to using Linux full time.\nYou need to not look far to find an ocean of reasons why Windows has been a bit miserable.\n\nThis post mainly serves as a logbook for fixes and workarounds for making games (and some applications) work on Linux (and sometimes macOS.)\n\n**Note:** This document has a lot of terminal commands and may be unsuitable for screen readers.\n\n[TOC]\n\n## Extreme stuttering after 30-60 minutes on Steam\n\n**Note:** This is apparently fixed in the Steam Client Beta as of June 2nd.\n\nThis is apparently due to the recent Steam Game Overlay update which added Game Recording.\n\nYou can disable it by prefixing `LD_PRELOAD=\"\"` to your launch arguments:\n\n`LD_PRELOAD=\"\" %command%`\n\nMore info on the following issue reports:\n\n- [doitsujin/dxvk#4436](https://github.com/doitsujin/dxvk/issues/4436#issuecomment-2466646597)\n- [ValveSoftware/steam-for-linux#11446](https://github.com/ValveSoftware/steam-for-linux/issues/11446)\n\nIf you want to load vulkan layers you must add `VK_LOADER_LAYERS_ENABLE` to your launch arguments:\n\n`VK_LOADER_LAYERS_ENABLE=\"LAYER NAMES\" %command%`\n\nReplace `LAYER NAMES` with the layers you wish to load, separated with a comma.\n\nKnown Vulkan layer names:\n\n- MangoHud: `VK_LAYER_MANGOHUD_overlay_x86_64`\n- OBS VkCapture: `VK_LAYER_OBS_vkcapture_64`\n- RenderDoc: `VK_LAYER_RENDERDOC_Capture`\n\nYou can view layers present on your system by running `vulkaninfo --summary` (present in the `vulkan-tools` package available in every distro worth using.)\n\n### Alternate Solution\n\nAccording to [this comment](https://github.com/ValveSoftware/steam-for-linux/issues/11446#issuecomment-2558605371) on [ValveSoftware/steam-for-linux#11446](https://github.com/ValveSoftware/steam-for-linux/issues/11446), disabling GPU acceleration for web views may also help performance related issues.\n\nYou disable GPU Web View Acceleration in the Steam settings.\n\n- Navigate to to \"Interface\" tab\n- Uncheck \"Enable GPU accelerated rendering in Web Views\"\n- Restart Steam\n\n## SDL \"Failed to Intialize Dependencies\" error\n\n(Also applies to EAC, since that uses SDL.)\n\nThis is likely due to a `SDL_VIDEODRIVER` and/or `SDL_VIDEO_DRIVER` environment variable being present.\n\nTry using the following launch arguments:\n\n`env --unset=SDL_VIDEODRIVER --unset=SDL_VIDEO_DRIVER %command%`\n\nor\n\n`env SDL_VIDEODRIVER=windows SDL_VIDEO_DRIVER=windows %command%`\n\nAlternatively, set the variable to an empty string in whatever launch manager you're using (Heroic, Lutris).\n\nThe EAC splash also sometimes fails to load if you are using the proprietary AMD drivers (i.e. `vk_pro %command%`.)\n\n## Enabling EAC on Wine outside of Proton\n\n***CAVEAT EMPTOR: I do not know if this works long term or if EAC will inevitably cause bans, if possible always prefer to do it via Proton and Steam. This is mostly intended as a guide to help prevent double purchases.***\n\nHere be dragons.\n\nThis is assuming the game uses EAC v2 (not v1, if there is an EasyAntiCheat.dll in the game install folder then it's using v1.)\n\nProton has a few workarounds to load the correct EAC libraries for it to work ([this is what the `PROTON_EAC_RUNTIME` environment variable is for](https://github.com/ValveSoftware/wine/blob/c8391877a485307cb67fecf963af6b855ed39b4b/dlls/ntdll/unix/loader.c#L374)).\n\nYou will need to download the Proton EasyAntiCheat Runtime from Steam. I do not know if these libraries depend on Steam or not.\n\nAssuming the Proton runtime exists at: `/home/user/.steam/steam/steamapps/common/Proton EasyAntiCheat Runtime`, and the program you are trying to start is called `nya.exe`:\n\n- For ProtonGE and wine with Proton patches (i.e. wine-tkg), you can simply set the `PROTON_EAC_RUNTIME` environment variable (ProtonGE has a helper that does this automatically.)\n\n`env PROTON_EAC_RUNTIME=\"/home/user/.steam/steam/steamapps/common/Proton EasyAntiCheat Runtime\" proton nya.exe`\n\n- For regular vanilla/staging Wine you can replicate the same behavior as that loader workaround:\n\n```sh\nexport PROTON_EAC_RUNTIME=\"/home/user/.steam/steam/steamapps/common/Proton EasyAntiCheat Runtime\"\nexport WINEDLLPATH=\"${PROTON_EAC_RUNTIME}/v2/lib32/:${PROTON_EAC_RUNTIME}/v2/lib64/\"\nwine nya.exe\n```\n\n**Note:** The client certificate for EAC (`EasyAntiCheat/Certificates/client.bin`) probably needs to accept linux. I do not know the specifics behind this besides that when games enable Linux support this file tends to update so I assume it is related. If the Steam version works on Linux with EAC enabled, but not outside of Steam, then the client certificate likely mismatches and you should ask (somehow, big task) the developers to keep the client certificates in line between Steam and the off-Steam platform.\n\n## Deleting Shader Cache Files\n\nDelete the mesa cache directories, preferrably when mesa isn't running, i.e. terminal session rather than desktop session. **Be careful you do not delete a directory you're not supposed to!**\n\n- `$XDG_CACHE_HOME/mesa_shader_cache`\n- `$XDG_CACHE_HOME/mesa_shader_cache_db`\n- `$XDG_CACHE_HOME/mesa_shader_cache_sf`\n\nNote: XDG_CACHE_HOME defaults to `$HOME/.cache` on most systems.\n\nYou can control where the shader cache is with the `MESA_SHADER_CACHE_DIR` environment variable.\n\nNote: Proton adds `steamapps/shadercache` to the cache path, relative to the library directory. If shader pre-caching is enabled this will always be populated, but may have directories for games that are no longer installed. Check if a related appmanifest_{id}.acf file is present in the upper directory.\n\nYou may also need to delete one or two files that reside in the same location as the executable. Some or none may exist.\n\n- `vkd3d-proton.{ExecutableName}.cache`\n- `vkd3d-proton.{ExecutableName}.cache.write`\n- `{ExecutableName}.dxvk-cache`\n\nYou can control where the cache files are saved with the `DXVK_STATE_CACHE_PATH` and `VKD3D_SHADER_CACHE_PATH` environment variables, i.e. I have the following in my dotfiles.\n\n```sh\nexport VKD3D_SHADER_CACHE_PATH=\"$HOME/.cache/vkd3d\"\nexport DXVK_STATE_CACHE_PATH=\"$HOME/.cache/dxvk\"\n```\n\nNote: I personally use these environment variables, as steam will not delete these cache files which may slowly rot your storage medium with leftover cache files. Where these distinct directories can easily be cleaned.\n\n## Checking if EAC is linux-enabled\n\nMost EAC games ship with a Settings.json file, this tells the EAC runtime what deployment and product to use.\nThis information is all logged in AppData, but the settings file exists in the game installation. \nThis is usually next to the installer (`EasyAntiCheat/Settings.json`, or next to the game executable but usually in the `EasyAntiCheat` directory inside of the game directory)\n\nWe can use this to find out if EAC is enabled for a particular game.\n\n```sh\njq -r \\\n\t'@uri \"https://modules-cdn.eac-prod.on.epicgames.com/modules/\\(.productid)/\\(.deploymentid)/linux64\"' \\\n\tSettings.json\n```\n\nThis will print out a URL. If the url yields HTTP 403, EAC Linux is disabled. If it returns HTTP 200, it is enabled.\n\n## Games not capturing mouse cursor\n\nSome games don't play nice with mouse capture, an easy way to solve this is by changing the launch arguments to:\n\n`gamescope --force-grab-cursor -f -- %command%`\n\nYou may swap -f with -b for borderless windowed instead of fullscreen.\n\n## Windows-only Third-Party Mod Tooling on Steam\n\nYou can add the mod tools as a non-steam game, given it has a GUI.\n\nWhen you do, force it to use **the same compatability tools as the game** and set the launch arguments to:\n\n`env STEAM_COMPAT_DATA_PATH=\"~/.steam/root/steamapps/compatdata/489830\" %command%`\n\nYou may need to install more dependencies like .NET 4.8/6.0, etc via protontricks, select the game *not* the non-steam app when installing.\n\nChange `~/.steam/root/steamapps` to the SteamLibrary steamapps directory if installed in a steam library directory.\n\nChange 489830 to match the steam id of the mod tool the game is for, 489830 is Skyrim SE/AE.\nIt is the number after `/app/` in the Steam store link, alternatively look it up on SteamDB.\n\n## Gamescope\n\n### Gamescope exiting early due to short-lived launcher processes\n\nSome games launch via third party launchers that cause gamescope to exit before the wine device.\n\nThis may be solved by forcing the SDL backend.\n\n`gamescope --backend sdl -- %command%`\n\n### Gamescope resolution being fixed to the first window size under SDL\n\nGamescope under SDL has a hard time adjusting to resolution changes (i.e. if you're using it for launcher processes.), to solve this you must force the window to be fullscreen.\n\n`gamescope -w YOUR_RESOLUTION_HERE -h YOUR_RESOLUTION_HERE -r YOUR_REFRESH_RATE_HERE --backend sdl --force-windows-fullscreen -f -- %command%`\n\n(Special thanks to [Hollyrious](https://twitch.tv/hollyrious) for helping me diagnose this!)\n\n### Gamescope and Steam\n\nGamescope might break steam ingration or just exit, in that case add ` -e ` to the end of your command. Also ensure that ` -- ` (with spaces) exists ***before*** `%command%`\n\n## SteamVR / Monado\n\nSteamVR and VR in general on Linux is not good, however it is possible.\nYour experience will be extremely improved if you have a Valve Index.\nAny other headset is unsupported and SteamVR will assume it just to be an Index (or not detect it at all.)\nTo remedy this, you can use Monado.\n\nIf you run a system that does not have pkexec set up with a GUI (or at all,) you have to manually run setcap (as root)\n\n`setcap CAP_SYS_NICE=eip ~/.steam/steam/steamapps/common/SteamVR/bin/linux64/vrcompositor-launcher`\n\nIf you plan on running monado as-is outside of SteamVR, you have to do the same for Monado.\n\n`setcap CAP_SYS_NICE=eip /usr/bin/monado-service`\n\nYou may also have to use the vrmonitor directly in the command:\n\n`~/.steam/steam/steamapps/common/SteamVR/bin/vrmonitor.sh %command%`\n\n### AMDGPU\n\nAMD does not ship the required Vulkan extensions that Monado needs in the FOSS driver present in the Linux kernel. You need to use the proprietary AMDGPU Radeon drivers and launch SteamVR as follows:\n\n`vk_pro ~/.steam/steam/steamapps/common/SteamVR/bin/vrmonitor.sh %command%`\n\n### WMR\n\nWindows Mixed Reality is [deprecated by Microsoft, and support will be removed in November 2026](https://learn.microsoft.com/en-us/windows/whats-new/deprecated-features#deprecated-features).\nThis makes Monado on Linux the only way to still use the device.\n\n**However** getting it to work is non-trivial. WMR supports needs a hidden dependency, Basalt. [Specifically a fork of Basalt](https://gitlab.freedesktop.org/mateosss/basalt).\n\nMy command looks something like:\n\n`env VIT_SYSTEM_LIBRARY_PATH=/usr/lib64/libbasalt-monado.so PRESSURE_VESSEL_FILESYSTEMS_RW=$XDG_RUNTIME_DIR/monado_comp_ipc SLAM_CONFIG=/usr/share/basalt/msdmo.toml SLAM_SUBMIT_FROM_START=1 SLAM_UI=0 XRT_COMPOSITOR_COMPUTE=1 XRT_DEBUG_GUI=0 AEG_USE_DYNAMIC_RANGE=0 WMR_AUTOEXPOSURE=0 STEAMVR_EMULATE_INDEX_CONTROLLER=0 ~/.steam/steam/steamapps/common/SteamVR/bin/vrmonitor.sh %command%`\n\nKeep in mind you need to replace `%command%` with the AMDGPU section if applicable, and adjust the `VIT_SYSTEM_LIBRARY_PATH` and `SLAM_CONFIG` path.\n\n- `VIT_SYSTEM_LIBRARY_PATH` is the path the modified basalt exists.\n- `SLAM_CONFIG` tells monado where the Basalt SLAM config is. `msdmo` is the one I found to work the best for the HMD Odyssey+, however `msdmi` and `msdmg` also exist.\n- `PRESSURE_VESSEL_FILESYSTEMS_RW` instructs the steam pressure vessel to allow access to the monado IPC socket.\n- `SLAM_SUBMIT_FROM_START` submits tracking info from initialization.\n- `SLAM_UI` controls whether or not to show the debug SLAM ui.\n- `XRT_COMPOSITOR_COMPUTE` setting this to true resolves stuttering if framerate is below the native refresh rate.\n- `XRT_DEBUG_GUI` controls whether or not to show the debug OpenXR ui.\n- `AEG_USE_DYNAMIC_RANGE` and `WMR_AUTOEXPOSURE` control auto exposure, you should keep this disabled.\n- `STEAMVR_EMULATE_INDEX_CONTROLLER` tells monado if it should emulate a Valve Index controller.\n\n### \"Headset Off\" when calibrating\n\nInstruct Monado to emulate the Index Controlller. This might magically fix it. It did for me.\n\nFailing that follow the instructions laid out by the [Linux VR Adventures Wiki](https://lvra.gitlab.io/docs/steamvr/).\n\n## AMDGPU-Pro\n\n### Gamescope\n\nAMD's proprietary drivers does not implement an extension that Gamescope relies on, which results in Gamescope crashing (See [this issue on GameScope's repository.](https://github.com/ValveSoftware/gamescope/issues/1465))\n\nYou can avoid this by moving the `amd_pro_icd64.json` file (`/usr/share/vulkan/icd.d/amd_pro_icd64.json`) to a different path (i.e. `/usr/share/vulkan/icd.disabled/amd_pro_icd64.json`)\n\nAnd then modifying your `vk_pro` wrapper script to point to the new path. When needed, prefix `vk_pro` to whatever command needs proprietary AMD drivers.\n\nYou can find [vk_pro](https://gitweb.gentoo.org/repo/gentoo.git/tree/media-libs/amdgpu-pro-vulkan/files/vk_pro) and [vk_radv](https://gitweb.gentoo.org/repo/gentoo.git/tree/media-libs/amdgpu-pro-vulkan/files/vk_radv) wrapper scripts provided by Gentoo, but they likely are also provided by other distros.\n\nGamescope seems to ignore the `VK_DRIVER_FILES`, `VK_ICD_FILENAMES`, `VK_LOADER_DRIVERS_DISABLE`, and `VK_LOADER_DRIVERS_SELECT` environment variables.\n\n## Battle.net\n\n### BLZBNTBNA00000005 / Sleeping Battle.net Agent\n\nAlso applies to if Battle.net spams the \"broken installation please restart\" popup.\n\nThis is due to a security change in Battle.net which verifies if the calling process is signed by Blizzard and trusted. \nWine 9 and earlier seem to incorrectly implement the WinTrust/WinCrypt functions, however Wine 10 does exhibit proper behavior.\n\nTry switching to Wine 10 or Proton 10 and restarting the process.\n\n## Steam\n\n### CEF 0x3009 or 0x3008 Error\n\nValve Updated CEF, utilizing new rendering APIs which may break specifically in non-CW D3DMetal. You can revert back to an older version of Steam by doing the following (or following the guide [here](https://docs.getwhisky.app/steam.html#steam-encountered-an-unexpected-error-during-startup-0x3008), though use the link below instead):\n\n`wine64 Steam.exe -forcesteamupdate -forcepackagedownload -overridepackageurl https://raw.githubusercontent.com/SteamDatabase/SteamTracking/4be7a78d0330c53ce059fdb50d198009f5ddeb73/ClientManifest/ -exitsteam`\n\nThe package URL is from the SteamDB Steam Tracking repository, 4be7a78d0330c53ce059fdb50d198009f5ddeb73 is the commit for the Steam Update from the 28th of January.\nSteam signs and strongly validates the signatures of manifests so tampering is unlikely if not impossible without Valve private keys.\n\nThen **only** launch with these arguments:\n\n`wine64 steam.exe -noverifyfiles -nobootstrapupdate -skipinitialbootstrap -norepairfiles -overridepackageurl  https://raw.githubusercontent.com/SteamDatabase/SteamTracking/4be7a78d0330c53ce059fdb50d198009f5ddeb73/ClientManifest/`\n\nOtherwise Steam **WILL** update to latest again and you **WILL** have to run the first command again.\n\n### Corrupt Download error\n\nWhen using the above workaround, Steam is running a pre-ZStandard version. On the 11th of March, Steam updated it's compression system to use ZStandard. Some parts of downloads may use this new system.\nThis means that you will occassionally (with increasing frequency as time goes on and more parts use the new download compression) get a \"Corrupt Download\" error.\n\nIn these cases, when pinning Steam to a specific version as above, you can use [SteamCMD](https://developer.valvesoftware.com/wiki/SteamCMD#Downloading_SteamCMD) to download the game files instead.\nSteamCMD uses the same SteamClient APIs to download games, however because it is a command line program it does not rely on CEF (ergo doesn't crash.)\n\nYou can use it as follows:\n\n```sh\nsteamcmd.sh \\\n\t+@sSteamCmdForcePlatformType windows \\\n\t+force_install_dir \"~/.wine/drive_c/Program Files (x86)/Steam/\" \\\n\t+login YOUR_USERNAME_HERE \\\n\t+app_update APP_ID_HERE \\\n\t+quit\n```\n\t\nor via wine:\n\n```sh\nwine64 steamcmd.exe \\\n\t+force_install_dir \"C:\\\\Program Files (x86)\\\\Steam\\\\\" \\\n\t+login YOUR_USERNAME_HERE \\\n\t+app_update APP_ID_HERE \\\n\t+quit\n```\n\nreplacing `YOUR_USERNAME_HERE` with your steam username, and `APP_ID_HERE` with the steam app id of the game (the first number in the store page.).\n\nNote: if you use a different wine prefix, steamcmd must also run in the same prefix when via wine, or the path to force_install_dir in the native command should point to the correct prefix. \n\n## Game-Specific Fixes\n\n### Monster Hunter Wilds\n\n#### Monster Hunter World Save Bonuses\n\nIf the game can't seem to locate the save bonuses for having a valid Monster Hunter World save, you need to copy the saves to the correct prefix.\n\nThe save files are stored on the steam cloud, if you have played the game on the machine before they will be in `~/.steam/steam/userdata/[YOUR STEAM ID]/582010/`.\nIf this directory does not exist ensure that steam cloud saves are enabled on Monster Hunter World, download it and lauch it completely once. Steam should have downloaded the save files.\nYou must copy **that entire directory** to the prefix-appropriate userdata directory. This is located in the same storage medium as the game.\n\nThe related path is: `/path/to/installed/SteamLibrary/steamapps/compatdata/2246340/pfx/drive_c/Program Files (x86)/Steam/userdata/[YOUR STEAM ID]`\n\nSo for example, if your Steam ID is 1024, and you have installed it to a partition that is mounted on /mnt/games/ you would do the following in a command prompt:\n\n```sh\ncp -rfvn \"~/.steam/steam/userdata/1024/582010\" \"/mnt/games/SteamLibrary/steamapps/compatdata/2246340/pfx/drive_c/Program Files (x86)/Steam/userdata/1024/\"\n```\n\nThe next time Monster Hunter Wilds launches, you should get a prompt for the Monster Hunter World incentives.\n\n#### Shader compilation on every startup\n\nIt would appear that in some situations, the game will compile shaders every time it has been started.\n\nThe following environment variables are shown to remove or at least reduce the instances of this happening significantly:\n\n`MESA_DISK_CACHE_SINGLE_FILE=0 __GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1`\n\n### Horizon Zero Dawn: Remastered\n\nIf you crash upon start, and have multiple displays it's likely due ot mismatched display resolutions.\n\nThis can be solved by using gamescope:\n\n`gamescope -w YOUR_RESOLUTION_HERE -h YOUR_RESOLUTION_HERE -r YOUR_REFRESH_RATE_HERE --backend sdl --force-windows-fullscreen -f %command%`\n\nIf the game complains about \"no internet connection\" upon start: add\n\n`%command% -showlinkingqr`\n\nto the command options.\n\n### Bloodborne (via shadPS4 with patches)\n\nIf you load in to the game and the world geometry is missing/textures are horrid, it's likely because the GPU drivers lack a certain extension set. In my experience running shadPS4 with AMD's proprietary drivers (i.e. `vk_pro shadps4`) resolves this.\n\nIf you start the game to a black screen follow the steps [outlined in this github issue](https://github.com/shadps4-emu56.6%\n\ufeff/shadPS4/issues/1409#issuecomment-2423382148).\n\nIf you *still* get a black screen, delete the shadPS4 directory in .local/share/shadPS4 and **launch the Qt6 GUI and configure your settings and launch the game via the GUI**. (I have no idea why this works.)\n\n### Marvel Rivals\n\nAs of Season 1 of Marvel Rivals (January 2024) the game skips the launcher and most modals if `SteamDeck=1` is passed as an launch argument. This may also resolve any startup crashes or weirdness.\n\n### AFK Journey\n\nSome games display a terms of service or login web view modal on the first launch, however the input window may be detatched from the renderer frame.\n\nIf you use a tiled window manager and see a black square, this is the input frame.\n\nIf you do not see a black square but the web view is not accepting inputs, the black square is likely beneath this window.\n\nLocate both frames and approximate the button locations using the render frame on the black frame.\n\n### Hollow Knight: Silksong on Steam Deck / Hollow Knight: Silksong Controller Input\n\nDiscovered by [navi@vlhl.dev](https://social.vlhl.dev/notice/AyGtMIGRViMhnSyhzE).\n\nThe input mapping that the SDL version Unity Linux uses has an oudated Steam Deck input scheme causing the triggers to not be recognized, to fix this run:\n\n`export SDL_GAMECONTROLLERCONFIG=\"03000000de2800000512000011010000,Steam Deck,a:b3,b:b4,back:b11,dpdown:b17,dpleft:b18,dpright:b19,dpup:b16,guide:b13,leftshoulder:b7,leftstick:b14,lefttrigger:h2.4,leftx:a0,lefty:a1,misc1:b2,paddle1:b21,paddle2:b20,paddle3:b23,paddle4:b22,rightshoulder:b8,rightstick:b15,righttrigger:h2.2,rightx:a2,righty:a3,start:b12,x:b5,y:b6,\"`\n\nin the same terminal before running the game.\n\n**Note** On Silksong Patch 4 onwards (1.0.28954) they have updated the input library to use the new Unity Input System [which has inconsistent support on Linux](https://docs.unity3d.com/Packages/com.unity.inputsystem@1.17/manual/SupportedDevices.html). My general recommendation at this point is to force Proton if your controller does not work.\n\n### FORSPOKEN\n\nOh, geez.\n\n#### FORSPOKEN Stuck on very low settings\n\nSteam advertises every game running through Proton as running on a SteamDeck,\nyou can opt out of this by setting \"SteamDeck=0\" as an environment variable in the launch command.\n\nNote: The game sets graphics settings to below \"low\" if you are on a steamdeck;\nincluding but not limited to restricting the game to use only 6 threads, enforcing VSync + 60 fps cap and mandating FSR + FSR FrameGen.\n\nThere's also more wine-related \"fixes\" and \"optimizations\" that makes it hard to diagnose how to properly implement the fix on wine.\nI have implemented a small(-ish) library that changes how the game behaves in this regard and will update the blog when it's fit for public consumption.\n\nParadoxically, the game runs better when it doesn't add hacks for Wine showing just how far Wine has come through Wine 9 and 10 onwards.\n\n#### FORSPOKEN Black Screen with SteamDeck=0\n\nLaunch the game at least once with SteamDeck=1, then navigate to the compat folder for the game. (`SteamLibrary/steamapps/compatdata/1680880`)\n\nNavigate to the save folder of the game (`pfx/drive_c/users/steamuser/AppData/Local/FORSPOKEN/Steam/{your steam id}/savestorage/`).\n\nCopy system_steamdeck.save to system.save.\n\nI don't know why this fixes it, but it does. The game likely sets some flag that disables a component that freezes under Wine.\n\n#### FORSPOKEN Flickering aggressively\n\nThis is due to the game having a broken FSR implementation that conflicts with the game's DLSS implementation. There's 2 ways to solve this.\n\nEither you enable FSR 3.0 and enable Native AA (this can create visual artifacting) OR\nyou install [DLSSG to FSR3](https://www.nexusmods.com/site/mods/738) ([github](https://github.com/Nukem9/dlssg-to-fsr3)).\n\nI know how this sounds, but the game apparently fixes itself when both FSR and DLSS are valid options.\nYou do not have to enable FSR or DLSS in this scenario, though I do not know if the game secretly enables DLSS or FSR in the background like it does with the SteamDeck.\n\nI only tested this with the addition of [OptiScaler](https://github.com/TheRazerMD/OptiScaler) to go the route of DLSS -> FSR 3.1 -> FSR 4,\nbut I believe it is purely the nvngx.dll that Nukem provides that fixes this.\n\n### Wild Assault Shows \"EasyAntiCheat_Setup_x64.exe\" command prompt and then exits\n\n**Use GE-Proton-10.25, Proton 10, or Proton Experimental**\n\nA valid audio device also has to be present.\n\nRun the following in a Wine cmd (via protontricks):\n\n```bash\nC:\ncd \"Program Files (x86)\\EasyAntiCheat_EOS\"\nEasyAntiCheat_EOS.exe uninstall 22af04e0d5714084adfd927e1d44ac16\nS:\ncd \"common\\WildAssaultEA\\EasyAntiCheat\"\ninstalleac.exe\n```\n\n`22af04e0d5714084adfd927e1d44ac16` is the Product ID, if you can find this ID in the `EasyAntiCheat/Settings.json` file from the game directory.\n\nGE-Proton-10.26 and any Proton-CachyOS version using a newer GE-Proton base may not launch. Unreal Engine never fully initializes, logs don't get written. Hopefully this is not a regression.\n\n## Changelog\n\n- *Update: 2025-12-12 - Wild Assault*\n- *Update: 2025-12-09 - Update Silksong*\n- *Update: 2025-12-09 - Update FORSPOKEN*\n- *Update: 2025-10-21 - FORSPOKEN*\n- *Update: 2025-10-21 - Silksong Note*\n- *Update: 2025-09-16 - Silksong Steam Deck*\n- *Update: 2025-06-24 - macOS Steam on Wine Workaround, rename page*\n- *Update: 2025-06-08 - Removed Dauntless* [It was a pleasure.](https://web.archive.org/web/20250601154507/https://playdauntless.com/news/sunset-notice/)\n- *Update: 2025-06-08 - Added EAC Outside of Proton*\n- *Update: 2025-06-02 - Added Steam Input Lag Note*\n- *Update: 2025-05-31 - Shader Cache*\n- *Update: 2025-05-08 - Battle.net eepy agent*\n- *Update: 2025-04-09 - Monster Hunter Wilds GPU Crash is fixed in dxvk*\n- *Update: 2025-04-04 - Monster Hunter Wilds GPU Crash is real, unfortunately*\n- *Update: 2025-03-31 - Added Monster Hunter Wilds GPU Crash pre-emptive workaround*\n- *Update: 2025-03-03 - Added Monster Hunter Wilds Shader compilation workaround*\n- *Update: 2025-03-03 - Added Monster Hunter Wilds/World incentives guide*\n- *Update: 2024-12-12 - Added Steam Game Recording workaround*\n- *Update: 2024-12-12 - Added Marvel Rivals / AFK Journey CEF frame note*\n- *Update: 2024-12-06 - Added even more Dauntless workarounds*\n- *Update: 2024-11-27 - Added method to verify if EAC is enabled*\n- *Update: 2024-11-13 - Added double dashes to Gamescope commands*\n- *Update: 2024-11-03 - Added Bloodborne emulation notes*\n- *Update: 2024-11-03 - Added AMDGPU-Pro notes*\n- *Update: 2024-10-31 - Added Horizon Zero Dawn: Remastered notes.*\n- *Update: 2024-10-29 - Added note about proprietary drivers and EAC*\n- *Update: 2024-10-29 - Added Monado/SteamVR*\n- *Update: 2024-09-22 - Added gamescope-related workarounds*"
    },
    {
      "title": "bitpacking cheat codes",
      "url": "https://chronovore.dev/posts/2024-09-27-1239A-bitpacking-cheat-codes.html",
      "id": "tag:chronovore.dev,posts:2024-09-27-1239A-bitpacking-cheat-codes",
      "summary": "up up down down left right left right b a start",
      "date_published": "2024-09-26T19:09:00+00:00",
      "date_modified": "2024-09-26T19:09:00+00:00",
      "content_text": "**Note:** This document has a lot of code snippets and may be unsuitable for screen readers.\n\nA little while ago we were talking to someone about cheat-codes in old games \nand how they're very often stored as numbers, but how would you implement something like that?\n\nIn theory you need to figure out how many key states you need to track, because the \"up-up-down-down\" code is extremely iconic I'll be using that for this post but really anything can work.\n\nSo the code goes something like `up up down down left right left right b a`. \nThat's 6 states, let's add in a seventh for a reset state.\n\n```py\nfrom enum import Enum\n\n\nclass BUTTON_STATE(Enum):\n  BUTTON_RESET = 0\n  BUTTON_UP = 1\n  BUTTON_RIGHT = 2\n  BUTTON_DOWN = 3\n  BUTTON_LEFT = 4\n  BUTTON_B = 5\n  BUTTON_A = 6\n```\n\nThat's the easy part. Now we need to figure out how many bits one state would take. Fortunately it's a simple equation.\n\n## $$\\frac{n}{\\log_2(S)}$$\n\nWhere `n` is the number of total bits available for storage, and `S` is the number of states. **Note:** the log2 product has to be **rounded up** and the fraction product has to be **rounded down.**\n\n```py\nfrom math import ceil, log2\n\n\n# we have 6 states + a reset value\nNUMBER_OF_STATES = 7\n\n# we are storing in a 32-bit integer\nBITS = 32\n\n# the number of bits required to safely store the state value, which can be no more than 7\nSTORAGE_WIDTH = ceil(log2(NUMBER_OF_STATES))\n\n# the maximum number of states we can store in our number\nMAX_SEQUENCE = BITS // STORAGE_WIDTH\n\nprint(f'{STORAGE_WIDTH} bits can fit {MAX_SEQUENCE} times in {BITS} bits')\n# 3 bits can fit 10 times in 32 bits\n```\n\nThis means that we can store 10 states in a 32-bit number!\n\nOkay, now we need to calculate a magic check value.\nWe do this by shifting the bits to the right by *3* (since that's the bit width of all states), and do the same with an accompanying mask value.\n\n```py\n# test sequence value\nseq = 0\n# test mask value\nmask = 0\n\n\ndef handle_seq(state):\n    global seq, mask\n\n    # shift sequence to the right by 3 bits, since that's all our states use.\n    seq = seq << 3\n    # apply the button state bits to the sequence\n    seq |= state\n\n    # repeat the same but with all bits set, so we can later check the appropriate amount of bits\n    mask = mask << 3\n    mask |= (1 << 3) - 1\n\n\n# emulate button presses\nhandle_seq(BUTTON_STATE.BUTTON_UP.value)\nhandle_seq(BUTTON_STATE.BUTTON_UP.value)\nhandle_seq(BUTTON_STATE.BUTTON_DOWN.value)\nhandle_seq(BUTTON_STATE.BUTTON_DOWN.value)\nhandle_seq(BUTTON_STATE.BUTTON_LEFT.value)\nhandle_seq(BUTTON_STATE.BUTTON_RIGHT.value)\nhandle_seq(BUTTON_STATE.BUTTON_LEFT.value)\nhandle_seq(BUTTON_STATE.BUTTON_RIGHT.value)\nhandle_seq(BUTTON_STATE.BUTTON_B.value)\nhandle_seq(BUTTON_STATE.BUTTON_A.value)\n\nprint(f'the code sequence is {hex(seq)} and masked by {hex(mask)}')\n# the code sequence is 0x96e28ae and masked by 0x3fffffff\n```\n\nSo now we have our input code sequence and our bit test mask. Now all we have to do is replicate the code in some input event loop.\n\n```py\negg_seq = 0\n\ndef handle_egg_button_press(state):\n  global egg_seq\n\n  # append the state bits to our sequence tracker for this easter egg (and restrict it to 64-bits)\n  egg_seq = ((egg_seq << 3) | state) & 0xffffffffffffffff\n\n  # if the last 30 bits do not match to our pre-calculated value, return early.\n  if (egg_seq & 0x3fffffff) != 0x96e28ae:\n    return\n  \n  # the secret has been found!\n\n  # reset state so we can't accidentally trigger it again \n  egg_seq = 0\n\n  # better trigger it :)\n  trigger_egg_secret()\n```\n\nAnd that's all there's to it. Just a little bit (heh) of bit manipulation and bit packing.\n\nThis has a lot of advantages, such as: \n\n- Only really using 4 (or 8) bytes of memory\n- Doesn't use recursion or any loops.\n- We aren't pre-emptively checking if the input state is correct for this code, we simply only care for the correct sequence. This means we can check multiple cheat sequences in the same function. \n- A little harder to notice if somene is hunting through game source code for debug or cheat input sequences\n\nIn practice, a conventional controller will have about 16 states and a keyboard will have on average 105 states. This means a code can reasonably be 4 keyboard inputs long in a 32-bit number, or 8 controller inputs. However, in the year 2024 we can safely use 64-bit, which expectedly double the storage space compared to 32-bit. We're also only using basic bit manipulation, so we could use SIMD and expand this to 128-bits and 256-bits of storage (18/36 states for keyboard, 32/64 for controller). 256-bit numbers will work on x86-64 computers released in the last decade, assuming they have the AVX2 instruction set available."
    },
    {
      "title": "compression algorithms",
      "url": "https://chronovore.dev/posts/2023-01-25-1234P-compression-deepdive.html",
      "id": "tag:chronovore.dev,posts:2023-01-25-1234P-compression-deepdive",
      "summary": "a deep dive into compression algorithms and how to notice them in hex",
      "date_published": "2023-01-25T09:12:00+00:00",
      "date_modified": "2025-04-09T13:09:00+00:00",
      "content_text": "One of the things that my programmer friends often ask me about is how I can tell what kind of compression algorithm is used by a file.\nThis is an interesting question, and I hope that this post will help you understand how I notice compression algorithms in hex.\n\nI will not be going over the fundamentals of compression algorithms or go into detail about how they work.\n\nThe forum posts[^eyes] and reference docs that have taught me how to do this are referenced where appropriate.\n\nI will explain some of the structure of how the compression algorithms are set up because I believe that understanding _what_ these values mean,\nit will help you understand _why_ they are there and how to notice them when the configuration values are anything but the defaults.\n\nAll magic values are written as byte sequences (i.e. big endian)\n\n[^eyes]: [https://zenhax.com/viewtopic.php?t=27](https://web.archive.org/web/20230109220055/https://zenhax.com/viewtopic.php?t=27) (archived)\n\n[TOC]\n\n## The Basics\n\nThe first thing you need to know is that compression algorithms are not magic.\nIn many cases compression algorithms have a sanity check (a \"magic\" number) that is used to verify and set up the decompressor.\nIn other cases parts of the data can be seen in the compressed data.\n\n## The Dreaded Lempel-Ziv Algorithm (Lz*)\n\nThe Lempel-Ziv algorithm is a compression algorithm that is used in many compression formats.\nIt comes in a lot of flavors and figuring out which one is used can be difficult.\n\nTo figure out if a file might be compressed with an Lz algorithm, you should look for the following:\n\n- The first byte is almost always `0F`, `1F`, `F0`, or `FF`\n- The following bytes are seemingly uncompressed data.\n- The first byte repeats itself frequently in the data, especially at the start of the file.\n\nThis is because LZ algorithms use a dictionary to store data that has been seen before,\nand the first byte (the \"block\") is used to determine the length of the data to be copied from the dictionary.\n\nI strongly suggest using comscan[^comscan] with quickbms[^bms] to test what compression algorithm is used by a file when you encounter this and LZ4 (see below) does not work.\n\n[^comscan]: [https://zenhax.com/viewtopic.php?t=23](https://web.archive.org/web/20221125023314/https://zenhax.com/viewtopic.php?t=23) (archived)\n\n[^bms]: [https://aluigi.altervista.org/quickbms.htm](https://aluigi.altervista.org/quickbms.htm)\n\n### LZ4\n\nLZ4[^lz4] is a common compression algorithm used especially in video games during the 2010s.\n\nLZ4's block has the following format:\n\n```c\nstruct lz4_block {\n    uint8_t encode_count : 4;\n    uint8_t literal_count : 4;\n}\n```\n\nThe `encode_count` is the number of bytes to copy from the decompressed stream, and the `literal_count` is the number of bytes to copy from the compressed data.\nThere is also a special case where either byte is `0F`, which means that the next bytes are added to the count (until the byte is no longer `FF`).\nThe minimal number of literals read is 4.\n\n[^lz4]: [https://github.com/lz4/lz4/](https://github.com/lz4/lz4/)\n\n\n### LZMA, and LZMA2\n\nLZMA[^7z] has no hard defined header[^lzma], though it will often start with `5D` or `2C` followed by a 32-bit integer that is the size of the inline dictionary data (usually zero.)\n\nLZMA2 likewise has no header, though it will often start with `18` followed by compressed data. Note that this byte is optional.\n\nI have not yet seen a raw LZMA stream in the wild beyond 7z files, likely due to it's large overhead.\n\n[^7z]: [https://7-zip.org/sdk.html](https://7-zip.org/sdk.html)\n[^lzma]: [https://github.com/jljusten/LZMA-SDK/blob/20d713a28e5aee284f5671c7cf41ffa52db0215e/DOC/lzma-specification.txt](https://github.com/jljusten/LZMA-SDK/blob/20d713a28e5aee284f5671c7cf41ffa52db0215e/DOC/lzma-specification.txt)\n\n\n## DEFLATE, Zlib, and GZip\n\nDEFLATE[^zlib] is a compression algorithm that is used in many files, and you will most likely have already seen it if you do any amount of file analysis.\n\nZLib uses a DEFLATE block with a header, and ADLER32 as it's checksum algorithm.\n\n[^zlib]: [https://github.com/madler/zlib](https://github.com/madler/zlib)\n\n### ZLib\n\nZLib[^rfc1950] preprends a 2 byte header to the compressed block (usually deflate). This usually is `78 9C` or `78 DA`\n\nThe first byte is the compression method, and the second byte has some flags. The compression method is usually `8`, which is DEFLATE.\n\n```c\nstruct zlib_header {\n    uint8_t compression_method : 4;\n    uint8_t compression_info : 4;\n    uint8_t checksum : 5;\n    uint8_t dict : 1;\n    uint8_t level : 2;\n}\n```\n\nThe `compression_info` is the log base 2 of the window size (the size of the dictionary used by the compressor), and the `checksum` a the checksum of the header.\nThe `dict` flag is set if a dictionary is used, and the `level` is the compression level used by the compressor.\n\nKnowing this, zlib header will always start with `78` if the compression method is DEFLATE.\n\n[^rfc1950]: [https://tools.ietf.org/html/rfc1950](https://tools.ietf.org/html/rfc1950)\n\n### DEFLATE\n\nA \"raw\" DEFLATE[^rfc1951] stream is not very common, but it is still used in some places (especially in files produced by C# projects).\n\nIt usually starts with `C#` or `E#` but it's a bit more complicated than that.\n\nThe DEFLATE block has the following format:\n\n```c\nstruct deflate_block {\n    uint8_t final : 1;\n    uint8_t type : 2;\n}\n```\n\nThe `final` flag is set if this is the last block in the stream, and the `type` is the type of block.\n\n[^rfc1951]: [https://tools.ietf.org/html/rfc1951](https://tools.ietf.org/html/rfc1951)\n\n### GZip\n\nGZip[^rfc1952] is a ZLib stream with a well formed header.\n\nGZip will always start with a magic number (`1F 8B`) as well as a compression method (`8` for DEFLATE).\n\nThe header has the following format:\n\n```c\nstruct gzip_header {\n    uint16_t magic;\n    uint8_t compression_method;\n    uint8_t flags;\n    uint32_t timestamp;\n    uint8_t xtra_flags;\n    uint8_t os;\n}\n```\n\n[^rfc1952]: [https://tools.ietf.org/html/rfc1952](https://tools.ietf.org/html/rfc1952)\n\n## ZStandard\n\nZStandard[^zstd] (zstd) is a relatively new compression algorithm that is starting to be used in many places due to it's ability to have very high compression ratios with specialized dictionaries.\n\nFortunately, ZStandard has a magic number that is used to identify the file, this usually is `## B5 2F FD` with the unknown byte being the specific version.\n\nThe ZStandard header has the following format[^zstddoc]:\n\n```c\nstruct zstd_header {\n    uint32_t magic;\n    uint8_t content_size_flag : 2;\n    uint8_t single_segment_flag : 1;\n    uint8_t unused : 1;\n    uint8_t reserved : 1;\n    uint8_t checksum_flag : 1;\n    uint8_t dict_id_flag : 2;\n}\n```\n\n[^zstd]: [https://github.com/facebook/zstd](https://github.com/facebook/zstd)\n[^zstddoc]: [https://github.com/facebook/zstd/blob/3732a08f5b82ed87a744e65daa2f11f77dabe954/doc/zstd_compression_format.md](https://github.com/facebook/zstd/blob/3732a08f5b82ed87a744e65daa2f11f77dabe954/doc/zstd_compression_format.md)\n\n### ZDictionary\n\nZStandard might use a dictionary[^zdict] to compress the data, and the dictionary is stored either as a separate file, or in the same file as the compressed data. In some cases it might be in the executable itself (very rare!)\nDocumentation on ZDict is sparse, however we know that the magic value is `37 A4 30 EC`\n\nThe ZDict header has the following format:\n\n```c\nstruct zdict_header {\n    uint32_t magic;\n    uint32_t dict_id;\n}\n```\n\n[^zdict]: [https://github.com/facebook/zstd/blob/3732a08f5b82ed87a744e65daa2f11f77dabe954/doc/zstd_compression_format.md#dictionary-format](https://github.com/facebook/zstd/blob/3732a08f5b82ed87a744e65daa2f11f77dabe954/doc/zstd_compression_format.md#dictionary-format)\n\n## Oodle\n\nOodle[^oodle] is a proprietary compression format used in many games, and has a hardware encoder in the PS5.\n\nOodle will always start with `#C` if it is made with version 4 or higher. Version 4 Oodle files have the following format:\n\n```c\nstruct oodle_block_header {\n    uint8_t magic : 4;\n    uint8_t version : 2;\n    bool copy : 1;\n    bool reset : 1;\n    uint8_t compression_type : 7;\n    bool has_checksum : 1;\n}\n```\n\nCompression type will be between 0 and 13 as of Oodle Version 9 (oo2core_9), and the checksum used is a modified Jenkins algorithm.\nFrom this we can deduce that the second byte will be between `00` and `0D`, or `80` and `8D`\n\nNote that Oodle will still load version 3 and older files, which will start with `#0`, `#1`, `#2`, or `#3` and will  usually look like LZW or LZB.\n\n[^oodle]: [http://www.radgametools.com/oodle.htm](http://www.radgametools.com/oodle.htm)\n\n## OodleTexture\n\nOodle Texture[^oodle-texture] is a Block Compression quantizer, with a special runtime for Block Compression 7[^bc7] since that does not compress well.\n\nOodle Texture will start with the hex `BC 07 00 00` as it's version number, a flags integer and 10 block sizes integer\n\n```c\nstruct oodle_texture_prep {\n    uint32_t magic; // 0x7bc\n    uint32_t flags;\n    uint32_t block_sizes[10];\n}\n```\n\n[^oodle-texture]: [https://www.radgametools.com/oodletexture.htm](https://www.radgametools.com/oodletexture.htm)\n[^bc7]: [https://registry.khronos.org/DataFormat/specs/1.1/dataformat.1.1.html#_bc7](https://registry.khronos.org/DataFormat/specs/1.1/dataformat.1.1.html#_bc7), [https://learn.microsoft.com/en-us/windows/win32/direct3d11/bc7-format](https://learn.microsoft.com/en-us/windows/win32/direct3d11/bc7-format)\n\n## BitKnit\n\nGranny is a resource container format by RAD Games, in the most recent versions it introduced a 8-byte block compression algorithm called BitKnit.\n\nA bitknit chunk (fortunately) has the magic signature of `0xB1 0x75`, following is a bitknit chunk.\n\n## Tile Streaming (dstorage)\n\nTile Streaming uses a system to decompress multiple blocks simultaneously[^dstorage].\nThe format supports various compression formats, but at the moment only GDeflate[^gdeflate] (a variation of DEFLATE) is recognized.\nThe header is easily tested, the first byte will be the compression type (`04` for GDeflate) followed by that byte XORed with 0xFF (`FB` for GDeflate.) Followed by the number of \"tiles\".\n\nData compressed with GDeflate Tile Streaming will start with `04 FB`.\n\nThis ends up being:\n\n```c\nstruct tile_stream_header {\n    uint8_t compressor_id;\n    uint8_t magic;\n    uint16_t num_tiles;\n    uint32_t tile_size_idx : 2; // this is always 1\n    uint32_t last_tile_size: 18;\n    uint32_t reserved : 12;\n};\n```\n\n[^dstorage]: [https://github.com/microsoft/DirectStorage/tree/56489d25900d916a9cc450f5efe9e62b01789030/GDeflate/GDeflate](https://github.com/microsoft/DirectStorage/tree/56489d25900d916a9cc450f5efe9e62b01789030/GDeflate/GDeflate)\n[^gdeflate]: [https://github.com/NVIDIA/libdeflate](https://github.com/NVIDIA/libdeflate)\n\n## DENSITY\n\nDensity[^density] is a compression algorithm that claims[^density-benchmarks] 2x decompression speed compared to LZ4.\n\nIt has a very predictable header that is easy to spot.\n\n```c\nstruct density_header {\n    uint8_t version_major;\n    uint8_t version_minor;\n    uint8_t version_revision;\n    uint8_t compression_type;\n    uint32_t reserved;\n}\n```\n\nDensity has 18 releases, of which only 3 are not marked as pre released (0.14.0, 0.14.1, 0.14.2). It has 3 compression types (1, 2, 3.)\n\nWe can reduce this to a set byte sequences `00 0E 02 01 00 00 00 00` with 02 being the revision version, and 01 being the compression type.\n\n[^density]: [https://github.com/g1mv/density](https://github.com/g1mv/density)\n[^density-benchmarks]: [https://github.com/g1mv/density?tab=readme-ov-file#benchmarks](https://github.com/g1mv/density?tab=readme-ov-file#benchmarks)\n\n## Mesh-related Compression\n\n### MeshOptimizer[^mesh-optimizer]\n\nWill always start with `0xA0`, will often emit a lot of `0xFF`, `0xF0` and `0x0F` values for vertex buffers, and a lot of `0x02` for index buffers.\n\n[^mesh-optimizer]: [https://github.com/zeux/meshoptimizer](https://github.com/zeux/meshoptimizer)\n\n### Draco[^draco]\n\nWill usually start with the string `DRACO`, followed by 2 version bytes, 2 encoder bytes and 2 flag bytes[^draco-spec].\n\n```c\nstruct draco_header {\n    char magic[5];\n    uint8_t version_major;\n    uint8_t version_minor;\n    uint8_t encoder_type;\n    uint8_t encoder_method;\n    uint16_t flags;\n}\n```\n\n[^draco]: [https://github.com/google/draco](https://github.com/google/draco)\n[^draco-spec]: [https://github.com/google/draco/blob/8a979f79a5f139880f17f296ace90bcfff025c4b/docs/spec/variable.descriptions.md#header](https://github.com/google/draco/blob/8a979f79a5f139880f17f296ace90bcfff025c4b/docs/spec/variable.descriptions.md#header)\n\n## Zip Signature Speedrun\n\nCompression archives almost always have a signature at the start of the file.\nI'm adding them here for completeness.\n\n- ZIP Magic: `50 4B` (PK)\n- BZip2 Magic: `42 5A 68` (BZh)\n- 7Zip Magic: `37 7A BC AF 27 1C` (7z)\n- Rar Magic: `52 61 72 21 1A 07` (Rar!)\n- WIM Magic: `4D 53 57 49 4D 00 00 00` (MSWIM)\n- Xz Magic: `FD 37 7A 58 5A 00` (7zXZ)\n- Tar Magic: `75 73 74 61 72` (ustar) - usually found at the end of filelist\n\n## Changelog\n\n- *Update: 2025-04-09 - Added Oodle Texture info.*\n- *Update: 2025-03-27 - Added Mesh Optimizer and Draco info.*\n- *Update: 2025-01-04 - Added BitKnit info.*\n- *Update: 2024-09-09 - Added DENSITY info.*\n- *Update: 2024-08-19 - Added Tile Streaming info.*\n- *Update: 2023-07-11 - zenhax is offline, replaced links with archive.org links.*"
    }
  ]
}
