Unpacking Software Livestream

Join our monthly Unpacking Software livestream to hear about the latest news, chat and opinion on packaging, software deployment and lifecycle management!

Learn More

Chocolatey Product Spotlight

Join the Chocolatey Team on our regular monthly stream where we put a spotlight on the most recent Chocolatey product releases. You'll have a chance to have your questions answered in a live Ask Me Anything format.

Learn More

Chocolatey Coding Livestream

Join us for the Chocolatey Coding Livestream, where members of our team dive into the heart of open source development by coding live on various Chocolatey projects. Tune in to witness real-time coding, ask questions, and gain insights into the world of package management. Don't miss this opportunity to engage with our team and contribute to the future of Chocolatey!

Learn More

Calling All Chocolatiers! Whipping Up Windows Automation with Chocolatey Central Management

Webinar from
Wednesday, 17 January 2024

We are delighted to announce the release of Chocolatey Central Management v0.12.0, featuring seamless Deployment Plan creation, time-saving duplications, insightful Group Details, an upgraded Dashboard, bug fixes, user interface polishing, and refined documentation. As an added bonus we'll have members of our Solutions Engineering team on-hand to dive into some interesting ways you can leverage the new features available!

Watch On-Demand
Chocolatey Community Coffee Break

Join the Chocolatey Team as we discuss all things Community, what we do, how you can get involved and answer your Chocolatey questions.

Watch The Replays
Chocolatey and Intune Overview

Webinar Replay from
Wednesday, 30 March 2022

At Chocolatey Software we strive for simple, and teaching others. Let us teach you just how simple it could be to keep your 3rd party applications updated across your devices, all with Intune!

Watch On-Demand
Chocolatey For Business. In Azure. In One Click.

Livestream from
Thursday, 9 June 2022

Join James and Josh to show you how you can get the Chocolatey For Business recommended infrastructure and workflow, created, in Azure, in around 20 minutes.

Watch On-Demand
The Future of Chocolatey CLI

Livestream from
Thursday, 04 August 2022

Join Paul and Gary to hear more about the plans for the Chocolatey CLI in the not so distant future. We'll talk about some cool new features, long term asks from Customers and Community and how you can get involved!

Watch On-Demand
Hacktoberfest Tuesdays 2022

Livestreams from
October 2022

For Hacktoberfest, Chocolatey ran a livestream every Tuesday! Re-watch Cory, James, Gary, and Rain as they share knowledge on how to contribute to open-source projects such as Chocolatey CLI.

Watch On-Demand

Downloads:

999

Downloads of v 2.7.3:

7

Last Update:

08 May 2025

Package Maintainer(s):

Software Author(s):

  • Deco

Tags:

dlss nvidia gaming update-tool admin

DLSS Updater

  • 1
  • 2
  • 3

2.7.3 | Updated: 08 May 2025

Downloads:

999

Downloads of v 2.7.3:

7

Maintainer(s):

Software Author(s):

  • Deco

DLSS Updater 2.7.3

Legal Disclaimer: Neither this package nor Chocolatey Software, Inc. are affiliated with or endorsed by Deco. The inclusion of Deco trademark(s), if any, upon this webpage is solely to identify Deco goods or services and not for commercial purposes.

  • 1
  • 2
  • 3

Some Checks Have Failed or Are Not Yet Complete

Not All Tests Have Passed


Validation Testing Passed


Verification Testing Passed

Details

Scan Testing Resulted in Flagged as a Note:

At least one file within this package has greater than 0 detections, but less than 5

Details
Learn More

Deployment Method: Individual Install, Upgrade, & Uninstall

To install DLSS Updater, run the following command from the command line or from PowerShell:

>

To upgrade DLSS Updater, run the following command from the command line or from PowerShell:

>

To uninstall DLSS Updater, run the following command from the command line or from PowerShell:

>

Deployment Method:

NOTE

This applies to both open source and commercial editions of Chocolatey.

1. Enter Your Internal Repository Url

(this should look similar to https://community.chocolatey.org/api/v2/)


2. Setup Your Environment

1. Ensure you are set for organizational deployment

Please see the organizational deployment guide

2. Get the package into your environment

  • Open Source or Commercial:
    • Proxy Repository - Create a proxy nuget repository on Nexus, Artifactory Pro, or a proxy Chocolatey repository on ProGet. Point your upstream to https://community.chocolatey.org/api/v2/. Packages cache on first access automatically. Make sure your choco clients are using your proxy repository as a source and NOT the default community repository. See source command for more information.
    • You can also just download the package and push it to a repository Download

3. Copy Your Script

choco upgrade dlss-updater -y --source="'INTERNAL REPO URL'" [other options]

See options you can pass to upgrade.

See best practices for scripting.

Add this to a PowerShell script or use a Batch script with tools and in places where you are calling directly to Chocolatey. If you are integrating, keep in mind enhanced exit codes.

If you do use a PowerShell script, use the following to ensure bad exit codes are shown as failures:


choco upgrade dlss-updater -y --source="'INTERNAL REPO URL'" 
$exitCode = $LASTEXITCODE

Write-Verbose "Exit code was $exitCode"
$validExitCodes = @(0, 1605, 1614, 1641, 3010)
if ($validExitCodes -contains $exitCode) {
  Exit 0
}

Exit $exitCode

- name: Install dlss-updater
  win_chocolatey:
    name: dlss-updater
    version: '2.7.3'
    source: INTERNAL REPO URL
    state: present

See docs at https://docs.ansible.com/ansible/latest/modules/win_chocolatey_module.html.


chocolatey_package 'dlss-updater' do
  action    :install
  source   'INTERNAL REPO URL'
  version  '2.7.3'
end

See docs at https://docs.chef.io/resource_chocolatey_package.html.


cChocoPackageInstaller dlss-updater
{
    Name     = "dlss-updater"
    Version  = "2.7.3"
    Source   = "INTERNAL REPO URL"
}

Requires cChoco DSC Resource. See docs at https://github.com/chocolatey/cChoco.


package { 'dlss-updater':
  ensure   => '2.7.3',
  provider => 'chocolatey',
  source   => 'INTERNAL REPO URL',
}

Requires Puppet Chocolatey Provider module. See docs at https://forge.puppet.com/puppetlabs/chocolatey.


4. If applicable - Chocolatey configuration/installation

See infrastructure management matrix for Chocolatey configuration elements and examples.

Package Approved

This package was approved as a trusted package on 08 May 2025.

Description

DLSS Updater is a utility that automatically updates DLSS (Deep Learning Super Sampling) DLLs for games across multiple platforms including Steam, EA Play, Ubisoft, Epic Games, GOG, and Battle.net. It simplifies the process of keeping your games up-to-date with the latest DLSS improvements.

Note: This application requires administrative privileges to run.

This package does not contain malware, it is a limitation of Pyinstaller with the binary not being signed, please see https://github.com/pyinstaller/pyinstaller/issues/6754#issuecomment-1100821249 for more details.


tools\DLSS_Updater.exe
md5: 6135D3A52CD3841165FDA88D4D7BDC8B | sha1: 056F882C71FB1FEB9E90D38A004B1DF17C41F96C | sha256: 597647620A746925A4663996C03ECBDCFA664013C8EC4A1097727FE20B13C67F | sha512: 261492CB5223188B58B8A6FBDAC317CAFD0946712C28AA8AED95ECAAC33FFDFFD54AD44861787FF1DA99454C7DC39D80037FD42A9EA204095C5F84759BDF0542
tools\dlss_updater.png
 
tools\VERIFICATION.TXT
VERIFICATION
Verification is intended to assist the Chocolatey moderators and community
in verifying that this package's contents are trustworthy.

This package is published by the DLSS Updater Project itself. The binaries are 
identical to those published in the GitHub releases section.

Project URL: https://github.com/Recol/DLSS-Updater
Releases: https://github.com/Recol/DLSS-Updater/releases

To verify the binaries:

1. Download the official release from the GitHub releases page
2. Generate a checksum using Get-FileHash in PowerShell
   Get-FileHash DLSS_Updater.exe -Algorithm SHA256
3. Compare the checksum with the one provided in the release notes or in this file

Alternatively, you can use the verification methods provided by GitHub to ensure
the release you're downloading matches the source code at the tagged commit.

Note: This file will be updated with each release to include the specific
checksum for the current version. Always refer to the VERIFICATION.txt
included in the package for the most up-to-date verification information.

Checksum (SHA256) for DLSS_Updater.exe: 597647620A746925A4663996C03ECBDCFA664013C8EC4A1097727FE20B13C67F
tools\_internal\api-ms-win-core-console-l1-1-0.dll
md5: 9F746F4F7D845F063FEA3C37DCEBC27C | sha1: 24D00523770127A5705FCC2A165731723DF36312 | sha256: 88ACE577A9C51061CB7D1A36BABBBEFA48212FADC838FFDE98FDFFF60DE18386 | sha512: 306952418B095E5CF139372A7E684062D05B2209E41D74798A20D7819EFEB41D9A53DC864CB62CC927A98DF45F7365F32B72EC9B17BA1AEE63E2BF4E1D61A6E4
tools\_internal\api-ms-win-core-datetime-l1-1-0.dll
md5: 8F8EB9CB9E78E3A611BC8ACAEC4399CB | sha1: 237EEE6E6E0705C4BE7B0EF716B6A4136BF4E8A8 | sha256: 1BD81DFD19204B44662510D9054852FB77C9F25C1088D647881C9B976CC16818 | sha512: 5B10404CDC29E9FC612A0111B0B22F41D78E9A694631F48F186BDDE940C477C88F202377E887B05D914108B9BE531E6790F8F56E6F03273AB964209D83A60596
tools\_internal\api-ms-win-core-debug-l1-1-0.dll
md5: 226A5983AE2CBBF0C1BDA85D65948ABC | sha1: D0F131DCBA0F0717C5DEA4A9CA7F2E2ECF0AD1C3 | sha256: 591358EB4D1531E9563EE0813E4301C552CE364C912CE684D16576EABF195DC3 | sha512: A1E6671091BD5B2F83BFAA8FCF47093026E354563F84559BD2B57D6E9FA1671EEA27B4ED8493E9FDF4BDE814074DC669DE047B4272B2D14B4F928D25C4BE819D
tools\_internal\api-ms-win-core-errorhandling-l1-1-0.dll
md5: C2F8C03ECCE9941492BFBE4B82F7D2D5 | sha1: 909C66C6DFEA5E0C74D3892D980918251BB08632 | sha256: D56CE7B1CD76108AD6C137326EC694A14C99D48C3D7B0ACE8C3FF4D9BCEE3CE8 | sha512: 7C6C85E390BBE903265574E0E7A074DA2CE30D9376D7A91A121A3E0B1A8B0FFFD5579F404D91836525D4400D2760CB74C9CB448F8C5AE9713385329612B074CF
tools\_internal\api-ms-win-core-fibers-l1-1-0.dll
md5: B5E2760C5A46DBEB8AE18C75F335707E | sha1: E71DB44FC0E0C125DE90A9A87CCB1461E72A9030 | sha256: 91D249D7BC0E38EF6BCB17158B1FDC6DD8888DC086615C9B8B750B87E52A5FB3 | sha512: C3400772D501C5356F873D96B95DC33428A34B6FCAAD83234B6782B5F4BF087121E4FD84885B1ABAB202066DA98EB424F93DD2EED19A0E2A9F6FF4A5CFD1E4F3
tools\_internal\api-ms-win-core-fibers-l1-1-1.dll
md5: 050A30A687E7A2FA6F086A0DB89AA131 | sha1: 1484322CAAF0D71CBB873A2B87BDD8D456DA1A3B | sha256: FC9D86CEC621383EAB636EBC87DDD3F5C19A3CB2A33D97BE112C051D0B275429 | sha512: 07A15AA3B0830F857B9B9FFEB57B6593AE40847A146C5041D38BE9CE3410F58CAA091A7D5671CC1BC7285B51D4547E3004CF0E634AE51FE3DA0051E54D8759E1
tools\_internal\api-ms-win-core-file-l1-1-0.dll
md5: 9F45A47EBFD9D0629F4935764243DD5A | sha1: 86A4A0EA205E31FB73F3BFCCE24945BD6BEA06C7 | sha256: 1CA895ABA4E7435563A6B43E85EBA67A0F8C74AA6A6A94D0FC48FA35535E2585 | sha512: 8C1CDCAD557BFF1685A633D181FCF14EC512D322CAEAEB9C937DA8794C74694FE93528FC9578CB75098F50A2489ED4A5DEDF8C8C2AC93EEB9C8F50E3DD690D5F
tools\_internal\api-ms-win-core-file-l1-2-0.dll
md5: CC228FF8D86B608E73026B1E9960B2F8 | sha1: CEF0705AEE1E8702589524879A49E859505D6FE0 | sha256: 4CADBC0C39DA7C6722206FDCEBD670ABE5B8D261E7B041DD94F9397A89D1990D | sha512: 17ABD9E0EC20B7EB686E3C0F41B043D0742AB7F9501A423B2D2922D44AF660379792D1CC6221EFFBD7E856575D5BABF72657AE9127C87CC5CF678BD2CEB1228F
tools\_internal\api-ms-win-core-file-l2-1-0.dll
md5: E368A236F5676A3DA44E76870CD691C9 | sha1: E4F1D2C6F714A47F0DC29021855C632EF98B0A74 | sha256: 93C624B366BA16C643FC8933070A26F03B073AD0CF7F80173266D67536C61989 | sha512: F5126498A8B65AB20AFAAF6B0F179AB5286810384D44638C35F3779F37E288A51C28BED3C3F8125D51FEB2A0909329F3B21273CB33B3C30728B87318480A9EF8
tools\_internal\api-ms-win-core-handle-l1-1-0.dll
md5: 416AA8314222DB6CBB3760856BE13D46 | sha1: 5F28FE2D565378C033EF8EEA874BC38F4B205327 | sha256: 39095F59C41D76EC81BB2723D646FDE4C148E7CC3402F4980D2ADE95CB9C84F9 | sha512: B16ED31DC3343CAEA47C771326810C040A082E0AB65D9AE69946498CEB6AE0DEE0A570DBCD88090668A100B952C1FF88BADE148811B913C90931AA0E657CD808
tools\_internal\api-ms-win-core-heap-l1-1-0.dll
md5: 344A09B4BE069F86356A89482C156647 | sha1: 2506FFEB157CB531195DD04D11D07C16E4429530 | sha256: 8F105771B236DBCB859DE271F0A6822CE1CB79C36988DD42C9E3F6F55C5F7EB9 | sha512: 4C1E616443576DC83200A4F98D122065926F23212B6647B601470806151FF15EA44996364674821AFEC492B29BA868F188A9D6119B1E1D378A268F1584CA5B29
tools\_internal\api-ms-win-core-interlocked-l1-1-0.dll
md5: 86023497FA48CA2C7705D3F90B76EBC5 | sha1: 835215D7954E57D33D9B34D8850E8DC82F6D09E8 | sha256: 53B25E753CA785BF8B695D89DDE5818A318890211DC992A89146F16658F0B606 | sha512: 8F8370F4C0B27779D18529164FA40CBFDDAFA81A4300D9273713B13428D0367D50583271EA388D43C1A96FED5893448CD14711D5312DA9DFA09B9893DF333186
tools\_internal\api-ms-win-core-kernel32-legacy-l1-1-1.dll
md5: 0C1CC0A54D4B38885E1B250B40A34A84 | sha1: 24400F712BBE1DD260ED407D1EB24C35DCB2ECAC | sha256: A9B13A1CD1B8C19B0C6B4AFCD5BB0DD29C0E2288231AC9E6DB8510094CE68BA6 | sha512: 71674E7ED8650CAC26B6F11A05BFC12BD7332588D21CF81D827C1D22DF5730A13C1E6B3BA797573BB05B3138F8D46091402E63C059650C7E33208D50973DDE39
tools\_internal\api-ms-win-core-libraryloader-l1-1-0.dll
md5: 5FBCB20D99E463259B4F15429010B9CD | sha1: B16770F8BB53DC2BAFCB309824D6FA7B57044D8A | sha256: 7F39BA298B41E4963047341288CAB36B6A241835EE11BA4AD70F44DACD40906C | sha512: 7BA1AC34B3ECFBFB8252F5875BE381D8EF823B50DFE0E070222175EE51191F5EE6D541EEEDD1445ED603A23D200CE9CE15914C8ED3FAFE7E7F3591F51F896C58
tools\_internal\api-ms-win-core-localization-l1-2-0.dll
md5: 5241DF2E95E31E73CCFD6357AD309DF0 | sha1: 2644CC5E86DFAD1AD2140181AB2CA79725F95411 | sha256: 6EE44DD0D8510DC024C9F7C79B1B9FA88C987B26B6BEB6653DDD11751C34E5DC | sha512: 52CCCD1DD237E764E34996C0C5F7A759A7F0EFF29B61BEFEAF96A16D80DF2BA9EE2C3615F875153198A145D68F275AEA6D02187E6EEE5A129E3E2AB81AACEB16
tools\_internal\api-ms-win-core-memory-l1-1-0.dll
md5: 8D285430E8BDA6D5C9B683579ADCB180 | sha1: 619DBBCFF06C659E3FC48F03917A4DADBFC1C275 | sha256: 0512A35316EC9180437F86696A84C5C06A7E4E82E050055A656E5BF9FCA206F9 | sha512: 38405DD85DD62F843ABB55ACEA1B64D7D63BB601445BF1B32078CDE5BBEF4861DD99F26659281FE2AEA86F58CFB1725D8C63D91FB539DCBF5D98CDBE783337FC
tools\_internal\api-ms-win-core-namedpipe-l1-1-0.dll
md5: 4A28CA64F44B91F43945EE3971E0996A | sha1: 45B3D8584C58E8D6AE507FDBD772FEEB1886C8B0 | sha256: C05F1FFFE3B5A2738EA54CE9485CCA026FB9635F982626FBA1E1DCC531897273 | sha512: 862A0428F08D447CD1EE0431969E0FBCB182F4C46418C26D26FA33E586E686D9C093C1CA5781F544CE9276195CE973850719636E39E465F059607F455ECFDD93
tools\_internal\api-ms-win-core-processenvironment-l1-1-0.dll
md5: 7FD4A71085783CCFE9C289C07BCF9B04 | sha1: BB6FFDB5C069DBBA06998DC877D24F72DAD6298D | sha256: C4ECA98C3C67B6395D5B005B00AC1EB0318B86B23AA71035A44C2B1602BEFBA9 | sha512: A96C5B90B8384B239BE111D90CAA3B947651AD73382AB9E5DBE4A4B6AD30921876545331D37C8D5A8F669E39D71BF60983C4BA39C479E23015C2F7579C5E55CD
tools\_internal\api-ms-win-core-processthreads-l1-1-0.dll
md5: C123F2C161884FBFF4F00EF1E1391266 | sha1: 7DB3055DA53916BEA2B85B159491A0772FB620CE | sha256: 5CCB89E93D67BC3288D4E84649C5346E66E15E3D7CD65D989DAF3F4CB584BE9A | sha512: DAC5616320B9052254B5687959E67126C4A938E79173D8245675A9651674384C36CC856F996EF88AE621EC67AFC6616626657585D92BB5D14602A7CC9FC0F669
tools\_internal\api-ms-win-core-processthreads-l1-1-1.dll
md5: 385F562BDC391CCD4F81ACA3719F3236 | sha1: F6633E1DAC227BA3CD14D004748EF0C1C4135E67 | sha256: 4AD565A8BA3EF0EA8AB87221AD11F83EE0BC844CE236607958406663B407333E | sha512: B72ED1A02D4A02791CA5490B35F7E2CB6CB988E4899EDA78134A34FB28964EA573D3289B69D5DB1AAC2289D1F24FD0A432B8187F7AE8147656D38691AE923F27
tools\_internal\api-ms-win-core-profile-l1-1-0.dll
md5: 7A629293EEB0BCA5F9BDEE8ADE477C54 | sha1: A25BF8BAC4FBFD9216EA827E71344BA07B1D463B | sha256: 7809160932F44E59B021699F5BC68799EB7293EE1FA926D6FCCA3C3445302E61 | sha512: 1C58C547D1FE9B54DDF07E5407EDAF3375C6425CA357AA81D09C76A001376C43487476A6F18C891065AB99680501B0F43A16A10ED8E0D5E87B9A9542098F45FE
tools\_internal\api-ms-win-core-rtlsupport-l1-1-0.dll
md5: 3C5C7A3130B075B2DEF5C413C127173F | sha1: F3D2B8AD93F3DC99C8410D34C871AEC56C52E317 | sha256: 9DC1E91E71C7C054854BD1487CB4E6946D82C9F463430F1C4E8D1471005172B1 | sha512: 46A52631E3DD49B0AE10AFBDF50A08D6D6575F3093B3921B2FA744704E2D317F8B10A6D48AD7F922A7843731782521773032A6CC04833B00BD85E404C168FFE4
tools\_internal\api-ms-win-core-string-l1-1-0.dll
md5: 28005B20FBEF6E1DB10912D0FDD6471C | sha1: 47B83697677E08E4EBCFF6FC41ECA7ECE120CC17 | sha256: 60FC31D2A0C634412F529DBA76AF3B9BF991352877C6DAE528186D3935704CFD | sha512: 45D6F860D7F7AEFAA7A0A3B4B21B5C3234F442E39D6259E0A9E2083890533C275F07DDDA93FDDC7445928A55475B83C63253D3B08E41E5576F9029B205DFB36A
tools\_internal\api-ms-win-core-synch-l1-1-0.dll
md5: 436EA0237ED040513EC887046418FAAA | sha1: 44BAFBBDB1B97D86505E16B8A5FCB42B2B771F91 | sha256: 3A72B4F29F39A265D32AD12F0CE15DBF60129C840E10D84D427829EDE45E78AD | sha512: 9F0DBFB538C05383AE9ABFE95E55740530ECC12C1890D8862DEACBC84212BE0740D82AFC9E81D529125221E00B2286CAE0D4B3CA8DD3A6C57774D59F37933692
tools\_internal\api-ms-win-core-synch-l1-2-0.dll
md5: 8F107A7BC018227B181A0E7E76E9CA39 | sha1: EF57E24F29D2B1DEEACEFD82171873B971A3F606 | sha256: EFC1E4460984A73CF47A3DEF033AF1C8F3B1DBC1A56CD27781D3AACF3E3330CB | sha512: D8D8250AAF93FA99E9D1E4286B32579DE0029C83867A787C0A765505A0F8CBD2DD076BB324509D5C4867423BC7DC8F00C8B8458E08E8CBFA8DD731D03DD1AE3F
tools\_internal\api-ms-win-core-sysinfo-l1-1-0.dll
md5: B65BF5EF316880FD8D21E1B34EB5C8A9 | sha1: 3AB4674CB5C76E261FE042D6D0DA8A20BFCBCBAE | sha256: B203D862DDEF1DD62BF623FC866C7F7A9C317C1C2AE30D1F52CB41F955B5698E | sha512: 4AF3B0EF9A813CE1A93A35DD6869817910AE4B628F374477F60EA1831D2CC1AAE7908262672E11954A4953BDFF22BCC5FE23B4A736788E8E5EF4F8AC30EB24F8
tools\_internal\api-ms-win-core-sysinfo-l1-2-0.dll
md5: FC9FC5F308FFC2D2D71814DF8E2AE107 | sha1: 24D7477F2A7DC2610EB701ED683108CD57ECA966 | sha256: 2703635D835396AFD0F138D7C73751AFE7E33A24F4225D08C1690B0A371932C0 | sha512: 490FA6DC846E11C94CFE2F80A781C1BD1943CDDD861D8907DE8F05D9DC7A6364A777C6988C58059E435AC7E5D523218A597B2E9C69C9C34C50D82CAC4400FE01
tools\_internal\api-ms-win-core-timezone-l1-1-0.dll
md5: 43D8D2FB8801C5BD90D9482DDF3EA356 | sha1: D582B55CD58531E726141C63BA9910FF185D72E0 | sha256: 33F4FDDC181066FCE06B2227BDED813F95E94ED1F3D785E982C6B6B56C510C57 | sha512: 0E073381A340DB3F95165DBCCEB8DFBF1ED1B4343E860446032400A7B321B7922C42EE5D9A881E28E69A3F55D56D63663ADB9BB5ABB69C5306EFBF116CC5E456
tools\_internal\api-ms-win-core-util-l1-1-0.dll
md5: 3C58A804B90A0782E80BBBF6C6B6F167 | sha1: B333143E0F6E508B51D27ADF7872B586FA54C794 | sha256: 6EDA016742A6171205A387A14B3C0B331841567740376F56768F8C151724207D | sha512: 773F8DEDED48B34BABE24D955A501F4F357C20125AFFB6EADE36CE6A7ACD380906713C366318F79D627747E636D156875C216FFFAC26DBA25373BBC1C820DA76
tools\_internal\api-ms-win-crt-conio-l1-1-0.dll
md5: 5794B8E183EB547AADD5FAF30A8C4DD2 | sha1: 5B1ED8A9DA14D8ECC4209662809727931AA49307 | sha256: B762061B688AAE679AFE788904D2C9970F74A7DAC98F3B42463D08F25E483D3F | sha512: 3E896854E5DD957AB2B88C82FBAF2EAA03729BAB30FD8518BD999081F4DA9000D9B22894B324E5930DF161C7ADAEC3FC87FD00DE60DCDA34876007AEA4A2FD31
tools\_internal\api-ms-win-crt-convert-l1-1-0.dll
md5: 3560176D0CDBE2F5D33F543348E0A027 | sha1: 1E35A1F7793FC3899927835491F28FE5B903EDCD | sha256: EBB2AE5535A64F65DAEAB8235585114FC9DD2CF1A49F5852D446250B998B6AE4 | sha512: 8AB24C8C9FE8331F21BE96818C5FA69AE5578EB742C4504596310BB0DB7C4C087D350FA47A13ED9FF2E051BB62AC5581DE082D0177923D24FEE6B140AFECF50B
tools\_internal\api-ms-win-crt-environment-l1-1-0.dll
md5: E93C7F013493B12AD40229B19DB02CE6 | sha1: EF878BFBFD2F8328BBB8CFF1AA29A39E624A8503 | sha256: 17D63275D00BDD8670422B95BD264C532998E0A1B041079E54FCE4B6B7A55819 | sha512: 2F4A25EA4062840BEA10442CAD665A72ABBCE747307AD9CE7B3BB89EAF7DCC28F1E9396749576BE304FD793690DDC445653613440442695E72B761EACACB6020
tools\_internal\api-ms-win-crt-filesystem-l1-1-0.dll
md5: 47555752931CECF90E796499B62EC729 | sha1: 217B171764FBA5E91190D1F8A36FECCB3F6D4585 | sha256: 9A9E2A65A281644E368D0F272B95BA5F6B445D1C35910D06056C5EBEB77402DB | sha512: A68009F0306D4D8E70951978D2C184EB80FBEC98C6DB0997BD7B0B503DD63019363CFEF68A9ADBFB568C0A552B774FBDBEB1BCF45F211A6A3224B49E85A5619C
tools\_internal\api-ms-win-crt-heap-l1-1-0.dll
md5: 527BBBFDED529EA77EE798D94CE0F243 | sha1: 647F8C89EB4DB3CF3656292B3DE984B32C6E02A5 | sha256: BAB9AC3EC83E380AE51E4295EF3BF2C738627812D3A49D1E713661ABBC8DC57A | sha512: C1ED69E15AB19084390CF9D1CEAB791758AC4DDD688169F3B814B0E4CF1FC3B6BA17651E35B25DCDC601A8A64821D58933D52A5E939942FA134DFD04FCA04C8B
tools\_internal\api-ms-win-crt-locale-l1-1-0.dll
md5: 09796DAB12CBBD920F632AEB89820193 | sha1: 7D81C0E5537B6D8B79AF0C28CD102E064027C78D | sha256: BD14C67EA28E21D6257AD780A37122C9B5773F69E693F5DB6BFFAEE4D839526E | sha512: 09A6175DCCBBD18A62209E156089F1167DFB8040C97C8C2C14724CE2A8FBE6CE039D7FE04FB8BD60092427BEB7FDD8E7127D611F006FFF1CF2A1AD75E9E5EF3A
tools\_internal\api-ms-win-crt-math-l1-1-0.dll
md5: AA9624CB27CC50A3FBBD3B223A617B1C | sha1: 797AEA1C5CEDD1125276BFC5DCD7A3FB8C6355AA | sha256: 606D66D82DB562EA7979179D06486A0F94D079941D26B80A1E2C49D29959DF6F | sha512: 024975E6787F7A6B0AB6E4B02AD33901F8473B97DC73D4F03B7A116B24AC74150C0C48990EA7A4FB750F9FE728DAFED172796743F802E70F2150EEFCF70FE96A
tools\_internal\api-ms-win-crt-process-l1-1-0.dll
md5: 9D6925407136753E8EB8234D59FA3F1F | sha1: 62631B7007D394FB4D406EA686B291FFF9E486CD | sha256: F6156B1020380EC4F0E48577EBEDAAEF5FB1AB1F337D8B4E72E6A33A7567A9CC | sha512: AB04DE62524E465810CD0EE81E85018863E276D49861E67A920667AF802E94869B816B47A6E3C4738179A7A7D726D44BBBA6E47D9097363A63EAFF51CD56DE8A
tools\_internal\api-ms-win-crt-runtime-l1-1-0.dll
md5: BBAA58E9E1ABDF7D8C4C69652D29D789 | sha1: 38AEF13ABC14502354E8C5C3C37B97A8E2E5FDCF | sha256: C5902934D026D7E15FBE9917D474F3322846A41A25E66F4B2B1F758801879F4B | sha512: 7882A8E1E1EA7E217F70FF9DF27D36709B4BE23588909EF002F3EB1B9A7D3EEA2591A8524AF2C83448DDFFF0911658517C6989683245C54678583F359A78B0AD
tools\_internal\api-ms-win-crt-stdio-l1-1-0.dll
md5: EF37235FC43157A4C93241D5E49E304B | sha1: D4DE26B36812C2DDCCD1618B4D7AC02AD1B42273 | sha256: A9C5A153D8C0286F9B41A2B1C65854AD9E6471B8755B7DE87BAE4470E60BCAB6 | sha512: C0857760D5D069BEEB1EB1737F4160530910331BF6047022836CF58137BD28C2A966A8760A681859F57EBD810FD424CE231402EDDDE1316EAEF7B6F9F773AFBB
tools\_internal\api-ms-win-crt-string-l1-1-0.dll
md5: 639B1FB35CB61BA633EB1791B750631F | sha1: 392A6925009F5FB02A4C122C9CE31D82B9059628 | sha256: 25B8F83A7767211B11132775A0E27A45AA4EC8AB4E6572599F9C172AE3606B40 | sha512: DEF547EF66673862CEA9BB13C433EDCE24A3075C328D9B3B9452F2F01F2F4243DAAB38C0F8571C52D601BC4AECAAA0682DBEBF6BE41CAE345787A719063EBF58
tools\_internal\api-ms-win-crt-time-l1-1-0.dll
md5: FCCCE207A34C947F01D3F23A7DD09569 | sha1: 75F722801C77285DB98A08AF763252A0255E99E2 | sha256: 7C7F6393F06DE11750ADB09CC5698AE55CD9FB27B2E51E207286FEB1B5B2B156 | sha512: D3D923F133594EB4325F4A6E5ED46FCC348A7C0F310F14EAA38C6FAD070BA637BDB4A77200FEB231114E111D07A86595A6130291028CDE3A284D9F847EC38AD4
tools\_internal\api-ms-win-crt-utility-l1-1-0.dll
md5: 708A5BC205384633A7B6674EECC7F0F0 | sha1: 01603A7826029293236C67FCE02ACE8D392A0514 | sha256: D8BA5F17B9FFCBF3AEAF3FA1DA226832D2FA90F81ACCE0CD669464E76CE434AC | sha512: 8638845326AB6543338BAA7A644AF8BE33A123E1FC9DA2037158BE7C8D165691CCD06CB3FF73696A30B8801EAB030E81F93DB81216BB3B7E83A320A0DF5AF270
tools\_internal\base_library.zip
md5: 82CF430B79A7257581CE8BED742D58E6 | sha1: 1368A13957008314909BBDF4DBFBF89BBC04B5F4 | sha256: 7CC5F2B2A7D663A2D5461FE8856268D85195BC3DBD30C5D44D426AABED42974C | sha512: AB2E4E43DDF1002AF293E333C10D5B920037500789A102A682512984C9565315E4EBA1720C6DA4B054BBF460B604FE0E60C21F7398F2009F8E41A640AD60C3E6
tools\_internal\certifi\cacert.pem
 
tools\_internal\certifi\py.typed
 
tools\_internal\charset_normalizer\md.cp313-win_amd64.pyd
md5: 52F4D871306079913ECD8D53EB9ECD05 | sha1: FCA56E0EA208691082A04198B3B517739669F001 | sha256: 76C8700FFC983BBEC07468E354039B21E25E49E7C19F43D7343994C90D4BB7BF | sha512: 4BB9D4161675B6F66C1EADF996DE57BA916497C92E6ED42D0A09DBFE97B243D5B3E9772F942C2B03FA75C2F305CA1584BD9B36D5EC226DCDB2EFC3261809DEA1
tools\_internal\charset_normalizer\md__mypyc.cp313-win_amd64.pyd
md5: 21E82AD181C636E1CF6C24610E2AF08F | sha1: 64F73187472D99632C8579AAC30FA03B20BA232B | sha256: E9C308245FE01D33EF92C7026115A0A930FD865FBE1BFCEFA91E76C6AA32A0B3 | sha512: 8B87A5ECD21A299A3A9A9A06E2C2AA94942B44280C8EECFDC2B92FBD660344F78A48D41DF7859A2F733243A0BFCE59CFCD16D25FDD6DC16279B17EE19EBD4484
tools\_internal\dlss_updater\auto_updater.py
import os
import sys
import json
import shutil
import zipfile
import subprocess
import time
from urllib import request
from urllib.error import URLError
from packaging import version
from dlss_updater.version import __version__
from dlss_updater.logger import setup_logger

logger = setup_logger()

GITHUB_API_URL = "https://api.github.com/repos/Recol/DLSS-Updater/releases/latest"


def check_for_updates():
    """
    Check for available updates by comparing versions.
    Returns (latest_version, download_url) tuple or (None, None) if no update available.
    """
    try:
        with request.urlopen(GITHUB_API_URL) as response:
            latest_release = json.loads(response.read().decode())
        latest_version = latest_release["tag_name"].lstrip("V")

        if version.parse(latest_version) > version.parse(__version__):
            return latest_version, latest_release["assets"][0]["browser_download_url"]
        else:
            return None, None
    except URLError as e:
        logger.error(f"Error checking for updates: {e}")
        return None, None


def download_update(download_url):
    """
    Download and extract the update package.
    Returns path to new executable or None if download/extraction fails.
    """
    try:
        # Create a temporary update directory
        base_dir = os.path.dirname(sys.executable)
        update_dir = os.path.join(base_dir, "update")

        # Remove old update directory if it exists
        if os.path.exists(update_dir):
            try:
                shutil.rmtree(update_dir)
                logger.info("Removed existing update directory")
            except Exception as e:
                logger.error(f"Failed to remove old update directory: {e}")
                return None

        # Create a fresh update directory
        os.makedirs(update_dir, exist_ok=True)
        update_zip = os.path.join(update_dir, "update.zip")

        logger.info("Downloading update package...")
        request.urlretrieve(download_url, update_zip)

        logger.info("Extracting update package...")
        with zipfile.ZipFile(update_zip, "r") as zip_ref:
            zip_ref.extractall(update_dir)

        os.remove(update_zip)

        # Look for the new executable
        new_exe = None
        for root, dirs, files in os.walk(update_dir):
            for file in files:
                if file.lower() == "dlss_updater.exe":
                    new_exe = os.path.join(root, file)
                    break
            if new_exe:
                break

        if new_exe:
            logger.info(f"Found new executable: {new_exe}")
            return new_exe
        else:
            logger.error("Error: New executable not found in the update package.")
            return None
    except Exception as e:
        logger.error(f"Error downloading update: {e}")
        if os.path.exists(update_dir):
            shutil.rmtree(update_dir)
        return None


def update_script(current_exe, new_exe):
    """
    Perform the actual update by replacing the old executable with the new one.
    """
    logger.info(f"Starting update process: from {current_exe} to {new_exe}")

    # Wait for the original process to exit
    time.sleep(2)

    try:
        # Get the command line arguments to pass to the new executable
        args = sys.argv[1:] if len(sys.argv) > 1 else []

        # Create a backup of the current executable path for cleanup
        current_dir = os.path.dirname(current_exe)
        backup_path = os.path.join(current_dir, "old_exe_backup.txt")
        with open(backup_path, "w") as f:
            f.write(current_exe)

        # Replace the old executable with the new one
        if os.path.exists(current_exe):
            os.chmod(current_exe, 0o777)  # Ensure we have write permission
            os.remove(current_exe)
            logger.info(f"Removed old executable: {current_exe}")

        # Move the new executable to the location of the old one
        shutil.move(new_exe, current_exe)
        logger.info(f"Moved new executable to: {current_exe}")

        # Wait briefly to ensure the file is fully written
        time.sleep(1)

        # Ensure the new executable has appropriate permissions
        os.chmod(current_exe, 0o755)

        # Clean up the update directory
        update_dir = os.path.dirname(new_exe)
        if os.path.exists(update_dir) and os.path.isdir(update_dir):
            shutil.rmtree(update_dir)
            logger.info(f"Cleaned up update directory: {update_dir}")

        # Start the updated executable with original arguments
        startup_args = [current_exe] + args
        logger.info(f"Starting updated executable with args: {startup_args}")
        subprocess.Popen(startup_args, creationflags=subprocess.CREATE_NEW_CONSOLE)

        # Log the update completion
        with open(
            os.path.join(os.path.dirname(current_exe), "update_log.txt"), "w"
        ) as f:
            f.write(
                f"Update completed at {time.ctime()}. New executable started with args: {args}"
            )

    except Exception as e:
        # Log the error
        error_msg = f"Error during update process at {time.ctime()}: {str(e)}"
        logger.error(error_msg)
        with open(
            os.path.join(os.path.dirname(current_exe), "update_error_log.txt"), "w"
        ) as f:
            f.write(error_msg)


def perform_update(new_exe_path):
    """
    Start the update process in a new process and exit the current one.
    """
    current_exe = sys.executable

    logger.info(f"Preparing to update from {current_exe} to {new_exe_path}")

    # Get command line arguments to pass to the updater
    args = sys.argv[1:] if len(sys.argv) > 1 else []

    # Create the update command
    update_cmd = [sys.executable, __file__, "update", current_exe, new_exe_path]

    # Add a log for debugging
    logger.info(f"Starting update process with command: {update_cmd}")

    # Start the update process in a separate process
    subprocess.Popen(
        update_cmd,
        creationflags=subprocess.CREATE_NO_WINDOW,
    )

    # Exit the current process
    sys.exit(0)


def auto_update():
    """
    Main update function that orchestrates the update process.
    Returns True if an update was downloaded and ready to install,
    False otherwise.
    """
    logger.info("Checking for updates...")
    latest_version, download_url = check_for_updates()

    if latest_version:
        logger.info(f"New version available: {latest_version}")
        new_exe_path = download_update(download_url)

        if new_exe_path:
            logger.info("Update downloaded successfully.")
            logger.info(
                "The application will now close and update. It will restart automatically."
            )
            perform_update(new_exe_path)
            return True
    else:
        logger.info("No updates available. You have the latest version.")

    return False


def cleanup_old_update_files():
    """
    Clean up any leftover files from previous updates.
    Should be called at application startup.
    """
    try:
        base_dir = os.path.dirname(sys.executable)

        # Check for old update directory
        update_dir = os.path.join(base_dir, "update")
        if os.path.exists(update_dir):
            logger.info(f"Cleaning up old update directory: {update_dir}")
            shutil.rmtree(update_dir)

        # Check for backup file from previous update
        backup_path = os.path.join(base_dir, "old_exe_backup.txt")
        if os.path.exists(backup_path):
            with open(backup_path, "r") as f:
                old_exe = f.read().strip()

            if os.path.exists(old_exe):
                logger.info(f"Removing old executable from previous update: {old_exe}")
                try:
                    os.remove(old_exe)
                except:
                    logger.warning(f"Could not remove old executable: {old_exe}")

            os.remove(backup_path)

        return True
    except Exception as e:
        logger.error(f"Error cleaning up update files: {e}")
        return False


if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] == "update":
        update_script(sys.argv[2], sys.argv[3])
