// // Copyright (c) 2008-2011, Kenneth Bell // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // using System; using System.Collections.Generic; using System.IO; using DiscUtils.Streams; namespace DiscUtils.Fat { internal class FatBuffer { /// /// The End-of-chain marker to WRITE (SetNext). Don't use this value to test for end of chain. /// /// /// The actual end-of-chain marker bits on disk vary by FAT type, and can end ...F8 through ...FF. /// public const uint EndOfChain = 0xFFFFFFFF; /// /// The Bad-Cluster marker to WRITE (SetNext). Don't use this value to test for bad clusters. /// /// /// The actual bad-cluster marker bits on disk vary by FAT type. /// public const uint BadCluster = 0xFFFFFFF7; /// /// The Free-Cluster marker to WRITE (SetNext). Don't use this value to test for free clusters. /// /// /// The actual free-cluster marker bits on disk vary by FAT type. /// public const uint FreeCluster = 0; private const uint DirtyRegionSize = 512; private readonly byte[] _buffer; private readonly Dictionary _dirtySectors; private readonly FatType _type; private uint _nextFreeCandidate; public FatBuffer(FatType type, byte[] buffer) { _type = type; _buffer = buffer; _dirtySectors = new Dictionary(); } internal int NumEntries { get { switch (_type) { case FatType.Fat12: return _buffer.Length / 3 * 2; case FatType.Fat16: return _buffer.Length / 2; default: // FAT32 return _buffer.Length / 4; } } } internal int Size { get { return _buffer.Length; } } internal bool IsFree(uint val) { return val == 0; } internal bool IsEndOfChain(uint val) { switch (_type) { case FatType.Fat12: return (val & 0x0FFF) >= 0x0FF8; case FatType.Fat16: return (val & 0xFFFF) >= 0xFFF8; case FatType.Fat32: return (val & 0x0FFFFFF8) >= 0x0FFFFFF8; default: throw new ArgumentException("Unknown FAT type"); } } internal bool IsBadCluster(uint val) { switch (_type) { case FatType.Fat12: return (val & 0x0FFF) == 0x0FF7; case FatType.Fat16: return (val & 0xFFFF) == 0xFFF7; case FatType.Fat32: return (val & 0x0FFFFFF8) == 0x0FFFFFF7; default: throw new ArgumentException("Unknown FAT type"); } } internal uint GetNext(uint cluster) { if (_type == FatType.Fat16) { return EndianUtilities.ToUInt16LittleEndian(_buffer, (int)(cluster * 2)); } if (_type == FatType.Fat32) { return EndianUtilities.ToUInt32LittleEndian(_buffer, (int)(cluster * 4)) & 0x0FFFFFFF; } // FAT12 if ((cluster & 1) != 0) { return (uint)((EndianUtilities.ToUInt16LittleEndian(_buffer, (int)(cluster + cluster / 2)) >> 4) & 0x0FFF); } return (uint)(EndianUtilities.ToUInt16LittleEndian(_buffer, (int)(cluster + cluster / 2)) & 0x0FFF); } internal void SetEndOfChain(uint cluster) { SetNext(cluster, EndOfChain); } internal void SetBadCluster(uint cluster) { SetNext(cluster, BadCluster); } internal void SetFree(uint cluster) { if (cluster < _nextFreeCandidate) { _nextFreeCandidate = cluster; } SetNext(cluster, FreeCluster); } internal void SetNext(uint cluster, uint next) { if (_type == FatType.Fat16) { MarkDirty(cluster * 2); EndianUtilities.WriteBytesLittleEndian((ushort)next, _buffer, (int)(cluster * 2)); } else if (_type == FatType.Fat32) { MarkDirty(cluster * 4); uint oldVal = EndianUtilities.ToUInt32LittleEndian(_buffer, (int)(cluster * 4)); uint newVal = (oldVal & 0xF0000000) | (next & 0x0FFFFFFF); EndianUtilities.WriteBytesLittleEndian(newVal, _buffer, (int)(cluster * 4)); } else { uint offset = cluster + cluster / 2; MarkDirty(offset); MarkDirty(offset + 1); // On alternate sector boundaries, cluster info crosses two sectors ushort maskedOldVal; if ((cluster & 1) != 0) { next = next << 4; maskedOldVal = (ushort)(EndianUtilities.ToUInt16LittleEndian(_buffer, (int)offset) & 0x000F); } else { next = next & 0x0FFF; maskedOldVal = (ushort)(EndianUtilities.ToUInt16LittleEndian(_buffer, (int)offset) & 0xF000); } ushort newVal = (ushort)(maskedOldVal | next); EndianUtilities.WriteBytesLittleEndian(newVal, _buffer, (int)offset); } } internal bool TryGetFreeCluster(out uint cluster) { // Simple scan - don't hold a free list... uint numEntries = (uint)NumEntries; for (uint i = 0; i < numEntries; i++) { uint candidate = (i + _nextFreeCandidate) % numEntries; if (IsFree(GetNext(candidate))) { cluster = candidate; _nextFreeCandidate = candidate + 1; return true; } } cluster = 0; return false; } internal void FreeChain(uint head) { foreach (uint cluster in GetChain(head)) { SetFree(cluster); } } internal List GetChain(uint head) { List result = new List(); if (head != 0) { uint focus = head; while (!IsEndOfChain(focus)) { result.Add(focus); focus = GetNext(focus); } } return result; } internal void MarkDirty(uint offset) { _dirtySectors[offset / DirtyRegionSize] = offset / DirtyRegionSize; } internal void WriteDirtyRegions(Stream stream, long position) { foreach (uint val in _dirtySectors.Values) { stream.Position = position + val * DirtyRegionSize; stream.Write(_buffer, (int)(val * DirtyRegionSize), (int)DirtyRegionSize); } } internal void ClearDirtyRegions() { _dirtySectors.Clear(); } } }