#! /usr/bin/env python # -*- coding: utf8 -*- # # Copyright (C) 2014 Evol Online # Author: Andrei Karas (4144) import array import base64 import gzip import os import re import datetime import xml import csv import ogg.vorbis import StringIO import sys import zlib import struct import shutil from sets import Set from xml.dom import minidom def detectCommand(): if sys.argv[0][-12:] == "/listmaps.py": return "listmaps" elif sys.argv[0][-15:] == "/extractmaps.py": return "extractmaps" elif sys.argv[0][-13:] == "/mapstotmx.py": return "mapstotmx" elif sys.argv[0][-15:] == "/queststoxml.py": return "queststoxml" elif sys.argv[0][-14:] == "/itemstoxml.py": return "itemstoxml" elif sys.argv[0][-17:] == "/monsterstoxml.py": return "monsterstoxml" elif sys.argv[0][-20:] == "/mercenariestoxml.py": return "mercenariestoxml" elif sys.argv[0][-13:] == "/petstoxml.py": return "petstoxml" elif sys.argv[0][-21:] == "/homunculusestoxml.py": return "homunculusestoxml" elif sys.argv[0][-15:] == "/skillstoxml.py": return "skillstoxml" elif sys.argv[0][-14:] == "/tmxtocache.py": return "tmxtocache" return "help" def makeDir(path): if not os.path.exists(path): os.makedirs(path) def readInt32(f): data = f.read(4) arr = array.array("I") arr.fromstring(data) return arr[0] def readInt16(f): data = f.read(2) arr = array.array("H") arr.fromstring(data) return arr[0] def readMapName(f): data = f.read(12) data = str(data) while data[-1] == '\x00': data = data[:-1] return data def readData(f, sz): return f.read(sz) def readFile(path): with open(path, "r") as f: return f.read() def writeInt32(f, i): f.write(struct.pack("I", i)) def writeInt16(f, i): f.write(struct.pack("H", i)) def writeMapName(f, name): if len(name) > 12: name = name[:12] while len(name) < 12: name = name + '\x00' f.write(struct.pack("12s", name)) def writeData(f, data): f.write(data) def getTileData(mapData, x, y, sx): data = mapData[y * sx + x] arr = array.array("B") arr.fromstring(data) data = arr[0] return data def getTile(data): normal = 0 collison = 0 if data == 0: # 000 normal walkable normal = 1 collision = 5 elif data == 1: # 001 non walkable normal = 2 collision = 6 elif data == 2: # 010 same with 0 normal = 1 collision = 5 elif data == 3: # 011 same with 0, but water normal = 3 collision = 5 elif data == 4: # 100 same with 0 normal = 1 collision = 5 elif data == 5: # 101 same with 1, but shootable (for now not supported!!!) normal = 4 collision = 6 elif data == 6: # 110 same with 0 normal = 1 collision = 5 return (str(normal), str(collision)) def getGroundTile(flag): return str(flag + 1) def getCollisionTile(flag): if flag == 0: return "0" return "4" def copyFile(src, dst, name): shutil.copyfile(src + name, dst + name) def saveFile(fileName, data): with open(fileName, "w") as w: w.write(data) def stripQuotes(data): if len(data) == 0: return data if data[-1] == "\"": data = data[:-1] if data[0] == "\"": data = data[1:] if data[-1] == "'": data = data[:-1] if data[0] == "'": data = data[1:] return data def stripQuotes2(data): for idx in xrange(0, len(data)): data[idx] = stripQuotes(data[idx]) return data def strToXml(data): data = data.replace("&", "&"); data = data.replace("<", "<"); data = data.replace(">", ">"); return data def printHelp(): print "Unknown options selected." print "" print "Use list.py for list maps in cache" print "Use extract.py to extract maps from cache" exit(0) def listMapCache(f, mapsCount): print "Known maps:" print "{0:12} {1:<4}x {2:<4} {3:<10}".format("Map name", "sx", "sy", "compressed size") for i in xrange(0, mapsCount): name = readMapName(f) sx = readInt16(f) sy = readInt16(f) sz = readInt32(f) print "{0:12} {1:<4}x {2:<4} {3:<10}".format(name, sx, sy, sz) f.seek(sz, 1) def extractMaps(f, mapsCount): destDir = "maps/" makeDir(destDir) for i in xrange(0, mapsCount): name = readMapName(f) sx = readInt16(f) sy = readInt16(f) sz = readInt32(f) data = readData(f, sz) dc = zlib.decompressobj() data = dc.decompress(data) with open(destDir + name, "wb") as w: w.write(struct.pack("H", sx)) w.write(struct.pack("H", sy)) w.write(data) def covertToTmx(f, mapsCount): destDir = "clientdata/" mapsDir = destDir + "maps/" tilesetsDir = destDir + "graphics/tilesets/" templatesDir = "templates/" makeDir(mapsDir) makeDir(tilesetsDir) copyFile(templatesDir, tilesetsDir, "collision.png") copyFile(templatesDir, tilesetsDir, "tileset.png") tmx = readFile("templates/template.tmx") for i in xrange(0, mapsCount): name = readMapName(f) print "converting map [{0:4}/{1:4}]: {2}".format(i, mapsCount + 1, name) sx = readInt16(f) sy = readInt16(f) sz = readInt32(f) mapData = readData(f, sz) dc = zlib.decompressobj() mapData = dc.decompress(mapData) ground = "" collision = "" fringe = "" for y in xrange(0, sy): for x in xrange(0, sx): tileData = getTileData(mapData, x, y, sx) tile = getTile(tileData) if x + 1 == sx and y + 1 == sy: ground = ground + tile[0] collision = collision + tile[1] fringe = fringe + "0"; else: ground = ground + tile[0] + "," collision = collision + tile[1] + "," fringe = fringe + "0,"; ground = ground + "\n" collision = collision + "\n" fringe = fringe + "\n" saveFile(mapsDir + name + ".tmx", tmx.format(sx, sy, ground, collision, fringe)) def covertQuests(): destDir = "clientdata/" templatesDir = "templates/" questsDbFile = "serverdata/db/quest_db.txt" fieldsSplit = re.compile(",") makeDir(destDir) tpl = readFile(templatesDir + "quest.tpl") quests = readFile(templatesDir + "quests.xml") data = "" with open(questsDbFile, "r") as f: for line in f: if line == "" or line[0:2] == "//": continue rows = fieldsSplit.split(line) if len(rows) < 9: continue questId = rows[0] text = rows[8] if text[-1] == "\n": text = text[:-1] text = strToXml(stripQuotes(text)) name = text if len(name) > 20: name = name[:19] data = data + tpl.format(questId, name, text + ": " + str(questId)) saveFile(destDir + "quests.xml", quests.format(data)) def prepStat(val, text): if val != "0" and val != "": return " {0}=\"{1}\"\n".format(text, val) return "" def convertItems(): destDir = "clientdata/" templatesDir = "templates/" itemsDbFile = "serverdata/sql-files/item_db_re.sql" fieldsSplit = re.compile(",") bracketsSplit = re.compile("[(]|[)]") makeDir(destDir) tpl = readFile(templatesDir + "item.tpl") items = readFile(templatesDir + "items.xml") data = "" ids = Set() with open(itemsDbFile, "r") as f: for line in f: if len(line) < 10 or line[0:2] == "//" or line[0:12] != "REPLACE INTO": continue rows = bracketsSplit.split(line) if len(rows) < 2: continue rows = fieldsSplit.split(rows[1]) if len(rows) < 31: continue rows = stripQuotes2(rows) itemId = rows[0] name = rows[1] name2 = rows[2] itemType = rows[3] priceBuy = rows[4] priceSell = rows[5] weight = rows[6] atk = rows[7] matk = rows[8] defence = rows[9] attackRange = rows[10] slots = rows[11] equipJobs = rows[12] equipUpper = rows[12] equipGender = rows[14] equipLocations = rows[15] weaponLevel = rows[16] equipLevelMin = rows[17] equipLevelMax = rows[18] refinable = rows[19] view = rows[20] bindOnEquip = rows[21] buyInStore = rows[22] delay = rows[23] tradeFlag = rows[24] tradeGroup = rows[25] nouseFlag = rows[26] nouseGroup = rows[27] stackAmount = rows[28] stackFlag = rows[29] sprite = rows[30] name = name.replace("\\'", "'") image = "" statStr = prepStat(atk, "attack") statStr = statStr + prepStat(matk, "mattack") statStr = statStr + prepStat(defence, "defence") statStr = statStr + prepStat(weight, "weight") statStr = statStr + prepStat(attackRange, "range") statStr = statStr + prepStat(delay, "speed") # print itemId + "," + equipLocations # typeStr = "other" typeStr = "equip-legs" spriteStr = "equipment/legs/trousers-male.xml" image = "generic/box-fish.png" if itemType == 0 or itemType == 2 or itemType == 18: # usable image = "usable/bread.png" typeStr = "usable" spriteStr = ""; elif equipLocations == "0": image = "usable/bread.png" typeStr = "usable" spriteStr = ""; elif equipLocations == "1": image = "equipment/legs/shorts.png|S:#4d4d4d,514d47,686868,706662,919191,99917b,b6b6b6,c0b698,dfdfdf,e4dfca" typeStr = "equip-legs" spriteStr = "equipment/legs/shorts-male.xml|#4d4d4d,514d47,686868,706662,919191,99917b,b6b6b6,c0b698,dfdfdf,e0d5bf"; elif equipLocations == "2": image = "equipment/weapons/knife.png" typeStr = "equip-1hand" spriteStr = "equipment/weapons/knife.xml"; elif equipLocations == "4": image = "equipment/hands/armbands.png" typeStr = "equip-arms" spriteStr = "equipment/hands/armbands-male.xml"; elif equipLocations == "16": image = "equipment/chest/cottonshirt.png|S:#3c3c3c,3e3c38,4d4d4d,514d47,686868,706662,919191,99917b,b6b6b6,c0b698,dfdfdf,e4dfca" typeStr = "equip-torso" spriteStr = "equipment/chest/cottonshirt-male.xml|#43413d,59544f,7a706c,8a8176,a69e88,d1c7a7,e0d5bf"; elif equipLocations == "64": image = "equipment/feet/boots.png|S:#3c3c3c,40332d,4d4d4d,5e4a3d,686868,705740,919191,a1825d,b6b6b6,b59767,dfdfdf,dbbf88" typeStr = "equip-feet" spriteStr = "equipment/feet/boots-male.xml|#40332d,5e4a3d,705740,a1825d,b59767,dbbf88"; elif equipLocations == "136": image = "equipment/chest/cottonshirt.png|S:#3c3c3c,3e3c38,4d4d4d,514d47,686868,706662,919191,99917b,b6b6b6,c0b698,dfdfdf,e4dfca" typeStr = "equip-torso" spriteStr = "equipment/chest/cottonshirt-male.xml|#43413d,59544f,7a706c,8a8176,a69e88,d1c7a7,e0d5bf"; elif equipLocations == "256": image = "equipment/head/bandana.png" typeStr = "equip-head" spriteStr = "equipment/head/bandana-male.xml"; elif equipLocations == "512": # no sprites in evol image = "equipment/chest/sailorshirt.png" typeStr = "equip-torso" spriteStr = "equipment/chest/shirt-male.xml|#131913,1b231d,233129,35433e,4e6059,6c8279;#72571e,836737,a5854d,b18f45"; name = strToXml(name); if itemId not in ids: ids.add(itemId) data = data + tpl.format(itemId, name, 0, statStr, image, typeStr, spriteStr) if view != "0" and view not in ids: ids.add(view) data = data + tpl.format(view, name, 0, statStr, image, typeStr, spriteStr) saveFile(destDir + "items.xml", items.format(data)) def convertMonsters(): destDir = "clientdata/" templatesDir = "templates/" monstersDbFile = "serverdata/sql-files/mob_db_re.sql" fieldsSplit = re.compile(",") bracketsSplit = re.compile("[(]|[)]") makeDir(destDir) tpl = readFile(templatesDir + "monster.tpl") monsters = readFile(templatesDir + "monsters.xml") data = "" ids = Set() monsterSprite = """monsters/blub.xml accessories/blub-tentacle.xml|#3e4164,3a3968,544a82,64437a,7d6db4,a26392,8f99c4,d294ab,b3cdcd,e7b8b8,d9ecd1,f0e8c5"""; with open(monstersDbFile, "r") as f: for line in f: if len(line) < 10 or line[0:2] == "//" or line[0:12] != "REPLACE INTO": continue rows = bracketsSplit.split(line) if len(rows) < 2: continue rows = fieldsSplit.split(rows[1]) if len(rows) < 5: continue monsterId = rows[0] name = strToXml(stripQuotes(rows[2])) data = data + tpl.format(monsterId, name, monsterSprite) saveFile(destDir + "monsters.xml", monsters.format(data)) def convertMercenaries(): destDir = "clientdata/" templatesDir = "templates/" mercenariesDbFile = "serverdata/db/mercenary_db.txt" fieldsSplit = re.compile(",") makeDir(destDir) tpl = readFile(templatesDir + "mercenary.tpl") mercenaries = readFile(templatesDir + "mercenaries.xml") data = "" mercenarySprite = "monsters/croc.xml"; with open(mercenariesDbFile, "r") as f: for line in f: if line == "" or line[0:2] == "//": continue rows = fieldsSplit.split(line) if len(rows) < 9: continue mercenaryId = rows[0] data = data + tpl.format(mercenaryId, mercenarySprite) saveFile(destDir + "mercenaries.xml", mercenaries.format(data)) def convertPets(): destDir = "clientdata/" templatesDir = "templates/" petsDbFile = "serverdata/db/pet_db.txt" fieldsSplit = re.compile(",") makeDir(destDir) tpl = readFile(templatesDir + "pet.tpl") pets = readFile(templatesDir + "pets.xml") data = "" petSprite = "monsters/tortuga.xml"; with open(petsDbFile, "r") as f: for line in f: if line == "" or line[0:2] == "//": continue rows = fieldsSplit.split(line) if len(rows) < 9: continue petId = rows[0] data = data + tpl.format(petId, petSprite) saveFile(destDir + "pets.xml", pets.format(data)) def convertHomunculuses(): destDir = "clientdata/" templatesDir = "templates/" homunculusesDbFile = "serverdata/db/re/homunculus_db.txt" fieldsSplit = re.compile(",") makeDir(destDir) tpl = readFile(templatesDir + "homunculus.tpl") homunculuses = readFile(templatesDir + "homunculuses.xml") data = "" homunculusSprite = "monsters/tortuga.xml"; with open(homunculusesDbFile, "r") as f: for line in f: if line == "" or line[0:2] == "//": continue rows = fieldsSplit.split(line) if len(rows) < 9: continue homunculusId = rows[0] data = data + tpl.format(homunculusId, homunculusSprite) saveFile(destDir + "homunculuses.xml", homunculuses.format(data)) def convertSkillsToXml(): destDir = "clientdata/" templatesDir = "templates/" skillsDbFile = "serverdata/db/re/skill_db.txt" fieldsSplit = re.compile(",") makeDir(destDir) tpl = readFile(templatesDir + "skill.tpl") skills = readFile(templatesDir + "skills.xml") data = "" with open(skillsDbFile, "r") as f: for line in f: if line == "" or line[0:2] == "//": continue rows = fieldsSplit.split(line) if len(rows) < 9: continue skillId = rows[0] name = rows[15].strip() description = rows[16].strip() idx = description.find ("//") if idx > 1: description = description[:idx] data = data + tpl.format(skillId, name, description) saveFile(destDir + "skills.xml", skills.format(data)) def getTmxFiles(srcDir): for name in os.listdir(srcDir): fileName = srcDir + name if os.path.isfile(fileName) == False: continue if name.endswith(".tmx") == False: continue yield fileName def recreateMapCache(): destDir = "mapcache/" srcDir = "clientdata/maps/" makeDir(destDir) sz = 0L mapsCount = 0 with open(destDir + "map_cache.dat", "wb") as w: writeInt32(w, 0) # file size writeInt16(w, 0) # maps count writeInt16(w, 0) # padding sz = sz + 8 for fileName in getTmxFiles(srcDir): dom = minidom.parse(fileName) root = dom.documentElement firstgid = 0 for tileset in root.getElementsByTagName("tileset"): try: name = tileset.attributes["name"].value except: name = "" if name == "Collision": firstgid = int(tileset.attributes["firstgid"].value) break for layer in root.getElementsByTagName("layer"): if layer.attributes["name"].value == "Collision": data = layer.getElementsByTagName("data") if data is None or len(data) != 1: continue data = data[0] width = int(layer.attributes["width"].value) height = int(layer.attributes["height"].value) encoding = data.attributes["encoding"].value compression = data.attributes["compression"].value if encoding == "base64": binData = data.childNodes[0].data.strip() binData = binData.decode('base64') if compression == "gzip": dc = zlib.decompressobj(16 + zlib.MAX_WBITS) else: dc = zlib.decompressobj() layerData = dc.decompress(binData) arr = array.array("I") arr.fromstring(layerData) else: print "map format not supported: " + fileName continue tiles = [] for tile in arr: if tile == 0: tileType = 0 else: tileType = tile - firstgid; if tileType == 0 or tileType == 4: tiles.append(0) else: tiles.append(1) comp = zlib.compressobj() binData = struct.pack(str(len(tiles))+"B", *tiles) binData = zlib.compress(binData) idx = fileName.rfind("/") + 1 mapName = fileName[idx:-4] writeMapName(w, mapName) writeInt16(w, width) writeInt16(w, height) writeInt32(w, len(binData)) writeData(w, binData) print fileName mapsCount = mapsCount + 1 sz = sz + 12 + 8 + len(binData) break w.seek(0); writeInt32(w, sz) writeInt16(w, mapsCount) def readMapCache(path, cmd): if cmd == "help": printHelp() if cmd == "queststoxml": covertQuests() return elif cmd == "itemstoxml": convertItems() return elif cmd == "monsterstoxml": convertMonsters(); return elif cmd == "mercenariestoxml": convertMercenaries(); return elif cmd == "petstoxml": convertPets(); return elif cmd == "homunculusestoxml": convertHomunculuses(); return elif cmd == "skillstoxml": convertSkillsToXml(); return elif cmd == "tmxtocache": recreateMapCache(); return with open(path, "rb") as f: size = readInt32(f) if os.path.getsize(path) != size: print "Map cache corrupted, wrong file size." exit(1) mapsCount = readInt16(f) print "Maps count: " + str(mapsCount) readInt16(f) # padding if cmd == "listmaps": listMapCache(f, mapsCount) elif cmd == "extractmaps": extractMaps(f, mapsCount) elif cmd == "mapstotmx": covertToTmx(f, mapsCount) readMapCache("serverdata/db/re/map_cache.dat", detectCommand())