tools\_internal\dlss_updater\config.py
import os
import sys
import configparser
from enum import StrEnum
import appdirs
from .logger import setup_logger

logger = setup_logger()


def get_config_path():
    """Get the path for storing configuration files"""
    app_name = "DLSS-Updater"
    app_author = "Recol"
    config_dir = appdirs.user_config_dir(app_name, app_author)
    os.makedirs(config_dir, exist_ok=True)
    return os.path.join(config_dir, "config.ini")


def resource_path(relative_path):
    """Get absolute path to resource, works for dev and for PyInstaller"""
    try:
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")
    return os.path.join(base_path, relative_path)


# Version information without paths (paths will be resolved at runtime)
LATEST_DLL_VERSIONS = {
    "nvngx_dlss.dll": "310.2.1.0",
    "nvngx_dlssg.dll": "310.2.1.0",
    "nvngx_dlssd.dll": "310.2.1.0",
    "libxess.dll": "2.0.1.41",
    "libxess_dx11.dll": "2.0.1.41",
    "dstorage.dll": "1.2.2504.401",
    "dstoragecore.dll": "1.2.2504.401",
}

# IMPORTANT: We'll initialize this later to avoid circular imports
LATEST_DLL_PATHS = {}


class LauncherPathName(StrEnum):
    STEAM = "SteamPath"
    EA = "EAPath"
    EPIC = "EpicPath"
    GOG = "GOGPath"
    UBISOFT = "UbisoftPath"
    BATTLENET = "BattleDotNetPath"
    XBOX = "XboxPath"
    CUSTOM1 = "CustomPath1"
    CUSTOM2 = "CustomPath2"
    CUSTOM3 = "CustomPath3"
    CUSTOM4 = "CustomPath4"


class ConfigManager(configparser.ConfigParser):
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super(ConfigManager, cls).__new__(cls)
        return cls._instance

    def __init__(self):
        if not hasattr(self, "initialized"):
            super().__init__()
            self.logger = setup_logger()
            self.config_path = get_config_path()
            self.read(self.config_path)

            # Initialize launcher paths section
            if not self.has_section("LauncherPaths"):
                self.add_section("LauncherPaths")
                self["LauncherPaths"].update(
                    {
                        LauncherPathName.STEAM: "",
                        LauncherPathName.EA: "",
                        LauncherPathName.EPIC: "",
                        LauncherPathName.GOG: "",
                        LauncherPathName.UBISOFT: "",
                        LauncherPathName.BATTLENET: "",
                        LauncherPathName.XBOX: "",
                        LauncherPathName.CUSTOM1: "",
                        LauncherPathName.CUSTOM2: "",
                        LauncherPathName.CUSTOM3: "",
                        LauncherPathName.CUSTOM4: "",
                    }
                )
                self.save()

            # Initialize update preferences section
            if not self.has_section("UpdatePreferences"):
                self.add_section("UpdatePreferences")
                self["UpdatePreferences"].update(
                    {
                        "UpdateDLSS": "true",
                        "UpdateDirectStorage": "true",
                        "UpdateXeSS": "true",
                    }
                )
                self.save()

            self.initialized = True

    def update_launcher_path(
        self, path_to_update: LauncherPathName, new_launcher_path: str
    ):
        self.logger.debug(f"Attempting to update path for {path_to_update}.")
        self["LauncherPaths"][path_to_update] = new_launcher_path
        self.save()
        self.logger.debug(f"Updated path for {path_to_update}.")

    def check_path_value(self, path_to_check: LauncherPathName) -> str:
        return self["LauncherPaths"].get(path_to_check, "")

    def reset_launcher_path(self, path_to_reset: LauncherPathName):
        self.logger.debug(f"Resetting path for {path_to_reset}.")
        self["LauncherPaths"][path_to_reset] = ""
        self.save()
        self.logger.debug(f"Reset path for {path_to_reset}.")

    def get_update_preference(self, technology):
        """Get update preference for a specific technology"""
        return self["UpdatePreferences"].getboolean(f"Update{technology}", True)

    def set_update_preference(self, technology, enabled):
        """Set update preference for a specific technology"""
        self["UpdatePreferences"][f"Update{technology}"] = str(enabled).lower()
        self.save()

    def get_all_blacklist_skips(self):
        """Get all games to skip in the blacklist"""
        if not self.has_section("BlacklistSkips"):
            self.add_section("BlacklistSkips")
            self.save()
        return [
            game
            for game, value in self["BlacklistSkips"].items()
            if value.lower() == "true"
        ]

    def add_blacklist_skip(self, game_name):
        """Add a game to skip in the blacklist"""
        if not self.has_section("BlacklistSkips"):
            self.add_section("BlacklistSkips")
        self["BlacklistSkips"][game_name] = "true"
        self.save()

    def clear_all_blacklist_skips(self):
        """Clear all blacklist skips"""
        if self.has_section("BlacklistSkips"):
            self.remove_section("BlacklistSkips")
            self.add_section("BlacklistSkips")
            self.save()

    def is_blacklist_skipped(self, game_name):
        """Check if a game is in the blacklist skip list"""
        if not self.has_section("BlacklistSkips"):
            return False
        return self["BlacklistSkips"].getboolean(game_name, False)

    def save(self):
        """Save configuration to disk"""
        with open(self.config_path, "w") as configfile:
            self.write(configfile)


config_manager = ConfigManager()


def initialize_dll_paths():
    """Initialize the DLL paths after all modules are loaded"""
    from .dll_repository import get_local_dll_path

    global LATEST_DLL_PATHS
    LATEST_DLL_PATHS = {
        "nvngx_dlss.dll": get_local_dll_path("nvngx_dlss.dll"),
        "nvngx_dlssg.dll": get_local_dll_path("nvngx_dlssg.dll"),
        "nvngx_dlssd.dll": get_local_dll_path("nvngx_dlssd.dll"),
        "libxess.dll": get_local_dll_path("libxess.dll"),
        "libxess_dx11.dll": get_local_dll_path("libxess_dx11.dll"),
        "dstorage.dll": get_local_dll_path("dstorage.dll"),
        "dstoragecore.dll": get_local_dll_path("dstoragecore.dll"),
    }
    return LATEST_DLL_PATHS
tools\_internal\dlss_updater\constants.py
DLL_TYPE_MAP = {
    "nvngx_dlss.dll": "DLSS DLL",
    "nvngx_dlssg.dll": "DLSS Frame Generation DLL",
    "nvngx_dlssd.dll": "DLSS Ray Reconstruction DLL",
    "libxess.dll": "XeSS DLL",
    "libxess_dx11.dll": "XeSS DX11 DLL",
}

