/
chunknorris.py
142 lines (122 loc) · 4.92 KB
/
chunknorris.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#!/usr/bin/env python
import sys
import os
import mclevel
import mclevelbase
import box
class InvalidDimensionError(RuntimeError): pass
def chunkbox(cPos):
boxpoint = (cPos[0] << 4, 0, cPos[1] << 4)
boxsize = (16, 128, 16)
return box.BoundingBox(boxpoint, boxsize)
class mclevelfixer(object):
def printUsage(self):
print "Usage: chunknorris.py WORLDDIR BACKUPDIR..."
print "You can specify more than one backup, they are tried in order."
print "Options:"
print " -h, --help You're reading it."
print " -n, --nether Fix the Nether instead of the overworld dimension"
print " -e, --end Fix the End instead of the overworld dimension"
def printUsageAndQuit(self):
self.printUsage()
raise SystemExit
def loadWorld(self, world, dimension):
worldpath = os.path.expanduser(world)
if os.path.exists(worldpath):
level = mclevel.fromFile(worldpath)
else:
level = mclevel.loadWorld(world)
if dimension is not None:
if dimension in level.dimensions:
level = level.dimensions[dimension]
else:
raise InvalidDimensionError, "Dimension {0} does not exist".format(dimension)
return level
def run(self):
dimension = None
worlds = []
programName = sys.argv.pop(0)
for arg in sys.argv:
if arg.lower() in ("-h", "--help"):
self.printUsageAndQuit()
elif arg.lower() in ("-n", "--nether"):
dimension = -1
elif arg.lower() in ("-e", "--end"):
dimension = 1
elif arg[0] == "-":
raise UsageError, "Unknown option ({0})".format(arg)
else:
worlds.append(arg)
validChunks = set()
damagedChunks = set()
# Process main level
print "Loading main level: {0}".format(worlds[0])
level = self.loadWorld(worlds[0], dimension)
print "Main level contains {0} chunks.".format(level.chunkCount)
for i, cPos in enumerate(level.allChunks, 1):
box = chunkbox(cPos)
try:
ch = level.getChunk(*cPos)
validChunks.add(cPos)
except mclevelbase.ChunkMalformed:
print "Malformed chunk: x={0},y={1},z={2}".format(*box.origin)
damagedChunks.add(cPos)
# Delete damaged chunks
for cPos in damagedChunks:
box = chunkbox(cPos)
level.deleteChunksInBox(box)
# Process backup levels
for world in worlds[1:]:
print "Loading backup level: {0}".format(world)
backup = self.loadWorld(world, dimension)
print "Backup level contains {0} chunks.".format(backup.chunkCount)
for i, cPos in enumerate(backup.allChunks, 1):
if cPos not in validChunks:
box = chunkbox(cPos)
try:
ch = backup.getChunk(*cPos)
#At this point, chunk seems to be valid
level.copyBlocksFrom(backup, box, box.origin)
validChunks.add(cPos)
if cPos in damagedChunks:
damagedChunks.remove(cPos)
print "Malformed chunk replaced with backup chunk: x={0},y={1},z={2}".format(*box.origin)
else:
print "Missing chunk replaced with backup chunk: x={0},y={1},z={2}".format(*box.origin)
except mclevelbase.ChunkMalformed:
print "Malformed chunk in backup: x={0},y={1},z={2}".format(*box.origin)
backup.close()
# Warn about chunks that are still damaged
if len(damagedChunks):
for cPos in damagedChunks:
box = chunkbox(cPos)
print "WARNING: Malformed chunk not found in any backup, deleting it: x={0},y={1},z={2}".format(*box.origin)
print "Enter Y to delete these chunks and save the level."
print "Enter N to abort."
answer = raw_input("Delete chunks and save? ")
if answer.lower() == 'y':
print "Damaged chunks deleted."
else:
print "Aborted."
level.close()
return
# Save level
print "Relighting..."
level.generateLights()
print "Saving level..."
level.saveInPlace()
# Repair region files
print "Repairing regions..."
if level.version:
level.preloadRegions()
for rf in level.regionFiles.itervalues():
rf.repair()
# Save level again
print "Saving level again..."
level.saveInPlace()
level.close()
def main(argv):
mclevelfixer().run()
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))