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- Software Specific:
- Software Site
- Software Source
- Software License
- Software Docs
- Software Issues
- Package Specific:
- Package Source
- Package outdated?
- Package broken?
- Contact Maintainers
- Contact Site Admins
- Software Vendor?
- Report Abuse
- Download
DLSS Updater
- 1
- 2
- 3
2.7.3 | Updated: 08 May 2025
- Software Specific:
- Software Site
- Software Source
- Software License
- Software Docs
- Software Issues
- Package Specific:
- Package Source
- Package outdated?
- Package broken?
- Contact Maintainers
- Contact Site Admins
- Software Vendor?
- Report Abuse
- Download
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
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:
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
Option 1: Cached Package (Unreliable, Requires Internet - Same As Community)-
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
-
Open Source
-
Download the package:
Download - Follow manual internalization instructions
-
-
Package Internalizer (C4B)
-
Run: (additional options)
choco download dlss-updater --internalize --source=https://community.chocolatey.org/api/v2/
-
For package and dependencies run:
choco push --source="'INTERNAL REPO URL'"
- Automate package internalization
-
Run: (additional options)
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.
This package was approved as a trusted package on 08 May 2025.
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.
md5: 6135D3A52CD3841165FDA88D4D7BDC8B | sha1: 056F882C71FB1FEB9E90D38A004B1DF17C41F96C | sha256: 597647620A746925A4663996C03ECBDCFA664013C8EC4A1097727FE20B13C67F | sha512: 261492CB5223188B58B8A6FBDAC317CAFD0946712C28AA8AED95ECAAC33FFDFFD54AD44861787FF1DA99454C7DC39D80037FD42A9EA204095C5F84759BDF0542
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
md5: 9F746F4F7D845F063FEA3C37DCEBC27C | sha1: 24D00523770127A5705FCC2A165731723DF36312 | sha256: 88ACE577A9C51061CB7D1A36BABBBEFA48212FADC838FFDE98FDFFF60DE18386 | sha512: 306952418B095E5CF139372A7E684062D05B2209E41D74798A20D7819EFEB41D9A53DC864CB62CC927A98DF45F7365F32B72EC9B17BA1AEE63E2BF4E1D61A6E4
md5: 8F8EB9CB9E78E3A611BC8ACAEC4399CB | sha1: 237EEE6E6E0705C4BE7B0EF716B6A4136BF4E8A8 | sha256: 1BD81DFD19204B44662510D9054852FB77C9F25C1088D647881C9B976CC16818 | sha512: 5B10404CDC29E9FC612A0111B0B22F41D78E9A694631F48F186BDDE940C477C88F202377E887B05D914108B9BE531E6790F8F56E6F03273AB964209D83A60596
md5: 226A5983AE2CBBF0C1BDA85D65948ABC | sha1: D0F131DCBA0F0717C5DEA4A9CA7F2E2ECF0AD1C3 | sha256: 591358EB4D1531E9563EE0813E4301C552CE364C912CE684D16576EABF195DC3 | sha512: A1E6671091BD5B2F83BFAA8FCF47093026E354563F84559BD2B57D6E9FA1671EEA27B4ED8493E9FDF4BDE814074DC669DE047B4272B2D14B4F928D25C4BE819D
md5: C2F8C03ECCE9941492BFBE4B82F7D2D5 | sha1: 909C66C6DFEA5E0C74D3892D980918251BB08632 | sha256: D56CE7B1CD76108AD6C137326EC694A14C99D48C3D7B0ACE8C3FF4D9BCEE3CE8 | sha512: 7C6C85E390BBE903265574E0E7A074DA2CE30D9376D7A91A121A3E0B1A8B0FFFD5579F404D91836525D4400D2760CB74C9CB448F8C5AE9713385329612B074CF
md5: B5E2760C5A46DBEB8AE18C75F335707E | sha1: E71DB44FC0E0C125DE90A9A87CCB1461E72A9030 | sha256: 91D249D7BC0E38EF6BCB17158B1FDC6DD8888DC086615C9B8B750B87E52A5FB3 | sha512: C3400772D501C5356F873D96B95DC33428A34B6FCAAD83234B6782B5F4BF087121E4FD84885B1ABAB202066DA98EB424F93DD2EED19A0E2A9F6FF4A5CFD1E4F3
md5: 050A30A687E7A2FA6F086A0DB89AA131 | sha1: 1484322CAAF0D71CBB873A2B87BDD8D456DA1A3B | sha256: FC9D86CEC621383EAB636EBC87DDD3F5C19A3CB2A33D97BE112C051D0B275429 | sha512: 07A15AA3B0830F857B9B9FFEB57B6593AE40847A146C5041D38BE9CE3410F58CAA091A7D5671CC1BC7285B51D4547E3004CF0E634AE51FE3DA0051E54D8759E1
md5: 9F45A47EBFD9D0629F4935764243DD5A | sha1: 86A4A0EA205E31FB73F3BFCCE24945BD6BEA06C7 | sha256: 1CA895ABA4E7435563A6B43E85EBA67A0F8C74AA6A6A94D0FC48FA35535E2585 | sha512: 8C1CDCAD557BFF1685A633D181FCF14EC512D322CAEAEB9C937DA8794C74694FE93528FC9578CB75098F50A2489ED4A5DEDF8C8C2AC93EEB9C8F50E3DD690D5F
md5: CC228FF8D86B608E73026B1E9960B2F8 | sha1: CEF0705AEE1E8702589524879A49E859505D6FE0 | sha256: 4CADBC0C39DA7C6722206FDCEBD670ABE5B8D261E7B041DD94F9397A89D1990D | sha512: 17ABD9E0EC20B7EB686E3C0F41B043D0742AB7F9501A423B2D2922D44AF660379792D1CC6221EFFBD7E856575D5BABF72657AE9127C87CC5CF678BD2CEB1228F
md5: E368A236F5676A3DA44E76870CD691C9 | sha1: E4F1D2C6F714A47F0DC29021855C632EF98B0A74 | sha256: 93C624B366BA16C643FC8933070A26F03B073AD0CF7F80173266D67536C61989 | sha512: F5126498A8B65AB20AFAAF6B0F179AB5286810384D44638C35F3779F37E288A51C28BED3C3F8125D51FEB2A0909329F3B21273CB33B3C30728B87318480A9EF8
md5: 416AA8314222DB6CBB3760856BE13D46 | sha1: 5F28FE2D565378C033EF8EEA874BC38F4B205327 | sha256: 39095F59C41D76EC81BB2723D646FDE4C148E7CC3402F4980D2ADE95CB9C84F9 | sha512: B16ED31DC3343CAEA47C771326810C040A082E0AB65D9AE69946498CEB6AE0DEE0A570DBCD88090668A100B952C1FF88BADE148811B913C90931AA0E657CD808
md5: 344A09B4BE069F86356A89482C156647 | sha1: 2506FFEB157CB531195DD04D11D07C16E4429530 | sha256: 8F105771B236DBCB859DE271F0A6822CE1CB79C36988DD42C9E3F6F55C5F7EB9 | sha512: 4C1E616443576DC83200A4F98D122065926F23212B6647B601470806151FF15EA44996364674821AFEC492B29BA868F188A9D6119B1E1D378A268F1584CA5B29
md5: 86023497FA48CA2C7705D3F90B76EBC5 | sha1: 835215D7954E57D33D9B34D8850E8DC82F6D09E8 | sha256: 53B25E753CA785BF8B695D89DDE5818A318890211DC992A89146F16658F0B606 | sha512: 8F8370F4C0B27779D18529164FA40CBFDDAFA81A4300D9273713B13428D0367D50583271EA388D43C1A96FED5893448CD14711D5312DA9DFA09B9893DF333186
md5: 0C1CC0A54D4B38885E1B250B40A34A84 | sha1: 24400F712BBE1DD260ED407D1EB24C35DCB2ECAC | sha256: A9B13A1CD1B8C19B0C6B4AFCD5BB0DD29C0E2288231AC9E6DB8510094CE68BA6 | sha512: 71674E7ED8650CAC26B6F11A05BFC12BD7332588D21CF81D827C1D22DF5730A13C1E6B3BA797573BB05B3138F8D46091402E63C059650C7E33208D50973DDE39
md5: 5FBCB20D99E463259B4F15429010B9CD | sha1: B16770F8BB53DC2BAFCB309824D6FA7B57044D8A | sha256: 7F39BA298B41E4963047341288CAB36B6A241835EE11BA4AD70F44DACD40906C | sha512: 7BA1AC34B3ECFBFB8252F5875BE381D8EF823B50DFE0E070222175EE51191F5EE6D541EEEDD1445ED603A23D200CE9CE15914C8ED3FAFE7E7F3591F51F896C58
md5: 5241DF2E95E31E73CCFD6357AD309DF0 | sha1: 2644CC5E86DFAD1AD2140181AB2CA79725F95411 | sha256: 6EE44DD0D8510DC024C9F7C79B1B9FA88C987B26B6BEB6653DDD11751C34E5DC | sha512: 52CCCD1DD237E764E34996C0C5F7A759A7F0EFF29B61BEFEAF96A16D80DF2BA9EE2C3615F875153198A145D68F275AEA6D02187E6EEE5A129E3E2AB81AACEB16
md5: 8D285430E8BDA6D5C9B683579ADCB180 | sha1: 619DBBCFF06C659E3FC48F03917A4DADBFC1C275 | sha256: 0512A35316EC9180437F86696A84C5C06A7E4E82E050055A656E5BF9FCA206F9 | sha512: 38405DD85DD62F843ABB55ACEA1B64D7D63BB601445BF1B32078CDE5BBEF4861DD99F26659281FE2AEA86F58CFB1725D8C63D91FB539DCBF5D98CDBE783337FC
md5: 4A28CA64F44B91F43945EE3971E0996A | sha1: 45B3D8584C58E8D6AE507FDBD772FEEB1886C8B0 | sha256: C05F1FFFE3B5A2738EA54CE9485CCA026FB9635F982626FBA1E1DCC531897273 | sha512: 862A0428F08D447CD1EE0431969E0FBCB182F4C46418C26D26FA33E586E686D9C093C1CA5781F544CE9276195CE973850719636E39E465F059607F455ECFDD93
md5: 7FD4A71085783CCFE9C289C07BCF9B04 | sha1: BB6FFDB5C069DBBA06998DC877D24F72DAD6298D | sha256: C4ECA98C3C67B6395D5B005B00AC1EB0318B86B23AA71035A44C2B1602BEFBA9 | sha512: A96C5B90B8384B239BE111D90CAA3B947651AD73382AB9E5DBE4A4B6AD30921876545331D37C8D5A8F669E39D71BF60983C4BA39C479E23015C2F7579C5E55CD
md5: C123F2C161884FBFF4F00EF1E1391266 | sha1: 7DB3055DA53916BEA2B85B159491A0772FB620CE | sha256: 5CCB89E93D67BC3288D4E84649C5346E66E15E3D7CD65D989DAF3F4CB584BE9A | sha512: DAC5616320B9052254B5687959E67126C4A938E79173D8245675A9651674384C36CC856F996EF88AE621EC67AFC6616626657585D92BB5D14602A7CC9FC0F669
md5: 385F562BDC391CCD4F81ACA3719F3236 | sha1: F6633E1DAC227BA3CD14D004748EF0C1C4135E67 | sha256: 4AD565A8BA3EF0EA8AB87221AD11F83EE0BC844CE236607958406663B407333E | sha512: B72ED1A02D4A02791CA5490B35F7E2CB6CB988E4899EDA78134A34FB28964EA573D3289B69D5DB1AAC2289D1F24FD0A432B8187F7AE8147656D38691AE923F27
md5: 7A629293EEB0BCA5F9BDEE8ADE477C54 | sha1: A25BF8BAC4FBFD9216EA827E71344BA07B1D463B | sha256: 7809160932F44E59B021699F5BC68799EB7293EE1FA926D6FCCA3C3445302E61 | sha512: 1C58C547D1FE9B54DDF07E5407EDAF3375C6425CA357AA81D09C76A001376C43487476A6F18C891065AB99680501B0F43A16A10ED8E0D5E87B9A9542098F45FE
md5: 3C5C7A3130B075B2DEF5C413C127173F | sha1: F3D2B8AD93F3DC99C8410D34C871AEC56C52E317 | sha256: 9DC1E91E71C7C054854BD1487CB4E6946D82C9F463430F1C4E8D1471005172B1 | sha512: 46A52631E3DD49B0AE10AFBDF50A08D6D6575F3093B3921B2FA744704E2D317F8B10A6D48AD7F922A7843731782521773032A6CC04833B00BD85E404C168FFE4
md5: 28005B20FBEF6E1DB10912D0FDD6471C | sha1: 47B83697677E08E4EBCFF6FC41ECA7ECE120CC17 | sha256: 60FC31D2A0C634412F529DBA76AF3B9BF991352877C6DAE528186D3935704CFD | sha512: 45D6F860D7F7AEFAA7A0A3B4B21B5C3234F442E39D6259E0A9E2083890533C275F07DDDA93FDDC7445928A55475B83C63253D3B08E41E5576F9029B205DFB36A
md5: 436EA0237ED040513EC887046418FAAA | sha1: 44BAFBBDB1B97D86505E16B8A5FCB42B2B771F91 | sha256: 3A72B4F29F39A265D32AD12F0CE15DBF60129C840E10D84D427829EDE45E78AD | sha512: 9F0DBFB538C05383AE9ABFE95E55740530ECC12C1890D8862DEACBC84212BE0740D82AFC9E81D529125221E00B2286CAE0D4B3CA8DD3A6C57774D59F37933692
md5: 8F107A7BC018227B181A0E7E76E9CA39 | sha1: EF57E24F29D2B1DEEACEFD82171873B971A3F606 | sha256: EFC1E4460984A73CF47A3DEF033AF1C8F3B1DBC1A56CD27781D3AACF3E3330CB | sha512: D8D8250AAF93FA99E9D1E4286B32579DE0029C83867A787C0A765505A0F8CBD2DD076BB324509D5C4867423BC7DC8F00C8B8458E08E8CBFA8DD731D03DD1AE3F
md5: B65BF5EF316880FD8D21E1B34EB5C8A9 | sha1: 3AB4674CB5C76E261FE042D6D0DA8A20BFCBCBAE | sha256: B203D862DDEF1DD62BF623FC866C7F7A9C317C1C2AE30D1F52CB41F955B5698E | sha512: 4AF3B0EF9A813CE1A93A35DD6869817910AE4B628F374477F60EA1831D2CC1AAE7908262672E11954A4953BDFF22BCC5FE23B4A736788E8E5EF4F8AC30EB24F8
md5: FC9FC5F308FFC2D2D71814DF8E2AE107 | sha1: 24D7477F2A7DC2610EB701ED683108CD57ECA966 | sha256: 2703635D835396AFD0F138D7C73751AFE7E33A24F4225D08C1690B0A371932C0 | sha512: 490FA6DC846E11C94CFE2F80A781C1BD1943CDDD861D8907DE8F05D9DC7A6364A777C6988C58059E435AC7E5D523218A597B2E9C69C9C34C50D82CAC4400FE01
md5: 43D8D2FB8801C5BD90D9482DDF3EA356 | sha1: D582B55CD58531E726141C63BA9910FF185D72E0 | sha256: 33F4FDDC181066FCE06B2227BDED813F95E94ED1F3D785E982C6B6B56C510C57 | sha512: 0E073381A340DB3F95165DBCCEB8DFBF1ED1B4343E860446032400A7B321B7922C42EE5D9A881E28E69A3F55D56D63663ADB9BB5ABB69C5306EFBF116CC5E456
md5: 3C58A804B90A0782E80BBBF6C6B6F167 | sha1: B333143E0F6E508B51D27ADF7872B586FA54C794 | sha256: 6EDA016742A6171205A387A14B3C0B331841567740376F56768F8C151724207D | sha512: 773F8DEDED48B34BABE24D955A501F4F357C20125AFFB6EADE36CE6A7ACD380906713C366318F79D627747E636D156875C216FFFAC26DBA25373BBC1C820DA76
md5: 5794B8E183EB547AADD5FAF30A8C4DD2 | sha1: 5B1ED8A9DA14D8ECC4209662809727931AA49307 | sha256: B762061B688AAE679AFE788904D2C9970F74A7DAC98F3B42463D08F25E483D3F | sha512: 3E896854E5DD957AB2B88C82FBAF2EAA03729BAB30FD8518BD999081F4DA9000D9B22894B324E5930DF161C7ADAEC3FC87FD00DE60DCDA34876007AEA4A2FD31
md5: 3560176D0CDBE2F5D33F543348E0A027 | sha1: 1E35A1F7793FC3899927835491F28FE5B903EDCD | sha256: EBB2AE5535A64F65DAEAB8235585114FC9DD2CF1A49F5852D446250B998B6AE4 | sha512: 8AB24C8C9FE8331F21BE96818C5FA69AE5578EB742C4504596310BB0DB7C4C087D350FA47A13ED9FF2E051BB62AC5581DE082D0177923D24FEE6B140AFECF50B
md5: E93C7F013493B12AD40229B19DB02CE6 | sha1: EF878BFBFD2F8328BBB8CFF1AA29A39E624A8503 | sha256: 17D63275D00BDD8670422B95BD264C532998E0A1B041079E54FCE4B6B7A55819 | sha512: 2F4A25EA4062840BEA10442CAD665A72ABBCE747307AD9CE7B3BB89EAF7DCC28F1E9396749576BE304FD793690DDC445653613440442695E72B761EACACB6020
md5: 47555752931CECF90E796499B62EC729 | sha1: 217B171764FBA5E91190D1F8A36FECCB3F6D4585 | sha256: 9A9E2A65A281644E368D0F272B95BA5F6B445D1C35910D06056C5EBEB77402DB | sha512: A68009F0306D4D8E70951978D2C184EB80FBEC98C6DB0997BD7B0B503DD63019363CFEF68A9ADBFB568C0A552B774FBDBEB1BCF45F211A6A3224B49E85A5619C
md5: 527BBBFDED529EA77EE798D94CE0F243 | sha1: 647F8C89EB4DB3CF3656292B3DE984B32C6E02A5 | sha256: BAB9AC3EC83E380AE51E4295EF3BF2C738627812D3A49D1E713661ABBC8DC57A | sha512: C1ED69E15AB19084390CF9D1CEAB791758AC4DDD688169F3B814B0E4CF1FC3B6BA17651E35B25DCDC601A8A64821D58933D52A5E939942FA134DFD04FCA04C8B
md5: 09796DAB12CBBD920F632AEB89820193 | sha1: 7D81C0E5537B6D8B79AF0C28CD102E064027C78D | sha256: BD14C67EA28E21D6257AD780A37122C9B5773F69E693F5DB6BFFAEE4D839526E | sha512: 09A6175DCCBBD18A62209E156089F1167DFB8040C97C8C2C14724CE2A8FBE6CE039D7FE04FB8BD60092427BEB7FDD8E7127D611F006FFF1CF2A1AD75E9E5EF3A
md5: AA9624CB27CC50A3FBBD3B223A617B1C | sha1: 797AEA1C5CEDD1125276BFC5DCD7A3FB8C6355AA | sha256: 606D66D82DB562EA7979179D06486A0F94D079941D26B80A1E2C49D29959DF6F | sha512: 024975E6787F7A6B0AB6E4B02AD33901F8473B97DC73D4F03B7A116B24AC74150C0C48990EA7A4FB750F9FE728DAFED172796743F802E70F2150EEFCF70FE96A
md5: 9D6925407136753E8EB8234D59FA3F1F | sha1: 62631B7007D394FB4D406EA686B291FFF9E486CD | sha256: F6156B1020380EC4F0E48577EBEDAAEF5FB1AB1F337D8B4E72E6A33A7567A9CC | sha512: AB04DE62524E465810CD0EE81E85018863E276D49861E67A920667AF802E94869B816B47A6E3C4738179A7A7D726D44BBBA6E47D9097363A63EAFF51CD56DE8A
md5: BBAA58E9E1ABDF7D8C4C69652D29D789 | sha1: 38AEF13ABC14502354E8C5C3C37B97A8E2E5FDCF | sha256: C5902934D026D7E15FBE9917D474F3322846A41A25E66F4B2B1F758801879F4B | sha512: 7882A8E1E1EA7E217F70FF9DF27D36709B4BE23588909EF002F3EB1B9A7D3EEA2591A8524AF2C83448DDFFF0911658517C6989683245C54678583F359A78B0AD
md5: EF37235FC43157A4C93241D5E49E304B | sha1: D4DE26B36812C2DDCCD1618B4D7AC02AD1B42273 | sha256: A9C5A153D8C0286F9B41A2B1C65854AD9E6471B8755B7DE87BAE4470E60BCAB6 | sha512: C0857760D5D069BEEB1EB1737F4160530910331BF6047022836CF58137BD28C2A966A8760A681859F57EBD810FD424CE231402EDDDE1316EAEF7B6F9F773AFBB
md5: 639B1FB35CB61BA633EB1791B750631F | sha1: 392A6925009F5FB02A4C122C9CE31D82B9059628 | sha256: 25B8F83A7767211B11132775A0E27A45AA4EC8AB4E6572599F9C172AE3606B40 | sha512: DEF547EF66673862CEA9BB13C433EDCE24A3075C328D9B3B9452F2F01F2F4243DAAB38C0F8571C52D601BC4AECAAA0682DBEBF6BE41CAE345787A719063EBF58
md5: FCCCE207A34C947F01D3F23A7DD09569 | sha1: 75F722801C77285DB98A08AF763252A0255E99E2 | sha256: 7C7F6393F06DE11750ADB09CC5698AE55CD9FB27B2E51E207286FEB1B5B2B156 | sha512: D3D923F133594EB4325F4A6E5ED46FCC348A7C0F310F14EAA38C6FAD070BA637BDB4A77200FEB231114E111D07A86595A6130291028CDE3A284D9F847EC38AD4
md5: 708A5BC205384633A7B6674EECC7F0F0 | sha1: 01603A7826029293236C67FCE02ACE8D392A0514 | sha256: D8BA5F17B9FFCBF3AEAF3FA1DA226832D2FA90F81ACCE0CD669464E76CE434AC | sha512: 8638845326AB6543338BAA7A644AF8BE33A123E1FC9DA2037158BE7C8D165691CCD06CB3FF73696A30B8801EAB030E81F93DB81216BB3B7E83A320A0DF5AF270
md5: 82CF430B79A7257581CE8BED742D58E6 | sha1: 1368A13957008314909BBDF4DBFBF89BBC04B5F4 | sha256: 7CC5F2B2A7D663A2D5461FE8856268D85195BC3DBD30C5D44D426AABED42974C | sha512: AB2E4E43DDF1002AF293E333C10D5B920037500789A102A682512984C9565315E4EBA1720C6DA4B054BBF460B604FE0E60C21F7398F2009F8E41A640AD60C3E6
md5: 52F4D871306079913ECD8D53EB9ECD05 | sha1: FCA56E0EA208691082A04198B3B517739669F001 | sha256: 76C8700FFC983BBEC07468E354039B21E25E49E7C19F43D7343994C90D4BB7BF | sha512: 4BB9D4161675B6F66C1EADF996DE57BA916497C92E6ED42D0A09DBFE97B243D5B3E9772F942C2B03FA75C2F305CA1584BD9B36D5EC226DCDB2EFC3261809DEA1
md5: 21E82AD181C636E1CF6C24610E2AF08F | sha1: 64F73187472D99632C8579AAC30FA03B20BA232B | sha256: E9C308245FE01D33EF92C7026115A0A930FD865FBE1BFCEFA91E76C6AA32A0B3 | sha512: 8B87A5ECD21A299A3A9A9A06E2C2AA94942B44280C8EECFDC2B92FBD660344F78A48D41DF7859A2F733243A0BFCE59CFCD16D25FDD6DC16279B17EE19EBD4484
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])
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
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",
],
}
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
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()
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")
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()
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;
}
"""
)
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": [],
}
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
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})")
__version__ = "2.7.2"
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)
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",
]
md5: AE5B2E9A3410839B31938F24B6FC5CD8 | sha1: 9F9A14EFC15C904F408A0D364D55A144427E4949 | sha256: CCFFFDDCD3DEFB8D899026298AF9AF43BC186130F8483D77E97C93233D5F27D7 | sha512: 36EA760A7B56EA74174882155EDDFB8726828240FCFC6B34D90ECDB7E50A7E632374DCBC9B2889081C0973CC51F50967E7D692498C4ABD1F2CBA3F7FE8D659CC
md5: 0F8E4992CA92BAAF54CC0B43AACCCE21 | sha1: C7300975DF267B1D6ADCBAC0AC93FD7B1AB49BD2 | sha256: EFF52743773EB550FCC6CE3EFC37C85724502233B6B002A35496D828BD7B280A | sha512: 6E1B223462DC124279BFCA74FD2C66FE18B368FFBCA540C84E82E0F5BCBEA0E10CC243975574FA95ACE437B9D8B03A446ED5EE0C9B1B094147CEFAF704DFE978
md5: 8D4805F0651186046C48D3E2356623DB | sha1: 18C27C000384418ABCF9C88A72F3D55D83BEDA91 | sha256: 007142039F04D04E0ED607BDA53DE095E5BC6A8A10D26ECEDDE94EA7D2D7EEFE | sha512: 1C4895D912F7085D6E46F6776034C9E3D8D7BF934BE858683BF6DEDB13ABCA360BA816D8A5528EC7A3AC6E33010FDB6FC89B2699B5CFEEDAABFDD5DF143DFFD1
ordlookup
pefile
peutils
psutil
md5: D30149D319EFCAECF0A5C5E71EF6CB39 | sha1: 99BEEB17BFC69E8370036F9457EDB4D6812B22E2 | sha256: 9C7FC855D9D1614E70705C7DCC6F4AC3CDCAB5ADFEB6A67D382F5ADE09EADC15 | sha512: B6FB265F0EFED56FDD3455ED620E1FB581D40D2B23B92544CCCBF331E30DC29592C4297E3FAAF437A9D1A33099E0B48D5B2344943FB7B581A448F6C5806ACEC6
md5: 4E6DE7116D8C1C418080580C9795AC15 | sha1: BA948A3C17E12F113477639702A82E96298D1938 | sha256: 554BBC65BFE8C19BA9BBD94F18977A8131109C6A4D64306778BD12250C2C5C56 | sha512: 853E5CD9F753145CCE9DD22F6E6A6E404FEC7F0DB322D2DB4D7B18E9CFC065503BA4FAB4ADC33CBF7D1C2DC0D884413F73CBC28C290D5A41CE7F3F610DAD99BC
md5: 01B946A2EDC5CC166DE018DBB754B69C | sha1: DBE09B7B9AB2D1A61EF63395111D2EB9B04F0A46 | sha256: 88F55D86B50B0A7E55E71AD2D8F7552146BA26E927230DAF2E26AD3A971973C5 | sha512: 65DC3F32FAF30E62DFDECB72775DF870AF4C3A32A0BF576ED1AAAE4B16AC6897B62B19E01DC2BF46F46FBE3F475C061F79CBE987EDA583FEE1817070779860E5
md5: 0FE6D52EB94C848FE258DC0EC9FF4C11 | sha1: 95CC74C64AB80785F3893D61A73B8A958D24DA29 | sha256: 446C48C1224C289BD3080087FE15D6759416D64F4136ADDF30086ABD5415D83F | sha512: C39A134210E314627B0F2072F4FFC9B2CE060D44D3365D11D8C1FE908B3B9403EBDD6F33E67D556BD052338D0ED3D5F16B54D628E8290FD3A155F55D36019A86
md5: 9002E0BEE6455B2322E3E717FE25F9BE | sha1: BC8DF83CC657F0F46A0BFF20565870A435ED1563 | sha256: 24B47C966B6E4A65B3E4DF866D347D3427E9BD709BE550C38224427EB5E143D3 | sha512: 28DDD087B48D5AA96EC39CCC29A4020CF75AE3C5CB6AF9A9571694D73F7AAA4FECB15336C9C7A7D12C93D8BF12EFA4FE4D8D612CD93D72C72130CAE52317D0D9
md5: 83BBECF92FB68795A620B395998B131B | sha1: 026F9E87A5623FE9370C2EEDEF24C765F7312800 | sha256: B04DE4541863BC7D8879040A78889C4849C1B1DA2784C4630F734C146C2998CE | sha512: C63CA8863F63C8F415D685EFF991A1AA67E3457AC2B1F6524DB271C2986F7E79415F98F212E1F7CDD644F41BC48D558661E1136716D63F81675F664E53FDFC70
md5: C9523863236A0125880654AC4440E16C | sha1: 0D5032EB2B0BEAC130EEC57337629A7BA941D6B2 | sha256: B95236241CE0B825857064A386B9A22B0F28FA24F3E246AD63528E87C2688B94 | sha512: E926C35109E6FCCA001367F0880658996A9252C5456487E8ECF766EA21934219E8FF22F26A54FC1882EAB39875228BD254E877EE5189DEA56101E4100FE91577
md5: 1487F1A29360F602863D829C5302854A | sha1: 73488033A373BB145DDA52D200FB46312A344EC8 | sha256: 8E81C9FAFD4711667CE81824DD9D54E128C562557A1AD5B247EAFB85E64A14E0 | sha512: D4BC15288CE37C3F94D44B8730909FAE5D3CDF0963A630B7A1AF183273A4ADB2337F931D1EE68C24F43CD1353FA9E25561B180843AC5F29DABAB580A571461E7
md5: 0A637D5E4D04462F5851DB72DACAFAB1 | sha1: 5AE85F101F1D603FA00E16CB4F24FEC6F6969237 | sha256: F1A184C8FFB602A1550BE4CA0BA06CD97551920B5E802B4FFA74587BF3A550BB | sha512: D86D45F2F6BD40F3C927A2FFBD8F6744F176ED7507C84C81DF4939E0A458831B9EACE737B54D5A268416887E74C130F110689858950240555B9CDA3899720BD6
md5: 0AF3578D91EEFDEFEEBBC89E9456F3A4 | sha1: E45CA7646A123D5FBB890357A375DC89F2AA4D5B | sha256: 1CC89C072C34CD28F8612EFD7A3BF866900B74585405FE590539D5894C7C3401 | sha512: 9AC7B9B5D55E4F511EFCA34C70108E6BFD86099A52528ECC3CC83973EF0AC2DDF3C095D18D66B0EC46A6C3C9B554C9D083CC50B2DF573E36F90E287E4523062C
md5: 5642F7BAEF071E135D7D009A75D472C5 | sha1: 41FFEA0200298425F6120777B39A985276DCC668 | sha256: ACAAA68BF7DC518B4DAA17D92A4E7AD4FCE3B0A8EF7E6E4D2C587C56D44B1DA4 | sha512: 99DFE44F68C51E35465915CBCDD2692B5D5645FA6C39C0BFEECEB2F2C3CEED690BE0311E5DFAE3CC49C5C27D7618ED229B0EAB7302BA8BC190BC7C4421DCB219
md5: 971DBBE854FC6AB78C095607DFAD7B5C | sha1: 1731FB947CD85F9017A95FDA1DC5E3B0F6B42CA2 | sha256: 5E197A086B6A7711BAA09AFE4EA7C68F0E777B2FF33F1DF25A21F375B7D9693A | sha512: B966AAB9C0D9459FADA3E5E96998292D6874A7078924EA2C171F0A1A50B0784C24CC408D00852BEC48D6A01E67E41D017684631176D3E90151EC692161F1814D
md5: 6BC084255A5E9EB8DF2BCD75B4CD0777 | sha1: CF071AD4E512CD934028F005CABE06384A3954B6 | sha256: 1F0F5F2CE671E0F68CF96176721DF0E5E6F527C8CA9CFA98AA875B5A3816D460 | sha512: B822538494D13BDA947655AF791FED4DAA811F20C4B63A45246C8F3BEFA3EC37FF1AA79246C89174FE35D76FFB636FA228AFA4BDA0BD6D2C41D01228B151FD89
md5: FED07A9B7E62FCC655F510169F03F3CD | sha1: A917D6D573D0B8BE802F82C6091960DA983C87E9 | sha256: 34D08A36CC2F2AD6128DD19FDE16EC54166C5C44DF023A79879C5DC841894729 | sha512: F582370497893C9FAFB421B67B6865E3D5B1D280FB6934629E37386117A5FC1CD020CAD7573E31D5A50D2BBE566C3C9D13DFFEBD36EE396E9BF3923C67F194C9
md5: FB8ABEFE7FC7C583A142A4921A889D32 | sha1: 1C605D8BD84CD5FFD18B24E943476A254BFF0668 | sha256: BF0B233139ECF92C7BC1F2327F91E4C7936E873CD458994EB23E8B05DF69AAB4 | sha512: 80CCE310DAAC448EB6FE091FA990600B9523AA65DA027A4884A4A92887B578B1C6B2DFA34C45A90B1ADB465EB1374249831E71E9702151227DB009CA8EC0D63C
md5: 848F16245B325FE16214D5A1F1B62C74 | sha1: DD64CE996AE05EA55D3BD8EA01B76A56CA8A5844 | sha256: 68E8A89B3E717674A25C1FC0E6E36C5DA6CDD69D87E00996726F06B2BB90088C | sha512: 408CE093B85C8C4D56ED769903EDED058C1EB3A8282C96FBBC56BF500AF3BD0D949C2C709FB5526BAAFBB08594307C9F0CE3B9A4EE1BB4C3A20B0826252CDFC3
md5: AFB4933B85924615485FE8CD56C697F6 | sha1: 9F02603CC6A237925790D25F4986CCFCDEAD2C4E | sha256: 0DAC8FE7C7781EFF6414D0B9C6DCB951AB24DF25A35A667B8280FCBEEF9F3B82 | sha512: FAF4EECE905155D82BE1A63B57FAE36BCE5475C89DD73427C75139FA0986A3A317B954455B696F086926FD4982F802671E011E1AB5EA3589C9F91684ACF7F51F
md5: 45C73D3F132424AAFF08BAA431D957DF | sha1: 448B889B4FDD95701D19F87AFEA0BCCCDE217811 | sha256: E1F90F7E760083C40014B72F5E1731B044EC9F881EC240C5B96469F9468EE729 | sha512: F5CB4DC640F0810FECF1C36313DEF60519331B518D36FB5D1097E38A5F8B61222D44785DB7A9EA6B244935864796678995BE139D020AB6D3D7616104C47242ED
md5: 26A61892F91351BC42C8F23937B60006 | sha1: 07EA4037D6303C4FF1FDBD522C159D0317383DCA | sha256: D047B52D2EA32F46DCD45D782C3D63E0F192BB1B3E262718A0DE12DF943F3420 | sha512: AA680A09FD2389B939069A8105AABD102EEE306C683BAD4F9BF8EF4032AE8D4B51226CD325B8648752D1691F2BF4D7139159295077F6CCD469A972C4B4BE7D1E
md5: 0F03D80F018A3FF5212C3DBE86DE41D0 | sha1: DEB4A2618B249DEF9E7ECE9A4B4573A5BE09859C | sha256: 245516C0CFB12D28638C68E0E297E653BC69A3CABB94748DC3FA1AEA67593C7C | sha512: 524B3C01641E7D36B2C2D96A517D65C8C0E0C98D59E8301D0110500956526D27BB88C3677E1121EA80D942114C687B7D048FF28786AA230AFB11B626A182BEA5
md5: 8FF515FE132D355FCCB4693D163B596B | sha1: 21FD63913E633FE3685C30D74DFA37ADAC7ADDD1 | sha256: 0FC3F0BC1EE332197EF2FF8F37ADA1D78A3FB298A46A75344B54035CFA1058E8 | sha512: ED6049EB75116DDC880E9444F132511947055BBDFBBC3DAB17048B6E618529CA8BBE61F7B13B50E773898AA5508DF7225841B414903052BD244811F98A39EB55
md5: 8130AA72D7918FEC0454488637EEF942 | sha1: E0985D25443CE3C0D66D062EFABA659A67A6E4AA | sha256: A781C0FB9AED52BA8EB38D68687818EA0763277F510CFB68D0DCD5EA6BF4E507 | sha512: FDE0261ABFC46D99220AA4F794357A2595BA63978051FAF66C926DDCF0253782D8EC13EF4568C7C8D6E2E30C3671B664AC0D99A9C7431ABF5C380C4DE0F81C67
md5: 31BD978BE967359C1BEFA2171C5051AE | sha1: 9759D00F0D68D06FAF3E62264A6A364374CDE7AD | sha256: 830E97751D3D19FD7CBDCB9BA83F9FD72CED43421609D7B4EDF05207BDC294DC | sha512: F223360DFA28B9333FDE0E93D6DA3E715B674CB6C78813B33AF46B5AD2DDEAEE08747C2CF25F43C8C21B3F575C4ED4F0B194A37AF1EB0B8B00DB982842E9606B
md5: D45BFBD0653B86682C73714D85448414 | sha1: 01810751A1C5F21999EED06EA2D0662B3B0F9CE8 | sha256: 426FEC36A39DB0D2D3EA7DB8D0DF12513CD67A6D511ECFFA91F18572CF968A1B | sha512: 1BA22CE64B178609A30D0845BC41BF5E1135B0D384FE8BCBEDF145BA9BCD67E85B82B2AECB475B1EFBF339811A82FA54D776E5E9BB5E009040AF9536E34B59F1
md5: DF36480CA906A784F193D2768E18B75A | sha1: 05045744159AAE78526EAEE8C339BE69C2B8E1B6 | sha256: 5555FBAB56D72F68FAF84845ABB573BB481556BC99B5564EC85D1C63702A06B4 | sha512: D0D0287F138F5BABEC1C29B624A92482CE7C94287525E587EC1A6D920081C17FE7C8F661D07927DA1EF9D20602BFE5672C9173555AC837EDF162CC3A8F0C36C9
md5: 7DE6AB9104F0A12D03EE2F91EB756138 | sha1: 57AEFDC331957164E7D6C062938F6F4A7D8DD970 | sha256: 18FF77A19555B45EDECF265D9FCCC6513CDEEFCB2036C4B0E2FDFF1F33B2C0B1 | sha512: 4EF75EBC7BD57DE5966B1FE49766994FA73C910DFDF12F0FA0E761434E8160B0BE85BDCF95017E559FF1A2557ACAB4A7737021D4DAA265780AF2C1E807B4B8F4
md5: 4AF90A0D27DFA67791D97B49FAD90A0D | sha1: 2DA8981BE159DDA321548210329586E42012765F | sha256: D28842EE1C2B420CF85F49649B17F523197B4CECFCDD0893C20671863DC18F1C | sha512: 17B0107DA9FFF3804A2B39A28F36F377BA04DB9BC4DEBC72230226A7276003B5F83C6978C297EFA4CA0BF3C1B845EDC014DE6EE88FE11C92CBEA2C730860D341
md5: 0AE46094796365591C19772ED06F3677 | sha1: FF76CBF41786D05983C9F82CBF22D1E9680DB731 | sha256: 210D8FB2ED1FDBFB4105E2025C3EC649C30D30442E0071D6925E95A2335EEDD4 | sha512: 22A84C00B015C0DC57FE88117A3FFCE541ED419D7E9F6FD38396C3868AAD916293F6FF2F87D288EB35F8B4876F6992A5C0C7032F5CBFB6205C35311E69524C62
md5: 55AD1911C5B8F8D51DB9EF02CB00F342 | sha1: 23835492EE81DB8900D9F784E83B3C8277B8F73F | sha256: 061B45CE229496A7BE8B152200D45248CB6C2F47CB725A684FD718D7FA68A8FB | sha512: 11A04149554769BF5F9EEA50E84CB1F596DCC0C33515A756FDC7449B5407F9C4D0C155AC3E58CD464DA8AAEDD8D4CADD9BD37DCEE3A57F2C3A926505F0B44FE3
md5: 6F0167B4B5F5B63992D97EA939477E14 | sha1: C042FC4F672F1F0F3710F197203AF1B192485116 | sha256: 9DC389DCD7C0AC40879B723E1AA51A39B727B983B3BD5540C9D14FBA6756C9F5 | sha512: 0D63B540B81FBB38E006B5D8F49E12A1CCF21DFC3A4A02B0ABC94A2697D94AB861A6665EA9C62980C7CD5B594FF10069BE215D1DE55DF9FDBC5BFE49AF2680CD
md5: F892F5D1E2D1AFA44E61A3C4AB67979C | sha1: DDA553060122E93369B7012D1E870C558679CA58 | sha256: AEF7DBAFCF00C437EBBB9B03A0B67D27334BA98889854172A96C24AC545A28BE | sha512: C5DBA15198E1F82056EA60773A88DBBC723379F266C506564901969D82C3E7296BA384BD0C7E7F3D98FF3A0F750F36FE872A52309E821AB353BDD459BB49D6B7
md5: 19216D581E072FCD6E38B8D0F54F2564 | sha1: B39AC076F5F117192ACBC9848B18C22D353746FB | sha256: 9D217CBB2F2C059850852D466C77D3C641078F9CFBE2C544588B23C2E3561F26 | sha512: 410BB4762E82919AE25716E0773CC15A5873FCD53091484A2FFC4CDE0F1AE7DE51942647F9E579D6651400B693A4C97D6DB2A2FDB4C6BD31DF5043D2C1217273
md5: D6DFB6A9518A57E180980F7A07098D7D | sha1: 6026120461F5CBCD9255670B6A906FD8F5329073 | sha256: FDD54B6C495E9278E73D68203FFF0C300E416E704852908CF5B06666CFFEAD51 | sha512: 2A0195A5038D7530B64A506A70DE3A6B9CB64CA9206006E03F726B4420304E3A76C10FDDA12C8A51F4DBD63E7112FD7E7727A4AB94E7A111587E4248A6B26A62
md5: 7387FE038EA75EB9A57B054FCCFE37BF | sha1: 5C532CBDFD718B5E80AFB2EE8DEA991E84757712 | sha256: 69FD86EA29370697C203F7E12830084F920F490766A8E3045AF52C036A9AD529 | sha512: C46C982B04079ED0B13617B81168598632D6C58D29E23FCBFA064B08E5836866B74880E1A9C01C12670531F13521A21177AAFB10BE0ABB329A79291D7BFF08BD
md5: 7B4BD20267C93E35C49C32AAD05B6B15 | sha1: 860A10D04C8764F540ED34CF08E06F32B7B37611 | sha256: 90BA935A0145EE9AE56267A365CC0088D34FA506B7AFEB2BD1BD78CD33359605 | sha512: 9E05566461D9BE1A234057E1AE9979B6D022189CB49B2C264C9AD253ABEC0F0235919F24159638ACCC45FA3E75AB324DB8EDF737E72DB1EFDA2CFA589531DDFB
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.
md5: 715A098175D3CA1C1DA2DC5756B31860 | sha1: 6B3EC06D679C48BFE4391535A822B58A02D79026 | sha256: 6393121130A3E85D0F6562948024D8614C4C144B84AB102AF711C638344D1599 | sha512: E92EDB98427F594BADEC592493469D45DEAB3B71E4598D544D0B9A1ACFFD5327A19C09029FB79D70971CB0ED0DBA56056BEF8455534D3F16EC35EAC723062F3C
importlib_metadata
[console_scripts]
wheel=wheel.cli:main
[distutils.commands]
bdist_wheel=wheel.bdist_wheel:bdist_wheel
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.
md5: 286B308DF8012A5DFC4276FB16DD9CCC | sha1: 8AE9DF813B281C2BD7A81DE1E4E9CEF8934A9120 | sha256: 2E5FB14B7BF8540278F3614A12F0226E56A7CC9E64B81CBD976C6FCF2F71CBFB | sha512: 24166CC1477CDE129A9AB5B71075A6D935EB6EEBCAE9B39C0A106C5394DED31AF3D93F6DEA147120243F7790D0A0C625A690FD76177DDDAB2D2685105C3EB7B2
md5: 503B3FFA6A5BF45AB34D6D74352F206B | sha1: CC13B85281E5D52413784E0B65A61B1D037C60CC | sha256: 071494856FDAD0042964769AA2FB1DE4EA95C2CFCBE27CC7132293C68D13D710 | sha512: D20B860974161CAA60A62268968AF353AD8063589F57D71F57C91855EB83DA78F40BAE7AA745CC7A945D92EBE08CF244C9560AE93449DE45B20A8B8FFF9F5010
md5: 32DA96115C9D783A0769312C0482A62D | sha1: 2EA840A5FAA87A2FE8D7E5CB4367F2418077D66B | sha256: 052AD6A20D375957E82AA6A3C441EA548D89BE0981516CA7EB306E063D5027F4 | sha512: 616C78B4A24761D4640AE2377B873F7779322EF7BC26F8DE7DA0D880B227C577ED6F5ED794FC733468477B2FCDB7916DEF250E5DC63E79257616F99768419087
md5: C0C0B4C611561F94798B62EB43097722 | sha1: 523F515EED3AF6D50E57A3EAEB906F4CCC1865FE | sha256: 6A99BC0128E0C7D6CBBF615FCC26909565E17D4CA3451B97F8987F9C6ACBC6C8 | sha512: 35DB454DBCC7ED89842C0440B92CE0B0B0DB41DBD5432A36A0B7E1EDDF51704B1F0D6CFF5E3A3B0C3FF5DB3D8632FED000471180AD72E39D8DBE68A757CCDFB0
md5: 747FC8B90E33F5E9048BCF26788B9169 | sha1: AC30AAE15BEA0514C7730B007B68DD841A7F3DDC | sha256: B1B1BB33AF9CC14749936B1F6BAC36B2FFC494EC1A5FB8B12FC9180A6454F545 | sha512: 51416CDA9A18F113D46C3CB06E7ED85717C64325156BE16C4FC78BDDC7A06F0E845B3FEDD2C1CA6C965517530D9CBB9B9497DD1B309BC16011D2E1499BB5D082
md5: 56F958EEBBC62305B4BF690D61C78E28 | sha1: 68D1A227F8BEF856469523364E37AE76B968162A | sha256: A5341A74BBEC1DDC807C0236FCB6BFACEAF3B957EB69CDD9BCA00657EB5E42B6 | sha512: 91B2A31835A5A0610856DF1851C7BB1DEA48A6740C63BD037971473706197E81E9904EAA6042A84FC15AA6AA74AC226463B67E2FA8370CBB8B0C987FED777169
md5: 684D656AADA9F7D74F5A5BDCF16D0EDB | sha1: F7586DA90D101B5EE3FA24F131EE93AB89606919 | sha256: 449058EFC99FCCB9E24D640084D845C78F3F86DD34C5C126CF69E523D6320D75 | sha512: 27FB2ECA382675316FB96D18A1AA6B2792077481BF899CBCC658D71F787876045C05C98ABF129C9670B6A1D2654D57F59E17580139FA7F482EC27234E44D4235
md5: 29873384E13B0A78EE9857604161514B | sha1: 110F60F74B06B3972ACD5908937A40E078636479 | sha256: 5C0D5082FBA1A2A3EB8D5E23073BE25164C19F21304B09CECAAB340DC7198815 | sha512: CA826FF5403700E6D8822634E364E43B14EF829095D8FE365B49731236F696FE86FFA3853CD1801DC3B7800D005A032FE23BBC25BEFE3952EF37790D56DEE3C5
md5: 21FCB8E3D4310346A5DC1A216E7E23CA | sha1: AAB11AEF9075715733E0FCDE9668C6A51654B9E1 | sha256: 4E27C06B84401039D10F800A0F06446B58508784EE366C7C8324D8FE9794E1A5 | sha512: C064550D1723E92512A42CE367ECEF9331A81121305D66199ABCE6E0977152D927F7223F475E22C67E3F64B0F612C5553F112D8CE653C666A98D1980D200A599
md5: AC10151B412BFB22BA9725BC9613C49E | sha1: 4152C799C6FAA2A1606D40E1B9089E67EFAEC951 | sha256: FE09D0408AAB3A6FAA71467F78433DF4C7F3AD0B033BB72EC43BDE85ABF6DCFB | sha512: BF0641606C45285C3F18454E8F855D12963F51D910F20419B76405CC80530C38E17A791C580A9DB6D171A5E1B9999A6DEA661E22A62360D804183F9C0210A107
md5: 3E540EF568215561590DF215801B0F59 | sha1: 3B6DB31A97115C10C33266CCE8FF80463763C7E6 | sha256: 52F29AEBE9886E830DEDC363CD64EB53B6830D84B26E14F1B6FAA655A0900B5D | sha512: 21497A4D1D999A420ED0E146544F4149C72AD4ACA4B869A0EE83267D92AFA07609ECE76A4E95EC706A21580D6544146D0A58C0BAA01AA2C242474A4816108527
md5: D63E2E743EA103626D33B3C1D882F419 | sha1: AF8A162B43F99B943D1C87C9A9E8088816263373 | sha256: 48F16B587C6FAA44A9E073365B19599200B0F0A0CCB70121E76C2DAC4ED53281 | sha512: D3F1450B5DEF3C21F47C5133073E76D2EC05787EB6AE88BB70D3A34BE84F6025540AC017E9415BB22EF36C2FFBFCEA38A28842EEFE366325F3D3CF2CCA1A3CB1
md5: 807DD90BE59EA971DAC06F3AAB4F2A7E | sha1: C4BEA9DB94127EF30E929B395D38175DC74E4DC0 | sha256: 82253E2D6EC717B317E26ED7DD141AADAEA6CB55A9D0FEE022A67D52B404FD06 | sha512: 61B9CF8AC06506002D273B59E2FB66AD96751B10D10FAFF9128749538867D45D561C1CF8DCB8E787CA6AFDC8A1D504CB7012135DFE3A1F3D1FC0B107E4E1A8F9
md5: 363409FBACB1867F2CE45E3C6922DDB4 | sha1: 045B1B90886F4B25D326EA3409A5F79570EAE4B2 | sha256: 7983F811CCD9C99C6DB34B653339605EA45EB384F5E88A8B23CCF9FA5F0170D9 | sha512: C89288DD76821A18E18CE3E67F01B1A9F6A55751832AA1A4B44882F2115474CA131F95F3545ADB9C2D8ECAF3269837126135395C719581A7493AFFAA96EA0DFE
md5: CC0F4A77CCFE39EFC8019FA8B74C06D0 | sha1: 77A713CD5880D5254DD0D1CBFE0D6A45DFC869CE | sha256: AF8AC8AB8B39F53B5DC192FBF58AD704A709DB34E69753B97B83D087202E3A36 | sha512: FFEA0BD7F73B6C02DF6FF37EF39B8E54E480A4CC734FB149ADC5C7410F445EFFD1FDD4F24E4619F7158913A50C28CC73629524D1A7389101A75257D5652C7823
md5: 566CB4D39B700C19DBD7175BD4F2B649 | sha1: BEDE896259B6D52D538C2182AEF87C334FC9C73C | sha256: BCED17D6F081D81EA7CD92F1E071E38F8840E61EE0FE1524221B776BCFA78650 | sha512: 6A26FD59E2C2EC34B673EF257A00D5577F52286D78525D05EFC8A88760FB575BE65C3E94E83396F4978C8734B513AFE7F09D3C49474169144F98ADD406530367
md5: 689F1ABAC772C9E4C2D3BAD3758CB398 | sha1: FE829E05D9F7838D1426F6D4A2F97165C09FD0F7 | sha256: 3301FF340D26495C95108199B67FDF3402742D13070AF8B6BF4EB2E0C5E13781 | sha512: 949404A76C731A92074B37EC0BBA88D873E56327B335B6C300EFF68C2B142E194B58DF59158B9BB92A5984C768B474F5DB5F80F6B610F6CCA78763604041BD82
md5: 93730CB349B216114B444CC9E30932CA | sha1: 689E63330F48877478D428F0E410AC7D69E7150A | sha256: 17C7856BDA73348CA541D01BA4881E4B327B15FB3D2CB90A92CA2BF0E6C4BAFE | sha512: AB312A908256D55CF883E90501DCF88175CC145207D2DA4E3CC8470E7FA3AFDCFD889F0B5C4488ACE6CA3B1F7BBA943F2156E839EDA80981FF592123C5777C34
md5: 47E6FD132F44A4FEB595BD0FDA3C4E1C | sha1: 37C6C2C1FF309DB7273AFC9324A37B716C5CBFDB | sha256: EBD252D21AF9C84128FCA04C994093A5BD6EE857F1581F06F4026FDD6A2C40E0 | sha512: 69C031D4FF2DAC70739F9C188FCA3C6969304F22782ADF5A9C0CA303A3A712630541BDA888EF25D3252B46D43DF56F6E7E03C83D331840088C4224D1A1A512C4
Log in or click on link to see number of positives.
- VCRUNTIME140.dll (5e197a086b6a) - ## / 72
- MSVCP140.dll (88f55d86b50b) - ## / 72
- MSVCP140_1.dll (446c48c1224c) - ## / 72
- VCRUNTIME140_1.dll (1f0f5f2ce671) - ## / 71
- MSVCP140_2.dll (24b47c966b6e) - ## / 72
- libffi-8.dll (eff52743773e) - ## / 72
- opengl32sw.dll (b04de4541863) - ## / 72
- api-ms-win-core-console-l1-1-0.dll (88ace577a9c5) - ## / 72
- api-ms-win-core-datetime-l1-1-0.dll (1bd81dfd1920) - ## / 72
- api-ms-win-core-debug-l1-1-0.dll (591358eb4d15) - ## / 70
- api-ms-win-core-errorhandling-l1-1-0.dll (d56ce7b1cd76) - ## / 71
- api-ms-win-core-fibers-l1-1-0.dll (91d249d7bc0e) - ## / 72
- api-ms-win-core-fibers-l1-1-1.dll (fc9d86cec621) - ## / 72
- api-ms-win-core-file-l1-1-0.dll (1ca895aba4e7) - ## / 72
- api-ms-win-core-file-l1-2-0.dll (4cadbc0c39da) - ## / 72
- api-ms-win-core-file-l2-1-0.dll (93c624b366ba) - ## / 72
- api-ms-win-core-handle-l1-1-0.dll (39095f59c41d) - ## / 72
- api-ms-win-core-heap-l1-1-0.dll (8f105771b236) - ## / 72
- api-ms-win-core-interlocked-l1-1-0.dll (53b25e753ca7) - ## / 67
- api-ms-win-core-kernel32-legacy-l1-1-1.dll (a9b13a1cd1b8) - ## / 70
- api-ms-win-core-libraryloader-l1-1-0.dll (7f39ba298b41) - ## / 72
- api-ms-win-core-localization-l1-2-0.dll (6ee44dd0d851) - ## / 71
- api-ms-win-core-memory-l1-1-0.dll (0512a35316ec) - ## / 67
- api-ms-win-core-namedpipe-l1-1-0.dll (c05f1fffe3b5) - ## / 72
- api-ms-win-core-processenvironment-l1-1-0.dll (c4eca98c3c67) - ## / 68
- api-ms-win-core-processthreads-l1-1-0.dll (5ccb89e93d67) - ## / 71
- api-ms-win-core-processthreads-l1-1-1.dll (4ad565a8ba3e) - ## / 71
- api-ms-win-core-profile-l1-1-0.dll (7809160932f4) - ## / 70
- api-ms-win-core-rtlsupport-l1-1-0.dll (9dc1e91e71c7) - ## / 70
- api-ms-win-core-string-l1-1-0.dll (60fc31d2a0c6) - ## / 73
- api-ms-win-core-synch-l1-1-0.dll (3a72b4f29f39) - ## / 72
- api-ms-win-core-synch-l1-2-0.dll (efc1e4460984) - ## / 67
- api-ms-win-core-sysinfo-l1-1-0.dll (b203d862ddef) - ## / 72
- api-ms-win-core-sysinfo-l1-2-0.dll (2703635d8353) - ## / 71
- api-ms-win-core-timezone-l1-1-0.dll (33f4fddc1810) - ## / 72
- api-ms-win-core-util-l1-1-0.dll (6eda016742a6) - ## / 72
- api-ms-win-crt-conio-l1-1-0.dll (b762061b688a) - ## / 72
- api-ms-win-crt-convert-l1-1-0.dll (ebb2ae5535a6) - ## / 71
- api-ms-win-crt-environment-l1-1-0.dll (17d63275d00b) - ## / 71
- api-ms-win-crt-filesystem-l1-1-0.dll (9a9e2a65a281) - ## / 72
- api-ms-win-crt-heap-l1-1-0.dll (bab9ac3ec83e) - ## / 72
- api-ms-win-crt-locale-l1-1-0.dll (bd14c67ea28e) - ## / 72
- api-ms-win-crt-math-l1-1-0.dll (606d66d82db5) - ## / 72
- api-ms-win-crt-process-l1-1-0.dll (f6156b102038) - ## / 71
- api-ms-win-crt-runtime-l1-1-0.dll (c5902934d026) - ## / 73
- api-ms-win-crt-stdio-l1-1-0.dll (a9c5a153d8c0) - ## / 72
- api-ms-win-crt-string-l1-1-0.dll (25b8f83a7767) - ## / 72
- api-ms-win-crt-time-l1-1-0.dll (7c7f6393f06d) - ## / 71
- api-ms-win-crt-utility-l1-1-0.dll (d8ba5f17b9ff) - ## / 70
- ucrtbase.dll (2e5fb14b7bf8) - ## / 72
- VCRUNTIME140.dll (052ad6a20d37) - ## / 72
- VCRUNTIME140_1.dll (6a99bc0128e0) - ## / 72
- pywintypes313.dll (90ba935a0145) - ## / 70
- Qt6Core.dll (b95236241ce0) - ## / 72
- Qt6Gui.dll (8e81c9fafd47) - ## / 72
- Qt6Network.dll (f1a184c8ffb6) - ## / 72
- Qt6Pdf.dll (1cc89c072c34) - ## / 72
- Qt6Svg.dll (acaaa68bf7dc) - ## / 72
- Qt6Widgets.dll (e7ca7a709520) - ## / 72
- qtuiotouchplugin.dll (34d08a36cc2f) - ## / 72
- qsvgicon.dll (bf0b233139ec) - ## / 72
- qgif.dll (68e8a89b3e71) - ## / 72
- qicns.dll (0dac8fe7c778) - ## / 72
- qico.dll (e1f90f7e7600) - ## / 72
- qjpeg.dll (d047b52d2ea3) - ## / 72
- qpdf.dll (245516c0cfb1) - ## / 72
- qsvg.dll (0fc3f0bc1ee3) - ## / 72
- qtga.dll (a781c0fb9aed) - ## / 72
- qtiff.dll (830e97751d3d) - ## / 72
- qwbmp.dll (426fec36a39d) - ## / 72
- qwebp.dll (5555fbab56d7) - ## / 72
- qminimal.dll (18ff77a19555) - ## / 72
- qoffscreen.dll (d28842ee1c2b) - ## / 72
- qwindows.dll (210d8fb2ed1f) - ## / 72
- qmodernwindowsstyle.dll (061b45ce2294) - ## / 72
- dlss-updater.2.7.3.nupkg (0abb00f2bbcb) - ## / 66
- DLSS_Updater.exe (597647620a74) - ## / 72
- base_library.zip (7cc5f2b2a7d6) - ## / 65
- libcrypto-3.dll (ccfffddcd3de) - ## / 72
- libssl-3.dll (007142039f04) - ## / 72
- python3.dll (fdd54b6c495e) - ## / 72
- python313.dll (69fd86ea2937) - ## / 72
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.3 | 7 | Thursday, May 8, 2025 | Approved | |
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 |
-
- chocolatey-core.extension (≥ 1.1.0)
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.