Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
aekhv committed Dec 20, 2024
1 parent 5c92509 commit ca980ad
Show file tree
Hide file tree
Showing 23 changed files with 1,727 additions and 2 deletions.
36 changes: 34 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,34 @@
# ace-database-viewer
ACE Lab PC-3000 database viewer and extractor
# Ace Database Viewer
This tiny app allows to view and unpack ACE Lab PC-3000 database files (*.pcr & *.fdb).

![](/img/screenshot.png)
***

# Download
See latest release [here](https://github.com/aekhv/ace-database-viewer/releases).
***

# Discuss and bug report
[https://forum.hddguru.com/viewtopic.php?f=7&t=44656](https://forum.hddguru.com/viewtopic.php?f=7&t=44656)
***

# How to build
I wish I could say all you need is Qt and Firebird library... But it's not true, he-he... Unfortunately ACE Lab (R) uses too old Firebird 2.x engine for their database and this engine is 32-bit only. This means Firebird library must be 32-bit too. And this also means your Qt application also must be 32-bit. And this also means you have to use old 32-bit MinGW available in Qt5 only. Yes, you can forget about Qt6. I recommend Qt 5.15.2 + MinGW 8.1 32-bit.

## Requirements
1. Last available Qt5 + MinGW 32-bit. Don't forget about Qt sources, you'll need them too.
2. Firebird 2.x sources, for example from [there](https://www.firebirdsql.org/en/firebird-2-5-4/#Win32). All you need is `Firebird-2.5.4.26856_0_Win32.exe`, just install it with default options.
3. QHexEdit2 widget from [there](https://github.com/simsys/qhexedit2).

## Adding Firebird support to Qt
To add Firebird (IBASE) support to Qt use `make-qt-ibase.cmd` command file. Do not forget to run this file from MinGW 32-bit environment.

## QHexEdit2 widget compilation
To compile QHexEdit2 widget type following commands from MinGW 32-bit environment:
```
qmake qhexedit.pro
mingw32-make
```

## Known troubleshooting
If you see error message - "driver not loaded" - try to copy `fbclient.dll` from the Firebird binaries to the folder of your application.
Binary file added img/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions make-qt-ibase.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@echo off
rem *** Run this script from MinGW 32-bit environment! ***

set fb_bin=c:\Program Files (x86)\Firebird\Firebird_2_5\bin
set fb_include=c:\Program Files (x86)\Firebird\Firebird_2_5\include
set fb_lib=c:\Program Files (x86)\Firebird\Firebird_2_5\lib\fbclient_ms.lib
set sql=c:\Qt\5.15.2\Src\qtbase\src\plugins\sqldrivers

for %%A in ("%fb_include%") do set fb_include=%%~sA
for %%A in ("%fb_lib%") do set fb_lib=%%~sA
set PATH=%PATH%;%fb_bin%
cd /d %sql%

qmake -- IBASE_INCDIR="%fb_include%" IBASE_LIBS="%fb_lib%"
mingw32-make sub-ibase
mingw32-make install
175 changes: 175 additions & 0 deletions src/DataViewDialog/DataViewDialog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/****************************************************************************
**
** This file is part of the Ace Database Viewer project.
** Copyright (C) 2024 Alexander E. <[email protected]>
** License: GNU GPL v2, see file LICENSE.
**
****************************************************************************/

#include "DataViewDialog.h"
#include "ui_DataViewDialog.h"
#include <QFileIconProvider>
#include <QMenuBar>
#include <QClipboard>
#include <QFileDialog>
#include <QMessageBox>

DataViewDialog::DataViewDialog(const QString &fname,
bool plainText,
const QByteArray &rawData,
const QByteArray &rawProfile,
QWidget *parent)
: QDialog(parent),
ui(new Ui::DataViewDialog),
m_fname(fname),
m_rawData(rawData)
{
ui->setupUi(this);

// Remove "?" button from window title
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);

// Window title
setWindowTitle("File Viewer: " + m_fname);

// Window icon
QFileInfo fileInfo(m_fname);
QFileIconProvider iconProvider;
setWindowIcon(iconProvider.icon(fileInfo));

// Buttons
connect(ui->exportButton, &QPushButton::clicked, this, &DataViewDialog::exportToFile);
connect(ui->closeButton, &QPushButton::clicked, this, &QDialog::accept);

// Monospace font definition
QFont mainFont = QApplication::font();
QFont monoFont = QFont("Courier", mainFont.pointSize());

// HEX viewer widget
m_hexEdit = new QHexEdit;
m_hexEdit->setReadOnly(true);
m_hexEdit->setFont(monoFont);

// Hex viewer layout
QVBoxLayout *vHexLayout = new QVBoxLayout;
ui->hexPage->setLayout(vHexLayout);
vHexLayout->setMargin(0);
vHexLayout->addWidget(m_hexEdit);

// Text viewer
m_textEdit = new QPlainTextEdit;
m_textEdit->setReadOnly(true);
m_textEdit->setFont(monoFont);
m_textEdit->setWordWrapMode(QTextOption::NoWrap);

// Text viewer layout
QVBoxLayout *vTextLayout = new QVBoxLayout;
ui->textPage->setLayout(vTextLayout);
vTextLayout->setMargin(0);
vTextLayout->addWidget(m_textEdit);

// Data type selection
if (plainText) {
QTextStream ts(m_rawData); // This trick also detects encoding
m_textEdit->appendPlainText(ts.readAll());
m_textEdit->moveCursor(QTextCursor::Start);
ui->stackedWidget->setCurrentWidget(ui->textPage);
} else {
m_hexEdit->setData(m_rawData);
ui->stackedWidget->setCurrentWidget(ui->hexPage);
}

// Profile model
m_profileModel = new ProfileModel(rawProfile, this);
ui->profileView->setModel(m_profileModel);
ui->profileView->setWordWrap(false);
ui->profileView->horizontalHeader()->setStretchLastSection(true);
ui->profileView->verticalHeader()->setDefaultSectionSize(
ui->profileView->verticalHeader()->fontMetrics().height());
ui->profileView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);

// Resize first column for better view
const int w = ui->profileView->horizontalHeader()->width();
ui->profileView->horizontalHeader()->resizeSection(0, w * 40 / 100);

// Context menu
QAction *m_copyAction = new QAction("Copy as text");
connect(m_copyAction, &QAction::triggered, this, &DataViewDialog::copyAsText);
QAction *m_copyAllAction = new QAction("Copy all as text");
connect(m_copyAllAction, &QAction::triggered, this, &DataViewDialog::copyAllAsText);
m_contextMenu = new QMenu(this);
m_contextMenu->addAction(m_copyAction);
m_contextMenu->addAction(m_copyAllAction);
ui->profileView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->profileView, &QTableView::customContextMenuRequested, this, &DataViewDialog::contextMenuRequested);

// Default tab
ui->tabWidget->setCurrentWidget(ui->dataTab);
}

DataViewDialog::~DataViewDialog()
{
delete ui;
}

void DataViewDialog::contextMenuRequested(QPoint pos)
{
QModelIndex index = ui->profileView->indexAt(pos);

if (!index.isValid())
return;

m_contextMenu->popup(ui->profileView->viewport()->mapToGlobal(pos));
}

void DataViewDialog::copyAsText()
{
QModelIndex index = ui->profileView->selectionModel()->currentIndex();

if (!index.isValid())
return;

QClipboard *clipboard = QGuiApplication::clipboard();
clipboard->setText(index.data().toString());
}

void DataViewDialog::copyAllAsText()
{
QModelIndex index = ui->profileView->selectionModel()->currentIndex();

if (!index.isValid())
return;

QStringList list;
for (int row = 0; row < m_profileModel->rowCount(QModelIndex()); row++) {
QModelIndex param = m_profileModel->index(row, 0);
QModelIndex value = m_profileModel->index(row, 1);
QString line = QString("%1: %2")
.arg(param.data().toString())
.arg(value.data().toString());
list.append(line);
}

QClipboard *clipboard = QGuiApplication::clipboard();
clipboard->setText(list.join(QChar::LineFeed));
}

void DataViewDialog::exportToFile()
{
const QStringList docs = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation);
const QString path = QFileDialog::getSaveFileName(this,
"Export",
docs.first() + QDir::separator() + m_fname);
if (path.isEmpty())
return;

QFile f(path);

if(!f.open(QIODevice::WriteOnly)) {
QMessageBox::critical(this, "Error", "File creation error!");
return;
}

f.write(m_rawData);
f.close();
}
51 changes: 51 additions & 0 deletions src/DataViewDialog/DataViewDialog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/****************************************************************************
**
** This file is part of the Ace Database Viewer project.
** Copyright (C) 2024 Alexander E. <[email protected]>
** License: GNU GPL v2, see file LICENSE.
**
****************************************************************************/

