diff --git a/.gitignore b/.gitignore
index 91926a8f..df911623 100644
--- a/.gitignore
+++ b/.gitignore
@@ -86,4 +86,5 @@ generated
docs/html
#imgui.ini file
-imgui.ini
\ No newline at end of file
+imgui.ini
+third_party/
diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt
index 10336a57..7ca05872 100644
--- a/client/CMakeLists.txt
+++ b/client/CMakeLists.txt
@@ -15,7 +15,21 @@ set(SOURCES
list(TRANSFORM SOURCES PREPEND ${CMAKE_CURRENT_LIST_DIR}/src/)
-add_executable(${PROJECT_NAME} ${SOURCES})
+# Create macOS app bundle on Apple platforms
+if(APPLE)
+ add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${SOURCES})
+
+ # Set bundle properties
+ set_target_properties(${PROJECT_NAME} PROPERTIES
+ MACOSX_BUNDLE_BUNDLE_NAME "Rehti MMORPG Client"
+ MACOSX_BUNDLE_GUI_IDENTIFIER "com.rehti.mmorpg.client"
+ MACOSX_BUNDLE_BUNDLE_VERSION "1.0"
+ MACOSX_BUNDLE_SHORT_VERSION_STRING "1.0"
+ MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in"
+ )
+else()
+ add_executable(${PROJECT_NAME} ${SOURCES})
+endif()
add_subdirectory(../rehtiLib/graphics ${CMAKE_CURRENT_LIST_DIR}/build/graphics)
add_subdirectory(../rehtiLib/network ${CMAKE_CURRENT_LIST_DIR}/build/network)
diff --git a/client/Info.plist.in b/client/Info.plist.in
new file mode 100644
index 00000000..2c180817
--- /dev/null
+++ b/client/Info.plist.in
@@ -0,0 +1,30 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ ${MACOSX_BUNDLE_EXECUTABLE_NAME}
+ CFBundleIdentifier
+ com.rehti.mmorpg.client
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ ${MACOSX_BUNDLE_BUNDLE_NAME}
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ LSMinimumSystemVersion
+ 10.15
+ NSHighResolutionCapable
+
+ NSSupportsAutomaticGraphicsSwitching
+
+ NSPrincipalClass
+ NSApplication
+
+
diff --git a/client/conanfile.py b/client/conanfile.py
index 1b18fea6..d5b31766 100644
--- a/client/conanfile.py
+++ b/client/conanfile.py
@@ -16,7 +16,7 @@ def requirements(self):
self.requires("spirv-tools/1.3.239.0", override=True)
self.requires("glfw/3.3.8")
self.requires("vulkan-memory-allocator/3.0.1")
- self.requires("stb/cci.20220909")
+ self.requires("stb/cci.20240531", override=True) # updated to latest version
self.requires("glm/cci.20230113")
self.requires("vulkan-headers/1.3.239.0", override=True)
self.requires("imgui/cci.20230105+1.89.2.docking")
diff --git a/client/src/Client.cpp b/client/src/Client.cpp
index a3b43b42..c4e997cc 100644
--- a/client/src/Client.cpp
+++ b/client/src/Client.cpp
@@ -4,6 +4,7 @@
#include
#include
#include
+#include
#include "Client.hpp"
#include "RehtiReader.hpp"
@@ -19,30 +20,34 @@ Client::Client(std::string ip, std::string port)
Connection::owner::client, ioContextM, std::move(boost::asio::ip::tcp::socket(ioContextM)), messagesM)),
audioLibM{}
{
- graphicsThreadM = std::thread([this]()
- { startGraphics(); });
-
+ // On macOS, GLFW/graphics must run on the main thread
+ // So we start IO and other work on background threads instead
ioThreadM = std::thread([this]()
{ ioContextM.run(); });
};
void Client::start()
{
- // Wait for graphics library to be ready
- if (!graphLibReadyFlagM)
- {
- std::unique_lock ul(graphLibMutexM);
- graphLibReadyM.wait(ul);
- }
+ // Start network and message processing on background threads
connectionThreadM = std::thread([this]()
{ boost::asio::co_spawn(ioContextM, connect(), boost::asio::detached); });
std::thread messageThread([this]()
{ processMessages(); });
- // Cleanup after graphics thread has exited
- graphicsThreadM.join();
+
+ // Run graphics on the main thread (required for macOS)
+ // This will block until the window is closed
+ startGraphics();
+
+ // Cleanup after graphics has exited
connectionM->disconnect();
ioContextM.stop();
+
+ // Wait for background threads to finish
+ if (connectionThreadM.joinable())
+ connectionThreadM.join();
+ if (messageThread.joinable())
+ messageThread.join();
}
boost::asio::awaitable Client::login()
@@ -437,13 +442,19 @@ void Client::handleMouseClick(const Hit& hit)
void Client::startGraphics()
{
+ try
+ {
+ std::cout << "[DEBUG] Starting graphics initialization..." << std::endl;
- // Load assets to memory
- assetCacheM.loadAssets();
- // init graphics library
- pGraphLibM = new RehtiGraphics();
+ // Load assets to memory
+ assetCacheM.loadAssets();
- // Create map bounding box
+ std::cout << "[DEBUG] Creating RehtiGraphics instance..." << std::endl;
+ // init graphics library
+ pGraphLibM = new RehtiGraphics();
+ std::cout << "[DEBUG] RehtiGraphics instance created successfully!" << std::endl;
+
+ // Create map bounding box
std::vector> heightMatrix = fetchHeightMatrix();
std::vector> areaMatrix = fetchAreaMatrix();
@@ -488,9 +499,22 @@ void Client::startGraphics()
pGraphLibM->getGui()->addEquipmentItemClickCallback([this](const int itemInstanceId)
{ boost::asio::co_spawn(ioContextM, unequipItem(itemInstanceId), boost::asio::detached); });
- std::cout << "Graphics library ready" << std::endl;
+ std::cout << "Graphics library ready" << std::endl;
- graphLibReadyFlagM = true;
- graphLibReadyM.notify_one();
- pGraphLibM->startMainLoop();
+ graphLibReadyFlagM = true;
+ graphLibReadyM.notify_one();
+ pGraphLibM->startMainLoop();
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << "[ERROR] Exception in startGraphics: " << e.what() << std::endl;
+ std::cerr << "[ERROR] Graphics initialization failed!" << std::endl;
+ throw;
+ }
+ catch (...)
+ {
+ std::cerr << "[ERROR] Unknown exception in startGraphics!" << std::endl;
+ std::cerr << "[ERROR] Graphics initialization failed!" << std::endl;
+ throw;
+ }
}
diff --git a/rehtiLib/graphics/CMakeLists.txt b/rehtiLib/graphics/CMakeLists.txt
index 3936d81d..824d16e2 100644
--- a/rehtiLib/graphics/CMakeLists.txt
+++ b/rehtiLib/graphics/CMakeLists.txt
@@ -1,16 +1,29 @@
add_library(rehtiGraphics "")
set(DEPENDENCY_DIR "${CMAKE_CURRENT_LIST_DIR}/../../third_party")
-set(VALIDATION_LAYER_DIR "${vulkan-validationlayers_INCLUDE_DIR}/../${CMAKE_INSTALL_BINDIR}")
-set($ENV{VK_LAYER_PATH} VALIDATION_LAYER_DIR)
-set($ENV{VK_LOADER_LAYERS_ENABLE} "*validation")
+# We *do not* set VK_LAYER_PATH here anymore.
+# We'll set it in the shell/run script instead, where it actually matters at runtime.
find_package(glfw3 3.3.8 REQUIRED)
find_package(Vulkan 1.3.239.0 REQUIRED)
find_package(SPIRV-Tools 1.3.239.0 REQUIRED)
-find_package(vulkan-memory-allocator 3.0.1 REQUIRED)
+find_package(VulkanMemoryAllocator CONFIG REQUIRED)
find_package(vulkan-validationlayers 1.3.239.0 REQUIRED)
-find_package(stb REQUIRED)
+# stb doesn't provide CMake config via Conan, manually locate and create interface library
+if(NOT TARGET stb::stb)
+ # Try to find stb_image.h in the Conan cache
+ execute_process(
+ COMMAND bash -c "find ~/.conan2/p/stb*/p/include -name stb_image.h 2>/dev/null | head -1 | xargs dirname"
+ OUTPUT_VARIABLE STB_INCLUDE_DIR
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+ if(NOT STB_INCLUDE_DIR)
+ message(FATAL_ERROR "Could not find stb include directory. Make sure stb is installed via Conan.")
+ endif()
+ message(STATUS "Found stb include directory: ${STB_INCLUDE_DIR}")
+ add_library(stb::stb INTERFACE IMPORTED)
+ target_include_directories(stb::stb INTERFACE ${STB_INCLUDE_DIR})
+endif()
find_package(glm REQUIRED)
find_package(imgui REQUIRED)
@@ -77,7 +90,7 @@ target_link_libraries(rehtiGraphics
glfw
spirv-tools::spirv-tools
${GLSLANG_LIBS}
- vulkan-memory-allocator::vulkan-memory-allocator
+ GPUOpen::VulkanMemoryAllocator
vulkan-validationlayers::vulkan-validationlayers
stb::stb
glm::glm
diff --git a/rehtiLib/graphics/src/RehtiGraphics.cpp b/rehtiLib/graphics/src/RehtiGraphics.cpp
index 7da9f0c0..34cc06ae 100644
--- a/rehtiLib/graphics/src/RehtiGraphics.cpp
+++ b/rehtiLib/graphics/src/RehtiGraphics.cpp
@@ -14,9 +14,24 @@ RehtiGraphics::RehtiGraphics(uint32_t width, uint32_t height, glm::vec3 cameraLo
: widthM(width), heightM(height), anisotropyM(1.f), cameraM(Camera(cameraLocation, static_cast(width), static_cast(height))),
sunM({glm::vec3(1.f, -1.f, 1.f), glm::vec3(1.f), 1.f})
{
- initWindow();
- cameraM.registerCameraControls(pWindowM);
- initVulkan();
+ try
+ {
+ std::cout << "[DEBUG] RehtiGraphics constructor starting..." << std::endl;
+ initWindow();
+ cameraM.registerCameraControls(pWindowM);
+ initVulkan();
+ std::cout << "[DEBUG] ✅ RehtiGraphics constructor complete!" << std::endl;
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << "[ERROR] Exception in RehtiGraphics constructor: " << e.what() << std::endl;
+ throw;
+ }
+ catch (...)
+ {
+ std::cerr << "[ERROR] Unknown exception in RehtiGraphics constructor!" << std::endl;
+ throw;
+ }
}
RehtiGraphics::~RehtiGraphics()
@@ -401,16 +416,35 @@ void RehtiGraphics::setEngineFlags(EngineFlags flags)
void RehtiGraphics::initWindow()
{
+ std::cout << "[DEBUG] Starting GLFW initialization..." << std::endl;
+
// Initialize glfw
- glfwInit();
+ if (!glfwInit())
+ {
+ std::cerr << "[ERROR] Failed to initialize GLFW!" << std::endl;
+ throw std::runtime_error("GLFW initialization failed");
+ }
+ std::cout << "[DEBUG] GLFW initialized successfully" << std::endl;
+
// Some arguments
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
+ std::cout << "[DEBUG] Creating GLFW window..." << std::endl;
pWindowM = glfwCreateWindow(widthM, heightM, "REHTI MMORPG", nullptr, nullptr);
+ if (!pWindowM)
+ {
+ std::cerr << "[ERROR] Failed to create GLFW window!" << std::endl;
+ glfwTerminate();
+ throw std::runtime_error("GLFW window creation failed");
+ }
+ std::cout << "[DEBUG] GLFW window created successfully" << std::endl;
+
glfwSetWindowUserPointer(pWindowM, this);
glfwSetFramebufferSizeCallback(pWindowM, RehtiGraphics::frameBufferResizeCallback);
+
+ std::cout << "[DEBUG] Window initialization complete" << std::endl;
}
void RehtiGraphics::frameBufferResizeCallback(GLFWwindow* window, int width, int height)
@@ -421,23 +455,77 @@ void RehtiGraphics::frameBufferResizeCallback(GLFWwindow* window, int width, int
void RehtiGraphics::initVulkan()
{
+ std::cout << "[DEBUG] Starting Vulkan initialization..." << std::endl;
+
+ std::cout << "[DEBUG] Creating Vulkan instance..." << std::endl;
createInstance();
+ std::cout << "[DEBUG] Vulkan instance created" << std::endl;
+
+ std::cout << "[DEBUG] Setting up debug messenger..." << std::endl;
setupDebugMessenger(); // Setup debugging
+ std::cout << "[DEBUG] Debug messenger setup complete" << std::endl;
+
+ std::cout << "[DEBUG] Creating surface..." << std::endl;
createSurface(); // Create the surface to draw into. Mostly handled by glfw.
+ std::cout << "[DEBUG] Surface created" << std::endl;
+
+ std::cout << "[DEBUG] Picking physical device..." << std::endl;
pickPhysicalDevice(); // Choose the physical device (gpu)
+ std::cout << "[DEBUG] Physical device picked" << std::endl;
+
+ std::cout << "[DEBUG] Creating logical device..." << std::endl;
createLogicalDevice(); // Create the interactable logical device
+ std::cout << "[DEBUG] Logical device created" << std::endl;
+
+ std::cout << "[DEBUG] Creating texture sampler..." << std::endl;
createTextureSampler(); // Create a sampler for textures
+ std::cout << "[DEBUG] Texture sampler created" << std::endl;
+
+ std::cout << "[DEBUG] Creating object manager..." << std::endl;
createObjectManager(); // Initializes the object management
+ std::cout << "[DEBUG] Object manager created" << std::endl;
+
+ std::cout << "[DEBUG] Creating swap chain..." << std::endl;
createSwapChain(); // Creates the swapchain
+ std::cout << "[DEBUG] Swap chain created" << std::endl;
+
+ std::cout << "[DEBUG] Creating depth resources..." << std::endl;
createDepthResources();
+ std::cout << "[DEBUG] Depth resources created" << std::endl;
+
+ std::cout << "[DEBUG] Creating image views..." << std::endl;
createImageViews(); // Creates the image view (how to access the image)
+ std::cout << "[DEBUG] Image views created" << std::endl;
+
+ std::cout << "[DEBUG] Creating render pass..." << std::endl;
createRenderPass();
+ std::cout << "[DEBUG] Render pass created" << std::endl;
+
+ std::cout << "[DEBUG] Creating graphics pipeline..." << std::endl;
createGraphicsPipeline(); // Creates a rendering pipeline
+ std::cout << "[DEBUG] Graphics pipeline created" << std::endl;
+
+ std::cout << "[DEBUG] Creating framebuffers..." << std::endl;
createFramebuffers(); // Creates the framebuffers
+ std::cout << "[DEBUG] Framebuffers created" << std::endl;
+
+ std::cout << "[DEBUG] Creating command pool..." << std::endl;
createCommandPool();
+ std::cout << "[DEBUG] Command pool created" << std::endl;
+
+ std::cout << "[DEBUG] Creating command buffers..." << std::endl;
createCommandBuffers();
+ std::cout << "[DEBUG] Command buffers created" << std::endl;
+
+ std::cout << "[DEBUG] Creating synchronization objects..." << std::endl;
createSynchronization();
+ std::cout << "[DEBUG] Synchronization objects created" << std::endl;
+
+ std::cout << "[DEBUG] Creating GUI..." << std::endl;
createGui();
+ std::cout << "[DEBUG] GUI created" << std::endl;
+
+ std::cout << "[DEBUG] ✅ Vulkan initialization complete!" << std::endl;
}
void RehtiGraphics::populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo)
@@ -527,8 +615,17 @@ void RehtiGraphics::createLogicalDevice()
devCreateInfo.pEnabledFeatures = &deviceFeatures;
- devCreateInfo.enabledExtensionCount = static_cast(kDeviceExtensionsM.size());
- devCreateInfo.ppEnabledExtensionNames = kDeviceExtensionsM.data();
+ // Prepare device extensions
+ std::vector deviceExtensions(kDeviceExtensionsM.begin(), kDeviceExtensionsM.end());
+
+#ifdef __APPLE__
+ // Add portability subset extension for MoltenVK
+ deviceExtensions.push_back("VK_KHR_portability_subset");
+ std::cout << "[DEBUG] Added VK_KHR_portability_subset extension for macOS" << std::endl;
+#endif
+
+ devCreateInfo.enabledExtensionCount = static_cast(deviceExtensions.size());
+ devCreateInfo.ppEnabledExtensionNames = deviceExtensions.data();
// TODO: this is not correct, as there can be layers other than validation layers
if (validationLayersEnabledM)
@@ -1178,12 +1275,24 @@ void RehtiGraphics::createInstance()
info.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
info.pEngineName = "RehtiEngine";
info.engineVersion = VK_MAKE_VERSION(1, 0, 0);
+#ifdef __APPLE__
+ // MoltenVK supports up to Vulkan 1.2
+ info.apiVersion = VK_API_VERSION_1_2;
+ std::cout << "[DEBUG] Using Vulkan API 1.2 for macOS/MoltenVK" << std::endl;
+#else
info.apiVersion = VK_API_VERSION_1_3; // Vulkan 1.3
+#endif
// Create info:
VkInstanceCreateInfo instanceInfo{};
instanceInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instanceInfo.pApplicationInfo = &info;
+
+#ifdef __APPLE__
+ // Required for MoltenVK portability
+ instanceInfo.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
+#endif
+
// Next up, extensions
auto extensions = this->getRequiredExtensions();
@@ -1529,6 +1638,13 @@ std::vector RehtiGraphics::getRequiredExtensions()
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
+#ifdef __APPLE__
+ // Required for MoltenVK portability
+ extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
+ extensions.push_back("VK_KHR_get_physical_device_properties2");
+ std::cout << "[DEBUG] Added macOS portability extensions" << std::endl;
+#endif
+
return extensions;
}
diff --git a/rehtiLib/graphics/src/RehtiGraphics.hpp b/rehtiLib/graphics/src/RehtiGraphics.hpp
index fd90379d..4c5cbb02 100644
--- a/rehtiLib/graphics/src/RehtiGraphics.hpp
+++ b/rehtiLib/graphics/src/RehtiGraphics.hpp
@@ -616,7 +616,8 @@ class RehtiGraphics
static void frameBufferResizeCallback(GLFWwindow* window, int width, int height);
-#ifdef NDEBUG
+#if defined(NDEBUG) || defined(__APPLE__)
+ // Disable validation layers in Release builds or on macOS (where they're not commonly installed)
const bool enableValidationLayers = false;
#else
const bool enableValidationLayers = true;
diff --git a/scripts/build-client.sh b/scripts/build-client.sh
index e0ddebfd..50ef6690 100755
--- a/scripts/build-client.sh
+++ b/scripts/build-client.sh
@@ -1,6 +1,19 @@
#!/bin/bash
+set -e
+
+# 1) Generate assets
./scripts/generate_assets.sh
+
+# 2) Move into client directory
cd ./client
+
+# 3) Have Conan resolve dependencies and generate toolchain into ./build
conan install . --output-folder=build --build=missing -s build_type=Debug -s compiler.cppstd=20
-cmake -S . -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -B build -DCMAKE_BUILD_TYPE=Debug
-cmake --build ./build
\ No newline at end of file
+
+# 4) Configure CMake using the Conan toolchain
+cmake -S . -B build -G "Unix Makefiles" \
+ -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake \
+ -DCMAKE_BUILD_TYPE=Debug
+
+# 5) Build the client
+cmake --build build --config Debug
diff --git a/scripts/generate_assets.sh b/scripts/generate_assets.sh
index d92d1600..3be0a16c 100755
--- a/scripts/generate_assets.sh
+++ b/scripts/generate_assets.sh
@@ -1,18 +1,30 @@
#!/bin/bash
-# Add log here
-echo ">>>>>> RUNNING ASSET GENERATION SCRIPT <<<<<<\n"
+echo ">>>>>> RUNNING ASSET GENERATION SCRIPT <<<<<<"
-if [[ "$(expr substr $(uname -s) 1 5)" == "Linux" ]]; then
+UNAME_S="$(uname -s)"
+
+case "$UNAME_S" in
+ Linux*)
# Linux
EXECUTABLE_PATH="./rehtiLib/assets/loader/build/asset_loader"
-elif [[ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" || "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]]; then
+ ;;
+
+ Darwin*)
+ # macOS (including Sequoia / Darwin 25)
+ EXECUTABLE_PATH="./rehtiLib/assets/loader/build/asset_loader"
+ ;;
+
+ MINGW32_NT*|MINGW64_NT*)
# Windows (MINGW32_NT or MINGW64_NT)
EXECUTABLE_PATH="./rehtiLib/assets/loader/build/Debug/asset_loader"
-else
- echo "Unsupported operating system"
+ ;;
+
+ *)
+ echo "Unsupported operating system: $UNAME_S"
exit 1
-fi
+ ;;
+esac
cmake -S ./rehtiLib/assets/loader -B ./rehtiLib/assets/loader/build -DCMAKE_BUILD_TYPE=Debug
cmake --build ./rehtiLib/assets/loader/build
@@ -25,4 +37,4 @@ else
exit 1
fi
-echo ">>>>>> ASSET GENERATION SCRIPT FINISHED <<<<<<\n"
\ No newline at end of file
+echo ">>>>>> ASSET GENERATION SCRIPT FINISHED <<<<<<"
diff --git a/scripts/run-client.sh b/scripts/run-client.sh
index ad6d1323..f02522ce 100755
--- a/scripts/run-client.sh
+++ b/scripts/run-client.sh
@@ -1,15 +1,50 @@
#!/bin/bash
-if [[ "$(expr substr $(uname -s) 1 5)" == "Linux" ]]; then
+UNAME_S="$(uname -s)"
+
+case "$UNAME_S" in
+ Linux*)
# Linux
EXECUTABLE_PATH="./client/build/Client"
-elif [[ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" || "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]]; then
+ ;;
+
+ Darwin*)
+ # macOS (including Sequoia / Darwin 25)
+
+ # Configure MoltenVK for Vulkan support on macOS
+ if [ -f "/opt/homebrew/etc/vulkan/icd.d/MoltenVK_icd.json" ]; then
+ export VK_ICD_FILENAMES=/opt/homebrew/etc/vulkan/icd.d/MoltenVK_icd.json
+ export DYLD_LIBRARY_PATH=/opt/homebrew/lib:$DYLD_LIBRARY_PATH
+ echo "MoltenVK configured (Apple Silicon)"
+ elif [ -f "/usr/local/etc/vulkan/icd.d/MoltenVK_icd.json" ]; then
+ export VK_ICD_FILENAMES=/usr/local/etc/vulkan/icd.d/MoltenVK_icd.json
+ export DYLD_LIBRARY_PATH=/usr/local/lib:$DYLD_LIBRARY_PATH
+ echo "MoltenVK configured (Intel)"
+ else
+ echo "WARNING: MoltenVK not found. Install with: brew install molten-vk"
+ fi
+
+ # Enable MoltenVK debug output (optional, comment out if too verbose)
+ export MVK_CONFIG_LOG_LEVEL=3
+
+ # Check if app bundle exists, otherwise fall back to plain executable
+ if [ -d "./client/build/Client.app" ]; then
+ EXECUTABLE_PATH="./client/build/Client.app/Contents/MacOS/Client"
+ else
+ EXECUTABLE_PATH="./client/build/Client"
+ fi
+ ;;
+
+ MINGW32_NT*|MINGW64_NT*)
# Windows (MINGW32_NT or MINGW64_NT)
EXECUTABLE_PATH="./client/build/Debug/Client"
-else
- echo "Unsupported operating system"
+ ;;
+
+ *)
+ echo "Unsupported operating system: $UNAME_S"
exit 1
-fi
+ ;;
+esac
./scripts/build-client.sh