-
Notifications
You must be signed in to change notification settings - Fork 7
/
PathPackage.cs
148 lines (114 loc) · 4.5 KB
/
PathPackage.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
using System;
namespace Marathon.Formats.Package
{
/// <summary>
/// File base for the PathObj.bin format.
/// <para>Used in SONIC THE HEDGEHOG for defining valid objects for common_path_obj.</para>
/// </summary>
public class PathPackage : FileBase
{
public PathPackage() { }
public PathPackage(string file, bool serialise = false)
{
switch (Path.GetExtension(file))
{
case ".json":
{
PathObjects = JsonDeserialise<List<PathObject>>(file);
// Save extension-less JSON (exploiting .NET weirdness, because it doesn't omit all extensions).
if (serialise)
Save(Path.GetFileNameWithoutExtension(file));
break;
}
default:
{
Load(file);
if (serialise)
JsonSerialise(PathObjects);
break;
}
}
}
public override string Extension { get; } = ".bin";
public List<PathObject> PathObjects { get; set; } = [];
public override void Load(Stream stream)
{
BINAReader reader = new(stream);
// Read the first entry's name to get the string table's position.
uint stringTablePosition = reader.ReadUInt32();
reader.JumpBehind(4);
// Loop through and read entries.
while (reader.BaseStream.Position < (stringTablePosition - 4) + 0x20)
{
var entry = new PathObject();
// Read string offsets.
uint nameOffset = reader.ReadUInt32();
uint modelOffset = reader.ReadUInt32();
uint animOffset = reader.ReadUInt32();
uint textOffset = reader.ReadUInt32();
uint matAnimOffset = reader.ReadUInt32();
long position = reader.BaseStream.Position;
if (nameOffset != 0)
entry.Name = reader.ReadNullTerminatedString(false, nameOffset, true);
if (modelOffset != 0)
entry.Model = reader.ReadNullTerminatedString(false, modelOffset, true);
if (animOffset != 0)
entry.Animation = reader.ReadNullTerminatedString(false, animOffset, true);
if (textOffset != 0)
entry.Text = reader.ReadNullTerminatedString(false, textOffset, true);
if (matAnimOffset != 0)
entry.MaterialAnimation = reader.ReadNullTerminatedString(false, matAnimOffset, true);
PathObjects.Add(entry);
reader.JumpTo(position);
}
}
public override void Save(Stream stream)
{
BINAWriter writer = new(stream);
for (int i = 0; i < PathObjects.Count; i++)
{
var pathObj = PathObjects[i];
writer.AddString($"Name{i}", pathObj.Name);
writer.AddString($"Model{i}", pathObj.Model);
writer.AddString($"Animation{i}", pathObj.Animation);
writer.AddString($"Text{i}", pathObj.Text);
writer.AddString($"MaterialAnimation{i}", pathObj.MaterialAnimation);
}
writer.WriteNulls(4);
writer.FinishWrite();
}
}
public class PathObject
{
/// <summary>
/// Name of this object.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Model this object should use.
/// </summary>
public string Model { get; set; }
/// <summary>
/// Animation this object should use.
/// </summary>
public string Animation { get; set; }
/// <summary>
/// TODO: Unknown.
/// </summary>
public string Text { get; set; }
/// <summary>
/// Material animation this object should use.
/// </summary>
public string MaterialAnimation { get; set; }
public PathObject() { }
public PathObject(string in_name, string in_model, string in_animation, string in_text, string in_materialAnimation)
{
Name = in_name;
Model = in_model;
Animation = in_animation;
Text = in_text;
MaterialAnimation = in_materialAnimation;
}
public override string ToString() => Name;
}
}