Repository navigation
Expand file tree
/
Copy pathPacExtractor.cs
More file actions
137 lines (114 loc) · 5.5 KB
/
Copy pathPacExtractor.cs
File metadata and controls
137 lines (114 loc) · 5.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace RePacManager
{
public class PacExtractor
{
public static void ExtractPac(string pacFile, string outDir, Action<string> logCallback = null, Action<double> progressCallback = null)
{
void Log(string msg) => logCallback?.Invoke(msg);
using (FileStream fs = new FileStream(pacFile, FileMode.Open, FileAccess.Read))
using (BinaryReader br = new BinaryReader(fs))
{
// Parse PAC Header (2124 bytes)
string szVersion = ReadString(br, 44);
uint dwHiSize = br.ReadUInt32();
uint dwLoSize = br.ReadUInt32();
string productName = ReadString(br, 512);
string firmwareName = ReadString(br, 512);
uint partitionCount = br.ReadUInt32();
uint partitionsListStart = br.ReadUInt32();
Log($"Product: {productName}");
Log($"Firmware: {firmwareName}");
Log($"Partitions: {partitionCount}");
List<FileHeader> files = new List<FileHeader>();
fs.Seek(partitionsListStart, SeekOrigin.Begin);
for (int i = 0; i < partitionCount; i++)
{
long startPos = fs.Position;
uint length = br.ReadUInt32();
string partitionName = ReadString(br, 512);
string fileName = ReadString(br, 512);
string szFileName = ReadString(br, 504);
uint hiPartitionSize = br.ReadUInt32();
uint hiDataOffset = br.ReadUInt32();
uint loPartitionSize = br.ReadUInt32();
uint nFileFlag = br.ReadUInt32();
uint nCheckFlag = br.ReadUInt32();
uint loDataOffset = br.ReadUInt32();
// Skip remaining to reach 2580 total header size for file
// Read so far is 4+512+512+504+24 = 1556
// 2580 - 1556 = 1024
fs.Seek(1024, SeekOrigin.Current);
long size = ((long)hiPartitionSize << 32) | loPartitionSize;
long offset = ((long)hiDataOffset << 32) | loDataOffset;
files.Add(new FileHeader
{
FileName = fileName,
PartitionName = partitionName,
Size = size,
Offset = offset,
IsFile = nFileFlag == 1
});
}
Log($"\nExtracting {files.Count} files to: {outDir}");
Directory.CreateDirectory(outDir);
int fileIndex = 0;
foreach (var file in files)
{
fileIndex++;
if (progressCallback != null)
{
progressCallback((double)fileIndex / files.Count * 100);
}
if (file.Size == 0) continue;
string name = string.IsNullOrWhiteSpace(file.FileName) ? file.PartitionName : file.FileName;
if (string.IsNullOrWhiteSpace(name)) continue;
foreach(char c in Path.GetInvalidFileNameChars()) { name = name.Replace(c, '_'); }
string destPath = Path.Combine(outDir, name);
Log($"Extracting {name} ({file.Size} bytes)... ");
try
{
using (var destStream = new FileStream(destPath, FileMode.Create, FileAccess.Write))
{
fs.Seek(file.Offset, SeekOrigin.Begin);
byte[] buffer = new byte[81920];
long remaining = file.Size;
while (remaining > 0)
{
int toRead = (int)Math.Min(remaining, buffer.Length);
int read = fs.Read(buffer, 0, toRead);
if (read == 0) break;
destStream.Write(buffer, 0, read);
remaining -= read;
}
}
}
catch (Exception ex)
{
Log($"Failed: {ex.Message}");
}
}
Log("Extraction complete.");
}
}
private static string ReadString(BinaryReader br, int length)
{
byte[] bytes = br.ReadBytes(length);
string s = Encoding.Unicode.GetString(bytes);
int nullIndex = s.IndexOf('\0');
if (nullIndex >= 0) s = s.Substring(0, nullIndex);
return s;
}
private class FileHeader
{
public string FileName { get; set; }
public string PartitionName { get; set; }
public long Size { get; set; }
public long Offset { get; set; }
public bool IsFile { get; set; }
}
}
}