|
| 1 | +using System; |
| 2 | +using System.Collections.Generic; |
| 3 | +using System.IO; |
| 4 | +using System.Linq; |
| 5 | +using Ionic.Zlib; |
| 6 | +using LibHac.IO; |
| 7 | +using LibHac.IO.RomFs; |
| 8 | + |
| 9 | +namespace XbTool.Xb2 |
| 10 | +{ |
| 11 | + public class ArchiveFileSystem : IFileSystem |
| 12 | + { |
| 13 | + private Node[] Nodes { get; } |
| 14 | + public FileInfo[] FileInfo { get; } |
| 15 | + public byte[] StringTable { get; } |
| 16 | + public int Field4 { get; } |
| 17 | + public int NodeCount { get; } |
| 18 | + public int StringTableOffset { get; } |
| 19 | + public int StringTableLength { get; } |
| 20 | + public int NodeTableOffset { get; } |
| 21 | + public int NodeTableLength { get; } |
| 22 | + public int FileTableOffset { get; } |
| 23 | + public int FileCount { get; } |
| 24 | + public uint Key { get; } |
| 25 | + |
| 26 | + private IStorage DataFile { get; } |
| 27 | + public HierarchicalRomFileTable FileTable { get; } |
| 28 | + |
| 29 | + public ArchiveFileSystem(IFile headerFile, IFile dataFile) |
| 30 | + { |
| 31 | + var header = new byte[headerFile.GetSize()]; |
| 32 | + headerFile.Read(header, 0); |
| 33 | + |
| 34 | + DecryptArh(header); |
| 35 | + |
| 36 | + using (var stream = new MemoryStream(header)) |
| 37 | + using (var reader = new BinaryReader(stream)) |
| 38 | + { |
| 39 | + stream.Position = 4; |
| 40 | + Field4 = reader.ReadInt32(); |
| 41 | + NodeCount = reader.ReadInt32(); |
| 42 | + StringTableOffset = reader.ReadInt32(); |
| 43 | + StringTableLength = reader.ReadInt32(); |
| 44 | + NodeTableOffset = reader.ReadInt32(); |
| 45 | + NodeTableLength = reader.ReadInt32(); |
| 46 | + FileTableOffset = reader.ReadInt32(); |
| 47 | + FileCount = reader.ReadInt32(); |
| 48 | + Key = reader.ReadUInt32() ^ 0xF3F35353; |
| 49 | + |
| 50 | + stream.Position = StringTableOffset; |
| 51 | + StringTable = reader.ReadBytes(StringTableLength); |
| 52 | + |
| 53 | + Nodes = new Node[NodeCount]; |
| 54 | + stream.Position = NodeTableOffset; |
| 55 | + |
| 56 | + for (int i = 0; i < NodeCount; i++) |
| 57 | + { |
| 58 | + Nodes[i] = new Node |
| 59 | + { |
| 60 | + Next = reader.ReadInt32(), |
| 61 | + Prev = reader.ReadInt32() |
| 62 | + }; |
| 63 | + } |
| 64 | + |
| 65 | + FileInfo = new FileInfo[FileCount]; |
| 66 | + stream.Position = FileTableOffset; |
| 67 | + |
| 68 | + for (int i = 0; i < FileCount; i++) |
| 69 | + { |
| 70 | + FileInfo[i] = new FileInfo(reader); |
| 71 | + } |
| 72 | + |
| 73 | + AddAllFilenames(); |
| 74 | + } |
| 75 | + |
| 76 | + DataFile = dataFile.AsStorage(); |
| 77 | + |
| 78 | + FileTable = new HierarchicalRomFileTable(); |
| 79 | + var romFileInfo = new RomFileInfo(); |
| 80 | + |
| 81 | + File.WriteAllLines("archive.txt", FileInfo.Select(x => x.Filename)); |
| 82 | + |
| 83 | + for (int i = 0; i < FileInfo.Length; i++) |
| 84 | + { |
| 85 | + if (FileInfo[i].Filename == null) continue; |
| 86 | + |
| 87 | + romFileInfo.Offset = i; |
| 88 | + FileTable.AddFile(FileInfo[i].Filename, ref romFileInfo); |
| 89 | + } |
| 90 | + |
| 91 | + FileTable.TrimExcess(); |
| 92 | + } |
| 93 | + |
| 94 | + public IDirectory OpenDirectory(string path, OpenDirectoryMode mode) |
| 95 | + { |
| 96 | + path = PathTools.Normalize(path); |
| 97 | + |
| 98 | + if (!FileTable.TryOpenDirectory(path, out FindPosition position)) |
| 99 | + { |
| 100 | + throw new DirectoryNotFoundException(); |
| 101 | + } |
| 102 | + |
| 103 | + return new ArchiveDirectory(this, path, position, mode); |
| 104 | + } |
| 105 | + |
| 106 | + public IFile OpenFile(string path, OpenMode mode) |
| 107 | + { |
| 108 | + if (!FileTable.TryOpenFile(path, out RomFileInfo romFileInfo)) |
| 109 | + { |
| 110 | + throw new FileNotFoundException(); |
| 111 | + } |
| 112 | + |
| 113 | + FileInfo fileInfo = FileInfo[romFileInfo.Offset]; |
| 114 | + |
| 115 | + switch (fileInfo.Type) |
| 116 | + { |
| 117 | + case 2: |
| 118 | + var decompData = new byte[fileInfo.UncompressedSize]; |
| 119 | + Stream compStream = DataFile.Slice(fileInfo.Offset + 0x30, fileInfo.CompressedSize).AsStream(); |
| 120 | + |
| 121 | + using (var deflate = new ZlibStream(compStream, CompressionMode.Decompress, true)) |
| 122 | + { |
| 123 | + deflate.CopyTo(new MemoryStream(decompData), fileInfo.UncompressedSize); |
| 124 | + } |
| 125 | + |
| 126 | + return new ArchiveFile(decompData); |
| 127 | + case 0: |
| 128 | + return new ArchiveFile(DataFile, fileInfo.Offset, fileInfo.UncompressedSize); |
| 129 | + default: |
| 130 | + throw new InvalidDataException(); |
| 131 | + } |
| 132 | + } |
| 133 | + |
| 134 | + public bool DirectoryExists(string path) |
| 135 | + { |
| 136 | + path = PathTools.Normalize(path); |
| 137 | + |
| 138 | + return FileTable.TryOpenDirectory(path, out FindPosition _); |
| 139 | + } |
| 140 | + |
| 141 | + public bool FileExists(string path) |
| 142 | + { |
| 143 | + path = PathTools.Normalize(path); |
| 144 | + |
| 145 | + return FileTable.TryOpenFile(path, out RomFileInfo _); |
| 146 | + } |
| 147 | + |
| 148 | + public DirectoryEntryType GetEntryType(string path) |
| 149 | + { |
| 150 | + path = PathTools.Normalize(path); |
| 151 | + |
| 152 | + if (FileExists(path)) return DirectoryEntryType.File; |
| 153 | + if (DirectoryExists(path)) return DirectoryEntryType.Directory; |
| 154 | + |
| 155 | + throw new FileNotFoundException(path); |
| 156 | + } |
| 157 | + |
| 158 | + public void Commit() { } |
| 159 | + |
| 160 | + private void AddAllFilenames() |
| 161 | + { |
| 162 | + for (int i = 0; i < Nodes.Length; i++) |
| 163 | + { |
| 164 | + if (Nodes[i].Next >= 0 || Nodes[i].Prev < 0) continue; |
| 165 | + |
| 166 | + int offset = -Nodes[i].Next; |
| 167 | + while (StringTable[offset] != 0) |
| 168 | + { |
| 169 | + offset++; |
| 170 | + } |
| 171 | + offset++; // Skip null byte |
| 172 | + |
| 173 | + int fileId = BitConverter.ToInt32(StringTable, offset); |
| 174 | + FileInfo[fileId].Filename = GetStringFromEndNode(i); |
| 175 | + } |
| 176 | + } |
| 177 | + |
| 178 | + private string GetStringFromEndNode(int endNodeIdx) |
| 179 | + { |
| 180 | + int cur = endNodeIdx; |
| 181 | + Node curNode = Nodes[cur]; |
| 182 | + string nameSuffix = Stuff.GetUTF8Z(StringTable, -curNode.Next); |
| 183 | + var chars = new List<char>(nameSuffix.Reverse()); |
| 184 | + |
| 185 | + while (curNode.Next != 0) |
| 186 | + { |
| 187 | + int prev = curNode.Prev; |
| 188 | + Node prevNode = Nodes[prev]; |
| 189 | + chars.Add((char)(cur ^ prevNode.Next)); |
| 190 | + cur = prev; |
| 191 | + curNode = prevNode; |
| 192 | + } |
| 193 | + |
| 194 | + chars.Reverse(); |
| 195 | + return new string(chars.ToArray()); |
| 196 | + } |
| 197 | + |
| 198 | + public static void DecryptArh(byte[] file) |
| 199 | + { |
| 200 | + var filei = new int[file.Length / 4]; |
| 201 | + Buffer.BlockCopy(file, 0, filei, 0, file.Length); |
| 202 | + |
| 203 | + int key = (int)(filei[9] ^ 0xF3F35353); |
| 204 | + filei[9] = unchecked((int)0xF3F35353); |
| 205 | + |
| 206 | + int stringTableStart = filei[3] / 4; |
| 207 | + int nodeTableStart = filei[5] / 4; |
| 208 | + int stringTableEnd = stringTableStart + filei[4] / 4; |
| 209 | + int nodeTableEnd = nodeTableStart + filei[6] / 4; |
| 210 | + |
| 211 | + for (int i = stringTableStart; i < stringTableEnd; i++) |
| 212 | + { |
| 213 | + filei[i] ^= key; |
| 214 | + } |
| 215 | + |
| 216 | + for (int i = nodeTableStart; i < nodeTableEnd; i++) |
| 217 | + { |
| 218 | + filei[i] ^= key; |
| 219 | + } |
| 220 | + |
| 221 | + Buffer.BlockCopy(filei, 0, file, 0, file.Length); |
| 222 | + } |
| 223 | + |
| 224 | + private class Node |
| 225 | + { |
| 226 | + public int Next { get; set; } |
| 227 | + public int Prev { get; set; } |
| 228 | + } |
| 229 | + |
| 230 | + public void CreateDirectory(string path) => throw new NotSupportedException(); |
| 231 | + public void CreateFile(string path, long size, CreateFileOptions options) => throw new NotSupportedException(); |
| 232 | + public void DeleteDirectory(string path) => throw new NotSupportedException(); |
| 233 | + public void DeleteFile(string path) => throw new NotSupportedException(); |
| 234 | + public void RenameDirectory(string srcPath, string dstPath) => throw new NotSupportedException(); |
| 235 | + public void RenameFile(string srcPath, string dstPath) => throw new NotSupportedException(); |
| 236 | + } |
| 237 | +} |
0 commit comments