?login_element?

Subversion Repositories NedoOS

Rev

Rev 539 | Blame | Compare with Previous | Last modification | View Log | Download

  1. /*
  2.  
  3.   SjASMPlus Z80 Cross Compiler
  4.  
  5.   Copyright (c) 2004-2006 Aprisobal
  6.  
  7.   This software is provided 'as-is', without any express or implied warranty.
  8.   In no event will the authors be held liable for any damages arising from the
  9.   use of this software.
  10.  
  11.   Permission is granted to anyone to use this software for any purpose,
  12.   including commercial applications, and to alter it and redistribute it freely,
  13.   subject to the following restrictions:
  14.  
  15.   1. The origin of this software must not be misrepresented; you must not claim
  16.          that you wrote the original software. If you use this software in a product,
  17.          an acknowledgment in the product documentation would be appreciated but is
  18.          not required.
  19.  
  20.   2. Altered source versions must be plainly marked as such, and must not be
  21.          misrepresented as being the original software.
  22.  
  23.   3. This notice may not be removed or altered from any source distribution.
  24.  
  25. */
  26.  
  27. // io_trd.cpp
  28.  
  29. #include "sjdefs.h"
  30.  
  31. #ifdef _MSC_VER
  32. #pragma pack(push, 1)
  33. #endif
  34. struct STrdFile {
  35.         constexpr static size_t NAME_BASE_SZ = 8;
  36.         constexpr static size_t NAME_EXT_SZ = 1;
  37.         constexpr static size_t NAME_ALT_EXT_SZ = 3;    // 3-letter extensions are sometimes used instead of "address" field
  38.         constexpr static size_t NAME_FULL_SZ = NAME_BASE_SZ + NAME_EXT_SZ;
  39.         constexpr static size_t NAME_ALT_FULL_SZ = NAME_BASE_SZ + NAME_ALT_EXT_SZ;
  40.  
  41.         byte            filename[NAME_BASE_SZ];
  42.         byte            ext;
  43.         byte            addressLo;              // sometimes: other two extension letters for 8.3 naming scheme
  44.         byte            addressHi;              // can't be `word` because of BE-hosts support
  45.         word            length;
  46.         byte            sectorLength;
  47.         byte            startSector;
  48.         byte            startTrack;
  49. }
  50. #ifndef _MSC_VER
  51.         __attribute__((packed));
  52. #else
  53.         ;
  54. #pragma pack(pop)
  55. #endif
  56. static_assert(16 == sizeof(STrdFile), "TRD file header is expected to be 16 bytes long!");
  57.  
  58. #ifdef _MSC_VER
  59. #pragma pack(push, 1)
  60. #endif
  61. struct STrdDisc {
  62.         constexpr static byte TRDOS_DISC_ID = 0x10;
  63.         constexpr static size_t SECTOR_SZ = 256;
  64.         constexpr static size_t SECTORS_PER_TRACK = 16;
  65.         constexpr static size_t PASSWORD_SZ = 9;
  66.         constexpr static size_t LABEL_SZ = 8;
  67.         constexpr static byte DISK_TYPE_T80_S2 = 0x16;          // 80 tracks, double sided
  68.         constexpr static byte DISK_TYPE_T40_S2 = 0x17;          // 40 tracks, double sided
  69.         constexpr static byte DISK_TYPE_T80_S1 = 0x18;          // 80 tracks, single sided
  70.         constexpr static byte DISK_TYPE_T40_S1 = 0x19;          // 40 tracks, single sided
  71.  
  72.         byte            _endOfRootDirectory             = 0x00;
  73.         byte            _unused[224]                    = {};
  74.         byte            freeSector                              = 0;
  75.         byte            freeTrack                               = 1;
  76.         byte            diskType                                = DISK_TYPE_T80_S2;
  77.         byte            numOfFiles                              = 0;
  78.         word            numOfFreeSectors                = (79+80)*SECTORS_PER_TRACK;    // 0x09F0 for T80_S2 empty disc
  79.         byte            trDosId                                 = TRDOS_DISC_ID;
  80.         byte            _unused2[2]                             = {};
  81.         byte            password[PASSWORD_SZ]   = {' ',' ',' ',' ',' ',' ',' ',' ',' '};
  82.         byte            _unused3[1]                             = {};
  83.         byte            numOfDeleted                    = 0;
  84.         byte            label[LABEL_SZ]                 = {' ',' ',' ',' ',' ',' ',' ',' '};
  85.         byte            _unused4[3]                             = {};
  86.  
  87.         bool isTrdInfo() const {
  88.                 return (TRDOS_DISC_ID == trDosId);
  89.         }
  90.  
  91.         static long fileOffset(const long track, const long sector) {
  92.                 return (track * SECTOR_SZ * SECTORS_PER_TRACK) + (sector * SECTOR_SZ);
  93.         }
  94.  
  95.         void swapEndianness();
  96.         bool writeToFile(FILE *ftrd);
  97. }
  98. #ifndef _MSC_VER
  99.         __attribute__((packed));
  100. #else
  101.         ;
  102. #pragma pack(pop)
  103. #endif
  104. static_assert(STrdDisc::SECTOR_SZ == sizeof(STrdDisc), "TRD disc info is expected to be 256 bytes long!");
  105.  
  106. void STrdDisc::swapEndianness() {
  107.         numOfFreeSectors = sj_bswap16(numOfFreeSectors);
  108. }
  109.  
  110. bool STrdDisc::writeToFile(FILE *ftrd) {
  111.         if (Options::IsBigEndian) swapEndianness();             // fix endianness in binary form before write
  112.         if (1 != fwrite(this, sizeof(STrdDisc), 1, ftrd)) return false;
  113.         if (Options::IsBigEndian) swapEndianness();             // revert endianness back to native host form
  114.         return true;
  115. }
  116.  
  117. #ifdef _MSC_VER
  118. #pragma pack(push, 1)
  119. #endif
  120. struct STrdHead {
  121.         constexpr static size_t NUM_OF_FILES_MAX = 128;         // 8 sectors with 16B records
  122.  
  123.         STrdFile        catalog[NUM_OF_FILES_MAX];
  124.         STrdDisc        info;
  125.  
  126.         void swapEndianness();
  127.         bool readFromFile(FILE *ftrd);
  128.         bool writeToFile(FILE *ftrd);
  129. }
  130. #ifndef _MSC_VER
  131.         __attribute__((packed));
  132. #else
  133.         ;
  134. #pragma pack(pop)
  135. #endif
  136. static_assert(9 * STrdDisc::SECTOR_SZ == sizeof(STrdHead), "TRD catalog and info area should be 9 sectors long!");
  137.  
  138. void STrdHead::swapEndianness() {
  139.         info.swapEndianness();
  140.         for (STrdFile & file : this->catalog) file.length = sj_bswap16(file.length);
  141. }
  142.  
  143. bool STrdHead::readFromFile(FILE *ftrd) {
  144.         if (1 != fread(this, sizeof(STrdHead), 1, ftrd)) return false;
  145.         if (Options::IsBigEndian) swapEndianness();
  146.         return this->info.isTrdInfo();
  147. }
  148.  
  149. bool STrdHead::writeToFile(FILE *ftrd) {
  150.         if (Options::IsBigEndian) swapEndianness();             // fix endianness in binary form before write
  151.         if (1 != fwrite(this, sizeof(STrdHead), 1, ftrd)) return false;
  152.         if (Options::IsBigEndian) swapEndianness();             // revert endianness back to native host form
  153.         return true;
  154. }
  155.  
  156. /**
  157.  * @brief Write empty TRD file (80 tracks, 2 sides) into file
  158.  *
  159.  * @param ff file handle to write content into
  160.  * @param buf 4096 bytes long buffer (must be zeroed by caller) (16 sectors = 1 track)
  161.  * @param label nullptr or 8 characters long disc label
  162.  * @return int 1 if OK, 0 in case of write error
  163.  */
  164. static int saveEmptyWrite(FILE* ff, byte* buf, const char label[8]) {
  165.         //catalog (8 zeroed sectors)
  166.         if (8 != fwrite(buf, STrdDisc::SECTOR_SZ, 8, ff)) return 0;
  167.         // disc info in sector 8
  168.         {
  169.                 // the default 80 track two sided disc info initialized
  170.                 STrdDisc discInfo{};
  171.                 // replace label data if requested
  172.                 if (label) memcpy(discInfo.label, label, STrdDisc::LABEL_SZ);
  173.                 if (!discInfo.writeToFile(ff)) return 0;
  174.         }
  175.         // zeroes till end of first track
  176.         if (7 != fwrite(buf, STrdDisc::SECTOR_SZ, 7, ff)) return 0;
  177.         // remaining tracks in image contains all zeroes
  178.         for (int i = 0; i < (79 + 80); ++i) {           // 80 tracks, two sides, one track is already done
  179.                 if (STrdDisc::SECTORS_PER_TRACK != fwrite(buf, STrdDisc::SECTOR_SZ, STrdDisc::SECTORS_PER_TRACK, ff)) return 0;
  180.         }
  181.         return 1;
  182. }
  183.  
  184. bool TRD_SaveEmpty(const char* fname, const char label[8]) {
  185.         FILE* ff;
  186.         if (!FOPEN_ISOK(ff, fname, "wb")) {
  187.                 Error("opening file for write", fname, IF_FIRST);
  188.                 return 0;
  189.         }
  190.         byte* buf = (byte*) calloc(STrdDisc::SECTORS_PER_TRACK*STrdDisc::SECTOR_SZ, sizeof(byte));
  191.         if (buf == NULL) ErrorOOM();
  192.         int result = saveEmptyWrite(ff, buf, label);
  193.         free(buf);
  194.         fclose(ff);
  195.         if (!result) Error("Write error (disk full?)", fname, IF_FIRST);
  196.         return result;
  197. }
  198.  
  199. ETrdFileName TRD_FileNameToBytes(const char* inputName, byte binName[12], int & nameL) {
  200.         constexpr int baseSz = int(STrdFile::NAME_BASE_SZ);     // pre-cast to `int` (vs `nameL`)
  201.         const char* ext = strrchr(inputName, '.');
  202.         const int maxL = std::min(baseSz, ext ? int(ext-inputName) : baseSz);
  203.         nameL = 0;
  204.         while (inputName[nameL] && nameL < maxL) {
  205.                 binName[nameL] = inputName[nameL];
  206.                 ++nameL;
  207.         }
  208.         while (nameL < baseSz) binName[nameL++] = ' ';
  209.         while (ext && ext[1] && nameL < int(STrdFile::NAME_ALT_FULL_SZ)) {
  210.                 binName[nameL] = ext[1];
  211.                 ++nameL;
  212.                 ++ext;
  213.         }
  214.         while (STrdFile::NAME_FULL_SZ != nameL && STrdFile::NAME_ALT_FULL_SZ != nameL) {
  215.                 binName[nameL++] = ' ';         // the file name is either 8+1 or 8+3 (not 8+2)
  216.         }
  217.         int fillIdx = nameL;
  218.         while (fillIdx < 12) binName[fillIdx++] = 0;
  219.         if (int(STrdFile::NAME_FULL_SZ) < nameL) return THREE_LETTER_EXTENSION;
  220.         switch (binName[baseSz]) {
  221.                 case 'B': case 'C': case 'D': case '#':
  222.                         return OK;
  223.         }
  224.         return INVALID_EXTENSION;
  225. }
  226.  
  227. static int ReturnWithError(const char* errorText, const char* fname, FILE* fileToClose) {
  228.         if (nullptr != fileToClose) fclose(fileToClose);
  229.         Error(errorText, fname, IF_FIRST);
  230.         return 0;
  231. }
  232.  
  233. // use autostart == -1 to disable it (the valid autostart is 0..9999 as line number of BASIC program)
  234. bool TRD_AddFile(const char* fname, const char* fhobname, int start, int length, int autostart, bool replace, bool addplace, int lengthMinusVars) {
  235.  
  236.         // do some preliminary checks with file name and autostart - prepare final catalog entry data
  237.         union {
  238.                 STrdFile trdf;                  // structure to hold future form of catalog record about new file
  239.                 byte longFname[12];             // 12 byte access for TRD_FileNameToBytes (to avoid LGTM alert)
  240.         };
  241.         int Lname = 0;
  242.         // this will overwrite also first byte of "trd.length" (12 bytes are affected, not just 11)
  243.         const ETrdFileName nameWarning = TRD_FileNameToBytes(fhobname, longFname, Lname);
  244.         const bool isExtensionB = ('B' == trdf.ext);
  245.         if (!addplace) {
  246.                 if (INVALID_EXTENSION == nameWarning) {
  247.                         WarningById(W_TRD_EXT_INVALID, fhobname);
  248.                 }
  249.                 if (THREE_LETTER_EXTENSION == nameWarning) {
  250.                         WarningById(W_TRD_EXT_3, fhobname);
  251.                         if (isExtensionB) {
  252.                                 WarningById(W_TRD_EXT_B, fhobname);
  253.                                 Lname = STrdFile::NAME_FULL_SZ;
  254.                         }
  255.                 }
  256.         }
  257.         if (0 <= autostart && (!isExtensionB || 9999 < autostart)) {
  258.                 Warning("zx.trdimage_add_file: autostart value is BASIC program line number (0..9999) (in lua use -1 otherwise).");
  259.                 autostart = -1;
  260.         }
  261.         if (-1 != lengthMinusVars) {
  262.                 if (!isExtensionB) {
  263.                         Error("zx.trdimage_add_file: length without variables is for BASIC files only.");
  264.                         return 0;
  265.                 } else if (lengthMinusVars < 0 || length < lengthMinusVars) {
  266.                         Error("zx.trdimage_add_file: length without variables is not in <0..length> range.");
  267.                         return 0;
  268.                 }
  269.         }
  270.  
  271.         // more validations - for Lua (or SAVETRD letting wrong values go through)
  272.         if (!DeviceID) {
  273.                 Error("zx.trdimage_add_file: this function available only in real device emulation mode.");
  274.                 return 0;
  275.         }
  276.         if (start < 0 || 0xFFFF < start) {
  277.                 Error("zx.trdimage_add_file: start address must be in 0000..FFFF range", bp, PASS3);
  278.                 return 0;
  279.         }
  280.         if (length <= 0 || 0xFF00 < length) {
  281.                 // zero length not allowed any more, because TRD docs on internet are imprecise
  282.                 // and I'm not sure what is the correct way of saving zero length file => error
  283.                 Error("zx.trdimage_add_file: length must be in 0001..FF00 range", bp, PASS3);
  284.                 return 0;
  285.         }
  286.         if (0x10000 < start+length) {
  287.                 Error("zx.trdimage_add_file: provided start+length will run out of device memory", bp, PASS3);
  288.                 return 0;
  289.         }
  290.         trdf.length = word(length);
  291.         trdf.sectorLength = byte((length + 255 + (0 <= autostart ? 4 : 0))>>8);
  292.         if (isExtensionB) {
  293.                 trdf.addressLo = byte(length);
  294.                 trdf.addressHi = byte(length>>8);
  295.                 if (-1 != lengthMinusVars) trdf.length = word(lengthMinusVars);
  296.         } else {
  297.                 if (Lname <= int(STrdFile::NAME_FULL_SZ)) {
  298.                         trdf.addressLo = byte(start);   // single letter extension => "start" field is used for start value
  299.                         trdf.addressHi = byte(start>>8);
  300.                 }
  301.         }
  302.         if (0 == trdf.sectorLength) {   // can overflow only when 0xFF00 length with autostart => 0
  303.                 Error("zx.trdimage_add_file: sector length over 0xFF max", bp, PASS3);
  304.                 return 0;
  305.         }
  306.  
  307.         // read 9 sectors of disk into "trdHead" (contains root directory catalog and disk info data)
  308.         FILE* ff;
  309.         if (!FOPEN_ISOK(ff, fname, "r+b")) return ReturnWithError("Error opening file", fname, ff);
  310.         STrdHead trdHead;
  311.         if (!trdHead.readFromFile(ff)) {
  312.                 return ReturnWithError("TRD image read error", fname, ff);
  313.         }
  314.  
  315.         // check if the requested file is already on the disk
  316.         // in "add" or "replace" mode also delete all extra ones with the same name, keeping only last
  317.         unsigned fileIndex = STrdHead::NUM_OF_FILES_MAX;
  318.         for (unsigned fatIndex = 0; fatIndex < STrdHead::NUM_OF_FILES_MAX; ++fatIndex) {
  319.                 auto & entry = trdHead.catalog[fatIndex];
  320.                 if (0 == entry.filename[0]) break;              // beyond last FAT record, finish the loop
  321.                 if (memcmp(entry.filename, trdf.filename, Lname)) continue;     // different file name -> continue
  322.                 // in "add" or "replace" mode delete the previous incarnations of this filename (returns only last one)
  323.                 if ((addplace || replace) && STrdHead::NUM_OF_FILES_MAX != fileIndex) {
  324.                         // delete the previously found file (it stays in catalog as deleted file)
  325.                         trdHead.catalog[fileIndex].filename[0] = 1;
  326.                         ++trdHead.info.numOfDeleted;
  327.                 }
  328.                 // remember the position of last entry with the requested file name
  329.                 fileIndex = fatIndex;
  330.         }
  331.  
  332.         // check and process [un]found file based on the requested mode
  333.         if (addplace) {
  334.                 // in "add" mode the file must already exist
  335.                 if (STrdHead::NUM_OF_FILES_MAX == fileIndex) {
  336.                         return ReturnWithError("TRD image does not have a specified file to add data", fname, ff);
  337.                 }
  338.         } else if (replace) {
  339.                 // in "replace" mode delete also the last occurance
  340.                 if (STrdHead::NUM_OF_FILES_MAX != fileIndex) {
  341.                         auto & entry = trdHead.catalog[fileIndex];
  342.                         if (fileIndex + 1 == trdHead.info.numOfFiles) {         // if last file in the catalog
  343.                                 // It's last file of catalog, erase it as if it was not on disc at all
  344.                                 // verify if the free space starts just where last file ends (integrity of TRD image)
  345.                                 const byte nextTrack = ((entry.sectorLength + entry.startSector) >> 4) + entry.startTrack;
  346.                                 const byte nextSector = (entry.sectorLength + entry.startSector) & 0x0F;
  347.                                 // if file connects to first free sector, salvage the space back
  348.                                 if (nextSector != trdHead.info.freeSector || nextTrack != trdHead.info.freeTrack) {
  349.                                         return ReturnWithError("TRD free sector was not connected to last file", fname, ff);
  350.                                 }
  351.                                 // return the sectors used by file back to "free sectors" pool
  352.                                 trdHead.info.freeSector = entry.startSector;
  353.                                 trdHead.info.freeTrack = entry.startTrack;
  354.                                 trdHead.info.numOfFreeSectors += entry.sectorLength;
  355.                                 // delete the file (wipe catalog entry completely as if it was not written)
  356.                                 --trdHead.info.numOfFiles;
  357.                                 entry.filename[0] = 0;
  358.                         } else {
  359.                                 // delete the file (but it stays in catalog as deleted file) (and new file will be added)
  360.                                 entry.filename[0] = 1;
  361.                                 ++trdHead.info.numOfDeleted;
  362.                         }
  363.                 }
  364.                 fileIndex = trdHead.info.numOfFiles;
  365.         } else {
  366.                 // in "normal" mode warn when file already exists
  367.                 if (STrdHead::NUM_OF_FILES_MAX != fileIndex) {
  368.                         // to keep legacy behaviour of older sjasmplus versions, this is just warning
  369.                         // and the same file will be added to end of directory any way
  370.                         WarningById(W_TRD_DUPLICATE, fname);
  371.                 }
  372.                 fileIndex = trdHead.info.numOfFiles;
  373.         }
  374.  
  375.         // fileIndex should point to valid record in catalog, verify the status and free space
  376.         if (STrdHead::NUM_OF_FILES_MAX == fileIndex) {
  377.                 return ReturnWithError("TRD image is full of files", fname, ff);
  378.         }
  379.         auto & target = trdHead.catalog[fileIndex];
  380.         const bool isNewTarget = !!memcmp(target.filename, trdf.filename, Lname);
  381.         if (0 != target.filename[0] && isNewTarget) {
  382.                 // the target entry must have zero as first char or must have requested name
  383.                 return ReturnWithError("TRD inconsistent catalog data", fname, ff);
  384.         }
  385.         if (trdHead.info.numOfFreeSectors < trdf.sectorLength) {
  386.                 return ReturnWithError("TRD image has not enough free space", fname, ff);
  387.         }
  388.         const int keepSectors = addplace ? target.sectorLength : 0;
  389.         if (0xFF < keepSectors + int(trdf.sectorLength)) {
  390.                 return ReturnWithError("zx.trdimage_add_file: new sector length over 0xFF max",  fname, ff);
  391.         }
  392.  
  393.         // set the target record in catalog
  394.         if (addplace) {
  395.                 // just add sector length, keep target.length at old value (no idea why, ask Dart Alver)
  396.                 target.sectorLength += trdf.sectorLength;
  397.                 // keeps basically EVERYTHING in the old catalog entry as it was, only sector length is raised
  398.         } else {
  399.                 // finalize the prepared catalog entry record with starting position
  400.                 if (isNewTarget) {
  401.                         trdf.startSector = trdHead.info.freeSector;
  402.                         trdf.startTrack = trdHead.info.freeTrack;
  403.                 } else {
  404.                         trdf.startSector = target.startSector;
  405.                         trdf.startTrack = target.startTrack;
  406.                 }
  407.                 // write it to the actual catalog
  408.                 target = trdf;
  409.         }
  410.  
  411.         // in "add" mode shift all data sectors to make room for the newly added ones
  412.         if (addplace) {
  413.                 const long targetPos = STrdDisc::fileOffset(target.startTrack, target.startSector);
  414.                 const long oldTargetEndPos = targetPos + (keepSectors * STrdDisc::SECTOR_SZ);
  415.                 const long newTargetEndPos = targetPos + (target.sectorLength * STrdDisc::SECTOR_SZ);
  416.                 const long freePos = STrdDisc::fileOffset(trdHead.info.freeTrack, trdHead.info.freeSector);
  417.                 if (oldTargetEndPos < freePos) {        // some data after old file -> shift them a bit
  418.                         // first move the data inside the TRD image
  419.                         size_t dataToMoveLength = freePos - oldTargetEndPos;
  420.                         std::unique_ptr<byte[]> dataToMove(new byte[dataToMoveLength]);
  421.                         if (nullptr == dataToMove.get()) ErrorOOM();
  422.                         if (fseek(ff, oldTargetEndPos, SEEK_SET)) {
  423.                                 return ReturnWithError("TRD image has wrong format", fname, ff);
  424.                         }
  425.                         if (dataToMoveLength != fread(dataToMove.get(), 1, dataToMoveLength, ff)) {
  426.                                 return ReturnWithError("TRD read error", fname, ff);
  427.                         }
  428.                         if (fseek(ff, newTargetEndPos, SEEK_SET)) {
  429.                                 return ReturnWithError("TRD image has wrong format", fname, ff);
  430.                         }
  431.                         // first modification of the provided TRD file (since here, if something fails, the file is damaged)
  432.                         if (dataToMoveLength != fwrite(dataToMove.get(), 1, dataToMoveLength, ff)) {
  433.                                 return ReturnWithError("TRD write error", fname, ff);
  434.                         }
  435.                         dataToMove.release();
  436.                         // adjust all catalog entries which got the content sectors shifted
  437.                         for (unsigned entryIndex = 0; entryIndex < STrdHead::NUM_OF_FILES_MAX; ++entryIndex) {
  438.                                 auto & entry = trdHead.catalog[entryIndex];
  439.                                 if (0 == entry.filename[0]) break;              // beyond last FAT record, finish the loop
  440.                                 if (entryIndex == fileIndex) continue;  // ignore the "target" itself
  441.                                 // check if all files in catalog after target are also affected by content shift and vice versa
  442.                                 const long entryPos = STrdDisc::fileOffset(entry.startTrack, entry.startSector);
  443.                                 if ((entryIndex < fileIndex) != (entryPos < targetPos)) {
  444.                                         return ReturnWithError("TRD inconsistent catalog data", fname, ff);
  445.                                 }
  446.                                 if (entryPos < targetPos) continue;             // this one is ahead of the target file
  447.                                 // the file got shifted content => update the catalog entry
  448.                                 entry.startTrack += (trdf.sectorLength + entry.startSector) >> 4;
  449.                                 entry.startSector = (trdf.sectorLength + entry.startSector) & 0x0F;
  450.                         }
  451.                 } // END if (oldTargetEndPos < freePos)
  452.         } // END if (addplace)
  453.  
  454.         // save the new data into the TRD (content sectors)
  455.         long writePos = STrdDisc::fileOffset(target.startTrack, target.startSector);
  456.         writePos += keepSectors * STrdDisc::SECTOR_SZ;
  457.         if (fseek(ff, writePos, SEEK_SET)) {
  458.                 return ReturnWithError("TRD image has wrong format", fname, ff);
  459.         }
  460.         if (!SaveRAM(ff, start, length)) {
  461.                 return ReturnWithError("TRD write device RAM error", fname, ff);
  462.         }
  463.         if (!addplace && 0 <= autostart) {
  464.                 byte abin[] {0x80, 0xAA, static_cast<byte>(autostart), static_cast<byte>(autostart>>8)};
  465.                 if (4 != fwrite(abin, 1, 4, ff)) {
  466.                         return ReturnWithError("Write error", fname, ff);
  467.                 }
  468.         }
  469.  
  470.         // update next free sector/track position
  471.         trdHead.info.freeTrack += (trdf.sectorLength + trdHead.info.freeSector) >> 4;
  472.         trdHead.info.freeSector = (trdf.sectorLength + trdHead.info.freeSector) & 0x0F;
  473.         // update remaining free sectors
  474.         trdHead.info.numOfFreeSectors -= trdf.sectorLength;
  475.         if (isNewTarget) ++trdHead.info.numOfFiles;
  476.  
  477.         // update whole catalog and disc info with modified data
  478.         if (fseek(ff, 0, SEEK_SET)) {
  479.                 return ReturnWithError("TRD image has wrong format", fname, ff);
  480.         }
  481.         if (!trdHead.writeToFile(ff)) {
  482.                 return ReturnWithError("TRD write error", fname, ff);
  483.         }
  484.  
  485.         fclose(ff);
  486.         return 1;
  487. }
  488.  
  489. int TRD_PrepareIncFile(const char* trdname, const char* filename, aint & offset, aint & length) {
  490.         // parse filename into TRD file form (max 8+3, don't warn about 3-letter extension)
  491.         byte trdFormName[12];
  492.         int Lname = 0;
  493.         TRD_FileNameToBytes(filename, trdFormName, Lname);      // ignore diagnostic info about extension
  494.  
  495.         // read 9 sectors of disk into "trdHead" (contains root directory catalog and disk info data)
  496.         STrdHead trdHead;
  497.         char* fullTrdName = GetPath(trdname);
  498.         FILE* ff = SJ_fopen(fullTrdName, "rb");
  499.         free(fullTrdName);
  500.         fullTrdName = nullptr;
  501.         if (nullptr == ff) return ReturnWithError("[INCTRD] Error opening file", trdname, ff);
  502.         if (!trdHead.readFromFile(ff)) {
  503.                 return ReturnWithError("TRD image read error", trdname, ff);
  504.         }
  505.         fclose(ff);
  506.         ff = nullptr;
  507.  
  508.         // find the requested file
  509.         unsigned fileIndex = 0;
  510.         for (fileIndex = 0; fileIndex < STrdHead::NUM_OF_FILES_MAX; ++fileIndex) {
  511.                 const auto & entry = trdHead.catalog[fileIndex];
  512.                 if (0 == entry.filename[0]) {   // beyond last FAT record, finish the loop
  513.                         fileIndex = STrdHead::NUM_OF_FILES_MAX;
  514.                         break;
  515.                 } else {
  516.                         if (!memcmp(entry.filename, trdFormName, Lname)) break; // found!
  517.                 }
  518.         }
  519.         if (STrdHead::NUM_OF_FILES_MAX == fileIndex) {
  520.                 return ReturnWithError("[INCTRD] File not found in TRD image", filename, ff);
  521.         }
  522.  
  523.         // calculate absolute file offset and length + validate input values
  524.         const auto & entry = trdHead.catalog[fileIndex];
  525.         if (INT_MAX == length) {
  526.                 length = entry.length;
  527.                 length -= offset;
  528.         }
  529.         const aint fileOffset = STrdDisc::fileOffset(entry.startTrack, entry.startSector);
  530.         const aint fileEnd = fileOffset + entry.length;
  531.         offset += fileOffset;
  532.  
  533.         // report success when resulting offset + length fits into the file definition
  534.         if (fileOffset <= offset && (offset + length) <= fileEnd && 0 < length) return 1;
  535.  
  536.         return ReturnWithError("[INCTRD] File too short to cover requested offset and length", bp, ff);
  537. }
  538.