170 lines
5.7 KiB
Python
170 lines
5.7 KiB
Python
"""
|
|
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 <http://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
import os
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logger.setLevel(logging.DEBUG)
|
|
|
|
LDD_INSTALL_PATH = r'C:\Program Files (x86)\LEGO Company\LEGO Digital Designer'
|
|
LDD_ASSET_PATH = os.path.join(LDD_INSTALL_PATH, 'Assets.lif')
|
|
|
|
fileData = ''
|
|
|
|
|
|
def asset_extract(design_ids=None, material_only=False, debug=True):
|
|
global fileData
|
|
|
|
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
|
|
print('xxx', entryType, offset)
|
|
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":
|
|
print('---', offset, fileData[offset + 1])
|
|
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
|
|
if debug:
|
|
logger.debug('PROCESSING: %s' % LDD_ASSET_PATH)
|
|
# Open the file if valid.
|
|
try:
|
|
with open(LDD_ASSET_PATH, "rb") as f:
|
|
fileData = f.read()
|
|
|
|
except IOError as e:
|
|
logger.error('ERROR: Failed to read file.')
|
|
return
|
|
|
|
if len(fileData) < 4 or fileData[0:4] != b"LIFF":
|
|
logger.error('ERROR: Not a LIF file.')
|
|
return
|
|
|
|
if debug:
|
|
logger.debug('EXTRACTING: Asset.lif: 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))
|
|
|
|
for i in fileList:
|
|
if 'db.lif' in i[0]:
|
|
fileData = fileData[i[1]:i[1] + i[2]]
|
|
break
|
|
if debug:
|
|
logger.debug('PROCESSING: db.lif')
|
|
if len(fileData) < 4 or fileData[0:4] != b"LIFF":
|
|
logger.error("\tERROR: Not a LIF file.")
|
|
return
|
|
if debug:
|
|
logger.debug('EXTRACTING db.lif: 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))
|
|
|
|
if material_only:
|
|
for i in fileList:
|
|
material_file = r'\Materials.xml'
|
|
if i[0] in [material_file]:
|
|
m_data = fileData[i[1]:i[1] + i[2]]
|
|
return m_data
|
|
return None
|
|
|
|
if design_ids:
|
|
g_data_map = {}
|
|
g_file_list = [r'\Primitives\LOD0\%s.g' % d for d in design_ids]
|
|
for i in fileList:
|
|
if i[0] not in g_file_list:
|
|
continue
|
|
g_data = fileData[i[1]:i[1] + i[2]]
|
|
design_id = design_ids[g_file_list.index(i[0])]
|
|
g_data_map[design_id] = g_data
|
|
|
|
return g_data_map
|
|
else:
|
|
design_id_list = []
|
|
for i in fileList:
|
|
if r'\Primitives\LOD0' not in i[0]:
|
|
continue
|
|
design_id = os.path.splitext(os.path.basename(i[0]))[0]
|
|
design_id_list.append(design_id)
|
|
|
|
return design_id_list
|
|
if __name__ == '__main__':
|
|
asset_extract('99818', material_only=True)
|