Initial support for Cyberpunk 2077. Enh, it works for me.

This commit is contained in:
Daniel O'Neill 2025-08-22 05:41:09 -07:00
parent 1393a796b2
commit d2e14a1630
15 changed files with 243 additions and 118 deletions

View file

@ -65,7 +65,7 @@ bool ArchiveManager::findFile(const QString &qpath, ModLibrary **library, ModEnt
return m_cache[qpath].m_valid;
}
qDebug() << "ArchiveManager::findFile: Searching for file" << qpath;
//qDebug() << "ArchiveManager::findFile: Searching for file" << qpath;
ArchiveCachedEntry cacheEntry;
QStringList qparts = qpath.split(QDir::separator());
if( 0 == qpath.length() )
@ -108,15 +108,15 @@ bool ArchiveManager::findFile(const QString &qpath, ModLibrary **library, ModEnt
if( parts.length() > qparts.length() )
{
qDebug() << "ArchiveManager::findFile: Found directory in" << mod->m_archivePath << "aka" << m_archives[lib]->getPath();
//qDebug() << "ArchiveManager::findFile: Found directory in" << mod->m_archivePath << "aka" << m_archives[lib]->getPath();
*library = NULL;
*modentry = NULL;
*archive = NULL;
cacheEntry.m_valid = true;
//cacheEntry.m_isdir = true;
qDebug() << "Caching...";
//qDebug() << "Caching...";
m_cache[qpath] = cacheEntry;
qDebug() << "Done.";
//qDebug() << "Done.";
return true;
}
@ -124,7 +124,7 @@ bool ArchiveManager::findFile(const QString &qpath, ModLibrary **library, ModEnt
*library = lib;
*modentry = mod;
*archive = m_archives[lib];
qDebug() << "ArchiveManager::findFile: Found file" << mod->m_installedPath << "in mod" << mod->m_archivePath << "aka" << m_archives[lib]->getPath();
//qDebug() << "ArchiveManager::findFile: Found file" << mod->m_installedPath << "in mod" << mod->m_archivePath << "aka" << m_archives[lib]->getPath();
cacheEntry.m_valid = true;
cacheEntry.m_library = lib;
@ -136,7 +136,7 @@ bool ArchiveManager::findFile(const QString &qpath, ModLibrary **library, ModEnt
return true;
}
}
qDebug() << "ArchiveManager::findFile: Couldn't find file" << qpath << "(Cached)";
//qDebug() << "ArchiveManager::findFile: Couldn't find file" << qpath << "(Cached)";
m_cache[qpath] = cacheEntry;
return false;
}

View file

