Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
ChokuretsuTranslationUtility/HaruhiChokuretsuLib/Archive/ArchiveFile.cs /
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
305 lines (276 sloc)
13.3 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Collections.Generic; | |
| using System.IO; | |
| using System.Linq; | |
| using System.Text; | |
| namespace HaruhiChokuretsuLib.Archive | |
| { | |
| public class ArchiveFile<T> | |
| where T : FileInArchive, new() | |
| { | |
| public const int FirstMagicIntegerOffset = 0x20; | |
| public string FileName { get; set; } | |
| public int NumFiles { get; set; } | |
| public int MagicIntegerMsbMultiplier { get; set; } | |
| public int MagicIntegerLsbMultiplier { get; set; } | |
| public int MagicIntegerLsbAnd { get; set; } | |
| public int MagicIntegerMsbShift { get; set; } | |
| public int FileNamesLength { get; set; } | |
| public uint Unknown1 { get; set; } | |
| public uint Unknown2 { get; set; } | |
| public List<uint> MagicIntegers { get; set; } = new(); | |
| public List<uint> SecondHeaderNumbers { get; set; } = new(); | |
| public List<byte> FileNamesSection { get; set; } | |
| public List<T> Files { get; set; } = new(); | |
| public Dictionary<int, int> LengthToMagicIntegerMap { get; private set; } = new(); | |
| public static ArchiveFile<T> FromFile(string fileName) | |
| { | |
| byte[] archiveBytes = File.ReadAllBytes(fileName); | |
| return new ArchiveFile<T>(archiveBytes) { FileName = Path.GetFileName(fileName) }; | |
| } | |
| public ArchiveFile(byte[] archiveBytes) | |
| { | |
| // Convert the main header components | |
| NumFiles = BitConverter.ToInt32(archiveBytes.Take(4).ToArray()); | |
| MagicIntegerMsbMultiplier = BitConverter.ToInt32(archiveBytes.Skip(0x04).Take(4).ToArray()); | |
| MagicIntegerLsbMultiplier = BitConverter.ToInt32(archiveBytes.Skip(0x08).Take(4).ToArray()); | |
| MagicIntegerLsbAnd = BitConverter.ToInt32(archiveBytes.Skip(0x10).Take(4).ToArray()); | |
| MagicIntegerMsbShift = BitConverter.ToInt32(archiveBytes.Skip(0x0C).Take(4).ToArray()); | |
| Unknown1 = BitConverter.ToUInt32(archiveBytes.Skip(0x14).Take(4).ToArray()); | |
| Unknown2 = BitConverter.ToUInt32(archiveBytes.Skip(0x18).Take(4).ToArray()); | |
| // Grab all the magic integers | |
| for (int i = 0; i <= MagicIntegerLsbAnd; i++) | |
| { | |
| int length = GetFileLength((uint)i); | |
| if (!LengthToMagicIntegerMap.ContainsKey(length)) | |
| { | |
| LengthToMagicIntegerMap.Add(length, i); | |
| } | |
| } | |
| // Grab the other parts of the header (not used in-game, but we should keep them for fidelity) | |
| FileNamesLength = BitConverter.ToUInt16(archiveBytes.Skip(0x1C).Take(4).ToArray()); | |
| for (int i = FirstMagicIntegerOffset; i < (NumFiles * 4) + 0x20; i += 4) | |
| { | |
| MagicIntegers.Add(BitConverter.ToUInt32(archiveBytes.Skip(i).Take(4).ToArray())); | |
| } | |
| int firstNextPointer = FirstMagicIntegerOffset + MagicIntegers.Count * 4; | |
| for (int i = firstNextPointer; i < (NumFiles * 4) + firstNextPointer; i += 4) | |
| { | |
| SecondHeaderNumbers.Add(BitConverter.ToUInt32(archiveBytes.Skip(i).Take(4).ToArray())); | |
| } | |
| FileNamesSection = archiveBytes.Skip(0x20 + (NumFiles * 8)).Take(FileNamesLength).ToList(); | |
| // Calculate file names based on the substitution cipher | |
| List<string> filenames = new(); | |
| for (int i = 0; i < FileNamesSection.Count;) | |
| { | |
| byte[] nameBytes = FileNamesSection.Skip(i).TakeWhile(b => b != 0x00).ToArray(); | |
| for (int j = 0; j < nameBytes.Length; j++) | |
| { | |
| if ((nameBytes[j] >= 67 && nameBytes[j] <= 70) | |
| || (nameBytes[j] >= 75 && nameBytes[j] <= 76) | |
| || (nameBytes[j] >= 84 && nameBytes[j] <= 86) | |
| || (nameBytes[j] >= 91 && nameBytes[j] <= 94) | |
| || (nameBytes[j] >= 99 && nameBytes[j] <= 103) | |
| || nameBytes[j] >= 107) | |
| { | |
| nameBytes[j] -= 19; | |
| } | |
| else | |
| { | |
| nameBytes[j] -= 11; | |
| } | |
| } | |
| filenames.Add(Encoding.ASCII.GetString(nameBytes)); | |
| i += nameBytes.Length + 1; | |
| } | |
| // Add all the files to the archive from the magic integer offsets | |
| for (int i = 0; i < MagicIntegers.Count; i++) | |
| { | |
| int offset = GetFileOffset(MagicIntegers[i]); | |
| int compressedLength = GetFileLength(MagicIntegers[i]); | |
| byte[] fileBytes = archiveBytes.Skip(offset).Take(compressedLength).ToArray(); | |
| if (fileBytes.Length > 0) | |
| { | |
| T file = new(); | |
| try | |
| { | |
| file = FileManager<T>.FromCompressedData(fileBytes, offset); | |
| } | |
| catch (IndexOutOfRangeException) | |
| { | |
| Console.WriteLine($"Failed to parse file at 0x{i:X8} due to index out of range exception (most likely during decompression)"); | |
| } | |
| file.Name = filenames[i]; | |
| file.Offset = offset; | |
| file.MagicInteger = MagicIntegers[i]; | |
| file.Index = i + 1; | |
| file.Length = compressedLength; | |
| file.CompressedData = fileBytes.ToArray(); | |
| Files.Add(file); | |
| } | |
| } | |
| } | |
| public int GetFileOffset(uint magicInteger) | |
| { | |
| return (int)((magicInteger >> MagicIntegerMsbShift) * MagicIntegerMsbMultiplier); | |
| } | |
| public int GetFileLength(uint magicInteger) | |
| { | |
| // absolutely unhinged routine | |
| int magicLengthInt = 0x7FF + (int)((magicInteger & (uint)MagicIntegerLsbAnd) * (uint)MagicIntegerLsbMultiplier); | |
| int standardLengthIncrement = 0x800; | |
| if (magicLengthInt < standardLengthIncrement) | |
| { | |
| magicLengthInt = 0; | |
| } | |
| else | |
| { | |
| int magicLengthIntLeftShift = 0x1C; | |
| uint salt = (uint)magicLengthInt >> 0x04; | |
| if (standardLengthIncrement <= salt >> 0x0C) | |
| { | |
| magicLengthIntLeftShift -= 0x10; | |
| salt >>= 0x10; | |
| } | |
| if (standardLengthIncrement <= salt >> 0x04) | |
| { | |
| magicLengthIntLeftShift -= 0x08; | |
| salt >>= 0x08; | |
| } | |
| if (standardLengthIncrement <= salt) | |
| { | |
| magicLengthIntLeftShift -= 0x04; | |
| salt >>= 0x04; | |
| } | |
| magicLengthInt = (int)((uint)magicLengthInt << magicLengthIntLeftShift); | |
| standardLengthIncrement = 0 - standardLengthIncrement; | |
| bool carryFlag = Helpers.AddWillCauseCarry(magicLengthInt, magicLengthInt); | |
| magicLengthInt *= 2; | |
| int pcIncrement = magicLengthIntLeftShift * 12; | |
| for (; pcIncrement <= 0x174; pcIncrement += 0x0C) | |
| { | |
| // ADCS | |
| bool nextCarryFlag = Helpers.AddWillCauseCarry(standardLengthIncrement, (int)(salt << 1) + (carryFlag ? 1 : 0)); | |
| salt = (uint)standardLengthIncrement + (salt << 1) + (uint)(carryFlag ? 1 : 0); | |
| carryFlag = nextCarryFlag; | |
| // SUBCC | |
| if (!carryFlag) | |
| { | |
| salt -= (uint)standardLengthIncrement; | |
| } | |
| // ADCS | |
| nextCarryFlag = Helpers.AddWillCauseCarry(magicLengthInt, magicLengthInt + (carryFlag ? 1 : 0)); | |
| magicLengthInt = (magicLengthInt * 2) + (carryFlag ? 1 : 0); | |
| carryFlag = nextCarryFlag; | |
| } | |
| } | |
| return magicLengthInt * 0x800; | |
| } | |
| public int RecalculateFileOffset(T file) | |
| { | |
| return GetFileOffset(file.MagicInteger); | |
| } | |
| public uint GetNewMagicInteger(T file, int compressedLength) | |
| { | |
| uint offsetComponent = (uint)(file.Offset / MagicIntegerMsbMultiplier) << MagicIntegerMsbShift; | |
| int newLength = (compressedLength + 0x7FF) & ~0x7FF; // round to nearest 0x800 | |
| int newLengthComponent = LengthToMagicIntegerMap[newLength]; | |
| return offsetComponent | (uint)newLengthComponent; | |
| } | |
| public void AddFile(string filename) | |
| { | |
| T file = new(); | |
| Console.WriteLine($"Creating new file from {Path.GetFileName(filename)}... "); | |
| file.NewFile(filename); | |
| AddFile(file); | |
| } | |
| public void AddFile(T file) | |
| { | |
| file.Edited = true; | |
| file.CompressedData = Helpers.CompressData(file.GetBytes()); | |
| file.Offset = GetBytes().Length; | |
| NumFiles++; | |
| file.Index = Files.Max(f => f.Index) + 1; | |
| Console.Write($"New file #{file.Index:X3} will be placed at offset 0x{file.Offset:X8}... "); | |
| file.Length = file.CompressedData.Length + (0x800 - (file.CompressedData.Length % 0x800) == 0 ? 0 : 0x800 - (file.CompressedData.Length % 0x800)); | |
| file.MagicInteger = GetNewMagicInteger(file, file.CompressedData.Length); | |
| uint secondHeaderNumber = 0xC0C0C0C0; | |
| MagicIntegers.Add(file.MagicInteger); | |
| SecondHeaderNumbers.Add(secondHeaderNumber); | |
| FileNamesSection.RemoveRange(FileNamesSection.Count - 8, 8); | |
| Files.Add(file); | |
| } | |
| public byte[] GetBytes() | |
| { | |
| List<byte> bytes = new(); | |
| bytes.AddRange(BitConverter.GetBytes(NumFiles)); | |
| bytes.AddRange(BitConverter.GetBytes(MagicIntegerMsbMultiplier)); | |
| bytes.AddRange(BitConverter.GetBytes(MagicIntegerLsbMultiplier)); | |
| bytes.AddRange(BitConverter.GetBytes(MagicIntegerMsbShift)); | |
| bytes.AddRange(BitConverter.GetBytes(MagicIntegerLsbAnd)); | |
| bytes.AddRange(BitConverter.GetBytes(Unknown1)); | |
| bytes.AddRange(BitConverter.GetBytes(Unknown2)); | |
| bytes.AddRange(BitConverter.GetBytes(FileNamesLength)); | |
| foreach (uint magicInteger in MagicIntegers) | |
| { | |
| bytes.AddRange(BitConverter.GetBytes(magicInteger)); | |
| } | |
| foreach (uint secondInteger in SecondHeaderNumbers) | |
| { | |
| bytes.AddRange(BitConverter.GetBytes(secondInteger)); | |
| } | |
| bytes.AddRange(FileNamesSection); | |
| bytes.AddRange(new byte[Files[0].Offset - bytes.Count]); | |
| for (int i = 0; i < Files.Count; i++) | |
| { | |
| byte[] compressedBytes; | |
| if (!Files[i].Edited || Files[i].Data is null || Files[i].Data.Count == 0) | |
| { | |
| compressedBytes = Files[i].CompressedData; | |
| } | |
| else | |
| { | |
| compressedBytes = Helpers.CompressData(Files[i].GetBytes()); | |
| byte[] newMagicalIntegerBytes = BitConverter.GetBytes(GetNewMagicInteger(Files[i], compressedBytes.Length)); | |
| int magicIntegerOffset = FirstMagicIntegerOffset + ((Files[i].Index - 1) * 4); | |
| for (int j = 0; j < newMagicalIntegerBytes.Length; j++) | |
| { | |
| bytes[magicIntegerOffset + j] = newMagicalIntegerBytes[j]; | |
| } | |
| } | |
| bytes.AddRange(compressedBytes); | |
| if (i < Files.Count - 1) // If we aren't on the last file | |
| { | |
| int pointerShift = 0; | |
| while (bytes.Count % 0x10 != 0) | |
| { | |
| bytes.Add(0); | |
| } | |
| // If the current size of the archive we’ve constructed so far is greater than | |
| // the next file’s offset, that means we need to adjust the next file’s offset | |
| if (bytes.Count > Files[i + 1].Offset) | |
| { | |
| pointerShift = ((bytes.Count - Files[i + 1].Offset) / MagicIntegerMsbMultiplier) + 1; | |
| } | |
| if (pointerShift > 0) | |
| { | |
| // Calculate the new magic integer factoring in pointer shift | |
| Files[i + 1].Offset = ((Files[i + 1].Offset / MagicIntegerMsbMultiplier) + pointerShift) * MagicIntegerMsbMultiplier; | |
| int magicIntegerOffset = FirstMagicIntegerOffset + (i + 1) * 4; | |
| uint newMagicInteger = GetNewMagicInteger(Files[i + 1], Files[i + 1].Length); | |
| Files[i + 1].MagicInteger = newMagicInteger; | |
| MagicIntegers[i + 1] = newMagicInteger; | |
| bytes.RemoveRange(magicIntegerOffset, 4); | |
| bytes.InsertRange(magicIntegerOffset, BitConverter.GetBytes(Files[i + 1].MagicInteger)); | |
| } | |
| bytes.AddRange(new byte[Files[i + 1].Offset - bytes.Count]); | |
| } | |
| } | |
| while (bytes.Count % 0x800 != 0) | |
| { | |
| bytes.Add(0); | |
| } | |
| return bytes.ToArray(); | |
| } | |
| } | |
| } |