From 8aa6f08959c55708994a69fc06a11a07023a81b2 Mon Sep 17 00:00:00 2001 From: "madmaxoft@gmail.com" Date: Wed, 1 May 2013 17:27:17 +0000 Subject: BlockZapper: Initial import, can zap blocks but not entities git-svn-id: http://mc-server.googlecode.com/svn/trunk@1439 0a769ca7-a7f5-676a-18bf-c427514a06d6 --- Tools/BlockZapper/BlockZapper.cpp | 97 ++++++++ Tools/BlockZapper/BlockZapper.sln | 34 +++ Tools/BlockZapper/BlockZapper.txt | 19 ++ Tools/BlockZapper/BlockZapper.vcproj | 313 +++++++++++++++++++++++++ Tools/BlockZapper/GNUmakefile | 185 +++++++++++++++ Tools/BlockZapper/Globals.cpp | 10 + Tools/BlockZapper/Globals.h | 14 ++ Tools/BlockZapper/Regions.cpp | 167 +++++++++++++ Tools/BlockZapper/Regions.h | 58 +++++ Tools/BlockZapper/Zapper.cpp | 440 +++++++++++++++++++++++++++++++++++ Tools/BlockZapper/Zapper.h | 80 +++++++ 11 files changed, 1417 insertions(+) create mode 100644 Tools/BlockZapper/BlockZapper.cpp create mode 100644 Tools/BlockZapper/BlockZapper.sln create mode 100644 Tools/BlockZapper/BlockZapper.txt create mode 100644 Tools/BlockZapper/BlockZapper.vcproj create mode 100644 Tools/BlockZapper/GNUmakefile create mode 100644 Tools/BlockZapper/Globals.cpp create mode 100644 Tools/BlockZapper/Globals.h create mode 100644 Tools/BlockZapper/Regions.cpp create mode 100644 Tools/BlockZapper/Regions.h create mode 100644 Tools/BlockZapper/Zapper.cpp create mode 100644 Tools/BlockZapper/Zapper.h (limited to 'Tools') diff --git a/Tools/BlockZapper/BlockZapper.cpp b/Tools/BlockZapper/BlockZapper.cpp new file mode 100644 index 000000000..fb2050d56 --- /dev/null +++ b/Tools/BlockZapper/BlockZapper.cpp @@ -0,0 +1,97 @@ + +// BlockZapper.cpp + +// Implements the main app entrypoint + +#include "Globals.h" + +#include + +#include "Regions.h" +#include "Zapper.h" + + + + + +#ifdef _MSC_VER + // Under MSVC, link to WinSock2 (needed by FastNBT's byteswapping) + #pragma comment(lib, "ws2_32.lib") +#endif + + + + + +void ShowHelp(const char * a_ProgramFullName) +{ + AString ProgramName(a_ProgramFullName); + size_t idx = ProgramName.rfind(cFile::PathSeparator); + if (idx != AString::npos) + { + ProgramName.erase(0, idx + 1); + } + printf("Tool written by _Xoft(o), code is public domain.\n"); + printf("Usage:\n"); + printf("%s [-w ]\n", ProgramName.c_str()); + printf("Zaps blocks and / or entities in specified regions.\n"); + printf("Regions are read from stdin, the format is:\n"); + printf(" x1 x2 y1 y2 z1 z2 [B|E|BE]\n"); + printf("B or no specifier zaps blocks only\n"); + printf("E zaps entities only\n"); + printf("BE zaps blocks and entities\n"); + printf("MCA files are searched in the ; if not specified, in the current folder.\n"); +} + + + + + +int main(int argc, char * argv[]) +{ + new cMCLogger; // Create a new logger, it will assign itself as the main logger instance + + AString MCAFolder = "."; + for (int i = 1; i < argc; i++) + { + if (strcmp(argv[i], "-w") == 0) + { + if (i < argc - 1) + { + MCAFolder = argv[i + 1]; + } + i++; + } + else if ( + (strcmp(argv[i], "help") == 0) || + (strcmp(argv[i], "-?") == 0) || + (strcmp(argv[i], "/?") == 0) || + (strcmp(argv[i], "-h") == 0) || + (strcmp(argv[i], "--help") == 0) + ) + { + ShowHelp(argv[0]); + return 0; + } + } + + cRegions Regions; + + /* + // DEBUG: Read input from a file instead of stdin: + std::fstream fs("test_in.txt"); + Regions.Read(fs); + //*/ + + Regions.Read(std::cin); + + cZapper Zapper(MCAFolder); + Zapper.ZapRegions(Regions.GetAll()); + + LOGINFO("Done"); + return 0; +} ; + + + + diff --git a/Tools/BlockZapper/BlockZapper.sln b/Tools/BlockZapper/BlockZapper.sln new file mode 100644 index 000000000..fd8029ed0 --- /dev/null +++ b/Tools/BlockZapper/BlockZapper.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual C++ Express 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BlockZapper", "BlockZapper.vcproj", "{CE317695-CCCC-4B11-B07B-21729A110FC2}" + ProjectSection(ProjectDependencies) = postProject + {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA} = {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\..\..\mc-server.clean\VC2008\zlib.vcproj", "{EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release profiled|Win32 = Release profiled|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CE317695-CCCC-4B11-B07B-21729A110FC2}.Debug|Win32.ActiveCfg = Debug|Win32 + {CE317695-CCCC-4B11-B07B-21729A110FC2}.Debug|Win32.Build.0 = Debug|Win32 + {CE317695-CCCC-4B11-B07B-21729A110FC2}.Release profiled|Win32.ActiveCfg = Release|Win32 + {CE317695-CCCC-4B11-B07B-21729A110FC2}.Release profiled|Win32.Build.0 = Release|Win32 + {CE317695-CCCC-4B11-B07B-21729A110FC2}.Release|Win32.ActiveCfg = Release|Win32 + {CE317695-CCCC-4B11-B07B-21729A110FC2}.Release|Win32.Build.0 = Release|Win32 + {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Debug|Win32.ActiveCfg = Debug|Win32 + {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Debug|Win32.Build.0 = Debug|Win32 + {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Release profiled|Win32.ActiveCfg = Release profiled|Win32 + {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Release profiled|Win32.Build.0 = Release profiled|Win32 + {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Release|Win32.ActiveCfg = Release|Win32 + {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Tools/BlockZapper/BlockZapper.txt b/Tools/BlockZapper/BlockZapper.txt new file mode 100644 index 000000000..af6b94054 --- /dev/null +++ b/Tools/BlockZapper/BlockZapper.txt @@ -0,0 +1,19 @@ + +// BlockZapper.txt + +/* +This project implements a simple tool that can "zap" blocks out of an Anvil-stored MineCraft world. +It is usually used by server admins when their servers fail with a bug and store an invalid block in the world. +This tool takes a coord triplet and a radius triplet and replaces all blocks within the (new york-metric) radius of the coords with air. +The triplets pair is given on stdin, and multiple such specifiers are allowed, each on a separate file. +If the specifier line ends with an additional " E", entities within that radius are zapped instead of blocks +If the specifier line ends with an additional " BE", both blocks and entities are zapped. + +The tool is aware of extended blocktypes (256 .. 4096). + +The source code for this tool is public domain, but note that it depends on a few shared sources in MCServer that may be under other licenses. +*/ + + + + diff --git a/Tools/BlockZapper/BlockZapper.vcproj b/Tools/BlockZapper/BlockZapper.vcproj new file mode 100644 index 000000000..195cdafbe --- /dev/null +++ b/Tools/BlockZapper/BlockZapper.vcproj @@ -0,0 +1,313 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tools/BlockZapper/GNUmakefile b/Tools/BlockZapper/GNUmakefile new file mode 100644 index 000000000..edd5f9009 --- /dev/null +++ b/Tools/BlockZapper/GNUmakefile @@ -0,0 +1,185 @@ +################################################### +# +# Makefile for BlockZapper +# Creator: xoft +# +################################################### +# +# Info: +# This makefile is gnu-make spacific, other make systems needn't understand it +# This makefile generates include-file dependencies into *.d files in each build and then reuses these dependencies in the following builds +# +# Usage: +# To make a release build, call "make" +# To make a debug build, call "make debug=1" +# +################################################### + +# +# Macros +# + +CC = /usr/bin/g++ + + +all: BlockZapper + + + + + +################################################### +# Set the variables used for compiling, based on the build mode requested: +# CC_OPTIONS ... options for the C code compiler +# CXX_OPTIONS ... options for the C++ code compiler +# LNK_OPTIONS ... options for the linker +# LNK_LIBS ... libraries to link in +# -- according to http://stackoverflow.com/questions/6183899/undefined-reference-to-dlopen, libs must come after all sources +# BUILDDIR ... folder where the intermediate object files are built + +LNK_LIBS = -lstdc++ -ldl -lz + +ifeq ($(debug),1) +################ +# debug build - fully traceable by gdb in C++ code, slowest +# Since C code is used only for supporting libraries (zlib, lua), it is still O3-optimized +################ +CC_OPTIONS = -s -ggdb -g -D_DEBUG -O3 +CXX_OPTIONS = -s -ggdb -g -D_DEBUG +LNK_OPTIONS = -pthread -g -ggdb +BUILDDIR = build/debug/ + +else +ifeq ($(profile),1) +################ +# profile build - a release build with symbols and profiling engine built in +################ +CC_OPTIONS = -s -g -ggdb -O3 -pg -DNDEBUG +CXX_OPTIONS = -s -g -ggdb -O3 -pg -DNDEBUG +LNK_OPTIONS = -pthread -ggdb -O3 -pg +BUILDDIR = build/profile/ + +else +ifeq ($(pedantic),1) +################ +# pedantic build - basically a debug build with lots of warnings +################ +CC_OPTIONS = -s -g -ggdb -D_DEBUG -Wall -Wextra -pedantic -ansi -Wno-long-long +CXX_OPTIONS = -s -g -ggdb -D_DEBUG -Wall -Wextra -pedantic -ansi -Wno-long-long +LNK_OPTIONS = -pthread -ggdb +BUILDDIR = build/pedantic/ + +else +################ +# release build - fastest run-time, no detailed gdb support +################ +CC_OPTIONS = -s -g -O3 -DNDEBUG +CXX_OPTIONS = -s -g -O3 -DNDEBUG +LNK_OPTIONS = -pthread -O3 +BUILDDIR = build/release/ +endif +endif +endif + + + +################ +# 32-bit build override in 64-bit build environments +# - so that BearBin doesn't need to modify his makefile after each makefile change :) +################ +ifeq ($(addm32),1) +CC_OPTIONS += -m32 +CXX_OPTIONS += -m32 +LNK_OPTIONS += -m32 +endif + + + +################################################### +# INCLUDE directories +# + +INCLUDE = -I.\ + -I../../source\ + -I../../zlib-1.2.7\ + + + + + +################################################### +# Build BlockZapper +# + +SOURCES := $(shell find . '(' -name '*.cpp' -o -name '*.c' ')') + +SHAREDSOURCES := \ + source/Log.cpp \ + source/MCLogger.cpp \ + source/Noise.cpp \ + source/StringCompression.cpp \ + source/StringUtils.cpp \ + source/OSSupport/CriticalSection.cpp \ + source/OSSupport/File.cpp \ + source/OSSupport/IsThread.cpp \ + source/OSSupport/MakeDir.cpp \ + source/WorldStorage/FastNBT.cpp + +SHAREDSOURCES := $(filter-out %minigzip.c,$(SHAREDSOURCES)) + +OBJECTS := $(patsubst %.c,$(BUILDDIR)%.o,$(SOURCES)) +OBJECTS := $(patsubst %.cpp,$(BUILDDIR)%.o,$(OBJECTS)) + +SHAREDOBJECTS := $(patsubst %.c,$(BUILDDIR)%.o,$(SHAREDSOURCES)) +SHAREDOBJECTS := $(patsubst %.cpp,$(BUILDDIR)%.o,$(SHAREDOBJECTS)) + +-include $(patsubst %.o,%.d,$(OBJECTS)) +-include $(patsubst %.o,%.d,$(SHAREDOBJECTS)) + +BlockZapper : $(OBJECTS) $(SHAREDOBJECTS) + $(CC) $(LNK_OPTIONS) $(OBJECTS) $(SHAREDOBJECTS) $(LNK_LIBS) -o BlockZapper + +clean : + rm -rf $(BUILDDIR) BlockZapper + +install : MCServer + cp MCServer MCServer + + + + + +################################################### +# Build the parts of BlockZapper +# +# options used: +# -x c ... compile as C code +# -c ... compile but do not link +# -MM ... generate a list of includes + +$(BUILDDIR)%.o: %.c + @mkdir -p $(dir $@) + $(CC) $(CC_OPTIONS) -x c -c $(INCLUDE) $< -o $@ + @$(CC) $(CC_OPTIONS) -x c -MM $(INCLUDE) $< > $(patsubst %.o,%.d,$@) + @mv -f $(patsubst %.o,%.d,$@) $(patsubst %.o,%.d,$@).tmp + @sed -e "s|.*:|$(BUILDDIR)$*.o:|" < $(patsubst %.o,%.d,$@).tmp > $(patsubst %.o,%.d,$@) + @sed -e 's/.*://' -e 's/\\$$//' < $(patsubst %.o,%.d,$@).tmp | fmt -1 | sed -e 's/^ *//' -e 's/$$/:/' >> $(patsubst %.o,%.d,$@) + @rm -f $(patsubst %.o,%.d,$@).tmp + +$(BUILDDIR)%.o: %.cpp + @mkdir -p $(dir $@) + $(CC) $(CXX_OPTIONS) -c $(INCLUDE) $< -o $@ + @$(CC) $(CXX_OPTIONS) -MM $(INCLUDE) $< > $(patsubst %.o,%.d,$@) + @mv -f $(patsubst %.o,%.d,$@) $(patsubst %.o,%.d,$@).tmp + @sed -e "s|.*:|$(BUILDDIR)$*.o:|" < $(patsubst %.o,%.d,$@).tmp > $(patsubst %.o,%.d,$@) + @sed -e 's/.*://' -e 's/\\$$//' < $(patsubst %.o,%.d,$@).tmp | fmt -1 | sed -e 's/^ *//' -e 's/$$/:/' >> $(patsubst %.o,%.d,$@) + @rm -f $(patsubst %.o,%.d,$@).tmp + +$(BUILDDIR)source/%.o: ../../source/%.cpp + @mkdir -p $(dir $@) + $(CC) $(CXX_OPTIONS) -c $(INCLUDE) $< -o $@ + @$(CC) $(CXX_OPTIONS) -MM $(INCLUDE) $< > $(patsubst %.o,%.d,$@) + @mv -f $(patsubst %.o,%.d,$@) $(patsubst %.o,%.d,$@).tmp + @sed -e "s|.*:|$(BUILDDIR)$*.o:|" < $(patsubst %.o,%.d,$@).tmp > $(patsubst %.o,%.d,$@) + @sed -e 's/.*://' -e 's/\\$$//' < $(patsubst %.o,%.d,$@).tmp | fmt -1 | sed -e 's/^ *//' -e 's/$$/:/' >> $(patsubst %.o,%.d,$@) + @rm -f $(patsubst %.o,%.d,$@).tmp diff --git a/Tools/BlockZapper/Globals.cpp b/Tools/BlockZapper/Globals.cpp new file mode 100644 index 000000000..31440fd44 --- /dev/null +++ b/Tools/BlockZapper/Globals.cpp @@ -0,0 +1,10 @@ + +// Globals.cpp + +// Used for precompiled header generation in MSVC + +#include "Globals.h" + + + + diff --git a/Tools/BlockZapper/Globals.h b/Tools/BlockZapper/Globals.h new file mode 100644 index 000000000..e90fa96bd --- /dev/null +++ b/Tools/BlockZapper/Globals.h @@ -0,0 +1,14 @@ + +// Globals.h + +// This file is used for precompiled header generation in MSVC + + + + + +#include "../../source/Globals.h" + + + + diff --git a/Tools/BlockZapper/Regions.cpp b/Tools/BlockZapper/Regions.cpp new file mode 100644 index 000000000..b4c8bddcd --- /dev/null +++ b/Tools/BlockZapper/Regions.cpp @@ -0,0 +1,167 @@ + +// Regions.cpp + +// Implements the cRegions class representing the list of regions to zap + +#include "Globals.h" + +#include "Regions.h" + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cRegion: + +cRegion::cRegion(void) : + m_MinX(0), + m_MaxX(0), + m_MinY(0), + m_MaxY(0), + m_MinZ(0), + m_MaxZ(0), + m_ShouldZapBlocks(false), + m_ShouldZapEntities(false) +{ +} + + + + + +cRegion::cRegion(int a_MinX, int a_MaxX, int a_MinY, int a_MaxY, int a_MinZ, int a_MaxZ, bool a_ShouldZapBlocks, bool a_ShouldZapEntities) : + m_MinX(a_MinX), + m_MaxX(a_MaxX), + m_MinY(std::max(0, std::min(255, a_MinY))), + m_MaxY(std::max(0, std::min(255, a_MaxY))), + m_MinZ(a_MinZ), + m_MaxZ(a_MaxZ), + m_ShouldZapBlocks(a_ShouldZapBlocks), + m_ShouldZapEntities(a_ShouldZapEntities) +{ +} + + + + + +bool cRegion::TouchesChunk(int a_ChunkX, int a_ChunkZ) const +{ + int ChunkBeginX = a_ChunkX * 16; + int ChunkEndX = a_ChunkX * 16 + 15; + int ChunkBeginZ = a_ChunkZ * 16; + int ChunkEndZ = a_ChunkZ * 16 + 15; + if ( + (m_MinX > ChunkEndX) || (m_MaxX < ChunkBeginX) || + (m_MinZ > ChunkEndZ) || (m_MaxZ < ChunkBeginZ) + ) + { + return false; + } + return true; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cRegions: + +void cRegions::Read(std::istream & a_Stream) +{ + while (!a_Stream.eof()) + { + AString Line; + std::getline(a_Stream, Line); + + // Process the line + AStringVector Split = StringSplit(Line, " \t"); + AStringVector NonEmpty; + for (AStringVector::const_iterator itr = Split.begin(), end = Split.end(); itr != end; ++itr) + { + if (!itr->empty()) + { + NonEmpty.push_back(*itr); + } + } // for itr - Split[] + switch (NonEmpty.size()) + { + case 6: + case 7: + { + AddRegion(NonEmpty); + break; + } + default: + { + fprintf(stderr, "Cannot parse line \"%s\", ignoring", Line.c_str()); + break; + } + } + } +} + + + + + +void cRegions::AddRegion(const AStringVector & a_Split) +{ + ASSERT((a_Split.size() == 6) || (a_Split.size() == 7)); + + int Coords[6]; + for (int i = 0; i < 6; i++) + { + Coords[i] = atoi(a_Split[i].c_str()); + if ((Coords[i] == 0) && (a_Split[i] != "0")) + { + fprintf(stderr, "Bad coord: \"%s\". Ignoring line.", a_Split[i].c_str()); + return; + } + } // for i - a_Split[] + + bool ShouldZapBlocks = true; + bool ShouldZapEntities = false; + + if (a_Split.size() == 7) + { + AString Upper = a_Split[6]; + StrToUpper(Upper); + if (Upper == "E") + { + ShouldZapEntities = true; + ShouldZapBlocks = false; + } + else if (Upper == "BE") + { + ShouldZapEntities = true; + } + else if (Upper == "B") + { + // Nothing needed + } + else + { + fprintf(stderr, "Bad zap specifier: \"%s\". Ignoring line.", a_Split[6].c_str()); + return; + } + } + + // Swap coords, if needed: + for (int i = 0; i < 3; i++) + { + if (Coords[2 * i] > Coords[2 * i + 1]) + { + std::swap(Coords[2 * i], Coords[2 * i + 1]); + } + } + + // Store the region + m_Regions.push_back(cRegion(Coords[0], Coords[1], Coords[2], Coords[3], Coords[4], Coords[5], ShouldZapBlocks, ShouldZapEntities)); +} + + + + diff --git a/Tools/BlockZapper/Regions.h b/Tools/BlockZapper/Regions.h new file mode 100644 index 000000000..d959200d2 --- /dev/null +++ b/Tools/BlockZapper/Regions.h @@ -0,0 +1,58 @@ + +// Regions.h + +// Declares the cRegions class representing individual regions to zap + + + + + +#pragma once + +#include + + + + + +struct cRegion +{ + int m_MinX, m_MaxX; + int m_MinY, m_MaxY; + int m_MinZ, m_MaxZ; + + bool m_ShouldZapBlocks; + bool m_ShouldZapEntities; + + cRegion(void); + cRegion(int a_MinX, int a_MaxX, int a_MinY, int a_MaxY, int a_MinZ, int a_MaxZ, bool a_ShouldZapBlocks, bool a_ShouldZapEntities); + + bool TouchesChunk(int a_ChunkX, int a_ChunkZ) const; +} ; + +typedef std::vector cRegionVector; + + + + + +class cRegions +{ +public: + /// Reads the list of regions from the specified stream + void Read(std::istream & a_Stream); + + /// Returns all regions in this container + const cRegionVector & GetAll(void) const { return m_Regions; } + +protected: + cRegionVector m_Regions; + + /// Adds a new region based on the contents of the split line. The split must already be the correct size + void AddRegion(const AStringVector & a_Split); + +} ; + + + + diff --git a/Tools/BlockZapper/Zapper.cpp b/Tools/BlockZapper/Zapper.cpp new file mode 100644 index 000000000..d5bc576ba --- /dev/null +++ b/Tools/BlockZapper/Zapper.cpp @@ -0,0 +1,440 @@ + +// Zapper.cpp + +// Implements the cZapper class representing the processor that actually zaps blocks and entities + +#include "Globals.h" + +#include "WorldStorage/FastNBT.h" +#include "StringCompression.h" +#include "zlib.h" + +#include "Zapper.h" + + + + + +/// The maximum size of an inflated chunk; raw chunk data is 192 KiB, allow 64 KiB more of entities +#define CHUNK_INFLATE_MAX 256 KiB + + + + + +cZapper::cZapper(const AString & a_MCAFolder) : + m_MCAFolder(a_MCAFolder) +{ +} + + + + + +void cZapper::ZapRegions(const cRegionVector & a_Regions) +{ + for (cRegionVector::const_iterator itr = a_Regions.begin(), end = a_Regions.end(); itr != end; ++itr) + { + int MinAnvX, MinAnvZ; + int MaxAnvX, MaxAnvZ; + BlockToMCA(itr->m_MinX, itr->m_MinZ, MinAnvX, MinAnvZ); + BlockToMCA(itr->m_MaxX, itr->m_MaxZ, MaxAnvX, MaxAnvZ); + for (int x = MinAnvX; x <= MaxAnvX; x++) + { + for (int z = MinAnvZ; z <= MaxAnvZ; z++) + { + ZapRegionInMCAFile(*itr, x, z); + } + } + } // for itr - a_Regions +} + + + + + +void cZapper::BlockToMCA(int a_BlockX, int a_BlockZ, int & a_MCAX, int & a_MCAZ) +{ + // These need to be arithmetic shifts, consult your compiler documentation to see if it's so + // MSVC and GCC both use arithmetic shifts + a_MCAX = a_BlockX >> 10; + a_MCAZ = a_BlockZ >> 10; +} + + + + + +void cZapper::BlockToChunk(int a_BlockX, int a_BlockZ, int & a_ChunkX, int & a_ChunkZ) +{ + // These need to be arithmetic shifts, consult your compiler documentation to see if it's so + // MSVC and GCC both use arithmetic shifts + a_ChunkX = a_BlockX >> 4; + a_ChunkZ = a_BlockZ >> 4; +} + + + + + +void cZapper::ZapRegionInMCAFile(const cRegion & a_Region, int a_MCAX, int a_MCAZ) +{ + cFile fIn; + AString FileNameIn = Printf("%s/r.%d.%d.mca", m_MCAFolder.c_str(), a_MCAX, a_MCAZ); + if (!fIn.Open(FileNameIn, cFile::fmRead)) + { + return; + } + cFile fOut; + AString FileNameOut = Printf("%s/r.%d.%d.zap", m_MCAFolder.c_str(), a_MCAX, a_MCAZ); + if (!fOut.Open(FileNameOut, cFile::fmWrite)) + { + fprintf(stderr, "Cannot open temporary file \"%s\" for writing, skipping file \"%s\".", FileNameOut.c_str(), FileNameIn.c_str()); + return; + } + + AString DataOut; + DataOut.reserve(fIn.GetSize()); + + int HeaderIn[2048]; + if (fIn.Read(HeaderIn, sizeof(HeaderIn)) != sizeof(HeaderIn)) + { + fprintf(stderr, "Cannot read header from file \"%s\", skipping file.", FileNameIn.c_str()); + } + int HeaderOut[2048]; + for (int i = 0; i < 1024; i++) + { + if (HeaderIn[i] == 0) + { + // Chunk not present + HeaderOut[i] = 0; + continue; + } + AString ChunkData; + int ChunkX = a_MCAX * ChunksPerMCAX + (i % ChunksPerMCAX); + int ChunkZ = a_MCAZ * ChunksPerMCAZ + (i / ChunksPerMCAX); + + LoadChunkData(fIn, HeaderIn[i], ChunkData, ChunkX, ChunkZ); + + if (a_Region.TouchesChunk(ChunkX, ChunkZ)) + { + ZapRegionInRawChunkData(a_Region, ChunkData, ChunkX, ChunkZ); + } + unsigned char ChunkHeader[5]; + size_t DataSize = ChunkData.size() + 1; + ChunkHeader[0] = (DataSize >> 24) & 0xff; + ChunkHeader[1] = (DataSize >> 16) & 0xff; + ChunkHeader[2] = (DataSize >> 8) & 0xff; + ChunkHeader[3] = DataSize & 0xff; + ChunkHeader[4] = 2; // zlib compression + size_t Alignment = 4096 - (ChunkData.size() + 5) % 4096; // 5 bytes of the header are appended outside of ChunkData + if (Alignment > 0) + { + ChunkData.append(Alignment, (char)0); + } + HeaderOut[i] = htonl(((DataOut.size() / 4096 + 2) << 8) | ((ChunkData.size() + 5) / 4096)); + DataOut.append((const char *)ChunkHeader, sizeof(ChunkHeader)); + DataOut.append(ChunkData); + } // for i - chunks in fIn + for (int i = 1024; i < 2048; i++) + { + HeaderOut[i] = HeaderIn[i]; + } + fIn.Close(); + fOut.Write(HeaderOut, sizeof(HeaderOut)); + fOut.Write(DataOut.data(), DataOut.size()); + fOut.Close(); + cFile::Delete(FileNameIn); + cFile::Rename(FileNameOut, FileNameIn); +} + + + + + +void cZapper::LoadChunkData(cFile & a_InFile, int a_ChunkHeaderValue, AString & a_ChunkData, int a_ChunkX, int a_ChunkZ) +{ + a_ChunkHeaderValue = ntohl(a_ChunkHeaderValue); // Convert from big-endian to system-endian + int ChunkOffset = (a_ChunkHeaderValue >> 8) * 4096; + int ChunkSize = (a_ChunkHeaderValue & 0xff) * 4096; + a_InFile.Seek(ChunkOffset); + unsigned char ChunkHeader[5]; + a_InFile.Read(ChunkHeader, sizeof(ChunkHeader)); + if (ChunkHeader[4] != 2) + { + fprintf(stderr, "Chunk [%d, %d] is compressed in an unknown scheme (%d), skipping", a_ChunkX, a_ChunkZ, ChunkHeader[5]); + return; + } + int ActualSize = (ChunkHeader[0] << 24) | (ChunkHeader[1] << 16) | (ChunkHeader[2] << 8) | ChunkHeader[3]; + ActualSize -= 1; // Compression took 1 byte + a_ChunkData.resize(ActualSize); + int BytesRead = a_InFile.Read((void *)(a_ChunkData.data()), ActualSize); + if (BytesRead != ActualSize) + { + fprintf(stderr, "Chunk is truncated in file (%d bytes out of %d), skipping.", BytesRead, ActualSize); + a_ChunkData.clear(); + return; + } +} + + + + + +void cZapper::ZapRegionInRawChunkData(const cRegion & a_Region, AString & a_ChunkData, int a_ChunkX, int a_ChunkZ) +{ + // Decompress the data: + char Uncompressed[CHUNK_INFLATE_MAX]; + z_stream strm; + strm.zalloc = (alloc_func)NULL; + strm.zfree = (free_func)NULL; + strm.opaque = NULL; + inflateInit(&strm); + strm.next_out = (Bytef *)Uncompressed; + strm.avail_out = sizeof(Uncompressed); + strm.next_in = (Bytef *)a_ChunkData.data(); + strm.avail_in = a_ChunkData.size(); + int res = inflate(&strm, Z_FINISH); + inflateEnd(&strm); + if (res != Z_STREAM_END) + { + fprintf(stderr, "Chunk [%d, %d] failed to decompress: error %d. Skipping chunk.", a_ChunkX, a_ChunkZ, res); + return; + } + + /* + // DEBUG: Output src to a file: + cFile f1; + if (f1.Open(Printf("chunk_%d_%d_in.nbt", a_ChunkX, a_ChunkZ), cFile::fmWrite)) + { + f1.Write(Uncompressed, strm.total_out); + } + //*/ + + cParsedNBT NBT(Uncompressed, strm.total_out); + if (!NBT.IsValid()) + { + fprintf(stderr, "Chunk [%d, %d] failed to parse. Skipping chunk.", a_ChunkX, a_ChunkZ); + return; + } + ZapRegionInNBTChunk(a_Region, NBT, a_ChunkX, a_ChunkZ); + + cFastNBTWriter Writer; + for (int ch = NBT.GetFirstChild(0); ch >= 0; ch = NBT.GetNextSibling(ch)) + { + SerializeNBTTag(NBT, ch, Writer); + } + Writer.Finish(); + + /* + // DEBUG: Output dst to a file: + cFile f2; + if (f2.Open(Printf("chunk_%d_%d_out.nbt", a_ChunkX, a_ChunkZ), cFile::fmWrite)) + { + f2.Write(Writer.GetResult().data(), Writer.GetResult().size()); + } + //*/ + + // Compress the serialized data into "Uncompressed" (reuse buffer) + CompressString(Writer.GetResult().data(), Writer.GetResult().size(), a_ChunkData); +} + + + + + +void cZapper::ZapRegionInNBTChunk(const cRegion & a_Region, cParsedNBT & a_NBT, int a_ChunkX, int a_ChunkZ) +{ + int LevelTag = a_NBT.FindChildByName(a_NBT.GetRoot(), "Level"); + if (LevelTag < 0) + { + fprintf(stderr, "Cannot find Level tag in chunk [%d, %d]'s NBT. Skipping chunk.", a_ChunkX, a_ChunkZ); + return; + } + + // Create a copy of the region and limit it to the current chunk: + int BlockX = a_ChunkX * 16; + int BlockZ = a_ChunkZ * 16; + cRegion Local; + Local.m_MinX = std::max(0, a_Region.m_MinX - BlockX); + Local.m_MaxX = std::min(15, a_Region.m_MaxX - BlockX); + Local.m_MinY = a_Region.m_MinY; + Local.m_MaxY = a_Region.m_MaxY; + Local.m_MinZ = std::max(0, a_Region.m_MinZ - BlockZ); + Local.m_MaxZ = std::min(15, a_Region.m_MaxZ - BlockZ); + + if (a_Region.m_ShouldZapBlocks) + { + int SectionsTag = a_NBT.FindChildByName(LevelTag, "Sections"); + if (SectionsTag < 0) + { + fprintf(stderr, "Cannot find the Sections tag in the Level tag in chunk [%d, %d]'s NBT. Skipping block-zapping in chunk.", a_ChunkX, a_ChunkZ); + return; + } + ZapRegionBlocksInNBT(Local, a_NBT, SectionsTag); + } + + if (a_Region.m_ShouldZapEntities) + { + int EntitiesTag = a_NBT.FindChildByName(LevelTag, "Entities"); + if (EntitiesTag < 0) + { + fprintf(stderr, "Cannot find the Entities tag in the Level tag in chunk [%d, %d]'s NBT. Skipping entity-zapping in chunk.", a_ChunkX, a_ChunkZ); + return; + } + ZapRegionEntitiesInNBT(Local, a_NBT, EntitiesTag); + } +} + + + + + +void cZapper::ZapRegionBlocksInNBT(const cRegion & a_Region, cParsedNBT & a_NBT, int a_SectionsTag) +{ + for (int Child = a_NBT.GetFirstChild(a_SectionsTag); Child >= 0; Child = a_NBT.GetNextSibling(Child)) + { + int y = 0; + int SectionY = a_NBT.FindChildByName(Child, "Y"); + if ((SectionY < 0) || (a_NBT.GetType(SectionY) != TAG_Byte)) + { + continue; + } + y = a_NBT.GetByte(SectionY); + if ((y * 16 > a_Region.m_MaxY) || (y * 16 + 16 < a_Region.m_MinY)) + { + continue; + } + int BlockDataTag = a_NBT.FindChildByName(Child, "Blocks"); + int BlockMetaTag = a_NBT.FindChildByName(Child, "Data"); + int BlockAddTag = a_NBT.FindChildByName(Child, "Add"); + if (BlockDataTag > 0) + { + ZapRegionInNBTSectionBytes(a_Region, y, (unsigned char *)(a_NBT.GetData(BlockDataTag))); + } + if (BlockMetaTag > 0) + { + ZapRegionInNBTSectionNibbles(a_Region, y, (unsigned char *)(a_NBT.GetData(BlockMetaTag))); + } + if (BlockAddTag > 0) + { + ZapRegionInNBTSectionNibbles(a_Region, y, (unsigned char *)(a_NBT.GetData(BlockAddTag))); + } + } // for Child - Level/Sections/[] +} + + + + + +void cZapper::ZapRegionInNBTSectionBytes(const cRegion & a_Region, int a_SectionY, unsigned char * a_BlockBytes) +{ + int MinY = std::max(0, a_Region.m_MinY - a_SectionY * 16); + int MaxY = std::min(15, a_Region.m_MaxY - a_SectionY * 16); + ASSERT(MinY >= 0); + ASSERT(MaxY >= 0); + for (int y = MinY; y <= MaxY; y++) + { + for (int z = a_Region.m_MinZ; z <= a_Region.m_MaxZ; z++) + { + for (int x = a_Region.m_MinX; x <= a_Region.m_MaxX; x++) + { + a_BlockBytes[x + z * 16 + y * 16 * 16] = 0; + } + } + } +} + + + + + +void cZapper::ZapRegionInNBTSectionNibbles(const cRegion & a_Region, int a_SectionY, unsigned char * a_BlockNibbles) +{ + int MinY = std::max(0, a_Region.m_MinY - a_SectionY * 16); + int MaxY = std::min(15, a_Region.m_MaxY - a_SectionY * 16); + ASSERT(MinY >= 0); + ASSERT(MaxY >= 0); + for (int y = MinY; y <= MaxY; y++) + { + for (int z = a_Region.m_MinZ; z < a_Region.m_MaxZ; z++) + { + for (int x = a_Region.m_MinX; x < a_Region.m_MaxX; x++) + { + cChunkDef::SetNibble(a_BlockNibbles, x, y, z, 0); + } + } + } +} + + + + + +void cZapper::ZapRegionEntitiesInNBT(const cRegion & a_Region, cParsedNBT & a_NBT, int a_EntitiesTag) +{ + // TODO +} + + + + + +void cZapper::SerializeNBTTag(const cParsedNBT & a_NBT, int a_Tag, cFastNBTWriter & a_Writer) +{ + switch (a_NBT.GetType(a_Tag)) + { + case TAG_Byte: a_Writer.AddByte (a_NBT.GetName(a_Tag), a_NBT.GetByte (a_Tag)); break; + case TAG_Short: a_Writer.AddShort (a_NBT.GetName(a_Tag), a_NBT.GetShort (a_Tag)); break; + case TAG_Int: a_Writer.AddInt (a_NBT.GetName(a_Tag), a_NBT.GetInt (a_Tag)); break; + case TAG_Long: a_Writer.AddLong (a_NBT.GetName(a_Tag), a_NBT.GetLong (a_Tag)); break; + case TAG_Float: a_Writer.AddFloat (a_NBT.GetName(a_Tag), a_NBT.GetFloat (a_Tag)); break; + case TAG_Double: a_Writer.AddDouble (a_NBT.GetName(a_Tag), a_NBT.GetDouble(a_Tag)); break; + case TAG_ByteArray: a_Writer.AddByteArray(a_NBT.GetName(a_Tag), a_NBT.GetData (a_Tag), a_NBT.GetDataLength(a_Tag)); break; + case TAG_String: a_Writer.AddString (a_NBT.GetName(a_Tag), a_NBT.GetString(a_Tag)); break; + case TAG_IntArray: + { + std::vector Data; + int NumInts = a_NBT.GetDataLength(a_Tag) / 4; + Data.reserve(NumInts); + int * OrigData = (int *)(a_NBT.GetData(a_Tag)); + for (int i = 0; i < NumInts; i++) + { + Data.push_back(ntohl(OrigData[i])); + } + a_Writer.AddIntArray (a_NBT.GetName(a_Tag), &Data.front(), Data.size()); break; + } + + case TAG_List: + { + a_Writer.BeginList(a_NBT.GetName(a_Tag), a_NBT.GetChildrenType(a_Tag)); + for (int ch = a_NBT.GetFirstChild(a_Tag); ch >= 0; ch = a_NBT.GetNextSibling(ch)) + { + SerializeNBTTag(a_NBT, ch, a_Writer); + } // for ch - children[] + a_Writer.EndList(); + break; + } + + case TAG_Compound: + { + a_Writer.BeginCompound(a_NBT.GetName(a_Tag)); + for (int ch = a_NBT.GetFirstChild(a_Tag); ch >= 0; ch = a_NBT.GetNextSibling(ch)) + { + SerializeNBTTag(a_NBT, ch, a_Writer); + } // for ch - children[] + a_Writer.EndCompound(); + break; + } + + default: + { + ASSERT(!"Unknown NBT tag"); + break; + } + } +} + + + + diff --git a/Tools/BlockZapper/Zapper.h b/Tools/BlockZapper/Zapper.h new file mode 100644 index 000000000..585c3a5ca --- /dev/null +++ b/Tools/BlockZapper/Zapper.h @@ -0,0 +1,80 @@ + +// Zapper.h + +// Declares the cZapper class representing the processor that actually zaps blocks and entities + + + + + +#pragma once + +#include "Regions.h" + + + + + +// fwd: ParsedNBT.h +class cParsedNBT; +class cFastNBTWriter; + + + + + +class cZapper +{ +public: + cZapper(const AString & a_MCAFolder); + + /// Zaps all the specified regions + void ZapRegions(const cRegionVector & a_Regions); + +protected: + static const int BlocksPerChunkX = 16; + static const int BlocksPerChunkZ = 16; + static const int ChunksPerMCAX = 32; + static const int ChunksPerMCAZ = 32; + + AString m_MCAFolder; + + /// Converts from block coords to MCA coords + void BlockToMCA(int a_BlockX, int a_BlockZ, int & a_MCAX, int & a_MCAZ); + + /// Converts from block coords to chunk coords + void BlockToChunk(int a_BlockX, int a_BlockZ, int & a_ChunkX, int & a_ChunkZ); + + /// Zaps the specified region in the MCA file with the specified MCA coords + void ZapRegionInMCAFile(const cRegion & a_Region, int a_MCAX, int a_MCAZ); + + /** Loads raw compressed chunk data from the specified file + * chunk is specified by ChunkHeaderValue, which is the int describing the chunk in file header. + */ + void LoadChunkData(cFile & a_InFile, int a_ChunkHeaderValue, AString & a_ChunkData, int a_ChunkX, int a_ChunkZ); + + /// Zaps the specified region in the raw (compressed) chunk data. + void ZapRegionInRawChunkData(const cRegion & a_Region, AString & a_ChunkData, int a_ChunkX, int a_ChunkZ); + + /// Zaps the specified region in the specified NBT structure + void ZapRegionInNBTChunk(const cRegion & a_Region, cParsedNBT & a_NBT, int a_ChunkX, int a_ChunkZ); + + /// Zaps the blocks in the specified region from the specified NBT + void ZapRegionBlocksInNBT(const cRegion & a_Region, cParsedNBT & a_NBT, int a_SectionsTag); + + /// Zaps the blocks in the specified bytes (types) from one vertical section (16^3 blocks) of a chunk. + void ZapRegionInNBTSectionBytes(const cRegion & a_Region, int a_SectionY, unsigned char * a_BlockBytes); + + /// Zaps the blocks in the specified nibbles (meta, add) from one vertical section (16^3 blocks) of a chunk. + void ZapRegionInNBTSectionNibbles(const cRegion & a_Region, int a_SectionY, unsigned char * a_BlockNibbles); + + /// Zaps entities in the specified region from the specified NBT + void ZapRegionEntitiesInNBT(const cRegion & a_Region, cParsedNBT & a_NBT, int a_EntitiesTag); + + /// Serializes the NBT subtree into a writer + void SerializeNBTTag(const cParsedNBT & a_NBT, int a_Tag, cFastNBTWriter & a_Writer); +} ; + + + + -- cgit v1.2.3