File indexing completed on 2025-01-05 05:23:52
0001 /* 0002 This file is part of the Okteta Kasten module, made within the KDE community. 0003 0004 SPDX-FileCopyrightText: 2010 Friedrich W. H. Kossebau <kossebau@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0007 */ 0008 0009 #include "bytearraysrecstreamencoder.hpp" 0010 0011 // lib 0012 #include <bytearrayview.hpp> 0013 // Okteta gui 0014 #include <Okteta/ByteArrayTableLayout> 0015 // Okteta core 0016 #include <Okteta/AbstractByteArrayModel> 0017 // KF 0018 #include <KConfigGroup> 0019 #include <KSharedConfig> 0020 #include <KLocalizedString> 0021 // Qt 0022 #include <QTextStream> 0023 // Std 0024 #include <algorithm> 0025 #include <iterator> 0026 0027 const std::array<QString, Kasten::SRecStreamEncoderSettings::AddressSizeCount> 0028 Kasten::SRecStreamEncoderSettings::addressSizeConfigValueList = { 0029 QStringLiteral("32"), 0030 QStringLiteral("24"), 0031 QStringLiteral("16"), 0032 }; 0033 0034 template <> 0035 inline Kasten::SRecStreamEncoderSettings::AddressSizeId KConfigGroup::readEntry(const char *key, 0036 const Kasten::SRecStreamEncoderSettings::AddressSizeId &defaultValue) const 0037 { 0038 const QString entry = readEntry(key, QString()); 0039 0040 auto it = std::find(Kasten::SRecStreamEncoderSettings::addressSizeConfigValueList.cbegin(), 0041 Kasten::SRecStreamEncoderSettings::addressSizeConfigValueList.cend(), 0042 entry); 0043 if (it == Kasten::SRecStreamEncoderSettings::addressSizeConfigValueList.cend()) { 0044 return defaultValue; 0045 } 0046 0047 const int listIndex = std::distance(Kasten::SRecStreamEncoderSettings::addressSizeConfigValueList.cbegin(), it); 0048 return static_cast<Kasten::SRecStreamEncoderSettings::AddressSizeId>(listIndex); 0049 } 0050 0051 template <> 0052 inline void KConfigGroup::writeEntry(const char *key, 0053 const Kasten::SRecStreamEncoderSettings::AddressSizeId &value, 0054 KConfigBase::WriteConfigFlags flags) 0055 { 0056 const int listIndex = static_cast<int>(value); 0057 writeEntry(key, Kasten::SRecStreamEncoderSettings::addressSizeConfigValueList[listIndex], flags); 0058 } 0059 0060 namespace Kasten { 0061 0062 static inline constexpr 0063 int addressSize(SRecStreamEncoderSettings::AddressSizeId id) 0064 { 0065 return 4 - static_cast<int>(id); 0066 } 0067 0068 SRecStreamEncoderSettings::SRecStreamEncoderSettings() = default; 0069 0070 bool SRecStreamEncoderSettings::operator==(const SRecStreamEncoderSettings& other) const 0071 { 0072 return (addressSizeId == other.addressSizeId); 0073 } 0074 0075 void SRecStreamEncoderSettings::loadConfig(const KConfigGroup& configGroup) 0076 { 0077 addressSizeId = configGroup.readEntry(AddressSizeConfigKey, DefaultAddressSize); 0078 } 0079 0080 void SRecStreamEncoderSettings::saveConfig(KConfigGroup& configGroup) const 0081 { 0082 configGroup.writeEntry(AddressSizeConfigKey, addressSizeId); 0083 } 0084 0085 0086 const char ByteArraySRecStreamEncoder::hexDigits[16] = { 0087 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' 0088 }; 0089 0090 void ByteArraySRecStreamEncoder::streamLine(QTextStream& textStream, RecordType recordType, 0091 const unsigned char* line) 0092 { 0093 // checksum is ones' complement of sum of the values in the line 0094 unsigned char checksum = 0; 0095 0096 textStream << startCode << charOfRecordType(recordType); 0097 0098 const uint length = line[0]; 0099 for (uint i = 0; i < length; ++i) { 0100 const unsigned char byte = line[i]; 0101 textStream << hexValueOfNibble(byte >> 4) 0102 << hexValueOfNibble(byte); 0103 checksum += byte; 0104 } 0105 0106 checksum = ~checksum; 0107 textStream << hexValueOfNibble(checksum >> 4) << hexValueOfNibble(checksum) << '\n'; 0108 } 0109 0110 void ByteArraySRecStreamEncoder::streamBlockHeader(QTextStream& textStream, unsigned char* line) 0111 // const char* moduleName = 0, const char* description = 0, 0112 // quint8 version = 0, quint8 revision = 0 ) 0113 { 0114 // cmp. https://linux.die.net/man/1/srec_cat 0115 // WP says: vendor specific data rather than program data 0116 // constexpr int moduleNameLineOffset = 3; 0117 // constexpr int moduleNameLength = 10; 0118 // constexpr int versionLineOffset = moduleNameLineOffset + moduleNameLength; 0119 // constexpr int versionLength = 1; 0120 // constexpr int revisionLineOffset = versionLineOffset + versionLength; 0121 // constexpr int revisionLength = 1; 0122 // constexpr int descriptionLineOffset = revisionLineOffset + revisionLength; 0123 // constexpr int descriptionLength = 18; 0124 constexpr int headerByteCount = 3; 0125 0126 line[addressLineOffset] = 0; // address unused 0127 line[addressLineOffset + 1] = 0; // address unused 0128 0129 // leave data empty for now 0130 line[byteCountLineOffset] = headerByteCount; 0131 0132 streamLine(textStream, RecordType::BlockHeader, line); 0133 } 0134 0135 void ByteArraySRecStreamEncoder::streamRecordCount(QTextStream& textStream, unsigned char* line, 0136 quint16 recordCount) 0137 { 0138 constexpr int recordCountLineSize = 2; 0139 constexpr int recordCountByteCount = byteCountLineSize + recordCountLineSize; 0140 0141 line[byteCountLineOffset] = recordCountByteCount; 0142 writeBigEndian(&line[addressLineOffset], recordCount, recordCountLineSize); 0143 0144 streamLine(textStream, RecordType::RecordCount, line); 0145 } 0146 0147 // from M68000PRM.pdf: 0148 // comp. with tty 28 bytes are max (with 3b address) 0149 // terminated with CR if downloading 0150 // s-record may have some initial field, e.g. for line-number 0151 // end of x-block: The address field may optionally contain the x-byte address of the instruction to which control is to be passed. 0152 // Under VERSAdos, the resident linkerOs ENTRY command can be used to 0153 // specify this address. If this address is not specified, the first entry point speci- 0154 // fication encountered in the object module input will be used. There is no code/ 0155 // data field. 0156 0157 // TODO: recordType is not limited to valid values, also brings recalculation of addressLineSize 0158 void ByteArraySRecStreamEncoder::streamBlockEnd(QTextStream& textStream, unsigned char* line, 0159 RecordType recordType, quint32 startAddress) 0160 { 0161 const int addressLineSize = endOfBlockAddressSize(recordType); 0162 const int blockEndByteCount = byteCountLineSize + addressLineSize; 0163 0164 line[byteCountLineOffset] = blockEndByteCount; 0165 writeBigEndian(&line[addressLineOffset], startAddress, addressLineSize); 0166 0167 streamLine(textStream, recordType, line); 0168 } 0169 0170 ByteArraySRecStreamEncoder::ByteArraySRecStreamEncoder() 0171 : AbstractByteArrayStreamEncoder(i18nc("name of the encoding target", "S-Record"), QStringLiteral("text/x-srecord")) 0172 { 0173 const KConfigGroup configGroup(KSharedConfig::openConfig(), ConfigGroupId); 0174 mSettings.loadConfig(configGroup); 0175 } 0176 0177 ByteArraySRecStreamEncoder::~ByteArraySRecStreamEncoder() = default; 0178 0179 void ByteArraySRecStreamEncoder::setSettings(const SRecStreamEncoderSettings& settings) 0180 { 0181 if (mSettings == settings) { 0182 return; 0183 } 0184 0185 mSettings = settings; 0186 KConfigGroup configGroup(KSharedConfig::openConfig(), ConfigGroupId); 0187 mSettings.saveConfig(configGroup); 0188 Q_EMIT settingsChanged(); 0189 } 0190 0191 bool ByteArraySRecStreamEncoder::encodeDataToStream(QIODevice* device, 0192 const ByteArrayView* byteArrayView, 0193 const Okteta::AbstractByteArrayModel* byteArrayModel, 0194 const Okteta::AddressRange& range) 0195 { 0196 Q_UNUSED(byteArrayView); 0197 0198 bool success = true; 0199 0200 // encode 0201 QTextStream textStream(device); 0202 0203 // prepare 0204 constexpr int maxLineLength = 64 / 2; 0205 const int addressLineSize = addressSize(mSettings.addressSizeId); 0206 const int maxDataPerLineCount = maxLineLength - byteCountLineSize - addressLineSize; 0207 const int dataLineOffset = addressLineOffset + addressLineSize; 0208 0209 const Okteta::ByteArrayTableLayout layout(byteArrayView->noOfBytesPerLine(), 0210 byteArrayView->firstLineOffset(), 0211 byteArrayView->startOffset(), 0, byteArrayModel->size()); 0212 0213 const Okteta::Coord startCoord = layout.coordOfIndex(range.start()); 0214 const int lastLinePosition = layout.lastLinePosition(startCoord.line()); 0215 const int dataPerLineCount = qMin(byteArrayView->noOfBytesPerLine(), maxDataPerLineCount); 0216 const RecordType dataSequenceType = dataSequenceRecordType(mSettings.addressSizeId); 0217 const RecordType endOfBlockType = endOfBlockRecordType(mSettings.addressSizeId); 0218 0219 unsigned char line[maxLineLength]; 0220 0221 unsigned char* const lineData = &line[dataLineOffset]; 0222 const int firstDataEnd = lastLinePosition - startCoord.pos() + 1; 0223 int d = 0; 0224 int nextDataEnd = qMin(firstDataEnd, dataPerLineCount); 0225 Okteta::Address recordOffset = range.start(); 0226 int recordCount = 0; 0227 0228 // header 0229 streamBlockHeader(textStream, line); 0230 0231 Okteta::Address i = range.start(); 0232 while (i <= range.end()) { 0233 const Okteta::Byte byte = byteArrayModel->byte(i); 0234 lineData[d] = byte; 0235 0236 ++d; 0237 ++i; 0238 if (d == nextDataEnd) { 0239 line[byteCountLineOffset] = d + 1 + addressLineSize; 0240 writeBigEndian(&line[addressLineOffset], recordOffset, addressLineSize); 0241 0242 streamLine(textStream, dataSequenceType, line); 0243 0244 ++recordCount; 0245 recordOffset = i; 0246 d = 0; 0247 nextDataEnd = qMin(range.end() - i + 1, dataPerLineCount); 0248 } 0249 } 0250 0251 // footer 0252 streamRecordCount(textStream, line, recordCount); 0253 streamBlockEnd(textStream, line, endOfBlockType); 0254 0255 return success; 0256 } 0257 0258 } 0259 0260 #include "moc_bytearraysrecstreamencoder.cpp"