Skip to content
This repository was archived by the owner on Jan 19, 2025. It is now read-only.

Commit 9e4c640

Browse files
committed
Support inflating fme/dcs scenes
1 parent 344827d commit 9e4c640

File tree

3 files changed

+95
-49
lines changed

3 files changed

+95
-49
lines changed

Src/Apps/ArkHelper/Apps/Ark2DirApp.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Mackiloha.App;
66
using Mackiloha.App.Extensions;
77
using Mackiloha.Ark;
8+
using Mackiloha.Chunk;
89
using Mackiloha.CSV;
910
using Mackiloha.IO;
1011
using Mackiloha.Milo;
@@ -18,11 +19,28 @@ public class Ark2DirApp
1819
{
1920
protected readonly ILogManager LogManager;
2021
protected readonly IScriptHelper ScriptHelper;
22+
protected readonly HashSet<string> ChunkExtensions;
2123

2224
public Ark2DirApp(ILogManager logManager, IScriptHelper scriptHelper)
2325
{
2426
LogManager = logManager;
2527
ScriptHelper = scriptHelper;
28+
29+
string[] platforms = ["durango", "xbox"];
30+
string[] formats = [
31+
"char",
32+
"cliptype",
33+
"dir",
34+
"entity",
35+
"layer",
36+
"scene",
37+
"song",
38+
"uiscreen",
39+
];
40+
41+
ChunkExtensions = new HashSet<string>(
42+
platforms.SelectMany(p => formats.Select(f => $"{f}_{p}")),
43+
StringComparer.InvariantCultureIgnoreCase);
2644
}
2745

2846
private string CombinePath(string basePath, string path)
@@ -142,11 +160,17 @@ public void Parse(Ark2DirOptions op)
142160
&& miloRegex.IsMatch(x.FullPath))
143161
.ToList();
144162

163+
var chunksToInflate = ark.Entries
164+
.Where(x => op.InflateMilos
165+
&& ChunkExtensions.Contains(x.Extension))
166+
.ToList();
167+
145168
var entriesToExtract = ark.Entries
146169
.Where(x => op.ExtractAll)
147170
.Except(scriptsToConvert)
148171
.Except(texturesToConvert)
149172
.Except(milosToInflate)
173+
.Except(chunksToInflate)
150174
.ToList();
151175

152176
foreach (var arkEntry in entriesToExtract)
@@ -230,6 +254,16 @@ var p when p.EndsWith("_xbox", StringComparison.InvariantCultureIgnoreCase) => P
230254
Log.Information("Wrote \"{ExtractedMiloPath}\"", extPath);
231255
}
232256