DLL_GROUPS = {
    "DLSS": [
        "nvngx_dlss.dll",
        "nvngx_dlssg.dll",
        "nvngx_dlssd.dll",
    ],
    "XeSS": [
        "libxess.dll",
        "libxess_dx11.dll",
    ],
    "DirectStorage": [
        "dstorage.dll",
        "dstoragecore.dll",
    ],
}
tools\_internal\dlss_updater\dll_repository.py
import os
import json
import requests
import shutil
from pathlib import Path
from packaging import version
from .logger import setup_logger

logger = setup_logger()

# Configuration
GITHUB_DLL_REPO = "Recol/DLSS-Updater-DLLs"
GITHUB_API_BASE = f"https://api.github.com/repos/{GITHUB_DLL_REPO}"
GITHUB_RAW_BASE = f"https://raw.githubusercontent.com/{GITHUB_DLL_REPO}/main"
DLL_MANIFEST_URL = f"{GITHUB_RAW_BASE}/manifest.json"
LOCAL_DLL_CACHE_DIR = os.path.join(
    os.path.expanduser("~"), ".dlss_updater", "dll_cache"
)


def ensure_cache_dir():
    """Ensure local cache directory exists"""
    os.makedirs(LOCAL_DLL_CACHE_DIR, exist_ok=True)


def get_local_dll_path(dll_name):
    """Get path to cached DLL, download if newer version exists"""
    ensure_cache_dir()
    local_path = os.path.join(LOCAL_DLL_CACHE_DIR, dll_name)

    # If it doesn't exist locally, try to download
    if not os.path.exists(local_path):
        if download_latest_dll(dll_name):
            return local_path
        else:
            logger.error(f"Failed to download {dll_name} and no local copy exists")
            return None

    # Check for updates
    if check_for_dll_update(dll_name):
        download_latest_dll(dll_name)

    return local_path


def get_remote_manifest():
    """Fetch the remote DLL manifest"""
    try:
        response = requests.get(DLL_MANIFEST_URL, timeout=10)
        response.raise_for_status()
        return response.json()
    except Exception as e:
        logger.error(f"Failed to fetch DLL manifest: {e}")
        return None


def get_cached_manifest():
    """Get the cached manifest if available"""
    manifest_path = os.path.join(LOCAL_DLL_CACHE_DIR, "manifest.json")
    if os.path.exists(manifest_path):
        try:
            with open(manifest_path, "r") as f:
                return json.load(f)
        except Exception as e:
            logger.error(f"Failed to read cached manifest: {e}")
    return None


def update_cached_manifest(manifest):
    """Update the cached manifest"""
    manifest_path = os.path.join(LOCAL_DLL_CACHE_DIR, "manifest.json")
    try:
        with open(manifest_path, "w") as f:
            json.dump(manifest, f, indent=2)
        return True
    except Exception as e:
        logger.error(f"Failed to update cached manifest: {e}")
        return False


def check_for_dll_update(dll_name):
    """Check if a newer version of the DLL is available"""
    # Import parse_version and get_dll_version inside the function
    from .updater import get_dll_version, parse_version

    local_path = os.path.join(LOCAL_DLL_CACHE_DIR, dll_name)
    if not os.path.exists(local_path):
        logger.info(f"No local copy of {dll_name} exists, download needed")
        return True  # No local copy, need to download

    local_version = get_dll_version(local_path)
    if not local_version:
        logger.info(
            f"Could not determine version of local {dll_name}, assuming update needed"
        )
        return True  # Can't determine local version, assume update needed

    # Get remote version info
    manifest = get_remote_manifest() or get_cached_manifest()
    if not manifest:
        logger.warning("No manifest available, can't check for updates")
        return False

    if dll_name not in manifest:
        logger.warning(f"{dll_name} not found in manifest")
        return False

    remote_version = manifest[dll_name]["version"]

    # Compare versions with better error handling
    try:
        local_parsed = parse_version(local_version)
        remote_parsed = parse_version(remote_version)

        # Add detailed logging for debugging
        logger.info(
            f"Comparing versions for {dll_name}: local={local_version} ({local_parsed}) remote={remote_version} ({remote_parsed})"
        )

        if remote_parsed > local_parsed:
            logger.info(
                f"Update available for {dll_name}: {local_version} -> {remote_version}"
            )
            return True
        else:
            logger.info(
                f"{dll_name} is up to date (local: {local_version}, remote: {remote_version})"
            )
            return False
    except Exception as e:
        logger.error(f"Version comparison error for {dll_name}: {e}")
        # Assume update needed if we can't compare versions safely
        logger.info(
            f"Assuming update needed for {dll_name} due to version comparison error"
        )
        return True


def download_latest_dll(dll_name):
    """Download the latest version of a DLL"""
    manifest = get_remote_manifest()
    if not manifest:
        logger.error("Failed to fetch manifest, cannot download DLL")
        return False

    if dll_name not in manifest:
        logger.error(f"{dll_name} not found in manifest")
        return False

    # Get download URL from manifest
    dll_info = manifest[dll_name]
    download_url = f"{GITHUB_RAW_BASE}/dlls/{dll_name}"

    # Download the DLL
    local_path = os.path.join(LOCAL_DLL_CACHE_DIR, dll_name)
    try:
        response = requests.get(download_url, stream=True, timeout=30)
        response.raise_for_status()

        # Save to temp file first, then rename to avoid partial downloads
        temp_path = f"{local_path}.tmp"
        with open(temp_path, "wb") as f:
            for chunk in response.iter_content(chunk_size=8192):
                f.write(chunk)

        # Replace existing file
        if os.path.exists(local_path):
            os.remove(local_path)
        os.rename(temp_path, local_path)

        logger.info(f"Successfully downloaded {dll_name} v{dll_info['version']}")
        return True
    except Exception as e:
        logger.error(f"Failed to download {dll_name}: {e}")
        return False


_cache_initialized = False


def initialize_dll_cache():
    """Initialize the DLL cache on application startup"""
    global _cache_initialized

    # Skip if already initialized
    if _cache_initialized:
        logger.debug("DLL cache already initialized, skipping")
        return

    # Ensure we initialize the DLL paths
    from .config import initialize_dll_paths

    logger.info("Initializing DLL cache")
    ensure_cache_dir()

    # Fetch latest manifest
    manifest = get_remote_manifest()
    if manifest:
        update_cached_manifest(manifest)

        # Check all DLLs for updates
        for dll_name in manifest:
            if check_for_dll_update(dll_name):
                logger.info(
                    f"Updating {dll_name} to version {manifest[dll_name]['version']}"
                )
                download_latest_dll(dll_name)
            else:
                logger.info(f"{dll_name} is up to date")
    else:
        logger.warning("Using cached manifest, updates may not be available")

    # Initialize DLL paths after cache initialization
    initialize_dll_paths()

    # Mark as initialized
    _cache_initialized = True
tools\_internal\dlss_updater\dlss_updater.log
 
tools\_internal\dlss_updater\icons\battlenet.png
 
tools\_internal\dlss_updater\icons\dlss_updater.ico
 
tools\_internal\dlss_updater\icons\dlss_updater.png
 
tools\_internal\dlss_updater\icons\dlss_updater_full.png
 
tools\_internal\dlss_updater\icons\ea.png
 
tools\_internal\dlss_updater\icons\epic.png
 
tools\_internal\dlss_updater\icons\gog.png
 
tools\_internal\dlss_updater\icons\reset.png
 
tools\_internal\dlss_updater\icons\steam.png
 
tools\_internal\dlss_updater\icons\ubisoft.png
 
tools\_internal\dlss_updater\icons\update.png
 
tools\_internal\dlss_updater\icons\xbox.png
 
tools\_internal\dlss_updater\lib\threading_lib.py
from PyQt6.QtCore import QThreadPool, QRunnable, pyqtSignal, QObject

class WorkerSignals(QObject):
    """Signals available from a running worker thread."""
    finished = pyqtSignal()
    result = pyqtSignal(object)
    error = pyqtSignal(tuple)
    progress = pyqtSignal(int)


class Worker(QRunnable):
    """
    Worker thread for running background tasks.
    
    Inherits from QRunnable for better thread pool management.
    """
    def __init__(self, fn, *args, **kwargs):
        super().__init__()
        
        # Store constructor arguments (re-used for processing)
        self.fn = fn
        self.args = args
        self.kwargs = kwargs
        
        # Create signals for communication
        self.signals = WorkerSignals()
        
        # Add an option to stop the worker if needed
        self.is_running = True

    def run(self):
        """
        Initialise the runner function with passed args, kwargs.
        
        Automatic handling of different function signatures and return values.
        """
        try:
            # Retrieve args/kwargs here; and fire processing using them
            result = self.fn(*self.args, **self.kwargs)
        except Exception as e:
            # If function raised an exception, capture it
            import traceback
            
            # Package the exception details
            exctype = type(e)
            value = str(e)
            tb = traceback.format_exc()
            
            # If still running, emit the error signal
            if self.is_running:
                self.signals.error.emit((exctype, value, tb))
        else:
            # If function successfully completed, emit result
            if self.is_running:
                if result is not None:
                    self.signals.result.emit(result)
        finally:
            # Always emit finished signal
            if self.is_running:
                self.signals.finished.emit()

    def stop(self):
        """Set the running state to False to prevent further signal emissions."""
        self.is_running = False


class ThreadManager:
    """
    Manages thread pool and worker creation for background tasks.
    
    Provides a simplified interface for running functions in a thread pool.
    """
    def __init__(self, parent=None):
        # Create a thread pool
        self.thread_pool = QThreadPool()
        
        # Set up maximum thread count (adjust as needed)
        self.thread_pool.setMaxThreadCount(1)
        
        # Store the current worker
        self.current_worker = None
        
        # Store the parent (optional)
        self.parent = parent
        
        # Signals to be connected from the current worker
        self.signals = None

    def assign_function(self, func, *args, **kwargs):
        """
        Assign a function to be run in a background thread.
        
        Stops any existing worker before creating a new one.
        """
        # Stop any existing worker
        if self.current_worker:
            self.current_worker.stop()
        
        # Create a new worker
        worker = Worker(func, *args, **kwargs)
        
        # Store the current worker
        self.current_worker = worker
        
        # Update signals reference
        self.signals = worker.signals

    def run(self):
        """
        Run the currently assigned worker in the thread pool.
        
        Adds the worker to the thread pool for execution.
        """
        if self.current_worker:
            # Add worker to thread pool
            self.thread_pool.start(self.current_worker)

    def waitForDone(self):
        """
        Wait for all threads in the pool to complete.
        
        Useful for clean shutdown of the application.
        """
        # Stop current worker if exists
        if self.current_worker:
            self.current_worker.stop()
        
        # Wait for thread pool to finish
        self.thread_pool.waitForDone()
tools\_internal\dlss_updater\logger.py
import logging
import sys
from pathlib import Path
from PyQt6.QtCore import QObject, pyqtSignal, Qt
from PyQt6.QtWidgets import QTextBrowser


class QLoggerLevelSignal(QObject):
    """Signals for the Logger QTextBrowser derived class."""
    debug = pyqtSignal()
    info = pyqtSignal()
    warning = pyqtSignal()
    error = pyqtSignal()


class LoggerWindow(QTextBrowser):
    """A QTextBrowser subclass that have signals and a dict for ease of access to said signals."""
    def __init__(self, parent=None):
        super().__init__(parent)
        self.signals = QLoggerLevelSignal()
        self.signals_to_emit = {
            "DEBUG": self.signals.debug,
            "INFO": self.signals.info,
            "WARNING": self.signals.warning,
            "ERROR": self.signals.error,
        }
        # Set document max size to prevent memory issues with very large logs
        self.document().setMaximumBlockCount(5000)  # Limit to last 5000 lines
        # Set text browser properties
        self.setReadOnly(True)
        self.setOpenExternalLinks(False)
        # Enable smooth scrolling
        self.verticalScrollBar().setSingleStep(2)


class QLogger(logging.Handler, QObject):
    """Logger handler for the Qt GUI"""
    # Define the signal as a class attribute
    logMessage = pyqtSignal(str, str)

    def __init__(self, text_browser):
        logging.Handler.__init__(self)
        QObject.__init__(self)  # Initialize QObject
        
        self.text_browser = text_browser
        self.colors_dict = {
            "DEBUG": "white",
            "INFO": "green", 
            "WARNING": "yellow",
            "ERROR": "red",
        }
        # Connect the signal to the slot method
        self.logMessage.connect(self.write_log, Qt.ConnectionType.QueuedConnection)

    def emit(self, record):
        """
        Logs the record to the text browser object.
        @param record: LogRecord object to log.
        """
        msg = self.format(record)
        # Emit the signal with levelname and formatted message
        self.logMessage.emit(record.levelname, msg)

    def write_log(self, levelname, msg):
        """Write the log message to the text browser in the main thread."""
        color = self.colors_dict[levelname]
        formatted_msg = f'<font color="{color}">{msg}</font>'
        self.text_browser.signals_to_emit[levelname].emit()
        self.text_browser.append(formatted_msg)
        # Scroll to bottom
        scrollbar = self.text_browser.verticalScrollBar()
        scrollbar.setValue(scrollbar.maximum())


def setup_logger(log_file_name="dlss_updater.log"):
    """
    Setups the initial logger.
    param: log_file_name: filename to be used for the logfile.
    return: logger instance created.
    """
    logger = logging.getLogger("DLSSUpdater")

    # Check if the logger has already been configured
    if not logger.handlers:
        logger.setLevel(logging.DEBUG)

        log_file_path = (
            Path(sys.executable).parent / log_file_name
            if getattr(sys, "frozen", False)
            else Path(__file__).parent / log_file_name
        )

        # Create handlers
        console_handler = logging.StreamHandler(sys.stdout)
        file_handler = logging.FileHandler(log_file_path, encoding="utf-8")

        # Create formatter and add it to handlers
        log_format = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
        console_handler.setFormatter(log_format)
        file_handler.setFormatter(log_format)

        # Add handlers to the logger
        logger.addHandler(console_handler)
        logger.addHandler(file_handler)

        # Prevent propagation to avoid duplicate logs
        logger.propagate = False

    return logger


def add_qt_handler(logger_to_extend, text_browser):
    """
    Add a QTextBrowser handler to an existing logger instance.
    @param: logger_to_extend: logger instance to be extended.
    @param: text_browser: QTextBrowser instance to be added as a logger.
    """
    # Remove any existing QLogger handlers
    for handler in logger_to_extend.handlers[:]:
        if isinstance(handler, QLogger):
            logger_to_extend.removeHandler(handler)

    # Create a new QLogger handler
    text_browser_handler = QLogger(text_browser)
    formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
    text_browser_handler.setFormatter(formatter)
    logger_to_extend.addHandler(text_browser_handler)


# Usage example
if __name__ == "__main__":
    logger = setup_logger()
    logger.info("This is a test log message")
    logger.info("This is a test logger.info message with an argument: %s", "test arg")
tools\_internal\dlss_updater\main_ui\animated_toggle.py
from PyQt6.QtCore import (
    Qt,
    QSize,
    QPoint,
    QPointF,
    QRectF,
    QEasingCurve,
    QPropertyAnimation,
    QSequentialAnimationGroup,
    pyqtSlot,
    pyqtProperty,
)
from PyQt6.QtWidgets import QCheckBox
from PyQt6.QtGui import QColor, QBrush, QPaintEvent, QPen, QPainter


class AnimatedToggle(QCheckBox):
    def __init__(
        self,
        parent=None,
        track_color="#888888",
        thumb_color="#FFFFFF",
        track_active_color="#2D6E88",
        thumb_active_color="#FFFFFF",
        animation_duration=120,
    ):
        super().__init__(parent)

        # Colors
        self._track_color = QColor(track_color)
        self._thumb_color = QColor(thumb_color)
        self._track_active_color = QColor(track_active_color)
        self._thumb_active_color = QColor(thumb_active_color)

        # Dimensions
        self._track_radius = 11
        self._thumb_radius = 8

        # Animation
        self._animation_duration = animation_duration
        self._margin = max(0, self._thumb_radius - self._track_radius)
        self._offset = 0
        self._pulse_radius = 0

        # Setup animations
        self.animation = QPropertyAnimation(self, b"offset")
        self.animation.setEasingCurve(QEasingCurve.Type.InOutCubic)
        self.animation.setDuration(self._animation_duration)

        self.pulse_animation = QPropertyAnimation(self, b"pulse_radius")
        self.pulse_animation.setDuration(self._animation_duration)
        self.pulse_animation.setEasingCurve(QEasingCurve.Type.InOutCubic)

        self.animations_group = QSequentialAnimationGroup()
        self.animations_group.addAnimation(self.animation)
        self.animations_group.addAnimation(self.pulse_animation)

        # Setup initial state
        self.setFixedSize(
            self._track_radius * 4 + self._margin * 2,
            self._track_radius * 2 + self._margin * 2,
        )
        self.setCursor(Qt.CursorShape.PointingHandCursor)

    def sizeHint(self):
        return QSize(
            4 * self._track_radius + 2 * self._margin,
            2 * self._track_radius + 2 * self._margin,
        )

    def hitButton(self, pos: QPoint):
        return self.contentsRect().contains(pos)

    @pyqtSlot(int)
    def setChecked(self, checked):
        super().setChecked(checked)
        self.offset = 1 if checked else 0

    def paintEvent(self, e: QPaintEvent):
        # Set up painter
        p = QPainter(self)
        p.setRenderHint(QPainter.RenderHint.Antialiasing)

        # Get current state
        checked = self.isChecked()
        enabled = self.isEnabled()

        # Calculate sizes
        track_opacity = 0.6 if enabled else 0.3
        margin = self._margin
        thumb_radius = self._thumb_radius
        track_radius = self._track_radius

        # Get widget dimensions
        width = self.width() - 2 * margin
        height = self.height() - 2 * margin

        # Draw track
        track_brush = QBrush(self._track_active_color if checked else self._track_color)
        track_pen = QPen(Qt.PenStyle.NoPen)

        p.setBrush(track_brush)
        p.setPen(track_pen)
        p.setOpacity(track_opacity)

        p.drawRoundedRect(margin, margin, width, height, track_radius, track_radius)

        # Calculate thumb position
        total_offset = width - 2 * thumb_radius
        offset = total_offset * self.offset

        # Draw thumb
        p.setBrush(QBrush(self._thumb_active_color if checked else self._thumb_color))
        p.setPen(QPen(Qt.PenStyle.NoPen))
        p.setOpacity(1.0)

        p.drawEllipse(
            QPointF(margin + thumb_radius + offset, margin + height / 2),
            thumb_radius,
            thumb_radius,
        )

        # Draw pulse if animating
        if self._pulse_radius > 0:
            p.setBrush(QBrush(QColor(0, 0, 0, 0)))
            p.setPen(QPen(QColor(0, 0, 0, 0)))
            p.setOpacity(0.1)

            p.drawEllipse(
                QPointF(margin + thumb_radius + offset, margin + height / 2),
                self._pulse_radius,
                self._pulse_radius,
            )

        p.end()

    # Property animations
    @pyqtProperty(float)
    def offset(self):
        return self._offset

    @offset.setter
    def offset(self, value):
        self._offset = value
        self.update()

    @pyqtProperty(float)
    def pulse_radius(self):
        return self._pulse_radius

    @pulse_radius.setter
    def pulse_radius(self, value):
        self._pulse_radius = value
        self.update()

    # Override the change event to handle state changes
    def mouseReleaseEvent(self, e):
        super().mouseReleaseEvent(e)

        if self.isEnabled():
            if self.isChecked():
                self.animation.setStartValue(0)
                self.animation.setEndValue(1)
            else:
                self.animation.setStartValue(1)
                self.animation.setEndValue(0)

            self.animations_group.start()
tools\_internal\dlss_updater\main_ui\main_window.py
from .. import __version__, resource_path
from ..utils import update_dlss_versions, extract_game_name, DLL_TYPE_MAP
import os
from PyQt6.QtCore import Qt, QUrl, QSize, QTimer, pyqtSignal
from PyQt6.QtGui import QDesktopServices, QIcon
from PyQt6.QtWidgets import (
    QMainWindow,
    QWidget,
    QVBoxLayout,
    QSplitter,
    QPushButton,
    QFileDialog,
    QHBoxLayout,
    QLabel,
    QMenu,
    QDialog,
    QTextBrowser,
    QScrollArea,
    QListWidget,
    QListWidgetItem,
    QDialogButtonBox,
    QFrame,
    QProgressBar,
    QSizePolicy,
    QMessageBox,
    QCheckBox,
)
from dlss_updater.lib.threading_lib import ThreadManager
from pathlib import Path
from dlss_updater.config import config_manager, LauncherPathName
from dlss_updater.logger import add_qt_handler, LoggerWindow, setup_logger
from dlss_updater.whitelist import get_all_blacklisted_games
from dlss_updater.main_ui.animated_toggle import AnimatedToggle


class LoadingOverlay(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        # Make this widget transparent
        self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
        self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)

        # Create a semi-transparent background
        self.setStyleSheet("background-color: rgba(0, 0, 0, 120);")

        # Add a progress bar
        layout = QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)

        container = QWidget()
        container_layout = QVBoxLayout(container)

        self.progress_bar = QProgressBar()
        self.progress_bar.setRange(0, 0)  # Indeterminate progress
        self.progress_bar.setFixedSize(250, 20)
        self.progress_bar.setStyleSheet(
            """
            QProgressBar {
                border: 1px solid #5A5A5A;
                border-radius: 5px;
                background-color: #3C3C3C;
                text-align: center;
            }
            QProgressBar::chunk {
                background-color: #2D6E88;
                width: 10px;
                margin: 0.5px;
            }
        """
        )

        self.label = QLabel("Processing...")
        self.label.setStyleSheet(
            "color: white; background-color: transparent; font-size: 14px; padding: 10px;"
        )

        container_layout.addWidget(self.label, 0, Qt.AlignmentFlag.AlignCenter)
        container_layout.addWidget(self.progress_bar, 0, Qt.AlignmentFlag.AlignCenter)

        # Center the container
        container.setStyleSheet(
            "background-color: #2E2E2E; border-radius: 10px; padding: 20px;"
        )
        container.setFixedSize(300, 100)

        layout.addWidget(container, 0, Qt.AlignmentFlag.AlignCenter)

        # Hide by default
        self.hide()

    def showEvent(self, event):
        # Position the overlay to cover the entire parent widget
        if self.parent():
            self.setGeometry(self.parent().rect())
        super().showEvent(event)

        # Manual fade in using timers but with smoother transition
        self.setWindowOpacity(0.0)
        for i in range(11):  # Keep original steps for stability
            opacity = i / 10.0
            QTimer.singleShot(i * 25, lambda op=opacity: self.setWindowOpacity(op))

    def set_message(self, message):
        self.label.setText(message)

    def hideWithAnimation(self):
        # Manual fade out using timers
        for i in range(11):  # Keep original steps for stability
            opacity = 1.0 - (i / 10.0)
            QTimer.singleShot(i * 25, lambda op=opacity: self.setWindowOpacity(op))

        # Hide when fully transparent
        QTimer.singleShot(300, self.hide)


