# Unreal Engine 4 - Unreal Tournament 4 (*WindowsNoEditor.pak) (script 0.4.27e)
# script for QuickBMS http://quickbms.aluigi.org
math NO_TAIL_INFO = 0 # set it to 1 for archives with corrupt/missing tail information (extract without index)
math VERSION = 3 # set it to 3 if NO_TAIL_INFO = 1 for most of modern games
quickbmsver "0.12"
callfunction QUICKBMS_4GB_CHECK 1
# set your AES_KEY here as umodel hex ("0x1122...") or C string ("\x11\x22...")
# don't change AES_KEY_IS_SET, it will be handled automatically
set AES_KEY binary "0xEA9051DDACE1CCF98A0510F0E370BD986A75C74756E0309E6A578A47AF564255"
math TOC_FILE = 0
math ALTERNATIVE_MODE = 0
math AES_KEY_IS_SET = 0
math BASE_PATH_INCLUDED = 1
math DIR_FLAG = 1
math NAME_FROM_ARRAY = 0
math SKIP_COUNT = 0
get ARCHIVE_NAME basename
get ARCHIVE_PATH FILEPATH
math CHUNK_OFFSET_ABSOLUTE = -1 # default, enabled
# 1 = HIT
math WORKAROUND = 0
if NO_TAIL_INFO != 0
get OFFSET asize
math ALTERNATIVE_MODE = 1
else
goto -0xcc # version 11 (4.26-4.27)
savepos MAGIC_OFF
get MAGIC long
get VERSION long
endian guess VERSION
get OFFSET longlong
get SIZE longlong
getdstring HASH 20
xmath SIZE "MAGIC_OFF - OFFSET - 1"
get FSIZE asize
savepos CUR_POS
if CUR_POS = FSIZE
string COMP1 = ""
else
get CHECK byte
if CHECK > 1
goto -1 0 SEEK_CUR
endif
getdstring COMP1 32
getdstring COMP2 32
string COMP1 l COMP1
string COMP2 l COMP2
endif
if VERSION >= 3
goto MAGIC_OFF
goto -1 0 SEEK_CUR
get ENCRYPTED byte
if ENCRYPTED != 0
callfunction SET_AES_KEY 1
log MEMORY_FILE5 OFFSET SIZE
encryption "" ""
else
log MEMORY_FILE5 OFFSET SIZE
endif
math TOC_FILE5 = -5
endif
goto 0
callfunction GET_BASE_PATH 1
endif
get FILES long TOC_FILE5
getdstring DUMMY 12 TOC_FILE5
get HASHES_OFFSET longlong TOC_FILE5
math HASHES_OFFSET - OFFSET
get HASHES_SIZE longlong TOC_FILE5
getdstring DUMMY 24 TOC_FILE5
get NAMES_OFFSET longlong TOC_FILE5
math NAMES_OFFSET - OFFSET
get NAMES_SIZE longlong TOC_FILE5
getdstring DUMMY 24 TOC_FILE5
savepos BASE_INDEX_OFF TOC_FILE5
goto NAMES_OFFSET TOC_FILE5
math CHUNK_SIZE = 0x10000 # just in case...
for i = 0 < FILES
callfunction GET_NAME_AND_OFFSET 1
if NAME = ""
continue NEXT0
endif
savepos TMP_OFF TOC_FILE
get OFFSET longlong TOC_FILE
get ZSIZE longlong TOC_FILE
get SIZE longlong TOC_FILE
get ZIP long TOC_FILE
getdstring HASH 20 TOC_FILE
math CHUNKS = 0
math ENCRYPTED = 0
if VERSION >= 3
if ZIP != 0
get CHUNKS long TOC_FILE
for x = 0 < CHUNKS
get CHUNK_OFFSET longlong TOC_FILE
get CHUNK_END_OFFSET longlong TOC_FILE
putarray 0 x CHUNK_OFFSET
putarray 1 x CHUNK_END_OFFSET
next x
endif
get ENCRYPTED byte TOC_FILE
get CHUNK_SIZE long TOC_FILE
endif
#if ALTERNATIVE_MODE != 0
savepos TMP_OFF TOC_FILE
math OFFSET + TMP_OFF
#endif
#comtype copy
callfunction COMPRESSION_TYPE 1
if CHUNKS > 0
log NAME 0 0
append
math TMP_SIZE = SIZE
if CHUNK_OFFSET_ABSOLUTE < 0 && OFFSET != 0
getarray CHUNK_OFFSET 0 0
if CHUNK_OFFSET u< OFFSET
math CHUNK_OFFSET_ABSOLUTE = 0
else
math CHUNK_OFFSET_ABSOLUTE = 1
endif
endif
for x = 0 < CHUNKS
getarray CHUNK_OFFSET 0 x
getarray CHUNK_END_OFFSET 1 x
math CHUNK_ZSIZE = CHUNK_END_OFFSET
math CHUNK_ZSIZE - CHUNK_OFFSET
math CHUNK_XSIZE = CHUNK_ZSIZE
if ENCRYPTED != 0
callfunction SET_AES_KEY 1
math CHUNK_XSIZE x 16
endif
if TMP_SIZE u< CHUNK_SIZE
math CHUNK_SIZE = TMP_SIZE
endif
math CHUNK_OFFSET = OFFSET
if ZIP == 0
log NAME CHUNK_OFFSET CHUNK_SIZE 0 CHUNK_XSIZE
else
clog NAME CHUNK_OFFSET CHUNK_ZSIZE CHUNK_SIZE 0 CHUNK_XSIZE
endif
math TMP_SIZE - CHUNK_SIZE
math OFFSET + CHUNK_XSIZE
next x
append
else
# the file offset points to an entry containing
# the "same" OFFSET ZSIZE SIZE ZIP HASH ZERO fields,
# just an additional backup... so let's skip them
savepos BASE_OFF TOC_FILE
math BASE_OFF - TMP_OFF
math OFFSET + BASE_OFF
math XSIZE = ZSIZE
if ENCRYPTED != 0
callfunction SET_AES_KEY 1
math XSIZE x 16
endif
if ZIP == 0
math BLOCK = 0x40000000
xmath FSIZE "OFFSET + ZSIZE"
log NAME 0 0
append
for OFFSET = OFFSET < FSIZE
xmath DIFF "FSIZE - OFFSET"
if DIFF < BLOCK
math XSIZE = DIFF
if ENCRYPTED != 0
math XSIZE x 16
endif
log NAME OFFSET DIFF 0 XSIZE
else
log NAME OFFSET BLOCK
endif
math OFFSET + BLOCK
next
append
else
clog NAME OFFSET ZSIZE SIZE 0 XSIZE
endif
endif
encryption "" ""
if ALTERNATIVE_MODE != 0
if CHUNKS == 0
math OFFSET + XSIZE
endif
goto OFFSET
get TMP1 longlong
get CHECK byte
if TMP1 == 0 && CHECK != 0
goto OFFSET
continue NEXT1
else
goto OFFSET
endif
xmath CHECK "0x800 - (OFFSET % 0x800)"
if CHECK <= 16
padding 0x800
endif
savepos OFFSET
get TMP1 longlong
get TMP2 longlong
if TMP2 == 0
padding 0x800
else
goto OFFSET
endif
label NEXT1
endif
label NEXT0
next i
print "\nEntries ignored: %SKIP_COUNT%"
for i = 0 < SKIP_COUNT
getarray NAME 7 i
print "Ignored entry: %NAME%"
next i
startfunction SET_AES_KEY_ASK
math AES_KEY_IS_SET = 1
print "The archive is encrypted, you need to provide the key"
if AES_KEY == ""
set KEY unknown "???"
else
set KEY binary AES_KEY
endif
if KEY == ""
math AES_KEY_IS_SET = -1
set AES_KEY string "No key provided, encryption disabled"
elif KEY strncmp "0x"
string KEY << 2
string AES_KEY h KEY
else
set AES_KEY binary KEY
endif
print "KEY: %AES_KEY%"
endfunction
startfunction SET_AES_KEY
if AES_KEY_IS_SET == 0
callfunction SET_AES_KEY_ASK 1
endif
if AES_KEY_IS_SET > 0
encryption aes AES_KEY "" 0 32
endif
endfunction
startfunction GET_BASE_PATH
get NAMESZ long TOC_FILE5
getdstring BASE_PATH NAMESZ TOC_FILE5
if NAMESZ != 0x0A && NAMESZ < 0xFF
string BASE_PATH | "../../../"
math BASE_PATH_INCLUDED = 0
endif
endfunction
startfunction CHECK_UNICODE
if NAMESZ >= 0
getdstring RESULT NAMESZ TOC_FILE5
else
math NAMESZ n NAMESZ
math NAMESZ * 2
getdstring RESULT NAMESZ TOC_FILE5
set RESULT unicode RESULT
endif
endfunction
startfunction GET_NAME_AND_OFFSET
if NAME_FROM_ARRAY = 1
if CURR_NAME < DIR_FILES
getarray NAME 5 CURR_NAME
getarray OFFSET 6 CURR_NAME
goto OFFSET
math CURR_NAME + 1
if CURR_NAME = DIR_FILES
math NAME_FROM_ARRAY = 0
endif
endif
else
if DIR_FLAG = 1
get DIR_COUNT long TOC_FILE5
math DIR_FLAG = 0
endif
if DIR_COUNT = 0
math DIR_FLAG = 1
callfunction GET_NAME_AND_OFFSET 1
else
math DIR_COUNT - 1
get NAMESZ signed_long TOC_FILE5
callfunction CHECK_UNICODE 1
string DIR_NAME = RESULT
get DIR_FILES long TOC_FILE5
if DIR_FILES = 0
callfunction GET_NAME_AND_OFFSET 1
else
for y = 0 < DIR_FILES
get NAMESZ signed_long TOC_FILE5
callfunction CHECK_UNICODE 1
string NAME = RESULT
string NAME p "%s%s" DIR_NAME NAME
if BASE_PATH_INCLUDED == 0
string NAME p "%s%s" BASE_PATH NAME
endif
putarray 5 y NAME
get OFFSET long TOC_FILE5
savepos TMP_INDEX_OFF TOC_FILE5
if OFFSET != 0x80000000 && OFFSET != 0x7FFFFFFF
xmath INDEX_OFF "BASE_INDEX_OFF + OFFSET"
goto INDEX_OFF TOC_FILE5
get FLAGS long TOC_FILE5
xmath HAS_SIZE "FLAGS & 0x3F"
xmath IS_64 "FLAGS >> 28"
if HAS_SIZE = 0x3F
get CHUNK_SIZE long TOC_FILE5
endif
if IS_64 = 0xE
get OFFSET long TOC_FILE5
else
get OFFSET longlong TOC_FILE5
endif
else
putarray 7 SKIP_COUNT NAME
math SKIP_COUNT + 1
string NAME = ""
putarray 5 y NAME
endif
putarray 6 y OFFSET
goto TMP_INDEX_OFF TOC_FILE5
next y
math NAME_FROM_ARRAY = 1
math CURR_NAME = 0
callfunction GET_NAME_AND_OFFSET 1
endif
endif
endif
endfunction
startfunction COMPRESSION_TYPE
if COMP1 = ""
comtype zlib
endif
if ZIP = 1 && COMP1 = "zlib"
comtype zlib
elif ZIP = 1 && COMP1 = "zstd"
comtype zstd
elif ZIP = 1 && COMP1 = "oodle"
comtype oodle
elif ZIP = 1 && COMP1 = "lz4"
comtype lz4
elif ZIP = 1 && COMP1 = "gzip"
comtype gzip
elif ZIP = 2 && COMP2 = "zlib"
comtype zlib
elif ZIP = 2 && COMP2 = "zstd"
comtype zstd
elif ZIP = 2 && COMP2 = "oodle"
comtype oodle
elif ZIP = 2 && COMP2 = "lz4"
comtype lz4
elif ZIP = 2 && COMP2 = "gzip"
comtype gzip
elif ZIP = 3 || ZIP = 4 || ZIP = 0x10 # 3 - Faith of Danschant, 4 - Days Gone, 10 - Ashen
comtype oodle
if WORKAROUND == 2
comtype lz4
endif
endif
endfunction
startfunction QUICKBMS_4GB_CHECK
math TMP64 = 0x10000000
math TMP64 * 16
if TMP64 == 0
print "You must use quickbms_4gb_files.exe with this script!"
cleanexit
endif
endfunction