Initial support for Cyberpunk 2077. Enh, it works for me.
This commit is contained in:
parent
1393a796b2
commit
d2e14a1630
15 changed files with 243 additions and 118 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() )
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
13
FuseMounter/fuseinterface.cpp
Normal file
13
FuseMounter/fuseinterface.cpp
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#include "fuseinterface.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
FuseInterface::FuseInterface(QObject *parent)
|
||||
: QObject{parent}
|
||||
{}
|
||||
|
||||
FuseInterface::~FuseInterface()
|
||||
{
|
||||
|
||||
}
|
||||
45
FuseMounter/fuseinterface.h
Normal file
45
FuseMounter/fuseinterface.h
Normal 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
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
111
qml/main.qml
111
qml/main.qml
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
69
qml/mods.js
69
qml/mods.js
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in a new issue