From bd355043bde9ba14b64de209d49d0af0c164dccf Mon Sep 17 00:00:00 2001 From: atxi Date: Wed, 18 Jan 2023 23:10:54 -0500 Subject: [PATCH] Add launch arguments and update readme - Add building and running instructions to readme. - Allow username and server to be configured using launch arguments. - Switch to using blocks-1.19.3.json instead of blocks.json so it doesn't try to load the wrong blocks file in the future. - Don't load vulkan debug extension in release mode. --- .gitignore | 2 +- README.md | 43 ++++++- polymer/asset/block_assets.cpp | 5 + polymer/connection.cpp | 21 +--- polymer/connection.h | 4 +- polymer/gamestate.h | 2 +- polymer/main.cpp | 192 +++++++++++++++++++++++++++++--- polymer/render/block_mesher.cpp | 1 + polymer/render/render.cpp | 4 +- 9 files changed, 237 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index 9adeede..ecb894f 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,7 @@ bench_results /out/build/x64-Debug cxx-17 /extras/bazel_usage_example/bazel-* /clang -blocks.json +blocks*.json /x64 *.spv *.jar diff --git a/README.md b/README.md index e350801..9ce76b1 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,46 @@ It uses the original assets from a user-provided Minecraft jar. Only basic block ### Screenshots ![Polymer Image](https://i.imgur.com/rAfkvtd.png) +### Running +Only runs on Windows currently. + +- Requires `1.19.3.jar` from your Minecraft installation to be in the working directory. You can find this in `%appdata%/.minecraft/versions/1.19.3/`. This is used to load the textures and block models. +- Requires `blocks-1.19.3.json` that is generated from running Minecraft with a [certain flag](https://wiki.vg/Data_Generators#Generators) or from the [polymer release page](https://github.com/atxi/Polymer/releases). +- Requires compiled shaders. Get them from the release page or read the shader section below if manually building. + +Running the exe will connect to localhost with the username 'polymer'. The server must be configured to be in offline mode. + +You can specify the username and server ip by command line. +`polymer.exe -u username -s 127.0.0.1:25565` + +Currently only a spectator camera is implemented for flying around and rendering the world. By default, you will be in the survival gamemode on the server. If you want chunks to load as you move, you need to put yourself in spectator gamemode. You can do this in the server terminal or with another client connected. + ### Building Currently only compiles on Windows, but other platforms are planned. -- C++ compiler (tested with MSVC and Clang) -- CMake -- Vulkan SDK +#### Requirements +- C++ compiler (tested with MSVC 2022 and Clang) +- [CMake](https://cmake.org/) +- [Vulkan SDK](https://www.lunarg.com/vulkan-sdk/) + +#### CMake Instructions +##### GUI +- Open CMake GUI. +- Put in path to source directory. +- Choose a directory where the build will go. +- Press Configure. +- Select the generator to use. Probably `Visual Studio 17 2022` on Windows. +- Press Finish. +- Press Generate. +##### Terminal +- Create 'build' sub-directory and open terminal in it. +- `cmake .. -G "Visual Studio 17 2022"` + +#### MSVC +- Browse to the build folder created from CMake. +- Open solution. +- Set to Release and x64 in the Standard Toolbar. +- Build or run. + +#### Shaders +The shaders must be compiled with glslc.exe that comes with the vulkan sdk. The `compile_shaders.bat` file will compile them if `%VULKAN_SDK%` is properly set in the environment variables. diff --git a/polymer/asset/block_assets.cpp b/polymer/asset/block_assets.cpp index 57dae21..3b65c3a 100644 --- a/polymer/asset/block_assets.cpp +++ b/polymer/asset/block_assets.cpp @@ -533,6 +533,11 @@ static u32 GetHighestStateId(json_object_s* root) { bool AssetParser::ParseBlocks(MemoryArena* perm_arena, const char* blocks_filename) { FILE* f = fopen(blocks_filename, "r"); + + if (!f) { + return false; + } + fseek(f, 0, SEEK_END); long file_size = ftell(f); fseek(f, 0, SEEK_SET); diff --git a/polymer/connection.cpp b/polymer/connection.cpp index e4e2bdd..3fec38d 100644 --- a/polymer/connection.cpp +++ b/polymer/connection.cpp @@ -125,23 +125,18 @@ void Connection::SetBlocking(bool blocking) { #endif } -void Connection::SendHandshake(u32 version, const char* address, u16 port, ProtocolState state_request) { +void Connection::SendHandshake(u32 version, const String& address, u16 port, ProtocolState state_request) { RingBuffer& wb = write_buffer; - - String sstr; - sstr.data = (char*)address; - sstr.size = strlen(address); - u32 pid = 0; - size_t size = GetVarIntSize(pid) + GetVarIntSize(version) + GetVarIntSize(sstr.size) + sstr.size + sizeof(u16) + + size_t size = GetVarIntSize(pid) + GetVarIntSize(version) + GetVarIntSize(address.size) + address.size + sizeof(u16) + GetVarIntSize((u64)state_request); wb.WriteVarInt(size); wb.WriteVarInt(pid); wb.WriteVarInt(version); - wb.WriteString(sstr); + wb.WriteString(address); wb.WriteU16(port); wb.WriteVarInt((u64)state_request); @@ -158,19 +153,15 @@ void Connection::SendPingRequest() { wb.WriteVarInt(pid); } -void Connection::SendLoginStart(const char* username) { +void Connection::SendLoginStart(const String& username) { RingBuffer& wb = write_buffer; - String sstr; - sstr.data = (char*)username; - sstr.size = strlen(username); - u32 pid = 0; - size_t size = GetVarIntSize(pid) + GetVarIntSize(sstr.size) + sstr.size + 1; + size_t size = GetVarIntSize(pid) + GetVarIntSize(username.size) + username.size + 1; wb.WriteVarInt(size); wb.WriteVarInt(pid); - wb.WriteString(sstr); + wb.WriteString(username); wb.WriteU8(0); // HasPlayerUUID } diff --git a/polymer/connection.h b/polymer/connection.h index 4fe5af3..76f0efe 100644 --- a/polymer/connection.h +++ b/polymer/connection.h @@ -39,9 +39,9 @@ struct Connection { TickResult Tick(); - void SendHandshake(u32 version, const char* address, u16 port, ProtocolState state_request); + void SendHandshake(u32 version, const String& address, u16 port, ProtocolState state_request); void SendPingRequest(); - void SendLoginStart(const char* username); + void SendLoginStart(const String& username); void SendKeepAlive(u64 id); void SendTeleportConfirm(u64 id); void SendPlayerPositionAndRotation(const Vector3f& position, float yaw, float pitch, bool on_ground); diff --git a/polymer/gamestate.h b/polymer/gamestate.h index 7d15b55..daf4180 100644 --- a/polymer/gamestate.h +++ b/polymer/gamestate.h @@ -42,7 +42,7 @@ struct PlayerManager { size_t player_count = 0; Player* client_player = nullptr; - char client_name[16]; + char client_name[17]; void SetClientPlayer(Player* player) { client_player = player; diff --git a/polymer/main.cpp b/polymer/main.cpp index f9519aa..8b4c2c2 100644 --- a/polymer/main.cpp +++ b/polymer/main.cpp @@ -14,7 +14,7 @@ #include #include -#include "polymer/miniz.h" +#include #define WIN32_LEAN_AND_MEAN #include @@ -23,9 +23,8 @@ namespace polymer { -constexpr const char* kServerIp = "127.0.0.1"; -constexpr u16 kServerPort = 25565; constexpr const char* kMinecraftJar = "1.19.3.jar"; +constexpr const char* kBlocksName = "blocks-1.19.3.json"; // Window surface width constexpr u32 kWidth = 1280; @@ -40,6 +39,143 @@ static InputState g_input = {}; static bool g_display_cursor = false; +struct ArgPair { + String name; + String value; +}; + +struct ArgParser { + ArgPair args[16]; + size_t arg_count; + + inline bool HasValue(const String& name) const { + for (size_t i = 0; i < arg_count; ++i) { + if (poly_strcmp(name, args[i].name) == 0) { + return true; + } + } + + return false; + } + + inline bool HasValue(const String* lookups, size_t count) const { + for (size_t i = 0; i < count; ++i) { + bool result = HasValue(lookups[i]); + + if (result) { + return true; + } + } + + return false; + } + + inline String GetValue(const String& name) const { + for (size_t i = 0; i < arg_count; ++i) { + if (poly_strcmp(name, args[i].name) == 0) { + return args[i].value; + } + } + + return String(); + } + + inline String GetValue(const String* lookups, size_t count) const { + for (size_t i = 0; i < count; ++i) { + String result = GetValue(lookups[i]); + + if (result.size > 0) { + return result; + } + } + + return String(); + } + + static ArgParser Parse(int argc, char* argv[]) { + ArgParser result = {}; + + for (int i = 0; i < argc; ++i) { + char* current = argv[i]; + + if (current[0] == '-') { + ++current; + + if (*current == '-') ++current; + + ArgPair& pair = result.args[result.arg_count++]; + + pair.name = String(current); + pair.value = String(); + + if (i < argc - 1) { + char* next = argv[i + 1]; + + if (*next != '-') { + pair.value = String(argv[i + 1]); + ++i; + } + } + } + } + + return result; + } +}; + +struct LaunchArgs { + String username; + String server; + u16 server_port; + bool help; + + static LaunchArgs Create(ArgParser& args) { + const String kUsernameArgs[] = {POLY_STR("username"), POLY_STR("user"), POLY_STR("u")}; + const String kServerArgs[] = {POLY_STR("server"), POLY_STR("s")}; + const String kHelpArgs[] = {POLY_STR("help"), POLY_STR("h")}; + + constexpr const char* kDefaultServerIp = "127.0.0.1"; + constexpr u16 kDefaultServerPort = 25565; + + constexpr const char* kDefaultUsername = "polymer"; + constexpr size_t kMaxUsernameSize = 16; + + LaunchArgs result = {}; + + result.username = args.GetValue(kUsernameArgs, polymer_array_count(kUsernameArgs)); + result.server = args.GetValue(kServerArgs, polymer_array_count(kServerArgs)); + + if (result.username.size == 0) { + result.username = String((char*)kDefaultUsername); + } + + if (result.username.size > kMaxUsernameSize) { + result.username.size = kMaxUsernameSize; + } + + result.server_port = kDefaultServerPort; + + if (result.server.size == 0) { + result.server = String((char*)kDefaultServerIp); + } else { + for (size_t i = 0; i < result.server.size; ++i) { + if (result.server.data[i] == ':') { + char* port_str = result.server.data + i + 1; + + result.server_port = (u16)strtol(port_str, nullptr, 10); + result.server.size = i; + result.server.data[i] = 0; + break; + } + } + } + + result.help = args.HasValue(kHelpArgs, polymer_array_count(kHelpArgs)); + + return result; + } +}; + LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_SIZE: { @@ -130,11 +266,24 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { return 0; } -int run() { +void PrintUsage() { + printf("Polymer\n\n"); + printf("Usage:\n\tpolymer.exe [OPTIONS]\n\n"); + printf("OPTIONS:\n"); + printf("\t-u, --user, --username\tOffline username. Default: polymer\n"); + printf("\t-s, --server\t\tDirect server. Default: 127.0.0.1:25565\n"); +} + +int run(const LaunchArgs& args) { constexpr size_t kMirrorBufferSize = 65536 * 32; constexpr size_t kPermanentSize = Gigabytes(1); constexpr size_t kTransientSize = Megabytes(32); + if (args.help) { + PrintUsage(); + return 0; + } + u8* perm_memory = (u8*)VirtualAlloc(NULL, kPermanentSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); u8* trans_memory = (u8*)VirtualAlloc(NULL, kTransientSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); @@ -147,6 +296,7 @@ int run() { vk_render.trans_arena = &trans_arena; printf("Polymer\n"); + fflush(stdout); GameState* game = memory_arena_construct_type(&perm_arena, GameState, &vk_render, &perm_arena, &trans_arena); PacketInterpreter interpreter(game); @@ -177,7 +327,7 @@ int run() { if (!RegisterClassEx(&wc)) { fprintf(stderr, "Failed to register window.\n"); - exit(1); + return 1; } RECT rect = {0, 0, kWidth, kHeight}; @@ -192,7 +342,7 @@ int run() { if (!hwnd) { fprintf(stderr, "Failed to create window.\n"); - exit(1); + return 1; } vk_render.Initialize(hwnd); @@ -202,8 +352,8 @@ int run() { using ms_float = std::chrono::duration; auto start = std::chrono::high_resolution_clock::now(); - if (!g_game->assets.Load(vk_render, kMinecraftJar, "blocks.json", &game->block_registry)) { - fprintf(stderr, "Failed to load minecraft assets. Requires blocks.json and %s.\n", kMinecraftJar); + if (!g_game->assets.Load(vk_render, kMinecraftJar, kBlocksName, &game->block_registry)) { + fprintf(stderr, "Failed to load minecraft assets. Requires %s and %s.\n", kBlocksName, kMinecraftJar); return 1; } @@ -242,7 +392,11 @@ int run() { g_game->font_renderer.CreateLayoutSet(vk_render, vk_render.device); vk_render.RecreateSwapchain(); - ConnectResult connect_result = connection->Connect(kServerIp, kServerPort); + printf("Connecting to '%.*s:%hu' with username '%.*s'.\n", (u32)args.server.size, args.server.data, args.server_port, + (u32)args.username.size, args.username.data); + fflush(stdout); + + ConnectResult connect_result = connection->Connect(args.server.data, args.server_port); switch (connect_result) { case ConnectResult::ErrorSocket: { @@ -267,9 +421,11 @@ int run() { constexpr u32 kProtocolVersion = 761; - connection->SendHandshake(kProtocolVersion, kServerIp, kServerPort, ProtocolState::Login); - connection->SendLoginStart("polymer"); - strcpy(game->player_manager.client_name, "polymer"); + connection->SendHandshake(kProtocolVersion, args.server, args.server_port, ProtocolState::Login); + connection->SendLoginStart(args.username); + + memcpy(game->player_manager.client_name, args.username.data, args.username.size); + game->player_manager.client_name[args.username.size] = 0; fflush(stdout); @@ -294,7 +450,7 @@ int run() { debug.position = Vector2f(8, 8); debug.color = Vector4f(1.0f, 0.67f, 0.0f, 1.0f); - debug.Write("Polymer"); + debug.Write("Polymer [%s]", game->player_manager.client_name); debug.color = Vector4f(1, 1, 1, 1); @@ -350,5 +506,13 @@ int run() { } // namespace polymer int main(int argc, char* argv[]) { - return polymer::run(); + polymer::ArgParser arg_parser = polymer::ArgParser::Parse(argc, argv); + polymer::LaunchArgs args = polymer::LaunchArgs::Create(arg_parser); + + int result = polymer::run(args); + + fflush(stdout); + fflush(stderr); + + return 0; } diff --git a/polymer/render/block_mesher.cpp b/polymer/render/block_mesher.cpp index a0fc480..a7dd38c 100644 --- a/polymer/render/block_mesher.cpp +++ b/polymer/render/block_mesher.cpp @@ -196,6 +196,7 @@ inline bool IsOccluding(BlockModel* from, BlockModel* to, BlockFace face) { // TODO: Clean this up once rotation is settled. if (to->element_count == 0) return false; + if (from->has_variant_rotation || to->has_variant_rotation) return false; if (to->has_leaves || !to->has_shaded) return false; for (size_t i = 0; i < from->element_count; ++i) { diff --git a/polymer/render/render.cpp b/polymer/render/render.cpp index f7b5e0d..e42efa5 100644 --- a/polymer/render/render.cpp +++ b/polymer/render/render.cpp @@ -16,8 +16,10 @@ const char* const kValidationLayers[] = {"VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool kEnableValidationLayers = false; +constexpr size_t kRequiredExtensionCount = polymer_array_count(kRequiredExtensions) - 1; #else constexpr bool kEnableValidationLayers = true; +constexpr size_t kRequiredExtensionCount = polymer_array_count(kRequiredExtensions); #endif static VkBool32 VKAPI_PTR DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, @@ -1039,7 +1041,7 @@ bool VulkanRenderer::CreateInstance() { inst_info.pNext = NULL; inst_info.flags = 0; inst_info.pApplicationInfo = &app_info; - inst_info.enabledExtensionCount = polymer_array_count(kRequiredExtensions); + inst_info.enabledExtensionCount = kRequiredExtensionCount; inst_info.ppEnabledExtensionNames = kRequiredExtensions; if (kEnableValidationLayers) { inst_info.enabledLayerCount = polymer_array_count(kValidationLayers);