29using std::string_view;
41 static constexpr unsigned FREE = 0x000;
42 static constexpr unsigned FIRST_CLUSTER = 0x002;
46 static constexpr unsigned BAD = 0xFF7;
47 static constexpr unsigned END_OF_CHAIN = 0xFFF;
49 static constexpr unsigned MAX_CLUSTER_COUNT = 0xFF4;
60 static constexpr unsigned BAD = 0xFFF7;
61 static constexpr unsigned END_OF_CHAIN = 0xFFFF;
63 static constexpr unsigned MAX_CLUSTER_COUNT = 0xFFF4;
73static constexpr unsigned SECTOR_SIZE =
sizeof(SectorBuffer);
74static constexpr unsigned DIR_ENTRIES_PER_SECTOR = SECTOR_SIZE /
sizeof(MSXDirEntry);
76static constexpr uint8_t EBPB_SIGNATURE = 0x29;
78static constexpr uint8_t T_MSX_REG = 0x00;
79static constexpr uint8_t T_MSX_READ = 0x01;
80static constexpr uint8_t T_MSX_HID = 0x02;
81static constexpr uint8_t T_MSX_SYS = 0x04;
82static constexpr uint8_t T_MSX_VOL = 0x08;
83static constexpr uint8_t T_MSX_DIR = 0x10;
84static constexpr uint8_t T_MSX_ARC = 0x20;
88static constexpr uint8_t T_MSX_LFN = 0x0F;
93unsigned MSXtar::clusterToSector(Cluster cluster)
const
95 return dataStart + sectorsPerCluster * cluster.index;
101Cluster MSXtar::sectorToCluster(
unsigned sector)
const
105 assert(sector >= dataStart);
106 return Cluster{(sector - dataStart) / sectorsPerCluster};
112void MSXtar::parseBootSector(
const MSXBootSector& boot)
114 fatCount = boot.nrFats;
115 sectorsPerFat = boot.sectorsFat;
116 sectorsPerCluster = boot.spCluster;
118 unsigned nrSectors = boot.nrSectors;
119 if (nrSectors == 0 && boot.params.extended.extendedBootSignature == EBPB_SIGNATURE) {
120 nrSectors = boot.params.extended.nrSectors;
123 if (boot.bpSector != SECTOR_SIZE) {
124 throw MSXException(
"Illegal sector size: ", boot.bpSector);
126 if (boot.resvSectors == 0) {
127 throw MSXException(
"Illegal number of reserved sectors: ", boot.resvSectors);
130 throw MSXException(
"Illegal number of FATs: ", fatCount);
132 if (sectorsPerFat == 0 || sectorsPerFat > 0x100) {
133 throw MSXException(
"Illegal number of sectors per FAT: ", sectorsPerFat);
135 if (boot.dirEntries == 0 || boot.dirEntries % DIR_ENTRIES_PER_SECTOR != 0) {
136 throw MSXException(
"Illegal number of root directory entries: ", boot.dirEntries);
138 if (!std::has_single_bit(sectorsPerCluster)) {
139 throw MSXException(
"Illegal number of sectors per cluster: ", sectorsPerCluster);
142 unsigned nbRootDirSectors = boot.dirEntries / DIR_ENTRIES_PER_SECTOR;
143 fatStart = boot.resvSectors;
144 rootDirStart = fatStart + fatCount * sectorsPerFat;
145 chrootSector = rootDirStart;
146 dataStart = rootDirStart + nbRootDirSectors;
153 constexpr unsigned fat16Threshold = (0x1000 * 3 / 2 + 0x1000 * 2) / 2 / SECTOR_SIZE;
154 fat16 = sectorsPerFat >= fat16Threshold;
156 if (dataStart + sectorsPerCluster > nrSectors) {
157 throw MSXException(
"Illegal number of sectors: ", nrSectors);
160 clusterCount =
std::min((nrSectors - dataStart) / sectorsPerCluster,
161 fat16 ? FAT16::MAX_CLUSTER_COUNT : FAT12::MAX_CLUSTER_COUNT);
166 unsigned fatCapacity = (2 * SECTOR_SIZE * sectorsPerFat) / 3 - FAT::FIRST_CLUSTER;
167 clusterCount =
std::min(clusterCount, fatCapacity);
170void MSXtar::writeLogicalSector(
unsigned sector,
const SectorBuffer& buf)
172 assert(!fatBuffer.empty());
173 unsigned fatSector = sector - fatStart;
174 if (fatSector < sectorsPerFat) {
177 fatBuffer[fatSector] = buf;
178 fatCacheDirty =
true;
184void MSXtar::readLogicalSector(
unsigned sector, SectorBuffer& buf)
186 assert(!fatBuffer.empty());
187 unsigned fatSector = sector - fatStart;
188 if (fatSector < sectorsPerFat) {
191 buf = fatBuffer[fatSector];
199 , msxChars(msxChars_)
200 , findFirstFreeClusterStart{0}
214 fatCacheDirty =
false;
215 fatBuffer.resize(sectorsPerFat);
216 disk.
readSectors(std::span{fatBuffer.data(), sectorsPerFat}, fatStart);
222 , fatBuffer(std::move(other.fatBuffer))
223 , msxChars(other.msxChars)
224 , findFirstFreeClusterStart(other.findFirstFreeClusterStart)
225 , clusterCount(other.clusterCount)
226 , fatCount(other.fatCount)
227 , sectorsPerCluster(other.sectorsPerCluster)
228 , sectorsPerFat(other.sectorsPerFat)
229 , fatStart(other.fatStart)
230 , rootDirStart(other.rootDirStart)
231 , dataStart(other.dataStart)
232 , chrootSector(other.chrootSector)
233 , fatCacheDirty(other.fatCacheDirty)
235 other.fatCacheDirty =
false;
240 if (!fatCacheDirty)
return;
242 for (
auto fat :
xrange(fatCount)) {
243 for (
auto i :
xrange(sectorsPerFat)) {
245 disk.
writeSector(i + fatStart + fat * sectorsPerFat, fatBuffer[i]);
256 assert(!fatBuffer.empty());
257 assert(cluster.
index < clusterCount);
259 std::span<const uint8_t> data{fatBuffer[0].raw.data(), sectorsPerFat * size_t(SECTOR_SIZE)};
261 unsigned index = FAT::FIRST_CLUSTER + cluster.
index;
262 unsigned value = [&] {
264 auto p = subspan<2>(data, index * 2);
265 return p[0] | p[1] << 8;
267 auto p = subspan<2>(data, (index * 3) / 2);
269 ? (p[0] >> 4) + (p[1] << 4)
270 : p[0] + ((p[1] & 0x0F) << 8);
277 if (value == FAT::FREE) {
279 }
else if (value >= FAT::FIRST_CLUSTER && value < FAT::FIRST_CLUSTER + clusterCount) {
280 return Cluster{value - FAT::FIRST_CLUSTER};
287void MSXtar::writeFAT(Cluster cluster,
FatCluster value)
289 assert(!fatBuffer.empty());
290 assert(cluster.index < clusterCount);
294 assert(!std::holds_alternative<Cluster>(value) || std::get<Cluster>(value).index < clusterCount);
296 std::span data{fatBuffer[0].raw.data(), sectorsPerFat * size_t(SECTOR_SIZE)};
298 unsigned index = FAT::FIRST_CLUSTER + cluster.index;
300 if (std::holds_alternative<Free>(value) && cluster < findFirstFreeClusterStart) {
302 findFirstFreeClusterStart = cluster;
307 auto p = subspan<2>(data, index * 2);
308 p[0] = narrow_cast<uint8_t>(fatValue);
309 p[1] = narrow_cast<uint8_t>(fatValue >> 8);
312 auto p = subspan<2>(data, (index * 3) / 2);
314 p[0] = narrow_cast<uint8_t>((p[0] & 0x0F) + (fatValue << 4));
315 p[1] = narrow_cast<uint8_t>(fatValue >> 4);
317 p[0] = narrow_cast<uint8_t>(fatValue);
318 p[1] = narrow_cast<uint8_t>((p[1] & 0xF0) + ((fatValue >> 8) & 0x0F));
321 fatCacheDirty =
true;
326Cluster MSXtar::findFirstFreeCluster()
328 for (
auto cluster :
xrange(findFirstFreeClusterStart.
index, clusterCount)) {
329 if (readFAT({cluster}) ==
FatCluster(Free{})) {
330 findFirstFreeClusterStart = {cluster};
331 return findFirstFreeClusterStart;
334 throw MSXException(
"Disk full.");
339unsigned MSXtar::getNextSector(
unsigned sector)
341 assert(sector >= rootDirStart);
342 if (sector < dataStart) {
344 return (sector == dataStart - 1) ? 0 : sector + 1;
346 Cluster currCluster = sectorToCluster(sector);
347 if (currCluster == sectorToCluster(sector + 1)) {
352 FatCluster nextCluster = readFAT(currCluster);
354 [](Free) {
return 0u; },
355 [](EndOfChain) {
return 0u; },
356 [
this](Cluster cluster) {
return clusterToSector(cluster); }
362DirCluster MSXtar::getStartCluster(
const MSXDirEntry& entry)
const
366 unsigned cluster = entry.startCluster;
367 if (cluster >= FAT::FIRST_CLUSTER && cluster < FAT::FIRST_CLUSTER + clusterCount) {
368 return Cluster{cluster - FAT::FIRST_CLUSTER};
375void MSXtar::setStartCluster(MSXDirEntry& entry,
DirCluster cluster)
const
379 assert(!std::holds_alternative<Cluster>(cluster) || std::get<Cluster>(cluster).index < clusterCount);
392unsigned MSXtar::appendClusterToSubdir(
unsigned sector)
394 Cluster nextCl = findFirstFreeCluster();
395 unsigned nextSector = clusterToSector(nextCl);
400 for (
auto i :
xrange(sectorsPerCluster)) {
401 writeLogicalSector(i + nextSector, buf);
404 Cluster curCl = sectorToCluster(sector);
405 assert(readFAT(curCl) ==
FatCluster(EndOfChain{}));
406 writeFAT(curCl, nextCl);
407 writeFAT(nextCl, EndOfChain{});
415unsigned MSXtar::findUsableIndexInSector(
unsigned sector)
418 readLogicalSector(sector, buf);
421 for (
auto i :
xrange(DIR_ENTRIES_PER_SECTOR)) {
422 if (buf.dirEntry[i].filename[0] ==
one_of(0x00,
char(0xE5))) {
433MSXtar::DirEntry MSXtar::addEntryToDir(
unsigned sector)
438 result.sector = sector;
440 assert(sector >= rootDirStart);
441 if (sector < dataStart) {
443 for ( ; result.sector < dataStart; result.sector++) {
444 result.index = findUsableIndexInSector(result.sector);
445 if (result.index !=
unsigned(-1)) {
449 throw MSXException(
"Root directory full.");
454 result.index = findUsableIndexInSector(result.sector);
455 if (result.index !=
unsigned(-1)) {
458 unsigned nextSector = getNextSector(result.sector);
459 if (nextSector == 0) {
460 nextSector = appendClusterToSubdir(result.sector);
462 result.sector = nextSector;
468static char toFileNameChar(
char a)
470 if ((a >= 0x00 && a < 0x20) || a ==
one_of(
471 ' ',
'"',
'*',
'+',
',',
'.',
'/',
':',
';',
'<',
'=',
'>',
'?',
'[',
'\\',
']',
'|', 0x7F, 0xFF)) {
474 return narrow<char>(toupper(a));
479FileName MSXtar::hostToMSXFileName(string_view hostName)
const
481 std::vector<uint8_t> hostMSXName = msxChars.
utf8ToMsx(hostName,
'_');
482 std::string_view hostMSXNameView(
reinterpret_cast<char*
>(hostMSXName.data()), hostMSXName.size());
488 if (hostFile ==
one_of(
".",
"..")) {
500 string fileS(file.data(), std::min<size_t>(8, file.size()));
501 string extS (ext .data(), std::min<size_t>(3, ext .
size()));
516unsigned MSXtar::addSubdir(
517 const FileName& msxName, uint16_t
t, uint16_t d,
unsigned sector)
520 DirEntry result = addEntryToDir(sector);
524 readLogicalSector(result.sector, buf);
526 auto& dirEntry = buf.dirEntry[result.index];
528 dirEntry.attrib = T_MSX_DIR;
533 Cluster curCl = findFirstFreeCluster();
534 setStartCluster(dirEntry, curCl);
535 writeFAT(curCl, EndOfChain{});
538 writeLogicalSector(result.sector, buf);
541 unsigned logicalSector = clusterToSector(curCl);
543 for (
auto i :
xrange(sectorsPerCluster)) {
544 writeLogicalSector(i + logicalSector, buf);
548 memset(&buf.dirEntry[0], 0,
sizeof(MSXDirEntry));
550 buf.dirEntry[0].filename[0] =
'.';
551 buf.dirEntry[0].attrib = T_MSX_DIR;
552 buf.dirEntry[0].time =
t;
553 buf.dirEntry[0].date = d;
554 setStartCluster(buf.dirEntry[0], curCl);
556 memset(&buf.dirEntry[1], 0,
sizeof(MSXDirEntry));
558 buf.dirEntry[1].filename[0] =
'.';
559 buf.dirEntry[1].filename[1] =
'.';
560 buf.dirEntry[1].attrib = T_MSX_DIR;
561 buf.dirEntry[1].time =
t;
562 buf.dirEntry[1].date = d;
563 if (sector == rootDirStart) {
564 setStartCluster(buf.dirEntry[1], Free{});
566 setStartCluster(buf.dirEntry[1], sectorToCluster(sector));
570 writeLogicalSector(logicalSector, buf);
572 return logicalSector;
578static TimeDate getTimeDate(time_t totalSeconds)
580 if (tm* mtim = localtime(&totalSeconds)) {
581 auto time = narrow<uint16_t>(
582 (
std::min(mtim->tm_sec, 59) >> 1) + (mtim->tm_min << 5) +
583 (mtim->tm_hour << 11));
584 auto date = narrow<uint16_t>(
585 mtim->tm_mday + ((mtim->tm_mon + 1) << 5) +
586 (
std::clamp(mtim->tm_year + 1900 - 1980, 0, 119) << 9));
600 return getTimeDate(
reinterpret_cast<time_t&
>(st->st_mtime));
612 auto [time, date] = getTimeDate(hostName);
613 return addSubdir(msxName, time, date, sector);
620void MSXtar::alterFileInDSK(MSXDirEntry& msxDirEntry,
const string& hostName)
625 throw MSXException(
"Error reading host file: ", hostName);
627 unsigned hostSize = narrow<unsigned>(st->st_size);
628 unsigned remaining = hostSize;
631 File file(hostName,
"rb");
636 [](Free) ->
FatCluster {
return EndOfChain{}; },
637 [](Cluster cluster) ->
FatCluster {
return cluster; }
638 }, getStartCluster(msxDirEntry));
645 [](Free) -> Cluster {
throw new MSXException(
"Invalid entry in FAT chain."); },
647 Cluster newCl = findFirstFreeCluster();
649 [&](Free) { setStartCluster(msxDirEntry, newCl); },
650 [&](Cluster cluster_) { writeFAT(cluster_, newCl); }
652 writeFAT(newCl, EndOfChain{});
655 [](Cluster cluster_) {
return cluster_; }
657 }
catch (MSXException&) {
663 unsigned logicalSector = clusterToSector(cluster);
664 for (
unsigned j = 0; (j < sectorsPerCluster) && remaining; ++j) {
666 unsigned chunkSize =
std::min(SECTOR_SIZE, remaining);
667 file.read(
subspan(buf.raw, 0, chunkSize));
669 writeLogicalSector(logicalSector + j, buf);
670 remaining -= chunkSize;
675 curCl = readFAT(cluster);
680 [&](Free free) { setStartCluster(msxDirEntry, free); },
681 [&](Cluster cluster) { writeFAT(cluster, EndOfChain{}); }
685 while (std::holds_alternative<Cluster>(curCl)) {
686 Cluster cluster = std::get<Cluster>(curCl);
688 writeFAT(cluster, Free{});
693 msxDirEntry.size = hostSize - remaining;
696 throw MSXException(
"Disk full, ", hostName,
" truncated.");
704MSXtar::DirEntry MSXtar::findEntryInDir(
705 const FileName& msxName,
unsigned sector, SectorBuffer& buf)
708 result.sector = sector;
710 while (result.sector) {
712 readLogicalSector(result.sector, buf);
713 for (result.index = 0; result.index < DIR_ENTRIES_PER_SECTOR; ++result.index) {
714 if (
ranges::equal(buf.dirEntry[result.index].filename, msxName)) {
719 result.sector = getNextSector(result.sector);
726string MSXtar::addFileToDSK(
const string& fullHostName,
unsigned rootSector)
729 FileName msxName = hostToMSXFileName(hostName);
733 DirEntry fullMsxDirEntry = findEntryInDir(msxName, rootSector, dummy);
734 if (fullMsxDirEntry.sector != 0) {
736 return strCat(
"Warning: preserving entry ", hostName,
'\n');
740 DirEntry entry = addEntryToDir(rootSector);
741 readLogicalSector(entry.sector, buf);
742 auto& dirEntry = buf.dirEntry[entry.index];
743 memset(&dirEntry, 0,
sizeof(dirEntry));
745 dirEntry.attrib = T_MSX_REG;
748 auto [time, date] = getTimeDate(fullHostName);
749 dirEntry.time = time;
750 dirEntry.date = date;
753 alterFileInDSK(dirEntry, fullHostName);
754 }
catch (MSXException&) {
756 writeLogicalSector(entry.sector, buf);
759 writeLogicalSector(entry.sector, buf);
765string MSXtar::recurseDirFill(string_view dirName,
unsigned sector)
769 auto fileAction = [&](
const string& path) {
771 messages += addFileToDSK(path, sector);
773 auto dirAction = [&](
const string& path, std::string_view name) {
774 FileName msxFileName = hostToMSXFileName(name);
776 DirEntry entry = findEntryInDir(msxFileName, sector, buf);
777 if (entry.sector != 0) {
779 auto& msxDirEntry = buf.dirEntry[entry.index];
780 if (msxDirEntry.attrib & T_MSX_DIR) {
782 DirCluster nextCluster = getStartCluster(msxDirEntry);
784 [&](Free) {
return strCat(
"Directory ", name,
" goes to root.\n"); },
785 [&](Cluster cluster) {
return recurseDirFill(path, clusterToSector(cluster)); }
789 strAppend(messages,
"MSX file ", name,
" is not a directory.\n");
793 unsigned nextSector = addSubdirToDSK(path, msxFileName, sector);
794 messages += recurseDirFill(path, nextSector);
803string MSXtar::msxToHostFileName(
const FileName& msxName)
const
806 for (
unsigned i = 0; i < 8 && msxName[i] !=
' '; ++i) {
807 result += char(tolower(msxName[i]));
809 if (msxName[8] !=
' ') {
811 for (
unsigned i = 8; i < 11 && msxName[i] !=
' '; ++i) {
812 result += char(tolower(msxName[i]));
815 std::span<const uint8_t> resultSpan(
reinterpret_cast<const uint8_t*
>(result.data()), result.size());
816 return msxChars.
msxToUtf8(resultSpan,
'_');
821static void changeTime(
zstring_view resultFile,
const MSXDirEntry& dirEntry)
823 unsigned t = dirEntry.time;
824 unsigned d = dirEntry.date;
827 mTim.tm_sec = narrow<int>((
t & 0x001f) << 1);
828 mTim.tm_min = narrow<int>((
t & 0x07e0) >> 5);
829 mTim.tm_hour = narrow<int>((
t & 0xf800) >> 11);
830 mTim.tm_mday = narrow<int>( (d & 0x001f));
831 mTim.tm_mon = narrow<int>(((d & 0x01e0) >> 5) - 1);
832 mTim.tm_year = narrow<int>(((d & 0xfe00) >> 9) + 80);
834 uTim.actime = mktime(&mTim);
835 uTim.modtime = mktime(&mTim);
836 utime(resultFile.
c_str(), &uTim);
842 for (
unsigned sector = chrootSector; sector != 0; sector = getNextSector(sector)) {
844 readLogicalSector(sector, buf);
845 for (
auto& dirEntry : buf.
dirEntry) {
846 if (dirEntry.filename[0] ==
char(0x00)) {
849 if (dirEntry.filename[0] ==
char(0xe5) || dirEntry.attrib == T_MSX_LFN) {
854 string hostName = msxToHostFileName(dirEntry.filename);
855 hostName.resize(13,
' ');
858 (dirEntry.attrib & T_MSX_DIR ?
'd' :
'-'),
859 (dirEntry.attrib & T_MSX_READ ?
'r' :
'-'),
860 (dirEntry.attrib & T_MSX_HID ?
'h' :
'-'),
861 (dirEntry.attrib & T_MSX_VOL ?
'v' :
'-'),
862 (dirEntry.attrib & T_MSX_ARC ?
'a' :
'-'),
865 dirEntry.size,
'\n');
874 chroot(newRootDir,
false);
879 unsigned tmpMSXchrootSector = chrootSector;
880 chroot(newRootDir,
true);
881 chrootSector = tmpMSXchrootSector;
884void MSXtar::chroot(string_view newRootDir,
bool createDir)
886 if (newRootDir.starts_with(
'/') || newRootDir.starts_with(
'\\')) {
888 chrootSector = rootDirStart;
892 while (!newRootDir.empty()) {
894 newRootDir = lastPart;
899 FileName msxName = hostToMSXFileName(firstPart);
900 DirEntry entry = findEntryInDir(msxName, chrootSector, buf);
901 if (entry.sector == 0) {
903 throw MSXException(
"Subdirectory ", firstPart,
909 auto [
t, d] = getTimeDate(now);
910 chrootSector = addSubdir(msxName,
t, d, chrootSector);
912 auto& dirEntry = buf.dirEntry[entry.index];
913 if (!(dirEntry.attrib & T_MSX_DIR)) {
914 throw MSXException(firstPart,
" is not a directory.");
916 DirCluster chrootCluster = getStartCluster(dirEntry);
918 [
this](Free) {
return rootDirStart; },
919 [
this](Cluster cluster) {
return clusterToSector(cluster); }
925void MSXtar::fileExtract(
const string& resultFile,
const MSXDirEntry& dirEntry)
927 unsigned size = dirEntry.size;
929 [](Free) {
return 0u; },
930 [
this](Cluster cluster) {
return clusterToSector(cluster); }
931 }, getStartCluster(dirEntry));
933 File file(resultFile,
"wb");
934 while (
size && sector) {
936 readLogicalSector(sector, buf);
938 file.write(
subspan(buf.raw, 0, saveSize));
940 sector = getNextSector(sector);
943 changeTime(resultFile, dirEntry);
947string MSXtar::singleItemExtract(string_view dirName, string_view itemName,
952 FileName msxName = hostToMSXFileName(itemName);
953 DirEntry entry = findEntryInDir(msxName, sector, buf);
954 if (entry.sector == 0) {
955 return strCat(itemName,
" not found!\n");
958 auto& msxDirEntry = buf.dirEntry[entry.index];
960 string fullName =
strCat(dirName,
'/', msxToHostFileName(msxDirEntry.filename));
963 if (msxDirEntry.attrib & T_MSX_DIR) {
966 DirCluster nextCluster = getStartCluster(msxDirEntry);
969 [&](Cluster cluster) { recurseDirExtract(fullName, clusterToSector(cluster)); }
973 fileExtract(fullName, msxDirEntry);
980void MSXtar::recurseDirExtract(string_view dirName,
unsigned sector)
982 for ( ; sector != 0; sector = getNextSector(sector)) {
984 readLogicalSector(sector, buf);
985 for (
auto& dirEntry : buf.dirEntry) {
986 if (dirEntry.filename[0] ==
char(0x00)) {
989 if (dirEntry.filename[0] ==
one_of(
char(0xe5),
'.') || dirEntry.attrib == T_MSX_LFN) {
992 string filename = msxToHostFileName(dirEntry.filename);
993 string fullName = filename;
994 if (!dirName.empty()) {
995 fullName =
strCat(dirName,
'/', filename);
997 if (dirEntry.attrib & T_MSX_DIR) {
1000 changeTime(fullName, dirEntry);
1003 [&](Cluster cluster) { recurseDirExtract(fullName, clusterToSector(cluster)); }
1004 }, getStartCluster(dirEntry));
1006 fileExtract(fullName, dirEntry);
1014 return recurseDirFill(rootDirName, chrootSector);
1019 return addFileToDSK(filename, chrootSector);
1024 return singleItemExtract(rootDirName, itemName, chrootSector);
1029 recurseDirExtract(rootDirName, chrootSector);
std::string getItemFromDir(std::string_view rootDirName, std::string_view itemName)
void getDir(std::string_view rootDirName)
std::string addFile(const std::string &filename)
void chdir(std::string_view newRootDir)
void mkdir(std::string_view newRootDir)
MSXtar(SectorAccessibleDisk &disk, const MsxChar2Unicode &msxChars_)
std::string addDir(std::string_view rootDirName)
std::vector< uint8_t > utf8ToMsx(std::string_view utf8, const std::function< uint8_t(uint32_t)> &fallback) const
TODO.
std::string msxToUtf8(std::span< const uint8_t > msx, const std::function< uint32_t(uint8_t)> &fallback) const
TODO.
void readSector(size_t sector, SectorBuffer &buf)
void readSectors(std::span< SectorBuffer > buffers, size_t startSector)
size_t getNbSectors() const
void writeSector(size_t sector, const SectorBuffer &buf)
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
constexpr const char * c_str() const
std::pair< string_view, string_view > splitOnLast(string_view str, string_view chars)
void trimRight(string &str, const char *chars)
std::pair< string_view, string_view > splitOnFirst(string_view str, string_view chars)
void trimLeft(string &str, const char *chars)
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
constexpr vecN< N, T > clamp(const vecN< N, T > &x, const vecN< N, T > &minVal, const vecN< N, T > &maxVal)
struct openmsx::FAT12::ToClusterNumber toClusterNumber
struct openmsx::FAT16::ToClusterNumber toClusterNumber
std::variant< Free, Cluster > DirCluster
std::variant< Free, EndOfChain, Cluster > FatCluster
decltype(MSXDirEntry::filename) FileName
std::optional< Stat > getStat(zstring_view filename)
Call stat() and return the stat structure.
void mkdirp(string path)
Acts like the unix command "mkdir -p".
This file implemented 3 utility functions:
auto visit(Visitor &&visitor, const Event &event)
bool foreach_file_and_directory(std::string path, FileAction fileAction, DirAction dirAction)
constexpr void fill(ForwardRange &&range, const T &value)
auto copy(InputRange &&range, OutputIter out)
bool equal(InputRange1 &&range1, InputRange2 &&range2, Pred pred={}, Proj1 proj1={}, Proj2 proj2={})
void swap(openmsx::MemBuffer< T > &l, openmsx::MemBuffer< T > &r) noexcept
size_t size(std::string_view utf8)
constexpr auto subspan(Range &&range, size_t offset, size_t count=std::dynamic_extent)
auto transform_in_place(ForwardRange &&range, UnaryOperation op)
std::string strCat(Ts &&...ts)
void strAppend(std::string &result, Ts &&...ts)
unsigned operator()(Free) const
unsigned operator()(Cluster cluster) const
unsigned operator()(EndOfChain) const
unsigned operator()(EndOfChain) const
unsigned operator()(Cluster cluster) const
unsigned operator()(Free) const
std::array< MSXDirEntry, 16 > dirEntry
constexpr auto xrange(T e)