#ifndef DATAVIEWDIALOG_H
#define DATAVIEWDIALOG_H

#include <QDialog>
#include "qhexedit.h"
#include "ProfileModel/ProfileModel.h"
#include <QPlainTextEdit>
#include <QMenu>

namespace Ui {
class DataViewDialog;
}

class DataViewDialog : public QDialog
{
Q_OBJECT

public:
explicit DataViewDialog(const QString &fname,
bool plainText,
const QByteArray &rawData,
const QByteArray &rawProfile,
QWidget *parent = nullptr);
~DataViewDialog();

private:
Ui::DataViewDialog *ui;
QHexEdit *m_hexEdit;
QPlainTextEdit *m_textEdit;
ProfileModel *m_profileModel;
QMenu *m_contextMenu;

QString m_fname;
QByteArray m_rawData;

private slots:
void contextMenuRequested(QPoint pos);
void copyAsText();
void copyAllAsText();
void exportToFile();
};

#endif // DATAVIEWDIALOG_H
82 changes: 82 additions & 0 deletions src/DataViewDialog/DataViewDialog.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DataViewDialog</class>
<widget class="QDialog" name="DataViewDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>650</width>
<height>450</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>1</number>
</property>
<widget class="QWidget" name="dataTab">
<attribute name="title">
<string>Data</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QStackedWidget" name="stackedWidget">
<widget class="QWidget" name="hexPage"/>
<widget class="QWidget" name="textPage"/>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="profileTab">
<attribute name="title">
<string>Profile</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QTableView" name="profileView"/>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="exportButton">
<property name="text">
<string>Export</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="closeButton">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
Loading

0 comments on commit ca980ad

Please sign in to comment.