"""
LIF Extractor - LEGO Digital Designer LIF extractor.
Copyright (C) 2012 JrMasterModelBuilder
You accept full responsibility for how you use this program.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
import os
import sys
def extract(path):
def uint32(offset):
if(bytesAre == "str"):
return (ord(fileData[offset]) * 16777216) + (ord(fileData[offset+1]) * 65536) + (ord(fileData[offset+2]) * 256) + ord(fileData[offset+3])
else:
return (fileData[offset] * 16777216) + (fileData[offset+1] * 65536) + (fileData[offset+2] * 256) + fileData[offset+3]
def uint16(offset):
if(bytesAre == "str"):
return (ord(fileData[offset]) * 256) + ord(fileData[offset+1])
else:
return (fileData[offset] * 256) + fileData[offset+1]
def recurse(prefix, offset):
if(prefix == ""):
offset += 36
else:
folderList.extend([prefix])
offset += 4
for i in range(uint32(offset)):
offset += 4
entryType = uint16(offset)#1 = directory, 2 = file
offset += 6
#Build the name.
entryName = os.sep
#Check both integer and byte for different versions of Python.
while(fileData[offset+1] != 0 and fileData[offset+1] != b"\x00"):
if(bytesAre == "str"):
entryName += fileData[offset+1]
else:
entryName += chr(fileData[offset+1])
offset += 2
offset += 6
if(entryType == 1):
#Recurse through the director, and update the offset.
packedFilesOffset[0] += 20
offset = recurse(prefix + entryName, offset)
elif(entryType == 2):
#File offset.
packedFilesOffset[0] += 20
fileOffset = packedFilesOffset[0]
#File size.
fileSize = uint32(offset) - 20
offset += 24
packedFilesOffset[0] += fileSize
fileList.extend([[prefix + entryName, fileOffset, fileSize]])
#Return the offset at the end of this run to update parent runnings.
return offset
print("PROCESSING: " + path)
#Open the file if valid.
try:
with open(path, "rb") as f:
fileData = f.read()
except IOError as e:
print("\tERROR: Failed to read file.")
return False
if(len(fileData) < 4 or fileData[0:4] != b"LIFF"):
print("\tERROR: Not a LIF file.")
return False
print("\tEXTRACTING: Please wait.")
#Recurse through, creating a list of all the files.
packedFilesOffset = [84]#Array for non-global function-modifiable variable.
folderList = []
fileList = []
#Check if this version of Python treats bytes as int or str
bytesAre = type(b'a'[0]).__name__
#Recuse through the archive.
recurse("", (uint32(72)+64))
#Create output path from input path.
if(path[-4:].lower() == ".lif"):
outFolder = path[:-4]
else:
outFolder = path
#Create the first non-existant folder to extract to.
if os.path.exists(outFolder):
i = 1
while(os.path.exists(outFolder + "_" + str(i))):
i += 1
outFolder = outFolder + "_" + str(i)
#Make the output folder.
os.makedirs(outFolder)
#Create all the folders we need to extract to.
for a in folderList:
if(a[0] == ""):
continue
if not os.path.exists(outFolder + a):
os.makedirs(outFolder + a)
#Loop through the list of files, saving them.
for a in fileList:
f = open((outFolder + a[0]), "wb")
f.write(fileData[a[1]:a[1]+a[2]])
f.close()
print("\tCOMPLETE: " + str(len(fileList)) + " files in " + str(len(folderList)) + " folders extracted.")
return True
#Detect if executable or not.
fileName = sys.argv[0].split(os.sep).pop()
if(fileName[-3:] == ".py" or fileName[-4:] == ".pyw"):
runCommand = "python " + fileName
else:
runCommand = fileName
if(len(sys.argv) > 1):
for i in range(1, len(sys.argv)):
extract(sys.argv[i])
else:
print("LIF Extractor 1.0\n\nThis program will extract LIF archives to an adjacent folder.\n\nCOPYRIGHT:\n\t(C) 2012 JrMasterModelBuilder\n\nLICENSE:\n\tGNU GPLv3\n\tYou accept full responsibility for how you use this program.\n\nUSEAGE:\n\t" + runCommand + " ")