fivefury 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fivefury-0.1.0/MANIFEST.in +1 -0
- fivefury-0.1.0/PKG-INFO +262 -0
- fivefury-0.1.0/README.md +253 -0
- fivefury-0.1.0/fivefury/__init__.py +185 -0
- fivefury-0.1.0/fivefury/_native.py +172 -0
- fivefury-0.1.0/fivefury/binary.py +100 -0
- fivefury-0.1.0/fivefury/cache.py +1702 -0
- fivefury-0.1.0/fivefury/crypto.py +634 -0
- fivefury-0.1.0/fivefury/data/lut.dat +0 -0
- fivefury-0.1.0/fivefury/data/magic.dat +0 -0
- fivefury-0.1.0/fivefury/data/ng.dat +0 -0
- fivefury-0.1.0/fivefury/extensions.py +769 -0
- fivefury-0.1.0/fivefury/gamefile.py +118 -0
- fivefury-0.1.0/fivefury/hashing.py +39 -0
- fivefury-0.1.0/fivefury/meta.py +1145 -0
- fivefury-0.1.0/fivefury/meta_defs.py +529 -0
- fivefury-0.1.0/fivefury/metahash.py +158 -0
- fivefury-0.1.0/fivefury/resolver.py +177 -0
- fivefury-0.1.0/fivefury/resource.py +142 -0
- fivefury-0.1.0/fivefury/rpf.py +979 -0
- fivefury-0.1.0/fivefury/ymap.py +1009 -0
- fivefury-0.1.0/fivefury/ymap_surfaces.py +603 -0
- fivefury-0.1.0/fivefury/ytyp.py +633 -0
- fivefury-0.1.0/fivefury.egg-info/PKG-INFO +262 -0
- fivefury-0.1.0/fivefury.egg-info/SOURCES.txt +35 -0
- fivefury-0.1.0/fivefury.egg-info/dependency_links.txt +1 -0
- fivefury-0.1.0/fivefury.egg-info/top_level.txt +1 -0
- fivefury-0.1.0/native/fivefury_native.cpp +1087 -0
- fivefury-0.1.0/native/rpf_index.cpp +257 -0
- fivefury-0.1.0/native/rpf_index.h +95 -0
- fivefury-0.1.0/native/rpf_scan.cpp +845 -0
- fivefury-0.1.0/native/rpf_scan.h +58 -0
- fivefury-0.1.0/pyproject.toml +30 -0
- fivefury-0.1.0/setup.cfg +4 -0
- fivefury-0.1.0/setup.py +28 -0
- fivefury-0.1.0/tests/test_hashing.py +330 -0
- fivefury-0.1.0/tests/test_meta_rpf_contracts.py +1263 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include native/*.h
|
fivefury-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fivefury
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Partial Python port of CodeWalker.GameFiles for YMAP, YTYP and RPF workflows.
|
|
5
|
+
Author: Codex
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# FiveFury
|
|
11
|
+
|
|
12
|
+
FiveFury is a Python library for GTA V asset workflows.
|
|
13
|
+
|
|
14
|
+
It provides practical support for:
|
|
15
|
+
|
|
16
|
+
- `YMAP` read/write
|
|
17
|
+
- `YTYP` read/write
|
|
18
|
+
- `RPF7 OPEN` archives and nested `.rpf`
|
|
19
|
+
- `ZIP -> RPF` and `RPF -> ZIP`
|
|
20
|
+
- fast asset indexing with `GameFileCache`
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install fivefury
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
For local development from a checkout:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install -e .
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Python `3.11+` is required.
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
### Create a YMAP
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from fivefury import Entity, Ymap
|
|
42
|
+
|
|
43
|
+
ymap = Ymap(name="example_map")
|
|
44
|
+
ymap.add_entity(
|
|
45
|
+
Entity(
|
|
46
|
+
archetype_name="prop_tree_pine_01",
|
|
47
|
+
guid=1,
|
|
48
|
+
position=(0.0, 0.0, 0.0),
|
|
49
|
+
rotation=(0.0, 0.0, 0.0, 1.0),
|
|
50
|
+
lod_dist=150.0,
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
ymap.recalculate_extents()
|
|
54
|
+
ymap.recalculate_flags()
|
|
55
|
+
ymap.save("example_map.ymap")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
If you want an internal resource path, set `ymap.resource_name` before saving.
|
|
59
|
+
|
|
60
|
+
### Load a YMAP
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from pathlib import Path
|
|
64
|
+
|
|
65
|
+
from fivefury import Ymap
|
|
66
|
+
|
|
67
|
+
ymap = Ymap.from_bytes(Path("example_map.ymap").read_bytes())
|
|
68
|
+
|
|
69
|
+
print(len(ymap.entities))
|
|
70
|
+
print(ymap.flags, ymap.content_flags)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Create a YTYP
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from fivefury import Archetype, ParticleEffectExtension, Ytyp
|
|
77
|
+
|
|
78
|
+
ytyp = Ytyp(name="example_types")
|
|
79
|
+
|
|
80
|
+
archetype = Archetype(
|
|
81
|
+
name="prop_tree_pine_01",
|
|
82
|
+
lod_dist=150.0,
|
|
83
|
+
asset_type=0,
|
|
84
|
+
bb_min=(-1.5, -1.5, -0.5),
|
|
85
|
+
bb_max=(1.5, 1.5, 8.0),
|
|
86
|
+
bs_centre=(0.0, 0.0, 3.5),
|
|
87
|
+
bs_radius=5.0,
|
|
88
|
+
)
|
|
89
|
+
archetype.add_extension(
|
|
90
|
+
ParticleEffectExtension(
|
|
91
|
+
name="fx_tree",
|
|
92
|
+
fx_name="scr_wheel_burnout",
|
|
93
|
+
fx_type=2,
|
|
94
|
+
scale=0.8,
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
ytyp.add_archetype(archetype)
|
|
99
|
+
ytyp.save("example_types.ytyp")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Pack Assets into an RPF
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from fivefury import Entity, Ymap, create_rpf
|
|
106
|
+
|
|
107
|
+
ymap = Ymap(name="packed_map")
|
|
108
|
+
ymap.add_entity(Entity(archetype_name="prop_tree_pine_01", position=(0.0, 0.0, 0.0), lod_dist=120.0))
|
|
109
|
+
ymap.recalculate_extents()
|
|
110
|
+
ymap.recalculate_flags()
|
|
111
|
+
|
|
112
|
+
archive = create_rpf("mods.rpf")
|
|
113
|
+
archive.add("stream/packed_map.ymap", ymap)
|
|
114
|
+
archive.add("docs/readme.txt", b"hello from fivefury")
|
|
115
|
+
archive.save("mods.rpf")
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Convert between ZIP and RPF
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from fivefury import rpf_to_zip, zip_to_rpf
|
|
122
|
+
|
|
123
|
+
zip_to_rpf("unpacked_mod_folder", "packed_mod.rpf")
|
|
124
|
+
rpf_to_zip("packed_mod.rpf", "packed_mod.zip")
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Directories ending in `.rpf` are packed as nested archives.
|
|
128
|
+
|
|
129
|
+
## GameFileCache
|
|
130
|
+
|
|
131
|
+
### Scan a Game Installation
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from fivefury import GameFileCache
|
|
135
|
+
|
|
136
|
+
cache = GameFileCache(
|
|
137
|
+
r"C:\Program Files (x86)\Steam\steamapps\common\Grand Theft Auto V",
|
|
138
|
+
scan_workers=8,
|
|
139
|
+
max_loaded_files=16,
|
|
140
|
+
)
|
|
141
|
+
cache.scan_game(use_index_cache=True)
|
|
142
|
+
|
|
143
|
+
print(cache.asset_count)
|
|
144
|
+
print(cache.stats_by_kind())
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`GameFileCache` indexes loose files and archive contents, then loads supported formats lazily.
|
|
148
|
+
|
|
149
|
+
### Control DLC and Scan Scope
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from fivefury import GameFileCache
|
|
153
|
+
|
|
154
|
+
cache = GameFileCache(
|
|
155
|
+
r"C:\Program Files (x86)\Steam\steamapps\common\Grand Theft Auto V",
|
|
156
|
+
dlc_level="mpbattle",
|
|
157
|
+
exclude_folders="mods;scratch",
|
|
158
|
+
load_audio=False,
|
|
159
|
+
load_vehicles=True,
|
|
160
|
+
load_peds=True,
|
|
161
|
+
)
|
|
162
|
+
cache.scan_game(use_index_cache=True)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Useful scan options:
|
|
166
|
+
|
|
167
|
+
- `dlc_level`: limit active DLCs
|
|
168
|
+
- `exclude_folders`: ignore folders by prefix
|
|
169
|
+
- `load_audio`: skip audio-related assets during scan
|
|
170
|
+
- `load_vehicles`: skip vehicle-related assets during scan
|
|
171
|
+
- `load_peds`: skip ped-related assets during scan
|
|
172
|
+
- `use_index_cache`: reuse the persisted scan index for faster startup
|
|
173
|
+
|
|
174
|
+
### Look Up Assets by Name and Type
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
asset = cache.get_asset("prop_tree_pine_01", kind=".ydr")
|
|
178
|
+
print(asset.path)
|
|
179
|
+
print(asset.short_name_hash)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
You can iterate the cache directly:
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
for asset in cache:
|
|
186
|
+
print(asset.path, asset.kind)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Or iterate a specific kind:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
for ydr in cache.iter_kind(".ydr"):
|
|
193
|
+
print(ydr.path)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Read and Extract Assets
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
from pathlib import Path
|
|
200
|
+
|
|
201
|
+
asset = cache.get_asset("prop_tree_pine_01", kind=".ydr")
|
|
202
|
+
data = cache.read_bytes(asset, logical=True)
|
|
203
|
+
out_path = cache.extract_asset(asset, Path("prop_tree_pine_01.ydr"))
|
|
204
|
+
|
|
205
|
+
print(len(data))
|
|
206
|
+
print(out_path)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Common access patterns:
|
|
210
|
+
|
|
211
|
+
- `get_asset(...)`: resolve one asset by path, name or hash
|
|
212
|
+
- `read_bytes(...)`: get bytes directly
|
|
213
|
+
- `get_file(...)`: build a lazy `GameFile` wrapper
|
|
214
|
+
- `extract_asset(...)`: write the asset to disk
|
|
215
|
+
|
|
216
|
+
Extraction defaults to standalone file output.
|
|
217
|
+
For resource assets such as `YDR`, `YDD`, `YFT`, `YTD`, `YMAP` and `YTYP`, this produces a valid standalone `RSC7` file.
|
|
218
|
+
|
|
219
|
+
If you want the logical payload instead:
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
cache.extract_asset("prop_tree_pine_01", "prop_tree_pine_01_payload.ydr", logical=True)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Type Dictionaries
|
|
226
|
+
|
|
227
|
+
`GameFileCache` exposes lazy type dictionaries keyed by `shortNameHash`.
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from fivefury import jenk_hash
|
|
231
|
+
|
|
232
|
+
ydr = cache.YdrDict[jenk_hash("prop_tree_pine_01")]
|
|
233
|
+
ytd = cache.YtdDict[jenk_hash("vehshare")]
|
|
234
|
+
ybn = cache.YbnDict[jenk_hash("v_carshowroom")]
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Available dictionaries include `YdrDict`, `YddDict`, `YtdDict`, `YmapDict`, `YtypDict`, `YftDict`, `YbnDict`, `YcdDict`, `YptDict`, `YndDict`, `YnvDict`, `YedDict`, `YwrDict`, `YvrDict`, `RelDict` and `Gxt2Dict`.
|
|
238
|
+
|
|
239
|
+
### Archetype Lookup
|
|
240
|
+
|
|
241
|
+
`GameFileCache` also builds a lazy global archetype lookup from indexed `YTYP` files.
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
archetype = cache.get_archetype("prop_tree_pine_01")
|
|
245
|
+
print(archetype.name)
|
|
246
|
+
|
|
247
|
+
for archetype in cache.iter_archetypes():
|
|
248
|
+
print(archetype.name)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Global Hash Resolver
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from fivefury import register_name, register_names_file, resolve_hash, jenk_hash
|
|
255
|
+
|
|
256
|
+
register_name("prop_tree_pine_01")
|
|
257
|
+
register_names_file("common_names.txt")
|
|
258
|
+
|
|
259
|
+
print(resolve_hash(jenk_hash("prop_tree_pine_01")))
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
The resolver is shared and optional. It is useful for display, lookups and tooling.
|
fivefury-0.1.0/README.md
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# FiveFury
|
|
2
|
+
|
|
3
|
+
FiveFury is a Python library for GTA V asset workflows.
|
|
4
|
+
|
|
5
|
+
It provides practical support for:
|
|
6
|
+
|
|
7
|
+
- `YMAP` read/write
|
|
8
|
+
- `YTYP` read/write
|
|
9
|
+
- `RPF7 OPEN` archives and nested `.rpf`
|
|
10
|
+
- `ZIP -> RPF` and `RPF -> ZIP`
|
|
11
|
+
- fast asset indexing with `GameFileCache`
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install fivefury
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
For local development from a checkout:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install -e .
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Python `3.11+` is required.
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### Create a YMAP
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from fivefury import Entity, Ymap
|
|
33
|
+
|
|
34
|
+
ymap = Ymap(name="example_map")
|
|
35
|
+
ymap.add_entity(
|
|
36
|
+
Entity(
|
|
37
|
+
archetype_name="prop_tree_pine_01",
|
|
38
|
+
guid=1,
|
|
39
|
+
position=(0.0, 0.0, 0.0),
|
|
40
|
+
rotation=(0.0, 0.0, 0.0, 1.0),
|
|
41
|
+
lod_dist=150.0,
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
ymap.recalculate_extents()
|
|
45
|
+
ymap.recalculate_flags()
|
|
46
|
+
ymap.save("example_map.ymap")
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
If you want an internal resource path, set `ymap.resource_name` before saving.
|
|
50
|
+
|
|
51
|
+
### Load a YMAP
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from pathlib import Path
|
|
55
|
+
|
|
56
|
+
from fivefury import Ymap
|
|
57
|
+
|
|
58
|
+
ymap = Ymap.from_bytes(Path("example_map.ymap").read_bytes())
|
|
59
|
+
|
|
60
|
+
print(len(ymap.entities))
|
|
61
|
+
print(ymap.flags, ymap.content_flags)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Create a YTYP
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from fivefury import Archetype, ParticleEffectExtension, Ytyp
|
|
68
|
+
|
|
69
|
+
ytyp = Ytyp(name="example_types")
|
|
70
|
+
|
|
71
|
+
archetype = Archetype(
|
|
72
|
+
name="prop_tree_pine_01",
|
|
73
|
+
lod_dist=150.0,
|
|
74
|
+
asset_type=0,
|
|
75
|
+
bb_min=(-1.5, -1.5, -0.5),
|
|
76
|
+
bb_max=(1.5, 1.5, 8.0),
|
|
77
|
+
bs_centre=(0.0, 0.0, 3.5),
|
|
78
|
+
bs_radius=5.0,
|
|
79
|
+
)
|
|
80
|
+
archetype.add_extension(
|
|
81
|
+
ParticleEffectExtension(
|
|
82
|
+
name="fx_tree",
|
|
83
|
+
fx_name="scr_wheel_burnout",
|
|
84
|
+
fx_type=2,
|
|
85
|
+
scale=0.8,
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
ytyp.add_archetype(archetype)
|
|
90
|
+
ytyp.save("example_types.ytyp")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Pack Assets into an RPF
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from fivefury import Entity, Ymap, create_rpf
|
|
97
|
+
|
|
98
|
+
ymap = Ymap(name="packed_map")
|
|
99
|
+
ymap.add_entity(Entity(archetype_name="prop_tree_pine_01", position=(0.0, 0.0, 0.0), lod_dist=120.0))
|
|
100
|
+
ymap.recalculate_extents()
|
|
101
|
+
ymap.recalculate_flags()
|
|
102
|
+
|
|
103
|
+
archive = create_rpf("mods.rpf")
|
|
104
|
+
archive.add("stream/packed_map.ymap", ymap)
|
|
105
|
+
archive.add("docs/readme.txt", b"hello from fivefury")
|
|
106
|
+
archive.save("mods.rpf")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Convert between ZIP and RPF
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from fivefury import rpf_to_zip, zip_to_rpf
|
|
113
|
+
|
|
114
|
+
zip_to_rpf("unpacked_mod_folder", "packed_mod.rpf")
|
|
115
|
+
rpf_to_zip("packed_mod.rpf", "packed_mod.zip")
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Directories ending in `.rpf` are packed as nested archives.
|
|
119
|
+
|
|
120
|
+
## GameFileCache
|
|
121
|
+
|
|
122
|
+
### Scan a Game Installation
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from fivefury import GameFileCache
|
|
126
|
+
|
|
127
|
+
cache = GameFileCache(
|
|
128
|
+
r"C:\Program Files (x86)\Steam\steamapps\common\Grand Theft Auto V",
|
|
129
|
+
scan_workers=8,
|
|
130
|
+
max_loaded_files=16,
|
|
131
|
+
)
|
|
132
|
+
cache.scan_game(use_index_cache=True)
|
|
133
|
+
|
|
134
|
+
print(cache.asset_count)
|
|
135
|
+
print(cache.stats_by_kind())
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`GameFileCache` indexes loose files and archive contents, then loads supported formats lazily.
|
|
139
|
+
|
|
140
|
+
### Control DLC and Scan Scope
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from fivefury import GameFileCache
|
|
144
|
+
|
|
145
|
+
cache = GameFileCache(
|
|
146
|
+
r"C:\Program Files (x86)\Steam\steamapps\common\Grand Theft Auto V",
|
|
147
|
+
dlc_level="mpbattle",
|
|
148
|
+
exclude_folders="mods;scratch",
|
|
149
|
+
load_audio=False,
|
|
150
|
+
load_vehicles=True,
|
|
151
|
+
load_peds=True,
|
|
152
|
+
)
|
|
153
|
+
cache.scan_game(use_index_cache=True)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Useful scan options:
|
|
157
|
+
|
|
158
|
+
- `dlc_level`: limit active DLCs
|
|
159
|
+
- `exclude_folders`: ignore folders by prefix
|
|
160
|
+
- `load_audio`: skip audio-related assets during scan
|
|
161
|
+
- `load_vehicles`: skip vehicle-related assets during scan
|
|
162
|
+
- `load_peds`: skip ped-related assets during scan
|
|
163
|
+
- `use_index_cache`: reuse the persisted scan index for faster startup
|
|
164
|
+
|
|
165
|
+
### Look Up Assets by Name and Type
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
asset = cache.get_asset("prop_tree_pine_01", kind=".ydr")
|
|
169
|
+
print(asset.path)
|
|
170
|
+
print(asset.short_name_hash)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
You can iterate the cache directly:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
for asset in cache:
|
|
177
|
+
print(asset.path, asset.kind)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Or iterate a specific kind:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
for ydr in cache.iter_kind(".ydr"):
|
|
184
|
+
print(ydr.path)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Read and Extract Assets
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
from pathlib import Path
|
|
191
|
+
|
|
192
|
+
asset = cache.get_asset("prop_tree_pine_01", kind=".ydr")
|
|
193
|
+
data = cache.read_bytes(asset, logical=True)
|
|
194
|
+
out_path = cache.extract_asset(asset, Path("prop_tree_pine_01.ydr"))
|
|
195
|
+
|
|
196
|
+
print(len(data))
|
|
197
|
+
print(out_path)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Common access patterns:
|
|
201
|
+
|
|
202
|
+
- `get_asset(...)`: resolve one asset by path, name or hash
|
|
203
|
+
- `read_bytes(...)`: get bytes directly
|
|
204
|
+
- `get_file(...)`: build a lazy `GameFile` wrapper
|
|
205
|
+
- `extract_asset(...)`: write the asset to disk
|
|
206
|
+
|
|
207
|
+
Extraction defaults to standalone file output.
|
|
208
|
+
For resource assets such as `YDR`, `YDD`, `YFT`, `YTD`, `YMAP` and `YTYP`, this produces a valid standalone `RSC7` file.
|
|
209
|
+
|
|
210
|
+
If you want the logical payload instead:
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
cache.extract_asset("prop_tree_pine_01", "prop_tree_pine_01_payload.ydr", logical=True)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Type Dictionaries
|
|
217
|
+
|
|
218
|
+
`GameFileCache` exposes lazy type dictionaries keyed by `shortNameHash`.
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from fivefury import jenk_hash
|
|
222
|
+
|
|
223
|
+
ydr = cache.YdrDict[jenk_hash("prop_tree_pine_01")]
|
|
224
|
+
ytd = cache.YtdDict[jenk_hash("vehshare")]
|
|
225
|
+
ybn = cache.YbnDict[jenk_hash("v_carshowroom")]
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Available dictionaries include `YdrDict`, `YddDict`, `YtdDict`, `YmapDict`, `YtypDict`, `YftDict`, `YbnDict`, `YcdDict`, `YptDict`, `YndDict`, `YnvDict`, `YedDict`, `YwrDict`, `YvrDict`, `RelDict` and `Gxt2Dict`.
|
|
229
|
+
|
|
230
|
+
### Archetype Lookup
|
|
231
|
+
|
|
232
|
+
`GameFileCache` also builds a lazy global archetype lookup from indexed `YTYP` files.
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
archetype = cache.get_archetype("prop_tree_pine_01")
|
|
236
|
+
print(archetype.name)
|
|
237
|
+
|
|
238
|
+
for archetype in cache.iter_archetypes():
|
|
239
|
+
print(archetype.name)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Global Hash Resolver
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
from fivefury import register_name, register_names_file, resolve_hash, jenk_hash
|
|
246
|
+
|
|
247
|
+
register_name("prop_tree_pine_01")
|
|
248
|
+
register_names_file("common_names.txt")
|
|
249
|
+
|
|
250
|
+
print(resolve_hash(jenk_hash("prop_tree_pine_01")))
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
The resolver is shared and optional. It is useful for display, lookups and tooling.
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
from .cache import AssetRecord, GameFileCache, ScanStats
|
|
2
|
+
from .crypto import (
|
|
3
|
+
AES_ENCRYPTION,
|
|
4
|
+
NG_ENCRYPTION,
|
|
5
|
+
OPEN_ENCRYPTION,
|
|
6
|
+
GameCrypto,
|
|
7
|
+
clear_game_crypto,
|
|
8
|
+
get_game_crypto,
|
|
9
|
+
load_game_keys,
|
|
10
|
+
)
|
|
11
|
+
from .extensions import (
|
|
12
|
+
AudioCollisionSettingsExtension,
|
|
13
|
+
AudioEmitterExtension,
|
|
14
|
+
BuoyancyExtension,
|
|
15
|
+
CapsuleBoundDef,
|
|
16
|
+
DoorExtension,
|
|
17
|
+
ExpressionExtension,
|
|
18
|
+
ExplosionEffectExtension,
|
|
19
|
+
LadderExtension,
|
|
20
|
+
LightAttrDef,
|
|
21
|
+
LightEffectExtension,
|
|
22
|
+
LightShaftExtension,
|
|
23
|
+
ParticleEffectExtension,
|
|
24
|
+
ProcObjectExtension,
|
|
25
|
+
SpawnPointExtension,
|
|
26
|
+
SpawnPointOverrideExtension,
|
|
27
|
+
VerletClothCustomBoundsExtension,
|
|
28
|
+
WindDisturbanceExtension,
|
|
29
|
+
)
|
|
30
|
+
from .gamefile import GameFile, GameFileType
|
|
31
|
+
from .hashing import jenk_hash
|
|
32
|
+
from .metahash import HashString, MetaHash
|
|
33
|
+
from .meta import Meta, read_meta
|
|
34
|
+
from .resolver import (
|
|
35
|
+
HashResolver,
|
|
36
|
+
clear_hash_resolver,
|
|
37
|
+
get_hash_resolver,
|
|
38
|
+
hash_matches,
|
|
39
|
+
register_name,
|
|
40
|
+
register_names,
|
|
41
|
+
register_names_file,
|
|
42
|
+
register_path_name,
|
|
43
|
+
register_path_names,
|
|
44
|
+
resolve_hash,
|
|
45
|
+
resolve_name,
|
|
46
|
+
)
|
|
47
|
+
from .resource import RSC7_MAGIC, ResourceHeader, build_rsc7, parse_rsc7
|
|
48
|
+
from .rpf import RpfArchive, create_rpf, load_rpf, rpf_to_zip, zip_to_rpf
|
|
49
|
+
from .ymap import (
|
|
50
|
+
Block,
|
|
51
|
+
CarGen,
|
|
52
|
+
CarGenerator,
|
|
53
|
+
Entity,
|
|
54
|
+
EntityDef,
|
|
55
|
+
MloInstance,
|
|
56
|
+
MloInstanceDef,
|
|
57
|
+
TimeCycleModifier,
|
|
58
|
+
Ymap,
|
|
59
|
+
read_ymap,
|
|
60
|
+
save_ymap,
|
|
61
|
+
)
|
|
62
|
+
from .ymap_surfaces import (
|
|
63
|
+
Aabb,
|
|
64
|
+
BoxOccluder,
|
|
65
|
+
DistantLodLights,
|
|
66
|
+
DistantLodLightsSoa,
|
|
67
|
+
GrassBatch,
|
|
68
|
+
GrassInstance,
|
|
69
|
+
GrassInstanceBatch,
|
|
70
|
+
InstancedData,
|
|
71
|
+
InstancedMapData,
|
|
72
|
+
LodLight,
|
|
73
|
+
LodLights,
|
|
74
|
+
LodLightsSoa,
|
|
75
|
+
OccludeModel,
|
|
76
|
+
)
|
|
77
|
+
from .ytyp import (
|
|
78
|
+
Archetype,
|
|
79
|
+
BaseArchetypeDef,
|
|
80
|
+
EntitySet,
|
|
81
|
+
MloArchetype,
|
|
82
|
+
MloArchetypeDef,
|
|
83
|
+
MloTimeModifier,
|
|
84
|
+
Portal,
|
|
85
|
+
Room,
|
|
86
|
+
TimeArchetype,
|
|
87
|
+
TimeArchetypeDef,
|
|
88
|
+
Ytyp,
|
|
89
|
+
read_ytyp,
|
|
90
|
+
save_ytyp,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
__all__ = [
|
|
94
|
+
"Aabb",
|
|
95
|
+
"Archetype",
|
|
96
|
+
"AudioCollisionSettingsExtension",
|
|
97
|
+
"AudioEmitterExtension",
|
|
98
|
+
"AES_ENCRYPTION",
|
|
99
|
+
"AssetRecord",
|
|
100
|
+
"BaseArchetypeDef",
|
|
101
|
+
"Block",
|
|
102
|
+
"BoxOccluder",
|
|
103
|
+
"BuoyancyExtension",
|
|
104
|
+
"CapsuleBoundDef",
|
|
105
|
+
"CarGen",
|
|
106
|
+
"CarGenerator",
|
|
107
|
+
"DistantLodLights",
|
|
108
|
+
"DistantLodLightsSoa",
|
|
109
|
+
"DoorExtension",
|
|
110
|
+
"Entity",
|
|
111
|
+
"EntityDef",
|
|
112
|
+
"EntitySet",
|
|
113
|
+
"ExpressionExtension",
|
|
114
|
+
"ExplosionEffectExtension",
|
|
115
|
+
"GameFile",
|
|
116
|
+
"GameFileCache",
|
|
117
|
+
"GameCrypto",
|
|
118
|
+
"GameFileType",
|
|
119
|
+
"ScanStats",
|
|
120
|
+
"GrassBatch",
|
|
121
|
+
"GrassInstance",
|
|
122
|
+
"GrassInstanceBatch",
|
|
123
|
+
"InstancedData",
|
|
124
|
+
"InstancedMapData",
|
|
125
|
+
"HashResolver",
|
|
126
|
+
"HashString",
|
|
127
|
+
"LadderExtension",
|
|
128
|
+
"LightAttrDef",
|
|
129
|
+
"LightEffectExtension",
|
|
130
|
+
"LightShaftExtension",
|
|
131
|
+
"LodLight",
|
|
132
|
+
"LodLights",
|
|
133
|
+
"LodLightsSoa",
|
|
134
|
+
"Meta",
|
|
135
|
+
"MetaHash",
|
|
136
|
+
"MloArchetype",
|
|
137
|
+
"MloArchetypeDef",
|
|
138
|
+
"MloInstance",
|
|
139
|
+
"MloInstanceDef",
|
|
140
|
+
"MloTimeModifier",
|
|
141
|
+
"NG_ENCRYPTION",
|
|
142
|
+
"OccludeModel",
|
|
143
|
+
"OPEN_ENCRYPTION",
|
|
144
|
+
"ParticleEffectExtension",
|
|
145
|
+
"Portal",
|
|
146
|
+
"ProcObjectExtension",
|
|
147
|
+
"ResourceHeader",
|
|
148
|
+
"Room",
|
|
149
|
+
"RpfArchive",
|
|
150
|
+
"RSC7_MAGIC",
|
|
151
|
+
"SpawnPointExtension",
|
|
152
|
+
"SpawnPointOverrideExtension",
|
|
153
|
+
"TimeArchetype",
|
|
154
|
+
"TimeArchetypeDef",
|
|
155
|
+
"TimeCycleModifier",
|
|
156
|
+
"VerletClothCustomBoundsExtension",
|
|
157
|
+
"WindDisturbanceExtension",
|
|
158
|
+
"Ymap",
|
|
159
|
+
"Ytyp",
|
|
160
|
+
"build_rsc7",
|
|
161
|
+
"clear_hash_resolver",
|
|
162
|
+
"clear_game_crypto",
|
|
163
|
+
"create_rpf",
|
|
164
|
+
"get_game_crypto",
|
|
165
|
+
"get_hash_resolver",
|
|
166
|
+
"hash_matches",
|
|
167
|
+
"jenk_hash",
|
|
168
|
+
"load_game_keys",
|
|
169
|
+
"load_rpf",
|
|
170
|
+
"parse_rsc7",
|
|
171
|
+
"read_meta",
|
|
172
|
+
"read_ymap",
|
|
173
|
+
"read_ytyp",
|
|
174
|
+
"register_name",
|
|
175
|
+
"register_names",
|
|
176
|
+
"register_names_file",
|
|
177
|
+
"register_path_name",
|
|
178
|
+
"register_path_names",
|
|
179
|
+
"resolve_hash",
|
|
180
|
+
"resolve_name",
|
|
181
|
+
"rpf_to_zip",
|
|
182
|
+
"save_ymap",
|
|
183
|
+
"save_ytyp",
|
|
184
|
+
"zip_to_rpf",
|
|
185
|
+
]
|