2015-08-09 11:05:21 +00:00
import configparser
2016-06-21 19:24:11 +00:00
import enum
2015-08-09 11:05:21 +00:00
import os . path
import sqlite3
2015-11-19 20:25:16 +00:00
import sys
2015-08-09 11:05:21 +00:00
import tkinter . filedialog as filedialog
2015-11-19 20:25:16 +00:00
import tkinter . messagebox as messagebox
2015-08-09 11:05:21 +00:00
from tkinter import END , Menu
import viewer
2017-12-25 12:43:33 +00:00
from pyraknet . bitstream import c_bool , c_float , c_int , c_int64 , c_ubyte , c_uint , c_uint64 , c_ushort , ReadStream
2016-06-21 19:24:11 +00:00
class PathType ( enum . IntEnum ) :
2017-12-25 12:25:53 +00:00
Movement = 0
2016-06-21 19:24:11 +00:00
MovingPlatform = 1
Property = 2
Camera = 3
Spawner = 4
Showcase = 5
Race = 6
Rail = 7
2015-08-09 11:05:21 +00:00
2016-10-22 12:02:51 +00:00
class PathBehavior ( enum . IntEnum ) :
Loop = 0
Bounce = 1
Once = 2
2015-08-09 11:05:21 +00:00
class LUZViewer ( viewer . Viewer ) :
def __init__ ( self ) :
super ( ) . __init__ ( )
config = configparser . ConfigParser ( )
config . read ( " luzviewer.ini " )
2015-11-19 20:25:16 +00:00
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 ( )
2015-08-09 11:05:21 +00:00
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 :
2018-01-12 22:41:25 +00:00
data = file . read ( )
luz_len = len ( data )
stream = ReadStream ( data , unlocked = True )
2015-08-09 11:05:21 +00:00
version = stream . read ( c_uint )
2016-06-21 19:24:11 +00:00
assert version in ( 36 , 38 , 39 , 40 , 41 ) , version
2015-08-09 11:05:21 +00:00
unknown1 = stream . read ( c_uint )
world_id = stream . read ( c_uint )
2016-06-21 19:24:11 +00:00
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 ) )
2015-08-09 11:05:21 +00:00
### scenes
scenes = self . tree . insert ( zone , END , text = " Scenes " )
2016-06-21 19:24:11 +00:00
if version > = 37 :
number_of_scenes = stream . read ( c_uint )
else :
number_of_scenes = stream . read ( c_ubyte )
for _ in range ( number_of_scenes ) :
2017-06-10 10:29:37 +00:00
filename = stream . read ( bytes , length_type = c_ubyte ) . decode ( " latin1 " )
2016-06-21 19:24:11 +00:00
scene_id = stream . read ( c_uint64 )
2017-06-10 10:29:37 +00:00
scene_name = stream . read ( bytes , length_type = c_ubyte ) . decode ( " latin1 " )
2016-06-21 19:24:11 +00:00
scene = self . tree . insert ( scenes , END , text = " Scene " , values = ( filename , scene_id , scene_name ) )
assert stream . read ( bytes , length = 3 )
2016-11-26 13:10:59 +00:00
lvl_path = os . path . join ( os . path . dirname ( luz_path ) , filename )
if os . path . exists ( lvl_path ) :
with open ( lvl_path , " rb " ) as lvl :
print ( " Loading lvl " , filename )
try :
2018-01-12 22:41:25 +00:00
self . parse_lvl ( ReadStream ( lvl . read ( ) , unlocked = True ) , scene )
2016-11-26 13:10:59 +00:00
except Exception :
import traceback
traceback . print_exc ( )
2015-08-09 11:05:21 +00:00
assert stream . read ( c_ubyte ) == 0
2016-06-21 19:24:11 +00:00
2015-08-09 11:05:21 +00:00
### terrain
2017-06-10 10:29:37 +00:00
filename = stream . read ( bytes , length_type = c_ubyte ) . decode ( " latin1 " )
name = stream . read ( bytes , length_type = c_ubyte ) . decode ( " latin1 " )
description = stream . read ( bytes , length_type = c_ubyte ) . decode ( " latin1 " )
2015-08-09 11:05:21 +00:00
self . tree . insert ( zone , END , text = " Terrain " , values = ( filename , name , description ) )
2016-06-21 19:24:11 +00:00
2016-06-29 09:47:58 +00:00
### scene transitions
scene_transitions = self . tree . insert ( zone , END , text = " Scene Transitions " )
2015-08-09 11:05:21 +00:00
for _ in range ( stream . read ( c_uint ) ) :
2016-06-29 09:47:58 +00:00
scene_transition_values = ( )
if version < 40 :
2017-06-10 10:29:37 +00:00
scene_transition_values + = stream . read ( bytes , length_type = c_ubyte ) ,
2016-06-29 09:47:58 +00:00
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 ) )
2016-06-21 19:24:11 +00:00
2015-08-09 11:05:21 +00:00
remaining_length = stream . read ( c_uint )
2018-01-12 22:41:25 +00:00
assert luz_len - stream . read_offset / / 8 == remaining_length
2015-08-09 11:05:21 +00:00
assert stream . read ( c_uint ) == 1
2016-06-21 19:24:11 +00:00
### 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 )
2016-10-22 12:02:51 +00:00
behavior = PathBehavior ( stream . read ( c_uint ) )
values = path_version , name , unknown1 , behavior
2016-06-21 19:24:11 +00:00
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 :
2017-12-25 12:25:53 +00:00
unknown3 = stream . read ( c_int )
price = stream . read ( c_int )
rental_time = stream . read ( c_int )
associated_zone = stream . read ( c_uint64 )
display_name = stream . read ( str , length_type = c_ubyte )
display_desc = stream . read ( str , length_type = c_uint )
unknown4 = stream . read ( c_int ) ,
clone_limit = stream . read ( c_int )
reputation_multiplier = stream . read ( c_float )
time_unit = stream . read ( c_int ) ,
achievement_required = stream . read ( c_int )
player_zone_coords = stream . read ( c_float ) , stream . read ( c_float ) , stream . read ( c_float )
max_build_height = stream . read ( c_float )
values + = unknown3 , price , rental_time , associated_zone , display_name , display_desc , unknown4 , clone_limit , reputation_multiplier , time_unit , achievement_required , player_zone_coords , max_build_height
2016-06-21 19:24:11 +00:00
elif path_type == PathType . Camera :
2017-12-25 12:25:53 +00:00
next_path = stream . read ( str , length_type = c_ubyte )
values + = next_path ,
2016-06-21 19:24:11 +00:00
if path_version > = 14 :
unknown3 = stream . read ( c_ubyte )
values + = unknown3 ,
elif path_type == PathType . Spawner :
spawn_lot = stream . read ( c_uint )
2017-04-07 20:21:49 +00:00
lot_name = str ( spawn_lot )
try :
lot_name + = " - " + self . db . execute ( " select name from Objects where id == " + str ( spawn_lot ) ) . fetchone ( ) [ 0 ]
except TypeError :
print ( " Name for lot " , spawn_lot , " not found " )
2017-12-25 12:25:53 +00:00
respawn_time = stream . read ( c_uint )
max_to_spawn = stream . read ( c_int )
num_to_maintain = stream . read ( c_uint )
2016-06-21 19:24:11 +00:00
object_id = stream . read ( c_int64 )
2017-12-25 12:25:53 +00:00
activate_on_load = stream . read ( c_bool )
values + = lot_name , respawn_time , max_to_spawn , num_to_maintain , object_id , activate_on_load
2016-06-21 19:24:11 +00:00
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 )
2017-12-25 12:25:53 +00:00
speed = stream . read ( c_float )
wait = stream . read ( c_float )
waypoint_values + = rotation , waypoint_unknown2 , speed , wait
2016-06-21 19:24:11 +00:00
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 :
2017-12-25 12:25:53 +00:00
waypoint_unknown1 = stream . read ( c_float ) , stream . read ( c_float ) , stream . read ( c_float ) , stream . read ( c_float )
time = stream . read ( c_float )
waypoint_unknown2 = stream . read ( c_float )
tension = stream . read ( c_float )
continuity = stream . read ( c_float )
bias = stream . read ( c_float )
waypoint_values + = waypoint_unknown1 , time , waypoint_unknown2 , tension , continuity , bias
2016-06-21 19:24:11 +00:00
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 ) )
2015-08-09 11:05:21 +00:00
def parse_lvl ( self , stream , scene ) :
2018-01-12 22:41:25 +00:00
header = stream . read ( bytes , length = 4 )
stream . read_offset = 0
if header == b " CHNK " :
2016-11-26 13:10:59 +00:00
# newer lvl file structure
# chunk based
while not stream . all_read ( ) :
2017-12-25 12:43:33 +00:00
assert stream . read_offset / / 8 % 16 == 0 # seems everything is aligned like this?
start_pos = stream . read_offset / / 8
2016-11-26 13:10:59 +00:00
assert stream . read ( bytes , length = 4 ) == b " CHNK "
chunk_type = 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 )
2017-12-25 12:43:33 +00:00
stream . read_offset = data_pos * 8
assert stream . read_offset / / 8 % 16 == 0
2016-11-26 13:10:59 +00:00
if chunk_type == 1000 :
pass
elif chunk_type == 2000 :
pass
elif chunk_type == 2001 :
self . lvl_parse_chunk_type_2001 ( stream , scene )
elif chunk_type == 2002 :
pass
2017-12-25 12:43:33 +00:00
stream . read_offset = ( start_pos + chunk_length ) * 8 # go to the next CHNK
2016-11-26 13:10:59 +00:00
else :
2017-09-17 09:34:52 +00:00
self . parse_old_lvl_header ( stream )
2016-11-26 13:10:59 +00:00
self . lvl_parse_chunk_type_2001 ( stream , scene )
2017-09-17 09:34:52 +00:00
def parse_old_lvl_header ( self , stream ) :
version = stream . read ( c_ushort )
assert stream . read ( c_ushort ) == version
stream . read ( c_ubyte )
stream . read ( c_uint )
if version > = 45 :
stream . read ( c_float )
for _ in range ( 4 * 3 ) :
stream . read ( c_float )
if version > = 31 :
if version > = 39 :
for _ in range ( 12 ) :
stream . read ( c_float )
if version > = 40 :
for _ in range ( stream . read ( c_uint ) ) :
stream . read ( c_uint )
stream . read ( c_float )
stream . read ( c_float )
else :
stream . read ( c_float )
stream . read ( c_float )
for _ in range ( 3 ) :
stream . read ( c_float )
if version > = 36 :
for _ in range ( 3 ) :
stream . read ( c_float )
if version < 42 :
for _ in range ( 3 ) :
stream . read ( c_float )
if version > = 33 :
for _ in range ( 4 ) :
stream . read ( c_float )
stream . read ( bytes , length_type = c_uint )
for _ in range ( 5 ) :
stream . read ( bytes , length_type = c_uint )
stream . skip_read ( 4 )
for _ in range ( stream . read ( c_uint ) ) :
stream . read ( c_float ) , stream . read ( c_float ) , stream . read ( c_float )
2016-11-26 13:10:59 +00:00
def lvl_parse_chunk_type_2001 ( self , stream , scene ) :
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 ) )
2015-08-09 11:05:21 +00:00
2015-10-12 17:33:34 +00:00
def on_item_select ( self , _ ) :
2015-08-09 11:05:21 +00:00
item = self . tree . selection ( ) [ 0 ]
item_type = self . tree . item ( item , " text " )
if item_type == " Zone " :
2016-06-29 09:47:58 +00:00
cols = " Version " , " unknown1 " , " World ID " , " Spawnpoint Pos " , " Spawnpoint Rot "
2015-08-09 11:05:21 +00:00
elif item_type == " Scene " :
2016-06-29 09:47:58 +00:00
cols = " Filename " , " Scene ID " , " Scene Name "
2015-08-09 11:05:21 +00:00
elif item_type == " Terrain " :
2016-06-29 09:47:58 +00:00
cols = " Filename " , " Name " , " Description "
elif item_type == " Transition Point " :
cols = " Scene ID " , " Position "
2015-08-09 11:05:21 +00:00
elif item_type == " Object " :
2016-06-29 09:47:58 +00:00
cols = " Object ID " , " LOT " , " unknown1 " , " unknown2 " , " Position " , " Rotation " , " Scale "
2017-12-25 12:25:53 +00:00
elif item_type == " Spawner " :
cols = " Path Version " , " Name " , " unknown1 " , " Behavior " , " Spawned LOT " , " Respawn Time " , " Max to Spawn " , " Num to maintain " , " Object ID " , " Activate on load "
2015-08-09 11:05:21 +00:00
else :
cols = ( )
if cols :
self . tree . configure ( columns = cols )
2017-12-25 12:25:53 +00:00
colwidth = self . tree . winfo_width ( ) / / ( len ( cols ) + 1 )
self . tree . column ( " #0 " , width = colwidth )
for i , col in enumerate ( cols ) :
2015-08-09 11:05:21 +00:00
self . tree . heading ( col , text = col , command = ( lambda col : lambda : self . sort_column ( col , False ) ) ( col ) )
2017-12-25 12:25:53 +00:00
self . tree . column ( i , width = colwidth )
2015-08-09 11:05:21 +00:00
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 ( )