Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- if __name__ != "__main__": exit(0) #lol
- _DEBUG = True
- #from os import listdir, system as cmd
- #from os.path import join
- from time import time
- from sys import argv
- from struct import pack as struct_pack
- from os import system as cmd
- import _parse_scene as ps
- '''public stuff from ps:
- ps.obj_data_types = {
- "u8", "s8", "u16", "s16", "u24", "s24",
- "u32", "s32", "u64", "s64", "f32", "f64",
- "rgb", "argb"
- }
- ps.printError(s, fatal=True)
- ps.sceneIdToPath(scene_id)
- #returns None if int() conversion fails
- ps.tryInt(string)
- #returns None if key is not in dict[ionary]
- ps.tryDict(dict, key)
- #dataValue should always be of type str
- #type should be one of: int, bool, float, color, object, file
- ps.convertType(dataType, dataValue)
- ps.printScene(scene, printLayers=_PRINT_LAYERS)
- ps.parseSceneMap(scene_id, announce=False)
- '''
- # 8 16 24 32 64
- unsigned_max = (-1, 0xff, 0xffff, 0xffffff, 0xffffffff, -1, -1, -1, 0xffffffffffffffff)
- signed_min = (-1,-0x80,-0x8000,-0x800000,-0x80000000, -1, -1, -1,-0x8000000000000000)
- signed_max = (-1, 0x7f, 0x7fff, 0x7fffff, 0x7fffffff, -1, -1, -1, 0x7fffffffffffffff)
- def to_bytes_integer(_v, byteCount, isSigned):
- v = ps.tryInt(_v)
- if v == None: ps.printError(f'cannot convert {_v} ({hex(v)}) to an integer')
- if isSigned:
- if v < signed_min[byteCount] or v > signed_max[byteCount]:
- ps.printError(f'cannot convert {v} ({hex(v)}) to s{byteCount*8}')
- if v < 0:
- v += unsigned_max[byteCount]+1
- else:
- if v < 0 or v > unsigned_max[byteCount]:
- ps.printError(f'cannot convert {v} ({hex(v)}) to u{byteCount*8}')
- return v.to_bytes(byteCount, "little")
- def to_bytes_float(_v, doublePrecision):
- v = float(_v)
- if doublePrecision: return struct_pack("d", v)
- else : return struct_pack("f", v)
- #assumes (r,g,b,a), converts to [a]rgb in bytes
- def to_bytes_color(_v, useAlpha):
- if type(_v) != list and type(_v != tuple):
- ps.printError("color must be of type \"list\" or \"tuple\"")
- if len(_v) < 3:
- ps.printError("color has less than 3 channels")
- red = to_bytes_integer(_v[0], 1, False)
- blue = to_bytes_integer(_v[1], 1, False)
- green = to_bytes_integer(_v[2], 1, False)
- if useAlpha:
- if len(_v) < 4:
- ps.printError("color has less than 4 channels, despite using alpha")
- alpha = to_bytes_integer(_v[3], 1, False)
- return alpha + red + green + blue
- else:
- return red + green + blue
- def to_bytes_u8(v): return to_bytes_integer(v, 1, False)
- def to_bytes_s8(v): return to_bytes_integer(v, 1, True )
- def to_bytes_u16(v): return to_bytes_integer(v, 2, False)
- def to_bytes_s16(v): return to_bytes_integer(v, 2, True )
- def to_bytes_u24(v): return to_bytes_integer(v, 3, False)
- def to_bytes_s24(v): return to_bytes_integer(v, 3, True )
- def to_bytes_u32(v): return to_bytes_integer(v, 4, False)
- def to_bytes_s32(v): return to_bytes_integer(v, 4, True )
- def to_bytes_u64(v): return to_bytes_integer(v, 8, False)
- def to_bytes_s64(v): return to_bytes_integer(v, 8, True )
- def to_bytes_f32(v): return to_bytes_float(v, False)
- def to_bytes_f64(v): return to_bytes_float(v, True )
- def to_bytes_rgb (v): return to_bytes_color(v, False)
- def to_bytes_argb(v): return to_bytes_color(v, True )
- to_bytes = { #for example, "to_bytes["u16"](55)" will output "b'\x37\x00'"
- "u8" : to_bytes_u8, "s8" : to_bytes_s8,
- "u16" : to_bytes_u16, "s16" : to_bytes_s16,
- "u24" : to_bytes_u24, "s24" : to_bytes_s24,
- "u32" : to_bytes_u32, "s32" : to_bytes_s32,
- "u64" : to_bytes_u64, "s64" : to_bytes_s64,
- "f32" : to_bytes_f32, "f64" : to_bytes_f64,
- "rgb" : to_bytes_rgb, "argb" : to_bytes_argb
- }
- def total_byte_length(bytes_list): return sum(len(i) for i in bytes_list)
- def join_bytes(bytes_list): return (b'').join(bytes_list)
- def high_bit_boolean(boolean, integer, byte_width):
- bool_binary = str(int(boolean))
- int_binary = bin(integer)[2:].rjust(byte_width*8-1,'0') #([2:] to remove the "0b" prefix)
- result_binary = bool_binary + int_binary
- if len(result_binary) > byte_width*8:
- ps.printError(f"high_bit_boolean() result longer than {byte_width} bytes")
- return to_bytes_integer(int(result_binary, 2), byte_width, False)
- '''struct Object { //48B (28 bytes reserved for the user data)
- kit::u64 _user_0; //data depends on object type
- kit::u64 _user_1; //
- kit::u64 _user_2; //
- kit::u32 _user_3; //
- struct {
- struct {
- kit::u16 x;
- kit::u16 y;
- } /*-----------*/ size;
- struct {
- kit::u8 x; //hitbox's offset relative to the
- kit::u8 y; //object's actual position
- } /*---------*/ offset;
- } /*-----------------*/ hb; //h[it]b[ox]
- kit::s16 x;
- kit::s16 y;
- struct {
- kit::u16 type : 14;
- kit::u16 persistent : 1; //'never reset this object's cached data?'
- kit::u16 in_front : 1; //'should be displayed in front of player?'
- };
- Object_TickCallback update;
- };'''
- #returns a single byte object
- def assemble_object(obj):
- if _DEBUG: print(f' assembling object "{obj["name"]}"...')
- #first 28 bytes of the 48 byte object
- customs = []
- for c in obj["data"]:
- customs.append(to_bytes[c[0]](c[1]))
- customs_len = total_byte_length(customs)
- if customs_len > 28: ps.printError("total length of properties exceeded 28 bytes")
- if customs_len < 28: customs.append(b'\x00'*(28-customs_len)) #add padding to end
- type = obj["type"]
- persistent = obj["name"][-1:] == "*"
- in_front = obj["name"][0:1] == "^"
- if type >= 2**14: ps.printError("object type id cannot exceed 16383")
- if persistent: type += 16384 #set bit 14
- #last 20 bytes of the 48 byte object
- intrinsics = [
- to_bytes_integer(obj["width" ], 2, False), #u16: hb.size.x
- to_bytes_integer(obj["height" ], 2, False), #u16: hb.size.y
- to_bytes_integer(obj["hb_offset_x"], 1, False), #u8 : hb.offset.x
- to_bytes_integer(obj["hb_offset_y"], 1, False), #u8 : hb.offset.y
- to_bytes_integer(obj["x" ], 2, True ), #s16: x
- to_bytes_integer(obj["y" ], 2, True ), #s16: y
- high_bit_boolean( in_front, type, 2 ), #u16: in_front, persistent, type
- #(doesn't matter what this is set to, as it's overwritten at runtime anyway)
- b'\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA' #ptr: update
- ]
- #concatenate the result of the object's custom and intrinsic properties
- return join_bytes(customs + intrinsics)
- '''//for storing the contents of a .ksd file (compressed scene data)
- #define KSD_MAGIC 0x4644536B //"kSDF"
- #define SD_FILELEN(_scene_desc) ((_scene_desc).dataSize+sizeof(SceneDescriptor))
- struct SceneDescriptor { //64B
- /*0x00*/ kit::u32 magic; //= 0x4644536B = "kSDF" (no null terminator)
- /*0x04*/ kit::u32 dataSize; //size of file in bytes, minus the header (which is always 64B)
- //offsets to array data in file (if nullptr, data is assumed to not be present!)
- //(also, objs[x].type refers to the index inside gl_objCallbacks used by the object.
- //each element of gl_objCallbacks is of type Object_TickCallback)
- /*0x08*/ Object* objs; //contains the original states of each object in the scene
- /*0x10*/ Tile* pat_mg; //pattern data is compressed using RLE, where the 1st element's .value
- /*0x18*/ Tile* pat_fg; //member is the run length, with the 2nd being the actual tile data
- struct {
- /*0x20*/ kit::u16 bmp_bg : 15; //associated background id
- /*0x21*/ kit::u16 repeat_bg : 1; //'should bg repeat?' (stretches to screen otherwise)
- };
- struct {
- /*0x22*/ kit::u16 objs_len : 15; //number of objects in scene
- /*0x23*/ kit::u16 visited : 1; //used to help determine if objects should reset on load
- };
- /*0x24*/ kit::u16 tileset_a; //1st associated tileset
- /*0x26*/ kit::u16 tileset_b; //2nd associated tileset
- /*0x28*/ kit::u16 edge_n; //scene id for north edge
- /*0x2A*/ kit::u16 edge_s; //scene id for south edge
- /*0x2C*/ kit::u16 edge_w; //scene id for west edge
- /*0x2E*/ kit::u16 edge_e; //scene id for east edge
- /*0x30*/ kit::u16 scene; //scene id for scene itself
- /*0x32*/ kit::u16 music; //music id; 0 for no change, -1 (65535) to stop
- /*0x34*/ kit::u16 ambience_a; //ambient track id a; 0 for no change, -1 to stop
- /*0x36*/ kit::u16 ambience_b; //ambient track id b; 0 for no change, -1 to stop
- /*0x38*/ kit::BinaryData* fileData = nullptr; //raw file data; appears as nullptr in file
- /*0x40*/ //... (array data is stored in order of: objs, pat_mg, and pat_fg)
- };'''
- #returns a single byte object
- def assemble_header(props, dataSize, objs, pat_mg, pat_fg):
- bmp_bg = props["bmp_bg" ]
- repeat_bg = props["repeat_bg"]
- if props["objs_len"] < 0:
- ps.printError("objs_len cannot be < 0")
- return join_bytes((
- to_bytes_integer( 0x4644536B, 4, False), #u32
- to_bytes_integer( dataSize, 4, False), #u32
- to_bytes_integer( objs, 8, False), #ptr
- to_bytes_integer( pat_mg, 8, False), #ptr
- to_bytes_integer( pat_fg, 8, False), #ptr
- high_bit_boolean( repeat_bg, bmp_bg, 2 ), #u16
- to_bytes_integer(props["objs_len" ], 2, True ), #u15 (yes, 15-bit)
- to_bytes_integer(props["tileset_a" ], 2, False), #u16
- to_bytes_integer(props["tileset_b" ], 2, False), #u16
- to_bytes_integer(props["edge_n" ], 2, False), #u16
- to_bytes_integer(props["edge_s" ], 2, False), #u16
- to_bytes_integer(props["edge_w" ], 2, False), #u16
- to_bytes_integer(props["edge_e" ], 2, False), #u16
- to_bytes_integer(props["scene" ], 2, False), #u16
- to_bytes_integer(props["music" ], 2, False), #u16
- to_bytes_integer(props["ambience_a"], 2, False), #u16
- to_bytes_integer(props["ambience_b"], 2, False), #u16
- b'\x00\x00\x00\x00\x00\x00\x00\x00' #ptr
- ))
- #returns a list of byte objects
- def assemble_layer(layers, which):
- layer_tile = layers[which]
- layer_collision = layers["collision"]
- raw = [None,]*len(layer_tile)
- for i in range(len(layer_tile)):
- tile = to_bytes_integer(layer_tile [i], 1, False)
- collision = to_bytes_integer(layer_collision[i], 1, False)
- raw[i] = tile + collision
- rle = []
- previous = raw[0]
- count = 0
- for current in raw:
- if current == previous:
- count += 1
- else:
- run = to_bytes_integer(count, 2, False)
- rle.append(run + previous)
- count = 1
- previous = current
- if count != 0:
- run = to_bytes_integer(count, 2, False)
- rle.append(run + previous)
- return rle
- #returns a list of byte objects
- def assemble_scene(scene):
- if _DEBUG: print(f' GENERATING SCENE { scene["properties"]["scene"] }\'S DATA:')
- objs = [ assemble_object(obj) for obj in scene["objs"] ]
- mg = assemble_layer(scene["layers"], "mg")
- fg = assemble_layer(scene["layers"], "fg")
- objs_nonzero = len(scene["objs"]) > 0
- fg_nonzero = scene["properties"]["fg_nonzero"]
- mg_nonzero = scene["properties"]["mg_nonzero"]
- #header_len = 64
- objs_len = total_byte_length(objs)
- mg_len = total_byte_length(mg )
- fg_len = total_byte_length(fg )
- dataSize = objs_len + mg_len + fg_len
- offset_objs = 64
- offset_mg = offset_objs + objs_len
- offset_fg = offset_mg + mg_len
- #offsets should be 0 in file if array is not used
- offset_objs *= objs_nonzero # x*0 = 0, x*1 = x, blah blah blah
- offset_mg *= mg_nonzero
- offset_fg *= fg_nonzero
- header = assemble_header(scene["properties"], dataSize,
- offset_objs, offset_mg, offset_fg)
- output = [header,]
- if objs_nonzero: output += objs
- if mg_nonzero : output += mg
- if fg_nonzero : output += fg
- return output
- def write_scene(list_of_byte_objects, scene_id):
- if _DEBUG: print(f' WRITING SCENE { scene_id }\'S DATA TO FILE:')
- with open(f'scene_{scene_id}.ksd', "wb") as file:
- for chunk in list_of_byte_objects:
- file.write(chunk)
- #writes scene data to descriptor file
- def compile_scene(scene_id):
- timeStartTotal = time()
- timeStart = time()
- scene = ps.parseSceneMap(scene_id, announce=_DEBUG)
- timeTakenMS = (time()-timeStart)*1000
- if _DEBUG: print(" FINISHED PARSING IN: {:.4}ms".format(timeTakenMS))
- timeStart = time()
- scene_bytes = assemble_scene(scene)
- timeTakenMS = (time()-timeStart)*1000
- if _DEBUG: print(" FINISHED GENERATING IN: {:.4}ms".format(timeTakenMS))
- timeStart = time()
- write_scene(scene_bytes, scene_id)
- timeTakenMS = (time()-timeStart)*1000
- if _DEBUG: print(" FINISHED WRITING IN: {:.4}ms".format(timeTakenMS))
- timeTakenMS = (time()-timeStartTotal)*1000
- if _DEBUG: print(" TOTAL TIME SPENT: {:.4}ms".format(timeTakenMS))
- return timeTakenMS
- #arguments go as follows: rangeMin, rangeMax, pauseOnExit, disablePrinting
- #rangeMax and pauseOnExit are optional, as if only rangeMin
- #is specified, rangeMax will be set to rangeMin as well
- #(pauseOnExit and disablePrinting are false by default)
- if len(argv) < 2: ps.printError("must provide at least 1 argument for rangeMin")
- rangeMin = int(argv[1])
- if len(argv) >= 5: _DEBUG = argv[4].lower() == "false"
- pauseOnExit = False
- if len(argv) >= 4: pauseOnExit = argv[3].lower() == "true"
- rangeMax = rangeMin
- if len(argv) >= 3: rangeMax = int(argv[2])
- cmd("cls")
- timeStart = time()
- for i in range(rangeMin, rangeMax+1):
- compile_scene(i)
- timeTakenMS = (time()-timeStart)*1000
- if _DEBUG: print(" SCENES {} -> {} COMPILED IN: {:.4}ms".format(rangeMin, rangeMax, timeTakenMS))
- if pauseOnExit: cmd("pause")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement