From fe07a0b1d882e211410109b1375b7440395450ed Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 2 Jul 2021 18:56:43 -0400 Subject: [PATCH] Starts to add a FAT[12] parser. --- Analyser/Static/Enterprise/StaticAnalyser.cpp | 8 +- .../Clock Signal.xcodeproj/project.pbxproj | 6 + Storage/Disk/Parsers/FAT.cpp | 178 ++++++++++++++++++ Storage/Disk/Parsers/FAT.hpp | 79 ++++++++ 4 files changed, 268 insertions(+), 3 deletions(-) create mode 100644 Storage/Disk/Parsers/FAT.cpp create mode 100644 Storage/Disk/Parsers/FAT.hpp diff --git a/Analyser/Static/Enterprise/StaticAnalyser.cpp b/Analyser/Static/Enterprise/StaticAnalyser.cpp index 7b701b1d9..4965b0496 100644 --- a/Analyser/Static/Enterprise/StaticAnalyser.cpp +++ b/Analyser/Static/Enterprise/StaticAnalyser.cpp @@ -9,13 +9,14 @@ #include "StaticAnalyser.hpp" #include "Target.hpp" +#include "../../../Storage/Disk/Parsers/FAT.hpp" + Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) { // This analyser can comprehend disks only. if(media.disks.empty()) return {}; - // Otherwise, for now: wave it through. + // Otherwise, assume a return will happen. Analyser::Static::TargetList targets; - using Target = Analyser::Static::Enterprise::Target; auto *const target = new Target; target->media = media; @@ -23,8 +24,9 @@ Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Medi // Always require a BASIC. target->basic_version = Target::BASICVersion::Any; - // If this is a single-sided floppy disk, guess the Macintosh 512kb. + // Inspect any supplied disks. if(!media.disks.empty()) { + auto volume = Storage::Disk::FAT::GetVolume(media.disks.front()); target->dos = Target::DOS::EXDOS; } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 8587d4e08..f2b162d23 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -241,6 +241,7 @@ 4B4518A31F75FD1C00926311 /* HFE.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518951F75FD1B00926311 /* HFE.cpp */; }; 4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */; }; 4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518991F75FD1B00926311 /* SSD.cpp */; }; + 4B47770B268FBE4D005C2340 /* FAT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B477709268FBE4D005C2340 /* FAT.cpp */; }; 4B47F6C5241C87A100ED06F7 /* Struct.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B47F6C4241C87A100ED06F7 /* Struct.cpp */; }; 4B47F6C6241C87A100ED06F7 /* Struct.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B47F6C4241C87A100ED06F7 /* Struct.cpp */; }; 4B49F0A923346F7A0045E6A6 /* MacintoshOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B49F0A723346F7A0045E6A6 /* MacintoshOptions.xib */; }; @@ -1276,6 +1277,8 @@ 4B45189A1F75FD1B00926311 /* SSD.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SSD.hpp; sourceTree = ""; }; 4B4518A71F76004200926311 /* TapeParser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = TapeParser.hpp; path = Parsers/TapeParser.hpp; sourceTree = ""; }; 4B4518A81F76022000926311 /* DiskImageImplementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DiskImageImplementation.hpp; sourceTree = ""; }; + 4B477709268FBE4D005C2340 /* FAT.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = FAT.cpp; path = Parsers/FAT.cpp; sourceTree = ""; }; + 4B47770A268FBE4D005C2340 /* FAT.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = FAT.hpp; path = Parsers/FAT.hpp; sourceTree = ""; }; 4B47F6C4241C87A100ED06F7 /* Struct.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Struct.cpp; sourceTree = ""; }; 4B49F0A823346F7A0045E6A6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MacintoshOptions.xib"; sourceTree = SOURCE_ROOT; }; 4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AY38910.cpp; sourceTree = ""; }; @@ -2657,7 +2660,9 @@ isa = PBXGroup; children = ( 4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */, + 4B477709268FBE4D005C2340 /* FAT.cpp */, 4B3FE75D1F3CF68B00448EE4 /* CPM.hpp */, + 4B47770A268FBE4D005C2340 /* FAT.hpp */, ); name = Parsers; sourceTree = ""; @@ -5579,6 +5584,7 @@ 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */, 4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */, 4B65086022F4CF8D009C1100 /* Keyboard.cpp in Sources */, + 4B47770B268FBE4D005C2340 /* FAT.cpp in Sources */, 4B894528201967B4007DE474 /* Disk.cpp in Sources */, 4B2E86CF25D8D8C70024F1E9 /* Keyboard.cpp in Sources */, 4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */, diff --git a/Storage/Disk/Parsers/FAT.cpp b/Storage/Disk/Parsers/FAT.cpp new file mode 100644 index 000000000..8e403c77d --- /dev/null +++ b/Storage/Disk/Parsers/FAT.cpp @@ -0,0 +1,178 @@ +// +// FAT.cpp +// Clock Signal +// +// Created by Thomas Harte on 02/07/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#include "FAT.hpp" + +#include "../Encodings/MFM/Parser.hpp" + +#include + +using namespace Storage::Disk; + +FAT::Volume::CHS FAT::Volume::chs_for_sector(int sector) const { + const auto sectors_per_head = total_sectors / head_count; + + // Guess here: there's no head interleaving. Let's see. + return CHS{ + sector / sectors_per_head, + (sector % sectors_per_head) / sectors_per_track, + 1 + (sector % sectors_per_track) + }; +} + +int FAT::Volume::sector_for_cluster(int cluster) const { + return (cluster * sectors_per_cluster) + first_data_sector; +} + +namespace { + +FAT::Directory directory_from(const std::vector &contents) { + FAT::Directory result; + + for(size_t base = 0; base < contents.size(); base += 32) { + // An entry starting with byte 0 indicates end-of-directory. + if(!contents[base]) { + break; + } + + // An entry starting in 0xe5 is merely deleted. + if(contents[base] == 0xe5) { + continue; + } + + // Otherwise create and populate a new entry. + result.emplace_back(); + result.back().name = std::string(&contents[base], &contents[base+8]); + result.back().extension = std::string(&contents[base+8], &contents[base+11]); + result.back().attributes = contents[base + 11]; + result.back().time = uint16_t(contents[base+22] | (contents[base+23] << 8)); + result.back().date = uint16_t(contents[base+24] | (contents[base+25] << 8)); + result.back().starting_cluster = uint16_t(contents[base+26] | (contents[base+27] << 8)); + result.back().size = uint32_t( + contents[base+28] | + (contents[base+29] << 8) | + (contents[base+30] << 16) | + (contents[base+31] << 24) + ); + } + + return result; +} + +} + +std::optional FAT::GetVolume(const std::shared_ptr &disk) { + Storage::Encodings::MFM::Parser parser(true, disk); + + // Grab the boot sector; that'll be enough to establish the volume. + Storage::Encodings::MFM::Sector *const boot_sector = parser.get_sector(0, 0, 1); + if(!boot_sector || boot_sector->samples.empty() || boot_sector->samples[0].size() < 512) { + return std::nullopt; + } + + // Obtain volume details. + const auto &data = boot_sector->samples[0]; + FAT::Volume volume; + volume.bytes_per_sector = uint16_t(data[11] | (data[12] << 8)); + volume.sectors_per_cluster = data[13]; + volume.fat_copies = data[16]; + const uint16_t root_directory_entries = uint16_t(data[17] | (data[18] << 8)); + volume.total_sectors = uint16_t(data[19] | (data[20] << 8)); + volume.sectors_per_fat = uint16_t(data[22] | (data[23] << 8)); + volume.sectors_per_track = uint16_t(data[24] | (data[25] << 8)); + volume.head_count = uint16_t(data[26] | (data[27] << 8)); + volume.correct_signature = data[510] == 0x55 && data[511] == 0xaa; + + const size_t root_directory_sectors = (root_directory_entries*32 + volume.bytes_per_sector - 1) / volume.bytes_per_sector; + volume.first_data_sector = int(volume.reserved_sectors + volume.sectors_per_fat*volume.fat_copies + root_directory_sectors); + + // Grab the FAT. + std::vector source_fat; + for(int c = 0; c < volume.sectors_per_fat; c++) { + const int sector_number = volume.reserved_sectors + c; + const auto address = volume.chs_for_sector(sector_number); + + Storage::Encodings::MFM::Sector *const fat_sector = + parser.get_sector(address.head, address.cylinder, uint8_t(address.sector)); + if(!fat_sector || fat_sector->samples.empty() || fat_sector->samples[0].size() != volume.bytes_per_sector) { + return std::nullopt; + } + std::copy(fat_sector->samples[0].begin(), fat_sector->samples[0].end(), std::back_inserter(source_fat)); + } + + // Decode the FAT. + // TODO: stop assuming FAT12 here. + for(size_t c = 0; c < source_fat.size(); c += 3) { + const uint32_t double_cluster = uint32_t(source_fat[c] + (source_fat[c + 1] << 8) + (source_fat[c + 2] << 16)); + volume.fat.push_back(double_cluster & 0xfff); + volume.fat.push_back(double_cluster >> 12); + } + + // Grab the root directory. + std::vector root_directory; + for(size_t c = 0; c < root_directory_sectors; c++) { + const auto sector_number = int(1 + c + volume.sectors_per_fat*volume.fat_copies); + const auto address = volume.chs_for_sector(sector_number); + + Storage::Encodings::MFM::Sector *const sector = + parser.get_sector(address.head, address.cylinder, uint8_t(address.sector)); + if(!sector || sector->samples.empty() || sector->samples[0].size() != volume.bytes_per_sector) { + return std::nullopt; + } + std::copy(sector->samples[0].begin(), sector->samples[0].end(), std::back_inserter(root_directory)); + } + volume.root_directory = directory_from(root_directory); + + // TEST! + // TODO: REMOVE. + for(const auto &file: volume.root_directory) { + if(!(file.attributes & File::Attribute::Directory)) { + continue; + } + const auto sub = GetDirectory(disk, volume, file); + if(!sub) { + continue; + } + std::cout << (*sub).size(); + } + + return volume; +} + +std::optional> FAT::GetFile(const std::shared_ptr &disk, const Volume &volume, const File &file) { + Storage::Encodings::MFM::Parser parser(true, disk); + + std::vector contents; + uint16_t cluster = file.starting_cluster; + while(contents.size() < file.size) { + int sector = volume.sector_for_cluster(cluster); + ++cluster; + + for(int c = 0; c < volume.sectors_per_cluster; c++) { + const auto address = volume.chs_for_sector(sector); + ++sector; + + Storage::Encodings::MFM::Sector *const sector_contents = + parser.get_sector(address.head, address.cylinder, uint8_t(address.sector)); + if(!sector_contents || sector_contents->samples.empty() || sector_contents->samples[0].size() != volume.bytes_per_sector) { + return std::nullopt; + } + std::copy(sector_contents->samples[0].begin(), sector_contents->samples[0].end(), std::back_inserter(contents)); + } + } + + return contents; +} + +std::optional FAT::GetDirectory(const std::shared_ptr &disk, const Volume &volume, const File &file) { + const auto contents = GetFile(disk, volume, file); + if(!contents) { + return std::nullopt; + } + return directory_from(*contents); +} diff --git a/Storage/Disk/Parsers/FAT.hpp b/Storage/Disk/Parsers/FAT.hpp new file mode 100644 index 000000000..cbf6cdb85 --- /dev/null +++ b/Storage/Disk/Parsers/FAT.hpp @@ -0,0 +1,79 @@ +// +// FAT.hpp +// Clock Signal +// +// Created by Thomas Harte on 02/07/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#ifndef Storage_Disk_Parsers_FAT_hpp +#define Storage_Disk_Parsers_FAT_hpp + +#include "../Disk.hpp" + +#include +#include +#include +#include + +namespace Storage { +namespace Disk { +namespace FAT { + +struct File { + std::string name; + std::string extension; + uint8_t attributes = 0; + uint16_t time = 0; // TODO: offer time/date decoders. + uint16_t date = 0; + uint16_t starting_cluster = 0; + uint32_t size = 0; + + enum Attribute: uint8_t { + ReadOnly = (1 << 0), + Hidden = (1 << 1), + System = (1 << 2), + VolumeLabel = (1 << 3), + Directory = (1 << 4), + Archive = (1 << 5), + }; +}; + +using Directory = std::vector; + +struct Volume { + uint16_t bytes_per_sector = 0; + uint8_t sectors_per_cluster = 0; + uint8_t reserved_sectors = 0; + uint8_t fat_copies = 0; + uint16_t total_sectors = 0; + uint16_t sectors_per_fat = 0; + uint16_t sectors_per_track = 0; + uint16_t head_count = 0; + uint16_t hidden_sectors = 0; + bool correct_signature = false; + int first_data_sector = 0; + + std::vector fat; + Directory root_directory; + + struct CHS { + int cylinder; + int head; + int sector; + }; + /// @returns a direct sector -> CHS address translation. + CHS chs_for_sector(int sector) const; + /// @returns the CHS address for the numbered cluster within the data area. + int sector_for_cluster(int cluster) const; +}; + +std::optional GetVolume(const std::shared_ptr &disk); +std::optional> GetFile(const std::shared_ptr &disk, const Volume &volume, const File &file); +std::optional GetDirectory(const std::shared_ptr &disk, const Volume &volume, const File &file); + +} +} +} + +#endif /* FAT_hpp */