simple parsing of resources
This commit is contained in:
parent
2405fa9bc1
commit
69f7ed2a2e
|
@ -5,12 +5,12 @@
|
|||
#include <memory>
|
||||
|
||||
struct RegisteredResource {
|
||||
RegisteredResource(int i, const unsigned char* resourceStruct, const unsigned char* resourceName, const unsigned char* resourceData);
|
||||
RegisteredResource(int version, const unsigned char* resourceStruct, const unsigned char* resourceName, const unsigned char* resourceData);
|
||||
|
||||
int i; // what is this? version? type? (always just 2?)
|
||||
int version;
|
||||
const unsigned char* resourceStruct;
|
||||
const unsigned char* resourceName;
|
||||
const unsigned char* resourceData;
|
||||
};
|
||||
|
||||
std::map<uint64_t, std::shared_ptr<RegisteredResource>>* registeredResources = nullptr;
|
||||
extern std::map<uint64_t, std::shared_ptr<RegisteredResource>>* registeredResources;
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileSystemModel>
|
||||
#include <QVector>
|
||||
|
||||
class QFileInfo;
|
||||
|
||||
class QtResourceModel : public QAbstractItemModel {
|
||||
|
||||
public:
|
||||
QtResourceModel( const QDir& root, QObject* parent = nullptr );
|
||||
virtual ~QtResourceModel();
|
||||
|
||||
virtual QModelIndex index(int, int, const QModelIndex&) const override;
|
||||
|
||||
virtual QModelIndex parent(const QModelIndex&) const override;
|
||||
|
||||
virtual int rowCount(const QModelIndex&) const override;
|
||||
|
||||
virtual int columnCount(const QModelIndex&) const override;
|
||||
|
||||
virtual QVariant data(const QModelIndex&, int) const override;
|
||||
|
||||
private:
|
||||
using internalData = QFileInfo;
|
||||
|
||||
QDir root;
|
||||
QVector<internalData*> internals;
|
||||
};
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
#include <QMainWindow>
|
||||
|
||||
#include <QString>
|
||||
#include <QStringRef>
|
||||
|
||||
namespace Ui {
|
||||
class ResourceExplorer;
|
||||
}
|
||||
|
@ -15,6 +18,37 @@ public:
|
|||
~ResourceExplorer();
|
||||
|
||||
private:
|
||||
struct MemoryMap {
|
||||
const unsigned char* begin;
|
||||
const unsigned char* end;
|
||||
QString path;
|
||||
};
|
||||
|
||||
enum ResourceFlags {
|
||||
NoFlags = 0,
|
||||
Compressed = 1,
|
||||
Directory = 2
|
||||
};
|
||||
|
||||
struct ResourceNode {
|
||||
QString name;
|
||||
ResourceFlags flags;
|
||||
uint32_t size;
|
||||
const unsigned char* start;
|
||||
uint16_t country;
|
||||
uint16_t language;
|
||||
uint64_t lastModTimeMS;
|
||||
uint32_t hash;
|
||||
std::vector<ResourceNode*> children;
|
||||
};
|
||||
|
||||
using data = const unsigned char*;
|
||||
|
||||
void rebuild();
|
||||
std::vector<MemoryMap> readMemoryMaps();
|
||||
std::vector<ResourceExplorer::ResourceNode*> parseResource(int version, data library, data names, data data );
|
||||
|
||||
|
||||
Ui::ResourceExplorer *ui;
|
||||
};
|
||||
|
||||
|
|
|
@ -30,12 +30,11 @@ INCLUDEPATH += $$PWD/include/
|
|||
SOURCES += \
|
||||
src/injector.cpp \
|
||||
src/qtdebugger.cpp \
|
||||
src/qtresourcemodel.cpp \
|
||||
src/resourceexplorer.cpp
|
||||
|
||||
HEADERS += \
|
||||
include/injector.cpp \
|
||||
include/qtdebugger.h \
|
||||
include/qtresourcemodel.h \
|
||||
include/resourceexplorer.h
|
||||
|
||||
FORMS += \
|
||||
|
|
|
@ -10,8 +10,10 @@
|
|||
|
||||
using registerFunc = bool (*) (int, const unsigned char*, const unsigned char*, const unsigned char*);
|
||||
|
||||
RegisteredResource::RegisteredResource(int i, const unsigned char* resourceStruct, const unsigned char* resourceName, const unsigned char* resourceData) :
|
||||
i( i ), resourceStruct( resourceStruct ), resourceName( resourceName ), resourceData( resourceData ) { }
|
||||
std::map<uint64_t, std::shared_ptr<RegisteredResource>>* registeredResources = nullptr;
|
||||
|
||||
RegisteredResource::RegisteredResource(int version, const unsigned char* resourceStruct, const unsigned char* resourceName, const unsigned char* resourceData) :
|
||||
version( version ), resourceStruct( resourceStruct ), resourceName( resourceName ), resourceData( resourceData ) { }
|
||||
|
||||
int QApplication::exec() {
|
||||
// init debugger
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
#include "qtresourcemodel.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
|
||||
QtResourceModel::QtResourceModel( const QDir& root, QObject* parent ) :
|
||||
QAbstractItemModel( parent ),
|
||||
root( root.absolutePath() )
|
||||
{
|
||||
}
|
||||
|
||||
QtResourceModel::~QtResourceModel() {
|
||||
for( internalData* inter : internals ) {
|
||||
delete inter;
|
||||
}
|
||||
internals.clear();
|
||||
}
|
||||
|
||||
QModelIndex QtResourceModel::index(int row, int column, const QModelIndex& parent) const {
|
||||
if (!hasIndex(row, column, parent))
|
||||
return QModelIndex();
|
||||
|
||||
internalData* parentItem;
|
||||
|
||||
if (parent.isValid()) {
|
||||
parentItem = static_cast<internalData*>(parent.internalPointer());
|
||||
} else {
|
||||
parentItem = new internalData( root, "." );
|
||||
// internals.append( parentItem );
|
||||
}
|
||||
|
||||
if (parentItem->isDir()) {
|
||||
QDir parentDir( parentItem->absolutePath() );
|
||||
QFileInfoList entrys = parentDir.entryInfoList(QDir::Filter::AllEntries, QDir::SortFlag::DirsFirst | QDir::SortFlag::Name );
|
||||
if ( entrys.size() < row ) {
|
||||
internalData* childItem = new internalData( entrys.at(row) );
|
||||
// internals.append( childItem );
|
||||
|
||||
return createIndex(row, column, childItem);
|
||||
}
|
||||
}
|
||||
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
QModelIndex QtResourceModel::parent(const QModelIndex& index) const {
|
||||
if (!index.isValid())
|
||||
return QModelIndex();
|
||||
|
||||
internalData* childItem = static_cast<internalData*>(index.internalPointer());
|
||||
internalData* parentItem = new internalData( childItem->dir().absolutePath() );
|
||||
// internals.append( parentItem );
|
||||
|
||||
if (parentItem->absolutePath() == root.absolutePath())
|
||||
return QModelIndex();
|
||||
|
||||
return createIndex(0, 0, parentItem);
|
||||
}
|
||||
|
||||
int QtResourceModel::rowCount(const QModelIndex& parent) const {
|
||||
if (parent.column() > 0)
|
||||
return 0;
|
||||
|
||||
|
||||
QDir parentDir;
|
||||
if( parent.isValid() ) {
|
||||
internalData* parentItem = static_cast<internalData*>(parent.internalPointer());
|
||||
if ( ! parentItem->isDir()) {
|
||||
return 0;
|
||||
}
|
||||
parentDir = QDir( parentItem->absolutePath() );
|
||||
} else {
|
||||
parentDir = root;
|
||||
}
|
||||
|
||||
QFileInfoList entrys = parentDir.entryInfoList(QDir::Filter::AllEntries, QDir::SortFlag::DirsFirst | QDir::SortFlag::Name );
|
||||
return entrys.size();
|
||||
}
|
||||
|
||||
int QtResourceModel::columnCount(const QModelIndex& parent) const {
|
||||
return 2;
|
||||
}
|
||||
|
||||
QVariant QtResourceModel::data(const QModelIndex& index, int role) const {
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
internalData* item = static_cast<internalData*>( index.internalPointer() );
|
||||
|
||||
if (role == Qt::DisplayRole) {
|
||||
if ( index.column() == 0)
|
||||
return QVariant( item->fileName() );
|
||||
if ( index.column() == 1)
|
||||
return QVariant( item->size() );
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
|
@ -1,15 +1,207 @@
|
|||
#include "resourceexplorer.h"
|
||||
#include "ui_resourceexplorer.h"
|
||||
|
||||
#include "qtresourcemodel.h"
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QDebug>
|
||||
|
||||
ResourceExplorer::ResourceExplorer(QWidget *parent) : QMainWindow( parent ), ui(new Ui::ResourceExplorer) {
|
||||
#include <endian.h>
|
||||
#include <list>
|
||||
|
||||
#include "injector.h"
|
||||
|
||||
#pragma pack (push, 1)
|
||||
|
||||
|
||||
struct ResourceLibraryEntry {
|
||||
uint32_t nameOffset;
|
||||
uint16_t flags; // use ResourceFlags
|
||||
union Content {
|
||||
struct Directory {
|
||||
uint32_t childCount;
|
||||
uint32_t childOffset;
|
||||
} directory;
|
||||
|
||||
struct File {
|
||||
uint16_t country;
|
||||
uint16_t language;
|
||||
uint32_t dataOffset;
|
||||
} file;
|
||||
} content;
|
||||
};
|
||||
|
||||
struct ResourceLibraryEntryV2 : public ResourceLibraryEntry {
|
||||
uint64_t lastModTimeMS;
|
||||
};
|
||||
|
||||
struct ResouceNameEntry {
|
||||
uint16_t length;
|
||||
uint32_t hash;
|
||||
// string content
|
||||
char str[0];
|
||||
};
|
||||
|
||||
struct ResouceDataEntry {
|
||||
uint32_t length;
|
||||
char data[0];
|
||||
};
|
||||
|
||||
#pragma pack (pop)
|
||||
|
||||
ResourceExplorer::ResourceExplorer( QWidget* parent ) : QMainWindow( parent ), ui( new Ui::ResourceExplorer ) {
|
||||
this->ui->setupUi(this);
|
||||
|
||||
QtResourceModel* model = new QtResourceModel( QDir( ":/" ), this );
|
||||
this->ui->treeView->setModel( model );
|
||||
this->rebuild();
|
||||
}
|
||||
|
||||
ResourceExplorer::~ResourceExplorer() {
|
||||
delete this->ui;
|
||||
}
|
||||
|
||||
void ResourceExplorer::rebuild() {
|
||||
this->ui->treeWidget->clear();
|
||||
|
||||
if ( !registeredResources || registeredResources->empty() ) {
|
||||
QTreeWidgetItem* resItem = new QTreeWidgetItem();
|
||||
resItem->setText( 0, "No Resources Captured" );
|
||||
this->ui->treeWidget->addTopLevelItem( resItem );
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<MemoryMap> memMaps = readMemoryMaps();
|
||||
|
||||
for ( auto it : *registeredResources ) {
|
||||
// find correct memory map
|
||||
auto memMapIt = std::find_if(memMaps.cbegin(), memMaps.cend(), [c = it.second](const MemoryMap& mm) { return mm.begin < c->resourceStruct && mm.end > c->resourceStruct; } );
|
||||
QString fileName( "<file unknown>" );
|
||||
QString filePath( "" );
|
||||
if ( memMapIt != memMaps.cend() ) {
|
||||
fileName = QFileInfo( memMapIt->path ).fileName();
|
||||
filePath = memMapIt->path;
|
||||
}
|
||||
|
||||
QTreeWidgetItem* resItem = new QTreeWidgetItem( );
|
||||
|
||||
resItem->setText( 0, fileName );
|
||||
resItem->setToolTip( 0, filePath );
|
||||
|
||||
std::shared_ptr<RegisteredResource> r = it.second;
|
||||
std::vector<ResourceNode*> resources = parseResource(r->version, r->resourceStruct, r->resourceName, r->resourceData);
|
||||
|
||||
this->ui->treeWidget->addTopLevelItem( resItem );
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ResourceExplorer::MemoryMap> ResourceExplorer::readMemoryMaps() {
|
||||
QFile maps("/proc/self/maps");
|
||||
|
||||
if ( !maps.open( QIODevice::ReadOnly | QIODevice::Text ) ) {
|
||||
qWarning() << "failed to read MemoryMaps";
|
||||
return {};
|
||||
}
|
||||
|
||||
// QTextStream stream(&maps);
|
||||
std::vector<MemoryMap> out;
|
||||
out.reserve( maps.size() / 100 ); // approximation of row count
|
||||
do {
|
||||
QString line = QString::fromUtf8( maps.readLine() );
|
||||
if( line.isEmpty() ) continue;
|
||||
|
||||
QVector<QStringRef> parts = line.splitRef(' ', QString::SplitBehavior::SkipEmptyParts );
|
||||
if ( parts.size() != 6) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QVector<QStringRef> borders = parts.at(0).split('-');
|
||||
if ( borders.size() != 2) continue;
|
||||
|
||||
bool ok = true;
|
||||
const unsigned char* begin = (const unsigned char*) borders.at( 0 ).toULongLong( &ok, 16);
|
||||
if ( !ok ) continue;
|
||||
const unsigned char* end = (const unsigned char*) borders.at( 1 ).toULongLong( &ok, 16);
|
||||
if ( !ok ) continue;
|
||||
|
||||
if ( begin > end ) std::swap( begin, end );
|
||||
|
||||
out.push_back( MemoryMap{ begin, end, parts.at( 5 ).toString().simplified() } );
|
||||
} while( !maps.atEnd() );
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static QString readUTF16String(const char16_t* ptr, uint32_t size) {
|
||||
char16_t* newptr = new char16_t[size];
|
||||
|
||||
for ( uint32_t i = 0; i < size; ++i ) {
|
||||
newptr[i] = be16toh(ptr[i]);
|
||||
}
|
||||
|
||||
QString out = QString::fromUtf16(newptr, size);
|
||||
delete[] newptr;
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<ResourceExplorer::ResourceNode*> ResourceExplorer::parseResource(int version, ResourceExplorer::data libraryIndex, ResourceExplorer::data names, ResourceExplorer::data data ) {
|
||||
std::vector<ResourceNode*> out;
|
||||
|
||||
if ( version > 2 ) {
|
||||
qWarning() << "resource has unknown version: " << version;
|
||||
return out;
|
||||
}
|
||||
|
||||
const uint32_t libraryOffset = ( version == 1 ) ? sizeof(ResourceLibraryEntry) : sizeof(ResourceLibraryEntryV2);
|
||||
|
||||
using task_t = std::pair<uint32_t, std::vector<ResourceNode*>*>;
|
||||
std::list<task_t> pendingOffsets;
|
||||
pendingOffsets.push_back( {0, &out} );
|
||||
while ( pendingOffsets.size() > 0) {
|
||||
task_t task = pendingOffsets.front();
|
||||
uint32_t offset = task.first;
|
||||
pendingOffsets.pop_front();
|
||||
|
||||
const ResourceLibraryEntryV2* entry = (const ResourceLibraryEntryV2*) &libraryIndex[ offset * libraryOffset ];
|
||||
|
||||
ResourceNode* newNode = new ResourceNode();
|
||||
newNode->flags = (ResourceFlags) be16toh( entry->flags );
|
||||
|
||||
|
||||
if( offset == 0) {
|
||||
newNode->name = ":";
|
||||
newNode->hash = 0;
|
||||
} else {
|
||||
uint32_t nameOffset = be32toh(entry->nameOffset);
|
||||
uint16_t nameLen = be16toh(*(uint16_t*) (names + nameOffset));
|
||||
newNode->name = readUTF16String((const char16_t*) (names + nameOffset + 6), nameLen);
|
||||
newNode->hash = *((uint16_t*) ( names + nameOffset + 2));
|
||||
}
|
||||
|
||||
if ( newNode->flags & ResourceFlags::Directory ) {
|
||||
// directory
|
||||
newNode->size = 0;
|
||||
newNode->country = 0;
|
||||
newNode->language = 0;
|
||||
|
||||
uint32_t childCount = be32toh( entry->content.directory.childCount );
|
||||
uint32_t childOffset = be32toh( entry->content.directory.childOffset );
|
||||
for ( uint32_t i = 0; i < childCount; ++i ) {
|
||||
pendingOffsets.push_back( { i + childOffset, &(newNode->children) } );
|
||||
}
|
||||
} else {
|
||||
// file
|
||||
newNode->country = be16toh(entry->content.file.country);
|
||||
newNode->language = be16toh(entry->content.file.language);
|
||||
uint32_t dataOffset = be32toh(entry->content.file.dataOffset);
|
||||
const unsigned char* dataBegin = data + dataOffset;
|
||||
newNode->size = *((const uint32_t*) dataBegin);
|
||||
newNode->start = dataBegin + 4;
|
||||
}
|
||||
|
||||
if ( version >= 2 ) {
|
||||
newNode->lastModTimeMS = entry->lastModTimeMS;
|
||||
}
|
||||
|
||||
task.second->push_back( newNode );
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,27 @@
|
|||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QTreeView" name="treeView"/>
|
||||
<widget class="QTreeWidget" name="treeWidget">
|
||||
<property name="columnCount">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<attribute name="headerMinimumSectionSize">
|
||||
<number>50</number>
|
||||
</attribute>
|
||||
<attribute name="headerDefaultSectionSize">
|
||||
<number>200</number>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">1</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">2</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
|
Loading…
Reference in New Issue