1
1
use std:: ffi:: OsStr ;
2
+ use std:: fs:: File ;
3
+ use std:: io:: { BufWriter , Read , Seek , SeekFrom , Write } ;
2
4
use std:: path:: { Path , PathBuf } ;
5
+
3
6
use anyhow:: Context ;
7
+ use binrw:: __private:: write_zeroes;
8
+ use binrw:: BinWrite ;
9
+ use binrw:: io:: BufReader ;
10
+ use byteorder:: WriteBytesExt ;
4
11
use walkdir:: WalkDir ;
12
+
13
+ use crate :: command:: pack;
5
14
use crate :: command:: patch:: xml_patcher;
15
+ use crate :: command:: unpack:: RepackInfo ;
16
+ use crate :: io_ext:: WriteExt ;
17
+ use crate :: unity:: { AssetsFile , AssetsFileContent , AssetsFileHeader , ObjectInfo } ;
18
+
19
+ /// Length of the header of the Art.dat object.
20
+ /// The header consists of:
21
+ /// - 4 bytes for object name length
22
+ /// - 7 bytes for object name (Art.dat)
23
+ /// - 1 byte for field index
24
+ /// - 4 bytes for data length
25
+ const ART_OBJ_HEADER_LEN : u64 = 4 + 7 + 1 + 4 ;
6
26
7
- pub fn patch_assets ( patch : & PathBuf , unpacked : & PathBuf , temp_dir : & PathBuf ) -> anyhow:: Result < PathBuf > {
27
+ pub fn patch_assets (
28
+ patch : & PathBuf ,
29
+ unpacked : & PathBuf ,
30
+ temp_dir : & PathBuf ,
31
+ repack_info : RepackInfo ,
32
+ ) -> anyhow:: Result < PathBuf > {
8
33
println ! ( "Patching assets.." ) ;
9
34
let patched_assets = temp_dir. join ( "patched" ) ;
10
35
std:: fs:: create_dir_all ( & patched_assets)
@@ -46,11 +71,10 @@ pub fn patch_assets(patch: &PathBuf, unpacked: &PathBuf, temp_dir: &PathBuf) ->
46
71
copy_file ( & patch_file. as_path ( ) , rel_path, & patched_assets) ?;
47
72
} else if ext == OsStr :: new ( "xml" ) || ext == OsStr :: new ( "fnt" ) {
48
73
println ! ( "Patching xml file: {}" , rel_path. display( ) ) ;
49
- patch_xml ( & file, & patch_file, rel_path, & patched_assets) ?;
74
+ patch_xml ( & file. path ( ) , & patch_file, rel_path, & patched_assets) ?;
50
75
} else {
51
76
anyhow:: bail!( "Unsupported file type: {}" , patch_file. display( ) ) ;
52
77
}
53
-
54
78
}
55
79
56
80
// Loop over any files newly added with the patch
@@ -79,7 +103,7 @@ pub fn patch_assets(patch: &PathBuf, unpacked: &PathBuf, temp_dir: &PathBuf) ->
79
103
}
80
104
}
81
105
82
- return Ok ( patched_assets) ;
106
+ pack_to_assets ( temp_dir , & patched_assets, repack_info )
83
107
}
84
108
85
109
/// Copies a file from one of the input directories to the patched assets directory and makes sure
@@ -94,11 +118,99 @@ fn copy_file(file: &Path, rel_path: &Path, patched_assets: &PathBuf) -> anyhow::
94
118
}
95
119
96
120
/// Patches an XML file using the given patch file and writes the output to the patched assets directory
97
- fn patch_xml ( original : & walkdir :: DirEntry , patch_file : & PathBuf , rel_path : & Path , patched_assets : & PathBuf ) -> anyhow:: Result < ( ) > {
121
+ fn patch_xml ( original : & Path , patch_file : & PathBuf , rel_path : & Path , patched_assets : & PathBuf ) -> anyhow:: Result < ( ) > {
98
122
let output = patched_assets. join ( rel_path) ;
99
123
if let Some ( parent) = output. parent ( ) {
100
124
std:: fs:: create_dir_all ( parent)
101
125
. context ( "Failed to create directory" ) ?;
102
126
}
103
- xml_patcher:: patch ( original. path ( ) , patch_file, & output)
127
+ xml_patcher:: patch ( original, patch_file, & output)
128
+ }
129
+
130
+ fn pack_to_assets ( temp_dir : & PathBuf , patched : & PathBuf , repack : RepackInfo ) -> anyhow:: Result < PathBuf > {
131
+ let output = temp_dir. join ( "repacked.assets" ) ;
132
+ let temp_art = temp_dir. join ( "patched-art.dat" ) ;
133
+ pack:: pack ( & repack. art_key , & Some ( patched. clone ( ) ) , & temp_art) ?;
134
+ let assets = repack. assets ;
135
+ let new_art_len = std:: fs:: metadata ( & temp_art)
136
+ . context ( "Failed to get metadata of temp art file" ) ?
137
+ . len ( ) ;
138
+
139
+ // header
140
+ let mut header = AssetsFileHeader { file_size : 0 , ..assets. header } ;
141
+
142
+ // content
143
+ let mut objects = Vec :: new ( ) ;
144
+ let mut current_offset = 0 ;
145
+ for obj in & assets. content . objects {
146
+ if current_offset % 4 != 0 {
147
+ current_offset += 4 - ( current_offset % 4 ) ;
148
+ }
149
+
150
+ let mut new_object = ObjectInfo {
151
+ path_id : obj. path_id ,
152
+ byte_start : current_offset,
153
+ byte_size : 0 ,
154
+ type_id : obj. type_id ,
155
+ } ;
156
+ if obj. path_id == repack. art_path_id {
157
+ new_object. byte_size = ( new_art_len + ART_OBJ_HEADER_LEN ) as u32 ;
158
+ } else {
159
+ new_object. byte_size = obj. byte_size ;
160
+ }
161
+ current_offset += new_object. byte_size as u64 ;
162
+ objects. push ( new_object) ;
163
+ }
164
+ header. file_size = header. offset_first_file + current_offset;
165
+ let content = AssetsFileContent { objects, ..assets. content } ;
166
+ let new_assets = AssetsFile { header, content } ;
167
+ let mut writer = BufWriter :: new ( File :: create ( & output)
168
+ . context ( "Failed to create output file" ) ?) ;
169
+ new_assets. write ( & mut writer)
170
+ . context ( "Failed to write assets file header" ) ?;
171
+
172
+ // pad with zeroes until first file offset is reached (yes this is also what Unity does)
173
+ let pad = assets. header . offset_first_file - writer. seek ( SeekFrom :: Current ( 0 ) )
174
+ . context ( "Failed to get current position in output file" ) ?;
175
+ write_zeroes ( & mut writer, pad) ?;
176
+
177
+ // write the actual object data
178
+ let mut original = BufReader :: new ( File :: open ( & repack. original_assets )
179
+ . context ( "Failed to open original assets file" ) ?) ;
180
+ let original_file_offset = & assets. header . offset_first_file ;
181
+ for ( obj, old_obj) in new_assets. content . objects . iter ( ) . zip ( assets. content . objects ) {
182
+
183
+ let pos = writer. stream_position ( )
184
+ . context ( "Failed to get current position in output file" ) ?;
185
+ if pos != obj. byte_start + original_file_offset {
186
+ // pad with zeroes until the object's start offset is reached
187
+ let pad = obj. byte_start + original_file_offset - pos;
188
+ write_zeroes ( & mut writer, pad) . context ( "Failed to write padding zeroes" ) ?;
189
+ }
190
+
191
+ if obj. path_id != repack. art_path_id {
192
+ original. seek ( SeekFrom :: Start ( original_file_offset + old_obj. byte_start ) )
193
+ . context ( "Failed to seek to object in original assets file" ) ?;
194
+ let mut data = vec ! [ 0 ; obj. byte_size as usize ] ;
195
+ original. read_exact ( & mut data)
196
+ . context ( "Failed to read object data from original assets file" ) ?;
197
+ writer. write_all ( & data) ?;
198
+ } else {
199
+ writer. write_dyn_string ( "Art.dat" , & new_assets. header . endianness )
200
+ . context ( "Failed to write object name" ) ?;
201
+ writer. write_u8 ( 0 )
202
+ . context ( "Failed to write field index" ) ?;
203
+ writer. write_u32_order ( & new_assets. header . endianness , new_art_len as u32 )
204
+ . context ( "Failed to write object data length" ) ?;
205
+ // copy over the new art file
206
+ let mut art_file = BufReader :: new ( File :: open ( & temp_art)
207
+ . context ( "Failed to open temp art file" ) ?) ;
208
+ std:: io:: copy ( & mut art_file, & mut writer)
209
+ . context ( "Failed to copy new art file to assets file" ) ?;
210
+ }
211
+
212
+ }
213
+
214
+ println ! ( "Packed objets to: {}" , output. display( ) ) ;
215
+ Ok ( output)
104
216
}
0 commit comments