aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authoronefang2020-01-13 06:07:11 +1000
committeronefang2020-01-13 06:07:11 +1000
commit50a5a08416ed8ebcfe3d35eec534de503e18679e (patch)
tree981e2d4527d10fda8941ad263b41d00380ab426c
parentMove luajit.pc to lib directory. (diff)
downloadSledjHamr-50a5a08416ed8ebcfe3d35eec534de503e18679e.zip
SledjHamr-50a5a08416ed8ebcfe3d35eec534de503e18679e.tar.gz
SledjHamr-50a5a08416ed8ebcfe3d35eec534de503e18679e.tar.bz2
SledjHamr-50a5a08416ed8ebcfe3d35eec534de503e18679e.tar.xz
Experimental IAR unpacker.
-rwxr-xr-xsrc/love/unpack_IAR.lua392
1 files changed, 392 insertions, 0 deletions
diff --git a/src/love/unpack_IAR.lua b/src/love/unpack_IAR.lua
new file mode 100755
index 0000000..eeacc52
--- /dev/null
+++ b/src/love/unpack_IAR.lua
@@ -0,0 +1,392 @@
1#!/usr/bin/env luajit
2
3-- Pass the name of an IAR or a gitIAR file, this will unpack it into something resembling an in world inventory folder structure.
4
5-- TODO - make the output compatible with SledjHamr/docs/SledjHamr/love.txt
6
7local lxp = require "lxp" -- Lua-expat
8lxp.lom = require "lxp/lom"
9local posix = require "posix"
10
11
12local args = {...}
13local tarball = args[1]
14
15
16assetsDir = "assets"
17local assetTypes =
18{
19 [0] = "texture.jp2", -- YAY, Luajit is sane and allows the number 0 as a table key.
20 [1] = "sound.ogg",
21 [2] = "callingcard.txt",
22 [3] = "landmark.txt",
23 [4] = "script.lsl",
24 [5] = "clothing.txt",
25 [6] = "object.xml",
26 [7] = "notecard.txt",
27 [8] = "CATEGARY", -- CATEGORY
28 [9] = "ROOT", -- ROOT_CATEGORY
29 [10] = "script.lsl",
30 [11] = "BYTECODE", -- LSL_BYTECODE
31 [12] = "texture.tga",
32 [13] = "bodypart.txt",
33 [14] = "TRASH", -- TRASH
34 [15] = "SNAPSHOTS", -- SNAPSHOT_CATEGORY
35 [16] = "LOSTANDFOUND", -- LOST_AND_FOUND
36 [17] = "sound.wav",
37 [18] = "image.tga",
38 [19] = "image.jpeg",
39 [20] = "animation.bvh",
40 [21] = "gesture.txt",
41 [22] = "SIMSTATE", -- SIMSTATE
42 [23] = "DUNNO",
43 [24] = "link", --[[
44 So the AssetID in this one is the AssetID for the thing it links to -
45 Current Outfit__6ff2f548-abfb-245f-d8cc-a72e7a7a08ef/Girl bold base Hair__4692489c-4a70-4b0c-acd4-87943a54436b.xml
46 <AssetID>56d40470-4501-4a1f-8c3a-b22bf1fd3893</AssetID>
47 Teen Girl Avatar__bc4c51e9-08a9-4f3b-89f5-de9dc68c8050/Girl bold base Hair__56d40470-4501-4a1f-8c3a-b22bf1fd3893.xml
48 <AssetID>104d875b-d8c9-49bf-a499-47728fdec164</AssetID>
49 assets/104d875b-d8c9-49bf-a499-47728fdec164_bodypart.txt
50 But I strip all those filename UUIDs, and the link doesn't include the folder details.
51 I'll have to keep track of the stripped UUIDs -> file mappings.
52 Which I wanted to do anyway, coz viewers and such will be asking for UUIDs.
53 ]]
54}
55
56-- https://en.wikipedia.org/wiki/Filename - Reserved characters and words
57-- OpenSim makes &#xx; out of characters it doesn't like.
58-- Unix wants &#; escaped in command lines. We are quoting them anyway, but let's be safe.
59local sanitiser =
60{
61 ["["] = "{",
62 ["]"] = "}",
63 ["\\"] = "_",
64 ["/"] = "_",
65 ["<"] = "_",
66 [">"] = "_",
67 [":"] = "_",
68 [";"] = "_",
69 ["*"] = "_",
70 ["?"] = "_",
71 ["'"] = "_",
72 ['"'] = "_",
73 ["|"] = "!",
74 ["@"] = "^",
75 ["#"] = "^",
76 ["$"] = "^",
77-- ["%"] = "^", -- Only a problem in RT-11
78 ["&"] = "^",
79 [" "] = "_",
80-- ["."] = "-", -- Position and operating system dependant.
81 ["%c"] = "^",
82 ["\x60"] = "_",
83 ["\x7F"] = "_",
84}
85local sanitise = function (s)
86 return s:gsub("(.)", sanitiser)
87end
88
89local uniqueTable = function (tab)
90 local r = {}
91 local l
92 table.sort(tab)
93 for k, v in pairs(tab) do
94 if v ~= l then
95 table.insert(r, v)
96 l = v
97 end
98 end
99 return r
100end
101
102-- Use this to dump a table to a string.
103dumpTable = function (table, space, name)
104 local r = ""
105 if "" == space then r = r .. space .. name .. " =\n" else r = r .. space .. "[" .. name .. "] =\n" end
106 r = r .. space .. "{\n"
107 r = r .. dumpTableSub(table, space .. " ")
108 if "" == space then r = r .. space .. "}\n" else r = r .. space .. "},\n" end
109 return r
110end
111dumpTableSub = function (table, space)
112 local r = ""
113 for k, v in pairs(table) do
114 if type(k) == "string" then k = '"' .. k .. '"' end
115 if type(v) == "table" then
116 r = r .. dumpTable(v, space, k)
117 elseif type(v) == "string" then
118 r = r .. space .. "[" .. k .. "] = '" .. v .. "';\n"
119 elseif type(v) == "function" then
120 r = r .. space .. "[" .. k .. "] = function ();\n"
121 elseif type(v) == "userdata" then
122 r = r .. space .. "userdata " .. "[" .. k .. "];\n"
123 elseif type(v) == "boolean" then
124 if (v) then
125 r = r .. space .. "boolean " .. "[" .. k .. "] = true;\n"
126 else
127 r = r .. space .. "boolean " .. "[" .. k .. "] = false;\n"
128 end
129 else
130 r = r .. space .. "[" .. k .. "] = " .. v .. ";\n"
131 end
132 end
133 return r
134end
135
136
137
138local getXML = function (file)
139 local s = ""
140 local iFile, e = io.open(file, "r")
141 if nil == iFile then print("ERROR opening file " .. file .. " - " .. e); return {} end
142 for l in iFile:lines() do
143 -- Strip white space from each end of the line.
144 l = string.gsub(l, '^%s*(.*)', '%1')
145 l = string.gsub(l, '(.-)%s*$', '%1')
146 if "<?xml " ~= l:sub(1, 6) then s = s .. l end
147 end
148 iFile:close()
149 return lxp.lom.parse(s)
150end
151
152reduceXML = function(tab)
153 local o = {}
154 if nil ~= tab.attr then
155 if 0 == #(tab.attr) then tab.attr = nil end
156 end
157
158 if nil ~= tab.tag then
159 local tag = tab.tag
160 tab.tag = nil
161 if 1 == #tab then
162 if "table" == type(tab[1]) then
163 o[tag] = {}
164 local n = reduceXML(tab[1])
165 for l, w in pairs(n) do
166 local c = 0
167 local n = l
168 while nil ~= o[tag][n] do
169 n = l .. '.' .. c
170 c = c + 1
171 end
172 o[tag][n] = w
173 end
174 else
175 o[tag] = tab[1]
176 end
177 else
178 o[tag] = {}
179 for k, v in pairs(tab) do
180 if "table" == type(v) then
181 local n = reduceXML(v)
182 for l, w in pairs(n) do
183 -- If there are multiple of the same name -> name.0 name.1 ...
184 local c = 0
185 local n = l
186 if (nil ~= o[tag][l]) then
187 n = l .. '.' .. c
188 o[tag][n] = o[tag][l]
189 o[tag][l] = nil
190 end
191 while nil ~= o[tag][n] do
192 c = c + 1
193 n = l .. '.' .. c
194 end
195 o[tag][n] = w
196 end
197 else
198 o[tag][k] = v
199 end
200 end
201 end
202 else
203--print("NO TAG!!")
204--printTable(tab, "", "NO TAG!!")
205--o = tab
206 end
207 return o
208end
209
210--[[ Silly SL allowed duplicate names in inventories, but not in object contents.
211 In contents they automatically get renamed to "name 1", even if they are different UUIDs.
212 The duplicated file__UUID.xml files in the IAR have different UUIDs. (ID field in the .xml)
213 "cp --backup=numbered " results in file.ext.~1~
214]]
215deDup = function (file, ext)
216 if nil == ext then ext = "" else ext = '.' .. ext end
217 local count = 0
218 local st = posix.stat(file .. ext, "type")
219 while nil ~= st do
220 count = count + 1
221 st = posix.stat(file .. ' ' .. count .. ext, "type")
222 end
223 if 0 < count then
224 print("Dupe detected " .. file .. ext)
225 file = file .. ' ' .. count
226 end
227 return file .. ext
228end
229
230writeTable = function (tab, file, title)
231 os.execute('touch "' .. file .. '"')
232 local iFile, e = io.open(file, "w+")
233 if nil == iFile then print("ERROR opening file " .. file .. " - " .. e); return false end
234 iFile:write(dumpTable(tab, "", title))
235 iFile:close()
236 return true
237end
238
239-- TODO - should sanitise all these names. The same way SledjHamr does.
240local stripUUID = "%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x"
241convertObject = function (source, dest, name)
242 -- It's a complex object, parse it's asset file and deal with it.
243 local tb = getXML(source)
244 if 0 == #tb then return end
245 local tbl = reduceXML(tb)
246 tbl.Name = name
247 tbl.AssetID = posix.basename(source):gsub("__" .. stripUUID, ""):gsub(".xml", "")
248 tbl.AssetType = 6
249
250 local ddest = deDup(dest .. "/" .. name)
251 posix.mkdir(ddest)
252 if not writeTable(tbl, deDup(dest .. "/." .. name, "lua"), tbl.AssetID) then return end
253
254 if (nil ~= tbl.SceneObjectGroup) then
255 if (nil ~= tbl.SceneObjectGroup.RootPart)
256 and (nil ~= tbl.SceneObjectGroup.RootPart.SceneObjectPart)
257 and (nil ~= tbl.SceneObjectGroup.RootPart.SceneObjectPart.TaskInventory) then
258 for k, v in pairs(tbl.SceneObjectGroup.RootPart.SceneObjectPart.TaskInventory) do
259 if "TaskInventoryItem" == k:sub(1, 17) then
260 local ext = assetTypes[tonumber(v.Type)]
261 local fl = assetsDir .. "/" .. v.AssetID.UUID .. "_" .. ext
262 -- If it's an object.xml, parse it recursively.
263 if "object.xml" ~= ext then
264 os.execute('cp --backup=numbered "' .. fl .. '" "' .. deDup(ddest .. "/" .. sanitise(v.Name), ext) .. '"')
265 else
266 convertObject(fl, ddest, sanitise(v.Name))
267 end
268 end
269 end
270 end
271
272 if (nil ~= tbl.SceneObjectGroup.OtherParts) then
273 for j, u in pairs(tbl.SceneObjectGroup.OtherParts) do
274 if "Part" == j:sub(1, 4) then
275 if (nil ~= u.SceneObjectPart) and (nil ~= u.SceneObjectPart.TaskInventory) then
276 local udest = ddest .. '/' .. sanitise(u.SceneObjectPart.Name)
277 posix.mkdir(udest)
278 for k, v in pairs(u.SceneObjectPart.TaskInventory) do
279 if "TaskInventoryItem" == k:sub(1, 17) then
280 local ext = assetTypes[tonumber(v.Type)]
281 local fl = assetsDir .. "/" .. v.AssetID.UUID .. "_" .. ext
282 -- If it's an object.xml, parse it recursively.
283 if "object.xml" ~= ext then
284 os.execute('cp --backup=numbered "' .. fl .. '" "' .. deDup(udest .. '/' .. sanitise(v.Name), ext) .. '"')
285 else
286 convertObject(fl, ddest, sanatise(v.Name))
287 end
288 end
289 end
290 end
291 end
292 end
293 end
294 end
295end
296
297convertFiles = function (st, source)
298 local name = posix.basename(source):gsub("__" .. stripUUID, ""):gsub(".xml", "")
299 local dest = "IARs/" .. posix.dirname( source):gsub("__" .. stripUUID, "")
300 local object = {}
301
302 -- Sanitise the directory name.
303 local d = dest
304 dest = ""
305 while "." ~= d do
306 dest = sanitise(posix.basename(d)) .. "/" .. dest
307 d = posix.dirname(d)
308 end
309 dest = dest:sub(1, -2)
310
311 posix.mkdir(dest)
312 if "regular" ~= st then return end
313
314 -- Parse the IAR XML file. which we assume this is.
315 for k, v in pairs(getXML(source)) do
316 if nil ~= v.tag then object[v.tag] = v[1] end
317 end
318
319 -- Double check we got an asset ID.
320 if nil ~= object.AssetID then
321 -- In SledjHamr these .lua table files are called .omg, and have a different structure. Call these ones .liar / .loar? .lOS?
322 local tp = assetTypes[tonumber(object.AssetType)]
323 if nil == tp then tp = "UNKNOWN" end
324 local asset = assetsDir .. "/" .. object.AssetID .. "_" .. tp
325
326 if "object.xml" ~= tp then
327 -- If it's an ordinary asset, create the metadata file and copy the asset file.
328 writeTable(object, deDup(dest .. "/." .. sanitise(name), "lua"), object.AssetID)
329 os.execute('cp --backup=numbered "' .. asset .. '" "' .. deDup(dest .. "/" .. sanitise(name), tp) .. '"')
330 else
331 convertObject(asset, dest, sanitise(object.Name))
332 end
333
334 end
335end
336
337scanDir = function (dir, func)
338 local files, errstr, errno = posix.dir(dir)
339 if files then
340 for a,b in ipairs(files) do
341 if ("." ~= b) and (".." ~= b) then
342 local file = dir .. "/" .. b
343 local st = posix.stat(file, "type")
344 if nil ~= st then
345 if nil ~= func then func(st, file) end
346 if "directory" == st then scanDir(dir .. "/" .. b, func) end
347 else
348 print("NOT" .. " " .. file)
349 end
350 end
351 end
352 else
353 print(errstr)
354 end
355end
356
357
358if 0 ~= #args then
359end
360
361
362local e = tarball:gsub("(.+)%.(%a+)", "%2")
363local archiver = "z"
364local IARname = tarball:gsub("(.+)%.(%a+)", "%1")
365if ("iar" ~= e:lower()) then
366 archiver = "a"
367 -- Assume the file is a tarball with ".tar." as part of the name.
368 IARname = IARname:gsub("(.+)%.(%a+)", "%1")
369end
370assetsDir = IARname .. "/assets"
371os.execute("rm -rf " .. IARname)
372posix.mkdir(IARname)
373os.execute("tar -x" .. archiver .. "f " .. tarball .. " -C " .. IARname)
374
375
376posix.mkdir("IARs")
377os.execute("rm -rf IARs/" .. IARname)
378posix.mkdir("IARs/" .. IARname)
379posix.mkdir("IARs/" .. IARname .."/inventory")
380
381local inv = IARname .. "/inventory"
382local st = posix.stat(inv, "type")
383if nil == st then
384 assetsDir = IARname .. "/" .. assetsDir
385 inv = IARname .. "/" .. inv
386 posix.mkdir("IARs/" .. IARname)
387 posix.mkdir("IARs/" .. IARname .."/" .. IARname)
388 posix.mkdir("IARs/" .. IARname .."/" .. IARname .. "/inventory")
389end
390
391scanDir(inv, convertFiles)
392--os.execute("rm -rf " .. IARname)