257+
foreach (var chunkEntry in chunksToInflate)
258+
{
259+
var filePath = ExtractEntry(ark, chunkEntry, CombinePath(op.OutputPath, chunkEntry.FullPath));
260+
261+
// Inflate chunk
262+
Chunk.DecompressChunkFile(filePath, filePath);
263+
264+
Log.Information("Wrote \"{FilePath}\"", filePath);
265+
}
266+
233267
foreach (var csvEntry in csvsToConvert)
234268
{
235269
var csvStream = ark.GetArkEntryFileStream(csvEntry);

Src/Apps/ArkHelper/Options/Ark2DirOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class Ark2DirOptions : BaseOptions
1919
[Option('t', "convertTextures", HelpText = "Convert textures to .png", Hidden = true)]
2020
public bool ConvertTextures { get; set; }
2121

22-
[Option('m', "inflateMilos", HelpText = "Inflate milo archives (decompress)")]
22+
[Option('m', "inflateMilos", HelpText = "Inflate milo/scene archives (decompress)")]
2323
public bool InflateMilos { get; set; }
2424

2525
[Option('x', "extractMilos", HelpText = "Extract milo archives (can't be used with decompress)", Hidden = true)]

Src/Core/Mackiloha/Chunk/Chunk.cs

Lines changed: 60 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,65 @@
1-
/*
2-
All bytes are in big endian order.
1+
namespace Mackiloha.Chunk;
32

4-
It looks like milo files were replaced with this. Max Block Size = 0x10000 (2^16)
5-
6-
BYTES(4) - "CHNK"
7-
INT32 - Uknown - Always 255?
8-
INT32 - Block Count
9-
INT32 - Largest Block (Uncompressed)
10-
INT16 - Always 1
11-
INT16 - Always 2
12-
BlockDetails[Block Count]
13-
14-
* ----Block Details----
15-
* =====================
16-
* INT32 - Size
17-
* INT32 - Decompressed Size
18-
* Bool? - If "01 00 00 00", then it's compressed.
19-
* INT32 - Offset
20-
21-
Begin ZLib'd blocks!
22-
*/
23-
24-
namespace Mackiloha.Chunk;
25-
26-
// Successor to Milo container (Used in FME/RBVR)
3+
// Successor to Milo container (Used in FME/DCS/RBVR)
274
public class Chunk
285
{
296
private const uint CHNK_MAGIC = 0x43484E4B; // "CHNK"
7+
private const uint IS_COMPRESSED = 0x01_00_00_00;
308

319
public Chunk()
3210
{
3311
Entries = new List<ChunkEntry>();
3412
}
3513

36-
public void WriteToFile(string outPath, bool noHeader = false)
14+
public void WriteToFile(string outPath, bool writeHeader = true)
3715
{
38-
using (FileStream fs = File.OpenWrite(outPath))
39-
{
40-
WriteToStream(fs, noHeader);
41-
}
16+
using var fs = File.OpenWrite(outPath);
17+
WriteToStream(fs, writeHeader);
4218
}
4319

44-
public void WriteToStream(Stream stream, bool noHeader)
20+
public void WriteToStream(Stream stream, bool writeHeader = true)
4521
{
4622
AwesomeWriter aw = new AwesomeWriter(stream, true);
4723

48-
if (!noHeader)
24+
if (writeHeader)
4925
{
26+
int endianFlag = 0xFF;
27+
short extraShort = 2;
28+
29+
if (IsDurango)
30+
{
31+
endianFlag = 0x1FF;
32+
extraShort = 5;
33+
}
34+
5035
aw.Write(CHNK_MAGIC);
51-
aw.Write((int)255);
36+
aw.Write((int)endianFlag);
37+
38+
aw.BigEndian = !IsDurango;
39+
5240
aw.Write(Entries.Count);
5341
aw.Write(Entries.Max(x => x.Data.Length));
5442
aw.Write((short)1);
55-
aw.Write((short)2);
43+
aw.Write((short)extraShort);
5644

57-
int currentIdx = 20 + (Entries.Count << 2);
45+
int currentIdx = 20 + (Entries.Count * 16);
5846

5947
// Writes block details
6048
foreach (ChunkEntry entry in Entries)
6149
{
6250
aw.Write(entry.Data.Length);
6351
aw.Write(entry.Data.Length);
6452

65-
aw.Write((int)(entry.Compressed ? 1 : 0));
66-
aw.Write(currentIdx);
53+
if (IsDurango)
54+
{
55+
aw.Write(currentIdx);
56+
aw.Write((int)(entry.Compressed ? IS_COMPRESSED : 0));
57+
}
58+
else
59+
{
60+
aw.Write((int)(entry.Compressed ? IS_COMPRESSED : 0));
61+
aw.Write(currentIdx);
62+
}
6763

6864
currentIdx += entry.Data.Length;
6965
}
@@ -73,13 +69,15 @@ public void WriteToStream(Stream stream, bool noHeader)
7369
Entries.ForEach(x => aw.Write(x.Data));
7470
}
7571

76-
public static void DecompressChunkFile(string inPath, string outPath, bool noHeader)
72+
public static void DecompressChunkFile(string inPath, string outPath, bool writeHeader = true)
7773
{
78-
using (FileStream fs = File.OpenRead(inPath))
74+
Chunk chunk;
75+
using (var fs = File.OpenRead(inPath))
7976
{
80-
Chunk chunk = ReadFromStream(fs);
81-
chunk.WriteToFile(outPath, noHeader);
77+
chunk = ReadFromStream(fs);
8278
}
79+
80+
chunk.WriteToFile(outPath, writeHeader);
8381
}
8482

8583
private static Chunk ReadFromStream(Stream stream)
@@ -89,9 +87,15 @@ private static Chunk ReadFromStream(Stream stream)
8987

9088
if (ar.ReadUInt32() != CHNK_MAGIC) return chunk;
9189

92-
ar.BaseStream.Position += 4; // Always 255?
90+
var flag = ar.ReadUInt32();
91+
if ((flag & 0x100) != 0)
92+
{
93+
chunk.IsDurango = true;
94+
ar.BigEndian = false;
95+
}
96+
9397
int blockCount = ar.ReadInt32();
94-
ar.BaseStream.Position += 8; // Skips 1, 2 (16-bits)
98+
ar.BaseStream.Position += 8; // Skips 1, 2/5 (16-bits)
9599

96100
int[] blockSize = new int[blockCount];
97101
bool[] compressed = new bool[blockCount]; // Uncompressed by default
@@ -102,10 +106,17 @@ private static Chunk ReadFromStream(Stream stream)
102106
blockSize[i] = ar.ReadInt32();
103107
ar.BaseStream.Position += 4; // Decompressed size (Not needed)
104108

105-
// Sets as compressed if it meets the requirement
106-
compressed[i] = ar.ReadInt32() == 0x1000000;
107-
108-
ar.BaseStream.Position += 4; // Offset (Not needed)
109+
// Fields are swapped depending on platform
110+
if (chunk.IsDurango)
111+
{
112+
ar.BaseStream.Position += 4; // Offset (Not needed)
113+
compressed[i] = ar.ReadInt32() == IS_COMPRESSED;
114+
}
115+
else
116+
{
117+
compressed[i] = ar.ReadInt32() == IS_COMPRESSED;
118+
ar.BaseStream.Position += 4; // Offset (Not needed)
119+
}
109120
}
110121

111122
for (int i = 0; i < blockCount; i++)
@@ -133,7 +144,8 @@ private static Chunk ReadFromStream(Stream stream)
133144
return chunk;
134145
}
135146

136-
public List<ChunkEntry> Entries;
147+
public bool IsDurango { get; set; }
148+
public List<ChunkEntry> Entries { get; set; }
137149
}
138150

139151
public class ChunkEntry

0 commit comments

Comments
 (0)