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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
/*!
Types for reading and writing League of Legends
property bin files (`.bin`).
Property bins are hierarchical data structures used throughout League's
game data. They contain objects with typed properties that can reference
other objects and external files.
## Quick Start
### Reading a bin file
```no_run
use std::fs::File;
use ltk_meta::Bin;
let mut file = File::open("data.bin")?;
let tree = Bin::from_reader(&mut file)?;
for (path_hash, object) in &tree.objects {
println!("Object {:08x} has {} properties", path_hash, object.properties.len());
}
# Ok::<(), Box<dyn std::error::Error>>(())
```
### Creating a bin file programmatically
The [`concrete`] module pins the metadata parameter to [`NoMeta`], so nothing
generic needs spelling:
```
use ltk_meta::concrete::{values, Bin, BinObject};
// Using the builder pattern
let tree = Bin::builder()
.dependency("common.bin")
.object(
BinObject::builder(0x12345678u32, 0xABCDEF00u32)
.property(0x1111, values::I32::new(42))
.property(0x2222, values::String::from("hello"))
.build()
)
.build();
// Or using the simple constructor
let tree = Bin::new(
[BinObject::new(0x1234u32, 0x5678u32)],
["dependency.bin"],
);
```
[`NoMeta`]: crate::property::NoMeta
### Streaming a bin file
Reading everything is the wrong shape for harvesting hashes across thousands of files or
peeking at one header. The [`stream`] module mounts a `PROP` file the way `ltk_wad::Wad`
mounts an archive and reads only what is asked for:
```no_run
use std::fs::File;
use ltk_meta::concrete::BinStream;
let mut stream = BinStream::mount(File::open("data.bin")?)?;
println!("version {}, {} objects", stream.version(), stream.class_hashes().len());
for entry in stream.entries() {
let entry = entry?;
println!("{:08x}: {:08x}", entry.path_hash, entry.class_hash);
}
# Ok::<(), Box<dyn std::error::Error>>(())
```
Descending into an object buffers its declared byte range once and views it in place, so
iteration and descent to any depth cost no I/O and materialize nothing:
```no_run
use std::fs::File;
use ltk_meta::{concrete::BinStream, ValueView};
let mut stream = BinStream::mount(File::open("data.bin")?)?;
let mut objects = stream.objects();
while let Some(mut object) = objects.next()? {
for property in object.view()?.properties() {
if let ValueView::String(text) = property?.value_view()? {
println!("{text}");
}
}
}
# Ok::<(), Box<dyn std::error::Error>>(())
```
[`Bin::from_reader`] is itself `BinStream::mount` plus [`BinStream::into_bin`], so the eager
tree and the streaming surface are one parser and cannot drift.
### Modifying a bin file
```no_run
use std::fs::File;
use std::io::Cursor;
use ltk_meta::{Bin, BinObject};
let mut file = File::open("data.bin")?;
let mut tree = Bin::from_reader(&mut file)?;
// Add a new object
tree.add_object(BinObject::new(0x11112222, 0x33334444));
// Remove an object
tree.remove_object(0x55556666);
// Write back
let mut output = Cursor::new(Vec::new());
tree.to_writer(&mut output)?;
# Ok::<(), Box<dyn std::error::Error>>(())
```
### Reading an override bin
Some bins are `PTCH` files: a patch of deletions, extra objects and property patch
records applied over one base bin. They are [`BinOverride`], not [`Bin`].
```no_run
use std::fs::File;
use ltk_meta::BinOverride;
let mut file = File::open("uiflipped.bin")?;
let patch_bin = BinOverride::from_reader(&mut file)?;
for patch in &patch_bin.patches {
// e.g. `4a47c414 Position.Anchors.Anchor = Vector2`
println!("{:08x} {} = {:?}", patch.object_hash, patch.path, patch.kind());
}
# Ok::<(), Box<dyn std::error::Error>>(())
```
### Applying an override bin
A patch only means something over the bin it patches. [`BinOverride::apply`] lays it over one,
in the order the client does, and reports what it could not apply instead of failing - which is
also what the client does with a stale path. [`BinOverride::check`] answers the same question
without touching anything, which is what to ask after a game update.
```no_run
use std::fs::File;
use ltk_meta::{Bin, BinOverride};
let mut base = Bin::from_reader(&mut File::open("uibase.bin")?)?;
let patch_bin = BinOverride::from_reader(&mut File::open("uiflipped.bin")?)?;
// Does it still fit?
let report = patch_bin.check(&base);
println!("{report}"); // "109 applied (5 inserted), 0 skipped, ..."
if report.is_clean() {
patch_bin.apply(&mut base);
}
# Ok::<(), Box<dyn std::error::Error>>(())
```
### Addressing a property by path
[`PropertyPath`](path::PropertyPath) is the language a patch record uses to name what it
overrides, and it works on any object: `Position.UIRect.Size`, `Elements[3]`,
`Lookup{"weapon"}`.
```no_run
use std::fs::File;
use ltk_meta::{path::PropertyPath, property::values, Bin};
let mut bin = Bin::from_reader(&mut File::open("uibase.bin")?)?;
let anchor = PropertyPath::new("Position.Anchors.Anchor")?;
println!("{:?}", bin.resolve(0x4a47c414_u32, &anchor)?);
// `patch` follows the client's type rule: the shape has to match, or nothing changes.
bin.patch(
0x4a47c414_u32,
&anchor,
values::Vector2::new(glam::Vec2::new(0.0, 1.0)).into(),
)?;
# Ok::<(), Box<dyn std::error::Error>>(())
```
### Reading a `.bin` of unknown kind
The extension does not say which kind a file is, so either read it as a [`BinFile`] and
match on what came back, or ask [`BinKind::identify_from_reader`] and call the reader you
want. It leaves the magic in place, so the same reader can be handed straight on.
```no_run
use std::fs::File;
use ltk_meta::{Bin, BinFile, BinKind, BinOverride};
let mut file = File::open("unknown.bin")?;
match BinFile::from_reader(&mut file)? {
BinFile::Prop(bin) => println!("{} objects", bin.objects.len()),
BinFile::Override(patch_bin) => println!("{} patches", patch_bin.patches.len()),
}
let mut file = File::open("unknown.bin")?;
match BinKind::identify_from_reader(&mut file)? {
BinKind::Prop => { let bin = Bin::from_reader(&mut file)?; }
BinKind::Override => { let patch_bin = BinOverride::from_reader(&mut file)?; }
}
# Ok::<(), Box<dyn std::error::Error>>(())
```
*/
pub use ;
pub use *;
pub use ;
pub use ;
pub use ;
pub use *;