class NotificationWidget(QWidget):
    """A notification widget with proper positioning and animations"""

    def __init__(self, message, parent=None):
        super().__init__(parent)
        # Setup widget
        self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.Tool)
        self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
        self.setStyleSheet("background-color: transparent;")

        # Main layout
        layout = QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)

        # Notification label
        self.label = QLabel(message)
        self.label.setStyleSheet(
            """
            background-color: #2D6E88; 
            color: white; 
            border-radius: 8px; 
            padding: 10px 20px;
            font: bold 12px;
        """
        )
        self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)

        layout.addWidget(self.label)

        # Size and position - now handled by the parent positioning method
        self.adjustSize()

    def showEvent(self, event):
        super().showEvent(event)

        # Keep original animation timing for stability
        self.setWindowOpacity(0.0)
        for i in range(11):
            opacity = i / 10.0
            QTimer.singleShot(i * 25, lambda op=opacity: self.setWindowOpacity(op))

        # Schedule fade out
        QTimer.singleShot(2000, self.start_fade_out)

    def start_fade_out(self):
        # Keep original animation timing for stability
        for i in range(11):
            opacity = 1.0 - (i / 10.0)
            QTimer.singleShot(i * 25, lambda op=opacity: self.setWindowOpacity(op))

        # Close when fully transparent
        QTimer.singleShot(300, self.close)


