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
/*!
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
### 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 *;