diff --git a/src/AppleSAWS.pro b/src/AppleSAWS.pro new file mode 100644 index 0000000..008ac96 --- /dev/null +++ b/src/AppleSAWS.pro @@ -0,0 +1,42 @@ + +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = AppleSAWS +TEMPLATE = app + +SOURCES += main.cpp \ + diskfile.cxx \ + sector.cxx \ + vtoc.cxx \ + catalogsector.cxx \ + applestring.cxx \ + tracksectorlist.cxx \ + filedescriptiveentry.cxx \ + applesoftfile.cxx \ + genericfile.cxx \ + disassembler.cxx \ + binaryfile.cxx \ + catalogwidget.cxx \ + mainwindow.cxx + +HEADERS += \ + diskfile.h \ + sector.h \ + vtoc.h \ + util.h \ + catalogsector.h \ + applestring.h \ + tracksectorlist.h \ + filedescriptiveentry.h \ + applesoftfile.h \ + genericfile.h \ + disassembler.h \ + binaryfile.h \ + catalogwidget.h \ + mainwindow.h + +FORMS += \ + catalogwidget.ui \ + mainwindow.ui diff --git a/src/applesoftfile.cxx b/src/applesoftfile.cxx new file mode 100644 index 0000000..3d19e37 --- /dev/null +++ b/src/applesoftfile.cxx @@ -0,0 +1,195 @@ +#include "applesoftfile.h" +#include + +ApplesoftFile::ApplesoftFile(QByteArray data) : GenericFile(data) +{ + if (m_tokens.size() == 0) + { + makeTokens(); + } + + if (!data.isEmpty()) + { + setData(data); + } +} + +void ApplesoftFile::setData(QByteArray data) +{ + GenericFile::setData(data); + + quint8 addlo = m_data.at(0); + quint8 addhi = m_data.at(1); + m_length = addlo + (addhi * 256); + m_data.remove(0,2); + m_detokenized = detokenize(); +} + +QList ApplesoftFile::detokenize() +{ + QList retval; + +/* + qDebug() << "Length: " << m_length; + for (int idx = 0; idx < m_length; idx += 8) + { + qDebug() + << QString("%1 (%2): %3 %4 %5 %6 %7 %8 %9 %10") + .arg((quint16) idx + 0x801,4,16,QChar('0')).toUpper() + .arg((quint16) idx + 0x801,4,10,QChar('0')) + .arg((quint8) m_data[idx],2,16,QChar('0')).toUpper() + .arg((quint8) m_data[idx+1],2,16,QChar('0')).toUpper() + .arg((quint8) m_data[idx+2],2,16,QChar('0')).toUpper() + .arg((quint8) m_data[idx+3],2,16,QChar('0')).toUpper() + .arg((quint8) m_data[idx+4],2,16,QChar('0')).toUpper() + .arg((quint8) m_data[idx+5],2,16,QChar('0')).toUpper() + .arg((quint8) m_data[idx+6],2,16,QChar('0')).toUpper() + .arg((quint8) m_data[idx+7],2,16,QChar('0')).toUpper(); + + } +*/ + int idx = 0; + quint8 val = 0; + while (idx < m_data.length()) { + ApplesoftLine line; + line.next_address = (quint8) m_data[idx] + (((quint8) m_data[idx+1]) *256); + line.tokens.append(m_data[idx]); + line.tokens.append(m_data[idx+1]); + idx++; idx++; + line.linenum = (quint8) m_data[idx] + (((quint8) m_data[idx+1])*256); + line.tokens.append(m_data[idx]); + line.tokens.append(m_data[idx+1]); + idx++; idx++; + if (line.next_address == 0x00) { break; } + do { + val = m_data[idx++]; + line.tokens.append(val); + if (val >= 0x80) { + line.detokenized_line.append(" " + m_tokens[val]); + } else { + if (val >= 0x20) { + line.detokenized_line.append(val); + } else if (val == 0x7F) { + QChar ch = QChar(0x2401); + line.detokenized_line.append(ch); + }else if (val > 0x00) { + QChar ch = QChar(0x00 + val); + line.detokenized_line.append(ch); + } + } + } while (val != 0x00); + retval.append(line); + } + + m_data_end = idx; + + if (idx < m_data.length()) { + qDebug() << QString("%1 byte(s) unaccounted for.").arg(m_data.length() - idx); + } + + return retval; +} + +void ApplesoftFile::list() { + foreach (ApplesoftLine line,detokenized()) { + QString debugline = QString("%1 %2 %3").arg(line.next_address,4,16,QChar('0')).arg(line.linenum).arg(line.detokenized_line); + qDebug() << debugline; +/* + debugline = ""; + foreach (quint8 val, line.tokens) { + debugline.append(QString("%1 ").arg(val,2,16,QChar('0'))); + } + qDebug() << " " << debugline; + qDebug() << "\n"; +*/ + } + + qDebug() << extraDataHexValues().join("\n"); +} + +QStringList ApplesoftFile::extraDataHexValues() { + QStringList retval; + + QString debugline = ""; + int count = 0; + foreach (quint8 val, extraData()) { + debugline.append(QString("%1").arg(val,2,16,QChar('0')).toUpper()); + count++; + if (count == 16) { + retval.append(debugline); + debugline = ""; + count = 0; + } else { + debugline.append(" "); + } + } + if (!debugline.simplified().isEmpty()) { + retval.append(debugline); + } + return retval; +} + +QByteArray ApplesoftFile::extraData() +{ + return m_data.mid(m_data_end); +} + +void ApplesoftFile::makeTokens() +{ + m_tokens[0x80] = "END "; m_tokens[0x81] = "FOR "; m_tokens[0x82] = "NEXT "; + m_tokens[0x83] = "DATA "; m_tokens[0x84] = "INPUT "; m_tokens[0x85] = " DEL "; + m_tokens[0x86] = "DIM "; m_tokens[0x87] = "READ "; m_tokens[0x88] = "GR "; + m_tokens[0x89] = "TEXT "; m_tokens[0x8A] = "PR# "; m_tokens[0x8B] = "IN# "; + m_tokens[0x8C] = "CALL "; m_tokens[0x8D] = "PLOT "; m_tokens[0x8E] = "HLIN "; + m_tokens[0x8F] = "VLIN "; m_tokens[0x90] = "HGR2 "; m_tokens[0x91] = "HGR "; + m_tokens[0x92] = "HCOLOR= "; m_tokens[0x93] = "HPLOT "; m_tokens[0x94] = "DRAW "; + m_tokens[0x95] = "XDRAW "; m_tokens[0x96] = "HTAB "; m_tokens[0x97] = "HOME "; + m_tokens[0x98] = "ROT= "; m_tokens[0x99] = "SCALE= "; m_tokens[0x9A] = "SHLOAD "; + m_tokens[0x9B] = "TRACE "; m_tokens[0x9C] = "NOTRACE "; m_tokens[0x9D] = "NORMAL "; + m_tokens[0x9E] = "INVERSE "; m_tokens[0x9F] = "FLASH "; m_tokens[0xA0] = "COLOR= "; + m_tokens[0xA1] = "POP "; m_tokens[0xA2] = "VTAB "; m_tokens[0xA3] = "HIMEM: "; + m_tokens[0xA4] = "LOMEM: "; m_tokens[0xA5] = "ONERR "; m_tokens[0xA6] = "RESUME "; + m_tokens[0xA7] = "RECALL "; m_tokens[0xA8] = "STORE "; m_tokens[0xA9] = "SPEED= "; + m_tokens[0xAA] = "LET "; m_tokens[0xAB] = "GOTO "; m_tokens[0xAC] = "RUN "; + m_tokens[0xAD] = "IF "; m_tokens[0xAE] = "RESTORE "; m_tokens[0xAF] = "& "; + m_tokens[0xB0] = "GOSUB "; m_tokens[0xB1] = "RETURN "; m_tokens[0xB2] = "REM "; + m_tokens[0xB3] = "STOP "; m_tokens[0xB4] = "ON "; m_tokens[0xB5] = "WAIT "; + m_tokens[0xB6] = "LOAD "; m_tokens[0xB7] = "SAVE "; m_tokens[0xB8] = "DEF "; + m_tokens[0xB9] = "POKE "; m_tokens[0xBA] = "PRINT "; m_tokens[0xBB] = "CONT "; + m_tokens[0xBC] = "LIST "; m_tokens[0xBD] = "CLEAR "; m_tokens[0xBE] = "GET "; + m_tokens[0xBF] = "NEW "; m_tokens[0xC0] = "TAB "; m_tokens[0xC1] = " TO "; + m_tokens[0xC2] = "FN "; m_tokens[0xC3] = "SPC( "; m_tokens[0xC4] = "THEN "; + m_tokens[0xC5] = " AT "; m_tokens[0xC6] = "NOT "; m_tokens[0xC7] = " STEP "; + m_tokens[0xC8] = "+ "; m_tokens[0xC9] = "- "; m_tokens[0xCA] = "* "; + m_tokens[0xCB] = "/ "; m_tokens[0xCC] = "^ "; m_tokens[0xCD] = " AND"; + m_tokens[0xCE] = " OR"; m_tokens[0xCF] = "> "; m_tokens[0xD0] = "= "; + m_tokens[0xD1] = "< "; m_tokens[0xD2] = "SGN "; m_tokens[0xD3] = "INT "; + m_tokens[0xD4] = "ABS "; m_tokens[0xD5] = "USR "; m_tokens[0xD6] = "FRE "; + m_tokens[0xD7] = "SCRN ( "; m_tokens[0xD8] = "PDL "; m_tokens[0xD9] = "POS "; + m_tokens[0xDA] = "SQR "; m_tokens[0xDB] = "RND "; m_tokens[0xDC] = "LOG "; + m_tokens[0xDD] = "EXP "; m_tokens[0xDE] = "COS "; m_tokens[0xDF] = "SIN "; + m_tokens[0xE0] = "TAN "; m_tokens[0xE1] = "ATN "; m_tokens[0xE2] = "PEEK "; + m_tokens[0xE3] = "LEN "; m_tokens[0xE4] = "STR$ "; m_tokens[0xE5] = "VAL "; + m_tokens[0xE6] = "ASC "; m_tokens[0xE7] = "CHR$ "; m_tokens[0xE8] = "LEFT$ "; + m_tokens[0xE9] = "RIGHT$ "; m_tokens[0xEA] = "MID$ "; m_tokens[0xEB] = "{Token 0xEB} "; + m_tokens[0xEC] = "{Token 0xEC} "; + m_tokens[0xED] = "{Token 0xED} "; + m_tokens[0xEE] = "{Token 0xEE} "; + m_tokens[0xEF] = "{Token 0xEF} "; + m_tokens[0xF0] = "{Token 0xF0} "; + m_tokens[0xF1] = "{Token 0xF1} "; + m_tokens[0xF2] = "{Token 0xF2} "; + m_tokens[0xF3] = "{Token 0xF3} "; + m_tokens[0xF4] = "{Token 0xF4} "; + m_tokens[0xF5] = "{Token 0xF5} "; + m_tokens[0xF6] = "{Token 0xF6} "; + m_tokens[0xF7] = "{Token 0xF7} "; + m_tokens[0xF8] = "{Token 0xF8} "; + m_tokens[0xF9] = "{Token 0xF9} "; + m_tokens[0xFA] = "{Token 0xFA} "; + m_tokens[0xFB] = "{Token 0xFB} "; + m_tokens[0xFC] = "{Token 0xFC} "; + m_tokens[0xFD] = "{Token 0xFD} "; + m_tokens[0xFE] = "{Token 0xFE} "; + m_tokens[0xFF] = "{Token 0xFF} "; +} diff --git a/src/applesoftfile.h b/src/applesoftfile.h new file mode 100644 index 0000000..07551b7 --- /dev/null +++ b/src/applesoftfile.h @@ -0,0 +1,38 @@ +#ifndef APPLESOFTFILE_H +#define APPLESOFTFILE_H + +#include +#include +#include + +#include "genericfile.h" + +struct ApplesoftLine { + quint16 next_address; + quint16 linenum; + QByteArray tokens; + QString detokenized_line; +}; + + +class ApplesoftFile : public GenericFile +{ +public: + ApplesoftFile(QByteArray data = QByteArray()); + void setData(QByteArray data); + QByteArray extraData(); + QList detokenized() { return m_detokenized; } + void list(); + QStringList extraDataHexValues(); + +private: + void makeTokens(); + QList detokenize(); + + int m_data_end; + QMap m_tokens; + quint16 m_length; + QList m_detokenized; +}; + +#endif // APPLESOFTFILE_H diff --git a/src/applestring.cxx b/src/applestring.cxx new file mode 100644 index 0000000..776bd16 --- /dev/null +++ b/src/applestring.cxx @@ -0,0 +1,78 @@ +#include "applestring.h" + + + +QString AppleString::printable() const +{ + QString retval; + foreach (quint8 ch, *this) { + retval.append(AppleChar(ch).printable()); + } + return retval; +} + +QVector AppleString::attributes() const +{ + QVector retval(this->length()); + int idx = 0; + foreach (quint8 ch, *this) { + retval[idx++] = AppleChar(ch).getAttribute(); + } + return retval; +} + + + +QChar AppleChar::printable() const +{ + quint8 newval; + switch (getTextSet()) { + case SetInvUC: + newval = m_val+0x40; + break; + case SetInvSp: + newval = m_val; + break; + case SetFlUC: + newval = m_val; + break; + case SetFlSp: + newval = m_val-0x40; + break; + case SetNormUC: + newval = m_val-0x40; + break; + case SetNormSp: + newval = m_val-0x80; + break; + case SetNormAltUC: + newval = m_val-0x80; + break; + case SetNormLC: + default: + newval = m_val-0x80; + break; + } + return QChar(newval); +} + +TextAttribute AppleChar::getAttribute() const +{ + if (m_val <= 0x3f) { return Inverse; } + if (m_val <= 0x7f) { return Flash; } + if (m_val <= 0xbf) { return Normal; } + if (m_val <= 0xdf) { return NormalHigh; } + return Normal; +} + +TextSet AppleChar::getTextSet() const { + if (m_val < 0x20) { return SetInvUC; } + if (m_val < 0x40) { return SetInvSp; } + if (m_val < 0x60) { return SetFlUC; } + if (m_val < 0x80) { return SetFlSp; } + if (m_val < 0xA0) { return SetNormUC; } + if (m_val < 0xC0) { return SetNormSp; } + if (m_val < 0xE0) { return SetNormAltUC; } + return SetNormLC; +} + diff --git a/src/applestring.h b/src/applestring.h new file mode 100644 index 0000000..5ac6090 --- /dev/null +++ b/src/applestring.h @@ -0,0 +1,32 @@ +#ifndef APPLESTRING_H +#define APPLESTRING_H + +#include +#include +#include +#include + +#include "util.h" + +class AppleChar { +public: + AppleChar() { m_val = 0; } + AppleChar(quint8 ch) { m_val = ch; } + + QChar printable() const; + + TextAttribute getAttribute() const; + TextSet getTextSet() const; + +private: + quint8 m_val; +}; + +class AppleString : public QByteArray { +public: + QString printable() const; + QVector attributes() const; + +}; + +#endif // APPLESTRING_H diff --git a/src/binaryfile.cxx b/src/binaryfile.cxx new file mode 100644 index 0000000..4a33e6c --- /dev/null +++ b/src/binaryfile.cxx @@ -0,0 +1,28 @@ +#include +#include "binaryfile.h" + +BinaryFile::BinaryFile(QByteArray data) : GenericFile(data) +{ + m_length = 0; + m_address = 0; + + if (!data.isEmpty()) { + setData(data); + } +} + +void BinaryFile::setData(QByteArray data) +{ + if (data.length() >= 4) { + QByteArray metadata = data.left(4); + m_data = data.mid(4); + m_address = (quint8) metadata[0] + ((quint8) metadata[1]*256); + m_length = (quint8) metadata[2] + ((quint8) metadata[3]*256); + } +} + +void BinaryFile::dump() +{ + qDebug() << QString("Address: %1 Length: %2").arg(m_address,4,16,QChar('0')).arg(m_length,4,16,QChar('0')).toUpper(); + qDebug() << QString("Data Length Recorded: %1").arg(m_data.length(),4,16,QChar('0')).toUpper(); +} diff --git a/src/binaryfile.h b/src/binaryfile.h new file mode 100644 index 0000000..bfc368e --- /dev/null +++ b/src/binaryfile.h @@ -0,0 +1,23 @@ +#ifndef BINARYFILE_H +#define BINARYFILE_H + +#include "genericfile.h" + +class BinaryFile : public GenericFile +{ +public: + BinaryFile(QByteArray data = QByteArray()); + void setData(QByteArray data); + + quint16 address() { return m_address; } + quint16 length() { return m_length; } + + QByteArray data() { return m_data; } + void dump(); + +protected: + quint16 m_address; + quint16 m_length; +}; + +#endif // BINARYFILE_H diff --git a/src/catalogsector.cxx b/src/catalogsector.cxx new file mode 100644 index 0000000..348da5a --- /dev/null +++ b/src/catalogsector.cxx @@ -0,0 +1,44 @@ +#include "catalogsector.h" +#include "sector.h" + +CatalogSector::CatalogSector(Sector *data) +{ + m_data = data; + + m_next.track = m_data->rawData()[0x01]; + m_next.sector = m_data->rawData()[0x02]; + + for (int idx = 0; idx<7; idx++) + { + FileDescriptiveEntry fde = makeFDE(idx*0x23+0x0B); + if (fde.firstTSListSector != TSPair(0,0)) { + m_fdes.append(fde); +// qDebug() << "FDE #"<rawData()[offset + 0x00]; + fde.firstTSListSector.sector = m_data->rawData()[offset + 0x01]; + fde.fileTypeAndFlags = m_data->rawData()[offset + 0x02]; + fde.lengthInSectors = m_data->rawData()[offset + 0x21] + (m_data->rawData()[offset + 0x22] * 256); + for (int idx = 0x03; idx <= 0x20; idx++) { + fde.filename.append(m_data->rawData()[idx+offset]); + } + return fde; +} diff --git a/src/catalogsector.h b/src/catalogsector.h new file mode 100644 index 0000000..15ef455 --- /dev/null +++ b/src/catalogsector.h @@ -0,0 +1,43 @@ +#ifndef CATALOGSECTOR_H +#define CATALOGSECTOR_H + +#include +#include +#include + +#include "util.h" +#include "applestring.h" +#include "filedescriptiveentry.h" + +class Sector; + + + +class CatalogSector +{ +public: + CatalogSector(Sector *sector); + + FileDescriptiveEntry &getFDE(quint8 number) { + if (number >= m_fdes.length()) { + number = m_fdes.length() - 1; + } + return m_fdes[number]; + } + + QList getFDEs() { return m_fdes; } + + TSPair nextCatalogSector() { return m_next; } + + void dumpFDEs(); + +private: + FileDescriptiveEntry makeFDE(int offset); + +private: + Sector *m_data; + QList m_fdes; + TSPair m_next; +}; + +#endif // CATALOGSECTOR_H diff --git a/src/catalogwidget.cxx b/src/catalogwidget.cxx new file mode 100644 index 0000000..4e39ad9 --- /dev/null +++ b/src/catalogwidget.cxx @@ -0,0 +1,61 @@ +#include "catalogwidget.h" +#include "ui_catalogwidget.h" +#include "filedescriptiveentry.h" +#include + +CatalogWidget::CatalogWidget(QWidget *parent) : + QWidget(parent), + ui(new Ui::CatalogWidget) +{ + ui->setupUi(this); + ui->catalog_list->setFont(QFont("monospace")); + +} + +CatalogWidget::~CatalogWidget() +{ + delete ui; +} + +void CatalogWidget::prepForNewDisk(QString filename, DiskFile *disk) +{ + m_disk = disk; + m_diskname = filename; +} + +void CatalogWidget::processNewlyLoadedDisk(QString diskfilename, DiskFile *disk) +{ + if (m_disk == disk) { + QUrl url = QUrl::fromLocalFile(diskfilename); + QString shortfilename = url.fileName(); + QFontMetrics *fm = new QFontMetrics(ui->catalog_list->font()); + QRect maxrect; + ui->volume_label->setText(shortfilename); + foreach(FileDescriptiveEntry fde, m_disk->getAllFDEs()) { + QString filetype = fde.fileType(); + QString filename = AppleString(fde.filename).printable().simplified(); + int size = fde.lengthInSectors; + bool locked = fde.isLocked(); + QString sizeStr = QString("%1").arg(size,5,10,QChar(' ')).toUpper(); + QString text = QString("%1 %2 %3 %4").arg(locked?"*":" ").arg(sizeStr).arg(filetype).arg(filename); + ui->catalog_list->addItem(new QListWidgetItem(text)); + QRect rect = fm->boundingRect(text); + if (rect.width() > maxrect.width()) { + maxrect = rect; + } + } + ui->catalog_list->resize(maxrect.width(),ui->catalog_list->size().height()); + + } +} + +void CatalogWidget::unloadDisk(DiskFile *disk) +{ + if (m_disk == disk) { + m_disk = 0; + } + ui->catalog_list->clear(); + ui->volume_label->clear(); +} + + diff --git a/src/catalogwidget.h b/src/catalogwidget.h new file mode 100644 index 0000000..3767f83 --- /dev/null +++ b/src/catalogwidget.h @@ -0,0 +1,37 @@ +#ifndef CATALOGWIDGET_H +#define CATALOGWIDGET_H + +#include + +#include "diskfile.h" + +namespace Ui { +class CatalogWidget; +} + +class CatalogWidget : public QWidget +{ + Q_OBJECT + + +public: + explicit CatalogWidget(QWidget *parent = 0); + ~CatalogWidget(); + +public slots: + void prepForNewDisk(QString filename, DiskFile *disk); + void processNewlyLoadedDisk(QString filename, DiskFile *disk); + void unloadDisk(DiskFile *disk); + +signals: + void newFileSelected(FileDescriptiveEntry *entry); + +private: + + Ui::CatalogWidget *ui; + + DiskFile *m_disk; + QString m_diskname; +}; + +#endif // CATALOGWIDGET_H diff --git a/src/catalogwidget.ui b/src/catalogwidget.ui new file mode 100644 index 0000000..1e1ce85 --- /dev/null +++ b/src/catalogwidget.ui @@ -0,0 +1,42 @@ + + + CatalogWidget + + + + 0 + 0 + 328 + 560 + + + + Form + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/disassembler.cxx b/src/disassembler.cxx new file mode 100644 index 0000000..ec451ec --- /dev/null +++ b/src/disassembler.cxx @@ -0,0 +1,7 @@ +#include "disassembler.h" + + +QStringList Disassembler::disassemble(quint16 address, QByteArray values) +{ + return QStringList() << "TODO"; +} diff --git a/src/disassembler.h b/src/disassembler.h new file mode 100644 index 0000000..cb58b59 --- /dev/null +++ b/src/disassembler.h @@ -0,0 +1,13 @@ +#ifndef DISASSEMBLER_H +#define DISASSEMBLER_H + +#include +#include + +class Disassembler +{ +public: + static QStringList disassemble(quint16 address, QByteArray values); +}; + +#endif // DISASSEMBLER_H diff --git a/src/diskfile.cxx b/src/diskfile.cxx new file mode 100644 index 0000000..6267a61 --- /dev/null +++ b/src/diskfile.cxx @@ -0,0 +1,109 @@ +#include "diskfile.h" + +#include +#include +#include + +#include "tracksectorlist.h" + +DiskFile::DiskFile(QString filename) +{ + if (!filename.isEmpty()) + { + read(filename); + } +} + +bool DiskFile::read(QString filename) +{ + QFile infile(filename); + if (infile.open(QIODevice::ReadOnly)) + { + QDataStream qds(&infile); + for (int track = 0; track < 35; track++) + { + for (int sector = 0; sector < 16; sector++) + { + char buffer[256]; + if (qds.readRawData(buffer,256) == 256) + { + Sector sec; + sec.setTrackSector(track,sector); + sec.setData(QByteArray(buffer,256)); + m_contents[track][sector] = sec; + } + else + { + qDebug() << "Invalid sector read!"; + return false; + } + } + } + + return true; + } + else + qDebug() << "Could not open file " << filename; + return false; +} + +VTOC DiskFile::getVTOC() +{ + return getSector(17,0).promoteToVTOC(); +} + +QList DiskFile::getCatalogSectors() +{ + QList retval; + VTOC vtoc = getVTOC(); + TSPair ts = vtoc.firstCatalogSector(); + + CatalogSector cs = getSector(ts).promoteToCatalogSector(); + retval.append(cs); + while (cs.nextCatalogSector() != TSPair(0,0)) { + ts = cs.nextCatalogSector(); + cs = getSector(ts).promoteToCatalogSector(); + retval.append(cs); + } + return retval; +} + +QByteArray DiskFile::getFile(FileDescriptiveEntry fde) +{ + QByteArray retval; + TrackSectorList tsl = getSector(fde.firstTSListSector).promoteToTrackSectorList(); + return getDataFromTrackSectorList(tsl); +} + + +QByteArray DiskFile::getDataFromTrackSectorList(TrackSectorList tsl) +{ + QByteArray retval; + + foreach(TSPair pair, tsl.getDataTSPairs()) + { + Sector sec = getSector(pair); + retval.append(sec.rawData()); + } + + if (tsl.getNextTSList() != TSPair(0,0)) { + TrackSectorList nextTsl = getSector(tsl.getNextTSList()).promoteToTrackSectorList(); + retval.append(getDataFromTrackSectorList(nextTsl)); + } + + return retval; +} + +QList DiskFile::getAllFDEs() { + QList retval; + + QList sectors = getCatalogSectors(); + + foreach (CatalogSector cs, sectors) + { + QList fdes = cs.getFDEs(); + retval.append(fdes); + } + return retval; +} + diff --git a/src/diskfile.h b/src/diskfile.h new file mode 100644 index 0000000..54198f5 --- /dev/null +++ b/src/diskfile.h @@ -0,0 +1,45 @@ +#ifndef DISKFILE_H +#define DISKFILE_H + +#include +#include +#include +#include + +#include "util.h" +#include "sector.h" +#include "vtoc.h" + + + +class DiskFile +{ +public: + DiskFile(QString filename = ""); + + bool read(QString filename); + + Sector &getSector(TSPair ts) { return getSector(ts.track, ts.sector); } + + Sector &getSector(int track, int sector) { + return m_contents[track][sector]; + } + + VTOC getVTOC(); + + QList getCatalogSectors(); + + QByteArray getFile(FileDescriptiveEntry fde); + + QByteArray getDataFromTrackSectorList(TrackSectorList tsl); + + QList getAllFDEs(); + + +private: + + QMap< int, QMap< int, Sector> > m_contents; + +}; + +#endif // DISKFILE_H diff --git a/src/filedescriptiveentry.cxx b/src/filedescriptiveentry.cxx new file mode 100644 index 0000000..8d3836e --- /dev/null +++ b/src/filedescriptiveentry.cxx @@ -0,0 +1,2 @@ +#include "filedescriptiveentry.h" + diff --git a/src/filedescriptiveentry.h b/src/filedescriptiveentry.h new file mode 100644 index 0000000..6179622 --- /dev/null +++ b/src/filedescriptiveentry.h @@ -0,0 +1,44 @@ +#ifndef FILEDESCRIPTIVEENTRY_H +#define FILEDESCRIPTIVEENTRY_H + +#include "util.h" +#include +#include "applestring.h" + +struct FileDescriptiveEntry { + TSPair firstTSListSector; + int fileTypeAndFlags; + AppleString filename; + quint16 lengthInSectors; + + QString fileType() { + if (fileTypeAndFlags & IntegerBasicFile) { return "I"; } + if (fileTypeAndFlags & ApplesoftBasicFile) { return "A"; } + if (fileTypeAndFlags & RelocatableFile) { return "R"; } + if (fileTypeAndFlags & RawBinaryFile) { return "B"; } + if (fileTypeAndFlags & TypeSFile) { return "S"; } + if (fileTypeAndFlags & TypeAFile) { return "a"; } + if (fileTypeAndFlags & TypeBFile) { return "b"; } + return "T"; + } + + bool isLocked() { return (fileTypeAndFlags & IsLocked); } + + void dump() { + qDebug() << "First TS List Sector: Track: " << QString("%1").arg(firstTSListSector.track,2,16,QChar('0')).toUpper() + << " Sector: " << QString("%1").arg(firstTSListSector.sector,2,16,QChar('0')).toUpper(); + qDebug() << "File Type and Flags: " << QString::number((quint8)fileTypeAndFlags) << "(" << fileType() << "," << (isLocked()?"Locked":"Unlocked") << ")"; + qDebug() << "Filename: " << filename.printable(); + qDebug() << "Length in Sectors: " << lengthInSectors; + } + + void catalog() { + QString output = QString("%1 %2 %3 %4").arg(QString(isLocked()?"*":" ")) + .arg(lengthInSectors,3,10,QChar('0')) + .arg(fileType()) + .arg(filename.printable().trimmed()); + qDebug() << output; + } +}; + +#endif // FILEDESCRIPTIVEENTRY_H diff --git a/src/genericfile.cxx b/src/genericfile.cxx new file mode 100644 index 0000000..bc169be --- /dev/null +++ b/src/genericfile.cxx @@ -0,0 +1,13 @@ +#include "genericfile.h" + +GenericFile::GenericFile(QByteArray data) +{ + if (!data.isEmpty()) { + setData(data); + } +} + +void GenericFile::setData(QByteArray data) +{ + m_data = data; +} diff --git a/src/genericfile.h b/src/genericfile.h new file mode 100644 index 0000000..f505184 --- /dev/null +++ b/src/genericfile.h @@ -0,0 +1,16 @@ +#ifndef GENERICFILE_H +#define GENERICFILE_H + +#include + +class GenericFile +{ +public: + GenericFile(QByteArray data = QByteArray()); + virtual void setData(QByteArray data); + +protected: + QByteArray m_data; +}; + +#endif // GENERICFILE_H diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..1abc3aa --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,48 @@ +#include + +#include "mainwindow.h" +#include "binaryfile.h" +#include "genericfile.h" +#include "diskfile.h" +#include "catalogsector.h" +#include "applesoftfile.h" + +int main(int argc, char** argv) +{ +/* +// DiskFile df("/home/mlong/Desktop/dos.3.3.system.master.dsk"); + DiskFile df("/home/mlong/Desktop/missing_ring_good.dsk"); + + df.getVTOC().dump(); + + QList sectors = df.getCatalogSectors(); + + FileDescriptiveEntry toLoad; + + foreach (FileDescriptiveEntry fde, df.getAllFDEs()) { + fde.catalog(); + if (fde.filename.printable().contains("ASM.DATA")) { + toLoad = fde; + } + } + + QByteArray file = df.getFile(toLoad); + + BinaryFile myfile; + myfile.setData(file); + myfile.dump(); + + + //ApplesoftFile af(file); + //af.list(); + */ + + QApplication a(argc, argv); + MainWindow w; + w.loadDiskFile("/home/mlong/Desktop/missing_ring_good.dsk"); + w.show(); + + return a.exec(); + +} + diff --git a/src/mainwindow.cxx b/src/mainwindow.cxx new file mode 100644 index 0000000..809add8 --- /dev/null +++ b/src/mainwindow.cxx @@ -0,0 +1,63 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); + m_disk = 0; + + connect(ui->action_Quit, SIGNAL(triggered()), qApp, SLOT(quit())); + connect(ui->action_Load_Disk_Image, SIGNAL(triggered()), SLOT(showLoadDialog())); + + connect(this, SIGNAL(diskFileLoading(QString, DiskFile*)), + ui->catalogWidget, SLOT(prepForNewDisk(QString,DiskFile*))); + connect(this, SIGNAL(diskFileLoaded(QString,DiskFile*)), + ui->catalogWidget, SLOT(processNewlyLoadedDisk(QString,DiskFile*))); + connect(this, SIGNAL(diskFileUnloading(DiskFile*)), + ui->catalogWidget, SLOT(unloadDisk(DiskFile*))); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::loadDiskFile(QString filename) +{ + if (m_disk) { + unloadDiskFile(); + } + + m_disk = new DiskFile(); + emit diskFileLoading(filename,m_disk); + if (m_disk->read(filename)) { + emit diskFileLoaded(filename,m_disk); + } else { + emit diskFileLoadFailed(filename,m_disk); + delete m_disk; + m_disk = 0; + } +} + +void MainWindow::unloadDiskFile() +{ + emit diskFileUnloading(m_disk); + delete m_disk; + m_disk = 0; + emit diskFileUnloaded(); +} + +void MainWindow::showLoadDialog() +{ + QString filename = QFileDialog::getOpenFileName(this, + tr("Open Disk Image"), + "/home/mlong/Desktop", + "Disk Images (*.do *.dsk)"); + if (!filename.isEmpty()) { + loadDiskFile(filename); + } +} diff --git a/src/mainwindow.h b/src/mainwindow.h new file mode 100644 index 0000000..8d7bd3e --- /dev/null +++ b/src/mainwindow.h @@ -0,0 +1,39 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +#include "diskfile.h" + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +public slots: + void loadDiskFile(QString filename); + void unloadDiskFile(); + + void showLoadDialog(); + +signals: + void diskFileLoading(QString filename, DiskFile *file); + void diskFileLoaded(QString filename, DiskFile *file); + void diskFileLoadFailed(QString filename, DiskFile *file); + void diskFileUnloading(DiskFile *file); + void diskFileUnloaded(); + +private: + Ui::MainWindow *ui; + + DiskFile *m_disk; +}; + +#endif // MAINWINDOW_H diff --git a/src/mainwindow.ui b/src/mainwindow.ui new file mode 100644 index 0000000..e03c355 --- /dev/null +++ b/src/mainwindow.ui @@ -0,0 +1,116 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + AppleSAWS + + + + + + + Qt::Horizontal + + + false + + + + + 2 + 0 + + + + + 200 + 0 + + + + + 400 + 16777215 + + + + + 300 + 0 + + + + + + + 3 + 0 + + + + + + + + + + + 0 + 0 + 800 + 20 + + + + + &File + + + + + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + &Load Disk Image + + + + + &Quit + + + + + + CatalogWidget + QWidget +
catalogwidget.h
+ 1 +
+
+ + +
diff --git a/src/sector.cxx b/src/sector.cxx new file mode 100644 index 0000000..000c7ac --- /dev/null +++ b/src/sector.cxx @@ -0,0 +1,47 @@ +#include "sector.h" + +#include +#include + +QByteRef Sector::operator[](uint offset) { + if (offset > 255) { + offset = 255; + } + return m_data[offset]; +} + +bool Sector::setData(QByteArray data) { + if (data.length() != 256) return false; + + m_data = data; + return true; +} + +void Sector::dump() { + qDebug() << "Dumping Track " << track() << "Sector " << sector() << " ..."; + for (int jdx = 0; jdx < 16; jdx++) + { + QString line; + line += QString("%1 (%2): ").arg(jdx*16,2,16,QChar('0')).arg(jdx*16,3,10,QChar(' ')); + for (int idx = 0; idx < 16; idx++) + { + int offset = (jdx*16) + idx; + quint8 val = m_data[offset]; + line += QString("%1 ").arg(val,2,16,QChar('0')); + if (idx == 7) line += " "; + } + line = line.toUpper(); + line += " "; + for (int idx = 0; idx < 16; idx++) + { + int offset = (jdx*16) + idx; + quint8 val = m_data[offset]; + if (val > 127) { val -= 128; } + QChar ch(val); + line += QString("%1").arg(ch.isPrint()?ch:'.'); + } + + qDebug() << line; + } +} + diff --git a/src/sector.h b/src/sector.h new file mode 100644 index 0000000..c1bdda9 --- /dev/null +++ b/src/sector.h @@ -0,0 +1,57 @@ +#ifndef SECTOR_H +#define SECTOR_H + +#include + +#include "vtoc.h" +#include "catalogsector.h" +#include "tracksectorlist.h" + +class Sector +{ +public: + + Sector() { + m_data.resize(256); + m_track = 255; + m_sector = 255; + } + + VTOC promoteToVTOC() { + return VTOC(this); + } + + CatalogSector promoteToCatalogSector() { + return CatalogSector(this); + } + + TrackSectorList promoteToTrackSectorList() { + return TrackSectorList(this); + } + + int sector() { return m_sector; } + int track() { return m_track; } + + void setTrackSector(int track, int sector) { + setTrack(track); + setSector(sector); + } + + void setTrack(int track) { m_track = track; } + void setSector(int sector) { m_sector = sector; } + + QByteRef operator[](uint offset); + + bool setData(QByteArray data); + + void dump(); + + QByteArray rawData() { return m_data; } + +private: + QByteArray m_data; + int m_track; + int m_sector; +}; + +#endif // SECTOR_H diff --git a/src/tracksectorlist.cxx b/src/tracksectorlist.cxx new file mode 100644 index 0000000..caa395c --- /dev/null +++ b/src/tracksectorlist.cxx @@ -0,0 +1,24 @@ +#include "tracksectorlist.h" + +#include "sector.h" + +TrackSectorList::TrackSectorList(Sector *data) +{ + m_data = data; + + m_next_tslist.track = m_data->rawData()[0x01]; + m_next_tslist.sector = m_data->rawData()[0x02]; + + m_sector_offset.track = m_data->rawData()[0x05]; + m_sector_offset.sector = m_data->rawData()[0x06]; + + for (int idx = 0x0C; idx < 0xff; idx+=2) + { + TSPair ts(m_data->rawData()[idx],m_data->rawData()[idx+1]); + if (ts == TSPair(0,0)) { + break; + } else { + m_ts_pairs_for_data.append(ts); + } + } +} diff --git a/src/tracksectorlist.h b/src/tracksectorlist.h new file mode 100644 index 0000000..4a24d7d --- /dev/null +++ b/src/tracksectorlist.h @@ -0,0 +1,27 @@ +#ifndef TRACKSECTORLIST_H +#define TRACKSECTORLIST_H + +#include "util.h" + +class Sector; + +class TrackSectorList +{ +public: + TrackSectorList(Sector *data); + + TSPair getNextTSList() const { return m_next_tslist; } + TSPair getSectorOffset() const { return m_sector_offset; } + + QList getDataTSPairs() { return m_ts_pairs_for_data; } + +private: + + TSPair m_next_tslist; + TSPair m_sector_offset; + QList m_ts_pairs_for_data; + + Sector *m_data; +}; + +#endif // TRACKSECTORLIST_H diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..2376064 --- /dev/null +++ b/src/util.h @@ -0,0 +1,58 @@ +#ifndef UTIL_H +#define UTIL_H + +#include +#include +#include + +typedef enum { + TextFile = 0x00, + IntegerBasicFile = 0x01, + ApplesoftBasicFile = 0x02, + RawBinaryFile = 0x04, + TypeSFile = 0x08, + RelocatableFile = 0x10, + TypeAFile = 0x20, + TypeBFile= 0x40, + IsLocked = 0x80 +} FileTypeFlag; + +typedef enum { + Inverse = 0x00, // 0x00 -- 0x3F + Flash = 0x01, // 0x40 -- 0x7F + Normal = 0x02, // 0x80 -- 0xBF + NormalHigh = 0x04 // 0xC0 -- 0xFF +} TextAttribute; + +typedef enum { + SetInvUC = 0x00, + SetInvSp = 0x20, + SetFlUC = 0x40, + SetFlSp = 0x60, + SetNormUC = 0x80, + SetNormSp = 0xA0, + SetNormAltUC = 0xC0, + SetNormLC = 0xE0 +} TextSet; + +struct TSPair { + TSPair() { track = sector = 0; } + TSPair(quint8 trackval, quint8 secval) { track=trackval; sector = secval; } + quint8 track; + quint8 sector; + + bool operator==(const TSPair &other) { + if (other.track == track && other.sector == sector) return true; + return false; + } + + bool operator!=(const TSPair &other) { + return !(operator==(other)); + } + + void dump() { qDebug() << "TSPair: track: " << track << " sector: " << sector; } +}; + + + +#endif // UTIL_H diff --git a/src/vtoc.cxx b/src/vtoc.cxx new file mode 100644 index 0000000..42bc261 --- /dev/null +++ b/src/vtoc.cxx @@ -0,0 +1,96 @@ +#include "vtoc.h" + +#include +#include + +#include "sector.h" + +VTOC::VTOC(Sector *data) +{ + m_data = data; +} + +TSPair VTOC::firstCatalogSector() { + return TSPair(m_data->rawData()[0x01], m_data->rawData()[0x02]); +} + +quint8 VTOC::dosVersion() { + return m_data->rawData()[0x03]; +} + +quint8 VTOC::volumeNumber() { + return m_data->rawData()[0x06]; +} + +quint8 VTOC::maxTSPairs() { + return m_data->rawData()[0x27]; +} + +quint8 VTOC::lastTrackAllocated() { + return m_data->rawData()[0x30]; +} + +qint8 VTOC::directionOfAllocation() { + return m_data->rawData()[0x31]; +} + +quint8 VTOC::tracksPerDisk() { + return m_data->rawData()[0x34]; +} + +quint8 VTOC::sectorsPerDisk() { + return m_data->rawData()[0x35]; +} + +qint16 VTOC::bytesPerSector() { + return m_data->rawData()[0x36] + (256 * m_data->rawData()[0x37]); +} + +bool VTOC::isSectorInUse(TSPair ts) { + quint8 track = ts.track; + quint8 sec = ts.sector; + quint8 baseaddr = (track * 4) + 0x38; + + quint16 word = (((quint16) m_data->rawData()[baseaddr]) *256) + (quint8) m_data->rawData()[baseaddr+1]; + quint16 bitpos = (quint16) 0x01 << (quint16) sec; + + return !(word & bitpos); +} + +void VTOC::dump() +{ + /* + for (qint8 idx = 0x0f; idx >= 0; idx--) { + quint8 shift; + if (idx < 0x08) { shift = idx; } else { shift = idx-0x08; } + qDebug() << "Idx: " << idx << "Shift: " << (quint8) shift << "Bitpos: " << (quint8) (0x01 << shift); + } +*/ + qDebug() << "Dumping VTOC Track " << m_data->track() << "Sector " << m_data->sector() << " ..."; + qDebug() << " Track number of first catalog sector: " << QString::number(firstCatalogSector().track); + qDebug() << " Sector number of first catalog sector: " << QString::number(firstCatalogSector().sector); + qDebug() << " Release number of DOS used to INIT disk: " << QString::number(dosVersion()); + qDebug() << " Disk Volume Number: " << QString::number(volumeNumber()); + qDebug() << " Max track/sector pairs that fit in t/s list sector (122=256): " << QString::number(maxTSPairs()); + qDebug() << " Last track where sectors were allocated: " << QString::number(lastTrackAllocated()); + qDebug() << " Direction of track allocations (+/- 1): " << QString::number(directionOfAllocation()); + qDebug() << " Number tracks per disk: " << QString::number(tracksPerDisk()); + qDebug() << " Number sectors per disk: " << QString::number(sectorsPerDisk()); + qDebug() << " Number bytes/sector: " << QString::number(bytesPerSector()); + qDebug() << " Track Usage (.=free, 0-F=used):"; + for (quint8 track = 0; track < m_data->rawData()[0x34];track++) + { + qDebug() << " " << QString("Track %1:").arg((int) track,2,10,QChar('0')) << buildUseString(track); + } + } + +QString VTOC::buildUseString(quint8 track) { + QString usestr; + for (qint8 sec = 0x0f; sec >= 0; sec--) + { + usestr.append(isSectorInUse(TSPair(track,sec))?QString::number(sec,16).toUpper():"-"); + } + return usestr; +} + + diff --git a/src/vtoc.h b/src/vtoc.h new file mode 100644 index 0000000..f829aa8 --- /dev/null +++ b/src/vtoc.h @@ -0,0 +1,33 @@ +#ifndef VTOC_H +#define VTOC_H + +#include +#include "util.h" +class Sector; +class QString; + + +class VTOC +{ +public: + VTOC(Sector *data); + + void dump(); + TSPair firstCatalogSector(); + quint8 dosVersion(); + quint8 volumeNumber(); + quint8 maxTSPairs(); + quint8 lastTrackAllocated(); + qint8 directionOfAllocation(); + quint8 tracksPerDisk(); + quint8 sectorsPerDisk(); + qint16 bytesPerSector(); + bool isSectorInUse(TSPair ts); + +private: + QString buildUseString(quint8 track); + + Sector *m_data; +}; + +#endif // VTOC_H