Refactor a lot, export directly to map.sqlite

This commit is contained in:
Novatux 2014-02-09 17:37:11 +01:00
parent bd46d3339b
commit 817196d2e4
5 changed files with 473 additions and 3 deletions

318
block.py Normal file
View File

@ -0,0 +1,318 @@
import os
import zlib
import nbt
from io import BytesIO
import sqlite3
def writeU8(os, u8):
os.write(bytes((u8&0xff,)))
def writeU16(os, u16):
os.write(bytes(((u16>>8)&0xff,)))
os.write(bytes((u16&0xff,)))
def writeU32(os, u32):
os.write(bytes(((u32>>24)&0xff,)))
os.write(bytes(((u32>>16)&0xff,)))
os.write(bytes(((u32>>8)&0xff,)))
os.write(bytes((u32&0xff,)))
def writeString(os, s):
writeU16(os, len(s))
os.write(bytes(s, "utf-8"))
def bytesToInt(b):
s = 0
for x in b:
s = (s<<8)+x
return s
class MCMap:
"""A MC map"""
def __init__(self, path):
self.world_path = os.path.join(path, "region")
self.chunk_pos = []
for ext in ["mca", "mcr"]:
filenames = [i for i in os.listdir(self.world_path)
if i.endswith("."+ext)]
if len(filenames) > 0:
self.ext = ext
break
for filename in filenames:
s = filename.split(".")
cx, cz = int(s[1])*32, int(s[2])*32
with open(os.path.join(self.world_path, filename), "rb") as f:
for chkx in range(cx, cx+32):
for chkz in range(cz, cz+32):
offset = ((chkx%32) + 32*(chkz%32))*4
f.seek(offset)
if bytesToInt(f.read(3)) != 0:
self.chunk_pos.append((chkx, chkz))
def getChunk(self, chkx, chkz):
return MCChunk(chkx, chkz, self.world_path, self.ext)
class MCChunk:
"""A 16x16 column of nodes"""
def __init__(self, chkx, chkz, path, ext):
filename = os.path.join(path,
"r.{}.{}.{}".format(chkx//32, chkz//32, ext))
with open(filename, "rb") as f:
ofs = ((chkx%32) + 32*(chkz%32))*4
f.seek(ofs)
offset = bytesToInt(f.read(3)) << 12
f.seek(offset)
length = bytesToInt(f.read(4))
compression_type = bytesToInt(f.read(1))
data = f.read(length - 1) # 1 byte for compression_type
if compression_type == 1: # Gzip
udata = zlib.decompress(data, 15+16)
elif compression_type == 2: # Zlib
udata = zlib.decompress(data)
else:
raise ValueError("Unsupported compression type")
raw_data = nbt.read(udata)['']['Level']
self.blocks = []
if ext == "mca":
# Anvil file format
for section in raw_data["Sections"]:
self.blocks.append(MCBlock(raw_data, (chkx, chkz), section["Y"], True))
else:
for yslice in range(8):
self.blocks.append(MCBlock(raw_data, (chkx, chkz), yslice, False))
class MCBlock:
"""A 16x16x16 block"""
def __init__(self, chunk, chunkpos, yslice, is_anvil=True):
self.pos = (chunkpos[0], yslice, chunkpos[1])
if is_anvil:
# Find the slice
for section in chuck["Sections"]:
if section["Y"] == yslice:
self.from_section(section)
break
else:
# No luck, we have to convert
self.from_chunk(chunk, yslice)
self.tile_entities = []
for te in chunk["TileEntities"]:
if (te["y"]>>4) == yslice:
t = te.copy()
t["y"] &= 0xf
self.tile_entities.append(t)
@staticmethod
def expand_half_bytes(l):
l2 = []
for i in l:
l2.append(i&0xf)
l2.append((i>>4)&0xf)
return l2
def from_section(self, section):
self.blocks = section["Blocks"]
self.data = self.expand_half_bytes(section["Data"])
self.sky_light = self.expand_half_bytes(section["SkyLight"])
self.block_light = self.expand_half_bytes(section["BlockLight"])
@staticmethod
def extract_slice(data, yslice):
data2 = [0]*4096
k = yslice << 4
k2 = 0
# Beware: impossible to understand code
# Sorry, but it has to be as fast as possible,
# as it is one bottleneck
# Basically: order is changed from XZY to YZX
for y in range(16):
for z in range(16):
for x in range(16):
data2[k2] = data[k]
k2 += 1
k += 2048
k = (k&0x7ff)+128
k = (k&0x7f)+1
return data2
@staticmethod
def extract_slice_half_bytes(data, yslice):
data2 = [0]*4096
k = yslice << 3
k2 = 0
k3 = 256 # One layer above the previous one
# Beware: impossible to understand code
# Even worse than before: that time we've got
# to extract half bytes at the same time
# Again, order is changed from XZY to YZX
for y in range(0, 16, 2): # 2 values for y at a time
for z in range(16):
for x in range(16):
data2[k2] = data[k]&0xf
data2[k3] = (data[k]>>4)&0xf
k2 += 1
k3 += 1
k += 1024
k = (k&0x3ff)+64
k = (k&0x3f)+1
k2 += 256 # Skip a layer
k3 += 256
return data2
def from_chunk(self, chunk, yslice):
self.blocks = self.extract_slice(chunk["Blocks"], yslice)
self.data = self.extract_slice_half_bytes(chunk["Data"], yslice)
self.sky_light = self.extract_slice_half_bytes(chunk["SkyLight"], yslice)
self.block_light = self.extract_slice_half_bytes(chunk["BlockLight"], yslice)
class MTBlock:
def __init__(self, name_id_mapping):
self.name_id_mapping = name_id_mapping
self.content = [0]*4096
self.param1 = [0]*4096
self.param2 = [0]*4096
self.metadata = {}
self.pos = (0, 0, 0)
def fromMCBlock(self, mcblock, conversion_table):
self.pos = (mcblock.pos[0], mcblock.pos[1]-4, mcblock.pos[2])
content = self.content
param1 = self.param1
param2 = self.param2
blocks = mcblock.blocks
data = mcblock.data
skylight = mcblock.sky_light
blocklight = mcblock.block_light
for i in range(4096):
content[i], param2[i] = conversion_table[blocks[i]][data[i]]
param1[i] = (max(blocklight[i], skylight[i])<<4)|blocklight[i]
def save(self):
os = BytesIO()
writeU8(os, 25) # Version
flags = 0x04
if self.pos[1] < -1:
flags |= 0x01
writeU8(os, flags)
writeU8(os, 2) # content_width
writeU8(os, 2) # params_width
cbuffer = BytesIO()
# Bulk node data
content = self.content
k = 0
for z in range(16):
for y in range(16):
for x in range(16):
writeU16(cbuffer, content[k])
k += 1
k += (256-16)
k += (16-16*256)
param1 = self.param1
k = 0
for z in range(16):
for y in range(16):
for x in range(16):
writeU8(cbuffer, param1[k])
k += 1
k += (256-16)
k += (16-16*256)
param2 = self.param2
k = 0
for z in range(16):
for y in range(16):
for x in range(16):
writeU8(cbuffer, content[k])
k += 1
k += (256-16)
k += (16-16*256)
os.write(zlib.compress(cbuffer.getvalue()))
# Nodemeta
cbuffer = BytesIO()
writeU8(cbuffer, 0) # TODO: actually store the meta
os.write(zlib.compress(cbuffer.getvalue()))
# Static objects
writeU8(os, 0) # Version
writeU16(os, 0) # Number of objects
# Timestamp
writeU32(os, 0xffffffff) # BLOCK_TIMESTAMP_UNDEFINED
# Name-ID mapping
writeU8(os, 0) # Version
writeU16(os, len(self.name_id_mapping))
for i in range(len(self.name_id_mapping)):
writeU16(os, i)
writeString(os, self.name_id_mapping[i])
# Node timer
writeU8(os, 2+4+4) # Timer data len
writeU16(os, 0) # Number of timers
return os.getvalue()
class MTMap:
def __init__(self, path):
self.world_path = path
self.blocks = []
@staticmethod
def getBlockAsInteger(p):
return p[0]+4096*(p[1]+4096*p[2])
def fromMCMap(self, mcmap, name_id_mapping, conversion_table):
num_converted = 0
num_to_convert = len(mcmap.chunk_pos)
for chkx, chkz in mcmap.chunk_pos:
if num_converted%20 == 0:
print(num_converted, num_to_convert)
#if num_converted == 100:
# break
num_converted += 1
mcblocks = mcmap.getChunk(chkx, chkz).blocks
for mcblock in mcblocks:
mtblock = MTBlock(name_id_mapping)
mtblock.fromMCBlock(mcblock, conversion_table)
self.blocks.append(mtblock)
def save(self):
conn = sqlite3.connect(self.world_path + "map.sqlite")
cur = conn.cursor()
cur.execute("CREATE TABLE IF NOT EXISTS `blocks` (\
`pos` INT NOT NULL PRIMARY KEY, `data` BLOB);")
num_saved = 0
num_to_save = len(self.blocks)
for block in self.blocks:
if num_saved%50 == 0:
print(num_saved, num_to_save)
num_saved += 1
cur.execute("INSERT INTO blocks VALUES (?,?)",
(self.getBlockAsInteger(block.pos),
block.save()))
conn.commit()
conn.close()
if __name__ == "__main__":
# Tests
from random import randrange
t = [randrange(256) for i in range(2048*8)]
assert(MCBlock.extract_slice(MCBlock.expand_half_bytes(t), 0)
== MCBlock.extract_slice_half_bytes(t, 0))
from time import time
t0 = time()
s1 = MCBlock.extract_slice(MCBlock.expand_half_bytes(t), 1)
print(time()-t0)
t0 = time()
s2 = MCBlock.extract_slice_half_bytes(t, 1)
print(time()-t0)
assert(s1 == s2)

70
content.py Normal file
View File

@ -0,0 +1,70 @@
def preprocess(lines, flags):
output = []
skip_level = 0
for line in lines:
line = line.split("//")[0].strip() # Remove comment
if line == "":
continue
if line[0] == "#":
if line.startswith("#if"):
cond = line[4:].strip()
if skip_level > 0 or cond not in flags:
skip_level += 1
elif line.startswith("#else"):
if skip_level == 0:
skip_level = 1
elif skip_level == 1:
skip_level = 0
elif line.startswith("#endif"):
if skip_level > 0:
skip_level -= 1
continue
if skip_level == 0:
output.append(line)
return output
def get_id(name_id_mapping, name):
try:
return name_id_mapping.index(name)
except:
name_id_mapping.append(name)
return len(name_id_mapping)-1
def read_content(flags):
with open("map_content.txt", "r") as f:
lines = f.readlines()
lines = preprocess(lines, flags)
name_id_mapping = ["air"]
bd = {}
for line in lines:
s = line.split("\t")
if len(s) >= 2:
r = s[1].split(" ")
if len(r) == 0:
print(line)
raise ValueError("Malformed data")
name = r[0]
param2 = 0
for i in range(1, len(r)):
if r[i] != "":
param2 = int(r[i])
break
t = s[0].split(" ")
if len(t) == 2:
for data in t[1].split(","):
key = (int(t[0]), int(data))
if key not in bd:
bd[key] = (get_id(name_id_mapping, name), param2)
elif len(t) == 1:
for data in range(16):
key = (int(t[0]), data)
if key not in bd:
bd[key] = (get_id(name_id_mapping, name), param2)
blocks_len = max([i[0] for i in bd.keys()])+1
blocks = [[(0, 0)]*16 for i in range(blocks_len)]
for (id, data), value in bd.items():
blocks[id][data] = value
return name_id_mapping, blocks

View File

@ -1,4 +1,15 @@
import zlib, gzip
import sys
from block import *
import content
mcmap = MCMap(sys.argv[1])
mtmap = MTMap(sys.argv[2])
nimap, ct = content.read_content(["MORETREES", "NETHER", "QUARTZ"])
mtmap.fromMCMap(mcmap, nimap, ct)
mtmap.save()
'''import zlib, gzip
import struct
import os
import sys
@ -236,7 +247,7 @@ def convert_block(block, yslice):
if section["Y"] == yslice:
return convert_section(section, block.get("TileEntities", []), yslice)
return None
def export_we(b, f):
blocks, param1, param2, metadata = b
L = []
@ -284,3 +295,4 @@ for key, value in blocks.items():
continue
with open(outputdir+str(key[0])+"."+str(yslice-4)+"."+str(key[1])+".we", "w") as f:
export_we(b, f)
'''

51
nbt.py Normal file
View File

@ -0,0 +1,51 @@
import struct
def _read_tag(bytes, index, tag):
if tag <= 6:
plen = (None, 1, 2, 4, 8, 4, 8)[tag]
value = struct.unpack(">"+(None, "b", "h", "i", "q", "f", "d")[tag],
bytes[index:index+plen])[0]
index += plen
return value, index
elif tag == 7:
plen, index = _read_tag(bytes, index, 3)
value = list(struct.unpack(">"+str(plen)+"B", bytes[index:index+plen]))
index += plen
return value, index
elif tag == 11:
plen, index = _read_tag(bytes, index, 3)
value = list(struct.unpack(">"+str(plen)+"i", bytes[index:index+4*plen]))
index += 4*plen
return value, index
elif tag == 8:
plen, index = _read_tag(bytes, index, 2)
value = bytes[index:index+plen].decode('utf-8')
index += plen
return value, index
elif tag == 9:
tagid = bytes[index]
index += 1
plen, index = _read_tag(bytes, index, 3)
value = []
for i in range(plen):
v, index = _read_tag(bytes, index, tagid)
value.append(v)
return value, index
elif tag == 10:
return _read_named(bytes, index)
def _read_named(bytes, index):
d = {}
while True:
if index >= len(bytes):
return d, index
tag = bytes[index]
index += 1
if tag == 0:
return d, index
name, index = _read_tag(bytes, index, 8)
value, index = _read_tag(bytes, index, tag)
d[name] = value
def read(bytes):
return _read_named(bytes, 0)[0]

View File

@ -21,6 +21,24 @@ def escape(s):
s2 += c
return s2
def convert_furnace(te):
src_time = 0
src_totaltime = 0
fuel_time = 0
fuel_totaltime = 0
infotext = "Furnace out of fuel"
meta = '''{fields={src_totaltime="'''+str(src_totaltime)+'''",\
src_time="'''+str(src_time)+'''",fuel_time="'''+str(fuel_time)+'''",\
fuel_totaltime="'''+str(fuel_totaltime)'''",\
formspec="size[8,9]image[2,2;1,1;default_furnace_fire_bg.png]\
list[current_name;fuel;2,3;1,1;]\
list[current_name;src;2,1;1,1;]\
list[current_name;dst;5,1;2,2;]\
list[current_player;main;0,5;8,4;]",\
infotext="'''+infotext+'''"},\
inventory={fuel={""},dst={"","","",""},src={""}}}'''
return None, None, meta
def convert_sign(te):
t = ""
for i in range(1, 5):
@ -36,4 +54,5 @@ def convert_sign(te):
return None, None, meta
te_convert = {"chest": convert_chest,
"sign": convert_sign}
"sign": convert_sign,
"furnace": convert_furnace}