class MainWindow(QMainWindow):
    # Define signals at the class level
    resized = pyqtSignal()

    def __init__(self, logger=None):
        super().__init__()
        self.thread_manager = ThreadManager(self)
        self.button_enum_dict = {}
        self.setWindowTitle("DLSS-Updater")
        self.setGeometry(100, 100, 700, 500)  # Starting size
        self.setMinimumSize(700, 500)  # Minimum allowed size

        # FIX: Limit maximum height more strictly to prevent excessive vertical space.
        # In theory no longer needed, but kept here just in case.
        # self.setMaximumSize(1200, 650)  # Reduced maximum height from 800 to 650

        # Control variables used to keep track of app state.
        self.logger_expanded = False
        self.original_width = None
        self.current_button_style = None

        # FIX: Keep track of active notifications for repositioning
        self.active_notifications = []

        # Load and set the window icon
        logo_path = resource_path(os.path.join("icons", "dlss_updater.png"))
        logo_icon = QIcon(logo_path)
        self.setWindowIcon(logo_icon)

        # Main container
        self.main_container = QWidget()
        main_layout = QVBoxLayout()
        main_layout.setSpacing(10)  # Consistent spacing
        main_layout.setContentsMargins(10, 10, 10, 10)  # Consistent margins
        header_layout = QHBoxLayout()

        main_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
        # Header section with welcome, logo, version, and other buttons
        header_left = QHBoxLayout()
        welcome_label = QLabel("Welcome to the GUI :) -Deco")
        version_label = QLabel(f"v{__version__}")
        welcome_label.setStyleSheet(
            "color: white; font-size: 16px; background-color: transparent;"
        )
        version_label.setStyleSheet(
            "color: #888888; font-size: 12px; margin-left: 8px; background-color: transparent;"
        )
        header_left.addWidget(welcome_label)
        header_left.addStretch()
        header_left.addWidget(version_label)

        # Add the header layout to the main layout
        header_layout.addLayout(header_left)
        main_layout.addLayout(header_layout)

        # Add the DLSS Updater logo as a separate element
        self.logo_label = QLabel()
        self.logo_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        main_layout.addWidget(self.logo_label)

        # Custom folders info
        info_label = QLabel(
            "Note: You can now use the custom folder buttons below to add up to 4 additional game folders."
        )
        info_label.setWordWrap(True)
        info_label.setStyleSheet(
            "color: white; background-color: #3C3C3C; padding: 10px; border-radius: 4px; border: none;"
        )
        main_layout.addWidget(info_label)

        # Donate, report a bug, contact, release notes, and view logs buttons
        button_layout = QHBoxLayout()
        button_layout.setSpacing(5)  # Consistent spacing
        donate_button = self.create_styled_button(
            "☕ Support Development", "heart.png", "Support the development"
        )
        donate_button.clicked.connect(
            lambda: QDesktopServices.openUrl(QUrl("https://buymeacoffee.com/decouk"))
        )

        report_bug_button = self.create_styled_button(
            "🐛 Report a Bug", "bug.png", "Report a bug"
        )
        report_bug_button.clicked.connect(
            lambda: QDesktopServices.openUrl(
                QUrl("https://github.com/Recol/DLSS-Updater/issues")
            )
        )

        contact_button = self.create_styled_button(
            "📞 Contact", "contact.png", "Contact the developer"
        )
        contact_menu = QMenu()
        twitter_action = contact_menu.addAction("Twitter")
        discord_action = contact_menu.addAction("Discord")
        twitter_action.triggered.connect(
            lambda: QDesktopServices.openUrl(QUrl("https://x.com/iDeco_UK"))
        )
        discord_action.triggered.connect(
            lambda: QDesktopServices.openUrl(
                QUrl("https://discord.com/users/162568099839606784")
            )
        )
        contact_button.setMenu(contact_menu)

        release_notes_button = self.create_styled_button(
            "📝 Release Notes", "notes.png", "View release notes"
        )
        release_notes_button.clicked.connect(self.show_release_notes)

        view_logs_button = self.create_styled_button(
            "📋 View Logs", "logs.png", "View application logs"
        )
        view_logs_button.clicked.connect(self.toggle_logger_window)

        # Add Blacklist Manager button
        blacklist_button = self.create_styled_button(
            "⚙ Manage Blacklist", "settings.png", "Manage blacklisted games"
        )
        blacklist_button.clicked.connect(self.show_blacklist_manager)
        self.preferences_button = self.create_styled_button("⚙ Update Preferences", "settings.png", "Configure which technologies to update")
        self.preferences_button.clicked.connect(self.show_update_preferences)
        # Add hover effect to buttons
        for btn in [
            donate_button,
            report_bug_button,
            contact_button,
            release_notes_button,
            view_logs_button,
            blacklist_button,
            self.preferences_button,
        ]:
            self.add_button_hover_effect(btn)

        button_layout.addWidget(donate_button)
        button_layout.addWidget(report_bug_button)
        button_layout.addWidget(contact_button)
        button_layout.addWidget(release_notes_button)
        button_layout.addWidget(view_logs_button)
        button_layout.addWidget(blacklist_button)
        button_layout.addWidget(self.preferences_button)
        main_layout.addLayout(button_layout)

        # Original logger splitter setup
        self.logger_splitter = QSplitter(Qt.Orientation.Horizontal)
        self.can_continue = False
        self.button_list = []
        self.path_list = []

        # Launcher buttons setup
        self.setup_launcher_buttons()

        # Create QTextBrowser widget
        self.logger_window = LoggerWindow(self)

        # Set up splitter layout
        self.logger_splitter.addWidget(self.browse_buttons_container_widget)
        self.logger_splitter.addWidget(self.logger_window)
        # We want the logger_window to be collapsed by default
        self.logger_splitter.setSizes([1, 0])

        main_layout.addWidget(self.logger_splitter)
        self.main_container.setLayout(main_layout)
        self.main_container.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding)
        self.logger_splitter.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding)
        self.setCentralWidget(self.main_container)

        # Create the loading overlay
        self.loading_overlay = LoadingOverlay(self.main_container)
        self.loading_overlay.hide()

        # Set up logging
        self.logger = logger or setup_logger()
        add_qt_handler(self.logger, self.logger_window)
        self.logger_window.signals.error.connect(self.expand_logger_window)

        # Connect the update button to the threaded update function
        self.start_update_button.clicked.connect(self.call_threaded_update)

        # Connect resize event to handle notification repositioning
        self.resized.connect(self.reposition_notifications)

        self.apply_dark_theme()

    def resizeEvent(self, event):
        """Handle window resize events"""
        super().resizeEvent(event)
        # Emit the custom resize signal
        self.resized.emit()

        # Update overlay position if it's visible
        if hasattr(self, "loading_overlay") and self.loading_overlay.isVisible():
            self.loading_overlay.setGeometry(self.main_container.rect())

    def reposition_notifications(self):
        """Reposition all active notifications when window size changes"""
        for notification in self.active_notifications[:]:
            if notification.isVisible():
                self.position_notification(notification)
            else:
                # Be careful with list modification during iteration
                try:
                    self.active_notifications.remove(notification)
                except ValueError:
                    # Notification might have been removed already
                    pass

    def position_notification(self, notification):
        """Position a notification properly within the window bounds"""
        if notification and notification.isVisible() and notification.parent():
            notification.adjustSize()
            parent_rect = self.rect()
            notification_width = notification.width()
            notification_height = notification.height()

            # Calculate position - centered horizontally, near bottom vertically
            x = max(0, (parent_rect.width() - notification_width) // 2)
            y = max(0, parent_rect.height() - notification_height - 30)

            # Ensure notification stays within parent bounds
            x = min(x, parent_rect.width() - notification_width)
            y = min(y, parent_rect.height() - notification_height)

            notification.move(x, y)

    def add_button_hover_effect(self, button):
        """Add hover effect to button while preserving original styling"""
        # Store the button's original style
        original_style = button.styleSheet()

        # Check if this is a custom folder button (blue background) or regular button (gray background)
        is_custom = (
            "background-color: #2D6E88" in original_style
            or "background-color: #2D5A88" in original_style
        )

        # Create hover style by changing only the background color while preserving other styles
        if is_custom:
            original_bg = (
                "#2D6E88"
                if "background-color: #2D6E88" in original_style
                else "#2D5A88"
            )
            hover_bg = "#367FA3" if original_bg == "#2D6E88" else "#366BA3"
            hover_style = original_style.replace(
                f"background-color: {original_bg}", f"background-color: {hover_bg}"
            )
        else:
            hover_style = original_style.replace(
                "background-color: #4D4D4D", "background-color: #5A5A5A"
            )

        # Store original event handlers
        original_enter = button.enterEvent
        original_leave = button.leaveEvent

        # Define new event handlers
        def new_enter_event(event):
            self.current_button_style = button.styleSheet()
            button.setStyleSheet(hover_style)
            if original_enter:
                original_enter(event)

        def new_leave_event(event):
            if self.current_button_style:
                button.setStyleSheet(self.current_button_style)
            if original_leave:
                original_leave(event)

        # Override event handlers
        button.enterEvent = new_enter_event
        button.leaveEvent = new_leave_event

    def show_blacklist_manager(self):
        """Show dialog to manage blacklisted games"""
        dialog = QDialog(self)
        dialog.setWindowTitle("Manage Blacklisted Games")
        dialog.setMinimumWidth(500)
        dialog.setMinimumHeight(400)

        layout = QVBoxLayout()

        info_label = QLabel(
            "Select games to ignore in the blacklist. Selected games will be updated even if they're in the blacklist."
        )
        info_label.setWordWrap(True)
        info_label.setStyleSheet(
            "margin-bottom: 10px; background-color: transparent; border: none;"
        )
        layout.addWidget(info_label)

        # Create a list widget with animated toggles for blacklisted games
        game_list = QListWidget()
        game_list.setStyleSheet("background-color: #3C3C3C;")

        # Get all blacklisted games
        blacklisted_games = get_all_blacklisted_games()
        blacklisted_games = sorted(blacklisted_games)  # Sort alphabetically

        # Get currently skipped games
        skipped_games = config_manager.get_all_blacklist_skips()

        # Store toggle widgets to access them later
        toggle_widgets = []

        for game in blacklisted_games:
            item = QListWidgetItem()
            item_widget = QWidget()
            item_layout = QHBoxLayout(item_widget)
            item_layout.setContentsMargins(5, 2, 5, 2)

            game_label = QLabel(game)
            game_label.setStyleSheet("color: white; background: transparent;")

            # Create animated toggle
            toggle = AnimatedToggle()
            toggle.setChecked(game in skipped_games)
            toggle.game_name = game  # Store game name with the toggle
            toggle_widgets.append(toggle)

            item_layout.addWidget(game_label)
            item_layout.addStretch()
            item_layout.addWidget(toggle)

            item.setSizeHint(item_widget.sizeHint())
            game_list.addItem(item)
            game_list.setItemWidget(item, item_widget)

        layout.addWidget(game_list)

        # Add Select All and Deselect All buttons
        selection_layout = QHBoxLayout()
        select_all_button = QPushButton("Select All")
        select_all_button.clicked.connect(
            lambda: self.toggle_all_toggles(toggle_widgets, True)
        )

        deselect_all_button = QPushButton("Deselect All")
        deselect_all_button.clicked.connect(
            lambda: self.toggle_all_toggles(toggle_widgets, False)
        )

        # Add hover effect to buttons
        self.add_button_hover_effect(select_all_button)
        self.add_button_hover_effect(deselect_all_button)

        selection_layout.addWidget(select_all_button)
        selection_layout.addWidget(deselect_all_button)
        layout.addLayout(selection_layout)

        # Add OK and Cancel buttons
        button_box = QDialogButtonBox(
            QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
        )
        button_box.accepted.connect(dialog.accept)
        button_box.rejected.connect(dialog.reject)
        layout.addWidget(button_box)

        dialog.setLayout(layout)

        # Process the result if accepted
        if dialog.exec() == QDialog.DialogCode.Accepted:
            # Clear existing skips
            config_manager.clear_all_blacklist_skips()

            # Add new skips
            for toggle in toggle_widgets:
                if toggle.isChecked():
                    config_manager.add_blacklist_skip(toggle.game_name)

            self.logger.info("Updated blacklist skip settings")

            # Show notification
            self.show_notification("Blacklist settings updated!")

    def toggle_all_toggles(self, toggles, state):
        """Toggle all toggle switches to the given state"""
        for toggle in toggles:
            toggle.setChecked(state)

    def safe_disconnect(self, signal, slot):
        """Safely disconnect a signal from a slot if connected"""
        try:
            if signal and slot:
                signal.disconnect(slot)
        except Exception:
            # Ignore any disconnect errors
            pass

    def show_notification(self, message, duration=2000):
        """Show a floating notification message properly positioned"""
        notification = NotificationWidget(message, self)

        # Add to active notifications list
        self.active_notifications.append(notification)

        # Position notification
        self.position_notification(notification)

        # Show notification
        notification.show()

        # Remove from active notifications after it's closed
        QTimer.singleShot(
            duration + 300, lambda: self._remove_notification(notification)
        )

    def _remove_notification(self, notification):
        """Safely remove a notification from the active list"""
        if notification in self.active_notifications:
            try:
                self.active_notifications.remove(notification)
            except ValueError:
                # Already removed
                pass

    def show_update_preferences(self):
        """Show dialog to configure update preferences"""
        dialog = QDialog(self)
        dialog.setWindowTitle("Update Preferences")
        dialog.setMinimumWidth(400)
        
        layout = QVBoxLayout()
        
        info_label = QLabel(
            "Select which technologies you want to update:"
        )
        info_label.setWordWrap(True)
        info_label.setStyleSheet(
            "margin-bottom: 10px; background-color: transparent; border: none;"
        )
        layout.addWidget(info_label)
        
        # Create checkboxes
        self.dlss_checkbox = QCheckBox("DLSS (Deep Learning Super Sampling)")
        self.dlss_checkbox.setChecked(config_manager.get_update_preference("DLSS"))
        
        self.ds_checkbox = QCheckBox("DirectStorage")
        self.ds_checkbox.setChecked(config_manager.get_update_preference("DirectStorage"))
        
        self.xess_checkbox = QCheckBox("XeSS (Intel Xe Super Sampling)")
        self.xess_checkbox.setChecked(config_manager.get_update_preference("XeSS"))
        
        # Apply styling to checkboxes
        checkbox_style = """
            QCheckBox {
                color: white;
                background-color: transparent;
                padding: 5px;
                font-size: 14px;
            }
            QCheckBox::indicator {
                width: 16px;
                height: 16px;
            }
            QCheckBox::indicator:unchecked {
                border: 1px solid #7F7F7F;
                background-color: #3C3C3C;
            }
            QCheckBox::indicator:checked {
                border: 1px solid #2D6E88;
                background-color: #2D6E88;
            }
        """
        self.dlss_checkbox.setStyleSheet(checkbox_style)
        self.ds_checkbox.setStyleSheet(checkbox_style)
        self.xess_checkbox.setStyleSheet(checkbox_style)
        
        # Add checkboxes to layout
        layout.addWidget(self.dlss_checkbox)
        layout.addWidget(self.ds_checkbox)
        layout.addWidget(self.xess_checkbox)
        
        # Add description of each technology
        help_text = QTextBrowser()
        help_text.setMaximumHeight(150)
        help_text.setHtml("""
            <p><b>DLSS</b>: NVIDIA Deep Learning Super Sampling technology improves performance while maintaining high image quality. Updates DLLs:</p>
            <ul>
                <li>nvngx_dlss.dll</li>
                <li>nvngx_dlssg.dll</li>
                <li>nvngx_dlssd.dll</li>
            </ul>
            <p><b>DirectStorage</b>: Microsoft's DirectStorage API accelerates game loading times and texture streaming. Updates DLLs:</p>
            <ul>
                <li>dstorage.dll</li>
                <li>dstoragecore.dll</li>
            </ul>
            <p><b>XeSS</b>: Intel's Xe Super Sampling technology provides performance improvements similar to DLSS for all GPU brands.</p>
        """)
        help_text.setStyleSheet("background-color: #3C3C3C; color: white; border: 1px solid #555;")
        layout.addWidget(help_text)
        
        # Add note about requiring at least one selection
        note_label = QLabel("Note: At least one technology must be selected for updates to function.")
        note_label.setWordWrap(True)
        note_label.setStyleSheet("color: #AAAAAA; font-style: italic; margin-top: 10px;")
        layout.addWidget(note_label)
        
        # Add buttons
        button_box = QDialogButtonBox(
            QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
        )
        button_box.accepted.connect(lambda: self.validate_preferences(dialog))
        button_box.rejected.connect(dialog.reject)
        layout.addWidget(button_box)
        
        dialog.setLayout(layout)
        dialog.exec()

    def validate_preferences(self, dialog):
        """Validate that at least one preference is selected"""
        if not (self.dlss_checkbox.isChecked() or self.ds_checkbox.isChecked() or self.xess_checkbox.isChecked()):
            # Show warning if no technologies selected
            warning_dialog = QMessageBox(self)
            warning_dialog.setIcon(QMessageBox.Icon.Warning)
            warning_dialog.setWindowTitle("Warning")
            warning_dialog.setText("At least one technology must be selected.")
            warning_dialog.setStandardButtons(QMessageBox.StandardButton.Ok)
            warning_dialog.exec()
        else:
            # Save preferences and close dialog
            config_manager.set_update_preference("DLSS", self.dlss_checkbox.isChecked())
            config_manager.set_update_preference("DirectStorage", self.ds_checkbox.isChecked())
            config_manager.set_update_preference("XeSS", self.xess_checkbox.isChecked())
            
            self.logger.info("Updated technology preferences")
            self.show_notification("Update preferences saved!")
            dialog.accept()

    def show_release_notes(self):
        """Display release notes in a dialog"""
        release_notes_file = Path(resource_path("release_notes.txt"))
        if release_notes_file.exists():
            with open(release_notes_file, "r") as file:
                notes = file.read()
                dialog = QDialog(self)
                dialog.setWindowTitle("Release Notes")
                layout = QVBoxLayout()
                text_browser = QTextBrowser()
                text_browser.setPlainText(notes)
                text_browser.setStyleSheet("background-color: #3C3C3C; color: white;")
                layout.addWidget(text_browser)
                dialog.setLayout(layout)
                dialog.resize(500, 400)

                # Simple fade-in effect with opacity
                dialog.setWindowOpacity(0.0)
                dialog.show()

                # Gradually increase opacity - keep original timing for stability
                for i in range(11):
                    opacity = i / 10.0
                    QTimer.singleShot(
                        i * 25, lambda op=opacity: dialog.setWindowOpacity(op)
                    )

                dialog.exec()

    def reset_path(self):
        """Reset the associated launcher path"""
        reset_button = self.sender()
        launcher_button = reset_button.property("reset_button")
        if launcher_button:
            launcher_enum = self.button_enum_dict.get(launcher_button.objectName())
            if launcher_enum:
                config_manager.reset_launcher_path(launcher_enum)
                launcher_button.setText(launcher_button.objectName())
                self.logger.info(f"Reset path for {launcher_button.objectName()}")

                # Show notification
                self.show_notification(f"Reset path for {launcher_button.objectName()}")

    def expand_logger_window(self):
        """Increase app window size and expands the logger window. Used only for errors."""
        if self.logger_expanded:
            return

        self.original_width = self.width()
        target_width = min(int(self.width() * 1.4), self.maximumWidth())

        # Set the splitter sizes directly
        self.logger_splitter.setSizes([target_width // 2, target_width // 2])
        self.logger_expanded = True

    def toggle_logger_window(self):
        """Toggle logger window with proper sizing constraints"""
        try:
            if self.logger_expanded:
                # Collapse logger
                self.logger_splitter.setSizes([self.original_width, 0])
                self.logger_expanded = False
                return

            # Store original width before expanding
            self.original_width = self.width()
            target_width = min(int(self.width() * 1.4), self.maximumWidth())

            # Expand logger
            self.logger_splitter.setSizes([target_width // 2, target_width // 2])
            self.logger_expanded = True

        except Exception as e:
            self.logger.error(f"Error toggling logger window: {e}")

    def create_styled_button(
        self, text: str, icon_path: str, tooltip: str = ""
    ) -> QPushButton:
        """
        Creates styled buttons with the specific icon and tooltip.
        @param text: Text to be displayed.
        @param icon_path: Path to the icon.
        @param tooltip: Tooltip on hover. Optional.
        @return: QPushButton Created button.
        """
        button = QPushButton(f"  {text}", self)

        # Load and process icon
        icon = QIcon(resource_path(os.path.join("icons", icon_path)))
        button.setIcon(icon)
        button.setIconSize(QSize(24, 24))  # Consistent icon size

        # Set fixed height for uniformity
        button.setMinimumHeight(40)

        # Set alignment and size policy
        button.setStyleSheet(button.styleSheet() + "text-align: left;")
        button.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)

        if tooltip:
            button.setToolTip(tooltip)

        # Connect to browse functionality if not the update button
        if "Update" not in text:
            if "Custom Folder" in text or any(
                launcher in text
                for launcher in [
                    "Steam",
                    "EA",
                    "Ubisoft",
                    "Epic",
                    "GOG",
                    "Battle.net",
                    "Xbox",
                ]
            ):
                button.clicked.connect(self.browse_folder)

        return button

    def setup_launcher_buttons(self):
        """Setups the launcher buttons."""
        # Create launcher buttons
        self.steam_text_browser = self.create_styled_button(
            "Steam Games", "steam.png", "Select Steam game locations"
        )
        self.ea_text_browser = self.create_styled_button(
            "EA Games", "ea.png", "Select EA game locations"
        )
        self.ubisoft_text_browser = self.create_styled_button(
            "Ubisoft Games", "ubisoft.png", "Select Ubisoft game locations"
        )
        self.epic_text_browser = self.create_styled_button(
            "Epic Games", "epic.png", "Select Epic game locations"
        )
        self.gog_text_browser = self.create_styled_button(
            "GOG Games", "gog.png", "Select GOG game locations"
        )
        self.battlenet_text_browser = self.create_styled_button(
            "Battle.net Games", "battlenet.png", "Select Battle.net game locations"
        )
        self.xbox_text_browser = self.create_styled_button(
            "Xbox Games", "xbox.png", "Select Xbox game locations"
        )

        # Add custom folder buttons
        self.custom1_text_browser = self.create_styled_button(
            "Custom Folder 1", "folder.png", "Select custom game location 1"
        )
        self.custom2_text_browser = self.create_styled_button(
            "Custom Folder 2", "folder.png", "Select custom game location 2"
        )
        self.custom3_text_browser = self.create_styled_button(
            "Custom Folder 3", "folder.png", "Select custom game location 3"
        )
        self.custom4_text_browser = self.create_styled_button(
            "Custom Folder 4", "folder.png", "Select custom game location 4"
        )

        # Update button with special styling
        self.start_update_button = self.create_styled_button(
            "Start Update", "update.png", "Start DLSS update process"
        )

        # Set object names for identification
        self.steam_text_browser.setObjectName("Steam")
        self.ea_text_browser.setObjectName("EA")
        self.ubisoft_text_browser.setObjectName("UBISOFT")
        self.epic_text_browser.setObjectName("EPIC")
        self.gog_text_browser.setObjectName("GOG")
        self.battlenet_text_browser.setObjectName("BATTLENET")
        self.xbox_text_browser.setObjectName("XBOX")
        self.custom1_text_browser.setObjectName("CUSTOM1")
        self.custom2_text_browser.setObjectName("CUSTOM2")
        self.custom3_text_browser.setObjectName("CUSTOM3")
        self.custom4_text_browser.setObjectName("CUSTOM4")

        # Store buttons in list
        self.button_list = [
            self.steam_text_browser,
            self.ea_text_browser,
            self.ubisoft_text_browser,
            self.epic_text_browser,
            self.gog_text_browser,
            self.battlenet_text_browser,
            self.xbox_text_browser,
            self.custom1_text_browser,
            self.custom2_text_browser,
            self.custom3_text_browser,
            self.custom4_text_browser,
        ]

        # Update button dictionary
        self.button_enum_dict.update(
            {
                "Steam": LauncherPathName.STEAM,
                "EA": LauncherPathName.EA,
                "UBISOFT": LauncherPathName.UBISOFT,
                "EPIC": LauncherPathName.EPIC,
                "GOG": LauncherPathName.GOG,
                "BATTLENET": LauncherPathName.BATTLENET,
                "XBOX": LauncherPathName.XBOX,
                "CUSTOM1": LauncherPathName.CUSTOM1,
                "CUSTOM2": LauncherPathName.CUSTOM2,
                "CUSTOM3": LauncherPathName.CUSTOM3,
                "CUSTOM4": LauncherPathName.CUSTOM4,
            }
        )

        # Create layout for buttons with reset buttons
        browse_buttons_layout = QVBoxLayout()
        browse_buttons_layout.setSpacing(5)  # Consistent spacing
        browse_buttons_layout.setContentsMargins(5, 5, 5, 5)  # Consistent margins

        # Create a separator for original launchers and custom folders
        def create_separator(text):
            # Create a horizontal line with label - fixed styling
            container = QWidget()
            separator_layout = QHBoxLayout(container)
            separator_layout.setContentsMargins(0, 10, 0, 5)

            # Create label with better styling
            label = QLabel(text)
            label.setStyleSheet(
                "color: white; background-color: transparent; border: none;"
            )
            label.setMaximumWidth(150)  # Limit label width

            # Create lines
            left_line = QFrame()
            left_line.setFrameShape(QFrame.Shape.HLine)
            left_line.setFrameShadow(QFrame.Shadow.Sunken)
            left_line.setStyleSheet("background-color: #5A5A5A; border: none;")

            right_line = QFrame()
            right_line.setFrameShape(QFrame.Shape.HLine)
            right_line.setFrameShadow(QFrame.Shadow.Sunken)
            right_line.setStyleSheet("background-color: #5A5A5A; border: none;")

            # Add everything to layout
            separator_layout.addWidget(left_line)
            separator_layout.addWidget(label, 0, Qt.AlignmentFlag.AlignCenter)
            separator_layout.addWidget(right_line)

            return container

        # Add built-in launchers label
        browse_buttons_layout.addWidget(create_separator("Game Launchers"))

        # Add standard launcher buttons
        for button in self.button_list[:7]:  # First 7 are standard launchers
            button_row = QHBoxLayout()
            button_row.addWidget(button, stretch=1)

            # Create reset button
            reset_button = QPushButton()
            reset_button.setIcon(
                QIcon(resource_path(os.path.join("icons", "reset.png")))
            )
            reset_button.setIconSize(QSize(16, 16))
            reset_button.setFixedSize(24, 24)
            reset_button.setToolTip("Reset path")
            reset_button.setProperty("reset_button", button)
            reset_button.clicked.connect(self.reset_path)
            reset_button.setStyleSheet(
                """
                QPushButton {
                    background-color: #4D4D4D;
                    border: 1px solid #7F7F7F;
                    border-radius: 4px;
                    padding: 2px;
                    margin: 2px;
                }
                QPushButton:hover {
                    background-color: #5A5A5A;
                }
                QPushButton:pressed {
                    background-color: #444444;
                }
            """
            )

            # Add hover effect to reset button
            self.add_button_hover_effect(reset_button)

            button_row.addWidget(reset_button)
            browse_buttons_layout.addLayout(button_row)

        # Add custom folders label
        browse_buttons_layout.addWidget(create_separator("Custom Folders"))

        # Add custom folder buttons
        for button in self.button_list[7:]:  # Last 4 are custom folders
            button_row = QHBoxLayout()
            button_row.addWidget(button, stretch=1)

            # Create reset button
            reset_button = QPushButton()
            reset_button.setIcon(
                QIcon(resource_path(os.path.join("icons", "reset.png")))
            )
            reset_button.setIconSize(QSize(16, 16))
            reset_button.setFixedSize(24, 24)
            reset_button.setToolTip("Reset path")
            reset_button.setProperty("reset_button", button)
            reset_button.clicked.connect(self.reset_path)
            reset_button.setStyleSheet(
                """
                QPushButton {
                    background-color: #4D4D4D;
                    border: 1px solid #7F7F7F;
                    border-radius: 4px;
                    padding: 2px;
                    margin: 2px;
                }
                QPushButton:hover {
                    background-color: #5A5A5A;
                }
                QPushButton:pressed {
                    background-color: #444444;
                }
            """
            )

            # Add hover effect to reset button
            self.add_button_hover_effect(reset_button)

            button_row.addWidget(reset_button)
            browse_buttons_layout.addLayout(button_row)

        # Add update button separator and button
        browse_buttons_layout.addWidget(create_separator("Update"))
        browse_buttons_layout.addWidget(self.start_update_button)

        # Add hover effects to all buttons
        for button in self.button_list:
            self.add_button_hover_effect(button)

        # Add special hover effect to update button
        self.add_button_hover_effect(self.start_update_button)

        # Create a scrollable area for the buttons
        scroll_area = QScrollArea()
        scroll_area.setWidgetResizable(True)
        scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
        scroll_area.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding)

        self.browse_buttons_container_widget = QWidget()
        self.browse_buttons_container_widget.setLayout(browse_buttons_layout)

        # Set size policy to ensure proper expansion
        self.browse_buttons_container_widget.setSizePolicy(
            QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding
        )

        scroll_area.setWidget(self.browse_buttons_container_widget)
        self.browse_buttons_container_widget = scroll_area

    def call_threaded_update(self):
        """Start the update process in a separate thread."""
        try:
            # Disable the button immediately to prevent multiple clicks
            self.start_update_button.setEnabled(False)
            self.logger.info("Starting update process in thread...")

            # Show loading overlay
            self.loading_overlay.set_message("Starting update process...")
            self.loading_overlay.setGeometry(
                self.main_container.rect()
            )  # Ensure correct position
            self.loading_overlay.show()

            # Clear any previous signal connections
            if self.thread_manager.signals:
                try:
                    # Disconnect previous connections if they exist
                    self.thread_manager.signals.finished.disconnect()
                    self.thread_manager.signals.result.disconnect()
                    self.thread_manager.signals.error.disconnect()
                except TypeError:
                    # Ignore errors if signals were not connected
                    pass

            # Assign the update function
            self.thread_manager.assign_function(update_dlss_versions)

            # Connect new signals
            self.thread_manager.signals.finished.connect(self.handle_update_finished)
            self.thread_manager.signals.result.connect(self.handle_update_result)
            self.thread_manager.signals.error.connect(self.handle_update_error)

            # Run the thread
            self.thread_manager.run()

            # Simulate progress updates with timer (since we don't have real progress data)
            self.progress_messages = [
                "Scanning for DLLs...",
                "Looking for DLSS files...",
                "Checking XeSS files...",
                "Creating backups...",
                "Updating DLL files...",
                "Finalizing updates...",
            ]
            self.message_index = 0

            # Keep original timer interval for stability
            self.progress_timer = QTimer()
            self.progress_timer.timeout.connect(self.update_loading_message)
            self.progress_timer.start(800)  # Original timing: 800ms

        except Exception as e:
            self.logger.error(f"Error starting update thread: {e}")
            import traceback

            self.logger.error(traceback.format_exc())
            # Ensure button is re-enabled in case of an error
            self.start_update_button.setEnabled(True)
            self.loading_overlay.hideWithAnimation()
            if hasattr(self, "progress_timer") and self.progress_timer.isActive():
                self.progress_timer.stop()

    def update_loading_message(self):
        """Update the loading message with next progress text"""
        if hasattr(self, "message_index") and self.message_index < len(
            self.progress_messages
        ):
            self.loading_overlay.set_message(self.progress_messages[self.message_index])
            self.message_index += 1
        else:
            # Reset to first message if we've gone through all messages
            self.message_index = 0

    def handle_update_error(self, error):
        """
        Handle errors from the update thread.
        @param error: The error from the update thread.
        """
        exctype, value, tb = error
        self.logger.error(f"Error: {exctype}")
        self.logger.error(f"Value: {value}")
        self.logger.error(f"Traceback: {tb}")
        self.start_update_button.setEnabled(True)

        # Hide loading overlay
        self.loading_overlay.hideWithAnimation()

        # Stop timer for progress messages
        if hasattr(self, "progress_timer") and self.progress_timer.isActive():
            self.progress_timer.stop()

        # Show error notification
        self.show_notification(f"Error: {value}", 5000)

    def handle_update_result(self, result):
        """
        Handle results from the update thread.
        @param result: Tuple containing (success, updated_games, skipped_games, successful_backups)
        """
        try:
            # Stop timer for progress messages
            if hasattr(self, "progress_timer") and self.progress_timer.isActive():
                self.progress_timer.stop()

            # Hide loading overlay with animation
            self.loading_overlay.hideWithAnimation()

            if isinstance(result, tuple) and len(result) == 4:
                success, updated_games, skipped_games, successful_backups = result
                if success:
                    self.logger.info("Update process completed successfully")

                    # Show success notification
                    if updated_games:
                        self.show_notification(
                            f"Update completed: {len(updated_games)} games updated",
                            3000,
                        )
                    else:
                        self.show_notification(
                            "Update completed: No games needed updates", 3000
                        )

                    self.show_update_summary(
                        (updated_games, skipped_games, successful_backups)
                    )
                else:
                    self.logger.error("Update process failed")
                    self.show_notification("Update process failed", 3000)
            else:
                self.logger.error(f"Unexpected result format: {result}")
                self.show_notification(
                    "Update process returned unexpected result", 3000
                )
        except Exception as e:
            self.logger.error(f"Error handling update result: {e}")
            import traceback

            self.logger.error(traceback.format_exc())
            self.show_notification(f"Error: {str(e)}", 3000)
        finally:
            self.start_update_button.setEnabled(True)

    def handle_update_finished(self):
        """Handle completion of the update thread."""
        try:
            self.logger.debug("Update thread finished")
            self.start_update_button.setEnabled(True)
            # Clean up worker reference
            self._current_worker = None
        except Exception as e:
            self.logger.error(f"Error in update finished handler: {e}")

    def closeEvent(self, event):
        """Handle application close event."""
        try:
            if self.thread_manager and self.thread_manager.current_worker:
                self.thread_manager.waitForDone()
        except Exception as e:
            self.logger.error(f"Error during cleanup: {e}")
        super().closeEvent(event)

    def get_current_settings(self):
        """Get the current settings from the settings file."""
        # Standard launchers
        steam_path = config_manager.check_path_value(LauncherPathName.STEAM)
        ea_path = config_manager.check_path_value(LauncherPathName.EA)
        ubisoft_path = config_manager.check_path_value(LauncherPathName.UBISOFT)
        epic_path = config_manager.check_path_value(LauncherPathName.EPIC)
        gog_path = config_manager.check_path_value(LauncherPathName.GOG)
        battlenet_path = config_manager.check_path_value(LauncherPathName.BATTLENET)
        xbox_path = config_manager.check_path_value(LauncherPathName.XBOX)

        # Custom paths
        custom1_path = config_manager.check_path_value(LauncherPathName.CUSTOM1)
        custom2_path = config_manager.check_path_value(LauncherPathName.CUSTOM2)
        custom3_path = config_manager.check_path_value(LauncherPathName.CUSTOM3)
        custom4_path = config_manager.check_path_value(LauncherPathName.CUSTOM4)

        self.path_list = [
            steam_path,
            ea_path,
            ubisoft_path,
            epic_path,
            gog_path,
            battlenet_path,
            xbox_path,
            custom1_path,
            custom2_path,
            custom3_path,
            custom4_path,
        ]

        for i, button in enumerate(self.button_list):
            if i < len(self.path_list) and self.path_list[i]:
                button.setText(self.path_list[i])

    def browse_folder(self):
        """Open a dialog to select a directory."""
        directory = QFileDialog.getExistingDirectory(self, "Select Folder")
        if directory:
            directory = directory.replace("/", "\\")
            self.sender().setText(directory)
            config_manager.update_launcher_path(
                self.button_enum_dict.get(self.sender().objectName()), directory
            )

            # Show notification
            self.show_notification(f"Folder path updated!")

    def show_update_summary(self, update_result):
        """Display the update summary in a message box."""
        updated_games, skipped_games, successful_backups = update_result

        # Create a custom dialog for better styling
        dialog = QDialog(self)
        dialog.setWindowTitle("DLSS Updater - Update Summary")
        dialog.setMinimumWidth(500)
        dialog.setMinimumHeight(300)

        layout = QVBoxLayout(dialog)

        text_browser = QTextBrowser()
        text_browser.setStyleSheet(
            "background-color: #3C3C3C; color: white; border: 1px solid #555;"
        )

        summary_text = ""
        if updated_games:
            summary_text += "Games updated successfully:\n"
            for dll_path, launcher, dll_type in updated_games:
                game_name = extract_game_name(dll_path, launcher)
                summary_text += f" - {game_name} - {launcher} ({dll_type})\n"
        else:
            summary_text += "No games were updated.\n"

        if successful_backups:
            summary_text += "\nSuccessful backups:\n"
            for dll_path, backup_path in successful_backups:
                game_name = extract_game_name(dll_path, "Unknown")
                dll_type = DLL_TYPE_MAP.get(
                    Path(dll_path).name.lower(), "Unknown DLL type"
                )
                summary_text += f" - {game_name}: {backup_path} ({dll_type})\n"
        else:
            summary_text += "\nNo backups were created.\n"

        if skipped_games:
            summary_text += "\nGames skipped:\n"
            for dll_path, launcher, reason, dll_type in skipped_games:
                game_name = extract_game_name(dll_path, launcher)
                summary_text += (
                    f" - {game_name} - {launcher} ({dll_type}) (Reason: {reason})\n"
                )

        text_browser.setPlainText(summary_text)
        layout.addWidget(text_browser)

        # Add close button
        button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok)
        button_box.accepted.connect(dialog.accept)
        layout.addWidget(button_box)

        # Simple fade-in effect with opacity
        dialog.setWindowOpacity(0.0)
        dialog.show()

        # Keep original animation timing for stability
        for i in range(11):
            opacity = i / 10.0
            QTimer.singleShot(i * 25, lambda op=opacity: dialog.setWindowOpacity(op))

        dialog.exec()

    def apply_dark_theme(self):
        """Apply a dark theme using stylesheets."""
        dark_stylesheet = """
            QMainWindow, QDialog {
                background-color: #2E2E2E; /* Dark background */
                color: #FFFFFF; /* White text */
            }
            QPushButton {
                background-color: #4D4D4D; /* Button background */
                color: #FFFFFF; /* Button text color */
                border: 1px solid #7F7F7F; /* Button border */
                padding: 5px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #5A5A5A; /* Button hover effect */
            }
            QPushButton:pressed {
                background-color: #444444; /* Button press effect */
            }
            QPushButton:disabled {
                background-color: #3D3D3D; /* Disabled button */
                color: #888888;
            }
            QTextBrowser {
                background-color: #3C3C3C; /* Text browser background */
                color: #FFFFFF; /* Text color */
                border: 1px solid #7F7F7F; /* Text browser border */
            }
            QMenu {
                background-color: #3C3C3C;
                color: #FFFFFF;
                border: 1px solid #7F7F7F;
            }
            QMenu::item:selected {
                background-color: #5A5A5A;
            }
            QScrollArea {
                background-color: #2E2E2E;
                border: none;
            }
            QLabel {
                color: #FFFFFF;
                background-color: transparent;
                border: none;
            }
            QCheckBox {
                color: #FFFFFF;
                background-color: transparent;
                border: none;
            }
            QListWidget {
                background-color: #3C3C3C;
                color: #FFFFFF;
                border: 1px solid #7F7F7F;
            }
            QListWidget::item {
                background-color: #3C3C3C;
            }
            QListWidget::item:hover {
                background-color: #444444;
            }
            QFrame {
                background-color: transparent;
                border: none;
            }
            QSplitter::handle {
                background-color: #5A5A5A;
            }
            QMessageBox {
                background-color: #2E2E2E;
                color: #FFFFFF;
            }
            QMessageBox QLabel {
                color: #FFFFFF;
                background-color: transparent;
                border: none;
            }
            QDialogButtonBox {
                background-color: transparent;
                border: none;
            }
            QScrollBar:vertical {
                border: none;
                background: #3C3C3C;
                width: 10px;
                margin: 0px;
            }
            QScrollBar::handle:vertical {
                background: #5A5A5A;
                min-height: 20px;
                border-radius: 5px;
            }
            QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
                height: 0px;
            }
            QProgressBar {
                border: 1px solid #5A5A5A;
                border-radius: 5px;
                background-color: #3C3C3C;
                text-align: center;
                color: white;
            }
            QProgressBar::chunk {
                background-color: #2D6E88;
                width: 10px;
                margin: 0.5px;
            }
        """
        self.setStyleSheet(dark_stylesheet)

        # Apply consistent styling to all launcher buttons
        button_style = """
                                QPushButton {
                                    background-color: #4D4D4D;
                                    color: white;
                                    border: 1px solid #7F7F7F;
                                    border-radius: 4px;
                                    padding: 8px 16px;
                                    text-align: left;
                                    margin: 2px 0px;
                                }
                                QPushButton:hover {
                                    background-color: #5A5A5A;
                                    border-color: #999999;
                                }
                                QPushButton:pressed {
                                    background-color: #444444;
                                }
                                QPushButton:disabled {
                                    background-color: #3D3D3D;
                                    color: #888888;
                                }
                            """

        for button in self.button_list[:7]:  # Apply to launcher buttons
            button.setStyleSheet(button_style)

        # Apply special styling to custom folder buttons
        custom_button_style = """
                                QPushButton {
                                    background-color: #2D6E88;
                                    color: white;
                                    border: 1px solid #7F7F7F;
                                    border-radius: 4px;
                                    padding: 8px 16px;
                                    text-align: left;
                                    margin: 2px 0px;
                                }
                                QPushButton:hover {
                                    background-color: #367FA3;
                                    border-color: #999999;
                                }
                                QPushButton:pressed {
                                    background-color: #245D73;
                                }
                                QPushButton:disabled {
                                    background-color: #1D3D5A;
                                    color: #888888;
                                }
                            """

        for button in self.button_list[7:]:  # Apply to custom folder buttons
            button.setStyleSheet(custom_button_style)

        self.start_update_button.setStyleSheet(
            """
                    QPushButton {
                        background-color: #2D5A88;
                        color: white;
                        border: 1px solid #7F7F7F;
                        border-radius: 4px;
                        padding: 8px 16px;
                        text-align: left;
                        font-weight: bold;
                        margin: 2px 0px;
                    }
                    QPushButton:hover {
                        background-color: #366BA3;
                        border-color: #999999;
                    }
                    QPushButton:pressed {
                        background-color: #244B73;
                    }
                    QPushButton:disabled {
                        background-color: #1D3D5A;
                        color: #888888;
                    }
                """
        )
tools\_internal\dlss_updater\scanner.py
import os
from pathlib import Path
from .config import LauncherPathName, config_manager
from .whitelist import is_whitelisted
from .constants import DLL_GROUPS
import asyncio
from dlss_updater.logger import setup_logger
import sys

logger = setup_logger()


def get_steam_install_path():
    try:
        if config_manager.check_path_value(LauncherPathName.STEAM):
            path = config_manager.check_path_value(LauncherPathName.STEAM)
            # Remove \steamapps\common if it exists
            path = path.replace("\\steamapps\\common", "")
            logger.debug(f"Using configured Steam path: {path}")
            return path

        import winreg

        key = winreg.OpenKey(
            winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\WOW6432Node\Valve\Steam"
        )
        value, _ = winreg.QueryValueEx(key, "InstallPath")
        config_manager.update_launcher_path(LauncherPathName.STEAM, str(value))
        return value
    except (FileNotFoundError, ImportError) as e:
        logger.debug(f"Could not find Steam install path: {e}")
        return None


def get_steam_libraries(steam_path):
    logger.debug(f"Looking for Steam libraries in: {steam_path}")
    library_folders_path = Path(steam_path) / "steamapps" / "libraryfolders.vdf"
    logger.debug(f"Checking libraryfolders.vdf at: {library_folders_path}")

    if not library_folders_path.exists():
        default_path = Path(steam_path) / "steamapps" / "common"
        logger.debug(
            f"libraryfolders.vdf not found, using default path: {default_path}"
        )
        return [default_path]

    libraries = []
    with library_folders_path.open("r", encoding="utf-8") as file:
        lines = file.readlines()
        for line in lines:
            if "path" in line:
                path = line.split('"')[3]
                library_path = Path(path) / "steamapps" / "common"
                logger.debug(f"Found Steam library: {library_path}")
                libraries.append(library_path)

    logger.debug(f"Found total Steam libraries: {len(libraries)}")
    for lib in libraries:
        logger.debug(f"Steam library path: {lib}")
    return libraries


async def find_dlls(library_paths, launcher_name, dll_names):
    """Find DLLs from a filtered list of DLL names"""
    dll_paths = []
    logger.debug(f"Searching for DLLs in {launcher_name}")

    for library_path in library_paths:
        logger.debug(f"Scanning directory: {library_path}")
        try:
            for root, _, files in os.walk(library_path):
                for dll_name in dll_names:
                    if dll_name.lower() in [f.lower() for f in files]:
                        dll_path = os.path.join(root, dll_name)
                        logger.debug(f"Found DLL: {dll_path}")
                        if not await is_whitelisted(dll_path):
                            logger.info(
                                f"Found non-whitelisted DLL in {launcher_name}: {dll_path}"
                            )
                            dll_paths.append(dll_path)
                        else:
                            logger.info(
                                f"Skipped whitelisted game in {launcher_name}: {dll_path}"
                            )
                await asyncio.sleep(0)
        except Exception as e:
            logger.error(f"Error scanning {library_path}: {e}")

    logger.debug(f"Found {len(dll_paths)} DLLs in {launcher_name}")
    return dll_paths


def get_user_input(prompt):
    user_input = input(prompt).strip()
    return None if user_input.lower() in ["n/a", ""] else user_input


async def get_ea_games():
    ea_path = config_manager.check_path_value(LauncherPathName.EA)
    if not ea_path or ea_path == "":
        return []
    ea_games_path = Path(ea_path)
    return [ea_games_path] if ea_games_path.exists() else []


def get_ubisoft_install_path():
    try:
        if config_manager.check_path_value(LauncherPathName.UBISOFT):
            return config_manager.check_path_value(LauncherPathName.UBISOFT)
        import winreg

        key = winreg.OpenKey(
            winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\WOW6432Node\Ubisoft\Launcher"
        )
        value, _ = winreg.QueryValueEx(key, "InstallDir")
        config_manager.update_launcher_path(LauncherPathName.UBISOFT, str(value))
        return value
    except (FileNotFoundError, ImportError):
        return None


async def get_ubisoft_games(ubisoft_path):
    ubisoft_games_path = Path(ubisoft_path) / "games"
    if not ubisoft_games_path.exists():
        return []
    return [ubisoft_games_path]


async def get_epic_games():
    epic_path = config_manager.check_path_value(LauncherPathName.EPIC)
    if not epic_path or epic_path == "":
        return []
    epic_games_path = Path(epic_path)
    return [epic_games_path] if epic_games_path.exists() else []


async def get_gog_games():
    gog_path = config_manager.check_path_value(LauncherPathName.GOG)
    if not gog_path or gog_path == "":
        return []
    gog_games_path = Path(gog_path)
    return [gog_games_path] if gog_games_path.exists() else []


async def get_battlenet_games():
    battlenet_path = config_manager.check_path_value(LauncherPathName.BATTLENET)
    if not battlenet_path or battlenet_path == "":
        return []
    battlenet_games_path = Path(battlenet_path)
    return [battlenet_games_path] if battlenet_games_path.exists() else []


async def get_xbox_games():
    xbox_path = config_manager.check_path_value(LauncherPathName.XBOX)
    if not xbox_path or xbox_path == "":
        return []
    xbox_games_path = Path(xbox_path)
    return [xbox_games_path] if xbox_games_path.exists() else []


async def get_custom_folder(folder_num):
    custom_path = config_manager.check_path_value(
        getattr(LauncherPathName, f"CUSTOM{folder_num}")
    )
    if not custom_path or custom_path == "":
        return []
    custom_path = Path(custom_path)
    return [custom_path] if custom_path.exists() else []


async def find_all_dlls():
    logger.info("Starting find_all_dlls function")
    all_dll_paths = {
        "Steam": [],
        "EA Launcher": [],
        "Ubisoft Launcher": [],
        "Epic Games Launcher": [],
        "GOG Launcher": [],
        "Battle.net Launcher": [],
        "Xbox Launcher": [],
        "Custom Folder 1": [],
        "Custom Folder 2": [],
        "Custom Folder 3": [],
        "Custom Folder 4": [],
    }

    # Get user preferences
    update_dlss = config_manager.get_update_preference("DLSS")
    update_ds = config_manager.get_update_preference("DirectStorage")
    update_xess = config_manager.get_update_preference("XeSS")

    # Build list of DLLs to search for based on preferences
    dll_names = []
    if update_dlss:
        dll_names.extend(DLL_GROUPS["DLSS"])
    if update_ds:
        dll_names.extend(DLL_GROUPS["DirectStorage"])
    if update_xess and DLL_GROUPS["XeSS"]:  # Only add if there are XeSS DLLs defined
        dll_names.extend(DLL_GROUPS["XeSS"])

    # Skip if no technologies selected
    if not dll_names:
        logger.info("No technologies selected for update, skipping scan")
        return all_dll_paths

    steam_path = get_steam_install_path()
    if steam_path:
        steam_libraries = get_steam_libraries(steam_path)
        all_dll_paths["Steam"] = await find_dlls(steam_libraries, "Steam", dll_names)

    ea_games = await get_ea_games()
    if ea_games:
        ea_dlls = await find_dlls(ea_games, "EA Launcher", dll_names)
        all_dll_paths["EA Launcher"].extend(ea_dlls)

    ubisoft_path = get_ubisoft_install_path()
    if ubisoft_path:
        ubisoft_games = await get_ubisoft_games(ubisoft_path)
        all_dll_paths["Ubisoft Launcher"] = await find_dlls(
            ubisoft_games, "Ubisoft Launcher", dll_names
        )

    epic_games = await get_epic_games()
    if epic_games:
        all_dll_paths["Epic Games Launcher"] = await find_dlls(
            epic_games, "Epic Games Launcher", dll_names
        )

    gog_games = await get_gog_games()
    if gog_games:
        all_dll_paths["GOG Launcher"] = await find_dlls(
            gog_games, "GOG Launcher", dll_names
        )

    battlenet_games = await get_battlenet_games()
    if battlenet_games:
        all_dll_paths["Battle.net Launcher"] = await find_dlls(
            battlenet_games, "Battle.net Launcher", dll_names
        )

    xbox_games = await get_xbox_games()
    if xbox_games:
        all_dll_paths["Xbox Launcher"] = await find_dlls(
            xbox_games, "Xbox Launcher", dll_names
        )

    # Add custom folders
    for i in range(1, 5):
        custom_folder = await get_custom_folder(i)
        if custom_folder:
            all_dll_paths[f"Custom Folder {i}"] = await find_dlls(
                custom_folder, f"Custom Folder {i}", dll_names
            )

    # Remove duplicates
    unique_dlls = set()
    for launcher in all_dll_paths:
        all_dll_paths[launcher] = [
            dll
            for dll in all_dll_paths[launcher]
            if str(dll) not in unique_dlls and not unique_dlls.add(str(dll))
        ]

    return all_dll_paths


def find_all_dlls_sync():
    """Synchronous wrapper for find_all_dlls to use in non-async contexts"""
    import asyncio

    try:
        # Use a new event loop to avoid conflicts with Qt
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        result = loop.run_until_complete(find_all_dlls())
        loop.close()
        return result
    except Exception as e:
        logger.error(f"Error in find_all_dlls_sync: {e}")
        import traceback

        logger.error(traceback.format_exc())
        # Return an empty dict as fallback
        return {
            "Steam": [],
            "EA Launcher": [],
            "Ubisoft Launcher": [],
            "Epic Games Launcher": [],
            "GOG Launcher": [],
            "Battle.net Launcher": [],
            "Xbox Launcher": [],
            "Custom Folder 1": [],
            "Custom Folder 2": [],
            "Custom Folder 3": [],
            "Custom Folder 4": [],
        }
tools\_internal\dlss_updater\updater.py
import os
import shutil
import pefile
from .config import LATEST_DLL_VERSIONS, LATEST_DLL_PATHS
from pathlib import Path
import stat
import time
import psutil
from packaging import version
from .logger import setup_logger
from .constants import DLL_TYPE_MAP

logger = setup_logger()


def parse_version(version_string):
    """
    Parse a version string into a format that can be compared.
    Handles both dot and comma-separated versions.
    """
    if not version_string:
        return version.parse("0.0.0")

    # Replace commas with dots
    cleaned_version = version_string.replace(",", ".")

    # Split by dots and take the first three components to standardize format
    components = cleaned_version.split(".")
    if len(components) >= 3:
        # Use the first three components for comparison
        standardized_version = ".".join(components[:3])
    else:
        # If less than three components, use what we have
        standardized_version = cleaned_version

    try:
        return version.parse(standardized_version)
    except Exception as e:
        logger.error(f"Error parsing version '{version_string}': {e}")
        # Return a very low version to encourage updates when parsing fails
        return version.parse("0.0.0")


def get_dll_version(dll_path):
    try:
        with open(dll_path, "rb") as file:
            pe = pefile.PE(data=file.read())
            for fileinfo in pe.FileInfo:
                for entry in fileinfo:
                    if hasattr(entry, "StringTable"):
                        for st in entry.StringTable:
                            for key, value in st.entries.items():
                                if key == b"FileVersion":
                                    return value.decode("utf-8").strip()
    except Exception as e:
        logger.error(f"Error reading version from {dll_path}: {e}")
    return None


def remove_read_only(file_path):
    if not os.access(file_path, os.W_OK):
        logger.info(f"Removing read-only attribute from {file_path}")
        os.chmod(file_path, stat.S_IWRITE)


def restore_permissions(file_path, original_permissions):
    os.chmod(file_path, original_permissions)


def is_file_in_use(file_path, timeout=5):
    start_time = time.time()
    while time.time() - start_time < timeout:
        try:
            with open(file_path, "rb"):
                return False
        except PermissionError:
            for proc in psutil.process_iter(["pid", "name", "open_files"]):
                try:
                    for file in proc.open_files():
                        if file.path == file_path:
                            logger.error(
                                f"File {file_path} is in use by process {proc.name()} (PID: {proc.pid})"
                            )
                            return True
                except (psutil.NoSuchProcess, psutil.AccessDenied):
                    pass
        time.sleep(0.1)
    logger.info(f"Timeout reached while checking if file {file_path} is in use")
    return True  # Assume file is NOT in use if we can't determine otherwise to prevent hanging conditions


def normalize_path(path):
    return os.path.normpath(path)


def create_backup(dll_path):
    backup_path = dll_path.with_suffix(".dlsss")
    try:
        logger.info(f"Attempting to create backup at: {backup_path}")
        if backup_path.exists():
            logger.info("Previous backup exists, removing...")
            try:
                os.chmod(backup_path, stat.S_IWRITE)
                os.remove(backup_path)
                logger.info("Successfully removed old backup")
            except Exception as e:
                logger.error(f"Failed to remove old backup: {e}")
                return None

        dir_path = os.path.dirname(backup_path)
        os.chmod(dir_path, stat.S_IWRITE | stat.S_IREAD | stat.S_IEXEC)

        shutil.copy2(dll_path, backup_path)

        if backup_path.exists():
            os.chmod(backup_path, stat.S_IWRITE | stat.S_IREAD)
            logger.info(f"Successfully created backup at: {backup_path}")
            return backup_path
        else:
            logger.error("Backup file not created")
            return None
    except Exception as e:
        logger.error(f"Failed to create backup for {dll_path}: {e}")
        logger.error(f"Error type: {type(e)}")
        return None


def update_dll(dll_path, latest_dll_path):
    dll_path = Path(normalize_path(dll_path)).resolve()
    latest_dll_path = Path(normalize_path(latest_dll_path)).resolve()
    logger.info(f"Checking DLL at {dll_path}...")

    dll_type = DLL_TYPE_MAP.get(dll_path.name.lower(), "Unknown DLL type")
    original_permissions = os.stat(dll_path).st_mode

    try:
        existing_version = get_dll_version(dll_path)
        latest_version = get_dll_version(latest_dll_path)

        if existing_version and latest_version:
            existing_parsed = parse_version(existing_version)
            latest_parsed = parse_version(latest_version)

            logger.info(
                f"Existing version: {existing_version}, Latest version: {latest_version}"
            )
            # Do not include FG/RR DLLs in the update check
            if dll_type == "nvngx_dlss.dll" and existing_parsed < parse_version(
                "2.0.0"
            ):
                logger.info(
                    f"Skipping update for {dll_path}: Version {existing_version} is less than 2.0.0 and cannot be updated."
                )
                return False, None, dll_type

            if existing_parsed >= latest_parsed:
                logger.info(
                    f"{dll_path} is already up-to-date (version {existing_version})."
                )
                return False, None, dll_type

        if not dll_path.exists():
            logger.error(f"Error: Target DLL path does not exist: {dll_path}")
            return False, None, dll_type

        if not latest_dll_path.exists():
            logger.error(f"Error: Latest DLL path does not exist: {latest_dll_path}")
            return False, None, dll_type

        if not os.access(dll_path.parent, os.W_OK):
            logger.error(
                f"Error: No write permission to the directory: {dll_path.parent}"
            )
            return False, None, dll_type

        backup_path = create_backup(dll_path)
        if not backup_path:
            return False, None, dll_type

        remove_read_only(dll_path)

        retry_count = 3
        while retry_count > 0:
            if not is_file_in_use(str(dll_path)):
                break
            logger.info(
                f"File is in use. Retrying in 2 seconds... (Attempts left: {retry_count})"
            )
            time.sleep(2)
            retry_count -= 1

        if retry_count == 0:
            logger.info(
                f"File {dll_path} is still in use after multiple attempts. Cannot update."
            )
            restore_permissions(dll_path, original_permissions)
            return False, None, dll_type

        try:
            os.remove(dll_path)
            shutil.copyfile(latest_dll_path, dll_path)
            restore_permissions(dll_path, original_permissions)

            # Verify update
            new_version = get_dll_version(dll_path)
            if new_version == latest_version:
                logger.info(
                    f"Successfully updated {dll_path} from version {existing_version} to {latest_version}."
                )
                return True, backup_path, dll_type
            else:
                logger.error(
                    f"Version verification failed - Expected: {latest_version}, Got: {new_version}"
                )
                return False, backup_path, dll_type

        except Exception as e:
            logger.error(f"File update operation failed: {e}")
            if backup_path and backup_path.exists():
                try:
                    shutil.copyfile(backup_path, dll_path)
                    logger.info("Restored backup after failed update")
                except Exception as restore_error:
                    logger.error(f"Failed to restore backup: {restore_error}")
            return False, backup_path, dll_type

    except Exception as e:
        logger.error(f"Error updating {dll_path}: {e}")
        restore_permissions(dll_path, original_permissions)
        return False, None, dll_type
tools\_internal\dlss_updater\utils.py
import os
import sys
from pathlib import Path
import ctypes
from dlss_updater.logger import setup_logger


logger = setup_logger()


try:
    from dlss_updater import (
        update_dll,
        is_whitelisted,
        __version__,
        LATEST_DLL_PATHS,
        DLL_TYPE_MAP,
        find_all_dlss_dlls,
        auto_update,
        resource_path,
    )
except ImportError as e:
    logger.error(f"Error importing dlss_updater modules: {e}")
    logger.error("Current sys.path:")
    for path in sys.path:
        logger.error(path)
    logger.error("\nCurrent directory contents:")
    for item in os.listdir():
        logger.error(item)
    logger.error("\ndlss_updater directory contents:")
    try:
        for item in os.listdir("dlss_updater"):
            logger.error(item)
    except FileNotFoundError:
        logger.error("dlss_updater directory not found")
    sys.exit(1)


def find_file_in_directory(directory, filename):
    for root, _, files in os.walk(directory):
        if filename in files:
            return os.path.join(root, filename)
    return None


def check_update_completion():
    update_log_path = os.path.join(os.path.dirname(sys.executable), "update_log.txt")
    if os.path.exists(update_log_path):
        with open(update_log_path, "r") as f:
            logger.info(f"Update completed: {f.read()}")
        os.remove(update_log_path)


def check_update_error():
    error_log_path = os.path.join(
        os.path.dirname(sys.executable), "update_error_log.txt"
    )
    if os.path.exists(error_log_path):
        with open(error_log_path, "r") as f:
            logger.error(f"Update error occurred: {f.read()}")
        os.remove(error_log_path)


def check_dependencies():
    try:
        from importlib.metadata import distributions

        required = {"pefile", "psutil"}
        installed = set()
        for dist in distributions():
            name = dist.metadata.get("Name")
            if name:
                installed.add(name.lower())
        missing = required - installed
        if missing:
            logger.info(f"Missing dependencies: {', '.join(missing)}")
            return False
        return True
    except ImportError:
        logger.error("Unable to check dependencies. Proceeding anyway.")
        return True


def run_as_admin():
    script = Path(sys.argv[0]).resolve()
    params = " ".join([str(script)] + sys.argv[1:])
    logger.info("Re-running script with admin privileges...")
    ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, params, None, 1)


def is_admin():
    try:
        return ctypes.windll.shell32.IsUserAnAdmin()
    except:
        return False


def extract_game_name(dll_path, launcher_name):
    parts = Path(dll_path).parts
    try:
        if "steamapps" in parts:
            return parts[parts.index("steamapps") + 2]
        elif "EA Games" in parts:
            return parts[parts.index("EA Games") + 1]
        elif "Ubisoft Game Launcher" in parts:
            return parts[parts.index("games") + 1]
        elif "Epic Games" in parts:
            return parts[parts.index("Epic Games") + 2]
        elif "GOG Galaxy" in parts:
            return parts[parts.index("Games") + 1]
        elif "Battle.net" in parts:
            return parts[parts.index("Battle.net") + 1]
        elif "Custom Path" in launcher_name:
            # For custom paths, try to get parent directory name
            return parts[-2]
        else:
            # If we can't determine the game name, use the parent directory name
            return parts[-2]
    except (ValueError, IndexError) as e:
        logger.error(
            f"Error extracting game name for {dll_path} in {launcher_name}: {e}"
        )
        return "Unknown Game"


def update_dlss_versions():
    logger.info(f"DLSS Updater version {__version__}")
    logger.info("Starting DLL search...")

    updated_games = []
    skipped_games = []
    successful_backups = []

    try:
        logger.info("Checking for updates...")
        if auto_update is None:
            logger.info("No updates were found.")
        else:
            try:
                update_available = auto_update()
                if update_available:
                    logger.info(
                        "The application will now close for the update. If the update does NOT automatically restart, please manually reboot it from the /update/ folder."
                    )
                    return True, [], [], []  # Early return for auto-update
            except Exception as e:
                logger.error(f"Error during update check: {e}")
                import traceback

                traceback.print_exc()

        try:
            # Use the synchronous version of find_all_dlls
            all_dll_paths = find_all_dlss_dlls()
            logger.info("DLL search completed.")
        except Exception as e:
            logger.error(f"Error finding DLLs: {e}")
            import traceback

            logger.error(traceback.format_exc())
            return False, [], [], []

        processed_dlls = set()

        if any(all_dll_paths.values()):
            logger.info("\nFound DLLs in the following launchers:")
            # Process each launcher
            for launcher, dll_paths in all_dll_paths.items():
                if dll_paths:
                    logger.info(f"{launcher}:")
                    for dll_path in dll_paths:
                        try:
                            dll_path = (
                                Path(dll_path)
                                if isinstance(dll_path, str)
                                else dll_path
                            )
                            if str(dll_path) not in processed_dlls:
                                result = process_single_dll(dll_path, launcher)
                                if result:
                                    success, backup_path, dll_type = result
                                    if success:
                                        logger.info(
                                            f"Successfully processed: {dll_path}"
                                        )
                                        updated_games.append(
                                            (str(dll_path), launcher, dll_type)
                                        )
                                        if backup_path:
                                            successful_backups.append(
                                                (str(dll_path), backup_path)
                                            )
                                    else:
                                        if backup_path:  # Attempted but failed
                                            skipped_games.append(
                                                (
                                                    str(dll_path),
                                                    launcher,
                                                    "Update failed",
                                                    dll_type,
                                                )
                                            )
                                        else:  # Skipped for other reasons
                                            skipped_games.append(
                                                (
                                                    str(dll_path),
                                                    launcher,
                                                    "Skipped",
                                                    dll_type,
                                                )
                                            )
                                processed_dlls.add(str(dll_path))
                        except Exception as e:
                            logger.error(f"Error processing DLL {dll_path}: {e}")
                            continue

            # Display summary after processing
            if updated_games:
                logger.info("\nGames updated successfully:")
                for dll_path, launcher, dll_type in updated_games:
                    game_name = extract_game_name(dll_path, launcher)
                    logger.info(f" - {game_name} - {launcher} ({dll_type})")
            else:
                logger.info("\nNo games were updated.")

            if successful_backups:
                logger.info("\nSuccessful backups:")
                for dll_path, backup_path in successful_backups:
                    game_name = extract_game_name(dll_path, "Unknown")
                    dll_type = DLL_TYPE_MAP.get(
                        Path(dll_path).name.lower(), "Unknown DLL type"
                    )
                    logger.info(f" - {game_name}: {backup_path} ({dll_type})")
            else:
                logger.info("\nNo backups were created.")

            if skipped_games:
                logger.info("\nGames skipped:")
                for dll_path, launcher, reason, dll_type in skipped_games:
                    game_name = extract_game_name(dll_path, launcher)
                    logger.info(
                        f" - {game_name} - {launcher} ({dll_type}) (Reason: {reason})"
                    )
        else:
            logger.info("No DLLs were found or processed.")

        return True, updated_games, skipped_games, successful_backups

    except Exception as e:
        import traceback

        trace = traceback.format_exc()
        logger.error(f"Critical error in update process: {e}")
        logger.error(f"Traceback:\n{trace}")
        return False, [], [], []


def process_single_dll(dll_path, launcher):
    """Process a single DLL file"""
    try:
        # Get the lowercase filename for consistency
        dll_name = dll_path.name.lower()

        # Directly check for known DLL types to avoid case sensitivity issues
        if "nvngx_dlss.dll" == dll_name:
            dll_type = "DLSS DLL"
        elif "nvngx_dlssg.dll" == dll_name:
            dll_type = "DLSS Frame Generation DLL"
        elif "nvngx_dlssd.dll" == dll_name:
            dll_type = "DLSS Ray Reconstruction DLL"
        elif "libxess.dll" == dll_name:
            dll_type = "XeSS DLL"
        elif "libxess_dx11.dll" == dll_name:
            dll_type = "XeSS DX11 DLL"
        elif "dstorage.dll" == dll_name:
            dll_type = "DirectStorage DLL"
        elif "dstoragecore.dll" == dll_name:
            dll_type = "DirectStorage Core DLL"
        else:
            dll_type = "Unknown DLL type"

        logger.info(f" - {dll_type}: {dll_path}")

        game_name = extract_game_name(str(dll_path), launcher)
        if "warframe" in game_name.lower():
            return None

        # Check if whitelisted
        import asyncio

        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        whitelisted = loop.run_until_complete(is_whitelisted(str(dll_path)))
        loop.close()

        if whitelisted:
            logger.debug(
                f"Game {game_name} is in whitelist, checking if it's on skip list..."
            )
            return False, None, dll_type

        if dll_name in LATEST_DLL_PATHS:
            latest_dll_path = LATEST_DLL_PATHS[dll_name]
            return update_dll(str(dll_path), latest_dll_path)

        return False, None, dll_type
    except Exception as e:
        logger.error(f"Error processing DLL {dll_path}: {e}")
        return False, None, "Error"


def display_update_summary(updated_games, skipped_games, successful_backups):
    """Display a summary of the update process"""
    logger.info("\nSummary:")
    if not (updated_games or skipped_games or successful_backups):
        logger.info("No DLLs were found or processed.")
        return

    if updated_games:
        logger.info("\nGames updated successfully:")
        for dll_path, launcher, dll_type in updated_games:
            game_name = extract_game_name(dll_path, launcher)
            logger.info(f" - {game_name} - {launcher} ({dll_type})")
    else:
        logger.info("\nNo games were updated.")

    if successful_backups:
        logger.info("\nSuccessful backups:")
        for dll_path, backup_path in successful_backups:
            game_name = extract_game_name(dll_path, "Unknown")
            dll_type = DLL_TYPE_MAP.get(Path(dll_path).name.lower(), "Unknown DLL type")
            logger.info(f" - {game_name}: {backup_path} ({dll_type})")
    else:
        logger.info("\nNo backups were created.")

    if skipped_games:
        logger.info("\nGames skipped:")
        for dll_path, launcher, reason, dll_type in skipped_games:
            game_name = extract_game_name(dll_path, launcher)
            logger.info(f" - {game_name} - {launcher} ({dll_type}) (Reason: {reason})")
tools\_internal\dlss_updater\version.py
__version__ = "2.7.2"
tools\_internal\dlss_updater\whitelist.py
import os
import csv
from io import StringIO
from urllib.request import urlopen
from urllib.error import URLError
from dlss_updater.logger import setup_logger
from dlss_updater.config import config_manager

logger = setup_logger()

WHITELIST_URL = (
    "https://raw.githubusercontent.com/Recol/DLSS-Updater-Whitelist/main/whitelist.csv"
)


def fetch_whitelist():
    try:
        with urlopen(WHITELIST_URL) as response:
            csv_data = StringIO(response.read().decode("utf-8"))
        reader = csv.reader(csv_data)
        return set(row[0].strip() for row in reader if row and row[0].strip())
    except URLError as e:
        logger.error(f"Failed to fetch whitelist: {e}")
        return set()
    except csv.Error as e:
        logger.error(f"Failed to parse whitelist CSV: {e}")
        return set()


WHITELISTED_GAMES = fetch_whitelist()


async def is_whitelisted(game_path):
    """
    Check if a game path matches any whitelisted games.
    Uses launcher pattern detection to find the actual game name.
    """
    logger.debug(f"Checking game against whitelist: {game_path}")

    # Extract path components
    path_parts = [p for p in game_path.split(os.path.sep) if p]

    # Skip if the path is too short
    if len(path_parts) < 3:
        return False

    # Look for known launcher patterns to identify the game directory
    game_dir = None

    # Epic Games pattern: <drive>:\Epic Games\<GameName>\...
    if "Epic Games" in path_parts:
        epic_index = path_parts.index("Epic Games")
        if epic_index + 1 < len(path_parts):
            game_dir = path_parts[epic_index + 1]

    # Steam pattern: <drive>:\<SteamLibrary>\steamapps\common\<GameName>\...
    elif "steamapps" in path_parts and "common" in path_parts:
        common_index = path_parts.index("common")
        if common_index + 1 < len(path_parts):
            game_dir = path_parts[common_index + 1]

    # EA Games pattern: <drive>:\EA Games\<GameName>\...
    elif "EA Games" in path_parts:
        ea_index = path_parts.index("EA Games")
        if ea_index + 1 < len(path_parts):
            game_dir = path_parts[ea_index + 1]

    # GOG pattern: <drive>:\GOG Games\<GameName>\... or <drive>:\GOG Galaxy\Games\<GameName>\...
    elif "GOG Games" in path_parts:
        gog_index = path_parts.index("GOG Games")
        if gog_index + 1 < len(path_parts):
            game_dir = path_parts[gog_index + 1]
    elif "GOG Galaxy" in path_parts and "Games" in path_parts:
        games_index = path_parts.index("Games")
        if games_index + 1 < len(path_parts):
            game_dir = path_parts[games_index + 1]

    # Ubisoft pattern: <drive>:\Ubisoft\Ubisoft Game Launcher\games\<GameName>\...
    elif "Ubisoft Game Launcher" in path_parts and "games" in path_parts:
        games_index = path_parts.index("games")
        if games_index + 1 < len(path_parts):
            game_dir = path_parts[games_index + 1]

    # Battle.net pattern: <drive>:\Battle.net\<GameName>\...
    elif "Battle.net" in path_parts:
        battlenet_index = path_parts.index("Battle.net")
        if battlenet_index + 1 < len(path_parts):
            game_dir = path_parts[battlenet_index + 1]

    # Xbox pattern: <drive>:\Xbox\<GameName>\...
    elif "Xbox" in path_parts:
        xbox_index = path_parts.index("Xbox")
        if xbox_index + 1 < len(path_parts):
            game_dir = path_parts[xbox_index + 1]

    # Fallback: Just use the parent directory if we couldn't identify the game
    if not game_dir:
        logger.debug(
            "Could not identify game from path patterns, using parent directory"
        )
        game_dir = path_parts[-2]

    logger.debug(f"Identified game directory: {game_dir}")

    # Check for skip list first
    for game in WHITELISTED_GAMES:
        if config_manager.is_blacklist_skipped(game):
            if game.lower() == game_dir.lower():
                logger.info(
                    f"Game '{game_dir}' is in whitelist but also in skip list - allowing update"
                )
                return False

    # Now check against whitelist
    for game in WHITELISTED_GAMES:
        # Skip if in skip list (already handled)
        if config_manager.is_blacklist_skipped(game):
            continue

        # Simple direct name comparison
        if game.lower() == game_dir.lower():
            logger.info(f"Whitelist match found: {game_dir}")
            return True

    logger.debug(f"No whitelist match found for: {game_dir}")
    return False


def get_all_blacklisted_games():
    """Return the list of all blacklisted games for UI display"""
    return list(WHITELISTED_GAMES)
tools\_internal\dlss_updater\__init__.py
from .scanner import get_steam_install_path, get_steam_libraries, find_dlls, find_all_dlls_sync
from .updater import update_dll
from .whitelist import is_whitelisted
from .version import __version__
from .config import resource_path, initialize_dll_paths
from .auto_updater import auto_update
from .logger import setup_logger
from .constants import DLL_TYPE_MAP
from .lib.threading_lib import ThreadManager, WorkerSignals

# We rename find_dlss_dlls to find_dlls and keep it for backward compatibility
find_dlss_dlls = find_dlls

# Let's export find_all_dlls_sync instead of the async version
find_all_dlss_dlls = find_all_dlls_sync

# Initialize DLL paths after imports are resolved
LATEST_DLL_PATHS = initialize_dll_paths()

__all__ = [
    "get_steam_install_path",
    "get_steam_libraries",
    "find_dlss_dlls",
    "find_all_dlss_dlls",
    "update_dll",
    "is_whitelisted",
    "__version__",
    "LATEST_DLL_PATHS", 
    "resource_path",
    "auto_update",
    "setup_logger",
    "DLL_TYPE_MAP",
    "ThreadManager",
    "WorkerSignals",
]
tools\_internal\icons\battlenet.png
 
tools\_internal\icons\dlss_updater.png
 
tools\_internal\icons\dlss_updater_full.png
 
tools\_internal\icons\ea.png
 
tools\_internal\icons\epic.png
 
tools\_internal\icons\gog.png
 
tools\_internal\icons\reset.png
 
tools\_internal\icons\steam.png
 
tools\_internal\icons\ubisoft.png
 
tools\_internal\icons\update.png
 
tools\_internal\icons\xbox.png
 
tools\_internal\libcrypto-3.dll
md5: AE5B2E9A3410839B31938F24B6FC5CD8 | sha1: 9F9A14EFC15C904F408A0D364D55A144427E4949 | sha256: CCFFFDDCD3DEFB8D899026298AF9AF43BC186130F8483D77E97C93233D5F27D7 | sha512: 36EA760A7B56EA74174882155EDDFB8726828240FCFC6B34D90ECDB7E50A7E632374DCBC9B2889081C0973CC51F50967E7D692498C4ABD1F2CBA3F7FE8D659CC
tools\_internal\libffi-8.dll
md5: 0F8E4992CA92BAAF54CC0B43AACCCE21 | sha1: C7300975DF267B1D6ADCBAC0AC93FD7B1AB49BD2 | sha256: EFF52743773EB550FCC6CE3EFC37C85724502233B6B002A35496D828BD7B280A | sha512: 6E1B223462DC124279BFCA74FD2C66FE18B368FFBCA540C84E82E0F5BCBEA0E10CC243975574FA95ACE437B9D8B03A446ED5EE0C9B1B094147CEFAF704DFE978
tools\_internal\libssl-3.dll
md5: 8D4805F0651186046C48D3E2356623DB | sha1: 18C27C000384418ABCF9C88A72F3D55D83BEDA91 | sha256: 007142039F04D04E0ED607BDA53DE095E5BC6A8A10D26ECEDDE94EA7D2D7EEFE | sha512: 1C4895D912F7085D6E46F6776034C9E3D8D7BF934BE858683BF6DEDB13ABCA360BA816D8A5528EC7A3AC6E33010FDB6FC89B2699B5CFEEDAABFDD5DF143DFFD1
tools\_internal\pefile-2023.2.7.dist-info\INSTALLER
 
tools\_internal\pefile-2023.2.7.dist-info\LICENSE
 
tools\_internal\pefile-2023.2.7.dist-info\METADATA
tools\_internal\pefile-2023.2.7.dist-info\RECORD
 
tools\_internal\pefile-2023.2.7.dist-info\REQUESTED
 
tools\_internal\pefile-2023.2.7.dist-info\top_level.txt
ordlookup
pefile
peutils
tools\_internal\pefile-2023.2.7.dist-info\WHEEL
 
tools\_internal\psutil-7.0.0.dist-info\INSTALLER
 
tools\_internal\psutil-7.0.0.dist-info\LICENSE
 
tools\_internal\psutil-7.0.0.dist-info\METADATA
tools\_internal\psutil-7.0.0.dist-info\RECORD
 
tools\_internal\psutil-7.0.0.dist-info\REQUESTED
 
tools\_internal\psutil-7.0.0.dist-info\top_level.txt
psutil
tools\_internal\psutil-7.0.0.dist-info\WHEEL
 
tools\_internal\psutil\_psutil_windows.pyd
md5: D30149D319EFCAECF0A5C5E71EF6CB39 | sha1: 99BEEB17BFC69E8370036F9457EDB4D6812B22E2 | sha256: 9C7FC855D9D1614E70705C7DCC6F4AC3CDCAB5ADFEB6A67D382F5ADE09EADC15 | sha512: B6FB265F0EFED56FDD3455ED620E1FB581D40D2B23B92544CCCBF331E30DC29592C4297E3FAAF437A9D1A33099E0B48D5B2344943FB7B581A448F6C5806ACEC6
tools\_internal\pyexpat.pyd
md5: 4E6DE7116D8C1C418080580C9795AC15 | sha1: BA948A3C17E12F113477639702A82E96298D1938 | sha256: 554BBC65BFE8C19BA9BBD94F18977A8131109C6A4D64306778BD12250C2C5C56 | sha512: 853E5CD9F753145CCE9DD22F6E6A6E404FEC7F0DB322D2DB4D7B18E9CFC065503BA4FAB4ADC33CBF7D1C2DC0D884413F73CBC28C290D5A41CE7F3F610DAD99BC
tools\_internal\PyQt6\Qt6\bin\MSVCP140.dll
md5: 01B946A2EDC5CC166DE018DBB754B69C | sha1: DBE09B7B9AB2D1A61EF63395111D2EB9B04F0A46 | sha256: 88F55D86B50B0A7E55E71AD2D8F7552146BA26E927230DAF2E26AD3A971973C5 | sha512: 65DC3F32FAF30E62DFDECB72775DF870AF4C3A32A0BF576ED1AAAE4B16AC6897B62B19E01DC2BF46F46FBE3F475C061F79CBE987EDA583FEE1817070779860E5
tools\_internal\PyQt6\Qt6\bin\MSVCP140_1.dll
md5: 0FE6D52EB94C848FE258DC0EC9FF4C11 | sha1: 95CC74C64AB80785F3893D61A73B8A958D24DA29 | sha256: 446C48C1224C289BD3080087FE15D6759416D64F4136ADDF30086ABD5415D83F | sha512: C39A134210E314627B0F2072F4FFC9B2CE060D44D3365D11D8C1FE908B3B9403EBDD6F33E67D556BD052338D0ED3D5F16B54D628E8290FD3A155F55D36019A86
tools\_internal\PyQt6\Qt6\bin\MSVCP140_2.dll
md5: 9002E0BEE6455B2322E3E717FE25F9BE | sha1: BC8DF83CC657F0F46A0BFF20565870A435ED1563 | sha256: 24B47C966B6E4A65B3E4DF866D347D3427E9BD709BE550C38224427EB5E143D3 | sha512: 28DDD087B48D5AA96EC39CCC29A4020CF75AE3C5CB6AF9A9571694D73F7AAA4FECB15336C9C7A7D12C93D8BF12EFA4FE4D8D612CD93D72C72130CAE52317D0D9
tools\_internal\PyQt6\Qt6\bin\opengl32sw.dll
md5: 83BBECF92FB68795A620B395998B131B | sha1: 026F9E87A5623FE9370C2EEDEF24C765F7312800 | sha256: B04DE4541863BC7D8879040A78889C4849C1B1DA2784C4630F734C146C2998CE | sha512: C63CA8863F63C8F415D685EFF991A1AA67E3457AC2B1F6524DB271C2986F7E79415F98F212E1F7CDD644F41BC48D558661E1136716D63F81675F664E53FDFC70
tools\_internal\PyQt6\Qt6\bin\Qt6Core.dll
md5: C9523863236A0125880654AC4440E16C | sha1: 0D5032EB2B0BEAC130EEC57337629A7BA941D6B2 | sha256: B95236241CE0B825857064A386B9A22B0F28FA24F3E246AD63528E87C2688B94 | sha512: E926C35109E6FCCA001367F0880658996A9252C5456487E8ECF766EA21934219E8FF22F26A54FC1882EAB39875228BD254E877EE5189DEA56101E4100FE91577
tools\_internal\PyQt6\Qt6\bin\Qt6Gui.dll
md5: 1487F1A29360F602863D829C5302854A | sha1: 73488033A373BB145DDA52D200FB46312A344EC8 | sha256: 8E81C9FAFD4711667CE81824DD9D54E128C562557A1AD5B247EAFB85E64A14E0 | sha512: D4BC15288CE37C3F94D44B8730909FAE5D3CDF0963A630B7A1AF183273A4ADB2337F931D1EE68C24F43CD1353FA9E25561B180843AC5F29DABAB580A571461E7
tools\_internal\PyQt6\Qt6\bin\Qt6Network.dll
md5: 0A637D5E4D04462F5851DB72DACAFAB1 | sha1: 5AE85F101F1D603FA00E16CB4F24FEC6F6969237 | sha256: F1A184C8FFB602A1550BE4CA0BA06CD97551920B5E802B4FFA74587BF3A550BB | sha512: D86D45F2F6BD40F3C927A2FFBD8F6744F176ED7507C84C81DF4939E0A458831B9EACE737B54D5A268416887E74C130F110689858950240555B9CDA3899720BD6
tools\_internal\PyQt6\Qt6\bin\Qt6Pdf.dll
md5: 0AF3578D91EEFDEFEEBBC89E9456F3A4 | sha1: E45CA7646A123D5FBB890357A375DC89F2AA4D5B | sha256: 1CC89C072C34CD28F8612EFD7A3BF866900B74585405FE590539D5894C7C3401 | sha512: 9AC7B9B5D55E4F511EFCA34C70108E6BFD86099A52528ECC3CC83973EF0AC2DDF3C095D18D66B0EC46A6C3C9B554C9D083CC50B2DF573E36F90E287E4523062C
tools\_internal\PyQt6\Qt6\bin\Qt6Svg.dll
md5: 5642F7BAEF071E135D7D009A75D472C5 | sha1: 41FFEA0200298425F6120777B39A985276DCC668 | sha256: ACAAA68BF7DC518B4DAA17D92A4E7AD4FCE3B0A8EF7E6E4D2C587C56D44B1DA4 | sha512: 99DFE44F68C51E35465915CBCDD2692B5D5645FA6C39C0BFEECEB2F2C3CEED690BE0311E5DFAE3CC49C5C27D7618ED229B0EAB7302BA8BC190BC7C4421DCB219
tools\_internal\PyQt6\Qt6\bin\Qt6Widgets.dll
md5: 5D1A41D396398382630992CCE49D2D8C | sha1: 32D60B6B78A7E3AC5C0717C662729AB40DDDFF7D | sha256: E7CA7A7095204C9C527ACA6D849A3560A292549FAFC595D63FFCE79D2E5F7331 | sha512: C0AC8AF433756A5F00905A656D6C827F19316DEA5070DA4F99FD1E86F3634B099C5F8CD9091160CC92979233BA6450185485A3741AEA1B6AC3BF859A45A47B98
tools\_internal\PyQt6\Qt6\bin\VCRUNTIME140.dll
md5: 971DBBE854FC6AB78C095607DFAD7B5C | sha1: 1731FB947CD85F9017A95FDA1DC5E3B0F6B42CA2 | sha256: 5E197A086B6A7711BAA09AFE4EA7C68F0E777B2FF33F1DF25A21F375B7D9693A | sha512: B966AAB9C0D9459FADA3E5E96998292D6874A7078924EA2C171F0A1A50B0784C24CC408D00852BEC48D6A01E67E41D017684631176D3E90151EC692161F1814D
tools\_internal\PyQt6\Qt6\bin\VCRUNTIME140_1.dll
md5: 6BC084255A5E9EB8DF2BCD75B4CD0777 | sha1: CF071AD4E512CD934028F005CABE06384A3954B6 | sha256: 1F0F5F2CE671E0F68CF96176721DF0E5E6F527C8CA9CFA98AA875B5A3816D460 | sha512: B822538494D13BDA947655AF791FED4DAA811F20C4B63A45246C8F3BEFA3EC37FF1AA79246C89174FE35D76FFB636FA228AFA4BDA0BD6D2C41D01228B151FD89
tools\_internal\PyQt6\Qt6\plugins\generic\qtuiotouchplugin.dll
md5: FED07A9B7E62FCC655F510169F03F3CD | sha1: A917D6D573D0B8BE802F82C6091960DA983C87E9 | sha256: 34D08A36CC2F2AD6128DD19FDE16EC54166C5C44DF023A79879C5DC841894729 | sha512: F582370497893C9FAFB421B67B6865E3D5B1D280FB6934629E37386117A5FC1CD020CAD7573E31D5A50D2BBE566C3C9D13DFFEBD36EE396E9BF3923C67F194C9
tools\_internal\PyQt6\Qt6\plugins\iconengines\qsvgicon.dll
md5: FB8ABEFE7FC7C583A142A4921A889D32 | sha1: 1C605D8BD84CD5FFD18B24E943476A254BFF0668 | sha256: BF0B233139ECF92C7BC1F2327F91E4C7936E873CD458994EB23E8B05DF69AAB4 | sha512: 80CCE310DAAC448EB6FE091FA990600B9523AA65DA027A4884A4A92887B578B1C6B2DFA34C45A90B1ADB465EB1374249831E71E9702151227DB009CA8EC0D63C
tools\_internal\PyQt6\Qt6\plugins\imageformats\qgif.dll
md5: 848F16245B325FE16214D5A1F1B62C74 | sha1: DD64CE996AE05EA55D3BD8EA01B76A56CA8A5844 | sha256: 68E8A89B3E717674A25C1FC0E6E36C5DA6CDD69D87E00996726F06B2BB90088C | sha512: 408CE093B85C8C4D56ED769903EDED058C1EB3A8282C96FBBC56BF500AF3BD0D949C2C709FB5526BAAFBB08594307C9F0CE3B9A4EE1BB4C3A20B0826252CDFC3
tools\_internal\PyQt6\Qt6\plugins\imageformats\qicns.dll
md5: AFB4933B85924615485FE8CD56C697F6 | sha1: 9F02603CC6A237925790D25F4986CCFCDEAD2C4E | sha256: 0DAC8FE7C7781EFF6414D0B9C6DCB951AB24DF25A35A667B8280FCBEEF9F3B82 | sha512: FAF4EECE905155D82BE1A63B57FAE36BCE5475C89DD73427C75139FA0986A3A317B954455B696F086926FD4982F802671E011E1AB5EA3589C9F91684ACF7F51F
tools\_internal\PyQt6\Qt6\plugins\imageformats\qico.dll
md5: 45C73D3F132424AAFF08BAA431D957DF | sha1: 448B889B4FDD95701D19F87AFEA0BCCCDE217811 | sha256: E1F90F7E760083C40014B72F5E1731B044EC9F881EC240C5B96469F9468EE729 | sha512: F5CB4DC640F0810FECF1C36313DEF60519331B518D36FB5D1097E38A5F8B61222D44785DB7A9EA6B244935864796678995BE139D020AB6D3D7616104C47242ED
tools\_internal\PyQt6\Qt6\plugins\imageformats\qjpeg.dll
md5: 26A61892F91351BC42C8F23937B60006 | sha1: 07EA4037D6303C4FF1FDBD522C159D0317383DCA | sha256: D047B52D2EA32F46DCD45D782C3D63E0F192BB1B3E262718A0DE12DF943F3420 | sha512: AA680A09FD2389B939069A8105AABD102EEE306C683BAD4F9BF8EF4032AE8D4B51226CD325B8648752D1691F2BF4D7139159295077F6CCD469A972C4B4BE7D1E
tools\_internal\PyQt6\Qt6\plugins\imageformats\qpdf.dll
md5: 0F03D80F018A3FF5212C3DBE86DE41D0 | sha1: DEB4A2618B249DEF9E7ECE9A4B4573A5BE09859C | sha256: 245516C0CFB12D28638C68E0E297E653BC69A3CABB94748DC3FA1AEA67593C7C | sha512: 524B3C01641E7D36B2C2D96A517D65C8C0E0C98D59E8301D0110500956526D27BB88C3677E1121EA80D942114C687B7D048FF28786AA230AFB11B626A182BEA5
tools\_internal\PyQt6\Qt6\plugins\imageformats\qsvg.dll
md5: 8FF515FE132D355FCCB4693D163B596B | sha1: 21FD63913E633FE3685C30D74DFA37ADAC7ADDD1 | sha256: 0FC3F0BC1EE332197EF2FF8F37ADA1D78A3FB298A46A75344B54035CFA1058E8 | sha512: ED6049EB75116DDC880E9444F132511947055BBDFBBC3DAB17048B6E618529CA8BBE61F7B13B50E773898AA5508DF7225841B414903052BD244811F98A39EB55
tools\_internal\PyQt6\Qt6\plugins\imageformats\qtga.dll
md5: 8130AA72D7918FEC0454488637EEF942 | sha1: E0985D25443CE3C0D66D062EFABA659A67A6E4AA | sha256: A781C0FB9AED52BA8EB38D68687818EA0763277F510CFB68D0DCD5EA6BF4E507 | sha512: FDE0261ABFC46D99220AA4F794357A2595BA63978051FAF66C926DDCF0253782D8EC13EF4568C7C8D6E2E30C3671B664AC0D99A9C7431ABF5C380C4DE0F81C67
tools\_internal\PyQt6\Qt6\plugins\imageformats\qtiff.dll
md5: 31BD978BE967359C1BEFA2171C5051AE | sha1: 9759D00F0D68D06FAF3E62264A6A364374CDE7AD | sha256: 830E97751D3D19FD7CBDCB9BA83F9FD72CED43421609D7B4EDF05207BDC294DC | sha512: F223360DFA28B9333FDE0E93D6DA3E715B674CB6C78813B33AF46B5AD2DDEAEE08747C2CF25F43C8C21B3F575C4ED4F0B194A37AF1EB0B8B00DB982842E9606B
tools\_internal\PyQt6\Qt6\plugins\imageformats\qwbmp.dll
md5: D45BFBD0653B86682C73714D85448414 | sha1: 01810751A1C5F21999EED06EA2D0662B3B0F9CE8 | sha256: 426FEC36A39DB0D2D3EA7DB8D0DF12513CD67A6D511ECFFA91F18572CF968A1B | sha512: 1BA22CE64B178609A30D0845BC41BF5E1135B0D384FE8BCBEDF145BA9BCD67E85B82B2AECB475B1EFBF339811A82FA54D776E5E9BB5E009040AF9536E34B59F1
tools\_internal\PyQt6\Qt6\plugins\imageformats\qwebp.dll
md5: DF36480CA906A784F193D2768E18B75A | sha1: 05045744159AAE78526EAEE8C339BE69C2B8E1B6 | sha256: 5555FBAB56D72F68FAF84845ABB573BB481556BC99B5564EC85D1C63702A06B4 | sha512: D0D0287F138F5BABEC1C29B624A92482CE7C94287525E587EC1A6D920081C17FE7C8F661D07927DA1EF9D20602BFE5672C9173555AC837EDF162CC3A8F0C36C9
tools\_internal\PyQt6\Qt6\plugins\platforms\qminimal.dll
md5: 7DE6AB9104F0A12D03EE2F91EB756138 | sha1: 57AEFDC331957164E7D6C062938F6F4A7D8DD970 | sha256: 18FF77A19555B45EDECF265D9FCCC6513CDEEFCB2036C4B0E2FDFF1F33B2C0B1 | sha512: 4EF75EBC7BD57DE5966B1FE49766994FA73C910DFDF12F0FA0E761434E8160B0BE85BDCF95017E559FF1A2557ACAB4A7737021D4DAA265780AF2C1E807B4B8F4
tools\_internal\PyQt6\Qt6\plugins\platforms\qoffscreen.dll
md5: 4AF90A0D27DFA67791D97B49FAD90A0D | sha1: 2DA8981BE159DDA321548210329586E42012765F | sha256: D28842EE1C2B420CF85F49649B17F523197B4CECFCDD0893C20671863DC18F1C | sha512: 17B0107DA9FFF3804A2B39A28F36F377BA04DB9BC4DEBC72230226A7276003B5F83C6978C297EFA4CA0BF3C1B845EDC014DE6EE88FE11C92CBEA2C730860D341
tools\_internal\PyQt6\Qt6\plugins\platforms\qwindows.dll
md5: 0AE46094796365591C19772ED06F3677 | sha1: FF76CBF41786D05983C9F82CBF22D1E9680DB731 | sha256: 210D8FB2ED1FDBFB4105E2025C3EC649C30D30442E0071D6925E95A2335EEDD4 | sha512: 22A84C00B015C0DC57FE88117A3FFCE541ED419D7E9F6FD38396C3868AAD916293F6FF2F87D288EB35F8B4876F6992A5C0C7032F5CBFB6205C35311E69524C62
tools\_internal\PyQt6\Qt6\plugins\styles\qmodernwindowsstyle.dll
md5: 55AD1911C5B8F8D51DB9EF02CB00F342 | sha1: 23835492EE81DB8900D9F784E83B3C8277B8F73F | sha256: 061B45CE229496A7BE8B152200D45248CB6C2F47CB725A684FD718D7FA68A8FB | sha512: 11A04149554769BF5F9EEA50E84CB1F596DCC0C33515A756FDC7449B5407F9C4D0C155AC3E58CD464DA8AAEDD8D4CADD9BD37DCEE3A57F2C3A926505F0B44FE3
tools\_internal\PyQt6\Qt6\translations\qtbase_ar.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_bg.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_ca.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_cs.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_da.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_de.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_en.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_es.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_fa.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_fi.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_fr.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_gd.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_he.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_hr.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_hu.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_it.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_ja.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_ka.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_ko.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_lv.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_nl.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_nn.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_pl.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_pt_BR.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_ru.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_sk.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_tr.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_uk.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_zh_CN.qm
 
tools\_internal\PyQt6\Qt6\translations\qtbase_zh_TW.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_ar.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_bg.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_ca.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_cs.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_da.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_de.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_en.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_es.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_fa.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_fi.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_fr.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_gd.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_gl.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_he.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_ar.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_bg.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_ca.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_cs.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_da.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_de.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_en.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_es.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_fr.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_gl.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_hr.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_hu.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_it.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_ja.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_ka.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_ko.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_nl.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_nn.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_pl.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_pt_BR.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_ru.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_sk.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_sl.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_tr.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_uk.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_zh_CN.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_help_zh_TW.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_hr.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_hu.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_it.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_ja.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_ka.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_ko.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_lt.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_lv.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_nl.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_nn.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_pl.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_pt_BR.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_pt_PT.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_ru.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_sk.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_sl.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_sv.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_tr.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_uk.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_zh_CN.qm
 
tools\_internal\PyQt6\Qt6\translations\qt_zh_TW.qm
 
tools\_internal\PyQt6\QtCore.pyd
md5: 6F0167B4B5F5B63992D97EA939477E14 | sha1: C042FC4F672F1F0F3710F197203AF1B192485116 | sha256: 9DC389DCD7C0AC40879B723E1AA51A39B727B983B3BD5540C9D14FBA6756C9F5 | sha512: 0D63B540B81FBB38E006B5D8F49E12A1CCF21DFC3A4A02B0ABC94A2697D94AB861A6665EA9C62980C7CD5B594FF10069BE215D1DE55DF9FDBC5BFE49AF2680CD
tools\_internal\PyQt6\QtGui.pyd
md5: F892F5D1E2D1AFA44E61A3C4AB67979C | sha1: DDA553060122E93369B7012D1E870C558679CA58 | sha256: AEF7DBAFCF00C437EBBB9B03A0B67D27334BA98889854172A96C24AC545A28BE | sha512: C5DBA15198E1F82056EA60773A88DBBC723379F266C506564901969D82C3E7296BA384BD0C7E7F3D98FF3A0F750F36FE872A52309E821AB353BDD459BB49D6B7
tools\_internal\PyQt6\QtWidgets.pyd
md5: 24EB51115BC6A6B0D14664875CD98674 | sha1: AC64A0B914427ECADF72BCE11FE86D8ECB49186B | sha256: 0CE2D53DA11BE928E4EC0F5D39A10C9BB7BD0FD07186B0E47EF15196B64A5216 | sha512: 2E36BD0C9125FF23F443DF15EC7CBEA3296B1CB09D16C06CE4A9071A1F6847FEDDF75B9BD7A637A77A09D0A5F2E959007FE608935C9F2E01F3FBD1164C1F06BB
tools\_internal\PyQt6\sip.cp313-win_amd64.pyd
md5: 19216D581E072FCD6E38B8D0F54F2564 | sha1: B39AC076F5F117192ACBC9848B18C22D353746FB | sha256: 9D217CBB2F2C059850852D466C77D3C641078F9CFBE2C544588B23C2E3561F26 | sha512: 410BB4762E82919AE25716E0773CC15A5873FCD53091484A2FFC4CDE0F1AE7DE51942647F9E579D6651400B693A4C97D6DB2A2FDB4C6BD31DF5043D2C1217273
tools\_internal\python3.dll
md5: D6DFB6A9518A57E180980F7A07098D7D | sha1: 6026120461F5CBCD9255670B6A906FD8F5329073 | sha256: FDD54B6C495E9278E73D68203FFF0C300E416E704852908CF5B06666CFFEAD51 | sha512: 2A0195A5038D7530B64A506A70DE3A6B9CB64CA9206006E03F726B4420304E3A76C10FDDA12C8A51F4DBD63E7112FD7E7727A4AB94E7A111587E4248A6B26A62
tools\_internal\python313.dll
md5: 7387FE038EA75EB9A57B054FCCFE37BF | sha1: 5C532CBDFD718B5E80AFB2EE8DEA991E84757712 | sha256: 69FD86EA29370697C203F7E12830084F920F490766A8E3045AF52C036A9AD529 | sha512: C46C982B04079ED0B13617B81168598632D6C58D29E23FCBFA064B08E5836866B74880E1A9C01C12670531F13521A21177AAFB10BE0ABB329A79291D7BFF08BD
tools\_internal\pywin32_system32\pywintypes313.dll
md5: 7B4BD20267C93E35C49C32AAD05B6B15 | sha1: 860A10D04C8764F540ED34CF08E06F32B7B37611 | sha256: 90BA935A0145EE9AE56267A365CC0088D34FA506B7AFEB2BD1BD78CD33359605 | sha512: 9E05566461D9BE1A234057E1AE9979B6D022189CB49B2C264C9AD253ABEC0F0235919F24159638ACCC45FA3E75AB324DB8EDF737E72DB1EFDA2CFA589531DDFB
tools\_internal\release_notes.txt
Version 2.6.4:
Performance improvements for scanning directories.
Rewrite of scanning subdirectories.
There is now an output of the results in a box.
Removal of the full banner from the GUI.

Version 2.6.5:
Updates the DLSS files to DLSS 4.0.
Use at your own discretion :).

