From 1efa8c196d8eca063254be19731bb3d1b5562dc7 Mon Sep 17 00:00:00 2001 From: Gavin MacGregor Date: Tue, 4 Nov 2025 17:56:04 +0000 Subject: [PATCH] Handle invalid triplets "Invalid triplets" are triplets that have failed Hamming 24/18 decoding. Previously, invalid triplets were converted at load time to either all zero bits or, in the case of X/26 enhancement triplets, to "dummy" triplets of reserved mode 11110 with a row address group that hopefully have no effect. Now the X/26 enhancement triplet list can explicitly store invalid triplets and will show them as "error decoding triplet". Invalid triplets in other packets such as X/28/0 will still be zeroed out at load time. Since the TTI format has no provision for storing invalid triplets, saving a page will convert the invalid triplets to reserved mode 11110 as described above. The actual bits of invalid triplets are not stored on the assumption that they are not recoverable. Thus exporting to t42 format will write an invalid triplet as a Hamming coded result of all zero bits which will still cause a Hamming decoding failure. --- src/qteletextdecoder/pagex26base.cpp | 25 +++++++--- src/qteletextdecoder/x26triplets.cpp | 14 +++++- src/qteletextdecoder/x26triplets.h | 4 +- src/qteletextmaker/loadformats.cpp | 25 +++++++--- src/qteletextmaker/saveformats.cpp | 74 +++++++++++++++++++--------- src/qteletextmaker/x26dockwidget.cpp | 9 +++- src/qteletextmaker/x26model.cpp | 18 +++++-- src/qteletextmaker/x26model.h | 3 +- 8 files changed, 125 insertions(+), 47 deletions(-) diff --git a/src/qteletextdecoder/pagex26base.cpp b/src/qteletextdecoder/pagex26base.cpp index fdb7048..c5305d9 100644 --- a/src/qteletextdecoder/pagex26base.cpp +++ b/src/qteletextdecoder/pagex26base.cpp @@ -36,9 +36,13 @@ QByteArray PageX26Base::packetFromEnhancementList(int p) const const int enhanceListPointer = p*13+t; if (enhanceListPointer < m_enhancements.size()) { - result[t*3+1] = m_enhancements.at(enhanceListPointer).address(); - result[t*3+2] = m_enhancements.at(enhanceListPointer).mode() | ((m_enhancements.at(enhanceListPointer).data() & 1) << 5); - result[t*3+3] = m_enhancements.at(enhanceListPointer).data() >> 1; + if (!m_enhancements.at(enhanceListPointer).isValid()) + result[t*3+1] = result[t*3+2] = result[t*3+3] = 0xff; + else { + result[t*3+1] = m_enhancements.at(enhanceListPointer).address(); + result[t*3+2] = m_enhancements.at(enhanceListPointer).mode() | ((m_enhancements.at(enhanceListPointer).data() & 1) << 5); + result[t*3+3] = m_enhancements.at(enhanceListPointer).data() >> 1; + } // If this is the last triplet, get a copy to repeat to the end of the packet if (enhanceListPointer == m_enhancements.size()-1) { @@ -64,19 +68,24 @@ QByteArray PageX26Base::packetFromEnhancementList(int p) const void PageX26Base::setEnhancementListFromPacket(int p, QByteArray pkt) { // Preallocate entries in the m_enhancements list to hold our incoming triplets. - // We write "dummy" reserved 11110 Row Triplets in the allocated entries which then get overwritten by the packet contents. + // We write invalid triplets in the allocated entries which then get overwritten by the packet contents. // This is in case of missing packets so we can keep Local Object pointers valid. while (m_enhancements.size() < (p+1)*13) - m_enhancements.append( X26Triplet{ 41, 0x1e, 0 } ); + m_enhancements.append( X26Triplet{ 0xff, 0xff, 0xff } ); X26Triplet newX26Triplet; for (int t=0; t<13; t++) { const int enhanceListPointer = p*13+t; - newX26Triplet.setAddress(pkt.at(t*3+1) & 0x3f); - newX26Triplet.setMode(pkt.at(t*3+2) & 0x1f); - newX26Triplet.setData(((pkt.at(t*3+3) & 0x3f) << 1) | ((pkt.at(t*3+2) & 0x20) >> 5)); + // Need the "& 0xff" since QByteArray.at() returns (signed) chars + if ((pkt.at(t*3+2) & 0xff) == 0xff) + newX26Triplet.setInvalid(); + else { + newX26Triplet.setAddress(pkt.at(t*3+1) & 0x3f); + newX26Triplet.setMode(pkt.at(t*3+2) & 0x1f); + newX26Triplet.setData(((pkt.at(t*3+3) & 0x3f) << 1) | ((pkt.at(t*3+2) & 0x20) >> 5)); + } m_enhancements.replace(enhanceListPointer, newX26Triplet); } if (newX26Triplet.mode() == 0x1f && newX26Triplet.address() == 0x3f && newX26Triplet.data() & 0x01) diff --git a/src/qteletextdecoder/x26triplets.cpp b/src/qteletextdecoder/x26triplets.cpp index cae0628..a1f753b 100644 --- a/src/qteletextdecoder/x26triplets.cpp +++ b/src/qteletextdecoder/x26triplets.cpp @@ -26,6 +26,11 @@ X26Triplet::X26Triplet(int address, int mode, int data) m_data = data; } +bool X26Triplet::isValid() const +{ + return m_mode != 0xff; +} + int X26Triplet::address() const { return m_address; @@ -61,6 +66,11 @@ bool X26Triplet::isRowTriplet() const return (m_address >= 40); } +void X26Triplet::setInvalid() +{ + m_address = m_mode = m_data = 0xff; +} + void X26Triplet::setAddress(int address) { m_address = address; @@ -180,7 +190,9 @@ void X26TripletList::updateInternalData() triplet->m_reservedMode = false; triplet->m_reservedData = false; - if (triplet->isRowTriplet()) { + if (!triplet->isValid()) + triplet->m_error = X26Triplet::ErrorDecodingTriplet; + else if (triplet->isRowTriplet()) { switch (triplet->modeExt()) { case 0x00: // Full screen colour if (activePosition.isDeployed()) diff --git a/src/qteletextdecoder/x26triplets.h b/src/qteletextdecoder/x26triplets.h index f8b48a4..000f0b8 100644 --- a/src/qteletextdecoder/x26triplets.h +++ b/src/qteletextdecoder/x26triplets.h @@ -26,7 +26,7 @@ class X26Triplet { public: // x26model.h has the Plain English descriptions of these errors - enum X26TripletError { NoError, ActivePositionMovedUp, ActivePositionMovedLeft, InvokePointerInvalid, InvokeTypeMismatch, OriginModifierAlone }; + enum X26TripletError { NoError, ErrorDecodingTriplet, ActivePositionMovedUp, ActivePositionMovedLeft, InvokePointerInvalid, InvokeTypeMismatch, OriginModifierAlone }; enum ObjectSource { InvalidObjectSource, LocalObject, POPObject, GPOPObject }; X26Triplet() {} @@ -36,6 +36,7 @@ public: X26Triplet(int address, int mode, int data); + bool isValid() const; int address() const; int mode() const; int modeExt() const; @@ -44,6 +45,7 @@ public: int addressColumn() const; bool isRowTriplet() const; + void setInvalid(); void setAddress(int address); void setMode(int mode); void setData(int data); diff --git a/src/qteletextmaker/loadformats.cpp b/src/qteletextmaker/loadformats.cpp index ed8ee2e..2d98c85 100644 --- a/src/qteletextmaker/loadformats.cpp +++ b/src/qteletextmaker/loadformats.cpp @@ -227,6 +227,9 @@ bool LoadT42Format::load(QFile *inFile, QList& subPages, QVariantHash int foundPageNumber = -1; bool firstPacket0Found = false; bool pageBodyPacketsFound = false; + bool errorEnhancements = false; + bool errorLinks = false; + bool errorPresentation = false; m_inFile = inFile; @@ -374,6 +377,7 @@ bool LoadT42Format::load(QFile *inFile, QList& subPages, QVariantHash // Error found in at least one byte of the link // Neutralise the whole link to same magazine, page FF, subcode 3F7F qDebug("X/27/%d link %d decoding error", readDesignationCode, i); + errorLinks = true; m_inLine[b] = 0xf; m_inLine[b+1] = 0xf; m_inLine[b+2] = 0xf; @@ -416,15 +420,17 @@ bool LoadT42Format::load(QFile *inFile, QList& subPages, QVariantHash // Error decoding Hamming 24/18 qDebug("X/%d/%d triplet %d decoding error", readPacketNumber, readDesignationCode, i); if (readPacketNumber == 26) { - // Enhancements packet, set to "dummy" Address 41, Mode 0x1e, Data 0 - m_inLine[b] = 41; - m_inLine[b+1] = 0x1e; - m_inLine[b+2] = 0; + // Enhancements packet, set to invalid triplet + m_inLine[b] = 0xff; + m_inLine[b+1] = 0xff; + m_inLine[b+2] = 0xff; + errorEnhancements = true; } else { // Zero out whole decoded triplet, bound to make things go wrong... m_inLine[b] = 0x00; m_inLine[b+1] = 0x00; m_inLine[b+2] = 0x00; + errorPresentation = true; } } else { m_inLine[b] = d & 0x0003f; @@ -441,8 +447,15 @@ bool LoadT42Format::load(QFile *inFile, QList& subPages, QVariantHash } else if (!pageBodyPacketsFound) { m_error = "X/0 found, but no page body packets were found."; return false; - } else - return true; + } + + if (errorEnhancements) + m_warnings.append("Error decoding triplet(s) in enhancement data."); + if (errorLinks) + m_warnings.append("Error decoding FLOF links."); + if (errorPresentation) + m_warnings.append("Error decoding triplet(s) in presentation data."); + return true; } diff --git a/src/qteletextmaker/saveformats.cpp b/src/qteletextmaker/saveformats.cpp index af08901..faf0afd 100644 --- a/src/qteletextmaker/saveformats.cpp +++ b/src/qteletextmaker/saveformats.cpp @@ -160,8 +160,23 @@ QByteArray SaveTTIFormat::format18BitPacket(QByteArray packet) // TTI stores the triplets 6 bits at a time like we do, without Hamming encoding // We don't touch the first byte; the caller replaces it with the designation code // unless it's X/1-X/25 used in (G)POP pages - for (int i=1; i> 0) & 0xff] ^ hamming_24_18_forward[1][(toEncode >> 8) & 0xff] ^ hamming_24_18_forward_2[(toEncode >> 16) & 0x03]); - packet[c] = Byte_0; + Byte_0 = (hamming_24_18_forward[0][(toEncode >> 0) & 0xff] ^ hamming_24_18_forward[1][(toEncode >> 8) & 0xff] ^ hamming_24_18_forward_2[(toEncode >> 16) & 0x03]); + packet[c] = Byte_0; - D5_D11 = (toEncode >> 4) & 0x7f; - D12_D18 = (toEncode >> 11) & 0x7f; + D5_D11 = (toEncode >> 4) & 0x7f; + D12_D18 = (toEncode >> 11) & 0x7f; - P5 = 0x80 & ~(hamming_24_18_parities[0][D12_D18] << 2); - packet[c+1] = D5_D11 | P5; + P5 = 0x80 & ~(hamming_24_18_parities[0][D12_D18] << 2); + packet[c+1] = D5_D11 | P5; - P6 = 0x80 & ((hamming_24_18_parities[0][Byte_0] ^ hamming_24_18_parities[0][D5_D11]) << 2); - packet[c+2] = D12_D18 | P6; - } + P6 = 0x80 & ((hamming_24_18_parities[0][Byte_0] ^ hamming_24_18_parities[0][D5_D11]) << 2); + packet[c+2] = D12_D18 | P6; + } return packet; } @@ -440,14 +459,21 @@ bool SaveEP1Format::getWarnings(const PageBase &subPage) QByteArray SaveEP1Format::format18BitPacket(QByteArray packet) { - for (int c=1; c> 5); - packet[c+1] = packet.at(c+1) & 0x1f; - // Address of termination marker is 7f instead of 3f - if (packet.at(c+1) == 0x1f && packet.at(c) == 0x3f) - packet[c] = 0x7f; - } + for (int c=1; c> 5); + packet[c+1] = packet.at(c+1) & 0x1f; + // Address of termination marker is 7f instead of 3f + if (packet.at(c+1) == 0x1f && packet.at(c) == 0x3f) + packet[c] = 0x7f; + } return packet; } diff --git a/src/qteletextmaker/x26dockwidget.cpp b/src/qteletextmaker/x26dockwidget.cpp index ea30bf6..d8587fc 100644 --- a/src/qteletextmaker/x26dockwidget.cpp +++ b/src/qteletextmaker/x26dockwidget.cpp @@ -678,6 +678,13 @@ void X26DockWidget::updateAllCookedTripletWidgets(const QModelIndex &index) { const int modeExt = index.model()->data(index.model()->index(index.row(), 2), Qt::EditRole).toInt(); + if (modeExt == 0xff) { + disableTripletWidgets(); + m_cookedModePushButton->setEnabled(true); + m_cookedModePushButton->setText("Replace..."); + return; + } + m_cookedModePushButton->setEnabled(true); m_cookedModePushButton->setText(m_modeTripletNames.modeName(modeExt)); @@ -1034,7 +1041,7 @@ void X26DockWidget::insertTriplet(int modeExt, int row) if (modeExt >= 0x20 && modeExt != 0x24 && modeExt != 0x25 && modeExt != 0x26 && modeExt != 0x2a) { const int existingTripletModeExt = index.model()->data(index.model()->index(index.row(), 2), Qt::EditRole).toInt(); - if (existingTripletModeExt >= 0x20 && existingTripletModeExt != 0x24 && existingTripletModeExt != 0x25 && existingTripletModeExt != 0x26 && existingTripletModeExt != 0x2a) + if (existingTripletModeExt >= 0x20 && existingTripletModeExt <= 0x3f && existingTripletModeExt != 0x24 && existingTripletModeExt != 0x25 && existingTripletModeExt != 0x26 && existingTripletModeExt != 0x2a) newTriplet.setAddress(index.model()->data(index.model()->index(index.row(), 0), Qt::UserRole).toInt()); } // If we're inserting a Set Active Position or Full Row Colour triplet, diff --git a/src/qteletextmaker/x26model.cpp b/src/qteletextmaker/x26model.cpp index 6b79ff3..8e424ac 100644 --- a/src/qteletextmaker/x26model.cpp +++ b/src/qteletextmaker/x26model.cpp @@ -108,7 +108,7 @@ QVariant X26Model::data(const QModelIndex &index, int role) const else return QVariant(); case 1: - if (!triplet.isRowTriplet()) + if (triplet.isValid() && !triplet.isRowTriplet()) return triplet.addressColumn(); // For Set Active Position and Origin Modifier, data is the column else if (triplet.modeExt() == 0x04) @@ -122,9 +122,14 @@ QVariant X26Model::data(const QModelIndex &index, int role) const QString result; if (role == Qt::DisplayRole) { - if (index.column() == 2) + if (index.column() == 2) { + if (!triplet.isValid()) + return "Error decoding triplet"; return (m_modeTripletNames.modeName(triplet.modeExt())); + } // Column 3 - describe effects of data/address triplet parameters in plain English + if (!triplet.isValid()) + return QVariant(); switch (triplet.modeExt()) { case 0x01: // Full row colour case 0x07: // Address row 0 @@ -609,12 +614,15 @@ bool X26Model::setData(const QModelIndex &index, const QVariant &value, int role return true; case 2: // Cooked triplet mode - if (intValue < 0x20 && !triplet.isRowTriplet()) { + if (!triplet.isValid()) { + // Changing from invalid triplet + m_parentMainWidget->document()->undoStack()->push(new EditTripletCommand(m_parentMainWidget->document(), this, index.row(), EditTripletCommand::ETaddress, 0x00, intValue < 0x20 ? 41 : 0, role)); + m_parentMainWidget->document()->undoStack()->push(new EditTripletCommand(m_parentMainWidget->document(), this, index.row(), EditTripletCommand::ETdata, 0x00, 32, role)); + } else if (intValue < 0x20 && !triplet.isRowTriplet()) { // Changing mode from column triplet to row triplet m_parentMainWidget->document()->undoStack()->push(new EditTripletCommand(m_parentMainWidget->document(), this, index.row(), EditTripletCommand::ETaddress, 0x00, 41, role)); m_parentMainWidget->document()->undoStack()->push(new EditTripletCommand(m_parentMainWidget->document(), this, index.row(), EditTripletCommand::ETdata, 0x00, 0, role)); - } - if (intValue >= 0x20 && triplet.isRowTriplet()) { + } else if (intValue >= 0x20 && triplet.isRowTriplet()) { // Changing mode from row triplet to column triplet m_parentMainWidget->document()->undoStack()->push(new EditTripletCommand(m_parentMainWidget->document(), this, index.row(), EditTripletCommand::ETaddress, 0x00, 0, role)); m_parentMainWidget->document()->undoStack()->push(new EditTripletCommand(m_parentMainWidget->document(), this, index.row(), EditTripletCommand::ETdata, 0x00, 0, role)); diff --git a/src/qteletextmaker/x26model.h b/src/qteletextmaker/x26model.h index 8eb87fe..4b62b48 100644 --- a/src/qteletextmaker/x26model.h +++ b/src/qteletextmaker/x26model.h @@ -60,8 +60,9 @@ private: }; // Needs to be in the same order as enum X26TripletError in x26triplets.h - const tripletErrorShow m_tripletErrors[6] { + const tripletErrorShow m_tripletErrors[7] { { "", 0 }, // No error + { "Error decoding triplet", 2 }, { "Active Position can't move up", 0 }, { "Active Position can't move left within row", 1 }, { "Invocation not pointing to Object Definition", 3 },