From d1a1486c6dda9e93c034c40fca55ff12f6aa22ba Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Tue, 31 Dec 2024 21:38:17 +0200 Subject: [PATCH] add files --- .gitignore | 5 + Makefile | 13 ++ README.md | 68 ++++++ src/stage-1/backends/cpu.cpp | 135 +++++++++++ src/stage-1/backends/dummy.cpp | 13 ++ src/stage-1/backends/gpu.cpp | 216 ++++++++++++++++++ src/stage-1/revengmc.cpp | 200 ++++++++++++++++ src/stage-1/revengmc.hpp | 38 +++ .../me/topchetoeu/revengmc/Searcher.java | 50 ++++ .../topchetoeu/revengmc/core/BogusServer.java | 136 +++++++++++ .../revengmc/core/MinecraftContext.java | 58 +++++ .../me/topchetoeu/revengmc/search/Test.java | 9 + .../me/topchetoeu/revengmc/search/Tester.java | 66 ++++++ 13 files changed, 1007 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100755 src/stage-1/backends/cpu.cpp create mode 100644 src/stage-1/backends/dummy.cpp create mode 100755 src/stage-1/backends/gpu.cpp create mode 100755 src/stage-1/revengmc.cpp create mode 100755 src/stage-1/revengmc.hpp create mode 100644 src/stage-2/me/topchetoeu/revengmc/Searcher.java create mode 100644 src/stage-2/me/topchetoeu/revengmc/core/BogusServer.java create mode 100644 src/stage-2/me/topchetoeu/revengmc/core/MinecraftContext.java create mode 100644 src/stage-2/me/topchetoeu/revengmc/search/Test.java create mode 100644 src/stage-2/me/topchetoeu/revengmc/search/Tester.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7d83b7d --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/* +!/src +!/.gitignore +!/Makefile +!/README.md diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e9a9096 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +BACKEND ?= gpu +SOURCES := src/stage-1/revengmc.cpp src/stage-1/backends/${BACKEND}.cpp + +CPP_FLAGS += -Wall -fdiagnostics-color=always -g --std=c++17 +CPP_LDFLAGS += -lOpenCL + +.PHONY: gpu cpu dummy +gpu: bin/revengmc-gpu +cpu: bin/revengmc-cpu +dummy: bin/revengmc-dummy + +bin/revengmc-%: src/stage-1/revengmc.cpp src/stage-1/backends/%.cpp + g++ ${CPP_FLAGS} $^ -o $@ ${CPP_LDFLAGS} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..24b239b --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# Reveng-MC + +A project, consisting of two tools, which I used to reverse engineer (brute force) the seed of a Minecraft world, using known locations of 8 structures. + +## How to use + +To use this tool, you will need the exact location of (from my experience) at least 5-6 structures, as well as a beefy GPU. First of all, you will need to put these coordinates trough the first tool, which is `revengmc-gpu` (if you don't have a GPU and eons to spare, `revengmc-cpu` will do too). At some point, the tool should yield a seed. Note that at this point you have discovered just the lower 48-bits of the 64-bit seed. At this point, you need to either manually, or using a program, go trough the remaining 16 bits (about 60000 seeds). + +Doing that by hand can prove to be, well, a major pain, so a second tool is included. To use it, you need to setup the MCP project with minecraft 1.16.5 (haven't tested it with other versions). After that, you just need to copy the files in `src/stage-2` directly in the generated sources of MCP (should be `src/main/java`). Finally, run `me.topchetoeu.revengmc.Searcher` as main with the same input, and the program should output the correct seed (from my experience). Worst-case scenario, you will get a few seeds, but they can be weeded out by hand. + +To test, I have included the data that I used for input in the files `example/input-stage-1.txt` and `example/input-stage-2.txt` + +## Compiling + +The first program can be compiled by running `make` (or `make cpu` to get the `cpu`) executable. You will need to install the OpenCL libraries to compile it. So far, this has only been run on Arch Linux, using Nvida RTX 3060, so milage may vary. + +## Input format + +### Stage 1 program: + +CLI arguments: +1. The input filename, or none for standard input + +\[feature count] \[first seed (inclusive, may be hex)] \[last seed (exclusive, may be hex)] +\[structure name] \[structure x] \[structure y] \(repeated as many times as there are structures) + +### Stage 2 program: + +CLI arguments: +1. Lower 48 bits of seed +2. The input filename, or - for standard input + +File data: +\[structure name] \[structure x] \[structure y] \(repeated as many times as there are structures) + +### Structure names (same for both stages): +- MINESHAFT +- MINESHAFT_MESA +- MANSION +- JUNGLE_PYRAMID +- DESERT_PYRAMID +- IGLOO +- SHIPWRECK +- SHIPWRECK_BEACHED +- SWAMP_HUT +- STRONGHOLD +- MONUMENT +- OCEAN_RUIN_COLD +- OCEAN_RUIN_WARM +- FORTRESS +- NETHER_FOSSIL +- END_CITY +- BURIED_TREASURE +- BASTION_REMNANT +- VILLAGE_PLAINS +- VILLAGE_DESERT +- VILLAGE_SAVANNA +- VILLAGE_SNOWY +- VILLAGE_TAIGA +- RUINED_PORTAL +- RUINED_PORTAL_DESERT +- RUINED_PORTAL_JUNGLE +- RUINED_PORTAL_SWAMP +- RUINED_PORTAL_MOUNTAIN +- RUINED_PORTAL_OCEAN +- RUINED_PORTAL_NETHER +- PILLAGER_OUTPOST + diff --git a/src/stage-1/backends/cpu.cpp b/src/stage-1/backends/cpu.cpp new file mode 100755 index 0000000..524bf7f --- /dev/null +++ b/src/stage-1/backends/cpu.cpp @@ -0,0 +1,135 @@ +#include "../revengmc.hpp" +#include +#include +#include + +#define MAX_48 0xFFFFFFFFFFFF +#define MAGIC_NUM 25214903917 + +#ifndef THREADS +#define THREADS 32 +#endif + +#define BATCH_SIZE ((size_t)1 << 20) + +struct range_t { + size_t start; + size_t end; +}; + +struct impl_t { + pthread_mutex_t print_lock; + + pthread_t threads[THREADS]; + range_t args[THREADS]; + + std::vector conditions; + std::vector results; +}; + +static impl_t *impl; + +static uint64_t enchant(uint64_t seed) { + return (seed ^ MAGIC_NUM) & MAX_48; +} + +static uint32_t next_32(size_t &seed, uint32_t max) { + uint64_t nextseed = (seed * MAGIC_NUM + 11) & MAX_48; + seed = nextseed; + return (nextseed >> 17) % max; +} + +static bool check_condition(const condition_t &cond, uint64_t seed) { + uint64_t rand = enchant(cond.part_seed + seed); + + int32_t res_x = cond.res_x; + int32_t res_y = cond.res_y; + + if (!cond.linear_sep) { + res_x += (next_32(rand, cond.diff) + next_32(rand, cond.diff)) / 2; + res_y += (next_32(rand, cond.diff) + next_32(rand, cond.diff)) / 2; + } + else { + res_x += next_32(rand, cond.diff); + res_y += next_32(rand, cond.diff); + } + + pthread_mutex_lock(&impl->print_lock); + // std::cout << res_x << "; " << cond.x << "; " << res_y << "; " << cond.y << std::endl; + pthread_mutex_unlock(&impl->print_lock); + return res_x == cond.x && res_y == cond.y; +} + +static bool check_seed(uint64_t seed) { + for (auto it = impl->conditions.begin(); it != impl->conditions.end(); it++) { + if (!check_condition(*it, seed)) return false; + } + + return true; +} + +void result(uint64_t seed) { + pthread_mutex_lock(&impl->print_lock); + impl->results.push_back(seed); + pthread_mutex_unlock(&impl->print_lock); +} + +void check_range(range_t *pargs) { + auto &args = *pargs; + + for (size_t i = args.start; i < args.end; i++) { + if (check_seed(i)) result(i); + } +} + +void engine::init(const std::vector &tasks) { + if (impl != nullptr) { + std::cerr << "Backend already initialized"; + std::exit(1); + } + + impl = new impl_t(); + + pthread_mutex_init(&impl->print_lock, nullptr); + + impl->conditions = tasks; +} + +size_t engine::batch_size() { + return THREADS * BATCH_SIZE; +} + +const std::vector &engine::run(size_t start, size_t end) { + pthread_t threads[THREADS]; + range_t args[THREADS]; + + size_t n = end - start; + size_t group_size = n / THREADS; + + for (size_t thread_i = 0; thread_i < THREADS; thread_i++) { + args[thread_i].start = group_size * thread_i + start; + args[thread_i].end = group_size * thread_i + start + group_size; + pthread_create(&threads[thread_i], NULL, (void *(*)(void *))check_range, &args[thread_i]); + } + + for (size_t i = 0; i < THREADS; i++) { + pthread_join(threads[i], NULL); + } + + if (group_size * THREADS < n) { + auto arg = range_t { .start = group_size * THREADS + start, .end = end }; + check_range(&arg); + } + + return impl->results; +} + +void engine::free() { + if (impl == nullptr) { + std::cerr << "Backend already freed"; + std::exit(1); + } + + pthread_mutex_destroy(&impl->print_lock); + delete impl; +} diff --git a/src/stage-1/backends/dummy.cpp b/src/stage-1/backends/dummy.cpp new file mode 100644 index 0000000..4ca185f --- /dev/null +++ b/src/stage-1/backends/dummy.cpp @@ -0,0 +1,13 @@ +#include "../revengmc.hpp" + +std::vector result; + +void engine::init(const std::vector &tasks) { + +} +size_t engine::batch_size() { + return 1024 * 1024 * 1024; +} +const std::vector &engine::run(size_t start, size_t end) { + return result; +} diff --git a/src/stage-1/backends/gpu.cpp b/src/stage-1/backends/gpu.cpp new file mode 100755 index 0000000..e44ff15 --- /dev/null +++ b/src/stage-1/backends/gpu.cpp @@ -0,0 +1,216 @@ +#define CL_HPP_TARGET_OPENCL_VERSION 300 + +#include +#include +#include +#include +#include + +#include "../revengmc.hpp" + +#define MAX_48 0xFFFFFFFFFFFF +#define WORKER_COUNT 1024 +#define MAGIC_NUM 25214903917 + +// const size_t GROUP_SIZE = 1 << 10ul; +// const size_t BATCH_SIZE = 1ul << 40ul; + +const size_t GROUP_SIZE = 1 << 10; +const size_t BATCH_SIZE = 1ull << 36; + +static cl_int err; + +static void handle(int err, int skip = 0) { + if (err != CL_SUCCESS) { + std::string strerr; + + switch (err) { + case CL_DEVICE_NOT_FOUND: strerr = "CL_DEVICE_NOT_FOUND"; break; + case CL_DEVICE_NOT_AVAILABLE: strerr = "CL_DEVICE_NOT_AVAILABLE"; break; + case CL_COMPILER_NOT_AVAILABLE: strerr = "CL_COMPILER_NOT_AVAILABLE"; break; + case CL_MEM_OBJECT_ALLOCATION_FAILURE: strerr = "CL_MEM_OBJECT_ALLOCATION_FAILURE"; break; + case CL_OUT_OF_RESOURCES: strerr = "CL_OUT_OF_RESOURCES"; break; + case CL_OUT_OF_HOST_MEMORY: strerr = "CL_OUT_OF_HOST_MEMORY"; break; + case CL_PROFILING_INFO_NOT_AVAILABLE: strerr = "CL_PROFILING_INFO_NOT_AVAILABLE"; break; + case CL_MEM_COPY_OVERLAP: strerr = "CL_MEM_COPY_OVERLAP"; break; + case CL_IMAGE_FORMAT_MISMATCH: strerr = "CL_IMAGE_FORMAT_MISMATCH"; break; + case CL_IMAGE_FORMAT_NOT_SUPPORTED: strerr = "CL_IMAGE_FORMAT_NOT_SUPPORTED"; break; + case CL_BUILD_PROGRAM_FAILURE: strerr = "CL_BUILD_PROGRAM_FAILURE"; break; + case CL_MAP_FAILURE: strerr = "CL_MAP_FAILURE"; break; + case CL_MISALIGNED_SUB_BUFFER_OFFSET: strerr = "CL_MISALIGNED_SUB_BUFFER_OFFSET"; break; + case CL_COMPILE_PROGRAM_FAILURE: strerr = "CL_COMPILE_PROGRAM_FAILURE"; break; + case CL_LINKER_NOT_AVAILABLE: strerr = "CL_LINKER_NOT_AVAILABLE"; break; + case CL_LINK_PROGRAM_FAILURE: strerr = "CL_LINK_PROGRAM_FAILURE"; break; + case CL_DEVICE_PARTITION_FAILED: strerr = "CL_DEVICE_PARTITION_FAILED"; break; + case CL_KERNEL_ARG_INFO_NOT_AVAILABLE: strerr = "CL_KERNEL_ARG_INFO_NOT_AVAILABLE"; break; + case CL_INVALID_VALUE: strerr = "CL_INVALID_VALUE"; break; + case CL_INVALID_DEVICE_TYPE: strerr = "CL_INVALID_DEVICE_TYPE"; break; + case CL_INVALID_PLATFORM: strerr = "CL_INVALID_PLATFORM"; break; + case CL_INVALID_DEVICE: strerr = "CL_INVALID_DEVICE"; break; + case CL_INVALID_CONTEXT: strerr = "CL_INVALID_CONTEXT"; break; + case CL_INVALID_QUEUE_PROPERTIES: strerr = "CL_INVALID_QUEUE_PROPERTIES"; break; + case CL_INVALID_COMMAND_QUEUE: strerr = "CL_INVALID_COMMAND_QUEUE"; break; + case CL_INVALID_HOST_PTR: strerr = "CL_INVALID_HOST_PTR"; break; + case CL_INVALID_MEM_OBJECT: strerr = "CL_INVALID_MEM_OBJECT"; break; + case CL_INVALID_IMAGE_FORMAT_DESCRIPTOR: strerr = "CL_INVALID_IMAGE_FORMAT_DESCRIPTOR"; break; + case CL_INVALID_IMAGE_SIZE: strerr = "CL_INVALID_IMAGE_SIZE"; break; + case CL_INVALID_SAMPLER: strerr = "CL_INVALID_SAMPLER"; break; + case CL_INVALID_BINARY: strerr = "CL_INVALID_BINARY"; break; + case CL_INVALID_BUILD_OPTIONS: strerr = "CL_INVALID_BUILD_OPTIONS"; break; + case CL_INVALID_PROGRAM: strerr = "CL_INVALID_PROGRAM"; break; + case CL_INVALID_PROGRAM_EXECUTABLE: strerr = "CL_INVALID_PROGRAM_EXECUTABLE"; break; + case CL_INVALID_KERNEL_NAME: strerr = "CL_INVALID_KERNEL_NAME"; break; + case CL_INVALID_KERNEL_DEFINITION: strerr = "CL_INVALID_KERNEL_DEFINITION"; break; + case CL_INVALID_KERNEL: strerr = "CL_INVALID_KERNEL"; break; + case CL_INVALID_ARG_INDEX: strerr = "CL_INVALID_ARG_INDEX"; break; + case CL_INVALID_ARG_VALUE: strerr = "CL_INVALID_ARG_VALUE"; break; + case CL_INVALID_ARG_SIZE: strerr = "CL_INVALID_ARG_SIZE"; break; + case CL_INVALID_KERNEL_ARGS: strerr = "CL_INVALID_KERNEL_ARGS"; break; + case CL_INVALID_WORK_DIMENSION: strerr = "CL_INVALID_WORK_DIMENSION"; break; + case CL_INVALID_WORK_GROUP_SIZE: strerr = "CL_INVALID_WORK_GROUP_SIZE"; break; + case CL_INVALID_WORK_ITEM_SIZE: strerr = "CL_INVALID_WORK_ITEM_SIZE"; break; + case CL_INVALID_GLOBAL_OFFSET: strerr = "CL_INVALID_GLOBAL_OFFSET"; break; + case CL_INVALID_EVENT_WAIT_LIST: strerr = "CL_INVALID_EVENT_WAIT_LIST"; break; + case CL_INVALID_EVENT: strerr = "CL_INVALID_EVENT"; break; + case CL_INVALID_OPERATION: strerr = "CL_INVALID_OPERATION"; break; + case CL_INVALID_GL_OBJECT: strerr = "CL_INVALID_GL_OBJECT"; break; + case CL_INVALID_BUFFER_SIZE: strerr = "CL_INVALID_BUFFER_SIZE"; break; + case CL_INVALID_MIP_LEVEL: strerr = "CL_INVALID_MIP_LEVEL"; break; + case CL_INVALID_GLOBAL_WORK_SIZE: strerr = "CL_INVALID_GLOBAL_WORK_SIZE"; break; + case CL_INVALID_PROPERTY: strerr = "CL_INVALID_PROPERTY"; break; + case CL_INVALID_IMAGE_DESCRIPTOR: strerr = "CL_INVALID_IMAGE_DESCRIPTOR"; break; + case CL_INVALID_COMPILER_OPTIONS: strerr = "CL_INVALID_COMPILER_OPTIONS"; break; + case CL_INVALID_LINKER_OPTIONS: strerr = "CL_INVALID_LINKER_OPTIONS"; break; + case CL_INVALID_DEVICE_PARTITION_COUNT: strerr = "CL_INVALID_DEVICE_PARTITION_COUNT"; break; + default: strerr = "UNKNOWN"; + } + + std::cerr << "Error " << strerr << "(" << err << ")!\n"; + std::exit(err); + } +} +static void handle() { + handle(err, 1); +} + +static std::string read_file(const std::string &path) { + std::ifstream f { path }; + if (f.fail()) return nullptr; + + f.seekg(0, std::ios_base::end); + size_t n = f.tellg(); + f.seekg(0, std::ios_base::beg); + + auto buff = new char[n + 1]; + buff[n] = 0; + f.read(buff, n); + + std::string res { buff }; + return res; +} + +static cl::Context make_context() { + // cl::Platform platform = cl::Platform::get(&err); handle(); + // cl_context_properties props[] = { CL_CONTEXT_PLATFORM, (cl_context_properties)platform(), 0 }; + // auto context = cl::Context(CL_DEVICE_TYPE_ALL, props, nullptr, nullptr, &err); + auto context = cl::Context::getDefault(&err); + handle(); + + return context; +} + +struct impl_t { + cl::Context ctx; + cl::Kernel kernel; + cl::CommandQueue queue; + cl::Buffer buffer; + cl::Event event; + + std::vector conditions; + + std::vector results; +}; + +struct args_t { + uint64_t task_n; + uint64_t start; + uint64_t group_size; +}; + +static impl_t *impl; + +void engine::init(const std::vector &tasks) { + if (impl != nullptr) { + std::cerr << "Backend already initialized"; + std::exit(1); + } + + impl = new impl_t(); + impl->ctx = make_context(); + impl->conditions = tasks; + + auto device = impl->ctx.getInfo()[0]; + + impl->queue = cl::CommandQueue(impl->ctx, device, 0, &err); handle(); + + size_t size = sizeof(condition_t) * impl->conditions.size(); + impl->buffer = cl::Buffer(impl->ctx, CL_MEM_READ_WRITE, size, nullptr, &err); handle(); + handle(impl->queue.enqueueWriteBuffer(impl->buffer, true, 0, size, &impl->conditions[0], nullptr, &impl->event)); + impl->event.wait(); + + auto program = cl::Program(impl->ctx, read_file("revengmc-kernel.cl")); handle(); + auto build_err = program.build(); + + if (build_err != CL_SUCCESS) { + auto build_log = program.getBuildInfo(&err); handle(); + std::cerr << build_log[0].second; + std::exit(1); + } + + impl->kernel = cl::Kernel(program, "run", &err); handle(); +} + +size_t engine::batch_size() { + return BATCH_SIZE; +} + +const std::vector &engine::run(size_t start, size_t end) { + size_t n = end - start; + size_t groups = n / GROUP_SIZE; + if (groups < GROUP_SIZE) groups = 0; + + cl::Event event; + handle(impl->kernel.setArg(0, args_t { + .task_n = impl->conditions.size(), + .start = start, + .group_size = GROUP_SIZE, + })); + handle(impl->kernel.setArg(1, impl->buffer)); + handle(impl->queue.enqueueNDRangeKernel(impl->kernel, cl::NullRange, cl::NDRange(groups * GROUP_SIZE), cl::NDRange(GROUP_SIZE), nullptr, &event)); + event.wait(); + + if (groups * GROUP_SIZE < n) { + cl::Event event; + + handle(impl->kernel.setArg(0, args_t { + .task_n = impl->conditions.size(), + .start = start + groups * GROUP_SIZE, + .group_size = 1, + })); + handle(impl->kernel.setArg(1, impl->buffer)); + handle(impl->queue.enqueueNDRangeKernel(impl->kernel, cl::NullRange, cl::NDRange(n - groups * GROUP_SIZE), cl::NDRange(1), nullptr, &event)); + event.wait(); + } + + return impl->results; +} + + +void engine::free() { + if (impl == nullptr) { + std::cerr << "Backend already freed"; + std::exit(1); + } + + delete impl; +} diff --git a/src/stage-1/revengmc.cpp b/src/stage-1/revengmc.cpp new file mode 100755 index 0000000..75093c3 --- /dev/null +++ b/src/stage-1/revengmc.cpp @@ -0,0 +1,200 @@ +#include +#include +#include +#include +#include + +#include "./revengmc.hpp" + +#define MAX_48 0xFFFFFFFFFFFF +#define WORKER_COUNT 1024 +#define MAGIC_NUM 25214903917 + +class noop_stream final : public std::ostream { +private: + class buffer final : public std::streambuf { + protected: + [[nodiscard]] auto overflow(int_type ch) noexcept -> int_type override { + return ch; + } + }; + + buffer os_buffer_{}; + +public: + noop_stream() noexcept : std::ostream(&os_buffer_) { } +}; + +condition_t condition_t::make(const feature_t &feat, int32_t x, int32_t y) { + condition_t res; + + res.x = x >> 4; + res.y = y >> 4; + + int32_t scaled_x = std::floor((double_t)res.x / feat.spacing); + int32_t scaled_y = std::floor((double_t)res.y / feat.spacing); + + res.diff = feat.spacing - feat.separation; + res.part_seed = (uint64_t)scaled_x * 341873128712L + (uint64_t)scaled_y * 132897987541L + (uint64_t)feat.salt; + + res.res_x = scaled_x * feat.spacing; + res.res_y = scaled_y * feat.spacing; + + res.linear_sep = feat.linear_sep; + + return res; +} + +const std::map FEATURES = { + { "MINESHAFT", { 1, 0, 0 } }, + { "MINESHAFT_MESA", { 1, 0, 0 } }, + { "MANSION", { 80, 20, 10387319, false } }, + { "JUNGLE_PYRAMID", { 32, 8, 14357619 } }, + { "DESERT_PYRAMID", { 32, 8, 14357617 } }, + { "IGLOO", { 32, 8, 14357618 } }, + { "SHIPWRECK", { 24, 4, 165745295 } }, + { "SHIPWRECK_BEACHED", { 24, 4, 165745295 } }, + { "SWAMP_HUT", { 32, 8, 14357620 } }, + { "STRONGHOLD", { 1, 0, 0 } }, + { "MONUMENT", { 32, 5, 10387313, false } }, + { "OCEAN_RUIN_COLD", { 20, 8, 14357621 } }, + { "OCEAN_RUIN_WARM", { 20, 8, 14357621 } }, + { "FORTRESS", { 27, 4, 30084232 } }, + { "NETHER_FOSSIL", { 2, 1, 14357921 } }, + { "END_CITY", { 20, 11, 10387313, false } }, + { "BURIED_TREASURE", { 1, 0, 0 } }, + { "BASTION_REMNANT", { 27, 4, 30084232 } }, + { "VILLAGE_PLAINS", { 32, 8, 10387312 } }, + { "VILLAGE_DESERT", { 32, 8, 10387312 } }, + { "VILLAGE_SAVANNA", { 32, 8, 10387312 } }, + { "VILLAGE_SNOWY", { 32, 8, 10387312 } }, + { "VILLAGE_TAIGA", { 32, 8, 10387312 } }, + { "RUINED_PORTAL", { 40, 15, 34222645 } }, + { "RUINED_PORTAL_DESERT", { 40, 15, 34222645 } }, + { "RUINED_PORTAL_JUNGLE", { 40, 15, 34222645 } }, + { "RUINED_PORTAL_SWAMP", { 40, 15, 34222645 } }, + { "RUINED_PORTAL_MOUNTAIN", { 40, 15, 34222645 } }, + { "RUINED_PORTAL_OCEAN", { 40, 15, 34222645 } }, + { "RUINED_PORTAL_NETHER", { 40, 15, 34222645 } }, + { "PILLAGER_OUTPOST", { 32, 8, 165745296 } }, +}; + + + +size_t percent = 0; +bool running; + +std::string read_file(const std::string &path) { + std::ifstream f { path }; + if (f.fail()) return nullptr; + + f.seekg(0, std::ios_base::end); + size_t n = f.tellg(); + f.seekg(0, std::ios_base::beg); + + auto buff = new char[n + 1]; + buff[n] = 0; + f.read(buff, n); + + std::string res { buff }; + return res; +} + +void read_input(std::istream &in, std::ostream &out, std::vector &tasks, size_t &start, size_t &end) { + size_t task_n; + + std::string start_str, end_str; + + out << "Feature count: "; + in >> task_n; + + out << "Seed range: "; + in >> start_str >> end_str; + + if (start_str.find("0x") == 0) sscanf(start_str.c_str(), "%lx", &start); + else sscanf(start_str.c_str(), "%lu", &start); + + if (end_str.find("0x") == 0) sscanf(end_str.c_str(), "%lx", &end); + else sscanf(end_str.c_str(), "%lu", &end); + + for (size_t i = 0; i < task_n; i++) { + std::string name; + int32_t x, y; + + out << "Structure " << i + 1 << " name, X, Y: "; + in >> name >> x >> y; + + auto it = FEATURES.find(name); + if (it == FEATURES.end()) { + std::cerr << "No structure named " << name << " found!\n"; + std::exit(1); + } + + tasks.push_back(condition_t::make(it->second, x, y)); + } +} + +std::string stringify_time(size_t time) { + time /= 1000; + int seconds = time % 60; + int minutes = (time / 60) % 60; + int hours = (time / 3600) % 24; + int days = time / (3600 * 24); + + char res[256]; + snprintf(res, sizeof res, "%d:%.2d:%.2d:%.2d", days, hours, minutes, seconds); + return std::string(res); +} + +int main(int argc, char **argv) { + std::vector tasks; + size_t start, end; + + if (argc >= 2) { + auto in = std::ifstream(argv[1]); + auto out = noop_stream(); + if (in.fail()) { + std::cerr << "Invalid input file provided!"; + return -1; + } + + read_input(in, out, tasks, start, end); + } + else { + read_input(std::cin, std::cerr, tasks, start, end); + } + + engine::init(tasks); + size_t batch_size = engine::batch_size(); + size_t elapsed = 0; + + for (size_t curr_start = start; curr_start <= end;) { + auto time_start = std::chrono::high_resolution_clock::now(); + + size_t curr_end = std::min(curr_start + batch_size, end + 1); + + size_t done = curr_start - start; + size_t all = end - start + 1; + float percent = (float)done / all * 100.f; + + size_t estimated = done == 0 ? 0 : ((size_t)(elapsed / (double)done * all)); + fprintf( + stderr, "%.4f%%, remaining: %s, elapsed: %s, estimated: %s, %lX out of %lX done, current batch: %lX - %lX\n", + percent, + stringify_time(estimated - elapsed).c_str(), stringify_time(elapsed).c_str(), stringify_time(estimated).c_str(), + done, all, curr_start, curr_end - 1 + ); + + auto &seeds = engine::run(curr_start, curr_end); + for (auto it = seeds.begin(); it != seeds.end(); it++) { + fprintf(stderr, "SEED FOUND! %lu\n", *it); + } + + curr_start = curr_end; + + auto time_end = std::chrono::high_resolution_clock::now(); + elapsed += std::chrono::duration_cast(time_end - time_start).count(); + } + + engine::free(); +} diff --git a/src/stage-1/revengmc.hpp b/src/stage-1/revengmc.hpp new file mode 100755 index 0000000..068970c --- /dev/null +++ b/src/stage-1/revengmc.hpp @@ -0,0 +1,38 @@ +#include +#include +#include +#include + +#define MAX_48 0xFFFFFFFFFFFF +#define WORKER_COUNT 1024 +#define MAGIC_NUM 25214903917 + +struct feature_t { + int32_t spacing; + int32_t separation; + int32_t salt; + bool linear_sep = true; + + feature_t(int32_t spacing, int32_t separation, int32_t salt, bool linear_sep = true) { + this->spacing = spacing; + this->separation = separation; + this->salt = salt; + this->linear_sep = linear_sep; + } +}; +struct condition_t { + int32_t x, res_x, y, res_y, diff; + uint64_t part_seed; + bool linear_sep; + + static condition_t make(const feature_t &feat, int32_t x, int32_t y); +}; + +extern const std::map FEATURES; + +namespace engine { + void init(const std::vector &tasks); + void free(); + size_t batch_size(); + const std::vector &run(size_t start, size_t end); +} diff --git a/src/stage-2/me/topchetoeu/revengmc/Searcher.java b/src/stage-2/me/topchetoeu/revengmc/Searcher.java new file mode 100644 index 0000000..82bac92 --- /dev/null +++ b/src/stage-2/me/topchetoeu/revengmc/Searcher.java @@ -0,0 +1,50 @@ +package me.topchetoeu.revengmc; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.util.ArrayList; +import java.util.Scanner; + +import me.topchetoeu.revengmc.core.MinecraftContext; +import me.topchetoeu.revengmc.search.Test; +import me.topchetoeu.revengmc.search.Tester; +import net.minecraft.data.BuiltinRegistries; +import net.minecraft.resources.ResourceLocation; + +public class Searcher { + public static void main(String[] args) throws Throwable { + var ctx = MinecraftContext.initialize(); + + + var rootSeed = Long.parseLong(args[0]); + var file = new Scanner(args[1].equals("-") ? new FileInputStream(args[1]) : System.in); + var tests = new ArrayList(); + + while (file.hasNextLine()) { + var line = file.nextLine(); + var parts = line.split(" "); + if (parts.length < 3) continue; + + var res = BuiltinRegistries.CONFIGURED_STRUCTURE_FEATURE.get(new ResourceLocation(parts[0].toLowerCase())); + var x = Math.floorDiv(Integer.parseInt(parts[1]), 16); + var y = Math.floorDiv(Integer.parseInt(parts[2]), 16); + + tests.add(new Test(res, x, y)); + } + file.close(); + + loop: for (var i = 0; i < 65536; i++) { + long seed = rootSeed | ((long)i << 48l); + try (var tester = new Tester(ctx, seed)) { + for (var test : tests) { + if (!test.test(tester)) continue loop; + } + + System.out.println("SEED " + seed); + } + } + + System.exit(0); + } +} diff --git a/src/stage-2/me/topchetoeu/revengmc/core/BogusServer.java b/src/stage-2/me/topchetoeu/revengmc/core/BogusServer.java new file mode 100644 index 0000000..e7ea953 --- /dev/null +++ b/src/stage-2/me/topchetoeu/revengmc/core/BogusServer.java @@ -0,0 +1,136 @@ +package me.topchetoeu.revengmc.core; + +import java.io.IOException; +import java.net.Proxy; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; + +import javax.annotation.Nullable; + +import com.mojang.authlib.GameProfile; +import com.mojang.serialization.Lifecycle; + +import net.minecraft.CrashReport; +import net.minecraft.SystemReport; +import net.minecraft.core.MappedRegistry; +import net.minecraft.core.Registry; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.progress.LoggerChunkProgressListener; +import net.minecraft.server.players.PlayerList; +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.world.Difficulty; +import net.minecraft.world.level.DataPackConfig; +import net.minecraft.world.level.GameRules; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelSettings; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; +import net.minecraft.world.level.levelgen.WorldGenSettings; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; +import net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess; + +// God i love this word. Perfectly sums up Notch's coding skills +public class BogusServer extends MinecraftServer { + private static final LevelSettings BOGUS_SETTINGS = new LevelSettings("Bogus Level", GameType.CREATIVE, false, Difficulty.NORMAL, true, new GameRules(), DataPackConfig.DEFAULT); + + public final ServerLevel level; + + public BogusServer(MinecraftContext ctx, long seed) throws IOException { + this( + ctx, seed, LevelStorageSource.createDefault(Path.of("levels")), + ctx.registry().registryOrThrow(Registry.NOISE_GENERATOR_SETTINGS_REGISTRY), + ctx.registry().registryOrThrow(Registry.BIOME_REGISTRY), + ctx.registry().registryOrThrow(Registry.DIMENSION_TYPE_REGISTRY) + ); + } + + @SuppressWarnings("null") + private BogusServer( + MinecraftContext ctx, + long seed, LevelStorageSource levelStorageSrc, + Registry noiseGenerators, + Registry biomes, + Registry dimensionTypes + ) throws IOException { + super( + null, ctx.registry(), levelStorageSrc.createAccess("world-" + seed), + new PrimaryLevelData( + BOGUS_SETTINGS, + new WorldGenSettings( + seed, + true, false, + WorldGenSettings.withOverworld( + dimensionTypes, + new MappedRegistry<>(Registry.LEVEL_STEM_REGISTRY, Lifecycle.experimental()), + WorldGenSettings.makeDefaultOverworld(biomes, noiseGenerators, seed) + ) + ), + Lifecycle.stable() + ), + ctx.packRepo(), + Proxy.NO_PROXY, DataFixers.getDataFixer(), + ctx.resources(), null, null, null, + LoggerChunkProgressListener::new + ); + + + this.setPlayerList(new PlayerList(this, this.registryHolder, this.playerDataStorage, 1) {}); + this.level = new ServerLevel( + this, r -> r.run(), this.levelStorage(), + this.getWorldData().overworldData(), + Level.OVERWORLD, ctx.overworldLocation(), + new LoggerChunkProgressListener(0), + this.getWorldData().worldGenSettings().overworld(), + false, seed, List.of(), false + ); + } + + public LevelStorageAccess levelStorage() { return this.storageSource; } + + @Override public boolean initServer() throws IOException { + // this.createLevels(new LoggerChunkProgressListener(0)); + return true; + } + @Override public SystemReport fillServerSystemReport(@Nullable SystemReport report) { return report; } + @Override public void onServerCrash(@Nullable CrashReport report) { System.exit(1); } + @Override public boolean isHardcore() { return false; } + @Override public int getOperatorUserPermissionLevel() { return 0; } + @Override public int getFunctionCompilationLevel() { return 4; } + @Override public boolean shouldRconBroadcast() { return false; } + @Override public boolean isDedicatedServer() { return false; } + @Override public int getRateLimitPacketsPerSecond() { return 0; } + @Override public boolean isEpollEnabled() { return false; } + @Override public boolean isCommandBlockEnabled() { return true; } + @Override public boolean isPublished() { return false; } + @Override public boolean shouldInformAdmins() { return false; } + @Override public boolean isSingleplayerOwner(@Nullable GameProfile profile) { return false; } + @Override public Optional getModdedStatus() { return Optional.empty(); } + @SuppressWarnings("null") + @Override public void close() { + try { + if (getConnection() != null) getConnection().stop(); + getPlayerList().removeAll(); + + for (var level : this.getAllLevels()) { + if (level != null) level.close(); + } + + if (this.getSnooper().isStarted()) { + this.getSnooper().interrupt(); + } + + this.storageSource.close(); + } + catch (RuntimeException e) { throw e; } + catch (Throwable e) { throw new RuntimeException(e); } + } + public void close(boolean full) { + if (full) super.close(); + else this.close(); + } +} \ No newline at end of file diff --git a/src/stage-2/me/topchetoeu/revengmc/core/MinecraftContext.java b/src/stage-2/me/topchetoeu/revengmc/core/MinecraftContext.java new file mode 100644 index 0000000..fcdb952 --- /dev/null +++ b/src/stage-2/me/topchetoeu/revengmc/core/MinecraftContext.java @@ -0,0 +1,58 @@ +package me.topchetoeu.revengmc.core; + +import java.io.File; + +import net.minecraft.CrashReport; +import net.minecraft.SharedConstants; +import net.minecraft.Util; +import net.minecraft.commands.Commands.CommandSelection; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.server.Bootstrap; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.ServerResources; +import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.repository.FolderRepositorySource; +import net.minecraft.server.packs.repository.PackRepository; +import net.minecraft.server.packs.repository.PackSource; +import net.minecraft.server.packs.repository.ServerPacksSource; +import net.minecraft.world.level.DataPackConfig; +import net.minecraft.world.level.dimension.DimensionType; + +public record MinecraftContext( + RegistryAccess.RegistryHolder registry, + PackRepository packRepo, + ServerResources resources, + DimensionType overworldLocation +) { + public static MinecraftContext initialize() throws Throwable { + SharedConstants.tryDetectVersion(); + + CrashReport.preload(); + Bootstrap.bootStrap(); + Bootstrap.validate(); + + var registry = RegistryAccess.builtin(); + + var packRepo = new PackRepository( + PackType.SERVER_DATA, new ServerPacksSource(), + new FolderRepositorySource(new File("world/datapacks"), PackSource.WORLD) + ); + + MinecraftServer.configurePackRepository(packRepo, DataPackConfig.DEFAULT, true); + + var resources = ServerResources.loadResources( + packRepo.openAllSelected(), + registry, + CommandSelection.DEDICATED, 4, + Util.backgroundExecutor(), + Runnable::run + ).get(); + resources.updateGlobals(); + + var overworldLocation = registry.registryOrThrow(Registry.DIMENSION_TYPE_REGISTRY).get(DimensionType.OVERWORLD_LOCATION); + if (overworldLocation == null) throw new RuntimeException("wtf"); + + return new MinecraftContext(registry, packRepo, resources, overworldLocation); + } +} \ No newline at end of file diff --git a/src/stage-2/me/topchetoeu/revengmc/search/Test.java b/src/stage-2/me/topchetoeu/revengmc/search/Test.java new file mode 100644 index 0000000..6f16456 --- /dev/null +++ b/src/stage-2/me/topchetoeu/revengmc/search/Test.java @@ -0,0 +1,9 @@ +package me.topchetoeu.revengmc.search; + +import net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature; + +public record Test(ConfiguredStructureFeature structure, int x, int y) { + public boolean test(Tester tester) { + return tester.placementPossible(structure, x, y); + } +} \ No newline at end of file diff --git a/src/stage-2/me/topchetoeu/revengmc/search/Tester.java b/src/stage-2/me/topchetoeu/revengmc/search/Tester.java new file mode 100644 index 0000000..46b4e26 --- /dev/null +++ b/src/stage-2/me/topchetoeu/revengmc/search/Tester.java @@ -0,0 +1,66 @@ +package me.topchetoeu.revengmc.search; + +import java.io.IOException; + +import me.topchetoeu.revengmc.core.BogusServer; +import me.topchetoeu.revengmc.core.MinecraftContext; +import net.minecraft.core.SectionPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.StructureFeatureManager; +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.chunk.ProtoChunk; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature; +import net.minecraft.world.level.levelgen.structure.StructureStart; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager; + +public class Tester implements AutoCloseable { + public final ServerLevel level; + public final long seed; + public final StructureFeatureManager features; + public final StructureManager structures; + public final ChunkGenerator chunkGen; + public final BiomeSource biomeSrc; + public final BogusServer server; + public final MinecraftContext ctx; + + public boolean placementPossible(ConfiguredStructureFeature structure, int chunkX, int chunkY) { + var chunkPos = new ChunkPos(chunkX, chunkY); + var chunk = new ProtoChunk(chunkPos, UpgradeData.EMPTY, level); + var biome = biomeSrc.getPrimaryBiome(chunk.getPos()); + + var sectionPos = SectionPos.bottomOf(chunk); + var start = features.getStartForFeature(sectionPos, structure.feature, chunk); + int refs = start != null ? start.getReferences() : 0; + var config = chunkGen.getSettings().getConfig(structure.feature); + + if (config != null) { + var newStart = structure.generate(ctx.registry(), chunkGen, biomeSrc, structures, seed, chunkPos, biome, refs, config, level); + if (newStart == StructureStart.INVALID_START) return false; + features.setStartForFeature(sectionPos, structure.feature, newStart, chunk); + return true; + } + + return false; + } + + public Tester(MinecraftContext ctx, long seed) throws IOException { + this.server = new BogusServer(ctx, seed); + this.ctx = ctx; + + this.level = server.level; + this.seed = seed; + + this.chunkGen = level.getChunkSource().getGenerator(); + this.features = level.structureFeatureManager(); + this.structures = level.getStructureManager(); + this.biomeSrc = chunkGen.getBiomeSource(); + } + + @Override public void close() throws IOException { + level.close(); + server.close(); + } +} \ No newline at end of file