Version 2.6.6:
Bug fix for FG/RR DLL's being included in the skip for <2.0.0.

Version 2.6.7:
Updates to the DLL's to allow for profile K (refined version of profile J) to be the default for DLSS.

Version 2.6.8:
Updates to the RR DLL to version 310.2.1.0.
Updates the frame generation DLL to version 310.2.1.0.

Version 2.7.0:
Added support for XeSS DLLs (libxess.dll and libxess_dx11.dll) - version 2.0.1.41.
Added support for up to 4 custom game folders.
Improved auto-updater to better handle update process and cleanup.
Added ability to skip specific games in the blacklist.
Enhanced UI with separated sections for launcher types.
Fixed bug in game name extraction for better reporting.
Introduction of animations for certain components, should be better for viewability.

Version 2.7.1:
UI spacing fixes with the logger output.
Hover bug mentioned in the prior release fixed.
Removal of the maximum span width so it's possible to resize the GUI fully.
Few other erronous colour fixes.

Version 2.7.2:
Addition of DirectStorage.
Offloading of the DLL's to a separate repository to strip down the file size.
Addition of a checklist to allow selection on what is updated.
Implemented DLL caching to check for new DLL's.
tools\_internal\select.pyd
md5: 715A098175D3CA1C1DA2DC5756B31860 | sha1: 6B3EC06D679C48BFE4391535A822B58A02D79026 | sha256: 6393121130A3E85D0F6562948024D8614C4C144B84AB102AF711C638344D1599 | sha512: E92EDB98427F594BADEC592493469D45DEAB3B71E4598D544D0B9A1ACFFD5327A19C09029FB79D70971CB0ED0DBA56056BEF8455534D3F16EC35EAC723062F3C
tools\_internal\setuptools\_vendor\importlib_metadata-8.0.0.dist-info\INSTALLER
 
