-
Notifications
You must be signed in to change notification settings - Fork 1
/
WadArchive.cs
183 lines (152 loc) · 5.42 KB
/
WadArchive.cs
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
using System.Collections.Frozen;
using System.Diagnostics;
using System.Text;
using Microsoft.Extensions.Logging;
using ShibuyaTools.Core;
namespace ShibuyaTools.Resources.Wad;
internal sealed class WadArchive : IDisposable
{
private readonly FileSource source;
private readonly WadHeader header;
private readonly long dataOffset;
private readonly FrozenSet<string> existingFileNames;
private readonly List<TargetFile> targetFileSourceList = [];
private Stream? stream;
public WadArchive(FileSource source)
{
this.source = source;
var stream = EnsureStream();
using var reader = CreateReader(stream);
header = WadHeader.Read(reader);
dataOffset = stream.Position;
existingFileNames = header.Files
.Select(file => file.Name)
.ToFrozenSet();
}
public IReadOnlyList<WadFile> Files => header.Files;
public void Dispose()
{
Close();
}
public bool Exists(string name) => existingFileNames.Contains(name);
public byte[] Read(WadFile file)
{
var stream = EnsureStream();
using var reader = CreateReader(stream);
stream.Position = dataOffset + file.Offset;
return reader.ReadBytes((int)file.Size);
}
public void Export(WadFile file, string path)
{
var stream = EnsureStream();
using var target = new FileTarget(path);
stream.Position = dataOffset + file.Offset;
stream.CopyBytesTo(target.Stream, file.Size);
target.Commit();
}
public void AddFile(WadFile file)
{
var body = new TargetBody.Internal(dataOffset + file.Offset, file.Size);
targetFileSourceList.Add(new TargetFile(Name: file.Name, Body: body));
}
public void AddFile(string name, FileInfo sourceInfo)
{
var body = new TargetBody.External(sourceInfo);
targetFileSourceList.Add(new TargetFile(Name: name, Body: body));
}
public void AddFile(string name, byte[] bytes)
{
var body = new TargetBody.Buffer(bytes);
targetFileSourceList.Add(new TargetFile(Name: name, Body: body));
}
public void Save(ILogger logger)
{
var targetFileOffset = 0L;
var targetFiles = new WadFile[targetFileSourceList.Count];
for (var i = 0; i < targetFiles.Length; i++)
{
var file = targetFileSourceList[i];
var pos = targetFileOffset;
var length = file.Body.GetLength();
targetFileOffset += length;
targetFiles[i] = new WadFile(
Name: file.Name,
RawOffset: pos,
RawSize: length);
}
var targetHeader = header with { Files = targetFiles };
Close();
logger.LogInformation("creating target...");
var scope = logger.BeginScope("creating target");
var progressReporter = new ProgressReporter(logger);
using var target = source.CreateTarget(progressReporter.ReportProgress);
scope?.Dispose();
logger.LogInformation("writing header...");
using var writer = new BinaryWriter(target.Stream);
targetHeader.WriteTo(writer);
writer.Flush();
var totalLength = target.Stream.Position + targetFileSourceList.Sum(file => file.Body.GetLength());
logger.LogInformation("writing content...");
scope = logger.BeginScope("writing content");
progressReporter.Restart();
foreach (var file in targetFileSourceList)
{
switch (file.Body)
{
case TargetBody.Internal(var offset, var length):
var stream = EnsureStream();
stream.Position = offset;
stream.CopyBytesTo(target.Stream, length);
break;
case TargetBody.External(var info):
using (var infoStream = info.OpenRead())
{
infoStream.CopyTo(target.Stream);
break;
}
case TargetBody.Buffer(var buffer):
target.Stream.Write(buffer);
break;
}
progressReporter.ReportProgress(
progress: new ProgressPayload<long>(
Total: totalLength,
Position: target.Stream.Position));
}
scope?.Dispose();
logger.LogDebug("writing wad done.");
Close();
target.Commit();
}
private Stream EnsureStream() => stream ??= source.OpenRead();
private static BinaryReader CreateReader(Stream stream)
{
return new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
}
private void Close()
{
var disposable = stream;
if (disposable is not null)
{
stream = null;
disposable?.Dispose();
}
}
private record TargetFile(string Name, TargetBody Body);
private abstract record TargetBody
{
public abstract long GetLength();
public record Internal(long Offset, long Length) : TargetBody
{
public override long GetLength() => Length;
}
public record External(FileInfo Info) : TargetBody
{
public override long GetLength() => Info.Length;
}
public record Buffer(byte[] Bytes) : TargetBody
{
public override long GetLength() => Bytes.LongLength;
}
}
}