--!movie
--****************************************************************************
-- Decompiler
-- @author Valentin Schmidt
-- @version 0.4
--****************************************************************************
-- We only use a single global, so we can restore it easily if a loaded movie called
-- "clearGlobals" in its "prepareMovie" handler (which unfortunately can't be blocked).
global $ -- Decompile "decompiler.exe" to see what framework "$" is about ;-)
----------------------------------------
--
----------------------------------------
on startMovie
_player.debugPlaybackEnabled = TRUE
-- libs
$.import("cast")
$.import("drop")
$.import("file")
$.import("filesystem")
$.import("regex")
$.import("shell")
_player.itemDelimiter = "."
$[#version] = float(_player.productVersion.item[1..2]) -- version of current Director runtime
-- movie props
_movie.stage.rect = rect(0, 0, 480, 320)
_movie.centerStage = TRUE
_movie.stage.title = "Drop .dcr/.dxr/.dir/.cct/.cxt/.cst/Projector file(s) into window"
_movie.stage.titlebarOptions.visible = TRUE
_movie.stage.bgColor = rgb("#cccccc")
_movie.puppetTempo(30)
makeUI()
_movie.updateStage()
_movie.stage.visible = TRUE
$.drop.setCallback(#dropEvent)
$.drop.start()
$[#alerthook] = $.include("data/alert.ls").new()
bin = $.PATH & "bin" & $.PD & "projectorrays.exe"
if bin contains SPACE then bin = QUOTE&bin"E
$[#bin] = bin
$[#magic_macos] = []
$.magic_macos.append([74, 111, 121, 33]) -- 4A 6F 79 21 = "Joy!" (D10- macOS projector)
$.magic_macos.append([202, 254, 186, 190]) -- CA FE BA BE (D11+ Projector Resources)
$.magic_macos.append([206, 250, 237, 254]) -- CE FA ED FE (D11+ Projector Intel Resources)
$.magic_macos.append([82, 73, 70, 88]) -- 52 49 46 58 = "RIFX" (raw data fork of classic mac app)
the floatPrecision = 1 -- for printing versions
end
----------------------------------------
--
----------------------------------------
on makeUI
sn = 1
y = 10
m = new(#button)
m.buttonType = #checkBox
m.name = "fixes"
m.rect = rect(0, 0, 460, 0)
m.text = "Apply heuristic fixes to decompiled code"
m.hilite = TRUE
_movie.puppetSprite(sn, TRUE)
sprite(sn).ink = 36
sprite(sn).loc = point(10, y)
sprite(sn).member = m
sn = sn + 1
y = y + 25
m = new(#button)
m.buttonType = #checkBox
m.name = "fix_d4"
m.rect = rect(0, 0, 460, 0)
m.text = "Fix decompiled scripts without handlers (Director 4)"
_movie.puppetSprite(sn, TRUE)
sprite(sn).ink = 36
sprite(sn).loc = point(10, y)
sprite(sn).member = m
sn = sn + 1
y = y + 25
m = new(#button)
m.buttonType = #checkBox
m.name = "no_dir_decompile"
m.rect = rect(0, 0, 460, 0)
m.text = "Don't decompile scripts of unprotected files (.dir and .cst)"
_movie.puppetSprite(sn, TRUE)
sprite(sn).ink = 36
sprite(sn).loc = point(10, y)
sprite(sn).member = m
sn = sn + 1
y = y + 25
m = new(#button)
m.buttonType = #checkBox
m.name = "keep"
m.rect = rect(0, 0, 460, 0)
m.text = "Keep decompiled scripts (in TMP folder)"
_movie.puppetSprite(sn, TRUE)
sprite(sn).ink = 36
sprite(sn).loc = point(10, y)
sprite(sn).member = m
sn = sn + 1
y = y + 25
m = new(#button)
m.buttonType = #checkBox
m.name = "version_only"
m.rect = rect(0, 0, 460, 0)
m.text = "Only print Director file version"
_movie.puppetSprite(sn, TRUE)
sprite(sn).ink = 36
sprite(sn).loc = point(10, y)
sprite(sn).member = m
sn = sn + 1
m = new(#button)
m.buttonType = #pushButton
m.name = "run"
m.rect = rect(0, 0, 200, 0)
m.alignment = "center"
m.text = "Try to run a movie..."
m.scriptText = "on mouseUp"&RETURN&"run"&RETURN&"end"
_movie.puppetSprite(sn, TRUE)
sprite(sn).ink = 36
sprite(sn).loc = point(140, 290)
sprite(sn).member = m
sn = sn + 1
end
----------------------------------------
--
----------------------------------------
on run
fn = $.file.selectFileOpen("Select movie", "Director movies (.dir .dxr .dcr),*.dir;*.dxr;*.dcr", "")
if fn <> "" then
w = window().new(fn)
w.open()
end if
end
----------------------------------------
-- @callback
----------------------------------------
on dropEvent (drop_data)
repeat with fn in drop_data[#folders]
_player.itemDelimiter = "."
if the last item of fn = "app" then
bn = $.file.getBaseName(fn)
fn_bin = fn & $.PD & "Contents" & $.PD & "MacOS" & $.PD & bn
if $.file.exists(fn_bin) then
msg "----------------------------------------"
msg "Unpacking macOS projector" && QUOTE & $.file.getFileName(fn) & QUOTE & " ..."
msg "----------------------------------------"
output_dir = $.file.getFilePath(fn) & $.PD & bn & "_contents"
$.filesystem.folderCreate(output_dir)
unpackProjector(fn_bin, output_dir, TRUE)
msg "Done" & RETURN
end if
next repeat
end if
end repeat
repeat with fn in drop_data[#files]
ext = $.file.getFileType(fn)
if ext = "exe" then
msg "----------------------------------------"
msg "Unpacking Windows projector" && QUOTE & $.file.getFileName(fn) & QUOTE & " ..."
msg "----------------------------------------"
output_dir = $.file.getFilePath(fn) & $.PD & $.file.getBaseName(fn) & "_contents"
$.filesystem.folderCreate(output_dir)
unpackProjector(fn, output_dir, FALSE)
msg "Done" & RETURN
next repeat
end if
if not ["dcr", "dxr", "dir", "cct", "cxt", "cst"].getPos(ext) then
-- check if it's a macOS binary
fp = $.file.fopen(fn, "rb")
magic = $.file.freadbytes(fp, 4)
$.file.fclose(fp)
is_macos_bin = FALSE
repeat with m in $.magic_macos
if magic[1]=m[1] and magic[2]=m[2] and magic[3]=m[3] and magic[4]=m[4] then
is_macos_bin = TRUE
exit repeat
end if
end repeat
if is_macos_bin then
msg "----------------------------------------"
msg "Unpacking macOS projector" && QUOTE & $.file.getFileName(fn) & QUOTE & " ..."
msg "----------------------------------------"
output_dir = $.file.getFilePath(fn) & $.PD & $.file.getBaseName(fn) & "_contents"
$.filesystem.folderCreate(output_dir)
unpackProjector(fn, output_dir, TRUE)
msg "Done" & RETURN
next repeat
end if
_player.alert("Unsupported file extension:" && ext)
next repeat
end if
if member("version_only").hilite then
checkVersion(fn, ext)
next repeat
end if
msg "----------------------------------------"
msg "Decompiling" && QUOTE & $.file.getFileName(fn) & QUOTE & " ..."
msg "----------------------------------------"
tmp_dir = getEnvVar("TMP") & $.PD & $.file.getBaseName(fn)
$.filesystem.folderDeleteRecursive(tmp_dir)
$.filesystem.folderCreate(tmp_dir)
if ["cct", "cxt", "cst"].getPos(ext) then
fn_dest = fn & ".cst"
$.filesystem.fileDelete(fn_dest)
unprotectCastLib(fn, fn_dest, tmp_dir, member("fixes").hilite, member("fix_d4").hilite)
else
fn_dest = fn & ".dir"
$.filesystem.fileDelete(fn_dest)
unprotectMovie(fn, fn_dest, tmp_dir, member("fixes").hilite, member("fix_d4").hilite)
end if
$.shell.shell_setcurrentdir($.PATH)
if not member("keep").hilite then
$.filesystem.folderDeleteRecursive(tmp_dir)
end if
if $.filesystem.fileExists(fn_dest) then
msg "File processed successfully" & RETURN
else
msg "Error: failed to process file" & RETURN
end if
end repeat
_player.alertHook = 0
end
----------------------------------------
--
----------------------------------------
on unprotectCastLib (fn, fn_dest, script_dir, apply_fixes, fix_d4)
ext = $.file.getFileType(fn)
-- version check
if not checkVersion(fn, ext) then return
decompile = ext <> "cst" or not member("no_dir_decompile").hilite
if decompile then
$.shell.shell_setcurrentdir(script_dir)
cmd = $.bin && QUOTE & fn & QUOTE
res = $.shell.shell_cmd(cmd).line[1]
scripts = $.filesystem.folderList(script_dir)
end if
_player.alertHook = $.alerthook
cn = $.cast.attachCastLib("dummy", fn)
if decompile then
_player.itemDelimiter = "_"
repeat with scr in scripts
bn = scr.char[1..scr.length-3]
mn = integer(bn.item[2])
-- custom projectorrays.exe uses 32000+ for scripts it failed to assign to members
if mn > 32000 then next repeat
code = $.file.getString(script_dir & $.PD & scr, "windows-1252")
code = cleanCode(code, apply_fixes)
msg "Loading script member("&mn&")"
mem = member(mn, cn)
if mem.linked and mem.type = #script then
a = mem.name
b = mem.scriptType
c = mem.scriptSyntax
m = new(#script, mem)
m.name = a
m.scriptType = b
m.scriptSyntax = c
end if
mem.scriptText = code
end repeat
end if
castLib(cn).save(fn_dest)
$.cast.detachCastLib(cn)
_player.alertHook = 0
end
----------------------------------------
--
----------------------------------------
on unprotectMovie (fn, fn_dest, script_dir, apply_fixes, fix_d4)
ext = $.file.getFileType(fn)
-- version check
if not checkVersion(fn, ext) then return
decompile = ext <> "dir" or not member("no_dir_decompile").hilite
--decompile = not member("no_dir_decompile").hilite
if decompile then
$.shell.shell_setcurrentdir(script_dir)
cmd = $.bin && QUOTE & fn & QUOTE
res = $.shell.shell_cmd(cmd).line[1]
scripts = $.filesystem.folderList(script_dir)
end if
-- load movie as MIAW
tmp = $ -- protect against "clearglobals()" call
w = window().new("tmp")
_player.alertHook = $.alerthook
w.fileName = fn
w.visible = FALSE
w.movie.pause()
$ = tmp
-- check for external castlibs
external_cst = []
repeat with i = 2 to w.movie.castlib.count
fn_cst = w.movie.castlib[i].filename
if fn_cst <> "" and fn_cst <> w.movie.path & w.movie.name then
external_cst.append(i)
if ext <> "dir" then
$.filesystem.folderCreate(script_dir & $.PD & string(i))
$.shell.shell_setcurrentdir(script_dir & $.PD & string(i))
msg "Decompiling external cast" && QUOTE & fn_cst & QUOTE
cmd = $.bin && QUOTE & fn_cst & QUOTE
res = $.shell.shell_cmd(cmd).line[1]
v = integer(res.word[3])
if v = 1200 then
movie_version = 11.5
else
movie_version = (v/10)/10. -- ignore final digit: 404 => 4.0
end if
msg res && "(" & movie_version & ")"
$.shell.shell_setcurrentdir(script_dir)
end if
end if
end repeat
if decompile then
-- Director 4 fix: replace UNKNOWN_NAME_ handlers either with exitFrame or mouseUp
if fix_d4 then
frame_scripts = []
cntf = w.movie.lastFrame
w.movie.go(cntf)
repeat with fn = 1 to cntf
w.movie.go(fn)
fs = w.movie.frameScript -- unique memberNum
if fs=0 then next repeat
mn = fs mod 131072 -- 0x20000
cn = fs / 131072 + 1
frame_scripts.append(w.movie.castlib[cn].member[mn])
fn = w.movie.sprite[0].endFrame
end repeat
end if
_player.itemDelimiter = "_"
scripts = $.filesystem.folderList(script_dir)
repeat with scr in scripts
if the last char of scr = $.PD then -- scripts of external castlib in subfolder
delete the last char of scr
cn = integer(scr)
cst_scripts = $.filesystem.folderList(script_dir & $.PD & scr)
repeat with cst_scr in cst_scripts
code = $.file.getString(script_dir & $.PD & scr & $.PD & cst_scr, "windows-1252")
code = cleanCode(code, apply_fixes)
bn = cst_scr.char[1..cst_scr.length-3]
mn = integer(bn.item[2])
if mn > 32000 then next repeat -- custom projectorrays.exe uses 32000+ for scripts it failed to assign to members
msg "Loading script member("&mn&", "&cn&")"
tell w
mem = member(mn, cn)
if mem.linked and mem.type = #script then
a = mem.name
b = mem.scriptType
c = mem.scriptSyntax
m = new(#script, mem)
m.name = a
m.scriptType = b
m.scriptSyntax = c
end if
mem.scriptText = code
if fix_d4 then
if mem.script.handlers().count = 1 then
if frame_scripts.getPos(mem) then
mem.scriptText = $.regex.replace("^on UNKNOWN_NAME_.*$", "on exitFrame", mem.scriptText, "m")
else
mem.scriptText = $.regex.replace("^on UNKNOWN_NAME_.*$", "on mouseUp", mem.scriptText, "m")
end if
end if
end if
end tell
end repeat
else
bn = scr.char[1..scr.length-3]
cn = integer(bn.item[1])
mn = integer(bn.item[2])
if cn = 0 then cn = 1
if mn > 32000 then next repeat --
code = $.file.getString(script_dir & $.PD & scr, "windows-1252")
code = cleanCode(code, apply_fixes)
msg "Loading script member("&mn&", "&cn&")"
tell w
mem = member(mn, cn)
if mem.linked and mem.type = #script then
a = mem.name
b = mem.scriptType
c = mem.scriptSyntax
m = new(#script, mem)
m.name = a
m.scriptType = b
m.scriptSyntax = c
end if
mem.scriptText = code
if fix_d4 then
if mem.script.handlers().count = 1 then
if frame_scripts.getPos(mem) then
mem.scriptText = $.regex.replace("^on UNKNOWN_NAME_.*$", "on exitFrame", mem.scriptText, "m")
else
mem.scriptText = $.regex.replace("^on UNKNOWN_NAME_.*$", "on mouseUp", mem.scriptText, "m")
end if
end if
end if
end tell
end if
end repeat
end if
repeat with n in external_cst
w.movie.castlib[n].save(w.movie.castlib[n].filename & ".cst")
end repeat
w.movie.saveMovie(fn_dest)
tmp = $
w.fileName = $.PATH & "data" & $.PD & "dummy.dir" -- prevents calling stopMovie in loaded movie
w.close()
w.forget()
$ = tmp
end
----------------------------------------
--
----------------------------------------
on checkVersion (fn, ext)
cmd = $.bin && "--no-decompile" && QUOTE & fn & QUOTE
res = $.shell.shell_cmd(cmd).line[1]
v = integer(res.word[3])
if v = 1200 then
movie_version = 11.5
else
movie_version = (v/10)/10. -- ignore final digit: 404 => 4.0
end if
msg res && "(" & movie_version & ")"
version_mayor = bitOr($.version, 0)
if movie_version > $.version then
_player.alert("File too new!"&RETURN&RETURN&\
"This decompiler only supports Director files"&RETURN&\
"up to version" && version_mayor & ".x."&RETURN&RETURN&\
"Use the D" & (version_mayor+1) & " decompiler version to handle this file.")
return FALSE
else if ext = "dcr" or ext = "cct" then
if movie_version >= version_mayor then
msg = "File too new!"&RETURN&RETURN&\
"This decompiler only supports compressed Director files"&RETURN&\
"up to version" && (version_mayor-1) & ".x."
if version_mayor < 12 then
put RETURN & RETURN & "Use the D" & (version_mayor+1) & " decompiler version to handle this file." after msg
end if
_player.alert(msg)
return FALSE
end if
end if
return TRUE
end
----------------------------------------
--
----------------------------------------
on cleanCode (code, apply_fixes)
code = $.regex.replace("\x0D\x0A", RETURN, code)
if apply_fixes then code = fixCode(code)
return code
end
----------------------------------------
-- Applies rules based on regular expressions that try to improve the code that ProjectorRays creates.
-- Dear user: comment out rules that fail and/or add new rules that make sense.
----------------------------------------
on fixCode (code)
if voidP($[#regList]) then
$[#regList] = []
$[#replList] = []
_addRule($.regex.re("sound\(#(\w+), (.+)\)"), "sound($2).$1()")
_addRule($.regex.re("(the [A-Za-z]+)\."), "($1).")
_addRule($.regex.re("([A-Za-z0-9]+)\.char\[(\d+)\].delete\(\)"), "delete char $2 of $1")
_addRule($.regex.re("([A-Za-z0-9]+)\.line\[(\d+)\].delete\(\)"), "delete line $2 of $1")
_addRule($.regex.re("the ERROR of sprite"), "the member of sprite")
_addRule($.regex.re("\.count\(#(\w+)\)"), ".$1.count")
_addRule($.regex.re("(.+=.+):$", "gm"), "($1):")
_addRule($.regex.re("open\((.+),(.+)\)"), "_player.open($1,$2)")
_addRule($.regex.re(" the ERROR"), " ERROR")
_addRule($.regex.re("set the keyDownScript to ""E&" nothing\x0D""E), "set the keyDownScript to EMPTY")
_addRule($.regex.re("set the timeoutScript to ""E&" nothing\x0D""E), "set the timeoutScript to EMPTY")
_addRule($.regex.re("set the keyDownScript to ""E&" (\w+)\x0D""E), "set the keyDownScript to ""E&"$1""E)
_addRule($.regex.re("(window\(.+\))\.windowType"), "$1().type")
q = QUOTE&""E&""E
_addRule($.regex.re("set the timeoutScript to ""E&" go movie ""E&"(.+)""E&"\x0D""E),\
"set the timeoutScript to ""E&"go movie "&q&"$1"&q&" ""E)
end if
code = $.regex.re_replace_multi($.regList, $.replList, code)
-- prepend line "global ERROR" to scripts that contain ERROR, so they (might) still compile.
if count($.regex.match("ERROR", code, "")) then put "global ERROR"&RETURN before code
return code
end
----------------------------------------
--
----------------------------------------
on _addRule (reg, repl)
$.regList.append(reg)
$.replList.append(repl)
end
----------------------------------------
--
----------------------------------------
on bytesOffset (needle, haystack, start_pos)
if voidP(start_pos) then start_pos = 1
if stringP(needle) then
s = needle
needle = []
len = s.length
repeat with i = 1 to len
if s.char[i] = "." then
needle[i] = -1
else
needle[i] = chartonum(s.char[i])
end if
end repeat
end if
cnt = haystack.length - needle.count + 1
cnt2 = needle.count
repeat with i = start_pos to cnt
ok = TRUE
repeat with j = 1 to cnt2
if needle[j] < 0 then next repeat
if haystack[i+j-1]<>needle[j] then
ok = FALSE
exit repeat
end if
end repeat
if ok then return i
end repeat
return 0
end
----------------------------------------
--
----------------------------------------
on unpackProjector (exe_file, output_dir, is_mac)
if the last char of output_dir<>$.PD then put $.PD after output_dir
strip = 32 -- 0 for raw data fork
fp = $.file.fopen(exe_file, "rb")
exe_size = $.file.fsize(fp) - strip
$.file.fseek(fp, strip)
data = $.file.freadbytes(fp, exe_size)
$.file.fclose(fp)
if exe_size<1 then return _player.alert("Couldn't open "&exe_file&"!")
if is_mac then
is_bigendian = TRUE
start_pos = bytesOffset("RIFX....APPL", data)
if start_pos = 0 then
is_bigendian = FALSE
start_pos = bytesOffset("XFIR....LPPA", data)
end if
else
is_bigendian = FALSE
start_pos = bytesOffset("XFIR....LPPA", data)
if start_pos = 0 then
is_bigendian = TRUE
start_pos = bytesOffset("RIFX....APPL", data)
end if
end if
if start_pos = 0 then
return _player.alert("Couldn't identify "&exe_file&" as Director projector!")
end if
endian = [#littleEndian, #bigEndian][is_bigendian+1]
data.endian = endian
-- find XFIR and RIFF chunks
res = []
res2 = []
xres = []
start_pos = start_pos + 12
if is_bigendian then
repeat with i = start_pos to exe_size
c = data[i]
if c = 82 then -- R
data.position = i + 1
s1 = data.readRawString(3)
if s1 = "IFX" then
data.position = data.position + 4
s2 = data.readRawString(4)
if s2 = "MV93" or s2 = "MC95" then --
res.add([i, s2])
i = i+8
else if s2 = "FGDM" OR s2 = "FGDC" then
res2.add([i, s2])
i = i + 8
end if
i = i+3
else if s1 = "IFF" then
data.position = data.position + 4
s2 = data.readRawString(8)
if s2 = "XtraFILE" then
xres.add(i)
i = i + 12
end if
i = i + 3
end if
end if
end repeat
else
repeat with i = start_pos to exe_size
c = data[i]
if c = 88 then -- X
data.position = i + 1
s1 = data.readRawString(3)
if s1 = "FIR" then
data.position = data.position + 4
s2 = data.readRawString(4)
if s2 = "39VM" then
res.add([i, s2])
i = i + 8
else if s2 = "MDGF" OR s2 = "CDGF" then
res2.add([i, s2])
i = i + 8
end if
end if
i = i + 3
else if c = 82 then -- R --> RIFF....XtraFILE
data.position = i + 1
s1 = data.readRawString(3)
if s1 = "IFF" then
data.position = data.position + 4
s2 = data.readRawString(8)
if s2 = "XtraFILE" then
xres.add(i)
i = i + 12
end if
i = i + 3
end if
end if
end repeat
end if
if res.count = 0 then
if res2.count = 0 then
return _player.alert("Nothing found to extract!")
else
compressFlag = 1
res = res2
end if
else
compressFlag = 0
end if
-- header-template
header = bytearray()
header.endian = endian
header.writeRawString(["XFIR", "RIFX"][is_bigendian+1], 4)
header.writeInt32(exe_size - 8 + strip)
header.writeRawString(["39VMpami", "MV93imap"][is_bigendian+1], 8)
header.writeInt32(24)
header.writeInt32(1)
header.writeInt32(0) --> size
header.writeInt32(1923)
-- extract file names from DICT
dir_names = []
x32_names = []
-- find Dict chunk (littleEndian: tciD, bigEndian: Dict)
pos = res[1][1] -- position of first XFIR/RIFX
needle = [[116,99,105,68], [68,105,99,116]][is_bigendian+1] -- tciD / Dict
repeat with i = pos - 3 down to 1
if data[i] = needle[1] then
if data[i + 1] = needle[2] then
if data[i + 2] = needle[3] then
if data[i + 3] = needle[4] then
exit repeat
end if
end if
end if
end if
end repeat
if i > 1 then -- found!
data.position = i
dict = data.readByteArray(pos-i)
dict.endian = endian
dict.position = 25
cnt = dict.readInt32()
-- no idea what's going on here
if cnt > 65535 then
dict.endian = [#bigEndian, #littleEndian][is_bigendian+1]
dict.position = 25
cnt = dict.readInt32()
dict.endian = endian
end if
if cnt = 1 then
dir_names.add("main.dxr")
else
pt = cnt*8 + [65, 68][is_bigendian+1]
repeat with i = 1 to cnt
len = dict[pt] - [0, 3][is_bigendian+1]
dict.position = pt + 4
fn = dict.readRawString(len)
if fn contains "Xtras:" or ["x32", "cpio"].getPos($.file.getFileType(fn)) then
x32_names.add($.file.getFileName(fn))
else
dir_names.add($.file.getFileName(fn))
end if
if i<cnt then
-- find next
pt = pt + 4 + len
repeat while (dict[pt] = 0)
pt = pt + 1
end repeat
end if
end repeat
end if
end if
if compressFlag then -- compressed files
msg "Files in projector are compressed"
file_num = 0
repeat with r in res
pos = r[1]
file_num = file_num + 1
fn = dir_names[file_num]
ext = $.file.getFileType(fn)
if ext = "" then -- just guessing
put ".cct" after fn
else if ext = "cst" then
fn = fn.char[1..fn.length - 3] & "cct"
else
fn = fn.char[1..fn.length - 3] & "dcr"
end if
msg "Extracting " & QUOTE & fn & QUOTE & " ..."
data.position = pos + 4
chunk_size = data.readInt32()
fp = $.file.fopen(output_dir & fn, "wb")
data.position = pos
chunk = data.readByteArray(chunk_size + 8)
$.file.fwritebytes(fp, chunk)
$.file.fclose(fp)
end repeat
else -- non-compressed files
msg "Files in projector are not compressed"
file_num = 0
repeat with r in res
pos = r[1] + 32 + strip
file_num = file_num + 1
header.position = 25
header.writeInt32(pos - 1 + 12)
fn = dir_names[file_num] -- err
ext = $.file.getFileType(fn)
if ext = "" then -- just guessing
put ".cxt" after fn
else if ext = "cst" then
fn = fn.char[1..fn.length - 3] & "cxt"
else
fn = fn.char[1..fn.length - 3] & "dxr"
end if
msg "Extracting " & QUOTE & fn & QUOTE & " ..."
fp = $.file.fopen(output_dir & fn, "wb")
$.file.fwritebytes(fp, header)
$.file.fwritebytes(fp, data)
$.file.fclose(fp)
end repeat
end if
-- extract xtras
file_num = 0
data.endian = #bigEndian -- always bigEndian
repeat with pos in xres
data.position = pos + 4
chunk_size = data.readInt32()
data.position = pos + 48
chunk = data.readBytearray(chunk_size - 40)
file_num = file_num + 1
fn = x32_names[file_num]
msg "Extracting " & QUOTE & fn & QUOTE & " ..."
chunk.uncompress()
$.file.putBytes(output_dir & fn, chunk)
end repeat
end
----------------------------------------
-- prints to message window without adding quotes
-- (based on CommandLine xtra)
----------------------------------------
on msg (str)
printMsg(str)
printMsg(RETURN)
end