├── .clang-format ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ ├── buildit.yaml │ ├── builditmac.yaml │ ├── qt.yaml │ ├── qtmac.yaml │ └── release.yaml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cslol-tools ├── CMakeLists.txt ├── LICENSE ├── README.md ├── dep │ └── CMakeLists.txt ├── lib │ └── lol │ │ ├── common.cpp │ │ ├── common.hpp │ │ ├── error.cpp │ │ ├── error.hpp │ │ ├── fs.cpp │ │ ├── fs.hpp │ │ ├── hash │ │ ├── dict.cpp │ │ ├── dict.hpp │ │ ├── fnv1a32.cpp │ │ ├── fnv1a32.hpp │ │ ├── xxh64.cpp │ │ └── xxh64.hpp │ │ ├── io │ │ ├── buffer.cpp │ │ ├── buffer.hpp │ │ ├── bytes.cpp │ │ ├── bytes.hpp │ │ ├── file.cpp │ │ ├── file.hpp │ │ ├── sys.cpp │ │ └── sys.hpp │ │ ├── log.cpp │ │ ├── log.hpp │ │ ├── patcher │ │ ├── asm │ │ │ ├── build.sh │ │ │ ├── macos_hook.asm │ │ │ ├── win32_hook_CRYPTO_free.asm │ │ │ └── win32_hook_CreateFileA.asm │ │ ├── patcher.hpp │ │ ├── patcher_dummy.cpp │ │ ├── patcher_macos.cpp │ │ ├── patcher_win32.cpp │ │ └── utility │ │ │ ├── delay.hpp │ │ │ ├── macho.hpp │ │ │ ├── process.hpp │ │ │ └── process_macos.cpp │ │ ├── utility │ │ ├── cli.cpp │ │ ├── cli.hpp │ │ ├── magic.cpp │ │ ├── magic.hpp │ │ ├── zip.cpp │ │ └── zip.hpp │ │ └── wad │ │ ├── archive.cpp │ │ ├── archive.hpp │ │ ├── entry.cpp │ │ ├── entry.hpp │ │ ├── index.cpp │ │ ├── index.hpp │ │ ├── mounted.cpp │ │ ├── mounted.hpp │ │ ├── toc.cpp │ │ └── toc.hpp ├── res │ ├── longpath.manifest │ └── utf8.manifest └── src │ ├── main_diag.cpp │ ├── main_mod_tools.cpp │ ├── main_wad_extract.cpp │ └── main_wad_make.cpp ├── dist ├── FIX-ADMIN.reg ├── FIX-NON-ENGLISH.reg ├── SOURCE.URL ├── wad-extract-multi.bat ├── wad-make-multi.bat └── wxy-extract-multi.bat ├── docs ├── manager-0.png ├── manager-1.png └── manager-2.png ├── make-release-mac.sh ├── make-release-remote.sh ├── make-release.sh └── src ├── CSLOLTools.cpp ├── CSLOLTools.h ├── CSLOLToolsImpl.cpp ├── CSLOLToolsImpl.h ├── CSLOLUtils.cpp ├── CSLOLUtils.h ├── CSLOLVersion.h ├── main.cpp ├── qml ├── CSLOLDialogEditMod.qml ├── CSLOLDialogError.qml ├── CSLOLDialogErrorUser.qml ├── CSLOLDialogGame.qml ├── CSLOLDialogLoLPath.qml ├── CSLOLDialogNewMod.qml ├── CSLOLDialogNewModImage.qml ├── CSLOLDialogNewModRAWFolder.qml ├── CSLOLDialogNewModWadFiles.qml ├── CSLOLDialogNewProfile.qml ├── CSLOLDialogOpenZipFantome.qml ├── CSLOLDialogSaveZipFantome.qml ├── CSLOLDialogSettings.qml ├── CSLOLDialogUpdate.qml ├── CSLOLModInfoEdit.qml ├── CSLOLModsView.qml ├── CSLOLStatusBar.qml ├── CSLOLToolBar.qml ├── CSLOLToolTip.qml ├── PageMods.qml ├── PageSettings.qml ├── fontawesome-webfont.ttf ├── icon.png ├── main.qml ├── qml.qrc └── qtquickcontrols2.conf └── res ├── MacOSXBundleInfo.plist.in ├── cslol-manager.exe.manifest ├── icon.icns ├── icon.ico └── rc.rc /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Google 3 | AccessModifierOffset: '-4' 4 | AlignTrailingComments: true 5 | AllowAllArgumentsOnNextLine: false 6 | AllowAllParametersOfDeclarationOnNextLine: false 7 | BinPackArguments: 'false' 8 | BinPackParameters: 'false' 9 | ColumnLimit: '120' 10 | FixNamespaceComments: 'false' 11 | IndentWidth: '4' 12 | IndentPPDirectives: AfterHash 13 | IndentRequires: 'true' 14 | KeepEmptyLinesAtTheStartOfBlocks: 'true' 15 | NamespaceIndentation: All 16 | ReflowComments: 'false' 17 | TabWidth: '4' 18 | UseTab: Never 19 | 20 | ... 21 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [moonshadow565] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: Please fix no work 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Check if the issue has known workarounds** 11 | https://github.com/LeagueToolkit/cslol-manager/issues/136 12 | 13 | **Upload log file** 14 | You can find "log" file next to cslol-manager executable. 15 | Please upload this file. 16 | 17 | **Describe the bug** 18 | A clear and concise description of what the bug is. 19 | 20 | **To Reproduce** 21 | Steps to reproduce the behavior: 22 | 1. Go to '...' 23 | 2. Click on '....' 24 | 3. Scroll down to '....' 25 | 4. See error 26 | 27 | **Expected behavior** 28 | A clear and concise description of what you expected to happen. 29 | 30 | **Screenshots** 31 | If applicable, add screenshots to help explain your problem. 32 | 33 | **Additional context** 34 | Add any other context about the problem here. 35 | -------------------------------------------------------------------------------- /.github/workflows/buildit.yaml: -------------------------------------------------------------------------------- 1 | name: BuildIt 2 | on: 3 | workflow_dispatch: 4 | jobs: 5 | build: 6 | runs-on: windows-2022 7 | steps: 8 | - name: "Clone source" 9 | uses: actions/checkout@v4 10 | with: 11 | submodules: 'true' 12 | - name: "Setup msvc" 13 | uses: ilammy/msvc-dev-cmd@v1 14 | - name: "Download Qt5" 15 | shell: bash 16 | run: | 17 | curl --output C:/qt5.7z -L "https://github.com/LoL-Fantome/lolcustomskin-tools/releases/download/release23/qt5.15.13-x86_64-msvc-static.7z" 18 | sha256sum C:/qt5.7z 19 | 7z x -oC:/ C:/qt5.7z 20 | - name: "Build" 21 | run: | 22 | mkdir build 23 | cd build 24 | cmake -G "NMake Makefiles" "-DCMAKE_PREFIX_PATH:PATH=C:/qt5.15.13-x86_64-msvc-static" "-DCMAKE_MSVC_RUNTIME_LIBRARY:STRING=MultiThreaded" "-DCMAKE_BUILD_TYPE:STRING=Release" .. 25 | cmake --build . 26 | - name: "Package" 27 | shell: bash 28 | run: | 29 | ./make-release.sh build 30 | 7z a cslol-manager-windows.zip cslol-manager/ 31 | - name: 'Upload Artifact' 32 | uses: actions/upload-artifact@v4 33 | with: 34 | name: cslol-manager-windows 35 | path: cslol-manager-windows.zip 36 | retention-days: 15 37 | -------------------------------------------------------------------------------- /.github/workflows/builditmac.yaml: -------------------------------------------------------------------------------- 1 | name: BuildItMac 2 | on: 3 | workflow_dispatch: 4 | jobs: 5 | build: 6 | runs-on: macos-12 7 | steps: 8 | - name: "Clone source" 9 | uses: actions/checkout@v4 10 | with: 11 | submodules: 'true' 12 | - name: "Download Qt5" 13 | run: | 14 | curl --output qt5.tar.xz -L "https://github.com/LeagueToolkit/cslol-manager/releases/download/release23/qt5.15.13-x86_64-macos-static.tar.xz" 15 | shasum -a 256 qt5.tar.xz 16 | tar xf qt5.tar.xz --strip-components 2 -C $HOME 17 | - name: "Build" 18 | run: | 19 | cmake -DCMAKE_PREFIX_PATH=$HOME/qt5.15.13-x86_64-macos-static -DCMAKE_BUILD_TYPE=Release -B build -S . 20 | cmake --build build 21 | - name: "Package" 22 | shell: bash 23 | run: | 24 | ./make-release-mac.sh "build" "cslol-manager-macos" 25 | tar caf cslol-manager-macos.tar.xz cslol-manager-macos/ 26 | - name: 'Upload Artifact' 27 | uses: actions/upload-artifact@v4 28 | with: 29 | name: cslol-manager-macos 30 | path: cslol-manager-macos.tar.xz 31 | retention-days: 15 32 | -------------------------------------------------------------------------------- /.github/workflows/qt.yaml: -------------------------------------------------------------------------------- 1 | name: Qt 2 | on: 3 | workflow_dispatch: 4 | jobs: 5 | build: 6 | runs-on: windows-2022 7 | steps: 8 | - name: "Setup msvc" 9 | uses: ilammy/msvc-dev-cmd@v1 10 | - name: "Clone Qt" 11 | shell: bash 12 | run: | 13 | git clone https://github.com/qt/qt5 14 | pushd qt5 15 | git checkout v5.15.13-lts-lgpl 16 | git submodule update --init {qtbase,qtsvg,qtdeclarative,qtgraphicaleffects,qtimageformats,qtquickcontrols2,qtwinextras} 17 | popd 18 | - name: "Build" 19 | run: | 20 | mkdir qt5-build 21 | cd qt5-build 22 | ..\qt5\configure.bat -platform win32-msvc2019 -release -mp -static -static-runtime -opensource -confirm-license -opengl desktop -no-angle -no-pch -no-iconv -no-dbus -no-icu -no-fontconfig -no-freetype -no-openssl -schannel -qt-harfbuzz -qt-doubleconversion -qt-zlib -qt-libpng -qt-libjpeg -qt-pcre -nomake tools -nomake examples -nomake tests -feature-d3d12 -no-feature-pdf -no-feature-sql -no-feature-sqlmodel -no-feature-testlib -no-feature-testlib_selfcover -no-feature-qml-profiler -no-feature-qml-preview -prefix C:/qt5.15.13-x86_64-msvc-static 23 | nmake 24 | nmake install 25 | - name: "Package" 26 | shell: bash 27 | run: | 28 | 7z a qt5.15.13-x86_64-msvc-static.7z C:/qt5.15.13-x86_64-msvc-static 29 | sha256sum qt5.15.13-x86_64-msvc-static.7z 30 | - name: 'Upload Artifact' 31 | uses: actions/upload-artifact@v4 32 | with: 33 | name: qt5.15.13-x86_64-msvc-static 34 | path: qt5.15.13-x86_64-msvc-static.7z 35 | retention-days: 15 36 | -------------------------------------------------------------------------------- /.github/workflows/qtmac.yaml: -------------------------------------------------------------------------------- 1 | name: QtMac 2 | on: 3 | workflow_dispatch: 4 | jobs: 5 | build: 6 | runs-on: macos-12 7 | steps: 8 | - name: "Clone Qt" 9 | run: | 10 | git clone https://github.com/qt/qt5 11 | pushd qt5 12 | git checkout v5.15.13-lts-lgpl 13 | git submodule update --init {qtbase,qtsvg,qtdeclarative,qtgraphicaleffects,qtimageformats,qtquickcontrols2} 14 | popd 15 | - name: "Build" 16 | run: | 17 | mkdir qt5-build 18 | pushd qt5-build 19 | ../qt5/configure -platform macx-clang -prefix $HOME/qt5.15.13-x86_64-macos-static -release -static -opensource -confirm-license -no-pch -no-iconv -no-dbus -no-icu -no-fontconfig -no-freetype -qt-harfbuzz -qt-doubleconversion -qt-zlib -qt-libpng -qt-libjpeg -qt-pcre -nomake tools -nomake examples -nomake tests -no-feature-pdf -no-feature-sql -no-feature-sqlmodel -no-feature-testlib -no-feature-testlib_selfcover -no-feature-qml-profiler -no-feature-qml-preview 20 | make 21 | mkdir $HOME/qt5.15.13-x86_64-macos-static 22 | make install 23 | popd 24 | - name: "Package" 25 | shell: bash 26 | run: | 27 | tar caf qt5.15.13-x86_64-macos-static.tar.xz $HOME/qt5.15.13-x86_64-macos-static 28 | shasum -a 256 qt5.15.13-x86_64-macos-static.tar.xz 29 | - name: 'Upload Artifact' 30 | uses: actions/upload-artifact@v4 31 | with: 32 | name: qt5.15.13-x86_64-macos-static 33 | path: qt5.15.13-x86_64-macos-static.tar.xz 34 | retention-days: 15 35 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - "*-*-*-*" 6 | jobs: 7 | build_win: 8 | runs-on: windows-2022 9 | steps: 10 | - name: "Clone source" 11 | uses: actions/checkout@v4 12 | with: 13 | submodules: 'true' 14 | - name: "Setup msvc" 15 | uses: ilammy/msvc-dev-cmd@v1 16 | - name: "Download Qt5" 17 | shell: bash 18 | run: | 19 | curl --output C:/qt5.7z -L "https://github.com/LoL-Fantome/lolcustomskin-tools/releases/download/release23/qt5.15.13-x86_64-msvc-static.7z" 20 | sha256sum C:/qt5.7z 21 | 7z x -oC:/ C:/qt5.7z 22 | - name: "Build" 23 | run: | 24 | mkdir build 25 | cd build 26 | cmake -G "NMake Makefiles" "-DCMAKE_PREFIX_PATH:PATH=C:/qt5.15.13-x86_64-msvc-static" "-DCMAKE_MSVC_RUNTIME_LIBRARY:STRING=MultiThreaded" "-DCMAKE_BUILD_TYPE:STRING=Release" .. 27 | cmake --build . 28 | - name: "Package" 29 | shell: bash 30 | run: | 31 | ./make-release.sh build 32 | 7z a cslol-manager-windows.exe -sfx7z.sfx cslol-manager/ 33 | - name: 'Upload Artifact' 34 | uses: actions/upload-artifact@v4 35 | with: 36 | name: cslol-manager-windows 37 | path: cslol-manager-windows.exe 38 | retention-days: 15 39 | build_mac: 40 | runs-on: macos-12 41 | steps: 42 | - name: "Clone source" 43 | uses: actions/checkout@v4 44 | with: 45 | submodules: 'true' 46 | - name: "Download Qt5" 47 | run: | 48 | curl --output qt5.tar.xz -L "https://github.com/LeagueToolkit/cslol-manager/releases/download/release23/qt5.15.13-x86_64-macos-static.tar.xz" 49 | shasum -a 256 qt5.tar.xz 50 | tar xf qt5.tar.xz --strip-components 2 -C $HOME 51 | - name: "Build" 52 | run: | 53 | cmake -DCMAKE_PREFIX_PATH=$HOME/qt5.15.13-x86_64-macos-static -DCMAKE_BUILD_TYPE=Release -B build -S . 54 | cmake --build build 55 | - name: "Package" 56 | shell: bash 57 | run: | 58 | ./make-release-mac.sh "build" "cslol-manager-macos" 59 | tar caf cslol-manager-macos.tar.xz cslol-manager-macos/ 60 | - name: 'Upload Artifact' 61 | uses: actions/upload-artifact@v4 62 | with: 63 | name: cslol-manager-macos 64 | path: cslol-manager-macos.tar.xz 65 | retention-days: 15 66 | release: 67 | needs: [build_win, build_mac] 68 | runs-on: ubuntu-22.04 69 | steps: 70 | - name: Download artifacts 71 | uses: actions/download-artifact@v4 72 | with: 73 | path: artifacts 74 | merge-multiple: true 75 | - name: Build Changelog 76 | id: github_release 77 | uses: mikepenz/release-changelog-builder-action@v4 78 | with: 79 | failOnError: 'true' 80 | commitMode: 'true' 81 | toTag: ${{ github.ref_name }} 82 | configurationJson: | 83 | { 84 | "tag_resolver": { 85 | "method": "sort", 86 | "filter": { 87 | "pattern": "\\d+-\\d+-\\d+-.+", 88 | "flags": "gu" 89 | } 90 | }, 91 | "pr_template": "- #{{MERGE_SHA}}: #{{TITLE}}", 92 | "template": "#{{UNCATEGORIZED}}" 93 | } 94 | ignorePreReleases: ${{ endsWith(github.ref_name, '-prerelease') || endsWith(github.ref_name, '-test') }} 95 | env: 96 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 97 | - name: "Release" 98 | uses: softprops/action-gh-release@v2 99 | with: 100 | files: artifacts/* 101 | prerelease: ${{ endsWith(github.ref_name, '-prerelease') || endsWith(github.ref_name, '-test') }} 102 | body: ${{ steps.github_release.outputs.changelog }} 103 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(cslol-manager LANGUAGES CXX C) 4 | 5 | set(CMAKE_C_STANDARD 99) 6 | set(CMAKE_C_STANDARD_REQUIRED ON) 7 | set(CMAKE_CXX_STANDARD 20) 8 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 9 | set(CMAKE_AUTOUIC OFF) 10 | set(CMAKE_AUTOMOC OFF) 11 | set(CMAKE_AUTORCC OFF) 12 | 13 | add_subdirectory(cslol-tools) 14 | 15 | option(USE_QT6 "Use Qt6 instead of Qt5" OFF) 16 | if (USE_QT6) 17 | find_package(Qt6 6.1 COMPONENTS Core Gui Quick QmlImportScanner Network REQUIRED) 18 | else() 19 | find_package(Qt5 5.15 COMPONENTS Core Gui Quick QmlImportScanner Network REQUIRED) 20 | endif() 21 | find_package(Threads REQUIRED) 22 | find_package(Git QUIET REQUIRED) 23 | 24 | add_custom_command( 25 | OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/CSLOLversion.cpp" 26 | COMMAND "${GIT_EXECUTABLE}" 27 | "-C" "${CMAKE_SOURCE_DIR}" 28 | "log" 29 | "-1" 30 | "--output" "${CMAKE_CURRENT_BINARY_DIR}/CSLOLversion.cpp" 31 | "--date=short" 32 | "--format=\ 33 | #include %n\ 34 | char const* CSLOL::VERSION=\"%ad-%h\";%n\ 35 | char const* CSLOL::COMMIT=\"%H\";%n\ 36 | char const* CSLOL::DATE=\"%aI\";%n" 37 | DEPENDS "${CMAKE_SOURCE_DIR}/.git/index" 38 | VERBATIM 39 | ) 40 | 41 | if (WIN32) 42 | add_executable(cslol-manager WIN32 43 | src/main.cpp 44 | src/res/rc.rc 45 | src/res/cslol-manager.exe.manifest 46 | ) 47 | target_link_libraries(cslol-manager PRIVATE user32) 48 | elseif(APPLE) 49 | set_source_files_properties(src/res/icon.icns 50 | PROPERTIES 51 | MACOSX_PACKAGE_LOCATION "Resources") 52 | add_executable(cslol-manager MACOSX_BUNDLE 53 | src/main.cpp 54 | src/res/icon.icns 55 | ) 56 | target_link_libraries(cslol-manager PRIVATE "-framework Security") 57 | set_target_properties(cslol-manager 58 | PROPERTIES 59 | MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/src/res/MacOSXBundleInfo.plist.in" 60 | MACOSX_BUNDLE_ICON_FILE icon 61 | MACOSX_BUNDLE_BUNDLE_NAME cslol-manager 62 | MACOSX_BUNDLE_BUNDLE_VERSION "0.1" 63 | MACOSX_BUNDLE_SHORT_VERSION_STRING "0.1" 64 | MACOSX_BUNDLE_GUI_IDENTIFIER cslol.manager 65 | ) 66 | install(CODE [[ 67 | include(BundleUtilities) 68 | fixup_bundle("${CMAKE_BINARY_DIR}/cslol-manager/cslol-manager.app" "" "") 69 | ]] COMPONENT Runtime) 70 | else() 71 | add_executable(cslol-manager 72 | src/main.cpp 73 | ) 74 | endif() 75 | 76 | target_sources(cslol-manager PRIVATE 77 | "${CMAKE_CURRENT_BINARY_DIR}/CSLOLversion.cpp" 78 | src/CSLOLTools.h 79 | src/CSLOLTools.cpp 80 | src/CSLOLToolsImpl.h 81 | src/CSLOLToolsImpl.cpp 82 | src/CSLOLUtils.h 83 | src/CSLOLUtils.cpp 84 | src/CSLOLVersion.h 85 | src/qml/qml.qrc 86 | ) 87 | 88 | set_target_properties(cslol-manager PROPERTIES AUTOUIC ON) 89 | set_target_properties(cslol-manager PROPERTIES AUTOMOC ON) 90 | set_target_properties(cslol-manager PROPERTIES AUTORCC ON) 91 | 92 | target_compile_definitions(cslol-manager PRIVATE $<$,$>:QT_QML_DEBUG>) 93 | target_link_libraries(cslol-manager PRIVATE Qt::Core Qt::Gui Qt::Quick Qt::Network Threads::Threads) 94 | target_include_directories(cslol-manager PRIVATE src/) 95 | 96 | qt_import_qml_plugins(cslol-manager) 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Download??? 2 | https://github.com/LeagueToolkit/cslol-manager/releases 3 | 4 | # Windows 10+ only 5 | Please update to windows 10 or 11! 6 | 7 | # About CustomSkin for LoL manager 8 | Mod manager for League of legends using [Fantome mod format](https://github.com/LeagueToolkit/Fantome/wiki/Mod-File-Format). 9 | ![Main window](docs/manager-0.png) 10 | ![Editing info](docs/manager-1.png) 11 | ![Editing files](docs/manager-2.png) 12 | 13 | Note that this software is "use at your own risk". 14 | 15 | Asian servers and garena are not officially supported and may not work. 16 | -------------------------------------------------------------------------------- /cslol-tools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(cslol-tools LANGUAGES C CXX) 4 | 5 | cmake_policy(SET CMP0169 OLD) 6 | 7 | set(CMAKE_CXX_STANDARD 20) 8 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 9 | 10 | add_subdirectory(dep) 11 | 12 | add_library(cslol-lib STATIC 13 | lib/lol/common.cpp 14 | lib/lol/common.hpp 15 | lib/lol/error.cpp 16 | lib/lol/error.hpp 17 | lib/lol/fs.cpp 18 | lib/lol/fs.hpp 19 | lib/lol/log.hpp 20 | lib/lol/log.cpp 21 | 22 | lib/lol/hash/dict.cpp 23 | lib/lol/hash/dict.hpp 24 | lib/lol/hash/fnv1a32.cpp 25 | lib/lol/hash/fnv1a32.hpp 26 | lib/lol/hash/xxh64.cpp 27 | lib/lol/hash/xxh64.hpp 28 | 29 | lib/lol/io/buffer.hpp 30 | lib/lol/io/buffer.cpp 31 | lib/lol/io/bytes.cpp 32 | lib/lol/io/bytes.hpp 33 | lib/lol/io/file.cpp 34 | lib/lol/io/file.hpp 35 | lib/lol/io/sys.cpp 36 | lib/lol/io/sys.hpp 37 | 38 | lib/lol/patcher/patcher.hpp 39 | lib/lol/patcher/patcher_dummy.cpp 40 | lib/lol/patcher/patcher_macos.cpp 41 | lib/lol/patcher/patcher_win32.cpp 42 | lib/lol/patcher/utility/delay.hpp 43 | lib/lol/patcher/utility/macho.hpp 44 | lib/lol/patcher/utility/process.hpp 45 | lib/lol/patcher/utility/process_macos.cpp 46 | 47 | lib/lol/utility/cli.hpp 48 | lib/lol/utility/cli.cpp 49 | lib/lol/utility/magic.cpp 50 | lib/lol/utility/magic.hpp 51 | lib/lol/utility/zip.cpp 52 | lib/lol/utility/zip.hpp 53 | 54 | lib/lol/wad/archive.cpp 55 | lib/lol/wad/archive.hpp 56 | lib/lol/wad/mounted.hpp 57 | lib/lol/wad/mounted.cpp 58 | lib/lol/wad/entry.cpp 59 | lib/lol/wad/entry.hpp 60 | lib/lol/wad/index.cpp 61 | lib/lol/wad/index.hpp 62 | lib/lol/wad/toc.cpp 63 | lib/lol/wad/toc.hpp 64 | ) 65 | if (WIN32) 66 | target_sources(cslol-lib INTERFACE 67 | res/longpath.manifest 68 | res/utf8.manifest 69 | ) 70 | endif() 71 | 72 | target_compile_definitions(cslol-lib PUBLIC -D_CRT_SECURE_NO_WARNINGS) # lmao, fuck off 73 | target_link_libraries(cslol-lib PUBLIC fmt fmtlog) 74 | target_link_libraries(cslol-lib PRIVATE libdeflate zstd xxhash miniz) 75 | target_include_directories(cslol-lib PUBLIC lib/) 76 | 77 | add_executable(mod-tools 78 | src/main_mod_tools.cpp 79 | ) 80 | target_link_libraries(mod-tools PRIVATE cslol-lib) 81 | 82 | add_executable(wad-extract 83 | src/main_wad_extract.cpp 84 | ) 85 | target_link_libraries(wad-extract PRIVATE cslol-lib) 86 | 87 | add_executable(wad-make 88 | src/main_wad_make.cpp 89 | ) 90 | target_link_libraries(wad-make PRIVATE cslol-lib) 91 | 92 | if (WIN32) 93 | add_executable(cslol-diag src/main_diag.cpp) 94 | target_sources(cslol-diag PUBLIC 95 | res/longpath.manifest 96 | res/utf8.manifest 97 | ) 98 | target_link_libraries(cslol-diag PRIVATE wintrust) 99 | endif() 100 | 101 | if (WIN32) 102 | FetchContent_Declare( 103 | cslol-patcher 104 | URL https://github.com/LeagueToolkit/cslol-patcher/releases/download/3/cslol-patcher.zip 105 | URL_HASH SHA256=af49b486406fdad789d8992b18674e8bc1164a3c29d99fce92af373f9ff07b7e 106 | CONFIGURE_COMMAND "" 107 | BUILD_COMMAND "" 108 | DOWNLOAD_EXTRACT_TIMESTAMP True 109 | ) 110 | FetchContent_GetProperties(cslol-patcher) 111 | if(NOT cslol-patcher_POPULATED) 112 | FetchContent_Populate(cslol-patcher) 113 | 114 | add_library(cslol-patcher SHARED IMPORTED) 115 | set_target_properties(cslol-patcher PROPERTIES 116 | IMPORTED_IMPLIB "${cslol-patcher_SOURCE_DIR}/cslol-dll.lib" 117 | IMPORTED_LOCATION "${cslol-patcher_SOURCE_DIR}/cslol-dll.dll" 118 | INTERFACE_INCLUDE_DIRECTORIES "${cslol-patcher_SOURCE_DIR}" 119 | ) 120 | endif() 121 | 122 | target_link_libraries(cslol-lib PUBLIC cslol-patcher) 123 | add_custom_command(TARGET mod-tools POST_BUILD 124 | COMMAND ${CMAKE_COMMAND} -E copy -t $ $ 125 | COMMAND_EXPAND_LISTS 126 | ) 127 | endif() 128 | -------------------------------------------------------------------------------- /cslol-tools/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /cslol-tools/README.md: -------------------------------------------------------------------------------- 1 | # Custom Skin for LoL tools 2 | Set of cli tools for custom skin managment and creation for LoL 3 | -------------------------------------------------------------------------------- /cslol-tools/dep/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | include(FetchContent) 4 | 5 | cmake_policy(SET CMP0169 OLD) 6 | 7 | FetchContent_Declare( 8 | xxhash 9 | GIT_REPOSITORY https://github.com/Cyan4973/xxHash.git 10 | GIT_TAG 35b0373c697b5f160d3db26b1cbb45a0d5ba788c # v0.8.1 11 | CONFIGURE_COMMAND "" 12 | BUILD_COMMAND "" 13 | ) 14 | FetchContent_GetProperties(xxhash) 15 | if(NOT xxhash_POPULATED) 16 | FetchContent_Populate(xxhash) 17 | add_library(xxhash STATIC ${xxhash_SOURCE_DIR}/xxhash.c) 18 | target_include_directories(xxhash PUBLIC ${xxhash_SOURCE_DIR}) 19 | target_compile_definitions(xxhash INTERFACE -DXXH_STATIC_LINKING_ONLY) 20 | target_compile_definitions(xxhash PRIVATE -DXXH_CPU_LITTLE_ENDIAN) 21 | endif() 22 | 23 | 24 | FetchContent_Declare( 25 | zstd 26 | GIT_REPOSITORY https://github.com/facebook/zstd.git 27 | GIT_TAG 63779c798237346c2b245c546c40b72a5a5913fe # v1.5.5 28 | CONFIGURE_COMMAND "" 29 | BUILD_COMMAND "" 30 | ) 31 | FetchContent_GetProperties(zstd) 32 | if(NOT zstd_POPULATED) 33 | FetchContent_Populate(zstd) 34 | file(GLOB zstd_SRCS 35 | ${zstd_SOURCE_DIR}/lib/common/*.c 36 | ${zstd_SOURCE_DIR}/lib/compress/*.c 37 | ${zstd_SOURCE_DIR}/lib/decompress/*.c 38 | ) 39 | add_library(zstd STATIC ${zstd_SRCS}) 40 | target_include_directories(zstd PUBLIC ${zstd_SOURCE_DIR}/lib) 41 | target_include_directories(zstd PRIVATE ${zstd_SOURCE_DIR}/lib/common) 42 | target_compile_definitions(zstd INTERFACE -DZSTD_STATIC_LINKING_ONLY) 43 | target_compile_definitions(zstd PRIVATE -DZSTD_DISABLE_ASM) 44 | if(MSVC) 45 | target_compile_options(zstd PRIVATE /wd4267) 46 | endif() 47 | endif() 48 | 49 | 50 | FetchContent_Declare( 51 | libdeflate 52 | GIT_REPOSITORY https://github.com/ebiggers/libdeflate.git 53 | GIT_TAG 495fee110ebb48a5eb63b75fd67e42b2955871e2 # v1.18 54 | CONFIGURE_COMMAND "" 55 | BUILD_COMMAND "" 56 | ) 57 | FetchContent_GetProperties(libdeflate) 58 | if(NOT libdeflate_POPULATED) 59 | FetchContent_Populate(libdeflate) 60 | file(GLOB libdeflate_SRCS 61 | ${libdeflate_SOURCE_DIR}/lib/*.c 62 | ${libdeflate_SOURCE_DIR}/lib/arm/*.c 63 | ${libdeflate_SOURCE_DIR}/lib/x86/*.c 64 | ) 65 | add_library(libdeflate STATIC ${libdeflate_SRCS}) 66 | target_include_directories(libdeflate PUBLIC ${libdeflate_SOURCE_DIR}) 67 | target_include_directories(libdeflate PRIVATE ${libdeflate_SOURCE_DIR}/lib) 68 | endif() 69 | 70 | 71 | FetchContent_Declare( 72 | miniz 73 | GIT_REPOSITORY https://github.com/richgel999/miniz.git 74 | GIT_TAG d9d197c92606da7be59cee2be2fbc6830b73c480 # 3.0.2 75 | ) 76 | FetchContent_GetProperties(miniz) 77 | if(NOT miniz_POPULATED) 78 | FetchContent_Populate(miniz) 79 | add_subdirectory(${miniz_SOURCE_DIR} ${miniz_BINARY_DIR}) 80 | target_compile_definitions(miniz PRIVATE -DMINIZ_DISABLE_ZIP_READER_CRC32_CHECKS=1) 81 | endif() 82 | 83 | 84 | FetchContent_Declare( 85 | fmt 86 | GIT_REPOSITORY https://github.com/fmtlib/fmt.git 87 | GIT_TAG a33701196adfad74917046096bf5a2aa0ab0bb50 # 9.1.0 88 | CONFIGURE_COMMAND "" 89 | BUILD_COMMAND "" 90 | ) 91 | FetchContent_GetProperties(fmt) 92 | if(NOT fmt_POPULATED) 93 | FetchContent_Populate(fmt) 94 | add_library(fmt STATIC ${fmt_SOURCE_DIR}/src/format.cc ${fmt_SOURCE_DIR}/src/os.cc) 95 | target_include_directories(fmt PUBLIC ${fmt_SOURCE_DIR}/include) 96 | target_compile_definitions(fmt PRIVATE -DFMT_UNICODE=1) 97 | endif() 98 | 99 | 100 | FetchContent_Declare( 101 | fmtlog 102 | GIT_REPOSITORY https://github.com/MengRao/fmtlog.git 103 | GIT_TAG 5aea0764c820358ec1a842cd34cb6d494f65cfa1 # 2.2.1 104 | CONFIGURE_COMMAND "" 105 | BUILD_COMMAND "" 106 | ) 107 | FetchContent_GetProperties(fmtlog) 108 | if(NOT fmtlog_POPULATED) 109 | FetchContent_Populate(fmtlog) 110 | add_library(fmtlog INTERFACE) 111 | target_include_directories(fmtlog INTERFACE ${fmtlog_SOURCE_DIR}) 112 | target_link_libraries(fmtlog INTERFACE fmt) 113 | endif() 114 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/common.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace lol; 6 | 7 | #ifdef _WIN32 8 | extern "C" { 9 | extern void Sleep(unsigned ms); 10 | } 11 | void lol::sleep_ms(std::uint32_t ms) { Sleep(ms); } 12 | #else 13 | void lol::sleep_ms(std::uint32_t ms) { std::this_thread::sleep_for(std::chrono::milliseconds{ms}); } 14 | #endif -------------------------------------------------------------------------------- /cslol-tools/lib/lol/common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace lol { 13 | void sleep_ms(std::uint32_t ms); 14 | } 15 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/error.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace lol; 6 | 7 | [[noreturn]] auto error::raise(std::string const& what) -> void { throw std::runtime_error(what); } 8 | 9 | auto error::stack() noexcept -> std::string& { 10 | thread_local std::string instance = {}; 11 | return instance; 12 | } 13 | 14 | auto error::stack_trace() noexcept -> std::string { 15 | auto& ss = stack(); 16 | std::string message = ss; 17 | ss.clear(); 18 | return message; 19 | } 20 | 21 | auto error::path_sanitize(std::string str) noexcept -> std::string { 22 | if (auto const& cd = fs::current_path_str(); !cd.empty()) { 23 | for (auto i = str.find(cd); i != std::string::npos; i = str.find(cd)) { 24 | str.replace(i, cd.size(), "./"); 25 | } 26 | } 27 | if (auto const& home = fs::home_path_str(); !home.empty()) { 28 | for (auto i = str.find(home); i != std::string::npos; i = str.find(home)) { 29 | str.replace(i, home.size(), "~/"); 30 | } 31 | } 32 | return str; 33 | } -------------------------------------------------------------------------------- /cslol-tools/lib/lol/error.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #ifdef _MSC_VER 8 | # define __PRETTY_FUNCTION__ __FUNCTION__ 9 | #endif 10 | 11 | #define lol_paste_impl(x, y) x##y 12 | #define lol_paste(x, y) lol_paste_impl(x, y) 13 | 14 | #define lol_trace_func(...) \ 15 | ::lol::error::trace lol_paste(trace_, __LINE__) { \ 16 | [&, func = __PRETTY_FUNCTION__] { ::lol::error::stack_push(func, ##__VA_ARGS__); } \ 17 | } 18 | 19 | #define lol_trace_var(fstr, name) fmt::format("{} = " fstr, #name, name) 20 | 21 | #define lol_throw_msg(fstr, ...) ::lol::error::raise(::fmt::format(fstr, ##__VA_ARGS__)) 22 | 23 | #define lol_throw_if_msg(cond, fstr, ...) \ 24 | do { \ 25 | [[unlikely]] if (auto expr = cond) { lol_throw_msg(fstr, ##__VA_ARGS__); } \ 26 | } while (false) 27 | 28 | #define lol_throw_if(cond) \ 29 | do { \ 30 | [[unlikely]] if (auto expr = cond) { lol_throw_msg("{} -> {}", #cond, expr); } \ 31 | } while (false) 32 | 33 | 34 | namespace lol::error { 35 | [[noreturn]] extern auto raise(std::string const &what) -> void; 36 | 37 | extern auto stack() noexcept -> std::string &; 38 | 39 | extern auto stack_trace() noexcept -> std::string; 40 | 41 | extern auto path_sanitize(std::string str) noexcept -> std::string; 42 | 43 | template 44 | inline auto stack_push(std::string_view func, Args &&...args) noexcept -> void { 45 | auto &stack_ref = stack(); 46 | stack_ref.append("\n ").append(func.substr(0, func.find_first_of("("))).append(":"); 47 | (stack_ref.append("\n ").append(std::forward(args)), ...); 48 | } 49 | 50 | template 51 | struct trace : private Func { 52 | inline trace(Func &&func) noexcept : Func(std::forward(func)) {} 53 | inline ~trace() noexcept { [[unlikely]] if (std::uncaught_exceptions()) Func::operator()(); } 54 | }; 55 | 56 | constexpr auto fix_func(std::string_view func) noexcept -> std::string_view { 57 | return func.substr(0, func.find_first_of("(")); 58 | } 59 | } 60 | 61 | template <> 62 | struct fmt::formatter : formatter { 63 | template 64 | auto format(std::error_code const &code, FormatContext &ctx) { 65 | return formatter::format(lol::error::path_sanitize(code.message()), ctx); 66 | } 67 | }; 68 | 69 | template <> 70 | struct fmt::formatter : formatter { 71 | template 72 | auto format(std::exception const &ex, FormatContext &ctx) { 73 | return formatter::format(lol::error::path_sanitize(ex.what()), ctx); 74 | } 75 | }; -------------------------------------------------------------------------------- /cslol-tools/lib/lol/fs.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | using namespace lol; 7 | 8 | auto fs::home_path_str() noexcept -> std::string const& { 9 | static std::string const instance = []() -> std::string { 10 | auto result = std::string{}; 11 | #ifdef _WIN32 12 | auto homedrive = _wgetenv(L"HOMEDRIVE"); 13 | auto homepath = _wgetenv(L"HOMEPATH"); 14 | if (homedrive && homepath) result = (fs::path(homedrive) / homepath).generic_string(); 15 | #else 16 | auto home = getenv("HOME"); 17 | if (home) result = fs::path(home).generic_string(); 18 | #endif 19 | if (!result.empty() && !result.ends_with('/')) result.push_back('/'); 20 | // std::transform(result.begin(), result.end(), result.begin(), ::tolower); 21 | return std::string(result.begin(), result.end()); 22 | }(); 23 | return instance; 24 | } 25 | 26 | auto fs::current_path_str() noexcept -> std::string const& { 27 | static std::string const instance = []() -> std::string { 28 | auto result = std::string{}; 29 | auto errc = std::error_code{}; 30 | result = fs::current_path().generic_string(); 31 | if (!result.empty() && !result.ends_with('/')) result.push_back('/'); 32 | // std::transform(result.begin(), result.end(), result.begin(), ::tolower); 33 | return std::string(result.begin(), result.end()); 34 | ; 35 | }(); 36 | return instance; 37 | } 38 | 39 | fs::tmp_dir::tmp_dir(fs::path path) { 40 | lol_trace_func(lol_trace_var("{}", path)); 41 | if (fs::exists(path)) { 42 | fs::remove_all(path); 43 | } 44 | fs::create_directories(path); 45 | this->path = std::move(path); 46 | } 47 | 48 | fs::tmp_dir::~tmp_dir() noexcept { 49 | if (!path.empty()) { 50 | std::error_code ec = {}; 51 | fs::remove_all(path, ec); 52 | } 53 | } 54 | 55 | auto fs::tmp_dir::move(fs::path const& dst) -> void { 56 | lol_trace_func(lol_trace_var("{}", path), lol_trace_var("{}", dst)); 57 | fs::rename(this->path, dst); 58 | this->path.clear(); 59 | } 60 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/fs.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace lol::fs { 7 | using namespace std::filesystem; 8 | 9 | using names = fs::path; 10 | 11 | extern auto home_path_str() noexcept -> std::string const &; 12 | 13 | extern auto current_path_str() noexcept -> std::string const &; 14 | 15 | struct tmp_dir { 16 | tmp_dir(fs::path path); 17 | ~tmp_dir() noexcept; 18 | 19 | tmp_dir(tmp_dir const &) = delete; 20 | tmp_dir(tmp_dir &&) = delete; 21 | tmp_dir &operator=(tmp_dir const &) = delete; 22 | tmp_dir &operator=(tmp_dir &&) = delete; 23 | 24 | auto move(fs::path const &dst) -> void; 25 | 26 | fs::path path; 27 | }; 28 | } 29 | 30 | template <> 31 | struct fmt::formatter : formatter { 32 | template 33 | auto format(lol::fs::path const &path, FormatContext &ctx) { 34 | auto result = path.generic_string(); 35 | if (auto const &cd = lol::fs::current_path_str(); !cd.empty() && result.starts_with(cd)) { 36 | result.replace(0, cd.size(), "./"); 37 | } else if (auto const &home = lol::fs::home_path_str(); !home.empty() && result.starts_with(home)) { 38 | result.replace(0, home.size(), "~/"); 39 | } 40 | return formatter::format(result, ctx); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/hash/dict.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace lol; 8 | using namespace lol::hash; 9 | 10 | auto Dict::load(fs::path const &path) noexcept -> bool { 11 | try { 12 | auto src = io::Bytes::from_file(path); 13 | auto i = src.data(); 14 | auto const end = src.data() + src.size(); 15 | while (i != end) { 16 | while (i != end && std::isspace(*i)) ++i; 17 | std::uint64_t h = {}; 18 | if (auto [ptr, ec] = std::from_chars(i, end, h, 16); ec == std::errc{}) { 19 | i = ptr; 20 | while (i != end && std::isspace(*i)) ++i; 21 | auto start = i; 22 | while (i != end && *i != '\r' && *i != '\n') ++i; 23 | unhashed_[h] = std::string_view(start, (std::size_t)(i - start)); 24 | } else { 25 | while (i != end && *i != '\r' && *i != '\n') ++i; 26 | } 27 | } 28 | return true; 29 | } catch (std::exception const &) { 30 | error::stack().clear(); 31 | return false; 32 | } 33 | } 34 | 35 | auto Dict::save(fs::path const &path) const -> void { 36 | auto dst = io::File::create(path); 37 | auto pos = 0; 38 | for (auto const &[h, name] : unhashed_) { 39 | auto line = fmt::format("{:016X} {}\n", h, name); 40 | dst.write(pos, line.data(), line.size()); 41 | pos += line.size(); 42 | } 43 | dst.resize(pos); 44 | } 45 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/hash/dict.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace lol::hash { 7 | //! Container for unhashed 8 | struct Dict { 9 | //! Create empty hash dictionary. 10 | Dict() noexcept {} 11 | 12 | //! Create hash dictionary with reserved size. 13 | Dict(std::size_t hint_reserve) noexcept : unhashed_(hint_reserve) {} 14 | 15 | //! Load hash dictionary entries. 16 | auto load(fs::path const &path) noexcept -> bool; 17 | 18 | //! Save hash dictionary entries. 19 | auto save(fs::path const &path) const -> void; 20 | 21 | //! Get key from hash dictionary. 22 | template 23 | auto get(HashType key) const noexcept -> std::string { 24 | if (auto i = unhashed_.find((std::uint64_t)key); i != unhashed_.end()) { 25 | return i->second; 26 | } 27 | return {}; 28 | } 29 | 30 | //! Add key to hash dictionary. 31 | template 32 | auto add(HashType key, std::string value) noexcept -> void { 33 | unhashed_[(std::uint64_t)key] = value; 34 | } 35 | 36 | protected: 37 | std::unordered_map unhashed_; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/hash/fnv1a32.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace lol; 6 | using namespace lol::hash; 7 | 8 | Fnv1a32::Fnv1a32(std::string_view str) noexcept { 9 | std::uint32_t h = 0x811c9dc5u; 10 | for (std::uint8_t c : str) { 11 | c = c >= 'A' && c <= 'Z' ? (c - 'A') + 'a' : c; 12 | h = ((h ^ c) * 0x01000193u) & 0xFFFFFFFFu; 13 | } 14 | value_ = h; 15 | } 16 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/hash/fnv1a32.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace lol::hash { 8 | //! Lower-case hash of string. 9 | struct Fnv1a32 { 10 | using underlying_t = std::uint32_t; 11 | 12 | //! Zero initialized hash. 13 | constexpr Fnv1a32() noexcept = default; 14 | 15 | //! Initialize hash from underlying type. 16 | explicit constexpr Fnv1a32(underlying_t value) noexcept : value_(value){}; 17 | 18 | //! Initialize hash from string. 19 | explicit Fnv1a32(std::string_view str) noexcept; 20 | 21 | //! Convert hash to a number. 22 | constexpr operator underlying_t() const noexcept { return value_; } 23 | 24 | private: 25 | underlying_t value_ = 0; 26 | }; 27 | 28 | constexpr auto operator""_fnv1a32(char const* str) noexcept -> Fnv1a32 { 29 | std::uint32_t h = 0x811c9dc5u; 30 | while (unsigned char c = (unsigned char)*str++) { 31 | c = c >= 'A' && c <= 'Z' ? (c - 'A') + 'a' : c; 32 | h = ((h ^ c) * 0x01000193u) & 0xFFFFFFFFu; 33 | } 34 | return Fnv1a32(h); 35 | } 36 | } 37 | 38 | template <> 39 | struct std::hash : std::hash {}; 40 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/hash/xxh64.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace lol; 7 | using namespace lol::hash; 8 | 9 | Xxh64::Xxh64(std::string_view str) noexcept { 10 | XXH64_state_t state; 11 | XXH64_reset(&state, 0); 12 | for (std::uint8_t c: str) { 13 | c = c >= 'A' && c <= 'Z' ? (c - 'A') + 'a' : c; 14 | XXH64_update(&state, (char const*)&c, 1); 15 | } 16 | value_ = XXH64_digest(&state); 17 | } 18 | 19 | auto Xxh64::from_path(std::string_view str) noexcept -> Xxh64 { 20 | while (str.starts_with('.') || str.starts_with('/')) str.remove_prefix(1); 21 | auto tmp = str.substr(0, str.find_first_of('.')); 22 | if (auto const data = (char const *)tmp.data(); tmp.size() == 16) { 23 | std::uint64_t value = 0; 24 | auto const [ptr, ec] = std::from_chars(data, data + tmp.size(), value, 16); 25 | if (ptr == data + tmp.size() && ec == std::errc{}) { 26 | return Xxh64(value); 27 | } 28 | } 29 | return Xxh64(str); 30 | } 31 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/hash/xxh64.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace lol::hash { 8 | //! Lower-case hash of path string. 9 | struct Xxh64 { 10 | using underlying_t = std::uint64_t; 11 | 12 | //! Zero initialized hash. 13 | constexpr Xxh64() noexcept = default; 14 | 15 | //! Initialize hash from underlying type. 16 | explicit constexpr Xxh64(underlying_t value) noexcept : value_(value){}; 17 | 18 | //! Initialize hash from string. 19 | explicit Xxh64(std::string_view str) noexcept; 20 | 21 | //! Tries to convert filename to hex string. 22 | static auto from_path(std::string_view str) noexcept -> Xxh64; 23 | 24 | //! Tries to convert filename to hex string. 25 | static auto from_path(fs::path const &path) noexcept -> Xxh64 { 26 | auto str = path.generic_string(); 27 | return from_path((std::string_view)str); 28 | } 29 | 30 | //! Convert hash to a number. 31 | constexpr operator underlying_t() const noexcept { return value_; } 32 | 33 | private: 34 | underlying_t value_ = 0; 35 | }; 36 | } 37 | 38 | template <> 39 | struct std::hash : std::hash {}; 40 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/io/buffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace lol::io { 6 | using std::dynamic_extent; 7 | 8 | inline constexpr std::size_t KiB = 1024; 9 | inline constexpr std::size_t MiB = KiB * 1024; 10 | inline constexpr std::size_t GiB = MiB * 1024; 11 | 12 | struct Bytes; 13 | struct Buffer { 14 | auto data() const noexcept -> char const* { return data_; } 15 | 16 | auto size() const noexcept -> std::size_t { return size_; } 17 | 18 | auto copy(std::size_t pos, std::size_t count) const -> Bytes; 19 | 20 | auto readsome(std::size_t pos, void* dst, std::size_t dst_count) const noexcept -> std::size_t; 21 | 22 | auto readsome_decompress_zstd(std::size_t pos, 23 | std::size_t count, 24 | void* dst, 25 | std::size_t dst_count) const noexcept -> std::size_t; 26 | 27 | auto read(std::size_t pos, void* dst, std::size_t dst_count) const -> std::size_t; 28 | 29 | auto reserve(std::size_t pos, std::size_t count) -> char*; 30 | 31 | auto resize(std::size_t count) -> char*; 32 | 33 | auto writesome(std::size_t pos, void const* src, std::size_t src_count) noexcept -> std::size_t; 34 | 35 | auto write(std::size_t pos, void const* src, std::size_t src_count) -> std::size_t; 36 | 37 | auto write_decompress_defl(std::size_t pos, std::size_t count, void const* src, std::size_t src_count) -> void; 38 | 39 | auto write_decompress_zlib(std::size_t pos, std::size_t count, void const* src, std::size_t src_count) -> void; 40 | 41 | auto write_decompress_gzip(std::size_t pos, std::size_t count, void const* src, std::size_t src_count) -> void; 42 | 43 | auto write_decompress_zstd(std::size_t pos, std::size_t count, void const* src, std::size_t src_count) -> void; 44 | 45 | auto write_decompress_zstd_hack(std::size_t pos, std::size_t count, void const* src, std::size_t src_count) 46 | -> void; 47 | 48 | auto write_compress_defl(std::size_t pos, void const* src, std::size_t src_count, int level = 6) -> std::size_t; 49 | 50 | auto write_compress_zlib(std::size_t pos, void const* src, std::size_t src_count, int level = 6) -> std::size_t; 51 | 52 | auto write_compress_gzip(std::size_t pos, void const* src, std::size_t src_count, int level = 6) -> std::size_t; 53 | 54 | auto write_compress_zstd(std::size_t pos, void const* src, std::size_t src_count, int level = 0) -> std::size_t; 55 | 56 | protected: 57 | virtual ~Buffer() noexcept = 0; 58 | 59 | virtual auto impl_copy(std::size_t pos, std::size_t count, Bytes& out) const noexcept -> std::error_code = 0; 60 | 61 | virtual auto impl_reserve(std::size_t count) noexcept -> std::error_code = 0; 62 | 63 | char* data_; 64 | std::size_t size_; 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/io/bytes.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace lol; 6 | using namespace lol::io; 7 | 8 | Bytes::Impl::MMap::~MMap() noexcept { 9 | sys::file_munmap(data, size); 10 | sys::file_close(file); 11 | } 12 | 13 | auto Bytes::from_capacity(std::size_t count) -> Bytes { 14 | lol_trace_func(lol_trace_var("{:#x}", count)); 15 | auto result = Bytes(); 16 | result.impl_ = new Impl{}; 17 | result.impl_->vec.resize(count); 18 | return result; 19 | } 20 | 21 | auto Bytes::from_static(std::span data) noexcept -> Bytes { 22 | auto result = Bytes{}; 23 | result.data_ = const_cast(data.data()); 24 | result.size_ = data.size(); 25 | return result; 26 | } 27 | 28 | auto Bytes::from_vector(std::vector vec) noexcept -> Bytes { 29 | try { 30 | vec.shrink_to_fit(); 31 | } catch (...) { 32 | // NOTE: this is fine 33 | } 34 | auto result = Bytes(); 35 | result.impl_ = new Impl{}; 36 | result.impl_->vec = std::move(vec); 37 | result.data_ = result.impl_->vec.data(); 38 | result.size_ = result.impl_->vec.size(); 39 | return result; 40 | } 41 | 42 | auto Bytes::from_file(fs::path const& path) -> Bytes { 43 | lol_trace_func(lol_trace_var("{}", path)); 44 | auto result = Bytes(); 45 | result.impl_ = new Impl{}; 46 | 47 | auto const file = sys::file_open(path, false); 48 | lol_throw_if(file.error); 49 | result.impl_->mmap.file = file.value; 50 | 51 | auto const file_size = sys::file_size(file.value); 52 | lol_throw_if(file_size.error); 53 | result.impl_->mmap.size = file_size.value; 54 | 55 | auto const mmap_data = sys::file_mmap(file.value, file_size.value, false); 56 | lol_throw_if(mmap_data.error); 57 | result.impl_->mmap.data = mmap_data.value; 58 | result.data_ = (char*)mmap_data.value; 59 | result.size_ = file_size.value; 60 | 61 | return result; 62 | } 63 | 64 | auto Bytes::impl_copy(std::size_t pos, std::size_t count, Bytes& out) const noexcept -> std::error_code { 65 | if (count == 0) return {}; 66 | out.data_ = pos + data_; 67 | out.size_ = count; 68 | if ((out.impl_ = impl_)) { 69 | ++(impl_->ref_count); 70 | } 71 | return {}; 72 | } 73 | 74 | auto Bytes::impl_reserve(std::size_t count) noexcept -> std::error_code { 75 | if (count > 64 * GiB) [[unlikely]] { 76 | return std::make_error_code(std::errc::value_too_large); 77 | } 78 | try { 79 | if (!impl_ || impl_->ref_count || impl_->vec.data() != data_) { 80 | auto result = Bytes(); 81 | result.impl_ = new Impl{}; 82 | result.impl_->vec.resize(std::max(count, size_)); 83 | result.data_ = result.impl_->vec.data(); 84 | result.size_ = size_; 85 | std::memcpy(result.data_, data_, size_); 86 | std::swap(result.data_, data_); 87 | std::swap(result.size_, size_); 88 | std::swap(result.impl_, impl_); 89 | return {}; 90 | } 91 | if (auto& vec = impl_->vec; count > vec.size()) { 92 | vec.resize(std::max(count, vec.capacity())); 93 | data_ = vec.data(); 94 | } 95 | return {}; 96 | } catch (std::system_error const& error) { 97 | return error.code(); 98 | } 99 | } 100 | 101 | Bytes::operator std::vector() const& { return std::vector(data_, data_ + size_); } 102 | 103 | Bytes::operator std::vector() && { 104 | auto result = std::vector(); 105 | if (!impl_ || impl_->ref_count || impl_->vec.data() != data_) { 106 | result = std::vector(data_, data_ + size_); 107 | } else { 108 | impl_->vec.resize(size_); 109 | result = std::move(impl_->vec); 110 | } 111 | data_ = {}; 112 | size_ = {}; 113 | delete std::exchange(impl_, {}); 114 | return result; 115 | } 116 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/io/bytes.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace lol::io { 7 | struct Bytes final : Buffer { 8 | public: 9 | Bytes() noexcept = default; 10 | 11 | Bytes(Bytes const& other) noexcept { 12 | data_ = other.data_; 13 | size_ = other.size_; 14 | if ((impl_ = other.impl_)) { 15 | ++(impl_->ref_count); 16 | } 17 | } 18 | 19 | Bytes(Bytes&& other) noexcept { 20 | std::swap(impl_, other.impl_); 21 | std::swap(data_, other.data_); 22 | std::swap(size_, other.size_); 23 | } 24 | 25 | Bytes& operator=(Bytes other) noexcept { 26 | std::swap(impl_, other.impl_); 27 | std::swap(data_, other.data_); 28 | std::swap(size_, other.size_); 29 | return *this; 30 | } 31 | 32 | ~Bytes() noexcept { 33 | auto data = std::exchange(data_, {}); 34 | auto size = std::exchange(size_, {}); 35 | auto impl = std::exchange(impl_, {}); 36 | if (impl && (impl->ref_count)-- == 0) { 37 | delete impl; 38 | (void)data; 39 | (void)size; 40 | } 41 | } 42 | 43 | static auto from_capacity(std::size_t cap) -> Bytes; 44 | 45 | static auto from_static(std::span data) noexcept -> Bytes; 46 | 47 | static auto from_vector(std::vector vec) noexcept -> Bytes; 48 | 49 | static auto from_file(fs::path const& path) -> Bytes; 50 | 51 | auto copy_decompress_defl(std::size_t size_decompressed) const -> Bytes { 52 | auto result = Bytes(); 53 | result.write_decompress_defl(0, size_decompressed, data_, size_); 54 | return result; 55 | } 56 | 57 | auto copy_decompress_zlib(std::size_t size_decompressed) const -> Bytes { 58 | auto result = Bytes(); 59 | result.write_decompress_zlib(0, size_decompressed, data_, size_); 60 | return result; 61 | } 62 | 63 | auto copy_decompress_gzip(std::size_t size_decompressed) const -> Bytes { 64 | auto result = Bytes(); 65 | result.write_decompress_gzip(0, size_decompressed, data_, size_); 66 | return result; 67 | } 68 | 69 | auto copy_decompress_zstd(std::size_t size_decompressed) const -> Bytes { 70 | auto result = Bytes(); 71 | result.write_decompress_zstd(0, size_decompressed, data_, size_); 72 | return result; 73 | } 74 | 75 | auto copy_decompress_zstd_hack(std::size_t size_decompressed) const -> Bytes { 76 | auto result = Bytes(); 77 | result.write_decompress_zstd_hack(0, size_decompressed, data_, size_); 78 | return result; 79 | } 80 | 81 | auto copy_compress_defl(int level = 6) const -> Bytes { 82 | auto result = Bytes(); 83 | result.write_compress_defl(0, data_, size_, level); 84 | return result; 85 | } 86 | 87 | auto copy_compress_zlib(int level = 6) const -> Bytes { 88 | auto result = Bytes(); 89 | result.write_compress_zlib(0, data_, size_, level); 90 | return result; 91 | } 92 | 93 | auto copy_compress_gzip(int level = 6) const -> Bytes { 94 | auto result = Bytes(); 95 | result.write_compress_gzip(0, data_, size_, level); 96 | return result; 97 | } 98 | 99 | auto copy_compress_zstd(int level = 0) const -> Bytes { 100 | auto result = Bytes(); 101 | result.write_compress_zstd(0, data_, size_, level); 102 | return result; 103 | } 104 | 105 | operator std::vector() const&; 106 | 107 | operator std::vector() &&; 108 | 109 | protected: 110 | auto impl_copy(std::size_t pos, std::size_t count, Bytes& out) const noexcept -> std::error_code override; 111 | 112 | auto impl_reserve(std::size_t count) noexcept -> std::error_code override; 113 | 114 | private: 115 | struct Impl final { 116 | std::size_t ref_count = 0; 117 | std::vector vec; 118 | struct MMap { 119 | ~MMap() noexcept; 120 | std::intptr_t file; 121 | void* data; 122 | std::size_t size; 123 | } mmap = {}; 124 | }; 125 | Impl* impl_ = {}; 126 | }; 127 | } 128 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/io/file.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace lol; 7 | using namespace lol::io; 8 | 9 | File::~File() noexcept { 10 | auto const file = std::exchange(file_, {}); 11 | auto const data = std::exchange(data_, {}); 12 | auto const size = std::exchange(size_, {}); 13 | auto const disk_size = std::exchange(disk_size_, {}); 14 | auto const mapped_size = std::exchange(mapped_size_, {}); 15 | if (data) { 16 | sys::file_munmap(data, mapped_size); 17 | } 18 | if (file) { 19 | if (size != disk_size) { 20 | (void)sys::file_resize(file, size); 21 | } 22 | sys::file_close(file); 23 | } 24 | } 25 | 26 | auto io::File::create(fs::path const& path) -> File { 27 | lol_trace_func(lol_trace_var("{}", path)); 28 | 29 | auto result = File{}; 30 | 31 | auto const file = sys::file_open(path, true); 32 | lol_throw_if(file.error); 33 | result.file_ = file.value; 34 | 35 | auto const file_size = sys::file_size(file.value); 36 | lol_throw_if(file_size.error); 37 | result.size_ = file_size.value; 38 | result.disk_size_ = file_size.value; 39 | result.mapped_size_ = file_size.value; 40 | 41 | auto const mmap_data = sys::file_mmap(file.value, file_size.value, true); 42 | lol_throw_if(mmap_data.error); 43 | result.data_ = (char*)mmap_data.value; 44 | 45 | return result; 46 | } 47 | 48 | auto io::File::impl_reserve(std::size_t count) noexcept -> std::error_code { 49 | if (count > mapped_size_) { 50 | if (count > 64 * GiB) [[unlikely]] { 51 | return std::make_error_code(std::errc::value_too_large); 52 | } 53 | if (count > disk_size_) { 54 | if (count > GiB) { 55 | count = ((count + (GiB - 1)) / GiB) * GiB; 56 | } else { 57 | count = ((count + (MiB - 1)) / MiB) * MiB; 58 | } 59 | if (auto error = sys::file_resize(file_, count).error) [[unlikely]] { 60 | return error; 61 | } else { 62 | disk_size_ = count; 63 | } 64 | } 65 | if (auto [error, data] = sys::file_mmap(file_, disk_size_, true); error) [[unlikely]] { 66 | return error; 67 | } else { 68 | sys::file_munmap(data_, mapped_size_); 69 | data_ = (char*)data; 70 | mapped_size_ = disk_size_; 71 | } 72 | } 73 | return {}; 74 | } 75 | 76 | auto io::File::impl_copy(std::size_t pos, std::size_t count, Bytes& out) const noexcept -> std::error_code { 77 | try { 78 | out = Bytes::from_vector(std::vector(data_ + pos, data_ + pos + count)); 79 | return {}; 80 | } catch (std::system_error const& error) { 81 | return error.code(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/io/file.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace lol::io { 7 | struct File final : Buffer { 8 | File() noexcept = default; 9 | 10 | File(File const& other) noexcept = delete; 11 | 12 | File(File&& other) noexcept { 13 | std::swap(file_, other.file_); 14 | std::swap(data_, other.data_); 15 | std::swap(size_, other.size_); 16 | std::swap(disk_size_, other.disk_size_); 17 | std::swap(mapped_size_, other.mapped_size_); 18 | } 19 | 20 | File& operator=(File other) noexcept { 21 | std::swap(file_, other.file_); 22 | std::swap(data_, other.data_); 23 | std::swap(size_, other.size_); 24 | std::swap(disk_size_, other.disk_size_); 25 | std::swap(mapped_size_, other.mapped_size_); 26 | return *this; 27 | } 28 | 29 | ~File() noexcept; 30 | 31 | static auto create(fs::path const& path) -> File; 32 | 33 | protected: 34 | auto impl_copy(std::size_t pos, std::size_t count, Bytes& out) const noexcept -> std::error_code override; 35 | 36 | auto impl_reserve(std::size_t count) noexcept -> std::error_code override; 37 | 38 | private: 39 | std::intptr_t file_ = {}; 40 | std::size_t disk_size_ = {}; 41 | std::size_t mapped_size_ = {}; 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/io/sys.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace lol; 4 | using namespace lol::io; 5 | 6 | #define handle_ok(handle) ((std::intptr_t)handle != 0 && (std::intptr_t)handle != -1) 7 | 8 | #ifdef _WIN32 9 | # ifndef WIN32_LEAN_AND_MEAN 10 | # define WIN32_LEAN_AND_MEAN 11 | # endif 12 | # ifndef NOMINMAX 13 | # define NOMINMAX 14 | # endif 15 | # include 16 | # define last_error() std::error_code((int)GetLastError(), std::system_category()) 17 | 18 | auto sys::file_open(std::filesystem::path const& path, bool write) noexcept -> Result { 19 | if (write) { 20 | std::error_code ec = {}; 21 | std::filesystem::create_directories(path.parent_path(), ec); 22 | if (ec) [[unlikely]] { 23 | return {ec}; 24 | } 25 | } 26 | auto const file = ::CreateFile(path.string().c_str(), 27 | write ? GENERIC_READ | GENERIC_WRITE : GENERIC_READ, 28 | write ? 0 : FILE_SHARE_READ, 29 | 0, 30 | write ? OPEN_ALWAYS : OPEN_EXISTING, 31 | FILE_ATTRIBUTE_NORMAL, 32 | 0); 33 | if (!handle_ok(file)) [[unlikely]] { 34 | return {last_error()}; 35 | } 36 | return {{}, (std::intptr_t)file}; 37 | } 38 | 39 | auto sys::file_size(std::intptr_t file) noexcept -> Result { 40 | LARGE_INTEGER result = {}; 41 | auto const status = ::GetFileSizeEx((HANDLE)file, &result); 42 | if (status == FALSE) [[unlikely]] { 43 | return {last_error()}; 44 | } 45 | return {{}, (std::size_t)result.QuadPart}; 46 | } 47 | 48 | auto sys::file_resize(std::intptr_t file, std::size_t count) noexcept -> Result { 49 | FILE_END_OF_FILE_INFO info = {.EndOfFile = {.QuadPart = (LONGLONG)count}}; 50 | auto const status = ::SetFileInformationByHandle((HANDLE)file, FileEndOfFileInfo, &info, sizeof(info)); 51 | if (status == FALSE) [[unlikely]] { 52 | return {last_error()}; 53 | } 54 | return {{}, count}; 55 | } 56 | 57 | auto sys::file_close(std::intptr_t file) noexcept -> void { 58 | if (file) { 59 | CloseHandle((HANDLE)file); 60 | } 61 | } 62 | 63 | auto sys::file_mmap(std::intptr_t file, std::size_t count, bool write) noexcept -> Result { 64 | if (count == 0) { 65 | return {{}, {}}; 66 | } 67 | auto mapping = ::CreateFileMapping((HANDLE)file, 0, write ? PAGE_READWRITE : PAGE_READONLY, count >> 32, count, 0); 68 | if (!handle_ok(mapping)) [[unlikely]] { 69 | return {last_error()}; 70 | } 71 | auto data = ::MapViewOfFile(mapping, write ? FILE_MAP_READ | FILE_MAP_WRITE : FILE_MAP_READ, 0, 0, count); 72 | if (!handle_ok(data)) [[unlikely]] { 73 | auto ec = last_error(); 74 | CloseHandle(mapping); 75 | return {ec, {}}; 76 | } 77 | CloseHandle(mapping); 78 | return {{}, data}; 79 | } 80 | 81 | auto sys::file_munmap(void* data, std::size_t count) noexcept -> void { 82 | if (data) { 83 | ::UnmapViewOfFile(data); 84 | } 85 | } 86 | #endif 87 | 88 | #ifndef _WIN32 89 | # include 90 | # include 91 | # include 92 | # include 93 | # include 94 | # include 95 | # define last_error() std::error_code((int)errno, std::system_category()) 96 | 97 | static struct ResourceLimit { 98 | public: ResourceLimit() { 99 | rlimit resourceLimit; 100 | getrlimit(RLIMIT_NOFILE, &resourceLimit); 101 | resourceLimit.rlim_cur = resourceLimit.rlim_max == RLIM_INFINITY ? 65000 : resourceLimit.rlim_max - 1; 102 | setrlimit(RLIMIT_NOFILE, &resourceLimit); 103 | } 104 | } resourceLimit_ = {}; 105 | 106 | auto sys::file_open(std::filesystem::path const& path, bool write) noexcept -> Result { 107 | if (write) { 108 | std::error_code ec = {}; 109 | std::filesystem::create_directories(path.parent_path(), ec); 110 | if (ec) [[unlikely]] { 111 | return {ec}; 112 | } 113 | } 114 | int fd = write ? ::open(path.string().c_str(), O_RDWR | O_CREAT, 0644) : ::open(path.string().c_str(), O_RDONLY); 115 | if (!handle_ok(fd)) [[unlikely]] { 116 | return {last_error()}; 117 | } 118 | return {{}, (std::intptr_t)fd}; 119 | } 120 | 121 | auto sys::file_size(std::intptr_t file) noexcept -> Result { 122 | struct ::stat result = {}; 123 | auto const status = ::fstat((int)file, &result); 124 | if (status == -1) [[unlikely]] { 125 | return {last_error()}; 126 | } 127 | return {{}, (std::size_t)result.st_size}; 128 | } 129 | 130 | auto sys::file_resize(std::intptr_t file, std::size_t count) noexcept -> Result { 131 | auto const status = ::ftruncate((int)file, (off_t)count); 132 | if (status == -1) [[unlikely]] { 133 | return {last_error()}; 134 | } 135 | return {{}, count}; 136 | } 137 | 138 | auto sys::file_close(std::intptr_t file) noexcept -> void { 139 | if (file) { 140 | ::close((int)file); 141 | } 142 | } 143 | 144 | auto sys::file_mmap(std::intptr_t file, std::size_t count, bool write) noexcept -> Result { 145 | if (count == 0) { 146 | return {{}, {}}; 147 | } 148 | void* data = ::mmap(0, count, write ? PROT_READ | PROT_WRITE : PROT_READ, MAP_SHARED, (int)file, 0); 149 | if (!handle_ok(data)) [[unlikely]] { 150 | return {last_error()}; 151 | } 152 | return {{}, data}; 153 | } 154 | 155 | auto sys::file_munmap(void* data, std::size_t count) noexcept -> void { 156 | if (data) { 157 | ::munmap(data, count); 158 | } 159 | } 160 | #endif 161 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/io/sys.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace lol::io::sys { 6 | template 7 | struct [[nodiscard]] Result { 8 | std::error_code error = {}; 9 | T value = {}; 10 | }; 11 | 12 | extern auto file_open(std::filesystem::path const& path, bool write) noexcept -> Result; 13 | 14 | extern auto file_size(std::intptr_t file) noexcept -> Result; 15 | 16 | extern auto file_resize(std::intptr_t file, std::size_t count) noexcept -> Result; 17 | 18 | extern auto file_close(std::intptr_t file) noexcept -> void; 19 | 20 | extern auto file_mmap(std::intptr_t file, std::size_t count, bool write) noexcept -> Result; 21 | 22 | extern auto file_munmap(void* data, std::size_t count) noexcept -> void; 23 | } 24 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/log.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | void lol::init_logging_thread() { 7 | fmtlogDetailWrapper<>::impl.stopPollingThread(); 8 | fmtlogDetailWrapper<>::impl.threadRunning = true; 9 | fmtlogDetailWrapper<>::impl.thr = std::thread([]() { 10 | while (fmtlogDetailWrapper<>::impl.threadRunning) { 11 | fmtlogDetailWrapper<>::impl.poll(false); 12 | // Why are we using our own polling thread instead of builtin one: 13 | // This chinese logging library uses nanoseconds to poll. 14 | // As it turns out windows is utterly incapable of sleeping below 1ms. 15 | // As a consequence std::this_thread_sleep sleeps by doing a busy wait on high precision system clock. 16 | // This absolutely destroys CPU performance on certain machines as it pins the core to 100%. 17 | lol::sleep_ms(1); 18 | } 19 | fmtlogDetailWrapper<>::impl.poll(true); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/log.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | 6 | namespace lol { 7 | void init_logging_thread(); 8 | } 9 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/patcher/asm/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | nasm -f bin -Ox -o win32_hook_CreateFileA.bin win32_hook_CreateFileA.asm 3 | nasm -f bin -Ox -o win32_hook_CRYPTO_free.bin win32_hook_CRYPTO_free.asm 4 | nasm -f bin -Ox -o macos_hook.bin macos_hook.asm 5 | 6 | xxd -i -c 8 win32_hook_CreateFileA.bin 7 | xxd -i -c 8 win32_hook_CRYPTO_free.bin 8 | xxd -i -c 8 macos_hook.bin 9 | 10 | rm -f win32_hook_CreateFileA.bin 11 | rm -f win32_hook_CRYPTO_free.bin 12 | rm -f macos_hook.bin 13 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/patcher/asm/macos_hook.asm: -------------------------------------------------------------------------------- 1 | [bits 64] 2 | %define buffer_size 0x800 3 | 4 | setup: 5 | push rbp 6 | mov rbp, rsp 7 | push r12 8 | push r13 9 | sub rsp, buffer_size 10 | mov r12, rdi ; filename 11 | mov r13, rsi ; mode 12 | 13 | check_args_not_null: 14 | test r12, r12 15 | je call_with_filename 16 | test r13, r13 17 | je call_with_filename 18 | 19 | check_mode_eq_rb: 20 | cmp byte [r13], 'r' 21 | jne call_with_filename 22 | cmp byte [r13 + 1], 'b' 23 | jne call_with_filename 24 | cmp byte [r13 + 2], 0 25 | jne call_with_filename 26 | 27 | write_prefix: 28 | mov rdi, rsp ; dst = buffer 29 | lea rsi, [rel prefix] ; src = prefix 30 | write_prefix_continue: 31 | lodsb 32 | stosb 33 | test al, al 34 | jne write_prefix_continue 35 | 36 | write_filename: 37 | dec rdi ; dst = buffer[strlen(buffer)] 38 | mov rsi, r12 ; src = filename 39 | write_filename_continue: 40 | lodsb 41 | stosb 42 | test al, al 43 | jne write_filename_continue 44 | 45 | call_with_buffer: 46 | mov rdi, rsp ; filename = buffer 47 | mov rsi, r13 ; mode = mode 48 | mov rax, qword [rel fopen_org_ref] 49 | call qword [rax] 50 | test rax, rax 51 | jne return 52 | 53 | call_with_filename: 54 | mov rdi, r12 ; filename = filename 55 | mov rsi, r13 ; mode = mode 56 | mov rax, qword [rel fopen_org_ref] 57 | call qword [rax] 58 | 59 | return: 60 | add rsp, buffer_size 61 | pop r13 62 | pop r12 63 | pop rbp 64 | ret 65 | 66 | align 0x80 67 | 68 | fopen_org_ref: 69 | dq 0x11223344556677 70 | prefix: 71 | dq 0x11223344556677 72 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/patcher/asm/win32_hook_CRYPTO_free.asm: -------------------------------------------------------------------------------- 1 | [bits 64] 2 | 3 | test rcx, rcx 4 | jz skip_free 5 | 6 | ; prologue 7 | push rbx 8 | mov rbx, rcx 9 | sub rsp, 32 10 | 11 | call QWORD [rel GetProcessHeap] 12 | 13 | mov rcx, rax 14 | xor rdx, rdx 15 | mov r8, rbx 16 | call QWORD [rel HeapFree] 17 | 18 | ; epilogue 19 | add rsp, 32 20 | pop rbx 21 | 22 | skip_free: 23 | mov rcx, QWORD [rsp] 24 | 25 | ; cancer 26 | mov rdx, rcx 27 | and rdx, 0xfff 28 | cmp rdx, 0xff8 29 | jg done 30 | 31 | mov rcx, [rcx] 32 | mov rdx, QWORD [rel match_ret] 33 | 34 | cmp rcx, rdx 35 | je ret_payload 36 | 37 | done: 38 | ret 39 | 40 | align 0x80 41 | 42 | GetProcessHeap: 43 | dq 0x11223344556677 44 | 45 | HeapFree: 46 | dq 0x11223344556677 47 | 48 | match_ret: 49 | dq 0x11223344556677 50 | 51 | ret_payload: 52 | dq 0x11223344556677 53 | 54 | 55 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/patcher/asm/win32_hook_CreateFileA.asm: -------------------------------------------------------------------------------- 1 | [bits 64] 2 | 3 | ; args 4 | %define arg_lpFileName 16 5 | %define arg_dwDesiredAccess 24 6 | %define arg_dwShareMode 32 7 | %define arg_lpSecurityAttributes 40 8 | %define arg_dwCreationDisposition 48 9 | %define arg_dwFlagsAndAttributes 56 10 | %define arg_hTemplateFile 72 11 | 12 | ; locals 13 | %define local_buffer_size 0x1000 14 | %define local_buffer -local_buffer_size 15 | 16 | ; prologue 17 | enter local_buffer_size, 0 18 | push rbx 19 | push rdi 20 | push rsi 21 | sub rsp, 72 22 | 23 | ; use original functions shadow stack to preserve original args 24 | mov arg_lpSecurityAttributes[rbp], r9 25 | mov arg_dwShareMode[rbp], r8 26 | mov arg_dwDesiredAccess[rbp], rdx 27 | mov arg_lpFileName[rbp], rcx 28 | 29 | ; GENERIC_READ 30 | cmp dword arg_dwDesiredAccess[rbp], 0x80000000 31 | jne original 32 | 33 | ; FILE_SHARE_READ 34 | cmp dword arg_dwShareMode[rbp], 1 35 | jne original 36 | 37 | ; OPEN_EXISTING 38 | cmp dword arg_dwCreationDisposition[rbp], 0x3 39 | jne original 40 | 41 | ; FILE_ATTRIBUTE_NORMAL 42 | cmp dword arg_dwFlagsAndAttributes[rbp], 0x80 43 | jne original 44 | 45 | ; only DATA 46 | cmp byte [rcx], 'D' 47 | jne original 48 | cmp byte [rcx+1], 'A' 49 | jne original 50 | cmp byte [rcx+2], 'T' 51 | jne original 52 | cmp byte [rcx+3], 'A' 53 | jne original 54 | 55 | ; prepare buffer for writing 56 | lea rdi, QWORD local_buffer[rbp] 57 | 58 | ; write prefix 59 | lea rsi, QWORD [rel prefix] 60 | write_prefix: lodsw 61 | stosw 62 | test ax, ax 63 | jne write_prefix 64 | 65 | ; remove null terminator 66 | sub rdi, 2 67 | 68 | ; write path 69 | mov rsi, arg_lpFileName[rbp] 70 | mov ah, 0 71 | write_path: lodsb 72 | cmp al, 47 73 | jne write_path_skip 74 | mov al, 92 75 | write_path_skip: 76 | stosw 77 | test al, al 78 | jne write_path 79 | 80 | ; call wide with modified buffer 81 | mov rax, arg_hTemplateFile[rbp] 82 | mov [rsp+48], rax 83 | mov rax, arg_dwFlagsAndAttributes[rbp] 84 | mov [rsp+40], rax 85 | mov rax, arg_dwCreationDisposition[rbp] 86 | mov [rsp+32], rax 87 | mov r9, arg_lpSecurityAttributes[rbp] 88 | mov r8, arg_dwShareMode[rbp] 89 | mov rdx, arg_dwDesiredAccess[rbp] 90 | lea rcx, QWORD local_buffer[rbp] 91 | call QWORD [rel ptr_CreateFileW] 92 | 93 | ; restore original arguments 94 | ; check for success 95 | cmp rax, -1 96 | jne done 97 | 98 | ; just call original 99 | original: 100 | mov rax, arg_hTemplateFile[rbp] 101 | mov [rsp+48], rax 102 | mov rax, arg_dwFlagsAndAttributes[rbp] 103 | mov [rsp+40], rax 104 | mov rax, arg_dwCreationDisposition[rbp] 105 | mov [rsp+32], rax 106 | mov r9, arg_lpSecurityAttributes[rbp] 107 | mov r8, arg_dwShareMode[rbp] 108 | mov rdx, arg_dwDesiredAccess[rbp] 109 | mov rcx, arg_lpFileName[rbp] 110 | call QWORD [rel ptr_CreateFileA] 111 | 112 | ; epilogue 113 | done: add rsp, 72 114 | pop rsi 115 | pop rdi 116 | pop rbx 117 | leave 118 | ret 119 | 120 | ; runtime data comes after 121 | align 0x100 122 | 123 | ptr_CreateFileA: 124 | dq 0x11223344556677 125 | 126 | ptr_CreateFileW: 127 | dq 0x11223344556677 128 | 129 | prefix: 130 | dq 0x11223344556677 131 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/patcher/patcher.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace lol::patcher { 8 | enum Message { 9 | M_WAIT_START, 10 | M_FOUND, 11 | M_WAIT_INIT, 12 | M_SCAN, 13 | M_NEED_SAVE, 14 | M_WAIT_PATCHABLE, 15 | M_PATCH, 16 | M_WAIT_EXIT, 17 | M_DONE, 18 | M_COUNT_OF, 19 | }; 20 | 21 | static constexpr const char* const STATUS_MSG[Message::M_COUNT_OF] = { 22 | "Waiting for league match to start", 23 | "Found League", 24 | "Wait initialized", 25 | "Scanning", 26 | "Saving", 27 | "Wait patchable", 28 | "Patching", 29 | "Waiting for exit", 30 | "League exited", 31 | }; 32 | 33 | extern auto run(std::function update, 34 | fs::path const& profile_path, 35 | fs::path const& config_path, 36 | fs::path const& game_path, 37 | fs::names const& opts) -> void; 38 | 39 | struct PatcherTimeout : std::runtime_error { 40 | using std::runtime_error::runtime_error; 41 | }; 42 | 43 | struct PatcherAborted : std::runtime_error { 44 | PatcherAborted() : std::runtime_error("Aborted as expected") {} 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/patcher/patcher_dummy.cpp: -------------------------------------------------------------------------------- 1 | #if !defined(_WIN32) && !defined(__APPLE__) 2 | # include 3 | # include 4 | // do not reorder 5 | # include 6 | # include 7 | 8 | using namespace lol; 9 | using namespace lol::patcher; 10 | using namespace std::chrono_literals; 11 | 12 | auto patcher::run(std::function update, 13 | fs::path const& profile_path, 14 | fs::path const& config_path, 15 | fs::path const& game_path, 16 | fs::names const& opts) -> void { 17 | (void)profile_path; 18 | (void)config_path; 19 | (void)game_path; 20 | (void)opts; 21 | for (;;) { 22 | update(M_WAIT_START, ""); 23 | sleep_ms(250); 24 | } 25 | } 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/patcher/patcher_macos.cpp: -------------------------------------------------------------------------------- 1 | #ifdef __APPLE__ 2 | # include 3 | # include 4 | # include 5 | # include 6 | # include 7 | # include 8 | 9 | # include "utility/delay.hpp" 10 | # include "utility/macho.hpp" 11 | # include "utility/process.hpp" 12 | 13 | // do not reorder 14 | # include 15 | # include 16 | 17 | using namespace lol; 18 | using namespace lol::patcher; 19 | using namespace std::chrono_literals; 20 | 21 | struct Payload { 22 | unsigned char ret_true[8] = {0xb8, 0x01, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00}; 23 | PtrStorage fopen_hook_ptr = {}; 24 | unsigned char fopen_hook[0x80] = { 25 | 0x55, 0x48, 0x89, 0xe5, 0x41, 0x54, 0x41, 0x55, 0x48, 0x81, 0xec, 0x00, 0x08, 0x00, 0x00, 0x49, 26 | 0x89, 0xfc, 0x49, 0x89, 0xf5, 0x4d, 0x85, 0xe4, 0x74, 0x4a, 0x4d, 0x85, 0xed, 0x74, 0x45, 0x41, 27 | 0x80, 0x7d, 0x00, 0x72, 0x75, 0x3e, 0x41, 0x80, 0x7d, 0x01, 0x62, 0x75, 0x37, 0x41, 0x80, 0x7d, 28 | 0x02, 0x00, 0x75, 0x30, 0x48, 0x89, 0xe7, 0x48, 0x8d, 0x35, 0x4a, 0x00, 0x00, 0x00, 0xac, 0xaa, 29 | 0x84, 0xc0, 0x75, 0xfa, 0x48, 0xff, 0xcf, 0x4c, 0x89, 0xe6, 0xac, 0xaa, 0x84, 0xc0, 0x75, 0xfa, 30 | 0x48, 0x89, 0xe7, 0x4c, 0x89, 0xee, 0x48, 0x8b, 0x05, 0x23, 0x00, 0x00, 0x00, 0xff, 0x10, 0x48, 31 | 0x85, 0xc0, 0x75, 0x0f, 0x4c, 0x89, 0xe7, 0x4c, 0x89, 0xee, 0x48, 0x8b, 0x05, 0x0f, 0x00, 0x00, 32 | 0x00, 0xff, 0x10, 0x48, 0x81, 0xc4, 0x00, 0x08, 0x00, 0x00, 0x41, 0x5d, 0x41, 0x5c, 0x5d, 0xc3, 33 | }; 34 | PtrStorage fopen_org_ptr = {}; 35 | char prefix[0x200] = {}; 36 | }; 37 | 38 | static uint8_t const PAT_INT_RSA_VERIFY[] = { 39 | 0x55, 0x48, 0x89, 0xE5, 0x41, 0x57, 0x41, 0x56, 0x41, 0x55, 0x41, 0x54, 0x53, 40 | 0x48, 0x83, 0xEC, 0x38, 0x4D, 0x89, 0xCD, 0x4D, 0x89, 0xC4, 0x48, 0x89, 0xCB, 41 | }; 42 | 43 | struct Context { 44 | std::uint64_t off_rsa_verify = {}; 45 | std::uint64_t off_fopen_org = {}; 46 | std::uint64_t off_fopen_ref = {}; 47 | std::string prefix; 48 | 49 | auto set_prefix(fs::path const& profile_path) -> void { 50 | prefix = fs::absolute(profile_path.lexically_normal()).generic_string(); 51 | if (!prefix.ends_with('/')) { 52 | prefix.push_back('/'); 53 | } 54 | if (prefix.size() > sizeof(Payload::prefix) - 1) { 55 | lol_throw_msg("Prefix path too big!"); 56 | } 57 | } 58 | 59 | auto scan(Process const& process) -> void { 60 | auto data = process.Dump(); 61 | auto macho = MachO{}; 62 | macho.parse_data((MachO::data_t)data.data(), data.size()); 63 | 64 | const auto i_rsa_verify = std::search((uint8_t const*)data.data(), 65 | (uint8_t const*)data.data() + data.size(), 66 | std::begin(PAT_INT_RSA_VERIFY), 67 | std::end(PAT_INT_RSA_VERIFY)); 68 | if (i_rsa_verify == (uint8_t const*)data.data() + data.size()) { 69 | throw std::runtime_error("Failed to find int_rsa_verify"); 70 | } 71 | off_rsa_verify = (std::uint64_t)(i_rsa_verify - (uint8_t const*)data.data()) + 0x100000000ull; 72 | 73 | if (!(off_fopen_org = macho.find_import_ptr("_fopen"))) { 74 | throw std::runtime_error("Failed to find fopen org"); 75 | } 76 | if (!(off_fopen_ref = macho.find_stub_refs(off_fopen_org))) { 77 | throw std::runtime_error("Failed to find fopen ref"); 78 | } 79 | } 80 | 81 | auto patch(Process const& process) -> void { 82 | auto payload = Payload{}; 83 | memcpy(payload.prefix, prefix.c_str(), prefix.size() + 1); 84 | payload.fopen_org_ptr = process.Rebase(off_fopen_org); 85 | 86 | auto const ptr_rsa_verify = process.Rebase(off_rsa_verify); 87 | auto const ptr_fopen_ref = process.Rebase(off_fopen_ref); 88 | 89 | // fopen_hook_ptr stores pointer to fopen_hook, fopen_ref uses relative addressing to call it 90 | payload.fopen_hook_ptr = (PtrStorage)ptr_rsa_verify + offsetof(Payload, fopen_hook); 91 | auto const fopen_hook_ref = 92 | (std::int32_t)(+(std::int64_t)((PtrStorage)ptr_rsa_verify + offsetof(Payload, fopen_hook_ptr)) // 93 | - (std::int64_t)((PtrStorage)ptr_fopen_ref + 4)); 94 | 95 | // Write payload 96 | process.MarkWritable(ptr_rsa_verify); 97 | process.Write(ptr_rsa_verify, payload); 98 | process.MarkExecutable(ptr_rsa_verify); 99 | 100 | // Write fopen ref 101 | process.MarkWritable(ptr_fopen_ref); 102 | process.Write(ptr_fopen_ref, fopen_hook_ref); 103 | process.MarkExecutable(ptr_fopen_ref); 104 | } 105 | }; 106 | 107 | auto patcher::run(std::function update, 108 | fs::path const& profile_path, 109 | fs::path const& config_path, 110 | fs::path const& game_path, 111 | fs::names const& opts) -> void { 112 | auto ctx = Context{}; 113 | ctx.set_prefix(profile_path); 114 | (void)config_path; 115 | (void)game_path; 116 | (void)opts; 117 | for (;;) { 118 | auto pid = Process::FindPid("/LeagueofLegends"); 119 | if (!pid) { 120 | update(M_WAIT_START, ""); 121 | sleep_ms(10); 122 | continue; 123 | } 124 | 125 | update(M_FOUND, ""); 126 | auto process = Process::Open(pid); 127 | 128 | update(M_SCAN, ""); 129 | ctx.scan(process); 130 | 131 | update(M_PATCH, ""); 132 | ctx.patch(process); 133 | 134 | update(M_WAIT_EXIT, ""); 135 | run_until_or( 136 | 3h, 137 | Intervals{5s, 10s, 15s}, 138 | [&] { return process.IsExited(); }, 139 | []() -> bool { throw PatcherTimeout(std::string("Timed out exit")); }); 140 | 141 | update(M_DONE, ""); 142 | } 143 | } 144 | 145 | #endif 146 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/patcher/patcher_win32.cpp: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | # include 3 | # include 4 | 5 | // do not reorder 6 | # include "cslol-api.h" 7 | # include "utility/delay.hpp" 8 | 9 | using namespace lol; 10 | using namespace lol::patcher; 11 | using namespace std::chrono_literals; 12 | 13 | auto patcher::run(std::function update, 14 | fs::path const& profile_path, 15 | fs::path const& config_path, 16 | fs::path const& game_path, 17 | fs::names const& opts) -> void { 18 | bool debugpatcher = 0; 19 | for (auto const& o : opts) { 20 | if (o == "debugpatcher") { 21 | debugpatcher = true; 22 | } 23 | } 24 | 25 | // Initialize first proces. 26 | lol_throw_if(cslol_init()); 27 | lol_throw_if(cslol_set_flags(CSLOL_HOOK_DISALBE_NONE)); 28 | lol_throw_if(cslol_set_log_level(debugpatcher ? CSLOL_LOG_DEBUG : CSLOL_LOG_INFO)); 29 | lol_throw_if(cslol_set_config(profile_path.generic_u16string().c_str())); 30 | 31 | for (;;) { 32 | auto tid = run_until_or( 33 | 30s, 34 | Intervals{10ms}, 35 | [&] { 36 | update(M_WAIT_START, ""); 37 | return cslol_find(); 38 | }, 39 | []() -> std::uint32_t { return 0; }); 40 | if (!tid) { 41 | continue; 42 | } 43 | 44 | // Signal that process has been found. 45 | update(M_FOUND, ""); 46 | 47 | // Hook in 5 seconds or error. 48 | lol_throw_if(cslol_hook(tid, 300000, 100)); 49 | 50 | // Wait for exit. 51 | update(M_WAIT_EXIT, ""); 52 | run_until_or( 53 | 3h, 54 | Intervals{5s, 10s, 15s}, 55 | [&, tid] { 56 | char const* msg = NULL; 57 | while ((msg = cslol_log_pull())) { 58 | // post log message 59 | update(M_WAIT_EXIT, msg); 60 | } 61 | return tid != cslol_find(); 62 | }, 63 | []() -> bool { throw PatcherTimeout(std::string("Timed out exit")); }); 64 | } 65 | } 66 | #endif 67 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/patcher/utility/delay.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "lol/common.hpp" 8 | 9 | namespace lol { 10 | template 11 | struct Intervals { 12 | Intervals() = default; 13 | 14 | template 15 | Intervals(T... intervals) 16 | : intervals{static_cast( 17 | std::chrono::duration_cast(intervals).count())...} {} 18 | 19 | std::array intervals{1}; 20 | }; 21 | 22 | template 23 | Intervals(T...) -> Intervals; 24 | 25 | Intervals() -> Intervals<1>; 26 | 27 | template 28 | inline auto run_until_or(D deadline, Intervals intervals, P&& poll, F&& fail) { 29 | auto total = static_cast(std::chrono::duration_cast(deadline).count()); 30 | for (auto interval : intervals.intervals) { 31 | auto slice = total / S; 32 | while (slice > 0) { 33 | if (auto result = poll()) { 34 | return result; 35 | } 36 | sleep_ms(interval); 37 | slice -= interval; 38 | } 39 | } 40 | return fail(); 41 | } 42 | } -------------------------------------------------------------------------------- /cslol-tools/lib/lol/patcher/utility/process.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace lol::patcher { 7 | template 8 | struct Ptr; 9 | 10 | template 11 | Ptr(T *) -> Ptr; 12 | 13 | using PtrStorage = uint64_t; 14 | 15 | template <> 16 | struct Ptr { 17 | PtrStorage storage = {}; 18 | inline Ptr() noexcept = default; 19 | inline Ptr(Ptr const &) noexcept = default; 20 | inline Ptr(Ptr &&) noexcept = default; 21 | inline Ptr &operator=(Ptr const &) noexcept = default; 22 | inline Ptr &operator=(Ptr &&) noexcept = default; 23 | inline Ptr(uintptr_t value) noexcept : storage((PtrStorage)(value)) {} 24 | explicit inline Ptr(void *value) noexcept : Ptr((uintptr_t)(value)) {} 25 | explicit inline operator void *() const noexcept { return (void *)((uintptr_t)(storage)); } 26 | explicit inline operator PtrStorage() const noexcept { return storage; } 27 | }; 28 | 29 | template 30 | struct Ptr : Ptr { 31 | static_assert(!std::is_pointer_v); 32 | using Ptr::Ptr; 33 | using Ptr::operator void *; 34 | using Ptr::operator PtrStorage; 35 | inline Ptr(T *value) noexcept : Ptr((uintptr_t)(value)) {} 36 | inline auto operator->() const noexcept { return (T *)((void *)(*this)); } 37 | }; 38 | 39 | struct Process { 40 | private: 41 | void *handle_ = nullptr; 42 | mutable PtrStorage base_ = {}; 43 | mutable std::filesystem::path path_ = {}; 44 | mutable uint32_t pid_ = {}; 45 | 46 | Process(void *handle, std::uint32_t pid) noexcept : handle_(handle), pid_(pid) {} 47 | 48 | public: 49 | Process() noexcept; 50 | 51 | Process(Process const &) = delete; 52 | Process &operator=(Process const &) = delete; 53 | 54 | Process(Process &&other) noexcept; 55 | Process &operator=(Process &&other) noexcept; 56 | 57 | ~Process() noexcept; 58 | 59 | explicit inline operator bool() const noexcept { return handle_; } 60 | 61 | inline bool operator!() const noexcept { return !handle_; } 62 | 63 | static auto Open(std::uint32_t pid) -> Process; 64 | 65 | static auto FindPid(char const *name) -> std::uint32_t; 66 | 67 | static auto FindPidWindow(char const *window) -> std::uint32_t; 68 | 69 | auto Base() const -> PtrStorage; 70 | 71 | auto TryBase() const noexcept -> std::optional; 72 | 73 | auto Path() const -> fs::path; 74 | 75 | auto Dump() const -> std::vector; 76 | 77 | auto WaitInitialized(uint32_t timeout = 1) const noexcept -> bool; 78 | 79 | auto IsExited() const noexcept -> bool; 80 | 81 | auto TryReadMemory(void *address, void *dest, size_t size) const noexcept -> bool; 82 | 83 | auto ReadMemory(void *address, void *dest, size_t size) const -> void; 84 | 85 | auto WriteMemory(void *address, void const *src, size_t size) const -> void; 86 | 87 | auto MarkMemoryExecutable(void *address, size_t size) const -> void; 88 | 89 | auto MarkMemoryWritable(void *address, size_t size) const -> void; 90 | 91 | auto AllocateMemory(size_t size) const -> void *; 92 | 93 | template 94 | inline auto TryRead(Ptr address) const noexcept -> std::optional { 95 | auto value = T{}; 96 | if (!TryReadMemory(static_cast(address), &value, sizeof(value))) { 97 | return std::nullopt; 98 | } 99 | return value; 100 | } 101 | 102 | template 103 | inline auto Read(Ptr address) const -> T { 104 | auto dest = T{}; 105 | ReadMemory(static_cast(address), &dest, sizeof(T)); 106 | return dest; 107 | } 108 | 109 | template 110 | inline auto Write(Ptr address, T const &src) const -> void { 111 | WriteMemory(static_cast(address), &src, sizeof(T)); 112 | } 113 | 114 | template 115 | inline auto Allocate() const -> Ptr { 116 | return static_cast>(AllocateMemory(sizeof(T))); 117 | } 118 | 119 | template 120 | inline auto MarkWritable(Ptr address) const -> void { 121 | MarkMemoryWritable(static_cast(address), sizeof(T)); 122 | } 123 | 124 | template 125 | inline auto MarkExecutable(Ptr address) const -> void { 126 | MarkMemoryExecutable(static_cast(address), sizeof(T)); 127 | } 128 | 129 | inline auto Rebase(PtrStorage offset) const -> PtrStorage { return offset + Base(); } 130 | 131 | inline auto Debase(PtrStorage offset) const -> PtrStorage { return offset - Base(); } 132 | 133 | template 134 | inline auto Rebase(PtrStorage offset) const -> Ptr { 135 | return Ptr{offset + Base()}; 136 | } 137 | }; 138 | } 139 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/patcher/utility/process_macos.cpp: -------------------------------------------------------------------------------- 1 | #ifdef __APPLE__ 2 | # include 3 | 4 | # include "process.hpp" 5 | // do not reoder 6 | # include 7 | # include 8 | # include 9 | # include 10 | # include 11 | 12 | using namespace lol; 13 | using namespace lol::patcher; 14 | 15 | Process::Process() noexcept = default; 16 | 17 | Process::Process(Process&& other) noexcept { 18 | std::swap(handle_, other.handle_); 19 | std::swap(base_, other.base_); 20 | std::swap(path_, other.path_); 21 | std::swap(pid_, other.pid_); 22 | } 23 | 24 | Process& Process::operator=(Process&& other) noexcept { 25 | if (this == &other) return *this; 26 | std::swap(handle_, other.handle_); 27 | std::swap(base_, other.base_); 28 | std::swap(path_, other.path_); 29 | std::swap(pid_, other.pid_); 30 | return *this; 31 | } 32 | 33 | Process::~Process() noexcept { 34 | if (handle_) { 35 | mach_port_deallocate(mach_task_self(), (mach_port_t)(std::size_t)handle_); 36 | } 37 | } 38 | 39 | auto Process::FindPid(char const* name) -> std::uint32_t { 40 | if (name) { 41 | pid_t pids[4096]; 42 | int bytes = proc_listpids(PROC_ALL_PIDS, 0, pids, sizeof(pids)); 43 | int n_proc = bytes / sizeof(pid_t); 44 | char pathbuf[PROC_PIDPATHINFO_MAXSIZE] = {}; 45 | for (auto p = pids; p != pids + n_proc; p++) { 46 | if (auto const ret = proc_pidpath(*p, pathbuf, sizeof(pathbuf)); ret > 0) { 47 | if (std::string_view{pathbuf, static_cast(ret)}.ends_with(name)) { 48 | return static_cast(*p); 49 | } 50 | } 51 | } 52 | } 53 | return 0; 54 | } 55 | 56 | auto Process::FindPidWindow(char const* window) -> std::uint32_t { return 0; } 57 | 58 | auto Process::Open(std::uint32_t pid) -> Process { 59 | if (mach_port_t task = {}; !task_for_pid(mach_task_self(), pid, &task)) { 60 | return Process((void*)(std::uintptr_t)task, pid); 61 | } 62 | lol_throw_msg("task_for_pid"); 63 | } 64 | 65 | auto Process::Base() const -> PtrStorage { 66 | if (!base_) { 67 | vm_map_offset_t vmoffset = {}; 68 | vm_map_size_t vmsize = {}; 69 | uint32_t nesting_depth = 0; 70 | struct vm_region_submap_info_64 vbr[16] = {}; 71 | mach_msg_type_number_t vbrcount = 16; 72 | kern_return_t kr; 73 | if (auto const err = mach_vm_region_recurse((mach_port_t)(uintptr_t)handle_, 74 | &vmoffset, 75 | &vmsize, 76 | &nesting_depth, 77 | (vm_region_recurse_info_t)&vbr, 78 | &vbrcount)) { 79 | lol_throw_msg("mach_vm_region_recurse: {:#x}", (std::uint32_t)err); 80 | } 81 | base_ = vmoffset - 0x100000000; 82 | } 83 | return base_; 84 | } 85 | 86 | auto Process::TryBase() const noexcept -> std::optional { 87 | if (!base_) { 88 | vm_map_offset_t vmoffset = {}; 89 | vm_map_size_t vmsize = {}; 90 | uint32_t nesting_depth = 0; 91 | struct vm_region_submap_info_64 vbr[16] = {}; 92 | mach_msg_type_number_t vbrcount = 16; 93 | kern_return_t kr; 94 | if (auto const err = mach_vm_region_recurse((mach_port_t)(uintptr_t)handle_, 95 | &vmoffset, 96 | &vmsize, 97 | &nesting_depth, 98 | (vm_region_recurse_info_t)&vbr, 99 | &vbrcount)) { 100 | return std::nullopt; 101 | } 102 | base_ = vmoffset - 0x100000000; 103 | } 104 | return base_; 105 | } 106 | 107 | auto Process::Path() const -> fs::path { 108 | lol_trace_func(); 109 | if (!path_.empty()) return path_; 110 | char pathbuf[PROC_PIDPATHINFO_MAXSIZE] = {}; 111 | if (auto const ret = proc_pidpath(pid_, pathbuf, sizeof(pathbuf)); ret <= 0) { 112 | lol_throw_msg("proc_pidpath(pid: {}): {:#x}", pid_, (std::uint32_t)ret); 113 | } else { 114 | path_ = std::string{pathbuf, pathbuf + ret}; 115 | } 116 | return path_; 117 | } 118 | 119 | auto Process::Dump() const -> std::vector { 120 | std::vector buffer = {}; 121 | auto const path = Path().generic_string(); 122 | if (auto const file = fopen(path.c_str(), "rb")) { 123 | auto const size = std::filesystem::file_size(path); 124 | buffer.resize(size); 125 | fread(buffer.data(), buffer.size(), 1, file); 126 | fclose(file); 127 | } else { 128 | lol_throw_msg("fopen(path: {}, \"rb\") failed", path.c_str()); 129 | } 130 | return buffer; 131 | } 132 | 133 | auto Process::IsExited() const noexcept -> bool { 134 | int p = 0; 135 | pid_for_task((mach_port_t)(uintptr_t)handle_, &p); 136 | return p < 0; 137 | } 138 | 139 | auto Process::WaitInitialized(uint32_t timeout) const noexcept -> bool { return true; } 140 | 141 | auto Process::TryReadMemory(void* address, void* dest, size_t size) const noexcept -> bool { 142 | mach_msg_type_number_t orgdata_read = 0; 143 | vm_offset_t offset = {}; 144 | if (auto const err = 145 | mach_vm_read((mach_port_t)(uintptr_t)handle_, (mach_vm_address_t)address, size, &offset, &orgdata_read)) { 146 | return false; 147 | } 148 | if (orgdata_read != size) { 149 | return false; 150 | } 151 | memcpy(dest, (void*)offset, orgdata_read); 152 | return true; 153 | } 154 | 155 | auto Process::ReadMemory(void* address, void* dest, size_t size) const -> void { 156 | mach_msg_type_number_t got_size = 0; 157 | vm_offset_t offset = {}; 158 | if (auto const err = 159 | mach_vm_read((mach_port_t)(uintptr_t)handle_, (mach_vm_address_t)address, size, &offset, &got_size)) { 160 | lol_throw_msg("mach_vm_read: {:#x}", (std::uint32_t)err); 161 | } 162 | if (got_size != size) { 163 | lol_throw_msg("mach_vm_read: got {:#x}", got_size); 164 | } 165 | memcpy(dest, (void*)offset, got_size); 166 | } 167 | 168 | auto Process::WriteMemory(void* address, void const* src, std::size_t sizeBytes) const -> void { 169 | if (auto const err = 170 | mach_vm_write((mach_port_t)(uintptr_t)handle_, (mach_vm_address_t)address, (vm_offset_t)src, sizeBytes)) { 171 | lol_throw_msg("mach_vm_write: {:#x}", (std::uint32_t)err); 172 | } 173 | } 174 | 175 | auto Process::MarkMemoryWritable(void* address, std::size_t size) const -> void { 176 | if (auto const err = mach_vm_protect((mach_port_t)(uintptr_t)handle_, 177 | (mach_vm_address_t)address, 178 | (mach_vm_size_t)size, 179 | FALSE, 180 | VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE | VM_PROT_COPY)) { 181 | lol_throw_msg("mach_vm_protect: {:#x}", (std::uint32_t)err); 182 | } 183 | } 184 | 185 | auto Process::MarkMemoryExecutable(void* address, std::size_t size) const -> void { 186 | if (auto const err = mach_vm_protect((mach_port_t)(uintptr_t)handle_, 187 | (mach_vm_address_t)address, 188 | (mach_vm_size_t)size, 189 | FALSE, 190 | VM_PROT_READ | VM_PROT_EXECUTE)) { 191 | lol_throw_msg("mach_vm_protect: {:#x}", (std::uint32_t)err); 192 | } 193 | } 194 | 195 | #endif -------------------------------------------------------------------------------- /cslol-tools/lib/lol/utility/cli.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | using namespace lol; 7 | 8 | #ifdef _WIN32 9 | # ifndef WIN32_LEAN_AND_MEAN 10 | # define WIN32_LEAN_AND_MEAN 11 | # endif 12 | # ifndef NOMINMAX 13 | # define NOMINMAX 14 | # endif 15 | # include 16 | // do not reorder 17 | # include 18 | # include 19 | // do not reoder 20 | # include 21 | # include 22 | #endif 23 | 24 | auto utility::argv_fix(int argc, char** argv) noexcept -> std::vector { 25 | lol_trace_func(); 26 | auto result = std::vector(argv, argv + argc); 27 | #ifdef _WIN32 28 | if (auto wargc = 0; auto wargv = CommandLineToArgvW(GetCommandLineW(), &wargc)) { 29 | result = std::vector(wargv, wargv + wargc); 30 | } 31 | if (result.size() < 1 || result[0].empty()) { 32 | result[0] = "./out.exe"; 33 | } 34 | // we can do this on windows... 35 | if (wchar_t buffer[1 << 15]; auto outsize = GetModuleFileNameW(NULL, buffer, 1 << 15)) { 36 | auto exepath = std::wstring_view(buffer, outsize); 37 | if (std::wstring_view root = L"\\\\?\\"; exepath.starts_with(root)) { 38 | exepath.remove_prefix(root.size()); 39 | } 40 | result[0] = exepath; 41 | } 42 | #else 43 | if (result.size() < 1 || result[0].empty()) { 44 | result[0] = "./a.out"; 45 | } 46 | #endif 47 | return result; 48 | } 49 | 50 | auto utility::set_binary_io() noexcept -> void { 51 | #ifdef _WIN32 52 | _setmode(_fileno(stdout), O_BINARY); 53 | _setmode(_fileno(stdin), O_BINARY); 54 | #endif 55 | } -------------------------------------------------------------------------------- /cslol-tools/lib/lol/utility/cli.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace lol::utility { 8 | extern auto set_binary_io() noexcept -> void; 9 | 10 | extern auto argv_fix(int argc, char **argv) noexcept -> std::vector; 11 | 12 | template 13 | inline std::map argv_parse(std::vector argv, ArgsOut &...positional) noexcept { 14 | auto flags = std::map{}; 15 | std::erase_if(argv, [&flags](fs::path const &cur) mutable -> bool { 16 | if (auto str = cur.generic_string(); str.starts_with("--")) { 17 | if (auto colon = str.find(':'); colon != std::string::npos) { 18 | flags[str.substr(0, colon + 1)] = str.substr(colon + 1); 19 | } else { 20 | flags[str] = ""; 21 | } 22 | return true; 23 | } 24 | return false; 25 | }); 26 | argv.resize(std::max(argv.size(), sizeof...(positional))); 27 | std::size_t i = 0; 28 | ((positional = argv[i++]), ...); 29 | return flags; 30 | } 31 | } -------------------------------------------------------------------------------- /cslol-tools/lib/lol/utility/magic.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace lol; 5 | using namespace lol::utility; 6 | 7 | [[nodiscard]] std::string_view Magic::find(std::span data) noexcept { 8 | constexpr Magic const list[] = { 9 | {"RW\x01", ".wad"}, 10 | {"RW\x02", ".wad"}, 11 | {"RW\x03", ".wad"}, 12 | {"RLSM", ".releasemanifest"}, 13 | {"RMAN", ".manifest"}, 14 | {"RADS Solution", ".solutionmanifest"}, 15 | {"RST", ".stringtable"}, 16 | {"\x00\x00\x01\x00", ".ico"}, 17 | {"\x47\x49\x46\x38\x37\x61", ".gif"}, 18 | {"\x47\x49\x46\x38\x39\x61", ".gif"}, 19 | {"\x49\x49\x2A\x00", ".tif"}, 20 | {"\x49\x49\x00\x2A", ".tiff"}, 21 | {"\xFF\xD8\xFF\xDB", ".jpg"}, 22 | {"\xFF\xD8\xFF\xE0", ".jpg"}, 23 | {"\xFF\xD8\xFF\xEE", ".jpg"}, 24 | {"\xFF\xD8\xFF\xE1", ".jpg"}, 25 | {"\x42\x4D", ".bmp"}, 26 | {"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", ".png"}, 27 | {"DDS", ".dds"}, 28 | {"TEX\0", ".tex"}, 29 | {"OggS", ".ogg"}, 30 | {"\x00\x01\x00\x00\x00", ".ttf"}, 31 | {"\x74\x72\x75\x65\x00", ".ttf"}, 32 | {"OTTO\x00", ".otf"}, 33 | {"PROP", ".bin"}, 34 | {"PTCH", ".bin"}, 35 | {"BKHD", ".bnk"}, 36 | {"[ObjectBegin]", ".sco"}, 37 | {"r3d2Mesh", ".scb"}, 38 | {"r3d2aims", ".aimesh"}, 39 | {"r3d2anmd", ".anm"}, 40 | {"r3d2canm", ".anm"}, 41 | {"r3d2sklt", ".skl"}, 42 | {"r3d2blnd", ".blnd"}, 43 | {"r3d2wght", ".wgt"}, 44 | {"r3d2", ".wpk"}, 45 | {"\xC3\x4F\xFD\x22", ".skl", 4}, 46 | {"\x33\x22\x11\x00", ".skn"}, 47 | {"PreLoad", ".preload"}, 48 | {"\x1BLuaQ\x00\x01\x04\x04", ".luabin"}, 49 | {"\x1BLuaQ\x00\x01\x04\x08", ".luabin64"}, 50 | {"OPAM", ".mob"}, 51 | {"[MaterialBegin]", ".mat"}, 52 | {"WGEO", ".wgeo"}, 53 | {"MGEO", ".mapgeo"}, 54 | {"OEGM", ".mapgeo"}, 55 | {"NVR\x00", ".nvr"}, 56 | {"{\r\n", ".json"}, 57 | {"[\r\n", ".json"}, 58 | {"= offset) { 80 | src = src.substr(offset); 81 | } else { 82 | continue; 83 | } 84 | if (src.starts_with(magic)) { 85 | return extension; 86 | } 87 | } 88 | return ""; 89 | } 90 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/utility/magic.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace lol::utility { 7 | struct Magic { 8 | std::string_view magic = {}; 9 | std::string_view extension = {}; 10 | std::size_t offset = 0; 11 | 12 | template 13 | [[nodiscard]] constexpr Magic(char const (&magic)[SM], 14 | char const (&extension)[SE], 15 | std::size_t offset = 0) noexcept 16 | : magic({magic, SM - 1}), extension({extension, SE - 1}), offset(offset) {} 17 | 18 | [[nodiscard]] static std::string_view find(std::span data) noexcept; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/utility/zip.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace lol; 10 | using namespace lol::utility; 11 | 12 | #define lol_throw_if_zip(func, ...) \ 13 | lol_throw_if_msg(!(func(__VA_ARGS__)), #func ": {}", mz_zip_get_error_string(mz_zip_get_last_error(zip.get()))) 14 | 15 | auto utility::zip(fs::path const& src, fs::path const& dst) -> void { 16 | lol_trace_func(lol_trace_var("{}", src), lol_trace_var("{}", dst)); 17 | auto zip = std::shared_ptr(new mz_zip_archive{}, [](mz_zip_archive* p) { 18 | mz_zip_writer_end(p); 19 | delete p; 20 | }); 21 | lol_throw_if(zip.get() == nullptr); 22 | auto dst_file = io::File::create(dst); 23 | dst_file.reserve(0, 1 * io::GiB); 24 | zip->m_pIO_opaque = &dst_file; 25 | zip->m_pWrite = [](void* opaq, mz_uint64 pos, void const* src, size_t n) -> size_t { 26 | return opaq ? ((io::File*)opaq)->writesome(pos, src, n) : 0; 27 | }; 28 | lol_throw_if_zip(mz_zip_writer_init_v2, zip.get(), 0, 0); 29 | for (auto const& dirent : fs::recursive_directory_iterator(src)) { 30 | if (!dirent.is_regular_file()) continue; 31 | auto file_path = dirent.path(); 32 | auto src_file = io::Bytes::from_file(file_path); 33 | auto file_name = fs::relative(file_path, src).generic_string(); 34 | lol_throw_if_zip(mz_zip_writer_add_mem, zip.get(), file_name.c_str(), src_file.data(), src_file.size(), 0); 35 | } 36 | lol_throw_if_zip(mz_zip_writer_finalize_archive, zip.get()); 37 | } 38 | 39 | auto utility::unzip(fs::path const& src, fs::path const& dst) -> void { 40 | lol_trace_func(lol_trace_var("{}", src), lol_trace_var("{}", dst)); 41 | auto zip = std::shared_ptr(new mz_zip_archive{}, [](mz_zip_archive* p) { 42 | mz_zip_reader_end(p); 43 | delete p; 44 | }); 45 | lol_throw_if(zip.get() == nullptr); 46 | auto src_file = io::Bytes::from_file(src); 47 | lol_throw_if_zip(mz_zip_reader_init_mem, zip.get(), src_file.data(), src_file.size(), 0); 48 | for (std::uint32_t index = 0; index < zip->m_total_files; ++index) { 49 | mz_zip_archive_file_stat stat = {}; 50 | lol_throw_if_zip(mz_zip_reader_file_stat, zip.get(), index, &stat); 51 | if (stat.m_is_directory || !stat.m_is_supported) continue; 52 | auto file_path = fs::path((char const*)stat.m_filename).lexically_normal(); 53 | for (auto const& component : file_path) { 54 | lol_throw_if(component == ".."); 55 | } 56 | auto dst_file = io::File::create(dst / file_path); 57 | auto dst_data = dst_file.resize(stat.m_uncomp_size); 58 | lol_throw_if_zip(mz_zip_reader_extract_to_mem_no_alloc, zip.get(), index, dst_data, dst_file.size(), 0, 0, 0); 59 | } 60 | } -------------------------------------------------------------------------------- /cslol-tools/lib/lol/utility/zip.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace lol::utility { 6 | extern auto zip(fs::path const& src, fs::path const& dst) -> void; 7 | extern auto unzip(fs::path const& src, fs::path const& dst) -> void; 8 | } 9 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/wad/archive.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace lol; 12 | using namespace lol::wad; 13 | 14 | auto Archive::read_from_toc(io::Bytes src, TOC const& toc) -> Archive { 15 | lol_trace_func(lol_trace_var("{}", toc.entries.size())); 16 | auto descriptors_by_checksum = std::unordered_map{}; 17 | descriptors_by_checksum.reserve(toc.entries.size()); 18 | auto archive = Archive{}; 19 | for (auto const& entry : toc.entries) { 20 | if (entry.loc.checksum) { 21 | if (auto old = descriptors_by_checksum.find(entry.loc.checksum); old != descriptors_by_checksum.end()) { 22 | archive.entries[entry.name] = old->second; 23 | continue; 24 | } 25 | } 26 | auto data = EntryData::from_loc(src, entry.loc); 27 | archive.entries[entry.name] = data; 28 | if (entry.loc.checksum) { 29 | descriptors_by_checksum[entry.loc.checksum] = data; 30 | } 31 | } 32 | return archive; 33 | } 34 | 35 | auto Archive::read_from_file(fs::path const& path) -> Archive { 36 | lol_trace_func(lol_trace_var("{}", path)); 37 | auto src = io::Bytes::from_file(path); 38 | auto toc = TOC{}; 39 | auto const toc_error = toc.read(src); 40 | lol_throw_if(toc_error); 41 | return read_from_toc(src, toc); 42 | } 43 | 44 | auto Archive::pack_from_directory(fs::path const& dir) -> Archive { 45 | lol_trace_func(lol_trace_var("{}", dir)); 46 | auto archive = Archive{}; 47 | for (auto const& dirent : fs::recursive_directory_iterator(dir)) { 48 | if (!dirent.is_regular_file()) continue; 49 | archive.add_from_directory(dirent.path(), dir); 50 | } 51 | return archive; 52 | } 53 | 54 | auto Archive::add_from_directory(fs::path const& path, fs::path const& dir) -> void { 55 | lol_trace_func(lol_trace_var("{}", path), lol_trace_var("{}", dir)); 56 | auto name = hash::Xxh64::from_path(fs::relative(path, dir)); 57 | auto data = EntryData::from_file(path); 58 | entries.insert_or_assign(name, data); 59 | } 60 | 61 | auto Archive::estimate_size() const noexcept -> std::size_t { 62 | auto estimate = sizeof(TOC::latest_header_t) + sizeof(TOC::latest_entry_t) * entries.size(); 63 | for (auto const& [name, data] : entries) { 64 | estimate += data.bytes_size(); 65 | } 66 | return estimate; 67 | } 68 | 69 | auto Archive::write_to_file(fs::path const& path) const -> void { 70 | lol_trace_func(lol_trace_var("{}", path)); 71 | using HeaderT = TOC::latest_header_t; 72 | using EntryT = TOC::latest_entry_t; 73 | 74 | auto file = io::File::create(path); 75 | 76 | auto new_header = HeaderT{ 77 | .version = TOC::Version::latest(), 78 | .signature = {}, 79 | .checksum = {}, 80 | .desc_count = (std::uint32_t)entries.size(), 81 | }; 82 | 83 | { 84 | auto version = TOC::Version::latest(); 85 | XXH3_state_s hashstate = {}; 86 | XXH3_128bits_reset(&hashstate); 87 | XXH3_128bits_update(&hashstate, &version, sizeof(version)); 88 | for (auto const& [name, data] : entries) { 89 | auto const checksum = data.checksum(); 90 | XXH3_128bits_update(&hashstate, &name, sizeof(name)); 91 | XXH3_128bits_update(&hashstate, &checksum, sizeof(checksum)); 92 | } 93 | auto const hashdigest = XXH3_128bits_digest(&hashstate); 94 | std::memcpy((char*)&new_header.signature, (char const*)&hashdigest, sizeof(TOC::Signature)); 95 | } 96 | 97 | if (auto old_header = HeaderT{}; file.readsome(0, &old_header, sizeof(HeaderT)) == sizeof(HeaderT)) { 98 | if (std::memcmp(&old_header, &new_header, sizeof(HeaderT)) == 0) { 99 | return; 100 | } 101 | } 102 | 103 | auto toc_entries = std::vector{}; 104 | toc_entries.reserve(entries.size()); 105 | 106 | std::size_t data_cur = sizeof(HeaderT) + sizeof(EntryT) * entries.size(); 107 | { 108 | // Order any potential reads by address, this guarantees sequential reads on any memory mapped files. 109 | auto entries_data = std::vector>(); 110 | entries_data.reserve(entries.size()); 111 | entries_data.insert(entries_data.end(), entries.begin(), entries.end()); 112 | std::sort(entries_data.begin(), entries_data.end(), [](auto const& l, auto const& r) -> bool { 113 | return (std::uintptr_t)l.second.bytes_data() < (std::uintptr_t)r.second.bytes_data(); 114 | }); 115 | 116 | // Deduplicate data locations by checksum 117 | auto loc_by_checksum = std::unordered_map(); 118 | loc_by_checksum.reserve(entries.size() * 2); 119 | 120 | // Write all entries data and generate TOC 121 | file.reserve(0, estimate_size()); 122 | for (auto const& entry : entries_data) { 123 | auto loc = EntryLoc{}; 124 | auto data = entry.second.into_optimal(); 125 | auto checksum = data.checksum(); 126 | if (auto j = loc_by_checksum.find(checksum); j != loc_by_checksum.end()) { 127 | loc = j->second; 128 | } else { 129 | loc = { 130 | .type = data.type(), 131 | .subchunk_count = data.subchunk_count(), 132 | .subchunk_index = data.subchunk_index(), 133 | .offset = data_cur, 134 | .size = data.bytes_size(), 135 | .size_decompressed = data.size_decompressed(), 136 | .checksum = checksum, 137 | }; 138 | lol_throw_if(loc.size >= 4 * io::GiB); 139 | lol_throw_if(loc.size_decompressed >= 4 * io::GiB); 140 | lol_throw_if(loc.offset >= 4 * io::GiB); 141 | file.write(data_cur, data.bytes_data(), data.bytes_size()); 142 | loc_by_checksum.emplace_hint(j, checksum, loc); 143 | data_cur += loc.size; 144 | } 145 | toc_entries.push_back(EntryT{ 146 | .name = entry.first, 147 | .offset = (std::uint32_t)loc.offset, 148 | .size = (std::uint32_t)loc.size, 149 | .size_decompressed = (std::uint32_t)loc.size_decompressed, 150 | .type = loc.type, 151 | .subchunk_count = loc.subchunk_count, 152 | .subchunk_index = loc.subchunk_index, 153 | .checksum = loc.checksum, 154 | }); 155 | }; 156 | } 157 | 158 | { 159 | // TOC entries MUST be sorted by name 160 | std::sort(toc_entries.begin(), toc_entries.end(), [](auto const& l, auto const& r) -> bool { 161 | return l.name < r.name; 162 | }); 163 | } 164 | 165 | // Write TOC after header 166 | file.write(sizeof(HeaderT), toc_entries.data(), toc_entries.size() * sizeof(EntryT)); 167 | 168 | // Write header 169 | file.write(0, &new_header, sizeof(HeaderT)); 170 | 171 | // Trunc data 172 | file.resize(data_cur); 173 | } 174 | 175 | auto Archive::touch() -> void { 176 | // Order any potential reads by address, this guarantees sequential reads on any memory mapped files. 177 | auto entries = std::vector>(); 178 | entries.reserve(entries.size()); 179 | entries.insert(entries.end(), entries.begin(), entries.end()); 180 | std::sort(entries.begin(), entries.end(), [](auto const& l, auto const& r) -> bool { 181 | return (std::uintptr_t)l.second.bytes_data() < (std::uintptr_t)r.second.bytes_data(); 182 | }); 183 | 184 | for (auto& [name, data] : entries) { 185 | data.touch(); 186 | } 187 | } 188 | 189 | auto Archive::mark_optimal() -> void { 190 | for (auto& [name, data] : entries) { 191 | data.mark_optimal(true); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/wad/archive.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace lol::wad { 13 | //! Wad archive. 14 | struct Archive { 15 | using Map = std::map; 16 | 17 | static auto read_from_toc(io::Bytes src, TOC const& toc) -> Archive; 18 | 19 | static auto read_from_file(fs::path const& path) -> Archive; 20 | 21 | static auto pack_from_directory(fs::path const& path) -> Archive; 22 | 23 | auto touch() -> void; 24 | 25 | auto mark_optimal() -> void; 26 | 27 | auto add_from_directory(fs::path const& path, fs::path const& dir) -> void; 28 | 29 | auto write_to_file(fs::path const& path) const -> void; 30 | 31 | auto estimate_size() const noexcept -> std::size_t; 32 | 33 | template 34 | requires(std::is_invocable::value) 35 | auto foreach_overlap(Archive const& rhs, Func&& func) const { 36 | auto lhs_cur = this->entries.begin(); 37 | auto const lhs_end = this->entries.end(); 38 | auto rhs_cur = rhs.entries.begin(); 39 | auto const rhs_end = rhs.entries.end(); 40 | while (lhs_cur != lhs_end && rhs_cur != rhs_end) { 41 | if (lhs_cur->first < rhs_cur->first) { 42 | ++lhs_cur; 43 | continue; 44 | } 45 | if (lhs_cur->first > rhs_cur->first) { 46 | ++rhs_cur; 47 | continue; 48 | } 49 | func(*lhs_cur, *rhs_cur); 50 | ++lhs_cur; 51 | ++rhs_cur; 52 | } 53 | } 54 | 55 | template 56 | requires(std::is_invocable::value) 57 | auto foreach_overlap_mut(Archive const& rhs, Func&& func) { 58 | auto lhs_cur = this->entries.begin(); 59 | auto const lhs_end = this->entries.end(); 60 | auto rhs_cur = rhs.entries.begin(); 61 | auto const rhs_end = rhs.entries.end(); 62 | while (lhs_cur != lhs_end && rhs_cur != rhs_end) { 63 | if (lhs_cur->first < rhs_cur->first) { 64 | ++lhs_cur; 65 | continue; 66 | } 67 | if (lhs_cur->first > rhs_cur->first) { 68 | ++rhs_cur; 69 | continue; 70 | } 71 | func(*lhs_cur, *rhs_cur); 72 | ++lhs_cur; 73 | ++rhs_cur; 74 | } 75 | } 76 | 77 | template 78 | requires(std::is_invocable_r::value) 79 | auto erase_overlap(Archive const& rhs, Func&& func) { 80 | auto lhs_cur = this->entries.begin(); 81 | auto rhs_cur = rhs.entries.begin(); 82 | auto const rhs_end = rhs.entries.end(); 83 | while (lhs_cur != entries.end() && rhs_cur != rhs_end) { 84 | if (lhs_cur->first < rhs_cur->first) { 85 | ++lhs_cur; 86 | continue; 87 | } 88 | if (lhs_cur->first > rhs_cur->first) { 89 | ++rhs_cur; 90 | continue; 91 | } 92 | if (func(*lhs_cur, *rhs_cur)) { 93 | lhs_cur = entries.erase(lhs_cur); 94 | } else { 95 | ++lhs_cur; 96 | } 97 | ++rhs_cur; 98 | } 99 | } 100 | 101 | auto overlaping(Archive const& upper) const noexcept -> Archive { 102 | auto result = Archive{}; 103 | foreach_overlap(upper, [&result](Map::const_reference lhs, Map::const_reference rhs) mutable { 104 | result.entries.insert_or_assign(rhs.first, rhs.second); 105 | }); 106 | return result; 107 | } 108 | 109 | auto merge_in(Archive const& other) -> void { 110 | Map::const_iterator beg = other.entries.begin(); 111 | Map::const_iterator end = other.entries.end(); 112 | for (auto hint = entries.begin(); beg != end; ++beg) { 113 | hint = entries.insert_or_assign(hint, beg->first, beg->second); 114 | } 115 | } 116 | 117 | Map entries; 118 | }; 119 | } 120 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/wad/entry.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace lol::wad { 10 | enum class EntryType : unsigned char { 11 | Raw = 0, 12 | Link = 1, 13 | Gzip = 2, 14 | Zstd = 3, 15 | ZstdMulti = 4, 16 | }; 17 | 18 | struct EntryLoc { 19 | EntryType type; 20 | std::uint8_t subchunk_count = {}; 21 | std::uint32_t subchunk_index = {}; 22 | std::uint64_t offset; 23 | std::uint64_t size; 24 | std::uint64_t size_decompressed; 25 | std::uint64_t checksum; 26 | }; 27 | 28 | struct EntryData { 29 | EntryData() noexcept = default; 30 | 31 | EntryData(EntryData const& other) noexcept : impl_(other.impl_) {} 32 | 33 | EntryData(EntryData&& other) noexcept : impl_(std::exchange(other.impl_, Impl::empty())) {} 34 | 35 | EntryData& operator=(EntryData other) noexcept { 36 | std::swap(other.impl_, impl_); 37 | return *this; 38 | } 39 | 40 | static auto from_raw(io::Bytes data, std::uint64_t checksum) -> EntryData; 41 | 42 | static auto from_link(io::Bytes data, std::uint64_t checksum) -> EntryData; 43 | 44 | static auto from_gzip(io::Bytes data, std::size_t decompressed, std::uint64_t checksum) -> EntryData; 45 | 46 | static auto from_zstd(io::Bytes data, std::size_t decompressed, std::uint64_t checksum) -> EntryData; 47 | 48 | static auto from_zstd_multi(io::Bytes data, 49 | std::size_t decompressed, 50 | std::uint64_t checksum, 51 | std::uint8_t subchunk_count, 52 | std::uint32_t subchunk_index) -> EntryData; 53 | 54 | static auto from_file(fs::path const& path) -> EntryData; 55 | 56 | static auto from_loc(io::Bytes src, EntryLoc const& loc) -> EntryData; 57 | 58 | auto into_compressed() const -> EntryData; 59 | 60 | auto into_decompressed() const -> EntryData; 61 | 62 | auto into_optimal() const -> EntryData; 63 | 64 | auto checksum() const -> std::uint64_t; 65 | 66 | auto extension() const -> std::string_view; 67 | 68 | auto type() const noexcept -> EntryType { return impl_->type; } 69 | 70 | auto subchunk_count() const noexcept -> std::uint8_t { return impl_->subchunk_count; } 71 | 72 | auto subchunk_index() const noexcept -> std::uint32_t { return impl_->subchunk_index; } 73 | 74 | auto bytes() const noexcept -> io::Bytes { return impl_->bytes; } 75 | 76 | auto bytes_data() const noexcept -> char const* { return impl_->bytes.data(); } 77 | 78 | auto bytes_size() const noexcept -> std::size_t { return impl_->bytes.size(); } 79 | 80 | auto size_decompressed() const noexcept -> std::size_t { return impl_->size_decompressed; } 81 | 82 | auto mark_dirty() -> void { impl_->checksum = 0; } 83 | 84 | auto mark_optimal(bool value) -> void { impl_->is_optimal = value; } 85 | 86 | auto touch() -> void { impl_->bytes.reserve(0, 0); } 87 | 88 | auto write_to_file(fs::path const& path) const -> void; 89 | 90 | auto write_to_dir(hash::Xxh64 name, fs::path const& dir, hash::Dict const* dict) const -> void; 91 | 92 | private: 93 | struct Impl { 94 | EntryType type = EntryType::Raw; 95 | bool is_optimal = false; 96 | std::uint8_t subchunk_count = {}; 97 | std::uint16_t subchunk_index = {}; 98 | std::optional extension = {}; 99 | io::Bytes bytes = {}; 100 | std::size_t size_decompressed = 0; 101 | mutable std::uint64_t checksum = 0; 102 | mutable std::weak_ptr compressed = {}; 103 | mutable std::weak_ptr decompressed = {}; 104 | 105 | static auto empty() noexcept -> std::shared_ptr const&; 106 | }; 107 | 108 | std::shared_ptr impl_ = Impl::empty(); 109 | 110 | explicit EntryData(std::shared_ptr impl) noexcept : impl_(std::move(impl)) {} 111 | }; 112 | } 113 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/wad/index.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace lol; 6 | using namespace lol::wad; 7 | 8 | auto Index::from_game_folder(fs::path const& game_path) -> Index { 9 | lol_trace_func(lol_trace_var("{}", game_path)); 10 | auto index = Index{game_path.filename().generic_string()}; 11 | if (fs::path final_path = game_path / "DATA" / "FINAL"; fs::exists(final_path)) { 12 | index.add_from_game_folder(final_path, game_path); 13 | } 14 | return index; 15 | } 16 | 17 | auto Index::add_from_game_folder(fs::path const& path, fs::path const& game_path) -> void { 18 | for (auto const& dirent : fs::directory_iterator(path)) { 19 | auto path = dirent.path(); 20 | auto filename = path.filename().generic_string(); 21 | if (dirent.is_directory()) { 22 | if (filename.ends_with(".wad") || filename.ends_with(".wad.client")) { 23 | continue; 24 | } 25 | this->add_from_game_folder(path, game_path); 26 | } else { 27 | if (!filename.ends_with(".wad.client")) { 28 | continue; 29 | } 30 | auto mounted = Mounted{}; 31 | if (auto error = mounted.read_from_game_file(path, game_path)) { 32 | logw("Failed to open game wad file {}: {}", path, error); 33 | continue; 34 | } 35 | auto name = mounted.name(); 36 | mounts.insert_or_assign(std::move(name), std::move(mounted)); 37 | } 38 | } 39 | } 40 | 41 | auto Index::from_mod_folder(fs::path const& mod_path) -> Index { 42 | lol_trace_func(lol_trace_var("{}", mod_path)); 43 | lol_throw_if_msg(!fs::exists(mod_path / "META" / "info.json"), "Not valid mod!"); 44 | auto index = Index{mod_path.filename().generic_string()}; 45 | if (fs::path wads_path = mod_path / "WAD"; fs::exists(wads_path)) { 46 | for (auto const& dirent : fs::directory_iterator(wads_path)) { 47 | auto path = dirent.path(); 48 | auto mounted = Mounted{}; 49 | if (dirent.is_regular_file()) { 50 | if (auto error = mounted.read_from_mod_file(path, mod_path)) { 51 | logw("Failed to open mod wad file {}: {}", path, error); 52 | continue; 53 | } 54 | } else if (dirent.is_directory()) { 55 | if (auto error = mounted.read_from_mod_folder(path, mod_path)) { 56 | logw("Failed to open mod wad folder {}: {}", path, error); 57 | continue; 58 | } 59 | } else { 60 | continue; 61 | } 62 | auto name = mounted.name(); 63 | index.mounts.insert_or_assign(std::move(name), std::move(mounted)); 64 | } 65 | } 66 | if (fs::path raw_path = mod_path / "RAW"; fs::exists(raw_path)) { 67 | auto mounted = Mounted{}; 68 | mounted.relpath = "WAD/_RAW.wad.client"; 69 | mounted.archive = Archive::pack_from_directory(raw_path); 70 | auto name = mounted.name(); 71 | index.mounts.insert_or_assign(std::move(name), std::move(mounted)); 72 | } 73 | return index; 74 | } 75 | 76 | auto Index::write_to_directory(fs::path const& dir) const -> void { 77 | lol_trace_func(lol_trace_var("{}", this->name), lol_trace_var("{}", dir)); 78 | fs::create_directories(dir); 79 | for (auto const& [mount_name, mounted] : mounts) { 80 | logi("Writing wad: {}", mounted.relpath); 81 | auto const& [relpath, archive] = mounted; 82 | archive.write_to_file(dir / relpath); 83 | } 84 | } 85 | 86 | auto Index::cleanup_in_directory(fs::path const& dir) const -> void { 87 | lol_trace_func(lol_trace_var("{}", this->name), lol_trace_var("{}", dir)); 88 | for (auto const& dirent : fs::recursive_directory_iterator(dir)) { 89 | auto path = dirent.path(); 90 | auto filename = path.filename().generic_string(); 91 | auto wadclient = std::string_view{".wad.client"}; 92 | if (!filename.ends_with(".wad.client")) { 93 | continue; 94 | } 95 | if (dirent.is_regular_file() && !mounts.contains(Mounted::make_name(path.filename()))) { 96 | fs::remove(path); 97 | } 98 | } 99 | } 100 | 101 | auto Index::find_by_overlap(Archive const& archive) const noexcept -> Mounted const* { 102 | std::size_t max_count = 0; 103 | Mounted const* found = nullptr; 104 | for (auto const& [mount_name, mounted] : mounts) { 105 | auto count = std::size_t{}; 106 | mounted.archive.foreach_overlap( 107 | archive, 108 | [&](Archive::Map::const_reference lhs, Archive::Map::const_reference rhs) { ++count; }); 109 | if (count > max_count) { 110 | max_count = count; 111 | found = &mounted; 112 | } 113 | } 114 | return found; 115 | } 116 | 117 | auto Index::find_by_mount_name(std::string const& mount_name) const noexcept -> Mounted const* { 118 | if (auto base = mounts.find(mount_name); base != mounts.end()) { 119 | return &base->second; 120 | } 121 | return nullptr; 122 | } 123 | 124 | auto Index::find_by_mount_name_or_overlap(std::string const& mount_name, Archive const& archive) const noexcept 125 | -> Mounted const* { 126 | if (auto base = find_by_mount_name(mount_name)) { 127 | return base; 128 | } 129 | return find_by_overlap(archive); 130 | } 131 | 132 | auto Index::touch() -> void { 133 | for (auto& [mount_name, mounted] : mounts) { 134 | mounted.archive.touch(); 135 | } 136 | } 137 | 138 | auto Index::rebase_from_game(Index const& game) const -> Index { 139 | lol_trace_func(lol_trace_var("{}", this->name)); 140 | auto copy = Index{}; 141 | for (auto const& [mount_name, mounted] : mounts) { 142 | if (mount_name.empty() || !game.mounts.contains(mount_name)) { 143 | auto base_mounted = game.find_by_overlap(mounted.archive); 144 | lol_throw_if_msg(!base_mounted, "Failed to find base wad for: {}", mount_name); 145 | auto fixed = Mounted{mounted.relpath.parent_path() / base_mounted->relpath.filename(), mounted.archive}; 146 | copy.mounts.emplace(base_mounted->name(), std::move(fixed)); 147 | continue; 148 | } 149 | copy.mounts.emplace(mount_name, mounted); 150 | } 151 | return copy; 152 | } 153 | 154 | auto Index::add_overlay_mod(Index const& game, Index const& mod) -> void { 155 | lol_trace_func(lol_trace_var("{}", mod.name)); 156 | for (auto const& mod_mounted : mod.mounts) { 157 | auto const& mod_mount_name = mod_mounted.first; 158 | auto const& mod_mounted_archive = mod_mounted.second.archive; 159 | auto base_mounted = game.find_by_mount_name_or_overlap(mod_mount_name, mod_mounted_archive); 160 | lol_throw_if_msg(!base_mounted, "Failed to find base wad for: {}", mod_mount_name); 161 | auto combined_mounted = mounts.find(mod_mount_name); 162 | if (combined_mounted == mounts.end()) { 163 | combined_mounted = mounts.emplace_hint(combined_mounted, base_mounted->name(), *base_mounted); 164 | } 165 | combined_mounted->second.archive.merge_in(mod_mounted_archive); 166 | for (auto const& [extra_mount_name, extra_mounted] : game.mounts) { 167 | if (&extra_mounted == base_mounted) continue; 168 | auto overlap_archive = extra_mounted.archive.overlaping(mod_mounted_archive); 169 | if (overlap_archive.entries.empty()) continue; 170 | auto combined = mounts.find(extra_mount_name); 171 | if (combined == mounts.end()) { 172 | combined = mounts.emplace_hint(combined, extra_mount_name, extra_mounted); 173 | } 174 | combined->second.archive.merge_in(overlap_archive); 175 | } 176 | } 177 | } 178 | 179 | auto Index::resolve_conflicts(Index const& other, bool ignore) -> void { 180 | lol_trace_func(lol_trace_var("{}", this->name), lol_trace_var("{}", other.name)); 181 | for (auto& [mount_name, mounted] : mounts) { 182 | for (auto const& [other_mount_name, other_mounted] : other.mounts) { 183 | mounted.resolve_conflicts(other_mounted, ignore); 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/wad/index.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace lol::wad { 10 | struct Index { 11 | using Map = std::map; 12 | 13 | static auto from_game_folder(fs::path const& game_path) -> Index; 14 | 15 | static auto from_mod_folder(fs::path const& mod_path) -> Index; 16 | 17 | auto write_to_directory(fs::path const& dir) const -> void; 18 | 19 | auto cleanup_in_directory(fs::path const& dir) const -> void; 20 | 21 | auto find_by_overlap(Archive const& archive) const noexcept -> Mounted const*; 22 | 23 | auto find_by_mount_name(std::string const& name) const noexcept -> Mounted const*; 24 | 25 | auto find_by_mount_name_or_overlap(std::string const& name, Archive const& archive) const noexcept 26 | -> Mounted const*; 27 | 28 | auto touch() -> void; 29 | 30 | auto rebase_from_game(Index const& game) const -> Index; 31 | 32 | auto add_overlay_mod(Index const& game, Index const& mod) -> void; 33 | 34 | auto resolve_conflicts(Index const& other, bool ignore) -> void; 35 | 36 | template 37 | requires(std::is_invocable_r_v) 38 | auto remove_filter(Func&& func) -> void { 39 | for (auto i = mounts.begin(); i != mounts.end();) { 40 | if (func(*i)) { 41 | i = mounts.erase(i); 42 | } else { 43 | ++i; 44 | } 45 | } 46 | } 47 | 48 | std::string name; 49 | Map mounts = {}; 50 | 51 | private: 52 | auto add_from_game_folder(fs::path const& path, fs::path const& game_path) -> void; 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/wad/mounted.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace lol; 6 | using namespace lol::wad; 7 | 8 | auto Mounted::resolve_conflicts(Mounted const& other, bool ignore) -> void { 9 | if (this == &other) return; 10 | lol_trace_func(lol_trace_var("{}", this->relpath), lol_trace_var("{}", other.relpath)); 11 | archive.foreach_overlap_mut( 12 | other.archive, 13 | [&, ignore](Archive::Map::reference old_entry, Archive::Map::const_reference new_entry) -> void { 14 | // try to reconcile if checksum is same 15 | if (old_entry.second.checksum() == new_entry.second.checksum()) { 16 | old_entry.second = new_entry.second; 17 | return; 18 | } 19 | // try to reconcile if uncompressed checksum is same 20 | if (old_entry.second.into_decompressed().checksum() == new_entry.second.into_decompressed().checksum()) { 21 | logw("Compressed checksum conflict: {:#016x} in {}", old_entry.first, other.relpath); 22 | old_entry.second = new_entry.second; 23 | return; 24 | } 25 | // ignore conflict regardles 26 | if (ignore) { 27 | old_entry.second = new_entry.second; 28 | logw("Conflicting file: {:#016x} in {}", old_entry.first, other.relpath); 29 | return; 30 | } 31 | lol_throw_msg("Conflicting file: {:#016x}", old_entry.first); 32 | }); 33 | } 34 | 35 | auto Mounted::remove_unknown(Mounted const& other) noexcept -> std::size_t { 36 | auto count = std::size_t{}; 37 | for (auto i = archive.entries.begin(); i != archive.entries.end();) { 38 | if (!other.archive.entries.contains(i->first)) { 39 | i = archive.entries.erase(i); 40 | ++count; 41 | } else { 42 | ++i; 43 | } 44 | } 45 | return count; 46 | } 47 | 48 | auto Mounted::remove_unmodified(Mounted const& other) noexcept -> std::size_t { 49 | auto count = std::size_t{}; 50 | archive.erase_overlap( 51 | other.archive, 52 | [&count](Archive::Map::const_reference old_entry, Archive::Map::const_reference new_entry) mutable -> bool { 53 | // remove if checksum is same 54 | if (old_entry.second.checksum() == new_entry.second.checksum()) { 55 | ++count; 56 | return true; 57 | } 58 | // remove if uncompressed checksum is same 59 | if (old_entry.second.into_decompressed().checksum() == new_entry.second.into_decompressed().checksum()) { 60 | ++count; 61 | return true; 62 | } 63 | return false; 64 | }); 65 | return count; 66 | } 67 | 68 | auto Mounted::read_from_game_file(fs::path const& path, fs::path const& game_path) -> char const* { 69 | lol_trace_func(lol_trace_var("{}", path), lol_trace_var("{}", game_path)); 70 | relpath = fs::relative(path, game_path); 71 | auto src = io::Bytes::from_file(path); 72 | auto toc = TOC{}; 73 | if (auto error = toc.read(src)) [[unlikely]] { 74 | return error; 75 | } 76 | if (toc.version != TOC::Version::latest()) [[unlikely]] { 77 | return "Unknown wad version"; 78 | } 79 | if (toc.entries.size() == 0) { 80 | return nullptr; 81 | } 82 | archive = Archive::read_from_toc(src, toc); 83 | archive.mark_optimal(); 84 | return nullptr; 85 | } 86 | 87 | auto Mounted::read_from_mod_file(fs::path const& path, fs::path const& mod_path) -> char const* { 88 | lol_trace_func(lol_trace_var("{}", path), lol_trace_var("{}", mod_path)); 89 | relpath = fs::relative(path, mod_path); 90 | auto filename = path.filename().generic_string(); 91 | if (!filename.ends_with(".wad.client")) { 92 | return "Not .wad.client file"; 93 | } 94 | archive = Archive::read_from_file(path); 95 | return nullptr; 96 | } 97 | 98 | auto Mounted::read_from_mod_folder(fs::path const& path, fs::path const& mod_path) -> char const* { 99 | lol_trace_func(lol_trace_var("{}", path), lol_trace_var("{}", mod_path)); 100 | relpath = fs::relative(path, mod_path); 101 | auto filename = path.filename().generic_string(); 102 | if (!filename.ends_with(".wad.client") && !filename.ends_with(".wad")) { 103 | return "Not .wad folder"; 104 | } 105 | archive = Archive::pack_from_directory(path); 106 | return nullptr; 107 | } 108 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/wad/mounted.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace lol::wad { 9 | struct Mounted { 10 | fs::path relpath; 11 | Archive archive; 12 | 13 | static auto make_name(fs::path const& path) noexcept -> std::string { 14 | std::string name = path.filename().generic_string(); 15 | std::transform(name.begin(), name.end(), name.begin(), ::tolower); 16 | if (std::string_view client = ".client"; name.ends_with(client)) { 17 | name.resize(name.size() - client.size()); 18 | } 19 | if (std::string_view wad = ".wad"; name.ends_with(".wad")) { 20 | name.resize(name.size() - wad.size()); 21 | } 22 | return name; 23 | }; 24 | 25 | auto name() const noexcept -> std::string { return make_name(relpath); } 26 | 27 | auto resolve_conflicts(Mounted const& other, bool ignor) -> void; 28 | 29 | auto remove_unknown(Mounted const& other) noexcept -> std::size_t; 30 | 31 | auto remove_unmodified(Mounted const& other) noexcept -> std::size_t; 32 | 33 | auto read_from_game_file(fs::path const& path, fs::path const& game_path) -> char const*; 34 | 35 | auto read_from_mod_file(fs::path const& path, fs::path const& mod_path) -> char const*; 36 | 37 | auto read_from_mod_folder(fs::path const& path, fs::path const& mod_path) -> char const*; 38 | 39 | auto add_from_mod_legacy_raw(fs::path const& path, fs::path const& mod_path) -> char const*; 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/wad/toc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace lol; 7 | using namespace lol::wad; 8 | 9 | auto TOC::read(io::Bytes src) noexcept -> char const* { 10 | if (src.size() < sizeof(TOC::Version)) { 11 | // NOTE: we atempt to ignore empty all 0 files early, proper solution is to handle locales 12 | return nullptr; 13 | } 14 | std::memcpy(&version, src.data(), sizeof(TOC::Version)); 15 | if (std::memcmp(&version, "\0\0\0\0", 4) == 0) { 16 | // NOTE: we atempt to ignore empty all 0 files early, proper solution is to handle locales 17 | return nullptr; 18 | } 19 | if (!version.is_wad()) { 20 | return "Bad Version::magic."; 21 | } 22 | auto read_raw = [&](io::Bytes src, auto header_raw, auto entry_raw) mutable noexcept -> char const* { 23 | if (src.size() < sizeof(header_raw)) { 24 | return "Not enough data for Header"; 25 | } 26 | std::memcpy(&header_raw, src.data(), sizeof(header_raw)); 27 | if (header_raw.desc_size != sizeof(entry_raw)) { 28 | return "Bad Header::desc_size."; 29 | } 30 | if (src.size() < header_raw.desc_offset) { 31 | return "Bad Header::desc_offset."; 32 | } 33 | if (src.size() - header_raw.desc_offset < sizeof(entry_raw) * header_raw.desc_count) { 34 | return "Not enough data for TOC."; 35 | } 36 | entries.clear(); 37 | entries.reserve(header_raw.desc_count); 38 | for (std::uint32_t i = 0; i != header_raw.desc_count; ++i) { 39 | std::memcpy(&entry_raw, src.data() + header_raw.desc_offset + sizeof(entry_raw) * i, sizeof(entry_raw)); 40 | entries.emplace_back(Entry{ 41 | .name = hash::Xxh64(entry_raw.name), 42 | .loc = 43 | { 44 | .type = entry_raw.type, 45 | .subchunk_count = entry_raw.subchunk_count, 46 | .subchunk_index = entry_raw.subchunk_index, 47 | .offset = entry_raw.offset, 48 | .size = entry_raw.size, 49 | .size_decompressed = entry_raw.size_decompressed, 50 | .checksum = entry_raw.checksum, 51 | }, 52 | }); 53 | } 54 | signature = header_raw.signature; 55 | return nullptr; 56 | }; 57 | switch (version.major) { 58 | case 0: 59 | case 1: 60 | return read_raw(src, HeaderV1{}, EntryV1{}); 61 | case 2: 62 | return read_raw(src, HeaderV2{}, EntryV2{}); 63 | case 3: 64 | switch (version.minor) { 65 | case 0: 66 | return read_raw(src, HeaderV3{}, EntryV3{}); 67 | case 1: 68 | case 2: 69 | case 3: 70 | return read_raw(src, HeaderV3{}, EntryV3_1{}); 71 | case 4: 72 | default: 73 | return read_raw(src, HeaderV3{}, EntryV3_4{}); 74 | } 75 | default: 76 | return "Bad Wad::Version::major."; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /cslol-tools/lib/lol/wad/toc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace lol::wad { 9 | // mixed-endian uint24_t with no alignment requirements 10 | struct UInt24ME { 11 | constexpr UInt24ME() = default; 12 | constexpr UInt24ME(uint32_t x) : hi_((uint8_t)(x >> 16)), lo_((uint8_t)x), mi_((uint8_t)(x >> 8)) {} 13 | constexpr operator uint32_t() const { return ((uint32_t)hi_ << 16) | ((uint32_t)mi_ << 8) | (uint32_t)lo_; } 14 | 15 | private: 16 | uint8_t hi_{}; 17 | uint8_t lo_{}; 18 | uint8_t mi_{}; 19 | }; 20 | 21 | struct TOC { 22 | struct Version { 23 | std::array magic; 24 | std::uint8_t major; 25 | std::uint8_t minor; 26 | 27 | static constexpr auto latest() noexcept -> Version { return {'R', 'W', 3, 4}; } 28 | 29 | inline bool is_wad() const noexcept { return magic == std::array{'R', 'W'}; } 30 | 31 | bool operator==(Version const& other) const noexcept { 32 | return magic == other.magic && major == other.major && minor == other.minor; 33 | } 34 | bool operator!=(Version const& other) const noexcept { 35 | return !(*this == other); 36 | } 37 | }; 38 | 39 | using Signature = std::array; 40 | 41 | struct HeaderV1 { 42 | Version version; 43 | static constexpr Signature signature = {}; 44 | static constexpr std::array checksum = {}; 45 | std::uint16_t desc_offset = 12; 46 | std::uint16_t desc_size = 24; 47 | std::uint32_t desc_count; 48 | }; 49 | 50 | struct HeaderV2 { 51 | Version version; 52 | static constexpr Signature signature = {}; 53 | std::array signature_unused; 54 | std::array checksum; 55 | std::uint16_t desc_offset = 104; 56 | std::uint16_t desc_size = 24; 57 | std::uint32_t desc_count; 58 | }; 59 | 60 | struct HeaderV3 { 61 | Version version; 62 | Signature signature = {}; 63 | std::array signature_unused; 64 | std::array checksum; 65 | static constexpr std::uint16_t desc_offset = 272; 66 | static constexpr std::uint16_t desc_size = 32; 67 | std::uint32_t desc_count; 68 | }; 69 | 70 | struct EntryV1 { 71 | std::uint64_t name; 72 | std::uint32_t offset; 73 | std::uint32_t size; 74 | std::uint32_t size_decompressed; 75 | EntryType type; 76 | uint8_t pad[3]; 77 | static constexpr std::uint8_t subchunk_count = {}; 78 | static constexpr bool is_duplicate = {}; 79 | static constexpr std::uint16_t subchunk_index = {}; 80 | static constexpr std::uint64_t checksum = {}; 81 | }; 82 | 83 | struct EntryV2 { 84 | std::uint64_t name; 85 | std::uint32_t offset; 86 | std::uint32_t size; 87 | std::uint32_t size_decompressed; 88 | EntryType type : 4; 89 | std::uint8_t subchunk_count : 4; 90 | std::uint8_t is_duplicate; 91 | std::uint16_t subchunk_index; 92 | static constexpr std::uint64_t checksum = {}; 93 | }; 94 | 95 | struct EntryV3 { 96 | std::uint64_t name; 97 | std::uint32_t offset; 98 | std::uint32_t size; 99 | std::uint32_t size_decompressed; 100 | EntryType type : 4; 101 | std::uint8_t subchunk_count : 4; 102 | std::uint8_t is_duplicate; 103 | std::uint16_t subchunk_index; 104 | std::uint64_t checksum_old; 105 | static constexpr std::uint64_t checksum = {}; 106 | }; 107 | 108 | struct EntryV3_1 { 109 | std::uint64_t name; 110 | std::uint32_t offset; 111 | std::uint32_t size; 112 | std::uint32_t size_decompressed; 113 | EntryType type : 4; 114 | std::uint8_t subchunk_count : 4; 115 | std::uint8_t is_duplicate; 116 | std::uint16_t subchunk_index; 117 | std::uint64_t checksum; 118 | }; 119 | 120 | struct EntryV3_4 { 121 | std::uint64_t name; 122 | std::uint32_t offset; 123 | std::uint32_t size; 124 | std::uint32_t size_decompressed; 125 | EntryType type : 4; 126 | std::uint8_t subchunk_count : 4; 127 | UInt24ME subchunk_index; 128 | std::uint64_t checksum; 129 | static constexpr bool is_duplicate = {}; 130 | }; 131 | 132 | struct Entry { 133 | hash::Xxh64 name; 134 | EntryLoc loc; 135 | }; 136 | 137 | using latest_header_t = HeaderV3; 138 | using latest_entry_t = EntryV3_4; 139 | 140 | Version version; 141 | Signature signature; 142 | std::vector entries; 143 | 144 | auto read(io::Bytes src) noexcept -> char const*; 145 | }; 146 | } 147 | -------------------------------------------------------------------------------- /cslol-tools/res/longpath.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /cslol-tools/res/utf8.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | UTF-8 7 | 8 | 9 | -------------------------------------------------------------------------------- /cslol-tools/src/main_wad_extract.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace lol; 10 | 11 | static auto wad_extract(fs::path src, fs::path dst, fs::path hashdict) -> void { 12 | lol_trace_func(lol_trace_var("{}", src), lol_trace_var("{}", dst), lol_trace_var("{}", hashdict)); 13 | lol_throw_if(src.empty()); 14 | 15 | if (dst.empty()) { 16 | dst = src; 17 | if (dst.extension().empty()) { 18 | dst.replace_extension(".wad"); 19 | } else { 20 | dst.replace_extension(); 21 | } 22 | } 23 | 24 | logi("Process dictionary"); 25 | hash::Dict dict = {}; 26 | if (hashdict.empty() || !dict.load(hashdict)) { 27 | dict.load(fs::current_path() / "hashes.game.txt"); 28 | } 29 | 30 | logi("Reading: {}", src); 31 | auto archive = wad::Archive::read_from_file(src); 32 | 33 | logi("Extracting to: {}", dst); 34 | fs::create_directories(dst); 35 | for (auto const& entry : archive.entries) { 36 | auto const name = entry.first; 37 | auto const& data = entry.second; 38 | logi("Writing: {:#016x}", name); 39 | data.write_to_dir(name, dst, &dict); 40 | } 41 | } 42 | 43 | int main(int argc, char** argv) { 44 | utility::set_binary_io(); 45 | fmtlog::setHeaderPattern("[{l}] "); 46 | fmtlog::setLogFile(stdout, false); 47 | lol::init_logging_thread(); 48 | try { 49 | fs::path exe, src, dst, hashdict; 50 | auto flags = utility::argv_parse(utility::argv_fix(argc, argv), exe, src, dst, hashdict); 51 | wad_extract(src, dst, !hashdict.empty() ? hashdict : exe.parent_path() / "hashes.game.txt"); 52 | logi("Done!"); 53 | } catch (std::exception const& error) { 54 | fmtlog::poll(true); 55 | fflush(stdout); 56 | 57 | fmt::print(stderr, "backtrace: {}\nerror: {}\n", lol::error::stack_trace(), error); 58 | fflush(stderr); 59 | 60 | if (argc <= 2) { // Backwards compatabile as interactive version 61 | fmt::print(stderr, "Press enter to exit...!\n"); 62 | getc(stdin); 63 | } 64 | return EXIT_FAILURE; 65 | } 66 | fmtlog::poll(true); 67 | fflush(stdout); 68 | return EXIT_SUCCESS; 69 | } 70 | -------------------------------------------------------------------------------- /cslol-tools/src/main_wad_make.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace lol; 9 | 10 | static auto wad_make(fs::path src, fs::path dst) -> void { 11 | lol_trace_func(lol_trace_var("{}", src), lol_trace_var("{}", dst)); 12 | lol_throw_if(src.empty()); 13 | 14 | if (dst.empty()) { 15 | if (auto filename = src.generic_string(); filename.ends_with(".wad")) { 16 | dst = filename + ".client"; 17 | } else { 18 | dst = filename + ".wad.client"; 19 | } 20 | } 21 | 22 | logi("Packing: {}", src); 23 | auto archive = wad::Archive{}; 24 | for (auto const& entry : fs::recursive_directory_iterator(src)) { 25 | if (!entry.is_regular_file()) continue; 26 | auto path = entry.path(); 27 | logi("Reading: {}", path); 28 | archive.add_from_directory(path, src); 29 | } 30 | 31 | logi("Writing: {}", dst); 32 | archive.write_to_file(dst); 33 | } 34 | 35 | int main(int argc, char** argv) { 36 | utility::set_binary_io(); 37 | fmtlog::setHeaderPattern("[{l}] "); 38 | fmtlog::setLogFile(stdout, false); 39 | lol::init_logging_thread(); 40 | try { 41 | fs::path exe, src, dst; 42 | auto flags = utility::argv_parse(utility::argv_fix(argc, argv), exe, src, dst); 43 | wad_make(src, dst); 44 | logi("Done!"); 45 | } catch (std::exception const& error) { 46 | fmtlog::poll(true); 47 | fflush(stdout); 48 | 49 | fmt::print(stderr, "backtrace: {}\nerror: {}\n", lol::error::stack_trace(), error); 50 | fflush(stderr); 51 | 52 | if (argc <= 2) { // Backwards compatabile as interactive version 53 | fmt::print(stderr, "Press enter to exit...!\n"); 54 | getc(stdin); 55 | } 56 | return EXIT_FAILURE; 57 | } 58 | fmtlog::poll(true); 59 | fflush(stdout); 60 | return EXIT_SUCCESS; 61 | } 62 | -------------------------------------------------------------------------------- /dist/FIX-ADMIN.reg: -------------------------------------------------------------------------------- 1 | Windows Registry Editor Version 5.00 2 | 3 | [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System] 4 | "EnableLUA"=dword:00000001 -------------------------------------------------------------------------------- /dist/FIX-NON-ENGLISH.reg: -------------------------------------------------------------------------------- 1 | Windows Registry Editor Version 5.00 2 | 3 | [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CodePage] 4 | "ACP"="65001" 5 | "MACCP"="65001" 6 | "OEMCP"="65001" 7 | 8 | [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem] 9 | "LongPathsEnabled"=dword:00000001 10 | -------------------------------------------------------------------------------- /dist/SOURCE.URL: -------------------------------------------------------------------------------- 1 | [InternetShortcut] 2 | URL=https://github.com/LeagueToolkit/cslol-manager 3 | IDList= 4 | HotKey=0 5 | [{000214A0-0000-0000-C000-000000000046}] 6 | Prop3=19,11 7 | -------------------------------------------------------------------------------- /dist/wad-extract-multi.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | for %%f in (%1\*.wad.client) do ( 3 | "%~dp0\wad-extract.exe" "%%f" 4 | ) 5 | pause 6 | -------------------------------------------------------------------------------- /dist/wad-make-multi.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | for /d %%f in (%1\*) do ( 3 | "%~dp0\wad-make.exe" "%%f" 4 | ) 5 | pause 6 | -------------------------------------------------------------------------------- /dist/wxy-extract-multi.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | for %%f in (%1\*.wxy) do ( 3 | "%~dp0\wxy-extract.exe" "%%f" 4 | ) 5 | pause 6 | -------------------------------------------------------------------------------- /docs/manager-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeagueToolkit/cslol-manager/401067d0604e0ef83162b42b3d1cb93434377df0/docs/manager-0.png -------------------------------------------------------------------------------- /docs/manager-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeagueToolkit/cslol-manager/401067d0604e0ef83162b42b3d1cb93434377df0/docs/manager-1.png -------------------------------------------------------------------------------- /docs/manager-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeagueToolkit/cslol-manager/401067d0604e0ef83162b42b3d1cb93434377df0/docs/manager-2.png -------------------------------------------------------------------------------- /make-release-mac.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | copy2folder() { 4 | mkdir -p "$2" 5 | 6 | cp "./dist/SOURCE.URL" "$2" 7 | cp "./LICENSE" "$2" 8 | 9 | cp -R "$1/cslol-manager.app" "$2" 10 | mkdir -p "$2/cslol-manager.app/Contents/MacOS/cslol-tools" 11 | cp "$1/cslol-tools/mod-tools" "$2/cslol-manager.app/Contents/MacOS/cslol-tools" 12 | cp "$1/cslol-tools/wad-"* "$2" 13 | } 14 | 15 | VERSION=$(git log --date=short --format="%ad-%h" -1) 16 | echo "Version: $VERSION" 17 | 18 | if [ "$#" -gt 0 ] && [ -d "$1" ]; then 19 | copy2folder "$1" "cslol-manager-macos" 20 | echo "Version: $VERSION" > "cslol-manager-macos/version.txt" 21 | else 22 | echo "Error: Provide at least one valid path." 23 | exit 24 | fi; 25 | -------------------------------------------------------------------------------- /make-release-remote.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | REMOTE=${1:-origin} 3 | SUFFIX="${2}" 4 | VERSION=$(git log --date=short --format="%ad-%h" -1)${SUFFIX} 5 | echo "Version: $VERSION" 6 | git tag $VERSION 7 | git push --tags $REMOTE $VERSION 8 | -------------------------------------------------------------------------------- /make-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | copy2folder() { 4 | mkdir -p "$2/cslol-tools" 5 | 6 | cp "./dist/"*.bat "$2/cslol-tools" 7 | cp "./dist/FIX-ADMIN.reg" "$2" 8 | cp "./dist/FIX-NON-ENGLISH.reg" "$2" 9 | cp "./dist/SOURCE.URL" "$2" 10 | cp "./LICENSE" "$2" 11 | 12 | cp "$1/cslol-tools/"*.exe "$2/cslol-tools" 13 | cp "$1/cslol-tools/"*.dll "$2/cslol-tools" 14 | cp "$1/cslol-manager.exe" "$2" 15 | echo "windeployqt is only necessary for non-static builds" 16 | windeployqt --qmldir "src/qml" "$2/cslol-manager.exe" 17 | curl -N -R -L -o "$2/cslol-tools/hashes.game.txt" "https://raw.communitydragon.org/data/hashes/lol/hashes.game.txt" 18 | } 19 | 20 | VERSION=$(git log --date=short --format="%ad-%h" -1) 21 | echo "Version: $VERSION" 22 | 23 | if [ "$#" -gt 0 ] && [ -d "$1" ]; then 24 | copy2folder "$1" "cslol-manager" 25 | echo "Version: $VERSION" > "cslol-manager/version.txt" 26 | else 27 | echo "Error: Provide at least one valid path." 28 | exit 29 | fi; 30 | -------------------------------------------------------------------------------- /src/CSLOLTools.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "CSLOLTools.h" 5 | #include "CSLOLToolsImpl.h" 6 | 7 | CSLOLTools::CSLOLTools(QObject *parent) : QObject(parent) { 8 | qRegisterMetaType("CSLOLState"); 9 | thread_ = new QThread(); 10 | worker_ = new CSLOLToolsImpl(); 11 | worker_->moveToThread(thread_); 12 | 13 | connect(worker_, &CSLOLToolsImpl::stateChanged, this, &CSLOLTools::setState); 14 | connect(worker_, &CSLOLToolsImpl::statusChanged, this, &CSLOLTools::setStatus); 15 | connect(worker_, &CSLOLToolsImpl::leaguePathChanged, this, &CSLOLTools::setLeaguePath); 16 | connect(worker_, &CSLOLToolsImpl::reportError, this, &CSLOLTools::reportError); 17 | 18 | connect(worker_, &CSLOLToolsImpl::blacklistChanged, this, &CSLOLTools::blacklistChanged); 19 | connect(worker_, &CSLOLToolsImpl::ignorebadChanged, this, &CSLOLTools::ignorebadChanged); 20 | connect(worker_, &CSLOLToolsImpl::initialized, this, &CSLOLTools::initialized); 21 | connect(worker_, &CSLOLToolsImpl::modDeleted, this, &CSLOLTools::modDeleted); 22 | connect(worker_, &CSLOLToolsImpl::installedMod, this, &CSLOLTools::installedMod); 23 | connect(worker_, &CSLOLToolsImpl::profileSaved, this, &CSLOLTools::profileSaved); 24 | connect(worker_, &CSLOLToolsImpl::profileLoaded, this, &CSLOLTools::profileLoaded); 25 | connect(worker_, &CSLOLToolsImpl::profileDeleted, this, &CSLOLTools::profileDeleted); 26 | connect(worker_, &CSLOLToolsImpl::modCreated, this, &CSLOLTools::modCreated); 27 | connect(worker_, &CSLOLToolsImpl::modEditStarted, this, &CSLOLTools::modEditStarted); 28 | connect(worker_, &CSLOLToolsImpl::modInfoChanged, this, &CSLOLTools::modInfoChanged); 29 | connect(worker_, &CSLOLToolsImpl::modWadsAdded, this, &CSLOLTools::modWadsAdded); 30 | connect(worker_, &CSLOLToolsImpl::modWadsRemoved, this, &CSLOLTools::modWadsRemoved); 31 | connect(worker_, &CSLOLToolsImpl::updatedMods, this, &CSLOLTools::updatedMods); 32 | 33 | connect(this, &CSLOLTools::changeLeaguePath, worker_, &CSLOLToolsImpl::changeLeaguePath); 34 | connect(this, &CSLOLTools::changeBlacklist, worker_, &CSLOLToolsImpl::changeBlacklist); 35 | connect(this, &CSLOLTools::changeIgnorebad, worker_, &CSLOLToolsImpl::changeIgnorebad); 36 | connect(this, &CSLOLTools::init, worker_, &CSLOLToolsImpl::init); 37 | connect(this, &CSLOLTools::deleteMod, worker_, &CSLOLToolsImpl::deleteMod); 38 | connect(this, &CSLOLTools::exportMod, worker_, &CSLOLToolsImpl::exportMod); 39 | connect(this, &CSLOLTools::installFantomeZip, worker_, &CSLOLToolsImpl::installFantomeZip); 40 | connect(this, &CSLOLTools::saveProfile, worker_, &CSLOLToolsImpl::saveProfile); 41 | connect(this, &CSLOLTools::loadProfile, worker_, &CSLOLToolsImpl::loadProfile); 42 | connect(this, &CSLOLTools::deleteProfile, worker_, &CSLOLToolsImpl::deleteProfile); 43 | connect(this, &CSLOLTools::stopProfile, worker_, &CSLOLToolsImpl::stopProfile); 44 | connect(this, &CSLOLTools::makeMod, worker_, &CSLOLToolsImpl::makeMod); 45 | connect(this, &CSLOLTools::startEditMod, worker_, &CSLOLToolsImpl::startEditMod); 46 | connect(this, &CSLOLTools::changeModInfo, worker_, &CSLOLToolsImpl::changeModInfo); 47 | connect(this, &CSLOLTools::addModWad, worker_, &CSLOLToolsImpl::addModWad); 48 | connect(this, &CSLOLTools::removeModWads, worker_, &CSLOLToolsImpl::removeModWads); 49 | connect(this, &CSLOLTools::refreshMods, worker_, &CSLOLToolsImpl::refreshMods); 50 | connect(this, &CSLOLTools::runDiag, worker_, &CSLOLToolsImpl::runDiag); 51 | 52 | connect(this, &CSLOLTools::destroyed, worker_, &CSLOLToolsImpl::deleteLater); 53 | connect(worker_, &CSLOLTools::destroyed, thread_, &QThread::deleteLater); 54 | 55 | thread_->start(); 56 | } 57 | 58 | CSLOLTools::~CSLOLTools() {} 59 | 60 | CSLOLToolsImpl::CSLOLState CSLOLTools::getState() { return state_; } 61 | 62 | QString CSLOLTools::getStatus() { return status_; } 63 | 64 | void CSLOLTools::setState(CSLOLState value) { 65 | if (state_ != value) { 66 | state_ = value; 67 | emit stateChanged(value); 68 | } 69 | } 70 | 71 | void CSLOLTools::setStatus(QString status) { 72 | if (status_ != status) { 73 | status_ = status; 74 | emit statusChanged(status); 75 | } 76 | } 77 | 78 | QString CSLOLTools::getLeaguePath() { return leaguePath_; } 79 | 80 | void CSLOLTools::setLeaguePath(QString value) { 81 | if (leaguePath_ != value) { 82 | leaguePath_ = value; 83 | emit leaguePathChanged(value); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/CSLOLTools.h: -------------------------------------------------------------------------------- 1 | #ifndef QMODMANAGER_H 2 | #define QMODMANAGER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "CSLOLToolsImpl.h" 17 | 18 | class CSLOLTools : public QObject { 19 | Q_OBJECT 20 | Q_PROPERTY(CSLOLToolsImpl::CSLOLState state READ getState NOTIFY stateChanged) 21 | Q_PROPERTY(QString status READ getStatus NOTIFY statusChanged) 22 | Q_PROPERTY(QString leaguePath READ getLeaguePath WRITE changeLeaguePath NOTIFY leaguePathChanged) 23 | public: 24 | using CSLOLState = CSLOLToolsImpl::CSLOLState; 25 | explicit CSLOLTools(QObject* parent = nullptr); 26 | ~CSLOLTools(); 27 | 28 | signals: 29 | void stateChanged(CSLOLToolsImpl::CSLOLState state); 30 | void statusChanged(QString status); 31 | void leaguePathChanged(QString leaguePath); 32 | void blacklistChanged(bool blacklist); 33 | void ignorebadChanged(bool ignorebad); 34 | 35 | void initialized(QJsonObject mods, QJsonArray profiles, QString profileName, QJsonObject profileMods); 36 | void modDeleted(QString name); 37 | void installedMod(QString fileName, QJsonObject infoData); 38 | void profileSaved(QString name, QJsonObject mods); 39 | void profileLoaded(QString name, QJsonObject profileMods); 40 | void profileDeleted(QString name); 41 | void modCreated(QString fileName, QJsonObject infoData, QString image); 42 | void modEditStarted(QString fileName, QJsonObject infoData, QString image, QJsonArray wads); 43 | void modInfoChanged(QString fileName, QJsonObject infoData, QString image); 44 | void modWadsAdded(QString modFileName, QJsonArray wads); 45 | void modWadsRemoved(QString modFileName, QJsonArray wads); 46 | void refreshed(QJsonObject mods); 47 | void updatedMods(QJsonArray mods); 48 | void reportError(QString name, QString message, QString stack_trace); 49 | 50 | void changeLeaguePath(QString newLeaguePath); 51 | void changeBlacklist(bool blacklist); 52 | void changeIgnorebad(bool ignorebad); 53 | void init(); 54 | void deleteMod(QString name); 55 | void exportMod(QString name, QString dest); 56 | void installFantomeZip(QString path); 57 | void saveProfile(QString name, QJsonObject mods, bool run, bool skipConflict, bool debugPatcher); 58 | void loadProfile(QString name); 59 | void deleteProfile(QString name); 60 | void stopProfile(); 61 | void makeMod(QString fileName, QJsonObject infoData, QString image); 62 | void startEditMod(QString fileName); 63 | void changeModInfo(QString fileName, QJsonObject infoData, QString image); 64 | void addModWad(QString modFileName, QString wad, bool removeUnknownNames); 65 | void removeModWads(QString modFileName, QJsonArray wads); 66 | void refreshMods(); 67 | void runDiag(); 68 | 69 | public slots: 70 | CSLOLToolsImpl::CSLOLState getState(); 71 | QString getStatus(); 72 | QString getLeaguePath(); 73 | 74 | private slots: 75 | void setState(CSLOLToolsImpl::CSLOLState value); 76 | void setStatus(QString status); 77 | void setLeaguePath(QString value); 78 | 79 | private: 80 | QThread* thread_ = nullptr; 81 | CSLOLToolsImpl* worker_ = nullptr; 82 | QString leaguePath_; 83 | CSLOLState state_ = CSLOLState::StateUnitialized; 84 | QString status_; 85 | }; 86 | 87 | #endif // QMODMANAGER_H 88 | -------------------------------------------------------------------------------- /src/CSLOLToolsImpl.h: -------------------------------------------------------------------------------- 1 | #ifndef QMODMANAGERWORKER_H 2 | #define QMODMANAGERWORKER_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | class CSLOLToolsImpl : public QObject { 18 | Q_OBJECT 19 | public: 20 | enum CSLOLState { 21 | StateCriticalError = -1, 22 | StateUnitialized = 0, 23 | StateIdle = 1, 24 | StateBusy = 2, 25 | StateRunning = 3, 26 | StatePatching = 4, 27 | StateStoping = 5, 28 | }; 29 | Q_ENUM(CSLOLState) 30 | 31 | explicit CSLOLToolsImpl(QObject* parent = nullptr); 32 | ~CSLOLToolsImpl() override; 33 | signals: 34 | void stateChanged(CSLOLToolsImpl::CSLOLState state); 35 | void blacklistChanged(bool blacklist); 36 | void ignorebadChanged(bool ignorebad); 37 | void statusChanged(QString message); 38 | void leaguePathChanged(QString leaguePath); 39 | 40 | void initialized(QJsonObject mods, QJsonArray profiles, QString profileName, QJsonObject profileMods); 41 | void modDeleted(QString name); 42 | void installedMod(QString fileName, QJsonObject infoData); 43 | void profileSaved(QString name, QJsonObject mods); 44 | void profileLoaded(QString name, QJsonObject profileMods); 45 | void profileDeleted(QString name); 46 | void modCreated(QString fileName, QJsonObject infoData, QString image); 47 | void modEditStarted(QString fileName, QJsonObject infoData, QString image, QJsonArray wads); 48 | void modInfoChanged(QString fileName, QJsonObject infoData, QString image); 49 | void modWadsAdded(QString modFileName, QJsonArray wads); 50 | void modWadsRemoved(QString modFileName, QJsonArray wads); 51 | void refreshed(QJsonObject mods); 52 | void updatedMods(QJsonArray mods); 53 | void reportError(QString name, QString message, QString stack_trace); 54 | 55 | public slots: 56 | void changeLeaguePath(QString newLeaguePath); 57 | void changeBlacklist(bool blacklist); 58 | void changeIgnorebad(bool blacklist); 59 | void init(); 60 | void deleteMod(QString name); 61 | void exportMod(QString name, QString dest); 62 | void installFantomeZip(QString path); 63 | void saveProfile(QString name, QJsonObject mods, bool run, bool skipConflict, bool oldPatcher); 64 | void loadProfile(QString name); 65 | void deleteProfile(QString name); 66 | void stopProfile(); 67 | void makeMod(QString fileName, QJsonObject infoData, QString image); 68 | void refreshMods(); 69 | void runDiag(); 70 | 71 | void startEditMod(QString fileName); 72 | void changeModInfo(QString fileName, QJsonObject infoData, QString image); 73 | void removeModWads(QString modFileName, QJsonArray wadNames); 74 | void addModWad(QString modFileName, QString wad, bool removeUnknownNames); 75 | 76 | CSLOLToolsImpl::CSLOLState getState(); 77 | QString getLeaguePath(); 78 | 79 | private: 80 | QNetworkAccessManager* networkManager_ = nullptr; 81 | std::vector networkResults_ = {}; 82 | std::vector networkRequests_ = {}; 83 | QLockFile* lockfile_ = nullptr; 84 | QProcess* patcherProcess_ = nullptr; 85 | QString prog_ = ""; 86 | QString game_ = ""; 87 | CSLOLToolsImpl::CSLOLState state_ = CSLOLState::StateUnitialized; 88 | bool blacklist_ = true; 89 | bool ignorebad_ = false; 90 | QString status_ = ""; 91 | QFile* logFile_ = nullptr; 92 | void setState(CSLOLToolsImpl::CSLOLState state); 93 | void setStatus(QString status); 94 | 95 | QStringList modList(); 96 | QStringList modWadsList(QString modName); 97 | QJsonObject modInfoRead(QString modName); 98 | bool modInfoWrite(QString modName, QJsonObject object); 99 | QString modImageGet(QString modName); 100 | QString modImageSet(QString modName, QString image); 101 | QStringList listProfiles(); 102 | QJsonObject readProfile(QString profileName); 103 | void writeProfile(QString profileName, QJsonObject profile); 104 | QString readCurrentProfile(); 105 | void writeCurrentProfile(QString profile); 106 | void doReportError(QString name, QString message, QString trace); 107 | 108 | void runPatcher(QStringList args); 109 | void runTool(QStringList args, std::function handle); 110 | void runDiagInternal(bool internal_once); 111 | }; 112 | 113 | #endif // QMODMANAGERWORKER_H 114 | -------------------------------------------------------------------------------- /src/CSLOLUtils.h: -------------------------------------------------------------------------------- 1 | #ifndef CSLOLUTILS_H 2 | #define CSLOLUTILS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class CSLOLUtils : public QObject { 10 | Q_OBJECT 11 | public: 12 | explicit CSLOLUtils(QObject* engine = nullptr); 13 | Q_INVOKABLE QString fromFile(QString file); 14 | Q_INVOKABLE QString toFile(QString file); 15 | Q_INVOKABLE QString checkGamePath(QString path); 16 | Q_INVOKABLE QString detectGamePath(); 17 | 18 | static QString isPlatformUnsuported(); 19 | static void relaunchAdmin(int argc, char *argv[]); 20 | private: 21 | }; 22 | 23 | #endif // CSLOLUTILS_H 24 | -------------------------------------------------------------------------------- /src/CSLOLVersion.h: -------------------------------------------------------------------------------- 1 | #ifndef CSLOLVERSION_H 2 | #define CSLOLVERSION_H 3 | 4 | namespace CSLOL { 5 | extern char const* VERSION; 6 | extern char const* COMMIT; 7 | extern char const* DATE; 8 | } 9 | 10 | #endif // CSLOLVERSION_H 11 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "CSLOLTools.h" 12 | #include "CSLOLUtils.h" 13 | #include "CSLOLVersion.h" 14 | 15 | int main(int argc, char *argv[]) { 16 | CSLOLUtils::relaunchAdmin(argc, argv); 17 | 18 | qmlRegisterType("customskinlol.tools", 1, 0, "CSLOLTools"); 19 | 20 | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 21 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 22 | #else 23 | if (QFileInfo info("opengl.txt"); info.exists()) { 24 | QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); 25 | } 26 | #endif 27 | QGuiApplication app(argc, argv); 28 | app.setOrganizationName("moonshadow565"); 29 | app.setOrganizationDomain("cslol"); 30 | app.setApplicationName("customskinlol-manager"); 31 | QDir::setCurrent(QCoreApplication::applicationDirPath()); 32 | QSettings::setPath(QSettings::Format::IniFormat, 33 | QSettings::Scope::SystemScope, 34 | QCoreApplication::applicationDirPath()); 35 | QSettings::setDefaultFormat(QSettings::Format::IniFormat); 36 | 37 | QQmlApplicationEngine engine; 38 | engine.rootContext()->setContextProperty("CSLOLUtils", new CSLOLUtils(&engine)); 39 | engine.rootContext()->setContextProperty("CSLOL_VERSION", CSLOL::VERSION); 40 | engine.rootContext()->setContextProperty("CSLOL_COMMIT", CSLOL::COMMIT); 41 | engine.rootContext()->setContextProperty("CSLOL_DATE", CSLOL::DATE); 42 | const QUrl url(QStringLiteral("qrc:/main.qml")); 43 | QFile fontfile(":/fontawesome-webfont.ttf"); 44 | fontfile.open(QFile::OpenModeFlag::ReadOnly); 45 | QFontDatabase::addApplicationFontFromData(fontfile.readAll()); 46 | QObject::connect( 47 | &engine, 48 | &QQmlApplicationEngine::objectCreated, 49 | &app, 50 | [url](QObject *obj, const QUrl &objUrl) { 51 | if (!obj && url == objUrl) QCoreApplication::exit(-1); 52 | }, 53 | Qt::QueuedConnection); 54 | engine.load(url); 55 | 56 | return app.exec(); 57 | } 58 | -------------------------------------------------------------------------------- /src/qml/CSLOLDialogEditMod.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Layouts 1.12 3 | import QtQuick.Controls 2.15 4 | 5 | Dialog { 6 | id: cslolDialogEditMod 7 | modal: true 8 | standardButtons: Dialog.Close 9 | closePolicy: Popup.NoAutoClose 10 | visible: false 11 | title: qsTr("Edit mod: ") + fileName 12 | width: parent.width * 0.9 13 | height: parent.height * 0.9 14 | x: (parent.width - width) / 2 15 | y: (parent.height - height) / 2 16 | 17 | Overlay.modal: Rectangle { 18 | color: "#aa333333" 19 | } 20 | 21 | property string fileName: "" 22 | property alias lastWadFileFolder: dialogWadFiles.folder 23 | property alias lastRawFolder: dialogRawFolder.folder 24 | property alias removeUnknownNames: checkBoxRemoveUnknownNames.checked 25 | 26 | signal changeInfoData(var infoData, string image) 27 | signal removeWads(var wads) 28 | signal addWad(var wad, bool removeUnknownNames) 29 | 30 | function infoDataChanged(infoData, image) { 31 | cslolModInfoEdit.setInfoData(infoData) 32 | cslolModInfoEdit.image = image 33 | } 34 | 35 | function wadsRemoved(wads) { 36 | for (let i in wads) { 37 | for (let c = 0; c < itemsModel.count; c++) { 38 | if (itemsModel.get(c)["Name"] === wads[i]) { 39 | itemsModel.remove(c) 40 | break 41 | } 42 | } 43 | } 44 | } 45 | 46 | function wadsAdded(wads) { 47 | for(let i in wads) { 48 | itemsModel.append({ "Name": wads[i] }) 49 | } 50 | } 51 | 52 | function load(fileName, info, image, wads, isnew) { 53 | cslolDialogEditMod.fileName = fileName 54 | cslolModInfoEdit.setInfoData(info) 55 | cslolModInfoEdit.image = image 56 | itemsModel.clear() 57 | for(let i in wads) { 58 | itemsModel.append({ "Name": wads[i] }) 59 | } 60 | if (isnew) { 61 | dialogEditModToolbar.currentIndex = 1 62 | } 63 | } 64 | 65 | ListModel { 66 | id: itemsModel 67 | } 68 | 69 | Column { 70 | id: dialogEditModLayout 71 | anchors.fill: parent 72 | spacing: 5 73 | TabBar { 74 | id: dialogEditModToolbar 75 | width: dialogEditModStackLayout.width 76 | 77 | TabButton { 78 | text: qsTr("Info") 79 | width: dialogEditModStackLayout.width / 2 80 | } 81 | TabButton { 82 | text: qsTr("Files") 83 | width: dialogEditModStackLayout.width / 2 84 | } 85 | } 86 | StackLayout { 87 | id: dialogEditModStackLayout 88 | width: parent.width 89 | height: parent.height - dialogEditModToolbar.height - 10 90 | currentIndex: dialogEditModToolbar.currentIndex 91 | // Info tab 92 | CSLOLModInfoEdit { 93 | width: parent.width 94 | height: parent.height 95 | id: cslolModInfoEdit 96 | } 97 | // Files tab 98 | ScrollView { 99 | width: parent.width 100 | height: parent.height 101 | padding: 5 102 | ListView { 103 | id: itemsView 104 | model: itemsModel 105 | flickableDirection: ListView.HorizontalAndVerticalFlick 106 | clip: true 107 | spacing: 5 108 | delegate: RowLayout{ 109 | width: itemsView.width 110 | Label { 111 | Layout.fillWidth: true 112 | text: model.Name 113 | elide: Text.ElideLeft 114 | } 115 | ToolButton { 116 | text: "\uf00d" 117 | font.family: "FontAwesome" 118 | onClicked: { 119 | let modName = model.Name 120 | cslolDialogEditMod.removeWads([modName]) 121 | } 122 | } 123 | } 124 | DropArea { 125 | id: fileDropArea 126 | anchors.fill: parent 127 | onDropped: function(drop) { 128 | if (drop.hasUrls) { 129 | cslolDialogEditMod.addWad(CSLOLUtils.fromFile(drop.urls[0]), checkBoxRemoveUnknownNames.checked) 130 | } 131 | } 132 | } 133 | } 134 | } 135 | } 136 | } 137 | 138 | footer: StackLayout { 139 | Layout.fillWidth: true 140 | currentIndex: dialogEditModToolbar.currentIndex 141 | RowLayout { 142 | Layout.fillWidth: true 143 | Item { 144 | Layout.fillWidth: true 145 | } 146 | DialogButtonBox { 147 | ToolButton { 148 | text: qsTr("Close") 149 | DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole 150 | onClicked: cslolDialogEditMod.close() 151 | } 152 | ToolButton { 153 | text: qsTr("Apply") 154 | DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole 155 | onClicked: { 156 | let infoData = cslolModInfoEdit.getInfoData(); 157 | let image = cslolModInfoEdit.image; 158 | cslolDialogEditMod.changeInfoData(infoData, image) 159 | } 160 | } 161 | } 162 | } 163 | RowLayout { 164 | Layout.fillWidth: true 165 | CheckBox { 166 | id: checkBoxRemoveUnknownNames 167 | checkable: true 168 | checked: true 169 | text: qsTr("Remove unknown") 170 | Layout.leftMargin: 5 171 | CSLOLToolTip { 172 | text: qsTr("Uncheck this if you are adding new files to game!") 173 | visible: parent.hovered 174 | } 175 | } 176 | Item { 177 | Layout.fillWidth: true 178 | } 179 | DialogButtonBox { 180 | ToolButton { 181 | text: qsTr("Close") 182 | DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole 183 | onClicked: cslolDialogEditMod.close() 184 | } 185 | ToolButton { 186 | text: qsTr("Add WAD") 187 | DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole 188 | onClicked: dialogWadFiles.open() 189 | } 190 | ToolButton { 191 | text: qsTr("Add RAW") 192 | DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole 193 | onClicked: dialogRawFolder.open() 194 | } 195 | ToolButton { 196 | text: qsTr("Browse") 197 | DialogButtonBox.buttonRole: DialogButtonBox.InvalidRole 198 | onClicked: Qt.openUrlExternally(CSLOLUtils.toFile("./installed/" + fileName)) 199 | } 200 | } 201 | } 202 | } 203 | 204 | CSLOLDialogNewModWadFiles { 205 | id: dialogWadFiles 206 | onAccepted: { 207 | cslolDialogEditMod.addWad(CSLOLUtils.fromFile(file), checkBoxRemoveUnknownNames.checked) 208 | } 209 | } 210 | 211 | CSLOLDialogNewModRAWFolder { 212 | id: dialogRawFolder 213 | onAccepted: { 214 | cslolDialogEditMod.addWad(CSLOLUtils.fromFile(folder), checkBoxRemoveUnknownNames.checked) 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/qml/CSLOLDialogError.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Layouts 1.12 3 | import QtQuick.Controls 2.15 4 | 5 | Dialog { 6 | id: cslolDialogError 7 | width: parent.width * 0.9 8 | height: parent.height * 0.9 9 | x: (parent.width - width) / 2 10 | y: (parent.height - height) / 2 11 | standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel 12 | closePolicy: Popup.NoAutoClose 13 | modal: true 14 | title: "\uf071 Error - " + name 15 | font.family: "FontAwesome" 16 | 17 | Overlay.modal: Rectangle { 18 | color: "#aa333333" 19 | } 20 | 21 | property string name: "" 22 | property string message: "" 23 | property string log_data: "" 24 | 25 | onOpened: { 26 | let scrollbar = logTextScroll.ScrollBar; 27 | scrollbar.horizontal.position = 0 28 | scrollbar.vertical.setPosition(1.0 - scrollbar.vertical.size) 29 | window.show() 30 | } 31 | 32 | ColumnLayout { 33 | id: cslolLogView 34 | spacing: 5 35 | width: parent.width 36 | height: parent.height 37 | ScrollView { 38 | Layout.fillWidth: true 39 | TextArea { 40 | id: logErrorMessageTextArea 41 | readOnly: true 42 | text: message 43 | Layout.fillWidth: true 44 | font.pointSize: 11 45 | } 46 | } 47 | ScrollView { 48 | id: logTextScroll 49 | Layout.fillWidth: true 50 | Layout.fillHeight: true 51 | TextArea { 52 | id: logTextArea 53 | readOnly: true 54 | Layout.fillWidth: true 55 | text: "```\n" + log_data + "\n```" 56 | textFormat: Text.MarkdownText 57 | font.pointSize: 10 58 | } 59 | } 60 | } 61 | 62 | footer: RowLayout { 63 | Layout.fillWidth: true 64 | Label { 65 | text: qsTr("Please use copy button to report your errors") 66 | font.italic: true 67 | Layout.fillWidth: true 68 | leftPadding: 10 69 | } 70 | DialogButtonBox { 71 | id: dialogButtonBox 72 | ToolButton { 73 | text: qsTr("Copy") 74 | onClicked: { 75 | logTextArea.selectAll() 76 | logTextArea.copy() 77 | logTextArea.deselect() 78 | } 79 | CSLOLToolTip { 80 | text: qsTr("Puts log contents in your clipboard") 81 | visible: parent.hovered 82 | } 83 | DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole 84 | } 85 | ToolButton { 86 | text: qsTr("Discard") 87 | onClicked: { 88 | cslolDialogError.close() 89 | } 90 | DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole 91 | } 92 | } 93 | } 94 | } 95 | 96 | -------------------------------------------------------------------------------- /src/qml/CSLOLDialogErrorUser.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Layouts 1.12 3 | import QtQuick.Controls 2.15 4 | 5 | Dialog { 6 | id: cslolDialogUserError 7 | width: parent.width * 0.9 8 | x: (parent.width - width) / 2 9 | y: (parent.height - height) / 2 10 | standardButtons: Dialog.Ok 11 | closePolicy: Popup.NoAutoClose 12 | modal: true 13 | title: "\uf071 Warning" 14 | font.family: "FontAwesome" 15 | Overlay.modal: Rectangle { 16 | color: "#aa333333" 17 | } 18 | onOpened: window.show() 19 | 20 | property alias text: warningTextLabel.text 21 | 22 | RowLayout { 23 | width: parent.width 24 | Label { 25 | id: warningTextLabel 26 | Layout.fillWidth: true 27 | maximumLineCount: 3 28 | text: "Oopsie!" 29 | } 30 | } 31 | onAccepted: { 32 | close() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/qml/CSLOLDialogGame.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Layouts 1.12 3 | import QtQuick.Controls 2.15 4 | 5 | Dialog { 6 | id: cslolDialogGame 7 | width: parent.width * 0.9 8 | height: parent.height * 0.9 9 | x: (parent.width - width) / 2 10 | y: (parent.height - height) / 2 11 | standardButtons: DialogButtonBox.Ok 12 | closePolicy: Popup.NoAutoClose 13 | modal: true 14 | title: "Select \"League of Legends.exe\"" 15 | font.family: "FontAwesome" 16 | Overlay.modal: Rectangle { 17 | color: "#aa333333" 18 | } 19 | 20 | signal selected(string path) 21 | 22 | Item { 23 | anchors.fill: parent 24 | Label { 25 | anchors.centerIn: parent 26 | font.pointSize: 48 27 | text: "\uf0ee" 28 | font.family: "FontAwesome" 29 | } 30 | DropArea { 31 | anchors.fill: parent 32 | onDropped: function(drop) { 33 | if (drop.hasUrls) { 34 | cslolDialogGame.selected(CSLOLUtils.fromFile(drop.urls[0])) 35 | } 36 | } 37 | } 38 | } 39 | 40 | footer: RowLayout { 41 | Layout.fillWidth: true 42 | Item { 43 | Layout.fillWidth: true 44 | } 45 | DialogButtonBox { 46 | id: dialogButtonBox 47 | ToolButton { 48 | text: qsTr("Detect") 49 | onClicked: { 50 | let detected = CSLOLUtils.detectGamePath() 51 | if (detected === "") { 52 | window.showUserError("Failed to detect game path", "Make sure LeagueClient is running to enable detect feature!") 53 | } else { 54 | cslolDialogGame.selected(detected) 55 | } 56 | } 57 | } 58 | ToolButton { 59 | text: qsTr("Browse") 60 | onClicked: cslolDialogLolPath.open() 61 | } 62 | ToolButton { 63 | text: qsTr("Close") 64 | onClicked: cslolDialogGame.close() 65 | DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole 66 | } 67 | } 68 | } 69 | 70 | CSLOLDialogLoLPath { 71 | id: cslolDialogLolPath 72 | folder: CSLOLUtils.toFile(cslolTools.leaguePath) 73 | onAccepted: { 74 | cslolDialogGame.selected(CSLOLUtils.fromFile(file)) 75 | } 76 | } 77 | } 78 | 79 | -------------------------------------------------------------------------------- /src/qml/CSLOLDialogLoLPath.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Layouts 1.12 3 | import QtQuick.Controls 2.15 4 | import Qt.labs.platform 1.0 5 | 6 | FileDialog { 7 | id: lolPathDialog 8 | visible: false 9 | title: qsTr("Select League of Legends Executable") 10 | fileMode: FileDialog.OpenFile 11 | nameFilters: "" 12 | folder: "" 13 | } 14 | -------------------------------------------------------------------------------- /src/qml/CSLOLDialogNewMod.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Layouts 1.12 3 | import QtQuick.Controls 2.15 4 | 5 | Dialog { 6 | id: cslolDialogNewMod 7 | modal: true 8 | standardButtons: Dialog.Save | Dialog.Close 9 | closePolicy: Popup.NoAutoClose 10 | visible: false 11 | title: qsTr("New mod") 12 | width: parent.width * 0.9 13 | height: parent.height * 0.9 14 | x: (parent.width - width) / 2 15 | y: (parent.height - height) / 2 16 | 17 | Overlay.modal: Rectangle { 18 | color: "#aa333333" 19 | } 20 | 21 | signal save(string fileName, var infoData, string image) 22 | 23 | function clear() { 24 | cslolModInfoEdit.clear() 25 | } 26 | 27 | onAccepted: { 28 | let infoData = cslolModInfoEdit.getInfoData() 29 | let fileName = infoData.Name + " V" + infoData.Version + " by " + infoData.Author 30 | cslolDialogNewMod.save(fileName, infoData, cslolModInfoEdit.image) 31 | } 32 | 33 | ListModel { 34 | id: itemsModel 35 | } 36 | 37 | CSLOLModInfoEdit { 38 | id: cslolModInfoEdit 39 | anchors.fill: parent 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/qml/CSLOLDialogNewModImage.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Layouts 1.12 3 | import QtQuick.Controls 2.15 4 | import Qt.labs.platform 1.0 5 | 6 | FileDialog { 7 | visible: false 8 | title: qsTr("Select image file") 9 | fileMode: FileDialog.OpenFile 10 | nameFilters: "PNG Image file (*.png)" 11 | } 12 | -------------------------------------------------------------------------------- /src/qml/CSLOLDialogNewModRAWFolder.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Layouts 1.12 3 | import QtQuick.Controls 2.15 4 | import Qt.labs.platform 1.0 5 | 6 | FolderDialog { 7 | visible: false 8 | title: qsTr("Select RAW folder") 9 | } 10 | -------------------------------------------------------------------------------- /src/qml/CSLOLDialogNewModWadFiles.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Layouts 1.12 3 | import QtQuick.Controls 2.15 4 | import Qt.labs.platform 1.0 5 | 6 | FileDialog { 7 | visible: false 8 | title: qsTr("Select .wad.client files") 9 | fileMode: FileDialog.OpenFile 10 | nameFilters: ".wad.client (*.wad.client)" 11 | } 12 | -------------------------------------------------------------------------------- /src/qml/CSLOLDialogNewProfile.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Layouts 1.12 3 | import QtQuick.Controls 2.15 4 | 5 | Dialog { 6 | id: newProfileDialog 7 | visible: false 8 | title: qsTr("New Profile Name") 9 | standardButtons: Dialog.Ok | Dialog.Cancel 10 | closePolicy: Popup.NoAutoClose 11 | modal: true 12 | width: parent.width * 0.5 13 | x: (parent.width - width) / 2 14 | y: (parent.height - height) / 2 15 | 16 | property alias text: newProfileName.text 17 | 18 | RowLayout { 19 | width: parent.width 20 | TextField { 21 | Layout.fillWidth: true 22 | id: newProfileName 23 | placeholderText: qsTr("Profile name") 24 | selectByMouse: true 25 | validator: RegularExpressionValidator { 26 | regularExpression: /[\w ]{3,50}/ 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/qml/CSLOLDialogOpenZipFantome.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Layouts 1.12 3 | import QtQuick.Controls 2.15 4 | import Qt.labs.platform 1.0 5 | 6 | FileDialog { 7 | id: cslolDialogOpenZipFantome 8 | visible: false 9 | title: qsTr("Select Mod File") 10 | fileMode: FileDialog.OpenFile 11 | nameFilters: "Fantome Mod files (*.fantome *.zip)" 12 | } 13 | -------------------------------------------------------------------------------- /src/qml/CSLOLDialogSaveZipFantome.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Layouts 1.12 3 | import QtQuick.Controls 2.15 4 | import Qt.labs.platform 1.0 5 | 6 | FileDialog { 7 | id: cslolCSLOLDialogSaveZipFantome 8 | visible: false 9 | title: qsTr("Save Fantome Mod") 10 | fileMode: FileDialog.SaveFile 11 | nameFilters: "Fantome Mod files (*.fantome *.zip)" 12 | property string modName: "mod.fantome" 13 | } 14 | -------------------------------------------------------------------------------- /src/qml/CSLOLDialogSettings.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Layouts 1.12 3 | import QtQuick.Controls 2.15 4 | 5 | 6 | Dialog { 7 | id: cslolDialogSettings 8 | modal: true 9 | standardButtons: Dialog.Close 10 | closePolicy: Popup.NoAutoClose 11 | visible: false 12 | title: qsTr("Settings ") 13 | width: parent.width * 0.9 14 | height: parent.height * 0.9 15 | x: (parent.width - width) / 2 16 | y: (parent.height - height) / 2 17 | Overlay.modal: Rectangle { 18 | color: "#aa333333" 19 | } 20 | 21 | property bool isBussy: false 22 | property alias detectGamePath: detectGamePathCheck.checked 23 | property alias blacklist: blacklistCheck.checked 24 | property alias ignorebad: ignorebadCheck.checked 25 | property alias enableUpdates: enableUpdatesCheck.checkState 26 | property alias themeDarkMode: themeDarkModeCheck.checked 27 | property alias themePrimaryColor: themePrimaryColorBox.currentIndex 28 | property alias themeAccentColor: themeAccentColorBox.currentIndex 29 | property alias suppressInstallConflicts: suppressInstallConflictsCheck.checked 30 | property alias enableSystray: enableSystrayCheck.checked 31 | property alias enableAutoRun: enableAutoRunCheck.checked 32 | property alias debugPatcher: debugPatcherCheck.checked 33 | 34 | property var colors_LIST: [ 35 | "Red", 36 | "Pink", 37 | "Purple", 38 | "DeepPurple", 39 | "Indigo", 40 | "Blue", 41 | "LightBlue", 42 | "Cyan", 43 | "Teal", 44 | "Green", 45 | "LightGreen", 46 | "Lime", 47 | "Yellow", 48 | "Amber", 49 | "Orange", 50 | "DeepOrange", 51 | "Brown", 52 | "Grey", 53 | "BlueGrey", 54 | ] 55 | 56 | 57 | signal changeGamePath() 58 | 59 | Column { 60 | id: settingsLayout 61 | anchors.fill: parent 62 | TabBar { 63 | id: settingsTabBar 64 | width: settingsStackLayout.width 65 | 66 | TabButton { 67 | text: qsTr("Game") 68 | width: settingsStackLayout.width / 3 69 | } 70 | TabButton { 71 | text: qsTr("System") 72 | width: settingsStackLayout.width / 3 73 | } 74 | TabButton { 75 | text: qsTr("Theme") 76 | width: settingsStackLayout.width / 3 77 | } 78 | } 79 | 80 | StackLayout { 81 | id: settingsStackLayout 82 | width: parent.width 83 | height: parent.height - settingsTabBar.height - 5 84 | currentIndex: settingsTabBar.currentIndex 85 | ColumnLayout { 86 | id: settingsGameTab 87 | spacing: 5 88 | Button { 89 | text: qsTr("Change Game folder") 90 | enabled: !isBussy 91 | onClicked: cslolDialogSettings.changeGamePath() 92 | Layout.fillWidth: true 93 | } 94 | Switch { 95 | id: detectGamePathCheck 96 | text: qsTr("Automatically detect game path") 97 | checked: true 98 | Layout.fillWidth: true 99 | } 100 | Switch { 101 | id: blacklistCheck 102 | text: qsTr("Blacklist extra gamemods") 103 | checked: true 104 | Layout.fillWidth: true 105 | } 106 | Switch { 107 | id: suppressInstallConflictsCheck 108 | text: qsTr("Suppress install conflicts") 109 | checked: false 110 | Layout.fillWidth: true 111 | } 112 | Switch { 113 | id: ignorebadCheck 114 | text: qsTr("Ignore faulty .wad's") 115 | checked: false 116 | Layout.fillWidth: true 117 | } 118 | } 119 | 120 | ColumnLayout { 121 | id: settingsSystemTab 122 | spacing: 5 123 | Button { 124 | text: qsTr("Logs") 125 | onClicked: Qt.openUrlExternally(CSLOLUtils.toFile("./log.txt")) 126 | Layout.fillWidth: true 127 | } 128 | CheckBox { 129 | id: enableUpdatesCheck 130 | text: qsTr("Enable updates") 131 | checkState: Qt.PartiallyChecked 132 | tristate: true 133 | Layout.fillWidth: true 134 | } 135 | Switch { 136 | id: enableSystrayCheck 137 | text: qsTr("Enable systray icon") 138 | checked: false 139 | Layout.fillWidth: true 140 | } 141 | Switch { 142 | id: enableAutoRunCheck 143 | text: qsTr("Auto Run on program start") 144 | checked: false 145 | Layout.fillWidth: true 146 | } 147 | Switch { 148 | id: debugPatcherCheck 149 | text: qsTr("Verbose logging") 150 | checked: false 151 | Layout.fillWidth: true 152 | CSLOLToolTip { 153 | text: qsTr("Helps diagnose issues comming from patcher.") 154 | visible: parent.hovered 155 | } 156 | } 157 | } 158 | ColumnLayout { 159 | id: settingsThemeTab 160 | spacing: 5 161 | RowLayout { 162 | Layout.fillWidth: true 163 | Label { 164 | text: qsTr("Primary color") 165 | } 166 | ComboBox { 167 | id: themePrimaryColorBox 168 | model: colors_LIST 169 | currentIndex: 4 170 | Layout.fillWidth: true 171 | } 172 | } 173 | RowLayout { 174 | Layout.fillWidth: true 175 | Label { 176 | text: qsTr("Accent color") 177 | } 178 | ComboBox { 179 | id: themeAccentColorBox 180 | model: colors_LIST 181 | currentIndex: 1 182 | Layout.fillWidth: true 183 | } 184 | } 185 | Switch { 186 | id: themeDarkModeCheck 187 | text: qsTr("Dark mode") 188 | checked: true 189 | Layout.fillWidth: true 190 | } 191 | } 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/qml/CSLOLDialogUpdate.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Layouts 1.12 3 | import QtQuick.Controls 2.15 4 | 5 | Dialog { 6 | id: cslolDialogUpdate 7 | width: parent.width * 0.9 8 | x: (parent.width - width) / 2 9 | y: (parent.height - height) / 2 10 | standardButtons: Dialog.Ok 11 | closePolicy: Popup.CloseOnEscape 12 | modal: true 13 | title: qsTr("Update please!") 14 | Overlay.modal: Rectangle { 15 | color: "#aa333333" 16 | } 17 | onOpened: window.show() 18 | 19 | property string update_url: "https://github.com/LeagueToolkit/cslol-manager/releases/latest" 20 | property int lastUpdateUTCMinutes: 0 21 | 22 | onAccepted: Qt.openUrlExternally(update_url) 23 | 24 | RowLayout { 25 | width: parent.width 26 | Label { 27 | text: qsTr("You will be redirected to download page after pressing OK button.\n") + update_url 28 | Layout.fillWidth: true 29 | wrapMode: Text.Wrap 30 | } 31 | } 32 | 33 | property int enableUpdates: Qt.PartiallyChecked 34 | 35 | function makeRequest(url, onDone) { 36 | let request = new XMLHttpRequest(); 37 | request.onreadystatechange = function() { 38 | if (request.readyState === XMLHttpRequest.DONE && request.status == 200) { 39 | onDone(JSON.parse(request.responseText)) 40 | } 41 | } 42 | request.open("GET", url); 43 | request.send(); 44 | } 45 | 46 | function checkForUpdates() { 47 | let cur_time = Date.now() / (1000 * 60) 48 | if (cur_time - lastUpdateUTCMinutes < 60) { 49 | return 50 | } 51 | lastUpdateUTCMinutes = cur_time 52 | let url = "https://api.github.com/repos/LeagueToolkit/cslol-manager"; 53 | makeRequest(url + "/releases", function(releases) { 54 | for (let index in releases) { 55 | let release = releases[index] 56 | switch (enableUpdates) { 57 | case Qt.Unchecked: 58 | // recieve only mandatory updates 59 | if (release["prerelease"] || !release["body"].includes("mandatory")) { 60 | continue; 61 | } 62 | break; 63 | case Qt.PartiallyChecked: 64 | // only receive non-prerelease updates 65 | if (release["prerelease"]) { 66 | continue; 67 | } 68 | break; 69 | case Qt.Checked: 70 | // allways receive the update 71 | break; 72 | } 73 | let tag_name = release["tag_name"]; 74 | console.log("Fetching release " + tag_name) 75 | makeRequest(url + "/git/ref/tags/" + tag_name, function(ref) { 76 | let commit_sha = ref["object"]["sha"]; 77 | let commit_url = ref["object"]["url"]; 78 | if (commit_sha === CSLOL_COMMIT) { 79 | return; 80 | } 81 | makeRequest(commit_url, function(commit) { 82 | let current_date = Date.parse(CSLOL_DATE) 83 | let commit_date = Date.parse(commit["committer"]["date"]); 84 | if (commit_date > current_date) { 85 | cslolDialogUpdate.open(); 86 | } 87 | }) 88 | }) 89 | break; 90 | } 91 | }) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/qml/CSLOLModInfoEdit.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Layouts 1.12 3 | import QtQuick.Controls 2.15 4 | 5 | ColumnLayout { 6 | id: cslolModInfoEdit 7 | width: parent.width 8 | property alias image: fieldImage.text 9 | 10 | function clear() { 11 | fieldName.text = "" 12 | fieldAuthor.text = "" 13 | fieldVersion.text = "" 14 | fieldDescription.text = "" 15 | fieldHome.text = "" 16 | fieldHeart.text = "" 17 | fieldImage.text = "" 18 | } 19 | 20 | function getInfoData() { 21 | if (fieldName.text === "") { 22 | window.showUserError("Edit mod", "Mod name can't be empty!") 23 | return 24 | } 25 | let name = fieldName.text == "" ? "UNKNOWN" : fieldName.text 26 | let author = fieldAuthor.text == "" ? "UNKNOWN" : fieldAuthor.text 27 | let version = fieldVersion.text == "" ? "1.0" : fieldVersion.text 28 | let description = fieldDescription.text 29 | let home = fieldHome.text 30 | let heart = fieldHeart.text 31 | let info = { 32 | "Name": name, 33 | "Author": author, 34 | "Version": version, 35 | "Description": description, 36 | "Home": home, 37 | "Heart": heart, 38 | } 39 | return info 40 | } 41 | 42 | function setInfoData(info) { 43 | fieldName.text = info["Name"] 44 | fieldAuthor.text = info["Author"] 45 | fieldVersion.text = info["Version"] 46 | fieldDescription.text = info["Description"] 47 | fieldHome.text = info["Home"] 48 | fieldHeart.text = info["Heart"] 49 | } 50 | 51 | RowLayout { 52 | Layout.fillWidth: true 53 | Label { 54 | text: qsTr("Name: ") 55 | } 56 | TextField { 57 | id: fieldName 58 | Layout.fillWidth: true 59 | placeholderText: "Name" 60 | selectByMouse: true 61 | validator: RegularExpressionValidator { 62 | regularExpression: window.validName 63 | } 64 | } 65 | } 66 | 67 | RowLayout { 68 | Layout.fillWidth: true 69 | Label { 70 | text: qsTr("Author: ") 71 | } 72 | TextField { 73 | id: fieldAuthor 74 | Layout.fillWidth: true 75 | placeholderText: "Author" 76 | selectByMouse: true 77 | validator: RegularExpressionValidator { 78 | regularExpression: window.validName 79 | } 80 | } 81 | } 82 | 83 | RowLayout { 84 | Layout.fillWidth: true 85 | Label { 86 | text: qsTr("Version: ") 87 | } 88 | TextField { 89 | id: fieldVersion 90 | Layout.fillWidth: true 91 | placeholderText: "0.0.0" 92 | selectByMouse: true 93 | validator: RegularExpressionValidator { 94 | regularExpression: window.validVersion 95 | } 96 | } 97 | } 98 | 99 | RowLayout { 100 | Layout.fillWidth: true 101 | Label { 102 | text: qsTr("Home: ") 103 | } 104 | TextField { 105 | id: fieldHome 106 | Layout.fillWidth: true 107 | placeholderText: "Home or update link for a skin" 108 | selectByMouse: true 109 | validator: RegularExpressionValidator { 110 | regularExpression: window.validUrl 111 | } 112 | } 113 | } 114 | 115 | RowLayout { 116 | Layout.fillWidth: true 117 | Label { 118 | text: qsTr("Heart: ") 119 | } 120 | TextField { 121 | id: fieldHeart 122 | Layout.fillWidth: true 123 | placeholderText: "Authors homepage link or twitter or whatever" 124 | selectByMouse: true 125 | validator: RegularExpressionValidator { 126 | regularExpression: window.validUrl 127 | } 128 | } 129 | } 130 | 131 | RowLayout { 132 | Layout.fillWidth: true 133 | Label { 134 | text: qsTr("Description: ") 135 | } 136 | TextField { 137 | id: fieldDescription 138 | placeholderText: "Description" 139 | Layout.fillWidth: true 140 | selectByMouse: true 141 | } 142 | } 143 | 144 | Image { 145 | id: imagePreview 146 | Layout.fillHeight: true 147 | Layout.fillWidth: true 148 | fillMode: Image.PreserveAspectFit 149 | source: CSLOLUtils.toFile(fieldImage.text) 150 | } 151 | 152 | RowLayout { 153 | Layout.fillWidth: true 154 | Button { 155 | text: qsTr("Select Image") 156 | onClicked: dialogImage.open() 157 | } 158 | TextField { 159 | id: fieldImage 160 | Layout.fillWidth: true 161 | placeholderText: "" 162 | readOnly: true 163 | selectByMouse: true 164 | } 165 | ToolButton { 166 | text: "\uf00d" 167 | font.family: "FontAwesome" 168 | onClicked: fieldImage.text = "" 169 | } 170 | } 171 | 172 | CSLOLDialogNewModImage { 173 | id: dialogImage 174 | onAccepted: fieldImage.text = CSLOLUtils.fromFile(file) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/qml/CSLOLStatusBar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Layouts 1.12 3 | import QtQuick.Controls 2.15 4 | 5 | ToolBar { 6 | property string statusMessage: "" 7 | 8 | RowLayout { 9 | id: row 10 | width: parent.width 11 | anchors.verticalCenter: parent.verticalCenter 12 | Label { 13 | Layout.fillWidth: true 14 | Layout.leftMargin: 5 15 | id: statusMessageLabel 16 | text: statusMessage 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/qml/CSLOLToolBar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Layouts 1.12 3 | import QtQuick.Controls 2.15 4 | 5 | ToolBar { 6 | id: cslolToolBar 7 | 8 | property bool isBussy: false 9 | property var profilesModel: [] 10 | property alias profilesCurrentIndex: profilesComboBox.currentIndex 11 | property alias profilesCurrentName: profilesComboBox.currentText 12 | property alias menuButtonHeight: mainMenuButton.height 13 | 14 | signal openSideMenu() 15 | signal saveProfileAndRun(bool run) 16 | signal stopProfile() 17 | signal loadProfile() 18 | signal newProfile() 19 | signal removeProfile() 20 | signal runDiag() 21 | signal openLogs() 22 | 23 | RowLayout { 24 | id: toolbarRow 25 | width: parent.width 26 | anchors.verticalCenter: parent.verticalCenter 27 | ToolButton { 28 | id: mainMenuButton 29 | text: "\uf013" 30 | font.family: "FontAwesome" 31 | onClicked: cslolToolBar.openSideMenu() 32 | CSLOLToolTip { 33 | text: qsTr("Open settings dialog") 34 | visible: parent.hovered 35 | } 36 | } 37 | ComboBox { 38 | id: profilesComboBox 39 | Layout.fillWidth: true 40 | currentIndex: 0 41 | enabled: !isBussy 42 | model: cslolToolBar.profilesModel 43 | CSLOLToolTip { 44 | text: qsTr("Select a profile to save, load or remove") 45 | visible: parent.hovered 46 | } 47 | } 48 | ToolButton { 49 | id: heartButton 50 | text: "\uf004" 51 | font.family: "FontAwesome" 52 | onClicked: { 53 | Qt.openUrlExternally("https://github.com/LeagueToolkit/cslol-manager/graphs/contributors") 54 | } 55 | } 56 | ToolButton { 57 | id: diagButton 58 | text: "\uF193" 59 | font.family: "FontAwesome" 60 | onClicked: cslolToolBar.runDiag() 61 | CSLOLToolTip { 62 | text: qsTr("Start troubleshoot tool") 63 | visible: parent.hovered 64 | } 65 | } 66 | ToolButton { 67 | id: logButton 68 | text: "\uf188" 69 | font.family: "FontAwesome" 70 | onClicked: cslolToolBar.openLogs() 71 | CSLOLToolTip { 72 | text: qsTr("Open log file") 73 | visible: parent.hovered 74 | } 75 | } 76 | ToolButton { 77 | text: "\uf0c7" 78 | font.family: "FontAwesome" 79 | onClicked: cslolToolBar.saveProfileAndRun(false) 80 | enabled: !isBussy 81 | CSLOLToolTip { 82 | text: qsTr("Save enabled mods for selected profile") 83 | visible: parent.hovered 84 | } 85 | } 86 | ToolButton { 87 | text: "\uf07c" 88 | font.family: "FontAwesome" 89 | onClicked: cslolToolBar.loadProfile() 90 | enabled: !isBussy 91 | CSLOLToolTip { 92 | text: qsTr("Load enabled mods for selected profile") 93 | visible: parent.hovered 94 | } 95 | } 96 | ToolButton { 97 | text: "\uf1f8" 98 | font.family: "FontAwesome" 99 | onClicked: cslolToolBar.removeProfile() 100 | enabled: !isBussy 101 | CSLOLToolTip { 102 | text: qsTr("Delete selected profile") 103 | visible: parent.hovered 104 | } 105 | } 106 | ToolButton { 107 | text: "\uf067" 108 | font.family: "FontAwesome" 109 | onClicked: cslolToolBar.newProfile() 110 | enabled: !isBussy 111 | CSLOLToolTip { 112 | text: qsTr("Create new profile") 113 | visible: parent.hovered 114 | } 115 | } 116 | ToolButton { 117 | text: window.patcherRunning ? "\uf04d" : "\uf04b" 118 | font.family: "FontAwesome" 119 | onClicked: { 120 | if (window.patcherRunning) { 121 | cslolToolBar.stopProfile() 122 | } else { 123 | cslolToolBar.saveProfileAndRun(true) 124 | } 125 | } 126 | enabled: !isBussy || window.patcherRunning 127 | CSLOLToolTip { 128 | text: qsTr("Runs patcher for currently selected mods") 129 | visible: parent.hovered 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/qml/CSLOLToolTip.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Controls 2.15 3 | 4 | ToolTip { 5 | visible: parent.hovered 6 | delay: 400 7 | } 8 | -------------------------------------------------------------------------------- /src/qml/PageMods.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Controls 2.5 3 | import QtQuick.Layouts 1.12 4 | 5 | Page { 6 | width: 600 7 | height: 400 8 | title: qsTr("Mods") 9 | 10 | ColumnLayout { 11 | anchors.fill: parent 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/qml/PageSettings.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Controls 2.5 3 | 4 | Page { 5 | width: 600 6 | height: 400 7 | title: qsTr("Settings") 8 | 9 | Label { 10 | text: qsTr("You are on the settings page.") 11 | anchors.centerIn: parent 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/qml/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeagueToolkit/cslol-manager/401067d0604e0ef83162b42b3d1cb93434377df0/src/qml/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/qml/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeagueToolkit/cslol-manager/401067d0604e0ef83162b42b3d1cb93434377df0/src/qml/icon.png -------------------------------------------------------------------------------- /src/qml/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | CSLOLToolBar.qml 5 | CSLOLDialogNewProfile.qml 6 | CSLOLDialogLoLPath.qml 7 | CSLOLModsView.qml 8 | CSLOLStatusBar.qml 9 | CSLOLDialogOpenZipFantome.qml 10 | CSLOLDialogError.qml 11 | CSLOLDialogErrorUser.qml 12 | CSLOLDialogNewMod.qml 13 | CSLOLDialogNewModImage.qml 14 | CSLOLDialogNewModWadFiles.qml 15 | CSLOLDialogNewModRAWFolder.qml 16 | CSLOLDialogSaveZipFantome.qml 17 | CSLOLDialogEditMod.qml 18 | icon.png 19 | CSLOLDialogUpdate.qml 20 | qtquickcontrols2.conf 21 | fontawesome-webfont.ttf 22 | CSLOLDialogSettings.qml 23 | CSLOLModInfoEdit.qml 24 | CSLOLDialogGame.qml 25 | CSLOLToolTip.qml 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/qml/qtquickcontrols2.conf: -------------------------------------------------------------------------------- 1 | [Controls] 2 | Style=Material 3 | -------------------------------------------------------------------------------- /src/res/MacOSXBundleInfo.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${MACOSX_BUNDLE_EXECUTABLE_NAME} 9 | CFBundleGetInfoString 10 | ${MACOSX_BUNDLE_INFO_STRING} 11 | CFBundleIconFile 12 | ${MACOSX_BUNDLE_ICON_FILE} 13 | CFBundleIdentifier 14 | ${MACOSX_BUNDLE_GUI_IDENTIFIER} 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleLongVersionString 18 | ${MACOSX_BUNDLE_LONG_VERSION_STRING} 19 | CFBundleName 20 | ${MACOSX_BUNDLE_BUNDLE_NAME} 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | ${MACOSX_BUNDLE_SHORT_VERSION_STRING} 25 | CFBundleSignature 26 | ???? 27 | CFBundleVersion 28 | ${MACOSX_BUNDLE_BUNDLE_VERSION} 29 | CSResourcesFileMapped 30 | 31 | NSHumanReadableCopyright 32 | ${MACOSX_BUNDLE_COPYRIGHT} 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/res/cslol-manager.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/res/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeagueToolkit/cslol-manager/401067d0604e0ef83162b42b3d1cb93434377df0/src/res/icon.icns -------------------------------------------------------------------------------- /src/res/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeagueToolkit/cslol-manager/401067d0604e0ef83162b42b3d1cb93434377df0/src/res/icon.ico -------------------------------------------------------------------------------- /src/res/rc.rc: -------------------------------------------------------------------------------- 1 | IDI_ICON1 ICON DISCARDABLE "icon.ico" 2 | --------------------------------------------------------------------------------