Merge pull request 'Massive refactoring of builder and its relations' (#18) from development into main

Reviewed-on: #18
This commit is contained in:
Bas Wiel, van de 2024-05-03 15:04:21 +02:00
commit 89dfbe0222
10 changed files with 532 additions and 409 deletions

67
Cargo.lock generated
View File

@ -34,47 +34,48 @@ dependencies = [
[[package]]
name = "anstream"
version = "0.6.13"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.6"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
[[package]]
name = "anstyle-parse"
version = "0.2.3"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.2"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
@ -148,9 +149,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
[[package]]
name = "cc"
version = "1.0.95"
version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b"
checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd"
[[package]]
name = "cfg-if"
@ -214,9 +215,9 @@ checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "colorchoice"
version = "1.0.0"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
[[package]]
name = "console"
@ -334,15 +335,15 @@ dependencies = [
[[package]]
name = "fastrand"
version = "2.0.2"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984"
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
[[package]]
name = "flate2"
version = "1.0.28"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
dependencies = [
"crc32fast",
"miniz_oxide",
@ -474,9 +475,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.14.3"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "heck"
@ -626,6 +627,12 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
[[package]]
name = "itoa"
version = "1.0.11"
@ -649,9 +656,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.153"
version = "0.2.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
[[package]]
name = "linux-raw-sys"
@ -955,18 +962,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.198"
version = "1.0.200"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.198"
version = "1.0.200"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb"
dependencies = [
"proc-macro2",
"quote",
@ -1031,9 +1038,9 @@ dependencies = [
[[package]]
name = "socket2"
version = "0.5.6"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871"
checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
dependencies = [
"libc",
"windows-sys 0.52.0",
@ -1229,9 +1236,9 @@ dependencies = [
[[package]]
name = "unicode-width"
version = "0.1.11"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6"
[[package]]
name = "unsafe-libyaml"

2
Jenkinsfile vendored
View File

@ -82,7 +82,7 @@ pipeline {
stage('Publish ZIP file') {
steps {
sshagent(credentials: ['jenkins-sshkey']) {
sh 'scp ./doscontainer-${BUILD_NUMBER}.zip uploader@10.20.0.17:/srv/ftp/doscontainer/builds/doscontainer-${BUILD_NUMBER}.zip'
sh 'scp ./doscontainer-${BUILD_NUMBER}.zip uploader@10.20.0.17:/srv/ftp/doscontainer/testbuilds/doscontainer-${BUILD_NUMBER}.zip'
}
}
}

View File

@ -6,109 +6,349 @@ use crate::{
Disk,
},
downloader::ZipDownloader,
fat::{direntry::DirEntryType, vbr::Vbr, Fat},
fat::{
direntry::{DirEntry, DirEntryType},
vbr::Vbr,
Fat,
},
manifest::Manifest,
os::OperatingSystem,
};
#[derive(Debug)]
pub struct DiskBuilder;
pub struct DiskBuilder {
filesystem: Fat,
operating_system: OperatingSystem,
disk: Disk,
partition: Partition,
manifest: Manifest,
}
impl DiskBuilder {
/// The builder is the struct that does all manner of validation before trying to
/// create a Disk so that what we create will make sense.
pub fn build(manifest: &Manifest) -> Result<(), std::io::Error> {
let mut gamedownloader = ZipDownloader::new();
let mut osdownloader = ZipDownloader::new();
gamedownloader.download_zip(
"https://dosk8s-dist.area536.com/alleycat.zip",
Some("Downloading Alley Cat"),
)?;
osdownloader.download_zip(
"https://dosk8s-dist.area536.com/ibm-pc-dos-200.zip",
Some("Downloading IBM PC-DOS 2.00"),
)?;
let gamefiles = gamedownloader.get_files();
let operating_system = OperatingSystem::from_str(manifest.os.as_str()).unwrap();
/// Create a new builder from a manifest
pub fn new(manifest: Manifest) -> Result<Self, std::io::Error> {
// Gather what we need from the manifest and instantiate structs as needed
let mut disk = Disk::new(manifest.disktype.as_str(), manifest.os.as_str())?;
let operating_system = OperatingSystem::from_str(manifest.os.as_str()).unwrap();
disk.attach(manifest.diskname.as_str())?;
disk.set_geometry(manifest.disksize)?;
let root_partition =
Partition::fill_disk(&disk, Some(Self::derive_partition_type(manifest)))?;
Partition::fill_disk(&disk, Some(Self::derive_partition_type(&manifest)))?;
disk.partition_table
.add_partition(root_partition.clone(), Some(0))?;
let mut filesystem = Fat::new(&disk, &root_partition, Some(operating_system))?;
let vbr = Vbr::from_os(&operating_system, &filesystem);
let filesystem = Fat::new(&disk, &root_partition, Some(operating_system))?;
disk.write_os_bootcode()?;
disk.write_partition_table()?;
disk.write_boot_signature()?;
disk.write_sector(vbr.as_bytes(), 1)?;
// Return the completed disk builder instance
Ok(DiskBuilder {
disk,
filesystem,
partition: root_partition,
operating_system,
manifest: manifest,
})
}
//let commandcom = operating_system.get_commandcom();
let commandcom = Some(
osdownloader
.get_file_by_name("COMMAND.COM")
.unwrap()
.get_bytes(),
)
.unwrap()
.to_vec();
let iosys = Some(
osdownloader
.get_file_by_name("IBMBIO.COM")
.unwrap()
.get_bytes(),
)
.unwrap()
.to_vec();
let msdossys = Some(
osdownloader
.get_file_by_name("IBMDOS.COM")
.unwrap()
.get_bytes(),
)
.unwrap()
.to_vec();
filesystem.mkfile(
None,
"ibmbio.com",
iosys.as_slice(),
&root_partition,
&mut disk,
)?;
filesystem.write_file(
msdossys.as_slice(),
"ibmdos.com",
None,
Some(DirEntryType::SysFile),
&root_partition,
&mut disk,
)?;
filesystem.write_file(
commandcom.as_slice(),
"command.com",
None,
Some(DirEntryType::File),
&root_partition,
&mut disk,
)?;
for file in gamefiles {
filesystem.write_file(
file.get_bytes(),
file.get_name(),
None,
None,
&root_partition,
&mut disk,
/// Copy a file from a downloaded ZIP to the VHD
pub fn copy_file(
&mut self,
downloader: &ZipDownloader,
filename: &str,
) -> Result<(), std::io::Error> {
if let Some(source_file) = downloader.get_file_by_name(filename) {
self.filesystem
.mkfile(None, source_file.get_name(), false, source_file.get_bytes())?;
Ok(())
} else {
// If the file was not found, nothing essentially bad happens. Just continue and do nothing for now.
Ok(())
}
}
/// Copy a file from a downloaded ZIP to the VHD
pub fn copy_sysfile(
&mut self,
downloader: &ZipDownloader,
filename: &str,
) -> Result<(), std::io::Error> {
if let Some(source_file) = downloader.get_file_by_name(filename) {
self.filesystem
.mkfile(None, source_file.get_name(), true, source_file.get_bytes())?;
Ok(())
} else {
// If the file was not found, nothing essentially bad happens. Just continue and do nothing for now.
Ok(())
}
}
/// Writes the contents of a `DirEntry` to the disk in clusters.
///
/// This function takes the data from a `DirEntry` and writes it to the disk,
/// segmenting the data into clusters based on the filesystem's cluster size.
/// Each segment of data is written to a specific disk cluster corresponding
/// to pre-allocated cluster addresses within the `DirEntry`.
///
/// # Parameters
/// - `file`: A reference to the `DirEntry` containing the data to be written.
/// The `DirEntry` must contain both the data (as a byte slice) and a list
/// of allocated cluster addresses where the data will be written.
///
/// # Returns
/// This function returns `Ok(())` if the data is successfully written to all
/// specified clusters. It returns an `Err(std::io::Error)` if there is a mismatch
/// in the number of allocated clusters and the number of data chunks, or if
/// any call to `write_cluster` fails.
///
/// # Errors
/// This function can error in the following scenarios:
/// - Returns an error if called on a directory entry type other than `File` or `SysFile`.
/// - If the number of clusters pre-allocated in `file.allocated_clusters` does
/// not match the number of chunks into which the data has been divided. This
/// situation triggers an error with `std::io::ErrorKind::Other`.
/// - If writing any cluster fails, the underlying `write_cluster` function
/// may also return an `std::io::Error` describing the specific problem encountered
/// during the write operation.
///
/// # Note
/// This function assumes that `file.get_data()` returns a slice of all data to be written
/// and that `file.allocated_clusters` contains a valid list of disk addresses. It also
/// assumes that each address in `file.allocated_clusters` can hold a cluster of data
/// of size defined by `filesystem.get_sectors_per_cluster() * 512`.
pub fn write_file_to_storage(&mut self, file: &DirEntry) -> Result<(), std::io::Error> {
if file.entry_type != DirEntryType::File && file.entry_type != DirEntryType::SysFile {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"This method only works on files, not on other entry types.",
));
}
let bytes = file.get_data(); // Assuming this returns a &[u8]
let cluster_size = self.filesystem.get_sectors_per_cluster() * 512;
let chunks = bytes.chunks(cluster_size);
let addresses = &file.allocated_clusters; // Use a reference
// Ensure the number of allocated clusters matches the number of chunks
if addresses.len() != chunks.len() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Number of allocated clusters does not match the number of data chunks.",
));
}
// Iterate over addresses and chunks to write data directly
for (address, chunk) in addresses.iter().zip(chunks) {
self.write_cluster(*address, chunk)?;
}
Ok(())
}
/// Writes the data of a cluster to the disk.
///
/// This method takes the data of a cluster, represented as a `&[u8]`, and writes it to the disk.
/// If the provided data is shorter than the size of a cluster, it is padded with zeroes.
/// Each cluster is divided into sectors, and the data is written sector by sector.
///
/// # Arguments
///
/// * `data`: The data of the cluster to be written to the disk.
/// * `cluster_address`: The address of the cluster on the disk where the data will be written.
///
/// # Errors
///
/// This method returns an `std::io::Error` if there is an I/O error during writing.
pub fn write_cluster(
&mut self,
cluster_address: u32,
data: &[u8],
) -> Result<(), std::io::Error> {
let bytes_per_cluster = self.filesystem.get_sectors_per_cluster() * 512;
let mut bytes = data.to_vec();
// If our data won't fill the cluster, pad it up to the right size with zero values.
if bytes.len() < bytes_per_cluster {
bytes.resize(bytes_per_cluster, 0);
}
// Calculate the base offset to figure out where the cluster should live on the disk
let mut sector_address = (cluster_address as usize
* self.filesystem.get_sectors_per_cluster() as usize
+ self.filesystem.get_first_data_sector() as usize)
- 2 * self.filesystem.get_sectors_per_cluster() as usize;
for chunk in bytes.chunks_exact(512) {
let mut chunk_array = [0u8; 512];
chunk_array.copy_from_slice(chunk);
self.partition
.write_sector(&mut self.disk, sector_address as u32, chunk_array)?;
sector_address += 1;
}
Ok(())
}
/// Writes the allocation tables to the disk for the specified partition.
pub fn write_allocation_tables(&mut self) -> Result<(), std::io::Error> {
let allocation_table_bytes = self
.filesystem
.allocation_table
.as_bytes(self.filesystem.fat_type)?;
let mut sectors: Vec<[u8; 512]> = Vec::new();
let mut current_chunk: [u8; 512] = [0; 512];
let mut current_index = 0;
for &byte in allocation_table_bytes.iter() {
current_chunk[current_index] = byte;
current_index += 1;
// If the current sector is full, push it to the sectors vector and reset
if current_index == 512 {
sectors.push(current_chunk);
current_chunk = [0; 512];
current_index = 0;
}
}
// If there are remaining bytes in the last chunk, fill the rest with zeros and push it
if current_index > 0 {
sectors.push(current_chunk);
}
let mut sector_offset = self.filesystem.reserved_sector_count;
let mut counter = 0;
while counter < self.filesystem.fat_copies {
for (index, sector) in sectors.iter().enumerate() {
self.partition.write_sector(
&mut self.disk,
sector_offset as u32 + index as u32,
*sector,
)?;
}
counter += 1;
sector_offset = sector_offset + self.filesystem.sectors_per_fat;
}
Ok(())
}
/// Writes directory entries from the root directory into the disk sectors.
///
/// This function serializes the directory entries from the root directory into bytes and writes
/// them into the disk sectors starting from a calculated sector offset. The entries are written
/// in 512-byte sectors. If the last set of bytes doesn't fill an entire sector, the remainder
/// is zero-padded and written as the final sector.
///
/// ## Parameters
/// - `&mut self`: Mutable reference to the current instance of the object that includes
/// filesystem and partition information. This is necessary for accessing the filesystem layout
/// and performing write operations.
///
/// ## Returns
/// - `Result<(), std::io::Error>`: The function returns `Ok(())` if all sectors are written
/// successfully to the disk. If there is an error during any of the write operations, an
/// `Err` containing the `std::io::Error` is returned.
///
/// ## Errors
/// - This function will return an `Err` if there is an issue with writing to the disk.
/// - Errors can include but are not limited to I/O errors like disk write failures.
///
pub fn write_dirs(&mut self) -> Result<(), std::io::Error> {
let sector_offset: u32 = self.filesystem.reserved_sector_count as u32
+ (self.filesystem.fat_copies as u32 * self.filesystem.sectors_per_fat as u32);
let mut bytes = Vec::new();
let mut sectors: Vec<[u8; 512]> = Vec::new();
// Run through all the root dir's children and get their byte-wise representation.
for entry in self.filesystem.root_directory.get_children() {
// Push all those raw bytes into the bytes vector for further processing
for byte in entry.as_bytes()? {
bytes.push(byte);
}
}
// Write the bytes off to disk
let mut current_chunk: [u8; 512] = [0; 512];
let mut current_index = 0;
for &byte in bytes.iter() {
current_chunk[current_index] = byte;
current_index += 1;
// Got a full sector? Push it onto the vec.
if current_index == 512 {
sectors.push(current_chunk);
current_chunk = [0; 512]; // Reset the chunk for new data
current_index = 0;
}
}
// Check if there is a leftover partial sector and push it if there is.
if current_index != 0 {
sectors.push(current_chunk);
}
// Write sectors to disk
for (index, sector) in sectors.iter().enumerate() {
self.partition.write_sector(
&mut self.disk,
sector_offset as u32 + index as u32,
*sector,
)?;
}
filesystem.mkdir(None, "DOS")?;
//filesystem.mkdir(None,"GAME")?;
Ok(())
}
/// The builder is the struct that does all manner of validation before trying to
/// create a Disk so that what we create will make sense.
pub fn build(&mut self) -> Result<(), std::io::Error> {
let vbr = Vbr::from_os(&self.operating_system, &self.filesystem);
self.disk.write_os_bootcode()?;
self.disk.write_partition_table()?;
self.disk.write_boot_signature()?;
self.disk.write_sector(vbr.as_bytes(), 1)?;
// Instantiate a downloader specifically for the operating system
let osdownloader = ZipDownloader::from_url(
&self.operating_system.get_download_url(),
Some(&format!(
"Downloading {}",
&self.operating_system.get_friendlyname()
)),
)?;
// Install the required components for a bootable operating system
self.copy_sysfile(&osdownloader, &self.operating_system.get_iosys())?;
self.copy_sysfile(&osdownloader, &self.operating_system.get_msdossys())?;
self.copy_file(&osdownloader, &self.operating_system.get_commandcom())?;
// Instantiate as many downloaders as we need for the application's distfiles
let mut gamedownloaders: Vec<ZipDownloader> =
Vec::with_capacity(self.manifest.application.len());
for distfile in &self.manifest.application {
let downloader = ZipDownloader::from_url(
&distfile.get_url(),
Some(&format!("Downloading {}", &distfile.get_label())),
)?;
gamedownloaders.push(downloader);
}
// Process all files in the game ZIP
for gamedownloader in gamedownloaders {
for file in gamedownloader.get_files() {
self.copy_file(&gamedownloader, file.get_name())?;
}
}
// Write all files to storage
for file in self.filesystem.root_directory.children.clone() {
self.write_file_to_storage(&file)?;
}
// Last action: write the allocation table copies to the disk
filesystem.write_allocation_tables(&root_partition, &mut disk)?;
filesystem.write_dirs(None, &root_partition, &mut disk)?;
disk.commit_storage()?;
self.write_allocation_tables()?;
self.write_dirs()?;
self.disk.commit_storage()?;
Ok(())
}

View File

@ -32,6 +32,15 @@ impl ZipDownloader {
}
}
/// Construct a new ZipDownloader from a URL. Combines the construction and initial download
/// of the ZIP file in a single function call. The struct can then be used to operate on its
/// contents.
pub fn from_url(url: &str, message: Option<&str>) -> Result<Self, std::io::Error> {
let mut downloader = Self::new();
downloader.download_zip(url, message)?;
Ok(downloader)
}
pub fn get_files(&self) -> &Vec<DownloadedFile> {
&self.files
}
@ -71,6 +80,7 @@ impl ZipDownloader {
Ok(())
}
/// Pull a file from the URL and save it to the file path.
fn download_file(url: &str, file_path: &Path) -> std::io::Result<()> {
let mut response = reqwest::blocking::get(url).unwrap();
let total_size = response.content_length().unwrap_or(0);

View File

@ -19,7 +19,7 @@ pub mod vbr;
/// Represents the overall structure of a FAT filesystem.
pub struct Fat {
/// Represents the type of the FAT filesystem (FAT12, FAT16, FAT32).
fat_type: FatType,
pub fat_type: FatType,
/// Holds the allocation table, which manages cluster allocation and deallocation.
pub allocation_table: AllocationTable,
/// Holds the root directory entries.
@ -31,9 +31,9 @@ pub struct Fat {
/// Specifies the number of sectors per cluster.
sectors_per_cluster: u8,
/// Indicates the count of reserved sectors at the beginning of the filesystem.
reserved_sector_count: u16,
pub reserved_sector_count: u16,
/// Specifies the number of copies of the FAT table.
fat_copies: u8,
pub fat_copies: u8,
/// Indicates the number of entries in the root directory (only applicable for FAT12 and FAT16).
root_dir_entries: u16,
/// Represents the total count of sectors in the filesystem.
@ -41,7 +41,7 @@ pub struct Fat {
/// Stores the media descriptor byte, indicating the type of storage media.
media_descriptor: MediaDescriptor,
/// Specifies the count of sectors per FAT table.
sectors_per_fat: u16,
pub sectors_per_fat: u16,
/// Indicates the count of sectors per track.
sectors_per_track: u16,
/// Specifies the count of heads (surfaces) on the storage device.
@ -176,7 +176,7 @@ impl Fat {
"ROOTDIR",
DirEntryType::RootDirectory,
None,
0,
&Vec::new(),
0,
None,
)?,
@ -267,7 +267,7 @@ impl Fat {
"ROOTDIR",
DirEntryType::RootDirectory,
None,
0,
&Vec::new(),
0,
None,
)?,
@ -335,54 +335,47 @@ impl Fat {
&mut self,
parent: Option<&mut DirEntry>,
filename: &str,
sysfile: bool,
data: &[u8],
partition: &Partition,
disk: &mut Disk,
) -> Result<(), std::io::Error> {
// 1. Allocate the required number of clusters on the allocation table
let clusters = self.allocate_clusters(data)?;
let bytes_per_cluster = self.sectors_per_cluster as usize * 512;
let mut remaining_data = data;
// Determine the DirEntryType based on sysfile flag
let entry_type = if sysfile {
DirEntryType::SysFile
} else {
DirEntryType::File
};
// 2. Create a directory entry for the new file.
// Attempt to allocate clusters and handle errors appropriately
let allocated_clusters = self.allocate_clusters(data)?;
// Write the proper values to the allocation table
if let Some((last, elements)) = allocated_clusters.split_last() {
// Iterate over all but the last element with their indices
for (i, &current_cluster) in elements.iter().enumerate() {
let next_cluster = allocated_clusters[i + 1];
self.allocation_table
.allocate_cluster(current_cluster, next_cluster)?;
}
// Handle the last cluster, which points to the end of the chain
self.allocation_table.mark_end_of_chain(*last)?;
}
// Create a new directory entry
let file_entry = DirEntry::new(
filename,
DirEntryType::File,
entry_type,
None,
clusters[0],
&allocated_clusters,
data.len() as u32,
Some(data),
)?;
// 3. Place the entry into the correct directory entry inside the file system
match parent {
Some(subdir) => subdir.add_child(file_entry),
None => self.root_directory.add_child(file_entry),
}
// 4. Write the file's bytes to the clusters
for i in 0..clusters.len() {
let mut data: Vec<u8> = Vec::with_capacity(bytes_per_cluster);
let cluster_number = clusters[i];
let bytes_to_write = std::cmp::min(bytes_per_cluster, remaining_data.len());
// Copy data to write into the buffer
data.extend_from_slice(&remaining_data[..bytes_to_write]);
remaining_data = &remaining_data[bytes_to_write..];
// Write data to cluster
self.write_cluster(&mut data, cluster_number as u16, partition, disk)?;
// Update cluster allocation table
if let Some(value) = clusters.get(i + 1).cloned() {
self.allocation_table
.allocate_cluster(cluster_number, value)?;
} else {
// No next cluster, mark end of chain.
self.allocation_table.mark_end_of_chain(cluster_number)?;
break;
}
// Add the new entry to the parent directory or use the root directory
if let Some(parent_dir) = parent {
parent_dir.add_child(file_entry);
} else {
self.root_directory.add_child(file_entry);
}
Ok(())
}
@ -396,6 +389,7 @@ impl Fat {
// Set the parent to the root dir if none is given
let new_start_cluster = self.allocate_cluster()?;
self.allocation_table.mark_end_of_chain(new_start_cluster)?;
let allocated_clusters: Vec<u32> = vec![new_start_cluster];
let parent_entry = parent.unwrap_or(&mut self.root_directory);
@ -404,7 +398,7 @@ impl Fat {
dir_name,
DirEntryType::Directory,
None,
new_start_cluster,
&allocated_clusters,
0,
None,
)?;
@ -414,7 +408,7 @@ impl Fat {
".",
DirEntryType::Directory,
None,
new_entry.get_start_cluster(),
&allocated_clusters,
0,
None,
)?);
@ -424,7 +418,7 @@ impl Fat {
"..",
DirEntryType::Directory,
None,
parent_entry.get_start_cluster(),
&allocated_clusters,
0,
None,
)?);
@ -435,207 +429,6 @@ impl Fat {
Ok(())
}
/// Writes the data of a cluster to the disk.
///
/// This method takes the data of a cluster, represented as a `Vec<u8>`, and writes it to the disk.
/// If the provided data is shorter than the size of a cluster, it is padded with zeroes.
/// Each cluster is divided into sectors, and the data is written sector by sector.
///
/// # Arguments
///
/// * `data`: The data of the cluster to be written to the disk.
/// * `cluster_address`: The address of the cluster on the disk where the data will be written.
/// * `partition`: A reference to the partition where the cluster resides.
/// * `disk`: A mutable reference to the disk where the data will be written.
///
/// # Errors
///
/// This method returns an `std::io::Error` if there is an I/O error during writing.
pub fn write_cluster(
&self,
data: &mut Vec<u8>,
cluster_address: u16,
partition: &Partition,
disk: &mut Disk,
) -> Result<(), std::io::Error> {
// Ensure data has the correct size
let cluster_size = self.sectors_per_cluster as usize * 512;
if data.len() < cluster_size {
data.resize(cluster_size, 0);
}
// Iterate over data in chunks and write sectors
let rootdir_sector_count = ((self.root_dir_entries * 32) / 512) & !512;
let first_data_sector = self.reserved_sector_count
+ (self.fat_copies as u16 * self.sectors_per_fat)
+ rootdir_sector_count;
let mut sector_address = (cluster_address as usize * self.sectors_per_cluster as usize
+ first_data_sector as usize)
- 2 * self.sectors_per_cluster as usize;
for chunk in data.chunks_exact(512) {
let mut chunk_array = [0u8; 512];
chunk_array.copy_from_slice(chunk);
partition.write_sector(disk, sector_address as u32, chunk_array)?;
sector_address += 1;
}
Ok(())
}
/// Writes the allocation tables to the disk for the specified partition.
///
/// This method takes a mutable reference to self, representing the file system, and
/// references to a `Partition` and a mutable `Disk` where the allocation tables will be written.
/// It returns a `Result<(), std::io::Error>` indicating whether the operation was successful or not.
///
/// # Arguments
///
/// * `partition` - A reference to the partition where the allocation tables will be written.
/// * `disk` - A mutable reference to the disk where the allocation tables will be written.
///
/// # Errors
///
/// Returns an `std::io::Error` if any I/O operation fails during writing.
pub fn write_allocation_tables(
&mut self,
partition: &Partition,
disk: &mut Disk,
) -> Result<(), std::io::Error> {
let allocation_table_bytes = self.allocation_table.as_bytes(self.fat_type)?;
let mut sectors: Vec<[u8; 512]> = Vec::new();
let mut current_chunk: [u8; 512] = [0; 512];
let mut current_index = 0;
for &byte in allocation_table_bytes.iter() {
current_chunk[current_index] = byte;
current_index += 1;
// If the current sector is full, push it to the sectors vector and reset
if current_index == 512 {
sectors.push(current_chunk);
current_chunk = [0; 512];
current_index = 0;
}
}
// If there are remaining bytes in the last chunk, fill the rest with zeros and push it
if current_index > 0 {
sectors.push(current_chunk);
}
let mut sector_offset = self.reserved_sector_count;
let mut counter = 0;
while counter < self.fat_copies {
for (index, sector) in sectors.iter().enumerate() {
partition.write_sector(disk, sector_offset as u32 + index as u32, *sector)?;
}
counter += 1;
sector_offset = sector_offset + self.sectors_per_fat;
}
Ok(())
}
pub fn write_dirs(
&self,
entry: Option<&DirEntry>,
partition: &Partition,
disk: &mut Disk,
) -> Result<(), std::io::Error> {
let directory = match entry {
None => self.root_directory.clone(),
Some(directory) => directory.clone(),
};
// Collect all the bytes in a Vec
let mut bytes: Vec<u8> = Vec::new();
// Loop over all the children and get them as bytes
for child in &directory.children {
bytes.extend_from_slice(&child.as_bytes()?);
// Do this again for all subdirs except for recursive system dirs
if child.get_type() == DirEntryType::Directory {
if child.directory_name() != "." && child.directory_name() != ".." {
self.write_dirs(Some(child), partition, disk)?;
}
}
}
self.write_cluster(
&mut bytes.to_vec(),
directory.get_start_cluster() as u16,
partition,
disk,
)?;
Ok(())
}
pub fn write_file(
&mut self,
file: &[u8],
filename: &str,
directory: Option<DirEntry>,
entry_type: Option<DirEntryType>,
partition: &Partition,
disk: &mut Disk,
) -> Result<(), std::io::Error> {
let cluster_numbers: Vec<u32> = self.allocate_clusters(file)?;
let bytes_per_cluster = self.sectors_per_cluster as usize * 512;
let mut remaining_data = file;
// Create a directory entry for the new file
let dir_entry = match entry_type {
None => DirEntry::new(
filename,
DirEntryType::File,
None,
cluster_numbers[0],
file.len() as u32,
Some(file),
)?,
Some(entry_type) => DirEntry::new(
filename,
entry_type,
None,
cluster_numbers[0],
file.len() as u32,
Some(file),
)?,
};
if directory.is_none() {
self.root_directory.add_child(dir_entry);
}
// Write the individual clusters to the file sytem
for i in 0..cluster_numbers.len() {
let mut data: Vec<u8> = Vec::with_capacity(bytes_per_cluster);
let cluster_number = cluster_numbers[i];
let bytes_to_write = std::cmp::min(bytes_per_cluster, remaining_data.len());
// Copy data to write into the buffer
data.extend_from_slice(&remaining_data[..bytes_to_write]);
remaining_data = &remaining_data[bytes_to_write..];
// Write data to cluster
self.write_cluster(&mut data, cluster_number as u16, partition, disk)?;
// Update cluster allocation table
if let Some(value) = cluster_numbers.get(i + 1).cloned() {
self.allocation_table
.allocate_cluster(cluster_number, value)?;
} else {
// No next cluster, mark end of chain.
self.allocation_table.mark_end_of_chain(cluster_number)?;
break;
}
}
Ok(())
}
/// Generate a BIOS Parameter Block (BPB) compatible with DOS 2.00.
///
/// Constructs a BPB suitable for DOS 2.00 based on the filesystem parameters.
@ -854,6 +647,21 @@ impl Fat {
file_size
}
}
/// Get the sectors per cluster value
pub fn get_sectors_per_cluster(&self) -> usize {
self.sectors_per_cluster as usize
}
/// Return the sector address of the first data sector
pub fn get_first_data_sector(&self) -> u32 {
// Iterate over data in chunks and write sectors
let rootdir_sector_count = ((self.root_dir_entries * 32) / 512) & !512;
let first_data_sector = self.reserved_sector_count
+ (self.fat_copies as u16 * self.sectors_per_fat)
+ rootdir_sector_count;
first_data_sector as u32
}
}
/// Enum representing different types of FAT (File Allocation Table) file systems.

View File

@ -6,10 +6,10 @@ pub struct DirEntry {
filename: FileName,
attributes: Attributes,
create_timedate: TimeDate,
pub start_cluster: u32,
pub file_size: u32,
pub entry_type: DirEntryType,
pub children: Vec<DirEntry>,
pub allocated_clusters: Vec<u32>,
data: Vec<u8>,
}
@ -39,11 +39,9 @@ impl fmt::Display for DirEntry {
write!(
f,
"File name : {}\n\
Start cluster : {}\n\
Entry type : {}\n\
",
self.filename.base_as_string(),
self.start_cluster,
self.entry_type
)
}
@ -56,7 +54,7 @@ impl DirEntry {
filename: &str,
entry_type: DirEntryType,
create_timedate: Option<TimeDate>,
start_cluster: u32,
allocated_clusters: &Vec<u32>,
file_size: u32,
data: Option<&[u8]>,
) -> Result<Self, std::io::Error> {
@ -64,15 +62,15 @@ impl DirEntry {
DirEntryType::File => Self::create_file(
filename,
create_timedate,
start_cluster,
allocated_clusters,
file_size,
data.unwrap().to_vec(),
),
DirEntryType::SysFile => {
Self::create_sysfile(filename, create_timedate, start_cluster, file_size)
Self::create_sysfile(filename, create_timedate, file_size, allocated_clusters, data)
}
DirEntryType::Directory => {
Self::create_directory(filename, create_timedate, start_cluster)
Self::create_directory(filename, create_timedate, allocated_clusters)
}
DirEntryType::RootDirectory => Self::create_rootdir(),
}
@ -83,8 +81,9 @@ impl DirEntry {
fn create_sysfile(
filename: &str,
create_timedate: Option<TimeDate>,
start_cluster: u32,
file_size: u32,
allocated_clusters: &Vec<u32>,
data: Option<&[u8]>,
) -> Result<Self, std::io::Error> {
// Set of attributes appropriate for a system file
let attributes = Attributes::new(true, true, true, true, false, false, false);
@ -93,21 +92,21 @@ impl DirEntry {
filename: FileName::new(filename)?,
attributes,
create_timedate: TimeDate::now(),
start_cluster,
file_size,
entry_type: DirEntryType::SysFile,
children: Vec::new(),
data: Vec::new(),
allocated_clusters: allocated_clusters.to_vec(),
data: data.unwrap().to_vec(),
}),
_ => Ok(DirEntry {
filename: FileName::new(filename)?,
attributes,
create_timedate: create_timedate.unwrap(),
start_cluster,
file_size,
entry_type: DirEntryType::SysFile,
children: Vec::new(),
data: Vec::new(),
allocated_clusters: allocated_clusters.to_vec(),
data: data.unwrap().to_vec(),
}),
}
}
@ -117,7 +116,7 @@ impl DirEntry {
fn create_file(
filename: &str,
create_timedate: Option<TimeDate>,
start_cluster: u32,
allocated_clusters: &Vec<u32>,
file_size: u32,
data: Vec<u8>,
) -> Result<Self, std::io::Error> {
@ -128,20 +127,20 @@ impl DirEntry {
filename: FileName::new(filename)?,
attributes,
create_timedate: TimeDate::now(),
start_cluster,
file_size,
entry_type: DirEntryType::File,
children: Vec::new(),
allocated_clusters: allocated_clusters.to_vec(),
data,
}),
_ => Ok(DirEntry {
filename: FileName::new(filename)?,
attributes,
create_timedate: create_timedate.unwrap(),
start_cluster,
file_size,
entry_type: DirEntryType::File,
children: Vec::new(),
allocated_clusters: allocated_clusters.to_vec(),
data,
}),
}
@ -152,10 +151,10 @@ impl DirEntry {
filename: FileName::new("ROOTDIR")?,
attributes: Attributes::new(false, false, false, false, false, true, false),
create_timedate: TimeDate::now(),
start_cluster: 0,
file_size: 0,
entry_type: DirEntryType::RootDirectory,
children: Vec::new(),
allocated_clusters: Vec::new(),
data: Vec::new(),
})
}
@ -163,7 +162,7 @@ impl DirEntry {
fn create_directory(
filename: &str,
create_timedate: Option<TimeDate>,
start_cluster: u32,
allocated_clusters: &Vec<u32>,
) -> Result<Self, std::io::Error> {
// Set of attributes appropriate to a directory
let attributes = Attributes::new(false, false, false, false, false, true, false);
@ -173,20 +172,20 @@ impl DirEntry {
filename: FileName::new(filename)?,
attributes: attributes.clone(),
create_timedate: TimeDate::now(),
start_cluster,
file_size: 0,
entry_type: DirEntryType::Directory,
children: Vec::new(),
allocated_clusters: allocated_clusters.to_vec(),
data: Vec::new(),
},
_ => DirEntry {
filename: FileName::new(filename)?,
attributes: attributes.clone(),
create_timedate: create_timedate.unwrap(),
start_cluster,
file_size: 0,
entry_type: DirEntryType::Directory,
children: Vec::new(),
allocated_clusters: allocated_clusters.to_vec(),
data: Vec::new(),
},
};
@ -213,7 +212,7 @@ impl DirEntry {
result[11] = self.attributes.as_byte();
result[22..24].copy_from_slice(&self.create_timedate.creation_time_bytes()?);
result[24..26].copy_from_slice(&self.create_timedate.creation_date_bytes()?);
result[26..28].copy_from_slice(&(self.start_cluster as u16).to_le_bytes());
result[26..28].copy_from_slice(&self.start_cluster_as_bytes());
result[28..32].copy_from_slice(&self.file_size.to_le_bytes());
Ok(result)
}
@ -230,10 +229,10 @@ impl DirEntry {
filename,
attributes: Attributes::from_byte(bytes[11]),
create_timedate: TimeDate::from_bytes(&datetime_bytes)?,
start_cluster: 2,
file_size: 0,
entry_type: DirEntryType::File, // This is just as wrong as the rest of this function.
children: Vec::new(),
allocated_clusters: Vec::new(),
data: Vec::new(),
};
Ok(new_direntry)
@ -307,7 +306,7 @@ impl DirEntry {
/// assert_eq!(bytes, [0x32, 0x04]); // Assuming the start cluster number is 1234
/// ```
pub fn start_cluster_as_bytes(&self) -> [u8; 2] {
let cluster: u16 = min(self.start_cluster, 65535) as u16;
let cluster: u16 = min(self.allocated_clusters[0], 65535) as u16;
cluster.to_le_bytes()
}
@ -333,6 +332,15 @@ impl DirEntry {
self.file_size.to_le_bytes()
}
/// The start cluster is either 0, or the first entry in the allocated clusters vec.
pub fn get_start_cluster(&self) -> u32 {
if self.allocated_clusters.len() == 0 {
return 0;
} else {
self.allocated_clusters[0]
}
}
pub fn get_children(&mut self) -> &Vec<DirEntry> {
&mut self.children
}
@ -345,10 +353,6 @@ impl DirEntry {
self.children.len()
}
pub fn get_start_cluster(&self) -> u32 {
self.start_cluster
}
pub fn directory_name(&self) -> String {
self.filename.base_as_string()
}
@ -357,8 +361,8 @@ impl DirEntry {
self.entry_type
}
pub fn set_start_cluster(&mut self, cluster: u32) {
self.start_cluster = cluster;
pub fn get_data(&self) -> &[u8] {
&self.data.as_slice()
}
}
@ -380,10 +384,10 @@ mod tests {
filename: FileName::new("ibmbio.com").unwrap(),
attributes: Attributes::default(),
create_timedate: creation_time,
start_cluster: 2,
file_size: 4608,
entry_type: DirEntryType::File, // Dummy placeholder
children: Vec::new(),
allocated_clusters: vec![2,3,4],
data: Vec::new(),
};
// Semi-representative entry that reflects IBMBIO.COM on April 4 1983.

View File

@ -37,11 +37,17 @@ fn main() {
let cli = Cli::parse();
match &cli.command {
Commands::Build { name } => {
// Load the manifest struct from a YAML file
let manifest = manifest::Manifest::load(name.as_str()).unwrap();
// Display the application header
print_header();
// Display some misc metadata from the manifest we got
println!("{}", manifest.get_metadata());
println!("{}", manifest);
DiskBuilder::build(&manifest).expect("Failed building the disk.");
// Construct a DiskBuilder struct from the manifest.
let mut builder = DiskBuilder::new(manifest).expect("Failed to create a DiskBuilder for this manifest.");
// If that worked, we're ready to do some actual building.
builder.build().expect("Failed to build the disk image.");
}
Commands::Analyze { name } => {
print_header();

View File

@ -21,12 +21,27 @@ impl fmt::Display for GameMetadata {
}
#[derive(Debug, Deserialize, Serialize)]
pub struct DistFile {
name: String,
pub struct Application {
url: String,
checksum: Option<String>,
label: Option<String>
}
impl Application {
pub fn get_url(&self) -> String {
self.url.to_owned()
}
pub fn get_label(&self) -> String {
if self.label.is_some() {
return self.label.clone().unwrap();
}
else {
return self.url.to_owned();
}
}
}
#[derive(Debug, Deserialize, Serialize)]
struct GameConfig {
autoexec: String,