diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5d34d38..29baf83 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,10 @@ on: - '**.txt' - '.github/workflows/build.yml' workflow_dispatch: - pull_request: + +env: + GIT_BRANCH: ${{ github.ref_type == 'branch' && github.ref_name || 'release' }} + BUILD_TYPE: ${{ (github.ref_type != 'branch' || github.ref_name == 'main') && 'Release' || 'Debug' }} jobs: windows: @@ -36,16 +39,17 @@ jobs: cmake -B build -S . -A Win32 ` -DGARRYSMOD_COMMON_PATH="third-party/garrysmod_common" ` -DAUTOINSTALL="." ` - -DBUILD_SHARED_LIBS=OFF + -DBUILD_SHARED_LIBS=OFF ` + -DGIT_BRANCH="$env:GIT_BRANCH" - name: '[x32] Build project (Serverside)' - run: cmake --build build --config Release -j -t moonloader + run: cmake --build build --config $env:BUILD_TYPE -j -t moonloader - name: '[x32] Configure project (Clientside)' run: cmake -B build -S . -DCLIENT_DLL=ON - name: '[x32] Build project (Clientside)' - run: cmake --build build --config Release -j -t moonloader + run: cmake --build build --config $env:BUILD_TYPE -j -t moonloader - name: '[x64] Download garrysmod_common' uses: actions/checkout@v3 @@ -60,16 +64,17 @@ jobs: cmake -B build64 -S . -A x64 ` -DGARRYSMOD_COMMON_PATH="third-party/garrysmod_common64" ` -DAUTOINSTALL="." ` - -DBUILD_SHARED_LIBS=OFF + -DBUILD_SHARED_LIBS=OFF ` + -DGIT_BRANCH="$env:GIT_BRANCH" - name: '[x64] Build project (Serverside)' - run: cmake --build build64 --config Release -j -t moonloader + run: cmake --build build64 --config $env:BUILD_TYPE -j -t moonloader - name: '[x64] Configure project (Clientside)' run: cmake -B build64 -S . -DCLIENT_DLL=ON - name: '[x64] Build project (Clientside)' - run: cmake --build build64 --config Release -j -t moonloader + run: cmake --build build64 --config $env:BUILD_TYPE -j -t moonloader - name: "Upload artifacts" uses: actions/upload-artifact@v3 @@ -79,6 +84,14 @@ jobs: ./*.dll if-no-files-found: error + - name: "Upload debug artifacts" + uses: actions/upload-artifact@v3 + if: ${{ env.BUILD_TYPE == 'Debug' }} + with: + name: Windows + path: | + ./*.pdb + linux: runs-on: ubuntu-20.04 # Using ubuntu 20.04 since we want to use old gcc version to ensure compatibility steps: @@ -104,16 +117,18 @@ jobs: -DGARRYSMOD_COMMON_PATH="third-party/garrysmod_common" \ -DAUTOINSTALL="." \ -DBUILD_SHARED_LIBS=OFF \ - -DCMAKE_C_FLAGS="-m32" -DCMAKE_CXX_FLAGS="-m32" + -DCMAKE_C_FLAGS="-m32" -DCMAKE_CXX_FLAGS="-m32" \ + -DGIT_BRANCH="$GIT_BRANCH" \ + -DCMAKE_BUILD_TYPE="$BUILD_TYPE" - name: '[x32] Build project (Serverside)' - run: cmake --build build --config Release -j -t moonloader + run: cmake --build build -j -t moonloader - name: '[x32] Configure project (Clientside)' run: cmake -B build -S . -DCLIENT_DLL=ON - name: '[x32] Build project (Clientside)' - run: cmake --build build --config Release -j -t moonloader + run: cmake --build build -j -t moonloader - name: '[x64] Download garrysmod_common' uses: actions/checkout@v3 @@ -128,16 +143,18 @@ jobs: cmake -B build64 -S . \ -DGARRYSMOD_COMMON_PATH="third-party/garrysmod_common64" \ -DAUTOINSTALL="." \ - -DBUILD_SHARED_LIBS=OFF + -DBUILD_SHARED_LIBS=OFF \ + -DGIT_BRANCH="$GIT_BRANCH" \ + -DCMAKE_BUILD_TYPE="$BUILD_TYPE" - name: '[x64] Build project (Serverside)' - run: cmake --build build64 --config Release -j -t moonloader + run: cmake --build build64 -j -t moonloader - name: '[x64] Configure project (Clientside)' run: cmake -B build64 -S . -DCLIENT_DLL=ON - name: '[x64] Build project (Clientside)' - run: cmake --build build64 --config Release -j -t moonloader + run: cmake --build build64 -j -t moonloader - name: "Upload artifacts" uses: actions/upload-artifact@v3 @@ -167,16 +184,18 @@ jobs: cmake -B build64 -S . \ -DGARRYSMOD_COMMON_PATH="third-party/garrysmod_common64" \ -DAUTOINSTALL="." \ - -DBUILD_SHARED_LIBS=OFF + -DBUILD_SHARED_LIBS=OFF \ + -DGIT_BRANCH="$GIT_BRANCH" \ + -DCMAKE_BUILD_TYPE="$BUILD_TYPE" - name: '[x64] Build project (Serverside)' - run: cmake --build build64 --config Release -j -t moonloader + run: cmake --build build64 -j -t moonloader - name: '[x64] Configure project (Clientside)' run: cmake -B build64 -S . -DCLIENT_DLL=ON - name: '[x64] Build project (Clientside)' - run: cmake --build build64 --config Release -j -t moonloader + run: cmake --build build64 -j -t moonloader - name: "Upload artifacts" uses: actions/upload-artifact@v3 diff --git a/CMakeLists.txt b/CMakeLists.txt index c1335a1..8dc3a3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,11 +28,6 @@ endif() # Include extensions add_subdirectory(cmake) -# CmakeRC compiler -file(DOWNLOAD "https://raw.githubusercontent.com/vector-of-bool/cmrc/master/CMakeRC.cmake" - "${CMAKE_BINARY_DIR}/CMakeRC.cmake") -include("${CMAKE_BINARY_DIR}/CMakeRC.cmake") - # Entropia File System Watcher add_subdirectory(third-party/efsw) @@ -57,8 +52,18 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE ) +# Get the current working branch +if(NOT DEFINED GIT_BRANCH) + execute_process( + COMMAND git rev-parse --abbrev-ref HEAD + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + OUTPUT_VARIABLE GIT_BRANCH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) +endif() + project(gm_moonloader - VERSION 1.2.0 + VERSION 1.3.0 LANGUAGES CXX HOMEPAGE_URL "https://github.com/Pika-Software/gm_moonloader" ) diff --git a/VERSION b/VERSION index 26aaba0..f0bb29e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.0 +1.3.0 diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 5ee0190..a591ba1 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -1 +1,2 @@ include(find_garrysmod_common.cmake) +include(CMakeRC.cmake) diff --git a/cmake/CMakeRC.cmake b/cmake/CMakeRC.cmake new file mode 100644 index 0000000..5cad705 --- /dev/null +++ b/cmake/CMakeRC.cmake @@ -0,0 +1,644 @@ +# This block is executed when generating an intermediate resource file, not when +# running in CMake configure mode +if(_CMRC_GENERATE_MODE) + # Read in the digits + file(READ "${INPUT_FILE}" bytes HEX) + # Format each pair into a character literal. Heuristics seem to favor doing + # the conversion in groups of five for fastest conversion + string(REGEX REPLACE "(..)(..)(..)(..)(..)" "'\\\\x\\1','\\\\x\\2','\\\\x\\3','\\\\x\\4','\\\\x\\5'," chars "${bytes}") + # Since we did this in groups, we have some leftovers to clean up + string(LENGTH "${bytes}" n_bytes2) + math(EXPR n_bytes "${n_bytes2} / 2") + math(EXPR remainder "${n_bytes} % 5") # <-- '5' is the grouping count from above + set(cleanup_re "$") + set(cleanup_sub ) + while(remainder) + set(cleanup_re "(..)${cleanup_re}") + set(cleanup_sub "'\\\\x\\${remainder}',${cleanup_sub}") + math(EXPR remainder "${remainder} - 1") + endwhile() + if(NOT cleanup_re STREQUAL "$") + string(REGEX REPLACE "${cleanup_re}" "${cleanup_sub}" chars "${chars}") + endif() + string(CONFIGURE [[ + namespace { const char file_array[] = { @chars@ 0 }; } + namespace cmrc { namespace @NAMESPACE@ { namespace res_chars { + extern const char* const @SYMBOL@_begin = file_array; + extern const char* const @SYMBOL@_end = file_array + @n_bytes@; + }}} + ]] code) + file(WRITE "${OUTPUT_FILE}" "${code}") + # Exit from the script. Nothing else needs to be processed + return() +endif() + +set(_version 2.0.0) + +cmake_minimum_required(VERSION 3.3) +include(CMakeParseArguments) + +if(COMMAND cmrc_add_resource_library) + if(NOT DEFINED _CMRC_VERSION OR NOT (_version STREQUAL _CMRC_VERSION)) + message(WARNING "More than one CMakeRC version has been included in this project.") + endif() + # CMakeRC has already been included! Don't do anything + return() +endif() + +set(_CMRC_VERSION "${_version}" CACHE INTERNAL "CMakeRC version. Used for checking for conflicts") + +set(_CMRC_SCRIPT "${CMAKE_CURRENT_LIST_FILE}" CACHE INTERNAL "Path to CMakeRC script") + +function(_cmrc_normalize_path var) + set(path "${${var}}") + file(TO_CMAKE_PATH "${path}" path) + while(path MATCHES "//") + string(REPLACE "//" "/" path "${path}") + endwhile() + string(REGEX REPLACE "/+$" "" path "${path}") + set("${var}" "${path}" PARENT_SCOPE) +endfunction() + +get_filename_component(_inc_dir "${CMAKE_BINARY_DIR}/_cmrc/include" ABSOLUTE) +set(CMRC_INCLUDE_DIR "${_inc_dir}" CACHE INTERNAL "Directory for CMakeRC include files") +# Let's generate the primary include file +file(MAKE_DIRECTORY "${CMRC_INCLUDE_DIR}/cmrc") +set(hpp_content [==[ +#ifndef CMRC_CMRC_HPP_INCLUDED +#define CMRC_CMRC_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !(defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) || defined(CMRC_NO_EXCEPTIONS)) +#define CMRC_NO_EXCEPTIONS 1 +#endif + +namespace cmrc { namespace detail { struct dummy; } } + +#define CMRC_DECLARE(libid) \ + namespace cmrc { namespace detail { \ + struct dummy; \ + static_assert(std::is_same::value, "CMRC_DECLARE() must only appear at the global namespace"); \ + } } \ + namespace cmrc { namespace libid { \ + cmrc::embedded_filesystem get_filesystem(); \ + } } static_assert(true, "") + +namespace cmrc { + +class file { + const char* _begin = nullptr; + const char* _end = nullptr; + +public: + using iterator = const char*; + using const_iterator = iterator; + iterator begin() const noexcept { return _begin; } + iterator cbegin() const noexcept { return _begin; } + iterator end() const noexcept { return _end; } + iterator cend() const noexcept { return _end; } + std::size_t size() const { return static_cast(std::distance(begin(), end())); } + + file() = default; + file(iterator beg, iterator end) noexcept : _begin(beg), _end(end) {} +}; + +class directory_entry; + +namespace detail { + +class directory; +class file_data; + +class file_or_directory { + union _data_t { + class file_data* file_data; + class directory* directory; + } _data; + bool _is_file = true; + +public: + explicit file_or_directory(file_data& f) { + _data.file_data = &f; + } + explicit file_or_directory(directory& d) { + _data.directory = &d; + _is_file = false; + } + bool is_file() const noexcept { + return _is_file; + } + bool is_directory() const noexcept { + return !is_file(); + } + const directory& as_directory() const noexcept { + assert(!is_file()); + return *_data.directory; + } + const file_data& as_file() const noexcept { + assert(is_file()); + return *_data.file_data; + } +}; + +class file_data { +public: + const char* begin_ptr; + const char* end_ptr; + file_data(const file_data&) = delete; + file_data(const char* b, const char* e) : begin_ptr(b), end_ptr(e) {} +}; + +inline std::pair split_path(const std::string& path) { + auto first_sep = path.find("/"); + if (first_sep == path.npos) { + return std::make_pair(path, ""); + } else { + return std::make_pair(path.substr(0, first_sep), path.substr(first_sep + 1)); + } +} + +struct created_subdirectory { + class directory& directory; + class file_or_directory& index_entry; +}; + +class directory { + std::list _files; + std::list _dirs; + std::map _index; + + using base_iterator = std::map::const_iterator; + +public: + + directory() = default; + directory(const directory&) = delete; + + created_subdirectory add_subdir(std::string name) & { + _dirs.emplace_back(); + auto& back = _dirs.back(); + auto& fod = _index.emplace(name, file_or_directory{back}).first->second; + return created_subdirectory{back, fod}; + } + + file_or_directory* add_file(std::string name, const char* begin, const char* end) & { + assert(_index.find(name) == _index.end()); + _files.emplace_back(begin, end); + return &_index.emplace(name, file_or_directory{_files.back()}).first->second; + } + + const file_or_directory* get(const std::string& path) const { + auto pair = split_path(path); + auto child = _index.find(pair.first); + if (child == _index.end()) { + return nullptr; + } + auto& entry = child->second; + if (pair.second.empty()) { + // We're at the end of the path + return &entry; + } + + if (entry.is_file()) { + // We can't traverse into a file. Stop. + return nullptr; + } + // Keep going down + return entry.as_directory().get(pair.second); + } + + class iterator { + base_iterator _base_iter; + base_iterator _end_iter; + public: + using value_type = directory_entry; + using difference_type = std::ptrdiff_t; + using pointer = const value_type*; + using reference = const value_type&; + using iterator_category = std::input_iterator_tag; + + iterator() = default; + explicit iterator(base_iterator iter, base_iterator end) : _base_iter(iter), _end_iter(end) {} + + iterator begin() const noexcept { + return *this; + } + + iterator end() const noexcept { + return iterator(_end_iter, _end_iter); + } + + inline value_type operator*() const noexcept; + + bool operator==(const iterator& rhs) const noexcept { + return _base_iter == rhs._base_iter; + } + + bool operator!=(const iterator& rhs) const noexcept { + return !(*this == rhs); + } + + iterator& operator++() noexcept { + ++_base_iter; + return *this; + } + + iterator operator++(int) noexcept { + auto cp = *this; + ++_base_iter; + return cp; + } + }; + + using const_iterator = iterator; + + iterator begin() const noexcept { + return iterator(_index.begin(), _index.end()); + } + + iterator end() const noexcept { + return iterator(); + } +}; + +inline std::string normalize_path(std::string path) { + while (path.find("/") == 0) { + path.erase(path.begin()); + } + while (!path.empty() && (path.rfind("/") == path.size() - 1)) { + path.pop_back(); + } + auto off = path.npos; + while ((off = path.find("//")) != path.npos) { + path.erase(path.begin() + static_cast(off)); + } + return path; +} + +using index_type = std::map; + +} // detail + +class directory_entry { + std::string _fname; + const detail::file_or_directory* _item; + +public: + directory_entry() = delete; + explicit directory_entry(std::string filename, const detail::file_or_directory& item) + : _fname(filename) + , _item(&item) + {} + + const std::string& filename() const & { + return _fname; + } + std::string filename() const && { + return std::move(_fname); + } + + bool is_file() const { + return _item->is_file(); + } + + bool is_directory() const { + return _item->is_directory(); + } +}; + +directory_entry detail::directory::iterator::operator*() const noexcept { + assert(begin() != end()); + return directory_entry(_base_iter->first, _base_iter->second); +} + +using directory_iterator = detail::directory::iterator; + +class embedded_filesystem { + // Never-null: + const cmrc::detail::index_type* _index; + const detail::file_or_directory* _get(std::string path) const { + path = detail::normalize_path(path); + auto found = _index->find(path); + if (found == _index->end()) { + return nullptr; + } else { + return found->second; + } + } + +public: + explicit embedded_filesystem(const detail::index_type& index) + : _index(&index) + {} + + file open(const std::string& path) const { + auto entry_ptr = _get(path); + if (!entry_ptr || !entry_ptr->is_file()) { +#ifdef CMRC_NO_EXCEPTIONS + fprintf(stderr, "Error no such file or directory: %s\n", path.c_str()); + abort(); +#else + throw std::system_error(make_error_code(std::errc::no_such_file_or_directory), path); +#endif + } + auto& dat = entry_ptr->as_file(); + return file{dat.begin_ptr, dat.end_ptr}; + } + + bool is_file(const std::string& path) const noexcept { + auto entry_ptr = _get(path); + return entry_ptr && entry_ptr->is_file(); + } + + bool is_directory(const std::string& path) const noexcept { + auto entry_ptr = _get(path); + return entry_ptr && entry_ptr->is_directory(); + } + + bool exists(const std::string& path) const noexcept { + return !!_get(path); + } + + directory_iterator iterate_directory(const std::string& path) const { + auto entry_ptr = _get(path); + if (!entry_ptr) { +#ifdef CMRC_NO_EXCEPTIONS + fprintf(stderr, "Error no such file or directory: %s\n", path.c_str()); + abort(); +#else + throw std::system_error(make_error_code(std::errc::no_such_file_or_directory), path); +#endif + } + if (!entry_ptr->is_directory()) { +#ifdef CMRC_NO_EXCEPTIONS + fprintf(stderr, "Error not a directory: %s\n", path.c_str()); + abort(); +#else + throw std::system_error(make_error_code(std::errc::not_a_directory), path); +#endif + } + return entry_ptr->as_directory().begin(); + } +}; + +} + +#endif // CMRC_CMRC_HPP_INCLUDED +]==]) + +set(cmrc_hpp "${CMRC_INCLUDE_DIR}/cmrc/cmrc.hpp" CACHE INTERNAL "") +set(_generate 1) +if(EXISTS "${cmrc_hpp}") + file(READ "${cmrc_hpp}" _current) + if(_current STREQUAL hpp_content) + set(_generate 0) + endif() +endif() +file(GENERATE OUTPUT "${cmrc_hpp}" CONTENT "${hpp_content}" CONDITION ${_generate}) + +add_library(cmrc-base INTERFACE) +target_include_directories(cmrc-base INTERFACE $) +# Signal a basic C++11 feature to require C++11. +target_compile_features(cmrc-base INTERFACE cxx_nullptr) +set_property(TARGET cmrc-base PROPERTY INTERFACE_CXX_EXTENSIONS OFF) +add_library(cmrc::base ALIAS cmrc-base) + +function(cmrc_add_resource_library name) + set(args ALIAS NAMESPACE TYPE) + cmake_parse_arguments(ARG "" "${args}" "" "${ARGN}") + # Generate the identifier for the resource library's namespace + set(ns_re "[a-zA-Z_][a-zA-Z0-9_]*") + if(NOT DEFINED ARG_NAMESPACE) + # Check that the library name is also a valid namespace + if(NOT name MATCHES "${ns_re}") + message(SEND_ERROR "Library name is not a valid namespace. Specify the NAMESPACE argument") + endif() + set(ARG_NAMESPACE "${name}") + else() + if(NOT ARG_NAMESPACE MATCHES "${ns_re}") + message(SEND_ERROR "NAMESPACE for ${name} is not a valid C++ namespace identifier (${ARG_NAMESPACE})") + endif() + endif() + set(libname "${name}") + # Check that type is either "STATIC" or "OBJECT", or default to "STATIC" if + # not set + if(NOT DEFINED ARG_TYPE) + set(ARG_TYPE STATIC) + elseif(NOT "${ARG_TYPE}" MATCHES "^(STATIC|OBJECT)$") + message(SEND_ERROR "${ARG_TYPE} is not a valid TYPE (STATIC and OBJECT are acceptable)") + set(ARG_TYPE STATIC) + endif() + # Generate a library with the compiled in character arrays. + string(CONFIGURE [=[ + #include + #include + #include + + namespace cmrc { + namespace @ARG_NAMESPACE@ { + + namespace res_chars { + // These are the files which are available in this resource library + $, + > + } + + namespace { + + const cmrc::detail::index_type& + get_root_index() { + static cmrc::detail::directory root_directory_; + static cmrc::detail::file_or_directory root_directory_fod{root_directory_}; + static cmrc::detail::index_type root_index; + root_index.emplace("", &root_directory_fod); + struct dir_inl { + class cmrc::detail::directory& directory; + }; + dir_inl root_directory_dir{root_directory_}; + (void)root_directory_dir; + $, + > + $, + > + return root_index; + } + + } + + cmrc::embedded_filesystem get_filesystem() { + static auto& index = get_root_index(); + return cmrc::embedded_filesystem{index}; + } + + } // @ARG_NAMESPACE@ + } // cmrc + ]=] cpp_content @ONLY) + get_filename_component(libdir "${CMAKE_CURRENT_BINARY_DIR}/__cmrc_${name}" ABSOLUTE) + get_filename_component(lib_tmp_cpp "${libdir}/lib_.cpp" ABSOLUTE) + string(REPLACE "\n " "\n" cpp_content "${cpp_content}") + file(GENERATE OUTPUT "${lib_tmp_cpp}" CONTENT "${cpp_content}") + get_filename_component(libcpp "${libdir}/lib.cpp" ABSOLUTE) + add_custom_command(OUTPUT "${libcpp}" + DEPENDS "${lib_tmp_cpp}" "${cmrc_hpp}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${lib_tmp_cpp}" "${libcpp}" + COMMENT "Generating ${name} resource loader" + ) + # Generate the actual static library. Each source file is just a single file + # with a character array compiled in containing the contents of the + # corresponding resource file. + add_library(${name} ${ARG_TYPE} ${libcpp}) + set_property(TARGET ${name} PROPERTY CMRC_LIBDIR "${libdir}") + set_property(TARGET ${name} PROPERTY CMRC_NAMESPACE "${ARG_NAMESPACE}") + target_link_libraries(${name} PUBLIC cmrc::base) + set_property(TARGET ${name} PROPERTY CMRC_IS_RESOURCE_LIBRARY TRUE) + if(ARG_ALIAS) + add_library("${ARG_ALIAS}" ALIAS ${name}) + endif() + cmrc_add_resources(${name} ${ARG_UNPARSED_ARGUMENTS}) +endfunction() + +function(_cmrc_register_dirs name dirpath) + if(dirpath STREQUAL "") + return() + endif() + # Skip this dir if we have already registered it + get_target_property(registered "${name}" _CMRC_REGISTERED_DIRS) + if(dirpath IN_LIST registered) + return() + endif() + # Register the parent directory first + get_filename_component(parent "${dirpath}" DIRECTORY) + if(NOT parent STREQUAL "") + _cmrc_register_dirs("${name}" "${parent}") + endif() + # Now generate the registration + set_property(TARGET "${name}" APPEND PROPERTY _CMRC_REGISTERED_DIRS "${dirpath}") + _cm_encode_fpath(sym "${dirpath}") + if(parent STREQUAL "") + set(parent_sym root_directory) + else() + _cm_encode_fpath(parent_sym "${parent}") + endif() + get_filename_component(leaf "${dirpath}" NAME) + set_property( + TARGET "${name}" + APPEND PROPERTY CMRC_MAKE_DIRS + "static auto ${sym}_dir = ${parent_sym}_dir.directory.add_subdir(\"${leaf}\")\;" + "root_index.emplace(\"${dirpath}\", &${sym}_dir.index_entry)\;" + ) +endfunction() + +function(cmrc_add_resources name) + get_target_property(is_reslib ${name} CMRC_IS_RESOURCE_LIBRARY) + if(NOT TARGET ${name} OR NOT is_reslib) + message(SEND_ERROR "cmrc_add_resources called on target '${name}' which is not an existing resource library") + return() + endif() + + set(options) + set(args WHENCE PREFIX) + set(list_args) + cmake_parse_arguments(ARG "${options}" "${args}" "${list_args}" "${ARGN}") + + if(NOT ARG_WHENCE) + set(ARG_WHENCE ${CMAKE_CURRENT_SOURCE_DIR}) + endif() + _cmrc_normalize_path(ARG_WHENCE) + get_filename_component(ARG_WHENCE "${ARG_WHENCE}" ABSOLUTE) + + # Generate the identifier for the resource library's namespace + get_target_property(lib_ns "${name}" CMRC_NAMESPACE) + + get_target_property(libdir ${name} CMRC_LIBDIR) + get_target_property(target_dir ${name} SOURCE_DIR) + file(RELATIVE_PATH reldir "${target_dir}" "${CMAKE_CURRENT_SOURCE_DIR}") + if(reldir MATCHES "^\\.\\.") + message(SEND_ERROR "Cannot call cmrc_add_resources in a parent directory from the resource library target") + return() + endif() + + foreach(input IN LISTS ARG_UNPARSED_ARGUMENTS) + _cmrc_normalize_path(input) + get_filename_component(abs_in "${input}" ABSOLUTE) + # Generate a filename based on the input filename that we can put in + # the intermediate directory. + file(RELATIVE_PATH relpath "${ARG_WHENCE}" "${abs_in}") + if(relpath MATCHES "^\\.\\.") + # For now we just error on files that exist outside of the soure dir. + message(SEND_ERROR "Cannot add file '${input}': File must be in a subdirectory of ${ARG_WHENCE}") + continue() + endif() + if(DEFINED ARG_PREFIX) + _cmrc_normalize_path(ARG_PREFIX) + endif() + if(ARG_PREFIX AND NOT ARG_PREFIX MATCHES "/$") + set(ARG_PREFIX "${ARG_PREFIX}/") + endif() + get_filename_component(dirpath "${ARG_PREFIX}${relpath}" DIRECTORY) + _cmrc_register_dirs("${name}" "${dirpath}") + get_filename_component(abs_out "${libdir}/intermediate/${ARG_PREFIX}${relpath}.cpp" ABSOLUTE) + # Generate a symbol name relpath the file's character array + _cm_encode_fpath(sym "${relpath}") + # Get the symbol name for the parent directory + if(dirpath STREQUAL "") + set(parent_sym root_directory) + else() + _cm_encode_fpath(parent_sym "${dirpath}") + endif() + # Generate the rule for the intermediate source file + _cmrc_generate_intermediate_cpp(${lib_ns} ${sym} "${abs_out}" "${abs_in}") + target_sources(${name} PRIVATE "${abs_out}") + set_property(TARGET ${name} APPEND PROPERTY CMRC_EXTERN_DECLS + "// Pointers to ${input}" + "extern const char* const ${sym}_begin\;" + "extern const char* const ${sym}_end\;" + ) + get_filename_component(leaf "${relpath}" NAME) + set_property( + TARGET ${name} + APPEND PROPERTY CMRC_MAKE_FILES + "root_index.emplace(" + " \"${ARG_PREFIX}${relpath}\"," + " ${parent_sym}_dir.directory.add_file(" + " \"${leaf}\"," + " res_chars::${sym}_begin," + " res_chars::${sym}_end" + " )" + ")\;" + ) + endforeach() +endfunction() + +function(_cmrc_generate_intermediate_cpp lib_ns symbol outfile infile) + add_custom_command( + # This is the file we will generate + OUTPUT "${outfile}" + # These are the primary files that affect the output + DEPENDS "${infile}" "${_CMRC_SCRIPT}" + COMMAND + "${CMAKE_COMMAND}" + -D_CMRC_GENERATE_MODE=TRUE + -DNAMESPACE=${lib_ns} + -DSYMBOL=${symbol} + "-DINPUT_FILE=${infile}" + "-DOUTPUT_FILE=${outfile}" + -P "${_CMRC_SCRIPT}" + COMMENT "Generating intermediate file for ${infile}" + ) +endfunction() + +function(_cm_encode_fpath var fpath) + string(MAKE_C_IDENTIFIER "${fpath}" ident) + string(MD5 hash "${fpath}") + string(SUBSTRING "${hash}" 0 4 hash) + set(${var} f_${hash}_${ident} PARENT_SCOPE) +endfunction() diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 9e7e0e8..4d3de33 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -4,18 +4,37 @@ configure_file(config.hpp.in ${CMAKE_BINARY_DIR}/include/config.hpp) file(GLOB_RECURSE SOURCES RELATIVE ${CMAKE_CURRENT_LIST_DIR} *.cpp *.hpp) source_group(TREE ${CMAKE_CURRENT_LIST_DIR} PREFIX "Sources" FILES ${SOURCES}) -add_library(moonloader SHARED ${SOURCES}) -target_link_libraries(moonloader PRIVATE - gmod::common - gmod::detouring - gmod::helpers_extended - sourcesdk::common - sourcesdk::tier0 - sourcesdk::tier1 - - efsw - moonengine +add_library(moonloader SHARED + # Minimal set of files for clientside + main.cpp + global.hpp + utils.hpp + lua_api.cpp lua_api.hpp ) +if(NOT ${CLIENT_DLL}) + target_sources(moonloader PRIVATE ${SOURCES}) + + target_link_libraries(moonloader PRIVATE + gmod::common + gmod::detouring + gmod::helpers_extended + sourcesdk::common + sourcesdk::tier0 + sourcesdk::tier1 + + efsw + moonengine + ) + + if(APPLE) + target_link_libraries(moonloader PRIVATE sourcesdk::lzma) + endif() +else() + target_link_libraries(moonloader PRIVATE + gmod::common + moonengine + ) +endif() target_include_directories(moonloader PRIVATE ${CMAKE_CURRENT_LIST_DIR} diff --git a/source/autorefresh.cpp b/source/autorefresh.cpp new file mode 100644 index 0000000..0c1b866 --- /dev/null +++ b/source/autorefresh.cpp @@ -0,0 +1,145 @@ +#include "autorefresh.hpp" +#include + +using namespace MoonLoader; + +// This code purpose is to only fix autorefresh issues on OSX +// If platform isn't OSX, no need in implementing anything + +#if !SYSTEM_IS_MACOSX +bool AutoRefresh::Sync(std::string_view path) { return true; } +void AutoRefresh::Initialize() {} +void AutoRefresh::Deinitialize() {} +#else +#include "global.hpp" +#include "utils.hpp" +#include "filesystem.hpp" +#include +#include +#include +#include +#include +#include +#include + +INetworkStringTable* g_ClientLuaFiles = nullptr; + +inline size_t MaximumCompressedSize(size_t inputSize) { + return (inputSize+1) + (inputSize+1) / 3 + 128; +} + +inline std::array GetSHA256(const char* data, size_t len) { + CSha256 hash; + std::array result{}; + Sha256_Init(&hash); + Sha256_Update(&hash, (const uint8_t*)data, len); + Sha256_Final(&hash, (uint8_t*)result.data()); + return result; +} + +std::vector Compress(const std::string& input) { + // The data is written: + // 5 bytes: props + // 8 bytes: uncompressed size + // the rest: the actual data + const size_t iInputLength = input.size() + 1; + size_t props_size = LZMA_PROPS_SIZE; + size_t iDestSize = iInputLength + iInputLength / 3 + 128; + + std::vector output(iDestSize + LZMA_PROPS_SIZE + 8, 0); + + const uint8_t* pInputData = reinterpret_cast(input.c_str()); + uint8_t* pPropStart = reinterpret_cast(output.data()); + uint8_t* pSizeStart = pPropStart + LZMA_PROPS_SIZE; + uint8_t* pBodyStart = pSizeStart + 8; + + const int res = LzmaCompress( + pBodyStart, &iDestSize, // Dest out + pInputData, iInputLength, // Input in + pPropStart, &props_size, // Props out + 5, // level [0-9] + 65536, // dict size ( ie 1 << 4 ) + 3, + 0, + 2, + 32, + 2 + ); + + if (props_size != LZMA_PROPS_SIZE) + return { }; + + if (res != SZ_OK) + return { }; + + // Write our 8 byte LE size + pSizeStart[0] = iInputLength & 0xFF; + pSizeStart[1] = (iInputLength >> 8) & 0xFF; + pSizeStart[2] = (iInputLength >> 16) & 0xFF; + pSizeStart[3] = (iInputLength >> 24) & 0xFF; + pSizeStart[4] = 0; + pSizeStart[5] = 0; + pSizeStart[6] = 0; + pSizeStart[7] = 0; + + output.resize(iDestSize + LZMA_PROPS_SIZE + 8); + return output; +} + +inline void SendFileToClient(const std::string& path) { + std::string fileData = g_Filesystem->ReadTextFile(path, "GAME"); + if (fileData.empty()) return; + + const auto compressedData = Compress(fileData); + if (compressedData.empty()) return; + + // Type 1b + SHA256 32b + compressed substitute Xb + alignment 4b + int bufferSize = 1 + 32 + compressedData.size() + 4; + // (File path + NUL) Xb + (SHA256 + compressed substitute) size 4b + bufferSize += path.size() + 1 + 4; + + std::vector buffer(bufferSize, 0); + bf_write writer( "moonloader SendLuaFile buffer", buffer.data(), buffer.size() ); + + writer.WriteByte(1); // Type + writer.WriteString(path.c_str()); + + writer.WriteUBitLong(32 + compressedData.size(), 32); + + auto hash = GetSHA256(fileData.c_str(), fileData.size() + 1); + writer.WriteBytes(hash.data(), hash.size()); + + writer.WriteBytes(compressedData.data(), compressedData.size()); + + DevMsg("[Moonloader] Autorefreshing file %s (payload size %d bytes)\n", path.c_str(), writer.GetNumBytesWritten()); + + // TODO: Send to all clients + g_EngineServer->GMOD_SendToClient(0, writer.GetData(), writer.GetNumBitsWritten()); +} + +bool AutoRefresh::Sync(std::string_view path) { + if (!g_ClientLuaFiles) return false; + std::string fullPath = Utils::JoinPaths(CACHE_PATH_LUA, path); + Utils::SetFileExtension(fullPath, "lua"); + + int fileID = g_ClientLuaFiles->FindStringIndex(fullPath.c_str()); + if (fileID == (uint16)-1) return false; + + SendFileToClient(fullPath); + return true; +} + +void AutoRefresh::Initialize() { + DevMsg("[Moonloader] Fixing file autorefresh for OSX...\n"); + if (g_EngineServer) { + INetworkStringTableContainer* container = Utils::LoadInterface("engine", INTERFACENAME_NETWORKSTRINGTABLESERVER); + if (container) g_ClientLuaFiles = container->FindTable("client_lua_files"); + } + if (!g_ClientLuaFiles) Warning("[Moonloader] Failed to find stringtable for fixing autorefresh :(\n"); +} + +void AutoRefresh::Deinitialize() { + g_ClientLuaFiles = nullptr; +} + +#endif diff --git a/source/autorefresh.hpp b/source/autorefresh.hpp new file mode 100644 index 0000000..7016533 --- /dev/null +++ b/source/autorefresh.hpp @@ -0,0 +1,14 @@ +#ifndef MOONLOADER_AUTOREFRESH_HPP +#define MOONLOADER_AUTOREFRESH_HPP + +#pragma once + +#include + +namespace MoonLoader::AutoRefresh { + bool Sync(std::string_view path); + void Initialize(); + void Deinitialize(); +} + +#endif // MOONLOADER_AUTOREFRESH_HPP diff --git a/source/compiler.cpp b/source/compiler.cpp index 32a41a5..c79b002 100644 --- a/source/compiler.cpp +++ b/source/compiler.cpp @@ -22,8 +22,8 @@ size_t LookupLineFromOffset(std::string_view str, size_t offset) { } namespace MoonLoader { - bool Compiler::WasModified(const std::string& path) { - size_t lastModification = g_pFilesystem->GetFileTime(path, GMOD_LUA_PATH_ID); + bool Compiler::WasModified(GarrysMod::Lua::ILuaInterface* LUA, const std::string& path) { + size_t lastModification = g_Filesystem->GetFileTime(path, LUA->GetPathID()); auto debug = GetDebugInfo(path); if (debug) { return debug->lastFileModification != lastModification; @@ -31,41 +31,41 @@ namespace MoonLoader { return true; } - bool Compiler::CompileMoonScript(std::string path, bool force) { - if (!force && !WasModified(path)) { + bool Compiler::CompileMoonScript(GarrysMod::Lua::ILuaInterface* LUA, std::string path, bool force) { + if (!force && !WasModified(LUA, path)) { return true; } - auto readData = g_pFilesystem->ReadBinaryFile(path, GMOD_LUA_PATH_ID); + auto readData = g_Filesystem->ReadBinaryFile(path, LUA->GetPathID()); if (readData.empty()) return false; // Watch for changes of .moon file - g_pWatchdog->WatchFile(path, GMOD_LUA_PATH_ID); + g_Watchdog->WatchFile(path, LUA->GetPathID()); MoonEngine::Engine::CompiledLines lines; - auto data = g_pMoonEngine->CompileString(readData.data(), readData.size(), &lines); + auto data = g_MoonEngine->CompileString(readData.data(), readData.size(), &lines); if (data.empty()) return false; // Create directories for .lua file std::string dir = path; - g_pFilesystem->StripFileName(dir); - g_pFilesystem->CreateDirs(dir.c_str(), "MOONLOADER"); + g_Filesystem->StripFileName(dir); + g_Filesystem->CreateDirs(dir.c_str(), "MOONLOADER"); // Write compiled code to .lua file std::string compiledPath = path; - g_pFilesystem->SetFileExtension(compiledPath, "lua"); - if (!g_pFilesystem->WriteToFile(compiledPath, "MOONLOADER", data.c_str(), data.size())) + g_Filesystem->SetFileExtension(compiledPath, "lua"); + if (!g_Filesystem->WriteToFile(compiledPath, "MOONLOADER", data.c_str(), data.size())) return false; // Add debug information - std::string fullSourcePath = g_pFilesystem->RelativeToFullPath(path, GMOD_LUA_PATH_ID); - fullSourcePath = g_pFilesystem->FullToRelativePath(fullSourcePath, "garrysmod"); // Platform is the root directory of the game, after garrysmod/ + std::string fullSourcePath = g_Filesystem->RelativeToFullPath(path, LUA->GetPathID()); + fullSourcePath = g_Filesystem->FullToRelativePath(fullSourcePath, "garrysmod"); // Platform is the root directory of the game, after garrysmod/ Filesystem::Normalize(fullSourcePath); - std::string fullCompiledPath = g_pFilesystem->RelativeToFullPath(path, "MOONLOADER"); - fullCompiledPath = g_pFilesystem->FullToRelativePath(fullCompiledPath, "garrysmod"); + std::string fullCompiledPath = g_Filesystem->RelativeToFullPath(path, "MOONLOADER"); + fullCompiledPath = g_Filesystem->FullToRelativePath(fullCompiledPath, "garrysmod"); Filesystem::Normalize(fullCompiledPath); MoonDebug debug {}; @@ -73,7 +73,7 @@ namespace MoonLoader { debug.fullSourcePath = std::move(fullSourcePath); debug.compiledPath = std::move(compiledPath); debug.fullCompiledPath = std::move(fullCompiledPath); - debug.lastFileModification = g_pFilesystem->GetFileTime(debug.sourcePath, GMOD_LUA_PATH_ID); + debug.lastFileModification = g_Filesystem->GetFileTime(debug.sourcePath, LUA->GetPathID()); for (auto it = lines.begin(); it != lines.end(); ++it) { debug.lines.insert_or_assign(it->first, LookupLineFromOffset({ readData.data(), readData.size() }, it->second)); @@ -82,8 +82,8 @@ namespace MoonLoader { m_CompiledFiles.insert_or_assign(debug.sourcePath, debug); // Notice lua about compiled file - g_pLua->PushString(debug.sourcePath.c_str()); - Utils::RunHook(g_pLua, "MoonFileCompiled", 1, 0); + LUA->PushString(debug.sourcePath.c_str()); + Utils::RunHook(LUA, "MoonFileCompiled", 1, 0); return true; } diff --git a/source/compiler.hpp b/source/compiler.hpp index fada4bb..dbdd0d4 100644 --- a/source/compiler.hpp +++ b/source/compiler.hpp @@ -1,11 +1,14 @@ #ifndef MOONLOADER_COMPILER_HPP #define MOONLOADER_COMPILER_HPP +#pragma once + #include #include #include #include #include +#include namespace MoonLoader { class Compiler { @@ -36,9 +39,9 @@ namespace MoonLoader { return m_CompiledFiles.find(path) != m_CompiledFiles.end(); } - bool WasModified(const std::string& path); + bool WasModified(GarrysMod::Lua::ILuaInterface* LUA, const std::string& path); - bool CompileMoonScript(std::string path, bool force = false); + bool CompileMoonScript(GarrysMod::Lua::ILuaInterface* LUA, std::string path, bool force = false); }; } diff --git a/source/config.hpp.in b/source/config.hpp.in index 600c926..f435327 100644 --- a/source/config.hpp.in +++ b/source/config.hpp.in @@ -6,6 +6,8 @@ #define MOONLOADER_VERSION_MINOR @PROJECT_VERSION_MINOR@ #define MOONLOADER_VERSION_PATCH @PROJECT_VERSION_PATCH@ #define MOONLOADER_GIT_HASH "@GIT_HASH@" +#define MOONLOADER_GIT_BRANCH "@GIT_BRANCH@" +#define MOONLOADER_FULL_VERSION MOONLOADER_VERSION "-" MOONLOADER_GIT_BRANCH "." MOONLOADER_GIT_HASH #define MOONLOADER_URL "@PROJECT_HOMEPAGE_URL@" #endif // MOONLOADER_CONFIG_HPP \ No newline at end of file diff --git a/source/filesystem.cpp b/source/filesystem.cpp index d5c17ef..d0d64fc 100644 --- a/source/filesystem.cpp +++ b/source/filesystem.cpp @@ -78,12 +78,12 @@ namespace MoonLoader { std::string Filesystem::RelativeToFullPath(const std::string& path, const char* pathID) { std::lock_guard lock(m_IOLock); - bool success = g_pFullFileSystem->RelativePathToFullPath(path.c_str(), pathID, PathBuffer(), PathBufferSize()) != NULL; + bool success = m_InternalFS->RelativePathToFullPath(path.c_str(), pathID, PathBuffer(), PathBufferSize()) != NULL; return success ? std::string(PathBuffer()) : std::string{}; } std::string Filesystem::FullToRelativePath(const std::string& fullPath, const char* pathID) { std::lock_guard lock(m_IOLock); - bool success = g_pFullFileSystem->FullPathToRelativePathEx(fullPath.c_str(), pathID, PathBuffer(), PathBufferSize()); + bool success = m_InternalFS->FullPathToRelativePathEx(fullPath.c_str(), pathID, PathBuffer(), PathBufferSize()); return success ? std::string(PathBuffer()) : std::string{}; } std::string Filesystem::TransverseRelativePath(const std::string& relativePath, const char* fromPathID, const char* toPathID) { @@ -132,30 +132,46 @@ namespace MoonLoader { void Filesystem::CreateDirs(const std::string& path, const char* pathID) { std::lock_guard lock(m_IOLock); - g_pFullFileSystem->CreateDirHierarchy(path.c_str(), pathID); + m_InternalFS->CreateDirHierarchy(path.c_str(), pathID); } std::vector Filesystem::ReadBinaryFile(const std::string& path, const char* pathID) { std::lock_guard lock(m_IOLock); std::vector result = {}; - FileHandle_t fh = g_pFullFileSystem->Open(path.c_str(), "rb", pathID); + FileHandle_t fh = m_InternalFS->Open(path.c_str(), "rb", pathID); if (fh) { - int fileSize = g_pFullFileSystem->Size(fh); + int fileSize = m_InternalFS->Size(fh); result.resize(fileSize); - g_pFullFileSystem->Read(result.data(), result.size(), fh); - g_pFullFileSystem->Close(fh); + m_InternalFS->Read(result.data(), result.size(), fh); + m_InternalFS->Close(fh); } return result; } + std::string Filesystem::ReadTextFile(const std::string& path, const char* pathID) { + // Yeah, this is the same as ReadBinaryFile + // but also copies data to string + // I don't know how to return std::string without copying data + std::lock_guard lock(m_IOLock); + std::vector result = {}; + FileHandle_t fh = m_InternalFS->Open(path.c_str(), "r", pathID); + if (fh) { + int fileSize = m_InternalFS->Size(fh); + result.resize(fileSize); + + m_InternalFS->Read(result.data(), result.size(), fh); + m_InternalFS->Close(fh); + } + return { result.data(), result.size() }; + } bool Filesystem::WriteToFile(const std::string& path, const char* pathID, const void* data, size_t len) { std::lock_guard lock(m_IOLock); - FileHandle_t fh = g_pFullFileSystem->Open(path.c_str(), "wb", pathID); + FileHandle_t fh = m_InternalFS->Open(path.c_str(), "wb", pathID); if (!fh) return false; - int written = g_pFullFileSystem->Write(data, len, fh); - g_pFullFileSystem->Close(fh); + int written = m_InternalFS->Write(data, len, fh); + m_InternalFS->Close(fh); return written == len; } @@ -164,6 +180,15 @@ namespace MoonLoader { return m_InternalFS->GetFileTime(path.c_str(), pathID); } + void Filesystem::AddSearchPath(const std::string& path, const char* pathID, bool addToFront) { + std::lock_guard lock(m_IOLock); + m_InternalFS->AddSearchPath(path.c_str(), pathID, addToFront ? PATH_ADD_TO_HEAD : PATH_ADD_TO_TAIL); + } + void Filesystem::RemoveSearchPath(const std::string& path, const char* pathID) { + std::lock_guard lock(m_IOLock); + m_InternalFS->RemoveSearchPath(path.c_str(), pathID); + } + // ---------------------------- FileFinder ---------------------------- Filesystem::FileFinder::iterator Filesystem::FileFinder::INVALID_ITERATOR = {}; @@ -188,11 +213,11 @@ namespace MoonLoader { FileFindHandle_t findHandle; m_Filesystem->m_IOLock.lock(); - const char* pFilename = g_pFullFileSystem->FindFirstEx(m_SearchWildcard.c_str(), m_PathID.c_str(), &findHandle); + const char* pFilename = m_Filesystem->m_InternalFS->FindFirstEx(m_SearchWildcard.c_str(), m_PathID.c_str(), &findHandle); while (pFilename != NULL && (strcmp(pFilename, ".") == 0 || strcmp(pFilename, "..") == 0)) { // Skip . and .. - pFilename = g_pFullFileSystem->FindNext(findHandle); + pFilename = m_Filesystem->m_InternalFS->FindNext(findHandle); } m_Filesystem->m_IOLock.unlock(); diff --git a/source/filesystem.hpp b/source/filesystem.hpp index ec31e87..95bbe41 100644 --- a/source/filesystem.hpp +++ b/source/filesystem.hpp @@ -64,9 +64,13 @@ namespace MoonLoader { void CreateDirs(const std::string& path, const char* pathID = 0); std::vector ReadBinaryFile(const std::string& path, const char* pathID = 0); + std::string ReadTextFile(const std::string& path, const char* pathID = 0); bool WriteToFile(const std::string& path, const char* pathID, const void* data, size_t len); size_t GetFileTime(const std::string& path, const char* pathID = 0); + + void AddSearchPath(const std::string& path, const char* pathID = 0, bool addToFront = false); + void RemoveSearchPath(const std::string& path, const char* pathID = 0); }; class Filesystem::FileFinder { diff --git a/source/global.hpp b/source/global.hpp index 3b1f5f9..88a6d21 100644 --- a/source/global.hpp +++ b/source/global.hpp @@ -1,7 +1,29 @@ #ifndef MOONLOADER_GLOBAL_HPP #define MOONLOADER_GLOBAL_HPP +#pragma once + #include +#include +#include +#include + +class IServer; +class IVEngineServer; + +#if IS_SERVERSIDE +#if ARCHITECTURE_IS_X86 +#include +#else +#include +#endif + +#include +#else +struct Color { + uint8_t r, g, b, a; +}; +#endif namespace GarrysMod::Lua { class ILuaInterface; @@ -16,14 +38,26 @@ namespace MoonLoader { class Watchdog; class Filesystem; - extern GarrysMod::Lua::ILuaInterface* g_pLua; - extern std::unique_ptr g_pMoonEngine; - extern std::unique_ptr g_pCompiler; - extern std::unique_ptr g_pWatchdog; - extern std::unique_ptr g_pFilesystem; - extern const char* GMOD_LUA_PATH_ID; + static Color MESSAGE_COLOR = {255, 236, 153, 255}; + #define CACHE_PATH "cache/moonloader/" + #define CACHE_PATH_LUA "cache/moonloader/lua/" + +#if IS_SERVERSIDE + constexpr int MAX_INITIALIZE_COUNT = 2; +#else + constexpr int MAX_INITIALIZE_COUNT = 1; +#endif + + extern std::atomic g_InitializeCount; + extern std::unique_ptr g_MoonEngine; - void StartVersionCheck(GarrysMod::Lua::ILuaInterface* LUA); +#if IS_SERVERSIDE + extern std::unordered_set g_LuaStates; + extern IVEngineServer* g_EngineServer; + extern std::unique_ptr g_Compiler; + extern std::unique_ptr g_Watchdog; + extern std::unique_ptr g_Filesystem; +#endif } #endif //MOONLOADER_GLOBAL_HPP \ No newline at end of file diff --git a/source/lua_api.cpp b/source/lua_api.cpp new file mode 100644 index 0000000..448f3f4 --- /dev/null +++ b/source/lua_api.cpp @@ -0,0 +1,146 @@ +#include "lua_api.hpp" +#include "config.hpp" +#include "global.hpp" + +#include +#include + +#if IS_SERVERSIDE +#include "compiler.hpp" +#include "utils.hpp" +#include "filesystem.hpp" +#include + +GarrysMod::Lua::AutoReference g_AddCSLuaFileRef{}; +#endif + +using namespace MoonLoader; + +#if IS_SERVERSIDE +bool CPreCacheFile(GarrysMod::Lua::ILuaInterface* LUA, const std::string& path) { + DevMsg("[Moonloader] Precaching %s\n", path.c_str()); + return g_Compiler->CompileMoonScript(LUA, path); +} + +void CPreCacheDir(GarrysMod::Lua::ILuaInterface* LUA, const std::string& startPath) { + for (auto file : g_Filesystem->Find(Utils::JoinPaths(startPath, "*"), LUA->GetPathID())) { + std::string path = Utils::JoinPaths(startPath, file); + if (g_Filesystem->IsDirectory(path, LUA->GetPathID())) { + CPreCacheDir(LUA, path); + } else if (Utils::FileExtension(path) == "moon") { + CPreCacheFile(LUA, path); + } + } +} +#endif + +namespace Functions { + LUA_FUNCTION(EmptyFunc) { + return 0; + } + + LUA_FUNCTION(ToLua) { + LUA->CheckType(1, GarrysMod::Lua::Type::String); + unsigned int codeLen = 0; + const char* code = LUA->GetString(1, &codeLen); + + std::string result; + MoonEngine::Engine::CompiledLines lines; + bool success = g_MoonEngine->CompileStringEx(code, codeLen, result, &lines); + + if (success) { + // lua_code + LUA->PushString(result.c_str(), result.size()); + + // line_table + LUA->CreateTable(); + for (auto& line : lines) { + LUA->PushNumber(line.first); + LUA->PushNumber(line.second); + LUA->SetTable(-3); + } + } else { + LUA->PushNil(); + LUA->PushString(result.c_str(), result.size()); + } + + return 2; + } + +#if IS_SERVERSIDE + LUA_FUNCTION(PreCacheDir) { + CPreCacheDir(reinterpret_cast(LUA), LUA->CheckString(1)); + return 0; + } + + LUA_FUNCTION(PreCacheFile) { + LUA->PushBool(CPreCacheFile(reinterpret_cast(LUA), LUA->CheckString(1))); + return 1; + } + + LUA_FUNCTION(AddCSLuaFile) { + GarrysMod::Lua::ILuaInterface* ILUA = reinterpret_cast(LUA); + if (g_AddCSLuaFileRef) { + if (!LUA->IsType(1, GarrysMod::Lua::Type::String)) { + LUA->ReferencePush(g_AddCSLuaFileRef); + LUA->Call(0, 0); + return 0; + } + + std::string targetFile = LUA->GetString(1); + if (Utils::FindMoonScript(ILUA, targetFile)) { + g_Compiler->CompileMoonScript(ILUA, targetFile); + Utils::SetFileExtension(targetFile, "lua"); + } + + LUA->ReferencePush(g_AddCSLuaFileRef); + LUA->PushString(targetFile.c_str()); + LUA->Call(1, 0); + } else { + Warning("Oh no! Did moonloader broke AddCSLuaFile? That's not good!\n"); + } + return 0; + } +#endif +} + +void LuaAPI::Initialize(GarrysMod::Lua::ILuaInterface* LUA) { + LUA->CreateTable(); + LUA->PushString("gm_moonloader"); LUA->SetField(-2, "_NAME"); + LUA->PushString("Pika-Software"); LUA->SetField(-2, "_AUTHORS"); + LUA->PushString(MOONLOADER_FULL_VERSION); LUA->SetField(-2, "_VERSION"); + LUA->PushNumber(MOONLOADER_VERSION_MAJOR); LUA->SetField(-2, "_VERSION_MAJOR"); + LUA->PushNumber(MOONLOADER_VERSION_MINOR); LUA->SetField(-2, "_VERSION_MINOR"); + LUA->PushNumber(MOONLOADER_VERSION_PATCH); LUA->SetField(-2, "_VERSION_PATCH"); + LUA->PushString(MOONLOADER_URL); LUA->SetField(-2, "_URL"); + + LUA->PushCFunction(Functions::ToLua); LUA->SetField(-2, "ToLua"); + LUA->PushCFunction(Functions::EmptyFunc); LUA->SetField(-2, "PreCacheDir"); + LUA->PushCFunction(Functions::EmptyFunc); LUA->SetField(-2, "PreCacheFile"); + +#if IS_SERVERSIDE + LUA->PushCFunction(Functions::PreCacheDir); LUA->SetField(-2, "PreCacheDir"); + LUA->PushCFunction(Functions::PreCacheFile); LUA->SetField(-2, "PreCacheFile"); +#endif + LUA->SetField(GarrysMod::Lua::INDEX_GLOBAL, "moonloader"); + +#if IS_SERVERSIDE + if (LUA->IsServer()) { + if (!g_AddCSLuaFileRef) { + LUA->GetField(GarrysMod::Lua::INDEX_GLOBAL, "AddCSLuaFile"); + if (LUA->IsType(-1, GarrysMod::Lua::Type::Function)) g_AddCSLuaFileRef = GarrysMod::Lua::AutoReference(LUA); + else LUA->Pop(); + } + + LUA->PushCFunction(Functions::AddCSLuaFile); + LUA->SetField(GarrysMod::Lua::INDEX_GLOBAL, "AddCSLuaFile"); + } +#endif +} + +void LuaAPI::Deinitialize() { +#if IS_SERVERSIDE + g_AddCSLuaFileRef.Free(); +#endif +} + diff --git a/source/lua_api.hpp b/source/lua_api.hpp new file mode 100644 index 0000000..4e5054a --- /dev/null +++ b/source/lua_api.hpp @@ -0,0 +1,13 @@ +#ifndef MOONLOADER_LUA_API_HPP +#define MOONLOADER_LUA_API_HPP + +#include + +namespace MoonLoader::LuaAPI { + void Initialize(GarrysMod::Lua::ILuaInterface* LUA); + void Deinitialize(); + + void BeginVersionCheck(GarrysMod::Lua::ILuaInterface* LUA); +} + +#endif //MOONLOADER_LUA_API_HPP diff --git a/source/main.cpp b/source/main.cpp index 44471f7..dd5b027 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,132 +1,54 @@ #include "global.hpp" +#include "config.hpp" +#include "utils.hpp" +#include "lua_api.hpp" +#include +#include +#include + +#if IS_SERVERSIDE #include "compiler.hpp" #include "watchdog.hpp" #include "filesystem.hpp" -#include "utils.hpp" -#include "config.hpp" +#include "autorefresh.hpp" -#include -#include -#include -#include -#include #include + +#include #include -#include -#include -#include +#include +#include +#include extern "C" { #include } - -const char* MoonLoader::GMOD_LUA_PATH_ID = nullptr; -IFileSystem* g_pFullFileSystem = nullptr; -Detouring::Hook lua_getinfo_hook; -std::unordered_set g_IncludedFiles; -int g_AddCSLuaFileRef = -1; +#endif using namespace MoonLoader; -GarrysMod::Lua::ILuaInterface* MoonLoader::g_pLua = nullptr; -std::unique_ptr MoonLoader::g_pMoonEngine; -std::unique_ptr MoonLoader::g_pCompiler; -std::unique_ptr MoonLoader::g_pWatchdog; -std::unique_ptr MoonLoader::g_pFilesystem; +std::atomic MoonLoader::g_InitializeCount = 0; +std::unique_ptr MoonLoader::g_MoonEngine; -bool CPreCacheFile(const std::string& path) { - DevMsg("[Moonloader] Precaching %s\n", path.c_str()); - return g_pCompiler->CompileMoonScript(path); -} +#if IS_SERVERSIDE +Detouring::Hook lua_getinfo_hook; +std::unordered_map> g_IncludedFiles; -void CPreCacheDir(const std::string& startPath) { - for (auto file : g_pFilesystem->Find(Filesystem::Join(startPath, "*"), GMOD_LUA_PATH_ID)) { - std::string path = Filesystem::Join(startPath, file); - if (g_pFilesystem->IsDirectory(path, GMOD_LUA_PATH_ID)) { - CPreCacheDir(path); - } - else if (Filesystem::FileExtension(path) == "moon") { - CPreCacheFile(path); - } - } -} - -template -T* LoadInterface(const char* moduleName, const char* version) { - SourceSDK::FactoryLoader module(moduleName); - return module.GetInterface(version); -} +std::unordered_set MoonLoader::g_LuaStates; +IVEngineServer* MoonLoader::g_EngineServer = nullptr; +std::unique_ptr MoonLoader::g_Compiler; +std::unique_ptr MoonLoader::g_Watchdog; +std::unique_ptr MoonLoader::g_Filesystem; IFileSystem* LoadFilesystem() { - auto iface = LoadInterface("filesystem_stdio", FILESYSTEM_INTERFACE_VERSION); + auto iface = Utils::LoadInterface("filesystem_stdio", FILESYSTEM_INTERFACE_VERSION); return iface != nullptr ? iface : InterfacePointers::FileSystem(); } -namespace LuaFuncs { - LUA_FUNCTION(ToLua) { - LUA->CheckType(1, GarrysMod::Lua::Type::String); - unsigned int codeLen = 0; - const char* code = LUA->GetString(1, &codeLen); - - std::string result; - MoonEngine::Engine::CompiledLines lines; - bool success = g_pMoonEngine->CompileStringEx(code, codeLen, result, &lines); - - if (success) { - // lua_code - LUA->PushString(result.c_str(), result.size()); - - // line_table - LUA->CreateTable(); - for (auto& line : lines) { - LUA->PushNumber(line.first); - LUA->PushNumber(line.second); - LUA->SetTable(-3); - } - } else { - LUA->PushNil(); - LUA->PushString(result.c_str(), result.size()); - } - - return 2; - } - - LUA_FUNCTION(PreCacheDir) { - CPreCacheDir(LUA->CheckString(1)); - return 0; - } - - LUA_FUNCTION(PreCacheFile) { - LUA->PushBool(CPreCacheFile(LUA->CheckString(1))); - return 1; - } - - LUA_FUNCTION(AddCSLuaFile) { - if (g_AddCSLuaFileRef != -1) { - if (!LUA->IsType(1, GarrysMod::Lua::Type::String)) { - LUA->ReferencePush(g_AddCSLuaFileRef); - LUA->Call(0, 0); - return 0; - } - - std::string targetFile = LUA->GetString(1); - if (Utils::FindMoonScript(targetFile)) { - g_pCompiler->CompileMoonScript(targetFile); - Filesystem::SetFileExtension(targetFile, "lua"); - } - - LUA->ReferencePush(g_AddCSLuaFileRef); - LUA->PushString(targetFile.c_str()); - LUA->Call(1, 0); - } - return 0; - } -} - class ILuaInterfaceProxy : public Detouring::ClassProxy { public: - bool Init() { - Initialize(g_pLua); + bool Init(GarrysMod::Lua::ILuaInterface* LUA) { + Initialize(LUA); return Hook(&GarrysMod::Lua::ILuaInterface::FindAndRunScript, &ILuaInterfaceProxy::FindAndRunScript) && Hook(&GarrysMod::Lua::ILuaInterface::Cycle, &ILuaInterfaceProxy::Cycle); } @@ -138,25 +60,24 @@ class ILuaInterfaceProxy : public Detouring::ClassProxyThink(); // Watch for file changes - } + g_Watchdog->Think(This()); // Watch for file changes } virtual bool FindAndRunScript(const char* fileName, bool run, bool showErrors, const char* runReason, bool noReturns) { - if (This() != g_pLua || fileName == NULL) { + auto LUA = This(); + if (fileName == NULL || LUA->IsClient()) { return Call(&GarrysMod::Lua::ILuaInterface::FindAndRunScript, fileName, run, showErrors, runReason, noReturns); } std::string path = fileName; - if (Utils::FindMoonScript(path)) { - g_pCompiler->CompileMoonScript(path); + if (Utils::FindMoonScript(LUA, path)) { + auto& includedFiles = (*g_IncludedFiles.try_emplace(LUA).first).second; if (runReason[0] != '!') { // Usually when runReason doesn't start with "!", // it means that it was included by "include" // Allow auto-reloads for this guy in a future - g_IncludedFiles.insert(path); + includedFiles.insert(path); } if (strcmp(runReason, "!RELOAD") == 0) { @@ -167,14 +88,19 @@ class ILuaInterfaceProxy : public Detouring::ClassProxyCompileMoonScript(path); + if (g_Compiler->CompileMoonScript(LUA, path)) { + // If file was reloaded, then we need to reload it on clients (for OSX only ofc) + if (strcmp(runReason, "!RELOAD") == 0) { + AutoRefresh::Sync(path); + } + } path = fileName; // Preserve original file path for the god's sake Utils::SetFileExtension(path, "lua"); // Do not forget to pass lua file to gmod!! @@ -183,21 +109,21 @@ class ILuaInterfaceProxy : public Detouring::ClassProxy Singleton; }; -ILuaInterfaceProxy* ILuaInterfaceProxy::Singleton; +std::unique_ptr ILuaInterfaceProxy::Singleton; typedef int (*lua_getinfo_t)(lua_State* L, const char* what, lua_Debug* ar); int lua_getinfo_detour(lua_State* L, const char* what, lua_Debug* ar) { int ret = lua_getinfo_hook.GetTrampoline()(L, what, ar); if (ret != 0) { - // File stored in cache/moonloader, so it must be our compiled script? - if (Utils::StartsWith(ar->short_src, "cache/moonloader/lua")) { - // Yeah, that's weird path transversies, but it works - std::string path = g_pFilesystem->TransverseRelativePath(ar->short_src, "GAME", GMOD_LUA_PATH_ID); - Filesystem::SetFileExtension(path, "moon"); + // File stored in cache/moonloader/lua, so it must be our compiled script? + if (Utils::StartsWith(ar->short_src, CACHE_PATH_LUA)) { + std::string path = ar->short_src; + Utils::RemovePrefix(path, CACHE_PATH_LUA); + Utils::SetFileExtension(path, "moon"); - auto debugInfo = g_pCompiler->GetDebugInfo(path); + auto debugInfo = g_Compiler->GetDebugInfo(path); if (debugInfo) { strncpy(ar->short_src, debugInfo->fullSourcePath.c_str(), sizeof(ar->short_src)); ar->currentline = debugInfo->lines[ar->currentline]; @@ -206,96 +132,112 @@ int lua_getinfo_detour(lua_State* L, const char* what, lua_Debug* ar) { } return ret; } +#endif GMOD_MODULE_OPEN() { - DevMsg("Moonloader %s-%s made by Pika-Software (%s)\n", MOONLOADER_VERSION, MOONLOADER_GIT_HASH, MOONLOADER_URL); + GarrysMod::Lua::ILuaInterface* ILUA = reinterpret_cast(LUA); + if (Utils::DeveloperEnabled(LUA)) { + ILUA->MsgColour(MESSAGE_COLOR, "Moonloader %s made by Pika-Software (%s)\n", MOONLOADER_FULL_VERSION, MOONLOADER_URL); + } - g_pLua = reinterpret_cast(LUA); - MoonLoader::GMOD_LUA_PATH_ID = g_pLua->GetPathID(); + if (!g_MoonEngine) g_MoonEngine = std::make_unique(); + if (!g_MoonEngine->IsInitialized()) + LUA->ThrowError("failed to initialize moonengine"); - g_pFullFileSystem = LoadFilesystem(); - if (!g_pFullFileSystem) - LUA->ThrowError("failed to get IFileSystem"); + // Why do you need to initialize moonloader more than once? + // Because serverside module can be initialized from menu and server! + g_InitializeCount++; + if (g_InitializeCount > MAX_INITIALIZE_COUNT) LUA->ThrowError("you have tried to initialize moonloader too many times"); - g_pFilesystem = std::make_unique(g_pFullFileSystem); - g_pMoonEngine = std::make_unique(); - g_pCompiler = std::make_unique(); - g_pWatchdog = std::make_unique(); +#if IS_SERVERSIDE + if (g_LuaStates.find(ILUA) != g_LuaStates.end()) LUA->ThrowError("moonloader is already initialized for this lua state >:("); + g_LuaStates.insert(ILUA); - if (!g_pMoonEngine->IsInitialized()) - LUA->ThrowError("failed to initialize moonengine"); + IFileSystem* filesystem = LoadFilesystem(); + if (!filesystem) + LUA->ThrowError("failed to get filesystem interface"); + + g_EngineServer = InterfacePointers::VEngineServer(); + + if (g_InitializeCount == 1) { + DevMsg("[Moonloader] Initializing...\n"); + g_Filesystem = std::make_unique(filesystem); + g_Compiler = std::make_unique(); + g_Watchdog = std::make_unique(); - ILuaInterfaceProxy::Singleton = new ILuaInterfaceProxy; - if (!ILuaInterfaceProxy::Singleton->Init()) - LUA->ThrowError("failed to initialize lua detouring"); - - // Cleanup old cache - int removed = g_pFilesystem->Remove("cache/moonloader/lua", "GAME"); - DevMsg("[Moonloader] Removed %d files from cache\n", removed); - - // Create cache directories, and add them to search path - g_pFilesystem->CreateDirs("cache/moonloader/lua"); - g_pFullFileSystem->AddSearchPath("garrysmod/cache/moonloader", "GAME", PATH_ADD_TO_HEAD); - g_pFullFileSystem->AddSearchPath("garrysmod/cache/moonloader/lua", MoonLoader::GMOD_LUA_PATH_ID, PATH_ADD_TO_HEAD); - g_pFullFileSystem->AddSearchPath("garrysmod/cache/moonloader/lua", "MOONLOADER", PATH_ADD_TO_HEAD); - - LUA->CreateTable(); - LUA->PushString("gm_moonloader"); LUA->SetField(-2, "_NAME"); - LUA->PushString("Pika-Software"); LUA->SetField(-2, "_AUTHORS"); - LUA->PushString(MOONLOADER_VERSION "-" MOONLOADER_GIT_HASH); LUA->SetField(-2, "_VERSION"); - LUA->PushNumber(MOONLOADER_VERSION_MAJOR); LUA->SetField(-2, "_VERSION_MAJOR"); - LUA->PushNumber(MOONLOADER_VERSION_MINOR); LUA->SetField(-2, "_VERSION_MINOR"); - LUA->PushNumber(MOONLOADER_VERSION_PATCH); LUA->SetField(-2, "_VERSION_PATCH"); - LUA->PushString(MOONLOADER_URL); LUA->SetField(-2, "_URL"); + DevMsg("[Moonloader] Interfaces:\n"); + DevMsg("\t- IFilesystem: %p\n", filesystem); + DevMsg("\t- IVEngineServer: %p\n", g_EngineServer); + + ILuaInterfaceProxy::Singleton = std::make_unique(); + if (!ILuaInterfaceProxy::Singleton->Init(ILUA)) + Warning("[Moonloader] Failed to initialize lua detouring. include and CompileFile won't work :(\n"); + + //Detour lua_getinfo and lua_pcall, so we can manipulate error stack traces + lua_getinfo_hook.Create( Utils::LoadSymbol("lua_shared", "lua_getinfo"), reinterpret_cast(&lua_getinfo_detour) ); + if (!lua_getinfo_hook.Enable()) Warning("[Moonloader] Failed to detour lua_getinfo. Advanced error stack traces won't be enabled\n"); - LUA->PushCFunction(LuaFuncs::ToLua); LUA->SetField(-2, "ToLua"); - LUA->PushCFunction(LuaFuncs::PreCacheDir); LUA->SetField(-2, "PreCacheDir"); - LUA->PushCFunction(LuaFuncs::PreCacheFile); LUA->SetField(-2, "PreCacheFile"); - LUA->SetField(GarrysMod::Lua::INDEX_GLOBAL, "moonloader"); - - // Detour AddCSLuaFile - if (g_pLua->IsServer()) { - LUA->GetField(GarrysMod::Lua::INDEX_GLOBAL, "AddCSLuaFile"); - if (LUA->IsType(-1, GarrysMod::Lua::Type::Function)) - g_AddCSLuaFileRef = LUA->ReferenceCreate(); - else - LUA->Pop(); - - LUA->PushCFunction(LuaFuncs::AddCSLuaFile); - LUA->SetField(GarrysMod::Lua::INDEX_GLOBAL, "AddCSLuaFile"); + DevMsg("[Moonloader] Removed %d files from cache\n", g_Filesystem->Remove(CACHE_PATH_LUA, "GAME")); + + g_Filesystem->CreateDirs(CACHE_PATH_LUA); + g_Filesystem->AddSearchPath("garrysmod/" CACHE_PATH, "GAME", true); + g_Filesystem->AddSearchPath("garrysmod/" CACHE_PATH_LUA, "MOONLOADER"); } - // Detour lua_getinfo and lua_pcall, so we can manipulate error stack traces - SourceSDK::ModuleLoader lua_shared("lua_shared"); - if (!lua_getinfo_hook.Create( - lua_shared.GetSymbol("lua_getinfo"), - reinterpret_cast(&lua_getinfo_detour) - )) LUA->ThrowError("failed to detour debug.getinfo"); - if (!lua_getinfo_hook.Enable()) LUA->ThrowError("failed to enable debug.getinfo detour"); + g_Filesystem->AddSearchPath("garrysmod/" CACHE_PATH_LUA, ILUA->GetPathID(), true); + if (ILUA->IsServer()) + g_Filesystem->AddSearchPath("garrysmod/" CACHE_PATH_LUA, "lcl", true); +#endif + + LuaAPI::Initialize(ILUA); - StartVersionCheck(g_pLua); +#if IS_SERVERSIDE + LuaAPI::BeginVersionCheck(ILUA); + + if (ILUA->IsServer()) + AutoRefresh::Initialize(); +#endif return 0; } GMOD_MODULE_CLOSE() { - g_AddCSLuaFileRef = -1; - - // Deinitialize lua detouring - ILuaInterfaceProxy::Singleton->Deinit(); - delete ILuaInterfaceProxy::Singleton; - ILuaInterfaceProxy::Singleton = nullptr; - - if (!lua_getinfo_hook.Disable()) - Warning("[Moonloader] Failed to disable lua_getinfo detour\n"); - if (!lua_getinfo_hook.Destroy()) - Warning("[Moonloader] Failed to destroy lua_getinfo detour\n"); - - // Release all our interfaces - g_pWatchdog.reset(); - g_pCompiler.reset(); - g_pMoonEngine.reset(); - g_pFilesystem.reset(); + GarrysMod::Lua::ILuaInterface* ILUA = reinterpret_cast(LUA); + g_InitializeCount--; + + LuaAPI::Deinitialize(); + +#if IS_SERVERSIDE + DevMsg("[Moonloader] [%s] Moonloader is shutting down... Bye bye\n", ILUA->IsServer() ? "Server" : ILUA->IsClient() ? "Client" : "Menu"); + + g_LuaStates.erase(ILUA); + g_IncludedFiles.erase(ILUA); + + g_Filesystem->RemoveSearchPath("garrysmod/" CACHE_PATH_LUA, ILUA->GetPathID()); + if (ILUA->IsServer()) { + g_Filesystem->RemoveSearchPath("garrysmod/" CACHE_PATH_LUA, "lcl"); + + AutoRefresh::Deinitialize(); + g_EngineServer = nullptr; + } +#endif + + if (g_InitializeCount == 0) { + g_MoonEngine.reset(); + +#if IS_SERVERSIDE + DevMsg("[Moonloader] Full shutdown!\n"); + + ILuaInterfaceProxy::Singleton->Deinit(); + ILuaInterfaceProxy::Singleton.reset(); + lua_getinfo_hook.Disable(); + lua_getinfo_hook.Destroy(); + + g_Watchdog.reset(); + g_Compiler.reset(); + g_Filesystem.reset(); +#endif + } return 0; } \ No newline at end of file diff --git a/source/utils.cpp b/source/utils.cpp index 4840cb2..7db37d1 100644 --- a/source/utils.cpp +++ b/source/utils.cpp @@ -6,52 +6,19 @@ using namespace MoonLoader; -void Utils::FindValue(GarrysMod::Lua::ILuaBase* LUA, std::string_view path) { - size_t firstPos = 0; - size_t endPos = 0; - do { - firstPos = endPos; - endPos = path.find(".", endPos) + 1; - std::string name{ path.substr(firstPos, endPos != 0 ? endPos - firstPos - 1 : path.size()) }; - - LUA->GetField(firstPos == 0 ? GarrysMod::Lua::INDEX_GLOBAL : -1, name.c_str()); - if (firstPos != 0) LUA->Remove(-2); - if (!LUA->IsType(-1, GarrysMod::Lua::Type::Table)) break; - } while (endPos != 0); -} - -bool Utils::RunHook(GarrysMod::Lua::ILuaBase* LUA, const std::string& hookName, int nArgs, int nReturns) { - FindValue(LUA, "hook.Run"); - if (!LUA->IsType(-1, GarrysMod::Lua::Type::Function)) { - LUA->Pop(); - return false; - } - - LUA->Insert(-nArgs - 1); - LUA->PushString(hookName.c_str()); - LUA->Insert(-nArgs - 1); - if (LUA->PCall(nArgs + 1, nReturns, 0) != 0) { - Warning("[Moonloader] Error while running hook %s: %s\n", hookName.c_str(), LUA->GetString(-1)); - LUA->Pop(); - return false; - } - - return true; -} - -bool Utils::FindMoonScript(std::string& path) { +bool Utils::FindMoonScript(GarrysMod::Lua::ILuaInterface* LUA, std::string& path) { std::string moonPath = path; Utils::SetFileExtension(moonPath, "moon"); - const char* currentDir = g_pLua->GetPath(); + const char* currentDir = LUA->GetPath(); if (currentDir) { std::string absolutePath = Utils::JoinPaths(currentDir, moonPath); - if (g_pFilesystem->Exists(absolutePath, GMOD_LUA_PATH_ID)) { + if (g_Filesystem->Exists(absolutePath, LUA->GetPathID())) { path = std::move(absolutePath); return true; } } - if (g_pFilesystem->Exists(moonPath, GMOD_LUA_PATH_ID)) { + if (g_Filesystem->Exists(moonPath, LUA->GetPathID())) { path = std::move(moonPath); return true; } diff --git a/source/utils.hpp b/source/utils.hpp index 5d902e0..f8f2f7e 100644 --- a/source/utils.hpp +++ b/source/utils.hpp @@ -3,12 +3,18 @@ #pragma once +#include #include #include #include -#include -#include +#include #include +#include + +#if IS_SERVERSIDE +#include +#include +#endif namespace MoonLoader::Utils { // --------------------------- @@ -78,20 +84,94 @@ namespace MoonLoader::Utils { path += ext; } + inline void RemovePrefix(std::string& str, std::string_view prefix) { + if (StartsWith(str, prefix)) + str.erase(0, prefix.size()); + } + // --------------------------- // - Lua utils - // --------------------------- - void FindValue(GarrysMod::Lua::ILuaBase* LUA, std::string_view path); - bool RunHook(GarrysMod::Lua::ILuaBase* LUA, const std::string& hookName, int nArgs, int nReturns); - bool FindMoonScript(std::string& path); + inline void FindValue(GarrysMod::Lua::ILuaBase* LUA, std::string_view path) { + size_t firstPos = 0; + size_t endPos = 0; + do { + firstPos = endPos; + endPos = path.find(".", endPos) + 1; + std::string name{ path.substr(firstPos, endPos != 0 ? endPos - firstPos - 1 : path.size()) }; + + LUA->GetField(firstPos == 0 ? GarrysMod::Lua::INDEX_GLOBAL : -1, name.c_str()); + if (firstPos != 0) LUA->Remove(-2); + if (!LUA->IsType(-1, GarrysMod::Lua::Type::Table)) break; + } while (endPos != 0); + } + inline bool RunHook(GarrysMod::Lua::ILuaInterface* LUA, const std::string& hookName, int nArgs, int nReturns) { + FindValue(LUA, "hook.Run"); + if (!LUA->IsType(-1, GarrysMod::Lua::Type::Function)) { + LUA->Pop(); + return false; + } + + LUA->Insert(-nArgs - 1); + LUA->PushString(hookName.c_str()); + LUA->Insert(-nArgs - 1); + if (LUA->PCall(nArgs + 1, nReturns, 0) != 0) { + LUA->ErrorNoHalt("[MoonLoader] Failed to run hook '%s': %s\n", hookName.c_str(), LUA->GetString(-1)); + LUA->Pop(); + return false; + } + + return true; + } + inline bool PopBool(GarrysMod::Lua::ILuaBase* LUA) { + bool value = LUA->GetBool(-1); + LUA->Pop(); + return value; + } + inline std::optional LuaBoolFromValue(GarrysMod::Lua::ILuaBase* LUA, std::string_view path, int args) { + FindValue(LUA, path); + if (LUA->IsType(-1, GarrysMod::Lua::Type::Bool)) { + return PopBool(LUA); + } + else if (LUA->IsType(-1, GarrysMod::Lua::Type::Function)) { + LUA->Insert(-1 - args); + if (LUA->PCall(args, 1, 0) != 0) { + LUA->Pop(); + return std::nullopt; + } + return PopBool(LUA); + } + + LUA->Pop(); + return std::nullopt; + } + inline bool DeveloperEnabled(GarrysMod::Lua::ILuaBase* LUA) { + LUA->PushString("developer"); + return LuaBoolFromValue(LUA, "cvars.Bool", 1).value_or(false); + } + bool FindMoonScript(GarrysMod::Lua::ILuaInterface* LUA, std::string& path); // --------------------------- // - Other - // --------------------------- - inline uint64 Timestamp() { + inline uint64_t Timestamp() { // Oh, yesss! I love one-liners! return std::chrono::duration_cast(std::chrono::system_clock().now().time_since_epoch()).count(); } + +#if IS_SERVERSIDE + template + inline T* LoadInterface(const char* moduleName, const char* version) { + SourceSDK::FactoryLoader module(moduleName); + return module.GetInterface(version); + } + + template + inline T* LoadSymbol(const char* moduleName, const std::string& symbol) { + SourceSDK::ModuleLoader module(moduleName); + return reinterpret_cast(module.GetSymbol(symbol)); + } +#endif } #endif // MOONLOADER_UTILS_HPP \ No newline at end of file diff --git a/source/version_checker.cpp b/source/version_checker.cpp index 3d0a608..12770b6 100644 --- a/source/version_checker.cpp +++ b/source/version_checker.cpp @@ -1,9 +1,9 @@ +#include "lua_api.hpp" #include "global.hpp" #include "utils.hpp" #include "config.hpp" #include -#include using namespace MoonLoader; @@ -17,6 +17,7 @@ bool IsVersionGreater(int major, int minor, int patch) { } LUA_FUNCTION(ValidateVersion) { + GarrysMod::Lua::ILuaInterface* ILUA = reinterpret_cast(LUA); unsigned int bodySize = 0; const char* body = LUA->GetString(1, &bodySize); int code = LUA->GetNumber(4); @@ -24,7 +25,7 @@ LUA_FUNCTION(ValidateVersion) { if (code == 200 || bodySize <= MAX_VERSION_LENGTH) { int major, minor, patch; if (sscanf(body, "%d.%d.%d", &major, &minor, &patch) == 3 && IsVersionGreater(major, minor, patch)) { - Msg("[Mooloader] New version available: %d.%d.%d -> %d.%d.%d (%s)\n", + ILUA->MsgColour(MESSAGE_COLOR, "[Mooloader] New version available: %d.%d.%d -> %d.%d.%d (%s)\n", MOONLOADER_VERSION_MAJOR, MOONLOADER_VERSION_MINOR, MOONLOADER_VERSION_PATCH, major, minor, patch, MOONLOADER_URL @@ -45,7 +46,7 @@ LUA_FUNCTION(CheckVersion) { return 0; } -void MoonLoader::StartVersionCheck(GarrysMod::Lua::ILuaInterface* LUA) { +void LuaAPI::BeginVersionCheck(GarrysMod::Lua::ILuaInterface* LUA) { Utils::FindValue(LUA, "timer.Simple"); LUA->PushNumber(5); // Only check for an update after 5 seconds LUA->PushCFunction(CheckVersion); diff --git a/source/watchdog.cpp b/source/watchdog.cpp index 52ad159..ee192ae 100644 --- a/source/watchdog.cpp +++ b/source/watchdog.cpp @@ -18,7 +18,7 @@ void WatchdogListener::handleFileAction(efsw::WatchID watchid, const std::string if (action == efsw::Actions::Modified) { std::string path = dir + filename; Filesystem::FixSlashes(path); - g_pWatchdog->OnFileModified(path); + g_Watchdog->OnFileModified(path); } } @@ -32,7 +32,7 @@ Watchdog::Watchdog() { void Watchdog::OnFileModified(const std::string& path) { // We only care about file we are watching - std::string relativePath = g_pFilesystem->FullToRelativePath(path, GMOD_LUA_PATH_ID); + std::string relativePath = g_Filesystem->FullToRelativePath(path, "lsv"); Utils::NormalizePath(relativePath); std::lock_guard guard(m_Lock); @@ -59,7 +59,7 @@ void MoonLoader::Watchdog::WatchFile(const std::string& path, const char* pathID // Our watchdog already registered here return; - std::string fullPath = g_pFilesystem->RelativeToFullPath(path, pathID); + std::string fullPath = g_Filesystem->RelativeToFullPath(path, pathID); Filesystem::Normalize(fullPath); Filesystem::StripFileName(fullPath); if (fullPath.empty()) { @@ -72,7 +72,7 @@ void MoonLoader::Watchdog::WatchFile(const std::string& path, const char* pathID m_WatchedFiles.insert(path.c_str()); } -void Watchdog::Think() { +void Watchdog::Think(GarrysMod::Lua::ILuaInterface* LUA) { if (m_ModifiedFiles.empty()) return; @@ -88,7 +88,9 @@ void Watchdog::Think() { // Moonloader should automatically recompile script, if needed // And then run it - g_pLua->FindAndRunScript(path.c_str(), true, true, "!MOONRELOAD", true); + for (auto state : g_LuaStates) { + LUA->FindAndRunScript(path.c_str(), true, true, "!MOONRELOAD", true); + } m_ModifiedFileDelays[path] = currentTimestamp + 200; // Add 200ms delay, before we can reload file again } diff --git a/source/watchdog.hpp b/source/watchdog.hpp index cd14dc2..d304abc 100644 --- a/source/watchdog.hpp +++ b/source/watchdog.hpp @@ -10,12 +10,7 @@ #include #include #include -#include - -namespace efsw { - class FileWatcher; - typedef long WatchID; -} +#include namespace MoonLoader { class WatchdogListener : public efsw::FileWatchListener { @@ -46,7 +41,7 @@ namespace MoonLoader { // Directory path must be absolute void WatchDirectory(const std::string& path); void WatchFile(const std::string& path, const char* pathID); - void Think(); + void Think(GarrysMod::Lua::ILuaInterface* LUA); }; }