Skip to content

Commit 82091e9

Browse files
committed
Add ArchiveFileSystem class
1 parent ca9d9db commit 82091e9

File tree

5 files changed

+385
-2
lines changed

5 files changed

+385
-2
lines changed

XbTool/XbTool/Xb2/ArchiveDirectory.cs

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using System.Collections.Generic;
2+
using LibHac.IO;
3+
using LibHac.IO.RomFs;
4+
5+
namespace XbTool.Xb2
6+
{
7+
public class ArchiveDirectory : IDirectory
8+
{
9+
IFileSystem IDirectory.ParentFileSystem => ParentFileSystem;
10+
public ArchiveFileSystem ParentFileSystem { get; }
11+
public string FullPath { get; }
12+
public OpenDirectoryMode Mode { get; }
13+
14+
private FindPosition InitialPosition { get; }
15+
16+
public ArchiveDirectory(ArchiveFileSystem fs, string path, FindPosition position, OpenDirectoryMode mode)
17+
{
18+
ParentFileSystem = fs;
19+
InitialPosition = position;
20+
FullPath = path;
21+
Mode = mode;
22+
}
23+
24+
public IEnumerable<DirectoryEntry> Read()
25+
{
26+
FindPosition position = InitialPosition;
27+
HierarchicalRomFileTable tab = ParentFileSystem.FileTable;
28+
29+
if (Mode.HasFlag(OpenDirectoryMode.Directories))
30+
{
31+
while (tab.FindNextDirectory(ref position, out string name))
32+
{
33+
yield return new DirectoryEntry(name, FullPath + '/' + name, DirectoryEntryType.Directory, 0);
34+
}
35+
}
36+
37+
if (Mode.HasFlag(OpenDirectoryMode.Files))
38+
{
39+
while (tab.FindNextFile(ref position, out RomFileInfo info, out string name))
40+
{
41+
yield return new DirectoryEntry(name, FullPath + '/' + name, DirectoryEntryType.File, info.Length);
42+
}
43+
}
44+
}
45+
46+
public int GetEntryCount()
47+
{
48+
int count = 0;
49+
50+
FindPosition position = InitialPosition;
51+
HierarchicalRomFileTable tab = ParentFileSystem.FileTable;
52+
53+
if (Mode.HasFlag(OpenDirectoryMode.Directories))
54+
{
55+
while (tab.FindNextDirectory(ref position, out string _))
56+
{
57+
count++;
58+
}
59+
}
60+
61+
if (Mode.HasFlag(OpenDirectoryMode.Files))
62+
{
63+
while (tab.FindNextFile(ref position, out RomFileInfo _, out string _))
64+
{
65+
count++;
66+
}
67+
}
68+
69+
return count;
70+
}
71+
}
72+
}

XbTool/XbTool/Xb2/ArchiveFile.cs

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System;
2+
using LibHac.IO;
3+
4+
namespace XbTool.Xb2
5+
{
6+
public class ArchiveFile : FileBase
7+
{
8+
private IStorage BaseStorage { get; }
9+
private long Offset { get; }
10+
private long Size { get; }
11+
12+
private bool IsCompressed { get; set; }
13+
private byte[] FileData { get; set; }
14+
15+
public ArchiveFile(byte[] file)
16+
{
17+
FileData = file;
18+
IsCompressed = true;
19+
Size = file.Length;
20+
}
21+
22+
public ArchiveFile(IStorage baseStorage, long offset, long size)
23+
{
24+
Mode = OpenMode.Read;
25+
BaseStorage = baseStorage;
26+
Offset = offset;
27+
Size = size;
28+
}
29+
30+
public override int Read(Span<byte> destination, long offset)
31+
{
32+
int toRead = ValidateReadParamsAndGetSize(destination, offset);
33+
34+
if (IsCompressed)
35+
{
36+
FileData.CopyTo(destination.Slice((int)offset, toRead));
37+
}
38+
else
39+
{
40+
long storageOffset = Offset + offset;
41+
BaseStorage.Read(destination.Slice(0, toRead), storageOffset);
42+
}
43+
44+
return toRead;
45+
}
46+
47+
public override long GetSize() => Size;
48+
49+
public override void Write(ReadOnlySpan<byte> source, long offset) => throw new NotSupportedException();
50+
public override void Flush() => throw new NotSupportedException();
51+
public override void SetSize(long size) => throw new NotSupportedException();
52+
}
53+
}
+237
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
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+
}

XbTool/XbTool/Xb2/FS/Create.cs

+22-1
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,30 @@ public static IFileSystem CreateFileSystem(string sdPath)
1414
SwitchFs sdFs = OpenSdCard(sdPath);
1515
Application xb2App = sdFs.Applications[0x0100E95004038000];
1616

17+
IFileSystem mainFs = xb2App.Patch.MainNca.OpenSectionFileSystem(1, IntegrityCheckLevel.ErrorOnInvalid);
18+
IFile mainArh = mainFs.OpenFile("/bf2.arh", OpenMode.Read);
19+
IFile mainArd = mainFs.OpenFile("/bf2.ard", OpenMode.Read);
20+
21+
var mainArchiveFs = new ArchiveFileSystem(mainArh, mainArd);
22+
1723
var fsList = new List<IFileSystem>();
1824
fsList.Add(xb2App.Patch.MainNca.OpenSectionFileSystem(1, IntegrityCheckLevel.ErrorOnInvalid));
19-
fsList.AddRange(xb2App.AddOnContent.Select(x => x.MainNca.OpenSectionFileSystem(0, IntegrityCheckLevel.ErrorOnInvalid)));
25+
fsList.Add(mainArchiveFs);
26+
27+
foreach (Title aoc in xb2App.AddOnContent.OrderBy(x => x.Id))
28+
{
29+
IFileSystem aocFs = aoc.MainNca.OpenSectionFileSystem(0, IntegrityCheckLevel.ErrorOnInvalid);
30+
fsList.Add(aocFs);
31+
32+
if (aoc.Id == 0x0100E95004039001)
33+
{
34+
IFile aocArh = aocFs.OpenFile("/aoc1.arh", OpenMode.Read);
35+
IFile aocArd = aocFs.OpenFile("/aoc1.ard", OpenMode.Read);
36+
37+
var aocArchiveFs = new ArchiveFileSystem(aocArh, aocArd);
38+
fsList.Add(aocArchiveFs);
39+
}
40+
}
2041

2142
return new LayeredFileSystem(fsList);
2243
}

0 commit comments

Comments
 (0)