2015-08-09 11:05:21 +00:00
import configparser
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
2015-09-01 18:50:21 +00:00
from pyraknet . bitstream import BitStream , c_float , c_int64 , c_ubyte , c_uint , c_uint64 , c_ushort
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 :
stream = BitStream ( file . read ( ) )
version = stream . read ( c_uint )
assert version in ( 40 , 41 ) , version
unknown1 = stream . read ( c_uint )
world_id = stream . read ( c_uint )
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 ) )
### scenes
scenes = self . tree . insert ( zone , END , text = " Scenes " )
for _ in range ( stream . read ( c_uint ) ) :
filename = stream . read ( str , char_size = 1 , length_type = c_ubyte )
scene_id = stream . read ( c_uint )
is_audio = stream . read ( c_uint )
scene_name = stream . read ( str , char_size = 1 , length_type = c_ubyte )
if is_audio :
assert scene_name == " Audio "
scene = self . tree . insert ( scenes , END , text = " Scene " , values = ( filename , scene_id , is_audio , scene_name ) )
assert stream . read ( bytes , length = 3 ) == b " \xff \xff \xff "
with open ( os . path . join ( os . path . dirname ( luz_path ) , filename ) , " rb " ) as lvl :
print ( " Loading lvl " , filename )
self . parse_lvl ( BitStream ( lvl . read ( ) ) , scene )
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 ) )
### unknown
unknowns = self . tree . insert ( zone , END , text = " Unknowns " )
for _ in range ( stream . read ( c_uint ) ) :
for _ in range ( 2 ) :
unknown1 = stream . read ( c_uint64 )
unknown2 = stream . read ( c_float ) , stream . read ( c_float ) , stream . read ( c_float )
self . tree . insert ( unknowns , END , text = " Unknown " , values = ( unknown1 , unknown2 ) )
remaining_length = stream . read ( c_uint )
assert len ( stream ) - stream . _read_offset / / 8 == remaining_length
assert stream . read ( c_uint ) == 1
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 )
2015-10-12 17:33:34 +00:00
config_data = config_data . replace ( " { " , " <crlbrktopen> " ) . replace ( " } " , " <crlbrktclose> " ) . replace ( " \\ " , " <backslash> " ) # for some reason these characters aren't properly escaped when sent to Tk
2015-08-09 11:05:21 +00:00
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
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 " :
cols = " version " , " unknown1 " , " world_id " , " spawnpoint_pos " , " spawnpoint_rot "
elif item_type == " Scene " :
cols = " filename " , " scene_id " , " is_audio " , " scene_name "
elif item_type == " Terrain " :
cols = " filename " , " name " , " description "
elif item_type == " Unknown " :
cols = " unknown1 " , " unknown2 "
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 ( )