tools\_internal\setuptools\_vendor\importlib_metadata-8.0.0.dist-info\LICENSE
 
tools\_internal\setuptools\_vendor\importlib_metadata-8.0.0.dist-info\METADATA
tools\_internal\setuptools\_vendor\importlib_metadata-8.0.0.dist-info\RECORD
 
tools\_internal\setuptools\_vendor\importlib_metadata-8.0.0.dist-info\REQUESTED
 
tools\_internal\setuptools\_vendor\importlib_metadata-8.0.0.dist-info\top_level.txt
importlib_metadata
tools\_internal\setuptools\_vendor\importlib_metadata-8.0.0.dist-info\WHEEL
 
tools\_internal\setuptools\_vendor\wheel-0.45.1.dist-info\entry_points.txt
[console_scripts]
wheel=wheel.cli:main

[distutils.commands]
bdist_wheel=wheel.bdist_wheel:bdist_wheel

tools\_internal\setuptools\_vendor\wheel-0.45.1.dist-info\INSTALLER
 
tools\_internal\setuptools\_vendor\wheel-0.45.1.dist-info\LICENSE.txt
MIT License

Copyright (c) 2012 Daniel Holth <[email protected]> and contributors

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
tools\_internal\setuptools\_vendor\wheel-0.45.1.dist-info\METADATA
tools\_internal\setuptools\_vendor\wheel-0.45.1.dist-info\RECORD
 