@ -52,7 +52,8 @@ QList<ModLibrary *> Database::archiveList(int profileId)
*
* Y'dig? Real swell. Stay cool, hep cat.
*/
QString qstr = QString("SELECT modId, filename FROM mods WHERE enabled != 0 AND moddir IS NULL AND modId IN (SELECT modId FROM profile_selections WHERE profileId=%1) ORDER BY idx DESC").arg(profileId);
//QString qstr = QString("SELECT modId, filename FROM mods WHERE enabled != 0 AND moddir IS NULL AND modId IN (SELECT modId FROM profile_selections WHERE profileId=%1) ORDER BY idx DESC").arg(profileId);
QString qstr = QString("SELECT modId, filename FROM mods WHERE moddir IS NULL AND modId IN (SELECT modId FROM profile_selections WHERE profileId=%1) ORDER BY idx DESC").arg(profileId);
qDebug() << qstr;
QSqlQuery q = QSqlQuery(qstr, m_database);
//QSqlQuery q = QSqlQuery("SELECT modId, filename FROM mods WHERE enabled != 0 ORDER BY idx DESC", m_database);
@ -78,7 +79,8 @@ QList<ModLibrary *> Database::archiveList(int profileId)
QStringList Database::proxyList(int profileId)
{
QStringList results;
QString qstr = QString("SELECT moddir FROM mods WHERE enabled != 0 AND NOT moddir IS NULL AND modId IN (SELECT modId FROM profile_selections WHERE profileId=%1) ORDER BY idx DESC").arg(profileId);
//QString qstr = QString("SELECT moddir FROM mods WHERE enabled != 0 AND NOT moddir IS NULL AND modId IN (SELECT modId FROM profile_selections WHERE profileId=%1) ORDER BY idx DESC").arg(profileId);
QString qstr = QString("SELECT moddir FROM mods WHERE NOT moddir IS NULL AND modId IN (SELECT modId FROM profile_selections WHERE profileId=%1) ORDER BY idx DESC").arg(profileId);
qDebug() << qstr;
QSqlQuery q = QSqlQuery(qstr, m_database);
while( q.next() )

View file

@ -41,7 +41,7 @@ void ProxyCacheEntry::clean()
if( c->m_lastAccess > now )
continue;
qDebug() << "ProxyCacheEntry::clean(): Freeing chunk #" << slot;
//qDebug() << "ProxyCacheEntry::clean(): Freeing chunk #" << slot;
m_chunks.take(slot)->deleteLater();
}
m_mutex.unlock();
@ -117,19 +117,22 @@ void FSProxy::clear()
m_filesMutex.unlock();
}
void FSProxy::setup(Database *db, int profileId, const QString &gamename, const QString &gamedir)
void FSProxy::setup(Database *db, int profileId, const QString &gamename, const QString &gamedir, const QString &createPath)
{
m_db = db;
m_profileId = profileId;
m_gamename = gamename;
m_gamedir = gamedir;
m_createPath = createPath;
reload();
}
void FSProxy::reload() {
clear();
addRoot( QString("/media/Sabrent/modding/%1/created/%2").arg(m_gamename).arg(m_profileId) );
// Order is important here:
addRoot( m_createPath );
QStringList others = m_db->proxyList(m_profileId);
for( const QString &e : others )
@ -156,7 +159,7 @@ ProxyCacheEntry *FSProxy::cacheFile(const QString &qpath)
QString fullpath = e->resolvePath(qpath);
ret = ::stat(fullpath.toStdString().c_str(), &st);
if( 0 == ret ) {
qDebug() << "FSProxy::getattr: Caching" << qpath << "to" << fullpath;
//qDebug() << "FSProxy::getattr: Caching" << qpath << "to" << fullpath;
ProxyCacheEntry *ne = new ProxyCacheEntry(this);
ne->m_logicalpath = qpath;
ne->m_filepath = fullpath;
@ -195,7 +198,7 @@ bool FSProxy::getattr(const QString &qpath, struct stat *st)
{
if( m_cache.contains(qpath) ) {
ProxyCacheEntry *ent = m_cache[qpath];
qDebug() << "FSProxy::getattr: Using cached entry of" << qpath << "to" << ent->m_filepath;
//qDebug() << "FSProxy::getattr: Using cached entry of" << qpath << "to" << ent->m_filepath;
memcpy( st, &ent->m_stat, sizeof(struct stat) );
return true;
}
@ -259,7 +262,7 @@ int FSProxy::open(const QString &qpath, bool createIfNeeded)
return -EACCES;
}
qDebug() << "FSProxy::open:" << fullpath;
//qDebug() << "FSProxy::open:" << fullpath;
if( !f->open(QIODevice::ReadWrite) )
{
qDebug() << "FSProxy::open: failed:" << f->errorString();
@ -323,7 +326,7 @@ qint64 ProxyCacheEntry::sparseRead(int fd, char *buf, size_t size, off_t offset)
{
ProxyCacheEntryChunk *chunk;
if( m_chunks.contains(cslot) ) {
qDebug() << "ProxyCacheEntry::sparseRead: Chunk #" << cslot << "already cached. Yay!";
//qDebug() << "ProxyCacheEntry::sparseRead: Chunk #" << cslot << "already cached. Yay!";
chunk = m_chunks[cslot];
} else {
if( !m_parent->m_openFiles.contains(fd) )
@ -348,7 +351,7 @@ qint64 ProxyCacheEntry::sparseRead(int fd, char *buf, size_t size, off_t offset)
chunk = new ProxyCacheEntryChunk(cstart, this);
m_chunks[cslot] = chunk;
chunk->m_data = f->m_file->read(CACHE_BLOCK_SIZE);
qDebug() << "ProxyCacheEntry::sparseRead: Chunk #" << cslot << "cached.";
//qDebug() << "ProxyCacheEntry::sparseRead: Chunk #" << cslot << "cached.";
}
if( size < rlen )

View file

@ -88,6 +88,7 @@ class FSProxy : public QObject
int m_profileId;
QString m_gamename;
QString m_gamedir;
QString m_createPath;
public:
//QString m_path;
@ -99,7 +100,7 @@ public:
explicit FSProxy(QObject *parent = nullptr);
void setup(Database *db, int profileId, const QString &gamename, const QString &gamedir);
void setup(Database *db, int profileId, const QString &gamename, const QString &gamedir, const QString &createPath);
//void setRoot(const QString &path);
QStringList readdir(const QString &path);

View file

@ -0,0 +1,13 @@
#include "fuseinterface.h"
#include <errno.h>
#include <string.h>
FuseInterface::FuseInterface(QObject *parent)
: QObject{parent}
{}
FuseInterface::~FuseInterface()
{
}

View file

@ -0,0 +1,45 @@
#ifndef FUSEINTERFACE_H
#define FUSEINTERFACE_H
#include <QObject>
#include <QDir>
#include <QSocketNotifier>
#include <QDateTime>
extern "C" {
#define FUSE_USE_VERSION 31
#include <fuse3/fuse.h>
#include <fuse3/fuse_lowlevel.h>
#include <fcntl.h>
#include <stdarg.h>
#include <unistd.h>
}
class ArchiveManager;
class FSProxy;
class Database;
class FuseInterface : public QObject
{
Q_OBJECT
int m_fuse_id;
struct fuse *m_fuse;
struct fuse_session *m_fuse_session;
ArchiveManager *m_archive;
FSProxy *m_proxy;
Database *m_database;
QMap< QString, QStringList > m_dircache;
QMap<QString, struct stat *> m_shortcuts;
QSocketNotifier *m_notifier;
public:
explicit FuseInterface(QObject *parent = nullptr);
~FuseInterface();
signals:
};
#endif // FUSEINTERFACE_H

View file

@ -27,7 +27,7 @@ QSocketNotifier *g_notifier;
void dolog(const char *fmt, ...)
{
//return;
return;
char ostr[8192];
va_list ap;
@ -557,7 +557,6 @@ static off_t qmfuse_lseek(const char *path, off_t off, int whence, struct fuse_f
return g_proxy->lseek(fi->fh, off, whence);
}
static const struct fuse_operations qmfuse_oper = {
.getattr = qmfuse_getattr,
.readlink = NULL,

View file

@ -11,6 +11,7 @@ Item {
property alias enabled: intobj.enabled
property alias gamePath: intobj.gamePath
property alias vfsPath: intobj.vfsPath
property alias vfsCreatedPath: intobj.vfsCreatedPath
property alias modsPath: intobj.modsPath
property alias modStagingPath: intobj.modStagingPath
property alias userDataPath: intobj.userDataPath
@ -25,6 +26,7 @@ Item {
property bool enabled: false
property string gamePath
property string vfsPath
property string vfsCreatedPath
property string modsPath
property string modStagingPath
property string userDataPath

View file

@ -36,6 +36,7 @@ Dialog {
ent.modstagingpath = sobj.modStagingPath || `/DATA/SteamLibrary/steamapps/common/${g['gamedir']}/QuickmodStaging`;
ent.gamepath = sobj.gamePath || `/DATA/SteamLibrary/steamapps/common/${g['gamedir']}`;
ent.vfsPath = sobj.vfsPath || 'unset';
ent.vfsCreatedPath = sobj.vfsCreatedPath || 'unset';
ent.userpath = sobj.userDataPath || `/DATA/SteamLibrary/steamapps/compatdata/${g['steamid']}/pfx/drive_c/users/steamuser`;
ent.dbpath = sobj.dbPath || `/DATA/modding/${g['gamedir']}/Quickmod.sqlite`;
}
@ -54,6 +55,7 @@ Dialog {
sobj.modStagingPath = ent.modstagingpath;
sobj.gamePath = ent.gamepath;
sobj.vfsPath = ent.vfsPath;
sobj.vfsCreatedPath = ent.vfsCreatedPath;
sobj.userDataPath = ent.userpath;
sobj.dbPath = ent.dbpath;
}
@ -203,6 +205,7 @@ Dialog {
property alias modstagingpath: modStagingPath.text
property alias gamepath: gamePath.text
property alias vfsPath: vfsPathEntry.text
property alias vfsCreatedPath: vfsCreatedPathEntry.text
property alias userpath: userDataPath.text
property alias dbpath: dbPath.text
@ -250,6 +253,11 @@ Dialog {
text: qsTr('VFS Mountpoint:')
enabled: cbEnabled.checked
}
Label {
height: gameItem.rowHeight
text: qsTr('VFS Sandbox:')
enabled: cbEnabled.checked
}
Label {
height: gameItem.rowHeight
text: qsTr('User Data Directory:')
@ -279,7 +287,7 @@ Dialog {
width: columnEdits.width
ToolTip.visible: hovered
ToolTip.text: qsTr('This is where the mod archive itself is stashed for safe-keeping.\n\nEg: /DATA/SteamLibrary/steamapps/common/%1/Quickmods').arg(gamename)
ToolTip.text: qsTr('This is where the mod archive itself is stashed for safe-keeping.\n\nEg: /DATA/modding/%1/mods').arg(gamename)
}
TextField {
id: modStagingPath
@ -288,7 +296,7 @@ Dialog {
width: columnEdits.width
ToolTip.visible: hovered
ToolTip.text: qsTr('This is where mods are extracted to and linked to the game from.\n\nEg: /DATA/SteamLibrary/steamapps/common/%1/QuickmodStaging').arg(gamename)
ToolTip.text: qsTr('This is where mods are extracted to and linked to the game from.\n\nEg: /DATA/modding/%1/staging').arg(gamename)
}
TextField {
id: gamePath
@ -308,6 +316,15 @@ Dialog {
ToolTip.visible: hovered
ToolTip.text: qsTr('Where to mount the VFS. Typically, this will be the original GOG or Steam path.\n\nEg: /DATA/SteamLibrary/steamapps/common/%1').arg(steamid)
}
TextField {
id: vfsCreatedPathEntry
enabled: cbEnabled.checked
height: gameItem.rowHeight
width: columnEdits.width
ToolTip.visible: hovered
ToolTip.text: qsTr('Where to place new (or modified) files written to the VFS.\n\nEg: /DATA/modding/%1/created').arg(gamename)
}
TextField {
id: userDataPath
enabled: cbEnabled.checked
@ -350,7 +367,7 @@ Dialog {
}
ToolTip.visible: hovered
ToolTip.text: qsTr('This is where the mod archive itself is stashed for safe-keeping.\n\nEg: /DATA/SteamLibrary/steamapps/common/%1/Quickmods').arg(gamename)
ToolTip.text: qsTr('This is where the mod archive itself is stashed for safe-keeping.\n\nEg: /DATA/modding/%1/mods').arg(gamename)
}
Button {
text: qsTr('Browse...')
@ -362,7 +379,7 @@ Dialog {
}
ToolTip.visible: hovered
ToolTip.text: qsTr('This is where mods are extracted to and linked to the game from.\n\nEg: /DATA/SteamLibrary/steamapps/common/%1/QuickmodStaging').arg(gamename)
ToolTip.text: qsTr('This is where mods are extracted to and linked to the game from.\n\nEg: /DATA/modding/%1/staging').arg(gamename)
}
Button {
text: qsTr('Browse...')
@ -374,7 +391,7 @@ Dialog {
}
ToolTip.visible: hovered
ToolTip.text: qsTr('Where the game is installed.\n\nEg: /DATA/SteamLibrary/steamapps/common/%1').arg(gamename)
ToolTip.text: qsTr('Where the game is installed.\n\nEg: /DATA/SteamLibrary/steamapps/common/%1-REAL').arg(gamename)
}
Button {
text: qsTr('Browse...')
@ -388,6 +405,18 @@ Dialog {
ToolTip.visible: hovered
ToolTip.text: qsTr('Where to mount the VFS. Typically, this will be the original GOG or Steam path.\n\nEg: /DATA/SteamLibrary/steamapps/common/%1').arg(steamid)
}
Button {
text: qsTr('Browse...')
height: gameItem.rowHeight
enabled: cbEnabled.checked
onClicked: {
vfsCreatedPathDialogue.currentFolder = 'file://' + vfsCreatedPathEntry.text;
vfsCreatedPathDialogue.open();
}
ToolTip.visible: hovered
ToolTip.text: qsTr('Where to place new (or modified) files written to the VFS.\n\nEg: /DATA/modding/%1/created').arg(gamename)
}
Button {
height: gameItem.rowHeight
text: qsTr('Browse...')
@ -450,6 +479,15 @@ Dialog {
}
}
Platform.FolderDialog {
id: vfsCreatedPathDialogue
//visible: false
title: qsTr("Select the VFS sandbox path...")
onAccepted: {
vfsCreatedPathEntry.text = (''+folder).substring(7);
}
}
Platform.FolderDialog {
id: userDataPathDialogue

View file

@ -55,7 +55,7 @@ function installedEntity(gamename)
}
const udpath = sobj.userDataPath;
if( !udpath || udpath.length === 0 )
if( ( !udpath || udpath.length === 0 ) && currentGameEntry['appdir'] )
{
console.log(`No userdata path, nope.`);
return false;

View file

@ -61,7 +61,7 @@ ApplicationWindow {
Action {
text: qsTr("&Enable mods in game");
onTriggered: Game.enableMods();
enabled: currentGame && gameDefinitions[currentGame] && gameDefinitions[currentGame]['enableMods']
enabled: currentGameEntry && currentGameEntry['enableMods']
}
MenuSeparator { }
Action {
@ -135,6 +135,7 @@ ApplicationWindow {
}
TabButton {
text: qsTr('Plugins')
enabled: currentGameEntry && currentGameEntry['plugins']
}
}
ComboBox {
@ -165,7 +166,7 @@ ApplicationWindow {
if( (''+currentGame).length > 0 && (''+sobj.vfsPath).length > 0 && sobj.profileId > 0 )
{
console.log(`Trying to mount: [${currentGame}] [${sobj.vfsPath}] [${sobj.profileId}]`);
FUSEManager.mount(db.conn, currentGame, sobj.vfsPath, sobj.profileId);
FUSEManager.mount(db.conn, currentGame, sobj.profileId);
}
}
}
@ -200,6 +201,9 @@ ApplicationWindow {
onDeleteMod: function(mod) { Mods.deleteMod(mod); }
onWriteRequested: function(plugins) {
if( !currentGameEntry['loadorder'] )
return;
Plugins.writePlugins(plugins);
Plugins.writeLoadOrder(plugins);
}
@ -212,6 +216,9 @@ ApplicationWindow {
onDisableMod: function(mod) { Plugins.disableMod(mod); }
onWriteRequested: function(plugins) {
if( !currentGameEntry['loadorder'] )
return;
Plugins.writePlugins(plugins);
Plugins.writeLoadOrder(plugins);
}
@ -387,44 +394,6 @@ ApplicationWindow {
id: gameSettings
}
function fileList(path, notfirstrun)
{
let list = [];
const arr = File.dirContents(path);
arr.forEach( function(e) {
if( e['fileName'] !== '.' && e['fileName'] !== '..' )
{
//console.log("Found: " + JSON.stringify(e,null,2));
if( e['type'] === 'file' && notfirstrun )
{
const fp = ""+e['filePath'];
if( !fp.endsWith('.bsa')
&& !fp.endsWith('.mp3')
&& !fp.endsWith('.bik')
&& !fp.endsWith('.sdp') )
//console.log("Found: " + JSON.stringify(e,null,2));
list.push(e);
}
else if( e['type'] === 'dir' )
list = list.concat( fileList(e['filePath'], true) );
}
} );
if( !notfirstrun )
{
let outlist = [];
list.forEach( function(e) {
console.log( e['filePath'].substring( path.length+1 ) );
outlist.push( e['filePath'].substring( path.length+1 ) );
} );
return outlist;
}
return list;
}
property var modMasterList: []
onModMasterListChanged: {
/*
@ -433,6 +402,9 @@ ApplicationWindow {
modTable.model.append(ent);
}
*/
console.log("Mod master listed updated, rebuilding list!");
modTable.model.clear();
for( let a=0; a < modMasterList.length; a++ )
{
let nent = modMasterList[a];
@ -589,13 +561,46 @@ ApplicationWindow {
const adroot = `${this['paths']['userData']}/${this['confdir']}`;
const inipath = `${adroot}/${this['ini']}`;
Utils.configSet(inipath, 'Archive', 'SInvalidationFile', 'ArchiveInvalidation.txt');
//Utils.configSet(inipath, 'Archive', 'iRetainFilenameOffsetTable', '1');
//Utils.configSet(inipath, 'Archive', 'iRetainFilenameStringTable', '1');
//Utils.configSet(inipath, 'Archive', 'iRetainDirectoryStringTable', '1');
//Utils.configSet(inipath, 'Archive', 'bCheckRuntimeCollisions', '0');
Utils.configSet(inipath, 'Archive', 'bInvalidateOlderFiles', '1');
//Utils.configSet(inipath, 'Archive', 'bUseArchives', '1');
//Utils.configSet(inipath, 'Archive', 'SArchiveList', 'Fallout - Textures.bsa, Fallout - Textures2.bsa, Fallout - Meshes.bsa, Fallout - Sound.bsa, Fallout - Voices1.bsa, Fallout - Misc.bsa,Update.bsa');
let fileList = function(path, notfirstrun)
{
let list = [];
const arr = File.dirContents(path);
arr.forEach( function(e) {
if( e['fileName'] !== '.' && e['fileName'] !== '..' )
{
//console.log("Found: " + JSON.stringify(e,null,2));
if( e['type'] === 'file' && notfirstrun )
{
const fp = ""+e['filePath'];
if( !fp.endsWith('.bsa')
&& !fp.endsWith('.mp3')
&& !fp.endsWith('.bik')
&& !fp.endsWith('.sdp') )
//console.log("Found: " + JSON.stringify(e,null,2));
list.push(e);
}
else if( e['type'] === 'dir' )
list = list.concat( fileList(e['filePath'], true) );
}
} );
if( !notfirstrun )
{
let outlist = [];
list.forEach( function(e) {
console.log( e['filePath'].substring( path.length+1 ) );
outlist.push( e['filePath'].substring( path.length+1 ) );
} );
return outlist;
}
return list;
}
// Regenerate ArchiveInvalidation.txt:
const p = `${this['paths']['gamePath']}/${this['datadir']}`;
@ -605,6 +610,20 @@ ApplicationWindow {
return true;
}
},
{
'steamid': '1091500',
'name': 'Cyberpunk 2077',
'gamedir': 'Cyberpunk 2077',
'gameexe': 'bin/x64/Cyberpunk2077.exe',
'appdir': false,
'confdir': false,
'ini': false,
'datadir': false,
'plugins': false,
'loadorder': false,
'builtin': false,
'enableMods': false
}
]
}

View file

@ -202,36 +202,40 @@ function checkOverwrites(fileMap)
function enableMod(mod)
{
let sobj = gameSettings.objFor(currentGame);
const files = db.getFiles(mod['modId']);
console.log(`Enabling "${mod['name']}"...`);
let updatePlugins = false;
let plugins = Plugins.readPlugins();
files.forEach( function(f) {
let baseName = f['dest'];
let parts = f['dest'].split(/\//g);
if( parts.length > 1 )
baseName = parts.pop();
const baseNameLC = baseName.toLowerCase();
if( baseNameLC.endsWith('.esp') || baseNameLC.endsWith('.esl') || baseNameLC.endsWith('.esm') )
{
const found = Plugins.enableMod({'filename':baseName});
if( !found )
{
const ent = { 'enabled':true, 'filename':baseName };
plugins.push(ent);
updatePlugins = true;
}
}
} );
if( updatePlugins )
if( currentGameEntry['plugins'] || currentGameEntry['loadorder'] )
{
Plugins.writePlugins(plugins);
Plugins.writeLoadOrder(plugins);
Plugins.readPlugins();
const files = db.getFiles(mod['modId']);
let updatePlugins = false;
let plugins = Plugins.readPlugins();
files.forEach( function(f) {
let baseName = f['dest'];
let parts = f['dest'].split(/\//g);
if( parts.length > 1 )
baseName = parts.pop();
const baseNameLC = baseName.toLowerCase();
if( baseNameLC.endsWith('.esp') || baseNameLC.endsWith('.esl') || baseNameLC.endsWith('.esm') )
{
const found = Plugins.enableMod({'filename':baseName});
if( !found )
{
const ent = { 'enabled':true, 'filename':baseName };
plugins.push(ent);
updatePlugins = true;
}
}
} );
if( updatePlugins )
{
Plugins.writePlugins(plugins);
Plugins.writeLoadOrder(plugins);
Plugins.readPlugins();
}
}
statusBar.text = qsTr('Enabled "%1".').arg(mod['name']);
@ -249,6 +253,9 @@ function rereadMods()
function removePlugin(mod)
{
if( !currentGameEntry['plugins'] && !currentGameEntry['loadorder'] )
return;
const files = db.getFiles(mod['modId']);
let updatePlugins = false;
@ -732,11 +739,11 @@ function installBasicMod(mod)
const p = e['pathname'].toLowerCase().split(/\//g);
if( !topdirs.includes(p[0]) )
topdirs.push(p[0]);
if( p[0] === currentGameEntry['datadir'].toLowerCase() )
if( currentGameEntry['datadir'] && p[0] === currentGameEntry['datadir'].toLowerCase() )
hasDataDir = true;
} );
if( topdirs.length === 1 && !hasDataDir )
if( topdirs.length === 1 && !hasDataDir && currentGameEntry['datadir'] )
{
for( let a=0; a < filelist.length; a++ )
{
@ -786,7 +793,7 @@ function installBasicMod(mod)
console.log("Popping-top, mod author is spanking me, Smalls.");
}
if( !hasDataDir )
if( !hasDataDir && currentGameEntry['datadir'] )
{
// Ex: "MCM/Config/Snappy_HouseK/config.json"
// -> "Data/MCM/Config/Snappy_HouseK/config.json"

View file

@ -7,6 +7,7 @@ QT += core quick widgets sql dbus qml
QMAKE_LDFLAGS += -fPIE
SOURCES += \
FuseMounter/fuseinterface.cpp \
src/file.cpp \
src/http.cpp \
src/main.cpp \
@ -19,6 +20,7 @@ SOURCES += \
src/sqldatabasemodel.cpp
HEADERS += \
FuseMounter/fuseinterface.h \
src/file.h \
src/fomodreader.h \
src/http.h \

View file

@ -41,36 +41,30 @@ FUSEManager::~FUSEManager()
delete m_manager;
}
bool FUSEManager::mount(const QVariant &dbconn, const QString &gameName, const QString &destPath, int profileId)
bool FUSEManager::mount(const QVariant &dbconn, const QString &gameName, int profileId)
{
m_currentGameName = gameName;
m_targetDir = destPath;
m_profileId = profileId;
QSettings s;
s.beginGroup(m_currentGameName);
s.beginGroup(gameName);
QString dbpath = s.value("dbPath").toString();
QString gamedir = s.value("gamePath").toString();
QString modsdir = s.value("modsPath").toString();
QString destPath = s.value("vfsPath").toString();
QString createPath = s.value("vfsCreatedPath").toString();
m_currentGameName = gameName;
m_targetDir = destPath;
m_profileId = profileId;
SqlDatabaseConnection *db = dbconn.value<SqlDatabaseConnection *>();
m_db = new Database(this);
m_db->setDatabase(db->m_db);
/*
if( !m_db->open(dbpath) )
{
m_db->deleteLater();
m_db = NULL;
return false;
}
*/
if( !m_proxy )
m_proxy = new FSProxy(this);
m_proxy->clear();
m_proxy->setup(m_db, m_profileId, m_currentGameName, gamedir);
m_proxy->setup(m_db, m_profileId, m_currentGameName, gamedir, createPath);
if( m_manager ) {
m_manager->deleteLater();

View file

@ -29,7 +29,7 @@ public:
explicit FUSEManager(QObject *parent = nullptr);
~FUSEManager();
Q_INVOKABLE bool mount(const QVariant &dbhandle, const QString &gameName, const QString &destPath, int profileId);
Q_INVOKABLE bool mount(const QVariant &dbhandle, const QString &gameName, int profileId);
Q_INVOKABLE void unmount();
private slots: