Skip to content

Commit e16cc9e

Browse files
committed
massive update works without steamtools
1 parent fd0e52f commit e16cc9e

7 files changed

Lines changed: 371 additions & 6 deletions

File tree

CMakeLists.txt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ endif()
3434
# Copy logo.ico to the build directory so it can be found at runtime
3535
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/logo.ico" "${CMAKE_BINARY_DIR}/logo.ico" COPYONLY)
3636

37+
# Qt resource file for bundling dwmapi.dll
38+
set(RESOURCES
39+
dwmapi.qrc
40+
)
41+
3742

3843
# Source files
3944
set(SOURCES
@@ -46,6 +51,7 @@ set(SOURCES
4651
src/workers/luadownloadworker.cpp
4752
src/workers/generatorworker.cpp
4853
src/workers/fixdownloadworker.cpp
54+
src/workers/dllpatchworker.cpp
4955
src/workers/restartworker.cpp
5056
src/utils/paths.cpp
5157
src/utils/colors.cpp
@@ -61,6 +67,7 @@ set(HEADERS
6167
src/workers/luadownloadworker.h
6268
src/workers/generatorworker.h
6369
src/workers/fixdownloadworker.h
70+
src/workers/dllpatchworker.h
6471
src/workers/restartworker.h
6572
src/utils/paths.h
6673
src/utils/colors.h
@@ -83,9 +90,9 @@ if(WIN32 AND APP_ICON_RESOURCE_WINDOWS)
8390
AUTOUIC OFF
8491
AUTORCC OFF
8592
)
86-
add_executable(SteamLuaPatcher WIN32 ${SOURCES} ${HEADERS} $<TARGET_OBJECTS:AppIcon>)
93+
add_executable(SteamLuaPatcher WIN32 ${SOURCES} ${HEADERS} ${RESOURCES} $<TARGET_OBJECTS:AppIcon>)
8794
else()
88-
add_executable(SteamLuaPatcher ${SOURCES} ${HEADERS})
95+
add_executable(SteamLuaPatcher ${SOURCES} ${HEADERS} ${RESOURCES})
8996
endif()
9097

9198

dwmapi.qrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<RCC>
2+
<qresource prefix="/">
3+
<file>dwmapi.dll</file>
4+
</qresource>
5+
</RCC>

src/config.h

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
#include <QStringList>
66
#include <QDir>
77
#include <QFileInfo>
8+
#include <QFile>
9+
#include <QTextStream>
10+
#include <QRegularExpression>
811

912
namespace Config {
1013
const QString APP_VERSION = "1.3.6";
@@ -106,6 +109,140 @@ namespace Config {
106109
QStringList paths = getAllSteamExePaths();
107110
return paths.isEmpty() ? "C:/Program Files (x86)/Steam/Steam.exe" : paths.first();
108111
}
112+
113+
// Find the Steam root directory (e.g. "C:/Program Files (x86)/Steam")
114+
inline QString findSteamDir() {
115+
QStringList exePaths = getAllSteamExePaths();
116+
if (!exePaths.isEmpty()) {
117+
return QFileInfo(exePaths.first()).absolutePath();
118+
}
119+
// Fallback
120+
return "C:/Program Files (x86)/Steam";
121+
}
122+
123+
// Parse a VDF field value: finds "key" "value" and returns value
124+
inline QString parseVdfField(const QString& content, const QString& key) {
125+
QRegularExpression re(QString("\"%1\"\\s+\"([^\"]+)\"").arg(QRegularExpression::escape(key)));
126+
auto match = re.match(content);
127+
if (match.hasMatch()) return match.captured(1);
128+
return QString();
129+
}
130+
131+
// Find a game's install directory by its App ID
132+
// Parses Steam's libraryfolders.vdf to find all library paths,
133+
// then looks for appmanifest_<appId>.acf in each library
134+
inline QString findGameInstallDir(const QString& appId) {
135+
QString steamDir = findSteamDir();
136+
137+
// Collect library paths to check
138+
QStringList libraryPaths;
139+
libraryPaths << steamDir + "/steamapps"; // default library
140+
141+
// Parse libraryfolders.vdf for additional library paths
142+
QString vdfPath = steamDir + "/config/libraryfolders.vdf";
143+
if (!QFile::exists(vdfPath)) {
144+
vdfPath = steamDir + "/steamapps/libraryfolders.vdf";
145+
}
146+
147+
if (QFile::exists(vdfPath)) {
148+
QFile vdfFile(vdfPath);
149+
if (vdfFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
150+
QString content = vdfFile.readAll();
151+
vdfFile.close();
152+
153+
// Match "path" fields in the VDF
154+
QRegularExpression pathRe("\"path\"\\s+\"([^\"]+)\"");
155+
auto it = pathRe.globalMatch(content);
156+
while (it.hasNext()) {
157+
auto match = it.next();
158+
QString libPath = match.captured(1).replace("\\\\", "/");
159+
QString steamapps = libPath + "/steamapps";
160+
if (QDir(steamapps).exists() && !libraryPaths.contains(steamapps)) {
161+
libraryPaths << steamapps;
162+
}
163+
}
164+
}
165+
}
166+
167+
// Search each library for the game's appmanifest
168+
for (const QString& steamappsDir : libraryPaths) {
169+
QString manifestPath = steamappsDir + "/appmanifest_" + appId + ".acf";
170+
if (QFile::exists(manifestPath)) {
171+
QFile manifest(manifestPath);
172+
if (manifest.open(QIODevice::ReadOnly | QIODevice::Text)) {
173+
QString content = manifest.readAll();
174+
manifest.close();
175+
176+
QString installDir = parseVdfField(content, "installdir");
177+
if (!installDir.isEmpty()) {
178+
QString gamePath = steamappsDir + "/common/" + installDir;
179+
if (QDir(gamePath).exists()) {
180+
return gamePath;
181+
}
182+
}
183+
}
184+
}
185+
}
186+
187+
return QString(); // Not found
188+
}
189+
190+
// Check if a game is patched with dwmapi.dll (YasoHook mode)
191+
inline bool isGamePatchedWithDll(const QString& appId) {
192+
QString gameDir = findGameInstallDir(appId);
193+
if (gameDir.isEmpty()) return false;
194+
return QFile::exists(gameDir + "/dwmapi.dll");
195+
}
196+
197+
// Get all game App IDs that are patched with dwmapi.dll
198+
inline QStringList getAllDllPatchedGameIds() {
199+
QStringList patchedIds;
200+
QString steamDir = findSteamDir();
201+
202+
QStringList libraryPaths;
203+
libraryPaths << steamDir + "/steamapps";
204+
205+
QString vdfPath = steamDir + "/config/libraryfolders.vdf";
206+
if (!QFile::exists(vdfPath)) {
207+
vdfPath = steamDir + "/steamapps/libraryfolders.vdf";
208+
}
209+
210+
if (QFile::exists(vdfPath)) {
211+
QFile vdfFile(vdfPath);
212+
if (vdfFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
213+
QString content = vdfFile.readAll();
214+
vdfFile.close();
215+
QRegularExpression pathRe("\"path\"\\s+\"([^\"]+)\"");
216+
auto it = pathRe.globalMatch(content);
217+
while (it.hasNext()) {
218+
auto match = it.next();
219+
QString libPath = match.captured(1).replace("\\\\", "/");
220+
QString steamapps = libPath + "/steamapps";
221+
if (QDir(steamapps).exists() && !libraryPaths.contains(steamapps)) {
222+
libraryPaths << steamapps;
223+
}
224+
}
225+
}
226+
}
227+
228+
for (const QString& steamappsDir : libraryPaths) {
229+
QDir dir(steamappsDir);
230+
QStringList manifests = dir.entryList({"appmanifest_*.acf"}, QDir::Files);
231+
for (const QString& manifest : manifests) {
232+
// Extract app ID from filename: appmanifest_730.acf -> 730
233+
QString appId = manifest.mid(12); // skip "appmanifest_"
234+
appId.chop(4); // remove ".acf"
235+
236+
QString installDir = findGameInstallDir(appId);
237+
if (!installDir.isEmpty() && QFile::exists(installDir + "/dwmapi.dll")) {
238+
patchedIds << appId;
239+
}
240+
}
241+
}
242+
243+
return patchedIds;
244+
}
109245
}
110246

111247
#endif // CONFIG_H
248+

src/mainwindow.cpp

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "workers/indexdownloadworker.h"
77
#include "workers/luadownloadworker.h"
88
#include "workers/generatorworker.h"
9+
#include "workers/dllpatchworker.h"
910
#include "workers/fixdownloadworker.h"
1011
#include "workers/restartworker.h"
1112
#include "utils/colors.h"
@@ -91,7 +92,9 @@ MainWindow::MainWindow(QWidget* parent)
9192
, m_syncWorker(nullptr)
9293
, m_dlWorker(nullptr)
9394
, m_genWorker(nullptr)
95+
, m_dllWorker(nullptr)
9496
, m_restartWorker(nullptr)
97+
, m_patchMode(PatchMode::SteamTools)
9598
, m_fetchingNames(false)
9699
, m_nameFetchSearchId(0)
97100
{
@@ -304,6 +307,28 @@ void MainWindow::initUI() {
304307
sidebarLayout->addWidget(m_btnRemove);
305308

306309
sidebarLayout->addSpacing(6);
310+
311+
// Patch mode toggle
312+
m_btnPatchMode = new GlassButton(MaterialIcons::Flash, "Mode: SteamTools", "Click to switch patch mode", Colors::OUTLINE);
313+
m_btnPatchMode->setFixedHeight(44);
314+
connect(m_btnPatchMode, &QPushButton::clicked, this, [this]() {
315+
if (m_patchMode == PatchMode::SteamTools) {
316+
m_patchMode = PatchMode::YasoHook;
317+
m_btnPatchMode->setText(" Mode: YasoHook");
318+
m_btnPatchMode->setDescription("Patches game folder directly");
319+
m_btnPatchMode->setColor(Colors::ACCENT_GREEN);
320+
m_statusLabel->setText("Switched to YasoHook mode (no SteamTools needed)");
321+
} else {
322+
m_patchMode = PatchMode::SteamTools;
323+
m_btnPatchMode->setText(" Mode: SteamTools");
324+
m_btnPatchMode->setDescription("Uses Lua scripts in stplug-in");
325+
m_btnPatchMode->setColor(Colors::OUTLINE);
326+
m_statusLabel->setText("Switched to SteamTools mode");
327+
}
328+
});
329+
sidebarLayout->addWidget(m_btnPatchMode);
330+
331+
sidebarLayout->addSpacing(4);
307332
m_btnRestart = new GlassButton(MaterialIcons::RestartAlt, "Restart Steam", "Apply Changes", Colors::PRIMARY);
308333
m_btnRestart->setFixedHeight(52);
309334
connect(m_btnRestart, &QPushButton::clicked, this, &MainWindow::doRestart);
@@ -493,9 +518,10 @@ void MainWindow::displayLibrary() {
493518
cancelNameFetches();
494519
m_pendingNameFetchIds.clear();
495520

496-
QStringList pluginDirs = Config::getAllSteamPluginDirs();
497521
QSet<QString> installedAppIds;
498522

523+
// Scan SteamTools patches (stplug-in .lua files)
524+
QStringList pluginDirs = Config::getAllSteamPluginDirs();
499525
for (const QString& dirPath : pluginDirs) {
500526
QDir dir(dirPath);
501527
QStringList luaFiles = dir.entryList({"*.lua"}, QDir::Files);
@@ -504,6 +530,12 @@ void MainWindow::displayLibrary() {
504530
if (!appId.isEmpty()) installedAppIds.insert(appId);
505531
}
506532
}
533+
534+
// Scan YasoHook patches (dwmapi.dll in game directories)
535+
QStringList dllPatched = Config::getAllDllPatchedGameIds();
536+
for (const QString& appId : dllPatched) {
537+
installedAppIds.insert(appId);
538+
}
507539

508540
if (installedAppIds.isEmpty()) {
509541
m_statusLabel->setText("No patches installed found.");
@@ -916,7 +948,14 @@ void MainWindow::onCardClicked(GameCard* card) {
916948
void MainWindow::doAddGame() {
917949
if (m_selectedGame.isEmpty()) return;
918950
bool isSupported = (m_selectedGame["supported"] == "true");
919-
if (isSupported) runPatchLogic(); else runGenerateLogic();
951+
952+
if (m_patchMode == PatchMode::YasoHook) {
953+
// YasoHook mode: copy dwmapi.dll to game directory (works for all games)
954+
runDllPatchLogic();
955+
} else {
956+
// SteamTools mode: download .lua or generate
957+
if (isSupported) runPatchLogic(); else runGenerateLogic();
958+
}
920959
}
921960

922961
void MainWindow::doRemoveGame() {
@@ -925,12 +964,13 @@ void MainWindow::doRemoveGame() {
925964
QString name = m_selectedGame["name"];
926965

927966
if (QMessageBox::question(this, "Remove Patch",
928-
QString("Are you sure you want to remove the patch for %1?\nThis will delete the lua file from your Steam plugin folder.").arg(name),
967+
QString("Are you sure you want to remove the patch for %1?").arg(name),
929968
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) return;
930969

931-
QStringList pluginDirs = Config::getAllSteamPluginDirs();
932970
bool deleted = false;
933971

972+
// Try removing SteamTools patch (.lua from stplug-in)
973+
QStringList pluginDirs = Config::getAllSteamPluginDirs();
934974
for (const QString& dirPath : pluginDirs) {
935975
QDir dir(dirPath);
936976
QString filePath = dir.filePath(appId + ".lua");
@@ -939,6 +979,22 @@ void MainWindow::doRemoveGame() {
939979
}
940980
}
941981

982+
// Try removing YasoHook patch (dwmapi.dll from game directory)
983+
QString gameDir = Config::findGameInstallDir(appId);
984+
if (!gameDir.isEmpty()) {
985+
QString dllPath = gameDir + "/dwmapi.dll";
986+
if (QFile::exists(dllPath)) {
987+
if (QFile::remove(dllPath)) {
988+
deleted = true;
989+
// Restore backup if it exists
990+
QString backupPath = gameDir + "/dwmapi.dll.bak";
991+
if (QFile::exists(backupPath)) {
992+
QFile::rename(backupPath, dllPath);
993+
}
994+
}
995+
}
996+
}
997+
942998
if (deleted) {
943999
m_statusLabel->setText(QString("Removed patch for %1").arg(name));
9441000
displayLibrary();
@@ -1014,6 +1070,36 @@ void MainWindow::onPatchError(QString error) {
10141070
m_terminalDialog->setFinished(false);
10151071
}
10161072

1073+
void MainWindow::runDllPatchLogic() {
1074+
if (m_selectedGame.isEmpty()) return;
1075+
m_btnAddToLibrary->setEnabled(false);
1076+
m_progress->setValue(0);
1077+
m_progress->show();
1078+
m_terminalDialog->clear();
1079+
m_terminalDialog->appendLog(QString("[YasoHook] Initializing patch for: %1 (%2)").arg(m_selectedGame["name"]).arg(m_selectedGame["appid"]), "INFO");
1080+
m_terminalDialog->show();
1081+
1082+
m_dllWorker = new DllPatchWorker(m_selectedGame["appid"], this);
1083+
connect(m_dllWorker, &DllPatchWorker::finished, this, &MainWindow::onDllPatchDone);
1084+
connect(m_dllWorker, &DllPatchWorker::progress, [this](qint64 dl, qint64 total) {
1085+
if (total > 0) m_progress->setValue(static_cast<int>(dl * 100 / total));
1086+
});
1087+
connect(m_dllWorker, &DllPatchWorker::status, [this](QString msg) { m_statusLabel->setText(msg); });
1088+
connect(m_dllWorker, &DllPatchWorker::log, m_terminalDialog, &TerminalDialog::appendLog);
1089+
connect(m_dllWorker, &DllPatchWorker::error, this, &MainWindow::onPatchError);
1090+
m_dllWorker->start();
1091+
}
1092+
1093+
void MainWindow::onDllPatchDone(QString gameDir) {
1094+
m_progress->hide();
1095+
m_btnAddToLibrary->setEnabled(true);
1096+
m_statusLabel->setText("YasoHook Patch Installed!");
1097+
m_terminalDialog->appendLog(QString("DLL installed to: %1").arg(gameDir), "SUCCESS");
1098+
m_terminalDialog->setFinished(true);
1099+
m_btnAddToLibrary->setDescription(QString("Re-patch %1").arg(m_selectedGame["name"]));
1100+
m_btnAddToLibrary->setColor(Colors::ACCENT_GREEN);
1101+
}
1102+
10171103
void MainWindow::runGenerateLogic() {
10181104
if (m_selectedGame.isEmpty()) return;
10191105
m_btnAddToLibrary->setEnabled(false);

0 commit comments

Comments
 (0)