protected bool AnyContentFileExceedsAvailableSpace(EdataFile file) { var contentFiles = file.ContentFiles .OfType <EdataContentFile>() .OrderBy(cf => cf.TotalOffset) .ToArray(); for (int i = 0; i < contentFiles.Length; ++i) { var currentFile = contentFiles[i]; if (currentFile.IsContentLoaded) { var nextFile = i + 1 < contentFiles.Length ? contentFiles[i + 1] : null; if (nextFile != null) { if ((currentFile.TotalOffset + currentFile.ContentSize + MinBytesBetweenFiles) >= nextFile.TotalOffset) { return(true); } } else { if ((currentFile.TotalOffset + currentFile.ContentSize + MinBytesBetweenFiles) >= (file.Header.FileOffset + file.Header.FileLenght)) { return(true); } } } } return(false); }
public EdataFile Read(String edataFilePath, bool loadContent, CancellationToken token) { //Cancel if requested; token.ThrowIfCancellationRequested(); if (!File.Exists(edataFilePath)) { throw new ArgumentException(String.Format("File '{0}' doesn't exist.", edataFilePath), "edataFilePath"); } EdataHeader header; IEnumerable <EdataContentFile> contentFiles; using (FileStream stream = new FileStream(edataFilePath, FileMode.Open)) { header = ReadHeader(stream); if (header.Version != 2) { throw new NotSupportedException(String.Format("Edata Version {0} is currently not supported", header.Version)); } var dictRoot = ReadDcitionaryEntries(stream, header.DictOffset, header.DictLength); contentFiles = TranslateDictionaryEntriesToContentFiles(stream, header.FileOffset, dictRoot); if (loadContent) { LoadContentFiles(stream, contentFiles); } } EdataFile edataFile = new EdataFile(edataFilePath, header, contentFiles); return(edataFile); }
/// <summary> /// /// </summary> /// <param name="edataFile"></param> /// <param name="token"></param> /// <returns></returns> protected virtual byte[] WriteContentInternal(EdataFile edataFile, CancellationToken?token = null) { //Cancel if requested; token.ThrowIfCanceledAndNotNull(); using (MemoryStream edataStream = new MemoryStream()) { WriteHeader(edataStream, edataFile); if (CanUseReplacementWrite(edataFile)) { //Wydaję się że w tym wypadku to nigdy nie będzie miało miejsca, bo załadoway content oryginale //(któy w tym przypadku powinien być załadowany zawsze w całości) zawsze będzie równy max dostępnej przestrzeni replacement. WriteLoadedContentByReplace(edataStream, edataFile, token); } else { WriteLoadedContent(edataStream, edataFile, token); } WriteDictionary(edataStream, edataFile); return(edataStream.ToArray()); } }
/// <summary> /// /// </summary> /// <param name="target"></param> /// <param name="edataFile"></param> /// <remarks> /// Method based on enohka's code. /// See more at: http://github.com/enohka/moddingSuite /// </remarks> protected virtual void WriteLoadedContent(Stream target, EdataFile edataFile, CancellationToken?token = null) { byte[] spaceBuffer = null; var sourceEdataHeader = edataFile.Header; uint filesContentLength = 0; foreach (EdataContentFile file in edataFile.ContentFiles) { //Cancel if requested; token.ThrowIfCanceledAndNotNull(); //long oldOffset = file.Offset; byte[] fileBuffer = file.Content; file.Checksum = MD5.Create().ComputeHash(fileBuffer); file.Size = file.Content.Length; file.Offset = target.Position - sourceEdataHeader.FileOffset; long spaceSize = GetSpaceSizeForFile(file); spaceBuffer = GetNewBufferIfNeeded(spaceBuffer, spaceSize); target.Write(fileBuffer, 0, fileBuffer.Length); target.Write(spaceBuffer, 0, (int)spaceSize); filesContentLength += (uint)fileBuffer.Length + (uint)spaceSize; } target.Seek(0x25, SeekOrigin.Begin); target.Write(BitConverter.GetBytes(filesContentLength), 0, 4); }
protected void SaveEdataFile(EdataFile edataFile, CancellationToken?token = null) { IEdataFileWriter edataWriter = new EdataFileWriter(); if (token.HasValue) { edataWriter.Write(edataFile, token.Value); } else { edataWriter.Write(edataFile); } }
/// <remarks> /// Method based on enohka's code. /// See more at: http://github.com/enohka/moddingSuite /// </remarks> protected virtual void WriteHeader(Stream target, EdataFile edataFile) { //var sourceEdataHeader = edataFile.Header; //var headerPart = new byte[sourceEdataHeader.FileOffset]; //sourceEdata.Read(headerPart, 0, headerPart.Length); //newEdata.Write(headerPart, 0, headerPart.Length); var sourceEdataHeader = edataFile.Header; byte[] rawHeader = MiscUtilities.StructToBytes(sourceEdataHeader); target.Write(rawHeader, 0, rawHeader.Length); target.Write(edataFile.PostHeaderData, 0, edataFile.PostHeaderData.Length); }
/// <summary> /// /// </summary> /// <param name="source">Source stream, from which content files will be loaded.</param> /// <param name="target"></param> /// <param name="edataFile"></param> /// <remarks> /// Method based on enohka's code. /// See more at: http://github.com/enohka/moddingSuite /// </remarks> protected virtual void WriteNotLoadedContent(Stream source, Stream target, EdataFile edataFile, CancellationToken?token = null) { byte[] spaceBuffer = null; var sourceEdataHeader = edataFile.Header; source.Seek(sourceEdataHeader.FileOffset, SeekOrigin.Begin); uint filesContentLength = 0; foreach (EdataContentFile file in edataFile.ContentFiles) { //Cancel if requested; token.ThrowIfCanceledAndNotNull(); long oldOffset = file.Offset; file.Offset = target.Position - sourceEdataHeader.FileOffset; byte[] fileBuffer; if (file.IsContentLoaded) { fileBuffer = file.Content; file.Size = file.Content.Length; // To przenieść do klasy tak aby było ustawiane przy zmianie contnetu file.Checksum = MD5.Create().ComputeHash(fileBuffer); //To przyszło z dołu } else { fileBuffer = new byte[file.Size]; source.Seek(oldOffset + sourceEdataHeader.FileOffset, SeekOrigin.Begin); source.Read(fileBuffer, 0, fileBuffer.Length); } long spaceSize = GetSpaceSizeForFile(file); spaceBuffer = GetNewBufferIfNeeded(spaceBuffer, spaceSize); target.Write(fileBuffer, 0, fileBuffer.Length); target.Write(spaceBuffer, 0, (int)spaceSize); filesContentLength += (uint)fileBuffer.Length + (uint)spaceSize; } target.Seek(0x25, SeekOrigin.Begin); target.Write(BitConverter.GetBytes(filesContentLength), 0, 4); }
/// <summary> /// /// </summary> /// <param name="source"></param> /// <param name="edataFile"></param> /// <param name="token"></param> protected virtual void WriteLoadedContentByReplace(Stream source, EdataFile edataFile, CancellationToken?token = null) { byte[] spaceBuffer = null; var contentFiles = edataFile.ContentFiles.OfType <EdataContentFile>(); foreach (var contentFile in contentFiles) { token.ThrowIfCanceledAndNotNull(); if (!contentFile.IsContentLoaded) { continue; } long orginalContentSize = contentFile.Size; byte[] fileBuffer = contentFile.Content; contentFile.Size = fileBuffer.Length; contentFile.Checksum = MD5.Create().ComputeHash(fileBuffer); source.Seek(contentFile.TotalOffset, SeekOrigin.Begin); source.Write(fileBuffer, 0, fileBuffer.Length); long contentSizeDffierence = orginalContentSize - contentFile.ContentSize; if (contentSizeDffierence > 0) { spaceBuffer = GetNewBufferIfNeeded(spaceBuffer, contentSizeDffierence); source.Write(spaceBuffer, 0, (int)contentSizeDffierence); } //Overwriting whole space, up to the next file: //var nextFile = (i + 1 < contentFiles.Length) ? contentFiles[i + 1] : null; //long fileSectionLength = edataFile.Header.FileOffset + edataFile.Header.FileLenght; //long spaceBetweenFiles = ((nextFile != null) ? nextFile.TotalOffset : fileSectionLength) - // (currentFile.TotalOffset + currentFile.ContentSize); //spaceBuffer = (spaceBuffer == null) ? // new byte[spaceBetweenFiles] : ((spaceBetweenFiles > spaceBuffer.Length) ? // new byte[spaceBetweenFiles] : spaceBuffer); //source.Write(spaceBuffer, 0, (int)spaceBetweenFiles); } }
public EdataFile Read(byte[] rawEdata, bool loadContent, CancellationToken token) { //Cancel if requested; token.ThrowIfCancellationRequested(); EdataHeader header; IEnumerable <EdataContentFile> contentFiles; using (MemoryStream stream = new MemoryStream(rawEdata)) { if (!CanReadHeaderFromBuffer(rawEdata)) { throw new ArgumentException("Cannot read header from the buffer," + " because header size exceeds size of the buffer", "rawEdata"); } header = ReadHeader(stream); if (!CanReadDictionaryFromBuffer(rawEdata, header.DictOffset, header.DictLength)) { throw new ArgumentException("Cannot read dictionary from the buffer," + " because dictionary size exceeds size of the buffer", "rawEdata"); } if (header.Version != 2) { throw new NotSupportedException(String.Format("Edata Version {0} is currently not supported", header.Version)); } var dictRoot = ReadDcitionaryEntries(stream, header.DictOffset, header.DictLength); contentFiles = TranslateDictionaryEntriesToContentFiles(stream, header.FileOffset, dictRoot); if (loadContent) { LoadContentFiles(stream, contentFiles); } } EdataFile edataFile = new EdataFile(header, contentFiles); return(edataFile); }
protected bool CanUseReplacementWrite(EdataFile file) { //chyba zbedne to sotrtowaie, ale dla pewności... var contentFiles = file.ContentFiles .OfType <EdataContentFile>() .OrderBy(cf => cf.TotalOffset) .ToArray(); for (int i = 0; i < contentFiles.Length; ++i) { var currentFile = contentFiles[i]; if (!currentFile.IsContentLoaded) { continue; } var nextFile = i + 1 < contentFiles.Length ? contentFiles[i + 1] : null; if (nextFile != null) { if ((currentFile.TotalOffset + currentFile.ContentSize + MinBytesBetweenFiles) >= nextFile.TotalOffset) { return(false); } } else { if ((currentFile.TotalOffset + currentFile.ContentSize + MinBytesBetweenFiles) >= (file.Header.FileOffset + file.Header.FileLenght)) { return(false); } } } return(true); }
/// <summary> /// /// </summary> /// <param name="fileToWrite"></param> public void Write(EdataFile edataFile) { Write(edataFile, CancellationToken.None); }
public byte[] Write(EdataFile edata) { return(WriteContentInternal(edata)); }
/// <remarks> /// Method based on enohka's code. /// See more at: http://github.com/enohka/moddingSuite /// </remarks> protected virtual void WriteDictionary(Stream target, EdataFile edataFile) { var sourceEdataHeader = edataFile.Header; var contentFilesDict = edataFile .ContentFiles .OfType <EdataContentFile>() .ToDictionary(x => x.Id); target.Seek(sourceEdataHeader.DictOffset, SeekOrigin.Begin); long dictEnd = sourceEdataHeader.DictOffset + sourceEdataHeader.DictLength; uint id = 0; //Odtworzenie słownika while (target.Position < dictEnd) { var buffer = new byte[4]; target.Read(buffer, 0, 4); int fileGroupId = BitConverter.ToInt32(buffer, 0); if (fileGroupId == 0) { EdataContentFile curFile = contentFilesDict[id]; // FileEntrySize target.Seek(4, SeekOrigin.Current); buffer = BitConverter.GetBytes(curFile.Offset); target.Write(buffer, 0, buffer.Length); buffer = BitConverter.GetBytes(curFile.Size); target.Write(buffer, 0, buffer.Length); byte[] checkSum = curFile.Checksum; target.Write(checkSum, 0, checkSum.Length); string name = MiscUtilities.ReadString(target); if ((name.Length + 1) % 2 == 1) { target.Seek(1, SeekOrigin.Current); } id++; } else if (fileGroupId > 0) { target.Seek(4, SeekOrigin.Current); string name = MiscUtilities.ReadString(target); if ((name.Length + 1) % 2 == 1) { target.Seek(1, SeekOrigin.Current); } } } target.Seek(sourceEdataHeader.DictOffset, SeekOrigin.Begin); var dictBuffer = new byte[sourceEdataHeader.DictLength]; target.Read(dictBuffer, 0, dictBuffer.Length); //Overwriting checksum byte[] dictCheckSum = MD5.Create().ComputeHash(dictBuffer); target.Seek(0x31, SeekOrigin.Begin); target.Write(dictCheckSum, 0, dictCheckSum.Length); }
public byte[] Write(EdataFile edata, CancellationToken token) { return(WriteContentInternal(edata, token)); }
/// <summary> /// /// </summary> /// <param name="fileToWrite"></param> public void Write(EdataFile fileToWrite) { WriteContentInternal(fileToWrite); }
/// <summary> /// /// </summary> /// <param name="fileToWrite"></param> /// <param name="token"></param> public void Write(EdataFile fileToWrite, CancellationToken token) { WriteContentInternal(fileToWrite, token); }
/// <remarks> /// Method based on enohka's code. /// See more at: http://github.com/enohka/moddingSuite /// </remarks> protected virtual void WriteContentInternal(EdataFile edataFile, CancellationToken?token = null) { //Uważać tu na ścieżke jaka ma edata pełną czy relatywną... //No i dodatkowo od tego momentu może być pusta. A z racji tego że tylko możemy podmieniać edata nie pasuje //dodawać dodatkowy argument ścieżki do zapisu, bo to jasno wskazywało by że możemy zapisywać do dowolnej lokacji // a tak naprawde można tylko podmieniać edata. if (!File.Exists(edataFile.Path)) { throw new ArgumentException( String.Format("A following Edata file: \"{0}\" doesn't exist", edataFile.Path), "edataFile"); } //Cancel if requested; token.ThrowIfCanceledAndNotNull(); if (CanUseReplacementWrite(edataFile)) { using (var sourceEdata = new FileStream(edataFile.Path, FileMode.Open)) { WriteHeader(sourceEdata, edataFile); WriteLoadedContentByReplace(sourceEdata, edataFile, token); WriteDictionary(sourceEdata, edataFile); } } else { //Try in the current dir to avoid double file moving String temporaryEdataPath = GetTemporaryEdataPathInCurrentLocation(edataFile.Path); if ((new FileInfo(edataFile.Path).Length > (new DriveInfo(temporaryEdataPath).AvailableFreeSpace))) { temporaryEdataPath = TryGetTemporaryEdataPathWhereFree(edataFile.Path); if (temporaryEdataPath == null) { throw new IOException( String.Format("Not enough free disk space for rebuilding the \"{0}\" file.", edataFile.Path)); } } //To avoid nested try catches. FileStream sourceEdata = null; FileStream newEdata = null; try { sourceEdata = new FileStream(edataFile.Path, FileMode.Open); newEdata = new FileStream(temporaryEdataPath, FileMode.Create); WriteHeader(newEdata, edataFile); WriteNotLoadedContent(sourceEdata, newEdata, edataFile, token); WriteDictionary(newEdata, edataFile); //Free file handles before the file move and delete CloseEdataFilesStreams(sourceEdata, newEdata); //Replace temporary file File.Delete(edataFile.Path); File.Move(temporaryEdataPath, edataFile.Path); } finally { //Spr czy zostały już zwolnione...? CloseEdataFilesStreams(sourceEdata, newEdata); if (File.Exists(temporaryEdataPath)) { File.Delete(temporaryEdataPath); } } } }
/// <summary> /// /// </summary> /// <param name="edataFile"></param> /// <param name="token"></param> /// <returns></returns> public virtual byte[] Write(EdataFile edataFile, CancellationToken token) { //Cancel if requested; token.ThrowIfCancellationRequested(); var edataContentFiles = edataFile.ContentFiles.OfType <EdataContentFile>(); using (MemoryStream edataStream = new MemoryStream()) { if (!AnyContentFileExceedsAvailableSpace(edataFile) && !edataFile.HasContentFilesCollectionChanged) { var header = edataFile.Header; var dictRoot = CreateDictionaryEntries(edataContentFiles); uint dictOffset = header.DictOffset; uint dictLength = ComputeDictionaryLength(dictRoot); uint dictEnd = dictOffset + dictLength; //Clear the old part of file up to content. WritePadding(edataStream, 0, header.FileOffset); WriteLoadedContentByReplace(edataStream, edataContentFiles, token); AssignContentFilesInfoToDictEntries(edataContentFiles, dictRoot); var dictWriteInfo = WriteDictionary(edataStream, dictRoot, dictOffset); header.Checksum_V2 = Md5Hash.GetHash(dictWriteInfo.Checksum); header.DictLength = dictWriteInfo.Length; WriteHeader(edataStream, header, 0); } else { //W tym przypadku rozmieszczamy wszystko od zera wg wartości obliczonych. var dictRoot = CreateDictionaryEntries(edataContentFiles); uint dictOffset = GetDictionaryOffset(); uint dictLength = ComputeDictionaryLength(dictRoot); uint dictEnd = dictOffset + dictLength; uint fileOffset = GetFileOffset(dictEnd); WritePadding(edataStream, 0, fileOffset); var contentWriteInfo = WriteLoadedContent(edataStream, edataContentFiles, fileOffset, token); AssignContentFilesInfoToDictEntries(edataContentFiles, dictRoot); var dictWriteInfo = WriteDictionary(edataStream, dictRoot, dictOffset); var header = edataFile.Header; header.Checksum_V2 = Md5Hash.GetHash(dictWriteInfo.Checksum); header.DictOffset = dictOffset; header.DictLength = dictWriteInfo.Length; header.FileOffset = fileOffset; header.FileLenght = contentWriteInfo.Length; WriteHeader(edataStream, header, 0); } return(edataStream.ToArray()); } }
/// <summary> /// /// </summary> /// <param name="fileToWrite"></param> /// <param name="token"></param> public void Write(EdataFile edataFile, CancellationToken token) { //Uważać tu na ścieżke jaka ma edata pełną czy relatywną.. //No i dodatkowo od tego momentu może być pusta. A z racji tego że tylko możemy podmieniać edata nie pasuje //dodawać dodatkowy argument ścieżki do zapisu, bo to jasno wskazywało by że możemy zapisywać do dowolnej lokacji // a tak naprawde można tylko podmieniać edata. if (!File.Exists(edataFile.Path)) { throw new ArgumentException( String.Format("A following Edata file: \"{0}\" doesn't exist", edataFile.Path), "edataFile"); } //Cancel if requested; token.ThrowIfCancellationRequested(); var edataContentFiles = edataFile.ContentFiles.OfType <EdataContentFile>(); //Wypieprzyć z tąd wszystkie zależności od starych metod, i stworzyć nowe metody zapisu poszczegołnych elementów //realizujące nową koncepcję wykorzystujaca model wpsiów słownika i być może w przyszłosci samego słownika. //W przypadku konieczności odbudowy słownika trzeba poszerzyć określenie czy można użyć ReplacemnetWrite //Jeśli nie ma nowego pliku, i słownik mieści sie w miejsce starego, to można użyć replacement write //Jeśli natomiast dodano nowy plik, lub słownik nie miesci sie na miejsce starego to trzeba zbudować plik Edata od zera. //Update na dobrą sprawę można założyć, że jeśli nie ma zmian w plikach to słownik zawszę będzie się mieścił w miejsce starego. if (!AnyContentFileExceedsAvailableSpace(edataFile) && !edataFile.HasContentFilesCollectionChanged) { using (var sourceEdata = new FileStream(edataFile.Path, FileMode.Open)) { //Taka uwaga: Nie robić canceli w trakcie zapisu czy to nagłowka czy słownika, zeby w przypadku przerwania bez backapu zminimalizwoać // szanse na uszkodzenie modyifkowane go pliku. //W tym przypadku używamy starego rozmieszczenia danych żeby nie odbudowywac pliku od nowa. var header = edataFile.Header; var dictRoot = CreateDictionaryEntries(edataContentFiles); uint dictOffset = header.DictOffset; uint dictLength = ComputeDictionaryLength(dictRoot); uint dictEnd = dictOffset + dictLength; WriteLoadedContentByReplace(sourceEdata, edataContentFiles, token); AssignContentFilesInfoToDictEntries(edataContentFiles, dictRoot); //Clear the old part of file up to content. WritePadding(sourceEdata, 0, header.FileOffset); var dictWriteInfo = WriteDictionary(sourceEdata, dictRoot, dictOffset); header.Checksum_V2 = Md5Hash.GetHash(dictWriteInfo.Checksum); header.DictLength = dictWriteInfo.Length; WriteHeader(sourceEdata, header, 0); } } else { //Try in the current dir to avoid double file moving String temporaryEdataPath = PathUtilities.GetTemporaryPath(edataFile.Path); if ((new FileInfo(edataFile.Path).Length > (new DriveInfo(temporaryEdataPath).AvailableFreeSpace))) { temporaryEdataPath = TryGetTemporaryEdataPathWhereFree(edataFile.Path); if (temporaryEdataPath == null) { throw new IOException( String.Format("Not enough free disk space for rebuilding the \"{0}\" file.", edataFile.Path)); } } //To avoid too many nested try catches. FileStream sourceEdata = null; FileStream newEdata = null; try { sourceEdata = new FileStream(edataFile.Path, FileMode.Open); newEdata = new FileStream(temporaryEdataPath, FileMode.Create); //W tym przypadku rozmieszczamy wszystko od zera wg wartości obliczonych. var dictRoot = CreateDictionaryEntries(edataContentFiles); uint dictOffset = GetDictionaryOffset(); uint dictLength = ComputeDictionaryLength(dictRoot); uint dictEnd = dictOffset + dictLength; uint fileOffset = GetFileOffset(dictEnd); var contentWriteInfo = WriteNotLoadedContent(sourceEdata, newEdata, edataContentFiles, fileOffset, token); AssignContentFilesInfoToDictEntries(edataContentFiles, dictRoot); WritePadding(newEdata, 0, fileOffset); var dictWriteInfo = WriteDictionary(newEdata, dictRoot, dictOffset); var header = edataFile.Header; header.Checksum_V2 = Md5Hash.GetHash(dictWriteInfo.Checksum); header.DictOffset = dictOffset; header.DictLength = dictWriteInfo.Length; header.FileOffset = fileOffset; header.FileLenght = contentWriteInfo.Length; WriteHeader(newEdata, header, 0); //Free file handles before the file move and delete CloseEdataFilesStreams(sourceEdata, newEdata); //Replace temporary file File.Delete(edataFile.Path); File.Move(temporaryEdataPath, edataFile.Path); } finally { //Spr czy zostały już zwolnione...? CloseEdataFilesStreams(sourceEdata, newEdata); if (File.Exists(temporaryEdataPath)) { File.Delete(temporaryEdataPath); } } } }
/// <summary> /// /// </summary> /// <param name="edataFile"></param> /// <returns></returns> public virtual byte[] Write(EdataFile edataFile) { return(Write(edataFile, CancellationToken.None)); }