Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- if __name__ != "__main__": exit(0) #lol
- _DEBUG = True
- from time import time
- from sys import argv
- from os import listdir, system as cmd
- from os.path import isfile, join, exists
- from pprint import pprint
- import xml.etree.ElementTree as ET
- def printError(s, fatal=True):
- print("Error: {}".format(s))
- if fatal: exit(-1)
- else : return False
- def sceneIdToPath(scene_id):
- return "../scene_{}/scene_{}_pattern.tmx".format(scene_id, scene_id)
- #poor man's regex lol
- def extractTilesetName(path):
- #will turn something like "../foo/tileset_collision.tsx" into "collision"
- name = path.split("/")[-1]
- return (("_").join(name.split("."))).split("_")[1]
- def tryInt(string):
- try:
- return int(string)
- except ValueError:
- return None
- except TypeError:
- return None
- def tryDict(dict, key):
- try:
- return dict[key]
- except KeyError:
- return None
- def propertyWarning(properties, name, fatal=False):
- if tryDict(properties, name) == None:
- #intentionally indented twice
- if fatal: print(" Error: property \"{}\" doesn't exist!".format(name)); exit(-1)
- else : print(" Warning: property \"{}\" doesn't exist!".format(name))
- return True #property does not exist
- else:
- return False #property exists
- #value is a hex quad string of "#AARRGGBB"
- def toRGBA(value):
- alpha = int(value[1:3], 16)
- red = int(value[3:5], 16)
- blue = int(value[5:7], 16)
- green = int(value[7:9], 16)
- return (red, green, blue, alpha)
- #dataValue should always be of type str
- def convertType(dataType, dataValue):
- #(i wish python had switch statements...)
- if dataType == "int" : return int(float(dataValue))
- elif dataType == "bool" : return bool(dataValue)
- elif dataType == "float" : return float(dataValue)
- elif dataType == "color" : return toRGBA(dataValue)
- elif dataType == "object" : return int(float(dataValue))
- #elif dataType == 'file' : return dataValue #(redundant)
- else : return dataValue
- def parseObject(obj_in):
- obj_out = {}
- props = {}
- #(tbd)
- return { "test" : 2 }
- def parseSceneMap(scene_id, announce=False):
- filename = sceneIdToPath(scene_id)
- if not exists(filename): printError("scene \"{}\" does not exist".format(filename))
- if not isfile(filename): printError("scene \"{}\" is not a file".format(filename))
- if _DEBUG or announce: print("PARSING \"{}\":".format(filename))
- tree = ET.parse(filename)
- map = tree.getroot()
- #get map's intrinsic properties
- width = int(map.attrib["width" ]) #should be 32
- height = int(map.attrib["height" ]) #should be 18
- #tileWidth = int(map.attrib["tilewidth" ]) #should be 24 (unused)
- #tileHeight = int(map.attrib["tileheight"]) #should be 24 (unused)
- mapLength = width*height
- #get map's custom properties
- props = {}
- for prop in map.find("properties"):
- pName = tryDict(prop.attrib, "name" ) or "NAMELESS"
- pType = tryDict(prop.attrib, "type" )
- pValue = tryDict(prop.attrib, "value") or "NOVALUE"
- props[ pName ] = convertType(pType, pValue)
- if propertyWarning(props, "bmp_bg" ): props["bmp_bg" ] = 0
- if propertyWarning(props, "repeat_bg" ): props["repeat_bg" ] = False #lol
- #if propertyWarning(props, "objs_len" ): props["objs_len" ] = 0 #(calculated later)
- #if propertyWarning(props, "tileset_a" ): props["tileset_a" ] = 0 #(calculated later)
- #if propertyWarning(props, "tileset_b" ): props["tileset_b" ] = 0 #(calculated later)
- if propertyWarning(props, "edge_n" ): props["edge_n" ] = 0
- if propertyWarning(props, "edge_s" ): props["edge_s" ] = 0
- if propertyWarning(props, "edge_w" ): props["edge_w" ] = 0
- if propertyWarning(props, "edge_e" ): props["edge_e" ] = 0
- #if propertyWarning(props, "scene" ): props["scene" ] = 0 #(calculated later)
- if propertyWarning(props, "music" ): props["music" ] = 0
- if propertyWarning(props, "ambience_a"): props["ambience_a"] = 0
- if propertyWarning(props, "ambience_b"): props["ambience_b"] = 0
- #calculate tileset boundaries
- _tsets = []
- for tset in map.findall("tileset"):
- tFirstGID = int(tryDict(tset.attrib, "firstgid")) - 1
- tName = extractTilesetName(tryDict(tset.attrib, "source"))
- _tsets.append( ( tFirstGID, tryInt(tName) or 0 ) )
- tsets = sorted(_tsets, key = lambda x: x[0] ) #sorted by first element
- if len(tsets) < 3: printError("map cannot have less than 3 tilesets (including collision)")
- elif len(tsets) > 3: printError("map cannot have more than 3 tilesets (including collision)")
- #there should only be 1 tileset that registers as null (the collision's tileset)
- tset_offsets = [0,] * 3
- tset_nulls, tset_valids = 0, 0
- tset_a, tset_b = 0, 0
- for i in range(len(tsets)):
- #collision tileset
- if tsets[i][1] == 0: tset_offsets[i] = -1; tset_nulls += 1
- #tileset_a
- elif tset_valids == 0: tset_a = tsets[i][1]; tset_valids += 1
- #tileset_b
- else : tset_b = tsets[i][1]#; tset_valids += 1
- if tset_nulls != 1: printError("map must have exactly 1 null tileset, reserved for collider map")
- #get map's layer data
- layers = {}
- for layer in map.findall("layer"):
- lName = tryDict(layer.attrib,"name" ) or "NAMELESS"
- lWidth = int( tryDict(layer.attrib,"width" ) or -1 )
- lHeight = int( tryDict(layer.attrib,"height") or -1 )
- # n-1 to make sure both tile 0 and 1 are treated as transparent
- lData = [ max(int(n)-1, 0) for n in layer.find("data").text.split(",") ] #csv to list
- if lWidth*lHeight != mapLength : valid = printError("layer dims inconsistent with map dimensions")
- if lWidth*lHeight != len(lData): valid = printError("layer dims inconsistent with its attributes")
- layers[ lName ] = lData
- valid = True
- if "collision" not in layers: valid = printError("layer \"collision\" doesn't exist", quit=False)
- if "fg" not in layers: valid = printError("layer \"fg\" doesn't exist", quit=False)
- if "mg" not in layers: valid = printError("layer \"mg\" doesn't exist", quit=False)
- if not valid: exit(-1)
- for i in range(len(layers["collision"])):
- #collider info is 6 bits (2^6 = 64),
- #which is a factor of a normal tileset's 7 (2^7 = 128) anyway
- layers["collision"][i] %= 64
- for i in range(len(layers["fg"])): #(fg & mg should be the same length)
- #fg
- tile = layers["fg"][i]
- offset = tset_offsets[tile//128] #tile//128 should be between 0 -> 2
- if offset == -1: printError("fg cannot contain collision map tiles")
- layers["fg"][i] = (tile%128) + offset
- #mg
- tile = layers["mg"][i]
- offset = tset_offsets[tile//128]
- if offset == -1: printError("mg cannot contain collision map tiles")
- layers["mg"][i] = (tile%128) + offset
- props["objs_len" ] = 0
- props["tileset_a"] = tset_a
- props["tileset_b"] = tset_b
- props["scene" ] = scene_id
- #get scene objects
- obj_groups = map.findall("objectgroup")
- objs = []
- if len(obj_groups) > 1: printError("scene cannot have more than 1 object group")
- if len(obj_groups) == 1:
- #(this loop will not run at all if there are no objects)
- for obj in obj_groups[0].findall("object"):
- props["objs_len" ] += 1
- objs.append(parseObject(obj))
- if _DEBUG:
- print(" --PROPERTIES--:")
- pprint(props, indent=4)
- print(" --TILESETS--:")
- pprint(tsets, indent=4, width=40)
- # print(" --LAYERS--")
- # print(layers)
- print(" --OBJECTS--")
- pprint(objs)
- scene = {}
- scene["properties"] = props
- scene["layers" ] = layers
- return scene
- '''
- struct Object { //48B
- kit::u64 _user_0;
- kit::u64 _user_1;
- kit::u64 _user_2;
- Object_TickCallback funcUpdate;
- struct {
- kit::u16 hb_size_x;
- kit::u16 hb_size_y;
- kit::u8 hb_offset_x;
- kit::u8 hb_offset_y;
- };
- kit::s16 x;
- kit::s16 y;
- kit::u16 type;
- };
- //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)
- void unload();
- ~SceneDescriptor(){ unload(); }
- SceneDescriptor(){ kit::memory::set(this, 0, sizeof(SceneDescriptor)); }
- SceneDescriptor(kit::u16 scene_id);
- };
- '''
- magic = "kSDF" #0x00: 4B
- dataSize = 0 #0x04: 8B
- objs = 0 #0x08: 8B
- pat_mg = 0 #0x10: 8B
- pat_mg = 0 #0x18: 8B
- bmp_bg = 0 #0x20: 15b (as in [b]its, not [B]ytes)
- repeat_bg = 0 #0x21: 1b
- objs_len = 0 #0x22: 15b
- visited = False #0x23: 1b (always 0 in file)
- tileset_a = 0 #0x24: 2B
- tileset_b = 0 #0x26: 2B
- edge_n = 0 #0x28: 2B
- edge_s = 0 #0x2A: 2B
- edge_w = 0 #0x2C: 2B
- edge_e = 0 #0x2D: 2B
- scene = 0 #0x30: 2B
- music = 0 #0x32: 2B
- ambience_a = 0 #0x34: 2B
- ambience_b = 0 #0x36: 2B
- fileData = 0 #0x38: 8B
- #(array data) #0x40: <dataSize>B
- '''
- #iirc byte objects are immutable, but the header is only 72 bytes anyway
- le = "little" #[l]ittle [e]ndian (i wish python had macros)
- header_data = bytes(magic,"ascii") # 0x00: magic
- header_data += format # 0x04: format
- header_data += headerSize.to_bytes( 2,le) # 0x06: headerSize
- fileoutPath = join(folderout,filenames[which])
- fileout = open(fileoutPath,"wb")
- fileout.write(header_data)
- fileout.write(sample_data)
- fileout.close()
- timeTakenMS = (time()-startTime)*1000
- print("\noutput file info:")
- print((" magic = \"{}\"").format(magic))
- print("\nsaved to: \"{}\"".format(fileoutPath))
- print("time taken: {:.4}ms".format(timeTakenMS))
- print("\npress any key to exit")
- cmd("pause >nul")
- '''
- startTime = time()
- parseSceneMap(1)
- timeTakenMS = (time()-startTime)*1000
- print(timeTakenMS)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement