quickmod/FuseMounter/modarchive.cpp
Daniel O'Neill 1393a796b2 Implement FUSE VFS as a very early implementation. It can, does, and will break your game.
Updated QML and C++ in various places to build and work with new Qt (6.4+)
Minor bugfixes, possibly caused by the Qt version bump, and mostly in JS logic.
Tried (again) to get mod sorting working by dragging them within the list.
Initial support for Profiles. (To switch between FO4 and FOLON, for example.)
Initial "Launch Game" stuff, but it's far from done. Don't use it (yet).
2025-08-20 07:57:00 -07:00

244 lines
6.4 KiB
C++

#include "modarchive.h"
//#include <archive_entry.h>
#include <fcntl.h>
#include <unistd.h>
#include <QDebug>
#define BLOCKSIZE 10240
Archive::Archive(const QString &arcpath, QObject *parent)
: QObject{parent},
m_stream{NULL},
m_archive{NULL},
m_arcpath{arcpath},
m_suicidal{false}
{
if( !this->open() )
return;
qDebug() << "Archive:" << arcpath;
while( ar_parse_entry(m_archive) )
{
const char *entryname = ar_entry_get_name(m_archive);
if( strlen(entryname) <= 0 )
continue;
/*
if( (st->st_mode & S_IFMT) == S_IFDIR )
continue;
*/
time64_t filetime = ar_entry_get_filetime(m_archive) / 100000000;
ArchiveCacheEntry *cacheEntry = new ArchiveCacheEntry(this);
cacheEntry->m_offset = ar_entry_get_offset(m_archive);
memset( (void*)&cacheEntry->m_stat, 0, sizeof(struct stat) );
cacheEntry->m_stat.st_size = ar_entry_get_size(m_archive);
cacheEntry->m_stat.st_mode = S_IFREG | 0755;
cacheEntry->m_stat.st_nlink = 1;
cacheEntry->m_stat.st_uid = geteuid();
cacheEntry->m_stat.st_gid = getegid();
cacheEntry->m_stat.st_atim.tv_sec = filetime;
cacheEntry->m_stat.st_mtim.tv_sec = filetime;
cacheEntry->m_stat.st_ctim.tv_sec = filetime;
QString lpath = QString(entryname).toLower();
m_cache.insert( lpath, cacheEntry );
//qDebug() << "\tAdding" << lpath << "/" << cacheEntry->m_stat.st_size << "bytes at offset" << cacheEntry->m_offset;
}
}
Archive::~Archive()
{
for( ArchiveCacheEntry *ent : m_cache.values() )
ent->deleteLater();
close();
}
bool Archive::open()
{
m_stream = ar_open_file(m_arcpath.toStdString().c_str());
if( !m_stream ) {
qDebug() << "Failed to open archive stream:" << m_arcpath;
return false;
}
if( m_arcpath.endsWith("7z", Qt::CaseInsensitive) )
m_archive = ar_open_7z_archive(m_stream);
else if( m_arcpath.endsWith("rar", Qt::CaseInsensitive) )
m_archive = ar_open_rar_archive(m_stream);
else if( m_arcpath.endsWith("zip", Qt::CaseInsensitive) )
m_archive = ar_open_zip_archive(m_stream, false);
else {
qDebug() << "Unsupported archive type:" << m_arcpath;
return false;
}
if( !m_archive )
qDebug() << "Failed to open archive:" << m_arcpath;
return NULL != m_archive;
}
bool Archive::close()
{
if( !m_archive )
return false;
m_archive = NULL;
m_stream = NULL;
ar_close_archive(m_archive);
ar_close(m_stream);
return true;
}
bool Archive::contains(const QString &path)
{
QString lpath = path.toLower();
return m_cache.contains(lpath);
}
QMap< QString, ArchiveCacheEntry * > *Archive::getEntries()
{
return &m_cache;
}
QByteArray *Archive::getEntry(const QString &path)
{
QString lpath = path.toLower();
if( !m_cache.contains(lpath) ) {
qDebug() << " 918273918273918273 NOT FOUND:" << lpath;
return NULL; // Not found.
}
else if( m_cache[lpath]->m_loading ) {
//qDebug() << QString("getEntry(%1) -> (Still Loading...)\n").arg(path);
qDebug() << "Archive::getEntry: Already loading, chill.";
return NULL;
}
else if( m_cache[lpath]->m_cached ) {
//qDebug() << QString("getEntry(%1) -> CACHED\n").arg(path);
m_cache[lpath]->m_lastAccess = time(NULL);
m_cache[lpath]->m_accessCount++;
m_cache[lpath]->m_locked = true;
m_cache[lpath]->m_mutex.lock();
return &m_cache[lpath]->m_data;
}
m_cache[lpath]->m_loading = true;
m_mutex.lock();
qDebug() << "Cheaty skip to" << m_cache[lpath]->m_offset << "bytes...";
if( !ar_parse_entry_at(m_archive, m_cache[lpath]->m_offset) ) {
qDebug() << "Archive::getEntry: ar_parse_entry_at failed!";
m_cache[lpath]->m_loading = false;
m_mutex.unlock();
return NULL;
}
//int64_t entrysize = archive_entry_size(entry);
QByteArray obuf;
char buff[BLOCKSIZE * 256];
size_t len = sizeof(buff);
if( len > (size_t)m_cache[lpath]->m_stat.st_size )
len = m_cache[lpath]->m_stat.st_size;
while( ar_entry_uncompress(m_archive, buff, len ) ) {
obuf.append(buff, len);
len = m_cache[lpath]->m_stat.st_size - obuf.length();
if( len <= 0 )
break;
if( len > sizeof(buff) )
len = sizeof(buff);
}
m_mutex.unlock();
if( 0 > obuf.length() ) {
m_cache[lpath]->m_loading = false;
return NULL;
}
m_cache[lpath]->m_data = obuf;
m_cache[lpath]->m_cached = true;
m_cache[lpath]->m_accessCount++;
m_cache[lpath]->m_loading = false;
m_cache[lpath]->m_lastAccess = time(NULL);
m_cache[lpath]->m_locked = true;
m_cache[lpath]->m_mutex.lock();
return &m_cache[lpath]->m_data;
}
void Archive::releaseEntry(const QString &path)
{
QString lpath = path.toLower();
if( !m_cache.contains(lpath) ) {
qDebug() << " releaseEntry: ---1231231 NOT FOUND:" << lpath;
return; // Not found.
}
/*
m_cache[lpath]->m_cached = false;
m_cache[lpath]->m_mutex.unlock();
m_cache[lpath]->m_locked = false;
m_cache[lpath]->m_data.clear();
*/
m_cache[lpath]->m_mutex.unlock();
m_cache[lpath]->m_locked = false;
if( m_suicidal )
checkIfLifeIsWorthIt();
}
ArchiveCacheEntry *Archive::cache(const QString &path)
{
QString lpath = path.toLower();
if( !m_cache.contains(lpath) )
return NULL;
return m_cache[lpath];
}
void Archive::cleanCache(time_t olderThan)
{
//return;
QStringList keys = m_cache.keys();
for( const QString &k : keys )
{
if( !m_cache[k]->m_cached )
continue;
if( m_cache[k]->m_loading )
continue;
if( m_cache[k]->m_locked )
continue;
if( m_cache[k]->m_lastAccess > olderThan )
continue;
qDebug() << "Archive::cleanCache: Clearing cache entry for:" << k;
m_cache[k]->m_mutex.lock();
m_cache[k]->m_cached = false;
m_cache[k]->m_data.clear();
m_cache[k]->m_mutex.unlock();
}
}
void Archive::killme()
{
m_suicidal = true;
checkIfLifeIsWorthIt();
}
void Archive::checkIfLifeIsWorthIt()
{
for( QString k : m_cache.keys() )
{
if( m_cache[k]->m_loading )
return;
if( m_cache[k]->m_locked )
return;
}
deleteLater();
}