//------------------------------------------------------------ // Game Framework // Copyright © 2013-2021 loyalsoft. All rights reserved. // Homepage: http://www.game7000.com/ // Feedback: http://www.game7000.com/ //------------------------------------------------------------ using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; namespace GameFramework.FileSystem { /// /// 文件系统。 /// internal sealed partial class FileSystem : IFileSystem { private const int ClusterSize = 1024 * 4; private const int CachedBytesLength = 0x1000; private static readonly string[] EmptyStringArray = new string[] { }; private static readonly byte[] s_CachedBytes = new byte[CachedBytesLength]; private static readonly int HeaderDataSize = Marshal.SizeOf(typeof(HeaderData)); private static readonly int BlockDataSize = Marshal.SizeOf(typeof(BlockData)); private static readonly int StringDataSize = Marshal.SizeOf(typeof(StringData)); private readonly string m_FullPath; private readonly FileSystemAccess m_Access; private readonly FileSystemStream m_Stream; private readonly Dictionary m_FileDatas; private readonly List m_BlockDatas; private readonly GameFrameworkMultiDictionary m_FreeBlockIndexes; private readonly SortedDictionary m_StringDatas; private readonly Queue> m_FreeStringDatas; private HeaderData m_HeaderData; private int m_BlockDataOffset; private int m_StringDataOffset; private int m_FileDataOffset; /// /// 初始化文件系统的新实例。 /// /// 文件系统完整路径。 /// 文件系统访问方式。 /// 文件系统流。 private FileSystem(string fullPath, FileSystemAccess access, FileSystemStream stream) { if (string.IsNullOrEmpty(fullPath)) { throw new GameFrameworkException("Full path is invalid."); } if (access == FileSystemAccess.Unspecified) { throw new GameFrameworkException("Access is invalid."); } if (stream == null) { throw new GameFrameworkException("Stream is invalid."); } m_FullPath = fullPath; m_Access = access; m_Stream = stream; m_FileDatas = new Dictionary(StringComparer.Ordinal); m_BlockDatas = new List(); m_FreeBlockIndexes = new GameFrameworkMultiDictionary(); m_StringDatas = new SortedDictionary(); m_FreeStringDatas = new Queue>(); m_HeaderData = default(HeaderData); m_BlockDataOffset = 0; m_StringDataOffset = 0; m_FileDataOffset = 0; Utility.Marshal.EnsureCachedHGlobalSize(CachedBytesLength); } /// /// 获取文件系统完整路径。 /// public string FullPath { get { return m_FullPath; } } /// /// 获取文件系统访问方式。 /// public FileSystemAccess Access { get { return m_Access; } } /// /// 获取文件数量。 /// public int FileCount { get { return m_FileDatas.Count; } } /// /// 获取最大文件数量。 /// public int MaxFileCount { get { return m_HeaderData.MaxFileCount; } } /// /// 创建文件系统。 /// /// 要创建的文件系统的完整路径。 /// 要创建的文件系统的访问方式。 /// 要创建的文件系统的文件系统流。 /// 要创建的文件系统的最大文件数量。 /// 要创建的文件系统的最大块数据数量。 /// 创建的文件系统。 public static FileSystem Create(string fullPath, FileSystemAccess access, FileSystemStream stream, int maxFileCount, int maxBlockCount) { if (maxFileCount <= 0) { throw new GameFrameworkException("Max file count is invalid."); } if (maxBlockCount <= 0) { throw new GameFrameworkException("Max block count is invalid."); } if (maxFileCount > maxBlockCount) { throw new GameFrameworkException("Max file count can not larger than max block count."); } FileSystem fileSystem = new FileSystem(fullPath, access, stream); fileSystem.m_HeaderData = new HeaderData(maxFileCount, maxBlockCount); CalcOffsets(fileSystem); Utility.Marshal.StructureToBytes(fileSystem.m_HeaderData, HeaderDataSize, s_CachedBytes); try { stream.Write(s_CachedBytes, 0, HeaderDataSize); stream.SetLength(fileSystem.m_FileDataOffset); return fileSystem; } catch { fileSystem.Shutdown(); return null; } } /// /// 加载文件系统。 /// /// 要加载的文件系统的完整路径。 /// 要加载的文件系统的访问方式。 /// 要加载的文件系统的文件系统流。 /// 加载的文件系统。 public static FileSystem Load(string fullPath, FileSystemAccess access, FileSystemStream stream) { FileSystem fileSystem = new FileSystem(fullPath, access, stream); stream.Read(s_CachedBytes, 0, HeaderDataSize); fileSystem.m_HeaderData = Utility.Marshal.BytesToStructure(HeaderDataSize, s_CachedBytes); if (!fileSystem.m_HeaderData.IsValid) { throw new GameFrameworkException(Utility.Text.Format("File system '{0}' is invalid.", fullPath)); } CalcOffsets(fileSystem); if (fileSystem.m_BlockDatas.Capacity < fileSystem.m_HeaderData.BlockCount) { fileSystem.m_BlockDatas.Capacity = fileSystem.m_HeaderData.BlockCount; } for (int i = 0; i < fileSystem.m_HeaderData.BlockCount; i++) { stream.Read(s_CachedBytes, 0, BlockDataSize); BlockData blockData = Utility.Marshal.BytesToStructure(BlockDataSize, s_CachedBytes); fileSystem.m_BlockDatas.Add(blockData); } for (int i = 0; i < fileSystem.m_BlockDatas.Count; i++) { BlockData blockData = fileSystem.m_BlockDatas[i]; if (blockData.Using) { StringData stringData = fileSystem.ReadStringData(blockData.StringIndex); fileSystem.m_StringDatas.Add(blockData.StringIndex, stringData); fileSystem.m_FileDatas.Add(stringData.GetString(fileSystem.m_HeaderData.GetEncryptBytes()), i); } else { fileSystem.m_FreeBlockIndexes.Add(blockData.Length, i); } } return fileSystem; } /// /// 关闭并清理文件系统。 /// public void Shutdown() { m_Stream.Close(); m_FileDatas.Clear(); m_BlockDatas.Clear(); m_FreeBlockIndexes.Clear(); m_StringDatas.Clear(); m_FreeStringDatas.Clear(); m_BlockDataOffset = 0; m_StringDataOffset = 0; m_FileDataOffset = 0; } /// /// 获取文件信息。 /// /// 要获取文件信息的文件名称。 /// 获取的文件信息。 public FileInfo GetFileInfo(string name) { if (string.IsNullOrEmpty(name)) { throw new GameFrameworkException("Name is invalid."); } int blockIndex = 0; if (!m_FileDatas.TryGetValue(name, out blockIndex)) { return default(FileInfo); } BlockData blockData = m_BlockDatas[blockIndex]; return new FileInfo(name, GetClusterOffset(blockData.ClusterIndex), blockData.Length); } /// /// 获取所有文件信息。 /// /// 获取的所有文件信息。 public FileInfo[] GetAllFileInfos() { int index = 0; FileInfo[] results = new FileInfo[m_FileDatas.Count]; foreach (KeyValuePair fileData in m_FileDatas) { BlockData blockData = m_BlockDatas[fileData.Value]; results[index++] = new FileInfo(fileData.Key, GetClusterOffset(blockData.ClusterIndex), blockData.Length); } return results; } /// /// 获取所有文件信息。 /// /// 获取的所有文件信息。 public void GetAllFileInfos(List results) { if (results == null) { throw new GameFrameworkException("Results is invalid."); } results.Clear(); foreach (KeyValuePair fileData in m_FileDatas) { BlockData blockData = m_BlockDatas[fileData.Value]; results.Add(new FileInfo(fileData.Key, GetClusterOffset(blockData.ClusterIndex), blockData.Length)); } } /// /// 检查是否存在指定文件。 /// /// 要检查的文件名称。 /// 是否存在指定文件。 public bool HasFile(string name) { if (string.IsNullOrEmpty(name)) { throw new GameFrameworkException("Name is invalid."); } return m_FileDatas.ContainsKey(name); } /// /// 读取指定文件。 /// /// 要读取的文件名称。 /// 存储读取文件内容的二进制流。 public byte[] ReadFile(string name) { if (m_Access != FileSystemAccess.Read && m_Access != FileSystemAccess.ReadWrite) { throw new GameFrameworkException("File system is not readable."); } if (string.IsNullOrEmpty(name)) { throw new GameFrameworkException("Name is invalid."); } FileInfo fileInfo = GetFileInfo(name); if (!fileInfo.IsValid) { return null; } int length = fileInfo.Length; byte[] buffer = new byte[length]; if (length > 0) { m_Stream.Position = fileInfo.Offset; m_Stream.Read(buffer, 0, length); } return buffer; } /// /// 读取指定文件。 /// /// 要读取的文件名称。 /// 存储读取文件内容的二进制流。 /// 实际读取了多少字节。 public int ReadFile(string name, byte[] buffer) { if (buffer == null) { throw new GameFrameworkException("Buffer is invalid."); } return ReadFile(name, buffer, 0, buffer.Length); } /// /// 读取指定文件。 /// /// 要读取的文件名称。 /// 存储读取文件内容的二进制流。 /// 存储读取文件内容的二进制流的起始位置。 /// 实际读取了多少字节。 public int ReadFile(string name, byte[] buffer, int startIndex) { if (buffer == null) { throw new GameFrameworkException("Buffer is invalid."); } return ReadFile(name, buffer, startIndex, buffer.Length - startIndex); } /// /// 读取指定文件。 /// /// 要读取的文件名称。 /// 存储读取文件内容的二进制流。 /// 存储读取文件内容的二进制流的起始位置。 /// 存储读取文件内容的二进制流的长度。 /// 实际读取了多少字节。 public int ReadFile(string name, byte[] buffer, int startIndex, int length) { if (m_Access != FileSystemAccess.Read && m_Access != FileSystemAccess.ReadWrite) { throw new GameFrameworkException("File system is not readable."); } if (string.IsNullOrEmpty(name)) { throw new GameFrameworkException("Name is invalid."); } if (buffer == null) { throw new GameFrameworkException("Buffer is invalid."); } if (startIndex < 0 || length < 0 || startIndex + length > buffer.Length) { throw new GameFrameworkException("Start index or length is invalid."); } FileInfo fileInfo = GetFileInfo(name); if (!fileInfo.IsValid) { return 0; } m_Stream.Position = fileInfo.Offset; if (length > fileInfo.Length) { length = fileInfo.Length; } if (length > 0) { return m_Stream.Read(buffer, startIndex, length); } return 0; } /// /// 读取指定文件。 /// /// 要读取的文件名称。 /// 存储读取文件内容的二进制流。 /// 实际读取了多少字节。 public int ReadFile(string name, Stream stream) { if (m_Access != FileSystemAccess.Read && m_Access != FileSystemAccess.ReadWrite) { throw new GameFrameworkException("File system is not readable."); } if (string.IsNullOrEmpty(name)) { throw new GameFrameworkException("Name is invalid."); } if (stream == null) { throw new GameFrameworkException("Stream is invalid."); } if (!stream.CanWrite) { throw new GameFrameworkException("Stream is not writable."); } FileInfo fileInfo = GetFileInfo(name); if (!fileInfo.IsValid) { return 0; } int length = fileInfo.Length; if (length > 0) { m_Stream.Position = fileInfo.Offset; return m_Stream.Read(stream, length); } return 0; } /// /// 读取指定文件的指定片段。 /// /// 要读取片段的文件名称。 /// 要读取片段的长度。 /// 存储读取文件片段内容的二进制流。 public byte[] ReadFileSegment(string name, int length) { return ReadFileSegment(name, 0, length); } /// /// 读取指定文件的指定片段。 /// /// 要读取片段的文件名称。 /// 要读取片段的偏移。 /// 要读取片段的长度。 /// 存储读取文件片段内容的二进制流。 public byte[] ReadFileSegment(string name, int offset, int length) { if (m_Access != FileSystemAccess.Read && m_Access != FileSystemAccess.ReadWrite) { throw new GameFrameworkException("File system is not readable."); } if (string.IsNullOrEmpty(name)) { throw new GameFrameworkException("Name is invalid."); } if (offset < 0) { throw new GameFrameworkException("Index is invalid."); } if (length < 0) { throw new GameFrameworkException("Length is invalid."); } FileInfo fileInfo = GetFileInfo(name); if (!fileInfo.IsValid) { return null; } if (offset > fileInfo.Length) { offset = fileInfo.Length; } int leftLength = fileInfo.Length - offset; if (length > leftLength) { length = leftLength; } byte[] buffer = new byte[length]; if (length > 0) { m_Stream.Position = fileInfo.Offset + offset; m_Stream.Read(buffer, 0, length); } return buffer; } /// /// 读取指定文件的指定片段。 /// /// 要读取片段的文件名称。 /// 存储读取文件片段内容的二进制流。 /// 实际读取了多少字节。 public int ReadFileSegment(string name, byte[] buffer) { if (buffer == null) { throw new GameFrameworkException("Buffer is invalid."); } return ReadFileSegment(name, 0, buffer, 0, buffer.Length); } /// /// 读取指定文件的指定片段。 /// /// 要读取片段的文件名称。 /// 存储读取文件片段内容的二进制流。 /// 要读取片段的长度。 /// 实际读取了多少字节。 public int ReadFileSegment(string name, byte[] buffer, int length) { return ReadFileSegment(name, 0, buffer, 0, length); } /// /// 读取指定文件的指定片段。 /// /// 要读取片段的文件名称。 /// 存储读取文件片段内容的二进制流。 /// 存储读取文件片段内容的二进制流的起始位置。 /// 要读取片段的长度。 /// 实际读取了多少字节。 public int ReadFileSegment(string name, byte[] buffer, int startIndex, int length) { return ReadFileSegment(name, 0, buffer, startIndex, length); } /// /// 读取指定文件的指定片段。 /// /// 要读取片段的文件名称。 /// 要读取片段的偏移。 /// 存储读取文件片段内容的二进制流。 /// 实际读取了多少字节。 public int ReadFileSegment(string name, int offset, byte[] buffer) { if (buffer == null) { throw new GameFrameworkException("Buffer is invalid."); } return ReadFileSegment(name, offset, buffer, 0, buffer.Length); } /// /// 读取指定文件的指定片段。 /// /// 要读取片段的文件名称。 /// 要读取片段的偏移。 /// 存储读取文件片段内容的二进制流。 /// 要读取片段的长度。 /// 实际读取了多少字节。 public int ReadFileSegment(string name, int offset, byte[] buffer, int length) { return ReadFileSegment(name, offset, buffer, 0, length); } /// /// 读取指定文件的指定片段。 /// /// 要读取片段的文件名称。 /// 要读取片段的偏移。 /// 存储读取文件片段内容的二进制流。 /// 存储读取文件片段内容的二进制流的起始位置。 /// 要读取片段的长度。 /// 实际读取了多少字节。 public int ReadFileSegment(string name, int offset, byte[] buffer, int startIndex, int length) { if (m_Access != FileSystemAccess.Read && m_Access != FileSystemAccess.ReadWrite) { throw new GameFrameworkException("File system is not readable."); } if (string.IsNullOrEmpty(name)) { throw new GameFrameworkException("Name is invalid."); } if (offset < 0) { throw new GameFrameworkException("Index is invalid."); } if (buffer == null) { throw new GameFrameworkException("Buffer is invalid."); } if (startIndex < 0 || length < 0 || startIndex + length > buffer.Length) { throw new GameFrameworkException("Start index or length is invalid."); } FileInfo fileInfo = GetFileInfo(name); if (!fileInfo.IsValid) { return 0; } if (offset > fileInfo.Length) { offset = fileInfo.Length; } int leftLength = fileInfo.Length - offset; if (length > leftLength) { length = leftLength; } if (length > 0) { m_Stream.Position = fileInfo.Offset + offset; return m_Stream.Read(buffer, startIndex, length); } return 0; } /// /// 读取指定文件的指定片段。 /// /// 要读取片段的文件名称。 /// 存储读取文件片段内容的二进制流。 /// 要读取片段的长度。 /// 实际读取了多少字节。 public int ReadFileSegment(string name, Stream stream, int length) { return ReadFileSegment(name, 0, stream, length); } /// /// 读取指定文件的指定片段。 /// /// 要读取片段的文件名称。 /// 要读取片段的偏移。 /// 存储读取文件片段内容的二进制流。 /// 要读取片段的长度。 /// 实际读取了多少字节。 public int ReadFileSegment(string name, int offset, Stream stream, int length) { if (m_Access != FileSystemAccess.Read && m_Access != FileSystemAccess.ReadWrite) { throw new GameFrameworkException("File system is not readable."); } if (string.IsNullOrEmpty(name)) { throw new GameFrameworkException("Name is invalid."); } if (offset < 0) { throw new GameFrameworkException("Index is invalid."); } if (stream == null) { throw new GameFrameworkException("Stream is invalid."); } if (!stream.CanWrite) { throw new GameFrameworkException("Stream is not writable."); } if (length < 0) { throw new GameFrameworkException("Length is invalid."); } FileInfo fileInfo = GetFileInfo(name); if (!fileInfo.IsValid) { return 0; } if (offset > fileInfo.Length) { offset = fileInfo.Length; } int leftLength = fileInfo.Length - offset; if (length > leftLength) { length = leftLength; } if (length > 0) { m_Stream.Position = fileInfo.Offset + offset; return m_Stream.Read(stream, length); } return 0; } /// /// 写入指定文件。 /// /// 要写入的文件名称。 /// 存储写入文件内容的二进制流。 /// 是否写入指定文件成功。 public bool WriteFile(string name, byte[] buffer) { if (buffer == null) { throw new GameFrameworkException("Buffer is invalid."); } return WriteFile(name, buffer, 0, buffer.Length); } /// /// 写入指定文件。 /// /// 要写入的文件名称。 /// 存储写入文件内容的二进制流。 /// 存储写入文件内容的二进制流的起始位置。 /// 是否写入指定文件成功。 public bool WriteFile(string name, byte[] buffer, int startIndex) { if (buffer == null) { throw new GameFrameworkException("Buffer is invalid."); } return WriteFile(name, buffer, startIndex, buffer.Length - startIndex); } /// /// 写入指定文件。 /// /// 要写入的文件名称。 /// 存储写入文件内容的二进制流。 /// 存储写入文件内容的二进制流的起始位置。 /// 存储写入文件内容的二进制流的长度。 /// 是否写入指定文件成功。 public bool WriteFile(string name, byte[] buffer, int startIndex, int length) { if (m_Access != FileSystemAccess.Write && m_Access != FileSystemAccess.ReadWrite) { throw new GameFrameworkException("File system is not writable."); } if (string.IsNullOrEmpty(name)) { throw new GameFrameworkException("Name is invalid."); } if (name.Length > byte.MaxValue) { throw new GameFrameworkException(Utility.Text.Format("Name '{0}' is too long.", name)); } if (buffer == null) { throw new GameFrameworkException("Buffer is invalid."); } if (startIndex < 0 || length < 0 || startIndex + length > buffer.Length) { throw new GameFrameworkException("Start index or length is invalid."); } bool hasFile = false; int oldBlockIndex = -1; if (m_FileDatas.TryGetValue(name, out oldBlockIndex)) { hasFile = true; } if (!hasFile && m_FileDatas.Count >= m_HeaderData.MaxFileCount) { return false; } int blockIndex = AllocBlock(length); if (blockIndex < 0) { return false; } if (length > 0) { m_Stream.Position = GetClusterOffset(m_BlockDatas[blockIndex].ClusterIndex); m_Stream.Write(buffer, startIndex, length); } ProcessWriteFile(name, hasFile, oldBlockIndex, blockIndex, length); m_Stream.Flush(); return true; } /// /// 写入指定文件。 /// /// 要写入的文件名称。 /// 存储写入文件内容的二进制流。 /// 是否写入指定文件成功。 public bool WriteFile(string name, Stream stream) { if (m_Access != FileSystemAccess.Write && m_Access != FileSystemAccess.ReadWrite) { throw new GameFrameworkException("File system is not writable."); } if (string.IsNullOrEmpty(name)) { throw new GameFrameworkException("Name is invalid."); } if (name.Length > byte.MaxValue) { throw new GameFrameworkException(Utility.Text.Format("Name '{0}' is too long.", name)); } if (stream == null) { throw new GameFrameworkException("Stream is invalid."); } if (!stream.CanRead) { throw new GameFrameworkException("Stream is not readable."); } bool hasFile = false; int oldBlockIndex = -1; if (m_FileDatas.TryGetValue(name, out oldBlockIndex)) { hasFile = true; } if (!hasFile && m_FileDatas.Count >= m_HeaderData.MaxFileCount) { return false; } int length = (int)(stream.Length - stream.Position); int blockIndex = AllocBlock(length); if (blockIndex < 0) { return false; } if (length > 0) { m_Stream.Position = GetClusterOffset(m_BlockDatas[blockIndex].ClusterIndex); m_Stream.Write(stream, length); } ProcessWriteFile(name, hasFile, oldBlockIndex, blockIndex, length); m_Stream.Flush(); return true; } /// /// 写入指定文件。 /// /// 要写入的文件名称。 /// 存储写入文件内容的文件路径。 /// 是否写入指定文件成功。 public bool WriteFile(string name, string filePath) { if (string.IsNullOrEmpty(filePath)) { throw new GameFrameworkException("File path is invalid"); } if (!File.Exists(filePath)) { return false; } using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) { return WriteFile(name, fileStream); } } /// /// 将指定文件另存为物理文件。 /// /// 要另存为的文件名称。 /// 存储写入文件内容的文件路径。 /// 是否将指定文件另存为物理文件成功。 public bool SaveAsFile(string name, string filePath) { if (m_Access != FileSystemAccess.Read && m_Access != FileSystemAccess.ReadWrite) { throw new GameFrameworkException("File system is not readable."); } if (string.IsNullOrEmpty(name)) { throw new GameFrameworkException("Name is invalid."); } if (string.IsNullOrEmpty(filePath)) { throw new GameFrameworkException("File path is invalid"); } FileInfo fileInfo = GetFileInfo(name); if (!fileInfo.IsValid) { return false; } try { if (File.Exists(filePath)) { File.Delete(filePath); } string directory = Path.GetDirectoryName(filePath); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None)) { int length = fileInfo.Length; if (length > 0) { m_Stream.Position = fileInfo.Offset; return m_Stream.Read(fileStream, length) == length; } return true; } } catch { return false; } } /// /// 重命名指定文件。 /// /// 要重命名的文件名称。 /// 重命名后的文件名称。 /// 是否重命名指定文件成功。 public bool RenameFile(string oldName, string newName) { if (m_Access != FileSystemAccess.Write && m_Access != FileSystemAccess.ReadWrite) { throw new GameFrameworkException("File system is not writable."); } if (string.IsNullOrEmpty(oldName)) { throw new GameFrameworkException("Old name is invalid."); } if (string.IsNullOrEmpty(newName)) { throw new GameFrameworkException("New name is invalid."); } if (newName.Length > byte.MaxValue) { throw new GameFrameworkException(Utility.Text.Format("New name '{0}' is too long.", newName)); } if (oldName == newName) { return true; } if (m_FileDatas.ContainsKey(newName)) { return false; } int blockIndex = 0; if (!m_FileDatas.TryGetValue(oldName, out blockIndex)) { return false; } int stringIndex = m_BlockDatas[blockIndex].StringIndex; StringData stringData = m_StringDatas[stringIndex].SetString(newName, m_HeaderData.GetEncryptBytes()); m_StringDatas[stringIndex] = stringData; WriteStringData(stringIndex, stringData); m_FileDatas.Add(newName, blockIndex); m_FileDatas.Remove(oldName); m_Stream.Flush(); return true; } /// /// 删除指定文件。 /// /// 要删除的文件名称。 /// 是否删除指定文件成功。 public bool DeleteFile(string name) { if (m_Access != FileSystemAccess.Write && m_Access != FileSystemAccess.ReadWrite) { throw new GameFrameworkException("File system is not writable."); } if (string.IsNullOrEmpty(name)) { throw new GameFrameworkException("Name is invalid."); } int blockIndex = 0; if (!m_FileDatas.TryGetValue(name, out blockIndex)) { return false; } m_FileDatas.Remove(name); BlockData blockData = m_BlockDatas[blockIndex]; int stringIndex = blockData.StringIndex; StringData stringData = m_StringDatas[stringIndex].Clear(); m_FreeStringDatas.Enqueue(new KeyValuePair(stringIndex, stringData)); m_StringDatas.Remove(stringIndex); WriteStringData(stringIndex, stringData); blockData = blockData.Free(); m_BlockDatas[blockIndex] = blockData; if (!TryCombineFreeBlocks(blockIndex)) { m_FreeBlockIndexes.Add(blockData.Length, blockIndex); WriteBlockData(blockIndex); } m_Stream.Flush(); return true; } private void ProcessWriteFile(string name, bool hasFile, int oldBlockIndex, int blockIndex, int length) { BlockData blockData = m_BlockDatas[blockIndex]; if (hasFile) { BlockData oldBlockData = m_BlockDatas[oldBlockIndex]; blockData = new BlockData(oldBlockData.StringIndex, blockData.ClusterIndex, length); m_BlockDatas[blockIndex] = blockData; WriteBlockData(blockIndex); oldBlockData = oldBlockData.Free(); m_BlockDatas[oldBlockIndex] = oldBlockData; if (!TryCombineFreeBlocks(oldBlockIndex)) { m_FreeBlockIndexes.Add(oldBlockData.Length, oldBlockIndex); WriteBlockData(oldBlockIndex); } } else { int stringIndex = AllocString(name); blockData = new BlockData(stringIndex, blockData.ClusterIndex, length); m_BlockDatas[blockIndex] = blockData; WriteBlockData(blockIndex); } if (hasFile) { m_FileDatas[name] = blockIndex; } else { m_FileDatas.Add(name, blockIndex); } } private bool TryCombineFreeBlocks(int freeBlockIndex) { BlockData freeBlockData = m_BlockDatas[freeBlockIndex]; if (freeBlockData.Length <= 0) { return false; } int previousFreeBlockIndex = -1; int nextFreeBlockIndex = -1; int nextBlockDataClusterIndex = freeBlockData.ClusterIndex + GetUpBoundClusterCount(freeBlockData.Length); foreach (KeyValuePair> blockIndexes in m_FreeBlockIndexes) { if (blockIndexes.Key <= 0) { continue; } int blockDataClusterCount = GetUpBoundClusterCount(blockIndexes.Key); foreach (int blockIndex in blockIndexes.Value) { BlockData blockData = m_BlockDatas[blockIndex]; if (blockData.ClusterIndex + blockDataClusterCount == freeBlockData.ClusterIndex) { previousFreeBlockIndex = blockIndex; } else if (blockData.ClusterIndex == nextBlockDataClusterIndex) { nextFreeBlockIndex = blockIndex; } } } if (previousFreeBlockIndex < 0 && nextFreeBlockIndex < 0) { return false; } m_FreeBlockIndexes.Remove(freeBlockData.Length, freeBlockIndex); if (previousFreeBlockIndex >= 0) { BlockData previousFreeBlockData = m_BlockDatas[previousFreeBlockIndex]; m_FreeBlockIndexes.Remove(previousFreeBlockData.Length, previousFreeBlockIndex); freeBlockData = new BlockData(previousFreeBlockData.ClusterIndex, previousFreeBlockData.Length + freeBlockData.Length); m_BlockDatas[previousFreeBlockIndex] = BlockData.Empty; m_FreeBlockIndexes.Add(0, previousFreeBlockIndex); WriteBlockData(previousFreeBlockIndex); } if (nextFreeBlockIndex >= 0) { BlockData nextFreeBlockData = m_BlockDatas[nextFreeBlockIndex]; m_FreeBlockIndexes.Remove(nextFreeBlockData.Length, nextFreeBlockIndex); freeBlockData = new BlockData(freeBlockData.ClusterIndex, freeBlockData.Length + nextFreeBlockData.Length); m_BlockDatas[nextFreeBlockIndex] = BlockData.Empty; m_FreeBlockIndexes.Add(0, nextFreeBlockIndex); WriteBlockData(nextFreeBlockIndex); } m_BlockDatas[freeBlockIndex] = freeBlockData; m_FreeBlockIndexes.Add(freeBlockData.Length, freeBlockIndex); WriteBlockData(freeBlockIndex); return true; } private int GetEmptyBlockIndex() { GameFrameworkLinkedListRange lengthRange = default(GameFrameworkLinkedListRange); if (m_FreeBlockIndexes.TryGetValue(0, out lengthRange)) { int blockIndex = lengthRange.First.Value; m_FreeBlockIndexes.Remove(0, blockIndex); return blockIndex; } if (m_BlockDatas.Count < m_HeaderData.MaxBlockCount) { int blockIndex = m_BlockDatas.Count; m_BlockDatas.Add(BlockData.Empty); WriteHeaderData(); return blockIndex; } return -1; } private int AllocBlock(int length) { if (length <= 0) { return GetEmptyBlockIndex(); } length = (int)GetUpBoundClusterOffset(length); int lengthFound = -1; GameFrameworkLinkedListRange lengthRange = default(GameFrameworkLinkedListRange); foreach (KeyValuePair> i in m_FreeBlockIndexes) { if (i.Key < length) { continue; } if (lengthFound >= 0 && lengthFound < i.Key) { continue; } lengthFound = i.Key; lengthRange = i.Value; } if (lengthFound >= 0) { if (lengthFound > length && m_BlockDatas.Count >= m_HeaderData.MaxBlockCount) { return -1; } int blockIndex = lengthRange.First.Value; m_FreeBlockIndexes.Remove(lengthFound, blockIndex); if (lengthFound > length) { BlockData blockData = m_BlockDatas[blockIndex]; m_BlockDatas[blockIndex] = new BlockData(blockData.ClusterIndex, length); WriteBlockData(blockIndex); int deltaLength = lengthFound - length; int anotherBlockIndex = GetEmptyBlockIndex(); m_BlockDatas[anotherBlockIndex] = new BlockData(blockData.ClusterIndex + GetUpBoundClusterCount(length), deltaLength); m_FreeBlockIndexes.Add(deltaLength, anotherBlockIndex); WriteBlockData(anotherBlockIndex); } return blockIndex; } else { int blockIndex = GetEmptyBlockIndex(); if (blockIndex < 0) { return -1; } long fileLength = m_Stream.Length; try { m_Stream.SetLength(fileLength + length); } catch { return -1; } m_BlockDatas[blockIndex] = new BlockData(GetUpBoundClusterCount(fileLength), length); WriteBlockData(blockIndex); return blockIndex; } } private int AllocString(string value) { int stringIndex = -1; StringData stringData = default(StringData); if (m_FreeStringDatas.Count > 0) { KeyValuePair freeStringData = m_FreeStringDatas.Dequeue(); stringIndex = freeStringData.Key; stringData = freeStringData.Value; } else { int index = 0; foreach (KeyValuePair i in m_StringDatas) { if (i.Key == index) { index++; continue; } break; } if (index < m_HeaderData.MaxFileCount) { stringIndex = index; byte[] bytes = new byte[byte.MaxValue]; Utility.Random.GetRandomBytes(bytes); stringData = new StringData(0, bytes); } } if (stringIndex < 0) { throw new GameFrameworkException("Alloc string internal error."); } stringData = stringData.SetString(value, m_HeaderData.GetEncryptBytes()); m_StringDatas.Add(stringIndex, stringData); WriteStringData(stringIndex, stringData); return stringIndex; } private void WriteHeaderData() { m_HeaderData = m_HeaderData.SetBlockCount(m_BlockDatas.Count); Utility.Marshal.StructureToBytes(m_HeaderData, HeaderDataSize, s_CachedBytes); m_Stream.Position = 0L; m_Stream.Write(s_CachedBytes, 0, HeaderDataSize); } private void WriteBlockData(int blockIndex) { Utility.Marshal.StructureToBytes(m_BlockDatas[blockIndex], BlockDataSize, s_CachedBytes); m_Stream.Position = m_BlockDataOffset + BlockDataSize * blockIndex; m_Stream.Write(s_CachedBytes, 0, BlockDataSize); } private StringData ReadStringData(int stringIndex) { m_Stream.Position = m_StringDataOffset + StringDataSize * stringIndex; m_Stream.Read(s_CachedBytes, 0, StringDataSize); return Utility.Marshal.BytesToStructure(StringDataSize, s_CachedBytes); } private void WriteStringData(int stringIndex, StringData stringData) { Utility.Marshal.StructureToBytes(stringData, StringDataSize, s_CachedBytes); m_Stream.Position = m_StringDataOffset + StringDataSize * stringIndex; m_Stream.Write(s_CachedBytes, 0, StringDataSize); } private static void CalcOffsets(FileSystem fileSystem) { fileSystem.m_BlockDataOffset = HeaderDataSize; fileSystem.m_StringDataOffset = fileSystem.m_BlockDataOffset + BlockDataSize * fileSystem.m_HeaderData.MaxBlockCount; fileSystem.m_FileDataOffset = (int)GetUpBoundClusterOffset(fileSystem.m_StringDataOffset + StringDataSize * fileSystem.m_HeaderData.MaxFileCount); } private static long GetUpBoundClusterOffset(long offset) { return (offset - 1L + ClusterSize) / ClusterSize * ClusterSize; } private static int GetUpBoundClusterCount(long length) { return (int)((length - 1L + ClusterSize) / ClusterSize); } private static long GetClusterOffset(int clusterIndex) { return (long)ClusterSize * clusterIndex; } } }