-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgodot.reb
554 lines (532 loc) · 14.8 KB
/
godot.reb
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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
REBOL [
Title: "Godot codecs and a scheme"
type: module
name: godot
date: 14-Dec-2022
version: 0.1.0
author: "Oldes"
exports: [extract-gpck]
needs: [json]
file: https://raw.githubusercontent.com/Oldes/Rebol-Godot/master/godot.reb
purpose: {
This script was maded only to examine files which may be found in an application made in Godot engine.
https://godotengine.org/
https://github.com/godotengine/godot
}
history: [
14-Dec-2022 "Oldes" {Initial version - useful to extract content of not encrypted Godot's *.pck files}
]
license: MIT
]
system/options/log/godot: 3
decode-pck: function[file [port! file!]][
src: either port? file [file][open/read/seek file]
if #{47445043} <> read/part src 4 [
sys/log/error 'GODOT "Not GPCK file!"
return none
]
bin: binary read/part src 64000
version: binary/read bin [UI32LE UI32LE UI32LE UI32LE]
;? version
binary/read bin [
SKIP 64 ;; Reserved space
files: UI32LE
]
;? files
info: make map! files
loop files [
if 1000 > length? bin/buffer [
append bin/buffer read/part src 32000
]
path: to string! trim/tail binary/read bin 'UI32LEBYTES
info/:path: binary/read bin [
offset: UI64LE
size: UI64LE
md5: BYTES 16
]
]
info
]
decode-stex: function [
{Extract content of the STEX file}
data [binary! file! url!]
/only "Don't try to load an image, return raw binary instead"
][
unless binary? data [ data: read data ]
inp: binary data
unless binary/read inp #{47445354} [return none]
spec: binary/read inp [
width: UI32LE
height: UI32LE
type: UI32LE ;; 4 = RGBA?
flags: BYTES 4 ;UI32LE
]
num: binary/read inp 'UI32LE
out: make block! num
loop num [
tmp: binary/read inp 'UI32LEBYTES
type: to string! take/part tmp 4
;print [type spec]
append out tmp
]
unless only [ forall out [out/1: to image! out/1] ]
either num == 1 [first out][out]
]
decode-rsrc: function[
{Extract content of the RSRC file}
data [binary! file! url!]
/only "Don't try to load an image, return raw binary instead"
][
unless binary? data [ data: read data ]
inp: binary data
unless binary/read inp #{52535243} [return none]
spec: binary/read inp [
UI32LE ;; endianess; 0 = LE, 1 = BE
UI32LE ;; reserved
UI32LE ;; MAJOR
UI32LE ;; MINOR
UI32LE ;; VERSION
]
if spec/1 <> 0 [
sys/log/error 'GODOT "Big-endian RSRC file format not supported!"
;@@ there should be support in the Bincode to set default endianess!
return none
]
out: copy []
class: to string! trim/tail binary/read inp 'UI32LEBYTES
;? class
append out class
spec: binary/read inp [
UI64LE ;; offset to metadata
UI32LE ;; format flags
UI64LE ;; UID
SKIP 44 ;; 11xUI32LE
]
;? spec
num: binary/read inp 'UI32LE ;; string table size
strings: make block! num
loop num [
tmp: to string! trim/tail binary/read inp 'UI32LEBYTES
try [tmp: to word! tmp]
append strings tmp
]
;? strings
num: binary/read inp 'UI32LE ;; amount of external resources
;? num
append/only out external: make block! num
loop num [
append external binary/read inp [
UI32LEBYTES ;; class
UI32LEBYTES ;; path
UI64LE ;; ResourceUID
]
]
num: binary/read inp 'UI32LE ;; amount of internal resources
;? num
append/only out internal: make block! num
offsets: make block! 2 * num
loop num [
append append offsets
to string! trim/tail binary/read inp 'UI32LEBYTES ;; local://...
binary/read inp 'UI64LE ;; offset
]
;? offsets
foreach [id ofs] offsets [
binary/read inp [
ATZ :ofs
str: UI32LEBYTES
num: UI32LE ;; properties
]
str: to string! trim/tail str
;? str ? num
loop num [
binary/read inp [
name: UI32LE ;; property name (index to the strings table)
type: UI32LE
]
name: pickz strings name
;? name
;? type
value: switch/default type [
;VARIANT_NIL
1 [ #[none] ]
;VARIANT_BOOL
2 [ 1 = binary/read inp 'UI32LE ]
;VARIANT_INT
3 [ binary/read inp 'UI32LE ]
;VARIANT_FLOAT
4 [ binary/read inp 'FLOAT ]
;VARIANT_STRING
5 [ to string! trim/tail binary/read inp 'UI32LEBYTES ]
;VARIANT_VECTOR2
10 [ as-pair binary/read inp [FLOAT FLOAT] ]
;VARIANT_RECT2
11 [ binary/read inp [FLOAT FLOAT FLOAT FLOAT] ]
;VARIANT_VECTOR3
12 [ binary/read inp [FLOAT FLOAT FLOAT] ]
;VARIANT_PLANE
13 [ binary/read inp [FLOAT FLOAT FLOAT FLOAT] ]
;VARIANT_QUATERNION
14 [ binary/read inp [FLOAT FLOAT FLOAT FLOAT] ]
;VARIANT_AABB
15 [ binary/read inp [FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT] ]
;VARIANT_BASIS
16 [ binary/read inp [FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT] ]
;VARIANT_TRANSFORM3D
17 [ binary/read inp [FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT] ]
;VARIANT_TRANSFORM2D
18 [ binary/read inp [FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT] ]
;VARIANT_COLOR
20 [ binary/read inp [FLOAT FLOAT FLOAT FLOAT] ]
;VARIANT_NODE_PATH
22 [
binary/read inp [names: UI16LE subnames: UI16LE]
absolute: subnames & 32768 ;8000h
subnames: subnames & 32767 ;7FFFh
node: make block! 3
append/only node tmp: make block! names
loop names [
append tmp to string! trim/tail binary/read inp 'UI32LEBYTES
]
append/only node tmp: make block! subnames
loop subnames [
append tmp to string! trim/tail binary/read inp 'UI32LEBYTES
]
head append node absolute
]
;VARIANT_RID
23 [ binary/read inp 'UI32LE ]
;VARIANT_OBJECT
24 [
reduce [
type: binary/read inp 'UI32LE
switch type [
0 [ #[none] ] ;OBJECT_EMPTY
1 [
;OBJECT_EXTERNAL_RESOURCE
reduce [
to string! trim/tail binary/read inp 'UI32LEBYTES ;exttype
to string! trim/tail binary/read inp 'UI32LEBYTES ;path
]
]
2 [
;OBJECT_INTERNAL_RESOURCE
binary/read inp 'UI32LE ;index
]
3 [
;OBJECT_EXTERNAL_RESOURCE_INDEX
binary/read inp 'UI32LE ;index
]
]
]
]
;@@VARIANT_INPUT_EVENT = 25,
;@@VARIANT_DICTIONARY = 26,
;@@VARIANT_ARRAY = 30,
;VARIANT_PACKED_BYTE_ARRAY
31 [
second binary/read inp [len: UI32LE BYTES :len]
;make vector! reduce ['uint8! tmp/2]
]
;VARIANT_PACKED_INT32_ARRAY
32 [
tmp: binary/read inp [len: UI32LE BYTES :len]
make vector! reduce ['int32! tmp/2]
]
;VARIANT_PACKED_FLOAT32_ARRAY
33 [
tmp: binary/read inp [len: UI32LE BYTES :len]
make vector! reduce ['float! tmp/2]
]
;@@VARIANT_PACKED_STRING_ARRAY = 34,
;@@VARIANT_PACKED_VECTOR3_ARRAY = 35,
;@@VARIANT_PACKED_COLOR_ARRAY = 36,
;@@VARIANT_PACKED_VECTOR2_ARRAY = 37,
;VARIANT_INT64
40 [ binary/read inp 'UI64LE ]
;VARIANT_DOUBLE
41 [ binary/read inp 'DOUBLE ]
;VARIANT_CALLABLE = 42,
;VARIANT_SIGNAL = 43,
;VARIANT_STRING_NAME
44 [ to string! trim/tail binary/read inp 'UI32LEBYTES ]
;VARIANT_VECTOR2I
45 [ binary/read inp [UI32LE UI32LE] ]
;VARIANT_RECT2I
46 [ binary/read inp [UI32LE UI32LE UI32LE UI32LE] ]
;VARIANT_VECTOR3I
47 [ binary/read inp [UI32LE UI32LE UI32LE] ]
;VARIANT_PACKED_INT64_ARRAY
48 [
tmp: binary/read inp [len: UI32LE BYTES :len]
make vector! reduce ['int64! tmp/2]
]
;VARIANT_PACKED_FLOAT64_ARRAY
49 [
tmp: binary/read inp [len: UI32LE BYTES :len]
make vector! reduce ['decimal! tmp/2]
]
;VARIANT_VECTOR4
50 [ binary/read inp [FLOAT FLOAT FLOAT FLOAT] ]
;VARIANT_VECTOR4I
51 [ binary/read inp [UI32LE UI32LE UI32LE UI32LE] ]
;VARIANT_PROJECTION
52 [ binary/read inp [FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT FLOAT] ]
][
sys/log/error 'GODOT ["Unsupported variant type:" as-red type]
return none
]
;? value
append append internal name value
]
]
unless parse inp/buffer [any #"^@" #{52535243}][
sys/log/error 'GODOT "RSRC magic at end not found!"
]
out
]
get-pck-file: func[
"Resolves binary data from a GPCK scheme"
port [port!] "GPCK scheme's context"
file [any-string!]
/check "Validate data's checksum"
/local ctx bin inf
][
all [
ctx: port/state
inf: pick ctx/info as string! :file
bin: read/part skip head ctx/conn inf/1 inf/2
any [not check inf/3 = checksum bin 'md5]
bin
]
]
sys/make-scheme [
name: 'gpck
actor: [
open: func [port [port!] /local path bin tmp][
if port/state [
cause-error 'access 'already-open port/spec/ref
]
port/state: context [
conn: none
version: none
files: none
info: none
]
with port/state [
tmp: port/spec
try/except [
;; when the source is in type: gpck://file.pck
;; than we must correct the path and the target,
;; because in such a case, the file.pck is recognized as host
path: as file! combine [
(select tmp 'host)
(select tmp 'path)
]
;; using extend so if there is no path yet, it is added
extend tmp 'path :path
;; in case there is no target (no path was used), use path value
;; and change the path to the current directory
unless select tmp 'target [
extend tmp 'target :path
tmp/path: what-dir
]
;; now use full path (dir+file)
path: append copy tmp/path tmp/target
;? path
all [
;; handle a case when the source is a path to an app folder
'dir = exists? path
block? tmp: try [read path/Contents/Resources/*.pck]
path: rejoin [dirize path %Contents/Resources/ first tmp]
]
conn: open/read/seek path
if #{47445043} <> read/part conn 4 [
cause-error 'access 'invalid-check port/spec/ref
]
bin: binary read/part conn 64000
version: binary/read bin [UI32LE UI32LE UI32LE UI32LE]
;? version
binary/read bin [
SKIP 64 ;; Reserved space
files: UI32LE
]
;? files
info: make map! files
loop files [
if 1000 > length? bin/buffer [
append bin/buffer read/part conn 32000
]
path: to string! trim/tail binary/read bin 'UI32LEBYTES
parse path ["res://" (take/part path 6)] ;; don't use the res:// scheme
;? path
info/:path: binary/read bin [
UI64LE ;; offset
UI64LE ;; size
BYTES 16 ;; MD5
]
]
][
sys/log/error 'GODOT system/state/last-error
cause-error 'access 'cannot-open reduce [port/spec/ref system/state/last-error/id]
]
]
port
]
open?: func[port [port!]][ object? port/state ]
read: func[port [port!] /local ctx query info num out][
unless port/state [open port]
ctx: port/state
;? ctx
if any-string? query: select port/spec 'query [
return get-pck-file port query
]
num: length? ctx/info
out: make block! num * 2
foreach [file info] ctx/info [
append append out as file! file read/part skip head ctx/conn info/1 info/2
]
out
]
pick: func[port [port!] value [any-string!]][
get-pck-file port :value
]
select: func[port [port!] value [any-string!]][
get-pck-file port :value
]
close: func[port [port!]][
try [close port/state/conn]
port/state: none
port
]
;query: func[port /mode field][ ? field ]
index?: func[port /local ctx][
unless ctx: port/state [cause-error 'access 'not-open port/spec/ref]
any [
all [block? ctx/files index? ctx/files]
1
]
]
length?: func[port /local ctx files ][
unless ctx: port/state [cause-error 'access 'not-open port/spec/ref]
files: ctx/files
;? files
any [
all [integer? files files]
length? files
]
]
]
]
import-file: function[
port [port!] "Opened GPCK port"
spec [binary! string! file!] "Godot's *.import file"
dir [file!] "Target directory"
][
if file? spec [spec: read/string spec]
unless parse to string! spec [
thru {^/importer="} copy importer: to dbl-quote
thru {^/path="res://} copy res: to dbl-quote
thru {^/source_file="res://} copy file: to dbl-quote
to end
] [
sys/log/error 'GODOT "Failed to parse import spec!"
return none
]
;? importer ? res ? file
res: as file! res
bin: get-pck-file port res
file: join dir second split-path :file
switch/default importer [
"texture" [
img: decode-stex/only bin ;; resolves just the raw binary
case [
binary? img [ write file img ]
block? img [
sys/log/error 'GODOT "Multi-image not supported!" ? img
ask "Press to continue..."
]
'else [
sys/log/error 'GODOT ["Failed to decode stex file:" as-red res]
]
]
return file
]
"wav" [
try/except [
rsrc: decode-rsrc bin
bin: encode 'wav object [
channels: either select rsrc/3 'stereo [2][1]
data: :rsrc/3/data
]
][
sys/log/error 'GODOT ["Failed to import wav resource:" as-red res]
]
]
"clyde.dialogue" [
try/except [
rsrc: decode-rsrc bin
bin: decode 'json :rsrc/3/__data__
][
sys/log/error 'GODOT ["Failed to import wav resource:" as-red res]
]
]
"mp3" [] ;; do nothing
][
sys/log/error 'GODOT ["Unknown resource importer:" as-yellow importer]
]
write file bin
]
register-codec [
name: 'stex
type: 'image
title: "Godot's texture file"
suffixes: [%.stex]
decode: :decode-stex
]
register-codec [
name: 'gpck
type: 'application
title: "Godot's archive file"
suffixes: [%.pck]
decode: :decode-pck
]
extract-gpck: function[
"Extract content of Godot's archive file"
gpck [file!] "Path to Godot's *.pck file (or *.app directory)"
/into "Target directory"
dir [file!] "If not specified, used is the directory when the source file is"
][
port: open join gpck:// gpck
target: any [
dir
undirize append copy port/spec/path port/spec/target
]
if find [%.app %.pck] tmp: skip tail target -4 [ clear tmp ]
if all [
not into
exists? target
not empty? read target
][
sys/log/error 'GODOT ["Target directory already exists and is not empty!"]
sys/log/error 'GODOT as-red target
exit
]
;? target
foreach [path info] port/state/info [
;? path
sys/log/info 'GODOT ["Extracting:" path]
try/except [
set [dir: file:] split-path path
make-dir/deep target/:dir
bin: get-pck-file port path
write target/:path bin
if %.import = suffix? file [
import-file :port :bin target/:dir
]
][
sys/log/error 'GODOT ["Failed to extract:" as-red path]
sys/log/error 'GODOT system/state/last-error
]
]
]