utils/luzviewer.pyw
2016-06-29 11:47:58 +02:00

272 lines
11 KiB
Python

import configparser
import enum
import os.path
import sqlite3
import sys
import tkinter.filedialog as filedialog
import tkinter.messagebox as messagebox
from tkinter import END, Menu
import viewer
from pyraknet.bitstream import BitStream, c_float, c_int, c_int64, c_ubyte, c_uint, c_uint64, c_ushort
class PathType(enum.IntEnum):
Movement = 0
MovingPlatform = 1
Property = 2
Camera = 3
Spawner = 4
Showcase = 5
Race = 6
Rail = 7
class LUZViewer(viewer.Viewer):
def __init__(self):
super().__init__()
config = configparser.ConfigParser()
config.read("luzviewer.ini")
try:
self.db = sqlite3.connect(config["paths"]["db_path"])
except:
messagebox.showerror("Can not open database", "Make sure db_path in the INI is set correctly.")
sys.exit()
self.create_widgets()
def create_widgets(self):
super().create_widgets()
menubar = Menu()
menubar.add_command(label="Open", command=self.askopenfile)
self.master.config(menu=menubar)
def askopenfile(self):
path = filedialog.askopenfilename(filetypes=[("LEGO Universe Zone", "*.luz")])
if path:
self.load_luz(path)
def load_luz(self, luz_path):
self.tree.set_children("")
print("Loading", luz_path)
with open(luz_path, "rb") as file:
stream = BitStream(file.read())
version = stream.read(c_uint)
assert version in (36, 38, 39, 40, 41), version
unknown1 = stream.read(c_uint)
world_id = stream.read(c_uint)
if version >= 38:
spawnpoint_pos = stream.read(c_float), stream.read(c_float), stream.read(c_float)
spawnpoint_rot = stream.read(c_float), stream.read(c_float), stream.read(c_float), stream.read(c_float)
zone = self.tree.insert("", END, text="Zone", values=(version, unknown1, world_id, spawnpoint_pos, spawnpoint_rot))
else:
zone = self.tree.insert("", END, text="Zone", values=(version, unknown1, world_id))
### scenes
scenes = self.tree.insert(zone, END, text="Scenes")
if version >= 37:
number_of_scenes = stream.read(c_uint)
else:
number_of_scenes = stream.read(c_ubyte)
for _ in range(number_of_scenes):
filename = stream.read(str, char_size=1, length_type=c_ubyte)
scene_id = stream.read(c_uint64)
scene_name = stream.read(str, char_size=1, length_type=c_ubyte)
scene = self.tree.insert(scenes, END, text="Scene", values=(filename, scene_id, scene_name))
assert stream.read(bytes, length=3)
with open(os.path.join(os.path.dirname(luz_path), filename), "rb") as lvl:
print("Loading lvl", filename)
try:
self.parse_lvl(BitStream(lvl.read()), scene)
except Exception:
import traceback
traceback.print_exc()
assert stream.read(c_ubyte) == 0
### terrain
filename = stream.read(str, char_size=1, length_type=c_ubyte)
name = stream.read(str, char_size=1, length_type=c_ubyte)
description = stream.read(str, char_size=1, length_type=c_ubyte)
self.tree.insert(zone, END, text="Terrain", values=(filename, name, description))
### scene transitions
scene_transitions = self.tree.insert(zone, END, text="Scene Transitions")
for _ in range(stream.read(c_uint)):
scene_transition_values = ()
if version < 40:
scene_transition_values += stream.read(str, char_size=1, length_type=c_ubyte),
scene_transition_values += stream.read(c_float),
scene_transition = self.tree.insert(scene_transitions, END, text="Scene Transition", values=scene_transition_values)
if version < 39:
transition_point_count = 5
else:
transition_point_count = 2
for _ in range(transition_point_count):
transition_point_scene_id = stream.read(c_uint64),
transition_point_position = stream.read(c_float), stream.read(c_float), stream.read(c_float)
self.tree.insert(scene_transition, END, text="Transition Point", values=(transition_point_scene_id, transition_point_position))
remaining_length = stream.read(c_uint)
assert len(stream) - stream._read_offset//8 == remaining_length
assert stream.read(c_uint) == 1
### paths
paths = self.tree.insert(zone, END, text="Paths")
for _ in range(stream.read(c_uint)):
path_version = stream.read(c_uint)
name = stream.read(str, length_type=c_ubyte)
path_type = stream.read(c_uint)
unknown1 = stream.read(c_uint)
unknown2 = stream.read(c_uint)
values = path_version, name, unknown1, unknown2
if path_type == PathType.MovingPlatform:
if path_version >= 18:
unknown3 = stream.read(c_ubyte)
values += unknown3,
elif path_version >= 13:
unknown_str = stream.read(str, length_type=c_ubyte)
values += unknown_str,
elif path_type == PathType.Property:
unknown3 = stream.read(c_int), stream.read(c_int), stream.read(c_int), stream.read(c_uint64)
unknown_str1 = stream.read(str, length_type=c_ubyte)
unknown_str2 = stream.read(str, length_type=c_uint)
unknown4 = stream.read(c_int), stream.read(c_int), stream.read(c_float)
unknown5 = stream.read(c_int), stream.read(c_int)
unknown6 = stream.read(c_float), stream.read(c_float), stream.read(c_float), stream.read(c_float)
values += unknown3, unknown_str1, unknown_str2, unknown4, unknown5, unknown6
elif path_type == PathType.Camera:
unknown_str = stream.read(str, length_type=c_ubyte)
values += unknown_str,
if path_version >= 14:
unknown3 = stream.read(c_ubyte)
values += unknown3,
elif path_type == PathType.Spawner:
spawn_lot = stream.read(c_uint)
unknown3 = stream.read(c_uint), stream.read(c_int), stream.read(c_uint)
object_id = stream.read(c_int64)
unknown4 = stream.read(c_ubyte)
values += spawn_lot, unknown3, object_id, unknown4
path = self.tree.insert(paths, END, text=PathType(path_type).name, values=values)
for _ in range(stream.read(c_uint)):
position = stream.read(c_float), stream.read(c_float), stream.read(c_float)
waypoint_values = position,
if path_type == PathType.MovingPlatform:
rotation = stream.read(c_float), stream.read(c_float), stream.read(c_float), stream.read(c_float)
waypoint_unknown2 = stream.read(c_ubyte)
waypoint_unknown3 = stream.read(c_float), stream.read(c_float)
waypoint_values += rotation, waypoint_unknown2, waypoint_unknown3
if path_version >= 13:
waypoint_audio_guid_1 = stream.read(str, length_type=c_ubyte)
waypoint_audio_guid_2 = stream.read(str, length_type=c_ubyte)
waypoint_values += waypoint_audio_guid_1, waypoint_audio_guid_2
elif path_type == PathType.Camera:
waypoint_unknown1 = (
stream.read(c_float), stream.read(c_float), stream.read(c_float),
stream.read(c_float), stream.read(c_float), stream.read(c_float),
stream.read(c_float), stream.read(c_float), stream.read(c_float))
waypoint_values += waypoint_unknown1,
elif path_type == PathType.Spawner:
rotation = stream.read(c_float), stream.read(c_float), stream.read(c_float), stream.read(c_float)
waypoint_values += rotation,
elif path_type == PathType.Race:
waypoint_unknown1 = stream.read(c_float), stream.read(c_float), stream.read(c_float), stream.read(c_float)
waypoint_unknown2 = stream.read(c_ubyte), stream.read(c_ubyte)
waypoint_unknown3 = stream.read(c_float), stream.read(c_float), stream.read(c_float)
waypoint_values += waypoint_unknown1, waypoint_unknown2, waypoint_unknown3
elif path_type == PathType.Rail:
waypoint_unknown1 = stream.read(c_float), stream.read(c_float), stream.read(c_float), stream.read(c_float)
waypoint_values += waypoint_unknown1,
if path_version >= 17:
waypoint_unknown2 = stream.read(c_float)
waypoint_values += waypoint_unknown2,
waypoint = self.tree.insert(path, END, text="Waypoint", values=waypoint_values)
if path_type in (PathType.Movement, PathType.Spawner, PathType.Rail):
for _ in range(stream.read(c_uint)):
config_name = stream.read(str, length_type=c_ubyte)
config_type_and_value = stream.read(str, length_type=c_ubyte)
self.tree.insert(waypoint, END, text="Config", values=(config_name, config_type_and_value))
def parse_lvl(self, stream, scene):
while not stream.all_read():
assert stream._read_offset//8 % 16 == 0 # seems everything is aligned like this?
start_pos = stream._read_offset//8
assert stream.read(bytes, length=4) == b"CHNK"
chunktype = stream.read(c_uint)
assert stream.read(c_ushort) == 1
assert stream.read(c_ushort) in (1, 2)
chunk_length = stream.read(c_uint)
data_pos = stream.read(c_uint)
stream._read_offset = data_pos * 8
assert stream._read_offset//8 % 16 == 0
if chunktype == 1000:
pass
elif chunktype == 2000:
pass
elif chunktype == 2001:
for _ in range(stream.read(c_uint)):
object_id = stream.read(c_int64) # seems like the object id, but without some bits
lot = stream.read(c_uint)
unknown1 = stream.read(c_uint)
unknown2 = stream.read(c_uint)
position = stream.read(c_float), stream.read(c_float), stream.read(c_float)
rotation = stream.read(c_float), stream.read(c_float), stream.read(c_float), stream.read(c_float)
scale = stream.read(c_float)
config_data = stream.read(str, length_type=c_uint)
config_data = config_data.replace("{", "<crlbrktopen>").replace("}", "<crlbrktclose>").replace("\\", "<backslash>") # for some reason these characters aren't properly escaped when sent to Tk
assert stream.read(c_uint) == 0
lot_name = ""
if lot == 176:
lot_name = "Spawner - "
lot = config_data[config_data.index("spawntemplate")+16:config_data.index("\n", config_data.index("spawntemplate")+16)]
try:
lot_name += self.db.execute("select name from Objects where id == "+str(lot)).fetchone()[0]
except TypeError:
print("Name for lot", lot, "not found")
lot_name += " - "+str(lot)
self.tree.insert(scene, END, text="Object", values=(object_id, lot_name, unknown1, unknown2, position, rotation, scale, config_data))
elif chunktype == 2002:
pass
stream._read_offset = (start_pos + chunk_length) * 8 # go to the next CHNK
def on_item_select(self, _):
item = self.tree.selection()[0]
item_type = self.tree.item(item, "text")
if item_type == "Zone":
cols = "Version", "unknown1", "World ID", "Spawnpoint Pos", "Spawnpoint Rot"
elif item_type == "Scene":
cols = "Filename", "Scene ID", "Scene Name"
elif item_type == "Terrain":
cols = "Filename", "Name", "Description"
elif item_type == "Transition Point":
cols = "Scene ID", "Position"
elif item_type == "Object":
cols = "Object ID", "LOT", "unknown1", "unknown2", "Position", "Rotation", "Scale"
else:
cols = ()
if cols:
self.tree.configure(columns=cols)
for col in cols:
self.tree.heading(col, text=col, command=(lambda col: lambda: self.sort_column(col, False))(col))
self.item_inspector.delete(1.0, END)
self.item_inspector.insert(END, "\n".join(self.tree.item(item, "values")))
if __name__ == "__main__":
app = LUZViewer()
app.mainloop()