diff --git a/d64.cpp b/d64.cpp index a1729cd..052e79f 100644 --- a/d64.cpp +++ b/d64.cpp @@ -229,15 +229,16 @@ std::optional d64::findEmptyDirectorySlot() while (dir_track != 0) { if (!validator.visit(dir_track, dir_sector)) return std::nullopt; directorySectorPtr dirSectorPtr = getDirectory_SectorPtr(dir_track, dir_sector); - for (auto& fileEntry : dirSectorPtr->fileEntry) { + for (int i = 0; i < FILES_PER_SECTOR; ++i) { + auto& fileEntry = dirSectorPtr->entry(i); if (!fileEntry.file_type.closed) { return &fileEntry; } } - dir_track = dirSectorPtr->next.track; - dir_sector = dirSectorPtr->next.sector; + dir_track = dirSectorPtr->next().track; + dir_sector = dirSectorPtr->next().sector; - if (dir_track == 0 || dir_track > TRACKS || dir_sector < 0 || dir_sector > SECTORS_PER_TRACK[dir_track - 1]) { + if (dir_track == 0 || dir_track > TRACKS || dir_sector < 0 || dir_sector >= SECTORS_PER_TRACK[dir_track - 1]) { if (!allocateNewDirectorySector(dir_track, dir_sector, dirSectorPtr)) { return std::nullopt; } @@ -251,12 +252,12 @@ bool d64::allocateNewDirectorySector(int& dir_track, int& dir_sector, directoryS if (!findAndAllocateFreeSector(dir_track, dir_sector, true)) { return false; } - dirSectorPtr->next.track = dir_track; - dirSectorPtr->next.sector = dir_sector; + dirSectorPtr->next().track = dir_track; + dirSectorPtr->next().sector = dir_sector; dirSectorPtr = getDirectory_SectorPtr(dir_track, dir_sector); - memset(dirSectorPtr, 0, SECTOR_SIZE); - dirSectorPtr->next.track = 0; - dirSectorPtr->next.sector = 0xFF; + std::fill_n(dirSectorPtr->raw, SECTOR_SIZE, 0); + dirSectorPtr->next().track = 0; + dirSectorPtr->next().sector = 0xFF; return true; } @@ -574,40 +575,44 @@ bool d64::verifyBAMIntegrity(bool fix, const std::string& logFile) auto dir_sector = DIRECTORY_SECTOR; directorySectorPtr dirSectorPtr; + auto markUsed = [&](int track, int sector) { + if (track >= 1 && track <= TRACKS_40 && sector >= 0 && sector < 21) { + sectorUsage[track - 1][sector] = true; + } + }; + sector_chain_validator dir_validator(*this); while (dir_track != 0) { if (!dir_validator.visit(dir_track, dir_sector)) break; dirSectorPtr = getDirectory_SectorPtr(dir_track, dir_sector); - // Mark directory sector as used - sectorUsage[dir_track - 1][dir_sector] = true; + markUsed(dir_track, dir_sector); - for (auto& entry : dirSectorPtr->fileEntry) { + for (int i = 0; i < FILES_PER_SECTOR; ++i) { + auto& entry = dirSectorPtr->entry(i); - if ((entry.file_type.closed) == 0) continue; // Skip deleted files + if ((entry.file_type.closed) == 0) continue; int track = entry.start.track; int sector = entry.start.sector; - sectorUsage[track - 1][sector] = true; + markUsed(track, sector); if (entry.file_type.type == d64FileTypes::REL) { - // get the first location of a side sector trackSector sidePosition = entry.side; - - // load the side sector + if (sidePosition.track == 0) continue; sideSectorPtr side = getSideSectorPtr(sidePosition.track, sidePosition.sector); for (auto side_sectors : side->sideSectors) { if (side_sectors.track == 0) break; side = getSideSectorPtr(side_sectors.track, side_sectors.sector); - sectorUsage[side_sectors.track - 1][side_sectors.sector] = true; + markUsed(side_sectors.track, side_sectors.sector); for (auto chainEntry : side->chain) { if (chainEntry.track == 0) { break; } - sectorUsage[chainEntry.track - 1][chainEntry.sector] = true; + markUsed(chainEntry.track, chainEntry.sector); } } } @@ -615,7 +620,7 @@ bool d64::verifyBAMIntegrity(bool fix, const std::string& logFile) sector_chain_validator file_validator(*this); while (track != 0) { if (!file_validator.visit(track, sector)) break; - sectorUsage[track - 1][sector] = true; + markUsed(track, sector); auto next = getTrackSectorPtr(track, sector); track = next->track; @@ -624,8 +629,8 @@ bool d64::verifyBAMIntegrity(bool fix, const std::string& logFile) } } - dir_track = dirSectorPtr->next.track; - dir_sector = dirSectorPtr->next.sector; + dir_track = dirSectorPtr->next().track; + dir_sector = dirSectorPtr->next().sector; } // **Step 3: Compare BAM against actual usage** @@ -729,93 +734,86 @@ bool d64::reorderDirectory(const std::vector& fileOrder) /// compact directory /// /// true on success -bool d64::compactDirectory() +bool d64::writeDirectoryChain(const std::vector& files) { - std::vector files; - int dir_track = DIRECTORY_TRACK; int dir_sector = DIRECTORY_SECTOR; - directorySectorPtr dirSectorPtr; + size_t index = 0; + sector_chain_validator rewrite(*this); - // **Step 1: Collect all valid directory entries** - sector_chain_validator validator1(*this); - while (dir_track != 0) { - if (!validator1.visit(dir_track, dir_sector)) break; - dirSectorPtr = getDirectory_SectorPtr(dir_track, dir_sector); + while (index < files.size() || dir_track != 0) { + if (dir_track == 0) return false; + if (!rewrite.visit(dir_track, dir_sector)) return false; - for (auto &entry : dirSectorPtr->fileEntry) { - if ((entry.file_type.closed) == 0) - continue; // Skip deleted files + auto dirSectorPtr = getDirectory_SectorPtr(dir_track, dir_sector); + int next_track = dirSectorPtr->next().track; + int next_sector = dirSectorPtr->next().sector; + std::fill_n(dirSectorPtr->raw, SECTOR_SIZE, 0); - files.push_back(entry); + for (int i = 0; i < FILES_PER_SECTOR && index < files.size(); ++i, ++index) { + dirSectorPtr->entry(i) = files[index]; } - dir_track = dirSectorPtr->next.track; - dir_sector = dirSectorPtr->next.sector; + if (index < files.size()) { + if (next_track == 0) { + if (!allocateNewDirectorySector(dir_track, dir_sector, dirSectorPtr)) { + return false; + } + continue; + } + dirSectorPtr->next().track = static_cast(next_track); + dirSectorPtr->next().sector = static_cast(next_sector); + dir_track = next_track; + dir_sector = next_sector; + } else { + dirSectorPtr->next().track = 0; + dirSectorPtr->next().sector = 0xFF; + sector_chain_validator rest(*this); + while (next_track != 0) { + if (!rest.visit(next_track, next_sector)) break; + auto extra = getDirectory_SectorPtr(next_track, next_sector); + int n_t = extra->next().track; + int n_s = extra->next().sector; + if (!(next_track == DIRECTORY_TRACK && next_sector == DIRECTORY_SECTOR)) { + freeSector(next_track, next_sector); + } + next_track = n_t; + next_sector = n_s; + } + break; + } } + return true; +} - if (files.empty()) return false; // No valid files - - // **Step 2: Rewrite the directory with compacted entries** - dir_track = DIRECTORY_TRACK; - dir_sector = DIRECTORY_SECTOR; - dirSectorPtr = getDirectory_SectorPtr(dir_track, dir_sector); +bool d64::compactDirectory() +{ + std::vector files; - size_t index = 0; - bool freedSector = false; + int dir_track = DIRECTORY_TRACK; + int dir_sector = DIRECTORY_SECTOR; + sector_chain_validator collect(*this); - sector_chain_validator validator2(*this); while (dir_track != 0) { - if (!validator2.visit(dir_track, dir_sector)) break; - std::fill_n(reinterpret_cast(dirSectorPtr), SECTOR_SIZE, 0); // Clear sector - - for (auto i = 0; i < FILES_PER_SECTOR && index < files.size(); ++i, ++index) { - dirSectorPtr->fileEntry[i] = files[index]; - } - - // **Step 3: If no more files, free remaining sectors** - if (index >= files.size()) { - // Mark remaining directory sectors as free in BAM - sector_chain_validator validator3(*this); - while (dir_track != 0) { - if (!validator3.visit(dir_track, dir_sector)) break; - int next_track = dirSectorPtr->next.track; - int next_sector = dirSectorPtr->next.sector; - - // never mark track 18 as free - if (dir_track != DIRECTORY_TRACK || dir_sector != DIRECTORY_SECTOR) { - - // Free the sector in BAM - freeSector(dir_track, dir_sector); - freedSector = true; - } - - if (next_track == 0) break; // No more directory sectors + if (!collect.visit(dir_track, dir_sector)) break; + auto dirSectorPtr = getDirectory_SectorPtr(dir_track, dir_sector); - dir_track = next_track; - dir_sector = next_sector; - dirSectorPtr = getDirectory_SectorPtr(dir_track, dir_sector); + for (int i = 0; i < FILES_PER_SECTOR; ++i) { + auto& entry = dirSectorPtr->entry(i); + if (entry.file_type.closed) { + files.push_back(entry); } - break; } - dir_track = dirSectorPtr->next.track; - dir_sector = dirSectorPtr->next.sector; - if (dir_track != 0) dirSectorPtr = getDirectory_SectorPtr(dir_track, dir_sector); + dir_track = dirSectorPtr->next().track; + dir_sector = dirSectorPtr->next().sector; } - if (freedSector) { - std::cerr << "FIXED: Freed unused directory sectors and updated BAM.\n"; - } + if (files.empty()) return false; - return true; + return writeDirectoryChain(files); } -/// -/// find a file on the disk -/// -/// file to find -/// optional pointer to the fiels directory entry std::optional d64::findFile(std::string_view filename) { try { @@ -826,7 +824,8 @@ std::optional d64::findFile(std::string_view filename) while (dir_track != 0) { if (!validator.visit(dir_track, dir_sector)) break; auto dirSectorPtr = getDirectory_SectorPtr(dir_track, dir_sector); - for (auto& fileEntry : dirSectorPtr->fileEntry) { + for (int i = 0; i < FILES_PER_SECTOR; ++i) { + auto& fileEntry = dirSectorPtr->entry(i); if (fileEntry.file_type.closed == 0) { continue; } @@ -836,8 +835,8 @@ std::optional d64::findFile(std::string_view filename) return &fileEntry; } } - dir_track = dirSectorPtr->next.track; - dir_sector = dirSectorPtr->next.sector; + dir_track = dirSectorPtr->next().track; + dir_sector = dirSectorPtr->next().sector; } } catch (const std::out_of_range& e) { @@ -864,18 +863,26 @@ bool d64::removeFile(std::string_view filename) if (!fileEntry.has_value()) { throw std::runtime_error("File not found: " + std::string(filename)); } - int track = fileEntry.value()->start.track; - int sector = fileEntry.value()->start.sector; + auto freeChain = [this](int track, int sector) { + sector_chain_validator validator(*this); + while (track != 0) { + if (!validator.visit(track, sector)) break; + auto sectorPtr = getTrackSectorPtr(track, sector); + auto next_track = sectorPtr->track; + auto next_sector = sectorPtr->sector; + freeSector(track, sector); + track = next_track; + sector = next_sector; + } + }; - sector_chain_validator validator(*this); - while (track != 0) { - if (!validator.visit(track, sector)) break; - auto sectorPtr = getTrackSectorPtr(track, sector); - auto next_track = sectorPtr->track; - auto next_sector = sectorPtr->sector; - freeSector(track, sector); - track = next_track; - sector = next_sector; + freeChain(fileEntry.value()->start.track, fileEntry.value()->start.sector); + if (fileEntry.value()->side.track != 0) { + if (fileEntry.value()->file_type.type == d64FileTypes::REL) { + freeChain(fileEntry.value()->side.track, fileEntry.value()->side.sector); + } else { + freeSector(fileEntry.value()->side.track, fileEntry.value()->side.sector); + } } memset(fileEntry.value(), 0, sizeof(directoryEntry)); @@ -890,12 +897,6 @@ bool d64::removeFile(std::string_view filename) return false; } -/// -/// Rename a file -/// -/// old file name -/// new file name -/// true if successful bool d64::renameFile(std::string_view oldfilename, std::string_view newfilename) { auto fileEntry = findFile(oldfilename); @@ -938,19 +939,28 @@ bool d64::extractFile(std::string filename) auto ext = extMap.at(fileEntry.value()->file_type.type); try { - std::ofstream outFile((filename + ext).c_str(), std::ios::binary); + std::string safeName = filename; + for (char& c : safeName) { + if (c == '/' || c == '\\' || c == ':') { + c = '_'; + } + } + if (safeName.empty() || safeName == "." || safeName == "..") { + safeName = "file"; + } + std::ofstream outFile((safeName + ext).c_str(), std::ios::binary); if (!outFile.is_open()) { - throw std::runtime_error("Failed to open output file: " + filename + ext); + throw std::runtime_error("Failed to open output file: " + safeName + ext); } outFile.write(reinterpret_cast(fileData->data()), fileData->size()); if (outFile.fail()) { - throw std::runtime_error("Failed to write to file: " + filename + ext); + throw std::runtime_error("Failed to write to file: " + safeName + ext); } - + outFile.close(); if (outFile.fail()) { - throw std::runtime_error("Failed to close file: " + filename + ext); + throw std::runtime_error("Failed to close file: " + safeName + ext); } } catch (const std::exception& e) { @@ -981,18 +991,20 @@ std::optional> d64::readFile(std::string filename) int track = fileEntry.value()->start.track; int sector = fileEntry.value()->start.sector; - // track 0 signifies end sector_chain_validator validator(*this); while (track != 0) { if (!validator.visit(track, sector)) break; auto sectorPtr = getSectorPtr(track, sector); - // if the track is not zero then write the whole block - int bytes = sectorPtr->next.track != 0 ? sizeof(sectorPtr->data) : sectorPtr->next.sector - 1; - // append the data at the end + int bytes = sectorPtr->next.track != 0 + ? static_cast(sizeof(sectorPtr->data)) + : static_cast(sectorPtr->next.sector) - 1; + if (bytes < 0) bytes = 0; + if (bytes > static_cast(sizeof(sectorPtr->data))) { + bytes = static_cast(sizeof(sectorPtr->data)); + } fileData.insert(fileData.end(), sectorPtr->data.begin(), sectorPtr->data.begin() + bytes); - // set current track and sector track = sectorPtr->next.track; sector = sectorPtr->next.sector; } @@ -1066,7 +1078,7 @@ bool d64::load(std::string filename) // validate the disk if (!validateD64()) { - formatDisk("NEW DISK"); + return false; } return true; @@ -1126,7 +1138,7 @@ bool d64::freeSector(const int& track, const int& sector) /// true if valid bool d64::isValidTrackSector(int track, int sector) const { - return track >= 1 && track <= TRACKS && sector >= 0 && sector < SECTORS_PER_TRACK[track - 1]; + return track >= 1 && track <= TRACKS && track <= TRACKS_40 && sector >= 0 && sector < SECTORS_PER_TRACK[track - 1]; } /// @@ -1340,29 +1352,9 @@ bool d64::reorderDirectory(std::vector& files) std::vector currentFiles = directory(); if (currentFiles == files) - return false; // No need to rewrite if already in the correct order - - int dir_track = DIRECTORY_TRACK; - int dir_sector = DIRECTORY_SECTOR; - auto dirSectorPtr = getDirectory_SectorPtr(dir_track, dir_sector); - size_t index = 0; - - sector_chain_validator validator(*this); - while (dir_track != 0 && index < files.size()) { - if (!validator.visit(dir_track, dir_sector)) break; - std::fill_n(reinterpret_cast(dirSectorPtr), SECTOR_SIZE, 0); // Clear sector - - auto len = std::min(FILES_PER_SECTOR, static_cast(files.size() - index)); - std::copy_n(files.begin() + index, len, dirSectorPtr->fileEntry); - index += len; + return false; - dir_track = dirSectorPtr->next.track; - dir_sector = dirSectorPtr->next.sector; - if (dir_track != 0) { - dirSectorPtr = getDirectory_SectorPtr(dir_track, dir_sector); - } - } - return true; + return writeDirectoryChain(files); } /// @@ -1396,18 +1388,20 @@ std::vector d64::directory() int dir_track = DIRECTORY_TRACK; int dir_sector = DIRECTORY_SECTOR; - // Read all directory entries sector_chain_validator validator(*this); while (dir_track != 0) { if (!validator.visit(dir_track, dir_sector)) break; auto dirSectorPtr = getDirectory_SectorPtr(dir_track, dir_sector); - std::copy_if(dirSectorPtr->fileEntry, - dirSectorPtr->fileEntry + FILES_PER_SECTOR, - std::back_inserter(files), [&](auto& entry) { return entry.file_type.closed; }); + for (int i = 0; i < FILES_PER_SECTOR; ++i) { + auto& entry = dirSectorPtr->entry(i); + if (entry.file_type.closed) { + files.push_back(entry); + } + } - dir_track = dirSectorPtr->next.track; - dir_sector = dirSectorPtr->next.sector; + dir_track = dirSectorPtr->next().track; + dir_sector = dirSectorPtr->next().sector; } return files; } @@ -1512,7 +1506,9 @@ int d64::getRecordCount(std::string_view filename) { if (dataSector->next.track != 0) { totalPayloadBytes += 254; } else { - totalPayloadBytes += dataSector->next.sector - 1; + int last = static_cast(dataSector->next.sector) - 1; + if (last < 0) last = 0; + totalPayloadBytes += last; break; } } @@ -1542,8 +1538,8 @@ std::optional> d64::readRecord(std::string_view filename, i int sectorIndex = byteOffset / 254; int byteOffsetInSector = (byteOffset % 254) + 2; - int sideSectorIndex = sectorIndex / 120; - int pointerIndex = sectorIndex % 120; + int sideSectorIndex = sectorIndex / SIDE_SECTOR_CHAIN_SZ; + int pointerIndex = sectorIndex % SIDE_SECTOR_CHAIN_SZ; trackSector sidePosition = fileEntry.value()->side; sideSectorPtr side = nullptr; @@ -1597,7 +1593,9 @@ bool d64::expandRelFile(std::string_view filename, int requiredBytes) { trackSector lastDataSectorPos = {0, 0}; int lastChainIndex = -1; + sector_chain_validator validator(*this); while (sidePosition.track != 0) { + if (!validator.visit(sidePosition.track, sidePosition.sector)) break; lastSidePosition = sidePosition; side = getSideSectorPtr(sidePosition.track, sidePosition.sector); for (int i = 0; i < SIDE_SECTOR_CHAIN_SZ; ++i) { @@ -1608,7 +1606,9 @@ bool d64::expandRelFile(std::string_view filename, int requiredBytes) { if (dataSector->next.track != 0) { totalPayloadBytes += 254; } else { - totalPayloadBytes += dataSector->next.sector - 1; + int last = static_cast(dataSector->next.sector) - 1; + if (last < 0) last = 0; + totalPayloadBytes += last; break; } } @@ -1621,7 +1621,8 @@ bool d64::expandRelFile(std::string_view filename, int requiredBytes) { if (lastDataSectorPos.track != 0) { auto lastDataSector = getSectorPtr(lastDataSectorPos.track, lastDataSectorPos.sector); - int currentSectorSize = lastDataSector->next.sector - 1; + int currentSectorSize = static_cast(lastDataSector->next.sector) - 1; + if (currentSectorSize < 0) currentSectorSize = 0; if (currentSectorSize < 254) { int toAdd = std::min(bytesToAdd, 254 - currentSectorSize); std::fill_n(lastDataSector->data.begin() + currentSectorSize, toAdd, 0); @@ -1675,11 +1676,15 @@ bool d64::expandRelFile(std::string_view filename, int requiredBytes) { int sideSectorsCount = newSide->block + 1; if (sideSectorsCount > SIDE_SECTOR_ENTRY_SIZE) return false; + + auto firstSide = getSideSectorPtr(fileEntry.value()->side.track, fileEntry.value()->side.sector); + std::copy(std::begin(firstSide->sideSectors), std::end(firstSide->sideSectors), newSide->sideSectors); + newSide->sideSectors[newSide->block] = {static_cast(newSideTrack), static_cast(newSideSector)}; trackSector iterSidePos = fileEntry.value()->side; - sector_chain_validator validator(*this); + sector_chain_validator sideWalk(*this); while (iterSidePos.track != 0) { - if (!validator.visit(iterSidePos.track, iterSidePos.sector)) break; + if (!sideWalk.visit(iterSidePos.track, iterSidePos.sector)) break; auto iterSide = getSideSectorPtr(iterSidePos.track, iterSidePos.sector); iterSide->sideSectors[newSide->block] = {static_cast(newSideTrack), static_cast(newSideSector)}; iterSidePos = iterSide->next; @@ -1722,8 +1727,8 @@ bool d64::writeRecord(std::string_view filename, int recordNumber, const std::ve int sectorIndex = byteOffset / 254; int byteOffsetInSector = (byteOffset % 254) + 2; - int sideSectorIndex = sectorIndex / 120; - int pointerIndex = sectorIndex % 120; + int sideSectorIndex = sectorIndex / SIDE_SECTOR_CHAIN_SZ; + int pointerIndex = sectorIndex % SIDE_SECTOR_CHAIN_SZ; trackSector sidePosition = fileEntry.value()->side; sideSectorPtr side = nullptr; diff --git a/d64.h b/d64.h index a1f3364..2dc78c4 100644 --- a/d64.h +++ b/d64.h @@ -1,6 +1,7 @@ // Written by Paul Baxter #pragma once #include +#include #include #include #include @@ -104,7 +105,8 @@ class d64 { explicit sector_chain_validator(const d64& d) : disk(d) {} - bool visit(int track, int sector) { + bool visit(int track, int sector) + { if (track == 0) return true; if (!disk.isValidTrackSector(track, sector)) return false; size_t idx = static_cast(disk.calcOffset(track, sector) / SECTOR_SIZE); @@ -137,6 +139,7 @@ class d64 { bool createDirectoryEntry(std::string_view filename, c64FileType type, int start_track, int start_sector, const std::vector& allocatedSectors, uint8_t record_size); bool findAndAllocateFirstSector(int& start_track, int& start_sector); bool allocateNewDirectorySector(int& dir_track, int& dir_sector, directorySectorPtr& dirSectorPtr); + bool writeDirectoryChain(const std::vector& files); bool expandRelFile(std::string_view filename, int requiredBytes); inline void initBAMPtr() diff --git a/d64_types.h b/d64_types.h index 57dec04..bd1da8a 100644 --- a/d64_types.h +++ b/d64_types.h @@ -11,6 +11,7 @@ inline constexpr int FILE_NAME_SZ = 16; inline constexpr int UNUSED3_SZ = 5; inline constexpr int UNUSED4_SZ = 84; inline constexpr int DIR_ENTRY_SZ = 30; +inline constexpr int DIR_SLOT_STRIDE = 32; inline constexpr int DIRECTORY_TRACK = 18; inline constexpr int DIRECTORY_SECTOR = 1; inline constexpr int TRACK_SECTOR = 0; @@ -82,8 +83,8 @@ class c64FileType { public: c64FileType() : closed(0), locked(0), replace(0), unused(0), type(d64FileTypes::DEL) {} - c64FileType(bool a, bool l, d64FileTypes t) : closed(a ? 1 : 0), locked(l ? 1 : 0), unused(0), type(t) {} - c64FileType(d64FileTypes t) : closed(1), locked(0), unused(0), type(t) {} + c64FileType(bool a, bool l, d64FileTypes t) : closed(a ? 1 : 0), locked(l ? 1 : 0), replace(0), unused(0), type(t) {} + c64FileType(d64FileTypes t) : closed(1), locked(0), replace(0), unused(0), type(t) {} c64FileType(uint8_t value) : closed(value & 0x80), locked(value & 0x40), @@ -109,6 +110,7 @@ struct bamTrackEntry { /// sector to test bool test(int sector) { + if (sector < 0 || sector >= 24) return false; auto byte = sector / 8; auto bit = sector % 8; @@ -122,6 +124,7 @@ struct bamTrackEntry { /// sector to mark inline void set(int sector) { + if (sector < 0 || sector >= 24) return; auto byte = sector / 8; auto bit = sector % 8; @@ -136,6 +139,7 @@ struct bamTrackEntry { /// sector to mark inline void reset(int sector) { + if (sector < 0 || sector >= 24) return; auto byte = sector / 8; auto bit = sector % 8; @@ -181,7 +185,6 @@ struct directoryEntry { uint8_t unused[4]; // $16 - $19 unused trackSector replace; // $1A - $1B track / sector of replacement file during @save uint8_t fileSize[2]; // $1C - $1D low byte high byte for file size - uint8_t padd[2]; // $1E - $1F undocumented padd bool operator==(const directoryEntry& other) const { @@ -205,9 +208,23 @@ struct directoryEntry { typedef struct directoryEntry* directoryEntryPtr; struct directorySector { - trackSector next; - directoryEntry fileEntry[FILES_PER_SECTOR]; + uint8_t raw[SECTOR_SIZE]; + + trackSector& next() { return *reinterpret_cast(raw); } + const trackSector& next() const { return *reinterpret_cast(raw); } + + directoryEntry& entry(int i) + { + return *reinterpret_cast(raw + 2 + i * DIR_SLOT_STRIDE); + } + const directoryEntry& entry(int i) const + { + return *reinterpret_cast(raw + 2 + i * DIR_SLOT_STRIDE); + } }; typedef struct directorySector* directorySectorPtr; +static_assert(sizeof(directoryEntry) == DIR_ENTRY_SZ); +static_assert(sizeof(directorySector) == SECTOR_SIZE); + #pragma pack(pop) diff --git a/geos.cpp b/geos.cpp index 298a0e1..7a80098 100644 --- a/geos.cpp +++ b/geos.cpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace d64lib::geos { @@ -38,6 +39,7 @@ bool isGeosDisk(d64& disk) { /// name of the the disk /// true on success bool formatGeosDisk(d64& disk, std::string_view name) { + if (disk.TRACKS != TRACKS_35) return false; disk.formatDisk(name); auto bamSector = disk.readSector(DIRECTORY_TRACK, 0); @@ -74,12 +76,11 @@ std::optional readInfoBlock(d64& disk, std::string_view filename) { InfoBlock info; info.iconWidth = sector[0x02]; - info.iconHeight = sector[0x03]; - - int iconSizeInBytes = info.iconWidth * 8 * info.iconHeight; + info.iconHeight = 21; info.iconData.clear(); - if (iconSizeInBytes > 0 && 0x05 + iconSizeInBytes <= 0x44) { - info.iconData.assign(sector.begin() + 0x05, sector.begin() + 0x05 + iconSizeInBytes); + int iconSizeInBytes = static_cast(info.iconWidth) * info.iconHeight; + if (iconSizeInBytes > 0 && 0x03 + iconSizeInBytes <= 0x44) { + info.iconData.assign(sector.begin() + 0x03, sector.begin() + 0x03 + iconSizeInBytes); } info.dosType = sector[0x44]; @@ -117,7 +118,7 @@ std::optional> readSequentialFile(d64& disk, std::string_vi /// 0-based record index (up to 127) /// Optional byte array of the record payload std::optional> readVlirRecord(d64& disk, std::string_view filename, int recordId) { - if (recordId < 0 || recordId > 127) return std::nullopt; + if (recordId < 0 || recordId > 126) return std::nullopt; auto fileEntry = disk.findFile(filename); if (!fileEntry.has_value()) return std::nullopt; @@ -142,8 +143,13 @@ std::optional> readVlirRecord(d64& disk, std::string_view f std::vector result; uint8_t currentTrack = recordTrack; uint8_t currentSector = recordSector; + std::bitset visited; while (currentTrack != 0x00) { + if (currentTrack < 1 || currentTrack > TRACKS_40 || currentSector >= 21) break; + size_t idx = static_cast(currentTrack - 1) * 21 + currentSector; + if (visited.test(idx)) break; + visited.set(idx); auto sectorOpt = disk.readSector(currentTrack, currentSector); if (!sectorOpt.has_value()) break; diff --git a/geos.h b/geos.h index 2cb15b1..0eaf2b2 100644 --- a/geos.h +++ b/geos.h @@ -81,7 +81,7 @@ std::optional readInfoBlock(d64& disk, std::string_view filename); /// /// d64 disk instance /// name of the file -/// 0-based record index (up to 127) +/// 0-based record index (0-126) /// Optional byte array of the record payload std::optional> readVlirRecord(d64& disk, std::string_view filename, int recordId); diff --git a/unittests/d64unittests.cpp b/unittests/d64unittests.cpp index 008ac0d..d35a307 100644 --- a/unittests/d64unittests.cpp +++ b/unittests/d64unittests.cpp @@ -1,6 +1,8 @@ // Written by Paul Baxter #include #include +#include +#include #include "d64.h" @@ -568,9 +570,21 @@ namespace d64lib_unit_test d64lib_unit_test_method_initialize(); d64 disk; - std::vector files; - bool result = disk.reorderDirectory(files); - // Add assertions here + std::vector payload(10, 0xAA); + for (int i = 0; i < 9; ++i) { + std::string name = "FILE" + std::to_string(i); + ASSERT_TRUE(disk.addFile(name, d64FileTypes::PRG, payload)); + } + + auto files = disk.directory(); + ASSERT_EQ(files.size(), 9u); + std::reverse(files.begin(), files.end()); + EXPECT_TRUE(disk.reorderDirectory(files)); + + auto after = disk.directory(); + ASSERT_EQ(after.size(), 9u); + EXPECT_EQ(d64::Trim(after[0].fileName), "FILE8"); + EXPECT_EQ(d64::Trim(after[8].fileName), "FILE0"); d64lib_unit_test_method_cleanup(disk); } @@ -766,8 +780,8 @@ namespace d64lib_unit_test d64 loadedDisk; bool loaded = loadedDisk.load(filename); - EXPECT_TRUE(loaded); - EXPECT_EQ(loadedDisk.diskname(), "NEW DISK"); + EXPECT_FALSE(loaded); + EXPECT_NE(loadedDisk.diskname(), "NEW DISK"); std::remove(filename.c_str()); d64lib_unit_test_method_cleanup(disk); @@ -781,13 +795,13 @@ namespace d64lib_unit_test disk.addFile("CYCLIC", d64FileTypes::PRG, data); auto entry = disk.findFile("CYCLIC"); - EXPECT_TRUE(entry.has_value()); + ASSERT_TRUE(entry.has_value()); int track = entry.value()->start.track; int sector = entry.value()->start.sector; - disk.writeByte(track, sector, 0, track); - disk.writeByte(track, sector, 1, sector); + disk.writeByte(track, sector, 0, static_cast(track)); + disk.writeByte(track, sector, 1, static_cast(sector)); auto readData = disk.readFile("CYCLIC"); EXPECT_TRUE(readData.has_value()); @@ -795,5 +809,24 @@ namespace d64lib_unit_test d64lib_unit_test_method_cleanup(disk); } + TEST(d64lib_unit_test, directory_sector_size_test) + { + EXPECT_EQ(sizeof(directoryEntry), static_cast(DIR_ENTRY_SZ)); + EXPECT_EQ(sizeof(directorySector), static_cast(SECTOR_SIZE)); + } + + TEST(d64lib_unit_test, compactDirectory_multi_sector_test) + { + d64lib_unit_test_method_initialize(); + d64 disk; + std::vector payload(10, 0xBB); + for (int i = 0; i < 10; ++i) { + ASSERT_TRUE(disk.addFile("C" + std::to_string(i), d64FileTypes::PRG, payload)); + } + EXPECT_TRUE(disk.compactDirectory()); + EXPECT_EQ(disk.directory().size(), 10u); + d64lib_unit_test_method_cleanup(disk); + } + } diff --git a/unittests/geosunittests.cpp b/unittests/geosunittests.cpp index d4250af..fbc4359 100644 --- a/unittests/geosunittests.cpp +++ b/unittests/geosunittests.cpp @@ -73,13 +73,8 @@ namespace { // Now craft the info block std::vector infoBlock(256, 0); - infoBlock[0x02] = 2; // icon width (2 bytes) - infoBlock[0x03] = 4; // icon height (4 rows) - // Icon data 0x05 to 0x05 + 63. But we only need a few bytes. - // Wait, width is in units of 8 pixels (1 byte). - // 2 bytes wide * 8 rows high = 16 bytes. Let's make height 8. - infoBlock[0x03] = 3; // 2 * 8 * 3 = 48 bytes - for(int i=0; i<48; i++) infoBlock[0x05 + i] = 0xAA; + infoBlock[0x02] = 3; + for (int i = 0; i < 63; i++) infoBlock[0x03 + i] = 0xAA; infoBlock[0x44] = 0x82; // PRG DOS Type infoBlock[0x45] = 0x06; // Application infoBlock[0x46] = 0x01; // VLIR @@ -103,7 +98,9 @@ namespace { EXPECT_EQ(info.value().loadAddress, 0x0400); EXPECT_EQ(info.value().author, "GEOS DEV"); EXPECT_EQ(info.value().className, "TEST CLASS"); - EXPECT_EQ(info.value().iconData.size(), 48); + EXPECT_EQ(info.value().iconWidth, 3); + EXPECT_EQ(info.value().iconHeight, 21); + EXPECT_EQ(info.value().iconData.size(), 63u); EXPECT_EQ(info.value().iconData[0], 0xAA); d64lib_unit_test_method_cleanup(disk);