Files
romrepomgr/RomRepoMgr.Core/Aaru/FAT.cs

252 lines
9.2 KiB
C#
Raw Normal View History

// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Info.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Microsoft FAT filesystem plugin.
//
// --[ Description ] ----------------------------------------------------------
//
// Identifies the Microsoft FAT filesystem and shows information.
//
// --[ License ] --------------------------------------------------------------
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
2024-11-08 19:13:57 +00:00
// Copyright © 2011-2024 Natalia Portillo
// ****************************************************************************/
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text;
2024-11-09 01:37:59 +00:00
namespace RomRepoMgr.Core.Aaru;
// TODO: This should be taken from Aaru as a nuget package in the future
public static class FAT
{
2024-11-09 01:37:59 +00:00
static int CountBits(uint number)
{
2024-11-09 01:37:59 +00:00
number -= number >> 1 & 0x55555555;
number = (number & 0x33333333) + (number >> 2 & 0x33333333);
2024-11-09 01:37:59 +00:00
return (int)((number + (number >> 4) & 0x0F0F0F0F) * 0x01010101 >> 24);
}
2024-11-09 01:37:59 +00:00
public static bool Identify(string path)
{
try
{
2024-11-09 01:37:59 +00:00
return Identify(new FileStream(path, FileMode.Open, FileAccess.Read));
}
2024-11-09 01:37:59 +00:00
catch(Exception e)
{
2024-11-09 01:37:59 +00:00
return false;
}
}
2024-11-09 01:37:59 +00:00
[SuppressMessage("ReSharper", "JoinDeclarationAndInitializer")]
static bool Identify(Stream imageStream)
{
ushort bps;
byte spc;
byte numberOfFats;
ushort reservedSecs;
ushort rootEntries;
ushort sectors;
ushort fatSectors;
uint bigSectors;
byte bpbSignature;
byte fat32Signature;
ulong hugeSectors;
var fat32Id = new byte[8];
var msxId = new byte[6];
var dosOem = new byte[8];
var atariOem = new byte[6];
ushort bootable = 0;
var bpbSector = new byte[512];
var fatSector = new byte[512];
imageStream.Position = 0;
imageStream.EnsureRead(bpbSector, 0, 512);
imageStream.EnsureRead(fatSector, 0, 512);
2024-11-09 01:37:59 +00:00
Array.Copy(bpbSector, 0x02, atariOem, 0, 6);
Array.Copy(bpbSector, 0x03, dosOem, 0, 8);
bps = BitConverter.ToUInt16(bpbSector, 0x00B);
spc = bpbSector[0x00D];
reservedSecs = BitConverter.ToUInt16(bpbSector, 0x00E);
numberOfFats = bpbSector[0x010];
rootEntries = BitConverter.ToUInt16(bpbSector, 0x011);
sectors = BitConverter.ToUInt16(bpbSector, 0x013);
fatSectors = BitConverter.ToUInt16(bpbSector, 0x016);
Array.Copy(bpbSector, 0x052, msxId, 0, 6);
bigSectors = BitConverter.ToUInt32(bpbSector, 0x020);
bpbSignature = bpbSector[0x026];
fat32Signature = bpbSector[0x042];
Array.Copy(bpbSector, 0x052, fat32Id, 0, 8);
hugeSectors = BitConverter.ToUInt64(bpbSector, 0x052);
int bitsInBps = CountBits(bps);
bootable = BitConverter.ToUInt16(bpbSector, 0x1FE);
2024-11-12 06:49:31 +00:00
bool correctSpc = spc is 1 or 2 or 4 or 8 or 16 or 32 or 64;
2024-11-09 01:37:59 +00:00
string msxString = Encoding.ASCII.GetString(msxId);
string fat32String = Encoding.ASCII.GetString(fat32Id);
string oemString = Encoding.ASCII.GetString(dosOem);
var apricotBps = BitConverter.ToUInt16(bpbSector, 0x50);
byte apricotSpc = bpbSector[0x52];
var apricotReservedSecs = BitConverter.ToUInt16(bpbSector, 0x53);
byte apricotFatsNo = bpbSector[0x55];
var apricotRootEntries = BitConverter.ToUInt16(bpbSector, 0x56);
var apricotSectors = BitConverter.ToUInt16(bpbSector, 0x58);
var apricotFatSectors = BitConverter.ToUInt16(bpbSector, 0x5B);
2024-11-12 06:49:31 +00:00
bool apricotCorrectSpc = apricotSpc is 1 or 2 or 4 or 8 or 16 or 32 or 64;
2024-11-09 01:37:59 +00:00
int bitsInApricotBps = CountBits(apricotBps);
byte apricotPartitions = bpbSector[0x0C];
switch(oemString)
{
// exFAT
case "EXFAT ":
2024-11-09 01:37:59 +00:00
// NTFS
case "NTFS " when bootable == 0xAA55 && numberOfFats == 0 && fatSectors == 0:
2024-11-09 01:37:59 +00:00
// QNX4
case "FQNX4FS ":
return false;
}
2024-11-09 01:37:59 +00:00
ulong imageSectors = (ulong)imageStream.Length / 512;
2024-11-09 01:37:59 +00:00
switch(bitsInBps)
{
// FAT32 for sure
case 1 when correctSpc &&
numberOfFats <= 2 &&
sectors == 0 &&
fatSectors == 0 &&
fat32Signature == 0x29 &&
fat32String == "FAT32 ":
return true;
2024-11-09 01:37:59 +00:00
// short FAT32
case 1 when correctSpc && numberOfFats <= 2 && sectors == 0 && fatSectors == 0 && fat32Signature == 0x28:
return bigSectors == 0 ? hugeSectors <= imageSectors : bigSectors <= imageSectors;
// MSX-DOS FAT12
case 1 when correctSpc &&
numberOfFats <= 2 &&
rootEntries > 0 &&
sectors <= imageSectors &&
fatSectors > 0 &&
msxString == "VOL_ID":
return true;
2024-11-09 01:37:59 +00:00
// EBPB
case 1 when correctSpc &&
numberOfFats <= 2 &&
rootEntries > 0 &&
fatSectors > 0 &&
2024-11-12 06:49:31 +00:00
bpbSignature is 0x28 or 0x29:
2024-11-09 01:37:59 +00:00
// BPB
case 1 when correctSpc &&
reservedSecs < imageSectors - 1 &&
numberOfFats <= 2 &&
rootEntries > 0 &&
fatSectors > 0:
return sectors == 0 ? bigSectors <= imageSectors : sectors <= imageSectors;
}
2024-11-09 01:37:59 +00:00
// Apricot BPB
if(bitsInApricotBps == 1 &&
apricotCorrectSpc &&
apricotReservedSecs < imageSectors - 1 &&
apricotFatsNo <= 2 &&
apricotRootEntries > 0 &&
apricotFatSectors > 0 &&
apricotSectors <= imageSectors &&
apricotPartitions == 0)
return true;
// DEC Rainbow, lacks a BPB but has a very concrete structure...
if(imageSectors != 800) return false;
// DEC Rainbow boots up with a Z80, first byte should be DI (disable interrupts)
byte z80Di = bpbSector[0];
// First FAT1 sector resides at LBA 0x14
var fat1Sector0 = new byte[512];
imageStream.Position = 0x14 * 512;
imageStream.EnsureRead(fat1Sector0, 0, 512);
2024-11-09 01:37:59 +00:00
// First FAT2 sector resides at LBA 0x1A
var fat2Sector0 = new byte[512];
imageStream.Position = 0x1A * 512;
imageStream.EnsureRead(fat2Sector0, 0, 512);
2024-11-09 01:37:59 +00:00
bool equalFatIds = fat1Sector0[0] == fat2Sector0[0] && fat1Sector0[1] == fat2Sector0[1];
// Volume is software interleaved 2:1
var rootMs = new MemoryStream();
var tmp = new byte[512];
foreach(long position in new long[]
{
0x17, 0x19, 0x1B, 0x1D, 0x1E, 0x20
})
{
imageStream.Position = position * 512;
imageStream.EnsureRead(tmp, 0, 512);
2024-11-09 01:37:59 +00:00
rootMs.Write(tmp, 0, tmp.Length);
}
2024-11-09 01:37:59 +00:00
byte[] rootDir = rootMs.ToArray();
var validRootDir = true;
2024-11-09 01:37:59 +00:00
// Iterate all root directory
for(var e = 0; e < 96 * 32; e += 32)
{
for(var c = 0; c < 11; c++)
{
if((rootDir[c + e] >= 0x20 || rootDir[c + e] == 0x00 || rootDir[c + e] == 0x05) &&
rootDir[c + e] != 0xFF &&
rootDir[c + e] != 0x2E)
continue;
validRootDir = false;
break;
}
2024-11-09 01:37:59 +00:00
if(!validRootDir) break;
}
2024-11-09 01:37:59 +00:00
return z80Di == 0xF3 &&
equalFatIds &&
(fat1Sector0[0] & 0xF0) == 0xF0 &&
fat1Sector0[1] == 0xFF &&
validRootDir;
}
}