// // 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. // namespace DiscUtils.Registry { using System; using System.Collections.Generic; using System.IO; /// /// An internal structure within registry files, bins are the major unit of allocation in a registry hive. /// /// Bins are divided into multiple cells, that contain actual registry data. internal sealed class Bin { private RegistryHive _hive; private Stream _fileStream; private long _streamPos; private BinHeader _header; private byte[] _buffer; private List> _freeCells; public Bin(RegistryHive hive, Stream stream) { _hive = hive; _fileStream = stream; _streamPos = stream.Position; stream.Position = _streamPos; byte[] buffer = Utilities.ReadFully(stream, 0x20); _header = new BinHeader(); _header.ReadFrom(buffer, 0); _fileStream.Position = _streamPos; _buffer = Utilities.ReadFully(_fileStream, _header.BinSize); // Gather list of all free cells. _freeCells = new List>(); int pos = 0x20; while (pos < _buffer.Length) { int size = Utilities.ToInt32LittleEndian(_buffer, pos); if (size > 0) { _freeCells.Add(new Range(pos, size)); } pos += Math.Abs(size); } } public Cell TryGetCell(int index) { int size = Utilities.ToInt32LittleEndian(_buffer, index - _header.FileOffset); if (size >= 0) { return null; } return Cell.Parse(_hive, index, _buffer, index + 4 - _header.FileOffset); } public void FreeCell(int index) { int freeIndex = index - _header.FileOffset; int len = Utilities.ToInt32LittleEndian(_buffer, freeIndex); if (len >= 0) { throw new ArgumentException("Attempt to free non-allocated cell"); } len = Math.Abs(len); // If there's a free cell before this one, combine int i = 0; while (i < _freeCells.Count && _freeCells[i].Offset < freeIndex) { if (_freeCells[i].Offset + _freeCells[i].Count == freeIndex) { freeIndex = _freeCells[i].Offset; len += _freeCells[i].Count; _freeCells.RemoveAt(i); } else { ++i; } } // If there's a free cell after this one, combine if (i < _freeCells.Count && _freeCells[i].Offset == freeIndex + len) { len += _freeCells[i].Count; _freeCells.RemoveAt(i); } // Record the new free cell _freeCells.Insert(i, new Range(freeIndex, len)); // Free cells are indicated by length > 0 Utilities.WriteBytesLittleEndian(len, _buffer, freeIndex); _fileStream.Position = _streamPos + freeIndex; _fileStream.Write(_buffer, freeIndex, 4); } public bool UpdateCell(Cell cell) { int index = cell.Index - _header.FileOffset; int allocSize = Math.Abs(Utilities.ToInt32LittleEndian(_buffer, index)); int newSize = cell.Size + 4; if (newSize > allocSize) { return false; } cell.WriteTo(_buffer, index + 4); _fileStream.Position = _streamPos + index; _fileStream.Write(_buffer, index, newSize); return true; } public byte[] ReadRawCellData(int cellIndex, int maxBytes) { int index = cellIndex - _header.FileOffset; int len = Math.Abs(Utilities.ToInt32LittleEndian(_buffer, index)); byte[] result = new byte[Math.Min(len - 4, maxBytes)]; Array.Copy(_buffer, index + 4, result, 0, result.Length); return result; } internal bool WriteRawCellData(int cellIndex, byte[] data, int offset, int count) { int index = cellIndex - _header.FileOffset; int allocSize = Math.Abs(Utilities.ToInt32LittleEndian(_buffer, index)); int newSize = count + 4; if (newSize > allocSize) { return false; } Array.Copy(data, offset, _buffer, index + 4, count); _fileStream.Position = _streamPos + index; _fileStream.Write(_buffer, index, newSize); return true; } internal int AllocateCell(int size) { if (size < 8 || size % 8 != 0) { throw new ArgumentException("Invalid cell size"); } // Very inefficient algorithm - will lead to fragmentation for (int i = 0; i < _freeCells.Count; ++i) { int result = _freeCells[i].Offset + _header.FileOffset; if (_freeCells[i].Count > size) { // Record the newly allocated cell Utilities.WriteBytesLittleEndian(-size, _buffer, _freeCells[i].Offset); _fileStream.Position = _streamPos + _freeCells[i].Offset; _fileStream.Write(_buffer, _freeCells[i].Offset, 4); // Keep the remainder of the free buffer as unallocated _freeCells[i] = new Range(_freeCells[i].Offset + size, _freeCells[i].Count - size); Utilities.WriteBytesLittleEndian(_freeCells[i].Count, _buffer, _freeCells[i].Offset); _fileStream.Position = _streamPos + _freeCells[i].Offset; _fileStream.Write(_buffer, _freeCells[i].Offset, 4); return result; } else if (_freeCells[i].Count == size) { // Record the whole of the free buffer as a newly allocated cell Utilities.WriteBytesLittleEndian(-size, _buffer, _freeCells[i].Offset); _fileStream.Position = _streamPos + _freeCells[i].Offset; _fileStream.Write(_buffer, _freeCells[i].Offset, 4); _freeCells.RemoveAt(i); return result; } } return -1; } } }