mirror of
https://github.com/holub/mame
synced 2025-04-16 13:34:55 +03:00
Initial FAT file system support for Floptool (#9119)
* Initial FAT file system support for Floptool Current Limitations: - Read only - Only supports floppy disks - No FAT32 support - No Long Filenames Support
This commit is contained in:
parent
9558786f3b
commit
695a9eb091
@ -2136,6 +2136,18 @@ if opt_tool(FORMATS, "FS_PRODOS") then
|
||||
}
|
||||
end
|
||||
|
||||
--------------------------------------------------
|
||||
--
|
||||
--@src/lib/formats/fs_fat.h,FORMATS["FS_FAT"] = true
|
||||
--------------------------------------------------
|
||||
|
||||
if opt_tool(FORMATS, "FS_FAT") then
|
||||
files {
|
||||
MAME_DIR.. "src/lib/formats/fs_fat.cpp",
|
||||
MAME_DIR.. "src/lib/formats/fs_fat.h",
|
||||
}
|
||||
end
|
||||
|
||||
--------------------------------------------------
|
||||
--
|
||||
--@src/lib/formats/fs_oric_jasmin.h,FORMATS["FS_ORIC_JASMIN"] = true
|
||||
|
@ -708,6 +708,10 @@
|
||||
#include "fs_prodos.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAS_FORMATS_FS_FAT
|
||||
#include "fs_fat.h"
|
||||
#endif
|
||||
|
||||
void mame_formats_full_list(mame_formats_enumerator &en)
|
||||
{
|
||||
en.category("Generic");
|
||||
@ -718,6 +722,9 @@ void mame_formats_full_list(mame_formats_enumerator &en)
|
||||
#endif
|
||||
en.add(FLOPPY_MFI_FORMAT); // mfi_dsk.h
|
||||
en.add(FLOPPY_DFI_FORMAT); // dfi_dsk.h
|
||||
#ifdef HAS_FORMATS_FS_FAT
|
||||
en.add(fs::PC_FAT);
|
||||
#endif
|
||||
|
||||
en.category("Container FM/MFM");
|
||||
en.add(FLOPPY_HFE_FORMAT); // hxchfe_dsk.h
|
||||
|
828
src/lib/formats/fs_fat.cpp
Normal file
828
src/lib/formats/fs_fat.cpp
Normal file
@ -0,0 +1,828 @@
|
||||
// license:BSD-3-Clause
|
||||
// copyright-holders:Nathan Woods
|
||||
/***************************************************************************
|
||||
|
||||
fs_fat.cpp
|
||||
|
||||
PC FAT disk images
|
||||
|
||||
Current Limitations:
|
||||
- Read only
|
||||
- Only supports floppy disks
|
||||
- No FAT32 support
|
||||
- No Long Filenames Support
|
||||
|
||||
*****************************************************************************
|
||||
|
||||
Master boot record format:
|
||||
|
||||
Offset Length Description
|
||||
------ ------ -----------
|
||||
0 446 Boot machine code
|
||||
446 16 Partion #1 info
|
||||
462 16 Partion #2 info
|
||||
478 16 Partion #3 info
|
||||
494 16 Partion #4 info
|
||||
510 2 Magic bytes (0x55 0xAA)
|
||||
|
||||
|
||||
Partition info format:
|
||||
|
||||
Offset Length Description
|
||||
------ ------ -----------
|
||||
0 1 Active byte (0x80=active 0x00=inactive)
|
||||
1 1 Starting head
|
||||
2 1 Starting sector (bits 5-0) and high bits of starting track (bits 6-5)
|
||||
3 1 Low bits of starting track
|
||||
4 1 Partition type:
|
||||
0x00 Unused
|
||||
0x?1 FAT12 (0-15 MB)
|
||||
0x?2 XENIX
|
||||
0x?4 FAT16 (16-32 MB)
|
||||
0x?6 FAT16` (32 MB-2 GB)
|
||||
0x?7 HPFS or NTFS
|
||||
0x?A Boot Manager
|
||||
0x?B FAT32 (512 MB-2 TB)
|
||||
0x?C FAT32 (512 MB-2 TB LBA)
|
||||
0x1? OS/2 Boot manager/Win95 hidden
|
||||
0xC? DR-DOS secured partition
|
||||
0xD? Multiuser DOS secured partition
|
||||
0xE? SpeedStor extended partition
|
||||
5 1 Ending head
|
||||
6 1 Ending sector (bits 5-0) and high bits of ending track (bits 6-5)
|
||||
7 1 Low bits of ending track
|
||||
8 4 Sector index of beginning of partition
|
||||
12 4 Total sectors in partition
|
||||
|
||||
|
||||
Boot sector format:
|
||||
|
||||
Offset Length Description
|
||||
------ ------ -----------
|
||||
0 3 Jump instruction (to skip over header on boot)
|
||||
3 8 OEM Name
|
||||
11 2 Bytes per sector
|
||||
13 1 Sectors per cluster
|
||||
14 2 Reserved sector count (including boot sector)
|
||||
16 1 Number of FATs (file allocation tables)
|
||||
17 2 Number of root directory entries
|
||||
19 2 Total sectors (bits 0-15)
|
||||
21 1 Media descriptor
|
||||
22 2 Sectors per FAT
|
||||
24 2 Sectors per track
|
||||
26 2 Number of heads
|
||||
28 4 Hidden sectors
|
||||
32 4 Total sectors (bits 16-47)
|
||||
36 1 Physical drive number
|
||||
37 1 Current head
|
||||
38 1 Signature
|
||||
39 4 ID
|
||||
43 11 Volume Label
|
||||
54 8 FAT file system type
|
||||
62 448 Boot machine code
|
||||
510 2 Magic bytes (0x55 0xAA)
|
||||
|
||||
For more information:
|
||||
http://support.microsoft.com/kb/q140418/
|
||||
|
||||
|
||||
Directory Entry Format:
|
||||
|
||||
Offset Length Description
|
||||
------ ------ -----------
|
||||
0 8 DOS File Name (padded with spaces)
|
||||
8 3 DOS File Extension (padded with spaces)
|
||||
11 1 File Attributes
|
||||
12 2 Unknown
|
||||
14 4 Time of Creation
|
||||
18 2 Last Access Time
|
||||
20 2 EA-Index (OS/2 stuff)
|
||||
22 4 Last Modified Time
|
||||
26 2 First Cluster
|
||||
28 4 File Size
|
||||
|
||||
|
||||
Dates and times are stored in separate words; when together, the time is
|
||||
first and the date is second.
|
||||
|
||||
Time:
|
||||
bits 15-11 Hour
|
||||
bits 10- 5 Minute
|
||||
bits 4- 0 Second / 2
|
||||
|
||||
Date:
|
||||
bits 15- 9 Year - 1980
|
||||
bits 8- 5 Month
|
||||
bits 4- 0 Day
|
||||
|
||||
LFN Entry Format:
|
||||
|
||||
Offset Length Description
|
||||
------ ------ -----------
|
||||
0 1 Sequence Number (bit 6 is set on highest sequence)
|
||||
1 10 Name characters (five UTF-16LE chars)
|
||||
11 1 Attributes (always 0x0F)
|
||||
12 1 Reserved (always 0x00)
|
||||
13 1 Checksum of short filename entry
|
||||
14 12 Name characters (six UTF-16LE chars)
|
||||
26 2 Entry Cluster (always 0x00)
|
||||
28 4 Name characters (two UTF-16LE chars)
|
||||
|
||||
Valid characters in DOS file names:
|
||||
- Upper case letters A-Z
|
||||
- Numbers 0-9
|
||||
- Space (though there is no way to identify a trailing space)
|
||||
- ! # $ % & ( ) - @ ^ _ ` { } ~
|
||||
- Characters 128-255 (though the code page is indeterminate)
|
||||
|
||||
For more information:
|
||||
http://en.wikipedia.org/wiki/File_Allocation_Table
|
||||
|
||||
****************************************************************************/
|
||||
|
||||
#include "fs_fat.h"
|
||||
#include "pc_dsk.h"
|
||||
#include "strformat.h"
|
||||
#include "util/corestr.h"
|
||||
#include "util/strformat.h"
|
||||
|
||||
using namespace fs;
|
||||
|
||||
const fs::pc_fat_image fs::PC_FAT;
|
||||
|
||||
//**************************************************************************
|
||||
// TYPE DECLARATIONS
|
||||
//**************************************************************************
|
||||
|
||||
namespace {
|
||||
|
||||
// ======================> directory_entry
|
||||
|
||||
class directory_entry
|
||||
{
|
||||
public:
|
||||
static const int SIZE = 32;
|
||||
|
||||
directory_entry(const fsblk_t::block_t &block, u32 offset)
|
||||
: m_block(block)
|
||||
, m_offset(offset)
|
||||
{
|
||||
}
|
||||
|
||||
std::string_view raw_stem() const { return std::string_view((const char *) &m_block.rodata()[m_offset + 0], 8); }
|
||||
std::string_view raw_ext() const { return std::string_view((const char *) &m_block.rodata()[m_offset + 8], 3); }
|
||||
u8 attributes() const { return m_block.r8(m_offset + 11); }
|
||||
u32 raw_create_datetime() const { return m_block.r32l(m_offset + 14); }
|
||||
u32 raw_modified_datetime() const { return m_block.r32l(m_offset + 22); }
|
||||
u32 start_cluster() const { return ((u32)m_block.r16l(m_offset + 20)) << 16 | m_block.r16l(m_offset + 26); }
|
||||
u32 file_size() const { return m_block.r32l(m_offset + 28); }
|
||||
|
||||
bool is_read_only() const { return (attributes() & 0x01) != 0x00; }
|
||||
bool is_hidden() const { return (attributes() & 0x02) != 0x00; }
|
||||
bool is_system() const { return (attributes() & 0x04) != 0x00; }
|
||||
bool is_volume_label() const { return (attributes() & 0x08) != 0x00; }
|
||||
bool is_subdirectory() const { return (attributes() & 0x10) != 0x00; }
|
||||
bool is_archive() const { return (attributes() & 0x20) != 0x00; }
|
||||
|
||||
std::string name() const;
|
||||
meta_data metadata() const;
|
||||
|
||||
private:
|
||||
fsblk_t::block_t m_block;
|
||||
u32 m_offset;
|
||||
};
|
||||
|
||||
|
||||
// ======================> directory_span
|
||||
|
||||
class directory_span
|
||||
{
|
||||
public:
|
||||
typedef std::unique_ptr<directory_span> ptr;
|
||||
|
||||
directory_span() = default;
|
||||
virtual ~directory_span() = default;
|
||||
|
||||
virtual std::vector<u32> get_directory_sectors() const = 0;
|
||||
};
|
||||
|
||||
|
||||
// ======================> directory_entry
|
||||
|
||||
class impl : public filesystem_t
|
||||
{
|
||||
public:
|
||||
// ctor/dtor
|
||||
impl(fsblk_t &blockdev, fsblk_t::block_t &&boot_sector_block, std::vector<u8> &&file_allocation_table, u32 starting_sector, u32 sector_count, u16 reserved_sector_count, u8 bits_per_fat_entry);
|
||||
virtual ~impl() = default;
|
||||
|
||||
// accessors
|
||||
fsblk_t &blockdev() { return m_blockdev; }
|
||||
u16 bytes_per_sector() const { return m_bytes_per_sector; }
|
||||
u32 dirents_per_sector() const { return bytes_per_sector() / directory_entry::SIZE; }
|
||||
|
||||
// virtuals
|
||||
virtual meta_data volume_metadata() override;
|
||||
virtual std::pair<err_t, meta_data> metadata(const std::vector<std::string> &path) override;
|
||||
virtual std::pair<err_t, std::vector<dir_entry>> directory_contents(const std::vector<std::string> &path) override;
|
||||
virtual std::pair<err_t, std::vector<u8>> file_read(const std::vector<std::string> &path) override;
|
||||
|
||||
// methods
|
||||
std::vector<u32> get_sectors_from_fat(const directory_entry &dirent) const;
|
||||
|
||||
private:
|
||||
fsblk_t::block_t m_boot_sector_block;
|
||||
std::vector<u8> m_file_allocation_table;
|
||||
u32 m_starting_sector;
|
||||
u32 m_sector_count;
|
||||
u16 m_reserved_sector_count;
|
||||
u16 m_bytes_per_sector;
|
||||
u8 m_bits_per_fat_entry;
|
||||
|
||||
// methods
|
||||
std::optional<directory_entry> find_entity(const std::vector<std::string> &path) const;
|
||||
directory_span::ptr find_directory(std::vector<std::string>::const_iterator path_begin, std::vector<std::string>::const_iterator path_end) const;
|
||||
std::optional<directory_entry> find_child(const directory_span ¤t_dir, std::string_view target) const;
|
||||
void iterate_directory_entries(const directory_span &dir, const std::function<bool(const directory_entry &dirent)> &callback) const;
|
||||
};
|
||||
|
||||
|
||||
// ======================> root_directory_span
|
||||
|
||||
class root_directory_span : public directory_span
|
||||
{
|
||||
public:
|
||||
root_directory_span(const impl &fs, u32 first_sector, u16 directory_entry_count);
|
||||
|
||||
virtual std::vector<u32> get_directory_sectors() const override;
|
||||
|
||||
private:
|
||||
const impl & m_fs;
|
||||
u32 m_first_sector;
|
||||
u16 m_directory_entry_count;
|
||||
};
|
||||
|
||||
|
||||
// ======================> subdirectory_span
|
||||
|
||||
class subdirectory_span : public directory_span
|
||||
{
|
||||
public:
|
||||
subdirectory_span(const impl &fs, directory_entry &&dirent);
|
||||
|
||||
virtual std::vector<u32> get_directory_sectors() const override;
|
||||
|
||||
private:
|
||||
const impl & m_fs;
|
||||
directory_entry m_dirent;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
//**************************************************************************
|
||||
// IMPLEMENTATION
|
||||
//**************************************************************************
|
||||
|
||||
//-------------------------------------------------
|
||||
// validate_filename
|
||||
//-------------------------------------------------
|
||||
|
||||
namespace {
|
||||
bool validate_filename(std::string_view name)
|
||||
{
|
||||
auto is_invalid_filename_char = [](char ch)
|
||||
{
|
||||
return ch == '\0' || strchr("\\/:*?\"<>|", ch);
|
||||
};
|
||||
|
||||
return !name.empty()
|
||||
&& std::find_if(name.begin(), name.end(), is_invalid_filename_char) == name.end();
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// decode_fat_datetime
|
||||
//-------------------------------------------------
|
||||
|
||||
util::arbitrary_datetime decode_fat_datetime(u32 dt)
|
||||
{
|
||||
util::arbitrary_datetime result;
|
||||
memset(&result, 0, sizeof(result));
|
||||
|
||||
result.year = ((dt >> 25) & 0x7F) + 1980;
|
||||
result.month = (dt >> 21) & 0x0F;
|
||||
result.day_of_month = (dt >> 16) & 0x1F;
|
||||
result.hour = (dt >> 11) & 0x1F;
|
||||
result.minute = (dt >> 5) & 0x3F;
|
||||
result.second = ((dt >> 0) & 0x1F) * 2;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//-------------------------------------------------
|
||||
// fat_image::can_format
|
||||
//-------------------------------------------------
|
||||
|
||||
bool fs::fat_image::can_format() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// fat_image::can_read
|
||||
//-------------------------------------------------
|
||||
|
||||
bool fs::fat_image::can_read() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// fat_image::can_write
|
||||
//-------------------------------------------------
|
||||
|
||||
bool fs::fat_image::can_write() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// fat_image::has_rsrc
|
||||
//-------------------------------------------------
|
||||
|
||||
bool fs::fat_image::has_rsrc() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// fat_image::directory_separator
|
||||
//-------------------------------------------------
|
||||
|
||||
char fs::fat_image::directory_separator() const
|
||||
{
|
||||
return '\\';
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// fat_image::volume_meta_description
|
||||
//-------------------------------------------------
|
||||
|
||||
std::vector<meta_description> fs::fat_image::volume_meta_description() const
|
||||
{
|
||||
std::vector<meta_description> results;
|
||||
results.emplace_back(meta_name::name, "UNTITLED", false, [](const meta_value &m) { return m.as_string().size() <= 11; }, "Volume name, up to 11 characters");
|
||||
results.emplace_back(meta_name::oem_name, "", false, [](const meta_value &m) { return m.as_string().size() <= 8; }, "OEM name, up to 8 characters");
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// fat_image::file_meta_description
|
||||
//-------------------------------------------------
|
||||
|
||||
std::vector<meta_description> fs::fat_image::file_meta_description() const
|
||||
{
|
||||
std::vector<meta_description> results;
|
||||
results.emplace_back(meta_name::name, "", false, [](const meta_value &m) { return validate_filename(m.as_string()); }, "File name");
|
||||
results.emplace_back(meta_name::creation_date, util::arbitrary_datetime::now(), false, nullptr, "Creation time");
|
||||
results.emplace_back(meta_name::modification_date, util::arbitrary_datetime::now(), false, nullptr, "Modification time");
|
||||
results.emplace_back(meta_name::length, 0, true, nullptr, "Size of the file in bytes");
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// fat_image::directory_meta_description
|
||||
//-------------------------------------------------
|
||||
|
||||
std::vector<meta_description> fs::fat_image::directory_meta_description() const
|
||||
{
|
||||
std::vector<meta_description> results;
|
||||
results.emplace_back(meta_name::name, "", false, [](const meta_value &m) { return validate_filename(m.as_string()); }, "File name");
|
||||
results.emplace_back(meta_name::creation_date, util::arbitrary_datetime::now(), false, nullptr, "Creation time");
|
||||
results.emplace_back(meta_name::modification_date, util::arbitrary_datetime::now(), false, nullptr, "Modification time");
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// fat_image::mount_partition
|
||||
//-------------------------------------------------
|
||||
|
||||
std::unique_ptr<filesystem_t> fs::fat_image::mount_partition(fsblk_t &blockdev, u32 starting_sector, u32 sector_count, u8 bits_per_fat_entry)
|
||||
{
|
||||
// load the boot sector block and get some basic info
|
||||
fsblk_t::block_t boot_sector_block = blockdev.get(starting_sector);
|
||||
u16 reserved_sector_count = boot_sector_block.r16l(14);
|
||||
|
||||
// load all file allocation table sectors
|
||||
u32 fat_count = boot_sector_block.r8(16);
|
||||
u32 sectors_per_fat = boot_sector_block.r16l(22);
|
||||
u16 bytes_per_sector = boot_sector_block.r16l(11);
|
||||
std::vector<u8> file_allocation_table;
|
||||
file_allocation_table.reserve(fat_count * sectors_per_fat * bytes_per_sector);
|
||||
for (auto i = 0; i < fat_count * sectors_per_fat; i++)
|
||||
{
|
||||
fsblk_t::block_t fatblk = blockdev.get(starting_sector + reserved_sector_count + i);
|
||||
file_allocation_table.insert(file_allocation_table.end(), fatblk.rodata(), fatblk.rodata() + bytes_per_sector);
|
||||
}
|
||||
|
||||
// and return the implementation
|
||||
return std::make_unique<impl>(blockdev, std::move(boot_sector_block), std::move(file_allocation_table),
|
||||
starting_sector, sector_count, reserved_sector_count, bits_per_fat_entry);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// directory_entry::name
|
||||
//-------------------------------------------------
|
||||
|
||||
std::string directory_entry::name() const
|
||||
{
|
||||
std::string_view stem = strtrimrightspace(raw_stem());
|
||||
std::string_view ext = strtrimrightspace(raw_ext());
|
||||
return !ext.empty()
|
||||
? util::string_format("%s.%s", stem, ext)
|
||||
: std::string(stem);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// directory_entry::metadata
|
||||
//-------------------------------------------------
|
||||
|
||||
meta_data directory_entry::metadata() const
|
||||
{
|
||||
meta_data result;
|
||||
result.set(meta_name::name, name());
|
||||
result.set(meta_name::creation_date, decode_fat_datetime(raw_create_datetime()));
|
||||
result.set(meta_name::modification_date, decode_fat_datetime(raw_modified_datetime()));
|
||||
result.set(meta_name::length, file_size());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// impl ctor
|
||||
//-------------------------------------------------
|
||||
|
||||
impl::impl(fsblk_t &blockdev, fsblk_t::block_t &&boot_sector_block, std::vector<u8> &&file_allocation_table, u32 starting_sector, u32 sector_count, u16 reserved_sector_count, u8 bits_per_fat_entry)
|
||||
: filesystem_t(blockdev, 512)
|
||||
, m_boot_sector_block(std::move(boot_sector_block))
|
||||
, m_file_allocation_table(std::move(file_allocation_table))
|
||||
, m_starting_sector(starting_sector)
|
||||
, m_sector_count(sector_count)
|
||||
, m_reserved_sector_count(reserved_sector_count)
|
||||
, m_bytes_per_sector(boot_sector_block.r16l(11))
|
||||
, m_bits_per_fat_entry(bits_per_fat_entry)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// impl::volume_metadata
|
||||
//-------------------------------------------------
|
||||
|
||||
meta_data impl::volume_metadata()
|
||||
{
|
||||
meta_data results;
|
||||
results.set(meta_name::name, m_boot_sector_block.rstr(43, 11));
|
||||
results.set(meta_name::oem_name, m_boot_sector_block.rstr(3, 8));
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// impl::metadata
|
||||
//-------------------------------------------------
|
||||
|
||||
std::pair<err_t, meta_data> impl::metadata(const std::vector<std::string> &path)
|
||||
{
|
||||
std::optional<directory_entry> dirent = find_entity(path);
|
||||
if (!dirent)
|
||||
return std::make_pair(ERR_NOT_FOUND, meta_data());
|
||||
|
||||
return std::make_pair(ERR_OK, dirent->metadata());
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// impl::directory_contents
|
||||
//-------------------------------------------------
|
||||
|
||||
std::pair<err_t, std::vector<dir_entry>> impl::directory_contents(const std::vector<std::string> &path)
|
||||
{
|
||||
directory_span::ptr dir = find_directory(path.begin(), path.end());
|
||||
if (!dir)
|
||||
return std::make_pair(ERR_NOT_FOUND, std::vector<dir_entry>());
|
||||
|
||||
std::vector<dir_entry> results;
|
||||
auto callback = [&results](const directory_entry &dirent)
|
||||
{
|
||||
dir_entry_type entry_type = dirent.is_subdirectory() ? dir_entry_type::dir : dir_entry_type::file;
|
||||
results.emplace_back(entry_type, dirent.metadata());
|
||||
return false;
|
||||
};
|
||||
iterate_directory_entries(*dir, callback);
|
||||
return std::make_pair(ERR_OK, std::move(results));
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// impl::file_read
|
||||
//-------------------------------------------------
|
||||
|
||||
std::pair<err_t, std::vector<u8>> impl::file_read(const std::vector<std::string> &path)
|
||||
{
|
||||
// find the file
|
||||
std::optional<directory_entry> dirent = find_entity(path);
|
||||
if (!dirent || dirent->is_subdirectory())
|
||||
return std::make_pair(ERR_NOT_FOUND, std::vector<u8>());
|
||||
|
||||
// get the list of sectors for this file
|
||||
std::vector<u32> sectors = get_sectors_from_fat(*dirent);
|
||||
|
||||
// prepare the results
|
||||
std::vector<u8> result;
|
||||
result.reserve(dirent->file_size());
|
||||
|
||||
// and add data from all sectors
|
||||
for (u32 sector : sectors)
|
||||
{
|
||||
fsblk_t::block_t block = m_blockdev.get(sector);
|
||||
const u8 *data = block.rodata();
|
||||
size_t length = std::min((size_t)dirent->file_size() - result.size(), (size_t)block.size());
|
||||
result.insert(result.end(), data, data + length);
|
||||
}
|
||||
return std::make_pair(ERR_OK, std::move(result));
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// impl::get_sectors_from_fat
|
||||
//-------------------------------------------------
|
||||
|
||||
std::vector<u32> impl::get_sectors_from_fat(const directory_entry &dirent) const
|
||||
{
|
||||
// prepare results
|
||||
std::vector<u32> results;
|
||||
results.reserve(dirent.file_size() / bytes_per_sector());
|
||||
|
||||
// get critical information
|
||||
u8 sectors_per_cluster = m_boot_sector_block.r8(13);
|
||||
u16 root_directory_entry_count = m_boot_sector_block.r16l(17);
|
||||
u16 root_directory_sector_count = (root_directory_entry_count + 1) / dirents_per_sector();
|
||||
u32 fat_sector_count = (u32)(m_file_allocation_table.size() / m_bytes_per_sector);
|
||||
u32 data_starting_sector = m_starting_sector + m_reserved_sector_count + fat_sector_count + root_directory_sector_count;
|
||||
u32 data_cluster_count = (m_sector_count - data_starting_sector) / sectors_per_cluster;
|
||||
|
||||
// find all clusters
|
||||
u32 start_cluster_mask = ((u64)1 << m_bits_per_fat_entry) - 1;
|
||||
u32 cluster = dirent.start_cluster() & start_cluster_mask;
|
||||
|
||||
while (cluster >= 2 && cluster < (data_cluster_count + 2))
|
||||
{
|
||||
// add the sectors for this cluster
|
||||
for (auto i = 0; i < sectors_per_cluster; i++)
|
||||
results.push_back(data_starting_sector + (cluster - 2) * sectors_per_cluster + i);
|
||||
|
||||
// determine the bit position of this entry
|
||||
u32 entry_bit_position = cluster * m_bits_per_fat_entry;
|
||||
|
||||
// sanity check; check for overflows
|
||||
u32 new_cluster = 0;
|
||||
if (entry_bit_position + m_bits_per_fat_entry <= m_file_allocation_table.size() * 8)
|
||||
{
|
||||
// this awkward logic is here because we cannot rely on FAT entries all being in one
|
||||
// sector (thank you FAT12)
|
||||
u32 current_bit = 0;
|
||||
while (current_bit < m_bits_per_fat_entry)
|
||||
{
|
||||
u32 pos = entry_bit_position + current_bit;
|
||||
u32 shift = pos % 8;
|
||||
u32 bit_count = std::min(8 - shift, m_bits_per_fat_entry - current_bit);
|
||||
u32 bits = (m_file_allocation_table[pos / 8] >> shift) & ((1 << bit_count) - 1);
|
||||
|
||||
new_cluster |= (bits << current_bit);
|
||||
current_bit += bit_count;
|
||||
}
|
||||
|
||||
// normalize special cluster IDs
|
||||
if (new_cluster > ((u32)1 << m_bits_per_fat_entry) - 0x10)
|
||||
new_cluster |= ~(((u32)1 << m_bits_per_fat_entry) - 1);
|
||||
}
|
||||
cluster = new_cluster;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// impl::find_entity
|
||||
//-------------------------------------------------
|
||||
|
||||
std::optional<directory_entry> impl::find_entity(const std::vector<std::string> &path) const
|
||||
{
|
||||
// special case; reject empty paths
|
||||
if (path.empty())
|
||||
return { };
|
||||
|
||||
// find the containing directory
|
||||
directory_span::ptr dir = find_directory(path.begin(), path.end() - 1);
|
||||
if (!dir)
|
||||
return { };
|
||||
|
||||
// find the last child
|
||||
return find_child(*dir, path[path.size() - 1]);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// impl::find_directory
|
||||
//-------------------------------------------------
|
||||
|
||||
directory_span::ptr impl::find_directory(std::vector<std::string>::const_iterator path_begin, std::vector<std::string>::const_iterator path_end) const
|
||||
{
|
||||
// the root directory is treated differently
|
||||
u32 first_sector = m_starting_sector + m_reserved_sector_count + (u32)m_file_allocation_table.size() / m_bytes_per_sector;
|
||||
u16 directory_entry_count = m_boot_sector_block.r16l(17);
|
||||
directory_span::ptr current_dir = std::make_unique<root_directory_span>(*this, first_sector, directory_entry_count);
|
||||
|
||||
// traverse the directory
|
||||
for (auto iter = path_begin; iter != path_end; iter++)
|
||||
{
|
||||
// find the child file
|
||||
std::optional<directory_entry> child_directory = find_child(*current_dir, *iter);
|
||||
if (!child_directory)
|
||||
return { };
|
||||
|
||||
// advance into the child directory
|
||||
current_dir = std::make_unique<subdirectory_span>(*this, std::move(*child_directory));
|
||||
}
|
||||
|
||||
return current_dir;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// impl::find_child
|
||||
//-------------------------------------------------
|
||||
|
||||
std::optional<directory_entry> impl::find_child(const directory_span ¤t_dir, std::string_view target) const
|
||||
{
|
||||
std::optional<directory_entry> result;
|
||||
auto callback = [&result, target](const directory_entry &dirent)
|
||||
{
|
||||
bool found = dirent.name() == target;
|
||||
if (found)
|
||||
result = dirent;
|
||||
return found;
|
||||
};
|
||||
iterate_directory_entries(current_dir, callback);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// impl::iterate_directory_entries
|
||||
//-------------------------------------------------
|
||||
|
||||
void impl::iterate_directory_entries(const directory_span &dir, const std::function<bool(const directory_entry &dirent)> &callback) const
|
||||
{
|
||||
std::vector<u32> sectors = dir.get_directory_sectors();
|
||||
for (u32 sector : sectors)
|
||||
{
|
||||
bool done = false;
|
||||
fsblk_t::block_t block = m_blockdev.get(sector);
|
||||
for (u32 index = 0; !done && (index < dirents_per_sector()); index++)
|
||||
{
|
||||
directory_entry dirent(block, index * 32);
|
||||
if (dirent.raw_stem()[0] != 0x00)
|
||||
{
|
||||
// get the filename
|
||||
std::string_view stem = strtrimrightspace(dirent.raw_stem());
|
||||
std::string_view ext = strtrimrightspace(dirent.raw_ext());
|
||||
if (ext.empty() && (stem == "." || stem == ".."))
|
||||
continue;
|
||||
|
||||
// invoke the callback
|
||||
done = callback(dirent);
|
||||
}
|
||||
}
|
||||
|
||||
if (done)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// root_directory_span ctor
|
||||
//-------------------------------------------------
|
||||
|
||||
root_directory_span::root_directory_span(const impl &fs, u32 first_sector, u16 directory_entry_count)
|
||||
: m_fs(fs)
|
||||
, m_first_sector(first_sector)
|
||||
, m_directory_entry_count(directory_entry_count)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// root_directory_span::get_directory_sectors
|
||||
//-------------------------------------------------
|
||||
|
||||
std::vector<u32> root_directory_span::get_directory_sectors() const
|
||||
{
|
||||
u32 directory_sector_count = (m_directory_entry_count + m_fs.dirents_per_sector() - 1) / m_fs.dirents_per_sector();
|
||||
|
||||
std::vector<u32> result;
|
||||
result.reserve(directory_sector_count);
|
||||
for (auto i = 0; i < directory_sector_count; i++)
|
||||
result.push_back(m_first_sector + i);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// subdirectory_span ctor
|
||||
//-------------------------------------------------
|
||||
|
||||
subdirectory_span::subdirectory_span(const impl &fs, directory_entry &&dirent)
|
||||
: m_fs(fs)
|
||||
, m_dirent(std::move(dirent))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// subdirectory_span::get_directory_sectors
|
||||
//-------------------------------------------------
|
||||
|
||||
std::vector<u32> subdirectory_span::get_directory_sectors() const
|
||||
{
|
||||
return m_fs.get_sectors_from_fat(m_dirent);
|
||||
}
|
||||
|
||||
|
||||
//**************************************************************************
|
||||
// PC FAT SPECIFIC
|
||||
//**************************************************************************
|
||||
|
||||
//-------------------------------------------------
|
||||
// pc_fat_image::name
|
||||
//-------------------------------------------------
|
||||
|
||||
const char *fs::pc_fat_image::name() const
|
||||
{
|
||||
return "pc_fat";
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// pc_fat_image::description
|
||||
//-------------------------------------------------
|
||||
|
||||
const char *fs::pc_fat_image::description() const
|
||||
{
|
||||
return "PC FAT";
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// pc_fat_image::enumerate_f
|
||||
//-------------------------------------------------
|
||||
|
||||
void pc_fat_image::enumerate_f(floppy_enumerator &fe, u32 form_factor, const std::vector<u32> &variants) const
|
||||
{
|
||||
if (has(form_factor, variants, floppy_image::FF_35, floppy_image::DSSD))
|
||||
fe.add(FLOPPY_PC_FORMAT, 368640, "pc_fat_dssd", "PC FAT 3.5\" dual-sided single density");
|
||||
if (has(form_factor, variants, floppy_image::FF_35, floppy_image::DSDD))
|
||||
fe.add(FLOPPY_PC_FORMAT, 737280, "pc_fat_dsdd", "PC FAT 3.5\" dual-sided double density");
|
||||
if (has(form_factor, variants, floppy_image::FF_35, floppy_image::DSHD))
|
||||
fe.add(FLOPPY_PC_FORMAT, 1474560, "pc_fat_dshd", "PC FAT 3.5\" dual-sided high density");
|
||||
if (has(form_factor, variants, floppy_image::FF_35, floppy_image::DSED))
|
||||
fe.add(FLOPPY_PC_FORMAT, 2949120, "pc_fat_dsed", "PC FAT 3.5\" dual-sided extra density");
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// pc_fat_image::mount
|
||||
//-------------------------------------------------
|
||||
|
||||
std::unique_ptr<filesystem_t> pc_fat_image::mount(fsblk_t &blockdev) const
|
||||
{
|
||||
blockdev.set_block_size(512);
|
||||
return mount_partition(blockdev, 0, blockdev.block_count(), 12);
|
||||
}
|
60
src/lib/formats/fs_fat.h
Normal file
60
src/lib/formats/fs_fat.h
Normal file
@ -0,0 +1,60 @@
|
||||
// license:BSD-3-Clause
|
||||
// copyright-holders:Nathan Woods
|
||||
/***************************************************************************
|
||||
|
||||
fs_fat.h
|
||||
|
||||
PC FAT disk images
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef MAME_FORMATS_FS_FAT_H
|
||||
#define MAME_FORMATS_FS_FAT_H
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fsmgr.h"
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
namespace fs {
|
||||
|
||||
// ======================> fat_image
|
||||
|
||||
class fat_image : public manager_t {
|
||||
public:
|
||||
fat_image() = default;
|
||||
|
||||
virtual bool can_format() const override;
|
||||
virtual bool can_read() const override;
|
||||
virtual bool can_write() const override;
|
||||
virtual bool has_rsrc() const override;
|
||||
virtual char directory_separator() const override;
|
||||
|
||||
virtual std::vector<meta_description> volume_meta_description() const override;
|
||||
virtual std::vector<meta_description> file_meta_description() const override;
|
||||
virtual std::vector<meta_description> directory_meta_description() const override;
|
||||
|
||||
protected:
|
||||
static std::unique_ptr<filesystem_t> mount_partition(fsblk_t &blockdev, u32 starting_sector, u32 sector_count, u8 bits_per_fat_entry);
|
||||
};
|
||||
|
||||
|
||||
// ======================> fat_image
|
||||
|
||||
class pc_fat_image : public fat_image {
|
||||
public:
|
||||
pc_fat_image() = default;
|
||||
|
||||
virtual const char *name() const override;
|
||||
virtual const char *description() const override;
|
||||
virtual void enumerate_f(floppy_enumerator &fe, u32 form_factor, const std::vector<u32> &variants) const override;
|
||||
virtual std::unique_ptr<filesystem_t> mount(fsblk_t &blockdev) const override;
|
||||
};
|
||||
|
||||
|
||||
extern const pc_fat_image PC_FAT;
|
||||
|
||||
} // namespace fs
|
||||
|
||||
#endif // MAME_FORMATS_FS_FAT_H
|
@ -30,6 +30,7 @@ const char *meta_data::entry_name(meta_name name)
|
||||
case meta_name::ascii_flag: return "ascii_flag";
|
||||
case meta_name::owner_id: return "owner_id";
|
||||
case meta_name::attributes: return "attributes";
|
||||
case meta_name::oem_name: return "oem_name";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
@ -35,7 +35,8 @@ enum class meta_name {
|
||||
file_type,
|
||||
ascii_flag,
|
||||
owner_id,
|
||||
attributes
|
||||
attributes,
|
||||
oem_name
|
||||
};
|
||||
|
||||
enum class meta_type {
|
||||
|
Loading…
Reference in New Issue
Block a user