tools\_internal\setuptools\_vendor\wheel-0.45.1.dist-info\REQUESTED
 
tools\_internal\setuptools\_vendor\wheel-0.45.1.dist-info\WHEEL
 
tools\_internal\ucrtbase.dll
md5: 286B308DF8012A5DFC4276FB16DD9CCC | sha1: 8AE9DF813B281C2BD7A81DE1E4E9CEF8934A9120 | sha256: 2E5FB14B7BF8540278F3614A12F0226E56A7CC9E64B81CBD976C6FCF2F71CBFB | sha512: 24166CC1477CDE129A9AB5B71075A6D935EB6EEBCAE9B39C0A106C5394DED31AF3D93F6DEA147120243F7790D0A0C625A690FD76177DDDAB2D2685105C3EB7B2
tools\_internal\unicodedata.pyd
md5: 503B3FFA6A5BF45AB34D6D74352F206B | sha1: CC13B85281E5D52413784E0B65A61B1D037C60CC | sha256: 071494856FDAD0042964769AA2FB1DE4EA95C2CFCBE27CC7132293C68D13D710 | sha512: D20B860974161CAA60A62268968AF353AD8063589F57D71F57C91855EB83DA78F40BAE7AA745CC7A945D92EBE08CF244C9560AE93449DE45B20A8B8FFF9F5010
tools\_internal\VCRUNTIME140.dll
md5: 32DA96115C9D783A0769312C0482A62D | sha1: 2EA840A5FAA87A2FE8D7E5CB4367F2418077D66B | sha256: 052AD6A20D375957E82AA6A3C441EA548D89BE0981516CA7EB306E063D5027F4 | sha512: 616C78B4A24761D4640AE2377B873F7779322EF7BC26F8DE7DA0D880B227C577ED6F5ED794FC733468477B2FCDB7916DEF250E5DC63E79257616F99768419087
tools\_internal\VCRUNTIME140_1.dll
md5: C0C0B4C611561F94798B62EB43097722 | sha1: 523F515EED3AF6D50E57A3EAEB906F4CCC1865FE | sha256: 6A99BC0128E0C7D6CBBF615FCC26909565E17D4CA3451B97F8987F9C6ACBC6C8 | sha512: 35DB454DBCC7ED89842C0440B92CE0B0B0DB41DBD5432A36A0B7E1EDDF51704B1F0D6CFF5E3A3B0C3FF5DB3D8632FED000471180AD72E39D8DBE68A757CCDFB0
tools\_internal\win32\win32api.pyd
md5: 747FC8B90E33F5E9048BCF26788B9169 | sha1: AC30AAE15BEA0514C7730B007B68DD841A7F3DDC | sha256: B1B1BB33AF9CC14749936B1F6BAC36B2FFC494EC1A5FB8B12FC9180A6454F545 | sha512: 51416CDA9A18F113D46C3CB06E7ED85717C64325156BE16C4FC78BDDC7A06F0E845B3FEDD2C1CA6C965517530D9CBB9B9497DD1B309BC16011D2E1499BB5D082
tools\_internal\_asyncio.pyd
md5: 56F958EEBBC62305B4BF690D61C78E28 | sha1: 68D1A227F8BEF856469523364E37AE76B968162A | sha256: A5341A74BBEC1DDC807C0236FCB6BFACEAF3B957EB69CDD9BCA00657EB5E42B6 | sha512: 91B2A31835A5A0610856DF1851C7BB1DEA48A6740C63BD037971473706197E81E9904EAA6042A84FC15AA6AA74AC226463B67E2FA8370CBB8B0C987FED777169
tools\_internal\_bz2.pyd
md5: 684D656AADA9F7D74F5A5BDCF16D0EDB | sha1: F7586DA90D101B5EE3FA24F131EE93AB89606919 | sha256: 449058EFC99FCCB9E24D640084D845C78F3F86DD34C5C126CF69E523D6320D75 | sha512: 27FB2ECA382675316FB96D18A1AA6B2792077481BF899CBCC658D71F787876045C05C98ABF129C9670B6A1D2654D57F59E17580139FA7F482EC27234E44D4235
tools\_internal\_ctypes.pyd
md5: 29873384E13B0A78EE9857604161514B | sha1: 110F60F74B06B3972ACD5908937A40E078636479 | sha256: 5C0D5082FBA1A2A3EB8D5E23073BE25164C19F21304B09CECAAB340DC7198815 | sha512: CA826FF5403700E6D8822634E364E43B14EF829095D8FE365B49731236F696FE86FFA3853CD1801DC3B7800D005A032FE23BBC25BEFE3952EF37790D56DEE3C5
tools\_internal\_decimal.pyd
md5: 21FCB8E3D4310346A5DC1A216E7E23CA | sha1: AAB11AEF9075715733E0FCDE9668C6A51654B9E1 | sha256: 4E27C06B84401039D10F800A0F06446B58508784EE366C7C8324D8FE9794E1A5 | sha512: C064550D1723E92512A42CE367ECEF9331A81121305D66199ABCE6E0977152D927F7223F475E22C67E3F64B0F612C5553F112D8CE653C666A98D1980D200A599
tools\_internal\_elementtree.pyd
md5: AC10151B412BFB22BA9725BC9613C49E | sha1: 4152C799C6FAA2A1606D40E1B9089E67EFAEC951 | sha256: FE09D0408AAB3A6FAA71467F78433DF4C7F3AD0B033BB72EC43BDE85ABF6DCFB | sha512: BF0641606C45285C3F18454E8F855D12963F51D910F20419B76405CC80530C38E17A791C580A9DB6D171A5E1B9999A6DEA661E22A62360D804183F9C0210A107
tools\_internal\_hashlib.pyd
md5: 3E540EF568215561590DF215801B0F59 | sha1: 3B6DB31A97115C10C33266CCE8FF80463763C7E6 | sha256: 52F29AEBE9886E830DEDC363CD64EB53B6830D84B26E14F1B6FAA655A0900B5D | sha512: 21497A4D1D999A420ED0E146544F4149C72AD4ACA4B869A0EE83267D92AFA07609ECE76A4E95EC706A21580D6544146D0A58C0BAA01AA2C242474A4816108527
tools\_internal\_lzma.pyd
md5: D63E2E743EA103626D33B3C1D882F419 | sha1: AF8A162B43F99B943D1C87C9A9E8088816263373 | sha256: 48F16B587C6FAA44A9E073365B19599200B0F0A0CCB70121E76C2DAC4ED53281 | sha512: D3F1450B5DEF3C21F47C5133073E76D2EC05787EB6AE88BB70D3A34BE84F6025540AC017E9415BB22EF36C2FFBFCEA38A28842EEFE366325F3D3CF2CCA1A3CB1
tools\_internal\_multiprocessing.pyd
md5: 807DD90BE59EA971DAC06F3AAB4F2A7E | sha1: C4BEA9DB94127EF30E929B395D38175DC74E4DC0 | sha256: 82253E2D6EC717B317E26ED7DD141AADAEA6CB55A9D0FEE022A67D52B404FD06 | sha512: 61B9CF8AC06506002D273B59E2FB66AD96751B10D10FAFF9128749538867D45D561C1CF8DCB8E787CA6AFDC8A1D504CB7012135DFE3A1F3D1FC0B107E4E1A8F9
tools\_internal\_overlapped.pyd
md5: 363409FBACB1867F2CE45E3C6922DDB4 | sha1: 045B1B90886F4B25D326EA3409A5F79570EAE4B2 | sha256: 7983F811CCD9C99C6DB34B653339605EA45EB384F5E88A8B23CCF9FA5F0170D9 | sha512: C89288DD76821A18E18CE3E67F01B1A9F6A55751832AA1A4B44882F2115474CA131F95F3545ADB9C2D8ECAF3269837126135395C719581A7493AFFAA96EA0DFE
tools\_internal\_queue.pyd
md5: CC0F4A77CCFE39EFC8019FA8B74C06D0 | sha1: 77A713CD5880D5254DD0D1CBFE0D6A45DFC869CE | sha256: AF8AC8AB8B39F53B5DC192FBF58AD704A709DB34E69753B97B83D087202E3A36 | sha512: FFEA0BD7F73B6C02DF6FF37EF39B8E54E480A4CC734FB149ADC5C7410F445EFFD1FDD4F24E4619F7158913A50C28CC73629524D1A7389101A75257D5652C7823
tools\_internal\_socket.pyd
md5: 566CB4D39B700C19DBD7175BD4F2B649 | sha1: BEDE896259B6D52D538C2182AEF87C334FC9C73C | sha256: BCED17D6F081D81EA7CD92F1E071E38F8840E61EE0FE1524221B776BCFA78650 | sha512: 6A26FD59E2C2EC34B673EF257A00D5577F52286D78525D05EFC8A88760FB575BE65C3E94E83396F4978C8734B513AFE7F09D3C49474169144F98ADD406530367
tools\_internal\_ssl.pyd
md5: 689F1ABAC772C9E4C2D3BAD3758CB398 | sha1: FE829E05D9F7838D1426F6D4A2F97165C09FD0F7 | sha256: 3301FF340D26495C95108199B67FDF3402742D13070AF8B6BF4EB2E0C5E13781 | sha512: 949404A76C731A92074B37EC0BBA88D873E56327B335B6C300EFF68C2B142E194B58DF59158B9BB92A5984C768B474F5DB5F80F6B610F6CCA78763604041BD82
tools\_internal\_uuid.pyd
md5: 93730CB349B216114B444CC9E30932CA | sha1: 689E63330F48877478D428F0E410AC7D69E7150A | sha256: 17C7856BDA73348CA541D01BA4881E4B327B15FB3D2CB90A92CA2BF0E6C4BAFE | sha512: AB312A908256D55CF883E90501DCF88175CC145207D2DA4E3CC8470E7FA3AFDCFD889F0B5C4488ACE6CA3B1F7BBA943F2156E839EDA80981FF592123C5777C34
tools\_internal\_wmi.pyd
md5: 47E6FD132F44A4FEB595BD0FDA3C4E1C | sha1: 37C6C2C1FF309DB7273AFC9324A37B716C5CBFDB | sha256: EBD252D21AF9C84128FCA04C994093A5BD6EE857F1581F06F4026FDD6A2C40E0 | sha512: 69C031D4FF2DAC70739F9C188FCA3C6969304F22782ADF5A9C0CA303A3A712630541BDA888EF25D3252B46D43DF56F6E7E03C83D331840088C4224D1A1A512C4

Log in or click on link to see number of positives.

In cases where actual malware is found, the packages are subject to removal. Software sometimes has false positives. Moderators do not necessarily validate the safety of the underlying software, only that a package retrieves software from the official distribution point and/or validate embedded software against official distribution point (where distribution rights allow redistribution).

Chocolatey Pro provides runtime protection from possible malware.

Add to Builder Version Downloads Last Updated Status
DLSS Updater 2.7.2 64 Wednesday, April 23, 2025 Approved
DLSS Updater 2.7.1 57 Tuesday, April 8, 2025 Approved
DLSS Updater 2.7.0 37 Saturday, April 5, 2025 Approved
DLSS Updater 2.6.8 51 Thursday, March 27, 2025 Approved
DLSS Updater 2.6.7 107 Saturday, February 1, 2025 Approved
DLSS Updater 2.6.6 13 Thursday, January 30, 2025 Approved
DLSS Updater 2.6.5 19 Saturday, January 25, 2025 Approved
DLSS Updater 2.6.4 151 Monday, January 13, 2025 Approved
DLSS Updater 2.6.3 19 Thursday, January 9, 2025 Approved
DLSS Updater 2.6.2 50 Friday, January 3, 2025 Approved
DLSS Updater 2.6.1 34 Saturday, December 28, 2024 Approved
DLSS Updater 2.5.2 57 Thursday, December 19, 2024 Approved
DLSS Updater 2.5.1 49 Saturday, December 14, 2024 Approved
DLSS Updater 2.2.6 49 Thursday, November 14, 2024 Approved
DLSS Updater 2.2.3 171 Thursday, September 26, 2024 Approved
Discussion for the DLSS Updater Package

Ground Rules:

  • This discussion is only about DLSS Updater and the DLSS Updater package. If you have feedback for Chocolatey, please contact the Google Group.
  • This discussion will carry over multiple versions. If you have a comment about a particular version, please note that in your comments.
  • The maintainers of this Chocolatey Package will be notified about new comments that are posted to this Disqus thread, however, it is NOT a guarantee that you will get a response. If you do not hear back from the maintainers after posting a message below, please follow up by using the link on the left side of this page or follow this link to contact maintainers. If you still hear nothing back, please follow the package triage process.
  • Tell us what you love about the package or DLSS Updater, or tell us what needs improvement.
  • Share your experiences with the package, or extra configuration or gotchas that you've found.
  • If you use a url, the comment will be flagged for moderation until you've been whitelisted. Disqus moderated comments are approved on a weekly schedule if not sooner. It could take between 1-5 days for your comment to show up.
comments powered by Disqus