mirror of
https://github.com/alterware/aw-installer.git
synced 2025-04-19 08:02:53 +00:00
init
This commit is contained in:
commit
095e3e8b42
7
.github/dependabot.yml
vendored
Normal file
7
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: gitsubmodule
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
open-pull-requests-limit: 10
|
162
.github/workflows/build.yml
vendored
Normal file
162
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "*"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "*"
|
||||||
|
types: [opened, synchronize, reopened]
|
||||||
|
|
||||||
|
env:
|
||||||
|
PREMAKE_VERSION: "5.0.0-beta2"
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-win:
|
||||||
|
name: Build Windows
|
||||||
|
runs-on: windows-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
configuration:
|
||||||
|
- debug
|
||||||
|
- release
|
||||||
|
arch:
|
||||||
|
- x64
|
||||||
|
include:
|
||||||
|
- arch: x64
|
||||||
|
platform: x64
|
||||||
|
steps:
|
||||||
|
- name: Check out files
|
||||||
|
uses: actions/checkout@main
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
fetch-depth: 0
|
||||||
|
# NOTE - If LFS ever starts getting used during builds, switch this to true!
|
||||||
|
lfs: false
|
||||||
|
|
||||||
|
- name: Add msbuild to PATH
|
||||||
|
uses: microsoft/setup-msbuild@main
|
||||||
|
|
||||||
|
- name: Install Premake5
|
||||||
|
uses: abel0b/setup-premake@v2.3
|
||||||
|
with:
|
||||||
|
version: ${{ env.PREMAKE_VERSION }}
|
||||||
|
|
||||||
|
- name: Generate project files
|
||||||
|
run: premake5 vs2022
|
||||||
|
|
||||||
|
- name: Set up problem matching
|
||||||
|
uses: ammaraskar/msvc-problem-matcher@master
|
||||||
|
|
||||||
|
- name: Build ${{matrix.arch}} ${{matrix.configuration}} binaries
|
||||||
|
run: msbuild /m /v:minimal /p:Configuration=${{matrix.configuration}} /p:Platform=${{matrix.platform}} build/aw-installer.sln
|
||||||
|
|
||||||
|
- name: Upload ${{matrix.arch}} ${{matrix.configuration}} binaries
|
||||||
|
uses: actions/upload-artifact@main
|
||||||
|
with:
|
||||||
|
name: windows-${{matrix.arch}}-${{matrix.configuration}}
|
||||||
|
path: |
|
||||||
|
build/bin/${{matrix.arch}}/${{matrix.configuration}}/aw-installer.exe
|
||||||
|
build/bin/${{matrix.arch}}/${{matrix.configuration}}/aw-installer.pdb
|
||||||
|
|
||||||
|
build-linux:
|
||||||
|
name: Build Linux
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
configuration:
|
||||||
|
- debug
|
||||||
|
- release
|
||||||
|
arch:
|
||||||
|
- x64
|
||||||
|
steps:
|
||||||
|
- name: Check out files
|
||||||
|
uses: actions/checkout@main
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
fetch-depth: 0
|
||||||
|
# NOTE - If LFS ever starts getting used during builds, switch this to true!
|
||||||
|
lfs: false
|
||||||
|
|
||||||
|
- name: Install dependencies (x64)
|
||||||
|
if: matrix.arch == 'x64'
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install libcurl4-gnutls-dev -y
|
||||||
|
|
||||||
|
- name: Install Premake5
|
||||||
|
uses: abel0b/setup-premake@v2.3
|
||||||
|
with:
|
||||||
|
version: ${{ env.PREMAKE_VERSION }}
|
||||||
|
|
||||||
|
- name: Generate project files
|
||||||
|
run: premake5 --cc=clang gmake2
|
||||||
|
|
||||||
|
- name: Set up problem matching
|
||||||
|
uses: ammaraskar/gcc-problem-matcher@master
|
||||||
|
|
||||||
|
- name: Build ${{matrix.arch}} ${{matrix.configuration}} binaries
|
||||||
|
run: |
|
||||||
|
pushd build
|
||||||
|
make config=${{matrix.configuration}}_${{matrix.arch}} -j$(nproc)
|
||||||
|
env:
|
||||||
|
CC: clang
|
||||||
|
CXX: clang++
|
||||||
|
|
||||||
|
- name: Upload ${{matrix.arch}} ${{matrix.configuration}} binaries
|
||||||
|
uses: actions/upload-artifact@main
|
||||||
|
with:
|
||||||
|
name: linux-${{matrix.arch}}-${{matrix.configuration}}
|
||||||
|
path: |
|
||||||
|
build/bin/${{matrix.arch}}/${{matrix.configuration}}/aw-installer
|
||||||
|
|
||||||
|
build-macos:
|
||||||
|
name: Build macOS
|
||||||
|
runs-on: macos-13
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
configuration:
|
||||||
|
- debug
|
||||||
|
- release
|
||||||
|
arch:
|
||||||
|
- x64
|
||||||
|
- arm64
|
||||||
|
steps:
|
||||||
|
- name: Check out files
|
||||||
|
uses: actions/checkout@main
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
fetch-depth: 0
|
||||||
|
# NOTE - If LFS ever starts getting used during builds, switch this to true!
|
||||||
|
lfs: false
|
||||||
|
|
||||||
|
- name: Install Premake5
|
||||||
|
uses: abel0b/setup-premake@v2.3
|
||||||
|
with:
|
||||||
|
version: ${{ env.PREMAKE_VERSION }}
|
||||||
|
|
||||||
|
- name: Generate project files
|
||||||
|
run: premake5 gmake2
|
||||||
|
|
||||||
|
- name: Set up problem matching
|
||||||
|
uses: ammaraskar/gcc-problem-matcher@master
|
||||||
|
|
||||||
|
- name: Build ${{matrix.arch}} ${{matrix.configuration}} binaries
|
||||||
|
run: |
|
||||||
|
pushd build
|
||||||
|
make config=${{matrix.configuration}}_${{matrix.arch}} -j$(sysctl -n hw.logicalcpu)
|
||||||
|
|
||||||
|
- name: Upload ${{matrix.arch}} ${{matrix.configuration}} binaries
|
||||||
|
uses: actions/upload-artifact@v3.1.3
|
||||||
|
with:
|
||||||
|
name: macos-${{matrix.arch}}-${{matrix.configuration}}
|
||||||
|
path: |
|
||||||
|
build/bin/${{matrix.arch}}/${{matrix.configuration}}/aw-installer
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Build results
|
||||||
|
build
|
13
.gitmodules
vendored
Normal file
13
.gitmodules
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[submodule "deps/GSL"]
|
||||||
|
path = deps/GSL
|
||||||
|
url = https://github.com/microsoft/GSL.git
|
||||||
|
[submodule "deps/curl"]
|
||||||
|
path = deps/curl
|
||||||
|
url = https://github.com/curl/curl.git
|
||||||
|
branch = curl-8_5_0
|
||||||
|
[submodule "deps/rapidjson"]
|
||||||
|
path = deps/rapidjson
|
||||||
|
url = https://github.com/Tencent/rapidjson.git
|
||||||
|
[submodule "deps/zlib"]
|
||||||
|
path = deps/zlib
|
||||||
|
url = https://github.com/madler/zlib.git
|
29
LICENSE
Normal file
29
LICENSE
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2024, AlterWare
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
24
README.md
Normal file
24
README.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
[](https://github.com/alterware/aw-installer/actions)
|
||||||
|
|
||||||
|
|
||||||
|
# AlterWare: Installer
|
||||||
|
This is the tool we use to pull changes made from the release page of some of our clients and install it where we need to.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
- Install [Premake5](premake5-link) and add it to your system PATH
|
||||||
|
- Clone this repository using [Git][git-link]
|
||||||
|
- Update the submodules using ``git submodule update --init --recursive``
|
||||||
|
- Run Premake with either of these two options ``premake5 vs2022`` (Windows) or ``premake5 gmake2`` (Linux/macOS)
|
||||||
|
|
||||||
|
**IMPORTANT**
|
||||||
|
Requirements for Unix systems:
|
||||||
|
- Compilation: Please use Clang as the preferred compiler
|
||||||
|
- Dependencies: Ensure the LLVM C++ Standard library is installed
|
||||||
|
- Alternative compilers: If you opt for a different compiler such as GCC, use the [Mold][mold-link] linker
|
||||||
|
- Customization: Modifications to the Premake5.lua script may be required
|
||||||
|
- Platform support: Details regarding supported platforms are available in [build.yml][build-link]
|
||||||
|
|
||||||
|
[premake5-link]: https://premake.github.io
|
||||||
|
[git-link]: https://git-scm.com
|
||||||
|
[mold-link]: https://github.com/rui314/mold
|
||||||
|
[build-link]: https://github.com/alterware/master-server/blob/master/.github/workflows/build.yml
|
1
deps/GSL
vendored
Submodule
1
deps/GSL
vendored
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit e64c97fc2cfc11992098bb38eda932de275e3f4d
|
1
deps/curl
vendored
Submodule
1
deps/curl
vendored
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 7161cb17c01dcff1dc5bf89a18437d9d729f1ecd
|
75
deps/premake/curl.lua
vendored
Normal file
75
deps/premake/curl.lua
vendored
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
curl = {
|
||||||
|
source = path.join(dependencies.basePath, "curl"),
|
||||||
|
}
|
||||||
|
|
||||||
|
function curl.import()
|
||||||
|
links { "curl" }
|
||||||
|
|
||||||
|
filter "toolset:msc*"
|
||||||
|
links { "Crypt32.lib" }
|
||||||
|
filter {}
|
||||||
|
|
||||||
|
curl.includes()
|
||||||
|
end
|
||||||
|
|
||||||
|
function curl.includes()
|
||||||
|
filter "toolset:msc*"
|
||||||
|
includedirs {
|
||||||
|
path.join(curl.source, "include"),
|
||||||
|
}
|
||||||
|
|
||||||
|
defines {
|
||||||
|
"CURL_STRICTER",
|
||||||
|
"CURL_STATICLIB",
|
||||||
|
"CURL_DISABLE_LDAP",
|
||||||
|
}
|
||||||
|
filter {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function curl.project()
|
||||||
|
if not os.istarget("windows") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
project "curl"
|
||||||
|
language "C"
|
||||||
|
|
||||||
|
curl.includes()
|
||||||
|
|
||||||
|
includedirs {
|
||||||
|
path.join(curl.source, "lib"),
|
||||||
|
}
|
||||||
|
|
||||||
|
files {
|
||||||
|
path.join(curl.source, "lib/**.c"),
|
||||||
|
path.join(curl.source, "lib/**.h"),
|
||||||
|
}
|
||||||
|
|
||||||
|
defines {
|
||||||
|
"BUILDING_LIBCURL",
|
||||||
|
}
|
||||||
|
|
||||||
|
filter "toolset:msc*"
|
||||||
|
|
||||||
|
defines {
|
||||||
|
"USE_SCHANNEL",
|
||||||
|
"USE_WINDOWS_SSPI",
|
||||||
|
"USE_THREADS_WIN32",
|
||||||
|
}
|
||||||
|
|
||||||
|
filter {}
|
||||||
|
|
||||||
|
filter "toolset:not msc*"
|
||||||
|
|
||||||
|
defines {
|
||||||
|
"USE_GNUTLS",
|
||||||
|
"USE_THREADS_POSIX",
|
||||||
|
}
|
||||||
|
|
||||||
|
filter {}
|
||||||
|
|
||||||
|
warnings "Off"
|
||||||
|
kind "StaticLib"
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(dependencies, curl)
|
19
deps/premake/gsl.lua
vendored
Normal file
19
deps/premake/gsl.lua
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
gsl = {
|
||||||
|
source = path.join(dependencies.basePath, "GSL"),
|
||||||
|
}
|
||||||
|
|
||||||
|
function gsl.import()
|
||||||
|
gsl.includes()
|
||||||
|
end
|
||||||
|
|
||||||
|
function gsl.includes()
|
||||||
|
includedirs {
|
||||||
|
path.join(gsl.source, "include")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function gsl.project()
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(dependencies, gsl)
|
50
deps/premake/minizip.lua
vendored
Normal file
50
deps/premake/minizip.lua
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
minizip = {
|
||||||
|
source = path.join(dependencies.basePath, "zlib/contrib/minizip"),
|
||||||
|
}
|
||||||
|
|
||||||
|
function minizip.import()
|
||||||
|
links { "minizip" }
|
||||||
|
zlib.import()
|
||||||
|
minizip.includes()
|
||||||
|
end
|
||||||
|
|
||||||
|
function minizip.includes()
|
||||||
|
includedirs {
|
||||||
|
minizip.source
|
||||||
|
}
|
||||||
|
|
||||||
|
zlib.includes()
|
||||||
|
end
|
||||||
|
|
||||||
|
function minizip.project()
|
||||||
|
project "minizip"
|
||||||
|
language "C"
|
||||||
|
cdialect "C89"
|
||||||
|
|
||||||
|
minizip.includes()
|
||||||
|
|
||||||
|
files {
|
||||||
|
path.join(minizip.source, "*.h"),
|
||||||
|
path.join(minizip.source, "*.c"),
|
||||||
|
}
|
||||||
|
|
||||||
|
filter "system:not windows"
|
||||||
|
removefiles {
|
||||||
|
path.join(minizip.source, "iowin32.c"),
|
||||||
|
}
|
||||||
|
filter {}
|
||||||
|
|
||||||
|
removefiles {
|
||||||
|
path.join(minizip.source, "miniunz.c"),
|
||||||
|
path.join(minizip.source, "minizip.c"),
|
||||||
|
}
|
||||||
|
|
||||||
|
filter { "system:windows" }
|
||||||
|
defines "_CRT_SECURE_NO_DEPRECATE"
|
||||||
|
filter {}
|
||||||
|
|
||||||
|
warnings "Off"
|
||||||
|
kind "StaticLib"
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(dependencies, minizip)
|
20
deps/premake/rapidjson.lua
vendored
Normal file
20
deps/premake/rapidjson.lua
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
rapidjson = {
|
||||||
|
source = path.join(dependencies.basePath, "rapidjson"),
|
||||||
|
}
|
||||||
|
|
||||||
|
function rapidjson.import()
|
||||||
|
defines{"RAPIDJSON_HAS_STDSTRING"}
|
||||||
|
rapidjson.includes()
|
||||||
|
end
|
||||||
|
|
||||||
|
function rapidjson.includes()
|
||||||
|
includedirs {
|
||||||
|
path.join(rapidjson.source, "include"),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function rapidjson.project()
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(dependencies, rapidjson)
|
40
deps/premake/zlib.lua
vendored
Normal file
40
deps/premake/zlib.lua
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
zlib = {
|
||||||
|
source = path.join(dependencies.basePath, "zlib"),
|
||||||
|
}
|
||||||
|
|
||||||
|
function zlib.import()
|
||||||
|
links { "zlib" }
|
||||||
|
zlib.includes()
|
||||||
|
end
|
||||||
|
|
||||||
|
function zlib.includes()
|
||||||
|
includedirs {
|
||||||
|
zlib.source
|
||||||
|
}
|
||||||
|
|
||||||
|
defines {
|
||||||
|
"ZLIB_CONST",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function zlib.project()
|
||||||
|
project "zlib"
|
||||||
|
language "C"
|
||||||
|
cdialect "C89"
|
||||||
|
|
||||||
|
zlib.includes()
|
||||||
|
|
||||||
|
files {
|
||||||
|
path.join(zlib.source, "*.h"),
|
||||||
|
path.join(zlib.source, "*.c"),
|
||||||
|
}
|
||||||
|
|
||||||
|
filter { "system:windows" }
|
||||||
|
defines "_CRT_SECURE_NO_DEPRECATE"
|
||||||
|
filter {}
|
||||||
|
|
||||||
|
warnings "Off"
|
||||||
|
kind "StaticLib"
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(dependencies, zlib)
|
1
deps/rapidjson
vendored
Submodule
1
deps/rapidjson
vendored
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 6089180ecb704cb2b136777798fa1be303618975
|
1
deps/zlib
vendored
Submodule
1
deps/zlib
vendored
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 643e17b7498d12ab8d15565662880579692f769d
|
142
premake5.lua
Normal file
142
premake5.lua
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
dependencies = {
|
||||||
|
basePath = "./deps"
|
||||||
|
}
|
||||||
|
|
||||||
|
function dependencies.load()
|
||||||
|
dir = path.join(dependencies.basePath, "premake/*.lua")
|
||||||
|
deps = os.matchfiles(dir)
|
||||||
|
|
||||||
|
for i, dep in pairs(deps) do
|
||||||
|
dep = dep:gsub(".lua", "")
|
||||||
|
require(dep)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function dependencies.imports()
|
||||||
|
for i, proj in pairs(dependencies) do
|
||||||
|
if type(i) == 'number' then
|
||||||
|
proj.import()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function dependencies.projects()
|
||||||
|
for i, proj in pairs(dependencies) do
|
||||||
|
if type(i) == 'number' then
|
||||||
|
proj.project()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
dependencies.load()
|
||||||
|
|
||||||
|
workspace "aw-installer"
|
||||||
|
startproject "aw-installer"
|
||||||
|
location "./build"
|
||||||
|
objdir "%{wks.location}/obj"
|
||||||
|
targetdir "%{wks.location}/bin/%{cfg.platform}/%{cfg.buildcfg}"
|
||||||
|
|
||||||
|
configurations {"debug", "release"}
|
||||||
|
|
||||||
|
language "C++"
|
||||||
|
cppdialect "C++20"
|
||||||
|
|
||||||
|
if os.istarget("darwin") then
|
||||||
|
platforms {"x64", "arm64"}
|
||||||
|
else
|
||||||
|
platforms {"x86", "x64"}
|
||||||
|
end
|
||||||
|
|
||||||
|
filter "platforms:x86"
|
||||||
|
architecture "x86"
|
||||||
|
filter {}
|
||||||
|
|
||||||
|
filter "platforms:x64"
|
||||||
|
architecture "x86_64"
|
||||||
|
filter {}
|
||||||
|
|
||||||
|
filter "platforms:arm64"
|
||||||
|
architecture "ARM64"
|
||||||
|
filter {}
|
||||||
|
|
||||||
|
symbols "On"
|
||||||
|
staticruntime "On"
|
||||||
|
editandcontinue "Off"
|
||||||
|
warnings "Extra"
|
||||||
|
characterset "ASCII"
|
||||||
|
|
||||||
|
filter { "system:linux", "system:macosx" }
|
||||||
|
buildoptions "-pthread"
|
||||||
|
linkoptions "-pthread"
|
||||||
|
filter {}
|
||||||
|
|
||||||
|
if os.istarget("linux") then
|
||||||
|
filter { "toolset:clang*" }
|
||||||
|
buildoptions "-stdlib=libc++"
|
||||||
|
linkoptions "-stdlib=libc++"
|
||||||
|
|
||||||
|
-- always try to use lld. LD or Gold will not work
|
||||||
|
linkoptions "-fuse-ld=lld"
|
||||||
|
filter {}
|
||||||
|
end
|
||||||
|
|
||||||
|
filter { "system:macosx", "platforms:arm64" }
|
||||||
|
buildoptions "-arch arm64"
|
||||||
|
linkoptions "-arch arm64"
|
||||||
|
filter {}
|
||||||
|
|
||||||
|
if _OPTIONS["dev-build"] then
|
||||||
|
defines {"DEV_BUILD"}
|
||||||
|
end
|
||||||
|
|
||||||
|
if os.getenv("CI") then
|
||||||
|
defines "CI"
|
||||||
|
end
|
||||||
|
|
||||||
|
flags {"NoIncrementalLink", "NoMinimalRebuild", "MultiProcessorCompile", "No64BitChecks"}
|
||||||
|
|
||||||
|
filter "configurations:Release"
|
||||||
|
optimize "Size"
|
||||||
|
defines "NDEBUG"
|
||||||
|
flags "FatalCompileWarnings"
|
||||||
|
filter {}
|
||||||
|
|
||||||
|
filter "configurations:Debug"
|
||||||
|
optimize "Debug"
|
||||||
|
defines {"DEBUG", "_DEBUG"}
|
||||||
|
filter {}
|
||||||
|
|
||||||
|
project "aw-installer"
|
||||||
|
kind "ConsoleApp"
|
||||||
|
language "C++"
|
||||||
|
|
||||||
|
pchheader "std_include.hpp"
|
||||||
|
pchsource "src/std_include.cpp"
|
||||||
|
|
||||||
|
files {"./src/**.rc", "./src/**.hpp", "./src/**.cpp"}
|
||||||
|
|
||||||
|
includedirs {"./src", "%{prj.location}/src"}
|
||||||
|
|
||||||
|
filter "system:windows"
|
||||||
|
files {
|
||||||
|
"./src/**.rc",
|
||||||
|
}
|
||||||
|
filter {}
|
||||||
|
|
||||||
|
filter { "system:windows", "toolset:not msc*" }
|
||||||
|
resincludedirs {
|
||||||
|
"%{_MAIN_SCRIPT_DIR}/src"
|
||||||
|
}
|
||||||
|
filter {}
|
||||||
|
|
||||||
|
filter { "system:windows", "toolset:msc*" }
|
||||||
|
resincludedirs {
|
||||||
|
"$(ProjectDir)src"
|
||||||
|
}
|
||||||
|
filter {}
|
||||||
|
|
||||||
|
dependencies.imports()
|
||||||
|
|
||||||
|
|
||||||
|
group "Dependencies"
|
||||||
|
dependencies.projects()
|
248
src/console.cpp
Normal file
248
src/console.cpp
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
#include "std_include.hpp"
|
||||||
|
#include "console.hpp"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define COLOR_LOG_INFO 11//15
|
||||||
|
#define COLOR_LOG_WARN 14
|
||||||
|
#define COLOR_LOG_ERROR 12
|
||||||
|
#define COLOR_LOG_DEBUG 15//7
|
||||||
|
#else
|
||||||
|
#define COLOR_LOG_INFO "\033[0;36m"
|
||||||
|
#define COLOR_LOG_WARN "\033[0;33m"
|
||||||
|
#define COLOR_LOG_ERROR "\033[0;31m"
|
||||||
|
#define COLOR_LOG_DEBUG "\033[0m"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace console
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::mutex signal_mutex;
|
||||||
|
std::function<void()> signal_callback;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define COLOR(win, posix) win
|
||||||
|
using color_type = WORD;
|
||||||
|
#else
|
||||||
|
#define COLOR(win, posix) posix
|
||||||
|
using color_type = const char*;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const color_type color_array[] =
|
||||||
|
{
|
||||||
|
COLOR(0x8, "\033[0;90m"), // 0 - black
|
||||||
|
COLOR(0xC, "\033[0;91m"), // 1 - red
|
||||||
|
COLOR(0xA, "\033[0;92m"), // 2 - green
|
||||||
|
COLOR(0xE, "\033[0;93m"), // 3 - yellow
|
||||||
|
COLOR(0x9, "\033[0;94m"), // 4 - blue
|
||||||
|
COLOR(0xB, "\033[0;96m"), // 5 - cyan
|
||||||
|
COLOR(0xD, "\033[0;95m"), // 6 - pink
|
||||||
|
COLOR(0xF, "\033[0;97m"), // 7 - white
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
BOOL WINAPI handler(const DWORD signal)
|
||||||
|
{
|
||||||
|
if (signal == CTRL_C_EVENT && signal_callback)
|
||||||
|
{
|
||||||
|
signal_callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
void handler(int signal)
|
||||||
|
{
|
||||||
|
if (signal == SIGINT && signal_callback)
|
||||||
|
{
|
||||||
|
signal_callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::string format(va_list* ap, const char* message)
|
||||||
|
{
|
||||||
|
static thread_local char buffer[0x1000];
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
const int count = vsnprintf_s(buffer, _TRUNCATE, message, *ap);
|
||||||
|
#else
|
||||||
|
const int count = vsnprintf(buffer, sizeof(buffer), message, *ap);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (count < 0) return {};
|
||||||
|
return {buffer, static_cast<size_t>(count)};
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
HANDLE get_console_handle()
|
||||||
|
{
|
||||||
|
return GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void set_color(const color_type color)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
SetConsoleTextAttribute(get_console_handle(), color);
|
||||||
|
#else
|
||||||
|
printf("%s", color);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool apply_color(const std::string& data, const size_t index, const color_type base_color)
|
||||||
|
{
|
||||||
|
if (data[index] != '^' || (index + 1) >= data.size())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto code = data[index + 1] - '0';
|
||||||
|
if (code < 0 || code > 11)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
code = std::min(code, 7); // Everything above white is white
|
||||||
|
if (code == 7)
|
||||||
|
{
|
||||||
|
set_color(base_color);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
set_color(color_array[code]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_colored(const std::string& line, const color_type base_color)
|
||||||
|
{
|
||||||
|
lock _{};
|
||||||
|
set_color(base_color);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < line.size(); ++i)
|
||||||
|
{
|
||||||
|
if (apply_color(line, i, base_color))
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
putchar(line[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
reset_color();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lock::lock()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
_lock_file(stdout);
|
||||||
|
#else
|
||||||
|
flockfile(stdout);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
lock::~lock()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
_unlock_file(stdout);
|
||||||
|
#else
|
||||||
|
funlockfile(stdout);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset_color()
|
||||||
|
{
|
||||||
|
lock _{};
|
||||||
|
#ifdef _WIN32
|
||||||
|
SetConsoleTextAttribute(get_console_handle(), 7);
|
||||||
|
#else
|
||||||
|
printf("\033[0m");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void info(const char* message, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, message);
|
||||||
|
|
||||||
|
const auto data = format(&ap, message);
|
||||||
|
print_colored("[+] " + data + "\n", COLOR_LOG_INFO);
|
||||||
|
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void warn(const char* message, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, message);
|
||||||
|
|
||||||
|
const auto data = format(&ap, message);
|
||||||
|
print_colored("[!] " + data + "\n", COLOR_LOG_WARN);
|
||||||
|
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void error(const char* message, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, message);
|
||||||
|
|
||||||
|
const auto data = format(&ap, message);
|
||||||
|
print_colored("[-] " + data + "\n", COLOR_LOG_ERROR);
|
||||||
|
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void log(const char* message, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, message);
|
||||||
|
|
||||||
|
const auto data = format(&ap, message);
|
||||||
|
print_colored("[*] " + data + "\n", COLOR_LOG_DEBUG);
|
||||||
|
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_title(const std::string& title)
|
||||||
|
{
|
||||||
|
lock _{};
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
SetConsoleTitleA(title.c_str());
|
||||||
|
#else
|
||||||
|
printf("\033]0;%s\007", title.c_str());
|
||||||
|
fflush(stdout);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
signal_handler::signal_handler(std::function<void()> callback)
|
||||||
|
: std::lock_guard<std::mutex>(signal_mutex)
|
||||||
|
{
|
||||||
|
signal_callback = std::move(callback);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
SetConsoleCtrlHandler(handler, TRUE);
|
||||||
|
#else
|
||||||
|
signal(SIGINT, handler);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
signal_handler::~signal_handler()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
SetConsoleCtrlHandler(handler, FALSE);
|
||||||
|
#else
|
||||||
|
signal(SIGINT, SIG_DFL);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
signal_callback = {};
|
||||||
|
}
|
||||||
|
}
|
32
src/console.hpp
Normal file
32
src/console.hpp
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace console
|
||||||
|
{
|
||||||
|
class lock
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
lock();
|
||||||
|
~lock();
|
||||||
|
|
||||||
|
lock(lock&&) = delete;
|
||||||
|
lock(const lock&) = delete;
|
||||||
|
lock& operator=(lock&&) = delete;
|
||||||
|
lock& operator=(const lock&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
void reset_color();
|
||||||
|
|
||||||
|
void info(const char* message, ...);
|
||||||
|
void warn(const char* message, ...);
|
||||||
|
void error(const char* message, ...);
|
||||||
|
void log(const char* message, ...);
|
||||||
|
|
||||||
|
void set_title(const std::string& title);
|
||||||
|
|
||||||
|
class signal_handler : std::lock_guard<std::mutex>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
signal_handler(std::function<void()> callback);
|
||||||
|
~signal_handler();
|
||||||
|
};
|
||||||
|
}
|
53
src/main.cpp
Normal file
53
src/main.cpp
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
|
||||||
|
#include "console.hpp"
|
||||||
|
|
||||||
|
#include "updater/updater.hpp"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
int unsafe_main(std::string&& prog, std::vector<std::string>&& args)
|
||||||
|
{
|
||||||
|
// Parse command-line flags (only increment i for matching flags)
|
||||||
|
for (auto i = args.begin(); i != args.end();)
|
||||||
|
{
|
||||||
|
if (*i == "-update-iw4x")
|
||||||
|
{
|
||||||
|
return updater::update_iw4x();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console::info("AlterWare Installer\n"
|
||||||
|
"Usage: %s OPTIONS\n"
|
||||||
|
" -update-iw4x\n",
|
||||||
|
prog.data()
|
||||||
|
);
|
||||||
|
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(const int argc, char* argv[])
|
||||||
|
{
|
||||||
|
console::set_title("AlterWare Installer");
|
||||||
|
console::log("AlterWare Installer");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::string prog(argv[0]);
|
||||||
|
std::vector<std::string> args;
|
||||||
|
|
||||||
|
args.reserve(argc - 1);
|
||||||
|
args.assign(argv + 1, argv + argc);
|
||||||
|
return unsafe_main(std::move(prog), std::move(args));
|
||||||
|
}
|
||||||
|
catch (const std::exception& ex)
|
||||||
|
{
|
||||||
|
console::error("Fatal error: %s", ex.what());
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
1
src/std_include.cpp
Normal file
1
src/std_include.cpp
Normal file
@ -0,0 +1 @@
|
|||||||
|
#include <std_include.hpp>
|
74
src/std_include.hpp
Normal file
74
src/std_include.hpp
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#ifdef _WIN32
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <WinSock2.h>
|
||||||
|
#include <WS2tcpip.h>
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#define ZeroMemory(x, y) std::memset(x, 0, y)
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// min and max is required by gdi, therefore NOMINMAX won't work
|
||||||
|
#ifdef max
|
||||||
|
#undef max
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef min
|
||||||
|
#undef min
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cctype>
|
||||||
|
#include <csignal>
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <ctime>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <functional>
|
||||||
|
#include <iostream>
|
||||||
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
#include <queue>
|
||||||
|
#include <ranges>
|
||||||
|
#include <regex>
|
||||||
|
#include <span>
|
||||||
|
#include <sstream>
|
||||||
|
#include <thread>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <gsl/gsl>
|
||||||
|
#include <rapidjson/document.h>
|
||||||
|
#include <rapidjson/prettywriter.h>
|
||||||
|
#include <rapidjson/stringbuffer.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
#pragma comment(lib, "ws2_32.lib")
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using namespace std::literals;
|
258
src/updater/file_updater.cpp
Normal file
258
src/updater/file_updater.cpp
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
|
||||||
|
#include <console.hpp>
|
||||||
|
|
||||||
|
#include "file_updater.hpp"
|
||||||
|
|
||||||
|
#include <utils/compression.hpp>
|
||||||
|
#include <utils/http.hpp>
|
||||||
|
#include <utils/io.hpp>
|
||||||
|
|
||||||
|
namespace updater
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::optional<std::string> get_release_tag(const std::string& release_url)
|
||||||
|
{
|
||||||
|
const auto release_info = utils::http::get_data(release_url);
|
||||||
|
if (!release_info.has_value())
|
||||||
|
{
|
||||||
|
console::warn("Could not reach remote URL \"%s\"", release_url.c_str());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
rapidjson::Document release_json{};
|
||||||
|
|
||||||
|
const rapidjson::ParseResult result = release_json.Parse(release_info.value());
|
||||||
|
if (!result || !release_json.IsObject())
|
||||||
|
{
|
||||||
|
console::error("Could not parse remote JSON response from \"%s\"", release_url.c_str());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (release_json.HasMember("tag_name") && release_json["tag_name"].IsString())
|
||||||
|
{
|
||||||
|
const auto* tag_name = release_json["tag_name"].GetString();
|
||||||
|
return tag_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
console::error("Remote JSON response from \"%s\" does not contain the data we expected", release_url.c_str());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file_updater::file_updater(std::string name, std::filesystem::path base, std::filesystem::path out_name,
|
||||||
|
std::filesystem::path version_file,
|
||||||
|
std::string remote_tag, std::string remote_download)
|
||||||
|
: name_(std::move(name))
|
||||||
|
, base_(std::move(base))
|
||||||
|
, out_name_(std::move(out_name))
|
||||||
|
, version_file_(std::move(version_file))
|
||||||
|
, remote_tag_(std::move(remote_tag))
|
||||||
|
, remote_download_(std::move(remote_download))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool file_updater::update_if_necessary() const
|
||||||
|
{
|
||||||
|
update_state update_state;
|
||||||
|
|
||||||
|
const auto local_version = this->read_local_revision_file();
|
||||||
|
if (!this->does_require_update(update_state, local_version))
|
||||||
|
{
|
||||||
|
console::log("%s does not require an update", this->name_.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
console::info("Updating %s", this->name_.c_str());
|
||||||
|
if (!this->update_file(this->remote_download_))
|
||||||
|
{
|
||||||
|
console::error("Update failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->cleanup_directories();
|
||||||
|
|
||||||
|
if (!this->deploy_files())
|
||||||
|
{
|
||||||
|
console::error("Unable to deploy files");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do this last to make sure we don't ever create a version file when something failed
|
||||||
|
this->create_version_file(update_state.latest_tag);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void file_updater::add_dir_to_clean(const std::string& dir)
|
||||||
|
{
|
||||||
|
this->cleanup_directories_.emplace_back(this->base_ / dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
void file_updater::add_file_to_skip(const std::string& file)
|
||||||
|
{
|
||||||
|
this->skip_files_.emplace_back(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string file_updater::read_local_revision_file() const
|
||||||
|
{
|
||||||
|
const std::filesystem::path revision_file_path = this->version_file_;
|
||||||
|
|
||||||
|
std::string data;
|
||||||
|
if (!utils::io::read_file(revision_file_path.string(), &data) || data.empty())
|
||||||
|
{
|
||||||
|
console::warn("Could not load \"%s\"", revision_file_path.string().c_str());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
rapidjson::Document doc{};
|
||||||
|
const rapidjson::ParseResult result = doc.Parse(data);
|
||||||
|
if (!result || !doc.IsObject())
|
||||||
|
{
|
||||||
|
console::error("Could not parse \"%s\"", revision_file_path.string().c_str());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doc.HasMember("version") || !doc["version"].IsString())
|
||||||
|
{
|
||||||
|
console::error("\"%s\" contains invalid data", revision_file_path.string().c_str());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return doc["version"].GetString();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool file_updater::does_require_update(update_state& update_state, const std::string& local_version) const
|
||||||
|
{
|
||||||
|
console::info("Fetching tags from GitHub");
|
||||||
|
|
||||||
|
const auto raw_files_tag = get_release_tag(this->remote_tag_);
|
||||||
|
if (!raw_files_tag.has_value())
|
||||||
|
{
|
||||||
|
console::warn("Failed to reach GitHub. Aborting the update");
|
||||||
|
|
||||||
|
update_state.requires_update = false;
|
||||||
|
return update_state.requires_update;
|
||||||
|
}
|
||||||
|
|
||||||
|
update_state.requires_update = local_version != raw_files_tag.value();
|
||||||
|
update_state.latest_tag = raw_files_tag.value();
|
||||||
|
|
||||||
|
console::info("Got release tag \"%s\". Requires updating: %s", raw_files_tag.value().c_str(), update_state.requires_update ? "Yes" : "No");
|
||||||
|
return update_state.requires_update;
|
||||||
|
}
|
||||||
|
|
||||||
|
void file_updater::create_version_file(const std::string& revision_version) const
|
||||||
|
{
|
||||||
|
console::info("Creating version file \"%s\". Revision is \"%s\"", this->version_file_.c_str(), revision_version.c_str());
|
||||||
|
|
||||||
|
rapidjson::Document doc{};
|
||||||
|
doc.SetObject();
|
||||||
|
|
||||||
|
doc.AddMember("version", revision_version, doc.GetAllocator());
|
||||||
|
|
||||||
|
rapidjson::StringBuffer buffer{};
|
||||||
|
rapidjson::Writer<rapidjson::StringBuffer, rapidjson::Document::EncodingType, rapidjson::ASCII<>>
|
||||||
|
writer(buffer);
|
||||||
|
doc.Accept(writer);
|
||||||
|
|
||||||
|
const std::string json(buffer.GetString(), buffer.GetLength());
|
||||||
|
if (utils::io::write_file(this->version_file_.string(), json))
|
||||||
|
{
|
||||||
|
console::info("File \"%s\" was created successfully", this->version_file_.string().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console::error("Error while writing file \"%s\"", this->version_file_.string().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool file_updater::update_file(const std::string& url) const
|
||||||
|
{
|
||||||
|
console::info("Downloading %s", url.c_str());
|
||||||
|
const auto data = utils::http::get_data(url, {});
|
||||||
|
if (!data)
|
||||||
|
{
|
||||||
|
console::error("Failed to download %s", url.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.value().empty())
|
||||||
|
{
|
||||||
|
console::error("The data buffer returned by Curl is empty");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download the files in the working directory, move them later.
|
||||||
|
const auto out_file = std::filesystem::current_path() / this->out_name_;
|
||||||
|
|
||||||
|
console::info("Writing file to \"%s\"", out_file.string().c_str());
|
||||||
|
|
||||||
|
if (!utils::io::write_file(out_file.string(), data.value(), false))
|
||||||
|
{
|
||||||
|
console::error("Error while writing file \"%s\"", out_file.string().c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console::info("Done updating file \"%s\"", out_file.string().c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a fan of using exceptions here. Once C++23 is more widespread I'd like to use <expected>
|
||||||
|
bool file_updater::deploy_files() const
|
||||||
|
{
|
||||||
|
const auto out_dir = std::filesystem::current_path() / ".out";
|
||||||
|
|
||||||
|
assert(utils::io::file_exists(this->out_name_.string()));
|
||||||
|
|
||||||
|
// Always try to cleanup
|
||||||
|
const auto _ = gsl::finally([this, &out_dir]() -> void
|
||||||
|
{
|
||||||
|
utils::io::remove_file(this->out_name_.string());
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
std::filesystem::remove_all(out_dir, ec);
|
||||||
|
});
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
utils::io::create_directory(out_dir);
|
||||||
|
utils::compression::zip::archive::decompress(this->out_name_.string(), out_dir);
|
||||||
|
}
|
||||||
|
catch (const std::exception& ex)
|
||||||
|
{
|
||||||
|
console::error("Get error \"%s\" while decompressing \"%s\"", ex.what(), this->out_name_.string().c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console::info("\"%s\" was decompressed. Removing files that must be skipped", this->out_name_.string().c_str());
|
||||||
|
this->skip_files(out_dir);
|
||||||
|
|
||||||
|
console::info("Deploying files to \"%s\"", this->base_.string().c_str());
|
||||||
|
|
||||||
|
utils::io::copy_folder(out_dir, this->base_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void file_updater::cleanup_directories() const
|
||||||
|
{
|
||||||
|
console::log("Cleaning up directories");
|
||||||
|
std::for_each(this->cleanup_directories_.begin(), this->cleanup_directories_.end(), [](const auto& dir)
|
||||||
|
{
|
||||||
|
std::error_code ec;
|
||||||
|
std::filesystem::remove_all(dir, ec);
|
||||||
|
console::log("Removed directory \"%s\"", dir.string().c_str());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void file_updater::skip_files(const std::filesystem::path& target_dir) const
|
||||||
|
{
|
||||||
|
console::log("Skipping files");
|
||||||
|
std::for_each(this->skip_files_.begin(), this->skip_files_.end(), [&target_dir](const auto& file)
|
||||||
|
{
|
||||||
|
const auto target_file = target_dir / file;
|
||||||
|
utils::io::remove_file(target_file.string());
|
||||||
|
console::log("Removed file \"%s\"", target_file.string().c_str());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
46
src/updater/file_updater.hpp
Normal file
46
src/updater/file_updater.hpp
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace updater
|
||||||
|
{
|
||||||
|
class file_updater
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
file_updater(std::string name, std::filesystem::path base, std::filesystem::path out_name, std::filesystem::path version_file, std::string remote_tag, std::string remote_download);
|
||||||
|
|
||||||
|
[[nodiscard]] bool update_if_necessary() const;
|
||||||
|
|
||||||
|
void add_dir_to_clean(const std::string& dir);
|
||||||
|
void add_file_to_skip(const std::string& file);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct update_state
|
||||||
|
{
|
||||||
|
bool requires_update = false;
|
||||||
|
std::string latest_tag;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string name_;
|
||||||
|
|
||||||
|
std::filesystem::path base_;
|
||||||
|
std::filesystem::path out_name_;
|
||||||
|
std::filesystem::path version_file_;
|
||||||
|
|
||||||
|
std::string remote_tag_;
|
||||||
|
std::string remote_download_;
|
||||||
|
|
||||||
|
// Directories to cleanup
|
||||||
|
std::vector<std::filesystem::path> cleanup_directories_;
|
||||||
|
|
||||||
|
// Files to skip
|
||||||
|
std::vector<std::string> skip_files_;
|
||||||
|
|
||||||
|
[[nodiscard]] std::string read_local_revision_file() const;
|
||||||
|
[[nodiscard]] bool does_require_update(update_state& update_state, const std::string& local_version) const;
|
||||||
|
void create_version_file(const std::string& revision_version) const;
|
||||||
|
[[nodiscard]] bool update_file(const std::string& url) const;
|
||||||
|
[[nodiscard]] bool deploy_files() const;
|
||||||
|
|
||||||
|
void cleanup_directories() const;
|
||||||
|
void skip_files(const std::filesystem::path& target_dir) const;
|
||||||
|
};
|
||||||
|
}
|
37
src/updater/updater.cpp
Normal file
37
src/updater/updater.cpp
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
|
||||||
|
#include <console.hpp>
|
||||||
|
|
||||||
|
#include "file_updater.hpp"
|
||||||
|
#include "updater.hpp"
|
||||||
|
|
||||||
|
#include <utils/properties.hpp>
|
||||||
|
|
||||||
|
#define IW4X_VERSION_FILE "iw4x-version.json"
|
||||||
|
#define IW4X_RAW_FILES_UPDATE_FILE "release.zip"
|
||||||
|
#define IW4X_RAW_FILES_UPDATE_URL "https://github.com/iw4x/iw4x-rawfiles/releases/latest/download/" IW4X_RAW_FILES_UPDATE_FILE
|
||||||
|
#define IW4X_RAW_FILES_TAGS "https://api.github.com/repos/iw4x/iw4x-rawfiles/releases/latest"
|
||||||
|
|
||||||
|
namespace updater
|
||||||
|
{
|
||||||
|
int update_iw4x()
|
||||||
|
{
|
||||||
|
const auto iw4_install = utils::properties::load("iw4-install");
|
||||||
|
if (!iw4_install)
|
||||||
|
{
|
||||||
|
console::error("Failed to load the properties file");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& base = iw4_install.value();
|
||||||
|
|
||||||
|
file_updater file_updater{ "IW4x", base, IW4X_RAW_FILES_UPDATE_FILE, IW4X_VERSION_FILE, IW4X_RAW_FILES_TAGS, IW4X_RAW_FILES_UPDATE_URL };
|
||||||
|
|
||||||
|
file_updater.add_dir_to_clean("iw4x");
|
||||||
|
file_updater.add_dir_to_clean("zone");
|
||||||
|
|
||||||
|
file_updater.add_file_to_skip("iw4sp.exe");
|
||||||
|
|
||||||
|
return file_updater.update_if_necessary();
|
||||||
|
}
|
||||||
|
}
|
6
src/updater/updater.hpp
Normal file
6
src/updater/updater.hpp
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace updater
|
||||||
|
{
|
||||||
|
int update_iw4x();
|
||||||
|
}
|
280
src/utils/compression.cpp
Normal file
280
src/utils/compression.cpp
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
|
||||||
|
#include "compression.hpp"
|
||||||
|
|
||||||
|
#include <unzip.h>
|
||||||
|
|
||||||
|
#include <zlib.h>
|
||||||
|
#include <zip.h>
|
||||||
|
|
||||||
|
#include <gsl/gsl>
|
||||||
|
|
||||||
|
#include "io.hpp"
|
||||||
|
#include "string.hpp"
|
||||||
|
|
||||||
|
#ifndef MAX_PATH
|
||||||
|
#define MAX_PATH 256
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace utils::compression
|
||||||
|
{
|
||||||
|
namespace zlib
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
class zlib_stream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
zlib_stream()
|
||||||
|
{
|
||||||
|
memset(&stream_, 0, sizeof(stream_));
|
||||||
|
valid_ = inflateInit(&stream_) == Z_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
zlib_stream(zlib_stream&&) = delete;
|
||||||
|
zlib_stream(const zlib_stream&) = delete;
|
||||||
|
zlib_stream& operator=(zlib_stream&&) = delete;
|
||||||
|
zlib_stream& operator=(const zlib_stream&) = delete;
|
||||||
|
|
||||||
|
~zlib_stream()
|
||||||
|
{
|
||||||
|
if (valid_)
|
||||||
|
{
|
||||||
|
inflateEnd(&stream_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
z_stream& get()
|
||||||
|
{
|
||||||
|
return stream_; //
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_valid() const
|
||||||
|
{
|
||||||
|
return valid_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool valid_{false};
|
||||||
|
z_stream stream_{};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string decompress(const std::string& data)
|
||||||
|
{
|
||||||
|
std::string buffer{};
|
||||||
|
zlib_stream stream_container{};
|
||||||
|
if (!stream_container.is_valid())
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret{};
|
||||||
|
size_t offset = 0;
|
||||||
|
static thread_local uint8_t dest[CHUNK] = {0};
|
||||||
|
auto& stream = stream_container.get();
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
const auto input_size = std::min(sizeof(dest), data.size() - offset);
|
||||||
|
stream.avail_in = static_cast<uInt>(input_size);
|
||||||
|
stream.next_in = reinterpret_cast<const Bytef*>(data.data()) + offset;
|
||||||
|
offset += stream.avail_in;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
stream.avail_out = sizeof(dest);
|
||||||
|
stream.next_out = dest;
|
||||||
|
|
||||||
|
ret = inflate(&stream, Z_NO_FLUSH);
|
||||||
|
if (ret != Z_OK && ret != Z_STREAM_END)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.insert(buffer.end(), dest, dest + sizeof(dest) - stream.avail_out);
|
||||||
|
}
|
||||||
|
while (stream.avail_out == 0);
|
||||||
|
}
|
||||||
|
while (ret != Z_STREAM_END);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string compress(const std::string& data)
|
||||||
|
{
|
||||||
|
std::string result{};
|
||||||
|
auto length = compressBound(static_cast<uLong>(data.size()));
|
||||||
|
result.resize(length);
|
||||||
|
|
||||||
|
if (compress2(reinterpret_cast<Bytef*>(result.data()), &length,
|
||||||
|
reinterpret_cast<const Bytef*>(data.data()), static_cast<uLong>(data.size()),
|
||||||
|
Z_BEST_COMPRESSION) != Z_OK)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
result.resize(length);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace zip
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
bool add_file(zipFile& zip_file, const std::string& filename, const std::string& data)
|
||||||
|
{
|
||||||
|
const auto zip_64 = data.size() > 0xffffffff ? 1 : 0;
|
||||||
|
if (ZIP_OK != zipOpenNewFileInZip64(zip_file, filename.c_str(), nullptr, nullptr, 0, nullptr, 0, nullptr,
|
||||||
|
Z_DEFLATED, Z_BEST_COMPRESSION, zip_64))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto _ = gsl::finally([&zip_file]() -> void
|
||||||
|
{
|
||||||
|
zipCloseFileInZip(zip_file);
|
||||||
|
});
|
||||||
|
|
||||||
|
return ZIP_OK == zipWriteInFileInZip(zip_file, data.c_str(), static_cast<unsigned>(data.size()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void archive::add(const std::string& filename, const std::string& data)
|
||||||
|
{
|
||||||
|
this->files_[filename] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool archive::write(const std::string& filename, const std::string& comment)
|
||||||
|
{
|
||||||
|
// Hack to create the directory :3
|
||||||
|
io::write_file(filename, {});
|
||||||
|
io::remove_file(filename);
|
||||||
|
|
||||||
|
auto* zip_file = zipOpen64(filename.c_str(), false);
|
||||||
|
if (!zip_file)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto _ = gsl::finally([&zip_file, &comment]() -> void
|
||||||
|
{
|
||||||
|
zipClose(zip_file, comment.empty() ? nullptr : comment.c_str());
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const auto& file : this->files_)
|
||||||
|
{
|
||||||
|
if (!add_file(zip_file, file.first, file.second))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// I apologize for writing such a huge function
|
||||||
|
void archive::decompress(const std::string& filename, const std::filesystem::path& out_dir)
|
||||||
|
{
|
||||||
|
unzFile file = unzOpen(filename.c_str());
|
||||||
|
if (!file)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(string::va("unzOpen failed on %s", filename.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
unz_global_info global_info;
|
||||||
|
if (unzGetGlobalInfo(file, &global_info) != UNZ_OK)
|
||||||
|
{
|
||||||
|
unzClose(file);
|
||||||
|
throw std::runtime_error(string::va("unzGetGlobalInfo failed on %s", filename.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr std::size_t READ_BUFFER_SIZE = 65336;
|
||||||
|
const auto read_buffer_large = std::make_unique<char[]>(READ_BUFFER_SIZE);
|
||||||
|
// No need to memset this to 0
|
||||||
|
auto* read_buffer = read_buffer_large.get();
|
||||||
|
|
||||||
|
// Loop to extract all the files
|
||||||
|
for (uLong i = 0; i < global_info.number_entry; ++i)
|
||||||
|
{
|
||||||
|
// Get info about the current file.
|
||||||
|
unz_file_info file_info;
|
||||||
|
char filename_buffer[MAX_PATH]{};
|
||||||
|
|
||||||
|
if (unzGetCurrentFileInfo(file, &file_info, filename_buffer, sizeof(filename_buffer) - 1,
|
||||||
|
nullptr, 0, nullptr, 0) != UNZ_OK)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this entry is a directory or a file.
|
||||||
|
std::string out_file = filename_buffer;
|
||||||
|
// Fix for UNIX Systems
|
||||||
|
std::replace(out_file.begin(), out_file.end(), '\\', '/');
|
||||||
|
|
||||||
|
const auto filename_length = out_file.size();
|
||||||
|
if (out_file[filename_length - 1] == '/') // ZIP is not directory-separator-agnostic
|
||||||
|
{
|
||||||
|
// Entry is a directory. Create it.
|
||||||
|
const auto dir = out_dir / out_file;
|
||||||
|
io::create_directory(dir);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Entry is a file. Extract it.
|
||||||
|
if (unzOpenCurrentFile(file) != UNZ_OK)
|
||||||
|
{
|
||||||
|
// Could not read file from the ZIP
|
||||||
|
throw std::runtime_error(string::va("Failed to read file \"%s\" from \"%s\"", out_file.c_str(), filename.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto path = out_dir / out_file;
|
||||||
|
// Must create any directories before opening a stream
|
||||||
|
io::create_directory(path.parent_path());
|
||||||
|
|
||||||
|
// Open a stream to write out the data.
|
||||||
|
std::ofstream out(path.string(), std::ios::binary | std::ios::trunc);
|
||||||
|
if (!out.is_open())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to open stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto read_bytes = UNZ_OK;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
read_bytes = unzReadCurrentFile(file, read_buffer, READ_BUFFER_SIZE);
|
||||||
|
if (read_bytes < 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(string::va("Error while reading \"%s\" from the archive", out_file.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (read_bytes > 0)
|
||||||
|
{
|
||||||
|
out.write(read_buffer, read_bytes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No more data to read, the loop will break
|
||||||
|
// This is normal behaviour
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go the the next entry listed in the ZIP file.
|
||||||
|
if ((i + 1) < global_info.number_entry)
|
||||||
|
{
|
||||||
|
if (unzGoToNextFile(file) != UNZ_OK)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unzClose(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
src/utils/compression.hpp
Normal file
31
src/utils/compression.hpp
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#define CHUNK 16384u
|
||||||
|
|
||||||
|
namespace utils::compression
|
||||||
|
{
|
||||||
|
namespace zlib
|
||||||
|
{
|
||||||
|
std::string compress(const std::string& data);
|
||||||
|
std::string decompress(const std::string& data);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace zip
|
||||||
|
{
|
||||||
|
class archive
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void add(const std::string& filename, const std::string& data);
|
||||||
|
[[nodiscard]] bool write(const std::string& filename, const std::string& comment = {});
|
||||||
|
|
||||||
|
static void decompress(const std::string& filename, const std::filesystem::path& out_dir);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<std::string, std::string> files_;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
65
src/utils/http.cpp
Normal file
65
src/utils/http.cpp
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
|
||||||
|
#include "http.hpp"
|
||||||
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
namespace utils::http
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
size_t write_callback(void* contents, const size_t size, const size_t nmemb, void* userp)
|
||||||
|
{
|
||||||
|
static_cast<std::string*>(userp)->append(static_cast<char*>(contents), size * nmemb);
|
||||||
|
return size * nmemb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> get_data(const std::string& url, const headers& headers)
|
||||||
|
{
|
||||||
|
curl_slist* header_list = nullptr;
|
||||||
|
auto* curl = curl_easy_init();
|
||||||
|
if (!curl)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto _ = gsl::finally([&]()
|
||||||
|
{
|
||||||
|
curl_slist_free_all(header_list);
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
for(const auto& header : headers)
|
||||||
|
{
|
||||||
|
auto data = header.first + ": "s + header.second;
|
||||||
|
header_list = curl_slist_append(header_list, data.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string buffer{};
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, "aw-installer/1.0");
|
||||||
|
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||||
|
|
||||||
|
if (curl_easy_perform(curl) == CURLE_OK)
|
||||||
|
{
|
||||||
|
return {std::move(buffer)};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::future<std::optional<std::string>> get_data_async(const std::string& url, const headers& headers)
|
||||||
|
{
|
||||||
|
return std::async(std::launch::async, [url, headers]() -> std::optional<std::string>
|
||||||
|
{
|
||||||
|
return get_data(url, headers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
14
src/utils/http.hpp
Normal file
14
src/utils/http.hpp
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <future>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace utils::http
|
||||||
|
{
|
||||||
|
using headers = std::unordered_map<std::string, std::string>;
|
||||||
|
|
||||||
|
std::optional<std::string> get_data(const std::string& url, const headers& headers = {});
|
||||||
|
std::future<std::optional<std::string>> get_data_async(const std::string& url, const headers& headers = {});
|
||||||
|
}
|
136
src/utils/io.cpp
Normal file
136
src/utils/io.cpp
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
|
||||||
|
#include "io.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
#include <ios>
|
||||||
|
|
||||||
|
namespace utils::io
|
||||||
|
{
|
||||||
|
bool remove_file(const std::string& file)
|
||||||
|
{
|
||||||
|
return remove(file.c_str()) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool move_file(const std::string& src, const std::string& target)
|
||||||
|
{
|
||||||
|
return rename(src.c_str(), target.c_str()) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool file_exists(const std::string& file)
|
||||||
|
{
|
||||||
|
return std::ifstream(file).good();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool write_file(const std::string& file, const std::string& data, const bool append)
|
||||||
|
{
|
||||||
|
const auto pos = file.find_last_of("/\\");
|
||||||
|
if (pos != std::string::npos)
|
||||||
|
{
|
||||||
|
create_directory(file.substr(0, pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mode = std::ios::binary | std::ofstream::out;
|
||||||
|
if (append)
|
||||||
|
{
|
||||||
|
mode |= std::ofstream::app;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream stream(file, mode);
|
||||||
|
|
||||||
|
if (stream.is_open())
|
||||||
|
{
|
||||||
|
stream.write(data.data(), static_cast<std::streamsize>(data.size()));
|
||||||
|
stream.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string read_file(const std::string& file)
|
||||||
|
{
|
||||||
|
std::string data;
|
||||||
|
read_file(file, &data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool read_file(const std::string& file, std::string* data)
|
||||||
|
{
|
||||||
|
if (!data) return false;
|
||||||
|
data->clear();
|
||||||
|
|
||||||
|
if (file_exists(file))
|
||||||
|
{
|
||||||
|
std::ifstream stream(file, std::ios::binary);
|
||||||
|
if (!stream.is_open()) return false;
|
||||||
|
|
||||||
|
stream.seekg(0, std::ios::end);
|
||||||
|
const std::streamsize size = stream.tellg();
|
||||||
|
stream.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
if (size > -1)
|
||||||
|
{
|
||||||
|
data->resize(static_cast<uint32_t>(size));
|
||||||
|
stream.read(const_cast<char*>(data->data()), size);
|
||||||
|
stream.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t file_size(const std::string& file)
|
||||||
|
{
|
||||||
|
if (file_exists(file))
|
||||||
|
{
|
||||||
|
std::ifstream stream(file, std::ios::binary);
|
||||||
|
|
||||||
|
if (stream.good())
|
||||||
|
{
|
||||||
|
stream.seekg(0, std::ios::end);
|
||||||
|
return static_cast<std::size_t>(stream.tellg());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool create_directory(const std::filesystem::path& directory)
|
||||||
|
{
|
||||||
|
std::error_code ec;
|
||||||
|
return std::filesystem::create_directories(directory, ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool directory_exists(const std::filesystem::path& directory)
|
||||||
|
{
|
||||||
|
std::error_code ec;
|
||||||
|
return std::filesystem::is_directory(directory, ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool directory_is_empty(const std::filesystem::path& directory)
|
||||||
|
{
|
||||||
|
std::error_code ec;
|
||||||
|
return std::filesystem::is_empty(directory, ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> list_files(const std::filesystem::path& directory)
|
||||||
|
{
|
||||||
|
std::vector<std::string> files;
|
||||||
|
|
||||||
|
for (auto& file : std::filesystem::directory_iterator(directory))
|
||||||
|
{
|
||||||
|
files.push_back(file.path().generic_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target)
|
||||||
|
{
|
||||||
|
std::error_code ec;
|
||||||
|
std::filesystem::copy(src, target,
|
||||||
|
std::filesystem::copy_options::overwrite_existing |
|
||||||
|
std::filesystem::copy_options::recursive, ec);
|
||||||
|
}
|
||||||
|
}
|
21
src/utils/io.hpp
Normal file
21
src/utils/io.hpp
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace utils::io
|
||||||
|
{
|
||||||
|
bool remove_file(const std::string& file);
|
||||||
|
bool move_file(const std::string& src, const std::string& target);
|
||||||
|
bool file_exists(const std::string& file);
|
||||||
|
bool write_file(const std::string& file, const std::string& data, bool append = false);
|
||||||
|
bool read_file(const std::string& file, std::string* data);
|
||||||
|
std::string read_file(const std::string& file);
|
||||||
|
std::size_t file_size(const std::string& file);
|
||||||
|
bool create_directory(const std::filesystem::path& directory);
|
||||||
|
bool directory_exists(const std::filesystem::path& directory);
|
||||||
|
bool directory_is_empty(const std::filesystem::path& directory);
|
||||||
|
std::vector<std::string> list_files(const std::filesystem::path& directory);
|
||||||
|
void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target);
|
||||||
|
}
|
109
src/utils/memory.cpp
Normal file
109
src/utils/memory.cpp
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
|
||||||
|
#include "memory.hpp"
|
||||||
|
|
||||||
|
namespace utils
|
||||||
|
{
|
||||||
|
memory::allocator memory::mem_allocator_;
|
||||||
|
|
||||||
|
memory::allocator::~allocator()
|
||||||
|
{
|
||||||
|
this->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void memory::allocator::clear()
|
||||||
|
{
|
||||||
|
std::lock_guard _(this->mutex_);
|
||||||
|
|
||||||
|
for (auto& data : this->pool_)
|
||||||
|
{
|
||||||
|
memory::free(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->pool_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void memory::allocator::free(void* data)
|
||||||
|
{
|
||||||
|
std::lock_guard _(this->mutex_);
|
||||||
|
|
||||||
|
const auto j = std::find(this->pool_.begin(), this->pool_.end(), data);
|
||||||
|
if (j != this->pool_.end())
|
||||||
|
{
|
||||||
|
memory::free(data);
|
||||||
|
this->pool_.erase(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void memory::allocator::free(const void* data)
|
||||||
|
{
|
||||||
|
this->free(const_cast<void*>(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
void* memory::allocator::allocate(const std::size_t length)
|
||||||
|
{
|
||||||
|
std::lock_guard _(this->mutex_);
|
||||||
|
|
||||||
|
const auto data = memory::allocate(length);
|
||||||
|
this->pool_.push_back(data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool memory::allocator::empty() const
|
||||||
|
{
|
||||||
|
return this->pool_.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
char* memory::allocator::duplicate_string(const std::string& string)
|
||||||
|
{
|
||||||
|
std::lock_guard _(this->mutex_);
|
||||||
|
|
||||||
|
const auto data = memory::duplicate_string(string);
|
||||||
|
this->pool_.push_back(data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* memory::allocate(const std::size_t length)
|
||||||
|
{
|
||||||
|
auto* buf = std::malloc(length);
|
||||||
|
std::memset(buf, 0, length);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* memory::duplicate_string(const std::string& string)
|
||||||
|
{
|
||||||
|
const auto new_string = allocate_array<char>(string.size() + 1);
|
||||||
|
std::memcpy(new_string, string.c_str(), string.size());
|
||||||
|
return new_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
void memory::free(void* data)
|
||||||
|
{
|
||||||
|
::free(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void memory::free(const void* data)
|
||||||
|
{
|
||||||
|
free(const_cast<void*>(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool memory::is_set(const void* mem, const char chr, const std::size_t length)
|
||||||
|
{
|
||||||
|
auto* const mem_arr = static_cast<const char*>(mem);
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < length; ++i)
|
||||||
|
{
|
||||||
|
if (mem_arr[i] != chr)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
memory::allocator* memory::get_allocator()
|
||||||
|
{
|
||||||
|
return &memory::mem_allocator_;
|
||||||
|
}
|
||||||
|
}
|
71
src/utils/memory.hpp
Normal file
71
src/utils/memory.hpp
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace utils
|
||||||
|
{
|
||||||
|
class memory final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
class allocator final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
~allocator();
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
void free(void* data);
|
||||||
|
|
||||||
|
void free(const void* data);
|
||||||
|
|
||||||
|
void* allocate(std::size_t length);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T* allocate()
|
||||||
|
{
|
||||||
|
return this->allocate_array<T>(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T* allocate_array(const std::size_t count = 1)
|
||||||
|
{
|
||||||
|
return static_cast<T*>(this->allocate(count * sizeof(T)));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty() const;
|
||||||
|
|
||||||
|
char* duplicate_string(const std::string& string);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::mutex mutex_;
|
||||||
|
std::vector<void*> pool_;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void* allocate(std::size_t length);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static T* allocate()
|
||||||
|
{
|
||||||
|
return allocate_array<T>(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static T* allocate_array(const std::size_t count = 1)
|
||||||
|
{
|
||||||
|
return static_cast<T*>(allocate(count * sizeof(T)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* duplicate_string(const std::string& string);
|
||||||
|
|
||||||
|
static void free(void* data);
|
||||||
|
static void free(const void* data);
|
||||||
|
|
||||||
|
static bool is_set(const void* mem, char chr, std::size_t length);
|
||||||
|
|
||||||
|
static allocator* get_allocator();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static allocator mem_allocator_;
|
||||||
|
};
|
||||||
|
}
|
94
src/utils/properties.cpp
Normal file
94
src/utils/properties.cpp
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
|
||||||
|
#include "io.hpp"
|
||||||
|
#include "properties.hpp"
|
||||||
|
|
||||||
|
#include <rapidjson/document.h>
|
||||||
|
#include <rapidjson/prettywriter.h>
|
||||||
|
#include <rapidjson/stringbuffer.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace utils::properties
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::string get_properties_file()
|
||||||
|
{
|
||||||
|
return "properties.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
rapidjson::Document load_properties()
|
||||||
|
{
|
||||||
|
rapidjson::Document default_doc{};
|
||||||
|
default_doc.SetObject();
|
||||||
|
|
||||||
|
std::string data{};
|
||||||
|
const auto& props = get_properties_file();
|
||||||
|
if (!io::read_file(props, &data))
|
||||||
|
{
|
||||||
|
return default_doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
rapidjson::Document doc{};
|
||||||
|
const rapidjson::ParseResult result = doc.Parse(data);
|
||||||
|
|
||||||
|
if (!result || !doc.IsObject())
|
||||||
|
{
|
||||||
|
return default_doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void store_properties(const rapidjson::Document& doc)
|
||||||
|
{
|
||||||
|
rapidjson::StringBuffer buffer{};
|
||||||
|
rapidjson::Writer<rapidjson::StringBuffer, rapidjson::Document::EncodingType, rapidjson::ASCII<>>
|
||||||
|
writer(buffer);
|
||||||
|
doc.Accept(writer);
|
||||||
|
|
||||||
|
const std::string json{ buffer.GetString(), buffer.GetLength() };
|
||||||
|
|
||||||
|
const auto& props = get_properties_file();
|
||||||
|
io::write_file(props, json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> load(const std::string& name)
|
||||||
|
{
|
||||||
|
const auto doc = load_properties();
|
||||||
|
|
||||||
|
if (!doc.HasMember(name))
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& value = doc[name];
|
||||||
|
if (!value.IsString())
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { std::string{ value.GetString() } };
|
||||||
|
}
|
||||||
|
|
||||||
|
void store(const std::string& name, const std::string& value)
|
||||||
|
{
|
||||||
|
auto doc = load_properties();
|
||||||
|
|
||||||
|
while (doc.HasMember(name))
|
||||||
|
{
|
||||||
|
doc.RemoveMember(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
rapidjson::Value key{};
|
||||||
|
key.SetString(name, doc.GetAllocator());
|
||||||
|
|
||||||
|
rapidjson::Value member{};
|
||||||
|
member.SetString(value, doc.GetAllocator());
|
||||||
|
|
||||||
|
doc.AddMember(key, member, doc.GetAllocator());
|
||||||
|
|
||||||
|
store_properties(doc);
|
||||||
|
}
|
||||||
|
}
|
10
src/utils/properties.hpp
Normal file
10
src/utils/properties.hpp
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace utils::properties
|
||||||
|
{
|
||||||
|
std::optional<std::string> load(const std::string& name);
|
||||||
|
void store(const std::string& name, const std::string& value);
|
||||||
|
}
|
102
src/utils/string.cpp
Normal file
102
src/utils/string.cpp
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
|
||||||
|
#include "string.hpp"
|
||||||
|
#include <sstream>
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace utils::string
|
||||||
|
{
|
||||||
|
const char* va(const char* fmt, ...)
|
||||||
|
{
|
||||||
|
static thread_local va_provider<8, 256> provider;
|
||||||
|
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
|
||||||
|
const char* result = provider.get(fmt, ap);
|
||||||
|
|
||||||
|
va_end(ap);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> split(const std::string& s, const char delim)
|
||||||
|
{
|
||||||
|
std::stringstream ss(s);
|
||||||
|
std::string item;
|
||||||
|
std::vector<std::string> elems;
|
||||||
|
|
||||||
|
while (std::getline(ss, item, delim))
|
||||||
|
{
|
||||||
|
elems.push_back(std::move(item));
|
||||||
|
item = std::string{};
|
||||||
|
}
|
||||||
|
|
||||||
|
return elems;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string to_lower(std::string text)
|
||||||
|
{
|
||||||
|
std::transform(text.begin(), text.end(), text.begin(), [](const unsigned char input)
|
||||||
|
{
|
||||||
|
return static_cast<char>(tolower(input));
|
||||||
|
});
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string to_upper(std::string text)
|
||||||
|
{
|
||||||
|
std::transform(text.begin(), text.end(), text.begin(), [](const unsigned char input)
|
||||||
|
{
|
||||||
|
return static_cast<char>(toupper(input));
|
||||||
|
});
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool starts_with(const std::string& text, const std::string& substring)
|
||||||
|
{
|
||||||
|
return text.find(substring) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ends_with(const std::string& text, const std::string& substring)
|
||||||
|
{
|
||||||
|
if (substring.size() > text.size()) return false;
|
||||||
|
return std::equal(substring.rbegin(), substring.rend(), text.rbegin());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string dump_hex(const std::string& data, const std::string& separator)
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < data.size(); ++i)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
result.append(separator);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.append(va("%02X", data[i] & 0xFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string replace(std::string str, const std::string& from, const std::string& to)
|
||||||
|
{
|
||||||
|
if (from.empty())
|
||||||
|
{
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t start_pos = 0;
|
||||||
|
while ((start_pos = str.find(from, start_pos)) != std::string::npos)
|
||||||
|
{
|
||||||
|
str.replace(start_pos, from.length(), to);
|
||||||
|
start_pos += to.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
97
src/utils/string.hpp
Normal file
97
src/utils/string.hpp
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "memory.hpp"
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
template <class Type, std::size_t n>
|
||||||
|
constexpr std::size_t ARRAY_COUNT(Type(&)[n]) { return n; }
|
||||||
|
|
||||||
|
namespace utils::string
|
||||||
|
{
|
||||||
|
template <std::size_t buffers, std::size_t min_buffer_size>
|
||||||
|
class va_provider final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static_assert(buffers != 0 && min_buffer_size != 0, "buffers and min_buffer_size mustn't be 0");
|
||||||
|
|
||||||
|
va_provider() : current_buffer_(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
char* get(const char* format, va_list ap)
|
||||||
|
{
|
||||||
|
++this->current_buffer_ %= ARRAY_COUNT(this->string_pool_);
|
||||||
|
auto entry = &this->string_pool_[this->current_buffer_];
|
||||||
|
|
||||||
|
if (!entry->size_ || !entry->buffer_)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("String pool not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
const auto res = vsnprintf_s(entry->buffer_, entry->size_, _TRUNCATE, format, ap);
|
||||||
|
#else
|
||||||
|
const auto res = vsnprintf(entry->buffer_, entry->size_, format, ap);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (res > 0) break; // Success
|
||||||
|
if (res == 0) return nullptr; // Error
|
||||||
|
|
||||||
|
entry->double_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry->buffer_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
class entry final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit entry(const std::size_t size = min_buffer_size) : size_(size), buffer_(nullptr)
|
||||||
|
{
|
||||||
|
if (this->size_ < min_buffer_size) this->size_ = min_buffer_size;
|
||||||
|
this->allocate();
|
||||||
|
}
|
||||||
|
|
||||||
|
~entry()
|
||||||
|
{
|
||||||
|
if (this->buffer_) memory::get_allocator()->free(this->buffer_);
|
||||||
|
this->size_ = 0;
|
||||||
|
this->buffer_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void allocate()
|
||||||
|
{
|
||||||
|
if (this->buffer_) memory::get_allocator()->free(this->buffer_);
|
||||||
|
this->buffer_ = memory::get_allocator()->allocate_array<char>(this->size_ + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void double_size()
|
||||||
|
{
|
||||||
|
this->size_ *= 2;
|
||||||
|
this->allocate();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t size_;
|
||||||
|
char* buffer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::size_t current_buffer_;
|
||||||
|
entry string_pool_[buffers];
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* va(const char* fmt, ...);
|
||||||
|
|
||||||
|
std::vector<std::string> split(const std::string& s, char delim);
|
||||||
|
|
||||||
|
std::string to_lower(std::string text);
|
||||||
|
std::string to_upper(std::string text);
|
||||||
|
|
||||||
|
bool starts_with(const std::string& text, const std::string& substring);
|
||||||
|
bool ends_with(const std::string& text, const std::string& substring);
|
||||||
|
|
||||||
|
std::string dump_hex(const std::string& data, const std::string& separator = " ");
|
||||||
|
|
||||||
|
std::string replace(std::string str, const std::string& from, const std::string& to);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user