?login_element?

Subversion Repositories NedoOS

Rev

Rev 129 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

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